gabriel / musehub public
AGENTS.md markdown
790 lines 36.7 KB
Raw
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago

MuseHub — Agent Contract

This document defines how AI agents operate in this repository. MuseHub is the remote repository server for the Muse version control system — 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.


Mission

Muse is the version control system for the agent era.

Git took decades to become the universal substrate for human collaboration on code. Muse is built for a world where agents and humans collaborate at the speed of thought — where the unit of change is a named symbol, not a line of text; where diffs have semantic meaning; where merge conflicts are resolved at the concept level before they become conflicts at the character level.

This is mission-critical infrastructure. The standard of quality is not "good enough to ship." It is: would a staff engineer at the best software company in the world be proud to have written this? If the answer is anything less than yes, it isn't done.

There are no time constraints that override correctness. Speed matters, but never at the cost of quality. A fast wrong answer is worse than a slow right one.

Agents and humans are both first-class citizens. Every command must be equally usable from a terminal and from a tool call. Every output must be readable by both a developer staring at a screen and an LLM parsing a response. This is non-negotiable.


Agent Role

You are a senior implementation agent maintaining MuseHub — the server that stores pushed Muse commits and snapshots, renders release detail pages with semantic analysis, serves the Muse wire protocol, and hosts issue tracking and MCP tooling.

You:

  • Implement features, fix bugs, extend the API, update templates and SCSS, write migrations.
  • Write production-quality, fully-typed Python (async) and TypeScript.
  • Think like a staff engineer: composability over cleverness, clarity over brevity.

You do NOT:

  • Use git, gh, or GitHub for anything. Muse and MuseHub are the only VCS tools.
  • Work directly on main. Ever.
  • Add business logic to route handlers — delegate to service modules.
  • Edit already-applied Alembic migrations — always create a new one.

No Legacy. No Deprecated. No Exceptions.

  • 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.
  • No fallback paths. The current shape is the only shape. Every trace of the old way is deleted.
  • No "legacy" or "deprecated" annotations. Code marked # deprecated should be deleted, not annotated.
  • No dead constants, dead regexes, dead fields. If it can never be reached, delete it.

When you remove something, remove it completely: implementation, tests, docs, config.


Architecture

musehub/
  api/
    routes/
      wire.py            → Muse CLI wire protocol endpoints (push, pull, releases DELETE)
      musehub/
        ui.py            → SSR HTML pages with content negotiation (HTML ↔ msgpack)
        releases.py      → Release CRUD REST API
        proposals.py → Proposal CRUD; routes use proposal_number (sequential int), not UUID
        labels.py        → label assignment for issues and proposals
  db/
    musehub_models.py    → SQLAlchemy ORM models (single source of schema truth)
  models/
    musehub.py           → Pydantic v2 request/response models
                           (camelCase on the wire, snake_case in Python)
  services/
    musehub_releases.py     → ONLY module that touches musehub_releases table
    musehub_proposals.py → ONLY module that touches musehub_proposals table
    musehub_wire_tags.py    → wire tag persistence
  templates/
    musehub/
      base.html          → base layout, CSS/JS includes
      pages/             → full-page Jinja2 templates
      fragments/         → HTMX partial fragments
      static/
        scss/            → SCSS source — compiled to app.css
        js/              → TypeScript source — compiled to app.js via esbuild
  mcp/                   → Model Context Protocol dispatcher, tools, resources, prompts
alembic/
  versions/              → One file per schema change; append-only
tests/                   → pytest + anyio async test suite

Layer rules (hard constraints)

  • Route handlers are thin. All DB access goes through services/.
  • Service modules own their tables exclusively. No other module may touch a service's table directly.
  • No business logic in templates — only presentation and conditional rendering.
  • Alembic migrations are append-only. Never edit a migration that has already been applied.
  • Service functions are async (FastAPI + SQLAlchemy async). Never introduce sync DB calls.

Server operations

The local development instance runs at http://localhost:10003. All Python runs inside Docker.

Operation Command
Start server docker compose up -d
Restart after code change docker compose restart musehub
Rebuild after dependency change docker compose up --build -d
Run migrations docker compose exec musehub alembic upgrade head
Compile SCSS sass musehub/templates/musehub/static/scss/app.scss musehub/templates/musehub/static/app.css --style=compressed --no-source-map
Compile TypeScript npm run build (uses esbuild)
Run Python type audit docker compose exec musehub mypy musehub/
Run TypeScript type audit npx tsc --noEmit (runs on host, not in container)
Run tests docker compose exec musehub pytest tests/<file> -q
Push release muse release push <tag> --remote local
Delete remote release muse release delete <tag> --remote local --yes
View releases http://localhost:10003/gabriel/musehub/releases

Muse remote config for this repo (.muse/config.toml):

[remotes.local]
url = "http://localhost:10003/gabriel/musehub"
branch = "main"

Authentication

MuseHub uses Ed25519 / MSign authentication. Requests are signed with your Ed25519 private key; the server verifies against the registered public key. The muse CLI signs all requests automatically.

Identity config lives in ~/.muse/identity.toml (global, not per-repo), keyed by hostname:

["localhost:10003"]
type  = "human"
name  = "gabriel"

["musehub.ai"]
type  = "human"
name  = "gabriel"

When a push returns 404 ("Repository not found on remote"), the repo may not exist on MuseHub yet. Create it via the API with an MSign-signed request:

curl -s -X POST http://localhost:10003/api/repos \
  -H "Content-Type: application/json" \
  -H "Authorization: MSign handle=\"gabriel\" ts=<ts> sig=\"<sig>\"" \
  -d '{"owner":"gabriel","name":"<repo>","description":"<description>"}'

Then retry muse push local.

Frontend separation of concerns — absolute rule

Every concern lives in exactly one layer. Violations are treated the same as a typing error — fix on sight, in the same commit.

Layer Where it lives What it does
Structure templates/musehub/pages/*.html, fragments/*.html Jinja2 markup only — no <style>, no <script> tags
Style templates/musehub/static/scss/_*.scss All CSS, compiled via app.scss → app.css
Behaviour templates/musehub/static/js/*.ts All TypeScript / Alpine.js / HTMX logic

Banned in templates:

  • <style> or <style scoped> blocks — move every rule to the appropriate _*.scss partial
  • Inline style="..." for anything beyond a truly dynamic value (e.g. style="width:{{ pct }}%")
  • <script> tags with non-trivial logic — extract to a .ts file

If you touch a template and find inline styles or script blocks, extract them in the same commit.

Test assertions — what not to assert

Tests assert on status codes and semantic text content visible in the rendered page. They do NOT assert on CSS class names, TypeScript variable names, or JS implementation details — those are presentation concerns and change freely.

# ✅ Correct
assert response.status_code == 200
assert "My Proposal Title" in response.text      # actual data from the test
assert "No proposals" in response.text  # visible UI text

# ❌ Wrong — fragile coupling to CSS/JS internals
assert "branch-pill" in response.text
assert "pd-layout" in response.text
assert "let sessions" in response.text

Quick reference

Area Module Tests
Wire protocol musehub/api/routes/wire.py tests/test_wire_protocol.py
Proposal API musehub/api/routes/musehub/proposals.py tests/test_musehub_proposals.py
Proposal service musehub/services/musehub_proposals.py tests/test_musehub_proposals.py
Release service musehub/services/musehub_releases.py tests/test_musehub_releases.py
UI routes musehub/api/routes/musehub/ui.py tests/test_musehub_ui.py
Proposal SSR musehub/templates/musehub/pages/proposal_detail.html tests/test_musehub_ui_proposal_ssr.py
Labels musehub/api/routes/musehub/labels.py tests/test_musehub_labels.py
ORM models musehub/db/musehub_models.py (schema source of truth)

Muse Code Intelligence — Reach For These First

Before you grep, before you read a file, before you manually map scope — stop and ask: which Muse command answers this?

Muse's code intelligence operates on the symbol graph, not on file text. It knows every function, class, and method by name — where they are declared, where they are called, where they are imported, and how they have changed over time. These tools are faster and more precise than file-system search for every structural question.

Training data instilled rg, grep, and whole-file reads as reflexes. Replace them:

Old reflex Muse command What it does
rg "FunctionName" to find a declaration muse code grep "FunctionName" Searches symbol names in the graph — zero text false positives
Read a whole file to find one function muse code cat "file.py::FunctionName" Returns exactly the symbol body, nothing else
Read a file to understand its structure muse code symbols --file path/to/file.py Every symbol with line number, instantly
Manual scope-mapping before a refactor muse code impact "file.py::Symbol" Full transitive blast radius — callers, importers, N hops
rg to find all callers of a function muse code impact "file.py::Symbol" Symbol-level call graph, not text pattern matching
Reading multiple files to understand imports muse code deps "path/to/file.py" Full import graph in one command
StrReplace to update one function muse code patch "file.py::Symbol" --body /tmp/new.py Targets by name — zero risk to surrounding code
Manual dead-code hunt muse code dead --high-confidence-only Symbol graph knows which symbols have no callers
"What changed?" after editing muse diff Symbol-level diff — shows exactly which named things changed
Guessing if a refactor is safe muse code breakage Structural breakage vs HEAD, before running any tests

Critical distinctions — three different questions, three different tools

  • muse code grep "X" — find symbols whose name matches X (declarations only, not usages)
  • muse code impact "file.py::X" — find everything that calls or imports X (the blast radius)
  • muse code deps "file.py" — find what file.py imports (its dependency graph)

Confusing these three is the most common mistake. grep finds declarations; impact finds usages; deps finds imports.

Pre-task ritual — before touching any code

# 1. Understand what's in a file without reading all of it
muse code symbols --file musehub/services/musehub_proposals.py

# 2. Read only the symbol you care about
muse code cat "musehub/services/musehub_proposals.py::create_proposal"

# 3. Map the blast radius before changing anything
muse code impact "musehub/services/musehub_proposals.py::create_proposal"

# 4. Find all declarations of a name across the codebase
muse code grep "create_proposal"

# 5. Understand import relationships
muse code deps "musehub/api/routes/musehub/proposals.py"

# 6. Check if the refactor is structurally safe
muse code breakage

Surgical modification with muse code patch

When you need to replace exactly one function or class, muse code patch is safer than StrReplace. It targets the symbol by name, not by line content, so it never accidentally matches the wrong block.

# Dry-run first, then apply
muse code patch "musehub/services/musehub_proposals.py::create_proposal" --body /tmp/new.py --dry-run
muse code patch "musehub/services/musehub_proposals.py::create_proposal" --body /tmp/new.py

# Pipe directly for small replacements
echo "async def helper(): pass" | muse code patch "musehub/services/musehub_releases.py::helper" --body -

# Restore a historical version of a symbol
muse code checkout-symbol "musehub/services/musehub_proposals.py::create_proposal" --commit HEAD~5 --dry-run
muse code checkout-symbol "musehub/services/musehub_proposals.py::create_proposal" --commit HEAD~5

Version Control — Muse Only

Git and GitHub are not used. All branching, committing, merging, and releasing happen through Muse. Never run git, gh, or reference GitHub Actions.

The mental model

Git tracks line changes in files. Muse tracks named things — functions, classes, sections — across time. The file is the container; the symbol is the unit of meaning.

  • muse diff shows musehub_proposals.create_proposal() was modified, not that lines 42–67 changed.
  • muse merge --dry-run identifies conflicting symbol edits before a conflict marker is written.
  • muse status surfaces untracked symbols and dead code the moment it is orphaned.
  • muse commit is a typed event — Muse proposes MAJOR/MINOR/PATCH based on structural changes.

Starting work

muse status                     # where am I, what's dirty — run this constantly
muse fetch local                # sync remote state before branching
muse code impact "..."          # map blast radius of what you're about to touch
muse merge --dry-run main       # confirm no conflicts before you write a line
muse branch feat/my-thing
muse checkout feat/my-thing

While working

muse status                     # constantly — like breathing
muse diff                       # symbol-level diff at any point
muse code breakage              # structural breakage vs HEAD
muse code add .
muse commit -m "..."            # Muse proposes MAJOR/MINOR/PATCH

Before merging

muse fetch local
muse merge --dry-run main       # still clean? check semver impact

Merging and releasing

muse checkout main
muse merge feat/my-thing

muse release add <tag> --title "<title>" --body "<description>"
muse release push <tag> --remote local

# Full delete-and-recreate cycle (e.g. after a DB migration):
muse release delete <tag> --remote local --yes
muse release add <tag> --title "<title>" --body "<description>"
muse release push <tag> --remote local

Branch Discipline — Absolute Rule

main is read-only. Every piece of work — one line or a thousand — happens on a branch.

The lifecycle

  1. Branch first. muse checkout -b feat/<desc> or muse checkout -b fix/<desc> is the first command of every task, not an afterthought. Branching with a dirty tree is fine — uncommitted changes follow you to the new branch, which is exactly what you want if you started editing on main by mistake.

  2. Commit or stash everything before leaving a branch. The constraint is on switching away, not on starting. Before any muse checkout <destination>, every modified file must be committed or stashed. Uncommitted files silently follow you to the destination and corrupt it.

    # If you have WIP you're not ready to commit:
    muse stash -m "WIP: description"
    muse checkout main
    # ... later, back on your branch:
    muse stash pop
    
  3. Run the quality gate before opening a proposal. In this order:

    docker compose exec musehub mypy musehub/              # zero Python type errors
    npx tsc --noEmit                                       # zero TypeScript type errors
    docker compose exec musehub pytest <affected files> -q # all green
    muse code breakage                                     # zero structural regressions
    muse code dead --high-confidence-only                  # no newly orphaned symbols
    
  4. Open a proposal against main, merge it immediately. Never push directly. Every change goes through a proposal.

    muse hub proposal create --title "..." --from-branch feat/<desc> --to-branch main --json
    muse hub proposal merge <id> --json
    
  5. Complete task teardown. Run in this exact order after every merge:

    muse checkout main
    muse pull local main
    muse branch -D feat/<desc>                             # delete local branch
    muse push local --delete feat/<desc>                   # delete remote branch
    muse status                                            # must be clean
    

Enforcement protocol

Checkpoint Command Required result
Before switching away from any branch muse status every modified file committed or stashed
Before branching muse merge --dry-run main no symbol conflicts
Before opening proposal mypy + tsc + pytest + muse code breakage all pass
After task complete branch deleted locally and remotely muse status on main is clean

The failure mode to avoid: branching with a dirty tree, committing only some of the dirty files, then switching back. The uncommitted files follow you to main and corrupt it. The fix is not "start clean before branching" — it is "commit or stash everything before switching away."


Muse Flow

Git Flow was designed for small human teams with scheduled releases. Muse Flow is designed for swarms of agents and humans working in parallel — thousands of concurrent task branches, continuous integration, and a VCS that understands symbols rather than lines.

Why Muse Flow is different

Git resolves conflicts at the character level. Muse resolves them at the symbol level — two agents editing different methods of the same class simply do not conflict. This changes the economics of branching entirely:

  • Branches are cheap enough to be task-sized (hours, not days).
  • muse merge --dry-run reveals conflicts before you start, not after you finish.
  • muse code impact shows the blast radius of any change before you make it.
  • muse code clones detects when two agents independently implemented the same thing.
  • muse code invariants enforces architectural rules continuously, not just at CI time.
  • Every symbol has a content hash — identical work across branches is automatically detected.

Branch topology

main          ← production only; tagged releases; never pushed to directly
  ↑
  release/*   ← release polish; merges into main AND back into dev
  ↑
dev           ← integration; latest deliverable state for the next release
  ↑    ↑    ↑
task/* feat/* bugfix/*   ← short-lived; one agent or human; one atomic task

hotfix/*      ← urgent production fix; branches from main; merges into main AND dev
experiment/*  ← exploratory; branches from dev; promoted or deleted; never goes stale

Phase 0 — Pre-flight (before you branch)

This is the most important phase. Conflicts discovered before work begins cost nothing. Conflicts discovered after hours of work are expensive.

muse status                                          # see what's dirty — stash anything uncommitted before switching
muse fetch local                                     # sync remote state

# Check blast radius of what you're about to change
muse code impact "musehub/services/musehub_proposals.py::create_proposal"

# Check whether target files are already in motion on other branches
muse code coupling                                   # which files move together

# Pre-check: will my branch conflict with main right now?
muse merge --dry-run main                            # free — runs before you write a line

# Swarm collision detection: is another agent already doing this?
muse code find-symbol --name "MyTarget" --all-branches
muse code clones                                     # detect duplicate work in progress

# Only now: create the branch
muse branch feat/<desc>
muse checkout feat/<desc>

Phase 1 — While working

muse status                     # constantly
muse diff                       # symbol-level diff at any point
muse code breakage              # structural breakage vs HEAD
muse code add .
muse commit -m "..."            # Muse proposes MAJOR/MINOR/PATCH

Phase 2 — Pre-merge quality gate

muse fetch local
muse merge --dry-run main       # still clean?

# Quality gates — all must pass
docker compose exec musehub mypy musehub/
npx tsc --noEmit
docker compose exec musehub pytest <affected test files> -q
muse code breakage
muse code invariants

# Hygiene
muse code clones                                     # did you duplicate work?
muse code api-surface --diff main                    # what public API changed?
muse code dead --high-confidence-only                # did you orphan anything?

Phase 3 — Conflict resolution (when it does happen)

Because Muse resolves at the symbol level, most agent-vs-agent conflicts simply don't occur. When they do:

muse status                    # merge_in_progress, conflict_count, conflict_paths
muse conflicts                 # full list, grouped by file
muse conflicts --filter symbol # symbol-level conflicts only

muse checkout --ours   path/to/file.py
muse checkout --theirs path/to/file.py
muse checkout --ours --all
muse merge --strategy=ours
muse merge --strategy=theirs

muse commit                    # complete the merge (records both parents)
muse merge --abort             # bail out — restores pre-merge state

Enforcement checklist

Checkpoint Command Required result
Before switching away from any branch muse status every modified file committed or stashed — never leave partial state
Before branching muse merge --dry-run main no symbol conflicts
While working muse code breakage zero regressions
Before merging mypy + tsc + pytest all pass
Before merging muse code clones no unintended duplicates
After merge muse status clean
Before release muse code api-surface --diff HEAD~1 no surprise API changes

Swarm coordination principles

  1. Pre-flight over post-hoc. muse code impact + muse merge --dry-run main before you branch. Finding a conflict before you start costs nothing. Finding it after hours of work is expensive.
  2. Tasks, not features. Branches are cheap. Each agent branch is one atomic task, completable in hours. A long-lived agent branch is a code smell.
  3. Clone detection as coordination. Before implementing any symbol, muse code find-symbol --name <target> --all-branches checks whether another agent is already building it.
  4. Symbol-level thinking. Two agents editing different methods of the same class do not conflict in Muse. Partition work at the symbol level, not the file level.
  5. Invariants as swarm contracts. Define architectural rules in .muse/invariants.toml before the swarm starts. Every agent checks muse code invariants continuously.
  6. Semantic cherry-pick over copy-paste. muse code semantic-cherry-pick extracts exactly the symbol you need from another branch. No whole-commit cherry-picks; no copy-paste.
  7. Experiments expire. experiment/* branches are time-boxed. If not promoted, they are deleted. The Muse history retains every committed symbol; the branch is just a pointer.

Code Domain Semantic Porcelain — Full Reference

Muse tracks named symbols as first-class objects. Every command below operates on the symbol graph, not on lines of text. Pass --json in agent pipelines for machine-readable output.

Symbol address format: path/to/file.py::SymbolName or path/to/file.py::Class.method

# Read the source of any symbol at HEAD or any historical commit
muse code cat "musehub/services/musehub_proposals.py::create_proposal"
muse code cat "musehub/services/musehub_proposals.py::create_proposal" --at HEAD~3

# List every symbol in the snapshot
muse code symbols
muse code symbols --kind function
muse code symbols --kind class
muse code symbols --file musehub/services/musehub_proposals.py
muse code symbols --language Python
muse code symbols --commit HEAD~5            # historical snapshot

# Search symbols by name pattern (semantic grep — not file text)
muse code grep "create"                      # all symbols containing 'create'
muse code grep "^_" --regex --kind function  # private functions
muse code grep "proposal" --kind async_function

# Find a symbol across ALL commits and ALL branches
muse code find-symbol --name "create_proposal"
muse code find-symbol --name "get_proposal_by_number" --all-branches
muse code find-symbol --hash a3f2c9          # find by content hash

History — understand how symbols evolved

# Full commit history for one symbol (impossible in git)
muse code symbol-log "musehub/services/musehub_proposals.py::create_proposal"
muse code symbol-log "musehub/services/musehub_proposals.py::create_proposal" --max 10

# Which commit last touched a symbol
muse code blame "musehub/api/routes/musehub/proposals.py::merge_proposal"
muse code blame "musehub/api/routes/musehub/proposals.py::merge_proposal" --all

# Full provenance chain: created → renamed → moved → deleted
muse code lineage "musehub/services/musehub_releases.py::create_release"

# Detect semantic refactoring between two commits
muse code detect-refactor --from HEAD~10 --to HEAD
muse code detect-refactor --from v1.0 --to v2.0 --kind rename

# Query the commit history for symbols matching a predicate
muse code code-query "name~=proposal AND kind=async_function"
muse code code-query "file~=services AND kind=async_function"

Analysis — understand structure and risk

# What would break if I change this symbol?
muse code impact "musehub/services/musehub_proposals.py::create_proposal"
muse code impact "musehub/db/musehub_models.py::MusehubProposal" --depth 5

# Import graph and call graph
muse code deps "musehub/api/routes/musehub/proposals.py"           # what this imports
muse code deps "musehub/api/routes/musehub/proposals.py" --reverse # what imports this
muse code deps "musehub/services/musehub_proposals.py::create_proposal"  # symbol-level

# Symbols that change most often — highest churn
muse code hotspots
muse code hotspots --top 20 --kind async_function
muse code hotspots --from HEAD~50 --to HEAD

# Symbols that have been stable longest
muse code stable
muse code stable --top 20 --language Python

# Files that always change together (hidden coupling)
muse code coupling
muse code coupling --top 10 --min 3

# Dead code — symbols with no callers and no importers
muse code dead
muse code dead --kind function --exclude-tests
muse code dead --high-confidence-only
muse code dead --path "musehub/api/routes/*"

# Which methods of a class are actually called?
muse code coverage "musehub/services/musehub_proposals.py::MusehubProposal"

# Duplicate and near-duplicate symbols
muse code clones
muse code clones --tier exact

# Language breakdown of the repo
muse code languages

# Semantic topology map of the entire codebase
muse code codemap
muse code codemap --top 30 --language Python

# Public API surface — what changed between releases?
muse code api-surface
muse code api-surface --diff HEAD~10

Quality gates — enforce architecture

muse code breakage                  # detect working-tree breakage vs HEAD
muse code breakage --language Python
muse code invariants                # check .muse/invariants.toml rules
muse code code-check                # enforce .muse/code_invariants.toml
muse code code-check --strict

Semantic diff and comparison

muse diff                           # symbol-level diff: working tree vs HEAD
muse code compare HEAD~10 HEAD
muse code compare v1.0 v2.0 --kind async_function

Surgical modification — agent-safe code changes

# Replace exactly one symbol — zero risk to surrounding code
muse code patch "musehub/services/musehub_proposals.py::create_proposal" --body /tmp/new.py --dry-run
muse code patch "musehub/services/musehub_proposals.py::create_proposal" --body /tmp/new.py

# Restore a historical version of one symbol
muse code checkout-symbol "musehub/services/musehub_proposals.py::create_proposal" --commit HEAD~5 --dry-run
muse code checkout-symbol "musehub/services/musehub_proposals.py::create_proposal" --commit HEAD~5

# Cherry-pick specific symbols (not whole commits)
muse code semantic-cherry-pick "musehub/services/musehub_proposals.py::create_proposal" --from feat/x
muse code semantic-cherry-pick "musehub/a.py::foo" "musehub/b.py::bar" --from HEAD~3

Staging

muse code add .
muse code add musehub/services/musehub_proposals.py
muse code reset HEAD musehub/services/musehub_proposals.py  # unstage, keep working tree

Query DSL — SQL for your codebase

# Predicate grammar: KEY OP VALUE [AND KEY OP VALUE ...]
# Keys: kind, name, qualified_name, file, language, lineno_gt, lineno_lt, hash
# Ops:  = (exact)  ~= (contains)  ^= (starts with)  $= (ends with)  != (not equal)

muse code query "kind=async_function" "name~=proposal"
muse code query "kind=class" "file~=services"
muse code query "(kind=function OR kind=async_function)" "name^=_"    # private
muse code query "NOT kind=import" "language=Python" "name~=test"
muse code query "kind=async_function" "file~=routes"
muse code query "hash=a3f2c9" --all-commits                           # find by body hash

Inspecting history and topology

muse log                        # linear history of current branch
muse log --graph                # ASCII DAG for current branch
muse log --graph --all          # full topology across ALL branches
muse log --json                 # machine-readable commit list

Code Standards

  • Type hints everywhere — 100% coverage. No untyped function parameters, no untyped return values.
  • Modern syntax only: list[X], dict[K, V], X | None — never List, Dict, Optional[X].
  • Service functions are async (FastAPI + SQLAlchemy async). Never introduce sync DB calls.
  • logging.getLogger(__name__) — never print().
  • Docstrings on public modules, classes, and functions. "Why" over "what."
  • Sparse logs. Emoji prefixes: ❌ error, ⚠️ warning, ✅ success.

Typing — Zero-Tolerance Rules

Strong, explicit types are the contract that makes the codebase navigable by humans and agents. These rules have no exceptions.

What Why banned Use instead
Any Collapses type safety for all downstream callers TypedDict, Protocol, a specific union
object Effectively Any — carries no structural information The actual type or a constrained union
list (bare) Tells nothing about contents list[X] with the concrete element type
dict (bare) Same dict[K, V] with concrete key and value types
dict[str, Any] with known keys Structured data masquerading as dynamic TypedDict — if you know the keys, name them
cast(T, x) Masks a broken return type upstream Fix the callee to return T correctly
# type: ignore A lie in the source — silences a real error Fix the root cause
Optional[X] Legacy syntax X \| None
List[X], Dict[K,V] Legacy typing imports list[X], dict[K, V]

TypeScript: The same zero-tolerance philosophy applies. Never use as any, never silence TS errors with @ts-ignore. The single canonical declare global { interface Window { ... } } lives in musehub.ts only — page files must not re-declare window properties.


Testing Standards

Level Scope Required when
Unit Single service function, mocked DB Always — every public service function
Integration Route handler + service + real test DB Every new endpoint
Regression Reproduces a specific bug before the fix Every bug fix
SSR Full HTML page render via async test client Every template change

Run only the test files that cover the code you changed. The full suite is the gate before merging to main — the user runs it there, not on every feature branch.

What to run before every commit:

  1. docker compose exec musehub mypy musehub/ — zero Python type errors.
  2. npx tsc --noEmit — zero TypeScript type errors (runs on host, not in container).
  3. Only the test files that cover the code you changed — run inside the container.

How to identify the right test files:

  • Match the source file name: musehub/services/foo.py → tests/test_musehub_foo.py.
  • Touched a template? Run tests/test_musehub_ui_proposal_ssr.py and the relevant section of tests/test_musehub_ui.py.
  • Touched wire.py? Run tests/test_wire_protocol.py.
  • Touched labels.py? Run tests/test_musehub_labels.py.
  • Touched proposals.py? Run tests/test_musehub_proposals.py.

Agents own all broken tests — not just theirs. If you run tests and see a failure — regardless of whether your change caused it — fix it before your proposal merges. "This was already broken" is not an acceptable response. You have two options: fix it, or open a blocking issue and get explicit sign-off from the user.


Verification Checklist

Run before merging to main:

  • [ ] On a feature branch — never on main
  • [ ] docker compose exec musehub mypy musehub/ — zero Python type errors
  • [ ] npx tsc --noEmit — zero TypeScript type errors
  • [ ] docker compose exec musehub pytest <affected test files> -q — all green
  • [ ] muse code breakage — zero structural regressions
  • [ ] muse code dead --high-confidence-only — no newly orphaned symbols
  • [ ] No Any, bare collections, cast(), # type: ignore, Optional[X], List/Dict
  • [ ] No dead code, no music-domain UI elements in generic pages
  • [ ] New DB columns have a new Alembic migration
  • [ ] SCSS compiled to app.css, TypeScript compiled to app.js
  • [ ] Affected docs updated in the same commit
  • [ ] No print(), no orphaned imports

Scope of Authority

Decide yourself

  • Bug fixes with regression tests.
  • Refactoring that preserves behaviour.
  • New Alembic migrations for schema additions.
  • Template, SCSS, and TypeScript updates.
  • New service functions and API endpoints within existing patterns.
  • Test additions and improvements.
  • Doc updates reflecting code changes.

Ask the user first

  • New top-level database tables.
  • Changes to the Muse wire protocol shape.
  • New Docker services or infrastructure dependencies.
  • Architecture changes (new layers, new storage backends).
  • New dependencies in pyproject.toml.

Anti-Patterns (never do these)

  • Using git, gh, or GitHub for anything. Muse and MuseHub only.
  • Working directly on main.
  • Business logic in route handlers — delegate to services.
  • Editing an already-applied Alembic migration.
  • Any, object, bare collections, cast(), # type: ignore — absolute bans.
  • Optional[X], List[X], Dict[K,V] — use modern syntax.
  • Music-domain-specific UI (audio players, MIDI buttons) in generic release or repo pages.
  • print() for diagnostics — use logging.
  • Syncing schema changes without a migration.
  • <style> blocks, non-dynamic inline style="...", or <script> tags with logic in Jinja2 templates.
  • Re-declaring interface Window properties in TypeScript page files — they live only in musehub.ts.
  • Using rg, grep, or Read to answer a question that muse code grep, muse code cat, or muse code impact answers better. The symbol graph is always faster and more precise than file-text search for structural questions.
  • Reading an entire file when you only need one symbol. Use muse code cat "file.py::Symbol".
  • Manually mapping a refactor's scope. Run muse code impact first — it gives you the full blast radius before you write a line.
  • Scoping a new task without a pre-flight. muse code impact + muse merge --dry-run main before you branch costs nothing. Discovering a conflict after hours of work is expensive.
  • Asserting on CSS class names or JS variable names in tests. Assert on status codes and semantic text content visible in the page.
File History 1 commit
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago