muse agent-config's canonical source file is never tracked by Muse, defeating its own purpose
muse agent-config's canonical source file is never tracked by Muse, defeating its own purpose
Background
The entire premise of muse agent-config is: one canonical, versioned source
file per repo/workspace, from which any developer on any tool (Claude, Codex,
Cursor, Windsurf) can generate their adapter file via muse agent-config sync
— enabling a new developer to clone the repo and get the right config for
their setup automatically.
That promise is broken. The canonical source lives at .muse/agent.md
(muse/core/paths.py::agent_md_path()), and .muse/ is unconditionally
excluded from every snapshot:
# muse/plugins/code/plugin.py:157-166
# Directories that are never versioned regardless of .museignore.
# These are implicit ignores that apply to all code repositories.
_ALWAYS_IGNORE_DIRS: frozenset[str] = frozenset({
".git",
".muse",
...
})
Confirmed live, not assumed: editing ~/ecosystem/.muse/agent.md (the
workspace-level rules file) produces zero change in muse status, and
the file is absent from muse ls-files entirely. CLAUDE.md at the repo
root is tracked, but it's just a one-line @.muse/agent.md include — the
actual rules content is local-only and would not survive a fresh clone.
This is not a workspace misconfiguration — agent_md_path() is a single,
hardcoded function every agent-config subcommand (init, sync, read,
status, inspect) routes through, so every user of Muse who has ever run
muse agent-config init has the identical problem: their canonical config
only exists on the machine it was created on.
Goal
The canonical agent-config source file must be a normal tracked file — living
outside .muse/ entirely — so it survives clone/push/pull like any other
repo content, restoring the actual promise: clone, run muse agent-config sync, get your tool's adapter.
Decided (2026-07-07): the new canonical filename is .museagent.md at
the repo/workspace root — matches the existing .museignore/.museattributes
dotfile naming convention, and avoids the near-collision risk of AGENT.md
vs. the existing AGENTS.md (the Codex adapter's generated output, not the
source).
"Done" means: agent_md_path() returns <root>/.museagent.md; every existing
.muse/agent.md in this workspace (muse, musehub, ~/ecosystem workspace
root, and any other member repo that has one) is migrated; generated adapter
files reference @.museagent.md not @.muse/agent.md; a one-time forward
migration (not a permanent fallback) handles repos that still have the old
file when init/sync runs.
Out of Scope
- Any change to what content
agent.md/.museagent.mdholds — this is a location fix, not a content redesign. - Per-tool adapter rendering logic beyond the path string itself (the
_render_adaptertemplates stay the same, just fed a different path). - Retroactively renaming the concept from "agent.md" to something else in prose/docstrings beyond what's needed for the new filename to make sense.
Phases
Phase 1 — Red: pin the bug and the target behavior
ACFG_01—agent_md_path(root)currently returns a path whose parent directory name is.muse— assert this is no longer true after the fix (returns<root>/.museagent.md). Must fail against current code.ACFG_02— Aftermuse agent-config initin a fresh repo, the created file must not be excluded by_ALWAYS_IGNORE_DIRS/ must appear inmuse ls-filesaftermuse code add .+ commit. Real integration test, not just a path-string assertion — this is the actual bug.ACFG_03— GeneratedCLAUDE.mdcontent contains@.museagent.md, not@.muse/agent.md, for both standalone and workspace-member scope (the latter also covers the workspace-root relative path,../.museagent.md).
Deliverable: all three red against current code, confirmed before any Phase 2 change.
Phase 2 — Fix: move the canonical path, migrate existing files
- Change
agent_md_path()inmuse/core/paths.pyto returnroot / ".museagent.md". - Update every literal
.muse/agent.md/{rel}/.muse/agent.mdstring inmuse/cli/commands/agent_config.py(path-building logic, docstrings, help text) to.museagent.md/{rel}/.museagent.md. - One-time forward migration: if
initorsyncfinds.muse/agent.mdpresent and.museagent.mdabsent, move the content across (not copy — the old location must not linger, per this workspace's "no legacy" convention) and log what happened. This is a migration step, not a permanent fallback path — no dual-read logic survives after the move. - Update
_WORKSPACE_ROOT_TEMPLATE/_WORKSPACE_MEMBER_TEMPLATE/_STANDALONE_TEMPLATEif they reference the old path internally.
Deliverable: ACFG_01-03 green.
Phase 3 — Migrate this workspace's existing files
- Run the migration (via
muse agent-config syncor manually) inmuse,musehub,~/ecosystem(workspace root), and any other member repo with an existing.muse/agent.md. - Commit each migrated
.museagent.md— verify withmuse ls-filesthat it's genuinely tracked this time, not just present on disk. - Regenerate all adapters (
muse agent-config sync --force) so everyCLAUDE.md/AGENTS.mdreferences@.museagent.md.
Deliverable: ACFG_04 — a fresh muse clone of each affected repo has
.museagent.md present and correct, with no manual agent-config init
required.
Phase 4 — Regression sweep
- Full
tests/test_cmd_agent_config.pyandtests/test_agent_config_envelope.pygreen — expect many existing assertions to need updating from.muse/agent.mdliterals to.museagent.md, not just the new tests passing. muse code test --jsonfor downstream impact.grep -rn '\.muse/agent\.md' muse/(excluding this ticket's own historical references) returns zero remaining hits in source or tests.
Deliverable: zero regressions, zero remaining old-path references.
Acceptance Criteria
agent_md_path()never returns a path inside.muse/.- A fresh
muse agent-config init+ commit produces a file that survivesmuse clone— proven by an integration test, not inferred. - Every existing
.muse/agent.mdin this workspace has been migrated and is now genuinely tracked. - Zero remaining
.muse/agent.mdreferences inmuse/'s source, tests, or docstrings. docs/agent-guide.mdand every workspaceCLAUDE.md/AGENTS.mdreflect the new path.
Implementation Order
Phase 1 → Phase 2 → Phase 3 → Phase 4. Phase 3 (migrating this workspace's real files) cannot start until Phase 2's migration logic exists and is tested — don't hand-migrate files before the tool that's supposed to do it is proven correct.
Phase 4 complete — closing.
Phases 1-3 (canonical source moved to .museagent.md, auto-migration logic) landed in v0.2.0-nightly.3, per your own comment. Verified today that the final workspace-wide sweep gap you flagged ("the workspace root's own CLAUDE.md still resolves via .muse/agent.md") was still real, but had narrowed: the workspace root (~/ecosystem) and musehub were both already correctly in sync (muse agent-config status → in_sync: true for claude on both) — but, ironically, the muse repo itself (the one hosting this very ticket) was the one left behind. muse agent-config status here showed claude adapter in_sync: false, and CLAUDE.md literally still read:
@../.muse/agent.md
@.muse/agent.md
— stale references into a path that's unconditionally excluded from every snapshot and had long since stopped existing on disk in this repo. Root cause of why it went unnoticed: this repo has no .musehooks.toml, so there was no pre-commit gate (muse agent-config status --fail-if-out-of-sync — the exact mechanism your own hooks feature, musehub#192, added specifically for this class of drift) to catch it here, unlike the workspace root and musehub where that hook is wired in.
Fix: regenerated via muse agent-config sync --force — CLAUDE.md now correctly includes .museagent.md, and muse agent-config status confirms out_of_sync_count: 0.
Also fixed: the one remaining stale-path reference anywhere in muse/'s docs — docs/issues/derive-semver-tag-from-pep440-version.md (an unrelated, still-open ticket) referenced musehub/.muse/agent.md twice as an edit target. Corrected both to musehub/.museagent.md — a pure path-string fix, no change to that ticket's actual scope.
Confirmed clean — every remaining .muse/agent.md string anywhere in muse/ is now intentional, not stale: agent_config.py's one-time forward-migration logic (must reference the old path to migrate from it), test_cmd_agent_config.py/test_cmd_code_add.py's migration/staging test scenarios (testing behavior at the old path on purpose), and the release notes / docs/agent-guide.md's historical note describing this exact incident as past fact.
Regression: tests/test_cmd_agent_config.py + tests/test_agent_config_envelope.py (144 tests) pass unchanged — this was a config-regeneration fix, not a code change, so the existing Phase 1-2 tests already cover the underlying mechanism.
Suggested follow-up, not done here (out of this ticket's scope): wire a .musehooks.toml into the muse repo itself, matching the workspace root and musehub, so this specific repo can't silently drift out of sync again. Worth its own small ticket if you want it.
Merged to dev → main, pushed to local/staging/production for both branches.
Phases 1-3 (migration tool + auto-migrate on init/sync + this workspace's muse and musehub repos) landed on dev/main tonight, part of v0.2.0-nightly.3. Leaving open — the workspace root's own CLAUDE.md still resolves via
.muse/agent.md, and Phase 4 (docs/workspace-wide sweep) hasn't been verified complete across every member repo.