archlet
NewInteractive, provenance-backed architecture maps in .archlet/. Two modes: MAP — analyze the whole codebase into .archlet/data.js and seed a starter overlay from a recent change; DIFF — given a PR, commit, range, branch, or working-tree changes, overlay what they touch. Use when the user asks to map / diagram / visualize a codebase's architecture, refresh the map, or show what a PR / commit / change touches.
Overview
archlet
You produce files under .archlet/ that the viewer renders. Pick the mode from the request:
- •MAP — "map / visualize the architecture", "refresh the map", no specific change given.
- •DIFF — a change is referenced (PR, commit, range
A..B, branch, or "my changes / staged").
Needs an existing .archlet/data.js — run MAP first if absent.
Nodes form a tree of any depth via parent: top-level nodes (source roots / apps) contain modules, which can nest further when a module is big enough to warrant it. Keep it shallow — add a level only when it earns its keep; the viewer expands/collapses at every level. Always finish by launching the viewer (see end).
Mode 1 — MAP → .archlet/data.js
The single source of truth the viewer reads:
/* GENERATED by the archlet skill. */
window.ARCH = {
config: { project, layers: { <key>: { label, border, ink, fill? }, ... } }, // project = repo name (viewer title); layers = the taxonomy YOU choose
nodes: [{ id, name, g, parent?, brief, role, rt, path, methods? }], // g = a layer key
edges: [{ s, t, k, l?, src, method }], // src = "file:line"
methodEdges: [{ sNode, sMethod, tNode, tMethod, src }], // method→method calls (step 5)
};- •node —
idinternal unique slug (never shown; namespace it, e.g.coding-toolsvsfoundation-tools) ·
name short heading on the card · g a layer key · parent the containing node's id, nested as deep as the structure needs (omit for top-level nodes) · brief ≤ ~6-word tagline · role one sentence · rt key file(s) · path dir relative to repo root · methods (optional, leaf nodes) [{ name, src:"file:line", calls? }] — the top ~3–6 functions by call count.
- •edge.k — a short lowercase kind YOU choose (static
import, runtime wire, app→engine…); keep the
Containment is implicit via parent — never emit containment edges.
- •edge.l (optional) — what flows over a wire edge (
messages.create,POST /webhook); set it only where
it adds signal, never a count (3×).
- •methodEdge — a call from one node's method to another's.
sMethod/tMethodmust exist in those nodes'
methods and src is the call site, else the edge is dropped.
Procedure:
- Scan structure — find source roots (
apps/*,packages/*,src); they become the top-level nodes. Read docs. - Choose the layer taxonomy (the key judgment) — a few layers, calm palette, e.g.
frontends / control-plane / runtime / shared-pkg / external. Put them in config.layers.
- Extract the mechanical floor — required, via `npx`:
- Routes + import/call graph (codegraph): npx -y @colbymchenry/codegraph@latest init -i, then npx -y @colbymchenry/codegraph@latest query "" --kind route --limit 99999 --json for the route inventory. The import + call graph lives in .codegraph/codegraph.db (query it directly). - Import graph (madge), JS/TS roots only: npx -y madge@latest --json --ts-config <root>/tsconfig.json <root>/src, then collapse file→file edges to module→module. Non-JS/TS roots: use that language's tool (go mod graph, pydeps) or codegraph's import graph instead. Build on these — only drop to Grep/Read for what a static tool can't see (most wire/cross-process edges).
- Build — use a workflow, one agent per major module. Split the repo into its handful of large/important
modules (in a monorepo, each app/package; in a single package, the main subsystems) and give each its own agent. Each agent picks core modules (routes/controller/plugin/tool/store/service, or doc-named), emits intra-module import edges (mechanical), and stitches wire edges out of its module by matching client call sites (fetch/SDK/db/proxy/queue) to a target or external node, each with a real file:line (manual). Wire edges are NOT in the import graph and are the most valuable.
- Methods + methodEdges — from codegraph's call graph (
.codegraph/codegraph.db; don't skip — this is
what makes nodes legible). Derive mechanically, don't invent names: - `node.methods` — top functions defined under each leaf node's path, ranked by call in-degree:
(SELECT COUNT(*) FROM edges e WHERE e.kind='calls' AND e.target=n.id) AS calls FROM nodes n WHERE n.kind IN ('method','function') AND n.file_path LIKE '<node.path>/%' ORDER BY calls DESC LIMIT 6; - **methodEdges** — calls edges crossing node boundaries, mapped to { sNode, sMethod, tNode, tMethod, src }; keep only if both method names survived into their nodes' methods. Bucket each side's file_path to its node by longest path` prefix.
- Write, then validate — write
.archlet/data.js, run `npx archlet validate` (checks shape +
referential integrity), fix what it reports, re-run until clean. Spot-check wire edges against their file:line — drop, don't fabricate.
- Seed a starter diff — by default. A fresh map is static; overlaying a real recent change shows the DIFF
feature working and where activity is concentrated. Pick one coherent change (prefer a recently-merged PR via gh pr list --state merged --limit 15 --json number,title,additions,changedFiles,url; else the largest meaningful commit from git log), discounting lockfile / generated / dist noise. Build it as in Mode 2, register it in the manifest but leave default null so the clean map opens first. Skip silently if history is too thin (shallow clone, lone initial commit, only trivial changes); never fabricate one. Mention it on handoff.
Launch the viewer — npx archlet view is a blocking server that auto-opens the browser; start it in the background and tell the user it's at http://localhost:4173 (stop the process to shut it down).
Mode 2 — DIFF → .archlet/diffs/<name>.js
Overlay the files a change touches onto the existing map. Read .archlet/data.js first for node paths.
1. Get the changed files (each: path, additions, deletions):
| source | command |
|---|---|
| PR | gh pr view <n> --repo <owner/name> --json files,title,additions,deletions,changedFiles,url,number |
| commit | git show --numstat --format= <sha> |
| range / branch | git diff --numstat <A>..<B> (use <base>...<head> for branch-vs-merge-base) |
| working tree / staged | git diff --numstat · git diff --numstat --staged |
--numstat lines are <add>\t<del>\t<path> (- = binary).
2. Map files → nodes. For each path, find the node whose path is the longest prefix; else fall back to its top-level root/app node; else report it unmapped. Aggregate per node { files, add, del, paths }. Compute contains = ancestor node ids of every changed node (walk parent).
3. Write the overlay .archlet/diffs/<name>.js:
window.ARCH = window.ARCH || {};
window.ARCH.pr = {
label: "<human label>", // "PR #565" · "commit a1b2c3d" · "working tree"
title: "<optional>", url: "<optional>", number: <n>, // number only for PRs (enables ?pr=<n>)
add, del, files,
nodes: { "<nodeId>": { files, add, del, paths:[...] }, ... },
contains: [ "<ancestorNodeId>", ... ],
links: { // OPTIONAL — connectivity the change alters
add: [ { s, t }, ... ], // a dependency/wire INTRODUCED
cut: [ { s, t }, ... ], // one REMOVED
},
};links.add renders as a green + arrow, links.cut as a dashed ✕ — between the nodes' visible ancestors. Only fill links when the change actually adds/removes a node→node connection (a new/removed import, fetch, db/proxy call); omit for internal-only changes. <name>: pr-<n>, else commit-<sha7> / wip / staged.
4. Register it in .archlet/diffs/manifest.js (create if missing; keep existing entries):
window.DIFFS = {
default: "<name>",
available: [ { name, label, number?, add, del, files }, ... ], // newest first
};Run `npx archlet validate`, fix any issues, then launch the viewer in the background (as above) at http://localhost:4173. The viewer has a Diffs menu; switch via the menu, ?pr=<n>, or ?diff=<name>.
Rules
- •Provenance or it didn't happen — every wire edge needs a real
file:line; never invent edges. - •You decide the taxonomy (small);
data.jsis the single source of truth and a draft the user can hand-edit. - •Always end by opening the map in the background, whichever mode you ran — don't leave a command to paste.
- •Monorepo gotchas: CommonJS/NestJS DI deps aren't plain imports; apps importing another app's built
dist;
dynamic plugin/route registration (invisible to static tools — read the registrar).
Install & Usage
mkdir -p .claude/skillsmkdir -p .claude/skills && curl -o .claude/skills/archlet.md https://raw.githubusercontent.com/superdesigndev/archlet/main/SKILL.md/archletFrequently Asked Questions
What is archlet?
Interactive, provenance-backed architecture maps in .archlet/. Two modes: MAP — analyze the whole codebase into .archlet/data.js and seed a starter overlay from a recent change; DIFF — given a PR, commit, range, branch, or working-tree changes, overlay what they touch. Use when the user asks to map / diagram / visualize a codebase's architecture, refresh the map, or show what a PR / commit / change touches.
How to install archlet?
To install archlet, create the .claude/skills directory in your project, then run the curl command to download the skill file. Once installed, invoke it in Claude Code with /archlet.
What is archlet best for?
archlet is a community categorized under General. Created by superdesigndev.