gabriel / muse public
Closed #78 Bug
filed by gabriel human · 108 days ago

muse agent-config's canonical source file is never tracked by Muse, defeating its own purpose

0 Anchors
— Blast radius
— Churn 30d
0 Proposals

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.md holds — this is a location fix, not a content redesign.
  • Per-tool adapter rendering logic beyond the path string itself (the _render_adapter templates 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 — After muse agent-config init in a fresh repo, the created file must not be excluded by _ALWAYS_IGNORE_DIRS / must appear in muse ls-files after muse code add . + commit. Real integration test, not just a path-string assertion — this is the actual bug.
  • ACFG_03 — Generated CLAUDE.md content 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() in muse/core/paths.py to return root / ".museagent.md".
  • Update every literal .muse/agent.md / {rel}/.muse/agent.md string in muse/cli/commands/agent_config.py (path-building logic, docstrings, help text) to .museagent.md / {rel}/.museagent.md.
  • One-time forward migration: if init or sync finds .muse/agent.md present and .museagent.md absent, 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_TEMPLATE if 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 sync or manually) in muse, musehub, ~/ecosystem (workspace root), and any other member repo with an existing .muse/agent.md.
  • Commit each migrated .museagent.md — verify with muse ls-files that it's genuinely tracked this time, not just present on disk.
  • Regenerate all adapters (muse agent-config sync --force) so every CLAUDE.md/AGENTS.md references @.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.py and tests/test_agent_config_envelope.py green — expect many existing assertions to need updating from .muse/agent.md literals to .museagent.md, not just the new tests passing.
  • muse code test --json for 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 survives muse clone — proven by an integration test, not inferred.
  • Every existing .muse/agent.md in this workspace has been migrated and is now genuinely tracked.
  • Zero remaining .muse/agent.md references in muse/'s source, tests, or docstrings.
  • docs/agent-guide.md and every workspace CLAUDE.md/AGENTS.md reflect 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.

◎ Activity2
●
gabriel opened this issue 108 days ago
○
gabriel 108 days ago

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.

○
gabriel 2 days ago

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.

✓
closed this issue 2 days ago