gabriel / muse public
.cursorrules
241 lines 11.1 KB
Raw
sha256:38dd0b4dc48e9ccb0207b5d79d0b39b5824230ff2768f41b0fec365ff55b8a4e remove files Human minor ⚠ breaking 52 days 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/<affected_files> -v # only files covering your changes
98 ```
99 The user runs `pytest tests/ -v` (full suite) before any merge to `main`. Agents never run the full suite.
100 5. `muse merge --dry-run main` — confirm clean.
101 6. `muse checkout main && muse merge feat/<description>`
102 7. `muse release add <tag> --title "<title>" --body "<description>"` then `muse release push <tag> --remote local`.
103
104 ## MuseHub interactions
105
106 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).
107
108 | Operation | Tool |
109 |-----------|------|
110 | Browse releases | `http://localhost:10003/<owner>/<repo>/releases` |
111 | Push a release | `muse release push <tag> --remote local` |
112 | Delete a remote release | `muse release delete <tag> --remote local --yes` |
113
114 ## Architecture (do not weaken)
115
116 ```
117 muse/
118 domain.py → MuseDomainPlugin protocol + LiveState / StateSnapshot / StateDelta types
119 core/
120 object_store.py → content-addressed blob storage (.muse/objects/)
121 snapshot.py → manifest hashing, workdir diff
122 store.py → file-based CRUD for commits, snapshots, tags (.muse/commits/ etc.)
123 merge_engine.py → three-way merge, merge-base, conflict detection
124 repo.py → require_repo() — locates .muse/ from cwd
125 errors.py → ExitCode enum
126 cli/
127 app.py → Typer root — registers all commands
128 commands/ → one file per command (init, commit, log, status, …)
129 models.py → re-exports CommitRecord, SnapshotRecord, TagRecord
130 config.py → .muse/config.toml helpers
131 midi_parser.py → MIDI / MusicXML → NoteEvent (MIDI domain utility)
132 plugins/
133 music/
134 plugin.py → MidiPlugin — reference MuseDomainPlugin implementation
135 tools/
136 typing_audit.py → typing violation scanner (ratchet: --max-any 0)
137 tests/ → pytest suite
138 ```
139
140 **Layer rules:**
141 - Commands are thin. No business logic in `cli/commands/` — delegate to `muse.core.*`.
142 - `muse.core.*` is domain-agnostic. It never imports from `muse.plugins.*`.
143 - `muse.plugins.music.plugin` is the only file that may import domain-specific logic.
144 - New domains are new directories under `muse/plugins/`. The core engine is never modified.
145
146 ## Frontend separation of concerns — absolute rule (applies to MuseHub contributions)
147
148 When working on any MuseHub template or static asset, every concern belongs in exactly one layer:
149
150 | Layer | Where | What |
151 |-------|-------|------|
152 | **Structure** | `templates/musehub/pages/*.html`, `fragments/*.html` | Jinja2 markup only — no `<style>`, no `<script>` |
153 | **Behaviour** | `templates/musehub/static/js/*.js` | All JS / Alpine / HTMX |
154 | **Style** | `templates/musehub/static/scss/_*.scss` | All CSS — compiled to `app.css` via `app.scss` |
155
156 **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.
157
158 ## Code standards
159
160 - Type hints everywhere — 100% coverage, no untyped functions or parameters.
161 - `list[X]` / `dict[K, V]` style — never `List`, `Dict`, `Optional`.
162 - `X | None` — never `Optional[X]`.
163 - Synchronous I/O throughout — no `async`, no `await`, no `asyncio`.
164 - `logging.getLogger(__name__)` — never `print()`.
165 - Sparse logs. Docstrings on public modules, classes, and functions. "Why" over "what."
166
167 ## Typing — zero-tolerance rules
168
169 Strong types are the contract. There are no exceptions.
170
171 - **No `Any`. Ever.** Use `TypedDict`, a `Protocol`, or a specific union. There is always a correct type.
172 - **No `object`. Ever.** It is `Any` with a different name. Express the actual shape.
173 - **No bare collections.** `list`, `dict`, `set`, `tuple` without type parameters are banned. Always `list[str]`, `dict[str, int]`, etc.
174 - **No `# type: ignore`.** Fix the root cause. If a third-party library forces the issue, write a typed adapter.
175 - **No `cast()`.** If you need a cast, the callee returns the wrong type — fix the callee.
176 - **No `Optional[X]`.** Write `X | None`.
177 - **No legacy typing imports.** `List`, `Dict`, `Set`, `Tuple` from `typing` are banned — use lowercase builtins.
178
179 ### What to use instead
180
181 | Banned | Use instead |
182 |--------|-------------|
183 | `Any` | `TypedDict`, `Protocol`, specific union |
184 | `object` | The actual type or a constrained union |
185 | `list` (bare) | `list[X]` |
186 | `dict` (bare) | `dict[K, V]` |
187 | `dict[str, Any]` with known keys | `TypedDict` — if you know the keys, name them |
188 | `cast(T, x)` | Fix the function producing `x` to return `T` |
189 | `# type: ignore` | Fix the underlying type error |
190 | `Optional[X]` | `X \| None` |
191 | `List[X]`, `Dict[K,V]` | `list[X]`, `dict[K, V]` |
192
193 ## Verification checklist
194
195 Run before every merge — in this exact order:
196
197 - [ ] On a feature branch, not `main`
198 - [ ] `mypy muse/` — zero errors, strict mode
199 - [ ] `python tools/typing_audit.py --dirs muse/ tests/ --max-any 0` — zero violations
200 - [ ] `pytest tests/<affected_files> -v` — all green (agent runs only affected files; user runs full suite)
201 - [ ] No `Any`, bare collections, `cast()`, `# type: ignore`, `Optional[X]`, legacy `List`/`Dict`
202 - [ ] No dead code, no legacy patterns
203 - [ ] Affected docs updated in the same commit
204 - [ ] No secrets, no `print()`, no orphaned imports
205
206 ## Anti-patterns (never do these)
207
208 - Using `git`, `gh`, or GitHub for anything. Muse and MuseHub are the only VCS tools.
209 - Working directly on `main`.
210 - `Any`, `object`, bare collections, `cast()`, `# type: ignore` — absolute bans.
211 - `Optional[X]`, `List[X]`, `Dict[K,V]` — use modern syntax.
212 - `async`/`await` anywhere in `muse/` — the CLI is synchronous by design.
213 - Importing from `muse.plugins.*` inside `muse.core.*`.
214 - Adding `fastapi`, `sqlalchemy`, `pydantic`, `httpx`, `asyncpg` as dependencies — the whole point of v2 is that they are gone.
215 - Hardcoded paths or repo IDs outside `.muse/repo.json`.
216 - `print()` for diagnostics — use `logging`.
217
218 ## Testing — mandatory protocol
219
220 **Agents do not run the full test suite. The user runs the full suite.**
221
222 What agents must run before every commit:
223 1. `mypy muse/` — zero errors.
224 2. `python tools/typing_audit.py --dirs muse/ tests/ --max-any 0` — zero violations.
225 3. Only the test files that cover the code you changed. Match source to test by name:
226 - `muse/core/foo.py` → `pytest tests/test_core_foo.py -v`
227 - `muse/cli/commands/bar.py` → `pytest tests/test_cmd_bar.py -v`
228
229 The user runs `pytest tests/ -v` before any merge to `main`. Do not repeat that work.
230
231 ## Quick reference
232
233 | Area | Module | Tests |
234 |------|--------|-------|
235 | Plugin contract | `muse/domain.py` | `tests/test_midi_plugin.py` |
236 | Object store | `muse/core/object_store.py` | `tests/test_core_snapshot.py` |
237 | File store | `muse/core/store.py` | `tests/test_core_store.py` |
238 | Merge engine | `muse/core/merge_engine.py` | `tests/test_core_merge_engine.py` |
239 | CLI commands | `muse/cli/commands/` | `tests/test_cli_workflow.py` |
240 | MIDI plugin | `muse/plugins/midi/plugin.py` | `tests/test_midi_plugin.py` |
241 | Typing audit | `tools/typing_audit.py` | run with `--max-any 0` |
File History 4 commits
sha256:fe844c2411edd1cec3d4c847f36a96c6ccd4e3d7d1a715106d2ecd64216bf94f fix: bare object detection and read recovery; rm adapter files Sonnet 4.6 minor 52 days ago
sha256:99f8eb388d9a9c353e68b9a4e5bebe1b4240a8f511e6f0928e58c0e95153e103 feat: branch --prune-config, fix hub repo delete docstrings… Sonnet 4.6 minor 53 days ago