opencode-codebase-index 0.14.0 → 0.16.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/README.md +73 -12
- package/dist/cli.cjs +1782 -607
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1781 -596
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1610 -533
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1606 -519
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +1396 -311
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +1395 -300
- package/dist/pi-extension.js.map +1 -1
- package/hooks/hooks.json +2 -2
- 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/skills/codebase-search/SKILL.md +9 -7
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}",
|
|
@@ -1058,7 +1058,7 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
|
|
|
1058
1058
|
);
|
|
1059
1059
|
|
|
1060
1060
|
// src/config/host.ts
|
|
1061
|
-
var HOST_MODES = ["opencode", "codex", "claude", "pi"];
|
|
1061
|
+
var HOST_MODES = ["opencode", "codex", "claude", "pi", "jcode"];
|
|
1062
1062
|
function isSupportedHostMode(value) {
|
|
1063
1063
|
return HOST_MODES.includes(value);
|
|
1064
1064
|
}
|
|
@@ -1113,9 +1113,9 @@ var import_fs = require("fs");
|
|
|
1113
1113
|
var path = __toESM(require("path"), 1);
|
|
1114
1114
|
|
|
1115
1115
|
// src/eval/report-formatters.ts
|
|
1116
|
-
function assertFiniteNumber(value,
|
|
1116
|
+
function assertFiniteNumber(value, path27) {
|
|
1117
1117
|
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1118
|
-
throw new Error(`${
|
|
1118
|
+
throw new Error(`${path27} must be a finite number`);
|
|
1119
1119
|
}
|
|
1120
1120
|
return value;
|
|
1121
1121
|
}
|
|
@@ -1303,13 +1303,13 @@ function buildPerQueryArtifact(perQuery) {
|
|
|
1303
1303
|
}
|
|
1304
1304
|
|
|
1305
1305
|
// src/eval/runner.ts
|
|
1306
|
-
var
|
|
1307
|
-
var
|
|
1306
|
+
var import_fs11 = require("fs");
|
|
1307
|
+
var path15 = __toESM(require("path"), 1);
|
|
1308
1308
|
var import_perf_hooks2 = require("perf_hooks");
|
|
1309
1309
|
|
|
1310
1310
|
// src/indexer/index.ts
|
|
1311
|
-
var
|
|
1312
|
-
var
|
|
1311
|
+
var import_fs8 = require("fs");
|
|
1312
|
+
var path12 = __toESM(require("path"), 1);
|
|
1313
1313
|
var import_perf_hooks = require("perf_hooks");
|
|
1314
1314
|
var import_child_process3 = require("child_process");
|
|
1315
1315
|
var import_util3 = require("util");
|
|
@@ -2591,17 +2591,17 @@ function validateExternalUrl(urlString) {
|
|
|
2591
2591
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2592
2592
|
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2593
2593
|
}
|
|
2594
|
-
const
|
|
2595
|
-
if (BLOCKED_HOSTNAMES.has(
|
|
2596
|
-
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})` };
|
|
2597
2597
|
}
|
|
2598
2598
|
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2599
|
-
if (pattern.test(
|
|
2600
|
-
return { valid: false, reason: `Blocked: cloud metadata IP (${
|
|
2599
|
+
if (pattern.test(hostname2)) {
|
|
2600
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
|
|
2601
2601
|
}
|
|
2602
2602
|
}
|
|
2603
|
-
if (/^169\.254\./.test(
|
|
2604
|
-
return { valid: false, reason: `Blocked: link-local address (${
|
|
2603
|
+
if (/^169\.254\./.test(hostname2)) {
|
|
2604
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
|
|
2605
2605
|
}
|
|
2606
2606
|
return { valid: true };
|
|
2607
2607
|
}
|
|
@@ -3792,6 +3792,12 @@ function createMockNativeBinding() {
|
|
|
3792
3792
|
constructor() {
|
|
3793
3793
|
throw error;
|
|
3794
3794
|
}
|
|
3795
|
+
static openReadOnly() {
|
|
3796
|
+
throw error;
|
|
3797
|
+
}
|
|
3798
|
+
static createEmptyReadOnly() {
|
|
3799
|
+
throw error;
|
|
3800
|
+
}
|
|
3795
3801
|
close() {
|
|
3796
3802
|
throw error;
|
|
3797
3803
|
}
|
|
@@ -3895,6 +3901,12 @@ var VectorStore = class {
|
|
|
3895
3901
|
load() {
|
|
3896
3902
|
this.inner.load();
|
|
3897
3903
|
}
|
|
3904
|
+
loadStrict() {
|
|
3905
|
+
this.inner.loadStrict();
|
|
3906
|
+
}
|
|
3907
|
+
hasFingerprint() {
|
|
3908
|
+
return this.inner.hasFingerprint();
|
|
3909
|
+
}
|
|
3898
3910
|
count() {
|
|
3899
3911
|
return this.inner.count();
|
|
3900
3912
|
}
|
|
@@ -4177,12 +4189,24 @@ var InvertedIndex = class {
|
|
|
4177
4189
|
return this.inner.documentCount();
|
|
4178
4190
|
}
|
|
4179
4191
|
};
|
|
4180
|
-
var Database = class {
|
|
4192
|
+
var Database = class _Database {
|
|
4181
4193
|
inner;
|
|
4182
4194
|
closed = false;
|
|
4183
4195
|
constructor(dbPath) {
|
|
4184
4196
|
this.inner = new native.Database(dbPath);
|
|
4185
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
|
+
}
|
|
4186
4210
|
throwIfClosed() {
|
|
4187
4211
|
if (this.closed) {
|
|
4188
4212
|
throw new Error("Database is closed");
|
|
@@ -4651,6 +4675,9 @@ function getHostProjectIndexRelativePath(host) {
|
|
|
4651
4675
|
function hasHostProjectConfig(projectRoot, host) {
|
|
4652
4676
|
return (0, import_fs6.existsSync)(path8.join(projectRoot, getProjectConfigRelativePath(host)));
|
|
4653
4677
|
}
|
|
4678
|
+
function hasHostGlobalConfig(host) {
|
|
4679
|
+
return (0, import_fs6.existsSync)(getGlobalConfigPath(host));
|
|
4680
|
+
}
|
|
4654
4681
|
function getGlobalIndexPath(host = "opencode") {
|
|
4655
4682
|
switch (host) {
|
|
4656
4683
|
case "opencode":
|
|
@@ -4690,6 +4717,9 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
4690
4717
|
return hostIndexPath;
|
|
4691
4718
|
}
|
|
4692
4719
|
if (host !== "opencode") {
|
|
4720
|
+
if (hasHostGlobalConfig(host)) {
|
|
4721
|
+
return hostIndexPath;
|
|
4722
|
+
}
|
|
4693
4723
|
const legacyIndexPath = getGlobalIndexPath("opencode");
|
|
4694
4724
|
if ((0, import_fs6.existsSync)(legacyIndexPath)) {
|
|
4695
4725
|
return legacyIndexPath;
|
|
@@ -4906,6 +4936,422 @@ async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
|
|
|
4906
4936
|
}
|
|
4907
4937
|
}
|
|
4908
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
|
+
|
|
4909
5355
|
// src/indexer/index.ts
|
|
4910
5356
|
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
|
|
4911
5357
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
@@ -4987,6 +5433,7 @@ function isSqliteCorruptionError(error) {
|
|
|
4987
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");
|
|
4988
5434
|
}
|
|
4989
5435
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
5436
|
+
var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
|
|
4990
5437
|
function metadataFromBlame(blame) {
|
|
4991
5438
|
if (!blame) {
|
|
4992
5439
|
return {};
|
|
@@ -5184,9 +5631,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
5184
5631
|
return true;
|
|
5185
5632
|
}
|
|
5186
5633
|
function isPathWithinRoot(filePath, rootPath) {
|
|
5187
|
-
const normalizedFilePath =
|
|
5188
|
-
const normalizedRoot =
|
|
5189
|
-
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}`);
|
|
5190
5637
|
}
|
|
5191
5638
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
5192
5639
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -6252,26 +6699,133 @@ var Indexer = class {
|
|
|
6252
6699
|
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
6253
6700
|
querySimilarityThreshold = 0.85;
|
|
6254
6701
|
indexCompatibility = null;
|
|
6255
|
-
|
|
6702
|
+
activeIndexLease = null;
|
|
6703
|
+
initializationPromise = null;
|
|
6704
|
+
initializationMode = "none";
|
|
6705
|
+
readIssues = [];
|
|
6706
|
+
retiredDatabases = [];
|
|
6707
|
+
readerArtifactFingerprint = null;
|
|
6708
|
+
writerArtifactFingerprint = null;
|
|
6709
|
+
readerArtifactRetryAfter = /* @__PURE__ */ new Map();
|
|
6256
6710
|
constructor(projectRoot, config, host = "opencode") {
|
|
6257
6711
|
this.projectRoot = projectRoot;
|
|
6258
6712
|
this.config = config;
|
|
6259
6713
|
this.host = host;
|
|
6260
6714
|
this.indexPath = this.getIndexPath();
|
|
6261
|
-
this.fileHashCachePath =
|
|
6262
|
-
this.failedBatchesPath =
|
|
6263
|
-
this.indexingLockPath = path11.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");
|
|
6264
6717
|
this.logger = initializeLogger(config.debug);
|
|
6265
6718
|
}
|
|
6266
6719
|
getIndexPath() {
|
|
6267
6720
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
|
|
6268
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
|
+
}
|
|
6269
6823
|
loadFileHashCache() {
|
|
6270
|
-
if (!(0,
|
|
6824
|
+
if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
|
|
6271
6825
|
return;
|
|
6272
6826
|
}
|
|
6273
6827
|
try {
|
|
6274
|
-
const data = (0,
|
|
6828
|
+
const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
|
|
6275
6829
|
const parsed = JSON.parse(data);
|
|
6276
6830
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
6277
6831
|
} catch (error) {
|
|
@@ -6291,15 +6845,26 @@ var Indexer = class {
|
|
|
6291
6845
|
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
6292
6846
|
}
|
|
6293
6847
|
atomicWriteSync(targetPath, data) {
|
|
6294
|
-
const
|
|
6295
|
-
(
|
|
6296
|
-
(0,
|
|
6297
|
-
|
|
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
|
+
);
|
|
6298
6863
|
}
|
|
6299
6864
|
getScopedRoots() {
|
|
6300
|
-
const roots = /* @__PURE__ */ new Set([
|
|
6865
|
+
const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
|
|
6301
6866
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
6302
|
-
roots.add(
|
|
6867
|
+
roots.add(path12.resolve(this.projectRoot, kbRoot));
|
|
6303
6868
|
}
|
|
6304
6869
|
return Array.from(roots);
|
|
6305
6870
|
}
|
|
@@ -6308,29 +6873,29 @@ var Indexer = class {
|
|
|
6308
6873
|
if (this.config.scope !== "global") {
|
|
6309
6874
|
return branchName;
|
|
6310
6875
|
}
|
|
6311
|
-
const projectHash = hashContent(
|
|
6876
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6312
6877
|
return `${projectHash}:${branchName}`;
|
|
6313
6878
|
}
|
|
6314
6879
|
getBranchCatalogKeyFor(branchName) {
|
|
6315
6880
|
if (this.config.scope !== "global") {
|
|
6316
6881
|
return branchName;
|
|
6317
6882
|
}
|
|
6318
|
-
const projectHash = hashContent(
|
|
6883
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6319
6884
|
return `${projectHash}:${branchName}`;
|
|
6320
6885
|
}
|
|
6321
6886
|
getLegacyBranchCatalogKey() {
|
|
6322
6887
|
return this.currentBranch || "default";
|
|
6323
6888
|
}
|
|
6324
6889
|
getLegacyMigrationMetadataKey() {
|
|
6325
|
-
const projectHash = hashContent(
|
|
6890
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6326
6891
|
return `index.globalBranchMigration.${projectHash}`;
|
|
6327
6892
|
}
|
|
6328
6893
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
6329
|
-
const projectHash = hashContent(
|
|
6894
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6330
6895
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
6331
6896
|
}
|
|
6332
6897
|
getProjectForceReembedMetadataKey() {
|
|
6333
|
-
const projectHash = hashContent(
|
|
6898
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6334
6899
|
return `index.forceReembed.${projectHash}`;
|
|
6335
6900
|
}
|
|
6336
6901
|
hasProjectForceReembedPending() {
|
|
@@ -6424,7 +6989,7 @@ var Indexer = class {
|
|
|
6424
6989
|
if (!this.database) {
|
|
6425
6990
|
return { chunkIds, symbolIds };
|
|
6426
6991
|
}
|
|
6427
|
-
const projectRootPath =
|
|
6992
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6428
6993
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6429
6994
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6430
6995
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6447,7 +7012,7 @@ var Indexer = class {
|
|
|
6447
7012
|
if (this.config.scope !== "global") {
|
|
6448
7013
|
return this.getBranchCatalogCleanupKeys();
|
|
6449
7014
|
}
|
|
6450
|
-
const projectHash = hashContent(
|
|
7015
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6451
7016
|
const keys = /* @__PURE__ */ new Set();
|
|
6452
7017
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6453
7018
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6531,7 +7096,7 @@ var Indexer = class {
|
|
|
6531
7096
|
if (!this.database || this.config.scope !== "global") {
|
|
6532
7097
|
return false;
|
|
6533
7098
|
}
|
|
6534
|
-
const projectHash = hashContent(
|
|
7099
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6535
7100
|
const roots = this.getScopedRoots();
|
|
6536
7101
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6537
7102
|
return this.database.getAllBranches().some(
|
|
@@ -6565,7 +7130,7 @@ var Indexer = class {
|
|
|
6565
7130
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6566
7131
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6567
7132
|
]);
|
|
6568
|
-
const projectRootPath =
|
|
7133
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6569
7134
|
const projectLocalFilePaths = new Set(
|
|
6570
7135
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6571
7136
|
);
|
|
@@ -6627,34 +7192,27 @@ var Indexer = class {
|
|
|
6627
7192
|
database.gcOrphanEmbeddings();
|
|
6628
7193
|
database.gcOrphanChunks();
|
|
6629
7194
|
store.save();
|
|
6630
|
-
|
|
7195
|
+
this.saveInvertedIndex(invertedIndex);
|
|
6631
7196
|
return {
|
|
6632
7197
|
removedChunkIds: removedChunkIdList,
|
|
6633
7198
|
hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
|
|
6634
7199
|
};
|
|
6635
7200
|
}
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
(0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
|
|
6645
|
-
}
|
|
6646
|
-
releaseIndexingLock() {
|
|
6647
|
-
if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
|
|
6648
|
-
(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
|
+
});
|
|
6649
7209
|
}
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
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();
|
|
6655
7215
|
}
|
|
6656
|
-
await this.healthCheck();
|
|
6657
|
-
this.releaseIndexingLock();
|
|
6658
7216
|
this.logger.info("Recovery complete, next index will re-process all files");
|
|
6659
7217
|
}
|
|
6660
7218
|
loadFailedBatches(maxChunkTokens) {
|
|
@@ -6670,10 +7228,10 @@ var Indexer = class {
|
|
|
6670
7228
|
}
|
|
6671
7229
|
}
|
|
6672
7230
|
loadSerializedFailedBatches() {
|
|
6673
|
-
if (!(0,
|
|
7231
|
+
if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
|
|
6674
7232
|
return [];
|
|
6675
7233
|
}
|
|
6676
|
-
const data = (0,
|
|
7234
|
+
const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
|
|
6677
7235
|
const parsed = JSON.parse(data);
|
|
6678
7236
|
return parsed.map((batch) => {
|
|
6679
7237
|
const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
|
|
@@ -6690,15 +7248,15 @@ var Indexer = class {
|
|
|
6690
7248
|
}
|
|
6691
7249
|
saveFailedBatches(batches) {
|
|
6692
7250
|
if (batches.length === 0) {
|
|
6693
|
-
if ((0,
|
|
7251
|
+
if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
|
|
6694
7252
|
try {
|
|
6695
|
-
(0,
|
|
7253
|
+
(0, import_fs8.unlinkSync)(this.failedBatchesPath);
|
|
6696
7254
|
} catch {
|
|
6697
7255
|
}
|
|
6698
7256
|
}
|
|
6699
7257
|
return;
|
|
6700
7258
|
}
|
|
6701
|
-
|
|
7259
|
+
this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
6702
7260
|
}
|
|
6703
7261
|
collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
|
|
6704
7262
|
const retryableById = /* @__PURE__ */ new Map();
|
|
@@ -6878,7 +7436,7 @@ var Indexer = class {
|
|
|
6878
7436
|
const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
|
|
6879
7437
|
parts.push(`intent_hint: ${intent}`);
|
|
6880
7438
|
try {
|
|
6881
|
-
const fileContent = await
|
|
7439
|
+
const fileContent = await import_fs8.promises.readFile(candidate.metadata.filePath, "utf-8");
|
|
6882
7440
|
const lines = fileContent.split("\n");
|
|
6883
7441
|
const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
|
|
6884
7442
|
const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
|
|
@@ -6892,6 +7450,222 @@ var Indexer = class {
|
|
|
6892
7450
|
return parts.join("\n");
|
|
6893
7451
|
}
|
|
6894
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();
|
|
6895
7669
|
if (this.config.embeddingProvider === "custom") {
|
|
6896
7670
|
if (!this.config.customProvider) {
|
|
6897
7671
|
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
@@ -6923,36 +7697,103 @@ var Indexer = class {
|
|
|
6923
7697
|
});
|
|
6924
7698
|
}
|
|
6925
7699
|
}
|
|
6926
|
-
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
6927
7700
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6928
|
-
const storePath =
|
|
6929
|
-
|
|
6930
|
-
const
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
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();
|
|
6941
7726
|
}
|
|
6942
7727
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
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;
|
|
6951
7746
|
}
|
|
7747
|
+
} else {
|
|
6952
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
|
+
}
|
|
6953
7762
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6954
|
-
|
|
6955
|
-
|
|
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
|
+
}
|
|
6956
7797
|
}
|
|
6957
7798
|
if (isGitRepo(this.projectRoot)) {
|
|
6958
7799
|
this.currentBranch = getBranchOrDefault(this.projectRoot);
|
|
@@ -6966,10 +7807,10 @@ var Indexer = class {
|
|
|
6966
7807
|
this.baseBranch = "default";
|
|
6967
7808
|
this.logger.branch("debug", "Not a git repository, using default branch");
|
|
6968
7809
|
}
|
|
6969
|
-
if (
|
|
6970
|
-
await this.
|
|
7810
|
+
if (mode === "writer" && recoveredOwners.length > 0) {
|
|
7811
|
+
await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
|
|
6971
7812
|
}
|
|
6972
|
-
if (dbIsNew && this.store.count() > 0) {
|
|
7813
|
+
if (mode === "writer" && dbIsNew && this.store.count() > 0) {
|
|
6973
7814
|
this.migrateFromLegacyIndex();
|
|
6974
7815
|
}
|
|
6975
7816
|
this.loadFileHashCache();
|
|
@@ -6981,9 +7822,11 @@ var Indexer = class {
|
|
|
6981
7822
|
configuredProviderInfo: this.configuredProviderInfo
|
|
6982
7823
|
});
|
|
6983
7824
|
}
|
|
6984
|
-
if (this.config.indexing.autoGc) {
|
|
7825
|
+
if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
|
|
6985
7826
|
await this.maybeRunAutoGc();
|
|
6986
7827
|
}
|
|
7828
|
+
this.initializationMode = mode;
|
|
7829
|
+
this.readerArtifactFingerprint = readerArtifactFingerprint;
|
|
6987
7830
|
}
|
|
6988
7831
|
async maybeRunAutoGc() {
|
|
6989
7832
|
if (!this.database) return;
|
|
@@ -7000,7 +7843,7 @@ var Indexer = class {
|
|
|
7000
7843
|
}
|
|
7001
7844
|
}
|
|
7002
7845
|
if (shouldRunGc) {
|
|
7003
|
-
const result = await this.
|
|
7846
|
+
const result = await this.healthCheckUnlocked();
|
|
7004
7847
|
if (result.warning) {
|
|
7005
7848
|
this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
|
|
7006
7849
|
} else {
|
|
@@ -7022,7 +7865,7 @@ var Indexer = class {
|
|
|
7022
7865
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
7023
7866
|
return {
|
|
7024
7867
|
resetCorruptedIndex: true,
|
|
7025
|
-
warning: this.getCorruptedIndexWarning(
|
|
7868
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
7026
7869
|
};
|
|
7027
7870
|
}
|
|
7028
7871
|
throw error;
|
|
@@ -7037,28 +7880,29 @@ var Indexer = class {
|
|
|
7037
7880
|
return;
|
|
7038
7881
|
}
|
|
7039
7882
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
7040
|
-
const storeBasePath =
|
|
7041
|
-
const storeIndexPath =
|
|
7883
|
+
const storeBasePath = path12.join(this.indexPath, "vectors");
|
|
7884
|
+
const storeIndexPath = storeBasePath;
|
|
7042
7885
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
7043
|
-
const
|
|
7044
|
-
const
|
|
7886
|
+
const lease = this.requireActiveLease();
|
|
7887
|
+
const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
|
|
7888
|
+
const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
|
|
7045
7889
|
let backedUpIndex = false;
|
|
7046
7890
|
let backedUpMetadata = false;
|
|
7047
7891
|
let rebuiltCount = 0;
|
|
7048
7892
|
let skippedCount = 0;
|
|
7049
|
-
if ((0,
|
|
7050
|
-
(0,
|
|
7893
|
+
if ((0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7894
|
+
(0, import_fs8.unlinkSync)(backupIndexPath);
|
|
7051
7895
|
}
|
|
7052
|
-
if ((0,
|
|
7053
|
-
(0,
|
|
7896
|
+
if ((0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7897
|
+
(0, import_fs8.unlinkSync)(backupMetadataPath);
|
|
7054
7898
|
}
|
|
7055
7899
|
try {
|
|
7056
|
-
if ((0,
|
|
7057
|
-
(0,
|
|
7900
|
+
if ((0, import_fs8.existsSync)(storeIndexPath)) {
|
|
7901
|
+
(0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
|
|
7058
7902
|
backedUpIndex = true;
|
|
7059
7903
|
}
|
|
7060
|
-
if ((0,
|
|
7061
|
-
(0,
|
|
7904
|
+
if ((0, import_fs8.existsSync)(storeMetadataPath)) {
|
|
7905
|
+
(0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
|
|
7062
7906
|
backedUpMetadata = true;
|
|
7063
7907
|
}
|
|
7064
7908
|
store.clear();
|
|
@@ -7078,11 +7922,11 @@ var Indexer = class {
|
|
|
7078
7922
|
rebuiltCount += 1;
|
|
7079
7923
|
}
|
|
7080
7924
|
store.save();
|
|
7081
|
-
if (backedUpIndex && (0,
|
|
7082
|
-
(0,
|
|
7925
|
+
if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7926
|
+
(0, import_fs8.unlinkSync)(backupIndexPath);
|
|
7083
7927
|
}
|
|
7084
|
-
if (backedUpMetadata && (0,
|
|
7085
|
-
(0,
|
|
7928
|
+
if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7929
|
+
(0, import_fs8.unlinkSync)(backupMetadataPath);
|
|
7086
7930
|
}
|
|
7087
7931
|
this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
|
|
7088
7932
|
excludedChunks: excludedSet.size,
|
|
@@ -7094,17 +7938,17 @@ var Indexer = class {
|
|
|
7094
7938
|
store.clear();
|
|
7095
7939
|
} catch {
|
|
7096
7940
|
}
|
|
7097
|
-
if ((0,
|
|
7098
|
-
(0,
|
|
7941
|
+
if ((0, import_fs8.existsSync)(storeIndexPath)) {
|
|
7942
|
+
(0, import_fs8.unlinkSync)(storeIndexPath);
|
|
7099
7943
|
}
|
|
7100
|
-
if ((0,
|
|
7101
|
-
(0,
|
|
7944
|
+
if ((0, import_fs8.existsSync)(storeMetadataPath)) {
|
|
7945
|
+
(0, import_fs8.unlinkSync)(storeMetadataPath);
|
|
7102
7946
|
}
|
|
7103
|
-
if (backedUpIndex && (0,
|
|
7104
|
-
(0,
|
|
7947
|
+
if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7948
|
+
(0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
|
|
7105
7949
|
}
|
|
7106
|
-
if (backedUpMetadata && (0,
|
|
7107
|
-
(0,
|
|
7950
|
+
if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7951
|
+
(0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
|
|
7108
7952
|
}
|
|
7109
7953
|
if (backedUpIndex || backedUpMetadata) {
|
|
7110
7954
|
store.load();
|
|
@@ -7118,11 +7962,37 @@ var Indexer = class {
|
|
|
7118
7962
|
}
|
|
7119
7963
|
return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
|
|
7120
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
|
+
}
|
|
7121
7991
|
async tryResetCorruptedIndex(stage, error) {
|
|
7122
7992
|
if (!isSqliteCorruptionError(error)) {
|
|
7123
7993
|
return false;
|
|
7124
7994
|
}
|
|
7125
|
-
const dbPath =
|
|
7995
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7126
7996
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
7127
7997
|
const errorMessage = getErrorMessage2(error);
|
|
7128
7998
|
if (this.config.scope === "global") {
|
|
@@ -7138,30 +8008,7 @@ var Indexer = class {
|
|
|
7138
8008
|
dbPath,
|
|
7139
8009
|
error: errorMessage
|
|
7140
8010
|
});
|
|
7141
|
-
this.
|
|
7142
|
-
this.invertedIndex = null;
|
|
7143
|
-
this.database?.close();
|
|
7144
|
-
this.database = null;
|
|
7145
|
-
this.indexCompatibility = null;
|
|
7146
|
-
this.fileHashCache.clear();
|
|
7147
|
-
const resetPaths = [
|
|
7148
|
-
path11.join(this.indexPath, "codebase.db"),
|
|
7149
|
-
path11.join(this.indexPath, "codebase.db-shm"),
|
|
7150
|
-
path11.join(this.indexPath, "codebase.db-wal"),
|
|
7151
|
-
path11.join(this.indexPath, "vectors.usearch"),
|
|
7152
|
-
path11.join(this.indexPath, "inverted-index.json"),
|
|
7153
|
-
path11.join(this.indexPath, "file-hashes.json"),
|
|
7154
|
-
path11.join(this.indexPath, "failed-batches.json"),
|
|
7155
|
-
path11.join(this.indexPath, "indexing.lock"),
|
|
7156
|
-
path11.join(this.indexPath, "vectors")
|
|
7157
|
-
];
|
|
7158
|
-
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
7159
|
-
try {
|
|
7160
|
-
await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
|
|
7161
|
-
} catch {
|
|
7162
|
-
}
|
|
7163
|
-
}));
|
|
7164
|
-
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
8011
|
+
await this.resetLocalIndexArtifacts();
|
|
7165
8012
|
return true;
|
|
7166
8013
|
}
|
|
7167
8014
|
migrateFromLegacyIndex() {
|
|
@@ -7280,8 +8127,62 @@ var Indexer = class {
|
|
|
7280
8127
|
return this.indexCompatibility;
|
|
7281
8128
|
}
|
|
7282
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() {
|
|
7283
8184
|
if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
|
|
7284
|
-
|
|
8185
|
+
throw new Error("Index state is not initialized");
|
|
7285
8186
|
}
|
|
7286
8187
|
return {
|
|
7287
8188
|
store: this.store,
|
|
@@ -7305,7 +8206,12 @@ var Indexer = class {
|
|
|
7305
8206
|
return createCostEstimate(files, configuredProviderInfo);
|
|
7306
8207
|
}
|
|
7307
8208
|
async index(onProgress) {
|
|
7308
|
-
|
|
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);
|
|
7309
8215
|
const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
7310
8216
|
const branchCatalogKey = this.getBranchCatalogKey();
|
|
7311
8217
|
const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
|
|
@@ -7315,7 +8221,6 @@ var Indexer = class {
|
|
|
7315
8221
|
`${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
|
|
7316
8222
|
);
|
|
7317
8223
|
}
|
|
7318
|
-
this.acquireIndexingLock();
|
|
7319
8224
|
this.logger.recordIndexingStart();
|
|
7320
8225
|
this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
|
|
7321
8226
|
const startTime = Date.now();
|
|
@@ -7366,7 +8271,7 @@ var Indexer = class {
|
|
|
7366
8271
|
unchangedFilePaths.add(f.path);
|
|
7367
8272
|
this.logger.recordCacheHit();
|
|
7368
8273
|
} else {
|
|
7369
|
-
const content = await
|
|
8274
|
+
const content = await import_fs8.promises.readFile(f.path, "utf-8");
|
|
7370
8275
|
changedFiles.push({ path: f.path, content, hash: currentHash });
|
|
7371
8276
|
this.logger.recordCacheMiss();
|
|
7372
8277
|
}
|
|
@@ -7464,7 +8369,7 @@ var Indexer = class {
|
|
|
7464
8369
|
for (const parsed of parsedFiles) {
|
|
7465
8370
|
currentFilePaths.add(parsed.path);
|
|
7466
8371
|
if (parsed.chunks.length === 0) {
|
|
7467
|
-
const relativePath =
|
|
8372
|
+
const relativePath = path12.relative(this.projectRoot, parsed.path);
|
|
7468
8373
|
stats.parseFailures.push(relativePath);
|
|
7469
8374
|
}
|
|
7470
8375
|
let fileChunkCount = 0;
|
|
@@ -7664,7 +8569,9 @@ var Indexer = class {
|
|
|
7664
8569
|
database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
|
|
7665
8570
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7666
8571
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7667
|
-
|
|
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) {
|
|
7668
8575
|
store.save();
|
|
7669
8576
|
}
|
|
7670
8577
|
if (scopedRoots) {
|
|
@@ -7685,7 +8592,6 @@ var Indexer = class {
|
|
|
7685
8592
|
chunksProcessed: 0,
|
|
7686
8593
|
totalChunks: 0
|
|
7687
8594
|
});
|
|
7688
|
-
this.releaseIndexingLock();
|
|
7689
8595
|
return stats;
|
|
7690
8596
|
}
|
|
7691
8597
|
if (pendingChunks.length === 0) {
|
|
@@ -7694,7 +8600,7 @@ var Indexer = class {
|
|
|
7694
8600
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7695
8601
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7696
8602
|
store.save();
|
|
7697
|
-
|
|
8603
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7698
8604
|
if (scopedRoots) {
|
|
7699
8605
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7700
8606
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7713,7 +8619,6 @@ var Indexer = class {
|
|
|
7713
8619
|
chunksProcessed: 0,
|
|
7714
8620
|
totalChunks: 0
|
|
7715
8621
|
});
|
|
7716
|
-
this.releaseIndexingLock();
|
|
7717
8622
|
return stats;
|
|
7718
8623
|
}
|
|
7719
8624
|
onProgress?.({
|
|
@@ -7944,7 +8849,7 @@ var Indexer = class {
|
|
|
7944
8849
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7945
8850
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7946
8851
|
store.save();
|
|
7947
|
-
|
|
8852
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7948
8853
|
if (scopedRoots) {
|
|
7949
8854
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7950
8855
|
} else {
|
|
@@ -7996,7 +8901,6 @@ var Indexer = class {
|
|
|
7996
8901
|
chunksProcessed: stats.indexedChunks,
|
|
7997
8902
|
totalChunks: pendingChunks.length
|
|
7998
8903
|
});
|
|
7999
|
-
this.releaseIndexingLock();
|
|
8000
8904
|
return stats;
|
|
8001
8905
|
}
|
|
8002
8906
|
async getQueryEmbedding(query, provider) {
|
|
@@ -8062,8 +8966,8 @@ var Indexer = class {
|
|
|
8062
8966
|
return intersection / union;
|
|
8063
8967
|
}
|
|
8064
8968
|
async search(query, limit, options) {
|
|
8065
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8066
|
-
|
|
8969
|
+
const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8970
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8067
8971
|
if (!compatibility.compatible) {
|
|
8068
8972
|
throw new Error(
|
|
8069
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.`
|
|
@@ -8077,6 +8981,7 @@ var Indexer = class {
|
|
|
8077
8981
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
8078
8982
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
8079
8983
|
const fusionStrategy = this.config.search.fusionStrategy;
|
|
8984
|
+
const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
|
|
8080
8985
|
const rrfK = this.config.search.rrfK;
|
|
8081
8986
|
const rerankTopN = this.config.search.rerankTopN;
|
|
8082
8987
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
@@ -8085,7 +8990,7 @@ var Indexer = class {
|
|
|
8085
8990
|
this.logger.search("debug", "Starting search", {
|
|
8086
8991
|
query,
|
|
8087
8992
|
maxResults,
|
|
8088
|
-
hybridWeight,
|
|
8993
|
+
hybridWeight: effectiveHybridWeight,
|
|
8089
8994
|
fusionStrategy,
|
|
8090
8995
|
rrfK,
|
|
8091
8996
|
rerankTopN,
|
|
@@ -8093,13 +8998,22 @@ var Indexer = class {
|
|
|
8093
8998
|
});
|
|
8094
8999
|
const embeddingStartTime = import_perf_hooks.performance.now();
|
|
8095
9000
|
const embeddingQuery = stripFilePathHint(query);
|
|
8096
|
-
|
|
9001
|
+
let embedding;
|
|
9002
|
+
try {
|
|
9003
|
+
embedding = await this.getQueryEmbedding(embeddingQuery, provider);
|
|
9004
|
+
} catch (error) {
|
|
9005
|
+
this.logger.warn("Query embedding failed; falling back to keyword-only search", {
|
|
9006
|
+
query,
|
|
9007
|
+
error: getErrorMessage2(error),
|
|
9008
|
+
action: "Check the embedding provider configuration and retry search after restoring provider health."
|
|
9009
|
+
});
|
|
9010
|
+
}
|
|
8097
9011
|
const embeddingMs = import_perf_hooks.performance.now() - embeddingStartTime;
|
|
8098
9012
|
const vectorStartTime = import_perf_hooks.performance.now();
|
|
8099
|
-
const semanticResults = store.search(embedding, maxResults * 4);
|
|
9013
|
+
const semanticResults = embedding ? store.search(embedding, maxResults * 4) : [];
|
|
8100
9014
|
const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
|
|
8101
9015
|
const keywordStartTime = import_perf_hooks.performance.now();
|
|
8102
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
9016
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
8103
9017
|
const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
|
|
8104
9018
|
let branchChunkIds = null;
|
|
8105
9019
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -8131,12 +9045,13 @@ var Indexer = class {
|
|
|
8131
9045
|
});
|
|
8132
9046
|
}
|
|
8133
9047
|
const fusionStartTime = import_perf_hooks.performance.now();
|
|
9048
|
+
const rankingHybridWeight = embedding === void 0 && fusionStrategy === "weighted" ? 1 : effectiveHybridWeight;
|
|
8134
9049
|
const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
|
|
8135
9050
|
fusionStrategy,
|
|
8136
9051
|
rrfK,
|
|
8137
9052
|
rerankTopN,
|
|
8138
9053
|
limit: maxResults,
|
|
8139
|
-
hybridWeight,
|
|
9054
|
+
hybridWeight: rankingHybridWeight,
|
|
8140
9055
|
prioritizeSourcePaths: sourceIntent
|
|
8141
9056
|
});
|
|
8142
9057
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -8210,7 +9125,7 @@ var Indexer = class {
|
|
|
8210
9125
|
let contextEndLine = r.metadata.endLine;
|
|
8211
9126
|
if (!metadataOnly && this.config.search.includeContext) {
|
|
8212
9127
|
try {
|
|
8213
|
-
const fileContent = await
|
|
9128
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8214
9129
|
r.metadata.filePath,
|
|
8215
9130
|
"utf-8"
|
|
8216
9131
|
);
|
|
@@ -8236,8 +9151,7 @@ var Indexer = class {
|
|
|
8236
9151
|
})
|
|
8237
9152
|
);
|
|
8238
9153
|
}
|
|
8239
|
-
async keywordSearch(query, limit) {
|
|
8240
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9154
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
8241
9155
|
const scores = invertedIndex.search(query);
|
|
8242
9156
|
if (scores.size === 0) {
|
|
8243
9157
|
return [];
|
|
@@ -8255,24 +9169,54 @@ var Indexer = class {
|
|
|
8255
9169
|
return results.slice(0, limit);
|
|
8256
9170
|
}
|
|
8257
9171
|
async getStatus() {
|
|
8258
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9172
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8259
9173
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9174
|
+
const vectorCount = store.count();
|
|
9175
|
+
const statusReadIssues = [...readIssues];
|
|
9176
|
+
let startupWarning = "";
|
|
9177
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9178
|
+
try {
|
|
9179
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9180
|
+
} catch (error) {
|
|
9181
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9182
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9183
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9184
|
+
this.recordReadIssue("database", message, error);
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9189
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9190
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
8260
9191
|
return {
|
|
8261
|
-
indexed:
|
|
8262
|
-
vectorCount
|
|
9192
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9193
|
+
vectorCount,
|
|
8263
9194
|
provider: configuredProviderInfo.provider,
|
|
8264
9195
|
model: configuredProviderInfo.modelInfo.model,
|
|
8265
9196
|
indexPath: this.indexPath,
|
|
8266
9197
|
currentBranch: this.currentBranch,
|
|
8267
9198
|
baseBranch: this.baseBranch,
|
|
8268
|
-
compatibility
|
|
9199
|
+
compatibility,
|
|
8269
9200
|
failedBatchesCount,
|
|
8270
9201
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
8271
|
-
warning:
|
|
9202
|
+
warning: warning || void 0
|
|
8272
9203
|
};
|
|
8273
9204
|
}
|
|
9205
|
+
async forceIndex(onProgress) {
|
|
9206
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9207
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9208
|
+
await this.clearIndexUnlocked();
|
|
9209
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9210
|
+
});
|
|
9211
|
+
}
|
|
8274
9212
|
async clearIndex() {
|
|
8275
|
-
|
|
9213
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9214
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9215
|
+
await this.clearIndexUnlocked();
|
|
9216
|
+
});
|
|
9217
|
+
}
|
|
9218
|
+
async clearIndexUnlocked() {
|
|
9219
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8276
9220
|
if (this.config.scope === "global") {
|
|
8277
9221
|
store.load();
|
|
8278
9222
|
invertedIndex.load();
|
|
@@ -8299,7 +9243,7 @@ var Indexer = class {
|
|
|
8299
9243
|
store.clear();
|
|
8300
9244
|
store.save();
|
|
8301
9245
|
invertedIndex.clear();
|
|
8302
|
-
|
|
9246
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8303
9247
|
this.fileHashCache.clear();
|
|
8304
9248
|
this.saveFileHashCache();
|
|
8305
9249
|
database.clearAllIndexedData();
|
|
@@ -8323,14 +9267,7 @@ var Indexer = class {
|
|
|
8323
9267
|
this.indexCompatibility = compatibility;
|
|
8324
9268
|
return;
|
|
8325
9269
|
}
|
|
8326
|
-
|
|
8327
|
-
if (this.host !== "opencode") {
|
|
8328
|
-
localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
8329
|
-
}
|
|
8330
|
-
const isLocalProjectIndex = localProjectIndexPaths.some(
|
|
8331
|
-
(localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
|
|
8332
|
-
);
|
|
8333
|
-
if (!isLocalProjectIndex) {
|
|
9270
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8334
9271
|
throw new Error(
|
|
8335
9272
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8336
9273
|
);
|
|
@@ -8338,7 +9275,7 @@ var Indexer = class {
|
|
|
8338
9275
|
store.clear();
|
|
8339
9276
|
store.save();
|
|
8340
9277
|
invertedIndex.clear();
|
|
8341
|
-
|
|
9278
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8342
9279
|
this.fileHashCache.clear();
|
|
8343
9280
|
this.saveFileHashCache();
|
|
8344
9281
|
database.clearAllIndexedData();
|
|
@@ -8356,7 +9293,13 @@ var Indexer = class {
|
|
|
8356
9293
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8357
9294
|
}
|
|
8358
9295
|
async healthCheck() {
|
|
8359
|
-
|
|
9296
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9297
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9298
|
+
return this.healthCheckUnlocked();
|
|
9299
|
+
});
|
|
9300
|
+
}
|
|
9301
|
+
async healthCheckUnlocked() {
|
|
9302
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8360
9303
|
this.logger.gc("info", "Starting health check");
|
|
8361
9304
|
const allMetadata = store.getAllMetadata();
|
|
8362
9305
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8369,7 +9312,7 @@ var Indexer = class {
|
|
|
8369
9312
|
const removedChunkKeys = [];
|
|
8370
9313
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8371
9314
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8372
|
-
if (!(0,
|
|
9315
|
+
if (!(0, import_fs8.existsSync)(filePath)) {
|
|
8373
9316
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8374
9317
|
for (const key of chunkKeys) {
|
|
8375
9318
|
removedChunkKeys.push(key);
|
|
@@ -8394,7 +9337,7 @@ var Indexer = class {
|
|
|
8394
9337
|
const removedCount = removedChunkKeys.length;
|
|
8395
9338
|
if (removedCount > 0) {
|
|
8396
9339
|
store.save();
|
|
8397
|
-
|
|
9340
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8398
9341
|
}
|
|
8399
9342
|
let gcOrphanEmbeddings;
|
|
8400
9343
|
let gcOrphanChunks;
|
|
@@ -8409,7 +9352,7 @@ var Indexer = class {
|
|
|
8409
9352
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8410
9353
|
throw error;
|
|
8411
9354
|
}
|
|
8412
|
-
await this.
|
|
9355
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8413
9356
|
return {
|
|
8414
9357
|
removed: 0,
|
|
8415
9358
|
filePaths: [],
|
|
@@ -8418,7 +9361,7 @@ var Indexer = class {
|
|
|
8418
9361
|
gcOrphanSymbols: 0,
|
|
8419
9362
|
gcOrphanCallEdges: 0,
|
|
8420
9363
|
resetCorruptedIndex: true,
|
|
8421
|
-
warning: this.getCorruptedIndexWarning(
|
|
9364
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
8422
9365
|
};
|
|
8423
9366
|
}
|
|
8424
9367
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8431,7 +9374,13 @@ var Indexer = class {
|
|
|
8431
9374
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8432
9375
|
}
|
|
8433
9376
|
async retryFailedBatches() {
|
|
8434
|
-
|
|
9377
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9378
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9379
|
+
return this.retryFailedBatchesUnlocked();
|
|
9380
|
+
});
|
|
9381
|
+
}
|
|
9382
|
+
async retryFailedBatchesUnlocked() {
|
|
9383
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8435
9384
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8436
9385
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8437
9386
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8595,7 +9544,7 @@ var Indexer = class {
|
|
|
8595
9544
|
}
|
|
8596
9545
|
if (succeeded > 0) {
|
|
8597
9546
|
store.save();
|
|
8598
|
-
|
|
9547
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8599
9548
|
}
|
|
8600
9549
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8601
9550
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8623,15 +9572,16 @@ var Indexer = class {
|
|
|
8623
9572
|
}
|
|
8624
9573
|
}
|
|
8625
9574
|
async getDatabaseStats() {
|
|
8626
|
-
const { database } = await this.ensureInitialized();
|
|
9575
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9576
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8627
9577
|
return database.getStats();
|
|
8628
9578
|
}
|
|
8629
9579
|
getLogger() {
|
|
8630
9580
|
return this.logger;
|
|
8631
9581
|
}
|
|
8632
9582
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8633
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8634
|
-
|
|
9583
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9584
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8635
9585
|
if (!compatibility.compatible) {
|
|
8636
9586
|
throw new Error(
|
|
8637
9587
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8721,7 +9671,7 @@ var Indexer = class {
|
|
|
8721
9671
|
let content = "";
|
|
8722
9672
|
if (this.config.search.includeContext) {
|
|
8723
9673
|
try {
|
|
8724
|
-
const fileContent = await
|
|
9674
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8725
9675
|
r.metadata.filePath,
|
|
8726
9676
|
"utf-8"
|
|
8727
9677
|
);
|
|
@@ -8745,7 +9695,8 @@ var Indexer = class {
|
|
|
8745
9695
|
);
|
|
8746
9696
|
}
|
|
8747
9697
|
async getCallers(targetName, callTypeFilter) {
|
|
8748
|
-
const { database } = await this.ensureInitialized();
|
|
9698
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9699
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8749
9700
|
const seen = /* @__PURE__ */ new Set();
|
|
8750
9701
|
const results = [];
|
|
8751
9702
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8759,7 +9710,8 @@ var Indexer = class {
|
|
|
8759
9710
|
return results;
|
|
8760
9711
|
}
|
|
8761
9712
|
async getCallees(symbolId, callTypeFilter) {
|
|
8762
|
-
const { database } = await this.ensureInitialized();
|
|
9713
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9714
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8763
9715
|
const seen = /* @__PURE__ */ new Set();
|
|
8764
9716
|
const results = [];
|
|
8765
9717
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8773,43 +9725,50 @@ var Indexer = class {
|
|
|
8773
9725
|
return results;
|
|
8774
9726
|
}
|
|
8775
9727
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8776
|
-
const { database } = await this.ensureInitialized();
|
|
9728
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9729
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8777
9730
|
let shortest = [];
|
|
8778
9731
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8779
|
-
const
|
|
8780
|
-
if (
|
|
8781
|
-
shortest =
|
|
9732
|
+
const path27 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9733
|
+
if (path27.length > 0 && (shortest.length === 0 || path27.length < shortest.length)) {
|
|
9734
|
+
shortest = path27;
|
|
8782
9735
|
}
|
|
8783
9736
|
}
|
|
8784
9737
|
return shortest;
|
|
8785
9738
|
}
|
|
8786
9739
|
async getSymbolsForBranch(branch) {
|
|
8787
|
-
const { database } = await this.ensureInitialized();
|
|
9740
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9741
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8788
9742
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8789
9743
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8790
9744
|
}
|
|
8791
9745
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8792
|
-
const { database } = await this.ensureInitialized();
|
|
9746
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9747
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8793
9748
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8794
9749
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8795
9750
|
}
|
|
8796
9751
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8797
|
-
const { database } = await this.ensureInitialized();
|
|
9752
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9753
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8798
9754
|
const branch = this.getBranchCatalogKey();
|
|
8799
9755
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8800
9756
|
}
|
|
8801
9757
|
async detectCommunities(branch, symbolIds) {
|
|
8802
|
-
const { database } = await this.ensureInitialized();
|
|
9758
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9759
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8803
9760
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8804
9761
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8805
9762
|
}
|
|
8806
9763
|
async computeCentrality(branch) {
|
|
8807
|
-
const { database } = await this.ensureInitialized();
|
|
9764
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9765
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8808
9766
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8809
9767
|
return database.computeCentrality(resolvedBranch);
|
|
8810
9768
|
}
|
|
8811
9769
|
async getPrImpact(opts) {
|
|
8812
|
-
const { database } = await this.ensureInitialized();
|
|
9770
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9771
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8813
9772
|
const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
|
|
8814
9773
|
const changedFilesResult = await getChangedFiles({
|
|
8815
9774
|
pr: opts.pr,
|
|
@@ -8832,7 +9791,7 @@ var Indexer = class {
|
|
|
8832
9791
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8833
9792
|
);
|
|
8834
9793
|
}
|
|
8835
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9794
|
+
const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
|
|
8836
9795
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8837
9796
|
const directIds = directSymbols.map((s) => s.id);
|
|
8838
9797
|
const direction = opts.direction ?? "both";
|
|
@@ -8915,7 +9874,7 @@ var Indexer = class {
|
|
|
8915
9874
|
projectRoot: this.projectRoot,
|
|
8916
9875
|
baseBranch: this.baseBranch
|
|
8917
9876
|
});
|
|
8918
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9877
|
+
const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
|
|
8919
9878
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8920
9879
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8921
9880
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8965,7 +9924,8 @@ var Indexer = class {
|
|
|
8965
9924
|
};
|
|
8966
9925
|
}
|
|
8967
9926
|
async getVisualizationData(options) {
|
|
8968
|
-
const { database, store } = await this.ensureInitialized();
|
|
9927
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9928
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8969
9929
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8970
9930
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8971
9931
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8978,12 +9938,12 @@ var Indexer = class {
|
|
|
8978
9938
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8979
9939
|
}
|
|
8980
9940
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8981
|
-
const absoluteDirectoryFilter = directory ?
|
|
9941
|
+
const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
|
|
8982
9942
|
for (const filePath of filePaths) {
|
|
8983
9943
|
if (directory) {
|
|
8984
|
-
const absoluteFilePath =
|
|
9944
|
+
const absoluteFilePath = path12.resolve(filePath);
|
|
8985
9945
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8986
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9946
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
|
|
8987
9947
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8988
9948
|
continue;
|
|
8989
9949
|
}
|
|
@@ -9005,12 +9965,23 @@ var Indexer = class {
|
|
|
9005
9965
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
9006
9966
|
}
|
|
9007
9967
|
async close() {
|
|
9008
|
-
|
|
9968
|
+
this.database?.close();
|
|
9969
|
+
for (const database of this.retiredDatabases) {
|
|
9970
|
+
database.close();
|
|
9971
|
+
}
|
|
9972
|
+
this.retiredDatabases = [];
|
|
9009
9973
|
this.database = null;
|
|
9010
9974
|
this.store = null;
|
|
9011
9975
|
this.invertedIndex = null;
|
|
9012
9976
|
this.provider = null;
|
|
9013
9977
|
this.reranker = null;
|
|
9978
|
+
this.configuredProviderInfo = null;
|
|
9979
|
+
this.indexCompatibility = null;
|
|
9980
|
+
this.initializationMode = "none";
|
|
9981
|
+
this.readIssues = [];
|
|
9982
|
+
this.readerArtifactFingerprint = null;
|
|
9983
|
+
this.writerArtifactFingerprint = null;
|
|
9984
|
+
this.readerArtifactRetryAfter.clear();
|
|
9014
9985
|
}
|
|
9015
9986
|
};
|
|
9016
9987
|
|
|
@@ -9261,15 +10232,15 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
9261
10232
|
}
|
|
9262
10233
|
|
|
9263
10234
|
// src/eval/runner-config.ts
|
|
9264
|
-
var
|
|
9265
|
-
var
|
|
9266
|
-
var
|
|
10235
|
+
var import_fs9 = require("fs");
|
|
10236
|
+
var os5 = __toESM(require("os"), 1);
|
|
10237
|
+
var path14 = __toESM(require("path"), 1);
|
|
9267
10238
|
|
|
9268
10239
|
// src/config/rebase.ts
|
|
9269
|
-
var
|
|
10240
|
+
var path13 = __toESM(require("path"), 1);
|
|
9270
10241
|
function isWithinRoot(rootDir, targetPath) {
|
|
9271
|
-
const relativePath =
|
|
9272
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
10242
|
+
const relativePath = path13.relative(rootDir, targetPath);
|
|
10243
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
|
|
9273
10244
|
}
|
|
9274
10245
|
function rebasePathEntries(values, fromDir, toDir) {
|
|
9275
10246
|
if (!Array.isArray(values)) {
|
|
@@ -9277,10 +10248,10 @@ function rebasePathEntries(values, fromDir, toDir) {
|
|
|
9277
10248
|
}
|
|
9278
10249
|
return values.filter((value) => typeof value === "string").map((value) => {
|
|
9279
10250
|
const trimmed = value.trim();
|
|
9280
|
-
if (!trimmed ||
|
|
10251
|
+
if (!trimmed || path13.isAbsolute(trimmed)) {
|
|
9281
10252
|
return trimmed;
|
|
9282
10253
|
}
|
|
9283
|
-
return normalizePathSeparators(
|
|
10254
|
+
return normalizePathSeparators(path13.normalize(path13.relative(toDir, path13.resolve(fromDir, trimmed))));
|
|
9284
10255
|
}).filter(Boolean);
|
|
9285
10256
|
}
|
|
9286
10257
|
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
@@ -9292,17 +10263,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
|
9292
10263
|
if (!trimmed) {
|
|
9293
10264
|
return trimmed;
|
|
9294
10265
|
}
|
|
9295
|
-
if (
|
|
10266
|
+
if (path13.isAbsolute(trimmed)) {
|
|
9296
10267
|
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
9297
|
-
return normalizePathSeparators(
|
|
10268
|
+
return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
|
|
9298
10269
|
}
|
|
9299
|
-
return
|
|
10270
|
+
return path13.normalize(trimmed);
|
|
9300
10271
|
}
|
|
9301
|
-
const resolvedFromSource =
|
|
10272
|
+
const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
|
|
9302
10273
|
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
9303
|
-
return normalizePathSeparators(
|
|
10274
|
+
return normalizePathSeparators(path13.normalize(trimmed));
|
|
9304
10275
|
}
|
|
9305
|
-
return normalizePathSeparators(
|
|
10276
|
+
return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
|
|
9306
10277
|
}).filter(Boolean);
|
|
9307
10278
|
}
|
|
9308
10279
|
|
|
@@ -9340,7 +10311,7 @@ function validateEvalConfigShape(rawConfig, filePath) {
|
|
|
9340
10311
|
}
|
|
9341
10312
|
function parseJsonConfigFile(filePath) {
|
|
9342
10313
|
try {
|
|
9343
|
-
return validateEvalConfigShape(JSON.parse((0,
|
|
10314
|
+
return validateEvalConfigShape(JSON.parse((0, import_fs9.readFileSync)(filePath, "utf-8")), filePath);
|
|
9344
10315
|
} catch (error) {
|
|
9345
10316
|
if (error instanceof Error && error.message.startsWith("Eval config at ")) {
|
|
9346
10317
|
throw error;
|
|
@@ -9350,20 +10321,20 @@ function parseJsonConfigFile(filePath) {
|
|
|
9350
10321
|
}
|
|
9351
10322
|
}
|
|
9352
10323
|
function toAbsolute(projectRoot, maybeRelative) {
|
|
9353
|
-
return
|
|
10324
|
+
return path14.isAbsolute(maybeRelative) ? maybeRelative : path14.join(projectRoot, maybeRelative);
|
|
9354
10325
|
}
|
|
9355
10326
|
function isProjectScopedConfigPath(configPath) {
|
|
9356
|
-
return
|
|
10327
|
+
return path14.basename(configPath) === "codebase-index.json" && path14.basename(path14.dirname(configPath)) === ".opencode";
|
|
9357
10328
|
}
|
|
9358
10329
|
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
9359
10330
|
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
9360
10331
|
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
9361
10332
|
values,
|
|
9362
|
-
|
|
10333
|
+
path14.dirname(path14.dirname(resolvedConfigPath)),
|
|
9363
10334
|
projectRoot
|
|
9364
10335
|
) : rebasePathEntries(
|
|
9365
10336
|
values,
|
|
9366
|
-
|
|
10337
|
+
path14.dirname(resolvedConfigPath),
|
|
9367
10338
|
projectRoot
|
|
9368
10339
|
);
|
|
9369
10340
|
if (Array.isArray(config.knowledgeBases)) {
|
|
@@ -9376,7 +10347,7 @@ function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfi
|
|
|
9376
10347
|
}
|
|
9377
10348
|
function loadRawConfig(projectRoot, configPath) {
|
|
9378
10349
|
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
9379
|
-
if (fromPath && (0,
|
|
10350
|
+
if (fromPath && (0, import_fs9.existsSync)(fromPath)) {
|
|
9380
10351
|
return normalizeEvalConfigKnowledgeBases(
|
|
9381
10352
|
parseJsonConfigFile(fromPath),
|
|
9382
10353
|
projectRoot,
|
|
@@ -9384,15 +10355,15 @@ function loadRawConfig(projectRoot, configPath) {
|
|
|
9384
10355
|
);
|
|
9385
10356
|
}
|
|
9386
10357
|
const projectConfig = resolveProjectConfigPath(projectRoot);
|
|
9387
|
-
if ((0,
|
|
10358
|
+
if ((0, import_fs9.existsSync)(projectConfig)) {
|
|
9388
10359
|
return normalizeEvalConfigKnowledgeBases(
|
|
9389
10360
|
parseJsonConfigFile(projectConfig),
|
|
9390
10361
|
projectRoot,
|
|
9391
10362
|
projectConfig
|
|
9392
10363
|
);
|
|
9393
10364
|
}
|
|
9394
|
-
const globalConfig =
|
|
9395
|
-
if ((0,
|
|
10365
|
+
const globalConfig = path14.join(os5.homedir(), ".config", "opencode", "codebase-index.json");
|
|
10366
|
+
if ((0, import_fs9.existsSync)(globalConfig)) {
|
|
9396
10367
|
return parseJsonConfigFile(globalConfig);
|
|
9397
10368
|
}
|
|
9398
10369
|
return {};
|
|
@@ -9401,24 +10372,24 @@ function getIndexRootPath(projectRoot, scope) {
|
|
|
9401
10372
|
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
9402
10373
|
}
|
|
9403
10374
|
function getLocalProjectIndexRoot(projectRoot) {
|
|
9404
|
-
return
|
|
10375
|
+
return path14.join(projectRoot, ".opencode", "index");
|
|
9405
10376
|
}
|
|
9406
10377
|
function getLocalProjectConfigPath(projectRoot) {
|
|
9407
|
-
return
|
|
10378
|
+
return path14.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9408
10379
|
}
|
|
9409
10380
|
function clearIndexRoot(projectRoot, scope) {
|
|
9410
10381
|
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
9411
|
-
if ((0,
|
|
9412
|
-
(0,
|
|
10382
|
+
if ((0, import_fs9.existsSync)(indexRoot)) {
|
|
10383
|
+
(0, import_fs9.rmSync)(indexRoot, { recursive: true, force: true });
|
|
9413
10384
|
}
|
|
9414
10385
|
}
|
|
9415
10386
|
function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
9416
10387
|
const localConfigPath = getLocalProjectConfigPath(projectRoot);
|
|
9417
10388
|
const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
|
|
9418
|
-
if (!configPath && (0,
|
|
10389
|
+
if (!configPath && (0, import_fs9.existsSync)(localConfigPath)) {
|
|
9419
10390
|
return localConfigPath;
|
|
9420
10391
|
}
|
|
9421
|
-
if (!(0,
|
|
10392
|
+
if (!(0, import_fs9.existsSync)(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
|
|
9422
10393
|
return resolvedConfigPath;
|
|
9423
10394
|
}
|
|
9424
10395
|
const sourceConfig = normalizeEvalConfigKnowledgeBases(
|
|
@@ -9426,8 +10397,8 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
|
9426
10397
|
projectRoot,
|
|
9427
10398
|
resolvedConfigPath
|
|
9428
10399
|
);
|
|
9429
|
-
(0,
|
|
9430
|
-
(0,
|
|
10400
|
+
(0, import_fs9.mkdirSync)(path14.dirname(localConfigPath), { recursive: true });
|
|
10401
|
+
(0, import_fs9.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
9431
10402
|
return localConfigPath;
|
|
9432
10403
|
}
|
|
9433
10404
|
function loadParsedConfig(projectRoot, configPath) {
|
|
@@ -9460,9 +10431,9 @@ function getEmbeddingCostPer1MTokens(embeddingProvider) {
|
|
|
9460
10431
|
}
|
|
9461
10432
|
|
|
9462
10433
|
// src/eval/schema.ts
|
|
9463
|
-
var
|
|
10434
|
+
var import_fs10 = require("fs");
|
|
9464
10435
|
function parseJsonFile(filePath) {
|
|
9465
|
-
const content = (0,
|
|
10436
|
+
const content = (0, import_fs10.readFileSync)(filePath, "utf-8");
|
|
9466
10437
|
try {
|
|
9467
10438
|
return JSON.parse(content);
|
|
9468
10439
|
} catch (error) {
|
|
@@ -9476,23 +10447,23 @@ function isRecord2(value) {
|
|
|
9476
10447
|
function isStringArray3(value) {
|
|
9477
10448
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
9478
10449
|
}
|
|
9479
|
-
function asPositiveNumber(value,
|
|
10450
|
+
function asPositiveNumber(value, path27) {
|
|
9480
10451
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
9481
|
-
throw new Error(`${
|
|
10452
|
+
throw new Error(`${path27} must be a non-negative number`);
|
|
9482
10453
|
}
|
|
9483
10454
|
return value;
|
|
9484
10455
|
}
|
|
9485
|
-
function parseQueryType(value,
|
|
10456
|
+
function parseQueryType(value, path27) {
|
|
9486
10457
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
9487
10458
|
return value;
|
|
9488
10459
|
}
|
|
9489
10460
|
throw new Error(
|
|
9490
|
-
`${
|
|
10461
|
+
`${path27} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
9491
10462
|
);
|
|
9492
10463
|
}
|
|
9493
|
-
function parseExpected(input,
|
|
10464
|
+
function parseExpected(input, path27) {
|
|
9494
10465
|
if (!isRecord2(input)) {
|
|
9495
|
-
throw new Error(`${
|
|
10466
|
+
throw new Error(`${path27} must be an object`);
|
|
9496
10467
|
}
|
|
9497
10468
|
const filePathRaw = input.filePath;
|
|
9498
10469
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -9501,16 +10472,16 @@ function parseExpected(input, path26) {
|
|
|
9501
10472
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
9502
10473
|
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
9503
10474
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
9504
|
-
throw new Error(`${
|
|
10475
|
+
throw new Error(`${path27} must include either expected.filePath or expected.acceptableFiles`);
|
|
9505
10476
|
}
|
|
9506
10477
|
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
9507
|
-
throw new Error(`${
|
|
10478
|
+
throw new Error(`${path27}.acceptableFiles must be an array of strings`);
|
|
9508
10479
|
}
|
|
9509
10480
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
9510
|
-
throw new Error(`${
|
|
10481
|
+
throw new Error(`${path27}.symbol must be a string when provided`);
|
|
9511
10482
|
}
|
|
9512
10483
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
9513
|
-
throw new Error(`${
|
|
10484
|
+
throw new Error(`${path27}.branch must be a string when provided`);
|
|
9514
10485
|
}
|
|
9515
10486
|
return {
|
|
9516
10487
|
filePath,
|
|
@@ -9520,25 +10491,25 @@ function parseExpected(input, path26) {
|
|
|
9520
10491
|
};
|
|
9521
10492
|
}
|
|
9522
10493
|
function parseQuery(input, index) {
|
|
9523
|
-
const
|
|
10494
|
+
const path27 = `queries[${index}]`;
|
|
9524
10495
|
if (!isRecord2(input)) {
|
|
9525
|
-
throw new Error(`${
|
|
10496
|
+
throw new Error(`${path27} must be an object`);
|
|
9526
10497
|
}
|
|
9527
10498
|
const id = input.id;
|
|
9528
10499
|
const query = input.query;
|
|
9529
10500
|
const queryType = input.queryType;
|
|
9530
10501
|
const expected = input.expected;
|
|
9531
10502
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
9532
|
-
throw new Error(`${
|
|
10503
|
+
throw new Error(`${path27}.id must be a non-empty string`);
|
|
9533
10504
|
}
|
|
9534
10505
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
9535
|
-
throw new Error(`${
|
|
10506
|
+
throw new Error(`${path27}.query must be a non-empty string`);
|
|
9536
10507
|
}
|
|
9537
10508
|
return {
|
|
9538
10509
|
id,
|
|
9539
10510
|
query,
|
|
9540
|
-
queryType: parseQueryType(queryType, `${
|
|
9541
|
-
expected: parseExpected(expected, `${
|
|
10511
|
+
queryType: parseQueryType(queryType, `${path27}.queryType`),
|
|
10512
|
+
expected: parseExpected(expected, `${path27}.expected`)
|
|
9542
10513
|
};
|
|
9543
10514
|
}
|
|
9544
10515
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -9721,13 +10692,13 @@ async function runEvaluation(options) {
|
|
|
9721
10692
|
};
|
|
9722
10693
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
9723
10694
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
9724
|
-
writeJson(
|
|
9725
|
-
writeJson(
|
|
10695
|
+
writeJson(path15.join(outputDir, "summary.json"), summary);
|
|
10696
|
+
writeJson(path15.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
9726
10697
|
let comparison;
|
|
9727
10698
|
if (againstPath) {
|
|
9728
10699
|
const baseline = loadSummary(againstPath);
|
|
9729
10700
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
9730
|
-
writeJson(
|
|
10701
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9731
10702
|
}
|
|
9732
10703
|
let gate;
|
|
9733
10704
|
if (options.ciMode) {
|
|
@@ -9737,10 +10708,10 @@ async function runEvaluation(options) {
|
|
|
9737
10708
|
const budget = loadBudget(budgetPath);
|
|
9738
10709
|
if (!comparison && budget.baselinePath) {
|
|
9739
10710
|
const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
|
|
9740
|
-
if ((0,
|
|
10711
|
+
if ((0, import_fs11.existsSync)(resolvedBaseline)) {
|
|
9741
10712
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
9742
10713
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
9743
|
-
writeJson(
|
|
10714
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9744
10715
|
} else if (budget.failOnMissingBaseline) {
|
|
9745
10716
|
throw new Error(
|
|
9746
10717
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -9750,7 +10721,7 @@ async function runEvaluation(options) {
|
|
|
9750
10721
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
9751
10722
|
}
|
|
9752
10723
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
9753
|
-
writeText(
|
|
10724
|
+
writeText(path15.join(outputDir, "summary.md"), markdown);
|
|
9754
10725
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
9755
10726
|
} finally {
|
|
9756
10727
|
await indexer.close();
|
|
@@ -9808,23 +10779,23 @@ async function runSweep(options, sweep) {
|
|
|
9808
10779
|
bestByMrrAt10,
|
|
9809
10780
|
bestByP95Latency
|
|
9810
10781
|
};
|
|
9811
|
-
writeJson(
|
|
10782
|
+
writeJson(path15.join(outputDir, "compare.json"), aggregate);
|
|
9812
10783
|
const md = createSummaryMarkdown(
|
|
9813
10784
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
9814
10785
|
bestByHitAt5?.comparison,
|
|
9815
10786
|
void 0,
|
|
9816
10787
|
aggregate
|
|
9817
10788
|
);
|
|
9818
|
-
writeText(
|
|
9819
|
-
writeJson(
|
|
10789
|
+
writeText(path15.join(outputDir, "summary.md"), md);
|
|
10790
|
+
writeJson(path15.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
9820
10791
|
return { outputDir, aggregate };
|
|
9821
10792
|
}
|
|
9822
10793
|
|
|
9823
10794
|
// src/eval/cli.ts
|
|
9824
|
-
var
|
|
10795
|
+
var path17 = __toESM(require("path"), 1);
|
|
9825
10796
|
|
|
9826
10797
|
// src/eval/cli-parser.ts
|
|
9827
|
-
var
|
|
10798
|
+
var path16 = __toESM(require("path"), 1);
|
|
9828
10799
|
function printUsage() {
|
|
9829
10800
|
console.log(`
|
|
9830
10801
|
Usage:
|
|
@@ -9896,12 +10867,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9896
10867
|
const arg = argv[i];
|
|
9897
10868
|
const next = argv[i + 1];
|
|
9898
10869
|
if (arg === "--project" && next) {
|
|
9899
|
-
parsed.projectRoot =
|
|
10870
|
+
parsed.projectRoot = path16.resolve(cwd, next);
|
|
9900
10871
|
i += 1;
|
|
9901
10872
|
continue;
|
|
9902
10873
|
}
|
|
9903
10874
|
if (arg === "--config" && next) {
|
|
9904
|
-
parsed.configPath =
|
|
10875
|
+
parsed.configPath = path16.resolve(cwd, next);
|
|
9905
10876
|
i += 1;
|
|
9906
10877
|
continue;
|
|
9907
10878
|
}
|
|
@@ -10097,22 +11068,22 @@ async function handleEvalCommand(args, cwd) {
|
|
|
10097
11068
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
10098
11069
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
10099
11070
|
}
|
|
10100
|
-
const currentSummary = loadSummary(
|
|
11071
|
+
const currentSummary = loadSummary(path17.resolve(parsed.projectRoot, currentPath), {
|
|
10101
11072
|
allowLegacyDiversityMetrics: true
|
|
10102
11073
|
});
|
|
10103
|
-
const baselineSummary = loadSummary(
|
|
11074
|
+
const baselineSummary = loadSummary(path17.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
10104
11075
|
allowLegacyDiversityMetrics: true
|
|
10105
11076
|
});
|
|
10106
11077
|
const comparison = compareSummaries(
|
|
10107
11078
|
currentSummary,
|
|
10108
11079
|
baselineSummary,
|
|
10109
|
-
|
|
11080
|
+
path17.resolve(parsed.projectRoot, parsed.againstPath)
|
|
10110
11081
|
);
|
|
10111
|
-
const outputDir = createRunDirectory(
|
|
11082
|
+
const outputDir = createRunDirectory(path17.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
10112
11083
|
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
10113
|
-
writeJson(
|
|
10114
|
-
writeText(
|
|
10115
|
-
writeJson(
|
|
11084
|
+
writeJson(path17.join(outputDir, "compare.json"), comparison);
|
|
11085
|
+
writeText(path17.join(outputDir, "summary.md"), summaryMd);
|
|
11086
|
+
writeJson(path17.join(outputDir, "summary.json"), currentSummary);
|
|
10116
11087
|
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
10117
11088
|
return 0;
|
|
10118
11089
|
}
|
|
@@ -10121,7 +11092,7 @@ async function handleEvalCommand(args, cwd) {
|
|
|
10121
11092
|
|
|
10122
11093
|
// src/mcp-server.ts
|
|
10123
11094
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
10124
|
-
var
|
|
11095
|
+
var import_fs15 = require("fs");
|
|
10125
11096
|
|
|
10126
11097
|
// src/mcp-server/register-prompts.ts
|
|
10127
11098
|
var import_zod = require("zod");
|
|
@@ -10312,6 +11283,10 @@ function formatStatus(status) {
|
|
|
10312
11283
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
10313
11284
|
}
|
|
10314
11285
|
}
|
|
11286
|
+
if (status.warning) {
|
|
11287
|
+
lines.push("");
|
|
11288
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
11289
|
+
}
|
|
10315
11290
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
10316
11291
|
lines.push("");
|
|
10317
11292
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -10417,16 +11392,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
|
10417
11392
|
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
10418
11393
|
}).join("\n");
|
|
10419
11394
|
}
|
|
10420
|
-
function formatCallGraphPath(from, to,
|
|
10421
|
-
if (
|
|
11395
|
+
function formatCallGraphPath(from, to, path27) {
|
|
11396
|
+
if (path27.length === 0) {
|
|
10422
11397
|
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
10423
11398
|
}
|
|
10424
|
-
const formatted =
|
|
11399
|
+
const formatted = path27.map((hop, index) => {
|
|
10425
11400
|
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10426
11401
|
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10427
11402
|
return `${prefix} ${hop.symbolName}${location}`;
|
|
10428
11403
|
});
|
|
10429
|
-
return `Path (${
|
|
11404
|
+
return `Path (${path27.length} hops):
|
|
10430
11405
|
${formatted.join("\n")}`;
|
|
10431
11406
|
}
|
|
10432
11407
|
function formatResultHeader(result, index) {
|
|
@@ -10517,12 +11492,12 @@ function formatPrImpact(result) {
|
|
|
10517
11492
|
}
|
|
10518
11493
|
|
|
10519
11494
|
// src/tools/operations.ts
|
|
10520
|
-
var
|
|
10521
|
-
var
|
|
11495
|
+
var import_fs14 = require("fs");
|
|
11496
|
+
var path21 = __toESM(require("path"), 1);
|
|
10522
11497
|
|
|
10523
11498
|
// src/config/merger.ts
|
|
10524
|
-
var
|
|
10525
|
-
var
|
|
11499
|
+
var import_fs12 = require("fs");
|
|
11500
|
+
var path18 = __toESM(require("path"), 1);
|
|
10526
11501
|
var PROJECT_OVERRIDE_KEYS = [
|
|
10527
11502
|
"embeddingProvider",
|
|
10528
11503
|
"customProvider",
|
|
@@ -10555,8 +11530,8 @@ function mergeUniqueStringArray(values) {
|
|
|
10555
11530
|
return [...new Set(values.map((value) => String(value).trim()))];
|
|
10556
11531
|
}
|
|
10557
11532
|
function normalizeKnowledgeBasePath(value) {
|
|
10558
|
-
let normalized =
|
|
10559
|
-
const root =
|
|
11533
|
+
let normalized = path18.normalize(String(value).trim());
|
|
11534
|
+
const root = path18.parse(normalized).root;
|
|
10560
11535
|
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
10561
11536
|
normalized = normalized.slice(0, -1);
|
|
10562
11537
|
}
|
|
@@ -10590,11 +11565,11 @@ function validateConfigLayerShape(rawConfig, filePath) {
|
|
|
10590
11565
|
return rawConfig;
|
|
10591
11566
|
}
|
|
10592
11567
|
function loadJsonFile(filePath) {
|
|
10593
|
-
if (!(0,
|
|
11568
|
+
if (!(0, import_fs12.existsSync)(filePath)) {
|
|
10594
11569
|
return null;
|
|
10595
11570
|
}
|
|
10596
11571
|
try {
|
|
10597
|
-
const content = (0,
|
|
11572
|
+
const content = (0, import_fs12.readFileSync)(filePath, "utf-8");
|
|
10598
11573
|
return validateConfigLayerShape(JSON.parse(content), filePath);
|
|
10599
11574
|
} catch (error) {
|
|
10600
11575
|
if (error instanceof Error && error.message.startsWith("Config file ")) {
|
|
@@ -10609,8 +11584,8 @@ function loadConfigFile(filePath) {
|
|
|
10609
11584
|
}
|
|
10610
11585
|
function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
|
|
10611
11586
|
const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
|
|
10612
|
-
(0,
|
|
10613
|
-
(0,
|
|
11587
|
+
(0, import_fs12.mkdirSync)(path18.dirname(localConfigPath), { recursive: true });
|
|
11588
|
+
(0, import_fs12.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
10614
11589
|
return localConfigPath;
|
|
10615
11590
|
}
|
|
10616
11591
|
function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
@@ -10620,7 +11595,7 @@ function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
|
10620
11595
|
return {};
|
|
10621
11596
|
}
|
|
10622
11597
|
const normalizedConfig = { ...projectConfig };
|
|
10623
|
-
const projectConfigBaseDir =
|
|
11598
|
+
const projectConfigBaseDir = path18.dirname(path18.dirname(projectConfigPath));
|
|
10624
11599
|
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
10625
11600
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
10626
11601
|
normalizedConfig.knowledgeBases,
|
|
@@ -10684,19 +11659,19 @@ function loadMergedConfig(projectRoot, host = "opencode") {
|
|
|
10684
11659
|
}
|
|
10685
11660
|
|
|
10686
11661
|
// src/tools/knowledge-base-paths.ts
|
|
10687
|
-
var
|
|
11662
|
+
var path19 = __toESM(require("path"), 1);
|
|
10688
11663
|
function resolveConfigPathValue(value, baseDir) {
|
|
10689
11664
|
const trimmed = value.trim();
|
|
10690
11665
|
if (!trimmed) {
|
|
10691
11666
|
return trimmed;
|
|
10692
11667
|
}
|
|
10693
|
-
const absolutePath =
|
|
10694
|
-
return
|
|
11668
|
+
const absolutePath = path19.isAbsolute(trimmed) ? trimmed : path19.resolve(baseDir, trimmed);
|
|
11669
|
+
return path19.normalize(absolutePath);
|
|
10695
11670
|
}
|
|
10696
11671
|
|
|
10697
11672
|
// src/tools/config-state.ts
|
|
10698
|
-
var
|
|
10699
|
-
var
|
|
11673
|
+
var import_fs13 = require("fs");
|
|
11674
|
+
var path20 = __toESM(require("path"), 1);
|
|
10700
11675
|
function normalizeKnowledgeBasePaths(config, projectRoot) {
|
|
10701
11676
|
const normalized = { ...config };
|
|
10702
11677
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
@@ -10720,6 +11695,24 @@ function loadRuntimeConfig(projectRoot, host = "opencode") {
|
|
|
10720
11695
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
10721
11696
|
var configCache = /* @__PURE__ */ new Map();
|
|
10722
11697
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
11698
|
+
function getIndexBusyResult(error) {
|
|
11699
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
11700
|
+
const owner = error.owner;
|
|
11701
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
11702
|
+
if (error.reason === "legacy-lock") {
|
|
11703
|
+
return {
|
|
11704
|
+
kind: "busy",
|
|
11705
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
11706
|
+
};
|
|
11707
|
+
}
|
|
11708
|
+
if (error.reason === "unknown-owner") {
|
|
11709
|
+
return {
|
|
11710
|
+
kind: "busy",
|
|
11711
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
11712
|
+
};
|
|
11713
|
+
}
|
|
11714
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
11715
|
+
}
|
|
10723
11716
|
function getProjectRoot(projectRoot, host) {
|
|
10724
11717
|
if (projectRoot) {
|
|
10725
11718
|
return projectRoot;
|
|
@@ -10764,8 +11757,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
|
|
|
10764
11757
|
}
|
|
10765
11758
|
function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
|
|
10766
11759
|
const root = getProjectRoot(projectRoot, host);
|
|
10767
|
-
const localIndexPath =
|
|
10768
|
-
if ((0,
|
|
11760
|
+
const localIndexPath = path21.join(root, getHostProjectIndexRelativePath(host));
|
|
11761
|
+
if ((0, import_fs14.existsSync)(localIndexPath)) {
|
|
10769
11762
|
return false;
|
|
10770
11763
|
}
|
|
10771
11764
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
@@ -10821,35 +11814,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
|
|
|
10821
11814
|
async function runIndexCodebase(projectRoot, host, args, onProgress) {
|
|
10822
11815
|
const root = getProjectRoot(projectRoot, host);
|
|
10823
11816
|
const key = getIndexerCacheKey(root, host);
|
|
10824
|
-
const cachedConfig = configCache.get(key);
|
|
10825
11817
|
let indexer = getIndexerForProject(root, host);
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
chunksProcessed: progress.chunksProcessed,
|
|
10846
|
-
totalChunks: progress.totalChunks,
|
|
10847
|
-
percentage: calculatePercentage(progress)
|
|
11818
|
+
const runtimeConfig = configCache.get(key);
|
|
11819
|
+
try {
|
|
11820
|
+
if (args.estimateOnly) {
|
|
11821
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
11822
|
+
}
|
|
11823
|
+
const runIndex = async (target) => {
|
|
11824
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
11825
|
+
return operation((progress) => {
|
|
11826
|
+
if (onProgress) {
|
|
11827
|
+
return onProgress(formatProgressTitle(progress), {
|
|
11828
|
+
phase: progress.phase,
|
|
11829
|
+
filesProcessed: progress.filesProcessed,
|
|
11830
|
+
totalFiles: progress.totalFiles,
|
|
11831
|
+
chunksProcessed: progress.chunksProcessed,
|
|
11832
|
+
totalChunks: progress.totalChunks,
|
|
11833
|
+
percentage: calculatePercentage(progress)
|
|
11834
|
+
});
|
|
11835
|
+
}
|
|
11836
|
+
return Promise.resolve();
|
|
10848
11837
|
});
|
|
11838
|
+
};
|
|
11839
|
+
let stats;
|
|
11840
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
11841
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
11842
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
11843
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
11844
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
11845
|
+
indexer = getIndexerForProject(root, host);
|
|
11846
|
+
return runIndex(indexer);
|
|
11847
|
+
}, { completeRecoveries: false });
|
|
11848
|
+
} else {
|
|
11849
|
+
stats = await runIndex(indexer);
|
|
10849
11850
|
}
|
|
10850
|
-
return
|
|
10851
|
-
})
|
|
10852
|
-
|
|
11851
|
+
return { kind: "stats", stats };
|
|
11852
|
+
} catch (error) {
|
|
11853
|
+
const busyResult = getIndexBusyResult(error);
|
|
11854
|
+
if (!busyResult) throw error;
|
|
11855
|
+
return busyResult;
|
|
11856
|
+
}
|
|
10853
11857
|
}
|
|
10854
11858
|
async function getIndexStatus(projectRoot, host) {
|
|
10855
11859
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
@@ -10859,6 +11863,15 @@ async function getIndexHealthCheck(projectRoot, host) {
|
|
|
10859
11863
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10860
11864
|
return indexer.healthCheck();
|
|
10861
11865
|
}
|
|
11866
|
+
async function runIndexHealthCheck(projectRoot, host) {
|
|
11867
|
+
try {
|
|
11868
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
|
|
11869
|
+
} catch (error) {
|
|
11870
|
+
const busyResult = getIndexBusyResult(error);
|
|
11871
|
+
if (!busyResult) throw error;
|
|
11872
|
+
return busyResult;
|
|
11873
|
+
}
|
|
11874
|
+
}
|
|
10862
11875
|
async function getPrImpact(projectRoot, host, params) {
|
|
10863
11876
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10864
11877
|
return indexer.getPrImpact({
|
|
@@ -10939,20 +11952,87 @@ var CHUNK_TYPE_ENUM = [
|
|
|
10939
11952
|
];
|
|
10940
11953
|
|
|
10941
11954
|
// src/mcp-server/register-tools.ts
|
|
11955
|
+
function allowNullAsUndefined(schema) {
|
|
11956
|
+
return import_zod2.z.preprocess((value) => value === null ? void 0 : value, schema);
|
|
11957
|
+
}
|
|
10942
11958
|
function registerMcpTools(server, runtime) {
|
|
11959
|
+
server.tool(
|
|
11960
|
+
"codebase_context",
|
|
11961
|
+
"PREFERRED FIRST TOOL for any question about this repository. Use before built-in code search, grep, shell search, or broad file reads. Provide from+to for a dependency path, symbol for a definition, or only query for low-token conceptual discovery. Use call_graph directly for callers or callees.",
|
|
11962
|
+
{
|
|
11963
|
+
query: import_zod2.z.string().describe("The codebase question or behavior to locate. Always provide the user's repository question here."),
|
|
11964
|
+
from: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Source symbol. For dependency-path questions, extract the first endpoint and provide it here."),
|
|
11965
|
+
to: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Target symbol. For dependency-path questions, extract the second endpoint and provide it here."),
|
|
11966
|
+
symbol: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Exact symbol for an authoritative definition lookup. Omit when from and to are supplied."),
|
|
11967
|
+
limit: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum number of search or definition results"),
|
|
11968
|
+
maxDepth: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum call-graph traversal depth for from/to path lookup"),
|
|
11969
|
+
fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
11970
|
+
directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')")
|
|
11971
|
+
},
|
|
11972
|
+
async (args) => {
|
|
11973
|
+
if (args.from && args.to) {
|
|
11974
|
+
const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
|
|
11975
|
+
if (path27.length > 0) {
|
|
11976
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
11977
|
+
}
|
|
11978
|
+
const { callers } = await getCallGraphData(runtime.projectRoot, runtime.host, {
|
|
11979
|
+
name: args.to,
|
|
11980
|
+
direction: "callers"
|
|
11981
|
+
});
|
|
11982
|
+
const directEdge = callers.find((edge) => edge.fromSymbolName === args.from);
|
|
11983
|
+
if (directEdge) {
|
|
11984
|
+
const location = directEdge.fromSymbolFilePath ? ` at ${directEdge.fromSymbolFilePath}:${directEdge.line}` : "";
|
|
11985
|
+
return {
|
|
11986
|
+
content: [{
|
|
11987
|
+
type: "text",
|
|
11988
|
+
text: `Direct path: ${args.from} --${directEdge.callType}--> ${args.to}${location} (edge is ${directEdge.isResolved ? "resolved" : "unresolved"}).`
|
|
11989
|
+
}]
|
|
11990
|
+
};
|
|
11991
|
+
}
|
|
11992
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
11993
|
+
}
|
|
11994
|
+
if (args.symbol) {
|
|
11995
|
+
const results2 = await implementationLookup(runtime.projectRoot, runtime.host, args.symbol, {
|
|
11996
|
+
limit: args.limit ?? 10,
|
|
11997
|
+
fileType: args.fileType,
|
|
11998
|
+
directory: args.directory
|
|
11999
|
+
});
|
|
12000
|
+
return { content: [{ type: "text", text: formatDefinitionLookup(results2, args.symbol) }] };
|
|
12001
|
+
}
|
|
12002
|
+
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
12003
|
+
limit: args.limit ?? 10,
|
|
12004
|
+
fileType: args.fileType,
|
|
12005
|
+
directory: args.directory,
|
|
12006
|
+
metadataOnly: true
|
|
12007
|
+
});
|
|
12008
|
+
if (results.length === 0) {
|
|
12009
|
+
return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
|
|
12010
|
+
}
|
|
12011
|
+
return {
|
|
12012
|
+
content: [{
|
|
12013
|
+
type: "text",
|
|
12014
|
+
text: `Found ${results.length} locations for "${args.query}":
|
|
12015
|
+
|
|
12016
|
+
${formatCodebasePeek(results)}
|
|
12017
|
+
|
|
12018
|
+
Use implementation_lookup for an authoritative definition, or call call_graph/call_graph_path once you have a symbol.`
|
|
12019
|
+
}]
|
|
12020
|
+
};
|
|
12021
|
+
}
|
|
12022
|
+
);
|
|
10943
12023
|
server.tool(
|
|
10944
12024
|
"codebase_search",
|
|
10945
|
-
"
|
|
12025
|
+
"FULL-CONTENT semantic retrieval. Use after codebase_peek when you need implementation text, not as the default first step. For exact identifiers or exhaustive matches use grep instead.",
|
|
10946
12026
|
{
|
|
10947
12027
|
query: import_zod2.z.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
|
|
10948
|
-
limit: import_zod2.z.number().optional().default(5).describe("Maximum number of results to return"),
|
|
10949
|
-
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10950
|
-
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10951
|
-
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
10952
|
-
contextLines: import_zod2.z.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
|
|
10953
|
-
blameAuthor: import_zod2.z.string().optional().describe("Filter by git blame author name or email"),
|
|
10954
|
-
blameSha: import_zod2.z.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
10955
|
-
blameSince: import_zod2.z.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
12028
|
+
limit: allowNullAsUndefined(import_zod2.z.number().optional().default(5)).describe("Maximum number of results to return"),
|
|
12029
|
+
fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12030
|
+
directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12031
|
+
chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
|
|
12032
|
+
contextLines: allowNullAsUndefined(import_zod2.z.number().optional()).describe("Number of extra lines to include before/after each match (default: 0)"),
|
|
12033
|
+
blameAuthor: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame author name or email"),
|
|
12034
|
+
blameSha: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame commit SHA or prefix"),
|
|
12035
|
+
blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date")
|
|
10956
12036
|
},
|
|
10957
12037
|
async (args) => {
|
|
10958
12038
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -10975,16 +12055,16 @@ ${formatSearchResults(results, "score")}` }] };
|
|
|
10975
12055
|
);
|
|
10976
12056
|
server.tool(
|
|
10977
12057
|
"codebase_peek",
|
|
10978
|
-
"
|
|
12058
|
+
"DIRECT LOW-TOKEN semantic location lookup for unfamiliar-code discovery. Prefer codebase_context when the request may involve definitions or graph navigation; use this specialized tool when you only need conceptual locations.",
|
|
10979
12059
|
{
|
|
10980
12060
|
query: import_zod2.z.string().describe("Natural language description of what code you're looking for."),
|
|
10981
|
-
limit: import_zod2.z.number().optional().default(10).describe("Maximum number of results to return"),
|
|
10982
|
-
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10983
|
-
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10984
|
-
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
10985
|
-
blameAuthor: import_zod2.z.string().optional().describe("Filter by git blame author name or email"),
|
|
10986
|
-
blameSha: import_zod2.z.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
10987
|
-
blameSince: import_zod2.z.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
12061
|
+
limit: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum number of results to return"),
|
|
12062
|
+
fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12063
|
+
directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12064
|
+
chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
|
|
12065
|
+
blameAuthor: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame author name or email"),
|
|
12066
|
+
blameSha: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame commit SHA or prefix"),
|
|
12067
|
+
blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date")
|
|
10988
12068
|
},
|
|
10989
12069
|
async (args) => {
|
|
10990
12070
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -11009,21 +12089,26 @@ Use Read tool to examine specific files.` }] };
|
|
|
11009
12089
|
);
|
|
11010
12090
|
server.tool(
|
|
11011
12091
|
"index_codebase",
|
|
11012
|
-
"
|
|
12092
|
+
"Create or refresh the semantic index. Call index_status first when readiness is unknown, then use this tool only if the index is missing, stale, or incompatible. Incremental by default; force=true rebuilds everything.",
|
|
11013
12093
|
{
|
|
11014
|
-
force: import_zod2.z.boolean().optional().default(false).describe("Force reindex even if already indexed"),
|
|
11015
|
-
estimateOnly: import_zod2.z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
|
|
11016
|
-
verbose: import_zod2.z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
|
|
12094
|
+
force: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Force reindex even if already indexed"),
|
|
12095
|
+
estimateOnly: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Only show cost estimate without indexing"),
|
|
12096
|
+
verbose: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Show detailed info about skipped files and parsing failures")
|
|
11017
12097
|
},
|
|
11018
12098
|
async (args) => {
|
|
11019
12099
|
const result = await runIndexCodebase(runtime.projectRoot, runtime.host, args);
|
|
11020
|
-
|
|
11021
|
-
|
|
12100
|
+
if (result.kind === "estimate") {
|
|
12101
|
+
return { content: [{ type: "text", text: formatCostEstimate(result.estimate) }] };
|
|
12102
|
+
}
|
|
12103
|
+
if (result.kind === "busy") {
|
|
12104
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12105
|
+
}
|
|
12106
|
+
return { content: [{ type: "text", text: formatIndexStats(result.stats, args.verbose ?? false) }] };
|
|
11022
12107
|
}
|
|
11023
12108
|
);
|
|
11024
12109
|
server.tool(
|
|
11025
12110
|
"index_status",
|
|
11026
|
-
"
|
|
12111
|
+
"START HERE once per repository task when index readiness or freshness is unknown. Reports whether semantic retrieval is ready, chunk counts, compatibility, and the embedding provider. If ready, continue with codebase_peek or implementation_lookup; otherwise run index_codebase.",
|
|
11027
12112
|
{},
|
|
11028
12113
|
async () => {
|
|
11029
12114
|
const status = await getIndexStatus(runtime.projectRoot, runtime.host);
|
|
@@ -11035,8 +12120,11 @@ Use Read tool to examine specific files.` }] };
|
|
|
11035
12120
|
"Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
|
|
11036
12121
|
{},
|
|
11037
12122
|
async () => {
|
|
11038
|
-
const result = await
|
|
11039
|
-
|
|
12123
|
+
const result = await runIndexHealthCheck(runtime.projectRoot, runtime.host);
|
|
12124
|
+
if (result.kind === "busy") {
|
|
12125
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12126
|
+
}
|
|
12127
|
+
return { content: [{ type: "text", text: formatHealthCheck(result.health) }] };
|
|
11040
12128
|
}
|
|
11041
12129
|
);
|
|
11042
12130
|
server.tool(
|
|
@@ -11052,9 +12140,13 @@ Use Read tool to examine specific files.` }] };
|
|
|
11052
12140
|
"index_logs",
|
|
11053
12141
|
"Get recent debug logs from the codebase indexer. Requires debug.enabled=true in config.",
|
|
11054
12142
|
{
|
|
11055
|
-
limit: import_zod2.z.number().optional().default(20).describe("Maximum number of log entries to return"),
|
|
11056
|
-
category:
|
|
11057
|
-
|
|
12143
|
+
limit: allowNullAsUndefined(import_zod2.z.number().optional().default(20)).describe("Maximum number of log entries to return"),
|
|
12144
|
+
category: allowNullAsUndefined(
|
|
12145
|
+
import_zod2.z.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional()
|
|
12146
|
+
).describe("Filter by log category"),
|
|
12147
|
+
level: allowNullAsUndefined(
|
|
12148
|
+
import_zod2.z.enum(["error", "warn", "info", "debug"]).optional()
|
|
12149
|
+
).describe("Filter by minimum log level")
|
|
11058
12150
|
},
|
|
11059
12151
|
async (args) => {
|
|
11060
12152
|
const result = await getIndexLogs(runtime.projectRoot, runtime.host, args);
|
|
@@ -11063,14 +12155,14 @@ Use Read tool to examine specific files.` }] };
|
|
|
11063
12155
|
);
|
|
11064
12156
|
server.tool(
|
|
11065
12157
|
"find_similar",
|
|
11066
|
-
"
|
|
12158
|
+
"Use when you already have a code snippet and need analogous implementations, duplicates, patterns, or refactoring candidates. For a natural-language concept without example code, start with codebase_peek instead.",
|
|
11067
12159
|
{
|
|
11068
12160
|
code: import_zod2.z.string().describe("The code snippet to find similar code for"),
|
|
11069
|
-
limit: import_zod2.z.number().optional().default(10).describe("Maximum number of results to return"),
|
|
11070
|
-
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
11071
|
-
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
11072
|
-
chunkType: import_zod2.z.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
11073
|
-
excludeFile: import_zod2.z.string().optional().describe("Exclude results from this file path")
|
|
12161
|
+
limit: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum number of results to return"),
|
|
12162
|
+
fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12163
|
+
directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12164
|
+
chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
|
|
12165
|
+
excludeFile: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Exclude results from this file path")
|
|
11074
12166
|
},
|
|
11075
12167
|
async (args) => {
|
|
11076
12168
|
const results = await findSimilarCode(runtime.projectRoot, runtime.host, args.code, {
|
|
@@ -11090,12 +12182,12 @@ ${formatSearchResults(results)}` }] };
|
|
|
11090
12182
|
);
|
|
11091
12183
|
server.tool(
|
|
11092
12184
|
"implementation_lookup",
|
|
11093
|
-
"
|
|
12185
|
+
"FIRST TOOL only for known-symbol definition questions. Returns authoritative source locations and prefers implementations over tests, docs, examples, and fixtures. Do not use for callers, callees, dependency paths, or code flow; use codebase_context with direction or from/to for those questions.",
|
|
11094
12186
|
{
|
|
11095
12187
|
query: import_zod2.z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
|
|
11096
|
-
limit: import_zod2.z.number().optional().default(5).describe("Maximum number of results"),
|
|
11097
|
-
fileType: import_zod2.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
11098
|
-
directory: import_zod2.z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
|
|
12188
|
+
limit: allowNullAsUndefined(import_zod2.z.number().optional().default(5)).describe("Maximum number of results"),
|
|
12189
|
+
fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
12190
|
+
directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils')")
|
|
11099
12191
|
},
|
|
11100
12192
|
async (args) => {
|
|
11101
12193
|
const results = await implementationLookup(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -11108,12 +12200,16 @@ ${formatSearchResults(results)}` }] };
|
|
|
11108
12200
|
);
|
|
11109
12201
|
server.tool(
|
|
11110
12202
|
"call_graph",
|
|
11111
|
-
"
|
|
12203
|
+
"Use after identifying a symbol to find its direct callers or callees and understand code flow. Use implementation_lookup first if the symbol or definition is still ambiguous. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
|
|
11112
12204
|
{
|
|
11113
12205
|
name: import_zod2.z.string().describe("Function or method name to query"),
|
|
11114
|
-
direction:
|
|
11115
|
-
|
|
11116
|
-
|
|
12206
|
+
direction: allowNullAsUndefined(
|
|
12207
|
+
import_zod2.z.enum(["callers", "callees"]).default("callers")
|
|
12208
|
+
).describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
12209
|
+
symbolId: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Symbol ID (required for 'callees' direction)"),
|
|
12210
|
+
relationshipType: allowNullAsUndefined(
|
|
12211
|
+
import_zod2.z.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional()
|
|
12212
|
+
).describe("Filter by relationship type. Omit to show all.")
|
|
11117
12213
|
},
|
|
11118
12214
|
async (args) => {
|
|
11119
12215
|
if (args.direction === "callees") {
|
|
@@ -11129,27 +12225,29 @@ ${formatSearchResults(results)}` }] };
|
|
|
11129
12225
|
);
|
|
11130
12226
|
server.tool(
|
|
11131
12227
|
"call_graph_path",
|
|
11132
|
-
"
|
|
12228
|
+
"Use after identifying both endpoint symbols to find the shortest known call path between them. Use codebase_peek or implementation_lookup first when either endpoint is unknown.",
|
|
11133
12229
|
{
|
|
11134
12230
|
from: import_zod2.z.string().describe("Source function/method name (starting point)"),
|
|
11135
12231
|
to: import_zod2.z.string().describe("Target function/method name (destination)"),
|
|
11136
|
-
maxDepth: import_zod2.z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
|
|
12232
|
+
maxDepth: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Maximum traversal depth (default: 10)")
|
|
11137
12233
|
},
|
|
11138
12234
|
async (args) => {
|
|
11139
|
-
const
|
|
11140
|
-
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to,
|
|
12235
|
+
const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
|
|
12236
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
11141
12237
|
}
|
|
11142
12238
|
);
|
|
11143
12239
|
server.tool(
|
|
11144
12240
|
"pr_impact",
|
|
11145
|
-
"
|
|
12241
|
+
"FIRST TOOL for pull-request or branch blast-radius questions. Analyzes changed files, affected symbols, transitive dependencies, communities, hub nodes, conflicts, and risk before merging.",
|
|
11146
12242
|
{
|
|
11147
|
-
pr: import_zod2.z.number().optional().describe("Pull request number to analyze"),
|
|
11148
|
-
branch: import_zod2.z.string().optional().describe("Branch name to analyze (defaults to current branch)"),
|
|
11149
|
-
maxDepth: import_zod2.z.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
|
|
11150
|
-
hubThreshold: import_zod2.z.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
|
|
11151
|
-
checkConflicts: import_zod2.z.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
|
|
11152
|
-
direction:
|
|
12243
|
+
pr: allowNullAsUndefined(import_zod2.z.number().optional()).describe("Pull request number to analyze"),
|
|
12244
|
+
branch: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Branch name to analyze (defaults to current branch)"),
|
|
12245
|
+
maxDepth: allowNullAsUndefined(import_zod2.z.number().optional().default(5)).describe("Maximum traversal depth for transitive callers (default: 5)"),
|
|
12246
|
+
hubThreshold: allowNullAsUndefined(import_zod2.z.number().optional().default(10)).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
|
|
12247
|
+
checkConflicts: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Check for conflicting open PRs touching the same communities (default: false)"),
|
|
12248
|
+
direction: allowNullAsUndefined(
|
|
12249
|
+
import_zod2.z.enum(["callers", "callees", "both"]).optional().default("both")
|
|
12250
|
+
).describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
|
|
11153
12251
|
},
|
|
11154
12252
|
async (args) => {
|
|
11155
12253
|
try {
|
|
@@ -11172,8 +12270,12 @@ ${formatSearchResults(results)}` }] };
|
|
|
11172
12270
|
|
|
11173
12271
|
// src/mcp-server.ts
|
|
11174
12272
|
var import_meta2 = {};
|
|
12273
|
+
function getServerInstructions(host) {
|
|
12274
|
+
const hostText = `host ${host}`;
|
|
12275
|
+
return `This MCP server is the preferred codebase-understanding path for ${hostText}. Start a repository task with index_status when index readiness or freshness is unknown. Use codebase_context as the preferred first entry point because it returns low-token locations first and routes to definitions or call-graph helpers when symbol intent is present. Use codebase_peek for direct conceptual location lookup, implementation_lookup for known-symbol definition questions, and codebase_search only when full semantic code content is needed. For exact identifiers or exhaustive matches, use grep. After identifying symbols, use call_graph or call_graph_path to trace dependencies. If the index is unavailable, run index_codebase, then retry the retrieval tool.`;
|
|
12276
|
+
}
|
|
11175
12277
|
function getPackageVersion() {
|
|
11176
|
-
const raw = JSON.parse((0,
|
|
12278
|
+
const raw = JSON.parse((0, import_fs15.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
|
|
11177
12279
|
if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
|
|
11178
12280
|
return raw.version;
|
|
11179
12281
|
}
|
|
@@ -11183,6 +12285,8 @@ function createMcpServer(projectRoot, config, host = "opencode") {
|
|
|
11183
12285
|
const server = new import_mcp.McpServer({
|
|
11184
12286
|
name: "opencode-codebase-index",
|
|
11185
12287
|
version: getPackageVersion()
|
|
12288
|
+
}, {
|
|
12289
|
+
instructions: getServerInstructions(host)
|
|
11186
12290
|
});
|
|
11187
12291
|
initializeTools(projectRoot, config, host);
|
|
11188
12292
|
registerMcpTools(server, {
|
|
@@ -11194,18 +12298,37 @@ function createMcpServer(projectRoot, config, host = "opencode") {
|
|
|
11194
12298
|
}
|
|
11195
12299
|
|
|
11196
12300
|
// src/utils/auto-index.ts
|
|
12301
|
+
var INITIAL_RETRY_DELAY_MS = 50;
|
|
12302
|
+
var MAX_RETRY_DELAY_MS = 500;
|
|
12303
|
+
var autoIndexStates = /* @__PURE__ */ new WeakMap();
|
|
11197
12304
|
function getErrorMessage3(error) {
|
|
11198
12305
|
return error instanceof Error ? error.message : String(error);
|
|
11199
12306
|
}
|
|
11200
|
-
function
|
|
11201
|
-
indexer.
|
|
11202
|
-
|
|
11203
|
-
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
11204
|
-
});
|
|
12307
|
+
function runAutoIndex(indexer, projectRoot, state) {
|
|
12308
|
+
void indexer.index().then(() => {
|
|
12309
|
+
autoIndexStates.delete(indexer);
|
|
11205
12310
|
}).catch((error) => {
|
|
11206
|
-
|
|
12311
|
+
if (!isTransientIndexLockContention(error)) {
|
|
12312
|
+
autoIndexStates.delete(indexer);
|
|
12313
|
+
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
12314
|
+
return;
|
|
12315
|
+
}
|
|
12316
|
+
const retryDelayMs = state.retryDelayMs;
|
|
12317
|
+
state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
|
|
12318
|
+
const retryTimer = setTimeout(() => {
|
|
12319
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12320
|
+
}, retryDelayMs);
|
|
12321
|
+
retryTimer.unref?.();
|
|
11207
12322
|
});
|
|
11208
12323
|
}
|
|
12324
|
+
function startAutoIndex(indexer, projectRoot) {
|
|
12325
|
+
if (autoIndexStates.has(indexer)) return;
|
|
12326
|
+
const state = {
|
|
12327
|
+
retryDelayMs: INITIAL_RETRY_DELAY_MS
|
|
12328
|
+
};
|
|
12329
|
+
autoIndexStates.set(indexer, state);
|
|
12330
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12331
|
+
}
|
|
11209
12332
|
|
|
11210
12333
|
// node_modules/chokidar/index.js
|
|
11211
12334
|
var import_node_events = require("events");
|
|
@@ -11297,7 +12420,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
11297
12420
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
11298
12421
|
const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
|
|
11299
12422
|
if (wantBigintFsStats) {
|
|
11300
|
-
this._stat = (
|
|
12423
|
+
this._stat = (path27) => statMethod(path27, { bigint: true });
|
|
11301
12424
|
} else {
|
|
11302
12425
|
this._stat = statMethod;
|
|
11303
12426
|
}
|
|
@@ -11322,8 +12445,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
11322
12445
|
const par = this.parent;
|
|
11323
12446
|
const fil = par && par.files;
|
|
11324
12447
|
if (fil && fil.length > 0) {
|
|
11325
|
-
const { path:
|
|
11326
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
12448
|
+
const { path: path27, depth } = par;
|
|
12449
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path27));
|
|
11327
12450
|
const awaited = await Promise.all(slice);
|
|
11328
12451
|
for (const entry of awaited) {
|
|
11329
12452
|
if (!entry)
|
|
@@ -11363,20 +12486,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
|
|
|
11363
12486
|
this.reading = false;
|
|
11364
12487
|
}
|
|
11365
12488
|
}
|
|
11366
|
-
async _exploreDir(
|
|
12489
|
+
async _exploreDir(path27, depth) {
|
|
11367
12490
|
let files;
|
|
11368
12491
|
try {
|
|
11369
|
-
files = await (0, import_promises.readdir)(
|
|
12492
|
+
files = await (0, import_promises.readdir)(path27, this._rdOptions);
|
|
11370
12493
|
} catch (error) {
|
|
11371
12494
|
this._onError(error);
|
|
11372
12495
|
}
|
|
11373
|
-
return { files, depth, path:
|
|
12496
|
+
return { files, depth, path: path27 };
|
|
11374
12497
|
}
|
|
11375
|
-
async _formatEntry(dirent,
|
|
12498
|
+
async _formatEntry(dirent, path27) {
|
|
11376
12499
|
let entry;
|
|
11377
12500
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
11378
12501
|
try {
|
|
11379
|
-
const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(
|
|
12502
|
+
const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path27, basename5));
|
|
11380
12503
|
entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
|
|
11381
12504
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
11382
12505
|
} catch (err) {
|
|
@@ -11776,16 +12899,16 @@ var delFromSet = (main2, prop, item) => {
|
|
|
11776
12899
|
};
|
|
11777
12900
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
11778
12901
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
11779
|
-
function createFsWatchInstance(
|
|
12902
|
+
function createFsWatchInstance(path27, options, listener, errHandler, emitRaw) {
|
|
11780
12903
|
const handleEvent = (rawEvent, evPath) => {
|
|
11781
|
-
listener(
|
|
11782
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
11783
|
-
if (evPath &&
|
|
11784
|
-
fsWatchBroadcast(sp.resolve(
|
|
12904
|
+
listener(path27);
|
|
12905
|
+
emitRaw(rawEvent, evPath, { watchedPath: path27 });
|
|
12906
|
+
if (evPath && path27 !== evPath) {
|
|
12907
|
+
fsWatchBroadcast(sp.resolve(path27, evPath), KEY_LISTENERS, sp.join(path27, evPath));
|
|
11785
12908
|
}
|
|
11786
12909
|
};
|
|
11787
12910
|
try {
|
|
11788
|
-
return (0, import_node_fs.watch)(
|
|
12911
|
+
return (0, import_node_fs.watch)(path27, {
|
|
11789
12912
|
persistent: options.persistent
|
|
11790
12913
|
}, handleEvent);
|
|
11791
12914
|
} catch (error) {
|
|
@@ -11801,12 +12924,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
11801
12924
|
listener(val1, val2, val3);
|
|
11802
12925
|
});
|
|
11803
12926
|
};
|
|
11804
|
-
var setFsWatchListener = (
|
|
12927
|
+
var setFsWatchListener = (path27, fullPath, options, handlers) => {
|
|
11805
12928
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
11806
12929
|
let cont = FsWatchInstances.get(fullPath);
|
|
11807
12930
|
let watcher;
|
|
11808
12931
|
if (!options.persistent) {
|
|
11809
|
-
watcher = createFsWatchInstance(
|
|
12932
|
+
watcher = createFsWatchInstance(path27, options, listener, errHandler, rawEmitter);
|
|
11810
12933
|
if (!watcher)
|
|
11811
12934
|
return;
|
|
11812
12935
|
return watcher.close.bind(watcher);
|
|
@@ -11817,7 +12940,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
|
|
|
11817
12940
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
11818
12941
|
} else {
|
|
11819
12942
|
watcher = createFsWatchInstance(
|
|
11820
|
-
|
|
12943
|
+
path27,
|
|
11821
12944
|
options,
|
|
11822
12945
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
11823
12946
|
errHandler,
|
|
@@ -11832,7 +12955,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
|
|
|
11832
12955
|
cont.watcherUnusable = true;
|
|
11833
12956
|
if (isWindows && error.code === "EPERM") {
|
|
11834
12957
|
try {
|
|
11835
|
-
const fd = await (0, import_promises2.open)(
|
|
12958
|
+
const fd = await (0, import_promises2.open)(path27, "r");
|
|
11836
12959
|
await fd.close();
|
|
11837
12960
|
broadcastErr(error);
|
|
11838
12961
|
} catch (err) {
|
|
@@ -11863,7 +12986,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
|
|
|
11863
12986
|
};
|
|
11864
12987
|
};
|
|
11865
12988
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
11866
|
-
var setFsWatchFileListener = (
|
|
12989
|
+
var setFsWatchFileListener = (path27, fullPath, options, handlers) => {
|
|
11867
12990
|
const { listener, rawEmitter } = handlers;
|
|
11868
12991
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
11869
12992
|
const copts = cont && cont.options;
|
|
@@ -11885,7 +13008,7 @@ var setFsWatchFileListener = (path26, fullPath, options, handlers) => {
|
|
|
11885
13008
|
});
|
|
11886
13009
|
const currmtime = curr.mtimeMs;
|
|
11887
13010
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
11888
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
13011
|
+
foreach(cont.listeners, (listener2) => listener2(path27, curr));
|
|
11889
13012
|
}
|
|
11890
13013
|
})
|
|
11891
13014
|
};
|
|
@@ -11915,13 +13038,13 @@ var NodeFsHandler = class {
|
|
|
11915
13038
|
* @param listener on fs change
|
|
11916
13039
|
* @returns closer for the watcher instance
|
|
11917
13040
|
*/
|
|
11918
|
-
_watchWithNodeFs(
|
|
13041
|
+
_watchWithNodeFs(path27, listener) {
|
|
11919
13042
|
const opts = this.fsw.options;
|
|
11920
|
-
const directory = sp.dirname(
|
|
11921
|
-
const basename5 = sp.basename(
|
|
13043
|
+
const directory = sp.dirname(path27);
|
|
13044
|
+
const basename5 = sp.basename(path27);
|
|
11922
13045
|
const parent = this.fsw._getWatchedDir(directory);
|
|
11923
13046
|
parent.add(basename5);
|
|
11924
|
-
const absolutePath = sp.resolve(
|
|
13047
|
+
const absolutePath = sp.resolve(path27);
|
|
11925
13048
|
const options = {
|
|
11926
13049
|
persistent: opts.persistent
|
|
11927
13050
|
};
|
|
@@ -11931,12 +13054,12 @@ var NodeFsHandler = class {
|
|
|
11931
13054
|
if (opts.usePolling) {
|
|
11932
13055
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
11933
13056
|
options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
|
|
11934
|
-
closer = setFsWatchFileListener(
|
|
13057
|
+
closer = setFsWatchFileListener(path27, absolutePath, options, {
|
|
11935
13058
|
listener,
|
|
11936
13059
|
rawEmitter: this.fsw._emitRaw
|
|
11937
13060
|
});
|
|
11938
13061
|
} else {
|
|
11939
|
-
closer = setFsWatchListener(
|
|
13062
|
+
closer = setFsWatchListener(path27, absolutePath, options, {
|
|
11940
13063
|
listener,
|
|
11941
13064
|
errHandler: this._boundHandleError,
|
|
11942
13065
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -11958,7 +13081,7 @@ var NodeFsHandler = class {
|
|
|
11958
13081
|
let prevStats = stats;
|
|
11959
13082
|
if (parent.has(basename5))
|
|
11960
13083
|
return;
|
|
11961
|
-
const listener = async (
|
|
13084
|
+
const listener = async (path27, newStats) => {
|
|
11962
13085
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
11963
13086
|
return;
|
|
11964
13087
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -11972,11 +13095,11 @@ var NodeFsHandler = class {
|
|
|
11972
13095
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
11973
13096
|
}
|
|
11974
13097
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
11975
|
-
this.fsw._closeFile(
|
|
13098
|
+
this.fsw._closeFile(path27);
|
|
11976
13099
|
prevStats = newStats2;
|
|
11977
13100
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
11978
13101
|
if (closer2)
|
|
11979
|
-
this.fsw._addPathCloser(
|
|
13102
|
+
this.fsw._addPathCloser(path27, closer2);
|
|
11980
13103
|
} else {
|
|
11981
13104
|
prevStats = newStats2;
|
|
11982
13105
|
}
|
|
@@ -12008,7 +13131,7 @@ var NodeFsHandler = class {
|
|
|
12008
13131
|
* @param item basename of this item
|
|
12009
13132
|
* @returns true if no more processing is needed for this entry.
|
|
12010
13133
|
*/
|
|
12011
|
-
async _handleSymlink(entry, directory,
|
|
13134
|
+
async _handleSymlink(entry, directory, path27, item) {
|
|
12012
13135
|
if (this.fsw.closed) {
|
|
12013
13136
|
return;
|
|
12014
13137
|
}
|
|
@@ -12018,7 +13141,7 @@ var NodeFsHandler = class {
|
|
|
12018
13141
|
this.fsw._incrReadyCount();
|
|
12019
13142
|
let linkPath;
|
|
12020
13143
|
try {
|
|
12021
|
-
linkPath = await (0, import_promises2.realpath)(
|
|
13144
|
+
linkPath = await (0, import_promises2.realpath)(path27);
|
|
12022
13145
|
} catch (e) {
|
|
12023
13146
|
this.fsw._emitReady();
|
|
12024
13147
|
return true;
|
|
@@ -12028,12 +13151,12 @@ var NodeFsHandler = class {
|
|
|
12028
13151
|
if (dir.has(item)) {
|
|
12029
13152
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
12030
13153
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
12031
|
-
this.fsw._emit(EV.CHANGE,
|
|
13154
|
+
this.fsw._emit(EV.CHANGE, path27, entry.stats);
|
|
12032
13155
|
}
|
|
12033
13156
|
} else {
|
|
12034
13157
|
dir.add(item);
|
|
12035
13158
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
12036
|
-
this.fsw._emit(EV.ADD,
|
|
13159
|
+
this.fsw._emit(EV.ADD, path27, entry.stats);
|
|
12037
13160
|
}
|
|
12038
13161
|
this.fsw._emitReady();
|
|
12039
13162
|
return true;
|
|
@@ -12063,9 +13186,9 @@ var NodeFsHandler = class {
|
|
|
12063
13186
|
return;
|
|
12064
13187
|
}
|
|
12065
13188
|
const item = entry.path;
|
|
12066
|
-
let
|
|
13189
|
+
let path27 = sp.join(directory, item);
|
|
12067
13190
|
current.add(item);
|
|
12068
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
13191
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path27, item)) {
|
|
12069
13192
|
return;
|
|
12070
13193
|
}
|
|
12071
13194
|
if (this.fsw.closed) {
|
|
@@ -12074,8 +13197,8 @@ var NodeFsHandler = class {
|
|
|
12074
13197
|
}
|
|
12075
13198
|
if (item === target || !target && !previous.has(item)) {
|
|
12076
13199
|
this.fsw._incrReadyCount();
|
|
12077
|
-
|
|
12078
|
-
this._addToNodeFs(
|
|
13200
|
+
path27 = sp.join(dir, sp.relative(dir, path27));
|
|
13201
|
+
this._addToNodeFs(path27, initialAdd, wh, depth + 1);
|
|
12079
13202
|
}
|
|
12080
13203
|
}).on(EV.ERROR, this._boundHandleError);
|
|
12081
13204
|
return new Promise((resolve15, reject) => {
|
|
@@ -12144,13 +13267,13 @@ var NodeFsHandler = class {
|
|
|
12144
13267
|
* @param depth Child path actually targeted for watch
|
|
12145
13268
|
* @param target Child path actually targeted for watch
|
|
12146
13269
|
*/
|
|
12147
|
-
async _addToNodeFs(
|
|
13270
|
+
async _addToNodeFs(path27, initialAdd, priorWh, depth, target) {
|
|
12148
13271
|
const ready = this.fsw._emitReady;
|
|
12149
|
-
if (this.fsw._isIgnored(
|
|
13272
|
+
if (this.fsw._isIgnored(path27) || this.fsw.closed) {
|
|
12150
13273
|
ready();
|
|
12151
13274
|
return false;
|
|
12152
13275
|
}
|
|
12153
|
-
const wh = this.fsw._getWatchHelpers(
|
|
13276
|
+
const wh = this.fsw._getWatchHelpers(path27);
|
|
12154
13277
|
if (priorWh) {
|
|
12155
13278
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
12156
13279
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -12166,8 +13289,8 @@ var NodeFsHandler = class {
|
|
|
12166
13289
|
const follow = this.fsw.options.followSymlinks;
|
|
12167
13290
|
let closer;
|
|
12168
13291
|
if (stats.isDirectory()) {
|
|
12169
|
-
const absPath = sp.resolve(
|
|
12170
|
-
const targetPath = follow ? await (0, import_promises2.realpath)(
|
|
13292
|
+
const absPath = sp.resolve(path27);
|
|
13293
|
+
const targetPath = follow ? await (0, import_promises2.realpath)(path27) : path27;
|
|
12171
13294
|
if (this.fsw.closed)
|
|
12172
13295
|
return;
|
|
12173
13296
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -12177,29 +13300,29 @@ var NodeFsHandler = class {
|
|
|
12177
13300
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
12178
13301
|
}
|
|
12179
13302
|
} else if (stats.isSymbolicLink()) {
|
|
12180
|
-
const targetPath = follow ? await (0, import_promises2.realpath)(
|
|
13303
|
+
const targetPath = follow ? await (0, import_promises2.realpath)(path27) : path27;
|
|
12181
13304
|
if (this.fsw.closed)
|
|
12182
13305
|
return;
|
|
12183
13306
|
const parent = sp.dirname(wh.watchPath);
|
|
12184
13307
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
12185
13308
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
12186
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
13309
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path27, wh, targetPath);
|
|
12187
13310
|
if (this.fsw.closed)
|
|
12188
13311
|
return;
|
|
12189
13312
|
if (targetPath !== void 0) {
|
|
12190
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
13313
|
+
this.fsw._symlinkPaths.set(sp.resolve(path27), targetPath);
|
|
12191
13314
|
}
|
|
12192
13315
|
} else {
|
|
12193
13316
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
12194
13317
|
}
|
|
12195
13318
|
ready();
|
|
12196
13319
|
if (closer)
|
|
12197
|
-
this.fsw._addPathCloser(
|
|
13320
|
+
this.fsw._addPathCloser(path27, closer);
|
|
12198
13321
|
return false;
|
|
12199
13322
|
} catch (error) {
|
|
12200
13323
|
if (this.fsw._handleError(error)) {
|
|
12201
13324
|
ready();
|
|
12202
|
-
return
|
|
13325
|
+
return path27;
|
|
12203
13326
|
}
|
|
12204
13327
|
}
|
|
12205
13328
|
}
|
|
@@ -12242,24 +13365,24 @@ function createPattern(matcher) {
|
|
|
12242
13365
|
}
|
|
12243
13366
|
return () => false;
|
|
12244
13367
|
}
|
|
12245
|
-
function normalizePath2(
|
|
12246
|
-
if (typeof
|
|
13368
|
+
function normalizePath2(path27) {
|
|
13369
|
+
if (typeof path27 !== "string")
|
|
12247
13370
|
throw new Error("string expected");
|
|
12248
|
-
|
|
12249
|
-
|
|
13371
|
+
path27 = sp2.normalize(path27);
|
|
13372
|
+
path27 = path27.replace(/\\/g, "/");
|
|
12250
13373
|
let prepend = false;
|
|
12251
|
-
if (
|
|
13374
|
+
if (path27.startsWith("//"))
|
|
12252
13375
|
prepend = true;
|
|
12253
|
-
|
|
13376
|
+
path27 = path27.replace(DOUBLE_SLASH_RE, "/");
|
|
12254
13377
|
if (prepend)
|
|
12255
|
-
|
|
12256
|
-
return
|
|
13378
|
+
path27 = "/" + path27;
|
|
13379
|
+
return path27;
|
|
12257
13380
|
}
|
|
12258
13381
|
function matchPatterns(patterns, testString, stats) {
|
|
12259
|
-
const
|
|
13382
|
+
const path27 = normalizePath2(testString);
|
|
12260
13383
|
for (let index = 0; index < patterns.length; index++) {
|
|
12261
13384
|
const pattern = patterns[index];
|
|
12262
|
-
if (pattern(
|
|
13385
|
+
if (pattern(path27, stats)) {
|
|
12263
13386
|
return true;
|
|
12264
13387
|
}
|
|
12265
13388
|
}
|
|
@@ -12297,19 +13420,19 @@ var toUnix = (string) => {
|
|
|
12297
13420
|
}
|
|
12298
13421
|
return str;
|
|
12299
13422
|
};
|
|
12300
|
-
var normalizePathToUnix = (
|
|
12301
|
-
var normalizeIgnored = (cwd = "") => (
|
|
12302
|
-
if (typeof
|
|
12303
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
13423
|
+
var normalizePathToUnix = (path27) => toUnix(sp2.normalize(toUnix(path27)));
|
|
13424
|
+
var normalizeIgnored = (cwd = "") => (path27) => {
|
|
13425
|
+
if (typeof path27 === "string") {
|
|
13426
|
+
return normalizePathToUnix(sp2.isAbsolute(path27) ? path27 : sp2.join(cwd, path27));
|
|
12304
13427
|
} else {
|
|
12305
|
-
return
|
|
13428
|
+
return path27;
|
|
12306
13429
|
}
|
|
12307
13430
|
};
|
|
12308
|
-
var getAbsolutePath = (
|
|
12309
|
-
if (sp2.isAbsolute(
|
|
12310
|
-
return
|
|
13431
|
+
var getAbsolutePath = (path27, cwd) => {
|
|
13432
|
+
if (sp2.isAbsolute(path27)) {
|
|
13433
|
+
return path27;
|
|
12311
13434
|
}
|
|
12312
|
-
return sp2.join(cwd,
|
|
13435
|
+
return sp2.join(cwd, path27);
|
|
12313
13436
|
};
|
|
12314
13437
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
12315
13438
|
var DirEntry = class {
|
|
@@ -12374,10 +13497,10 @@ var WatchHelper = class {
|
|
|
12374
13497
|
dirParts;
|
|
12375
13498
|
followSymlinks;
|
|
12376
13499
|
statMethod;
|
|
12377
|
-
constructor(
|
|
13500
|
+
constructor(path27, follow, fsw) {
|
|
12378
13501
|
this.fsw = fsw;
|
|
12379
|
-
const watchPath =
|
|
12380
|
-
this.path =
|
|
13502
|
+
const watchPath = path27;
|
|
13503
|
+
this.path = path27 = path27.replace(REPLACER_RE, "");
|
|
12381
13504
|
this.watchPath = watchPath;
|
|
12382
13505
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
12383
13506
|
this.dirParts = [];
|
|
@@ -12517,20 +13640,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12517
13640
|
this._closePromise = void 0;
|
|
12518
13641
|
let paths = unifyPaths(paths_);
|
|
12519
13642
|
if (cwd) {
|
|
12520
|
-
paths = paths.map((
|
|
12521
|
-
const absPath = getAbsolutePath(
|
|
13643
|
+
paths = paths.map((path27) => {
|
|
13644
|
+
const absPath = getAbsolutePath(path27, cwd);
|
|
12522
13645
|
return absPath;
|
|
12523
13646
|
});
|
|
12524
13647
|
}
|
|
12525
|
-
paths.forEach((
|
|
12526
|
-
this._removeIgnoredPath(
|
|
13648
|
+
paths.forEach((path27) => {
|
|
13649
|
+
this._removeIgnoredPath(path27);
|
|
12527
13650
|
});
|
|
12528
13651
|
this._userIgnored = void 0;
|
|
12529
13652
|
if (!this._readyCount)
|
|
12530
13653
|
this._readyCount = 0;
|
|
12531
13654
|
this._readyCount += paths.length;
|
|
12532
|
-
Promise.all(paths.map(async (
|
|
12533
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
13655
|
+
Promise.all(paths.map(async (path27) => {
|
|
13656
|
+
const res = await this._nodeFsHandler._addToNodeFs(path27, !_internal, void 0, 0, _origAdd);
|
|
12534
13657
|
if (res)
|
|
12535
13658
|
this._emitReady();
|
|
12536
13659
|
return res;
|
|
@@ -12552,17 +13675,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12552
13675
|
return this;
|
|
12553
13676
|
const paths = unifyPaths(paths_);
|
|
12554
13677
|
const { cwd } = this.options;
|
|
12555
|
-
paths.forEach((
|
|
12556
|
-
if (!sp2.isAbsolute(
|
|
13678
|
+
paths.forEach((path27) => {
|
|
13679
|
+
if (!sp2.isAbsolute(path27) && !this._closers.has(path27)) {
|
|
12557
13680
|
if (cwd)
|
|
12558
|
-
|
|
12559
|
-
|
|
13681
|
+
path27 = sp2.join(cwd, path27);
|
|
13682
|
+
path27 = sp2.resolve(path27);
|
|
12560
13683
|
}
|
|
12561
|
-
this._closePath(
|
|
12562
|
-
this._addIgnoredPath(
|
|
12563
|
-
if (this._watched.has(
|
|
13684
|
+
this._closePath(path27);
|
|
13685
|
+
this._addIgnoredPath(path27);
|
|
13686
|
+
if (this._watched.has(path27)) {
|
|
12564
13687
|
this._addIgnoredPath({
|
|
12565
|
-
path:
|
|
13688
|
+
path: path27,
|
|
12566
13689
|
recursive: true
|
|
12567
13690
|
});
|
|
12568
13691
|
}
|
|
@@ -12626,38 +13749,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12626
13749
|
* @param stats arguments to be passed with event
|
|
12627
13750
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
12628
13751
|
*/
|
|
12629
|
-
async _emit(event,
|
|
13752
|
+
async _emit(event, path27, stats) {
|
|
12630
13753
|
if (this.closed)
|
|
12631
13754
|
return;
|
|
12632
13755
|
const opts = this.options;
|
|
12633
13756
|
if (isWindows)
|
|
12634
|
-
|
|
13757
|
+
path27 = sp2.normalize(path27);
|
|
12635
13758
|
if (opts.cwd)
|
|
12636
|
-
|
|
12637
|
-
const args = [
|
|
13759
|
+
path27 = sp2.relative(opts.cwd, path27);
|
|
13760
|
+
const args = [path27];
|
|
12638
13761
|
if (stats != null)
|
|
12639
13762
|
args.push(stats);
|
|
12640
13763
|
const awf = opts.awaitWriteFinish;
|
|
12641
13764
|
let pw;
|
|
12642
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
13765
|
+
if (awf && (pw = this._pendingWrites.get(path27))) {
|
|
12643
13766
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
12644
13767
|
return this;
|
|
12645
13768
|
}
|
|
12646
13769
|
if (opts.atomic) {
|
|
12647
13770
|
if (event === EVENTS.UNLINK) {
|
|
12648
|
-
this._pendingUnlinks.set(
|
|
13771
|
+
this._pendingUnlinks.set(path27, [event, ...args]);
|
|
12649
13772
|
setTimeout(() => {
|
|
12650
|
-
this._pendingUnlinks.forEach((entry,
|
|
13773
|
+
this._pendingUnlinks.forEach((entry, path28) => {
|
|
12651
13774
|
this.emit(...entry);
|
|
12652
13775
|
this.emit(EVENTS.ALL, ...entry);
|
|
12653
|
-
this._pendingUnlinks.delete(
|
|
13776
|
+
this._pendingUnlinks.delete(path28);
|
|
12654
13777
|
});
|
|
12655
13778
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
12656
13779
|
return this;
|
|
12657
13780
|
}
|
|
12658
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
13781
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path27)) {
|
|
12659
13782
|
event = EVENTS.CHANGE;
|
|
12660
|
-
this._pendingUnlinks.delete(
|
|
13783
|
+
this._pendingUnlinks.delete(path27);
|
|
12661
13784
|
}
|
|
12662
13785
|
}
|
|
12663
13786
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -12675,16 +13798,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12675
13798
|
this.emitWithAll(event, args);
|
|
12676
13799
|
}
|
|
12677
13800
|
};
|
|
12678
|
-
this._awaitWriteFinish(
|
|
13801
|
+
this._awaitWriteFinish(path27, awf.stabilityThreshold, event, awfEmit);
|
|
12679
13802
|
return this;
|
|
12680
13803
|
}
|
|
12681
13804
|
if (event === EVENTS.CHANGE) {
|
|
12682
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
13805
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path27, 50);
|
|
12683
13806
|
if (isThrottled)
|
|
12684
13807
|
return this;
|
|
12685
13808
|
}
|
|
12686
13809
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
12687
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
13810
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path27) : path27;
|
|
12688
13811
|
let stats2;
|
|
12689
13812
|
try {
|
|
12690
13813
|
stats2 = await (0, import_promises3.stat)(fullPath);
|
|
@@ -12715,23 +13838,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12715
13838
|
* @param timeout duration of time to suppress duplicate actions
|
|
12716
13839
|
* @returns tracking object or false if action should be suppressed
|
|
12717
13840
|
*/
|
|
12718
|
-
_throttle(actionType,
|
|
13841
|
+
_throttle(actionType, path27, timeout) {
|
|
12719
13842
|
if (!this._throttled.has(actionType)) {
|
|
12720
13843
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
12721
13844
|
}
|
|
12722
13845
|
const action = this._throttled.get(actionType);
|
|
12723
13846
|
if (!action)
|
|
12724
13847
|
throw new Error("invalid throttle");
|
|
12725
|
-
const actionPath = action.get(
|
|
13848
|
+
const actionPath = action.get(path27);
|
|
12726
13849
|
if (actionPath) {
|
|
12727
13850
|
actionPath.count++;
|
|
12728
13851
|
return false;
|
|
12729
13852
|
}
|
|
12730
13853
|
let timeoutObject;
|
|
12731
13854
|
const clear = () => {
|
|
12732
|
-
const item = action.get(
|
|
13855
|
+
const item = action.get(path27);
|
|
12733
13856
|
const count = item ? item.count : 0;
|
|
12734
|
-
action.delete(
|
|
13857
|
+
action.delete(path27);
|
|
12735
13858
|
clearTimeout(timeoutObject);
|
|
12736
13859
|
if (item)
|
|
12737
13860
|
clearTimeout(item.timeoutObject);
|
|
@@ -12739,7 +13862,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12739
13862
|
};
|
|
12740
13863
|
timeoutObject = setTimeout(clear, timeout);
|
|
12741
13864
|
const thr = { timeoutObject, clear, count: 0 };
|
|
12742
|
-
action.set(
|
|
13865
|
+
action.set(path27, thr);
|
|
12743
13866
|
return thr;
|
|
12744
13867
|
}
|
|
12745
13868
|
_incrReadyCount() {
|
|
@@ -12753,44 +13876,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12753
13876
|
* @param event
|
|
12754
13877
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
12755
13878
|
*/
|
|
12756
|
-
_awaitWriteFinish(
|
|
13879
|
+
_awaitWriteFinish(path27, threshold, event, awfEmit) {
|
|
12757
13880
|
const awf = this.options.awaitWriteFinish;
|
|
12758
13881
|
if (typeof awf !== "object")
|
|
12759
13882
|
return;
|
|
12760
13883
|
const pollInterval = awf.pollInterval;
|
|
12761
13884
|
let timeoutHandler;
|
|
12762
|
-
let fullPath =
|
|
12763
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
12764
|
-
fullPath = sp2.join(this.options.cwd,
|
|
13885
|
+
let fullPath = path27;
|
|
13886
|
+
if (this.options.cwd && !sp2.isAbsolute(path27)) {
|
|
13887
|
+
fullPath = sp2.join(this.options.cwd, path27);
|
|
12765
13888
|
}
|
|
12766
13889
|
const now = /* @__PURE__ */ new Date();
|
|
12767
13890
|
const writes = this._pendingWrites;
|
|
12768
13891
|
function awaitWriteFinishFn(prevStat) {
|
|
12769
13892
|
(0, import_node_fs2.stat)(fullPath, (err, curStat) => {
|
|
12770
|
-
if (err || !writes.has(
|
|
13893
|
+
if (err || !writes.has(path27)) {
|
|
12771
13894
|
if (err && err.code !== "ENOENT")
|
|
12772
13895
|
awfEmit(err);
|
|
12773
13896
|
return;
|
|
12774
13897
|
}
|
|
12775
13898
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
12776
13899
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
12777
|
-
writes.get(
|
|
13900
|
+
writes.get(path27).lastChange = now2;
|
|
12778
13901
|
}
|
|
12779
|
-
const pw = writes.get(
|
|
13902
|
+
const pw = writes.get(path27);
|
|
12780
13903
|
const df = now2 - pw.lastChange;
|
|
12781
13904
|
if (df >= threshold) {
|
|
12782
|
-
writes.delete(
|
|
13905
|
+
writes.delete(path27);
|
|
12783
13906
|
awfEmit(void 0, curStat);
|
|
12784
13907
|
} else {
|
|
12785
13908
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
12786
13909
|
}
|
|
12787
13910
|
});
|
|
12788
13911
|
}
|
|
12789
|
-
if (!writes.has(
|
|
12790
|
-
writes.set(
|
|
13912
|
+
if (!writes.has(path27)) {
|
|
13913
|
+
writes.set(path27, {
|
|
12791
13914
|
lastChange: now,
|
|
12792
13915
|
cancelWait: () => {
|
|
12793
|
-
writes.delete(
|
|
13916
|
+
writes.delete(path27);
|
|
12794
13917
|
clearTimeout(timeoutHandler);
|
|
12795
13918
|
return event;
|
|
12796
13919
|
}
|
|
@@ -12801,8 +13924,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12801
13924
|
/**
|
|
12802
13925
|
* Determines whether user has asked to ignore this path.
|
|
12803
13926
|
*/
|
|
12804
|
-
_isIgnored(
|
|
12805
|
-
if (this.options.atomic && DOT_RE.test(
|
|
13927
|
+
_isIgnored(path27, stats) {
|
|
13928
|
+
if (this.options.atomic && DOT_RE.test(path27))
|
|
12806
13929
|
return true;
|
|
12807
13930
|
if (!this._userIgnored) {
|
|
12808
13931
|
const { cwd } = this.options;
|
|
@@ -12812,17 +13935,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12812
13935
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
12813
13936
|
this._userIgnored = anymatch(list, void 0);
|
|
12814
13937
|
}
|
|
12815
|
-
return this._userIgnored(
|
|
13938
|
+
return this._userIgnored(path27, stats);
|
|
12816
13939
|
}
|
|
12817
|
-
_isntIgnored(
|
|
12818
|
-
return !this._isIgnored(
|
|
13940
|
+
_isntIgnored(path27, stat4) {
|
|
13941
|
+
return !this._isIgnored(path27, stat4);
|
|
12819
13942
|
}
|
|
12820
13943
|
/**
|
|
12821
13944
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
12822
13945
|
* @param path file or directory pattern being watched
|
|
12823
13946
|
*/
|
|
12824
|
-
_getWatchHelpers(
|
|
12825
|
-
return new WatchHelper(
|
|
13947
|
+
_getWatchHelpers(path27) {
|
|
13948
|
+
return new WatchHelper(path27, this.options.followSymlinks, this);
|
|
12826
13949
|
}
|
|
12827
13950
|
// Directory helpers
|
|
12828
13951
|
// -----------------
|
|
@@ -12854,63 +13977,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
|
|
|
12854
13977
|
* @param item base path of item/directory
|
|
12855
13978
|
*/
|
|
12856
13979
|
_remove(directory, item, isDirectory) {
|
|
12857
|
-
const
|
|
12858
|
-
const fullPath = sp2.resolve(
|
|
12859
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
12860
|
-
if (!this._throttle("remove",
|
|
13980
|
+
const path27 = sp2.join(directory, item);
|
|
13981
|
+
const fullPath = sp2.resolve(path27);
|
|
13982
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path27) || this._watched.has(fullPath);
|
|
13983
|
+
if (!this._throttle("remove", path27, 100))
|
|
12861
13984
|
return;
|
|
12862
13985
|
if (!isDirectory && this._watched.size === 1) {
|
|
12863
13986
|
this.add(directory, item, true);
|
|
12864
13987
|
}
|
|
12865
|
-
const wp = this._getWatchedDir(
|
|
13988
|
+
const wp = this._getWatchedDir(path27);
|
|
12866
13989
|
const nestedDirectoryChildren = wp.getChildren();
|
|
12867
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
13990
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path27, nested));
|
|
12868
13991
|
const parent = this._getWatchedDir(directory);
|
|
12869
13992
|
const wasTracked = parent.has(item);
|
|
12870
13993
|
parent.remove(item);
|
|
12871
13994
|
if (this._symlinkPaths.has(fullPath)) {
|
|
12872
13995
|
this._symlinkPaths.delete(fullPath);
|
|
12873
13996
|
}
|
|
12874
|
-
let relPath =
|
|
13997
|
+
let relPath = path27;
|
|
12875
13998
|
if (this.options.cwd)
|
|
12876
|
-
relPath = sp2.relative(this.options.cwd,
|
|
13999
|
+
relPath = sp2.relative(this.options.cwd, path27);
|
|
12877
14000
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
12878
14001
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
12879
14002
|
if (event === EVENTS.ADD)
|
|
12880
14003
|
return;
|
|
12881
14004
|
}
|
|
12882
|
-
this._watched.delete(
|
|
14005
|
+
this._watched.delete(path27);
|
|
12883
14006
|
this._watched.delete(fullPath);
|
|
12884
14007
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
12885
|
-
if (wasTracked && !this._isIgnored(
|
|
12886
|
-
this._emit(eventName,
|
|
12887
|
-
this._closePath(
|
|
14008
|
+
if (wasTracked && !this._isIgnored(path27))
|
|
14009
|
+
this._emit(eventName, path27);
|
|
14010
|
+
this._closePath(path27);
|
|
12888
14011
|
}
|
|
12889
14012
|
/**
|
|
12890
14013
|
* Closes all watchers for a path
|
|
12891
14014
|
*/
|
|
12892
|
-
_closePath(
|
|
12893
|
-
this._closeFile(
|
|
12894
|
-
const dir = sp2.dirname(
|
|
12895
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
14015
|
+
_closePath(path27) {
|
|
14016
|
+
this._closeFile(path27);
|
|
14017
|
+
const dir = sp2.dirname(path27);
|
|
14018
|
+
this._getWatchedDir(dir).remove(sp2.basename(path27));
|
|
12896
14019
|
}
|
|
12897
14020
|
/**
|
|
12898
14021
|
* Closes only file-specific watchers
|
|
12899
14022
|
*/
|
|
12900
|
-
_closeFile(
|
|
12901
|
-
const closers = this._closers.get(
|
|
14023
|
+
_closeFile(path27) {
|
|
14024
|
+
const closers = this._closers.get(path27);
|
|
12902
14025
|
if (!closers)
|
|
12903
14026
|
return;
|
|
12904
14027
|
closers.forEach((closer) => closer());
|
|
12905
|
-
this._closers.delete(
|
|
14028
|
+
this._closers.delete(path27);
|
|
12906
14029
|
}
|
|
12907
|
-
_addPathCloser(
|
|
14030
|
+
_addPathCloser(path27, closer) {
|
|
12908
14031
|
if (!closer)
|
|
12909
14032
|
return;
|
|
12910
|
-
let list = this._closers.get(
|
|
14033
|
+
let list = this._closers.get(path27);
|
|
12911
14034
|
if (!list) {
|
|
12912
14035
|
list = [];
|
|
12913
|
-
this._closers.set(
|
|
14036
|
+
this._closers.set(path27, list);
|
|
12914
14037
|
}
|
|
12915
14038
|
list.push(closer);
|
|
12916
14039
|
}
|
|
@@ -12940,7 +14063,7 @@ function watch(paths, options = {}) {
|
|
|
12940
14063
|
var chokidar_default = { watch, FSWatcher };
|
|
12941
14064
|
|
|
12942
14065
|
// src/watcher/file-watcher.ts
|
|
12943
|
-
var
|
|
14066
|
+
var path22 = __toESM(require("path"), 1);
|
|
12944
14067
|
var FileWatcher = class {
|
|
12945
14068
|
watcher = null;
|
|
12946
14069
|
projectRoot;
|
|
@@ -12951,6 +14074,8 @@ var FileWatcher = class {
|
|
|
12951
14074
|
debounceTimer = null;
|
|
12952
14075
|
debounceMs = 1e3;
|
|
12953
14076
|
onChanges = null;
|
|
14077
|
+
readyPromise = null;
|
|
14078
|
+
resolveReady = null;
|
|
12954
14079
|
constructor(projectRoot, config, host = "opencode", options = {}) {
|
|
12955
14080
|
this.projectRoot = projectRoot;
|
|
12956
14081
|
this.config = config;
|
|
@@ -12966,15 +14091,15 @@ var FileWatcher = class {
|
|
|
12966
14091
|
const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
|
|
12967
14092
|
this.watcher = chokidar_default.watch(watchTargets, {
|
|
12968
14093
|
ignored: (filePath) => {
|
|
12969
|
-
const relativePath =
|
|
14094
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
12970
14095
|
if (!relativePath) return false;
|
|
12971
14096
|
if (this.isProjectConfigPathOrAncestor(relativePath)) {
|
|
12972
14097
|
return false;
|
|
12973
14098
|
}
|
|
12974
|
-
if (hasFilteredPathSegment(relativePath,
|
|
14099
|
+
if (hasFilteredPathSegment(relativePath, path22.sep)) {
|
|
12975
14100
|
return true;
|
|
12976
14101
|
}
|
|
12977
|
-
if (isRestrictedDirectory(relativePath,
|
|
14102
|
+
if (isRestrictedDirectory(relativePath, path22.sep)) {
|
|
12978
14103
|
return true;
|
|
12979
14104
|
}
|
|
12980
14105
|
if (ignoreFilter.ignores(relativePath)) {
|
|
@@ -12989,6 +14114,13 @@ var FileWatcher = class {
|
|
|
12989
14114
|
pollInterval: 100
|
|
12990
14115
|
}
|
|
12991
14116
|
});
|
|
14117
|
+
this.readyPromise = new Promise((resolve15) => {
|
|
14118
|
+
this.resolveReady = resolve15;
|
|
14119
|
+
});
|
|
14120
|
+
this.watcher.once("ready", () => {
|
|
14121
|
+
this.resolveReady?.();
|
|
14122
|
+
this.resolveReady = null;
|
|
14123
|
+
});
|
|
12992
14124
|
this.watcher.on("error", (error) => {
|
|
12993
14125
|
const err = error instanceof Error ? error : null;
|
|
12994
14126
|
if (err?.code === "EPERM" || err?.code === "EACCES") {
|
|
@@ -13020,24 +14152,24 @@ var FileWatcher = class {
|
|
|
13020
14152
|
this.scheduleFlush();
|
|
13021
14153
|
}
|
|
13022
14154
|
isProjectConfigPath(filePath) {
|
|
13023
|
-
const relativePath =
|
|
13024
|
-
const normalizedRelativePath =
|
|
14155
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
14156
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
13025
14157
|
return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
|
|
13026
14158
|
}
|
|
13027
14159
|
isProjectConfigPathOrAncestor(relativePath) {
|
|
13028
|
-
const normalizedRelativePath =
|
|
14160
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
13029
14161
|
return this.getProjectConfigRelativePaths().some(
|
|
13030
|
-
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${
|
|
14162
|
+
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path22.sep}`)
|
|
13031
14163
|
);
|
|
13032
14164
|
}
|
|
13033
14165
|
getProjectConfigRelativePaths() {
|
|
13034
14166
|
if (this.configPath) {
|
|
13035
|
-
return [
|
|
14167
|
+
return [path22.normalize(path22.relative(this.projectRoot, this.configPath))];
|
|
13036
14168
|
}
|
|
13037
14169
|
return [
|
|
13038
14170
|
resolveProjectConfigPath(this.projectRoot, this.host),
|
|
13039
14171
|
resolveWritableProjectConfigPath(this.projectRoot, this.host)
|
|
13040
|
-
].map((configPath) =>
|
|
14172
|
+
].map((configPath) => path22.normalize(path22.relative(this.projectRoot, configPath)));
|
|
13041
14173
|
}
|
|
13042
14174
|
scheduleFlush() {
|
|
13043
14175
|
if (this.debounceTimer) {
|
|
@@ -13052,7 +14184,7 @@ var FileWatcher = class {
|
|
|
13052
14184
|
return;
|
|
13053
14185
|
}
|
|
13054
14186
|
const changes = Array.from(this.pendingChanges.entries()).map(
|
|
13055
|
-
([
|
|
14187
|
+
([path27, type]) => ({ path: path27, type })
|
|
13056
14188
|
);
|
|
13057
14189
|
this.pendingChanges.clear();
|
|
13058
14190
|
try {
|
|
@@ -13061,25 +14193,32 @@ var FileWatcher = class {
|
|
|
13061
14193
|
console.error("Error handling file changes:", error);
|
|
13062
14194
|
}
|
|
13063
14195
|
}
|
|
13064
|
-
stop() {
|
|
14196
|
+
async stop() {
|
|
13065
14197
|
if (this.debounceTimer) {
|
|
13066
14198
|
clearTimeout(this.debounceTimer);
|
|
13067
14199
|
this.debounceTimer = null;
|
|
13068
14200
|
}
|
|
13069
14201
|
if (this.watcher) {
|
|
13070
|
-
this.watcher
|
|
14202
|
+
const watcher = this.watcher;
|
|
13071
14203
|
this.watcher = null;
|
|
14204
|
+
await watcher.close();
|
|
13072
14205
|
}
|
|
14206
|
+
this.resolveReady?.();
|
|
14207
|
+
this.resolveReady = null;
|
|
14208
|
+
this.readyPromise = null;
|
|
13073
14209
|
this.pendingChanges.clear();
|
|
13074
14210
|
this.onChanges = null;
|
|
13075
14211
|
}
|
|
13076
14212
|
isRunning() {
|
|
13077
14213
|
return this.watcher !== null;
|
|
13078
14214
|
}
|
|
14215
|
+
async waitUntilReady() {
|
|
14216
|
+
await (this.readyPromise ?? Promise.resolve());
|
|
14217
|
+
}
|
|
13079
14218
|
};
|
|
13080
14219
|
|
|
13081
14220
|
// src/watcher/git-head-watcher.ts
|
|
13082
|
-
var
|
|
14221
|
+
var path23 = __toESM(require("path"), 1);
|
|
13083
14222
|
var GitHeadWatcher = class {
|
|
13084
14223
|
watcher = null;
|
|
13085
14224
|
projectRoot;
|
|
@@ -13101,7 +14240,7 @@ var GitHeadWatcher = class {
|
|
|
13101
14240
|
this.onBranchChange = handler;
|
|
13102
14241
|
this.currentBranch = getCurrentBranch(this.projectRoot);
|
|
13103
14242
|
const headPath = getHeadPath(this.projectRoot);
|
|
13104
|
-
const refsPath =
|
|
14243
|
+
const refsPath = path23.join(this.projectRoot, ".git", "refs", "heads");
|
|
13105
14244
|
this.watcher = chokidar_default.watch([headPath, refsPath], {
|
|
13106
14245
|
persistent: true,
|
|
13107
14246
|
ignoreInitial: true,
|
|
@@ -13138,14 +14277,15 @@ var GitHeadWatcher = class {
|
|
|
13138
14277
|
getCurrentBranch() {
|
|
13139
14278
|
return this.currentBranch;
|
|
13140
14279
|
}
|
|
13141
|
-
stop() {
|
|
14280
|
+
async stop() {
|
|
13142
14281
|
if (this.debounceTimer) {
|
|
13143
14282
|
clearTimeout(this.debounceTimer);
|
|
13144
14283
|
this.debounceTimer = null;
|
|
13145
14284
|
}
|
|
13146
14285
|
if (this.watcher) {
|
|
13147
|
-
this.watcher
|
|
14286
|
+
const watcher = this.watcher;
|
|
13148
14287
|
this.watcher = null;
|
|
14288
|
+
await watcher.close();
|
|
13149
14289
|
}
|
|
13150
14290
|
this.onBranchChange = null;
|
|
13151
14291
|
}
|
|
@@ -13163,6 +14303,8 @@ var BackgroundReindexer = class {
|
|
|
13163
14303
|
running = false;
|
|
13164
14304
|
pending = false;
|
|
13165
14305
|
stopped = false;
|
|
14306
|
+
retryTimer = null;
|
|
14307
|
+
retryDelayMs = 50;
|
|
13166
14308
|
request() {
|
|
13167
14309
|
if (this.stopped) {
|
|
13168
14310
|
return;
|
|
@@ -13173,9 +14315,13 @@ var BackgroundReindexer = class {
|
|
|
13173
14315
|
stop() {
|
|
13174
14316
|
this.stopped = true;
|
|
13175
14317
|
this.pending = false;
|
|
14318
|
+
if (this.retryTimer) {
|
|
14319
|
+
clearTimeout(this.retryTimer);
|
|
14320
|
+
this.retryTimer = null;
|
|
14321
|
+
}
|
|
13176
14322
|
}
|
|
13177
14323
|
drain() {
|
|
13178
|
-
if (this.stopped || this.running || !this.pending) {
|
|
14324
|
+
if (this.stopped || this.running || this.retryTimer || !this.pending) {
|
|
13179
14325
|
return;
|
|
13180
14326
|
}
|
|
13181
14327
|
this.pending = false;
|
|
@@ -13183,13 +14329,29 @@ var BackgroundReindexer = class {
|
|
|
13183
14329
|
void this.run();
|
|
13184
14330
|
}
|
|
13185
14331
|
async run() {
|
|
14332
|
+
let retryAfterContention = false;
|
|
13186
14333
|
try {
|
|
13187
14334
|
await this.runIndex();
|
|
14335
|
+
this.retryDelayMs = 50;
|
|
13188
14336
|
} catch (error) {
|
|
13189
|
-
|
|
14337
|
+
if (isTransientIndexLockContention(error)) {
|
|
14338
|
+
this.pending = true;
|
|
14339
|
+
retryAfterContention = true;
|
|
14340
|
+
} else {
|
|
14341
|
+
console.error("[codebase-index] Background reindex failed:", error);
|
|
14342
|
+
}
|
|
13190
14343
|
} finally {
|
|
13191
14344
|
this.running = false;
|
|
13192
|
-
this.
|
|
14345
|
+
if (retryAfterContention && !this.stopped) {
|
|
14346
|
+
const delay = this.retryDelayMs;
|
|
14347
|
+
this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
|
|
14348
|
+
this.retryTimer = setTimeout(() => {
|
|
14349
|
+
this.retryTimer = null;
|
|
14350
|
+
this.drain();
|
|
14351
|
+
}, delay);
|
|
14352
|
+
} else {
|
|
14353
|
+
this.drain();
|
|
14354
|
+
}
|
|
13193
14355
|
}
|
|
13194
14356
|
}
|
|
13195
14357
|
};
|
|
@@ -13223,10 +14385,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
|
|
|
13223
14385
|
return {
|
|
13224
14386
|
fileWatcher,
|
|
13225
14387
|
gitWatcher,
|
|
13226
|
-
|
|
14388
|
+
whenReady() {
|
|
14389
|
+
return fileWatcher.waitUntilReady();
|
|
14390
|
+
},
|
|
14391
|
+
async stop() {
|
|
13227
14392
|
backgroundReindexer.stop();
|
|
13228
|
-
fileWatcher.stop();
|
|
13229
|
-
gitWatcher?.stop();
|
|
14393
|
+
await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
|
|
13230
14394
|
}
|
|
13231
14395
|
};
|
|
13232
14396
|
}
|
|
@@ -13245,7 +14409,7 @@ function getConfigPaths(projectRoot, host, options) {
|
|
|
13245
14409
|
|
|
13246
14410
|
// src/tools/visualize/activity.ts
|
|
13247
14411
|
var import_child_process4 = require("child_process");
|
|
13248
|
-
var
|
|
14412
|
+
var path24 = __toESM(require("path"), 1);
|
|
13249
14413
|
function attachRecentActivity(data, projectRoot) {
|
|
13250
14414
|
const activity = readGitActivity(projectRoot);
|
|
13251
14415
|
const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
|
|
@@ -13407,7 +14571,7 @@ function normalizePath3(filePath) {
|
|
|
13407
14571
|
return filePath.replace(/\\/g, "/");
|
|
13408
14572
|
}
|
|
13409
14573
|
function toGitRelativePath(projectRoot, filePath) {
|
|
13410
|
-
const relativePath =
|
|
14574
|
+
const relativePath = path24.isAbsolute(filePath) ? path24.relative(projectRoot, filePath) : filePath;
|
|
13411
14575
|
return normalizePath3(relativePath);
|
|
13412
14576
|
}
|
|
13413
14577
|
|
|
@@ -13665,7 +14829,7 @@ render();
|
|
|
13665
14829
|
}
|
|
13666
14830
|
|
|
13667
14831
|
// src/tools/visualize/transform.ts
|
|
13668
|
-
var
|
|
14832
|
+
var path25 = __toESM(require("path"), 1);
|
|
13669
14833
|
|
|
13670
14834
|
// src/tools/visualize/modules.ts
|
|
13671
14835
|
var MAX_MODULES = 18;
|
|
@@ -13925,7 +15089,7 @@ function transformForVisualization(symbols, edges, options = {}) {
|
|
|
13925
15089
|
filePath: s.filePath,
|
|
13926
15090
|
kind: s.kind,
|
|
13927
15091
|
line: s.startLine,
|
|
13928
|
-
directory:
|
|
15092
|
+
directory: path25.dirname(s.filePath),
|
|
13929
15093
|
moduleId: "",
|
|
13930
15094
|
moduleLabel: ""
|
|
13931
15095
|
}));
|
|
@@ -13954,9 +15118,9 @@ function parseArgs(argv) {
|
|
|
13954
15118
|
let host = "opencode";
|
|
13955
15119
|
for (let i = 2; i < argv.length; i++) {
|
|
13956
15120
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
13957
|
-
project =
|
|
15121
|
+
project = path26.resolve(argv[++i]);
|
|
13958
15122
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
13959
|
-
config =
|
|
15123
|
+
config = path26.resolve(argv[++i]);
|
|
13960
15124
|
} else if (argv[i] === "--host" && argv[i + 1]) {
|
|
13961
15125
|
host = parseHostMode(argv[++i]);
|
|
13962
15126
|
} else if (argv[i] === "--host") {
|
|
@@ -13969,7 +15133,7 @@ function loadCliRawConfig(args) {
|
|
|
13969
15133
|
return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
|
|
13970
15134
|
}
|
|
13971
15135
|
function isCliEntrypoint(moduleUrl, argvPath) {
|
|
13972
|
-
return argvPath !== void 0 && (0,
|
|
15136
|
+
return argvPath !== void 0 && (0, import_fs16.realpathSync)((0, import_url2.fileURLToPath)(moduleUrl)) === (0, import_fs16.realpathSync)(argvPath);
|
|
13973
15137
|
}
|
|
13974
15138
|
function parseVisualizeArgs(argv, cwd) {
|
|
13975
15139
|
let project = cwd;
|
|
@@ -13979,7 +15143,7 @@ function parseVisualizeArgs(argv, cwd) {
|
|
|
13979
15143
|
for (let i = 0; i < argv.length; i++) {
|
|
13980
15144
|
const arg = argv[i];
|
|
13981
15145
|
if (arg === "--project" && argv[i + 1]) {
|
|
13982
|
-
project =
|
|
15146
|
+
project = path26.resolve(argv[++i]);
|
|
13983
15147
|
} else if (arg === "--max" && argv[i + 1]) {
|
|
13984
15148
|
maxNodes = Number(argv[++i]);
|
|
13985
15149
|
} else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
|
|
@@ -14016,8 +15180,8 @@ async function handleVisualizeCommand(argv, cwd) {
|
|
|
14016
15180
|
console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
|
|
14017
15181
|
return 1;
|
|
14018
15182
|
}
|
|
14019
|
-
const outputPath =
|
|
14020
|
-
(0,
|
|
15183
|
+
const outputPath = path26.join(os6.tmpdir(), `call-graph-${Date.now()}.html`);
|
|
15184
|
+
(0, import_fs16.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
|
|
14021
15185
|
console.log(`Temporal call graph visualization generated: ${outputPath}`);
|
|
14022
15186
|
console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
|
|
14023
15187
|
console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);
|
|
@@ -14046,7 +15210,7 @@ async function main() {
|
|
|
14046
15210
|
const transport = new import_stdio.StdioServerTransport();
|
|
14047
15211
|
await server.connect(transport);
|
|
14048
15212
|
let watcher = null;
|
|
14049
|
-
const isHomeDir =
|
|
15213
|
+
const isHomeDir = path26.resolve(args.project) === path26.resolve(os6.homedir());
|
|
14050
15214
|
const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
|
|
14051
15215
|
if (config.indexing.autoIndex && isValidProject) {
|
|
14052
15216
|
const indexer = getIndexerForProject(args.project, args.host);
|
|
@@ -14061,14 +15225,25 @@ async function main() {
|
|
|
14061
15225
|
args.config ? { configPath: args.config } : {}
|
|
14062
15226
|
);
|
|
14063
15227
|
}
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
15228
|
+
let shuttingDown = false;
|
|
15229
|
+
const shutdown = async () => {
|
|
15230
|
+
if (shuttingDown) return;
|
|
15231
|
+
shuttingDown = true;
|
|
15232
|
+
try {
|
|
15233
|
+
await watcher?.stop();
|
|
15234
|
+
await server.close();
|
|
15235
|
+
process.exit(0);
|
|
15236
|
+
} catch (error) {
|
|
15237
|
+
console.error("Failed to stop MCP server cleanly:", error);
|
|
15238
|
+
process.exit(1);
|
|
15239
|
+
}
|
|
14069
15240
|
};
|
|
14070
|
-
process.on("SIGINT",
|
|
14071
|
-
|
|
15241
|
+
process.on("SIGINT", () => {
|
|
15242
|
+
void shutdown();
|
|
15243
|
+
});
|
|
15244
|
+
process.on("SIGTERM", () => {
|
|
15245
|
+
void shutdown();
|
|
15246
|
+
});
|
|
14072
15247
|
}
|
|
14073
15248
|
function handleMainError(error) {
|
|
14074
15249
|
if (error instanceof Error && error.message.startsWith("Invalid host mode")) {
|