open-codebase-index 0.22.3 → 0.22.4

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.
package/dist/index.cjs CHANGED
@@ -1925,6 +1925,9 @@ var RELATIONSHIP_TYPES = [
1925
1925
  ];
1926
1926
  var INDEX_LOG_CATEGORIES = ["search", "embedding", "cache", "gc", "branch", "general"];
1927
1927
  var INDEX_LOG_LEVELS = ["error", "warn", "info", "debug"];
1928
+ var MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT = 1;
1929
+ var MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT = 20;
1930
+ var DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT = 5;
1928
1931
  var CODE_COMMUNITIES_MIN_SIZE = 1;
1929
1932
  var CODE_COMMUNITIES_DEFAULT_LIMIT = 20;
1930
1933
  var CODE_COMMUNITIES_MAX_LIMIT = 100;
@@ -2548,6 +2551,16 @@ function compactEvidenceValue(value, maxChars) {
2548
2551
  }
2549
2552
  var MAX_EXACT_SEARCH_HANDOFF_NAMES = 3;
2550
2553
  var MAX_EXACT_SEARCH_HANDOFF_NAME_CHARS = 64;
2554
+ function toContextPackTraceCandidate(result) {
2555
+ return {
2556
+ filePath: result.filePath,
2557
+ startLine: result.startLine,
2558
+ endLine: result.endLine,
2559
+ score: result.score,
2560
+ chunkType: result.chunkType,
2561
+ name: result.name
2562
+ };
2563
+ }
2551
2564
  function formatExactSearchHandoff(results) {
2552
2565
  const suggestedNames = [];
2553
2566
  const seen = /* @__PURE__ */ new Set();
@@ -2596,13 +2609,13 @@ function buildContextPack(results, options = {}) {
2596
2609
  const maxResults = Math.max(0, Math.floor(options.maxResults ?? results.length));
2597
2610
  const includeExactSearchHandoff = options.includeExactSearchHandoff ?? false;
2598
2611
  const candidateCount = results.length;
2599
- const deduplicated = deduplicateContextCandidates(
2600
- rankContextCandidates(
2601
- results,
2602
- options.preferImplementationPaths ?? false
2603
- )
2604
- );
2605
- const diversified = diversifyContextCandidates(deduplicated);
2612
+ const preserveInputOrder = options.preserveInputOrder ?? false;
2613
+ const ranked = preserveInputOrder ? results.map((result, originalIndex) => ({ result, originalIndex })) : rankContextCandidates(results, options.preferImplementationPaths ?? false);
2614
+ const rankedCandidates = ranked.map((entry) => toContextPackTraceCandidate(entry.result));
2615
+ const deduplicated = deduplicateContextCandidates(ranked);
2616
+ const deduplicatedCandidates = deduplicated.map((result) => toContextPackTraceCandidate(result));
2617
+ const diversified = preserveInputOrder ? deduplicated : diversifyContextCandidates(deduplicated);
2618
+ const diversifiedCandidates = diversified.map((result) => toContextPackTraceCandidate(result));
2606
2619
  const duplicateCount = candidateCount - deduplicated.length;
2607
2620
  const selectable = diversified.slice(0, maxResults);
2608
2621
  const limitOmittedCount = deduplicated.length - selectable.length;
@@ -2635,6 +2648,15 @@ function buildContextPack(results, options = {}) {
2635
2648
  const fitted = fitTextToContextBudget(text, tokenBudget);
2636
2649
  const budgetOmittedCount = selectable.length - selected.length;
2637
2650
  const omittedCount = candidateCount - selected.length;
2651
+ if (options.trace) {
2652
+ options.trace({
2653
+ inputCandidates: results.map(toContextPackTraceCandidate),
2654
+ rankedCandidates,
2655
+ deduplicatedCandidates,
2656
+ diversifiedCandidates,
2657
+ selectedCandidates: selected.map(toContextPackTraceCandidate)
2658
+ });
2659
+ }
2638
2660
  return {
2639
2661
  requestedTokenBudget,
2640
2662
  tokenBudget,
@@ -8613,12 +8635,12 @@ function diversifyGroupBySymbol(entries, getCandidate) {
8613
8635
  return [...primary, ...remainder];
8614
8636
  }
8615
8637
  function buildDiversityKey(metadata) {
8616
- const normalizedPath = metadata.filePath.toLowerCase();
8638
+ const normalizedPath2 = metadata.filePath.toLowerCase();
8617
8639
  const normalizedName = (metadata.name ?? "").trim().toLowerCase();
8618
8640
  if (normalizedName.length > 0) {
8619
- return `${normalizedPath}#${normalizedName}`;
8641
+ return `${normalizedPath2}#${normalizedName}`;
8620
8642
  }
8621
- return normalizedPath;
8643
+ return normalizedPath2;
8622
8644
  }
8623
8645
  function rankHybridResults(query, semanticResults, keywordResults, options) {
8624
8646
  const prioritizeSourcePaths = options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source";
@@ -9067,9 +9089,9 @@ function normalizeFilePathForHintMatch(filePath) {
9067
9089
  return filePath.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
9068
9090
  }
9069
9091
  function pathMatchesHint(filePath, hint) {
9070
- const normalizedPath = normalizeFilePathForHintMatch(filePath);
9092
+ const normalizedPath2 = normalizeFilePathForHintMatch(filePath);
9071
9093
  const normalizedHint = normalizeFilePathForHintMatch(hint);
9072
- return normalizedPath.endsWith(normalizedHint) || normalizedPath.includes(`/${normalizedHint}`) || normalizedPath.includes(normalizedHint);
9094
+ return normalizedPath2.endsWith(normalizedHint) || normalizedPath2.includes(`/${normalizedHint}`) || normalizedPath2.includes(normalizedHint);
9073
9095
  }
9074
9096
  function extractFilePathHint(query) {
9075
9097
  const match = query.match(FILE_PATH_HINT_SUFFIX_REGEX);
@@ -12942,6 +12964,20 @@ var Indexer = class _Indexer {
12942
12964
  requestedLimit = nextLimit;
12943
12965
  }
12944
12966
  }
12967
+ buildCandidateSnapshot(candidate) {
12968
+ return {
12969
+ id: candidate.id,
12970
+ filePath: candidate.metadata.filePath,
12971
+ startLine: candidate.metadata.startLine,
12972
+ endLine: candidate.metadata.endLine,
12973
+ score: candidate.score,
12974
+ chunkType: candidate.metadata.chunkType,
12975
+ name: candidate.metadata.name
12976
+ };
12977
+ }
12978
+ buildCandidateSnapshotList(candidates) {
12979
+ return candidates.map((candidate) => this.buildCandidateSnapshot(candidate));
12980
+ }
12945
12981
  searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
12946
12982
  return this.searchCandidatesWithBranchPrefilter(
12947
12983
  initialLimit,
@@ -13133,6 +13169,16 @@ var Indexer = class _Indexer {
13133
13169
  prefilterMs: Math.round(prefilterMs * 100) / 100,
13134
13170
  fusionMs: Math.round(fusionMs * 100) / 100
13135
13171
  });
13172
+ if (options?.trace) {
13173
+ options.trace({
13174
+ semanticCandidates: this.buildCandidateSnapshotList(scopedSemanticCandidates),
13175
+ keywordCandidates: this.buildCandidateSnapshotList(scopedKeywordCandidates),
13176
+ hybridCandidates: this.buildCandidateSnapshotList(combined),
13177
+ postExternalRerankCandidates: this.buildCandidateSnapshotList(rerankedCombined),
13178
+ tieredCandidates: this.buildCandidateSnapshotList(tiered),
13179
+ finalCandidates: this.buildCandidateSnapshotList(finalResults)
13180
+ });
13181
+ }
13136
13182
  const metadataOnly = options?.metadataOnly ?? false;
13137
13183
  return Promise.all(
13138
13184
  finalResults.map(async (r) => {
@@ -14400,7 +14446,8 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
14400
14446
  definitionIntent: options.definitionIntent,
14401
14447
  blameAuthor: options.blameAuthor,
14402
14448
  blameSha: options.blameSha,
14403
- blameSince: options.blameSince
14449
+ blameSince: options.blameSince,
14450
+ trace: options.trace
14404
14451
  });
14405
14452
  }
14406
14453
  async function searchCodebaseWithEffectiveness(projectRoot, host, route, query, options, render) {
@@ -14454,15 +14501,19 @@ async function implementationLookup(projectRoot, host, query, options = {}) {
14454
14501
  return indexer.search(query, options.limit, {
14455
14502
  fileType: options.fileType,
14456
14503
  directory: options.directory,
14457
- definitionIntent: true
14504
+ definitionIntent: true,
14505
+ trace: options.trace
14458
14506
  });
14459
14507
  }
14460
14508
  async function getCallGraphData(projectRoot, host, params) {
14461
14509
  await ensureAutoIndexReadyForRetrieval(projectRoot, host);
14462
14510
  const root = getProjectRoot(projectRoot, host);
14463
14511
  const indexer = getIndexerForProject(root, host);
14512
+ return getCallGraphDataForIndexer(indexer, root, params);
14513
+ }
14514
+ async function getCallGraphDataForIndexer(indexer, projectRoot, params) {
14464
14515
  const symbols = await indexer.getCallGraphSymbols();
14465
- const resolution = resolveCallGraphSymbol(symbols, root, params.name, params.filePath, params.symbolId);
14516
+ const resolution = resolveCallGraphSymbol(symbols, projectRoot, params.name, params.filePath, params.symbolId);
14466
14517
  const direction = params.direction === "callees" ? "callees" : "callers";
14467
14518
  if (resolution.status !== "resolved") {
14468
14519
  return { direction, resolution, callers: [], callees: [], relationshipType: params.relationshipType };
@@ -14678,17 +14729,17 @@ async function getIndexLogs(projectRoot, host, args) {
14678
14729
  function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
14679
14730
  const root = getProjectRoot(projectRoot, host);
14680
14731
  const inputPath = knowledgeBasePath.trim();
14681
- const normalizedPath = path20.resolve(
14732
+ const normalizedPath2 = path20.resolve(
14682
14733
  path20.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
14683
14734
  );
14684
- if (!(0, import_fs13.existsSync)(normalizedPath)) {
14685
- return `Error: Directory does not exist: ${normalizedPath}`;
14735
+ if (!(0, import_fs13.existsSync)(normalizedPath2)) {
14736
+ return `Error: Directory does not exist: ${normalizedPath2}`;
14686
14737
  }
14687
14738
  let realPath;
14688
14739
  try {
14689
- realPath = (0, import_fs13.realpathSync)(normalizedPath);
14740
+ realPath = (0, import_fs13.realpathSync)(normalizedPath2);
14690
14741
  } catch {
14691
- return `Error: Cannot resolve path: ${normalizedPath}`;
14742
+ return `Error: Cannot resolve path: ${normalizedPath2}`;
14692
14743
  }
14693
14744
  const blockedPrefixes = [
14694
14745
  "/etc",
@@ -14711,34 +14762,34 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
14711
14762
  ];
14712
14763
  for (const prefix of blockedPrefixes) {
14713
14764
  if (realPath === prefix || realPath.startsWith(`${prefix}/`)) {
14714
- return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
14765
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath2}`;
14715
14766
  }
14716
14767
  }
14717
14768
  for (const dotDir of sensitiveDotDirs) {
14718
14769
  const sensitiveDir = path20.join(homeDir, dotDir);
14719
14770
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
14720
- return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
14771
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath2}`;
14721
14772
  }
14722
14773
  }
14723
14774
  try {
14724
- const stat4 = (0, import_fs13.statSync)(normalizedPath);
14775
+ const stat4 = (0, import_fs13.statSync)(normalizedPath2);
14725
14776
  if (!stat4.isDirectory()) {
14726
- return `Error: Path is not a directory: ${normalizedPath}`;
14777
+ return `Error: Path is not a directory: ${normalizedPath2}`;
14727
14778
  }
14728
14779
  } catch (error) {
14729
- return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
14780
+ return `Error: Cannot access directory: ${normalizedPath2} - ${error instanceof Error ? error.message : String(error)}`;
14730
14781
  }
14731
14782
  const config = loadEditableConfig(root, host);
14732
14783
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
14733
- const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, root);
14784
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath2, root);
14734
14785
  if (alreadyExists) {
14735
- return `Knowledge base already configured: ${normalizedPath}`;
14786
+ return `Knowledge base already configured: ${normalizedPath2}`;
14736
14787
  }
14737
- knowledgeBases.push(normalizedPath);
14788
+ knowledgeBases.push(normalizedPath2);
14738
14789
  config.knowledgeBases = knowledgeBases;
14739
14790
  saveConfig(root, config, host);
14740
14791
  refreshIndexerForDirectory(root, host);
14741
- let result = `${normalizedPath}
14792
+ let result = `${normalizedPath2}
14742
14793
  `;
14743
14794
  result += `Total knowledge bases: ${knowledgeBases.length}
14744
14795
  `;
@@ -17051,6 +17102,181 @@ var pr_impact = tool({
17051
17102
  }
17052
17103
  });
17053
17104
 
17105
+ // src/tools/edit-context.ts
17106
+ function edgeLimit(value) {
17107
+ if (value === null || value === void 0 || !Number.isFinite(value)) {
17108
+ return DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT;
17109
+ }
17110
+ return Math.min(
17111
+ MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT,
17112
+ Math.max(MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT, Math.floor(value))
17113
+ );
17114
+ }
17115
+ function normalizedPath(value) {
17116
+ return value.replaceAll("\\", "/").replace(/^\.\//, "");
17117
+ }
17118
+ function pathsMatch(left, right) {
17119
+ const normalizedLeft = normalizedPath(left);
17120
+ const normalizedRight = normalizedPath(right);
17121
+ return normalizedLeft === normalizedRight || normalizedLeft.endsWith(`/${normalizedRight}`) || normalizedRight.endsWith(`/${normalizedLeft}`);
17122
+ }
17123
+ function targetSource(results, resolution) {
17124
+ return results.find((result) => pathsMatch(result.filePath, resolution.filePath) && result.startLine <= resolution.startLine && result.endLine >= resolution.startLine) ?? results.find((result) => pathsMatch(result.filePath, resolution.filePath) && result.name === resolution.name);
17125
+ }
17126
+ function formatSource(result) {
17127
+ const name = result.name ? ` ${result.name}` : "";
17128
+ return [
17129
+ "## Target implementation",
17130
+ `${result.filePath}:${result.startLine}-${result.endLine} (${result.chunkType}${name})`,
17131
+ "```",
17132
+ result.content,
17133
+ "```"
17134
+ ].join("\n");
17135
+ }
17136
+ function formatCallers(edges) {
17137
+ if (edges.length === 0) return "## Direct callers\nNone found.";
17138
+ return [
17139
+ "## Direct callers",
17140
+ ...edges.map((edge) => `- ${edge.fromSymbolName ?? "<unknown>"} at ${edge.fromSymbolFilePath ?? "<unknown file>"}:${edge.line} (${edge.callType}, ${edge.isResolved ? "resolved" : "unresolved"})`)
17141
+ ].join("\n");
17142
+ }
17143
+ function formatCallees(edges, sourceFilePath) {
17144
+ if (edges.length === 0) return "## Direct callees\nNone found.";
17145
+ return [
17146
+ "## Direct callees",
17147
+ ...edges.map((edge) => `- ${edge.targetName} from ${sourceFilePath}:${edge.line} (${edge.callType}, ${edge.isResolved ? "resolved" : "unresolved"})`)
17148
+ ].join("\n");
17149
+ }
17150
+ function formatResolutionRisk(resolution) {
17151
+ if (resolution.status === "ambiguous") {
17152
+ const candidates = resolution.candidates.map((candidate) => `${candidate.filePath}:${candidate.startLine}`).join(", ");
17153
+ return `Risk: symbol "${resolution.name}" is ambiguous. Pass filePath to select a target.${candidates ? ` Candidates: ${candidates}.` : ""}`;
17154
+ }
17155
+ if (resolution.filePath && resolution.totalCandidates > 0) {
17156
+ return `Risk: symbol "${resolution.name}" did not resolve at filePath="${resolution.filePath}". Review the conceptual evidence before editing.`;
17157
+ }
17158
+ return `Risk: symbol "${resolution.name}" could not be resolved. Review the conceptual evidence before editing.`;
17159
+ }
17160
+ async function fallbackPack(dependencies, query, risk, resolution, tokenBudget, candidateSource = []) {
17161
+ const conceptual = await dependencies.searchCodebase(query, { limit: 5 });
17162
+ const pack = buildContextPack([...candidateSource, ...conceptual], {
17163
+ tokenBudget: tokenBudget ?? void 0,
17164
+ heading: "## Conceptual evidence",
17165
+ maxResults: 5,
17166
+ includeExactSearchHandoff: false,
17167
+ preferImplementationPaths: true
17168
+ });
17169
+ const fitted = fitTextToContextBudget(`${risk}
17170
+
17171
+ ${pack.text}`, tokenBudget ?? void 0);
17172
+ return {
17173
+ text: fitted.text,
17174
+ details: {
17175
+ resolution,
17176
+ tokenBudget: fitted.tokenBudget,
17177
+ tokenEstimate: fitted.tokenEstimate,
17178
+ truncated: fitted.truncated,
17179
+ sourceIncluded: candidateSource.length > 0,
17180
+ callerCount: 0,
17181
+ calleeCount: 0
17182
+ }
17183
+ };
17184
+ }
17185
+ async function resolveCodebaseEditContextWithDependencies(input, dependencies) {
17186
+ const symbol = input.symbol?.trim();
17187
+ if (!symbol) {
17188
+ return fallbackPack(
17189
+ dependencies,
17190
+ input.query,
17191
+ "Risk: no authoritative symbol was supplied. Review the conceptual evidence before editing.",
17192
+ "not_requested",
17193
+ input.tokenBudget
17194
+ );
17195
+ }
17196
+ let callersResult;
17197
+ try {
17198
+ callersResult = await dependencies.getCallGraphData({
17199
+ name: symbol,
17200
+ filePath: input.filePath ?? void 0,
17201
+ direction: "callers"
17202
+ });
17203
+ } catch (error) {
17204
+ const candidates = await dependencies.implementationLookup(symbol, { limit: 5 });
17205
+ const message = error instanceof Error ? error.message : String(error);
17206
+ return fallbackPack(
17207
+ dependencies,
17208
+ input.query,
17209
+ `Risk: graph data is unavailable (${message}). Target and dependencies are not graph-verified.`,
17210
+ "graph_unavailable",
17211
+ input.tokenBudget,
17212
+ candidates
17213
+ );
17214
+ }
17215
+ if (callersResult.resolution.status !== "resolved") {
17216
+ return fallbackPack(
17217
+ dependencies,
17218
+ input.query,
17219
+ formatResolutionRisk(callersResult.resolution),
17220
+ callersResult.resolution.status,
17221
+ input.tokenBudget
17222
+ );
17223
+ }
17224
+ const resolution = callersResult.resolution;
17225
+ const [definitionsResult, calleesResult] = await Promise.allSettled([
17226
+ dependencies.implementationLookup(symbol, { limit: 10 }),
17227
+ dependencies.getCallGraphData({
17228
+ name: symbol,
17229
+ filePath: input.filePath ?? resolution.filePath,
17230
+ direction: "callees"
17231
+ })
17232
+ ]);
17233
+ if (definitionsResult.status === "rejected") throw definitionsResult.reason;
17234
+ let graphRisk;
17235
+ let callees = [];
17236
+ if (calleesResult.status === "rejected") {
17237
+ const message = calleesResult.reason instanceof Error ? calleesResult.reason.message : String(calleesResult.reason);
17238
+ graphRisk = `Risk: callee graph data is unavailable (${message}). Dependency evidence is incomplete.`;
17239
+ } else if (calleesResult.value.resolution.status !== "resolved") {
17240
+ graphRisk = "Risk: the target resolved for callers but not callees. Dependency evidence is incomplete.";
17241
+ } else {
17242
+ callees = calleesResult.value.callees.slice(0, edgeLimit(input.calleeLimit));
17243
+ }
17244
+ const source = targetSource(definitionsResult.value, resolution);
17245
+ const callers = callersResult.callers.slice(0, edgeLimit(input.callerLimit));
17246
+ const sourceBudget = Math.max(
17247
+ MIN_CONTEXT_PACK_TOKEN_BUDGET,
17248
+ Math.floor((input.tokenBudget ?? DEFAULT_CONTEXT_PACK_TOKEN_BUDGET) * 0.6)
17249
+ );
17250
+ const sourceText = source ? fitTextToContextBudget(formatSource(source), sourceBudget).text : `## Target implementation
17251
+ Risk: no implementation source matched the resolved target at ${resolution.filePath}:${resolution.startLine}.`;
17252
+ const fitted = fitTextToContextBudget([
17253
+ `# Pre-edit context for ${resolution.name}`,
17254
+ graphRisk,
17255
+ sourceText,
17256
+ formatCallers(callers),
17257
+ formatCallees(callees, resolution.filePath)
17258
+ ].filter((section) => section !== void 0).join("\n\n"), input.tokenBudget ?? void 0);
17259
+ return {
17260
+ text: fitted.text,
17261
+ details: {
17262
+ resolution: "resolved",
17263
+ tokenBudget: fitted.tokenBudget,
17264
+ tokenEstimate: fitted.tokenEstimate,
17265
+ truncated: fitted.truncated,
17266
+ sourceIncluded: source !== void 0,
17267
+ callerCount: callers.length,
17268
+ calleeCount: callees.length
17269
+ }
17270
+ };
17271
+ }
17272
+ async function resolveCodebaseEditContext(projectRoot, host, input) {
17273
+ return resolveCodebaseEditContextWithDependencies(input, {
17274
+ searchCodebase: (query, options) => searchCodebase(projectRoot, host, query, options),
17275
+ implementationLookup: (query, options) => implementationLookup(projectRoot, host, query, options),
17276
+ getCallGraphData: (params) => getCallGraphData(projectRoot, host, params)
17277
+ });
17278
+ }
17279
+
17054
17280
  // src/tools/context-search.ts
17055
17281
  var MIN_CONTEXT_RESULT_LIMIT = 1;
17056
17282
  var MAX_CONTEXT_RESULT_LIMIT = 100;
@@ -17153,6 +17379,27 @@ function buildRecoveryDetails(attempts, successIndex) {
17153
17379
  successfulAttemptIndex: successIndex
17154
17380
  };
17155
17381
  }
17382
+ function serializeAttempts(attempts) {
17383
+ return attempts.map((attempt) => ({
17384
+ kind: attempt.kind,
17385
+ scope: attempt.scope,
17386
+ resultCount: attempt.resultCount,
17387
+ relaxedFields: attempt.relaxedFields
17388
+ }));
17389
+ }
17390
+ function buildSearchDiagnostic(attempt) {
17391
+ if (!attempt) {
17392
+ return void 0;
17393
+ }
17394
+ return {
17395
+ route: attempt.kind,
17396
+ routedQuery: attempt.query,
17397
+ searchQuery: attempt.query,
17398
+ searchScope: attempt.scopeFilter,
17399
+ searchTrace: attempt.searchTrace,
17400
+ contextPackTrace: attempt.contextPackTrace
17401
+ };
17402
+ }
17156
17403
  function trimOrUndefined2(value) {
17157
17404
  const normalized = value?.trim();
17158
17405
  if (!normalized) {
@@ -17192,6 +17439,7 @@ async function resolveSearchContext(input, operations) {
17192
17439
  const hasFilters = Boolean(fileType || directory);
17193
17440
  const relaxedFields = relaxedHintFields(fileType, directory);
17194
17441
  const attempts = [];
17442
+ const attemptStates = [];
17195
17443
  const decisions = {
17196
17444
  inferredDefinitionMiss: false,
17197
17445
  fallbackFromOriginalConceptualToInferred: false,
@@ -17220,8 +17468,20 @@ async function resolveSearchContext(input, operations) {
17220
17468
  if (seenAttempts.has(key)) {
17221
17469
  return [];
17222
17470
  }
17223
- const results = await runAttempt();
17471
+ const attemptState = {
17472
+ kind,
17473
+ scope: describeScope(scope.fileType, scope.directory),
17474
+ resultCount: 0,
17475
+ relaxedFields: [...relaxedFieldsForAttempt],
17476
+ query: attemptQuery,
17477
+ scopeFilter: scope
17478
+ };
17479
+ const results = await runAttempt((trace) => {
17480
+ attemptState.searchTrace = trace;
17481
+ });
17482
+ attemptState.resultCount = results.length;
17224
17483
  seenAttempts.add(key);
17484
+ attemptStates.push(attemptState);
17225
17485
  attempts.push({
17226
17486
  kind,
17227
17487
  scope: describeScope(scope.fileType, scope.directory),
@@ -17241,7 +17501,7 @@ async function resolveSearchContext(input, operations) {
17241
17501
  symbol,
17242
17502
  scope,
17243
17503
  relaxedFieldsForAttempt,
17244
- () => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope)
17504
+ (trace) => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
17245
17505
  );
17246
17506
  };
17247
17507
  const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt) => {
@@ -17250,13 +17510,23 @@ async function resolveSearchContext(input, operations) {
17250
17510
  searchQuery,
17251
17511
  scope,
17252
17512
  relaxedFieldsForAttempt,
17253
- () => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope)
17513
+ (trace) => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
17254
17514
  );
17255
17515
  };
17256
- const toResult = (route, routedQuery, pack) => {
17516
+ const findSuccessfulAttemptState = (route) => {
17517
+ for (let index = attemptStates.length - 1; index >= 0; index -= 1) {
17518
+ const attempt = attemptStates[index];
17519
+ if (attempt.kind === route && attempt.resultCount > 0) {
17520
+ return attempt;
17521
+ }
17522
+ }
17523
+ return void 0;
17524
+ };
17525
+ const toResult = (route, routedQuery, pack, successfulAttempt) => {
17257
17526
  const base = packedResult(route, routedQuery, pack);
17258
17527
  const baseDetails = base.details;
17259
17528
  const successIndex = findSuccessfulAttemptIndex(route, attempts);
17529
+ const successState = successfulAttempt ?? findSuccessfulAttemptState(route);
17260
17530
  return {
17261
17531
  text: base.text,
17262
17532
  details: {
@@ -17264,7 +17534,10 @@ async function resolveSearchContext(input, operations) {
17264
17534
  tokenBudget: baseDetails.tokenBudget,
17265
17535
  tokenEstimate: baseDetails.tokenEstimate,
17266
17536
  truncated: false,
17267
- recovery: buildRecoveryDetails(attempts, successIndex)
17537
+ recovery: buildRecoveryDetails(serializeAttempts(attemptStates), successIndex),
17538
+ ...input.diagnostic && {
17539
+ diagnostic: buildSearchDiagnostic(successState)
17540
+ }
17268
17541
  }
17269
17542
  };
17270
17543
  };
@@ -17278,8 +17551,18 @@ async function resolveSearchContext(input, operations) {
17278
17551
  buildContextPack(scopedDefinitionResults, {
17279
17552
  tokenBudget,
17280
17553
  maxResults: limit,
17281
- heading
17282
- })
17554
+ heading,
17555
+ preserveInputOrder: true,
17556
+ ...input.diagnostic ? {
17557
+ trace: (trace) => {
17558
+ const attemptState = findSuccessfulAttemptState("definition");
17559
+ if (attemptState) {
17560
+ attemptState.contextPackTrace = trace;
17561
+ }
17562
+ }
17563
+ } : void 0
17564
+ }),
17565
+ findSuccessfulAttemptState("definition")
17283
17566
  );
17284
17567
  }
17285
17568
  if (explicitSymbol) {
@@ -17297,8 +17580,18 @@ async function resolveSearchContext(input, operations) {
17297
17580
  buildContextPack(unscopedDefinitionResults, {
17298
17581
  tokenBudget,
17299
17582
  maxResults: limit,
17300
- heading: heading2
17301
- })
17583
+ heading: heading2,
17584
+ preserveInputOrder: true,
17585
+ ...input.diagnostic ? {
17586
+ trace: (trace) => {
17587
+ const attemptState = findSuccessfulAttemptState("definition");
17588
+ if (attemptState) {
17589
+ attemptState.contextPackTrace = trace;
17590
+ }
17591
+ }
17592
+ } : void 0
17593
+ }),
17594
+ findSuccessfulAttemptState("definition")
17302
17595
  );
17303
17596
  }
17304
17597
  }
@@ -17317,7 +17610,10 @@ Explicit symbol lookup only; conceptual search was not attempted.`
17317
17610
  tokenBudget: heading.tokenBudget,
17318
17611
  tokenEstimate: heading.tokenEstimate,
17319
17612
  truncated: heading.truncated,
17320
- recovery: buildRecoveryDetails(attempts, null)
17613
+ recovery: buildRecoveryDetails(serializeAttempts(attemptStates), null),
17614
+ ...input.diagnostic && {
17615
+ diagnostic: buildSearchDiagnostic(attemptStates[attemptStates.length - 1])
17616
+ }
17321
17617
  }
17322
17618
  };
17323
17619
  }
@@ -17357,8 +17653,17 @@ Explicit symbol lookup only; conceptual search was not attempted.`
17357
17653
  maxResults: limit,
17358
17654
  heading,
17359
17655
  includeExactSearchHandoff: true,
17360
- preferImplementationPaths: intent.preferSourcePaths || intent.primary !== "docs" && intent.primary !== "test"
17361
- })
17656
+ preferImplementationPaths: intent.preferSourcePaths || intent.primary !== "docs" && intent.primary !== "test",
17657
+ ...input.diagnostic ? {
17658
+ trace: (trace) => {
17659
+ const attemptState = findSuccessfulAttemptState("conceptual");
17660
+ if (attemptState) {
17661
+ attemptState.contextPackTrace = trace;
17662
+ }
17663
+ }
17664
+ } : void 0
17665
+ }),
17666
+ findSuccessfulAttemptState("conceptual")
17362
17667
  );
17363
17668
  }
17364
17669
  }
@@ -17374,7 +17679,10 @@ Explicit symbol lookup only; conceptual search was not attempted.`
17374
17679
  tokenBudget: fallbackText.tokenBudget,
17375
17680
  tokenEstimate: fallbackText.tokenEstimate,
17376
17681
  truncated: fallbackText.truncated,
17377
- recovery: buildRecoveryDetails(attempts, null)
17682
+ recovery: buildRecoveryDetails(serializeAttempts(attemptStates), null),
17683
+ ...input.diagnostic && {
17684
+ diagnostic: buildSearchDiagnostic(attemptStates[attemptStates.length - 1])
17685
+ }
17378
17686
  }
17379
17687
  };
17380
17688
  }
@@ -17455,17 +17763,27 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
17455
17763
  details: fittedDetails("path", fitted, 0)
17456
17764
  };
17457
17765
  }
17458
- return resolveSearchContext({ query: input.query, symbol, limit, tokenBudget, fileType, directory }, {
17459
- lookup: (lookupSymbol, retrievalLimit, scope) => implementationLookup(projectRoot, host, lookupSymbol, {
17766
+ return resolveSearchContext({
17767
+ query: input.query,
17768
+ symbol,
17769
+ limit,
17770
+ tokenBudget,
17771
+ fileType,
17772
+ directory,
17773
+ diagnostic: input.diagnostic
17774
+ }, {
17775
+ lookup: (lookupSymbol, retrievalLimit, scope, trace) => implementationLookup(projectRoot, host, lookupSymbol, {
17460
17776
  limit: retrievalLimit,
17461
17777
  fileType: scope.fileType,
17462
- directory: scope.directory
17778
+ directory: scope.directory,
17779
+ trace
17463
17780
  }),
17464
- search: (queryText, retrievalLimit, scope) => searchCodebase(projectRoot, host, queryText, {
17781
+ search: (queryText, retrievalLimit, scope, trace) => searchCodebase(projectRoot, host, queryText, {
17465
17782
  limit: retrievalLimit,
17466
17783
  fileType: scope.fileType,
17467
17784
  directory: scope.directory,
17468
- metadataOnly: true
17785
+ metadataOnly: true,
17786
+ trace
17469
17787
  })
17470
17788
  });
17471
17789
  }
@@ -17532,7 +17850,11 @@ async function resolveCodebaseContext(projectRoot, host, input) {
17532
17850
 
17533
17851
  // src/tools/execute-common.ts
17534
17852
  async function executeCodebaseContext(projectRoot, host, args) {
17535
- return { text: (await resolveCodebaseContext(projectRoot, host, args)).text };
17853
+ const result = await resolveCodebaseContext(projectRoot, host, args);
17854
+ return { text: result.text, details: result.details };
17855
+ }
17856
+ async function executeCodebaseEditContext(projectRoot, host, args) {
17857
+ return { text: (await resolveCodebaseEditContext(projectRoot, host, args)).text };
17536
17858
  }
17537
17859
  async function executeIndexCodebase(projectRoot, host, args, onProgress) {
17538
17860
  const result = await runIndexCodebase(projectRoot, host, args, onProgress);
@@ -17635,9 +17957,9 @@ function parseGitActivity(output) {
17635
17957
  if (!filePath || addedRaw === "-" || deletedRaw === "-") continue;
17636
17958
  const churn = Number(addedRaw) + Number(deletedRaw);
17637
17959
  if (!Number.isFinite(churn) || churn <= 0) continue;
17638
- const normalizedPath = normalizePath3(filePath);
17639
- const previous = activity.get(normalizedPath);
17640
- activity.set(normalizedPath, {
17960
+ const normalizedPath2 = normalizePath3(filePath);
17961
+ const previous = activity.get(normalizedPath2);
17962
+ activity.set(normalizedPath2, {
17641
17963
  churn: (previous?.churn ?? 0) + churn,
17642
17964
  commits: (previous?.commits ?? 0) + 1,
17643
17965
  latestDate: previous?.latestDate ?? latestDate,
@@ -18227,8 +18549,8 @@ function transformForVisualization(symbols, edges, options = {}) {
18227
18549
  const normalizedAbsoluteSuffix = `/${normalizedDirWithSlash}`;
18228
18550
  filteredSymbols = symbols.filter(
18229
18551
  (s) => {
18230
- const normalizedPath = s.filePath.replace(/\\/g, "/");
18231
- return normalizedPath === normalizedDir || normalizedPath.startsWith(normalizedDirWithSlash) || normalizedPath.endsWith(`/${normalizedDir}`) || normalizedPath.includes(normalizedAbsoluteSuffix);
18552
+ const normalizedPath2 = s.filePath.replace(/\\/g, "/");
18553
+ return normalizedPath2 === normalizedDir || normalizedPath2.startsWith(normalizedDirWithSlash) || normalizedPath2.endsWith(`/${normalizedDir}`) || normalizedPath2.includes(normalizedAbsoluteSuffix);
18232
18554
  }
18233
18555
  );
18234
18556
  }
@@ -18302,6 +18624,26 @@ var z3 = tool.schema;
18302
18624
  var DEFAULT_HOST = "opencode";
18303
18625
  var CHUNK_TYPE_VALUES = CHUNK_TYPES;
18304
18626
  var RELATIONSHIP_TYPE_VALUES = RELATIONSHIP_TYPES;
18627
+ function stableSortedDiagnosticValue(value) {
18628
+ if (Array.isArray(value)) {
18629
+ return value.map((item) => stableSortedDiagnosticValue(item));
18630
+ }
18631
+ if (value === null || typeof value !== "object") {
18632
+ return value;
18633
+ }
18634
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
18635
+ const sorted = {};
18636
+ for (const [key, item] of entries) {
18637
+ sorted[key] = stableSortedDiagnosticValue(item);
18638
+ }
18639
+ return sorted;
18640
+ }
18641
+ function formatCodebaseContextDiagnostic(details) {
18642
+ const sorted = stableSortedDiagnosticValue(details);
18643
+ return `
18644
+ Diagnostics:
18645
+ ${JSON.stringify(sorted, null, 2)}`;
18646
+ }
18305
18647
  function initializeTools2(projectRoot, config) {
18306
18648
  initializeTools(projectRoot, config, DEFAULT_HOST);
18307
18649
  }
@@ -18321,10 +18663,29 @@ var codebase_context = tool({
18321
18663
  maxDepth: z3.number().int().min(MIN_CONTEXT_PATH_DEPTH).max(MAX_CONTEXT_PATH_DEPTH).nullable().optional().default(10).describe(`Maximum call-path traversal depth (${MIN_CONTEXT_PATH_DEPTH}-${MAX_CONTEXT_PATH_DEPTH})`),
18322
18664
  fileType: z3.string().nullable().optional().describe("Filter by file extension"),
18323
18665
  directory: z3.string().nullable().optional().describe("Filter by directory path"),
18324
- tokenBudget: z3.number().int().min(MIN_CONTEXT_PACK_TOKEN_BUDGET).max(MAX_CONTEXT_PACK_TOKEN_BUDGET).nullable().optional().default(DEFAULT_CONTEXT_PACK_TOKEN_BUDGET).describe(`Maximum response tokens (${MIN_CONTEXT_PACK_TOKEN_BUDGET}-${MAX_CONTEXT_PACK_TOKEN_BUDGET})`)
18666
+ tokenBudget: z3.number().int().min(MIN_CONTEXT_PACK_TOKEN_BUDGET).max(MAX_CONTEXT_PACK_TOKEN_BUDGET).nullable().optional().default(DEFAULT_CONTEXT_PACK_TOKEN_BUDGET).describe(`Maximum response tokens (${MIN_CONTEXT_PACK_TOKEN_BUDGET}-${MAX_CONTEXT_PACK_TOKEN_BUDGET})`),
18667
+ diagnostic: z3.boolean().optional().describe("Collect diagnostic routing and search traces without changing normal text output.")
18668
+ },
18669
+ async execute(args, context) {
18670
+ const result = await executeCodebaseContext(context?.worktree, DEFAULT_HOST, args);
18671
+ if (!args.diagnostic || !result.details?.diagnostic) {
18672
+ return result.text;
18673
+ }
18674
+ return `${result.text}${formatCodebaseContextDiagnostic(result.details.diagnostic)}`;
18675
+ }
18676
+ });
18677
+ var codebase_edit_context = tool({
18678
+ description: "PRE-EDIT TOOL for a known or suspected symbol. Returns token-bounded target source, direct callers and callees, or a risk-marked conceptual fallback when the target cannot be resolved.",
18679
+ args: {
18680
+ query: z3.string().describe("The requested change or target behavior"),
18681
+ symbol: z3.string().nullable().optional().describe("Authoritative target symbol when known"),
18682
+ filePath: z3.string().nullable().optional().describe("Optional file path used to disambiguate duplicate symbol names"),
18683
+ callerLimit: z3.number().int().min(MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT).max(MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT).nullable().optional().default(DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT),
18684
+ calleeLimit: z3.number().int().min(MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT).max(MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT).nullable().optional().default(DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT),
18685
+ tokenBudget: z3.number().int().min(MIN_CONTEXT_PACK_TOKEN_BUDGET).max(MAX_CONTEXT_PACK_TOKEN_BUDGET).nullable().optional().default(DEFAULT_CONTEXT_PACK_TOKEN_BUDGET)
18325
18686
  },
18326
18687
  async execute(args, context) {
18327
- return (await executeCodebaseContext(context?.worktree, DEFAULT_HOST, args)).text;
18688
+ return (await executeCodebaseEditContext(context?.worktree, DEFAULT_HOST, args)).text;
18328
18689
  }
18329
18690
  });
18330
18691
  var codebase_peek = tool({
@@ -18581,6 +18942,7 @@ var index_visualize = tool({
18581
18942
  // src/tools/tool-names.ts
18582
18943
  var TOOL_NAME = {
18583
18944
  CODEBASE_CONTEXT: "codebase_context",
18945
+ CODEBASE_EDIT_CONTEXT: "codebase_edit_context",
18584
18946
  CODEBASE_SEARCH: "codebase_search",
18585
18947
  CODEBASE_PEEK: "codebase_peek",
18586
18948
  FIND_SIMILAR: "find_similar",
@@ -18604,6 +18966,7 @@ var TOOL_NAME = {
18604
18966
  };
18605
18967
  var PORTABLE_TOOL_NAMES = [
18606
18968
  TOOL_NAME.CODEBASE_CONTEXT,
18969
+ TOOL_NAME.CODEBASE_EDIT_CONTEXT,
18607
18970
  TOOL_NAME.CODEBASE_SEARCH,
18608
18971
  TOOL_NAME.CODEBASE_PEEK,
18609
18972
  TOOL_NAME.INDEX_CODEBASE,
@@ -18620,6 +18983,7 @@ var PORTABLE_TOOL_NAMES = [
18620
18983
  ];
18621
18984
  var OPENCODE_TOOL_NAMES = [
18622
18985
  TOOL_NAME.CODEBASE_CONTEXT,
18986
+ TOOL_NAME.CODEBASE_EDIT_CONTEXT,
18623
18987
  TOOL_NAME.CODEBASE_SEARCH,
18624
18988
  TOOL_NAME.CODEBASE_PEEK,
18625
18989
  TOOL_NAME.INDEX_CODEBASE,
@@ -18640,6 +19004,7 @@ var OPENCODE_TOOL_NAMES = [
18640
19004
  ];
18641
19005
  var PI_TOOL_NAMES = [
18642
19006
  TOOL_NAME.CODEBASE_CONTEXT,
19007
+ TOOL_NAME.CODEBASE_EDIT_CONTEXT,
18643
19008
  TOOL_NAME.CODEBASE_SEARCH,
18644
19009
  TOOL_NAME.CODEBASE_PEEK,
18645
19010
  TOOL_NAME.FIND_SIMILAR,
@@ -19094,6 +19459,7 @@ var plugin = async ({ directory, worktree }) => {
19094
19459
  return {
19095
19460
  tool: {
19096
19461
  [TOOL_NAME.CODEBASE_CONTEXT]: codebase_context,
19462
+ [TOOL_NAME.CODEBASE_EDIT_CONTEXT]: codebase_edit_context,
19097
19463
  [TOOL_NAME.CODEBASE_SEARCH]: codebase_search,
19098
19464
  [TOOL_NAME.CODEBASE_PEEK]: codebase_peek,
19099
19465
  [TOOL_NAME.INDEX_CODEBASE]: index_codebase,