opencode-codebase-index 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "name": "codebase-index",
11
11
  "description": "Semantic code search and codebase graph tools for Claude Code",
12
12
  "source": "./",
13
- "version": "0.13.2"
13
+ "version": "0.16.0"
14
14
  }
15
15
  ]
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebase-index",
3
- "version": "0.13.2",
3
+ "version": "0.16.0",
4
4
  "description": "Semantic code search and codebase graph tools for Claude Code",
5
5
  "displayName": "Codebase Index",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebase-index",
3
- "version": "0.13.2",
3
+ "version": "0.16.0",
4
4
  "description": "Semantic code search and codebase graph tools for Codex",
5
5
  "author": {
6
6
  "name": "Kenneth",
@@ -37,7 +37,7 @@
37
37
  "termsOfServiceURL": "https://github.com/Helweg/opencode-codebase-index/blob/main/LICENSE",
38
38
  "brandColor": "#3B82F6",
39
39
  "defaultPrompt": [
40
- "Use codebase-search skills and MCP tools to find local implementation context before edits."
40
+ "For repository questions, use index_status when readiness is unknown, then call codebase_context before shell search or broad file reads."
41
41
  ]
42
42
  }
43
43
  }
package/README.md CHANGED
@@ -115,6 +115,8 @@ pi install ./path/to/opencode-codebase-index
115
115
 
116
116
  Pi uses the neutral `.codebase-index/` project storage and falls back to existing OpenCode state when present.
117
117
 
118
+ The Pi extension injects lightweight routing guidance and exposes `codebase_context` as its preferred first repository tool. It routes conceptual discovery, known-symbol definitions, and dependency paths before broad shell search or file reads.
119
+
118
120
  ## 🧩 Codex Plugin
119
121
  Install once for Codex threads and get skill guidance plus MCP tools in one manifest.
120
122
 
@@ -144,6 +146,8 @@ npm run dev:link-mcp
144
146
 
145
147
  After that, the normal `.mcp.json` command also works when Codex starts the plugin from this repository.
146
148
 
149
+ The native Codex plugin is important: its session hook and `codebase-search` skill tell Codex to use `index_status` and `codebase_context` before shell exploration. A bare MCP configuration does not provide the same selection reliability. Current Codex `exec` releases may cancel MCP calls in non-interactive mode while waiting for an app-tool approval; use an interactive Codex thread to approve the tool call. This is a Codex client limitation, not an MCP server failure.
150
+
147
151
  ## 🧩 Claude Code Plugin
148
152
  Install once for Claude Code sessions and get skill guidance plus MCP tools in one manifest.
149
153
 
@@ -225,7 +229,19 @@ Use the same semantic search from any MCP-compatible client. Index once, search
225
229
  npx -y --package opencode-codebase-index opencode-codebase-index-mcp
226
230
  ```
227
231
 
228
- The MCP server exposes all 12 tools (`codebase_search`, `codebase_peek`, `find_similar`, `implementation_lookup`, `call_graph`, `call_graph_path`, `pr_impact`, `index_codebase`, `index_status`, `index_health_check`, `index_metrics`, `index_logs`) and 5 prompts (`search`, `find`, `definition`, `index`, `status`).
232
+ The MCP server exposes all 13 tools (`codebase_context`, `codebase_search`, `codebase_peek`, `find_similar`, `implementation_lookup`, `call_graph`, `call_graph_path`, `pr_impact`, `index_codebase`, `index_status`, `index_health_check`, `index_metrics`, `index_logs`) and 5 prompts (`search`, `find`, `definition`, `index`, `status`).
233
+
234
+ The tools carry self-routing descriptions so clients can choose the lightweight path without relying on separate documentation:
235
+
236
+ 1. `codebase_context` as the preferred single entry point for repository questions
237
+ 2. `index_status` when index readiness is unknown
238
+ 3. `codebase_peek` for direct low-token conceptual discovery
239
+ 4. `implementation_lookup` for direct known-symbol definition lookup
240
+ 5. `codebase_search` only when full semantic content is needed
241
+ 6. `grep` for exact identifiers or exhaustive matches
242
+ 7. `call_graph` / `call_graph_path` for direct graph queries
243
+
244
+ The server also publishes this workflow through the standard MCP initialization `instructions` field. Client behavior remains client-controlled: an MCP server can describe and recommend its tools, but cannot force an agent host to read server instructions or invoke a tool before filesystem search. Clients that ignore MCP instructions still receive the routing guidance in each tool description.
229
245
 
230
246
  The MCP dependencies (`@modelcontextprotocol/sdk`, `zod`) ship with the package so published `npx --package opencode-codebase-index` launches work in clean MCP clients.
231
247
 
@@ -254,9 +270,9 @@ src/api/checkout.ts:89 (Route handler for /pay)
254
270
 
255
271
  | Scenario | Tool | Why |
256
272
  |----------|------|-----|
257
- | Don't know the function name | `codebase_search` | Semantic search finds by meaning |
258
- | Exploring unfamiliar codebase | `codebase_search` | Discovers related code across files |
259
- | Just need to find locations | `codebase_peek` | Returns metadata only, saves ~90% tokens |
273
+ | Don't know the function name | `codebase_context` (MCP) | Routes conceptual questions to low-token discovery first |
274
+ | Exploring unfamiliar codebase | `codebase_context` (MCP) | Discovers related code and then guides to definitions or graph queries |
275
+ | Just need to find locations | `codebase_peek` (or `codebase_context`) | Returns metadata only, saves ~90% tokens |
260
276
  | Need the authoritative definition site | `implementation_lookup` | Prioritizes real implementation definitions over docs/tests |
261
277
  | Understand code flow | `call_graph` | Find callers/callees of any function |
262
278
  | Trace dependency paths | `call_graph_path` | Find the shortest known call path between two symbols |
@@ -264,7 +280,7 @@ src/api/checkout.ts:89 (Route handler for /pay)
264
280
  | Need ALL matches | `grep` | Semantic returns top N only |
265
281
  | Mixed discovery + precision | `/find` (hybrid) | Best of both worlds |
266
282
 
267
- **Rule of thumb**: `codebase_peek` to find locations `Read` to examine `grep` for precision. For symbol-definition questions, use `implementation_lookup` first.
283
+ **Rule of thumb**: `codebase_context` to route discovery first. Then `Read` to examine exact content and `grep` for precision. For symbol-definition questions, use `implementation_lookup` first.
268
284
 
269
285
  ## 🧭 OMO CodeGraph Compatibility
270
286
 
@@ -281,7 +297,7 @@ Recent OMO releases include a built-in CodeGraph MCP and make it part of the def
281
297
 
282
298
  Recommended OMO workflow:
283
299
 
284
- 1. Start broad with `codebase_peek` when the prompt is conceptual, such as "where is auth enforced?" or "payment validation flow".
300
+ 1. Start broad with `codebase_context` when the prompt is conceptual, such as "where is auth enforced?" or "payment validation flow".
285
301
  2. Use `implementation_lookup` once you have a symbol or concept that should resolve to a definition.
286
302
  3. Use OMO CodeGraph, `call_graph`, or `call_graph_path` after locating the relevant symbol to check blast radius and dependency flow.
287
303
  4. Keep `grep` for exact identifiers and exhaustive text matches.
@@ -424,9 +440,18 @@ The following files/folders are excluded from indexing by default:
424
440
 
425
441
  The plugin exposes these tools to the OpenCode agent:
426
442
 
443
+ `codebase_context` is MCP-server-only.
444
+
445
+ ### `codebase_context`
446
+ *MCP-only entrypoint for combined routing*
447
+ **Preferred first tool for repository questions.** Routes to the lowest-token indexed operation that matches the query: conceptual discovery, definition lookup, callers/callees, or symbol-to-symbol paths.
448
+ - **Use for**: New questions about behavior, locating symbols, or tracing direct call relationships.
449
+ - **Example**: `"Where is the payment validation logic?"`
450
+ - **Workflow**: If the query is conceptual, it may return locations first. For exact behavior text, follow with `codebase_search`.
451
+
427
452
  ### `codebase_search`
428
- **The primary tool.** Searches code by describing behavior.
429
- - **Use for**: Discovery, understanding flows, finding logic when you don't know the names.
453
+ **Behavioral semantic retrieval with full content.** Searches code by describing behavior.
454
+ - **Use for**: Discovery when you already want full matching snippets and are ready to inspect implementation text.
430
455
  - **Example**: `"find the middleware that sanitizes input"`
431
456
  - **Ranking path**: hybrid retrieval → fusion (`search.fusionStrategy`) → deterministic rerank (`search.rerankTopN`) → filters
432
457
  - **Blame filters**: when `indexing.gitBlame.enabled` is `true`, filter with `blameAuthor`, `blameSha`, or `blameSince`.
package/dist/cli.cjs CHANGED
@@ -8998,10 +8998,19 @@ var Indexer = class {
8998
8998
  });
8999
8999
  const embeddingStartTime = import_perf_hooks.performance.now();
9000
9000
  const embeddingQuery = stripFilePathHint(query);
9001
- const embedding = await this.getQueryEmbedding(embeddingQuery, provider);
9001
+ let embedding;
9002
+ try {
9003
+ embedding = await this.getQueryEmbedding(embeddingQuery, provider);
9004
+ } catch (error) {
9005
+ this.logger.warn("Query embedding failed; falling back to keyword-only search", {
9006
+ query,
9007
+ error: getErrorMessage2(error),
9008
+ action: "Check the embedding provider configuration and retry search after restoring provider health."
9009
+ });
9010
+ }
9002
9011
  const embeddingMs = import_perf_hooks.performance.now() - embeddingStartTime;
9003
9012
  const vectorStartTime = import_perf_hooks.performance.now();
9004
- const semanticResults = store.search(embedding, maxResults * 4);
9013
+ const semanticResults = embedding ? store.search(embedding, maxResults * 4) : [];
9005
9014
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
9006
9015
  const keywordStartTime = import_perf_hooks.performance.now();
9007
9016
  const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
@@ -9036,12 +9045,13 @@ var Indexer = class {
9036
9045
  });
9037
9046
  }
9038
9047
  const fusionStartTime = import_perf_hooks.performance.now();
9048
+ const rankingHybridWeight = embedding === void 0 && fusionStrategy === "weighted" ? 1 : effectiveHybridWeight;
9039
9049
  const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
9040
9050
  fusionStrategy,
9041
9051
  rrfK,
9042
9052
  rerankTopN,
9043
9053
  limit: maxResults,
9044
- hybridWeight: effectiveHybridWeight,
9054
+ hybridWeight: rankingHybridWeight,
9045
9055
  prioritizeSourcePaths: sourceIntent
9046
9056
  });
9047
9057
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -11942,20 +11952,87 @@ var CHUNK_TYPE_ENUM = [
11942
11952
  ];
11943
11953
 
11944
11954
  // src/mcp-server/register-tools.ts
11955
+ function allowNullAsUndefined(schema) {
11956
+ return import_zod2.z.preprocess((value) => value === null ? void 0 : value, schema);
11957
+ }
11945
11958
  function registerMcpTools(server, runtime) {
11959
+ server.tool(
11960
+ "codebase_context",
11961
+ "PREFERRED FIRST TOOL for any question about this repository. Use before built-in code search, grep, shell search, or broad file reads. Provide from+to for a dependency path, symbol for a definition, or only query for low-token conceptual discovery. Use call_graph directly for callers or callees.",
11962
+ {
11963
+ query: import_zod2.z.string().describe("The codebase question or behavior to locate. Always provide the user's repository question here."),
11964
+ from: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Source symbol. For dependency-path questions, extract the first endpoint and provide it here."),
11965
+ to: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Target symbol. For dependency-path questions, extract the second endpoint and provide it here."),
11966
+ symbol: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Exact symbol for an authoritative definition lookup. Omit when from and to are supplied."),
11967
+ limit: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum number of search or definition results"),
11968
+ maxDepth: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum call-graph traversal depth for from/to path lookup"),
11969
+ fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11970
+ directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')")
11971
+ },
11972
+ async (args) => {
11973
+ if (args.from && args.to) {
11974
+ const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
11975
+ if (path27.length > 0) {
11976
+ return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
11977
+ }
11978
+ const { callers } = await getCallGraphData(runtime.projectRoot, runtime.host, {
11979
+ name: args.to,
11980
+ direction: "callers"
11981
+ });
11982
+ const directEdge = callers.find((edge) => edge.fromSymbolName === args.from);
11983
+ if (directEdge) {
11984
+ const location = directEdge.fromSymbolFilePath ? ` at ${directEdge.fromSymbolFilePath}:${directEdge.line}` : "";
11985
+ return {
11986
+ content: [{
11987
+ type: "text",
11988
+ text: `Direct path: ${args.from} --${directEdge.callType}--> ${args.to}${location} (edge is ${directEdge.isResolved ? "resolved" : "unresolved"}).`
11989
+ }]
11990
+ };
11991
+ }
11992
+ return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
11993
+ }
11994
+ if (args.symbol) {
11995
+ const results2 = await implementationLookup(runtime.projectRoot, runtime.host, args.symbol, {
11996
+ limit: args.limit ?? 10,
11997
+ fileType: args.fileType,
11998
+ directory: args.directory
11999
+ });
12000
+ return { content: [{ type: "text", text: formatDefinitionLookup(results2, args.symbol) }] };
12001
+ }
12002
+ const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
12003
+ limit: args.limit ?? 10,
12004
+ fileType: args.fileType,
12005
+ directory: args.directory,
12006
+ metadataOnly: true
12007
+ });
12008
+ if (results.length === 0) {
12009
+ return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
12010
+ }
12011
+ return {
12012
+ content: [{
12013
+ type: "text",
12014
+ text: `Found ${results.length} locations for "${args.query}":
12015
+
12016
+ ${formatCodebasePeek(results)}
12017
+
12018
+ Use implementation_lookup for an authoritative definition, or call call_graph/call_graph_path once you have a symbol.`
12019
+ }]
12020
+ };
12021
+ }
12022
+ );
11946
12023
  server.tool(
11947
12024
  "codebase_search",
11948
- "Search codebase by MEANING, not keywords. Returns full code content. For just finding WHERE code is (saves ~90% tokens), use codebase_peek instead.",
12025
+ "FULL-CONTENT semantic retrieval. Use after codebase_peek when you need implementation text, not as the default first step. For exact identifiers or exhaustive matches use grep instead.",
11949
12026
  {
11950
12027
  query: import_zod2.z.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
11951
- limit: import_zod2.z.number().optional().default(5).describe("Maximum number of results to return"),
11952
- fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11953
- directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
11954
- chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
11955
- contextLines: import_zod2.z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
11956
- blameAuthor: import_zod2.z.string().optional().describe("Filter by git blame author name or email"),
11957
- blameSha: import_zod2.z.string().optional().describe("Filter by git blame commit SHA or prefix"),
11958
- blameSince: import_zod2.z.string().optional().describe("Filter to chunks last changed on or after this date")
12028
+ limit: allowNullAsUndefined(import_zod2.z.number().optional().default(5)).describe("Maximum number of results to return"),
12029
+ fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12030
+ directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12031
+ chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
12032
+ contextLines: allowNullAsUndefined(import_zod2.z.number().optional()).describe("Number of extra lines to include before/after each match (default: 0)"),
12033
+ blameAuthor: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame author name or email"),
12034
+ blameSha: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame commit SHA or prefix"),
12035
+ blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date")
11959
12036
  },
11960
12037
  async (args) => {
11961
12038
  const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
@@ -11978,16 +12055,16 @@ ${formatSearchResults(results, "score")}` }] };
11978
12055
  );
11979
12056
  server.tool(
11980
12057
  "codebase_peek",
11981
- "Quick lookup of code locations by meaning. Returns only metadata (file, line, name, type) WITHOUT code content. Saves ~90% tokens vs codebase_search.",
12058
+ "DIRECT LOW-TOKEN semantic location lookup for unfamiliar-code discovery. Prefer codebase_context when the request may involve definitions or graph navigation; use this specialized tool when you only need conceptual locations.",
11982
12059
  {
11983
12060
  query: import_zod2.z.string().describe("Natural language description of what code you're looking for."),
11984
- limit: import_zod2.z.number().optional().default(10).describe("Maximum number of results to return"),
11985
- fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
11986
- directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
11987
- chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
11988
- blameAuthor: import_zod2.z.string().optional().describe("Filter by git blame author name or email"),
11989
- blameSha: import_zod2.z.string().optional().describe("Filter by git blame commit SHA or prefix"),
11990
- blameSince: import_zod2.z.string().optional().describe("Filter to chunks last changed on or after this date")
12061
+ limit: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum number of results to return"),
12062
+ fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12063
+ directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12064
+ chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
12065
+ blameAuthor: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame author name or email"),
12066
+ blameSha: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame commit SHA or prefix"),
12067
+ blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date")
11991
12068
  },
11992
12069
  async (args) => {
11993
12070
  const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
@@ -12012,11 +12089,11 @@ Use Read tool to examine specific files.` }] };
12012
12089
  );
12013
12090
  server.tool(
12014
12091
  "index_codebase",
12015
- "Index the codebase for semantic search. Creates vector embeddings of code chunks. Incremental - only re-indexes changed files. Run before first codebase_search.",
12092
+ "Create or refresh the semantic index. Call index_status first when readiness is unknown, then use this tool only if the index is missing, stale, or incompatible. Incremental by default; force=true rebuilds everything.",
12016
12093
  {
12017
- force: import_zod2.z.boolean().optional().default(false).describe("Force reindex even if already indexed"),
12018
- estimateOnly: import_zod2.z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
12019
- verbose: import_zod2.z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
12094
+ force: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Force reindex even if already indexed"),
12095
+ estimateOnly: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Only show cost estimate without indexing"),
12096
+ verbose: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Show detailed info about skipped files and parsing failures")
12020
12097
  },
12021
12098
  async (args) => {
12022
12099
  const result = await runIndexCodebase(runtime.projectRoot, runtime.host, args);
@@ -12031,7 +12108,7 @@ Use Read tool to examine specific files.` }] };
12031
12108
  );
12032
12109
  server.tool(
12033
12110
  "index_status",
12034
- "Check the status of the codebase index. Shows whether the codebase is indexed, how many chunks are stored, and the embedding provider being used.",
12111
+ "START HERE once per repository task when index readiness or freshness is unknown. Reports whether semantic retrieval is ready, chunk counts, compatibility, and the embedding provider. If ready, continue with codebase_peek or implementation_lookup; otherwise run index_codebase.",
12035
12112
  {},
12036
12113
  async () => {
12037
12114
  const status = await getIndexStatus(runtime.projectRoot, runtime.host);
@@ -12063,9 +12140,13 @@ Use Read tool to examine specific files.` }] };
12063
12140
  "index_logs",
12064
12141
  "Get recent debug logs from the codebase indexer. Requires debug.enabled=true in config.",
12065
12142
  {
12066
- limit: import_zod2.z.number().optional().default(20).describe("Maximum number of log entries to return"),
12067
- category: import_zod2.z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional().describe("Filter by log category"),
12068
- level: import_zod2.z.enum(["error", "warn", "info", "debug"]).optional().describe("Filter by minimum log level")
12143
+ limit: allowNullAsUndefined(import_zod2.z.number().optional().default(20)).describe("Maximum number of log entries to return"),
12144
+ category: allowNullAsUndefined(
12145
+ import_zod2.z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional()
12146
+ ).describe("Filter by log category"),
12147
+ level: allowNullAsUndefined(
12148
+ import_zod2.z.enum(["error", "warn", "info", "debug"]).optional()
12149
+ ).describe("Filter by minimum log level")
12069
12150
  },
12070
12151
  async (args) => {
12071
12152
  const result = await getIndexLogs(runtime.projectRoot, runtime.host, args);
@@ -12074,14 +12155,14 @@ Use Read tool to examine specific files.` }] };
12074
12155
  );
12075
12156
  server.tool(
12076
12157
  "find_similar",
12077
- "Find code similar to a given snippet. Use for duplicate detection, pattern discovery, or refactoring prep.",
12158
+ "Use when you already have a code snippet and need analogous implementations, duplicates, patterns, or refactoring candidates. For a natural-language concept without example code, start with codebase_peek instead.",
12078
12159
  {
12079
12160
  code: import_zod2.z.string().describe("The code snippet to find similar code for"),
12080
- limit: import_zod2.z.number().optional().default(10).describe("Maximum number of results to return"),
12081
- fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12082
- directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12083
- chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
12084
- excludeFile: import_zod2.z.string().optional().describe("Exclude results from this file path")
12161
+ limit: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum number of results to return"),
12162
+ fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
12163
+ directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
12164
+ chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
12165
+ excludeFile: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Exclude results from this file path")
12085
12166
  },
12086
12167
  async (args) => {
12087
12168
  const results = await findSimilarCode(runtime.projectRoot, runtime.host, args.code, {
@@ -12101,12 +12182,12 @@ ${formatSearchResults(results)}` }] };
12101
12182
  );
12102
12183
  server.tool(
12103
12184
  "implementation_lookup",
12104
- "Jump to symbol definition. Find WHERE something is defined. Returns the authoritative source location(s). Prefers real implementation files over tests, docs, examples, and fixtures.",
12185
+ "FIRST TOOL only for known-symbol definition questions. Returns authoritative source locations and prefers implementations over tests, docs, examples, and fixtures. Do not use for callers, callees, dependency paths, or code flow; use codebase_context with direction or from/to for those questions.",
12105
12186
  {
12106
12187
  query: import_zod2.z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
12107
- limit: import_zod2.z.number().optional().default(5).describe("Maximum number of results"),
12108
- fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
12109
- directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
12188
+ limit: allowNullAsUndefined(import_zod2.z.number().optional().default(5)).describe("Maximum number of results"),
12189
+ fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py')"),
12190
+ directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils')")
12110
12191
  },
12111
12192
  async (args) => {
12112
12193
  const results = await implementationLookup(runtime.projectRoot, runtime.host, args.query, {
@@ -12119,12 +12200,16 @@ ${formatSearchResults(results)}` }] };
12119
12200
  );
12120
12201
  server.tool(
12121
12202
  "call_graph",
12122
- "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
12203
+ "Use after identifying a symbol to find its direct callers or callees and understand code flow. Use implementation_lookup first if the symbol or definition is still ambiguous. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
12123
12204
  {
12124
12205
  name: import_zod2.z.string().describe("Function or method name to query"),
12125
- direction: import_zod2.z.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
12126
- symbolId: import_zod2.z.string().optional().describe("Symbol ID (required for 'callees' direction)"),
12127
- relationshipType: import_zod2.z.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
12206
+ direction: allowNullAsUndefined(
12207
+ import_zod2.z.enum(["callers", "callees"]).default("callers")
12208
+ ).describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
12209
+ symbolId: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Symbol ID (required for 'callees' direction)"),
12210
+ relationshipType: allowNullAsUndefined(
12211
+ import_zod2.z.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional()
12212
+ ).describe("Filter by relationship type. Omit to show all.")
12128
12213
  },
12129
12214
  async (args) => {
12130
12215
  if (args.direction === "callees") {
@@ -12140,11 +12225,11 @@ ${formatSearchResults(results)}` }] };
12140
12225
  );
12141
12226
  server.tool(
12142
12227
  "call_graph_path",
12143
- "Find the shortest connection path between two symbols in the call graph. Returns the chain of calls connecting them.",
12228
+ "Use after identifying both endpoint symbols to find the shortest known call path between them. Use codebase_peek or implementation_lookup first when either endpoint is unknown.",
12144
12229
  {
12145
12230
  from: import_zod2.z.string().describe("Source function/method name (starting point)"),
12146
12231
  to: import_zod2.z.string().describe("Target function/method name (destination)"),
12147
- maxDepth: import_zod2.z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
12232
+ maxDepth: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum traversal depth (default: 10)")
12148
12233
  },
12149
12234
  async (args) => {
12150
12235
  const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
@@ -12153,14 +12238,16 @@ ${formatSearchResults(results)}` }] };
12153
12238
  );
12154
12239
  server.tool(
12155
12240
  "pr_impact",
12156
- "Analyze the impact of a pull request or branch by examining changed files, affected symbols, transitive callers, communities touched, hub nodes, and risk level. Use to understand blast radius before merging.",
12241
+ "FIRST TOOL for pull-request or branch blast-radius questions. Analyzes changed files, affected symbols, transitive dependencies, communities, hub nodes, conflicts, and risk before merging.",
12157
12242
  {
12158
- pr: import_zod2.z.number().optional().describe("Pull request number to analyze"),
12159
- branch: import_zod2.z.string().optional().describe("Branch name to analyze (defaults to current branch)"),
12160
- maxDepth: import_zod2.z.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
12161
- hubThreshold: import_zod2.z.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
12162
- checkConflicts: import_zod2.z.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
12163
- direction: import_zod2.z.enum(["callers", "callees", "both"]).optional().default("both").describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
12243
+ pr: allowNullAsUndefined(import_zod2.z.number().optional()).describe("Pull request number to analyze"),
12244
+ branch: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Branch name to analyze (defaults to current branch)"),
12245
+ maxDepth: allowNullAsUndefined(import_zod2.z.number().optional().default(5)).describe("Maximum traversal depth for transitive callers (default: 5)"),
12246
+ hubThreshold: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
12247
+ checkConflicts: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Check for conflicting open PRs touching the same communities (default: false)"),
12248
+ direction: allowNullAsUndefined(
12249
+ import_zod2.z.enum(["callers", "callees", "both"]).optional().default("both")
12250
+ ).describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
12164
12251
  },
12165
12252
  async (args) => {
12166
12253
  try {
@@ -12183,6 +12270,10 @@ ${formatSearchResults(results)}` }] };
12183
12270
 
12184
12271
  // src/mcp-server.ts
12185
12272
  var import_meta2 = {};
12273
+ function getServerInstructions(host) {
12274
+ const hostText = `host ${host}`;
12275
+ return `This MCP server is the preferred codebase-understanding path for ${hostText}. Start a repository task with index_status when index readiness or freshness is unknown. Use codebase_context as the preferred first entry point because it returns low-token locations first and routes to definitions or call-graph helpers when symbol intent is present. Use codebase_peek for direct conceptual location lookup, implementation_lookup for known-symbol definition questions, and codebase_search only when full semantic code content is needed. For exact identifiers or exhaustive matches, use grep. After identifying symbols, use call_graph or call_graph_path to trace dependencies. If the index is unavailable, run index_codebase, then retry the retrieval tool.`;
12276
+ }
12186
12277
  function getPackageVersion() {
12187
12278
  const raw = JSON.parse((0, import_fs15.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
12188
12279
  if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
@@ -12194,6 +12285,8 @@ function createMcpServer(projectRoot, config, host = "opencode") {
12194
12285
  const server = new import_mcp.McpServer({
12195
12286
  name: "opencode-codebase-index",
12196
12287
  version: getPackageVersion()
12288
+ }, {
12289
+ instructions: getServerInstructions(host)
12197
12290
  });
12198
12291
  initializeTools(projectRoot, config, host);
12199
12292
  registerMcpTools(server, {