open-codebase-index 0.22.2 → 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.
@@ -1,4 +1,4 @@
1
- // opencode-codebase-index - Semantic codebase search for OpenCode
1
+ // open-codebase-index - Semantic codebase indexing and search
2
2
  "use strict";
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
@@ -2053,6 +2053,9 @@ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
2053
2053
  }
2054
2054
 
2055
2055
  // src/tools/contracts.ts
2056
+ var MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT = 1;
2057
+ var MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT = 20;
2058
+ var DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT = 5;
2056
2059
  var CODE_COMMUNITIES_MIN_SIZE = 1;
2057
2060
  var CODE_COMMUNITIES_DEFAULT_LIMIT = 20;
2058
2061
  var CODE_COMMUNITIES_MAX_LIMIT = 100;
@@ -2676,6 +2679,16 @@ function compactEvidenceValue(value, maxChars) {
2676
2679
  }
2677
2680
  var MAX_EXACT_SEARCH_HANDOFF_NAMES = 3;
2678
2681
  var MAX_EXACT_SEARCH_HANDOFF_NAME_CHARS = 64;
2682
+ function toContextPackTraceCandidate(result) {
2683
+ return {
2684
+ filePath: result.filePath,
2685
+ startLine: result.startLine,
2686
+ endLine: result.endLine,
2687
+ score: result.score,
2688
+ chunkType: result.chunkType,
2689
+ name: result.name
2690
+ };
2691
+ }
2679
2692
  function formatExactSearchHandoff(results) {
2680
2693
  const suggestedNames = [];
2681
2694
  const seen = /* @__PURE__ */ new Set();
@@ -2724,13 +2737,13 @@ function buildContextPack(results, options = {}) {
2724
2737
  const maxResults = Math.max(0, Math.floor(options.maxResults ?? results.length));
2725
2738
  const includeExactSearchHandoff = options.includeExactSearchHandoff ?? false;
2726
2739
  const candidateCount = results.length;
2727
- const deduplicated = deduplicateContextCandidates(
2728
- rankContextCandidates(
2729
- results,
2730
- options.preferImplementationPaths ?? false
2731
- )
2732
- );
2733
- const diversified = diversifyContextCandidates(deduplicated);
2740
+ const preserveInputOrder = options.preserveInputOrder ?? false;
2741
+ const ranked = preserveInputOrder ? results.map((result, originalIndex) => ({ result, originalIndex })) : rankContextCandidates(results, options.preferImplementationPaths ?? false);
2742
+ const rankedCandidates = ranked.map((entry) => toContextPackTraceCandidate(entry.result));
2743
+ const deduplicated = deduplicateContextCandidates(ranked);
2744
+ const deduplicatedCandidates = deduplicated.map((result) => toContextPackTraceCandidate(result));
2745
+ const diversified = preserveInputOrder ? deduplicated : diversifyContextCandidates(deduplicated);
2746
+ const diversifiedCandidates = diversified.map((result) => toContextPackTraceCandidate(result));
2734
2747
  const duplicateCount = candidateCount - deduplicated.length;
2735
2748
  const selectable = diversified.slice(0, maxResults);
2736
2749
  const limitOmittedCount = deduplicated.length - selectable.length;
@@ -2763,6 +2776,15 @@ function buildContextPack(results, options = {}) {
2763
2776
  const fitted = fitTextToContextBudget(text3, tokenBudget);
2764
2777
  const budgetOmittedCount = selectable.length - selected.length;
2765
2778
  const omittedCount = candidateCount - selected.length;
2779
+ if (options.trace) {
2780
+ options.trace({
2781
+ inputCandidates: results.map(toContextPackTraceCandidate),
2782
+ rankedCandidates,
2783
+ deduplicatedCandidates,
2784
+ diversifiedCandidates,
2785
+ selectedCandidates: selected.map(toContextPackTraceCandidate)
2786
+ });
2787
+ }
2766
2788
  return {
2767
2789
  requestedTokenBudget,
2768
2790
  tokenBudget,
@@ -8578,12 +8600,12 @@ function diversifyGroupBySymbol(entries, getCandidate) {
8578
8600
  return [...primary, ...remainder];
8579
8601
  }
8580
8602
  function buildDiversityKey(metadata) {
8581
- const normalizedPath = metadata.filePath.toLowerCase();
8603
+ const normalizedPath2 = metadata.filePath.toLowerCase();
8582
8604
  const normalizedName = (metadata.name ?? "").trim().toLowerCase();
8583
8605
  if (normalizedName.length > 0) {
8584
- return `${normalizedPath}#${normalizedName}`;
8606
+ return `${normalizedPath2}#${normalizedName}`;
8585
8607
  }
8586
- return normalizedPath;
8608
+ return normalizedPath2;
8587
8609
  }
8588
8610
  function rankHybridResults(query, semanticResults, keywordResults, options) {
8589
8611
  const prioritizeSourcePaths = options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source";
@@ -9032,9 +9054,9 @@ function normalizeFilePathForHintMatch(filePath) {
9032
9054
  return filePath.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
9033
9055
  }
9034
9056
  function pathMatchesHint(filePath, hint) {
9035
- const normalizedPath = normalizeFilePathForHintMatch(filePath);
9057
+ const normalizedPath2 = normalizeFilePathForHintMatch(filePath);
9036
9058
  const normalizedHint = normalizeFilePathForHintMatch(hint);
9037
- return normalizedPath.endsWith(normalizedHint) || normalizedPath.includes(`/${normalizedHint}`) || normalizedPath.includes(normalizedHint);
9059
+ return normalizedPath2.endsWith(normalizedHint) || normalizedPath2.includes(`/${normalizedHint}`) || normalizedPath2.includes(normalizedHint);
9038
9060
  }
9039
9061
  function extractFilePathHint(query) {
9040
9062
  const match = query.match(FILE_PATH_HINT_SUFFIX_REGEX);
@@ -12907,6 +12929,20 @@ var Indexer = class _Indexer {
12907
12929
  requestedLimit = nextLimit;
12908
12930
  }
12909
12931
  }
12932
+ buildCandidateSnapshot(candidate) {
12933
+ return {
12934
+ id: candidate.id,
12935
+ filePath: candidate.metadata.filePath,
12936
+ startLine: candidate.metadata.startLine,
12937
+ endLine: candidate.metadata.endLine,
12938
+ score: candidate.score,
12939
+ chunkType: candidate.metadata.chunkType,
12940
+ name: candidate.metadata.name
12941
+ };
12942
+ }
12943
+ buildCandidateSnapshotList(candidates) {
12944
+ return candidates.map((candidate) => this.buildCandidateSnapshot(candidate));
12945
+ }
12910
12946
  searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
12911
12947
  return this.searchCandidatesWithBranchPrefilter(
12912
12948
  initialLimit,
@@ -13098,6 +13134,16 @@ var Indexer = class _Indexer {
13098
13134
  prefilterMs: Math.round(prefilterMs * 100) / 100,
13099
13135
  fusionMs: Math.round(fusionMs * 100) / 100
13100
13136
  });
13137
+ if (options?.trace) {
13138
+ options.trace({
13139
+ semanticCandidates: this.buildCandidateSnapshotList(scopedSemanticCandidates),
13140
+ keywordCandidates: this.buildCandidateSnapshotList(scopedKeywordCandidates),
13141
+ hybridCandidates: this.buildCandidateSnapshotList(combined),
13142
+ postExternalRerankCandidates: this.buildCandidateSnapshotList(rerankedCombined),
13143
+ tieredCandidates: this.buildCandidateSnapshotList(tiered),
13144
+ finalCandidates: this.buildCandidateSnapshotList(finalResults)
13145
+ });
13146
+ }
13101
13147
  const metadataOnly = options?.metadataOnly ?? false;
13102
13148
  return Promise.all(
13103
13149
  finalResults.map(async (r) => {
@@ -14358,7 +14404,8 @@ async function searchCodebase(projectRoot3, host, query, options = {}) {
14358
14404
  definitionIntent: options.definitionIntent,
14359
14405
  blameAuthor: options.blameAuthor,
14360
14406
  blameSha: options.blameSha,
14361
- blameSince: options.blameSince
14407
+ blameSince: options.blameSince,
14408
+ trace: options.trace
14362
14409
  });
14363
14410
  }
14364
14411
  async function searchCodebaseWithEffectiveness(projectRoot3, host, route, query, options, render) {
@@ -14412,15 +14459,19 @@ async function implementationLookup(projectRoot3, host, query, options = {}) {
14412
14459
  return indexer.search(query, options.limit, {
14413
14460
  fileType: options.fileType,
14414
14461
  directory: options.directory,
14415
- definitionIntent: true
14462
+ definitionIntent: true,
14463
+ trace: options.trace
14416
14464
  });
14417
14465
  }
14418
14466
  async function getCallGraphData(projectRoot3, host, params) {
14419
14467
  await ensureAutoIndexReadyForRetrieval(projectRoot3, host);
14420
14468
  const root = getProjectRoot(projectRoot3, host);
14421
14469
  const indexer = getIndexerForProject(root, host);
14470
+ return getCallGraphDataForIndexer(indexer, root, params);
14471
+ }
14472
+ async function getCallGraphDataForIndexer(indexer, projectRoot3, params) {
14422
14473
  const symbols = await indexer.getCallGraphSymbols();
14423
- const resolution = resolveCallGraphSymbol(symbols, root, params.name, params.filePath, params.symbolId);
14474
+ const resolution = resolveCallGraphSymbol(symbols, projectRoot3, params.name, params.filePath, params.symbolId);
14424
14475
  const direction = params.direction === "callees" ? "callees" : "callers";
14425
14476
  if (resolution.status !== "resolved") {
14426
14477
  return { direction, resolution, callers: [], callees: [], relationshipType: params.relationshipType };
@@ -14648,17 +14699,17 @@ async function getIndexLogs(projectRoot3, host, args) {
14648
14699
  function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
14649
14700
  const root = getProjectRoot(projectRoot3, host);
14650
14701
  const inputPath = knowledgeBasePath.trim();
14651
- const normalizedPath = path20.resolve(
14702
+ const normalizedPath2 = path20.resolve(
14652
14703
  path20.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
14653
14704
  );
14654
- if (!(0, import_fs13.existsSync)(normalizedPath)) {
14655
- return `Error: Directory does not exist: ${normalizedPath}`;
14705
+ if (!(0, import_fs13.existsSync)(normalizedPath2)) {
14706
+ return `Error: Directory does not exist: ${normalizedPath2}`;
14656
14707
  }
14657
14708
  let realPath;
14658
14709
  try {
14659
- realPath = (0, import_fs13.realpathSync)(normalizedPath);
14710
+ realPath = (0, import_fs13.realpathSync)(normalizedPath2);
14660
14711
  } catch {
14661
- return `Error: Cannot resolve path: ${normalizedPath}`;
14712
+ return `Error: Cannot resolve path: ${normalizedPath2}`;
14662
14713
  }
14663
14714
  const blockedPrefixes = [
14664
14715
  "/etc",
@@ -14681,34 +14732,34 @@ function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
14681
14732
  ];
14682
14733
  for (const prefix of blockedPrefixes) {
14683
14734
  if (realPath === prefix || realPath.startsWith(`${prefix}/`)) {
14684
- return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
14735
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath2}`;
14685
14736
  }
14686
14737
  }
14687
14738
  for (const dotDir of sensitiveDotDirs) {
14688
14739
  const sensitiveDir = path20.join(homeDir, dotDir);
14689
14740
  if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
14690
- return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
14741
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath2}`;
14691
14742
  }
14692
14743
  }
14693
14744
  try {
14694
- const stat = (0, import_fs13.statSync)(normalizedPath);
14745
+ const stat = (0, import_fs13.statSync)(normalizedPath2);
14695
14746
  if (!stat.isDirectory()) {
14696
- return `Error: Path is not a directory: ${normalizedPath}`;
14747
+ return `Error: Path is not a directory: ${normalizedPath2}`;
14697
14748
  }
14698
14749
  } catch (error) {
14699
- return `Error: Cannot access directory: ${normalizedPath} - ${error instanceof Error ? error.message : String(error)}`;
14750
+ return `Error: Cannot access directory: ${normalizedPath2} - ${error instanceof Error ? error.message : String(error)}`;
14700
14751
  }
14701
14752
  const config = loadEditableConfig(root, host);
14702
14753
  const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
14703
- const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath, root);
14754
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath2, root);
14704
14755
  if (alreadyExists) {
14705
- return `Knowledge base already configured: ${normalizedPath}`;
14756
+ return `Knowledge base already configured: ${normalizedPath2}`;
14706
14757
  }
14707
- knowledgeBases.push(normalizedPath);
14758
+ knowledgeBases.push(normalizedPath2);
14708
14759
  config.knowledgeBases = knowledgeBases;
14709
14760
  saveConfig(root, config, host);
14710
14761
  refreshIndexerForDirectory(root, host);
14711
- let result = `${normalizedPath}
14762
+ let result = `${normalizedPath2}
14712
14763
  `;
14713
14764
  result += `Total knowledge bases: ${knowledgeBases.length}
14714
14765
  `;
@@ -14883,6 +14934,27 @@ function buildRecoveryDetails(attempts, successIndex) {
14883
14934
  successfulAttemptIndex: successIndex
14884
14935
  };
14885
14936
  }
14937
+ function serializeAttempts(attempts) {
14938
+ return attempts.map((attempt) => ({
14939
+ kind: attempt.kind,
14940
+ scope: attempt.scope,
14941
+ resultCount: attempt.resultCount,
14942
+ relaxedFields: attempt.relaxedFields
14943
+ }));
14944
+ }
14945
+ function buildSearchDiagnostic(attempt) {
14946
+ if (!attempt) {
14947
+ return void 0;
14948
+ }
14949
+ return {
14950
+ route: attempt.kind,
14951
+ routedQuery: attempt.query,
14952
+ searchQuery: attempt.query,
14953
+ searchScope: attempt.scopeFilter,
14954
+ searchTrace: attempt.searchTrace,
14955
+ contextPackTrace: attempt.contextPackTrace
14956
+ };
14957
+ }
14886
14958
  function trimOrUndefined2(value) {
14887
14959
  const normalized = value?.trim();
14888
14960
  if (!normalized) {
@@ -14922,6 +14994,7 @@ async function resolveSearchContext(input, operations) {
14922
14994
  const hasFilters = Boolean(fileType || directory);
14923
14995
  const relaxedFields = relaxedHintFields(fileType, directory);
14924
14996
  const attempts = [];
14997
+ const attemptStates = [];
14925
14998
  const decisions = {
14926
14999
  inferredDefinitionMiss: false,
14927
15000
  fallbackFromOriginalConceptualToInferred: false,
@@ -14950,8 +15023,20 @@ async function resolveSearchContext(input, operations) {
14950
15023
  if (seenAttempts.has(key)) {
14951
15024
  return [];
14952
15025
  }
14953
- const results = await runAttempt();
15026
+ const attemptState = {
15027
+ kind,
15028
+ scope: describeScope(scope.fileType, scope.directory),
15029
+ resultCount: 0,
15030
+ relaxedFields: [...relaxedFieldsForAttempt],
15031
+ query: attemptQuery,
15032
+ scopeFilter: scope
15033
+ };
15034
+ const results = await runAttempt((trace) => {
15035
+ attemptState.searchTrace = trace;
15036
+ });
15037
+ attemptState.resultCount = results.length;
14954
15038
  seenAttempts.add(key);
15039
+ attemptStates.push(attemptState);
14955
15040
  attempts.push({
14956
15041
  kind,
14957
15042
  scope: describeScope(scope.fileType, scope.directory),
@@ -14971,7 +15056,7 @@ async function resolveSearchContext(input, operations) {
14971
15056
  symbol,
14972
15057
  scope,
14973
15058
  relaxedFieldsForAttempt,
14974
- () => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope)
15059
+ (trace) => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
14975
15060
  );
14976
15061
  };
14977
15062
  const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt) => {
@@ -14980,13 +15065,23 @@ async function resolveSearchContext(input, operations) {
14980
15065
  searchQuery,
14981
15066
  scope,
14982
15067
  relaxedFieldsForAttempt,
14983
- () => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope)
15068
+ (trace) => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
14984
15069
  );
14985
15070
  };
14986
- const toResult = (route, routedQuery, pack) => {
15071
+ const findSuccessfulAttemptState = (route) => {
15072
+ for (let index = attemptStates.length - 1; index >= 0; index -= 1) {
15073
+ const attempt = attemptStates[index];
15074
+ if (attempt.kind === route && attempt.resultCount > 0) {
15075
+ return attempt;
15076
+ }
15077
+ }
15078
+ return void 0;
15079
+ };
15080
+ const toResult = (route, routedQuery, pack, successfulAttempt) => {
14987
15081
  const base = packedResult(route, routedQuery, pack);
14988
15082
  const baseDetails = base.details;
14989
15083
  const successIndex = findSuccessfulAttemptIndex(route, attempts);
15084
+ const successState = successfulAttempt ?? findSuccessfulAttemptState(route);
14990
15085
  return {
14991
15086
  text: base.text,
14992
15087
  details: {
@@ -14994,7 +15089,10 @@ async function resolveSearchContext(input, operations) {
14994
15089
  tokenBudget: baseDetails.tokenBudget,
14995
15090
  tokenEstimate: baseDetails.tokenEstimate,
14996
15091
  truncated: false,
14997
- recovery: buildRecoveryDetails(attempts, successIndex)
15092
+ recovery: buildRecoveryDetails(serializeAttempts(attemptStates), successIndex),
15093
+ ...input.diagnostic && {
15094
+ diagnostic: buildSearchDiagnostic(successState)
15095
+ }
14998
15096
  }
14999
15097
  };
15000
15098
  };
@@ -15008,8 +15106,18 @@ async function resolveSearchContext(input, operations) {
15008
15106
  buildContextPack(scopedDefinitionResults, {
15009
15107
  tokenBudget,
15010
15108
  maxResults: limit,
15011
- heading
15012
- })
15109
+ heading,
15110
+ preserveInputOrder: true,
15111
+ ...input.diagnostic ? {
15112
+ trace: (trace) => {
15113
+ const attemptState = findSuccessfulAttemptState("definition");
15114
+ if (attemptState) {
15115
+ attemptState.contextPackTrace = trace;
15116
+ }
15117
+ }
15118
+ } : void 0
15119
+ }),
15120
+ findSuccessfulAttemptState("definition")
15013
15121
  );
15014
15122
  }
15015
15123
  if (explicitSymbol) {
@@ -15027,8 +15135,18 @@ async function resolveSearchContext(input, operations) {
15027
15135
  buildContextPack(unscopedDefinitionResults, {
15028
15136
  tokenBudget,
15029
15137
  maxResults: limit,
15030
- heading: heading2
15031
- })
15138
+ heading: heading2,
15139
+ preserveInputOrder: true,
15140
+ ...input.diagnostic ? {
15141
+ trace: (trace) => {
15142
+ const attemptState = findSuccessfulAttemptState("definition");
15143
+ if (attemptState) {
15144
+ attemptState.contextPackTrace = trace;
15145
+ }
15146
+ }
15147
+ } : void 0
15148
+ }),
15149
+ findSuccessfulAttemptState("definition")
15032
15150
  );
15033
15151
  }
15034
15152
  }
@@ -15047,7 +15165,10 @@ Explicit symbol lookup only; conceptual search was not attempted.`
15047
15165
  tokenBudget: heading.tokenBudget,
15048
15166
  tokenEstimate: heading.tokenEstimate,
15049
15167
  truncated: heading.truncated,
15050
- recovery: buildRecoveryDetails(attempts, null)
15168
+ recovery: buildRecoveryDetails(serializeAttempts(attemptStates), null),
15169
+ ...input.diagnostic && {
15170
+ diagnostic: buildSearchDiagnostic(attemptStates[attemptStates.length - 1])
15171
+ }
15051
15172
  }
15052
15173
  };
15053
15174
  }
@@ -15087,8 +15208,17 @@ Explicit symbol lookup only; conceptual search was not attempted.`
15087
15208
  maxResults: limit,
15088
15209
  heading,
15089
15210
  includeExactSearchHandoff: true,
15090
- preferImplementationPaths: intent.preferSourcePaths || intent.primary !== "docs" && intent.primary !== "test"
15091
- })
15211
+ preferImplementationPaths: intent.preferSourcePaths || intent.primary !== "docs" && intent.primary !== "test",
15212
+ ...input.diagnostic ? {
15213
+ trace: (trace) => {
15214
+ const attemptState = findSuccessfulAttemptState("conceptual");
15215
+ if (attemptState) {
15216
+ attemptState.contextPackTrace = trace;
15217
+ }
15218
+ }
15219
+ } : void 0
15220
+ }),
15221
+ findSuccessfulAttemptState("conceptual")
15092
15222
  );
15093
15223
  }
15094
15224
  }
@@ -15104,7 +15234,10 @@ Explicit symbol lookup only; conceptual search was not attempted.`
15104
15234
  tokenBudget: fallbackText.tokenBudget,
15105
15235
  tokenEstimate: fallbackText.tokenEstimate,
15106
15236
  truncated: fallbackText.truncated,
15107
- recovery: buildRecoveryDetails(attempts, null)
15237
+ recovery: buildRecoveryDetails(serializeAttempts(attemptStates), null),
15238
+ ...input.diagnostic && {
15239
+ diagnostic: buildSearchDiagnostic(attemptStates[attemptStates.length - 1])
15240
+ }
15108
15241
  }
15109
15242
  };
15110
15243
  }
@@ -15185,17 +15318,27 @@ async function resolveCodebaseContextUnmeasured(projectRoot3, host, input) {
15185
15318
  details: fittedDetails("path", fitted, 0)
15186
15319
  };
15187
15320
  }
15188
- return resolveSearchContext({ query: input.query, symbol, limit, tokenBudget, fileType, directory }, {
15189
- lookup: (lookupSymbol, retrievalLimit, scope) => implementationLookup(projectRoot3, host, lookupSymbol, {
15321
+ return resolveSearchContext({
15322
+ query: input.query,
15323
+ symbol,
15324
+ limit,
15325
+ tokenBudget,
15326
+ fileType,
15327
+ directory,
15328
+ diagnostic: input.diagnostic
15329
+ }, {
15330
+ lookup: (lookupSymbol, retrievalLimit, scope, trace) => implementationLookup(projectRoot3, host, lookupSymbol, {
15190
15331
  limit: retrievalLimit,
15191
15332
  fileType: scope.fileType,
15192
- directory: scope.directory
15333
+ directory: scope.directory,
15334
+ trace
15193
15335
  }),
15194
- search: (queryText, retrievalLimit, scope) => searchCodebase(projectRoot3, host, queryText, {
15336
+ search: (queryText, retrievalLimit, scope, trace) => searchCodebase(projectRoot3, host, queryText, {
15195
15337
  limit: retrievalLimit,
15196
15338
  fileType: scope.fileType,
15197
15339
  directory: scope.directory,
15198
- metadataOnly: true
15340
+ metadataOnly: true,
15341
+ trace
15199
15342
  })
15200
15343
  });
15201
15344
  }
@@ -15260,12 +15403,188 @@ async function resolveCodebaseContext(projectRoot3, host, input) {
15260
15403
  }
15261
15404
  }
15262
15405
 
15406
+ // src/tools/edit-context.ts
15407
+ function edgeLimit(value) {
15408
+ if (value === null || value === void 0 || !Number.isFinite(value)) {
15409
+ return DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT;
15410
+ }
15411
+ return Math.min(
15412
+ MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT,
15413
+ Math.max(MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT, Math.floor(value))
15414
+ );
15415
+ }
15416
+ function normalizedPath(value) {
15417
+ return value.replaceAll("\\", "/").replace(/^\.\//, "");
15418
+ }
15419
+ function pathsMatch(left, right) {
15420
+ const normalizedLeft = normalizedPath(left);
15421
+ const normalizedRight = normalizedPath(right);
15422
+ return normalizedLeft === normalizedRight || normalizedLeft.endsWith(`/${normalizedRight}`) || normalizedRight.endsWith(`/${normalizedLeft}`);
15423
+ }
15424
+ function targetSource(results, resolution) {
15425
+ 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);
15426
+ }
15427
+ function formatSource(result) {
15428
+ const name = result.name ? ` ${result.name}` : "";
15429
+ return [
15430
+ "## Target implementation",
15431
+ `${result.filePath}:${result.startLine}-${result.endLine} (${result.chunkType}${name})`,
15432
+ "```",
15433
+ result.content,
15434
+ "```"
15435
+ ].join("\n");
15436
+ }
15437
+ function formatCallers(edges) {
15438
+ if (edges.length === 0) return "## Direct callers\nNone found.";
15439
+ return [
15440
+ "## Direct callers",
15441
+ ...edges.map((edge) => `- ${edge.fromSymbolName ?? "<unknown>"} at ${edge.fromSymbolFilePath ?? "<unknown file>"}:${edge.line} (${edge.callType}, ${edge.isResolved ? "resolved" : "unresolved"})`)
15442
+ ].join("\n");
15443
+ }
15444
+ function formatCallees(edges, sourceFilePath) {
15445
+ if (edges.length === 0) return "## Direct callees\nNone found.";
15446
+ return [
15447
+ "## Direct callees",
15448
+ ...edges.map((edge) => `- ${edge.targetName} from ${sourceFilePath}:${edge.line} (${edge.callType}, ${edge.isResolved ? "resolved" : "unresolved"})`)
15449
+ ].join("\n");
15450
+ }
15451
+ function formatResolutionRisk(resolution) {
15452
+ if (resolution.status === "ambiguous") {
15453
+ const candidates = resolution.candidates.map((candidate) => `${candidate.filePath}:${candidate.startLine}`).join(", ");
15454
+ return `Risk: symbol "${resolution.name}" is ambiguous. Pass filePath to select a target.${candidates ? ` Candidates: ${candidates}.` : ""}`;
15455
+ }
15456
+ if (resolution.filePath && resolution.totalCandidates > 0) {
15457
+ return `Risk: symbol "${resolution.name}" did not resolve at filePath="${resolution.filePath}". Review the conceptual evidence before editing.`;
15458
+ }
15459
+ return `Risk: symbol "${resolution.name}" could not be resolved. Review the conceptual evidence before editing.`;
15460
+ }
15461
+ async function fallbackPack(dependencies, query, risk, resolution, tokenBudget, candidateSource = []) {
15462
+ const conceptual = await dependencies.searchCodebase(query, { limit: 5 });
15463
+ const pack = buildContextPack([...candidateSource, ...conceptual], {
15464
+ tokenBudget: tokenBudget ?? void 0,
15465
+ heading: "## Conceptual evidence",
15466
+ maxResults: 5,
15467
+ includeExactSearchHandoff: false,
15468
+ preferImplementationPaths: true
15469
+ });
15470
+ const fitted = fitTextToContextBudget(`${risk}
15471
+
15472
+ ${pack.text}`, tokenBudget ?? void 0);
15473
+ return {
15474
+ text: fitted.text,
15475
+ details: {
15476
+ resolution,
15477
+ tokenBudget: fitted.tokenBudget,
15478
+ tokenEstimate: fitted.tokenEstimate,
15479
+ truncated: fitted.truncated,
15480
+ sourceIncluded: candidateSource.length > 0,
15481
+ callerCount: 0,
15482
+ calleeCount: 0
15483
+ }
15484
+ };
15485
+ }
15486
+ async function resolveCodebaseEditContextWithDependencies(input, dependencies) {
15487
+ const symbol = input.symbol?.trim();
15488
+ if (!symbol) {
15489
+ return fallbackPack(
15490
+ dependencies,
15491
+ input.query,
15492
+ "Risk: no authoritative symbol was supplied. Review the conceptual evidence before editing.",
15493
+ "not_requested",
15494
+ input.tokenBudget
15495
+ );
15496
+ }
15497
+ let callersResult;
15498
+ try {
15499
+ callersResult = await dependencies.getCallGraphData({
15500
+ name: symbol,
15501
+ filePath: input.filePath ?? void 0,
15502
+ direction: "callers"
15503
+ });
15504
+ } catch (error) {
15505
+ const candidates = await dependencies.implementationLookup(symbol, { limit: 5 });
15506
+ const message = error instanceof Error ? error.message : String(error);
15507
+ return fallbackPack(
15508
+ dependencies,
15509
+ input.query,
15510
+ `Risk: graph data is unavailable (${message}). Target and dependencies are not graph-verified.`,
15511
+ "graph_unavailable",
15512
+ input.tokenBudget,
15513
+ candidates
15514
+ );
15515
+ }
15516
+ if (callersResult.resolution.status !== "resolved") {
15517
+ return fallbackPack(
15518
+ dependencies,
15519
+ input.query,
15520
+ formatResolutionRisk(callersResult.resolution),
15521
+ callersResult.resolution.status,
15522
+ input.tokenBudget
15523
+ );
15524
+ }
15525
+ const resolution = callersResult.resolution;
15526
+ const [definitionsResult, calleesResult] = await Promise.allSettled([
15527
+ dependencies.implementationLookup(symbol, { limit: 10 }),
15528
+ dependencies.getCallGraphData({
15529
+ name: symbol,
15530
+ filePath: input.filePath ?? resolution.filePath,
15531
+ direction: "callees"
15532
+ })
15533
+ ]);
15534
+ if (definitionsResult.status === "rejected") throw definitionsResult.reason;
15535
+ let graphRisk;
15536
+ let callees = [];
15537
+ if (calleesResult.status === "rejected") {
15538
+ const message = calleesResult.reason instanceof Error ? calleesResult.reason.message : String(calleesResult.reason);
15539
+ graphRisk = `Risk: callee graph data is unavailable (${message}). Dependency evidence is incomplete.`;
15540
+ } else if (calleesResult.value.resolution.status !== "resolved") {
15541
+ graphRisk = "Risk: the target resolved for callers but not callees. Dependency evidence is incomplete.";
15542
+ } else {
15543
+ callees = calleesResult.value.callees.slice(0, edgeLimit(input.calleeLimit));
15544
+ }
15545
+ const source = targetSource(definitionsResult.value, resolution);
15546
+ const callers = callersResult.callers.slice(0, edgeLimit(input.callerLimit));
15547
+ const sourceBudget = Math.max(
15548
+ MIN_CONTEXT_PACK_TOKEN_BUDGET,
15549
+ Math.floor((input.tokenBudget ?? DEFAULT_CONTEXT_PACK_TOKEN_BUDGET) * 0.6)
15550
+ );
15551
+ const sourceText = source ? fitTextToContextBudget(formatSource(source), sourceBudget).text : `## Target implementation
15552
+ Risk: no implementation source matched the resolved target at ${resolution.filePath}:${resolution.startLine}.`;
15553
+ const fitted = fitTextToContextBudget([
15554
+ `# Pre-edit context for ${resolution.name}`,
15555
+ graphRisk,
15556
+ sourceText,
15557
+ formatCallers(callers),
15558
+ formatCallees(callees, resolution.filePath)
15559
+ ].filter((section) => section !== void 0).join("\n\n"), input.tokenBudget ?? void 0);
15560
+ return {
15561
+ text: fitted.text,
15562
+ details: {
15563
+ resolution: "resolved",
15564
+ tokenBudget: fitted.tokenBudget,
15565
+ tokenEstimate: fitted.tokenEstimate,
15566
+ truncated: fitted.truncated,
15567
+ sourceIncluded: source !== void 0,
15568
+ callerCount: callers.length,
15569
+ calleeCount: callees.length
15570
+ }
15571
+ };
15572
+ }
15573
+ async function resolveCodebaseEditContext(projectRoot3, host, input) {
15574
+ return resolveCodebaseEditContextWithDependencies(input, {
15575
+ searchCodebase: (query, options) => searchCodebase(projectRoot3, host, query, options),
15576
+ implementationLookup: (query, options) => implementationLookup(projectRoot3, host, query, options),
15577
+ getCallGraphData: (params) => getCallGraphData(projectRoot3, host, params)
15578
+ });
15579
+ }
15580
+
15263
15581
  // src/adapters/pi/call-graph.ts
15264
15582
  var import_typebox = require("typebox");
15265
15583
 
15266
15584
  // src/tools/tool-names.ts
15267
15585
  var TOOL_NAME = {
15268
15586
  CODEBASE_CONTEXT: "codebase_context",
15587
+ CODEBASE_EDIT_CONTEXT: "codebase_edit_context",
15269
15588
  CODEBASE_SEARCH: "codebase_search",
15270
15589
  CODEBASE_PEEK: "codebase_peek",
15271
15590
  FIND_SIMILAR: "find_similar",
@@ -15289,6 +15608,7 @@ var TOOL_NAME = {
15289
15608
  };
15290
15609
  var PORTABLE_TOOL_NAMES = [
15291
15610
  TOOL_NAME.CODEBASE_CONTEXT,
15611
+ TOOL_NAME.CODEBASE_EDIT_CONTEXT,
15292
15612
  TOOL_NAME.CODEBASE_SEARCH,
15293
15613
  TOOL_NAME.CODEBASE_PEEK,
15294
15614
  TOOL_NAME.INDEX_CODEBASE,
@@ -15305,6 +15625,7 @@ var PORTABLE_TOOL_NAMES = [
15305
15625
  ];
15306
15626
  var OPENCODE_TOOL_NAMES = [
15307
15627
  TOOL_NAME.CODEBASE_CONTEXT,
15628
+ TOOL_NAME.CODEBASE_EDIT_CONTEXT,
15308
15629
  TOOL_NAME.CODEBASE_SEARCH,
15309
15630
  TOOL_NAME.CODEBASE_PEEK,
15310
15631
  TOOL_NAME.INDEX_CODEBASE,
@@ -15325,6 +15646,7 @@ var OPENCODE_TOOL_NAMES = [
15325
15646
  ];
15326
15647
  var PI_TOOL_NAMES = [
15327
15648
  TOOL_NAME.CODEBASE_CONTEXT,
15649
+ TOOL_NAME.CODEBASE_EDIT_CONTEXT,
15328
15650
  TOOL_NAME.CODEBASE_SEARCH,
15329
15651
  TOOL_NAME.CODEBASE_PEEK,
15330
15652
  TOOL_NAME.FIND_SIMILAR,
@@ -15439,6 +15761,7 @@ function codebaseIndexPiExtension(pi) {
15439
15761
  import_typebox2.Type.Integer({ minimum: MIN_CONTEXT_PATH_DEPTH, maximum: MAX_CONTEXT_PATH_DEPTH }),
15440
15762
  import_typebox2.Type.Null()
15441
15763
  ], { default: 10, description: `Maximum call-graph traversal depth (${MIN_CONTEXT_PATH_DEPTH}-${MAX_CONTEXT_PATH_DEPTH})` })),
15764
+ diagnostic: import_typebox2.Type.Optional(import_typebox2.Type.Union([import_typebox2.Type.Boolean(), import_typebox2.Type.Null()], { description: "Collect diagnostic routing and search traces without changing normal output." })),
15442
15765
  fileType: import_typebox2.Type.Optional(import_typebox2.Type.Union([import_typebox2.Type.String(), import_typebox2.Type.Null()], { description: "Filter by file extension, e.g., ts, py, rs" })),
15443
15766
  directory: import_typebox2.Type.Optional(import_typebox2.Type.Union([import_typebox2.Type.String(), import_typebox2.Type.Null()], { description: "Filter by directory path" })),
15444
15767
  tokenBudget: import_typebox2.Type.Optional(import_typebox2.Type.Union([
@@ -15450,10 +15773,40 @@ function codebaseIndexPiExtension(pi) {
15450
15773
  }))
15451
15774
  }),
15452
15775
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
15453
- const result = await resolveCodebaseContext(projectRoot2(ctx), HOST2, params);
15776
+ const normalizedParams = {
15777
+ ...params,
15778
+ diagnostic: params.diagnostic ?? void 0
15779
+ };
15780
+ const result = await resolveCodebaseContext(projectRoot2(ctx), HOST2, normalizedParams);
15454
15781
  return text2(result.text, result.details);
15455
15782
  }
15456
15783
  });
15784
+ pi.registerTool({
15785
+ name: TOOL_NAME.CODEBASE_EDIT_CONTEXT,
15786
+ label: "Codebase Edit Context",
15787
+ description: "PRE-EDIT TOOL for a known or suspected symbol. Returns bounded target source and direct graph evidence, with a risk-marked fallback when unresolved.",
15788
+ parameters: import_typebox2.Type.Object({
15789
+ query: import_typebox2.Type.String({ description: "The requested change or target behavior" }),
15790
+ symbol: import_typebox2.Type.Optional(import_typebox2.Type.Union([import_typebox2.Type.String(), import_typebox2.Type.Null()])),
15791
+ filePath: import_typebox2.Type.Optional(import_typebox2.Type.Union([import_typebox2.Type.String(), import_typebox2.Type.Null()])),
15792
+ callerLimit: import_typebox2.Type.Optional(import_typebox2.Type.Union([
15793
+ import_typebox2.Type.Integer({ minimum: MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT, maximum: MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT }),
15794
+ import_typebox2.Type.Null()
15795
+ ], { default: DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT })),
15796
+ calleeLimit: import_typebox2.Type.Optional(import_typebox2.Type.Union([
15797
+ import_typebox2.Type.Integer({ minimum: MIN_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT, maximum: MAX_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT }),
15798
+ import_typebox2.Type.Null()
15799
+ ], { default: DEFAULT_CODEBASE_EDIT_CONTEXT_EDGE_LIMIT })),
15800
+ tokenBudget: import_typebox2.Type.Optional(import_typebox2.Type.Union([
15801
+ import_typebox2.Type.Integer({ minimum: MIN_CONTEXT_PACK_TOKEN_BUDGET, maximum: MAX_CONTEXT_PACK_TOKEN_BUDGET }),
15802
+ import_typebox2.Type.Null()
15803
+ ], { default: DEFAULT_CONTEXT_PACK_TOKEN_BUDGET }))
15804
+ }),
15805
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
15806
+ const result = await resolveCodebaseEditContext(projectRoot2(ctx), HOST2, params);
15807
+ return text2(result.text);
15808
+ }
15809
+ });
15457
15810
  pi.registerTool({
15458
15811
  name: TOOL_NAME.CODEBASE_SEARCH,
15459
15812
  label: "Codebase Search",