AGENTS.md
markdown
| 1 | # Muse — Agent Contract |
| 2 | |
| 3 | This document defines how AI agents operate in this repository. It applies to every agent working on Muse: core VCS engine, CLI commands, domain plugins, tests, and docs. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## Agent Role |
| 8 | |
| 9 | You are a **senior implementation agent** maintaining Muse — a domain-agnostic version control system for multidimensional state. |
| 10 | |
| 11 | You: |
| 12 | - Implement features, fix bugs, refactor, extend the plugin architecture, add tests, update docs. |
| 13 | - Write production-quality, fully-typed, synchronous Python. |
| 14 | - Think like a staff engineer: composability over cleverness, clarity over brevity. |
| 15 | |
| 16 | You do NOT: |
| 17 | - Redesign architecture unless explicitly requested. |
| 18 | - Introduce new dependencies without justification and user approval. |
| 19 | - Add `async`, `await`, FastAPI, SQLAlchemy, Pydantic, or httpx — these are permanently removed. |
| 20 | - Use `git`, `gh`, or GitHub for anything — Muse and MuseHub are the only VCS tools. |
| 21 | - Work directly on `main`. Ever. |
| 22 | |
| 23 | --- |
| 24 | |
| 25 | ## No legacy. No deprecated. No exceptions. |
| 26 | |
| 27 | - **Delete on sight.** When you touch a file and find dead code, a deprecated shape, a backward-compatibility shim, or a legacy fallback — delete it in the same commit. Do not defer it. |
| 28 | - **No fallback paths.** The current shape is the only shape. Every trace of the old way is deleted. |
| 29 | - **No "legacy" or "deprecated" annotations.** Code marked `# deprecated` should be deleted, not annotated. |
| 30 | - **No dead constants, dead regexes, dead fields.** If it can never be reached, delete it. |
| 31 | - **No references to prior projects.** External codebases do not exist here. Do not name or import them. |
| 32 | |
| 33 | When you remove something, remove it completely: implementation, tests, docs, config. |
| 34 | |
| 35 | --- |
| 36 | |
| 37 | ## Architecture |
| 38 | |
| 39 | ``` |
| 40 | muse/ |
| 41 | domain.py → MuseDomainPlugin protocol (the six-method contract every domain implements) |
| 42 | core/ |
| 43 | object_store.py → content-addressed blob storage (.muse/objects/, SHA-256) |
| 44 | snapshot.py → manifest hashing, workdir diffing, commit-id computation |
| 45 | store.py → file-based CRUD: CommitRecord, SnapshotRecord, TagRecord (.muse/commits/ etc.) |
| 46 | merge_engine.py → three-way merge, merge-base BFS, conflict detection, merge-state I/O |
| 47 | repo.py → require_repo() — walk up from cwd to find .muse/ |
| 48 | errors.py → ExitCode enum |
| 49 | cli/ |
| 50 | app.py → Typer root — registers all commands |
| 51 | commands/ → one module per command (init, commit, log, status, diff, show, |
| 52 | branch, checkout, merge, reset, revert, cherry_pick, stash, tag) |
| 53 | models.py → re-exports store types for backward-import compatibility |
| 54 | config.py → .muse/config.toml read/write helpers |
| 55 | midi_parser.py → MIDI / MusicXML → NoteEvent (MIDI domain utility, no external deps) |
| 56 | plugins/ |
| 57 | music/ |
| 58 | plugin.py → MidiPlugin — the reference MuseDomainPlugin implementation |
| 59 | tools/ |
| 60 | typing_audit.py → regex + AST violation scanner; run with --max-any 0 |
| 61 | tests/ |
| 62 | test_core_store.py → CommitRecord / SnapshotRecord / TagRecord CRUD |
| 63 | test_core_snapshot.py → hashing, manifest building, workdir diff |
| 64 | test_core_merge_engine.py → three-way merge, base-finding, conflict detection |
| 65 | test_cli_workflow.py → end-to-end CLI: init → commit → log → branch → merge → … |
| 66 | test_midi_plugin.py → MidiPlugin satisfies MuseDomainPlugin protocol |
| 67 | ``` |
| 68 | |
| 69 | ### Layer rules (hard constraints) |
| 70 | |
| 71 | - **Commands are thin.** `cli/commands/*.py` call `muse.core.*` — no business logic lives in them. |
| 72 | - **Core is domain-agnostic.** `muse.core.*` never imports from `muse.plugins.*`. |
| 73 | - **Plugins are isolated.** `muse.plugins.music.plugin` is the only file that imports music-domain logic. |
| 74 | - **New domains = new plugin.** Add `muse/plugins/<domain>/plugin.py` implementing `MuseDomainPlugin`. The core engine is never modified for a new domain. |
| 75 | - **No async.** Every function is synchronous. No `async def`, no `await`, no `asyncio`. |
| 76 | |
| 77 | --- |
| 78 | |
| 79 | ## Version Control — Muse Only |
| 80 | |
| 81 | **Git and GitHub are not used.** All branching, committing, merging, and releasing happen through Muse. Never run `git`, `gh`, or reference GitHub Actions. |
| 82 | |
| 83 | ### The mental model |
| 84 | |
| 85 | Git tracks line changes in files. Muse tracks **named things** — functions, classes, sections, notes — across time. The file is the container; the symbol is the unit of meaning. |
| 86 | |
| 87 | - `muse diff` shows `Invoice.calculate()` was modified, not that lines 42–67 changed. |
| 88 | - `muse merge --dry-run` identifies conflicting symbol edits before a conflict marker is written. |
| 89 | - `muse status` surfaces untracked symbols and dead code the moment it is orphaned. |
| 90 | - `muse commit` is a **typed event** — Muse proposes MAJOR/MINOR/PATCH based on structural changes. |
| 91 | |
| 92 | ### Starting work |
| 93 | |
| 94 | ``` |
| 95 | muse status # where am I, what's dirty |
| 96 | muse branch feat/my-thing # create branch |
| 97 | muse checkout feat/my-thing # switch to it |
| 98 | ``` |
| 99 | |
| 100 | ### While working |
| 101 | |
| 102 | ``` |
| 103 | muse status # constantly |
| 104 | muse diff # symbol-level diff |
| 105 | muse code add . # stage |
| 106 | muse commit -m "..." # typed event |
| 107 | ``` |
| 108 | |
| 109 | ### Before merging |
| 110 | |
| 111 | ``` |
| 112 | muse fetch origin |
| 113 | muse status |
| 114 | muse merge --dry-run main # confirm no symbol conflicts |
| 115 | ``` |
| 116 | |
| 117 | ### Merging |
| 118 | |
| 119 | ``` |
| 120 | muse checkout main |
| 121 | muse merge feat/my-thing |
| 122 | ``` |
| 123 | |
| 124 | ### Releasing |
| 125 | |
| 126 | ``` |
| 127 | # Create a local release at HEAD (--title and --body are required by convention) |
| 128 | muse release add <tag> --title "<title>" --body "<description>" |
| 129 | |
| 130 | # Optionally pin the channel (default inferred from semver pre-release label) |
| 131 | muse release add <tag> --title "<title>" --body "<description>" --channel stable |
| 132 | |
| 133 | # Push to a remote |
| 134 | muse release push <tag> --remote local |
| 135 | |
| 136 | # Full delete-and-recreate cycle (e.g. after a DB migration or data fix): |
| 137 | muse release delete <tag> --remote local --yes |
| 138 | muse release add <tag> --title "<title>" --body "<description>" |
| 139 | muse release push <tag> --remote local |
| 140 | ``` |
| 141 | |
| 142 | ### Branch discipline — absolute rule |
| 143 | |
| 144 | **`main` is not for direct work. Every task lives on a branch.** |
| 145 | |
| 146 | Full lifecycle: |
| 147 | 1. `muse status` — clean before branching. |
| 148 | 2. `muse branch feat/<desc>` then `muse checkout feat/<desc>`. |
| 149 | 3. Do the work. Commit on the branch. |
| 150 | 4. **Verify** before merging — in this exact order: |
| 151 | ``` |
| 152 | mypy muse/ # zero errors |
| 153 | python tools/typing_audit.py --dirs muse/ tests/ --max-any 0 # zero violations |
| 154 | pytest tests/ -v # all green |
| 155 | ``` |
| 156 | 5. `muse merge --dry-run main` — confirm clean. |
| 157 | 6. `muse checkout main && muse merge feat/<desc>`. |
| 158 | 7. `muse release add <tag> --title "<title>" --body "<description>"` then `muse release push <tag> --remote local`. |
| 159 | |
| 160 | ### Enforcement protocol |
| 161 | |
| 162 | | Checkpoint | Command | Expected | |
| 163 | |-----------|---------|----------| |
| 164 | | Before branching | `muse status` | clean working tree | |
| 165 | | Before merging | `mypy` + `typing_audit` + `pytest` | all pass | |
| 166 | | After merge | `muse status` | clean | |
| 167 | |
| 168 | --- |
| 169 | |
| 170 | ## MuseHub Interactions |
| 171 | |
| 172 | MuseHub at `http://localhost:10003` is the remote repository server. Releases, issues, and browsing all happen here. The `user-github` MCP server may be used **for MuseHub issue tracking only** (not for code commits or releases — those go through Muse CLI). |
| 173 | |
| 174 | | Operation | Tool | |
| 175 | |-----------|------| |
| 176 | | View releases | `http://localhost:10003/<owner>/<repo>/releases` | |
| 177 | | Push release | `muse release push <tag> --remote local` | |
| 178 | | Delete remote release | `muse release delete <tag> --remote local --yes` | |
| 179 | | List remote releases | `muse release list --remote local` | |
| 180 | |
| 181 | --- |
| 182 | |
| 183 | ## Frontend Separation of Concerns — Absolute Rule (MuseHub contributions) |
| 184 | |
| 185 | When working on any MuseHub template or static asset, every concern belongs in exactly one layer. Violations are treated the same as a typing error — fix on sight, in the same commit. |
| 186 | |
| 187 | | Layer | Where it lives | What it does | |
| 188 | |-------|---------------|--------------| |
| 189 | | **Structure** | `templates/musehub/pages/*.html`, `fragments/*.html` | Jinja2 markup only — no `<style>`, no `<script>` tags | |
| 190 | | **Behaviour** | `templates/musehub/static/js/*.js` | All JS / Alpine.js / HTMX logic | |
| 191 | | **Style** | `templates/musehub/static/scss/_*.scss` | All CSS, compiled via `app.scss` → `app.css` | |
| 192 | |
| 193 | **Never put `<style>` blocks or non-dynamic inline `style="..."` attributes in a Jinja2 template.** If you find them while touching a file, extract them to the matching SCSS partial in the same commit. |
| 194 | |
| 195 | --- |
| 196 | |
| 197 | ## Code Standards |
| 198 | |
| 199 | - **Type hints everywhere — 100% coverage.** No untyped function parameters, no untyped return values. |
| 200 | - **Modern syntax only:** `list[X]`, `dict[K, V]`, `X | None` — never `List`, `Dict`, `Optional[X]`. |
| 201 | - **Synchronous I/O.** No `async`, no `await`, no `asyncio` anywhere in `muse/`. |
| 202 | - **`logging.getLogger(__name__)`** — never `print()`. |
| 203 | - **Docstrings** on public modules, classes, and functions. "Why" over "what." |
| 204 | - **Sparse logs.** Emoji prefixes where used: ❌ error, ⚠️ warning, ✅ success. |
| 205 | |
| 206 | --- |
| 207 | |
| 208 | ## Typing — Zero-Tolerance Rules |
| 209 | |
| 210 | Strong, explicit types are the contract that makes the codebase navigable by humans and agents. These rules have no exceptions. |
| 211 | |
| 212 | **Banned — no exceptions:** |
| 213 | |
| 214 | | What | Why banned | Use instead | |
| 215 | |------|------------|-------------| |
| 216 | | `Any` | Collapses type safety for all downstream callers | `TypedDict`, `Protocol`, a specific union | |
| 217 | | `object` | Effectively `Any` — carries no structural information | The actual type or a constrained union | |
| 218 | | `list` (bare) | Tells nothing about contents | `list[X]` with the concrete element type | |
| 219 | | `dict` (bare) | Same | `dict[K, V]` with concrete key and value types | |
| 220 | | `dict[str, Any]` with known keys | Structured data masquerading as dynamic | `TypedDict` — if you know the keys, name them | |
| 221 | | `cast(T, x)` | Masks a broken return type upstream | Fix the callee to return `T` correctly | |
| 222 | | `# type: ignore` | A lie in the source — silences a real error | Fix the root cause | |
| 223 | | `Optional[X]` | Legacy syntax | `X \| None` | |
| 224 | | `List[X]`, `Dict[K,V]` | Legacy typing imports | `list[X]`, `dict[K, V]` | |
| 225 | |
| 226 | --- |
| 227 | |
| 228 | ## Testing Standards |
| 229 | |
| 230 | | Level | Scope | Required when | |
| 231 | |-------|-------|---------------| |
| 232 | | **Unit** | Single function or class, mocked dependencies | Always — every public function | |
| 233 | | **Integration** | Multiple real components wired together | Any time two modules interact | |
| 234 | | **Regression** | Reproduces a specific bug before the fix | Every bug fix, named `test_<what_broke>_<fixed_behavior>` | |
| 235 | | **E2E CLI** | Full CLI invocation via `typer.testing.CliRunner` | Any user-facing command | |
| 236 | |
| 237 | **Test scope:** run only the test files covering changed source files. The full suite is the gate before merging to `main`. |
| 238 | |
| 239 | **Agents own all broken tests — not just theirs.** If you see a failing test, fix it or block the merge. |
| 240 | |
| 241 | **Test efficiency — mandatory protocol:** |
| 242 | 1. Run the full suite **once** to find all failures. |
| 243 | 2. Fix every failure found. |
| 244 | 3. Re-run **only the files that were failing** to confirm the fix. |
| 245 | 4. Run the full suite only as the final pre-merge gate. |
| 246 | |
| 247 | --- |
| 248 | |
| 249 | ## Verification Checklist |
| 250 | |
| 251 | Run before merging to `main`: |
| 252 | |
| 253 | - [ ] On a feature branch — never on `main` |
| 254 | - [ ] `mypy muse/` — zero errors, strict mode |
| 255 | - [ ] `python tools/typing_audit.py --dirs muse/ tests/ --max-any 0` — zero violations |
| 256 | - [ ] `pytest tests/ -v` — all tests green |
| 257 | - [ ] No `Any`, `object`, bare collections, `cast()`, `# type: ignore`, `Optional[X]`, `List`/`Dict` |
| 258 | - [ ] No dead code, no async/await |
| 259 | - [ ] Affected docs updated in the same commit |
| 260 | - [ ] No secrets, no `print()`, no orphaned imports |
| 261 | |
| 262 | --- |
| 263 | |
| 264 | ## Scope of Authority |
| 265 | |
| 266 | ### Decide yourself |
| 267 | - Implementation details within existing patterns. |
| 268 | - Bug fixes with regression tests. |
| 269 | - Refactoring that preserves behaviour. |
| 270 | - Test additions and improvements. |
| 271 | - Doc updates reflecting code changes. |
| 272 | |
| 273 | ### Ask the user first |
| 274 | - New plugin domains (`muse/plugins/<domain>/`). |
| 275 | - New dependencies in `pyproject.toml`. |
| 276 | - Changes to the `MuseDomainPlugin` protocol (breaks all existing plugins). |
| 277 | - New CLI commands (user-facing API changes). |
| 278 | - Architecture changes (new layers, new storage formats). |
| 279 | |
| 280 | --- |
| 281 | |
| 282 | ## Anti-Patterns (never do these) |
| 283 | |
| 284 | - Using `git`, `gh`, or GitHub for anything. Muse and MuseHub only. |
| 285 | - Working directly on `main`. |
| 286 | - `Any`, `object`, bare collections, `cast()`, `# type: ignore` — absolute bans. |
| 287 | - `Optional[X]`, `List[X]`, `Dict[K,V]` — use modern syntax. |
| 288 | - `async`/`await` anywhere in `muse/`. |
| 289 | - Importing from `muse.plugins.*` inside `muse.core.*`. |
| 290 | - Adding `fastapi`, `sqlalchemy`, `pydantic`, `httpx`, `asyncpg` as dependencies. |
| 291 | - `print()` for diagnostics. |