Your project's architecture plan as markdown files in the repo: typed cards, connected into a graph, validated by lint, diffed by git, readable by humans on GitHub and by AI agents with nothing more than file access.
constellation/
plan.md ← the living project plan (PLAN-PROJECT)
api/API-TICKETS.md ← one file per card; the filename IS the handle
datatype/DATATYPE-TICKET.md
db/DB-TICKETS.md
flow/FLOW-CREATE-TICKET.md
...
Each card is frontmatter (structure) + markdown (narrative). Connections come from
frontmatter: a plain connections: list of handles, plus handle-shaped values in
other frontmatter fields. A [[HANDLE]] link in prose or a handle used as a Mermaid
node ID is a hyperlink — clickable, linted, but never a graph edge. The indexer
derives the graph; nothing derived is ever stored.
Why files? Plans drift from code when they live somewhere else. Here a plan
change is a commit: it rides the same branch and PR as the code it describes,
merges per-card, and "what changed in the plan" is git diff -- constellation/.
npm install -g @magic-spells/constellation # the `constellation` binary
# or run without installing:
npx @magic-spells/constellation lintRequires Node ≥ 22.
constellation init # scaffold constellation/ with a starter plan.md
constellation lint # validate handles, references, folders, schemas
constellation rename A-X A-Y # rename a card + rewrite every reference to it
constellation mcp # run the MCP server (stdio) for AI agents
constellation serve # open the local viewer (editable; --readonly to disable)
constellation repos # list sibling repos declared in connected_repos
constellation working # print the working memory set (what the SessionStart hook runs)
constellation add skills # install the skills (authoring + /working) into ~/.claude, ~/.codex, …
constellation version # print the CLI version (`v` also works)
constellation upgrade # npm install -g @magic-spells/constellation@latestLint errors (broken graph: bad handles, dangling structured references, duplicates) exit non-zero for CI; warnings (wrong folder, schema violations, unknown fields, dangling prose links) don't block.
| Path | What |
|---|---|
.constellation/ |
Working memory — the session scratchpad (gitignored except its CLAUDE.md); never part of the plan |
constellation/ |
Constellation's own plan — the format spec, MCP design, and architecture as connected cards (formerly docs/); also a flagship real-world plan |
schemas/ |
JSON Schemas: card.json (reserved keys) + one per type |
skill/ |
AI authoring skill: SKILL.md + per-type references with golden examples |
skill-working/ |
The user-invocable /working command skill, installed beside the authoring one |
src/core/ |
Parser, reference extraction, indexer, schema validation, lint |
src/cli/ |
The constellation binary (init, lint, rename, mcp, serve, repos, working, add skills, version, upgrade) |
src/mcp/ |
MCP server: hydrated retrieval, validated writes, git tools |
viewer/ |
The Puzzle single-page viewer — themes, card pages, neighborhood diagrams |
examples/constellation/ |
Golden sample plan — one card of every type, lints clean, doubles as the test fixture |
npm install
npm test # vitest
npm run lint:examples # lint the golden plan
npm run build # tsc → dist/constellation mcp exposes the plan to AI agents over stdio:
- Self-serving:
orientopens a session with the whole plan at a glance in one small call;describe_typehands back a card type's schema and authoring guidance, so an agent can write correct cards with no skill installed. - Hydrated retrieval:
get_card,search, andtraversecan return connected cards with their complete frontmatter and body in one call.list_cardsandtraversefilter by status —["planned", "building", "none"]is the backlog view (everything not yet built). - Validated writes:
create_card/create_cards,update_card,append_note/edit_section(byte-cheap memory writes),set_verified,rename_card(rewrites every reference plan-wide),delete_card,add_connection(s),remove_connection— every write lints and returns issues. Body-only updates never reformat frontmatter. - Queryable memory:
searchmatches appended note text;list_noteslists notes across cards by kind (every gotcha / decision in one call). - Git-powered change tracking:
diff_plan(per-card changes since the sync marker),plan_log,set_sync_point,stale_report/check_sync(bound-code drift since a card was verified),check_integrity. - Connected repos (multi-repo): a plan can declare sibling repos
(
add_connected_repo/list_connected_repos/remove_connected_repo); every tool takes an optionalreposelector to read or write a sibling's plan — or a package's plan in a monorepo (see Monorepos). Omit it and single-repo behavior is unchanged. - Visual viewer:
start_viewer/stop_vieweropen and close the local web viewer from inside an agent session, returning a clickable URL.
Run from your repo root, so the server starts there and finds the plan:
# Run straight from npm — no install needed:
claude mcp add constellation -- npx -y @magic-spells/constellation mcp
# Or, if installed globally (npm i -g @magic-spells/constellation):
claude mcp add constellation -- constellation mcp
# Share with everyone who clones the repo (writes .mcp.json):
claude mcp add --scope project constellation -- npx -y @magic-spells/constellation mcpManage it with claude mcp list, claude mcp get constellation, and
claude mcp remove constellation.
To build a plan from an existing codebase (or audit one), ask the agent to bootstrap or
audit — the server ships bootstrap_plan and audit_plan prompts (slash commands
in Claude Code) that walk the code macro→micro: follow the data, follow the user/auth, then
step back and pressure-test the plan for blind spots (missing unhappy paths, auth gaps,
forgotten cross-cutting concerns) and recommend. The full method is in
skill/methodology.md.
Codex CLI keeps MCP servers in ~/.codex/config.toml (TOML, not JSON). Add it with the CLI:
# Run straight from npm — no install needed:
codex mcp add constellation -- npx -y @magic-spells/constellation mcp
# Or, if installed globally (npm i -g @magic-spells/constellation):
codex mcp add constellation -- constellation mcp…or hand-edit the config (a project-scoped .codex/config.toml also works in trusted repos):
[mcp_servers.constellation]
command = "npx"
args = ["-y", "@magic-spells/constellation", "mcp"]
cwd = "/path/to/your/repo" # so the server walks up to your planLaunch codex from your repo root (or set cwd above) so the server starts there and finds
the plan — otherwise tools return NO_PLAN_FOUND. Run /mcp inside a Codex session to confirm
it's connected; manage it with codex mcp list and codex mcp remove constellation.
Hand-edit the client's config (Claude Desktop, a project .mcp.json, etc.):
{
"mcpServers": {
"constellation": {
"command": "constellation",
"args": ["mcp"],
"cwd": "/path/to/your/repo"
}
}
}Set cwd to your repo root (or any folder inside it). The server finds the plan by
walking up from its working directory; without cwd it inherits the client's, which
may not be your project — in which case tools return NO_PLAN_FOUND.
Cards are durable architecture; a session also needs short-term memory. .constellation/
sits beside the plan (dotted, gitignored, never a card — nothing in it is indexed,
linted, diffed or shown in the viewer) and holds one line per live item: what is in flight,
in which worktree, held by which agent, what waits on the user, what was decided. Agents
read it at session start and right after every compaction — that is the point: a compaction
summary is written by a model with no tool access, so the state that must not depend on it
lives on disk instead. working_list / working_set / working_drop / working_log /
working_init are the MCP tools; orient embeds the set when the folder exists.
Updated 2026-09-17 06:56 · branch `release/1.0.0` @ 62da1b5 · next G3 C11 P4 F2 T15 Q8 I9 D12
## GOAL
- G1 [5] Finish Phase 6 so the platform can replace Clerk → [[FEATURE-PHASE-6-COMMERCE]]
## CONSTRAINT
- C3 [5] "Fable tokens use a lot of my limits" — Fable agents only for narrow passes
## FOCUS
- F1 [5] Waiting on the Codex webhook re-review
## TASK
- T12 [4] Stripe webhook — wt stripe-webhook, 148846a, opus a2c1 → merge
Types, in file order: GOAL, CONSTRAINT, PLAN, FOCUS, TASK, QUESTION,
IDEA, DECISION. Ids are allocated per type and never reused; [1-5] is importance
(what gets cut first). There is no status field — an item is in the file or it is dropped,
and working_drop with a reason appends the reason to .constellation/log/YYYY-MM-DD.md,
which is the history. .constellation/CLAUDE.md carries the rules and is the one file in
the folder that is committed.
In a session, /working prints the set (and applies one instruction, e.g. /working drop T12); ! npx constellation working prints it with no model turn at all.
constellation working prints the set (silent, exit 0, when there is no folder), so it is
safe as a SessionStart hook — constellation working install-hook, or working_init { hook: true }, writes this into the repo's .claude/settings.json:
{
"hooks": {
"SessionStart": [
{ "matcher": "startup|resume|compact|clear",
"hooks": [ { "type": "command", "command": "npx --no-install constellation working 2>/dev/null || true" } ] }
]
}
}A plan doesn't have to live at the git root. In a monorepo each package keeps its own plan, and the root holds at most a thin signpost that routes to them:
packages/puzzle/constellation/plan.md ← the framework's plan
packages/puzzle-pieces/constellation/plan.md ← the registry's plan
constellation/plan.md ← signpost: connected_repos, no cards
A card's path: and code_refs are relative to its plan's code root — the
folder containing that constellation/ dir, or the code_root field on the
plan's frontmatter. Staleness, the version check, commit scoping and code
attachment all follow the code root, so each package measures drift against its
own history.
The signpost is plan.md and nothing else: its connected_repos names the
package plans (puzzle → packages/puzzle), so repo=puzzle addresses that
plan from any MCP tool. Never init_plan a full plan at a monorepo root —
architecture cards belong to the package plans, and cards never connect across
plans.
constellation serve at a monorepo root hosts every plan in the repo from
one server, with a plan-switcher dropdown in the viewer; --plan <id> picks
which one opens by default, and constellation serve <path> still serves exactly
one. Discovery runs at startup and never crosses into a nested .git, so a
brand-new plan needs a restart.
Single-repo behavior is unchanged: the code root is the repo root, there's no dropdown, and every path and URL stays what it was.
constellation serve renders the plan as a local website, editable in place
(pass --readonly to disable writes). Five colour schemes toggle in the header —
observatory (dark, star-field; the default), default, warm, void and
dim — each with a light / dark / follow-system switch.
The Overview page is a status board for the plan:
- a health strip — the freshness verdict, the counts that qualify it (cards, connections, integrity, drift), and a Set sync point button that stamps the baseline every claim card is measured against
- Releases — every
RELEASEcard newest-first, the one still in flight expanded with itsFEATUREcards grouped Breaking / Features / Fixes / Chores (from each feature'schange:field), the shipped ones collapsed and openable. A release describes itself from the features pointing at it — never a hand-written changelog - Activity — plan commits and code commits in one stream, tagged by kind
- Code drift — the cards whose bound code moved since they were last verified, as a verdict rather than a list, with the fix named when nothing is tracked yet
- Notes — the latest
append_notememory across cards
Card pages show structured fields, the
markdown body, connection chips in both directions, and a small constellation
diagram of the card's neighborhood — its nodes tinted by card type. Mermaid blocks
render in-browser, [[HANDLE]] links navigate, and the page live-reloads when plan
files change on disk.
constellation serve # http://localhost:4747 (walks upward if busy; assets ship prebuilt)
npm run build:viewer # only when developing from sourceIn an agent session you don't need the CLI — ask Claude to open the viewer and it
calls the start_viewer MCP tool, which returns the URL (stop_viewer closes it).
Made by Cory Schulz