opencode-codebase-index 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -3
- package/commands/call-graph.md +9 -5
- package/commands/pr-impact.md +23 -0
- package/dist/cli.cjs +570 -162
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +570 -162
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +745 -328
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +745 -328
- package/dist/index.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.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +2 -2
- package/skill/SKILL.md +6 -3
package/dist/cli.cjs
CHANGED
|
@@ -491,7 +491,7 @@ var require_ignore = __commonJS({
|
|
|
491
491
|
// path matching.
|
|
492
492
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
493
493
|
// @returns {TestResult} true if a file is ignored
|
|
494
|
-
test(
|
|
494
|
+
test(path19, checkUnignored, mode) {
|
|
495
495
|
let ignored = false;
|
|
496
496
|
let unignored = false;
|
|
497
497
|
let matchedRule;
|
|
@@ -500,7 +500,7 @@ var require_ignore = __commonJS({
|
|
|
500
500
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
501
501
|
return;
|
|
502
502
|
}
|
|
503
|
-
const matched = rule[mode].test(
|
|
503
|
+
const matched = rule[mode].test(path19);
|
|
504
504
|
if (!matched) {
|
|
505
505
|
return;
|
|
506
506
|
}
|
|
@@ -521,17 +521,17 @@ var require_ignore = __commonJS({
|
|
|
521
521
|
var throwError = (message, Ctor) => {
|
|
522
522
|
throw new Ctor(message);
|
|
523
523
|
};
|
|
524
|
-
var checkPath = (
|
|
525
|
-
if (!isString(
|
|
524
|
+
var checkPath = (path19, originalPath, doThrow) => {
|
|
525
|
+
if (!isString(path19)) {
|
|
526
526
|
return doThrow(
|
|
527
527
|
`path must be a string, but got \`${originalPath}\``,
|
|
528
528
|
TypeError
|
|
529
529
|
);
|
|
530
530
|
}
|
|
531
|
-
if (!
|
|
531
|
+
if (!path19) {
|
|
532
532
|
return doThrow(`path must not be empty`, TypeError);
|
|
533
533
|
}
|
|
534
|
-
if (checkPath.isNotRelative(
|
|
534
|
+
if (checkPath.isNotRelative(path19)) {
|
|
535
535
|
const r = "`path.relative()`d";
|
|
536
536
|
return doThrow(
|
|
537
537
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -540,7 +540,7 @@ var require_ignore = __commonJS({
|
|
|
540
540
|
}
|
|
541
541
|
return true;
|
|
542
542
|
};
|
|
543
|
-
var isNotRelative = (
|
|
543
|
+
var isNotRelative = (path19) => REGEX_TEST_INVALID_PATH.test(path19);
|
|
544
544
|
checkPath.isNotRelative = isNotRelative;
|
|
545
545
|
checkPath.convert = (p) => p;
|
|
546
546
|
var Ignore2 = class {
|
|
@@ -570,19 +570,19 @@ var require_ignore = __commonJS({
|
|
|
570
570
|
}
|
|
571
571
|
// @returns {TestResult}
|
|
572
572
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
573
|
-
const
|
|
573
|
+
const path19 = originalPath && checkPath.convert(originalPath);
|
|
574
574
|
checkPath(
|
|
575
|
-
|
|
575
|
+
path19,
|
|
576
576
|
originalPath,
|
|
577
577
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
578
578
|
);
|
|
579
|
-
return this._t(
|
|
579
|
+
return this._t(path19, cache, checkUnignored, slices);
|
|
580
580
|
}
|
|
581
|
-
checkIgnore(
|
|
582
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
583
|
-
return this.test(
|
|
581
|
+
checkIgnore(path19) {
|
|
582
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path19)) {
|
|
583
|
+
return this.test(path19);
|
|
584
584
|
}
|
|
585
|
-
const slices =
|
|
585
|
+
const slices = path19.split(SLASH).filter(Boolean);
|
|
586
586
|
slices.pop();
|
|
587
587
|
if (slices.length) {
|
|
588
588
|
const parent = this._t(
|
|
@@ -595,18 +595,18 @@ var require_ignore = __commonJS({
|
|
|
595
595
|
return parent;
|
|
596
596
|
}
|
|
597
597
|
}
|
|
598
|
-
return this._rules.test(
|
|
598
|
+
return this._rules.test(path19, false, MODE_CHECK_IGNORE);
|
|
599
599
|
}
|
|
600
|
-
_t(
|
|
601
|
-
if (
|
|
602
|
-
return cache[
|
|
600
|
+
_t(path19, cache, checkUnignored, slices) {
|
|
601
|
+
if (path19 in cache) {
|
|
602
|
+
return cache[path19];
|
|
603
603
|
}
|
|
604
604
|
if (!slices) {
|
|
605
|
-
slices =
|
|
605
|
+
slices = path19.split(SLASH).filter(Boolean);
|
|
606
606
|
}
|
|
607
607
|
slices.pop();
|
|
608
608
|
if (!slices.length) {
|
|
609
|
-
return cache[
|
|
609
|
+
return cache[path19] = this._rules.test(path19, checkUnignored, MODE_IGNORE);
|
|
610
610
|
}
|
|
611
611
|
const parent = this._t(
|
|
612
612
|
slices.join(SLASH) + SLASH,
|
|
@@ -614,29 +614,29 @@ var require_ignore = __commonJS({
|
|
|
614
614
|
checkUnignored,
|
|
615
615
|
slices
|
|
616
616
|
);
|
|
617
|
-
return cache[
|
|
617
|
+
return cache[path19] = parent.ignored ? parent : this._rules.test(path19, checkUnignored, MODE_IGNORE);
|
|
618
618
|
}
|
|
619
|
-
ignores(
|
|
620
|
-
return this._test(
|
|
619
|
+
ignores(path19) {
|
|
620
|
+
return this._test(path19, this._ignoreCache, false).ignored;
|
|
621
621
|
}
|
|
622
622
|
createFilter() {
|
|
623
|
-
return (
|
|
623
|
+
return (path19) => !this.ignores(path19);
|
|
624
624
|
}
|
|
625
625
|
filter(paths) {
|
|
626
626
|
return makeArray(paths).filter(this.createFilter());
|
|
627
627
|
}
|
|
628
628
|
// @returns {TestResult}
|
|
629
|
-
test(
|
|
630
|
-
return this._test(
|
|
629
|
+
test(path19) {
|
|
630
|
+
return this._test(path19, this._testCache, true);
|
|
631
631
|
}
|
|
632
632
|
};
|
|
633
633
|
var factory = (options) => new Ignore2(options);
|
|
634
|
-
var isPathValid = (
|
|
634
|
+
var isPathValid = (path19) => checkPath(path19 && checkPath.convert(path19), path19, RETURN_FALSE);
|
|
635
635
|
var setupWindows = () => {
|
|
636
636
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
637
637
|
checkPath.convert = makePosix;
|
|
638
638
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
639
|
-
checkPath.isNotRelative = (
|
|
639
|
+
checkPath.isNotRelative = (path19) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path19) || isNotRelative(path19);
|
|
640
640
|
};
|
|
641
641
|
if (
|
|
642
642
|
// Detect `process` so that it can run in browsers.
|
|
@@ -653,7 +653,7 @@ var require_ignore = __commonJS({
|
|
|
653
653
|
|
|
654
654
|
// src/cli.ts
|
|
655
655
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
656
|
-
var
|
|
656
|
+
var path18 = __toESM(require("path"), 1);
|
|
657
657
|
|
|
658
658
|
// src/config/constants.ts
|
|
659
659
|
var DEFAULT_INCLUDE = [
|
|
@@ -799,6 +799,7 @@ function getDefaultSearchConfig() {
|
|
|
799
799
|
rerankTopN: 20,
|
|
800
800
|
contextLines: 0,
|
|
801
801
|
routingHints: true,
|
|
802
|
+
routingGraphHandoffHints: false,
|
|
802
803
|
routingHintRole: "system"
|
|
803
804
|
};
|
|
804
805
|
}
|
|
@@ -921,6 +922,7 @@ function parseConfig(raw) {
|
|
|
921
922
|
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
922
923
|
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
|
|
923
924
|
routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
|
|
925
|
+
routingGraphHandoffHints: typeof rawSearch.routingGraphHandoffHints === "boolean" ? rawSearch.routingGraphHandoffHints : defaultSearch.routingGraphHandoffHints,
|
|
924
926
|
routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
|
|
925
927
|
};
|
|
926
928
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
@@ -1079,9 +1081,9 @@ var import_fs = require("fs");
|
|
|
1079
1081
|
var path = __toESM(require("path"), 1);
|
|
1080
1082
|
|
|
1081
1083
|
// src/eval/report-formatters.ts
|
|
1082
|
-
function assertFiniteNumber(value,
|
|
1084
|
+
function assertFiniteNumber(value, path19) {
|
|
1083
1085
|
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1084
|
-
throw new Error(`${
|
|
1086
|
+
throw new Error(`${path19} must be a finite number`);
|
|
1085
1087
|
}
|
|
1086
1088
|
return value;
|
|
1087
1089
|
}
|
|
@@ -1270,13 +1272,15 @@ function buildPerQueryArtifact(perQuery) {
|
|
|
1270
1272
|
|
|
1271
1273
|
// src/eval/runner.ts
|
|
1272
1274
|
var import_fs10 = require("fs");
|
|
1273
|
-
var
|
|
1275
|
+
var path13 = __toESM(require("path"), 1);
|
|
1274
1276
|
var import_perf_hooks2 = require("perf_hooks");
|
|
1275
1277
|
|
|
1276
1278
|
// src/indexer/index.ts
|
|
1277
1279
|
var import_fs7 = require("fs");
|
|
1278
|
-
var
|
|
1280
|
+
var path10 = __toESM(require("path"), 1);
|
|
1279
1281
|
var import_perf_hooks = require("perf_hooks");
|
|
1282
|
+
var import_child_process2 = require("child_process");
|
|
1283
|
+
var import_util2 = require("util");
|
|
1280
1284
|
|
|
1281
1285
|
// node_modules/eventemitter3/index.mjs
|
|
1282
1286
|
var import_index = __toESM(require_eventemitter3(), 1);
|
|
@@ -1300,7 +1304,7 @@ function pTimeout(promise, options) {
|
|
|
1300
1304
|
} = options;
|
|
1301
1305
|
let timer;
|
|
1302
1306
|
let abortHandler;
|
|
1303
|
-
const wrappedPromise = new Promise((
|
|
1307
|
+
const wrappedPromise = new Promise((resolve11, reject) => {
|
|
1304
1308
|
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
|
|
1305
1309
|
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
|
1306
1310
|
}
|
|
@@ -1314,7 +1318,7 @@ function pTimeout(promise, options) {
|
|
|
1314
1318
|
};
|
|
1315
1319
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1316
1320
|
}
|
|
1317
|
-
promise.then(
|
|
1321
|
+
promise.then(resolve11, reject);
|
|
1318
1322
|
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
1319
1323
|
return;
|
|
1320
1324
|
}
|
|
@@ -1322,7 +1326,7 @@ function pTimeout(promise, options) {
|
|
|
1322
1326
|
timer = customTimers.setTimeout.call(void 0, () => {
|
|
1323
1327
|
if (fallback) {
|
|
1324
1328
|
try {
|
|
1325
|
-
|
|
1329
|
+
resolve11(fallback());
|
|
1326
1330
|
} catch (error) {
|
|
1327
1331
|
reject(error);
|
|
1328
1332
|
}
|
|
@@ -1332,7 +1336,7 @@ function pTimeout(promise, options) {
|
|
|
1332
1336
|
promise.cancel();
|
|
1333
1337
|
}
|
|
1334
1338
|
if (message === false) {
|
|
1335
|
-
|
|
1339
|
+
resolve11();
|
|
1336
1340
|
} else if (message instanceof Error) {
|
|
1337
1341
|
reject(message);
|
|
1338
1342
|
} else {
|
|
@@ -1734,7 +1738,7 @@ var PQueue = class extends import_index.default {
|
|
|
1734
1738
|
// Assign unique ID if not provided
|
|
1735
1739
|
id: options.id ?? (this.#idAssigner++).toString()
|
|
1736
1740
|
};
|
|
1737
|
-
return new Promise((
|
|
1741
|
+
return new Promise((resolve11, reject) => {
|
|
1738
1742
|
const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
|
|
1739
1743
|
let cleanupQueueAbortHandler = () => void 0;
|
|
1740
1744
|
const run = async () => {
|
|
@@ -1774,7 +1778,7 @@ var PQueue = class extends import_index.default {
|
|
|
1774
1778
|
})]);
|
|
1775
1779
|
}
|
|
1776
1780
|
const result = await operation;
|
|
1777
|
-
|
|
1781
|
+
resolve11(result);
|
|
1778
1782
|
this.emit("completed", result);
|
|
1779
1783
|
} catch (error) {
|
|
1780
1784
|
reject(error);
|
|
@@ -1962,13 +1966,13 @@ var PQueue = class extends import_index.default {
|
|
|
1962
1966
|
});
|
|
1963
1967
|
}
|
|
1964
1968
|
async #onEvent(event, filter) {
|
|
1965
|
-
return new Promise((
|
|
1969
|
+
return new Promise((resolve11) => {
|
|
1966
1970
|
const listener = () => {
|
|
1967
1971
|
if (filter && !filter()) {
|
|
1968
1972
|
return;
|
|
1969
1973
|
}
|
|
1970
1974
|
this.off(event, listener);
|
|
1971
|
-
|
|
1975
|
+
resolve11();
|
|
1972
1976
|
};
|
|
1973
1977
|
this.on(event, listener);
|
|
1974
1978
|
});
|
|
@@ -2254,7 +2258,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2254
2258
|
const finalDelay = Math.min(delayTime, remainingTime);
|
|
2255
2259
|
options.signal?.throwIfAborted();
|
|
2256
2260
|
if (finalDelay > 0) {
|
|
2257
|
-
await new Promise((
|
|
2261
|
+
await new Promise((resolve11, reject) => {
|
|
2258
2262
|
const onAbort = () => {
|
|
2259
2263
|
clearTimeout(timeoutToken);
|
|
2260
2264
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -2262,7 +2266,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2262
2266
|
};
|
|
2263
2267
|
const timeoutToken = setTimeout(() => {
|
|
2264
2268
|
options.signal?.removeEventListener("abort", onAbort);
|
|
2265
|
-
|
|
2269
|
+
resolve11();
|
|
2266
2270
|
}, finalDelay);
|
|
2267
2271
|
if (options.unref) {
|
|
2268
2272
|
timeoutToken.unref?.();
|
|
@@ -3682,6 +3686,15 @@ function createMockNativeBinding() {
|
|
|
3682
3686
|
close() {
|
|
3683
3687
|
throw error;
|
|
3684
3688
|
}
|
|
3689
|
+
getTransitiveReachability() {
|
|
3690
|
+
throw error;
|
|
3691
|
+
}
|
|
3692
|
+
detectCommunities() {
|
|
3693
|
+
throw error;
|
|
3694
|
+
}
|
|
3695
|
+
computeCentrality() {
|
|
3696
|
+
throw error;
|
|
3697
|
+
}
|
|
3685
3698
|
}
|
|
3686
3699
|
};
|
|
3687
3700
|
}
|
|
@@ -4232,6 +4245,14 @@ var Database = class {
|
|
|
4232
4245
|
this.throwIfClosed();
|
|
4233
4246
|
return this.inner.getSymbolsByNameCi(name);
|
|
4234
4247
|
}
|
|
4248
|
+
getSymbolsForBranch(branch) {
|
|
4249
|
+
this.throwIfClosed();
|
|
4250
|
+
return this.inner.getSymbolsForBranch(branch);
|
|
4251
|
+
}
|
|
4252
|
+
getSymbolsForFiles(filePaths, branch) {
|
|
4253
|
+
this.throwIfClosed();
|
|
4254
|
+
return this.inner.getSymbolsForFiles(filePaths, branch);
|
|
4255
|
+
}
|
|
4235
4256
|
deleteSymbolsByFile(filePath) {
|
|
4236
4257
|
this.throwIfClosed();
|
|
4237
4258
|
return this.inner.deleteSymbolsByFile(filePath);
|
|
@@ -4309,6 +4330,23 @@ var Database = class {
|
|
|
4309
4330
|
this.throwIfClosed();
|
|
4310
4331
|
return this.inner.gcOrphanCallEdges();
|
|
4311
4332
|
}
|
|
4333
|
+
getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth) {
|
|
4334
|
+
this.throwIfClosed();
|
|
4335
|
+
return this.inner.getTransitiveReachability(
|
|
4336
|
+
rootSymbolIds,
|
|
4337
|
+
branch,
|
|
4338
|
+
direction,
|
|
4339
|
+
maxDepth ?? null
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
detectCommunities(branch, symbolIds) {
|
|
4343
|
+
this.throwIfClosed();
|
|
4344
|
+
return this.inner.detectCommunities(branch, symbolIds ?? null);
|
|
4345
|
+
}
|
|
4346
|
+
computeCentrality(branch) {
|
|
4347
|
+
this.throwIfClosed();
|
|
4348
|
+
return this.inner.computeCentrality(branch);
|
|
4349
|
+
}
|
|
4312
4350
|
};
|
|
4313
4351
|
|
|
4314
4352
|
// src/git/index.ts
|
|
@@ -4482,6 +4520,106 @@ function resolveProjectIndexPath(projectRoot, scope) {
|
|
|
4482
4520
|
return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
|
|
4483
4521
|
}
|
|
4484
4522
|
|
|
4523
|
+
// src/tools/changed-files.ts
|
|
4524
|
+
var path9 = __toESM(require("path"), 1);
|
|
4525
|
+
var import_child_process = require("child_process");
|
|
4526
|
+
var import_util = require("util");
|
|
4527
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
|
|
4528
|
+
function getErrorMessage(error) {
|
|
4529
|
+
if (error instanceof Error) return error.message;
|
|
4530
|
+
return String(error);
|
|
4531
|
+
}
|
|
4532
|
+
async function getChangedFiles(opts) {
|
|
4533
|
+
const { pr, branch, projectRoot, baseBranch = "main" } = opts;
|
|
4534
|
+
if (pr !== void 0) {
|
|
4535
|
+
return getChangedFilesForPr(pr, projectRoot, baseBranch);
|
|
4536
|
+
}
|
|
4537
|
+
return getChangedFilesForBranch(branch, projectRoot, baseBranch);
|
|
4538
|
+
}
|
|
4539
|
+
async function getChangedFilesForPr(pr, projectRoot, baseBranch) {
|
|
4540
|
+
let headRefName;
|
|
4541
|
+
let actualBaseBranch = baseBranch;
|
|
4542
|
+
try {
|
|
4543
|
+
const { stdout } = await execFileAsync(
|
|
4544
|
+
"gh",
|
|
4545
|
+
["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
|
|
4546
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4547
|
+
);
|
|
4548
|
+
const data = JSON.parse(stdout);
|
|
4549
|
+
headRefName = data.headRefName;
|
|
4550
|
+
actualBaseBranch = data.baseRefName || baseBranch;
|
|
4551
|
+
if (data.files && data.files.length > 0) {
|
|
4552
|
+
return {
|
|
4553
|
+
files: normalizeFiles(
|
|
4554
|
+
data.files.map((f) => f.path),
|
|
4555
|
+
projectRoot
|
|
4556
|
+
),
|
|
4557
|
+
baseBranch: actualBaseBranch,
|
|
4558
|
+
source: "gh",
|
|
4559
|
+
headRefName
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4562
|
+
} catch (error) {
|
|
4563
|
+
throw new Error(
|
|
4564
|
+
`Failed to retrieve PR #${pr} via gh CLI: ${getErrorMessage(error)}`
|
|
4565
|
+
);
|
|
4566
|
+
}
|
|
4567
|
+
if (headRefName === void 0) {
|
|
4568
|
+
throw new Error(
|
|
4569
|
+
`PR #${pr} returned no usable branch or file information.`
|
|
4570
|
+
);
|
|
4571
|
+
}
|
|
4572
|
+
const result = await getChangedFilesForBranch(headRefName, projectRoot, actualBaseBranch);
|
|
4573
|
+
return { ...result, headRefName };
|
|
4574
|
+
}
|
|
4575
|
+
async function getChangedFilesForBranch(branch, projectRoot, baseBranch) {
|
|
4576
|
+
const targetBranch = branch || await getCurrentBranch2(projectRoot);
|
|
4577
|
+
const mergeBase = await getMergeBase(projectRoot, baseBranch, targetBranch);
|
|
4578
|
+
const { stdout } = await execFileAsync(
|
|
4579
|
+
"git",
|
|
4580
|
+
["diff", "--name-only", `${mergeBase}...${targetBranch}`],
|
|
4581
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4582
|
+
);
|
|
4583
|
+
return {
|
|
4584
|
+
files: normalizeFiles(stdout.split("\n"), projectRoot),
|
|
4585
|
+
baseBranch,
|
|
4586
|
+
source: "git",
|
|
4587
|
+
headRefName: targetBranch
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
async function getCurrentBranch2(projectRoot) {
|
|
4591
|
+
const { stdout } = await execFileAsync(
|
|
4592
|
+
"git",
|
|
4593
|
+
["branch", "--show-current"],
|
|
4594
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4595
|
+
);
|
|
4596
|
+
return stdout.trim() || "HEAD";
|
|
4597
|
+
}
|
|
4598
|
+
async function getMergeBase(projectRoot, baseBranch, branch) {
|
|
4599
|
+
const { stdout } = await execFileAsync(
|
|
4600
|
+
"git",
|
|
4601
|
+
["merge-base", baseBranch, branch],
|
|
4602
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4603
|
+
);
|
|
4604
|
+
return stdout.trim();
|
|
4605
|
+
}
|
|
4606
|
+
function normalizeFiles(rawFiles, projectRoot) {
|
|
4607
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4608
|
+
const result = [];
|
|
4609
|
+
for (const raw of rawFiles) {
|
|
4610
|
+
const trimmed = raw.trim();
|
|
4611
|
+
if (!trimmed) continue;
|
|
4612
|
+
const absolute = path9.resolve(projectRoot, trimmed);
|
|
4613
|
+
const relative5 = path9.relative(projectRoot, absolute);
|
|
4614
|
+
const cleaned = relative5.startsWith("./") ? relative5.slice(2) : relative5;
|
|
4615
|
+
if (!seen.has(cleaned)) {
|
|
4616
|
+
seen.add(cleaned);
|
|
4617
|
+
result.push(cleaned);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
return result;
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4485
4623
|
// src/indexer/index.ts
|
|
4486
4624
|
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
4487
4625
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
@@ -4489,6 +4627,7 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
|
4489
4627
|
"function_declaration",
|
|
4490
4628
|
"function",
|
|
4491
4629
|
"arrow_function",
|
|
4630
|
+
"export_statement",
|
|
4492
4631
|
"method_definition",
|
|
4493
4632
|
"class_declaration",
|
|
4494
4633
|
"interface_declaration",
|
|
@@ -4527,7 +4666,7 @@ function float32ArrayToBuffer(arr) {
|
|
|
4527
4666
|
function bufferToFloat32Array(buf) {
|
|
4528
4667
|
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
4529
4668
|
}
|
|
4530
|
-
function
|
|
4669
|
+
function getErrorMessage2(error) {
|
|
4531
4670
|
if (error instanceof Error) {
|
|
4532
4671
|
return error.message;
|
|
4533
4672
|
}
|
|
@@ -4540,7 +4679,7 @@ function getErrorMessage(error) {
|
|
|
4540
4679
|
return String(error);
|
|
4541
4680
|
}
|
|
4542
4681
|
function isRateLimitError(error) {
|
|
4543
|
-
const message =
|
|
4682
|
+
const message = getErrorMessage2(error);
|
|
4544
4683
|
return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
|
|
4545
4684
|
}
|
|
4546
4685
|
function getSafeEmbeddingChunkTokenLimit(provider) {
|
|
@@ -4558,7 +4697,7 @@ function getDynamicBatchOptions(provider) {
|
|
|
4558
4697
|
return {};
|
|
4559
4698
|
}
|
|
4560
4699
|
function isSqliteCorruptionError(error) {
|
|
4561
|
-
const message =
|
|
4700
|
+
const message = getErrorMessage2(error).toLowerCase();
|
|
4562
4701
|
return message.includes("database disk image is malformed") || message.includes("file is not a database") || message.includes("database schema is corrupt") || message.includes("sqlite_corrupt");
|
|
4563
4702
|
}
|
|
4564
4703
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
@@ -4720,9 +4859,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
4720
4859
|
return true;
|
|
4721
4860
|
}
|
|
4722
4861
|
function isPathWithinRoot(filePath, rootPath) {
|
|
4723
|
-
const normalizedFilePath =
|
|
4724
|
-
const normalizedRoot =
|
|
4725
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
4862
|
+
const normalizedFilePath = path10.resolve(filePath);
|
|
4863
|
+
const normalizedRoot = path10.resolve(rootPath);
|
|
4864
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path10.sep}`);
|
|
4726
4865
|
}
|
|
4727
4866
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
4728
4867
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5775,9 +5914,9 @@ var Indexer = class {
|
|
|
5775
5914
|
this.projectRoot = projectRoot;
|
|
5776
5915
|
this.config = config;
|
|
5777
5916
|
this.indexPath = this.getIndexPath();
|
|
5778
|
-
this.fileHashCachePath =
|
|
5779
|
-
this.failedBatchesPath =
|
|
5780
|
-
this.indexingLockPath =
|
|
5917
|
+
this.fileHashCachePath = path10.join(this.indexPath, "file-hashes.json");
|
|
5918
|
+
this.failedBatchesPath = path10.join(this.indexPath, "failed-batches.json");
|
|
5919
|
+
this.indexingLockPath = path10.join(this.indexPath, "indexing.lock");
|
|
5781
5920
|
this.logger = initializeLogger(config.debug);
|
|
5782
5921
|
}
|
|
5783
5922
|
getIndexPath() {
|
|
@@ -5809,14 +5948,14 @@ var Indexer = class {
|
|
|
5809
5948
|
}
|
|
5810
5949
|
atomicWriteSync(targetPath, data) {
|
|
5811
5950
|
const tempPath = `${targetPath}.tmp`;
|
|
5812
|
-
(0, import_fs7.mkdirSync)(
|
|
5951
|
+
(0, import_fs7.mkdirSync)(path10.dirname(targetPath), { recursive: true });
|
|
5813
5952
|
(0, import_fs7.writeFileSync)(tempPath, data);
|
|
5814
5953
|
(0, import_fs7.renameSync)(tempPath, targetPath);
|
|
5815
5954
|
}
|
|
5816
5955
|
getScopedRoots() {
|
|
5817
|
-
const roots = /* @__PURE__ */ new Set([
|
|
5956
|
+
const roots = /* @__PURE__ */ new Set([path10.resolve(this.projectRoot)]);
|
|
5818
5957
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
5819
|
-
roots.add(
|
|
5958
|
+
roots.add(path10.resolve(this.projectRoot, kbRoot));
|
|
5820
5959
|
}
|
|
5821
5960
|
return Array.from(roots);
|
|
5822
5961
|
}
|
|
@@ -5825,22 +5964,29 @@ var Indexer = class {
|
|
|
5825
5964
|
if (this.config.scope !== "global") {
|
|
5826
5965
|
return branchName;
|
|
5827
5966
|
}
|
|
5828
|
-
const projectHash = hashContent(
|
|
5967
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5968
|
+
return `${projectHash}:${branchName}`;
|
|
5969
|
+
}
|
|
5970
|
+
getBranchCatalogKeyFor(branchName) {
|
|
5971
|
+
if (this.config.scope !== "global") {
|
|
5972
|
+
return branchName;
|
|
5973
|
+
}
|
|
5974
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5829
5975
|
return `${projectHash}:${branchName}`;
|
|
5830
5976
|
}
|
|
5831
5977
|
getLegacyBranchCatalogKey() {
|
|
5832
5978
|
return this.currentBranch || "default";
|
|
5833
5979
|
}
|
|
5834
5980
|
getLegacyMigrationMetadataKey() {
|
|
5835
|
-
const projectHash = hashContent(
|
|
5981
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5836
5982
|
return `index.globalBranchMigration.${projectHash}`;
|
|
5837
5983
|
}
|
|
5838
5984
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
5839
|
-
const projectHash = hashContent(
|
|
5985
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5840
5986
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
5841
5987
|
}
|
|
5842
5988
|
getProjectForceReembedMetadataKey() {
|
|
5843
|
-
const projectHash = hashContent(
|
|
5989
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5844
5990
|
return `index.forceReembed.${projectHash}`;
|
|
5845
5991
|
}
|
|
5846
5992
|
hasProjectForceReembedPending() {
|
|
@@ -5934,7 +6080,7 @@ var Indexer = class {
|
|
|
5934
6080
|
if (!this.database) {
|
|
5935
6081
|
return { chunkIds, symbolIds };
|
|
5936
6082
|
}
|
|
5937
|
-
const projectRootPath =
|
|
6083
|
+
const projectRootPath = path10.resolve(this.projectRoot);
|
|
5938
6084
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
5939
6085
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
5940
6086
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -5957,7 +6103,7 @@ var Indexer = class {
|
|
|
5957
6103
|
if (this.config.scope !== "global") {
|
|
5958
6104
|
return this.getBranchCatalogCleanupKeys();
|
|
5959
6105
|
}
|
|
5960
|
-
const projectHash = hashContent(
|
|
6106
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5961
6107
|
const keys = /* @__PURE__ */ new Set();
|
|
5962
6108
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
5963
6109
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6041,7 +6187,7 @@ var Indexer = class {
|
|
|
6041
6187
|
if (!this.database || this.config.scope !== "global") {
|
|
6042
6188
|
return false;
|
|
6043
6189
|
}
|
|
6044
|
-
const projectHash = hashContent(
|
|
6190
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
6045
6191
|
const roots = this.getScopedRoots();
|
|
6046
6192
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6047
6193
|
return this.database.getAllBranches().some(
|
|
@@ -6075,7 +6221,7 @@ var Indexer = class {
|
|
|
6075
6221
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6076
6222
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6077
6223
|
]);
|
|
6078
|
-
const projectRootPath =
|
|
6224
|
+
const projectRootPath = path10.resolve(this.projectRoot);
|
|
6079
6225
|
const projectLocalFilePaths = new Set(
|
|
6080
6226
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6081
6227
|
);
|
|
@@ -6328,7 +6474,7 @@ var Indexer = class {
|
|
|
6328
6474
|
this.logger.search("warn", "External reranker failed; using deterministic order", {
|
|
6329
6475
|
provider: reranker.provider,
|
|
6330
6476
|
model: reranker.model,
|
|
6331
|
-
error:
|
|
6477
|
+
error: getErrorMessage2(error)
|
|
6332
6478
|
});
|
|
6333
6479
|
return candidates;
|
|
6334
6480
|
}
|
|
@@ -6435,13 +6581,13 @@ var Indexer = class {
|
|
|
6435
6581
|
}
|
|
6436
6582
|
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
6437
6583
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6438
|
-
const storePath =
|
|
6584
|
+
const storePath = path10.join(this.indexPath, "vectors");
|
|
6439
6585
|
this.store = new VectorStore(storePath, dimensions);
|
|
6440
|
-
const indexFilePath =
|
|
6586
|
+
const indexFilePath = path10.join(this.indexPath, "vectors.usearch");
|
|
6441
6587
|
if ((0, import_fs7.existsSync)(indexFilePath)) {
|
|
6442
6588
|
this.store.load();
|
|
6443
6589
|
}
|
|
6444
|
-
const invertedIndexPath =
|
|
6590
|
+
const invertedIndexPath = path10.join(this.indexPath, "inverted-index.json");
|
|
6445
6591
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6446
6592
|
try {
|
|
6447
6593
|
this.invertedIndex.load();
|
|
@@ -6451,7 +6597,7 @@ var Indexer = class {
|
|
|
6451
6597
|
}
|
|
6452
6598
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6453
6599
|
}
|
|
6454
|
-
const dbPath =
|
|
6600
|
+
const dbPath = path10.join(this.indexPath, "codebase.db");
|
|
6455
6601
|
let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
|
|
6456
6602
|
try {
|
|
6457
6603
|
this.database = new Database(dbPath);
|
|
@@ -6532,7 +6678,7 @@ var Indexer = class {
|
|
|
6532
6678
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6533
6679
|
return {
|
|
6534
6680
|
resetCorruptedIndex: true,
|
|
6535
|
-
warning: this.getCorruptedIndexWarning(
|
|
6681
|
+
warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
|
|
6536
6682
|
};
|
|
6537
6683
|
}
|
|
6538
6684
|
throw error;
|
|
@@ -6547,7 +6693,7 @@ var Indexer = class {
|
|
|
6547
6693
|
return;
|
|
6548
6694
|
}
|
|
6549
6695
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6550
|
-
const storeBasePath =
|
|
6696
|
+
const storeBasePath = path10.join(this.indexPath, "vectors");
|
|
6551
6697
|
const storeIndexPath = `${storeBasePath}.usearch`;
|
|
6552
6698
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6553
6699
|
const backupIndexPath = `${storeIndexPath}.bak`;
|
|
@@ -6632,9 +6778,9 @@ var Indexer = class {
|
|
|
6632
6778
|
if (!isSqliteCorruptionError(error)) {
|
|
6633
6779
|
return false;
|
|
6634
6780
|
}
|
|
6635
|
-
const dbPath =
|
|
6781
|
+
const dbPath = path10.join(this.indexPath, "codebase.db");
|
|
6636
6782
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
6637
|
-
const errorMessage =
|
|
6783
|
+
const errorMessage = getErrorMessage2(error);
|
|
6638
6784
|
if (this.config.scope === "global") {
|
|
6639
6785
|
this.logger.error("Detected corrupted shared global index database", {
|
|
6640
6786
|
stage,
|
|
@@ -6655,15 +6801,15 @@ var Indexer = class {
|
|
|
6655
6801
|
this.indexCompatibility = null;
|
|
6656
6802
|
this.fileHashCache.clear();
|
|
6657
6803
|
const resetPaths = [
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6804
|
+
path10.join(this.indexPath, "codebase.db"),
|
|
6805
|
+
path10.join(this.indexPath, "codebase.db-shm"),
|
|
6806
|
+
path10.join(this.indexPath, "codebase.db-wal"),
|
|
6807
|
+
path10.join(this.indexPath, "vectors.usearch"),
|
|
6808
|
+
path10.join(this.indexPath, "inverted-index.json"),
|
|
6809
|
+
path10.join(this.indexPath, "file-hashes.json"),
|
|
6810
|
+
path10.join(this.indexPath, "failed-batches.json"),
|
|
6811
|
+
path10.join(this.indexPath, "indexing.lock"),
|
|
6812
|
+
path10.join(this.indexPath, "vectors")
|
|
6667
6813
|
];
|
|
6668
6814
|
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
6669
6815
|
try {
|
|
@@ -6928,7 +7074,7 @@ var Indexer = class {
|
|
|
6928
7074
|
for (const parsed of parsedFiles) {
|
|
6929
7075
|
currentFilePaths.add(parsed.path);
|
|
6930
7076
|
if (parsed.chunks.length === 0) {
|
|
6931
|
-
const relativePath =
|
|
7077
|
+
const relativePath = path10.relative(this.projectRoot, parsed.path);
|
|
6932
7078
|
stats.parseFailures.push(relativePath);
|
|
6933
7079
|
}
|
|
6934
7080
|
let fileChunkCount = 0;
|
|
@@ -7212,7 +7358,7 @@ var Indexer = class {
|
|
|
7212
7358
|
for (const requestBatch of requestBatches) {
|
|
7213
7359
|
queue.add(async () => {
|
|
7214
7360
|
if (rateLimitBackoffMs > 0) {
|
|
7215
|
-
await new Promise((
|
|
7361
|
+
await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
|
|
7216
7362
|
}
|
|
7217
7363
|
try {
|
|
7218
7364
|
const result = await pRetry(
|
|
@@ -7227,7 +7373,7 @@ var Indexer = class {
|
|
|
7227
7373
|
factor: 2,
|
|
7228
7374
|
shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
|
|
7229
7375
|
onFailedAttempt: (error) => {
|
|
7230
|
-
const message =
|
|
7376
|
+
const message = getErrorMessage2(error);
|
|
7231
7377
|
if (isRateLimitError(error)) {
|
|
7232
7378
|
rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
|
|
7233
7379
|
this.logger.embedding("warn", `Rate limited, backing off`, {
|
|
@@ -7334,7 +7480,7 @@ var Indexer = class {
|
|
|
7334
7480
|
});
|
|
7335
7481
|
} catch (error) {
|
|
7336
7482
|
const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
|
|
7337
|
-
const failureMessage =
|
|
7483
|
+
const failureMessage = getErrorMessage2(error);
|
|
7338
7484
|
const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
7339
7485
|
for (const chunk of failedChunks) {
|
|
7340
7486
|
if (!failedChunkIds.has(chunk.id)) {
|
|
@@ -7773,8 +7919,8 @@ var Indexer = class {
|
|
|
7773
7919
|
this.indexCompatibility = compatibility;
|
|
7774
7920
|
return;
|
|
7775
7921
|
}
|
|
7776
|
-
const localProjectIndexPath =
|
|
7777
|
-
if (
|
|
7922
|
+
const localProjectIndexPath = path10.join(this.projectRoot, ".opencode", "index");
|
|
7923
|
+
if (path10.resolve(this.indexPath) !== path10.resolve(localProjectIndexPath)) {
|
|
7778
7924
|
throw new Error(
|
|
7779
7925
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
7780
7926
|
);
|
|
@@ -7862,7 +8008,7 @@ var Indexer = class {
|
|
|
7862
8008
|
gcOrphanSymbols: 0,
|
|
7863
8009
|
gcOrphanCallEdges: 0,
|
|
7864
8010
|
resetCorruptedIndex: true,
|
|
7865
|
-
warning: this.getCorruptedIndexWarning(
|
|
8011
|
+
warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
|
|
7866
8012
|
};
|
|
7867
8013
|
}
|
|
7868
8014
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8013,7 +8159,7 @@ var Indexer = class {
|
|
|
8013
8159
|
succeeded += successfulResults.length;
|
|
8014
8160
|
stillFailing.push(...failedChunksForBatch.values());
|
|
8015
8161
|
} catch (error) {
|
|
8016
|
-
const failureMessage =
|
|
8162
|
+
const failureMessage = getErrorMessage2(error);
|
|
8017
8163
|
const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
8018
8164
|
const unaccountedChunks = batch.chunks.filter(
|
|
8019
8165
|
(chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
|
|
@@ -8219,13 +8365,194 @@ var Indexer = class {
|
|
|
8219
8365
|
const { database } = await this.ensureInitialized();
|
|
8220
8366
|
let shortest = [];
|
|
8221
8367
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8222
|
-
const
|
|
8223
|
-
if (
|
|
8224
|
-
shortest =
|
|
8368
|
+
const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
8369
|
+
if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
|
|
8370
|
+
shortest = path19;
|
|
8225
8371
|
}
|
|
8226
8372
|
}
|
|
8227
8373
|
return shortest;
|
|
8228
8374
|
}
|
|
8375
|
+
async getSymbolsForBranch(branch) {
|
|
8376
|
+
const { database } = await this.ensureInitialized();
|
|
8377
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8378
|
+
return database.getSymbolsForBranch(resolvedBranch);
|
|
8379
|
+
}
|
|
8380
|
+
async getSymbolsForFiles(filePaths, branch) {
|
|
8381
|
+
const { database } = await this.ensureInitialized();
|
|
8382
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8383
|
+
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8384
|
+
}
|
|
8385
|
+
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8386
|
+
const { database } = await this.ensureInitialized();
|
|
8387
|
+
const branch = this.getBranchCatalogKey();
|
|
8388
|
+
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8389
|
+
}
|
|
8390
|
+
async detectCommunities(branch, symbolIds) {
|
|
8391
|
+
const { database } = await this.ensureInitialized();
|
|
8392
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8393
|
+
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8394
|
+
}
|
|
8395
|
+
async computeCentrality(branch) {
|
|
8396
|
+
const { database } = await this.ensureInitialized();
|
|
8397
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8398
|
+
return database.computeCentrality(resolvedBranch);
|
|
8399
|
+
}
|
|
8400
|
+
async getPrImpact(opts) {
|
|
8401
|
+
const { database } = await this.ensureInitialized();
|
|
8402
|
+
const execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
|
|
8403
|
+
const changedFilesResult = await getChangedFiles({
|
|
8404
|
+
pr: opts.pr,
|
|
8405
|
+
branch: opts.branch,
|
|
8406
|
+
projectRoot: this.projectRoot,
|
|
8407
|
+
baseBranch: this.baseBranch
|
|
8408
|
+
});
|
|
8409
|
+
const changedFiles = changedFilesResult.files;
|
|
8410
|
+
const headRefName = changedFilesResult.headRefName;
|
|
8411
|
+
if (opts.pr !== void 0 && headRefName === void 0) {
|
|
8412
|
+
throw new Error(
|
|
8413
|
+
`Could not resolve head branch for PR #${opts.pr}. Run index_codebase on the PR branch first.`
|
|
8414
|
+
);
|
|
8415
|
+
}
|
|
8416
|
+
const resolvedBranch = opts.pr !== void 0 ? headRefName : opts.branch || this.currentBranch;
|
|
8417
|
+
const branchKey = this.getBranchCatalogKeyFor(resolvedBranch || "default");
|
|
8418
|
+
const branchSymbols = database.getSymbolsForBranch(branchKey);
|
|
8419
|
+
if (branchSymbols.length === 0) {
|
|
8420
|
+
throw new Error(
|
|
8421
|
+
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8422
|
+
);
|
|
8423
|
+
}
|
|
8424
|
+
const absoluteChangedFiles = changedFiles.map((f) => path10.resolve(this.projectRoot, f));
|
|
8425
|
+
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8426
|
+
const directIds = directSymbols.map((s) => s.id);
|
|
8427
|
+
const direction = opts.direction ?? "both";
|
|
8428
|
+
const maxDepth = opts.maxDepth ?? 5;
|
|
8429
|
+
const transitiveCallers = database.getTransitiveReachability(
|
|
8430
|
+
directIds,
|
|
8431
|
+
branchKey,
|
|
8432
|
+
direction,
|
|
8433
|
+
maxDepth
|
|
8434
|
+
);
|
|
8435
|
+
const affectedIdsSet = new Set(directIds);
|
|
8436
|
+
for (const caller of transitiveCallers) {
|
|
8437
|
+
affectedIdsSet.add(caller.symbolId);
|
|
8438
|
+
}
|
|
8439
|
+
const allAffectedIds = Array.from(affectedIdsSet);
|
|
8440
|
+
const communitiesData = database.detectCommunities(branchKey, allAffectedIds);
|
|
8441
|
+
const communityMap = /* @__PURE__ */ new Map();
|
|
8442
|
+
for (const c of communitiesData) {
|
|
8443
|
+
if (!communityMap.has(c.communityLabel)) {
|
|
8444
|
+
communityMap.set(c.communityLabel, {
|
|
8445
|
+
label: c.communityLabel,
|
|
8446
|
+
symbolCount: 0,
|
|
8447
|
+
directSymbols: /* @__PURE__ */ new Set()
|
|
8448
|
+
});
|
|
8449
|
+
}
|
|
8450
|
+
const entry = communityMap.get(c.communityLabel);
|
|
8451
|
+
entry.symbolCount++;
|
|
8452
|
+
if (directIds.includes(c.symbolId)) {
|
|
8453
|
+
entry.directSymbols.add(c.symbolId);
|
|
8454
|
+
}
|
|
8455
|
+
}
|
|
8456
|
+
const communities = Array.from(communityMap.values()).map((c) => ({
|
|
8457
|
+
label: c.label,
|
|
8458
|
+
symbolCount: c.symbolCount,
|
|
8459
|
+
directSymbols: Array.from(c.directSymbols)
|
|
8460
|
+
}));
|
|
8461
|
+
const centralityData = database.computeCentrality(branchKey);
|
|
8462
|
+
const hubThreshold = opts.hubThreshold ?? 10;
|
|
8463
|
+
const hubNodes = centralityData.filter((c) => directIds.includes(c.symbolId) && c.callerCount >= hubThreshold).map((c) => ({
|
|
8464
|
+
id: c.symbolId,
|
|
8465
|
+
name: c.symbolName,
|
|
8466
|
+
callerCount: c.callerCount,
|
|
8467
|
+
filePath: c.filePath
|
|
8468
|
+
}));
|
|
8469
|
+
const totalAffected = allAffectedIds.length;
|
|
8470
|
+
let riskLevel;
|
|
8471
|
+
let riskReason;
|
|
8472
|
+
if (totalAffected < 5 && hubNodes.length === 0) {
|
|
8473
|
+
riskLevel = "LOW";
|
|
8474
|
+
riskReason = `Small impact: ${totalAffected} affected symbols, no hub nodes touched.`;
|
|
8475
|
+
} else if (totalAffected > 20 || hubNodes.length > 1) {
|
|
8476
|
+
riskLevel = "HIGH";
|
|
8477
|
+
riskReason = `Large impact: ${totalAffected} affected symbols${hubNodes.length > 0 ? `, ${hubNodes.length} hub nodes touched` : ""}.`;
|
|
8478
|
+
} else {
|
|
8479
|
+
riskLevel = "MEDIUM";
|
|
8480
|
+
riskReason = `Moderate impact: ${totalAffected} affected symbols${hubNodes.length === 1 ? ", 1 hub node touched" : ""}.`;
|
|
8481
|
+
}
|
|
8482
|
+
let conflictingPRs;
|
|
8483
|
+
if (opts.checkConflicts) {
|
|
8484
|
+
conflictingPRs = [];
|
|
8485
|
+
try {
|
|
8486
|
+
const { stdout } = await execFileAsync2(
|
|
8487
|
+
"gh",
|
|
8488
|
+
["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
|
|
8489
|
+
{ cwd: this.projectRoot, timeout: 3e4 }
|
|
8490
|
+
);
|
|
8491
|
+
const openPRs = JSON.parse(stdout);
|
|
8492
|
+
const currentCommunityLabels = new Set(communities.map((c) => c.label));
|
|
8493
|
+
const allCommunitiesData = database.detectCommunities(branchKey);
|
|
8494
|
+
const symbolToCommunity = /* @__PURE__ */ new Map();
|
|
8495
|
+
const structuralKey = (filePath, name) => `${filePath.toLowerCase()}:${name.toLowerCase()}`;
|
|
8496
|
+
for (const c of allCommunitiesData) {
|
|
8497
|
+
symbolToCommunity.set(structuralKey(c.filePath, c.symbolName), c.communityLabel);
|
|
8498
|
+
}
|
|
8499
|
+
for (const openPr of openPRs) {
|
|
8500
|
+
if (openPr.number === opts.pr) continue;
|
|
8501
|
+
try {
|
|
8502
|
+
const otherChanged = await getChangedFiles({
|
|
8503
|
+
pr: openPr.number,
|
|
8504
|
+
projectRoot: this.projectRoot,
|
|
8505
|
+
baseBranch: this.baseBranch
|
|
8506
|
+
});
|
|
8507
|
+
const otherAbsolute = otherChanged.files.map((f) => path10.resolve(this.projectRoot, f));
|
|
8508
|
+
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8509
|
+
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8510
|
+
const otherLabels = /* @__PURE__ */ new Set();
|
|
8511
|
+
for (const sym of otherSymbols) {
|
|
8512
|
+
const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
|
|
8513
|
+
if (label) {
|
|
8514
|
+
otherLabels.add(label);
|
|
8515
|
+
}
|
|
8516
|
+
}
|
|
8517
|
+
const overlapping = Array.from(otherLabels).filter(
|
|
8518
|
+
(l) => currentCommunityLabels.has(l)
|
|
8519
|
+
);
|
|
8520
|
+
if (overlapping.length > 0) {
|
|
8521
|
+
conflictingPRs.push({
|
|
8522
|
+
pr: openPr.number,
|
|
8523
|
+
branch: openPr.headRefName,
|
|
8524
|
+
overlappingCommunities: overlapping
|
|
8525
|
+
});
|
|
8526
|
+
}
|
|
8527
|
+
} catch {
|
|
8528
|
+
}
|
|
8529
|
+
}
|
|
8530
|
+
} catch {
|
|
8531
|
+
}
|
|
8532
|
+
}
|
|
8533
|
+
return {
|
|
8534
|
+
changedFiles,
|
|
8535
|
+
directSymbols: directSymbols.map((s) => ({
|
|
8536
|
+
id: s.id,
|
|
8537
|
+
name: s.name,
|
|
8538
|
+
kind: s.kind,
|
|
8539
|
+
filePath: s.filePath
|
|
8540
|
+
})),
|
|
8541
|
+
transitiveCallers: transitiveCallers.map((c) => ({
|
|
8542
|
+
id: c.symbolId,
|
|
8543
|
+
name: c.symbolName,
|
|
8544
|
+
filePath: c.filePath,
|
|
8545
|
+
depth: c.depth
|
|
8546
|
+
})),
|
|
8547
|
+
totalAffected,
|
|
8548
|
+
communities,
|
|
8549
|
+
hubNodes,
|
|
8550
|
+
riskLevel,
|
|
8551
|
+
riskReason,
|
|
8552
|
+
direction,
|
|
8553
|
+
conflictingPRs
|
|
8554
|
+
};
|
|
8555
|
+
}
|
|
8229
8556
|
async close() {
|
|
8230
8557
|
await this.database?.close();
|
|
8231
8558
|
this.database = null;
|
|
@@ -8485,13 +8812,13 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
8485
8812
|
// src/eval/runner-config.ts
|
|
8486
8813
|
var import_fs8 = require("fs");
|
|
8487
8814
|
var os4 = __toESM(require("os"), 1);
|
|
8488
|
-
var
|
|
8815
|
+
var path12 = __toESM(require("path"), 1);
|
|
8489
8816
|
|
|
8490
8817
|
// src/config/rebase.ts
|
|
8491
|
-
var
|
|
8818
|
+
var path11 = __toESM(require("path"), 1);
|
|
8492
8819
|
function isWithinRoot(rootDir, targetPath) {
|
|
8493
|
-
const relativePath =
|
|
8494
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
8820
|
+
const relativePath = path11.relative(rootDir, targetPath);
|
|
8821
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
8495
8822
|
}
|
|
8496
8823
|
function rebasePathEntries(values, fromDir, toDir) {
|
|
8497
8824
|
if (!Array.isArray(values)) {
|
|
@@ -8499,10 +8826,10 @@ function rebasePathEntries(values, fromDir, toDir) {
|
|
|
8499
8826
|
}
|
|
8500
8827
|
return values.filter((value) => typeof value === "string").map((value) => {
|
|
8501
8828
|
const trimmed = value.trim();
|
|
8502
|
-
if (!trimmed ||
|
|
8829
|
+
if (!trimmed || path11.isAbsolute(trimmed)) {
|
|
8503
8830
|
return trimmed;
|
|
8504
8831
|
}
|
|
8505
|
-
return normalizePathSeparators(
|
|
8832
|
+
return normalizePathSeparators(path11.normalize(path11.relative(toDir, path11.resolve(fromDir, trimmed))));
|
|
8506
8833
|
}).filter(Boolean);
|
|
8507
8834
|
}
|
|
8508
8835
|
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
@@ -8514,17 +8841,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
|
8514
8841
|
if (!trimmed) {
|
|
8515
8842
|
return trimmed;
|
|
8516
8843
|
}
|
|
8517
|
-
if (
|
|
8844
|
+
if (path11.isAbsolute(trimmed)) {
|
|
8518
8845
|
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
8519
|
-
return normalizePathSeparators(
|
|
8846
|
+
return normalizePathSeparators(path11.normalize(path11.relative(sourceRoot, trimmed) || "."));
|
|
8520
8847
|
}
|
|
8521
|
-
return
|
|
8848
|
+
return path11.normalize(trimmed);
|
|
8522
8849
|
}
|
|
8523
|
-
const resolvedFromSource =
|
|
8850
|
+
const resolvedFromSource = path11.resolve(sourceRoot, trimmed);
|
|
8524
8851
|
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
8525
|
-
return normalizePathSeparators(
|
|
8852
|
+
return normalizePathSeparators(path11.normalize(trimmed));
|
|
8526
8853
|
}
|
|
8527
|
-
return normalizePathSeparators(
|
|
8854
|
+
return normalizePathSeparators(path11.normalize(path11.relative(targetRoot, resolvedFromSource)));
|
|
8528
8855
|
}).filter(Boolean);
|
|
8529
8856
|
}
|
|
8530
8857
|
|
|
@@ -8572,20 +8899,20 @@ function parseJsonConfigFile(filePath) {
|
|
|
8572
8899
|
}
|
|
8573
8900
|
}
|
|
8574
8901
|
function toAbsolute(projectRoot, maybeRelative) {
|
|
8575
|
-
return
|
|
8902
|
+
return path12.isAbsolute(maybeRelative) ? maybeRelative : path12.join(projectRoot, maybeRelative);
|
|
8576
8903
|
}
|
|
8577
8904
|
function isProjectScopedConfigPath(configPath) {
|
|
8578
|
-
return
|
|
8905
|
+
return path12.basename(configPath) === "codebase-index.json" && path12.basename(path12.dirname(configPath)) === ".opencode";
|
|
8579
8906
|
}
|
|
8580
8907
|
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
8581
8908
|
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
8582
8909
|
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
8583
8910
|
values,
|
|
8584
|
-
|
|
8911
|
+
path12.dirname(path12.dirname(resolvedConfigPath)),
|
|
8585
8912
|
projectRoot
|
|
8586
8913
|
) : rebasePathEntries(
|
|
8587
8914
|
values,
|
|
8588
|
-
|
|
8915
|
+
path12.dirname(resolvedConfigPath),
|
|
8589
8916
|
projectRoot
|
|
8590
8917
|
);
|
|
8591
8918
|
if (Array.isArray(config.knowledgeBases)) {
|
|
@@ -8613,7 +8940,7 @@ function loadRawConfig(projectRoot, configPath) {
|
|
|
8613
8940
|
projectConfig
|
|
8614
8941
|
);
|
|
8615
8942
|
}
|
|
8616
|
-
const globalConfig =
|
|
8943
|
+
const globalConfig = path12.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
|
|
8617
8944
|
if ((0, import_fs8.existsSync)(globalConfig)) {
|
|
8618
8945
|
return parseJsonConfigFile(globalConfig);
|
|
8619
8946
|
}
|
|
@@ -8623,10 +8950,10 @@ function getIndexRootPath(projectRoot, scope) {
|
|
|
8623
8950
|
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
8624
8951
|
}
|
|
8625
8952
|
function getLocalProjectIndexRoot(projectRoot) {
|
|
8626
|
-
return
|
|
8953
|
+
return path12.join(projectRoot, ".opencode", "index");
|
|
8627
8954
|
}
|
|
8628
8955
|
function getLocalProjectConfigPath(projectRoot) {
|
|
8629
|
-
return
|
|
8956
|
+
return path12.join(projectRoot, ".opencode", "codebase-index.json");
|
|
8630
8957
|
}
|
|
8631
8958
|
function clearIndexRoot(projectRoot, scope) {
|
|
8632
8959
|
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
@@ -8648,7 +8975,7 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
|
8648
8975
|
projectRoot,
|
|
8649
8976
|
resolvedConfigPath
|
|
8650
8977
|
);
|
|
8651
|
-
(0, import_fs8.mkdirSync)(
|
|
8978
|
+
(0, import_fs8.mkdirSync)(path12.dirname(localConfigPath), { recursive: true });
|
|
8652
8979
|
(0, import_fs8.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
8653
8980
|
return localConfigPath;
|
|
8654
8981
|
}
|
|
@@ -8698,23 +9025,23 @@ function isRecord2(value) {
|
|
|
8698
9025
|
function isStringArray3(value) {
|
|
8699
9026
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
8700
9027
|
}
|
|
8701
|
-
function asPositiveNumber(value,
|
|
9028
|
+
function asPositiveNumber(value, path19) {
|
|
8702
9029
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
8703
|
-
throw new Error(`${
|
|
9030
|
+
throw new Error(`${path19} must be a non-negative number`);
|
|
8704
9031
|
}
|
|
8705
9032
|
return value;
|
|
8706
9033
|
}
|
|
8707
|
-
function parseQueryType(value,
|
|
9034
|
+
function parseQueryType(value, path19) {
|
|
8708
9035
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
8709
9036
|
return value;
|
|
8710
9037
|
}
|
|
8711
9038
|
throw new Error(
|
|
8712
|
-
`${
|
|
9039
|
+
`${path19} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
8713
9040
|
);
|
|
8714
9041
|
}
|
|
8715
|
-
function parseExpected(input,
|
|
9042
|
+
function parseExpected(input, path19) {
|
|
8716
9043
|
if (!isRecord2(input)) {
|
|
8717
|
-
throw new Error(`${
|
|
9044
|
+
throw new Error(`${path19} must be an object`);
|
|
8718
9045
|
}
|
|
8719
9046
|
const filePathRaw = input.filePath;
|
|
8720
9047
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -8723,16 +9050,16 @@ function parseExpected(input, path18) {
|
|
|
8723
9050
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
8724
9051
|
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
8725
9052
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
8726
|
-
throw new Error(`${
|
|
9053
|
+
throw new Error(`${path19} must include either expected.filePath or expected.acceptableFiles`);
|
|
8727
9054
|
}
|
|
8728
9055
|
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
8729
|
-
throw new Error(`${
|
|
9056
|
+
throw new Error(`${path19}.acceptableFiles must be an array of strings`);
|
|
8730
9057
|
}
|
|
8731
9058
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
8732
|
-
throw new Error(`${
|
|
9059
|
+
throw new Error(`${path19}.symbol must be a string when provided`);
|
|
8733
9060
|
}
|
|
8734
9061
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
8735
|
-
throw new Error(`${
|
|
9062
|
+
throw new Error(`${path19}.branch must be a string when provided`);
|
|
8736
9063
|
}
|
|
8737
9064
|
return {
|
|
8738
9065
|
filePath,
|
|
@@ -8742,25 +9069,25 @@ function parseExpected(input, path18) {
|
|
|
8742
9069
|
};
|
|
8743
9070
|
}
|
|
8744
9071
|
function parseQuery(input, index) {
|
|
8745
|
-
const
|
|
9072
|
+
const path19 = `queries[${index}]`;
|
|
8746
9073
|
if (!isRecord2(input)) {
|
|
8747
|
-
throw new Error(`${
|
|
9074
|
+
throw new Error(`${path19} must be an object`);
|
|
8748
9075
|
}
|
|
8749
9076
|
const id = input.id;
|
|
8750
9077
|
const query = input.query;
|
|
8751
9078
|
const queryType = input.queryType;
|
|
8752
9079
|
const expected = input.expected;
|
|
8753
9080
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
8754
|
-
throw new Error(`${
|
|
9081
|
+
throw new Error(`${path19}.id must be a non-empty string`);
|
|
8755
9082
|
}
|
|
8756
9083
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
8757
|
-
throw new Error(`${
|
|
9084
|
+
throw new Error(`${path19}.query must be a non-empty string`);
|
|
8758
9085
|
}
|
|
8759
9086
|
return {
|
|
8760
9087
|
id,
|
|
8761
9088
|
query,
|
|
8762
|
-
queryType: parseQueryType(queryType, `${
|
|
8763
|
-
expected: parseExpected(expected, `${
|
|
9089
|
+
queryType: parseQueryType(queryType, `${path19}.queryType`),
|
|
9090
|
+
expected: parseExpected(expected, `${path19}.expected`)
|
|
8764
9091
|
};
|
|
8765
9092
|
}
|
|
8766
9093
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -8943,13 +9270,13 @@ async function runEvaluation(options) {
|
|
|
8943
9270
|
};
|
|
8944
9271
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
8945
9272
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
8946
|
-
writeJson(
|
|
8947
|
-
writeJson(
|
|
9273
|
+
writeJson(path13.join(outputDir, "summary.json"), summary);
|
|
9274
|
+
writeJson(path13.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
8948
9275
|
let comparison;
|
|
8949
9276
|
if (againstPath) {
|
|
8950
9277
|
const baseline = loadSummary(againstPath);
|
|
8951
9278
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
8952
|
-
writeJson(
|
|
9279
|
+
writeJson(path13.join(outputDir, "compare.json"), comparison);
|
|
8953
9280
|
}
|
|
8954
9281
|
let gate;
|
|
8955
9282
|
if (options.ciMode) {
|
|
@@ -8962,7 +9289,7 @@ async function runEvaluation(options) {
|
|
|
8962
9289
|
if ((0, import_fs10.existsSync)(resolvedBaseline)) {
|
|
8963
9290
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
8964
9291
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
8965
|
-
writeJson(
|
|
9292
|
+
writeJson(path13.join(outputDir, "compare.json"), comparison);
|
|
8966
9293
|
} else if (budget.failOnMissingBaseline) {
|
|
8967
9294
|
throw new Error(
|
|
8968
9295
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -8972,7 +9299,7 @@ async function runEvaluation(options) {
|
|
|
8972
9299
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
8973
9300
|
}
|
|
8974
9301
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
8975
|
-
writeText(
|
|
9302
|
+
writeText(path13.join(outputDir, "summary.md"), markdown);
|
|
8976
9303
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
8977
9304
|
} finally {
|
|
8978
9305
|
await indexer.close();
|
|
@@ -9030,23 +9357,23 @@ async function runSweep(options, sweep) {
|
|
|
9030
9357
|
bestByMrrAt10,
|
|
9031
9358
|
bestByP95Latency
|
|
9032
9359
|
};
|
|
9033
|
-
writeJson(
|
|
9360
|
+
writeJson(path13.join(outputDir, "compare.json"), aggregate);
|
|
9034
9361
|
const md = createSummaryMarkdown(
|
|
9035
9362
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
9036
9363
|
bestByHitAt5?.comparison,
|
|
9037
9364
|
void 0,
|
|
9038
9365
|
aggregate
|
|
9039
9366
|
);
|
|
9040
|
-
writeText(
|
|
9041
|
-
writeJson(
|
|
9367
|
+
writeText(path13.join(outputDir, "summary.md"), md);
|
|
9368
|
+
writeJson(path13.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
9042
9369
|
return { outputDir, aggregate };
|
|
9043
9370
|
}
|
|
9044
9371
|
|
|
9045
9372
|
// src/eval/cli.ts
|
|
9046
|
-
var
|
|
9373
|
+
var path15 = __toESM(require("path"), 1);
|
|
9047
9374
|
|
|
9048
9375
|
// src/eval/cli-parser.ts
|
|
9049
|
-
var
|
|
9376
|
+
var path14 = __toESM(require("path"), 1);
|
|
9050
9377
|
function printUsage() {
|
|
9051
9378
|
console.log(`
|
|
9052
9379
|
Usage:
|
|
@@ -9118,12 +9445,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9118
9445
|
const arg = argv[i];
|
|
9119
9446
|
const next = argv[i + 1];
|
|
9120
9447
|
if (arg === "--project" && next) {
|
|
9121
|
-
parsed.projectRoot =
|
|
9448
|
+
parsed.projectRoot = path14.resolve(cwd, next);
|
|
9122
9449
|
i += 1;
|
|
9123
9450
|
continue;
|
|
9124
9451
|
}
|
|
9125
9452
|
if (arg === "--config" && next) {
|
|
9126
|
-
parsed.configPath =
|
|
9453
|
+
parsed.configPath = path14.resolve(cwd, next);
|
|
9127
9454
|
i += 1;
|
|
9128
9455
|
continue;
|
|
9129
9456
|
}
|
|
@@ -9319,22 +9646,22 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9319
9646
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
9320
9647
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
9321
9648
|
}
|
|
9322
|
-
const currentSummary = loadSummary(
|
|
9649
|
+
const currentSummary = loadSummary(path15.resolve(parsed.projectRoot, currentPath), {
|
|
9323
9650
|
allowLegacyDiversityMetrics: true
|
|
9324
9651
|
});
|
|
9325
|
-
const baselineSummary = loadSummary(
|
|
9652
|
+
const baselineSummary = loadSummary(path15.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
9326
9653
|
allowLegacyDiversityMetrics: true
|
|
9327
9654
|
});
|
|
9328
9655
|
const comparison = compareSummaries(
|
|
9329
9656
|
currentSummary,
|
|
9330
9657
|
baselineSummary,
|
|
9331
|
-
|
|
9658
|
+
path15.resolve(parsed.projectRoot, parsed.againstPath)
|
|
9332
9659
|
);
|
|
9333
|
-
const outputDir = createRunDirectory(
|
|
9660
|
+
const outputDir = createRunDirectory(path15.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
9334
9661
|
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
9335
|
-
writeJson(
|
|
9336
|
-
writeText(
|
|
9337
|
-
writeJson(
|
|
9662
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9663
|
+
writeText(path15.join(outputDir, "summary.md"), summaryMd);
|
|
9664
|
+
writeJson(path15.join(outputDir, "summary.json"), currentSummary);
|
|
9338
9665
|
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
9339
9666
|
return 0;
|
|
9340
9667
|
}
|
|
@@ -9343,7 +9670,7 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9343
9670
|
|
|
9344
9671
|
// src/mcp-server.ts
|
|
9345
9672
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9346
|
-
var
|
|
9673
|
+
var path17 = __toESM(require("path"), 1);
|
|
9347
9674
|
var import_fs12 = require("fs");
|
|
9348
9675
|
|
|
9349
9676
|
// src/mcp-server/register-prompts.ts
|
|
@@ -9442,7 +9769,7 @@ var import_zod2 = require("zod");
|
|
|
9442
9769
|
// src/config/merger.ts
|
|
9443
9770
|
var import_fs11 = require("fs");
|
|
9444
9771
|
var os5 = __toESM(require("os"), 1);
|
|
9445
|
-
var
|
|
9772
|
+
var path16 = __toESM(require("path"), 1);
|
|
9446
9773
|
var PROJECT_OVERRIDE_KEYS = [
|
|
9447
9774
|
"embeddingProvider",
|
|
9448
9775
|
"customProvider",
|
|
@@ -9475,8 +9802,8 @@ function mergeUniqueStringArray(values) {
|
|
|
9475
9802
|
return [...new Set(values.map((value) => String(value).trim()))];
|
|
9476
9803
|
}
|
|
9477
9804
|
function normalizeKnowledgeBasePath(value) {
|
|
9478
|
-
let normalized =
|
|
9479
|
-
const root =
|
|
9805
|
+
let normalized = path16.normalize(String(value).trim());
|
|
9806
|
+
const root = path16.parse(normalized).root;
|
|
9480
9807
|
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
9481
9808
|
normalized = normalized.slice(0, -1);
|
|
9482
9809
|
}
|
|
@@ -9526,8 +9853,8 @@ function loadJsonFile(filePath) {
|
|
|
9526
9853
|
return null;
|
|
9527
9854
|
}
|
|
9528
9855
|
function materializeLocalProjectConfig(projectRoot, config) {
|
|
9529
|
-
const localConfigPath =
|
|
9530
|
-
(0, import_fs11.mkdirSync)(
|
|
9856
|
+
const localConfigPath = path16.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9857
|
+
(0, import_fs11.mkdirSync)(path16.dirname(localConfigPath), { recursive: true });
|
|
9531
9858
|
(0, import_fs11.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
9532
9859
|
return localConfigPath;
|
|
9533
9860
|
}
|
|
@@ -9538,7 +9865,7 @@ function loadProjectConfigLayer(projectRoot) {
|
|
|
9538
9865
|
return {};
|
|
9539
9866
|
}
|
|
9540
9867
|
const normalizedConfig = { ...projectConfig };
|
|
9541
|
-
const projectConfigBaseDir =
|
|
9868
|
+
const projectConfigBaseDir = path16.dirname(path16.dirname(projectConfigPath));
|
|
9542
9869
|
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
9543
9870
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
9544
9871
|
normalizedConfig.knowledgeBases,
|
|
@@ -9757,6 +10084,57 @@ ${truncateContent(r.content)}
|
|
|
9757
10084
|
return formatted.join("\n\n");
|
|
9758
10085
|
}
|
|
9759
10086
|
|
|
10087
|
+
// src/tools/format-pr-impact.ts
|
|
10088
|
+
function formatPrImpact(result) {
|
|
10089
|
+
const lines = [];
|
|
10090
|
+
lines.push(`\u2192 Files changed: ${result.changedFiles.length}`);
|
|
10091
|
+
for (const file of result.changedFiles) {
|
|
10092
|
+
lines.push(` - ${file}`);
|
|
10093
|
+
}
|
|
10094
|
+
const directCount = result.directSymbols.length;
|
|
10095
|
+
const transitiveCount = result.transitiveCallers.length;
|
|
10096
|
+
const directionLabel = result.direction === "callees" ? "callees" : result.direction === "both" ? "reachable (callers + callees)" : "callers";
|
|
10097
|
+
lines.push(
|
|
10098
|
+
`\u2192 Symbols affected: ${result.totalAffected} (${directCount} direct, ${transitiveCount} transitive ${directionLabel})`
|
|
10099
|
+
);
|
|
10100
|
+
if (directCount > 0) {
|
|
10101
|
+
lines.push(
|
|
10102
|
+
` Direct: ${result.directSymbols.map((s) => `${s.name} (${s.kind})`).join(", ")}`
|
|
10103
|
+
);
|
|
10104
|
+
}
|
|
10105
|
+
if (transitiveCount > 0) {
|
|
10106
|
+
lines.push(
|
|
10107
|
+
` Transitive ${directionLabel}: ${result.transitiveCallers.map((s) => s.name).join(", ")}`
|
|
10108
|
+
);
|
|
10109
|
+
}
|
|
10110
|
+
if (result.communities.length > 0) {
|
|
10111
|
+
lines.push(
|
|
10112
|
+
`\u2192 Communities touched: ${result.communities.map((c) => c.label).join(", ")}`
|
|
10113
|
+
);
|
|
10114
|
+
for (const community of result.communities) {
|
|
10115
|
+
lines.push(
|
|
10116
|
+
` - ${community.label}: ${community.symbolCount} symbols`
|
|
10117
|
+
);
|
|
10118
|
+
}
|
|
10119
|
+
} else {
|
|
10120
|
+
lines.push("\u2192 Communities touched: none");
|
|
10121
|
+
}
|
|
10122
|
+
lines.push(`\u2192 Risk: ${result.riskLevel} \u2014 ${result.riskReason}`);
|
|
10123
|
+
if (result.hubNodes.length > 0) {
|
|
10124
|
+
lines.push(" Hub nodes in change scope:");
|
|
10125
|
+
for (const hub of result.hubNodes) {
|
|
10126
|
+
lines.push(` - ${hub.name} (${hub.callerCount} callers) at ${hub.filePath}`);
|
|
10127
|
+
}
|
|
10128
|
+
}
|
|
10129
|
+
if (result.conflictingPRs && result.conflictingPRs.length > 0) {
|
|
10130
|
+
const conflictList = result.conflictingPRs.map(
|
|
10131
|
+
(c) => `PR #${c.pr} (also touches ${c.overlappingCommunities.join(", ")} community)`
|
|
10132
|
+
);
|
|
10133
|
+
lines.push(`\u2192 Potential conflicts with: ${conflictList.join(", ")}`);
|
|
10134
|
+
}
|
|
10135
|
+
return lines.join("\n");
|
|
10136
|
+
}
|
|
10137
|
+
|
|
9760
10138
|
// src/mcp-server/shared.ts
|
|
9761
10139
|
var MAX_CONTENT_LINES2 = 30;
|
|
9762
10140
|
function truncateContent2(content) {
|
|
@@ -10051,19 +10429,49 @@ ${formatted.join("\n")}` }] };
|
|
|
10051
10429
|
async (args) => {
|
|
10052
10430
|
await runtime.ensureInitialized();
|
|
10053
10431
|
const indexer = runtime.getIndexer();
|
|
10054
|
-
const
|
|
10055
|
-
if (
|
|
10432
|
+
const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
|
|
10433
|
+
if (path19.length === 0) {
|
|
10056
10434
|
return { content: [{ type: "text", text: `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.` }] };
|
|
10057
10435
|
}
|
|
10058
|
-
const formatted =
|
|
10436
|
+
const formatted = path19.map((hop, i) => {
|
|
10059
10437
|
const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10060
10438
|
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10061
10439
|
return `${prefix} ${hop.symbolName}${location}`;
|
|
10062
10440
|
});
|
|
10063
|
-
return { content: [{ type: "text", text: `Path (${
|
|
10441
|
+
return { content: [{ type: "text", text: `Path (${path19.length} hops):
|
|
10064
10442
|
${formatted.join("\n")}` }] };
|
|
10065
10443
|
}
|
|
10066
10444
|
);
|
|
10445
|
+
server.tool(
|
|
10446
|
+
"pr_impact",
|
|
10447
|
+
"Analyze the impact of a pull request or branch by examining changed files, affected symbols, transitive callers, communities touched, hub nodes, and risk level. Use to understand blast radius before merging.",
|
|
10448
|
+
{
|
|
10449
|
+
pr: import_zod2.z.number().optional().describe("Pull request number to analyze"),
|
|
10450
|
+
branch: import_zod2.z.string().optional().describe("Branch name to analyze (defaults to current branch)"),
|
|
10451
|
+
maxDepth: import_zod2.z.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
|
|
10452
|
+
hubThreshold: import_zod2.z.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
|
|
10453
|
+
checkConflicts: import_zod2.z.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
|
|
10454
|
+
direction: import_zod2.z.enum(["callers", "callees", "both"]).optional().default("both").describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
|
|
10455
|
+
},
|
|
10456
|
+
async (args) => {
|
|
10457
|
+
await runtime.ensureInitialized();
|
|
10458
|
+
const indexer = runtime.getIndexer();
|
|
10459
|
+
try {
|
|
10460
|
+
const result = await indexer.getPrImpact({
|
|
10461
|
+
pr: args.pr,
|
|
10462
|
+
branch: args.branch,
|
|
10463
|
+
maxDepth: args.maxDepth,
|
|
10464
|
+
hubThreshold: args.hubThreshold,
|
|
10465
|
+
checkConflicts: args.checkConflicts,
|
|
10466
|
+
direction: args.direction
|
|
10467
|
+
});
|
|
10468
|
+
return { content: [{ type: "text", text: formatPrImpact(result) }] };
|
|
10469
|
+
} catch (error) {
|
|
10470
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10471
|
+
return { content: [{ type: "text", text: `Error analyzing PR impact: ${message}` }] };
|
|
10472
|
+
}
|
|
10473
|
+
}
|
|
10474
|
+
);
|
|
10067
10475
|
}
|
|
10068
10476
|
|
|
10069
10477
|
// src/mcp-server.ts
|
|
@@ -10082,12 +10490,12 @@ function createMcpServer(projectRoot, config) {
|
|
|
10082
10490
|
if (config.scope !== "project") {
|
|
10083
10491
|
return false;
|
|
10084
10492
|
}
|
|
10085
|
-
const localIndexPath =
|
|
10493
|
+
const localIndexPath = path17.join(projectRoot, ".opencode", "index");
|
|
10086
10494
|
const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
|
|
10087
10495
|
if (!mainRepoRoot) {
|
|
10088
10496
|
return false;
|
|
10089
10497
|
}
|
|
10090
|
-
const inheritedIndexPath =
|
|
10498
|
+
const inheritedIndexPath = path17.join(mainRepoRoot, ".opencode", "index");
|
|
10091
10499
|
return !(0, import_fs12.existsSync)(localIndexPath) && (0, import_fs12.existsSync)(inheritedIndexPath);
|
|
10092
10500
|
}
|
|
10093
10501
|
async function ensureInitialized() {
|
|
@@ -10113,9 +10521,9 @@ function parseArgs(argv) {
|
|
|
10113
10521
|
let config;
|
|
10114
10522
|
for (let i = 2; i < argv.length; i++) {
|
|
10115
10523
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
10116
|
-
project =
|
|
10524
|
+
project = path18.resolve(argv[++i]);
|
|
10117
10525
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
10118
|
-
config =
|
|
10526
|
+
config = path18.resolve(argv[++i]);
|
|
10119
10527
|
}
|
|
10120
10528
|
}
|
|
10121
10529
|
return { project, config };
|