opencode-codebase-index 0.13.2 → 0.15.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 +45 -6
- package/commands/peek.md +4 -0
- package/commands/search.md +4 -0
- package/dist/cli.cjs +1921 -640
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1919 -628
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1856 -580
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1855 -569
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +1855 -610
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +1861 -606
- package/dist/pi-extension.js.map +1 -1
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.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 +11 -8
- package/skill/SKILL.md +5 -0
package/dist/cli.cjs
CHANGED
|
@@ -496,7 +496,7 @@ var require_ignore = __commonJS({
|
|
|
496
496
|
// path matching.
|
|
497
497
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
498
498
|
// @returns {TestResult} true if a file is ignored
|
|
499
|
-
test(
|
|
499
|
+
test(path27, checkUnignored, mode) {
|
|
500
500
|
let ignored = false;
|
|
501
501
|
let unignored = false;
|
|
502
502
|
let matchedRule;
|
|
@@ -505,7 +505,7 @@ var require_ignore = __commonJS({
|
|
|
505
505
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
506
506
|
return;
|
|
507
507
|
}
|
|
508
|
-
const matched = rule[mode].test(
|
|
508
|
+
const matched = rule[mode].test(path27);
|
|
509
509
|
if (!matched) {
|
|
510
510
|
return;
|
|
511
511
|
}
|
|
@@ -526,17 +526,17 @@ var require_ignore = __commonJS({
|
|
|
526
526
|
var throwError = (message, Ctor) => {
|
|
527
527
|
throw new Ctor(message);
|
|
528
528
|
};
|
|
529
|
-
var checkPath = (
|
|
530
|
-
if (!isString(
|
|
529
|
+
var checkPath = (path27, originalPath, doThrow) => {
|
|
530
|
+
if (!isString(path27)) {
|
|
531
531
|
return doThrow(
|
|
532
532
|
`path must be a string, but got \`${originalPath}\``,
|
|
533
533
|
TypeError
|
|
534
534
|
);
|
|
535
535
|
}
|
|
536
|
-
if (!
|
|
536
|
+
if (!path27) {
|
|
537
537
|
return doThrow(`path must not be empty`, TypeError);
|
|
538
538
|
}
|
|
539
|
-
if (checkPath.isNotRelative(
|
|
539
|
+
if (checkPath.isNotRelative(path27)) {
|
|
540
540
|
const r = "`path.relative()`d";
|
|
541
541
|
return doThrow(
|
|
542
542
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -545,7 +545,7 @@ var require_ignore = __commonJS({
|
|
|
545
545
|
}
|
|
546
546
|
return true;
|
|
547
547
|
};
|
|
548
|
-
var isNotRelative = (
|
|
548
|
+
var isNotRelative = (path27) => REGEX_TEST_INVALID_PATH.test(path27);
|
|
549
549
|
checkPath.isNotRelative = isNotRelative;
|
|
550
550
|
checkPath.convert = (p) => p;
|
|
551
551
|
var Ignore2 = class {
|
|
@@ -575,19 +575,19 @@ var require_ignore = __commonJS({
|
|
|
575
575
|
}
|
|
576
576
|
// @returns {TestResult}
|
|
577
577
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
578
|
-
const
|
|
578
|
+
const path27 = originalPath && checkPath.convert(originalPath);
|
|
579
579
|
checkPath(
|
|
580
|
-
|
|
580
|
+
path27,
|
|
581
581
|
originalPath,
|
|
582
582
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
583
583
|
);
|
|
584
|
-
return this._t(
|
|
584
|
+
return this._t(path27, cache, checkUnignored, slices);
|
|
585
585
|
}
|
|
586
|
-
checkIgnore(
|
|
587
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
588
|
-
return this.test(
|
|
586
|
+
checkIgnore(path27) {
|
|
587
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path27)) {
|
|
588
|
+
return this.test(path27);
|
|
589
589
|
}
|
|
590
|
-
const slices =
|
|
590
|
+
const slices = path27.split(SLASH2).filter(Boolean);
|
|
591
591
|
slices.pop();
|
|
592
592
|
if (slices.length) {
|
|
593
593
|
const parent = this._t(
|
|
@@ -600,18 +600,18 @@ var require_ignore = __commonJS({
|
|
|
600
600
|
return parent;
|
|
601
601
|
}
|
|
602
602
|
}
|
|
603
|
-
return this._rules.test(
|
|
603
|
+
return this._rules.test(path27, false, MODE_CHECK_IGNORE);
|
|
604
604
|
}
|
|
605
|
-
_t(
|
|
606
|
-
if (
|
|
607
|
-
return cache[
|
|
605
|
+
_t(path27, cache, checkUnignored, slices) {
|
|
606
|
+
if (path27 in cache) {
|
|
607
|
+
return cache[path27];
|
|
608
608
|
}
|
|
609
609
|
if (!slices) {
|
|
610
|
-
slices =
|
|
610
|
+
slices = path27.split(SLASH2).filter(Boolean);
|
|
611
611
|
}
|
|
612
612
|
slices.pop();
|
|
613
613
|
if (!slices.length) {
|
|
614
|
-
return cache[
|
|
614
|
+
return cache[path27] = this._rules.test(path27, checkUnignored, MODE_IGNORE);
|
|
615
615
|
}
|
|
616
616
|
const parent = this._t(
|
|
617
617
|
slices.join(SLASH2) + SLASH2,
|
|
@@ -619,29 +619,29 @@ var require_ignore = __commonJS({
|
|
|
619
619
|
checkUnignored,
|
|
620
620
|
slices
|
|
621
621
|
);
|
|
622
|
-
return cache[
|
|
622
|
+
return cache[path27] = parent.ignored ? parent : this._rules.test(path27, checkUnignored, MODE_IGNORE);
|
|
623
623
|
}
|
|
624
|
-
ignores(
|
|
625
|
-
return this._test(
|
|
624
|
+
ignores(path27) {
|
|
625
|
+
return this._test(path27, this._ignoreCache, false).ignored;
|
|
626
626
|
}
|
|
627
627
|
createFilter() {
|
|
628
|
-
return (
|
|
628
|
+
return (path27) => !this.ignores(path27);
|
|
629
629
|
}
|
|
630
630
|
filter(paths) {
|
|
631
631
|
return makeArray(paths).filter(this.createFilter());
|
|
632
632
|
}
|
|
633
633
|
// @returns {TestResult}
|
|
634
|
-
test(
|
|
635
|
-
return this._test(
|
|
634
|
+
test(path27) {
|
|
635
|
+
return this._test(path27, this._testCache, true);
|
|
636
636
|
}
|
|
637
637
|
};
|
|
638
638
|
var factory = (options) => new Ignore2(options);
|
|
639
|
-
var isPathValid = (
|
|
639
|
+
var isPathValid = (path27) => checkPath(path27 && checkPath.convert(path27), path27, RETURN_FALSE);
|
|
640
640
|
var setupWindows = () => {
|
|
641
641
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
642
642
|
checkPath.convert = makePosix;
|
|
643
643
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
644
|
-
checkPath.isNotRelative = (
|
|
644
|
+
checkPath.isNotRelative = (path27) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path27) || isNotRelative(path27);
|
|
645
645
|
};
|
|
646
646
|
if (
|
|
647
647
|
// Detect `process` so that it can run in browsers.
|
|
@@ -665,17 +665,17 @@ __export(cli_exports, {
|
|
|
665
665
|
});
|
|
666
666
|
module.exports = __toCommonJS(cli_exports);
|
|
667
667
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
668
|
-
var
|
|
669
|
-
var
|
|
670
|
-
var
|
|
668
|
+
var import_fs16 = require("fs");
|
|
669
|
+
var os6 = __toESM(require("os"), 1);
|
|
670
|
+
var path26 = __toESM(require("path"), 1);
|
|
671
671
|
var import_url2 = require("url");
|
|
672
672
|
|
|
673
673
|
// src/config/constants.ts
|
|
674
674
|
var DEFAULT_INCLUDE = [
|
|
675
|
-
"**/*.{ts,tsx,js,jsx,mjs,cjs}",
|
|
675
|
+
"**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
676
676
|
"**/*.{py,pyi}",
|
|
677
|
-
"**/*.{go,rs,java,kt,scala}",
|
|
678
|
-
"**/*.{c,cpp,cc,h,hpp}",
|
|
677
|
+
"**/*.{go,rs,java,cs,kt,scala}",
|
|
678
|
+
"**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
|
|
679
679
|
"**/*.{rb,php,inc,swift}",
|
|
680
680
|
"**/*.{cls,trigger}",
|
|
681
681
|
"**/*.{vue,svelte,astro}",
|
|
@@ -800,7 +800,8 @@ function getDefaultIndexingConfig() {
|
|
|
800
800
|
requireProjectMarker: true,
|
|
801
801
|
maxDepth: 5,
|
|
802
802
|
maxFilesPerDirectory: 100,
|
|
803
|
-
fallbackToTextOnMaxChunks: true
|
|
803
|
+
fallbackToTextOnMaxChunks: true,
|
|
804
|
+
gitBlame: { enabled: false }
|
|
804
805
|
};
|
|
805
806
|
}
|
|
806
807
|
function getDefaultSearchConfig() {
|
|
@@ -924,7 +925,10 @@ function parseConfig(raw) {
|
|
|
924
925
|
requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
|
|
925
926
|
maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
|
|
926
927
|
maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
|
|
927
|
-
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
|
|
928
|
+
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
|
|
929
|
+
gitBlame: {
|
|
930
|
+
enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
|
|
931
|
+
}
|
|
928
932
|
};
|
|
929
933
|
const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
|
|
930
934
|
const search = {
|
|
@@ -1054,7 +1058,7 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
|
|
|
1054
1058
|
);
|
|
1055
1059
|
|
|
1056
1060
|
// src/config/host.ts
|
|
1057
|
-
var HOST_MODES = ["opencode", "codex", "claude", "pi"];
|
|
1061
|
+
var HOST_MODES = ["opencode", "codex", "claude", "pi", "jcode"];
|
|
1058
1062
|
function isSupportedHostMode(value) {
|
|
1059
1063
|
return HOST_MODES.includes(value);
|
|
1060
1064
|
}
|
|
@@ -1109,9 +1113,9 @@ var import_fs = require("fs");
|
|
|
1109
1113
|
var path = __toESM(require("path"), 1);
|
|
1110
1114
|
|
|
1111
1115
|
// src/eval/report-formatters.ts
|
|
1112
|
-
function assertFiniteNumber(value,
|
|
1116
|
+
function assertFiniteNumber(value, path27) {
|
|
1113
1117
|
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1114
|
-
throw new Error(`${
|
|
1118
|
+
throw new Error(`${path27} must be a finite number`);
|
|
1115
1119
|
}
|
|
1116
1120
|
return value;
|
|
1117
1121
|
}
|
|
@@ -1299,16 +1303,16 @@ function buildPerQueryArtifact(perQuery) {
|
|
|
1299
1303
|
}
|
|
1300
1304
|
|
|
1301
1305
|
// src/eval/runner.ts
|
|
1302
|
-
var
|
|
1303
|
-
var
|
|
1306
|
+
var import_fs11 = require("fs");
|
|
1307
|
+
var path15 = __toESM(require("path"), 1);
|
|
1304
1308
|
var import_perf_hooks2 = require("perf_hooks");
|
|
1305
1309
|
|
|
1306
1310
|
// src/indexer/index.ts
|
|
1307
|
-
var
|
|
1308
|
-
var
|
|
1311
|
+
var import_fs8 = require("fs");
|
|
1312
|
+
var path12 = __toESM(require("path"), 1);
|
|
1309
1313
|
var import_perf_hooks = require("perf_hooks");
|
|
1310
|
-
var
|
|
1311
|
-
var
|
|
1314
|
+
var import_child_process3 = require("child_process");
|
|
1315
|
+
var import_util3 = require("util");
|
|
1312
1316
|
|
|
1313
1317
|
// node_modules/eventemitter3/index.mjs
|
|
1314
1318
|
var import_index = __toESM(require_eventemitter3(), 1);
|
|
@@ -2587,17 +2591,17 @@ function validateExternalUrl(urlString) {
|
|
|
2587
2591
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2588
2592
|
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2589
2593
|
}
|
|
2590
|
-
const
|
|
2591
|
-
if (BLOCKED_HOSTNAMES.has(
|
|
2592
|
-
return { valid: false, reason: `Blocked: cloud metadata service (${
|
|
2594
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
2595
|
+
if (BLOCKED_HOSTNAMES.has(hostname2)) {
|
|
2596
|
+
return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
|
|
2593
2597
|
}
|
|
2594
2598
|
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2595
|
-
if (pattern.test(
|
|
2596
|
-
return { valid: false, reason: `Blocked: cloud metadata IP (${
|
|
2599
|
+
if (pattern.test(hostname2)) {
|
|
2600
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
|
|
2597
2601
|
}
|
|
2598
2602
|
}
|
|
2599
|
-
if (/^169\.254\./.test(
|
|
2600
|
-
return { valid: false, reason: `Blocked: link-local address (${
|
|
2603
|
+
if (/^169\.254\./.test(hostname2)) {
|
|
2604
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
|
|
2601
2605
|
}
|
|
2602
2606
|
return { valid: true };
|
|
2603
2607
|
}
|
|
@@ -3788,6 +3792,12 @@ function createMockNativeBinding() {
|
|
|
3788
3792
|
constructor() {
|
|
3789
3793
|
throw error;
|
|
3790
3794
|
}
|
|
3795
|
+
static openReadOnly() {
|
|
3796
|
+
throw error;
|
|
3797
|
+
}
|
|
3798
|
+
static createEmptyReadOnly() {
|
|
3799
|
+
throw error;
|
|
3800
|
+
}
|
|
3791
3801
|
close() {
|
|
3792
3802
|
throw error;
|
|
3793
3803
|
}
|
|
@@ -3891,6 +3901,12 @@ var VectorStore = class {
|
|
|
3891
3901
|
load() {
|
|
3892
3902
|
this.inner.load();
|
|
3893
3903
|
}
|
|
3904
|
+
loadStrict() {
|
|
3905
|
+
this.inner.loadStrict();
|
|
3906
|
+
}
|
|
3907
|
+
hasFingerprint() {
|
|
3908
|
+
return this.inner.hasFingerprint();
|
|
3909
|
+
}
|
|
3894
3910
|
count() {
|
|
3895
3911
|
return this.inner.count();
|
|
3896
3912
|
}
|
|
@@ -4173,12 +4189,24 @@ var InvertedIndex = class {
|
|
|
4173
4189
|
return this.inner.documentCount();
|
|
4174
4190
|
}
|
|
4175
4191
|
};
|
|
4176
|
-
var Database = class {
|
|
4192
|
+
var Database = class _Database {
|
|
4177
4193
|
inner;
|
|
4178
4194
|
closed = false;
|
|
4179
4195
|
constructor(dbPath) {
|
|
4180
4196
|
this.inner = new native.Database(dbPath);
|
|
4181
4197
|
}
|
|
4198
|
+
static fromNative(inner) {
|
|
4199
|
+
const database = Object.create(_Database.prototype);
|
|
4200
|
+
database.inner = inner;
|
|
4201
|
+
database.closed = false;
|
|
4202
|
+
return database;
|
|
4203
|
+
}
|
|
4204
|
+
static openReadOnly(dbPath) {
|
|
4205
|
+
return _Database.fromNative(native.Database.openReadOnly(dbPath));
|
|
4206
|
+
}
|
|
4207
|
+
static createEmptyReadOnly() {
|
|
4208
|
+
return _Database.fromNative(native.Database.createEmptyReadOnly());
|
|
4209
|
+
}
|
|
4182
4210
|
throwIfClosed() {
|
|
4183
4211
|
if (this.closed) {
|
|
4184
4212
|
throw new Error("Database is closed");
|
|
@@ -4647,6 +4675,9 @@ function getHostProjectIndexRelativePath(host) {
|
|
|
4647
4675
|
function hasHostProjectConfig(projectRoot, host) {
|
|
4648
4676
|
return (0, import_fs6.existsSync)(path8.join(projectRoot, getProjectConfigRelativePath(host)));
|
|
4649
4677
|
}
|
|
4678
|
+
function hasHostGlobalConfig(host) {
|
|
4679
|
+
return (0, import_fs6.existsSync)(getGlobalConfigPath(host));
|
|
4680
|
+
}
|
|
4650
4681
|
function getGlobalIndexPath(host = "opencode") {
|
|
4651
4682
|
switch (host) {
|
|
4652
4683
|
case "opencode":
|
|
@@ -4686,6 +4717,9 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
4686
4717
|
return hostIndexPath;
|
|
4687
4718
|
}
|
|
4688
4719
|
if (host !== "opencode") {
|
|
4720
|
+
if (hasHostGlobalConfig(host)) {
|
|
4721
|
+
return hostIndexPath;
|
|
4722
|
+
}
|
|
4689
4723
|
const legacyIndexPath = getGlobalIndexPath("opencode");
|
|
4690
4724
|
if ((0, import_fs6.existsSync)(legacyIndexPath)) {
|
|
4691
4725
|
return legacyIndexPath;
|
|
@@ -4839,8 +4873,8 @@ function normalizeFiles(rawFiles, projectRoot) {
|
|
|
4839
4873
|
const trimmed = raw.trim();
|
|
4840
4874
|
if (!trimmed) continue;
|
|
4841
4875
|
const absolute = path9.resolve(projectRoot, trimmed);
|
|
4842
|
-
const
|
|
4843
|
-
const cleaned =
|
|
4876
|
+
const relative11 = path9.relative(projectRoot, absolute);
|
|
4877
|
+
const cleaned = relative11.startsWith("./") ? relative11.slice(2) : relative11;
|
|
4844
4878
|
if (!seen.has(cleaned)) {
|
|
4845
4879
|
seen.add(cleaned);
|
|
4846
4880
|
result.push(cleaned);
|
|
@@ -4849,8 +4883,477 @@ function normalizeFiles(rawFiles, projectRoot) {
|
|
|
4849
4883
|
return result;
|
|
4850
4884
|
}
|
|
4851
4885
|
|
|
4886
|
+
// src/indexer/git-blame.ts
|
|
4887
|
+
var import_child_process2 = require("child_process");
|
|
4888
|
+
var path10 = __toESM(require("path"), 1);
|
|
4889
|
+
var import_util2 = require("util");
|
|
4890
|
+
var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
|
|
4891
|
+
function parseGitBlamePorcelain(output) {
|
|
4892
|
+
const commits = /* @__PURE__ */ new Map();
|
|
4893
|
+
let current;
|
|
4894
|
+
for (const line of output.split("\n")) {
|
|
4895
|
+
if (/^[0-9a-f]{40} /.test(line)) {
|
|
4896
|
+
const sha = line.slice(0, 40);
|
|
4897
|
+
current = commits.get(sha) ?? {
|
|
4898
|
+
sha,
|
|
4899
|
+
author: "",
|
|
4900
|
+
authorEmail: "",
|
|
4901
|
+
committedAt: 0,
|
|
4902
|
+
summary: "",
|
|
4903
|
+
lines: 0
|
|
4904
|
+
};
|
|
4905
|
+
commits.set(sha, current);
|
|
4906
|
+
continue;
|
|
4907
|
+
}
|
|
4908
|
+
if (!current) {
|
|
4909
|
+
continue;
|
|
4910
|
+
}
|
|
4911
|
+
if (line.startsWith("author ")) {
|
|
4912
|
+
current.author = line.slice("author ".length);
|
|
4913
|
+
} else if (line.startsWith("author-mail ")) {
|
|
4914
|
+
current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
|
|
4915
|
+
} else if (line.startsWith("author-time ")) {
|
|
4916
|
+
current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
|
|
4917
|
+
} else if (line.startsWith("summary ")) {
|
|
4918
|
+
current.summary = line.slice("summary ".length);
|
|
4919
|
+
} else if (line.startsWith(" ")) {
|
|
4920
|
+
current.lines += 1;
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
|
|
4924
|
+
}
|
|
4925
|
+
async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
|
|
4926
|
+
const relativePath = path10.relative(projectRoot, filePath);
|
|
4927
|
+
try {
|
|
4928
|
+
const { stdout } = await execFileAsync2(
|
|
4929
|
+
"git",
|
|
4930
|
+
["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
|
|
4931
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4932
|
+
);
|
|
4933
|
+
return parseGitBlamePorcelain(stdout);
|
|
4934
|
+
} catch {
|
|
4935
|
+
return void 0;
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4939
|
+
// src/indexer/index-lock.ts
|
|
4940
|
+
var import_crypto = require("crypto");
|
|
4941
|
+
var import_fs7 = require("fs");
|
|
4942
|
+
var os4 = __toESM(require("os"), 1);
|
|
4943
|
+
var path11 = __toESM(require("path"), 1);
|
|
4944
|
+
var OWNER_FILE_NAME = "owner.json";
|
|
4945
|
+
var RECLAIM_DIRECTORY_NAME = "reclaiming";
|
|
4946
|
+
var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
|
|
4947
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4948
|
+
var VALID_OPERATIONS = /* @__PURE__ */ new Set([
|
|
4949
|
+
"initialize",
|
|
4950
|
+
"index",
|
|
4951
|
+
"force-index",
|
|
4952
|
+
"clear",
|
|
4953
|
+
"health-check",
|
|
4954
|
+
"retry-failed-batches",
|
|
4955
|
+
"recovery"
|
|
4956
|
+
]);
|
|
4957
|
+
var temporaryCounter = 0;
|
|
4958
|
+
function getErrorCode(error) {
|
|
4959
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
4960
|
+
}
|
|
4961
|
+
function retryTransientFilesystemOperation(operation) {
|
|
4962
|
+
let lastError;
|
|
4963
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4964
|
+
try {
|
|
4965
|
+
operation();
|
|
4966
|
+
return;
|
|
4967
|
+
} catch (error) {
|
|
4968
|
+
lastError = error;
|
|
4969
|
+
const code = getErrorCode(error);
|
|
4970
|
+
if (code !== "EBUSY" && code !== "EPERM") throw error;
|
|
4971
|
+
if (attempt < 2) {
|
|
4972
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
throw lastError;
|
|
4977
|
+
}
|
|
4978
|
+
function parseOwner(value) {
|
|
4979
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4980
|
+
const candidate = value;
|
|
4981
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4982
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4983
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4984
|
+
if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
|
|
4985
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4986
|
+
return candidate;
|
|
4987
|
+
}
|
|
4988
|
+
function parseReclaimOwner(value) {
|
|
4989
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4990
|
+
const candidate = value;
|
|
4991
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4992
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4993
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4994
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4995
|
+
if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
|
|
4996
|
+
return candidate;
|
|
4997
|
+
}
|
|
4998
|
+
function readJsonDirectory(directoryPath, parser) {
|
|
4999
|
+
try {
|
|
5000
|
+
return parser(JSON.parse((0, import_fs7.readFileSync)(path11.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
|
|
5001
|
+
} catch {
|
|
5002
|
+
return null;
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
function readDirectoryOwner(lockPath) {
|
|
5006
|
+
return readJsonDirectory(lockPath, parseOwner);
|
|
5007
|
+
}
|
|
5008
|
+
function readReclaimOwner(markerPath) {
|
|
5009
|
+
return readJsonDirectory(markerPath, parseReclaimOwner);
|
|
5010
|
+
}
|
|
5011
|
+
function readRecoveryOwner(markerPath) {
|
|
5012
|
+
return readDirectoryOwner(markerPath);
|
|
5013
|
+
}
|
|
5014
|
+
function readLegacyOwner(lockPath) {
|
|
5015
|
+
try {
|
|
5016
|
+
const parsed = JSON.parse((0, import_fs7.readFileSync)(lockPath, "utf-8"));
|
|
5017
|
+
if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
|
|
5018
|
+
if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
|
|
5019
|
+
return {
|
|
5020
|
+
pid: Number(parsed.pid),
|
|
5021
|
+
hostname: typeof parsed.hostname === "string" ? parsed.hostname : os4.hostname(),
|
|
5022
|
+
startedAt: parsed.startedAt,
|
|
5023
|
+
operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
|
|
5024
|
+
token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
|
|
5025
|
+
};
|
|
5026
|
+
} catch {
|
|
5027
|
+
return null;
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
function getOwnerLiveness(owner) {
|
|
5031
|
+
if (owner.hostname !== os4.hostname()) return "unknown";
|
|
5032
|
+
try {
|
|
5033
|
+
process.kill(owner.pid, 0);
|
|
5034
|
+
return "alive";
|
|
5035
|
+
} catch (error) {
|
|
5036
|
+
const code = getErrorCode(error);
|
|
5037
|
+
if (code === "ESRCH") return "dead";
|
|
5038
|
+
if (code === "EPERM") return "alive";
|
|
5039
|
+
return "unknown";
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
function sameOwner(left, right) {
|
|
5043
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
|
|
5044
|
+
}
|
|
5045
|
+
function sameReclaimOwner(left, right) {
|
|
5046
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
|
|
5047
|
+
}
|
|
5048
|
+
function publishJsonDirectory(finalPath, value) {
|
|
5049
|
+
const candidatePath = `${finalPath}.candidate.${process.pid}.${(0, import_crypto.randomUUID)()}`;
|
|
5050
|
+
(0, import_fs7.mkdirSync)(candidatePath, { mode: 448 });
|
|
5051
|
+
try {
|
|
5052
|
+
(0, import_fs7.writeFileSync)(path11.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
|
|
5053
|
+
encoding: "utf-8",
|
|
5054
|
+
flag: "wx",
|
|
5055
|
+
mode: 384
|
|
5056
|
+
});
|
|
5057
|
+
if ((0, import_fs7.existsSync)(finalPath)) return false;
|
|
5058
|
+
try {
|
|
5059
|
+
(0, import_fs7.renameSync)(candidatePath, finalPath);
|
|
5060
|
+
return true;
|
|
5061
|
+
} catch (error) {
|
|
5062
|
+
if ((0, import_fs7.existsSync)(finalPath)) return false;
|
|
5063
|
+
throw error;
|
|
5064
|
+
}
|
|
5065
|
+
} finally {
|
|
5066
|
+
if ((0, import_fs7.existsSync)(candidatePath)) (0, import_fs7.rmSync)(candidatePath, { recursive: true, force: true });
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
function createOwner(operation) {
|
|
5070
|
+
return {
|
|
5071
|
+
pid: process.pid,
|
|
5072
|
+
hostname: os4.hostname(),
|
|
5073
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5074
|
+
operation,
|
|
5075
|
+
token: (0, import_crypto.randomUUID)()
|
|
5076
|
+
};
|
|
5077
|
+
}
|
|
5078
|
+
function recoveryMarkerPath(indexPath, owner) {
|
|
5079
|
+
return path11.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
|
|
5080
|
+
}
|
|
5081
|
+
function publishRecoveryMarker(indexPath, owner) {
|
|
5082
|
+
const markerPath = recoveryMarkerPath(indexPath, owner);
|
|
5083
|
+
if (!publishJsonDirectory(markerPath, owner)) {
|
|
5084
|
+
const markerOwner = readRecoveryOwner(markerPath);
|
|
5085
|
+
if (!markerOwner || !sameOwner(markerOwner, owner)) {
|
|
5086
|
+
throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
return markerPath;
|
|
5090
|
+
}
|
|
5091
|
+
function getPendingRecoveries(indexPath) {
|
|
5092
|
+
const recoveries = [];
|
|
5093
|
+
const markerNames = (0, import_fs7.readdirSync)(indexPath).filter((name) => {
|
|
5094
|
+
if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
|
|
5095
|
+
return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
|
|
5096
|
+
}).sort();
|
|
5097
|
+
for (const markerName of markerNames) {
|
|
5098
|
+
const markerPath = path11.join(indexPath, markerName);
|
|
5099
|
+
const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
|
|
5100
|
+
let markerStats;
|
|
5101
|
+
try {
|
|
5102
|
+
markerStats = (0, import_fs7.lstatSync)(markerPath);
|
|
5103
|
+
} catch (error) {
|
|
5104
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5105
|
+
throw error;
|
|
5106
|
+
}
|
|
5107
|
+
if (!markerStats.isDirectory()) {
|
|
5108
|
+
throw new IndexLockContentionError(markerPath, null, "unknown-owner");
|
|
5109
|
+
}
|
|
5110
|
+
const owner = readRecoveryOwner(markerPath);
|
|
5111
|
+
if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
|
|
5112
|
+
throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
|
|
5113
|
+
}
|
|
5114
|
+
recoveries.push({ owner, markerPath });
|
|
5115
|
+
}
|
|
5116
|
+
recoveries.sort((left, right) => {
|
|
5117
|
+
const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
|
|
5118
|
+
return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
|
|
5119
|
+
});
|
|
5120
|
+
return recoveries;
|
|
5121
|
+
}
|
|
5122
|
+
function cleanupDeadPublicationCandidates(indexPath) {
|
|
5123
|
+
const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
|
|
5124
|
+
for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
|
|
5125
|
+
const match = candidatePattern.exec(entry);
|
|
5126
|
+
if (!match) continue;
|
|
5127
|
+
const pid = Number(match[1]);
|
|
5128
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
5129
|
+
if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
|
|
5130
|
+
(0, import_fs7.rmSync)(path11.join(indexPath, entry), { recursive: true, force: true });
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
function removeDeadReclaimMarker(lockPath, expectedOwner) {
|
|
5134
|
+
const markerPath = path11.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5135
|
+
const marker = readReclaimOwner(markerPath);
|
|
5136
|
+
if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
|
|
5137
|
+
return false;
|
|
5138
|
+
}
|
|
5139
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5140
|
+
if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5141
|
+
return false;
|
|
5142
|
+
}
|
|
5143
|
+
const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${(0, import_crypto.randomUUID)()}`;
|
|
5144
|
+
try {
|
|
5145
|
+
(0, import_fs7.renameSync)(markerPath, claimedMarkerPath);
|
|
5146
|
+
} catch (error) {
|
|
5147
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5148
|
+
throw error;
|
|
5149
|
+
}
|
|
5150
|
+
const claimedMarker = readReclaimOwner(claimedMarkerPath);
|
|
5151
|
+
const ownerAfterClaim = readDirectoryOwner(lockPath);
|
|
5152
|
+
if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
|
|
5153
|
+
if (!(0, import_fs7.existsSync)(markerPath) && (0, import_fs7.existsSync)(claimedMarkerPath)) {
|
|
5154
|
+
(0, import_fs7.renameSync)(claimedMarkerPath, markerPath);
|
|
5155
|
+
}
|
|
5156
|
+
return false;
|
|
5157
|
+
}
|
|
5158
|
+
(0, import_fs7.rmSync)(claimedMarkerPath, { recursive: true, force: true });
|
|
5159
|
+
return true;
|
|
5160
|
+
}
|
|
5161
|
+
function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
|
|
5162
|
+
const reclaimPath = path11.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5163
|
+
const reclaimOwner = {
|
|
5164
|
+
pid: process.pid,
|
|
5165
|
+
hostname: os4.hostname(),
|
|
5166
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5167
|
+
token: (0, import_crypto.randomUUID)(),
|
|
5168
|
+
expectedOwnerToken: expectedOwner.token
|
|
5169
|
+
};
|
|
5170
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
5171
|
+
if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
|
|
5172
|
+
if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
|
|
5173
|
+
return false;
|
|
5174
|
+
}
|
|
5175
|
+
try {
|
|
5176
|
+
const currentReclaimer = readReclaimOwner(reclaimPath);
|
|
5177
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5178
|
+
if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5179
|
+
return false;
|
|
5180
|
+
}
|
|
5181
|
+
publishRecoveryMarker(indexPath, expectedOwner);
|
|
5182
|
+
const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
|
|
5183
|
+
const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
|
|
5184
|
+
if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
|
|
5185
|
+
return false;
|
|
5186
|
+
}
|
|
5187
|
+
const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
|
|
5188
|
+
(0, import_fs7.renameSync)(lockPath, quarantinePath);
|
|
5189
|
+
(0, import_fs7.rmSync)(quarantinePath, { recursive: true, force: true });
|
|
5190
|
+
return true;
|
|
5191
|
+
} catch (error) {
|
|
5192
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5193
|
+
throw error;
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
var IndexLockContentionError = class extends Error {
|
|
5197
|
+
constructor(lockPath, owner, reason) {
|
|
5198
|
+
const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
|
|
5199
|
+
super(`Index mutation already in progress: ${ownerDescription}`);
|
|
5200
|
+
this.lockPath = lockPath;
|
|
5201
|
+
this.owner = owner;
|
|
5202
|
+
this.reason = reason;
|
|
5203
|
+
this.name = "IndexLockContentionError";
|
|
5204
|
+
}
|
|
5205
|
+
lockPath;
|
|
5206
|
+
owner;
|
|
5207
|
+
reason;
|
|
5208
|
+
code = "INDEX_BUSY";
|
|
5209
|
+
};
|
|
5210
|
+
function isIndexLockContentionError(error) {
|
|
5211
|
+
return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
|
|
5212
|
+
}
|
|
5213
|
+
function isTransientIndexLockContention(error) {
|
|
5214
|
+
if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
|
|
5215
|
+
return error.reason === "active" || error.reason === "reclaiming";
|
|
5216
|
+
}
|
|
5217
|
+
function acquireIndexLock(indexPath, operation) {
|
|
5218
|
+
(0, import_fs7.mkdirSync)(indexPath, { recursive: true });
|
|
5219
|
+
const canonicalIndexPath = import_fs7.realpathSync.native(indexPath);
|
|
5220
|
+
const lockPath = path11.join(canonicalIndexPath, "indexing.lock");
|
|
5221
|
+
cleanupDeadPublicationCandidates(canonicalIndexPath);
|
|
5222
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
5223
|
+
const owner = createOwner(operation);
|
|
5224
|
+
if (publishJsonDirectory(lockPath, owner)) {
|
|
5225
|
+
const lease = {
|
|
5226
|
+
canonicalIndexPath,
|
|
5227
|
+
lockPath,
|
|
5228
|
+
owner,
|
|
5229
|
+
recoveries: []
|
|
5230
|
+
};
|
|
5231
|
+
try {
|
|
5232
|
+
lease.recoveries = getPendingRecoveries(canonicalIndexPath);
|
|
5233
|
+
return lease;
|
|
5234
|
+
} catch (error) {
|
|
5235
|
+
releaseIndexLock(lease);
|
|
5236
|
+
throw error;
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
let stats;
|
|
5240
|
+
try {
|
|
5241
|
+
stats = (0, import_fs7.lstatSync)(lockPath);
|
|
5242
|
+
} catch (error) {
|
|
5243
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5244
|
+
throw error;
|
|
5245
|
+
}
|
|
5246
|
+
if (!stats.isDirectory()) {
|
|
5247
|
+
const legacyOwner = readLegacyOwner(lockPath);
|
|
5248
|
+
throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
|
|
5249
|
+
}
|
|
5250
|
+
const existingOwner = readDirectoryOwner(lockPath);
|
|
5251
|
+
if (!existingOwner) {
|
|
5252
|
+
throw new IndexLockContentionError(lockPath, null, "unknown-owner");
|
|
5253
|
+
}
|
|
5254
|
+
const liveness = getOwnerLiveness(existingOwner);
|
|
5255
|
+
if (liveness !== "dead") {
|
|
5256
|
+
throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
|
|
5257
|
+
}
|
|
5258
|
+
if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
|
|
5259
|
+
throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
throw new IndexLockContentionError(lockPath, null, "reclaiming");
|
|
5263
|
+
}
|
|
5264
|
+
function releaseIndexLock(lease) {
|
|
5265
|
+
const currentOwner = readDirectoryOwner(lease.lockPath);
|
|
5266
|
+
if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
|
|
5267
|
+
const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
|
|
5268
|
+
try {
|
|
5269
|
+
retryTransientFilesystemOperation(() => (0, import_fs7.renameSync)(lease.lockPath, releasePath));
|
|
5270
|
+
} catch (error) {
|
|
5271
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5272
|
+
throw error;
|
|
5273
|
+
}
|
|
5274
|
+
const claimedOwner = readDirectoryOwner(releasePath);
|
|
5275
|
+
if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
|
|
5276
|
+
if (!(0, import_fs7.existsSync)(lease.lockPath) && (0, import_fs7.existsSync)(releasePath)) {
|
|
5277
|
+
(0, import_fs7.renameSync)(releasePath, lease.lockPath);
|
|
5278
|
+
}
|
|
5279
|
+
return false;
|
|
5280
|
+
}
|
|
5281
|
+
try {
|
|
5282
|
+
retryTransientFilesystemOperation(() => (0, import_fs7.rmSync)(releasePath, { recursive: true, force: true }));
|
|
5283
|
+
} catch (error) {
|
|
5284
|
+
console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
|
|
5285
|
+
}
|
|
5286
|
+
return true;
|
|
5287
|
+
}
|
|
5288
|
+
async function withIndexLock(indexPath, operation, callback, options = {}) {
|
|
5289
|
+
const lease = acquireIndexLock(indexPath, operation);
|
|
5290
|
+
let result;
|
|
5291
|
+
let callbackError;
|
|
5292
|
+
let callbackFailed = false;
|
|
5293
|
+
try {
|
|
5294
|
+
result = await callback(lease);
|
|
5295
|
+
} catch (error) {
|
|
5296
|
+
callbackFailed = true;
|
|
5297
|
+
callbackError = error;
|
|
5298
|
+
}
|
|
5299
|
+
if (!callbackFailed && options.completeRecoveries !== false) {
|
|
5300
|
+
try {
|
|
5301
|
+
completeLeaseRecovery(lease);
|
|
5302
|
+
} catch (error) {
|
|
5303
|
+
callbackFailed = true;
|
|
5304
|
+
callbackError = error;
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
let releaseError;
|
|
5308
|
+
try {
|
|
5309
|
+
if (!releaseIndexLock(lease)) {
|
|
5310
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
5311
|
+
}
|
|
5312
|
+
} catch (error) {
|
|
5313
|
+
releaseError = error;
|
|
5314
|
+
}
|
|
5315
|
+
if (releaseError !== void 0) {
|
|
5316
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
5317
|
+
throw releaseError;
|
|
5318
|
+
}
|
|
5319
|
+
if (callbackFailed) throw callbackError;
|
|
5320
|
+
return result;
|
|
5321
|
+
}
|
|
5322
|
+
function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
|
|
5323
|
+
if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
|
|
5324
|
+
temporaryCounter += 1;
|
|
5325
|
+
return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
|
|
5326
|
+
}
|
|
5327
|
+
function removeLeaseTemporaryPath(temporaryPath) {
|
|
5328
|
+
if ((0, import_fs7.existsSync)(temporaryPath)) (0, import_fs7.rmSync)(temporaryPath, { recursive: true, force: true });
|
|
5329
|
+
}
|
|
5330
|
+
function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
|
|
5331
|
+
for (const targetPath of backupTargets) {
|
|
5332
|
+
const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
|
|
5333
|
+
if (!(0, import_fs7.existsSync)(backupPath)) continue;
|
|
5334
|
+
if ((0, import_fs7.existsSync)(targetPath)) (0, import_fs7.rmSync)(targetPath, { recursive: true, force: true });
|
|
5335
|
+
(0, import_fs7.renameSync)(backupPath, targetPath);
|
|
5336
|
+
}
|
|
5337
|
+
const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
|
|
5338
|
+
for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
|
|
5339
|
+
if (!entry.includes(temporaryOwnerMarker)) continue;
|
|
5340
|
+
(0, import_fs7.rmSync)(path11.join(indexPath, entry), { recursive: true, force: true });
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
function completeLeaseRecovery(lease) {
|
|
5344
|
+
for (const recovery of lease.recoveries) {
|
|
5345
|
+
const markerOwner = readRecoveryOwner(recovery.markerPath);
|
|
5346
|
+
if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
|
|
5347
|
+
throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
for (const recovery of lease.recoveries) {
|
|
5351
|
+
(0, import_fs7.rmSync)(recovery.markerPath, { recursive: true, force: true });
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
|
|
4852
5355
|
// src/indexer/index.ts
|
|
4853
|
-
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
5356
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
|
|
4854
5357
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
4855
5358
|
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
4856
5359
|
"function_declaration",
|
|
@@ -4930,6 +5433,46 @@ function isSqliteCorruptionError(error) {
|
|
|
4930
5433
|
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");
|
|
4931
5434
|
}
|
|
4932
5435
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
5436
|
+
var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
|
|
5437
|
+
function metadataFromBlame(blame) {
|
|
5438
|
+
if (!blame) {
|
|
5439
|
+
return {};
|
|
5440
|
+
}
|
|
5441
|
+
return {
|
|
5442
|
+
blameSha: blame.sha,
|
|
5443
|
+
blameAuthor: blame.author,
|
|
5444
|
+
blameAuthorEmail: blame.authorEmail,
|
|
5445
|
+
blameCommittedAt: blame.committedAt,
|
|
5446
|
+
blameSummary: blame.summary
|
|
5447
|
+
};
|
|
5448
|
+
}
|
|
5449
|
+
function blameFromChunkData(chunk) {
|
|
5450
|
+
if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
|
|
5451
|
+
return void 0;
|
|
5452
|
+
}
|
|
5453
|
+
return {
|
|
5454
|
+
sha: chunk.blameSha,
|
|
5455
|
+
author: chunk.blameAuthor,
|
|
5456
|
+
authorEmail: chunk.blameAuthorEmail,
|
|
5457
|
+
committedAt: chunk.blameCommittedAt,
|
|
5458
|
+
summary: chunk.blameSummary
|
|
5459
|
+
};
|
|
5460
|
+
}
|
|
5461
|
+
function blameFromMetadata(metadata) {
|
|
5462
|
+
if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
|
|
5463
|
+
return void 0;
|
|
5464
|
+
}
|
|
5465
|
+
return {
|
|
5466
|
+
sha: metadata.blameSha,
|
|
5467
|
+
author: metadata.blameAuthor,
|
|
5468
|
+
authorEmail: metadata.blameAuthorEmail,
|
|
5469
|
+
committedAt: metadata.blameCommittedAt,
|
|
5470
|
+
summary: metadata.blameSummary
|
|
5471
|
+
};
|
|
5472
|
+
}
|
|
5473
|
+
function hasBlameMetadata(metadata) {
|
|
5474
|
+
return blameFromMetadata(metadata) !== void 0;
|
|
5475
|
+
}
|
|
4933
5476
|
var INDEX_METADATA_VERSION = "1";
|
|
4934
5477
|
var EMBEDDING_STRATEGY_VERSION = "2";
|
|
4935
5478
|
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
@@ -5088,9 +5631,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
5088
5631
|
return true;
|
|
5089
5632
|
}
|
|
5090
5633
|
function isPathWithinRoot(filePath, rootPath) {
|
|
5091
|
-
const normalizedFilePath =
|
|
5092
|
-
const normalizedRoot =
|
|
5093
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
5634
|
+
const normalizedFilePath = path12.resolve(filePath);
|
|
5635
|
+
const normalizedRoot = path12.resolve(rootPath);
|
|
5636
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
|
|
5094
5637
|
}
|
|
5095
5638
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
5096
5639
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5849,7 +6392,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
|
|
|
5849
6392
|
chunkType,
|
|
5850
6393
|
name: chunk.name ?? void 0,
|
|
5851
6394
|
language: chunk.language,
|
|
5852
|
-
hash: chunk.contentHash
|
|
6395
|
+
hash: chunk.contentHash,
|
|
6396
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5853
6397
|
};
|
|
5854
6398
|
const baselineScore = existing?.score ?? 0.5;
|
|
5855
6399
|
candidateUnion.set(chunk.chunkId, {
|
|
@@ -5933,7 +6477,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
|
|
|
5933
6477
|
chunkType,
|
|
5934
6478
|
name: chunk.name ?? void 0,
|
|
5935
6479
|
language: chunk.language,
|
|
5936
|
-
hash: chunk.contentHash
|
|
6480
|
+
hash: chunk.contentHash,
|
|
6481
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5937
6482
|
}
|
|
5938
6483
|
});
|
|
5939
6484
|
}
|
|
@@ -6102,6 +6647,21 @@ function matchesSearchFilters(candidate, options, minScore) {
|
|
|
6102
6647
|
if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
|
|
6103
6648
|
return false;
|
|
6104
6649
|
}
|
|
6650
|
+
if (options?.blameAuthor) {
|
|
6651
|
+
const author = options.blameAuthor.toLowerCase();
|
|
6652
|
+
const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
|
|
6653
|
+
const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
|
|
6654
|
+
if (candidateAuthor !== author && candidateEmail !== author) return false;
|
|
6655
|
+
}
|
|
6656
|
+
if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
|
|
6657
|
+
return false;
|
|
6658
|
+
}
|
|
6659
|
+
if (options?.blameSince) {
|
|
6660
|
+
const sinceMs = Date.parse(options.blameSince);
|
|
6661
|
+
if (Number.isNaN(sinceMs)) return false;
|
|
6662
|
+
const committedAt = candidate.metadata.blameCommittedAt;
|
|
6663
|
+
if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
|
|
6664
|
+
}
|
|
6105
6665
|
return true;
|
|
6106
6666
|
}
|
|
6107
6667
|
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
@@ -6139,26 +6699,133 @@ var Indexer = class {
|
|
|
6139
6699
|
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
6140
6700
|
querySimilarityThreshold = 0.85;
|
|
6141
6701
|
indexCompatibility = null;
|
|
6142
|
-
|
|
6702
|
+
activeIndexLease = null;
|
|
6703
|
+
initializationPromise = null;
|
|
6704
|
+
initializationMode = "none";
|
|
6705
|
+
readIssues = [];
|
|
6706
|
+
retiredDatabases = [];
|
|
6707
|
+
readerArtifactFingerprint = null;
|
|
6708
|
+
writerArtifactFingerprint = null;
|
|
6709
|
+
readerArtifactRetryAfter = /* @__PURE__ */ new Map();
|
|
6143
6710
|
constructor(projectRoot, config, host = "opencode") {
|
|
6144
6711
|
this.projectRoot = projectRoot;
|
|
6145
6712
|
this.config = config;
|
|
6146
6713
|
this.host = host;
|
|
6147
6714
|
this.indexPath = this.getIndexPath();
|
|
6148
|
-
this.fileHashCachePath =
|
|
6149
|
-
this.failedBatchesPath =
|
|
6150
|
-
this.indexingLockPath = path10.join(this.indexPath, "indexing.lock");
|
|
6715
|
+
this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
|
|
6716
|
+
this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
|
|
6151
6717
|
this.logger = initializeLogger(config.debug);
|
|
6152
6718
|
}
|
|
6153
6719
|
getIndexPath() {
|
|
6154
6720
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
|
|
6155
6721
|
}
|
|
6722
|
+
isLocalProjectIndexPath() {
|
|
6723
|
+
const localProjectIndexPaths = [path12.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
|
|
6724
|
+
if (this.host !== "opencode") {
|
|
6725
|
+
localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
6726
|
+
}
|
|
6727
|
+
return localProjectIndexPaths.some((localPath) => {
|
|
6728
|
+
if (!(0, import_fs8.existsSync)(localPath) || !(0, import_fs8.existsSync)(this.indexPath)) {
|
|
6729
|
+
return path12.resolve(this.indexPath) === path12.resolve(localPath);
|
|
6730
|
+
}
|
|
6731
|
+
const indexStats = (0, import_fs8.statSync)(this.indexPath);
|
|
6732
|
+
const localStats = (0, import_fs8.statSync)(localPath);
|
|
6733
|
+
return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
|
|
6734
|
+
});
|
|
6735
|
+
}
|
|
6736
|
+
resetLoadedIndexState(retireDatabase = false) {
|
|
6737
|
+
if (this.database) {
|
|
6738
|
+
if (retireDatabase) {
|
|
6739
|
+
this.retiredDatabases.push(this.database);
|
|
6740
|
+
} else {
|
|
6741
|
+
this.database.close();
|
|
6742
|
+
}
|
|
6743
|
+
}
|
|
6744
|
+
this.store = null;
|
|
6745
|
+
this.invertedIndex = null;
|
|
6746
|
+
this.database = null;
|
|
6747
|
+
this.provider = null;
|
|
6748
|
+
this.configuredProviderInfo = null;
|
|
6749
|
+
this.reranker = null;
|
|
6750
|
+
this.indexCompatibility = null;
|
|
6751
|
+
this.initializationMode = "none";
|
|
6752
|
+
this.readIssues = [];
|
|
6753
|
+
this.readerArtifactFingerprint = null;
|
|
6754
|
+
this.writerArtifactFingerprint = null;
|
|
6755
|
+
this.readerArtifactRetryAfter.clear();
|
|
6756
|
+
this.fileHashCache.clear();
|
|
6757
|
+
}
|
|
6758
|
+
refreshLoadedIndexState() {
|
|
6759
|
+
if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
|
|
6760
|
+
this.store.load();
|
|
6761
|
+
this.invertedIndex.load();
|
|
6762
|
+
this.fileHashCache.clear();
|
|
6763
|
+
this.loadFileHashCache();
|
|
6764
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
6765
|
+
this.readIssues = [];
|
|
6766
|
+
this.readerArtifactRetryAfter.clear();
|
|
6767
|
+
}
|
|
6768
|
+
async withIndexMutationLease(operation, callback) {
|
|
6769
|
+
const lease = acquireIndexLock(this.indexPath, operation);
|
|
6770
|
+
this.indexPath = lease.canonicalIndexPath;
|
|
6771
|
+
this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
|
|
6772
|
+
this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
|
|
6773
|
+
this.activeIndexLease = lease;
|
|
6774
|
+
let result;
|
|
6775
|
+
let callbackError;
|
|
6776
|
+
let callbackFailed = false;
|
|
6777
|
+
try {
|
|
6778
|
+
result = await callback(lease.recoveries.map(({ owner }) => owner));
|
|
6779
|
+
} catch (error) {
|
|
6780
|
+
callbackFailed = true;
|
|
6781
|
+
callbackError = error;
|
|
6782
|
+
}
|
|
6783
|
+
if (!callbackFailed) {
|
|
6784
|
+
try {
|
|
6785
|
+
completeLeaseRecovery(lease);
|
|
6786
|
+
this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
|
|
6787
|
+
} catch (error) {
|
|
6788
|
+
callbackFailed = true;
|
|
6789
|
+
callbackError = error;
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
let releaseError;
|
|
6793
|
+
try {
|
|
6794
|
+
if (!releaseIndexLock(lease)) {
|
|
6795
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
6796
|
+
this.writerArtifactFingerprint = null;
|
|
6797
|
+
if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6798
|
+
this.activeIndexLease = null;
|
|
6799
|
+
}
|
|
6800
|
+
} else if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6801
|
+
this.activeIndexLease = null;
|
|
6802
|
+
}
|
|
6803
|
+
} catch (error) {
|
|
6804
|
+
releaseError = error;
|
|
6805
|
+
this.writerArtifactFingerprint = null;
|
|
6806
|
+
if (!(0, import_fs8.existsSync)(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6807
|
+
this.activeIndexLease = null;
|
|
6808
|
+
}
|
|
6809
|
+
}
|
|
6810
|
+
if (releaseError !== void 0) {
|
|
6811
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
6812
|
+
throw releaseError;
|
|
6813
|
+
}
|
|
6814
|
+
if (callbackFailed) throw callbackError;
|
|
6815
|
+
return result;
|
|
6816
|
+
}
|
|
6817
|
+
requireActiveLease() {
|
|
6818
|
+
if (!this.activeIndexLease) {
|
|
6819
|
+
throw new Error("Index mutation attempted without an active interprocess lease");
|
|
6820
|
+
}
|
|
6821
|
+
return this.activeIndexLease;
|
|
6822
|
+
}
|
|
6156
6823
|
loadFileHashCache() {
|
|
6157
|
-
if (!(0,
|
|
6824
|
+
if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
|
|
6158
6825
|
return;
|
|
6159
6826
|
}
|
|
6160
6827
|
try {
|
|
6161
|
-
const data = (0,
|
|
6828
|
+
const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
|
|
6162
6829
|
const parsed = JSON.parse(data);
|
|
6163
6830
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
6164
6831
|
} catch (error) {
|
|
@@ -6178,15 +6845,26 @@ var Indexer = class {
|
|
|
6178
6845
|
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
6179
6846
|
}
|
|
6180
6847
|
atomicWriteSync(targetPath, data) {
|
|
6181
|
-
const
|
|
6182
|
-
(
|
|
6183
|
-
(0,
|
|
6184
|
-
|
|
6848
|
+
const lease = this.requireActiveLease();
|
|
6849
|
+
const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
|
|
6850
|
+
(0, import_fs8.mkdirSync)(path12.dirname(targetPath), { recursive: true });
|
|
6851
|
+
try {
|
|
6852
|
+
(0, import_fs8.writeFileSync)(tempPath, data);
|
|
6853
|
+
(0, import_fs8.renameSync)(tempPath, targetPath);
|
|
6854
|
+
} finally {
|
|
6855
|
+
removeLeaseTemporaryPath(tempPath);
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
saveInvertedIndex(invertedIndex) {
|
|
6859
|
+
this.atomicWriteSync(
|
|
6860
|
+
path12.join(this.indexPath, "inverted-index.json"),
|
|
6861
|
+
invertedIndex.serialize()
|
|
6862
|
+
);
|
|
6185
6863
|
}
|
|
6186
6864
|
getScopedRoots() {
|
|
6187
|
-
const roots = /* @__PURE__ */ new Set([
|
|
6865
|
+
const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
|
|
6188
6866
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
6189
|
-
roots.add(
|
|
6867
|
+
roots.add(path12.resolve(this.projectRoot, kbRoot));
|
|
6190
6868
|
}
|
|
6191
6869
|
return Array.from(roots);
|
|
6192
6870
|
}
|
|
@@ -6195,29 +6873,29 @@ var Indexer = class {
|
|
|
6195
6873
|
if (this.config.scope !== "global") {
|
|
6196
6874
|
return branchName;
|
|
6197
6875
|
}
|
|
6198
|
-
const projectHash = hashContent(
|
|
6876
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6199
6877
|
return `${projectHash}:${branchName}`;
|
|
6200
6878
|
}
|
|
6201
6879
|
getBranchCatalogKeyFor(branchName) {
|
|
6202
6880
|
if (this.config.scope !== "global") {
|
|
6203
6881
|
return branchName;
|
|
6204
6882
|
}
|
|
6205
|
-
const projectHash = hashContent(
|
|
6883
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6206
6884
|
return `${projectHash}:${branchName}`;
|
|
6207
6885
|
}
|
|
6208
6886
|
getLegacyBranchCatalogKey() {
|
|
6209
6887
|
return this.currentBranch || "default";
|
|
6210
6888
|
}
|
|
6211
6889
|
getLegacyMigrationMetadataKey() {
|
|
6212
|
-
const projectHash = hashContent(
|
|
6890
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6213
6891
|
return `index.globalBranchMigration.${projectHash}`;
|
|
6214
6892
|
}
|
|
6215
6893
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
6216
|
-
const projectHash = hashContent(
|
|
6894
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6217
6895
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
6218
6896
|
}
|
|
6219
6897
|
getProjectForceReembedMetadataKey() {
|
|
6220
|
-
const projectHash = hashContent(
|
|
6898
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6221
6899
|
return `index.forceReembed.${projectHash}`;
|
|
6222
6900
|
}
|
|
6223
6901
|
hasProjectForceReembedPending() {
|
|
@@ -6311,7 +6989,7 @@ var Indexer = class {
|
|
|
6311
6989
|
if (!this.database) {
|
|
6312
6990
|
return { chunkIds, symbolIds };
|
|
6313
6991
|
}
|
|
6314
|
-
const projectRootPath =
|
|
6992
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6315
6993
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6316
6994
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6317
6995
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6334,7 +7012,7 @@ var Indexer = class {
|
|
|
6334
7012
|
if (this.config.scope !== "global") {
|
|
6335
7013
|
return this.getBranchCatalogCleanupKeys();
|
|
6336
7014
|
}
|
|
6337
|
-
const projectHash = hashContent(
|
|
7015
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6338
7016
|
const keys = /* @__PURE__ */ new Set();
|
|
6339
7017
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6340
7018
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6418,7 +7096,7 @@ var Indexer = class {
|
|
|
6418
7096
|
if (!this.database || this.config.scope !== "global") {
|
|
6419
7097
|
return false;
|
|
6420
7098
|
}
|
|
6421
|
-
const projectHash = hashContent(
|
|
7099
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6422
7100
|
const roots = this.getScopedRoots();
|
|
6423
7101
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6424
7102
|
return this.database.getAllBranches().some(
|
|
@@ -6452,7 +7130,7 @@ var Indexer = class {
|
|
|
6452
7130
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6453
7131
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6454
7132
|
]);
|
|
6455
|
-
const projectRootPath =
|
|
7133
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6456
7134
|
const projectLocalFilePaths = new Set(
|
|
6457
7135
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6458
7136
|
);
|
|
@@ -6514,34 +7192,27 @@ var Indexer = class {
|
|
|
6514
7192
|
database.gcOrphanEmbeddings();
|
|
6515
7193
|
database.gcOrphanChunks();
|
|
6516
7194
|
store.save();
|
|
6517
|
-
|
|
7195
|
+
this.saveInvertedIndex(invertedIndex);
|
|
6518
7196
|
return {
|
|
6519
7197
|
removedChunkIds: removedChunkIdList,
|
|
6520
7198
|
hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
|
|
6521
7199
|
};
|
|
6522
7200
|
}
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
(0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
|
|
6532
|
-
}
|
|
6533
|
-
releaseIndexingLock() {
|
|
6534
|
-
if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
|
|
6535
|
-
(0, import_fs7.unlinkSync)(this.indexingLockPath);
|
|
7201
|
+
async recoverFromInterruptedIndexingUnlocked(owners) {
|
|
7202
|
+
for (const owner of owners) {
|
|
7203
|
+
this.logger.warn("Detected interrupted indexing session, recovering...", {
|
|
7204
|
+
pid: owner.pid,
|
|
7205
|
+
hostname: owner.hostname,
|
|
7206
|
+
operation: owner.operation,
|
|
7207
|
+
startedAt: owner.startedAt
|
|
7208
|
+
});
|
|
6536
7209
|
}
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
7210
|
+
if (this.config.scope === "global") {
|
|
7211
|
+
if ((0, import_fs8.existsSync)(this.fileHashCachePath)) {
|
|
7212
|
+
(0, import_fs8.unlinkSync)(this.fileHashCachePath);
|
|
7213
|
+
}
|
|
7214
|
+
await this.healthCheckUnlocked();
|
|
6542
7215
|
}
|
|
6543
|
-
await this.healthCheck();
|
|
6544
|
-
this.releaseIndexingLock();
|
|
6545
7216
|
this.logger.info("Recovery complete, next index will re-process all files");
|
|
6546
7217
|
}
|
|
6547
7218
|
loadFailedBatches(maxChunkTokens) {
|
|
@@ -6557,10 +7228,10 @@ var Indexer = class {
|
|
|
6557
7228
|
}
|
|
6558
7229
|
}
|
|
6559
7230
|
loadSerializedFailedBatches() {
|
|
6560
|
-
if (!(0,
|
|
7231
|
+
if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
|
|
6561
7232
|
return [];
|
|
6562
7233
|
}
|
|
6563
|
-
const data = (0,
|
|
7234
|
+
const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
|
|
6564
7235
|
const parsed = JSON.parse(data);
|
|
6565
7236
|
return parsed.map((batch) => {
|
|
6566
7237
|
const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
|
|
@@ -6577,15 +7248,15 @@ var Indexer = class {
|
|
|
6577
7248
|
}
|
|
6578
7249
|
saveFailedBatches(batches) {
|
|
6579
7250
|
if (batches.length === 0) {
|
|
6580
|
-
if ((0,
|
|
7251
|
+
if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
|
|
6581
7252
|
try {
|
|
6582
|
-
(0,
|
|
7253
|
+
(0, import_fs8.unlinkSync)(this.failedBatchesPath);
|
|
6583
7254
|
} catch {
|
|
6584
7255
|
}
|
|
6585
7256
|
}
|
|
6586
7257
|
return;
|
|
6587
7258
|
}
|
|
6588
|
-
|
|
7259
|
+
this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
6589
7260
|
}
|
|
6590
7261
|
collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
|
|
6591
7262
|
const retryableById = /* @__PURE__ */ new Map();
|
|
@@ -6765,7 +7436,7 @@ var Indexer = class {
|
|
|
6765
7436
|
const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
|
|
6766
7437
|
parts.push(`intent_hint: ${intent}`);
|
|
6767
7438
|
try {
|
|
6768
|
-
const fileContent = await
|
|
7439
|
+
const fileContent = await import_fs8.promises.readFile(candidate.metadata.filePath, "utf-8");
|
|
6769
7440
|
const lines = fileContent.split("\n");
|
|
6770
7441
|
const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
|
|
6771
7442
|
const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
|
|
@@ -6778,7 +7449,223 @@ var Indexer = class {
|
|
|
6778
7449
|
}
|
|
6779
7450
|
return parts.join("\n");
|
|
6780
7451
|
}
|
|
6781
|
-
async initialize() {
|
|
7452
|
+
async initialize() {
|
|
7453
|
+
if (this.initializationPromise) {
|
|
7454
|
+
await this.initializationPromise;
|
|
7455
|
+
}
|
|
7456
|
+
if (this.isInitializedFor("reader")) {
|
|
7457
|
+
return;
|
|
7458
|
+
}
|
|
7459
|
+
await this.initializeOnce("reader", [], { skipAutoGc: true });
|
|
7460
|
+
}
|
|
7461
|
+
async initializeOnce(mode, recoveredOwners, options) {
|
|
7462
|
+
if (this.initializationPromise) {
|
|
7463
|
+
await this.initializationPromise;
|
|
7464
|
+
if (this.isInitializedFor(mode)) {
|
|
7465
|
+
return;
|
|
7466
|
+
}
|
|
7467
|
+
return this.initializeOnce(mode, recoveredOwners, options);
|
|
7468
|
+
}
|
|
7469
|
+
if (this.isInitializedFor(mode)) {
|
|
7470
|
+
return;
|
|
7471
|
+
}
|
|
7472
|
+
const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
|
|
7473
|
+
this.resetLoadedIndexState();
|
|
7474
|
+
throw error;
|
|
7475
|
+
}).finally(() => {
|
|
7476
|
+
if (this.initializationPromise === initialization) {
|
|
7477
|
+
this.initializationPromise = null;
|
|
7478
|
+
}
|
|
7479
|
+
});
|
|
7480
|
+
this.initializationPromise = initialization;
|
|
7481
|
+
await initialization;
|
|
7482
|
+
}
|
|
7483
|
+
isInitializedFor(mode) {
|
|
7484
|
+
const hasState = Boolean(
|
|
7485
|
+
this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
|
|
7486
|
+
);
|
|
7487
|
+
if (!hasState) {
|
|
7488
|
+
return false;
|
|
7489
|
+
}
|
|
7490
|
+
return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
|
|
7491
|
+
}
|
|
7492
|
+
recordReadIssue(component, message, error) {
|
|
7493
|
+
this.readIssues.push(this.createReadIssue(component, message));
|
|
7494
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7495
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7496
|
+
}
|
|
7497
|
+
createReadIssue(component, message) {
|
|
7498
|
+
return {
|
|
7499
|
+
component,
|
|
7500
|
+
message,
|
|
7501
|
+
blocking: component !== "keyword"
|
|
7502
|
+
};
|
|
7503
|
+
}
|
|
7504
|
+
getVectorReadIssueMessage() {
|
|
7505
|
+
if (this.config.scope === "global") {
|
|
7506
|
+
return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
|
|
7507
|
+
}
|
|
7508
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7509
|
+
return "Vector index could not be read from an inherited project index. Restore or fingerprint it from the checkout that owns the index; do not remove or rebuild it from this worktree.";
|
|
7510
|
+
}
|
|
7511
|
+
return "Vector index could not be read. Run index_codebase after the active writer finishes to fingerprint a structurally valid legacy pair, or remove this checkout's local index directory and run index_codebase to rebuild it.";
|
|
7512
|
+
}
|
|
7513
|
+
getKeywordReadIssueMessage() {
|
|
7514
|
+
if (this.config.scope === "global") {
|
|
7515
|
+
return "Shared keyword index could not be read; semantic search remains available. Restore or repair the shared keyword artifact; automatic reset is disabled for global scope.";
|
|
7516
|
+
}
|
|
7517
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7518
|
+
return "Keyword index could not be read from an inherited project index; semantic search remains available. Restore or repair it from the checkout that owns the index; do not rebuild it from this worktree.";
|
|
7519
|
+
}
|
|
7520
|
+
return "Keyword index could not be read; semantic search remains available. Restore a readable published keyword index, or run index_codebase with force=true after the active writer finishes.";
|
|
7521
|
+
}
|
|
7522
|
+
getDatabaseReadIssueMessage() {
|
|
7523
|
+
if (this.config.scope === "global") {
|
|
7524
|
+
return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
|
|
7525
|
+
}
|
|
7526
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7527
|
+
return "Index database could not be read from an inherited project index. Restore or repair it from the checkout that owns the index; do not migrate or rebuild it from this worktree.";
|
|
7528
|
+
}
|
|
7529
|
+
return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
|
|
7530
|
+
}
|
|
7531
|
+
getReaderFileFingerprint(filePath, identityOnly = false) {
|
|
7532
|
+
try {
|
|
7533
|
+
const stats = (0, import_fs8.statSync)(filePath);
|
|
7534
|
+
if (identityOnly) {
|
|
7535
|
+
return `${stats.dev}:${stats.ino}`;
|
|
7536
|
+
}
|
|
7537
|
+
return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
|
|
7538
|
+
} catch (error) {
|
|
7539
|
+
return `unavailable:${getErrorMessage2(error)}`;
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
captureReaderArtifactFingerprint() {
|
|
7543
|
+
const storePath = path12.join(this.indexPath, "vectors");
|
|
7544
|
+
return {
|
|
7545
|
+
vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
|
|
7546
|
+
keyword: this.getReaderFileFingerprint(path12.join(this.indexPath, "inverted-index.json")),
|
|
7547
|
+
database: this.getReaderFileFingerprint(path12.join(this.indexPath, "codebase.db")),
|
|
7548
|
+
databaseIdentity: this.getReaderFileFingerprint(path12.join(this.indexPath, "codebase.db"), true)
|
|
7549
|
+
};
|
|
7550
|
+
}
|
|
7551
|
+
refreshReaderArtifacts() {
|
|
7552
|
+
if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
|
|
7553
|
+
return;
|
|
7554
|
+
}
|
|
7555
|
+
const previousFingerprint = this.readerArtifactFingerprint;
|
|
7556
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7557
|
+
const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
|
|
7558
|
+
const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
|
|
7559
|
+
const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
|
|
7560
|
+
const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
|
|
7561
|
+
const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
|
|
7562
|
+
const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7563
|
+
if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
|
|
7564
|
+
return;
|
|
7565
|
+
}
|
|
7566
|
+
const setIssue = (component, message, error) => {
|
|
7567
|
+
if (!issues.has(component)) {
|
|
7568
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7569
|
+
}
|
|
7570
|
+
issues.set(component, this.createReadIssue(component, message));
|
|
7571
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7572
|
+
};
|
|
7573
|
+
const storePath = path12.join(this.indexPath, "vectors");
|
|
7574
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7575
|
+
const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
|
|
7576
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7577
|
+
if (vectorsChanged || retryDue("vectors")) {
|
|
7578
|
+
const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
|
|
7579
|
+
const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
|
|
7580
|
+
if (vectorStoreExists && vectorMetadataExists) {
|
|
7581
|
+
try {
|
|
7582
|
+
const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
|
|
7583
|
+
store.loadStrict();
|
|
7584
|
+
this.store = store;
|
|
7585
|
+
issues.delete("vectors");
|
|
7586
|
+
this.readerArtifactRetryAfter.delete("vectors");
|
|
7587
|
+
} catch (error) {
|
|
7588
|
+
setIssue("vectors", this.getVectorReadIssueMessage(), error);
|
|
7589
|
+
}
|
|
7590
|
+
} else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
|
|
7591
|
+
setIssue("vectors", this.getVectorReadIssueMessage());
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
7594
|
+
if (keywordChanged || retryDue("keyword") || !(0, import_fs8.existsSync)(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
|
|
7595
|
+
if ((0, import_fs8.existsSync)(invertedIndexPath)) {
|
|
7596
|
+
try {
|
|
7597
|
+
const invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7598
|
+
invertedIndex.load();
|
|
7599
|
+
this.invertedIndex = invertedIndex;
|
|
7600
|
+
issues.delete("keyword");
|
|
7601
|
+
this.readerArtifactRetryAfter.delete("keyword");
|
|
7602
|
+
} catch (error) {
|
|
7603
|
+
setIssue("keyword", this.getKeywordReadIssueMessage(), error);
|
|
7604
|
+
}
|
|
7605
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
|
|
7606
|
+
setIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7609
|
+
if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
|
|
7610
|
+
if ((0, import_fs8.existsSync)(dbPath)) {
|
|
7611
|
+
try {
|
|
7612
|
+
const database = Database.openReadOnly(dbPath);
|
|
7613
|
+
if (this.database) {
|
|
7614
|
+
this.retiredDatabases.push(this.database);
|
|
7615
|
+
}
|
|
7616
|
+
this.database = database;
|
|
7617
|
+
issues.delete("database");
|
|
7618
|
+
this.readerArtifactRetryAfter.delete("database");
|
|
7619
|
+
} catch (error) {
|
|
7620
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7621
|
+
}
|
|
7622
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
|
|
7623
|
+
setIssue("database", this.getDatabaseReadIssueMessage());
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
if (!issues.has("database")) {
|
|
7627
|
+
try {
|
|
7628
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
7629
|
+
} catch (error) {
|
|
7630
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
this.readIssues = Array.from(issues.values());
|
|
7634
|
+
this.readerArtifactFingerprint = currentFingerprint;
|
|
7635
|
+
}
|
|
7636
|
+
refreshInactiveWriterArtifacts() {
|
|
7637
|
+
if (this.initializationMode !== "writer" || this.activeIndexLease) {
|
|
7638
|
+
return true;
|
|
7639
|
+
}
|
|
7640
|
+
const previousFingerprint = this.writerArtifactFingerprint;
|
|
7641
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7642
|
+
const retryDue = this.readIssues.some(
|
|
7643
|
+
(issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
|
|
7644
|
+
);
|
|
7645
|
+
const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7646
|
+
if (!artifactsChanged && !retryDue) {
|
|
7647
|
+
return true;
|
|
7648
|
+
}
|
|
7649
|
+
if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
|
|
7650
|
+
return false;
|
|
7651
|
+
}
|
|
7652
|
+
this.initializationMode = "reader";
|
|
7653
|
+
this.readerArtifactFingerprint = previousFingerprint;
|
|
7654
|
+
try {
|
|
7655
|
+
this.refreshReaderArtifacts();
|
|
7656
|
+
this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
|
|
7657
|
+
} finally {
|
|
7658
|
+
this.readerArtifactFingerprint = null;
|
|
7659
|
+
this.initializationMode = "writer";
|
|
7660
|
+
}
|
|
7661
|
+
return true;
|
|
7662
|
+
}
|
|
7663
|
+
async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
|
|
7664
|
+
if (mode === "writer") {
|
|
7665
|
+
this.requireActiveLease();
|
|
7666
|
+
}
|
|
7667
|
+
this.readIssues = [];
|
|
7668
|
+
this.readerArtifactRetryAfter.clear();
|
|
6782
7669
|
if (this.config.embeddingProvider === "custom") {
|
|
6783
7670
|
if (!this.config.customProvider) {
|
|
6784
7671
|
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
@@ -6810,36 +7697,103 @@ var Indexer = class {
|
|
|
6810
7697
|
});
|
|
6811
7698
|
}
|
|
6812
7699
|
}
|
|
6813
|
-
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
6814
7700
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6815
|
-
const storePath =
|
|
6816
|
-
|
|
6817
|
-
const
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
7701
|
+
const storePath = path12.join(this.indexPath, "vectors");
|
|
7702
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7703
|
+
const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
|
|
7704
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7705
|
+
let dbIsNew = !(0, import_fs8.existsSync)(dbPath);
|
|
7706
|
+
const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
|
|
7707
|
+
if (mode === "writer") {
|
|
7708
|
+
await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
|
|
7709
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
|
|
7710
|
+
throw new Error(
|
|
7711
|
+
"Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
|
|
7712
|
+
);
|
|
7713
|
+
}
|
|
7714
|
+
for (const recoveredOwner of recoveredOwners) {
|
|
7715
|
+
recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
|
|
7716
|
+
storePath,
|
|
7717
|
+
`${storePath}.meta.json`
|
|
7718
|
+
]);
|
|
7719
|
+
}
|
|
7720
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project") {
|
|
7721
|
+
await this.resetLocalIndexArtifacts();
|
|
7722
|
+
}
|
|
7723
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7724
|
+
if ((0, import_fs8.existsSync)(storePath) || (0, import_fs8.existsSync)(vectorMetadataPath)) {
|
|
7725
|
+
this.store.load();
|
|
6828
7726
|
}
|
|
6829
7727
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
7728
|
+
try {
|
|
7729
|
+
this.invertedIndex.load();
|
|
7730
|
+
} catch {
|
|
7731
|
+
if ((0, import_fs8.existsSync)(invertedIndexPath)) {
|
|
7732
|
+
await import_fs8.promises.unlink(invertedIndexPath);
|
|
7733
|
+
}
|
|
7734
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7735
|
+
}
|
|
7736
|
+
try {
|
|
7737
|
+
this.database = new Database(dbPath);
|
|
7738
|
+
} catch (error) {
|
|
7739
|
+
if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
|
|
7740
|
+
throw error;
|
|
7741
|
+
}
|
|
7742
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7743
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7744
|
+
this.database = new Database(dbPath);
|
|
7745
|
+
dbIsNew = true;
|
|
6838
7746
|
}
|
|
7747
|
+
} else {
|
|
6839
7748
|
this.store = new VectorStore(storePath, dimensions);
|
|
7749
|
+
const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
|
|
7750
|
+
const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
|
|
7751
|
+
const vectorReadFailureMessage = this.getVectorReadIssueMessage();
|
|
7752
|
+
if (vectorStoreExists !== vectorMetadataExists) {
|
|
7753
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage);
|
|
7754
|
+
} else if (vectorStoreExists) {
|
|
7755
|
+
try {
|
|
7756
|
+
this.store.loadStrict();
|
|
7757
|
+
} catch (error) {
|
|
7758
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage, error);
|
|
7759
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7760
|
+
}
|
|
7761
|
+
}
|
|
6840
7762
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6841
|
-
|
|
6842
|
-
|
|
7763
|
+
if ((0, import_fs8.existsSync)(invertedIndexPath)) {
|
|
7764
|
+
try {
|
|
7765
|
+
this.invertedIndex.load();
|
|
7766
|
+
} catch (error) {
|
|
7767
|
+
this.recordReadIssue(
|
|
7768
|
+
"keyword",
|
|
7769
|
+
this.getKeywordReadIssueMessage(),
|
|
7770
|
+
error
|
|
7771
|
+
);
|
|
7772
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7773
|
+
}
|
|
7774
|
+
} else if (this.store.count() > 0) {
|
|
7775
|
+
this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7776
|
+
}
|
|
7777
|
+
if ((0, import_fs8.existsSync)(dbPath)) {
|
|
7778
|
+
try {
|
|
7779
|
+
this.database = Database.openReadOnly(dbPath);
|
|
7780
|
+
} catch (error) {
|
|
7781
|
+
this.recordReadIssue(
|
|
7782
|
+
"database",
|
|
7783
|
+
this.getDatabaseReadIssueMessage(),
|
|
7784
|
+
error
|
|
7785
|
+
);
|
|
7786
|
+
this.database = Database.createEmptyReadOnly();
|
|
7787
|
+
}
|
|
7788
|
+
} else {
|
|
7789
|
+
this.database = Database.createEmptyReadOnly();
|
|
7790
|
+
if (this.store.count() > 0) {
|
|
7791
|
+
this.recordReadIssue(
|
|
7792
|
+
"database",
|
|
7793
|
+
`Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
|
|
7794
|
+
);
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
6843
7797
|
}
|
|
6844
7798
|
if (isGitRepo(this.projectRoot)) {
|
|
6845
7799
|
this.currentBranch = getBranchOrDefault(this.projectRoot);
|
|
@@ -6853,10 +7807,10 @@ var Indexer = class {
|
|
|
6853
7807
|
this.baseBranch = "default";
|
|
6854
7808
|
this.logger.branch("debug", "Not a git repository, using default branch");
|
|
6855
7809
|
}
|
|
6856
|
-
if (
|
|
6857
|
-
await this.
|
|
7810
|
+
if (mode === "writer" && recoveredOwners.length > 0) {
|
|
7811
|
+
await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
|
|
6858
7812
|
}
|
|
6859
|
-
if (dbIsNew && this.store.count() > 0) {
|
|
7813
|
+
if (mode === "writer" && dbIsNew && this.store.count() > 0) {
|
|
6860
7814
|
this.migrateFromLegacyIndex();
|
|
6861
7815
|
}
|
|
6862
7816
|
this.loadFileHashCache();
|
|
@@ -6868,9 +7822,11 @@ var Indexer = class {
|
|
|
6868
7822
|
configuredProviderInfo: this.configuredProviderInfo
|
|
6869
7823
|
});
|
|
6870
7824
|
}
|
|
6871
|
-
if (this.config.indexing.autoGc) {
|
|
7825
|
+
if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
|
|
6872
7826
|
await this.maybeRunAutoGc();
|
|
6873
7827
|
}
|
|
7828
|
+
this.initializationMode = mode;
|
|
7829
|
+
this.readerArtifactFingerprint = readerArtifactFingerprint;
|
|
6874
7830
|
}
|
|
6875
7831
|
async maybeRunAutoGc() {
|
|
6876
7832
|
if (!this.database) return;
|
|
@@ -6887,7 +7843,7 @@ var Indexer = class {
|
|
|
6887
7843
|
}
|
|
6888
7844
|
}
|
|
6889
7845
|
if (shouldRunGc) {
|
|
6890
|
-
const result = await this.
|
|
7846
|
+
const result = await this.healthCheckUnlocked();
|
|
6891
7847
|
if (result.warning) {
|
|
6892
7848
|
this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
|
|
6893
7849
|
} else {
|
|
@@ -6909,7 +7865,7 @@ var Indexer = class {
|
|
|
6909
7865
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6910
7866
|
return {
|
|
6911
7867
|
resetCorruptedIndex: true,
|
|
6912
|
-
warning: this.getCorruptedIndexWarning(
|
|
7868
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
6913
7869
|
};
|
|
6914
7870
|
}
|
|
6915
7871
|
throw error;
|
|
@@ -6924,28 +7880,29 @@ var Indexer = class {
|
|
|
6924
7880
|
return;
|
|
6925
7881
|
}
|
|
6926
7882
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6927
|
-
const storeBasePath =
|
|
6928
|
-
const storeIndexPath =
|
|
7883
|
+
const storeBasePath = path12.join(this.indexPath, "vectors");
|
|
7884
|
+
const storeIndexPath = storeBasePath;
|
|
6929
7885
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6930
|
-
const
|
|
6931
|
-
const
|
|
7886
|
+
const lease = this.requireActiveLease();
|
|
7887
|
+
const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
|
|
7888
|
+
const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
|
|
6932
7889
|
let backedUpIndex = false;
|
|
6933
7890
|
let backedUpMetadata = false;
|
|
6934
7891
|
let rebuiltCount = 0;
|
|
6935
7892
|
let skippedCount = 0;
|
|
6936
|
-
if ((0,
|
|
6937
|
-
(0,
|
|
7893
|
+
if ((0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7894
|
+
(0, import_fs8.unlinkSync)(backupIndexPath);
|
|
6938
7895
|
}
|
|
6939
|
-
if ((0,
|
|
6940
|
-
(0,
|
|
7896
|
+
if ((0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7897
|
+
(0, import_fs8.unlinkSync)(backupMetadataPath);
|
|
6941
7898
|
}
|
|
6942
7899
|
try {
|
|
6943
|
-
if ((0,
|
|
6944
|
-
(0,
|
|
7900
|
+
if ((0, import_fs8.existsSync)(storeIndexPath)) {
|
|
7901
|
+
(0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
|
|
6945
7902
|
backedUpIndex = true;
|
|
6946
7903
|
}
|
|
6947
|
-
if ((0,
|
|
6948
|
-
(0,
|
|
7904
|
+
if ((0, import_fs8.existsSync)(storeMetadataPath)) {
|
|
7905
|
+
(0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
|
|
6949
7906
|
backedUpMetadata = true;
|
|
6950
7907
|
}
|
|
6951
7908
|
store.clear();
|
|
@@ -6965,11 +7922,11 @@ var Indexer = class {
|
|
|
6965
7922
|
rebuiltCount += 1;
|
|
6966
7923
|
}
|
|
6967
7924
|
store.save();
|
|
6968
|
-
if (backedUpIndex && (0,
|
|
6969
|
-
(0,
|
|
7925
|
+
if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7926
|
+
(0, import_fs8.unlinkSync)(backupIndexPath);
|
|
6970
7927
|
}
|
|
6971
|
-
if (backedUpMetadata && (0,
|
|
6972
|
-
(0,
|
|
7928
|
+
if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7929
|
+
(0, import_fs8.unlinkSync)(backupMetadataPath);
|
|
6973
7930
|
}
|
|
6974
7931
|
this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
|
|
6975
7932
|
excludedChunks: excludedSet.size,
|
|
@@ -6981,17 +7938,17 @@ var Indexer = class {
|
|
|
6981
7938
|
store.clear();
|
|
6982
7939
|
} catch {
|
|
6983
7940
|
}
|
|
6984
|
-
if ((0,
|
|
6985
|
-
(0,
|
|
7941
|
+
if ((0, import_fs8.existsSync)(storeIndexPath)) {
|
|
7942
|
+
(0, import_fs8.unlinkSync)(storeIndexPath);
|
|
6986
7943
|
}
|
|
6987
|
-
if ((0,
|
|
6988
|
-
(0,
|
|
7944
|
+
if ((0, import_fs8.existsSync)(storeMetadataPath)) {
|
|
7945
|
+
(0, import_fs8.unlinkSync)(storeMetadataPath);
|
|
6989
7946
|
}
|
|
6990
|
-
if (backedUpIndex && (0,
|
|
6991
|
-
(0,
|
|
7947
|
+
if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7948
|
+
(0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
|
|
6992
7949
|
}
|
|
6993
|
-
if (backedUpMetadata && (0,
|
|
6994
|
-
(0,
|
|
7950
|
+
if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7951
|
+
(0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
|
|
6995
7952
|
}
|
|
6996
7953
|
if (backedUpIndex || backedUpMetadata) {
|
|
6997
7954
|
store.load();
|
|
@@ -7005,11 +7962,37 @@ var Indexer = class {
|
|
|
7005
7962
|
}
|
|
7006
7963
|
return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
|
|
7007
7964
|
}
|
|
7965
|
+
async resetLocalIndexArtifacts() {
|
|
7966
|
+
this.store = null;
|
|
7967
|
+
this.invertedIndex = null;
|
|
7968
|
+
this.database?.close();
|
|
7969
|
+
this.database = null;
|
|
7970
|
+
this.indexCompatibility = null;
|
|
7971
|
+
this.initializationMode = "none";
|
|
7972
|
+
this.readIssues = [];
|
|
7973
|
+
this.readerArtifactFingerprint = null;
|
|
7974
|
+
this.writerArtifactFingerprint = null;
|
|
7975
|
+
this.readerArtifactRetryAfter.clear();
|
|
7976
|
+
this.fileHashCache.clear();
|
|
7977
|
+
const resetPaths = [
|
|
7978
|
+
path12.join(this.indexPath, "codebase.db"),
|
|
7979
|
+
path12.join(this.indexPath, "codebase.db-shm"),
|
|
7980
|
+
path12.join(this.indexPath, "codebase.db-wal"),
|
|
7981
|
+
path12.join(this.indexPath, "vectors"),
|
|
7982
|
+
path12.join(this.indexPath, "vectors.usearch"),
|
|
7983
|
+
path12.join(this.indexPath, "vectors.meta.json"),
|
|
7984
|
+
path12.join(this.indexPath, "inverted-index.json"),
|
|
7985
|
+
path12.join(this.indexPath, "file-hashes.json"),
|
|
7986
|
+
path12.join(this.indexPath, "failed-batches.json")
|
|
7987
|
+
];
|
|
7988
|
+
await Promise.all(resetPaths.map((targetPath) => import_fs8.promises.rm(targetPath, { recursive: true, force: true })));
|
|
7989
|
+
await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
|
|
7990
|
+
}
|
|
7008
7991
|
async tryResetCorruptedIndex(stage, error) {
|
|
7009
7992
|
if (!isSqliteCorruptionError(error)) {
|
|
7010
7993
|
return false;
|
|
7011
7994
|
}
|
|
7012
|
-
const dbPath =
|
|
7995
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7013
7996
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
7014
7997
|
const errorMessage = getErrorMessage2(error);
|
|
7015
7998
|
if (this.config.scope === "global") {
|
|
@@ -7025,30 +8008,7 @@ var Indexer = class {
|
|
|
7025
8008
|
dbPath,
|
|
7026
8009
|
error: errorMessage
|
|
7027
8010
|
});
|
|
7028
|
-
this.
|
|
7029
|
-
this.invertedIndex = null;
|
|
7030
|
-
this.database?.close();
|
|
7031
|
-
this.database = null;
|
|
7032
|
-
this.indexCompatibility = null;
|
|
7033
|
-
this.fileHashCache.clear();
|
|
7034
|
-
const resetPaths = [
|
|
7035
|
-
path10.join(this.indexPath, "codebase.db"),
|
|
7036
|
-
path10.join(this.indexPath, "codebase.db-shm"),
|
|
7037
|
-
path10.join(this.indexPath, "codebase.db-wal"),
|
|
7038
|
-
path10.join(this.indexPath, "vectors.usearch"),
|
|
7039
|
-
path10.join(this.indexPath, "inverted-index.json"),
|
|
7040
|
-
path10.join(this.indexPath, "file-hashes.json"),
|
|
7041
|
-
path10.join(this.indexPath, "failed-batches.json"),
|
|
7042
|
-
path10.join(this.indexPath, "indexing.lock"),
|
|
7043
|
-
path10.join(this.indexPath, "vectors")
|
|
7044
|
-
];
|
|
7045
|
-
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
7046
|
-
try {
|
|
7047
|
-
await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
|
|
7048
|
-
} catch {
|
|
7049
|
-
}
|
|
7050
|
-
}));
|
|
7051
|
-
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
8011
|
+
await this.resetLocalIndexArtifacts();
|
|
7052
8012
|
return true;
|
|
7053
8013
|
}
|
|
7054
8014
|
migrateFromLegacyIndex() {
|
|
@@ -7167,8 +8127,62 @@ var Indexer = class {
|
|
|
7167
8127
|
return this.indexCompatibility;
|
|
7168
8128
|
}
|
|
7169
8129
|
async ensureInitialized() {
|
|
8130
|
+
let initializedReader = false;
|
|
8131
|
+
while (true) {
|
|
8132
|
+
if (this.initializationPromise) {
|
|
8133
|
+
await this.initializationPromise;
|
|
8134
|
+
}
|
|
8135
|
+
if (!this.isInitializedFor("reader")) {
|
|
8136
|
+
await this.initialize();
|
|
8137
|
+
initializedReader = true;
|
|
8138
|
+
continue;
|
|
8139
|
+
}
|
|
8140
|
+
if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
|
|
8141
|
+
if (!this.refreshInactiveWriterArtifacts()) {
|
|
8142
|
+
this.resetLoadedIndexState(true);
|
|
8143
|
+
await this.initialize();
|
|
8144
|
+
initializedReader = true;
|
|
8145
|
+
continue;
|
|
8146
|
+
}
|
|
8147
|
+
}
|
|
8148
|
+
if (this.initializationMode === "reader" && !initializedReader) {
|
|
8149
|
+
this.refreshReaderArtifacts();
|
|
8150
|
+
}
|
|
8151
|
+
const state = this.requireLoadedIndexState();
|
|
8152
|
+
return {
|
|
8153
|
+
...state,
|
|
8154
|
+
readIssues: [...this.readIssues],
|
|
8155
|
+
compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
|
|
8156
|
+
};
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
async ensureInitializedUnlocked(recoveredOwners = []) {
|
|
8160
|
+
this.requireActiveLease();
|
|
8161
|
+
if (this.initializationPromise) {
|
|
8162
|
+
await this.initializationPromise;
|
|
8163
|
+
}
|
|
8164
|
+
if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
|
|
8165
|
+
const retireReaderDatabase = this.initializationMode === "reader";
|
|
8166
|
+
this.resetLoadedIndexState(retireReaderDatabase);
|
|
8167
|
+
await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
|
|
8168
|
+
} else {
|
|
8169
|
+
this.refreshLoadedIndexState();
|
|
8170
|
+
}
|
|
8171
|
+
if (this.config.indexing.autoGc) {
|
|
8172
|
+
await this.maybeRunAutoGc();
|
|
8173
|
+
}
|
|
8174
|
+
return this.requireLoadedIndexState();
|
|
8175
|
+
}
|
|
8176
|
+
requireReadableComponents(readIssues, ...components) {
|
|
8177
|
+
const componentSet = new Set(components);
|
|
8178
|
+
const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
|
|
8179
|
+
if (issues.length > 0) {
|
|
8180
|
+
throw new Error(issues.map((issue) => issue.message).join(" "));
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
requireLoadedIndexState() {
|
|
7170
8184
|
if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
|
|
7171
|
-
|
|
8185
|
+
throw new Error("Index state is not initialized");
|
|
7172
8186
|
}
|
|
7173
8187
|
return {
|
|
7174
8188
|
store: this.store,
|
|
@@ -7192,7 +8206,12 @@ var Indexer = class {
|
|
|
7192
8206
|
return createCostEstimate(files, configuredProviderInfo);
|
|
7193
8207
|
}
|
|
7194
8208
|
async index(onProgress) {
|
|
7195
|
-
|
|
8209
|
+
return this.withIndexMutationLease("index", async (recoveredOwners) => {
|
|
8210
|
+
return this.indexUnlocked(onProgress, recoveredOwners);
|
|
8211
|
+
});
|
|
8212
|
+
}
|
|
8213
|
+
async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
|
|
8214
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
|
|
7196
8215
|
const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
7197
8216
|
const branchCatalogKey = this.getBranchCatalogKey();
|
|
7198
8217
|
const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
|
|
@@ -7202,7 +8221,6 @@ var Indexer = class {
|
|
|
7202
8221
|
`${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
|
|
7203
8222
|
);
|
|
7204
8223
|
}
|
|
7205
|
-
this.acquireIndexingLock();
|
|
7206
8224
|
this.logger.recordIndexingStart();
|
|
7207
8225
|
this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
|
|
7208
8226
|
const startTime = Date.now();
|
|
@@ -7253,7 +8271,7 @@ var Indexer = class {
|
|
|
7253
8271
|
unchangedFilePaths.add(f.path);
|
|
7254
8272
|
this.logger.recordCacheHit();
|
|
7255
8273
|
} else {
|
|
7256
|
-
const content = await
|
|
8274
|
+
const content = await import_fs8.promises.readFile(f.path, "utf-8");
|
|
7257
8275
|
changedFiles.push({ path: f.path, content, hash: currentHash });
|
|
7258
8276
|
this.logger.recordCacheMiss();
|
|
7259
8277
|
}
|
|
@@ -7277,6 +8295,7 @@ var Indexer = class {
|
|
|
7277
8295
|
this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
|
|
7278
8296
|
const existingChunks = /* @__PURE__ */ new Map();
|
|
7279
8297
|
const existingChunksByFile = /* @__PURE__ */ new Map();
|
|
8298
|
+
const existingMetadataById = /* @__PURE__ */ new Map();
|
|
7280
8299
|
for (const { key, metadata } of store.getAllMetadata()) {
|
|
7281
8300
|
if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
|
|
7282
8301
|
continue;
|
|
@@ -7285,6 +8304,7 @@ var Indexer = class {
|
|
|
7285
8304
|
continue;
|
|
7286
8305
|
}
|
|
7287
8306
|
existingChunks.set(key, metadata.hash);
|
|
8307
|
+
existingMetadataById.set(key, metadata);
|
|
7288
8308
|
const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
|
|
7289
8309
|
fileChunks.add(key);
|
|
7290
8310
|
existingChunksByFile.set(metadata.filePath, fileChunks);
|
|
@@ -7292,6 +8312,8 @@ var Indexer = class {
|
|
|
7292
8312
|
const currentChunkIds = /* @__PURE__ */ new Set();
|
|
7293
8313
|
const currentFilePaths = /* @__PURE__ */ new Set();
|
|
7294
8314
|
const pendingChunks = [];
|
|
8315
|
+
const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
|
|
8316
|
+
let backfilledBlameMetadata = false;
|
|
7295
8317
|
for (const filePath of unchangedFilePaths) {
|
|
7296
8318
|
currentFilePaths.add(filePath);
|
|
7297
8319
|
const fileChunks = existingChunksByFile.get(filePath);
|
|
@@ -7302,10 +8324,52 @@ var Indexer = class {
|
|
|
7302
8324
|
}
|
|
7303
8325
|
}
|
|
7304
8326
|
const chunkDataBatch = [];
|
|
8327
|
+
if (gitBlameEnabled) {
|
|
8328
|
+
const backfillItems = [];
|
|
8329
|
+
for (const chunkId of currentChunkIds) {
|
|
8330
|
+
const metadata = existingMetadataById.get(chunkId);
|
|
8331
|
+
if (!metadata || hasBlameMetadata(metadata)) {
|
|
8332
|
+
continue;
|
|
8333
|
+
}
|
|
8334
|
+
const chunk = database.getChunk(chunkId);
|
|
8335
|
+
if (!chunk) {
|
|
8336
|
+
continue;
|
|
8337
|
+
}
|
|
8338
|
+
const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
|
|
8339
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
8340
|
+
if (!blameMetadata.blameSha) {
|
|
8341
|
+
continue;
|
|
8342
|
+
}
|
|
8343
|
+
chunkDataBatch.push({
|
|
8344
|
+
...chunk,
|
|
8345
|
+
blameSha: blameMetadata.blameSha,
|
|
8346
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8347
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8348
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8349
|
+
blameSummary: blameMetadata.blameSummary
|
|
8350
|
+
});
|
|
8351
|
+
const embeddingBuffer = database.getEmbedding(chunk.contentHash);
|
|
8352
|
+
if (!embeddingBuffer) {
|
|
8353
|
+
continue;
|
|
8354
|
+
}
|
|
8355
|
+
backfillItems.push({
|
|
8356
|
+
id: chunkId,
|
|
8357
|
+
vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
|
|
8358
|
+
metadata: {
|
|
8359
|
+
...metadata,
|
|
8360
|
+
...blameMetadata
|
|
8361
|
+
}
|
|
8362
|
+
});
|
|
8363
|
+
}
|
|
8364
|
+
if (backfillItems.length > 0) {
|
|
8365
|
+
store.addBatch(backfillItems);
|
|
8366
|
+
backfilledBlameMetadata = true;
|
|
8367
|
+
}
|
|
8368
|
+
}
|
|
7305
8369
|
for (const parsed of parsedFiles) {
|
|
7306
8370
|
currentFilePaths.add(parsed.path);
|
|
7307
8371
|
if (parsed.chunks.length === 0) {
|
|
7308
|
-
const relativePath =
|
|
8372
|
+
const relativePath = path12.relative(this.projectRoot, parsed.path);
|
|
7309
8373
|
stats.parseFailures.push(relativePath);
|
|
7310
8374
|
}
|
|
7311
8375
|
let fileChunkCount = 0;
|
|
@@ -7326,6 +8390,10 @@ var Indexer = class {
|
|
|
7326
8390
|
}
|
|
7327
8391
|
const id = generateChunkId(parsed.path, chunk);
|
|
7328
8392
|
const contentHash = generateChunkHash(chunk);
|
|
8393
|
+
const existingContentHash = existingChunks.get(id);
|
|
8394
|
+
const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
|
|
8395
|
+
const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
|
|
8396
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
7329
8397
|
currentChunkIds.add(id);
|
|
7330
8398
|
chunkDataBatch.push({
|
|
7331
8399
|
chunkId: id,
|
|
@@ -7335,9 +8403,14 @@ var Indexer = class {
|
|
|
7335
8403
|
endLine: chunk.endLine,
|
|
7336
8404
|
nodeType: chunk.chunkType,
|
|
7337
8405
|
name: chunk.name,
|
|
7338
|
-
language: chunk.language
|
|
8406
|
+
language: chunk.language,
|
|
8407
|
+
blameSha: blameMetadata.blameSha,
|
|
8408
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8409
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8410
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8411
|
+
blameSummary: blameMetadata.blameSummary
|
|
7339
8412
|
});
|
|
7340
|
-
if (
|
|
8413
|
+
if (existingContentHash === contentHash) {
|
|
7341
8414
|
fileChunkCount++;
|
|
7342
8415
|
continue;
|
|
7343
8416
|
}
|
|
@@ -7352,7 +8425,8 @@ var Indexer = class {
|
|
|
7352
8425
|
chunkType: chunk.chunkType,
|
|
7353
8426
|
name: chunk.name,
|
|
7354
8427
|
language: chunk.language,
|
|
7355
|
-
hash: contentHash
|
|
8428
|
+
hash: contentHash,
|
|
8429
|
+
...blameMetadata
|
|
7356
8430
|
};
|
|
7357
8431
|
pendingChunks.push({
|
|
7358
8432
|
id,
|
|
@@ -7495,6 +8569,11 @@ var Indexer = class {
|
|
|
7495
8569
|
database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
|
|
7496
8570
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7497
8571
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
8572
|
+
const vectorPath = path12.join(this.indexPath, "vectors");
|
|
8573
|
+
const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs8.existsSync)(vectorPath) && (0, import_fs8.existsSync)(`${vectorPath}.meta.json`);
|
|
8574
|
+
if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
|
|
8575
|
+
store.save();
|
|
8576
|
+
}
|
|
7498
8577
|
if (scopedRoots) {
|
|
7499
8578
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7500
8579
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7513,7 +8592,6 @@ var Indexer = class {
|
|
|
7513
8592
|
chunksProcessed: 0,
|
|
7514
8593
|
totalChunks: 0
|
|
7515
8594
|
});
|
|
7516
|
-
this.releaseIndexingLock();
|
|
7517
8595
|
return stats;
|
|
7518
8596
|
}
|
|
7519
8597
|
if (pendingChunks.length === 0) {
|
|
@@ -7522,7 +8600,7 @@ var Indexer = class {
|
|
|
7522
8600
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7523
8601
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7524
8602
|
store.save();
|
|
7525
|
-
|
|
8603
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7526
8604
|
if (scopedRoots) {
|
|
7527
8605
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7528
8606
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7541,7 +8619,6 @@ var Indexer = class {
|
|
|
7541
8619
|
chunksProcessed: 0,
|
|
7542
8620
|
totalChunks: 0
|
|
7543
8621
|
});
|
|
7544
|
-
this.releaseIndexingLock();
|
|
7545
8622
|
return stats;
|
|
7546
8623
|
}
|
|
7547
8624
|
onProgress?.({
|
|
@@ -7772,7 +8849,7 @@ var Indexer = class {
|
|
|
7772
8849
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7773
8850
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7774
8851
|
store.save();
|
|
7775
|
-
|
|
8852
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7776
8853
|
if (scopedRoots) {
|
|
7777
8854
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7778
8855
|
} else {
|
|
@@ -7824,7 +8901,6 @@ var Indexer = class {
|
|
|
7824
8901
|
chunksProcessed: stats.indexedChunks,
|
|
7825
8902
|
totalChunks: pendingChunks.length
|
|
7826
8903
|
});
|
|
7827
|
-
this.releaseIndexingLock();
|
|
7828
8904
|
return stats;
|
|
7829
8905
|
}
|
|
7830
8906
|
async getQueryEmbedding(query, provider) {
|
|
@@ -7890,8 +8966,8 @@ var Indexer = class {
|
|
|
7890
8966
|
return intersection / union;
|
|
7891
8967
|
}
|
|
7892
8968
|
async search(query, limit, options) {
|
|
7893
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
7894
|
-
|
|
8969
|
+
const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8970
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
7895
8971
|
if (!compatibility.compatible) {
|
|
7896
8972
|
throw new Error(
|
|
7897
8973
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
|
|
@@ -7905,6 +8981,7 @@ var Indexer = class {
|
|
|
7905
8981
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
7906
8982
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
7907
8983
|
const fusionStrategy = this.config.search.fusionStrategy;
|
|
8984
|
+
const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
|
|
7908
8985
|
const rrfK = this.config.search.rrfK;
|
|
7909
8986
|
const rerankTopN = this.config.search.rerankTopN;
|
|
7910
8987
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
@@ -7913,7 +8990,7 @@ var Indexer = class {
|
|
|
7913
8990
|
this.logger.search("debug", "Starting search", {
|
|
7914
8991
|
query,
|
|
7915
8992
|
maxResults,
|
|
7916
|
-
hybridWeight,
|
|
8993
|
+
hybridWeight: effectiveHybridWeight,
|
|
7917
8994
|
fusionStrategy,
|
|
7918
8995
|
rrfK,
|
|
7919
8996
|
rerankTopN,
|
|
@@ -7927,7 +9004,7 @@ var Indexer = class {
|
|
|
7927
9004
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
7928
9005
|
const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
|
|
7929
9006
|
const keywordStartTime = import_perf_hooks.performance.now();
|
|
7930
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
9007
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
7931
9008
|
const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
|
|
7932
9009
|
let branchChunkIds = null;
|
|
7933
9010
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -7964,7 +9041,7 @@ var Indexer = class {
|
|
|
7964
9041
|
rrfK,
|
|
7965
9042
|
rerankTopN,
|
|
7966
9043
|
limit: maxResults,
|
|
7967
|
-
hybridWeight,
|
|
9044
|
+
hybridWeight: effectiveHybridWeight,
|
|
7968
9045
|
prioritizeSourcePaths: sourceIntent
|
|
7969
9046
|
});
|
|
7970
9047
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -8038,7 +9115,7 @@ var Indexer = class {
|
|
|
8038
9115
|
let contextEndLine = r.metadata.endLine;
|
|
8039
9116
|
if (!metadataOnly && this.config.search.includeContext) {
|
|
8040
9117
|
try {
|
|
8041
|
-
const fileContent = await
|
|
9118
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8042
9119
|
r.metadata.filePath,
|
|
8043
9120
|
"utf-8"
|
|
8044
9121
|
);
|
|
@@ -8058,13 +9135,13 @@ var Indexer = class {
|
|
|
8058
9135
|
content,
|
|
8059
9136
|
score: r.score,
|
|
8060
9137
|
chunkType: r.metadata.chunkType,
|
|
8061
|
-
name: r.metadata.name
|
|
9138
|
+
name: r.metadata.name,
|
|
9139
|
+
blame: blameFromMetadata(r.metadata)
|
|
8062
9140
|
};
|
|
8063
9141
|
})
|
|
8064
9142
|
);
|
|
8065
9143
|
}
|
|
8066
|
-
async keywordSearch(query, limit) {
|
|
8067
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9144
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
8068
9145
|
const scores = invertedIndex.search(query);
|
|
8069
9146
|
if (scores.size === 0) {
|
|
8070
9147
|
return [];
|
|
@@ -8082,24 +9159,54 @@ var Indexer = class {
|
|
|
8082
9159
|
return results.slice(0, limit);
|
|
8083
9160
|
}
|
|
8084
9161
|
async getStatus() {
|
|
8085
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9162
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8086
9163
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9164
|
+
const vectorCount = store.count();
|
|
9165
|
+
const statusReadIssues = [...readIssues];
|
|
9166
|
+
let startupWarning = "";
|
|
9167
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9168
|
+
try {
|
|
9169
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9170
|
+
} catch (error) {
|
|
9171
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9172
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9173
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9174
|
+
this.recordReadIssue("database", message, error);
|
|
9175
|
+
}
|
|
9176
|
+
}
|
|
9177
|
+
}
|
|
9178
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9179
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9180
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
8087
9181
|
return {
|
|
8088
|
-
indexed:
|
|
8089
|
-
vectorCount
|
|
9182
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9183
|
+
vectorCount,
|
|
8090
9184
|
provider: configuredProviderInfo.provider,
|
|
8091
9185
|
model: configuredProviderInfo.modelInfo.model,
|
|
8092
9186
|
indexPath: this.indexPath,
|
|
8093
9187
|
currentBranch: this.currentBranch,
|
|
8094
9188
|
baseBranch: this.baseBranch,
|
|
8095
|
-
compatibility
|
|
9189
|
+
compatibility,
|
|
8096
9190
|
failedBatchesCount,
|
|
8097
9191
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
8098
|
-
warning:
|
|
9192
|
+
warning: warning || void 0
|
|
8099
9193
|
};
|
|
8100
9194
|
}
|
|
9195
|
+
async forceIndex(onProgress) {
|
|
9196
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9197
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9198
|
+
await this.clearIndexUnlocked();
|
|
9199
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9200
|
+
});
|
|
9201
|
+
}
|
|
8101
9202
|
async clearIndex() {
|
|
8102
|
-
|
|
9203
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9204
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9205
|
+
await this.clearIndexUnlocked();
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9208
|
+
async clearIndexUnlocked() {
|
|
9209
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8103
9210
|
if (this.config.scope === "global") {
|
|
8104
9211
|
store.load();
|
|
8105
9212
|
invertedIndex.load();
|
|
@@ -8126,7 +9233,7 @@ var Indexer = class {
|
|
|
8126
9233
|
store.clear();
|
|
8127
9234
|
store.save();
|
|
8128
9235
|
invertedIndex.clear();
|
|
8129
|
-
|
|
9236
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8130
9237
|
this.fileHashCache.clear();
|
|
8131
9238
|
this.saveFileHashCache();
|
|
8132
9239
|
database.clearAllIndexedData();
|
|
@@ -8150,14 +9257,7 @@ var Indexer = class {
|
|
|
8150
9257
|
this.indexCompatibility = compatibility;
|
|
8151
9258
|
return;
|
|
8152
9259
|
}
|
|
8153
|
-
|
|
8154
|
-
if (this.host !== "opencode") {
|
|
8155
|
-
localProjectIndexPaths.push(path10.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
8156
|
-
}
|
|
8157
|
-
const isLocalProjectIndex = localProjectIndexPaths.some(
|
|
8158
|
-
(localPath) => path10.resolve(this.indexPath) === path10.resolve(localPath)
|
|
8159
|
-
);
|
|
8160
|
-
if (!isLocalProjectIndex) {
|
|
9260
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8161
9261
|
throw new Error(
|
|
8162
9262
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8163
9263
|
);
|
|
@@ -8165,7 +9265,7 @@ var Indexer = class {
|
|
|
8165
9265
|
store.clear();
|
|
8166
9266
|
store.save();
|
|
8167
9267
|
invertedIndex.clear();
|
|
8168
|
-
|
|
9268
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8169
9269
|
this.fileHashCache.clear();
|
|
8170
9270
|
this.saveFileHashCache();
|
|
8171
9271
|
database.clearAllIndexedData();
|
|
@@ -8183,7 +9283,13 @@ var Indexer = class {
|
|
|
8183
9283
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8184
9284
|
}
|
|
8185
9285
|
async healthCheck() {
|
|
8186
|
-
|
|
9286
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9287
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9288
|
+
return this.healthCheckUnlocked();
|
|
9289
|
+
});
|
|
9290
|
+
}
|
|
9291
|
+
async healthCheckUnlocked() {
|
|
9292
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8187
9293
|
this.logger.gc("info", "Starting health check");
|
|
8188
9294
|
const allMetadata = store.getAllMetadata();
|
|
8189
9295
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8196,7 +9302,7 @@ var Indexer = class {
|
|
|
8196
9302
|
const removedChunkKeys = [];
|
|
8197
9303
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8198
9304
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8199
|
-
if (!(0,
|
|
9305
|
+
if (!(0, import_fs8.existsSync)(filePath)) {
|
|
8200
9306
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8201
9307
|
for (const key of chunkKeys) {
|
|
8202
9308
|
removedChunkKeys.push(key);
|
|
@@ -8221,7 +9327,7 @@ var Indexer = class {
|
|
|
8221
9327
|
const removedCount = removedChunkKeys.length;
|
|
8222
9328
|
if (removedCount > 0) {
|
|
8223
9329
|
store.save();
|
|
8224
|
-
|
|
9330
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8225
9331
|
}
|
|
8226
9332
|
let gcOrphanEmbeddings;
|
|
8227
9333
|
let gcOrphanChunks;
|
|
@@ -8236,7 +9342,7 @@ var Indexer = class {
|
|
|
8236
9342
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8237
9343
|
throw error;
|
|
8238
9344
|
}
|
|
8239
|
-
await this.
|
|
9345
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8240
9346
|
return {
|
|
8241
9347
|
removed: 0,
|
|
8242
9348
|
filePaths: [],
|
|
@@ -8245,7 +9351,7 @@ var Indexer = class {
|
|
|
8245
9351
|
gcOrphanSymbols: 0,
|
|
8246
9352
|
gcOrphanCallEdges: 0,
|
|
8247
9353
|
resetCorruptedIndex: true,
|
|
8248
|
-
warning: this.getCorruptedIndexWarning(
|
|
9354
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
8249
9355
|
};
|
|
8250
9356
|
}
|
|
8251
9357
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8258,7 +9364,13 @@ var Indexer = class {
|
|
|
8258
9364
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8259
9365
|
}
|
|
8260
9366
|
async retryFailedBatches() {
|
|
8261
|
-
|
|
9367
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9368
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9369
|
+
return this.retryFailedBatchesUnlocked();
|
|
9370
|
+
});
|
|
9371
|
+
}
|
|
9372
|
+
async retryFailedBatchesUnlocked() {
|
|
9373
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8262
9374
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8263
9375
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8264
9376
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8422,7 +9534,7 @@ var Indexer = class {
|
|
|
8422
9534
|
}
|
|
8423
9535
|
if (succeeded > 0) {
|
|
8424
9536
|
store.save();
|
|
8425
|
-
|
|
9537
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8426
9538
|
}
|
|
8427
9539
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8428
9540
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8450,15 +9562,16 @@ var Indexer = class {
|
|
|
8450
9562
|
}
|
|
8451
9563
|
}
|
|
8452
9564
|
async getDatabaseStats() {
|
|
8453
|
-
const { database } = await this.ensureInitialized();
|
|
9565
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9566
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8454
9567
|
return database.getStats();
|
|
8455
9568
|
}
|
|
8456
9569
|
getLogger() {
|
|
8457
9570
|
return this.logger;
|
|
8458
9571
|
}
|
|
8459
9572
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8460
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8461
|
-
|
|
9573
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9574
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8462
9575
|
if (!compatibility.compatible) {
|
|
8463
9576
|
throw new Error(
|
|
8464
9577
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8548,7 +9661,7 @@ var Indexer = class {
|
|
|
8548
9661
|
let content = "";
|
|
8549
9662
|
if (this.config.search.includeContext) {
|
|
8550
9663
|
try {
|
|
8551
|
-
const fileContent = await
|
|
9664
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8552
9665
|
r.metadata.filePath,
|
|
8553
9666
|
"utf-8"
|
|
8554
9667
|
);
|
|
@@ -8565,13 +9678,15 @@ var Indexer = class {
|
|
|
8565
9678
|
content,
|
|
8566
9679
|
score: r.score,
|
|
8567
9680
|
chunkType: r.metadata.chunkType,
|
|
8568
|
-
name: r.metadata.name
|
|
9681
|
+
name: r.metadata.name,
|
|
9682
|
+
blame: blameFromMetadata(r.metadata)
|
|
8569
9683
|
};
|
|
8570
9684
|
})
|
|
8571
9685
|
);
|
|
8572
9686
|
}
|
|
8573
9687
|
async getCallers(targetName, callTypeFilter) {
|
|
8574
|
-
const { database } = await this.ensureInitialized();
|
|
9688
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9689
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8575
9690
|
const seen = /* @__PURE__ */ new Set();
|
|
8576
9691
|
const results = [];
|
|
8577
9692
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8585,7 +9700,8 @@ var Indexer = class {
|
|
|
8585
9700
|
return results;
|
|
8586
9701
|
}
|
|
8587
9702
|
async getCallees(symbolId, callTypeFilter) {
|
|
8588
|
-
const { database } = await this.ensureInitialized();
|
|
9703
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9704
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8589
9705
|
const seen = /* @__PURE__ */ new Set();
|
|
8590
9706
|
const results = [];
|
|
8591
9707
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8599,44 +9715,51 @@ var Indexer = class {
|
|
|
8599
9715
|
return results;
|
|
8600
9716
|
}
|
|
8601
9717
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8602
|
-
const { database } = await this.ensureInitialized();
|
|
9718
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9719
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8603
9720
|
let shortest = [];
|
|
8604
9721
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8605
|
-
const
|
|
8606
|
-
if (
|
|
8607
|
-
shortest =
|
|
9722
|
+
const path27 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9723
|
+
if (path27.length > 0 && (shortest.length === 0 || path27.length < shortest.length)) {
|
|
9724
|
+
shortest = path27;
|
|
8608
9725
|
}
|
|
8609
9726
|
}
|
|
8610
9727
|
return shortest;
|
|
8611
9728
|
}
|
|
8612
9729
|
async getSymbolsForBranch(branch) {
|
|
8613
|
-
const { database } = await this.ensureInitialized();
|
|
9730
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9731
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8614
9732
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8615
9733
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8616
9734
|
}
|
|
8617
9735
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8618
|
-
const { database } = await this.ensureInitialized();
|
|
9736
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9737
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8619
9738
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8620
9739
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8621
9740
|
}
|
|
8622
9741
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8623
|
-
const { database } = await this.ensureInitialized();
|
|
9742
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9743
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8624
9744
|
const branch = this.getBranchCatalogKey();
|
|
8625
9745
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8626
9746
|
}
|
|
8627
9747
|
async detectCommunities(branch, symbolIds) {
|
|
8628
|
-
const { database } = await this.ensureInitialized();
|
|
9748
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9749
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8629
9750
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8630
9751
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8631
9752
|
}
|
|
8632
9753
|
async computeCentrality(branch) {
|
|
8633
|
-
const { database } = await this.ensureInitialized();
|
|
9754
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9755
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8634
9756
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8635
9757
|
return database.computeCentrality(resolvedBranch);
|
|
8636
9758
|
}
|
|
8637
9759
|
async getPrImpact(opts) {
|
|
8638
|
-
const { database } = await this.ensureInitialized();
|
|
8639
|
-
|
|
9760
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9761
|
+
this.requireReadableComponents(readIssues, "database");
|
|
9762
|
+
const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
|
|
8640
9763
|
const changedFilesResult = await getChangedFiles({
|
|
8641
9764
|
pr: opts.pr,
|
|
8642
9765
|
branch: opts.branch,
|
|
@@ -8658,7 +9781,7 @@ var Indexer = class {
|
|
|
8658
9781
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8659
9782
|
);
|
|
8660
9783
|
}
|
|
8661
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9784
|
+
const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
|
|
8662
9785
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8663
9786
|
const directIds = directSymbols.map((s) => s.id);
|
|
8664
9787
|
const direction = opts.direction ?? "both";
|
|
@@ -8720,7 +9843,7 @@ var Indexer = class {
|
|
|
8720
9843
|
if (opts.checkConflicts) {
|
|
8721
9844
|
conflictingPRs = [];
|
|
8722
9845
|
try {
|
|
8723
|
-
const { stdout } = await
|
|
9846
|
+
const { stdout } = await execFileAsync3(
|
|
8724
9847
|
"gh",
|
|
8725
9848
|
["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
|
|
8726
9849
|
{ cwd: this.projectRoot, timeout: 3e4 }
|
|
@@ -8741,7 +9864,7 @@ var Indexer = class {
|
|
|
8741
9864
|
projectRoot: this.projectRoot,
|
|
8742
9865
|
baseBranch: this.baseBranch
|
|
8743
9866
|
});
|
|
8744
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9867
|
+
const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
|
|
8745
9868
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8746
9869
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8747
9870
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8791,7 +9914,8 @@ var Indexer = class {
|
|
|
8791
9914
|
};
|
|
8792
9915
|
}
|
|
8793
9916
|
async getVisualizationData(options) {
|
|
8794
|
-
const { database, store } = await this.ensureInitialized();
|
|
9917
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9918
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8795
9919
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8796
9920
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8797
9921
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8804,12 +9928,12 @@ var Indexer = class {
|
|
|
8804
9928
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8805
9929
|
}
|
|
8806
9930
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8807
|
-
const absoluteDirectoryFilter = directory ?
|
|
9931
|
+
const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
|
|
8808
9932
|
for (const filePath of filePaths) {
|
|
8809
9933
|
if (directory) {
|
|
8810
|
-
const absoluteFilePath =
|
|
9934
|
+
const absoluteFilePath = path12.resolve(filePath);
|
|
8811
9935
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8812
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9936
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
|
|
8813
9937
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8814
9938
|
continue;
|
|
8815
9939
|
}
|
|
@@ -8831,12 +9955,23 @@ var Indexer = class {
|
|
|
8831
9955
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
8832
9956
|
}
|
|
8833
9957
|
async close() {
|
|
8834
|
-
|
|
9958
|
+
this.database?.close();
|
|
9959
|
+
for (const database of this.retiredDatabases) {
|
|
9960
|
+
database.close();
|
|
9961
|
+
}
|
|
9962
|
+
this.retiredDatabases = [];
|
|
8835
9963
|
this.database = null;
|
|
8836
9964
|
this.store = null;
|
|
8837
9965
|
this.invertedIndex = null;
|
|
8838
9966
|
this.provider = null;
|
|
8839
9967
|
this.reranker = null;
|
|
9968
|
+
this.configuredProviderInfo = null;
|
|
9969
|
+
this.indexCompatibility = null;
|
|
9970
|
+
this.initializationMode = "none";
|
|
9971
|
+
this.readIssues = [];
|
|
9972
|
+
this.readerArtifactFingerprint = null;
|
|
9973
|
+
this.writerArtifactFingerprint = null;
|
|
9974
|
+
this.readerArtifactRetryAfter.clear();
|
|
8840
9975
|
}
|
|
8841
9976
|
};
|
|
8842
9977
|
|
|
@@ -9087,15 +10222,15 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
9087
10222
|
}
|
|
9088
10223
|
|
|
9089
10224
|
// src/eval/runner-config.ts
|
|
9090
|
-
var
|
|
9091
|
-
var
|
|
9092
|
-
var
|
|
10225
|
+
var import_fs9 = require("fs");
|
|
10226
|
+
var os5 = __toESM(require("os"), 1);
|
|
10227
|
+
var path14 = __toESM(require("path"), 1);
|
|
9093
10228
|
|
|
9094
10229
|
// src/config/rebase.ts
|
|
9095
|
-
var
|
|
10230
|
+
var path13 = __toESM(require("path"), 1);
|
|
9096
10231
|
function isWithinRoot(rootDir, targetPath) {
|
|
9097
|
-
const relativePath =
|
|
9098
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
10232
|
+
const relativePath = path13.relative(rootDir, targetPath);
|
|
10233
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
|
|
9099
10234
|
}
|
|
9100
10235
|
function rebasePathEntries(values, fromDir, toDir) {
|
|
9101
10236
|
if (!Array.isArray(values)) {
|
|
@@ -9103,10 +10238,10 @@ function rebasePathEntries(values, fromDir, toDir) {
|
|
|
9103
10238
|
}
|
|
9104
10239
|
return values.filter((value) => typeof value === "string").map((value) => {
|
|
9105
10240
|
const trimmed = value.trim();
|
|
9106
|
-
if (!trimmed ||
|
|
10241
|
+
if (!trimmed || path13.isAbsolute(trimmed)) {
|
|
9107
10242
|
return trimmed;
|
|
9108
10243
|
}
|
|
9109
|
-
return normalizePathSeparators(
|
|
10244
|
+
return normalizePathSeparators(path13.normalize(path13.relative(toDir, path13.resolve(fromDir, trimmed))));
|
|
9110
10245
|
}).filter(Boolean);
|
|
9111
10246
|
}
|
|
9112
10247
|
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
@@ -9118,17 +10253,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
|
9118
10253
|
if (!trimmed) {
|
|
9119
10254
|
return trimmed;
|
|
9120
10255
|
}
|
|
9121
|
-
if (
|
|
10256
|
+
if (path13.isAbsolute(trimmed)) {
|
|
9122
10257
|
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
9123
|
-
return normalizePathSeparators(
|
|
10258
|
+
return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
|
|
9124
10259
|
}
|
|
9125
|
-
return
|
|
10260
|
+
return path13.normalize(trimmed);
|
|
9126
10261
|
}
|
|
9127
|
-
const resolvedFromSource =
|
|
10262
|
+
const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
|
|
9128
10263
|
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
9129
|
-
return normalizePathSeparators(
|
|
10264
|
+
return normalizePathSeparators(path13.normalize(trimmed));
|
|
9130
10265
|
}
|
|
9131
|
-
return normalizePathSeparators(
|
|
10266
|
+
return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
|
|
9132
10267
|
}).filter(Boolean);
|
|
9133
10268
|
}
|
|
9134
10269
|
|
|
@@ -9166,7 +10301,7 @@ function validateEvalConfigShape(rawConfig, filePath) {
|
|
|
9166
10301
|
}
|
|
9167
10302
|
function parseJsonConfigFile(filePath) {
|
|
9168
10303
|
try {
|
|
9169
|
-
return validateEvalConfigShape(JSON.parse((0,
|
|
10304
|
+
return validateEvalConfigShape(JSON.parse((0, import_fs9.readFileSync)(filePath, "utf-8")), filePath);
|
|
9170
10305
|
} catch (error) {
|
|
9171
10306
|
if (error instanceof Error && error.message.startsWith("Eval config at ")) {
|
|
9172
10307
|
throw error;
|
|
@@ -9176,20 +10311,20 @@ function parseJsonConfigFile(filePath) {
|
|
|
9176
10311
|
}
|
|
9177
10312
|
}
|
|
9178
10313
|
function toAbsolute(projectRoot, maybeRelative) {
|
|
9179
|
-
return
|
|
10314
|
+
return path14.isAbsolute(maybeRelative) ? maybeRelative : path14.join(projectRoot, maybeRelative);
|
|
9180
10315
|
}
|
|
9181
10316
|
function isProjectScopedConfigPath(configPath) {
|
|
9182
|
-
return
|
|
10317
|
+
return path14.basename(configPath) === "codebase-index.json" && path14.basename(path14.dirname(configPath)) === ".opencode";
|
|
9183
10318
|
}
|
|
9184
10319
|
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
9185
10320
|
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
9186
10321
|
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
9187
10322
|
values,
|
|
9188
|
-
|
|
10323
|
+
path14.dirname(path14.dirname(resolvedConfigPath)),
|
|
9189
10324
|
projectRoot
|
|
9190
10325
|
) : rebasePathEntries(
|
|
9191
10326
|
values,
|
|
9192
|
-
|
|
10327
|
+
path14.dirname(resolvedConfigPath),
|
|
9193
10328
|
projectRoot
|
|
9194
10329
|
);
|
|
9195
10330
|
if (Array.isArray(config.knowledgeBases)) {
|
|
@@ -9202,7 +10337,7 @@ function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfi
|
|
|
9202
10337
|
}
|
|
9203
10338
|
function loadRawConfig(projectRoot, configPath) {
|
|
9204
10339
|
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
9205
|
-
if (fromPath && (0,
|
|
10340
|
+
if (fromPath && (0, import_fs9.existsSync)(fromPath)) {
|
|
9206
10341
|
return normalizeEvalConfigKnowledgeBases(
|
|
9207
10342
|
parseJsonConfigFile(fromPath),
|
|
9208
10343
|
projectRoot,
|
|
@@ -9210,15 +10345,15 @@ function loadRawConfig(projectRoot, configPath) {
|
|
|
9210
10345
|
);
|
|
9211
10346
|
}
|
|
9212
10347
|
const projectConfig = resolveProjectConfigPath(projectRoot);
|
|
9213
|
-
if ((0,
|
|
10348
|
+
if ((0, import_fs9.existsSync)(projectConfig)) {
|
|
9214
10349
|
return normalizeEvalConfigKnowledgeBases(
|
|
9215
10350
|
parseJsonConfigFile(projectConfig),
|
|
9216
10351
|
projectRoot,
|
|
9217
10352
|
projectConfig
|
|
9218
10353
|
);
|
|
9219
10354
|
}
|
|
9220
|
-
const globalConfig =
|
|
9221
|
-
if ((0,
|
|
10355
|
+
const globalConfig = path14.join(os5.homedir(), ".config", "opencode", "codebase-index.json");
|
|
10356
|
+
if ((0, import_fs9.existsSync)(globalConfig)) {
|
|
9222
10357
|
return parseJsonConfigFile(globalConfig);
|
|
9223
10358
|
}
|
|
9224
10359
|
return {};
|
|
@@ -9227,24 +10362,24 @@ function getIndexRootPath(projectRoot, scope) {
|
|
|
9227
10362
|
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
9228
10363
|
}
|
|
9229
10364
|
function getLocalProjectIndexRoot(projectRoot) {
|
|
9230
|
-
return
|
|
10365
|
+
return path14.join(projectRoot, ".opencode", "index");
|
|
9231
10366
|
}
|
|
9232
10367
|
function getLocalProjectConfigPath(projectRoot) {
|
|
9233
|
-
return
|
|
10368
|
+
return path14.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9234
10369
|
}
|
|
9235
10370
|
function clearIndexRoot(projectRoot, scope) {
|
|
9236
10371
|
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
9237
|
-
if ((0,
|
|
9238
|
-
(0,
|
|
10372
|
+
if ((0, import_fs9.existsSync)(indexRoot)) {
|
|
10373
|
+
(0, import_fs9.rmSync)(indexRoot, { recursive: true, force: true });
|
|
9239
10374
|
}
|
|
9240
10375
|
}
|
|
9241
10376
|
function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
9242
10377
|
const localConfigPath = getLocalProjectConfigPath(projectRoot);
|
|
9243
10378
|
const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
|
|
9244
|
-
if (!configPath && (0,
|
|
10379
|
+
if (!configPath && (0, import_fs9.existsSync)(localConfigPath)) {
|
|
9245
10380
|
return localConfigPath;
|
|
9246
10381
|
}
|
|
9247
|
-
if (!(0,
|
|
10382
|
+
if (!(0, import_fs9.existsSync)(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
|
|
9248
10383
|
return resolvedConfigPath;
|
|
9249
10384
|
}
|
|
9250
10385
|
const sourceConfig = normalizeEvalConfigKnowledgeBases(
|
|
@@ -9252,8 +10387,8 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
|
9252
10387
|
projectRoot,
|
|
9253
10388
|
resolvedConfigPath
|
|
9254
10389
|
);
|
|
9255
|
-
(0,
|
|
9256
|
-
(0,
|
|
10390
|
+
(0, import_fs9.mkdirSync)(path14.dirname(localConfigPath), { recursive: true });
|
|
10391
|
+
(0, import_fs9.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
9257
10392
|
return localConfigPath;
|
|
9258
10393
|
}
|
|
9259
10394
|
function loadParsedConfig(projectRoot, configPath) {
|
|
@@ -9286,9 +10421,9 @@ function getEmbeddingCostPer1MTokens(embeddingProvider) {
|
|
|
9286
10421
|
}
|
|
9287
10422
|
|
|
9288
10423
|
// src/eval/schema.ts
|
|
9289
|
-
var
|
|
10424
|
+
var import_fs10 = require("fs");
|
|
9290
10425
|
function parseJsonFile(filePath) {
|
|
9291
|
-
const content = (0,
|
|
10426
|
+
const content = (0, import_fs10.readFileSync)(filePath, "utf-8");
|
|
9292
10427
|
try {
|
|
9293
10428
|
return JSON.parse(content);
|
|
9294
10429
|
} catch (error) {
|
|
@@ -9302,23 +10437,23 @@ function isRecord2(value) {
|
|
|
9302
10437
|
function isStringArray3(value) {
|
|
9303
10438
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
9304
10439
|
}
|
|
9305
|
-
function asPositiveNumber(value,
|
|
10440
|
+
function asPositiveNumber(value, path27) {
|
|
9306
10441
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
9307
|
-
throw new Error(`${
|
|
10442
|
+
throw new Error(`${path27} must be a non-negative number`);
|
|
9308
10443
|
}
|
|
9309
10444
|
return value;
|
|
9310
10445
|
}
|
|
9311
|
-
function parseQueryType(value,
|
|
10446
|
+
function parseQueryType(value, path27) {
|
|
9312
10447
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
9313
10448
|
return value;
|
|
9314
10449
|
}
|
|
9315
10450
|
throw new Error(
|
|
9316
|
-
`${
|
|
10451
|
+
`${path27} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
9317
10452
|
);
|
|
9318
10453
|
}
|
|
9319
|
-
function parseExpected(input,
|
|
10454
|
+
function parseExpected(input, path27) {
|
|
9320
10455
|
if (!isRecord2(input)) {
|
|
9321
|
-
throw new Error(`${
|
|
10456
|
+
throw new Error(`${path27} must be an object`);
|
|
9322
10457
|
}
|
|
9323
10458
|
const filePathRaw = input.filePath;
|
|
9324
10459
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -9327,16 +10462,16 @@ function parseExpected(input, path25) {
|
|
|
9327
10462
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
9328
10463
|
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
9329
10464
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
9330
|
-
throw new Error(`${
|
|
10465
|
+
throw new Error(`${path27} must include either expected.filePath or expected.acceptableFiles`);
|
|
9331
10466
|
}
|
|
9332
10467
|
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
9333
|
-
throw new Error(`${
|
|
10468
|
+
throw new Error(`${path27}.acceptableFiles must be an array of strings`);
|
|
9334
10469
|
}
|
|
9335
10470
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
9336
|
-
throw new Error(`${
|
|
10471
|
+
throw new Error(`${path27}.symbol must be a string when provided`);
|
|
9337
10472
|
}
|
|
9338
10473
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
9339
|
-
throw new Error(`${
|
|
10474
|
+
throw new Error(`${path27}.branch must be a string when provided`);
|
|
9340
10475
|
}
|
|
9341
10476
|
return {
|
|
9342
10477
|
filePath,
|
|
@@ -9346,25 +10481,25 @@ function parseExpected(input, path25) {
|
|
|
9346
10481
|
};
|
|
9347
10482
|
}
|
|
9348
10483
|
function parseQuery(input, index) {
|
|
9349
|
-
const
|
|
10484
|
+
const path27 = `queries[${index}]`;
|
|
9350
10485
|
if (!isRecord2(input)) {
|
|
9351
|
-
throw new Error(`${
|
|
10486
|
+
throw new Error(`${path27} must be an object`);
|
|
9352
10487
|
}
|
|
9353
10488
|
const id = input.id;
|
|
9354
10489
|
const query = input.query;
|
|
9355
10490
|
const queryType = input.queryType;
|
|
9356
10491
|
const expected = input.expected;
|
|
9357
10492
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
9358
|
-
throw new Error(`${
|
|
10493
|
+
throw new Error(`${path27}.id must be a non-empty string`);
|
|
9359
10494
|
}
|
|
9360
10495
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
9361
|
-
throw new Error(`${
|
|
10496
|
+
throw new Error(`${path27}.query must be a non-empty string`);
|
|
9362
10497
|
}
|
|
9363
10498
|
return {
|
|
9364
10499
|
id,
|
|
9365
10500
|
query,
|
|
9366
|
-
queryType: parseQueryType(queryType, `${
|
|
9367
|
-
expected: parseExpected(expected, `${
|
|
10501
|
+
queryType: parseQueryType(queryType, `${path27}.queryType`),
|
|
10502
|
+
expected: parseExpected(expected, `${path27}.expected`)
|
|
9368
10503
|
};
|
|
9369
10504
|
}
|
|
9370
10505
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -9547,13 +10682,13 @@ async function runEvaluation(options) {
|
|
|
9547
10682
|
};
|
|
9548
10683
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
9549
10684
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
9550
|
-
writeJson(
|
|
9551
|
-
writeJson(
|
|
10685
|
+
writeJson(path15.join(outputDir, "summary.json"), summary);
|
|
10686
|
+
writeJson(path15.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
9552
10687
|
let comparison;
|
|
9553
10688
|
if (againstPath) {
|
|
9554
10689
|
const baseline = loadSummary(againstPath);
|
|
9555
10690
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
9556
|
-
writeJson(
|
|
10691
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9557
10692
|
}
|
|
9558
10693
|
let gate;
|
|
9559
10694
|
if (options.ciMode) {
|
|
@@ -9563,10 +10698,10 @@ async function runEvaluation(options) {
|
|
|
9563
10698
|
const budget = loadBudget(budgetPath);
|
|
9564
10699
|
if (!comparison && budget.baselinePath) {
|
|
9565
10700
|
const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
|
|
9566
|
-
if ((0,
|
|
10701
|
+
if ((0, import_fs11.existsSync)(resolvedBaseline)) {
|
|
9567
10702
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
9568
10703
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
9569
|
-
writeJson(
|
|
10704
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9570
10705
|
} else if (budget.failOnMissingBaseline) {
|
|
9571
10706
|
throw new Error(
|
|
9572
10707
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -9576,7 +10711,7 @@ async function runEvaluation(options) {
|
|
|
9576
10711
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
9577
10712
|
}
|
|
9578
10713
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
9579
|
-
writeText(
|
|
10714
|
+
writeText(path15.join(outputDir, "summary.md"), markdown);
|
|
9580
10715
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
9581
10716
|
} finally {
|
|
9582
10717
|
await indexer.close();
|
|
@@ -9634,23 +10769,23 @@ async function runSweep(options, sweep) {
|
|
|
9634
10769
|
bestByMrrAt10,
|
|
9635
10770
|
bestByP95Latency
|
|
9636
10771
|
};
|
|
9637
|
-
writeJson(
|
|
10772
|
+
writeJson(path15.join(outputDir, "compare.json"), aggregate);
|
|
9638
10773
|
const md = createSummaryMarkdown(
|
|
9639
10774
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
9640
10775
|
bestByHitAt5?.comparison,
|
|
9641
10776
|
void 0,
|
|
9642
10777
|
aggregate
|
|
9643
10778
|
);
|
|
9644
|
-
writeText(
|
|
9645
|
-
writeJson(
|
|
10779
|
+
writeText(path15.join(outputDir, "summary.md"), md);
|
|
10780
|
+
writeJson(path15.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
9646
10781
|
return { outputDir, aggregate };
|
|
9647
10782
|
}
|
|
9648
10783
|
|
|
9649
10784
|
// src/eval/cli.ts
|
|
9650
|
-
var
|
|
10785
|
+
var path17 = __toESM(require("path"), 1);
|
|
9651
10786
|
|
|
9652
10787
|
// src/eval/cli-parser.ts
|
|
9653
|
-
var
|
|
10788
|
+
var path16 = __toESM(require("path"), 1);
|
|
9654
10789
|
function printUsage() {
|
|
9655
10790
|
console.log(`
|
|
9656
10791
|
Usage:
|
|
@@ -9722,12 +10857,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9722
10857
|
const arg = argv[i];
|
|
9723
10858
|
const next = argv[i + 1];
|
|
9724
10859
|
if (arg === "--project" && next) {
|
|
9725
|
-
parsed.projectRoot =
|
|
10860
|
+
parsed.projectRoot = path16.resolve(cwd, next);
|
|
9726
10861
|
i += 1;
|
|
9727
10862
|
continue;
|
|
9728
10863
|
}
|
|
9729
10864
|
if (arg === "--config" && next) {
|
|
9730
|
-
parsed.configPath =
|
|
10865
|
+
parsed.configPath = path16.resolve(cwd, next);
|
|
9731
10866
|
i += 1;
|
|
9732
10867
|
continue;
|
|
9733
10868
|
}
|
|
@@ -9923,22 +11058,22 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9923
11058
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
9924
11059
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
9925
11060
|
}
|
|
9926
|
-
const currentSummary = loadSummary(
|
|
11061
|
+
const currentSummary = loadSummary(path17.resolve(parsed.projectRoot, currentPath), {
|
|
9927
11062
|
allowLegacyDiversityMetrics: true
|
|
9928
11063
|
});
|
|
9929
|
-
const baselineSummary = loadSummary(
|
|
11064
|
+
const baselineSummary = loadSummary(path17.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
9930
11065
|
allowLegacyDiversityMetrics: true
|
|
9931
11066
|
});
|
|
9932
11067
|
const comparison = compareSummaries(
|
|
9933
11068
|
currentSummary,
|
|
9934
11069
|
baselineSummary,
|
|
9935
|
-
|
|
11070
|
+
path17.resolve(parsed.projectRoot, parsed.againstPath)
|
|
9936
11071
|
);
|
|
9937
|
-
const outputDir = createRunDirectory(
|
|
11072
|
+
const outputDir = createRunDirectory(path17.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
9938
11073
|
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
9939
|
-
writeJson(
|
|
9940
|
-
writeText(
|
|
9941
|
-
writeJson(
|
|
11074
|
+
writeJson(path17.join(outputDir, "compare.json"), comparison);
|
|
11075
|
+
writeText(path17.join(outputDir, "summary.md"), summaryMd);
|
|
11076
|
+
writeJson(path17.join(outputDir, "summary.json"), currentSummary);
|
|
9942
11077
|
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
9943
11078
|
return 0;
|
|
9944
11079
|
}
|
|
@@ -9947,7 +11082,7 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9947
11082
|
|
|
9948
11083
|
// src/mcp-server.ts
|
|
9949
11084
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9950
|
-
var
|
|
11085
|
+
var import_fs15 = require("fs");
|
|
9951
11086
|
|
|
9952
11087
|
// src/mcp-server/register-prompts.ts
|
|
9953
11088
|
var import_zod = require("zod");
|
|
@@ -10138,6 +11273,10 @@ function formatStatus(status) {
|
|
|
10138
11273
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
10139
11274
|
}
|
|
10140
11275
|
}
|
|
11276
|
+
if (status.warning) {
|
|
11277
|
+
lines.push("");
|
|
11278
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
11279
|
+
}
|
|
10141
11280
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
10142
11281
|
lines.push("");
|
|
10143
11282
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -10183,6 +11322,17 @@ function calculatePercentage(progress) {
|
|
|
10183
11322
|
if (progress.phase === "storing") return 95;
|
|
10184
11323
|
return 0;
|
|
10185
11324
|
}
|
|
11325
|
+
function formatCodebasePeek(results) {
|
|
11326
|
+
if (results.length === 0) {
|
|
11327
|
+
return "No matching code found. Try a different query or run index_codebase first.";
|
|
11328
|
+
}
|
|
11329
|
+
const formatted = results.map((r, idx) => {
|
|
11330
|
+
const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
11331
|
+
const name = r.name ? `"${r.name}"` : "(anonymous)";
|
|
11332
|
+
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})${formatBlame(r)}`;
|
|
11333
|
+
});
|
|
11334
|
+
return formatted.join("\n");
|
|
11335
|
+
}
|
|
10186
11336
|
function formatHealthCheck(result) {
|
|
10187
11337
|
if (result.resetCorruptedIndex) {
|
|
10188
11338
|
return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
|
|
@@ -10211,16 +11361,68 @@ function formatHealthCheck(result) {
|
|
|
10211
11361
|
}
|
|
10212
11362
|
return lines.join("\n");
|
|
10213
11363
|
}
|
|
11364
|
+
function formatCallGraphCallers(name, callers, relationshipType) {
|
|
11365
|
+
if (callers.length === 0) {
|
|
11366
|
+
return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
|
|
11367
|
+
}
|
|
11368
|
+
const formatted = callers.map((edge, index) => {
|
|
11369
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
11370
|
+
return `[${index + 1}] \u2190 from ${edge.fromSymbolName ?? "<unknown>"} in ${edge.fromSymbolFilePath ?? "<unknown file>"} [${edge.fromSymbolId}] (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? " [resolved]" : " [unresolved]"}`;
|
|
11371
|
+
});
|
|
11372
|
+
return `"${name}" is called by ${callers.length} function(s):
|
|
11373
|
+
|
|
11374
|
+
${formatted.join("\n")}`;
|
|
11375
|
+
}
|
|
11376
|
+
function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
11377
|
+
if (callees.length === 0) {
|
|
11378
|
+
return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
|
|
11379
|
+
}
|
|
11380
|
+
return callees.map((edge, index) => {
|
|
11381
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
11382
|
+
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
11383
|
+
}).join("\n");
|
|
11384
|
+
}
|
|
11385
|
+
function formatCallGraphPath(from, to, path27) {
|
|
11386
|
+
if (path27.length === 0) {
|
|
11387
|
+
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
11388
|
+
}
|
|
11389
|
+
const formatted = path27.map((hop, index) => {
|
|
11390
|
+
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
11391
|
+
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
11392
|
+
return `${prefix} ${hop.symbolName}${location}`;
|
|
11393
|
+
});
|
|
11394
|
+
return `Path (${path27.length} hops):
|
|
11395
|
+
${formatted.join("\n")}`;
|
|
11396
|
+
}
|
|
10214
11397
|
function formatResultHeader(result, index) {
|
|
10215
11398
|
return result.name ? `[${index + 1}] ${result.chunkType} "${result.name}" in ${result.filePath}:${result.startLine}-${result.endLine}` : `[${index + 1}] ${result.chunkType} in ${result.filePath}:${result.startLine}-${result.endLine}`;
|
|
10216
11399
|
}
|
|
11400
|
+
function formatBlame(result) {
|
|
11401
|
+
if (!result.blame) {
|
|
11402
|
+
return "";
|
|
11403
|
+
}
|
|
11404
|
+
const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
|
|
11405
|
+
return `
|
|
11406
|
+
${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
|
|
11407
|
+
}
|
|
10217
11408
|
function formatDefinitionLookup(results, query) {
|
|
10218
11409
|
if (results.length === 0) {
|
|
10219
11410
|
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
10220
11411
|
}
|
|
10221
11412
|
const formatted = results.map((r, idx) => {
|
|
10222
11413
|
const header = formatResultHeader(r, idx);
|
|
10223
|
-
return `${header} (score: ${r.score.toFixed(2)})
|
|
11414
|
+
return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
|
|
11415
|
+
\`\`\`
|
|
11416
|
+
${truncateContent(r.content)}
|
|
11417
|
+
\`\`\``;
|
|
11418
|
+
});
|
|
11419
|
+
return formatted.join("\n\n");
|
|
11420
|
+
}
|
|
11421
|
+
function formatSearchResults(results, scoreFormat = "similarity") {
|
|
11422
|
+
const formatted = results.map((r, idx) => {
|
|
11423
|
+
const header = formatResultHeader(r, idx);
|
|
11424
|
+
const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
|
|
11425
|
+
return `${header} ${scoreLabel}${formatBlame(r)}
|
|
10224
11426
|
\`\`\`
|
|
10225
11427
|
${truncateContent(r.content)}
|
|
10226
11428
|
\`\`\``;
|
|
@@ -10280,12 +11482,12 @@ function formatPrImpact(result) {
|
|
|
10280
11482
|
}
|
|
10281
11483
|
|
|
10282
11484
|
// src/tools/operations.ts
|
|
10283
|
-
var
|
|
10284
|
-
var
|
|
11485
|
+
var import_fs14 = require("fs");
|
|
11486
|
+
var path21 = __toESM(require("path"), 1);
|
|
10285
11487
|
|
|
10286
11488
|
// src/config/merger.ts
|
|
10287
|
-
var
|
|
10288
|
-
var
|
|
11489
|
+
var import_fs12 = require("fs");
|
|
11490
|
+
var path18 = __toESM(require("path"), 1);
|
|
10289
11491
|
var PROJECT_OVERRIDE_KEYS = [
|
|
10290
11492
|
"embeddingProvider",
|
|
10291
11493
|
"customProvider",
|
|
@@ -10318,8 +11520,8 @@ function mergeUniqueStringArray(values) {
|
|
|
10318
11520
|
return [...new Set(values.map((value) => String(value).trim()))];
|
|
10319
11521
|
}
|
|
10320
11522
|
function normalizeKnowledgeBasePath(value) {
|
|
10321
|
-
let normalized =
|
|
10322
|
-
const root =
|
|
11523
|
+
let normalized = path18.normalize(String(value).trim());
|
|
11524
|
+
const root = path18.parse(normalized).root;
|
|
10323
11525
|
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
10324
11526
|
normalized = normalized.slice(0, -1);
|
|
10325
11527
|
}
|
|
@@ -10353,11 +11555,11 @@ function validateConfigLayerShape(rawConfig, filePath) {
|
|
|
10353
11555
|
return rawConfig;
|
|
10354
11556
|
}
|
|
10355
11557
|
function loadJsonFile(filePath) {
|
|
10356
|
-
if (!(0,
|
|
11558
|
+
if (!(0, import_fs12.existsSync)(filePath)) {
|
|
10357
11559
|
return null;
|
|
10358
11560
|
}
|
|
10359
11561
|
try {
|
|
10360
|
-
const content = (0,
|
|
11562
|
+
const content = (0, import_fs12.readFileSync)(filePath, "utf-8");
|
|
10361
11563
|
return validateConfigLayerShape(JSON.parse(content), filePath);
|
|
10362
11564
|
} catch (error) {
|
|
10363
11565
|
if (error instanceof Error && error.message.startsWith("Config file ")) {
|
|
@@ -10372,8 +11574,8 @@ function loadConfigFile(filePath) {
|
|
|
10372
11574
|
}
|
|
10373
11575
|
function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
|
|
10374
11576
|
const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
|
|
10375
|
-
(0,
|
|
10376
|
-
(0,
|
|
11577
|
+
(0, import_fs12.mkdirSync)(path18.dirname(localConfigPath), { recursive: true });
|
|
11578
|
+
(0, import_fs12.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
10377
11579
|
return localConfigPath;
|
|
10378
11580
|
}
|
|
10379
11581
|
function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
@@ -10383,7 +11585,7 @@ function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
|
10383
11585
|
return {};
|
|
10384
11586
|
}
|
|
10385
11587
|
const normalizedConfig = { ...projectConfig };
|
|
10386
|
-
const projectConfigBaseDir =
|
|
11588
|
+
const projectConfigBaseDir = path18.dirname(path18.dirname(projectConfigPath));
|
|
10387
11589
|
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
10388
11590
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
10389
11591
|
normalizedConfig.knowledgeBases,
|
|
@@ -10447,19 +11649,19 @@ function loadMergedConfig(projectRoot, host = "opencode") {
|
|
|
10447
11649
|
}
|
|
10448
11650
|
|
|
10449
11651
|
// src/tools/knowledge-base-paths.ts
|
|
10450
|
-
var
|
|
11652
|
+
var path19 = __toESM(require("path"), 1);
|
|
10451
11653
|
function resolveConfigPathValue(value, baseDir) {
|
|
10452
11654
|
const trimmed = value.trim();
|
|
10453
11655
|
if (!trimmed) {
|
|
10454
11656
|
return trimmed;
|
|
10455
11657
|
}
|
|
10456
|
-
const absolutePath =
|
|
10457
|
-
return
|
|
11658
|
+
const absolutePath = path19.isAbsolute(trimmed) ? trimmed : path19.resolve(baseDir, trimmed);
|
|
11659
|
+
return path19.normalize(absolutePath);
|
|
10458
11660
|
}
|
|
10459
11661
|
|
|
10460
11662
|
// src/tools/config-state.ts
|
|
10461
|
-
var
|
|
10462
|
-
var
|
|
11663
|
+
var import_fs13 = require("fs");
|
|
11664
|
+
var path20 = __toESM(require("path"), 1);
|
|
10463
11665
|
function normalizeKnowledgeBasePaths(config, projectRoot) {
|
|
10464
11666
|
const normalized = { ...config };
|
|
10465
11667
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
@@ -10483,6 +11685,24 @@ function loadRuntimeConfig(projectRoot, host = "opencode") {
|
|
|
10483
11685
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
10484
11686
|
var configCache = /* @__PURE__ */ new Map();
|
|
10485
11687
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
11688
|
+
function getIndexBusyResult(error) {
|
|
11689
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
11690
|
+
const owner = error.owner;
|
|
11691
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
11692
|
+
if (error.reason === "legacy-lock") {
|
|
11693
|
+
return {
|
|
11694
|
+
kind: "busy",
|
|
11695
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
11696
|
+
};
|
|
11697
|
+
}
|
|
11698
|
+
if (error.reason === "unknown-owner") {
|
|
11699
|
+
return {
|
|
11700
|
+
kind: "busy",
|
|
11701
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
11702
|
+
};
|
|
11703
|
+
}
|
|
11704
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
11705
|
+
}
|
|
10486
11706
|
function getProjectRoot(projectRoot, host) {
|
|
10487
11707
|
if (projectRoot) {
|
|
10488
11708
|
return projectRoot;
|
|
@@ -10527,8 +11747,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
|
|
|
10527
11747
|
}
|
|
10528
11748
|
function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
|
|
10529
11749
|
const root = getProjectRoot(projectRoot, host);
|
|
10530
|
-
const localIndexPath =
|
|
10531
|
-
if ((0,
|
|
11750
|
+
const localIndexPath = path21.join(root, getHostProjectIndexRelativePath(host));
|
|
11751
|
+
if ((0, import_fs14.existsSync)(localIndexPath)) {
|
|
10532
11752
|
return false;
|
|
10533
11753
|
}
|
|
10534
11754
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
@@ -10542,7 +11762,10 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
|
|
|
10542
11762
|
chunkType: options.chunkType,
|
|
10543
11763
|
contextLines: options.contextLines,
|
|
10544
11764
|
metadataOnly: options.metadataOnly,
|
|
10545
|
-
definitionIntent: options.definitionIntent
|
|
11765
|
+
definitionIntent: options.definitionIntent,
|
|
11766
|
+
blameAuthor: options.blameAuthor,
|
|
11767
|
+
blameSha: options.blameSha,
|
|
11768
|
+
blameSince: options.blameSince
|
|
10546
11769
|
});
|
|
10547
11770
|
}
|
|
10548
11771
|
async function findSimilarCode(projectRoot, host, code, options = {}) {
|
|
@@ -10581,35 +11804,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
|
|
|
10581
11804
|
async function runIndexCodebase(projectRoot, host, args, onProgress) {
|
|
10582
11805
|
const root = getProjectRoot(projectRoot, host);
|
|
10583
11806
|
const key = getIndexerCacheKey(root, host);
|
|
10584
|
-
const cachedConfig = configCache.get(key);
|
|
10585
11807
|
let indexer = getIndexerForProject(root, host);
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
chunksProcessed: progress.chunksProcessed,
|
|
10606
|
-
totalChunks: progress.totalChunks,
|
|
10607
|
-
percentage: calculatePercentage(progress)
|
|
11808
|
+
const runtimeConfig = configCache.get(key);
|
|
11809
|
+
try {
|
|
11810
|
+
if (args.estimateOnly) {
|
|
11811
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
11812
|
+
}
|
|
11813
|
+
const runIndex = async (target) => {
|
|
11814
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
11815
|
+
return operation((progress) => {
|
|
11816
|
+
if (onProgress) {
|
|
11817
|
+
return onProgress(formatProgressTitle(progress), {
|
|
11818
|
+
phase: progress.phase,
|
|
11819
|
+
filesProcessed: progress.filesProcessed,
|
|
11820
|
+
totalFiles: progress.totalFiles,
|
|
11821
|
+
chunksProcessed: progress.chunksProcessed,
|
|
11822
|
+
totalChunks: progress.totalChunks,
|
|
11823
|
+
percentage: calculatePercentage(progress)
|
|
11824
|
+
});
|
|
11825
|
+
}
|
|
11826
|
+
return Promise.resolve();
|
|
10608
11827
|
});
|
|
11828
|
+
};
|
|
11829
|
+
let stats;
|
|
11830
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
11831
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
11832
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
11833
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
11834
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
11835
|
+
indexer = getIndexerForProject(root, host);
|
|
11836
|
+
return runIndex(indexer);
|
|
11837
|
+
}, { completeRecoveries: false });
|
|
11838
|
+
} else {
|
|
11839
|
+
stats = await runIndex(indexer);
|
|
10609
11840
|
}
|
|
10610
|
-
return
|
|
10611
|
-
})
|
|
10612
|
-
|
|
11841
|
+
return { kind: "stats", stats };
|
|
11842
|
+
} catch (error) {
|
|
11843
|
+
const busyResult = getIndexBusyResult(error);
|
|
11844
|
+
if (!busyResult) throw error;
|
|
11845
|
+
return busyResult;
|
|
11846
|
+
}
|
|
10613
11847
|
}
|
|
10614
11848
|
async function getIndexStatus(projectRoot, host) {
|
|
10615
11849
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
@@ -10619,6 +11853,15 @@ async function getIndexHealthCheck(projectRoot, host) {
|
|
|
10619
11853
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10620
11854
|
return indexer.healthCheck();
|
|
10621
11855
|
}
|
|
11856
|
+
async function runIndexHealthCheck(projectRoot, host) {
|
|
11857
|
+
try {
|
|
11858
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
|
|
11859
|
+
} catch (error) {
|
|
11860
|
+
const busyResult = getIndexBusyResult(error);
|
|
11861
|
+
if (!busyResult) throw error;
|
|
11862
|
+
return busyResult;
|
|
11863
|
+
}
|
|
11864
|
+
}
|
|
10622
11865
|
async function getPrImpact(projectRoot, host, params) {
|
|
10623
11866
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10624
11867
|
return indexer.getPrImpact({
|
|
@@ -10684,13 +11927,6 @@ async function getIndexLogs(projectRoot, host, args) {
|
|
|
10684
11927
|
}
|
|
10685
11928
|
|
|
10686
11929
|
// src/mcp-server/shared.ts
|
|
10687
|
-
var MAX_CONTENT_LINES2 = 30;
|
|
10688
|
-
function truncateContent2(content) {
|
|
10689
|
-
const lines = content.split("\n");
|
|
10690
|
-
if (lines.length <= MAX_CONTENT_LINES2) return content;
|
|
10691
|
-
return lines.slice(0, MAX_CONTENT_LINES2).join("\n") + `
|
|
10692
|
-
// ... (${lines.length - MAX_CONTENT_LINES2} more lines)`;
|
|
10693
|
-
}
|
|
10694
11930
|
var CHUNK_TYPE_ENUM = [
|
|
10695
11931
|
"function",
|
|
10696
11932
|
"class",
|
|
@@ -10716,7 +11952,10 @@ function registerMcpTools(server, runtime) {
|
|
|
10716
11952
|
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10717
11953
|
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10718
11954
|
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
10719
|
-
contextLines: import_zod2.z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
|
|
11955
|
+
contextLines: import_zod2.z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
|
|
11956
|
+
blameAuthor: import_zod2.z.string().optional().describe("Filter by git blame author name or email"),
|
|
11957
|
+
blameSha: import_zod2.z.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
11958
|
+
blameSince: import_zod2.z.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
10720
11959
|
},
|
|
10721
11960
|
async (args) => {
|
|
10722
11961
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -10724,21 +11963,17 @@ function registerMcpTools(server, runtime) {
|
|
|
10724
11963
|
fileType: args.fileType,
|
|
10725
11964
|
directory: args.directory,
|
|
10726
11965
|
chunkType: args.chunkType,
|
|
10727
|
-
contextLines: args.contextLines
|
|
11966
|
+
contextLines: args.contextLines,
|
|
11967
|
+
blameAuthor: args.blameAuthor,
|
|
11968
|
+
blameSha: args.blameSha,
|
|
11969
|
+
blameSince: args.blameSince
|
|
10728
11970
|
});
|
|
10729
11971
|
if (results.length === 0) {
|
|
10730
11972
|
return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
|
|
10731
11973
|
}
|
|
10732
|
-
const formatted = results.map((r, idx) => {
|
|
10733
|
-
const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10734
|
-
return `${header} (score: ${r.score.toFixed(2)})
|
|
10735
|
-
\`\`\`
|
|
10736
|
-
${truncateContent2(r.content)}
|
|
10737
|
-
\`\`\``;
|
|
10738
|
-
});
|
|
10739
11974
|
return { content: [{ type: "text", text: `Found ${results.length} results for "${args.query}":
|
|
10740
11975
|
|
|
10741
|
-
${
|
|
11976
|
+
${formatSearchResults(results, "score")}` }] };
|
|
10742
11977
|
}
|
|
10743
11978
|
);
|
|
10744
11979
|
server.tool(
|
|
@@ -10749,7 +11984,10 @@ ${formatted.join("\n\n")}` }] };
|
|
|
10749
11984
|
limit: import_zod2.z.number().optional().default(10).describe("Maximum number of results to return"),
|
|
10750
11985
|
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10751
11986
|
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10752
|
-
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type")
|
|
11987
|
+
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
11988
|
+
blameAuthor: import_zod2.z.string().optional().describe("Filter by git blame author name or email"),
|
|
11989
|
+
blameSha: import_zod2.z.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
11990
|
+
blameSince: import_zod2.z.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
10753
11991
|
},
|
|
10754
11992
|
async (args) => {
|
|
10755
11993
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -10757,19 +11995,17 @@ ${formatted.join("\n\n")}` }] };
|
|
|
10757
11995
|
fileType: args.fileType,
|
|
10758
11996
|
directory: args.directory,
|
|
10759
11997
|
chunkType: args.chunkType,
|
|
10760
|
-
metadataOnly: true
|
|
11998
|
+
metadataOnly: true,
|
|
11999
|
+
blameAuthor: args.blameAuthor,
|
|
12000
|
+
blameSha: args.blameSha,
|
|
12001
|
+
blameSince: args.blameSince
|
|
10761
12002
|
});
|
|
10762
12003
|
if (results.length === 0) {
|
|
10763
12004
|
return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
|
|
10764
12005
|
}
|
|
10765
|
-
const formatted = results.map((r, idx) => {
|
|
10766
|
-
const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10767
|
-
const name = r.name ? `"${r.name}"` : "(anonymous)";
|
|
10768
|
-
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})`;
|
|
10769
|
-
});
|
|
10770
12006
|
return { content: [{ type: "text", text: `Found ${results.length} locations for "${args.query}":
|
|
10771
12007
|
|
|
10772
|
-
${
|
|
12008
|
+
${formatCodebasePeek(results)}
|
|
10773
12009
|
|
|
10774
12010
|
Use Read tool to examine specific files.` }] };
|
|
10775
12011
|
}
|
|
@@ -10784,8 +12020,13 @@ Use Read tool to examine specific files.` }] };
|
|
|
10784
12020
|
},
|
|
10785
12021
|
async (args) => {
|
|
10786
12022
|
const result = await runIndexCodebase(runtime.projectRoot, runtime.host, args);
|
|
10787
|
-
|
|
10788
|
-
|
|
12023
|
+
if (result.kind === "estimate") {
|
|
12024
|
+
return { content: [{ type: "text", text: formatCostEstimate(result.estimate) }] };
|
|
12025
|
+
}
|
|
12026
|
+
if (result.kind === "busy") {
|
|
12027
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12028
|
+
}
|
|
12029
|
+
return { content: [{ type: "text", text: formatIndexStats(result.stats, args.verbose ?? false) }] };
|
|
10789
12030
|
}
|
|
10790
12031
|
);
|
|
10791
12032
|
server.tool(
|
|
@@ -10802,8 +12043,11 @@ Use Read tool to examine specific files.` }] };
|
|
|
10802
12043
|
"Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
|
|
10803
12044
|
{},
|
|
10804
12045
|
async () => {
|
|
10805
|
-
const result = await
|
|
10806
|
-
|
|
12046
|
+
const result = await runIndexHealthCheck(runtime.projectRoot, runtime.host);
|
|
12047
|
+
if (result.kind === "busy") {
|
|
12048
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12049
|
+
}
|
|
12050
|
+
return { content: [{ type: "text", text: formatHealthCheck(result.health) }] };
|
|
10807
12051
|
}
|
|
10808
12052
|
);
|
|
10809
12053
|
server.tool(
|
|
@@ -10850,16 +12094,9 @@ Use Read tool to examine specific files.` }] };
|
|
|
10850
12094
|
if (results.length === 0) {
|
|
10851
12095
|
return { content: [{ type: "text", text: "No similar code found. Try a different snippet or run index_codebase first." }] };
|
|
10852
12096
|
}
|
|
10853
|
-
const formatted = results.map((r, idx) => {
|
|
10854
|
-
const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10855
|
-
return `${header} (similarity: ${(r.score * 100).toFixed(1)}%)
|
|
10856
|
-
\`\`\`
|
|
10857
|
-
${truncateContent2(r.content)}
|
|
10858
|
-
\`\`\``;
|
|
10859
|
-
});
|
|
10860
12097
|
return { content: [{ type: "text", text: `Found ${results.length} similar code blocks:
|
|
10861
12098
|
|
|
10862
|
-
${
|
|
12099
|
+
${formatSearchResults(results)}` }] };
|
|
10863
12100
|
}
|
|
10864
12101
|
);
|
|
10865
12102
|
server.tool(
|
|
@@ -10895,28 +12132,10 @@ ${formatted.join("\n\n")}` }] };
|
|
|
10895
12132
|
return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
|
|
10896
12133
|
}
|
|
10897
12134
|
const { callees } = await getCallGraphData(runtime.projectRoot, runtime.host, args);
|
|
10898
|
-
|
|
10899
|
-
return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
|
|
10900
|
-
}
|
|
10901
|
-
const formatted2 = callees.map((e, i) => {
|
|
10902
|
-
const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
|
|
10903
|
-
return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
|
|
10904
|
-
});
|
|
10905
|
-
return { content: [{ type: "text", text: `Callees (${callees.length}):
|
|
10906
|
-
|
|
10907
|
-
${formatted2.join("\n")}` }] };
|
|
12135
|
+
return { content: [{ type: "text", text: formatCallGraphCallees(args.symbolId, callees, args.relationshipType) }] };
|
|
10908
12136
|
}
|
|
10909
12137
|
const { callers } = await getCallGraphData(runtime.projectRoot, runtime.host, args);
|
|
10910
|
-
|
|
10911
|
-
return { content: [{ type: "text", text: `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
|
|
10912
|
-
}
|
|
10913
|
-
const formatted = callers.map((e, i) => {
|
|
10914
|
-
const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
|
|
10915
|
-
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]"}`;
|
|
10916
|
-
});
|
|
10917
|
-
return { content: [{ type: "text", text: `"${args.name}" is called by ${callers.length} function(s):
|
|
10918
|
-
|
|
10919
|
-
${formatted.join("\n")}` }] };
|
|
12138
|
+
return { content: [{ type: "text", text: formatCallGraphCallers(args.name, callers, args.relationshipType) }] };
|
|
10920
12139
|
}
|
|
10921
12140
|
);
|
|
10922
12141
|
server.tool(
|
|
@@ -10928,17 +12147,8 @@ ${formatted.join("\n")}` }] };
|
|
|
10928
12147
|
maxDepth: import_zod2.z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
|
|
10929
12148
|
},
|
|
10930
12149
|
async (args) => {
|
|
10931
|
-
const
|
|
10932
|
-
|
|
10933
|
-
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.` }] };
|
|
10934
|
-
}
|
|
10935
|
-
const formatted = path25.map((hop, i) => {
|
|
10936
|
-
const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10937
|
-
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10938
|
-
return `${prefix} ${hop.symbolName}${location}`;
|
|
10939
|
-
});
|
|
10940
|
-
return { content: [{ type: "text", text: `Path (${path25.length} hops):
|
|
10941
|
-
${formatted.join("\n")}` }] };
|
|
12150
|
+
const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
|
|
12151
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
10942
12152
|
}
|
|
10943
12153
|
);
|
|
10944
12154
|
server.tool(
|
|
@@ -10974,7 +12184,7 @@ ${formatted.join("\n")}` }] };
|
|
|
10974
12184
|
// src/mcp-server.ts
|
|
10975
12185
|
var import_meta2 = {};
|
|
10976
12186
|
function getPackageVersion() {
|
|
10977
|
-
const raw = JSON.parse((0,
|
|
12187
|
+
const raw = JSON.parse((0, import_fs15.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
|
|
10978
12188
|
if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
|
|
10979
12189
|
return raw.version;
|
|
10980
12190
|
}
|
|
@@ -10995,18 +12205,37 @@ function createMcpServer(projectRoot, config, host = "opencode") {
|
|
|
10995
12205
|
}
|
|
10996
12206
|
|
|
10997
12207
|
// src/utils/auto-index.ts
|
|
12208
|
+
var INITIAL_RETRY_DELAY_MS = 50;
|
|
12209
|
+
var MAX_RETRY_DELAY_MS = 500;
|
|
12210
|
+
var autoIndexStates = /* @__PURE__ */ new WeakMap();
|
|
10998
12211
|
function getErrorMessage3(error) {
|
|
10999
12212
|
return error instanceof Error ? error.message : String(error);
|
|
11000
12213
|
}
|
|
11001
|
-
function
|
|
11002
|
-
indexer.
|
|
11003
|
-
|
|
11004
|
-
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
11005
|
-
});
|
|
12214
|
+
function runAutoIndex(indexer, projectRoot, state) {
|
|
12215
|
+
void indexer.index().then(() => {
|
|
12216
|
+
autoIndexStates.delete(indexer);
|
|
11006
12217
|
}).catch((error) => {
|
|
11007
|
-
|
|
12218
|
+
if (!isTransientIndexLockContention(error)) {
|
|
12219
|
+
autoIndexStates.delete(indexer);
|
|
12220
|
+
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
12221
|
+
return;
|
|
12222
|
+
}
|
|
12223
|
+
const retryDelayMs = state.retryDelayMs;
|
|
12224
|
+
state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
|
|
12225
|
+
const retryTimer = setTimeout(() => {
|
|
12226
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12227
|
+
}, retryDelayMs);
|
|
12228
|
+
retryTimer.unref?.();
|
|
11008
12229
|
});
|
|
11009
12230
|
}
|
|
12231
|
+
function startAutoIndex(indexer, projectRoot) {
|
|
12232
|
+
if (autoIndexStates.has(indexer)) return;
|
|
12233
|
+
const state = {
|
|
12234
|
+
retryDelayMs: INITIAL_RETRY_DELAY_MS
|
|
12235
|
+
};
|
|
12236
|
+
autoIndexStates.set(indexer, state);
|
|
12237
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12238
|
+
}
|
|
11010
12239
|
|
|
11011
12240
|
// node_modules/chokidar/index.js
|
|
11012
12241
|
var import_node_events = require("events");
|
|
@@ -11098,7 +12327,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
11098
12327
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
11099
12328
|
const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
|
|
11100
12329
|
if (wantBigintFsStats) {
|
|
11101
|
-
this._stat = (
|
|
12330
|
+
this._stat = (path27) => statMethod(path27, { bigint: true });
|
|
11102
12331
|
} else {
|
|
11103
12332
|
this._stat = statMethod;
|
|
11104
12333
|
}
|
|
@@ -11123,8 +12352,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
11123
12352
|
const par = this.parent;
|
|
11124
12353
|
const fil = par && par.files;
|
|
11125
12354
|
if (fil && fil.length > 0) {
|
|
11126
|
-
const { path:
|
|
11127
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
12355
|
+
const { path: path27, depth } = par;
|
|
12356
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path27));
|
|
11128
12357
|
const awaited = await Promise.all(slice);
|
|
11129
12358
|
for (const entry of awaited) {
|
|
11130
12359
|
if (!entry)
|
|
@@ -11164,20 +12393,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
11164
12393
|
this.reading = false;
|
|
11165
12394
|
}
|
|
11166
12395
|
}
|
|
11167
|
-
async _exploreDir(
|
|
12396
|
+
async _exploreDir(path27, depth) {
|
|
11168
12397
|
let files;
|
|
11169
12398
|
try {
|
|
11170
|
-
files = await (0, import_promises.readdir)(
|
|
12399
|
+
files = await (0, import_promises.readdir)(path27, this._rdOptions);
|
|
11171
12400
|
} catch (error) {
|
|
11172
12401
|
this._onError(error);
|
|
11173
12402
|
}
|
|
11174
|
-
return { files, depth, path:
|
|
12403
|
+
return { files, depth, path: path27 };
|
|
11175
12404
|
}
|
|
11176
|
-
async _formatEntry(dirent,
|
|
12405
|
+
async _formatEntry(dirent, path27) {
|
|
11177
12406
|
let entry;
|
|
11178
12407
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
11179
12408
|
try {
|
|
11180
|
-
const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(
|
|
12409
|
+
const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path27, basename5));
|
|
11181
12410
|
entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
|
|
11182
12411
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
11183
12412
|
} catch (err) {
|
|
@@ -11577,16 +12806,16 @@ var delFromSet = (main2, prop, item) => {
|
|
|
11577
12806
|
};
|
|
11578
12807
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
11579
12808
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
11580
|
-
function createFsWatchInstance(
|
|
12809
|
+
function createFsWatchInstance(path27, options, listener, errHandler, emitRaw) {
|
|
11581
12810
|
const handleEvent = (rawEvent, evPath) => {
|
|
11582
|
-
listener(
|
|
11583
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
11584
|
-
if (evPath &&
|
|
11585
|
-
fsWatchBroadcast(sp.resolve(
|
|
12811
|
+
listener(path27);
|
|
12812
|
+
emitRaw(rawEvent, evPath, { watchedPath: path27 });
|
|
12813
|
+
if (evPath && path27 !== evPath) {
|
|
12814
|
+
fsWatchBroadcast(sp.resolve(path27, evPath), KEY_LISTENERS, sp.join(path27, evPath));
|
|
11586
12815
|
}
|
|
11587
12816
|
};
|
|
11588
12817
|
try {
|
|
11589
|
-
return (0, import_node_fs.watch)(
|
|
12818
|
+
return (0, import_node_fs.watch)(path27, {
|
|
11590
12819
|
persistent: options.persistent
|
|
11591
12820
|
}, handleEvent);
|
|
11592
12821
|
} catch (error) {
|
|
@@ -11602,12 +12831,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
11602
12831
|
listener(val1, val2, val3);
|
|
11603
12832
|
});
|
|
11604
12833
|
};
|
|
11605
|
-
var setFsWatchListener = (
|
|
12834
|
+
var setFsWatchListener = (path27, fullPath, options, handlers) => {
|
|
11606
12835
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
11607
12836
|
let cont = FsWatchInstances.get(fullPath);
|
|
11608
12837
|
let watcher;
|
|
11609
12838
|
if (!options.persistent) {
|
|
11610
|
-
watcher = createFsWatchInstance(
|
|
12839
|
+
watcher = createFsWatchInstance(path27, options, listener, errHandler, rawEmitter);
|
|
11611
12840
|
if (!watcher)
|
|
11612
12841
|
return;
|
|
11613
12842
|
return watcher.close.bind(watcher);
|
|
@@ -11618,7 +12847,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
11618
12847
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
11619
12848
|
} else {
|
|
11620
12849
|
watcher = createFsWatchInstance(
|
|
11621
|
-
|
|
12850
|
+
path27,
|
|
11622
12851
|
options,
|
|
11623
12852
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
11624
12853
|
errHandler,
|
|
@@ -11633,7 +12862,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
11633
12862
|
cont.watcherUnusable = true;
|
|
11634
12863
|
if (isWindows && error.code === "EPERM") {
|
|
11635
12864
|
try {
|
|
11636
|
-
const fd = await (0, import_promises2.open)(
|
|
12865
|
+
const fd = await (0, import_promises2.open)(path27, "r");
|
|
11637
12866
|
await fd.close();
|
|
11638
12867
|
broadcastErr(error);
|
|
11639
12868
|
} catch (err) {
|
|
@@ -11664,7 +12893,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
11664
12893
|
};
|
|
11665
12894
|
};
|
|
11666
12895
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
11667
|
-
var setFsWatchFileListener = (
|
|
12896
|
+
var setFsWatchFileListener = (path27, fullPath, options, handlers) => {
|
|
11668
12897
|
const { listener, rawEmitter } = handlers;
|
|
11669
12898
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
11670
12899
|
const copts = cont && cont.options;
|
|
@@ -11686,7 +12915,7 @@ var setFsWatchFileListener = (path25, fullPath, options, handlers) => {
|
|
|
11686
12915
|
});
|
|
11687
12916
|
const currmtime = curr.mtimeMs;
|
|
11688
12917
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
11689
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
12918
|
+
foreach(cont.listeners, (listener2) => listener2(path27, curr));
|
|
11690
12919
|
}
|
|
11691
12920
|
})
|
|
11692
12921
|
};
|
|
@@ -11716,13 +12945,13 @@ var NodeFsHandler = class {
|
|
|
11716
12945
|
* @param listener on fs change
|
|
11717
12946
|
* @returns closer for the watcher instance
|
|
11718
12947
|
*/
|
|
11719
|
-
_watchWithNodeFs(
|
|
12948
|
+
_watchWithNodeFs(path27, listener) {
|
|
11720
12949
|
const opts = this.fsw.options;
|
|
11721
|
-
const directory = sp.dirname(
|
|
11722
|
-
const basename5 = sp.basename(
|
|
12950
|
+
const directory = sp.dirname(path27);
|
|
12951
|
+
const basename5 = sp.basename(path27);
|
|
11723
12952
|
const parent = this.fsw._getWatchedDir(directory);
|
|
11724
12953
|
parent.add(basename5);
|
|
11725
|
-
const absolutePath = sp.resolve(
|
|
12954
|
+
const absolutePath = sp.resolve(path27);
|
|
11726
12955
|
const options = {
|
|
11727
12956
|
persistent: opts.persistent
|
|
11728
12957
|
};
|
|
@@ -11732,12 +12961,12 @@ var NodeFsHandler = class {
|
|
|
11732
12961
|
if (opts.usePolling) {
|
|
11733
12962
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
11734
12963
|
options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
|
|
11735
|
-
closer = setFsWatchFileListener(
|
|
12964
|
+
closer = setFsWatchFileListener(path27, absolutePath, options, {
|
|
11736
12965
|
listener,
|
|
11737
12966
|
rawEmitter: this.fsw._emitRaw
|
|
11738
12967
|
});
|
|
11739
12968
|
} else {
|
|
11740
|
-
closer = setFsWatchListener(
|
|
12969
|
+
closer = setFsWatchListener(path27, absolutePath, options, {
|
|
11741
12970
|
listener,
|
|
11742
12971
|
errHandler: this._boundHandleError,
|
|
11743
12972
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -11759,7 +12988,7 @@ var NodeFsHandler = class {
|
|
|
11759
12988
|
let prevStats = stats;
|
|
11760
12989
|
if (parent.has(basename5))
|
|
11761
12990
|
return;
|
|
11762
|
-
const listener = async (
|
|
12991
|
+
const listener = async (path27, newStats) => {
|
|
11763
12992
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
11764
12993
|
return;
|
|
11765
12994
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -11773,11 +13002,11 @@ var NodeFsHandler = class {
|
|
|
11773
13002
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
11774
13003
|
}
|
|
11775
13004
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
11776
|
-
this.fsw._closeFile(
|
|
13005
|
+
this.fsw._closeFile(path27);
|
|
11777
13006
|
prevStats = newStats2;
|
|
11778
13007
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
11779
13008
|
if (closer2)
|
|
11780
|
-
this.fsw._addPathCloser(
|
|
13009
|
+
this.fsw._addPathCloser(path27, closer2);
|
|
11781
13010
|
} else {
|
|
11782
13011
|
prevStats = newStats2;
|
|
11783
13012
|
}
|
|
@@ -11809,7 +13038,7 @@ var NodeFsHandler = class {
|
|
|
11809
13038
|
* @param item basename of this item
|
|
11810
13039
|
* @returns true if no more processing is needed for this entry.
|
|
11811
13040
|
*/
|
|
11812
|
-
async _handleSymlink(entry, directory,
|
|
13041
|
+
async _handleSymlink(entry, directory, path27, item) {
|
|
11813
13042
|
if (this.fsw.closed) {
|
|
11814
13043
|
return;
|
|
11815
13044
|
}
|
|
@@ -11819,7 +13048,7 @@ var NodeFsHandler = class {
|
|
|
11819
13048
|
this.fsw._incrReadyCount();
|
|
11820
13049
|
let linkPath;
|
|
11821
13050
|
try {
|
|
11822
|
-
linkPath = await (0, import_promises2.realpath)(
|
|
13051
|
+
linkPath = await (0, import_promises2.realpath)(path27);
|
|
11823
13052
|
} catch (e) {
|
|
11824
13053
|
this.fsw._emitReady();
|
|
11825
13054
|
return true;
|
|
@@ -11829,12 +13058,12 @@ var NodeFsHandler = class {
|
|
|
11829
13058
|
if (dir.has(item)) {
|
|
11830
13059
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
11831
13060
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
11832
|
-
this.fsw._emit(EV.CHANGE,
|
|
13061
|
+
this.fsw._emit(EV.CHANGE, path27, entry.stats);
|
|
11833
13062
|
}
|
|
11834
13063
|
} else {
|
|
11835
13064
|
dir.add(item);
|
|
11836
13065
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
11837
|
-
this.fsw._emit(EV.ADD,
|
|
13066
|
+
this.fsw._emit(EV.ADD, path27, entry.stats);
|
|
11838
13067
|
}
|
|
11839
13068
|
this.fsw._emitReady();
|
|
11840
13069
|
return true;
|
|
@@ -11864,9 +13093,9 @@ var NodeFsHandler = class {
|
|
|
11864
13093
|
return;
|
|
11865
13094
|
}
|
|
11866
13095
|
const item = entry.path;
|
|
11867
|
-
let
|
|
13096
|
+
let path27 = sp.join(directory, item);
|
|
11868
13097
|
current.add(item);
|
|
11869
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
13098
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path27, item)) {
|
|
11870
13099
|
return;
|
|
11871
13100
|
}
|
|
11872
13101
|
if (this.fsw.closed) {
|
|
@@ -11875,8 +13104,8 @@ var NodeFsHandler = class {
|
|
|
11875
13104
|
}
|
|
11876
13105
|
if (item === target || !target && !previous.has(item)) {
|
|
11877
13106
|
this.fsw._incrReadyCount();
|
|
11878
|
-
|
|
11879
|
-
this._addToNodeFs(
|
|
13107
|
+
path27 = sp.join(dir, sp.relative(dir, path27));
|
|
13108
|
+
this._addToNodeFs(path27, initialAdd, wh, depth + 1);
|
|
11880
13109
|
}
|
|
11881
13110
|
}).on(EV.ERROR, this._boundHandleError);
|
|
11882
13111
|
return new Promise((resolve15, reject) => {
|
|
@@ -11945,13 +13174,13 @@ var NodeFsHandler = class {
|
|
|
11945
13174
|
* @param depth Child path actually targeted for watch
|
|
11946
13175
|
* @param target Child path actually targeted for watch
|
|
11947
13176
|
*/
|
|
11948
|
-
async _addToNodeFs(
|
|
13177
|
+
async _addToNodeFs(path27, initialAdd, priorWh, depth, target) {
|
|
11949
13178
|
const ready = this.fsw._emitReady;
|
|
11950
|
-
if (this.fsw._isIgnored(
|
|
13179
|
+
if (this.fsw._isIgnored(path27) || this.fsw.closed) {
|
|
11951
13180
|
ready();
|
|
11952
13181
|
return false;
|
|
11953
13182
|
}
|
|
11954
|
-
const wh = this.fsw._getWatchHelpers(
|
|
13183
|
+
const wh = this.fsw._getWatchHelpers(path27);
|
|
11955
13184
|
if (priorWh) {
|
|
11956
13185
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
11957
13186
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -11967,8 +13196,8 @@ var NodeFsHandler = class {
|
|
|
11967
13196
|
const follow = this.fsw.options.followSymlinks;
|
|
11968
13197
|
let closer;
|
|
11969
13198
|
if (stats.isDirectory()) {
|
|
11970
|
-
const absPath = sp.resolve(
|
|
11971
|
-
const targetPath = follow ? await (0, import_promises2.realpath)(
|
|
13199
|
+
const absPath = sp.resolve(path27);
|
|
13200
|
+
const targetPath = follow ? await (0, import_promises2.realpath)(path27) : path27;
|
|
11972
13201
|
if (this.fsw.closed)
|
|
11973
13202
|
return;
|
|
11974
13203
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -11978,29 +13207,29 @@ var NodeFsHandler = class {
|
|
|
11978
13207
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
11979
13208
|
}
|
|
11980
13209
|
} else if (stats.isSymbolicLink()) {
|
|
11981
|
-
const targetPath = follow ? await (0, import_promises2.realpath)(
|
|
13210
|
+
const targetPath = follow ? await (0, import_promises2.realpath)(path27) : path27;
|
|
11982
13211
|
if (this.fsw.closed)
|
|
11983
13212
|
return;
|
|
11984
13213
|
const parent = sp.dirname(wh.watchPath);
|
|
11985
13214
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
11986
13215
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
11987
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
13216
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path27, wh, targetPath);
|
|
11988
13217
|
if (this.fsw.closed)
|
|
11989
13218
|
return;
|
|
11990
13219
|
if (targetPath !== void 0) {
|
|
11991
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
13220
|
+
this.fsw._symlinkPaths.set(sp.resolve(path27), targetPath);
|
|
11992
13221
|
}
|
|
11993
13222
|
} else {
|
|
11994
13223
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
11995
13224
|
}
|
|
11996
13225
|
ready();
|
|
11997
13226
|
if (closer)
|
|
11998
|
-
this.fsw._addPathCloser(
|
|
13227
|
+
this.fsw._addPathCloser(path27, closer);
|
|
11999
13228
|
return false;
|
|
12000
13229
|
} catch (error) {
|
|
12001
13230
|
if (this.fsw._handleError(error)) {
|
|
12002
13231
|
ready();
|
|
12003
|
-
return
|
|
13232
|
+
return path27;
|
|
12004
13233
|
}
|
|
12005
13234
|
}
|
|
12006
13235
|
}
|
|
@@ -12032,35 +13261,35 @@ function createPattern(matcher) {
|
|
|
12032
13261
|
if (matcher.path === string)
|
|
12033
13262
|
return true;
|
|
12034
13263
|
if (matcher.recursive) {
|
|
12035
|
-
const
|
|
12036
|
-
if (!
|
|
13264
|
+
const relative11 = sp2.relative(matcher.path, string);
|
|
13265
|
+
if (!relative11) {
|
|
12037
13266
|
return false;
|
|
12038
13267
|
}
|
|
12039
|
-
return !
|
|
13268
|
+
return !relative11.startsWith("..") && !sp2.isAbsolute(relative11);
|
|
12040
13269
|
}
|
|
12041
13270
|
return false;
|
|
12042
13271
|
};
|
|
12043
13272
|
}
|
|
12044
13273
|
return () => false;
|
|
12045
13274
|
}
|
|
12046
|
-
function normalizePath2(
|
|
12047
|
-
if (typeof
|
|
13275
|
+
function normalizePath2(path27) {
|
|
13276
|
+
if (typeof path27 !== "string")
|
|
12048
13277
|
throw new Error("string expected");
|
|
12049
|
-
|
|
12050
|
-
|
|
13278
|
+
path27 = sp2.normalize(path27);
|
|
13279
|
+
path27 = path27.replace(/\\/g, "/");
|
|
12051
13280
|
let prepend = false;
|
|
12052
|
-
if (
|
|
13281
|
+
if (path27.startsWith("//"))
|
|
12053
13282
|
prepend = true;
|
|
12054
|
-
|
|
13283
|
+
path27 = path27.replace(DOUBLE_SLASH_RE, "/");
|
|
12055
13284
|
if (prepend)
|
|
12056
|
-
|
|
12057
|
-
return
|
|
13285
|
+
path27 = "/" + path27;
|
|
13286
|
+
return path27;
|
|
12058
13287
|
}
|
|
12059
13288
|
function matchPatterns(patterns, testString, stats) {
|
|
12060
|
-
const
|
|
13289
|
+
const path27 = normalizePath2(testString);
|
|
12061
13290
|
for (let index = 0; index < patterns.length; index++) {
|
|
12062
13291
|
const pattern = patterns[index];
|
|
12063
|
-
if (pattern(
|
|
13292
|
+
if (pattern(path27, stats)) {
|
|
12064
13293
|
return true;
|
|
12065
13294
|
}
|
|
12066
13295
|
}
|
|
@@ -12098,19 +13327,19 @@ var toUnix = (string) => {
|
|
|
12098
13327
|
}
|
|
12099
13328
|
return str;
|
|
12100
13329
|
};
|
|
12101
|
-
var normalizePathToUnix = (
|
|
12102
|
-
var normalizeIgnored = (cwd = "") => (
|
|
12103
|
-
if (typeof
|
|
12104
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
13330
|
+
var normalizePathToUnix = (path27) => toUnix(sp2.normalize(toUnix(path27)));
|
|
13331
|
+
var normalizeIgnored = (cwd = "") => (path27) => {
|
|
13332
|
+
if (typeof path27 === "string") {
|
|
13333
|
+
return normalizePathToUnix(sp2.isAbsolute(path27) ? path27 : sp2.join(cwd, path27));
|
|
12105
13334
|
} else {
|
|
12106
|
-
return
|
|
13335
|
+
return path27;
|
|
12107
13336
|
}
|
|
12108
13337
|
};
|
|
12109
|
-
var getAbsolutePath = (
|
|
12110
|
-
if (sp2.isAbsolute(
|
|
12111
|
-
return
|
|
13338
|
+
var getAbsolutePath = (path27, cwd) => {
|
|
13339
|
+
if (sp2.isAbsolute(path27)) {
|
|
13340
|
+
return path27;
|
|
12112
13341
|
}
|
|
12113
|
-
return sp2.join(cwd,
|
|
13342
|
+
return sp2.join(cwd, path27);
|
|
12114
13343
|
};
|
|
12115
13344
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
12116
13345
|
var DirEntry = class {
|
|
@@ -12175,10 +13404,10 @@ var WatchHelper = class {
|
|
|
12175
13404
|
dirParts;
|
|
12176
13405
|
followSymlinks;
|
|
12177
13406
|
statMethod;
|
|
12178
|
-
constructor(
|
|
13407
|
+
constructor(path27, follow, fsw) {
|
|
12179
13408
|
this.fsw = fsw;
|
|
12180
|
-
const watchPath =
|
|
12181
|
-
this.path =
|
|
13409
|
+
const watchPath = path27;
|
|
13410
|
+
this.path = path27 = path27.replace(REPLACER_RE, "");
|
|
12182
13411
|
this.watchPath = watchPath;
|
|
12183
13412
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
12184
13413
|
this.dirParts = [];
|
|
@@ -12318,20 +13547,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12318
13547
|
this._closePromise = void 0;
|
|
12319
13548
|
let paths = unifyPaths(paths_);
|
|
12320
13549
|
if (cwd) {
|
|
12321
|
-
paths = paths.map((
|
|
12322
|
-
const absPath = getAbsolutePath(
|
|
13550
|
+
paths = paths.map((path27) => {
|
|
13551
|
+
const absPath = getAbsolutePath(path27, cwd);
|
|
12323
13552
|
return absPath;
|
|
12324
13553
|
});
|
|
12325
13554
|
}
|
|
12326
|
-
paths.forEach((
|
|
12327
|
-
this._removeIgnoredPath(
|
|
13555
|
+
paths.forEach((path27) => {
|
|
13556
|
+
this._removeIgnoredPath(path27);
|
|
12328
13557
|
});
|
|
12329
13558
|
this._userIgnored = void 0;
|
|
12330
13559
|
if (!this._readyCount)
|
|
12331
13560
|
this._readyCount = 0;
|
|
12332
13561
|
this._readyCount += paths.length;
|
|
12333
|
-
Promise.all(paths.map(async (
|
|
12334
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
13562
|
+
Promise.all(paths.map(async (path27) => {
|
|
13563
|
+
const res = await this._nodeFsHandler._addToNodeFs(path27, !_internal, void 0, 0, _origAdd);
|
|
12335
13564
|
if (res)
|
|
12336
13565
|
this._emitReady();
|
|
12337
13566
|
return res;
|
|
@@ -12353,17 +13582,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12353
13582
|
return this;
|
|
12354
13583
|
const paths = unifyPaths(paths_);
|
|
12355
13584
|
const { cwd } = this.options;
|
|
12356
|
-
paths.forEach((
|
|
12357
|
-
if (!sp2.isAbsolute(
|
|
13585
|
+
paths.forEach((path27) => {
|
|
13586
|
+
if (!sp2.isAbsolute(path27) && !this._closers.has(path27)) {
|
|
12358
13587
|
if (cwd)
|
|
12359
|
-
|
|
12360
|
-
|
|
13588
|
+
path27 = sp2.join(cwd, path27);
|
|
13589
|
+
path27 = sp2.resolve(path27);
|
|
12361
13590
|
}
|
|
12362
|
-
this._closePath(
|
|
12363
|
-
this._addIgnoredPath(
|
|
12364
|
-
if (this._watched.has(
|
|
13591
|
+
this._closePath(path27);
|
|
13592
|
+
this._addIgnoredPath(path27);
|
|
13593
|
+
if (this._watched.has(path27)) {
|
|
12365
13594
|
this._addIgnoredPath({
|
|
12366
|
-
path:
|
|
13595
|
+
path: path27,
|
|
12367
13596
|
recursive: true
|
|
12368
13597
|
});
|
|
12369
13598
|
}
|
|
@@ -12427,38 +13656,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12427
13656
|
* @param stats arguments to be passed with event
|
|
12428
13657
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
12429
13658
|
*/
|
|
12430
|
-
async _emit(event,
|
|
13659
|
+
async _emit(event, path27, stats) {
|
|
12431
13660
|
if (this.closed)
|
|
12432
13661
|
return;
|
|
12433
13662
|
const opts = this.options;
|
|
12434
13663
|
if (isWindows)
|
|
12435
|
-
|
|
13664
|
+
path27 = sp2.normalize(path27);
|
|
12436
13665
|
if (opts.cwd)
|
|
12437
|
-
|
|
12438
|
-
const args = [
|
|
13666
|
+
path27 = sp2.relative(opts.cwd, path27);
|
|
13667
|
+
const args = [path27];
|
|
12439
13668
|
if (stats != null)
|
|
12440
13669
|
args.push(stats);
|
|
12441
13670
|
const awf = opts.awaitWriteFinish;
|
|
12442
13671
|
let pw;
|
|
12443
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
13672
|
+
if (awf && (pw = this._pendingWrites.get(path27))) {
|
|
12444
13673
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
12445
13674
|
return this;
|
|
12446
13675
|
}
|
|
12447
13676
|
if (opts.atomic) {
|
|
12448
13677
|
if (event === EVENTS.UNLINK) {
|
|
12449
|
-
this._pendingUnlinks.set(
|
|
13678
|
+
this._pendingUnlinks.set(path27, [event, ...args]);
|
|
12450
13679
|
setTimeout(() => {
|
|
12451
|
-
this._pendingUnlinks.forEach((entry,
|
|
13680
|
+
this._pendingUnlinks.forEach((entry, path28) => {
|
|
12452
13681
|
this.emit(...entry);
|
|
12453
13682
|
this.emit(EVENTS.ALL, ...entry);
|
|
12454
|
-
this._pendingUnlinks.delete(
|
|
13683
|
+
this._pendingUnlinks.delete(path28);
|
|
12455
13684
|
});
|
|
12456
13685
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
12457
13686
|
return this;
|
|
12458
13687
|
}
|
|
12459
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
13688
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path27)) {
|
|
12460
13689
|
event = EVENTS.CHANGE;
|
|
12461
|
-
this._pendingUnlinks.delete(
|
|
13690
|
+
this._pendingUnlinks.delete(path27);
|
|
12462
13691
|
}
|
|
12463
13692
|
}
|
|
12464
13693
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -12476,16 +13705,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12476
13705
|
this.emitWithAll(event, args);
|
|
12477
13706
|
}
|
|
12478
13707
|
};
|
|
12479
|
-
this._awaitWriteFinish(
|
|
13708
|
+
this._awaitWriteFinish(path27, awf.stabilityThreshold, event, awfEmit);
|
|
12480
13709
|
return this;
|
|
12481
13710
|
}
|
|
12482
13711
|
if (event === EVENTS.CHANGE) {
|
|
12483
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
13712
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path27, 50);
|
|
12484
13713
|
if (isThrottled)
|
|
12485
13714
|
return this;
|
|
12486
13715
|
}
|
|
12487
13716
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
12488
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
13717
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path27) : path27;
|
|
12489
13718
|
let stats2;
|
|
12490
13719
|
try {
|
|
12491
13720
|
stats2 = await (0, import_promises3.stat)(fullPath);
|
|
@@ -12516,23 +13745,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12516
13745
|
* @param timeout duration of time to suppress duplicate actions
|
|
12517
13746
|
* @returns tracking object or false if action should be suppressed
|
|
12518
13747
|
*/
|
|
12519
|
-
_throttle(actionType,
|
|
13748
|
+
_throttle(actionType, path27, timeout) {
|
|
12520
13749
|
if (!this._throttled.has(actionType)) {
|
|
12521
13750
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
12522
13751
|
}
|
|
12523
13752
|
const action = this._throttled.get(actionType);
|
|
12524
13753
|
if (!action)
|
|
12525
13754
|
throw new Error("invalid throttle");
|
|
12526
|
-
const actionPath = action.get(
|
|
13755
|
+
const actionPath = action.get(path27);
|
|
12527
13756
|
if (actionPath) {
|
|
12528
13757
|
actionPath.count++;
|
|
12529
13758
|
return false;
|
|
12530
13759
|
}
|
|
12531
13760
|
let timeoutObject;
|
|
12532
13761
|
const clear = () => {
|
|
12533
|
-
const item = action.get(
|
|
13762
|
+
const item = action.get(path27);
|
|
12534
13763
|
const count = item ? item.count : 0;
|
|
12535
|
-
action.delete(
|
|
13764
|
+
action.delete(path27);
|
|
12536
13765
|
clearTimeout(timeoutObject);
|
|
12537
13766
|
if (item)
|
|
12538
13767
|
clearTimeout(item.timeoutObject);
|
|
@@ -12540,7 +13769,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12540
13769
|
};
|
|
12541
13770
|
timeoutObject = setTimeout(clear, timeout);
|
|
12542
13771
|
const thr = { timeoutObject, clear, count: 0 };
|
|
12543
|
-
action.set(
|
|
13772
|
+
action.set(path27, thr);
|
|
12544
13773
|
return thr;
|
|
12545
13774
|
}
|
|
12546
13775
|
_incrReadyCount() {
|
|
@@ -12554,44 +13783,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12554
13783
|
* @param event
|
|
12555
13784
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
12556
13785
|
*/
|
|
12557
|
-
_awaitWriteFinish(
|
|
13786
|
+
_awaitWriteFinish(path27, threshold, event, awfEmit) {
|
|
12558
13787
|
const awf = this.options.awaitWriteFinish;
|
|
12559
13788
|
if (typeof awf !== "object")
|
|
12560
13789
|
return;
|
|
12561
13790
|
const pollInterval = awf.pollInterval;
|
|
12562
13791
|
let timeoutHandler;
|
|
12563
|
-
let fullPath =
|
|
12564
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
12565
|
-
fullPath = sp2.join(this.options.cwd,
|
|
13792
|
+
let fullPath = path27;
|
|
13793
|
+
if (this.options.cwd && !sp2.isAbsolute(path27)) {
|
|
13794
|
+
fullPath = sp2.join(this.options.cwd, path27);
|
|
12566
13795
|
}
|
|
12567
13796
|
const now = /* @__PURE__ */ new Date();
|
|
12568
13797
|
const writes = this._pendingWrites;
|
|
12569
13798
|
function awaitWriteFinishFn(prevStat) {
|
|
12570
13799
|
(0, import_node_fs2.stat)(fullPath, (err, curStat) => {
|
|
12571
|
-
if (err || !writes.has(
|
|
13800
|
+
if (err || !writes.has(path27)) {
|
|
12572
13801
|
if (err && err.code !== "ENOENT")
|
|
12573
13802
|
awfEmit(err);
|
|
12574
13803
|
return;
|
|
12575
13804
|
}
|
|
12576
13805
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
12577
13806
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
12578
|
-
writes.get(
|
|
13807
|
+
writes.get(path27).lastChange = now2;
|
|
12579
13808
|
}
|
|
12580
|
-
const pw = writes.get(
|
|
13809
|
+
const pw = writes.get(path27);
|
|
12581
13810
|
const df = now2 - pw.lastChange;
|
|
12582
13811
|
if (df >= threshold) {
|
|
12583
|
-
writes.delete(
|
|
13812
|
+
writes.delete(path27);
|
|
12584
13813
|
awfEmit(void 0, curStat);
|
|
12585
13814
|
} else {
|
|
12586
13815
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
12587
13816
|
}
|
|
12588
13817
|
});
|
|
12589
13818
|
}
|
|
12590
|
-
if (!writes.has(
|
|
12591
|
-
writes.set(
|
|
13819
|
+
if (!writes.has(path27)) {
|
|
13820
|
+
writes.set(path27, {
|
|
12592
13821
|
lastChange: now,
|
|
12593
13822
|
cancelWait: () => {
|
|
12594
|
-
writes.delete(
|
|
13823
|
+
writes.delete(path27);
|
|
12595
13824
|
clearTimeout(timeoutHandler);
|
|
12596
13825
|
return event;
|
|
12597
13826
|
}
|
|
@@ -12602,8 +13831,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12602
13831
|
/**
|
|
12603
13832
|
* Determines whether user has asked to ignore this path.
|
|
12604
13833
|
*/
|
|
12605
|
-
_isIgnored(
|
|
12606
|
-
if (this.options.atomic && DOT_RE.test(
|
|
13834
|
+
_isIgnored(path27, stats) {
|
|
13835
|
+
if (this.options.atomic && DOT_RE.test(path27))
|
|
12607
13836
|
return true;
|
|
12608
13837
|
if (!this._userIgnored) {
|
|
12609
13838
|
const { cwd } = this.options;
|
|
@@ -12613,17 +13842,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12613
13842
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
12614
13843
|
this._userIgnored = anymatch(list, void 0);
|
|
12615
13844
|
}
|
|
12616
|
-
return this._userIgnored(
|
|
13845
|
+
return this._userIgnored(path27, stats);
|
|
12617
13846
|
}
|
|
12618
|
-
_isntIgnored(
|
|
12619
|
-
return !this._isIgnored(
|
|
13847
|
+
_isntIgnored(path27, stat4) {
|
|
13848
|
+
return !this._isIgnored(path27, stat4);
|
|
12620
13849
|
}
|
|
12621
13850
|
/**
|
|
12622
13851
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
12623
13852
|
* @param path file or directory pattern being watched
|
|
12624
13853
|
*/
|
|
12625
|
-
_getWatchHelpers(
|
|
12626
|
-
return new WatchHelper(
|
|
13854
|
+
_getWatchHelpers(path27) {
|
|
13855
|
+
return new WatchHelper(path27, this.options.followSymlinks, this);
|
|
12627
13856
|
}
|
|
12628
13857
|
// Directory helpers
|
|
12629
13858
|
// -----------------
|
|
@@ -12655,63 +13884,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12655
13884
|
* @param item base path of item/directory
|
|
12656
13885
|
*/
|
|
12657
13886
|
_remove(directory, item, isDirectory) {
|
|
12658
|
-
const
|
|
12659
|
-
const fullPath = sp2.resolve(
|
|
12660
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
12661
|
-
if (!this._throttle("remove",
|
|
13887
|
+
const path27 = sp2.join(directory, item);
|
|
13888
|
+
const fullPath = sp2.resolve(path27);
|
|
13889
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path27) || this._watched.has(fullPath);
|
|
13890
|
+
if (!this._throttle("remove", path27, 100))
|
|
12662
13891
|
return;
|
|
12663
13892
|
if (!isDirectory && this._watched.size === 1) {
|
|
12664
13893
|
this.add(directory, item, true);
|
|
12665
13894
|
}
|
|
12666
|
-
const wp = this._getWatchedDir(
|
|
13895
|
+
const wp = this._getWatchedDir(path27);
|
|
12667
13896
|
const nestedDirectoryChildren = wp.getChildren();
|
|
12668
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
13897
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path27, nested));
|
|
12669
13898
|
const parent = this._getWatchedDir(directory);
|
|
12670
13899
|
const wasTracked = parent.has(item);
|
|
12671
13900
|
parent.remove(item);
|
|
12672
13901
|
if (this._symlinkPaths.has(fullPath)) {
|
|
12673
13902
|
this._symlinkPaths.delete(fullPath);
|
|
12674
13903
|
}
|
|
12675
|
-
let relPath =
|
|
13904
|
+
let relPath = path27;
|
|
12676
13905
|
if (this.options.cwd)
|
|
12677
|
-
relPath = sp2.relative(this.options.cwd,
|
|
13906
|
+
relPath = sp2.relative(this.options.cwd, path27);
|
|
12678
13907
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
12679
13908
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
12680
13909
|
if (event === EVENTS.ADD)
|
|
12681
13910
|
return;
|
|
12682
13911
|
}
|
|
12683
|
-
this._watched.delete(
|
|
13912
|
+
this._watched.delete(path27);
|
|
12684
13913
|
this._watched.delete(fullPath);
|
|
12685
13914
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
12686
|
-
if (wasTracked && !this._isIgnored(
|
|
12687
|
-
this._emit(eventName,
|
|
12688
|
-
this._closePath(
|
|
13915
|
+
if (wasTracked && !this._isIgnored(path27))
|
|
13916
|
+
this._emit(eventName, path27);
|
|
13917
|
+
this._closePath(path27);
|
|
12689
13918
|
}
|
|
12690
13919
|
/**
|
|
12691
13920
|
* Closes all watchers for a path
|
|
12692
13921
|
*/
|
|
12693
|
-
_closePath(
|
|
12694
|
-
this._closeFile(
|
|
12695
|
-
const dir = sp2.dirname(
|
|
12696
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
13922
|
+
_closePath(path27) {
|
|
13923
|
+
this._closeFile(path27);
|
|
13924
|
+
const dir = sp2.dirname(path27);
|
|
13925
|
+
this._getWatchedDir(dir).remove(sp2.basename(path27));
|
|
12697
13926
|
}
|
|
12698
13927
|
/**
|
|
12699
13928
|
* Closes only file-specific watchers
|
|
12700
13929
|
*/
|
|
12701
|
-
_closeFile(
|
|
12702
|
-
const closers = this._closers.get(
|
|
13930
|
+
_closeFile(path27) {
|
|
13931
|
+
const closers = this._closers.get(path27);
|
|
12703
13932
|
if (!closers)
|
|
12704
13933
|
return;
|
|
12705
13934
|
closers.forEach((closer) => closer());
|
|
12706
|
-
this._closers.delete(
|
|
13935
|
+
this._closers.delete(path27);
|
|
12707
13936
|
}
|
|
12708
|
-
_addPathCloser(
|
|
13937
|
+
_addPathCloser(path27, closer) {
|
|
12709
13938
|
if (!closer)
|
|
12710
13939
|
return;
|
|
12711
|
-
let list = this._closers.get(
|
|
13940
|
+
let list = this._closers.get(path27);
|
|
12712
13941
|
if (!list) {
|
|
12713
13942
|
list = [];
|
|
12714
|
-
this._closers.set(
|
|
13943
|
+
this._closers.set(path27, list);
|
|
12715
13944
|
}
|
|
12716
13945
|
list.push(closer);
|
|
12717
13946
|
}
|
|
@@ -12741,7 +13970,7 @@ function watch(paths, options = {}) {
|
|
|
12741
13970
|
var chokidar_default = { watch, FSWatcher };
|
|
12742
13971
|
|
|
12743
13972
|
// src/watcher/file-watcher.ts
|
|
12744
|
-
var
|
|
13973
|
+
var path22 = __toESM(require("path"), 1);
|
|
12745
13974
|
var FileWatcher = class {
|
|
12746
13975
|
watcher = null;
|
|
12747
13976
|
projectRoot;
|
|
@@ -12752,6 +13981,8 @@ var FileWatcher = class {
|
|
|
12752
13981
|
debounceTimer = null;
|
|
12753
13982
|
debounceMs = 1e3;
|
|
12754
13983
|
onChanges = null;
|
|
13984
|
+
readyPromise = null;
|
|
13985
|
+
resolveReady = null;
|
|
12755
13986
|
constructor(projectRoot, config, host = "opencode", options = {}) {
|
|
12756
13987
|
this.projectRoot = projectRoot;
|
|
12757
13988
|
this.config = config;
|
|
@@ -12767,15 +13998,15 @@ var FileWatcher = class {
|
|
|
12767
13998
|
const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
|
|
12768
13999
|
this.watcher = chokidar_default.watch(watchTargets, {
|
|
12769
14000
|
ignored: (filePath) => {
|
|
12770
|
-
const relativePath =
|
|
14001
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
12771
14002
|
if (!relativePath) return false;
|
|
12772
14003
|
if (this.isProjectConfigPathOrAncestor(relativePath)) {
|
|
12773
14004
|
return false;
|
|
12774
14005
|
}
|
|
12775
|
-
if (hasFilteredPathSegment(relativePath,
|
|
14006
|
+
if (hasFilteredPathSegment(relativePath, path22.sep)) {
|
|
12776
14007
|
return true;
|
|
12777
14008
|
}
|
|
12778
|
-
if (isRestrictedDirectory(relativePath,
|
|
14009
|
+
if (isRestrictedDirectory(relativePath, path22.sep)) {
|
|
12779
14010
|
return true;
|
|
12780
14011
|
}
|
|
12781
14012
|
if (ignoreFilter.ignores(relativePath)) {
|
|
@@ -12790,6 +14021,13 @@ var FileWatcher = class {
|
|
|
12790
14021
|
pollInterval: 100
|
|
12791
14022
|
}
|
|
12792
14023
|
});
|
|
14024
|
+
this.readyPromise = new Promise((resolve15) => {
|
|
14025
|
+
this.resolveReady = resolve15;
|
|
14026
|
+
});
|
|
14027
|
+
this.watcher.once("ready", () => {
|
|
14028
|
+
this.resolveReady?.();
|
|
14029
|
+
this.resolveReady = null;
|
|
14030
|
+
});
|
|
12793
14031
|
this.watcher.on("error", (error) => {
|
|
12794
14032
|
const err = error instanceof Error ? error : null;
|
|
12795
14033
|
if (err?.code === "EPERM" || err?.code === "EACCES") {
|
|
@@ -12821,24 +14059,24 @@ var FileWatcher = class {
|
|
|
12821
14059
|
this.scheduleFlush();
|
|
12822
14060
|
}
|
|
12823
14061
|
isProjectConfigPath(filePath) {
|
|
12824
|
-
const relativePath =
|
|
12825
|
-
const normalizedRelativePath =
|
|
14062
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
14063
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
12826
14064
|
return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
|
|
12827
14065
|
}
|
|
12828
14066
|
isProjectConfigPathOrAncestor(relativePath) {
|
|
12829
|
-
const normalizedRelativePath =
|
|
14067
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
12830
14068
|
return this.getProjectConfigRelativePaths().some(
|
|
12831
|
-
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${
|
|
14069
|
+
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path22.sep}`)
|
|
12832
14070
|
);
|
|
12833
14071
|
}
|
|
12834
14072
|
getProjectConfigRelativePaths() {
|
|
12835
14073
|
if (this.configPath) {
|
|
12836
|
-
return [
|
|
14074
|
+
return [path22.normalize(path22.relative(this.projectRoot, this.configPath))];
|
|
12837
14075
|
}
|
|
12838
14076
|
return [
|
|
12839
14077
|
resolveProjectConfigPath(this.projectRoot, this.host),
|
|
12840
14078
|
resolveWritableProjectConfigPath(this.projectRoot, this.host)
|
|
12841
|
-
].map((configPath) =>
|
|
14079
|
+
].map((configPath) => path22.normalize(path22.relative(this.projectRoot, configPath)));
|
|
12842
14080
|
}
|
|
12843
14081
|
scheduleFlush() {
|
|
12844
14082
|
if (this.debounceTimer) {
|
|
@@ -12853,7 +14091,7 @@ var FileWatcher = class {
|
|
|
12853
14091
|
return;
|
|
12854
14092
|
}
|
|
12855
14093
|
const changes = Array.from(this.pendingChanges.entries()).map(
|
|
12856
|
-
([
|
|
14094
|
+
([path27, type]) => ({ path: path27, type })
|
|
12857
14095
|
);
|
|
12858
14096
|
this.pendingChanges.clear();
|
|
12859
14097
|
try {
|
|
@@ -12862,25 +14100,32 @@ var FileWatcher = class {
|
|
|
12862
14100
|
console.error("Error handling file changes:", error);
|
|
12863
14101
|
}
|
|
12864
14102
|
}
|
|
12865
|
-
stop() {
|
|
14103
|
+
async stop() {
|
|
12866
14104
|
if (this.debounceTimer) {
|
|
12867
14105
|
clearTimeout(this.debounceTimer);
|
|
12868
14106
|
this.debounceTimer = null;
|
|
12869
14107
|
}
|
|
12870
14108
|
if (this.watcher) {
|
|
12871
|
-
this.watcher
|
|
14109
|
+
const watcher = this.watcher;
|
|
12872
14110
|
this.watcher = null;
|
|
14111
|
+
await watcher.close();
|
|
12873
14112
|
}
|
|
14113
|
+
this.resolveReady?.();
|
|
14114
|
+
this.resolveReady = null;
|
|
14115
|
+
this.readyPromise = null;
|
|
12874
14116
|
this.pendingChanges.clear();
|
|
12875
14117
|
this.onChanges = null;
|
|
12876
14118
|
}
|
|
12877
14119
|
isRunning() {
|
|
12878
14120
|
return this.watcher !== null;
|
|
12879
14121
|
}
|
|
14122
|
+
async waitUntilReady() {
|
|
14123
|
+
await (this.readyPromise ?? Promise.resolve());
|
|
14124
|
+
}
|
|
12880
14125
|
};
|
|
12881
14126
|
|
|
12882
14127
|
// src/watcher/git-head-watcher.ts
|
|
12883
|
-
var
|
|
14128
|
+
var path23 = __toESM(require("path"), 1);
|
|
12884
14129
|
var GitHeadWatcher = class {
|
|
12885
14130
|
watcher = null;
|
|
12886
14131
|
projectRoot;
|
|
@@ -12902,7 +14147,7 @@ var GitHeadWatcher = class {
|
|
|
12902
14147
|
this.onBranchChange = handler;
|
|
12903
14148
|
this.currentBranch = getCurrentBranch(this.projectRoot);
|
|
12904
14149
|
const headPath = getHeadPath(this.projectRoot);
|
|
12905
|
-
const refsPath =
|
|
14150
|
+
const refsPath = path23.join(this.projectRoot, ".git", "refs", "heads");
|
|
12906
14151
|
this.watcher = chokidar_default.watch([headPath, refsPath], {
|
|
12907
14152
|
persistent: true,
|
|
12908
14153
|
ignoreInitial: true,
|
|
@@ -12939,14 +14184,15 @@ var GitHeadWatcher = class {
|
|
|
12939
14184
|
getCurrentBranch() {
|
|
12940
14185
|
return this.currentBranch;
|
|
12941
14186
|
}
|
|
12942
|
-
stop() {
|
|
14187
|
+
async stop() {
|
|
12943
14188
|
if (this.debounceTimer) {
|
|
12944
14189
|
clearTimeout(this.debounceTimer);
|
|
12945
14190
|
this.debounceTimer = null;
|
|
12946
14191
|
}
|
|
12947
14192
|
if (this.watcher) {
|
|
12948
|
-
this.watcher
|
|
14193
|
+
const watcher = this.watcher;
|
|
12949
14194
|
this.watcher = null;
|
|
14195
|
+
await watcher.close();
|
|
12950
14196
|
}
|
|
12951
14197
|
this.onBranchChange = null;
|
|
12952
14198
|
}
|
|
@@ -12964,6 +14210,8 @@ var BackgroundReindexer = class {
|
|
|
12964
14210
|
running = false;
|
|
12965
14211
|
pending = false;
|
|
12966
14212
|
stopped = false;
|
|
14213
|
+
retryTimer = null;
|
|
14214
|
+
retryDelayMs = 50;
|
|
12967
14215
|
request() {
|
|
12968
14216
|
if (this.stopped) {
|
|
12969
14217
|
return;
|
|
@@ -12974,9 +14222,13 @@ var BackgroundReindexer = class {
|
|
|
12974
14222
|
stop() {
|
|
12975
14223
|
this.stopped = true;
|
|
12976
14224
|
this.pending = false;
|
|
14225
|
+
if (this.retryTimer) {
|
|
14226
|
+
clearTimeout(this.retryTimer);
|
|
14227
|
+
this.retryTimer = null;
|
|
14228
|
+
}
|
|
12977
14229
|
}
|
|
12978
14230
|
drain() {
|
|
12979
|
-
if (this.stopped || this.running || !this.pending) {
|
|
14231
|
+
if (this.stopped || this.running || this.retryTimer || !this.pending) {
|
|
12980
14232
|
return;
|
|
12981
14233
|
}
|
|
12982
14234
|
this.pending = false;
|
|
@@ -12984,13 +14236,29 @@ var BackgroundReindexer = class {
|
|
|
12984
14236
|
void this.run();
|
|
12985
14237
|
}
|
|
12986
14238
|
async run() {
|
|
14239
|
+
let retryAfterContention = false;
|
|
12987
14240
|
try {
|
|
12988
14241
|
await this.runIndex();
|
|
14242
|
+
this.retryDelayMs = 50;
|
|
12989
14243
|
} catch (error) {
|
|
12990
|
-
|
|
14244
|
+
if (isTransientIndexLockContention(error)) {
|
|
14245
|
+
this.pending = true;
|
|
14246
|
+
retryAfterContention = true;
|
|
14247
|
+
} else {
|
|
14248
|
+
console.error("[codebase-index] Background reindex failed:", error);
|
|
14249
|
+
}
|
|
12991
14250
|
} finally {
|
|
12992
14251
|
this.running = false;
|
|
12993
|
-
this.
|
|
14252
|
+
if (retryAfterContention && !this.stopped) {
|
|
14253
|
+
const delay = this.retryDelayMs;
|
|
14254
|
+
this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
|
|
14255
|
+
this.retryTimer = setTimeout(() => {
|
|
14256
|
+
this.retryTimer = null;
|
|
14257
|
+
this.drain();
|
|
14258
|
+
}, delay);
|
|
14259
|
+
} else {
|
|
14260
|
+
this.drain();
|
|
14261
|
+
}
|
|
12994
14262
|
}
|
|
12995
14263
|
}
|
|
12996
14264
|
};
|
|
@@ -13024,10 +14292,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
|
|
|
13024
14292
|
return {
|
|
13025
14293
|
fileWatcher,
|
|
13026
14294
|
gitWatcher,
|
|
13027
|
-
|
|
14295
|
+
whenReady() {
|
|
14296
|
+
return fileWatcher.waitUntilReady();
|
|
14297
|
+
},
|
|
14298
|
+
async stop() {
|
|
13028
14299
|
backgroundReindexer.stop();
|
|
13029
|
-
fileWatcher.stop();
|
|
13030
|
-
gitWatcher?.stop();
|
|
14300
|
+
await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
|
|
13031
14301
|
}
|
|
13032
14302
|
};
|
|
13033
14303
|
}
|
|
@@ -13045,8 +14315,8 @@ function getConfigPaths(projectRoot, host, options) {
|
|
|
13045
14315
|
}
|
|
13046
14316
|
|
|
13047
14317
|
// src/tools/visualize/activity.ts
|
|
13048
|
-
var
|
|
13049
|
-
var
|
|
14318
|
+
var import_child_process4 = require("child_process");
|
|
14319
|
+
var path24 = __toESM(require("path"), 1);
|
|
13050
14320
|
function attachRecentActivity(data, projectRoot) {
|
|
13051
14321
|
const activity = readGitActivity(projectRoot);
|
|
13052
14322
|
const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
|
|
@@ -13057,7 +14327,7 @@ function attachRecentActivity(data, projectRoot) {
|
|
|
13057
14327
|
}
|
|
13058
14328
|
function readGitActivity(projectRoot) {
|
|
13059
14329
|
try {
|
|
13060
|
-
const output = (0,
|
|
14330
|
+
const output = (0, import_child_process4.execFileSync)(
|
|
13061
14331
|
"git",
|
|
13062
14332
|
["-C", projectRoot, "log", "--since=90.days", "--numstat", "--date=short", "--pretty=format:__COMMIT__%x09%h%x09%ad%x09%s"],
|
|
13063
14333
|
{ encoding: "utf8", maxBuffer: 8 * 1024 * 1024, stdio: ["ignore", "pipe", "ignore"] }
|
|
@@ -13208,7 +14478,7 @@ function normalizePath3(filePath) {
|
|
|
13208
14478
|
return filePath.replace(/\\/g, "/");
|
|
13209
14479
|
}
|
|
13210
14480
|
function toGitRelativePath(projectRoot, filePath) {
|
|
13211
|
-
const relativePath =
|
|
14481
|
+
const relativePath = path24.isAbsolute(filePath) ? path24.relative(projectRoot, filePath) : filePath;
|
|
13212
14482
|
return normalizePath3(relativePath);
|
|
13213
14483
|
}
|
|
13214
14484
|
|
|
@@ -13466,7 +14736,7 @@ render();
|
|
|
13466
14736
|
}
|
|
13467
14737
|
|
|
13468
14738
|
// src/tools/visualize/transform.ts
|
|
13469
|
-
var
|
|
14739
|
+
var path25 = __toESM(require("path"), 1);
|
|
13470
14740
|
|
|
13471
14741
|
// src/tools/visualize/modules.ts
|
|
13472
14742
|
var MAX_MODULES = 18;
|
|
@@ -13599,8 +14869,8 @@ function compactModules(prefixToNodes) {
|
|
|
13599
14869
|
function deriveModules(nodes) {
|
|
13600
14870
|
const initial = /* @__PURE__ */ new Map();
|
|
13601
14871
|
for (const node of nodes) {
|
|
13602
|
-
const
|
|
13603
|
-
const prefix = modulePrefixFromRelativePath(
|
|
14872
|
+
const relative11 = stripToProjectRelative(node.filePath);
|
|
14873
|
+
const prefix = modulePrefixFromRelativePath(relative11);
|
|
13604
14874
|
if (!initial.has(prefix)) initial.set(prefix, []);
|
|
13605
14875
|
initial.get(prefix)?.push(node);
|
|
13606
14876
|
}
|
|
@@ -13726,7 +14996,7 @@ function transformForVisualization(symbols, edges, options = {}) {
|
|
|
13726
14996
|
filePath: s.filePath,
|
|
13727
14997
|
kind: s.kind,
|
|
13728
14998
|
line: s.startLine,
|
|
13729
|
-
directory:
|
|
14999
|
+
directory: path25.dirname(s.filePath),
|
|
13730
15000
|
moduleId: "",
|
|
13731
15001
|
moduleLabel: ""
|
|
13732
15002
|
}));
|
|
@@ -13755,9 +15025,9 @@ function parseArgs(argv) {
|
|
|
13755
15025
|
let host = "opencode";
|
|
13756
15026
|
for (let i = 2; i < argv.length; i++) {
|
|
13757
15027
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
13758
|
-
project =
|
|
15028
|
+
project = path26.resolve(argv[++i]);
|
|
13759
15029
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
13760
|
-
config =
|
|
15030
|
+
config = path26.resolve(argv[++i]);
|
|
13761
15031
|
} else if (argv[i] === "--host" && argv[i + 1]) {
|
|
13762
15032
|
host = parseHostMode(argv[++i]);
|
|
13763
15033
|
} else if (argv[i] === "--host") {
|
|
@@ -13770,7 +15040,7 @@ function loadCliRawConfig(args) {
|
|
|
13770
15040
|
return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
|
|
13771
15041
|
}
|
|
13772
15042
|
function isCliEntrypoint(moduleUrl, argvPath) {
|
|
13773
|
-
return argvPath !== void 0 && (0,
|
|
15043
|
+
return argvPath !== void 0 && (0, import_fs16.realpathSync)((0, import_url2.fileURLToPath)(moduleUrl)) === (0, import_fs16.realpathSync)(argvPath);
|
|
13774
15044
|
}
|
|
13775
15045
|
function parseVisualizeArgs(argv, cwd) {
|
|
13776
15046
|
let project = cwd;
|
|
@@ -13780,7 +15050,7 @@ function parseVisualizeArgs(argv, cwd) {
|
|
|
13780
15050
|
for (let i = 0; i < argv.length; i++) {
|
|
13781
15051
|
const arg = argv[i];
|
|
13782
15052
|
if (arg === "--project" && argv[i + 1]) {
|
|
13783
|
-
project =
|
|
15053
|
+
project = path26.resolve(argv[++i]);
|
|
13784
15054
|
} else if (arg === "--max" && argv[i + 1]) {
|
|
13785
15055
|
maxNodes = Number(argv[++i]);
|
|
13786
15056
|
} else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
|
|
@@ -13817,8 +15087,8 @@ async function handleVisualizeCommand(argv, cwd) {
|
|
|
13817
15087
|
console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
|
|
13818
15088
|
return 1;
|
|
13819
15089
|
}
|
|
13820
|
-
const outputPath =
|
|
13821
|
-
(0,
|
|
15090
|
+
const outputPath = path26.join(os6.tmpdir(), `call-graph-${Date.now()}.html`);
|
|
15091
|
+
(0, import_fs16.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
|
|
13822
15092
|
console.log(`Temporal call graph visualization generated: ${outputPath}`);
|
|
13823
15093
|
console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
|
|
13824
15094
|
console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);
|
|
@@ -13847,7 +15117,7 @@ async function main() {
|
|
|
13847
15117
|
const transport = new import_stdio.StdioServerTransport();
|
|
13848
15118
|
await server.connect(transport);
|
|
13849
15119
|
let watcher = null;
|
|
13850
|
-
const isHomeDir =
|
|
15120
|
+
const isHomeDir = path26.resolve(args.project) === path26.resolve(os6.homedir());
|
|
13851
15121
|
const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
|
|
13852
15122
|
if (config.indexing.autoIndex && isValidProject) {
|
|
13853
15123
|
const indexer = getIndexerForProject(args.project, args.host);
|
|
@@ -13862,14 +15132,25 @@ async function main() {
|
|
|
13862
15132
|
args.config ? { configPath: args.config } : {}
|
|
13863
15133
|
);
|
|
13864
15134
|
}
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
15135
|
+
let shuttingDown = false;
|
|
15136
|
+
const shutdown = async () => {
|
|
15137
|
+
if (shuttingDown) return;
|
|
15138
|
+
shuttingDown = true;
|
|
15139
|
+
try {
|
|
15140
|
+
await watcher?.stop();
|
|
15141
|
+
await server.close();
|
|
15142
|
+
process.exit(0);
|
|
15143
|
+
} catch (error) {
|
|
15144
|
+
console.error("Failed to stop MCP server cleanly:", error);
|
|
15145
|
+
process.exit(1);
|
|
15146
|
+
}
|
|
13870
15147
|
};
|
|
13871
|
-
process.on("SIGINT",
|
|
13872
|
-
|
|
15148
|
+
process.on("SIGINT", () => {
|
|
15149
|
+
void shutdown();
|
|
15150
|
+
});
|
|
15151
|
+
process.on("SIGTERM", () => {
|
|
15152
|
+
void shutdown();
|
|
15153
|
+
});
|
|
13873
15154
|
}
|
|
13874
15155
|
function handleMainError(error) {
|
|
13875
15156
|
if (error instanceof Error && error.message.startsWith("Invalid host mode")) {
|