Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- **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.
- **Crash-safe rebuilds (Phase 06)**: Full rebuilds write to `.staging/` and swap atomically only on success. Failed rebuilds don't corrupt the active index.
- **Relationship sidecar (Phase 07)**: New `relationships.json` artifact containing file import graph, reverse imports, and symbol export index. Updated incrementally alongside the main index.
- **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).
- Tree-sitter-backed symbol extraction is now used by the Generic analyzer when available (with safe fallbacks).
- Expanded language/extension detection to improve indexing coverage (e.g. `.pyi`, `.php`, `.kt`/`.kts`, `.cc`/`.cxx`, `.cs`, `.swift`, `.scala`, `.toml`, `.xml`).
- New tool: `get_symbol_references` for concrete symbol usage evidence (usageCount + top snippets).
Expand Down
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ This is where it all comes together. One call returns:
- **Code results** with `file` (path + line range), `summary`, `score`
- **Type** per result: compact `componentType:layer` (e.g., `service:data`) — helps agents orient
- **Pattern signals** per result: `trend` (Rising/Declining — Stable is omitted) and `patternWarning` when using legacy code
- **Relationships** per result: `importedByCount` and `hasTests` (condensed)
- **Relationships** per result: `importedByCount` and `hasTests` (condensed) + **hints** (capped ranked callers, consumers, tests)
- **Related memories**: up to 3 team decisions, gotchas, and failures matched to the query
- **Search quality**: `ok` or `low_confidence` with confidence score and `hint` when low
- **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`.
Expand All @@ -137,7 +137,11 @@ Snippets are opt-in (`includeSnippets: true`). Default output is lean — if the
"score": 0.72,
"type": "service:core",
"trend": "Rising",
"relationships": { "importedByCount": 4, "hasTests": true }
"relationships": { "importedByCount": 4, "hasTests": true },
"hints": {
"callers": ["src/app.module.ts", "src/boot.ts"],
"tests": ["src/auth/auth.interceptor.spec.ts"]
}
}
],
"relatedMemories": ["Always use HttpInterceptorFn (0.97)"]
Expand Down Expand Up @@ -165,19 +169,18 @@ Record a decision once. It surfaces automatically in search results and prefligh

### All Tools

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

## Evaluation Harness (`npm run eval`)

Expand Down
13 changes: 6 additions & 7 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ Technical reference for what `codebase-context` ships today. For the user-facing

## Tool Surface

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

### Core Tools

| Tool | Input | Output |
| ----------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `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. |
| Tool | Input | Output |
| ----------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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. |
| `get_team_patterns` | optional `category` | Pattern frequencies, trends, golden files, conflicts |
| `get_symbol_references` | `symbol`, optional `limit` | Concrete symbol usage evidence: total `usageCount` + top usage snippets |
| `get_component_usage` | `name` (import source) | Files importing the given package/module |
| `get_symbol_references` | `symbol`, optional `limit` | Concrete symbol usage evidence: `usageCount` + top usage snippets + `confidence` ("syntactic") + `isComplete` boolean |
| `remember` | `type`, `category`, `memory`, `reason` | Persists to `.codebase-context/memory.json` |
| `get_memory` | optional `category`, `type`, `query`, `limit` | Memories with confidence decay scoring |

Expand All @@ -39,7 +38,7 @@ Ordered by execution:
6. **Contamination control** — test file filtering for non-test queries.
7. **File deduplication** — best chunk per file.
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.
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.
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.

### Defaults

Expand Down
6 changes: 5 additions & 1 deletion src/core/symbol-references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface SymbolReferencesSuccess {
symbol: string;
usageCount: number;
usages: SymbolUsage[];
confidence: 'syntactic';
isComplete: boolean;
}

interface SymbolReferencesError {
Expand Down Expand Up @@ -141,6 +143,8 @@ export async function findSymbolReferences(
status: 'success',
symbol: normalizedSymbol,
usageCount,
usages
usages,
confidence: 'syntactic',
isComplete: usageCount < normalizedLimit
};
}
4 changes: 3 additions & 1 deletion src/tools/get-symbol-references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ export async function handle(
status: 'success',
symbol: result.symbol,
usageCount: result.usageCount,
usages: result.usages
usages: result.usages,
confidence: result.confidence,
isComplete: result.isComplete
},
null,
2
Expand Down
17 changes: 7 additions & 10 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import { definition as d4, handle as h4 } from './refresh-index.js';
import { definition as d5, handle as h5 } from './get-style-guide.js';
import { definition as d6, handle as h6 } from './get-team-patterns.js';
import { definition as d7, handle as h7 } from './get-symbol-references.js';
import { definition as d8, handle as h8 } from './get-component-usage.js';
import { definition as d9, handle as h9 } from './detect-circular-dependencies.js';
import { definition as d10, handle as h10 } from './remember.js';
import { definition as d11, handle as h11 } from './get-memory.js';
import { definition as d8, handle as h8 } from './detect-circular-dependencies.js';
import { definition as d9, handle as h9 } from './remember.js';
import { definition as d10, handle as h10 } from './get-memory.js';

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

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

export async function dispatchTool(
name: string,
Expand All @@ -38,14 +37,12 @@ export async function dispatchTool(
return h6(args, ctx);
case 'get_symbol_references':
return h7(args, ctx);
case 'get_component_usage':
return h8(args, ctx);
case 'detect_circular_dependencies':
return h9(args, ctx);
return h8(args, ctx);
case 'remember':
return h10(args, ctx);
return h9(args, ctx);
case 'get_memory':
return h11(args, ctx);
return h10(args, ctx);
default:
return {
content: [{ type: 'text', text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
Expand Down
102 changes: 65 additions & 37 deletions src/tools/search-codebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { promises as fs } from 'fs';
import path from 'path';
import type { ToolContext, ToolResponse } from './types.js';
import { CodebaseSearcher } from '../core/search.js';
import type { SearchResult } from '../types/index.js';
import { buildEvidenceLock } from '../preflight/evidence-lock.js';
import { shouldIncludePatternConflictCategory } from '../preflight/query-scope.js';
import {
Expand Down Expand Up @@ -297,24 +298,28 @@ export async function handle(
}
}

// Enrich a search result with relationship data
function enrichResult(r: any): any | undefined {
const rPath = r.filePath;
// Build relationship hints with capped arrays ranked by importedByCount
interface RelationshipHints {
relationships?: {
importedByCount?: number;
hasTests?: boolean;
};
hints?: {
callers?: string[];
consumers?: string[];
tests?: string[];
};
}

// importedBy: files that import this result (reverse lookup)
const importedBy: string[] = [];
function buildRelationshipHints(result: SearchResult): RelationshipHints {
const rPath = result.filePath;

// importedBy: files that import this result (reverse lookup), collect with counts
const importedByMap = new Map<string, number>();
for (const [dep, importers] of reverseImports) {
if (dep.endsWith(rPath) || rPath.endsWith(dep)) {
importedBy.push(...importers);
}
}

// imports: files this result depends on (forward lookup)
const imports: string[] = [];
if (importsGraph) {
for (const [file, deps] of Object.entries<string[]>(importsGraph)) {
if (file.endsWith(rPath) || rPath.endsWith(file)) {
imports.push(...deps);
for (const importer of importers) {
importedByMap.set(importer, (importedByMap.get(importer) || 0) + 1);
}
}
}
Expand All @@ -334,16 +339,50 @@ export async function handle(
}
}

// Only return if we have at least one piece of data
if (importedBy.length === 0 && imports.length === 0 && testedIn.length === 0) {
return undefined;
// Build condensed relationships
const condensedRel: Record<string, number | boolean> = {};
if (importedByMap.size > 0) {
condensedRel.importedByCount = importedByMap.size;
}
if (testedIn.length > 0) {
condensedRel.hasTests = true;
}

return {
...(importedBy.length > 0 && { importedBy }),
...(imports.length > 0 && { imports }),
...(testedIn.length > 0 && { testedIn })
};
// Build hints object with capped arrays
const hintsObj: Record<string, string[]> = {};

// Rank importers by count descending, cap at 3
if (importedByMap.size > 0) {
const sortedCallers = Array.from(importedByMap.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 3)
.map(([file]) => file);
hintsObj.callers = sortedCallers;
hintsObj.consumers = sortedCallers; // Same data, different label
}

// Cap tests at 3
if (testedIn.length > 0) {
hintsObj.tests = testedIn.slice(0, 3);
}

// Return both condensed and hints (hints only included if non-empty)
const output: RelationshipHints = {};
if (Object.keys(condensedRel).length > 0) {
output.relationships = condensedRel as {
importedByCount?: number;
hasTests?: boolean;
};
}
if (Object.keys(hintsObj).length > 0) {
output.hints = hintsObj as {
callers?: string[];
consumers?: string[];
tests?: string[];
};
}

return output;
}

const searchQuality = assessSearchQuality(query, results);
Expand Down Expand Up @@ -600,19 +639,7 @@ export async function handle(
},
...(preflightPayload && { preflight: preflightPayload }),
results: results.map((r) => {
const relationships = enrichResult(r);
// Condensed relationships: importedBy count + hasTests flag
const condensedRel = relationships
? {
...(relationships.importedBy &&
relationships.importedBy.length > 0 && {
importedByCount: relationships.importedBy.length
}),
...(relationships.testedIn &&
relationships.testedIn.length > 0 && { hasTests: true })
}
: undefined;
const hasCondensedRel = condensedRel && Object.keys(condensedRel).length > 0;
const relationshipsAndHints = buildRelationshipHints(r);

return {
file: `${r.filePath}:${r.startLine}-${r.endLine}`,
Expand All @@ -621,7 +648,8 @@ export async function handle(
...(r.componentType && r.layer && { type: `${r.componentType}:${r.layer}` }),
...(r.trend && r.trend !== 'Stable' && { trend: r.trend }),
...(r.patternWarning && { patternWarning: r.patternWarning }),
...(hasCondensedRel && { relationships: condensedRel }),
...(relationshipsAndHints.relationships && { relationships: relationshipsAndHints.relationships }),
...(relationshipsAndHints.hints && { hints: relationshipsAndHints.hints }),
...(includeSnippets && r.snippet && { snippet: r.snippet })
};
}),
Expand Down
Loading
Loading