opencode-codebase-index 0.10.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 +635 -178
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +635 -178
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +805 -338
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +805 -338
- 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 +4 -3
- package/skill/SKILL.md +6 -3
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
8
8
|
var __getProtoOf = Object.getPrototypeOf;
|
|
9
9
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
10
|
var __commonJS = (cb, mod) => function __require() {
|
|
11
|
-
|
|
11
|
+
try {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw mod = 0, e;
|
|
15
|
+
}
|
|
12
16
|
};
|
|
13
17
|
var __copyProps = (to, from, except, desc) => {
|
|
14
18
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -487,7 +491,7 @@ var require_ignore = __commonJS({
|
|
|
487
491
|
// path matching.
|
|
488
492
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
489
493
|
// @returns {TestResult} true if a file is ignored
|
|
490
|
-
test(
|
|
494
|
+
test(path19, checkUnignored, mode) {
|
|
491
495
|
let ignored = false;
|
|
492
496
|
let unignored = false;
|
|
493
497
|
let matchedRule;
|
|
@@ -496,7 +500,7 @@ var require_ignore = __commonJS({
|
|
|
496
500
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
497
501
|
return;
|
|
498
502
|
}
|
|
499
|
-
const matched = rule[mode].test(
|
|
503
|
+
const matched = rule[mode].test(path19);
|
|
500
504
|
if (!matched) {
|
|
501
505
|
return;
|
|
502
506
|
}
|
|
@@ -517,17 +521,17 @@ var require_ignore = __commonJS({
|
|
|
517
521
|
var throwError = (message, Ctor) => {
|
|
518
522
|
throw new Ctor(message);
|
|
519
523
|
};
|
|
520
|
-
var checkPath = (
|
|
521
|
-
if (!isString(
|
|
524
|
+
var checkPath = (path19, originalPath, doThrow) => {
|
|
525
|
+
if (!isString(path19)) {
|
|
522
526
|
return doThrow(
|
|
523
527
|
`path must be a string, but got \`${originalPath}\``,
|
|
524
528
|
TypeError
|
|
525
529
|
);
|
|
526
530
|
}
|
|
527
|
-
if (!
|
|
531
|
+
if (!path19) {
|
|
528
532
|
return doThrow(`path must not be empty`, TypeError);
|
|
529
533
|
}
|
|
530
|
-
if (checkPath.isNotRelative(
|
|
534
|
+
if (checkPath.isNotRelative(path19)) {
|
|
531
535
|
const r = "`path.relative()`d";
|
|
532
536
|
return doThrow(
|
|
533
537
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -536,7 +540,7 @@ var require_ignore = __commonJS({
|
|
|
536
540
|
}
|
|
537
541
|
return true;
|
|
538
542
|
};
|
|
539
|
-
var isNotRelative = (
|
|
543
|
+
var isNotRelative = (path19) => REGEX_TEST_INVALID_PATH.test(path19);
|
|
540
544
|
checkPath.isNotRelative = isNotRelative;
|
|
541
545
|
checkPath.convert = (p) => p;
|
|
542
546
|
var Ignore2 = class {
|
|
@@ -566,19 +570,19 @@ var require_ignore = __commonJS({
|
|
|
566
570
|
}
|
|
567
571
|
// @returns {TestResult}
|
|
568
572
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
569
|
-
const
|
|
573
|
+
const path19 = originalPath && checkPath.convert(originalPath);
|
|
570
574
|
checkPath(
|
|
571
|
-
|
|
575
|
+
path19,
|
|
572
576
|
originalPath,
|
|
573
577
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
574
578
|
);
|
|
575
|
-
return this._t(
|
|
579
|
+
return this._t(path19, cache, checkUnignored, slices);
|
|
576
580
|
}
|
|
577
|
-
checkIgnore(
|
|
578
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
579
|
-
return this.test(
|
|
581
|
+
checkIgnore(path19) {
|
|
582
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path19)) {
|
|
583
|
+
return this.test(path19);
|
|
580
584
|
}
|
|
581
|
-
const slices =
|
|
585
|
+
const slices = path19.split(SLASH).filter(Boolean);
|
|
582
586
|
slices.pop();
|
|
583
587
|
if (slices.length) {
|
|
584
588
|
const parent = this._t(
|
|
@@ -591,18 +595,18 @@ var require_ignore = __commonJS({
|
|
|
591
595
|
return parent;
|
|
592
596
|
}
|
|
593
597
|
}
|
|
594
|
-
return this._rules.test(
|
|
598
|
+
return this._rules.test(path19, false, MODE_CHECK_IGNORE);
|
|
595
599
|
}
|
|
596
|
-
_t(
|
|
597
|
-
if (
|
|
598
|
-
return cache[
|
|
600
|
+
_t(path19, cache, checkUnignored, slices) {
|
|
601
|
+
if (path19 in cache) {
|
|
602
|
+
return cache[path19];
|
|
599
603
|
}
|
|
600
604
|
if (!slices) {
|
|
601
|
-
slices =
|
|
605
|
+
slices = path19.split(SLASH).filter(Boolean);
|
|
602
606
|
}
|
|
603
607
|
slices.pop();
|
|
604
608
|
if (!slices.length) {
|
|
605
|
-
return cache[
|
|
609
|
+
return cache[path19] = this._rules.test(path19, checkUnignored, MODE_IGNORE);
|
|
606
610
|
}
|
|
607
611
|
const parent = this._t(
|
|
608
612
|
slices.join(SLASH) + SLASH,
|
|
@@ -610,29 +614,29 @@ var require_ignore = __commonJS({
|
|
|
610
614
|
checkUnignored,
|
|
611
615
|
slices
|
|
612
616
|
);
|
|
613
|
-
return cache[
|
|
617
|
+
return cache[path19] = parent.ignored ? parent : this._rules.test(path19, checkUnignored, MODE_IGNORE);
|
|
614
618
|
}
|
|
615
|
-
ignores(
|
|
616
|
-
return this._test(
|
|
619
|
+
ignores(path19) {
|
|
620
|
+
return this._test(path19, this._ignoreCache, false).ignored;
|
|
617
621
|
}
|
|
618
622
|
createFilter() {
|
|
619
|
-
return (
|
|
623
|
+
return (path19) => !this.ignores(path19);
|
|
620
624
|
}
|
|
621
625
|
filter(paths) {
|
|
622
626
|
return makeArray(paths).filter(this.createFilter());
|
|
623
627
|
}
|
|
624
628
|
// @returns {TestResult}
|
|
625
|
-
test(
|
|
626
|
-
return this._test(
|
|
629
|
+
test(path19) {
|
|
630
|
+
return this._test(path19, this._testCache, true);
|
|
627
631
|
}
|
|
628
632
|
};
|
|
629
633
|
var factory = (options) => new Ignore2(options);
|
|
630
|
-
var isPathValid = (
|
|
634
|
+
var isPathValid = (path19) => checkPath(path19 && checkPath.convert(path19), path19, RETURN_FALSE);
|
|
631
635
|
var setupWindows = () => {
|
|
632
636
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
633
637
|
checkPath.convert = makePosix;
|
|
634
638
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
635
|
-
checkPath.isNotRelative = (
|
|
639
|
+
checkPath.isNotRelative = (path19) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path19) || isNotRelative(path19);
|
|
636
640
|
};
|
|
637
641
|
if (
|
|
638
642
|
// Detect `process` so that it can run in browsers.
|
|
@@ -649,7 +653,7 @@ var require_ignore = __commonJS({
|
|
|
649
653
|
|
|
650
654
|
// src/cli.ts
|
|
651
655
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
652
|
-
import * as
|
|
656
|
+
import * as path18 from "path";
|
|
653
657
|
|
|
654
658
|
// src/config/constants.ts
|
|
655
659
|
var DEFAULT_INCLUDE = [
|
|
@@ -795,6 +799,7 @@ function getDefaultSearchConfig() {
|
|
|
795
799
|
rerankTopN: 20,
|
|
796
800
|
contextLines: 0,
|
|
797
801
|
routingHints: true,
|
|
802
|
+
routingGraphHandoffHints: false,
|
|
798
803
|
routingHintRole: "system"
|
|
799
804
|
};
|
|
800
805
|
}
|
|
@@ -917,6 +922,7 @@ function parseConfig(raw) {
|
|
|
917
922
|
rerankTopN: typeof rawSearch.rerankTopN === "number" ? Math.min(200, Math.max(0, Math.floor(rawSearch.rerankTopN))) : defaultSearch.rerankTopN,
|
|
918
923
|
contextLines: typeof rawSearch.contextLines === "number" ? Math.min(50, Math.max(0, rawSearch.contextLines)) : defaultSearch.contextLines,
|
|
919
924
|
routingHints: typeof rawSearch.routingHints === "boolean" ? rawSearch.routingHints : defaultSearch.routingHints,
|
|
925
|
+
routingGraphHandoffHints: typeof rawSearch.routingGraphHandoffHints === "boolean" ? rawSearch.routingGraphHandoffHints : defaultSearch.routingGraphHandoffHints,
|
|
920
926
|
routingHintRole: rawSearch.routingHintRole === "developer" || rawSearch.routingHintRole === "system" ? rawSearch.routingHintRole : defaultSearch.routingHintRole
|
|
921
927
|
};
|
|
922
928
|
const rawDebug = input.debug && typeof input.debug === "object" ? input.debug : {};
|
|
@@ -1075,9 +1081,9 @@ import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
1075
1081
|
import * as path from "path";
|
|
1076
1082
|
|
|
1077
1083
|
// src/eval/report-formatters.ts
|
|
1078
|
-
function assertFiniteNumber(value,
|
|
1084
|
+
function assertFiniteNumber(value, path19) {
|
|
1079
1085
|
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1080
|
-
throw new Error(`${
|
|
1086
|
+
throw new Error(`${path19} must be a finite number`);
|
|
1081
1087
|
}
|
|
1082
1088
|
return value;
|
|
1083
1089
|
}
|
|
@@ -1266,13 +1272,15 @@ function buildPerQueryArtifact(perQuery) {
|
|
|
1266
1272
|
|
|
1267
1273
|
// src/eval/runner.ts
|
|
1268
1274
|
import { existsSync as existsSync8 } from "fs";
|
|
1269
|
-
import * as
|
|
1275
|
+
import * as path13 from "path";
|
|
1270
1276
|
import { performance as performance3 } from "perf_hooks";
|
|
1271
1277
|
|
|
1272
1278
|
// src/indexer/index.ts
|
|
1273
1279
|
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync2, renameSync, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
|
|
1274
|
-
import * as
|
|
1280
|
+
import * as path10 from "path";
|
|
1275
1281
|
import { performance as performance2 } from "perf_hooks";
|
|
1282
|
+
import { execFile as execFile2 } from "child_process";
|
|
1283
|
+
import { promisify as promisify2 } from "util";
|
|
1276
1284
|
|
|
1277
1285
|
// node_modules/eventemitter3/index.mjs
|
|
1278
1286
|
var import_index = __toESM(require_eventemitter3(), 1);
|
|
@@ -1296,7 +1304,7 @@ function pTimeout(promise, options) {
|
|
|
1296
1304
|
} = options;
|
|
1297
1305
|
let timer;
|
|
1298
1306
|
let abortHandler;
|
|
1299
|
-
const wrappedPromise = new Promise((
|
|
1307
|
+
const wrappedPromise = new Promise((resolve11, reject) => {
|
|
1300
1308
|
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
|
|
1301
1309
|
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
|
1302
1310
|
}
|
|
@@ -1310,7 +1318,7 @@ function pTimeout(promise, options) {
|
|
|
1310
1318
|
};
|
|
1311
1319
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1312
1320
|
}
|
|
1313
|
-
promise.then(
|
|
1321
|
+
promise.then(resolve11, reject);
|
|
1314
1322
|
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
1315
1323
|
return;
|
|
1316
1324
|
}
|
|
@@ -1318,7 +1326,7 @@ function pTimeout(promise, options) {
|
|
|
1318
1326
|
timer = customTimers.setTimeout.call(void 0, () => {
|
|
1319
1327
|
if (fallback) {
|
|
1320
1328
|
try {
|
|
1321
|
-
|
|
1329
|
+
resolve11(fallback());
|
|
1322
1330
|
} catch (error) {
|
|
1323
1331
|
reject(error);
|
|
1324
1332
|
}
|
|
@@ -1328,7 +1336,7 @@ function pTimeout(promise, options) {
|
|
|
1328
1336
|
promise.cancel();
|
|
1329
1337
|
}
|
|
1330
1338
|
if (message === false) {
|
|
1331
|
-
|
|
1339
|
+
resolve11();
|
|
1332
1340
|
} else if (message instanceof Error) {
|
|
1333
1341
|
reject(message);
|
|
1334
1342
|
} else {
|
|
@@ -1730,7 +1738,7 @@ var PQueue = class extends import_index.default {
|
|
|
1730
1738
|
// Assign unique ID if not provided
|
|
1731
1739
|
id: options.id ?? (this.#idAssigner++).toString()
|
|
1732
1740
|
};
|
|
1733
|
-
return new Promise((
|
|
1741
|
+
return new Promise((resolve11, reject) => {
|
|
1734
1742
|
const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
|
|
1735
1743
|
let cleanupQueueAbortHandler = () => void 0;
|
|
1736
1744
|
const run = async () => {
|
|
@@ -1770,7 +1778,7 @@ var PQueue = class extends import_index.default {
|
|
|
1770
1778
|
})]);
|
|
1771
1779
|
}
|
|
1772
1780
|
const result = await operation;
|
|
1773
|
-
|
|
1781
|
+
resolve11(result);
|
|
1774
1782
|
this.emit("completed", result);
|
|
1775
1783
|
} catch (error) {
|
|
1776
1784
|
reject(error);
|
|
@@ -1958,13 +1966,13 @@ var PQueue = class extends import_index.default {
|
|
|
1958
1966
|
});
|
|
1959
1967
|
}
|
|
1960
1968
|
async #onEvent(event, filter) {
|
|
1961
|
-
return new Promise((
|
|
1969
|
+
return new Promise((resolve11) => {
|
|
1962
1970
|
const listener = () => {
|
|
1963
1971
|
if (filter && !filter()) {
|
|
1964
1972
|
return;
|
|
1965
1973
|
}
|
|
1966
1974
|
this.off(event, listener);
|
|
1967
|
-
|
|
1975
|
+
resolve11();
|
|
1968
1976
|
};
|
|
1969
1977
|
this.on(event, listener);
|
|
1970
1978
|
});
|
|
@@ -2250,7 +2258,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2250
2258
|
const finalDelay = Math.min(delayTime, remainingTime);
|
|
2251
2259
|
options.signal?.throwIfAborted();
|
|
2252
2260
|
if (finalDelay > 0) {
|
|
2253
|
-
await new Promise((
|
|
2261
|
+
await new Promise((resolve11, reject) => {
|
|
2254
2262
|
const onAbort = () => {
|
|
2255
2263
|
clearTimeout(timeoutToken);
|
|
2256
2264
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -2258,7 +2266,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
2258
2266
|
};
|
|
2259
2267
|
const timeoutToken = setTimeout(() => {
|
|
2260
2268
|
options.signal?.removeEventListener("abort", onAbort);
|
|
2261
|
-
|
|
2269
|
+
resolve11();
|
|
2262
2270
|
}, finalDelay);
|
|
2263
2271
|
if (options.unref) {
|
|
2264
2272
|
timeoutToken.unref?.();
|
|
@@ -2499,6 +2507,8 @@ var BaseEmbeddingProvider = class {
|
|
|
2499
2507
|
this.credentials = credentials;
|
|
2500
2508
|
this.modelInfo = modelInfo;
|
|
2501
2509
|
}
|
|
2510
|
+
credentials;
|
|
2511
|
+
modelInfo;
|
|
2502
2512
|
async embedQuery(query) {
|
|
2503
2513
|
const result = await this.embedBatch([query]);
|
|
2504
2514
|
return {
|
|
@@ -3675,6 +3685,15 @@ function createMockNativeBinding() {
|
|
|
3675
3685
|
close() {
|
|
3676
3686
|
throw error;
|
|
3677
3687
|
}
|
|
3688
|
+
getTransitiveReachability() {
|
|
3689
|
+
throw error;
|
|
3690
|
+
}
|
|
3691
|
+
detectCommunities() {
|
|
3692
|
+
throw error;
|
|
3693
|
+
}
|
|
3694
|
+
computeCentrality() {
|
|
3695
|
+
throw error;
|
|
3696
|
+
}
|
|
3678
3697
|
}
|
|
3679
3698
|
};
|
|
3680
3699
|
}
|
|
@@ -4225,6 +4244,14 @@ var Database = class {
|
|
|
4225
4244
|
this.throwIfClosed();
|
|
4226
4245
|
return this.inner.getSymbolsByNameCi(name);
|
|
4227
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
|
+
}
|
|
4228
4255
|
deleteSymbolsByFile(filePath) {
|
|
4229
4256
|
this.throwIfClosed();
|
|
4230
4257
|
return this.inner.deleteSymbolsByFile(filePath);
|
|
@@ -4238,17 +4265,17 @@ var Database = class {
|
|
|
4238
4265
|
if (edges.length === 0) return;
|
|
4239
4266
|
this.inner.upsertCallEdgesBatch(edges);
|
|
4240
4267
|
}
|
|
4241
|
-
getCallers(targetName, branch) {
|
|
4268
|
+
getCallers(targetName, branch, callTypeFilter) {
|
|
4242
4269
|
this.throwIfClosed();
|
|
4243
|
-
return this.inner.getCallers(targetName, branch);
|
|
4270
|
+
return this.inner.getCallers(targetName, branch, callTypeFilter ?? null);
|
|
4244
4271
|
}
|
|
4245
|
-
getCallersWithContext(targetName, branch) {
|
|
4272
|
+
getCallersWithContext(targetName, branch, callTypeFilter) {
|
|
4246
4273
|
this.throwIfClosed();
|
|
4247
|
-
return this.inner.getCallersWithContext(targetName, branch);
|
|
4274
|
+
return this.inner.getCallersWithContext(targetName, branch, callTypeFilter ?? null);
|
|
4248
4275
|
}
|
|
4249
|
-
getCallees(symbolId, branch) {
|
|
4276
|
+
getCallees(symbolId, branch, callTypeFilter) {
|
|
4250
4277
|
this.throwIfClosed();
|
|
4251
|
-
return this.inner.getCallees(symbolId, branch);
|
|
4278
|
+
return this.inner.getCallees(symbolId, branch, callTypeFilter ?? null);
|
|
4252
4279
|
}
|
|
4253
4280
|
deleteCallEdgesByFile(filePath) {
|
|
4254
4281
|
this.throwIfClosed();
|
|
@@ -4258,6 +4285,10 @@ var Database = class {
|
|
|
4258
4285
|
this.throwIfClosed();
|
|
4259
4286
|
this.inner.resolveCallEdge(edgeId, toSymbolId);
|
|
4260
4287
|
}
|
|
4288
|
+
findShortestPath(fromName, toName, branch, maxDepth) {
|
|
4289
|
+
this.throwIfClosed();
|
|
4290
|
+
return this.inner.findShortestPath(fromName, toName, branch, maxDepth ?? null);
|
|
4291
|
+
}
|
|
4261
4292
|
addSymbolsToBranch(branch, symbolIds) {
|
|
4262
4293
|
this.throwIfClosed();
|
|
4263
4294
|
this.inner.addSymbolsToBranch(branch, symbolIds);
|
|
@@ -4298,6 +4329,23 @@ var Database = class {
|
|
|
4298
4329
|
this.throwIfClosed();
|
|
4299
4330
|
return this.inner.gcOrphanCallEdges();
|
|
4300
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
|
+
}
|
|
4301
4349
|
};
|
|
4302
4350
|
|
|
4303
4351
|
// src/git/index.ts
|
|
@@ -4471,6 +4519,106 @@ function resolveProjectIndexPath(projectRoot, scope) {
|
|
|
4471
4519
|
return resolveWorktreeFallbackPath(projectRoot, PROJECT_INDEX_RELATIVE_PATH) ?? localIndexPath;
|
|
4472
4520
|
}
|
|
4473
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
|
+
|
|
4474
4622
|
// src/indexer/index.ts
|
|
4475
4623
|
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
4476
4624
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
@@ -4478,6 +4626,7 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
|
4478
4626
|
"function_declaration",
|
|
4479
4627
|
"function",
|
|
4480
4628
|
"arrow_function",
|
|
4629
|
+
"export_statement",
|
|
4481
4630
|
"method_definition",
|
|
4482
4631
|
"class_declaration",
|
|
4483
4632
|
"interface_declaration",
|
|
@@ -4516,7 +4665,7 @@ function float32ArrayToBuffer(arr) {
|
|
|
4516
4665
|
function bufferToFloat32Array(buf) {
|
|
4517
4666
|
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
4518
4667
|
}
|
|
4519
|
-
function
|
|
4668
|
+
function getErrorMessage2(error) {
|
|
4520
4669
|
if (error instanceof Error) {
|
|
4521
4670
|
return error.message;
|
|
4522
4671
|
}
|
|
@@ -4529,7 +4678,7 @@ function getErrorMessage(error) {
|
|
|
4529
4678
|
return String(error);
|
|
4530
4679
|
}
|
|
4531
4680
|
function isRateLimitError(error) {
|
|
4532
|
-
const message =
|
|
4681
|
+
const message = getErrorMessage2(error);
|
|
4533
4682
|
return message.includes("429") || message.toLowerCase().includes("rate limit") || message.toLowerCase().includes("too many requests");
|
|
4534
4683
|
}
|
|
4535
4684
|
function getSafeEmbeddingChunkTokenLimit(provider) {
|
|
@@ -4547,7 +4696,7 @@ function getDynamicBatchOptions(provider) {
|
|
|
4547
4696
|
return {};
|
|
4548
4697
|
}
|
|
4549
4698
|
function isSqliteCorruptionError(error) {
|
|
4550
|
-
const message =
|
|
4699
|
+
const message = getErrorMessage2(error).toLowerCase();
|
|
4551
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");
|
|
4552
4701
|
}
|
|
4553
4702
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
@@ -4709,9 +4858,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
4709
4858
|
return true;
|
|
4710
4859
|
}
|
|
4711
4860
|
function isPathWithinRoot(filePath, rootPath) {
|
|
4712
|
-
const normalizedFilePath =
|
|
4713
|
-
const normalizedRoot =
|
|
4714
|
-
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}`);
|
|
4715
4864
|
}
|
|
4716
4865
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
4717
4866
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5764,9 +5913,9 @@ var Indexer = class {
|
|
|
5764
5913
|
this.projectRoot = projectRoot;
|
|
5765
5914
|
this.config = config;
|
|
5766
5915
|
this.indexPath = this.getIndexPath();
|
|
5767
|
-
this.fileHashCachePath =
|
|
5768
|
-
this.failedBatchesPath =
|
|
5769
|
-
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");
|
|
5770
5919
|
this.logger = initializeLogger(config.debug);
|
|
5771
5920
|
}
|
|
5772
5921
|
getIndexPath() {
|
|
@@ -5798,14 +5947,14 @@ var Indexer = class {
|
|
|
5798
5947
|
}
|
|
5799
5948
|
atomicWriteSync(targetPath, data) {
|
|
5800
5949
|
const tempPath = `${targetPath}.tmp`;
|
|
5801
|
-
mkdirSync2(
|
|
5950
|
+
mkdirSync2(path10.dirname(targetPath), { recursive: true });
|
|
5802
5951
|
writeFileSync2(tempPath, data);
|
|
5803
5952
|
renameSync(tempPath, targetPath);
|
|
5804
5953
|
}
|
|
5805
5954
|
getScopedRoots() {
|
|
5806
|
-
const roots = /* @__PURE__ */ new Set([
|
|
5955
|
+
const roots = /* @__PURE__ */ new Set([path10.resolve(this.projectRoot)]);
|
|
5807
5956
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
5808
|
-
roots.add(
|
|
5957
|
+
roots.add(path10.resolve(this.projectRoot, kbRoot));
|
|
5809
5958
|
}
|
|
5810
5959
|
return Array.from(roots);
|
|
5811
5960
|
}
|
|
@@ -5814,22 +5963,29 @@ var Indexer = class {
|
|
|
5814
5963
|
if (this.config.scope !== "global") {
|
|
5815
5964
|
return branchName;
|
|
5816
5965
|
}
|
|
5817
|
-
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);
|
|
5818
5974
|
return `${projectHash}:${branchName}`;
|
|
5819
5975
|
}
|
|
5820
5976
|
getLegacyBranchCatalogKey() {
|
|
5821
5977
|
return this.currentBranch || "default";
|
|
5822
5978
|
}
|
|
5823
5979
|
getLegacyMigrationMetadataKey() {
|
|
5824
|
-
const projectHash = hashContent(
|
|
5980
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5825
5981
|
return `index.globalBranchMigration.${projectHash}`;
|
|
5826
5982
|
}
|
|
5827
5983
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
5828
|
-
const projectHash = hashContent(
|
|
5984
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5829
5985
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
5830
5986
|
}
|
|
5831
5987
|
getProjectForceReembedMetadataKey() {
|
|
5832
|
-
const projectHash = hashContent(
|
|
5988
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5833
5989
|
return `index.forceReembed.${projectHash}`;
|
|
5834
5990
|
}
|
|
5835
5991
|
hasProjectForceReembedPending() {
|
|
@@ -5923,7 +6079,7 @@ var Indexer = class {
|
|
|
5923
6079
|
if (!this.database) {
|
|
5924
6080
|
return { chunkIds, symbolIds };
|
|
5925
6081
|
}
|
|
5926
|
-
const projectRootPath =
|
|
6082
|
+
const projectRootPath = path10.resolve(this.projectRoot);
|
|
5927
6083
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
5928
6084
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
5929
6085
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -5946,7 +6102,7 @@ var Indexer = class {
|
|
|
5946
6102
|
if (this.config.scope !== "global") {
|
|
5947
6103
|
return this.getBranchCatalogCleanupKeys();
|
|
5948
6104
|
}
|
|
5949
|
-
const projectHash = hashContent(
|
|
6105
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
5950
6106
|
const keys = /* @__PURE__ */ new Set();
|
|
5951
6107
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
5952
6108
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6030,7 +6186,7 @@ var Indexer = class {
|
|
|
6030
6186
|
if (!this.database || this.config.scope !== "global") {
|
|
6031
6187
|
return false;
|
|
6032
6188
|
}
|
|
6033
|
-
const projectHash = hashContent(
|
|
6189
|
+
const projectHash = hashContent(path10.resolve(this.projectRoot)).slice(0, 16);
|
|
6034
6190
|
const roots = this.getScopedRoots();
|
|
6035
6191
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6036
6192
|
return this.database.getAllBranches().some(
|
|
@@ -6064,7 +6220,7 @@ var Indexer = class {
|
|
|
6064
6220
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6065
6221
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6066
6222
|
]);
|
|
6067
|
-
const projectRootPath =
|
|
6223
|
+
const projectRootPath = path10.resolve(this.projectRoot);
|
|
6068
6224
|
const projectLocalFilePaths = new Set(
|
|
6069
6225
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6070
6226
|
);
|
|
@@ -6317,7 +6473,7 @@ var Indexer = class {
|
|
|
6317
6473
|
this.logger.search("warn", "External reranker failed; using deterministic order", {
|
|
6318
6474
|
provider: reranker.provider,
|
|
6319
6475
|
model: reranker.model,
|
|
6320
|
-
error:
|
|
6476
|
+
error: getErrorMessage2(error)
|
|
6321
6477
|
});
|
|
6322
6478
|
return candidates;
|
|
6323
6479
|
}
|
|
@@ -6424,13 +6580,13 @@ var Indexer = class {
|
|
|
6424
6580
|
}
|
|
6425
6581
|
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
6426
6582
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6427
|
-
const storePath =
|
|
6583
|
+
const storePath = path10.join(this.indexPath, "vectors");
|
|
6428
6584
|
this.store = new VectorStore(storePath, dimensions);
|
|
6429
|
-
const indexFilePath =
|
|
6585
|
+
const indexFilePath = path10.join(this.indexPath, "vectors.usearch");
|
|
6430
6586
|
if (existsSync6(indexFilePath)) {
|
|
6431
6587
|
this.store.load();
|
|
6432
6588
|
}
|
|
6433
|
-
const invertedIndexPath =
|
|
6589
|
+
const invertedIndexPath = path10.join(this.indexPath, "inverted-index.json");
|
|
6434
6590
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6435
6591
|
try {
|
|
6436
6592
|
this.invertedIndex.load();
|
|
@@ -6440,7 +6596,7 @@ var Indexer = class {
|
|
|
6440
6596
|
}
|
|
6441
6597
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6442
6598
|
}
|
|
6443
|
-
const dbPath =
|
|
6599
|
+
const dbPath = path10.join(this.indexPath, "codebase.db");
|
|
6444
6600
|
let dbIsNew = !existsSync6(dbPath);
|
|
6445
6601
|
try {
|
|
6446
6602
|
this.database = new Database(dbPath);
|
|
@@ -6521,7 +6677,7 @@ var Indexer = class {
|
|
|
6521
6677
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6522
6678
|
return {
|
|
6523
6679
|
resetCorruptedIndex: true,
|
|
6524
|
-
warning: this.getCorruptedIndexWarning(
|
|
6680
|
+
warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
|
|
6525
6681
|
};
|
|
6526
6682
|
}
|
|
6527
6683
|
throw error;
|
|
@@ -6536,7 +6692,7 @@ var Indexer = class {
|
|
|
6536
6692
|
return;
|
|
6537
6693
|
}
|
|
6538
6694
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6539
|
-
const storeBasePath =
|
|
6695
|
+
const storeBasePath = path10.join(this.indexPath, "vectors");
|
|
6540
6696
|
const storeIndexPath = `${storeBasePath}.usearch`;
|
|
6541
6697
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6542
6698
|
const backupIndexPath = `${storeIndexPath}.bak`;
|
|
@@ -6621,9 +6777,9 @@ var Indexer = class {
|
|
|
6621
6777
|
if (!isSqliteCorruptionError(error)) {
|
|
6622
6778
|
return false;
|
|
6623
6779
|
}
|
|
6624
|
-
const dbPath =
|
|
6780
|
+
const dbPath = path10.join(this.indexPath, "codebase.db");
|
|
6625
6781
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
6626
|
-
const errorMessage =
|
|
6782
|
+
const errorMessage = getErrorMessage2(error);
|
|
6627
6783
|
if (this.config.scope === "global") {
|
|
6628
6784
|
this.logger.error("Detected corrupted shared global index database", {
|
|
6629
6785
|
stage,
|
|
@@ -6644,15 +6800,15 @@ var Indexer = class {
|
|
|
6644
6800
|
this.indexCompatibility = null;
|
|
6645
6801
|
this.fileHashCache.clear();
|
|
6646
6802
|
const resetPaths = [
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
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")
|
|
6656
6812
|
];
|
|
6657
6813
|
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
6658
6814
|
try {
|
|
@@ -6917,7 +7073,7 @@ var Indexer = class {
|
|
|
6917
7073
|
for (const parsed of parsedFiles) {
|
|
6918
7074
|
currentFilePaths.add(parsed.path);
|
|
6919
7075
|
if (parsed.chunks.length === 0) {
|
|
6920
|
-
const relativePath =
|
|
7076
|
+
const relativePath = path10.relative(this.projectRoot, parsed.path);
|
|
6921
7077
|
stats.parseFailures.push(relativePath);
|
|
6922
7078
|
}
|
|
6923
7079
|
let fileChunkCount = 0;
|
|
@@ -7056,6 +7212,7 @@ var Indexer = class {
|
|
|
7056
7212
|
targetName: site.calleeName,
|
|
7057
7213
|
toSymbolId: void 0,
|
|
7058
7214
|
callType: site.callType,
|
|
7215
|
+
confidence: site.confidence,
|
|
7059
7216
|
line: site.line,
|
|
7060
7217
|
col: site.column,
|
|
7061
7218
|
isResolved: false
|
|
@@ -7200,7 +7357,7 @@ var Indexer = class {
|
|
|
7200
7357
|
for (const requestBatch of requestBatches) {
|
|
7201
7358
|
queue.add(async () => {
|
|
7202
7359
|
if (rateLimitBackoffMs > 0) {
|
|
7203
|
-
await new Promise((
|
|
7360
|
+
await new Promise((resolve11) => setTimeout(resolve11, rateLimitBackoffMs));
|
|
7204
7361
|
}
|
|
7205
7362
|
try {
|
|
7206
7363
|
const result = await pRetry(
|
|
@@ -7215,7 +7372,7 @@ var Indexer = class {
|
|
|
7215
7372
|
factor: 2,
|
|
7216
7373
|
shouldRetry: (error) => !(error.error instanceof CustomProviderNonRetryableError),
|
|
7217
7374
|
onFailedAttempt: (error) => {
|
|
7218
|
-
const message =
|
|
7375
|
+
const message = getErrorMessage2(error);
|
|
7219
7376
|
if (isRateLimitError(error)) {
|
|
7220
7377
|
rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
|
|
7221
7378
|
this.logger.embedding("warn", `Rate limited, backing off`, {
|
|
@@ -7322,7 +7479,7 @@ var Indexer = class {
|
|
|
7322
7479
|
});
|
|
7323
7480
|
} catch (error) {
|
|
7324
7481
|
const failedChunks = getUniquePendingChunksFromRequests(requestBatch).filter((chunk) => !completedChunkIds.has(chunk.id));
|
|
7325
|
-
const failureMessage =
|
|
7482
|
+
const failureMessage = getErrorMessage2(error);
|
|
7326
7483
|
const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
7327
7484
|
for (const chunk of failedChunks) {
|
|
7328
7485
|
if (!failedChunkIds.has(chunk.id)) {
|
|
@@ -7761,8 +7918,8 @@ var Indexer = class {
|
|
|
7761
7918
|
this.indexCompatibility = compatibility;
|
|
7762
7919
|
return;
|
|
7763
7920
|
}
|
|
7764
|
-
const localProjectIndexPath =
|
|
7765
|
-
if (
|
|
7921
|
+
const localProjectIndexPath = path10.join(this.projectRoot, ".opencode", "index");
|
|
7922
|
+
if (path10.resolve(this.indexPath) !== path10.resolve(localProjectIndexPath)) {
|
|
7766
7923
|
throw new Error(
|
|
7767
7924
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
7768
7925
|
);
|
|
@@ -7850,7 +8007,7 @@ var Indexer = class {
|
|
|
7850
8007
|
gcOrphanSymbols: 0,
|
|
7851
8008
|
gcOrphanCallEdges: 0,
|
|
7852
8009
|
resetCorruptedIndex: true,
|
|
7853
|
-
warning: this.getCorruptedIndexWarning(
|
|
8010
|
+
warning: this.getCorruptedIndexWarning(path10.join(this.indexPath, "codebase.db"))
|
|
7854
8011
|
};
|
|
7855
8012
|
}
|
|
7856
8013
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8001,7 +8158,7 @@ var Indexer = class {
|
|
|
8001
8158
|
succeeded += successfulResults.length;
|
|
8002
8159
|
stillFailing.push(...failedChunksForBatch.values());
|
|
8003
8160
|
} catch (error) {
|
|
8004
|
-
const failureMessage =
|
|
8161
|
+
const failureMessage = getErrorMessage2(error);
|
|
8005
8162
|
const failureTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
8006
8163
|
const unaccountedChunks = batch.chunks.filter(
|
|
8007
8164
|
(chunk) => !failedChunksForBatch.has(chunk.id) && !completedChunkIds.has(chunk.id)
|
|
@@ -8175,12 +8332,12 @@ var Indexer = class {
|
|
|
8175
8332
|
})
|
|
8176
8333
|
);
|
|
8177
8334
|
}
|
|
8178
|
-
async getCallers(targetName) {
|
|
8335
|
+
async getCallers(targetName, callTypeFilter) {
|
|
8179
8336
|
const { database } = await this.ensureInitialized();
|
|
8180
8337
|
const seen = /* @__PURE__ */ new Set();
|
|
8181
8338
|
const results = [];
|
|
8182
8339
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8183
|
-
for (const edge of database.getCallersWithContext(targetName, branchKey)) {
|
|
8340
|
+
for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
|
|
8184
8341
|
if (!seen.has(edge.id)) {
|
|
8185
8342
|
seen.add(edge.id);
|
|
8186
8343
|
results.push(edge);
|
|
@@ -8189,12 +8346,12 @@ var Indexer = class {
|
|
|
8189
8346
|
}
|
|
8190
8347
|
return results;
|
|
8191
8348
|
}
|
|
8192
|
-
async getCallees(symbolId) {
|
|
8349
|
+
async getCallees(symbolId, callTypeFilter) {
|
|
8193
8350
|
const { database } = await this.ensureInitialized();
|
|
8194
8351
|
const seen = /* @__PURE__ */ new Set();
|
|
8195
8352
|
const results = [];
|
|
8196
8353
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8197
|
-
for (const edge of database.getCallees(symbolId, branchKey)) {
|
|
8354
|
+
for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
|
|
8198
8355
|
if (!seen.has(edge.id)) {
|
|
8199
8356
|
seen.add(edge.id);
|
|
8200
8357
|
results.push(edge);
|
|
@@ -8203,6 +8360,198 @@ var Indexer = class {
|
|
|
8203
8360
|
}
|
|
8204
8361
|
return results;
|
|
8205
8362
|
}
|
|
8363
|
+
async findCallPath(fromName, toName, maxDepth) {
|
|
8364
|
+
const { database } = await this.ensureInitialized();
|
|
8365
|
+
let shortest = [];
|
|
8366
|
+
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8367
|
+
const path19 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
8368
|
+
if (path19.length > 0 && (shortest.length === 0 || path19.length < shortest.length)) {
|
|
8369
|
+
shortest = path19;
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
return shortest;
|
|
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
|
+
}
|
|
8206
8555
|
async close() {
|
|
8207
8556
|
await this.database?.close();
|
|
8208
8557
|
this.database = null;
|
|
@@ -8462,13 +8811,13 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
8462
8811
|
// src/eval/runner-config.ts
|
|
8463
8812
|
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
8464
8813
|
import * as os4 from "os";
|
|
8465
|
-
import * as
|
|
8814
|
+
import * as path12 from "path";
|
|
8466
8815
|
|
|
8467
8816
|
// src/config/rebase.ts
|
|
8468
|
-
import * as
|
|
8817
|
+
import * as path11 from "path";
|
|
8469
8818
|
function isWithinRoot(rootDir, targetPath) {
|
|
8470
|
-
const relativePath =
|
|
8471
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
8819
|
+
const relativePath = path11.relative(rootDir, targetPath);
|
|
8820
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
8472
8821
|
}
|
|
8473
8822
|
function rebasePathEntries(values, fromDir, toDir) {
|
|
8474
8823
|
if (!Array.isArray(values)) {
|
|
@@ -8476,10 +8825,10 @@ function rebasePathEntries(values, fromDir, toDir) {
|
|
|
8476
8825
|
}
|
|
8477
8826
|
return values.filter((value) => typeof value === "string").map((value) => {
|
|
8478
8827
|
const trimmed = value.trim();
|
|
8479
|
-
if (!trimmed ||
|
|
8828
|
+
if (!trimmed || path11.isAbsolute(trimmed)) {
|
|
8480
8829
|
return trimmed;
|
|
8481
8830
|
}
|
|
8482
|
-
return normalizePathSeparators(
|
|
8831
|
+
return normalizePathSeparators(path11.normalize(path11.relative(toDir, path11.resolve(fromDir, trimmed))));
|
|
8483
8832
|
}).filter(Boolean);
|
|
8484
8833
|
}
|
|
8485
8834
|
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
@@ -8491,17 +8840,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
|
8491
8840
|
if (!trimmed) {
|
|
8492
8841
|
return trimmed;
|
|
8493
8842
|
}
|
|
8494
|
-
if (
|
|
8843
|
+
if (path11.isAbsolute(trimmed)) {
|
|
8495
8844
|
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
8496
|
-
return normalizePathSeparators(
|
|
8845
|
+
return normalizePathSeparators(path11.normalize(path11.relative(sourceRoot, trimmed) || "."));
|
|
8497
8846
|
}
|
|
8498
|
-
return
|
|
8847
|
+
return path11.normalize(trimmed);
|
|
8499
8848
|
}
|
|
8500
|
-
const resolvedFromSource =
|
|
8849
|
+
const resolvedFromSource = path11.resolve(sourceRoot, trimmed);
|
|
8501
8850
|
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
8502
|
-
return normalizePathSeparators(
|
|
8851
|
+
return normalizePathSeparators(path11.normalize(trimmed));
|
|
8503
8852
|
}
|
|
8504
|
-
return normalizePathSeparators(
|
|
8853
|
+
return normalizePathSeparators(path11.normalize(path11.relative(targetRoot, resolvedFromSource)));
|
|
8505
8854
|
}).filter(Boolean);
|
|
8506
8855
|
}
|
|
8507
8856
|
|
|
@@ -8549,20 +8898,20 @@ function parseJsonConfigFile(filePath) {
|
|
|
8549
8898
|
}
|
|
8550
8899
|
}
|
|
8551
8900
|
function toAbsolute(projectRoot, maybeRelative) {
|
|
8552
|
-
return
|
|
8901
|
+
return path12.isAbsolute(maybeRelative) ? maybeRelative : path12.join(projectRoot, maybeRelative);
|
|
8553
8902
|
}
|
|
8554
8903
|
function isProjectScopedConfigPath(configPath) {
|
|
8555
|
-
return
|
|
8904
|
+
return path12.basename(configPath) === "codebase-index.json" && path12.basename(path12.dirname(configPath)) === ".opencode";
|
|
8556
8905
|
}
|
|
8557
8906
|
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
8558
8907
|
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
8559
8908
|
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
8560
8909
|
values,
|
|
8561
|
-
|
|
8910
|
+
path12.dirname(path12.dirname(resolvedConfigPath)),
|
|
8562
8911
|
projectRoot
|
|
8563
8912
|
) : rebasePathEntries(
|
|
8564
8913
|
values,
|
|
8565
|
-
|
|
8914
|
+
path12.dirname(resolvedConfigPath),
|
|
8566
8915
|
projectRoot
|
|
8567
8916
|
);
|
|
8568
8917
|
if (Array.isArray(config.knowledgeBases)) {
|
|
@@ -8590,7 +8939,7 @@ function loadRawConfig(projectRoot, configPath) {
|
|
|
8590
8939
|
projectConfig
|
|
8591
8940
|
);
|
|
8592
8941
|
}
|
|
8593
|
-
const globalConfig =
|
|
8942
|
+
const globalConfig = path12.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
|
|
8594
8943
|
if (existsSync7(globalConfig)) {
|
|
8595
8944
|
return parseJsonConfigFile(globalConfig);
|
|
8596
8945
|
}
|
|
@@ -8600,10 +8949,10 @@ function getIndexRootPath(projectRoot, scope) {
|
|
|
8600
8949
|
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
8601
8950
|
}
|
|
8602
8951
|
function getLocalProjectIndexRoot(projectRoot) {
|
|
8603
|
-
return
|
|
8952
|
+
return path12.join(projectRoot, ".opencode", "index");
|
|
8604
8953
|
}
|
|
8605
8954
|
function getLocalProjectConfigPath(projectRoot) {
|
|
8606
|
-
return
|
|
8955
|
+
return path12.join(projectRoot, ".opencode", "codebase-index.json");
|
|
8607
8956
|
}
|
|
8608
8957
|
function clearIndexRoot(projectRoot, scope) {
|
|
8609
8958
|
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
@@ -8625,7 +8974,7 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
|
8625
8974
|
projectRoot,
|
|
8626
8975
|
resolvedConfigPath
|
|
8627
8976
|
);
|
|
8628
|
-
mkdirSync3(
|
|
8977
|
+
mkdirSync3(path12.dirname(localConfigPath), { recursive: true });
|
|
8629
8978
|
writeFileSync3(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
8630
8979
|
return localConfigPath;
|
|
8631
8980
|
}
|
|
@@ -8675,23 +9024,23 @@ function isRecord2(value) {
|
|
|
8675
9024
|
function isStringArray3(value) {
|
|
8676
9025
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
8677
9026
|
}
|
|
8678
|
-
function asPositiveNumber(value,
|
|
9027
|
+
function asPositiveNumber(value, path19) {
|
|
8679
9028
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
8680
|
-
throw new Error(`${
|
|
9029
|
+
throw new Error(`${path19} must be a non-negative number`);
|
|
8681
9030
|
}
|
|
8682
9031
|
return value;
|
|
8683
9032
|
}
|
|
8684
|
-
function parseQueryType(value,
|
|
9033
|
+
function parseQueryType(value, path19) {
|
|
8685
9034
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
8686
9035
|
return value;
|
|
8687
9036
|
}
|
|
8688
9037
|
throw new Error(
|
|
8689
|
-
`${
|
|
9038
|
+
`${path19} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
8690
9039
|
);
|
|
8691
9040
|
}
|
|
8692
|
-
function parseExpected(input,
|
|
9041
|
+
function parseExpected(input, path19) {
|
|
8693
9042
|
if (!isRecord2(input)) {
|
|
8694
|
-
throw new Error(`${
|
|
9043
|
+
throw new Error(`${path19} must be an object`);
|
|
8695
9044
|
}
|
|
8696
9045
|
const filePathRaw = input.filePath;
|
|
8697
9046
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -8700,16 +9049,16 @@ function parseExpected(input, path18) {
|
|
|
8700
9049
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
8701
9050
|
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
8702
9051
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
8703
|
-
throw new Error(`${
|
|
9052
|
+
throw new Error(`${path19} must include either expected.filePath or expected.acceptableFiles`);
|
|
8704
9053
|
}
|
|
8705
9054
|
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
8706
|
-
throw new Error(`${
|
|
9055
|
+
throw new Error(`${path19}.acceptableFiles must be an array of strings`);
|
|
8707
9056
|
}
|
|
8708
9057
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
8709
|
-
throw new Error(`${
|
|
9058
|
+
throw new Error(`${path19}.symbol must be a string when provided`);
|
|
8710
9059
|
}
|
|
8711
9060
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
8712
|
-
throw new Error(`${
|
|
9061
|
+
throw new Error(`${path19}.branch must be a string when provided`);
|
|
8713
9062
|
}
|
|
8714
9063
|
return {
|
|
8715
9064
|
filePath,
|
|
@@ -8719,25 +9068,25 @@ function parseExpected(input, path18) {
|
|
|
8719
9068
|
};
|
|
8720
9069
|
}
|
|
8721
9070
|
function parseQuery(input, index) {
|
|
8722
|
-
const
|
|
9071
|
+
const path19 = `queries[${index}]`;
|
|
8723
9072
|
if (!isRecord2(input)) {
|
|
8724
|
-
throw new Error(`${
|
|
9073
|
+
throw new Error(`${path19} must be an object`);
|
|
8725
9074
|
}
|
|
8726
9075
|
const id = input.id;
|
|
8727
9076
|
const query = input.query;
|
|
8728
9077
|
const queryType = input.queryType;
|
|
8729
9078
|
const expected = input.expected;
|
|
8730
9079
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
8731
|
-
throw new Error(`${
|
|
9080
|
+
throw new Error(`${path19}.id must be a non-empty string`);
|
|
8732
9081
|
}
|
|
8733
9082
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
8734
|
-
throw new Error(`${
|
|
9083
|
+
throw new Error(`${path19}.query must be a non-empty string`);
|
|
8735
9084
|
}
|
|
8736
9085
|
return {
|
|
8737
9086
|
id,
|
|
8738
9087
|
query,
|
|
8739
|
-
queryType: parseQueryType(queryType, `${
|
|
8740
|
-
expected: parseExpected(expected, `${
|
|
9088
|
+
queryType: parseQueryType(queryType, `${path19}.queryType`),
|
|
9089
|
+
expected: parseExpected(expected, `${path19}.expected`)
|
|
8741
9090
|
};
|
|
8742
9091
|
}
|
|
8743
9092
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -8920,13 +9269,13 @@ async function runEvaluation(options) {
|
|
|
8920
9269
|
};
|
|
8921
9270
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
8922
9271
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
8923
|
-
writeJson(
|
|
8924
|
-
writeJson(
|
|
9272
|
+
writeJson(path13.join(outputDir, "summary.json"), summary);
|
|
9273
|
+
writeJson(path13.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
8925
9274
|
let comparison;
|
|
8926
9275
|
if (againstPath) {
|
|
8927
9276
|
const baseline = loadSummary(againstPath);
|
|
8928
9277
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
8929
|
-
writeJson(
|
|
9278
|
+
writeJson(path13.join(outputDir, "compare.json"), comparison);
|
|
8930
9279
|
}
|
|
8931
9280
|
let gate;
|
|
8932
9281
|
if (options.ciMode) {
|
|
@@ -8939,7 +9288,7 @@ async function runEvaluation(options) {
|
|
|
8939
9288
|
if (existsSync8(resolvedBaseline)) {
|
|
8940
9289
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
8941
9290
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
8942
|
-
writeJson(
|
|
9291
|
+
writeJson(path13.join(outputDir, "compare.json"), comparison);
|
|
8943
9292
|
} else if (budget.failOnMissingBaseline) {
|
|
8944
9293
|
throw new Error(
|
|
8945
9294
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -8949,7 +9298,7 @@ async function runEvaluation(options) {
|
|
|
8949
9298
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
8950
9299
|
}
|
|
8951
9300
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
8952
|
-
writeText(
|
|
9301
|
+
writeText(path13.join(outputDir, "summary.md"), markdown);
|
|
8953
9302
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
8954
9303
|
} finally {
|
|
8955
9304
|
await indexer.close();
|
|
@@ -9007,23 +9356,23 @@ async function runSweep(options, sweep) {
|
|
|
9007
9356
|
bestByMrrAt10,
|
|
9008
9357
|
bestByP95Latency
|
|
9009
9358
|
};
|
|
9010
|
-
writeJson(
|
|
9359
|
+
writeJson(path13.join(outputDir, "compare.json"), aggregate);
|
|
9011
9360
|
const md = createSummaryMarkdown(
|
|
9012
9361
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
9013
9362
|
bestByHitAt5?.comparison,
|
|
9014
9363
|
void 0,
|
|
9015
9364
|
aggregate
|
|
9016
9365
|
);
|
|
9017
|
-
writeText(
|
|
9018
|
-
writeJson(
|
|
9366
|
+
writeText(path13.join(outputDir, "summary.md"), md);
|
|
9367
|
+
writeJson(path13.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
9019
9368
|
return { outputDir, aggregate };
|
|
9020
9369
|
}
|
|
9021
9370
|
|
|
9022
9371
|
// src/eval/cli.ts
|
|
9023
|
-
import * as
|
|
9372
|
+
import * as path15 from "path";
|
|
9024
9373
|
|
|
9025
9374
|
// src/eval/cli-parser.ts
|
|
9026
|
-
import * as
|
|
9375
|
+
import * as path14 from "path";
|
|
9027
9376
|
function printUsage() {
|
|
9028
9377
|
console.log(`
|
|
9029
9378
|
Usage:
|
|
@@ -9095,12 +9444,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9095
9444
|
const arg = argv[i];
|
|
9096
9445
|
const next = argv[i + 1];
|
|
9097
9446
|
if (arg === "--project" && next) {
|
|
9098
|
-
parsed.projectRoot =
|
|
9447
|
+
parsed.projectRoot = path14.resolve(cwd, next);
|
|
9099
9448
|
i += 1;
|
|
9100
9449
|
continue;
|
|
9101
9450
|
}
|
|
9102
9451
|
if (arg === "--config" && next) {
|
|
9103
|
-
parsed.configPath =
|
|
9452
|
+
parsed.configPath = path14.resolve(cwd, next);
|
|
9104
9453
|
i += 1;
|
|
9105
9454
|
continue;
|
|
9106
9455
|
}
|
|
@@ -9296,22 +9645,22 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9296
9645
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
9297
9646
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
9298
9647
|
}
|
|
9299
|
-
const currentSummary = loadSummary(
|
|
9648
|
+
const currentSummary = loadSummary(path15.resolve(parsed.projectRoot, currentPath), {
|
|
9300
9649
|
allowLegacyDiversityMetrics: true
|
|
9301
9650
|
});
|
|
9302
|
-
const baselineSummary = loadSummary(
|
|
9651
|
+
const baselineSummary = loadSummary(path15.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
9303
9652
|
allowLegacyDiversityMetrics: true
|
|
9304
9653
|
});
|
|
9305
9654
|
const comparison = compareSummaries(
|
|
9306
9655
|
currentSummary,
|
|
9307
9656
|
baselineSummary,
|
|
9308
|
-
|
|
9657
|
+
path15.resolve(parsed.projectRoot, parsed.againstPath)
|
|
9309
9658
|
);
|
|
9310
|
-
const outputDir = createRunDirectory(
|
|
9659
|
+
const outputDir = createRunDirectory(path15.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
9311
9660
|
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
9312
|
-
writeJson(
|
|
9313
|
-
writeText(
|
|
9314
|
-
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);
|
|
9315
9664
|
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
9316
9665
|
return 0;
|
|
9317
9666
|
}
|
|
@@ -9320,7 +9669,7 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9320
9669
|
|
|
9321
9670
|
// src/mcp-server.ts
|
|
9322
9671
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9323
|
-
import * as
|
|
9672
|
+
import * as path17 from "path";
|
|
9324
9673
|
import { existsSync as existsSync10 } from "fs";
|
|
9325
9674
|
|
|
9326
9675
|
// src/mcp-server/register-prompts.ts
|
|
@@ -9419,7 +9768,7 @@ import { z as z2 } from "zod";
|
|
|
9419
9768
|
// src/config/merger.ts
|
|
9420
9769
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
9421
9770
|
import * as os5 from "os";
|
|
9422
|
-
import * as
|
|
9771
|
+
import * as path16 from "path";
|
|
9423
9772
|
var PROJECT_OVERRIDE_KEYS = [
|
|
9424
9773
|
"embeddingProvider",
|
|
9425
9774
|
"customProvider",
|
|
@@ -9452,8 +9801,8 @@ function mergeUniqueStringArray(values) {
|
|
|
9452
9801
|
return [...new Set(values.map((value) => String(value).trim()))];
|
|
9453
9802
|
}
|
|
9454
9803
|
function normalizeKnowledgeBasePath(value) {
|
|
9455
|
-
let normalized =
|
|
9456
|
-
const root =
|
|
9804
|
+
let normalized = path16.normalize(String(value).trim());
|
|
9805
|
+
const root = path16.parse(normalized).root;
|
|
9457
9806
|
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
9458
9807
|
normalized = normalized.slice(0, -1);
|
|
9459
9808
|
}
|
|
@@ -9503,8 +9852,8 @@ function loadJsonFile(filePath) {
|
|
|
9503
9852
|
return null;
|
|
9504
9853
|
}
|
|
9505
9854
|
function materializeLocalProjectConfig(projectRoot, config) {
|
|
9506
|
-
const localConfigPath =
|
|
9507
|
-
mkdirSync4(
|
|
9855
|
+
const localConfigPath = path16.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9856
|
+
mkdirSync4(path16.dirname(localConfigPath), { recursive: true });
|
|
9508
9857
|
writeFileSync4(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
9509
9858
|
return localConfigPath;
|
|
9510
9859
|
}
|
|
@@ -9515,7 +9864,7 @@ function loadProjectConfigLayer(projectRoot) {
|
|
|
9515
9864
|
return {};
|
|
9516
9865
|
}
|
|
9517
9866
|
const normalizedConfig = { ...projectConfig };
|
|
9518
|
-
const projectConfigBaseDir =
|
|
9867
|
+
const projectConfigBaseDir = path16.dirname(path16.dirname(projectConfigPath));
|
|
9519
9868
|
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
9520
9869
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
9521
9870
|
normalizedConfig.knowledgeBases,
|
|
@@ -9734,6 +10083,57 @@ ${truncateContent(r.content)}
|
|
|
9734
10083
|
return formatted.join("\n\n");
|
|
9735
10084
|
}
|
|
9736
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
|
+
|
|
9737
10137
|
// src/mcp-server/shared.ts
|
|
9738
10138
|
var MAX_CONTENT_LINES2 = 30;
|
|
9739
10139
|
function truncateContent2(content) {
|
|
@@ -9978,11 +10378,12 @@ ${formatted.join("\n\n")}` }] };
|
|
|
9978
10378
|
);
|
|
9979
10379
|
server.tool(
|
|
9980
10380
|
"call_graph",
|
|
9981
|
-
"Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
|
|
10381
|
+
"Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
|
|
9982
10382
|
{
|
|
9983
10383
|
name: z2.string().describe("Function or method name to query"),
|
|
9984
10384
|
direction: z2.enum(["callers", "callees"]).default("callers").describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
9985
|
-
symbolId: z2.string().optional().describe("Symbol ID (required for 'callees' direction)")
|
|
10385
|
+
symbolId: z2.string().optional().describe("Symbol ID (required for 'callees' direction)"),
|
|
10386
|
+
relationshipType: z2.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional().describe("Filter by relationship type. Omit to show all.")
|
|
9986
10387
|
},
|
|
9987
10388
|
async (args) => {
|
|
9988
10389
|
await runtime.ensureInitialized();
|
|
@@ -9991,29 +10392,85 @@ ${formatted.join("\n\n")}` }] };
|
|
|
9991
10392
|
if (!args.symbolId) {
|
|
9992
10393
|
return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
|
|
9993
10394
|
}
|
|
9994
|
-
const callees = await indexer.getCallees(args.symbolId);
|
|
10395
|
+
const callees = await indexer.getCallees(args.symbolId, args.relationshipType);
|
|
9995
10396
|
if (callees.length === 0) {
|
|
9996
|
-
return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}.` }] };
|
|
10397
|
+
return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
|
|
9997
10398
|
}
|
|
9998
|
-
const formatted2 = callees.map(
|
|
9999
|
-
|
|
10000
|
-
|
|
10399
|
+
const formatted2 = callees.map((e, i) => {
|
|
10400
|
+
const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
|
|
10401
|
+
return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
|
|
10402
|
+
});
|
|
10001
10403
|
return { content: [{ type: "text", text: `Callees (${callees.length}):
|
|
10002
10404
|
|
|
10003
10405
|
${formatted2.join("\n")}` }] };
|
|
10004
10406
|
}
|
|
10005
|
-
const callers = await indexer.getCallers(args.name);
|
|
10407
|
+
const callers = await indexer.getCallers(args.name, args.relationshipType);
|
|
10006
10408
|
if (callers.length === 0) {
|
|
10007
|
-
return { content: [{ type: "text", text: `No callers found for "${args.name}".` }] };
|
|
10409
|
+
return { content: [{ type: "text", text: `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
|
|
10008
10410
|
}
|
|
10009
|
-
const formatted = callers.map(
|
|
10010
|
-
|
|
10011
|
-
|
|
10411
|
+
const formatted = callers.map((e, i) => {
|
|
10412
|
+
const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
|
|
10413
|
+
return `[${i + 1}] \u2190 from ${e.fromSymbolName ?? "<unknown>"} in ${e.fromSymbolFilePath ?? "<unknown file>"} [${e.fromSymbolId}] (${e.callType})${conf} at line ${e.line}${e.isResolved ? " [resolved]" : " [unresolved]"}`;
|
|
10414
|
+
});
|
|
10012
10415
|
return { content: [{ type: "text", text: `"${args.name}" is called by ${callers.length} function(s):
|
|
10013
10416
|
|
|
10014
10417
|
${formatted.join("\n")}` }] };
|
|
10015
10418
|
}
|
|
10016
10419
|
);
|
|
10420
|
+
server.tool(
|
|
10421
|
+
"call_graph_path",
|
|
10422
|
+
"Find the shortest connection path between two symbols in the call graph. Returns the chain of calls connecting them.",
|
|
10423
|
+
{
|
|
10424
|
+
from: z2.string().describe("Source function/method name (starting point)"),
|
|
10425
|
+
to: z2.string().describe("Target function/method name (destination)"),
|
|
10426
|
+
maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
|
|
10427
|
+
},
|
|
10428
|
+
async (args) => {
|
|
10429
|
+
await runtime.ensureInitialized();
|
|
10430
|
+
const indexer = runtime.getIndexer();
|
|
10431
|
+
const path19 = await indexer.findCallPath(args.from, args.to, args.maxDepth);
|
|
10432
|
+
if (path19.length === 0) {
|
|
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.` }] };
|
|
10434
|
+
}
|
|
10435
|
+
const formatted = path19.map((hop, i) => {
|
|
10436
|
+
const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10437
|
+
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10438
|
+
return `${prefix} ${hop.symbolName}${location}`;
|
|
10439
|
+
});
|
|
10440
|
+
return { content: [{ type: "text", text: `Path (${path19.length} hops):
|
|
10441
|
+
${formatted.join("\n")}` }] };
|
|
10442
|
+
}
|
|
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
|
+
);
|
|
10017
10474
|
}
|
|
10018
10475
|
|
|
10019
10476
|
// src/mcp-server.ts
|
|
@@ -10032,12 +10489,12 @@ function createMcpServer(projectRoot, config) {
|
|
|
10032
10489
|
if (config.scope !== "project") {
|
|
10033
10490
|
return false;
|
|
10034
10491
|
}
|
|
10035
|
-
const localIndexPath =
|
|
10492
|
+
const localIndexPath = path17.join(projectRoot, ".opencode", "index");
|
|
10036
10493
|
const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
|
|
10037
10494
|
if (!mainRepoRoot) {
|
|
10038
10495
|
return false;
|
|
10039
10496
|
}
|
|
10040
|
-
const inheritedIndexPath =
|
|
10497
|
+
const inheritedIndexPath = path17.join(mainRepoRoot, ".opencode", "index");
|
|
10041
10498
|
return !existsSync10(localIndexPath) && existsSync10(inheritedIndexPath);
|
|
10042
10499
|
}
|
|
10043
10500
|
async function ensureInitialized() {
|
|
@@ -10063,9 +10520,9 @@ function parseArgs(argv) {
|
|
|
10063
10520
|
let config;
|
|
10064
10521
|
for (let i = 2; i < argv.length; i++) {
|
|
10065
10522
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
10066
|
-
project =
|
|
10523
|
+
project = path18.resolve(argv[++i]);
|
|
10067
10524
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
10068
|
-
config =
|
|
10525
|
+
config = path18.resolve(argv[++i]);
|
|
10069
10526
|
}
|
|
10070
10527
|
}
|
|
10071
10528
|
return { project, config };
|