opencode-codebase-index 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/README.md +73 -12
- package/dist/cli.cjs +1782 -607
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1781 -596
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1610 -533
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1606 -519
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +1396 -311
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +1395 -300
- package/dist/pi-extension.js.map +1 -1
- package/hooks/hooks.json +2 -2
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +11 -8
- package/skills/codebase-search/SKILL.md +9 -7
package/dist/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,
|
|
@@ -8001,13 +8902,22 @@ var Indexer = class {
|
|
|
8001
8902
|
});
|
|
8002
8903
|
const embeddingStartTime = import_perf_hooks.performance.now();
|
|
8003
8904
|
const embeddingQuery = stripFilePathHint(query);
|
|
8004
|
-
|
|
8905
|
+
let embedding;
|
|
8906
|
+
try {
|
|
8907
|
+
embedding = await this.getQueryEmbedding(embeddingQuery, provider);
|
|
8908
|
+
} catch (error) {
|
|
8909
|
+
this.logger.warn("Query embedding failed; falling back to keyword-only search", {
|
|
8910
|
+
query,
|
|
8911
|
+
error: getErrorMessage2(error),
|
|
8912
|
+
action: "Check the embedding provider configuration and retry search after restoring provider health."
|
|
8913
|
+
});
|
|
8914
|
+
}
|
|
8005
8915
|
const embeddingMs = import_perf_hooks.performance.now() - embeddingStartTime;
|
|
8006
8916
|
const vectorStartTime = import_perf_hooks.performance.now();
|
|
8007
|
-
const semanticResults = store.search(embedding, maxResults * 4);
|
|
8917
|
+
const semanticResults = embedding ? store.search(embedding, maxResults * 4) : [];
|
|
8008
8918
|
const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
|
|
8009
8919
|
const keywordStartTime = import_perf_hooks.performance.now();
|
|
8010
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
8920
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
8011
8921
|
const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
|
|
8012
8922
|
let branchChunkIds = null;
|
|
8013
8923
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -8039,12 +8949,13 @@ var Indexer = class {
|
|
|
8039
8949
|
});
|
|
8040
8950
|
}
|
|
8041
8951
|
const fusionStartTime = import_perf_hooks.performance.now();
|
|
8952
|
+
const rankingHybridWeight = embedding === void 0 && fusionStrategy === "weighted" ? 1 : effectiveHybridWeight;
|
|
8042
8953
|
const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
|
|
8043
8954
|
fusionStrategy,
|
|
8044
8955
|
rrfK,
|
|
8045
8956
|
rerankTopN,
|
|
8046
8957
|
limit: maxResults,
|
|
8047
|
-
hybridWeight,
|
|
8958
|
+
hybridWeight: rankingHybridWeight,
|
|
8048
8959
|
prioritizeSourcePaths: sourceIntent
|
|
8049
8960
|
});
|
|
8050
8961
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -8118,7 +9029,7 @@ var Indexer = class {
|
|
|
8118
9029
|
let contextEndLine = r.metadata.endLine;
|
|
8119
9030
|
if (!metadataOnly && this.config.search.includeContext) {
|
|
8120
9031
|
try {
|
|
8121
|
-
const fileContent = await
|
|
9032
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8122
9033
|
r.metadata.filePath,
|
|
8123
9034
|
"utf-8"
|
|
8124
9035
|
);
|
|
@@ -8144,8 +9055,7 @@ var Indexer = class {
|
|
|
8144
9055
|
})
|
|
8145
9056
|
);
|
|
8146
9057
|
}
|
|
8147
|
-
async keywordSearch(query, limit) {
|
|
8148
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9058
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
8149
9059
|
const scores = invertedIndex.search(query);
|
|
8150
9060
|
if (scores.size === 0) {
|
|
8151
9061
|
return [];
|
|
@@ -8163,24 +9073,54 @@ var Indexer = class {
|
|
|
8163
9073
|
return results.slice(0, limit);
|
|
8164
9074
|
}
|
|
8165
9075
|
async getStatus() {
|
|
8166
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9076
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8167
9077
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9078
|
+
const vectorCount = store.count();
|
|
9079
|
+
const statusReadIssues = [...readIssues];
|
|
9080
|
+
let startupWarning = "";
|
|
9081
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9082
|
+
try {
|
|
9083
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9084
|
+
} catch (error) {
|
|
9085
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9086
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9087
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9088
|
+
this.recordReadIssue("database", message, error);
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
}
|
|
9092
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9093
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9094
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
8168
9095
|
return {
|
|
8169
|
-
indexed:
|
|
8170
|
-
vectorCount
|
|
9096
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9097
|
+
vectorCount,
|
|
8171
9098
|
provider: configuredProviderInfo.provider,
|
|
8172
9099
|
model: configuredProviderInfo.modelInfo.model,
|
|
8173
9100
|
indexPath: this.indexPath,
|
|
8174
9101
|
currentBranch: this.currentBranch,
|
|
8175
9102
|
baseBranch: this.baseBranch,
|
|
8176
|
-
compatibility
|
|
9103
|
+
compatibility,
|
|
8177
9104
|
failedBatchesCount,
|
|
8178
9105
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
8179
|
-
warning:
|
|
9106
|
+
warning: warning || void 0
|
|
8180
9107
|
};
|
|
8181
9108
|
}
|
|
9109
|
+
async forceIndex(onProgress) {
|
|
9110
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9111
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9112
|
+
await this.clearIndexUnlocked();
|
|
9113
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9114
|
+
});
|
|
9115
|
+
}
|
|
8182
9116
|
async clearIndex() {
|
|
8183
|
-
|
|
9117
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9118
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9119
|
+
await this.clearIndexUnlocked();
|
|
9120
|
+
});
|
|
9121
|
+
}
|
|
9122
|
+
async clearIndexUnlocked() {
|
|
9123
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8184
9124
|
if (this.config.scope === "global") {
|
|
8185
9125
|
store.load();
|
|
8186
9126
|
invertedIndex.load();
|
|
@@ -8207,7 +9147,7 @@ var Indexer = class {
|
|
|
8207
9147
|
store.clear();
|
|
8208
9148
|
store.save();
|
|
8209
9149
|
invertedIndex.clear();
|
|
8210
|
-
|
|
9150
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8211
9151
|
this.fileHashCache.clear();
|
|
8212
9152
|
this.saveFileHashCache();
|
|
8213
9153
|
database.clearAllIndexedData();
|
|
@@ -8231,14 +9171,7 @@ var Indexer = class {
|
|
|
8231
9171
|
this.indexCompatibility = compatibility;
|
|
8232
9172
|
return;
|
|
8233
9173
|
}
|
|
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) {
|
|
9174
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8242
9175
|
throw new Error(
|
|
8243
9176
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8244
9177
|
);
|
|
@@ -8246,7 +9179,7 @@ var Indexer = class {
|
|
|
8246
9179
|
store.clear();
|
|
8247
9180
|
store.save();
|
|
8248
9181
|
invertedIndex.clear();
|
|
8249
|
-
|
|
9182
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8250
9183
|
this.fileHashCache.clear();
|
|
8251
9184
|
this.saveFileHashCache();
|
|
8252
9185
|
database.clearAllIndexedData();
|
|
@@ -8264,7 +9197,13 @@ var Indexer = class {
|
|
|
8264
9197
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8265
9198
|
}
|
|
8266
9199
|
async healthCheck() {
|
|
8267
|
-
|
|
9200
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9201
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9202
|
+
return this.healthCheckUnlocked();
|
|
9203
|
+
});
|
|
9204
|
+
}
|
|
9205
|
+
async healthCheckUnlocked() {
|
|
9206
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8268
9207
|
this.logger.gc("info", "Starting health check");
|
|
8269
9208
|
const allMetadata = store.getAllMetadata();
|
|
8270
9209
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8277,7 +9216,7 @@ var Indexer = class {
|
|
|
8277
9216
|
const removedChunkKeys = [];
|
|
8278
9217
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8279
9218
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8280
|
-
if (!(0,
|
|
9219
|
+
if (!(0, import_fs8.existsSync)(filePath)) {
|
|
8281
9220
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8282
9221
|
for (const key of chunkKeys) {
|
|
8283
9222
|
removedChunkKeys.push(key);
|
|
@@ -8302,7 +9241,7 @@ var Indexer = class {
|
|
|
8302
9241
|
const removedCount = removedChunkKeys.length;
|
|
8303
9242
|
if (removedCount > 0) {
|
|
8304
9243
|
store.save();
|
|
8305
|
-
|
|
9244
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8306
9245
|
}
|
|
8307
9246
|
let gcOrphanEmbeddings;
|
|
8308
9247
|
let gcOrphanChunks;
|
|
@@ -8317,7 +9256,7 @@ var Indexer = class {
|
|
|
8317
9256
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8318
9257
|
throw error;
|
|
8319
9258
|
}
|
|
8320
|
-
await this.
|
|
9259
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8321
9260
|
return {
|
|
8322
9261
|
removed: 0,
|
|
8323
9262
|
filePaths: [],
|
|
@@ -8326,7 +9265,7 @@ var Indexer = class {
|
|
|
8326
9265
|
gcOrphanSymbols: 0,
|
|
8327
9266
|
gcOrphanCallEdges: 0,
|
|
8328
9267
|
resetCorruptedIndex: true,
|
|
8329
|
-
warning: this.getCorruptedIndexWarning(
|
|
9268
|
+
warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
|
|
8330
9269
|
};
|
|
8331
9270
|
}
|
|
8332
9271
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8339,7 +9278,13 @@ var Indexer = class {
|
|
|
8339
9278
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8340
9279
|
}
|
|
8341
9280
|
async retryFailedBatches() {
|
|
8342
|
-
|
|
9281
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9282
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9283
|
+
return this.retryFailedBatchesUnlocked();
|
|
9284
|
+
});
|
|
9285
|
+
}
|
|
9286
|
+
async retryFailedBatchesUnlocked() {
|
|
9287
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8343
9288
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8344
9289
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8345
9290
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8503,7 +9448,7 @@ var Indexer = class {
|
|
|
8503
9448
|
}
|
|
8504
9449
|
if (succeeded > 0) {
|
|
8505
9450
|
store.save();
|
|
8506
|
-
|
|
9451
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8507
9452
|
}
|
|
8508
9453
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8509
9454
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8531,15 +9476,16 @@ var Indexer = class {
|
|
|
8531
9476
|
}
|
|
8532
9477
|
}
|
|
8533
9478
|
async getDatabaseStats() {
|
|
8534
|
-
const { database } = await this.ensureInitialized();
|
|
9479
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9480
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8535
9481
|
return database.getStats();
|
|
8536
9482
|
}
|
|
8537
9483
|
getLogger() {
|
|
8538
9484
|
return this.logger;
|
|
8539
9485
|
}
|
|
8540
9486
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8541
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8542
|
-
|
|
9487
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9488
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8543
9489
|
if (!compatibility.compatible) {
|
|
8544
9490
|
throw new Error(
|
|
8545
9491
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8629,7 +9575,7 @@ var Indexer = class {
|
|
|
8629
9575
|
let content = "";
|
|
8630
9576
|
if (this.config.search.includeContext) {
|
|
8631
9577
|
try {
|
|
8632
|
-
const fileContent = await
|
|
9578
|
+
const fileContent = await import_fs8.promises.readFile(
|
|
8633
9579
|
r.metadata.filePath,
|
|
8634
9580
|
"utf-8"
|
|
8635
9581
|
);
|
|
@@ -8653,7 +9599,8 @@ var Indexer = class {
|
|
|
8653
9599
|
);
|
|
8654
9600
|
}
|
|
8655
9601
|
async getCallers(targetName, callTypeFilter) {
|
|
8656
|
-
const { database } = await this.ensureInitialized();
|
|
9602
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9603
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8657
9604
|
const seen = /* @__PURE__ */ new Set();
|
|
8658
9605
|
const results = [];
|
|
8659
9606
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8667,7 +9614,8 @@ var Indexer = class {
|
|
|
8667
9614
|
return results;
|
|
8668
9615
|
}
|
|
8669
9616
|
async getCallees(symbolId, callTypeFilter) {
|
|
8670
|
-
const { database } = await this.ensureInitialized();
|
|
9617
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9618
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8671
9619
|
const seen = /* @__PURE__ */ new Set();
|
|
8672
9620
|
const results = [];
|
|
8673
9621
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8681,43 +9629,50 @@ var Indexer = class {
|
|
|
8681
9629
|
return results;
|
|
8682
9630
|
}
|
|
8683
9631
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8684
|
-
const { database } = await this.ensureInitialized();
|
|
9632
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9633
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8685
9634
|
let shortest = [];
|
|
8686
9635
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8687
|
-
const
|
|
8688
|
-
if (
|
|
8689
|
-
shortest =
|
|
9636
|
+
const path17 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9637
|
+
if (path17.length > 0 && (shortest.length === 0 || path17.length < shortest.length)) {
|
|
9638
|
+
shortest = path17;
|
|
8690
9639
|
}
|
|
8691
9640
|
}
|
|
8692
9641
|
return shortest;
|
|
8693
9642
|
}
|
|
8694
9643
|
async getSymbolsForBranch(branch) {
|
|
8695
|
-
const { database } = await this.ensureInitialized();
|
|
9644
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9645
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8696
9646
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8697
9647
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8698
9648
|
}
|
|
8699
9649
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8700
|
-
const { database } = await this.ensureInitialized();
|
|
9650
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9651
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8701
9652
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8702
9653
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8703
9654
|
}
|
|
8704
9655
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8705
|
-
const { database } = await this.ensureInitialized();
|
|
9656
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9657
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8706
9658
|
const branch = this.getBranchCatalogKey();
|
|
8707
9659
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8708
9660
|
}
|
|
8709
9661
|
async detectCommunities(branch, symbolIds) {
|
|
8710
|
-
const { database } = await this.ensureInitialized();
|
|
9662
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9663
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8711
9664
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8712
9665
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8713
9666
|
}
|
|
8714
9667
|
async computeCentrality(branch) {
|
|
8715
|
-
const { database } = await this.ensureInitialized();
|
|
9668
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9669
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8716
9670
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8717
9671
|
return database.computeCentrality(resolvedBranch);
|
|
8718
9672
|
}
|
|
8719
9673
|
async getPrImpact(opts) {
|
|
8720
|
-
const { database } = await this.ensureInitialized();
|
|
9674
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9675
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8721
9676
|
const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
|
|
8722
9677
|
const changedFilesResult = await getChangedFiles({
|
|
8723
9678
|
pr: opts.pr,
|
|
@@ -8740,7 +9695,7 @@ var Indexer = class {
|
|
|
8740
9695
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8741
9696
|
);
|
|
8742
9697
|
}
|
|
8743
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9698
|
+
const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
|
|
8744
9699
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8745
9700
|
const directIds = directSymbols.map((s) => s.id);
|
|
8746
9701
|
const direction = opts.direction ?? "both";
|
|
@@ -8823,7 +9778,7 @@ var Indexer = class {
|
|
|
8823
9778
|
projectRoot: this.projectRoot,
|
|
8824
9779
|
baseBranch: this.baseBranch
|
|
8825
9780
|
});
|
|
8826
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9781
|
+
const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
|
|
8827
9782
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8828
9783
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8829
9784
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8873,7 +9828,8 @@ var Indexer = class {
|
|
|
8873
9828
|
};
|
|
8874
9829
|
}
|
|
8875
9830
|
async getVisualizationData(options) {
|
|
8876
|
-
const { database, store } = await this.ensureInitialized();
|
|
9831
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9832
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8877
9833
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8878
9834
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8879
9835
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8886,12 +9842,12 @@ var Indexer = class {
|
|
|
8886
9842
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8887
9843
|
}
|
|
8888
9844
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8889
|
-
const absoluteDirectoryFilter = directory ?
|
|
9845
|
+
const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
|
|
8890
9846
|
for (const filePath of filePaths) {
|
|
8891
9847
|
if (directory) {
|
|
8892
|
-
const absoluteFilePath =
|
|
9848
|
+
const absoluteFilePath = path13.resolve(filePath);
|
|
8893
9849
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8894
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9850
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
|
|
8895
9851
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8896
9852
|
continue;
|
|
8897
9853
|
}
|
|
@@ -8913,53 +9869,64 @@ var Indexer = class {
|
|
|
8913
9869
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
8914
9870
|
}
|
|
8915
9871
|
async close() {
|
|
8916
|
-
|
|
9872
|
+
this.database?.close();
|
|
9873
|
+
for (const database of this.retiredDatabases) {
|
|
9874
|
+
database.close();
|
|
9875
|
+
}
|
|
9876
|
+
this.retiredDatabases = [];
|
|
8917
9877
|
this.database = null;
|
|
8918
9878
|
this.store = null;
|
|
8919
9879
|
this.invertedIndex = null;
|
|
8920
9880
|
this.provider = null;
|
|
8921
9881
|
this.reranker = null;
|
|
9882
|
+
this.configuredProviderInfo = null;
|
|
9883
|
+
this.indexCompatibility = null;
|
|
9884
|
+
this.initializationMode = "none";
|
|
9885
|
+
this.readIssues = [];
|
|
9886
|
+
this.readerArtifactFingerprint = null;
|
|
9887
|
+
this.writerArtifactFingerprint = null;
|
|
9888
|
+
this.readerArtifactRetryAfter.clear();
|
|
8922
9889
|
}
|
|
8923
9890
|
};
|
|
8924
9891
|
|
|
8925
9892
|
// src/tools/knowledge-base-paths.ts
|
|
8926
|
-
var
|
|
9893
|
+
var path14 = __toESM(require("path"), 1);
|
|
8927
9894
|
function resolveConfigPathValue(value, baseDir) {
|
|
8928
9895
|
const trimmed = value.trim();
|
|
8929
9896
|
if (!trimmed) {
|
|
8930
9897
|
return trimmed;
|
|
8931
9898
|
}
|
|
8932
|
-
const absolutePath =
|
|
8933
|
-
return
|
|
9899
|
+
const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
|
|
9900
|
+
return path14.normalize(absolutePath);
|
|
8934
9901
|
}
|
|
8935
9902
|
function serializeConfigPathValue(value, baseDir) {
|
|
8936
9903
|
const trimmed = value.trim();
|
|
8937
9904
|
if (!trimmed) {
|
|
8938
9905
|
return trimmed;
|
|
8939
9906
|
}
|
|
8940
|
-
if (!
|
|
8941
|
-
return normalizePathSeparators(
|
|
9907
|
+
if (!path14.isAbsolute(trimmed)) {
|
|
9908
|
+
return normalizePathSeparators(path14.normalize(trimmed));
|
|
8942
9909
|
}
|
|
8943
|
-
const relativePath =
|
|
8944
|
-
if (!relativePath || !relativePath.startsWith("..") && !
|
|
8945
|
-
return normalizePathSeparators(
|
|
9910
|
+
const relativePath = path14.relative(baseDir, trimmed);
|
|
9911
|
+
if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
|
|
9912
|
+
return normalizePathSeparators(path14.normalize(relativePath || "."));
|
|
8946
9913
|
}
|
|
8947
|
-
return
|
|
9914
|
+
return path14.normalize(trimmed);
|
|
8948
9915
|
}
|
|
8949
9916
|
function resolveKnowledgeBasePath(value, projectRoot3) {
|
|
8950
|
-
return
|
|
9917
|
+
return path14.isAbsolute(value) ? value : path14.resolve(projectRoot3, value);
|
|
8951
9918
|
}
|
|
8952
9919
|
function normalizeKnowledgeBasePath2(value, projectRoot3) {
|
|
8953
|
-
return
|
|
9920
|
+
return path14.normalize(resolveKnowledgeBasePath(value, projectRoot3));
|
|
8954
9921
|
}
|
|
8955
9922
|
function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
|
|
8956
|
-
const normalizedInput =
|
|
9923
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
8957
9924
|
return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
|
|
8958
9925
|
}
|
|
8959
9926
|
function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
|
|
8960
|
-
const normalizedInput =
|
|
9927
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
8961
9928
|
return knowledgeBases.findIndex(
|
|
8962
|
-
(kb) =>
|
|
9929
|
+
(kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
|
|
8963
9930
|
);
|
|
8964
9931
|
}
|
|
8965
9932
|
|
|
@@ -9059,6 +10026,10 @@ function formatStatus(status) {
|
|
|
9059
10026
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
9060
10027
|
}
|
|
9061
10028
|
}
|
|
10029
|
+
if (status.warning) {
|
|
10030
|
+
lines.push("");
|
|
10031
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
10032
|
+
}
|
|
9062
10033
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
9063
10034
|
lines.push("");
|
|
9064
10035
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -9104,6 +10075,17 @@ function calculatePercentage(progress) {
|
|
|
9104
10075
|
if (progress.phase === "storing") return 95;
|
|
9105
10076
|
return 0;
|
|
9106
10077
|
}
|
|
10078
|
+
function formatCodebasePeek(results) {
|
|
10079
|
+
if (results.length === 0) {
|
|
10080
|
+
return "No matching code found. Try a different query or run index_codebase first.";
|
|
10081
|
+
}
|
|
10082
|
+
const formatted = results.map((r, idx) => {
|
|
10083
|
+
const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10084
|
+
const name = r.name ? `"${r.name}"` : "(anonymous)";
|
|
10085
|
+
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})${formatBlame(r)}`;
|
|
10086
|
+
});
|
|
10087
|
+
return formatted.join("\n");
|
|
10088
|
+
}
|
|
9107
10089
|
function formatHealthCheck(result) {
|
|
9108
10090
|
if (result.resetCorruptedIndex) {
|
|
9109
10091
|
return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
|
|
@@ -9153,16 +10135,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
|
9153
10135
|
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
9154
10136
|
}).join("\n");
|
|
9155
10137
|
}
|
|
9156
|
-
function formatCallGraphPath(from, to,
|
|
9157
|
-
if (
|
|
10138
|
+
function formatCallGraphPath(from, to, path17) {
|
|
10139
|
+
if (path17.length === 0) {
|
|
9158
10140
|
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
9159
10141
|
}
|
|
9160
|
-
const formatted =
|
|
10142
|
+
const formatted = path17.map((hop, index) => {
|
|
9161
10143
|
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
9162
10144
|
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
9163
10145
|
return `${prefix} ${hop.symbolName}${location}`;
|
|
9164
10146
|
});
|
|
9165
|
-
return `Path (${
|
|
10147
|
+
return `Path (${path17.length} hops):
|
|
9166
10148
|
${formatted.join("\n")}`;
|
|
9167
10149
|
}
|
|
9168
10150
|
function formatResultHeader(result, index) {
|
|
@@ -9202,8 +10184,8 @@ ${truncateContent(r.content)}
|
|
|
9202
10184
|
}
|
|
9203
10185
|
|
|
9204
10186
|
// src/tools/config-state.ts
|
|
9205
|
-
var
|
|
9206
|
-
var
|
|
10187
|
+
var import_fs9 = require("fs");
|
|
10188
|
+
var path15 = __toESM(require("path"), 1);
|
|
9207
10189
|
function normalizeKnowledgeBasePaths(config, projectRoot3) {
|
|
9208
10190
|
const normalized = { ...config };
|
|
9209
10191
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
@@ -9230,10 +10212,10 @@ function loadEditableConfig(projectRoot3, host = "opencode") {
|
|
|
9230
10212
|
}
|
|
9231
10213
|
function saveConfig(projectRoot3, config, host = "opencode") {
|
|
9232
10214
|
const configPath = getConfigPath(projectRoot3, host);
|
|
9233
|
-
const configDir =
|
|
9234
|
-
const configBaseDir =
|
|
9235
|
-
if (!(0,
|
|
9236
|
-
(0,
|
|
10215
|
+
const configDir = path15.dirname(configPath);
|
|
10216
|
+
const configBaseDir = path15.dirname(configDir);
|
|
10217
|
+
if (!(0, import_fs9.existsSync)(configDir)) {
|
|
10218
|
+
(0, import_fs9.mkdirSync)(configDir, { recursive: true });
|
|
9237
10219
|
}
|
|
9238
10220
|
const serializableConfig = { ...config };
|
|
9239
10221
|
if (Array.isArray(serializableConfig.knowledgeBases)) {
|
|
@@ -9241,13 +10223,31 @@ function saveConfig(projectRoot3, config, host = "opencode") {
|
|
|
9241
10223
|
(kb) => serializeConfigPathValue(kb, configBaseDir)
|
|
9242
10224
|
);
|
|
9243
10225
|
}
|
|
9244
|
-
(0,
|
|
10226
|
+
(0, import_fs9.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
|
|
9245
10227
|
}
|
|
9246
10228
|
|
|
9247
10229
|
// src/tools/operations.ts
|
|
9248
10230
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
9249
10231
|
var configCache = /* @__PURE__ */ new Map();
|
|
9250
10232
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
10233
|
+
function getIndexBusyResult(error) {
|
|
10234
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
10235
|
+
const owner = error.owner;
|
|
10236
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
10237
|
+
if (error.reason === "legacy-lock") {
|
|
10238
|
+
return {
|
|
10239
|
+
kind: "busy",
|
|
10240
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
10241
|
+
};
|
|
10242
|
+
}
|
|
10243
|
+
if (error.reason === "unknown-owner") {
|
|
10244
|
+
return {
|
|
10245
|
+
kind: "busy",
|
|
10246
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
10247
|
+
};
|
|
10248
|
+
}
|
|
10249
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
10250
|
+
}
|
|
9251
10251
|
function getProjectRoot(projectRoot3, host) {
|
|
9252
10252
|
if (projectRoot3) {
|
|
9253
10253
|
return projectRoot3;
|
|
@@ -9285,8 +10285,8 @@ function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = pa
|
|
|
9285
10285
|
}
|
|
9286
10286
|
function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
|
|
9287
10287
|
const root = getProjectRoot(projectRoot3, host);
|
|
9288
|
-
const localIndexPath =
|
|
9289
|
-
if ((0,
|
|
10288
|
+
const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
|
|
10289
|
+
if ((0, import_fs10.existsSync)(localIndexPath)) {
|
|
9290
10290
|
return false;
|
|
9291
10291
|
}
|
|
9292
10292
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
@@ -9342,35 +10342,46 @@ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
|
|
|
9342
10342
|
async function runIndexCodebase(projectRoot3, host, args, onProgress) {
|
|
9343
10343
|
const root = getProjectRoot(projectRoot3, host);
|
|
9344
10344
|
const key = getIndexerCacheKey(root, host);
|
|
9345
|
-
const cachedConfig = configCache.get(key);
|
|
9346
10345
|
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)
|
|
10346
|
+
const runtimeConfig = configCache.get(key);
|
|
10347
|
+
try {
|
|
10348
|
+
if (args.estimateOnly) {
|
|
10349
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
10350
|
+
}
|
|
10351
|
+
const runIndex = async (target) => {
|
|
10352
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
10353
|
+
return operation((progress) => {
|
|
10354
|
+
if (onProgress) {
|
|
10355
|
+
return onProgress(formatProgressTitle(progress), {
|
|
10356
|
+
phase: progress.phase,
|
|
10357
|
+
filesProcessed: progress.filesProcessed,
|
|
10358
|
+
totalFiles: progress.totalFiles,
|
|
10359
|
+
chunksProcessed: progress.chunksProcessed,
|
|
10360
|
+
totalChunks: progress.totalChunks,
|
|
10361
|
+
percentage: calculatePercentage(progress)
|
|
10362
|
+
});
|
|
10363
|
+
}
|
|
10364
|
+
return Promise.resolve();
|
|
9369
10365
|
});
|
|
10366
|
+
};
|
|
10367
|
+
let stats;
|
|
10368
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
10369
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
10370
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
10371
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
10372
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
10373
|
+
indexer = getIndexerForProject(root, host);
|
|
10374
|
+
return runIndex(indexer);
|
|
10375
|
+
}, { completeRecoveries: false });
|
|
10376
|
+
} else {
|
|
10377
|
+
stats = await runIndex(indexer);
|
|
9370
10378
|
}
|
|
9371
|
-
return
|
|
9372
|
-
})
|
|
9373
|
-
|
|
10379
|
+
return { kind: "stats", stats };
|
|
10380
|
+
} catch (error) {
|
|
10381
|
+
const busyResult = getIndexBusyResult(error);
|
|
10382
|
+
if (!busyResult) throw error;
|
|
10383
|
+
return busyResult;
|
|
10384
|
+
}
|
|
9374
10385
|
}
|
|
9375
10386
|
async function getIndexStatus(projectRoot3, host) {
|
|
9376
10387
|
const indexer = getIndexerForProject(projectRoot3, host);
|
|
@@ -9380,6 +10391,15 @@ async function getIndexHealthCheck(projectRoot3, host) {
|
|
|
9380
10391
|
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9381
10392
|
return indexer.healthCheck();
|
|
9382
10393
|
}
|
|
10394
|
+
async function runIndexHealthCheck(projectRoot3, host) {
|
|
10395
|
+
try {
|
|
10396
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot3, host) };
|
|
10397
|
+
} catch (error) {
|
|
10398
|
+
const busyResult = getIndexBusyResult(error);
|
|
10399
|
+
if (!busyResult) throw error;
|
|
10400
|
+
return busyResult;
|
|
10401
|
+
}
|
|
10402
|
+
}
|
|
9383
10403
|
async function getPrImpact(projectRoot3, host, params) {
|
|
9384
10404
|
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9385
10405
|
return indexer.getPrImpact({
|
|
@@ -9446,15 +10466,15 @@ async function getIndexLogs(projectRoot3, host, args) {
|
|
|
9446
10466
|
function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
|
|
9447
10467
|
const root = getProjectRoot(projectRoot3, host);
|
|
9448
10468
|
const inputPath = knowledgeBasePath.trim();
|
|
9449
|
-
const normalizedPath =
|
|
9450
|
-
|
|
10469
|
+
const normalizedPath = path16.resolve(
|
|
10470
|
+
path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
|
|
9451
10471
|
);
|
|
9452
|
-
if (!(0,
|
|
10472
|
+
if (!(0, import_fs10.existsSync)(normalizedPath)) {
|
|
9453
10473
|
return `Error: Directory does not exist: ${normalizedPath}`;
|
|
9454
10474
|
}
|
|
9455
10475
|
let realPath;
|
|
9456
10476
|
try {
|
|
9457
|
-
realPath = (0,
|
|
10477
|
+
realPath = (0, import_fs10.realpathSync)(normalizedPath);
|
|
9458
10478
|
} catch {
|
|
9459
10479
|
return `Error: Cannot resolve path: ${normalizedPath}`;
|
|
9460
10480
|
}
|
|
@@ -9483,13 +10503,13 @@ function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
|
|
|
9483
10503
|
}
|
|
9484
10504
|
}
|
|
9485
10505
|
for (const dotDir of sensitiveDotDirs) {
|
|
9486
|
-
const sensitiveDir =
|
|
10506
|
+
const sensitiveDir = path16.join(homeDir, dotDir);
|
|
9487
10507
|
if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
|
|
9488
10508
|
return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
|
|
9489
10509
|
}
|
|
9490
10510
|
}
|
|
9491
10511
|
try {
|
|
9492
|
-
const stat = (0,
|
|
10512
|
+
const stat = (0, import_fs10.statSync)(normalizedPath);
|
|
9493
10513
|
if (!stat.isDirectory()) {
|
|
9494
10514
|
return `Error: Path is not a directory: ${normalizedPath}`;
|
|
9495
10515
|
}
|
|
@@ -9529,7 +10549,7 @@ function listKnowledgeBases(projectRoot3, host) {
|
|
|
9529
10549
|
for (let i = 0; i < knowledgeBases.length; i++) {
|
|
9530
10550
|
const kb = knowledgeBases[i];
|
|
9531
10551
|
const resolvedPath = resolveKnowledgeBasePath(kb, root);
|
|
9532
|
-
const exists = (0,
|
|
10552
|
+
const exists = (0, import_fs10.existsSync)(resolvedPath);
|
|
9533
10553
|
result += `[${i + 1}] ${kb}
|
|
9534
10554
|
`;
|
|
9535
10555
|
result += ` Resolved: ${resolvedPath}
|
|
@@ -9538,7 +10558,7 @@ function listKnowledgeBases(projectRoot3, host) {
|
|
|
9538
10558
|
`;
|
|
9539
10559
|
if (exists) {
|
|
9540
10560
|
try {
|
|
9541
|
-
const stat = (0,
|
|
10561
|
+
const stat = (0, import_fs10.statSync)(resolvedPath);
|
|
9542
10562
|
result += ` Type: ${stat.isDirectory() ? "Directory" : "File"}
|
|
9543
10563
|
`;
|
|
9544
10564
|
} catch {
|
|
@@ -9546,7 +10566,7 @@ function listKnowledgeBases(projectRoot3, host) {
|
|
|
9546
10566
|
}
|
|
9547
10567
|
result += "\n";
|
|
9548
10568
|
}
|
|
9549
|
-
const hasHostConfig = (0,
|
|
10569
|
+
const hasHostConfig = (0, import_fs10.existsSync)(path16.join(root, getHostProjectConfigRelativePath(host)));
|
|
9550
10570
|
if (hasHostConfig) {
|
|
9551
10571
|
result += `
|
|
9552
10572
|
Config sources: 1 file(s).`;
|
|
@@ -9652,10 +10672,67 @@ function projectRoot2(ctx) {
|
|
|
9652
10672
|
return ctx?.cwd ?? process.cwd();
|
|
9653
10673
|
}
|
|
9654
10674
|
function codebaseIndexPiExtension(pi) {
|
|
10675
|
+
pi.registerTool({
|
|
10676
|
+
name: "codebase_context",
|
|
10677
|
+
label: "Codebase Context",
|
|
10678
|
+
description: "PREFERRED FIRST TOOL for any repository question. Check index_status when freshness is unknown, then use this tool for low-token location discovery and dependency flow.",
|
|
10679
|
+
parameters: import_typebox2.Type.Object({
|
|
10680
|
+
query: import_typebox2.Type.String({ description: "Natural language description of what code you're trying to locate" }),
|
|
10681
|
+
from: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Source symbol when asking for a dependency path." })),
|
|
10682
|
+
to: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Target symbol when asking for a dependency path." })),
|
|
10683
|
+
symbol: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Exact symbol name for an authoritative definition lookup." })),
|
|
10684
|
+
limit: import_typebox2.Type.Optional(import_typebox2.Type.Number({ description: "Maximum results (default: 10)" })),
|
|
10685
|
+
maxDepth: import_typebox2.Type.Optional(import_typebox2.Type.Number({ description: "Maximum call-graph traversal depth for from/to paths" })),
|
|
10686
|
+
fileType: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Filter by file extension, e.g., ts, py, rs" })),
|
|
10687
|
+
directory: import_typebox2.Type.Optional(import_typebox2.Type.String({ description: "Filter by directory path" }))
|
|
10688
|
+
}),
|
|
10689
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
10690
|
+
const root = projectRoot2(ctx);
|
|
10691
|
+
if (params.from && params.to) {
|
|
10692
|
+
const path17 = await getCallGraphPath(root, HOST2, params.from, params.to, params.maxDepth);
|
|
10693
|
+
if (path17.length > 0) {
|
|
10694
|
+
return text2(formatCallGraphPath(params.from, params.to, path17), path17);
|
|
10695
|
+
}
|
|
10696
|
+
const { callers } = await getCallGraphData(root, HOST2, {
|
|
10697
|
+
name: params.to,
|
|
10698
|
+
direction: "callers"
|
|
10699
|
+
});
|
|
10700
|
+
const directEdge = callers.find((edge) => edge.fromSymbolName === params.from);
|
|
10701
|
+
if (directEdge) {
|
|
10702
|
+
const location = directEdge.fromSymbolFilePath ? ` at ${directEdge.fromSymbolFilePath}:${directEdge.line}` : "";
|
|
10703
|
+
return text2(
|
|
10704
|
+
`Direct path: ${params.from} --${directEdge.callType}--> ${params.to}${location} (edge is ${directEdge.isResolved ? "resolved" : "unresolved"}).`,
|
|
10705
|
+
path17
|
|
10706
|
+
);
|
|
10707
|
+
}
|
|
10708
|
+
return text2(formatCallGraphPath(params.from, params.to, path17), path17);
|
|
10709
|
+
}
|
|
10710
|
+
if (params.symbol) {
|
|
10711
|
+
const results2 = await implementationLookup(root, HOST2, params.symbol, {
|
|
10712
|
+
limit: params.limit ?? 10,
|
|
10713
|
+
fileType: params.fileType,
|
|
10714
|
+
directory: params.directory
|
|
10715
|
+
});
|
|
10716
|
+
return text2(formatDefinitionLookup(results2, params.symbol), results2);
|
|
10717
|
+
}
|
|
10718
|
+
const results = await searchCodebase(root, HOST2, params.query, {
|
|
10719
|
+
limit: params.limit ?? 10,
|
|
10720
|
+
fileType: params.fileType,
|
|
10721
|
+
directory: params.directory,
|
|
10722
|
+
metadataOnly: true
|
|
10723
|
+
});
|
|
10724
|
+
if (results.length === 0) {
|
|
10725
|
+
return text2("No matching code found. Try a different query or run index_status/index_codebase first.");
|
|
10726
|
+
}
|
|
10727
|
+
return text2(`Found ${results.length} locations for "${params.query}":
|
|
10728
|
+
|
|
10729
|
+
${formatCodebasePeek(results)}`, results);
|
|
10730
|
+
}
|
|
10731
|
+
});
|
|
9655
10732
|
pi.registerTool({
|
|
9656
10733
|
name: "codebase_search",
|
|
9657
10734
|
label: "Codebase Search",
|
|
9658
|
-
description: "
|
|
10735
|
+
description: "Use this after codebase_context when you need semantic content, not just locations. Describe behavior, not syntax.",
|
|
9659
10736
|
parameters: import_typebox2.Type.Object({
|
|
9660
10737
|
query: import_typebox2.Type.String({ description: "Natural language description of what code you're looking for" }),
|
|
9661
10738
|
limit: import_typebox2.Type.Optional(import_typebox2.Type.Number({ description: "Maximum results (default: 10)" })),
|
|
@@ -9675,7 +10752,7 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9675
10752
|
pi.registerTool({
|
|
9676
10753
|
name: "codebase_peek",
|
|
9677
10754
|
label: "Codebase Peek",
|
|
9678
|
-
description: "
|
|
10755
|
+
description: "LOW-TOKEN location-first retrieval. Prefer codebase_context first, then use this for cheap conceptual lookup.",
|
|
9679
10756
|
parameters: import_typebox2.Type.Object({
|
|
9680
10757
|
query: import_typebox2.Type.String(),
|
|
9681
10758
|
limit: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
|
|
@@ -9694,7 +10771,7 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9694
10771
|
pi.registerTool({
|
|
9695
10772
|
name: "find_similar",
|
|
9696
10773
|
label: "Find Similar Code",
|
|
9697
|
-
description: "Find code similar to a snippet for duplicate detection and
|
|
10774
|
+
description: "Find code similar to a snippet for duplicate detection, pattern discovery, and refactor planning.",
|
|
9698
10775
|
parameters: import_typebox2.Type.Object({
|
|
9699
10776
|
code: import_typebox2.Type.String({ description: "Code snippet to compare" }),
|
|
9700
10777
|
limit: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
|
|
@@ -9711,7 +10788,7 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9711
10788
|
pi.registerTool({
|
|
9712
10789
|
name: "implementation_lookup",
|
|
9713
10790
|
label: "Implementation Lookup",
|
|
9714
|
-
description: "Find likely symbol definitions or implementations
|
|
10791
|
+
description: "Find likely symbol definitions or implementations after codebase_context identifies a symbol.",
|
|
9715
10792
|
parameters: import_typebox2.Type.Object({
|
|
9716
10793
|
query: import_typebox2.Type.String(),
|
|
9717
10794
|
limit: import_typebox2.Type.Optional(import_typebox2.Type.Number()),
|
|
@@ -9726,7 +10803,7 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9726
10803
|
pi.registerTool({
|
|
9727
10804
|
name: "index_codebase",
|
|
9728
10805
|
label: "Index Codebase",
|
|
9729
|
-
description: "Build or refresh the semantic codebase index.",
|
|
10806
|
+
description: "Build or refresh the semantic codebase index. Run index_status when freshness is unknown.",
|
|
9730
10807
|
parameters: import_typebox2.Type.Object({
|
|
9731
10808
|
force: import_typebox2.Type.Optional(import_typebox2.Type.Boolean({ default: false })),
|
|
9732
10809
|
estimateOnly: import_typebox2.Type.Optional(import_typebox2.Type.Boolean({ default: false })),
|
|
@@ -9734,7 +10811,9 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9734
10811
|
}),
|
|
9735
10812
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9736
10813
|
const result = await runIndexCodebase(projectRoot2(ctx), HOST2, params);
|
|
9737
|
-
|
|
10814
|
+
if (result.kind === "estimate") return text2(formatCostEstimate(result.estimate), result.estimate);
|
|
10815
|
+
if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
|
|
10816
|
+
return text2(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
|
|
9738
10817
|
}
|
|
9739
10818
|
});
|
|
9740
10819
|
pi.registerTool({
|
|
@@ -9750,11 +10829,12 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9750
10829
|
pi.registerTool({
|
|
9751
10830
|
name: "index_health_check",
|
|
9752
10831
|
label: "Index Health Check",
|
|
9753
|
-
description: "Garbage collect orphaned embeddings/chunks and report health.",
|
|
10832
|
+
description: "Garbage collect orphaned embeddings/chunks and report index health status.",
|
|
9754
10833
|
parameters: import_typebox2.Type.Object({}),
|
|
9755
10834
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
9756
|
-
const result = await
|
|
9757
|
-
return text2(
|
|
10835
|
+
const result = await runIndexHealthCheck(projectRoot2(ctx), HOST2);
|
|
10836
|
+
if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
|
|
10837
|
+
return text2(formatHealthCheck(result.health), result.health);
|
|
9758
10838
|
}
|
|
9759
10839
|
});
|
|
9760
10840
|
pi.registerTool({
|
|
@@ -9789,6 +10869,11 @@ function codebaseIndexPiExtension(pi) {
|
|
|
9789
10869
|
}
|
|
9790
10870
|
});
|
|
9791
10871
|
registerPiCallGraphTools(pi);
|
|
10872
|
+
pi.on("before_agent_start", (event) => ({
|
|
10873
|
+
systemPrompt: `${event.systemPrompt}
|
|
10874
|
+
|
|
10875
|
+
Check index_status first when index readiness is unknown. For repository questions, call codebase_context before search/grep/bash/read-style broad reads. Use implementation_lookup for known symbols and call_graph/call_graph_path after endpoints are identified for dependency flow. Avoid broad tool calls until semantic locations are known.`
|
|
10876
|
+
}));
|
|
9792
10877
|
pi.registerTool({
|
|
9793
10878
|
name: "pr_impact",
|
|
9794
10879
|
label: "PR Impact",
|