From greenloop
Build and maintain a persistent codemap (.codemap/*.json — structure, dependencies, functions, warnings) of any codebase. Function-level extraction runs in four stages — tags, spec_refs, qualified names + git blame default-on; call graph opt-in. Use whenever the user asks to build, refresh, or rebuild the codemap; to map the code; to find duplicate, stale, or unused files; to ask "what's impacted if I change X" (forward/backward impact analysis over the call graph, spec sections joined in); or as part of an audit (app-audit Phase 0.5). Incremental refresh after the first full build. Works on any language.
How this skill is triggered — by the user, by Claude, or both
Slash command
/greenloop:cartographerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build and maintain persistent, structured maps of a codebase. Cartographer reads the source tree, extracts what each file is and how the pieces relate, and writes the result as JSON in `.codemap/` so future sessions (and skills like `app-audit`) can consult the maps instead of re-scanning.
Build and maintain persistent, structured maps of a codebase. Cartographer reads the source tree, extracts what each file is and how the pieces relate, and writes the result as JSON in .codemap/ so future sessions (and skills like app-audit) can consult the maps instead of re-scanning.
Two real problems Cartographer solves:
auth.old.ts instead of auth.ts. Cartographer's warnings.json proactively surfaces duplicate filenames, suspicious naming patterns, backup directories, orphan files, and near-duplicate content — the conditions that produce that failure.The maps are persistent and incrementally refreshed. Initial build is expensive (one pass over the codebase). Subsequent refreshes only re-analyze changed files.
Trigger Cartographer when the user asks to:
Trigger automatically when:
app-audit runs Phase 0.5 (codemap freshness check).codemap/ and state.json is materially behind HEADFive files in .codemap/ at the repo root:
.codemap/
├── structure.json — where every file lives, what it is, what it's for
├── dependencies.json — what each file imports, what imports it
├── functions.json — function-level details (signatures, calls, tags, spec_refs, qualified names, blame)
├── warnings.json — duplicates, suspicious names, backup dirs, orphans, near-duplicates, canonical designations
└── state.json — internal: last-built commit, per-file content hashes, build metadata
A sixth file appears only when the call-graph stage has run:
.codemap/_call_graph_cache.json — opt-in cache of function-to-function call edges (stage 4)
The cache is reused across runs as long as ≥90% of files are unchanged.
Full schemas: references/codemap-schemas.md.
Cartographer can run in either of two ways, and a project should pick one and stick with it:
Script path (recommended for any non-trivial project). A project-level Python script at .codemap/_build.py does the extraction. Faster, deterministic, easy to invoke from a SessionStart hook. The skill ships a template at references/_build.py — copy it into the project's .codemap/ on first install or when the skill template changes.
cp ~/.claude/skills/cartographer/references/_build.py /path/to/project/.codemap/_build.py
The script writes state.json.cartographer_version matching the CARTOGRAPHER_VERSION constant in the template header (check the template, don't hardcode an expectation — this doc and the script have drifted before) and state.json.build_metadata.build_method = "py-script-richer". If the project's state.json shows an older version than the current template, the template wasn't re-copied after a skill upgrade — the most common gotcha. The script detects this itself: a version mismatch triggers an automatic full rebuild on next run.
Script capabilities — the honesty contract. The script records exactly what it can do in state.json.capabilities: which warning detectors ran (detectors_run), which languages got function/import extraction, and that import resolution and the stage-4 call graph are best-effort static analysis. Skills that consume the codemap (app-audit, audit-fix) must read capabilities before trusting a field — if a detector or language isn't listed, that data wasn't produced and the consumer falls back to inline analysis (Read/Grep) for that piece. An empty array in warnings.json means "detector ran, found nothing" only when the detector appears in detectors_run.
Direct path (small projects, no Python available, ad-hoc one-offs). Claude does the extraction inline using its own tools (Read, Grep, etc.). Slower and less deterministic than the script, but useful when there's no _build.py and the project is small enough that token cost is fine.
For the rest of this document, "Cartographer runs" means either path. Where the two paths diverge, it's called out explicitly.
Cartographer extracts function-level information in four stages. Stages 1–3 are default-on (run on every build and refresh). Stage 4 is opt-in (the user is prompted, or it's invoked via flag/skill hand-off).
| Stage | What it populates | Default | Cost |
|---|---|---|---|
| 1 — Tag propagation | Each function's tags (inherits the file's tags) | On | ~0 |
| 2 — Spec_refs propagation | Each function's spec_refs (inherits file refs + scans 4 comment lines above the signature) | On | ~10% extra build time |
| 3 — Qualified names + git blame | Each function's qualified_name (module path) and last_modified_commit | On | ~5% extra build time |
| 4 — Call graph | Each function's calls and called_by arrays | Opt-in | +30–60s build, +30–50% audit token cost |
Full details on each stage and the cache-reuse policy: references/build-stages.md.
Why stage 4 is opt-in: call-graph extraction adds real cost on both sides — slower build and a larger functions.json (which means anything consuming the codemap pays more tokens). For simple architectures, the cost isn't worth it. For complex projects (security-sensitive code, many cross-module calls, refactors in progress), it is. The prompt lets the user decide per project.
When Cartographer runs directly (not via the script), Claude:
audit-fix invokes Cartographer in its Phase 0 (audit-fix's tier classification requires called_by)When Cartographer runs via the script, the flags are:
python3 .codemap/_build.py # interactive — prompts for stage 4 if cache stale
python3 .codemap/_build.py --non-interactive # never prompts; stage 4 off unless --with-call-graph
python3 .codemap/_build.py --with-call-graph # run stage 4 unconditionally
python3 .codemap/_build.py --no-call-graph # skip stage 4 even if the cache is fresh
python3 .codemap/_build.py --rebuild-call-graph # force-rebuild the stage 4 cache
python3 .codemap/_build.py --full # ignore prior state; full rebuild
The script refreshes incrementally by default: each file's content hash is compared against state.json.per_file_state, and unchanged files reuse their prior analysis (no re-extraction, no per-file git log). Only changed, new, or deleted files pay the cost. --full forces a from-scratch rebuild.
The SessionStart hook should use --non-interactive, otherwise the script will block on the prompt.
When stage 4 runs, it writes .codemap/_call_graph_cache.json, including a per-file content-hash snapshot. On subsequent runs, if ≥90% of current files have unchanged content (hash comparison, not just file presence), the cache is reused silently. Below 90%, the cache is treated as stale (re-extracted or skipped per the flags above).
{
"name": "refresh_token",
"qualified_name": "backend::services::oauth::refresh_token",
"signature": "pub async fn refresh_token(account_id: &str) -> Result<TokenPair, AuthError>",
"loc_range": [42, 78],
"calls": [],
"called_by": [],
"tags": ["rust", "auth", "security-sensitive"],
"spec_refs": [
{"ref": "§8.1", "source": "explicit", "confidence": 1.0},
{"ref": "§8.2", "source": "inferred", "confidence": 0.80}
],
"exported": true,
"last_modified_commit": "abc123def456"
}
With stage 4 also active, calls and called_by are populated.
Two main operations: full build (first run, or rebuild) and incremental refresh (every subsequent run).
Use when:
.codemap/ doesn't existstate.json is corrupted or its schema version is incompatibleSteps:
Set expectations.
"Building the codemap from scratch. One-time cost — future refreshes will only re-read changed files."
Walk the source tree. Respect .gitignore. Skip:
node_modules/, .venv/, target/, dist/, .next/, build/, generated output directories.git/, .DS_Store, lock files.claude/ — excluded so the walk never descends into Desktop-dispatched code-task worktrees (.claude/worktrees/<name>/), which are full copies of the repo. Without this, every worktree file is double-counted as a duplicate of the real source..gitignoreFor each source file, extract:
// @spec: §8.1)git logInfer purpose and tags per file. Read the file (or just the top portion) and decide:
purposereferences/tag-vocabulary.md): auth, db, worker, ui, api, test, config, security-sensitive, concurrency, external-api, etc.references/spec-extraction.md)Build cross-references. Once all files are analyzed:
imports_from entry, populate the corresponding imported_by on the target filecalls entry, populate called_by on the target function (stage 4 only)Run warning detectors. See "Warning detectors" below.
Write all five JSON files. Schemas in references/codemap-schemas.md. Pretty-print for diff readability.
Commit recommendation. Don't auto-commit, but tell the user:
"Codemap written to .codemap/. Recommend committing — these files are institutional memory, useful across machines and team members. Merge conflicts (any JSON file gets them) are resolved by re-running cartographer."
Use when .codemap/ already exists at the current schema version and the user asks to refresh, or app-audit Phase 0.5 invoked us.
Steps:
Read state.json for last_full_build_commit and per-file content hashes.
Identify changed files. Two signals:
git diff --name-status <last_full_build_commit>..HEAD for committed changesstate.json (catches uncommitted changes and works even if git is in an odd state)For each changed file:
Patch cross-references for affected files. If file A's imports changed, update imported_by on the old and new targets. If function signatures changed, update called_by on functions this one used to / now calls (stage 4 only).
Re-run warning detectors. These run over the codemap JSONs (no file I/O) and are cheap.
Update state.json: new last_refresh_commit, refreshed content hashes, bumped last_refresh_at.
Report what changed:
"Refreshed N files (X added, Y modified, Z deleted). Warnings.json updated. Cross-references patched on M affected files."
Run over the codemap to produce warnings.json. These directly address the "running stale code" failure mode.
Any filename that appears in more than one location, e.g. auth.ts in both src/auth/ and src/legacy/. Severity: high if both are imported anywhere, medium if only one is.
Why: ambiguous imports may resolve to the wrong file, and the running app may load a different version than the developer expects.
Exception: basenames that are duplicated by convention are never flagged — mod.rs, lib.rs, __init__.py, index.ts, Next.js page.tsx/layout.tsx/route.ts, README.md, Cargo.toml, package.json, and the like. The list lives in CONVENTIONAL_BASENAMES in _build.py; extend it per project rather than tolerating noise.
Match filenames against patterns that signal stale or backup files:
*.old.*, *.OLD.**.backup.*, *.bak.*, *.bkp.**_old.*, *_backup.*, *_archive.**_v[0-9]+.* (versioned files: auth_v2.ts, handler_v3.rs)*-copy*, *_copy*, *Copy.**DELETE*, *REMOVE*, *TODO_REMOVE*Untitled.*, New File.*Severity: medium for any match. The file may be legitimate, but the name is a smell that needs human review.
Exception: files inside migrations/ directories are excluded from the *_v[0-9]+.* pattern. Migration files like 0003_org_map_v2.sql are sequenced, not stale.
Find directories matching:
*_backup*, *_archive*, *_old*OLD_*, BACKUP_*, ARCHIVE_*src_* when src/ also exists at the same level (e.g., src_2026_04/)*_YYYY_MM_DD, *_YYYYMMDDSeverity: high if inside the build path; medium elsewhere.
A source file with empty imported_by is either an entry point (server.ts, main.rs, index.ts) or dead code. Cross-reference against known entry points (references/entry-point-patterns.md).
Severity: medium for non-entry-point orphans.
For pairs of files in the same directory (or with similar paths), compute content similarity. If ≥85%, flag as near-duplicate. Severity: high.
Implementation: shingling on lines or tokens, Jaccard similarity over hash sets. Only compare pairs that already share directory and have similar lengths — global pairwise comparison is too expensive.
This is the detector that most directly catches unfinished refactors.
In generated output directories (dist/, build/, .next/, out/):
Severity: medium. The fix is a rebuild. (target/ is excluded — cargo manages its own staleness. Orphaned outputs with no corresponding source are a direct-path check; the script only does the mtime comparison.)
For every cluster of duplicate-basename or near-duplicate files from detectors 1 and 5, designate one as canonical. The non-canonical members are flagged as "stale candidates."
Designation algorithm (priority order):
auth.ts beats auth.old.ts even at similar commit ages.alphabetical_tiebreak with a note that it was arbitrary — treat like ambiguous for review purposes.Recorded in warnings.json under canonical_designations:
"canonical_designations": [
{
"cluster": ["src/auth/refresh.ts", "src/auth/refresh_v2.ts"],
"canonical": "src/auth/refresh.ts",
"stale_candidates": ["src/auth/refresh_v2.ts"],
"designation_reason": "import_graph_reachability",
"concern": "src/auth/refresh.ts is imported by src/workers/sync.ts; refresh_v2.ts has no imported_by entries.",
"severity": "high",
"suggested_action": "Remove src/auth/refresh_v2.ts or archive outside source tree."
}
]
When designation is ambiguous (algorithm produces ties or imports are split between candidates), set designation_reason: "ambiguous", severity high, and surface it prominently — these are the cases most likely to produce "running stale code" failures.
app-audit consumes this: any finding located in a stale_candidates file is downgraded to Info unless the user explicitly directs attention there.
Cartographer processes documentation alongside source. Every doc gets classified into one of three classes and entered in structure.json with kind: "documentation". High-information specs are also used to populate spec_refs on source files.
spec class — high-information docs that describe what the code should do. Match any of:
*DESIGN*.md, *SPEC*.md, *PLAN*.md, *ROADMAP*.md, ARCHITECTURE.md, *-v[0-9]*-design.md§\d, ## \d+\.\d+, ### \d+\.\d+\.\d+canonical_spec or supporting_docs in .codemap/spec-config.ymlSpec-class docs get full processing: file entry in structure.json, used for inferred spec_refs extraction on source files.
operational class — files describing project conventions: AGENTS.md, CONTRIBUTING.md, STYLE.md, UI_DESIGN_PRINCIPLES.md, anything in spec-config.yml's operational_docs. File entry + tags, but not used for spec_refs inference.
informational class — README.md, CHANGELOG.md, miscellaneous notes. File entry in structure.json, minimal metadata.
Excluded entirely — anything under node_modules/, vendor/, target/, .git/, archive directories already flagged as such, plus anything .gitignore'd.
Cartographer can guess which doc is the canonical spec by filename patterns and section-marker density, but ambiguity is common. The user disambiguates with an optional .codemap/spec-config.yml:
canonical_spec: PROJECT_DESIGN.md
spec_version: v3
supporting_docs:
- ARCHITECTURE.md
- PLAN.md
operational_docs:
- AGENTS.md
- STYLE.md
tag_spec_map: # optional: drives tag-based spec_refs inference
auth: "§8.1 @ 0.85" # files tagged `auth` → inferred ref §8.1, confidence 0.85
worker: "§6 @ 0.75"
tag_spec_map is how tag-based inference gets its section numbers. The _build.py template ships with no hardcoded tag→section mappings — section numbers are inherently project-specific, and a baked-in map would write wrong inferred refs into every other project's codemap. No tag_spec_map → the script records only explicit @spec: annotations (the direct path may still infer by reading the spec).
When this file exists, Cartographer uses it as authoritative. When it doesn't, Cartographer picks the best candidate and tells the user:
"No spec-config.yml found. Picked
PROJECT_DESIGN.mdas the canonical spec based on filename + section density. Other candidates:PLAN.md,ARCHITECTURE.md. Add a.codemap/spec-config.ymlto override."
Two ways a source file gets spec_refs populated:
Explicit annotations (always win). Comments like:
// @spec: §8.1# @spec: §8.1/** @spec §8.1 */// implements: §X.YCartographer extracts these directly. Explicit annotations always override inferred ones.
Inferred spec_refs (gap-filling). For source files without explicit annotations, Cartographer does inference only when the file matches one of these tag profiles (the ones audit Category 7 cares about most):
auth, security-sensitivedb, data-model, schemaworker, concurrencyapi (top-level route handlers)cryptoexternal-apiFor each such file, Cartographer reads the canonical spec's section list plus the file's purpose and a small portion of its code, and decides whether it implements one or more sections. Records as spec_refs with a confidence score.
"spec_refs": [
{ "ref": "§8.1", "source": "explicit" },
{ "ref": "§3.7", "source": "inferred", "confidence": 0.82 }
]
Inferred refs below confidence 0.7 are not recorded — better no inference than wrong inference.
How inference happens depends on the execution path: the direct path reads the canonical spec and decides per file; the script path only applies the project's tag_spec_map from spec-config.yml (it cannot read and reason about the spec). No map configured → script emits explicit refs only.
Full details and examples: references/spec-extraction.md.
"PROJECT_DESIGN.md": {
"kind": "documentation",
"language": "markdown",
"loc": 1194,
"size_bytes": 59169,
"purpose": "Design specification for X",
"tags": ["documentation", "spec"],
"doc_class": "spec",
"is_canonical_spec": true,
"section_count": 47,
"spec_refs": [],
"entry_point": false,
"test_file": false
}
doc_class enum: spec | operational | informational. section_count is the number of top-level ## or ### \d+\. headers — useful to audit Category 7 for understanding how much spec content exists.
Cartographer refreshes aggressively without asking permission. When invoked:
last_refresh_commit behind HEAD, or any file's content hash doesn't match disk): refresh affected files automatically and report.The principle: Cartographer's job is to keep the maps current. Asking permission to do that job is friction.
Exceptions where Cartographer asks first:
.codemap/ (e.g., suggesting deletion of *_old.ts). Cartographer never deletes source code; it only writes to .codemap/.app-audit Phase 3 (Execute) uses the codemap for targeted retrieval instead of grep:
structure.json for tags includes 'security-sensitive'spec_refs index in structure.json and functions.jsonauth/refresh.ts?" → read dependencies.json["src/auth/refresh.ts"].imported_bywarnings.json directlyaudit-fix Phase 1 (Plan) uses the codemap for blast-radius ordering:
dependencies.json[file].imported_byfunctions.json[file].functions[fn].called_by (requires stage 4)When stage 4 hasn't run, audit-fix's function-level tier classification silently degrades to file-level. audit-fix Phase 0 detects this and invokes Cartographer with --with-call-graph before continuing.
The codemap answers change-impact questions directly — no new build, just a graph walk over data already on disk. Trigger phrases: "what's impacted if I change X", "what depends on X", "what breaks if I remove X", "impact analysis on X".
functions.json … called_by (function level, needs stage 4) and
dependencies.json … imported_by (file level). Default depth 2 — deeper
walks converge on "everything" and stop informing decisions; state the depth
in the answer and offer to go deeper.calls / imports — what
must hold for X to be correct. Useful before trusting X as a fix point.event_bus.emit reaches 14 files
across 2 levels, implementing §3.1 (auth events) and §6.2 (sync ordering)."file:function — via <edge>,
with a closing summary line (N files / M functions / spec sections touched).
Offer the Mermaid graph form only when asked — the list is the working
artifact.Honesty (same as the limits below): the static graph misses dynamic dispatch,
reflection, dependency injection, and event-bus indirection — say so in
every impact answer. An impact list is a lower bound, not a clearance.
Consumers: app-audit Phase 2 scope sizing and Step 2.4 blast-radius expansion,
audit-fix Phase 1 tiering, and the user asking before a risky refactor
(app-audit's references/spec-surface.md §3).
The codemap is Claude's understanding of the code, written to JSON. It can be wrong:
purpose (Claude misread the file)auth but it's actually session management)spec_refsHard rule for skills that consume the codemap: the codemap directs attention but does not substitute for reading the code. When acting on codemap data — recording an audit finding, ordering a fix — re-read the actual file before recording. The codemap saves time on "which file should I look at"; it does not save time on "is this code correct."
references/codemap-schemas.md — full JSON schemas for all five files. Read whenever writing or reading the codemap.references/tag-vocabulary.md — controlled vocabulary for tags. Read during full build when inferring tags.references/entry-point-patterns.md — patterns for legitimate entry points so they're not flagged as orphans.references/extraction-by-language.md — language-specific notes for imports, exports, and function signatures (including markdown).references/spec-extraction.md — how spec_refs are populated, both explicit and inferred.references/build-stages.md — full details on stages 1–4, the stage-4 opt-in flow, the cache-reuse policy.references/_build.py — drop-in Python build script template (script execution path).End every Cartographer run with a brief summary:
Cartographer complete.
- Operation: full_build | incremental_refresh
- Files in codemap: 247
- Files refreshed this run: 14 (12 modified, 2 added, 0 deleted)
- Stages run: 1_tags, 2_spec_refs, 3_qualified_names [+ 4_call_graph if active]
- Warnings: 6 (1 high, 3 medium, 2 low)
- Codemap location: .codemap/
- Recommended next step: review warnings.json, commit the codemap.
Surface any high-severity warnings inline in the summary so the user doesn't miss them.
npx claudepluginhub apourmd941/selran-devloop --plugin greenloopOffers UI/UX design guidance for web and mobile with 50+ styles, 161 color palettes, 57 font pairings, and 99 UX guidelines across 10 stacks. Use for designing pages, components, color systems, or reviewing UI code.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.