Conversation
ADR-0008 commits to directory layout (`agents/<name>/agent.md`, `commands/<name>/command.md`) for vendor overrides at `<type>/<name>/overrides/<vendor>.<ext>`. PR-B shipped install only for skills because agents/commands canonical was flat (`<name>.md`) — no place for an `overrides/` subdir without breaking fan-out. PR-C[1/3] closes the gap without migrating user files: * `mm context install` accepts `skill | agent | command` (was `skill` only). `install_agent` / `install_command` are thin wrappers around the existing `_install_asset` pipeline. * `list_canonical_agents` / `list_canonical_commands` return `list[tuple[Path, Layout]]` and enumerate both flat (legacy) and dir layouts. Dir wins on collision; both-present logs a WARNING. * `parse_canonical_*` takes `layout=` and uses `path.parent.name` for dir form (avoids the brittle `path.stem == "agent"` heuristic). * `extract_*_to_canonical` preserves the existing layout per name — flat stays flat, dir stays dir, new agents land in dir layout per ADR. Dir+flat coexist case emits a separate WARNING about silent flat divergence; `mm context migrate` (PR-D) will be the supported consolidation path. * `InstallResult.asset_type` widened from `Literal["skills"]` to the three-kind union; the cast at the construction site widens with it. A byte-identical regression test (`test_context_override.py`) is included as a guard for PR-B fan-out semantics: with no overrides present, every vendor's SKILL.md MUST equal canonical byte-for-byte. PR-C will only ever diverge that equality when a real override file is staged (commit 2/3). Web routes (`context_agents.py`, `context_commands.py`) updated to unpack the layout-tagged tuples and to derive canonical names layout-aware in import responses. Co-Authored-By: Claude <[email protected]>
Adds the override layer described by ADR-0008 Invariant 4: ``<project>/.memtomem/<type>/<name>/overrides/<vendor>.<ext>``, when present, byte-replaces that vendor's runtime artifact and skips auto-conversion. PR-C[2/3] activates this for skills only — the ``OVERRIDE_FORMATS`` matrix ships all 9 ``(asset_type, vendor)`` cells but the resolver gates non-skills with ``_PR_C_ACTIVE_TYPES``; flip the gate in a follow-up PR to activate agents/commands. * `_names.py`: ship `OVERRIDE_FORMATS` (9-cell, ADR-pinned location) and `GENERATOR_VENDOR` (`<vendor>_<asset_type>` → vendor) so fan-out and PR-D's lint/status share one source of truth. * `context/override.py`: new module with `resolve(project_root, type, name, vendor) -> Path | None`. Reads ONLY from project tree per Invariant 1 — the docstring explicitly forbids a wiki argument so future-self does not accidentally re-couple sync to the wiki. * `skills.py`: per-vendor hook after `copy_skill` that replaces the vendor's `SKILL.md` only. Auxiliary files (`scripts/`, `references/`) stay from canonical (Invariant 4 says ``byte-copy that file`` — singular). Tests in `test_context_override.py`: * `test_resolve_*` — happy path, no-override, unknown vendor, the Invariant 1 no-wiki guarantee, and explicit gate tests for agents and commands so the enable-day diff is one-line revertable. * `test_skills_fanout_applies_claude_override_only` — staged override per vendor diverges only that vendor's bytes; others stay canonical. * `test_skills_fanout_applies_all_three_overrides` — full triplet. * `test_override_only_touches_skill_md_not_scripts` — auxiliary files remain canonical even when an override is present. The pre-existing byte-identical regression test (added in commit 1/3) still passes — with no overrides on disk every vendor's SKILL.md still equals canonical byte-for-byte. Co-Authored-By: Claude <[email protected]>
Adds the user-facing surface for ADR-0008 PR-C[3/3]: a single command that seeds ``<wiki>/skills/<name>/overrides/<vendor>.md`` from the canonical SKILL.md so the user's edit starts from a working baseline rather than a blank file. * `wiki/override.py`: `seed_override(store, type, name, vendor, *, force)` writes the override file (and a `.bak` sibling on `--force`). `render_seed_bytes` returns canonical bytes for skills; agents/commands raise `NotImplementedError` matching the resolver gate in `context/override.py`. * `cli/wiki_cmd.py`: `mm wiki skill override <name> --vendor <V>` with `--force` (overwrite + .bak) and `--editor` (open `$EDITOR` after seeding). Exit-code-bearing classified errors for missing wiki / missing skill / collision; no traceback leaks. Stdout prints three things — the human "Seeded …" line (substring contract), the bare absolute path on its own line for shell capture, and a commit hint that names the exact `git add` / `git commit` pair to run inside the wiki repo. * `agent` and `command` subgroups are intentionally NOT stubbed — click's "no such command" UX is acceptable v1; PR-D adds them alongside `diff`/`lint`/`edit`. Tests cover happy path, refuse + force + .bak, unknown vendor, missing skill, missing wiki, editor on/off, and the stdout contract (substring/presence — no strict line ordering so future UX polish does not churn the test). Co-Authored-By: Claude <[email protected]>
* `_names.py`: hoist `Layout = Literal["flat", "dir"]` here so agents.py and commands.py share one type definition rather than cross-importing. * `agents.py` / `commands.py`: introduce `canonical_agent_name(path, layout)` / `canonical_command_name(path, layout)` as the single source of truth for path → name dispatch. Widen `ExtractResult.imported` from `list[Path]` to `list[tuple[Path, Layout]]` so the layout tag flows through to consumers; the previously-unused `_layout` from `_resolve_*_extract_target` is now used (drops the YAGNI complaint). * `web/routes/context_agents.py` + `context_commands.py`: drop the brittle ``path.name == "agent.md"`` heuristic in import responses. List + import handlers both go through the helper now; same dispatch shape across the surface, single change point if the layout semantics ever shift. * `wiki/override.py`: pre-flight `render_seed_bytes` (which does the source check) BEFORE `target.parent.mkdir(parents=True)` so a refused call (missing wiki / missing canonical / collision without ``--force``) does not leave a half-built ``overrides/`` directory in the wiki tree. Rename the unused `vendor` parameter on `render_seed_bytes` to `_vendor` (conventional unused-arg signal), drop the `del vendor` line. * `docs/adr/0008-wiki-layer.md`: update the Status header to reflect PR-A + PR-B merged and PR-C in flight; expand the PR-C/D table rows to mention the dir-layout BC read, the `_PR_C_ACTIVE_TYPES` gate, and `mm context migrate` (PR-D scope). The ADR's Decision and Invariants are unchanged. Tests: * New `test_canonical_agent_name_dispatch_on_layout` + `test_canonical_command_name_dispatch_on_layout` exercise the helper for both layouts and the literal ``agent.md`` / ``command.md`` flat files (the case the brittle heuristic would misclassify). * New `test_wiki_skill_override_does_not_create_overrides_dir_on_missing_skill` verifies the mkdir-then-refuse fix — refused call leaves the ``skills/<name>/`` subtree absent. * Existing `result.imported` consumers updated to unpack the ``(path, layout)`` tuples and call the helper. Out of scope (carry-forward): - Two-phase write window in `skills.py` override hook (refactor `copy_skill` to take an override-aware source). - `install_agent` / `install_command` concurrency tests — natural fit for the PR-D `_PR_C_ACTIVE_TYPES` flip (more fan-out paths exercised concurrently). - Wiki working-tree dirty UX — natural fit for PR-E (web UI surfaces the wiki state already). Co-Authored-By: Claude <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ADR-0008 PR-C in three commits — install widening, vendor override
resolution for skills, and the wiki-side seed CLI.
mm context installtoskill | agent | commandand migrates fan-out enumeration to read both legacy flat
(
agents/<name>.md) and new directory layout (agents/<name>/agent.md,commands/<name>/command.md) per ADR-0008 — without moving anyexisting user files. New canonicals land in directory layout;
legacy flat keeps working.
context/override.py)and the per-vendor hook in
skills.py.OVERRIDE_FORMATSis thefull 9-cell matrix; the resolver activates only skills in v1 via
_PR_C_ACTIVE_TYPES = frozenset({"skills"}). Auxiliary files(
scripts/,references/) stay from canonical when an override isstaged (Invariant 4 says
byte-copy that file— singular).mm wiki skill override <name> --vendor <V>with--force(creates a.baksibling) and--editor. Stdout printsthree pieces — human line, bare absolute path for shell capture,
and a
git add/git commithint that names the exact paths torun in the wiki repo.
A byte-identical regression test (
test_context_override.py) waswritten first as a guard for PR-B fan-out semantics: with no overrides
on disk, every vendor's
SKILL.mdMUST equal canonical byte-for-byte.The test stays green across all three commits.
Why directory layout for agents/commands
ADR-0008 commits to
<type>/<name>/overrides/<vendor>.<ext>— thatrequires a directory shape around each canonical file. Before PR-C,
agents/commands canonical was flat (
<name>.md) with no place foran
overrides/subdir without breaking fan-out. PR-B'sinstall.pydocstring already called this out:
Closing the gap without a file migration:
list_canonical_*returnslist[tuple[Path, Layout]]and enumerates both layouts; dir wins oncollision (with WARN);
parse_canonical_*takeslayout=and usespath.parent.namefor dir form (avoids the brittlepath.stem == "agent"heuristic);extract_*_to_canonicalpreserves the existinglayout per name.
mm context migrate(PR-D) will be the supportedconsolidation path for users who want to move flat → dir.
Test plan
test_context_override.pybyte-identical regression — greentest_context_install_widening.py— install_agent/install_command + CLI dispatchtest_context_agents_bc_layout.py+ commands twin — flat-only / dir-only / both-present collision warning / parse layout dispatch / extract preserves layout / extract warns on coexisttest_context_override.pyresolver unit + skills override apply (per-vendor + all three) + scripts untouched + Invariant 1 no-wiki + agents/commands gatetest_wiki_cmd_override.py— happy path, refuse + force + .bak, unknown vendor, missing skill, missing wiki, editor on/off, stdout contractuv run pytest packages/memtomem/tests/ -m "not ollama"— 3444 passed, 0 faileduv run ruff check && uv run ruff format --check— cleanOut of scope (PR-D and later)
mm context update(mtime-based dirty detection,--force + .bak)mm wiki <type> {diff, lint, edit}and theagent/commandsubgroups_PR_C_ACTIVE_TYPESflip,
wiki/override.pyrendering for non-skills)mm context migrateto consolidate flat → dir layout🤖 Generated with Claude Code