app-dev/codebase-atlas/SKILL.md

name: codebase-atlas description: >- Scan a repository into a graph with the atlas CLI, and author the .codebase-index/_story.json narrative the Codebase Atlas story view reads. Use when asked to map, scan, or visualize a codebase with Atlas, to write or update a repo's story file, or to explain a codebase to a non-programmer.

codebase-atlas

Codebase Atlas (~/dev/codebase-atlas) turns a repository into a graph and draws it three ways: a story of how the code works, a 3D map of the directory tree, and a flow diagram of imports. The CLI hands the same graph to an agent as JSON.

The CLI

cargo install --locked --path ~/dev/codebase-atlas/src-tauri --bin atlas
atlas scan <repo>                      # RepositoryGraph JSON to stdout
atlas scan --pretty -o map.atlas.json <repo>
atlas serve [--port 7420] [--token CODE] <repo>...

atlas scan writes only graph JSON to stdout; diagnostics go to stderr, usage errors exit 2, scan failures exit 1. Safe to pipe.

The graph carries nodes, containment and import edges, per-file declarations, the bindings that cross each import, .codebase-index/ summaries as node.description, and story when the repo has a story file.

Useful reads without opening the app:

atlas scan . | jq '.stats'                       # files, lines, languages
atlas scan . | jq -r '.warnings[]'               # what the scan could not do
atlas scan . | jq '.story.actors[].name'         # the narrative, if any

atlas serve exposes GET /v1/health (open), GET /v1/catalog and POST /v1/scan (both Authorization: Bearer <pairing code>). Tokens normalize — non-alphanumerics stripped, uppercased — so abcd-2345 == ABCD2345. Requested paths must sit under a shared root.

The story file

.codebase-index/_story.json is what the story view draws. It is authored, not derived: the parts that matter most to a reader — the person typing, the chat service, the model being called — are not files, so no scan can invent them. A story built only from in-repo modules is just the flow view with fewer boxes; that is the failure to avoid.

{
  "summary": "One paragraph a non-programmer can read.",
  "actors": [
    { "id": "person", "name": "Someone in Discord", "role": "person",
      "blurb": "Anyone chatting with the bot in a server or a DM." },
    { "id": "front-door", "name": "The front door", "role": "door",
      "blurb": "Every request lands here first. It checks who is calling.",
      "modules": ["apps/service/src/app.ts", "packages/api-client"] }
  ],
  "flows": [
    { "from": "person", "to": "front-door",
      "carries": "a message someone typed",
      "returns": "the reply, posted back in the same place" }
  ],
  "journeys": [
    { "name": "Someone asks a question", "blurb": "The ordinary path.",
      "steps": ["person", "front-door", "person"] }
  ]
}

Rules the renderer depends on

  • role is the column, in this order: person, surface, door, core, store, external. There are no coordinates — naming the role honestly is the layout. Unused roles collapse instead of leaving a gap.
  • Do not put sequential actors in the same role. Two things that hand work to each other land in one column and draw as an awkward side-loop. Chat bodies, a terminal, a phone relay are surface; the single API boundary they all call is door.
  • modules are scan paths, and an actor is a role, not a directory — one actor can list several, and people and outside services list none.
  • One arrow carries both directions. Write carries and, when something comes back, returns. Never add a second flow for the reply; a round-trip journey would then double every arc.
  • steps are actor ids. Each consecutive pair needs a flow in one direction or the other. A step taken against a flow renders as its returns.

Writing the prose

Blurbs and carries text are the whole product. Aim at a reader who has never opened a codebase.

  • Name roles, not packages: "His Discord accounts", not "discord-bridge".
  • Say what travels in words: "the message, and who sent it" — never a type name.
  • Blurbs wrap to 5 lines and clip; keep them to one or two sentences.
  • Journeys are what make it flow rather than another box diagram. Write 3–5, each following one piece of data all the way there and back.

Workflow

  1. Read .codebase-index/_root.md and the top-level *.md entries for the repo's own account of itself. Fall back to READMEs and entry points.
  2. Draft actors first, roles second, then flows, then journeys.
  3. Verify every modules path against the tree, not against the index. .codebase-index/ drifts from HEAD, so paths it names may be renamed or gone.
  4. Scan and read the warnings — this is the real check:
atlas scan <repo> | jq -r '.warnings[]'

Unknown actor ids, stale module paths, and journey steps with no flow behind them are dropped and reported here. The scan never fails on a bad story; it renders the part that is still true, so silence in warnings is the pass.

  1. Eyeball it in the app when the story matters: pnpm tauri dev in ~/dev/codebase-atlas, then Scan directory on the repo.

Notes

  • The story file sits beside the markdown mirror the codebase-index skill maintains, but is a separate artifact with a separate consumer. Updating one does not update the other.
  • Whether .codebase-index/ is committed is the repo's call. Where it is gitignored, the story file stays local and does not travel with the repo — worth saying out loud when writing one.
  • Port 7420 is Atlas's default and a desktop Share session holds it. A stale listener silently answers atlas serve curls with a different pairing code, which reads as an auth bug; check lsof -ti:7420 before debugging a 401.
  • Atlas is read-only: it reads metadata and bounded text files, and never sends file contents anywhere.
  • atlas is a snapshot: cargo install copies the binary, so a change to the Rust scan side does not reach atlas scan until it is reinstalled. After touching src-tauri/, run the install line again before regenerating a map, or you will debug output the new code never produced.
  • Clicking an import arc (3D map) or route (flow view) opens what crosses it — the named bindings, carried up through aggregation. Useful for answering "what does this dependency actually mean" without reading either file.