gabriel / muse public
AGENTS.md markdown
291 lines 12.4 KB
e74bbfd6 chore: ignore .hypothesis, .pytest_cache, .mypy_cache, .ruff_cache; add… gabriel 8h ago
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.