opencode-codebase-index 0.16.0 → 0.17.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/dist/cli.cjs +2400 -1786
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2404 -1790
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +454 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +454 -10
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +459 -69
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +459 -69
- package/dist/pi-extension.js.map +1 -1
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +4 -2
package/dist/pi-extension.js
CHANGED
|
@@ -4954,7 +4954,12 @@ function sameReclaimOwner(left, right) {
|
|
|
4954
4954
|
}
|
|
4955
4955
|
function publishJsonDirectory(finalPath, value) {
|
|
4956
4956
|
const candidatePath = `${finalPath}.candidate.${process.pid}.${randomUUID()}`;
|
|
4957
|
-
|
|
4957
|
+
try {
|
|
4958
|
+
mkdirSync2(candidatePath, { mode: 448 });
|
|
4959
|
+
} catch (error) {
|
|
4960
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
4961
|
+
throw error;
|
|
4962
|
+
}
|
|
4958
4963
|
try {
|
|
4959
4964
|
writeFileSync2(path12.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
|
|
4960
4965
|
encoding: "utf-8",
|
|
@@ -4967,8 +4972,12 @@ function publishJsonDirectory(finalPath, value) {
|
|
|
4967
4972
|
return true;
|
|
4968
4973
|
} catch (error) {
|
|
4969
4974
|
if (existsSync7(finalPath)) return false;
|
|
4975
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
4970
4976
|
throw error;
|
|
4971
4977
|
}
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
4980
|
+
throw error;
|
|
4972
4981
|
} finally {
|
|
4973
4982
|
if (existsSync7(candidatePath)) rmSync(candidatePath, { recursive: true, force: true });
|
|
4974
4983
|
}
|
|
@@ -6537,6 +6546,27 @@ function mergeTieredResults(symbolLane, hybridLane, limit) {
|
|
|
6537
6546
|
}
|
|
6538
6547
|
return out;
|
|
6539
6548
|
}
|
|
6549
|
+
function selectChunksWithFileCoverage(chunks, limit) {
|
|
6550
|
+
if (limit <= 0 || chunks.length === 0) {
|
|
6551
|
+
return [];
|
|
6552
|
+
}
|
|
6553
|
+
if (chunks.length <= limit) {
|
|
6554
|
+
return chunks;
|
|
6555
|
+
}
|
|
6556
|
+
if (limit === 1) {
|
|
6557
|
+
return [chunks[Math.floor((chunks.length - 1) / 2)]];
|
|
6558
|
+
}
|
|
6559
|
+
const selected = [];
|
|
6560
|
+
for (let index = 0; index < limit; index++) {
|
|
6561
|
+
const sourceIndex = Math.round(index * (chunks.length - 1) / (limit - 1));
|
|
6562
|
+
selected.push(chunks[sourceIndex]);
|
|
6563
|
+
}
|
|
6564
|
+
return selected;
|
|
6565
|
+
}
|
|
6566
|
+
function selectIndexableChunks(chunks, limit, semanticOnly) {
|
|
6567
|
+
const indexableChunks = semanticOnly ? chunks.filter((chunk) => chunk.chunkType !== "other") : chunks;
|
|
6568
|
+
return selectChunksWithFileCoverage(indexableChunks, limit);
|
|
6569
|
+
}
|
|
6540
6570
|
function matchesSearchFilters(candidate, options, minScore) {
|
|
6541
6571
|
if (candidate.score < minScore) return false;
|
|
6542
6572
|
if (options?.fileType) {
|
|
@@ -8275,7 +8305,6 @@ var Indexer = class {
|
|
|
8275
8305
|
const relativePath = path13.relative(this.projectRoot, parsed.path);
|
|
8276
8306
|
stats.parseFailures.push(relativePath);
|
|
8277
8307
|
}
|
|
8278
|
-
let fileChunkCount = 0;
|
|
8279
8308
|
let chunksToProcess = parsed.chunks;
|
|
8280
8309
|
if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
|
|
8281
8310
|
const changedFile = changedFiles.find((f) => f.path === parsed.path);
|
|
@@ -8284,13 +8313,12 @@ var Indexer = class {
|
|
|
8284
8313
|
chunksToProcess = textChunks;
|
|
8285
8314
|
}
|
|
8286
8315
|
}
|
|
8316
|
+
chunksToProcess = selectIndexableChunks(
|
|
8317
|
+
chunksToProcess,
|
|
8318
|
+
this.config.indexing.maxChunksPerFile,
|
|
8319
|
+
this.config.indexing.semanticOnly
|
|
8320
|
+
);
|
|
8287
8321
|
for (const chunk of chunksToProcess) {
|
|
8288
|
-
if (fileChunkCount >= this.config.indexing.maxChunksPerFile) {
|
|
8289
|
-
break;
|
|
8290
|
-
}
|
|
8291
|
-
if (this.config.indexing.semanticOnly && chunk.chunkType === "other") {
|
|
8292
|
-
continue;
|
|
8293
|
-
}
|
|
8294
8322
|
const id = generateChunkId(parsed.path, chunk);
|
|
8295
8323
|
const contentHash = generateChunkHash(chunk);
|
|
8296
8324
|
const existingContentHash = existingChunks.get(id);
|
|
@@ -8314,7 +8342,6 @@ var Indexer = class {
|
|
|
8314
8342
|
blameSummary: blameMetadata.blameSummary
|
|
8315
8343
|
});
|
|
8316
8344
|
if (existingContentHash === contentHash) {
|
|
8317
|
-
fileChunkCount++;
|
|
8318
8345
|
continue;
|
|
8319
8346
|
}
|
|
8320
8347
|
const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text3) => ({
|
|
@@ -8339,7 +8366,6 @@ var Indexer = class {
|
|
|
8339
8366
|
contentHash,
|
|
8340
8367
|
metadata
|
|
8341
8368
|
});
|
|
8342
|
-
fileChunkCount++;
|
|
8343
8369
|
}
|
|
8344
8370
|
}
|
|
8345
8371
|
const retryableFailedChunks = this.collectRetryableFailedChunks(
|
|
@@ -9930,6 +9956,159 @@ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
|
|
|
9930
9956
|
}
|
|
9931
9957
|
|
|
9932
9958
|
// src/tools/utils.ts
|
|
9959
|
+
import { get_encoding } from "tiktoken";
|
|
9960
|
+
var MIN_CONTEXT_PACK_TOKEN_BUDGET = 128;
|
|
9961
|
+
var MAX_CONTEXT_PACK_TOKEN_BUDGET = 4e3;
|
|
9962
|
+
var DEFAULT_CONTEXT_PACK_TOKEN_BUDGET = 1200;
|
|
9963
|
+
var CONTEXT_TOKENIZER = get_encoding("cl100k_base");
|
|
9964
|
+
function clampContextPackTokenBudget(tokenBudget) {
|
|
9965
|
+
if (tokenBudget === void 0 || !Number.isFinite(tokenBudget)) {
|
|
9966
|
+
return DEFAULT_CONTEXT_PACK_TOKEN_BUDGET;
|
|
9967
|
+
}
|
|
9968
|
+
return Math.min(
|
|
9969
|
+
MAX_CONTEXT_PACK_TOKEN_BUDGET,
|
|
9970
|
+
Math.max(MIN_CONTEXT_PACK_TOKEN_BUDGET, Math.floor(tokenBudget))
|
|
9971
|
+
);
|
|
9972
|
+
}
|
|
9973
|
+
function countContextTokens(text3) {
|
|
9974
|
+
return CONTEXT_TOKENIZER.encode(text3).length;
|
|
9975
|
+
}
|
|
9976
|
+
function fitTextToContextBudget(text3, tokenBudget) {
|
|
9977
|
+
const normalizedBudget = clampContextPackTokenBudget(tokenBudget);
|
|
9978
|
+
const tokenEstimate = countContextTokens(text3);
|
|
9979
|
+
if (tokenEstimate <= normalizedBudget) {
|
|
9980
|
+
return {
|
|
9981
|
+
text: text3,
|
|
9982
|
+
tokenBudget: normalizedBudget,
|
|
9983
|
+
tokenEstimate,
|
|
9984
|
+
truncated: false
|
|
9985
|
+
};
|
|
9986
|
+
}
|
|
9987
|
+
const suffix = "\n...[truncated to context token budget]";
|
|
9988
|
+
const codePoints = Array.from(text3);
|
|
9989
|
+
let low = 0;
|
|
9990
|
+
let high = codePoints.length;
|
|
9991
|
+
while (low < high) {
|
|
9992
|
+
const middle = Math.ceil((low + high) / 2);
|
|
9993
|
+
const candidate = `${codePoints.slice(0, middle).join("").trimEnd()}${suffix}`;
|
|
9994
|
+
if (countContextTokens(candidate) <= normalizedBudget) low = middle;
|
|
9995
|
+
else high = middle - 1;
|
|
9996
|
+
}
|
|
9997
|
+
const fitted = `${codePoints.slice(0, low).join("").trimEnd()}${suffix}`;
|
|
9998
|
+
return {
|
|
9999
|
+
text: fitted,
|
|
10000
|
+
tokenBudget: normalizedBudget,
|
|
10001
|
+
tokenEstimate: countContextTokens(fitted),
|
|
10002
|
+
truncated: true
|
|
10003
|
+
};
|
|
10004
|
+
}
|
|
10005
|
+
function normalizedLineRange(result) {
|
|
10006
|
+
return result.startLine <= result.endLine ? { start: result.startLine, end: result.endLine } : { start: result.endLine, end: result.startLine };
|
|
10007
|
+
}
|
|
10008
|
+
function rankContextCandidates(results) {
|
|
10009
|
+
return results.map((result, originalIndex) => ({ result, originalIndex })).sort((left, right) => right.result.score - left.result.score || left.originalIndex - right.originalIndex);
|
|
10010
|
+
}
|
|
10011
|
+
function deduplicateContextCandidates(candidates) {
|
|
10012
|
+
const acceptedByFile = /* @__PURE__ */ new Map();
|
|
10013
|
+
const deduplicated = [];
|
|
10014
|
+
for (const { result } of candidates) {
|
|
10015
|
+
const range = normalizedLineRange(result);
|
|
10016
|
+
const accepted = acceptedByFile.get(result.filePath) ?? [];
|
|
10017
|
+
if (accepted.some((item) => item.start <= range.end && range.start <= item.end)) {
|
|
10018
|
+
continue;
|
|
10019
|
+
}
|
|
10020
|
+
accepted.push(range);
|
|
10021
|
+
acceptedByFile.set(result.filePath, accepted);
|
|
10022
|
+
deduplicated.push(result);
|
|
10023
|
+
}
|
|
10024
|
+
return deduplicated;
|
|
10025
|
+
}
|
|
10026
|
+
function diversifyContextCandidates(results) {
|
|
10027
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
10028
|
+
for (const result of results) {
|
|
10029
|
+
const bucket = byFile.get(result.filePath) ?? [];
|
|
10030
|
+
bucket.push(result);
|
|
10031
|
+
byFile.set(result.filePath, bucket);
|
|
10032
|
+
}
|
|
10033
|
+
const files = [...byFile.keys()];
|
|
10034
|
+
const diversified = [];
|
|
10035
|
+
for (let depth = 0; diversified.length < results.length; depth += 1) {
|
|
10036
|
+
for (const file of files) {
|
|
10037
|
+
const result = byFile.get(file)?.[depth];
|
|
10038
|
+
if (result) diversified.push(result);
|
|
10039
|
+
}
|
|
10040
|
+
}
|
|
10041
|
+
return diversified;
|
|
10042
|
+
}
|
|
10043
|
+
function compactEvidenceValue(value, maxChars) {
|
|
10044
|
+
const characters = [...value];
|
|
10045
|
+
if (characters.length <= maxChars) return value;
|
|
10046
|
+
return `\u2026${characters.slice(-(maxChars - 1)).join("")}`;
|
|
10047
|
+
}
|
|
10048
|
+
function formatContextEvidence(result, index) {
|
|
10049
|
+
const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
|
|
10050
|
+
const path17 = compactEvidenceValue(result.filePath, 120);
|
|
10051
|
+
return `[${index}] ${result.chunkType}${symbol} in ${path17}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
|
|
10052
|
+
}
|
|
10053
|
+
function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount) {
|
|
10054
|
+
const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
|
|
10055
|
+
const notes = [];
|
|
10056
|
+
if (duplicateCount > 0) notes.push(`${duplicateCount} overlapping duplicate${duplicateCount === 1 ? "" : "s"} removed`);
|
|
10057
|
+
if (limitOmittedCount > 0) notes.push(`${limitOmittedCount} additional result${limitOmittedCount === 1 ? "" : "s"} excluded by result limit`);
|
|
10058
|
+
if (budgetOmittedCount > 0) notes.push(`${budgetOmittedCount} additional result${budgetOmittedCount === 1 ? "" : "s"} omitted by token budget`);
|
|
10059
|
+
const footer = notes.length > 0 ? `Selected ${selected.length} of ${candidateCount} candidates; ${notes.join("; ")}.` : `Selected ${selected.length} of ${candidateCount} candidates.`;
|
|
10060
|
+
return `${heading}
|
|
10061
|
+
|
|
10062
|
+
${lines.join("\n")}
|
|
10063
|
+
|
|
10064
|
+
${footer}`;
|
|
10065
|
+
}
|
|
10066
|
+
function buildContextPack(results, options = {}) {
|
|
10067
|
+
const requestedTokenBudget = options.tokenBudget ?? DEFAULT_CONTEXT_PACK_TOKEN_BUDGET;
|
|
10068
|
+
const tokenBudget = clampContextPackTokenBudget(options.tokenBudget);
|
|
10069
|
+
const heading = compactEvidenceValue(options.heading?.trim() || "Codebase evidence", 160);
|
|
10070
|
+
const maxResults = Math.max(0, Math.floor(options.maxResults ?? results.length));
|
|
10071
|
+
const candidateCount = results.length;
|
|
10072
|
+
const deduplicated = deduplicateContextCandidates(rankContextCandidates(results));
|
|
10073
|
+
const diversified = diversifyContextCandidates(deduplicated);
|
|
10074
|
+
const duplicateCount = candidateCount - deduplicated.length;
|
|
10075
|
+
const selectable = diversified.slice(0, maxResults);
|
|
10076
|
+
const limitOmittedCount = deduplicated.length - selectable.length;
|
|
10077
|
+
let selected = [];
|
|
10078
|
+
let text3 = formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, selectable.length);
|
|
10079
|
+
for (let count = 1; count <= selectable.length; count += 1) {
|
|
10080
|
+
const candidateSelection = selectable.slice(0, count);
|
|
10081
|
+
const budgetOmittedCount2 = selectable.length - candidateSelection.length;
|
|
10082
|
+
const candidateText = formatContextPack(
|
|
10083
|
+
heading,
|
|
10084
|
+
candidateSelection,
|
|
10085
|
+
candidateCount,
|
|
10086
|
+
duplicateCount,
|
|
10087
|
+
limitOmittedCount,
|
|
10088
|
+
budgetOmittedCount2
|
|
10089
|
+
);
|
|
10090
|
+
if (countContextTokens(candidateText) > tokenBudget) break;
|
|
10091
|
+
selected = candidateSelection;
|
|
10092
|
+
text3 = candidateText;
|
|
10093
|
+
}
|
|
10094
|
+
const fitted = fitTextToContextBudget(text3, tokenBudget);
|
|
10095
|
+
const budgetOmittedCount = selectable.length - selected.length;
|
|
10096
|
+
const omittedCount = candidateCount - selected.length;
|
|
10097
|
+
return {
|
|
10098
|
+
requestedTokenBudget,
|
|
10099
|
+
tokenBudget,
|
|
10100
|
+
text: fitted.text,
|
|
10101
|
+
tokenEstimate: fitted.tokenEstimate,
|
|
10102
|
+
results: selected,
|
|
10103
|
+
candidateCount,
|
|
10104
|
+
deduplicatedCount: deduplicated.length,
|
|
10105
|
+
selectedCount: selected.length,
|
|
10106
|
+
omittedCount,
|
|
10107
|
+
duplicateCount,
|
|
10108
|
+
limitOmittedCount,
|
|
10109
|
+
budgetOmittedCount
|
|
10110
|
+
};
|
|
10111
|
+
}
|
|
9933
10112
|
var MAX_CONTENT_LINES = 30;
|
|
9934
10113
|
function truncateContent(content) {
|
|
9935
10114
|
const lines = content.split("\n");
|
|
@@ -10074,17 +10253,6 @@ function calculatePercentage(progress) {
|
|
|
10074
10253
|
if (progress.phase === "storing") return 95;
|
|
10075
10254
|
return 0;
|
|
10076
10255
|
}
|
|
10077
|
-
function formatCodebasePeek(results) {
|
|
10078
|
-
if (results.length === 0) {
|
|
10079
|
-
return "No matching code found. Try a different query or run index_codebase first.";
|
|
10080
|
-
}
|
|
10081
|
-
const formatted = results.map((r, idx) => {
|
|
10082
|
-
const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10083
|
-
const name = r.name ? `"${r.name}"` : "(anonymous)";
|
|
10084
|
-
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})${formatBlame(r)}`;
|
|
10085
|
-
});
|
|
10086
|
-
return formatted.join("\n");
|
|
10087
|
-
}
|
|
10088
10256
|
function formatHealthCheck(result) {
|
|
10089
10257
|
if (result.resetCorruptedIndex) {
|
|
10090
10258
|
return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
|
|
@@ -10598,6 +10766,253 @@ Run /index to rebuild the index without the removed knowledge base.`;
|
|
|
10598
10766
|
return result;
|
|
10599
10767
|
}
|
|
10600
10768
|
|
|
10769
|
+
// src/tools/symbol-inference.ts
|
|
10770
|
+
var IDENTIFIER_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
10771
|
+
var QUOTED_BACKTICK_RE = /`([^`]+)`/g;
|
|
10772
|
+
var QUOTED_SINGLE_RE = /'([^'\\]+)'/g;
|
|
10773
|
+
var QUOTED_DOUBLE_RE = /"([^"]+)"/g;
|
|
10774
|
+
var SYMBOL_LIKE_RE = /^(?:[A-Za-z_$][A-Za-z0-9_$]*)$/;
|
|
10775
|
+
var CAMEL_CASE_RE = /^[a-z_][A-Za-z0-9_$]*[A-Z][A-Za-z0-9_$]*$/;
|
|
10776
|
+
var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9_$]*$/;
|
|
10777
|
+
var SNAKE_CASE_RE = /^[a-z][a-z0-9_]*_[a-z0-9_]+$/;
|
|
10778
|
+
var DEFINITION_INTENT_RE = /\b(where|defined|definition|define|declaration|symbol|function|method|class|interface|type)\b/i;
|
|
10779
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
10780
|
+
"a",
|
|
10781
|
+
"an",
|
|
10782
|
+
"and",
|
|
10783
|
+
"are",
|
|
10784
|
+
"at",
|
|
10785
|
+
"for",
|
|
10786
|
+
"find",
|
|
10787
|
+
"how",
|
|
10788
|
+
"i",
|
|
10789
|
+
"in",
|
|
10790
|
+
"is",
|
|
10791
|
+
"it",
|
|
10792
|
+
"of",
|
|
10793
|
+
"on",
|
|
10794
|
+
"that",
|
|
10795
|
+
"the",
|
|
10796
|
+
"definition",
|
|
10797
|
+
"show",
|
|
10798
|
+
"to",
|
|
10799
|
+
"where",
|
|
10800
|
+
"which",
|
|
10801
|
+
"what",
|
|
10802
|
+
"you",
|
|
10803
|
+
"your",
|
|
10804
|
+
"with"
|
|
10805
|
+
]);
|
|
10806
|
+
function stripCallSuffix(token) {
|
|
10807
|
+
return token.replace(/\(\s*\)$/, "");
|
|
10808
|
+
}
|
|
10809
|
+
function isLikelySymbolName(token) {
|
|
10810
|
+
if (!SYMBOL_LIKE_RE.test(token)) {
|
|
10811
|
+
return false;
|
|
10812
|
+
}
|
|
10813
|
+
if (STOP_WORDS.has(token.toLowerCase())) {
|
|
10814
|
+
return false;
|
|
10815
|
+
}
|
|
10816
|
+
return CAMEL_CASE_RE.test(token) || PASCAL_CASE_RE.test(token) || SNAKE_CASE_RE.test(token);
|
|
10817
|
+
}
|
|
10818
|
+
function extractQuotedIdentifiers(query) {
|
|
10819
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
10820
|
+
for (const match of query.matchAll(QUOTED_BACKTICK_RE)) {
|
|
10821
|
+
const candidate = stripCallSuffix(match[1].trim());
|
|
10822
|
+
if (candidate && isLikelySymbolName(candidate)) {
|
|
10823
|
+
identifiers.add(candidate);
|
|
10824
|
+
}
|
|
10825
|
+
}
|
|
10826
|
+
for (const match of query.matchAll(QUOTED_SINGLE_RE)) {
|
|
10827
|
+
const candidate = stripCallSuffix(match[1].trim());
|
|
10828
|
+
if (candidate && isLikelySymbolName(candidate)) {
|
|
10829
|
+
identifiers.add(candidate);
|
|
10830
|
+
}
|
|
10831
|
+
}
|
|
10832
|
+
for (const match of query.matchAll(QUOTED_DOUBLE_RE)) {
|
|
10833
|
+
const candidate = stripCallSuffix(match[1].trim());
|
|
10834
|
+
if (candidate && isLikelySymbolName(candidate)) {
|
|
10835
|
+
identifiers.add(candidate);
|
|
10836
|
+
}
|
|
10837
|
+
}
|
|
10838
|
+
return [...identifiers];
|
|
10839
|
+
}
|
|
10840
|
+
function extractBareIdentifiers(query) {
|
|
10841
|
+
const unquoted = query.replace(QUOTED_BACKTICK_RE, " ").replace(QUOTED_SINGLE_RE, " ").replace(QUOTED_DOUBLE_RE, " ");
|
|
10842
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
10843
|
+
for (const match of unquoted.matchAll(IDENTIFIER_RE)) {
|
|
10844
|
+
const candidate = stripCallSuffix(match[0]);
|
|
10845
|
+
if (isLikelySymbolName(candidate)) {
|
|
10846
|
+
identifiers.add(candidate);
|
|
10847
|
+
}
|
|
10848
|
+
}
|
|
10849
|
+
return [...identifiers];
|
|
10850
|
+
}
|
|
10851
|
+
function isSingleMeaningfulToken(query, symbol) {
|
|
10852
|
+
const tokens = query.replace(/[`'"()]/g, " ").split(/[^A-Za-z0-9_$]+/).map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0).filter((token) => !STOP_WORDS.has(token));
|
|
10853
|
+
return tokens.length === 1 && tokens[0] === symbol.toLowerCase();
|
|
10854
|
+
}
|
|
10855
|
+
function inferExactSymbolFromQuery(query) {
|
|
10856
|
+
const quoted = extractQuotedIdentifiers(query);
|
|
10857
|
+
if (quoted.length === 1) {
|
|
10858
|
+
return quoted[0];
|
|
10859
|
+
}
|
|
10860
|
+
if (quoted.length > 1) {
|
|
10861
|
+
return void 0;
|
|
10862
|
+
}
|
|
10863
|
+
const candidates = extractBareIdentifiers(query);
|
|
10864
|
+
if (candidates.length !== 1) {
|
|
10865
|
+
return void 0;
|
|
10866
|
+
}
|
|
10867
|
+
const candidate = candidates[0];
|
|
10868
|
+
if (DEFINITION_INTENT_RE.test(query)) {
|
|
10869
|
+
return candidate;
|
|
10870
|
+
}
|
|
10871
|
+
if (isSingleMeaningfulToken(query, candidate)) {
|
|
10872
|
+
return candidate;
|
|
10873
|
+
}
|
|
10874
|
+
return void 0;
|
|
10875
|
+
}
|
|
10876
|
+
|
|
10877
|
+
// src/tools/context.ts
|
|
10878
|
+
var MIN_CONTEXT_RESULT_LIMIT = 1;
|
|
10879
|
+
var MAX_CONTEXT_RESULT_LIMIT = 100;
|
|
10880
|
+
var MIN_CONTEXT_PATH_DEPTH = 1;
|
|
10881
|
+
var MAX_CONTEXT_PATH_DEPTH = 100;
|
|
10882
|
+
function locations(results) {
|
|
10883
|
+
return results.map((result) => ({
|
|
10884
|
+
filePath: result.filePath,
|
|
10885
|
+
startLine: result.startLine,
|
|
10886
|
+
endLine: result.endLine,
|
|
10887
|
+
score: result.score,
|
|
10888
|
+
chunkType: result.chunkType,
|
|
10889
|
+
name: result.name
|
|
10890
|
+
}));
|
|
10891
|
+
}
|
|
10892
|
+
function packedResult(route, routedQuery, pack) {
|
|
10893
|
+
return {
|
|
10894
|
+
text: pack.text,
|
|
10895
|
+
details: {
|
|
10896
|
+
route,
|
|
10897
|
+
routedQuery,
|
|
10898
|
+
tokenBudget: pack.tokenBudget,
|
|
10899
|
+
tokenEstimate: pack.tokenEstimate,
|
|
10900
|
+
candidateCount: pack.candidateCount,
|
|
10901
|
+
deduplicatedCount: pack.deduplicatedCount,
|
|
10902
|
+
selectedCount: pack.selectedCount,
|
|
10903
|
+
omittedCount: pack.omittedCount,
|
|
10904
|
+
duplicateCount: pack.duplicateCount,
|
|
10905
|
+
limitOmittedCount: pack.limitOmittedCount,
|
|
10906
|
+
budgetOmittedCount: pack.budgetOmittedCount,
|
|
10907
|
+
results: locations(pack.results)
|
|
10908
|
+
}
|
|
10909
|
+
};
|
|
10910
|
+
}
|
|
10911
|
+
async function resolveSearchContext(input, operations) {
|
|
10912
|
+
const explicitSymbol = input.symbol ?? void 0;
|
|
10913
|
+
const limit = input.limit ?? 10;
|
|
10914
|
+
const tokenBudget = input.tokenBudget ?? void 0;
|
|
10915
|
+
const lookupSymbol = explicitSymbol ?? inferExactSymbolFromQuery(input.query);
|
|
10916
|
+
if (lookupSymbol) {
|
|
10917
|
+
const definitions = await operations.lookup(lookupSymbol, MAX_CONTEXT_RESULT_LIMIT);
|
|
10918
|
+
if (definitions.length > 0) {
|
|
10919
|
+
return packedResult("definition", lookupSymbol, buildContextPack(definitions, {
|
|
10920
|
+
tokenBudget,
|
|
10921
|
+
maxResults: limit,
|
|
10922
|
+
heading: `Definition evidence for ${JSON.stringify(lookupSymbol)}`
|
|
10923
|
+
}));
|
|
10924
|
+
}
|
|
10925
|
+
if (explicitSymbol) {
|
|
10926
|
+
const fitted = fitTextToContextBudget(
|
|
10927
|
+
formatDefinitionLookup(definitions, lookupSymbol),
|
|
10928
|
+
tokenBudget
|
|
10929
|
+
);
|
|
10930
|
+
return { text: fitted.text };
|
|
10931
|
+
}
|
|
10932
|
+
}
|
|
10933
|
+
const results = await operations.search(input.query, MAX_CONTEXT_RESULT_LIMIT);
|
|
10934
|
+
if (results.length === 0) {
|
|
10935
|
+
const fitted = fitTextToContextBudget(
|
|
10936
|
+
"No matching code found. Try a different query or run index_codebase first.",
|
|
10937
|
+
tokenBudget
|
|
10938
|
+
);
|
|
10939
|
+
return { text: fitted.text };
|
|
10940
|
+
}
|
|
10941
|
+
return packedResult("conceptual", input.query, buildContextPack(results, {
|
|
10942
|
+
tokenBudget,
|
|
10943
|
+
maxResults: limit,
|
|
10944
|
+
heading: `Codebase evidence for ${JSON.stringify(input.query)}`
|
|
10945
|
+
}));
|
|
10946
|
+
}
|
|
10947
|
+
function fittedDetails(route, fitted) {
|
|
10948
|
+
return {
|
|
10949
|
+
route,
|
|
10950
|
+
tokenBudget: fitted.tokenBudget,
|
|
10951
|
+
tokenEstimate: fitted.tokenEstimate,
|
|
10952
|
+
truncated: fitted.truncated
|
|
10953
|
+
};
|
|
10954
|
+
}
|
|
10955
|
+
async function resolveCodebaseContext(projectRoot3, host, input) {
|
|
10956
|
+
const from = input.from ?? void 0;
|
|
10957
|
+
const to = input.to ?? void 0;
|
|
10958
|
+
const symbol = input.symbol ?? void 0;
|
|
10959
|
+
const limit = input.limit ?? 10;
|
|
10960
|
+
const maxDepth = input.maxDepth ?? 10;
|
|
10961
|
+
const fileType = input.fileType ?? void 0;
|
|
10962
|
+
const directory = input.directory ?? void 0;
|
|
10963
|
+
const tokenBudget = input.tokenBudget ?? void 0;
|
|
10964
|
+
if (from && to) {
|
|
10965
|
+
const path17 = await getCallGraphPath(projectRoot3, host, from, to, maxDepth);
|
|
10966
|
+
if (path17.length > 0) {
|
|
10967
|
+
const fitted2 = fitTextToContextBudget(
|
|
10968
|
+
formatCallGraphPath(from, to, path17),
|
|
10969
|
+
tokenBudget
|
|
10970
|
+
);
|
|
10971
|
+
return {
|
|
10972
|
+
text: fitted2.text,
|
|
10973
|
+
details: fittedDetails("path", fitted2)
|
|
10974
|
+
};
|
|
10975
|
+
}
|
|
10976
|
+
const { callers } = await getCallGraphData(projectRoot3, host, {
|
|
10977
|
+
name: to,
|
|
10978
|
+
direction: "callers"
|
|
10979
|
+
});
|
|
10980
|
+
const directEdge = callers.find((edge) => edge.fromSymbolName === from);
|
|
10981
|
+
if (directEdge) {
|
|
10982
|
+
const location = directEdge.fromSymbolFilePath ? ` at ${directEdge.fromSymbolFilePath}:${directEdge.line}` : "";
|
|
10983
|
+
const fitted2 = fitTextToContextBudget(
|
|
10984
|
+
`Direct path: ${from} --${directEdge.callType}--> ${to}${location} (edge is ${directEdge.isResolved ? "resolved" : "unresolved"}).`,
|
|
10985
|
+
tokenBudget
|
|
10986
|
+
);
|
|
10987
|
+
return {
|
|
10988
|
+
text: fitted2.text,
|
|
10989
|
+
details: fittedDetails("direct-edge", fitted2)
|
|
10990
|
+
};
|
|
10991
|
+
}
|
|
10992
|
+
const fitted = fitTextToContextBudget(
|
|
10993
|
+
formatCallGraphPath(from, to, path17),
|
|
10994
|
+
tokenBudget
|
|
10995
|
+
);
|
|
10996
|
+
return {
|
|
10997
|
+
text: fitted.text,
|
|
10998
|
+
details: fittedDetails("path", fitted)
|
|
10999
|
+
};
|
|
11000
|
+
}
|
|
11001
|
+
return resolveSearchContext({ query: input.query, symbol, limit, tokenBudget }, {
|
|
11002
|
+
lookup: (lookupSymbol, retrievalLimit) => implementationLookup(projectRoot3, host, lookupSymbol, {
|
|
11003
|
+
limit: retrievalLimit,
|
|
11004
|
+
fileType,
|
|
11005
|
+
directory
|
|
11006
|
+
}),
|
|
11007
|
+
search: (query, retrievalLimit) => searchCodebase(projectRoot3, host, query, {
|
|
11008
|
+
limit: retrievalLimit,
|
|
11009
|
+
fileType,
|
|
11010
|
+
directory,
|
|
11011
|
+
metadataOnly: true
|
|
11012
|
+
})
|
|
11013
|
+
});
|
|
11014
|
+
}
|
|
11015
|
+
|
|
10601
11016
|
// src/pi-call-graph.ts
|
|
10602
11017
|
import { Type } from "typebox";
|
|
10603
11018
|
var HOST = "pi";
|
|
@@ -10674,58 +11089,33 @@ function codebaseIndexPiExtension(pi) {
|
|
|
10674
11089
|
pi.registerTool({
|
|
10675
11090
|
name: "codebase_context",
|
|
10676
11091
|
label: "Codebase Context",
|
|
10677
|
-
description: "PREFERRED FIRST TOOL for any repository question. Check index_status when freshness is unknown, then use this tool for low-token location discovery and dependency flow.",
|
|
11092
|
+
description: "PREFERRED FIRST TOOL for any repository question. Returns a deduplicated, file-diverse evidence pack within tokenBudget. Check index_status when freshness is unknown, then use this tool for low-token location discovery and dependency flow.",
|
|
10678
11093
|
parameters: Type2.Object({
|
|
10679
11094
|
query: Type2.String({ description: "Natural language description of what code you're trying to locate" }),
|
|
10680
|
-
from: Type2.Optional(Type2.String({ description: "Source symbol when asking for a dependency path." })),
|
|
10681
|
-
to: Type2.Optional(Type2.String({ description: "Target symbol when asking for a dependency path." })),
|
|
10682
|
-
symbol: Type2.Optional(Type2.String({ description: "Exact symbol name for an authoritative definition lookup." })),
|
|
10683
|
-
limit: Type2.Optional(Type2.
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
11095
|
+
from: Type2.Optional(Type2.Union([Type2.String(), Type2.Null()], { description: "Source symbol when asking for a dependency path." })),
|
|
11096
|
+
to: Type2.Optional(Type2.Union([Type2.String(), Type2.Null()], { description: "Target symbol when asking for a dependency path." })),
|
|
11097
|
+
symbol: Type2.Optional(Type2.Union([Type2.String(), Type2.Null()], { description: "Exact symbol name for an authoritative definition lookup." })),
|
|
11098
|
+
limit: Type2.Optional(Type2.Union([
|
|
11099
|
+
Type2.Integer({ minimum: MIN_CONTEXT_RESULT_LIMIT, maximum: MAX_CONTEXT_RESULT_LIMIT }),
|
|
11100
|
+
Type2.Null()
|
|
11101
|
+
], { default: 10, description: `Maximum results (${MIN_CONTEXT_RESULT_LIMIT}-${MAX_CONTEXT_RESULT_LIMIT})` })),
|
|
11102
|
+
maxDepth: Type2.Optional(Type2.Union([
|
|
11103
|
+
Type2.Integer({ minimum: MIN_CONTEXT_PATH_DEPTH, maximum: MAX_CONTEXT_PATH_DEPTH }),
|
|
11104
|
+
Type2.Null()
|
|
11105
|
+
], { default: 10, description: `Maximum call-graph traversal depth (${MIN_CONTEXT_PATH_DEPTH}-${MAX_CONTEXT_PATH_DEPTH})` })),
|
|
11106
|
+
fileType: Type2.Optional(Type2.Union([Type2.String(), Type2.Null()], { description: "Filter by file extension, e.g., ts, py, rs" })),
|
|
11107
|
+
directory: Type2.Optional(Type2.Union([Type2.String(), Type2.Null()], { description: "Filter by directory path" })),
|
|
11108
|
+
tokenBudget: Type2.Optional(Type2.Union([
|
|
11109
|
+
Type2.Integer({ minimum: MIN_CONTEXT_PACK_TOKEN_BUDGET, maximum: MAX_CONTEXT_PACK_TOKEN_BUDGET }),
|
|
11110
|
+
Type2.Null()
|
|
11111
|
+
], {
|
|
11112
|
+
default: DEFAULT_CONTEXT_PACK_TOKEN_BUDGET,
|
|
11113
|
+
description: `Maximum response tokens (${MIN_CONTEXT_PACK_TOKEN_BUDGET}-${MAX_CONTEXT_PACK_TOKEN_BUDGET})`
|
|
11114
|
+
}))
|
|
10687
11115
|
}),
|
|
10688
11116
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
10689
|
-
const
|
|
10690
|
-
|
|
10691
|
-
const path17 = await getCallGraphPath(root, HOST2, params.from, params.to, params.maxDepth);
|
|
10692
|
-
if (path17.length > 0) {
|
|
10693
|
-
return text2(formatCallGraphPath(params.from, params.to, path17), path17);
|
|
10694
|
-
}
|
|
10695
|
-
const { callers } = await getCallGraphData(root, HOST2, {
|
|
10696
|
-
name: params.to,
|
|
10697
|
-
direction: "callers"
|
|
10698
|
-
});
|
|
10699
|
-
const directEdge = callers.find((edge) => edge.fromSymbolName === params.from);
|
|
10700
|
-
if (directEdge) {
|
|
10701
|
-
const location = directEdge.fromSymbolFilePath ? ` at ${directEdge.fromSymbolFilePath}:${directEdge.line}` : "";
|
|
10702
|
-
return text2(
|
|
10703
|
-
`Direct path: ${params.from} --${directEdge.callType}--> ${params.to}${location} (edge is ${directEdge.isResolved ? "resolved" : "unresolved"}).`,
|
|
10704
|
-
path17
|
|
10705
|
-
);
|
|
10706
|
-
}
|
|
10707
|
-
return text2(formatCallGraphPath(params.from, params.to, path17), path17);
|
|
10708
|
-
}
|
|
10709
|
-
if (params.symbol) {
|
|
10710
|
-
const results2 = await implementationLookup(root, HOST2, params.symbol, {
|
|
10711
|
-
limit: params.limit ?? 10,
|
|
10712
|
-
fileType: params.fileType,
|
|
10713
|
-
directory: params.directory
|
|
10714
|
-
});
|
|
10715
|
-
return text2(formatDefinitionLookup(results2, params.symbol), results2);
|
|
10716
|
-
}
|
|
10717
|
-
const results = await searchCodebase(root, HOST2, params.query, {
|
|
10718
|
-
limit: params.limit ?? 10,
|
|
10719
|
-
fileType: params.fileType,
|
|
10720
|
-
directory: params.directory,
|
|
10721
|
-
metadataOnly: true
|
|
10722
|
-
});
|
|
10723
|
-
if (results.length === 0) {
|
|
10724
|
-
return text2("No matching code found. Try a different query or run index_status/index_codebase first.");
|
|
10725
|
-
}
|
|
10726
|
-
return text2(`Found ${results.length} locations for "${params.query}":
|
|
10727
|
-
|
|
10728
|
-
${formatCodebasePeek(results)}`, results);
|
|
11117
|
+
const result = await resolveCodebaseContext(projectRoot2(ctx), HOST2, params);
|
|
11118
|
+
return text2(result.text, result.details);
|
|
10729
11119
|
}
|
|
10730
11120
|
});
|
|
10731
11121
|
pi.registerTool({
|