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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Source-backed architecture context**: Added the portable `architecture_context` tool across OpenCode, MCP, and Pi. It produces deterministic, token-bounded repository maps with source-derived responsibility excerpts, cited community and boundary evidence, strict query and directory focus, graph-sparse source-directory fallback, uncertainty notes, precise follow-up tool calls, and optional Git-backed recent activity. Architecture planning evaluation now measures graded evidence relevance and actual response token cost.

## [0.25.1] - 2026-08-23

### Fixed
Expand Down
16 changes: 16 additions & 0 deletions benchmarks/budgets/architecture-context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "architecture-context-eval-budget",
"failOnMissingBaseline": false,
"thresholds": {
"p95LatencyMaxAbsoluteMs": 5000,
"minHitAt5": 0.75,
"minMrrAt10": 0.5,
"maxContextResponseTokensAverage": 1000,
"maxContextResponseTokensP95": 1000,
"maxContextResponseTokensMax": 1000,
"maxContextDuplicateCandidateRatio": 0.5,
"minContextSelectedFileRatio": 0.3,
"minContextHitAt5Per1kResponseTokens": 0.75,
"minContextMrrAt10Per1kResponseTokens": 0.5
}
}
131 changes: 131 additions & 0 deletions benchmarks/golden/architecture-context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{
"version": "1.0.0",
"name": "architecture-context",
"description": "Repository planning queries evaluated through source-backed architecture_context evidence and response token cost.",
"queries": [
{
"id": "architecture-portable-tool",
"query": "map the portable architecture_context operation and how host adapters execute it",
"queryType": "architecture",
"retrievalMode": "architecture",
"difficulty": "medium",
"args": {
"directory": "src",
"depth": 3,
"tokenBudget": 1000
},
"tags": ["architecture", "planning", "portable-tools"],
"expected": {
"gradedEvidence": [
{
"path": "src/tools/architecture-context.ts",
"relevance": 3
},
{
"path": "src/tools/operations.ts",
"relevance": 3
},
{
"path": "src/tools/execute-common.ts",
"relevance": 2
},
{
"path": "src/adapters/mcp/register-tools.ts",
"relevance": 1
}
]
}
},
{
"id": "architecture-evaluation-pipeline",
"query": "plan a change to evaluation routing, golden dataset parsing, relevance metrics, and token budgets",
"queryType": "architecture",
"retrievalMode": "architecture",
"difficulty": "hard",
"args": {
"directory": "src/eval",
"depth": 3,
"tokenBudget": 1000
},
"tags": ["architecture", "planning", "evaluation"],
"expected": {
"gradedEvidence": [
{
"path": "src/eval/runner.ts",
"relevance": 3
},
{
"path": "src/eval/schema.ts",
"relevance": 2
},
{
"path": "src/eval/metrics.ts",
"relevance": 2
},
{
"path": "src/eval/budget.ts",
"relevance": 1
}
]
}
},
{
"id": "architecture-mcp-boundary",
"query": "understand MCP server tool registration and shared execution boundaries before adding a portable tool",
"queryType": "architecture",
"retrievalMode": "architecture",
"difficulty": "medium",
"args": {
"directory": "src/adapters/mcp",
"depth": 2,
"tokenBudget": 900
},
"tags": ["architecture", "planning", "mcp"],
"expected": {
"gradedEvidence": [
{
"path": "src/adapters/mcp/register-tools.ts",
"relevance": 3
},
{
"path": "src/adapters/mcp/server.ts",
"relevance": 2
},
{
"path": "src/adapters/mcp/shared.ts",
"relevance": 1
}
]
}
},
{
"id": "architecture-indexing-boundaries",
"query": "map indexing orchestration, embedding batches, and native persistence boundaries before changing indexing",
"queryType": "architecture",
"retrievalMode": "architecture",
"difficulty": "hard",
"args": {
"directory": "src/indexer",
"depth": 3,
"tokenBudget": 1000
},
"tags": ["architecture", "planning", "indexing"],
"expected": {
"gradedEvidence": [
{
"path": "src/indexer/index.ts",
"relevance": 3
},
{
"path": "src/indexer/embedding-batches.ts",
"relevance": 2
},
{
"path": "src/indexer/search-ranking.ts",
"relevance": 1
}
]
}
}
]
}
34 changes: 22 additions & 12 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Tool availability depends on the host mode.

| Host / integration | Tool surface | Additional capabilities |
|---|---|---|
| `opencode` (plugin) | 19 tools in total (15 portable tools + 3 knowledge-base tools + 1 OpenCode-native tool) | Slash commands and `index_visualize` |
| MCP clients, including `codex`, `claude`, and `jcode` | 18 tools + 5 prompts (15 portable tools + 3 knowledge-base tools) | Knowledge-base management for every MCP client; no OpenCode slash commands |
| `pi` (Pi extension) | 18 tools total (15 portable tools + 3 Pi knowledge-base tools) | Bundled `codebase-search` skill with host-specific knowledge-base names |
| `opencode` (plugin) | 20 tools in total (16 portable tools + 3 knowledge-base tools + 1 OpenCode-native tool) | Slash commands and `index_visualize` |
| MCP clients, including `codex`, `claude`, and `jcode` | 19 tools + 5 prompts (16 portable tools + 3 knowledge-base tools) | Knowledge-base management for every MCP client; no OpenCode slash commands |
| `pi` (Pi extension) | 19 tools total (16 portable tools + 3 Pi knowledge-base tools) | Bundled `codebase-search` skill with host-specific knowledge-base names |

### Portable MCP core (15 tools)
### Portable MCP core (16 tools)

These tools are available through the MCP server and the OpenCode plugin.

Expand All @@ -28,6 +28,7 @@ These tools are available through the MCP server and the OpenCode plugin.
- `call_graph`
- `call_graph_path`
- `pr_impact`
- `architecture_context`
- `code_communities`

The MCP server also exposes five prompts:
Expand Down Expand Up @@ -72,21 +73,30 @@ Pi does not expose the shared knowledge-base names. It registers equivalent tool
- `knowledge_base_add`
- `knowledge_base_remove`

Pi exposes all 15 portable tools, including `call_graph` and `call_graph_path`, plus its three host-specific knowledge-base aliases.
Pi exposes all 16 portable tools, including `architecture_context`, `call_graph`, and `call_graph_path`, plus its three host-specific knowledge-base aliases.

## Recommended selection order

1. `index_status` when index readiness is unknown.
2. `codebase_context` for a repository question that may require discovery, a definition, or a dependency path.
3. `codebase_edit_context` optionally when a broad change request already has a known or suspected target symbol, for compact pre-edit source plus caller/callee context.
4. `codebase_peek` for direct low-token location discovery.
5. `implementation_lookup` for a known symbol or definition question.
6. `codebase_search` when full matching source content is required.
7. `grep` for exact identifiers or exhaustive text matches.
8. `call_graph` or `call_graph_path` for graph-specific questions.
2. `architecture_context` before repository-scale planning when module responsibilities and boundaries are not yet known.
3. `codebase_context` for a repository question that may require discovery, a definition, or a dependency path.
4. `codebase_edit_context` optionally when a broad change request already has a known or suspected target symbol, for compact pre-edit source plus caller/callee context.
5. `codebase_peek` for direct low-token location discovery.
6. `implementation_lookup` for a known symbol or definition question.
7. `codebase_search` when full matching source content is required.
8. `grep` for exact identifiers or exhaustive text matches.
9. `call_graph` or `call_graph_path` for graph-specific questions.

## Core retrieval tools

### `architecture_context`

Use `architecture_context` when an agent needs a concise repository map before focused retrieval or edits. Each module includes a responsibility excerpt derived from readable source and exact symbol/file/line citations. Cross-module boundaries include representative source and target symbols, while missing or sparse graph coverage is reported explicitly instead of inferred.

`query` and `directory` constrain which modules can consume the response budget. `depth` controls detail from 1 to 3, and `tokenBudget` is enforced from 128 to 4000 estimated tokens without cutting claims or citations mid-entry. When community data is unavailable, the tool can still group matching indexed symbols by source directory, but it labels that fallback and does not invent relationships.

Set `includeRecentActivity: true` to include matching Git activity from the last 90 days with commit, date, summary, and file provenance. If no matching Git history exists, the tool reports that directly and does not substitute graph importance as recent activity.

### `codebase_context`

Preferred entry point for general repository questions. It returns a bounded, deduplicated, file-diverse evidence pack. It can also route explicit symbol definitions or `from`/`to` dependency-path requests.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"dev": "tsup --watch",
"eval": "npx tsx src/cli.ts eval run",
"eval:agent": "npx tsx src/cli.ts eval run --dataset benchmarks/golden/agent-context.json",
"eval:architecture": "npx tsx src/cli.ts eval run --dataset benchmarks/golden/architecture-context.json",
"eval:architecture:ci": "npx tsx src/cli.ts eval run --config .github/eval-config.json --reindex --dataset benchmarks/golden/architecture-context.json --ci --budget benchmarks/budgets/architecture-context.json",
"eval:pre-edit": "npx tsx src/cli.ts eval run --dataset benchmarks/golden/pre-edit-context.json",
"eval:representative": "npx tsx src/cli.ts eval run --dataset benchmarks/golden/representative.json",
"eval:representative:ollama": "npx tsx src/cli.ts eval run --config .github/eval-ollama-full-config.json --reindex --dataset benchmarks/golden/representative.json --budget benchmarks/budgets/representative.json",
Expand Down
17 changes: 17 additions & 0 deletions src/adapters/mcp/register-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
executeCodebaseContext,
executeCodebaseEditContext,
executeCodeCommunities,
executeArchitectureContext,
executeIndexCodebase,
executeIndexHealthCheck,
executeIndexLogs,
Expand Down Expand Up @@ -373,6 +374,22 @@ export function registerMcpTools(server: McpServer, runtime: McpServerRuntime):
},
);

server.tool(
TOOL_NAME.ARCHITECTURE_CONTEXT,
"Repository-scale architecture map backed by cited graph symbols and relationships. Use before focused retrieval when module boundaries or entry points are needed.",
{
query: allowNullAsUndefined(z.string().optional()).describe("Optional subsystem or planning focus"),
directory: allowNullAsUndefined(z.string().optional()).describe("Constrain the map to this directory"),
depth: allowNullAsUndefined(z.number().int().min(1).max(3).optional().default(2)).describe("Summary detail level (1-3)"),
includeRecentActivity: allowNullAsUndefined(z.boolean().optional().default(false)).describe("Include recent activity when available"),
tokenBudget: allowNullAsUndefined(z.number().int().min(128).max(4000).optional().default(1200)).describe("Maximum response token budget"),
},
async (args) => {
const result = await executeArchitectureContext(runtime.projectRoot, runtime.host, args);
return { content: [{ type: "text", text: result.text }] };
},
);

server.tool(
TOOL_NAME.CODE_COMMUNITIES,
"Discover natural module boundaries and hub symbols using graph community detection. " +
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/opencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
find_similar,
call_graph,
call_graph_path,
architecture_context,
code_communities,
implementation_lookup,
add_knowledge_base,
Expand Down Expand Up @@ -159,6 +160,7 @@ const plugin: Plugin = async ({ directory, worktree }) => {
[TOOL_NAME.LIST_KNOWLEDGE_BASES]: list_knowledge_bases,
[TOOL_NAME.REMOVE_KNOWLEDGE_BASE]: remove_knowledge_base,
[TOOL_NAME.PR_IMPACT]: pr_impact,
[TOOL_NAME.ARCHITECTURE_CONTEXT]: architecture_context,
[TOOL_NAME.CODE_COMMUNITIES]: code_communities,
[TOOL_NAME.INDEX_VISUALIZE]: index_visualize,
},
Expand Down
15 changes: 15 additions & 0 deletions src/adapters/opencode/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
executeCodebaseContext,
executeCodebaseEditContext,
executeCodeCommunities,
executeArchitectureContext,
executeIndexCodebase,
executeIndexHealthCheck,
executeIndexLogs,
Expand Down Expand Up @@ -394,6 +395,20 @@ export const remove_knowledge_base: ToolDefinition = tool({

export { pr_impact };

export const architecture_context: ToolDefinition = tool({
description: "Repository-scale architecture map backed by cited graph symbols and relationships. Use before focused retrieval when you need module boundaries, entry points, and safe next steps.",
args: {
query: z.string().nullable().optional().describe("Optional subsystem or planning focus"),
directory: z.string().nullable().optional().describe("Constrain the map to this directory"),
depth: z.number().int().min(1).max(3).optional().default(2).describe("Summary detail level (1-3)"),
includeRecentActivity: z.boolean().optional().default(false).describe("Include matching Git activity from the last 90 days when available"),
tokenBudget: z.number().int().min(128).max(4000).optional().default(1200).describe("Maximum response token budget"),
},
async execute(args, context) {
return (await executeArchitectureContext(context?.worktree, DEFAULT_HOST, args)).text;
},
});

export const code_communities: ToolDefinition = tool({
description:
"Discover natural module boundaries and hub symbols in the codebase using graph community detection. " +
Expand Down
18 changes: 18 additions & 0 deletions src/adapters/pi/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { loadMergedConfig } from "../../config/merger.js";
import { formatCostEstimate, formatDryRunEstimate } from "../../utils/cost.js";
import { formatPrImpact } from "../../tools/format-pr-impact.js";
import { formatCodeCommunities } from "../../tools/format-communities.js";
import { executeArchitectureContext } from "../../tools/execute-common.js";
import {
addKnowledgeBase,
findSimilarCode,
Expand Down Expand Up @@ -403,6 +404,23 @@ export default function codebaseIndexPiExtension(pi: ExtensionAPI): void {
},
});

pi.registerTool({
name: TOOL_NAME.ARCHITECTURE_CONTEXT,
label: "Architecture Context",
description: "Repository-scale architecture map with source-backed module, boundary, and hub evidence.",
parameters: Type.Object({
query: Type.Optional(Type.String()),
directory: Type.Optional(Type.String()),
depth: Type.Optional(Type.Integer({ minimum: 1, maximum: 3, default: 2 })),
includeRecentActivity: Type.Optional(Type.Boolean({ default: false })),
tokenBudget: Type.Optional(Type.Integer({ minimum: 128, maximum: 4000, default: 1200 })),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const result = await executeArchitectureContext(projectRoot(ctx), HOST, params);
return text(result.text, result.details);
},
});

pi.registerTool({
name: TOOL_NAME.CODE_COMMUNITIES,
label: "Code Communities",
Expand Down
17 changes: 17 additions & 0 deletions src/eval/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
GoldenGradedEvidence,
GoldenQuery,
PerQueryEvalResult,
GoldenRetrievalMode,
} from "./types.js";

function percentile(values: number[], p: number): number {
Expand Down Expand Up @@ -435,6 +436,12 @@ export function computeEvalMetrics(
hitAt10: 0,
mrrAt10: 0,
ndcgAt10: 0,
retrievalModeCounts: {
search: 0,
context: 0,
"edit-context": 0,
architecture: 0,
} satisfies Partial<Record<GoldenRetrievalMode, number>>,
distinctTop3Ratio: 0,
rawDistinctTop3Ratio: 0,
};
Expand Down Expand Up @@ -462,6 +469,15 @@ export function computeEvalMetrics(
let graphNeighborExpectedCount = 0;

for (const query of perQuery) {
if (
query.retrievalMode === "search"
|| query.retrievalMode === "context"
|| query.retrievalMode === "edit-context"
|| query.retrievalMode === "architecture"
) {
sum.retrievalModeCounts[query.retrievalMode] += 1;
}

if (positiveQueryIds.has(query.id)) {
if (query.hitAt1) sum.hitAt1 += 1;
if (query.hitAt3) sum.hitAt3 += 1;
Expand Down Expand Up @@ -505,6 +521,7 @@ export function computeEvalMetrics(
hitAt10: safePositiveDiv(sum.hitAt10),
mrrAt10: safePositiveDiv(sum.mrrAt10),
ndcgAt10: safePositiveDiv(sum.ndcgAt10),
retrievalModeCounts: sum.retrievalModeCounts,
routeAccuracy: routeExpectedCount === 0 ? 0 : routeMatchedCount / routeExpectedCount,
outcomeAccuracy: outcomeExpectedCount === 0 ? 0 : outcomeMatchedCount / outcomeExpectedCount,
recoveryAccuracy: recoveryExpectedCount === 0 ? 0 : recoveryMatchedCount / recoveryExpectedCount,
Expand Down
Loading
Loading