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.js
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
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
656
|
-
import * as
|
|
656
|
+
import * as path18 from "path";
|
|
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 @@ import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
1079
1081
|
import * as path from "path";
|
|
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
|
import { existsSync as existsSync8 } from "fs";
|
|
1273
|
-
import * as
|
|
1275
|
+
import * as path13 from "path";
|
|
1274
1276
|
import { performance as performance3 } from "perf_hooks";
|
|
1275
1277
|
|
|
1276
1278
|
// src/indexer/index.ts
|
|
1277
1279
|
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
|
|
1278
|
-
import * as
|
|
1280
|
+
import * as path10 from "path";
|
|
1279
1281
|
import { performance as performance2 } from "perf_hooks";
|
|
1282
|
+
import { execFile as execFile2 } from "child_process";
|
|
1283
|
+
import { promisify as promisify2 } from "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?.();
|
|
@@ -3681,6 +3685,15 @@ function createMockNativeBinding() {
|
|
|
3681
3685
|
close() {
|
|
3682
3686
|
throw error;
|
|
3683
3687
|
}
|
|
3688
|
+
getTransitiveReachability() {
|
|
3689
|
+
throw error;
|
|
3690
|
+
}
|
|
3691
|
+
detectCommunities() {
|
|
3692
|
+
throw error;
|
|
3693
|
+
}
|
|
3694
|
+
computeCentrality() {
|
|
3695
|
+
throw error;
|
|
3696
|
+
}
|
|
3684
3697
|
}
|
|
3685
3698
|
};
|
|
3686
3699
|
}
|
|
@@ -4231,6 +4244,14 @@ var Database = class {
|
|
|
4231
4244
|
this.throwIfClosed();
|
|
4232
4245
|
return this.inner.getSymbolsByNameCi(name);
|
|
4233
4246
|
}
|
|
4247
|
+
getSymbolsForBranch(branch) {
|
|
4248
|
+
this.throwIfClosed();
|
|
4249
|
+
return this.inner.getSymbolsForBranch(branch);
|
|
4250
|
+
}
|
|
4251
|
+
getSymbolsForFiles(filePaths, branch) {
|
|
4252
|
+
this.throwIfClosed();
|
|
4253
|
+
return this.inner.getSymbolsForFiles(filePaths, branch);
|
|
4254
|
+
}
|
|
4234
4255
|
deleteSymbolsByFile(filePath) {
|
|
4235
4256
|
this.throwIfClosed();
|
|
4236
4257
|
return this.inner.deleteSymbolsByFile(filePath);
|
|
@@ -4308,6 +4329,23 @@ var Database = class {
|
|
|
4308
4329
|
this.throwIfClosed();
|
|
4309
4330
|
return this.inner.gcOrphanCallEdges();
|
|
4310
4331
|
}
|
|
4332
|
+
getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth) {
|
|
4333
|
+
this.throwIfClosed();
|
|
4334
|
+
return this.inner.getTransitiveReachability(
|
|
4335
|
+
rootSymbolIds,
|
|
4336
|
+
branch,
|
|
4337
|
+
direction,
|
|
4338
|
+
maxDepth ?? null
|
|
4339
|
+
);
|
|
4340
|
+
}
|
|
4341
|
+
detectCommunities(branch, symbolIds) {
|
|
4342
|
+
this.throwIfClosed();
|
|
4343
|
+
return this.inner.detectCommunities(branch, symbolIds ?? null);
|
|
4344
|
+
}
|
|
4345
|
+
computeCentrality(branch) {
|
|
4346
|
+
this.throwIfClosed();
|
|
4347
|
+
return this.inner.computeCentrality(branch);
|
|
4348
|
+
}
|
|
4311
4349
|
};
|
|
4312
4350
|
|
|
4313
4351
|
// src/git/index.ts
|
|
@@ -4481,6 +4519,106 @@ function resolveProjectIndexPath(projectRoot, scope) {
|
|
|
4481
4519
|
return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
|
|
4482
4520
|
}
|
|
4483
4521
|
|
|
4522
|
+
// src/tools/changed-files.ts
|
|
4523
|
+
import * as path9 from "path";
|
|
4524
|
+
import { execFile } from "child_process";
|
|
4525
|
+
import { promisify } from "util";
|
|
4526
|
+
var execFileAsync = promisify(execFile);
|
|
4527
|
+
function getErrorMessage(error) {
|
|
4528
|
+
if (error instanceof Error) return error.message;
|
|
4529
|
+
return String(error);
|
|
4530
|
+
}
|
|
4531
|
+
async function getChangedFiles(opts) {
|
|
4532
|
+
const { pr, branch, projectRoot, baseBranch = "main" } = opts;
|
|
4533
|
+
if (pr !== void 0) {
|
|
4534
|
+
return getChangedFilesForPr(pr, projectRoot, baseBranch);
|
|
4535
|
+
}
|
|
4536
|
+
return getChangedFilesForBranch(branch, projectRoot, baseBranch);
|
|
4537
|
+
}
|
|
4538
|
+
async function getChangedFilesForPr(pr, projectRoot, baseBranch) {
|
|
4539
|
+
let headRefName;
|
|
4540
|
+
let actualBaseBranch = baseBranch;
|
|
4541
|
+
try {
|
|
4542
|
+
const { stdout } = await execFileAsync(
|
|
4543
|
+
"gh",
|
|
4544
|
+
["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
|
|
4545
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4546
|
+
);
|
|
4547
|
+
const data = JSON.parse(stdout);
|
|
4548
|
+
headRefName = data.headRefName;
|
|
4549
|
+
actualBaseBranch = data.baseRefName || baseBranch;
|
|
4550
|
+
if (data.files && data.files.length > 0) {
|
|
4551
|
+
return {
|
|
4552
|
+
files: normalizeFiles(
|
|
4553
|
+
data.files.map((f) => f.path),
|
|
4554
|
+
projectRoot
|
|
4555
|
+
),
|
|
4556
|
+
baseBranch: actualBaseBranch,
|
|
4557
|
+
source: "gh",
|
|
4558
|
+
headRefName
|
|
4559
|
+
};
|
|
4560
|
+
}
|
|
4561
|
+
} catch (error) {
|
|
4562
|
+
throw new Error(
|
|
4563
|
+
`Failed to retrieve PR #${pr} via gh CLI: ${getErrorMessage(error)}`
|
|
4564
|
+
);
|
|
4565
|
+
}
|
|
4566
|
+
if (headRefName === void 0) {
|
|
4567
|
+
throw new Error(
|
|
4568
|
+
`PR #${pr} returned no usable branch or file information.`
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4571
|
+
const result = await getChangedFilesForBranch(headRefName, projectRoot, actualBaseBranch);
|
|
4572
|
+
return { ...result, headRefName };
|
|
4573
|
+
}
|
|
4574
|
+
async function getChangedFilesForBranch(branch, projectRoot, baseBranch) {
|
|
4575
|
+
const targetBranch = branch || await getCurrentBranch2(projectRoot);
|
|
4576
|
+
const mergeBase = await getMergeBase(projectRoot, baseBranch, targetBranch);
|
|
4577
|
+
const { stdout } = await execFileAsync(
|
|
4578
|
+
"git",
|
|
4579
|
+
["diff", "--name-only", `${mergeBase}...${targetBranch}`],
|
|
4580
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4581
|
+
);
|
|
4582
|
+
return {
|
|
4583
|
+
files: normalizeFiles(stdout.split("\n"), projectRoot),
|
|
4584
|
+
baseBranch,
|
|
4585
|
+
source: "git",
|
|
4586
|
+
headRefName: targetBranch
|
|
4587
|
+
};
|
|
4588
|
+
}
|
|
4589
|
+
async function getCurrentBranch2(projectRoot) {
|
|
4590
|
+
const { stdout } = await execFileAsync(
|
|
4591
|
+
"git",
|
|
4592
|
+
["branch", "--show-current"],
|
|
4593
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4594
|
+
);
|
|
4595
|
+
return stdout.trim() || "HEAD";
|
|
4596
|
+
}
|
|
4597
|
+
async function getMergeBase(projectRoot, baseBranch, branch) {
|
|
4598
|
+
const { stdout } = await execFileAsync(
|
|
4599
|
+
"git",
|
|
4600
|
+
["merge-base", baseBranch, branch],
|
|
4601
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4602
|
+
);
|
|
4603
|
+
return stdout.trim();
|
|
4604
|
+
}
|
|
4605
|
+
function normalizeFiles(rawFiles, projectRoot) {
|
|
4606
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4607
|
+
const result = [];
|
|
4608
|
+
for (const raw of rawFiles) {
|
|
4609
|
+
const trimmed = raw.trim();
|
|
4610
|
+
if (!trimmed) continue;
|
|
4611
|
+
const absolute = path9.resolve(projectRoot, trimmed);
|
|
4612
|
+
const relative5 = path9.relative(projectRoot, absolute);
|
|
4613
|
+
const cleaned = relative5.startsWith("./") ? relative5.slice(2) : relative5;
|
|
4614
|
+
if (!seen.has(cleaned)) {
|
|
4615
|
+
seen.add(cleaned);
|
|
4616
|
+
result.push(cleaned);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
return result;
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4484
4622
|
// src/indexer/index.ts
|
|
4485
4623
|
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
4486
4624
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
@@ -4488,6 +4626,7 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
|
4488
4626
|
"function_declaration",
|
|
4489
4627
|
"function",
|
|
4490
4628
|
"arrow_function",
|
|
4629
|
+
"export_statement",
|
|
4491
4630
|
"method_definition",
|
|
4492
4631
|
"class_declaration",
|
|
4493
4632
|
"interface_declaration",
|
|
@@ -4526,7 +4665,7 @@ function float32ArrayToBuffer(arr) {
|
|
|
4526
4665
|
function bufferToFloat32Array(buf) {
|
|
4527
4666
|
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
4528
4667
|
}
|
|
4529
|
-
function
|
|
4668
|
+
function getErrorMessage2(error) {
|
|
4530
4669
|
if (error instanceof Error) {
|
|
4531
4670
|
return error.message;
|
|
4532
4671
|
}
|
|
@@ -4539,7 +4678,7 @@ function getErrorMessage(error) {
|
|
|
4539
4678
|
return String(error);
|
|
4540
4679
|
}
|
|
4541
4680
|
function isRateLimitError(error) {
|
|
4542
|
-
const message =
|
|
4681
|
+
const message = getErrorMessage2(error);
|
|
4543
4682
|
return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
|
|
4544
4683
|
}
|
|
4545
4684
|
function getSafeEmbeddingChunkTokenLimit(provider) {
|
|
@@ -4557,7 +4696,7 @@ function getDynamicBatchOptions(provider) {
|
|
|
4557
4696
|
return {};
|
|
4558
4697
|
}
|
|
4559
4698
|
function isSqliteCorruptionError(error) {
|
|
4560
|
-
const message =
|
|
4699
|
+
const message = getErrorMessage2(error).toLowerCase();
|
|
4561
4700
|
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");
|
|
4562
4701
|
}
|
|
4563
4702
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
@@ -4719,9 +4858,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
4719
4858
|
return true;
|
|
4720
4859
|
}
|
|
4721
4860
|
function isPathWithinRoot(filePath, rootPath) {
|
|
4722
|
-
const normalizedFilePath =
|
|
4723
|
-
const normalizedRoot =
|
|
4724
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
4861
|
+
const normalizedFilePath = path10.resolve(filePath);
|
|
4862
|
+
const normalizedRoot = path10.resolve(rootPath);
|
|
4863
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path10.sep}`);
|
|
4725
4864
|
}
|
|
4726
4865
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
4727
4866
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5774,9 +5913,9 @@ var Indexer = class {
|
|
|
5774
5913
|
this.projectRoot = projectRoot;
|
|
5775
5914
|
this.config = config;
|
|
5776
5915
|
this.indexPath = this.getIndexPath();
|
|
5777
|
-
this.fileHashCachePath =
|
|
5778
|
-
this.failedBatchesPath =
|
|
5779
|
-
this.indexingLockPath =
|
|
5916
|
+
this.fileHashCachePath = path10.join(this.indexPath, "file-hashes.json");
|
|
5917
|
+
this.failedBatchesPath = path10.join(this.indexPath, "failed-batches.json");
|
|
5918
|
+
this.indexingLockPath = path10.join(this.indexPath, "indexing.lock");
|
|
5780
5919
|
this.logger = initializeLogger(config.debug);
|
|
5781
5920
|
}
|
|
5782
5921
|
getIndexPath() {
|
|
@@ -5808,14 +5947,14 @@ var Indexer = class {
|
|
|
5808
5947
|
}
|
|
5809
5948
|
atomicWriteSync(targetPath, data) {
|
|
5810
5949
|
const tempPath = `${targetPath}.tmp`;
|
|
5811
|
-
mkdirSync2(
|
|
5950
|
+
mkdirSync2(path10.dirname(targetPath), { recursive: true });
|
|
5812
5951
|
writeFileSync2(tempPath, data);
|
|
5813
5952
|
renameSync(tempPath, targetPath);
|
|
5814
5953
|
}
|
|
5815
5954
|
getScopedRoots() {
|
|
5816
|
-
const roots = /* @__PURE__ */ new Set([
|
|
5955
|
+
const roots = /* @__PURE__ */ new Set([path10.resolve(this.projectRoot)]);
|
|
5817
5956
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
5818
|
-
roots.add(
|
|
5957
|
+
roots.add(path10.resolve(this.projectRoot, kbRoot));
|
|
5819
5958
|
}
|
|
5820
5959
|
return Array.from(roots);
|
|
5821
5960
|
}
|
|
@@ -5824,22 +5963,29 @@ var Indexer = class {
|
|
|
5824
5963
|
if (this.config.scope !== "global") {
|
|
5825
5964
|
return branchName;
|
|
5826
5965
|
}
|
|
5827
|
-
const projectHash = hashContent(
|
|
5966
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5967
|
+
return `${projectHash}:${branchName}`;
|
|
5968
|
+
}
|
|
5969
|
+
getBranchCatalogKeyFor(branchName) {
|
|
5970
|
+
if (this.config.scope !== "global") {
|
|
5971
|
+
return branchName;
|
|
5972
|
+
}
|
|
5973
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5828
5974
|
return `${projectHash}:${branchName}`;
|
|
5829
5975
|
}
|
|
5830
5976
|
getLegacyBranchCatalogKey() {
|
|
5831
5977
|
return this.currentBranch || "default";
|
|
5832
5978
|
}
|
|
5833
5979
|
getLegacyMigrationMetadataKey() {
|
|
5834
|
-
const projectHash = hashContent(
|
|
5980
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5835
5981
|
return `index.globalBranchMigration.${projectHash}`;
|
|
5836
5982
|
}
|
|
5837
5983
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
5838
|
-
const projectHash = hashContent(
|
|
5984
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5839
5985
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
5840
5986
|
}
|
|
5841
5987
|
getProjectForceReembedMetadataKey() {
|
|
5842
|
-
const projectHash = hashContent(
|
|
5988
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5843
5989
|
return `index.forceReembed.${projectHash}`;
|
|
5844
5990
|
}
|
|
5845
5991
|
hasProjectForceReembedPending() {
|
|
@@ -5933,7 +6079,7 @@ var Indexer = class {
|
|
|
5933
6079
|
if (!this.database) {
|
|
5934
6080
|
return { chunkIds, symbolIds };
|
|
5935
6081
|
}
|
|
5936
|
-
const projectRootPath =
|
|
6082
|
+
const projectRootPath = path10.resolve(this.projectRoot);
|
|
5937
6083
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
5938
6084
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
5939
6085
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -5956,7 +6102,7 @@ var Indexer = class {
|
|
|
5956
6102
|
if (this.config.scope !== "global") {
|
|
5957
6103
|
return this.getBranchCatalogCleanupKeys();
|
|
5958
6104
|
}
|
|
5959
|
-
const projectHash = hashContent(
|
|
6105
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5960
6106
|
const keys = /* @__PURE__ */ new Set();
|
|
5961
6107
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
5962
6108
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6040,7 +6186,7 @@ var Indexer = class {
|
|
|
6040
6186
|
if (!this.database || this.config.scope !== "global") {
|
|
6041
6187
|
return false;
|
|
6042
6188
|
}
|
|
6043
|
-
const projectHash = hashContent(
|
|
6189
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
6044
6190
|
const roots = this.getScopedRoots();
|
|
6045
6191
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6046
6192
|
return this.database.getAllBranches().some(
|
|
@@ -6074,7 +6220,7 @@ var Indexer = class {
|
|
|
6074
6220
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6075
6221
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6076
6222
|
]);
|
|
6077
|
-
const projectRootPath =
|
|
6223
|
+
const projectRootPath = path10.resolve(this.projectRoot);
|
|
6078
6224
|
const projectLocalFilePaths = new Set(
|
|
6079
6225
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6080
6226
|
);
|
|
@@ -6327,7 +6473,7 @@ var Indexer = class {
|
|
|
6327
6473
|
this.logger.search("warn", "External reranker failed; using deterministic order", {
|
|
6328
6474
|
provider: reranker.provider,
|
|
6329
6475
|
model: reranker.model,
|
|
6330
|
-
error:
|
|
6476
|
+
error: getErrorMessage2(error)
|
|
6331
6477
|
});
|
|
6332
6478
|
return candidates;
|
|
6333
6479
|
}
|
|
@@ -6434,13 +6580,13 @@ var Indexer = class {
|
|
|
6434
6580
|
}
|
|
6435
6581
|
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
6436
6582
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6437
|
-
const storePath =
|
|
6583
|
+
const storePath = path10.join(this.indexPath, "vectors");
|
|
6438
6584
|
this.store = new VectorStore(storePath, dimensions);
|
|
6439
|
-
const indexFilePath =
|
|
6585
|
+
const indexFilePath = path10.join(this.indexPath, "vectors.usearch");
|
|
6440
6586
|
if (existsSync6(indexFilePath)) {
|
|
6441
6587
|
this.store.load();
|
|
6442
6588
|
}
|
|
6443
|
-
const invertedIndexPath =
|
|
6589
|
+
const invertedIndexPath = path10.join(this.indexPath, "inverted-index.json");
|
|
6444
6590
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6445
6591
|
try {
|
|
6446
6592
|
this.invertedIndex.load();
|
|
@@ -6450,7 +6596,7 @@ var Indexer = class {
|
|
|
6450
6596
|
}
|
|
6451
6597
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6452
6598
|
}
|
|
6453
|
-
const dbPath =
|
|
6599
|
+
const dbPath = path10.join(this.indexPath, "codebase.db");
|
|
6454
6600
|
let dbIsNew = !existsSync6(dbPath);
|
|
6455
6601
|
try {
|
|
6456
6602
|
this.database = new Database(dbPath);
|
|
@@ -6531,7 +6677,7 @@ var Indexer = class {
|
|
|
6531
6677
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6532
6678
|
return {
|
|
6533
6679
|
resetCorruptedIndex: true,
|
|
6534
|
-
warning: this.getCorruptedIndexWarning(
|
|
6680
|
+
warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
|
|
6535
6681
|
};
|
|
6536
6682
|
}
|
|
6537
6683
|
throw error;
|
|
@@ -6546,7 +6692,7 @@ var Indexer = class {
|
|
|
6546
6692
|
return;
|
|
6547
6693
|
}
|
|
6548
6694
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6549
|
-
const storeBasePath =
|
|
6695
|
+
const storeBasePath = path10.join(this.indexPath, "vectors");
|
|
6550
6696
|
const storeIndexPath = `${storeBasePath}.usearch`;
|
|
6551
6697
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6552
6698
|
const backupIndexPath = `${storeIndexPath}.bak`;
|
|
@@ -6631,9 +6777,9 @@ var Indexer = class {
|
|
|
6631
6777
|
if (!isSqliteCorruptionError(error)) {
|
|
6632
6778
|
return false;
|
|
6633
6779
|
}
|
|
6634
|
-
const dbPath =
|
|
6780
|
+
const dbPath = path10.join(this.indexPath, "codebase.db");
|
|
6635
6781
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
6636
|
-
const errorMessage =
|
|
6782
|
+
const errorMessage = getErrorMessage2(error);
|
|
6637
6783
|
if (this.config.scope === "global") {
|
|
6638
6784
|
this.logger.error("Detected corrupted shared global index database", {
|
|
6639
6785
|
stage,
|
|
@@ -6654,15 +6800,15 @@ var Indexer = class {
|
|
|
6654
6800
|
this.indexCompatibility = null;
|
|
6655
6801
|
this.fileHashCache.clear();
|
|
6656
6802
|
const resetPaths = [
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6803
|
+
path10.join(this.indexPath, "codebase.db"),
|
|
6804
|
+
path10.join(this.indexPath, "codebase.db-shm"),
|
|
6805
|
+
path10.join(this.indexPath, "codebase.db-wal"),
|
|
6806
|
+
path10.join(this.indexPath, "vectors.usearch"),
|
|
6807
|
+
path10.join(this.indexPath, "inverted-index.json"),
|
|
6808
|
+
path10.join(this.indexPath, "file-hashes.json"),
|
|
6809
|
+
path10.join(this.indexPath, "failed-batches.json"),
|
|
6810
|
+
path10.join(this.indexPath, "indexing.lock"),
|
|
6811
|
+
path10.join(this.indexPath, "vectors")
|
|
6666
6812
|
];
|
|
6667
6813
|
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
6668
6814
|
try {
|
|
@@ -6927,7 +7073,7 @@ var Indexer = class {
|
|
|
6927
7073
|
for (const parsed of parsedFiles) {
|
|
6928
7074
|
currentFilePaths.add(parsed.path);
|
|
6929
7075
|
if (parsed.chunks.length === 0) {
|
|
6930
|
-
const relativePath =
|
|
7076
|
+
const relativePath = path10.relative(this.projectRoot, parsed.path);
|
|
6931
7077
|
stats.parseFailures.push(relativePath);
|
|
6932
7078
|
}
|
|
6933
7079
|
let fileChunkCount = 0;
|
|
@@ -7211,7 +7357,7 @@ var Indexer = class {
|
|
|
7211
7357
|
for (const requestBatch of requestBatches) {
|
|
7212
7358
|
queue.add(async () => {
|
|
7213
7359
|
if (rateLimitBackoffMs > 0) {
|
|
7214
|
-
await new Promise((
|
|
7360
|
+
await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
|
|
7215
7361
|
}
|
|
7216
7362
|
try {
|
|
7217
7363
|
const result = await pRetry(
|
|
@@ -7226,7 +7372,7 @@ var Indexer = class {
|
|
|
7226
7372
|
factor: 2,
|
|
7227
7373
|
shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
|
|
7228
7374
|
onFailedAttempt: (error) => {
|
|
7229
|
-
const message =
|
|
7375
|
+
const message = getErrorMessage2(error);
|
|
7230
7376
|
if (isRateLimitError(error)) {
|
|
7231
7377
|
rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
|
|
7232
7378
|
this.logger.embedding("warn", `Rate limited, backing off`, {
|
|
@@ -7333,7 +7479,7 @@ var Indexer = class {
|
|
|
7333
7479
|
});
|
|
7334
7480
|
} catch (error) {
|
|
7335
7481
|
const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
|
|
7336
|
-
const failureMessage =
|
|
7482
|
+
const failureMessage = getErrorMessage2(error);
|
|
7337
7483
|
const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
7338
7484
|
for (const chunk of failedChunks) {
|
|
7339
7485
|
if (!failedChunkIds.has(chunk.id)) {
|
|
@@ -7772,8 +7918,8 @@ var Indexer = class {
|
|
|
7772
7918
|
this.indexCompatibility = compatibility;
|
|
7773
7919
|
return;
|
|
7774
7920
|
}
|
|
7775
|
-
const localProjectIndexPath =
|
|
7776
|
-
if (
|
|
7921
|
+
const localProjectIndexPath = path10.join(this.projectRoot, ".opencode", "index");
|
|
7922
|
+
if (path10.resolve(this.indexPath) !== path10.resolve(localProjectIndexPath)) {
|
|
7777
7923
|
throw new Error(
|
|
7778
7924
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
7779
7925
|
);
|
|
@@ -7861,7 +8007,7 @@ var Indexer = class {
|
|
|
7861
8007
|
gcOrphanSymbols: 0,
|
|
7862
8008
|
gcOrphanCallEdges: 0,
|
|
7863
8009
|
resetCorruptedIndex: true,
|
|
7864
|
-
warning: this.getCorruptedIndexWarning(
|
|
8010
|
+
warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
|
|
7865
8011
|
};
|
|
7866
8012
|
}
|
|
7867
8013
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8012,7 +8158,7 @@ var Indexer = class {
|
|
|
8012
8158
|
succeeded += successfulResults.length;
|
|
8013
8159
|
stillFailing.push(...failedChunksForBatch.values());
|
|
8014
8160
|
} catch (error) {
|
|
8015
|
-
const failureMessage =
|
|
8161
|
+
const failureMessage = getErrorMessage2(error);
|
|
8016
8162
|
const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
8017
8163
|
const unaccountedChunks = batch.chunks.filter(
|
|
8018
8164
|
(chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
|
|
@@ -8218,13 +8364,194 @@ var Indexer = class {
|
|
|
8218
8364
|
const { database } = await this.ensureInitialized();
|
|
8219
8365
|
let shortest = [];
|
|
8220
8366
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8221
|
-
const
|
|
8222
|
-
if (
|
|
8223
|
-
shortest =
|
|
8367
|
+
const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
8368
|
+
if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
|
|
8369
|
+
shortest = path19;
|
|
8224
8370
|
}
|
|
8225
8371
|
}
|
|
8226
8372
|
return shortest;
|
|
8227
8373
|
}
|
|
8374
|
+
async getSymbolsForBranch(branch) {
|
|
8375
|
+
const { database } = await this.ensureInitialized();
|
|
8376
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8377
|
+
return database.getSymbolsForBranch(resolvedBranch);
|
|
8378
|
+
}
|
|
8379
|
+
async getSymbolsForFiles(filePaths, branch) {
|
|
8380
|
+
const { database } = await this.ensureInitialized();
|
|
8381
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8382
|
+
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8383
|
+
}
|
|
8384
|
+
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8385
|
+
const { database } = await this.ensureInitialized();
|
|
8386
|
+
const branch = this.getBranchCatalogKey();
|
|
8387
|
+
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8388
|
+
}
|
|
8389
|
+
async detectCommunities(branch, symbolIds) {
|
|
8390
|
+
const { database } = await this.ensureInitialized();
|
|
8391
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8392
|
+
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8393
|
+
}
|
|
8394
|
+
async computeCentrality(branch) {
|
|
8395
|
+
const { database } = await this.ensureInitialized();
|
|
8396
|
+
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8397
|
+
return database.computeCentrality(resolvedBranch);
|
|
8398
|
+
}
|
|
8399
|
+
async getPrImpact(opts) {
|
|
8400
|
+
const { database } = await this.ensureInitialized();
|
|
8401
|
+
const execFileAsync2 = promisify2(execFile2);
|
|
8402
|
+
const changedFilesResult = await getChangedFiles({
|
|
8403
|
+
pr: opts.pr,
|
|
8404
|
+
branch: opts.branch,
|
|
8405
|
+
projectRoot: this.projectRoot,
|
|
8406
|
+
baseBranch: this.baseBranch
|
|
8407
|
+
});
|
|
8408
|
+
const changedFiles = changedFilesResult.files;
|
|
8409
|
+
const headRefName = changedFilesResult.headRefName;
|
|
8410
|
+
if (opts.pr !== void 0 && headRefName === void 0) {
|
|
8411
|
+
throw new Error(
|
|
8412
|
+
`Could not resolve head branch for PR #${opts.pr}. Run index_codebase on the PR branch first.`
|
|
8413
|
+
);
|
|
8414
|
+
}
|
|
8415
|
+
const resolvedBranch = opts.pr !== void 0 ? headRefName : opts.branch || this.currentBranch;
|
|
8416
|
+
const branchKey = this.getBranchCatalogKeyFor(resolvedBranch || "default");
|
|
8417
|
+
const branchSymbols = database.getSymbolsForBranch(branchKey);
|
|
8418
|
+
if (branchSymbols.length === 0) {
|
|
8419
|
+
throw new Error(
|
|
8420
|
+
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8421
|
+
);
|
|
8422
|
+
}
|
|
8423
|
+
const absoluteChangedFiles = changedFiles.map((f) => path10.resolve(this.projectRoot, f));
|
|
8424
|
+
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8425
|
+
const directIds = directSymbols.map((s) => s.id);
|
|
8426
|
+
const direction = opts.direction ?? "both";
|
|
8427
|
+
const maxDepth = opts.maxDepth ?? 5;
|
|
8428
|
+
const transitiveCallers = database.getTransitiveReachability(
|
|
8429
|
+
directIds,
|
|
8430
|
+
branchKey,
|
|
8431
|
+
direction,
|
|
8432
|
+
maxDepth
|
|
8433
|
+
);
|
|
8434
|
+
const affectedIdsSet = new Set(directIds);
|
|
8435
|
+
for (const caller of transitiveCallers) {
|
|
8436
|
+
affectedIdsSet.add(caller.symbolId);
|
|
8437
|
+
}
|
|
8438
|
+
const allAffectedIds = Array.from(affectedIdsSet);
|
|
8439
|
+
const communitiesData = database.detectCommunities(branchKey, allAffectedIds);
|
|
8440
|
+
const communityMap = /* @__PURE__ */ new Map();
|
|
8441
|
+
for (const c of communitiesData) {
|
|
8442
|
+
if (!communityMap.has(c.communityLabel)) {
|
|
8443
|
+
communityMap.set(c.communityLabel, {
|
|
8444
|
+
label: c.communityLabel,
|
|
8445
|
+
symbolCount: 0,
|
|
8446
|
+
directSymbols: /* @__PURE__ */ new Set()
|
|
8447
|
+
});
|
|
8448
|
+
}
|
|
8449
|
+
const entry = communityMap.get(c.communityLabel);
|
|
8450
|
+
entry.symbolCount++;
|
|
8451
|
+
if (directIds.includes(c.symbolId)) {
|
|
8452
|
+
entry.directSymbols.add(c.symbolId);
|
|
8453
|
+
}
|
|
8454
|
+
}
|
|
8455
|
+
const communities = Array.from(communityMap.values()).map((c) => ({
|
|
8456
|
+
label: c.label,
|
|
8457
|
+
symbolCount: c.symbolCount,
|
|
8458
|
+
directSymbols: Array.from(c.directSymbols)
|
|
8459
|
+
}));
|
|
8460
|
+
const centralityData = database.computeCentrality(branchKey);
|
|
8461
|
+
const hubThreshold = opts.hubThreshold ?? 10;
|
|
8462
|
+
const hubNodes = centralityData.filter((c) => directIds.includes(c.symbolId) && c.callerCount >= hubThreshold).map((c) => ({
|
|
8463
|
+
id: c.symbolId,
|
|
8464
|
+
name: c.symbolName,
|
|
8465
|
+
callerCount: c.callerCount,
|
|
8466
|
+
filePath: c.filePath
|
|
8467
|
+
}));
|
|
8468
|
+
const totalAffected = allAffectedIds.length;
|
|
8469
|
+
let riskLevel;
|
|
8470
|
+
let riskReason;
|
|
8471
|
+
if (totalAffected < 5 && hubNodes.length === 0) {
|
|
8472
|
+
riskLevel = "LOW";
|
|
8473
|
+
riskReason = `Small impact: ${totalAffected} affected symbols, no hub nodes touched.`;
|
|
8474
|
+
} else if (totalAffected > 20 || hubNodes.length > 1) {
|
|
8475
|
+
riskLevel = "HIGH";
|
|
8476
|
+
riskReason = `Large impact: ${totalAffected} affected symbols${hubNodes.length > 0 ? `, ${hubNodes.length} hub nodes touched` : ""}.`;
|
|
8477
|
+
} else {
|
|
8478
|
+
riskLevel = "MEDIUM";
|
|
8479
|
+
riskReason = `Moderate impact: ${totalAffected} affected symbols${hubNodes.length === 1 ? ", 1 hub node touched" : ""}.`;
|
|
8480
|
+
}
|
|
8481
|
+
let conflictingPRs;
|
|
8482
|
+
if (opts.checkConflicts) {
|
|
8483
|
+
conflictingPRs = [];
|
|
8484
|
+
try {
|
|
8485
|
+
const { stdout } = await execFileAsync2(
|
|
8486
|
+
"gh",
|
|
8487
|
+
["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
|
|
8488
|
+
{ cwd: this.projectRoot, timeout: 3e4 }
|
|
8489
|
+
);
|
|
8490
|
+
const openPRs = JSON.parse(stdout);
|
|
8491
|
+
const currentCommunityLabels = new Set(communities.map((c) => c.label));
|
|
8492
|
+
const allCommunitiesData = database.detectCommunities(branchKey);
|
|
8493
|
+
const symbolToCommunity = /* @__PURE__ */ new Map();
|
|
8494
|
+
const structuralKey = (filePath, name) => `${filePath.toLowerCase()}:${name.toLowerCase()}`;
|
|
8495
|
+
for (const c of allCommunitiesData) {
|
|
8496
|
+
symbolToCommunity.set(structuralKey(c.filePath, c.symbolName), c.communityLabel);
|
|
8497
|
+
}
|
|
8498
|
+
for (const openPr of openPRs) {
|
|
8499
|
+
if (openPr.number === opts.pr) continue;
|
|
8500
|
+
try {
|
|
8501
|
+
const otherChanged = await getChangedFiles({
|
|
8502
|
+
pr: openPr.number,
|
|
8503
|
+
projectRoot: this.projectRoot,
|
|
8504
|
+
baseBranch: this.baseBranch
|
|
8505
|
+
});
|
|
8506
|
+
const otherAbsolute = otherChanged.files.map((f) => path10.resolve(this.projectRoot, f));
|
|
8507
|
+
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8508
|
+
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8509
|
+
const otherLabels = /* @__PURE__ */ new Set();
|
|
8510
|
+
for (const sym of otherSymbols) {
|
|
8511
|
+
const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
|
|
8512
|
+
if (label) {
|
|
8513
|
+
otherLabels.add(label);
|
|
8514
|
+
}
|
|
8515
|
+
}
|
|
8516
|
+
const overlapping = Array.from(otherLabels).filter(
|
|
8517
|
+
(l) => currentCommunityLabels.has(l)
|
|
8518
|
+
);
|
|
8519
|
+
if (overlapping.length > 0) {
|
|
8520
|
+
conflictingPRs.push({
|
|
8521
|
+
pr: openPr.number,
|
|
8522
|
+
branch: openPr.headRefName,
|
|
8523
|
+
overlappingCommunities: overlapping
|
|
8524
|
+
});
|
|
8525
|
+
}
|
|
8526
|
+
} catch {
|
|
8527
|
+
}
|
|
8528
|
+
}
|
|
8529
|
+
} catch {
|
|
8530
|
+
}
|
|
8531
|
+
}
|
|
8532
|
+
return {
|
|
8533
|
+
changedFiles,
|
|
8534
|
+
directSymbols: directSymbols.map((s) => ({
|
|
8535
|
+
id: s.id,
|
|
8536
|
+
name: s.name,
|
|
8537
|
+
kind: s.kind,
|
|
8538
|
+
filePath: s.filePath
|
|
8539
|
+
})),
|
|
8540
|
+
transitiveCallers: transitiveCallers.map((c) => ({
|
|
8541
|
+
id: c.symbolId,
|
|
8542
|
+
name: c.symbolName,
|
|
8543
|
+
filePath: c.filePath,
|
|
8544
|
+
depth: c.depth
|
|
8545
|
+
})),
|
|
8546
|
+
totalAffected,
|
|
8547
|
+
communities,
|
|
8548
|
+
hubNodes,
|
|
8549
|
+
riskLevel,
|
|
8550
|
+
riskReason,
|
|
8551
|
+
direction,
|
|
8552
|
+
conflictingPRs
|
|
8553
|
+
};
|
|
8554
|
+
}
|
|
8228
8555
|
async close() {
|
|
8229
8556
|
await this.database?.close();
|
|
8230
8557
|
this.database = null;
|
|
@@ -8484,13 +8811,13 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
8484
8811
|
// src/eval/runner-config.ts
|
|
8485
8812
|
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8486
8813
|
import * as os4 from "os";
|
|
8487
|
-
import * as
|
|
8814
|
+
import * as path12 from "path";
|
|
8488
8815
|
|
|
8489
8816
|
// src/config/rebase.ts
|
|
8490
|
-
import * as
|
|
8817
|
+
import * as path11 from "path";
|
|
8491
8818
|
function isWithinRoot(rootDir, targetPath) {
|
|
8492
|
-
const relativePath =
|
|
8493
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
8819
|
+
const relativePath = path11.relative(rootDir, targetPath);
|
|
8820
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
8494
8821
|
}
|
|
8495
8822
|
function rebasePathEntries(values, fromDir, toDir) {
|
|
8496
8823
|
if (!Array.isArray(values)) {
|
|
@@ -8498,10 +8825,10 @@ function rebasePathEntries(values, fromDir, toDir) {
|
|
|
8498
8825
|
}
|
|
8499
8826
|
return values.filter((value) => typeof value === "string").map((value) => {
|
|
8500
8827
|
const trimmed = value.trim();
|
|
8501
|
-
if (!trimmed ||
|
|
8828
|
+
if (!trimmed || path11.isAbsolute(trimmed)) {
|
|
8502
8829
|
return trimmed;
|
|
8503
8830
|
}
|
|
8504
|
-
return normalizePathSeparators(
|
|
8831
|
+
return normalizePathSeparators(path11.normalize(path11.relative(toDir, path11.resolve(fromDir, trimmed))));
|
|
8505
8832
|
}).filter(Boolean);
|
|
8506
8833
|
}
|
|
8507
8834
|
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
@@ -8513,17 +8840,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
|
8513
8840
|
if (!trimmed) {
|
|
8514
8841
|
return trimmed;
|
|
8515
8842
|
}
|
|
8516
|
-
if (
|
|
8843
|
+
if (path11.isAbsolute(trimmed)) {
|
|
8517
8844
|
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
8518
|
-
return normalizePathSeparators(
|
|
8845
|
+
return normalizePathSeparators(path11.normalize(path11.relative(sourceRoot, trimmed) || "."));
|
|
8519
8846
|
}
|
|
8520
|
-
return
|
|
8847
|
+
return path11.normalize(trimmed);
|
|
8521
8848
|
}
|
|
8522
|
-
const resolvedFromSource =
|
|
8849
|
+
const resolvedFromSource = path11.resolve(sourceRoot, trimmed);
|
|
8523
8850
|
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
8524
|
-
return normalizePathSeparators(
|
|
8851
|
+
return normalizePathSeparators(path11.normalize(trimmed));
|
|
8525
8852
|
}
|
|
8526
|
-
return normalizePathSeparators(
|
|
8853
|
+
return normalizePathSeparators(path11.normalize(path11.relative(targetRoot, resolvedFromSource)));
|
|
8527
8854
|
}).filter(Boolean);
|
|
8528
8855
|
}
|
|
8529
8856
|
|
|
@@ -8571,20 +8898,20 @@ function parseJsonConfigFile(filePath) {
|
|
|
8571
8898
|
}
|
|
8572
8899
|
}
|
|
8573
8900
|
function toAbsolute(projectRoot, maybeRelative) {
|
|
8574
|
-
return
|
|
8901
|
+
return path12.isAbsolute(maybeRelative) ? maybeRelative : path12.join(projectRoot, maybeRelative);
|
|
8575
8902
|
}
|
|
8576
8903
|
function isProjectScopedConfigPath(configPath) {
|
|
8577
|
-
return
|
|
8904
|
+
return path12.basename(configPath) === "codebase-index.json" && path12.basename(path12.dirname(configPath)) === ".opencode";
|
|
8578
8905
|
}
|
|
8579
8906
|
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
8580
8907
|
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
8581
8908
|
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
8582
8909
|
values,
|
|
8583
|
-
|
|
8910
|
+
path12.dirname(path12.dirname(resolvedConfigPath)),
|
|
8584
8911
|
projectRoot
|
|
8585
8912
|
) : rebasePathEntries(
|
|
8586
8913
|
values,
|
|
8587
|
-
|
|
8914
|
+
path12.dirname(resolvedConfigPath),
|
|
8588
8915
|
projectRoot
|
|
8589
8916
|
);
|
|
8590
8917
|
if (Array.isArray(config.knowledgeBases)) {
|
|
@@ -8612,7 +8939,7 @@ function loadRawConfig(projectRoot, configPath) {
|
|
|
8612
8939
|
projectConfig
|
|
8613
8940
|
);
|
|
8614
8941
|
}
|
|
8615
|
-
const globalConfig =
|
|
8942
|
+
const globalConfig = path12.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
|
|
8616
8943
|
if (existsSync7(globalConfig)) {
|
|
8617
8944
|
return parseJsonConfigFile(globalConfig);
|
|
8618
8945
|
}
|
|
@@ -8622,10 +8949,10 @@ function getIndexRootPath(projectRoot, scope) {
|
|
|
8622
8949
|
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
8623
8950
|
}
|
|
8624
8951
|
function getLocalProjectIndexRoot(projectRoot) {
|
|
8625
|
-
return
|
|
8952
|
+
return path12.join(projectRoot, ".opencode", "index");
|
|
8626
8953
|
}
|
|
8627
8954
|
function getLocalProjectConfigPath(projectRoot) {
|
|
8628
|
-
return
|
|
8955
|
+
return path12.join(projectRoot, ".opencode", "codebase-index.json");
|
|
8629
8956
|
}
|
|
8630
8957
|
function clearIndexRoot(projectRoot, scope) {
|
|
8631
8958
|
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
@@ -8647,7 +8974,7 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
|
8647
8974
|
projectRoot,
|
|
8648
8975
|
resolvedConfigPath
|
|
8649
8976
|
);
|
|
8650
|
-
mkdirSync3(
|
|
8977
|
+
mkdirSync3(path12.dirname(localConfigPath), { recursive: true });
|
|
8651
8978
|
writeFileSync3(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
8652
8979
|
return localConfigPath;
|
|
8653
8980
|
}
|
|
@@ -8697,23 +9024,23 @@ function isRecord2(value) {
|
|
|
8697
9024
|
function isStringArray3(value) {
|
|
8698
9025
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
8699
9026
|
}
|
|
8700
|
-
function asPositiveNumber(value,
|
|
9027
|
+
function asPositiveNumber(value, path19) {
|
|
8701
9028
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
8702
|
-
throw new Error(`${
|
|
9029
|
+
throw new Error(`${path19} must be a non-negative number`);
|
|
8703
9030
|
}
|
|
8704
9031
|
return value;
|
|
8705
9032
|
}
|
|
8706
|
-
function parseQueryType(value,
|
|
9033
|
+
function parseQueryType(value, path19) {
|
|
8707
9034
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
8708
9035
|
return value;
|
|
8709
9036
|
}
|
|
8710
9037
|
throw new Error(
|
|
8711
|
-
`${
|
|
9038
|
+
`${path19} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
8712
9039
|
);
|
|
8713
9040
|
}
|
|
8714
|
-
function parseExpected(input,
|
|
9041
|
+
function parseExpected(input, path19) {
|
|
8715
9042
|
if (!isRecord2(input)) {
|
|
8716
|
-
throw new Error(`${
|
|
9043
|
+
throw new Error(`${path19} must be an object`);
|
|
8717
9044
|
}
|
|
8718
9045
|
const filePathRaw = input.filePath;
|
|
8719
9046
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -8722,16 +9049,16 @@ function parseExpected(input, path18) {
|
|
|
8722
9049
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
8723
9050
|
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
8724
9051
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
8725
|
-
throw new Error(`${
|
|
9052
|
+
throw new Error(`${path19} must include either expected.filePath or expected.acceptableFiles`);
|
|
8726
9053
|
}
|
|
8727
9054
|
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
8728
|
-
throw new Error(`${
|
|
9055
|
+
throw new Error(`${path19}.acceptableFiles must be an array of strings`);
|
|
8729
9056
|
}
|
|
8730
9057
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
8731
|
-
throw new Error(`${
|
|
9058
|
+
throw new Error(`${path19}.symbol must be a string when provided`);
|
|
8732
9059
|
}
|
|
8733
9060
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
8734
|
-
throw new Error(`${
|
|
9061
|
+
throw new Error(`${path19}.branch must be a string when provided`);
|
|
8735
9062
|
}
|
|
8736
9063
|
return {
|
|
8737
9064
|
filePath,
|
|
@@ -8741,25 +9068,25 @@ function parseExpected(input, path18) {
|
|
|
8741
9068
|
};
|
|
8742
9069
|
}
|
|
8743
9070
|
function parseQuery(input, index) {
|
|
8744
|
-
const
|
|
9071
|
+
const path19 = `queries[${index}]`;
|
|
8745
9072
|
if (!isRecord2(input)) {
|
|
8746
|
-
throw new Error(`${
|
|
9073
|
+
throw new Error(`${path19} must be an object`);
|
|
8747
9074
|
}
|
|
8748
9075
|
const id = input.id;
|
|
8749
9076
|
const query = input.query;
|
|
8750
9077
|
const queryType = input.queryType;
|
|
8751
9078
|
const expected = input.expected;
|
|
8752
9079
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
8753
|
-
throw new Error(`${
|
|
9080
|
+
throw new Error(`${path19}.id must be a non-empty string`);
|
|
8754
9081
|
}
|
|
8755
9082
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
8756
|
-
throw new Error(`${
|
|
9083
|
+
throw new Error(`${path19}.query must be a non-empty string`);
|
|
8757
9084
|
}
|
|
8758
9085
|
return {
|
|
8759
9086
|
id,
|
|
8760
9087
|
query,
|
|
8761
|
-
queryType: parseQueryType(queryType, `${
|
|
8762
|
-
expected: parseExpected(expected, `${
|
|
9088
|
+
queryType: parseQueryType(queryType, `${path19}.queryType`),
|
|
9089
|
+
expected: parseExpected(expected, `${path19}.expected`)
|
|
8763
9090
|
};
|
|
8764
9091
|
}
|
|
8765
9092
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -8942,13 +9269,13 @@ async function runEvaluation(options) {
|
|
|
8942
9269
|
};
|
|
8943
9270
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
8944
9271
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
8945
|
-
writeJson(
|
|
8946
|
-
writeJson(
|
|
9272
|
+
writeJson(path13.join(outputDir, "summary.json"), summary);
|
|
9273
|
+
writeJson(path13.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
8947
9274
|
let comparison;
|
|
8948
9275
|
if (againstPath) {
|
|
8949
9276
|
const baseline = loadSummary(againstPath);
|
|
8950
9277
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
8951
|
-
writeJson(
|
|
9278
|
+
writeJson(path13.join(outputDir, "compare.json"), comparison);
|
|
8952
9279
|
}
|
|
8953
9280
|
let gate;
|
|
8954
9281
|
if (options.ciMode) {
|
|
@@ -8961,7 +9288,7 @@ async function runEvaluation(options) {
|
|
|
8961
9288
|
if (existsSync8(resolvedBaseline)) {
|
|
8962
9289
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
8963
9290
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
8964
|
-
writeJson(
|
|
9291
|
+
writeJson(path13.join(outputDir, "compare.json"), comparison);
|
|
8965
9292
|
} else if (budget.failOnMissingBaseline) {
|
|
8966
9293
|
throw new Error(
|
|
8967
9294
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -8971,7 +9298,7 @@ async function runEvaluation(options) {
|
|
|
8971
9298
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
8972
9299
|
}
|
|
8973
9300
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
8974
|
-
writeText(
|
|
9301
|
+
writeText(path13.join(outputDir, "summary.md"), markdown);
|
|
8975
9302
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
8976
9303
|
} finally {
|
|
8977
9304
|
await indexer.close();
|
|
@@ -9029,23 +9356,23 @@ async function runSweep(options, sweep) {
|
|
|
9029
9356
|
bestByMrrAt10,
|
|
9030
9357
|
bestByP95Latency
|
|
9031
9358
|
};
|
|
9032
|
-
writeJson(
|
|
9359
|
+
writeJson(path13.join(outputDir, "compare.json"), aggregate);
|
|
9033
9360
|
const md = createSummaryMarkdown(
|
|
9034
9361
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
9035
9362
|
bestByHitAt5?.comparison,
|
|
9036
9363
|
void 0,
|
|
9037
9364
|
aggregate
|
|
9038
9365
|
);
|
|
9039
|
-
writeText(
|
|
9040
|
-
writeJson(
|
|
9366
|
+
writeText(path13.join(outputDir, "summary.md"), md);
|
|
9367
|
+
writeJson(path13.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
9041
9368
|
return { outputDir, aggregate };
|
|
9042
9369
|
}
|
|
9043
9370
|
|
|
9044
9371
|
// src/eval/cli.ts
|
|
9045
|
-
import * as
|
|
9372
|
+
import * as path15 from "path";
|
|
9046
9373
|
|
|
9047
9374
|
// src/eval/cli-parser.ts
|
|
9048
|
-
import * as
|
|
9375
|
+
import * as path14 from "path";
|
|
9049
9376
|
function printUsage() {
|
|
9050
9377
|
console.log(`
|
|
9051
9378
|
Usage:
|
|
@@ -9117,12 +9444,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9117
9444
|
const arg = argv[i];
|
|
9118
9445
|
const next = argv[i + 1];
|
|
9119
9446
|
if (arg === "--project" && next) {
|
|
9120
|
-
parsed.projectRoot =
|
|
9447
|
+
parsed.projectRoot = path14.resolve(cwd, next);
|
|
9121
9448
|
i += 1;
|
|
9122
9449
|
continue;
|
|
9123
9450
|
}
|
|
9124
9451
|
if (arg === "--config" && next) {
|
|
9125
|
-
parsed.configPath =
|
|
9452
|
+
parsed.configPath = path14.resolve(cwd, next);
|
|
9126
9453
|
i += 1;
|
|
9127
9454
|
continue;
|
|
9128
9455
|
}
|
|
@@ -9318,22 +9645,22 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9318
9645
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
9319
9646
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
9320
9647
|
}
|
|
9321
|
-
const currentSummary = loadSummary(
|
|
9648
|
+
const currentSummary = loadSummary(path15.resolve(parsed.projectRoot, currentPath), {
|
|
9322
9649
|
allowLegacyDiversityMetrics: true
|
|
9323
9650
|
});
|
|
9324
|
-
const baselineSummary = loadSummary(
|
|
9651
|
+
const baselineSummary = loadSummary(path15.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
9325
9652
|
allowLegacyDiversityMetrics: true
|
|
9326
9653
|
});
|
|
9327
9654
|
const comparison = compareSummaries(
|
|
9328
9655
|
currentSummary,
|
|
9329
9656
|
baselineSummary,
|
|
9330
|
-
|
|
9657
|
+
path15.resolve(parsed.projectRoot, parsed.againstPath)
|
|
9331
9658
|
);
|
|
9332
|
-
const outputDir = createRunDirectory(
|
|
9659
|
+
const outputDir = createRunDirectory(path15.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
9333
9660
|
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
9334
|
-
writeJson(
|
|
9335
|
-
writeText(
|
|
9336
|
-
writeJson(
|
|
9661
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9662
|
+
writeText(path15.join(outputDir, "summary.md"), summaryMd);
|
|
9663
|
+
writeJson(path15.join(outputDir, "summary.json"), currentSummary);
|
|
9337
9664
|
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
9338
9665
|
return 0;
|
|
9339
9666
|
}
|
|
@@ -9342,7 +9669,7 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9342
9669
|
|
|
9343
9670
|
// src/mcp-server.ts
|
|
9344
9671
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9345
|
-
import * as
|
|
9672
|
+
import * as path17 from "path";
|
|
9346
9673
|
import { existsSync as existsSync10 } from "fs";
|
|
9347
9674
|
|
|
9348
9675
|
// src/mcp-server/register-prompts.ts
|
|
@@ -9441,7 +9768,7 @@ import { z as z2 } from "zod";
|
|
|
9441
9768
|
// src/config/merger.ts
|
|
9442
9769
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
9443
9770
|
import * as os5 from "os";
|
|
9444
|
-
import * as
|
|
9771
|
+
import * as path16 from "path";
|
|
9445
9772
|
var PROJECT_OVERRIDE_KEYS = [
|
|
9446
9773
|
"embeddingProvider",
|
|
9447
9774
|
"customProvider",
|
|
@@ -9474,8 +9801,8 @@ function mergeUniqueStringArray(values) {
|
|
|
9474
9801
|
return [...new Set(values.map((value) => String(value).trim()))];
|
|
9475
9802
|
}
|
|
9476
9803
|
function normalizeKnowledgeBasePath(value) {
|
|
9477
|
-
let normalized =
|
|
9478
|
-
const root =
|
|
9804
|
+
let normalized = path16.normalize(String(value).trim());
|
|
9805
|
+
const root = path16.parse(normalized).root;
|
|
9479
9806
|
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
9480
9807
|
normalized = normalized.slice(0, -1);
|
|
9481
9808
|
}
|
|
@@ -9525,8 +9852,8 @@ function loadJsonFile(filePath) {
|
|
|
9525
9852
|
return null;
|
|
9526
9853
|
}
|
|
9527
9854
|
function materializeLocalProjectConfig(projectRoot, config) {
|
|
9528
|
-
const localConfigPath =
|
|
9529
|
-
mkdirSync4(
|
|
9855
|
+
const localConfigPath = path16.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9856
|
+
mkdirSync4(path16.dirname(localConfigPath), { recursive: true });
|
|
9530
9857
|
writeFileSync4(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
9531
9858
|
return localConfigPath;
|
|
9532
9859
|
}
|
|
@@ -9537,7 +9864,7 @@ function loadProjectConfigLayer(projectRoot) {
|
|
|
9537
9864
|
return {};
|
|
9538
9865
|
}
|
|
9539
9866
|
const normalizedConfig = { ...projectConfig };
|
|
9540
|
-
const projectConfigBaseDir =
|
|
9867
|
+
const projectConfigBaseDir = path16.dirname(path16.dirname(projectConfigPath));
|
|
9541
9868
|
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
9542
9869
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
9543
9870
|
normalizedConfig.knowledgeBases,
|
|
@@ -9756,6 +10083,57 @@ ${truncateContent(r.content)}
|
|
|
9756
10083
|
return formatted.join("\n\n");
|
|
9757
10084
|
}
|
|
9758
10085
|
|
|
10086
|
+
// src/tools/format-pr-impact.ts
|
|
10087
|
+
function formatPrImpact(result) {
|
|
10088
|
+
const lines = [];
|
|
10089
|
+
lines.push(`\u2192 Files changed: ${result.changedFiles.length}`);
|
|
10090
|
+
for (const file of result.changedFiles) {
|
|
10091
|
+
lines.push(` - ${file}`);
|
|
10092
|
+
}
|
|
10093
|
+
const directCount = result.directSymbols.length;
|
|
10094
|
+
const transitiveCount = result.transitiveCallers.length;
|
|
10095
|
+
const directionLabel = result.direction === "callees" ? "callees" : result.direction === "both" ? "reachable (callers + callees)" : "callers";
|
|
10096
|
+
lines.push(
|
|
10097
|
+
`\u2192 Symbols affected: ${result.totalAffected} (${directCount} direct, ${transitiveCount} transitive ${directionLabel})`
|
|
10098
|
+
);
|
|
10099
|
+
if (directCount > 0) {
|
|
10100
|
+
lines.push(
|
|
10101
|
+
` Direct: ${result.directSymbols.map((s) => `${s.name} (${s.kind})`).join(", ")}`
|
|
10102
|
+
);
|
|
10103
|
+
}
|
|
10104
|
+
if (transitiveCount > 0) {
|
|
10105
|
+
lines.push(
|
|
10106
|
+
` Transitive ${directionLabel}: ${result.transitiveCallers.map((s) => s.name).join(", ")}`
|
|
10107
|
+
);
|
|
10108
|
+
}
|
|
10109
|
+
if (result.communities.length > 0) {
|
|
10110
|
+
lines.push(
|
|
10111
|
+
`\u2192 Communities touched: ${result.communities.map((c) => c.label).join(", ")}`
|
|
10112
|
+
);
|
|
10113
|
+
for (const community of result.communities) {
|
|
10114
|
+
lines.push(
|
|
10115
|
+
` - ${community.label}: ${community.symbolCount} symbols`
|
|
10116
|
+
);
|
|
10117
|
+
}
|
|
10118
|
+
} else {
|
|
10119
|
+
lines.push("\u2192 Communities touched: none");
|
|
10120
|
+
}
|
|
10121
|
+
lines.push(`\u2192 Risk: ${result.riskLevel} \u2014 ${result.riskReason}`);
|
|
10122
|
+
if (result.hubNodes.length > 0) {
|
|
10123
|
+
lines.push(" Hub nodes in change scope:");
|
|
10124
|
+
for (const hub of result.hubNodes) {
|
|
10125
|
+
lines.push(` - ${hub.name} (${hub.callerCount} callers) at ${hub.filePath}`);
|
|
10126
|
+
}
|
|
10127
|
+
}
|
|
10128
|
+
if (result.conflictingPRs && result.conflictingPRs.length > 0) {
|
|
10129
|
+
const conflictList = result.conflictingPRs.map(
|
|
10130
|
+
(c) => `PR #${c.pr} (also touches ${c.overlappingCommunities.join(", ")} community)`
|
|
10131
|
+
);
|
|
10132
|
+
lines.push(`\u2192 Potential conflicts with: ${conflictList.join(", ")}`);
|
|
10133
|
+
}
|
|
10134
|
+
return lines.join("\n");
|
|
10135
|
+
}
|
|
10136
|
+
|
|
9759
10137
|
// src/mcp-server/shared.ts
|
|
9760
10138
|
var MAX_CONTENT_LINES2 = 30;
|
|
9761
10139
|
function truncateContent2(content) {
|
|
@@ -10050,19 +10428,49 @@ ${formatted.join("\n")}` }] };
|
|
|
10050
10428
|
async (args) => {
|
|
10051
10429
|
await runtime.ensureInitialized();
|
|
10052
10430
|
const indexer = runtime.getIndexer();
|
|
10053
|
-
const
|
|
10054
|
-
if (
|
|
10431
|
+
const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
|
|
10432
|
+
if (path19.length === 0) {
|
|
10055
10433
|
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.` }] };
|
|
10056
10434
|
}
|
|
10057
|
-
const formatted =
|
|
10435
|
+
const formatted = path19.map((hop, i) => {
|
|
10058
10436
|
const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10059
10437
|
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10060
10438
|
return `${prefix} ${hop.symbolName}${location}`;
|
|
10061
10439
|
});
|
|
10062
|
-
return { content: [{ type: "text", text: `Path (${
|
|
10440
|
+
return { content: [{ type: "text", text: `Path (${path19.length} hops):
|
|
10063
10441
|
${formatted.join("\n")}` }] };
|
|
10064
10442
|
}
|
|
10065
10443
|
);
|
|
10444
|
+
server.tool(
|
|
10445
|
+
"pr_impact",
|
|
10446
|
+
"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.",
|
|
10447
|
+
{
|
|
10448
|
+
pr: z2.number().optional().describe("Pull request number to analyze"),
|
|
10449
|
+
branch: z2.string().optional().describe("Branch name to analyze (defaults to current branch)"),
|
|
10450
|
+
maxDepth: z2.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
|
|
10451
|
+
hubThreshold: z2.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
|
|
10452
|
+
checkConflicts: z2.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
|
|
10453
|
+
direction: z2.enum(["callers", "callees", "both"]).optional().default("both").describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
|
|
10454
|
+
},
|
|
10455
|
+
async (args) => {
|
|
10456
|
+
await runtime.ensureInitialized();
|
|
10457
|
+
const indexer = runtime.getIndexer();
|
|
10458
|
+
try {
|
|
10459
|
+
const result = await indexer.getPrImpact({
|
|
10460
|
+
pr: args.pr,
|
|
10461
|
+
branch: args.branch,
|
|
10462
|
+
maxDepth: args.maxDepth,
|
|
10463
|
+
hubThreshold: args.hubThreshold,
|
|
10464
|
+
checkConflicts: args.checkConflicts,
|
|
10465
|
+
direction: args.direction
|
|
10466
|
+
});
|
|
10467
|
+
return { content: [{ type: "text", text: formatPrImpact(result) }] };
|
|
10468
|
+
} catch (error) {
|
|
10469
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10470
|
+
return { content: [{ type: "text", text: `Error analyzing PR impact: ${message}` }] };
|
|
10471
|
+
}
|
|
10472
|
+
}
|
|
10473
|
+
);
|
|
10066
10474
|
}
|
|
10067
10475
|
|
|
10068
10476
|
// src/mcp-server.ts
|
|
@@ -10081,12 +10489,12 @@ function createMcpServer(projectRoot, config) {
|
|
|
10081
10489
|
if (config.scope !== "project") {
|
|
10082
10490
|
return false;
|
|
10083
10491
|
}
|
|
10084
|
-
const localIndexPath =
|
|
10492
|
+
const localIndexPath = path17.join(projectRoot, ".opencode", "index");
|
|
10085
10493
|
const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
|
|
10086
10494
|
if (!mainRepoRoot) {
|
|
10087
10495
|
return false;
|
|
10088
10496
|
}
|
|
10089
|
-
const inheritedIndexPath =
|
|
10497
|
+
const inheritedIndexPath = path17.join(mainRepoRoot, ".opencode", "index");
|
|
10090
10498
|
return !existsSync10(localIndexPath) && existsSync10(inheritedIndexPath);
|
|
10091
10499
|
}
|
|
10092
10500
|
async function ensureInitialized() {
|
|
@@ -10112,9 +10520,9 @@ function parseArgs(argv) {
|
|
|
10112
10520
|
let config;
|
|
10113
10521
|
for (let i = 2; i < argv.length; i++) {
|
|
10114
10522
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
10115
|
-
project =
|
|
10523
|
+
project = path18.resolve(argv[++i]);
|
|
10116
10524
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
10117
|
-
config =
|
|
10525
|
+
config = path18.resolve(argv[++i]);
|
|
10118
10526
|
}
|
|
10119
10527
|
}
|
|
10120
10528
|
return { project, config };
|