opencode-codebase-index 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -6
- package/commands/peek.md +4 -0
- package/commands/search.md +4 -0
- package/dist/cli.cjs +1921 -640
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1919 -628
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1856 -580
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1855 -569
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +1855 -610
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +1861 -606
- package/dist/pi-extension.js.map +1 -1
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +11 -8
- package/skill/SKILL.md +5 -0
package/dist/cli.js
CHANGED
|
@@ -491,7 +491,7 @@ var require_ignore = __commonJS({
|
|
|
491
491
|
// path matching.
|
|
492
492
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
493
493
|
// @returns {TestResult} true if a file is ignored
|
|
494
|
-
test(
|
|
494
|
+
test(path27, checkUnignored, mode) {
|
|
495
495
|
let ignored = false;
|
|
496
496
|
let unignored = false;
|
|
497
497
|
let matchedRule;
|
|
@@ -500,7 +500,7 @@ var require_ignore = __commonJS({
|
|
|
500
500
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
501
501
|
return;
|
|
502
502
|
}
|
|
503
|
-
const matched = rule[mode].test(
|
|
503
|
+
const matched = rule[mode].test(path27);
|
|
504
504
|
if (!matched) {
|
|
505
505
|
return;
|
|
506
506
|
}
|
|
@@ -521,17 +521,17 @@ var require_ignore = __commonJS({
|
|
|
521
521
|
var throwError = (message, Ctor) => {
|
|
522
522
|
throw new Ctor(message);
|
|
523
523
|
};
|
|
524
|
-
var checkPath = (
|
|
525
|
-
if (!isString(
|
|
524
|
+
var checkPath = (path27, originalPath, doThrow) => {
|
|
525
|
+
if (!isString(path27)) {
|
|
526
526
|
return doThrow(
|
|
527
527
|
`path must be a string, but got \`${originalPath}\``,
|
|
528
528
|
TypeError
|
|
529
529
|
);
|
|
530
530
|
}
|
|
531
|
-
if (!
|
|
531
|
+
if (!path27) {
|
|
532
532
|
return doThrow(`path must not be empty`, TypeError);
|
|
533
533
|
}
|
|
534
|
-
if (checkPath.isNotRelative(
|
|
534
|
+
if (checkPath.isNotRelative(path27)) {
|
|
535
535
|
const r = "`path.relative()`d";
|
|
536
536
|
return doThrow(
|
|
537
537
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -540,7 +540,7 @@ var require_ignore = __commonJS({
|
|
|
540
540
|
}
|
|
541
541
|
return true;
|
|
542
542
|
};
|
|
543
|
-
var isNotRelative = (
|
|
543
|
+
var isNotRelative = (path27) => REGEX_TEST_INVALID_PATH.test(path27);
|
|
544
544
|
checkPath.isNotRelative = isNotRelative;
|
|
545
545
|
checkPath.convert = (p) => p;
|
|
546
546
|
var Ignore2 = class {
|
|
@@ -570,19 +570,19 @@ var require_ignore = __commonJS({
|
|
|
570
570
|
}
|
|
571
571
|
// @returns {TestResult}
|
|
572
572
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
573
|
-
const
|
|
573
|
+
const path27 = originalPath && checkPath.convert(originalPath);
|
|
574
574
|
checkPath(
|
|
575
|
-
|
|
575
|
+
path27,
|
|
576
576
|
originalPath,
|
|
577
577
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
578
578
|
);
|
|
579
|
-
return this._t(
|
|
579
|
+
return this._t(path27, cache, checkUnignored, slices);
|
|
580
580
|
}
|
|
581
|
-
checkIgnore(
|
|
582
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
583
|
-
return this.test(
|
|
581
|
+
checkIgnore(path27) {
|
|
582
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path27)) {
|
|
583
|
+
return this.test(path27);
|
|
584
584
|
}
|
|
585
|
-
const slices =
|
|
585
|
+
const slices = path27.split(SLASH2).filter(Boolean);
|
|
586
586
|
slices.pop();
|
|
587
587
|
if (slices.length) {
|
|
588
588
|
const parent = this._t(
|
|
@@ -595,18 +595,18 @@ var require_ignore = __commonJS({
|
|
|
595
595
|
return parent;
|
|
596
596
|
}
|
|
597
597
|
}
|
|
598
|
-
return this._rules.test(
|
|
598
|
+
return this._rules.test(path27, false, MODE_CHECK_IGNORE);
|
|
599
599
|
}
|
|
600
|
-
_t(
|
|
601
|
-
if (
|
|
602
|
-
return cache[
|
|
600
|
+
_t(path27, cache, checkUnignored, slices) {
|
|
601
|
+
if (path27 in cache) {
|
|
602
|
+
return cache[path27];
|
|
603
603
|
}
|
|
604
604
|
if (!slices) {
|
|
605
|
-
slices =
|
|
605
|
+
slices = path27.split(SLASH2).filter(Boolean);
|
|
606
606
|
}
|
|
607
607
|
slices.pop();
|
|
608
608
|
if (!slices.length) {
|
|
609
|
-
return cache[
|
|
609
|
+
return cache[path27] = this._rules.test(path27, checkUnignored, MODE_IGNORE);
|
|
610
610
|
}
|
|
611
611
|
const parent = this._t(
|
|
612
612
|
slices.join(SLASH2) + SLASH2,
|
|
@@ -614,29 +614,29 @@ var require_ignore = __commonJS({
|
|
|
614
614
|
checkUnignored,
|
|
615
615
|
slices
|
|
616
616
|
);
|
|
617
|
-
return cache[
|
|
617
|
+
return cache[path27] = parent.ignored ? parent : this._rules.test(path27, checkUnignored, MODE_IGNORE);
|
|
618
618
|
}
|
|
619
|
-
ignores(
|
|
620
|
-
return this._test(
|
|
619
|
+
ignores(path27) {
|
|
620
|
+
return this._test(path27, this._ignoreCache, false).ignored;
|
|
621
621
|
}
|
|
622
622
|
createFilter() {
|
|
623
|
-
return (
|
|
623
|
+
return (path27) => !this.ignores(path27);
|
|
624
624
|
}
|
|
625
625
|
filter(paths) {
|
|
626
626
|
return makeArray(paths).filter(this.createFilter());
|
|
627
627
|
}
|
|
628
628
|
// @returns {TestResult}
|
|
629
|
-
test(
|
|
630
|
-
return this._test(
|
|
629
|
+
test(path27) {
|
|
630
|
+
return this._test(path27, this._testCache, true);
|
|
631
631
|
}
|
|
632
632
|
};
|
|
633
633
|
var factory = (options) => new Ignore2(options);
|
|
634
|
-
var isPathValid = (
|
|
634
|
+
var isPathValid = (path27) => checkPath(path27 && checkPath.convert(path27), path27, RETURN_FALSE);
|
|
635
635
|
var setupWindows = () => {
|
|
636
636
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
637
637
|
checkPath.convert = makePosix;
|
|
638
638
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
639
|
-
checkPath.isNotRelative = (
|
|
639
|
+
checkPath.isNotRelative = (path27) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path27) || isNotRelative(path27);
|
|
640
640
|
};
|
|
641
641
|
if (
|
|
642
642
|
// Detect `process` so that it can run in browsers.
|
|
@@ -653,17 +653,17 @@ var require_ignore = __commonJS({
|
|
|
653
653
|
|
|
654
654
|
// src/cli.ts
|
|
655
655
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
656
|
-
import { realpathSync as
|
|
657
|
-
import * as
|
|
658
|
-
import * as
|
|
656
|
+
import { realpathSync as realpathSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
657
|
+
import * as os6 from "os";
|
|
658
|
+
import * as path26 from "path";
|
|
659
659
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
660
660
|
|
|
661
661
|
// src/config/constants.ts
|
|
662
662
|
var DEFAULT_INCLUDE = [
|
|
663
|
-
"**/*.{ts,tsx,js,jsx,mjs,cjs}",
|
|
663
|
+
"**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
664
664
|
"**/*.{py,pyi}",
|
|
665
|
-
"**/*.{go,rs,java,kt,scala}",
|
|
666
|
-
"**/*.{c,cpp,cc,h,hpp}",
|
|
665
|
+
"**/*.{go,rs,java,cs,kt,scala}",
|
|
666
|
+
"**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
|
|
667
667
|
"**/*.{rb,php,inc,swift}",
|
|
668
668
|
"**/*.{cls,trigger}",
|
|
669
669
|
"**/*.{vue,svelte,astro}",
|
|
@@ -788,7 +788,8 @@ function getDefaultIndexingConfig() {
|
|
|
788
788
|
requireProjectMarker: true,
|
|
789
789
|
maxDepth: 5,
|
|
790
790
|
maxFilesPerDirectory: 100,
|
|
791
|
-
fallbackToTextOnMaxChunks: true
|
|
791
|
+
fallbackToTextOnMaxChunks: true,
|
|
792
|
+
gitBlame: { enabled: false }
|
|
792
793
|
};
|
|
793
794
|
}
|
|
794
795
|
function getDefaultSearchConfig() {
|
|
@@ -912,7 +913,10 @@ function parseConfig(raw) {
|
|
|
912
913
|
requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
|
|
913
914
|
maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
|
|
914
915
|
maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
|
|
915
|
-
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
|
|
916
|
+
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
|
|
917
|
+
gitBlame: {
|
|
918
|
+
enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
|
|
919
|
+
}
|
|
916
920
|
};
|
|
917
921
|
const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
|
|
918
922
|
const search = {
|
|
@@ -1042,7 +1046,7 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
|
|
|
1042
1046
|
);
|
|
1043
1047
|
|
|
1044
1048
|
// src/config/host.ts
|
|
1045
|
-
var HOST_MODES = ["opencode", "codex", "claude", "pi"];
|
|
1049
|
+
var HOST_MODES = ["opencode", "codex", "claude", "pi", "jcode"];
|
|
1046
1050
|
function isSupportedHostMode(value) {
|
|
1047
1051
|
return HOST_MODES.includes(value);
|
|
1048
1052
|
}
|
|
@@ -1097,9 +1101,9 @@ import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
1097
1101
|
import * as path from "path";
|
|
1098
1102
|
|
|
1099
1103
|
// src/eval/report-formatters.ts
|
|
1100
|
-
function assertFiniteNumber(value,
|
|
1104
|
+
function assertFiniteNumber(value, path27) {
|
|
1101
1105
|
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1102
|
-
throw new Error(`${
|
|
1106
|
+
throw new Error(`${path27} must be a finite number`);
|
|
1103
1107
|
}
|
|
1104
1108
|
return value;
|
|
1105
1109
|
}
|
|
@@ -1287,16 +1291,16 @@ function buildPerQueryArtifact(perQuery) {
|
|
|
1287
1291
|
}
|
|
1288
1292
|
|
|
1289
1293
|
// src/eval/runner.ts
|
|
1290
|
-
import { existsSync as
|
|
1291
|
-
import * as
|
|
1294
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1295
|
+
import * as path15 from "path";
|
|
1292
1296
|
import { performance as performance3 } from "perf_hooks";
|
|
1293
1297
|
|
|
1294
1298
|
// src/indexer/index.ts
|
|
1295
|
-
import { existsSync as
|
|
1296
|
-
import * as
|
|
1299
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync, mkdirSync as mkdirSync3, promises as fsPromises2 } from "fs";
|
|
1300
|
+
import * as path12 from "path";
|
|
1297
1301
|
import { performance as performance2 } from "perf_hooks";
|
|
1298
|
-
import { execFile as
|
|
1299
|
-
import { promisify as
|
|
1302
|
+
import { execFile as execFile3 } from "child_process";
|
|
1303
|
+
import { promisify as promisify3 } from "util";
|
|
1300
1304
|
|
|
1301
1305
|
// node_modules/eventemitter3/index.mjs
|
|
1302
1306
|
var import_index = __toESM(require_eventemitter3(), 1);
|
|
@@ -2575,17 +2579,17 @@ function validateExternalUrl(urlString) {
|
|
|
2575
2579
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2576
2580
|
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2577
2581
|
}
|
|
2578
|
-
const
|
|
2579
|
-
if (BLOCKED_HOSTNAMES.has(
|
|
2580
|
-
return { valid: false, reason: `Blocked: cloud metadata service (${
|
|
2582
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
2583
|
+
if (BLOCKED_HOSTNAMES.has(hostname2)) {
|
|
2584
|
+
return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
|
|
2581
2585
|
}
|
|
2582
2586
|
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2583
|
-
if (pattern.test(
|
|
2584
|
-
return { valid: false, reason: `Blocked: cloud metadata IP (${
|
|
2587
|
+
if (pattern.test(hostname2)) {
|
|
2588
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
|
|
2585
2589
|
}
|
|
2586
2590
|
}
|
|
2587
|
-
if (/^169\.254\./.test(
|
|
2588
|
-
return { valid: false, reason: `Blocked: link-local address (${
|
|
2591
|
+
if (/^169\.254\./.test(hostname2)) {
|
|
2592
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
|
|
2589
2593
|
}
|
|
2590
2594
|
return { valid: true };
|
|
2591
2595
|
}
|
|
@@ -3775,6 +3779,12 @@ function createMockNativeBinding() {
|
|
|
3775
3779
|
constructor() {
|
|
3776
3780
|
throw error;
|
|
3777
3781
|
}
|
|
3782
|
+
static openReadOnly() {
|
|
3783
|
+
throw error;
|
|
3784
|
+
}
|
|
3785
|
+
static createEmptyReadOnly() {
|
|
3786
|
+
throw error;
|
|
3787
|
+
}
|
|
3778
3788
|
close() {
|
|
3779
3789
|
throw error;
|
|
3780
3790
|
}
|
|
@@ -3878,6 +3888,12 @@ var VectorStore = class {
|
|
|
3878
3888
|
load() {
|
|
3879
3889
|
this.inner.load();
|
|
3880
3890
|
}
|
|
3891
|
+
loadStrict() {
|
|
3892
|
+
this.inner.loadStrict();
|
|
3893
|
+
}
|
|
3894
|
+
hasFingerprint() {
|
|
3895
|
+
return this.inner.hasFingerprint();
|
|
3896
|
+
}
|
|
3881
3897
|
count() {
|
|
3882
3898
|
return this.inner.count();
|
|
3883
3899
|
}
|
|
@@ -4160,12 +4176,24 @@ var InvertedIndex = class {
|
|
|
4160
4176
|
return this.inner.documentCount();
|
|
4161
4177
|
}
|
|
4162
4178
|
};
|
|
4163
|
-
var Database = class {
|
|
4179
|
+
var Database = class _Database {
|
|
4164
4180
|
inner;
|
|
4165
4181
|
closed = false;
|
|
4166
4182
|
constructor(dbPath) {
|
|
4167
4183
|
this.inner = new native.Database(dbPath);
|
|
4168
4184
|
}
|
|
4185
|
+
static fromNative(inner) {
|
|
4186
|
+
const database = Object.create(_Database.prototype);
|
|
4187
|
+
database.inner = inner;
|
|
4188
|
+
database.closed = false;
|
|
4189
|
+
return database;
|
|
4190
|
+
}
|
|
4191
|
+
static openReadOnly(dbPath) {
|
|
4192
|
+
return _Database.fromNative(native.Database.openReadOnly(dbPath));
|
|
4193
|
+
}
|
|
4194
|
+
static createEmptyReadOnly() {
|
|
4195
|
+
return _Database.fromNative(native.Database.createEmptyReadOnly());
|
|
4196
|
+
}
|
|
4169
4197
|
throwIfClosed() {
|
|
4170
4198
|
if (this.closed) {
|
|
4171
4199
|
throw new Error("Database is closed");
|
|
@@ -4634,6 +4662,9 @@ function getHostProjectIndexRelativePath(host) {
|
|
|
4634
4662
|
function hasHostProjectConfig(projectRoot, host) {
|
|
4635
4663
|
return existsSync5(path8.join(projectRoot, getProjectConfigRelativePath(host)));
|
|
4636
4664
|
}
|
|
4665
|
+
function hasHostGlobalConfig(host) {
|
|
4666
|
+
return existsSync5(getGlobalConfigPath(host));
|
|
4667
|
+
}
|
|
4637
4668
|
function getGlobalIndexPath(host = "opencode") {
|
|
4638
4669
|
switch (host) {
|
|
4639
4670
|
case "opencode":
|
|
@@ -4673,6 +4704,9 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
4673
4704
|
return hostIndexPath;
|
|
4674
4705
|
}
|
|
4675
4706
|
if (host !== "opencode") {
|
|
4707
|
+
if (hasHostGlobalConfig(host)) {
|
|
4708
|
+
return hostIndexPath;
|
|
4709
|
+
}
|
|
4676
4710
|
const legacyIndexPath = getGlobalIndexPath("opencode");
|
|
4677
4711
|
if (existsSync5(legacyIndexPath)) {
|
|
4678
4712
|
return legacyIndexPath;
|
|
@@ -4826,8 +4860,8 @@ function normalizeFiles(rawFiles, projectRoot) {
|
|
|
4826
4860
|
const trimmed = raw.trim();
|
|
4827
4861
|
if (!trimmed) continue;
|
|
4828
4862
|
const absolute = path9.resolve(projectRoot, trimmed);
|
|
4829
|
-
const
|
|
4830
|
-
const cleaned =
|
|
4863
|
+
const relative11 = path9.relative(projectRoot, absolute);
|
|
4864
|
+
const cleaned = relative11.startsWith("./") ? relative11.slice(2) : relative11;
|
|
4831
4865
|
if (!seen.has(cleaned)) {
|
|
4832
4866
|
seen.add(cleaned);
|
|
4833
4867
|
result.push(cleaned);
|
|
@@ -4836,8 +4870,487 @@ function normalizeFiles(rawFiles, projectRoot) {
|
|
|
4836
4870
|
return result;
|
|
4837
4871
|
}
|
|
4838
4872
|
|
|
4873
|
+
// src/indexer/git-blame.ts
|
|
4874
|
+
import { execFile as execFile2 } from "child_process";
|
|
4875
|
+
import * as path10 from "path";
|
|
4876
|
+
import { promisify as promisify2 } from "util";
|
|
4877
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
4878
|
+
function parseGitBlamePorcelain(output) {
|
|
4879
|
+
const commits = /* @__PURE__ */ new Map();
|
|
4880
|
+
let current;
|
|
4881
|
+
for (const line of output.split("\n")) {
|
|
4882
|
+
if (/^[0-9a-f]{40} /.test(line)) {
|
|
4883
|
+
const sha = line.slice(0, 40);
|
|
4884
|
+
current = commits.get(sha) ?? {
|
|
4885
|
+
sha,
|
|
4886
|
+
author: "",
|
|
4887
|
+
authorEmail: "",
|
|
4888
|
+
committedAt: 0,
|
|
4889
|
+
summary: "",
|
|
4890
|
+
lines: 0
|
|
4891
|
+
};
|
|
4892
|
+
commits.set(sha, current);
|
|
4893
|
+
continue;
|
|
4894
|
+
}
|
|
4895
|
+
if (!current) {
|
|
4896
|
+
continue;
|
|
4897
|
+
}
|
|
4898
|
+
if (line.startsWith("author ")) {
|
|
4899
|
+
current.author = line.slice("author ".length);
|
|
4900
|
+
} else if (line.startsWith("author-mail ")) {
|
|
4901
|
+
current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
|
|
4902
|
+
} else if (line.startsWith("author-time ")) {
|
|
4903
|
+
current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
|
|
4904
|
+
} else if (line.startsWith("summary ")) {
|
|
4905
|
+
current.summary = line.slice("summary ".length);
|
|
4906
|
+
} else if (line.startsWith(" ")) {
|
|
4907
|
+
current.lines += 1;
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
|
|
4911
|
+
}
|
|
4912
|
+
async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
|
|
4913
|
+
const relativePath = path10.relative(projectRoot, filePath);
|
|
4914
|
+
try {
|
|
4915
|
+
const { stdout } = await execFileAsync2(
|
|
4916
|
+
"git",
|
|
4917
|
+
["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
|
|
4918
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
4919
|
+
);
|
|
4920
|
+
return parseGitBlamePorcelain(stdout);
|
|
4921
|
+
} catch {
|
|
4922
|
+
return void 0;
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
// src/indexer/index-lock.ts
|
|
4927
|
+
import { randomUUID } from "crypto";
|
|
4928
|
+
import {
|
|
4929
|
+
existsSync as existsSync6,
|
|
4930
|
+
lstatSync,
|
|
4931
|
+
mkdirSync as mkdirSync2,
|
|
4932
|
+
readFileSync as readFileSync6,
|
|
4933
|
+
readdirSync as readdirSync2,
|
|
4934
|
+
realpathSync,
|
|
4935
|
+
renameSync,
|
|
4936
|
+
rmSync,
|
|
4937
|
+
writeFileSync as writeFileSync2
|
|
4938
|
+
} from "fs";
|
|
4939
|
+
import * as os4 from "os";
|
|
4940
|
+
import * as path11 from "path";
|
|
4941
|
+
var OWNER_FILE_NAME = "owner.json";
|
|
4942
|
+
var RECLAIM_DIRECTORY_NAME = "reclaiming";
|
|
4943
|
+
var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
|
|
4944
|
+
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;
|
|
4945
|
+
var VALID_OPERATIONS = /* @__PURE__ */ new Set([
|
|
4946
|
+
"initialize",
|
|
4947
|
+
"index",
|
|
4948
|
+
"force-index",
|
|
4949
|
+
"clear",
|
|
4950
|
+
"health-check",
|
|
4951
|
+
"retry-failed-batches",
|
|
4952
|
+
"recovery"
|
|
4953
|
+
]);
|
|
4954
|
+
var temporaryCounter = 0;
|
|
4955
|
+
function getErrorCode(error) {
|
|
4956
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
4957
|
+
}
|
|
4958
|
+
function retryTransientFilesystemOperation(operation) {
|
|
4959
|
+
let lastError;
|
|
4960
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4961
|
+
try {
|
|
4962
|
+
operation();
|
|
4963
|
+
return;
|
|
4964
|
+
} catch (error) {
|
|
4965
|
+
lastError = error;
|
|
4966
|
+
const code = getErrorCode(error);
|
|
4967
|
+
if (code !== "EBUSY" && code !== "EPERM") throw error;
|
|
4968
|
+
if (attempt < 2) {
|
|
4969
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
throw lastError;
|
|
4974
|
+
}
|
|
4975
|
+
function parseOwner(value) {
|
|
4976
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4977
|
+
const candidate = value;
|
|
4978
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4979
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4980
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4981
|
+
if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
|
|
4982
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4983
|
+
return candidate;
|
|
4984
|
+
}
|
|
4985
|
+
function parseReclaimOwner(value) {
|
|
4986
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4987
|
+
const candidate = value;
|
|
4988
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4989
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4990
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4991
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4992
|
+
if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
|
|
4993
|
+
return candidate;
|
|
4994
|
+
}
|
|
4995
|
+
function readJsonDirectory(directoryPath, parser) {
|
|
4996
|
+
try {
|
|
4997
|
+
return parser(JSON.parse(readFileSync6(path11.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
|
|
4998
|
+
} catch {
|
|
4999
|
+
return null;
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
function readDirectoryOwner(lockPath) {
|
|
5003
|
+
return readJsonDirectory(lockPath, parseOwner);
|
|
5004
|
+
}
|
|
5005
|
+
function readReclaimOwner(markerPath) {
|
|
5006
|
+
return readJsonDirectory(markerPath, parseReclaimOwner);
|
|
5007
|
+
}
|
|
5008
|
+
function readRecoveryOwner(markerPath) {
|
|
5009
|
+
return readDirectoryOwner(markerPath);
|
|
5010
|
+
}
|
|
5011
|
+
function readLegacyOwner(lockPath) {
|
|
5012
|
+
try {
|
|
5013
|
+
const parsed = JSON.parse(readFileSync6(lockPath, "utf-8"));
|
|
5014
|
+
if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
|
|
5015
|
+
if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
|
|
5016
|
+
return {
|
|
5017
|
+
pid: Number(parsed.pid),
|
|
5018
|
+
hostname: typeof parsed.hostname === "string" ? parsed.hostname : os4.hostname(),
|
|
5019
|
+
startedAt: parsed.startedAt,
|
|
5020
|
+
operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
|
|
5021
|
+
token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
|
|
5022
|
+
};
|
|
5023
|
+
} catch {
|
|
5024
|
+
return null;
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
function getOwnerLiveness(owner) {
|
|
5028
|
+
if (owner.hostname !== os4.hostname()) return "unknown";
|
|
5029
|
+
try {
|
|
5030
|
+
process.kill(owner.pid, 0);
|
|
5031
|
+
return "alive";
|
|
5032
|
+
} catch (error) {
|
|
5033
|
+
const code = getErrorCode(error);
|
|
5034
|
+
if (code === "ESRCH") return "dead";
|
|
5035
|
+
if (code === "EPERM") return "alive";
|
|
5036
|
+
return "unknown";
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
function sameOwner(left, right) {
|
|
5040
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
|
|
5041
|
+
}
|
|
5042
|
+
function sameReclaimOwner(left, right) {
|
|
5043
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
|
|
5044
|
+
}
|
|
5045
|
+
function publishJsonDirectory(finalPath, value) {
|
|
5046
|
+
const candidatePath = `${finalPath}.candidate.${process.pid}.${randomUUID()}`;
|
|
5047
|
+
mkdirSync2(candidatePath, { mode: 448 });
|
|
5048
|
+
try {
|
|
5049
|
+
writeFileSync2(path11.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
|
|
5050
|
+
encoding: "utf-8",
|
|
5051
|
+
flag: "wx",
|
|
5052
|
+
mode: 384
|
|
5053
|
+
});
|
|
5054
|
+
if (existsSync6(finalPath)) return false;
|
|
5055
|
+
try {
|
|
5056
|
+
renameSync(candidatePath, finalPath);
|
|
5057
|
+
return true;
|
|
5058
|
+
} catch (error) {
|
|
5059
|
+
if (existsSync6(finalPath)) return false;
|
|
5060
|
+
throw error;
|
|
5061
|
+
}
|
|
5062
|
+
} finally {
|
|
5063
|
+
if (existsSync6(candidatePath)) rmSync(candidatePath, { recursive: true, force: true });
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
function createOwner(operation) {
|
|
5067
|
+
return {
|
|
5068
|
+
pid: process.pid,
|
|
5069
|
+
hostname: os4.hostname(),
|
|
5070
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5071
|
+
operation,
|
|
5072
|
+
token: randomUUID()
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
function recoveryMarkerPath(indexPath, owner) {
|
|
5076
|
+
return path11.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
|
|
5077
|
+
}
|
|
5078
|
+
function publishRecoveryMarker(indexPath, owner) {
|
|
5079
|
+
const markerPath = recoveryMarkerPath(indexPath, owner);
|
|
5080
|
+
if (!publishJsonDirectory(markerPath, owner)) {
|
|
5081
|
+
const markerOwner = readRecoveryOwner(markerPath);
|
|
5082
|
+
if (!markerOwner || !sameOwner(markerOwner, owner)) {
|
|
5083
|
+
throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
return markerPath;
|
|
5087
|
+
}
|
|
5088
|
+
function getPendingRecoveries(indexPath) {
|
|
5089
|
+
const recoveries = [];
|
|
5090
|
+
const markerNames = readdirSync2(indexPath).filter((name) => {
|
|
5091
|
+
if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
|
|
5092
|
+
return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
|
|
5093
|
+
}).sort();
|
|
5094
|
+
for (const markerName of markerNames) {
|
|
5095
|
+
const markerPath = path11.join(indexPath, markerName);
|
|
5096
|
+
const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
|
|
5097
|
+
let markerStats;
|
|
5098
|
+
try {
|
|
5099
|
+
markerStats = lstatSync(markerPath);
|
|
5100
|
+
} catch (error) {
|
|
5101
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5102
|
+
throw error;
|
|
5103
|
+
}
|
|
5104
|
+
if (!markerStats.isDirectory()) {
|
|
5105
|
+
throw new IndexLockContentionError(markerPath, null, "unknown-owner");
|
|
5106
|
+
}
|
|
5107
|
+
const owner = readRecoveryOwner(markerPath);
|
|
5108
|
+
if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
|
|
5109
|
+
throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
|
|
5110
|
+
}
|
|
5111
|
+
recoveries.push({ owner, markerPath });
|
|
5112
|
+
}
|
|
5113
|
+
recoveries.sort((left, right) => {
|
|
5114
|
+
const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
|
|
5115
|
+
return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
|
|
5116
|
+
});
|
|
5117
|
+
return recoveries;
|
|
5118
|
+
}
|
|
5119
|
+
function cleanupDeadPublicationCandidates(indexPath) {
|
|
5120
|
+
const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
|
|
5121
|
+
for (const entry of readdirSync2(indexPath)) {
|
|
5122
|
+
const match = candidatePattern.exec(entry);
|
|
5123
|
+
if (!match) continue;
|
|
5124
|
+
const pid = Number(match[1]);
|
|
5125
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
5126
|
+
if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
|
|
5127
|
+
rmSync(path11.join(indexPath, entry), { recursive: true, force: true });
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
function removeDeadReclaimMarker(lockPath, expectedOwner) {
|
|
5131
|
+
const markerPath = path11.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5132
|
+
const marker = readReclaimOwner(markerPath);
|
|
5133
|
+
if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
|
|
5134
|
+
return false;
|
|
5135
|
+
}
|
|
5136
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5137
|
+
if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5138
|
+
return false;
|
|
5139
|
+
}
|
|
5140
|
+
const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${randomUUID()}`;
|
|
5141
|
+
try {
|
|
5142
|
+
renameSync(markerPath, claimedMarkerPath);
|
|
5143
|
+
} catch (error) {
|
|
5144
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5145
|
+
throw error;
|
|
5146
|
+
}
|
|
5147
|
+
const claimedMarker = readReclaimOwner(claimedMarkerPath);
|
|
5148
|
+
const ownerAfterClaim = readDirectoryOwner(lockPath);
|
|
5149
|
+
if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
|
|
5150
|
+
if (!existsSync6(markerPath) && existsSync6(claimedMarkerPath)) {
|
|
5151
|
+
renameSync(claimedMarkerPath, markerPath);
|
|
5152
|
+
}
|
|
5153
|
+
return false;
|
|
5154
|
+
}
|
|
5155
|
+
rmSync(claimedMarkerPath, { recursive: true, force: true });
|
|
5156
|
+
return true;
|
|
5157
|
+
}
|
|
5158
|
+
function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
|
|
5159
|
+
const reclaimPath = path11.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5160
|
+
const reclaimOwner = {
|
|
5161
|
+
pid: process.pid,
|
|
5162
|
+
hostname: os4.hostname(),
|
|
5163
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5164
|
+
token: randomUUID(),
|
|
5165
|
+
expectedOwnerToken: expectedOwner.token
|
|
5166
|
+
};
|
|
5167
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
5168
|
+
if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
|
|
5169
|
+
if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
|
|
5170
|
+
return false;
|
|
5171
|
+
}
|
|
5172
|
+
try {
|
|
5173
|
+
const currentReclaimer = readReclaimOwner(reclaimPath);
|
|
5174
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5175
|
+
if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5176
|
+
return false;
|
|
5177
|
+
}
|
|
5178
|
+
publishRecoveryMarker(indexPath, expectedOwner);
|
|
5179
|
+
const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
|
|
5180
|
+
const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
|
|
5181
|
+
if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
|
|
5182
|
+
return false;
|
|
5183
|
+
}
|
|
5184
|
+
const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
|
|
5185
|
+
renameSync(lockPath, quarantinePath);
|
|
5186
|
+
rmSync(quarantinePath, { recursive: true, force: true });
|
|
5187
|
+
return true;
|
|
5188
|
+
} catch (error) {
|
|
5189
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5190
|
+
throw error;
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
5193
|
+
var IndexLockContentionError = class extends Error {
|
|
5194
|
+
constructor(lockPath, owner, reason) {
|
|
5195
|
+
const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
|
|
5196
|
+
super(`Index mutation already in progress: ${ownerDescription}`);
|
|
5197
|
+
this.lockPath = lockPath;
|
|
5198
|
+
this.owner = owner;
|
|
5199
|
+
this.reason = reason;
|
|
5200
|
+
this.name = "IndexLockContentionError";
|
|
5201
|
+
}
|
|
5202
|
+
lockPath;
|
|
5203
|
+
owner;
|
|
5204
|
+
reason;
|
|
5205
|
+
code = "INDEX_BUSY";
|
|
5206
|
+
};
|
|
5207
|
+
function isIndexLockContentionError(error) {
|
|
5208
|
+
return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
|
|
5209
|
+
}
|
|
5210
|
+
function isTransientIndexLockContention(error) {
|
|
5211
|
+
if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
|
|
5212
|
+
return error.reason === "active" || error.reason === "reclaiming";
|
|
5213
|
+
}
|
|
5214
|
+
function acquireIndexLock(indexPath, operation) {
|
|
5215
|
+
mkdirSync2(indexPath, { recursive: true });
|
|
5216
|
+
const canonicalIndexPath = realpathSync.native(indexPath);
|
|
5217
|
+
const lockPath = path11.join(canonicalIndexPath, "indexing.lock");
|
|
5218
|
+
cleanupDeadPublicationCandidates(canonicalIndexPath);
|
|
5219
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
5220
|
+
const owner = createOwner(operation);
|
|
5221
|
+
if (publishJsonDirectory(lockPath, owner)) {
|
|
5222
|
+
const lease = {
|
|
5223
|
+
canonicalIndexPath,
|
|
5224
|
+
lockPath,
|
|
5225
|
+
owner,
|
|
5226
|
+
recoveries: []
|
|
5227
|
+
};
|
|
5228
|
+
try {
|
|
5229
|
+
lease.recoveries = getPendingRecoveries(canonicalIndexPath);
|
|
5230
|
+
return lease;
|
|
5231
|
+
} catch (error) {
|
|
5232
|
+
releaseIndexLock(lease);
|
|
5233
|
+
throw error;
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
let stats;
|
|
5237
|
+
try {
|
|
5238
|
+
stats = lstatSync(lockPath);
|
|
5239
|
+
} catch (error) {
|
|
5240
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5241
|
+
throw error;
|
|
5242
|
+
}
|
|
5243
|
+
if (!stats.isDirectory()) {
|
|
5244
|
+
const legacyOwner = readLegacyOwner(lockPath);
|
|
5245
|
+
throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
|
|
5246
|
+
}
|
|
5247
|
+
const existingOwner = readDirectoryOwner(lockPath);
|
|
5248
|
+
if (!existingOwner) {
|
|
5249
|
+
throw new IndexLockContentionError(lockPath, null, "unknown-owner");
|
|
5250
|
+
}
|
|
5251
|
+
const liveness = getOwnerLiveness(existingOwner);
|
|
5252
|
+
if (liveness !== "dead") {
|
|
5253
|
+
throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
|
|
5254
|
+
}
|
|
5255
|
+
if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
|
|
5256
|
+
throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
throw new IndexLockContentionError(lockPath, null, "reclaiming");
|
|
5260
|
+
}
|
|
5261
|
+
function releaseIndexLock(lease) {
|
|
5262
|
+
const currentOwner = readDirectoryOwner(lease.lockPath);
|
|
5263
|
+
if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
|
|
5264
|
+
const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
|
|
5265
|
+
try {
|
|
5266
|
+
retryTransientFilesystemOperation(() => renameSync(lease.lockPath, releasePath));
|
|
5267
|
+
} catch (error) {
|
|
5268
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5269
|
+
throw error;
|
|
5270
|
+
}
|
|
5271
|
+
const claimedOwner = readDirectoryOwner(releasePath);
|
|
5272
|
+
if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
|
|
5273
|
+
if (!existsSync6(lease.lockPath) && existsSync6(releasePath)) {
|
|
5274
|
+
renameSync(releasePath, lease.lockPath);
|
|
5275
|
+
}
|
|
5276
|
+
return false;
|
|
5277
|
+
}
|
|
5278
|
+
try {
|
|
5279
|
+
retryTransientFilesystemOperation(() => rmSync(releasePath, { recursive: true, force: true }));
|
|
5280
|
+
} catch (error) {
|
|
5281
|
+
console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
|
|
5282
|
+
}
|
|
5283
|
+
return true;
|
|
5284
|
+
}
|
|
5285
|
+
async function withIndexLock(indexPath, operation, callback, options = {}) {
|
|
5286
|
+
const lease = acquireIndexLock(indexPath, operation);
|
|
5287
|
+
let result;
|
|
5288
|
+
let callbackError;
|
|
5289
|
+
let callbackFailed = false;
|
|
5290
|
+
try {
|
|
5291
|
+
result = await callback(lease);
|
|
5292
|
+
} catch (error) {
|
|
5293
|
+
callbackFailed = true;
|
|
5294
|
+
callbackError = error;
|
|
5295
|
+
}
|
|
5296
|
+
if (!callbackFailed && options.completeRecoveries !== false) {
|
|
5297
|
+
try {
|
|
5298
|
+
completeLeaseRecovery(lease);
|
|
5299
|
+
} catch (error) {
|
|
5300
|
+
callbackFailed = true;
|
|
5301
|
+
callbackError = error;
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
let releaseError;
|
|
5305
|
+
try {
|
|
5306
|
+
if (!releaseIndexLock(lease)) {
|
|
5307
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
5308
|
+
}
|
|
5309
|
+
} catch (error) {
|
|
5310
|
+
releaseError = error;
|
|
5311
|
+
}
|
|
5312
|
+
if (releaseError !== void 0) {
|
|
5313
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
5314
|
+
throw releaseError;
|
|
5315
|
+
}
|
|
5316
|
+
if (callbackFailed) throw callbackError;
|
|
5317
|
+
return result;
|
|
5318
|
+
}
|
|
5319
|
+
function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
|
|
5320
|
+
if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
|
|
5321
|
+
temporaryCounter += 1;
|
|
5322
|
+
return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
|
|
5323
|
+
}
|
|
5324
|
+
function removeLeaseTemporaryPath(temporaryPath) {
|
|
5325
|
+
if (existsSync6(temporaryPath)) rmSync(temporaryPath, { recursive: true, force: true });
|
|
5326
|
+
}
|
|
5327
|
+
function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
|
|
5328
|
+
for (const targetPath of backupTargets) {
|
|
5329
|
+
const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
|
|
5330
|
+
if (!existsSync6(backupPath)) continue;
|
|
5331
|
+
if (existsSync6(targetPath)) rmSync(targetPath, { recursive: true, force: true });
|
|
5332
|
+
renameSync(backupPath, targetPath);
|
|
5333
|
+
}
|
|
5334
|
+
const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
|
|
5335
|
+
for (const entry of readdirSync2(indexPath)) {
|
|
5336
|
+
if (!entry.includes(temporaryOwnerMarker)) continue;
|
|
5337
|
+
rmSync(path11.join(indexPath, entry), { recursive: true, force: true });
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
function completeLeaseRecovery(lease) {
|
|
5341
|
+
for (const recovery of lease.recoveries) {
|
|
5342
|
+
const markerOwner = readRecoveryOwner(recovery.markerPath);
|
|
5343
|
+
if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
|
|
5344
|
+
throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
5347
|
+
for (const recovery of lease.recoveries) {
|
|
5348
|
+
rmSync(recovery.markerPath, { recursive: true, force: true });
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
|
|
4839
5352
|
// src/indexer/index.ts
|
|
4840
|
-
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
5353
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
|
|
4841
5354
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
4842
5355
|
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
4843
5356
|
"function_declaration",
|
|
@@ -4917,6 +5430,46 @@ function isSqliteCorruptionError(error) {
|
|
|
4917
5430
|
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");
|
|
4918
5431
|
}
|
|
4919
5432
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
5433
|
+
var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
|
|
5434
|
+
function metadataFromBlame(blame) {
|
|
5435
|
+
if (!blame) {
|
|
5436
|
+
return {};
|
|
5437
|
+
}
|
|
5438
|
+
return {
|
|
5439
|
+
blameSha: blame.sha,
|
|
5440
|
+
blameAuthor: blame.author,
|
|
5441
|
+
blameAuthorEmail: blame.authorEmail,
|
|
5442
|
+
blameCommittedAt: blame.committedAt,
|
|
5443
|
+
blameSummary: blame.summary
|
|
5444
|
+
};
|
|
5445
|
+
}
|
|
5446
|
+
function blameFromChunkData(chunk) {
|
|
5447
|
+
if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
|
|
5448
|
+
return void 0;
|
|
5449
|
+
}
|
|
5450
|
+
return {
|
|
5451
|
+
sha: chunk.blameSha,
|
|
5452
|
+
author: chunk.blameAuthor,
|
|
5453
|
+
authorEmail: chunk.blameAuthorEmail,
|
|
5454
|
+
committedAt: chunk.blameCommittedAt,
|
|
5455
|
+
summary: chunk.blameSummary
|
|
5456
|
+
};
|
|
5457
|
+
}
|
|
5458
|
+
function blameFromMetadata(metadata) {
|
|
5459
|
+
if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
|
|
5460
|
+
return void 0;
|
|
5461
|
+
}
|
|
5462
|
+
return {
|
|
5463
|
+
sha: metadata.blameSha,
|
|
5464
|
+
author: metadata.blameAuthor,
|
|
5465
|
+
authorEmail: metadata.blameAuthorEmail,
|
|
5466
|
+
committedAt: metadata.blameCommittedAt,
|
|
5467
|
+
summary: metadata.blameSummary
|
|
5468
|
+
};
|
|
5469
|
+
}
|
|
5470
|
+
function hasBlameMetadata(metadata) {
|
|
5471
|
+
return blameFromMetadata(metadata) !== void 0;
|
|
5472
|
+
}
|
|
4920
5473
|
var INDEX_METADATA_VERSION = "1";
|
|
4921
5474
|
var EMBEDDING_STRATEGY_VERSION = "2";
|
|
4922
5475
|
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
@@ -5075,9 +5628,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
5075
5628
|
return true;
|
|
5076
5629
|
}
|
|
5077
5630
|
function isPathWithinRoot(filePath, rootPath) {
|
|
5078
|
-
const normalizedFilePath =
|
|
5079
|
-
const normalizedRoot =
|
|
5080
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
5631
|
+
const normalizedFilePath = path12.resolve(filePath);
|
|
5632
|
+
const normalizedRoot = path12.resolve(rootPath);
|
|
5633
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
|
|
5081
5634
|
}
|
|
5082
5635
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
5083
5636
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5836,7 +6389,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
|
|
|
5836
6389
|
chunkType,
|
|
5837
6390
|
name: chunk.name ?? void 0,
|
|
5838
6391
|
language: chunk.language,
|
|
5839
|
-
hash: chunk.contentHash
|
|
6392
|
+
hash: chunk.contentHash,
|
|
6393
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5840
6394
|
};
|
|
5841
6395
|
const baselineScore = existing?.score ?? 0.5;
|
|
5842
6396
|
candidateUnion.set(chunk.chunkId, {
|
|
@@ -5920,7 +6474,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
|
|
|
5920
6474
|
chunkType,
|
|
5921
6475
|
name: chunk.name ?? void 0,
|
|
5922
6476
|
language: chunk.language,
|
|
5923
|
-
hash: chunk.contentHash
|
|
6477
|
+
hash: chunk.contentHash,
|
|
6478
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5924
6479
|
}
|
|
5925
6480
|
});
|
|
5926
6481
|
}
|
|
@@ -6089,6 +6644,21 @@ function matchesSearchFilters(candidate, options, minScore) {
|
|
|
6089
6644
|
if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
|
|
6090
6645
|
return false;
|
|
6091
6646
|
}
|
|
6647
|
+
if (options?.blameAuthor) {
|
|
6648
|
+
const author = options.blameAuthor.toLowerCase();
|
|
6649
|
+
const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
|
|
6650
|
+
const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
|
|
6651
|
+
if (candidateAuthor !== author && candidateEmail !== author) return false;
|
|
6652
|
+
}
|
|
6653
|
+
if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
|
|
6654
|
+
return false;
|
|
6655
|
+
}
|
|
6656
|
+
if (options?.blameSince) {
|
|
6657
|
+
const sinceMs = Date.parse(options.blameSince);
|
|
6658
|
+
if (Number.isNaN(sinceMs)) return false;
|
|
6659
|
+
const committedAt = candidate.metadata.blameCommittedAt;
|
|
6660
|
+
if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
|
|
6661
|
+
}
|
|
6092
6662
|
return true;
|
|
6093
6663
|
}
|
|
6094
6664
|
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
@@ -6126,26 +6696,133 @@ var Indexer = class {
|
|
|
6126
6696
|
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
6127
6697
|
querySimilarityThreshold = 0.85;
|
|
6128
6698
|
indexCompatibility = null;
|
|
6129
|
-
|
|
6699
|
+
activeIndexLease = null;
|
|
6700
|
+
initializationPromise = null;
|
|
6701
|
+
initializationMode = "none";
|
|
6702
|
+
readIssues = [];
|
|
6703
|
+
retiredDatabases = [];
|
|
6704
|
+
readerArtifactFingerprint = null;
|
|
6705
|
+
writerArtifactFingerprint = null;
|
|
6706
|
+
readerArtifactRetryAfter = /* @__PURE__ */ new Map();
|
|
6130
6707
|
constructor(projectRoot, config, host = "opencode") {
|
|
6131
6708
|
this.projectRoot = projectRoot;
|
|
6132
6709
|
this.config = config;
|
|
6133
6710
|
this.host = host;
|
|
6134
6711
|
this.indexPath = this.getIndexPath();
|
|
6135
|
-
this.fileHashCachePath =
|
|
6136
|
-
this.failedBatchesPath =
|
|
6137
|
-
this.indexingLockPath = path10.join(this.indexPath, "indexing.lock");
|
|
6712
|
+
this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
|
|
6713
|
+
this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
|
|
6138
6714
|
this.logger = initializeLogger(config.debug);
|
|
6139
6715
|
}
|
|
6140
6716
|
getIndexPath() {
|
|
6141
6717
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
|
|
6142
6718
|
}
|
|
6719
|
+
isLocalProjectIndexPath() {
|
|
6720
|
+
const localProjectIndexPaths = [path12.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
|
|
6721
|
+
if (this.host !== "opencode") {
|
|
6722
|
+
localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
6723
|
+
}
|
|
6724
|
+
return localProjectIndexPaths.some((localPath) => {
|
|
6725
|
+
if (!existsSync7(localPath) || !existsSync7(this.indexPath)) {
|
|
6726
|
+
return path12.resolve(this.indexPath) === path12.resolve(localPath);
|
|
6727
|
+
}
|
|
6728
|
+
const indexStats = statSync3(this.indexPath);
|
|
6729
|
+
const localStats = statSync3(localPath);
|
|
6730
|
+
return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
|
|
6731
|
+
});
|
|
6732
|
+
}
|
|
6733
|
+
resetLoadedIndexState(retireDatabase = false) {
|
|
6734
|
+
if (this.database) {
|
|
6735
|
+
if (retireDatabase) {
|
|
6736
|
+
this.retiredDatabases.push(this.database);
|
|
6737
|
+
} else {
|
|
6738
|
+
this.database.close();
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
this.store = null;
|
|
6742
|
+
this.invertedIndex = null;
|
|
6743
|
+
this.database = null;
|
|
6744
|
+
this.provider = null;
|
|
6745
|
+
this.configuredProviderInfo = null;
|
|
6746
|
+
this.reranker = null;
|
|
6747
|
+
this.indexCompatibility = null;
|
|
6748
|
+
this.initializationMode = "none";
|
|
6749
|
+
this.readIssues = [];
|
|
6750
|
+
this.readerArtifactFingerprint = null;
|
|
6751
|
+
this.writerArtifactFingerprint = null;
|
|
6752
|
+
this.readerArtifactRetryAfter.clear();
|
|
6753
|
+
this.fileHashCache.clear();
|
|
6754
|
+
}
|
|
6755
|
+
refreshLoadedIndexState() {
|
|
6756
|
+
if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
|
|
6757
|
+
this.store.load();
|
|
6758
|
+
this.invertedIndex.load();
|
|
6759
|
+
this.fileHashCache.clear();
|
|
6760
|
+
this.loadFileHashCache();
|
|
6761
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
6762
|
+
this.readIssues = [];
|
|
6763
|
+
this.readerArtifactRetryAfter.clear();
|
|
6764
|
+
}
|
|
6765
|
+
async withIndexMutationLease(operation, callback) {
|
|
6766
|
+
const lease = acquireIndexLock(this.indexPath, operation);
|
|
6767
|
+
this.indexPath = lease.canonicalIndexPath;
|
|
6768
|
+
this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
|
|
6769
|
+
this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
|
|
6770
|
+
this.activeIndexLease = lease;
|
|
6771
|
+
let result;
|
|
6772
|
+
let callbackError;
|
|
6773
|
+
let callbackFailed = false;
|
|
6774
|
+
try {
|
|
6775
|
+
result = await callback(lease.recoveries.map(({ owner }) => owner));
|
|
6776
|
+
} catch (error) {
|
|
6777
|
+
callbackFailed = true;
|
|
6778
|
+
callbackError = error;
|
|
6779
|
+
}
|
|
6780
|
+
if (!callbackFailed) {
|
|
6781
|
+
try {
|
|
6782
|
+
completeLeaseRecovery(lease);
|
|
6783
|
+
this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
|
|
6784
|
+
} catch (error) {
|
|
6785
|
+
callbackFailed = true;
|
|
6786
|
+
callbackError = error;
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
let releaseError;
|
|
6790
|
+
try {
|
|
6791
|
+
if (!releaseIndexLock(lease)) {
|
|
6792
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
6793
|
+
this.writerArtifactFingerprint = null;
|
|
6794
|
+
if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6795
|
+
this.activeIndexLease = null;
|
|
6796
|
+
}
|
|
6797
|
+
} else if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6798
|
+
this.activeIndexLease = null;
|
|
6799
|
+
}
|
|
6800
|
+
} catch (error) {
|
|
6801
|
+
releaseError = error;
|
|
6802
|
+
this.writerArtifactFingerprint = null;
|
|
6803
|
+
if (!existsSync7(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6804
|
+
this.activeIndexLease = null;
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
if (releaseError !== void 0) {
|
|
6808
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
6809
|
+
throw releaseError;
|
|
6810
|
+
}
|
|
6811
|
+
if (callbackFailed) throw callbackError;
|
|
6812
|
+
return result;
|
|
6813
|
+
}
|
|
6814
|
+
requireActiveLease() {
|
|
6815
|
+
if (!this.activeIndexLease) {
|
|
6816
|
+
throw new Error("Index mutation attempted without an active interprocess lease");
|
|
6817
|
+
}
|
|
6818
|
+
return this.activeIndexLease;
|
|
6819
|
+
}
|
|
6143
6820
|
loadFileHashCache() {
|
|
6144
|
-
if (!
|
|
6821
|
+
if (!existsSync7(this.fileHashCachePath)) {
|
|
6145
6822
|
return;
|
|
6146
6823
|
}
|
|
6147
6824
|
try {
|
|
6148
|
-
const data =
|
|
6825
|
+
const data = readFileSync7(this.fileHashCachePath, "utf-8");
|
|
6149
6826
|
const parsed = JSON.parse(data);
|
|
6150
6827
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
6151
6828
|
} catch (error) {
|
|
@@ -6165,15 +6842,26 @@ var Indexer = class {
|
|
|
6165
6842
|
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
6166
6843
|
}
|
|
6167
6844
|
atomicWriteSync(targetPath, data) {
|
|
6168
|
-
const
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6845
|
+
const lease = this.requireActiveLease();
|
|
6846
|
+
const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
|
|
6847
|
+
mkdirSync3(path12.dirname(targetPath), { recursive: true });
|
|
6848
|
+
try {
|
|
6849
|
+
writeFileSync3(tempPath, data);
|
|
6850
|
+
renameSync2(tempPath, targetPath);
|
|
6851
|
+
} finally {
|
|
6852
|
+
removeLeaseTemporaryPath(tempPath);
|
|
6853
|
+
}
|
|
6854
|
+
}
|
|
6855
|
+
saveInvertedIndex(invertedIndex) {
|
|
6856
|
+
this.atomicWriteSync(
|
|
6857
|
+
path12.join(this.indexPath, "inverted-index.json"),
|
|
6858
|
+
invertedIndex.serialize()
|
|
6859
|
+
);
|
|
6172
6860
|
}
|
|
6173
6861
|
getScopedRoots() {
|
|
6174
|
-
const roots = /* @__PURE__ */ new Set([
|
|
6862
|
+
const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
|
|
6175
6863
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
6176
|
-
roots.add(
|
|
6864
|
+
roots.add(path12.resolve(this.projectRoot, kbRoot));
|
|
6177
6865
|
}
|
|
6178
6866
|
return Array.from(roots);
|
|
6179
6867
|
}
|
|
@@ -6182,29 +6870,29 @@ var Indexer = class {
|
|
|
6182
6870
|
if (this.config.scope !== "global") {
|
|
6183
6871
|
return branchName;
|
|
6184
6872
|
}
|
|
6185
|
-
const projectHash = hashContent(
|
|
6873
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6186
6874
|
return `${projectHash}:${branchName}`;
|
|
6187
6875
|
}
|
|
6188
6876
|
getBranchCatalogKeyFor(branchName) {
|
|
6189
6877
|
if (this.config.scope !== "global") {
|
|
6190
6878
|
return branchName;
|
|
6191
6879
|
}
|
|
6192
|
-
const projectHash = hashContent(
|
|
6880
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6193
6881
|
return `${projectHash}:${branchName}`;
|
|
6194
6882
|
}
|
|
6195
6883
|
getLegacyBranchCatalogKey() {
|
|
6196
6884
|
return this.currentBranch || "default";
|
|
6197
6885
|
}
|
|
6198
6886
|
getLegacyMigrationMetadataKey() {
|
|
6199
|
-
const projectHash = hashContent(
|
|
6887
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6200
6888
|
return `index.globalBranchMigration.${projectHash}`;
|
|
6201
6889
|
}
|
|
6202
6890
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
6203
|
-
const projectHash = hashContent(
|
|
6891
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6204
6892
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
6205
6893
|
}
|
|
6206
6894
|
getProjectForceReembedMetadataKey() {
|
|
6207
|
-
const projectHash = hashContent(
|
|
6895
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6208
6896
|
return `index.forceReembed.${projectHash}`;
|
|
6209
6897
|
}
|
|
6210
6898
|
hasProjectForceReembedPending() {
|
|
@@ -6298,7 +6986,7 @@ var Indexer = class {
|
|
|
6298
6986
|
if (!this.database) {
|
|
6299
6987
|
return { chunkIds, symbolIds };
|
|
6300
6988
|
}
|
|
6301
|
-
const projectRootPath =
|
|
6989
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6302
6990
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6303
6991
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6304
6992
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6321,7 +7009,7 @@ var Indexer = class {
|
|
|
6321
7009
|
if (this.config.scope !== "global") {
|
|
6322
7010
|
return this.getBranchCatalogCleanupKeys();
|
|
6323
7011
|
}
|
|
6324
|
-
const projectHash = hashContent(
|
|
7012
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6325
7013
|
const keys = /* @__PURE__ */ new Set();
|
|
6326
7014
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6327
7015
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6405,7 +7093,7 @@ var Indexer = class {
|
|
|
6405
7093
|
if (!this.database || this.config.scope !== "global") {
|
|
6406
7094
|
return false;
|
|
6407
7095
|
}
|
|
6408
|
-
const projectHash = hashContent(
|
|
7096
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6409
7097
|
const roots = this.getScopedRoots();
|
|
6410
7098
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6411
7099
|
return this.database.getAllBranches().some(
|
|
@@ -6439,7 +7127,7 @@ var Indexer = class {
|
|
|
6439
7127
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6440
7128
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6441
7129
|
]);
|
|
6442
|
-
const projectRootPath =
|
|
7130
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6443
7131
|
const projectLocalFilePaths = new Set(
|
|
6444
7132
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6445
7133
|
);
|
|
@@ -6501,34 +7189,27 @@ var Indexer = class {
|
|
|
6501
7189
|
database.gcOrphanEmbeddings();
|
|
6502
7190
|
database.gcOrphanChunks();
|
|
6503
7191
|
store.save();
|
|
6504
|
-
|
|
7192
|
+
this.saveInvertedIndex(invertedIndex);
|
|
6505
7193
|
return {
|
|
6506
7194
|
removedChunkIds: removedChunkIdList,
|
|
6507
7195
|
hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
|
|
6508
7196
|
};
|
|
6509
7197
|
}
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
|
|
6519
|
-
}
|
|
6520
|
-
releaseIndexingLock() {
|
|
6521
|
-
if (existsSync6(this.indexingLockPath)) {
|
|
6522
|
-
unlinkSync(this.indexingLockPath);
|
|
7198
|
+
async recoverFromInterruptedIndexingUnlocked(owners) {
|
|
7199
|
+
for (const owner of owners) {
|
|
7200
|
+
this.logger.warn("Detected interrupted indexing session, recovering...", {
|
|
7201
|
+
pid: owner.pid,
|
|
7202
|
+
hostname: owner.hostname,
|
|
7203
|
+
operation: owner.operation,
|
|
7204
|
+
startedAt: owner.startedAt
|
|
7205
|
+
});
|
|
6523
7206
|
}
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
7207
|
+
if (this.config.scope === "global") {
|
|
7208
|
+
if (existsSync7(this.fileHashCachePath)) {
|
|
7209
|
+
unlinkSync(this.fileHashCachePath);
|
|
7210
|
+
}
|
|
7211
|
+
await this.healthCheckUnlocked();
|
|
6529
7212
|
}
|
|
6530
|
-
await this.healthCheck();
|
|
6531
|
-
this.releaseIndexingLock();
|
|
6532
7213
|
this.logger.info("Recovery complete, next index will re-process all files");
|
|
6533
7214
|
}
|
|
6534
7215
|
loadFailedBatches(maxChunkTokens) {
|
|
@@ -6544,10 +7225,10 @@ var Indexer = class {
|
|
|
6544
7225
|
}
|
|
6545
7226
|
}
|
|
6546
7227
|
loadSerializedFailedBatches() {
|
|
6547
|
-
if (!
|
|
7228
|
+
if (!existsSync7(this.failedBatchesPath)) {
|
|
6548
7229
|
return [];
|
|
6549
7230
|
}
|
|
6550
|
-
const data =
|
|
7231
|
+
const data = readFileSync7(this.failedBatchesPath, "utf-8");
|
|
6551
7232
|
const parsed = JSON.parse(data);
|
|
6552
7233
|
return parsed.map((batch) => {
|
|
6553
7234
|
const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
|
|
@@ -6564,7 +7245,7 @@ var Indexer = class {
|
|
|
6564
7245
|
}
|
|
6565
7246
|
saveFailedBatches(batches) {
|
|
6566
7247
|
if (batches.length === 0) {
|
|
6567
|
-
if (
|
|
7248
|
+
if (existsSync7(this.failedBatchesPath)) {
|
|
6568
7249
|
try {
|
|
6569
7250
|
unlinkSync(this.failedBatchesPath);
|
|
6570
7251
|
} catch {
|
|
@@ -6572,7 +7253,7 @@ var Indexer = class {
|
|
|
6572
7253
|
}
|
|
6573
7254
|
return;
|
|
6574
7255
|
}
|
|
6575
|
-
|
|
7256
|
+
this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
6576
7257
|
}
|
|
6577
7258
|
collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
|
|
6578
7259
|
const retryableById = /* @__PURE__ */ new Map();
|
|
@@ -6763,9 +7444,225 @@ var Indexer = class {
|
|
|
6763
7444
|
parts.push("snippet:");
|
|
6764
7445
|
parts.push("[unavailable]");
|
|
6765
7446
|
}
|
|
6766
|
-
return parts.join("\n");
|
|
7447
|
+
return parts.join("\n");
|
|
7448
|
+
}
|
|
7449
|
+
async initialize() {
|
|
7450
|
+
if (this.initializationPromise) {
|
|
7451
|
+
await this.initializationPromise;
|
|
7452
|
+
}
|
|
7453
|
+
if (this.isInitializedFor("reader")) {
|
|
7454
|
+
return;
|
|
7455
|
+
}
|
|
7456
|
+
await this.initializeOnce("reader", [], { skipAutoGc: true });
|
|
7457
|
+
}
|
|
7458
|
+
async initializeOnce(mode, recoveredOwners, options) {
|
|
7459
|
+
if (this.initializationPromise) {
|
|
7460
|
+
await this.initializationPromise;
|
|
7461
|
+
if (this.isInitializedFor(mode)) {
|
|
7462
|
+
return;
|
|
7463
|
+
}
|
|
7464
|
+
return this.initializeOnce(mode, recoveredOwners, options);
|
|
7465
|
+
}
|
|
7466
|
+
if (this.isInitializedFor(mode)) {
|
|
7467
|
+
return;
|
|
7468
|
+
}
|
|
7469
|
+
const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
|
|
7470
|
+
this.resetLoadedIndexState();
|
|
7471
|
+
throw error;
|
|
7472
|
+
}).finally(() => {
|
|
7473
|
+
if (this.initializationPromise === initialization) {
|
|
7474
|
+
this.initializationPromise = null;
|
|
7475
|
+
}
|
|
7476
|
+
});
|
|
7477
|
+
this.initializationPromise = initialization;
|
|
7478
|
+
await initialization;
|
|
7479
|
+
}
|
|
7480
|
+
isInitializedFor(mode) {
|
|
7481
|
+
const hasState = Boolean(
|
|
7482
|
+
this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
|
|
7483
|
+
);
|
|
7484
|
+
if (!hasState) {
|
|
7485
|
+
return false;
|
|
7486
|
+
}
|
|
7487
|
+
return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
|
|
7488
|
+
}
|
|
7489
|
+
recordReadIssue(component, message, error) {
|
|
7490
|
+
this.readIssues.push(this.createReadIssue(component, message));
|
|
7491
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7492
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7493
|
+
}
|
|
7494
|
+
createReadIssue(component, message) {
|
|
7495
|
+
return {
|
|
7496
|
+
component,
|
|
7497
|
+
message,
|
|
7498
|
+
blocking: component !== "keyword"
|
|
7499
|
+
};
|
|
7500
|
+
}
|
|
7501
|
+
getVectorReadIssueMessage() {
|
|
7502
|
+
if (this.config.scope === "global") {
|
|
7503
|
+
return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
|
|
7504
|
+
}
|
|
7505
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7506
|
+
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.";
|
|
7507
|
+
}
|
|
7508
|
+
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.";
|
|
7509
|
+
}
|
|
7510
|
+
getKeywordReadIssueMessage() {
|
|
7511
|
+
if (this.config.scope === "global") {
|
|
7512
|
+
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.";
|
|
7513
|
+
}
|
|
7514
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7515
|
+
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.";
|
|
7516
|
+
}
|
|
7517
|
+
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.";
|
|
7518
|
+
}
|
|
7519
|
+
getDatabaseReadIssueMessage() {
|
|
7520
|
+
if (this.config.scope === "global") {
|
|
7521
|
+
return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
|
|
7522
|
+
}
|
|
7523
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7524
|
+
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.";
|
|
7525
|
+
}
|
|
7526
|
+
return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
|
|
7527
|
+
}
|
|
7528
|
+
getReaderFileFingerprint(filePath, identityOnly = false) {
|
|
7529
|
+
try {
|
|
7530
|
+
const stats = statSync3(filePath);
|
|
7531
|
+
if (identityOnly) {
|
|
7532
|
+
return `${stats.dev}:${stats.ino}`;
|
|
7533
|
+
}
|
|
7534
|
+
return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
|
|
7535
|
+
} catch (error) {
|
|
7536
|
+
return `unavailable:${getErrorMessage2(error)}`;
|
|
7537
|
+
}
|
|
7538
|
+
}
|
|
7539
|
+
captureReaderArtifactFingerprint() {
|
|
7540
|
+
const storePath = path12.join(this.indexPath, "vectors");
|
|
7541
|
+
return {
|
|
7542
|
+
vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
|
|
7543
|
+
keyword: this.getReaderFileFingerprint(path12.join(this.indexPath, "inverted-index.json")),
|
|
7544
|
+
database: this.getReaderFileFingerprint(path12.join(this.indexPath, "codebase.db")),
|
|
7545
|
+
databaseIdentity: this.getReaderFileFingerprint(path12.join(this.indexPath, "codebase.db"), true)
|
|
7546
|
+
};
|
|
7547
|
+
}
|
|
7548
|
+
refreshReaderArtifacts() {
|
|
7549
|
+
if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
|
|
7550
|
+
return;
|
|
7551
|
+
}
|
|
7552
|
+
const previousFingerprint = this.readerArtifactFingerprint;
|
|
7553
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7554
|
+
const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
|
|
7555
|
+
const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
|
|
7556
|
+
const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
|
|
7557
|
+
const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
|
|
7558
|
+
const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
|
|
7559
|
+
const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7560
|
+
if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
|
|
7561
|
+
return;
|
|
7562
|
+
}
|
|
7563
|
+
const setIssue = (component, message, error) => {
|
|
7564
|
+
if (!issues.has(component)) {
|
|
7565
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7566
|
+
}
|
|
7567
|
+
issues.set(component, this.createReadIssue(component, message));
|
|
7568
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7569
|
+
};
|
|
7570
|
+
const storePath = path12.join(this.indexPath, "vectors");
|
|
7571
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7572
|
+
const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
|
|
7573
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7574
|
+
if (vectorsChanged || retryDue("vectors")) {
|
|
7575
|
+
const vectorStoreExists = existsSync7(storePath);
|
|
7576
|
+
const vectorMetadataExists = existsSync7(vectorMetadataPath);
|
|
7577
|
+
if (vectorStoreExists && vectorMetadataExists) {
|
|
7578
|
+
try {
|
|
7579
|
+
const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
|
|
7580
|
+
store.loadStrict();
|
|
7581
|
+
this.store = store;
|
|
7582
|
+
issues.delete("vectors");
|
|
7583
|
+
this.readerArtifactRetryAfter.delete("vectors");
|
|
7584
|
+
} catch (error) {
|
|
7585
|
+
setIssue("vectors", this.getVectorReadIssueMessage(), error);
|
|
7586
|
+
}
|
|
7587
|
+
} else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
|
|
7588
|
+
setIssue("vectors", this.getVectorReadIssueMessage());
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
if (keywordChanged || retryDue("keyword") || !existsSync7(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
|
|
7592
|
+
if (existsSync7(invertedIndexPath)) {
|
|
7593
|
+
try {
|
|
7594
|
+
const invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7595
|
+
invertedIndex.load();
|
|
7596
|
+
this.invertedIndex = invertedIndex;
|
|
7597
|
+
issues.delete("keyword");
|
|
7598
|
+
this.readerArtifactRetryAfter.delete("keyword");
|
|
7599
|
+
} catch (error) {
|
|
7600
|
+
setIssue("keyword", this.getKeywordReadIssueMessage(), error);
|
|
7601
|
+
}
|
|
7602
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
|
|
7603
|
+
setIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
|
|
7607
|
+
if (existsSync7(dbPath)) {
|
|
7608
|
+
try {
|
|
7609
|
+
const database = Database.openReadOnly(dbPath);
|
|
7610
|
+
if (this.database) {
|
|
7611
|
+
this.retiredDatabases.push(this.database);
|
|
7612
|
+
}
|
|
7613
|
+
this.database = database;
|
|
7614
|
+
issues.delete("database");
|
|
7615
|
+
this.readerArtifactRetryAfter.delete("database");
|
|
7616
|
+
} catch (error) {
|
|
7617
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7618
|
+
}
|
|
7619
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
|
|
7620
|
+
setIssue("database", this.getDatabaseReadIssueMessage());
|
|
7621
|
+
}
|
|
7622
|
+
}
|
|
7623
|
+
if (!issues.has("database")) {
|
|
7624
|
+
try {
|
|
7625
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
7626
|
+
} catch (error) {
|
|
7627
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
this.readIssues = Array.from(issues.values());
|
|
7631
|
+
this.readerArtifactFingerprint = currentFingerprint;
|
|
7632
|
+
}
|
|
7633
|
+
refreshInactiveWriterArtifacts() {
|
|
7634
|
+
if (this.initializationMode !== "writer" || this.activeIndexLease) {
|
|
7635
|
+
return true;
|
|
7636
|
+
}
|
|
7637
|
+
const previousFingerprint = this.writerArtifactFingerprint;
|
|
7638
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7639
|
+
const retryDue = this.readIssues.some(
|
|
7640
|
+
(issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
|
|
7641
|
+
);
|
|
7642
|
+
const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7643
|
+
if (!artifactsChanged && !retryDue) {
|
|
7644
|
+
return true;
|
|
7645
|
+
}
|
|
7646
|
+
if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
|
|
7647
|
+
return false;
|
|
7648
|
+
}
|
|
7649
|
+
this.initializationMode = "reader";
|
|
7650
|
+
this.readerArtifactFingerprint = previousFingerprint;
|
|
7651
|
+
try {
|
|
7652
|
+
this.refreshReaderArtifacts();
|
|
7653
|
+
this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
|
|
7654
|
+
} finally {
|
|
7655
|
+
this.readerArtifactFingerprint = null;
|
|
7656
|
+
this.initializationMode = "writer";
|
|
7657
|
+
}
|
|
7658
|
+
return true;
|
|
6767
7659
|
}
|
|
6768
|
-
async
|
|
7660
|
+
async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
|
|
7661
|
+
if (mode === "writer") {
|
|
7662
|
+
this.requireActiveLease();
|
|
7663
|
+
}
|
|
7664
|
+
this.readIssues = [];
|
|
7665
|
+
this.readerArtifactRetryAfter.clear();
|
|
6769
7666
|
if (this.config.embeddingProvider === "custom") {
|
|
6770
7667
|
if (!this.config.customProvider) {
|
|
6771
7668
|
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
@@ -6797,36 +7694,103 @@ var Indexer = class {
|
|
|
6797
7694
|
});
|
|
6798
7695
|
}
|
|
6799
7696
|
}
|
|
6800
|
-
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
6801
7697
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6802
|
-
const storePath =
|
|
6803
|
-
|
|
6804
|
-
const
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
7698
|
+
const storePath = path12.join(this.indexPath, "vectors");
|
|
7699
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7700
|
+
const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
|
|
7701
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7702
|
+
let dbIsNew = !existsSync7(dbPath);
|
|
7703
|
+
const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
|
|
7704
|
+
if (mode === "writer") {
|
|
7705
|
+
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7706
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
|
|
7707
|
+
throw new Error(
|
|
7708
|
+
"Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
|
|
7709
|
+
);
|
|
7710
|
+
}
|
|
7711
|
+
for (const recoveredOwner of recoveredOwners) {
|
|
7712
|
+
recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
|
|
7713
|
+
storePath,
|
|
7714
|
+
`${storePath}.meta.json`
|
|
7715
|
+
]);
|
|
7716
|
+
}
|
|
7717
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project") {
|
|
7718
|
+
await this.resetLocalIndexArtifacts();
|
|
7719
|
+
}
|
|
7720
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7721
|
+
if (existsSync7(storePath) || existsSync7(vectorMetadataPath)) {
|
|
7722
|
+
this.store.load();
|
|
6815
7723
|
}
|
|
6816
7724
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
7725
|
+
try {
|
|
7726
|
+
this.invertedIndex.load();
|
|
7727
|
+
} catch {
|
|
7728
|
+
if (existsSync7(invertedIndexPath)) {
|
|
7729
|
+
await fsPromises2.unlink(invertedIndexPath);
|
|
7730
|
+
}
|
|
7731
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7732
|
+
}
|
|
7733
|
+
try {
|
|
7734
|
+
this.database = new Database(dbPath);
|
|
7735
|
+
} catch (error) {
|
|
7736
|
+
if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
|
|
7737
|
+
throw error;
|
|
7738
|
+
}
|
|
7739
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7740
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7741
|
+
this.database = new Database(dbPath);
|
|
7742
|
+
dbIsNew = true;
|
|
6825
7743
|
}
|
|
7744
|
+
} else {
|
|
6826
7745
|
this.store = new VectorStore(storePath, dimensions);
|
|
7746
|
+
const vectorStoreExists = existsSync7(storePath);
|
|
7747
|
+
const vectorMetadataExists = existsSync7(vectorMetadataPath);
|
|
7748
|
+
const vectorReadFailureMessage = this.getVectorReadIssueMessage();
|
|
7749
|
+
if (vectorStoreExists !== vectorMetadataExists) {
|
|
7750
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage);
|
|
7751
|
+
} else if (vectorStoreExists) {
|
|
7752
|
+
try {
|
|
7753
|
+
this.store.loadStrict();
|
|
7754
|
+
} catch (error) {
|
|
7755
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage, error);
|
|
7756
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
6827
7759
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6828
|
-
|
|
6829
|
-
|
|
7760
|
+
if (existsSync7(invertedIndexPath)) {
|
|
7761
|
+
try {
|
|
7762
|
+
this.invertedIndex.load();
|
|
7763
|
+
} catch (error) {
|
|
7764
|
+
this.recordReadIssue(
|
|
7765
|
+
"keyword",
|
|
7766
|
+
this.getKeywordReadIssueMessage(),
|
|
7767
|
+
error
|
|
7768
|
+
);
|
|
7769
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7770
|
+
}
|
|
7771
|
+
} else if (this.store.count() > 0) {
|
|
7772
|
+
this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7773
|
+
}
|
|
7774
|
+
if (existsSync7(dbPath)) {
|
|
7775
|
+
try {
|
|
7776
|
+
this.database = Database.openReadOnly(dbPath);
|
|
7777
|
+
} catch (error) {
|
|
7778
|
+
this.recordReadIssue(
|
|
7779
|
+
"database",
|
|
7780
|
+
this.getDatabaseReadIssueMessage(),
|
|
7781
|
+
error
|
|
7782
|
+
);
|
|
7783
|
+
this.database = Database.createEmptyReadOnly();
|
|
7784
|
+
}
|
|
7785
|
+
} else {
|
|
7786
|
+
this.database = Database.createEmptyReadOnly();
|
|
7787
|
+
if (this.store.count() > 0) {
|
|
7788
|
+
this.recordReadIssue(
|
|
7789
|
+
"database",
|
|
7790
|
+
`Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
|
|
7791
|
+
);
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
6830
7794
|
}
|
|
6831
7795
|
if (isGitRepo(this.projectRoot)) {
|
|
6832
7796
|
this.currentBranch = getBranchOrDefault(this.projectRoot);
|
|
@@ -6840,10 +7804,10 @@ var Indexer = class {
|
|
|
6840
7804
|
this.baseBranch = "default";
|
|
6841
7805
|
this.logger.branch("debug", "Not a git repository, using default branch");
|
|
6842
7806
|
}
|
|
6843
|
-
if (
|
|
6844
|
-
await this.
|
|
7807
|
+
if (mode === "writer" && recoveredOwners.length > 0) {
|
|
7808
|
+
await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
|
|
6845
7809
|
}
|
|
6846
|
-
if (dbIsNew && this.store.count() > 0) {
|
|
7810
|
+
if (mode === "writer" && dbIsNew && this.store.count() > 0) {
|
|
6847
7811
|
this.migrateFromLegacyIndex();
|
|
6848
7812
|
}
|
|
6849
7813
|
this.loadFileHashCache();
|
|
@@ -6855,9 +7819,11 @@ var Indexer = class {
|
|
|
6855
7819
|
configuredProviderInfo: this.configuredProviderInfo
|
|
6856
7820
|
});
|
|
6857
7821
|
}
|
|
6858
|
-
if (this.config.indexing.autoGc) {
|
|
7822
|
+
if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
|
|
6859
7823
|
await this.maybeRunAutoGc();
|
|
6860
7824
|
}
|
|
7825
|
+
this.initializationMode = mode;
|
|
7826
|
+
this.readerArtifactFingerprint = readerArtifactFingerprint;
|
|
6861
7827
|
}
|
|
6862
7828
|
async maybeRunAutoGc() {
|
|
6863
7829
|
if (!this.database) return;
|
|
@@ -6874,7 +7840,7 @@ var Indexer = class {
|
|
|
6874
7840
|
}
|
|
6875
7841
|
}
|
|
6876
7842
|
if (shouldRunGc) {
|
|
6877
|
-
const result = await this.
|
|
7843
|
+
const result = await this.healthCheckUnlocked();
|
|
6878
7844
|
if (result.warning) {
|
|
6879
7845
|
this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
|
|
6880
7846
|
} else {
|
|
@@ -6896,7 +7862,7 @@ var Indexer = class {
|
|
|
6896
7862
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6897
7863
|
return {
|
|
6898
7864
|
resetCorruptedIndex: true,
|
|
6899
|
-
warning: this.getCorruptedIndexWarning(
|
|
7865
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
6900
7866
|
};
|
|
6901
7867
|
}
|
|
6902
7868
|
throw error;
|
|
@@ -6911,28 +7877,29 @@ var Indexer = class {
|
|
|
6911
7877
|
return;
|
|
6912
7878
|
}
|
|
6913
7879
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6914
|
-
const storeBasePath =
|
|
6915
|
-
const storeIndexPath =
|
|
7880
|
+
const storeBasePath = path12.join(this.indexPath, "vectors");
|
|
7881
|
+
const storeIndexPath = storeBasePath;
|
|
6916
7882
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6917
|
-
const
|
|
6918
|
-
const
|
|
7883
|
+
const lease = this.requireActiveLease();
|
|
7884
|
+
const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
|
|
7885
|
+
const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
|
|
6919
7886
|
let backedUpIndex = false;
|
|
6920
7887
|
let backedUpMetadata = false;
|
|
6921
7888
|
let rebuiltCount = 0;
|
|
6922
7889
|
let skippedCount = 0;
|
|
6923
|
-
if (
|
|
7890
|
+
if (existsSync7(backupIndexPath)) {
|
|
6924
7891
|
unlinkSync(backupIndexPath);
|
|
6925
7892
|
}
|
|
6926
|
-
if (
|
|
7893
|
+
if (existsSync7(backupMetadataPath)) {
|
|
6927
7894
|
unlinkSync(backupMetadataPath);
|
|
6928
7895
|
}
|
|
6929
7896
|
try {
|
|
6930
|
-
if (
|
|
6931
|
-
|
|
7897
|
+
if (existsSync7(storeIndexPath)) {
|
|
7898
|
+
renameSync2(storeIndexPath, backupIndexPath);
|
|
6932
7899
|
backedUpIndex = true;
|
|
6933
7900
|
}
|
|
6934
|
-
if (
|
|
6935
|
-
|
|
7901
|
+
if (existsSync7(storeMetadataPath)) {
|
|
7902
|
+
renameSync2(storeMetadataPath, backupMetadataPath);
|
|
6936
7903
|
backedUpMetadata = true;
|
|
6937
7904
|
}
|
|
6938
7905
|
store.clear();
|
|
@@ -6952,10 +7919,10 @@ var Indexer = class {
|
|
|
6952
7919
|
rebuiltCount += 1;
|
|
6953
7920
|
}
|
|
6954
7921
|
store.save();
|
|
6955
|
-
if (backedUpIndex &&
|
|
7922
|
+
if (backedUpIndex && existsSync7(backupIndexPath)) {
|
|
6956
7923
|
unlinkSync(backupIndexPath);
|
|
6957
7924
|
}
|
|
6958
|
-
if (backedUpMetadata &&
|
|
7925
|
+
if (backedUpMetadata && existsSync7(backupMetadataPath)) {
|
|
6959
7926
|
unlinkSync(backupMetadataPath);
|
|
6960
7927
|
}
|
|
6961
7928
|
this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
|
|
@@ -6968,17 +7935,17 @@ var Indexer = class {
|
|
|
6968
7935
|
store.clear();
|
|
6969
7936
|
} catch {
|
|
6970
7937
|
}
|
|
6971
|
-
if (
|
|
7938
|
+
if (existsSync7(storeIndexPath)) {
|
|
6972
7939
|
unlinkSync(storeIndexPath);
|
|
6973
7940
|
}
|
|
6974
|
-
if (
|
|
7941
|
+
if (existsSync7(storeMetadataPath)) {
|
|
6975
7942
|
unlinkSync(storeMetadataPath);
|
|
6976
7943
|
}
|
|
6977
|
-
if (backedUpIndex &&
|
|
6978
|
-
|
|
7944
|
+
if (backedUpIndex && existsSync7(backupIndexPath)) {
|
|
7945
|
+
renameSync2(backupIndexPath, storeIndexPath);
|
|
6979
7946
|
}
|
|
6980
|
-
if (backedUpMetadata &&
|
|
6981
|
-
|
|
7947
|
+
if (backedUpMetadata && existsSync7(backupMetadataPath)) {
|
|
7948
|
+
renameSync2(backupMetadataPath, storeMetadataPath);
|
|
6982
7949
|
}
|
|
6983
7950
|
if (backedUpIndex || backedUpMetadata) {
|
|
6984
7951
|
store.load();
|
|
@@ -6992,11 +7959,37 @@ var Indexer = class {
|
|
|
6992
7959
|
}
|
|
6993
7960
|
return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
|
|
6994
7961
|
}
|
|
7962
|
+
async resetLocalIndexArtifacts() {
|
|
7963
|
+
this.store = null;
|
|
7964
|
+
this.invertedIndex = null;
|
|
7965
|
+
this.database?.close();
|
|
7966
|
+
this.database = null;
|
|
7967
|
+
this.indexCompatibility = null;
|
|
7968
|
+
this.initializationMode = "none";
|
|
7969
|
+
this.readIssues = [];
|
|
7970
|
+
this.readerArtifactFingerprint = null;
|
|
7971
|
+
this.writerArtifactFingerprint = null;
|
|
7972
|
+
this.readerArtifactRetryAfter.clear();
|
|
7973
|
+
this.fileHashCache.clear();
|
|
7974
|
+
const resetPaths = [
|
|
7975
|
+
path12.join(this.indexPath, "codebase.db"),
|
|
7976
|
+
path12.join(this.indexPath, "codebase.db-shm"),
|
|
7977
|
+
path12.join(this.indexPath, "codebase.db-wal"),
|
|
7978
|
+
path12.join(this.indexPath, "vectors"),
|
|
7979
|
+
path12.join(this.indexPath, "vectors.usearch"),
|
|
7980
|
+
path12.join(this.indexPath, "vectors.meta.json"),
|
|
7981
|
+
path12.join(this.indexPath, "inverted-index.json"),
|
|
7982
|
+
path12.join(this.indexPath, "file-hashes.json"),
|
|
7983
|
+
path12.join(this.indexPath, "failed-batches.json")
|
|
7984
|
+
];
|
|
7985
|
+
await Promise.all(resetPaths.map((targetPath) => fsPromises2.rm(targetPath, { recursive: true, force: true })));
|
|
7986
|
+
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7987
|
+
}
|
|
6995
7988
|
async tryResetCorruptedIndex(stage, error) {
|
|
6996
7989
|
if (!isSqliteCorruptionError(error)) {
|
|
6997
7990
|
return false;
|
|
6998
7991
|
}
|
|
6999
|
-
const dbPath =
|
|
7992
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7000
7993
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
7001
7994
|
const errorMessage = getErrorMessage2(error);
|
|
7002
7995
|
if (this.config.scope === "global") {
|
|
@@ -7012,30 +8005,7 @@ var Indexer = class {
|
|
|
7012
8005
|
dbPath,
|
|
7013
8006
|
error: errorMessage
|
|
7014
8007
|
});
|
|
7015
|
-
this.
|
|
7016
|
-
this.invertedIndex = null;
|
|
7017
|
-
this.database?.close();
|
|
7018
|
-
this.database = null;
|
|
7019
|
-
this.indexCompatibility = null;
|
|
7020
|
-
this.fileHashCache.clear();
|
|
7021
|
-
const resetPaths = [
|
|
7022
|
-
path10.join(this.indexPath, "codebase.db"),
|
|
7023
|
-
path10.join(this.indexPath, "codebase.db-shm"),
|
|
7024
|
-
path10.join(this.indexPath, "codebase.db-wal"),
|
|
7025
|
-
path10.join(this.indexPath, "vectors.usearch"),
|
|
7026
|
-
path10.join(this.indexPath, "inverted-index.json"),
|
|
7027
|
-
path10.join(this.indexPath, "file-hashes.json"),
|
|
7028
|
-
path10.join(this.indexPath, "failed-batches.json"),
|
|
7029
|
-
path10.join(this.indexPath, "indexing.lock"),
|
|
7030
|
-
path10.join(this.indexPath, "vectors")
|
|
7031
|
-
];
|
|
7032
|
-
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
7033
|
-
try {
|
|
7034
|
-
await fsPromises2.rm(targetPath, { recursive: true, force: true });
|
|
7035
|
-
} catch {
|
|
7036
|
-
}
|
|
7037
|
-
}));
|
|
7038
|
-
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
8008
|
+
await this.resetLocalIndexArtifacts();
|
|
7039
8009
|
return true;
|
|
7040
8010
|
}
|
|
7041
8011
|
migrateFromLegacyIndex() {
|
|
@@ -7154,8 +8124,62 @@ var Indexer = class {
|
|
|
7154
8124
|
return this.indexCompatibility;
|
|
7155
8125
|
}
|
|
7156
8126
|
async ensureInitialized() {
|
|
8127
|
+
let initializedReader = false;
|
|
8128
|
+
while (true) {
|
|
8129
|
+
if (this.initializationPromise) {
|
|
8130
|
+
await this.initializationPromise;
|
|
8131
|
+
}
|
|
8132
|
+
if (!this.isInitializedFor("reader")) {
|
|
8133
|
+
await this.initialize();
|
|
8134
|
+
initializedReader = true;
|
|
8135
|
+
continue;
|
|
8136
|
+
}
|
|
8137
|
+
if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
|
|
8138
|
+
if (!this.refreshInactiveWriterArtifacts()) {
|
|
8139
|
+
this.resetLoadedIndexState(true);
|
|
8140
|
+
await this.initialize();
|
|
8141
|
+
initializedReader = true;
|
|
8142
|
+
continue;
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
8145
|
+
if (this.initializationMode === "reader" && !initializedReader) {
|
|
8146
|
+
this.refreshReaderArtifacts();
|
|
8147
|
+
}
|
|
8148
|
+
const state = this.requireLoadedIndexState();
|
|
8149
|
+
return {
|
|
8150
|
+
...state,
|
|
8151
|
+
readIssues: [...this.readIssues],
|
|
8152
|
+
compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
|
|
8153
|
+
};
|
|
8154
|
+
}
|
|
8155
|
+
}
|
|
8156
|
+
async ensureInitializedUnlocked(recoveredOwners = []) {
|
|
8157
|
+
this.requireActiveLease();
|
|
8158
|
+
if (this.initializationPromise) {
|
|
8159
|
+
await this.initializationPromise;
|
|
8160
|
+
}
|
|
8161
|
+
if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
|
|
8162
|
+
const retireReaderDatabase = this.initializationMode === "reader";
|
|
8163
|
+
this.resetLoadedIndexState(retireReaderDatabase);
|
|
8164
|
+
await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
|
|
8165
|
+
} else {
|
|
8166
|
+
this.refreshLoadedIndexState();
|
|
8167
|
+
}
|
|
8168
|
+
if (this.config.indexing.autoGc) {
|
|
8169
|
+
await this.maybeRunAutoGc();
|
|
8170
|
+
}
|
|
8171
|
+
return this.requireLoadedIndexState();
|
|
8172
|
+
}
|
|
8173
|
+
requireReadableComponents(readIssues, ...components) {
|
|
8174
|
+
const componentSet = new Set(components);
|
|
8175
|
+
const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
|
|
8176
|
+
if (issues.length > 0) {
|
|
8177
|
+
throw new Error(issues.map((issue) => issue.message).join(" "));
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
8180
|
+
requireLoadedIndexState() {
|
|
7157
8181
|
if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
|
|
7158
|
-
|
|
8182
|
+
throw new Error("Index state is not initialized");
|
|
7159
8183
|
}
|
|
7160
8184
|
return {
|
|
7161
8185
|
store: this.store,
|
|
@@ -7179,7 +8203,12 @@ var Indexer = class {
|
|
|
7179
8203
|
return createCostEstimate(files, configuredProviderInfo);
|
|
7180
8204
|
}
|
|
7181
8205
|
async index(onProgress) {
|
|
7182
|
-
|
|
8206
|
+
return this.withIndexMutationLease("index", async (recoveredOwners) => {
|
|
8207
|
+
return this.indexUnlocked(onProgress, recoveredOwners);
|
|
8208
|
+
});
|
|
8209
|
+
}
|
|
8210
|
+
async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
|
|
8211
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
|
|
7183
8212
|
const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
7184
8213
|
const branchCatalogKey = this.getBranchCatalogKey();
|
|
7185
8214
|
const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
|
|
@@ -7189,7 +8218,6 @@ var Indexer = class {
|
|
|
7189
8218
|
`${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
|
|
7190
8219
|
);
|
|
7191
8220
|
}
|
|
7192
|
-
this.acquireIndexingLock();
|
|
7193
8221
|
this.logger.recordIndexingStart();
|
|
7194
8222
|
this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
|
|
7195
8223
|
const startTime = Date.now();
|
|
@@ -7264,6 +8292,7 @@ var Indexer = class {
|
|
|
7264
8292
|
this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
|
|
7265
8293
|
const existingChunks = /* @__PURE__ */ new Map();
|
|
7266
8294
|
const existingChunksByFile = /* @__PURE__ */ new Map();
|
|
8295
|
+
const existingMetadataById = /* @__PURE__ */ new Map();
|
|
7267
8296
|
for (const { key, metadata } of store.getAllMetadata()) {
|
|
7268
8297
|
if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
|
|
7269
8298
|
continue;
|
|
@@ -7272,6 +8301,7 @@ var Indexer = class {
|
|
|
7272
8301
|
continue;
|
|
7273
8302
|
}
|
|
7274
8303
|
existingChunks.set(key, metadata.hash);
|
|
8304
|
+
existingMetadataById.set(key, metadata);
|
|
7275
8305
|
const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
|
|
7276
8306
|
fileChunks.add(key);
|
|
7277
8307
|
existingChunksByFile.set(metadata.filePath, fileChunks);
|
|
@@ -7279,6 +8309,8 @@ var Indexer = class {
|
|
|
7279
8309
|
const currentChunkIds = /* @__PURE__ */ new Set();
|
|
7280
8310
|
const currentFilePaths = /* @__PURE__ */ new Set();
|
|
7281
8311
|
const pendingChunks = [];
|
|
8312
|
+
const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
|
|
8313
|
+
let backfilledBlameMetadata = false;
|
|
7282
8314
|
for (const filePath of unchangedFilePaths) {
|
|
7283
8315
|
currentFilePaths.add(filePath);
|
|
7284
8316
|
const fileChunks = existingChunksByFile.get(filePath);
|
|
@@ -7289,10 +8321,52 @@ var Indexer = class {
|
|
|
7289
8321
|
}
|
|
7290
8322
|
}
|
|
7291
8323
|
const chunkDataBatch = [];
|
|
8324
|
+
if (gitBlameEnabled) {
|
|
8325
|
+
const backfillItems = [];
|
|
8326
|
+
for (const chunkId of currentChunkIds) {
|
|
8327
|
+
const metadata = existingMetadataById.get(chunkId);
|
|
8328
|
+
if (!metadata || hasBlameMetadata(metadata)) {
|
|
8329
|
+
continue;
|
|
8330
|
+
}
|
|
8331
|
+
const chunk = database.getChunk(chunkId);
|
|
8332
|
+
if (!chunk) {
|
|
8333
|
+
continue;
|
|
8334
|
+
}
|
|
8335
|
+
const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
|
|
8336
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
8337
|
+
if (!blameMetadata.blameSha) {
|
|
8338
|
+
continue;
|
|
8339
|
+
}
|
|
8340
|
+
chunkDataBatch.push({
|
|
8341
|
+
...chunk,
|
|
8342
|
+
blameSha: blameMetadata.blameSha,
|
|
8343
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8344
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8345
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8346
|
+
blameSummary: blameMetadata.blameSummary
|
|
8347
|
+
});
|
|
8348
|
+
const embeddingBuffer = database.getEmbedding(chunk.contentHash);
|
|
8349
|
+
if (!embeddingBuffer) {
|
|
8350
|
+
continue;
|
|
8351
|
+
}
|
|
8352
|
+
backfillItems.push({
|
|
8353
|
+
id: chunkId,
|
|
8354
|
+
vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
|
|
8355
|
+
metadata: {
|
|
8356
|
+
...metadata,
|
|
8357
|
+
...blameMetadata
|
|
8358
|
+
}
|
|
8359
|
+
});
|
|
8360
|
+
}
|
|
8361
|
+
if (backfillItems.length > 0) {
|
|
8362
|
+
store.addBatch(backfillItems);
|
|
8363
|
+
backfilledBlameMetadata = true;
|
|
8364
|
+
}
|
|
8365
|
+
}
|
|
7292
8366
|
for (const parsed of parsedFiles) {
|
|
7293
8367
|
currentFilePaths.add(parsed.path);
|
|
7294
8368
|
if (parsed.chunks.length === 0) {
|
|
7295
|
-
const relativePath =
|
|
8369
|
+
const relativePath = path12.relative(this.projectRoot, parsed.path);
|
|
7296
8370
|
stats.parseFailures.push(relativePath);
|
|
7297
8371
|
}
|
|
7298
8372
|
let fileChunkCount = 0;
|
|
@@ -7313,6 +8387,10 @@ var Indexer = class {
|
|
|
7313
8387
|
}
|
|
7314
8388
|
const id = generateChunkId(parsed.path, chunk);
|
|
7315
8389
|
const contentHash = generateChunkHash(chunk);
|
|
8390
|
+
const existingContentHash = existingChunks.get(id);
|
|
8391
|
+
const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
|
|
8392
|
+
const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
|
|
8393
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
7316
8394
|
currentChunkIds.add(id);
|
|
7317
8395
|
chunkDataBatch.push({
|
|
7318
8396
|
chunkId: id,
|
|
@@ -7322,9 +8400,14 @@ var Indexer = class {
|
|
|
7322
8400
|
endLine: chunk.endLine,
|
|
7323
8401
|
nodeType: chunk.chunkType,
|
|
7324
8402
|
name: chunk.name,
|
|
7325
|
-
language: chunk.language
|
|
8403
|
+
language: chunk.language,
|
|
8404
|
+
blameSha: blameMetadata.blameSha,
|
|
8405
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8406
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8407
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8408
|
+
blameSummary: blameMetadata.blameSummary
|
|
7326
8409
|
});
|
|
7327
|
-
if (
|
|
8410
|
+
if (existingContentHash === contentHash) {
|
|
7328
8411
|
fileChunkCount++;
|
|
7329
8412
|
continue;
|
|
7330
8413
|
}
|
|
@@ -7339,7 +8422,8 @@ var Indexer = class {
|
|
|
7339
8422
|
chunkType: chunk.chunkType,
|
|
7340
8423
|
name: chunk.name,
|
|
7341
8424
|
language: chunk.language,
|
|
7342
|
-
hash: contentHash
|
|
8425
|
+
hash: contentHash,
|
|
8426
|
+
...blameMetadata
|
|
7343
8427
|
};
|
|
7344
8428
|
pendingChunks.push({
|
|
7345
8429
|
id,
|
|
@@ -7482,6 +8566,11 @@ var Indexer = class {
|
|
|
7482
8566
|
database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
|
|
7483
8567
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7484
8568
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
8569
|
+
const vectorPath = path12.join(this.indexPath, "vectors");
|
|
8570
|
+
const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync7(vectorPath) && existsSync7(`${vectorPath}.meta.json`);
|
|
8571
|
+
if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
|
|
8572
|
+
store.save();
|
|
8573
|
+
}
|
|
7485
8574
|
if (scopedRoots) {
|
|
7486
8575
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7487
8576
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7500,7 +8589,6 @@ var Indexer = class {
|
|
|
7500
8589
|
chunksProcessed: 0,
|
|
7501
8590
|
totalChunks: 0
|
|
7502
8591
|
});
|
|
7503
|
-
this.releaseIndexingLock();
|
|
7504
8592
|
return stats;
|
|
7505
8593
|
}
|
|
7506
8594
|
if (pendingChunks.length === 0) {
|
|
@@ -7509,7 +8597,7 @@ var Indexer = class {
|
|
|
7509
8597
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7510
8598
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7511
8599
|
store.save();
|
|
7512
|
-
|
|
8600
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7513
8601
|
if (scopedRoots) {
|
|
7514
8602
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7515
8603
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7528,7 +8616,6 @@ var Indexer = class {
|
|
|
7528
8616
|
chunksProcessed: 0,
|
|
7529
8617
|
totalChunks: 0
|
|
7530
8618
|
});
|
|
7531
|
-
this.releaseIndexingLock();
|
|
7532
8619
|
return stats;
|
|
7533
8620
|
}
|
|
7534
8621
|
onProgress?.({
|
|
@@ -7759,7 +8846,7 @@ var Indexer = class {
|
|
|
7759
8846
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7760
8847
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7761
8848
|
store.save();
|
|
7762
|
-
|
|
8849
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7763
8850
|
if (scopedRoots) {
|
|
7764
8851
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7765
8852
|
} else {
|
|
@@ -7811,7 +8898,6 @@ var Indexer = class {
|
|
|
7811
8898
|
chunksProcessed: stats.indexedChunks,
|
|
7812
8899
|
totalChunks: pendingChunks.length
|
|
7813
8900
|
});
|
|
7814
|
-
this.releaseIndexingLock();
|
|
7815
8901
|
return stats;
|
|
7816
8902
|
}
|
|
7817
8903
|
async getQueryEmbedding(query, provider) {
|
|
@@ -7877,8 +8963,8 @@ var Indexer = class {
|
|
|
7877
8963
|
return intersection / union;
|
|
7878
8964
|
}
|
|
7879
8965
|
async search(query, limit, options) {
|
|
7880
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
7881
|
-
|
|
8966
|
+
const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8967
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
7882
8968
|
if (!compatibility.compatible) {
|
|
7883
8969
|
throw new Error(
|
|
7884
8970
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
|
|
@@ -7892,6 +8978,7 @@ var Indexer = class {
|
|
|
7892
8978
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
7893
8979
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
7894
8980
|
const fusionStrategy = this.config.search.fusionStrategy;
|
|
8981
|
+
const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
|
|
7895
8982
|
const rrfK = this.config.search.rrfK;
|
|
7896
8983
|
const rerankTopN = this.config.search.rerankTopN;
|
|
7897
8984
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
@@ -7900,7 +8987,7 @@ var Indexer = class {
|
|
|
7900
8987
|
this.logger.search("debug", "Starting search", {
|
|
7901
8988
|
query,
|
|
7902
8989
|
maxResults,
|
|
7903
|
-
hybridWeight,
|
|
8990
|
+
hybridWeight: effectiveHybridWeight,
|
|
7904
8991
|
fusionStrategy,
|
|
7905
8992
|
rrfK,
|
|
7906
8993
|
rerankTopN,
|
|
@@ -7914,7 +9001,7 @@ var Indexer = class {
|
|
|
7914
9001
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
7915
9002
|
const vectorMs = performance2.now() - vectorStartTime;
|
|
7916
9003
|
const keywordStartTime = performance2.now();
|
|
7917
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
9004
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
7918
9005
|
const keywordMs = performance2.now() - keywordStartTime;
|
|
7919
9006
|
let branchChunkIds = null;
|
|
7920
9007
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -7951,7 +9038,7 @@ var Indexer = class {
|
|
|
7951
9038
|
rrfK,
|
|
7952
9039
|
rerankTopN,
|
|
7953
9040
|
limit: maxResults,
|
|
7954
|
-
hybridWeight,
|
|
9041
|
+
hybridWeight: effectiveHybridWeight,
|
|
7955
9042
|
prioritizeSourcePaths: sourceIntent
|
|
7956
9043
|
});
|
|
7957
9044
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -8045,13 +9132,13 @@ var Indexer = class {
|
|
|
8045
9132
|
content,
|
|
8046
9133
|
score: r.score,
|
|
8047
9134
|
chunkType: r.metadata.chunkType,
|
|
8048
|
-
name: r.metadata.name
|
|
9135
|
+
name: r.metadata.name,
|
|
9136
|
+
blame: blameFromMetadata(r.metadata)
|
|
8049
9137
|
};
|
|
8050
9138
|
})
|
|
8051
9139
|
);
|
|
8052
9140
|
}
|
|
8053
|
-
async keywordSearch(query, limit) {
|
|
8054
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9141
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
8055
9142
|
const scores = invertedIndex.search(query);
|
|
8056
9143
|
if (scores.size === 0) {
|
|
8057
9144
|
return [];
|
|
@@ -8069,24 +9156,54 @@ var Indexer = class {
|
|
|
8069
9156
|
return results.slice(0, limit);
|
|
8070
9157
|
}
|
|
8071
9158
|
async getStatus() {
|
|
8072
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9159
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8073
9160
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9161
|
+
const vectorCount = store.count();
|
|
9162
|
+
const statusReadIssues = [...readIssues];
|
|
9163
|
+
let startupWarning = "";
|
|
9164
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9165
|
+
try {
|
|
9166
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9167
|
+
} catch (error) {
|
|
9168
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9169
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9170
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9171
|
+
this.recordReadIssue("database", message, error);
|
|
9172
|
+
}
|
|
9173
|
+
}
|
|
9174
|
+
}
|
|
9175
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9176
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9177
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
8074
9178
|
return {
|
|
8075
|
-
indexed:
|
|
8076
|
-
vectorCount
|
|
9179
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9180
|
+
vectorCount,
|
|
8077
9181
|
provider: configuredProviderInfo.provider,
|
|
8078
9182
|
model: configuredProviderInfo.modelInfo.model,
|
|
8079
9183
|
indexPath: this.indexPath,
|
|
8080
9184
|
currentBranch: this.currentBranch,
|
|
8081
9185
|
baseBranch: this.baseBranch,
|
|
8082
|
-
compatibility
|
|
9186
|
+
compatibility,
|
|
8083
9187
|
failedBatchesCount,
|
|
8084
9188
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
8085
|
-
warning:
|
|
9189
|
+
warning: warning || void 0
|
|
8086
9190
|
};
|
|
8087
9191
|
}
|
|
9192
|
+
async forceIndex(onProgress) {
|
|
9193
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9194
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9195
|
+
await this.clearIndexUnlocked();
|
|
9196
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9197
|
+
});
|
|
9198
|
+
}
|
|
8088
9199
|
async clearIndex() {
|
|
8089
|
-
|
|
9200
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9201
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9202
|
+
await this.clearIndexUnlocked();
|
|
9203
|
+
});
|
|
9204
|
+
}
|
|
9205
|
+
async clearIndexUnlocked() {
|
|
9206
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8090
9207
|
if (this.config.scope === "global") {
|
|
8091
9208
|
store.load();
|
|
8092
9209
|
invertedIndex.load();
|
|
@@ -8113,7 +9230,7 @@ var Indexer = class {
|
|
|
8113
9230
|
store.clear();
|
|
8114
9231
|
store.save();
|
|
8115
9232
|
invertedIndex.clear();
|
|
8116
|
-
|
|
9233
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8117
9234
|
this.fileHashCache.clear();
|
|
8118
9235
|
this.saveFileHashCache();
|
|
8119
9236
|
database.clearAllIndexedData();
|
|
@@ -8137,14 +9254,7 @@ var Indexer = class {
|
|
|
8137
9254
|
this.indexCompatibility = compatibility;
|
|
8138
9255
|
return;
|
|
8139
9256
|
}
|
|
8140
|
-
|
|
8141
|
-
if (this.host !== "opencode") {
|
|
8142
|
-
localProjectIndexPaths.push(path10.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
8143
|
-
}
|
|
8144
|
-
const isLocalProjectIndex = localProjectIndexPaths.some(
|
|
8145
|
-
(localPath) => path10.resolve(this.indexPath) === path10.resolve(localPath)
|
|
8146
|
-
);
|
|
8147
|
-
if (!isLocalProjectIndex) {
|
|
9257
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8148
9258
|
throw new Error(
|
|
8149
9259
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8150
9260
|
);
|
|
@@ -8152,7 +9262,7 @@ var Indexer = class {
|
|
|
8152
9262
|
store.clear();
|
|
8153
9263
|
store.save();
|
|
8154
9264
|
invertedIndex.clear();
|
|
8155
|
-
|
|
9265
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8156
9266
|
this.fileHashCache.clear();
|
|
8157
9267
|
this.saveFileHashCache();
|
|
8158
9268
|
database.clearAllIndexedData();
|
|
@@ -8170,7 +9280,13 @@ var Indexer = class {
|
|
|
8170
9280
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8171
9281
|
}
|
|
8172
9282
|
async healthCheck() {
|
|
8173
|
-
|
|
9283
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9284
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9285
|
+
return this.healthCheckUnlocked();
|
|
9286
|
+
});
|
|
9287
|
+
}
|
|
9288
|
+
async healthCheckUnlocked() {
|
|
9289
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8174
9290
|
this.logger.gc("info", "Starting health check");
|
|
8175
9291
|
const allMetadata = store.getAllMetadata();
|
|
8176
9292
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8183,7 +9299,7 @@ var Indexer = class {
|
|
|
8183
9299
|
const removedChunkKeys = [];
|
|
8184
9300
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8185
9301
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8186
|
-
if (!
|
|
9302
|
+
if (!existsSync7(filePath)) {
|
|
8187
9303
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8188
9304
|
for (const key of chunkKeys) {
|
|
8189
9305
|
removedChunkKeys.push(key);
|
|
@@ -8208,7 +9324,7 @@ var Indexer = class {
|
|
|
8208
9324
|
const removedCount = removedChunkKeys.length;
|
|
8209
9325
|
if (removedCount > 0) {
|
|
8210
9326
|
store.save();
|
|
8211
|
-
|
|
9327
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8212
9328
|
}
|
|
8213
9329
|
let gcOrphanEmbeddings;
|
|
8214
9330
|
let gcOrphanChunks;
|
|
@@ -8223,7 +9339,7 @@ var Indexer = class {
|
|
|
8223
9339
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8224
9340
|
throw error;
|
|
8225
9341
|
}
|
|
8226
|
-
await this.
|
|
9342
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8227
9343
|
return {
|
|
8228
9344
|
removed: 0,
|
|
8229
9345
|
filePaths: [],
|
|
@@ -8232,7 +9348,7 @@ var Indexer = class {
|
|
|
8232
9348
|
gcOrphanSymbols: 0,
|
|
8233
9349
|
gcOrphanCallEdges: 0,
|
|
8234
9350
|
resetCorruptedIndex: true,
|
|
8235
|
-
warning: this.getCorruptedIndexWarning(
|
|
9351
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
8236
9352
|
};
|
|
8237
9353
|
}
|
|
8238
9354
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8245,7 +9361,13 @@ var Indexer = class {
|
|
|
8245
9361
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8246
9362
|
}
|
|
8247
9363
|
async retryFailedBatches() {
|
|
8248
|
-
|
|
9364
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9365
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9366
|
+
return this.retryFailedBatchesUnlocked();
|
|
9367
|
+
});
|
|
9368
|
+
}
|
|
9369
|
+
async retryFailedBatchesUnlocked() {
|
|
9370
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8249
9371
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8250
9372
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8251
9373
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8409,7 +9531,7 @@ var Indexer = class {
|
|
|
8409
9531
|
}
|
|
8410
9532
|
if (succeeded > 0) {
|
|
8411
9533
|
store.save();
|
|
8412
|
-
|
|
9534
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8413
9535
|
}
|
|
8414
9536
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8415
9537
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8437,15 +9559,16 @@ var Indexer = class {
|
|
|
8437
9559
|
}
|
|
8438
9560
|
}
|
|
8439
9561
|
async getDatabaseStats() {
|
|
8440
|
-
const { database } = await this.ensureInitialized();
|
|
9562
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9563
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8441
9564
|
return database.getStats();
|
|
8442
9565
|
}
|
|
8443
9566
|
getLogger() {
|
|
8444
9567
|
return this.logger;
|
|
8445
9568
|
}
|
|
8446
9569
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8447
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8448
|
-
|
|
9570
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9571
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8449
9572
|
if (!compatibility.compatible) {
|
|
8450
9573
|
throw new Error(
|
|
8451
9574
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8552,13 +9675,15 @@ var Indexer = class {
|
|
|
8552
9675
|
content,
|
|
8553
9676
|
score: r.score,
|
|
8554
9677
|
chunkType: r.metadata.chunkType,
|
|
8555
|
-
name: r.metadata.name
|
|
9678
|
+
name: r.metadata.name,
|
|
9679
|
+
blame: blameFromMetadata(r.metadata)
|
|
8556
9680
|
};
|
|
8557
9681
|
})
|
|
8558
9682
|
);
|
|
8559
9683
|
}
|
|
8560
9684
|
async getCallers(targetName, callTypeFilter) {
|
|
8561
|
-
const { database } = await this.ensureInitialized();
|
|
9685
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9686
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8562
9687
|
const seen = /* @__PURE__ */ new Set();
|
|
8563
9688
|
const results = [];
|
|
8564
9689
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8572,7 +9697,8 @@ var Indexer = class {
|
|
|
8572
9697
|
return results;
|
|
8573
9698
|
}
|
|
8574
9699
|
async getCallees(symbolId, callTypeFilter) {
|
|
8575
|
-
const { database } = await this.ensureInitialized();
|
|
9700
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9701
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8576
9702
|
const seen = /* @__PURE__ */ new Set();
|
|
8577
9703
|
const results = [];
|
|
8578
9704
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8586,44 +9712,51 @@ var Indexer = class {
|
|
|
8586
9712
|
return results;
|
|
8587
9713
|
}
|
|
8588
9714
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8589
|
-
const { database } = await this.ensureInitialized();
|
|
9715
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9716
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8590
9717
|
let shortest = [];
|
|
8591
9718
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8592
|
-
const
|
|
8593
|
-
if (
|
|
8594
|
-
shortest =
|
|
9719
|
+
const path27 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9720
|
+
if (path27.length > 0 && (shortest.length === 0 || path27.length < shortest.length)) {
|
|
9721
|
+
shortest = path27;
|
|
8595
9722
|
}
|
|
8596
9723
|
}
|
|
8597
9724
|
return shortest;
|
|
8598
9725
|
}
|
|
8599
9726
|
async getSymbolsForBranch(branch) {
|
|
8600
|
-
const { database } = await this.ensureInitialized();
|
|
9727
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9728
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8601
9729
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8602
9730
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8603
9731
|
}
|
|
8604
9732
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8605
|
-
const { database } = await this.ensureInitialized();
|
|
9733
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9734
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8606
9735
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8607
9736
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8608
9737
|
}
|
|
8609
9738
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8610
|
-
const { database } = await this.ensureInitialized();
|
|
9739
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9740
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8611
9741
|
const branch = this.getBranchCatalogKey();
|
|
8612
9742
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8613
9743
|
}
|
|
8614
9744
|
async detectCommunities(branch, symbolIds) {
|
|
8615
|
-
const { database } = await this.ensureInitialized();
|
|
9745
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9746
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8616
9747
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8617
9748
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8618
9749
|
}
|
|
8619
9750
|
async computeCentrality(branch) {
|
|
8620
|
-
const { database } = await this.ensureInitialized();
|
|
9751
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9752
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8621
9753
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8622
9754
|
return database.computeCentrality(resolvedBranch);
|
|
8623
9755
|
}
|
|
8624
9756
|
async getPrImpact(opts) {
|
|
8625
|
-
const { database } = await this.ensureInitialized();
|
|
8626
|
-
|
|
9757
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9758
|
+
this.requireReadableComponents(readIssues, "database");
|
|
9759
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
8627
9760
|
const changedFilesResult = await getChangedFiles({
|
|
8628
9761
|
pr: opts.pr,
|
|
8629
9762
|
branch: opts.branch,
|
|
@@ -8645,7 +9778,7 @@ var Indexer = class {
|
|
|
8645
9778
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8646
9779
|
);
|
|
8647
9780
|
}
|
|
8648
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9781
|
+
const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
|
|
8649
9782
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8650
9783
|
const directIds = directSymbols.map((s) => s.id);
|
|
8651
9784
|
const direction = opts.direction ?? "both";
|
|
@@ -8707,7 +9840,7 @@ var Indexer = class {
|
|
|
8707
9840
|
if (opts.checkConflicts) {
|
|
8708
9841
|
conflictingPRs = [];
|
|
8709
9842
|
try {
|
|
8710
|
-
const { stdout } = await
|
|
9843
|
+
const { stdout } = await execFileAsync3(
|
|
8711
9844
|
"gh",
|
|
8712
9845
|
["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
|
|
8713
9846
|
{ cwd: this.projectRoot, timeout: 3e4 }
|
|
@@ -8728,7 +9861,7 @@ var Indexer = class {
|
|
|
8728
9861
|
projectRoot: this.projectRoot,
|
|
8729
9862
|
baseBranch: this.baseBranch
|
|
8730
9863
|
});
|
|
8731
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9864
|
+
const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
|
|
8732
9865
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8733
9866
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8734
9867
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8778,7 +9911,8 @@ var Indexer = class {
|
|
|
8778
9911
|
};
|
|
8779
9912
|
}
|
|
8780
9913
|
async getVisualizationData(options) {
|
|
8781
|
-
const { database, store } = await this.ensureInitialized();
|
|
9914
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9915
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8782
9916
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8783
9917
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8784
9918
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8791,12 +9925,12 @@ var Indexer = class {
|
|
|
8791
9925
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8792
9926
|
}
|
|
8793
9927
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8794
|
-
const absoluteDirectoryFilter = directory ?
|
|
9928
|
+
const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
|
|
8795
9929
|
for (const filePath of filePaths) {
|
|
8796
9930
|
if (directory) {
|
|
8797
|
-
const absoluteFilePath =
|
|
9931
|
+
const absoluteFilePath = path12.resolve(filePath);
|
|
8798
9932
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8799
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9933
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
|
|
8800
9934
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8801
9935
|
continue;
|
|
8802
9936
|
}
|
|
@@ -8818,12 +9952,23 @@ var Indexer = class {
|
|
|
8818
9952
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
8819
9953
|
}
|
|
8820
9954
|
async close() {
|
|
8821
|
-
|
|
9955
|
+
this.database?.close();
|
|
9956
|
+
for (const database of this.retiredDatabases) {
|
|
9957
|
+
database.close();
|
|
9958
|
+
}
|
|
9959
|
+
this.retiredDatabases = [];
|
|
8822
9960
|
this.database = null;
|
|
8823
9961
|
this.store = null;
|
|
8824
9962
|
this.invertedIndex = null;
|
|
8825
9963
|
this.provider = null;
|
|
8826
9964
|
this.reranker = null;
|
|
9965
|
+
this.configuredProviderInfo = null;
|
|
9966
|
+
this.indexCompatibility = null;
|
|
9967
|
+
this.initializationMode = "none";
|
|
9968
|
+
this.readIssues = [];
|
|
9969
|
+
this.readerArtifactFingerprint = null;
|
|
9970
|
+
this.writerArtifactFingerprint = null;
|
|
9971
|
+
this.readerArtifactRetryAfter.clear();
|
|
8827
9972
|
}
|
|
8828
9973
|
};
|
|
8829
9974
|
|
|
@@ -9074,15 +10219,15 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
9074
10219
|
}
|
|
9075
10220
|
|
|
9076
10221
|
// src/eval/runner-config.ts
|
|
9077
|
-
import { existsSync as
|
|
9078
|
-
import * as
|
|
9079
|
-
import * as
|
|
10222
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
10223
|
+
import * as os5 from "os";
|
|
10224
|
+
import * as path14 from "path";
|
|
9080
10225
|
|
|
9081
10226
|
// src/config/rebase.ts
|
|
9082
|
-
import * as
|
|
10227
|
+
import * as path13 from "path";
|
|
9083
10228
|
function isWithinRoot(rootDir, targetPath) {
|
|
9084
|
-
const relativePath =
|
|
9085
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
10229
|
+
const relativePath = path13.relative(rootDir, targetPath);
|
|
10230
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
|
|
9086
10231
|
}
|
|
9087
10232
|
function rebasePathEntries(values, fromDir, toDir) {
|
|
9088
10233
|
if (!Array.isArray(values)) {
|
|
@@ -9090,10 +10235,10 @@ function rebasePathEntries(values, fromDir, toDir) {
|
|
|
9090
10235
|
}
|
|
9091
10236
|
return values.filter((value) => typeof value === "string").map((value) => {
|
|
9092
10237
|
const trimmed = value.trim();
|
|
9093
|
-
if (!trimmed ||
|
|
10238
|
+
if (!trimmed || path13.isAbsolute(trimmed)) {
|
|
9094
10239
|
return trimmed;
|
|
9095
10240
|
}
|
|
9096
|
-
return normalizePathSeparators(
|
|
10241
|
+
return normalizePathSeparators(path13.normalize(path13.relative(toDir, path13.resolve(fromDir, trimmed))));
|
|
9097
10242
|
}).filter(Boolean);
|
|
9098
10243
|
}
|
|
9099
10244
|
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
@@ -9105,17 +10250,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
|
9105
10250
|
if (!trimmed) {
|
|
9106
10251
|
return trimmed;
|
|
9107
10252
|
}
|
|
9108
|
-
if (
|
|
10253
|
+
if (path13.isAbsolute(trimmed)) {
|
|
9109
10254
|
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
9110
|
-
return normalizePathSeparators(
|
|
10255
|
+
return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
|
|
9111
10256
|
}
|
|
9112
|
-
return
|
|
10257
|
+
return path13.normalize(trimmed);
|
|
9113
10258
|
}
|
|
9114
|
-
const resolvedFromSource =
|
|
10259
|
+
const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
|
|
9115
10260
|
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
9116
|
-
return normalizePathSeparators(
|
|
10261
|
+
return normalizePathSeparators(path13.normalize(trimmed));
|
|
9117
10262
|
}
|
|
9118
|
-
return normalizePathSeparators(
|
|
10263
|
+
return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
|
|
9119
10264
|
}).filter(Boolean);
|
|
9120
10265
|
}
|
|
9121
10266
|
|
|
@@ -9153,7 +10298,7 @@ function validateEvalConfigShape(rawConfig, filePath) {
|
|
|
9153
10298
|
}
|
|
9154
10299
|
function parseJsonConfigFile(filePath) {
|
|
9155
10300
|
try {
|
|
9156
|
-
return validateEvalConfigShape(JSON.parse(
|
|
10301
|
+
return validateEvalConfigShape(JSON.parse(readFileSync8(filePath, "utf-8")), filePath);
|
|
9157
10302
|
} catch (error) {
|
|
9158
10303
|
if (error instanceof Error && error.message.startsWith("Eval config at ")) {
|
|
9159
10304
|
throw error;
|
|
@@ -9163,20 +10308,20 @@ function parseJsonConfigFile(filePath) {
|
|
|
9163
10308
|
}
|
|
9164
10309
|
}
|
|
9165
10310
|
function toAbsolute(projectRoot, maybeRelative) {
|
|
9166
|
-
return
|
|
10311
|
+
return path14.isAbsolute(maybeRelative) ? maybeRelative : path14.join(projectRoot, maybeRelative);
|
|
9167
10312
|
}
|
|
9168
10313
|
function isProjectScopedConfigPath(configPath) {
|
|
9169
|
-
return
|
|
10314
|
+
return path14.basename(configPath) === "codebase-index.json" && path14.basename(path14.dirname(configPath)) === ".opencode";
|
|
9170
10315
|
}
|
|
9171
10316
|
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
9172
10317
|
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
9173
10318
|
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
9174
10319
|
values,
|
|
9175
|
-
|
|
10320
|
+
path14.dirname(path14.dirname(resolvedConfigPath)),
|
|
9176
10321
|
projectRoot
|
|
9177
10322
|
) : rebasePathEntries(
|
|
9178
10323
|
values,
|
|
9179
|
-
|
|
10324
|
+
path14.dirname(resolvedConfigPath),
|
|
9180
10325
|
projectRoot
|
|
9181
10326
|
);
|
|
9182
10327
|
if (Array.isArray(config.knowledgeBases)) {
|
|
@@ -9189,7 +10334,7 @@ function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfi
|
|
|
9189
10334
|
}
|
|
9190
10335
|
function loadRawConfig(projectRoot, configPath) {
|
|
9191
10336
|
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
9192
|
-
if (fromPath &&
|
|
10337
|
+
if (fromPath && existsSync8(fromPath)) {
|
|
9193
10338
|
return normalizeEvalConfigKnowledgeBases(
|
|
9194
10339
|
parseJsonConfigFile(fromPath),
|
|
9195
10340
|
projectRoot,
|
|
@@ -9197,15 +10342,15 @@ function loadRawConfig(projectRoot, configPath) {
|
|
|
9197
10342
|
);
|
|
9198
10343
|
}
|
|
9199
10344
|
const projectConfig = resolveProjectConfigPath(projectRoot);
|
|
9200
|
-
if (
|
|
10345
|
+
if (existsSync8(projectConfig)) {
|
|
9201
10346
|
return normalizeEvalConfigKnowledgeBases(
|
|
9202
10347
|
parseJsonConfigFile(projectConfig),
|
|
9203
10348
|
projectRoot,
|
|
9204
10349
|
projectConfig
|
|
9205
10350
|
);
|
|
9206
10351
|
}
|
|
9207
|
-
const globalConfig =
|
|
9208
|
-
if (
|
|
10352
|
+
const globalConfig = path14.join(os5.homedir(), ".config", "opencode", "codebase-index.json");
|
|
10353
|
+
if (existsSync8(globalConfig)) {
|
|
9209
10354
|
return parseJsonConfigFile(globalConfig);
|
|
9210
10355
|
}
|
|
9211
10356
|
return {};
|
|
@@ -9214,24 +10359,24 @@ function getIndexRootPath(projectRoot, scope) {
|
|
|
9214
10359
|
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
9215
10360
|
}
|
|
9216
10361
|
function getLocalProjectIndexRoot(projectRoot) {
|
|
9217
|
-
return
|
|
10362
|
+
return path14.join(projectRoot, ".opencode", "index");
|
|
9218
10363
|
}
|
|
9219
10364
|
function getLocalProjectConfigPath(projectRoot) {
|
|
9220
|
-
return
|
|
10365
|
+
return path14.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9221
10366
|
}
|
|
9222
10367
|
function clearIndexRoot(projectRoot, scope) {
|
|
9223
10368
|
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
9224
|
-
if (
|
|
9225
|
-
|
|
10369
|
+
if (existsSync8(indexRoot)) {
|
|
10370
|
+
rmSync2(indexRoot, { recursive: true, force: true });
|
|
9226
10371
|
}
|
|
9227
10372
|
}
|
|
9228
10373
|
function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
9229
10374
|
const localConfigPath = getLocalProjectConfigPath(projectRoot);
|
|
9230
10375
|
const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
|
|
9231
|
-
if (!configPath &&
|
|
10376
|
+
if (!configPath && existsSync8(localConfigPath)) {
|
|
9232
10377
|
return localConfigPath;
|
|
9233
10378
|
}
|
|
9234
|
-
if (!
|
|
10379
|
+
if (!existsSync8(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
|
|
9235
10380
|
return resolvedConfigPath;
|
|
9236
10381
|
}
|
|
9237
10382
|
const sourceConfig = normalizeEvalConfigKnowledgeBases(
|
|
@@ -9239,8 +10384,8 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
|
9239
10384
|
projectRoot,
|
|
9240
10385
|
resolvedConfigPath
|
|
9241
10386
|
);
|
|
9242
|
-
|
|
9243
|
-
|
|
10387
|
+
mkdirSync4(path14.dirname(localConfigPath), { recursive: true });
|
|
10388
|
+
writeFileSync4(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
9244
10389
|
return localConfigPath;
|
|
9245
10390
|
}
|
|
9246
10391
|
function loadParsedConfig(projectRoot, configPath) {
|
|
@@ -9273,9 +10418,9 @@ function getEmbeddingCostPer1MTokens(embeddingProvider) {
|
|
|
9273
10418
|
}
|
|
9274
10419
|
|
|
9275
10420
|
// src/eval/schema.ts
|
|
9276
|
-
import { readFileSync as
|
|
10421
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
9277
10422
|
function parseJsonFile(filePath) {
|
|
9278
|
-
const content =
|
|
10423
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
9279
10424
|
try {
|
|
9280
10425
|
return JSON.parse(content);
|
|
9281
10426
|
} catch (error) {
|
|
@@ -9289,23 +10434,23 @@ function isRecord2(value) {
|
|
|
9289
10434
|
function isStringArray3(value) {
|
|
9290
10435
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
9291
10436
|
}
|
|
9292
|
-
function asPositiveNumber(value,
|
|
10437
|
+
function asPositiveNumber(value, path27) {
|
|
9293
10438
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
9294
|
-
throw new Error(`${
|
|
10439
|
+
throw new Error(`${path27} must be a non-negative number`);
|
|
9295
10440
|
}
|
|
9296
10441
|
return value;
|
|
9297
10442
|
}
|
|
9298
|
-
function parseQueryType(value,
|
|
10443
|
+
function parseQueryType(value, path27) {
|
|
9299
10444
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
9300
10445
|
return value;
|
|
9301
10446
|
}
|
|
9302
10447
|
throw new Error(
|
|
9303
|
-
`${
|
|
10448
|
+
`${path27} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
9304
10449
|
);
|
|
9305
10450
|
}
|
|
9306
|
-
function parseExpected(input,
|
|
10451
|
+
function parseExpected(input, path27) {
|
|
9307
10452
|
if (!isRecord2(input)) {
|
|
9308
|
-
throw new Error(`${
|
|
10453
|
+
throw new Error(`${path27} must be an object`);
|
|
9309
10454
|
}
|
|
9310
10455
|
const filePathRaw = input.filePath;
|
|
9311
10456
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -9314,16 +10459,16 @@ function parseExpected(input, path25) {
|
|
|
9314
10459
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
9315
10460
|
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
9316
10461
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
9317
|
-
throw new Error(`${
|
|
10462
|
+
throw new Error(`${path27} must include either expected.filePath or expected.acceptableFiles`);
|
|
9318
10463
|
}
|
|
9319
10464
|
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
9320
|
-
throw new Error(`${
|
|
10465
|
+
throw new Error(`${path27}.acceptableFiles must be an array of strings`);
|
|
9321
10466
|
}
|
|
9322
10467
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
9323
|
-
throw new Error(`${
|
|
10468
|
+
throw new Error(`${path27}.symbol must be a string when provided`);
|
|
9324
10469
|
}
|
|
9325
10470
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
9326
|
-
throw new Error(`${
|
|
10471
|
+
throw new Error(`${path27}.branch must be a string when provided`);
|
|
9327
10472
|
}
|
|
9328
10473
|
return {
|
|
9329
10474
|
filePath,
|
|
@@ -9333,25 +10478,25 @@ function parseExpected(input, path25) {
|
|
|
9333
10478
|
};
|
|
9334
10479
|
}
|
|
9335
10480
|
function parseQuery(input, index) {
|
|
9336
|
-
const
|
|
10481
|
+
const path27 = `queries[${index}]`;
|
|
9337
10482
|
if (!isRecord2(input)) {
|
|
9338
|
-
throw new Error(`${
|
|
10483
|
+
throw new Error(`${path27} must be an object`);
|
|
9339
10484
|
}
|
|
9340
10485
|
const id = input.id;
|
|
9341
10486
|
const query = input.query;
|
|
9342
10487
|
const queryType = input.queryType;
|
|
9343
10488
|
const expected = input.expected;
|
|
9344
10489
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
9345
|
-
throw new Error(`${
|
|
10490
|
+
throw new Error(`${path27}.id must be a non-empty string`);
|
|
9346
10491
|
}
|
|
9347
10492
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
9348
|
-
throw new Error(`${
|
|
10493
|
+
throw new Error(`${path27}.query must be a non-empty string`);
|
|
9349
10494
|
}
|
|
9350
10495
|
return {
|
|
9351
10496
|
id,
|
|
9352
10497
|
query,
|
|
9353
|
-
queryType: parseQueryType(queryType, `${
|
|
9354
|
-
expected: parseExpected(expected, `${
|
|
10498
|
+
queryType: parseQueryType(queryType, `${path27}.queryType`),
|
|
10499
|
+
expected: parseExpected(expected, `${path27}.expected`)
|
|
9355
10500
|
};
|
|
9356
10501
|
}
|
|
9357
10502
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -9534,13 +10679,13 @@ async function runEvaluation(options) {
|
|
|
9534
10679
|
};
|
|
9535
10680
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
9536
10681
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
9537
|
-
writeJson(
|
|
9538
|
-
writeJson(
|
|
10682
|
+
writeJson(path15.join(outputDir, "summary.json"), summary);
|
|
10683
|
+
writeJson(path15.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
9539
10684
|
let comparison;
|
|
9540
10685
|
if (againstPath) {
|
|
9541
10686
|
const baseline = loadSummary(againstPath);
|
|
9542
10687
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
9543
|
-
writeJson(
|
|
10688
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9544
10689
|
}
|
|
9545
10690
|
let gate;
|
|
9546
10691
|
if (options.ciMode) {
|
|
@@ -9550,10 +10695,10 @@ async function runEvaluation(options) {
|
|
|
9550
10695
|
const budget = loadBudget(budgetPath);
|
|
9551
10696
|
if (!comparison && budget.baselinePath) {
|
|
9552
10697
|
const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
|
|
9553
|
-
if (
|
|
10698
|
+
if (existsSync9(resolvedBaseline)) {
|
|
9554
10699
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
9555
10700
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
9556
|
-
writeJson(
|
|
10701
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9557
10702
|
} else if (budget.failOnMissingBaseline) {
|
|
9558
10703
|
throw new Error(
|
|
9559
10704
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -9563,7 +10708,7 @@ async function runEvaluation(options) {
|
|
|
9563
10708
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
9564
10709
|
}
|
|
9565
10710
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
9566
|
-
writeText(
|
|
10711
|
+
writeText(path15.join(outputDir, "summary.md"), markdown);
|
|
9567
10712
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
9568
10713
|
} finally {
|
|
9569
10714
|
await indexer.close();
|
|
@@ -9621,23 +10766,23 @@ async function runSweep(options, sweep) {
|
|
|
9621
10766
|
bestByMrrAt10,
|
|
9622
10767
|
bestByP95Latency
|
|
9623
10768
|
};
|
|
9624
|
-
writeJson(
|
|
10769
|
+
writeJson(path15.join(outputDir, "compare.json"), aggregate);
|
|
9625
10770
|
const md = createSummaryMarkdown(
|
|
9626
10771
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
9627
10772
|
bestByHitAt5?.comparison,
|
|
9628
10773
|
void 0,
|
|
9629
10774
|
aggregate
|
|
9630
10775
|
);
|
|
9631
|
-
writeText(
|
|
9632
|
-
writeJson(
|
|
10776
|
+
writeText(path15.join(outputDir, "summary.md"), md);
|
|
10777
|
+
writeJson(path15.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
9633
10778
|
return { outputDir, aggregate };
|
|
9634
10779
|
}
|
|
9635
10780
|
|
|
9636
10781
|
// src/eval/cli.ts
|
|
9637
|
-
import * as
|
|
10782
|
+
import * as path17 from "path";
|
|
9638
10783
|
|
|
9639
10784
|
// src/eval/cli-parser.ts
|
|
9640
|
-
import * as
|
|
10785
|
+
import * as path16 from "path";
|
|
9641
10786
|
function printUsage() {
|
|
9642
10787
|
console.log(`
|
|
9643
10788
|
Usage:
|
|
@@ -9709,12 +10854,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9709
10854
|
const arg = argv[i];
|
|
9710
10855
|
const next = argv[i + 1];
|
|
9711
10856
|
if (arg === "--project" && next) {
|
|
9712
|
-
parsed.projectRoot =
|
|
10857
|
+
parsed.projectRoot = path16.resolve(cwd, next);
|
|
9713
10858
|
i += 1;
|
|
9714
10859
|
continue;
|
|
9715
10860
|
}
|
|
9716
10861
|
if (arg === "--config" && next) {
|
|
9717
|
-
parsed.configPath =
|
|
10862
|
+
parsed.configPath = path16.resolve(cwd, next);
|
|
9718
10863
|
i += 1;
|
|
9719
10864
|
continue;
|
|
9720
10865
|
}
|
|
@@ -9910,22 +11055,22 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9910
11055
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
9911
11056
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
9912
11057
|
}
|
|
9913
|
-
const currentSummary = loadSummary(
|
|
11058
|
+
const currentSummary = loadSummary(path17.resolve(parsed.projectRoot, currentPath), {
|
|
9914
11059
|
allowLegacyDiversityMetrics: true
|
|
9915
11060
|
});
|
|
9916
|
-
const baselineSummary = loadSummary(
|
|
11061
|
+
const baselineSummary = loadSummary(path17.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
9917
11062
|
allowLegacyDiversityMetrics: true
|
|
9918
11063
|
});
|
|
9919
11064
|
const comparison = compareSummaries(
|
|
9920
11065
|
currentSummary,
|
|
9921
11066
|
baselineSummary,
|
|
9922
|
-
|
|
11067
|
+
path17.resolve(parsed.projectRoot, parsed.againstPath)
|
|
9923
11068
|
);
|
|
9924
|
-
const outputDir = createRunDirectory(
|
|
11069
|
+
const outputDir = createRunDirectory(path17.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
9925
11070
|
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
9926
|
-
writeJson(
|
|
9927
|
-
writeText(
|
|
9928
|
-
writeJson(
|
|
11071
|
+
writeJson(path17.join(outputDir, "compare.json"), comparison);
|
|
11072
|
+
writeText(path17.join(outputDir, "summary.md"), summaryMd);
|
|
11073
|
+
writeJson(path17.join(outputDir, "summary.json"), currentSummary);
|
|
9929
11074
|
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
9930
11075
|
return 0;
|
|
9931
11076
|
}
|
|
@@ -9934,7 +11079,7 @@ async function handleEvalCommand(args, cwd) {
|
|
|
9934
11079
|
|
|
9935
11080
|
// src/mcp-server.ts
|
|
9936
11081
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9937
|
-
import { readFileSync as
|
|
11082
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
9938
11083
|
|
|
9939
11084
|
// src/mcp-server/register-prompts.ts
|
|
9940
11085
|
import { z } from "zod";
|
|
@@ -10125,6 +11270,10 @@ function formatStatus(status) {
|
|
|
10125
11270
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
10126
11271
|
}
|
|
10127
11272
|
}
|
|
11273
|
+
if (status.warning) {
|
|
11274
|
+
lines.push("");
|
|
11275
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
11276
|
+
}
|
|
10128
11277
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
10129
11278
|
lines.push("");
|
|
10130
11279
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -10170,6 +11319,17 @@ function calculatePercentage(progress) {
|
|
|
10170
11319
|
if (progress.phase === "storing") return 95;
|
|
10171
11320
|
return 0;
|
|
10172
11321
|
}
|
|
11322
|
+
function formatCodebasePeek(results) {
|
|
11323
|
+
if (results.length === 0) {
|
|
11324
|
+
return "No matching code found. Try a different query or run index_codebase first.";
|
|
11325
|
+
}
|
|
11326
|
+
const formatted = results.map((r, idx) => {
|
|
11327
|
+
const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
11328
|
+
const name = r.name ? `"${r.name}"` : "(anonymous)";
|
|
11329
|
+
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})${formatBlame(r)}`;
|
|
11330
|
+
});
|
|
11331
|
+
return formatted.join("\n");
|
|
11332
|
+
}
|
|
10173
11333
|
function formatHealthCheck(result) {
|
|
10174
11334
|
if (result.resetCorruptedIndex) {
|
|
10175
11335
|
return result.warning ?? "Detected a corrupted local index and reset it. Run index_codebase to rebuild search data.";
|
|
@@ -10198,16 +11358,68 @@ function formatHealthCheck(result) {
|
|
|
10198
11358
|
}
|
|
10199
11359
|
return lines.join("\n");
|
|
10200
11360
|
}
|
|
11361
|
+
function formatCallGraphCallers(name, callers, relationshipType) {
|
|
11362
|
+
if (callers.length === 0) {
|
|
11363
|
+
return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
|
|
11364
|
+
}
|
|
11365
|
+
const formatted = callers.map((edge, index) => {
|
|
11366
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
11367
|
+
return `[${index + 1}] \u2190 from ${edge.fromSymbolName ?? "<unknown>"} in ${edge.fromSymbolFilePath ?? "<unknown file>"} [${edge.fromSymbolId}] (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? " [resolved]" : " [unresolved]"}`;
|
|
11368
|
+
});
|
|
11369
|
+
return `"${name}" is called by ${callers.length} function(s):
|
|
11370
|
+
|
|
11371
|
+
${formatted.join("\n")}`;
|
|
11372
|
+
}
|
|
11373
|
+
function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
11374
|
+
if (callees.length === 0) {
|
|
11375
|
+
return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
|
|
11376
|
+
}
|
|
11377
|
+
return callees.map((edge, index) => {
|
|
11378
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
11379
|
+
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
11380
|
+
}).join("\n");
|
|
11381
|
+
}
|
|
11382
|
+
function formatCallGraphPath(from, to, path27) {
|
|
11383
|
+
if (path27.length === 0) {
|
|
11384
|
+
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
11385
|
+
}
|
|
11386
|
+
const formatted = path27.map((hop, index) => {
|
|
11387
|
+
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
11388
|
+
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
11389
|
+
return `${prefix} ${hop.symbolName}${location}`;
|
|
11390
|
+
});
|
|
11391
|
+
return `Path (${path27.length} hops):
|
|
11392
|
+
${formatted.join("\n")}`;
|
|
11393
|
+
}
|
|
10201
11394
|
function formatResultHeader(result, index) {
|
|
10202
11395
|
return result.name ? `[${index + 1}] ${result.chunkType} "${result.name}" in ${result.filePath}:${result.startLine}-${result.endLine}` : `[${index + 1}] ${result.chunkType} in ${result.filePath}:${result.startLine}-${result.endLine}`;
|
|
10203
11396
|
}
|
|
11397
|
+
function formatBlame(result) {
|
|
11398
|
+
if (!result.blame) {
|
|
11399
|
+
return "";
|
|
11400
|
+
}
|
|
11401
|
+
const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
|
|
11402
|
+
return `
|
|
11403
|
+
${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
|
|
11404
|
+
}
|
|
10204
11405
|
function formatDefinitionLookup(results, query) {
|
|
10205
11406
|
if (results.length === 0) {
|
|
10206
11407
|
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
10207
11408
|
}
|
|
10208
11409
|
const formatted = results.map((r, idx) => {
|
|
10209
11410
|
const header = formatResultHeader(r, idx);
|
|
10210
|
-
return `${header} (score: ${r.score.toFixed(2)})
|
|
11411
|
+
return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
|
|
11412
|
+
\`\`\`
|
|
11413
|
+
${truncateContent(r.content)}
|
|
11414
|
+
\`\`\``;
|
|
11415
|
+
});
|
|
11416
|
+
return formatted.join("\n\n");
|
|
11417
|
+
}
|
|
11418
|
+
function formatSearchResults(results, scoreFormat = "similarity") {
|
|
11419
|
+
const formatted = results.map((r, idx) => {
|
|
11420
|
+
const header = formatResultHeader(r, idx);
|
|
11421
|
+
const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
|
|
11422
|
+
return `${header} ${scoreLabel}${formatBlame(r)}
|
|
10211
11423
|
\`\`\`
|
|
10212
11424
|
${truncateContent(r.content)}
|
|
10213
11425
|
\`\`\``;
|
|
@@ -10267,12 +11479,12 @@ function formatPrImpact(result) {
|
|
|
10267
11479
|
}
|
|
10268
11480
|
|
|
10269
11481
|
// src/tools/operations.ts
|
|
10270
|
-
import { existsSync as
|
|
10271
|
-
import * as
|
|
11482
|
+
import { existsSync as existsSync12, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
11483
|
+
import * as path21 from "path";
|
|
10272
11484
|
|
|
10273
11485
|
// src/config/merger.ts
|
|
10274
|
-
import { existsSync as
|
|
10275
|
-
import * as
|
|
11486
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
11487
|
+
import * as path18 from "path";
|
|
10276
11488
|
var PROJECT_OVERRIDE_KEYS = [
|
|
10277
11489
|
"embeddingProvider",
|
|
10278
11490
|
"customProvider",
|
|
@@ -10305,8 +11517,8 @@ function mergeUniqueStringArray(values) {
|
|
|
10305
11517
|
return [...new Set(values.map((value) => String(value).trim()))];
|
|
10306
11518
|
}
|
|
10307
11519
|
function normalizeKnowledgeBasePath(value) {
|
|
10308
|
-
let normalized =
|
|
10309
|
-
const root =
|
|
11520
|
+
let normalized = path18.normalize(String(value).trim());
|
|
11521
|
+
const root = path18.parse(normalized).root;
|
|
10310
11522
|
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
10311
11523
|
normalized = normalized.slice(0, -1);
|
|
10312
11524
|
}
|
|
@@ -10340,11 +11552,11 @@ function validateConfigLayerShape(rawConfig, filePath) {
|
|
|
10340
11552
|
return rawConfig;
|
|
10341
11553
|
}
|
|
10342
11554
|
function loadJsonFile(filePath) {
|
|
10343
|
-
if (!
|
|
11555
|
+
if (!existsSync10(filePath)) {
|
|
10344
11556
|
return null;
|
|
10345
11557
|
}
|
|
10346
11558
|
try {
|
|
10347
|
-
const content =
|
|
11559
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
10348
11560
|
return validateConfigLayerShape(JSON.parse(content), filePath);
|
|
10349
11561
|
} catch (error) {
|
|
10350
11562
|
if (error instanceof Error && error.message.startsWith("Config file ")) {
|
|
@@ -10359,8 +11571,8 @@ function loadConfigFile(filePath) {
|
|
|
10359
11571
|
}
|
|
10360
11572
|
function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
|
|
10361
11573
|
const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
|
|
10362
|
-
|
|
10363
|
-
|
|
11574
|
+
mkdirSync5(path18.dirname(localConfigPath), { recursive: true });
|
|
11575
|
+
writeFileSync5(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
10364
11576
|
return localConfigPath;
|
|
10365
11577
|
}
|
|
10366
11578
|
function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
@@ -10370,7 +11582,7 @@ function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
|
10370
11582
|
return {};
|
|
10371
11583
|
}
|
|
10372
11584
|
const normalizedConfig = { ...projectConfig };
|
|
10373
|
-
const projectConfigBaseDir =
|
|
11585
|
+
const projectConfigBaseDir = path18.dirname(path18.dirname(projectConfigPath));
|
|
10374
11586
|
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
10375
11587
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
10376
11588
|
normalizedConfig.knowledgeBases,
|
|
@@ -10434,19 +11646,19 @@ function loadMergedConfig(projectRoot, host = "opencode") {
|
|
|
10434
11646
|
}
|
|
10435
11647
|
|
|
10436
11648
|
// src/tools/knowledge-base-paths.ts
|
|
10437
|
-
import * as
|
|
11649
|
+
import * as path19 from "path";
|
|
10438
11650
|
function resolveConfigPathValue(value, baseDir) {
|
|
10439
11651
|
const trimmed = value.trim();
|
|
10440
11652
|
if (!trimmed) {
|
|
10441
11653
|
return trimmed;
|
|
10442
11654
|
}
|
|
10443
|
-
const absolutePath =
|
|
10444
|
-
return
|
|
11655
|
+
const absolutePath = path19.isAbsolute(trimmed) ? trimmed : path19.resolve(baseDir, trimmed);
|
|
11656
|
+
return path19.normalize(absolutePath);
|
|
10445
11657
|
}
|
|
10446
11658
|
|
|
10447
11659
|
// src/tools/config-state.ts
|
|
10448
|
-
import { existsSync as
|
|
10449
|
-
import * as
|
|
11660
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
11661
|
+
import * as path20 from "path";
|
|
10450
11662
|
function normalizeKnowledgeBasePaths(config, projectRoot) {
|
|
10451
11663
|
const normalized = { ...config };
|
|
10452
11664
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
@@ -10470,6 +11682,24 @@ function loadRuntimeConfig(projectRoot, host = "opencode") {
|
|
|
10470
11682
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
10471
11683
|
var configCache = /* @__PURE__ */ new Map();
|
|
10472
11684
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
11685
|
+
function getIndexBusyResult(error) {
|
|
11686
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
11687
|
+
const owner = error.owner;
|
|
11688
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
11689
|
+
if (error.reason === "legacy-lock") {
|
|
11690
|
+
return {
|
|
11691
|
+
kind: "busy",
|
|
11692
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
11693
|
+
};
|
|
11694
|
+
}
|
|
11695
|
+
if (error.reason === "unknown-owner") {
|
|
11696
|
+
return {
|
|
11697
|
+
kind: "busy",
|
|
11698
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
11699
|
+
};
|
|
11700
|
+
}
|
|
11701
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
11702
|
+
}
|
|
10473
11703
|
function getProjectRoot(projectRoot, host) {
|
|
10474
11704
|
if (projectRoot) {
|
|
10475
11705
|
return projectRoot;
|
|
@@ -10514,8 +11744,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
|
|
|
10514
11744
|
}
|
|
10515
11745
|
function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
|
|
10516
11746
|
const root = getProjectRoot(projectRoot, host);
|
|
10517
|
-
const localIndexPath =
|
|
10518
|
-
if (
|
|
11747
|
+
const localIndexPath = path21.join(root, getHostProjectIndexRelativePath(host));
|
|
11748
|
+
if (existsSync12(localIndexPath)) {
|
|
10519
11749
|
return false;
|
|
10520
11750
|
}
|
|
10521
11751
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
@@ -10529,7 +11759,10 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
|
|
|
10529
11759
|
chunkType: options.chunkType,
|
|
10530
11760
|
contextLines: options.contextLines,
|
|
10531
11761
|
metadataOnly: options.metadataOnly,
|
|
10532
|
-
definitionIntent: options.definitionIntent
|
|
11762
|
+
definitionIntent: options.definitionIntent,
|
|
11763
|
+
blameAuthor: options.blameAuthor,
|
|
11764
|
+
blameSha: options.blameSha,
|
|
11765
|
+
blameSince: options.blameSince
|
|
10533
11766
|
});
|
|
10534
11767
|
}
|
|
10535
11768
|
async function findSimilarCode(projectRoot, host, code, options = {}) {
|
|
@@ -10568,35 +11801,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
|
|
|
10568
11801
|
async function runIndexCodebase(projectRoot, host, args, onProgress) {
|
|
10569
11802
|
const root = getProjectRoot(projectRoot, host);
|
|
10570
11803
|
const key = getIndexerCacheKey(root, host);
|
|
10571
|
-
const cachedConfig = configCache.get(key);
|
|
10572
11804
|
let indexer = getIndexerForProject(root, host);
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
chunksProcessed: progress.chunksProcessed,
|
|
10593
|
-
totalChunks: progress.totalChunks,
|
|
10594
|
-
percentage: calculatePercentage(progress)
|
|
11805
|
+
const runtimeConfig = configCache.get(key);
|
|
11806
|
+
try {
|
|
11807
|
+
if (args.estimateOnly) {
|
|
11808
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
11809
|
+
}
|
|
11810
|
+
const runIndex = async (target) => {
|
|
11811
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
11812
|
+
return operation((progress) => {
|
|
11813
|
+
if (onProgress) {
|
|
11814
|
+
return onProgress(formatProgressTitle(progress), {
|
|
11815
|
+
phase: progress.phase,
|
|
11816
|
+
filesProcessed: progress.filesProcessed,
|
|
11817
|
+
totalFiles: progress.totalFiles,
|
|
11818
|
+
chunksProcessed: progress.chunksProcessed,
|
|
11819
|
+
totalChunks: progress.totalChunks,
|
|
11820
|
+
percentage: calculatePercentage(progress)
|
|
11821
|
+
});
|
|
11822
|
+
}
|
|
11823
|
+
return Promise.resolve();
|
|
10595
11824
|
});
|
|
11825
|
+
};
|
|
11826
|
+
let stats;
|
|
11827
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
11828
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
11829
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
11830
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
11831
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
11832
|
+
indexer = getIndexerForProject(root, host);
|
|
11833
|
+
return runIndex(indexer);
|
|
11834
|
+
}, { completeRecoveries: false });
|
|
11835
|
+
} else {
|
|
11836
|
+
stats = await runIndex(indexer);
|
|
10596
11837
|
}
|
|
10597
|
-
return
|
|
10598
|
-
})
|
|
10599
|
-
|
|
11838
|
+
return { kind: "stats", stats };
|
|
11839
|
+
} catch (error) {
|
|
11840
|
+
const busyResult = getIndexBusyResult(error);
|
|
11841
|
+
if (!busyResult) throw error;
|
|
11842
|
+
return busyResult;
|
|
11843
|
+
}
|
|
10600
11844
|
}
|
|
10601
11845
|
async function getIndexStatus(projectRoot, host) {
|
|
10602
11846
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
@@ -10606,6 +11850,15 @@ async function getIndexHealthCheck(projectRoot, host) {
|
|
|
10606
11850
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10607
11851
|
return indexer.healthCheck();
|
|
10608
11852
|
}
|
|
11853
|
+
async function runIndexHealthCheck(projectRoot, host) {
|
|
11854
|
+
try {
|
|
11855
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
|
|
11856
|
+
} catch (error) {
|
|
11857
|
+
const busyResult = getIndexBusyResult(error);
|
|
11858
|
+
if (!busyResult) throw error;
|
|
11859
|
+
return busyResult;
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
10609
11862
|
async function getPrImpact(projectRoot, host, params) {
|
|
10610
11863
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10611
11864
|
return indexer.getPrImpact({
|
|
@@ -10671,13 +11924,6 @@ async function getIndexLogs(projectRoot, host, args) {
|
|
|
10671
11924
|
}
|
|
10672
11925
|
|
|
10673
11926
|
// src/mcp-server/shared.ts
|
|
10674
|
-
var MAX_CONTENT_LINES2 = 30;
|
|
10675
|
-
function truncateContent2(content) {
|
|
10676
|
-
const lines = content.split("\n");
|
|
10677
|
-
if (lines.length <= MAX_CONTENT_LINES2) return content;
|
|
10678
|
-
return lines.slice(0, MAX_CONTENT_LINES2).join("\n") + `
|
|
10679
|
-
// ... (${lines.length - MAX_CONTENT_LINES2} more lines)`;
|
|
10680
|
-
}
|
|
10681
11927
|
var CHUNK_TYPE_ENUM = [
|
|
10682
11928
|
"function",
|
|
10683
11929
|
"class",
|
|
@@ -10703,7 +11949,10 @@ function registerMcpTools(server, runtime) {
|
|
|
10703
11949
|
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10704
11950
|
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10705
11951
|
chunkType: z2.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
10706
|
-
contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
|
|
11952
|
+
contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
|
|
11953
|
+
blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
|
|
11954
|
+
blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
11955
|
+
blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
10707
11956
|
},
|
|
10708
11957
|
async (args) => {
|
|
10709
11958
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -10711,21 +11960,17 @@ function registerMcpTools(server, runtime) {
|
|
|
10711
11960
|
fileType: args.fileType,
|
|
10712
11961
|
directory: args.directory,
|
|
10713
11962
|
chunkType: args.chunkType,
|
|
10714
|
-
contextLines: args.contextLines
|
|
11963
|
+
contextLines: args.contextLines,
|
|
11964
|
+
blameAuthor: args.blameAuthor,
|
|
11965
|
+
blameSha: args.blameSha,
|
|
11966
|
+
blameSince: args.blameSince
|
|
10715
11967
|
});
|
|
10716
11968
|
if (results.length === 0) {
|
|
10717
11969
|
return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
|
|
10718
11970
|
}
|
|
10719
|
-
const formatted = results.map((r, idx) => {
|
|
10720
|
-
const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10721
|
-
return `${header} (score: ${r.score.toFixed(2)})
|
|
10722
|
-
\`\`\`
|
|
10723
|
-
${truncateContent2(r.content)}
|
|
10724
|
-
\`\`\``;
|
|
10725
|
-
});
|
|
10726
11971
|
return { content: [{ type: "text", text: `Found ${results.length} results for "${args.query}":
|
|
10727
11972
|
|
|
10728
|
-
${
|
|
11973
|
+
${formatSearchResults(results, "score")}` }] };
|
|
10729
11974
|
}
|
|
10730
11975
|
);
|
|
10731
11976
|
server.tool(
|
|
@@ -10736,7 +11981,10 @@ ${formatted.join("\n\n")}` }] };
|
|
|
10736
11981
|
limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
|
|
10737
11982
|
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10738
11983
|
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10739
|
-
chunkType: z2.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type")
|
|
11984
|
+
chunkType: z2.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
11985
|
+
blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
|
|
11986
|
+
blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
11987
|
+
blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
10740
11988
|
},
|
|
10741
11989
|
async (args) => {
|
|
10742
11990
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -10744,19 +11992,17 @@ ${formatted.join("\n\n")}` }] };
|
|
|
10744
11992
|
fileType: args.fileType,
|
|
10745
11993
|
directory: args.directory,
|
|
10746
11994
|
chunkType: args.chunkType,
|
|
10747
|
-
metadataOnly: true
|
|
11995
|
+
metadataOnly: true,
|
|
11996
|
+
blameAuthor: args.blameAuthor,
|
|
11997
|
+
blameSha: args.blameSha,
|
|
11998
|
+
blameSince: args.blameSince
|
|
10748
11999
|
});
|
|
10749
12000
|
if (results.length === 0) {
|
|
10750
12001
|
return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
|
|
10751
12002
|
}
|
|
10752
|
-
const formatted = results.map((r, idx) => {
|
|
10753
|
-
const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10754
|
-
const name = r.name ? `"${r.name}"` : "(anonymous)";
|
|
10755
|
-
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})`;
|
|
10756
|
-
});
|
|
10757
12003
|
return { content: [{ type: "text", text: `Found ${results.length} locations for "${args.query}":
|
|
10758
12004
|
|
|
10759
|
-
${
|
|
12005
|
+
${formatCodebasePeek(results)}
|
|
10760
12006
|
|
|
10761
12007
|
Use Read tool to examine specific files.` }] };
|
|
10762
12008
|
}
|
|
@@ -10771,8 +12017,13 @@ Use Read tool to examine specific files.` }] };
|
|
|
10771
12017
|
},
|
|
10772
12018
|
async (args) => {
|
|
10773
12019
|
const result = await runIndexCodebase(runtime.projectRoot, runtime.host, args);
|
|
10774
|
-
|
|
10775
|
-
|
|
12020
|
+
if (result.kind === "estimate") {
|
|
12021
|
+
return { content: [{ type: "text", text: formatCostEstimate(result.estimate) }] };
|
|
12022
|
+
}
|
|
12023
|
+
if (result.kind === "busy") {
|
|
12024
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12025
|
+
}
|
|
12026
|
+
return { content: [{ type: "text", text: formatIndexStats(result.stats, args.verbose ?? false) }] };
|
|
10776
12027
|
}
|
|
10777
12028
|
);
|
|
10778
12029
|
server.tool(
|
|
@@ -10789,8 +12040,11 @@ Use Read tool to examine specific files.` }] };
|
|
|
10789
12040
|
"Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
|
|
10790
12041
|
{},
|
|
10791
12042
|
async () => {
|
|
10792
|
-
const result = await
|
|
10793
|
-
|
|
12043
|
+
const result = await runIndexHealthCheck(runtime.projectRoot, runtime.host);
|
|
12044
|
+
if (result.kind === "busy") {
|
|
12045
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12046
|
+
}
|
|
12047
|
+
return { content: [{ type: "text", text: formatHealthCheck(result.health) }] };
|
|
10794
12048
|
}
|
|
10795
12049
|
);
|
|
10796
12050
|
server.tool(
|
|
@@ -10837,16 +12091,9 @@ Use Read tool to examine specific files.` }] };
|
|
|
10837
12091
|
if (results.length === 0) {
|
|
10838
12092
|
return { content: [{ type: "text", text: "No similar code found. Try a different snippet or run index_codebase first." }] };
|
|
10839
12093
|
}
|
|
10840
|
-
const formatted = results.map((r, idx) => {
|
|
10841
|
-
const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
10842
|
-
return `${header} (similarity: ${(r.score * 100).toFixed(1)}%)
|
|
10843
|
-
\`\`\`
|
|
10844
|
-
${truncateContent2(r.content)}
|
|
10845
|
-
\`\`\``;
|
|
10846
|
-
});
|
|
10847
12094
|
return { content: [{ type: "text", text: `Found ${results.length} similar code blocks:
|
|
10848
12095
|
|
|
10849
|
-
${
|
|
12096
|
+
${formatSearchResults(results)}` }] };
|
|
10850
12097
|
}
|
|
10851
12098
|
);
|
|
10852
12099
|
server.tool(
|
|
@@ -10882,28 +12129,10 @@ ${formatted.join("\n\n")}` }] };
|
|
|
10882
12129
|
return { content: [{ type: "text", text: "Error: 'symbolId' is required when direction is 'callees'." }] };
|
|
10883
12130
|
}
|
|
10884
12131
|
const { callees } = await getCallGraphData(runtime.projectRoot, runtime.host, args);
|
|
10885
|
-
|
|
10886
|
-
return { content: [{ type: "text", text: `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
|
|
10887
|
-
}
|
|
10888
|
-
const formatted2 = callees.map((e, i) => {
|
|
10889
|
-
const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
|
|
10890
|
-
return `[${i + 1}] \u2192 ${e.targetName} (${e.callType})${conf} at line ${e.line}${e.isResolved ? ` [resolved: ${e.toSymbolId}]` : " [unresolved]"}`;
|
|
10891
|
-
});
|
|
10892
|
-
return { content: [{ type: "text", text: `Callees (${callees.length}):
|
|
10893
|
-
|
|
10894
|
-
${formatted2.join("\n")}` }] };
|
|
12132
|
+
return { content: [{ type: "text", text: formatCallGraphCallees(args.symbolId, callees, args.relationshipType) }] };
|
|
10895
12133
|
}
|
|
10896
12134
|
const { callers } = await getCallGraphData(runtime.projectRoot, runtime.host, args);
|
|
10897
|
-
|
|
10898
|
-
return { content: [{ type: "text", text: `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}.` }] };
|
|
10899
|
-
}
|
|
10900
|
-
const formatted = callers.map((e, i) => {
|
|
10901
|
-
const conf = e.confidence !== "Direct" ? ` [${e.confidence.toLowerCase()}]` : "";
|
|
10902
|
-
return `[${i + 1}] \u2190 from ${e.fromSymbolName ?? "<unknown>"} in ${e.fromSymbolFilePath ?? "<unknown file>"} [${e.fromSymbolId}] (${e.callType})${conf} at line ${e.line}${e.isResolved ? " [resolved]" : " [unresolved]"}`;
|
|
10903
|
-
});
|
|
10904
|
-
return { content: [{ type: "text", text: `"${args.name}" is called by ${callers.length} function(s):
|
|
10905
|
-
|
|
10906
|
-
${formatted.join("\n")}` }] };
|
|
12135
|
+
return { content: [{ type: "text", text: formatCallGraphCallers(args.name, callers, args.relationshipType) }] };
|
|
10907
12136
|
}
|
|
10908
12137
|
);
|
|
10909
12138
|
server.tool(
|
|
@@ -10915,17 +12144,8 @@ ${formatted.join("\n")}` }] };
|
|
|
10915
12144
|
maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
|
|
10916
12145
|
},
|
|
10917
12146
|
async (args) => {
|
|
10918
|
-
const
|
|
10919
|
-
|
|
10920
|
-
return { content: [{ type: "text", text: `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.` }] };
|
|
10921
|
-
}
|
|
10922
|
-
const formatted = path25.map((hop, i) => {
|
|
10923
|
-
const prefix = i === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10924
|
-
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10925
|
-
return `${prefix} ${hop.symbolName}${location}`;
|
|
10926
|
-
});
|
|
10927
|
-
return { content: [{ type: "text", text: `Path (${path25.length} hops):
|
|
10928
|
-
${formatted.join("\n")}` }] };
|
|
12147
|
+
const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
|
|
12148
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
10929
12149
|
}
|
|
10930
12150
|
);
|
|
10931
12151
|
server.tool(
|
|
@@ -10960,7 +12180,7 @@ ${formatted.join("\n")}` }] };
|
|
|
10960
12180
|
|
|
10961
12181
|
// src/mcp-server.ts
|
|
10962
12182
|
function getPackageVersion() {
|
|
10963
|
-
const raw = JSON.parse(
|
|
12183
|
+
const raw = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8"));
|
|
10964
12184
|
if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
|
|
10965
12185
|
return raw.version;
|
|
10966
12186
|
}
|
|
@@ -10981,18 +12201,37 @@ function createMcpServer(projectRoot, config, host = "opencode") {
|
|
|
10981
12201
|
}
|
|
10982
12202
|
|
|
10983
12203
|
// src/utils/auto-index.ts
|
|
12204
|
+
var INITIAL_RETRY_DELAY_MS = 50;
|
|
12205
|
+
var MAX_RETRY_DELAY_MS = 500;
|
|
12206
|
+
var autoIndexStates = /* @__PURE__ */ new WeakMap();
|
|
10984
12207
|
function getErrorMessage3(error) {
|
|
10985
12208
|
return error instanceof Error ? error.message : String(error);
|
|
10986
12209
|
}
|
|
10987
|
-
function
|
|
10988
|
-
indexer.
|
|
10989
|
-
|
|
10990
|
-
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
10991
|
-
});
|
|
12210
|
+
function runAutoIndex(indexer, projectRoot, state) {
|
|
12211
|
+
void indexer.index().then(() => {
|
|
12212
|
+
autoIndexStates.delete(indexer);
|
|
10992
12213
|
}).catch((error) => {
|
|
10993
|
-
|
|
12214
|
+
if (!isTransientIndexLockContention(error)) {
|
|
12215
|
+
autoIndexStates.delete(indexer);
|
|
12216
|
+
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
12217
|
+
return;
|
|
12218
|
+
}
|
|
12219
|
+
const retryDelayMs = state.retryDelayMs;
|
|
12220
|
+
state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
|
|
12221
|
+
const retryTimer = setTimeout(() => {
|
|
12222
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12223
|
+
}, retryDelayMs);
|
|
12224
|
+
retryTimer.unref?.();
|
|
10994
12225
|
});
|
|
10995
12226
|
}
|
|
12227
|
+
function startAutoIndex(indexer, projectRoot) {
|
|
12228
|
+
if (autoIndexStates.has(indexer)) return;
|
|
12229
|
+
const state = {
|
|
12230
|
+
retryDelayMs: INITIAL_RETRY_DELAY_MS
|
|
12231
|
+
};
|
|
12232
|
+
autoIndexStates.set(indexer, state);
|
|
12233
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12234
|
+
}
|
|
10996
12235
|
|
|
10997
12236
|
// node_modules/chokidar/index.js
|
|
10998
12237
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
@@ -11084,7 +12323,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
11084
12323
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
11085
12324
|
const statMethod = opts.lstat ? lstat : stat;
|
|
11086
12325
|
if (wantBigintFsStats) {
|
|
11087
|
-
this._stat = (
|
|
12326
|
+
this._stat = (path27) => statMethod(path27, { bigint: true });
|
|
11088
12327
|
} else {
|
|
11089
12328
|
this._stat = statMethod;
|
|
11090
12329
|
}
|
|
@@ -11109,8 +12348,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
11109
12348
|
const par = this.parent;
|
|
11110
12349
|
const fil = par && par.files;
|
|
11111
12350
|
if (fil && fil.length > 0) {
|
|
11112
|
-
const { path:
|
|
11113
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
12351
|
+
const { path: path27, depth } = par;
|
|
12352
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path27));
|
|
11114
12353
|
const awaited = await Promise.all(slice);
|
|
11115
12354
|
for (const entry of awaited) {
|
|
11116
12355
|
if (!entry)
|
|
@@ -11150,20 +12389,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
11150
12389
|
this.reading = false;
|
|
11151
12390
|
}
|
|
11152
12391
|
}
|
|
11153
|
-
async _exploreDir(
|
|
12392
|
+
async _exploreDir(path27, depth) {
|
|
11154
12393
|
let files;
|
|
11155
12394
|
try {
|
|
11156
|
-
files = await readdir(
|
|
12395
|
+
files = await readdir(path27, this._rdOptions);
|
|
11157
12396
|
} catch (error) {
|
|
11158
12397
|
this._onError(error);
|
|
11159
12398
|
}
|
|
11160
|
-
return { files, depth, path:
|
|
12399
|
+
return { files, depth, path: path27 };
|
|
11161
12400
|
}
|
|
11162
|
-
async _formatEntry(dirent,
|
|
12401
|
+
async _formatEntry(dirent, path27) {
|
|
11163
12402
|
let entry;
|
|
11164
12403
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
11165
12404
|
try {
|
|
11166
|
-
const fullPath = presolve(pjoin(
|
|
12405
|
+
const fullPath = presolve(pjoin(path27, basename5));
|
|
11167
12406
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
|
|
11168
12407
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
11169
12408
|
} catch (err) {
|
|
@@ -11563,16 +12802,16 @@ var delFromSet = (main2, prop, item) => {
|
|
|
11563
12802
|
};
|
|
11564
12803
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
11565
12804
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
11566
|
-
function createFsWatchInstance(
|
|
12805
|
+
function createFsWatchInstance(path27, options, listener, errHandler, emitRaw) {
|
|
11567
12806
|
const handleEvent = (rawEvent, evPath) => {
|
|
11568
|
-
listener(
|
|
11569
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
11570
|
-
if (evPath &&
|
|
11571
|
-
fsWatchBroadcast(sp.resolve(
|
|
12807
|
+
listener(path27);
|
|
12808
|
+
emitRaw(rawEvent, evPath, { watchedPath: path27 });
|
|
12809
|
+
if (evPath && path27 !== evPath) {
|
|
12810
|
+
fsWatchBroadcast(sp.resolve(path27, evPath), KEY_LISTENERS, sp.join(path27, evPath));
|
|
11572
12811
|
}
|
|
11573
12812
|
};
|
|
11574
12813
|
try {
|
|
11575
|
-
return fs_watch(
|
|
12814
|
+
return fs_watch(path27, {
|
|
11576
12815
|
persistent: options.persistent
|
|
11577
12816
|
}, handleEvent);
|
|
11578
12817
|
} catch (error) {
|
|
@@ -11588,12 +12827,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
11588
12827
|
listener(val1, val2, val3);
|
|
11589
12828
|
});
|
|
11590
12829
|
};
|
|
11591
|
-
var setFsWatchListener = (
|
|
12830
|
+
var setFsWatchListener = (path27, fullPath, options, handlers) => {
|
|
11592
12831
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
11593
12832
|
let cont = FsWatchInstances.get(fullPath);
|
|
11594
12833
|
let watcher;
|
|
11595
12834
|
if (!options.persistent) {
|
|
11596
|
-
watcher = createFsWatchInstance(
|
|
12835
|
+
watcher = createFsWatchInstance(path27, options, listener, errHandler, rawEmitter);
|
|
11597
12836
|
if (!watcher)
|
|
11598
12837
|
return;
|
|
11599
12838
|
return watcher.close.bind(watcher);
|
|
@@ -11604,7 +12843,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
11604
12843
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
11605
12844
|
} else {
|
|
11606
12845
|
watcher = createFsWatchInstance(
|
|
11607
|
-
|
|
12846
|
+
path27,
|
|
11608
12847
|
options,
|
|
11609
12848
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
11610
12849
|
errHandler,
|
|
@@ -11619,7 +12858,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
11619
12858
|
cont.watcherUnusable = true;
|
|
11620
12859
|
if (isWindows && error.code === "EPERM") {
|
|
11621
12860
|
try {
|
|
11622
|
-
const fd = await open(
|
|
12861
|
+
const fd = await open(path27, "r");
|
|
11623
12862
|
await fd.close();
|
|
11624
12863
|
broadcastErr(error);
|
|
11625
12864
|
} catch (err) {
|
|
@@ -11650,7 +12889,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
11650
12889
|
};
|
|
11651
12890
|
};
|
|
11652
12891
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
11653
|
-
var setFsWatchFileListener = (
|
|
12892
|
+
var setFsWatchFileListener = (path27, fullPath, options, handlers) => {
|
|
11654
12893
|
const { listener, rawEmitter } = handlers;
|
|
11655
12894
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
11656
12895
|
const copts = cont && cont.options;
|
|
@@ -11672,7 +12911,7 @@ var setFsWatchFileListener = (path25, fullPath, options, handlers) => {
|
|
|
11672
12911
|
});
|
|
11673
12912
|
const currmtime = curr.mtimeMs;
|
|
11674
12913
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
11675
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
12914
|
+
foreach(cont.listeners, (listener2) => listener2(path27, curr));
|
|
11676
12915
|
}
|
|
11677
12916
|
})
|
|
11678
12917
|
};
|
|
@@ -11702,13 +12941,13 @@ var NodeFsHandler = class {
|
|
|
11702
12941
|
* @param listener on fs change
|
|
11703
12942
|
* @returns closer for the watcher instance
|
|
11704
12943
|
*/
|
|
11705
|
-
_watchWithNodeFs(
|
|
12944
|
+
_watchWithNodeFs(path27, listener) {
|
|
11706
12945
|
const opts = this.fsw.options;
|
|
11707
|
-
const directory = sp.dirname(
|
|
11708
|
-
const basename5 = sp.basename(
|
|
12946
|
+
const directory = sp.dirname(path27);
|
|
12947
|
+
const basename5 = sp.basename(path27);
|
|
11709
12948
|
const parent = this.fsw._getWatchedDir(directory);
|
|
11710
12949
|
parent.add(basename5);
|
|
11711
|
-
const absolutePath = sp.resolve(
|
|
12950
|
+
const absolutePath = sp.resolve(path27);
|
|
11712
12951
|
const options = {
|
|
11713
12952
|
persistent: opts.persistent
|
|
11714
12953
|
};
|
|
@@ -11718,12 +12957,12 @@ var NodeFsHandler = class {
|
|
|
11718
12957
|
if (opts.usePolling) {
|
|
11719
12958
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
11720
12959
|
options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
|
|
11721
|
-
closer = setFsWatchFileListener(
|
|
12960
|
+
closer = setFsWatchFileListener(path27, absolutePath, options, {
|
|
11722
12961
|
listener,
|
|
11723
12962
|
rawEmitter: this.fsw._emitRaw
|
|
11724
12963
|
});
|
|
11725
12964
|
} else {
|
|
11726
|
-
closer = setFsWatchListener(
|
|
12965
|
+
closer = setFsWatchListener(path27, absolutePath, options, {
|
|
11727
12966
|
listener,
|
|
11728
12967
|
errHandler: this._boundHandleError,
|
|
11729
12968
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -11745,7 +12984,7 @@ var NodeFsHandler = class {
|
|
|
11745
12984
|
let prevStats = stats;
|
|
11746
12985
|
if (parent.has(basename5))
|
|
11747
12986
|
return;
|
|
11748
|
-
const listener = async (
|
|
12987
|
+
const listener = async (path27, newStats) => {
|
|
11749
12988
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
11750
12989
|
return;
|
|
11751
12990
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -11759,11 +12998,11 @@ var NodeFsHandler = class {
|
|
|
11759
12998
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
11760
12999
|
}
|
|
11761
13000
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
11762
|
-
this.fsw._closeFile(
|
|
13001
|
+
this.fsw._closeFile(path27);
|
|
11763
13002
|
prevStats = newStats2;
|
|
11764
13003
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
11765
13004
|
if (closer2)
|
|
11766
|
-
this.fsw._addPathCloser(
|
|
13005
|
+
this.fsw._addPathCloser(path27, closer2);
|
|
11767
13006
|
} else {
|
|
11768
13007
|
prevStats = newStats2;
|
|
11769
13008
|
}
|
|
@@ -11795,7 +13034,7 @@ var NodeFsHandler = class {
|
|
|
11795
13034
|
* @param item basename of this item
|
|
11796
13035
|
* @returns true if no more processing is needed for this entry.
|
|
11797
13036
|
*/
|
|
11798
|
-
async _handleSymlink(entry, directory,
|
|
13037
|
+
async _handleSymlink(entry, directory, path27, item) {
|
|
11799
13038
|
if (this.fsw.closed) {
|
|
11800
13039
|
return;
|
|
11801
13040
|
}
|
|
@@ -11805,7 +13044,7 @@ var NodeFsHandler = class {
|
|
|
11805
13044
|
this.fsw._incrReadyCount();
|
|
11806
13045
|
let linkPath;
|
|
11807
13046
|
try {
|
|
11808
|
-
linkPath = await fsrealpath(
|
|
13047
|
+
linkPath = await fsrealpath(path27);
|
|
11809
13048
|
} catch (e) {
|
|
11810
13049
|
this.fsw._emitReady();
|
|
11811
13050
|
return true;
|
|
@@ -11815,12 +13054,12 @@ var NodeFsHandler = class {
|
|
|
11815
13054
|
if (dir.has(item)) {
|
|
11816
13055
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
11817
13056
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
11818
|
-
this.fsw._emit(EV.CHANGE,
|
|
13057
|
+
this.fsw._emit(EV.CHANGE, path27, entry.stats);
|
|
11819
13058
|
}
|
|
11820
13059
|
} else {
|
|
11821
13060
|
dir.add(item);
|
|
11822
13061
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
11823
|
-
this.fsw._emit(EV.ADD,
|
|
13062
|
+
this.fsw._emit(EV.ADD, path27, entry.stats);
|
|
11824
13063
|
}
|
|
11825
13064
|
this.fsw._emitReady();
|
|
11826
13065
|
return true;
|
|
@@ -11850,9 +13089,9 @@ var NodeFsHandler = class {
|
|
|
11850
13089
|
return;
|
|
11851
13090
|
}
|
|
11852
13091
|
const item = entry.path;
|
|
11853
|
-
let
|
|
13092
|
+
let path27 = sp.join(directory, item);
|
|
11854
13093
|
current.add(item);
|
|
11855
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
13094
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path27, item)) {
|
|
11856
13095
|
return;
|
|
11857
13096
|
}
|
|
11858
13097
|
if (this.fsw.closed) {
|
|
@@ -11861,8 +13100,8 @@ var NodeFsHandler = class {
|
|
|
11861
13100
|
}
|
|
11862
13101
|
if (item === target || !target && !previous.has(item)) {
|
|
11863
13102
|
this.fsw._incrReadyCount();
|
|
11864
|
-
|
|
11865
|
-
this._addToNodeFs(
|
|
13103
|
+
path27 = sp.join(dir, sp.relative(dir, path27));
|
|
13104
|
+
this._addToNodeFs(path27, initialAdd, wh, depth + 1);
|
|
11866
13105
|
}
|
|
11867
13106
|
}).on(EV.ERROR, this._boundHandleError);
|
|
11868
13107
|
return new Promise((resolve15, reject) => {
|
|
@@ -11931,13 +13170,13 @@ var NodeFsHandler = class {
|
|
|
11931
13170
|
* @param depth Child path actually targeted for watch
|
|
11932
13171
|
* @param target Child path actually targeted for watch
|
|
11933
13172
|
*/
|
|
11934
|
-
async _addToNodeFs(
|
|
13173
|
+
async _addToNodeFs(path27, initialAdd, priorWh, depth, target) {
|
|
11935
13174
|
const ready = this.fsw._emitReady;
|
|
11936
|
-
if (this.fsw._isIgnored(
|
|
13175
|
+
if (this.fsw._isIgnored(path27) || this.fsw.closed) {
|
|
11937
13176
|
ready();
|
|
11938
13177
|
return false;
|
|
11939
13178
|
}
|
|
11940
|
-
const wh = this.fsw._getWatchHelpers(
|
|
13179
|
+
const wh = this.fsw._getWatchHelpers(path27);
|
|
11941
13180
|
if (priorWh) {
|
|
11942
13181
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
11943
13182
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -11953,8 +13192,8 @@ var NodeFsHandler = class {
|
|
|
11953
13192
|
const follow = this.fsw.options.followSymlinks;
|
|
11954
13193
|
let closer;
|
|
11955
13194
|
if (stats.isDirectory()) {
|
|
11956
|
-
const absPath = sp.resolve(
|
|
11957
|
-
const targetPath = follow ? await fsrealpath(
|
|
13195
|
+
const absPath = sp.resolve(path27);
|
|
13196
|
+
const targetPath = follow ? await fsrealpath(path27) : path27;
|
|
11958
13197
|
if (this.fsw.closed)
|
|
11959
13198
|
return;
|
|
11960
13199
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -11964,29 +13203,29 @@ var NodeFsHandler = class {
|
|
|
11964
13203
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
11965
13204
|
}
|
|
11966
13205
|
} else if (stats.isSymbolicLink()) {
|
|
11967
|
-
const targetPath = follow ? await fsrealpath(
|
|
13206
|
+
const targetPath = follow ? await fsrealpath(path27) : path27;
|
|
11968
13207
|
if (this.fsw.closed)
|
|
11969
13208
|
return;
|
|
11970
13209
|
const parent = sp.dirname(wh.watchPath);
|
|
11971
13210
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
11972
13211
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
11973
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
13212
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path27, wh, targetPath);
|
|
11974
13213
|
if (this.fsw.closed)
|
|
11975
13214
|
return;
|
|
11976
13215
|
if (targetPath !== void 0) {
|
|
11977
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
13216
|
+
this.fsw._symlinkPaths.set(sp.resolve(path27), targetPath);
|
|
11978
13217
|
}
|
|
11979
13218
|
} else {
|
|
11980
13219
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
11981
13220
|
}
|
|
11982
13221
|
ready();
|
|
11983
13222
|
if (closer)
|
|
11984
|
-
this.fsw._addPathCloser(
|
|
13223
|
+
this.fsw._addPathCloser(path27, closer);
|
|
11985
13224
|
return false;
|
|
11986
13225
|
} catch (error) {
|
|
11987
13226
|
if (this.fsw._handleError(error)) {
|
|
11988
13227
|
ready();
|
|
11989
|
-
return
|
|
13228
|
+
return path27;
|
|
11990
13229
|
}
|
|
11991
13230
|
}
|
|
11992
13231
|
}
|
|
@@ -12018,35 +13257,35 @@ function createPattern(matcher) {
|
|
|
12018
13257
|
if (matcher.path === string)
|
|
12019
13258
|
return true;
|
|
12020
13259
|
if (matcher.recursive) {
|
|
12021
|
-
const
|
|
12022
|
-
if (!
|
|
13260
|
+
const relative11 = sp2.relative(matcher.path, string);
|
|
13261
|
+
if (!relative11) {
|
|
12023
13262
|
return false;
|
|
12024
13263
|
}
|
|
12025
|
-
return !
|
|
13264
|
+
return !relative11.startsWith("..") && !sp2.isAbsolute(relative11);
|
|
12026
13265
|
}
|
|
12027
13266
|
return false;
|
|
12028
13267
|
};
|
|
12029
13268
|
}
|
|
12030
13269
|
return () => false;
|
|
12031
13270
|
}
|
|
12032
|
-
function normalizePath2(
|
|
12033
|
-
if (typeof
|
|
13271
|
+
function normalizePath2(path27) {
|
|
13272
|
+
if (typeof path27 !== "string")
|
|
12034
13273
|
throw new Error("string expected");
|
|
12035
|
-
|
|
12036
|
-
|
|
13274
|
+
path27 = sp2.normalize(path27);
|
|
13275
|
+
path27 = path27.replace(/\\/g, "/");
|
|
12037
13276
|
let prepend = false;
|
|
12038
|
-
if (
|
|
13277
|
+
if (path27.startsWith("//"))
|
|
12039
13278
|
prepend = true;
|
|
12040
|
-
|
|
13279
|
+
path27 = path27.replace(DOUBLE_SLASH_RE, "/");
|
|
12041
13280
|
if (prepend)
|
|
12042
|
-
|
|
12043
|
-
return
|
|
13281
|
+
path27 = "/" + path27;
|
|
13282
|
+
return path27;
|
|
12044
13283
|
}
|
|
12045
13284
|
function matchPatterns(patterns, testString, stats) {
|
|
12046
|
-
const
|
|
13285
|
+
const path27 = normalizePath2(testString);
|
|
12047
13286
|
for (let index = 0; index < patterns.length; index++) {
|
|
12048
13287
|
const pattern = patterns[index];
|
|
12049
|
-
if (pattern(
|
|
13288
|
+
if (pattern(path27, stats)) {
|
|
12050
13289
|
return true;
|
|
12051
13290
|
}
|
|
12052
13291
|
}
|
|
@@ -12084,19 +13323,19 @@ var toUnix = (string) => {
|
|
|
12084
13323
|
}
|
|
12085
13324
|
return str;
|
|
12086
13325
|
};
|
|
12087
|
-
var normalizePathToUnix = (
|
|
12088
|
-
var normalizeIgnored = (cwd = "") => (
|
|
12089
|
-
if (typeof
|
|
12090
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
13326
|
+
var normalizePathToUnix = (path27) => toUnix(sp2.normalize(toUnix(path27)));
|
|
13327
|
+
var normalizeIgnored = (cwd = "") => (path27) => {
|
|
13328
|
+
if (typeof path27 === "string") {
|
|
13329
|
+
return normalizePathToUnix(sp2.isAbsolute(path27) ? path27 : sp2.join(cwd, path27));
|
|
12091
13330
|
} else {
|
|
12092
|
-
return
|
|
13331
|
+
return path27;
|
|
12093
13332
|
}
|
|
12094
13333
|
};
|
|
12095
|
-
var getAbsolutePath = (
|
|
12096
|
-
if (sp2.isAbsolute(
|
|
12097
|
-
return
|
|
13334
|
+
var getAbsolutePath = (path27, cwd) => {
|
|
13335
|
+
if (sp2.isAbsolute(path27)) {
|
|
13336
|
+
return path27;
|
|
12098
13337
|
}
|
|
12099
|
-
return sp2.join(cwd,
|
|
13338
|
+
return sp2.join(cwd, path27);
|
|
12100
13339
|
};
|
|
12101
13340
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
12102
13341
|
var DirEntry = class {
|
|
@@ -12161,10 +13400,10 @@ var WatchHelper = class {
|
|
|
12161
13400
|
dirParts;
|
|
12162
13401
|
followSymlinks;
|
|
12163
13402
|
statMethod;
|
|
12164
|
-
constructor(
|
|
13403
|
+
constructor(path27, follow, fsw) {
|
|
12165
13404
|
this.fsw = fsw;
|
|
12166
|
-
const watchPath =
|
|
12167
|
-
this.path =
|
|
13405
|
+
const watchPath = path27;
|
|
13406
|
+
this.path = path27 = path27.replace(REPLACER_RE, "");
|
|
12168
13407
|
this.watchPath = watchPath;
|
|
12169
13408
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
12170
13409
|
this.dirParts = [];
|
|
@@ -12304,20 +13543,20 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12304
13543
|
this._closePromise = void 0;
|
|
12305
13544
|
let paths = unifyPaths(paths_);
|
|
12306
13545
|
if (cwd) {
|
|
12307
|
-
paths = paths.map((
|
|
12308
|
-
const absPath = getAbsolutePath(
|
|
13546
|
+
paths = paths.map((path27) => {
|
|
13547
|
+
const absPath = getAbsolutePath(path27, cwd);
|
|
12309
13548
|
return absPath;
|
|
12310
13549
|
});
|
|
12311
13550
|
}
|
|
12312
|
-
paths.forEach((
|
|
12313
|
-
this._removeIgnoredPath(
|
|
13551
|
+
paths.forEach((path27) => {
|
|
13552
|
+
this._removeIgnoredPath(path27);
|
|
12314
13553
|
});
|
|
12315
13554
|
this._userIgnored = void 0;
|
|
12316
13555
|
if (!this._readyCount)
|
|
12317
13556
|
this._readyCount = 0;
|
|
12318
13557
|
this._readyCount += paths.length;
|
|
12319
|
-
Promise.all(paths.map(async (
|
|
12320
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
13558
|
+
Promise.all(paths.map(async (path27) => {
|
|
13559
|
+
const res = await this._nodeFsHandler._addToNodeFs(path27, !_internal, void 0, 0, _origAdd);
|
|
12321
13560
|
if (res)
|
|
12322
13561
|
this._emitReady();
|
|
12323
13562
|
return res;
|
|
@@ -12339,17 +13578,17 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12339
13578
|
return this;
|
|
12340
13579
|
const paths = unifyPaths(paths_);
|
|
12341
13580
|
const { cwd } = this.options;
|
|
12342
|
-
paths.forEach((
|
|
12343
|
-
if (!sp2.isAbsolute(
|
|
13581
|
+
paths.forEach((path27) => {
|
|
13582
|
+
if (!sp2.isAbsolute(path27) && !this._closers.has(path27)) {
|
|
12344
13583
|
if (cwd)
|
|
12345
|
-
|
|
12346
|
-
|
|
13584
|
+
path27 = sp2.join(cwd, path27);
|
|
13585
|
+
path27 = sp2.resolve(path27);
|
|
12347
13586
|
}
|
|
12348
|
-
this._closePath(
|
|
12349
|
-
this._addIgnoredPath(
|
|
12350
|
-
if (this._watched.has(
|
|
13587
|
+
this._closePath(path27);
|
|
13588
|
+
this._addIgnoredPath(path27);
|
|
13589
|
+
if (this._watched.has(path27)) {
|
|
12351
13590
|
this._addIgnoredPath({
|
|
12352
|
-
path:
|
|
13591
|
+
path: path27,
|
|
12353
13592
|
recursive: true
|
|
12354
13593
|
});
|
|
12355
13594
|
}
|
|
@@ -12413,38 +13652,38 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12413
13652
|
* @param stats arguments to be passed with event
|
|
12414
13653
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
12415
13654
|
*/
|
|
12416
|
-
async _emit(event,
|
|
13655
|
+
async _emit(event, path27, stats) {
|
|
12417
13656
|
if (this.closed)
|
|
12418
13657
|
return;
|
|
12419
13658
|
const opts = this.options;
|
|
12420
13659
|
if (isWindows)
|
|
12421
|
-
|
|
13660
|
+
path27 = sp2.normalize(path27);
|
|
12422
13661
|
if (opts.cwd)
|
|
12423
|
-
|
|
12424
|
-
const args = [
|
|
13662
|
+
path27 = sp2.relative(opts.cwd, path27);
|
|
13663
|
+
const args = [path27];
|
|
12425
13664
|
if (stats != null)
|
|
12426
13665
|
args.push(stats);
|
|
12427
13666
|
const awf = opts.awaitWriteFinish;
|
|
12428
13667
|
let pw;
|
|
12429
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
13668
|
+
if (awf && (pw = this._pendingWrites.get(path27))) {
|
|
12430
13669
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
12431
13670
|
return this;
|
|
12432
13671
|
}
|
|
12433
13672
|
if (opts.atomic) {
|
|
12434
13673
|
if (event === EVENTS.UNLINK) {
|
|
12435
|
-
this._pendingUnlinks.set(
|
|
13674
|
+
this._pendingUnlinks.set(path27, [event, ...args]);
|
|
12436
13675
|
setTimeout(() => {
|
|
12437
|
-
this._pendingUnlinks.forEach((entry,
|
|
13676
|
+
this._pendingUnlinks.forEach((entry, path28) => {
|
|
12438
13677
|
this.emit(...entry);
|
|
12439
13678
|
this.emit(EVENTS.ALL, ...entry);
|
|
12440
|
-
this._pendingUnlinks.delete(
|
|
13679
|
+
this._pendingUnlinks.delete(path28);
|
|
12441
13680
|
});
|
|
12442
13681
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
12443
13682
|
return this;
|
|
12444
13683
|
}
|
|
12445
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
13684
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path27)) {
|
|
12446
13685
|
event = EVENTS.CHANGE;
|
|
12447
|
-
this._pendingUnlinks.delete(
|
|
13686
|
+
this._pendingUnlinks.delete(path27);
|
|
12448
13687
|
}
|
|
12449
13688
|
}
|
|
12450
13689
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -12462,16 +13701,16 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12462
13701
|
this.emitWithAll(event, args);
|
|
12463
13702
|
}
|
|
12464
13703
|
};
|
|
12465
|
-
this._awaitWriteFinish(
|
|
13704
|
+
this._awaitWriteFinish(path27, awf.stabilityThreshold, event, awfEmit);
|
|
12466
13705
|
return this;
|
|
12467
13706
|
}
|
|
12468
13707
|
if (event === EVENTS.CHANGE) {
|
|
12469
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
13708
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path27, 50);
|
|
12470
13709
|
if (isThrottled)
|
|
12471
13710
|
return this;
|
|
12472
13711
|
}
|
|
12473
13712
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
12474
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
13713
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path27) : path27;
|
|
12475
13714
|
let stats2;
|
|
12476
13715
|
try {
|
|
12477
13716
|
stats2 = await stat3(fullPath);
|
|
@@ -12502,23 +13741,23 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12502
13741
|
* @param timeout duration of time to suppress duplicate actions
|
|
12503
13742
|
* @returns tracking object or false if action should be suppressed
|
|
12504
13743
|
*/
|
|
12505
|
-
_throttle(actionType,
|
|
13744
|
+
_throttle(actionType, path27, timeout) {
|
|
12506
13745
|
if (!this._throttled.has(actionType)) {
|
|
12507
13746
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
12508
13747
|
}
|
|
12509
13748
|
const action = this._throttled.get(actionType);
|
|
12510
13749
|
if (!action)
|
|
12511
13750
|
throw new Error("invalid throttle");
|
|
12512
|
-
const actionPath = action.get(
|
|
13751
|
+
const actionPath = action.get(path27);
|
|
12513
13752
|
if (actionPath) {
|
|
12514
13753
|
actionPath.count++;
|
|
12515
13754
|
return false;
|
|
12516
13755
|
}
|
|
12517
13756
|
let timeoutObject;
|
|
12518
13757
|
const clear = () => {
|
|
12519
|
-
const item = action.get(
|
|
13758
|
+
const item = action.get(path27);
|
|
12520
13759
|
const count = item ? item.count : 0;
|
|
12521
|
-
action.delete(
|
|
13760
|
+
action.delete(path27);
|
|
12522
13761
|
clearTimeout(timeoutObject);
|
|
12523
13762
|
if (item)
|
|
12524
13763
|
clearTimeout(item.timeoutObject);
|
|
@@ -12526,7 +13765,7 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12526
13765
|
};
|
|
12527
13766
|
timeoutObject = setTimeout(clear, timeout);
|
|
12528
13767
|
const thr = { timeoutObject, clear, count: 0 };
|
|
12529
|
-
action.set(
|
|
13768
|
+
action.set(path27, thr);
|
|
12530
13769
|
return thr;
|
|
12531
13770
|
}
|
|
12532
13771
|
_incrReadyCount() {
|
|
@@ -12540,44 +13779,44 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12540
13779
|
* @param event
|
|
12541
13780
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
12542
13781
|
*/
|
|
12543
|
-
_awaitWriteFinish(
|
|
13782
|
+
_awaitWriteFinish(path27, threshold, event, awfEmit) {
|
|
12544
13783
|
const awf = this.options.awaitWriteFinish;
|
|
12545
13784
|
if (typeof awf !== "object")
|
|
12546
13785
|
return;
|
|
12547
13786
|
const pollInterval = awf.pollInterval;
|
|
12548
13787
|
let timeoutHandler;
|
|
12549
|
-
let fullPath =
|
|
12550
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
12551
|
-
fullPath = sp2.join(this.options.cwd,
|
|
13788
|
+
let fullPath = path27;
|
|
13789
|
+
if (this.options.cwd && !sp2.isAbsolute(path27)) {
|
|
13790
|
+
fullPath = sp2.join(this.options.cwd, path27);
|
|
12552
13791
|
}
|
|
12553
13792
|
const now = /* @__PURE__ */ new Date();
|
|
12554
13793
|
const writes = this._pendingWrites;
|
|
12555
13794
|
function awaitWriteFinishFn(prevStat) {
|
|
12556
13795
|
statcb(fullPath, (err, curStat) => {
|
|
12557
|
-
if (err || !writes.has(
|
|
13796
|
+
if (err || !writes.has(path27)) {
|
|
12558
13797
|
if (err && err.code !== "ENOENT")
|
|
12559
13798
|
awfEmit(err);
|
|
12560
13799
|
return;
|
|
12561
13800
|
}
|
|
12562
13801
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
12563
13802
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
12564
|
-
writes.get(
|
|
13803
|
+
writes.get(path27).lastChange = now2;
|
|
12565
13804
|
}
|
|
12566
|
-
const pw = writes.get(
|
|
13805
|
+
const pw = writes.get(path27);
|
|
12567
13806
|
const df = now2 - pw.lastChange;
|
|
12568
13807
|
if (df >= threshold) {
|
|
12569
|
-
writes.delete(
|
|
13808
|
+
writes.delete(path27);
|
|
12570
13809
|
awfEmit(void 0, curStat);
|
|
12571
13810
|
} else {
|
|
12572
13811
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
12573
13812
|
}
|
|
12574
13813
|
});
|
|
12575
13814
|
}
|
|
12576
|
-
if (!writes.has(
|
|
12577
|
-
writes.set(
|
|
13815
|
+
if (!writes.has(path27)) {
|
|
13816
|
+
writes.set(path27, {
|
|
12578
13817
|
lastChange: now,
|
|
12579
13818
|
cancelWait: () => {
|
|
12580
|
-
writes.delete(
|
|
13819
|
+
writes.delete(path27);
|
|
12581
13820
|
clearTimeout(timeoutHandler);
|
|
12582
13821
|
return event;
|
|
12583
13822
|
}
|
|
@@ -12588,8 +13827,8 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12588
13827
|
/**
|
|
12589
13828
|
* Determines whether user has asked to ignore this path.
|
|
12590
13829
|
*/
|
|
12591
|
-
_isIgnored(
|
|
12592
|
-
if (this.options.atomic && DOT_RE.test(
|
|
13830
|
+
_isIgnored(path27, stats) {
|
|
13831
|
+
if (this.options.atomic && DOT_RE.test(path27))
|
|
12593
13832
|
return true;
|
|
12594
13833
|
if (!this._userIgnored) {
|
|
12595
13834
|
const { cwd } = this.options;
|
|
@@ -12599,17 +13838,17 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12599
13838
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
12600
13839
|
this._userIgnored = anymatch(list, void 0);
|
|
12601
13840
|
}
|
|
12602
|
-
return this._userIgnored(
|
|
13841
|
+
return this._userIgnored(path27, stats);
|
|
12603
13842
|
}
|
|
12604
|
-
_isntIgnored(
|
|
12605
|
-
return !this._isIgnored(
|
|
13843
|
+
_isntIgnored(path27, stat4) {
|
|
13844
|
+
return !this._isIgnored(path27, stat4);
|
|
12606
13845
|
}
|
|
12607
13846
|
/**
|
|
12608
13847
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
12609
13848
|
* @param path file or directory pattern being watched
|
|
12610
13849
|
*/
|
|
12611
|
-
_getWatchHelpers(
|
|
12612
|
-
return new WatchHelper(
|
|
13850
|
+
_getWatchHelpers(path27) {
|
|
13851
|
+
return new WatchHelper(path27, this.options.followSymlinks, this);
|
|
12613
13852
|
}
|
|
12614
13853
|
// Directory helpers
|
|
12615
13854
|
// -----------------
|
|
@@ -12641,63 +13880,63 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12641
13880
|
* @param item base path of item/directory
|
|
12642
13881
|
*/
|
|
12643
13882
|
_remove(directory, item, isDirectory) {
|
|
12644
|
-
const
|
|
12645
|
-
const fullPath = sp2.resolve(
|
|
12646
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
12647
|
-
if (!this._throttle("remove",
|
|
13883
|
+
const path27 = sp2.join(directory, item);
|
|
13884
|
+
const fullPath = sp2.resolve(path27);
|
|
13885
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path27) || this._watched.has(fullPath);
|
|
13886
|
+
if (!this._throttle("remove", path27, 100))
|
|
12648
13887
|
return;
|
|
12649
13888
|
if (!isDirectory && this._watched.size === 1) {
|
|
12650
13889
|
this.add(directory, item, true);
|
|
12651
13890
|
}
|
|
12652
|
-
const wp = this._getWatchedDir(
|
|
13891
|
+
const wp = this._getWatchedDir(path27);
|
|
12653
13892
|
const nestedDirectoryChildren = wp.getChildren();
|
|
12654
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
13893
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path27, nested));
|
|
12655
13894
|
const parent = this._getWatchedDir(directory);
|
|
12656
13895
|
const wasTracked = parent.has(item);
|
|
12657
13896
|
parent.remove(item);
|
|
12658
13897
|
if (this._symlinkPaths.has(fullPath)) {
|
|
12659
13898
|
this._symlinkPaths.delete(fullPath);
|
|
12660
13899
|
}
|
|
12661
|
-
let relPath =
|
|
13900
|
+
let relPath = path27;
|
|
12662
13901
|
if (this.options.cwd)
|
|
12663
|
-
relPath = sp2.relative(this.options.cwd,
|
|
13902
|
+
relPath = sp2.relative(this.options.cwd, path27);
|
|
12664
13903
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
12665
13904
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
12666
13905
|
if (event === EVENTS.ADD)
|
|
12667
13906
|
return;
|
|
12668
13907
|
}
|
|
12669
|
-
this._watched.delete(
|
|
13908
|
+
this._watched.delete(path27);
|
|
12670
13909
|
this._watched.delete(fullPath);
|
|
12671
13910
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
12672
|
-
if (wasTracked && !this._isIgnored(
|
|
12673
|
-
this._emit(eventName,
|
|
12674
|
-
this._closePath(
|
|
13911
|
+
if (wasTracked && !this._isIgnored(path27))
|
|
13912
|
+
this._emit(eventName, path27);
|
|
13913
|
+
this._closePath(path27);
|
|
12675
13914
|
}
|
|
12676
13915
|
/**
|
|
12677
13916
|
* Closes all watchers for a path
|
|
12678
13917
|
*/
|
|
12679
|
-
_closePath(
|
|
12680
|
-
this._closeFile(
|
|
12681
|
-
const dir = sp2.dirname(
|
|
12682
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
13918
|
+
_closePath(path27) {
|
|
13919
|
+
this._closeFile(path27);
|
|
13920
|
+
const dir = sp2.dirname(path27);
|
|
13921
|
+
this._getWatchedDir(dir).remove(sp2.basename(path27));
|
|
12683
13922
|
}
|
|
12684
13923
|
/**
|
|
12685
13924
|
* Closes only file-specific watchers
|
|
12686
13925
|
*/
|
|
12687
|
-
_closeFile(
|
|
12688
|
-
const closers = this._closers.get(
|
|
13926
|
+
_closeFile(path27) {
|
|
13927
|
+
const closers = this._closers.get(path27);
|
|
12689
13928
|
if (!closers)
|
|
12690
13929
|
return;
|
|
12691
13930
|
closers.forEach((closer) => closer());
|
|
12692
|
-
this._closers.delete(
|
|
13931
|
+
this._closers.delete(path27);
|
|
12693
13932
|
}
|
|
12694
|
-
_addPathCloser(
|
|
13933
|
+
_addPathCloser(path27, closer) {
|
|
12695
13934
|
if (!closer)
|
|
12696
13935
|
return;
|
|
12697
|
-
let list = this._closers.get(
|
|
13936
|
+
let list = this._closers.get(path27);
|
|
12698
13937
|
if (!list) {
|
|
12699
13938
|
list = [];
|
|
12700
|
-
this._closers.set(
|
|
13939
|
+
this._closers.set(path27, list);
|
|
12701
13940
|
}
|
|
12702
13941
|
list.push(closer);
|
|
12703
13942
|
}
|
|
@@ -12727,7 +13966,7 @@ function watch(paths, options = {}) {
|
|
|
12727
13966
|
var chokidar_default = { watch, FSWatcher };
|
|
12728
13967
|
|
|
12729
13968
|
// src/watcher/file-watcher.ts
|
|
12730
|
-
import * as
|
|
13969
|
+
import * as path22 from "path";
|
|
12731
13970
|
var FileWatcher = class {
|
|
12732
13971
|
watcher = null;
|
|
12733
13972
|
projectRoot;
|
|
@@ -12738,6 +13977,8 @@ var FileWatcher = class {
|
|
|
12738
13977
|
debounceTimer = null;
|
|
12739
13978
|
debounceMs = 1e3;
|
|
12740
13979
|
onChanges = null;
|
|
13980
|
+
readyPromise = null;
|
|
13981
|
+
resolveReady = null;
|
|
12741
13982
|
constructor(projectRoot, config, host = "opencode", options = {}) {
|
|
12742
13983
|
this.projectRoot = projectRoot;
|
|
12743
13984
|
this.config = config;
|
|
@@ -12753,15 +13994,15 @@ var FileWatcher = class {
|
|
|
12753
13994
|
const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
|
|
12754
13995
|
this.watcher = chokidar_default.watch(watchTargets, {
|
|
12755
13996
|
ignored: (filePath) => {
|
|
12756
|
-
const relativePath =
|
|
13997
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
12757
13998
|
if (!relativePath) return false;
|
|
12758
13999
|
if (this.isProjectConfigPathOrAncestor(relativePath)) {
|
|
12759
14000
|
return false;
|
|
12760
14001
|
}
|
|
12761
|
-
if (hasFilteredPathSegment(relativePath,
|
|
14002
|
+
if (hasFilteredPathSegment(relativePath, path22.sep)) {
|
|
12762
14003
|
return true;
|
|
12763
14004
|
}
|
|
12764
|
-
if (isRestrictedDirectory(relativePath,
|
|
14005
|
+
if (isRestrictedDirectory(relativePath, path22.sep)) {
|
|
12765
14006
|
return true;
|
|
12766
14007
|
}
|
|
12767
14008
|
if (ignoreFilter.ignores(relativePath)) {
|
|
@@ -12776,6 +14017,13 @@ var FileWatcher = class {
|
|
|
12776
14017
|
pollInterval: 100
|
|
12777
14018
|
}
|
|
12778
14019
|
});
|
|
14020
|
+
this.readyPromise = new Promise((resolve15) => {
|
|
14021
|
+
this.resolveReady = resolve15;
|
|
14022
|
+
});
|
|
14023
|
+
this.watcher.once("ready", () => {
|
|
14024
|
+
this.resolveReady?.();
|
|
14025
|
+
this.resolveReady = null;
|
|
14026
|
+
});
|
|
12779
14027
|
this.watcher.on("error", (error) => {
|
|
12780
14028
|
const err = error instanceof Error ? error : null;
|
|
12781
14029
|
if (err?.code === "EPERM" || err?.code === "EACCES") {
|
|
@@ -12807,24 +14055,24 @@ var FileWatcher = class {
|
|
|
12807
14055
|
this.scheduleFlush();
|
|
12808
14056
|
}
|
|
12809
14057
|
isProjectConfigPath(filePath) {
|
|
12810
|
-
const relativePath =
|
|
12811
|
-
const normalizedRelativePath =
|
|
14058
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
14059
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
12812
14060
|
return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
|
|
12813
14061
|
}
|
|
12814
14062
|
isProjectConfigPathOrAncestor(relativePath) {
|
|
12815
|
-
const normalizedRelativePath =
|
|
14063
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
12816
14064
|
return this.getProjectConfigRelativePaths().some(
|
|
12817
|
-
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${
|
|
14065
|
+
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path22.sep}`)
|
|
12818
14066
|
);
|
|
12819
14067
|
}
|
|
12820
14068
|
getProjectConfigRelativePaths() {
|
|
12821
14069
|
if (this.configPath) {
|
|
12822
|
-
return [
|
|
14070
|
+
return [path22.normalize(path22.relative(this.projectRoot, this.configPath))];
|
|
12823
14071
|
}
|
|
12824
14072
|
return [
|
|
12825
14073
|
resolveProjectConfigPath(this.projectRoot, this.host),
|
|
12826
14074
|
resolveWritableProjectConfigPath(this.projectRoot, this.host)
|
|
12827
|
-
].map((configPath) =>
|
|
14075
|
+
].map((configPath) => path22.normalize(path22.relative(this.projectRoot, configPath)));
|
|
12828
14076
|
}
|
|
12829
14077
|
scheduleFlush() {
|
|
12830
14078
|
if (this.debounceTimer) {
|
|
@@ -12839,7 +14087,7 @@ var FileWatcher = class {
|
|
|
12839
14087
|
return;
|
|
12840
14088
|
}
|
|
12841
14089
|
const changes = Array.from(this.pendingChanges.entries()).map(
|
|
12842
|
-
([
|
|
14090
|
+
([path27, type]) => ({ path: path27, type })
|
|
12843
14091
|
);
|
|
12844
14092
|
this.pendingChanges.clear();
|
|
12845
14093
|
try {
|
|
@@ -12848,25 +14096,32 @@ var FileWatcher = class {
|
|
|
12848
14096
|
console.error("Error handling file changes:", error);
|
|
12849
14097
|
}
|
|
12850
14098
|
}
|
|
12851
|
-
stop() {
|
|
14099
|
+
async stop() {
|
|
12852
14100
|
if (this.debounceTimer) {
|
|
12853
14101
|
clearTimeout(this.debounceTimer);
|
|
12854
14102
|
this.debounceTimer = null;
|
|
12855
14103
|
}
|
|
12856
14104
|
if (this.watcher) {
|
|
12857
|
-
this.watcher
|
|
14105
|
+
const watcher = this.watcher;
|
|
12858
14106
|
this.watcher = null;
|
|
14107
|
+
await watcher.close();
|
|
12859
14108
|
}
|
|
14109
|
+
this.resolveReady?.();
|
|
14110
|
+
this.resolveReady = null;
|
|
14111
|
+
this.readyPromise = null;
|
|
12860
14112
|
this.pendingChanges.clear();
|
|
12861
14113
|
this.onChanges = null;
|
|
12862
14114
|
}
|
|
12863
14115
|
isRunning() {
|
|
12864
14116
|
return this.watcher !== null;
|
|
12865
14117
|
}
|
|
14118
|
+
async waitUntilReady() {
|
|
14119
|
+
await (this.readyPromise ?? Promise.resolve());
|
|
14120
|
+
}
|
|
12866
14121
|
};
|
|
12867
14122
|
|
|
12868
14123
|
// src/watcher/git-head-watcher.ts
|
|
12869
|
-
import * as
|
|
14124
|
+
import * as path23 from "path";
|
|
12870
14125
|
var GitHeadWatcher = class {
|
|
12871
14126
|
watcher = null;
|
|
12872
14127
|
projectRoot;
|
|
@@ -12888,7 +14143,7 @@ var GitHeadWatcher = class {
|
|
|
12888
14143
|
this.onBranchChange = handler;
|
|
12889
14144
|
this.currentBranch = getCurrentBranch(this.projectRoot);
|
|
12890
14145
|
const headPath = getHeadPath(this.projectRoot);
|
|
12891
|
-
const refsPath =
|
|
14146
|
+
const refsPath = path23.join(this.projectRoot, ".git", "refs", "heads");
|
|
12892
14147
|
this.watcher = chokidar_default.watch([headPath, refsPath], {
|
|
12893
14148
|
persistent: true,
|
|
12894
14149
|
ignoreInitial: true,
|
|
@@ -12925,14 +14180,15 @@ var GitHeadWatcher = class {
|
|
|
12925
14180
|
getCurrentBranch() {
|
|
12926
14181
|
return this.currentBranch;
|
|
12927
14182
|
}
|
|
12928
|
-
stop() {
|
|
14183
|
+
async stop() {
|
|
12929
14184
|
if (this.debounceTimer) {
|
|
12930
14185
|
clearTimeout(this.debounceTimer);
|
|
12931
14186
|
this.debounceTimer = null;
|
|
12932
14187
|
}
|
|
12933
14188
|
if (this.watcher) {
|
|
12934
|
-
this.watcher
|
|
14189
|
+
const watcher = this.watcher;
|
|
12935
14190
|
this.watcher = null;
|
|
14191
|
+
await watcher.close();
|
|
12936
14192
|
}
|
|
12937
14193
|
this.onBranchChange = null;
|
|
12938
14194
|
}
|
|
@@ -12950,6 +14206,8 @@ var BackgroundReindexer = class {
|
|
|
12950
14206
|
running = false;
|
|
12951
14207
|
pending = false;
|
|
12952
14208
|
stopped = false;
|
|
14209
|
+
retryTimer = null;
|
|
14210
|
+
retryDelayMs = 50;
|
|
12953
14211
|
request() {
|
|
12954
14212
|
if (this.stopped) {
|
|
12955
14213
|
return;
|
|
@@ -12960,9 +14218,13 @@ var BackgroundReindexer = class {
|
|
|
12960
14218
|
stop() {
|
|
12961
14219
|
this.stopped = true;
|
|
12962
14220
|
this.pending = false;
|
|
14221
|
+
if (this.retryTimer) {
|
|
14222
|
+
clearTimeout(this.retryTimer);
|
|
14223
|
+
this.retryTimer = null;
|
|
14224
|
+
}
|
|
12963
14225
|
}
|
|
12964
14226
|
drain() {
|
|
12965
|
-
if (this.stopped || this.running || !this.pending) {
|
|
14227
|
+
if (this.stopped || this.running || this.retryTimer || !this.pending) {
|
|
12966
14228
|
return;
|
|
12967
14229
|
}
|
|
12968
14230
|
this.pending = false;
|
|
@@ -12970,13 +14232,29 @@ var BackgroundReindexer = class {
|
|
|
12970
14232
|
void this.run();
|
|
12971
14233
|
}
|
|
12972
14234
|
async run() {
|
|
14235
|
+
let retryAfterContention = false;
|
|
12973
14236
|
try {
|
|
12974
14237
|
await this.runIndex();
|
|
14238
|
+
this.retryDelayMs = 50;
|
|
12975
14239
|
} catch (error) {
|
|
12976
|
-
|
|
14240
|
+
if (isTransientIndexLockContention(error)) {
|
|
14241
|
+
this.pending = true;
|
|
14242
|
+
retryAfterContention = true;
|
|
14243
|
+
} else {
|
|
14244
|
+
console.error("[codebase-index] Background reindex failed:", error);
|
|
14245
|
+
}
|
|
12977
14246
|
} finally {
|
|
12978
14247
|
this.running = false;
|
|
12979
|
-
this.
|
|
14248
|
+
if (retryAfterContention && !this.stopped) {
|
|
14249
|
+
const delay = this.retryDelayMs;
|
|
14250
|
+
this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
|
|
14251
|
+
this.retryTimer = setTimeout(() => {
|
|
14252
|
+
this.retryTimer = null;
|
|
14253
|
+
this.drain();
|
|
14254
|
+
}, delay);
|
|
14255
|
+
} else {
|
|
14256
|
+
this.drain();
|
|
14257
|
+
}
|
|
12980
14258
|
}
|
|
12981
14259
|
}
|
|
12982
14260
|
};
|
|
@@ -13010,10 +14288,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
|
|
|
13010
14288
|
return {
|
|
13011
14289
|
fileWatcher,
|
|
13012
14290
|
gitWatcher,
|
|
13013
|
-
|
|
14291
|
+
whenReady() {
|
|
14292
|
+
return fileWatcher.waitUntilReady();
|
|
14293
|
+
},
|
|
14294
|
+
async stop() {
|
|
13014
14295
|
backgroundReindexer.stop();
|
|
13015
|
-
fileWatcher.stop();
|
|
13016
|
-
gitWatcher?.stop();
|
|
14296
|
+
await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
|
|
13017
14297
|
}
|
|
13018
14298
|
};
|
|
13019
14299
|
}
|
|
@@ -13032,7 +14312,7 @@ function getConfigPaths(projectRoot, host, options) {
|
|
|
13032
14312
|
|
|
13033
14313
|
// src/tools/visualize/activity.ts
|
|
13034
14314
|
import { execFileSync } from "child_process";
|
|
13035
|
-
import * as
|
|
14315
|
+
import * as path24 from "path";
|
|
13036
14316
|
function attachRecentActivity(data, projectRoot) {
|
|
13037
14317
|
const activity = readGitActivity(projectRoot);
|
|
13038
14318
|
const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
|
|
@@ -13194,7 +14474,7 @@ function normalizePath3(filePath) {
|
|
|
13194
14474
|
return filePath.replace(/\\/g, "/");
|
|
13195
14475
|
}
|
|
13196
14476
|
function toGitRelativePath(projectRoot, filePath) {
|
|
13197
|
-
const relativePath =
|
|
14477
|
+
const relativePath = path24.isAbsolute(filePath) ? path24.relative(projectRoot, filePath) : filePath;
|
|
13198
14478
|
return normalizePath3(relativePath);
|
|
13199
14479
|
}
|
|
13200
14480
|
|
|
@@ -13452,7 +14732,7 @@ render();
|
|
|
13452
14732
|
}
|
|
13453
14733
|
|
|
13454
14734
|
// src/tools/visualize/transform.ts
|
|
13455
|
-
import * as
|
|
14735
|
+
import * as path25 from "path";
|
|
13456
14736
|
|
|
13457
14737
|
// src/tools/visualize/modules.ts
|
|
13458
14738
|
var MAX_MODULES = 18;
|
|
@@ -13585,8 +14865,8 @@ function compactModules(prefixToNodes) {
|
|
|
13585
14865
|
function deriveModules(nodes) {
|
|
13586
14866
|
const initial = /* @__PURE__ */ new Map();
|
|
13587
14867
|
for (const node of nodes) {
|
|
13588
|
-
const
|
|
13589
|
-
const prefix = modulePrefixFromRelativePath(
|
|
14868
|
+
const relative11 = stripToProjectRelative(node.filePath);
|
|
14869
|
+
const prefix = modulePrefixFromRelativePath(relative11);
|
|
13590
14870
|
if (!initial.has(prefix)) initial.set(prefix, []);
|
|
13591
14871
|
initial.get(prefix)?.push(node);
|
|
13592
14872
|
}
|
|
@@ -13712,7 +14992,7 @@ function transformForVisualization(symbols, edges, options = {}) {
|
|
|
13712
14992
|
filePath: s.filePath,
|
|
13713
14993
|
kind: s.kind,
|
|
13714
14994
|
line: s.startLine,
|
|
13715
|
-
directory:
|
|
14995
|
+
directory: path25.dirname(s.filePath),
|
|
13716
14996
|
moduleId: "",
|
|
13717
14997
|
moduleLabel: ""
|
|
13718
14998
|
}));
|
|
@@ -13740,9 +15020,9 @@ function parseArgs(argv) {
|
|
|
13740
15020
|
let host = "opencode";
|
|
13741
15021
|
for (let i = 2; i < argv.length; i++) {
|
|
13742
15022
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
13743
|
-
project =
|
|
15023
|
+
project = path26.resolve(argv[++i]);
|
|
13744
15024
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
13745
|
-
config =
|
|
15025
|
+
config = path26.resolve(argv[++i]);
|
|
13746
15026
|
} else if (argv[i] === "--host" && argv[i + 1]) {
|
|
13747
15027
|
host = parseHostMode(argv[++i]);
|
|
13748
15028
|
} else if (argv[i] === "--host") {
|
|
@@ -13755,7 +15035,7 @@ function loadCliRawConfig(args) {
|
|
|
13755
15035
|
return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
|
|
13756
15036
|
}
|
|
13757
15037
|
function isCliEntrypoint(moduleUrl, argvPath) {
|
|
13758
|
-
return argvPath !== void 0 &&
|
|
15038
|
+
return argvPath !== void 0 && realpathSync3(fileURLToPath2(moduleUrl)) === realpathSync3(argvPath);
|
|
13759
15039
|
}
|
|
13760
15040
|
function parseVisualizeArgs(argv, cwd) {
|
|
13761
15041
|
let project = cwd;
|
|
@@ -13765,7 +15045,7 @@ function parseVisualizeArgs(argv, cwd) {
|
|
|
13765
15045
|
for (let i = 0; i < argv.length; i++) {
|
|
13766
15046
|
const arg = argv[i];
|
|
13767
15047
|
if (arg === "--project" && argv[i + 1]) {
|
|
13768
|
-
project =
|
|
15048
|
+
project = path26.resolve(argv[++i]);
|
|
13769
15049
|
} else if (arg === "--max" && argv[i + 1]) {
|
|
13770
15050
|
maxNodes = Number(argv[++i]);
|
|
13771
15051
|
} else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
|
|
@@ -13802,8 +15082,8 @@ async function handleVisualizeCommand(argv, cwd) {
|
|
|
13802
15082
|
console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
|
|
13803
15083
|
return 1;
|
|
13804
15084
|
}
|
|
13805
|
-
const outputPath =
|
|
13806
|
-
|
|
15085
|
+
const outputPath = path26.join(os6.tmpdir(), `call-graph-${Date.now()}.html`);
|
|
15086
|
+
writeFileSync7(outputPath, generateVisualizationHtml(vizData), "utf-8");
|
|
13807
15087
|
console.log(`Temporal call graph visualization generated: ${outputPath}`);
|
|
13808
15088
|
console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
|
|
13809
15089
|
console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);
|
|
@@ -13832,7 +15112,7 @@ async function main() {
|
|
|
13832
15112
|
const transport = new StdioServerTransport();
|
|
13833
15113
|
await server.connect(transport);
|
|
13834
15114
|
let watcher = null;
|
|
13835
|
-
const isHomeDir =
|
|
15115
|
+
const isHomeDir = path26.resolve(args.project) === path26.resolve(os6.homedir());
|
|
13836
15116
|
const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
|
|
13837
15117
|
if (config.indexing.autoIndex && isValidProject) {
|
|
13838
15118
|
const indexer = getIndexerForProject(args.project, args.host);
|
|
@@ -13847,14 +15127,25 @@ async function main() {
|
|
|
13847
15127
|
args.config ? { configPath: args.config } : {}
|
|
13848
15128
|
);
|
|
13849
15129
|
}
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
15130
|
+
let shuttingDown = false;
|
|
15131
|
+
const shutdown = async () => {
|
|
15132
|
+
if (shuttingDown) return;
|
|
15133
|
+
shuttingDown = true;
|
|
15134
|
+
try {
|
|
15135
|
+
await watcher?.stop();
|
|
15136
|
+
await server.close();
|
|
15137
|
+
process.exit(0);
|
|
15138
|
+
} catch (error) {
|
|
15139
|
+
console.error("Failed to stop MCP server cleanly:", error);
|
|
15140
|
+
process.exit(1);
|
|
15141
|
+
}
|
|
13855
15142
|
};
|
|
13856
|
-
process.on("SIGINT",
|
|
13857
|
-
|
|
15143
|
+
process.on("SIGINT", () => {
|
|
15144
|
+
void shutdown();
|
|
15145
|
+
});
|
|
15146
|
+
process.on("SIGTERM", () => {
|
|
15147
|
+
void shutdown();
|
|
15148
|
+
});
|
|
13858
15149
|
}
|
|
13859
15150
|
function handleMainError(error) {
|
|
13860
15151
|
if (error instanceof Error && error.message.startsWith("Invalid host mode")) {
|