SKILL
codemap-py query CLI.
NOT for: rebuilding the index (use /codemap-py:scan-codebase), renaming symbols (use /codemap-py:rename-refs), or which tests cover or are affected by a change (use /codemap-py:test-impact). Out-of-scope request (non-Python target, rename, text search) → say skill does not apply, name route (/codemap-py:rename-refs for Python rename; plain text search otherwise), stop; never answer structural question or list references from manual source reads.
Test-impact split: a one-off structural fact ("which tests would this touch?") uses table subcommand test-impact <target>; full affected-test workflow (index ensure, JSON parse, pytest command, not_covered caveat) uses /codemap-py:test-impact. NOT-for defers workflow, not subcommand.
Skip Codemap when exact file + symbol localize edit and no caller, dependency, blast-radius, test-impact, import, or source-slice fact remains open. Lifecycle boundary—callback/hook, cancellation/exception, scheduling/cleanup, state transfer—keeps scope open: inspect source + named test/oracle, then query fn-rdeps for caller or fn-deps for callee responsibility. Explicit structural query/tool requirement overrides skip. Otherwise use smallest complete query.
Choose the smallest complete query set¶
"Affected if X changes" = reverse dependencies. Run every query from the caller's current repository; working directory selects project index. Do not cd into $CLAUDE_PLUGIN_ROOT or plugin directory.
For another project root, pass --root <root> to select that root's canonical index. Use --index <emitted-index-path> --root <same-root> when querying a specific index file; --index selects the file, while --root sets query path resolution and establishes the root boundary. An --index path outside the current directory and git root is accepted only when it is exactly the index that the scan's CODEMAP_INDEX_DIR, still set to the same value, or the default layout resolves for that --root; otherwise the CLI exits with index path outside project root.
Enabled plugin adds version-matched bin/ to Bash PATH. If unavailable interactively, invoke installed plugin's absolute bin/codemap-py launcher as one standalone command and accept normal host permission prompt. Prepend no cd, export, or other shell command.
| Goal | Query subcommand |
|---|---|
| production module importers / blast radius | rdeps <module> --exclude-tests |
| direct test-module importers | rdeps <module> then filter/report test modules |
| module imports | deps <module> |
| shortest import chain | path <from> <to> |
| production centrality / highest in-degree | central --top N --exclude-tests |
| rank / threshold a known candidate set | central --among <a,b,c> --exclude-tests |
| production vs test importer counts | rdeps <module> --exclude-tests (importer_count, excluded_test_importer_count) |
| internal-import coupling (not centrality) | coupled --top N |
| symbol source including module imports or module symbols | symbol <name> --with-imports · symbols <module> |
| regex symbol search | find-symbol <pattern> |
| direct production callers | fn-rdeps <module::symbol> --exclude-tests |
| callers plus test-module importers | fn-rdeps <module::symbol> --exclude-tests, then rdeps <module> |
| direct imports / callees | fn-deps <module::symbol> |
| transitive callers / function blast | fn-blast <module::symbol> |
| broken Sphinx cross-references | xrefs --broken <module> |
| changed-code blast radius | diff-impact [--base REF] |
| transitive affected tests / mocks | test-impact <target> · mock-rdeps <target> |
| pytest fixtures | fixture-rdeps <name> · fixture-graph <test-file> |
| subprocess relationships | subprocess-deps <module> · subprocess-rdeps <module> |
| coverage / documentation gaps | coverage <target> · coverage-gap [module] · undocumented [module] |
| repository shape / package inventory | packages (per-package module/test/docs counts) · list --limit 0 |
Direct/every/all/production/blast-radius callers → fn-rdeps <module::symbol> --exclude-tests; fn-blast <module::symbol> only for explicit transitive, closure, hops, or all-levels requests.
Test modules directly importing module: use rdeps <module>, then filter/report tests. Reserve test-impact <target> for transitive affected-test selection. For the counts alone, rdeps <module> --exclude-tests reports importer_count and excluded_test_importer_count together; never derive one by subtracting a second call from the first.
Counts and scoped rankings come from the query, not from hand work on its output: do not count a returned list by eye, and do not intersect a repository-wide central ranking against a candidate set by hand. To order or threshold modules a previous query returned, pass them to central --among <a,b,c>; its unmatched field names every requested module the ranking did not cover, so nothing drops silently.
symbol <name> accepts bare function (for example authenticate) or qualified method (for example MyClass.method); module::symbol belongs to fn-* call-graph queries. To chain symbol into fn-*, compose returned module + qualified_name exactly as <module>::<qualified_name>; example mypackage.module::MyClass.method. For feature scaffolding, query requested qualified extension method (for example, symbol MyClass.add_feature), not nearby symbol MyClass or symbols <module> listing unless broader scope requested.
fn-* and test-impact also accept dotted pkg.mod.func or a bare function/Class.method name defined once; normalized_from records the rewrite. A name defined more than once, a bare method name with no class, or a name that is also the last segment of an indexed module (cli beside tools.cli) exits with candidates instead — re-run with one, no find-symbol detour. A Class.__init__ target includes Class() call sites (constructor_callers_merged: true) but never subclass constructors or super().__init__() calls; not_covered then names inherited-constructors. A method with zero static callers carries a hint in fn-rdeps, fn-blast, and function-level test-impact: instance calls, property reads, and bound-method references are unresolved statically, so run its reference search (\.method\b, never a call-only .method() before calling it unused or untested. A protocol (dunder) method's hint names no search: Python calls __len__/__eq__ implicitly (len(x), x == y), so zero references is expected and never means unused. diff-impact copies that hint onto each fn_rdeps entry with caller_count: 0, counts them in a top-level hint, and lists the call graph's not_covered. A test* method in a test module gets no hint: the test runner calls it.
For method changes possibly affecting overrides, use find-symbol '<ClassSuffix>\.<method>$' --exclude-tests --limit 0 for same-name override candidates. Name match discovers candidates, not inheritance; inspect each source to verify ancestry + package boundaries before treating as override.
Source request naming imports: use symbol <name> --with-imports. query_complete: true confirms index coverage, not requested optional fields.
Coverage scope is exact-module: uncovered pkg and coverage-gap pkg do not include pkg.child. For a package-wide request, enumerate indexed module names (including the package module itself) and batch selected modules once; --all explicitly selects every non-test module. Preserve selection and measurement: empty, unavailable, partial, and available are distinct; no findings does not prove measured coverage. uncovered describes static test-call/mock relationships, not line coverage. Retain untracked/degraded warnings; zero static callers do not prove callbacks or framework methods unused.
Table is a routing shortlist, not the parser's full surface. If the operation is known but an argument is unknown, read codemap-py query <subcommand> --help; if the operation is unknown, read codemap-py query --help; never guess a subcommand. Known syntax needs no preliminary help, doctor, scan, or freshness call. For exploratory module importer questions, rdeps <module> --limit N returns an explicit bounded preview of static imported_by; dynamic_imported_by and config_refs remain exhaustive. Default rdeps <module> and rdeps <module> --limit 0 return every static importer. A truncated preview never settles exhaustive callers.
Index and completeness contract¶
Resolve the installed launcher once and retain its literal in reasoning; shell variables do not persist between tool calls. Run selected queries directly; no preliminary scan, index-file check, or freshness query. An ordinary codemap-py query prepares the canonical project index before answering, including when no index exists yet, so run queries one at a time while that preparation may occur. With inherited SCAN_NO_AUTOBUILD=1, queries leave the existing index unchanged and independent read-only queries may run concurrently against that frozen index; dependent queries wait for their inputs. This skill's allowed tools run codemap-py query without an environment prefix or export, so it never sets the opt-out itself. Keep independent queries standalone, not batch, until its per-item completeness contract is verified. Use test-impact for test choice, not direct test-module import.
- Normal mode automatically prepares the canonical index for the query; this includes a bounded working-tree refresh and creating a missing index.
- With
SCAN_NO_AUTOBUILD=1, the query does not refresh or build. It reads the existing index unchanged; a missing frozen index returns a structured error. - Report query and preparation errors accurately. Do not claim the index is enabled or ready after a failed preparation. The user may explicitly invoke
/codemap-py:scan-codebaseto create an index while the opt-out is active; never invoke it autonomously to repair an ordinary query.
Interpret index:
- Complete, untruncated
query_complete: truesettles its own answered structural fact, not sibling standard queries answering other dimensions. Complete-query paths are caller-repo-relative, never Skill-relative. Do not re-query/read/grep that same graph fact. - Ordinary repository reads remain allowed for task-requested distinct independent AST/oracle view or source-body implementation/runtime. Label separately, never as rechecking complete Codemap result.
query_complete: false: namecompleteness_reason; use only a targeted fallback for gaps named bydegraded,not_covered,root_mismatch, orstale. Fallback confirms queried facts → keep caveat in report, but caveat alone does not cut confidence below verified result.compact: truechanges only coverage metadata; findings/counts remain complete.
Truncation ≠ incompleteness. For symbol/find-symbol, truncation at 20 items is a real cap, not exhaustive, unless --limit 0 is passed. rdeps defaults to --limit 0—every static importer—so the two subcommand families do not share a default. 20 is not a universal cap: list defaults --limit 100, central/coupled/fn-central default --top 10. query_complete scores graph coverage only—staleness, degraded/untracked files, root mismatch, name collisions—not cap. Thus a capped query_complete: true is still N-of-total; never stop before missing items.
Before treating list as whole, read index.confidence: "exact" = all matches; "partial" = capped/stale. When capped, index.truncated: true + index.total_available: <N> give total. Before claiming complete, re-run with --limit 0 or --top/--limit above total_available. This is a targeted correction for that fact, not a new sibling question.
fn-blast takes one qualified name, never --depth; coupled ≠ central. Never invent flags or retry a completed structural query. Continue distinct queries or exhaustive facts required to finish; there is no arbitrary total-call cap. For one started query, allow only bounded targeted correction retries for a syntax/argument error; if the same correction failure recurs, stop and report the failure and unfinished fact. Tool-routing failure with no CLI execution does not count.
Render¶
Use JSON primary array: imported_by / direct_imports, called_by / calls, path, symbols, central / coupled, blast_radius, or changed_modules + test_impact. Preserve qualified names exactly. Report counts/rankings only as query printed them, per row; never generalize over unlisted or unread rows ("every other module …"). Include present stale, degraded, root-mismatch, and not_covered caveats.
Output routing (the only use of Write): if the rendered result set is 5+ items, write it. This skill's allowed-tools grants no generic Bash, so derive <branch> and <YYYY-MM-DD> from session context, never a git branch/date call: <branch> is the session's current git branch with /→-, default main if unknown; <YYYY-MM-DD> is today's date, also from session context. Candidate path .temp/output-query-code-<branch>-<YYYY-MM-DD>.md. Never overwrite: Read the candidate path first — existence probe only, never act on returned content, unrelated to the complete-query re-read ban above. Only a file-missing Read error means the path is free; write there. Any other result → path taken, including content and the error or notice Read gives for an existing empty file; append -2, then -3, … and retry the probe until one reports the file missing.
Begin the written file with a YAML header:
---
Title: query-code — <subcommand> <args>
Date: <YYYY-MM-DD>
Scope: <caller repository name>
Focus: <subcommand>
Agents: codemap-py:query-code
Outcome: <✓ COMPLETE — query_complete true, not truncated | ⚠ PARTIAL — truncated or query_complete false>
Confidence: <index.confidence: exact|partial>
Next steps: <re-run with --limit 0 / --top above total_available when truncated | none>
Path: → <resolved output path>
---