Skip to content

Commit 33616aa

Browse files
authored
feat: references confidence, remove get_component_usage, ranked search hints (#39)
- get_symbol_references: adds confidence and isComplete fields for result completeness - search_codebase: includes ranked, capped hints (callers/consumers/tests) - get_component_usage: removed from MCP surface (11→10 tools) - All tests pass: 224 tests
1 parent 5b05092 commit 33616aa

File tree

10 files changed

+567
-75
lines changed

10 files changed

+567
-75
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- **Index versioning (Phase 06)**: Index artifacts are versioned via `index-meta.json`. Mixed-version indexes are never served; version mismatches or corruption trigger automatic rebuild.
88
- **Crash-safe rebuilds (Phase 06)**: Full rebuilds write to `.staging/` and swap atomically only on success. Failed rebuilds don't corrupt the active index.
99
- **Relationship sidecar (Phase 07)**: New `relationships.json` artifact containing file import graph, reverse imports, and symbol export index. Updated incrementally alongside the main index.
10+
- **References confidence + hints (Phase 08)**: `get_symbol_references` now includes `confidence: "syntactic"` and `isComplete: boolean` to help agents assess result completeness. `search_codebase` results now include a structured `hints` object (capped callers/consumers/tests ranked by frequency) drawn from the relationships sidecar. `get_component_usage` removed from MCP surface (11→10 tools).
1011
- Tree-sitter-backed symbol extraction is now used by the Generic analyzer when available (with safe fallbacks).
1112
- Expanded language/extension detection to improve indexing coverage (e.g. `.pyi`, `.php`, `.kt`/`.kts`, `.cc`/`.cxx`, `.cs`, `.swift`, `.scala`, `.toml`, `.xml`).
1213
- New tool: `get_symbol_references` for concrete symbol usage evidence (usageCount + top snippets).

README.md

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ This is where it all comes together. One call returns:
119119
- **Code results** with `file` (path + line range), `summary`, `score`
120120
- **Type** per result: compact `componentType:layer` (e.g., `service:data`) — helps agents orient
121121
- **Pattern signals** per result: `trend` (Rising/Declining — Stable is omitted) and `patternWarning` when using legacy code
122-
- **Relationships** per result: `importedByCount` and `hasTests` (condensed)
122+
- **Relationships** per result: `importedByCount` and `hasTests` (condensed) + **hints** (capped ranked callers, consumers, tests)
123123
- **Related memories**: up to 3 team decisions, gotchas, and failures matched to the query
124124
- **Search quality**: `ok` or `low_confidence` with confidence score and `hint` when low
125125
- **Preflight**: `ready` (boolean) + `reason` when evidence is thin. Pass `intent="edit"` to get the full preflight card. If search quality is low, `ready` is always `false`.
@@ -137,7 +137,11 @@ Snippets are opt-in (`includeSnippets: true`). Default output is lean — if the
137137
"score": 0.72,
138138
"type": "service:core",
139139
"trend": "Rising",
140-
"relationships": { "importedByCount": 4, "hasTests": true }
140+
"relationships": { "importedByCount": 4, "hasTests": true },
141+
"hints": {
142+
"callers": ["src/app.module.ts", "src/boot.ts"],
143+
"tests": ["src/auth/auth.interceptor.spec.ts"]
144+
}
141145
}
142146
],
143147
"relatedMemories": ["Always use HttpInterceptorFn (0.97)"]
@@ -165,19 +169,18 @@ Record a decision once. It surfaces automatically in search results and prefligh
165169

166170
### All Tools
167171

168-
| Tool | What it does |
169-
| ------------------------------ | ----------------------------------------------------------------------------------------- |
170-
| `search_codebase` | Hybrid search with enrichment + preflight. Pass `intent="edit"` for edit readiness check. |
171-
| `get_team_patterns` | Pattern frequencies, golden files, conflict detection |
172-
| `get_symbol_references` | Find concrete references to a symbol (usageCount + top snippets) |
173-
| `get_component_usage` | "Find Usages" - where a library or component is imported |
174-
| `remember` | Record a convention, decision, gotcha, or failure |
175-
| `get_memory` | Query team memory with confidence decay scoring |
176-
| `get_codebase_metadata` | Project structure, frameworks, dependencies |
177-
| `get_style_guide` | Style guide rules for the current project |
178-
| `detect_circular_dependencies` | Import cycles between files |
179-
| `refresh_index` | Re-index (full or incremental) + extract git memories |
180-
| `get_indexing_status` | Progress and stats for the current index |
172+
| Tool | What it does |
173+
| ------------------------------ | ------------------------------------------------------------------------------------------- |
174+
| `search_codebase` | Hybrid search with enrichment + preflight + ranked relationship hints. Pass `intent="edit"` for edit readiness check. |
175+
| `get_team_patterns` | Pattern frequencies, golden files, conflict detection |
176+
| `get_symbol_references` | Find concrete references to a symbol (usageCount + top snippets + confidence + completeness) |
177+
| `remember` | Record a convention, decision, gotcha, or failure |
178+
| `get_memory` | Query team memory with confidence decay scoring |
179+
| `get_codebase_metadata` | Project structure, frameworks, dependencies |
180+
| `get_style_guide` | Style guide rules for the current project |
181+
| `detect_circular_dependencies` | Import cycles between files |
182+
| `refresh_index` | Re-index (full or incremental) + extract git memories |
183+
| `get_indexing_status` | Progress and stats for the current index |
181184

182185
## Evaluation Harness (`npm run eval`)
183186

docs/capabilities.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ Technical reference for what `codebase-context` ships today. For the user-facing
44

55
## Tool Surface
66

7-
11 MCP tools + 1 optional resource (`codebase://context`).
7+
10 MCP tools + 1 optional resource (`codebase://context`).
88

99
### Core Tools
1010

11-
| Tool | Input | Output |
12-
| ----------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
13-
| `search_codebase` | `query`, optional `intent`, `limit`, `filters`, `includeSnippets` | Ranked results (`file`, `summary`, `score`, `type`, `trend`, `patternWarning`) + `searchQuality` (with `hint` when low confidence) + `preflight` ({ready, reason}). Snippets opt-in. |
11+
| Tool | Input | Output |
12+
| ----------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13+
| `search_codebase` | `query`, optional `intent`, `limit`, `filters`, `includeSnippets` | Ranked results (`file`, `summary`, `score`, `type`, `trend`, `patternWarning`, `relationships`, `hints`) + `searchQuality` (with `hint` when low confidence) + `preflight` ({ready, reason}). Hints capped at 3 per category. |
1414
| `get_team_patterns` | optional `category` | Pattern frequencies, trends, golden files, conflicts |
15-
| `get_symbol_references` | `symbol`, optional `limit` | Concrete symbol usage evidence: total `usageCount` + top usage snippets |
16-
| `get_component_usage` | `name` (import source) | Files importing the given package/module |
15+
| `get_symbol_references` | `symbol`, optional `limit` | Concrete symbol usage evidence: `usageCount` + top usage snippets + `confidence` ("syntactic") + `isComplete` boolean |
1716
| `remember` | `type`, `category`, `memory`, `reason` | Persists to `.codebase-context/memory.json` |
1817
| `get_memory` | optional `category`, `type`, `query`, `limit` | Memories with confidence decay scoring |
1918

@@ -39,7 +38,7 @@ Ordered by execution:
3938
6. **Contamination control** — test file filtering for non-test queries.
4039
7. **File deduplication** — best chunk per file.
4140
8. **Stage-2 reranking** — cross-encoder (`Xenova/ms-marco-MiniLM-L-6-v2`) triggers when the score between the top files are very close. CPU-only, top-10 bounded.
42-
9. **Result enrichment** — compact type (`componentType:layer`), pattern momentum (`trend` Rising/Declining only, Stable omitted), `patternWarning`, condensed relationships (`importedByCount`/`hasTests`), related memories (capped to 3), search quality assessment with `hint` when low confidence.
41+
9. **Result enrichment** — compact type (`componentType:layer`), pattern momentum (`trend` Rising/Declining only, Stable omitted), `patternWarning`, condensed relationships (`importedByCount`/`hasTests`), structured hints (capped callers/consumers/tests ranked by frequency), related memories (capped to 3), search quality assessment with `hint` when low confidence.
4342

4443
### Defaults
4544

src/core/symbol-references.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ interface SymbolReferencesSuccess {
2121
symbol: string;
2222
usageCount: number;
2323
usages: SymbolUsage[];
24+
confidence: 'syntactic';
25+
isComplete: boolean;
2426
}
2527

2628
interface SymbolReferencesError {
@@ -141,6 +143,8 @@ export async function findSymbolReferences(
141143
status: 'success',
142144
symbol: normalizedSymbol,
143145
usageCount,
144-
usages
146+
usages,
147+
confidence: 'syntactic',
148+
isComplete: usageCount < normalizedLimit
145149
};
146150
}

src/tools/get-symbol-references.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export async function handle(
8181
status: 'success',
8282
symbol: result.symbol,
8383
usageCount: result.usageCount,
84-
usages: result.usages
84+
usages: result.usages,
85+
confidence: result.confidence,
86+
isComplete: result.isComplete
8587
},
8688
null,
8789
2

src/tools/index.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ import { definition as d4, handle as h4 } from './refresh-index.js';
99
import { definition as d5, handle as h5 } from './get-style-guide.js';
1010
import { definition as d6, handle as h6 } from './get-team-patterns.js';
1111
import { definition as d7, handle as h7 } from './get-symbol-references.js';
12-
import { definition as d8, handle as h8 } from './get-component-usage.js';
13-
import { definition as d9, handle as h9 } from './detect-circular-dependencies.js';
14-
import { definition as d10, handle as h10 } from './remember.js';
15-
import { definition as d11, handle as h11 } from './get-memory.js';
12+
import { definition as d8, handle as h8 } from './detect-circular-dependencies.js';
13+
import { definition as d9, handle as h9 } from './remember.js';
14+
import { definition as d10, handle as h10 } from './get-memory.js';
1615

1716
import type { ToolContext, ToolResponse } from './types.js';
1817

19-
export const TOOLS: Tool[] = [d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11];
18+
export const TOOLS: Tool[] = [d1, d2, d3, d4, d5, d6, d7, d8, d9, d10];
2019

2120
export async function dispatchTool(
2221
name: string,
@@ -38,14 +37,12 @@ export async function dispatchTool(
3837
return h6(args, ctx);
3938
case 'get_symbol_references':
4039
return h7(args, ctx);
41-
case 'get_component_usage':
42-
return h8(args, ctx);
4340
case 'detect_circular_dependencies':
44-
return h9(args, ctx);
41+
return h8(args, ctx);
4542
case 'remember':
46-
return h10(args, ctx);
43+
return h9(args, ctx);
4744
case 'get_memory':
48-
return h11(args, ctx);
45+
return h10(args, ctx);
4946
default:
5047
return {
5148
content: [{ type: 'text', text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],

src/tools/search-codebase.ts

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { promises as fs } from 'fs';
55
import path from 'path';
66
import type { ToolContext, ToolResponse } from './types.js';
77
import { CodebaseSearcher } from '../core/search.js';
8+
import type { SearchResult } from '../types/index.js';
89
import { buildEvidenceLock } from '../preflight/evidence-lock.js';
910
import { shouldIncludePatternConflictCategory } from '../preflight/query-scope.js';
1011
import {
@@ -297,24 +298,28 @@ export async function handle(
297298
}
298299
}
299300

300-
// Enrich a search result with relationship data
301-
function enrichResult(r: any): any | undefined {
302-
const rPath = r.filePath;
301+
// Build relationship hints with capped arrays ranked by importedByCount
302+
interface RelationshipHints {
303+
relationships?: {
304+
importedByCount?: number;
305+
hasTests?: boolean;
306+
};
307+
hints?: {
308+
callers?: string[];
309+
consumers?: string[];
310+
tests?: string[];
311+
};
312+
}
303313

304-
// importedBy: files that import this result (reverse lookup)
305-
const importedBy: string[] = [];
314+
function buildRelationshipHints(result: SearchResult): RelationshipHints {
315+
const rPath = result.filePath;
316+
317+
// importedBy: files that import this result (reverse lookup), collect with counts
318+
const importedByMap = new Map<string, number>();
306319
for (const [dep, importers] of reverseImports) {
307320
if (dep.endsWith(rPath) || rPath.endsWith(dep)) {
308-
importedBy.push(...importers);
309-
}
310-
}
311-
312-
// imports: files this result depends on (forward lookup)
313-
const imports: string[] = [];
314-
if (importsGraph) {
315-
for (const [file, deps] of Object.entries<string[]>(importsGraph)) {
316-
if (file.endsWith(rPath) || rPath.endsWith(file)) {
317-
imports.push(...deps);
321+
for (const importer of importers) {
322+
importedByMap.set(importer, (importedByMap.get(importer) || 0) + 1);
318323
}
319324
}
320325
}
@@ -334,16 +339,50 @@ export async function handle(
334339
}
335340
}
336341

337-
// Only return if we have at least one piece of data
338-
if (importedBy.length === 0 && imports.length === 0 && testedIn.length === 0) {
339-
return undefined;
342+
// Build condensed relationships
343+
const condensedRel: Record<string, number | boolean> = {};
344+
if (importedByMap.size > 0) {
345+
condensedRel.importedByCount = importedByMap.size;
346+
}
347+
if (testedIn.length > 0) {
348+
condensedRel.hasTests = true;
340349
}
341350

342-
return {
343-
...(importedBy.length > 0 && { importedBy }),
344-
...(imports.length > 0 && { imports }),
345-
...(testedIn.length > 0 && { testedIn })
346-
};
351+
// Build hints object with capped arrays
352+
const hintsObj: Record<string, string[]> = {};
353+
354+
// Rank importers by count descending, cap at 3
355+
if (importedByMap.size > 0) {
356+
const sortedCallers = Array.from(importedByMap.entries())
357+
.sort((a, b) => b[1] - a[1])
358+
.slice(0, 3)
359+
.map(([file]) => file);
360+
hintsObj.callers = sortedCallers;
361+
hintsObj.consumers = sortedCallers; // Same data, different label
362+
}
363+
364+
// Cap tests at 3
365+
if (testedIn.length > 0) {
366+
hintsObj.tests = testedIn.slice(0, 3);
367+
}
368+
369+
// Return both condensed and hints (hints only included if non-empty)
370+
const output: RelationshipHints = {};
371+
if (Object.keys(condensedRel).length > 0) {
372+
output.relationships = condensedRel as {
373+
importedByCount?: number;
374+
hasTests?: boolean;
375+
};
376+
}
377+
if (Object.keys(hintsObj).length > 0) {
378+
output.hints = hintsObj as {
379+
callers?: string[];
380+
consumers?: string[];
381+
tests?: string[];
382+
};
383+
}
384+
385+
return output;
347386
}
348387

349388
const searchQuality = assessSearchQuality(query, results);
@@ -600,19 +639,7 @@ export async function handle(
600639
},
601640
...(preflightPayload && { preflight: preflightPayload }),
602641
results: results.map((r) => {
603-
const relationships = enrichResult(r);
604-
// Condensed relationships: importedBy count + hasTests flag
605-
const condensedRel = relationships
606-
? {
607-
...(relationships.importedBy &&
608-
relationships.importedBy.length > 0 && {
609-
importedByCount: relationships.importedBy.length
610-
}),
611-
...(relationships.testedIn &&
612-
relationships.testedIn.length > 0 && { hasTests: true })
613-
}
614-
: undefined;
615-
const hasCondensedRel = condensedRel && Object.keys(condensedRel).length > 0;
642+
const relationshipsAndHints = buildRelationshipHints(r);
616643

617644
return {
618645
file: `${r.filePath}:${r.startLine}-${r.endLine}`,
@@ -621,7 +648,8 @@ export async function handle(
621648
...(r.componentType && r.layer && { type: `${r.componentType}:${r.layer}` }),
622649
...(r.trend && r.trend !== 'Stable' && { trend: r.trend }),
623650
...(r.patternWarning && { patternWarning: r.patternWarning }),
624-
...(hasCondensedRel && { relationships: condensedRel }),
651+
...(relationshipsAndHints.relationships && { relationships: relationshipsAndHints.relationships }),
652+
...(relationshipsAndHints.hints && { hints: relationshipsAndHints.hints }),
625653
...(includeSnippets && r.snippet && { snippet: r.snippet })
626654
};
627655
}),

0 commit comments

Comments
 (0)