Skip to content

Commit f0a317d

Browse files
committed
harden: consumer-surface parity and stricter EXPLAIN test
Document optional kind/in on fast-tier show/snippet across MCP, rule, and skill; align snippet CLI help and changeset; assert COVERING INDEX use.
1 parent 1c3d4c5 commit f0a317d

7 files changed

Lines changed: 28 additions & 25 deletions

File tree

.changeset/tiered-lookup-fast-paths.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@stainless-code/codemap": patch
33
---
44

5-
`show` and `snippet` now use fast equality lookup for exact `name` and lone `name:Token` queries (no wildcards); substring, multi-field, and FTS paths stay on the broader slow tier. CLI and MCP tool descriptions document the two tiers.
5+
`show` and `snippet` now use fast equality lookup for exact `name` and lone `name:Token` queries (no wildcards); substring, multi-field, and FTS paths stay on the broader slow tier. CLI help, MCP tool descriptions, and bundled agent guidance document the two tiers.

src/application/mcp-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ function registerShowTool(server: McpServer, opts: ServerOpts): void {
342342
"show",
343343
withToolAnnotations("show", {
344344
description:
345-
"Look up symbol(s) by exact name or field-qualified `query` search; returns {matches: [{name, kind, file_path, line_start, line_end, signature, ...}], disambiguation?, warning?}. Fast tier: exact `name` arg or `query` with lone `name:Token` (no %/_ wildcards, no kind/path/in/free text) uses equality index (`name = ?`). Slow tier: `name:%pat%` substring LIKE, multi-field query, or free text (name LIKE or source_fts with with_fts when indexed — FTS matches file bodies and returns every symbol in matching files). Use `snippet` for source text; use `query` tool for arbitrary SQL.",
345+
"Look up symbol(s) by exact name or field-qualified `query` search; returns {matches: [{name, kind, file_path, line_start, line_end, signature, ...}], disambiguation?, warning?}. Fast tier: exact `name` (optional `kind`, `in` filters) or `query` with lone `name:Token` (no %/_ wildcards, no other query fields) uses equality index (`name = ?`). Slow tier: `name:%pat%` substring LIKE, multi-field query, or free text (name LIKE or source_fts with with_fts when indexed — FTS matches file bodies and returns every symbol in matching files). Use `snippet` for source text; use `query` tool for arbitrary SQL.",
346346
inputSchema: showArgsSchema,
347347
}),
348348
(args) => wrapToolResult(handleShow(args, opts.root)),
@@ -354,7 +354,7 @@ function registerSnippetTool(server: McpServer, opts: ServerOpts): void {
354354
"snippet",
355355
withToolAnnotations("snippet", {
356356
description:
357-
"Same lookup tiers as `show` (fast: exact `name` or lone `name:Token` without wildcards → equality index; slow: substring LIKE, multi-field query, or free text with optional `with_fts` — FTS matches file bodies and returns every symbol in matching files) but each match carries `source` (file lines from disk at line_start..line_end) plus `stale` (true when content_hash drifted since indexing — line range may have shifted; agent decides whether to act or re-index) and `missing` (true when file is gone). Returns `{matches, disambiguation?, warning?}`; source/stale/missing are additive fields on each match.",
357+
"Same lookup tiers as `show` (fast: exact `name` with optional `kind`/`in`, or lone `name:Token` without wildcards → equality index; slow: substring LIKE, multi-field query, or free text with optional `with_fts` — FTS matches file bodies and returns every symbol in matching files) but each match carries `source` (file lines from disk at line_start..line_end) plus `stale` (true when content_hash drifted since indexing — line range may have shifted; agent decides whether to act or re-index) and `missing` (true when file is gone). Returns `{matches, disambiguation?, warning?}`; source/stale/missing are additive fields on each match.",
358358
inputSchema: snippetArgsSchema,
359359
}),
360360
(args) => wrapToolResult(handleSnippet(args, opts.root)),

src/application/show-engine.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,14 @@ describe("findSymbolsByName", () => {
8585
.query(
8686
`EXPLAIN QUERY PLAN SELECT name, kind, file_path, line_start, line_end, signature,
8787
is_exported, parent_name, visibility
88-
FROM symbols WHERE name = ?`,
88+
FROM symbols
89+
WHERE name = ?
90+
ORDER BY file_path ASC, line_start ASC`,
8991
)
9092
.all("foo") as Array<{ detail: string }>;
9193
const text = plan.map((r) => r.detail).join("\n");
9294
expect(text).toContain("idx_symbols_name_covering");
95+
expect(text).toMatch(/USING COVERING INDEX/i);
9396
});
9497

9598
it("returns all matches for an ambiguous name (deterministic order)", () => {

src/cli/cmd-snippet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ sliced from disk at line_start..line_end.
3939
4040
Lookup tiers (same as \`codemap show\`):
4141
Fast (equality index) — positional <name>, or \`name:<Token>\` with no
42-
wildcards (% / _) and no other query fields.
42+
wildcards (% / _) and no other query fields (same rows as exact <name>).
4343
Slow (broader scan) — \`name:%pat%\` substring LIKE, multi-field query,
4444
free-text tokens (name LIKE or source_fts when --with-fts / fts5: true).
4545

templates/agent-content/mcp-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Key fields: `pending_sync` (watcher debounce queue or in-flight reindex), `commi
7070

7171
## Anti-patterns
7272

73-
- Don't grep for "where is X defined" — **`show`** (exact `name` or `{query: …}`) or **`query_recipe`**.
73+
- Don't grep for "where is X defined" — **`show`** / **`snippet`** fast tier (`name` with optional `kind`/`in`, or lone `query: 'name:Token'` without wildcards) or **`query_recipe`** `find-symbol-definitions`; slow tier for `name:%pat%`, multi-field `query`, or free text.
7474
- Don't hand-roll `WITH RECURSIVE` for impact — **`impact`**.
7575
- Convenience tools are thin composers — fall back to **`query_recipe`** / **`query`** when unsure.
7676
- Don't skip **`context`** at session start.

templates/agent-content/rule/00-full.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,23 @@ If the question matches any of these, use the index instead of grepping:
7777

7878
## Quick reference queries
7979

80-
| I need to... | Query |
81-
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
82-
| Find a symbol (fast tier) | `codemap show <name>` or lone `codemap show --query 'name:Token'` (no `%`/`_` wildcards) — equality index; recipe: `find-symbol-definitions` |
83-
| Field-qualified search (slow tier) | `codemap show --query 'kind:function name:Auth path:src/'` or `name:%pat%` substring; MCP: `show` / `snippet` with `query` (+ `with_fts` for free text) |
84-
| Call path / neighborhood | `codemap trace` / `explore` / `node` (or MCP twins; recipes: `call-path`, `symbol-neighborhood`) |
85-
| Affected tests | `codemap affected --json` or MCP `affected` (recipe: `affected-tests`) |
86-
| Find a symbol (fuzzy) | `SELECT name, kind, file_path, line_start FROM symbols WHERE name LIKE '%...%'` |
87-
| Symbol docs | `SELECT name, signature, doc_comment FROM symbols WHERE name = '...'` |
88-
| Who imports this file? | `SELECT DISTINCT from_path FROM dependencies WHERE to_path LIKE '%...'` |
89-
| What does this depend on? | `SELECT DISTINCT to_path FROM dependencies WHERE from_path LIKE '%...'` |
90-
| Who calls X? | `SELECT DISTINCT caller_name, file_path FROM calls WHERE callee_name = '...' AND (provenance IS NULL OR provenance = 'ast')` |
91-
| Component info | `SELECT name, props_type, hooks_used FROM components WHERE name = '...'` |
92-
| TODOs in a file | `SELECT line_number, content FROM markers WHERE file_path LIKE '%...' AND kind = 'TODO'` |
93-
| Deprecated symbols | `SELECT name, kind, file_path FROM symbols WHERE doc_comment LIKE '%@deprecated%'` |
94-
| Symbol coverage | `SELECT name, hit_statements, total_statements, coverage_pct FROM coverage WHERE file_path = '...'` |
95-
| Untested + dead exports | `codemap query --json --recipe untested-and-dead` |
96-
| Coverage-confirmed dead | `codemap query --json --recipe coverage-confirmed-dead` (sort by `confidence`) |
80+
| I need to... | Query |
81+
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
82+
| Find a symbol (fast tier) | `codemap show <name>` or lone `codemap show --query 'name:Token'` (no `%`/`_` wildcards; optional `--kind` / `--in` stay fast); MCP: `show` / `snippet` (`name`, optional `kind`, `in`); recipe: `find-symbol-definitions` |
83+
| Field-qualified search (slow tier) | `codemap show --query 'kind:function name:Auth path:src/'` or `name:%pat%` substring; MCP: `show` / `snippet` with `query` (+ `with_fts` for free text) |
84+
| Call path / neighborhood | `codemap trace` / `explore` / `node` (or MCP twins; recipes: `call-path`, `symbol-neighborhood`) |
85+
| Affected tests | `codemap affected --json` or MCP `affected` (recipe: `affected-tests`) |
86+
| Find a symbol (fuzzy) | `SELECT name, kind, file_path, line_start FROM symbols WHERE name LIKE '%...%'` |
87+
| Symbol docs | `SELECT name, signature, doc_comment FROM symbols WHERE name = '...'` |
88+
| Who imports this file? | `SELECT DISTINCT from_path FROM dependencies WHERE to_path LIKE '%...'` |
89+
| What does this depend on? | `SELECT DISTINCT to_path FROM dependencies WHERE from_path LIKE '%...'` |
90+
| Who calls X? | `SELECT DISTINCT caller_name, file_path FROM calls WHERE callee_name = '...' AND (provenance IS NULL OR provenance = 'ast')` |
91+
| Component info | `SELECT name, props_type, hooks_used FROM components WHERE name = '...'` |
92+
| TODOs in a file | `SELECT line_number, content FROM markers WHERE file_path LIKE '%...' AND kind = 'TODO'` |
93+
| Deprecated symbols | `SELECT name, kind, file_path FROM symbols WHERE doc_comment LIKE '%@deprecated%'` |
94+
| Symbol coverage | `SELECT name, hit_statements, total_statements, coverage_pct FROM coverage WHERE file_path = '...'` |
95+
| Untested + dead exports | `codemap query --json --recipe untested-and-dead` |
96+
| Coverage-confirmed dead | `codemap query --json --recipe coverage-confirmed-dead` (sort by `confidence`) |
9797

9898
## When Grep / Read IS appropriate
9999

templates/agent-content/skill/10-recipes-context.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ Each emitted delta carries its own `base` metadata so mixed-baseline audits are
4848
- **`drop_baseline`**`{name}``{dropped}` on success; structured `{error}` on unknown name (MCP sets `isError: true`).
4949
- **`context`**`{compact?, intent?, include_snippets?, include_codebase_map?}`. CLI: `codemap context [--include-snippets] [--no-codebase-map]`. Session-start project envelope with `start_here` shortcuts (one call replaces 4-5 `query`s). Non-compact responses also ship **`map_id`** (hash-stable fingerprint) and **`codebase_map`** (hub paths + codemap CLI/MCP routing hints); omit with `compact: true`, `include_codebase_map: false`, or CLI `--no-codebase-map`. `index_summary.file_churn` row count; **`churn_hint`** when empty (steers to index, **`ingest-churn`**, or **`churn.file`**). `include_snippets` adds one-line export previews on hub leaders (capped to adaptive `signature_max_chars`; may set `stale`/`missing`); no-op when `compact: true`. Whitespace-only `intent` is treated as no intent. Prefer `start_here.hub_leaders` over legacy `hubs` for signatures — `hubs` keeps the full bundled `fan-in` recipe limit for backward compatibility. `sample_markers` count scales down on repos >500 / >5000 files.
5050
- **`validate`**`{paths?: string[]}`. SHA-256 vs `files.content_hash`; returns only out-of-sync rows (`stale` / `missing` / `unindexed` / `rejected` — fresh paths are omitted; `rejected` includes optional `reason`: `path escapes project root` | `path escapes via symlink` | `path resolves outside project root`). Output `path` keys are project-relative POSIX paths.
51-
- **`show`**`{name, kind?, in?}` or `{query, with_fts?}`. Fast tier: exact `name` or lone `name:Token` (no `%`/`_` wildcards, no other query fields) → equality index (`name = ?`). Slow tier: `name:%pat%` LIKE, multi-field query, or free text (name LIKE / `with_fts` source_fts). CLI: `codemap show --query '…' [--print-sql]`.
52-
- **`snippet`** — same lookup tiers as `show` (`{name, kind?, in?}` or `{query, with_fts?}`) but each match also carries `source` (file text) + `stale` / `missing` flags → `{matches, disambiguation?, warning?}`. No reindex side-effects.
51+
- **`show`**`{name, kind?, in?}` or `{query, with_fts?}`. Fast tier: exact `name` (optional `kind`/`in` filters) or lone `name:Token` (no `%`/`_` wildcards, no other query fields) → equality index (`name = ?`). Slow tier: `name:%pat%` LIKE, multi-field query, or free text (name LIKE / `with_fts` source_fts). CLI: `codemap show --query '…' [--print-sql]`.
52+
- **`snippet`** — same lookup tiers as `show` (fast: exact `name` with optional `kind`/`in`, or lone `name:Token`; slow: `query` / `with_fts`) but each match also carries `source` (file text) + `stale` / `missing` flags → `{matches, disambiguation?, warning?}`. No reindex side-effects.
5353
- **`impact`**`{target, direction?, via?, depth?, limit?, in?, summary?}`. Symbol/file blast-radius walker (replaces hand-composed `WITH RECURSIVE`). Auto-resolves symbol vs file target; `via` defaults to every backend compatible with the kind. `in` disambiguates symbol homonyms (prefix/exact like `show`); unscoped homonyms union per-defining-file call graphs; mismatch → empty `matches` + `skipped_scope`.
5454
- **`trace`**`{from, to, max_depth?, via?, budget_chars?}`. CLI: `codemap trace --from … --to …`. Shortest call path + budget-capped snippets (`call-path` recipe twin). Omitted `budget_chars` scales with indexed file count (15k / 10k / 6k). `truncated` when snippet budget hit (`truncation.snippets`); dependency hops set `snippets_skipped_reason` instead of auto-snippets.
5555
- **`explore`**`{names, depth?, kind?, budget_chars?}`. CLI: `codemap explore <name>…`. Multi-name neighborhood survey + snippets (`symbol-neighborhood` per deduped name). Explore row cap is always adaptive (500 / 250 / 125 by repo size); snippet budget is adaptive (15k / 10k / 6k) when `budget_chars` omitted. `truncated` when row cap and/or snippet budget hit (`truncation.rows` / `truncation.snippets`).

0 commit comments

Comments
 (0)