ποΈ codemap β Claude Code Plugin¶
Every
/develop:fix,/develop:refactor,/oss:reviewrun gets blast-radius context automatic β you do nothing.
codemap builds structural index of Python project β import graph, blast-radius scores, function call graph β injects context into existing /develop and /oss skills. Setup once; after that, invisible infrastructure. Ask Claude fix auth.py β agent already knows which 38 other modules import it before touching single line.
No direct querying. Wire in once, let other skills pick it up.
Python first. Scanner uses ast.parse to index .py files. .rst and docs/**/*.md also scanned for Sphinx/MkDocs cross-refs, included in cache-invalidation hashing β doc-only edits trigger incremental re-scans. Non-Python symbol indexing (TypeScript, Go, Rust) planned.
π Contents
- [What is codemap?](#what-is-codemap) - [Why codemap?](#why-codemap) - [Install](#install) - [Quick start](#quick-start) - [Best-practice integration](#best-practice-integration) - [Skills reference](#skills-reference) - [integration](#integration) - [scan-codebase](#scan-codebase) - [query-code](#query-code) - [rename-refs](#rename-refs) - [debrief-coding](#debrief-coding) - [How it works](#how-it-works) - [Configuration](#configuration) - [Troubleshooting](#troubleshooting) - [Contributing / feedback](#contributing--feedback)π€ What is codemap?¶
Claude Code plugin for Python projects. Pre-builds structural index β who imports whom, which modules widest blast radius, how functions call each other β injects context into /develop and /oss skills doing real code work. Index built once; currency gates at skill-invocation time detect stale state auto (covers git pull, branch switches, uncommitted edits), prompt refresh when needed. Optional post-commit hook accelerates refresh after local commits. Every skill invocation starts with structural awareness in hand.
Without codemap, every session starts blind: agent gropes through codebase with Glob and Grep, burns 20β30 tool calls just understanding structure before real work. On 200-module project those calls still miss blast-radius risks and import cycles structural scan surfaces instant.
codemap fix: scan once, wire in once, every code-touching skill benefits auto.
π― Why codemap?¶
Without codemap¶
Ask Claude refactor auth.py. Agent:
- Globs every
.pyfile for project layout. - Reads files one by one to find what imports
auth. - Guesses blast radius from files it happened to read.
- Starts editing, discovers mid-refactor
middleware.pyalso importsauth, backtracks. - Times out on large projects before surfacing all affected modules.
On pytorch-lightning (646 modules), plain-arm agents hit 300-second hard timeout on three of eight benchmark tasks.
With codemap¶
After /codemap:integration init, existing skills wired. Run /develop:refactor auth.py β before spawning any agent, skill silent runs:
scan-query central --top 5 # which modules are highest risk overall?
scan-query rdeps mypackage.auth # what breaks if auth changes?
Output prepended to agent spawn prompt as structural context. Agent starts refactor knowing full blast radius β no cold exploration, no mid-refactor surprise that middleware.py also imports auth. Across benchmark runs on pytorch-lightning, codemap cuts tool calls 50β80% while improving structural-recall metrics on import-graph tasks.
Agentic benchmark (import-graph tasks on pytorch-lightning): clean v0.13.2 numbers pending full benchmark re-run after RC1 fix; published here once available.
Real-codebase benchmark β 44 developer tasks Γ 2 arms (plain vs codemap) Γ 3 model tiers on pytorch-lightning-master (646 modules, 8 task types). Scope: pre-implementation structural-query tasks (blast-radius enumeration, caller discovery) β end-to-end patch quality and test-pass rate not yet measured. Benchmark repo-agnostic: tasks-bench.json ships repo header so harness points at any Python codebase. Zero codemap timeouts; plain-arm agents hit 300-second hard limit on several tasks.
Three-model comparison¶
June 22 2026 β 44 tasks Γ 2 arms Γ 3 models, pytorch-lightning-master.
| Model | Plain accuracy | Codemap accuracy | Accuracy lift | Safety-grade plainβcodemap | Token ratio (median) | Token ratio range |
|---|---|---|---|---|---|---|
| Haiku 4.5 | 85.3% (29/34) | 93.9% (31/33) | +9 pp | 5/13 β 12/13 | 0.38Γ | 0.04β68.2Γβ |
| Sonnet 4.6 | 83.8% (31/37) | 91.9% (34/37) | +8 pp | 11/13 β 12/12 | 0.22Γ | 0.05β1.21Γ |
| Opus 4.6 | 86.1% (31/36) | 91.7% (33/36) | +6 pp | 13/13 β 12/12 | 0.31Γ | 0.05β1.46Γ |
Safety-grade = fraction of FN + BR tasks with explicit recall where recall β₯ 0.90. Accuracy = fraction of tasks where recall β₯ 0.90 (task correct when rdep coverage meets threshold). Token savings model-independent; accuracy lift model-dependent. Single-repo caveat: all figures on pytorch-lightning-master; gains on other Python codebases directionally consistent, magnitude may differ.
β Haiku 68.2Γ = RI-04 token spiral (error_max_turns); fixed June 23. Excluding RI-04, Haiku max 1.82Γ.
June 23 fix: Opus FN-02 and BR-03 regressions resolved (evaluator v3 β both recallβ1.000); Haiku RI-02/RI-04 fixed (blocked python3/python on both arms β both recallβ1.000).
Model-specific notes¶
Haiku 4.5 β largest correctness gap between arms. Plain arm safety-grade 5/13 reflects chronic failures on FN-series (alias/lazy-import gaps) and real-issue tasks. Codemap restores 12/13. Token median 0.38Γ across all 44 tasks; query-type workflows median 0.28Γ. RI-02/RI-04 fixed June 23 (recallβ1.000 after python3/python blocked). BR-07 minor regression: codemap recall=0.778 vs plain=0.889.
Sonnet 4.6 β smallest token ratio (median 0.22Γ, query-type 0.14Γ). Accuracy parity: plain 83.8% / codemap 91.9%. FN-03 codemap extraction_failed; FT-03 codemap recall=0.500 vs plain not-scored. RI workflow cm_acc=75%. DG and SE both arms 100%.
Opus 4.6 β token median 0.31Γ. Best plain accuracy (86.1%). FN-02 and BR-03 regressions fixed June 23 (recallβ1.000 both arms). RI workflow cm_acc=100% (sonnet/opus succeed where haiku spirals). CQ-series: codemap lifts CQ-01/CQ-03/CQ-04/CQ-05 to 1.000 from poor plain scores.
By series (opus β June 23 full run, bench-opus-20260623-023648.jsonl):
| Series | plain | codemap | Notes |
|---|---|---|---|
| SE β symbol extraction | 5/5 | 5/5 | Both arms perfect; codemap saves 37β63% tokens |
| FN β call graph | 4/5 | 3/4 | Plain misses FN-01 (0.808); FN-03 codemap extraction failed |
| BR β blast radius | 8/8 | 8/8 | Both arms perfect; codemap saves 49β97% tokens |
| RV β review assistance | 2/5 | 3/5 | RV-03/04 over-count both arms; RV-05 codemap lift (0.80 β 1.00) |
| CQ β code quality | β | 5/5 | Count-based scoring (no recall); codemap hits all 5, plain unreliable |
FN-series = starkest signal for haiku and opus: plain arm burns 0.85Mβ4.0M tokens, fails 2β3 of 5 call-graph tasks; codemap resolves full caller set in one query at 4β16% token cost. Sonnet inverts β strong reasoning compensates for missing structural index on FN, but codemap execution failure on two tasks pulls safety-grade below plain.
Static AST limitations: scan-query does not resolve dynamic dispatch, hook callbacks,
importlib.import_module, lazy-loading patterns, or string-based dispatch. Calls through these not counted. Semble, when available, cuts tool calls further, slight erec boost at modest rrec trade-off. When semble MCP server available, agents also getmcp__semble__searchas optional semantic search β useful when codemap index non-exhaustive.β Integration quality matters β poor wiring can make things worse.
codemap injects rich dependency graph into every agent prompt. On weaker models or tasks with large blast-radius graphs, extra context can overwhelm model, cause fallback to grep-heavy loops β performing worse than plain arm. Benchmark labels this failure mode
degenerate_grep_loop.Good integration needs three things: (1) skill-first protocol β agent calls
/codemap:query-codebefore any Grep/Glob; (2) bounded call budget β max 3 codemap queries per task; (3) hard stop onquery_complete: trueβ when index says list complete for query direction, write answer immediate, no more tool calls.query_completedirection-scoped:deps/symbolsquery on healthy module can be complete while another file degraded, butrdeps/central/pathrequire zero degraded files. Legacyexhaustivefield mirrorsquery_completefor one deprecation cycle. Skipping any β especially ignoring completeness flag β primary cause of regressions flipping codemap benefit into liability. Use/codemap:integration initto wire correct, not manual context injection.
Real-world proof: daily-work benchmark¶
Benchmarks above measure discovery phase β enumerating callers, assessing blast radius before code written. fix_multicaller suite extends coverage to edit phase: real signature change where all callers must update in one pass.
Benchmark scope: 7 tasks in benchmarks/run-codemap-agentic.py across two families. Both use archive/restore isolation β demo codebase copied per arm run, agent edits copy, diff -ru captured against original. No git required; original codebase never mutated.
| Family | Tasks | What it tests | Scored by |
|---|---|---|---|
fix_single (FS-01βFS-04) |
Single-file bug fix | Validates archive/restore isolation; EarlyStopping/ModelCheckpoint guards |
Diff keyword recall (erec) |
fix_multicaller (FM-01βFM-03) |
Signature change + all callers | codemap fn-rdeps enumerates callers before editing; plain arm must grep |
Diff keyword recall (erec) + file recall (rrec) |
FM-03 (Strategy.setup) = decisive test: adding verbose: bool = False to base-class setup method requires updating 6 subclass overrides in ddp.py, fsdp.py, deepspeed.py, model_parallel.py, single_xla.py, xla.py. Codemap arm runs scan-query fn-rdeps lightning.pytorch.strategies.strategy::Strategy.setup before any edit, gets complete override list in one call. Plain arm must grep def setup, read candidate files. Missing overrides = silent super().setup() signature mismatch at runtime. File recall (rrec) captures whether right files actually changed.
Only public Claude Code plugin benchmark measuring edit-phase caller coverage β not just structural discovery.
# Fix-multicaller: the codemap vs plain edit-assist test
python benchmarks/run-codemap-agentic.py \
--repo-path /path/to/pytorch-lightning/src/lightning \
--tasks "['FM-01','FM-02','FM-03']" --run-all --model haiku --report
# Fix-single: validates the archive/restore isolation mechanism
python benchmarks/run-codemap-agentic.py \
--repo-path /path/to/pytorch-lightning/src/lightning \
--tasks "['FS-01','FS-02','FS-03','FS-04']" --run-all --model haiku
Integration with develop and oss plugins¶
codemap not standalone tool β primary value = structural context fed into /develop and /oss skills doing real code work. This section documents what wired today, what each integration delivers per benchmark data, where current implementation has known gaps.
What is wired today¶
| Skill | Integration type | What codemap provides |
|---|---|---|
/develop:review |
Active β per changed module | rdeps, fn-blast, mock-rdeps, uncovered, xrefs, undocumented β results injected into every dimension-agent prompt with "trust codemap, skip redundant Grep/Read" |
/oss:review |
Active β per changed module | Same per-module query set as develop:review; codemap context piped to each reviewer agent |
/develop:refactor |
Active β per affected module | rdeps + coupled callers; flags callers OUTSIDE refactoring scope as silent-contract-break risk |
/develop:fix |
Active β per target function | fn-rdeps fires for direct callers of bug's target function (module::function from ARGUMENTS or auto-derived from checkpoint.md after Step 1) |
/develop:feature |
Active (integration) / Passive (new surface) | Integration target (module::function supplied): fn-rdeps fires for direct callers. Module-only target: rdeps for importers. Net-new surface (no existing symbol): central baseline only |
Expected benefits per skill (based on benchmark data β haiku/sonnet, 28-task suite)¶
| Skill task type | Token savings (codemap vs plain) | Accuracy lift |
|---|---|---|
| Review (per-module impact) | 80β90% fewer tokens | Maintains accuracy, kills redundant grep walks |
| Blast radius / caller count | 6β17Γ fewer tokens | +40 pp (haiku: 50% β 90%) β codemap returns exact caller list in 1 call vs 150+ grep/read ops |
| Symbol location | 20β75% fewer tokens | No accuracy change β both find it, codemap faster |
| Refactor impact | 80β90% fewer tokens | Systematic caller coverage β plain arm misses 15β54% of callers on large functions |
Graceful degradation¶
Skills use two gates at invocation time:
- Gate A (missing index):
scan-queryavailable but index file absent β skill pauses, asks: (a) build index inline via/codemap:scan-codebase, or (b) skip, continue without codemap context. - Gate B (stale index):
check-index-currencydetects index no longer matches source (changed files since last scan) β skill warns, asks: (a) rescan now, (b) continue with stale index, or (c) abort. scan-queryabsent: skill auto-degrades silent, proceeds without codemap β binary absence means plugin not installed, not source changed.
Known gaps (challenger audit 2026-06-20)¶
| Gap | Status |
|---|---|
fn-rdeps not used β benchmark-proven subcommand for caller accuracy invoked in zero develop/oss skill workflows; skills used fn-blast (transitive) instead |
Fixed β fn-rdeps added to /develop:review, /oss:review, and codemap-context.md review pipeline |
/develop:fix blast-radius dead code β TARGET_FN/TARGET_MODULE never set β only central --top 5 ran β no per-bug caller impact |
Verified working β fn-rdeps fires via codemap-context.md when module::function format supplied; checkpoint.md auto-derive covers free-text ARGUMENTS |
/develop:feature blast-radius dead code β same TARGET-unset defect as fix path |
Verified working β both TARGET_MODULE and TARGET_FN extracted; fn-rdeps fires via codemap-context.md when TARGET_FN set |
| Silent degradation β index missing β skills proceed at full token cost, no warning | Fixed β codemap-context.md emits β warning to stderr when scan-query unavailable or index missing |
check_injection.py blind spot β health check detected marker comment presence only; could not catch TARGET-unset defect or missing fn-rdeps wiring |
Fixed β second audit layer added: check_fn_rdeps_wiring() now reports whether fn-rdeps wired in all required files |
π¦ Install¶
Prerequisites
- Claude Code installed, working - Python 3 on PATH (standard library only β no `pip install`) - Git (recommended β used for staleness detection, incremental rebuilds)Install the plugin
That's it. No build step. Scanner (scan-index) and query CLI (scan-query) plain Python scripts β run immediate.
Make scan-query available in your terminal (optional)
Inside Claude Code sessions, scan-query and scan-index on PATH auto via plugin bin/ directory. For regular terminal too, add to ~/.zshrc or ~/.bashrc:
CODEMAP_TOOLS=$(ls -d "$HOME/.claude/plugins/cache/borda-ai-rig/codemap"/*/bin 2>/dev/null | sort -V | tail -1)
[ -n "$CODEMAP_TOOLS" ] && export PATH="$PATH:$CODEMAP_TOOLS"
Reload shell (source ~/.zshrc), scan-query available everywhere. Snippet always picks latest installed version β no version pins to maintain.
Upgrade
After upgrade, re-run `/codemap:integration init` to re-apply injection blocks β plugin cache replaced on reinstall, prior injections lost.β‘ Quick start¶
Two commands β then forget codemap, use normal skills.
Step 1 β build the index:
Output:
[codemap] β .cache/codemap/myproject.json
[codemap] 312 modules indexed, 2 degraded
Modules: 312 indexed, 2 degraded
Symbols: 4,821 (functions, classes, methods)
Calls: 18,340 resolved call edges (v3 index)
Most central (by rdep_count):
89 myproject.models
41 myproject.config
38 myproject.utils
27 myproject.exceptions
19 myproject.auth
Step 2 β wire codemap into your installed skills:
Discovers all installed develop and oss skills, shows recommendation table, injects structural context block into each you approve. Also offers post-commit git hook install so index stays current auto.
Done. Run normal skills β codemap works silent in background:
/develop:fix auth.py # agent already knows blast radius of auth before it starts
/develop:refactor models.py # agent sees which 89 modules import models upfront
/oss:review # reviewer gets structural context on changed modules
Want manual structure exploration β /codemap:query-code there. Most users rarely need it.
β Best-practice integration¶
Six rules cover 95% of what you need:
1 β Build the index once¶
Run /codemap:scan-codebase after clone or project setup. Index lands in .cache/codemap/<project>.json. Re-run only after major structural changes or when gate fires.
2 β Wire in once per project¶
Run /codemap:integration init once. Injects structural context block into each /develop and /oss skill, (optional) installs post-commit hook. Without wiring, index exists but no skill uses it.
3 β Gates are the primary safety mechanism¶
After wiring, two gates fire auto at start of each skill invocation:
- Gate A β missing index: fires when index absent. Offers: build now, continue without codemap, or abort.
- Gate B β stale index: fires when
check-index-currencydetects drift (git HEAD changed, uncommitted.pyedits, or per-file SHA-256 mismatch). Offers: rescan, continue with stale data, or skip codemap.
Gates cover what post-commit hook misses: git pull, branch switches, uncommitted edits.
4 β Post-commit hook is optional¶
Hook triggers scan-codebase --incremental after local commits only β convenience accelerator, not safety net. Gates work without it. Install via /codemap:integration init; skip for manual control.
5 β Ambient index status (UserPromptSubmit hook)¶
UserPromptSubmit hook fires every user message, injects one-line codemap status into Claude context when index exists at .cache/codemap/<project>.json. Index absent: hook silent for non-Python dirs (zero output, near-zero overhead); Python projects get once-per-session bootstrap prompt (below).
[codemap] .cache/codemap/rfdetr.json Β· 47 modules Β· current (git: f20fa19) Β· scanned: 2026-06-23
Prefer scan-query over file reads: rdeps, fn-rdeps, fn-blast, xrefs, symbol.
Index stale (git HEAD differs from stored sha): hook spawns scan-index --incremental --root <scan_root> in background (incremental β 41msβ1.7s measured; scan-index falls back to a full scan when the on-disk index predates v3) (non-blocking, 10-minute lockfile guard) β index refreshes silent while Claude answers. Status reads Β· refresh started first stale turn, Β· refresh in progress subsequent turns until scan completes.
Separate: scan-query self-heals at query time. On stale index, runs bounded inline scan-index --incremental (skipped when more than 50 .py files changed or scan exceeds 10 s wall-clock cap), answers from refreshed graph β edge added by just-committed change visible next query. Heal skipped or unavailable: query still answers, honest flagged stale: true. Pass --no-heal to disable inline heal.
Index current: hook injects status line once per session (30-min TTL flag at /tmp/codemap-preamble-<proj>). Subsequent turns skip injection β saves ~30 tokens Γ N turns β ~900 tokens/session. Stale index always injects regardless of TTL so auto-refresh note always reaches agent.
No index yet + project is Python (__init__.py at git root or one level down, src/<pkg>/__init__.py src-layout, or β failing those β pyproject.toml/setup.py at root): hook emits once-per-session directive (30-min TTL flag at /tmp/codemap-noindex-<proj>) asking agent raise AskUserQuestion offering index build. On consent, agent runs scan-index foreground, waits for finish before continuing. Bootstraps first-time projects that would never self-scan β stale auto-refresh only fires on existing index, skill-level Gate A missing-index prompt only fires inside wired /develop//oss skills. Non-Python dirs get nothing.
Complements per-skill SKILL.md injection β which handles dynamic per-PR scan-query output and interactive Gate A/B prompts β with lightweight always-on preamble reaching every turn, not just skill invocations.
6 β Redundant-scan guard (Pre/PostToolUse hooks)¶
Once scan-query rdeps <module> returns query_complete result (legacy alias exhaustive), import graph for that module complete and authoritative β re-grepping with grep/rg adds nothing but tokens. Benchmarks showed agents (weak tiers especially) ignoring "stop" instruction, looping verification greps, burning millions of input tokens at zero recall gain.
Two hooks close this mechanical: record-exhausted.js (PostToolUse on Bash) notes each module returned complete this session (matches query_complete: true or legacy exhaustive: true); guard-redundant-scan.js (PreToolUse on Bash) then denies import-discovery greps (grep/rg for import/from) targeting already-complete module, points agent back to codemap result. Scope deliberate narrow and fail-open: only import-greps for already-complete module blocked (source reads via cat/Read never touched), only same session, any hook error allows call. Sessions never running codemap (no sentinel) unaffected. Disable by removing the record-exhausted.js and guard-redundant-scan.js entries from hooks/hooks.json (the third Bash-matcher entry, sentinel-read-allow.js, is the unrelated blueprint auto-allow hook β leave it).
Because query_complete direction-scoped, guard only ever arms for rdeps/fn-rdeps (global-in) results, marked complete only when zero files degraded β false complete can never block exact grep that would surface hidden edge.
7 β Two-tier currency check¶
check-index-currency runs inside Gate B:
- Tier 1 (git repos): compares stored
git_shavsHEAD; counts uncommitted.pychanges viagit status --porcelain. Fast β no file reads. - Tier 2 (no git or no stored SHA): compares per-file git blob SHA-1 (git repos) or MD5 (non-git) hashes stored at scan time against current content, mtime pre-filtering skips unchanged files. Catches changes in non-git workflows or when
git_shaabsent.
π§ Skills reference¶
integration¶
Trigger: /codemap:integration check | init [--approve] | demo [--repo <path|url>] [--public] [--anonymize] [--keep-clone] [--output <path>]
Three modes. init once β wires codemap into existing skills and agents. check anytime β verifies setup healthy. demo β validates end-to-end codemap plugged in correct, yields expected gains.
check mode¶
Fast diagnostic, no side effects. Checks:
scan-queryreachable on PATH (or via fallback locations)- Index file exists for current project
- Index age (warns if older than 7 days)
- Smoke test: runs
central --top 3, verifies output - Which installed skill files have codemap injection block
Each check prints β, β, or β with one-line remediation hint if needed.
init mode¶
Interactive onboarding for current project:
- Builds index if missing (offers
/codemap:scan-codebase) - Discovers all installed skills and agents across all plugins
- Scores candidates by value tier (High / Medium / Low / Skip) β would structural context help
- Presents recommendation table, asks which to wire in
- Inserts correct injection block into each selected skill or agent file
- Offers
.git/hooks/post-commithook install for automatic incremental rebuilds
Pass --approve to apply all High and Medium recommendations non-interactive:
--approve delegates injection to bin/inject_codemap.py β scores each skill candidate for Python/codemap relevance (0β4), injects context block before ## Step 1, backs up before writing, rolls back on failure. Run direct for scripted or CI use:
python "${CLAUDE_PLUGIN_ROOT:-plugins/codemap}/bin/inject_codemap.py" \
--plugin-root <path> [--apply] [--dry-run] [--verbose]
Manual injection¶
Write custom skills or agents, want codemap yourself β injection block single source of truth in bin/_injection_block.py (BLOCK constant). init and check both import it; hand-written variant drifts, fails check. Print canonical block, paste before first agent spawn:
# timeout: 5000
python -c "import sys; sys.path.insert(0, '${CLAUDE_PLUGIN_ROOT:-plugins/codemap}/bin'); import _injection_block as b; sys.stdout.write(b.BLOCK)"
Block:
- detects index with
command -v scan-query+.cache/codemap/<project>.jsoncheck (silent skip when absent); - runs
scan-query central --top 3for global baseline; - runs one targeted query when you set
TARGET_MODULE/TARGET_FNfirst βfn-rdepsfor known function, elserdepsfor known module; - prints
codemap_evidence:line summarising retrieval reliability; - carries
codemap-block: vNversion stamp socheckreports OUTDATED after block upgrade.
Full query map lives in skills/_shared/codemap-context.md. For agent .md files (no $ARGUMENTS): add instruction running scan-query central --top 5 plus scan-query rdeps <target_module> when target derivable from task, before any Glob/Grep exploration; skip silent when index absent.
Durability: injecting into plugin's own cache file (under ~/.claude/plugins/cache/) wiped on next claude plugin install β Claude Code has no project-local override for single plugin file, plugin skills namespace-isolated. After upgrade, run /codemap:integration check (reports wiped blocks MISSING, or OUTDATED when block version changed), re-run init to re-inject. Personal skills and agents under .claude/skills/, ~/.claude/skills/, .claude/agents/ are project/user files, not cache β survive upgrades untouched; prefer them when same skill exists both places.
demo mode¶
End-to-end validation for repo. Runs plumbing check, builds index if missing, executes sample tasks to populate telemetry logs, runs plain-vs-codemap A/B proving expected gains, produces final report with link to debrief output.
Flags (all optional):
| Flag | Effect |
|---|---|
--repo <path\|url> |
Target repo β local path or git URL; URL triggers clone gate |
--public |
Force clone gate even if current repo has .py files |
--anonymize |
Forward --anonymize to debrief-coding in final report |
--probe-skill <name> |
Probe specific user skill (priority: this arg > develop/oss list > synthetic); report states which probe ran |
--keep-clone |
Skip cleanup prompt after demo on cloned repo |
--output <path> |
Override report output path (default: .reports/codemap/demo-<date>.md) |
# Validate current repo
/codemap:integration demo
# Validate with a fresh public-repo clone (gate fires first)
/codemap:integration demo --public
# Run demo on a specific repo path
/codemap:integration demo --repo /path/to/myproject
# Produce an anonymized shareable report
/codemap:integration demo --anonymize
A/B caveat: arms prompt-gated (not hard tool deny-list). Tool-call counts serve as cost proxy. Recall scored against ground truth for psf/requests pinned task set; other repos use cross-arm agreement as recall proxy.
Scenarios covered:
- Fresh repo, no index β demo builds it (D3), reports module count.
- Stale index β D2 flags stale age; D3 refreshes.
- Skills never invoked (Sk=0) β D7 flags this, explains diagnostic artifact.
- Public-repo demo β D1a clone gate fires before any clone; D9 offers cleanup.
- Anonymized report β
--anonymizeforwarded todebrief-coding; output safe to share.
scan-codebase¶
Trigger: /codemap:scan-codebase
Builds structural index β runs ast.parse across every .py file in project. Writes index to .cache/codemap/<project>.json. Reports modules indexed, modules degraded (parse errors), five highest-blast-radius modules.
Flags¶
| Flag | What it does |
|---|---|
| (none) | Full scan β re-parses every .py file |
--incremental |
Re-parse only files changed since last scan (git blob SHA comparison); falls back to full scan if no v3 index exists |
--root <path> |
Scan specific directory instead of git root |
When to run¶
Full scan once at project setup. After that, skill-invocation currency gates detect stale state, prompt rescan auto β rarely need manual run. Want forced refresh β --incremental fast enough for most changes. Install optional post-commit git hook (via /codemap:integration init) for background auto-refresh after local commits.
Performance¶
| Project size | Full scan | Incremental (5 files changed) |
|---|---|---|
| ~200 modules | ~25s | ~75ms |
| ~650 modules | ~60s | ~75ms |
Example¶
Excluding paths from the index¶
Scanner always skips built-in noise directories (.git, .venv, node_modules, build/cache dirs, agent/tooling scratch dirs like .claude, .temp, .reports, .plans, generated site/_site). Anything else to keep out β vendored copy of another project, generated code, large fixtures tree β declare in either of two places at project root:
pyproject.tomlunder[tool.codemap]table:
.codemapignoreβ one pattern per line,#starts comment:
Entry with no / or glob character (*, ?, [, ]) = directory name, pruned anywhere in tree (like built-ins). Entry with path separator or glob character = fnmatch pattern matched against each file path relative to project root. Excluded paths dropped from both module list and change-detection hash set β never trigger incremental rebuilds.
Built-in prune: besides the named SKIP_DIRS (venv, build, dist, node_modules, caches, β¦), every dot-directory (.sandbox, .agents, any .name) is pruned generically β dot-dirs are never part of a project's import space but can hold whole vendored checkouts (a .sandbox/ tree once contributed 646 of 928 indexed modules and dominated centrality). The staleness diff applies the same rule, so dot-dir files never re-enter as permanently "added".
Monorepos with several source roots declare them explicit:
Module names derive from first-listed matching root (file under libs/core/src/pkg_a/mod.py indexed as pkg_a.mod), declaration order doubles as collision priority. Without src_roots, single-root auto-detection behaves as before.
Index records what excluded, effective source roots, name collisions in three meta keys:
excluded_rootsβ list of{"pattern", "kind": "dir"|"glob", "source": "pyproject.toml"|".codemapignore", "count"}, wherecount= number of.pyfiles entry removed.src_rootsβ list of effective source-root paths (posix, relative to project root); empty for flat repo, no configured roots.collisionsβ two files resolve to same dotted module name (e.g. duplicate package tree not excluded) β only one indexed. Each record{"name", "kept", "dropped": [...]}. Kept path chosen deterministic: path under configured source root wins (earlier-listedsrc_rootsbeat later), then path under detected source root, then shortest path, then lexicographic β same file always wins regardless of filesystem walk order.
### query-code β full subcommand reference
### query-code **Trigger**: `/codemap:query-code# Before refactoring auth.py β understand full blast radius
/codemap:query-code rdeps myproject.auth
# Before adding a dependency to models.py β see what already imports it
/codemap:query-code central --top 5
# Check if api and db are already coupled before adding a direct import
/codemap:query-code path myproject.api myproject.db
# Read just the validate_token function without loading the whole file
/codemap:query-code symbol validate_token
# Read a function and its module-level imports (for type-context analysis)
/codemap:query-code symbol --with-imports validate_token
# Find all functions whose name starts with "validate" (unlimited results)
/codemap:query-code find-symbol "^validate" --limit 0
# Check transitive impact of changing fetch_user at the function level
/codemap:query-code fn-blast myproject.db::fetch_user
# Exclude test modules from blast-radius analysis
/codemap:query-code central --exclude-tests --top 10
# Query a specific index file (monorepo with multiple projects)
/codemap:query-code central --index /path/to/.cache/codemap/subproject.json
batch mode¶
batch runs many queries inside single scan-query process β pays process-spawn and coverage-block cost once, not per call. Reads JSON array of {cmd, args} objects from file path or stdin (-), runs each request through same code path as standalone form, returns results keyed by input order under one shared coverage block:
echo '[{"cmd":"rdeps","args":["myproject.auth"]},{"cmd":"fn-blast","args":["myproject.db::fetch_user"]}]' \
| scan-query batch -
Response shape: {"batch": [{"ok": bool, "index": N, "cmd": "...", "result": {...}}, ...], "count": N, "index": <shared coverage block>}. Request that fails parse or errors yields per-item {"ok": false, ...} object β one bad query never aborts batch. batch cannot nest inside batch. This is the form /develop:review and /oss:review pre-flight uses to collect every per-module query in one call.
coverage block diet¶
Every query result carries index coverage block. Session-invariant fields (module counts, degraded file list, star-import count, etc.) identical across queries β after first query of Claude Code session, scan-query emits compact block carrying only per-query honesty signals β query_complete, stale, root_mismatch, plus compact: true, and (only when result incomplete) degraded count and note explaining why. Session identity from hook-written marker at <git-root>/.cache/codemap/current-session; marker missing, unparsable, or stale β every query emits full block (fail-verbose). Pass --verbose-coverage to force full block every query.
test-impact¶
Trigger: /codemap:test-impact <module::symbol | module> [--no-mocks]
Auto-invokes when: user asks which tests affected by change, wants skip unrelated tests, or asks about selective test runs; phrases: "which tests cover this", "what tests to rerun", "test impact of", "run only affected tests".
Identifies minimal test set to rerun after changing function or module β static analysis, no test execution.
Two modes:
module::symbolβ BFS over reverse call graph; finds every test calling changed function direct or transitive. Also includes tests mocking symbol viapatch().moduleβ BFS over reverse import graph; finds every test importing module through any chain. Also includes tests mocking any symbol in module.
/codemap:test-impact myproject.auth::validate_token
/codemap:test-impact myproject.utils
/codemap:test-impact myproject.auth::validate_token --no-mocks
Output includes test_files, via_call/via_mock breakdown, ready-to-run pytest_cmd. Limitation: static-AST only β dynamic dispatch and hook-callback callers not covered; not_covered field signals this, hint provides grep fallback.
### rename-refs β atomic symbol and module rename
### rename-refs **Trigger**: `/codemap:rename-refs symbol# Rename a function and update all call sites
/codemap:rename-refs symbol mypackage.auth::validate_token mypackage.auth::verify_token
# Preview what would change without editing
/codemap:rename-refs symbol MyClass MyNewClass --dry-run
# Rename with backward-compatible deprecated alias
/codemap:rename-refs symbol mypackage.utils::compute_score mypackage.utils::score --deprecate --since 2.1 --removed-in 3.0
# Rename a module (renames file + all import lines)
/codemap:rename-refs module mypackage.old_utils mypackage.utils
debrief-coding¶
Trigger: /codemap:debrief-coding
Reads .cache/codemap/logs/ JSONL telemetry from core CLI tools (scan-query, scan-index) and skill-start PreToolUse hook, writes diagnostic usage report. Useful for debugging query patterns, investigating errors, understanding which skills drive most queries, preparing shareable anonymized summary for feedback.
Flags¶
| Flag | Effect |
|---|---|
--since <YYYY-MM-DD> |
Filter to records on or after this date (default: all records) |
--session <id> |
Filter to single session UUID |
--anonymize |
Replace qualified names (module paths, symbol names) with stable pseudonyms before reading β output safe to share |
--output <path> |
Write report to this path (default: .reports/codemap/debrief-<date>.md) |
What is logged¶
All logs local to .cache/codemap/logs/, never leave machine.
| File | Layer | When written |
|---|---|---|
cli_<session>.jsonl |
cli | Every scan-query query and every scan-index build (core CLI tools) |
skills_<session>.jsonl |
skill | Every /codemap:* skill start (via PreToolUse hook) |
tools_<session>.jsonl |
tool | Every Grep / Read / Glob call plus search-shaped Bash commands (rg/grep at a command position) via PostToolUse hook log-tool-use.js |
Logs sharded per session: SessionStart hook (seed-session.js) seeds Claude Code session id into $TMPDIR/codemap-<project>-session, all layers append to <layer>_<session>.jsonl. CLI runs outside session (no seeded id) fall back to unsuffixed cli.jsonl / skills.jsonl / tools.jsonl. Per-session filenames keep concurrent sessions from interleaving appends.
CLI records include: cmd (query subcommand, or index for scan-index build), plugin version v (from .claude-plugin/plugin.json β lets debrief split before/after across releases), optional source (from CODEMAP_TELEMETRY_SOURCE, e.g. bench for demo/benchmark runs so debrief separates scripted load from organic usage), full argv, result summary (query: count, method, exhaustive flag, completeness_reason veto slug, not_covered list, error; index: modules_indexed, degraded, incremental), timing_ms, stderr tail if any, exit code if non-zero.
Skill records include: skill name, session UUID, intent (first 300 chars of args string).
Tool records include: tool (Grep|Read|Glob|Bash), plugin version v, session UUID, target (Grep/Glob pattern or search path, Read file_path, Bash command truncated to 200 chars). Bash commands are logged only when search-shaped (rg/grep/egrep/fgrep at a command position, excluding scan-query wrappers) β in harness configs without native Grep/Glob tools all search volume flows through Bash, and without this row the grep-reduction baseline is unmeasurable. Measure raw grep/read volume per session β signal codemap context injection aims to reduce. The same hook nudges once per file per session: the 3rd Read of one non-test .py file prints a one-line hint that structural queries (symbol --with-imports, rdeps, fn-rdeps) may be cheaper. log-tool-use.js hook never reads tool_response (no parse of search/read output) β per-call cost sub-millisecond; opt out with CODEMAP_LOGGING=false.
Debrief joins tool layer against cli layer measuring avoidance events (bin/join_avoidance.py): Grep/Read/Glob on module within time window (default 10 min) after query_complete: true answer already covered that module = leak β agent re-derived what index had answered. Join uses same word-boundary module matching as live guard-redundant-scan.js hook β offline rate measures exactly what online guard meant to deny. High avoidance rate = dead-chain signal: queries succeed, downstream behavior ignores them.
Logs rotate auto at 10 MB (3 rotations). Disable logging entirely with CODEMAP_LOGGING=false β useful in benchmark scripts.
Anonymization¶
--anonymize runs bin/anonymize.py on every present log file before reading. Qualified names (strings containing . or ::) replaced with stable sym_<hash> pseudonyms using project-local salt stored at .cache/codemap/logs/.salt. Scrubbing reaches into free-text error and stderr fields (each embedded qualified name pseudonymized in place, surrounding prose preserved), hashes every element of not_covered lists. Anonymized -anon.jsonl files written to dedicated export directory (--out-dir, default .cache/codemap/export/) kept separate from salt: anonymize.py refuses (nonzero exit) to write into any directory already containing .salt file β recipient handed both could reverse pseudonyms. Salt must stay local β never share alongside anonymized output. Without salt, pseudonyms not reversible.
Examples¶
# Basic report of all collected telemetry
/codemap:debrief-coding
# Last week only
/codemap:debrief-coding --since 2026-06-15
# Single session trace (correlate a skill run with its scan-query calls)
/codemap:debrief-coding --session 3f2e1a90-...
# Anonymized report safe to share
/codemap:debrief-coding --anonymize --output /tmp/codemap-report.md
βοΈ How it works¶
The scanner (scan-index)¶
scan-index = plain Python 3 script, no external dependencies. It:
- Walks every
.pyfile under project root, skipping common non-source directories (.git,.venv,__pycache__,dist,build, others). - Parses each file with
ast.parseβ extracts import statements and symbol definitions (classes, functions, methods with line ranges). - Resolves call edges per function: cross-module calls tagged
import, same-file callslocal,self.method()patternsself, star-import callsstar. - Computes graph metrics per module:
rdep_count(how many project modules import this one),dep_count(how many modules this one imports),rcall_count(how many functions across project call any function in this module). - Stores per-file git blob SHAs (
file_shas) for.py,.rst,docs/**/*.mdfiles β incremental rebuilds identify exactly which files changed. - Writes everything to
.cache/codemap/<project>.jsonas single JSON file.
Files that cannot parse (syntax errors, encoding issues) marked degraded with reason. Scan never aborts β file failing parse noted, skipped.
The query CLI (scan-query)¶
scan-query = companion Python 3 script β loads index, answers structural questions. Checks staleness every call: compares current git blob SHAs against stored file_shas. Files changed β warns to stderr, returns results anyway.
All output JSON. Easy pipe into agent spawn prompts, shell scripts, further analysis.
Every command embeds index object in output β coverage block β so consumers know exact result reliability:
| Field | Type | Meaning |
|---|---|---|
method |
string | How result was produced: index-lookup, static-ast, import-graph, ast-flags |
confidence |
string | "exact" when result complete; "partial" when truncated or any symbol stale |
truncated |
bool | Present and true when --limit cut result; absent otherwise |
total_available |
int | Total matches before truncation (only present when truncated: true) |
not_covered |
list[str] | Call patterns static analysis cannot see (dynamic dispatch, hook callbacks, etc.) |
hint |
string | Suggested grep/fallback for residual-risk verification when not_covered non-empty |
scope |
string | Sub-graph or index slice command operated on |
total_modules |
int | Modules in index at query time |
total_symbols |
int | Symbols across all modules |
degraded |
int | Modules skipped due to parse errors |
exhaustive |
bool | true when every module parsed successfully |
stale |
bool | true when index predates recent file change |
not_covered non-empty β agents surface caveat. confidence="exact" β no grep re-verification needed.
The index file¶
Index lives at .cache/codemap/<project>.json β <project> = basename of git root directory. Single flat JSON file β nothing keeps running. Format versioned (scan_version: 3 in current builds).
Key fields per module entry:
| Field | Meaning |
|---|---|
name |
Fully qualified module name (e.g. mypackage.auth) |
path |
Path to .py file relative to project root |
rdep_count |
Number of project modules importing this one (blast-radius proxy) |
dep_count |
Number of modules this one imports (coupling proxy) |
rcall_count |
Number of functions across project calling into this module (function-level blast-radius proxy) |
direct_imports |
List of modules this file imports |
symbols |
Functions, classes, methods with line ranges and call edges |
status |
ok or degraded |
is_test |
Whether file in test directory |
file_shas |
Git blob SHA or MD5 hash for incremental rebuild detection |
scan_root |
Absolute path of project root at scan time β used by scan-query to resolve file paths; superseded by --root flag if provided |
How agents use it¶
When develop plugin (or any codemap-integrated skill) spawns agent, runs scan-query central --top 5 and optionally scan-query rdeps <target_module> first. JSON output prepended to agent spawn prompt as ## Structural Context (codemap) block. Agent starts work knowing which modules highest risk, what depends on target β no cold exploration.
codemap not installed β soft-check block silent skips, skill works exact as before.
βοΈ Configuration¶
No required configuration. Everything automatic once installed.
Index location¶
Index written to .cache/codemap/<project>.json at project root by default. Set CODEMAP_INDEX_DIR to absolute path to store elsewhere β useful when project root read-only, on slow drive, or shared across machines via home directory:
With CODEMAP_INDEX_DIR set, index lands at $CODEMAP_INDEX_DIR/<project>.json. All skills and bin scripts respect variable auto.
Set SCAN_NO_AUTOBUILD=1 to disable query-time auto-build: /codemap:query-code and /codemap:test-impact then use existing index exact as-is (no incremental refresh), refuse to build missing one β fail with message naming variable and manual /codemap:scan-codebase command. Useful in CI or benchmarks where build cost must stay out of measured query path.
Directory gitignored by default in borda-ai-rig artifact layout. Project name derived from basename $(git rev-parse --show-toplevel) β directory name of git root.
Non-git projects¶
scan-index falls back to MD5 file hashes when git unavailable. Staleness detection and incremental rebuilds still work β use file content hashes instead of git blob SHAs.
Custom scan root¶
Python source not at git root β pass --root:
Or from terminal:
Custom root specified β scan-index stores it as scan_root in index. scan-query reads field auto β file path resolution works correct even querying from different working directory, e.g. querying sub-project index from monorepo root. Override stored root at query time:
Priority chain: --root flag βΊ scan_root in index βΊ git rev-parse --show-toplevel βΊ current directory.
--root only changes where file paths resolve β never re-scans or re-targets index. Root queried against (--root, or CWD git root) disagrees with index stored scan_root β index describes different project: scan-query sets root_mismatch: true in coverage block, forces query_complete: false, prints warning to stderr. Re-scan current root, or point --root at tree index was built for.
Keeping the index current¶
Primary mechanism β skill-invocation currency gates: every /develop:* or /oss:* skill run calls check-index-currency before spawning any agent. Two-tier check: stored git_sha vs HEAD (Tier 1, git repos), or per-file content hashes from stored file_shas map (Tier 2, non-git or after pull/branch switch). If stale:
- Gate A (index missing): skill pauses, offers build inline or skip.
- Gate B (index stale): skill warns, offers: rescan now, continue with stale index, or abort.
Catches all staleness paths post-commit hook misses: git pull, branch switches, uncommitted edits, non-git projects.
Secondary mechanism β post-commit hook (optional, local commits only): install once via /codemap:integration init, every git commit triggers incremental background rebuild:
# .git/hooks/post-commit (installed by /codemap:integration init)
# codemap:start β managed block, do not edit between start/end
# codemap: incremental index rebuild β do not remove this line
if command -v scan-index >/dev/null 2>&1; then
scan-index --incremental 2>/dev/null &
fi
# codemap:end
Rebuild runs in background β commit completes immediate, index updates silent within seconds. Managed block bounded by # codemap:start/# codemap:end sentinels: reinstalling replaces in place (upgrading body across plugin versions) while preserving surrounding user hook content β re-running installer idempotent. Hook = convenience shortcut; skill-invocation gates = authoritative safety net.
π Troubleshooting¶
"index not found" or empty results¶
/codemap:query-code now builds index auto on first use β rarely see this. If appears, auto-build (Step 0) failed β confirm project has .py files and python3 on PATH, build manual:
Stale index warning¶
scan-query detected Python files committed after index built. Run incremental rebuild:
Or full rebuild after large structural changes:
scan-query not found in the terminal¶
Outside Claude Code session β plugin bin/ directory not on PATH. Add to shell config (see Install β shell PATH snippet). After shell reload, scan-query available. Verify:
Degraded modules in the scan report
### Degraded modules in the scan report Some files could not parse β usually generated code, syntax errors, or Python syntax features not yet supported by standard library `ast` module. Degraded modules skipped, rest of index fully usable. See which files degraded:python -c "
import json, os, subprocess
proj = os.path.basename(subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).decode().strip())
d = json.load(open(f'.cache/codemap/{proj}.json'))
for m in d['modules']:
if m.get('status') == 'degraded':
print(m['path'], '--', m.get('reason', 'unknown'))
"
fn-* commands return "upgrade required"¶
Function-level call graph queries (fn-deps, fn-rdeps, fn-central, fn-blast) require v3 index. Current index older. Rebuild:
The develop plugin does not seem to use codemap¶
Run integration check:
Look for β missing injection in: lines pointing to specific skill files. Injection missing β run:
and select skills to wire in.
π Contributing / feedback¶
codemap lives in plugins/codemap/ directory of Borda-AI-Rig repository.
Found bug or want feature? Open issue in repository. Include:
- Python version (
python --version) - codemap version (
cat ~/.claude/plugins/cache/borda-ai-rig/codemap/*/.claude-plugin/plugin.json) - Error message or unexpected behavior
- Approximate project size scanned (module count from scan output)
Want to extend codemap?
Scanner and query CLI = standalone Python scripts in plugins/codemap/bin/. No external dependencies, easy to read and modify. Index schema versioned β adding new fields, bump SCAN_VERSION in scan-index, handle version check in scan-query.
Skills live in plugins/codemap/skills/*/SKILL.md. New skill = new subdirectory with SKILL.md following existing pattern.
After any edit to agents, skills, or index schema β update this README before committing; plugin CLAUDE.md requires it.
Plugin updates propagate via normal install path:
After upgrade, run /codemap:integration check to confirm everything still wired correct.