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/index.cjs
CHANGED
|
@@ -1708,7 +1708,12 @@ function sameReclaimOwner(left, right) {
|
|
|
1708
1708
|
}
|
|
1709
1709
|
function publishJsonDirectory(finalPath, value) {
|
|
1710
1710
|
const candidatePath = `${finalPath}.candidate.${process.pid}.${(0, import_crypto.randomUUID)()}`;
|
|
1711
|
-
|
|
1711
|
+
try {
|
|
1712
|
+
(0, import_fs5.mkdirSync)(candidatePath, { mode: 448 });
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
1715
|
+
throw error;
|
|
1716
|
+
}
|
|
1712
1717
|
try {
|
|
1713
1718
|
(0, import_fs5.writeFileSync)(path7.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
|
|
1714
1719
|
encoding: "utf-8",
|
|
@@ -1721,8 +1726,12 @@ function publishJsonDirectory(finalPath, value) {
|
|
|
1721
1726
|
return true;
|
|
1722
1727
|
} catch (error) {
|
|
1723
1728
|
if ((0, import_fs5.existsSync)(finalPath)) return false;
|
|
1729
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
1724
1730
|
throw error;
|
|
1725
1731
|
}
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
1734
|
+
throw error;
|
|
1726
1735
|
} finally {
|
|
1727
1736
|
if ((0, import_fs5.existsSync)(candidatePath)) (0, import_fs5.rmSync)(candidatePath, { recursive: true, force: true });
|
|
1728
1737
|
}
|
|
@@ -6579,6 +6588,27 @@ function mergeTieredResults(symbolLane, hybridLane, limit) {
|
|
|
6579
6588
|
}
|
|
6580
6589
|
return out;
|
|
6581
6590
|
}
|
|
6591
|
+
function selectChunksWithFileCoverage(chunks, limit) {
|
|
6592
|
+
if (limit <= 0 || chunks.length === 0) {
|
|
6593
|
+
return [];
|
|
6594
|
+
}
|
|
6595
|
+
if (chunks.length <= limit) {
|
|
6596
|
+
return chunks;
|
|
6597
|
+
}
|
|
6598
|
+
if (limit === 1) {
|
|
6599
|
+
return [chunks[Math.floor((chunks.length - 1) / 2)]];
|
|
6600
|
+
}
|
|
6601
|
+
const selected = [];
|
|
6602
|
+
for (let index = 0; index < limit; index++) {
|
|
6603
|
+
const sourceIndex = Math.round(index * (chunks.length - 1) / (limit - 1));
|
|
6604
|
+
selected.push(chunks[sourceIndex]);
|
|
6605
|
+
}
|
|
6606
|
+
return selected;
|
|
6607
|
+
}
|
|
6608
|
+
function selectIndexableChunks(chunks, limit, semanticOnly) {
|
|
6609
|
+
const indexableChunks = semanticOnly ? chunks.filter((chunk) => chunk.chunkType !== "other") : chunks;
|
|
6610
|
+
return selectChunksWithFileCoverage(indexableChunks, limit);
|
|
6611
|
+
}
|
|
6582
6612
|
function matchesSearchFilters(candidate, options, minScore) {
|
|
6583
6613
|
if (candidate.score < minScore) return false;
|
|
6584
6614
|
if (options?.fileType) {
|
|
@@ -8317,7 +8347,6 @@ var Indexer = class {
|
|
|
8317
8347
|
const relativePath = path13.relative(this.projectRoot, parsed.path);
|
|
8318
8348
|
stats.parseFailures.push(relativePath);
|
|
8319
8349
|
}
|
|
8320
|
-
let fileChunkCount = 0;
|
|
8321
8350
|
let chunksToProcess = parsed.chunks;
|
|
8322
8351
|
if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
|
|
8323
8352
|
const changedFile = changedFiles.find((f) => f.path === parsed.path);
|
|
@@ -8326,13 +8355,12 @@ var Indexer = class {
|
|
|
8326
8355
|
chunksToProcess = textChunks;
|
|
8327
8356
|
}
|
|
8328
8357
|
}
|
|
8358
|
+
chunksToProcess = selectIndexableChunks(
|
|
8359
|
+
chunksToProcess,
|
|
8360
|
+
this.config.indexing.maxChunksPerFile,
|
|
8361
|
+
this.config.indexing.semanticOnly
|
|
8362
|
+
);
|
|
8329
8363
|
for (const chunk of chunksToProcess) {
|
|
8330
|
-
if (fileChunkCount >= this.config.indexing.maxChunksPerFile) {
|
|
8331
|
-
break;
|
|
8332
|
-
}
|
|
8333
|
-
if (this.config.indexing.semanticOnly && chunk.chunkType === "other") {
|
|
8334
|
-
continue;
|
|
8335
|
-
}
|
|
8336
8364
|
const id = generateChunkId(parsed.path, chunk);
|
|
8337
8365
|
const contentHash = generateChunkHash(chunk);
|
|
8338
8366
|
const existingContentHash = existingChunks.get(id);
|
|
@@ -8356,7 +8384,6 @@ var Indexer = class {
|
|
|
8356
8384
|
blameSummary: blameMetadata.blameSummary
|
|
8357
8385
|
});
|
|
8358
8386
|
if (existingContentHash === contentHash) {
|
|
8359
|
-
fileChunkCount++;
|
|
8360
8387
|
continue;
|
|
8361
8388
|
}
|
|
8362
8389
|
const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text) => ({
|
|
@@ -8381,7 +8408,6 @@ var Indexer = class {
|
|
|
8381
8408
|
contentHash,
|
|
8382
8409
|
metadata
|
|
8383
8410
|
});
|
|
8384
|
-
fileChunkCount++;
|
|
8385
8411
|
}
|
|
8386
8412
|
}
|
|
8387
8413
|
const retryableFailedChunks = this.collectRetryableFailedChunks(
|
|
@@ -9972,6 +9998,159 @@ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
|
|
|
9972
9998
|
}
|
|
9973
9999
|
|
|
9974
10000
|
// src/tools/utils.ts
|
|
10001
|
+
var import_tiktoken = require("tiktoken");
|
|
10002
|
+
var MIN_CONTEXT_PACK_TOKEN_BUDGET = 128;
|
|
10003
|
+
var MAX_CONTEXT_PACK_TOKEN_BUDGET = 4e3;
|
|
10004
|
+
var DEFAULT_CONTEXT_PACK_TOKEN_BUDGET = 1200;
|
|
10005
|
+
var CONTEXT_TOKENIZER = (0, import_tiktoken.get_encoding)("cl100k_base");
|
|
10006
|
+
function clampContextPackTokenBudget(tokenBudget) {
|
|
10007
|
+
if (tokenBudget === void 0 || !Number.isFinite(tokenBudget)) {
|
|
10008
|
+
return DEFAULT_CONTEXT_PACK_TOKEN_BUDGET;
|
|
10009
|
+
}
|
|
10010
|
+
return Math.min(
|
|
10011
|
+
MAX_CONTEXT_PACK_TOKEN_BUDGET,
|
|
10012
|
+
Math.max(MIN_CONTEXT_PACK_TOKEN_BUDGET, Math.floor(tokenBudget))
|
|
10013
|
+
);
|
|
10014
|
+
}
|
|
10015
|
+
function countContextTokens(text) {
|
|
10016
|
+
return CONTEXT_TOKENIZER.encode(text).length;
|
|
10017
|
+
}
|
|
10018
|
+
function fitTextToContextBudget(text, tokenBudget) {
|
|
10019
|
+
const normalizedBudget = clampContextPackTokenBudget(tokenBudget);
|
|
10020
|
+
const tokenEstimate = countContextTokens(text);
|
|
10021
|
+
if (tokenEstimate <= normalizedBudget) {
|
|
10022
|
+
return {
|
|
10023
|
+
text,
|
|
10024
|
+
tokenBudget: normalizedBudget,
|
|
10025
|
+
tokenEstimate,
|
|
10026
|
+
truncated: false
|
|
10027
|
+
};
|
|
10028
|
+
}
|
|
10029
|
+
const suffix = "\n...[truncated to context token budget]";
|
|
10030
|
+
const codePoints = Array.from(text);
|
|
10031
|
+
let low = 0;
|
|
10032
|
+
let high = codePoints.length;
|
|
10033
|
+
while (low < high) {
|
|
10034
|
+
const middle = Math.ceil((low + high) / 2);
|
|
10035
|
+
const candidate = `${codePoints.slice(0, middle).join("").trimEnd()}${suffix}`;
|
|
10036
|
+
if (countContextTokens(candidate) <= normalizedBudget) low = middle;
|
|
10037
|
+
else high = middle - 1;
|
|
10038
|
+
}
|
|
10039
|
+
const fitted = `${codePoints.slice(0, low).join("").trimEnd()}${suffix}`;
|
|
10040
|
+
return {
|
|
10041
|
+
text: fitted,
|
|
10042
|
+
tokenBudget: normalizedBudget,
|
|
10043
|
+
tokenEstimate: countContextTokens(fitted),
|
|
10044
|
+
truncated: true
|
|
10045
|
+
};
|
|
10046
|
+
}
|
|
10047
|
+
function normalizedLineRange(result) {
|
|
10048
|
+
return result.startLine <= result.endLine ? { start: result.startLine, end: result.endLine } : { start: result.endLine, end: result.startLine };
|
|
10049
|
+
}
|
|
10050
|
+
function rankContextCandidates(results) {
|
|
10051
|
+
return results.map((result, originalIndex) => ({ result, originalIndex })).sort((left, right) => right.result.score - left.result.score || left.originalIndex - right.originalIndex);
|
|
10052
|
+
}
|
|
10053
|
+
function deduplicateContextCandidates(candidates) {
|
|
10054
|
+
const acceptedByFile = /* @__PURE__ */ new Map();
|
|
10055
|
+
const deduplicated = [];
|
|
10056
|
+
for (const { result } of candidates) {
|
|
10057
|
+
const range = normalizedLineRange(result);
|
|
10058
|
+
const accepted = acceptedByFile.get(result.filePath) ?? [];
|
|
10059
|
+
if (accepted.some((item) => item.start <= range.end && range.start <= item.end)) {
|
|
10060
|
+
continue;
|
|
10061
|
+
}
|
|
10062
|
+
accepted.push(range);
|
|
10063
|
+
acceptedByFile.set(result.filePath, accepted);
|
|
10064
|
+
deduplicated.push(result);
|
|
10065
|
+
}
|
|
10066
|
+
return deduplicated;
|
|
10067
|
+
}
|
|
10068
|
+
function diversifyContextCandidates(results) {
|
|
10069
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
10070
|
+
for (const result of results) {
|
|
10071
|
+
const bucket = byFile.get(result.filePath) ?? [];
|
|
10072
|
+
bucket.push(result);
|
|
10073
|
+
byFile.set(result.filePath, bucket);
|
|
10074
|
+
}
|
|
10075
|
+
const files = [...byFile.keys()];
|
|
10076
|
+
const diversified = [];
|
|
10077
|
+
for (let depth = 0; diversified.length < results.length; depth += 1) {
|
|
10078
|
+
for (const file of files) {
|
|
10079
|
+
const result = byFile.get(file)?.[depth];
|
|
10080
|
+
if (result) diversified.push(result);
|
|
10081
|
+
}
|
|
10082
|
+
}
|
|
10083
|
+
return diversified;
|
|
10084
|
+
}
|
|
10085
|
+
function compactEvidenceValue(value, maxChars) {
|
|
10086
|
+
const characters = [...value];
|
|
10087
|
+
if (characters.length <= maxChars) return value;
|
|
10088
|
+
return `\u2026${characters.slice(-(maxChars - 1)).join("")}`;
|
|
10089
|
+
}
|
|
10090
|
+
function formatContextEvidence(result, index) {
|
|
10091
|
+
const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
|
|
10092
|
+
const path24 = compactEvidenceValue(result.filePath, 120);
|
|
10093
|
+
return `[${index}] ${result.chunkType}${symbol} in ${path24}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
|
|
10094
|
+
}
|
|
10095
|
+
function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount) {
|
|
10096
|
+
const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
|
|
10097
|
+
const notes = [];
|
|
10098
|
+
if (duplicateCount > 0) notes.push(`${duplicateCount} overlapping duplicate${duplicateCount === 1 ? "" : "s"} removed`);
|
|
10099
|
+
if (limitOmittedCount > 0) notes.push(`${limitOmittedCount} additional result${limitOmittedCount === 1 ? "" : "s"} excluded by result limit`);
|
|
10100
|
+
if (budgetOmittedCount > 0) notes.push(`${budgetOmittedCount} additional result${budgetOmittedCount === 1 ? "" : "s"} omitted by token budget`);
|
|
10101
|
+
const footer = notes.length > 0 ? `Selected ${selected.length} of ${candidateCount} candidates; ${notes.join("; ")}.` : `Selected ${selected.length} of ${candidateCount} candidates.`;
|
|
10102
|
+
return `${heading}
|
|
10103
|
+
|
|
10104
|
+
${lines.join("\n")}
|
|
10105
|
+
|
|
10106
|
+
${footer}`;
|
|
10107
|
+
}
|
|
10108
|
+
function buildContextPack(results, options = {}) {
|
|
10109
|
+
const requestedTokenBudget = options.tokenBudget ?? DEFAULT_CONTEXT_PACK_TOKEN_BUDGET;
|
|
10110
|
+
const tokenBudget = clampContextPackTokenBudget(options.tokenBudget);
|
|
10111
|
+
const heading = compactEvidenceValue(options.heading?.trim() || "Codebase evidence", 160);
|
|
10112
|
+
const maxResults = Math.max(0, Math.floor(options.maxResults ?? results.length));
|
|
10113
|
+
const candidateCount = results.length;
|
|
10114
|
+
const deduplicated = deduplicateContextCandidates(rankContextCandidates(results));
|
|
10115
|
+
const diversified = diversifyContextCandidates(deduplicated);
|
|
10116
|
+
const duplicateCount = candidateCount - deduplicated.length;
|
|
10117
|
+
const selectable = diversified.slice(0, maxResults);
|
|
10118
|
+
const limitOmittedCount = deduplicated.length - selectable.length;
|
|
10119
|
+
let selected = [];
|
|
10120
|
+
let text = formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, selectable.length);
|
|
10121
|
+
for (let count = 1; count <= selectable.length; count += 1) {
|
|
10122
|
+
const candidateSelection = selectable.slice(0, count);
|
|
10123
|
+
const budgetOmittedCount2 = selectable.length - candidateSelection.length;
|
|
10124
|
+
const candidateText = formatContextPack(
|
|
10125
|
+
heading,
|
|
10126
|
+
candidateSelection,
|
|
10127
|
+
candidateCount,
|
|
10128
|
+
duplicateCount,
|
|
10129
|
+
limitOmittedCount,
|
|
10130
|
+
budgetOmittedCount2
|
|
10131
|
+
);
|
|
10132
|
+
if (countContextTokens(candidateText) > tokenBudget) break;
|
|
10133
|
+
selected = candidateSelection;
|
|
10134
|
+
text = candidateText;
|
|
10135
|
+
}
|
|
10136
|
+
const fitted = fitTextToContextBudget(text, tokenBudget);
|
|
10137
|
+
const budgetOmittedCount = selectable.length - selected.length;
|
|
10138
|
+
const omittedCount = candidateCount - selected.length;
|
|
10139
|
+
return {
|
|
10140
|
+
requestedTokenBudget,
|
|
10141
|
+
tokenBudget,
|
|
10142
|
+
text: fitted.text,
|
|
10143
|
+
tokenEstimate: fitted.tokenEstimate,
|
|
10144
|
+
results: selected,
|
|
10145
|
+
candidateCount,
|
|
10146
|
+
deduplicatedCount: deduplicated.length,
|
|
10147
|
+
selectedCount: selected.length,
|
|
10148
|
+
omittedCount,
|
|
10149
|
+
duplicateCount,
|
|
10150
|
+
limitOmittedCount,
|
|
10151
|
+
budgetOmittedCount
|
|
10152
|
+
};
|
|
10153
|
+
}
|
|
9975
10154
|
var MAX_CONTENT_LINES = 30;
|
|
9976
10155
|
function truncateContent(content) {
|
|
9977
10156
|
const lines = content.split("\n");
|
|
@@ -12801,6 +12980,253 @@ var pr_impact = (0, import_plugin.tool)({
|
|
|
12801
12980
|
}
|
|
12802
12981
|
});
|
|
12803
12982
|
|
|
12983
|
+
// src/tools/symbol-inference.ts
|
|
12984
|
+
var IDENTIFIER_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
12985
|
+
var QUOTED_BACKTICK_RE = /`([^`]+)`/g;
|
|
12986
|
+
var QUOTED_SINGLE_RE = /'([^'\\]+)'/g;
|
|
12987
|
+
var QUOTED_DOUBLE_RE = /"([^"]+)"/g;
|
|
12988
|
+
var SYMBOL_LIKE_RE = /^(?:[A-Za-z_$][A-Za-z0-9_$]*)$/;
|
|
12989
|
+
var CAMEL_CASE_RE = /^[a-z_][A-Za-z0-9_$]*[A-Z][A-Za-z0-9_$]*$/;
|
|
12990
|
+
var PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9_$]*$/;
|
|
12991
|
+
var SNAKE_CASE_RE = /^[a-z][a-z0-9_]*_[a-z0-9_]+$/;
|
|
12992
|
+
var DEFINITION_INTENT_RE = /\b(where|defined|definition|define|declaration|symbol|function|method|class|interface|type)\b/i;
|
|
12993
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
12994
|
+
"a",
|
|
12995
|
+
"an",
|
|
12996
|
+
"and",
|
|
12997
|
+
"are",
|
|
12998
|
+
"at",
|
|
12999
|
+
"for",
|
|
13000
|
+
"find",
|
|
13001
|
+
"how",
|
|
13002
|
+
"i",
|
|
13003
|
+
"in",
|
|
13004
|
+
"is",
|
|
13005
|
+
"it",
|
|
13006
|
+
"of",
|
|
13007
|
+
"on",
|
|
13008
|
+
"that",
|
|
13009
|
+
"the",
|
|
13010
|
+
"definition",
|
|
13011
|
+
"show",
|
|
13012
|
+
"to",
|
|
13013
|
+
"where",
|
|
13014
|
+
"which",
|
|
13015
|
+
"what",
|
|
13016
|
+
"you",
|
|
13017
|
+
"your",
|
|
13018
|
+
"with"
|
|
13019
|
+
]);
|
|
13020
|
+
function stripCallSuffix(token) {
|
|
13021
|
+
return token.replace(/\(\s*\)$/, "");
|
|
13022
|
+
}
|
|
13023
|
+
function isLikelySymbolName(token) {
|
|
13024
|
+
if (!SYMBOL_LIKE_RE.test(token)) {
|
|
13025
|
+
return false;
|
|
13026
|
+
}
|
|
13027
|
+
if (STOP_WORDS.has(token.toLowerCase())) {
|
|
13028
|
+
return false;
|
|
13029
|
+
}
|
|
13030
|
+
return CAMEL_CASE_RE.test(token) || PASCAL_CASE_RE.test(token) || SNAKE_CASE_RE.test(token);
|
|
13031
|
+
}
|
|
13032
|
+
function extractQuotedIdentifiers(query) {
|
|
13033
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
13034
|
+
for (const match of query.matchAll(QUOTED_BACKTICK_RE)) {
|
|
13035
|
+
const candidate = stripCallSuffix(match[1].trim());
|
|
13036
|
+
if (candidate && isLikelySymbolName(candidate)) {
|
|
13037
|
+
identifiers.add(candidate);
|
|
13038
|
+
}
|
|
13039
|
+
}
|
|
13040
|
+
for (const match of query.matchAll(QUOTED_SINGLE_RE)) {
|
|
13041
|
+
const candidate = stripCallSuffix(match[1].trim());
|
|
13042
|
+
if (candidate && isLikelySymbolName(candidate)) {
|
|
13043
|
+
identifiers.add(candidate);
|
|
13044
|
+
}
|
|
13045
|
+
}
|
|
13046
|
+
for (const match of query.matchAll(QUOTED_DOUBLE_RE)) {
|
|
13047
|
+
const candidate = stripCallSuffix(match[1].trim());
|
|
13048
|
+
if (candidate && isLikelySymbolName(candidate)) {
|
|
13049
|
+
identifiers.add(candidate);
|
|
13050
|
+
}
|
|
13051
|
+
}
|
|
13052
|
+
return [...identifiers];
|
|
13053
|
+
}
|
|
13054
|
+
function extractBareIdentifiers(query) {
|
|
13055
|
+
const unquoted = query.replace(QUOTED_BACKTICK_RE, " ").replace(QUOTED_SINGLE_RE, " ").replace(QUOTED_DOUBLE_RE, " ");
|
|
13056
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
13057
|
+
for (const match of unquoted.matchAll(IDENTIFIER_RE)) {
|
|
13058
|
+
const candidate = stripCallSuffix(match[0]);
|
|
13059
|
+
if (isLikelySymbolName(candidate)) {
|
|
13060
|
+
identifiers.add(candidate);
|
|
13061
|
+
}
|
|
13062
|
+
}
|
|
13063
|
+
return [...identifiers];
|
|
13064
|
+
}
|
|
13065
|
+
function isSingleMeaningfulToken(query, symbol) {
|
|
13066
|
+
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));
|
|
13067
|
+
return tokens.length === 1 && tokens[0] === symbol.toLowerCase();
|
|
13068
|
+
}
|
|
13069
|
+
function inferExactSymbolFromQuery(query) {
|
|
13070
|
+
const quoted = extractQuotedIdentifiers(query);
|
|
13071
|
+
if (quoted.length === 1) {
|
|
13072
|
+
return quoted[0];
|
|
13073
|
+
}
|
|
13074
|
+
if (quoted.length > 1) {
|
|
13075
|
+
return void 0;
|
|
13076
|
+
}
|
|
13077
|
+
const candidates = extractBareIdentifiers(query);
|
|
13078
|
+
if (candidates.length !== 1) {
|
|
13079
|
+
return void 0;
|
|
13080
|
+
}
|
|
13081
|
+
const candidate = candidates[0];
|
|
13082
|
+
if (DEFINITION_INTENT_RE.test(query)) {
|
|
13083
|
+
return candidate;
|
|
13084
|
+
}
|
|
13085
|
+
if (isSingleMeaningfulToken(query, candidate)) {
|
|
13086
|
+
return candidate;
|
|
13087
|
+
}
|
|
13088
|
+
return void 0;
|
|
13089
|
+
}
|
|
13090
|
+
|
|
13091
|
+
// src/tools/context.ts
|
|
13092
|
+
var MIN_CONTEXT_RESULT_LIMIT = 1;
|
|
13093
|
+
var MAX_CONTEXT_RESULT_LIMIT = 100;
|
|
13094
|
+
var MIN_CONTEXT_PATH_DEPTH = 1;
|
|
13095
|
+
var MAX_CONTEXT_PATH_DEPTH = 100;
|
|
13096
|
+
function locations(results) {
|
|
13097
|
+
return results.map((result) => ({
|
|
13098
|
+
filePath: result.filePath,
|
|
13099
|
+
startLine: result.startLine,
|
|
13100
|
+
endLine: result.endLine,
|
|
13101
|
+
score: result.score,
|
|
13102
|
+
chunkType: result.chunkType,
|
|
13103
|
+
name: result.name
|
|
13104
|
+
}));
|
|
13105
|
+
}
|
|
13106
|
+
function packedResult(route, routedQuery, pack) {
|
|
13107
|
+
return {
|
|
13108
|
+
text: pack.text,
|
|
13109
|
+
details: {
|
|
13110
|
+
route,
|
|
13111
|
+
routedQuery,
|
|
13112
|
+
tokenBudget: pack.tokenBudget,
|
|
13113
|
+
tokenEstimate: pack.tokenEstimate,
|
|
13114
|
+
candidateCount: pack.candidateCount,
|
|
13115
|
+
deduplicatedCount: pack.deduplicatedCount,
|
|
13116
|
+
selectedCount: pack.selectedCount,
|
|
13117
|
+
omittedCount: pack.omittedCount,
|
|
13118
|
+
duplicateCount: pack.duplicateCount,
|
|
13119
|
+
limitOmittedCount: pack.limitOmittedCount,
|
|
13120
|
+
budgetOmittedCount: pack.budgetOmittedCount,
|
|
13121
|
+
results: locations(pack.results)
|
|
13122
|
+
}
|
|
13123
|
+
};
|
|
13124
|
+
}
|
|
13125
|
+
async function resolveSearchContext(input, operations) {
|
|
13126
|
+
const explicitSymbol = input.symbol ?? void 0;
|
|
13127
|
+
const limit = input.limit ?? 10;
|
|
13128
|
+
const tokenBudget = input.tokenBudget ?? void 0;
|
|
13129
|
+
const lookupSymbol = explicitSymbol ?? inferExactSymbolFromQuery(input.query);
|
|
13130
|
+
if (lookupSymbol) {
|
|
13131
|
+
const definitions = await operations.lookup(lookupSymbol, MAX_CONTEXT_RESULT_LIMIT);
|
|
13132
|
+
if (definitions.length > 0) {
|
|
13133
|
+
return packedResult("definition", lookupSymbol, buildContextPack(definitions, {
|
|
13134
|
+
tokenBudget,
|
|
13135
|
+
maxResults: limit,
|
|
13136
|
+
heading: `Definition evidence for ${JSON.stringify(lookupSymbol)}`
|
|
13137
|
+
}));
|
|
13138
|
+
}
|
|
13139
|
+
if (explicitSymbol) {
|
|
13140
|
+
const fitted = fitTextToContextBudget(
|
|
13141
|
+
formatDefinitionLookup(definitions, lookupSymbol),
|
|
13142
|
+
tokenBudget
|
|
13143
|
+
);
|
|
13144
|
+
return { text: fitted.text };
|
|
13145
|
+
}
|
|
13146
|
+
}
|
|
13147
|
+
const results = await operations.search(input.query, MAX_CONTEXT_RESULT_LIMIT);
|
|
13148
|
+
if (results.length === 0) {
|
|
13149
|
+
const fitted = fitTextToContextBudget(
|
|
13150
|
+
"No matching code found. Try a different query or run index_codebase first.",
|
|
13151
|
+
tokenBudget
|
|
13152
|
+
);
|
|
13153
|
+
return { text: fitted.text };
|
|
13154
|
+
}
|
|
13155
|
+
return packedResult("conceptual", input.query, buildContextPack(results, {
|
|
13156
|
+
tokenBudget,
|
|
13157
|
+
maxResults: limit,
|
|
13158
|
+
heading: `Codebase evidence for ${JSON.stringify(input.query)}`
|
|
13159
|
+
}));
|
|
13160
|
+
}
|
|
13161
|
+
function fittedDetails(route, fitted) {
|
|
13162
|
+
return {
|
|
13163
|
+
route,
|
|
13164
|
+
tokenBudget: fitted.tokenBudget,
|
|
13165
|
+
tokenEstimate: fitted.tokenEstimate,
|
|
13166
|
+
truncated: fitted.truncated
|
|
13167
|
+
};
|
|
13168
|
+
}
|
|
13169
|
+
async function resolveCodebaseContext(projectRoot, host, input) {
|
|
13170
|
+
const from = input.from ?? void 0;
|
|
13171
|
+
const to = input.to ?? void 0;
|
|
13172
|
+
const symbol = input.symbol ?? void 0;
|
|
13173
|
+
const limit = input.limit ?? 10;
|
|
13174
|
+
const maxDepth = input.maxDepth ?? 10;
|
|
13175
|
+
const fileType = input.fileType ?? void 0;
|
|
13176
|
+
const directory = input.directory ?? void 0;
|
|
13177
|
+
const tokenBudget = input.tokenBudget ?? void 0;
|
|
13178
|
+
if (from && to) {
|
|
13179
|
+
const path24 = await getCallGraphPath(projectRoot, host, from, to, maxDepth);
|
|
13180
|
+
if (path24.length > 0) {
|
|
13181
|
+
const fitted2 = fitTextToContextBudget(
|
|
13182
|
+
formatCallGraphPath(from, to, path24),
|
|
13183
|
+
tokenBudget
|
|
13184
|
+
);
|
|
13185
|
+
return {
|
|
13186
|
+
text: fitted2.text,
|
|
13187
|
+
details: fittedDetails("path", fitted2)
|
|
13188
|
+
};
|
|
13189
|
+
}
|
|
13190
|
+
const { callers } = await getCallGraphData(projectRoot, host, {
|
|
13191
|
+
name: to,
|
|
13192
|
+
direction: "callers"
|
|
13193
|
+
});
|
|
13194
|
+
const directEdge = callers.find((edge) => edge.fromSymbolName === from);
|
|
13195
|
+
if (directEdge) {
|
|
13196
|
+
const location = directEdge.fromSymbolFilePath ? ` at ${directEdge.fromSymbolFilePath}:${directEdge.line}` : "";
|
|
13197
|
+
const fitted2 = fitTextToContextBudget(
|
|
13198
|
+
`Direct path: ${from} --${directEdge.callType}--> ${to}${location} (edge is ${directEdge.isResolved ? "resolved" : "unresolved"}).`,
|
|
13199
|
+
tokenBudget
|
|
13200
|
+
);
|
|
13201
|
+
return {
|
|
13202
|
+
text: fitted2.text,
|
|
13203
|
+
details: fittedDetails("direct-edge", fitted2)
|
|
13204
|
+
};
|
|
13205
|
+
}
|
|
13206
|
+
const fitted = fitTextToContextBudget(
|
|
13207
|
+
formatCallGraphPath(from, to, path24),
|
|
13208
|
+
tokenBudget
|
|
13209
|
+
);
|
|
13210
|
+
return {
|
|
13211
|
+
text: fitted.text,
|
|
13212
|
+
details: fittedDetails("path", fitted)
|
|
13213
|
+
};
|
|
13214
|
+
}
|
|
13215
|
+
return resolveSearchContext({ query: input.query, symbol, limit, tokenBudget }, {
|
|
13216
|
+
lookup: (lookupSymbol, retrievalLimit) => implementationLookup(projectRoot, host, lookupSymbol, {
|
|
13217
|
+
limit: retrievalLimit,
|
|
13218
|
+
fileType,
|
|
13219
|
+
directory
|
|
13220
|
+
}),
|
|
13221
|
+
search: (query, retrievalLimit) => searchCodebase(projectRoot, host, query, {
|
|
13222
|
+
limit: retrievalLimit,
|
|
13223
|
+
fileType,
|
|
13224
|
+
directory,
|
|
13225
|
+
metadataOnly: true
|
|
13226
|
+
})
|
|
13227
|
+
});
|
|
13228
|
+
}
|
|
13229
|
+
|
|
12804
13230
|
// src/tools/index.ts
|
|
12805
13231
|
var import_fs11 = require("fs");
|
|
12806
13232
|
var os5 = __toESM(require("os"), 1);
|
|
@@ -13532,6 +13958,23 @@ function initializeTools2(projectRoot, config) {
|
|
|
13532
13958
|
function getIndexerForProject2(directory) {
|
|
13533
13959
|
return getIndexerForProject(directory, DEFAULT_HOST);
|
|
13534
13960
|
}
|
|
13961
|
+
var codebase_context = (0, import_plugin2.tool)({
|
|
13962
|
+
description: "PREFERRED FIRST TOOL for repository questions. Returns a deduplicated, file-diverse evidence pack within tokenBudget. Provide from and to for dependency paths, symbol for definitions, or only query for conceptual discovery.",
|
|
13963
|
+
args: {
|
|
13964
|
+
query: z2.string().describe("The repository question or behavior to locate"),
|
|
13965
|
+
from: z2.string().nullable().optional().describe("Source symbol for a dependency path"),
|
|
13966
|
+
to: z2.string().nullable().optional().describe("Target symbol for a dependency path"),
|
|
13967
|
+
symbol: z2.string().nullable().optional().describe("Exact symbol for authoritative definition lookup"),
|
|
13968
|
+
limit: z2.number().int().min(MIN_CONTEXT_RESULT_LIMIT).max(MAX_CONTEXT_RESULT_LIMIT).nullable().optional().default(10).describe(`Maximum number of results (${MIN_CONTEXT_RESULT_LIMIT}-${MAX_CONTEXT_RESULT_LIMIT})`),
|
|
13969
|
+
maxDepth: z2.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})`),
|
|
13970
|
+
fileType: z2.string().nullable().optional().describe("Filter by file extension"),
|
|
13971
|
+
directory: z2.string().nullable().optional().describe("Filter by directory path"),
|
|
13972
|
+
tokenBudget: z2.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})`)
|
|
13973
|
+
},
|
|
13974
|
+
async execute(args, context) {
|
|
13975
|
+
return (await resolveCodebaseContext(context?.worktree, DEFAULT_HOST, args)).text;
|
|
13976
|
+
}
|
|
13977
|
+
});
|
|
13535
13978
|
var codebase_peek = (0, import_plugin2.tool)({
|
|
13536
13979
|
description: "Quick lookup of code locations by meaning. Returns only metadata (file, line, name, type) WITHOUT code content. Use this first to find WHERE code is, then use Read tool to examine specific files. Saves tokens by not returning full code blocks. Best for: discovery, navigation, finding multiple related locations.",
|
|
13537
13980
|
args: {
|
|
@@ -14215,6 +14658,7 @@ var plugin = async ({ directory, worktree }) => {
|
|
|
14215
14658
|
}
|
|
14216
14659
|
return {
|
|
14217
14660
|
tool: {
|
|
14661
|
+
codebase_context,
|
|
14218
14662
|
codebase_search,
|
|
14219
14663
|
codebase_peek,
|
|
14220
14664
|
index_codebase,
|