opencode-codebase-index 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/README.md +73 -12
- package/dist/cli.cjs +1782 -607
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1781 -596
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1610 -533
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1606 -519
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +1396 -311
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +1395 -300
- package/dist/pi-extension.js.map +1 -1
- package/hooks/hooks.json +2 -2
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +11 -8
- package/skills/codebase-search/SKILL.md +9 -7
package/dist/cli.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}",
|
|
@@ -1046,7 +1046,7 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
|
|
|
1046
1046
|
);
|
|
1047
1047
|
|
|
1048
1048
|
// src/config/host.ts
|
|
1049
|
-
var HOST_MODES = ["opencode", "codex", "claude", "pi"];
|
|
1049
|
+
var HOST_MODES = ["opencode", "codex", "claude", "pi", "jcode"];
|
|
1050
1050
|
function isSupportedHostMode(value) {
|
|
1051
1051
|
return HOST_MODES.includes(value);
|
|
1052
1052
|
}
|
|
@@ -1101,9 +1101,9 @@ import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
1101
1101
|
import * as path from "path";
|
|
1102
1102
|
|
|
1103
1103
|
// src/eval/report-formatters.ts
|
|
1104
|
-
function assertFiniteNumber(value,
|
|
1104
|
+
function assertFiniteNumber(value, path27) {
|
|
1105
1105
|
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
1106
|
-
throw new Error(`${
|
|
1106
|
+
throw new Error(`${path27} must be a finite number`);
|
|
1107
1107
|
}
|
|
1108
1108
|
return value;
|
|
1109
1109
|
}
|
|
@@ -1291,13 +1291,13 @@ function buildPerQueryArtifact(perQuery) {
|
|
|
1291
1291
|
}
|
|
1292
1292
|
|
|
1293
1293
|
// src/eval/runner.ts
|
|
1294
|
-
import { existsSync as
|
|
1295
|
-
import * as
|
|
1294
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1295
|
+
import * as path15 from "path";
|
|
1296
1296
|
import { performance as performance3 } from "perf_hooks";
|
|
1297
1297
|
|
|
1298
1298
|
// src/indexer/index.ts
|
|
1299
|
-
import { existsSync as
|
|
1300
|
-
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";
|
|
1301
1301
|
import { performance as performance2 } from "perf_hooks";
|
|
1302
1302
|
import { execFile as execFile3 } from "child_process";
|
|
1303
1303
|
import { promisify as promisify3 } from "util";
|
|
@@ -2579,17 +2579,17 @@ function validateExternalUrl(urlString) {
|
|
|
2579
2579
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2580
2580
|
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2581
2581
|
}
|
|
2582
|
-
const
|
|
2583
|
-
if (BLOCKED_HOSTNAMES.has(
|
|
2584
|
-
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})` };
|
|
2585
2585
|
}
|
|
2586
2586
|
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2587
|
-
if (pattern.test(
|
|
2588
|
-
return { valid: false, reason: `Blocked: cloud metadata IP (${
|
|
2587
|
+
if (pattern.test(hostname2)) {
|
|
2588
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
|
|
2589
2589
|
}
|
|
2590
2590
|
}
|
|
2591
|
-
if (/^169\.254\./.test(
|
|
2592
|
-
return { valid: false, reason: `Blocked: link-local address (${
|
|
2591
|
+
if (/^169\.254\./.test(hostname2)) {
|
|
2592
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
|
|
2593
2593
|
}
|
|
2594
2594
|
return { valid: true };
|
|
2595
2595
|
}
|
|
@@ -3779,6 +3779,12 @@ function createMockNativeBinding() {
|
|
|
3779
3779
|
constructor() {
|
|
3780
3780
|
throw error;
|
|
3781
3781
|
}
|
|
3782
|
+
static openReadOnly() {
|
|
3783
|
+
throw error;
|
|
3784
|
+
}
|
|
3785
|
+
static createEmptyReadOnly() {
|
|
3786
|
+
throw error;
|
|
3787
|
+
}
|
|
3782
3788
|
close() {
|
|
3783
3789
|
throw error;
|
|
3784
3790
|
}
|
|
@@ -3882,6 +3888,12 @@ var VectorStore = class {
|
|
|
3882
3888
|
load() {
|
|
3883
3889
|
this.inner.load();
|
|
3884
3890
|
}
|
|
3891
|
+
loadStrict() {
|
|
3892
|
+
this.inner.loadStrict();
|
|
3893
|
+
}
|
|
3894
|
+
hasFingerprint() {
|
|
3895
|
+
return this.inner.hasFingerprint();
|
|
3896
|
+
}
|
|
3885
3897
|
count() {
|
|
3886
3898
|
return this.inner.count();
|
|
3887
3899
|
}
|
|
@@ -4164,12 +4176,24 @@ var InvertedIndex = class {
|
|
|
4164
4176
|
return this.inner.documentCount();
|
|
4165
4177
|
}
|
|
4166
4178
|
};
|
|
4167
|
-
var Database = class {
|
|
4179
|
+
var Database = class _Database {
|
|
4168
4180
|
inner;
|
|
4169
4181
|
closed = false;
|
|
4170
4182
|
constructor(dbPath) {
|
|
4171
4183
|
this.inner = new native.Database(dbPath);
|
|
4172
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
|
+
}
|
|
4173
4197
|
throwIfClosed() {
|
|
4174
4198
|
if (this.closed) {
|
|
4175
4199
|
throw new Error("Database is closed");
|
|
@@ -4638,6 +4662,9 @@ function getHostProjectIndexRelativePath(host) {
|
|
|
4638
4662
|
function hasHostProjectConfig(projectRoot, host) {
|
|
4639
4663
|
return existsSync5(path8.join(projectRoot, getProjectConfigRelativePath(host)));
|
|
4640
4664
|
}
|
|
4665
|
+
function hasHostGlobalConfig(host) {
|
|
4666
|
+
return existsSync5(getGlobalConfigPath(host));
|
|
4667
|
+
}
|
|
4641
4668
|
function getGlobalIndexPath(host = "opencode") {
|
|
4642
4669
|
switch (host) {
|
|
4643
4670
|
case "opencode":
|
|
@@ -4677,6 +4704,9 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
4677
4704
|
return hostIndexPath;
|
|
4678
4705
|
}
|
|
4679
4706
|
if (host !== "opencode") {
|
|
4707
|
+
if (hasHostGlobalConfig(host)) {
|
|
4708
|
+
return hostIndexPath;
|
|
4709
|
+
}
|
|
4680
4710
|
const legacyIndexPath = getGlobalIndexPath("opencode");
|
|
4681
4711
|
if (existsSync5(legacyIndexPath)) {
|
|
4682
4712
|
return legacyIndexPath;
|
|
@@ -4893,6 +4923,432 @@ async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
|
|
|
4893
4923
|
}
|
|
4894
4924
|
}
|
|
4895
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
|
+
|
|
4896
5352
|
// src/indexer/index.ts
|
|
4897
5353
|
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
|
|
4898
5354
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
@@ -4974,6 +5430,7 @@ function isSqliteCorruptionError(error) {
|
|
|
4974
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");
|
|
4975
5431
|
}
|
|
4976
5432
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
5433
|
+
var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
|
|
4977
5434
|
function metadataFromBlame(blame) {
|
|
4978
5435
|
if (!blame) {
|
|
4979
5436
|
return {};
|
|
@@ -5171,9 +5628,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
5171
5628
|
return true;
|
|
5172
5629
|
}
|
|
5173
5630
|
function isPathWithinRoot(filePath, rootPath) {
|
|
5174
|
-
const normalizedFilePath =
|
|
5175
|
-
const normalizedRoot =
|
|
5176
|
-
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}`);
|
|
5177
5634
|
}
|
|
5178
5635
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
5179
5636
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -6239,26 +6696,133 @@ var Indexer = class {
|
|
|
6239
6696
|
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
6240
6697
|
querySimilarityThreshold = 0.85;
|
|
6241
6698
|
indexCompatibility = null;
|
|
6242
|
-
|
|
6699
|
+
activeIndexLease = null;
|
|
6700
|
+
initializationPromise = null;
|
|
6701
|
+
initializationMode = "none";
|
|
6702
|
+
readIssues = [];
|
|
6703
|
+
retiredDatabases = [];
|
|
6704
|
+
readerArtifactFingerprint = null;
|
|
6705
|
+
writerArtifactFingerprint = null;
|
|
6706
|
+
readerArtifactRetryAfter = /* @__PURE__ */ new Map();
|
|
6243
6707
|
constructor(projectRoot, config, host = "opencode") {
|
|
6244
6708
|
this.projectRoot = projectRoot;
|
|
6245
6709
|
this.config = config;
|
|
6246
6710
|
this.host = host;
|
|
6247
6711
|
this.indexPath = this.getIndexPath();
|
|
6248
|
-
this.fileHashCachePath =
|
|
6249
|
-
this.failedBatchesPath =
|
|
6250
|
-
this.indexingLockPath = path11.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");
|
|
6251
6714
|
this.logger = initializeLogger(config.debug);
|
|
6252
6715
|
}
|
|
6253
6716
|
getIndexPath() {
|
|
6254
6717
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
|
|
6255
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
|
+
}
|
|
6256
6820
|
loadFileHashCache() {
|
|
6257
|
-
if (!
|
|
6821
|
+
if (!existsSync7(this.fileHashCachePath)) {
|
|
6258
6822
|
return;
|
|
6259
6823
|
}
|
|
6260
6824
|
try {
|
|
6261
|
-
const data =
|
|
6825
|
+
const data = readFileSync7(this.fileHashCachePath, "utf-8");
|
|
6262
6826
|
const parsed = JSON.parse(data);
|
|
6263
6827
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
6264
6828
|
} catch (error) {
|
|
@@ -6278,15 +6842,26 @@ var Indexer = class {
|
|
|
6278
6842
|
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
6279
6843
|
}
|
|
6280
6844
|
atomicWriteSync(targetPath, data) {
|
|
6281
|
-
const
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
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
|
+
);
|
|
6285
6860
|
}
|
|
6286
6861
|
getScopedRoots() {
|
|
6287
|
-
const roots = /* @__PURE__ */ new Set([
|
|
6862
|
+
const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
|
|
6288
6863
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
6289
|
-
roots.add(
|
|
6864
|
+
roots.add(path12.resolve(this.projectRoot, kbRoot));
|
|
6290
6865
|
}
|
|
6291
6866
|
return Array.from(roots);
|
|
6292
6867
|
}
|
|
@@ -6295,29 +6870,29 @@ var Indexer = class {
|
|
|
6295
6870
|
if (this.config.scope !== "global") {
|
|
6296
6871
|
return branchName;
|
|
6297
6872
|
}
|
|
6298
|
-
const projectHash = hashContent(
|
|
6873
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6299
6874
|
return `${projectHash}:${branchName}`;
|
|
6300
6875
|
}
|
|
6301
6876
|
getBranchCatalogKeyFor(branchName) {
|
|
6302
6877
|
if (this.config.scope !== "global") {
|
|
6303
6878
|
return branchName;
|
|
6304
6879
|
}
|
|
6305
|
-
const projectHash = hashContent(
|
|
6880
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6306
6881
|
return `${projectHash}:${branchName}`;
|
|
6307
6882
|
}
|
|
6308
6883
|
getLegacyBranchCatalogKey() {
|
|
6309
6884
|
return this.currentBranch || "default";
|
|
6310
6885
|
}
|
|
6311
6886
|
getLegacyMigrationMetadataKey() {
|
|
6312
|
-
const projectHash = hashContent(
|
|
6887
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6313
6888
|
return `index.globalBranchMigration.${projectHash}`;
|
|
6314
6889
|
}
|
|
6315
6890
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
6316
|
-
const projectHash = hashContent(
|
|
6891
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6317
6892
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
6318
6893
|
}
|
|
6319
6894
|
getProjectForceReembedMetadataKey() {
|
|
6320
|
-
const projectHash = hashContent(
|
|
6895
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6321
6896
|
return `index.forceReembed.${projectHash}`;
|
|
6322
6897
|
}
|
|
6323
6898
|
hasProjectForceReembedPending() {
|
|
@@ -6411,7 +6986,7 @@ var Indexer = class {
|
|
|
6411
6986
|
if (!this.database) {
|
|
6412
6987
|
return { chunkIds, symbolIds };
|
|
6413
6988
|
}
|
|
6414
|
-
const projectRootPath =
|
|
6989
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6415
6990
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6416
6991
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6417
6992
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6434,7 +7009,7 @@ var Indexer = class {
|
|
|
6434
7009
|
if (this.config.scope !== "global") {
|
|
6435
7010
|
return this.getBranchCatalogCleanupKeys();
|
|
6436
7011
|
}
|
|
6437
|
-
const projectHash = hashContent(
|
|
7012
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6438
7013
|
const keys = /* @__PURE__ */ new Set();
|
|
6439
7014
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6440
7015
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6518,7 +7093,7 @@ var Indexer = class {
|
|
|
6518
7093
|
if (!this.database || this.config.scope !== "global") {
|
|
6519
7094
|
return false;
|
|
6520
7095
|
}
|
|
6521
|
-
const projectHash = hashContent(
|
|
7096
|
+
const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
|
|
6522
7097
|
const roots = this.getScopedRoots();
|
|
6523
7098
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6524
7099
|
return this.database.getAllBranches().some(
|
|
@@ -6552,7 +7127,7 @@ var Indexer = class {
|
|
|
6552
7127
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6553
7128
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6554
7129
|
]);
|
|
6555
|
-
const projectRootPath =
|
|
7130
|
+
const projectRootPath = path12.resolve(this.projectRoot);
|
|
6556
7131
|
const projectLocalFilePaths = new Set(
|
|
6557
7132
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6558
7133
|
);
|
|
@@ -6614,34 +7189,27 @@ var Indexer = class {
|
|
|
6614
7189
|
database.gcOrphanEmbeddings();
|
|
6615
7190
|
database.gcOrphanChunks();
|
|
6616
7191
|
store.save();
|
|
6617
|
-
|
|
7192
|
+
this.saveInvertedIndex(invertedIndex);
|
|
6618
7193
|
return {
|
|
6619
7194
|
removedChunkIds: removedChunkIdList,
|
|
6620
7195
|
hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
|
|
6621
7196
|
};
|
|
6622
7197
|
}
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
|
|
6632
|
-
}
|
|
6633
|
-
releaseIndexingLock() {
|
|
6634
|
-
if (existsSync6(this.indexingLockPath)) {
|
|
6635
|
-
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
|
+
});
|
|
6636
7206
|
}
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
7207
|
+
if (this.config.scope === "global") {
|
|
7208
|
+
if (existsSync7(this.fileHashCachePath)) {
|
|
7209
|
+
unlinkSync(this.fileHashCachePath);
|
|
7210
|
+
}
|
|
7211
|
+
await this.healthCheckUnlocked();
|
|
6642
7212
|
}
|
|
6643
|
-
await this.healthCheck();
|
|
6644
|
-
this.releaseIndexingLock();
|
|
6645
7213
|
this.logger.info("Recovery complete, next index will re-process all files");
|
|
6646
7214
|
}
|
|
6647
7215
|
loadFailedBatches(maxChunkTokens) {
|
|
@@ -6657,10 +7225,10 @@ var Indexer = class {
|
|
|
6657
7225
|
}
|
|
6658
7226
|
}
|
|
6659
7227
|
loadSerializedFailedBatches() {
|
|
6660
|
-
if (!
|
|
7228
|
+
if (!existsSync7(this.failedBatchesPath)) {
|
|
6661
7229
|
return [];
|
|
6662
7230
|
}
|
|
6663
|
-
const data =
|
|
7231
|
+
const data = readFileSync7(this.failedBatchesPath, "utf-8");
|
|
6664
7232
|
const parsed = JSON.parse(data);
|
|
6665
7233
|
return parsed.map((batch) => {
|
|
6666
7234
|
const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
|
|
@@ -6677,7 +7245,7 @@ var Indexer = class {
|
|
|
6677
7245
|
}
|
|
6678
7246
|
saveFailedBatches(batches) {
|
|
6679
7247
|
if (batches.length === 0) {
|
|
6680
|
-
if (
|
|
7248
|
+
if (existsSync7(this.failedBatchesPath)) {
|
|
6681
7249
|
try {
|
|
6682
7250
|
unlinkSync(this.failedBatchesPath);
|
|
6683
7251
|
} catch {
|
|
@@ -6685,7 +7253,7 @@ var Indexer = class {
|
|
|
6685
7253
|
}
|
|
6686
7254
|
return;
|
|
6687
7255
|
}
|
|
6688
|
-
|
|
7256
|
+
this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
6689
7257
|
}
|
|
6690
7258
|
collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
|
|
6691
7259
|
const retryableById = /* @__PURE__ */ new Map();
|
|
@@ -6879,6 +7447,222 @@ var Indexer = class {
|
|
|
6879
7447
|
return parts.join("\n");
|
|
6880
7448
|
}
|
|
6881
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;
|
|
7659
|
+
}
|
|
7660
|
+
async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
|
|
7661
|
+
if (mode === "writer") {
|
|
7662
|
+
this.requireActiveLease();
|
|
7663
|
+
}
|
|
7664
|
+
this.readIssues = [];
|
|
7665
|
+
this.readerArtifactRetryAfter.clear();
|
|
6882
7666
|
if (this.config.embeddingProvider === "custom") {
|
|
6883
7667
|
if (!this.config.customProvider) {
|
|
6884
7668
|
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
@@ -6910,36 +7694,103 @@ var Indexer = class {
|
|
|
6910
7694
|
});
|
|
6911
7695
|
}
|
|
6912
7696
|
}
|
|
6913
|
-
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
6914
7697
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6915
|
-
const storePath =
|
|
6916
|
-
|
|
6917
|
-
const
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
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();
|
|
6928
7723
|
}
|
|
6929
7724
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
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;
|
|
6938
7743
|
}
|
|
7744
|
+
} else {
|
|
6939
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
|
+
}
|
|
6940
7759
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6941
|
-
|
|
6942
|
-
|
|
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
|
+
}
|
|
6943
7794
|
}
|
|
6944
7795
|
if (isGitRepo(this.projectRoot)) {
|
|
6945
7796
|
this.currentBranch = getBranchOrDefault(this.projectRoot);
|
|
@@ -6953,10 +7804,10 @@ var Indexer = class {
|
|
|
6953
7804
|
this.baseBranch = "default";
|
|
6954
7805
|
this.logger.branch("debug", "Not a git repository, using default branch");
|
|
6955
7806
|
}
|
|
6956
|
-
if (
|
|
6957
|
-
await this.
|
|
7807
|
+
if (mode === "writer" && recoveredOwners.length > 0) {
|
|
7808
|
+
await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
|
|
6958
7809
|
}
|
|
6959
|
-
if (dbIsNew && this.store.count() > 0) {
|
|
7810
|
+
if (mode === "writer" && dbIsNew && this.store.count() > 0) {
|
|
6960
7811
|
this.migrateFromLegacyIndex();
|
|
6961
7812
|
}
|
|
6962
7813
|
this.loadFileHashCache();
|
|
@@ -6968,9 +7819,11 @@ var Indexer = class {
|
|
|
6968
7819
|
configuredProviderInfo: this.configuredProviderInfo
|
|
6969
7820
|
});
|
|
6970
7821
|
}
|
|
6971
|
-
if (this.config.indexing.autoGc) {
|
|
7822
|
+
if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
|
|
6972
7823
|
await this.maybeRunAutoGc();
|
|
6973
7824
|
}
|
|
7825
|
+
this.initializationMode = mode;
|
|
7826
|
+
this.readerArtifactFingerprint = readerArtifactFingerprint;
|
|
6974
7827
|
}
|
|
6975
7828
|
async maybeRunAutoGc() {
|
|
6976
7829
|
if (!this.database) return;
|
|
@@ -6987,7 +7840,7 @@ var Indexer = class {
|
|
|
6987
7840
|
}
|
|
6988
7841
|
}
|
|
6989
7842
|
if (shouldRunGc) {
|
|
6990
|
-
const result = await this.
|
|
7843
|
+
const result = await this.healthCheckUnlocked();
|
|
6991
7844
|
if (result.warning) {
|
|
6992
7845
|
this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
|
|
6993
7846
|
} else {
|
|
@@ -7009,7 +7862,7 @@ var Indexer = class {
|
|
|
7009
7862
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
7010
7863
|
return {
|
|
7011
7864
|
resetCorruptedIndex: true,
|
|
7012
|
-
warning: this.getCorruptedIndexWarning(
|
|
7865
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
7013
7866
|
};
|
|
7014
7867
|
}
|
|
7015
7868
|
throw error;
|
|
@@ -7024,28 +7877,29 @@ var Indexer = class {
|
|
|
7024
7877
|
return;
|
|
7025
7878
|
}
|
|
7026
7879
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
7027
|
-
const storeBasePath =
|
|
7028
|
-
const storeIndexPath =
|
|
7880
|
+
const storeBasePath = path12.join(this.indexPath, "vectors");
|
|
7881
|
+
const storeIndexPath = storeBasePath;
|
|
7029
7882
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
7030
|
-
const
|
|
7031
|
-
const
|
|
7883
|
+
const lease = this.requireActiveLease();
|
|
7884
|
+
const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
|
|
7885
|
+
const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
|
|
7032
7886
|
let backedUpIndex = false;
|
|
7033
7887
|
let backedUpMetadata = false;
|
|
7034
7888
|
let rebuiltCount = 0;
|
|
7035
7889
|
let skippedCount = 0;
|
|
7036
|
-
if (
|
|
7890
|
+
if (existsSync7(backupIndexPath)) {
|
|
7037
7891
|
unlinkSync(backupIndexPath);
|
|
7038
7892
|
}
|
|
7039
|
-
if (
|
|
7893
|
+
if (existsSync7(backupMetadataPath)) {
|
|
7040
7894
|
unlinkSync(backupMetadataPath);
|
|
7041
7895
|
}
|
|
7042
7896
|
try {
|
|
7043
|
-
if (
|
|
7044
|
-
|
|
7897
|
+
if (existsSync7(storeIndexPath)) {
|
|
7898
|
+
renameSync2(storeIndexPath, backupIndexPath);
|
|
7045
7899
|
backedUpIndex = true;
|
|
7046
7900
|
}
|
|
7047
|
-
if (
|
|
7048
|
-
|
|
7901
|
+
if (existsSync7(storeMetadataPath)) {
|
|
7902
|
+
renameSync2(storeMetadataPath, backupMetadataPath);
|
|
7049
7903
|
backedUpMetadata = true;
|
|
7050
7904
|
}
|
|
7051
7905
|
store.clear();
|
|
@@ -7065,10 +7919,10 @@ var Indexer = class {
|
|
|
7065
7919
|
rebuiltCount += 1;
|
|
7066
7920
|
}
|
|
7067
7921
|
store.save();
|
|
7068
|
-
if (backedUpIndex &&
|
|
7922
|
+
if (backedUpIndex && existsSync7(backupIndexPath)) {
|
|
7069
7923
|
unlinkSync(backupIndexPath);
|
|
7070
7924
|
}
|
|
7071
|
-
if (backedUpMetadata &&
|
|
7925
|
+
if (backedUpMetadata && existsSync7(backupMetadataPath)) {
|
|
7072
7926
|
unlinkSync(backupMetadataPath);
|
|
7073
7927
|
}
|
|
7074
7928
|
this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
|
|
@@ -7081,17 +7935,17 @@ var Indexer = class {
|
|
|
7081
7935
|
store.clear();
|
|
7082
7936
|
} catch {
|
|
7083
7937
|
}
|
|
7084
|
-
if (
|
|
7938
|
+
if (existsSync7(storeIndexPath)) {
|
|
7085
7939
|
unlinkSync(storeIndexPath);
|
|
7086
7940
|
}
|
|
7087
|
-
if (
|
|
7941
|
+
if (existsSync7(storeMetadataPath)) {
|
|
7088
7942
|
unlinkSync(storeMetadataPath);
|
|
7089
7943
|
}
|
|
7090
|
-
if (backedUpIndex &&
|
|
7091
|
-
|
|
7944
|
+
if (backedUpIndex && existsSync7(backupIndexPath)) {
|
|
7945
|
+
renameSync2(backupIndexPath, storeIndexPath);
|
|
7092
7946
|
}
|
|
7093
|
-
if (backedUpMetadata &&
|
|
7094
|
-
|
|
7947
|
+
if (backedUpMetadata && existsSync7(backupMetadataPath)) {
|
|
7948
|
+
renameSync2(backupMetadataPath, storeMetadataPath);
|
|
7095
7949
|
}
|
|
7096
7950
|
if (backedUpIndex || backedUpMetadata) {
|
|
7097
7951
|
store.load();
|
|
@@ -7105,11 +7959,37 @@ var Indexer = class {
|
|
|
7105
7959
|
}
|
|
7106
7960
|
return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
|
|
7107
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
|
+
}
|
|
7108
7988
|
async tryResetCorruptedIndex(stage, error) {
|
|
7109
7989
|
if (!isSqliteCorruptionError(error)) {
|
|
7110
7990
|
return false;
|
|
7111
7991
|
}
|
|
7112
|
-
const dbPath =
|
|
7992
|
+
const dbPath = path12.join(this.indexPath, "codebase.db");
|
|
7113
7993
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
7114
7994
|
const errorMessage = getErrorMessage2(error);
|
|
7115
7995
|
if (this.config.scope === "global") {
|
|
@@ -7125,30 +8005,7 @@ var Indexer = class {
|
|
|
7125
8005
|
dbPath,
|
|
7126
8006
|
error: errorMessage
|
|
7127
8007
|
});
|
|
7128
|
-
this.
|
|
7129
|
-
this.invertedIndex = null;
|
|
7130
|
-
this.database?.close();
|
|
7131
|
-
this.database = null;
|
|
7132
|
-
this.indexCompatibility = null;
|
|
7133
|
-
this.fileHashCache.clear();
|
|
7134
|
-
const resetPaths = [
|
|
7135
|
-
path11.join(this.indexPath, "codebase.db"),
|
|
7136
|
-
path11.join(this.indexPath, "codebase.db-shm"),
|
|
7137
|
-
path11.join(this.indexPath, "codebase.db-wal"),
|
|
7138
|
-
path11.join(this.indexPath, "vectors.usearch"),
|
|
7139
|
-
path11.join(this.indexPath, "inverted-index.json"),
|
|
7140
|
-
path11.join(this.indexPath, "file-hashes.json"),
|
|
7141
|
-
path11.join(this.indexPath, "failed-batches.json"),
|
|
7142
|
-
path11.join(this.indexPath, "indexing.lock"),
|
|
7143
|
-
path11.join(this.indexPath, "vectors")
|
|
7144
|
-
];
|
|
7145
|
-
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
7146
|
-
try {
|
|
7147
|
-
await fsPromises2.rm(targetPath, { recursive: true, force: true });
|
|
7148
|
-
} catch {
|
|
7149
|
-
}
|
|
7150
|
-
}));
|
|
7151
|
-
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
8008
|
+
await this.resetLocalIndexArtifacts();
|
|
7152
8009
|
return true;
|
|
7153
8010
|
}
|
|
7154
8011
|
migrateFromLegacyIndex() {
|
|
@@ -7267,8 +8124,62 @@ var Indexer = class {
|
|
|
7267
8124
|
return this.indexCompatibility;
|
|
7268
8125
|
}
|
|
7269
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() {
|
|
7270
8181
|
if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
|
|
7271
|
-
|
|
8182
|
+
throw new Error("Index state is not initialized");
|
|
7272
8183
|
}
|
|
7273
8184
|
return {
|
|
7274
8185
|
store: this.store,
|
|
@@ -7292,7 +8203,12 @@ var Indexer = class {
|
|
|
7292
8203
|
return createCostEstimate(files, configuredProviderInfo);
|
|
7293
8204
|
}
|
|
7294
8205
|
async index(onProgress) {
|
|
7295
|
-
|
|
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);
|
|
7296
8212
|
const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
7297
8213
|
const branchCatalogKey = this.getBranchCatalogKey();
|
|
7298
8214
|
const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
|
|
@@ -7302,7 +8218,6 @@ var Indexer = class {
|
|
|
7302
8218
|
`${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
|
|
7303
8219
|
);
|
|
7304
8220
|
}
|
|
7305
|
-
this.acquireIndexingLock();
|
|
7306
8221
|
this.logger.recordIndexingStart();
|
|
7307
8222
|
this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
|
|
7308
8223
|
const startTime = Date.now();
|
|
@@ -7451,7 +8366,7 @@ var Indexer = class {
|
|
|
7451
8366
|
for (const parsed of parsedFiles) {
|
|
7452
8367
|
currentFilePaths.add(parsed.path);
|
|
7453
8368
|
if (parsed.chunks.length === 0) {
|
|
7454
|
-
const relativePath =
|
|
8369
|
+
const relativePath = path12.relative(this.projectRoot, parsed.path);
|
|
7455
8370
|
stats.parseFailures.push(relativePath);
|
|
7456
8371
|
}
|
|
7457
8372
|
let fileChunkCount = 0;
|
|
@@ -7651,7 +8566,9 @@ var Indexer = class {
|
|
|
7651
8566
|
database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
|
|
7652
8567
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7653
8568
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7654
|
-
|
|
8569
|
+
const vectorPath = path12.join(this.indexPath, "vectors");
|
|
8570
|
+
const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync7(vectorPath) && existsSync7(`${vectorPath}.meta.json`);
|
|
8571
|
+
if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
|
|
7655
8572
|
store.save();
|
|
7656
8573
|
}
|
|
7657
8574
|
if (scopedRoots) {
|
|
@@ -7672,7 +8589,6 @@ var Indexer = class {
|
|
|
7672
8589
|
chunksProcessed: 0,
|
|
7673
8590
|
totalChunks: 0
|
|
7674
8591
|
});
|
|
7675
|
-
this.releaseIndexingLock();
|
|
7676
8592
|
return stats;
|
|
7677
8593
|
}
|
|
7678
8594
|
if (pendingChunks.length === 0) {
|
|
@@ -7681,7 +8597,7 @@ var Indexer = class {
|
|
|
7681
8597
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7682
8598
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7683
8599
|
store.save();
|
|
7684
|
-
|
|
8600
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7685
8601
|
if (scopedRoots) {
|
|
7686
8602
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7687
8603
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7700,7 +8616,6 @@ var Indexer = class {
|
|
|
7700
8616
|
chunksProcessed: 0,
|
|
7701
8617
|
totalChunks: 0
|
|
7702
8618
|
});
|
|
7703
|
-
this.releaseIndexingLock();
|
|
7704
8619
|
return stats;
|
|
7705
8620
|
}
|
|
7706
8621
|
onProgress?.({
|
|
@@ -7931,7 +8846,7 @@ var Indexer = class {
|
|
|
7931
8846
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7932
8847
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7933
8848
|
store.save();
|
|
7934
|
-
|
|
8849
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7935
8850
|
if (scopedRoots) {
|
|
7936
8851
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7937
8852
|
} else {
|
|
@@ -7983,7 +8898,6 @@ var Indexer = class {
|
|
|
7983
8898
|
chunksProcessed: stats.indexedChunks,
|
|
7984
8899
|
totalChunks: pendingChunks.length
|
|
7985
8900
|
});
|
|
7986
|
-
this.releaseIndexingLock();
|
|
7987
8901
|
return stats;
|
|
7988
8902
|
}
|
|
7989
8903
|
async getQueryEmbedding(query, provider) {
|
|
@@ -8049,8 +8963,8 @@ var Indexer = class {
|
|
|
8049
8963
|
return intersection / union;
|
|
8050
8964
|
}
|
|
8051
8965
|
async search(query, limit, options) {
|
|
8052
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8053
|
-
|
|
8966
|
+
const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8967
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8054
8968
|
if (!compatibility.compatible) {
|
|
8055
8969
|
throw new Error(
|
|
8056
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.`
|
|
@@ -8064,6 +8978,7 @@ var Indexer = class {
|
|
|
8064
8978
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
8065
8979
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
8066
8980
|
const fusionStrategy = this.config.search.fusionStrategy;
|
|
8981
|
+
const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
|
|
8067
8982
|
const rrfK = this.config.search.rrfK;
|
|
8068
8983
|
const rerankTopN = this.config.search.rerankTopN;
|
|
8069
8984
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
@@ -8072,7 +8987,7 @@ var Indexer = class {
|
|
|
8072
8987
|
this.logger.search("debug", "Starting search", {
|
|
8073
8988
|
query,
|
|
8074
8989
|
maxResults,
|
|
8075
|
-
hybridWeight,
|
|
8990
|
+
hybridWeight: effectiveHybridWeight,
|
|
8076
8991
|
fusionStrategy,
|
|
8077
8992
|
rrfK,
|
|
8078
8993
|
rerankTopN,
|
|
@@ -8080,13 +8995,22 @@ var Indexer = class {
|
|
|
8080
8995
|
});
|
|
8081
8996
|
const embeddingStartTime = performance2.now();
|
|
8082
8997
|
const embeddingQuery = stripFilePathHint(query);
|
|
8083
|
-
|
|
8998
|
+
let embedding;
|
|
8999
|
+
try {
|
|
9000
|
+
embedding = await this.getQueryEmbedding(embeddingQuery, provider);
|
|
9001
|
+
} catch (error) {
|
|
9002
|
+
this.logger.warn("Query embedding failed; falling back to keyword-only search", {
|
|
9003
|
+
query,
|
|
9004
|
+
error: getErrorMessage2(error),
|
|
9005
|
+
action: "Check the embedding provider configuration and retry search after restoring provider health."
|
|
9006
|
+
});
|
|
9007
|
+
}
|
|
8084
9008
|
const embeddingMs = performance2.now() - embeddingStartTime;
|
|
8085
9009
|
const vectorStartTime = performance2.now();
|
|
8086
|
-
const semanticResults = store.search(embedding, maxResults * 4);
|
|
9010
|
+
const semanticResults = embedding ? store.search(embedding, maxResults * 4) : [];
|
|
8087
9011
|
const vectorMs = performance2.now() - vectorStartTime;
|
|
8088
9012
|
const keywordStartTime = performance2.now();
|
|
8089
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
9013
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
8090
9014
|
const keywordMs = performance2.now() - keywordStartTime;
|
|
8091
9015
|
let branchChunkIds = null;
|
|
8092
9016
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -8118,12 +9042,13 @@ var Indexer = class {
|
|
|
8118
9042
|
});
|
|
8119
9043
|
}
|
|
8120
9044
|
const fusionStartTime = performance2.now();
|
|
9045
|
+
const rankingHybridWeight = embedding === void 0 && fusionStrategy === "weighted" ? 1 : effectiveHybridWeight;
|
|
8121
9046
|
const combined = rankHybridResults(query, semanticCandidates, keywordCandidates, {
|
|
8122
9047
|
fusionStrategy,
|
|
8123
9048
|
rrfK,
|
|
8124
9049
|
rerankTopN,
|
|
8125
9050
|
limit: maxResults,
|
|
8126
|
-
hybridWeight,
|
|
9051
|
+
hybridWeight: rankingHybridWeight,
|
|
8127
9052
|
prioritizeSourcePaths: sourceIntent
|
|
8128
9053
|
});
|
|
8129
9054
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -8223,8 +9148,7 @@ var Indexer = class {
|
|
|
8223
9148
|
})
|
|
8224
9149
|
);
|
|
8225
9150
|
}
|
|
8226
|
-
async keywordSearch(query, limit) {
|
|
8227
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9151
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
8228
9152
|
const scores = invertedIndex.search(query);
|
|
8229
9153
|
if (scores.size === 0) {
|
|
8230
9154
|
return [];
|
|
@@ -8242,24 +9166,54 @@ var Indexer = class {
|
|
|
8242
9166
|
return results.slice(0, limit);
|
|
8243
9167
|
}
|
|
8244
9168
|
async getStatus() {
|
|
8245
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9169
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8246
9170
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9171
|
+
const vectorCount = store.count();
|
|
9172
|
+
const statusReadIssues = [...readIssues];
|
|
9173
|
+
let startupWarning = "";
|
|
9174
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9175
|
+
try {
|
|
9176
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9177
|
+
} catch (error) {
|
|
9178
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9179
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9180
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9181
|
+
this.recordReadIssue("database", message, error);
|
|
9182
|
+
}
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9186
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9187
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
8247
9188
|
return {
|
|
8248
|
-
indexed:
|
|
8249
|
-
vectorCount
|
|
9189
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9190
|
+
vectorCount,
|
|
8250
9191
|
provider: configuredProviderInfo.provider,
|
|
8251
9192
|
model: configuredProviderInfo.modelInfo.model,
|
|
8252
9193
|
indexPath: this.indexPath,
|
|
8253
9194
|
currentBranch: this.currentBranch,
|
|
8254
9195
|
baseBranch: this.baseBranch,
|
|
8255
|
-
compatibility
|
|
9196
|
+
compatibility,
|
|
8256
9197
|
failedBatchesCount,
|
|
8257
9198
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
8258
|
-
warning:
|
|
9199
|
+
warning: warning || void 0
|
|
8259
9200
|
};
|
|
8260
9201
|
}
|
|
9202
|
+
async forceIndex(onProgress) {
|
|
9203
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9204
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9205
|
+
await this.clearIndexUnlocked();
|
|
9206
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9207
|
+
});
|
|
9208
|
+
}
|
|
8261
9209
|
async clearIndex() {
|
|
8262
|
-
|
|
9210
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9211
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9212
|
+
await this.clearIndexUnlocked();
|
|
9213
|
+
});
|
|
9214
|
+
}
|
|
9215
|
+
async clearIndexUnlocked() {
|
|
9216
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8263
9217
|
if (this.config.scope === "global") {
|
|
8264
9218
|
store.load();
|
|
8265
9219
|
invertedIndex.load();
|
|
@@ -8286,7 +9240,7 @@ var Indexer = class {
|
|
|
8286
9240
|
store.clear();
|
|
8287
9241
|
store.save();
|
|
8288
9242
|
invertedIndex.clear();
|
|
8289
|
-
|
|
9243
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8290
9244
|
this.fileHashCache.clear();
|
|
8291
9245
|
this.saveFileHashCache();
|
|
8292
9246
|
database.clearAllIndexedData();
|
|
@@ -8310,14 +9264,7 @@ var Indexer = class {
|
|
|
8310
9264
|
this.indexCompatibility = compatibility;
|
|
8311
9265
|
return;
|
|
8312
9266
|
}
|
|
8313
|
-
|
|
8314
|
-
if (this.host !== "opencode") {
|
|
8315
|
-
localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
8316
|
-
}
|
|
8317
|
-
const isLocalProjectIndex = localProjectIndexPaths.some(
|
|
8318
|
-
(localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
|
|
8319
|
-
);
|
|
8320
|
-
if (!isLocalProjectIndex) {
|
|
9267
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8321
9268
|
throw new Error(
|
|
8322
9269
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8323
9270
|
);
|
|
@@ -8325,7 +9272,7 @@ var Indexer = class {
|
|
|
8325
9272
|
store.clear();
|
|
8326
9273
|
store.save();
|
|
8327
9274
|
invertedIndex.clear();
|
|
8328
|
-
|
|
9275
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8329
9276
|
this.fileHashCache.clear();
|
|
8330
9277
|
this.saveFileHashCache();
|
|
8331
9278
|
database.clearAllIndexedData();
|
|
@@ -8343,7 +9290,13 @@ var Indexer = class {
|
|
|
8343
9290
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8344
9291
|
}
|
|
8345
9292
|
async healthCheck() {
|
|
8346
|
-
|
|
9293
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9294
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9295
|
+
return this.healthCheckUnlocked();
|
|
9296
|
+
});
|
|
9297
|
+
}
|
|
9298
|
+
async healthCheckUnlocked() {
|
|
9299
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8347
9300
|
this.logger.gc("info", "Starting health check");
|
|
8348
9301
|
const allMetadata = store.getAllMetadata();
|
|
8349
9302
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8356,7 +9309,7 @@ var Indexer = class {
|
|
|
8356
9309
|
const removedChunkKeys = [];
|
|
8357
9310
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8358
9311
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8359
|
-
if (!
|
|
9312
|
+
if (!existsSync7(filePath)) {
|
|
8360
9313
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8361
9314
|
for (const key of chunkKeys) {
|
|
8362
9315
|
removedChunkKeys.push(key);
|
|
@@ -8381,7 +9334,7 @@ var Indexer = class {
|
|
|
8381
9334
|
const removedCount = removedChunkKeys.length;
|
|
8382
9335
|
if (removedCount > 0) {
|
|
8383
9336
|
store.save();
|
|
8384
|
-
|
|
9337
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8385
9338
|
}
|
|
8386
9339
|
let gcOrphanEmbeddings;
|
|
8387
9340
|
let gcOrphanChunks;
|
|
@@ -8396,7 +9349,7 @@ var Indexer = class {
|
|
|
8396
9349
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8397
9350
|
throw error;
|
|
8398
9351
|
}
|
|
8399
|
-
await this.
|
|
9352
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8400
9353
|
return {
|
|
8401
9354
|
removed: 0,
|
|
8402
9355
|
filePaths: [],
|
|
@@ -8405,7 +9358,7 @@ var Indexer = class {
|
|
|
8405
9358
|
gcOrphanSymbols: 0,
|
|
8406
9359
|
gcOrphanCallEdges: 0,
|
|
8407
9360
|
resetCorruptedIndex: true,
|
|
8408
|
-
warning: this.getCorruptedIndexWarning(
|
|
9361
|
+
warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
|
|
8409
9362
|
};
|
|
8410
9363
|
}
|
|
8411
9364
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8418,7 +9371,13 @@ var Indexer = class {
|
|
|
8418
9371
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8419
9372
|
}
|
|
8420
9373
|
async retryFailedBatches() {
|
|
8421
|
-
|
|
9374
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9375
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9376
|
+
return this.retryFailedBatchesUnlocked();
|
|
9377
|
+
});
|
|
9378
|
+
}
|
|
9379
|
+
async retryFailedBatchesUnlocked() {
|
|
9380
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8422
9381
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8423
9382
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8424
9383
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8582,7 +9541,7 @@ var Indexer = class {
|
|
|
8582
9541
|
}
|
|
8583
9542
|
if (succeeded > 0) {
|
|
8584
9543
|
store.save();
|
|
8585
|
-
|
|
9544
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8586
9545
|
}
|
|
8587
9546
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8588
9547
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8610,15 +9569,16 @@ var Indexer = class {
|
|
|
8610
9569
|
}
|
|
8611
9570
|
}
|
|
8612
9571
|
async getDatabaseStats() {
|
|
8613
|
-
const { database } = await this.ensureInitialized();
|
|
9572
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9573
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8614
9574
|
return database.getStats();
|
|
8615
9575
|
}
|
|
8616
9576
|
getLogger() {
|
|
8617
9577
|
return this.logger;
|
|
8618
9578
|
}
|
|
8619
9579
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8620
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8621
|
-
|
|
9580
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9581
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8622
9582
|
if (!compatibility.compatible) {
|
|
8623
9583
|
throw new Error(
|
|
8624
9584
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8732,7 +9692,8 @@ var Indexer = class {
|
|
|
8732
9692
|
);
|
|
8733
9693
|
}
|
|
8734
9694
|
async getCallers(targetName, callTypeFilter) {
|
|
8735
|
-
const { database } = await this.ensureInitialized();
|
|
9695
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9696
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8736
9697
|
const seen = /* @__PURE__ */ new Set();
|
|
8737
9698
|
const results = [];
|
|
8738
9699
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8746,7 +9707,8 @@ var Indexer = class {
|
|
|
8746
9707
|
return results;
|
|
8747
9708
|
}
|
|
8748
9709
|
async getCallees(symbolId, callTypeFilter) {
|
|
8749
|
-
const { database } = await this.ensureInitialized();
|
|
9710
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9711
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8750
9712
|
const seen = /* @__PURE__ */ new Set();
|
|
8751
9713
|
const results = [];
|
|
8752
9714
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8760,43 +9722,50 @@ var Indexer = class {
|
|
|
8760
9722
|
return results;
|
|
8761
9723
|
}
|
|
8762
9724
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8763
|
-
const { database } = await this.ensureInitialized();
|
|
9725
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9726
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8764
9727
|
let shortest = [];
|
|
8765
9728
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8766
|
-
const
|
|
8767
|
-
if (
|
|
8768
|
-
shortest =
|
|
9729
|
+
const path27 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9730
|
+
if (path27.length > 0 && (shortest.length === 0 || path27.length < shortest.length)) {
|
|
9731
|
+
shortest = path27;
|
|
8769
9732
|
}
|
|
8770
9733
|
}
|
|
8771
9734
|
return shortest;
|
|
8772
9735
|
}
|
|
8773
9736
|
async getSymbolsForBranch(branch) {
|
|
8774
|
-
const { database } = await this.ensureInitialized();
|
|
9737
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9738
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8775
9739
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8776
9740
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8777
9741
|
}
|
|
8778
9742
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8779
|
-
const { database } = await this.ensureInitialized();
|
|
9743
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9744
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8780
9745
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8781
9746
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8782
9747
|
}
|
|
8783
9748
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8784
|
-
const { database } = await this.ensureInitialized();
|
|
9749
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9750
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8785
9751
|
const branch = this.getBranchCatalogKey();
|
|
8786
9752
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8787
9753
|
}
|
|
8788
9754
|
async detectCommunities(branch, symbolIds) {
|
|
8789
|
-
const { database } = await this.ensureInitialized();
|
|
9755
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9756
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8790
9757
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8791
9758
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8792
9759
|
}
|
|
8793
9760
|
async computeCentrality(branch) {
|
|
8794
|
-
const { database } = await this.ensureInitialized();
|
|
9761
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9762
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8795
9763
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8796
9764
|
return database.computeCentrality(resolvedBranch);
|
|
8797
9765
|
}
|
|
8798
9766
|
async getPrImpact(opts) {
|
|
8799
|
-
const { database } = await this.ensureInitialized();
|
|
9767
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9768
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8800
9769
|
const execFileAsync3 = promisify3(execFile3);
|
|
8801
9770
|
const changedFilesResult = await getChangedFiles({
|
|
8802
9771
|
pr: opts.pr,
|
|
@@ -8819,7 +9788,7 @@ var Indexer = class {
|
|
|
8819
9788
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8820
9789
|
);
|
|
8821
9790
|
}
|
|
8822
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9791
|
+
const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
|
|
8823
9792
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8824
9793
|
const directIds = directSymbols.map((s) => s.id);
|
|
8825
9794
|
const direction = opts.direction ?? "both";
|
|
@@ -8902,7 +9871,7 @@ var Indexer = class {
|
|
|
8902
9871
|
projectRoot: this.projectRoot,
|
|
8903
9872
|
baseBranch: this.baseBranch
|
|
8904
9873
|
});
|
|
8905
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9874
|
+
const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
|
|
8906
9875
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8907
9876
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8908
9877
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8952,7 +9921,8 @@ var Indexer = class {
|
|
|
8952
9921
|
};
|
|
8953
9922
|
}
|
|
8954
9923
|
async getVisualizationData(options) {
|
|
8955
|
-
const { database, store } = await this.ensureInitialized();
|
|
9924
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9925
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8956
9926
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8957
9927
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8958
9928
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8965,12 +9935,12 @@ var Indexer = class {
|
|
|
8965
9935
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8966
9936
|
}
|
|
8967
9937
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8968
|
-
const absoluteDirectoryFilter = directory ?
|
|
9938
|
+
const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
|
|
8969
9939
|
for (const filePath of filePaths) {
|
|
8970
9940
|
if (directory) {
|
|
8971
|
-
const absoluteFilePath =
|
|
9941
|
+
const absoluteFilePath = path12.resolve(filePath);
|
|
8972
9942
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8973
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9943
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
|
|
8974
9944
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8975
9945
|
continue;
|
|
8976
9946
|
}
|
|
@@ -8992,12 +9962,23 @@ var Indexer = class {
|
|
|
8992
9962
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
8993
9963
|
}
|
|
8994
9964
|
async close() {
|
|
8995
|
-
|
|
9965
|
+
this.database?.close();
|
|
9966
|
+
for (const database of this.retiredDatabases) {
|
|
9967
|
+
database.close();
|
|
9968
|
+
}
|
|
9969
|
+
this.retiredDatabases = [];
|
|
8996
9970
|
this.database = null;
|
|
8997
9971
|
this.store = null;
|
|
8998
9972
|
this.invertedIndex = null;
|
|
8999
9973
|
this.provider = null;
|
|
9000
9974
|
this.reranker = null;
|
|
9975
|
+
this.configuredProviderInfo = null;
|
|
9976
|
+
this.indexCompatibility = null;
|
|
9977
|
+
this.initializationMode = "none";
|
|
9978
|
+
this.readIssues = [];
|
|
9979
|
+
this.readerArtifactFingerprint = null;
|
|
9980
|
+
this.writerArtifactFingerprint = null;
|
|
9981
|
+
this.readerArtifactRetryAfter.clear();
|
|
9001
9982
|
}
|
|
9002
9983
|
};
|
|
9003
9984
|
|
|
@@ -9248,15 +10229,15 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
|
|
|
9248
10229
|
}
|
|
9249
10230
|
|
|
9250
10231
|
// src/eval/runner-config.ts
|
|
9251
|
-
import { existsSync as
|
|
9252
|
-
import * as
|
|
9253
|
-
import * as
|
|
10232
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
10233
|
+
import * as os5 from "os";
|
|
10234
|
+
import * as path14 from "path";
|
|
9254
10235
|
|
|
9255
10236
|
// src/config/rebase.ts
|
|
9256
|
-
import * as
|
|
10237
|
+
import * as path13 from "path";
|
|
9257
10238
|
function isWithinRoot(rootDir, targetPath) {
|
|
9258
|
-
const relativePath =
|
|
9259
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
10239
|
+
const relativePath = path13.relative(rootDir, targetPath);
|
|
10240
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
|
|
9260
10241
|
}
|
|
9261
10242
|
function rebasePathEntries(values, fromDir, toDir) {
|
|
9262
10243
|
if (!Array.isArray(values)) {
|
|
@@ -9264,10 +10245,10 @@ function rebasePathEntries(values, fromDir, toDir) {
|
|
|
9264
10245
|
}
|
|
9265
10246
|
return values.filter((value) => typeof value === "string").map((value) => {
|
|
9266
10247
|
const trimmed = value.trim();
|
|
9267
|
-
if (!trimmed ||
|
|
10248
|
+
if (!trimmed || path13.isAbsolute(trimmed)) {
|
|
9268
10249
|
return trimmed;
|
|
9269
10250
|
}
|
|
9270
|
-
return normalizePathSeparators(
|
|
10251
|
+
return normalizePathSeparators(path13.normalize(path13.relative(toDir, path13.resolve(fromDir, trimmed))));
|
|
9271
10252
|
}).filter(Boolean);
|
|
9272
10253
|
}
|
|
9273
10254
|
function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
@@ -9279,17 +10260,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
|
|
|
9279
10260
|
if (!trimmed) {
|
|
9280
10261
|
return trimmed;
|
|
9281
10262
|
}
|
|
9282
|
-
if (
|
|
10263
|
+
if (path13.isAbsolute(trimmed)) {
|
|
9283
10264
|
if (isWithinRoot(sourceRoot, trimmed)) {
|
|
9284
|
-
return normalizePathSeparators(
|
|
10265
|
+
return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
|
|
9285
10266
|
}
|
|
9286
|
-
return
|
|
10267
|
+
return path13.normalize(trimmed);
|
|
9287
10268
|
}
|
|
9288
|
-
const resolvedFromSource =
|
|
10269
|
+
const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
|
|
9289
10270
|
if (isWithinRoot(sourceRoot, resolvedFromSource)) {
|
|
9290
|
-
return normalizePathSeparators(
|
|
10271
|
+
return normalizePathSeparators(path13.normalize(trimmed));
|
|
9291
10272
|
}
|
|
9292
|
-
return normalizePathSeparators(
|
|
10273
|
+
return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
|
|
9293
10274
|
}).filter(Boolean);
|
|
9294
10275
|
}
|
|
9295
10276
|
|
|
@@ -9327,7 +10308,7 @@ function validateEvalConfigShape(rawConfig, filePath) {
|
|
|
9327
10308
|
}
|
|
9328
10309
|
function parseJsonConfigFile(filePath) {
|
|
9329
10310
|
try {
|
|
9330
|
-
return validateEvalConfigShape(JSON.parse(
|
|
10311
|
+
return validateEvalConfigShape(JSON.parse(readFileSync8(filePath, "utf-8")), filePath);
|
|
9331
10312
|
} catch (error) {
|
|
9332
10313
|
if (error instanceof Error && error.message.startsWith("Eval config at ")) {
|
|
9333
10314
|
throw error;
|
|
@@ -9337,20 +10318,20 @@ function parseJsonConfigFile(filePath) {
|
|
|
9337
10318
|
}
|
|
9338
10319
|
}
|
|
9339
10320
|
function toAbsolute(projectRoot, maybeRelative) {
|
|
9340
|
-
return
|
|
10321
|
+
return path14.isAbsolute(maybeRelative) ? maybeRelative : path14.join(projectRoot, maybeRelative);
|
|
9341
10322
|
}
|
|
9342
10323
|
function isProjectScopedConfigPath(configPath) {
|
|
9343
|
-
return
|
|
10324
|
+
return path14.basename(configPath) === "codebase-index.json" && path14.basename(path14.dirname(configPath)) === ".opencode";
|
|
9344
10325
|
}
|
|
9345
10326
|
function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
|
|
9346
10327
|
const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
|
|
9347
10328
|
const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
|
|
9348
10329
|
values,
|
|
9349
|
-
|
|
10330
|
+
path14.dirname(path14.dirname(resolvedConfigPath)),
|
|
9350
10331
|
projectRoot
|
|
9351
10332
|
) : rebasePathEntries(
|
|
9352
10333
|
values,
|
|
9353
|
-
|
|
10334
|
+
path14.dirname(resolvedConfigPath),
|
|
9354
10335
|
projectRoot
|
|
9355
10336
|
);
|
|
9356
10337
|
if (Array.isArray(config.knowledgeBases)) {
|
|
@@ -9363,7 +10344,7 @@ function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfi
|
|
|
9363
10344
|
}
|
|
9364
10345
|
function loadRawConfig(projectRoot, configPath) {
|
|
9365
10346
|
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
9366
|
-
if (fromPath &&
|
|
10347
|
+
if (fromPath && existsSync8(fromPath)) {
|
|
9367
10348
|
return normalizeEvalConfigKnowledgeBases(
|
|
9368
10349
|
parseJsonConfigFile(fromPath),
|
|
9369
10350
|
projectRoot,
|
|
@@ -9371,15 +10352,15 @@ function loadRawConfig(projectRoot, configPath) {
|
|
|
9371
10352
|
);
|
|
9372
10353
|
}
|
|
9373
10354
|
const projectConfig = resolveProjectConfigPath(projectRoot);
|
|
9374
|
-
if (
|
|
10355
|
+
if (existsSync8(projectConfig)) {
|
|
9375
10356
|
return normalizeEvalConfigKnowledgeBases(
|
|
9376
10357
|
parseJsonConfigFile(projectConfig),
|
|
9377
10358
|
projectRoot,
|
|
9378
10359
|
projectConfig
|
|
9379
10360
|
);
|
|
9380
10361
|
}
|
|
9381
|
-
const globalConfig =
|
|
9382
|
-
if (
|
|
10362
|
+
const globalConfig = path14.join(os5.homedir(), ".config", "opencode", "codebase-index.json");
|
|
10363
|
+
if (existsSync8(globalConfig)) {
|
|
9383
10364
|
return parseJsonConfigFile(globalConfig);
|
|
9384
10365
|
}
|
|
9385
10366
|
return {};
|
|
@@ -9388,24 +10369,24 @@ function getIndexRootPath(projectRoot, scope) {
|
|
|
9388
10369
|
return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
|
|
9389
10370
|
}
|
|
9390
10371
|
function getLocalProjectIndexRoot(projectRoot) {
|
|
9391
|
-
return
|
|
10372
|
+
return path14.join(projectRoot, ".opencode", "index");
|
|
9392
10373
|
}
|
|
9393
10374
|
function getLocalProjectConfigPath(projectRoot) {
|
|
9394
|
-
return
|
|
10375
|
+
return path14.join(projectRoot, ".opencode", "codebase-index.json");
|
|
9395
10376
|
}
|
|
9396
10377
|
function clearIndexRoot(projectRoot, scope) {
|
|
9397
10378
|
const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
|
|
9398
|
-
if (
|
|
9399
|
-
|
|
10379
|
+
if (existsSync8(indexRoot)) {
|
|
10380
|
+
rmSync2(indexRoot, { recursive: true, force: true });
|
|
9400
10381
|
}
|
|
9401
10382
|
}
|
|
9402
10383
|
function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
9403
10384
|
const localConfigPath = getLocalProjectConfigPath(projectRoot);
|
|
9404
10385
|
const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
|
|
9405
|
-
if (!configPath &&
|
|
10386
|
+
if (!configPath && existsSync8(localConfigPath)) {
|
|
9406
10387
|
return localConfigPath;
|
|
9407
10388
|
}
|
|
9408
|
-
if (!
|
|
10389
|
+
if (!existsSync8(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
|
|
9409
10390
|
return resolvedConfigPath;
|
|
9410
10391
|
}
|
|
9411
10392
|
const sourceConfig = normalizeEvalConfigKnowledgeBases(
|
|
@@ -9413,8 +10394,8 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
|
|
|
9413
10394
|
projectRoot,
|
|
9414
10395
|
resolvedConfigPath
|
|
9415
10396
|
);
|
|
9416
|
-
|
|
9417
|
-
|
|
10397
|
+
mkdirSync4(path14.dirname(localConfigPath), { recursive: true });
|
|
10398
|
+
writeFileSync4(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
|
|
9418
10399
|
return localConfigPath;
|
|
9419
10400
|
}
|
|
9420
10401
|
function loadParsedConfig(projectRoot, configPath) {
|
|
@@ -9447,9 +10428,9 @@ function getEmbeddingCostPer1MTokens(embeddingProvider) {
|
|
|
9447
10428
|
}
|
|
9448
10429
|
|
|
9449
10430
|
// src/eval/schema.ts
|
|
9450
|
-
import { readFileSync as
|
|
10431
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
9451
10432
|
function parseJsonFile(filePath) {
|
|
9452
|
-
const content =
|
|
10433
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
9453
10434
|
try {
|
|
9454
10435
|
return JSON.parse(content);
|
|
9455
10436
|
} catch (error) {
|
|
@@ -9463,23 +10444,23 @@ function isRecord2(value) {
|
|
|
9463
10444
|
function isStringArray3(value) {
|
|
9464
10445
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
9465
10446
|
}
|
|
9466
|
-
function asPositiveNumber(value,
|
|
10447
|
+
function asPositiveNumber(value, path27) {
|
|
9467
10448
|
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
9468
|
-
throw new Error(`${
|
|
10449
|
+
throw new Error(`${path27} must be a non-negative number`);
|
|
9469
10450
|
}
|
|
9470
10451
|
return value;
|
|
9471
10452
|
}
|
|
9472
|
-
function parseQueryType(value,
|
|
10453
|
+
function parseQueryType(value, path27) {
|
|
9473
10454
|
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
9474
10455
|
return value;
|
|
9475
10456
|
}
|
|
9476
10457
|
throw new Error(
|
|
9477
|
-
`${
|
|
10458
|
+
`${path27} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
9478
10459
|
);
|
|
9479
10460
|
}
|
|
9480
|
-
function parseExpected(input,
|
|
10461
|
+
function parseExpected(input, path27) {
|
|
9481
10462
|
if (!isRecord2(input)) {
|
|
9482
|
-
throw new Error(`${
|
|
10463
|
+
throw new Error(`${path27} must be an object`);
|
|
9483
10464
|
}
|
|
9484
10465
|
const filePathRaw = input.filePath;
|
|
9485
10466
|
const acceptableFilesRaw = input.acceptableFiles;
|
|
@@ -9488,16 +10469,16 @@ function parseExpected(input, path26) {
|
|
|
9488
10469
|
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
9489
10470
|
const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
9490
10471
|
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
9491
|
-
throw new Error(`${
|
|
10472
|
+
throw new Error(`${path27} must include either expected.filePath or expected.acceptableFiles`);
|
|
9492
10473
|
}
|
|
9493
10474
|
if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
|
|
9494
|
-
throw new Error(`${
|
|
10475
|
+
throw new Error(`${path27}.acceptableFiles must be an array of strings`);
|
|
9495
10476
|
}
|
|
9496
10477
|
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
9497
|
-
throw new Error(`${
|
|
10478
|
+
throw new Error(`${path27}.symbol must be a string when provided`);
|
|
9498
10479
|
}
|
|
9499
10480
|
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
9500
|
-
throw new Error(`${
|
|
10481
|
+
throw new Error(`${path27}.branch must be a string when provided`);
|
|
9501
10482
|
}
|
|
9502
10483
|
return {
|
|
9503
10484
|
filePath,
|
|
@@ -9507,25 +10488,25 @@ function parseExpected(input, path26) {
|
|
|
9507
10488
|
};
|
|
9508
10489
|
}
|
|
9509
10490
|
function parseQuery(input, index) {
|
|
9510
|
-
const
|
|
10491
|
+
const path27 = `queries[${index}]`;
|
|
9511
10492
|
if (!isRecord2(input)) {
|
|
9512
|
-
throw new Error(`${
|
|
10493
|
+
throw new Error(`${path27} must be an object`);
|
|
9513
10494
|
}
|
|
9514
10495
|
const id = input.id;
|
|
9515
10496
|
const query = input.query;
|
|
9516
10497
|
const queryType = input.queryType;
|
|
9517
10498
|
const expected = input.expected;
|
|
9518
10499
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
9519
|
-
throw new Error(`${
|
|
10500
|
+
throw new Error(`${path27}.id must be a non-empty string`);
|
|
9520
10501
|
}
|
|
9521
10502
|
if (typeof query !== "string" || query.trim().length === 0) {
|
|
9522
|
-
throw new Error(`${
|
|
10503
|
+
throw new Error(`${path27}.query must be a non-empty string`);
|
|
9523
10504
|
}
|
|
9524
10505
|
return {
|
|
9525
10506
|
id,
|
|
9526
10507
|
query,
|
|
9527
|
-
queryType: parseQueryType(queryType, `${
|
|
9528
|
-
expected: parseExpected(expected, `${
|
|
10508
|
+
queryType: parseQueryType(queryType, `${path27}.queryType`),
|
|
10509
|
+
expected: parseExpected(expected, `${path27}.expected`)
|
|
9529
10510
|
};
|
|
9530
10511
|
}
|
|
9531
10512
|
function parseGoldenDataset(raw, sourceLabel) {
|
|
@@ -9708,13 +10689,13 @@ async function runEvaluation(options) {
|
|
|
9708
10689
|
};
|
|
9709
10690
|
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
9710
10691
|
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
9711
|
-
writeJson(
|
|
9712
|
-
writeJson(
|
|
10692
|
+
writeJson(path15.join(outputDir, "summary.json"), summary);
|
|
10693
|
+
writeJson(path15.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
9713
10694
|
let comparison;
|
|
9714
10695
|
if (againstPath) {
|
|
9715
10696
|
const baseline = loadSummary(againstPath);
|
|
9716
10697
|
comparison = compareSummaries(summary, baseline, againstPath);
|
|
9717
|
-
writeJson(
|
|
10698
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9718
10699
|
}
|
|
9719
10700
|
let gate;
|
|
9720
10701
|
if (options.ciMode) {
|
|
@@ -9724,10 +10705,10 @@ async function runEvaluation(options) {
|
|
|
9724
10705
|
const budget = loadBudget(budgetPath);
|
|
9725
10706
|
if (!comparison && budget.baselinePath) {
|
|
9726
10707
|
const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
|
|
9727
|
-
if (
|
|
10708
|
+
if (existsSync9(resolvedBaseline)) {
|
|
9728
10709
|
const baselineSummary = loadSummary(resolvedBaseline);
|
|
9729
10710
|
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
9730
|
-
writeJson(
|
|
10711
|
+
writeJson(path15.join(outputDir, "compare.json"), comparison);
|
|
9731
10712
|
} else if (budget.failOnMissingBaseline) {
|
|
9732
10713
|
throw new Error(
|
|
9733
10714
|
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
@@ -9737,7 +10718,7 @@ async function runEvaluation(options) {
|
|
|
9737
10718
|
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
9738
10719
|
}
|
|
9739
10720
|
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
9740
|
-
writeText(
|
|
10721
|
+
writeText(path15.join(outputDir, "summary.md"), markdown);
|
|
9741
10722
|
return { outputDir, summary, perQuery, comparison, gate };
|
|
9742
10723
|
} finally {
|
|
9743
10724
|
await indexer.close();
|
|
@@ -9795,23 +10776,23 @@ async function runSweep(options, sweep) {
|
|
|
9795
10776
|
bestByMrrAt10,
|
|
9796
10777
|
bestByP95Latency
|
|
9797
10778
|
};
|
|
9798
|
-
writeJson(
|
|
10779
|
+
writeJson(path15.join(outputDir, "compare.json"), aggregate);
|
|
9799
10780
|
const md = createSummaryMarkdown(
|
|
9800
10781
|
bestByHitAt5?.summary ?? runs[0].summary,
|
|
9801
10782
|
bestByHitAt5?.comparison,
|
|
9802
10783
|
void 0,
|
|
9803
10784
|
aggregate
|
|
9804
10785
|
);
|
|
9805
|
-
writeText(
|
|
9806
|
-
writeJson(
|
|
10786
|
+
writeText(path15.join(outputDir, "summary.md"), md);
|
|
10787
|
+
writeJson(path15.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
9807
10788
|
return { outputDir, aggregate };
|
|
9808
10789
|
}
|
|
9809
10790
|
|
|
9810
10791
|
// src/eval/cli.ts
|
|
9811
|
-
import * as
|
|
10792
|
+
import * as path17 from "path";
|
|
9812
10793
|
|
|
9813
10794
|
// src/eval/cli-parser.ts
|
|
9814
|
-
import * as
|
|
10795
|
+
import * as path16 from "path";
|
|
9815
10796
|
function printUsage() {
|
|
9816
10797
|
console.log(`
|
|
9817
10798
|
Usage:
|
|
@@ -9883,12 +10864,12 @@ function parseEvalArgs(argv, cwd) {
|
|
|
9883
10864
|
const arg = argv[i];
|
|
9884
10865
|
const next = argv[i + 1];
|
|
9885
10866
|
if (arg === "--project" && next) {
|
|
9886
|
-
parsed.projectRoot =
|
|
10867
|
+
parsed.projectRoot = path16.resolve(cwd, next);
|
|
9887
10868
|
i += 1;
|
|
9888
10869
|
continue;
|
|
9889
10870
|
}
|
|
9890
10871
|
if (arg === "--config" && next) {
|
|
9891
|
-
parsed.configPath =
|
|
10872
|
+
parsed.configPath = path16.resolve(cwd, next);
|
|
9892
10873
|
i += 1;
|
|
9893
10874
|
continue;
|
|
9894
10875
|
}
|
|
@@ -10084,22 +11065,22 @@ async function handleEvalCommand(args, cwd) {
|
|
|
10084
11065
|
if (!parsed.againstPath.endsWith(".json")) {
|
|
10085
11066
|
throw new Error("eval diff --against must point to a summary JSON file");
|
|
10086
11067
|
}
|
|
10087
|
-
const currentSummary = loadSummary(
|
|
11068
|
+
const currentSummary = loadSummary(path17.resolve(parsed.projectRoot, currentPath), {
|
|
10088
11069
|
allowLegacyDiversityMetrics: true
|
|
10089
11070
|
});
|
|
10090
|
-
const baselineSummary = loadSummary(
|
|
11071
|
+
const baselineSummary = loadSummary(path17.resolve(parsed.projectRoot, parsed.againstPath), {
|
|
10091
11072
|
allowLegacyDiversityMetrics: true
|
|
10092
11073
|
});
|
|
10093
11074
|
const comparison = compareSummaries(
|
|
10094
11075
|
currentSummary,
|
|
10095
11076
|
baselineSummary,
|
|
10096
|
-
|
|
11077
|
+
path17.resolve(parsed.projectRoot, parsed.againstPath)
|
|
10097
11078
|
);
|
|
10098
|
-
const outputDir = createRunDirectory(
|
|
11079
|
+
const outputDir = createRunDirectory(path17.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
10099
11080
|
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
10100
|
-
writeJson(
|
|
10101
|
-
writeText(
|
|
10102
|
-
writeJson(
|
|
11081
|
+
writeJson(path17.join(outputDir, "compare.json"), comparison);
|
|
11082
|
+
writeText(path17.join(outputDir, "summary.md"), summaryMd);
|
|
11083
|
+
writeJson(path17.join(outputDir, "summary.json"), currentSummary);
|
|
10103
11084
|
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
10104
11085
|
return 0;
|
|
10105
11086
|
}
|
|
@@ -10108,7 +11089,7 @@ async function handleEvalCommand(args, cwd) {
|
|
|
10108
11089
|
|
|
10109
11090
|
// src/mcp-server.ts
|
|
10110
11091
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10111
|
-
import { readFileSync as
|
|
11092
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
10112
11093
|
|
|
10113
11094
|
// src/mcp-server/register-prompts.ts
|
|
10114
11095
|
import { z } from "zod";
|
|
@@ -10299,6 +11280,10 @@ function formatStatus(status) {
|
|
|
10299
11280
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
10300
11281
|
}
|
|
10301
11282
|
}
|
|
11283
|
+
if (status.warning) {
|
|
11284
|
+
lines.push("");
|
|
11285
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
11286
|
+
}
|
|
10302
11287
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
10303
11288
|
lines.push("");
|
|
10304
11289
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -10404,16 +11389,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
|
10404
11389
|
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
10405
11390
|
}).join("\n");
|
|
10406
11391
|
}
|
|
10407
|
-
function formatCallGraphPath(from, to,
|
|
10408
|
-
if (
|
|
11392
|
+
function formatCallGraphPath(from, to, path27) {
|
|
11393
|
+
if (path27.length === 0) {
|
|
10409
11394
|
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
10410
11395
|
}
|
|
10411
|
-
const formatted =
|
|
11396
|
+
const formatted = path27.map((hop, index) => {
|
|
10412
11397
|
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10413
11398
|
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10414
11399
|
return `${prefix} ${hop.symbolName}${location}`;
|
|
10415
11400
|
});
|
|
10416
|
-
return `Path (${
|
|
11401
|
+
return `Path (${path27.length} hops):
|
|
10417
11402
|
${formatted.join("\n")}`;
|
|
10418
11403
|
}
|
|
10419
11404
|
function formatResultHeader(result, index) {
|
|
@@ -10504,12 +11489,12 @@ function formatPrImpact(result) {
|
|
|
10504
11489
|
}
|
|
10505
11490
|
|
|
10506
11491
|
// src/tools/operations.ts
|
|
10507
|
-
import { existsSync as
|
|
10508
|
-
import * as
|
|
11492
|
+
import { existsSync as existsSync12, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
11493
|
+
import * as path21 from "path";
|
|
10509
11494
|
|
|
10510
11495
|
// src/config/merger.ts
|
|
10511
|
-
import { existsSync as
|
|
10512
|
-
import * as
|
|
11496
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
11497
|
+
import * as path18 from "path";
|
|
10513
11498
|
var PROJECT_OVERRIDE_KEYS = [
|
|
10514
11499
|
"embeddingProvider",
|
|
10515
11500
|
"customProvider",
|
|
@@ -10542,8 +11527,8 @@ function mergeUniqueStringArray(values) {
|
|
|
10542
11527
|
return [...new Set(values.map((value) => String(value).trim()))];
|
|
10543
11528
|
}
|
|
10544
11529
|
function normalizeKnowledgeBasePath(value) {
|
|
10545
|
-
let normalized =
|
|
10546
|
-
const root =
|
|
11530
|
+
let normalized = path18.normalize(String(value).trim());
|
|
11531
|
+
const root = path18.parse(normalized).root;
|
|
10547
11532
|
while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
|
|
10548
11533
|
normalized = normalized.slice(0, -1);
|
|
10549
11534
|
}
|
|
@@ -10577,11 +11562,11 @@ function validateConfigLayerShape(rawConfig, filePath) {
|
|
|
10577
11562
|
return rawConfig;
|
|
10578
11563
|
}
|
|
10579
11564
|
function loadJsonFile(filePath) {
|
|
10580
|
-
if (!
|
|
11565
|
+
if (!existsSync10(filePath)) {
|
|
10581
11566
|
return null;
|
|
10582
11567
|
}
|
|
10583
11568
|
try {
|
|
10584
|
-
const content =
|
|
11569
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
10585
11570
|
return validateConfigLayerShape(JSON.parse(content), filePath);
|
|
10586
11571
|
} catch (error) {
|
|
10587
11572
|
if (error instanceof Error && error.message.startsWith("Config file ")) {
|
|
@@ -10596,8 +11581,8 @@ function loadConfigFile(filePath) {
|
|
|
10596
11581
|
}
|
|
10597
11582
|
function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
|
|
10598
11583
|
const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
|
|
10599
|
-
|
|
10600
|
-
|
|
11584
|
+
mkdirSync5(path18.dirname(localConfigPath), { recursive: true });
|
|
11585
|
+
writeFileSync5(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
10601
11586
|
return localConfigPath;
|
|
10602
11587
|
}
|
|
10603
11588
|
function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
@@ -10607,7 +11592,7 @@ function loadProjectConfigLayer(projectRoot, host = "opencode") {
|
|
|
10607
11592
|
return {};
|
|
10608
11593
|
}
|
|
10609
11594
|
const normalizedConfig = { ...projectConfig };
|
|
10610
|
-
const projectConfigBaseDir =
|
|
11595
|
+
const projectConfigBaseDir = path18.dirname(path18.dirname(projectConfigPath));
|
|
10611
11596
|
if (Array.isArray(normalizedConfig.knowledgeBases)) {
|
|
10612
11597
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
10613
11598
|
normalizedConfig.knowledgeBases,
|
|
@@ -10671,19 +11656,19 @@ function loadMergedConfig(projectRoot, host = "opencode") {
|
|
|
10671
11656
|
}
|
|
10672
11657
|
|
|
10673
11658
|
// src/tools/knowledge-base-paths.ts
|
|
10674
|
-
import * as
|
|
11659
|
+
import * as path19 from "path";
|
|
10675
11660
|
function resolveConfigPathValue(value, baseDir) {
|
|
10676
11661
|
const trimmed = value.trim();
|
|
10677
11662
|
if (!trimmed) {
|
|
10678
11663
|
return trimmed;
|
|
10679
11664
|
}
|
|
10680
|
-
const absolutePath =
|
|
10681
|
-
return
|
|
11665
|
+
const absolutePath = path19.isAbsolute(trimmed) ? trimmed : path19.resolve(baseDir, trimmed);
|
|
11666
|
+
return path19.normalize(absolutePath);
|
|
10682
11667
|
}
|
|
10683
11668
|
|
|
10684
11669
|
// src/tools/config-state.ts
|
|
10685
|
-
import { existsSync as
|
|
10686
|
-
import * as
|
|
11670
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
11671
|
+
import * as path20 from "path";
|
|
10687
11672
|
function normalizeKnowledgeBasePaths(config, projectRoot) {
|
|
10688
11673
|
const normalized = { ...config };
|
|
10689
11674
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
@@ -10707,6 +11692,24 @@ function loadRuntimeConfig(projectRoot, host = "opencode") {
|
|
|
10707
11692
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
10708
11693
|
var configCache = /* @__PURE__ */ new Map();
|
|
10709
11694
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
11695
|
+
function getIndexBusyResult(error) {
|
|
11696
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
11697
|
+
const owner = error.owner;
|
|
11698
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
11699
|
+
if (error.reason === "legacy-lock") {
|
|
11700
|
+
return {
|
|
11701
|
+
kind: "busy",
|
|
11702
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
11703
|
+
};
|
|
11704
|
+
}
|
|
11705
|
+
if (error.reason === "unknown-owner") {
|
|
11706
|
+
return {
|
|
11707
|
+
kind: "busy",
|
|
11708
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
11709
|
+
};
|
|
11710
|
+
}
|
|
11711
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
11712
|
+
}
|
|
10710
11713
|
function getProjectRoot(projectRoot, host) {
|
|
10711
11714
|
if (projectRoot) {
|
|
10712
11715
|
return projectRoot;
|
|
@@ -10751,8 +11754,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
|
|
|
10751
11754
|
}
|
|
10752
11755
|
function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
|
|
10753
11756
|
const root = getProjectRoot(projectRoot, host);
|
|
10754
|
-
const localIndexPath =
|
|
10755
|
-
if (
|
|
11757
|
+
const localIndexPath = path21.join(root, getHostProjectIndexRelativePath(host));
|
|
11758
|
+
if (existsSync12(localIndexPath)) {
|
|
10756
11759
|
return false;
|
|
10757
11760
|
}
|
|
10758
11761
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
@@ -10808,35 +11811,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
|
|
|
10808
11811
|
async function runIndexCodebase(projectRoot, host, args, onProgress) {
|
|
10809
11812
|
const root = getProjectRoot(projectRoot, host);
|
|
10810
11813
|
const key = getIndexerCacheKey(root, host);
|
|
10811
|
-
const cachedConfig = configCache.get(key);
|
|
10812
11814
|
let indexer = getIndexerForProject(root, host);
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
chunksProcessed: progress.chunksProcessed,
|
|
10833
|
-
totalChunks: progress.totalChunks,
|
|
10834
|
-
percentage: calculatePercentage(progress)
|
|
11815
|
+
const runtimeConfig = configCache.get(key);
|
|
11816
|
+
try {
|
|
11817
|
+
if (args.estimateOnly) {
|
|
11818
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
11819
|
+
}
|
|
11820
|
+
const runIndex = async (target) => {
|
|
11821
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
11822
|
+
return operation((progress) => {
|
|
11823
|
+
if (onProgress) {
|
|
11824
|
+
return onProgress(formatProgressTitle(progress), {
|
|
11825
|
+
phase: progress.phase,
|
|
11826
|
+
filesProcessed: progress.filesProcessed,
|
|
11827
|
+
totalFiles: progress.totalFiles,
|
|
11828
|
+
chunksProcessed: progress.chunksProcessed,
|
|
11829
|
+
totalChunks: progress.totalChunks,
|
|
11830
|
+
percentage: calculatePercentage(progress)
|
|
11831
|
+
});
|
|
11832
|
+
}
|
|
11833
|
+
return Promise.resolve();
|
|
10835
11834
|
});
|
|
11835
|
+
};
|
|
11836
|
+
let stats;
|
|
11837
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
11838
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
11839
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
11840
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
11841
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
11842
|
+
indexer = getIndexerForProject(root, host);
|
|
11843
|
+
return runIndex(indexer);
|
|
11844
|
+
}, { completeRecoveries: false });
|
|
11845
|
+
} else {
|
|
11846
|
+
stats = await runIndex(indexer);
|
|
10836
11847
|
}
|
|
10837
|
-
return
|
|
10838
|
-
})
|
|
10839
|
-
|
|
11848
|
+
return { kind: "stats", stats };
|
|
11849
|
+
} catch (error) {
|
|
11850
|
+
const busyResult = getIndexBusyResult(error);
|
|
11851
|
+
if (!busyResult) throw error;
|
|
11852
|
+
return busyResult;
|
|
11853
|
+
}
|
|
10840
11854
|
}
|
|
10841
11855
|
async function getIndexStatus(projectRoot, host) {
|
|
10842
11856
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
@@ -10846,6 +11860,15 @@ async function getIndexHealthCheck(projectRoot, host) {
|
|
|
10846
11860
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10847
11861
|
return indexer.healthCheck();
|
|
10848
11862
|
}
|
|
11863
|
+
async function runIndexHealthCheck(projectRoot, host) {
|
|
11864
|
+
try {
|
|
11865
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
|
|
11866
|
+
} catch (error) {
|
|
11867
|
+
const busyResult = getIndexBusyResult(error);
|
|
11868
|
+
if (!busyResult) throw error;
|
|
11869
|
+
return busyResult;
|
|
11870
|
+
}
|
|
11871
|
+
}
|
|
10849
11872
|
async function getPrImpact(projectRoot, host, params) {
|
|
10850
11873
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
10851
11874
|
return indexer.getPrImpact({
|
|
@@ -10926,20 +11949,87 @@ var CHUNK_TYPE_ENUM = [
|
|
|
10926
11949
|
];
|
|
10927
11950
|
|
|
10928
11951
|
// src/mcp-server/register-tools.ts
|
|
11952
|
+
function allowNullAsUndefined(schema) {
|
|
11953
|
+
return z2.preprocess((value) => value === null ? void 0 : value, schema);
|
|
11954
|
+
}
|
|
10929
11955
|
function registerMcpTools(server, runtime) {
|
|
11956
|
+
server.tool(
|
|
11957
|
+
"codebase_context",
|
|
11958
|
+
"PREFERRED FIRST TOOL for any question about this repository. Use before built-in code search, grep, shell search, or broad file reads. Provide from+to for a dependency path, symbol for a definition, or only query for low-token conceptual discovery. Use call_graph directly for callers or callees.",
|
|
11959
|
+
{
|
|
11960
|
+
query: z2.string().describe("The codebase question or behavior to locate. Always provide the user's repository question here."),
|
|
11961
|
+
from: allowNullAsUndefined(z2.string().optional()).describe("Source symbol. For dependency-path questions, extract the first endpoint and provide it here."),
|
|
11962
|
+
to: allowNullAsUndefined(z2.string().optional()).describe("Target symbol. For dependency-path questions, extract the second endpoint and provide it here."),
|
|
11963
|
+
symbol: allowNullAsUndefined(z2.string().optional()).describe("Exact symbol for an authoritative definition lookup. Omit when from and to are supplied."),
|
|
11964
|
+
limit: allowNullAsUndefined(z2.number().optional().default(10)).describe("Maximum number of search or definition results"),
|
|
11965
|
+
maxDepth: allowNullAsUndefined(z2.number().optional().default(10)).describe("Maximum call-graph traversal depth for from/to path lookup"),
|
|
11966
|
+
fileType: allowNullAsUndefined(z2.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
11967
|
+
directory: allowNullAsUndefined(z2.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')")
|
|
11968
|
+
},
|
|
11969
|
+
async (args) => {
|
|
11970
|
+
if (args.from && args.to) {
|
|
11971
|
+
const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
|
|
11972
|
+
if (path27.length > 0) {
|
|
11973
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
11974
|
+
}
|
|
11975
|
+
const { callers } = await getCallGraphData(runtime.projectRoot, runtime.host, {
|
|
11976
|
+
name: args.to,
|
|
11977
|
+
direction: "callers"
|
|
11978
|
+
});
|
|
11979
|
+
const directEdge = callers.find((edge) => edge.fromSymbolName === args.from);
|
|
11980
|
+
if (directEdge) {
|
|
11981
|
+
const location = directEdge.fromSymbolFilePath ? ` at ${directEdge.fromSymbolFilePath}:${directEdge.line}` : "";
|
|
11982
|
+
return {
|
|
11983
|
+
content: [{
|
|
11984
|
+
type: "text",
|
|
11985
|
+
text: `Direct path: ${args.from} --${directEdge.callType}--> ${args.to}${location} (edge is ${directEdge.isResolved ? "resolved" : "unresolved"}).`
|
|
11986
|
+
}]
|
|
11987
|
+
};
|
|
11988
|
+
}
|
|
11989
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
11990
|
+
}
|
|
11991
|
+
if (args.symbol) {
|
|
11992
|
+
const results2 = await implementationLookup(runtime.projectRoot, runtime.host, args.symbol, {
|
|
11993
|
+
limit: args.limit ?? 10,
|
|
11994
|
+
fileType: args.fileType,
|
|
11995
|
+
directory: args.directory
|
|
11996
|
+
});
|
|
11997
|
+
return { content: [{ type: "text", text: formatDefinitionLookup(results2, args.symbol) }] };
|
|
11998
|
+
}
|
|
11999
|
+
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
12000
|
+
limit: args.limit ?? 10,
|
|
12001
|
+
fileType: args.fileType,
|
|
12002
|
+
directory: args.directory,
|
|
12003
|
+
metadataOnly: true
|
|
12004
|
+
});
|
|
12005
|
+
if (results.length === 0) {
|
|
12006
|
+
return { content: [{ type: "text", text: "No matching code found. Try a different query or run index_codebase first." }] };
|
|
12007
|
+
}
|
|
12008
|
+
return {
|
|
12009
|
+
content: [{
|
|
12010
|
+
type: "text",
|
|
12011
|
+
text: `Found ${results.length} locations for "${args.query}":
|
|
12012
|
+
|
|
12013
|
+
${formatCodebasePeek(results)}
|
|
12014
|
+
|
|
12015
|
+
Use implementation_lookup for an authoritative definition, or call call_graph/call_graph_path once you have a symbol.`
|
|
12016
|
+
}]
|
|
12017
|
+
};
|
|
12018
|
+
}
|
|
12019
|
+
);
|
|
10930
12020
|
server.tool(
|
|
10931
12021
|
"codebase_search",
|
|
10932
|
-
"
|
|
12022
|
+
"FULL-CONTENT semantic retrieval. Use after codebase_peek when you need implementation text, not as the default first step. For exact identifiers or exhaustive matches use grep instead.",
|
|
10933
12023
|
{
|
|
10934
12024
|
query: z2.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
|
|
10935
|
-
limit: z2.number().optional().default(5).describe("Maximum number of results to return"),
|
|
10936
|
-
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10937
|
-
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10938
|
-
chunkType: z2.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
10939
|
-
contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
|
|
10940
|
-
blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
|
|
10941
|
-
blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
10942
|
-
blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
12025
|
+
limit: allowNullAsUndefined(z2.number().optional().default(5)).describe("Maximum number of results to return"),
|
|
12026
|
+
fileType: allowNullAsUndefined(z2.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12027
|
+
directory: allowNullAsUndefined(z2.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12028
|
+
chunkType: allowNullAsUndefined(z2.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
|
|
12029
|
+
contextLines: allowNullAsUndefined(z2.number().optional()).describe("Number of extra lines to include before/after each match (default: 0)"),
|
|
12030
|
+
blameAuthor: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame author name or email"),
|
|
12031
|
+
blameSha: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame commit SHA or prefix"),
|
|
12032
|
+
blameSince: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or after this date")
|
|
10943
12033
|
},
|
|
10944
12034
|
async (args) => {
|
|
10945
12035
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -10962,16 +12052,16 @@ ${formatSearchResults(results, "score")}` }] };
|
|
|
10962
12052
|
);
|
|
10963
12053
|
server.tool(
|
|
10964
12054
|
"codebase_peek",
|
|
10965
|
-
"
|
|
12055
|
+
"DIRECT LOW-TOKEN semantic location lookup for unfamiliar-code discovery. Prefer codebase_context when the request may involve definitions or graph navigation; use this specialized tool when you only need conceptual locations.",
|
|
10966
12056
|
{
|
|
10967
12057
|
query: z2.string().describe("Natural language description of what code you're looking for."),
|
|
10968
|
-
limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
|
|
10969
|
-
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
10970
|
-
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
10971
|
-
chunkType: z2.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
10972
|
-
blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
|
|
10973
|
-
blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
10974
|
-
blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date")
|
|
12058
|
+
limit: allowNullAsUndefined(z2.number().optional().default(10)).describe("Maximum number of results to return"),
|
|
12059
|
+
fileType: allowNullAsUndefined(z2.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12060
|
+
directory: allowNullAsUndefined(z2.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12061
|
+
chunkType: allowNullAsUndefined(z2.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
|
|
12062
|
+
blameAuthor: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame author name or email"),
|
|
12063
|
+
blameSha: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame commit SHA or prefix"),
|
|
12064
|
+
blameSince: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or after this date")
|
|
10975
12065
|
},
|
|
10976
12066
|
async (args) => {
|
|
10977
12067
|
const results = await searchCodebase(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -10996,21 +12086,26 @@ Use Read tool to examine specific files.` }] };
|
|
|
10996
12086
|
);
|
|
10997
12087
|
server.tool(
|
|
10998
12088
|
"index_codebase",
|
|
10999
|
-
"
|
|
12089
|
+
"Create or refresh the semantic index. Call index_status first when readiness is unknown, then use this tool only if the index is missing, stale, or incompatible. Incremental by default; force=true rebuilds everything.",
|
|
11000
12090
|
{
|
|
11001
|
-
force: z2.boolean().optional().default(false).describe("Force reindex even if already indexed"),
|
|
11002
|
-
estimateOnly: z2.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
|
|
11003
|
-
verbose: z2.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
|
|
12091
|
+
force: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Force reindex even if already indexed"),
|
|
12092
|
+
estimateOnly: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Only show cost estimate without indexing"),
|
|
12093
|
+
verbose: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Show detailed info about skipped files and parsing failures")
|
|
11004
12094
|
},
|
|
11005
12095
|
async (args) => {
|
|
11006
12096
|
const result = await runIndexCodebase(runtime.projectRoot, runtime.host, args);
|
|
11007
|
-
|
|
11008
|
-
|
|
12097
|
+
if (result.kind === "estimate") {
|
|
12098
|
+
return { content: [{ type: "text", text: formatCostEstimate(result.estimate) }] };
|
|
12099
|
+
}
|
|
12100
|
+
if (result.kind === "busy") {
|
|
12101
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12102
|
+
}
|
|
12103
|
+
return { content: [{ type: "text", text: formatIndexStats(result.stats, args.verbose ?? false) }] };
|
|
11009
12104
|
}
|
|
11010
12105
|
);
|
|
11011
12106
|
server.tool(
|
|
11012
12107
|
"index_status",
|
|
11013
|
-
"
|
|
12108
|
+
"START HERE once per repository task when index readiness or freshness is unknown. Reports whether semantic retrieval is ready, chunk counts, compatibility, and the embedding provider. If ready, continue with codebase_peek or implementation_lookup; otherwise run index_codebase.",
|
|
11014
12109
|
{},
|
|
11015
12110
|
async () => {
|
|
11016
12111
|
const status = await getIndexStatus(runtime.projectRoot, runtime.host);
|
|
@@ -11022,8 +12117,11 @@ Use Read tool to examine specific files.` }] };
|
|
|
11022
12117
|
"Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
|
|
11023
12118
|
{},
|
|
11024
12119
|
async () => {
|
|
11025
|
-
const result = await
|
|
11026
|
-
|
|
12120
|
+
const result = await runIndexHealthCheck(runtime.projectRoot, runtime.host);
|
|
12121
|
+
if (result.kind === "busy") {
|
|
12122
|
+
return { content: [{ type: "text", text: result.text }], isError: true };
|
|
12123
|
+
}
|
|
12124
|
+
return { content: [{ type: "text", text: formatHealthCheck(result.health) }] };
|
|
11027
12125
|
}
|
|
11028
12126
|
);
|
|
11029
12127
|
server.tool(
|
|
@@ -11039,9 +12137,13 @@ Use Read tool to examine specific files.` }] };
|
|
|
11039
12137
|
"index_logs",
|
|
11040
12138
|
"Get recent debug logs from the codebase indexer. Requires debug.enabled=true in config.",
|
|
11041
12139
|
{
|
|
11042
|
-
limit: z2.number().optional().default(20).describe("Maximum number of log entries to return"),
|
|
11043
|
-
category:
|
|
11044
|
-
|
|
12140
|
+
limit: allowNullAsUndefined(z2.number().optional().default(20)).describe("Maximum number of log entries to return"),
|
|
12141
|
+
category: allowNullAsUndefined(
|
|
12142
|
+
z2.enum(["search", "embedding", "cache", "gc", "branch", "general"]).optional()
|
|
12143
|
+
).describe("Filter by log category"),
|
|
12144
|
+
level: allowNullAsUndefined(
|
|
12145
|
+
z2.enum(["error", "warn", "info", "debug"]).optional()
|
|
12146
|
+
).describe("Filter by minimum log level")
|
|
11045
12147
|
},
|
|
11046
12148
|
async (args) => {
|
|
11047
12149
|
const result = await getIndexLogs(runtime.projectRoot, runtime.host, args);
|
|
@@ -11050,14 +12152,14 @@ Use Read tool to examine specific files.` }] };
|
|
|
11050
12152
|
);
|
|
11051
12153
|
server.tool(
|
|
11052
12154
|
"find_similar",
|
|
11053
|
-
"
|
|
12155
|
+
"Use when you already have a code snippet and need analogous implementations, duplicates, patterns, or refactoring candidates. For a natural-language concept without example code, start with codebase_peek instead.",
|
|
11054
12156
|
{
|
|
11055
12157
|
code: z2.string().describe("The code snippet to find similar code for"),
|
|
11056
|
-
limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
|
|
11057
|
-
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
11058
|
-
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
11059
|
-
chunkType: z2.enum(CHUNK_TYPE_ENUM).optional().describe("Filter by code chunk type"),
|
|
11060
|
-
excludeFile: z2.string().optional().describe("Exclude results from this file path")
|
|
12158
|
+
limit: allowNullAsUndefined(z2.number().optional().default(10)).describe("Maximum number of results to return"),
|
|
12159
|
+
fileType: allowNullAsUndefined(z2.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12160
|
+
directory: allowNullAsUndefined(z2.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12161
|
+
chunkType: allowNullAsUndefined(z2.enum(CHUNK_TYPE_ENUM).optional()).describe("Filter by code chunk type"),
|
|
12162
|
+
excludeFile: allowNullAsUndefined(z2.string().optional()).describe("Exclude results from this file path")
|
|
11061
12163
|
},
|
|
11062
12164
|
async (args) => {
|
|
11063
12165
|
const results = await findSimilarCode(runtime.projectRoot, runtime.host, args.code, {
|
|
@@ -11077,12 +12179,12 @@ ${formatSearchResults(results)}` }] };
|
|
|
11077
12179
|
);
|
|
11078
12180
|
server.tool(
|
|
11079
12181
|
"implementation_lookup",
|
|
11080
|
-
"
|
|
12182
|
+
"FIRST TOOL only for known-symbol definition questions. Returns authoritative source locations and prefers implementations over tests, docs, examples, and fixtures. Do not use for callers, callees, dependency paths, or code flow; use codebase_context with direction or from/to for those questions.",
|
|
11081
12183
|
{
|
|
11082
12184
|
query: z2.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
|
|
11083
|
-
limit: z2.number().optional().default(5).describe("Maximum number of results"),
|
|
11084
|
-
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
11085
|
-
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
|
|
12185
|
+
limit: allowNullAsUndefined(z2.number().optional().default(5)).describe("Maximum number of results"),
|
|
12186
|
+
fileType: allowNullAsUndefined(z2.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
12187
|
+
directory: allowNullAsUndefined(z2.string().optional()).describe("Filter by directory path (e.g., 'src/utils')")
|
|
11086
12188
|
},
|
|
11087
12189
|
async (args) => {
|
|
11088
12190
|
const results = await implementationLookup(runtime.projectRoot, runtime.host, args.query, {
|
|
@@ -11095,12 +12197,16 @@ ${formatSearchResults(results)}` }] };
|
|
|
11095
12197
|
);
|
|
11096
12198
|
server.tool(
|
|
11097
12199
|
"call_graph",
|
|
11098
|
-
"
|
|
12200
|
+
"Use after identifying a symbol to find its direct callers or callees and understand code flow. Use implementation_lookup first if the symbol or definition is still ambiguous. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
|
|
11099
12201
|
{
|
|
11100
12202
|
name: z2.string().describe("Function or method name to query"),
|
|
11101
|
-
direction:
|
|
11102
|
-
|
|
11103
|
-
|
|
12203
|
+
direction: allowNullAsUndefined(
|
|
12204
|
+
z2.enum(["callers", "callees"]).default("callers")
|
|
12205
|
+
).describe("Direction: 'callers' finds who calls this function, 'callees' finds what this function calls"),
|
|
12206
|
+
symbolId: allowNullAsUndefined(z2.string().optional()).describe("Symbol ID (required for 'callees' direction)"),
|
|
12207
|
+
relationshipType: allowNullAsUndefined(
|
|
12208
|
+
z2.enum(["Call", "MethodCall", "Constructor", "Import", "Inherits", "Implements"]).optional()
|
|
12209
|
+
).describe("Filter by relationship type. Omit to show all.")
|
|
11104
12210
|
},
|
|
11105
12211
|
async (args) => {
|
|
11106
12212
|
if (args.direction === "callees") {
|
|
@@ -11116,27 +12222,29 @@ ${formatSearchResults(results)}` }] };
|
|
|
11116
12222
|
);
|
|
11117
12223
|
server.tool(
|
|
11118
12224
|
"call_graph_path",
|
|
11119
|
-
"
|
|
12225
|
+
"Use after identifying both endpoint symbols to find the shortest known call path between them. Use codebase_peek or implementation_lookup first when either endpoint is unknown.",
|
|
11120
12226
|
{
|
|
11121
12227
|
from: z2.string().describe("Source function/method name (starting point)"),
|
|
11122
12228
|
to: z2.string().describe("Target function/method name (destination)"),
|
|
11123
|
-
maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
|
|
12229
|
+
maxDepth: allowNullAsUndefined(z2.number().optional().default(10)).describe("Maximum traversal depth (default: 10)")
|
|
11124
12230
|
},
|
|
11125
12231
|
async (args) => {
|
|
11126
|
-
const
|
|
11127
|
-
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to,
|
|
12232
|
+
const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
|
|
12233
|
+
return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
|
|
11128
12234
|
}
|
|
11129
12235
|
);
|
|
11130
12236
|
server.tool(
|
|
11131
12237
|
"pr_impact",
|
|
11132
|
-
"
|
|
12238
|
+
"FIRST TOOL for pull-request or branch blast-radius questions. Analyzes changed files, affected symbols, transitive dependencies, communities, hub nodes, conflicts, and risk before merging.",
|
|
11133
12239
|
{
|
|
11134
|
-
pr: z2.number().optional().describe("Pull request number to analyze"),
|
|
11135
|
-
branch: z2.string().optional().describe("Branch name to analyze (defaults to current branch)"),
|
|
11136
|
-
maxDepth: z2.number().optional().default(5).describe("Maximum traversal depth for transitive callers (default: 5)"),
|
|
11137
|
-
hubThreshold: z2.number().optional().default(10).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
|
|
11138
|
-
checkConflicts: z2.boolean().optional().default(false).describe("Check for conflicting open PRs touching the same communities (default: false)"),
|
|
11139
|
-
direction:
|
|
12240
|
+
pr: allowNullAsUndefined(z2.number().optional()).describe("Pull request number to analyze"),
|
|
12241
|
+
branch: allowNullAsUndefined(z2.string().optional()).describe("Branch name to analyze (defaults to current branch)"),
|
|
12242
|
+
maxDepth: allowNullAsUndefined(z2.number().optional().default(5)).describe("Maximum traversal depth for transitive callers (default: 5)"),
|
|
12243
|
+
hubThreshold: allowNullAsUndefined(z2.number().optional().default(10)).describe("Minimum caller count to flag a symbol as a hub node (default: 10)"),
|
|
12244
|
+
checkConflicts: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Check for conflicting open PRs touching the same communities (default: false)"),
|
|
12245
|
+
direction: allowNullAsUndefined(
|
|
12246
|
+
z2.enum(["callers", "callees", "both"]).optional().default("both")
|
|
12247
|
+
).describe("Call-graph traversal direction: 'callers' for upstream, 'callees' for downstream, 'both' for union (default: both)")
|
|
11140
12248
|
},
|
|
11141
12249
|
async (args) => {
|
|
11142
12250
|
try {
|
|
@@ -11158,8 +12266,12 @@ ${formatSearchResults(results)}` }] };
|
|
|
11158
12266
|
}
|
|
11159
12267
|
|
|
11160
12268
|
// src/mcp-server.ts
|
|
12269
|
+
function getServerInstructions(host) {
|
|
12270
|
+
const hostText = `host ${host}`;
|
|
12271
|
+
return `This MCP server is the preferred codebase-understanding path for ${hostText}. Start a repository task with index_status when index readiness or freshness is unknown. Use codebase_context as the preferred first entry point because it returns low-token locations first and routes to definitions or call-graph helpers when symbol intent is present. Use codebase_peek for direct conceptual location lookup, implementation_lookup for known-symbol definition questions, and codebase_search only when full semantic code content is needed. For exact identifiers or exhaustive matches, use grep. After identifying symbols, use call_graph or call_graph_path to trace dependencies. If the index is unavailable, run index_codebase, then retry the retrieval tool.`;
|
|
12272
|
+
}
|
|
11161
12273
|
function getPackageVersion() {
|
|
11162
|
-
const raw = JSON.parse(
|
|
12274
|
+
const raw = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8"));
|
|
11163
12275
|
if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
|
|
11164
12276
|
return raw.version;
|
|
11165
12277
|
}
|
|
@@ -11169,6 +12281,8 @@ function createMcpServer(projectRoot, config, host = "opencode") {
|
|
|
11169
12281
|
const server = new McpServer({
|
|
11170
12282
|
name: "opencode-codebase-index",
|
|
11171
12283
|
version: getPackageVersion()
|
|
12284
|
+
}, {
|
|
12285
|
+
instructions: getServerInstructions(host)
|
|
11172
12286
|
});
|
|
11173
12287
|
initializeTools(projectRoot, config, host);
|
|
11174
12288
|
registerMcpTools(server, {
|
|
@@ -11180,18 +12294,37 @@ function createMcpServer(projectRoot, config, host = "opencode") {
|
|
|
11180
12294
|
}
|
|
11181
12295
|
|
|
11182
12296
|
// src/utils/auto-index.ts
|
|
12297
|
+
var INITIAL_RETRY_DELAY_MS = 50;
|
|
12298
|
+
var MAX_RETRY_DELAY_MS = 500;
|
|
12299
|
+
var autoIndexStates = /* @__PURE__ */ new WeakMap();
|
|
11183
12300
|
function getErrorMessage3(error) {
|
|
11184
12301
|
return error instanceof Error ? error.message : String(error);
|
|
11185
12302
|
}
|
|
11186
|
-
function
|
|
11187
|
-
indexer.
|
|
11188
|
-
|
|
11189
|
-
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
11190
|
-
});
|
|
12303
|
+
function runAutoIndex(indexer, projectRoot, state) {
|
|
12304
|
+
void indexer.index().then(() => {
|
|
12305
|
+
autoIndexStates.delete(indexer);
|
|
11191
12306
|
}).catch((error) => {
|
|
11192
|
-
|
|
12307
|
+
if (!isTransientIndexLockContention(error)) {
|
|
12308
|
+
autoIndexStates.delete(indexer);
|
|
12309
|
+
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
12310
|
+
return;
|
|
12311
|
+
}
|
|
12312
|
+
const retryDelayMs = state.retryDelayMs;
|
|
12313
|
+
state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
|
|
12314
|
+
const retryTimer = setTimeout(() => {
|
|
12315
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12316
|
+
}, retryDelayMs);
|
|
12317
|
+
retryTimer.unref?.();
|
|
11193
12318
|
});
|
|
11194
12319
|
}
|
|
12320
|
+
function startAutoIndex(indexer, projectRoot) {
|
|
12321
|
+
if (autoIndexStates.has(indexer)) return;
|
|
12322
|
+
const state = {
|
|
12323
|
+
retryDelayMs: INITIAL_RETRY_DELAY_MS
|
|
12324
|
+
};
|
|
12325
|
+
autoIndexStates.set(indexer, state);
|
|
12326
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
12327
|
+
}
|
|
11195
12328
|
|
|
11196
12329
|
// node_modules/chokidar/index.js
|
|
11197
12330
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
@@ -11283,7 +12416,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
11283
12416
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
11284
12417
|
const statMethod = opts.lstat ? lstat : stat;
|
|
11285
12418
|
if (wantBigintFsStats) {
|
|
11286
|
-
this._stat = (
|
|
12419
|
+
this._stat = (path27) => statMethod(path27, { bigint: true });
|
|
11287
12420
|
} else {
|
|
11288
12421
|
this._stat = statMethod;
|
|
11289
12422
|
}
|
|
@@ -11308,8 +12441,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
11308
12441
|
const par = this.parent;
|
|
11309
12442
|
const fil = par && par.files;
|
|
11310
12443
|
if (fil && fil.length > 0) {
|
|
11311
|
-
const { path:
|
|
11312
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
12444
|
+
const { path: path27, depth } = par;
|
|
12445
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path27));
|
|
11313
12446
|
const awaited = await Promise.all(slice);
|
|
11314
12447
|
for (const entry of awaited) {
|
|
11315
12448
|
if (!entry)
|
|
@@ -11349,20 +12482,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
11349
12482
|
this.reading = false;
|
|
11350
12483
|
}
|
|
11351
12484
|
}
|
|
11352
|
-
async _exploreDir(
|
|
12485
|
+
async _exploreDir(path27, depth) {
|
|
11353
12486
|
let files;
|
|
11354
12487
|
try {
|
|
11355
|
-
files = await readdir(
|
|
12488
|
+
files = await readdir(path27, this._rdOptions);
|
|
11356
12489
|
} catch (error) {
|
|
11357
12490
|
this._onError(error);
|
|
11358
12491
|
}
|
|
11359
|
-
return { files, depth, path:
|
|
12492
|
+
return { files, depth, path: path27 };
|
|
11360
12493
|
}
|
|
11361
|
-
async _formatEntry(dirent,
|
|
12494
|
+
async _formatEntry(dirent, path27) {
|
|
11362
12495
|
let entry;
|
|
11363
12496
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
11364
12497
|
try {
|
|
11365
|
-
const fullPath = presolve(pjoin(
|
|
12498
|
+
const fullPath = presolve(pjoin(path27, basename5));
|
|
11366
12499
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
|
|
11367
12500
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
11368
12501
|
} catch (err) {
|
|
@@ -11762,16 +12895,16 @@ var delFromSet = (main2, prop, item) => {
|
|
|
11762
12895
|
};
|
|
11763
12896
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
11764
12897
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
11765
|
-
function createFsWatchInstance(
|
|
12898
|
+
function createFsWatchInstance(path27, options, listener, errHandler, emitRaw) {
|
|
11766
12899
|
const handleEvent = (rawEvent, evPath) => {
|
|
11767
|
-
listener(
|
|
11768
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
11769
|
-
if (evPath &&
|
|
11770
|
-
fsWatchBroadcast(sp.resolve(
|
|
12900
|
+
listener(path27);
|
|
12901
|
+
emitRaw(rawEvent, evPath, { watchedPath: path27 });
|
|
12902
|
+
if (evPath && path27 !== evPath) {
|
|
12903
|
+
fsWatchBroadcast(sp.resolve(path27, evPath), KEY_LISTENERS, sp.join(path27, evPath));
|
|
11771
12904
|
}
|
|
11772
12905
|
};
|
|
11773
12906
|
try {
|
|
11774
|
-
return fs_watch(
|
|
12907
|
+
return fs_watch(path27, {
|
|
11775
12908
|
persistent: options.persistent
|
|
11776
12909
|
}, handleEvent);
|
|
11777
12910
|
} catch (error) {
|
|
@@ -11787,12 +12920,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
11787
12920
|
listener(val1, val2, val3);
|
|
11788
12921
|
});
|
|
11789
12922
|
};
|
|
11790
|
-
var setFsWatchListener = (
|
|
12923
|
+
var setFsWatchListener = (path27, fullPath, options, handlers) => {
|
|
11791
12924
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
11792
12925
|
let cont = FsWatchInstances.get(fullPath);
|
|
11793
12926
|
let watcher;
|
|
11794
12927
|
if (!options.persistent) {
|
|
11795
|
-
watcher = createFsWatchInstance(
|
|
12928
|
+
watcher = createFsWatchInstance(path27, options, listener, errHandler, rawEmitter);
|
|
11796
12929
|
if (!watcher)
|
|
11797
12930
|
return;
|
|
11798
12931
|
return watcher.close.bind(watcher);
|
|
@@ -11803,7 +12936,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
|
|
|
11803
12936
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
11804
12937
|
} else {
|
|
11805
12938
|
watcher = createFsWatchInstance(
|
|
11806
|
-
|
|
12939
|
+
path27,
|
|
11807
12940
|
options,
|
|
11808
12941
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
11809
12942
|
errHandler,
|
|
@@ -11818,7 +12951,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
|
|
|
11818
12951
|
cont.watcherUnusable = true;
|
|
11819
12952
|
if (isWindows && error.code === "EPERM") {
|
|
11820
12953
|
try {
|
|
11821
|
-
const fd = await open(
|
|
12954
|
+
const fd = await open(path27, "r");
|
|
11822
12955
|
await fd.close();
|
|
11823
12956
|
broadcastErr(error);
|
|
11824
12957
|
} catch (err) {
|
|
@@ -11849,7 +12982,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
|
|
|
11849
12982
|
};
|
|
11850
12983
|
};
|
|
11851
12984
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
11852
|
-
var setFsWatchFileListener = (
|
|
12985
|
+
var setFsWatchFileListener = (path27, fullPath, options, handlers) => {
|
|
11853
12986
|
const { listener, rawEmitter } = handlers;
|
|
11854
12987
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
11855
12988
|
const copts = cont && cont.options;
|
|
@@ -11871,7 +13004,7 @@ var setFsWatchFileListener = (path26, fullPath, options, handlers) => {
|
|
|
11871
13004
|
});
|
|
11872
13005
|
const currmtime = curr.mtimeMs;
|
|
11873
13006
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
11874
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
13007
|
+
foreach(cont.listeners, (listener2) => listener2(path27, curr));
|
|
11875
13008
|
}
|
|
11876
13009
|
})
|
|
11877
13010
|
};
|
|
@@ -11901,13 +13034,13 @@ var NodeFsHandler = class {
|
|
|
11901
13034
|
* @param listener on fs change
|
|
11902
13035
|
* @returns closer for the watcher instance
|
|
11903
13036
|
*/
|
|
11904
|
-
_watchWithNodeFs(
|
|
13037
|
+
_watchWithNodeFs(path27, listener) {
|
|
11905
13038
|
const opts = this.fsw.options;
|
|
11906
|
-
const directory = sp.dirname(
|
|
11907
|
-
const basename5 = sp.basename(
|
|
13039
|
+
const directory = sp.dirname(path27);
|
|
13040
|
+
const basename5 = sp.basename(path27);
|
|
11908
13041
|
const parent = this.fsw._getWatchedDir(directory);
|
|
11909
13042
|
parent.add(basename5);
|
|
11910
|
-
const absolutePath = sp.resolve(
|
|
13043
|
+
const absolutePath = sp.resolve(path27);
|
|
11911
13044
|
const options = {
|
|
11912
13045
|
persistent: opts.persistent
|
|
11913
13046
|
};
|
|
@@ -11917,12 +13050,12 @@ var NodeFsHandler = class {
|
|
|
11917
13050
|
if (opts.usePolling) {
|
|
11918
13051
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
11919
13052
|
options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
|
|
11920
|
-
closer = setFsWatchFileListener(
|
|
13053
|
+
closer = setFsWatchFileListener(path27, absolutePath, options, {
|
|
11921
13054
|
listener,
|
|
11922
13055
|
rawEmitter: this.fsw._emitRaw
|
|
11923
13056
|
});
|
|
11924
13057
|
} else {
|
|
11925
|
-
closer = setFsWatchListener(
|
|
13058
|
+
closer = setFsWatchListener(path27, absolutePath, options, {
|
|
11926
13059
|
listener,
|
|
11927
13060
|
errHandler: this._boundHandleError,
|
|
11928
13061
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -11944,7 +13077,7 @@ var NodeFsHandler = class {
|
|
|
11944
13077
|
let prevStats = stats;
|
|
11945
13078
|
if (parent.has(basename5))
|
|
11946
13079
|
return;
|
|
11947
|
-
const listener = async (
|
|
13080
|
+
const listener = async (path27, newStats) => {
|
|
11948
13081
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
11949
13082
|
return;
|
|
11950
13083
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -11958,11 +13091,11 @@ var NodeFsHandler = class {
|
|
|
11958
13091
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
11959
13092
|
}
|
|
11960
13093
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
11961
|
-
this.fsw._closeFile(
|
|
13094
|
+
this.fsw._closeFile(path27);
|
|
11962
13095
|
prevStats = newStats2;
|
|
11963
13096
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
11964
13097
|
if (closer2)
|
|
11965
|
-
this.fsw._addPathCloser(
|
|
13098
|
+
this.fsw._addPathCloser(path27, closer2);
|
|
11966
13099
|
} else {
|
|
11967
13100
|
prevStats = newStats2;
|
|
11968
13101
|
}
|
|
@@ -11994,7 +13127,7 @@ var NodeFsHandler = class {
|
|
|
11994
13127
|
* @param item basename of this item
|
|
11995
13128
|
* @returns true if no more processing is needed for this entry.
|
|
11996
13129
|
*/
|
|
11997
|
-
async _handleSymlink(entry, directory,
|
|
13130
|
+
async _handleSymlink(entry, directory, path27, item) {
|
|
11998
13131
|
if (this.fsw.closed) {
|
|
11999
13132
|
return;
|
|
12000
13133
|
}
|
|
@@ -12004,7 +13137,7 @@ var NodeFsHandler = class {
|
|
|
12004
13137
|
this.fsw._incrReadyCount();
|
|
12005
13138
|
let linkPath;
|
|
12006
13139
|
try {
|
|
12007
|
-
linkPath = await fsrealpath(
|
|
13140
|
+
linkPath = await fsrealpath(path27);
|
|
12008
13141
|
} catch (e) {
|
|
12009
13142
|
this.fsw._emitReady();
|
|
12010
13143
|
return true;
|
|
@@ -12014,12 +13147,12 @@ var NodeFsHandler = class {
|
|
|
12014
13147
|
if (dir.has(item)) {
|
|
12015
13148
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
12016
13149
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
12017
|
-
this.fsw._emit(EV.CHANGE,
|
|
13150
|
+
this.fsw._emit(EV.CHANGE, path27, entry.stats);
|
|
12018
13151
|
}
|
|
12019
13152
|
} else {
|
|
12020
13153
|
dir.add(item);
|
|
12021
13154
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
12022
|
-
this.fsw._emit(EV.ADD,
|
|
13155
|
+
this.fsw._emit(EV.ADD, path27, entry.stats);
|
|
12023
13156
|
}
|
|
12024
13157
|
this.fsw._emitReady();
|
|
12025
13158
|
return true;
|
|
@@ -12049,9 +13182,9 @@ var NodeFsHandler = class {
|
|
|
12049
13182
|
return;
|
|
12050
13183
|
}
|
|
12051
13184
|
const item = entry.path;
|
|
12052
|
-
let
|
|
13185
|
+
let path27 = sp.join(directory, item);
|
|
12053
13186
|
current.add(item);
|
|
12054
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
13187
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path27, item)) {
|
|
12055
13188
|
return;
|
|
12056
13189
|
}
|
|
12057
13190
|
if (this.fsw.closed) {
|
|
@@ -12060,8 +13193,8 @@ var NodeFsHandler = class {
|
|
|
12060
13193
|
}
|
|
12061
13194
|
if (item === target || !target && !previous.has(item)) {
|
|
12062
13195
|
this.fsw._incrReadyCount();
|
|
12063
|
-
|
|
12064
|
-
this._addToNodeFs(
|
|
13196
|
+
path27 = sp.join(dir, sp.relative(dir, path27));
|
|
13197
|
+
this._addToNodeFs(path27, initialAdd, wh, depth + 1);
|
|
12065
13198
|
}
|
|
12066
13199
|
}).on(EV.ERROR, this._boundHandleError);
|
|
12067
13200
|
return new Promise((resolve15, reject) => {
|
|
@@ -12130,13 +13263,13 @@ var NodeFsHandler = class {
|
|
|
12130
13263
|
* @param depth Child path actually targeted for watch
|
|
12131
13264
|
* @param target Child path actually targeted for watch
|
|
12132
13265
|
*/
|
|
12133
|
-
async _addToNodeFs(
|
|
13266
|
+
async _addToNodeFs(path27, initialAdd, priorWh, depth, target) {
|
|
12134
13267
|
const ready = this.fsw._emitReady;
|
|
12135
|
-
if (this.fsw._isIgnored(
|
|
13268
|
+
if (this.fsw._isIgnored(path27) || this.fsw.closed) {
|
|
12136
13269
|
ready();
|
|
12137
13270
|
return false;
|
|
12138
13271
|
}
|
|
12139
|
-
const wh = this.fsw._getWatchHelpers(
|
|
13272
|
+
const wh = this.fsw._getWatchHelpers(path27);
|
|
12140
13273
|
if (priorWh) {
|
|
12141
13274
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
12142
13275
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -12152,8 +13285,8 @@ var NodeFsHandler = class {
|
|
|
12152
13285
|
const follow = this.fsw.options.followSymlinks;
|
|
12153
13286
|
let closer;
|
|
12154
13287
|
if (stats.isDirectory()) {
|
|
12155
|
-
const absPath = sp.resolve(
|
|
12156
|
-
const targetPath = follow ? await fsrealpath(
|
|
13288
|
+
const absPath = sp.resolve(path27);
|
|
13289
|
+
const targetPath = follow ? await fsrealpath(path27) : path27;
|
|
12157
13290
|
if (this.fsw.closed)
|
|
12158
13291
|
return;
|
|
12159
13292
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -12163,29 +13296,29 @@ var NodeFsHandler = class {
|
|
|
12163
13296
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
12164
13297
|
}
|
|
12165
13298
|
} else if (stats.isSymbolicLink()) {
|
|
12166
|
-
const targetPath = follow ? await fsrealpath(
|
|
13299
|
+
const targetPath = follow ? await fsrealpath(path27) : path27;
|
|
12167
13300
|
if (this.fsw.closed)
|
|
12168
13301
|
return;
|
|
12169
13302
|
const parent = sp.dirname(wh.watchPath);
|
|
12170
13303
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
12171
13304
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
12172
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
13305
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path27, wh, targetPath);
|
|
12173
13306
|
if (this.fsw.closed)
|
|
12174
13307
|
return;
|
|
12175
13308
|
if (targetPath !== void 0) {
|
|
12176
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
13309
|
+
this.fsw._symlinkPaths.set(sp.resolve(path27), targetPath);
|
|
12177
13310
|
}
|
|
12178
13311
|
} else {
|
|
12179
13312
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
12180
13313
|
}
|
|
12181
13314
|
ready();
|
|
12182
13315
|
if (closer)
|
|
12183
|
-
this.fsw._addPathCloser(
|
|
13316
|
+
this.fsw._addPathCloser(path27, closer);
|
|
12184
13317
|
return false;
|
|
12185
13318
|
} catch (error) {
|
|
12186
13319
|
if (this.fsw._handleError(error)) {
|
|
12187
13320
|
ready();
|
|
12188
|
-
return
|
|
13321
|
+
return path27;
|
|
12189
13322
|
}
|
|
12190
13323
|
}
|
|
12191
13324
|
}
|
|
@@ -12228,24 +13361,24 @@ function createPattern(matcher) {
|
|
|
12228
13361
|
}
|
|
12229
13362
|
return () => false;
|
|
12230
13363
|
}
|
|
12231
|
-
function normalizePath2(
|
|
12232
|
-
if (typeof
|
|
13364
|
+
function normalizePath2(path27) {
|
|
13365
|
+
if (typeof path27 !== "string")
|
|
12233
13366
|
throw new Error("string expected");
|
|
12234
|
-
|
|
12235
|
-
|
|
13367
|
+
path27 = sp2.normalize(path27);
|
|
13368
|
+
path27 = path27.replace(/\\/g, "/");
|
|
12236
13369
|
let prepend = false;
|
|
12237
|
-
if (
|
|
13370
|
+
if (path27.startsWith("//"))
|
|
12238
13371
|
prepend = true;
|
|
12239
|
-
|
|
13372
|
+
path27 = path27.replace(DOUBLE_SLASH_RE, "/");
|
|
12240
13373
|
if (prepend)
|
|
12241
|
-
|
|
12242
|
-
return
|
|
13374
|
+
path27 = "/" + path27;
|
|
13375
|
+
return path27;
|
|
12243
13376
|
}
|
|
12244
13377
|
function matchPatterns(patterns, testString, stats) {
|
|
12245
|
-
const
|
|
13378
|
+
const path27 = normalizePath2(testString);
|
|
12246
13379
|
for (let index = 0; index < patterns.length; index++) {
|
|
12247
13380
|
const pattern = patterns[index];
|
|
12248
|
-
if (pattern(
|
|
13381
|
+
if (pattern(path27, stats)) {
|
|
12249
13382
|
return true;
|
|
12250
13383
|
}
|
|
12251
13384
|
}
|
|
@@ -12283,19 +13416,19 @@ var toUnix = (string) => {
|
|
|
12283
13416
|
}
|
|
12284
13417
|
return str;
|
|
12285
13418
|
};
|
|
12286
|
-
var normalizePathToUnix = (
|
|
12287
|
-
var normalizeIgnored = (cwd = "") => (
|
|
12288
|
-
if (typeof
|
|
12289
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
13419
|
+
var normalizePathToUnix = (path27) => toUnix(sp2.normalize(toUnix(path27)));
|
|
13420
|
+
var normalizeIgnored = (cwd = "") => (path27) => {
|
|
13421
|
+
if (typeof path27 === "string") {
|
|
13422
|
+
return normalizePathToUnix(sp2.isAbsolute(path27) ? path27 : sp2.join(cwd, path27));
|
|
12290
13423
|
} else {
|
|
12291
|
-
return
|
|
13424
|
+
return path27;
|
|
12292
13425
|
}
|
|
12293
13426
|
};
|
|
12294
|
-
var getAbsolutePath = (
|
|
12295
|
-
if (sp2.isAbsolute(
|
|
12296
|
-
return
|
|
13427
|
+
var getAbsolutePath = (path27, cwd) => {
|
|
13428
|
+
if (sp2.isAbsolute(path27)) {
|
|
13429
|
+
return path27;
|
|
12297
13430
|
}
|
|
12298
|
-
return sp2.join(cwd,
|
|
13431
|
+
return sp2.join(cwd, path27);
|
|
12299
13432
|
};
|
|
12300
13433
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
12301
13434
|
var DirEntry = class {
|
|
@@ -12360,10 +13493,10 @@ var WatchHelper = class {
|
|
|
12360
13493
|
dirParts;
|
|
12361
13494
|
followSymlinks;
|
|
12362
13495
|
statMethod;
|
|
12363
|
-
constructor(
|
|
13496
|
+
constructor(path27, follow, fsw) {
|
|
12364
13497
|
this.fsw = fsw;
|
|
12365
|
-
const watchPath =
|
|
12366
|
-
this.path =
|
|
13498
|
+
const watchPath = path27;
|
|
13499
|
+
this.path = path27 = path27.replace(REPLACER_RE, "");
|
|
12367
13500
|
this.watchPath = watchPath;
|
|
12368
13501
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
12369
13502
|
this.dirParts = [];
|
|
@@ -12503,20 +13636,20 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12503
13636
|
this._closePromise = void 0;
|
|
12504
13637
|
let paths = unifyPaths(paths_);
|
|
12505
13638
|
if (cwd) {
|
|
12506
|
-
paths = paths.map((
|
|
12507
|
-
const absPath = getAbsolutePath(
|
|
13639
|
+
paths = paths.map((path27) => {
|
|
13640
|
+
const absPath = getAbsolutePath(path27, cwd);
|
|
12508
13641
|
return absPath;
|
|
12509
13642
|
});
|
|
12510
13643
|
}
|
|
12511
|
-
paths.forEach((
|
|
12512
|
-
this._removeIgnoredPath(
|
|
13644
|
+
paths.forEach((path27) => {
|
|
13645
|
+
this._removeIgnoredPath(path27);
|
|
12513
13646
|
});
|
|
12514
13647
|
this._userIgnored = void 0;
|
|
12515
13648
|
if (!this._readyCount)
|
|
12516
13649
|
this._readyCount = 0;
|
|
12517
13650
|
this._readyCount += paths.length;
|
|
12518
|
-
Promise.all(paths.map(async (
|
|
12519
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
13651
|
+
Promise.all(paths.map(async (path27) => {
|
|
13652
|
+
const res = await this._nodeFsHandler._addToNodeFs(path27, !_internal, void 0, 0, _origAdd);
|
|
12520
13653
|
if (res)
|
|
12521
13654
|
this._emitReady();
|
|
12522
13655
|
return res;
|
|
@@ -12538,17 +13671,17 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12538
13671
|
return this;
|
|
12539
13672
|
const paths = unifyPaths(paths_);
|
|
12540
13673
|
const { cwd } = this.options;
|
|
12541
|
-
paths.forEach((
|
|
12542
|
-
if (!sp2.isAbsolute(
|
|
13674
|
+
paths.forEach((path27) => {
|
|
13675
|
+
if (!sp2.isAbsolute(path27) && !this._closers.has(path27)) {
|
|
12543
13676
|
if (cwd)
|
|
12544
|
-
|
|
12545
|
-
|
|
13677
|
+
path27 = sp2.join(cwd, path27);
|
|
13678
|
+
path27 = sp2.resolve(path27);
|
|
12546
13679
|
}
|
|
12547
|
-
this._closePath(
|
|
12548
|
-
this._addIgnoredPath(
|
|
12549
|
-
if (this._watched.has(
|
|
13680
|
+
this._closePath(path27);
|
|
13681
|
+
this._addIgnoredPath(path27);
|
|
13682
|
+
if (this._watched.has(path27)) {
|
|
12550
13683
|
this._addIgnoredPath({
|
|
12551
|
-
path:
|
|
13684
|
+
path: path27,
|
|
12552
13685
|
recursive: true
|
|
12553
13686
|
});
|
|
12554
13687
|
}
|
|
@@ -12612,38 +13745,38 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12612
13745
|
* @param stats arguments to be passed with event
|
|
12613
13746
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
12614
13747
|
*/
|
|
12615
|
-
async _emit(event,
|
|
13748
|
+
async _emit(event, path27, stats) {
|
|
12616
13749
|
if (this.closed)
|
|
12617
13750
|
return;
|
|
12618
13751
|
const opts = this.options;
|
|
12619
13752
|
if (isWindows)
|
|
12620
|
-
|
|
13753
|
+
path27 = sp2.normalize(path27);
|
|
12621
13754
|
if (opts.cwd)
|
|
12622
|
-
|
|
12623
|
-
const args = [
|
|
13755
|
+
path27 = sp2.relative(opts.cwd, path27);
|
|
13756
|
+
const args = [path27];
|
|
12624
13757
|
if (stats != null)
|
|
12625
13758
|
args.push(stats);
|
|
12626
13759
|
const awf = opts.awaitWriteFinish;
|
|
12627
13760
|
let pw;
|
|
12628
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
13761
|
+
if (awf && (pw = this._pendingWrites.get(path27))) {
|
|
12629
13762
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
12630
13763
|
return this;
|
|
12631
13764
|
}
|
|
12632
13765
|
if (opts.atomic) {
|
|
12633
13766
|
if (event === EVENTS.UNLINK) {
|
|
12634
|
-
this._pendingUnlinks.set(
|
|
13767
|
+
this._pendingUnlinks.set(path27, [event, ...args]);
|
|
12635
13768
|
setTimeout(() => {
|
|
12636
|
-
this._pendingUnlinks.forEach((entry,
|
|
13769
|
+
this._pendingUnlinks.forEach((entry, path28) => {
|
|
12637
13770
|
this.emit(...entry);
|
|
12638
13771
|
this.emit(EVENTS.ALL, ...entry);
|
|
12639
|
-
this._pendingUnlinks.delete(
|
|
13772
|
+
this._pendingUnlinks.delete(path28);
|
|
12640
13773
|
});
|
|
12641
13774
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
12642
13775
|
return this;
|
|
12643
13776
|
}
|
|
12644
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
13777
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path27)) {
|
|
12645
13778
|
event = EVENTS.CHANGE;
|
|
12646
|
-
this._pendingUnlinks.delete(
|
|
13779
|
+
this._pendingUnlinks.delete(path27);
|
|
12647
13780
|
}
|
|
12648
13781
|
}
|
|
12649
13782
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -12661,16 +13794,16 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12661
13794
|
this.emitWithAll(event, args);
|
|
12662
13795
|
}
|
|
12663
13796
|
};
|
|
12664
|
-
this._awaitWriteFinish(
|
|
13797
|
+
this._awaitWriteFinish(path27, awf.stabilityThreshold, event, awfEmit);
|
|
12665
13798
|
return this;
|
|
12666
13799
|
}
|
|
12667
13800
|
if (event === EVENTS.CHANGE) {
|
|
12668
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
13801
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path27, 50);
|
|
12669
13802
|
if (isThrottled)
|
|
12670
13803
|
return this;
|
|
12671
13804
|
}
|
|
12672
13805
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
12673
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
13806
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path27) : path27;
|
|
12674
13807
|
let stats2;
|
|
12675
13808
|
try {
|
|
12676
13809
|
stats2 = await stat3(fullPath);
|
|
@@ -12701,23 +13834,23 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12701
13834
|
* @param timeout duration of time to suppress duplicate actions
|
|
12702
13835
|
* @returns tracking object or false if action should be suppressed
|
|
12703
13836
|
*/
|
|
12704
|
-
_throttle(actionType,
|
|
13837
|
+
_throttle(actionType, path27, timeout) {
|
|
12705
13838
|
if (!this._throttled.has(actionType)) {
|
|
12706
13839
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
12707
13840
|
}
|
|
12708
13841
|
const action = this._throttled.get(actionType);
|
|
12709
13842
|
if (!action)
|
|
12710
13843
|
throw new Error("invalid throttle");
|
|
12711
|
-
const actionPath = action.get(
|
|
13844
|
+
const actionPath = action.get(path27);
|
|
12712
13845
|
if (actionPath) {
|
|
12713
13846
|
actionPath.count++;
|
|
12714
13847
|
return false;
|
|
12715
13848
|
}
|
|
12716
13849
|
let timeoutObject;
|
|
12717
13850
|
const clear = () => {
|
|
12718
|
-
const item = action.get(
|
|
13851
|
+
const item = action.get(path27);
|
|
12719
13852
|
const count = item ? item.count : 0;
|
|
12720
|
-
action.delete(
|
|
13853
|
+
action.delete(path27);
|
|
12721
13854
|
clearTimeout(timeoutObject);
|
|
12722
13855
|
if (item)
|
|
12723
13856
|
clearTimeout(item.timeoutObject);
|
|
@@ -12725,7 +13858,7 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12725
13858
|
};
|
|
12726
13859
|
timeoutObject = setTimeout(clear, timeout);
|
|
12727
13860
|
const thr = { timeoutObject, clear, count: 0 };
|
|
12728
|
-
action.set(
|
|
13861
|
+
action.set(path27, thr);
|
|
12729
13862
|
return thr;
|
|
12730
13863
|
}
|
|
12731
13864
|
_incrReadyCount() {
|
|
@@ -12739,44 +13872,44 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12739
13872
|
* @param event
|
|
12740
13873
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
12741
13874
|
*/
|
|
12742
|
-
_awaitWriteFinish(
|
|
13875
|
+
_awaitWriteFinish(path27, threshold, event, awfEmit) {
|
|
12743
13876
|
const awf = this.options.awaitWriteFinish;
|
|
12744
13877
|
if (typeof awf !== "object")
|
|
12745
13878
|
return;
|
|
12746
13879
|
const pollInterval = awf.pollInterval;
|
|
12747
13880
|
let timeoutHandler;
|
|
12748
|
-
let fullPath =
|
|
12749
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
12750
|
-
fullPath = sp2.join(this.options.cwd,
|
|
13881
|
+
let fullPath = path27;
|
|
13882
|
+
if (this.options.cwd && !sp2.isAbsolute(path27)) {
|
|
13883
|
+
fullPath = sp2.join(this.options.cwd, path27);
|
|
12751
13884
|
}
|
|
12752
13885
|
const now = /* @__PURE__ */ new Date();
|
|
12753
13886
|
const writes = this._pendingWrites;
|
|
12754
13887
|
function awaitWriteFinishFn(prevStat) {
|
|
12755
13888
|
statcb(fullPath, (err, curStat) => {
|
|
12756
|
-
if (err || !writes.has(
|
|
13889
|
+
if (err || !writes.has(path27)) {
|
|
12757
13890
|
if (err && err.code !== "ENOENT")
|
|
12758
13891
|
awfEmit(err);
|
|
12759
13892
|
return;
|
|
12760
13893
|
}
|
|
12761
13894
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
12762
13895
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
12763
|
-
writes.get(
|
|
13896
|
+
writes.get(path27).lastChange = now2;
|
|
12764
13897
|
}
|
|
12765
|
-
const pw = writes.get(
|
|
13898
|
+
const pw = writes.get(path27);
|
|
12766
13899
|
const df = now2 - pw.lastChange;
|
|
12767
13900
|
if (df >= threshold) {
|
|
12768
|
-
writes.delete(
|
|
13901
|
+
writes.delete(path27);
|
|
12769
13902
|
awfEmit(void 0, curStat);
|
|
12770
13903
|
} else {
|
|
12771
13904
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
12772
13905
|
}
|
|
12773
13906
|
});
|
|
12774
13907
|
}
|
|
12775
|
-
if (!writes.has(
|
|
12776
|
-
writes.set(
|
|
13908
|
+
if (!writes.has(path27)) {
|
|
13909
|
+
writes.set(path27, {
|
|
12777
13910
|
lastChange: now,
|
|
12778
13911
|
cancelWait: () => {
|
|
12779
|
-
writes.delete(
|
|
13912
|
+
writes.delete(path27);
|
|
12780
13913
|
clearTimeout(timeoutHandler);
|
|
12781
13914
|
return event;
|
|
12782
13915
|
}
|
|
@@ -12787,8 +13920,8 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12787
13920
|
/**
|
|
12788
13921
|
* Determines whether user has asked to ignore this path.
|
|
12789
13922
|
*/
|
|
12790
|
-
_isIgnored(
|
|
12791
|
-
if (this.options.atomic && DOT_RE.test(
|
|
13923
|
+
_isIgnored(path27, stats) {
|
|
13924
|
+
if (this.options.atomic && DOT_RE.test(path27))
|
|
12792
13925
|
return true;
|
|
12793
13926
|
if (!this._userIgnored) {
|
|
12794
13927
|
const { cwd } = this.options;
|
|
@@ -12798,17 +13931,17 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12798
13931
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
12799
13932
|
this._userIgnored = anymatch(list, void 0);
|
|
12800
13933
|
}
|
|
12801
|
-
return this._userIgnored(
|
|
13934
|
+
return this._userIgnored(path27, stats);
|
|
12802
13935
|
}
|
|
12803
|
-
_isntIgnored(
|
|
12804
|
-
return !this._isIgnored(
|
|
13936
|
+
_isntIgnored(path27, stat4) {
|
|
13937
|
+
return !this._isIgnored(path27, stat4);
|
|
12805
13938
|
}
|
|
12806
13939
|
/**
|
|
12807
13940
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
12808
13941
|
* @param path file or directory pattern being watched
|
|
12809
13942
|
*/
|
|
12810
|
-
_getWatchHelpers(
|
|
12811
|
-
return new WatchHelper(
|
|
13943
|
+
_getWatchHelpers(path27) {
|
|
13944
|
+
return new WatchHelper(path27, this.options.followSymlinks, this);
|
|
12812
13945
|
}
|
|
12813
13946
|
// Directory helpers
|
|
12814
13947
|
// -----------------
|
|
@@ -12840,63 +13973,63 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
12840
13973
|
* @param item base path of item/directory
|
|
12841
13974
|
*/
|
|
12842
13975
|
_remove(directory, item, isDirectory) {
|
|
12843
|
-
const
|
|
12844
|
-
const fullPath = sp2.resolve(
|
|
12845
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
12846
|
-
if (!this._throttle("remove",
|
|
13976
|
+
const path27 = sp2.join(directory, item);
|
|
13977
|
+
const fullPath = sp2.resolve(path27);
|
|
13978
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path27) || this._watched.has(fullPath);
|
|
13979
|
+
if (!this._throttle("remove", path27, 100))
|
|
12847
13980
|
return;
|
|
12848
13981
|
if (!isDirectory && this._watched.size === 1) {
|
|
12849
13982
|
this.add(directory, item, true);
|
|
12850
13983
|
}
|
|
12851
|
-
const wp = this._getWatchedDir(
|
|
13984
|
+
const wp = this._getWatchedDir(path27);
|
|
12852
13985
|
const nestedDirectoryChildren = wp.getChildren();
|
|
12853
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
13986
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path27, nested));
|
|
12854
13987
|
const parent = this._getWatchedDir(directory);
|
|
12855
13988
|
const wasTracked = parent.has(item);
|
|
12856
13989
|
parent.remove(item);
|
|
12857
13990
|
if (this._symlinkPaths.has(fullPath)) {
|
|
12858
13991
|
this._symlinkPaths.delete(fullPath);
|
|
12859
13992
|
}
|
|
12860
|
-
let relPath =
|
|
13993
|
+
let relPath = path27;
|
|
12861
13994
|
if (this.options.cwd)
|
|
12862
|
-
relPath = sp2.relative(this.options.cwd,
|
|
13995
|
+
relPath = sp2.relative(this.options.cwd, path27);
|
|
12863
13996
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
12864
13997
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
12865
13998
|
if (event === EVENTS.ADD)
|
|
12866
13999
|
return;
|
|
12867
14000
|
}
|
|
12868
|
-
this._watched.delete(
|
|
14001
|
+
this._watched.delete(path27);
|
|
12869
14002
|
this._watched.delete(fullPath);
|
|
12870
14003
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
12871
|
-
if (wasTracked && !this._isIgnored(
|
|
12872
|
-
this._emit(eventName,
|
|
12873
|
-
this._closePath(
|
|
14004
|
+
if (wasTracked && !this._isIgnored(path27))
|
|
14005
|
+
this._emit(eventName, path27);
|
|
14006
|
+
this._closePath(path27);
|
|
12874
14007
|
}
|
|
12875
14008
|
/**
|
|
12876
14009
|
* Closes all watchers for a path
|
|
12877
14010
|
*/
|
|
12878
|
-
_closePath(
|
|
12879
|
-
this._closeFile(
|
|
12880
|
-
const dir = sp2.dirname(
|
|
12881
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
14011
|
+
_closePath(path27) {
|
|
14012
|
+
this._closeFile(path27);
|
|
14013
|
+
const dir = sp2.dirname(path27);
|
|
14014
|
+
this._getWatchedDir(dir).remove(sp2.basename(path27));
|
|
12882
14015
|
}
|
|
12883
14016
|
/**
|
|
12884
14017
|
* Closes only file-specific watchers
|
|
12885
14018
|
*/
|
|
12886
|
-
_closeFile(
|
|
12887
|
-
const closers = this._closers.get(
|
|
14019
|
+
_closeFile(path27) {
|
|
14020
|
+
const closers = this._closers.get(path27);
|
|
12888
14021
|
if (!closers)
|
|
12889
14022
|
return;
|
|
12890
14023
|
closers.forEach((closer) => closer());
|
|
12891
|
-
this._closers.delete(
|
|
14024
|
+
this._closers.delete(path27);
|
|
12892
14025
|
}
|
|
12893
|
-
_addPathCloser(
|
|
14026
|
+
_addPathCloser(path27, closer) {
|
|
12894
14027
|
if (!closer)
|
|
12895
14028
|
return;
|
|
12896
|
-
let list = this._closers.get(
|
|
14029
|
+
let list = this._closers.get(path27);
|
|
12897
14030
|
if (!list) {
|
|
12898
14031
|
list = [];
|
|
12899
|
-
this._closers.set(
|
|
14032
|
+
this._closers.set(path27, list);
|
|
12900
14033
|
}
|
|
12901
14034
|
list.push(closer);
|
|
12902
14035
|
}
|
|
@@ -12926,7 +14059,7 @@ function watch(paths, options = {}) {
|
|
|
12926
14059
|
var chokidar_default = { watch, FSWatcher };
|
|
12927
14060
|
|
|
12928
14061
|
// src/watcher/file-watcher.ts
|
|
12929
|
-
import * as
|
|
14062
|
+
import * as path22 from "path";
|
|
12930
14063
|
var FileWatcher = class {
|
|
12931
14064
|
watcher = null;
|
|
12932
14065
|
projectRoot;
|
|
@@ -12937,6 +14070,8 @@ var FileWatcher = class {
|
|
|
12937
14070
|
debounceTimer = null;
|
|
12938
14071
|
debounceMs = 1e3;
|
|
12939
14072
|
onChanges = null;
|
|
14073
|
+
readyPromise = null;
|
|
14074
|
+
resolveReady = null;
|
|
12940
14075
|
constructor(projectRoot, config, host = "opencode", options = {}) {
|
|
12941
14076
|
this.projectRoot = projectRoot;
|
|
12942
14077
|
this.config = config;
|
|
@@ -12952,15 +14087,15 @@ var FileWatcher = class {
|
|
|
12952
14087
|
const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
|
|
12953
14088
|
this.watcher = chokidar_default.watch(watchTargets, {
|
|
12954
14089
|
ignored: (filePath) => {
|
|
12955
|
-
const relativePath =
|
|
14090
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
12956
14091
|
if (!relativePath) return false;
|
|
12957
14092
|
if (this.isProjectConfigPathOrAncestor(relativePath)) {
|
|
12958
14093
|
return false;
|
|
12959
14094
|
}
|
|
12960
|
-
if (hasFilteredPathSegment(relativePath,
|
|
14095
|
+
if (hasFilteredPathSegment(relativePath, path22.sep)) {
|
|
12961
14096
|
return true;
|
|
12962
14097
|
}
|
|
12963
|
-
if (isRestrictedDirectory(relativePath,
|
|
14098
|
+
if (isRestrictedDirectory(relativePath, path22.sep)) {
|
|
12964
14099
|
return true;
|
|
12965
14100
|
}
|
|
12966
14101
|
if (ignoreFilter.ignores(relativePath)) {
|
|
@@ -12975,6 +14110,13 @@ var FileWatcher = class {
|
|
|
12975
14110
|
pollInterval: 100
|
|
12976
14111
|
}
|
|
12977
14112
|
});
|
|
14113
|
+
this.readyPromise = new Promise((resolve15) => {
|
|
14114
|
+
this.resolveReady = resolve15;
|
|
14115
|
+
});
|
|
14116
|
+
this.watcher.once("ready", () => {
|
|
14117
|
+
this.resolveReady?.();
|
|
14118
|
+
this.resolveReady = null;
|
|
14119
|
+
});
|
|
12978
14120
|
this.watcher.on("error", (error) => {
|
|
12979
14121
|
const err = error instanceof Error ? error : null;
|
|
12980
14122
|
if (err?.code === "EPERM" || err?.code === "EACCES") {
|
|
@@ -13006,24 +14148,24 @@ var FileWatcher = class {
|
|
|
13006
14148
|
this.scheduleFlush();
|
|
13007
14149
|
}
|
|
13008
14150
|
isProjectConfigPath(filePath) {
|
|
13009
|
-
const relativePath =
|
|
13010
|
-
const normalizedRelativePath =
|
|
14151
|
+
const relativePath = path22.relative(this.projectRoot, filePath);
|
|
14152
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
13011
14153
|
return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
|
|
13012
14154
|
}
|
|
13013
14155
|
isProjectConfigPathOrAncestor(relativePath) {
|
|
13014
|
-
const normalizedRelativePath =
|
|
14156
|
+
const normalizedRelativePath = path22.normalize(relativePath);
|
|
13015
14157
|
return this.getProjectConfigRelativePaths().some(
|
|
13016
|
-
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${
|
|
14158
|
+
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path22.sep}`)
|
|
13017
14159
|
);
|
|
13018
14160
|
}
|
|
13019
14161
|
getProjectConfigRelativePaths() {
|
|
13020
14162
|
if (this.configPath) {
|
|
13021
|
-
return [
|
|
14163
|
+
return [path22.normalize(path22.relative(this.projectRoot, this.configPath))];
|
|
13022
14164
|
}
|
|
13023
14165
|
return [
|
|
13024
14166
|
resolveProjectConfigPath(this.projectRoot, this.host),
|
|
13025
14167
|
resolveWritableProjectConfigPath(this.projectRoot, this.host)
|
|
13026
|
-
].map((configPath) =>
|
|
14168
|
+
].map((configPath) => path22.normalize(path22.relative(this.projectRoot, configPath)));
|
|
13027
14169
|
}
|
|
13028
14170
|
scheduleFlush() {
|
|
13029
14171
|
if (this.debounceTimer) {
|
|
@@ -13038,7 +14180,7 @@ var FileWatcher = class {
|
|
|
13038
14180
|
return;
|
|
13039
14181
|
}
|
|
13040
14182
|
const changes = Array.from(this.pendingChanges.entries()).map(
|
|
13041
|
-
([
|
|
14183
|
+
([path27, type]) => ({ path: path27, type })
|
|
13042
14184
|
);
|
|
13043
14185
|
this.pendingChanges.clear();
|
|
13044
14186
|
try {
|
|
@@ -13047,25 +14189,32 @@ var FileWatcher = class {
|
|
|
13047
14189
|
console.error("Error handling file changes:", error);
|
|
13048
14190
|
}
|
|
13049
14191
|
}
|
|
13050
|
-
stop() {
|
|
14192
|
+
async stop() {
|
|
13051
14193
|
if (this.debounceTimer) {
|
|
13052
14194
|
clearTimeout(this.debounceTimer);
|
|
13053
14195
|
this.debounceTimer = null;
|
|
13054
14196
|
}
|
|
13055
14197
|
if (this.watcher) {
|
|
13056
|
-
this.watcher
|
|
14198
|
+
const watcher = this.watcher;
|
|
13057
14199
|
this.watcher = null;
|
|
14200
|
+
await watcher.close();
|
|
13058
14201
|
}
|
|
14202
|
+
this.resolveReady?.();
|
|
14203
|
+
this.resolveReady = null;
|
|
14204
|
+
this.readyPromise = null;
|
|
13059
14205
|
this.pendingChanges.clear();
|
|
13060
14206
|
this.onChanges = null;
|
|
13061
14207
|
}
|
|
13062
14208
|
isRunning() {
|
|
13063
14209
|
return this.watcher !== null;
|
|
13064
14210
|
}
|
|
14211
|
+
async waitUntilReady() {
|
|
14212
|
+
await (this.readyPromise ?? Promise.resolve());
|
|
14213
|
+
}
|
|
13065
14214
|
};
|
|
13066
14215
|
|
|
13067
14216
|
// src/watcher/git-head-watcher.ts
|
|
13068
|
-
import * as
|
|
14217
|
+
import * as path23 from "path";
|
|
13069
14218
|
var GitHeadWatcher = class {
|
|
13070
14219
|
watcher = null;
|
|
13071
14220
|
projectRoot;
|
|
@@ -13087,7 +14236,7 @@ var GitHeadWatcher = class {
|
|
|
13087
14236
|
this.onBranchChange = handler;
|
|
13088
14237
|
this.currentBranch = getCurrentBranch(this.projectRoot);
|
|
13089
14238
|
const headPath = getHeadPath(this.projectRoot);
|
|
13090
|
-
const refsPath =
|
|
14239
|
+
const refsPath = path23.join(this.projectRoot, ".git", "refs", "heads");
|
|
13091
14240
|
this.watcher = chokidar_default.watch([headPath, refsPath], {
|
|
13092
14241
|
persistent: true,
|
|
13093
14242
|
ignoreInitial: true,
|
|
@@ -13124,14 +14273,15 @@ var GitHeadWatcher = class {
|
|
|
13124
14273
|
getCurrentBranch() {
|
|
13125
14274
|
return this.currentBranch;
|
|
13126
14275
|
}
|
|
13127
|
-
stop() {
|
|
14276
|
+
async stop() {
|
|
13128
14277
|
if (this.debounceTimer) {
|
|
13129
14278
|
clearTimeout(this.debounceTimer);
|
|
13130
14279
|
this.debounceTimer = null;
|
|
13131
14280
|
}
|
|
13132
14281
|
if (this.watcher) {
|
|
13133
|
-
this.watcher
|
|
14282
|
+
const watcher = this.watcher;
|
|
13134
14283
|
this.watcher = null;
|
|
14284
|
+
await watcher.close();
|
|
13135
14285
|
}
|
|
13136
14286
|
this.onBranchChange = null;
|
|
13137
14287
|
}
|
|
@@ -13149,6 +14299,8 @@ var BackgroundReindexer = class {
|
|
|
13149
14299
|
running = false;
|
|
13150
14300
|
pending = false;
|
|
13151
14301
|
stopped = false;
|
|
14302
|
+
retryTimer = null;
|
|
14303
|
+
retryDelayMs = 50;
|
|
13152
14304
|
request() {
|
|
13153
14305
|
if (this.stopped) {
|
|
13154
14306
|
return;
|
|
@@ -13159,9 +14311,13 @@ var BackgroundReindexer = class {
|
|
|
13159
14311
|
stop() {
|
|
13160
14312
|
this.stopped = true;
|
|
13161
14313
|
this.pending = false;
|
|
14314
|
+
if (this.retryTimer) {
|
|
14315
|
+
clearTimeout(this.retryTimer);
|
|
14316
|
+
this.retryTimer = null;
|
|
14317
|
+
}
|
|
13162
14318
|
}
|
|
13163
14319
|
drain() {
|
|
13164
|
-
if (this.stopped || this.running || !this.pending) {
|
|
14320
|
+
if (this.stopped || this.running || this.retryTimer || !this.pending) {
|
|
13165
14321
|
return;
|
|
13166
14322
|
}
|
|
13167
14323
|
this.pending = false;
|
|
@@ -13169,13 +14325,29 @@ var BackgroundReindexer = class {
|
|
|
13169
14325
|
void this.run();
|
|
13170
14326
|
}
|
|
13171
14327
|
async run() {
|
|
14328
|
+
let retryAfterContention = false;
|
|
13172
14329
|
try {
|
|
13173
14330
|
await this.runIndex();
|
|
14331
|
+
this.retryDelayMs = 50;
|
|
13174
14332
|
} catch (error) {
|
|
13175
|
-
|
|
14333
|
+
if (isTransientIndexLockContention(error)) {
|
|
14334
|
+
this.pending = true;
|
|
14335
|
+
retryAfterContention = true;
|
|
14336
|
+
} else {
|
|
14337
|
+
console.error("[codebase-index] Background reindex failed:", error);
|
|
14338
|
+
}
|
|
13176
14339
|
} finally {
|
|
13177
14340
|
this.running = false;
|
|
13178
|
-
this.
|
|
14341
|
+
if (retryAfterContention && !this.stopped) {
|
|
14342
|
+
const delay = this.retryDelayMs;
|
|
14343
|
+
this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
|
|
14344
|
+
this.retryTimer = setTimeout(() => {
|
|
14345
|
+
this.retryTimer = null;
|
|
14346
|
+
this.drain();
|
|
14347
|
+
}, delay);
|
|
14348
|
+
} else {
|
|
14349
|
+
this.drain();
|
|
14350
|
+
}
|
|
13179
14351
|
}
|
|
13180
14352
|
}
|
|
13181
14353
|
};
|
|
@@ -13209,10 +14381,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
|
|
|
13209
14381
|
return {
|
|
13210
14382
|
fileWatcher,
|
|
13211
14383
|
gitWatcher,
|
|
13212
|
-
|
|
14384
|
+
whenReady() {
|
|
14385
|
+
return fileWatcher.waitUntilReady();
|
|
14386
|
+
},
|
|
14387
|
+
async stop() {
|
|
13213
14388
|
backgroundReindexer.stop();
|
|
13214
|
-
fileWatcher.stop();
|
|
13215
|
-
gitWatcher?.stop();
|
|
14389
|
+
await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
|
|
13216
14390
|
}
|
|
13217
14391
|
};
|
|
13218
14392
|
}
|
|
@@ -13231,7 +14405,7 @@ function getConfigPaths(projectRoot, host, options) {
|
|
|
13231
14405
|
|
|
13232
14406
|
// src/tools/visualize/activity.ts
|
|
13233
14407
|
import { execFileSync } from "child_process";
|
|
13234
|
-
import * as
|
|
14408
|
+
import * as path24 from "path";
|
|
13235
14409
|
function attachRecentActivity(data, projectRoot) {
|
|
13236
14410
|
const activity = readGitActivity(projectRoot);
|
|
13237
14411
|
const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
|
|
@@ -13393,7 +14567,7 @@ function normalizePath3(filePath) {
|
|
|
13393
14567
|
return filePath.replace(/\\/g, "/");
|
|
13394
14568
|
}
|
|
13395
14569
|
function toGitRelativePath(projectRoot, filePath) {
|
|
13396
|
-
const relativePath =
|
|
14570
|
+
const relativePath = path24.isAbsolute(filePath) ? path24.relative(projectRoot, filePath) : filePath;
|
|
13397
14571
|
return normalizePath3(relativePath);
|
|
13398
14572
|
}
|
|
13399
14573
|
|
|
@@ -13651,7 +14825,7 @@ render();
|
|
|
13651
14825
|
}
|
|
13652
14826
|
|
|
13653
14827
|
// src/tools/visualize/transform.ts
|
|
13654
|
-
import * as
|
|
14828
|
+
import * as path25 from "path";
|
|
13655
14829
|
|
|
13656
14830
|
// src/tools/visualize/modules.ts
|
|
13657
14831
|
var MAX_MODULES = 18;
|
|
@@ -13911,7 +15085,7 @@ function transformForVisualization(symbols, edges, options = {}) {
|
|
|
13911
15085
|
filePath: s.filePath,
|
|
13912
15086
|
kind: s.kind,
|
|
13913
15087
|
line: s.startLine,
|
|
13914
|
-
directory:
|
|
15088
|
+
directory: path25.dirname(s.filePath),
|
|
13915
15089
|
moduleId: "",
|
|
13916
15090
|
moduleLabel: ""
|
|
13917
15091
|
}));
|
|
@@ -13939,9 +15113,9 @@ function parseArgs(argv) {
|
|
|
13939
15113
|
let host = "opencode";
|
|
13940
15114
|
for (let i = 2; i < argv.length; i++) {
|
|
13941
15115
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
13942
|
-
project =
|
|
15116
|
+
project = path26.resolve(argv[++i]);
|
|
13943
15117
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
13944
|
-
config =
|
|
15118
|
+
config = path26.resolve(argv[++i]);
|
|
13945
15119
|
} else if (argv[i] === "--host" && argv[i + 1]) {
|
|
13946
15120
|
host = parseHostMode(argv[++i]);
|
|
13947
15121
|
} else if (argv[i] === "--host") {
|
|
@@ -13954,7 +15128,7 @@ function loadCliRawConfig(args) {
|
|
|
13954
15128
|
return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
|
|
13955
15129
|
}
|
|
13956
15130
|
function isCliEntrypoint(moduleUrl, argvPath) {
|
|
13957
|
-
return argvPath !== void 0 &&
|
|
15131
|
+
return argvPath !== void 0 && realpathSync3(fileURLToPath2(moduleUrl)) === realpathSync3(argvPath);
|
|
13958
15132
|
}
|
|
13959
15133
|
function parseVisualizeArgs(argv, cwd) {
|
|
13960
15134
|
let project = cwd;
|
|
@@ -13964,7 +15138,7 @@ function parseVisualizeArgs(argv, cwd) {
|
|
|
13964
15138
|
for (let i = 0; i < argv.length; i++) {
|
|
13965
15139
|
const arg = argv[i];
|
|
13966
15140
|
if (arg === "--project" && argv[i + 1]) {
|
|
13967
|
-
project =
|
|
15141
|
+
project = path26.resolve(argv[++i]);
|
|
13968
15142
|
} else if (arg === "--max" && argv[i + 1]) {
|
|
13969
15143
|
maxNodes = Number(argv[++i]);
|
|
13970
15144
|
} else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
|
|
@@ -14001,8 +15175,8 @@ async function handleVisualizeCommand(argv, cwd) {
|
|
|
14001
15175
|
console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
|
|
14002
15176
|
return 1;
|
|
14003
15177
|
}
|
|
14004
|
-
const outputPath =
|
|
14005
|
-
|
|
15178
|
+
const outputPath = path26.join(os6.tmpdir(), `call-graph-${Date.now()}.html`);
|
|
15179
|
+
writeFileSync7(outputPath, generateVisualizationHtml(vizData), "utf-8");
|
|
14006
15180
|
console.log(`Temporal call graph visualization generated: ${outputPath}`);
|
|
14007
15181
|
console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
|
|
14008
15182
|
console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);
|
|
@@ -14031,7 +15205,7 @@ async function main() {
|
|
|
14031
15205
|
const transport = new StdioServerTransport();
|
|
14032
15206
|
await server.connect(transport);
|
|
14033
15207
|
let watcher = null;
|
|
14034
|
-
const isHomeDir =
|
|
15208
|
+
const isHomeDir = path26.resolve(args.project) === path26.resolve(os6.homedir());
|
|
14035
15209
|
const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
|
|
14036
15210
|
if (config.indexing.autoIndex && isValidProject) {
|
|
14037
15211
|
const indexer = getIndexerForProject(args.project, args.host);
|
|
@@ -14046,14 +15220,25 @@ async function main() {
|
|
|
14046
15220
|
args.config ? { configPath: args.config } : {}
|
|
14047
15221
|
);
|
|
14048
15222
|
}
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
15223
|
+
let shuttingDown = false;
|
|
15224
|
+
const shutdown = async () => {
|
|
15225
|
+
if (shuttingDown) return;
|
|
15226
|
+
shuttingDown = true;
|
|
15227
|
+
try {
|
|
15228
|
+
await watcher?.stop();
|
|
15229
|
+
await server.close();
|
|
15230
|
+
process.exit(0);
|
|
15231
|
+
} catch (error) {
|
|
15232
|
+
console.error("Failed to stop MCP server cleanly:", error);
|
|
15233
|
+
process.exit(1);
|
|
15234
|
+
}
|
|
14054
15235
|
};
|
|
14055
|
-
process.on("SIGINT",
|
|
14056
|
-
|
|
15236
|
+
process.on("SIGINT", () => {
|
|
15237
|
+
void shutdown();
|
|
15238
|
+
});
|
|
15239
|
+
process.on("SIGTERM", () => {
|
|
15240
|
+
void shutdown();
|
|
15241
|
+
});
|
|
14057
15242
|
}
|
|
14058
15243
|
function handleMainError(error) {
|
|
14059
15244
|
if (error instanceof Error && error.message.startsWith("Invalid host mode")) {
|