# Code Search Protocol

## Purpose

Define a deterministic retrieval hierarchy for agents and skills that need to
find code (identifiers, references, definitions, similar patterns) inside the
consumer repo. The goal is to filter noise **before** Claude reads files, not
after — Grep on a common function name returns thousands of textual matches and
burns context; LSP returns only the references that point to the same symbol.

## Scope

**In**: How agents/skills should choose between LSP, Grep, and Git when
searching for code. Fallback rules when a tier is unavailable.
**Out**: Documentation search (see `agents/llm-wiki-methodology.md`), test
discovery (see `agents/testing.md`).

## Gating

This protocol is **conditional** on `features.has_lsp_layer: true` in
`baldart.config.yml`. When the flag is `false` or missing, agents fall back to
the Grep → Git flow and skip the LSP tier entirely.

Adapters for installed servers live under `src/utils/lsp-adapters/`; the
current set of servers BALDART has wired in this project is recorded at
`lsp.installed_servers` in `baldart.config.yml`.

## Retrieval Hierarchy (in order)

0. **Code graph (Graphify)** — when the query is **structural / relational**
   ("what connects X to Y", impact/blast-radius, clusters) and
   `features.has_code_graph: true`. This tier sits *above* LSP for relationship
   questions only; for a single-symbol or textual query, skip it and start at
   LSP/Grep below. Full contract in `agents/code-graph-protocol.md`. Falls back
   silently to the tiers below when the graph is unavailable.
1. **LSP symbol search** — when the query is an identifier (function, type,
   class, variable name) and `features.has_lsp_layer: true`. Use the LSP tool
   (`ToolSearch("LSP")` in Claude Code) for:
   - **find references**: every callsite of a symbol across the project.
   - **go to definition**: the single authoritative declaration.
   - **type info / hover**: signature, parameters, return type.
   - **document/workspace symbols**: enumerate top-level symbols.
2. **Grep** — for textual patterns that aren't symbols (a comment, a magic
   string, a regex match, a config key, a TODO marker). Also the fallback when
   LSP is unavailable or returns nothing for a symbol that clearly exists.
   For free-text / conceptual questions ("how does session token storage
   work"), start from the canonical docs router (`ssot-registry.md` →
   reference docs / ADRs) and Grep over `docs/`, then read the matched
   sections.
3. **Git log** — `git log --oneline -20 --grep="keyword"` to find when and why
   code changed. Last resort, but cheap when you suspect a regression.

## Query-type discrimination

Before picking a tier, classify the query:

| Query shape                            | Start with |
|----------------------------------------|------------|
| "what connects X to Y" / impact / clusters | code graph (if `has_code_graph`) — see `code-graph-protocol.md` |
| `handleSubmit`, `UserSchema`, `useAuth`| LSP        |
| `"TODO: encrypt tokens"`               | Grep       |
| `/api/v1/users` route handler          | Grep then LSP on the handler name |
| "how is auth wired"                    | canonical docs router + Grep over `docs/` |
| "when did we change session storage"   | Git log    |
| Mixed (refactor a function used in 5 places) | Grep to disambiguate, then LSP find-references |

## Budget

- **LSP**: up to **3 calls per task** for symbol resolution. If after 3 calls
  the picture is still unclear, escalate to a direct file read —
  don't keep ping-ponging through references.
- **Grep**: existing project budget (typically 15–25 calls per agent).
- **Git**: no hard budget.

## Fallback rules

- LSP tool not loaded / plugin missing → silently fall back to Grep with the
  symbol name. Do **not** prompt the user mid-task — the missing tool is a
  setup concern surfaced by `baldart doctor`.
- LSP returns zero references for a symbol you know exists → fall back to
  Grep; the symbol may live in a file the LSP server hasn't indexed yet (e.g.
  a path excluded by `tsconfig.json`).
- LSP times out (>8s) → fall back to Grep for this query, log the symptom in
  the agent's reasoning so the user can re-run doctor.
- **Cold start under-reporting**: the first LSP query in a session can return
  an incomplete set (e.g. find-references returning 1 hit where the symbol has
  6) until the language server finishes indexing the workspace. If a reference
  count looks suspiciously low for a symbol you expect to be widely used, treat
  the first result as a warm-up: re-issue the same query once before trusting
  it, or cross-check with a single Grep. Do not act on a cold-start count as if
  it were exhaustive (e.g. don't declare a helper "unused / safe to delete"
  off one cold find-references).

## Examples

### Refactor: rename a function used across the repo

1. Grep to confirm the function is the right one and not a name collision.
2. LSP find-references on the symbol → exact callsite list (file + line +
   column).
3. Read each callsite, propose the rename, apply edits.

Why this beats Grep alone: a project with both `handleSubmit` in a form
component and `handleSubmit` in a CLI utility gets two semantically distinct
sets from LSP, but a single conflated list from Grep.

### Bug: trace where a value originates

1. LSP go-to-definition on the suspect variable → authoritative source.
2. LSP find-references back from the definition → call graph upstream.
3. Grep only if LSP cannot resolve (e.g. the value passes through a
   dynamically-typed boundary).

### Cleanup: remove a deprecated helper

1. LSP find-references on the helper. Zero hits → safe to delete.
2. Non-zero hits → migrate callsites first, then delete.

Skip Grep here: textual matches inside string literals or comments are not
true callsites and would falsely block the deletion.

## When to skip LSP entirely

- Query is a phrase or comment, not a symbol.
- The project's `lsp.installed_servers` is empty for the relevant language.
- You are reading a single small file (under 200 lines) — direct Read is
  cheaper than a tool call.

## Maintenance

When adding a new language to `src/utils/lsp-adapters/`, no change is required
here unless the new language has special tooling caveats (e.g. multi-root
workspaces, monorepos). In that case, append a subsection below.

## See also

- `framework/.claude/skills/lsp-bootstrap/SKILL.md` — install + verify flow.
- `framework/agents/project-context.md` — always-ask / never-assume contract.
- `framework/.claude/agents/codebase-architect.md` — primary consumer of this
  protocol.
