Code search
Retrieval is cheap, reading is expensive. Every rule here moves work from the second into
the first.
The tool surface
| Tool |
Present |
Arguments |
code_search |
always |
query (free-form), intent, scope |
find_dependencies, find_dependents, call_tree, find_implementations, impact |
only when the configured engine genuinely supports the operation |
symbol, depth / max_depth, scope |
Read, Grep, Glob |
always |
— |
Read the tool list; never assume a structural tool exists. Absence means the engine
cannot answer that class of question at all — not that it would be slow or approximate. When
a tool you wanted is missing, say so in the report and answer with what is present.
code_search infers the intent from the query, so write the real question rather than a
hint. Pass intent only to override an inference you have watched go wrong.
Every response names the capability that served it. Read that field — it teaches the routing
by example at no cost, and it is how you notice a substitution.
Classify before you query
| The request asks for |
Kind |
Route to |
| meaning, behaviour or a flow — "how does authentication work", "how does user data reach the database" |
conceptual |
code_search |
| the shape of the system, or where a named symbol lives and what it touches — "map the service layer", "find class UserService", "who calls parse" |
structural |
code_search, then the structural tools |
an exact literal, an occurrence count or a filename pattern — "find DEPRECATED_FLAG", "how many TODO comments", "all *.config.ts" |
lexical |
Grep / Glob |
Locating a named symbol is structural, not lexical. Searching text for a class name
returns every mention — comments, strings, imports — unranked, and still misses re-exports.
Ask for the definition instead.
Lexical tools are the correct tool for the lexical row. That is routing, not a fallback
and not a violation, and it needs no approval. What is forbidden is doing it silently: name
the method you used.
Intercept bulk reads
| Situation |
Intercept? |
| read 1-2 named files |
no |
| read 3+ files in one investigation |
yes — one ranked query first |
| glob for an exact filename |
no |
| glob for pattern discovery, then read every match |
yes |
| grep for an exact string |
no |
| grep for a concept |
yes |
| "read the files I mentioned" |
yes — search first, then read the spans |
Ranked results carry the surrounding context, so a hit is frequently the whole answer with
no file opened. When you do open one, read only the returned line range — never the
whole file.
Line numbers go stale the moment any edit lands. Re-resolve a span before acting on it.
The five phases
Each phase narrows the next phase's query. Cost rises sharply left to right, which is the
second reason for the order.
- Structure. Before opening anything, get a task-scoped overview: which files hold
relevant code, which symbols are central, what the shape is.
- Locate. Resolve a now-known name to an exact span with its kind, signature and export
status. Disambiguate same-named symbols by centrality and export status.
- Dependencies, both directions, before modifying anything. Inbound edges answer what
breaks if I change this; outbound edges answer what does this need. Ask both. Edge
kind matters as much as edge existence —
call, import, extends and implements
have different blast radii.
- Full context for a complex change: definition plus both edge directions together, so
no step is taken on a partial picture.
- Content search last, only when actual code text is needed. A query written with the
map already in hand is materially better targeted than the same query written first.
Then read the identified ranges, make the change, and re-check that the inbound edges still
hold.
A text search cannot enumerate call sites — it misses type-aliased calls, cross-file calls
and re-exported names. Use the call graph.
Centrality
Centrality is how connected a symbol is within the call graph. Treat it as relative
tiers, never as an absolute number: the scale differs per engine, so a numeric threshold
copied from one is meaningless in another.
| Tier |
Meaning |
Action |
| top |
core abstraction — this tier is the architecture |
understand first; changing it is expensive |
| second |
key building block |
worth understanding |
| third |
ordinary code |
read as needed |
| bottom |
leaf or utility |
read only if directly relevant; skip in an architecture pass |
Ranking by centrality is what makes "read the top N" sound and "read every match" the
failure mode. It also combines with other signals to produce verdicts no single measure
gives — see the investigate and deep-analysis skills.
Some engines cannot rank. An absent centrality means unknown, not low.
Result discipline
Never rank-truncate. | head, | tail, sed -n, sort | head over a ranked result
set deletes exactly the results that mattered, because ranking already put them first. Use
scope and the query's own limits instead. The ban is on cutting a ranked list by line
count; filtering or extracting a field from unrelated shell output stays legitimate.
An error and an empty result look alike and mean opposite things. Establish which one
you have before reporting either.
Empty is not proof of absence. Symbol vocabulary varies by codebase: if authenticate
misses, try login, verify, validate. Rephrase or broaden before concluding the code
does not exist.
Validate relevance, not just success. If none of the query's key terms appear anywhere
in the results, the results are off-target — reformulate rather than build on them.
A clean result is a finding. Report "no dead code found" with the threshold that
produced it. That is evidence of hygiene, not a failed search.
Surface advisory notes. A response may report that the index lags the working tree, or
that a result was cut short. Pass those on; never drop one silently.
Name the method. Every report states which method produced each finding. If you moved
from one method to another, say which and what it cost.
Blocked, never stalled. You may be running as a subagent, where no tool exists to ask
the user a question. If you genuinely cannot proceed, do not stall waiting for an answer
that cannot arrive, and do not decide on the user's behalf. Return a result beginning
BLOCKED: that states what is missing and what would unblock it, and let the dispatching
orchestrator ask.
What static analysis cannot see
Limits of the category, not of one implementation. A finding touched by any of these is
"requires manual review", never an assertion.
| Pattern |
Example |
Consequence |
| dynamic import |
import() with a computed path |
target looks uncalled |
reflection, eval, bracket dispatch |
obj[methodName]() |
call sites invisible |
| event and callback registration |
emitter.on("x", handler) |
handler shows 0 callers |
| dependency-injection wiring |
container.register(IService, Impl) |
implementation shows 0 callers |
| consumers in another repository |
an exported public API |
0 callers here, many outside |
Index-derived metadata — signatures, docstrings — is captured at index time and can lag the
file. When currency matters, or when the name is overloaded (TypeScript, Java, C++) or
generic, open the returned span and read the declaration instead of trusting the summary.
Renaming and other cross-cutting edits
- Enumerate the complete change set before touching anything, and apply it as one
transaction. A half-applied multi-file rename leaves the tree inconsistent.
- A rename reaches further than call sites: type annotations, generics, import statements,
test files, and string literals that match the name. Counting callers under-counts it.
- Verify before you mutate — confirm the symbol resolves, confirm its current signature,
think, then edit. Never grep, read, edit.
Five recurring shapes
The sequence of questions is the durable part.
| Shape |
Sequence |
Report |
| Bug |
locate the symptom symbol, full context, trace inbound to the suspected source, full transitive impact, read the ranges, fix, re-check the callers still hold |
symptom, root cause, call chain, impact radius, fix, verification |
| Feature |
map the area, find extension points from the nearest existing feature's outbound edges, full context at the insertion point, follow the existing pattern, check coverage on what you touched |
extension point, dependencies, pattern followed, test requirements |
| Refactor |
confirm the exact symbol, take the transitive impact rather than the direct callers, group by file, update systematically, re-query the new name, run the affected tests |
direct vs transitive caller counts, files modified, verification |
| Architecture |
full structural map, pillars by centrality, full context per pillar, trace major flows outbound, dead-code sweep, test-gap sweep |
core abstractions, layers, major flows, health indicators |
| Security |
map the security vocabulary, find authentication entry points (try synonyms), trace the auth flow both directions, map authorization, map sensitive-data handling, check coverage on security-relevant symbols |
entry points, flow, authorization coverage, secret handling, gaps, prioritised recommendations |
1---2name: code-search3description: Finds code by meaning, structure or exact text, then reads only the spans returned. Use when searching a codebase, locating a symbol, tracing callers, or about to open three or more files.4---56# Code search78Retrieval is cheap, reading is expensive. Every rule here moves work from the second into9the first.1011## The tool surface1213| Tool | Present | Arguments |14|---|---|---|15| `code_search` | always | `query` (free-form), `intent`, `scope` |16| `find_dependencies`, `find_dependents`, `call_tree`, `find_implementations`, `impact` | **only when the configured engine genuinely supports the operation** | `symbol`, `depth` / `max_depth`, `scope` |17| `Read`, `Grep`, `Glob` | always | — |1819**Read the tool list; never assume a structural tool exists.** Absence means the engine20cannot answer that class of question at all — not that it would be slow or approximate. When21a tool you wanted is missing, say so in the report and answer with what is present.2223`code_search` infers the intent from the query, so write the real question rather than a24hint. Pass `intent` only to override an inference you have watched go wrong.2526Every response names the capability that served it. Read that field — it teaches the routing27by example at no cost, and it is how you notice a substitution.2829## Classify before you query3031| The request asks for | Kind | Route to |32|---|---|---|33| meaning, behaviour or a flow — "how does authentication work", "how does user data reach the database" | conceptual | `code_search` |34| the shape of the system, or where a **named** symbol lives and what it touches — "map the service layer", "find class UserService", "who calls parse" | structural | `code_search`, then the structural tools |35| an exact literal, an occurrence count or a filename pattern — "find DEPRECATED_FLAG", "how many TODO comments", "all `*.config.ts`" | lexical | `Grep` / `Glob` |3637**Locating a named symbol is structural, not lexical.** Searching text for a class name38returns every mention — comments, strings, imports — unranked, and still misses re-exports.39Ask for the definition instead.4041**Lexical tools are the correct tool for the lexical row.** That is routing, not a fallback42and not a violation, and it needs no approval. What is forbidden is doing it silently: name43the method you used.4445## Intercept bulk reads4647| Situation | Intercept? |48|---|---|49| read 1-2 named files | no |50| read 3+ files in one investigation | **yes** — one ranked query first |51| glob for an exact filename | no |52| glob for pattern discovery, then read every match | **yes** |53| grep for an exact string | no |54| grep for a concept | **yes** |55| "read the files I mentioned" | **yes** — search first, then read the spans |5657Ranked results carry the surrounding context, so a hit is frequently the whole answer with58no file opened. When you do open one, **read only the returned line range** — never the59whole file.6061Line numbers go stale the moment any edit lands. Re-resolve a span before acting on it.6263## The five phases6465Each phase narrows the next phase's query. Cost rises sharply left to right, which is the66second reason for the order.67681. **Structure.** Before opening anything, get a task-scoped overview: which files hold69 relevant code, which symbols are central, what the shape is.702. **Locate.** Resolve a now-known name to an exact span with its kind, signature and export71 status. Disambiguate same-named symbols by centrality and export status.723. **Dependencies, both directions, before modifying anything.** Inbound edges answer *what73 breaks if I change this*; outbound edges answer *what does this need*. Ask both. Edge74 kind matters as much as edge existence — `call`, `import`, `extends` and `implements`75 have different blast radii.764. **Full context** for a complex change: definition plus both edge directions together, so77 no step is taken on a partial picture.785. **Content search last**, only when actual code text is needed. A query written with the79 map already in hand is materially better targeted than the same query written first.8081Then read the identified ranges, make the change, and re-check that the inbound edges still82hold.8384A text search cannot enumerate call sites — it misses type-aliased calls, cross-file calls85and re-exported names. Use the call graph.8687## Centrality8889Centrality is how connected a symbol is within the call graph. Treat it as **relative90tiers**, never as an absolute number: the scale differs per engine, so a numeric threshold91copied from one is meaningless in another.9293| Tier | Meaning | Action |94|---|---|---|95| top | core abstraction — this tier **is** the architecture | understand first; changing it is expensive |96| second | key building block | worth understanding |97| third | ordinary code | read as needed |98| bottom | leaf or utility | read only if directly relevant; skip in an architecture pass |99100Ranking by centrality is what makes "read the top N" sound and "read every match" the101failure mode. It also combines with other signals to produce verdicts no single measure102gives — see the `investigate` and `deep-analysis` skills.103104Some engines cannot rank. An absent centrality means *unknown*, not *low*.105106## Result discipline107108**Never rank-truncate.** `| head`, `| tail`, `sed -n`, `sort | head` over a ranked result109set deletes exactly the results that mattered, because ranking already put them first. Use110`scope` and the query's own limits instead. The ban is on cutting a *ranked* list by line111count; filtering or extracting a field from unrelated shell output stays legitimate.112113**An error and an empty result look alike and mean opposite things.** Establish which one114you have before reporting either.115116**Empty is not proof of absence.** Symbol vocabulary varies by codebase: if `authenticate`117misses, try `login`, `verify`, `validate`. Rephrase or broaden before concluding the code118does not exist.119120**Validate relevance, not just success.** If none of the query's key terms appear anywhere121in the results, the results are off-target — reformulate rather than build on them.122123**A clean result is a finding.** Report "no dead code found" with the threshold that124produced it. That is evidence of hygiene, not a failed search.125126**Surface advisory notes.** A response may report that the index lags the working tree, or127that a result was cut short. Pass those on; never drop one silently.128129**Name the method.** Every report states which method produced each finding. If you moved130from one method to another, say which and what it cost.131132**Blocked, never stalled.** You may be running as a subagent, where no tool exists to ask133the user a question. If you genuinely cannot proceed, do not stall waiting for an answer134that cannot arrive, and do not decide on the user's behalf. Return a result beginning135`BLOCKED:` that states what is missing and what would unblock it, and let the dispatching136orchestrator ask.137138## What static analysis cannot see139140Limits of the category, not of one implementation. A finding touched by any of these is141**"requires manual review"**, never an assertion.142143| Pattern | Example | Consequence |144|---|---|---|145| dynamic import | `import()` with a computed path | target looks uncalled |146| reflection, `eval`, bracket dispatch | `obj[methodName]()` | call sites invisible |147| event and callback registration | `emitter.on("x", handler)` | handler shows 0 callers |148| dependency-injection wiring | `container.register(IService, Impl)` | implementation shows 0 callers |149| consumers in another repository | an exported public API | 0 callers here, many outside |150151Index-derived metadata — signatures, docstrings — is captured at index time and can lag the152file. When currency matters, or when the name is overloaded (TypeScript, Java, C++) or153generic, open the returned span and read the declaration instead of trusting the summary.154155## Renaming and other cross-cutting edits156157- Enumerate the **complete** change set before touching anything, and apply it as one158 transaction. A half-applied multi-file rename leaves the tree inconsistent.159- A rename reaches further than call sites: type annotations, generics, import statements,160 test files, and string literals that match the name. Counting callers under-counts it.161- Verify before you mutate — confirm the symbol resolves, confirm its current signature,162 think, then edit. Never grep, read, edit.163164## Five recurring shapes165166The sequence of questions is the durable part.167168| Shape | Sequence | Report |169|---|---|---|170| **Bug** | locate the symptom symbol, full context, trace inbound to the suspected source, full transitive impact, read the ranges, fix, re-check the callers still hold | symptom, root cause, call chain, impact radius, fix, verification |171| **Feature** | map the area, find extension points from the nearest existing feature's outbound edges, full context at the insertion point, follow the existing pattern, check coverage on what you touched | extension point, dependencies, pattern followed, test requirements |172| **Refactor** | confirm the exact symbol, take the transitive impact rather than the direct callers, group by file, update systematically, re-query the new name, run the affected tests | direct vs transitive caller counts, files modified, verification |173| **Architecture** | full structural map, pillars by centrality, full context per pillar, trace major flows outbound, dead-code sweep, test-gap sweep | core abstractions, layers, major flows, health indicators |174| **Security** | map the security vocabulary, find authentication entry points (try synonyms), trace the auth flow both directions, map authorization, map sensitive-data handling, check coverage on security-relevant symbols | entry points, flow, authorization coverage, secret handling, gaps, prioritised recommendations |