Architecture map
Turn a repository into a place you can walk around: an isometric city where
every building is a real subsystem sized by its real weight, every line is a
call path that exists in the code, and every moving dot is a payload the app
actually ships.
The one rule
Prose, groups and flows are authored. Counts, coverage and geometry are
measured.
No scanner can say what a subsystem is for, and no human can keep file counts
honest. Every good property of this page falls out of that line — including the
fact that it does not rot, because "unmapped files" is only meaningful once a
human has claimed the rest.
Do not try to generate the authored half mechanically. Read the code and write
about it. That is the work, and it is why this is a skill rather than a
codegen script.
Before you start
Read
. It has the voice, the archetype vocabulary, and
worked examples of good and bad node prose. Read
only
if you need to hand-tune footprints or edge routes.
Step 1 — Detect
Answer these from the repo. Do not ask.
| Question | Where to look |
|---|
| Framework and router | , vs vs , , , |
| Design tokens | global stylesheet for custom properties; ; any / module |
| Dark mode mechanism | class, , or |
| Test runner | scripts, , |
| Package manager | lockfile |
| Monorepo | , , |
| Existing map | a previous — if present, this is an update |
Then run the proposer to get a first read of the shape. It lives beside this
file, not in the repo you are mapping, so resolve its path first:
— the absolute path of the directory containing
this
SKILL.md, which your harness reported when it loaded this file. It differs
per tool (
~/.claude/skills/architecture-map
,
~/.codex/skills/architecture-map
,
~/.agents/skills/architecture-map
, a
plugin cache, or a project-local
). Substitute the literal
path; do not rely on an environment variable.
bash
node "$SKILL_DIR/scripts/propose-coverage.mjs" --root . --target 22
It returns directory clusters with file counts and line totals, plus suggested
groups. Treat it as a draft, not an answer — it knows where code is, not what
it does.
Step 2 — Ask exactly four questions
Ask them together — in one structured-question call if your harness has one
(Claude Code:
), otherwise as a single numbered message — then
work uninterrupted. Do not drip-feed them one at a time.
- Where should it live? Recommend — a segment reads
as "internal tool" and sorts away from real routes. Offer and
.
- What should it cover? Whole repo / source only / one package. Preselect
sensibly if it is a monorepo.
- Design system. State what you detected — "Tailwind v4 with CSS custom
properties" — and offer: use it, or the bundled neutral palette.
- Extras. Wire the freshness check into CI? Add the sync to /
? Include the version-history dropdown?
Step 3 — Install the core
Copy
,
and
into
the repo under the path you agreed (e.g.
). These are dependency-free
apart from React, and typecheck under
.
Then write
at the repo root:
json
{
"coverage": "src/architecture/coverage.json",
"output": "src/architecture/measured.generated.ts",
"sources": ["src/**/*.{ts,tsx}", "scripts/**/*.mjs"],
"ignore": ["next-env.d.ts"]
}
Copy
$SKILL_DIR/scripts/architecture-sync.mjs
into the repo's own
and add
"architecture:sync": "node scripts/architecture-sync.mjs"
.
Adapt the theme
Edit
only. Point each semantic name at the repo's tokens:
ts
export const paint = {
surface: 'var(--tsc-background)',
border: 'var(--tsc-foreground-tertiary)',
accent: 'var(--tsc-brand)',
// …
}
If the repo has no design system, leave the defaults — they define
fallbacks and work standalone.
Never reach for a host token
anywhere except this file.
Step 4 — Author the graph
This is the real work. Write
exporting
,
,
,
and
, typed by
.
Groups — 4–7 neighborhoods, named the way the team talks: "Entry &
control", "The pixel pipeline", "Outside world". Not "utils" and "lib".
Nodes — aim for 15–25. For each, read the actual files and write:
- — one or two sentences, plain language, no jargon
- — the interesting decision, not a dependency list
- — a short noun phrase for the flow captions: "the session gate"
- — real paths a reader can open
ts
const { archetype, params } = deriveArchetype(measure)
const height = deriveHeight(measure)
const footprints = packLayout(inputs, GROUPS.map((g) => g.id))
Keep a hand-written footprint if a human already tuned one — the merge prefers
the authored value.
Edges — real call and data paths only. If you cannot point at the code that
makes the call, do not draw the line. Add
waypoints when a route would
otherwise cut through a building.
Flows — 3–6, each an ordered list of edge ids with a payload name. These are
the page's verbs and the first thing a newcomer presses. Find them by tracing
real paths: sign-in, the main create/read loop, the expensive background job.
Coverage — write
so every source file is claimed exactly
once.
-prefixed keys are notes. Use
when a nested directory must
win over its parent. Then:
bash
node scripts/architecture-sync.mjs
Iterate until it reports zero unmapped, or until what remains genuinely is not
part of the system.
Step 5 — Mount it
Create the route for the detected framework — see
.
Import
once. Pass
in from your graph module
plus
from the generated file.
Add
if the route is public: this is a tool handed out by
link, not a search result.
Step 6 — Verify, then be honest
- Typecheck and lint.
node scripts/architecture-sync.mjs --check
— must pass.
- Run the app and look at it. Screenshot it. Check: no overlapping
buildings, no edge cutting through a facade, every flow plays start to
finish, the rail and the map agree on what is lit.
- Both themes if the repo has two.
Then tell the user plainly:
The geometry and measurements are correct — they are derived.
The prose is
a first draft. I read the code, but "what this subsystem does" is where a
single pass is weakest. Edit
; nothing else needs to change.
Do not leave mediocre writing behind a confident-looking map without saying so.
Updating an existing map
If
exists, this is an update.
Never clobber
authored content.
- Run the sync. New numbers land in the generated file; nothing else moves.
- Read . Each entry is either a subsystem the map has not been told
about, or an existing module whose pattern is too narrow.
- For genuinely new subsystems: append a node with derived geometry and
drafted prose, and extend . Leave every existing node's
prose, footprint and edges exactly as they are.
- Report what you added and what you left alone.
Scale
Past ~25 buildings the map stops being readable. The proposer folds the
smallest siblings into a parent node that owns the wider glob — the partition
stays total, only the drawing simplifies. If a repo genuinely needs more, map
one package at a time rather than shrinking everything.
What not to do
- Do not draw an edge you cannot trace to a call in the code.
- Do not set prose in the mono face. Monospace is for codes and paths.
- Do not import a host repo's or — the map ships its own.
- Do not hand-write file counts. That is what the scanner is for.
- Do not invent flows that sound good. A flow nobody can follow in the source
is a lie the page tells confidently.