opencode-codebase-index 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -6
- package/commands/peek.md +4 -0
- package/commands/search.md +4 -0
- package/dist/cli.cjs +1921 -640
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1919 -628
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1856 -580
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1855 -569
- package/dist/index.js.map +1 -1
- package/dist/pi-extension.cjs +1855 -610
- package/dist/pi-extension.cjs.map +1 -1
- package/dist/pi-extension.js +1861 -606
- package/dist/pi-extension.js.map +1 -1
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +11 -8
- package/skill/SKILL.md +5 -0
package/dist/index.js
CHANGED
|
@@ -490,7 +490,7 @@ var require_ignore = __commonJS({
|
|
|
490
490
|
// path matching.
|
|
491
491
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
492
492
|
// @returns {TestResult} true if a file is ignored
|
|
493
|
-
test(
|
|
493
|
+
test(path24, checkUnignored, mode) {
|
|
494
494
|
let ignored = false;
|
|
495
495
|
let unignored = false;
|
|
496
496
|
let matchedRule;
|
|
@@ -499,7 +499,7 @@ var require_ignore = __commonJS({
|
|
|
499
499
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
500
500
|
return;
|
|
501
501
|
}
|
|
502
|
-
const matched = rule[mode].test(
|
|
502
|
+
const matched = rule[mode].test(path24);
|
|
503
503
|
if (!matched) {
|
|
504
504
|
return;
|
|
505
505
|
}
|
|
@@ -520,17 +520,17 @@ var require_ignore = __commonJS({
|
|
|
520
520
|
var throwError = (message, Ctor) => {
|
|
521
521
|
throw new Ctor(message);
|
|
522
522
|
};
|
|
523
|
-
var checkPath = (
|
|
524
|
-
if (!isString(
|
|
523
|
+
var checkPath = (path24, originalPath, doThrow) => {
|
|
524
|
+
if (!isString(path24)) {
|
|
525
525
|
return doThrow(
|
|
526
526
|
`path must be a string, but got \`${originalPath}\``,
|
|
527
527
|
TypeError
|
|
528
528
|
);
|
|
529
529
|
}
|
|
530
|
-
if (!
|
|
530
|
+
if (!path24) {
|
|
531
531
|
return doThrow(`path must not be empty`, TypeError);
|
|
532
532
|
}
|
|
533
|
-
if (checkPath.isNotRelative(
|
|
533
|
+
if (checkPath.isNotRelative(path24)) {
|
|
534
534
|
const r = "`path.relative()`d";
|
|
535
535
|
return doThrow(
|
|
536
536
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -539,7 +539,7 @@ var require_ignore = __commonJS({
|
|
|
539
539
|
}
|
|
540
540
|
return true;
|
|
541
541
|
};
|
|
542
|
-
var isNotRelative = (
|
|
542
|
+
var isNotRelative = (path24) => REGEX_TEST_INVALID_PATH.test(path24);
|
|
543
543
|
checkPath.isNotRelative = isNotRelative;
|
|
544
544
|
checkPath.convert = (p) => p;
|
|
545
545
|
var Ignore2 = class {
|
|
@@ -569,19 +569,19 @@ var require_ignore = __commonJS({
|
|
|
569
569
|
}
|
|
570
570
|
// @returns {TestResult}
|
|
571
571
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
572
|
-
const
|
|
572
|
+
const path24 = originalPath && checkPath.convert(originalPath);
|
|
573
573
|
checkPath(
|
|
574
|
-
|
|
574
|
+
path24,
|
|
575
575
|
originalPath,
|
|
576
576
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
577
577
|
);
|
|
578
|
-
return this._t(
|
|
578
|
+
return this._t(path24, cache, checkUnignored, slices);
|
|
579
579
|
}
|
|
580
|
-
checkIgnore(
|
|
581
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
582
|
-
return this.test(
|
|
580
|
+
checkIgnore(path24) {
|
|
581
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path24)) {
|
|
582
|
+
return this.test(path24);
|
|
583
583
|
}
|
|
584
|
-
const slices =
|
|
584
|
+
const slices = path24.split(SLASH2).filter(Boolean);
|
|
585
585
|
slices.pop();
|
|
586
586
|
if (slices.length) {
|
|
587
587
|
const parent = this._t(
|
|
@@ -594,18 +594,18 @@ var require_ignore = __commonJS({
|
|
|
594
594
|
return parent;
|
|
595
595
|
}
|
|
596
596
|
}
|
|
597
|
-
return this._rules.test(
|
|
597
|
+
return this._rules.test(path24, false, MODE_CHECK_IGNORE);
|
|
598
598
|
}
|
|
599
|
-
_t(
|
|
600
|
-
if (
|
|
601
|
-
return cache[
|
|
599
|
+
_t(path24, cache, checkUnignored, slices) {
|
|
600
|
+
if (path24 in cache) {
|
|
601
|
+
return cache[path24];
|
|
602
602
|
}
|
|
603
603
|
if (!slices) {
|
|
604
|
-
slices =
|
|
604
|
+
slices = path24.split(SLASH2).filter(Boolean);
|
|
605
605
|
}
|
|
606
606
|
slices.pop();
|
|
607
607
|
if (!slices.length) {
|
|
608
|
-
return cache[
|
|
608
|
+
return cache[path24] = this._rules.test(path24, checkUnignored, MODE_IGNORE);
|
|
609
609
|
}
|
|
610
610
|
const parent = this._t(
|
|
611
611
|
slices.join(SLASH2) + SLASH2,
|
|
@@ -613,29 +613,29 @@ var require_ignore = __commonJS({
|
|
|
613
613
|
checkUnignored,
|
|
614
614
|
slices
|
|
615
615
|
);
|
|
616
|
-
return cache[
|
|
616
|
+
return cache[path24] = parent.ignored ? parent : this._rules.test(path24, checkUnignored, MODE_IGNORE);
|
|
617
617
|
}
|
|
618
|
-
ignores(
|
|
619
|
-
return this._test(
|
|
618
|
+
ignores(path24) {
|
|
619
|
+
return this._test(path24, this._ignoreCache, false).ignored;
|
|
620
620
|
}
|
|
621
621
|
createFilter() {
|
|
622
|
-
return (
|
|
622
|
+
return (path24) => !this.ignores(path24);
|
|
623
623
|
}
|
|
624
624
|
filter(paths) {
|
|
625
625
|
return makeArray(paths).filter(this.createFilter());
|
|
626
626
|
}
|
|
627
627
|
// @returns {TestResult}
|
|
628
|
-
test(
|
|
629
|
-
return this._test(
|
|
628
|
+
test(path24) {
|
|
629
|
+
return this._test(path24, this._testCache, true);
|
|
630
630
|
}
|
|
631
631
|
};
|
|
632
632
|
var factory = (options) => new Ignore2(options);
|
|
633
|
-
var isPathValid = (
|
|
633
|
+
var isPathValid = (path24) => checkPath(path24 && checkPath.convert(path24), path24, RETURN_FALSE);
|
|
634
634
|
var setupWindows = () => {
|
|
635
635
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
636
636
|
checkPath.convert = makePosix;
|
|
637
637
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
638
|
-
checkPath.isNotRelative = (
|
|
638
|
+
checkPath.isNotRelative = (path24) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path24) || isNotRelative(path24);
|
|
639
639
|
};
|
|
640
640
|
if (
|
|
641
641
|
// Detect `process` so that it can run in browsers.
|
|
@@ -651,16 +651,16 @@ var require_ignore = __commonJS({
|
|
|
651
651
|
});
|
|
652
652
|
|
|
653
653
|
// src/index.ts
|
|
654
|
-
import * as
|
|
655
|
-
import * as
|
|
654
|
+
import * as os6 from "os";
|
|
655
|
+
import * as path23 from "path";
|
|
656
656
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
657
657
|
|
|
658
658
|
// src/config/constants.ts
|
|
659
659
|
var DEFAULT_INCLUDE = [
|
|
660
|
-
"**/*.{ts,tsx,js,jsx,mjs,cjs}",
|
|
660
|
+
"**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
661
661
|
"**/*.{py,pyi}",
|
|
662
|
-
"**/*.{go,rs,java,kt,scala}",
|
|
663
|
-
"**/*.{c,cpp,cc,h,hpp}",
|
|
662
|
+
"**/*.{go,rs,java,cs,kt,scala}",
|
|
663
|
+
"**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
|
|
664
664
|
"**/*.{rb,php,inc,swift}",
|
|
665
665
|
"**/*.{cls,trigger}",
|
|
666
666
|
"**/*.{vue,svelte,astro}",
|
|
@@ -785,7 +785,8 @@ function getDefaultIndexingConfig() {
|
|
|
785
785
|
requireProjectMarker: true,
|
|
786
786
|
maxDepth: 5,
|
|
787
787
|
maxFilesPerDirectory: 100,
|
|
788
|
-
fallbackToTextOnMaxChunks: true
|
|
788
|
+
fallbackToTextOnMaxChunks: true,
|
|
789
|
+
gitBlame: { enabled: false }
|
|
789
790
|
};
|
|
790
791
|
}
|
|
791
792
|
function getDefaultSearchConfig() {
|
|
@@ -909,7 +910,10 @@ function parseConfig(raw) {
|
|
|
909
910
|
requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
|
|
910
911
|
maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
|
|
911
912
|
maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
|
|
912
|
-
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
|
|
913
|
+
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
|
|
914
|
+
gitBlame: {
|
|
915
|
+
enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
|
|
916
|
+
}
|
|
913
917
|
};
|
|
914
918
|
const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
|
|
915
919
|
const search = {
|
|
@@ -1240,6 +1244,9 @@ function getHostProjectIndexRelativePath(host) {
|
|
|
1240
1244
|
function hasHostProjectConfig(projectRoot, host) {
|
|
1241
1245
|
return existsSync3(path3.join(projectRoot, getProjectConfigRelativePath(host)));
|
|
1242
1246
|
}
|
|
1247
|
+
function hasHostGlobalConfig(host) {
|
|
1248
|
+
return existsSync3(getGlobalConfigPath(host));
|
|
1249
|
+
}
|
|
1243
1250
|
function getGlobalIndexPath(host = "opencode") {
|
|
1244
1251
|
switch (host) {
|
|
1245
1252
|
case "opencode":
|
|
@@ -1279,6 +1286,9 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
1279
1286
|
return hostIndexPath;
|
|
1280
1287
|
}
|
|
1281
1288
|
if (host !== "opencode") {
|
|
1289
|
+
if (hasHostGlobalConfig(host)) {
|
|
1290
|
+
return hostIndexPath;
|
|
1291
|
+
}
|
|
1282
1292
|
const legacyIndexPath = getGlobalIndexPath("opencode");
|
|
1283
1293
|
if (existsSync3(legacyIndexPath)) {
|
|
1284
1294
|
return legacyIndexPath;
|
|
@@ -1577,16 +1587,442 @@ function loadMergedConfig(projectRoot, host = "opencode") {
|
|
|
1577
1587
|
return merged;
|
|
1578
1588
|
}
|
|
1579
1589
|
|
|
1590
|
+
// src/indexer/index-lock.ts
|
|
1591
|
+
import { randomUUID } from "crypto";
|
|
1592
|
+
import {
|
|
1593
|
+
existsSync as existsSync5,
|
|
1594
|
+
lstatSync,
|
|
1595
|
+
mkdirSync as mkdirSync2,
|
|
1596
|
+
readFileSync as readFileSync4,
|
|
1597
|
+
readdirSync as readdirSync2,
|
|
1598
|
+
realpathSync,
|
|
1599
|
+
renameSync,
|
|
1600
|
+
rmSync,
|
|
1601
|
+
writeFileSync as writeFileSync2
|
|
1602
|
+
} from "fs";
|
|
1603
|
+
import * as os2 from "os";
|
|
1604
|
+
import * as path7 from "path";
|
|
1605
|
+
var OWNER_FILE_NAME = "owner.json";
|
|
1606
|
+
var RECLAIM_DIRECTORY_NAME = "reclaiming";
|
|
1607
|
+
var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
|
|
1608
|
+
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;
|
|
1609
|
+
var VALID_OPERATIONS = /* @__PURE__ */ new Set([
|
|
1610
|
+
"initialize",
|
|
1611
|
+
"index",
|
|
1612
|
+
"force-index",
|
|
1613
|
+
"clear",
|
|
1614
|
+
"health-check",
|
|
1615
|
+
"retry-failed-batches",
|
|
1616
|
+
"recovery"
|
|
1617
|
+
]);
|
|
1618
|
+
var temporaryCounter = 0;
|
|
1619
|
+
function getErrorCode(error) {
|
|
1620
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
1621
|
+
}
|
|
1622
|
+
function retryTransientFilesystemOperation(operation) {
|
|
1623
|
+
let lastError;
|
|
1624
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1625
|
+
try {
|
|
1626
|
+
operation();
|
|
1627
|
+
return;
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
lastError = error;
|
|
1630
|
+
const code = getErrorCode(error);
|
|
1631
|
+
if (code !== "EBUSY" && code !== "EPERM") throw error;
|
|
1632
|
+
if (attempt < 2) {
|
|
1633
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
throw lastError;
|
|
1638
|
+
}
|
|
1639
|
+
function parseOwner(value) {
|
|
1640
|
+
if (typeof value !== "object" || value === null) return null;
|
|
1641
|
+
const candidate = value;
|
|
1642
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
1643
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
1644
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
1645
|
+
if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
|
|
1646
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
1647
|
+
return candidate;
|
|
1648
|
+
}
|
|
1649
|
+
function parseReclaimOwner(value) {
|
|
1650
|
+
if (typeof value !== "object" || value === null) return null;
|
|
1651
|
+
const candidate = value;
|
|
1652
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
1653
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
1654
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
1655
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
1656
|
+
if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
|
|
1657
|
+
return candidate;
|
|
1658
|
+
}
|
|
1659
|
+
function readJsonDirectory(directoryPath, parser) {
|
|
1660
|
+
try {
|
|
1661
|
+
return parser(JSON.parse(readFileSync4(path7.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
|
|
1662
|
+
} catch {
|
|
1663
|
+
return null;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
function readDirectoryOwner(lockPath) {
|
|
1667
|
+
return readJsonDirectory(lockPath, parseOwner);
|
|
1668
|
+
}
|
|
1669
|
+
function readReclaimOwner(markerPath) {
|
|
1670
|
+
return readJsonDirectory(markerPath, parseReclaimOwner);
|
|
1671
|
+
}
|
|
1672
|
+
function readRecoveryOwner(markerPath) {
|
|
1673
|
+
return readDirectoryOwner(markerPath);
|
|
1674
|
+
}
|
|
1675
|
+
function readLegacyOwner(lockPath) {
|
|
1676
|
+
try {
|
|
1677
|
+
const parsed = JSON.parse(readFileSync4(lockPath, "utf-8"));
|
|
1678
|
+
if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
|
|
1679
|
+
if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
|
|
1680
|
+
return {
|
|
1681
|
+
pid: Number(parsed.pid),
|
|
1682
|
+
hostname: typeof parsed.hostname === "string" ? parsed.hostname : os2.hostname(),
|
|
1683
|
+
startedAt: parsed.startedAt,
|
|
1684
|
+
operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
|
|
1685
|
+
token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
|
|
1686
|
+
};
|
|
1687
|
+
} catch {
|
|
1688
|
+
return null;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
function getOwnerLiveness(owner) {
|
|
1692
|
+
if (owner.hostname !== os2.hostname()) return "unknown";
|
|
1693
|
+
try {
|
|
1694
|
+
process.kill(owner.pid, 0);
|
|
1695
|
+
return "alive";
|
|
1696
|
+
} catch (error) {
|
|
1697
|
+
const code = getErrorCode(error);
|
|
1698
|
+
if (code === "ESRCH") return "dead";
|
|
1699
|
+
if (code === "EPERM") return "alive";
|
|
1700
|
+
return "unknown";
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function sameOwner(left, right) {
|
|
1704
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
|
|
1705
|
+
}
|
|
1706
|
+
function sameReclaimOwner(left, right) {
|
|
1707
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
|
|
1708
|
+
}
|
|
1709
|
+
function publishJsonDirectory(finalPath, value) {
|
|
1710
|
+
const candidatePath = `${finalPath}.candidate.${process.pid}.${randomUUID()}`;
|
|
1711
|
+
mkdirSync2(candidatePath, { mode: 448 });
|
|
1712
|
+
try {
|
|
1713
|
+
writeFileSync2(path7.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
|
|
1714
|
+
encoding: "utf-8",
|
|
1715
|
+
flag: "wx",
|
|
1716
|
+
mode: 384
|
|
1717
|
+
});
|
|
1718
|
+
if (existsSync5(finalPath)) return false;
|
|
1719
|
+
try {
|
|
1720
|
+
renameSync(candidatePath, finalPath);
|
|
1721
|
+
return true;
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
if (existsSync5(finalPath)) return false;
|
|
1724
|
+
throw error;
|
|
1725
|
+
}
|
|
1726
|
+
} finally {
|
|
1727
|
+
if (existsSync5(candidatePath)) rmSync(candidatePath, { recursive: true, force: true });
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
function createOwner(operation) {
|
|
1731
|
+
return {
|
|
1732
|
+
pid: process.pid,
|
|
1733
|
+
hostname: os2.hostname(),
|
|
1734
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1735
|
+
operation,
|
|
1736
|
+
token: randomUUID()
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function recoveryMarkerPath(indexPath, owner) {
|
|
1740
|
+
return path7.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
|
|
1741
|
+
}
|
|
1742
|
+
function publishRecoveryMarker(indexPath, owner) {
|
|
1743
|
+
const markerPath = recoveryMarkerPath(indexPath, owner);
|
|
1744
|
+
if (!publishJsonDirectory(markerPath, owner)) {
|
|
1745
|
+
const markerOwner = readRecoveryOwner(markerPath);
|
|
1746
|
+
if (!markerOwner || !sameOwner(markerOwner, owner)) {
|
|
1747
|
+
throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
return markerPath;
|
|
1751
|
+
}
|
|
1752
|
+
function getPendingRecoveries(indexPath) {
|
|
1753
|
+
const recoveries = [];
|
|
1754
|
+
const markerNames = readdirSync2(indexPath).filter((name) => {
|
|
1755
|
+
if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
|
|
1756
|
+
return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
|
|
1757
|
+
}).sort();
|
|
1758
|
+
for (const markerName of markerNames) {
|
|
1759
|
+
const markerPath = path7.join(indexPath, markerName);
|
|
1760
|
+
const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
|
|
1761
|
+
let markerStats;
|
|
1762
|
+
try {
|
|
1763
|
+
markerStats = lstatSync(markerPath);
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
1766
|
+
throw error;
|
|
1767
|
+
}
|
|
1768
|
+
if (!markerStats.isDirectory()) {
|
|
1769
|
+
throw new IndexLockContentionError(markerPath, null, "unknown-owner");
|
|
1770
|
+
}
|
|
1771
|
+
const owner = readRecoveryOwner(markerPath);
|
|
1772
|
+
if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
|
|
1773
|
+
throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
|
|
1774
|
+
}
|
|
1775
|
+
recoveries.push({ owner, markerPath });
|
|
1776
|
+
}
|
|
1777
|
+
recoveries.sort((left, right) => {
|
|
1778
|
+
const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
|
|
1779
|
+
return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
|
|
1780
|
+
});
|
|
1781
|
+
return recoveries;
|
|
1782
|
+
}
|
|
1783
|
+
function cleanupDeadPublicationCandidates(indexPath) {
|
|
1784
|
+
const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
|
|
1785
|
+
for (const entry of readdirSync2(indexPath)) {
|
|
1786
|
+
const match = candidatePattern.exec(entry);
|
|
1787
|
+
if (!match) continue;
|
|
1788
|
+
const pid = Number(match[1]);
|
|
1789
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
1790
|
+
if (getOwnerLiveness({ pid, hostname: os2.hostname() }) !== "dead") continue;
|
|
1791
|
+
rmSync(path7.join(indexPath, entry), { recursive: true, force: true });
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
function removeDeadReclaimMarker(lockPath, expectedOwner) {
|
|
1795
|
+
const markerPath = path7.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
1796
|
+
const marker = readReclaimOwner(markerPath);
|
|
1797
|
+
if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
|
|
1798
|
+
return false;
|
|
1799
|
+
}
|
|
1800
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
1801
|
+
if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${randomUUID()}`;
|
|
1805
|
+
try {
|
|
1806
|
+
renameSync(markerPath, claimedMarkerPath);
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
1809
|
+
throw error;
|
|
1810
|
+
}
|
|
1811
|
+
const claimedMarker = readReclaimOwner(claimedMarkerPath);
|
|
1812
|
+
const ownerAfterClaim = readDirectoryOwner(lockPath);
|
|
1813
|
+
if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
|
|
1814
|
+
if (!existsSync5(markerPath) && existsSync5(claimedMarkerPath)) {
|
|
1815
|
+
renameSync(claimedMarkerPath, markerPath);
|
|
1816
|
+
}
|
|
1817
|
+
return false;
|
|
1818
|
+
}
|
|
1819
|
+
rmSync(claimedMarkerPath, { recursive: true, force: true });
|
|
1820
|
+
return true;
|
|
1821
|
+
}
|
|
1822
|
+
function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
|
|
1823
|
+
const reclaimPath = path7.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
1824
|
+
const reclaimOwner = {
|
|
1825
|
+
pid: process.pid,
|
|
1826
|
+
hostname: os2.hostname(),
|
|
1827
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1828
|
+
token: randomUUID(),
|
|
1829
|
+
expectedOwnerToken: expectedOwner.token
|
|
1830
|
+
};
|
|
1831
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1832
|
+
if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
|
|
1833
|
+
if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
try {
|
|
1837
|
+
const currentReclaimer = readReclaimOwner(reclaimPath);
|
|
1838
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
1839
|
+
if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
publishRecoveryMarker(indexPath, expectedOwner);
|
|
1843
|
+
const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
|
|
1844
|
+
const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
|
|
1845
|
+
if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
|
|
1849
|
+
renameSync(lockPath, quarantinePath);
|
|
1850
|
+
rmSync(quarantinePath, { recursive: true, force: true });
|
|
1851
|
+
return true;
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
1854
|
+
throw error;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
var IndexLockContentionError = class extends Error {
|
|
1858
|
+
constructor(lockPath, owner, reason) {
|
|
1859
|
+
const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
|
|
1860
|
+
super(`Index mutation already in progress: ${ownerDescription}`);
|
|
1861
|
+
this.lockPath = lockPath;
|
|
1862
|
+
this.owner = owner;
|
|
1863
|
+
this.reason = reason;
|
|
1864
|
+
this.name = "IndexLockContentionError";
|
|
1865
|
+
}
|
|
1866
|
+
lockPath;
|
|
1867
|
+
owner;
|
|
1868
|
+
reason;
|
|
1869
|
+
code = "INDEX_BUSY";
|
|
1870
|
+
};
|
|
1871
|
+
function isIndexLockContentionError(error) {
|
|
1872
|
+
return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
|
|
1873
|
+
}
|
|
1874
|
+
function isTransientIndexLockContention(error) {
|
|
1875
|
+
if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
|
|
1876
|
+
return error.reason === "active" || error.reason === "reclaiming";
|
|
1877
|
+
}
|
|
1878
|
+
function acquireIndexLock(indexPath, operation) {
|
|
1879
|
+
mkdirSync2(indexPath, { recursive: true });
|
|
1880
|
+
const canonicalIndexPath = realpathSync.native(indexPath);
|
|
1881
|
+
const lockPath = path7.join(canonicalIndexPath, "indexing.lock");
|
|
1882
|
+
cleanupDeadPublicationCandidates(canonicalIndexPath);
|
|
1883
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
1884
|
+
const owner = createOwner(operation);
|
|
1885
|
+
if (publishJsonDirectory(lockPath, owner)) {
|
|
1886
|
+
const lease = {
|
|
1887
|
+
canonicalIndexPath,
|
|
1888
|
+
lockPath,
|
|
1889
|
+
owner,
|
|
1890
|
+
recoveries: []
|
|
1891
|
+
};
|
|
1892
|
+
try {
|
|
1893
|
+
lease.recoveries = getPendingRecoveries(canonicalIndexPath);
|
|
1894
|
+
return lease;
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
releaseIndexLock(lease);
|
|
1897
|
+
throw error;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
let stats;
|
|
1901
|
+
try {
|
|
1902
|
+
stats = lstatSync(lockPath);
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
1905
|
+
throw error;
|
|
1906
|
+
}
|
|
1907
|
+
if (!stats.isDirectory()) {
|
|
1908
|
+
const legacyOwner = readLegacyOwner(lockPath);
|
|
1909
|
+
throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
|
|
1910
|
+
}
|
|
1911
|
+
const existingOwner = readDirectoryOwner(lockPath);
|
|
1912
|
+
if (!existingOwner) {
|
|
1913
|
+
throw new IndexLockContentionError(lockPath, null, "unknown-owner");
|
|
1914
|
+
}
|
|
1915
|
+
const liveness = getOwnerLiveness(existingOwner);
|
|
1916
|
+
if (liveness !== "dead") {
|
|
1917
|
+
throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
|
|
1918
|
+
}
|
|
1919
|
+
if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
|
|
1920
|
+
throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
throw new IndexLockContentionError(lockPath, null, "reclaiming");
|
|
1924
|
+
}
|
|
1925
|
+
function releaseIndexLock(lease) {
|
|
1926
|
+
const currentOwner = readDirectoryOwner(lease.lockPath);
|
|
1927
|
+
if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
|
|
1928
|
+
const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
|
|
1929
|
+
try {
|
|
1930
|
+
retryTransientFilesystemOperation(() => renameSync(lease.lockPath, releasePath));
|
|
1931
|
+
} catch (error) {
|
|
1932
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
1933
|
+
throw error;
|
|
1934
|
+
}
|
|
1935
|
+
const claimedOwner = readDirectoryOwner(releasePath);
|
|
1936
|
+
if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
|
|
1937
|
+
if (!existsSync5(lease.lockPath) && existsSync5(releasePath)) {
|
|
1938
|
+
renameSync(releasePath, lease.lockPath);
|
|
1939
|
+
}
|
|
1940
|
+
return false;
|
|
1941
|
+
}
|
|
1942
|
+
try {
|
|
1943
|
+
retryTransientFilesystemOperation(() => rmSync(releasePath, { recursive: true, force: true }));
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
|
|
1946
|
+
}
|
|
1947
|
+
return true;
|
|
1948
|
+
}
|
|
1949
|
+
async function withIndexLock(indexPath, operation, callback, options = {}) {
|
|
1950
|
+
const lease = acquireIndexLock(indexPath, operation);
|
|
1951
|
+
let result;
|
|
1952
|
+
let callbackError;
|
|
1953
|
+
let callbackFailed = false;
|
|
1954
|
+
try {
|
|
1955
|
+
result = await callback(lease);
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
callbackFailed = true;
|
|
1958
|
+
callbackError = error;
|
|
1959
|
+
}
|
|
1960
|
+
if (!callbackFailed && options.completeRecoveries !== false) {
|
|
1961
|
+
try {
|
|
1962
|
+
completeLeaseRecovery(lease);
|
|
1963
|
+
} catch (error) {
|
|
1964
|
+
callbackFailed = true;
|
|
1965
|
+
callbackError = error;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
let releaseError;
|
|
1969
|
+
try {
|
|
1970
|
+
if (!releaseIndexLock(lease)) {
|
|
1971
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
1972
|
+
}
|
|
1973
|
+
} catch (error) {
|
|
1974
|
+
releaseError = error;
|
|
1975
|
+
}
|
|
1976
|
+
if (releaseError !== void 0) {
|
|
1977
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
1978
|
+
throw releaseError;
|
|
1979
|
+
}
|
|
1980
|
+
if (callbackFailed) throw callbackError;
|
|
1981
|
+
return result;
|
|
1982
|
+
}
|
|
1983
|
+
function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
|
|
1984
|
+
if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
|
|
1985
|
+
temporaryCounter += 1;
|
|
1986
|
+
return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
|
|
1987
|
+
}
|
|
1988
|
+
function removeLeaseTemporaryPath(temporaryPath) {
|
|
1989
|
+
if (existsSync5(temporaryPath)) rmSync(temporaryPath, { recursive: true, force: true });
|
|
1990
|
+
}
|
|
1991
|
+
function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
|
|
1992
|
+
for (const targetPath of backupTargets) {
|
|
1993
|
+
const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
|
|
1994
|
+
if (!existsSync5(backupPath)) continue;
|
|
1995
|
+
if (existsSync5(targetPath)) rmSync(targetPath, { recursive: true, force: true });
|
|
1996
|
+
renameSync(backupPath, targetPath);
|
|
1997
|
+
}
|
|
1998
|
+
const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
|
|
1999
|
+
for (const entry of readdirSync2(indexPath)) {
|
|
2000
|
+
if (!entry.includes(temporaryOwnerMarker)) continue;
|
|
2001
|
+
rmSync(path7.join(indexPath, entry), { recursive: true, force: true });
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
function completeLeaseRecovery(lease) {
|
|
2005
|
+
for (const recovery of lease.recoveries) {
|
|
2006
|
+
const markerOwner = readRecoveryOwner(recovery.markerPath);
|
|
2007
|
+
if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
|
|
2008
|
+
throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
for (const recovery of lease.recoveries) {
|
|
2012
|
+
rmSync(recovery.markerPath, { recursive: true, force: true });
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
1580
2016
|
// src/tools/operations.ts
|
|
1581
|
-
import { existsSync as
|
|
1582
|
-
import * as
|
|
2017
|
+
import { existsSync as existsSync10, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
2018
|
+
import * as path16 from "path";
|
|
1583
2019
|
|
|
1584
2020
|
// src/indexer/index.ts
|
|
1585
|
-
import { existsSync as
|
|
1586
|
-
import * as
|
|
2021
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync, mkdirSync as mkdirSync3, promises as fsPromises2 } from "fs";
|
|
2022
|
+
import * as path13 from "path";
|
|
1587
2023
|
import { performance as performance2 } from "perf_hooks";
|
|
1588
|
-
import { execFile as
|
|
1589
|
-
import { promisify as
|
|
2024
|
+
import { execFile as execFile3 } from "child_process";
|
|
2025
|
+
import { promisify as promisify3 } from "util";
|
|
1590
2026
|
|
|
1591
2027
|
// node_modules/eventemitter3/index.mjs
|
|
1592
2028
|
var import_index = __toESM(require_eventemitter3(), 1);
|
|
@@ -2633,17 +3069,17 @@ async function pRetry(input, options = {}) {
|
|
|
2633
3069
|
}
|
|
2634
3070
|
|
|
2635
3071
|
// src/embeddings/detector.ts
|
|
2636
|
-
import { existsSync as
|
|
2637
|
-
import * as
|
|
2638
|
-
import * as
|
|
3072
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
3073
|
+
import * as path8 from "path";
|
|
3074
|
+
import * as os3 from "os";
|
|
2639
3075
|
function getOpenCodeAuthPath() {
|
|
2640
|
-
return
|
|
3076
|
+
return path8.join(os3.homedir(), ".local", "share", "opencode", "auth.json");
|
|
2641
3077
|
}
|
|
2642
3078
|
function loadOpenCodeAuth() {
|
|
2643
3079
|
const authPath = getOpenCodeAuthPath();
|
|
2644
3080
|
try {
|
|
2645
|
-
if (
|
|
2646
|
-
return JSON.parse(
|
|
3081
|
+
if (existsSync6(authPath)) {
|
|
3082
|
+
return JSON.parse(readFileSync5(authPath, "utf-8"));
|
|
2647
3083
|
}
|
|
2648
3084
|
} catch {
|
|
2649
3085
|
}
|
|
@@ -2865,17 +3301,17 @@ function validateExternalUrl(urlString) {
|
|
|
2865
3301
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2866
3302
|
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2867
3303
|
}
|
|
2868
|
-
const
|
|
2869
|
-
if (BLOCKED_HOSTNAMES.has(
|
|
2870
|
-
return { valid: false, reason: `Blocked: cloud metadata service (${
|
|
3304
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
3305
|
+
if (BLOCKED_HOSTNAMES.has(hostname2)) {
|
|
3306
|
+
return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
|
|
2871
3307
|
}
|
|
2872
3308
|
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2873
|
-
if (pattern.test(
|
|
2874
|
-
return { valid: false, reason: `Blocked: cloud metadata IP (${
|
|
3309
|
+
if (pattern.test(hostname2)) {
|
|
3310
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
|
|
2875
3311
|
}
|
|
2876
3312
|
}
|
|
2877
|
-
if (/^169\.254\./.test(
|
|
2878
|
-
return { valid: false, reason: `Blocked: link-local address (${
|
|
3313
|
+
if (/^169\.254\./.test(hostname2)) {
|
|
3314
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
|
|
2879
3315
|
}
|
|
2880
3316
|
return { valid: true };
|
|
2881
3317
|
}
|
|
@@ -3346,8 +3782,8 @@ var SiliconFlowReranker = class {
|
|
|
3346
3782
|
|
|
3347
3783
|
// src/utils/files.ts
|
|
3348
3784
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
3349
|
-
import { existsSync as
|
|
3350
|
-
import * as
|
|
3785
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, promises as fsPromises } from "fs";
|
|
3786
|
+
import * as path9 from "path";
|
|
3351
3787
|
var PROJECT_MARKERS = [
|
|
3352
3788
|
".git",
|
|
3353
3789
|
"package.json",
|
|
@@ -3367,7 +3803,7 @@ var PROJECT_MARKERS = [
|
|
|
3367
3803
|
];
|
|
3368
3804
|
function hasProjectMarker(projectRoot) {
|
|
3369
3805
|
for (const marker of PROJECT_MARKERS) {
|
|
3370
|
-
if (
|
|
3806
|
+
if (existsSync7(path9.join(projectRoot, marker))) {
|
|
3371
3807
|
return true;
|
|
3372
3808
|
}
|
|
3373
3809
|
}
|
|
@@ -3394,16 +3830,16 @@ function createIgnoreFilter(projectRoot) {
|
|
|
3394
3830
|
"**/*build*/**"
|
|
3395
3831
|
];
|
|
3396
3832
|
ig.add(defaultIgnores);
|
|
3397
|
-
const gitignorePath =
|
|
3398
|
-
if (
|
|
3399
|
-
const gitignoreContent =
|
|
3833
|
+
const gitignorePath = path9.join(projectRoot, ".gitignore");
|
|
3834
|
+
if (existsSync7(gitignorePath)) {
|
|
3835
|
+
const gitignoreContent = readFileSync6(gitignorePath, "utf-8");
|
|
3400
3836
|
ig.add(gitignoreContent);
|
|
3401
3837
|
}
|
|
3402
3838
|
return ig;
|
|
3403
3839
|
}
|
|
3404
3840
|
function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatterns, ignoreFilter) {
|
|
3405
|
-
const relativePath =
|
|
3406
|
-
if (hasFilteredPathSegment(relativePath,
|
|
3841
|
+
const relativePath = path9.relative(projectRoot, filePath);
|
|
3842
|
+
if (hasFilteredPathSegment(relativePath, path9.sep)) {
|
|
3407
3843
|
return false;
|
|
3408
3844
|
}
|
|
3409
3845
|
if (ignoreFilter.ignores(relativePath)) {
|
|
@@ -3441,8 +3877,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
|
|
|
3441
3877
|
const filesInDir = [];
|
|
3442
3878
|
const subdirs = [];
|
|
3443
3879
|
for (const entry of entries) {
|
|
3444
|
-
const fullPath =
|
|
3445
|
-
const relativePath =
|
|
3880
|
+
const fullPath = path9.join(dir, entry.name);
|
|
3881
|
+
const relativePath = path9.relative(projectRoot, fullPath);
|
|
3446
3882
|
if (isHiddenPathSegment(entry.name)) {
|
|
3447
3883
|
if (entry.isDirectory()) {
|
|
3448
3884
|
skipped.push({ path: relativePath, reason: "excluded" });
|
|
@@ -3491,7 +3927,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
|
|
|
3491
3927
|
yield f;
|
|
3492
3928
|
}
|
|
3493
3929
|
for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
|
|
3494
|
-
skipped.push({ path:
|
|
3930
|
+
skipped.push({ path: path9.relative(projectRoot, filesInDir[i].path), reason: "excluded" });
|
|
3495
3931
|
}
|
|
3496
3932
|
const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
|
|
3497
3933
|
if (canRecurse) {
|
|
@@ -3531,8 +3967,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
|
|
|
3531
3967
|
if (additionalRoots && additionalRoots.length > 0) {
|
|
3532
3968
|
const normalizedRoots = /* @__PURE__ */ new Set();
|
|
3533
3969
|
for (const kbRoot of additionalRoots) {
|
|
3534
|
-
const resolved =
|
|
3535
|
-
|
|
3970
|
+
const resolved = path9.normalize(
|
|
3971
|
+
path9.isAbsolute(kbRoot) ? kbRoot : path9.resolve(projectRoot, kbRoot)
|
|
3536
3972
|
);
|
|
3537
3973
|
normalizedRoots.add(resolved);
|
|
3538
3974
|
}
|
|
@@ -3939,13 +4375,13 @@ function initializeLogger(config) {
|
|
|
3939
4375
|
}
|
|
3940
4376
|
|
|
3941
4377
|
// src/native/index.ts
|
|
3942
|
-
import * as
|
|
3943
|
-
import * as
|
|
4378
|
+
import * as path10 from "path";
|
|
4379
|
+
import * as os4 from "os";
|
|
3944
4380
|
import * as module from "module";
|
|
3945
4381
|
import { fileURLToPath } from "url";
|
|
3946
4382
|
function getNativeBinding() {
|
|
3947
|
-
const platform2 =
|
|
3948
|
-
const arch2 =
|
|
4383
|
+
const platform2 = os4.platform();
|
|
4384
|
+
const arch2 = os4.arch();
|
|
3949
4385
|
let bindingName;
|
|
3950
4386
|
if (platform2 === "darwin" && arch2 === "arm64") {
|
|
3951
4387
|
bindingName = "codebase-index-native.darwin-arm64.node";
|
|
@@ -3963,19 +4399,19 @@ function getNativeBinding() {
|
|
|
3963
4399
|
let currentDir;
|
|
3964
4400
|
let requireTarget;
|
|
3965
4401
|
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
3966
|
-
currentDir =
|
|
4402
|
+
currentDir = path10.dirname(fileURLToPath(import.meta.url));
|
|
3967
4403
|
requireTarget = import.meta.url;
|
|
3968
4404
|
} else if (typeof __dirname !== "undefined") {
|
|
3969
4405
|
currentDir = __dirname;
|
|
3970
4406
|
requireTarget = __filename;
|
|
3971
4407
|
} else {
|
|
3972
4408
|
currentDir = process.cwd();
|
|
3973
|
-
requireTarget =
|
|
4409
|
+
requireTarget = path10.join(currentDir, "index.js");
|
|
3974
4410
|
}
|
|
3975
4411
|
const normalizedDir = currentDir.replace(/\\/g, "/");
|
|
3976
|
-
const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(
|
|
3977
|
-
const packageRoot = isDevMode ?
|
|
3978
|
-
const nativePath =
|
|
4412
|
+
const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path10.join("src", "native"));
|
|
4413
|
+
const packageRoot = isDevMode ? path10.resolve(currentDir, "../..") : path10.resolve(currentDir, "..");
|
|
4414
|
+
const nativePath = path10.join(packageRoot, "native", bindingName);
|
|
3979
4415
|
const require2 = module.createRequire(requireTarget);
|
|
3980
4416
|
return require2(nativePath);
|
|
3981
4417
|
}
|
|
@@ -4017,6 +4453,12 @@ function createMockNativeBinding() {
|
|
|
4017
4453
|
constructor() {
|
|
4018
4454
|
throw error;
|
|
4019
4455
|
}
|
|
4456
|
+
static openReadOnly() {
|
|
4457
|
+
throw error;
|
|
4458
|
+
}
|
|
4459
|
+
static createEmptyReadOnly() {
|
|
4460
|
+
throw error;
|
|
4461
|
+
}
|
|
4020
4462
|
close() {
|
|
4021
4463
|
throw error;
|
|
4022
4464
|
}
|
|
@@ -4120,6 +4562,12 @@ var VectorStore = class {
|
|
|
4120
4562
|
load() {
|
|
4121
4563
|
this.inner.load();
|
|
4122
4564
|
}
|
|
4565
|
+
loadStrict() {
|
|
4566
|
+
this.inner.loadStrict();
|
|
4567
|
+
}
|
|
4568
|
+
hasFingerprint() {
|
|
4569
|
+
return this.inner.hasFingerprint();
|
|
4570
|
+
}
|
|
4123
4571
|
count() {
|
|
4124
4572
|
return this.inner.count();
|
|
4125
4573
|
}
|
|
@@ -4402,12 +4850,24 @@ var InvertedIndex = class {
|
|
|
4402
4850
|
return this.inner.documentCount();
|
|
4403
4851
|
}
|
|
4404
4852
|
};
|
|
4405
|
-
var Database = class {
|
|
4853
|
+
var Database = class _Database {
|
|
4406
4854
|
inner;
|
|
4407
4855
|
closed = false;
|
|
4408
4856
|
constructor(dbPath) {
|
|
4409
4857
|
this.inner = new native.Database(dbPath);
|
|
4410
4858
|
}
|
|
4859
|
+
static fromNative(inner) {
|
|
4860
|
+
const database = Object.create(_Database.prototype);
|
|
4861
|
+
database.inner = inner;
|
|
4862
|
+
database.closed = false;
|
|
4863
|
+
return database;
|
|
4864
|
+
}
|
|
4865
|
+
static openReadOnly(dbPath) {
|
|
4866
|
+
return _Database.fromNative(native.Database.openReadOnly(dbPath));
|
|
4867
|
+
}
|
|
4868
|
+
static createEmptyReadOnly() {
|
|
4869
|
+
return _Database.fromNative(native.Database.createEmptyReadOnly());
|
|
4870
|
+
}
|
|
4411
4871
|
throwIfClosed() {
|
|
4412
4872
|
if (this.closed) {
|
|
4413
4873
|
throw new Error("Database is closed");
|
|
@@ -4684,7 +5144,7 @@ var Database = class {
|
|
|
4684
5144
|
};
|
|
4685
5145
|
|
|
4686
5146
|
// src/tools/changed-files.ts
|
|
4687
|
-
import * as
|
|
5147
|
+
import * as path11 from "path";
|
|
4688
5148
|
import { execFile } from "child_process";
|
|
4689
5149
|
import { promisify } from "util";
|
|
4690
5150
|
var execFileAsync = promisify(execFile);
|
|
@@ -4772,9 +5232,9 @@ function normalizeFiles(rawFiles, projectRoot) {
|
|
|
4772
5232
|
for (const raw of rawFiles) {
|
|
4773
5233
|
const trimmed = raw.trim();
|
|
4774
5234
|
if (!trimmed) continue;
|
|
4775
|
-
const absolute =
|
|
4776
|
-
const
|
|
4777
|
-
const cleaned =
|
|
5235
|
+
const absolute = path11.resolve(projectRoot, trimmed);
|
|
5236
|
+
const relative11 = path11.relative(projectRoot, absolute);
|
|
5237
|
+
const cleaned = relative11.startsWith("./") ? relative11.slice(2) : relative11;
|
|
4778
5238
|
if (!seen.has(cleaned)) {
|
|
4779
5239
|
seen.add(cleaned);
|
|
4780
5240
|
result.push(cleaned);
|
|
@@ -4783,8 +5243,61 @@ function normalizeFiles(rawFiles, projectRoot) {
|
|
|
4783
5243
|
return result;
|
|
4784
5244
|
}
|
|
4785
5245
|
|
|
5246
|
+
// src/indexer/git-blame.ts
|
|
5247
|
+
import { execFile as execFile2 } from "child_process";
|
|
5248
|
+
import * as path12 from "path";
|
|
5249
|
+
import { promisify as promisify2 } from "util";
|
|
5250
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
5251
|
+
function parseGitBlamePorcelain(output) {
|
|
5252
|
+
const commits = /* @__PURE__ */ new Map();
|
|
5253
|
+
let current;
|
|
5254
|
+
for (const line of output.split("\n")) {
|
|
5255
|
+
if (/^[0-9a-f]{40} /.test(line)) {
|
|
5256
|
+
const sha = line.slice(0, 40);
|
|
5257
|
+
current = commits.get(sha) ?? {
|
|
5258
|
+
sha,
|
|
5259
|
+
author: "",
|
|
5260
|
+
authorEmail: "",
|
|
5261
|
+
committedAt: 0,
|
|
5262
|
+
summary: "",
|
|
5263
|
+
lines: 0
|
|
5264
|
+
};
|
|
5265
|
+
commits.set(sha, current);
|
|
5266
|
+
continue;
|
|
5267
|
+
}
|
|
5268
|
+
if (!current) {
|
|
5269
|
+
continue;
|
|
5270
|
+
}
|
|
5271
|
+
if (line.startsWith("author ")) {
|
|
5272
|
+
current.author = line.slice("author ".length);
|
|
5273
|
+
} else if (line.startsWith("author-mail ")) {
|
|
5274
|
+
current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
|
|
5275
|
+
} else if (line.startsWith("author-time ")) {
|
|
5276
|
+
current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
|
|
5277
|
+
} else if (line.startsWith("summary ")) {
|
|
5278
|
+
current.summary = line.slice("summary ".length);
|
|
5279
|
+
} else if (line.startsWith(" ")) {
|
|
5280
|
+
current.lines += 1;
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
|
|
5284
|
+
}
|
|
5285
|
+
async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
|
|
5286
|
+
const relativePath = path12.relative(projectRoot, filePath);
|
|
5287
|
+
try {
|
|
5288
|
+
const { stdout } = await execFileAsync2(
|
|
5289
|
+
"git",
|
|
5290
|
+
["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
|
|
5291
|
+
{ cwd: projectRoot, timeout: 3e4 }
|
|
5292
|
+
);
|
|
5293
|
+
return parseGitBlamePorcelain(stdout);
|
|
5294
|
+
} catch {
|
|
5295
|
+
return void 0;
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
|
|
4786
5299
|
// src/indexer/index.ts
|
|
4787
|
-
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
5300
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
|
|
4788
5301
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
4789
5302
|
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
4790
5303
|
"function_declaration",
|
|
@@ -4864,6 +5377,46 @@ function isSqliteCorruptionError(error) {
|
|
|
4864
5377
|
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");
|
|
4865
5378
|
}
|
|
4866
5379
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
5380
|
+
var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
|
|
5381
|
+
function metadataFromBlame(blame) {
|
|
5382
|
+
if (!blame) {
|
|
5383
|
+
return {};
|
|
5384
|
+
}
|
|
5385
|
+
return {
|
|
5386
|
+
blameSha: blame.sha,
|
|
5387
|
+
blameAuthor: blame.author,
|
|
5388
|
+
blameAuthorEmail: blame.authorEmail,
|
|
5389
|
+
blameCommittedAt: blame.committedAt,
|
|
5390
|
+
blameSummary: blame.summary
|
|
5391
|
+
};
|
|
5392
|
+
}
|
|
5393
|
+
function blameFromChunkData(chunk) {
|
|
5394
|
+
if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
|
|
5395
|
+
return void 0;
|
|
5396
|
+
}
|
|
5397
|
+
return {
|
|
5398
|
+
sha: chunk.blameSha,
|
|
5399
|
+
author: chunk.blameAuthor,
|
|
5400
|
+
authorEmail: chunk.blameAuthorEmail,
|
|
5401
|
+
committedAt: chunk.blameCommittedAt,
|
|
5402
|
+
summary: chunk.blameSummary
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
function blameFromMetadata(metadata) {
|
|
5406
|
+
if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
|
|
5407
|
+
return void 0;
|
|
5408
|
+
}
|
|
5409
|
+
return {
|
|
5410
|
+
sha: metadata.blameSha,
|
|
5411
|
+
author: metadata.blameAuthor,
|
|
5412
|
+
authorEmail: metadata.blameAuthorEmail,
|
|
5413
|
+
committedAt: metadata.blameCommittedAt,
|
|
5414
|
+
summary: metadata.blameSummary
|
|
5415
|
+
};
|
|
5416
|
+
}
|
|
5417
|
+
function hasBlameMetadata(metadata) {
|
|
5418
|
+
return blameFromMetadata(metadata) !== void 0;
|
|
5419
|
+
}
|
|
4867
5420
|
var INDEX_METADATA_VERSION = "1";
|
|
4868
5421
|
var EMBEDDING_STRATEGY_VERSION = "2";
|
|
4869
5422
|
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
@@ -5022,9 +5575,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
5022
5575
|
return true;
|
|
5023
5576
|
}
|
|
5024
5577
|
function isPathWithinRoot(filePath, rootPath) {
|
|
5025
|
-
const normalizedFilePath =
|
|
5026
|
-
const normalizedRoot =
|
|
5027
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
5578
|
+
const normalizedFilePath = path13.resolve(filePath);
|
|
5579
|
+
const normalizedRoot = path13.resolve(rootPath);
|
|
5580
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
|
|
5028
5581
|
}
|
|
5029
5582
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
5030
5583
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5783,7 +6336,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
|
|
|
5783
6336
|
chunkType,
|
|
5784
6337
|
name: chunk.name ?? void 0,
|
|
5785
6338
|
language: chunk.language,
|
|
5786
|
-
hash: chunk.contentHash
|
|
6339
|
+
hash: chunk.contentHash,
|
|
6340
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5787
6341
|
};
|
|
5788
6342
|
const baselineScore = existing?.score ?? 0.5;
|
|
5789
6343
|
candidateUnion.set(chunk.chunkId, {
|
|
@@ -5867,7 +6421,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
|
|
|
5867
6421
|
chunkType,
|
|
5868
6422
|
name: chunk.name ?? void 0,
|
|
5869
6423
|
language: chunk.language,
|
|
5870
|
-
hash: chunk.contentHash
|
|
6424
|
+
hash: chunk.contentHash,
|
|
6425
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5871
6426
|
}
|
|
5872
6427
|
});
|
|
5873
6428
|
}
|
|
@@ -6036,6 +6591,21 @@ function matchesSearchFilters(candidate, options, minScore) {
|
|
|
6036
6591
|
if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
|
|
6037
6592
|
return false;
|
|
6038
6593
|
}
|
|
6594
|
+
if (options?.blameAuthor) {
|
|
6595
|
+
const author = options.blameAuthor.toLowerCase();
|
|
6596
|
+
const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
|
|
6597
|
+
const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
|
|
6598
|
+
if (candidateAuthor !== author && candidateEmail !== author) return false;
|
|
6599
|
+
}
|
|
6600
|
+
if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
|
|
6601
|
+
return false;
|
|
6602
|
+
}
|
|
6603
|
+
if (options?.blameSince) {
|
|
6604
|
+
const sinceMs = Date.parse(options.blameSince);
|
|
6605
|
+
if (Number.isNaN(sinceMs)) return false;
|
|
6606
|
+
const committedAt = candidate.metadata.blameCommittedAt;
|
|
6607
|
+
if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
|
|
6608
|
+
}
|
|
6039
6609
|
return true;
|
|
6040
6610
|
}
|
|
6041
6611
|
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
@@ -6073,26 +6643,133 @@ var Indexer = class {
|
|
|
6073
6643
|
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
6074
6644
|
querySimilarityThreshold = 0.85;
|
|
6075
6645
|
indexCompatibility = null;
|
|
6076
|
-
|
|
6646
|
+
activeIndexLease = null;
|
|
6647
|
+
initializationPromise = null;
|
|
6648
|
+
initializationMode = "none";
|
|
6649
|
+
readIssues = [];
|
|
6650
|
+
retiredDatabases = [];
|
|
6651
|
+
readerArtifactFingerprint = null;
|
|
6652
|
+
writerArtifactFingerprint = null;
|
|
6653
|
+
readerArtifactRetryAfter = /* @__PURE__ */ new Map();
|
|
6077
6654
|
constructor(projectRoot, config, host = "opencode") {
|
|
6078
6655
|
this.projectRoot = projectRoot;
|
|
6079
6656
|
this.config = config;
|
|
6080
6657
|
this.host = host;
|
|
6081
6658
|
this.indexPath = this.getIndexPath();
|
|
6082
|
-
this.fileHashCachePath =
|
|
6083
|
-
this.failedBatchesPath =
|
|
6084
|
-
this.indexingLockPath = path11.join(this.indexPath, "indexing.lock");
|
|
6659
|
+
this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
|
|
6660
|
+
this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
|
|
6085
6661
|
this.logger = initializeLogger(config.debug);
|
|
6086
6662
|
}
|
|
6087
6663
|
getIndexPath() {
|
|
6088
6664
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
|
|
6089
6665
|
}
|
|
6666
|
+
isLocalProjectIndexPath() {
|
|
6667
|
+
const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
|
|
6668
|
+
if (this.host !== "opencode") {
|
|
6669
|
+
localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
6670
|
+
}
|
|
6671
|
+
return localProjectIndexPaths.some((localPath) => {
|
|
6672
|
+
if (!existsSync8(localPath) || !existsSync8(this.indexPath)) {
|
|
6673
|
+
return path13.resolve(this.indexPath) === path13.resolve(localPath);
|
|
6674
|
+
}
|
|
6675
|
+
const indexStats = statSync3(this.indexPath);
|
|
6676
|
+
const localStats = statSync3(localPath);
|
|
6677
|
+
return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
|
|
6678
|
+
});
|
|
6679
|
+
}
|
|
6680
|
+
resetLoadedIndexState(retireDatabase = false) {
|
|
6681
|
+
if (this.database) {
|
|
6682
|
+
if (retireDatabase) {
|
|
6683
|
+
this.retiredDatabases.push(this.database);
|
|
6684
|
+
} else {
|
|
6685
|
+
this.database.close();
|
|
6686
|
+
}
|
|
6687
|
+
}
|
|
6688
|
+
this.store = null;
|
|
6689
|
+
this.invertedIndex = null;
|
|
6690
|
+
this.database = null;
|
|
6691
|
+
this.provider = null;
|
|
6692
|
+
this.configuredProviderInfo = null;
|
|
6693
|
+
this.reranker = null;
|
|
6694
|
+
this.indexCompatibility = null;
|
|
6695
|
+
this.initializationMode = "none";
|
|
6696
|
+
this.readIssues = [];
|
|
6697
|
+
this.readerArtifactFingerprint = null;
|
|
6698
|
+
this.writerArtifactFingerprint = null;
|
|
6699
|
+
this.readerArtifactRetryAfter.clear();
|
|
6700
|
+
this.fileHashCache.clear();
|
|
6701
|
+
}
|
|
6702
|
+
refreshLoadedIndexState() {
|
|
6703
|
+
if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
|
|
6704
|
+
this.store.load();
|
|
6705
|
+
this.invertedIndex.load();
|
|
6706
|
+
this.fileHashCache.clear();
|
|
6707
|
+
this.loadFileHashCache();
|
|
6708
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
6709
|
+
this.readIssues = [];
|
|
6710
|
+
this.readerArtifactRetryAfter.clear();
|
|
6711
|
+
}
|
|
6712
|
+
async withIndexMutationLease(operation, callback) {
|
|
6713
|
+
const lease = acquireIndexLock(this.indexPath, operation);
|
|
6714
|
+
this.indexPath = lease.canonicalIndexPath;
|
|
6715
|
+
this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
|
|
6716
|
+
this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
|
|
6717
|
+
this.activeIndexLease = lease;
|
|
6718
|
+
let result;
|
|
6719
|
+
let callbackError;
|
|
6720
|
+
let callbackFailed = false;
|
|
6721
|
+
try {
|
|
6722
|
+
result = await callback(lease.recoveries.map(({ owner }) => owner));
|
|
6723
|
+
} catch (error) {
|
|
6724
|
+
callbackFailed = true;
|
|
6725
|
+
callbackError = error;
|
|
6726
|
+
}
|
|
6727
|
+
if (!callbackFailed) {
|
|
6728
|
+
try {
|
|
6729
|
+
completeLeaseRecovery(lease);
|
|
6730
|
+
this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
|
|
6731
|
+
} catch (error) {
|
|
6732
|
+
callbackFailed = true;
|
|
6733
|
+
callbackError = error;
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
let releaseError;
|
|
6737
|
+
try {
|
|
6738
|
+
if (!releaseIndexLock(lease)) {
|
|
6739
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
6740
|
+
this.writerArtifactFingerprint = null;
|
|
6741
|
+
if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6742
|
+
this.activeIndexLease = null;
|
|
6743
|
+
}
|
|
6744
|
+
} else if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6745
|
+
this.activeIndexLease = null;
|
|
6746
|
+
}
|
|
6747
|
+
} catch (error) {
|
|
6748
|
+
releaseError = error;
|
|
6749
|
+
this.writerArtifactFingerprint = null;
|
|
6750
|
+
if (!existsSync8(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6751
|
+
this.activeIndexLease = null;
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
if (releaseError !== void 0) {
|
|
6755
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
6756
|
+
throw releaseError;
|
|
6757
|
+
}
|
|
6758
|
+
if (callbackFailed) throw callbackError;
|
|
6759
|
+
return result;
|
|
6760
|
+
}
|
|
6761
|
+
requireActiveLease() {
|
|
6762
|
+
if (!this.activeIndexLease) {
|
|
6763
|
+
throw new Error("Index mutation attempted without an active interprocess lease");
|
|
6764
|
+
}
|
|
6765
|
+
return this.activeIndexLease;
|
|
6766
|
+
}
|
|
6090
6767
|
loadFileHashCache() {
|
|
6091
|
-
if (!
|
|
6768
|
+
if (!existsSync8(this.fileHashCachePath)) {
|
|
6092
6769
|
return;
|
|
6093
6770
|
}
|
|
6094
6771
|
try {
|
|
6095
|
-
const data =
|
|
6772
|
+
const data = readFileSync7(this.fileHashCachePath, "utf-8");
|
|
6096
6773
|
const parsed = JSON.parse(data);
|
|
6097
6774
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
6098
6775
|
} catch (error) {
|
|
@@ -6112,15 +6789,26 @@ var Indexer = class {
|
|
|
6112
6789
|
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
6113
6790
|
}
|
|
6114
6791
|
atomicWriteSync(targetPath, data) {
|
|
6115
|
-
const
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6792
|
+
const lease = this.requireActiveLease();
|
|
6793
|
+
const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
|
|
6794
|
+
mkdirSync3(path13.dirname(targetPath), { recursive: true });
|
|
6795
|
+
try {
|
|
6796
|
+
writeFileSync3(tempPath, data);
|
|
6797
|
+
renameSync2(tempPath, targetPath);
|
|
6798
|
+
} finally {
|
|
6799
|
+
removeLeaseTemporaryPath(tempPath);
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
saveInvertedIndex(invertedIndex) {
|
|
6803
|
+
this.atomicWriteSync(
|
|
6804
|
+
path13.join(this.indexPath, "inverted-index.json"),
|
|
6805
|
+
invertedIndex.serialize()
|
|
6806
|
+
);
|
|
6119
6807
|
}
|
|
6120
6808
|
getScopedRoots() {
|
|
6121
|
-
const roots = /* @__PURE__ */ new Set([
|
|
6809
|
+
const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
|
|
6122
6810
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
6123
|
-
roots.add(
|
|
6811
|
+
roots.add(path13.resolve(this.projectRoot, kbRoot));
|
|
6124
6812
|
}
|
|
6125
6813
|
return Array.from(roots);
|
|
6126
6814
|
}
|
|
@@ -6129,29 +6817,29 @@ var Indexer = class {
|
|
|
6129
6817
|
if (this.config.scope !== "global") {
|
|
6130
6818
|
return branchName;
|
|
6131
6819
|
}
|
|
6132
|
-
const projectHash = hashContent(
|
|
6820
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6133
6821
|
return `${projectHash}:${branchName}`;
|
|
6134
6822
|
}
|
|
6135
6823
|
getBranchCatalogKeyFor(branchName) {
|
|
6136
6824
|
if (this.config.scope !== "global") {
|
|
6137
6825
|
return branchName;
|
|
6138
6826
|
}
|
|
6139
|
-
const projectHash = hashContent(
|
|
6827
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6140
6828
|
return `${projectHash}:${branchName}`;
|
|
6141
6829
|
}
|
|
6142
6830
|
getLegacyBranchCatalogKey() {
|
|
6143
6831
|
return this.currentBranch || "default";
|
|
6144
6832
|
}
|
|
6145
6833
|
getLegacyMigrationMetadataKey() {
|
|
6146
|
-
const projectHash = hashContent(
|
|
6834
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6147
6835
|
return `index.globalBranchMigration.${projectHash}`;
|
|
6148
6836
|
}
|
|
6149
6837
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
6150
|
-
const projectHash = hashContent(
|
|
6838
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6151
6839
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
6152
6840
|
}
|
|
6153
6841
|
getProjectForceReembedMetadataKey() {
|
|
6154
|
-
const projectHash = hashContent(
|
|
6842
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6155
6843
|
return `index.forceReembed.${projectHash}`;
|
|
6156
6844
|
}
|
|
6157
6845
|
hasProjectForceReembedPending() {
|
|
@@ -6245,7 +6933,7 @@ var Indexer = class {
|
|
|
6245
6933
|
if (!this.database) {
|
|
6246
6934
|
return { chunkIds, symbolIds };
|
|
6247
6935
|
}
|
|
6248
|
-
const projectRootPath =
|
|
6936
|
+
const projectRootPath = path13.resolve(this.projectRoot);
|
|
6249
6937
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6250
6938
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6251
6939
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6268,7 +6956,7 @@ var Indexer = class {
|
|
|
6268
6956
|
if (this.config.scope !== "global") {
|
|
6269
6957
|
return this.getBranchCatalogCleanupKeys();
|
|
6270
6958
|
}
|
|
6271
|
-
const projectHash = hashContent(
|
|
6959
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6272
6960
|
const keys = /* @__PURE__ */ new Set();
|
|
6273
6961
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6274
6962
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6352,7 +7040,7 @@ var Indexer = class {
|
|
|
6352
7040
|
if (!this.database || this.config.scope !== "global") {
|
|
6353
7041
|
return false;
|
|
6354
7042
|
}
|
|
6355
|
-
const projectHash = hashContent(
|
|
7043
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6356
7044
|
const roots = this.getScopedRoots();
|
|
6357
7045
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6358
7046
|
return this.database.getAllBranches().some(
|
|
@@ -6386,7 +7074,7 @@ var Indexer = class {
|
|
|
6386
7074
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6387
7075
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6388
7076
|
]);
|
|
6389
|
-
const projectRootPath =
|
|
7077
|
+
const projectRootPath = path13.resolve(this.projectRoot);
|
|
6390
7078
|
const projectLocalFilePaths = new Set(
|
|
6391
7079
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6392
7080
|
);
|
|
@@ -6448,34 +7136,27 @@ var Indexer = class {
|
|
|
6448
7136
|
database.gcOrphanEmbeddings();
|
|
6449
7137
|
database.gcOrphanChunks();
|
|
6450
7138
|
store.save();
|
|
6451
|
-
|
|
7139
|
+
this.saveInvertedIndex(invertedIndex);
|
|
6452
7140
|
return {
|
|
6453
7141
|
removedChunkIds: removedChunkIdList,
|
|
6454
7142
|
hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
|
|
6455
7143
|
};
|
|
6456
7144
|
}
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
|
|
6466
|
-
}
|
|
6467
|
-
releaseIndexingLock() {
|
|
6468
|
-
if (existsSync7(this.indexingLockPath)) {
|
|
6469
|
-
unlinkSync(this.indexingLockPath);
|
|
7145
|
+
async recoverFromInterruptedIndexingUnlocked(owners) {
|
|
7146
|
+
for (const owner of owners) {
|
|
7147
|
+
this.logger.warn("Detected interrupted indexing session, recovering...", {
|
|
7148
|
+
pid: owner.pid,
|
|
7149
|
+
hostname: owner.hostname,
|
|
7150
|
+
operation: owner.operation,
|
|
7151
|
+
startedAt: owner.startedAt
|
|
7152
|
+
});
|
|
6470
7153
|
}
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
7154
|
+
if (this.config.scope === "global") {
|
|
7155
|
+
if (existsSync8(this.fileHashCachePath)) {
|
|
7156
|
+
unlinkSync(this.fileHashCachePath);
|
|
7157
|
+
}
|
|
7158
|
+
await this.healthCheckUnlocked();
|
|
6476
7159
|
}
|
|
6477
|
-
await this.healthCheck();
|
|
6478
|
-
this.releaseIndexingLock();
|
|
6479
7160
|
this.logger.info("Recovery complete, next index will re-process all files");
|
|
6480
7161
|
}
|
|
6481
7162
|
loadFailedBatches(maxChunkTokens) {
|
|
@@ -6491,10 +7172,10 @@ var Indexer = class {
|
|
|
6491
7172
|
}
|
|
6492
7173
|
}
|
|
6493
7174
|
loadSerializedFailedBatches() {
|
|
6494
|
-
if (!
|
|
7175
|
+
if (!existsSync8(this.failedBatchesPath)) {
|
|
6495
7176
|
return [];
|
|
6496
7177
|
}
|
|
6497
|
-
const data =
|
|
7178
|
+
const data = readFileSync7(this.failedBatchesPath, "utf-8");
|
|
6498
7179
|
const parsed = JSON.parse(data);
|
|
6499
7180
|
return parsed.map((batch) => {
|
|
6500
7181
|
const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
|
|
@@ -6511,7 +7192,7 @@ var Indexer = class {
|
|
|
6511
7192
|
}
|
|
6512
7193
|
saveFailedBatches(batches) {
|
|
6513
7194
|
if (batches.length === 0) {
|
|
6514
|
-
if (
|
|
7195
|
+
if (existsSync8(this.failedBatchesPath)) {
|
|
6515
7196
|
try {
|
|
6516
7197
|
unlinkSync(this.failedBatchesPath);
|
|
6517
7198
|
} catch {
|
|
@@ -6519,7 +7200,7 @@ var Indexer = class {
|
|
|
6519
7200
|
}
|
|
6520
7201
|
return;
|
|
6521
7202
|
}
|
|
6522
|
-
|
|
7203
|
+
this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
6523
7204
|
}
|
|
6524
7205
|
collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
|
|
6525
7206
|
const retryableById = /* @__PURE__ */ new Map();
|
|
@@ -6713,15 +7394,231 @@ var Indexer = class {
|
|
|
6713
7394
|
return parts.join("\n");
|
|
6714
7395
|
}
|
|
6715
7396
|
async initialize() {
|
|
6716
|
-
if (this.
|
|
6717
|
-
|
|
6718
|
-
|
|
7397
|
+
if (this.initializationPromise) {
|
|
7398
|
+
await this.initializationPromise;
|
|
7399
|
+
}
|
|
7400
|
+
if (this.isInitializedFor("reader")) {
|
|
7401
|
+
return;
|
|
7402
|
+
}
|
|
7403
|
+
await this.initializeOnce("reader", [], { skipAutoGc: true });
|
|
7404
|
+
}
|
|
7405
|
+
async initializeOnce(mode, recoveredOwners, options) {
|
|
7406
|
+
if (this.initializationPromise) {
|
|
7407
|
+
await this.initializationPromise;
|
|
7408
|
+
if (this.isInitializedFor(mode)) {
|
|
7409
|
+
return;
|
|
6719
7410
|
}
|
|
6720
|
-
this.
|
|
6721
|
-
}
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
7411
|
+
return this.initializeOnce(mode, recoveredOwners, options);
|
|
7412
|
+
}
|
|
7413
|
+
if (this.isInitializedFor(mode)) {
|
|
7414
|
+
return;
|
|
7415
|
+
}
|
|
7416
|
+
const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
|
|
7417
|
+
this.resetLoadedIndexState();
|
|
7418
|
+
throw error;
|
|
7419
|
+
}).finally(() => {
|
|
7420
|
+
if (this.initializationPromise === initialization) {
|
|
7421
|
+
this.initializationPromise = null;
|
|
7422
|
+
}
|
|
7423
|
+
});
|
|
7424
|
+
this.initializationPromise = initialization;
|
|
7425
|
+
await initialization;
|
|
7426
|
+
}
|
|
7427
|
+
isInitializedFor(mode) {
|
|
7428
|
+
const hasState = Boolean(
|
|
7429
|
+
this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
|
|
7430
|
+
);
|
|
7431
|
+
if (!hasState) {
|
|
7432
|
+
return false;
|
|
7433
|
+
}
|
|
7434
|
+
return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
|
|
7435
|
+
}
|
|
7436
|
+
recordReadIssue(component, message, error) {
|
|
7437
|
+
this.readIssues.push(this.createReadIssue(component, message));
|
|
7438
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7439
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7440
|
+
}
|
|
7441
|
+
createReadIssue(component, message) {
|
|
7442
|
+
return {
|
|
7443
|
+
component,
|
|
7444
|
+
message,
|
|
7445
|
+
blocking: component !== "keyword"
|
|
7446
|
+
};
|
|
7447
|
+
}
|
|
7448
|
+
getVectorReadIssueMessage() {
|
|
7449
|
+
if (this.config.scope === "global") {
|
|
7450
|
+
return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
|
|
7451
|
+
}
|
|
7452
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7453
|
+
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.";
|
|
7454
|
+
}
|
|
7455
|
+
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.";
|
|
7456
|
+
}
|
|
7457
|
+
getKeywordReadIssueMessage() {
|
|
7458
|
+
if (this.config.scope === "global") {
|
|
7459
|
+
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.";
|
|
7460
|
+
}
|
|
7461
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7462
|
+
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.";
|
|
7463
|
+
}
|
|
7464
|
+
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.";
|
|
7465
|
+
}
|
|
7466
|
+
getDatabaseReadIssueMessage() {
|
|
7467
|
+
if (this.config.scope === "global") {
|
|
7468
|
+
return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
|
|
7469
|
+
}
|
|
7470
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7471
|
+
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.";
|
|
7472
|
+
}
|
|
7473
|
+
return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
|
|
7474
|
+
}
|
|
7475
|
+
getReaderFileFingerprint(filePath, identityOnly = false) {
|
|
7476
|
+
try {
|
|
7477
|
+
const stats = statSync3(filePath);
|
|
7478
|
+
if (identityOnly) {
|
|
7479
|
+
return `${stats.dev}:${stats.ino}`;
|
|
7480
|
+
}
|
|
7481
|
+
return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
|
|
7482
|
+
} catch (error) {
|
|
7483
|
+
return `unavailable:${getErrorMessage2(error)}`;
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
captureReaderArtifactFingerprint() {
|
|
7487
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7488
|
+
return {
|
|
7489
|
+
vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
|
|
7490
|
+
keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
|
|
7491
|
+
database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
|
|
7492
|
+
databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
|
|
7493
|
+
};
|
|
7494
|
+
}
|
|
7495
|
+
refreshReaderArtifacts() {
|
|
7496
|
+
if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
|
|
7497
|
+
return;
|
|
7498
|
+
}
|
|
7499
|
+
const previousFingerprint = this.readerArtifactFingerprint;
|
|
7500
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7501
|
+
const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
|
|
7502
|
+
const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
|
|
7503
|
+
const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
|
|
7504
|
+
const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
|
|
7505
|
+
const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
|
|
7506
|
+
const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7507
|
+
if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
|
|
7508
|
+
return;
|
|
7509
|
+
}
|
|
7510
|
+
const setIssue = (component, message, error) => {
|
|
7511
|
+
if (!issues.has(component)) {
|
|
7512
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7513
|
+
}
|
|
7514
|
+
issues.set(component, this.createReadIssue(component, message));
|
|
7515
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7516
|
+
};
|
|
7517
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7518
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7519
|
+
const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
|
|
7520
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
7521
|
+
if (vectorsChanged || retryDue("vectors")) {
|
|
7522
|
+
const vectorStoreExists = existsSync8(storePath);
|
|
7523
|
+
const vectorMetadataExists = existsSync8(vectorMetadataPath);
|
|
7524
|
+
if (vectorStoreExists && vectorMetadataExists) {
|
|
7525
|
+
try {
|
|
7526
|
+
const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
|
|
7527
|
+
store.loadStrict();
|
|
7528
|
+
this.store = store;
|
|
7529
|
+
issues.delete("vectors");
|
|
7530
|
+
this.readerArtifactRetryAfter.delete("vectors");
|
|
7531
|
+
} catch (error) {
|
|
7532
|
+
setIssue("vectors", this.getVectorReadIssueMessage(), error);
|
|
7533
|
+
}
|
|
7534
|
+
} else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
|
|
7535
|
+
setIssue("vectors", this.getVectorReadIssueMessage());
|
|
7536
|
+
}
|
|
7537
|
+
}
|
|
7538
|
+
if (keywordChanged || retryDue("keyword") || !existsSync8(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
|
|
7539
|
+
if (existsSync8(invertedIndexPath)) {
|
|
7540
|
+
try {
|
|
7541
|
+
const invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7542
|
+
invertedIndex.load();
|
|
7543
|
+
this.invertedIndex = invertedIndex;
|
|
7544
|
+
issues.delete("keyword");
|
|
7545
|
+
this.readerArtifactRetryAfter.delete("keyword");
|
|
7546
|
+
} catch (error) {
|
|
7547
|
+
setIssue("keyword", this.getKeywordReadIssueMessage(), error);
|
|
7548
|
+
}
|
|
7549
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
|
|
7550
|
+
setIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
|
|
7554
|
+
if (existsSync8(dbPath)) {
|
|
7555
|
+
try {
|
|
7556
|
+
const database = Database.openReadOnly(dbPath);
|
|
7557
|
+
if (this.database) {
|
|
7558
|
+
this.retiredDatabases.push(this.database);
|
|
7559
|
+
}
|
|
7560
|
+
this.database = database;
|
|
7561
|
+
issues.delete("database");
|
|
7562
|
+
this.readerArtifactRetryAfter.delete("database");
|
|
7563
|
+
} catch (error) {
|
|
7564
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7565
|
+
}
|
|
7566
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
|
|
7567
|
+
setIssue("database", this.getDatabaseReadIssueMessage());
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
if (!issues.has("database")) {
|
|
7571
|
+
try {
|
|
7572
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
7573
|
+
} catch (error) {
|
|
7574
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7575
|
+
}
|
|
7576
|
+
}
|
|
7577
|
+
this.readIssues = Array.from(issues.values());
|
|
7578
|
+
this.readerArtifactFingerprint = currentFingerprint;
|
|
7579
|
+
}
|
|
7580
|
+
refreshInactiveWriterArtifacts() {
|
|
7581
|
+
if (this.initializationMode !== "writer" || this.activeIndexLease) {
|
|
7582
|
+
return true;
|
|
7583
|
+
}
|
|
7584
|
+
const previousFingerprint = this.writerArtifactFingerprint;
|
|
7585
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7586
|
+
const retryDue = this.readIssues.some(
|
|
7587
|
+
(issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
|
|
7588
|
+
);
|
|
7589
|
+
const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7590
|
+
if (!artifactsChanged && !retryDue) {
|
|
7591
|
+
return true;
|
|
7592
|
+
}
|
|
7593
|
+
if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
|
|
7594
|
+
return false;
|
|
7595
|
+
}
|
|
7596
|
+
this.initializationMode = "reader";
|
|
7597
|
+
this.readerArtifactFingerprint = previousFingerprint;
|
|
7598
|
+
try {
|
|
7599
|
+
this.refreshReaderArtifacts();
|
|
7600
|
+
this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
|
|
7601
|
+
} finally {
|
|
7602
|
+
this.readerArtifactFingerprint = null;
|
|
7603
|
+
this.initializationMode = "writer";
|
|
7604
|
+
}
|
|
7605
|
+
return true;
|
|
7606
|
+
}
|
|
7607
|
+
async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
|
|
7608
|
+
if (mode === "writer") {
|
|
7609
|
+
this.requireActiveLease();
|
|
7610
|
+
}
|
|
7611
|
+
this.readIssues = [];
|
|
7612
|
+
this.readerArtifactRetryAfter.clear();
|
|
7613
|
+
if (this.config.embeddingProvider === "custom") {
|
|
7614
|
+
if (!this.config.customProvider) {
|
|
7615
|
+
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
7616
|
+
}
|
|
7617
|
+
this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
|
|
7618
|
+
} else if (this.config.embeddingProvider === "auto") {
|
|
7619
|
+
this.configuredProviderInfo = await tryDetectProvider();
|
|
7620
|
+
} else {
|
|
7621
|
+
this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
|
|
6725
7622
|
}
|
|
6726
7623
|
if (!this.configuredProviderInfo) {
|
|
6727
7624
|
throw new Error(
|
|
@@ -6744,36 +7641,103 @@ var Indexer = class {
|
|
|
6744
7641
|
});
|
|
6745
7642
|
}
|
|
6746
7643
|
}
|
|
6747
|
-
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
6748
7644
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
6749
|
-
const storePath =
|
|
6750
|
-
|
|
6751
|
-
const
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
7645
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7646
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7647
|
+
const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
|
|
7648
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
7649
|
+
let dbIsNew = !existsSync8(dbPath);
|
|
7650
|
+
const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
|
|
7651
|
+
if (mode === "writer") {
|
|
7652
|
+
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7653
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
|
|
7654
|
+
throw new Error(
|
|
7655
|
+
"Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
|
|
7656
|
+
);
|
|
7657
|
+
}
|
|
7658
|
+
for (const recoveredOwner of recoveredOwners) {
|
|
7659
|
+
recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
|
|
7660
|
+
storePath,
|
|
7661
|
+
`${storePath}.meta.json`
|
|
7662
|
+
]);
|
|
7663
|
+
}
|
|
7664
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project") {
|
|
7665
|
+
await this.resetLocalIndexArtifacts();
|
|
7666
|
+
}
|
|
7667
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7668
|
+
if (existsSync8(storePath) || existsSync8(vectorMetadataPath)) {
|
|
7669
|
+
this.store.load();
|
|
6762
7670
|
}
|
|
6763
7671
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
throw error;
|
|
7672
|
+
try {
|
|
7673
|
+
this.invertedIndex.load();
|
|
7674
|
+
} catch {
|
|
7675
|
+
if (existsSync8(invertedIndexPath)) {
|
|
7676
|
+
await fsPromises2.unlink(invertedIndexPath);
|
|
7677
|
+
}
|
|
7678
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6772
7679
|
}
|
|
7680
|
+
try {
|
|
7681
|
+
this.database = new Database(dbPath);
|
|
7682
|
+
} catch (error) {
|
|
7683
|
+
if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
|
|
7684
|
+
throw error;
|
|
7685
|
+
}
|
|
7686
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7687
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7688
|
+
this.database = new Database(dbPath);
|
|
7689
|
+
dbIsNew = true;
|
|
7690
|
+
}
|
|
7691
|
+
} else {
|
|
6773
7692
|
this.store = new VectorStore(storePath, dimensions);
|
|
7693
|
+
const vectorStoreExists = existsSync8(storePath);
|
|
7694
|
+
const vectorMetadataExists = existsSync8(vectorMetadataPath);
|
|
7695
|
+
const vectorReadFailureMessage = this.getVectorReadIssueMessage();
|
|
7696
|
+
if (vectorStoreExists !== vectorMetadataExists) {
|
|
7697
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage);
|
|
7698
|
+
} else if (vectorStoreExists) {
|
|
7699
|
+
try {
|
|
7700
|
+
this.store.loadStrict();
|
|
7701
|
+
} catch (error) {
|
|
7702
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage, error);
|
|
7703
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
6774
7706
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6775
|
-
|
|
6776
|
-
|
|
7707
|
+
if (existsSync8(invertedIndexPath)) {
|
|
7708
|
+
try {
|
|
7709
|
+
this.invertedIndex.load();
|
|
7710
|
+
} catch (error) {
|
|
7711
|
+
this.recordReadIssue(
|
|
7712
|
+
"keyword",
|
|
7713
|
+
this.getKeywordReadIssueMessage(),
|
|
7714
|
+
error
|
|
7715
|
+
);
|
|
7716
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7717
|
+
}
|
|
7718
|
+
} else if (this.store.count() > 0) {
|
|
7719
|
+
this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7720
|
+
}
|
|
7721
|
+
if (existsSync8(dbPath)) {
|
|
7722
|
+
try {
|
|
7723
|
+
this.database = Database.openReadOnly(dbPath);
|
|
7724
|
+
} catch (error) {
|
|
7725
|
+
this.recordReadIssue(
|
|
7726
|
+
"database",
|
|
7727
|
+
this.getDatabaseReadIssueMessage(),
|
|
7728
|
+
error
|
|
7729
|
+
);
|
|
7730
|
+
this.database = Database.createEmptyReadOnly();
|
|
7731
|
+
}
|
|
7732
|
+
} else {
|
|
7733
|
+
this.database = Database.createEmptyReadOnly();
|
|
7734
|
+
if (this.store.count() > 0) {
|
|
7735
|
+
this.recordReadIssue(
|
|
7736
|
+
"database",
|
|
7737
|
+
`Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
|
|
7738
|
+
);
|
|
7739
|
+
}
|
|
7740
|
+
}
|
|
6777
7741
|
}
|
|
6778
7742
|
if (isGitRepo(this.projectRoot)) {
|
|
6779
7743
|
this.currentBranch = getBranchOrDefault(this.projectRoot);
|
|
@@ -6787,10 +7751,10 @@ var Indexer = class {
|
|
|
6787
7751
|
this.baseBranch = "default";
|
|
6788
7752
|
this.logger.branch("debug", "Not a git repository, using default branch");
|
|
6789
7753
|
}
|
|
6790
|
-
if (
|
|
6791
|
-
await this.
|
|
7754
|
+
if (mode === "writer" && recoveredOwners.length > 0) {
|
|
7755
|
+
await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
|
|
6792
7756
|
}
|
|
6793
|
-
if (dbIsNew && this.store.count() > 0) {
|
|
7757
|
+
if (mode === "writer" && dbIsNew && this.store.count() > 0) {
|
|
6794
7758
|
this.migrateFromLegacyIndex();
|
|
6795
7759
|
}
|
|
6796
7760
|
this.loadFileHashCache();
|
|
@@ -6802,9 +7766,11 @@ var Indexer = class {
|
|
|
6802
7766
|
configuredProviderInfo: this.configuredProviderInfo
|
|
6803
7767
|
});
|
|
6804
7768
|
}
|
|
6805
|
-
if (this.config.indexing.autoGc) {
|
|
7769
|
+
if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
|
|
6806
7770
|
await this.maybeRunAutoGc();
|
|
6807
7771
|
}
|
|
7772
|
+
this.initializationMode = mode;
|
|
7773
|
+
this.readerArtifactFingerprint = readerArtifactFingerprint;
|
|
6808
7774
|
}
|
|
6809
7775
|
async maybeRunAutoGc() {
|
|
6810
7776
|
if (!this.database) return;
|
|
@@ -6821,7 +7787,7 @@ var Indexer = class {
|
|
|
6821
7787
|
}
|
|
6822
7788
|
}
|
|
6823
7789
|
if (shouldRunGc) {
|
|
6824
|
-
const result = await this.
|
|
7790
|
+
const result = await this.healthCheckUnlocked();
|
|
6825
7791
|
if (result.warning) {
|
|
6826
7792
|
this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
|
|
6827
7793
|
} else {
|
|
@@ -6843,7 +7809,7 @@ var Indexer = class {
|
|
|
6843
7809
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6844
7810
|
return {
|
|
6845
7811
|
resetCorruptedIndex: true,
|
|
6846
|
-
warning: this.getCorruptedIndexWarning(
|
|
7812
|
+
warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
|
|
6847
7813
|
};
|
|
6848
7814
|
}
|
|
6849
7815
|
throw error;
|
|
@@ -6858,28 +7824,29 @@ var Indexer = class {
|
|
|
6858
7824
|
return;
|
|
6859
7825
|
}
|
|
6860
7826
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6861
|
-
const storeBasePath =
|
|
6862
|
-
const storeIndexPath =
|
|
7827
|
+
const storeBasePath = path13.join(this.indexPath, "vectors");
|
|
7828
|
+
const storeIndexPath = storeBasePath;
|
|
6863
7829
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6864
|
-
const
|
|
6865
|
-
const
|
|
7830
|
+
const lease = this.requireActiveLease();
|
|
7831
|
+
const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
|
|
7832
|
+
const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
|
|
6866
7833
|
let backedUpIndex = false;
|
|
6867
7834
|
let backedUpMetadata = false;
|
|
6868
7835
|
let rebuiltCount = 0;
|
|
6869
7836
|
let skippedCount = 0;
|
|
6870
|
-
if (
|
|
7837
|
+
if (existsSync8(backupIndexPath)) {
|
|
6871
7838
|
unlinkSync(backupIndexPath);
|
|
6872
7839
|
}
|
|
6873
|
-
if (
|
|
7840
|
+
if (existsSync8(backupMetadataPath)) {
|
|
6874
7841
|
unlinkSync(backupMetadataPath);
|
|
6875
7842
|
}
|
|
6876
7843
|
try {
|
|
6877
|
-
if (
|
|
6878
|
-
|
|
7844
|
+
if (existsSync8(storeIndexPath)) {
|
|
7845
|
+
renameSync2(storeIndexPath, backupIndexPath);
|
|
6879
7846
|
backedUpIndex = true;
|
|
6880
7847
|
}
|
|
6881
|
-
if (
|
|
6882
|
-
|
|
7848
|
+
if (existsSync8(storeMetadataPath)) {
|
|
7849
|
+
renameSync2(storeMetadataPath, backupMetadataPath);
|
|
6883
7850
|
backedUpMetadata = true;
|
|
6884
7851
|
}
|
|
6885
7852
|
store.clear();
|
|
@@ -6899,10 +7866,10 @@ var Indexer = class {
|
|
|
6899
7866
|
rebuiltCount += 1;
|
|
6900
7867
|
}
|
|
6901
7868
|
store.save();
|
|
6902
|
-
if (backedUpIndex &&
|
|
7869
|
+
if (backedUpIndex && existsSync8(backupIndexPath)) {
|
|
6903
7870
|
unlinkSync(backupIndexPath);
|
|
6904
7871
|
}
|
|
6905
|
-
if (backedUpMetadata &&
|
|
7872
|
+
if (backedUpMetadata && existsSync8(backupMetadataPath)) {
|
|
6906
7873
|
unlinkSync(backupMetadataPath);
|
|
6907
7874
|
}
|
|
6908
7875
|
this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
|
|
@@ -6915,17 +7882,17 @@ var Indexer = class {
|
|
|
6915
7882
|
store.clear();
|
|
6916
7883
|
} catch {
|
|
6917
7884
|
}
|
|
6918
|
-
if (
|
|
7885
|
+
if (existsSync8(storeIndexPath)) {
|
|
6919
7886
|
unlinkSync(storeIndexPath);
|
|
6920
7887
|
}
|
|
6921
|
-
if (
|
|
7888
|
+
if (existsSync8(storeMetadataPath)) {
|
|
6922
7889
|
unlinkSync(storeMetadataPath);
|
|
6923
7890
|
}
|
|
6924
|
-
if (backedUpIndex &&
|
|
6925
|
-
|
|
7891
|
+
if (backedUpIndex && existsSync8(backupIndexPath)) {
|
|
7892
|
+
renameSync2(backupIndexPath, storeIndexPath);
|
|
6926
7893
|
}
|
|
6927
|
-
if (backedUpMetadata &&
|
|
6928
|
-
|
|
7894
|
+
if (backedUpMetadata && existsSync8(backupMetadataPath)) {
|
|
7895
|
+
renameSync2(backupMetadataPath, storeMetadataPath);
|
|
6929
7896
|
}
|
|
6930
7897
|
if (backedUpIndex || backedUpMetadata) {
|
|
6931
7898
|
store.load();
|
|
@@ -6939,11 +7906,37 @@ var Indexer = class {
|
|
|
6939
7906
|
}
|
|
6940
7907
|
return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
|
|
6941
7908
|
}
|
|
7909
|
+
async resetLocalIndexArtifacts() {
|
|
7910
|
+
this.store = null;
|
|
7911
|
+
this.invertedIndex = null;
|
|
7912
|
+
this.database?.close();
|
|
7913
|
+
this.database = null;
|
|
7914
|
+
this.indexCompatibility = null;
|
|
7915
|
+
this.initializationMode = "none";
|
|
7916
|
+
this.readIssues = [];
|
|
7917
|
+
this.readerArtifactFingerprint = null;
|
|
7918
|
+
this.writerArtifactFingerprint = null;
|
|
7919
|
+
this.readerArtifactRetryAfter.clear();
|
|
7920
|
+
this.fileHashCache.clear();
|
|
7921
|
+
const resetPaths = [
|
|
7922
|
+
path13.join(this.indexPath, "codebase.db"),
|
|
7923
|
+
path13.join(this.indexPath, "codebase.db-shm"),
|
|
7924
|
+
path13.join(this.indexPath, "codebase.db-wal"),
|
|
7925
|
+
path13.join(this.indexPath, "vectors"),
|
|
7926
|
+
path13.join(this.indexPath, "vectors.usearch"),
|
|
7927
|
+
path13.join(this.indexPath, "vectors.meta.json"),
|
|
7928
|
+
path13.join(this.indexPath, "inverted-index.json"),
|
|
7929
|
+
path13.join(this.indexPath, "file-hashes.json"),
|
|
7930
|
+
path13.join(this.indexPath, "failed-batches.json")
|
|
7931
|
+
];
|
|
7932
|
+
await Promise.all(resetPaths.map((targetPath) => fsPromises2.rm(targetPath, { recursive: true, force: true })));
|
|
7933
|
+
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7934
|
+
}
|
|
6942
7935
|
async tryResetCorruptedIndex(stage, error) {
|
|
6943
7936
|
if (!isSqliteCorruptionError(error)) {
|
|
6944
7937
|
return false;
|
|
6945
7938
|
}
|
|
6946
|
-
const dbPath =
|
|
7939
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
6947
7940
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
6948
7941
|
const errorMessage = getErrorMessage2(error);
|
|
6949
7942
|
if (this.config.scope === "global") {
|
|
@@ -6959,30 +7952,7 @@ var Indexer = class {
|
|
|
6959
7952
|
dbPath,
|
|
6960
7953
|
error: errorMessage
|
|
6961
7954
|
});
|
|
6962
|
-
this.
|
|
6963
|
-
this.invertedIndex = null;
|
|
6964
|
-
this.database?.close();
|
|
6965
|
-
this.database = null;
|
|
6966
|
-
this.indexCompatibility = null;
|
|
6967
|
-
this.fileHashCache.clear();
|
|
6968
|
-
const resetPaths = [
|
|
6969
|
-
path11.join(this.indexPath, "codebase.db"),
|
|
6970
|
-
path11.join(this.indexPath, "codebase.db-shm"),
|
|
6971
|
-
path11.join(this.indexPath, "codebase.db-wal"),
|
|
6972
|
-
path11.join(this.indexPath, "vectors.usearch"),
|
|
6973
|
-
path11.join(this.indexPath, "inverted-index.json"),
|
|
6974
|
-
path11.join(this.indexPath, "file-hashes.json"),
|
|
6975
|
-
path11.join(this.indexPath, "failed-batches.json"),
|
|
6976
|
-
path11.join(this.indexPath, "indexing.lock"),
|
|
6977
|
-
path11.join(this.indexPath, "vectors")
|
|
6978
|
-
];
|
|
6979
|
-
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
6980
|
-
try {
|
|
6981
|
-
await fsPromises2.rm(targetPath, { recursive: true, force: true });
|
|
6982
|
-
} catch {
|
|
6983
|
-
}
|
|
6984
|
-
}));
|
|
6985
|
-
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7955
|
+
await this.resetLocalIndexArtifacts();
|
|
6986
7956
|
return true;
|
|
6987
7957
|
}
|
|
6988
7958
|
migrateFromLegacyIndex() {
|
|
@@ -7101,8 +8071,62 @@ var Indexer = class {
|
|
|
7101
8071
|
return this.indexCompatibility;
|
|
7102
8072
|
}
|
|
7103
8073
|
async ensureInitialized() {
|
|
8074
|
+
let initializedReader = false;
|
|
8075
|
+
while (true) {
|
|
8076
|
+
if (this.initializationPromise) {
|
|
8077
|
+
await this.initializationPromise;
|
|
8078
|
+
}
|
|
8079
|
+
if (!this.isInitializedFor("reader")) {
|
|
8080
|
+
await this.initialize();
|
|
8081
|
+
initializedReader = true;
|
|
8082
|
+
continue;
|
|
8083
|
+
}
|
|
8084
|
+
if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
|
|
8085
|
+
if (!this.refreshInactiveWriterArtifacts()) {
|
|
8086
|
+
this.resetLoadedIndexState(true);
|
|
8087
|
+
await this.initialize();
|
|
8088
|
+
initializedReader = true;
|
|
8089
|
+
continue;
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
if (this.initializationMode === "reader" && !initializedReader) {
|
|
8093
|
+
this.refreshReaderArtifacts();
|
|
8094
|
+
}
|
|
8095
|
+
const state = this.requireLoadedIndexState();
|
|
8096
|
+
return {
|
|
8097
|
+
...state,
|
|
8098
|
+
readIssues: [...this.readIssues],
|
|
8099
|
+
compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
|
|
8100
|
+
};
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
async ensureInitializedUnlocked(recoveredOwners = []) {
|
|
8104
|
+
this.requireActiveLease();
|
|
8105
|
+
if (this.initializationPromise) {
|
|
8106
|
+
await this.initializationPromise;
|
|
8107
|
+
}
|
|
8108
|
+
if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
|
|
8109
|
+
const retireReaderDatabase = this.initializationMode === "reader";
|
|
8110
|
+
this.resetLoadedIndexState(retireReaderDatabase);
|
|
8111
|
+
await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
|
|
8112
|
+
} else {
|
|
8113
|
+
this.refreshLoadedIndexState();
|
|
8114
|
+
}
|
|
8115
|
+
if (this.config.indexing.autoGc) {
|
|
8116
|
+
await this.maybeRunAutoGc();
|
|
8117
|
+
}
|
|
8118
|
+
return this.requireLoadedIndexState();
|
|
8119
|
+
}
|
|
8120
|
+
requireReadableComponents(readIssues, ...components) {
|
|
8121
|
+
const componentSet = new Set(components);
|
|
8122
|
+
const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
|
|
8123
|
+
if (issues.length > 0) {
|
|
8124
|
+
throw new Error(issues.map((issue) => issue.message).join(" "));
|
|
8125
|
+
}
|
|
8126
|
+
}
|
|
8127
|
+
requireLoadedIndexState() {
|
|
7104
8128
|
if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
|
|
7105
|
-
|
|
8129
|
+
throw new Error("Index state is not initialized");
|
|
7106
8130
|
}
|
|
7107
8131
|
return {
|
|
7108
8132
|
store: this.store,
|
|
@@ -7126,7 +8150,12 @@ var Indexer = class {
|
|
|
7126
8150
|
return createCostEstimate(files, configuredProviderInfo);
|
|
7127
8151
|
}
|
|
7128
8152
|
async index(onProgress) {
|
|
7129
|
-
|
|
8153
|
+
return this.withIndexMutationLease("index", async (recoveredOwners) => {
|
|
8154
|
+
return this.indexUnlocked(onProgress, recoveredOwners);
|
|
8155
|
+
});
|
|
8156
|
+
}
|
|
8157
|
+
async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
|
|
8158
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
|
|
7130
8159
|
const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
7131
8160
|
const branchCatalogKey = this.getBranchCatalogKey();
|
|
7132
8161
|
const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
|
|
@@ -7136,7 +8165,6 @@ var Indexer = class {
|
|
|
7136
8165
|
`${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
|
|
7137
8166
|
);
|
|
7138
8167
|
}
|
|
7139
|
-
this.acquireIndexingLock();
|
|
7140
8168
|
this.logger.recordIndexingStart();
|
|
7141
8169
|
this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
|
|
7142
8170
|
const startTime = Date.now();
|
|
@@ -7211,6 +8239,7 @@ var Indexer = class {
|
|
|
7211
8239
|
this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
|
|
7212
8240
|
const existingChunks = /* @__PURE__ */ new Map();
|
|
7213
8241
|
const existingChunksByFile = /* @__PURE__ */ new Map();
|
|
8242
|
+
const existingMetadataById = /* @__PURE__ */ new Map();
|
|
7214
8243
|
for (const { key, metadata } of store.getAllMetadata()) {
|
|
7215
8244
|
if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
|
|
7216
8245
|
continue;
|
|
@@ -7219,6 +8248,7 @@ var Indexer = class {
|
|
|
7219
8248
|
continue;
|
|
7220
8249
|
}
|
|
7221
8250
|
existingChunks.set(key, metadata.hash);
|
|
8251
|
+
existingMetadataById.set(key, metadata);
|
|
7222
8252
|
const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
|
|
7223
8253
|
fileChunks.add(key);
|
|
7224
8254
|
existingChunksByFile.set(metadata.filePath, fileChunks);
|
|
@@ -7226,6 +8256,8 @@ var Indexer = class {
|
|
|
7226
8256
|
const currentChunkIds = /* @__PURE__ */ new Set();
|
|
7227
8257
|
const currentFilePaths = /* @__PURE__ */ new Set();
|
|
7228
8258
|
const pendingChunks = [];
|
|
8259
|
+
const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
|
|
8260
|
+
let backfilledBlameMetadata = false;
|
|
7229
8261
|
for (const filePath of unchangedFilePaths) {
|
|
7230
8262
|
currentFilePaths.add(filePath);
|
|
7231
8263
|
const fileChunks = existingChunksByFile.get(filePath);
|
|
@@ -7236,10 +8268,52 @@ var Indexer = class {
|
|
|
7236
8268
|
}
|
|
7237
8269
|
}
|
|
7238
8270
|
const chunkDataBatch = [];
|
|
8271
|
+
if (gitBlameEnabled) {
|
|
8272
|
+
const backfillItems = [];
|
|
8273
|
+
for (const chunkId of currentChunkIds) {
|
|
8274
|
+
const metadata = existingMetadataById.get(chunkId);
|
|
8275
|
+
if (!metadata || hasBlameMetadata(metadata)) {
|
|
8276
|
+
continue;
|
|
8277
|
+
}
|
|
8278
|
+
const chunk = database.getChunk(chunkId);
|
|
8279
|
+
if (!chunk) {
|
|
8280
|
+
continue;
|
|
8281
|
+
}
|
|
8282
|
+
const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
|
|
8283
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
8284
|
+
if (!blameMetadata.blameSha) {
|
|
8285
|
+
continue;
|
|
8286
|
+
}
|
|
8287
|
+
chunkDataBatch.push({
|
|
8288
|
+
...chunk,
|
|
8289
|
+
blameSha: blameMetadata.blameSha,
|
|
8290
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8291
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8292
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8293
|
+
blameSummary: blameMetadata.blameSummary
|
|
8294
|
+
});
|
|
8295
|
+
const embeddingBuffer = database.getEmbedding(chunk.contentHash);
|
|
8296
|
+
if (!embeddingBuffer) {
|
|
8297
|
+
continue;
|
|
8298
|
+
}
|
|
8299
|
+
backfillItems.push({
|
|
8300
|
+
id: chunkId,
|
|
8301
|
+
vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
|
|
8302
|
+
metadata: {
|
|
8303
|
+
...metadata,
|
|
8304
|
+
...blameMetadata
|
|
8305
|
+
}
|
|
8306
|
+
});
|
|
8307
|
+
}
|
|
8308
|
+
if (backfillItems.length > 0) {
|
|
8309
|
+
store.addBatch(backfillItems);
|
|
8310
|
+
backfilledBlameMetadata = true;
|
|
8311
|
+
}
|
|
8312
|
+
}
|
|
7239
8313
|
for (const parsed of parsedFiles) {
|
|
7240
8314
|
currentFilePaths.add(parsed.path);
|
|
7241
8315
|
if (parsed.chunks.length === 0) {
|
|
7242
|
-
const relativePath =
|
|
8316
|
+
const relativePath = path13.relative(this.projectRoot, parsed.path);
|
|
7243
8317
|
stats.parseFailures.push(relativePath);
|
|
7244
8318
|
}
|
|
7245
8319
|
let fileChunkCount = 0;
|
|
@@ -7260,6 +8334,10 @@ var Indexer = class {
|
|
|
7260
8334
|
}
|
|
7261
8335
|
const id = generateChunkId(parsed.path, chunk);
|
|
7262
8336
|
const contentHash = generateChunkHash(chunk);
|
|
8337
|
+
const existingContentHash = existingChunks.get(id);
|
|
8338
|
+
const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
|
|
8339
|
+
const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
|
|
8340
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
7263
8341
|
currentChunkIds.add(id);
|
|
7264
8342
|
chunkDataBatch.push({
|
|
7265
8343
|
chunkId: id,
|
|
@@ -7269,9 +8347,14 @@ var Indexer = class {
|
|
|
7269
8347
|
endLine: chunk.endLine,
|
|
7270
8348
|
nodeType: chunk.chunkType,
|
|
7271
8349
|
name: chunk.name,
|
|
7272
|
-
language: chunk.language
|
|
8350
|
+
language: chunk.language,
|
|
8351
|
+
blameSha: blameMetadata.blameSha,
|
|
8352
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8353
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8354
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8355
|
+
blameSummary: blameMetadata.blameSummary
|
|
7273
8356
|
});
|
|
7274
|
-
if (
|
|
8357
|
+
if (existingContentHash === contentHash) {
|
|
7275
8358
|
fileChunkCount++;
|
|
7276
8359
|
continue;
|
|
7277
8360
|
}
|
|
@@ -7286,7 +8369,8 @@ var Indexer = class {
|
|
|
7286
8369
|
chunkType: chunk.chunkType,
|
|
7287
8370
|
name: chunk.name,
|
|
7288
8371
|
language: chunk.language,
|
|
7289
|
-
hash: contentHash
|
|
8372
|
+
hash: contentHash,
|
|
8373
|
+
...blameMetadata
|
|
7290
8374
|
};
|
|
7291
8375
|
pendingChunks.push({
|
|
7292
8376
|
id,
|
|
@@ -7429,6 +8513,11 @@ var Indexer = class {
|
|
|
7429
8513
|
database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
|
|
7430
8514
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7431
8515
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
8516
|
+
const vectorPath = path13.join(this.indexPath, "vectors");
|
|
8517
|
+
const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync8(vectorPath) && existsSync8(`${vectorPath}.meta.json`);
|
|
8518
|
+
if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
|
|
8519
|
+
store.save();
|
|
8520
|
+
}
|
|
7432
8521
|
if (scopedRoots) {
|
|
7433
8522
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7434
8523
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7447,7 +8536,6 @@ var Indexer = class {
|
|
|
7447
8536
|
chunksProcessed: 0,
|
|
7448
8537
|
totalChunks: 0
|
|
7449
8538
|
});
|
|
7450
|
-
this.releaseIndexingLock();
|
|
7451
8539
|
return stats;
|
|
7452
8540
|
}
|
|
7453
8541
|
if (pendingChunks.length === 0) {
|
|
@@ -7456,7 +8544,7 @@ var Indexer = class {
|
|
|
7456
8544
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7457
8545
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7458
8546
|
store.save();
|
|
7459
|
-
|
|
8547
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7460
8548
|
if (scopedRoots) {
|
|
7461
8549
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7462
8550
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7475,7 +8563,6 @@ var Indexer = class {
|
|
|
7475
8563
|
chunksProcessed: 0,
|
|
7476
8564
|
totalChunks: 0
|
|
7477
8565
|
});
|
|
7478
|
-
this.releaseIndexingLock();
|
|
7479
8566
|
return stats;
|
|
7480
8567
|
}
|
|
7481
8568
|
onProgress?.({
|
|
@@ -7706,7 +8793,7 @@ var Indexer = class {
|
|
|
7706
8793
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7707
8794
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7708
8795
|
store.save();
|
|
7709
|
-
|
|
8796
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7710
8797
|
if (scopedRoots) {
|
|
7711
8798
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7712
8799
|
} else {
|
|
@@ -7758,7 +8845,6 @@ var Indexer = class {
|
|
|
7758
8845
|
chunksProcessed: stats.indexedChunks,
|
|
7759
8846
|
totalChunks: pendingChunks.length
|
|
7760
8847
|
});
|
|
7761
|
-
this.releaseIndexingLock();
|
|
7762
8848
|
return stats;
|
|
7763
8849
|
}
|
|
7764
8850
|
async getQueryEmbedding(query, provider) {
|
|
@@ -7824,8 +8910,8 @@ var Indexer = class {
|
|
|
7824
8910
|
return intersection / union;
|
|
7825
8911
|
}
|
|
7826
8912
|
async search(query, limit, options) {
|
|
7827
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
7828
|
-
|
|
8913
|
+
const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8914
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
7829
8915
|
if (!compatibility.compatible) {
|
|
7830
8916
|
throw new Error(
|
|
7831
8917
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
|
|
@@ -7839,6 +8925,7 @@ var Indexer = class {
|
|
|
7839
8925
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
7840
8926
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
7841
8927
|
const fusionStrategy = this.config.search.fusionStrategy;
|
|
8928
|
+
const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
|
|
7842
8929
|
const rrfK = this.config.search.rrfK;
|
|
7843
8930
|
const rerankTopN = this.config.search.rerankTopN;
|
|
7844
8931
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
@@ -7847,7 +8934,7 @@ var Indexer = class {
|
|
|
7847
8934
|
this.logger.search("debug", "Starting search", {
|
|
7848
8935
|
query,
|
|
7849
8936
|
maxResults,
|
|
7850
|
-
hybridWeight,
|
|
8937
|
+
hybridWeight: effectiveHybridWeight,
|
|
7851
8938
|
fusionStrategy,
|
|
7852
8939
|
rrfK,
|
|
7853
8940
|
rerankTopN,
|
|
@@ -7861,7 +8948,7 @@ var Indexer = class {
|
|
|
7861
8948
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
7862
8949
|
const vectorMs = performance2.now() - vectorStartTime;
|
|
7863
8950
|
const keywordStartTime = performance2.now();
|
|
7864
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
8951
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
7865
8952
|
const keywordMs = performance2.now() - keywordStartTime;
|
|
7866
8953
|
let branchChunkIds = null;
|
|
7867
8954
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -7898,7 +8985,7 @@ var Indexer = class {
|
|
|
7898
8985
|
rrfK,
|
|
7899
8986
|
rerankTopN,
|
|
7900
8987
|
limit: maxResults,
|
|
7901
|
-
hybridWeight,
|
|
8988
|
+
hybridWeight: effectiveHybridWeight,
|
|
7902
8989
|
prioritizeSourcePaths: sourceIntent
|
|
7903
8990
|
});
|
|
7904
8991
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -7992,13 +9079,13 @@ var Indexer = class {
|
|
|
7992
9079
|
content,
|
|
7993
9080
|
score: r.score,
|
|
7994
9081
|
chunkType: r.metadata.chunkType,
|
|
7995
|
-
name: r.metadata.name
|
|
9082
|
+
name: r.metadata.name,
|
|
9083
|
+
blame: blameFromMetadata(r.metadata)
|
|
7996
9084
|
};
|
|
7997
9085
|
})
|
|
7998
9086
|
);
|
|
7999
9087
|
}
|
|
8000
|
-
async keywordSearch(query, limit) {
|
|
8001
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9088
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
8002
9089
|
const scores = invertedIndex.search(query);
|
|
8003
9090
|
if (scores.size === 0) {
|
|
8004
9091
|
return [];
|
|
@@ -8016,24 +9103,54 @@ var Indexer = class {
|
|
|
8016
9103
|
return results.slice(0, limit);
|
|
8017
9104
|
}
|
|
8018
9105
|
async getStatus() {
|
|
8019
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9106
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8020
9107
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9108
|
+
const vectorCount = store.count();
|
|
9109
|
+
const statusReadIssues = [...readIssues];
|
|
9110
|
+
let startupWarning = "";
|
|
9111
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9112
|
+
try {
|
|
9113
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9114
|
+
} catch (error) {
|
|
9115
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9116
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9117
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9118
|
+
this.recordReadIssue("database", message, error);
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9123
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9124
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
8021
9125
|
return {
|
|
8022
|
-
indexed:
|
|
8023
|
-
vectorCount
|
|
9126
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9127
|
+
vectorCount,
|
|
8024
9128
|
provider: configuredProviderInfo.provider,
|
|
8025
9129
|
model: configuredProviderInfo.modelInfo.model,
|
|
8026
9130
|
indexPath: this.indexPath,
|
|
8027
9131
|
currentBranch: this.currentBranch,
|
|
8028
9132
|
baseBranch: this.baseBranch,
|
|
8029
|
-
compatibility
|
|
9133
|
+
compatibility,
|
|
8030
9134
|
failedBatchesCount,
|
|
8031
9135
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
8032
|
-
warning:
|
|
9136
|
+
warning: warning || void 0
|
|
8033
9137
|
};
|
|
8034
9138
|
}
|
|
9139
|
+
async forceIndex(onProgress) {
|
|
9140
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9141
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9142
|
+
await this.clearIndexUnlocked();
|
|
9143
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9144
|
+
});
|
|
9145
|
+
}
|
|
8035
9146
|
async clearIndex() {
|
|
8036
|
-
|
|
9147
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9148
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9149
|
+
await this.clearIndexUnlocked();
|
|
9150
|
+
});
|
|
9151
|
+
}
|
|
9152
|
+
async clearIndexUnlocked() {
|
|
9153
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8037
9154
|
if (this.config.scope === "global") {
|
|
8038
9155
|
store.load();
|
|
8039
9156
|
invertedIndex.load();
|
|
@@ -8060,7 +9177,7 @@ var Indexer = class {
|
|
|
8060
9177
|
store.clear();
|
|
8061
9178
|
store.save();
|
|
8062
9179
|
invertedIndex.clear();
|
|
8063
|
-
|
|
9180
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8064
9181
|
this.fileHashCache.clear();
|
|
8065
9182
|
this.saveFileHashCache();
|
|
8066
9183
|
database.clearAllIndexedData();
|
|
@@ -8084,14 +9201,7 @@ var Indexer = class {
|
|
|
8084
9201
|
this.indexCompatibility = compatibility;
|
|
8085
9202
|
return;
|
|
8086
9203
|
}
|
|
8087
|
-
|
|
8088
|
-
if (this.host !== "opencode") {
|
|
8089
|
-
localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
8090
|
-
}
|
|
8091
|
-
const isLocalProjectIndex = localProjectIndexPaths.some(
|
|
8092
|
-
(localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
|
|
8093
|
-
);
|
|
8094
|
-
if (!isLocalProjectIndex) {
|
|
9204
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8095
9205
|
throw new Error(
|
|
8096
9206
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8097
9207
|
);
|
|
@@ -8099,7 +9209,7 @@ var Indexer = class {
|
|
|
8099
9209
|
store.clear();
|
|
8100
9210
|
store.save();
|
|
8101
9211
|
invertedIndex.clear();
|
|
8102
|
-
|
|
9212
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8103
9213
|
this.fileHashCache.clear();
|
|
8104
9214
|
this.saveFileHashCache();
|
|
8105
9215
|
database.clearAllIndexedData();
|
|
@@ -8117,7 +9227,13 @@ var Indexer = class {
|
|
|
8117
9227
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8118
9228
|
}
|
|
8119
9229
|
async healthCheck() {
|
|
8120
|
-
|
|
9230
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9231
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9232
|
+
return this.healthCheckUnlocked();
|
|
9233
|
+
});
|
|
9234
|
+
}
|
|
9235
|
+
async healthCheckUnlocked() {
|
|
9236
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8121
9237
|
this.logger.gc("info", "Starting health check");
|
|
8122
9238
|
const allMetadata = store.getAllMetadata();
|
|
8123
9239
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8130,7 +9246,7 @@ var Indexer = class {
|
|
|
8130
9246
|
const removedChunkKeys = [];
|
|
8131
9247
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8132
9248
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8133
|
-
if (!
|
|
9249
|
+
if (!existsSync8(filePath)) {
|
|
8134
9250
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8135
9251
|
for (const key of chunkKeys) {
|
|
8136
9252
|
removedChunkKeys.push(key);
|
|
@@ -8155,7 +9271,7 @@ var Indexer = class {
|
|
|
8155
9271
|
const removedCount = removedChunkKeys.length;
|
|
8156
9272
|
if (removedCount > 0) {
|
|
8157
9273
|
store.save();
|
|
8158
|
-
|
|
9274
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8159
9275
|
}
|
|
8160
9276
|
let gcOrphanEmbeddings;
|
|
8161
9277
|
let gcOrphanChunks;
|
|
@@ -8170,7 +9286,7 @@ var Indexer = class {
|
|
|
8170
9286
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8171
9287
|
throw error;
|
|
8172
9288
|
}
|
|
8173
|
-
await this.
|
|
9289
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8174
9290
|
return {
|
|
8175
9291
|
removed: 0,
|
|
8176
9292
|
filePaths: [],
|
|
@@ -8179,7 +9295,7 @@ var Indexer = class {
|
|
|
8179
9295
|
gcOrphanSymbols: 0,
|
|
8180
9296
|
gcOrphanCallEdges: 0,
|
|
8181
9297
|
resetCorruptedIndex: true,
|
|
8182
|
-
warning: this.getCorruptedIndexWarning(
|
|
9298
|
+
warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
|
|
8183
9299
|
};
|
|
8184
9300
|
}
|
|
8185
9301
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8192,7 +9308,13 @@ var Indexer = class {
|
|
|
8192
9308
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8193
9309
|
}
|
|
8194
9310
|
async retryFailedBatches() {
|
|
8195
|
-
|
|
9311
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9312
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9313
|
+
return this.retryFailedBatchesUnlocked();
|
|
9314
|
+
});
|
|
9315
|
+
}
|
|
9316
|
+
async retryFailedBatchesUnlocked() {
|
|
9317
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8196
9318
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8197
9319
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8198
9320
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8356,7 +9478,7 @@ var Indexer = class {
|
|
|
8356
9478
|
}
|
|
8357
9479
|
if (succeeded > 0) {
|
|
8358
9480
|
store.save();
|
|
8359
|
-
|
|
9481
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8360
9482
|
}
|
|
8361
9483
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8362
9484
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8384,15 +9506,16 @@ var Indexer = class {
|
|
|
8384
9506
|
}
|
|
8385
9507
|
}
|
|
8386
9508
|
async getDatabaseStats() {
|
|
8387
|
-
const { database } = await this.ensureInitialized();
|
|
9509
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9510
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8388
9511
|
return database.getStats();
|
|
8389
9512
|
}
|
|
8390
9513
|
getLogger() {
|
|
8391
9514
|
return this.logger;
|
|
8392
9515
|
}
|
|
8393
9516
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8394
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8395
|
-
|
|
9517
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9518
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8396
9519
|
if (!compatibility.compatible) {
|
|
8397
9520
|
throw new Error(
|
|
8398
9521
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8499,13 +9622,15 @@ var Indexer = class {
|
|
|
8499
9622
|
content,
|
|
8500
9623
|
score: r.score,
|
|
8501
9624
|
chunkType: r.metadata.chunkType,
|
|
8502
|
-
name: r.metadata.name
|
|
9625
|
+
name: r.metadata.name,
|
|
9626
|
+
blame: blameFromMetadata(r.metadata)
|
|
8503
9627
|
};
|
|
8504
9628
|
})
|
|
8505
9629
|
);
|
|
8506
9630
|
}
|
|
8507
9631
|
async getCallers(targetName, callTypeFilter) {
|
|
8508
|
-
const { database } = await this.ensureInitialized();
|
|
9632
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9633
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8509
9634
|
const seen = /* @__PURE__ */ new Set();
|
|
8510
9635
|
const results = [];
|
|
8511
9636
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8519,7 +9644,8 @@ var Indexer = class {
|
|
|
8519
9644
|
return results;
|
|
8520
9645
|
}
|
|
8521
9646
|
async getCallees(symbolId, callTypeFilter) {
|
|
8522
|
-
const { database } = await this.ensureInitialized();
|
|
9647
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9648
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8523
9649
|
const seen = /* @__PURE__ */ new Set();
|
|
8524
9650
|
const results = [];
|
|
8525
9651
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8533,44 +9659,51 @@ var Indexer = class {
|
|
|
8533
9659
|
return results;
|
|
8534
9660
|
}
|
|
8535
9661
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8536
|
-
const { database } = await this.ensureInitialized();
|
|
9662
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9663
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8537
9664
|
let shortest = [];
|
|
8538
9665
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8539
|
-
const
|
|
8540
|
-
if (
|
|
8541
|
-
shortest =
|
|
9666
|
+
const path24 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9667
|
+
if (path24.length > 0 && (shortest.length === 0 || path24.length < shortest.length)) {
|
|
9668
|
+
shortest = path24;
|
|
8542
9669
|
}
|
|
8543
9670
|
}
|
|
8544
9671
|
return shortest;
|
|
8545
9672
|
}
|
|
8546
9673
|
async getSymbolsForBranch(branch) {
|
|
8547
|
-
const { database } = await this.ensureInitialized();
|
|
9674
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9675
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8548
9676
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8549
9677
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8550
9678
|
}
|
|
8551
9679
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8552
|
-
const { database } = await this.ensureInitialized();
|
|
9680
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9681
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8553
9682
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8554
9683
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8555
9684
|
}
|
|
8556
9685
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8557
|
-
const { database } = await this.ensureInitialized();
|
|
9686
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9687
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8558
9688
|
const branch = this.getBranchCatalogKey();
|
|
8559
9689
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8560
9690
|
}
|
|
8561
9691
|
async detectCommunities(branch, symbolIds) {
|
|
8562
|
-
const { database } = await this.ensureInitialized();
|
|
9692
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9693
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8563
9694
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8564
9695
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8565
9696
|
}
|
|
8566
9697
|
async computeCentrality(branch) {
|
|
8567
|
-
const { database } = await this.ensureInitialized();
|
|
9698
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9699
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8568
9700
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8569
9701
|
return database.computeCentrality(resolvedBranch);
|
|
8570
9702
|
}
|
|
8571
9703
|
async getPrImpact(opts) {
|
|
8572
|
-
const { database } = await this.ensureInitialized();
|
|
8573
|
-
|
|
9704
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9705
|
+
this.requireReadableComponents(readIssues, "database");
|
|
9706
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
8574
9707
|
const changedFilesResult = await getChangedFiles({
|
|
8575
9708
|
pr: opts.pr,
|
|
8576
9709
|
branch: opts.branch,
|
|
@@ -8592,7 +9725,7 @@ var Indexer = class {
|
|
|
8592
9725
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8593
9726
|
);
|
|
8594
9727
|
}
|
|
8595
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9728
|
+
const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
|
|
8596
9729
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8597
9730
|
const directIds = directSymbols.map((s) => s.id);
|
|
8598
9731
|
const direction = opts.direction ?? "both";
|
|
@@ -8654,7 +9787,7 @@ var Indexer = class {
|
|
|
8654
9787
|
if (opts.checkConflicts) {
|
|
8655
9788
|
conflictingPRs = [];
|
|
8656
9789
|
try {
|
|
8657
|
-
const { stdout } = await
|
|
9790
|
+
const { stdout } = await execFileAsync3(
|
|
8658
9791
|
"gh",
|
|
8659
9792
|
["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
|
|
8660
9793
|
{ cwd: this.projectRoot, timeout: 3e4 }
|
|
@@ -8675,7 +9808,7 @@ var Indexer = class {
|
|
|
8675
9808
|
projectRoot: this.projectRoot,
|
|
8676
9809
|
baseBranch: this.baseBranch
|
|
8677
9810
|
});
|
|
8678
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9811
|
+
const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
|
|
8679
9812
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8680
9813
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8681
9814
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8725,7 +9858,8 @@ var Indexer = class {
|
|
|
8725
9858
|
};
|
|
8726
9859
|
}
|
|
8727
9860
|
async getVisualizationData(options) {
|
|
8728
|
-
const { database, store } = await this.ensureInitialized();
|
|
9861
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9862
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8729
9863
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8730
9864
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8731
9865
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8738,12 +9872,12 @@ var Indexer = class {
|
|
|
8738
9872
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8739
9873
|
}
|
|
8740
9874
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8741
|
-
const absoluteDirectoryFilter = directory ?
|
|
9875
|
+
const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
|
|
8742
9876
|
for (const filePath of filePaths) {
|
|
8743
9877
|
if (directory) {
|
|
8744
|
-
const absoluteFilePath =
|
|
9878
|
+
const absoluteFilePath = path13.resolve(filePath);
|
|
8745
9879
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8746
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9880
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
|
|
8747
9881
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8748
9882
|
continue;
|
|
8749
9883
|
}
|
|
@@ -8765,53 +9899,64 @@ var Indexer = class {
|
|
|
8765
9899
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
8766
9900
|
}
|
|
8767
9901
|
async close() {
|
|
8768
|
-
|
|
9902
|
+
this.database?.close();
|
|
9903
|
+
for (const database of this.retiredDatabases) {
|
|
9904
|
+
database.close();
|
|
9905
|
+
}
|
|
9906
|
+
this.retiredDatabases = [];
|
|
8769
9907
|
this.database = null;
|
|
8770
9908
|
this.store = null;
|
|
8771
9909
|
this.invertedIndex = null;
|
|
8772
9910
|
this.provider = null;
|
|
8773
9911
|
this.reranker = null;
|
|
9912
|
+
this.configuredProviderInfo = null;
|
|
9913
|
+
this.indexCompatibility = null;
|
|
9914
|
+
this.initializationMode = "none";
|
|
9915
|
+
this.readIssues = [];
|
|
9916
|
+
this.readerArtifactFingerprint = null;
|
|
9917
|
+
this.writerArtifactFingerprint = null;
|
|
9918
|
+
this.readerArtifactRetryAfter.clear();
|
|
8774
9919
|
}
|
|
8775
9920
|
};
|
|
8776
9921
|
|
|
8777
9922
|
// src/tools/knowledge-base-paths.ts
|
|
8778
|
-
import * as
|
|
9923
|
+
import * as path14 from "path";
|
|
8779
9924
|
function resolveConfigPathValue(value, baseDir) {
|
|
8780
9925
|
const trimmed = value.trim();
|
|
8781
9926
|
if (!trimmed) {
|
|
8782
9927
|
return trimmed;
|
|
8783
9928
|
}
|
|
8784
|
-
const absolutePath =
|
|
8785
|
-
return
|
|
9929
|
+
const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
|
|
9930
|
+
return path14.normalize(absolutePath);
|
|
8786
9931
|
}
|
|
8787
9932
|
function serializeConfigPathValue(value, baseDir) {
|
|
8788
9933
|
const trimmed = value.trim();
|
|
8789
9934
|
if (!trimmed) {
|
|
8790
9935
|
return trimmed;
|
|
8791
9936
|
}
|
|
8792
|
-
if (!
|
|
8793
|
-
return normalizePathSeparators(
|
|
9937
|
+
if (!path14.isAbsolute(trimmed)) {
|
|
9938
|
+
return normalizePathSeparators(path14.normalize(trimmed));
|
|
8794
9939
|
}
|
|
8795
|
-
const relativePath =
|
|
8796
|
-
if (!relativePath || !relativePath.startsWith("..") && !
|
|
8797
|
-
return normalizePathSeparators(
|
|
9940
|
+
const relativePath = path14.relative(baseDir, trimmed);
|
|
9941
|
+
if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
|
|
9942
|
+
return normalizePathSeparators(path14.normalize(relativePath || "."));
|
|
8798
9943
|
}
|
|
8799
|
-
return
|
|
9944
|
+
return path14.normalize(trimmed);
|
|
8800
9945
|
}
|
|
8801
9946
|
function resolveKnowledgeBasePath(value, projectRoot) {
|
|
8802
|
-
return
|
|
9947
|
+
return path14.isAbsolute(value) ? value : path14.resolve(projectRoot, value);
|
|
8803
9948
|
}
|
|
8804
9949
|
function normalizeKnowledgeBasePath2(value, projectRoot) {
|
|
8805
|
-
return
|
|
9950
|
+
return path14.normalize(resolveKnowledgeBasePath(value, projectRoot));
|
|
8806
9951
|
}
|
|
8807
9952
|
function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
|
|
8808
|
-
const normalizedInput =
|
|
9953
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
8809
9954
|
return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput);
|
|
8810
9955
|
}
|
|
8811
9956
|
function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
|
|
8812
|
-
const normalizedInput =
|
|
9957
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
8813
9958
|
return knowledgeBases.findIndex(
|
|
8814
|
-
(kb) =>
|
|
9959
|
+
(kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot) === normalizedInput
|
|
8815
9960
|
);
|
|
8816
9961
|
}
|
|
8817
9962
|
|
|
@@ -8911,6 +10056,10 @@ function formatStatus(status) {
|
|
|
8911
10056
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
8912
10057
|
}
|
|
8913
10058
|
}
|
|
10059
|
+
if (status.warning) {
|
|
10060
|
+
lines.push("");
|
|
10061
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
10062
|
+
}
|
|
8914
10063
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
8915
10064
|
lines.push("");
|
|
8916
10065
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -8963,7 +10112,7 @@ function formatCodebasePeek(results) {
|
|
|
8963
10112
|
const formatted = results.map((r, idx) => {
|
|
8964
10113
|
const location = `${r.filePath}:${r.startLine}-${r.endLine}`;
|
|
8965
10114
|
const name = r.name ? `"${r.name}"` : "(anonymous)";
|
|
8966
|
-
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})`;
|
|
10115
|
+
return `[${idx + 1}] ${r.chunkType} ${name} at ${location} (score: ${r.score.toFixed(2)})${formatBlame(r)}`;
|
|
8967
10116
|
});
|
|
8968
10117
|
return formatted.join("\n");
|
|
8969
10118
|
}
|
|
@@ -8995,16 +10144,57 @@ function formatHealthCheck(result) {
|
|
|
8995
10144
|
}
|
|
8996
10145
|
return lines.join("\n");
|
|
8997
10146
|
}
|
|
10147
|
+
function formatCallGraphCallers(name, callers, relationshipType) {
|
|
10148
|
+
if (callers.length === 0) {
|
|
10149
|
+
return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
|
|
10150
|
+
}
|
|
10151
|
+
const formatted = callers.map((edge, index) => {
|
|
10152
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
10153
|
+
return `[${index + 1}] \u2190 from ${edge.fromSymbolName ?? "<unknown>"} in ${edge.fromSymbolFilePath ?? "<unknown file>"} [${edge.fromSymbolId}] (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? " [resolved]" : " [unresolved]"}`;
|
|
10154
|
+
});
|
|
10155
|
+
return `"${name}" is called by ${callers.length} function(s):
|
|
10156
|
+
|
|
10157
|
+
${formatted.join("\n")}`;
|
|
10158
|
+
}
|
|
10159
|
+
function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
10160
|
+
if (callees.length === 0) {
|
|
10161
|
+
return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
|
|
10162
|
+
}
|
|
10163
|
+
return callees.map((edge, index) => {
|
|
10164
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
10165
|
+
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
10166
|
+
}).join("\n");
|
|
10167
|
+
}
|
|
10168
|
+
function formatCallGraphPath(from, to, path24) {
|
|
10169
|
+
if (path24.length === 0) {
|
|
10170
|
+
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
10171
|
+
}
|
|
10172
|
+
const formatted = path24.map((hop, index) => {
|
|
10173
|
+
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10174
|
+
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10175
|
+
return `${prefix} ${hop.symbolName}${location}`;
|
|
10176
|
+
});
|
|
10177
|
+
return `Path (${path24.length} hops):
|
|
10178
|
+
${formatted.join("\n")}`;
|
|
10179
|
+
}
|
|
8998
10180
|
function formatResultHeader(result, index) {
|
|
8999
10181
|
return result.name ? `[${index + 1}] ${result.chunkType} "${result.name}" in ${result.filePath}:${result.startLine}-${result.endLine}` : `[${index + 1}] ${result.chunkType} in ${result.filePath}:${result.startLine}-${result.endLine}`;
|
|
9000
10182
|
}
|
|
10183
|
+
function formatBlame(result) {
|
|
10184
|
+
if (!result.blame) {
|
|
10185
|
+
return "";
|
|
10186
|
+
}
|
|
10187
|
+
const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
|
|
10188
|
+
return `
|
|
10189
|
+
${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
|
|
10190
|
+
}
|
|
9001
10191
|
function formatDefinitionLookup(results, query) {
|
|
9002
10192
|
if (results.length === 0) {
|
|
9003
10193
|
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
9004
10194
|
}
|
|
9005
10195
|
const formatted = results.map((r, idx) => {
|
|
9006
10196
|
const header = formatResultHeader(r, idx);
|
|
9007
|
-
return `${header} (score: ${r.score.toFixed(2)})
|
|
10197
|
+
return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
|
|
9008
10198
|
\`\`\`
|
|
9009
10199
|
${truncateContent(r.content)}
|
|
9010
10200
|
\`\`\``;
|
|
@@ -9015,7 +10205,7 @@ function formatSearchResults(results, scoreFormat = "similarity") {
|
|
|
9015
10205
|
const formatted = results.map((r, idx) => {
|
|
9016
10206
|
const header = formatResultHeader(r, idx);
|
|
9017
10207
|
const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
|
|
9018
|
-
return `${header} ${scoreLabel}
|
|
10208
|
+
return `${header} ${scoreLabel}${formatBlame(r)}
|
|
9019
10209
|
\`\`\`
|
|
9020
10210
|
${truncateContent(r.content)}
|
|
9021
10211
|
\`\`\``;
|
|
@@ -9024,8 +10214,8 @@ ${truncateContent(r.content)}
|
|
|
9024
10214
|
}
|
|
9025
10215
|
|
|
9026
10216
|
// src/tools/config-state.ts
|
|
9027
|
-
import { existsSync as
|
|
9028
|
-
import * as
|
|
10217
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
10218
|
+
import * as path15 from "path";
|
|
9029
10219
|
function normalizeKnowledgeBasePaths(config, projectRoot) {
|
|
9030
10220
|
const normalized = { ...config };
|
|
9031
10221
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
@@ -9052,10 +10242,10 @@ function loadEditableConfig(projectRoot, host = "opencode") {
|
|
|
9052
10242
|
}
|
|
9053
10243
|
function saveConfig(projectRoot, config, host = "opencode") {
|
|
9054
10244
|
const configPath = getConfigPath(projectRoot, host);
|
|
9055
|
-
const configDir =
|
|
9056
|
-
const configBaseDir =
|
|
9057
|
-
if (!
|
|
9058
|
-
|
|
10245
|
+
const configDir = path15.dirname(configPath);
|
|
10246
|
+
const configBaseDir = path15.dirname(configDir);
|
|
10247
|
+
if (!existsSync9(configDir)) {
|
|
10248
|
+
mkdirSync4(configDir, { recursive: true });
|
|
9059
10249
|
}
|
|
9060
10250
|
const serializableConfig = { ...config };
|
|
9061
10251
|
if (Array.isArray(serializableConfig.knowledgeBases)) {
|
|
@@ -9063,13 +10253,31 @@ function saveConfig(projectRoot, config, host = "opencode") {
|
|
|
9063
10253
|
(kb) => serializeConfigPathValue(kb, configBaseDir)
|
|
9064
10254
|
);
|
|
9065
10255
|
}
|
|
9066
|
-
|
|
10256
|
+
writeFileSync4(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
|
|
9067
10257
|
}
|
|
9068
10258
|
|
|
9069
10259
|
// src/tools/operations.ts
|
|
9070
10260
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
9071
10261
|
var configCache = /* @__PURE__ */ new Map();
|
|
9072
10262
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
10263
|
+
function getIndexBusyResult(error) {
|
|
10264
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
10265
|
+
const owner = error.owner;
|
|
10266
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
10267
|
+
if (error.reason === "legacy-lock") {
|
|
10268
|
+
return {
|
|
10269
|
+
kind: "busy",
|
|
10270
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
10271
|
+
};
|
|
10272
|
+
}
|
|
10273
|
+
if (error.reason === "unknown-owner") {
|
|
10274
|
+
return {
|
|
10275
|
+
kind: "busy",
|
|
10276
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
10277
|
+
};
|
|
10278
|
+
}
|
|
10279
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
10280
|
+
}
|
|
9073
10281
|
function getProjectRoot(projectRoot, host) {
|
|
9074
10282
|
if (projectRoot) {
|
|
9075
10283
|
return projectRoot;
|
|
@@ -9114,8 +10322,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
|
|
|
9114
10322
|
}
|
|
9115
10323
|
function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
|
|
9116
10324
|
const root = getProjectRoot(projectRoot, host);
|
|
9117
|
-
const localIndexPath =
|
|
9118
|
-
if (
|
|
10325
|
+
const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
|
|
10326
|
+
if (existsSync10(localIndexPath)) {
|
|
9119
10327
|
return false;
|
|
9120
10328
|
}
|
|
9121
10329
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
@@ -9129,7 +10337,10 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
|
|
|
9129
10337
|
chunkType: options.chunkType,
|
|
9130
10338
|
contextLines: options.contextLines,
|
|
9131
10339
|
metadataOnly: options.metadataOnly,
|
|
9132
|
-
definitionIntent: options.definitionIntent
|
|
10340
|
+
definitionIntent: options.definitionIntent,
|
|
10341
|
+
blameAuthor: options.blameAuthor,
|
|
10342
|
+
blameSha: options.blameSha,
|
|
10343
|
+
blameSince: options.blameSince
|
|
9133
10344
|
});
|
|
9134
10345
|
}
|
|
9135
10346
|
async function findSimilarCode(projectRoot, host, code, options = {}) {
|
|
@@ -9168,35 +10379,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
|
|
|
9168
10379
|
async function runIndexCodebase(projectRoot, host, args, onProgress) {
|
|
9169
10380
|
const root = getProjectRoot(projectRoot, host);
|
|
9170
10381
|
const key = getIndexerCacheKey(root, host);
|
|
9171
|
-
const cachedConfig = configCache.get(key);
|
|
9172
10382
|
let indexer = getIndexerForProject(root, host);
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
chunksProcessed: progress.chunksProcessed,
|
|
9193
|
-
totalChunks: progress.totalChunks,
|
|
9194
|
-
percentage: calculatePercentage(progress)
|
|
10383
|
+
const runtimeConfig = configCache.get(key);
|
|
10384
|
+
try {
|
|
10385
|
+
if (args.estimateOnly) {
|
|
10386
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
10387
|
+
}
|
|
10388
|
+
const runIndex = async (target) => {
|
|
10389
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
10390
|
+
return operation((progress) => {
|
|
10391
|
+
if (onProgress) {
|
|
10392
|
+
return onProgress(formatProgressTitle(progress), {
|
|
10393
|
+
phase: progress.phase,
|
|
10394
|
+
filesProcessed: progress.filesProcessed,
|
|
10395
|
+
totalFiles: progress.totalFiles,
|
|
10396
|
+
chunksProcessed: progress.chunksProcessed,
|
|
10397
|
+
totalChunks: progress.totalChunks,
|
|
10398
|
+
percentage: calculatePercentage(progress)
|
|
10399
|
+
});
|
|
10400
|
+
}
|
|
10401
|
+
return Promise.resolve();
|
|
9195
10402
|
});
|
|
10403
|
+
};
|
|
10404
|
+
let stats;
|
|
10405
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
10406
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
10407
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
10408
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
10409
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
10410
|
+
indexer = getIndexerForProject(root, host);
|
|
10411
|
+
return runIndex(indexer);
|
|
10412
|
+
}, { completeRecoveries: false });
|
|
10413
|
+
} else {
|
|
10414
|
+
stats = await runIndex(indexer);
|
|
9196
10415
|
}
|
|
9197
|
-
return
|
|
9198
|
-
})
|
|
9199
|
-
|
|
10416
|
+
return { kind: "stats", stats };
|
|
10417
|
+
} catch (error) {
|
|
10418
|
+
const busyResult = getIndexBusyResult(error);
|
|
10419
|
+
if (!busyResult) throw error;
|
|
10420
|
+
return busyResult;
|
|
10421
|
+
}
|
|
9200
10422
|
}
|
|
9201
10423
|
async function getIndexStatus(projectRoot, host) {
|
|
9202
10424
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
@@ -9206,6 +10428,15 @@ async function getIndexHealthCheck(projectRoot, host) {
|
|
|
9206
10428
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
9207
10429
|
return indexer.healthCheck();
|
|
9208
10430
|
}
|
|
10431
|
+
async function runIndexHealthCheck(projectRoot, host) {
|
|
10432
|
+
try {
|
|
10433
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
|
|
10434
|
+
} catch (error) {
|
|
10435
|
+
const busyResult = getIndexBusyResult(error);
|
|
10436
|
+
if (!busyResult) throw error;
|
|
10437
|
+
return busyResult;
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
9209
10440
|
async function getIndexMetrics(projectRoot, host) {
|
|
9210
10441
|
const indexer = getIndexerForProject(projectRoot, host);
|
|
9211
10442
|
const logger = indexer.getLogger();
|
|
@@ -9261,15 +10492,15 @@ async function getIndexLogs(projectRoot, host, args) {
|
|
|
9261
10492
|
function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
|
|
9262
10493
|
const root = getProjectRoot(projectRoot, host);
|
|
9263
10494
|
const inputPath = knowledgeBasePath.trim();
|
|
9264
|
-
const normalizedPath =
|
|
9265
|
-
|
|
10495
|
+
const normalizedPath = path16.resolve(
|
|
10496
|
+
path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
|
|
9266
10497
|
);
|
|
9267
|
-
if (!
|
|
10498
|
+
if (!existsSync10(normalizedPath)) {
|
|
9268
10499
|
return `Error: Directory does not exist: ${normalizedPath}`;
|
|
9269
10500
|
}
|
|
9270
10501
|
let realPath;
|
|
9271
10502
|
try {
|
|
9272
|
-
realPath =
|
|
10503
|
+
realPath = realpathSync2(normalizedPath);
|
|
9273
10504
|
} catch {
|
|
9274
10505
|
return `Error: Cannot resolve path: ${normalizedPath}`;
|
|
9275
10506
|
}
|
|
@@ -9298,13 +10529,13 @@ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
|
|
|
9298
10529
|
}
|
|
9299
10530
|
}
|
|
9300
10531
|
for (const dotDir of sensitiveDotDirs) {
|
|
9301
|
-
const sensitiveDir =
|
|
10532
|
+
const sensitiveDir = path16.join(homeDir, dotDir);
|
|
9302
10533
|
if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
|
|
9303
10534
|
return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
|
|
9304
10535
|
}
|
|
9305
10536
|
}
|
|
9306
10537
|
try {
|
|
9307
|
-
const stat4 =
|
|
10538
|
+
const stat4 = statSync4(normalizedPath);
|
|
9308
10539
|
if (!stat4.isDirectory()) {
|
|
9309
10540
|
return `Error: Path is not a directory: ${normalizedPath}`;
|
|
9310
10541
|
}
|
|
@@ -9344,7 +10575,7 @@ function listKnowledgeBases(projectRoot, host) {
|
|
|
9344
10575
|
for (let i = 0; i < knowledgeBases.length; i++) {
|
|
9345
10576
|
const kb = knowledgeBases[i];
|
|
9346
10577
|
const resolvedPath = resolveKnowledgeBasePath(kb, root);
|
|
9347
|
-
const exists =
|
|
10578
|
+
const exists = existsSync10(resolvedPath);
|
|
9348
10579
|
result += `[${i + 1}] ${kb}
|
|
9349
10580
|
`;
|
|
9350
10581
|
result += ` Resolved: ${resolvedPath}
|
|
@@ -9353,7 +10584,7 @@ function listKnowledgeBases(projectRoot, host) {
|
|
|
9353
10584
|
`;
|
|
9354
10585
|
if (exists) {
|
|
9355
10586
|
try {
|
|
9356
|
-
const stat4 =
|
|
10587
|
+
const stat4 = statSync4(resolvedPath);
|
|
9357
10588
|
result += ` Type: ${stat4.isDirectory() ? "Directory" : "File"}
|
|
9358
10589
|
`;
|
|
9359
10590
|
} catch {
|
|
@@ -9361,7 +10592,7 @@ function listKnowledgeBases(projectRoot, host) {
|
|
|
9361
10592
|
}
|
|
9362
10593
|
result += "\n";
|
|
9363
10594
|
}
|
|
9364
|
-
const hasHostConfig =
|
|
10595
|
+
const hasHostConfig = existsSync10(path16.join(root, getHostProjectConfigRelativePath(host)));
|
|
9365
10596
|
if (hasHostConfig) {
|
|
9366
10597
|
result += `
|
|
9367
10598
|
Config sources: 1 file(s).`;
|
|
@@ -9484,7 +10715,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
9484
10715
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
9485
10716
|
const statMethod = opts.lstat ? lstat : stat;
|
|
9486
10717
|
if (wantBigintFsStats) {
|
|
9487
|
-
this._stat = (
|
|
10718
|
+
this._stat = (path24) => statMethod(path24, { bigint: true });
|
|
9488
10719
|
} else {
|
|
9489
10720
|
this._stat = statMethod;
|
|
9490
10721
|
}
|
|
@@ -9509,8 +10740,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
9509
10740
|
const par = this.parent;
|
|
9510
10741
|
const fil = par && par.files;
|
|
9511
10742
|
if (fil && fil.length > 0) {
|
|
9512
|
-
const { path:
|
|
9513
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
10743
|
+
const { path: path24, depth } = par;
|
|
10744
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
|
|
9514
10745
|
const awaited = await Promise.all(slice);
|
|
9515
10746
|
for (const entry of awaited) {
|
|
9516
10747
|
if (!entry)
|
|
@@ -9550,20 +10781,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
9550
10781
|
this.reading = false;
|
|
9551
10782
|
}
|
|
9552
10783
|
}
|
|
9553
|
-
async _exploreDir(
|
|
10784
|
+
async _exploreDir(path24, depth) {
|
|
9554
10785
|
let files;
|
|
9555
10786
|
try {
|
|
9556
|
-
files = await readdir(
|
|
10787
|
+
files = await readdir(path24, this._rdOptions);
|
|
9557
10788
|
} catch (error) {
|
|
9558
10789
|
this._onError(error);
|
|
9559
10790
|
}
|
|
9560
|
-
return { files, depth, path:
|
|
10791
|
+
return { files, depth, path: path24 };
|
|
9561
10792
|
}
|
|
9562
|
-
async _formatEntry(dirent,
|
|
10793
|
+
async _formatEntry(dirent, path24) {
|
|
9563
10794
|
let entry;
|
|
9564
10795
|
const basename5 = this._isDirent ? dirent.name : dirent;
|
|
9565
10796
|
try {
|
|
9566
|
-
const fullPath = presolve(pjoin(
|
|
10797
|
+
const fullPath = presolve(pjoin(path24, basename5));
|
|
9567
10798
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
|
|
9568
10799
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
9569
10800
|
} catch (err) {
|
|
@@ -9963,16 +11194,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
9963
11194
|
};
|
|
9964
11195
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
9965
11196
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
9966
|
-
function createFsWatchInstance(
|
|
11197
|
+
function createFsWatchInstance(path24, options, listener, errHandler, emitRaw) {
|
|
9967
11198
|
const handleEvent = (rawEvent, evPath) => {
|
|
9968
|
-
listener(
|
|
9969
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
9970
|
-
if (evPath &&
|
|
9971
|
-
fsWatchBroadcast(sp.resolve(
|
|
11199
|
+
listener(path24);
|
|
11200
|
+
emitRaw(rawEvent, evPath, { watchedPath: path24 });
|
|
11201
|
+
if (evPath && path24 !== evPath) {
|
|
11202
|
+
fsWatchBroadcast(sp.resolve(path24, evPath), KEY_LISTENERS, sp.join(path24, evPath));
|
|
9972
11203
|
}
|
|
9973
11204
|
};
|
|
9974
11205
|
try {
|
|
9975
|
-
return fs_watch(
|
|
11206
|
+
return fs_watch(path24, {
|
|
9976
11207
|
persistent: options.persistent
|
|
9977
11208
|
}, handleEvent);
|
|
9978
11209
|
} catch (error) {
|
|
@@ -9988,12 +11219,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
9988
11219
|
listener(val1, val2, val3);
|
|
9989
11220
|
});
|
|
9990
11221
|
};
|
|
9991
|
-
var setFsWatchListener = (
|
|
11222
|
+
var setFsWatchListener = (path24, fullPath, options, handlers) => {
|
|
9992
11223
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
9993
11224
|
let cont = FsWatchInstances.get(fullPath);
|
|
9994
11225
|
let watcher;
|
|
9995
11226
|
if (!options.persistent) {
|
|
9996
|
-
watcher = createFsWatchInstance(
|
|
11227
|
+
watcher = createFsWatchInstance(path24, options, listener, errHandler, rawEmitter);
|
|
9997
11228
|
if (!watcher)
|
|
9998
11229
|
return;
|
|
9999
11230
|
return watcher.close.bind(watcher);
|
|
@@ -10004,7 +11235,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
10004
11235
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
10005
11236
|
} else {
|
|
10006
11237
|
watcher = createFsWatchInstance(
|
|
10007
|
-
|
|
11238
|
+
path24,
|
|
10008
11239
|
options,
|
|
10009
11240
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
10010
11241
|
errHandler,
|
|
@@ -10019,7 +11250,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
10019
11250
|
cont.watcherUnusable = true;
|
|
10020
11251
|
if (isWindows && error.code === "EPERM") {
|
|
10021
11252
|
try {
|
|
10022
|
-
const fd = await open(
|
|
11253
|
+
const fd = await open(path24, "r");
|
|
10023
11254
|
await fd.close();
|
|
10024
11255
|
broadcastErr(error);
|
|
10025
11256
|
} catch (err) {
|
|
@@ -10050,7 +11281,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
10050
11281
|
};
|
|
10051
11282
|
};
|
|
10052
11283
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
10053
|
-
var setFsWatchFileListener = (
|
|
11284
|
+
var setFsWatchFileListener = (path24, fullPath, options, handlers) => {
|
|
10054
11285
|
const { listener, rawEmitter } = handlers;
|
|
10055
11286
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
10056
11287
|
const copts = cont && cont.options;
|
|
@@ -10072,7 +11303,7 @@ var setFsWatchFileListener = (path22, fullPath, options, handlers) => {
|
|
|
10072
11303
|
});
|
|
10073
11304
|
const currmtime = curr.mtimeMs;
|
|
10074
11305
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
10075
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
11306
|
+
foreach(cont.listeners, (listener2) => listener2(path24, curr));
|
|
10076
11307
|
}
|
|
10077
11308
|
})
|
|
10078
11309
|
};
|
|
@@ -10102,13 +11333,13 @@ var NodeFsHandler = class {
|
|
|
10102
11333
|
* @param listener on fs change
|
|
10103
11334
|
* @returns closer for the watcher instance
|
|
10104
11335
|
*/
|
|
10105
|
-
_watchWithNodeFs(
|
|
11336
|
+
_watchWithNodeFs(path24, listener) {
|
|
10106
11337
|
const opts = this.fsw.options;
|
|
10107
|
-
const directory = sp.dirname(
|
|
10108
|
-
const basename5 = sp.basename(
|
|
11338
|
+
const directory = sp.dirname(path24);
|
|
11339
|
+
const basename5 = sp.basename(path24);
|
|
10109
11340
|
const parent = this.fsw._getWatchedDir(directory);
|
|
10110
11341
|
parent.add(basename5);
|
|
10111
|
-
const absolutePath = sp.resolve(
|
|
11342
|
+
const absolutePath = sp.resolve(path24);
|
|
10112
11343
|
const options = {
|
|
10113
11344
|
persistent: opts.persistent
|
|
10114
11345
|
};
|
|
@@ -10118,12 +11349,12 @@ var NodeFsHandler = class {
|
|
|
10118
11349
|
if (opts.usePolling) {
|
|
10119
11350
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
10120
11351
|
options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
|
|
10121
|
-
closer = setFsWatchFileListener(
|
|
11352
|
+
closer = setFsWatchFileListener(path24, absolutePath, options, {
|
|
10122
11353
|
listener,
|
|
10123
11354
|
rawEmitter: this.fsw._emitRaw
|
|
10124
11355
|
});
|
|
10125
11356
|
} else {
|
|
10126
|
-
closer = setFsWatchListener(
|
|
11357
|
+
closer = setFsWatchListener(path24, absolutePath, options, {
|
|
10127
11358
|
listener,
|
|
10128
11359
|
errHandler: this._boundHandleError,
|
|
10129
11360
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -10145,7 +11376,7 @@ var NodeFsHandler = class {
|
|
|
10145
11376
|
let prevStats = stats;
|
|
10146
11377
|
if (parent.has(basename5))
|
|
10147
11378
|
return;
|
|
10148
|
-
const listener = async (
|
|
11379
|
+
const listener = async (path24, newStats) => {
|
|
10149
11380
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
10150
11381
|
return;
|
|
10151
11382
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -10159,11 +11390,11 @@ var NodeFsHandler = class {
|
|
|
10159
11390
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
10160
11391
|
}
|
|
10161
11392
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
10162
|
-
this.fsw._closeFile(
|
|
11393
|
+
this.fsw._closeFile(path24);
|
|
10163
11394
|
prevStats = newStats2;
|
|
10164
11395
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
10165
11396
|
if (closer2)
|
|
10166
|
-
this.fsw._addPathCloser(
|
|
11397
|
+
this.fsw._addPathCloser(path24, closer2);
|
|
10167
11398
|
} else {
|
|
10168
11399
|
prevStats = newStats2;
|
|
10169
11400
|
}
|
|
@@ -10195,7 +11426,7 @@ var NodeFsHandler = class {
|
|
|
10195
11426
|
* @param item basename of this item
|
|
10196
11427
|
* @returns true if no more processing is needed for this entry.
|
|
10197
11428
|
*/
|
|
10198
|
-
async _handleSymlink(entry, directory,
|
|
11429
|
+
async _handleSymlink(entry, directory, path24, item) {
|
|
10199
11430
|
if (this.fsw.closed) {
|
|
10200
11431
|
return;
|
|
10201
11432
|
}
|
|
@@ -10205,7 +11436,7 @@ var NodeFsHandler = class {
|
|
|
10205
11436
|
this.fsw._incrReadyCount();
|
|
10206
11437
|
let linkPath;
|
|
10207
11438
|
try {
|
|
10208
|
-
linkPath = await fsrealpath(
|
|
11439
|
+
linkPath = await fsrealpath(path24);
|
|
10209
11440
|
} catch (e) {
|
|
10210
11441
|
this.fsw._emitReady();
|
|
10211
11442
|
return true;
|
|
@@ -10215,12 +11446,12 @@ var NodeFsHandler = class {
|
|
|
10215
11446
|
if (dir.has(item)) {
|
|
10216
11447
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
10217
11448
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
10218
|
-
this.fsw._emit(EV.CHANGE,
|
|
11449
|
+
this.fsw._emit(EV.CHANGE, path24, entry.stats);
|
|
10219
11450
|
}
|
|
10220
11451
|
} else {
|
|
10221
11452
|
dir.add(item);
|
|
10222
11453
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
10223
|
-
this.fsw._emit(EV.ADD,
|
|
11454
|
+
this.fsw._emit(EV.ADD, path24, entry.stats);
|
|
10224
11455
|
}
|
|
10225
11456
|
this.fsw._emitReady();
|
|
10226
11457
|
return true;
|
|
@@ -10250,9 +11481,9 @@ var NodeFsHandler = class {
|
|
|
10250
11481
|
return;
|
|
10251
11482
|
}
|
|
10252
11483
|
const item = entry.path;
|
|
10253
|
-
let
|
|
11484
|
+
let path24 = sp.join(directory, item);
|
|
10254
11485
|
current.add(item);
|
|
10255
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
11486
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
|
|
10256
11487
|
return;
|
|
10257
11488
|
}
|
|
10258
11489
|
if (this.fsw.closed) {
|
|
@@ -10261,8 +11492,8 @@ var NodeFsHandler = class {
|
|
|
10261
11492
|
}
|
|
10262
11493
|
if (item === target || !target && !previous.has(item)) {
|
|
10263
11494
|
this.fsw._incrReadyCount();
|
|
10264
|
-
|
|
10265
|
-
this._addToNodeFs(
|
|
11495
|
+
path24 = sp.join(dir, sp.relative(dir, path24));
|
|
11496
|
+
this._addToNodeFs(path24, initialAdd, wh, depth + 1);
|
|
10266
11497
|
}
|
|
10267
11498
|
}).on(EV.ERROR, this._boundHandleError);
|
|
10268
11499
|
return new Promise((resolve13, reject) => {
|
|
@@ -10331,13 +11562,13 @@ var NodeFsHandler = class {
|
|
|
10331
11562
|
* @param depth Child path actually targeted for watch
|
|
10332
11563
|
* @param target Child path actually targeted for watch
|
|
10333
11564
|
*/
|
|
10334
|
-
async _addToNodeFs(
|
|
11565
|
+
async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
|
|
10335
11566
|
const ready = this.fsw._emitReady;
|
|
10336
|
-
if (this.fsw._isIgnored(
|
|
11567
|
+
if (this.fsw._isIgnored(path24) || this.fsw.closed) {
|
|
10337
11568
|
ready();
|
|
10338
11569
|
return false;
|
|
10339
11570
|
}
|
|
10340
|
-
const wh = this.fsw._getWatchHelpers(
|
|
11571
|
+
const wh = this.fsw._getWatchHelpers(path24);
|
|
10341
11572
|
if (priorWh) {
|
|
10342
11573
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
10343
11574
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -10353,8 +11584,8 @@ var NodeFsHandler = class {
|
|
|
10353
11584
|
const follow = this.fsw.options.followSymlinks;
|
|
10354
11585
|
let closer;
|
|
10355
11586
|
if (stats.isDirectory()) {
|
|
10356
|
-
const absPath = sp.resolve(
|
|
10357
|
-
const targetPath = follow ? await fsrealpath(
|
|
11587
|
+
const absPath = sp.resolve(path24);
|
|
11588
|
+
const targetPath = follow ? await fsrealpath(path24) : path24;
|
|
10358
11589
|
if (this.fsw.closed)
|
|
10359
11590
|
return;
|
|
10360
11591
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -10364,29 +11595,29 @@ var NodeFsHandler = class {
|
|
|
10364
11595
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
10365
11596
|
}
|
|
10366
11597
|
} else if (stats.isSymbolicLink()) {
|
|
10367
|
-
const targetPath = follow ? await fsrealpath(
|
|
11598
|
+
const targetPath = follow ? await fsrealpath(path24) : path24;
|
|
10368
11599
|
if (this.fsw.closed)
|
|
10369
11600
|
return;
|
|
10370
11601
|
const parent = sp.dirname(wh.watchPath);
|
|
10371
11602
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
10372
11603
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
10373
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
11604
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
|
|
10374
11605
|
if (this.fsw.closed)
|
|
10375
11606
|
return;
|
|
10376
11607
|
if (targetPath !== void 0) {
|
|
10377
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
11608
|
+
this.fsw._symlinkPaths.set(sp.resolve(path24), targetPath);
|
|
10378
11609
|
}
|
|
10379
11610
|
} else {
|
|
10380
11611
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
10381
11612
|
}
|
|
10382
11613
|
ready();
|
|
10383
11614
|
if (closer)
|
|
10384
|
-
this.fsw._addPathCloser(
|
|
11615
|
+
this.fsw._addPathCloser(path24, closer);
|
|
10385
11616
|
return false;
|
|
10386
11617
|
} catch (error) {
|
|
10387
11618
|
if (this.fsw._handleError(error)) {
|
|
10388
11619
|
ready();
|
|
10389
|
-
return
|
|
11620
|
+
return path24;
|
|
10390
11621
|
}
|
|
10391
11622
|
}
|
|
10392
11623
|
}
|
|
@@ -10418,35 +11649,35 @@ function createPattern(matcher) {
|
|
|
10418
11649
|
if (matcher.path === string)
|
|
10419
11650
|
return true;
|
|
10420
11651
|
if (matcher.recursive) {
|
|
10421
|
-
const
|
|
10422
|
-
if (!
|
|
11652
|
+
const relative11 = sp2.relative(matcher.path, string);
|
|
11653
|
+
if (!relative11) {
|
|
10423
11654
|
return false;
|
|
10424
11655
|
}
|
|
10425
|
-
return !
|
|
11656
|
+
return !relative11.startsWith("..") && !sp2.isAbsolute(relative11);
|
|
10426
11657
|
}
|
|
10427
11658
|
return false;
|
|
10428
11659
|
};
|
|
10429
11660
|
}
|
|
10430
11661
|
return () => false;
|
|
10431
11662
|
}
|
|
10432
|
-
function normalizePath(
|
|
10433
|
-
if (typeof
|
|
11663
|
+
function normalizePath(path24) {
|
|
11664
|
+
if (typeof path24 !== "string")
|
|
10434
11665
|
throw new Error("string expected");
|
|
10435
|
-
|
|
10436
|
-
|
|
11666
|
+
path24 = sp2.normalize(path24);
|
|
11667
|
+
path24 = path24.replace(/\\/g, "/");
|
|
10437
11668
|
let prepend = false;
|
|
10438
|
-
if (
|
|
11669
|
+
if (path24.startsWith("//"))
|
|
10439
11670
|
prepend = true;
|
|
10440
|
-
|
|
11671
|
+
path24 = path24.replace(DOUBLE_SLASH_RE, "/");
|
|
10441
11672
|
if (prepend)
|
|
10442
|
-
|
|
10443
|
-
return
|
|
11673
|
+
path24 = "/" + path24;
|
|
11674
|
+
return path24;
|
|
10444
11675
|
}
|
|
10445
11676
|
function matchPatterns(patterns, testString, stats) {
|
|
10446
|
-
const
|
|
11677
|
+
const path24 = normalizePath(testString);
|
|
10447
11678
|
for (let index = 0; index < patterns.length; index++) {
|
|
10448
11679
|
const pattern = patterns[index];
|
|
10449
|
-
if (pattern(
|
|
11680
|
+
if (pattern(path24, stats)) {
|
|
10450
11681
|
return true;
|
|
10451
11682
|
}
|
|
10452
11683
|
}
|
|
@@ -10484,19 +11715,19 @@ var toUnix = (string) => {
|
|
|
10484
11715
|
}
|
|
10485
11716
|
return str;
|
|
10486
11717
|
};
|
|
10487
|
-
var normalizePathToUnix = (
|
|
10488
|
-
var normalizeIgnored = (cwd = "") => (
|
|
10489
|
-
if (typeof
|
|
10490
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
11718
|
+
var normalizePathToUnix = (path24) => toUnix(sp2.normalize(toUnix(path24)));
|
|
11719
|
+
var normalizeIgnored = (cwd = "") => (path24) => {
|
|
11720
|
+
if (typeof path24 === "string") {
|
|
11721
|
+
return normalizePathToUnix(sp2.isAbsolute(path24) ? path24 : sp2.join(cwd, path24));
|
|
10491
11722
|
} else {
|
|
10492
|
-
return
|
|
11723
|
+
return path24;
|
|
10493
11724
|
}
|
|
10494
11725
|
};
|
|
10495
|
-
var getAbsolutePath = (
|
|
10496
|
-
if (sp2.isAbsolute(
|
|
10497
|
-
return
|
|
11726
|
+
var getAbsolutePath = (path24, cwd) => {
|
|
11727
|
+
if (sp2.isAbsolute(path24)) {
|
|
11728
|
+
return path24;
|
|
10498
11729
|
}
|
|
10499
|
-
return sp2.join(cwd,
|
|
11730
|
+
return sp2.join(cwd, path24);
|
|
10500
11731
|
};
|
|
10501
11732
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
10502
11733
|
var DirEntry = class {
|
|
@@ -10561,10 +11792,10 @@ var WatchHelper = class {
|
|
|
10561
11792
|
dirParts;
|
|
10562
11793
|
followSymlinks;
|
|
10563
11794
|
statMethod;
|
|
10564
|
-
constructor(
|
|
11795
|
+
constructor(path24, follow, fsw) {
|
|
10565
11796
|
this.fsw = fsw;
|
|
10566
|
-
const watchPath =
|
|
10567
|
-
this.path =
|
|
11797
|
+
const watchPath = path24;
|
|
11798
|
+
this.path = path24 = path24.replace(REPLACER_RE, "");
|
|
10568
11799
|
this.watchPath = watchPath;
|
|
10569
11800
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
10570
11801
|
this.dirParts = [];
|
|
@@ -10704,20 +11935,20 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10704
11935
|
this._closePromise = void 0;
|
|
10705
11936
|
let paths = unifyPaths(paths_);
|
|
10706
11937
|
if (cwd) {
|
|
10707
|
-
paths = paths.map((
|
|
10708
|
-
const absPath = getAbsolutePath(
|
|
11938
|
+
paths = paths.map((path24) => {
|
|
11939
|
+
const absPath = getAbsolutePath(path24, cwd);
|
|
10709
11940
|
return absPath;
|
|
10710
11941
|
});
|
|
10711
11942
|
}
|
|
10712
|
-
paths.forEach((
|
|
10713
|
-
this._removeIgnoredPath(
|
|
11943
|
+
paths.forEach((path24) => {
|
|
11944
|
+
this._removeIgnoredPath(path24);
|
|
10714
11945
|
});
|
|
10715
11946
|
this._userIgnored = void 0;
|
|
10716
11947
|
if (!this._readyCount)
|
|
10717
11948
|
this._readyCount = 0;
|
|
10718
11949
|
this._readyCount += paths.length;
|
|
10719
|
-
Promise.all(paths.map(async (
|
|
10720
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
11950
|
+
Promise.all(paths.map(async (path24) => {
|
|
11951
|
+
const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
|
|
10721
11952
|
if (res)
|
|
10722
11953
|
this._emitReady();
|
|
10723
11954
|
return res;
|
|
@@ -10739,17 +11970,17 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10739
11970
|
return this;
|
|
10740
11971
|
const paths = unifyPaths(paths_);
|
|
10741
11972
|
const { cwd } = this.options;
|
|
10742
|
-
paths.forEach((
|
|
10743
|
-
if (!sp2.isAbsolute(
|
|
11973
|
+
paths.forEach((path24) => {
|
|
11974
|
+
if (!sp2.isAbsolute(path24) && !this._closers.has(path24)) {
|
|
10744
11975
|
if (cwd)
|
|
10745
|
-
|
|
10746
|
-
|
|
11976
|
+
path24 = sp2.join(cwd, path24);
|
|
11977
|
+
path24 = sp2.resolve(path24);
|
|
10747
11978
|
}
|
|
10748
|
-
this._closePath(
|
|
10749
|
-
this._addIgnoredPath(
|
|
10750
|
-
if (this._watched.has(
|
|
11979
|
+
this._closePath(path24);
|
|
11980
|
+
this._addIgnoredPath(path24);
|
|
11981
|
+
if (this._watched.has(path24)) {
|
|
10751
11982
|
this._addIgnoredPath({
|
|
10752
|
-
path:
|
|
11983
|
+
path: path24,
|
|
10753
11984
|
recursive: true
|
|
10754
11985
|
});
|
|
10755
11986
|
}
|
|
@@ -10813,38 +12044,38 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10813
12044
|
* @param stats arguments to be passed with event
|
|
10814
12045
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
10815
12046
|
*/
|
|
10816
|
-
async _emit(event,
|
|
12047
|
+
async _emit(event, path24, stats) {
|
|
10817
12048
|
if (this.closed)
|
|
10818
12049
|
return;
|
|
10819
12050
|
const opts = this.options;
|
|
10820
12051
|
if (isWindows)
|
|
10821
|
-
|
|
12052
|
+
path24 = sp2.normalize(path24);
|
|
10822
12053
|
if (opts.cwd)
|
|
10823
|
-
|
|
10824
|
-
const args = [
|
|
12054
|
+
path24 = sp2.relative(opts.cwd, path24);
|
|
12055
|
+
const args = [path24];
|
|
10825
12056
|
if (stats != null)
|
|
10826
12057
|
args.push(stats);
|
|
10827
12058
|
const awf = opts.awaitWriteFinish;
|
|
10828
12059
|
let pw;
|
|
10829
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
12060
|
+
if (awf && (pw = this._pendingWrites.get(path24))) {
|
|
10830
12061
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
10831
12062
|
return this;
|
|
10832
12063
|
}
|
|
10833
12064
|
if (opts.atomic) {
|
|
10834
12065
|
if (event === EVENTS.UNLINK) {
|
|
10835
|
-
this._pendingUnlinks.set(
|
|
12066
|
+
this._pendingUnlinks.set(path24, [event, ...args]);
|
|
10836
12067
|
setTimeout(() => {
|
|
10837
|
-
this._pendingUnlinks.forEach((entry,
|
|
12068
|
+
this._pendingUnlinks.forEach((entry, path25) => {
|
|
10838
12069
|
this.emit(...entry);
|
|
10839
12070
|
this.emit(EVENTS.ALL, ...entry);
|
|
10840
|
-
this._pendingUnlinks.delete(
|
|
12071
|
+
this._pendingUnlinks.delete(path25);
|
|
10841
12072
|
});
|
|
10842
12073
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
10843
12074
|
return this;
|
|
10844
12075
|
}
|
|
10845
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
12076
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
|
|
10846
12077
|
event = EVENTS.CHANGE;
|
|
10847
|
-
this._pendingUnlinks.delete(
|
|
12078
|
+
this._pendingUnlinks.delete(path24);
|
|
10848
12079
|
}
|
|
10849
12080
|
}
|
|
10850
12081
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -10862,16 +12093,16 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10862
12093
|
this.emitWithAll(event, args);
|
|
10863
12094
|
}
|
|
10864
12095
|
};
|
|
10865
|
-
this._awaitWriteFinish(
|
|
12096
|
+
this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
|
|
10866
12097
|
return this;
|
|
10867
12098
|
}
|
|
10868
12099
|
if (event === EVENTS.CHANGE) {
|
|
10869
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
12100
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
|
|
10870
12101
|
if (isThrottled)
|
|
10871
12102
|
return this;
|
|
10872
12103
|
}
|
|
10873
12104
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
10874
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
12105
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path24) : path24;
|
|
10875
12106
|
let stats2;
|
|
10876
12107
|
try {
|
|
10877
12108
|
stats2 = await stat3(fullPath);
|
|
@@ -10902,23 +12133,23 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10902
12133
|
* @param timeout duration of time to suppress duplicate actions
|
|
10903
12134
|
* @returns tracking object or false if action should be suppressed
|
|
10904
12135
|
*/
|
|
10905
|
-
_throttle(actionType,
|
|
12136
|
+
_throttle(actionType, path24, timeout) {
|
|
10906
12137
|
if (!this._throttled.has(actionType)) {
|
|
10907
12138
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
10908
12139
|
}
|
|
10909
12140
|
const action = this._throttled.get(actionType);
|
|
10910
12141
|
if (!action)
|
|
10911
12142
|
throw new Error("invalid throttle");
|
|
10912
|
-
const actionPath = action.get(
|
|
12143
|
+
const actionPath = action.get(path24);
|
|
10913
12144
|
if (actionPath) {
|
|
10914
12145
|
actionPath.count++;
|
|
10915
12146
|
return false;
|
|
10916
12147
|
}
|
|
10917
12148
|
let timeoutObject;
|
|
10918
12149
|
const clear = () => {
|
|
10919
|
-
const item = action.get(
|
|
12150
|
+
const item = action.get(path24);
|
|
10920
12151
|
const count = item ? item.count : 0;
|
|
10921
|
-
action.delete(
|
|
12152
|
+
action.delete(path24);
|
|
10922
12153
|
clearTimeout(timeoutObject);
|
|
10923
12154
|
if (item)
|
|
10924
12155
|
clearTimeout(item.timeoutObject);
|
|
@@ -10926,7 +12157,7 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10926
12157
|
};
|
|
10927
12158
|
timeoutObject = setTimeout(clear, timeout);
|
|
10928
12159
|
const thr = { timeoutObject, clear, count: 0 };
|
|
10929
|
-
action.set(
|
|
12160
|
+
action.set(path24, thr);
|
|
10930
12161
|
return thr;
|
|
10931
12162
|
}
|
|
10932
12163
|
_incrReadyCount() {
|
|
@@ -10940,44 +12171,44 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10940
12171
|
* @param event
|
|
10941
12172
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
10942
12173
|
*/
|
|
10943
|
-
_awaitWriteFinish(
|
|
12174
|
+
_awaitWriteFinish(path24, threshold, event, awfEmit) {
|
|
10944
12175
|
const awf = this.options.awaitWriteFinish;
|
|
10945
12176
|
if (typeof awf !== "object")
|
|
10946
12177
|
return;
|
|
10947
12178
|
const pollInterval = awf.pollInterval;
|
|
10948
12179
|
let timeoutHandler;
|
|
10949
|
-
let fullPath =
|
|
10950
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
10951
|
-
fullPath = sp2.join(this.options.cwd,
|
|
12180
|
+
let fullPath = path24;
|
|
12181
|
+
if (this.options.cwd && !sp2.isAbsolute(path24)) {
|
|
12182
|
+
fullPath = sp2.join(this.options.cwd, path24);
|
|
10952
12183
|
}
|
|
10953
12184
|
const now = /* @__PURE__ */ new Date();
|
|
10954
12185
|
const writes = this._pendingWrites;
|
|
10955
12186
|
function awaitWriteFinishFn(prevStat) {
|
|
10956
12187
|
statcb(fullPath, (err, curStat) => {
|
|
10957
|
-
if (err || !writes.has(
|
|
12188
|
+
if (err || !writes.has(path24)) {
|
|
10958
12189
|
if (err && err.code !== "ENOENT")
|
|
10959
12190
|
awfEmit(err);
|
|
10960
12191
|
return;
|
|
10961
12192
|
}
|
|
10962
12193
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
10963
12194
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
10964
|
-
writes.get(
|
|
12195
|
+
writes.get(path24).lastChange = now2;
|
|
10965
12196
|
}
|
|
10966
|
-
const pw = writes.get(
|
|
12197
|
+
const pw = writes.get(path24);
|
|
10967
12198
|
const df = now2 - pw.lastChange;
|
|
10968
12199
|
if (df >= threshold) {
|
|
10969
|
-
writes.delete(
|
|
12200
|
+
writes.delete(path24);
|
|
10970
12201
|
awfEmit(void 0, curStat);
|
|
10971
12202
|
} else {
|
|
10972
12203
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
10973
12204
|
}
|
|
10974
12205
|
});
|
|
10975
12206
|
}
|
|
10976
|
-
if (!writes.has(
|
|
10977
|
-
writes.set(
|
|
12207
|
+
if (!writes.has(path24)) {
|
|
12208
|
+
writes.set(path24, {
|
|
10978
12209
|
lastChange: now,
|
|
10979
12210
|
cancelWait: () => {
|
|
10980
|
-
writes.delete(
|
|
12211
|
+
writes.delete(path24);
|
|
10981
12212
|
clearTimeout(timeoutHandler);
|
|
10982
12213
|
return event;
|
|
10983
12214
|
}
|
|
@@ -10988,8 +12219,8 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10988
12219
|
/**
|
|
10989
12220
|
* Determines whether user has asked to ignore this path.
|
|
10990
12221
|
*/
|
|
10991
|
-
_isIgnored(
|
|
10992
|
-
if (this.options.atomic && DOT_RE.test(
|
|
12222
|
+
_isIgnored(path24, stats) {
|
|
12223
|
+
if (this.options.atomic && DOT_RE.test(path24))
|
|
10993
12224
|
return true;
|
|
10994
12225
|
if (!this._userIgnored) {
|
|
10995
12226
|
const { cwd } = this.options;
|
|
@@ -10999,17 +12230,17 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
10999
12230
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
11000
12231
|
this._userIgnored = anymatch(list, void 0);
|
|
11001
12232
|
}
|
|
11002
|
-
return this._userIgnored(
|
|
12233
|
+
return this._userIgnored(path24, stats);
|
|
11003
12234
|
}
|
|
11004
|
-
_isntIgnored(
|
|
11005
|
-
return !this._isIgnored(
|
|
12235
|
+
_isntIgnored(path24, stat4) {
|
|
12236
|
+
return !this._isIgnored(path24, stat4);
|
|
11006
12237
|
}
|
|
11007
12238
|
/**
|
|
11008
12239
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
11009
12240
|
* @param path file or directory pattern being watched
|
|
11010
12241
|
*/
|
|
11011
|
-
_getWatchHelpers(
|
|
11012
|
-
return new WatchHelper(
|
|
12242
|
+
_getWatchHelpers(path24) {
|
|
12243
|
+
return new WatchHelper(path24, this.options.followSymlinks, this);
|
|
11013
12244
|
}
|
|
11014
12245
|
// Directory helpers
|
|
11015
12246
|
// -----------------
|
|
@@ -11041,63 +12272,63 @@ var FSWatcher = class extends EventEmitter2 {
|
|
|
11041
12272
|
* @param item base path of item/directory
|
|
11042
12273
|
*/
|
|
11043
12274
|
_remove(directory, item, isDirectory) {
|
|
11044
|
-
const
|
|
11045
|
-
const fullPath = sp2.resolve(
|
|
11046
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
11047
|
-
if (!this._throttle("remove",
|
|
12275
|
+
const path24 = sp2.join(directory, item);
|
|
12276
|
+
const fullPath = sp2.resolve(path24);
|
|
12277
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
|
|
12278
|
+
if (!this._throttle("remove", path24, 100))
|
|
11048
12279
|
return;
|
|
11049
12280
|
if (!isDirectory && this._watched.size === 1) {
|
|
11050
12281
|
this.add(directory, item, true);
|
|
11051
12282
|
}
|
|
11052
|
-
const wp = this._getWatchedDir(
|
|
12283
|
+
const wp = this._getWatchedDir(path24);
|
|
11053
12284
|
const nestedDirectoryChildren = wp.getChildren();
|
|
11054
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
12285
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
|
|
11055
12286
|
const parent = this._getWatchedDir(directory);
|
|
11056
12287
|
const wasTracked = parent.has(item);
|
|
11057
12288
|
parent.remove(item);
|
|
11058
12289
|
if (this._symlinkPaths.has(fullPath)) {
|
|
11059
12290
|
this._symlinkPaths.delete(fullPath);
|
|
11060
12291
|
}
|
|
11061
|
-
let relPath =
|
|
12292
|
+
let relPath = path24;
|
|
11062
12293
|
if (this.options.cwd)
|
|
11063
|
-
relPath = sp2.relative(this.options.cwd,
|
|
12294
|
+
relPath = sp2.relative(this.options.cwd, path24);
|
|
11064
12295
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
11065
12296
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
11066
12297
|
if (event === EVENTS.ADD)
|
|
11067
12298
|
return;
|
|
11068
12299
|
}
|
|
11069
|
-
this._watched.delete(
|
|
12300
|
+
this._watched.delete(path24);
|
|
11070
12301
|
this._watched.delete(fullPath);
|
|
11071
12302
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
11072
|
-
if (wasTracked && !this._isIgnored(
|
|
11073
|
-
this._emit(eventName,
|
|
11074
|
-
this._closePath(
|
|
12303
|
+
if (wasTracked && !this._isIgnored(path24))
|
|
12304
|
+
this._emit(eventName, path24);
|
|
12305
|
+
this._closePath(path24);
|
|
11075
12306
|
}
|
|
11076
12307
|
/**
|
|
11077
12308
|
* Closes all watchers for a path
|
|
11078
12309
|
*/
|
|
11079
|
-
_closePath(
|
|
11080
|
-
this._closeFile(
|
|
11081
|
-
const dir = sp2.dirname(
|
|
11082
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
12310
|
+
_closePath(path24) {
|
|
12311
|
+
this._closeFile(path24);
|
|
12312
|
+
const dir = sp2.dirname(path24);
|
|
12313
|
+
this._getWatchedDir(dir).remove(sp2.basename(path24));
|
|
11083
12314
|
}
|
|
11084
12315
|
/**
|
|
11085
12316
|
* Closes only file-specific watchers
|
|
11086
12317
|
*/
|
|
11087
|
-
_closeFile(
|
|
11088
|
-
const closers = this._closers.get(
|
|
12318
|
+
_closeFile(path24) {
|
|
12319
|
+
const closers = this._closers.get(path24);
|
|
11089
12320
|
if (!closers)
|
|
11090
12321
|
return;
|
|
11091
12322
|
closers.forEach((closer) => closer());
|
|
11092
|
-
this._closers.delete(
|
|
12323
|
+
this._closers.delete(path24);
|
|
11093
12324
|
}
|
|
11094
|
-
_addPathCloser(
|
|
12325
|
+
_addPathCloser(path24, closer) {
|
|
11095
12326
|
if (!closer)
|
|
11096
12327
|
return;
|
|
11097
|
-
let list = this._closers.get(
|
|
12328
|
+
let list = this._closers.get(path24);
|
|
11098
12329
|
if (!list) {
|
|
11099
12330
|
list = [];
|
|
11100
|
-
this._closers.set(
|
|
12331
|
+
this._closers.set(path24, list);
|
|
11101
12332
|
}
|
|
11102
12333
|
list.push(closer);
|
|
11103
12334
|
}
|
|
@@ -11127,7 +12358,7 @@ function watch(paths, options = {}) {
|
|
|
11127
12358
|
var chokidar_default = { watch, FSWatcher };
|
|
11128
12359
|
|
|
11129
12360
|
// src/watcher/file-watcher.ts
|
|
11130
|
-
import * as
|
|
12361
|
+
import * as path17 from "path";
|
|
11131
12362
|
var FileWatcher = class {
|
|
11132
12363
|
watcher = null;
|
|
11133
12364
|
projectRoot;
|
|
@@ -11138,6 +12369,8 @@ var FileWatcher = class {
|
|
|
11138
12369
|
debounceTimer = null;
|
|
11139
12370
|
debounceMs = 1e3;
|
|
11140
12371
|
onChanges = null;
|
|
12372
|
+
readyPromise = null;
|
|
12373
|
+
resolveReady = null;
|
|
11141
12374
|
constructor(projectRoot, config, host = "opencode", options = {}) {
|
|
11142
12375
|
this.projectRoot = projectRoot;
|
|
11143
12376
|
this.config = config;
|
|
@@ -11153,15 +12386,15 @@ var FileWatcher = class {
|
|
|
11153
12386
|
const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
|
|
11154
12387
|
this.watcher = chokidar_default.watch(watchTargets, {
|
|
11155
12388
|
ignored: (filePath) => {
|
|
11156
|
-
const relativePath =
|
|
12389
|
+
const relativePath = path17.relative(this.projectRoot, filePath);
|
|
11157
12390
|
if (!relativePath) return false;
|
|
11158
12391
|
if (this.isProjectConfigPathOrAncestor(relativePath)) {
|
|
11159
12392
|
return false;
|
|
11160
12393
|
}
|
|
11161
|
-
if (hasFilteredPathSegment(relativePath,
|
|
12394
|
+
if (hasFilteredPathSegment(relativePath, path17.sep)) {
|
|
11162
12395
|
return true;
|
|
11163
12396
|
}
|
|
11164
|
-
if (isRestrictedDirectory(relativePath,
|
|
12397
|
+
if (isRestrictedDirectory(relativePath, path17.sep)) {
|
|
11165
12398
|
return true;
|
|
11166
12399
|
}
|
|
11167
12400
|
if (ignoreFilter.ignores(relativePath)) {
|
|
@@ -11176,6 +12409,13 @@ var FileWatcher = class {
|
|
|
11176
12409
|
pollInterval: 100
|
|
11177
12410
|
}
|
|
11178
12411
|
});
|
|
12412
|
+
this.readyPromise = new Promise((resolve13) => {
|
|
12413
|
+
this.resolveReady = resolve13;
|
|
12414
|
+
});
|
|
12415
|
+
this.watcher.once("ready", () => {
|
|
12416
|
+
this.resolveReady?.();
|
|
12417
|
+
this.resolveReady = null;
|
|
12418
|
+
});
|
|
11179
12419
|
this.watcher.on("error", (error) => {
|
|
11180
12420
|
const err = error instanceof Error ? error : null;
|
|
11181
12421
|
if (err?.code === "EPERM" || err?.code === "EACCES") {
|
|
@@ -11207,24 +12447,24 @@ var FileWatcher = class {
|
|
|
11207
12447
|
this.scheduleFlush();
|
|
11208
12448
|
}
|
|
11209
12449
|
isProjectConfigPath(filePath) {
|
|
11210
|
-
const relativePath =
|
|
11211
|
-
const normalizedRelativePath =
|
|
12450
|
+
const relativePath = path17.relative(this.projectRoot, filePath);
|
|
12451
|
+
const normalizedRelativePath = path17.normalize(relativePath);
|
|
11212
12452
|
return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
|
|
11213
12453
|
}
|
|
11214
12454
|
isProjectConfigPathOrAncestor(relativePath) {
|
|
11215
|
-
const normalizedRelativePath =
|
|
12455
|
+
const normalizedRelativePath = path17.normalize(relativePath);
|
|
11216
12456
|
return this.getProjectConfigRelativePaths().some(
|
|
11217
|
-
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${
|
|
12457
|
+
(configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path17.sep}`)
|
|
11218
12458
|
);
|
|
11219
12459
|
}
|
|
11220
12460
|
getProjectConfigRelativePaths() {
|
|
11221
12461
|
if (this.configPath) {
|
|
11222
|
-
return [
|
|
12462
|
+
return [path17.normalize(path17.relative(this.projectRoot, this.configPath))];
|
|
11223
12463
|
}
|
|
11224
12464
|
return [
|
|
11225
12465
|
resolveProjectConfigPath(this.projectRoot, this.host),
|
|
11226
12466
|
resolveWritableProjectConfigPath(this.projectRoot, this.host)
|
|
11227
|
-
].map((configPath) =>
|
|
12467
|
+
].map((configPath) => path17.normalize(path17.relative(this.projectRoot, configPath)));
|
|
11228
12468
|
}
|
|
11229
12469
|
scheduleFlush() {
|
|
11230
12470
|
if (this.debounceTimer) {
|
|
@@ -11239,7 +12479,7 @@ var FileWatcher = class {
|
|
|
11239
12479
|
return;
|
|
11240
12480
|
}
|
|
11241
12481
|
const changes = Array.from(this.pendingChanges.entries()).map(
|
|
11242
|
-
([
|
|
12482
|
+
([path24, type]) => ({ path: path24, type })
|
|
11243
12483
|
);
|
|
11244
12484
|
this.pendingChanges.clear();
|
|
11245
12485
|
try {
|
|
@@ -11248,25 +12488,32 @@ var FileWatcher = class {
|
|
|
11248
12488
|
console.error("Error handling file changes:", error);
|
|
11249
12489
|
}
|
|
11250
12490
|
}
|
|
11251
|
-
stop() {
|
|
12491
|
+
async stop() {
|
|
11252
12492
|
if (this.debounceTimer) {
|
|
11253
12493
|
clearTimeout(this.debounceTimer);
|
|
11254
12494
|
this.debounceTimer = null;
|
|
11255
12495
|
}
|
|
11256
12496
|
if (this.watcher) {
|
|
11257
|
-
this.watcher
|
|
12497
|
+
const watcher = this.watcher;
|
|
11258
12498
|
this.watcher = null;
|
|
12499
|
+
await watcher.close();
|
|
11259
12500
|
}
|
|
12501
|
+
this.resolveReady?.();
|
|
12502
|
+
this.resolveReady = null;
|
|
12503
|
+
this.readyPromise = null;
|
|
11260
12504
|
this.pendingChanges.clear();
|
|
11261
12505
|
this.onChanges = null;
|
|
11262
12506
|
}
|
|
11263
12507
|
isRunning() {
|
|
11264
12508
|
return this.watcher !== null;
|
|
11265
12509
|
}
|
|
12510
|
+
async waitUntilReady() {
|
|
12511
|
+
await (this.readyPromise ?? Promise.resolve());
|
|
12512
|
+
}
|
|
11266
12513
|
};
|
|
11267
12514
|
|
|
11268
12515
|
// src/watcher/git-head-watcher.ts
|
|
11269
|
-
import * as
|
|
12516
|
+
import * as path18 from "path";
|
|
11270
12517
|
var GitHeadWatcher = class {
|
|
11271
12518
|
watcher = null;
|
|
11272
12519
|
projectRoot;
|
|
@@ -11288,7 +12535,7 @@ var GitHeadWatcher = class {
|
|
|
11288
12535
|
this.onBranchChange = handler;
|
|
11289
12536
|
this.currentBranch = getCurrentBranch(this.projectRoot);
|
|
11290
12537
|
const headPath = getHeadPath(this.projectRoot);
|
|
11291
|
-
const refsPath =
|
|
12538
|
+
const refsPath = path18.join(this.projectRoot, ".git", "refs", "heads");
|
|
11292
12539
|
this.watcher = chokidar_default.watch([headPath, refsPath], {
|
|
11293
12540
|
persistent: true,
|
|
11294
12541
|
ignoreInitial: true,
|
|
@@ -11325,14 +12572,15 @@ var GitHeadWatcher = class {
|
|
|
11325
12572
|
getCurrentBranch() {
|
|
11326
12573
|
return this.currentBranch;
|
|
11327
12574
|
}
|
|
11328
|
-
stop() {
|
|
12575
|
+
async stop() {
|
|
11329
12576
|
if (this.debounceTimer) {
|
|
11330
12577
|
clearTimeout(this.debounceTimer);
|
|
11331
12578
|
this.debounceTimer = null;
|
|
11332
12579
|
}
|
|
11333
12580
|
if (this.watcher) {
|
|
11334
|
-
this.watcher
|
|
12581
|
+
const watcher = this.watcher;
|
|
11335
12582
|
this.watcher = null;
|
|
12583
|
+
await watcher.close();
|
|
11336
12584
|
}
|
|
11337
12585
|
this.onBranchChange = null;
|
|
11338
12586
|
}
|
|
@@ -11350,6 +12598,8 @@ var BackgroundReindexer = class {
|
|
|
11350
12598
|
running = false;
|
|
11351
12599
|
pending = false;
|
|
11352
12600
|
stopped = false;
|
|
12601
|
+
retryTimer = null;
|
|
12602
|
+
retryDelayMs = 50;
|
|
11353
12603
|
request() {
|
|
11354
12604
|
if (this.stopped) {
|
|
11355
12605
|
return;
|
|
@@ -11360,9 +12610,13 @@ var BackgroundReindexer = class {
|
|
|
11360
12610
|
stop() {
|
|
11361
12611
|
this.stopped = true;
|
|
11362
12612
|
this.pending = false;
|
|
12613
|
+
if (this.retryTimer) {
|
|
12614
|
+
clearTimeout(this.retryTimer);
|
|
12615
|
+
this.retryTimer = null;
|
|
12616
|
+
}
|
|
11363
12617
|
}
|
|
11364
12618
|
drain() {
|
|
11365
|
-
if (this.stopped || this.running || !this.pending) {
|
|
12619
|
+
if (this.stopped || this.running || this.retryTimer || !this.pending) {
|
|
11366
12620
|
return;
|
|
11367
12621
|
}
|
|
11368
12622
|
this.pending = false;
|
|
@@ -11370,13 +12624,29 @@ var BackgroundReindexer = class {
|
|
|
11370
12624
|
void this.run();
|
|
11371
12625
|
}
|
|
11372
12626
|
async run() {
|
|
12627
|
+
let retryAfterContention = false;
|
|
11373
12628
|
try {
|
|
11374
12629
|
await this.runIndex();
|
|
12630
|
+
this.retryDelayMs = 50;
|
|
11375
12631
|
} catch (error) {
|
|
11376
|
-
|
|
12632
|
+
if (isTransientIndexLockContention(error)) {
|
|
12633
|
+
this.pending = true;
|
|
12634
|
+
retryAfterContention = true;
|
|
12635
|
+
} else {
|
|
12636
|
+
console.error("[codebase-index] Background reindex failed:", error);
|
|
12637
|
+
}
|
|
11377
12638
|
} finally {
|
|
11378
12639
|
this.running = false;
|
|
11379
|
-
this.
|
|
12640
|
+
if (retryAfterContention && !this.stopped) {
|
|
12641
|
+
const delay = this.retryDelayMs;
|
|
12642
|
+
this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
|
|
12643
|
+
this.retryTimer = setTimeout(() => {
|
|
12644
|
+
this.retryTimer = null;
|
|
12645
|
+
this.drain();
|
|
12646
|
+
}, delay);
|
|
12647
|
+
} else {
|
|
12648
|
+
this.drain();
|
|
12649
|
+
}
|
|
11380
12650
|
}
|
|
11381
12651
|
}
|
|
11382
12652
|
};
|
|
@@ -11410,10 +12680,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
|
|
|
11410
12680
|
return {
|
|
11411
12681
|
fileWatcher,
|
|
11412
12682
|
gitWatcher,
|
|
11413
|
-
|
|
12683
|
+
whenReady() {
|
|
12684
|
+
return fileWatcher.waitUntilReady();
|
|
12685
|
+
},
|
|
12686
|
+
async stop() {
|
|
11414
12687
|
backgroundReindexer.stop();
|
|
11415
|
-
fileWatcher.stop();
|
|
11416
|
-
gitWatcher?.stop();
|
|
12688
|
+
await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
|
|
11417
12689
|
}
|
|
11418
12690
|
};
|
|
11419
12691
|
}
|
|
@@ -11519,13 +12791,13 @@ var pr_impact = tool({
|
|
|
11519
12791
|
});
|
|
11520
12792
|
|
|
11521
12793
|
// src/tools/index.ts
|
|
11522
|
-
import { writeFileSync as
|
|
11523
|
-
import * as
|
|
11524
|
-
import * as
|
|
12794
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
12795
|
+
import * as os5 from "os";
|
|
12796
|
+
import * as path21 from "path";
|
|
11525
12797
|
|
|
11526
12798
|
// src/tools/visualize/activity.ts
|
|
11527
12799
|
import { execFileSync } from "child_process";
|
|
11528
|
-
import * as
|
|
12800
|
+
import * as path19 from "path";
|
|
11529
12801
|
function attachRecentActivity(data, projectRoot) {
|
|
11530
12802
|
const activity = readGitActivity(projectRoot);
|
|
11531
12803
|
const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
|
|
@@ -11687,7 +12959,7 @@ function normalizePath2(filePath) {
|
|
|
11687
12959
|
return filePath.replace(/\\/g, "/");
|
|
11688
12960
|
}
|
|
11689
12961
|
function toGitRelativePath(projectRoot, filePath) {
|
|
11690
|
-
const relativePath =
|
|
12962
|
+
const relativePath = path19.isAbsolute(filePath) ? path19.relative(projectRoot, filePath) : filePath;
|
|
11691
12963
|
return normalizePath2(relativePath);
|
|
11692
12964
|
}
|
|
11693
12965
|
|
|
@@ -11945,7 +13217,7 @@ render();
|
|
|
11945
13217
|
}
|
|
11946
13218
|
|
|
11947
13219
|
// src/tools/visualize/transform.ts
|
|
11948
|
-
import * as
|
|
13220
|
+
import * as path20 from "path";
|
|
11949
13221
|
|
|
11950
13222
|
// src/tools/visualize/modules.ts
|
|
11951
13223
|
var MAX_MODULES = 18;
|
|
@@ -12078,8 +13350,8 @@ function compactModules(prefixToNodes) {
|
|
|
12078
13350
|
function deriveModules(nodes) {
|
|
12079
13351
|
const initial = /* @__PURE__ */ new Map();
|
|
12080
13352
|
for (const node of nodes) {
|
|
12081
|
-
const
|
|
12082
|
-
const prefix = modulePrefixFromRelativePath(
|
|
13353
|
+
const relative11 = stripToProjectRelative(node.filePath);
|
|
13354
|
+
const prefix = modulePrefixFromRelativePath(relative11);
|
|
12083
13355
|
if (!initial.has(prefix)) initial.set(prefix, []);
|
|
12084
13356
|
initial.get(prefix)?.push(node);
|
|
12085
13357
|
}
|
|
@@ -12205,7 +13477,7 @@ function transformForVisualization(symbols, edges, options = {}) {
|
|
|
12205
13477
|
filePath: s.filePath,
|
|
12206
13478
|
kind: s.kind,
|
|
12207
13479
|
line: s.startLine,
|
|
12208
|
-
directory:
|
|
13480
|
+
directory: path20.dirname(s.filePath),
|
|
12209
13481
|
moduleId: "",
|
|
12210
13482
|
moduleLabel: ""
|
|
12211
13483
|
}));
|
|
@@ -12256,7 +13528,10 @@ var codebase_peek = tool2({
|
|
|
12256
13528
|
limit: z2.number().optional().default(10).describe("Maximum number of results to return"),
|
|
12257
13529
|
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12258
13530
|
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12259
|
-
chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type")
|
|
13531
|
+
chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
|
|
13532
|
+
blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
|
|
13533
|
+
blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
13534
|
+
blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
|
|
12260
13535
|
},
|
|
12261
13536
|
async execute(args, context) {
|
|
12262
13537
|
const results = await searchCodebase(context?.worktree, DEFAULT_HOST, args.query, {
|
|
@@ -12264,7 +13539,10 @@ var codebase_peek = tool2({
|
|
|
12264
13539
|
fileType: args.fileType,
|
|
12265
13540
|
directory: args.directory,
|
|
12266
13541
|
chunkType: args.chunkType,
|
|
12267
|
-
metadataOnly: true
|
|
13542
|
+
metadataOnly: true,
|
|
13543
|
+
blameAuthor: args.blameAuthor,
|
|
13544
|
+
blameSha: args.blameSha,
|
|
13545
|
+
blameSince: args.blameSince
|
|
12268
13546
|
});
|
|
12269
13547
|
return formatCodebasePeek(results);
|
|
12270
13548
|
}
|
|
@@ -12280,7 +13558,9 @@ var index_codebase = tool2({
|
|
|
12280
13558
|
const result = await runIndexCodebase(context?.worktree, DEFAULT_HOST, args, (title, metadata) => {
|
|
12281
13559
|
context.metadata({ title, metadata });
|
|
12282
13560
|
});
|
|
12283
|
-
|
|
13561
|
+
if (result.kind === "estimate") return formatCostEstimate(result.estimate);
|
|
13562
|
+
if (result.kind === "busy") return result.text;
|
|
13563
|
+
return formatIndexStats(result.stats, args.verbose ?? false);
|
|
12284
13564
|
}
|
|
12285
13565
|
});
|
|
12286
13566
|
var index_status = tool2({
|
|
@@ -12294,7 +13574,9 @@ var index_health_check = tool2({
|
|
|
12294
13574
|
description: "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
|
|
12295
13575
|
args: {},
|
|
12296
13576
|
async execute(_args, context) {
|
|
12297
|
-
|
|
13577
|
+
const result = await runIndexHealthCheck(context?.worktree, DEFAULT_HOST);
|
|
13578
|
+
if (result.kind === "busy") return result.text;
|
|
13579
|
+
return formatHealthCheck(result.health);
|
|
12298
13580
|
}
|
|
12299
13581
|
});
|
|
12300
13582
|
var index_metrics = tool2({
|
|
@@ -12347,7 +13629,10 @@ var codebase_search = tool2({
|
|
|
12347
13629
|
fileType: z2.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
|
|
12348
13630
|
directory: z2.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
|
|
12349
13631
|
chunkType: z2.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
|
|
12350
|
-
contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)")
|
|
13632
|
+
contextLines: z2.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
|
|
13633
|
+
blameAuthor: z2.string().optional().describe("Filter by git blame author name or email"),
|
|
13634
|
+
blameSha: z2.string().optional().describe("Filter by git blame commit SHA or prefix"),
|
|
13635
|
+
blameSince: z2.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
|
|
12351
13636
|
},
|
|
12352
13637
|
async execute(args, context) {
|
|
12353
13638
|
const results = await searchCodebase(context?.worktree, DEFAULT_HOST, args.query, {
|
|
@@ -12355,7 +13640,10 @@ var codebase_search = tool2({
|
|
|
12355
13640
|
fileType: args.fileType,
|
|
12356
13641
|
directory: args.directory,
|
|
12357
13642
|
chunkType: args.chunkType,
|
|
12358
|
-
contextLines: args.contextLines
|
|
13643
|
+
contextLines: args.contextLines,
|
|
13644
|
+
blameAuthor: args.blameAuthor,
|
|
13645
|
+
blameSha: args.blameSha,
|
|
13646
|
+
blameSince: args.blameSince
|
|
12359
13647
|
});
|
|
12360
13648
|
if (results.length === 0) {
|
|
12361
13649
|
return "No matching code found. Try a different query or run index_codebase first.";
|
|
@@ -12394,22 +13682,10 @@ var call_graph = tool2({
|
|
|
12394
13682
|
return "Error: 'symbolId' is required when direction is 'callees'. First use direction='callers' to find the symbol ID.";
|
|
12395
13683
|
}
|
|
12396
13684
|
const { callees } = await getCallGraphData(context?.worktree, DEFAULT_HOST, args);
|
|
12397
|
-
|
|
12398
|
-
return `No callees found for symbol ${args.symbolId}${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. The function may not call any other tracked functions.`;
|
|
12399
|
-
}
|
|
12400
|
-
return callees.map((edge, index) => {
|
|
12401
|
-
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
12402
|
-
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
12403
|
-
}).join("\n");
|
|
13685
|
+
return formatCallGraphCallees(args.symbolId, callees, args.relationshipType);
|
|
12404
13686
|
}
|
|
12405
13687
|
const { callers } = await getCallGraphData(context?.worktree, DEFAULT_HOST, args);
|
|
12406
|
-
|
|
12407
|
-
return `No callers found for "${args.name}"${args.relationshipType ? ` with type ${args.relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
|
|
12408
|
-
}
|
|
12409
|
-
return callers.map((edge, index) => {
|
|
12410
|
-
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
12411
|
-
return `[${index + 1}] \u2190 from ${edge.fromSymbolName ?? "<unknown>"} in ${edge.fromSymbolFilePath ?? "<unknown file>"} [${edge.fromSymbolId}] (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? " [resolved]" : " [unresolved]"}`;
|
|
12412
|
-
}).join("\n");
|
|
13688
|
+
return formatCallGraphCallers(args.name, callers, args.relationshipType);
|
|
12413
13689
|
}
|
|
12414
13690
|
});
|
|
12415
13691
|
var call_graph_path = tool2({
|
|
@@ -12420,17 +13696,8 @@ var call_graph_path = tool2({
|
|
|
12420
13696
|
maxDepth: z2.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
|
|
12421
13697
|
},
|
|
12422
13698
|
async execute(args, context) {
|
|
12423
|
-
const
|
|
12424
|
-
|
|
12425
|
-
return `No path found between "${args.from}" and "${args.to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
12426
|
-
}
|
|
12427
|
-
const formatted = path22.map((hop, index) => {
|
|
12428
|
-
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
12429
|
-
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
12430
|
-
return `${prefix} ${hop.symbolName}${location}`;
|
|
12431
|
-
});
|
|
12432
|
-
return `Path (${path22.length} hops):
|
|
12433
|
-
${formatted.join("\n")}`;
|
|
13699
|
+
const path24 = await getCallGraphPath(context?.worktree, DEFAULT_HOST, args.from, args.to, args.maxDepth);
|
|
13700
|
+
return formatCallGraphPath(args.from, args.to, path24);
|
|
12434
13701
|
}
|
|
12435
13702
|
});
|
|
12436
13703
|
var add_knowledge_base = tool2({
|
|
@@ -12483,8 +13750,8 @@ var index_visualize = tool2({
|
|
|
12483
13750
|
return "No connected symbols found for visualization. Try including orphans with includeOrphans=true, or check that the call graph has resolved edges.";
|
|
12484
13751
|
}
|
|
12485
13752
|
const html = generateVisualizationHtml(vizData);
|
|
12486
|
-
const outputPath =
|
|
12487
|
-
|
|
13753
|
+
const outputPath = path21.join(os5.tmpdir(), `call-graph-${Date.now()}.html`);
|
|
13754
|
+
writeFileSync5(outputPath, html, "utf-8");
|
|
12488
13755
|
let result = `Temporal call graph visualization generated: ${outputPath}
|
|
12489
13756
|
|
|
12490
13757
|
`;
|
|
@@ -12505,8 +13772,8 @@ var index_visualize = tool2({
|
|
|
12505
13772
|
});
|
|
12506
13773
|
|
|
12507
13774
|
// src/commands/loader.ts
|
|
12508
|
-
import { existsSync as
|
|
12509
|
-
import * as
|
|
13775
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
|
|
13776
|
+
import * as path22 from "path";
|
|
12510
13777
|
function parseFrontmatter(content) {
|
|
12511
13778
|
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
|
|
12512
13779
|
const match = content.match(frontmatterRegex);
|
|
@@ -12527,21 +13794,21 @@ function parseFrontmatter(content) {
|
|
|
12527
13794
|
}
|
|
12528
13795
|
function loadCommandsFromDirectory(commandsDir) {
|
|
12529
13796
|
const commands = /* @__PURE__ */ new Map();
|
|
12530
|
-
if (!
|
|
13797
|
+
if (!existsSync11(commandsDir)) {
|
|
12531
13798
|
return commands;
|
|
12532
13799
|
}
|
|
12533
|
-
const files =
|
|
13800
|
+
const files = readdirSync3(commandsDir).filter((f) => f.endsWith(".md"));
|
|
12534
13801
|
for (const file of files) {
|
|
12535
|
-
const filePath =
|
|
13802
|
+
const filePath = path22.join(commandsDir, file);
|
|
12536
13803
|
let content;
|
|
12537
13804
|
try {
|
|
12538
|
-
content =
|
|
13805
|
+
content = readFileSync8(filePath, "utf-8");
|
|
12539
13806
|
} catch (error) {
|
|
12540
13807
|
const message = error instanceof Error ? error.message : String(error);
|
|
12541
13808
|
throw new Error(`Failed to load command file ${filePath}: ${message}`);
|
|
12542
13809
|
}
|
|
12543
13810
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
12544
|
-
const name =
|
|
13811
|
+
const name = path22.basename(file, ".md");
|
|
12545
13812
|
const description = frontmatter.description || `Run the ${name} command`;
|
|
12546
13813
|
commands.set(name, {
|
|
12547
13814
|
description,
|
|
@@ -12845,18 +14112,37 @@ var RoutingHintController = class {
|
|
|
12845
14112
|
};
|
|
12846
14113
|
|
|
12847
14114
|
// src/utils/auto-index.ts
|
|
14115
|
+
var INITIAL_RETRY_DELAY_MS = 50;
|
|
14116
|
+
var MAX_RETRY_DELAY_MS = 500;
|
|
14117
|
+
var autoIndexStates = /* @__PURE__ */ new WeakMap();
|
|
12848
14118
|
function getErrorMessage3(error) {
|
|
12849
14119
|
return error instanceof Error ? error.message : String(error);
|
|
12850
14120
|
}
|
|
12851
|
-
function
|
|
12852
|
-
indexer.
|
|
12853
|
-
|
|
12854
|
-
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
12855
|
-
});
|
|
14121
|
+
function runAutoIndex(indexer, projectRoot, state) {
|
|
14122
|
+
void indexer.index().then(() => {
|
|
14123
|
+
autoIndexStates.delete(indexer);
|
|
12856
14124
|
}).catch((error) => {
|
|
12857
|
-
|
|
14125
|
+
if (!isTransientIndexLockContention(error)) {
|
|
14126
|
+
autoIndexStates.delete(indexer);
|
|
14127
|
+
console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
|
|
14128
|
+
return;
|
|
14129
|
+
}
|
|
14130
|
+
const retryDelayMs = state.retryDelayMs;
|
|
14131
|
+
state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
|
|
14132
|
+
const retryTimer = setTimeout(() => {
|
|
14133
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
14134
|
+
}, retryDelayMs);
|
|
14135
|
+
retryTimer.unref?.();
|
|
12858
14136
|
});
|
|
12859
14137
|
}
|
|
14138
|
+
function startAutoIndex(indexer, projectRoot) {
|
|
14139
|
+
if (autoIndexStates.has(indexer)) return;
|
|
14140
|
+
const state = {
|
|
14141
|
+
retryDelayMs: INITIAL_RETRY_DELAY_MS
|
|
14142
|
+
};
|
|
14143
|
+
autoIndexStates.set(indexer, state);
|
|
14144
|
+
runAutoIndex(indexer, projectRoot, state);
|
|
14145
|
+
}
|
|
12860
14146
|
|
|
12861
14147
|
// src/index.ts
|
|
12862
14148
|
var activeWatchers = /* @__PURE__ */ new Map();
|
|
@@ -12873,9 +14159,9 @@ function replaceActiveWatcher(projectRoot, nextWatcher) {
|
|
|
12873
14159
|
function getCommandsDir() {
|
|
12874
14160
|
let currentDir = process.cwd();
|
|
12875
14161
|
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
12876
|
-
currentDir =
|
|
14162
|
+
currentDir = path23.dirname(fileURLToPath2(import.meta.url));
|
|
12877
14163
|
}
|
|
12878
|
-
return
|
|
14164
|
+
return path23.join(currentDir, "..", "commands");
|
|
12879
14165
|
}
|
|
12880
14166
|
function appendRoutingHints(output, hints, preferredRole) {
|
|
12881
14167
|
const preferredBucket = preferredRole === "developer" ? output.developer : output.system;
|
|
@@ -12895,7 +14181,7 @@ var plugin = async ({ directory, worktree }) => {
|
|
|
12895
14181
|
initializeTools2(projectRoot, config);
|
|
12896
14182
|
const getProjectIndexer = () => getIndexerForProject2(projectRoot);
|
|
12897
14183
|
const routingHints = config.search.routingHints ? new RoutingHintController(() => getProjectIndexer().getStatus(), 200, config.search.routingGraphHandoffHints) : null;
|
|
12898
|
-
const isHomeDir =
|
|
14184
|
+
const isHomeDir = path23.resolve(projectRoot) === path23.resolve(os6.homedir());
|
|
12899
14185
|
const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(projectRoot));
|
|
12900
14186
|
if (isHomeDir) {
|
|
12901
14187
|
console.warn(
|