.cursorrules
| 1 | # MuseHub — Agent Rules |
| 2 | |
| 3 | ## Identity |
| 4 | |
| 5 | MuseHub — the remote repository server for the Muse version control system. It is the GitHub analogue in the Muse ecosystem: stores pushed commits and snapshots, renders release detail pages, serves the wire protocol, hosts issue tracking and MCP tooling. |
| 6 | |
| 7 | - **Stack:** Python 3.14, FastAPI, SQLAlchemy (async), Alembic, Jinja2, SCSS. |
| 8 | - **Database:** SQLite (local) / PostgreSQL (production), accessed only through the service layer. |
| 9 | - **Remote name for local dev:** `local` → `http://localhost:10003/gabriel/musehub` |
| 10 | - **Version control:** Muse and MuseHub exclusively. Git and GitHub are not used. |
| 11 | |
| 12 | ## No legacy. No deprecated. No exceptions. |
| 13 | |
| 14 | - **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. |
| 15 | - **No fallback paths for old shapes.** Remove every trace of the old way. |
| 16 | - **No "legacy" or "deprecated" comments.** If it's marked deprecated, delete it. |
| 17 | - **No dead constants, dead regexes, dead fields.** If it can never be reached, delete it. |
| 18 | |
| 19 | When you remove something, remove it completely: implementation, tests, docs, config. |
| 20 | |
| 21 | ## Version control — Muse only. Git and GitHub are not used. |
| 22 | |
| 23 | All code changes, branching, merging, and releases happen through Muse. Never run `git`, `gh`, or reference GitHub. The Muse remote for this repo is `local`. |
| 24 | |
| 25 | ### Starting work |
| 26 | |
| 27 | ``` |
| 28 | muse status # where am I, what's dirty |
| 29 | muse branch feat/my-thing # create branch |
| 30 | muse checkout feat/my-thing # switch to it |
| 31 | ``` |
| 32 | |
| 33 | ### While working |
| 34 | |
| 35 | ``` |
| 36 | muse status # constantly — like breathing |
| 37 | muse diff # symbol-level diff |
| 38 | muse code add . # stage |
| 39 | muse commit -m "..." # typed event; Muse proposes MAJOR/MINOR/PATCH |
| 40 | ``` |
| 41 | |
| 42 | ### Before merging |
| 43 | |
| 44 | ``` |
| 45 | muse fetch local |
| 46 | muse status |
| 47 | muse merge --dry-run main # confirm no symbol conflicts |
| 48 | ``` |
| 49 | |
| 50 | ### Merging |
| 51 | |
| 52 | ``` |
| 53 | muse checkout main |
| 54 | muse merge feat/my-thing |
| 55 | ``` |
| 56 | |
| 57 | ### Releasing |
| 58 | |
| 59 | ``` |
| 60 | # Create a local release at HEAD (--title and --body are required by convention) |
| 61 | muse release add <tag> --title "<title>" --body "<description>" |
| 62 | |
| 63 | # Optionally set channel (default is inferred from semver pre-release label) |
| 64 | muse release add <tag> --title "<title>" --body "<description>" --channel stable |
| 65 | |
| 66 | # Push to a remote |
| 67 | muse release push <tag> --remote local |
| 68 | |
| 69 | # Full delete-and-recreate cycle (e.g. after a DB migration or data fix): |
| 70 | muse release delete <tag> --remote local --yes |
| 71 | muse release add <tag> --title "<title>" --body "<description>" |
| 72 | muse release push <tag> --remote local |
| 73 | ``` |
| 74 | |
| 75 | ### Branch discipline |
| 76 | |
| 77 | **Never work directly on `main`.** Every task lives on a feature branch. |
| 78 | |
| 79 | Full lifecycle: |
| 80 | 1. `muse status` — clean before branching. |
| 81 | 2. `muse branch feat/<desc>` then `muse checkout feat/<desc>`. |
| 82 | 3. Do the work. Commit on the branch. |
| 83 | 4. **Verify** before merging — in this exact order: |
| 84 | ``` |
| 85 | mypy musehub/ # zero errors |
| 86 | pytest tests/ -v # all green |
| 87 | ``` |
| 88 | 5. `muse merge --dry-run main` — confirm clean. |
| 89 | 6. `muse checkout main && muse merge feat/<desc>`. |
| 90 | 7. `muse release add <tag> --title "<title>" --body "<description>"` then `muse release push <tag> --remote local`. |
| 91 | |
| 92 | ## MuseHub interactions |
| 93 | |
| 94 | The running server is at `http://localhost:10003`. Start/restart it with Docker Compose. |
| 95 | |
| 96 | | Operation | Command | |
| 97 | |-----------|---------| |
| 98 | | Start server | `docker compose up -d` | |
| 99 | | View releases | `http://localhost:10003/gabriel/musehub/releases` | |
| 100 | | Push release | `muse release push <tag> --remote local` | |
| 101 | | Delete remote release | `muse release delete <tag> --remote local --yes` | |
| 102 | | Run migrations | `docker compose exec musehub alembic upgrade head` | |
| 103 | | Build SCSS | `docker compose exec musehub python -m tools.build_scss` (or equivalent) | |
| 104 | |
| 105 | ## Architecture (do not weaken) |
| 106 | |
| 107 | ``` |
| 108 | musehub/ |
| 109 | api/ |
| 110 | routes/ |
| 111 | wire.py → Muse CLI wire protocol (push, pull, releases) |
| 112 | musehub/ |
| 113 | ui.py → Server-side rendered HTML pages |
| 114 | releases.py → Release CRUD API |
| 115 | db/ |
| 116 | musehub_models.py → SQLAlchemy ORM models |
| 117 | models/ |
| 118 | musehub.py → Pydantic request/response models (camelCase wire, snake_case Python) |
| 119 | services/ |
| 120 | musehub_releases.py → ONLY module that touches musehub_releases table |
| 121 | templates/ |
| 122 | musehub/ |
| 123 | pages/ → Full-page Jinja2 templates |
| 124 | fragments/ → HTMX partial fragments |
| 125 | static/ |
| 126 | scss/ → SCSS source; compiled to app.css |
| 127 | mcp/ → Model Context Protocol tools and resources |
| 128 | alembic/ |
| 129 | versions/ → One migration per schema change; never edit applied migrations |
| 130 | tests/ → pytest + anyio async test suite |
| 131 | ``` |
| 132 | |
| 133 | **Layer rules:** |
| 134 | - Route handlers are thin — delegate all DB access to `services/`. |
| 135 | - `services/musehub_releases.py` is the ONLY file that touches `musehub_releases`. |
| 136 | - No business logic in templates — only presentation. |
| 137 | - Alembic migrations are append-only. Never edit an already-applied migration. |
| 138 | |
| 139 | ## Frontend separation of concerns — absolute rule |
| 140 | |
| 141 | Every concern lives in exactly one layer. Never mix them. |
| 142 | |
| 143 | | Layer | Where it lives | What it does | |
| 144 | |-------|---------------|--------------| |
| 145 | | **Structure** | `templates/musehub/pages/*.html`, `fragments/*.html` | Jinja2 markup only — no `<style>`, no `<script>` tags | |
| 146 | | **Behaviour** | `templates/musehub/static/js/*.js` | Vanilla JS / Alpine.js / HTMX configuration | |
| 147 | | **Style** | `templates/musehub/static/scss/_*.scss` | All CSS, compiled to `app.css` via `app.scss` | |
| 148 | |
| 149 | **Banned in templates:** |
| 150 | - `<style>` or `<style scoped>` blocks — move every rule to the appropriate `_*.scss` partial |
| 151 | - Inline `style="..."` attributes for anything beyond a truly dynamic value (e.g. `style="width:{{ pct }}%"`) |
| 152 | - `<script>` tags that contain non-trivial logic — extract to a `.js` file |
| 153 | |
| 154 | **Required when adding new UI:** |
| 155 | 1. New CSS classes → new or existing `scss/_*.scss` partial (use the file that matches the page/component) |
| 156 | 2. New interactive behaviour → `static/js/` file imported by `app.js` |
| 157 | 3. Compile SCSS locally after any change — `app.css` is committed and baked into the Docker image: |
| 158 | ``` |
| 159 | sass musehub/templates/musehub/static/scss/app.scss musehub/templates/musehub/static/app.css --style=compressed --no-source-map |
| 160 | ``` |
| 161 | 4. Then rebuild the container: `docker compose up --build -d` |
| 162 | |
| 163 | This rule applies retroactively. If you touch a template and find inline styles, pull them into SCSS in the same commit. |
| 164 | |
| 165 | ## Code standards |
| 166 | |
| 167 | - Type hints everywhere — 100% coverage, no untyped functions or parameters. |
| 168 | - `list[X]` / `dict[K, V]` style — never `List`, `Dict`, `Optional`. |
| 169 | - `X | None` — never `Optional[X]`. |
| 170 | - `logging.getLogger(__name__)` — never `print()`. |
| 171 | - Docstrings on public modules, classes, and functions. |
| 172 | |
| 173 | ## Typing — zero-tolerance rules |
| 174 | |
| 175 | | Banned | Use instead | |
| 176 | |--------|-------------| |
| 177 | | `Any` | `TypedDict`, `Protocol`, specific union | |
| 178 | | `object` | The actual type or a constrained union | |
| 179 | | `list` (bare) | `list[X]` | |
| 180 | | `dict` (bare) | `dict[K, V]` | |
| 181 | | `cast(T, x)` | Fix the callee to return `T` | |
| 182 | | `# type: ignore` | Fix the root cause | |
| 183 | | `Optional[X]` | `X \| None` | |
| 184 | | `List[X]`, `Dict[K,V]` | `list[X]`, `dict[K, V]` | |
| 185 | |
| 186 | ## Verification checklist |
| 187 | |
| 188 | Run before every merge to `main`: |
| 189 | |
| 190 | - [ ] On a feature branch, not `main` |
| 191 | - [ ] `mypy musehub/` — zero errors |
| 192 | - [ ] `pytest tests/ -v` — all green |
| 193 | - [ ] No `Any`, bare collections, `cast()`, `# type: ignore`, `Optional[X]`, legacy `List`/`Dict` |
| 194 | - [ ] No dead code, no music-domain specific UI elements |
| 195 | - [ ] New DB columns have an Alembic migration |
| 196 | - [ ] Affected docs updated in the same commit |
| 197 | |
| 198 | ## Anti-patterns (never do these) |
| 199 | |
| 200 | - Using `git`, `gh`, or GitHub for anything. Muse and MuseHub are the only VCS tools. |
| 201 | - Working directly on `main`. |
| 202 | - Adding business logic to route handlers — delegate to services. |
| 203 | - Editing an already-applied Alembic migration — always create a new one. |
| 204 | - `Any`, bare collections, `cast()`, `# type: ignore` — absolute bans. |
| 205 | - `Optional[X]`, `List[X]`, `Dict[K,V]` — use modern syntax. |
| 206 | - Music-domain-specific UI elements (audio players, MIDI download buttons) in generic release pages. |
| 207 | - `print()` for diagnostics — use `logging`. |