query-code · git:20261023.0d5e714 · 2026-10-23 · sha256 b502cf206a43bcd8

query-code git:20261023.0d5e714A

Immutable. This exact content never changes and is served at /api/v1/blob/b502cf206a43bcd8.

---
name: query-code
description: >-
  Query Codemap's Python structural index for dependencies, callers, paths, symbols, blast radius, test impact,
  mocks, fixtures, subprocesses, and static gaps. Trigger for "what depends on", "who calls", "imports of",
  "dependency graph", or "blast radius". Skip for renames, text search, non-Python repositories, index rebuilds, or
  the affected-test workflow (use test-impact).
argument-hint: <rdeps|deps|path|central|coupled|symbol|symbols|find-symbol|fn-rdeps|fn-deps|fn-blast|fn-central|diff-impact|test-impact|mock-rdeps|fixture-rdeps|fixture-graph|subprocess-deps|subprocess-rdeps|coverage|coverage-gap|uncovered|undocumented|xrefs|packages|list|batch> ...
allowed-tools: Bash(codemap-py query:*), Bash(*/bin/codemap-py* query:*), Read, Write
model: haiku
effort: low
---

<objective>
Answer structural Python questions via unified `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. </objective>

<workflow>

## 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.

```bash
codemap-py query --compact <subcommand> [arguments]
```

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-codebase` to create an index while the opt-out is active; never invoke it autonomously to repair an ordinary query.

Interpret `index`:

- Complete, untruncated `query_complete: true` settles 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`: name `completeness_reason`; use only a targeted fallback for gaps named by `degraded`, `not_covered`, `root_mismatch`, or `stale`. Fallback confirms queried facts → keep caveat in report, but caveat alone does not cut confidence below verified result.
- `compact: true` changes 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:

```yaml
---
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>
---
```

</workflow>