opencode-codebase-index 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -4
- package/dist/cli.cjs +1639 -557
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1638 -546
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1598 -531
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1594 -517
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +1305 -303
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +1304 -292
- package/dist/pi-extension.js.map +1 -1
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +11 -8
package/dist/pi-extension.cjs
CHANGED
|
@@ -495,7 +495,7 @@ var require_ignore = __commonJS({
|
|
|
495
495
|
// path matching.
|
|
496
496
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
497
497
|
// @returns {TestResult} true if a file is ignored
|
|
498
|
-
test(
|
|
498
|
+
test(path17, checkUnignored, mode) {
|
|
499
499
|
let ignored = false;
|
|
500
500
|
let unignored = false;
|
|
501
501
|
let matchedRule;
|
|
@@ -504,7 +504,7 @@ var require_ignore = __commonJS({
|
|
|
504
504
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
505
505
|
return;
|
|
506
506
|
}
|
|
507
|
-
const matched = rule[mode].test(
|
|
507
|
+
const matched = rule[mode].test(path17);
|
|
508
508
|
if (!matched) {
|
|
509
509
|
return;
|
|
510
510
|
}
|
|
@@ -525,17 +525,17 @@ var require_ignore = __commonJS({
|
|
|
525
525
|
var throwError = (message, Ctor) => {
|
|
526
526
|
throw new Ctor(message);
|
|
527
527
|
};
|
|
528
|
-
var checkPath = (
|
|
529
|
-
if (!isString(
|
|
528
|
+
var checkPath = (path17, originalPath, doThrow) => {
|
|
529
|
+
if (!isString(path17)) {
|
|
530
530
|
return doThrow(
|
|
531
531
|
`path must be a string, but got \`${originalPath}\``,
|
|
532
532
|
TypeError
|
|
533
533
|
);
|
|
534
534
|
}
|
|
535
|
-
if (!
|
|
535
|
+
if (!path17) {
|
|
536
536
|
return doThrow(`path must not be empty`, TypeError);
|
|
537
537
|
}
|
|
538
|
-
if (checkPath.isNotRelative(
|
|
538
|
+
if (checkPath.isNotRelative(path17)) {
|
|
539
539
|
const r = "`path.relative()`d";
|
|
540
540
|
return doThrow(
|
|
541
541
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -544,7 +544,7 @@ var require_ignore = __commonJS({
|
|
|
544
544
|
}
|
|
545
545
|
return true;
|
|
546
546
|
};
|
|
547
|
-
var isNotRelative = (
|
|
547
|
+
var isNotRelative = (path17) => REGEX_TEST_INVALID_PATH.test(path17);
|
|
548
548
|
checkPath.isNotRelative = isNotRelative;
|
|
549
549
|
checkPath.convert = (p) => p;
|
|
550
550
|
var Ignore2 = class {
|
|
@@ -574,19 +574,19 @@ var require_ignore = __commonJS({
|
|
|
574
574
|
}
|
|
575
575
|
// @returns {TestResult}
|
|
576
576
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
577
|
-
const
|
|
577
|
+
const path17 = originalPath && checkPath.convert(originalPath);
|
|
578
578
|
checkPath(
|
|
579
|
-
|
|
579
|
+
path17,
|
|
580
580
|
originalPath,
|
|
581
581
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
582
582
|
);
|
|
583
|
-
return this._t(
|
|
583
|
+
return this._t(path17, cache, checkUnignored, slices);
|
|
584
584
|
}
|
|
585
|
-
checkIgnore(
|
|
586
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
587
|
-
return this.test(
|
|
585
|
+
checkIgnore(path17) {
|
|
586
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path17)) {
|
|
587
|
+
return this.test(path17);
|
|
588
588
|
}
|
|
589
|
-
const slices =
|
|
589
|
+
const slices = path17.split(SLASH).filter(Boolean);
|
|
590
590
|
slices.pop();
|
|
591
591
|
if (slices.length) {
|
|
592
592
|
const parent = this._t(
|
|
@@ -599,18 +599,18 @@ var require_ignore = __commonJS({
|
|
|
599
599
|
return parent;
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
|
-
return this._rules.test(
|
|
602
|
+
return this._rules.test(path17, false, MODE_CHECK_IGNORE);
|
|
603
603
|
}
|
|
604
|
-
_t(
|
|
605
|
-
if (
|
|
606
|
-
return cache[
|
|
604
|
+
_t(path17, cache, checkUnignored, slices) {
|
|
605
|
+
if (path17 in cache) {
|
|
606
|
+
return cache[path17];
|
|
607
607
|
}
|
|
608
608
|
if (!slices) {
|
|
609
|
-
slices =
|
|
609
|
+
slices = path17.split(SLASH).filter(Boolean);
|
|
610
610
|
}
|
|
611
611
|
slices.pop();
|
|
612
612
|
if (!slices.length) {
|
|
613
|
-
return cache[
|
|
613
|
+
return cache[path17] = this._rules.test(path17, checkUnignored, MODE_IGNORE);
|
|
614
614
|
}
|
|
615
615
|
const parent = this._t(
|
|
616
616
|
slices.join(SLASH) + SLASH,
|
|
@@ -618,29 +618,29 @@ var require_ignore = __commonJS({
|
|
|
618
618
|
checkUnignored,
|
|
619
619
|
slices
|
|
620
620
|
);
|
|
621
|
-
return cache[
|
|
621
|
+
return cache[path17] = parent.ignored ? parent : this._rules.test(path17, checkUnignored, MODE_IGNORE);
|
|
622
622
|
}
|
|
623
|
-
ignores(
|
|
624
|
-
return this._test(
|
|
623
|
+
ignores(path17) {
|
|
624
|
+
return this._test(path17, this._ignoreCache, false).ignored;
|
|
625
625
|
}
|
|
626
626
|
createFilter() {
|
|
627
|
-
return (
|
|
627
|
+
return (path17) => !this.ignores(path17);
|
|
628
628
|
}
|
|
629
629
|
filter(paths) {
|
|
630
630
|
return makeArray(paths).filter(this.createFilter());
|
|
631
631
|
}
|
|
632
632
|
// @returns {TestResult}
|
|
633
|
-
test(
|
|
634
|
-
return this._test(
|
|
633
|
+
test(path17) {
|
|
634
|
+
return this._test(path17, this._testCache, true);
|
|
635
635
|
}
|
|
636
636
|
};
|
|
637
637
|
var factory = (options) => new Ignore2(options);
|
|
638
|
-
var isPathValid = (
|
|
638
|
+
var isPathValid = (path17) => checkPath(path17 && checkPath.convert(path17), path17, RETURN_FALSE);
|
|
639
639
|
var setupWindows = () => {
|
|
640
640
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
641
641
|
checkPath.convert = makePosix;
|
|
642
642
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
643
|
-
checkPath.isNotRelative = (
|
|
643
|
+
checkPath.isNotRelative = (path17) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path17) || isNotRelative(path17);
|
|
644
644
|
};
|
|
645
645
|
if (
|
|
646
646
|
// Detect `process` so that it can run in browsers.
|
|
@@ -665,10 +665,10 @@ var import_typebox2 = require("typebox");
|
|
|
665
665
|
|
|
666
666
|
// src/config/constants.ts
|
|
667
667
|
var DEFAULT_INCLUDE = [
|
|
668
|
-
"**/*.{ts,tsx,js,jsx,mjs,cjs}",
|
|
668
|
+
"**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
669
669
|
"**/*.{py,pyi}",
|
|
670
|
-
"**/*.{go,rs,java,kt,scala}",
|
|
671
|
-
"**/*.{c,cpp,cc,h,hpp}",
|
|
670
|
+
"**/*.{go,rs,java,cs,kt,scala}",
|
|
671
|
+
"**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
|
|
672
672
|
"**/*.{rb,php,inc,swift}",
|
|
673
673
|
"**/*.{cls,trigger}",
|
|
674
674
|
"**/*.{vue,svelte,astro}",
|
|
@@ -1337,8 +1337,8 @@ function formatPrImpact(result) {
|
|
|
1337
1337
|
}
|
|
1338
1338
|
|
|
1339
1339
|
// src/tools/operations.ts
|
|
1340
|
-
var
|
|
1341
|
-
var
|
|
1340
|
+
var import_fs10 = require("fs");
|
|
1341
|
+
var path16 = __toESM(require("path"), 1);
|
|
1342
1342
|
|
|
1343
1343
|
// src/config/merger.ts
|
|
1344
1344
|
var import_fs5 = require("fs");
|
|
@@ -1535,6 +1535,9 @@ function getHostProjectIndexRelativePath(host) {
|
|
|
1535
1535
|
function hasHostProjectConfig(projectRoot3, host) {
|
|
1536
1536
|
return (0, import_fs4.existsSync)(path4.join(projectRoot3, getProjectConfigRelativePath(host)));
|
|
1537
1537
|
}
|
|
1538
|
+
function hasHostGlobalConfig(host) {
|
|
1539
|
+
return (0, import_fs4.existsSync)(getGlobalConfigPath(host));
|
|
1540
|
+
}
|
|
1538
1541
|
function getGlobalIndexPath(host = "opencode") {
|
|
1539
1542
|
switch (host) {
|
|
1540
1543
|
case "opencode":
|
|
@@ -1574,6 +1577,9 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
1574
1577
|
return hostIndexPath;
|
|
1575
1578
|
}
|
|
1576
1579
|
if (host !== "opencode") {
|
|
1580
|
+
if (hasHostGlobalConfig(host)) {
|
|
1581
|
+
return hostIndexPath;
|
|
1582
|
+
}
|
|
1577
1583
|
const legacyIndexPath = getGlobalIndexPath("opencode");
|
|
1578
1584
|
if ((0, import_fs4.existsSync)(legacyIndexPath)) {
|
|
1579
1585
|
return legacyIndexPath;
|
|
@@ -1839,8 +1845,8 @@ function loadMergedConfig(projectRoot3, host = "opencode") {
|
|
|
1839
1845
|
}
|
|
1840
1846
|
|
|
1841
1847
|
// src/indexer/index.ts
|
|
1842
|
-
var
|
|
1843
|
-
var
|
|
1848
|
+
var import_fs8 = require("fs");
|
|
1849
|
+
var path13 = __toESM(require("path"), 1);
|
|
1844
1850
|
var import_perf_hooks = require("perf_hooks");
|
|
1845
1851
|
var import_child_process3 = require("child_process");
|
|
1846
1852
|
var import_util3 = require("util");
|
|
@@ -2947,17 +2953,17 @@ function validateExternalUrl(urlString) {
|
|
|
2947
2953
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2948
2954
|
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2949
2955
|
}
|
|
2950
|
-
const
|
|
2951
|
-
if (BLOCKED_HOSTNAMES.has(
|
|
2952
|
-
return { valid: false, reason: `Blocked: cloud metadata service (${
|
|
2956
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
2957
|
+
if (BLOCKED_HOSTNAMES.has(hostname2)) {
|
|
2958
|
+
return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
|
|
2953
2959
|
}
|
|
2954
2960
|
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2955
|
-
if (pattern.test(
|
|
2956
|
-
return { valid: false, reason: `Blocked: cloud metadata IP (${
|
|
2961
|
+
if (pattern.test(hostname2)) {
|
|
2962
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
|
|
2957
2963
|
}
|
|
2958
2964
|
}
|
|
2959
|
-
if (/^169\.254\./.test(
|
|
2960
|
-
return { valid: false, reason: `Blocked: link-local address (${
|
|
2965
|
+
if (/^169\.254\./.test(hostname2)) {
|
|
2966
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
|
|
2961
2967
|
}
|
|
2962
2968
|
return { valid: true };
|
|
2963
2969
|
}
|
|
@@ -3995,6 +4001,12 @@ function createMockNativeBinding() {
|
|
|
3995
4001
|
constructor() {
|
|
3996
4002
|
throw error;
|
|
3997
4003
|
}
|
|
4004
|
+
static openReadOnly() {
|
|
4005
|
+
throw error;
|
|
4006
|
+
}
|
|
4007
|
+
static createEmptyReadOnly() {
|
|
4008
|
+
throw error;
|
|
4009
|
+
}
|
|
3998
4010
|
close() {
|
|
3999
4011
|
throw error;
|
|
4000
4012
|
}
|
|
@@ -4098,6 +4110,12 @@ var VectorStore = class {
|
|
|
4098
4110
|
load() {
|
|
4099
4111
|
this.inner.load();
|
|
4100
4112
|
}
|
|
4113
|
+
loadStrict() {
|
|
4114
|
+
this.inner.loadStrict();
|
|
4115
|
+
}
|
|
4116
|
+
hasFingerprint() {
|
|
4117
|
+
return this.inner.hasFingerprint();
|
|
4118
|
+
}
|
|
4101
4119
|
count() {
|
|
4102
4120
|
return this.inner.count();
|
|
4103
4121
|
}
|
|
@@ -4380,12 +4398,24 @@ var InvertedIndex = class {
|
|
|
4380
4398
|
return this.inner.documentCount();
|
|
4381
4399
|
}
|
|
4382
4400
|
};
|
|
4383
|
-
var Database = class {
|
|
4401
|
+
var Database = class _Database {
|
|
4384
4402
|
inner;
|
|
4385
4403
|
closed = false;
|
|
4386
4404
|
constructor(dbPath) {
|
|
4387
4405
|
this.inner = new native.Database(dbPath);
|
|
4388
4406
|
}
|
|
4407
|
+
static fromNative(inner) {
|
|
4408
|
+
const database = Object.create(_Database.prototype);
|
|
4409
|
+
database.inner = inner;
|
|
4410
|
+
database.closed = false;
|
|
4411
|
+
return database;
|
|
4412
|
+
}
|
|
4413
|
+
static openReadOnly(dbPath) {
|
|
4414
|
+
return _Database.fromNative(native.Database.openReadOnly(dbPath));
|
|
4415
|
+
}
|
|
4416
|
+
static createEmptyReadOnly() {
|
|
4417
|
+
return _Database.fromNative(native.Database.createEmptyReadOnly());
|
|
4418
|
+
}
|
|
4389
4419
|
throwIfClosed() {
|
|
4390
4420
|
if (this.closed) {
|
|
4391
4421
|
throw new Error("Database is closed");
|
|
@@ -4814,6 +4844,418 @@ async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
|
|
|
4814
4844
|
}
|
|
4815
4845
|
}
|
|
4816
4846
|
|
|
4847
|
+
// src/indexer/index-lock.ts
|
|
4848
|
+
var import_crypto = require("crypto");
|
|
4849
|
+
var import_fs7 = require("fs");
|
|
4850
|
+
var os4 = __toESM(require("os"), 1);
|
|
4851
|
+
var path12 = __toESM(require("path"), 1);
|
|
4852
|
+
var OWNER_FILE_NAME = "owner.json";
|
|
4853
|
+
var RECLAIM_DIRECTORY_NAME = "reclaiming";
|
|
4854
|
+
var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
|
|
4855
|
+
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;
|
|
4856
|
+
var VALID_OPERATIONS = /* @__PURE__ */ new Set([
|
|
4857
|
+
"initialize",
|
|
4858
|
+
"index",
|
|
4859
|
+
"force-index",
|
|
4860
|
+
"clear",
|
|
4861
|
+
"health-check",
|
|
4862
|
+
"retry-failed-batches",
|
|
4863
|
+
"recovery"
|
|
4864
|
+
]);
|
|
4865
|
+
var temporaryCounter = 0;
|
|
4866
|
+
function getErrorCode(error) {
|
|
4867
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
4868
|
+
}
|
|
4869
|
+
function retryTransientFilesystemOperation(operation) {
|
|
4870
|
+
let lastError;
|
|
4871
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4872
|
+
try {
|
|
4873
|
+
operation();
|
|
4874
|
+
return;
|
|
4875
|
+
} catch (error) {
|
|
4876
|
+
lastError = error;
|
|
4877
|
+
const code = getErrorCode(error);
|
|
4878
|
+
if (code !== "EBUSY" && code !== "EPERM") throw error;
|
|
4879
|
+
if (attempt < 2) {
|
|
4880
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
throw lastError;
|
|
4885
|
+
}
|
|
4886
|
+
function parseOwner(value) {
|
|
4887
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4888
|
+
const candidate = value;
|
|
4889
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4890
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4891
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4892
|
+
if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
|
|
4893
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4894
|
+
return candidate;
|
|
4895
|
+
}
|
|
4896
|
+
function parseReclaimOwner(value) {
|
|
4897
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4898
|
+
const candidate = value;
|
|
4899
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4900
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4901
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4902
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4903
|
+
if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
|
|
4904
|
+
return candidate;
|
|
4905
|
+
}
|
|
4906
|
+
function readJsonDirectory(directoryPath, parser) {
|
|
4907
|
+
try {
|
|
4908
|
+
return parser(JSON.parse((0, import_fs7.readFileSync)(path12.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
|
|
4909
|
+
} catch {
|
|
4910
|
+
return null;
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
function readDirectoryOwner(lockPath) {
|
|
4914
|
+
return readJsonDirectory(lockPath, parseOwner);
|
|
4915
|
+
}
|
|
4916
|
+
function readReclaimOwner(markerPath) {
|
|
4917
|
+
return readJsonDirectory(markerPath, parseReclaimOwner);
|
|
4918
|
+
}
|
|
4919
|
+
function readRecoveryOwner(markerPath) {
|
|
4920
|
+
return readDirectoryOwner(markerPath);
|
|
4921
|
+
}
|
|
4922
|
+
function readLegacyOwner(lockPath) {
|
|
4923
|
+
try {
|
|
4924
|
+
const parsed = JSON.parse((0, import_fs7.readFileSync)(lockPath, "utf-8"));
|
|
4925
|
+
if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
|
|
4926
|
+
if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
|
|
4927
|
+
return {
|
|
4928
|
+
pid: Number(parsed.pid),
|
|
4929
|
+
hostname: typeof parsed.hostname === "string" ? parsed.hostname : os4.hostname(),
|
|
4930
|
+
startedAt: parsed.startedAt,
|
|
4931
|
+
operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
|
|
4932
|
+
token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
|
|
4933
|
+
};
|
|
4934
|
+
} catch {
|
|
4935
|
+
return null;
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
function getOwnerLiveness(owner) {
|
|
4939
|
+
if (owner.hostname !== os4.hostname()) return "unknown";
|
|
4940
|
+
try {
|
|
4941
|
+
process.kill(owner.pid, 0);
|
|
4942
|
+
return "alive";
|
|
4943
|
+
} catch (error) {
|
|
4944
|
+
const code = getErrorCode(error);
|
|
4945
|
+
if (code === "ESRCH") return "dead";
|
|
4946
|
+
if (code === "EPERM") return "alive";
|
|
4947
|
+
return "unknown";
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
function sameOwner(left, right) {
|
|
4951
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
|
|
4952
|
+
}
|
|
4953
|
+
function sameReclaimOwner(left, right) {
|
|
4954
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
|
|
4955
|
+
}
|
|
4956
|
+
function publishJsonDirectory(finalPath, value) {
|
|
4957
|
+
const candidatePath = `${finalPath}.candidate.${process.pid}.${(0, import_crypto.randomUUID)()}`;
|
|
4958
|
+
(0, import_fs7.mkdirSync)(candidatePath, { mode: 448 });
|
|
4959
|
+
try {
|
|
4960
|
+
(0, import_fs7.writeFileSync)(path12.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
|
|
4961
|
+
encoding: "utf-8",
|
|
4962
|
+
flag: "wx",
|
|
4963
|
+
mode: 384
|
|
4964
|
+
});
|
|
4965
|
+
if ((0, import_fs7.existsSync)(finalPath)) return false;
|
|
4966
|
+
try {
|
|
4967
|
+
(0, import_fs7.renameSync)(candidatePath, finalPath);
|
|
4968
|
+
return true;
|
|
4969
|
+
} catch (error) {
|
|
4970
|
+
if ((0, import_fs7.existsSync)(finalPath)) return false;
|
|
4971
|
+
throw error;
|
|
4972
|
+
}
|
|
4973
|
+
} finally {
|
|
4974
|
+
if ((0, import_fs7.existsSync)(candidatePath)) (0, import_fs7.rmSync)(candidatePath, { recursive: true, force: true });
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4977
|
+
function createOwner(operation) {
|
|
4978
|
+
return {
|
|
4979
|
+
pid: process.pid,
|
|
4980
|
+
hostname: os4.hostname(),
|
|
4981
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4982
|
+
operation,
|
|
4983
|
+
token: (0, import_crypto.randomUUID)()
|
|
4984
|
+
};
|
|
4985
|
+
}
|
|
4986
|
+
function recoveryMarkerPath(indexPath, owner) {
|
|
4987
|
+
return path12.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
|
|
4988
|
+
}
|
|
4989
|
+
function publishRecoveryMarker(indexPath, owner) {
|
|
4990
|
+
const markerPath = recoveryMarkerPath(indexPath, owner);
|
|
4991
|
+
if (!publishJsonDirectory(markerPath, owner)) {
|
|
4992
|
+
const markerOwner = readRecoveryOwner(markerPath);
|
|
4993
|
+
if (!markerOwner || !sameOwner(markerOwner, owner)) {
|
|
4994
|
+
throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
return markerPath;
|
|
4998
|
+
}
|
|
4999
|
+
function getPendingRecoveries(indexPath) {
|
|
5000
|
+
const recoveries = [];
|
|
5001
|
+
const markerNames = (0, import_fs7.readdirSync)(indexPath).filter((name) => {
|
|
5002
|
+
if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
|
|
5003
|
+
return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
|
|
5004
|
+
}).sort();
|
|
5005
|
+
for (const markerName of markerNames) {
|
|
5006
|
+
const markerPath = path12.join(indexPath, markerName);
|
|
5007
|
+
const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
|
|
5008
|
+
let markerStats;
|
|
5009
|
+
try {
|
|
5010
|
+
markerStats = (0, import_fs7.lstatSync)(markerPath);
|
|
5011
|
+
} catch (error) {
|
|
5012
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5013
|
+
throw error;
|
|
5014
|
+
}
|
|
5015
|
+
if (!markerStats.isDirectory()) {
|
|
5016
|
+
throw new IndexLockContentionError(markerPath, null, "unknown-owner");
|
|
5017
|
+
}
|
|
5018
|
+
const owner = readRecoveryOwner(markerPath);
|
|
5019
|
+
if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
|
|
5020
|
+
throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
|
|
5021
|
+
}
|
|
5022
|
+
recoveries.push({ owner, markerPath });
|
|
5023
|
+
}
|
|
5024
|
+
recoveries.sort((left, right) => {
|
|
5025
|
+
const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
|
|
5026
|
+
return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
|
|
5027
|
+
});
|
|
5028
|
+
return recoveries;
|
|
5029
|
+
}
|
|
5030
|
+
function cleanupDeadPublicationCandidates(indexPath) {
|
|
5031
|
+
const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
|
|
5032
|
+
for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
|
|
5033
|
+
const match = candidatePattern.exec(entry);
|
|
5034
|
+
if (!match) continue;
|
|
5035
|
+
const pid = Number(match[1]);
|
|
5036
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
5037
|
+
if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
|
|
5038
|
+
(0, import_fs7.rmSync)(path12.join(indexPath, entry), { recursive: true, force: true });
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
function removeDeadReclaimMarker(lockPath, expectedOwner) {
|
|
5042
|
+
const markerPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5043
|
+
const marker = readReclaimOwner(markerPath);
|
|
5044
|
+
if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
|
|
5045
|
+
return false;
|
|
5046
|
+
}
|
|
5047
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5048
|
+
if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5049
|
+
return false;
|
|
5050
|
+
}
|
|
5051
|
+
const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${(0, import_crypto.randomUUID)()}`;
|
|
5052
|
+
try {
|
|
5053
|
+
(0, import_fs7.renameSync)(markerPath, claimedMarkerPath);
|
|
5054
|
+
} catch (error) {
|
|
5055
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5056
|
+
throw error;
|
|
5057
|
+
}
|
|
5058
|
+
const claimedMarker = readReclaimOwner(claimedMarkerPath);
|
|
5059
|
+
const ownerAfterClaim = readDirectoryOwner(lockPath);
|
|
5060
|
+
if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
|
|
5061
|
+
if (!(0, import_fs7.existsSync)(markerPath) && (0, import_fs7.existsSync)(claimedMarkerPath)) {
|
|
5062
|
+
(0, import_fs7.renameSync)(claimedMarkerPath, markerPath);
|
|
5063
|
+
}
|
|
5064
|
+
return false;
|
|
5065
|
+
}
|
|
5066
|
+
(0, import_fs7.rmSync)(claimedMarkerPath, { recursive: true, force: true });
|
|
5067
|
+
return true;
|
|
5068
|
+
}
|
|
5069
|
+
function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
|
|
5070
|
+
const reclaimPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5071
|
+
const reclaimOwner = {
|
|
5072
|
+
pid: process.pid,
|
|
5073
|
+
hostname: os4.hostname(),
|
|
5074
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5075
|
+
token: (0, import_crypto.randomUUID)(),
|
|
5076
|
+
expectedOwnerToken: expectedOwner.token
|
|
5077
|
+
};
|
|
5078
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
5079
|
+
if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
|
|
5080
|
+
if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
|
|
5081
|
+
return false;
|
|
5082
|
+
}
|
|
5083
|
+
try {
|
|
5084
|
+
const currentReclaimer = readReclaimOwner(reclaimPath);
|
|
5085
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5086
|
+
if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5087
|
+
return false;
|
|
5088
|
+
}
|
|
5089
|
+
publishRecoveryMarker(indexPath, expectedOwner);
|
|
5090
|
+
const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
|
|
5091
|
+
const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
|
|
5092
|
+
if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
|
|
5093
|
+
return false;
|
|
5094
|
+
}
|
|
5095
|
+
const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
|
|
5096
|
+
(0, import_fs7.renameSync)(lockPath, quarantinePath);
|
|
5097
|
+
(0, import_fs7.rmSync)(quarantinePath, { recursive: true, force: true });
|
|
5098
|
+
return true;
|
|
5099
|
+
} catch (error) {
|
|
5100
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5101
|
+
throw error;
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5104
|
+
var IndexLockContentionError = class extends Error {
|
|
5105
|
+
constructor(lockPath, owner, reason) {
|
|
5106
|
+
const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
|
|
5107
|
+
super(`Index mutation already in progress: ${ownerDescription}`);
|
|
5108
|
+
this.lockPath = lockPath;
|
|
5109
|
+
this.owner = owner;
|
|
5110
|
+
this.reason = reason;
|
|
5111
|
+
this.name = "IndexLockContentionError";
|
|
5112
|
+
}
|
|
5113
|
+
lockPath;
|
|
5114
|
+
owner;
|
|
5115
|
+
reason;
|
|
5116
|
+
code = "INDEX_BUSY";
|
|
5117
|
+
};
|
|
5118
|
+
function isIndexLockContentionError(error) {
|
|
5119
|
+
return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
|
|
5120
|
+
}
|
|
5121
|
+
function acquireIndexLock(indexPath, operation) {
|
|
5122
|
+
(0, import_fs7.mkdirSync)(indexPath, { recursive: true });
|
|
5123
|
+
const canonicalIndexPath = import_fs7.realpathSync.native(indexPath);
|
|
5124
|
+
const lockPath = path12.join(canonicalIndexPath, "indexing.lock");
|
|
5125
|
+
cleanupDeadPublicationCandidates(canonicalIndexPath);
|
|
5126
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
5127
|
+
const owner = createOwner(operation);
|
|
5128
|
+
if (publishJsonDirectory(lockPath, owner)) {
|
|
5129
|
+
const lease = {
|
|
5130
|
+
canonicalIndexPath,
|
|
5131
|
+
lockPath,
|
|
5132
|
+
owner,
|
|
5133
|
+
recoveries: []
|
|
5134
|
+
};
|
|
5135
|
+
try {
|
|
5136
|
+
lease.recoveries = getPendingRecoveries(canonicalIndexPath);
|
|
5137
|
+
return lease;
|
|
5138
|
+
} catch (error) {
|
|
5139
|
+
releaseIndexLock(lease);
|
|
5140
|
+
throw error;
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
let stats;
|
|
5144
|
+
try {
|
|
5145
|
+
stats = (0, import_fs7.lstatSync)(lockPath);
|
|
5146
|
+
} catch (error) {
|
|
5147
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5148
|
+
throw error;
|
|
5149
|
+
}
|
|
5150
|
+
if (!stats.isDirectory()) {
|
|
5151
|
+
const legacyOwner = readLegacyOwner(lockPath);
|
|
5152
|
+
throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
|
|
5153
|
+
}
|
|
5154
|
+
const existingOwner = readDirectoryOwner(lockPath);
|
|
5155
|
+
if (!existingOwner) {
|
|
5156
|
+
throw new IndexLockContentionError(lockPath, null, "unknown-owner");
|
|
5157
|
+
}
|
|
5158
|
+
const liveness = getOwnerLiveness(existingOwner);
|
|
5159
|
+
if (liveness !== "dead") {
|
|
5160
|
+
throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
|
|
5161
|
+
}
|
|
5162
|
+
if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
|
|
5163
|
+
throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
throw new IndexLockContentionError(lockPath, null, "reclaiming");
|
|
5167
|
+
}
|
|
5168
|
+
function releaseIndexLock(lease) {
|
|
5169
|
+
const currentOwner = readDirectoryOwner(lease.lockPath);
|
|
5170
|
+
if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
|
|
5171
|
+
const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
|
|
5172
|
+
try {
|
|
5173
|
+
retryTransientFilesystemOperation(() => (0, import_fs7.renameSync)(lease.lockPath, releasePath));
|
|
5174
|
+
} catch (error) {
|
|
5175
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5176
|
+
throw error;
|
|
5177
|
+
}
|
|
5178
|
+
const claimedOwner = readDirectoryOwner(releasePath);
|
|
5179
|
+
if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
|
|
5180
|
+
if (!(0, import_fs7.existsSync)(lease.lockPath) && (0, import_fs7.existsSync)(releasePath)) {
|
|
5181
|
+
(0, import_fs7.renameSync)(releasePath, lease.lockPath);
|
|
5182
|
+
}
|
|
5183
|
+
return false;
|
|
5184
|
+
}
|
|
5185
|
+
try {
|
|
5186
|
+
retryTransientFilesystemOperation(() => (0, import_fs7.rmSync)(releasePath, { recursive: true, force: true }));
|
|
5187
|
+
} catch (error) {
|
|
5188
|
+
console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
|
|
5189
|
+
}
|
|
5190
|
+
return true;
|
|
5191
|
+
}
|
|
5192
|
+
async function withIndexLock(indexPath, operation, callback, options = {}) {
|
|
5193
|
+
const lease = acquireIndexLock(indexPath, operation);
|
|
5194
|
+
let result;
|
|
5195
|
+
let callbackError;
|
|
5196
|
+
let callbackFailed = false;
|
|
5197
|
+
try {
|
|
5198
|
+
result = await callback(lease);
|
|
5199
|
+
} catch (error) {
|
|
5200
|
+
callbackFailed = true;
|
|
5201
|
+
callbackError = error;
|
|
5202
|
+
}
|
|
5203
|
+
if (!callbackFailed && options.completeRecoveries !== false) {
|
|
5204
|
+
try {
|
|
5205
|
+
completeLeaseRecovery(lease);
|
|
5206
|
+
} catch (error) {
|
|
5207
|
+
callbackFailed = true;
|
|
5208
|
+
callbackError = error;
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
let releaseError;
|
|
5212
|
+
try {
|
|
5213
|
+
if (!releaseIndexLock(lease)) {
|
|
5214
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
5215
|
+
}
|
|
5216
|
+
} catch (error) {
|
|
5217
|
+
releaseError = error;
|
|
5218
|
+
}
|
|
5219
|
+
if (releaseError !== void 0) {
|
|
5220
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
5221
|
+
throw releaseError;
|
|
5222
|
+
}
|
|
5223
|
+
if (callbackFailed) throw callbackError;
|
|
5224
|
+
return result;
|
|
5225
|
+
}
|
|
5226
|
+
function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
|
|
5227
|
+
if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
|
|
5228
|
+
temporaryCounter += 1;
|
|
5229
|
+
return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
|
|
5230
|
+
}
|
|
5231
|
+
function removeLeaseTemporaryPath(temporaryPath) {
|
|
5232
|
+
if ((0, import_fs7.existsSync)(temporaryPath)) (0, import_fs7.rmSync)(temporaryPath, { recursive: true, force: true });
|
|
5233
|
+
}
|
|
5234
|
+
function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
|
|
5235
|
+
for (const targetPath of backupTargets) {
|
|
5236
|
+
const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
|
|
5237
|
+
if (!(0, import_fs7.existsSync)(backupPath)) continue;
|
|
5238
|
+
if ((0, import_fs7.existsSync)(targetPath)) (0, import_fs7.rmSync)(targetPath, { recursive: true, force: true });
|
|
5239
|
+
(0, import_fs7.renameSync)(backupPath, targetPath);
|
|
5240
|
+
}
|
|
5241
|
+
const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
|
|
5242
|
+
for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
|
|
5243
|
+
if (!entry.includes(temporaryOwnerMarker)) continue;
|
|
5244
|
+
(0, import_fs7.rmSync)(path12.join(indexPath, entry), { recursive: true, force: true });
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
function completeLeaseRecovery(lease) {
|
|
5248
|
+
for (const recovery of lease.recoveries) {
|
|
5249
|
+
const markerOwner = readRecoveryOwner(recovery.markerPath);
|
|
5250
|
+
if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
|
|
5251
|
+
throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
for (const recovery of lease.recoveries) {
|
|
5255
|
+
(0, import_fs7.rmSync)(recovery.markerPath, { recursive: true, force: true });
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
|
|
4817
5259
|
// src/indexer/index.ts
|
|
4818
5260
|
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
|
|
4819
5261
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
@@ -4895,6 +5337,7 @@ function isSqliteCorruptionError(error) {
|
|
|
4895
5337
|
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");
|
|
4896
5338
|
}
|
|
4897
5339
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
5340
|
+
var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
|
|
4898
5341
|
function metadataFromBlame(blame) {
|
|
4899
5342
|
if (!blame) {
|
|
4900
5343
|
return {};
|
|
@@ -5092,9 +5535,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
5092
5535
|
return true;
|
|
5093
5536
|
}
|
|
5094
5537
|
function isPathWithinRoot(filePath, rootPath) {
|
|
5095
|
-
const normalizedFilePath =
|
|
5096
|
-
const normalizedRoot =
|
|
5097
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
5538
|
+
const normalizedFilePath = path13.resolve(filePath);
|
|
5539
|
+
const normalizedRoot = path13.resolve(rootPath);
|
|
5540
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
|
|
5098
5541
|
}
|
|
5099
5542
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
5100
5543
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -6160,26 +6603,133 @@ var Indexer = class {
|
|
|
6160
6603
|
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
6161
6604
|
querySimilarityThreshold = 0.85;
|
|
6162
6605
|
indexCompatibility = null;
|
|
6163
|
-
|
|
6606
|
+
activeIndexLease = null;
|
|
6607
|
+
initializationPromise = null;
|
|
6608
|
+
initializationMode = "none";
|
|
6609
|
+
readIssues = [];
|
|
6610
|
+
retiredDatabases = [];
|
|
6611
|
+
readerArtifactFingerprint = null;
|
|
6612
|
+
writerArtifactFingerprint = null;
|
|
6613
|
+
readerArtifactRetryAfter = /* @__PURE__ */ new Map();
|
|
6164
6614
|
constructor(projectRoot3, config, host = "opencode") {
|
|
6165
6615
|
this.projectRoot = projectRoot3;
|
|
6166
6616
|
this.config = config;
|
|
6167
6617
|
this.host = host;
|
|
6168
6618
|
this.indexPath = this.getIndexPath();
|
|
6169
|
-
this.fileHashCachePath =
|
|
6170
|
-
this.failedBatchesPath =
|
|
6171
|
-
this.indexingLockPath = path12.join(this.indexPath, "indexing.lock");
|
|
6619
|
+
this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
|
|
6620
|
+
this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
|
|
6172
6621
|
this.logger = initializeLogger(config.debug);
|
|
6173
6622
|
}
|
|
6174
6623
|
getIndexPath() {
|
|
6175
6624
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
|
|
6176
6625
|
}
|
|
6626
|
+
isLocalProjectIndexPath() {
|
|
6627
|
+
const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
|
|
6628
|
+
if (this.host !== "opencode") {
|
|
6629
|
+
localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
6630
|
+
}
|
|
6631
|
+
return localProjectIndexPaths.some((localPath) => {
|
|
6632
|
+
if (!(0, import_fs8.existsSync)(localPath) || !(0, import_fs8.existsSync)(this.indexPath)) {
|
|
6633
|
+
return path13.resolve(this.indexPath) === path13.resolve(localPath);
|
|
6634
|
+
}
|
|
6635
|
+
const indexStats = (0, import_fs8.statSync)(this.indexPath);
|
|
6636
|
+
const localStats = (0, import_fs8.statSync)(localPath);
|
|
6637
|
+
return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
|
|
6638
|
+
});
|
|
6639
|
+
}
|
|
6640
|
+
resetLoadedIndexState(retireDatabase = false) {
|
|
6641
|
+
if (this.database) {
|
|
6642
|
+
if (retireDatabase) {
|
|
6643
|
+
this.retiredDatabases.push(this.database);
|
|
6644
|
+
} else {
|
|
6645
|
+
this.database.close();
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
6648
|
+
this.store = null;
|
|
6649
|
+
this.invertedIndex = null;
|
|
6650
|
+
this.database = null;
|
|
6651
|
+
this.provider = null;
|
|
6652
|
+
this.configuredProviderInfo = null;
|
|
6653
|
+
this.reranker = null;
|
|
6654
|
+
this.indexCompatibility = null;
|
|
6655
|
+
this.initializationMode = "none";
|
|
6656
|
+
this.readIssues = [];
|
|
6657
|
+
this.readerArtifactFingerprint = null;
|
|
6658
|
+
this.writerArtifactFingerprint = null;
|
|
6659
|
+
this.readerArtifactRetryAfter.clear();
|
|
6660
|
+
this.fileHashCache.clear();
|
|
6661
|
+
}
|
|
6662
|
+
refreshLoadedIndexState() {
|
|
6663
|
+
if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
|
|
6664
|
+
this.store.load();
|
|
6665
|
+
this.invertedIndex.load();
|
|
6666
|
+
this.fileHashCache.clear();
|
|
6667
|
+
this.loadFileHashCache();
|
|
6668
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
6669
|
+
this.readIssues = [];
|
|
6670
|
+
this.readerArtifactRetryAfter.clear();
|
|
6671
|
+
}
|
|
6672
|
+
async withIndexMutationLease(operation, callback) {
|
|
6673
|
+
const lease = acquireIndexLock(this.indexPath, operation);
|
|
6674
|
+
this.indexPath = lease.canonicalIndexPath;
|
|
6675
|
+
this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
|
|
6676
|
+
this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
|
|
6677
|
+
this.activeIndexLease = lease;
|
|
6678
|
+
let result;
|
|
6679
|
+
let callbackError;
|
|
6680
|
+
let callbackFailed = false;
|
|
6681
|
+
try {
|
|
6682
|
+
result = await callback(lease.recoveries.map(({ owner }) => owner));
|
|
6683
|
+
} catch (error) {
|
|
6684
|
+
callbackFailed = true;
|
|
6685
|
+
callbackError = error;
|
|
6686
|
+
}
|
|
6687
|
+
if (!callbackFailed) {
|
|
6688
|
+
try {
|
|
6689
|
+
completeLeaseRecovery(lease);
|
|
6690
|
+
this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
|
|
6691
|
+
} catch (error) {
|
|
6692
|
+
callbackFailed = true;
|
|
6693
|
+
callbackError = error;
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
let releaseError;
|
|
6697
|
+
try {
|
|
6698
|
+
if (!releaseIndexLock(lease)) {
|
|
6699
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
6700
|
+
this.writerArtifactFingerprint = null;
|
|
6701
|
+
if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6702
|
+
this.activeIndexLease = null;
|
|
6703
|
+
}
|
|
6704
|
+
} else if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6705
|
+
this.activeIndexLease = null;
|
|
6706
|
+
}
|
|
6707
|
+
} catch (error) {
|
|
6708
|
+
releaseError = error;
|
|
6709
|
+
this.writerArtifactFingerprint = null;
|
|
6710
|
+
if (!(0, import_fs8.existsSync)(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6711
|
+
this.activeIndexLease = null;
|
|
6712
|
+
}
|
|
6713
|
+
}
|
|
6714
|
+
if (releaseError !== void 0) {
|
|
6715
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
6716
|
+
throw releaseError;
|
|
6717
|
+
}
|
|
6718
|
+
if (callbackFailed) throw callbackError;
|
|
6719
|
+
return result;
|
|
6720
|
+
}
|
|
6721
|
+
requireActiveLease() {
|
|
6722
|
+
if (!this.activeIndexLease) {
|
|
6723
|
+
throw new Error("Index mutation attempted without an active interprocess lease");
|
|
6724
|
+
}
|
|
6725
|
+
return this.activeIndexLease;
|
|
6726
|
+
}
|
|
6177
6727
|
loadFileHashCache() {
|
|
6178
|
-
if (!(0,
|
|
6728
|
+
if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
|
|
6179
6729
|
return;
|
|
6180
6730
|
}
|
|
6181
6731
|
try {
|
|
6182
|
-
const data = (0,
|
|
6732
|
+
const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
|
|
6183
6733
|
const parsed = JSON.parse(data);
|
|
6184
6734
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
6185
6735
|
} catch (error) {
|
|
@@ -6199,15 +6749,26 @@ var Indexer = class {
|
|
|
6199
6749
|
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
6200
6750
|
}
|
|
6201
6751
|
atomicWriteSync(targetPath, data) {
|
|
6202
|
-
const
|
|
6203
|
-
(
|
|
6204
|
-
(0,
|
|
6205
|
-
|
|
6752
|
+
const lease = this.requireActiveLease();
|
|
6753
|
+
const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
|
|
6754
|
+
(0, import_fs8.mkdirSync)(path13.dirname(targetPath), { recursive: true });
|
|
6755
|
+
try {
|
|
6756
|
+
(0, import_fs8.writeFileSync)(tempPath, data);
|
|
6757
|
+
(0, import_fs8.renameSync)(tempPath, targetPath);
|
|
6758
|
+
} finally {
|
|
6759
|
+
removeLeaseTemporaryPath(tempPath);
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
saveInvertedIndex(invertedIndex) {
|
|
6763
|
+
this.atomicWriteSync(
|
|
6764
|
+
path13.join(this.indexPath, "inverted-index.json"),
|
|
6765
|
+
invertedIndex.serialize()
|
|
6766
|
+
);
|
|
6206
6767
|
}
|
|
6207
6768
|
getScopedRoots() {
|
|
6208
|
-
const roots = /* @__PURE__ */ new Set([
|
|
6769
|
+
const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
|
|
6209
6770
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
6210
|
-
roots.add(
|
|
6771
|
+
roots.add(path13.resolve(this.projectRoot, kbRoot));
|
|
6211
6772
|
}
|
|
6212
6773
|
return Array.from(roots);
|
|
6213
6774
|
}
|
|
@@ -6216,29 +6777,29 @@ var Indexer = class {
|
|
|
6216
6777
|
if (this.config.scope !== "global") {
|
|
6217
6778
|
return branchName;
|
|
6218
6779
|
}
|
|
6219
|
-
const projectHash = hashContent(
|
|
6780
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6220
6781
|
return `${projectHash}:${branchName}`;
|
|
6221
6782
|
}
|
|
6222
6783
|
getBranchCatalogKeyFor(branchName) {
|
|
6223
6784
|
if (this.config.scope !== "global") {
|
|
6224
6785
|
return branchName;
|
|
6225
6786
|
}
|
|
6226
|
-
const projectHash = hashContent(
|
|
6787
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6227
6788
|
return `${projectHash}:${branchName}`;
|
|
6228
6789
|
}
|
|
6229
6790
|
getLegacyBranchCatalogKey() {
|
|
6230
6791
|
return this.currentBranch || "default";
|
|
6231
6792
|
}
|
|
6232
6793
|
getLegacyMigrationMetadataKey() {
|
|
6233
|
-
const projectHash = hashContent(
|
|
6794
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6234
6795
|
return `index.globalBranchMigration.${projectHash}`;
|
|
6235
6796
|
}
|
|
6236
6797
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
6237
|
-
const projectHash = hashContent(
|
|
6798
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6238
6799
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
6239
6800
|
}
|
|
6240
6801
|
getProjectForceReembedMetadataKey() {
|
|
6241
|
-
const projectHash = hashContent(
|
|
6802
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6242
6803
|
return `index.forceReembed.${projectHash}`;
|
|
6243
6804
|
}
|
|
6244
6805
|
hasProjectForceReembedPending() {
|
|
@@ -6332,7 +6893,7 @@ var Indexer = class {
|
|
|
6332
6893
|
if (!this.database) {
|
|
6333
6894
|
return { chunkIds, symbolIds };
|
|
6334
6895
|
}
|
|
6335
|
-
const projectRootPath =
|
|
6896
|
+
const projectRootPath = path13.resolve(this.projectRoot);
|
|
6336
6897
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6337
6898
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6338
6899
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6355,7 +6916,7 @@ var Indexer = class {
|
|
|
6355
6916
|
if (this.config.scope !== "global") {
|
|
6356
6917
|
return this.getBranchCatalogCleanupKeys();
|
|
6357
6918
|
}
|
|
6358
|
-
const projectHash = hashContent(
|
|
6919
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6359
6920
|
const keys = /* @__PURE__ */ new Set();
|
|
6360
6921
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6361
6922
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6439,7 +7000,7 @@ var Indexer = class {
|
|
|
6439
7000
|
if (!this.database || this.config.scope !== "global") {
|
|
6440
7001
|
return false;
|
|
6441
7002
|
}
|
|
6442
|
-
const projectHash = hashContent(
|
|
7003
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6443
7004
|
const roots = this.getScopedRoots();
|
|
6444
7005
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6445
7006
|
return this.database.getAllBranches().some(
|
|
@@ -6473,7 +7034,7 @@ var Indexer = class {
|
|
|
6473
7034
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6474
7035
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6475
7036
|
]);
|
|
6476
|
-
const projectRootPath =
|
|
7037
|
+
const projectRootPath = path13.resolve(this.projectRoot);
|
|
6477
7038
|
const projectLocalFilePaths = new Set(
|
|
6478
7039
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6479
7040
|
);
|
|
@@ -6535,34 +7096,27 @@ var Indexer = class {
|
|
|
6535
7096
|
database.gcOrphanEmbeddings();
|
|
6536
7097
|
database.gcOrphanChunks();
|
|
6537
7098
|
store.save();
|
|
6538
|
-
|
|
7099
|
+
this.saveInvertedIndex(invertedIndex);
|
|
6539
7100
|
return {
|
|
6540
7101
|
removedChunkIds: removedChunkIdList,
|
|
6541
7102
|
hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
|
|
6542
7103
|
};
|
|
6543
7104
|
}
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
(0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
|
|
6553
|
-
}
|
|
6554
|
-
releaseIndexingLock() {
|
|
6555
|
-
if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
|
|
6556
|
-
(0, import_fs7.unlinkSync)(this.indexingLockPath);
|
|
7105
|
+
async recoverFromInterruptedIndexingUnlocked(owners) {
|
|
7106
|
+
for (const owner of owners) {
|
|
7107
|
+
this.logger.warn("Detected interrupted indexing session, recovering...", {
|
|
7108
|
+
pid: owner.pid,
|
|
7109
|
+
hostname: owner.hostname,
|
|
7110
|
+
operation: owner.operation,
|
|
7111
|
+
startedAt: owner.startedAt
|
|
7112
|
+
});
|
|
6557
7113
|
}
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
7114
|
+
if (this.config.scope === "global") {
|
|
7115
|
+
if ((0, import_fs8.existsSync)(this.fileHashCachePath)) {
|
|
7116
|
+
(0, import_fs8.unlinkSync)(this.fileHashCachePath);
|
|
7117
|
+
}
|
|
7118
|
+
await this.healthCheckUnlocked();
|
|
6563
7119
|
}
|
|
6564
|
-
await this.healthCheck();
|
|
6565
|
-
this.releaseIndexingLock();
|
|
6566
7120
|
this.logger.info("Recovery complete, next index will re-process all files");
|
|
6567
7121
|
}
|
|
6568
7122
|
loadFailedBatches(maxChunkTokens) {
|
|
@@ -6578,10 +7132,10 @@ var Indexer = class {
|
|
|
6578
7132
|
}
|
|
6579
7133
|
}
|
|
6580
7134
|
loadSerializedFailedBatches() {
|
|
6581
|
-
if (!(0,
|
|
7135
|
+
if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
|
|
6582
7136
|
return [];
|
|
6583
7137
|
}
|
|
6584
|
-
const data = (0,
|
|
7138
|
+
const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
|
|
6585
7139
|
const parsed = JSON.parse(data);
|
|
6586
7140
|
return parsed.map((batch) => {
|
|
6587
7141
|
const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
|
|
@@ -6598,15 +7152,15 @@ var Indexer = class {
|
|
|
6598
7152
|
}
|
|
6599
7153
|
saveFailedBatches(batches) {
|
|
6600
7154
|
if (batches.length === 0) {
|
|
6601
|
-
if ((0,
|
|
7155
|
+
if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
|
|
6602
7156
|
try {
|
|
6603
|
-
(0,
|
|
7157
|
+
(0, import_fs8.unlinkSync)(this.failedBatchesPath);
|
|
6604
7158
|
} catch {
|
|
6605
7159
|
}
|
|
6606
7160
|
}
|
|
6607
7161
|
return;
|
|
6608
7162
|
}
|
|
6609
|
-
|
|
7163
|
+
this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
6610
7164
|
}
|
|
6611
7165
|
collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
|
|
6612
7166
|
const retryableById = /* @__PURE__ */ new Map();
|
|
@@ -6786,7 +7340,7 @@ var Indexer = class {
|
|
|
6786
7340
|
const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
|
|
6787
7341
|
parts.push(`intent_hint: ${intent}`);
|
|
6788
7342
|
try {
|
|
6789
|
-
const fileContent = await
|
|
7343
|
+
const fileContent = await import_fs8.promises.readFile(candidate.metadata.filePath, "utf-8");
|
|
6790
7344
|
const lines = fileContent.split("\n");
|
|
6791
7345
|
const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
|
|
6792
7346
|
const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
|
|
@@ -6800,6 +7354,222 @@ var Indexer = class {
|
|
|
6800
7354
|
return parts.join("\n");
|
|
6801
7355
|
}
|
|
6802
7356
|
async initialize() {
|
|
7357
|
+
if (this.initializationPromise) {
|
|
7358
|
+
await this.initializationPromise;
|
|
7359
|
+
}
|
|
7360
|
+
if (this.isInitializedFor("reader")) {
|
|
7361
|
+
return;
|
|
7362
|
+
}
|
|
7363
|
+
await this.initializeOnce("reader", [], { skipAutoGc: true });
|
|
7364
|
+
}
|
|
7365
|
+
async initializeOnce(mode, recoveredOwners, options) {
|
|
7366
|
+
if (this.initializationPromise) {
|
|
7367
|
+
await this.initializationPromise;
|
|
7368
|
+
if (this.isInitializedFor(mode)) {
|
|
7369
|
+
return;
|
|
7370
|
+
}
|
|
7371
|
+
return this.initializeOnce(mode, recoveredOwners, options);
|
|
7372
|
+
}
|
|
7373
|
+
if (this.isInitializedFor(mode)) {
|
|
7374
|
+
return;
|
|
7375
|
+
}
|
|
7376
|
+
const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
|
|
7377
|
+
this.resetLoadedIndexState();
|
|
7378
|
+
throw error;
|
|
7379
|
+
}).finally(() => {
|
|
7380
|
+
if (this.initializationPromise === initialization) {
|
|
7381
|
+
this.initializationPromise = null;
|
|
7382
|
+
}
|
|
7383
|
+
});
|
|
7384
|
+
this.initializationPromise = initialization;
|
|
7385
|
+
await initialization;
|
|
7386
|
+
}
|
|
7387
|
+
isInitializedFor(mode) {
|
|
7388
|
+
const hasState = Boolean(
|
|
7389
|
+
this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
|
|
7390
|
+
);
|
|
7391
|
+
if (!hasState) {
|
|
7392
|
+
return false;
|
|
7393
|
+
}
|
|
7394
|
+
return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
|
|
7395
|
+
}
|
|
7396
|
+
recordReadIssue(component, message, error) {
|
|
7397
|
+
this.readIssues.push(this.createReadIssue(component, message));
|
|
7398
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7399
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7400
|
+
}
|
|
7401
|
+
createReadIssue(component, message) {
|
|
7402
|
+
return {
|
|
7403
|
+
component,
|
|
7404
|
+
message,
|
|
7405
|
+
blocking: component !== "keyword"
|
|
7406
|
+
};
|
|
7407
|
+
}
|
|
7408
|
+
getVectorReadIssueMessage() {
|
|
7409
|
+
if (this.config.scope === "global") {
|
|
7410
|
+
return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
|
|
7411
|
+
}
|
|
7412
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7413
|
+
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.";
|
|
7414
|
+
}
|
|
7415
|
+
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.";
|
|
7416
|
+
}
|
|
7417
|
+
getKeywordReadIssueMessage() {
|
|
7418
|
+
if (this.config.scope === "global") {
|
|
7419
|
+
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.";
|
|
7420
|
+
}
|
|
7421
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7422
|
+
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.";
|
|
7423
|
+
}
|
|
7424
|
+
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.";
|
|
7425
|
+
}
|
|
7426
|
+
getDatabaseReadIssueMessage() {
|
|
7427
|
+
if (this.config.scope === "global") {
|
|
7428
|
+
return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
|
|
7429
|
+
}
|
|
7430
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7431
|
+
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.";
|
|
7432
|
+
}
|
|
7433
|
+
return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
|
|
7434
|
+
}
|
|
7435
|
+
getReaderFileFingerprint(filePath, identityOnly = false) {
|
|
7436
|
+
try {
|
|
7437
|
+
const stats = (0, import_fs8.statSync)(filePath);
|
|
7438
|
+
if (identityOnly) {
|
|
7439
|
+
return `${stats.dev}:${stats.ino}`;
|
|
7440
|
+
}
|
|
7441
|
+
return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
|
|
7442
|
+
} catch (error) {
|
|
7443
|
+
return `unavailable:${getErrorMessage2(error)}`;
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
captureReaderArtifactFingerprint() {
|
|
7447
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7448
|
+
return {
|
|
7449
|
+
vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
|
|
7450
|
+
keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
|
|
7451
|
+
database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
|
|
7452
|
+
databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
|
|
7453
|
+
};
|
|
7454
|
+
}
|
|
7455
|
+
refreshReaderArtifacts() {
|
|
7456
|
+
if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
|
|
7457
|
+
return;
|
|
7458
|
+
}
|
|
7459
|
+
const previousFingerprint = this.readerArtifactFingerprint;
|
|
7460
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7461
|
+
const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
|
|
7462
|
+
const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
|
|
7463
|
+
const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
|
|
7464
|
+
const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
|
|
7465
|
+
const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
|
|
7466
|
+
const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7467
|
+
if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
|
|
7468
|
+
return;
|
|
7469
|
+
}
|
|
7470
|
+
const setIssue = (component, message, error) => {
|
|
7471
|
+
if (!issues.has(component)) {
|
|
7472
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7473
|
+
}
|
|
7474
|
+
issues.set(component, this.createReadIssue(component, message));
|
|
7475
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7476
|
+
};
|
|
7477
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7478
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7479
|
+
const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
|
|
7480
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
7481
|
+
if (vectorsChanged || retryDue("vectors")) {
|
|
7482
|
+
const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
|
|
7483
|
+
const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
|
|
7484
|
+
if (vectorStoreExists && vectorMetadataExists) {
|
|
7485
|
+
try {
|
|
7486
|
+
const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
|
|
7487
|
+
store.loadStrict();
|
|
7488
|
+
this.store = store;
|
|
7489
|
+
issues.delete("vectors");
|
|
7490
|
+
this.readerArtifactRetryAfter.delete("vectors");
|
|
7491
|
+
} catch (error) {
|
|
7492
|
+
setIssue("vectors", this.getVectorReadIssueMessage(), error);
|
|
7493
|
+
}
|
|
7494
|
+
} else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
|
|
7495
|
+
setIssue("vectors", this.getVectorReadIssueMessage());
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
if (keywordChanged || retryDue("keyword") || !(0, import_fs8.existsSync)(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
|
|
7499
|
+
if ((0, import_fs8.existsSync)(invertedIndexPath)) {
|
|
7500
|
+
try {
|
|
7501
|
+
const invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7502
|
+
invertedIndex.load();
|
|
7503
|
+
this.invertedIndex = invertedIndex;
|
|
7504
|
+
issues.delete("keyword");
|
|
7505
|
+
this.readerArtifactRetryAfter.delete("keyword");
|
|
7506
|
+
} catch (error) {
|
|
7507
|
+
setIssue("keyword", this.getKeywordReadIssueMessage(), error);
|
|
7508
|
+
}
|
|
7509
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
|
|
7510
|
+
setIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
|
|
7514
|
+
if ((0, import_fs8.existsSync)(dbPath)) {
|
|
7515
|
+
try {
|
|
7516
|
+
const database = Database.openReadOnly(dbPath);
|
|
7517
|
+
if (this.database) {
|
|
7518
|
+
this.retiredDatabases.push(this.database);
|
|
7519
|
+
}
|
|
7520
|
+
this.database = database;
|
|
7521
|
+
issues.delete("database");
|
|
7522
|
+
this.readerArtifactRetryAfter.delete("database");
|
|
7523
|
+
} catch (error) {
|
|
7524
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7525
|
+
}
|
|
7526
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
|
|
7527
|
+
setIssue("database", this.getDatabaseReadIssueMessage());
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
if (!issues.has("database")) {
|
|
7531
|
+
try {
|
|
7532
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
7533
|
+
} catch (error) {
|
|
7534
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
this.readIssues = Array.from(issues.values());
|
|
7538
|
+
this.readerArtifactFingerprint = currentFingerprint;
|
|
7539
|
+
}
|
|
7540
|
+
refreshInactiveWriterArtifacts() {
|
|
7541
|
+
if (this.initializationMode !== "writer" || this.activeIndexLease) {
|
|
7542
|
+
return true;
|
|
7543
|
+
}
|
|
7544
|
+
const previousFingerprint = this.writerArtifactFingerprint;
|
|
7545
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7546
|
+
const retryDue = this.readIssues.some(
|
|
7547
|
+
(issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
|
|
7548
|
+
);
|
|
7549
|
+
const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7550
|
+
if (!artifactsChanged && !retryDue) {
|
|
7551
|
+
return true;
|
|
7552
|
+
}
|
|
7553
|
+
if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
|
|
7554
|
+
return false;
|
|
7555
|
+
}
|
|
7556
|
+
this.initializationMode = "reader";
|
|
7557
|
+
this.readerArtifactFingerprint = previousFingerprint;
|
|
7558
|
+
try {
|
|
7559
|
+
this.refreshReaderArtifacts();
|
|
7560
|
+
this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
|
|
7561
|
+
} finally {
|
|
7562
|
+
this.readerArtifactFingerprint = null;
|
|
7563
|
+
this.initializationMode = "writer";
|
|
7564
|
+
}
|
|
7565
|
+
return true;
|
|
7566
|
+
}
|
|
7567
|
+
async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
|
|
7568
|
+
if (mode === "writer") {
|
|
7569
|
+
this.requireActiveLease();
|
|
7570
|
+
}
|
|
7571
|
+
this.readIssues = [];
|
|
7572
|
+
this.readerArtifactRetryAfter.clear();
|
|
6803
7573
|
if (this.config.embeddingProvider === "custom") {
|
|
6804
7574
|
if (!this.config.customProvider) {
|
|
6805
7575
|
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
@@ -6831,36 +7601,103 @@ var Indexer = class {
|
|
|
6831
7601
|
});
|
|
6832
7602
|
}
|
|
6833
7603
|
}
|
|
6834
|
-
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
6835
7604
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6836
|
-
const storePath =
|
|
6837
|
-
|
|
6838
|
-
const
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
7605
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7606
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7607
|
+
const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
|
|
7608
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
7609
|
+
let dbIsNew = !(0, import_fs8.existsSync)(dbPath);
|
|
7610
|
+
const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
|
|
7611
|
+
if (mode === "writer") {
|
|
7612
|
+
await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
|
|
7613
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
|
|
7614
|
+
throw new Error(
|
|
7615
|
+
"Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
|
|
7616
|
+
);
|
|
7617
|
+
}
|
|
7618
|
+
for (const recoveredOwner of recoveredOwners) {
|
|
7619
|
+
recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
|
|
7620
|
+
storePath,
|
|
7621
|
+
`${storePath}.meta.json`
|
|
7622
|
+
]);
|
|
7623
|
+
}
|
|
7624
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project") {
|
|
7625
|
+
await this.resetLocalIndexArtifacts();
|
|
7626
|
+
}
|
|
7627
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7628
|
+
if ((0, import_fs8.existsSync)(storePath) || (0, import_fs8.existsSync)(vectorMetadataPath)) {
|
|
7629
|
+
this.store.load();
|
|
6849
7630
|
}
|
|
6850
7631
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
throw error;
|
|
7632
|
+
try {
|
|
7633
|
+
this.invertedIndex.load();
|
|
7634
|
+
} catch {
|
|
7635
|
+
if ((0, import_fs8.existsSync)(invertedIndexPath)) {
|
|
7636
|
+
await import_fs8.promises.unlink(invertedIndexPath);
|
|
7637
|
+
}
|
|
7638
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6859
7639
|
}
|
|
7640
|
+
try {
|
|
7641
|
+
this.database = new Database(dbPath);
|
|
7642
|
+
} catch (error) {
|
|
7643
|
+
if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
|
|
7644
|
+
throw error;
|
|
7645
|
+
}
|
|
7646
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7647
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7648
|
+
this.database = new Database(dbPath);
|
|
7649
|
+
dbIsNew = true;
|
|
7650
|
+
}
|
|
7651
|
+
} else {
|
|
6860
7652
|
this.store = new VectorStore(storePath, dimensions);
|
|
7653
|
+
const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
|
|
7654
|
+
const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
|
|
7655
|
+
const vectorReadFailureMessage = this.getVectorReadIssueMessage();
|
|
7656
|
+
if (vectorStoreExists !== vectorMetadataExists) {
|
|
7657
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage);
|
|
7658
|
+
} else if (vectorStoreExists) {
|
|
7659
|
+
try {
|
|
7660
|
+
this.store.loadStrict();
|
|
7661
|
+
} catch (error) {
|
|
7662
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage, error);
|
|
7663
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
6861
7666
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6862
|
-
|
|
6863
|
-
|
|
7667
|
+
if ((0, import_fs8.existsSync)(invertedIndexPath)) {
|
|
7668
|
+
try {
|
|
7669
|
+
this.invertedIndex.load();
|
|
7670
|
+
} catch (error) {
|
|
7671
|
+
this.recordReadIssue(
|
|
7672
|
+
"keyword",
|
|
7673
|
+
this.getKeywordReadIssueMessage(),
|
|
7674
|
+
error
|
|
7675
|
+
);
|
|
7676
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7677
|
+
}
|
|
7678
|
+
} else if (this.store.count() > 0) {
|
|
7679
|
+
this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7680
|
+
}
|
|
7681
|
+
if ((0, import_fs8.existsSync)(dbPath)) {
|
|
7682
|
+
try {
|
|
7683
|
+
this.database = Database.openReadOnly(dbPath);
|
|
7684
|
+
} catch (error) {
|
|
7685
|
+
this.recordReadIssue(
|
|
7686
|
+
"database",
|
|
7687
|
+
this.getDatabaseReadIssueMessage(),
|
|
7688
|
+
error
|
|
7689
|
+
);
|
|
7690
|
+
this.database = Database.createEmptyReadOnly();
|
|
7691
|
+
}
|
|
7692
|
+
} else {
|
|
7693
|
+
this.database = Database.createEmptyReadOnly();
|
|
7694
|
+
if (this.store.count() > 0) {
|
|
7695
|
+
this.recordReadIssue(
|
|
7696
|
+
"database",
|
|
7697
|
+
`Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
|
|
7698
|
+
);
|
|
7699
|
+
}
|
|
7700
|
+
}
|
|
6864
7701
|
}
|
|
6865
7702
|
if (isGitRepo(this.projectRoot)) {
|
|
6866
7703
|
this.currentBranch = getBranchOrDefault(this.projectRoot);
|
|
@@ -6874,10 +7711,10 @@ var Indexer = class {
|
|
|
6874
7711
|
this.baseBranch = "default";
|
|
6875
7712
|
this.logger.branch("debug", "Not a git repository, using default branch");
|
|
6876
7713
|
}
|
|
6877
|
-
if (
|
|
6878
|
-
await this.
|
|
7714
|
+
if (mode === "writer" && recoveredOwners.length > 0) {
|
|
7715
|
+
await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
|
|
6879
7716
|
}
|
|
6880
|
-
if (dbIsNew && this.store.count() > 0) {
|
|
7717
|
+
if (mode === "writer" && dbIsNew && this.store.count() > 0) {
|
|
6881
7718
|
this.migrateFromLegacyIndex();
|
|
6882
7719
|
}
|
|
6883
7720
|
this.loadFileHashCache();
|
|
@@ -6889,9 +7726,11 @@ var Indexer = class {
|
|
|
6889
7726
|
configuredProviderInfo: this.configuredProviderInfo
|
|
6890
7727
|
});
|
|
6891
7728
|
}
|
|
6892
|
-
if (this.config.indexing.autoGc) {
|
|
7729
|
+
if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
|
|
6893
7730
|
await this.maybeRunAutoGc();
|
|
6894
7731
|
}
|
|
7732
|
+
this.initializationMode = mode;
|
|
7733
|
+
this.readerArtifactFingerprint = readerArtifactFingerprint;
|
|
6895
7734
|
}
|
|
6896
7735
|
async maybeRunAutoGc() {
|
|
6897
7736
|
if (!this.database) return;
|
|
@@ -6908,7 +7747,7 @@ var Indexer = class {
|
|
|
6908
7747
|
}
|
|
6909
7748
|
}
|
|
6910
7749
|
if (shouldRunGc) {
|
|
6911
|
-
const result = await this.
|
|
7750
|
+
const result = await this.healthCheckUnlocked();
|
|
6912
7751
|
if (result.warning) {
|
|
6913
7752
|
this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
|
|
6914
7753
|
} else {
|
|
@@ -6930,7 +7769,7 @@ var Indexer = class {
|
|
|
6930
7769
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6931
7770
|
return {
|
|
6932
7771
|
resetCorruptedIndex: true,
|
|
6933
|
-
warning: this.getCorruptedIndexWarning(
|
|
7772
|
+
warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
|
|
6934
7773
|
};
|
|
6935
7774
|
}
|
|
6936
7775
|
throw error;
|
|
@@ -6945,28 +7784,29 @@ var Indexer = class {
|
|
|
6945
7784
|
return;
|
|
6946
7785
|
}
|
|
6947
7786
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6948
|
-
const storeBasePath =
|
|
6949
|
-
const storeIndexPath =
|
|
7787
|
+
const storeBasePath = path13.join(this.indexPath, "vectors");
|
|
7788
|
+
const storeIndexPath = storeBasePath;
|
|
6950
7789
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6951
|
-
const
|
|
6952
|
-
const
|
|
7790
|
+
const lease = this.requireActiveLease();
|
|
7791
|
+
const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
|
|
7792
|
+
const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
|
|
6953
7793
|
let backedUpIndex = false;
|
|
6954
7794
|
let backedUpMetadata = false;
|
|
6955
7795
|
let rebuiltCount = 0;
|
|
6956
7796
|
let skippedCount = 0;
|
|
6957
|
-
if ((0,
|
|
6958
|
-
(0,
|
|
7797
|
+
if ((0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7798
|
+
(0, import_fs8.unlinkSync)(backupIndexPath);
|
|
6959
7799
|
}
|
|
6960
|
-
if ((0,
|
|
6961
|
-
(0,
|
|
7800
|
+
if ((0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7801
|
+
(0, import_fs8.unlinkSync)(backupMetadataPath);
|
|
6962
7802
|
}
|
|
6963
7803
|
try {
|
|
6964
|
-
if ((0,
|
|
6965
|
-
(0,
|
|
7804
|
+
if ((0, import_fs8.existsSync)(storeIndexPath)) {
|
|
7805
|
+
(0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
|
|
6966
7806
|
backedUpIndex = true;
|
|
6967
7807
|
}
|
|
6968
|
-
if ((0,
|
|
6969
|
-
(0,
|
|
7808
|
+
if ((0, import_fs8.existsSync)(storeMetadataPath)) {
|
|
7809
|
+
(0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
|
|
6970
7810
|
backedUpMetadata = true;
|
|
6971
7811
|
}
|
|
6972
7812
|
store.clear();
|
|
@@ -6986,11 +7826,11 @@ var Indexer = class {
|
|
|
6986
7826
|
rebuiltCount += 1;
|
|
6987
7827
|
}
|
|
6988
7828
|
store.save();
|
|
6989
|
-
if (backedUpIndex && (0,
|
|
6990
|
-
(0,
|
|
7829
|
+
if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7830
|
+
(0, import_fs8.unlinkSync)(backupIndexPath);
|
|
6991
7831
|
}
|
|
6992
|
-
if (backedUpMetadata && (0,
|
|
6993
|
-
(0,
|
|
7832
|
+
if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7833
|
+
(0, import_fs8.unlinkSync)(backupMetadataPath);
|
|
6994
7834
|
}
|
|
6995
7835
|
this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
|
|
6996
7836
|
excludedChunks: excludedSet.size,
|
|
@@ -7002,17 +7842,17 @@ var Indexer = class {
|
|
|
7002
7842
|
store.clear();
|
|
7003
7843
|
} catch {
|
|
7004
7844
|
}
|
|
7005
|
-
if ((0,
|
|
7006
|
-
(0,
|
|
7845
|
+
if ((0, import_fs8.existsSync)(storeIndexPath)) {
|
|
7846
|
+
(0, import_fs8.unlinkSync)(storeIndexPath);
|
|
7007
7847
|
}
|
|
7008
|
-
if ((0,
|
|
7009
|
-
(0,
|
|
7848
|
+
if ((0, import_fs8.existsSync)(storeMetadataPath)) {
|
|
7849
|
+
(0, import_fs8.unlinkSync)(storeMetadataPath);
|
|
7010
7850
|
}
|
|
7011
|
-
if (backedUpIndex && (0,
|
|
7012
|
-
(0,
|
|
7851
|
+
if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
|
|
7852
|
+
(0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
|
|
7013
7853
|
}
|
|
7014
|
-
if (backedUpMetadata && (0,
|
|
7015
|
-
(0,
|
|
7854
|
+
if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
|
|
7855
|
+
(0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
|
|
7016
7856
|
}
|
|
7017
7857
|
if (backedUpIndex || backedUpMetadata) {
|
|
7018
7858
|
store.load();
|
|
@@ -7026,11 +7866,37 @@ var Indexer = class {
|
|
|
7026
7866
|
}
|
|
7027
7867
|
return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
|
|
7028
7868
|
}
|
|
7869
|
+
async resetLocalIndexArtifacts() {
|
|
7870
|
+
this.store = null;
|
|
7871
|
+
this.invertedIndex = null;
|
|
7872
|
+
this.database?.close();
|
|
7873
|
+
this.database = null;
|
|
7874
|
+
this.indexCompatibility = null;
|
|
7875
|
+
this.initializationMode = "none";
|
|
7876
|
+
this.readIssues = [];
|
|
7877
|
+
this.readerArtifactFingerprint = null;
|
|
7878
|
+
this.writerArtifactFingerprint = null;
|
|
7879
|
+
this.readerArtifactRetryAfter.clear();
|
|
7880
|
+
this.fileHashCache.clear();
|
|
7881
|
+
const resetPaths = [
|
|
7882
|
+
path13.join(this.indexPath, "codebase.db"),
|
|
7883
|
+
path13.join(this.indexPath, "codebase.db-shm"),
|
|
7884
|
+
path13.join(this.indexPath, "codebase.db-wal"),
|
|
7885
|
+
path13.join(this.indexPath, "vectors"),
|
|
7886
|
+
path13.join(this.indexPath, "vectors.usearch"),
|
|
7887
|
+
path13.join(this.indexPath, "vectors.meta.json"),
|
|
7888
|
+
path13.join(this.indexPath, "inverted-index.json"),
|
|
7889
|
+
path13.join(this.indexPath, "file-hashes.json"),
|
|
7890
|
+
path13.join(this.indexPath, "failed-batches.json")
|
|
7891
|
+
];
|
|
7892
|
+
await Promise.all(resetPaths.map((targetPath) => import_fs8.promises.rm(targetPath, { recursive: true, force: true })));
|
|
7893
|
+
await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
|
|
7894
|
+
}
|
|
7029
7895
|
async tryResetCorruptedIndex(stage, error) {
|
|
7030
7896
|
if (!isSqliteCorruptionError(error)) {
|
|
7031
7897
|
return false;
|
|
7032
7898
|
}
|
|
7033
|
-
const dbPath =
|
|
7899
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
7034
7900
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
7035
7901
|
const errorMessage = getErrorMessage2(error);
|
|
7036
7902
|
if (this.config.scope === "global") {
|
|
@@ -7046,30 +7912,7 @@ var Indexer = class {
|
|
|
7046
7912
|
dbPath,
|
|
7047
7913
|
error: errorMessage
|
|
7048
7914
|
});
|
|
7049
|
-
this.
|
|
7050
|
-
this.invertedIndex = null;
|
|
7051
|
-
this.database?.close();
|
|
7052
|
-
this.database = null;
|
|
7053
|
-
this.indexCompatibility = null;
|
|
7054
|
-
this.fileHashCache.clear();
|
|
7055
|
-
const resetPaths = [
|
|
7056
|
-
path12.join(this.indexPath, "codebase.db"),
|
|
7057
|
-
path12.join(this.indexPath, "codebase.db-shm"),
|
|
7058
|
-
path12.join(this.indexPath, "codebase.db-wal"),
|
|
7059
|
-
path12.join(this.indexPath, "vectors.usearch"),
|
|
7060
|
-
path12.join(this.indexPath, "inverted-index.json"),
|
|
7061
|
-
path12.join(this.indexPath, "file-hashes.json"),
|
|
7062
|
-
path12.join(this.indexPath, "failed-batches.json"),
|
|
7063
|
-
path12.join(this.indexPath, "indexing.lock"),
|
|
7064
|
-
path12.join(this.indexPath, "vectors")
|
|
7065
|
-
];
|
|
7066
|
-
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
7067
|
-
try {
|
|
7068
|
-
await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
|
|
7069
|
-
} catch {
|
|
7070
|
-
}
|
|
7071
|
-
}));
|
|
7072
|
-
await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
|
|
7915
|
+
await this.resetLocalIndexArtifacts();
|
|
7073
7916
|
return true;
|
|
7074
7917
|
}
|
|
7075
7918
|
migrateFromLegacyIndex() {
|
|
@@ -7188,8 +8031,62 @@ var Indexer = class {
|
|
|
7188
8031
|
return this.indexCompatibility;
|
|
7189
8032
|
}
|
|
7190
8033
|
async ensureInitialized() {
|
|
8034
|
+
let initializedReader = false;
|
|
8035
|
+
while (true) {
|
|
8036
|
+
if (this.initializationPromise) {
|
|
8037
|
+
await this.initializationPromise;
|
|
8038
|
+
}
|
|
8039
|
+
if (!this.isInitializedFor("reader")) {
|
|
8040
|
+
await this.initialize();
|
|
8041
|
+
initializedReader = true;
|
|
8042
|
+
continue;
|
|
8043
|
+
}
|
|
8044
|
+
if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
|
|
8045
|
+
if (!this.refreshInactiveWriterArtifacts()) {
|
|
8046
|
+
this.resetLoadedIndexState(true);
|
|
8047
|
+
await this.initialize();
|
|
8048
|
+
initializedReader = true;
|
|
8049
|
+
continue;
|
|
8050
|
+
}
|
|
8051
|
+
}
|
|
8052
|
+
if (this.initializationMode === "reader" && !initializedReader) {
|
|
8053
|
+
this.refreshReaderArtifacts();
|
|
8054
|
+
}
|
|
8055
|
+
const state = this.requireLoadedIndexState();
|
|
8056
|
+
return {
|
|
8057
|
+
...state,
|
|
8058
|
+
readIssues: [...this.readIssues],
|
|
8059
|
+
compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
|
|
8060
|
+
};
|
|
8061
|
+
}
|
|
8062
|
+
}
|
|
8063
|
+
async ensureInitializedUnlocked(recoveredOwners = []) {
|
|
8064
|
+
this.requireActiveLease();
|
|
8065
|
+
if (this.initializationPromise) {
|
|
8066
|
+
await this.initializationPromise;
|
|
8067
|
+
}
|
|
8068
|
+
if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
|
|
8069
|
+
const retireReaderDatabase = this.initializationMode === "reader";
|
|
8070
|
+
this.resetLoadedIndexState(retireReaderDatabase);
|
|
8071
|
+
await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
|
|
8072
|
+
} else {
|
|
8073
|
+
this.refreshLoadedIndexState();
|
|
8074
|
+
}
|
|
8075
|
+
if (this.config.indexing.autoGc) {
|
|
8076
|
+
await this.maybeRunAutoGc();
|
|
8077
|
+
}
|
|
8078
|
+
return this.requireLoadedIndexState();
|
|
8079
|
+
}
|
|
8080
|
+
requireReadableComponents(readIssues, ...components) {
|
|
8081
|
+
const componentSet = new Set(components);
|
|
8082
|
+
const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
|
|
8083
|
+
if (issues.length > 0) {
|
|
8084
|
+
throw new Error(issues.map((issue) => issue.message).join(" "));
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
requireLoadedIndexState() {
|
|
7191
8088
|
if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
|
|
7192
|
-
|
|
8089
|
+
throw new Error("Index state is not initialized");
|
|
7193
8090
|
}
|
|
7194
8091
|
return {
|
|
7195
8092
|
store: this.store,
|
|
@@ -7213,7 +8110,12 @@ var Indexer = class {
|
|
|
7213
8110
|
return createCostEstimate(files, configuredProviderInfo);
|
|
7214
8111
|
}
|
|
7215
8112
|
async index(onProgress) {
|
|
7216
|
-
|
|
8113
|
+
return this.withIndexMutationLease("index", async (recoveredOwners) => {
|
|
8114
|
+
return this.indexUnlocked(onProgress, recoveredOwners);
|
|
8115
|
+
});
|
|
8116
|
+
}
|
|
8117
|
+
async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
|
|
8118
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
|
|
7217
8119
|
const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
7218
8120
|
const branchCatalogKey = this.getBranchCatalogKey();
|
|
7219
8121
|
const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
|
|
@@ -7223,7 +8125,6 @@ var Indexer = class {
|
|
|
7223
8125
|
`${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
|
|
7224
8126
|
);
|
|
7225
8127
|
}
|
|
7226
|
-
this.acquireIndexingLock();
|
|
7227
8128
|
this.logger.recordIndexingStart();
|
|
7228
8129
|
this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
|
|
7229
8130
|
const startTime = Date.now();
|
|
@@ -7274,7 +8175,7 @@ var Indexer = class {
|
|
|
7274
8175
|
unchangedFilePaths.add(f.path);
|
|
7275
8176
|
this.logger.recordCacheHit();
|
|
7276
8177
|
} else {
|
|
7277
|
-
const content = await
|
|
8178
|
+
const content = await import_fs8.promises.readFile(f.path, "utf-8");
|
|
7278
8179
|
changedFiles.push({ path: f.path, content, hash: currentHash });
|
|
7279
8180
|
this.logger.recordCacheMiss();
|
|
7280
8181
|
}
|
|
@@ -7372,7 +8273,7 @@ var Indexer = class {
|
|
|
7372
8273
|
for (const parsed of parsedFiles) {
|
|
7373
8274
|
currentFilePaths.add(parsed.path);
|
|
7374
8275
|
if (parsed.chunks.length === 0) {
|
|
7375
|
-
const relativePath =
|
|
8276
|
+
const relativePath = path13.relative(this.projectRoot, parsed.path);
|
|
7376
8277
|
stats.parseFailures.push(relativePath);
|
|
7377
8278
|
}
|
|
7378
8279
|
let fileChunkCount = 0;
|
|
@@ -7572,7 +8473,9 @@ var Indexer = class {
|
|
|
7572
8473
|
database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
|
|
7573
8474
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7574
8475
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7575
|
-
|
|
8476
|
+
const vectorPath = path13.join(this.indexPath, "vectors");
|
|
8477
|
+
const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs8.existsSync)(vectorPath) && (0, import_fs8.existsSync)(`${vectorPath}.meta.json`);
|
|
8478
|
+
if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
|
|
7576
8479
|
store.save();
|
|
7577
8480
|
}
|
|
7578
8481
|
if (scopedRoots) {
|
|
@@ -7593,7 +8496,6 @@ var Indexer = class {
|
|
|
7593
8496
|
chunksProcessed: 0,
|
|
7594
8497
|
totalChunks: 0
|
|
7595
8498
|
});
|
|
7596
|
-
this.releaseIndexingLock();
|
|
7597
8499
|
return stats;
|
|
7598
8500
|
}
|
|
7599
8501
|
if (pendingChunks.length === 0) {
|
|
@@ -7602,7 +8504,7 @@ var Indexer = class {
|
|
|
7602
8504
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7603
8505
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7604
8506
|
store.save();
|
|
7605
|
-
|
|
8507
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7606
8508
|
if (scopedRoots) {
|
|
7607
8509
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7608
8510
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7621,7 +8523,6 @@ var Indexer = class {
|
|
|
7621
8523
|
chunksProcessed: 0,
|
|
7622
8524
|
totalChunks: 0
|
|
7623
8525
|
});
|
|
7624
|
-
this.releaseIndexingLock();
|
|
7625
8526
|
return stats;
|
|
7626
8527
|
}
|
|
7627
8528
|
onProgress?.({
|
|
@@ -7852,7 +8753,7 @@ var Indexer = class {
|
|
|
7852
8753
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7853
8754
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7854
8755
|
store.save();
|
|
7855
|
-
|
|
8756
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7856
8757
|
if (scopedRoots) {
|
|
7857
8758
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7858
8759
|
} else {
|
|
@@ -7904,7 +8805,6 @@ var Indexer = class {
|
|
|
7904
8805
|
chunksProcessed: stats.indexedChunks,
|
|
7905
8806
|
totalChunks: pendingChunks.length
|
|
7906
8807
|
});
|
|
7907
|
-
this.releaseIndexingLock();
|
|
7908
8808
|
return stats;
|
|
7909
8809
|
}
|
|
7910
8810
|
async getQueryEmbedding(query, provider) {
|
|
@@ -7970,8 +8870,8 @@ var Indexer = class {
|
|
|
7970
8870
|
return intersection / union;
|
|
7971
8871
|
}
|
|
7972
8872
|
async search(query, limit, options) {
|
|
7973
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
7974
|
-
|
|
8873
|
+
const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8874
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
7975
8875
|
if (!compatibility.compatible) {
|
|
7976
8876
|
throw new Error(
|
|
7977
8877
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
|
|
@@ -7985,6 +8885,7 @@ var Indexer = class {
|
|
|
7985
8885
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
7986
8886
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
7987
8887
|
const fusionStrategy = this.config.search.fusionStrategy;
|
|
8888
|
+
const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
|
|
7988
8889
|
const rrfK = this.config.search.rrfK;
|
|
7989
8890
|
const rerankTopN = this.config.search.rerankTopN;
|
|
7990
8891
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
@@ -7993,7 +8894,7 @@ var Indexer = class {
|
|
|
7993
8894
|
this.logger.search("debug", "Starting search", {
|
|
7994
8895
|
query,
|
|
7995
8896
|
maxResults,
|
|
7996
|
-
hybridWeight,
|
|
8897
|
+
hybridWeight: effectiveHybridWeight,
|
|
7997
8898
|
fusionStrategy,
|
|
7998
8899
|
rrfK,
|
|
7999
8900
|
rerankTopN,
|
|
@@ -8007,7 +8908,7 @@ var Indexer = class {
|
|
|
8007
8908
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
8008
8909
|
const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
|
|
8009
8910
|
const keywordStartTime = import_perf_hooks.performance.now();
|
|
8010
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
8911
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
8011
8912
|
const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
|
|
8012
8913
|
let branchChunkIds = null;
|
|
8013
8914
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -8044,7 +8945,7 @@ var Indexer = class {
|
|
|
8044
8945
|
rrfK,
|
|
8045
8946
|
rerankTopN,
|
|
8046
8947
|
limit: maxResults,
|
|
8047
|
-
hybridWeight,
|
|
8948
|
+
hybridWeight: effectiveHybridWeight,
|
|
8048
8949
|
prioritizeSourcePaths: sourceIntent
|
|
8049
8950
|
});
|
|
8050
8951
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -8118,7 +9019,7 @@ var Indexer = class {
|
|
|
8118
9019
|
let contextEndLine = r.metadata.endLine;
|
|
8119
9020
|
if (!metadataOnly && this.config.search.includeContext) {
|
|
8120
9021
|
try {
|
|
8121
|
-
const fileContent = await
|
|
9022
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8122
9023
|
r.metadata.filePath,
|
|
8123
9024
|
"utf-8"
|
|
8124
9025
|
);
|
|
@@ -8144,8 +9045,7 @@ var Indexer = class {
|
|
|
8144
9045
|
})
|
|
8145
9046
|
);
|
|
8146
9047
|
}
|
|
8147
|
-
async keywordSearch(query, limit) {
|
|
8148
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9048
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
8149
9049
|
const scores = invertedIndex.search(query);
|
|
8150
9050
|
if (scores.size === 0) {
|
|
8151
9051
|
return [];
|
|
@@ -8163,24 +9063,54 @@ var Indexer = class {
|
|
|
8163
9063
|
return results.slice(0, limit);
|
|
8164
9064
|
}
|
|
8165
9065
|
async getStatus() {
|
|
8166
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9066
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8167
9067
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9068
|
+
const vectorCount = store.count();
|
|
9069
|
+
const statusReadIssues = [...readIssues];
|
|
9070
|
+
let startupWarning = "";
|
|
9071
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9072
|
+
try {
|
|
9073
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9074
|
+
} catch (error) {
|
|
9075
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9076
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9077
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9078
|
+
this.recordReadIssue("database", message, error);
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9083
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9084
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
8168
9085
|
return {
|
|
8169
|
-
indexed:
|
|
8170
|
-
vectorCount
|
|
9086
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9087
|
+
vectorCount,
|
|
8171
9088
|
provider: configuredProviderInfo.provider,
|
|
8172
9089
|
model: configuredProviderInfo.modelInfo.model,
|
|
8173
9090
|
indexPath: this.indexPath,
|
|
8174
9091
|
currentBranch: this.currentBranch,
|
|
8175
9092
|
baseBranch: this.baseBranch,
|
|
8176
|
-
compatibility
|
|
9093
|
+
compatibility,
|
|
8177
9094
|
failedBatchesCount,
|
|
8178
9095
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
8179
|
-
warning:
|
|
9096
|
+
warning: warning || void 0
|
|
8180
9097
|
};
|
|
8181
9098
|
}
|
|
9099
|
+
async forceIndex(onProgress) {
|
|
9100
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9101
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9102
|
+
await this.clearIndexUnlocked();
|
|
9103
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9104
|
+
});
|
|
9105
|
+
}
|
|
8182
9106
|
async clearIndex() {
|
|
8183
|
-
|
|
9107
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9108
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9109
|
+
await this.clearIndexUnlocked();
|
|
9110
|
+
});
|
|
9111
|
+
}
|
|
9112
|
+
async clearIndexUnlocked() {
|
|
9113
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8184
9114
|
if (this.config.scope === "global") {
|
|
8185
9115
|
store.load();
|
|
8186
9116
|
invertedIndex.load();
|
|
@@ -8207,7 +9137,7 @@ var Indexer = class {
|
|
|
8207
9137
|
store.clear();
|
|
8208
9138
|
store.save();
|
|
8209
9139
|
invertedIndex.clear();
|
|
8210
|
-
|
|
9140
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8211
9141
|
this.fileHashCache.clear();
|
|
8212
9142
|
this.saveFileHashCache();
|
|
8213
9143
|
database.clearAllIndexedData();
|
|
@@ -8231,14 +9161,7 @@ var Indexer = class {
|
|
|
8231
9161
|
this.indexCompatibility = compatibility;
|
|
8232
9162
|
return;
|
|
8233
9163
|
}
|
|
8234
|
-
|
|
8235
|
-
if (this.host !== "opencode") {
|
|
8236
|
-
localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
8237
|
-
}
|
|
8238
|
-
const isLocalProjectIndex = localProjectIndexPaths.some(
|
|
8239
|
-
(localPath) => path12.resolve(this.indexPath) === path12.resolve(localPath)
|
|
8240
|
-
);
|
|
8241
|
-
if (!isLocalProjectIndex) {
|
|
9164
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8242
9165
|
throw new Error(
|
|
8243
9166
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8244
9167
|
);
|
|
@@ -8246,7 +9169,7 @@ var Indexer = class {
|
|
|
8246
9169
|
store.clear();
|
|
8247
9170
|
store.save();
|
|
8248
9171
|
invertedIndex.clear();
|
|
8249
|
-
|
|
9172
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8250
9173
|
this.fileHashCache.clear();
|
|
8251
9174
|
this.saveFileHashCache();
|
|
8252
9175
|
database.clearAllIndexedData();
|
|
@@ -8264,7 +9187,13 @@ var Indexer = class {
|
|
|
8264
9187
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8265
9188
|
}
|
|
8266
9189
|
async healthCheck() {
|
|
8267
|
-
|
|
9190
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9191
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9192
|
+
return this.healthCheckUnlocked();
|
|
9193
|
+
});
|
|
9194
|
+
}
|
|
9195
|
+
async healthCheckUnlocked() {
|
|
9196
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8268
9197
|
this.logger.gc("info", "Starting health check");
|
|
8269
9198
|
const allMetadata = store.getAllMetadata();
|
|
8270
9199
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8277,7 +9206,7 @@ var Indexer = class {
|
|
|
8277
9206
|
const removedChunkKeys = [];
|
|
8278
9207
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8279
9208
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8280
|
-
if (!(0,
|
|
9209
|
+
if (!(0, import_fs8.existsSync)(filePath)) {
|
|
8281
9210
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8282
9211
|
for (const key of chunkKeys) {
|
|
8283
9212
|
removedChunkKeys.push(key);
|
|
@@ -8302,7 +9231,7 @@ var Indexer = class {
|
|
|
8302
9231
|
const removedCount = removedChunkKeys.length;
|
|
8303
9232
|
if (removedCount > 0) {
|
|
8304
9233
|
store.save();
|
|
8305
|
-
|
|
9234
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8306
9235
|
}
|
|
8307
9236
|
let gcOrphanEmbeddings;
|
|
8308
9237
|
let gcOrphanChunks;
|
|
@@ -8317,7 +9246,7 @@ var Indexer = class {
|
|
|
8317
9246
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8318
9247
|
throw error;
|
|
8319
9248
|
}
|
|
8320
|
-
await this.
|
|
9249
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8321
9250
|
return {
|
|
8322
9251
|
removed: 0,
|
|
8323
9252
|
filePaths: [],
|
|
@@ -8326,7 +9255,7 @@ var Indexer = class {
|
|
|
8326
9255
|
gcOrphanSymbols: 0,
|
|
8327
9256
|
gcOrphanCallEdges: 0,
|
|
8328
9257
|
resetCorruptedIndex: true,
|
|
8329
|
-
warning: this.getCorruptedIndexWarning(
|
|
9258
|
+
warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
|
|
8330
9259
|
};
|
|
8331
9260
|
}
|
|
8332
9261
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8339,7 +9268,13 @@ var Indexer = class {
|
|
|
8339
9268
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8340
9269
|
}
|
|
8341
9270
|
async retryFailedBatches() {
|
|
8342
|
-
|
|
9271
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9272
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9273
|
+
return this.retryFailedBatchesUnlocked();
|
|
9274
|
+
});
|
|
9275
|
+
}
|
|
9276
|
+
async retryFailedBatchesUnlocked() {
|
|
9277
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8343
9278
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8344
9279
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8345
9280
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8503,7 +9438,7 @@ var Indexer = class {
|
|
|
8503
9438
|
}
|
|
8504
9439
|
if (succeeded > 0) {
|
|
8505
9440
|
store.save();
|
|
8506
|
-
|
|
9441
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8507
9442
|
}
|
|
8508
9443
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8509
9444
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8531,15 +9466,16 @@ var Indexer = class {
|
|
|
8531
9466
|
}
|
|
8532
9467
|
}
|
|
8533
9468
|
async getDatabaseStats() {
|
|
8534
|
-
const { database } = await this.ensureInitialized();
|
|
9469
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9470
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8535
9471
|
return database.getStats();
|
|
8536
9472
|
}
|
|
8537
9473
|
getLogger() {
|
|
8538
9474
|
return this.logger;
|
|
8539
9475
|
}
|
|
8540
9476
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8541
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8542
|
-
|
|
9477
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9478
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8543
9479
|
if (!compatibility.compatible) {
|
|
8544
9480
|
throw new Error(
|
|
8545
9481
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8629,7 +9565,7 @@ var Indexer = class {
|
|
|
8629
9565
|
let content = "";
|
|
8630
9566
|
if (this.config.search.includeContext) {
|
|
8631
9567
|
try {
|
|
8632
|
-
const fileContent = await
|
|
9568
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8633
9569
|
r.metadata.filePath,
|
|
8634
9570
|
"utf-8"
|
|
8635
9571
|
);
|
|
@@ -8653,7 +9589,8 @@ var Indexer = class {
|
|
|
8653
9589
|
);
|
|
8654
9590
|
}
|
|
8655
9591
|
async getCallers(targetName, callTypeFilter) {
|
|
8656
|
-
const { database } = await this.ensureInitialized();
|
|
9592
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9593
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8657
9594
|
const seen = /* @__PURE__ */ new Set();
|
|
8658
9595
|
const results = [];
|
|
8659
9596
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8667,7 +9604,8 @@ var Indexer = class {
|
|
|
8667
9604
|
return results;
|
|
8668
9605
|
}
|
|
8669
9606
|
async getCallees(symbolId, callTypeFilter) {
|
|
8670
|
-
const { database } = await this.ensureInitialized();
|
|
9607
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9608
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8671
9609
|
const seen = /* @__PURE__ */ new Set();
|
|
8672
9610
|
const results = [];
|
|
8673
9611
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8681,43 +9619,50 @@ var Indexer = class {
|
|
|
8681
9619
|
return results;
|
|
8682
9620
|
}
|
|
8683
9621
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8684
|
-
const { database } = await this.ensureInitialized();
|
|
9622
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9623
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8685
9624
|
let shortest = [];
|
|
8686
9625
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8687
|
-
const
|
|
8688
|
-
if (
|
|
8689
|
-
shortest =
|
|
9626
|
+
const path17 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9627
|
+
if (path17.length > 0 && (shortest.length === 0 || path17.length < shortest.length)) {
|
|
9628
|
+
shortest = path17;
|
|
8690
9629
|
}
|
|
8691
9630
|
}
|
|
8692
9631
|
return shortest;
|
|
8693
9632
|
}
|
|
8694
9633
|
async getSymbolsForBranch(branch) {
|
|
8695
|
-
const { database } = await this.ensureInitialized();
|
|
9634
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9635
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8696
9636
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8697
9637
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8698
9638
|
}
|
|
8699
9639
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8700
|
-
const { database } = await this.ensureInitialized();
|
|
9640
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9641
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8701
9642
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8702
9643
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8703
9644
|
}
|
|
8704
9645
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8705
|
-
const { database } = await this.ensureInitialized();
|
|
9646
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9647
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8706
9648
|
const branch = this.getBranchCatalogKey();
|
|
8707
9649
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8708
9650
|
}
|
|
8709
9651
|
async detectCommunities(branch, symbolIds) {
|
|
8710
|
-
const { database } = await this.ensureInitialized();
|
|
9652
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9653
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8711
9654
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8712
9655
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8713
9656
|
}
|
|
8714
9657
|
async computeCentrality(branch) {
|
|
8715
|
-
const { database } = await this.ensureInitialized();
|
|
9658
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9659
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8716
9660
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8717
9661
|
return database.computeCentrality(resolvedBranch);
|
|
8718
9662
|
}
|
|
8719
9663
|
async getPrImpact(opts) {
|
|
8720
|
-
const { database } = await this.ensureInitialized();
|
|
9664
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9665
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8721
9666
|
const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
|
|
8722
9667
|
const changedFilesResult = await getChangedFiles({
|
|
8723
9668
|
pr: opts.pr,
|
|
@@ -8740,7 +9685,7 @@ var Indexer = class {
|
|
|
8740
9685
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8741
9686
|
);
|
|
8742
9687
|
}
|
|
8743
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9688
|
+
const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
|
|
8744
9689
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8745
9690
|
const directIds = directSymbols.map((s) => s.id);
|
|
8746
9691
|
const direction = opts.direction ?? "both";
|
|
@@ -8823,7 +9768,7 @@ var Indexer = class {
|
|
|
8823
9768
|
projectRoot: this.projectRoot,
|
|
8824
9769
|
baseBranch: this.baseBranch
|
|
8825
9770
|
});
|
|
8826
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9771
|
+
const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
|
|
8827
9772
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8828
9773
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8829
9774
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8873,7 +9818,8 @@ var Indexer = class {
|
|
|
8873
9818
|
};
|
|
8874
9819
|
}
|
|
8875
9820
|
async getVisualizationData(options) {
|
|
8876
|
-
const { database, store } = await this.ensureInitialized();
|
|
9821
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9822
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8877
9823
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8878
9824
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8879
9825
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8886,12 +9832,12 @@ var Indexer = class {
|
|
|
8886
9832
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8887
9833
|
}
|
|
8888
9834
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8889
|
-
const absoluteDirectoryFilter = directory ?
|
|
9835
|
+
const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
|
|
8890
9836
|
for (const filePath of filePaths) {
|
|
8891
9837
|
if (directory) {
|
|
8892
|
-
const absoluteFilePath =
|
|
9838
|
+
const absoluteFilePath = path13.resolve(filePath);
|
|
8893
9839
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8894
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9840
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
|
|
8895
9841
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8896
9842
|
continue;
|
|
8897
9843
|
}
|
|
@@ -8913,53 +9859,64 @@ var Indexer = class {
|
|
|
8913
9859
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
8914
9860
|
}
|
|
8915
9861
|
async close() {
|
|
8916
|
-
|
|
9862
|
+
this.database?.close();
|
|
9863
|
+
for (const database of this.retiredDatabases) {
|
|
9864
|
+
database.close();
|
|
9865
|
+
}
|
|
9866
|
+
this.retiredDatabases = [];
|
|
8917
9867
|
this.database = null;
|
|
8918
9868
|
this.store = null;
|
|
8919
9869
|
this.invertedIndex = null;
|
|
8920
9870
|
this.provider = null;
|
|
8921
9871
|
this.reranker = null;
|
|
9872
|
+
this.configuredProviderInfo = null;
|
|
9873
|
+
this.indexCompatibility = null;
|
|
9874
|
+
this.initializationMode = "none";
|
|
9875
|
+
this.readIssues = [];
|
|
9876
|
+
this.readerArtifactFingerprint = null;
|
|
9877
|
+
this.writerArtifactFingerprint = null;
|
|
9878
|
+
this.readerArtifactRetryAfter.clear();
|
|
8922
9879
|
}
|
|
8923
9880
|
};
|
|
8924
9881
|
|
|
8925
9882
|
// src/tools/knowledge-base-paths.ts
|
|
8926
|
-
var
|
|
9883
|
+
var path14 = __toESM(require("path"), 1);
|
|
8927
9884
|
function resolveConfigPathValue(value, baseDir) {
|
|
8928
9885
|
const trimmed = value.trim();
|
|
8929
9886
|
if (!trimmed) {
|
|
8930
9887
|
return trimmed;
|
|
8931
9888
|
}
|
|
8932
|
-
const absolutePath =
|
|
8933
|
-
return
|
|
9889
|
+
const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
|
|
9890
|
+
return path14.normalize(absolutePath);
|
|
8934
9891
|
}
|
|
8935
9892
|
function serializeConfigPathValue(value, baseDir) {
|
|
8936
9893
|
const trimmed = value.trim();
|
|
8937
9894
|
if (!trimmed) {
|
|
8938
9895
|
return trimmed;
|
|
8939
9896
|
}
|
|
8940
|
-
if (!
|
|
8941
|
-
return normalizePathSeparators(
|
|
9897
|
+
if (!path14.isAbsolute(trimmed)) {
|
|
9898
|
+
return normalizePathSeparators(path14.normalize(trimmed));
|
|
8942
9899
|
}
|
|
8943
|
-
const relativePath =
|
|
8944
|
-
if (!relativePath || !relativePath.startsWith("..") && !
|
|
8945
|
-
return normalizePathSeparators(
|
|
9900
|
+
const relativePath = path14.relative(baseDir, trimmed);
|
|
9901
|
+
if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
|
|
9902
|
+
return normalizePathSeparators(path14.normalize(relativePath || "."));
|
|
8946
9903
|
}
|
|
8947
|
-
return
|
|
9904
|
+
return path14.normalize(trimmed);
|
|
8948
9905
|
}
|
|
8949
9906
|
function resolveKnowledgeBasePath(value, projectRoot3) {
|
|
8950
|
-
return
|
|
9907
|
+
return path14.isAbsolute(value) ? value : path14.resolve(projectRoot3, value);
|
|
8951
9908
|
}
|
|
8952
9909
|
function normalizeKnowledgeBasePath2(value, projectRoot3) {
|
|
8953
|
-
return
|
|
9910
|
+
return path14.normalize(resolveKnowledgeBasePath(value, projectRoot3));
|
|
8954
9911
|
}
|
|
8955
9912
|
function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
|
|
8956
|
-
const normalizedInput =
|
|
9913
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
8957
9914
|
return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
|
|
8958
9915
|
}
|
|
8959
9916
|
function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
|
|
8960
|
-
const normalizedInput =
|
|
9917
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
8961
9918
|
return knowledgeBases.findIndex(
|
|
8962
|
-
(kb) =>
|
|
9919
|
+
(kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
|
|
8963
9920
|
);
|
|
8964
9921
|
}
|
|
8965
9922
|
|
|
@@ -9059,6 +10016,10 @@ function formatStatus(status) {
|
|
|
9059
10016
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
9060
10017
|
}
|
|
9061
10018
|
}
|
|
10019
|
+
if (status.warning) {
|
|
10020
|
+
lines.push("");
|
|
10021
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
10022
|
+
}
|
|
9062
10023
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
9063
10024
|
lines.push("");
|
|
9064
10025
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -9153,16 +10114,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
|
9153
10114
|
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
9154
10115
|
}).join("\n");
|
|
9155
10116
|
}
|
|
9156
|
-
function formatCallGraphPath(from, to,
|
|
9157
|
-
if (
|
|
10117
|
+
function formatCallGraphPath(from, to, path17) {
|
|
10118
|
+
if (path17.length === 0) {
|
|
9158
10119
|
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
9159
10120
|
}
|
|
9160
|
-
const formatted =
|
|
10121
|
+
const formatted = path17.map((hop, index) => {
|
|
9161
10122
|
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
9162
10123
|
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
9163
10124
|
return `${prefix} ${hop.symbolName}${location}`;
|
|
9164
10125
|
});
|
|
9165
|
-
return `Path (${
|
|
10126
|
+
return `Path (${path17.length} hops):
|
|
9166
10127
|
${formatted.join("\n")}`;
|
|
9167
10128
|
}
|
|
9168
10129
|
function formatResultHeader(result, index) {
|
|
@@ -9202,8 +10163,8 @@ ${truncateContent(r.content)}
|
|
|
9202
10163
|
}
|
|
9203
10164
|
|
|
9204
10165
|
// src/tools/config-state.ts
|
|
9205
|
-
var
|
|
9206
|
-
var
|
|
10166
|
+
var import_fs9 = require("fs");
|
|
10167
|
+
var path15 = __toESM(require("path"), 1);
|
|
9207
10168
|
function normalizeKnowledgeBasePaths(config, projectRoot3) {
|
|
9208
10169
|
const normalized = { ...config };
|
|
9209
10170
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
@@ -9230,10 +10191,10 @@ function loadEditableConfig(projectRoot3, host = "opencode") {
|
|
|
9230
10191
|
}
|
|
9231
10192
|
function saveConfig(projectRoot3, config, host = "opencode") {
|
|
9232
10193
|
const configPath = getConfigPath(projectRoot3, host);
|
|
9233
|
-
const configDir =
|
|
9234
|
-
const configBaseDir =
|
|
9235
|
-
if (!(0,
|
|
9236
|
-
(0,
|
|
10194
|
+
const configDir = path15.dirname(configPath);
|
|
10195
|
+
const configBaseDir = path15.dirname(configDir);
|
|
10196
|
+
if (!(0, import_fs9.existsSync)(configDir)) {
|
|
10197
|
+
(0, import_fs9.mkdirSync)(configDir, { recursive: true });
|
|
9237
10198
|
}
|
|
9238
10199
|
const serializableConfig = { ...config };
|
|
9239
10200
|
if (Array.isArray(serializableConfig.knowledgeBases)) {
|
|
@@ -9241,13 +10202,31 @@ function saveConfig(projectRoot3, config, host = "opencode") {
|
|
|
9241
10202
|
(kb) => serializeConfigPathValue(kb, configBaseDir)
|
|
9242
10203
|
);
|
|
9243
10204
|
}
|
|
9244
|
-
(0,
|
|
10205
|
+
(0, import_fs9.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
|
|
9245
10206
|
}
|
|
9246
10207
|
|
|
9247
10208
|
// src/tools/operations.ts
|
|
9248
10209
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
9249
10210
|
var configCache = /* @__PURE__ */ new Map();
|
|
9250
10211
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
10212
|
+
function getIndexBusyResult(error) {
|
|
10213
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
10214
|
+
const owner = error.owner;
|
|
10215
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
10216
|
+
if (error.reason === "legacy-lock") {
|
|
10217
|
+
return {
|
|
10218
|
+
kind: "busy",
|
|
10219
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
10220
|
+
};
|
|
10221
|
+
}
|
|
10222
|
+
if (error.reason === "unknown-owner") {
|
|
10223
|
+
return {
|
|
10224
|
+
kind: "busy",
|
|
10225
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
10226
|
+
};
|
|
10227
|
+
}
|
|
10228
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
10229
|
+
}
|
|
9251
10230
|
function getProjectRoot(projectRoot3, host) {
|
|
9252
10231
|
if (projectRoot3) {
|
|
9253
10232
|
return projectRoot3;
|
|
@@ -9285,8 +10264,8 @@ function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = pa
|
|
|
9285
10264
|
}
|
|
9286
10265
|
function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
|
|
9287
10266
|
const root = getProjectRoot(projectRoot3, host);
|
|
9288
|
-
const localIndexPath =
|
|
9289
|
-
if ((0,
|
|
10267
|
+
const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
|
|
10268
|
+
if ((0, import_fs10.existsSync)(localIndexPath)) {
|
|
9290
10269
|
return false;
|
|
9291
10270
|
}
|
|
9292
10271
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
@@ -9342,35 +10321,46 @@ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
|
|
|
9342
10321
|
async function runIndexCodebase(projectRoot3, host, args, onProgress) {
|
|
9343
10322
|
const root = getProjectRoot(projectRoot3, host);
|
|
9344
10323
|
const key = getIndexerCacheKey(root, host);
|
|
9345
|
-
const cachedConfig = configCache.get(key);
|
|
9346
10324
|
let indexer = getIndexerForProject(root, host);
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
chunksProcessed: progress.chunksProcessed,
|
|
9367
|
-
totalChunks: progress.totalChunks,
|
|
9368
|
-
percentage: calculatePercentage(progress)
|
|
10325
|
+
const runtimeConfig = configCache.get(key);
|
|
10326
|
+
try {
|
|
10327
|
+
if (args.estimateOnly) {
|
|
10328
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
10329
|
+
}
|
|
10330
|
+
const runIndex = async (target) => {
|
|
10331
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
10332
|
+
return operation((progress) => {
|
|
10333
|
+
if (onProgress) {
|
|
10334
|
+
return onProgress(formatProgressTitle(progress), {
|
|
10335
|
+
phase: progress.phase,
|
|
10336
|
+
filesProcessed: progress.filesProcessed,
|
|
10337
|
+
totalFiles: progress.totalFiles,
|
|
10338
|
+
chunksProcessed: progress.chunksProcessed,
|
|
10339
|
+
totalChunks: progress.totalChunks,
|
|
10340
|
+
percentage: calculatePercentage(progress)
|
|
10341
|
+
});
|
|
10342
|
+
}
|
|
10343
|
+
return Promise.resolve();
|
|
9369
10344
|
});
|
|
10345
|
+
};
|
|
10346
|
+
let stats;
|
|
10347
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
10348
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
10349
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
10350
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
10351
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
10352
|
+
indexer = getIndexerForProject(root, host);
|
|
10353
|
+
return runIndex(indexer);
|
|
10354
|
+
}, { completeRecoveries: false });
|
|
10355
|
+
} else {
|
|
10356
|
+
stats = await runIndex(indexer);
|
|
9370
10357
|
}
|
|
9371
|
-
return
|
|
9372
|
-
})
|
|
9373
|
-
|
|
10358
|
+
return { kind: "stats", stats };
|
|
10359
|
+
} catch (error) {
|
|
10360
|
+
const busyResult = getIndexBusyResult(error);
|
|
10361
|
+
if (!busyResult) throw error;
|
|
10362
|
+
return busyResult;
|
|
10363
|
+
}
|
|
9374
10364
|
}
|
|
9375
10365
|
async function getIndexStatus(projectRoot3, host) {
|
|
9376
10366
|
const indexer = getIndexerForProject(projectRoot3, host);
|
|
@@ -9380,6 +10370,15 @@ async function getIndexHealthCheck(projectRoot3, host) {
|
|
|
9380
10370
|
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9381
10371
|
return indexer.healthCheck();
|
|
9382
10372
|
}
|
|
10373
|
+
async function runIndexHealthCheck(projectRoot3, host) {
|
|
10374
|
+
try {
|
|
10375
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot3, host) };
|
|
10376
|
+
} catch (error) {
|
|
10377
|
+
const busyResult = getIndexBusyResult(error);
|
|
10378
|
+
if (!busyResult) throw error;
|
|
10379
|
+
return busyResult;
|
|
10380
|
+
}
|
|
10381
|
+
}
|
|
9383
10382
|
async function getPrImpact(projectRoot3, host, params) {
|
|
9384
10383
|
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9385
10384
|
return indexer.getPrImpact({
|
|
@@ -9446,15 +10445,15 @@ async function getIndexLogs(projectRoot3, host, args) {
|
|
|
9446
10445
|
function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
|
|
9447
10446
|
const root = getProjectRoot(projectRoot3, host);
|
|
9448
10447
|
const inputPath = knowledgeBasePath.trim();
|
|
9449
|
-
const normalizedPath =
|
|
9450
|
-
|
|
10448
|
+
const normalizedPath = path16.resolve(
|
|
10449
|
+
path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
|
|
9451
10450
|
);
|
|
9452
|
-
if (!(0,
|
|
10451
|
+
if (!(0, import_fs10.existsSync)(normalizedPath)) {
|
|
9453
10452
|
return `Error: Directory does not exist: ${normalizedPath}`;
|
|
9454
10453
|
}
|
|
9455
10454
|
let realPath;
|
|
9456
10455
|
try {
|
|
9457
|
-
realPath = (0,
|
|
10456
|
+
realPath = (0, import_fs10.realpathSync)(normalizedPath);
|
|
9458
10457
|
} catch {
|
|
9459
10458
|
return `Error: Cannot resolve path: ${normalizedPath}`;
|
|
9460
10459
|
}
|
|
@@ -9483,13 +10482,13 @@ function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
|
|
|
9483
10482
|
}
|
|
9484
10483
|
}
|
|
9485
10484
|
for (const dotDir of sensitiveDotDirs) {
|
|
9486
|
-
const sensitiveDir =
|
|
10485
|
+
const sensitiveDir = path16.join(homeDir, dotDir);
|
|
9487
10486
|
if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
|
|
9488
10487
|
return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
|
|
9489
10488
|
}
|
|
9490
10489
|
}
|
|
9491
10490
|
try {
|
|
9492
|
-
const stat = (0,
|
|
10491
|
+
const stat = (0, import_fs10.statSync)(normalizedPath);
|
|
9493
10492
|
if (!stat.isDirectory()) {
|
|
9494
10493
|
return `Error: Path is not a directory: ${normalizedPath}`;
|
|
9495
10494
|
}
|
|
@@ -9529,7 +10528,7 @@ function listKnowledgeBases(projectRoot3, host) {
|
|
|
9529
10528
|
for (let i = 0; i < knowledgeBases.length; i++) {
|
|
9530
10529
|
const kb = knowledgeBases[i];
|
|
9531
10530
|
const resolvedPath = resolveKnowledgeBasePath(kb, root);
|
|
9532
|
-
const exists = (0,
|
|
10531
|
+
const exists = (0, import_fs10.existsSync)(resolvedPath);
|
|
9533
10532
|
result += `[${i + 1}] ${kb}
|
|
9534
10533
|
`;
|
|
9535
10534
|
result += ` Resolved: ${resolvedPath}
|
|
@@ -9538,7 +10537,7 @@ function listKnowledgeBases(projectRoot3, host) {
|
|
|
9538
10537
|
`;
|
|
9539
10538
|
if (exists) {
|
|
9540
10539
|
try {
|
|
9541
|
-
const stat = (0,
|
|
10540
|
+
const stat = (0, import_fs10.statSync)(resolvedPath);
|
|
9542
10541
|
result += ` Type: ${stat.isDirectory() ? "Directory" : "File"}
|
|
9543
10542
|
`;
|
|
9544
10543
|
} catch {
|
|
@@ -9546,7 +10545,7 @@ function listKnowledgeBases(projectRoot3, host) {
|
|
|
9546
10545
|
}
|
|
9547
10546
|
result += "\n";
|
|
9548
10547
|
}
|
|
9549
|
-
const hasHostConfig = (0,
|
|
10548
|
+
const hasHostConfig = (0, import_fs10.existsSync)(path16.join(root, getHostProjectConfigRelativePath(host)));
|
|
9550
10549
|
if (hasHostConfig) {
|
|
9551
10550
|
result += `
|
|
9552
10551
|
Config sources: 1 file(s).`;
|
|
@@ -9734,7 +10733,9 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9734
10733
|
}),
|
|
9735
10734
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9736
10735
|
const result = await runIndexCodebase(projectRoot2(ctx), HOST2, params);
|
|
9737
|
-
|
|
10736
|
+
if (result.kind === "estimate") return text2(formatCostEstimate(result.estimate), result.estimate);
|
|
10737
|
+
if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
|
|
10738
|
+
return text2(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
|
|
9738
10739
|
}
|
|
9739
10740
|
});
|
|
9740
10741
|
pi.registerTool({
|
|
@@ -9753,8 +10754,9 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9753
10754
|
description: "Garbage collect orphaned embeddings/chunks and report health.",
|
|
9754
10755
|
parameters: import_typebox2.Type.Object({}),
|
|
9755
10756
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
9756
|
-
const result = await
|
|
9757
|
-
return text2(
|
|
10757
|
+
const result = await runIndexHealthCheck(projectRoot2(ctx), HOST2);
|
|
10758
|
+
if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
|
|
10759
|
+
return text2(formatHealthCheck(result.health), result.health);
|
|
9758
10760
|
}
|
|
9759
10761
|
});
|
|
9760
10762
|
pi.registerTool({
|