gabriel / muse public
.cursorrules
234 lines 10.6 KB
e74bbfd6 chore: ignore .hypothesis, .pytest_cache, .mypy_cache, .ruff_cache; add… gabriel 8h ago
1 # Muse — Agent Rules
2
3 ## Identity
4
5 Muse — domain-agnostic version control for multidimensional state. Version control is the abstraction; music is the first domain; genomics, scientific simulation, 3D spatial design, and spacetime simulation are next.
6
7 - **Entry point:** `muse` CLI. No web server, no HTTP API, no database.
8 - **Stack:** Python 3.14, Typer, file-based content-addressed storage (`.muse/`), synchronous I/O throughout.
9 - **Plugin contract:** `MuseDomainPlugin` protocol in `muse/domain.py`. Every new domain is a new plugin — the core engine never changes.
10 - **Version control:** Muse and MuseHub exclusively. Git and GitHub are not used.
11
12 ## No legacy. No deprecated. No exceptions.
13
14 This is the single most repeated rule. It is a hard constraint enforced on every change.
15
16 - **Delete on sight.** If you touch a file and find dead code, a deprecated API shape, a backward-compatibility shim, or a legacy fallback — delete it in the same commit.
17 - **No fallback paths for old shapes.** Remove every trace of the old way.
18 - **No "legacy" or "deprecated" comments.** If it's marked deprecated, delete it.
19 - **No dead constants, dead regexes, dead fields.** If it can never be reached, delete it.
20
21 When you remove something, remove it completely: implementation, tests, docs, config.
22
23 ## Version control — Muse only. Git and GitHub are not used.
24
25 All code changes, branching, merging, and releases happen through Muse commands. Git and GitHub do not exist in this workflow. Never run `git`, `gh`, or reference GitHub.
26
27 ### Starting work
28
29 ```
30 muse status # where am I, what's dirty
31 muse branch feat/my-thing # create branch
32 muse checkout feat/my-thing # switch to it
33 ```
34
35 ### While working
36
37 ```
38 muse status # constantly — like breathing
39 muse diff # what exactly changed, symbol-level
40 muse code add . # stage what you want in the next snapshot
41 muse commit -m "..." # typed event: Muse proposes MAJOR/MINOR/PATCH
42 ```
43
44 ### Before merging
45
46 ```
47 muse fetch origin # pull down what's changed upstream
48 muse status # shows if you're behind
49 muse merge --dry-run main # will this conflict? what's the semver impact?
50 ```
51
52 ### Merging
53
54 ```
55 muse merge main # if dry-run was clean
56 ```
57
58 ### Releasing
59
60 ```
61 # Create a local release at HEAD (--title and --body are required by convention)
62 muse release add <tag> --title "<title>" --body "<description>"
63
64 # Optionally set channel (default is inferred from semver pre-release label)
65 muse release add <tag> --title "<title>" --body "<description>" --channel stable
66
67 # Push to a remote
68 muse release push <tag> --remote local
69
70 # Full delete-and-recreate cycle (e.g. after a DB migration or data fix):
71 muse release delete <tag> --remote local --yes
72 muse release add <tag> --title "<title>" --body "<description>"
73 muse release push <tag> --remote local
74 ```
75
76 ### The mental model
77
78 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.
79
80 - `muse diff` shows `Invoice.calculate()` was modified, not that lines 42–67 changed.
81 - `muse merge --dry-run` identifies conflicting symbol edits before a conflict marker is written.
82 - `muse status` surfaces untracked symbols and dead code the moment it is orphaned.
83 - `muse commit` is a **typed event** — Muse proposes MAJOR/MINOR/PATCH based on what changed structurally.
84
85 ### Branch discipline
86
87 **Never work directly on `main`.**
88
89 Full task lifecycle:
90 1. `muse status` — must be clean before branching.
91 2. `muse branch feat/<description>` then `muse checkout feat/<description>` — always branch first.
92 3. Do the work, commit on the branch.
93 4. **Verify locally before merging — in this exact order:**
94 ```
95 mypy muse/ # zero errors
96 python tools/typing_audit.py --dirs muse/ tests/ --max-any 0 # zero violations
97 pytest tests/ -v # all green
98 ```
99 5. `muse merge --dry-run main` — confirm clean.
100 6. `muse checkout main && muse merge feat/<description>`
101 7. `muse release add <tag> --title "<title>" --body "<description>"` then `muse release push <tag> --remote local`.
102
103 ## MuseHub interactions
104
105 MuseHub at `http://localhost:10003` is the remote repository server. The `user-github` MCP server can be used for **MuseHub issue tracking only** (not for code commits or releases — those go through Muse).
106
107 | Operation | Tool |
108 |-----------|------|
109 | Browse releases | `http://localhost:10003/<owner>/<repo>/releases` |
110 | Push a release | `muse release push <tag> --remote local` |
111 | Delete a remote release | `muse release delete <tag> --remote local --yes` |
112
113 ## Architecture (do not weaken)
114
115 ```
116 muse/
117 domain.py → MuseDomainPlugin protocol + LiveState / StateSnapshot / StateDelta types
118 core/
119 object_store.py → content-addressed blob storage (.muse/objects/)
120 snapshot.py → manifest hashing, workdir diff
121 store.py → file-based CRUD for commits, snapshots, tags (.muse/commits/ etc.)
122 merge_engine.py → three-way merge, merge-base, conflict detection
123 repo.py → require_repo() — locates .muse/ from cwd
124 errors.py → ExitCode enum
125 cli/
126 app.py → Typer root — registers all commands
127 commands/ → one file per command (init, commit, log, status, …)
128 models.py → re-exports CommitRecord, SnapshotRecord, TagRecord
129 config.py → .muse/config.toml helpers
130 midi_parser.py → MIDI / MusicXML → NoteEvent (MIDI domain utility)
131 plugins/
132 music/
133 plugin.py → MidiPlugin — reference MuseDomainPlugin implementation
134 tools/
135 typing_audit.py → typing violation scanner (ratchet: --max-any 0)
136 tests/ → pytest suite
137 ```
138
139 **Layer rules:**
140 - Commands are thin. No business logic in `cli/commands/` — delegate to `muse.core.*`.
141 - `muse.core.*` is domain-agnostic. It never imports from `muse.plugins.*`.
142 - `muse.plugins.music.plugin` is the only file that may import domain-specific logic.
143 - New domains are new directories under `muse/plugins/`. The core engine is never modified.
144
145 ## Frontend separation of concerns — absolute rule (applies to MuseHub contributions)
146
147 When working on any MuseHub template or static asset, every concern belongs in exactly one layer:
148
149 | Layer | Where | What |
150 |-------|-------|------|
151 | **Structure** | `templates/musehub/pages/*.html`, `fragments/*.html` | Jinja2 markup only — no `<style>`, no `<script>` |
152 | **Behaviour** | `templates/musehub/static/js/*.js` | All JS / Alpine / HTMX |
153 | **Style** | `templates/musehub/static/scss/_*.scss` | All CSS — compiled to `app.css` via `app.scss` |
154
155 **Never put `<style>` blocks or inline `style="..."` (beyond truly dynamic values) in a template.** If you find them, extract them to the matching SCSS partial in the same commit.
156
157 ## Code standards
158
159 - Type hints everywhere — 100% coverage, no untyped functions or parameters.
160 - `list[X]` / `dict[K, V]` style — never `List`, `Dict`, `Optional`.
161 - `X | None` — never `Optional[X]`.
162 - Synchronous I/O throughout — no `async`, no `await`, no `asyncio`.
163 - `logging.getLogger(__name__)` — never `print()`.
164 - Sparse logs. Docstrings on public modules, classes, and functions. "Why" over "what."
165
166 ## Typing — zero-tolerance rules
167
168 Strong types are the contract. There are no exceptions.
169
170 - **No `Any`. Ever.** Use `TypedDict`, a `Protocol`, or a specific union. There is always a correct type.
171 - **No `object`. Ever.** It is `Any` with a different name. Express the actual shape.
172 - **No bare collections.** `list`, `dict`, `set`, `tuple` without type parameters are banned. Always `list[str]`, `dict[str, int]`, etc.
173 - **No `# type: ignore`.** Fix the root cause. If a third-party library forces the issue, write a typed adapter.
174 - **No `cast()`.** If you need a cast, the callee returns the wrong type — fix the callee.
175 - **No `Optional[X]`.** Write `X | None`.
176 - **No legacy typing imports.** `List`, `Dict`, `Set`, `Tuple` from `typing` are banned — use lowercase builtins.
177
178 ### What to use instead
179
180 | Banned | Use instead |
181 |--------|-------------|
182 | `Any` | `TypedDict`, `Protocol`, specific union |
183 | `object` | The actual type or a constrained union |
184 | `list` (bare) | `list[X]` |
185 | `dict` (bare) | `dict[K, V]` |
186 | `dict[str, Any]` with known keys | `TypedDict` — if you know the keys, name them |
187 | `cast(T, x)` | Fix the function producing `x` to return `T` |
188 | `# type: ignore` | Fix the underlying type error |
189 | `Optional[X]` | `X \| None` |
190 | `List[X]`, `Dict[K,V]` | `list[X]`, `dict[K, V]` |
191
192 ## Verification checklist
193
194 Run before every merge — in this exact order:
195
196 - [ ] On a feature branch, not `main`
197 - [ ] `mypy muse/` — zero errors, strict mode
198 - [ ] `python tools/typing_audit.py --dirs muse/ tests/ --max-any 0` — zero violations
199 - [ ] `pytest tests/ -v` — all green
200 - [ ] No `Any`, bare collections, `cast()`, `# type: ignore`, `Optional[X]`, legacy `List`/`Dict`
201 - [ ] No dead code, no legacy patterns
202 - [ ] Affected docs updated in the same commit
203 - [ ] No secrets, no `print()`, no orphaned imports
204
205 ## Anti-patterns (never do these)
206
207 - Using `git`, `gh`, or GitHub for anything. Muse and MuseHub are the only VCS tools.
208 - Working directly on `main`.
209 - `Any`, `object`, bare collections, `cast()`, `# type: ignore` — absolute bans.
210 - `Optional[X]`, `List[X]`, `Dict[K,V]` — use modern syntax.
211 - `async`/`await` anywhere in `muse/` — the CLI is synchronous by design.
212 - Importing from `muse.plugins.*` inside `muse.core.*`.
213 - Adding `fastapi`, `sqlalchemy`, `pydantic`, `httpx`, `asyncpg` as dependencies — the whole point of v2 is that they are gone.
214 - Hardcoded paths or repo IDs outside `.muse/repo.json`.
215 - `print()` for diagnostics — use `logging`.
216
217 ## Test efficiency — mandatory protocol
218
219 1. Run the full suite **once** to find all failures.
220 2. Fix every failure found.
221 3. Re-run **only the specific failing file(s)** to confirm the fix: `pytest tests/test_foo.py -v`
222 4. Run the full suite only as the final pre-merge gate.
223
224 ## Quick reference
225
226 | Area | Module | Tests |
227 |------|--------|-------|
228 | Plugin contract | `muse/domain.py` | `tests/test_midi_plugin.py` |
229 | Object store | `muse/core/object_store.py` | `tests/test_core_snapshot.py` |
230 | File store | `muse/core/store.py` | `tests/test_core_store.py` |
231 | Merge engine | `muse/core/merge_engine.py` | `tests/test_core_merge_engine.py` |
232 | CLI commands | `muse/cli/commands/` | `tests/test_cli_workflow.py` |
233 | MIDI plugin | `muse/plugins/midi/plugin.py` | `tests/test_midi_plugin.py` |
234 | Typing audit | `tools/typing_audit.py` | run with `--max-any 0` |