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/pi-extension.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(path17, 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(path17);
|
|
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 = (path17, originalPath, doThrow) => {
|
|
524
|
+
if (!isString(path17)) {
|
|
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 (!path17) {
|
|
531
531
|
return doThrow(`path must not be empty`, TypeError);
|
|
532
532
|
}
|
|
533
|
-
if (checkPath.isNotRelative(
|
|
533
|
+
if (checkPath.isNotRelative(path17)) {
|
|
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 = (path17) => REGEX_TEST_INVALID_PATH.test(path17);
|
|
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 path17 = originalPath && checkPath.convert(originalPath);
|
|
573
573
|
checkPath(
|
|
574
|
-
|
|
574
|
+
path17,
|
|
575
575
|
originalPath,
|
|
576
576
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
577
577
|
);
|
|
578
|
-
return this._t(
|
|
578
|
+
return this._t(path17, cache, checkUnignored, slices);
|
|
579
579
|
}
|
|
580
|
-
checkIgnore(
|
|
581
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
582
|
-
return this.test(
|
|
580
|
+
checkIgnore(path17) {
|
|
581
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path17)) {
|
|
582
|
+
return this.test(path17);
|
|
583
583
|
}
|
|
584
|
-
const slices =
|
|
584
|
+
const slices = path17.split(SLASH).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(path17, false, MODE_CHECK_IGNORE);
|
|
598
598
|
}
|
|
599
|
-
_t(
|
|
600
|
-
if (
|
|
601
|
-
return cache[
|
|
599
|
+
_t(path17, cache, checkUnignored, slices) {
|
|
600
|
+
if (path17 in cache) {
|
|
601
|
+
return cache[path17];
|
|
602
602
|
}
|
|
603
603
|
if (!slices) {
|
|
604
|
-
slices =
|
|
604
|
+
slices = path17.split(SLASH).filter(Boolean);
|
|
605
605
|
}
|
|
606
606
|
slices.pop();
|
|
607
607
|
if (!slices.length) {
|
|
608
|
-
return cache[
|
|
608
|
+
return cache[path17] = this._rules.test(path17, checkUnignored, MODE_IGNORE);
|
|
609
609
|
}
|
|
610
610
|
const parent = this._t(
|
|
611
611
|
slices.join(SLASH) + SLASH,
|
|
@@ -613,29 +613,29 @@ var require_ignore = __commonJS({
|
|
|
613
613
|
checkUnignored,
|
|
614
614
|
slices
|
|
615
615
|
);
|
|
616
|
-
return cache[
|
|
616
|
+
return cache[path17] = parent.ignored ? parent : this._rules.test(path17, checkUnignored, MODE_IGNORE);
|
|
617
617
|
}
|
|
618
|
-
ignores(
|
|
619
|
-
return this._test(
|
|
618
|
+
ignores(path17) {
|
|
619
|
+
return this._test(path17, this._ignoreCache, false).ignored;
|
|
620
620
|
}
|
|
621
621
|
createFilter() {
|
|
622
|
-
return (
|
|
622
|
+
return (path17) => !this.ignores(path17);
|
|
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(path17) {
|
|
629
|
+
return this._test(path17, this._testCache, true);
|
|
630
630
|
}
|
|
631
631
|
};
|
|
632
632
|
var factory = (options) => new Ignore2(options);
|
|
633
|
-
var isPathValid = (
|
|
633
|
+
var isPathValid = (path17) => checkPath(path17 && checkPath.convert(path17), path17, 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 = (path17) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path17) || isNotRelative(path17);
|
|
639
639
|
};
|
|
640
640
|
if (
|
|
641
641
|
// Detect `process` so that it can run in browsers.
|
|
@@ -651,14 +651,14 @@ var require_ignore = __commonJS({
|
|
|
651
651
|
});
|
|
652
652
|
|
|
653
653
|
// src/pi-extension.ts
|
|
654
|
-
import { Type } from "typebox";
|
|
654
|
+
import { Type as Type2 } from "typebox";
|
|
655
655
|
|
|
656
656
|
// src/config/constants.ts
|
|
657
657
|
var DEFAULT_INCLUDE = [
|
|
658
|
-
"**/*.{ts,tsx,js,jsx,mjs,cjs}",
|
|
658
|
+
"**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
|
|
659
659
|
"**/*.{py,pyi}",
|
|
660
|
-
"**/*.{go,rs,java,kt,scala}",
|
|
661
|
-
"**/*.{c,cpp,cc,h,hpp}",
|
|
660
|
+
"**/*.{go,rs,java,cs,kt,scala}",
|
|
661
|
+
"**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
|
|
662
662
|
"**/*.{rb,php,inc,swift}",
|
|
663
663
|
"**/*.{cls,trigger}",
|
|
664
664
|
"**/*.{vue,svelte,astro}",
|
|
@@ -783,7 +783,8 @@ function getDefaultIndexingConfig() {
|
|
|
783
783
|
requireProjectMarker: true,
|
|
784
784
|
maxDepth: 5,
|
|
785
785
|
maxFilesPerDirectory: 100,
|
|
786
|
-
fallbackToTextOnMaxChunks: true
|
|
786
|
+
fallbackToTextOnMaxChunks: true,
|
|
787
|
+
gitBlame: { enabled: false }
|
|
787
788
|
};
|
|
788
789
|
}
|
|
789
790
|
function getDefaultSearchConfig() {
|
|
@@ -907,7 +908,10 @@ function parseConfig(raw) {
|
|
|
907
908
|
requireProjectMarker: typeof rawIndexing.requireProjectMarker === "boolean" ? rawIndexing.requireProjectMarker : defaultIndexing.requireProjectMarker,
|
|
908
909
|
maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
|
|
909
910
|
maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
|
|
910
|
-
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks
|
|
911
|
+
fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
|
|
912
|
+
gitBlame: {
|
|
913
|
+
enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
|
|
914
|
+
}
|
|
911
915
|
};
|
|
912
916
|
const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
|
|
913
917
|
const search = {
|
|
@@ -1323,8 +1327,8 @@ function formatPrImpact(result) {
|
|
|
1323
1327
|
}
|
|
1324
1328
|
|
|
1325
1329
|
// src/tools/operations.ts
|
|
1326
|
-
import { existsSync as
|
|
1327
|
-
import * as
|
|
1330
|
+
import { existsSync as existsSync10, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
1331
|
+
import * as path16 from "path";
|
|
1328
1332
|
|
|
1329
1333
|
// src/config/merger.ts
|
|
1330
1334
|
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
@@ -1494,21 +1498,21 @@ function getProjectIndexRelativePath(host) {
|
|
|
1494
1498
|
return CODEBASE_PROJECT_INDEX_RELATIVE_PATH;
|
|
1495
1499
|
}
|
|
1496
1500
|
}
|
|
1497
|
-
function resolveWorktreeFallbackPath(
|
|
1498
|
-
const mainRepoRoot = resolveWorktreeMainRepoRoot(
|
|
1501
|
+
function resolveWorktreeFallbackPath(projectRoot3, relativePath) {
|
|
1502
|
+
const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot3);
|
|
1499
1503
|
if (!mainRepoRoot) {
|
|
1500
1504
|
return null;
|
|
1501
1505
|
}
|
|
1502
1506
|
const fallbackPath = path4.join(mainRepoRoot, relativePath);
|
|
1503
1507
|
return existsSync4(fallbackPath) ? fallbackPath : null;
|
|
1504
1508
|
}
|
|
1505
|
-
function resolveWorktreeFallbackProjectIndexPath(
|
|
1506
|
-
const inheritedHostPath = resolveWorktreeFallbackPath(
|
|
1509
|
+
function resolveWorktreeFallbackProjectIndexPath(projectRoot3, host) {
|
|
1510
|
+
const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
|
|
1507
1511
|
if (inheritedHostPath) {
|
|
1508
1512
|
return inheritedHostPath;
|
|
1509
1513
|
}
|
|
1510
1514
|
if (host !== "opencode") {
|
|
1511
|
-
return resolveWorktreeFallbackPath(
|
|
1515
|
+
return resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
|
|
1512
1516
|
}
|
|
1513
1517
|
return null;
|
|
1514
1518
|
}
|
|
@@ -1518,8 +1522,11 @@ function getHostProjectConfigRelativePath(host) {
|
|
|
1518
1522
|
function getHostProjectIndexRelativePath(host) {
|
|
1519
1523
|
return getProjectIndexRelativePath(host);
|
|
1520
1524
|
}
|
|
1521
|
-
function hasHostProjectConfig(
|
|
1522
|
-
return existsSync4(path4.join(
|
|
1525
|
+
function hasHostProjectConfig(projectRoot3, host) {
|
|
1526
|
+
return existsSync4(path4.join(projectRoot3, getProjectConfigRelativePath(host)));
|
|
1527
|
+
}
|
|
1528
|
+
function hasHostGlobalConfig(host) {
|
|
1529
|
+
return existsSync4(getGlobalConfigPath(host));
|
|
1523
1530
|
}
|
|
1524
1531
|
function getGlobalIndexPath(host = "opencode") {
|
|
1525
1532
|
switch (host) {
|
|
@@ -1560,6 +1567,9 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
1560
1567
|
return hostIndexPath;
|
|
1561
1568
|
}
|
|
1562
1569
|
if (host !== "opencode") {
|
|
1570
|
+
if (hasHostGlobalConfig(host)) {
|
|
1571
|
+
return hostIndexPath;
|
|
1572
|
+
}
|
|
1563
1573
|
const legacyIndexPath = getGlobalIndexPath("opencode");
|
|
1564
1574
|
if (existsSync4(legacyIndexPath)) {
|
|
1565
1575
|
return legacyIndexPath;
|
|
@@ -1567,55 +1577,55 @@ function resolveGlobalIndexPath(host = "opencode") {
|
|
|
1567
1577
|
}
|
|
1568
1578
|
return hostIndexPath;
|
|
1569
1579
|
}
|
|
1570
|
-
function resolveProjectConfigPath(
|
|
1571
|
-
const hostConfigPath = path4.join(
|
|
1580
|
+
function resolveProjectConfigPath(projectRoot3, host = "opencode") {
|
|
1581
|
+
const hostConfigPath = path4.join(projectRoot3, getProjectConfigRelativePath(host));
|
|
1572
1582
|
if (existsSync4(hostConfigPath)) {
|
|
1573
1583
|
return hostConfigPath;
|
|
1574
1584
|
}
|
|
1575
1585
|
if (host !== "opencode") {
|
|
1576
|
-
const legacyConfigPath = path4.join(
|
|
1586
|
+
const legacyConfigPath = path4.join(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
|
|
1577
1587
|
if (existsSync4(legacyConfigPath)) {
|
|
1578
1588
|
return legacyConfigPath;
|
|
1579
1589
|
}
|
|
1580
1590
|
}
|
|
1581
|
-
const hostFallback = resolveWorktreeFallbackPath(
|
|
1591
|
+
const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectConfigRelativePath(host));
|
|
1582
1592
|
if (hostFallback) {
|
|
1583
1593
|
return hostFallback;
|
|
1584
1594
|
}
|
|
1585
1595
|
if (host !== "opencode") {
|
|
1586
|
-
const legacyFallback = resolveWorktreeFallbackPath(
|
|
1596
|
+
const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
|
|
1587
1597
|
if (legacyFallback) {
|
|
1588
1598
|
return legacyFallback;
|
|
1589
1599
|
}
|
|
1590
1600
|
}
|
|
1591
1601
|
return hostConfigPath;
|
|
1592
1602
|
}
|
|
1593
|
-
function resolveWritableProjectConfigPath(
|
|
1594
|
-
return path4.join(
|
|
1603
|
+
function resolveWritableProjectConfigPath(projectRoot3, host = "opencode") {
|
|
1604
|
+
return path4.join(projectRoot3, getProjectConfigRelativePath(host));
|
|
1595
1605
|
}
|
|
1596
|
-
function resolveProjectIndexPath(
|
|
1606
|
+
function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
|
|
1597
1607
|
if (scope === "global") {
|
|
1598
1608
|
return resolveGlobalIndexPath(host);
|
|
1599
1609
|
}
|
|
1600
|
-
const localIndexPath = path4.join(
|
|
1610
|
+
const localIndexPath = path4.join(projectRoot3, getProjectIndexRelativePath(host));
|
|
1601
1611
|
if (existsSync4(localIndexPath)) {
|
|
1602
1612
|
return localIndexPath;
|
|
1603
1613
|
}
|
|
1604
1614
|
if (host !== "opencode") {
|
|
1605
|
-
const legacyIndexPath = path4.join(
|
|
1606
|
-
if (existsSync4(legacyIndexPath) && !hasHostProjectConfig(
|
|
1615
|
+
const legacyIndexPath = path4.join(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
|
|
1616
|
+
if (existsSync4(legacyIndexPath) && !hasHostProjectConfig(projectRoot3, host)) {
|
|
1607
1617
|
return legacyIndexPath;
|
|
1608
1618
|
}
|
|
1609
1619
|
}
|
|
1610
|
-
if (hasHostProjectConfig(
|
|
1620
|
+
if (hasHostProjectConfig(projectRoot3, host)) {
|
|
1611
1621
|
return localIndexPath;
|
|
1612
1622
|
}
|
|
1613
|
-
const hostFallback = resolveWorktreeFallbackPath(
|
|
1623
|
+
const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
|
|
1614
1624
|
if (hostFallback) {
|
|
1615
1625
|
return hostFallback;
|
|
1616
1626
|
}
|
|
1617
1627
|
if (host !== "opencode") {
|
|
1618
|
-
const legacyFallback = resolveWorktreeFallbackPath(
|
|
1628
|
+
const legacyFallback = resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
|
|
1619
1629
|
if (legacyFallback) {
|
|
1620
1630
|
return legacyFallback;
|
|
1621
1631
|
}
|
|
@@ -1748,14 +1758,14 @@ function loadJsonFile(filePath) {
|
|
|
1748
1758
|
throw new Error(`Failed to load config file ${filePath}: ${message}`);
|
|
1749
1759
|
}
|
|
1750
1760
|
}
|
|
1751
|
-
function materializeLocalProjectConfig(
|
|
1752
|
-
const localConfigPath = resolveWritableProjectConfigPath(
|
|
1761
|
+
function materializeLocalProjectConfig(projectRoot3, config, host = "opencode") {
|
|
1762
|
+
const localConfigPath = resolveWritableProjectConfigPath(projectRoot3, host);
|
|
1753
1763
|
mkdirSync(path7.dirname(localConfigPath), { recursive: true });
|
|
1754
1764
|
writeFileSync(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1755
1765
|
return localConfigPath;
|
|
1756
1766
|
}
|
|
1757
|
-
function loadProjectConfigLayer(
|
|
1758
|
-
const projectConfigPath = resolveProjectConfigPath(
|
|
1767
|
+
function loadProjectConfigLayer(projectRoot3, host = "opencode") {
|
|
1768
|
+
const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
|
|
1759
1769
|
const projectConfig = loadJsonFile(projectConfigPath);
|
|
1760
1770
|
if (!projectConfig) {
|
|
1761
1771
|
return {};
|
|
@@ -1766,14 +1776,14 @@ function loadProjectConfigLayer(projectRoot2, host = "opencode") {
|
|
|
1766
1776
|
normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
|
|
1767
1777
|
normalizedConfig.knowledgeBases,
|
|
1768
1778
|
projectConfigBaseDir,
|
|
1769
|
-
|
|
1779
|
+
projectRoot3
|
|
1770
1780
|
);
|
|
1771
1781
|
}
|
|
1772
1782
|
return normalizedConfig;
|
|
1773
1783
|
}
|
|
1774
|
-
function loadMergedConfig(
|
|
1784
|
+
function loadMergedConfig(projectRoot3, host = "opencode") {
|
|
1775
1785
|
const globalConfigPath = resolveGlobalConfigPath(host);
|
|
1776
|
-
const projectConfigPath = resolveProjectConfigPath(
|
|
1786
|
+
const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
|
|
1777
1787
|
let globalConfig = null;
|
|
1778
1788
|
let globalConfigError = null;
|
|
1779
1789
|
try {
|
|
@@ -1782,7 +1792,7 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
|
|
|
1782
1792
|
globalConfigError = error instanceof Error ? error : new Error(String(error));
|
|
1783
1793
|
}
|
|
1784
1794
|
const projectConfig = loadJsonFile(projectConfigPath);
|
|
1785
|
-
const normalizedProjectConfig = loadProjectConfigLayer(
|
|
1795
|
+
const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
|
|
1786
1796
|
if (globalConfigError) {
|
|
1787
1797
|
if (!projectConfig) {
|
|
1788
1798
|
throw globalConfigError;
|
|
@@ -1825,11 +1835,11 @@ function loadMergedConfig(projectRoot2, host = "opencode") {
|
|
|
1825
1835
|
}
|
|
1826
1836
|
|
|
1827
1837
|
// src/indexer/index.ts
|
|
1828
|
-
import { existsSync as
|
|
1829
|
-
import * as
|
|
1838
|
+
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";
|
|
1839
|
+
import * as path13 from "path";
|
|
1830
1840
|
import { performance as performance2 } from "perf_hooks";
|
|
1831
|
-
import { execFile as
|
|
1832
|
-
import { promisify as
|
|
1841
|
+
import { execFile as execFile3 } from "child_process";
|
|
1842
|
+
import { promisify as promisify3 } from "util";
|
|
1833
1843
|
|
|
1834
1844
|
// node_modules/eventemitter3/index.mjs
|
|
1835
1845
|
var import_index = __toESM(require_eventemitter3(), 1);
|
|
@@ -2933,17 +2943,17 @@ function validateExternalUrl(urlString) {
|
|
|
2933
2943
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2934
2944
|
return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
|
|
2935
2945
|
}
|
|
2936
|
-
const
|
|
2937
|
-
if (BLOCKED_HOSTNAMES.has(
|
|
2938
|
-
return { valid: false, reason: `Blocked: cloud metadata service (${
|
|
2946
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
2947
|
+
if (BLOCKED_HOSTNAMES.has(hostname2)) {
|
|
2948
|
+
return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
|
|
2939
2949
|
}
|
|
2940
2950
|
for (const pattern of BLOCKED_METADATA_IPS) {
|
|
2941
|
-
if (pattern.test(
|
|
2942
|
-
return { valid: false, reason: `Blocked: cloud metadata IP (${
|
|
2951
|
+
if (pattern.test(hostname2)) {
|
|
2952
|
+
return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
|
|
2943
2953
|
}
|
|
2944
2954
|
}
|
|
2945
|
-
if (/^169\.254\./.test(
|
|
2946
|
-
return { valid: false, reason: `Blocked: link-local address (${
|
|
2955
|
+
if (/^169\.254\./.test(hostname2)) {
|
|
2956
|
+
return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
|
|
2947
2957
|
}
|
|
2948
2958
|
return { valid: true };
|
|
2949
2959
|
}
|
|
@@ -3137,10 +3147,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
|
|
|
3137
3147
|
}
|
|
3138
3148
|
const batchResults = await Promise.all(
|
|
3139
3149
|
batches.map(async (batch) => {
|
|
3140
|
-
const requests = batch.map((
|
|
3150
|
+
const requests = batch.map((text3) => ({
|
|
3141
3151
|
model: `models/${this.modelInfo.model}`,
|
|
3142
3152
|
content: {
|
|
3143
|
-
parts: [{ text:
|
|
3153
|
+
parts: [{ text: text3 }]
|
|
3144
3154
|
},
|
|
3145
3155
|
taskType,
|
|
3146
3156
|
outputDimensionality: this.modelInfo.dimensions
|
|
@@ -3163,7 +3173,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
|
|
|
3163
3173
|
const data = await response.json();
|
|
3164
3174
|
return {
|
|
3165
3175
|
embeddings: data.embeddings.map((e) => e.values),
|
|
3166
|
-
tokensUsed: batch.reduce((sum,
|
|
3176
|
+
tokensUsed: batch.reduce((sum, text3) => sum + Math.ceil(text3.length / 4), 0)
|
|
3167
3177
|
};
|
|
3168
3178
|
})
|
|
3169
3179
|
);
|
|
@@ -3180,28 +3190,28 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
|
|
|
3180
3190
|
constructor(credentials, modelInfo) {
|
|
3181
3191
|
super(credentials, modelInfo);
|
|
3182
3192
|
}
|
|
3183
|
-
estimateTokens(
|
|
3184
|
-
return Math.ceil(
|
|
3193
|
+
estimateTokens(text3) {
|
|
3194
|
+
return Math.ceil(text3.length / 4);
|
|
3185
3195
|
}
|
|
3186
|
-
truncateToCharLimit(
|
|
3187
|
-
if (
|
|
3188
|
-
return
|
|
3196
|
+
truncateToCharLimit(text3, maxChars) {
|
|
3197
|
+
if (text3.length <= maxChars) {
|
|
3198
|
+
return text3;
|
|
3189
3199
|
}
|
|
3190
|
-
return `${
|
|
3200
|
+
return `${text3.slice(0, Math.max(0, maxChars - 17))}
|
|
3191
3201
|
... [truncated]`;
|
|
3192
3202
|
}
|
|
3193
3203
|
isContextLengthError(error) {
|
|
3194
3204
|
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
3195
3205
|
return message.includes("context length") && (message.includes("exceed") || message.includes("exceeded") || message.includes("too long")) || message.includes("input length exceeds the context length") || message.includes("context length exceeded");
|
|
3196
3206
|
}
|
|
3197
|
-
buildTruncationCandidates(
|
|
3207
|
+
buildTruncationCandidates(text3) {
|
|
3198
3208
|
const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
|
|
3199
3209
|
const candidateLimits = /* @__PURE__ */ new Set();
|
|
3200
|
-
const baselineLimit =
|
|
3210
|
+
const baselineLimit = text3.length > baseMaxChars ? baseMaxChars : Math.max(
|
|
3201
3211
|
_OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
|
|
3202
|
-
Math.floor(
|
|
3212
|
+
Math.floor(text3.length * 0.9)
|
|
3203
3213
|
);
|
|
3204
|
-
if (baselineLimit <
|
|
3214
|
+
if (baselineLimit < text3.length) {
|
|
3205
3215
|
candidateLimits.add(baselineLimit);
|
|
3206
3216
|
}
|
|
3207
3217
|
for (const factor of [0.75, 0.6, 0.45, 0.35, 0.25]) {
|
|
@@ -3209,19 +3219,19 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
|
|
|
3209
3219
|
_OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS,
|
|
3210
3220
|
Math.floor(baselineLimit * factor)
|
|
3211
3221
|
);
|
|
3212
|
-
if (scaledLimit <
|
|
3222
|
+
if (scaledLimit < text3.length) {
|
|
3213
3223
|
candidateLimits.add(scaledLimit);
|
|
3214
3224
|
}
|
|
3215
3225
|
}
|
|
3216
|
-
candidateLimits.add(Math.min(
|
|
3226
|
+
candidateLimits.add(Math.min(text3.length - 1, _OllamaEmbeddingProvider.MIN_TRUNCATION_CHARS));
|
|
3217
3227
|
const candidates = [];
|
|
3218
3228
|
const seen = /* @__PURE__ */ new Set();
|
|
3219
3229
|
for (const limit of [...candidateLimits].sort((a, b) => b - a)) {
|
|
3220
|
-
if (limit <= 0 || limit >=
|
|
3230
|
+
if (limit <= 0 || limit >= text3.length) {
|
|
3221
3231
|
continue;
|
|
3222
3232
|
}
|
|
3223
|
-
const truncated = this.truncateToCharLimit(
|
|
3224
|
-
if (truncated ===
|
|
3233
|
+
const truncated = this.truncateToCharLimit(text3, limit);
|
|
3234
|
+
if (truncated === text3 || seen.has(truncated)) {
|
|
3225
3235
|
continue;
|
|
3226
3236
|
}
|
|
3227
3237
|
seen.add(truncated);
|
|
@@ -3229,15 +3239,15 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
|
|
|
3229
3239
|
}
|
|
3230
3240
|
return candidates;
|
|
3231
3241
|
}
|
|
3232
|
-
async embedSingleWithFallback(
|
|
3242
|
+
async embedSingleWithFallback(text3) {
|
|
3233
3243
|
try {
|
|
3234
|
-
return await this.embedSingle(
|
|
3244
|
+
return await this.embedSingle(text3);
|
|
3235
3245
|
} catch (error) {
|
|
3236
3246
|
if (!this.isContextLengthError(error)) {
|
|
3237
3247
|
throw error;
|
|
3238
3248
|
}
|
|
3239
3249
|
let lastError = error;
|
|
3240
|
-
for (const truncated of this.buildTruncationCandidates(
|
|
3250
|
+
for (const truncated of this.buildTruncationCandidates(text3)) {
|
|
3241
3251
|
try {
|
|
3242
3252
|
return await this.embedSingle(truncated);
|
|
3243
3253
|
} catch (retryError) {
|
|
@@ -3250,7 +3260,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
|
|
|
3250
3260
|
throw lastError;
|
|
3251
3261
|
}
|
|
3252
3262
|
}
|
|
3253
|
-
async embedSingle(
|
|
3263
|
+
async embedSingle(text3) {
|
|
3254
3264
|
const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
|
|
3255
3265
|
method: "POST",
|
|
3256
3266
|
headers: {
|
|
@@ -3258,7 +3268,7 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
|
|
|
3258
3268
|
},
|
|
3259
3269
|
body: JSON.stringify({
|
|
3260
3270
|
model: this.modelInfo.model,
|
|
3261
|
-
prompt:
|
|
3271
|
+
prompt: text3,
|
|
3262
3272
|
truncate: false
|
|
3263
3273
|
})
|
|
3264
3274
|
});
|
|
@@ -3269,13 +3279,13 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
|
|
|
3269
3279
|
const data = await response.json();
|
|
3270
3280
|
return {
|
|
3271
3281
|
embedding: data.embedding,
|
|
3272
|
-
tokensUsed: this.estimateTokens(
|
|
3282
|
+
tokensUsed: this.estimateTokens(text3)
|
|
3273
3283
|
};
|
|
3274
3284
|
}
|
|
3275
3285
|
async embedBatch(texts) {
|
|
3276
3286
|
const results = [];
|
|
3277
|
-
for (const
|
|
3278
|
-
results.push(await this.embedSingleWithFallback(
|
|
3287
|
+
for (const text3 of texts) {
|
|
3288
|
+
results.push(await this.embedSingleWithFallback(text3));
|
|
3279
3289
|
}
|
|
3280
3290
|
return {
|
|
3281
3291
|
embeddings: results.map((r) => r.embedding),
|
|
@@ -3416,7 +3426,7 @@ var SiliconFlowReranker = class {
|
|
|
3416
3426
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
3417
3427
|
import { existsSync as existsSync6, readFileSync as readFileSync5, promises as fsPromises } from "fs";
|
|
3418
3428
|
import * as path8 from "path";
|
|
3419
|
-
function createIgnoreFilter(
|
|
3429
|
+
function createIgnoreFilter(projectRoot3) {
|
|
3420
3430
|
const ig = (0, import_ignore.default)();
|
|
3421
3431
|
const defaultIgnores = [
|
|
3422
3432
|
"node_modules",
|
|
@@ -3437,7 +3447,7 @@ function createIgnoreFilter(projectRoot2) {
|
|
|
3437
3447
|
"**/*build*/**"
|
|
3438
3448
|
];
|
|
3439
3449
|
ig.add(defaultIgnores);
|
|
3440
|
-
const gitignorePath = path8.join(
|
|
3450
|
+
const gitignorePath = path8.join(projectRoot3, ".gitignore");
|
|
3441
3451
|
if (existsSync6(gitignorePath)) {
|
|
3442
3452
|
const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
|
|
3443
3453
|
ig.add(gitignoreContent);
|
|
@@ -3459,13 +3469,13 @@ function matchGlob(filePath, pattern) {
|
|
|
3459
3469
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
3460
3470
|
return regex.test(filePath);
|
|
3461
3471
|
}
|
|
3462
|
-
async function* walkDirectory(dir,
|
|
3472
|
+
async function* walkDirectory(dir, projectRoot3, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
|
|
3463
3473
|
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
3464
3474
|
const filesInDir = [];
|
|
3465
3475
|
const subdirs = [];
|
|
3466
3476
|
for (const entry of entries) {
|
|
3467
3477
|
const fullPath = path8.join(dir, entry.name);
|
|
3468
|
-
const relativePath = path8.relative(
|
|
3478
|
+
const relativePath = path8.relative(projectRoot3, fullPath);
|
|
3469
3479
|
if (isHiddenPathSegment(entry.name)) {
|
|
3470
3480
|
if (entry.isDirectory()) {
|
|
3471
3481
|
skipped.push({ path: relativePath, reason: "excluded" });
|
|
@@ -3514,14 +3524,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
|
|
|
3514
3524
|
yield f;
|
|
3515
3525
|
}
|
|
3516
3526
|
for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
|
|
3517
|
-
skipped.push({ path: path8.relative(
|
|
3527
|
+
skipped.push({ path: path8.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
|
|
3518
3528
|
}
|
|
3519
3529
|
const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
|
|
3520
3530
|
if (canRecurse) {
|
|
3521
3531
|
for (const sub of subdirs) {
|
|
3522
3532
|
yield* walkDirectory(
|
|
3523
3533
|
sub.fullPath,
|
|
3524
|
-
|
|
3534
|
+
projectRoot3,
|
|
3525
3535
|
includePatterns,
|
|
3526
3536
|
excludePatterns,
|
|
3527
3537
|
ignoreFilter,
|
|
@@ -3533,14 +3543,14 @@ async function* walkDirectory(dir, projectRoot2, includePatterns, excludePattern
|
|
|
3533
3543
|
}
|
|
3534
3544
|
}
|
|
3535
3545
|
}
|
|
3536
|
-
async function collectFiles(
|
|
3546
|
+
async function collectFiles(projectRoot3, includePatterns, excludePatterns, maxFileSize, additionalRoots, walkOptions) {
|
|
3537
3547
|
const opts = walkOptions ?? { maxDepth: 5, maxFilesPerDirectory: 100 };
|
|
3538
|
-
const ignoreFilter = createIgnoreFilter(
|
|
3548
|
+
const ignoreFilter = createIgnoreFilter(projectRoot3);
|
|
3539
3549
|
const files = [];
|
|
3540
3550
|
const skipped = [];
|
|
3541
3551
|
for await (const file of walkDirectory(
|
|
3542
|
-
|
|
3543
|
-
|
|
3552
|
+
projectRoot3,
|
|
3553
|
+
projectRoot3,
|
|
3544
3554
|
includePatterns,
|
|
3545
3555
|
excludePatterns,
|
|
3546
3556
|
ignoreFilter,
|
|
@@ -3555,7 +3565,7 @@ async function collectFiles(projectRoot2, includePatterns, excludePatterns, maxF
|
|
|
3555
3565
|
const normalizedRoots = /* @__PURE__ */ new Set();
|
|
3556
3566
|
for (const kbRoot of additionalRoots) {
|
|
3557
3567
|
const resolved = path8.normalize(
|
|
3558
|
-
path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(
|
|
3568
|
+
path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot3, kbRoot)
|
|
3559
3569
|
);
|
|
3560
3570
|
normalizedRoots.add(resolved);
|
|
3561
3571
|
}
|
|
@@ -3980,6 +3990,12 @@ function createMockNativeBinding() {
|
|
|
3980
3990
|
constructor() {
|
|
3981
3991
|
throw error;
|
|
3982
3992
|
}
|
|
3993
|
+
static openReadOnly() {
|
|
3994
|
+
throw error;
|
|
3995
|
+
}
|
|
3996
|
+
static createEmptyReadOnly() {
|
|
3997
|
+
throw error;
|
|
3998
|
+
}
|
|
3983
3999
|
close() {
|
|
3984
4000
|
throw error;
|
|
3985
4001
|
}
|
|
@@ -4083,6 +4099,12 @@ var VectorStore = class {
|
|
|
4083
4099
|
load() {
|
|
4084
4100
|
this.inner.load();
|
|
4085
4101
|
}
|
|
4102
|
+
loadStrict() {
|
|
4103
|
+
this.inner.loadStrict();
|
|
4104
|
+
}
|
|
4105
|
+
hasFingerprint() {
|
|
4106
|
+
return this.inner.hasFingerprint();
|
|
4107
|
+
}
|
|
4086
4108
|
count() {
|
|
4087
4109
|
return this.inner.count();
|
|
4088
4110
|
}
|
|
@@ -4121,8 +4143,8 @@ var VectorStore = class {
|
|
|
4121
4143
|
var CHARS_PER_TOKEN = 4;
|
|
4122
4144
|
var MAX_BATCH_TOKENS = 7500;
|
|
4123
4145
|
var MAX_SINGLE_CHUNK_TOKENS = 2e3;
|
|
4124
|
-
function estimateTokens(
|
|
4125
|
-
return Math.ceil(
|
|
4146
|
+
function estimateTokens(text3) {
|
|
4147
|
+
return Math.ceil(text3.length / CHARS_PER_TOKEN);
|
|
4126
4148
|
}
|
|
4127
4149
|
function getEmbeddingHeaderParts(chunk, filePath) {
|
|
4128
4150
|
const parts = [];
|
|
@@ -4365,12 +4387,24 @@ var InvertedIndex = class {
|
|
|
4365
4387
|
return this.inner.documentCount();
|
|
4366
4388
|
}
|
|
4367
4389
|
};
|
|
4368
|
-
var Database = class {
|
|
4390
|
+
var Database = class _Database {
|
|
4369
4391
|
inner;
|
|
4370
4392
|
closed = false;
|
|
4371
4393
|
constructor(dbPath) {
|
|
4372
4394
|
this.inner = new native.Database(dbPath);
|
|
4373
4395
|
}
|
|
4396
|
+
static fromNative(inner) {
|
|
4397
|
+
const database = Object.create(_Database.prototype);
|
|
4398
|
+
database.inner = inner;
|
|
4399
|
+
database.closed = false;
|
|
4400
|
+
return database;
|
|
4401
|
+
}
|
|
4402
|
+
static openReadOnly(dbPath) {
|
|
4403
|
+
return _Database.fromNative(native.Database.openReadOnly(dbPath));
|
|
4404
|
+
}
|
|
4405
|
+
static createEmptyReadOnly() {
|
|
4406
|
+
return _Database.fromNative(native.Database.createEmptyReadOnly());
|
|
4407
|
+
}
|
|
4374
4408
|
throwIfClosed() {
|
|
4375
4409
|
if (this.closed) {
|
|
4376
4410
|
throw new Error("Database is closed");
|
|
@@ -4656,20 +4690,20 @@ function getErrorMessage(error) {
|
|
|
4656
4690
|
return String(error);
|
|
4657
4691
|
}
|
|
4658
4692
|
async function getChangedFiles(opts) {
|
|
4659
|
-
const { pr, branch, projectRoot:
|
|
4693
|
+
const { pr, branch, projectRoot: projectRoot3, baseBranch = "main" } = opts;
|
|
4660
4694
|
if (pr !== void 0) {
|
|
4661
|
-
return getChangedFilesForPr(pr,
|
|
4695
|
+
return getChangedFilesForPr(pr, projectRoot3, baseBranch);
|
|
4662
4696
|
}
|
|
4663
|
-
return getChangedFilesForBranch(branch,
|
|
4697
|
+
return getChangedFilesForBranch(branch, projectRoot3, baseBranch);
|
|
4664
4698
|
}
|
|
4665
|
-
async function getChangedFilesForPr(pr,
|
|
4699
|
+
async function getChangedFilesForPr(pr, projectRoot3, baseBranch) {
|
|
4666
4700
|
let headRefName;
|
|
4667
4701
|
let actualBaseBranch = baseBranch;
|
|
4668
4702
|
try {
|
|
4669
4703
|
const { stdout } = await execFileAsync(
|
|
4670
4704
|
"gh",
|
|
4671
4705
|
["pr", "view", String(pr), "--json", "headRefName,baseRefName,files"],
|
|
4672
|
-
{ cwd:
|
|
4706
|
+
{ cwd: projectRoot3, timeout: 3e4 }
|
|
4673
4707
|
);
|
|
4674
4708
|
const data = JSON.parse(stdout);
|
|
4675
4709
|
headRefName = data.headRefName;
|
|
@@ -4678,7 +4712,7 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
|
|
|
4678
4712
|
return {
|
|
4679
4713
|
files: normalizeFiles(
|
|
4680
4714
|
data.files.map((f) => f.path),
|
|
4681
|
-
|
|
4715
|
+
projectRoot3
|
|
4682
4716
|
),
|
|
4683
4717
|
baseBranch: actualBaseBranch,
|
|
4684
4718
|
source: "gh",
|
|
@@ -4695,49 +4729,49 @@ async function getChangedFilesForPr(pr, projectRoot2, baseBranch) {
|
|
|
4695
4729
|
`PR #${pr} returned no usable branch or file information.`
|
|
4696
4730
|
);
|
|
4697
4731
|
}
|
|
4698
|
-
const result = await getChangedFilesForBranch(headRefName,
|
|
4732
|
+
const result = await getChangedFilesForBranch(headRefName, projectRoot3, actualBaseBranch);
|
|
4699
4733
|
return { ...result, headRefName };
|
|
4700
4734
|
}
|
|
4701
|
-
async function getChangedFilesForBranch(branch,
|
|
4702
|
-
const targetBranch = branch || await getCurrentBranch2(
|
|
4703
|
-
const mergeBase = await getMergeBase(
|
|
4735
|
+
async function getChangedFilesForBranch(branch, projectRoot3, baseBranch) {
|
|
4736
|
+
const targetBranch = branch || await getCurrentBranch2(projectRoot3);
|
|
4737
|
+
const mergeBase = await getMergeBase(projectRoot3, baseBranch, targetBranch);
|
|
4704
4738
|
const { stdout } = await execFileAsync(
|
|
4705
4739
|
"git",
|
|
4706
4740
|
["diff", "--name-only", `${mergeBase}...${targetBranch}`],
|
|
4707
|
-
{ cwd:
|
|
4741
|
+
{ cwd: projectRoot3, timeout: 3e4 }
|
|
4708
4742
|
);
|
|
4709
4743
|
return {
|
|
4710
|
-
files: normalizeFiles(stdout.split("\n"),
|
|
4744
|
+
files: normalizeFiles(stdout.split("\n"), projectRoot3),
|
|
4711
4745
|
baseBranch,
|
|
4712
4746
|
source: "git",
|
|
4713
4747
|
headRefName: targetBranch
|
|
4714
4748
|
};
|
|
4715
4749
|
}
|
|
4716
|
-
async function getCurrentBranch2(
|
|
4750
|
+
async function getCurrentBranch2(projectRoot3) {
|
|
4717
4751
|
const { stdout } = await execFileAsync(
|
|
4718
4752
|
"git",
|
|
4719
4753
|
["branch", "--show-current"],
|
|
4720
|
-
{ cwd:
|
|
4754
|
+
{ cwd: projectRoot3, timeout: 3e4 }
|
|
4721
4755
|
);
|
|
4722
4756
|
return stdout.trim() || "HEAD";
|
|
4723
4757
|
}
|
|
4724
|
-
async function getMergeBase(
|
|
4758
|
+
async function getMergeBase(projectRoot3, baseBranch, branch) {
|
|
4725
4759
|
const { stdout } = await execFileAsync(
|
|
4726
4760
|
"git",
|
|
4727
4761
|
["merge-base", baseBranch, branch],
|
|
4728
|
-
{ cwd:
|
|
4762
|
+
{ cwd: projectRoot3, timeout: 3e4 }
|
|
4729
4763
|
);
|
|
4730
4764
|
return stdout.trim();
|
|
4731
4765
|
}
|
|
4732
|
-
function normalizeFiles(rawFiles,
|
|
4766
|
+
function normalizeFiles(rawFiles, projectRoot3) {
|
|
4733
4767
|
const seen = /* @__PURE__ */ new Set();
|
|
4734
4768
|
const result = [];
|
|
4735
4769
|
for (const raw of rawFiles) {
|
|
4736
4770
|
const trimmed = raw.trim();
|
|
4737
4771
|
if (!trimmed) continue;
|
|
4738
|
-
const absolute = path10.resolve(
|
|
4739
|
-
const
|
|
4740
|
-
const cleaned =
|
|
4772
|
+
const absolute = path10.resolve(projectRoot3, trimmed);
|
|
4773
|
+
const relative7 = path10.relative(projectRoot3, absolute);
|
|
4774
|
+
const cleaned = relative7.startsWith("./") ? relative7.slice(2) : relative7;
|
|
4741
4775
|
if (!seen.has(cleaned)) {
|
|
4742
4776
|
seen.add(cleaned);
|
|
4743
4777
|
result.push(cleaned);
|
|
@@ -4746,8 +4780,483 @@ function normalizeFiles(rawFiles, projectRoot2) {
|
|
|
4746
4780
|
return result;
|
|
4747
4781
|
}
|
|
4748
4782
|
|
|
4783
|
+
// src/indexer/git-blame.ts
|
|
4784
|
+
import { execFile as execFile2 } from "child_process";
|
|
4785
|
+
import * as path11 from "path";
|
|
4786
|
+
import { promisify as promisify2 } from "util";
|
|
4787
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
4788
|
+
function parseGitBlamePorcelain(output) {
|
|
4789
|
+
const commits = /* @__PURE__ */ new Map();
|
|
4790
|
+
let current;
|
|
4791
|
+
for (const line of output.split("\n")) {
|
|
4792
|
+
if (/^[0-9a-f]{40} /.test(line)) {
|
|
4793
|
+
const sha = line.slice(0, 40);
|
|
4794
|
+
current = commits.get(sha) ?? {
|
|
4795
|
+
sha,
|
|
4796
|
+
author: "",
|
|
4797
|
+
authorEmail: "",
|
|
4798
|
+
committedAt: 0,
|
|
4799
|
+
summary: "",
|
|
4800
|
+
lines: 0
|
|
4801
|
+
};
|
|
4802
|
+
commits.set(sha, current);
|
|
4803
|
+
continue;
|
|
4804
|
+
}
|
|
4805
|
+
if (!current) {
|
|
4806
|
+
continue;
|
|
4807
|
+
}
|
|
4808
|
+
if (line.startsWith("author ")) {
|
|
4809
|
+
current.author = line.slice("author ".length);
|
|
4810
|
+
} else if (line.startsWith("author-mail ")) {
|
|
4811
|
+
current.authorEmail = line.slice("author-mail ".length).replace(/^<|>$/g, "");
|
|
4812
|
+
} else if (line.startsWith("author-time ")) {
|
|
4813
|
+
current.committedAt = Number.parseInt(line.slice("author-time ".length), 10);
|
|
4814
|
+
} else if (line.startsWith("summary ")) {
|
|
4815
|
+
current.summary = line.slice("summary ".length);
|
|
4816
|
+
} else if (line.startsWith(" ")) {
|
|
4817
|
+
current.lines += 1;
|
|
4818
|
+
}
|
|
4819
|
+
}
|
|
4820
|
+
return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
|
|
4821
|
+
}
|
|
4822
|
+
async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
|
|
4823
|
+
const relativePath = path11.relative(projectRoot3, filePath);
|
|
4824
|
+
try {
|
|
4825
|
+
const { stdout } = await execFileAsync2(
|
|
4826
|
+
"git",
|
|
4827
|
+
["blame", "--line-porcelain", "-L", `${startLine},${endLine}`, "--", relativePath],
|
|
4828
|
+
{ cwd: projectRoot3, timeout: 3e4 }
|
|
4829
|
+
);
|
|
4830
|
+
return parseGitBlamePorcelain(stdout);
|
|
4831
|
+
} catch {
|
|
4832
|
+
return void 0;
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4836
|
+
// src/indexer/index-lock.ts
|
|
4837
|
+
import { randomUUID } from "crypto";
|
|
4838
|
+
import {
|
|
4839
|
+
existsSync as existsSync7,
|
|
4840
|
+
lstatSync,
|
|
4841
|
+
mkdirSync as mkdirSync2,
|
|
4842
|
+
readFileSync as readFileSync6,
|
|
4843
|
+
readdirSync as readdirSync2,
|
|
4844
|
+
realpathSync,
|
|
4845
|
+
renameSync,
|
|
4846
|
+
rmSync,
|
|
4847
|
+
writeFileSync as writeFileSync2
|
|
4848
|
+
} from "fs";
|
|
4849
|
+
import * as os4 from "os";
|
|
4850
|
+
import * as path12 from "path";
|
|
4851
|
+
var OWNER_FILE_NAME = "owner.json";
|
|
4852
|
+
var RECLAIM_DIRECTORY_NAME = "reclaiming";
|
|
4853
|
+
var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
|
|
4854
|
+
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;
|
|
4855
|
+
var VALID_OPERATIONS = /* @__PURE__ */ new Set([
|
|
4856
|
+
"initialize",
|
|
4857
|
+
"index",
|
|
4858
|
+
"force-index",
|
|
4859
|
+
"clear",
|
|
4860
|
+
"health-check",
|
|
4861
|
+
"retry-failed-batches",
|
|
4862
|
+
"recovery"
|
|
4863
|
+
]);
|
|
4864
|
+
var temporaryCounter = 0;
|
|
4865
|
+
function getErrorCode(error) {
|
|
4866
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
4867
|
+
}
|
|
4868
|
+
function retryTransientFilesystemOperation(operation) {
|
|
4869
|
+
let lastError;
|
|
4870
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4871
|
+
try {
|
|
4872
|
+
operation();
|
|
4873
|
+
return;
|
|
4874
|
+
} catch (error) {
|
|
4875
|
+
lastError = error;
|
|
4876
|
+
const code = getErrorCode(error);
|
|
4877
|
+
if (code !== "EBUSY" && code !== "EPERM") throw error;
|
|
4878
|
+
if (attempt < 2) {
|
|
4879
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
throw lastError;
|
|
4884
|
+
}
|
|
4885
|
+
function parseOwner(value) {
|
|
4886
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4887
|
+
const candidate = value;
|
|
4888
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4889
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4890
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4891
|
+
if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
|
|
4892
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4893
|
+
return candidate;
|
|
4894
|
+
}
|
|
4895
|
+
function parseReclaimOwner(value) {
|
|
4896
|
+
if (typeof value !== "object" || value === null) return null;
|
|
4897
|
+
const candidate = value;
|
|
4898
|
+
if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
|
|
4899
|
+
if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
|
|
4900
|
+
if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
|
|
4901
|
+
if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
|
|
4902
|
+
if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
|
|
4903
|
+
return candidate;
|
|
4904
|
+
}
|
|
4905
|
+
function readJsonDirectory(directoryPath, parser) {
|
|
4906
|
+
try {
|
|
4907
|
+
return parser(JSON.parse(readFileSync6(path12.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
|
|
4908
|
+
} catch {
|
|
4909
|
+
return null;
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
function readDirectoryOwner(lockPath) {
|
|
4913
|
+
return readJsonDirectory(lockPath, parseOwner);
|
|
4914
|
+
}
|
|
4915
|
+
function readReclaimOwner(markerPath) {
|
|
4916
|
+
return readJsonDirectory(markerPath, parseReclaimOwner);
|
|
4917
|
+
}
|
|
4918
|
+
function readRecoveryOwner(markerPath) {
|
|
4919
|
+
return readDirectoryOwner(markerPath);
|
|
4920
|
+
}
|
|
4921
|
+
function readLegacyOwner(lockPath) {
|
|
4922
|
+
try {
|
|
4923
|
+
const parsed = JSON.parse(readFileSync6(lockPath, "utf-8"));
|
|
4924
|
+
if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
|
|
4925
|
+
if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
|
|
4926
|
+
return {
|
|
4927
|
+
pid: Number(parsed.pid),
|
|
4928
|
+
hostname: typeof parsed.hostname === "string" ? parsed.hostname : os4.hostname(),
|
|
4929
|
+
startedAt: parsed.startedAt,
|
|
4930
|
+
operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
|
|
4931
|
+
token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
|
|
4932
|
+
};
|
|
4933
|
+
} catch {
|
|
4934
|
+
return null;
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
function getOwnerLiveness(owner) {
|
|
4938
|
+
if (owner.hostname !== os4.hostname()) return "unknown";
|
|
4939
|
+
try {
|
|
4940
|
+
process.kill(owner.pid, 0);
|
|
4941
|
+
return "alive";
|
|
4942
|
+
} catch (error) {
|
|
4943
|
+
const code = getErrorCode(error);
|
|
4944
|
+
if (code === "ESRCH") return "dead";
|
|
4945
|
+
if (code === "EPERM") return "alive";
|
|
4946
|
+
return "unknown";
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
function sameOwner(left, right) {
|
|
4950
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
|
|
4951
|
+
}
|
|
4952
|
+
function sameReclaimOwner(left, right) {
|
|
4953
|
+
return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
|
|
4954
|
+
}
|
|
4955
|
+
function publishJsonDirectory(finalPath, value) {
|
|
4956
|
+
const candidatePath = `${finalPath}.candidate.${process.pid}.${randomUUID()}`;
|
|
4957
|
+
mkdirSync2(candidatePath, { mode: 448 });
|
|
4958
|
+
try {
|
|
4959
|
+
writeFileSync2(path12.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
|
|
4960
|
+
encoding: "utf-8",
|
|
4961
|
+
flag: "wx",
|
|
4962
|
+
mode: 384
|
|
4963
|
+
});
|
|
4964
|
+
if (existsSync7(finalPath)) return false;
|
|
4965
|
+
try {
|
|
4966
|
+
renameSync(candidatePath, finalPath);
|
|
4967
|
+
return true;
|
|
4968
|
+
} catch (error) {
|
|
4969
|
+
if (existsSync7(finalPath)) return false;
|
|
4970
|
+
throw error;
|
|
4971
|
+
}
|
|
4972
|
+
} finally {
|
|
4973
|
+
if (existsSync7(candidatePath)) rmSync(candidatePath, { recursive: true, force: true });
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
function createOwner(operation) {
|
|
4977
|
+
return {
|
|
4978
|
+
pid: process.pid,
|
|
4979
|
+
hostname: os4.hostname(),
|
|
4980
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4981
|
+
operation,
|
|
4982
|
+
token: randomUUID()
|
|
4983
|
+
};
|
|
4984
|
+
}
|
|
4985
|
+
function recoveryMarkerPath(indexPath, owner) {
|
|
4986
|
+
return path12.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
|
|
4987
|
+
}
|
|
4988
|
+
function publishRecoveryMarker(indexPath, owner) {
|
|
4989
|
+
const markerPath = recoveryMarkerPath(indexPath, owner);
|
|
4990
|
+
if (!publishJsonDirectory(markerPath, owner)) {
|
|
4991
|
+
const markerOwner = readRecoveryOwner(markerPath);
|
|
4992
|
+
if (!markerOwner || !sameOwner(markerOwner, owner)) {
|
|
4993
|
+
throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
return markerPath;
|
|
4997
|
+
}
|
|
4998
|
+
function getPendingRecoveries(indexPath) {
|
|
4999
|
+
const recoveries = [];
|
|
5000
|
+
const markerNames = readdirSync2(indexPath).filter((name) => {
|
|
5001
|
+
if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
|
|
5002
|
+
return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
|
|
5003
|
+
}).sort();
|
|
5004
|
+
for (const markerName of markerNames) {
|
|
5005
|
+
const markerPath = path12.join(indexPath, markerName);
|
|
5006
|
+
const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
|
|
5007
|
+
let markerStats;
|
|
5008
|
+
try {
|
|
5009
|
+
markerStats = lstatSync(markerPath);
|
|
5010
|
+
} catch (error) {
|
|
5011
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5012
|
+
throw error;
|
|
5013
|
+
}
|
|
5014
|
+
if (!markerStats.isDirectory()) {
|
|
5015
|
+
throw new IndexLockContentionError(markerPath, null, "unknown-owner");
|
|
5016
|
+
}
|
|
5017
|
+
const owner = readRecoveryOwner(markerPath);
|
|
5018
|
+
if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
|
|
5019
|
+
throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
|
|
5020
|
+
}
|
|
5021
|
+
recoveries.push({ owner, markerPath });
|
|
5022
|
+
}
|
|
5023
|
+
recoveries.sort((left, right) => {
|
|
5024
|
+
const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
|
|
5025
|
+
return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
|
|
5026
|
+
});
|
|
5027
|
+
return recoveries;
|
|
5028
|
+
}
|
|
5029
|
+
function cleanupDeadPublicationCandidates(indexPath) {
|
|
5030
|
+
const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
|
|
5031
|
+
for (const entry of readdirSync2(indexPath)) {
|
|
5032
|
+
const match = candidatePattern.exec(entry);
|
|
5033
|
+
if (!match) continue;
|
|
5034
|
+
const pid = Number(match[1]);
|
|
5035
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
5036
|
+
if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
|
|
5037
|
+
rmSync(path12.join(indexPath, entry), { recursive: true, force: true });
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
function removeDeadReclaimMarker(lockPath, expectedOwner) {
|
|
5041
|
+
const markerPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5042
|
+
const marker = readReclaimOwner(markerPath);
|
|
5043
|
+
if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
|
|
5044
|
+
return false;
|
|
5045
|
+
}
|
|
5046
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5047
|
+
if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5048
|
+
return false;
|
|
5049
|
+
}
|
|
5050
|
+
const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${randomUUID()}`;
|
|
5051
|
+
try {
|
|
5052
|
+
renameSync(markerPath, claimedMarkerPath);
|
|
5053
|
+
} catch (error) {
|
|
5054
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5055
|
+
throw error;
|
|
5056
|
+
}
|
|
5057
|
+
const claimedMarker = readReclaimOwner(claimedMarkerPath);
|
|
5058
|
+
const ownerAfterClaim = readDirectoryOwner(lockPath);
|
|
5059
|
+
if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
|
|
5060
|
+
if (!existsSync7(markerPath) && existsSync7(claimedMarkerPath)) {
|
|
5061
|
+
renameSync(claimedMarkerPath, markerPath);
|
|
5062
|
+
}
|
|
5063
|
+
return false;
|
|
5064
|
+
}
|
|
5065
|
+
rmSync(claimedMarkerPath, { recursive: true, force: true });
|
|
5066
|
+
return true;
|
|
5067
|
+
}
|
|
5068
|
+
function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
|
|
5069
|
+
const reclaimPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
|
|
5070
|
+
const reclaimOwner = {
|
|
5071
|
+
pid: process.pid,
|
|
5072
|
+
hostname: os4.hostname(),
|
|
5073
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5074
|
+
token: randomUUID(),
|
|
5075
|
+
expectedOwnerToken: expectedOwner.token
|
|
5076
|
+
};
|
|
5077
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
5078
|
+
if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
|
|
5079
|
+
if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
|
|
5080
|
+
return false;
|
|
5081
|
+
}
|
|
5082
|
+
try {
|
|
5083
|
+
const currentReclaimer = readReclaimOwner(reclaimPath);
|
|
5084
|
+
const currentOwner = readDirectoryOwner(lockPath);
|
|
5085
|
+
if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
|
|
5086
|
+
return false;
|
|
5087
|
+
}
|
|
5088
|
+
publishRecoveryMarker(indexPath, expectedOwner);
|
|
5089
|
+
const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
|
|
5090
|
+
const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
|
|
5091
|
+
if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
|
|
5092
|
+
return false;
|
|
5093
|
+
}
|
|
5094
|
+
const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
|
|
5095
|
+
renameSync(lockPath, quarantinePath);
|
|
5096
|
+
rmSync(quarantinePath, { recursive: true, force: true });
|
|
5097
|
+
return true;
|
|
5098
|
+
} catch (error) {
|
|
5099
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5100
|
+
throw error;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
var IndexLockContentionError = class extends Error {
|
|
5104
|
+
constructor(lockPath, owner, reason) {
|
|
5105
|
+
const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
|
|
5106
|
+
super(`Index mutation already in progress: ${ownerDescription}`);
|
|
5107
|
+
this.lockPath = lockPath;
|
|
5108
|
+
this.owner = owner;
|
|
5109
|
+
this.reason = reason;
|
|
5110
|
+
this.name = "IndexLockContentionError";
|
|
5111
|
+
}
|
|
5112
|
+
lockPath;
|
|
5113
|
+
owner;
|
|
5114
|
+
reason;
|
|
5115
|
+
code = "INDEX_BUSY";
|
|
5116
|
+
};
|
|
5117
|
+
function isIndexLockContentionError(error) {
|
|
5118
|
+
return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
|
|
5119
|
+
}
|
|
5120
|
+
function acquireIndexLock(indexPath, operation) {
|
|
5121
|
+
mkdirSync2(indexPath, { recursive: true });
|
|
5122
|
+
const canonicalIndexPath = realpathSync.native(indexPath);
|
|
5123
|
+
const lockPath = path12.join(canonicalIndexPath, "indexing.lock");
|
|
5124
|
+
cleanupDeadPublicationCandidates(canonicalIndexPath);
|
|
5125
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
5126
|
+
const owner = createOwner(operation);
|
|
5127
|
+
if (publishJsonDirectory(lockPath, owner)) {
|
|
5128
|
+
const lease = {
|
|
5129
|
+
canonicalIndexPath,
|
|
5130
|
+
lockPath,
|
|
5131
|
+
owner,
|
|
5132
|
+
recoveries: []
|
|
5133
|
+
};
|
|
5134
|
+
try {
|
|
5135
|
+
lease.recoveries = getPendingRecoveries(canonicalIndexPath);
|
|
5136
|
+
return lease;
|
|
5137
|
+
} catch (error) {
|
|
5138
|
+
releaseIndexLock(lease);
|
|
5139
|
+
throw error;
|
|
5140
|
+
}
|
|
5141
|
+
}
|
|
5142
|
+
let stats;
|
|
5143
|
+
try {
|
|
5144
|
+
stats = lstatSync(lockPath);
|
|
5145
|
+
} catch (error) {
|
|
5146
|
+
if (getErrorCode(error) === "ENOENT") continue;
|
|
5147
|
+
throw error;
|
|
5148
|
+
}
|
|
5149
|
+
if (!stats.isDirectory()) {
|
|
5150
|
+
const legacyOwner = readLegacyOwner(lockPath);
|
|
5151
|
+
throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
|
|
5152
|
+
}
|
|
5153
|
+
const existingOwner = readDirectoryOwner(lockPath);
|
|
5154
|
+
if (!existingOwner) {
|
|
5155
|
+
throw new IndexLockContentionError(lockPath, null, "unknown-owner");
|
|
5156
|
+
}
|
|
5157
|
+
const liveness = getOwnerLiveness(existingOwner);
|
|
5158
|
+
if (liveness !== "dead") {
|
|
5159
|
+
throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
|
|
5160
|
+
}
|
|
5161
|
+
if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
|
|
5162
|
+
throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
throw new IndexLockContentionError(lockPath, null, "reclaiming");
|
|
5166
|
+
}
|
|
5167
|
+
function releaseIndexLock(lease) {
|
|
5168
|
+
const currentOwner = readDirectoryOwner(lease.lockPath);
|
|
5169
|
+
if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
|
|
5170
|
+
const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
|
|
5171
|
+
try {
|
|
5172
|
+
retryTransientFilesystemOperation(() => renameSync(lease.lockPath, releasePath));
|
|
5173
|
+
} catch (error) {
|
|
5174
|
+
if (getErrorCode(error) === "ENOENT") return false;
|
|
5175
|
+
throw error;
|
|
5176
|
+
}
|
|
5177
|
+
const claimedOwner = readDirectoryOwner(releasePath);
|
|
5178
|
+
if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
|
|
5179
|
+
if (!existsSync7(lease.lockPath) && existsSync7(releasePath)) {
|
|
5180
|
+
renameSync(releasePath, lease.lockPath);
|
|
5181
|
+
}
|
|
5182
|
+
return false;
|
|
5183
|
+
}
|
|
5184
|
+
try {
|
|
5185
|
+
retryTransientFilesystemOperation(() => rmSync(releasePath, { recursive: true, force: true }));
|
|
5186
|
+
} catch (error) {
|
|
5187
|
+
console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
|
|
5188
|
+
}
|
|
5189
|
+
return true;
|
|
5190
|
+
}
|
|
5191
|
+
async function withIndexLock(indexPath, operation, callback, options = {}) {
|
|
5192
|
+
const lease = acquireIndexLock(indexPath, operation);
|
|
5193
|
+
let result;
|
|
5194
|
+
let callbackError;
|
|
5195
|
+
let callbackFailed = false;
|
|
5196
|
+
try {
|
|
5197
|
+
result = await callback(lease);
|
|
5198
|
+
} catch (error) {
|
|
5199
|
+
callbackFailed = true;
|
|
5200
|
+
callbackError = error;
|
|
5201
|
+
}
|
|
5202
|
+
if (!callbackFailed && options.completeRecoveries !== false) {
|
|
5203
|
+
try {
|
|
5204
|
+
completeLeaseRecovery(lease);
|
|
5205
|
+
} catch (error) {
|
|
5206
|
+
callbackFailed = true;
|
|
5207
|
+
callbackError = error;
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
let releaseError;
|
|
5211
|
+
try {
|
|
5212
|
+
if (!releaseIndexLock(lease)) {
|
|
5213
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
5214
|
+
}
|
|
5215
|
+
} catch (error) {
|
|
5216
|
+
releaseError = error;
|
|
5217
|
+
}
|
|
5218
|
+
if (releaseError !== void 0) {
|
|
5219
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
5220
|
+
throw releaseError;
|
|
5221
|
+
}
|
|
5222
|
+
if (callbackFailed) throw callbackError;
|
|
5223
|
+
return result;
|
|
5224
|
+
}
|
|
5225
|
+
function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
|
|
5226
|
+
if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
|
|
5227
|
+
temporaryCounter += 1;
|
|
5228
|
+
return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
|
|
5229
|
+
}
|
|
5230
|
+
function removeLeaseTemporaryPath(temporaryPath) {
|
|
5231
|
+
if (existsSync7(temporaryPath)) rmSync(temporaryPath, { recursive: true, force: true });
|
|
5232
|
+
}
|
|
5233
|
+
function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
|
|
5234
|
+
for (const targetPath of backupTargets) {
|
|
5235
|
+
const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
|
|
5236
|
+
if (!existsSync7(backupPath)) continue;
|
|
5237
|
+
if (existsSync7(targetPath)) rmSync(targetPath, { recursive: true, force: true });
|
|
5238
|
+
renameSync(backupPath, targetPath);
|
|
5239
|
+
}
|
|
5240
|
+
const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
|
|
5241
|
+
for (const entry of readdirSync2(indexPath)) {
|
|
5242
|
+
if (!entry.includes(temporaryOwnerMarker)) continue;
|
|
5243
|
+
rmSync(path12.join(indexPath, entry), { recursive: true, force: true });
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
function completeLeaseRecovery(lease) {
|
|
5247
|
+
for (const recovery of lease.recoveries) {
|
|
5248
|
+
const markerOwner = readRecoveryOwner(recovery.markerPath);
|
|
5249
|
+
if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
|
|
5250
|
+
throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
for (const recovery of lease.recoveries) {
|
|
5254
|
+
rmSync(recovery.markerPath, { recursive: true, force: true });
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
|
|
4749
5258
|
// src/indexer/index.ts
|
|
4750
|
-
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab"]);
|
|
5259
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
|
|
4751
5260
|
var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
|
|
4752
5261
|
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
4753
5262
|
"function_declaration",
|
|
@@ -4827,6 +5336,46 @@ function isSqliteCorruptionError(error) {
|
|
|
4827
5336
|
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");
|
|
4828
5337
|
}
|
|
4829
5338
|
var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
|
|
5339
|
+
var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
|
|
5340
|
+
function metadataFromBlame(blame) {
|
|
5341
|
+
if (!blame) {
|
|
5342
|
+
return {};
|
|
5343
|
+
}
|
|
5344
|
+
return {
|
|
5345
|
+
blameSha: blame.sha,
|
|
5346
|
+
blameAuthor: blame.author,
|
|
5347
|
+
blameAuthorEmail: blame.authorEmail,
|
|
5348
|
+
blameCommittedAt: blame.committedAt,
|
|
5349
|
+
blameSummary: blame.summary
|
|
5350
|
+
};
|
|
5351
|
+
}
|
|
5352
|
+
function blameFromChunkData(chunk) {
|
|
5353
|
+
if (!chunk?.blameSha || !chunk.blameAuthor || !chunk.blameAuthorEmail || chunk.blameCommittedAt === void 0 || !chunk.blameSummary) {
|
|
5354
|
+
return void 0;
|
|
5355
|
+
}
|
|
5356
|
+
return {
|
|
5357
|
+
sha: chunk.blameSha,
|
|
5358
|
+
author: chunk.blameAuthor,
|
|
5359
|
+
authorEmail: chunk.blameAuthorEmail,
|
|
5360
|
+
committedAt: chunk.blameCommittedAt,
|
|
5361
|
+
summary: chunk.blameSummary
|
|
5362
|
+
};
|
|
5363
|
+
}
|
|
5364
|
+
function blameFromMetadata(metadata) {
|
|
5365
|
+
if (!metadata.blameSha || !metadata.blameAuthor || !metadata.blameAuthorEmail || metadata.blameCommittedAt === void 0 || !metadata.blameSummary) {
|
|
5366
|
+
return void 0;
|
|
5367
|
+
}
|
|
5368
|
+
return {
|
|
5369
|
+
sha: metadata.blameSha,
|
|
5370
|
+
author: metadata.blameAuthor,
|
|
5371
|
+
authorEmail: metadata.blameAuthorEmail,
|
|
5372
|
+
committedAt: metadata.blameCommittedAt,
|
|
5373
|
+
summary: metadata.blameSummary
|
|
5374
|
+
};
|
|
5375
|
+
}
|
|
5376
|
+
function hasBlameMetadata(metadata) {
|
|
5377
|
+
return blameFromMetadata(metadata) !== void 0;
|
|
5378
|
+
}
|
|
4830
5379
|
var INDEX_METADATA_VERSION = "1";
|
|
4831
5380
|
var EMBEDDING_STRATEGY_VERSION = "2";
|
|
4832
5381
|
var RANKING_TOKEN_CACHE_LIMIT = 4096;
|
|
@@ -4870,9 +5419,9 @@ function normalizePendingChunk(rawChunk, maxChunkTokens) {
|
|
|
4870
5419
|
};
|
|
4871
5420
|
const filePath = typeof metadata.filePath === "string" ? metadata.filePath : "unknown";
|
|
4872
5421
|
texts.push(
|
|
4873
|
-
...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((
|
|
4874
|
-
text:
|
|
4875
|
-
tokenCount: estimateTokens(
|
|
5422
|
+
...createEmbeddingTexts(rebuiltChunk, filePath, maxChunkTokens).map((text3) => ({
|
|
5423
|
+
text: text3,
|
|
5424
|
+
tokenCount: estimateTokens(text3)
|
|
4876
5425
|
}))
|
|
4877
5426
|
);
|
|
4878
5427
|
} else {
|
|
@@ -4985,9 +5534,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
|
|
|
4985
5534
|
return true;
|
|
4986
5535
|
}
|
|
4987
5536
|
function isPathWithinRoot(filePath, rootPath) {
|
|
4988
|
-
const normalizedFilePath =
|
|
4989
|
-
const normalizedRoot =
|
|
4990
|
-
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${
|
|
5537
|
+
const normalizedFilePath = path13.resolve(filePath);
|
|
5538
|
+
const normalizedRoot = path13.resolve(rootPath);
|
|
5539
|
+
return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
|
|
4991
5540
|
}
|
|
4992
5541
|
var rankingQueryTokenCache = /* @__PURE__ */ new Map();
|
|
4993
5542
|
var rankingNameTokenCache = /* @__PURE__ */ new Map();
|
|
@@ -5103,11 +5652,11 @@ function setBoundedCache(cache, key, value) {
|
|
|
5103
5652
|
}
|
|
5104
5653
|
cache.set(key, value);
|
|
5105
5654
|
}
|
|
5106
|
-
function tokenizeTextForRanking(
|
|
5107
|
-
if (!
|
|
5655
|
+
function tokenizeTextForRanking(text3) {
|
|
5656
|
+
if (!text3) {
|
|
5108
5657
|
return /* @__PURE__ */ new Set();
|
|
5109
5658
|
}
|
|
5110
|
-
const lowered =
|
|
5659
|
+
const lowered = text3.toLowerCase();
|
|
5111
5660
|
const cache = rankingQueryTokenCache.get(lowered) ?? rankingTextTokenCache.get(lowered);
|
|
5112
5661
|
if (cache) {
|
|
5113
5662
|
return cache;
|
|
@@ -5746,7 +6295,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
|
|
|
5746
6295
|
chunkType,
|
|
5747
6296
|
name: chunk.name ?? void 0,
|
|
5748
6297
|
language: chunk.language,
|
|
5749
|
-
hash: chunk.contentHash
|
|
6298
|
+
hash: chunk.contentHash,
|
|
6299
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5750
6300
|
};
|
|
5751
6301
|
const baselineScore = existing?.score ?? 0.5;
|
|
5752
6302
|
candidateUnion.set(chunk.chunkId, {
|
|
@@ -5830,7 +6380,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
|
|
|
5830
6380
|
chunkType,
|
|
5831
6381
|
name: chunk.name ?? void 0,
|
|
5832
6382
|
language: chunk.language,
|
|
5833
|
-
hash: chunk.contentHash
|
|
6383
|
+
hash: chunk.contentHash,
|
|
6384
|
+
...metadataFromBlame(blameFromChunkData(chunk))
|
|
5834
6385
|
}
|
|
5835
6386
|
});
|
|
5836
6387
|
}
|
|
@@ -5999,6 +6550,21 @@ function matchesSearchFilters(candidate, options, minScore) {
|
|
|
5999
6550
|
if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
|
|
6000
6551
|
return false;
|
|
6001
6552
|
}
|
|
6553
|
+
if (options?.blameAuthor) {
|
|
6554
|
+
const author = options.blameAuthor.toLowerCase();
|
|
6555
|
+
const candidateAuthor = candidate.metadata.blameAuthor?.toLowerCase();
|
|
6556
|
+
const candidateEmail = candidate.metadata.blameAuthorEmail?.toLowerCase();
|
|
6557
|
+
if (candidateAuthor !== author && candidateEmail !== author) return false;
|
|
6558
|
+
}
|
|
6559
|
+
if (options?.blameSha && !candidate.metadata.blameSha?.toLowerCase().startsWith(options.blameSha.toLowerCase())) {
|
|
6560
|
+
return false;
|
|
6561
|
+
}
|
|
6562
|
+
if (options?.blameSince) {
|
|
6563
|
+
const sinceMs = Date.parse(options.blameSince);
|
|
6564
|
+
if (Number.isNaN(sinceMs)) return false;
|
|
6565
|
+
const committedAt = candidate.metadata.blameCommittedAt;
|
|
6566
|
+
if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
|
|
6567
|
+
}
|
|
6002
6568
|
return true;
|
|
6003
6569
|
}
|
|
6004
6570
|
function unionCandidates(semanticCandidates, keywordCandidates) {
|
|
@@ -6036,26 +6602,133 @@ var Indexer = class {
|
|
|
6036
6602
|
queryCacheTtlMs = 5 * 60 * 1e3;
|
|
6037
6603
|
querySimilarityThreshold = 0.85;
|
|
6038
6604
|
indexCompatibility = null;
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6605
|
+
activeIndexLease = null;
|
|
6606
|
+
initializationPromise = null;
|
|
6607
|
+
initializationMode = "none";
|
|
6608
|
+
readIssues = [];
|
|
6609
|
+
retiredDatabases = [];
|
|
6610
|
+
readerArtifactFingerprint = null;
|
|
6611
|
+
writerArtifactFingerprint = null;
|
|
6612
|
+
readerArtifactRetryAfter = /* @__PURE__ */ new Map();
|
|
6613
|
+
constructor(projectRoot3, config, host = "opencode") {
|
|
6614
|
+
this.projectRoot = projectRoot3;
|
|
6042
6615
|
this.config = config;
|
|
6043
6616
|
this.host = host;
|
|
6044
6617
|
this.indexPath = this.getIndexPath();
|
|
6045
|
-
this.fileHashCachePath =
|
|
6046
|
-
this.failedBatchesPath =
|
|
6047
|
-
this.indexingLockPath = path11.join(this.indexPath, "indexing.lock");
|
|
6618
|
+
this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
|
|
6619
|
+
this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
|
|
6048
6620
|
this.logger = initializeLogger(config.debug);
|
|
6049
6621
|
}
|
|
6050
6622
|
getIndexPath() {
|
|
6051
6623
|
return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
|
|
6052
6624
|
}
|
|
6625
|
+
isLocalProjectIndexPath() {
|
|
6626
|
+
const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
|
|
6627
|
+
if (this.host !== "opencode") {
|
|
6628
|
+
localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
6629
|
+
}
|
|
6630
|
+
return localProjectIndexPaths.some((localPath) => {
|
|
6631
|
+
if (!existsSync8(localPath) || !existsSync8(this.indexPath)) {
|
|
6632
|
+
return path13.resolve(this.indexPath) === path13.resolve(localPath);
|
|
6633
|
+
}
|
|
6634
|
+
const indexStats = statSync3(this.indexPath);
|
|
6635
|
+
const localStats = statSync3(localPath);
|
|
6636
|
+
return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
|
|
6637
|
+
});
|
|
6638
|
+
}
|
|
6639
|
+
resetLoadedIndexState(retireDatabase = false) {
|
|
6640
|
+
if (this.database) {
|
|
6641
|
+
if (retireDatabase) {
|
|
6642
|
+
this.retiredDatabases.push(this.database);
|
|
6643
|
+
} else {
|
|
6644
|
+
this.database.close();
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
this.store = null;
|
|
6648
|
+
this.invertedIndex = null;
|
|
6649
|
+
this.database = null;
|
|
6650
|
+
this.provider = null;
|
|
6651
|
+
this.configuredProviderInfo = null;
|
|
6652
|
+
this.reranker = null;
|
|
6653
|
+
this.indexCompatibility = null;
|
|
6654
|
+
this.initializationMode = "none";
|
|
6655
|
+
this.readIssues = [];
|
|
6656
|
+
this.readerArtifactFingerprint = null;
|
|
6657
|
+
this.writerArtifactFingerprint = null;
|
|
6658
|
+
this.readerArtifactRetryAfter.clear();
|
|
6659
|
+
this.fileHashCache.clear();
|
|
6660
|
+
}
|
|
6661
|
+
refreshLoadedIndexState() {
|
|
6662
|
+
if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
|
|
6663
|
+
this.store.load();
|
|
6664
|
+
this.invertedIndex.load();
|
|
6665
|
+
this.fileHashCache.clear();
|
|
6666
|
+
this.loadFileHashCache();
|
|
6667
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
6668
|
+
this.readIssues = [];
|
|
6669
|
+
this.readerArtifactRetryAfter.clear();
|
|
6670
|
+
}
|
|
6671
|
+
async withIndexMutationLease(operation, callback) {
|
|
6672
|
+
const lease = acquireIndexLock(this.indexPath, operation);
|
|
6673
|
+
this.indexPath = lease.canonicalIndexPath;
|
|
6674
|
+
this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
|
|
6675
|
+
this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
|
|
6676
|
+
this.activeIndexLease = lease;
|
|
6677
|
+
let result;
|
|
6678
|
+
let callbackError;
|
|
6679
|
+
let callbackFailed = false;
|
|
6680
|
+
try {
|
|
6681
|
+
result = await callback(lease.recoveries.map(({ owner }) => owner));
|
|
6682
|
+
} catch (error) {
|
|
6683
|
+
callbackFailed = true;
|
|
6684
|
+
callbackError = error;
|
|
6685
|
+
}
|
|
6686
|
+
if (!callbackFailed) {
|
|
6687
|
+
try {
|
|
6688
|
+
completeLeaseRecovery(lease);
|
|
6689
|
+
this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
|
|
6690
|
+
} catch (error) {
|
|
6691
|
+
callbackFailed = true;
|
|
6692
|
+
callbackError = error;
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
let releaseError;
|
|
6696
|
+
try {
|
|
6697
|
+
if (!releaseIndexLock(lease)) {
|
|
6698
|
+
releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
|
|
6699
|
+
this.writerArtifactFingerprint = null;
|
|
6700
|
+
if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6701
|
+
this.activeIndexLease = null;
|
|
6702
|
+
}
|
|
6703
|
+
} else if (this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6704
|
+
this.activeIndexLease = null;
|
|
6705
|
+
}
|
|
6706
|
+
} catch (error) {
|
|
6707
|
+
releaseError = error;
|
|
6708
|
+
this.writerArtifactFingerprint = null;
|
|
6709
|
+
if (!existsSync8(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
|
|
6710
|
+
this.activeIndexLease = null;
|
|
6711
|
+
}
|
|
6712
|
+
}
|
|
6713
|
+
if (releaseError !== void 0) {
|
|
6714
|
+
if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
|
|
6715
|
+
throw releaseError;
|
|
6716
|
+
}
|
|
6717
|
+
if (callbackFailed) throw callbackError;
|
|
6718
|
+
return result;
|
|
6719
|
+
}
|
|
6720
|
+
requireActiveLease() {
|
|
6721
|
+
if (!this.activeIndexLease) {
|
|
6722
|
+
throw new Error("Index mutation attempted without an active interprocess lease");
|
|
6723
|
+
}
|
|
6724
|
+
return this.activeIndexLease;
|
|
6725
|
+
}
|
|
6053
6726
|
loadFileHashCache() {
|
|
6054
|
-
if (!
|
|
6727
|
+
if (!existsSync8(this.fileHashCachePath)) {
|
|
6055
6728
|
return;
|
|
6056
6729
|
}
|
|
6057
6730
|
try {
|
|
6058
|
-
const data =
|
|
6731
|
+
const data = readFileSync7(this.fileHashCachePath, "utf-8");
|
|
6059
6732
|
const parsed = JSON.parse(data);
|
|
6060
6733
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
6061
6734
|
} catch (error) {
|
|
@@ -6075,15 +6748,26 @@ var Indexer = class {
|
|
|
6075
6748
|
this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
|
|
6076
6749
|
}
|
|
6077
6750
|
atomicWriteSync(targetPath, data) {
|
|
6078
|
-
const
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6751
|
+
const lease = this.requireActiveLease();
|
|
6752
|
+
const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
|
|
6753
|
+
mkdirSync3(path13.dirname(targetPath), { recursive: true });
|
|
6754
|
+
try {
|
|
6755
|
+
writeFileSync3(tempPath, data);
|
|
6756
|
+
renameSync2(tempPath, targetPath);
|
|
6757
|
+
} finally {
|
|
6758
|
+
removeLeaseTemporaryPath(tempPath);
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
saveInvertedIndex(invertedIndex) {
|
|
6762
|
+
this.atomicWriteSync(
|
|
6763
|
+
path13.join(this.indexPath, "inverted-index.json"),
|
|
6764
|
+
invertedIndex.serialize()
|
|
6765
|
+
);
|
|
6082
6766
|
}
|
|
6083
6767
|
getScopedRoots() {
|
|
6084
|
-
const roots = /* @__PURE__ */ new Set([
|
|
6768
|
+
const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
|
|
6085
6769
|
for (const kbRoot of this.config.knowledgeBases) {
|
|
6086
|
-
roots.add(
|
|
6770
|
+
roots.add(path13.resolve(this.projectRoot, kbRoot));
|
|
6087
6771
|
}
|
|
6088
6772
|
return Array.from(roots);
|
|
6089
6773
|
}
|
|
@@ -6092,29 +6776,29 @@ var Indexer = class {
|
|
|
6092
6776
|
if (this.config.scope !== "global") {
|
|
6093
6777
|
return branchName;
|
|
6094
6778
|
}
|
|
6095
|
-
const projectHash = hashContent(
|
|
6779
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6096
6780
|
return `${projectHash}:${branchName}`;
|
|
6097
6781
|
}
|
|
6098
6782
|
getBranchCatalogKeyFor(branchName) {
|
|
6099
6783
|
if (this.config.scope !== "global") {
|
|
6100
6784
|
return branchName;
|
|
6101
6785
|
}
|
|
6102
|
-
const projectHash = hashContent(
|
|
6786
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6103
6787
|
return `${projectHash}:${branchName}`;
|
|
6104
6788
|
}
|
|
6105
6789
|
getLegacyBranchCatalogKey() {
|
|
6106
6790
|
return this.currentBranch || "default";
|
|
6107
6791
|
}
|
|
6108
6792
|
getLegacyMigrationMetadataKey() {
|
|
6109
|
-
const projectHash = hashContent(
|
|
6793
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6110
6794
|
return `index.globalBranchMigration.${projectHash}`;
|
|
6111
6795
|
}
|
|
6112
6796
|
getProjectEmbeddingStrategyMetadataKey() {
|
|
6113
|
-
const projectHash = hashContent(
|
|
6797
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6114
6798
|
return `index.embeddingStrategyVersion.${projectHash}`;
|
|
6115
6799
|
}
|
|
6116
6800
|
getProjectForceReembedMetadataKey() {
|
|
6117
|
-
const projectHash = hashContent(
|
|
6801
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6118
6802
|
return `index.forceReembed.${projectHash}`;
|
|
6119
6803
|
}
|
|
6120
6804
|
hasProjectForceReembedPending() {
|
|
@@ -6208,7 +6892,7 @@ var Indexer = class {
|
|
|
6208
6892
|
if (!this.database) {
|
|
6209
6893
|
return { chunkIds, symbolIds };
|
|
6210
6894
|
}
|
|
6211
|
-
const projectRootPath =
|
|
6895
|
+
const projectRootPath = path13.resolve(this.projectRoot);
|
|
6212
6896
|
const projectLocalFilePaths = /* @__PURE__ */ new Set([
|
|
6213
6897
|
...Array.from(this.fileHashCache.keys()).filter(
|
|
6214
6898
|
(filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
|
|
@@ -6231,7 +6915,7 @@ var Indexer = class {
|
|
|
6231
6915
|
if (this.config.scope !== "global") {
|
|
6232
6916
|
return this.getBranchCatalogCleanupKeys();
|
|
6233
6917
|
}
|
|
6234
|
-
const projectHash = hashContent(
|
|
6918
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6235
6919
|
const keys = /* @__PURE__ */ new Set();
|
|
6236
6920
|
const projectChunkIdSet = new Set(projectChunkIds);
|
|
6237
6921
|
const projectSymbolIdSet = new Set(projectSymbolIds);
|
|
@@ -6315,7 +6999,7 @@ var Indexer = class {
|
|
|
6315
6999
|
if (!this.database || this.config.scope !== "global") {
|
|
6316
7000
|
return false;
|
|
6317
7001
|
}
|
|
6318
|
-
const projectHash = hashContent(
|
|
7002
|
+
const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
|
|
6319
7003
|
const roots = this.getScopedRoots();
|
|
6320
7004
|
const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
|
|
6321
7005
|
return this.database.getAllBranches().some(
|
|
@@ -6349,7 +7033,7 @@ var Indexer = class {
|
|
|
6349
7033
|
...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
|
|
6350
7034
|
...scopedEntries.map(({ metadata }) => metadata.filePath)
|
|
6351
7035
|
]);
|
|
6352
|
-
const projectRootPath =
|
|
7036
|
+
const projectRootPath = path13.resolve(this.projectRoot);
|
|
6353
7037
|
const projectLocalFilePaths = new Set(
|
|
6354
7038
|
Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
|
|
6355
7039
|
);
|
|
@@ -6411,34 +7095,27 @@ var Indexer = class {
|
|
|
6411
7095
|
database.gcOrphanEmbeddings();
|
|
6412
7096
|
database.gcOrphanChunks();
|
|
6413
7097
|
store.save();
|
|
6414
|
-
|
|
7098
|
+
this.saveInvertedIndex(invertedIndex);
|
|
6415
7099
|
return {
|
|
6416
7100
|
removedChunkIds: removedChunkIdList,
|
|
6417
7101
|
hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
|
|
6418
7102
|
};
|
|
6419
7103
|
}
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
|
|
6429
|
-
}
|
|
6430
|
-
releaseIndexingLock() {
|
|
6431
|
-
if (existsSync7(this.indexingLockPath)) {
|
|
6432
|
-
unlinkSync(this.indexingLockPath);
|
|
7104
|
+
async recoverFromInterruptedIndexingUnlocked(owners) {
|
|
7105
|
+
for (const owner of owners) {
|
|
7106
|
+
this.logger.warn("Detected interrupted indexing session, recovering...", {
|
|
7107
|
+
pid: owner.pid,
|
|
7108
|
+
hostname: owner.hostname,
|
|
7109
|
+
operation: owner.operation,
|
|
7110
|
+
startedAt: owner.startedAt
|
|
7111
|
+
});
|
|
6433
7112
|
}
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
7113
|
+
if (this.config.scope === "global") {
|
|
7114
|
+
if (existsSync8(this.fileHashCachePath)) {
|
|
7115
|
+
unlinkSync(this.fileHashCachePath);
|
|
7116
|
+
}
|
|
7117
|
+
await this.healthCheckUnlocked();
|
|
6439
7118
|
}
|
|
6440
|
-
await this.healthCheck();
|
|
6441
|
-
this.releaseIndexingLock();
|
|
6442
7119
|
this.logger.info("Recovery complete, next index will re-process all files");
|
|
6443
7120
|
}
|
|
6444
7121
|
loadFailedBatches(maxChunkTokens) {
|
|
@@ -6454,10 +7131,10 @@ var Indexer = class {
|
|
|
6454
7131
|
}
|
|
6455
7132
|
}
|
|
6456
7133
|
loadSerializedFailedBatches() {
|
|
6457
|
-
if (!
|
|
7134
|
+
if (!existsSync8(this.failedBatchesPath)) {
|
|
6458
7135
|
return [];
|
|
6459
7136
|
}
|
|
6460
|
-
const data =
|
|
7137
|
+
const data = readFileSync7(this.failedBatchesPath, "utf-8");
|
|
6461
7138
|
const parsed = JSON.parse(data);
|
|
6462
7139
|
return parsed.map((batch) => {
|
|
6463
7140
|
const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
|
|
@@ -6474,7 +7151,7 @@ var Indexer = class {
|
|
|
6474
7151
|
}
|
|
6475
7152
|
saveFailedBatches(batches) {
|
|
6476
7153
|
if (batches.length === 0) {
|
|
6477
|
-
if (
|
|
7154
|
+
if (existsSync8(this.failedBatchesPath)) {
|
|
6478
7155
|
try {
|
|
6479
7156
|
unlinkSync(this.failedBatchesPath);
|
|
6480
7157
|
} catch {
|
|
@@ -6482,7 +7159,7 @@ var Indexer = class {
|
|
|
6482
7159
|
}
|
|
6483
7160
|
return;
|
|
6484
7161
|
}
|
|
6485
|
-
|
|
7162
|
+
this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
6486
7163
|
}
|
|
6487
7164
|
collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
|
|
6488
7165
|
const retryableById = /* @__PURE__ */ new Map();
|
|
@@ -6676,67 +7353,350 @@ var Indexer = class {
|
|
|
6676
7353
|
return parts.join("\n");
|
|
6677
7354
|
}
|
|
6678
7355
|
async initialize() {
|
|
6679
|
-
if (this.
|
|
6680
|
-
|
|
6681
|
-
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
6682
|
-
}
|
|
6683
|
-
this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
|
|
6684
|
-
} else if (this.config.embeddingProvider === "auto") {
|
|
6685
|
-
this.configuredProviderInfo = await tryDetectProvider();
|
|
6686
|
-
} else {
|
|
6687
|
-
this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
|
|
7356
|
+
if (this.initializationPromise) {
|
|
7357
|
+
await this.initializationPromise;
|
|
6688
7358
|
}
|
|
6689
|
-
if (
|
|
6690
|
-
|
|
6691
|
-
"No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
|
|
6692
|
-
);
|
|
7359
|
+
if (this.isInitializedFor("reader")) {
|
|
7360
|
+
return;
|
|
6693
7361
|
}
|
|
6694
|
-
this.
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
if (this.config.reranker?.enabled) {
|
|
6702
|
-
this.reranker = createReranker(this.config.reranker);
|
|
6703
|
-
if (this.reranker.isAvailable()) {
|
|
6704
|
-
this.logger.info("Reranker initialized", {
|
|
6705
|
-
model: this.config.reranker.model,
|
|
6706
|
-
baseUrl: this.config.reranker.baseUrl
|
|
6707
|
-
});
|
|
7362
|
+
await this.initializeOnce("reader", [], { skipAutoGc: true });
|
|
7363
|
+
}
|
|
7364
|
+
async initializeOnce(mode, recoveredOwners, options) {
|
|
7365
|
+
if (this.initializationPromise) {
|
|
7366
|
+
await this.initializationPromise;
|
|
7367
|
+
if (this.isInitializedFor(mode)) {
|
|
7368
|
+
return;
|
|
6708
7369
|
}
|
|
7370
|
+
return this.initializeOnce(mode, recoveredOwners, options);
|
|
6709
7371
|
}
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6720
|
-
try {
|
|
6721
|
-
this.invertedIndex.load();
|
|
6722
|
-
} catch {
|
|
6723
|
-
if (existsSync7(invertedIndexPath)) {
|
|
6724
|
-
await fsPromises2.unlink(invertedIndexPath);
|
|
7372
|
+
if (this.isInitializedFor(mode)) {
|
|
7373
|
+
return;
|
|
7374
|
+
}
|
|
7375
|
+
const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
|
|
7376
|
+
this.resetLoadedIndexState();
|
|
7377
|
+
throw error;
|
|
7378
|
+
}).finally(() => {
|
|
7379
|
+
if (this.initializationPromise === initialization) {
|
|
7380
|
+
this.initializationPromise = null;
|
|
6725
7381
|
}
|
|
6726
|
-
|
|
7382
|
+
});
|
|
7383
|
+
this.initializationPromise = initialization;
|
|
7384
|
+
await initialization;
|
|
7385
|
+
}
|
|
7386
|
+
isInitializedFor(mode) {
|
|
7387
|
+
const hasState = Boolean(
|
|
7388
|
+
this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
|
|
7389
|
+
);
|
|
7390
|
+
if (!hasState) {
|
|
7391
|
+
return false;
|
|
6727
7392
|
}
|
|
6728
|
-
|
|
6729
|
-
|
|
7393
|
+
return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
|
|
7394
|
+
}
|
|
7395
|
+
recordReadIssue(component, message, error) {
|
|
7396
|
+
this.readIssues.push(this.createReadIssue(component, message));
|
|
7397
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7398
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7399
|
+
}
|
|
7400
|
+
createReadIssue(component, message) {
|
|
7401
|
+
return {
|
|
7402
|
+
component,
|
|
7403
|
+
message,
|
|
7404
|
+
blocking: component !== "keyword"
|
|
7405
|
+
};
|
|
7406
|
+
}
|
|
7407
|
+
getVectorReadIssueMessage() {
|
|
7408
|
+
if (this.config.scope === "global") {
|
|
7409
|
+
return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
|
|
7410
|
+
}
|
|
7411
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7412
|
+
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.";
|
|
7413
|
+
}
|
|
7414
|
+
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.";
|
|
7415
|
+
}
|
|
7416
|
+
getKeywordReadIssueMessage() {
|
|
7417
|
+
if (this.config.scope === "global") {
|
|
7418
|
+
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.";
|
|
7419
|
+
}
|
|
7420
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7421
|
+
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.";
|
|
7422
|
+
}
|
|
7423
|
+
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.";
|
|
7424
|
+
}
|
|
7425
|
+
getDatabaseReadIssueMessage() {
|
|
7426
|
+
if (this.config.scope === "global") {
|
|
7427
|
+
return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
|
|
7428
|
+
}
|
|
7429
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
7430
|
+
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.";
|
|
7431
|
+
}
|
|
7432
|
+
return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
|
|
7433
|
+
}
|
|
7434
|
+
getReaderFileFingerprint(filePath, identityOnly = false) {
|
|
6730
7435
|
try {
|
|
6731
|
-
|
|
7436
|
+
const stats = statSync3(filePath);
|
|
7437
|
+
if (identityOnly) {
|
|
7438
|
+
return `${stats.dev}:${stats.ino}`;
|
|
7439
|
+
}
|
|
7440
|
+
return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
|
|
6732
7441
|
} catch (error) {
|
|
6733
|
-
|
|
6734
|
-
|
|
7442
|
+
return `unavailable:${getErrorMessage2(error)}`;
|
|
7443
|
+
}
|
|
7444
|
+
}
|
|
7445
|
+
captureReaderArtifactFingerprint() {
|
|
7446
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7447
|
+
return {
|
|
7448
|
+
vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
|
|
7449
|
+
keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
|
|
7450
|
+
database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
|
|
7451
|
+
databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
|
|
7452
|
+
};
|
|
7453
|
+
}
|
|
7454
|
+
refreshReaderArtifacts() {
|
|
7455
|
+
if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
|
|
7456
|
+
return;
|
|
7457
|
+
}
|
|
7458
|
+
const previousFingerprint = this.readerArtifactFingerprint;
|
|
7459
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7460
|
+
const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
|
|
7461
|
+
const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
|
|
7462
|
+
const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
|
|
7463
|
+
const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
|
|
7464
|
+
const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
|
|
7465
|
+
const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7466
|
+
if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
|
|
7467
|
+
return;
|
|
7468
|
+
}
|
|
7469
|
+
const setIssue = (component, message, error) => {
|
|
7470
|
+
if (!issues.has(component)) {
|
|
7471
|
+
this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
|
|
7472
|
+
}
|
|
7473
|
+
issues.set(component, this.createReadIssue(component, message));
|
|
7474
|
+
this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
|
|
7475
|
+
};
|
|
7476
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7477
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7478
|
+
const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
|
|
7479
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
7480
|
+
if (vectorsChanged || retryDue("vectors")) {
|
|
7481
|
+
const vectorStoreExists = existsSync8(storePath);
|
|
7482
|
+
const vectorMetadataExists = existsSync8(vectorMetadataPath);
|
|
7483
|
+
if (vectorStoreExists && vectorMetadataExists) {
|
|
7484
|
+
try {
|
|
7485
|
+
const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
|
|
7486
|
+
store.loadStrict();
|
|
7487
|
+
this.store = store;
|
|
7488
|
+
issues.delete("vectors");
|
|
7489
|
+
this.readerArtifactRetryAfter.delete("vectors");
|
|
7490
|
+
} catch (error) {
|
|
7491
|
+
setIssue("vectors", this.getVectorReadIssueMessage(), error);
|
|
7492
|
+
}
|
|
7493
|
+
} else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
|
|
7494
|
+
setIssue("vectors", this.getVectorReadIssueMessage());
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
if (keywordChanged || retryDue("keyword") || !existsSync8(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
|
|
7498
|
+
if (existsSync8(invertedIndexPath)) {
|
|
7499
|
+
try {
|
|
7500
|
+
const invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7501
|
+
invertedIndex.load();
|
|
7502
|
+
this.invertedIndex = invertedIndex;
|
|
7503
|
+
issues.delete("keyword");
|
|
7504
|
+
this.readerArtifactRetryAfter.delete("keyword");
|
|
7505
|
+
} catch (error) {
|
|
7506
|
+
setIssue("keyword", this.getKeywordReadIssueMessage(), error);
|
|
7507
|
+
}
|
|
7508
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
|
|
7509
|
+
setIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
|
|
7513
|
+
if (existsSync8(dbPath)) {
|
|
7514
|
+
try {
|
|
7515
|
+
const database = Database.openReadOnly(dbPath);
|
|
7516
|
+
if (this.database) {
|
|
7517
|
+
this.retiredDatabases.push(this.database);
|
|
7518
|
+
}
|
|
7519
|
+
this.database = database;
|
|
7520
|
+
issues.delete("database");
|
|
7521
|
+
this.readerArtifactRetryAfter.delete("database");
|
|
7522
|
+
} catch (error) {
|
|
7523
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7524
|
+
}
|
|
7525
|
+
} else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
|
|
7526
|
+
setIssue("database", this.getDatabaseReadIssueMessage());
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
if (!issues.has("database")) {
|
|
7530
|
+
try {
|
|
7531
|
+
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
7532
|
+
} catch (error) {
|
|
7533
|
+
setIssue("database", this.getDatabaseReadIssueMessage(), error);
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
7536
|
+
this.readIssues = Array.from(issues.values());
|
|
7537
|
+
this.readerArtifactFingerprint = currentFingerprint;
|
|
7538
|
+
}
|
|
7539
|
+
refreshInactiveWriterArtifacts() {
|
|
7540
|
+
if (this.initializationMode !== "writer" || this.activeIndexLease) {
|
|
7541
|
+
return true;
|
|
7542
|
+
}
|
|
7543
|
+
const previousFingerprint = this.writerArtifactFingerprint;
|
|
7544
|
+
const currentFingerprint = this.captureReaderArtifactFingerprint();
|
|
7545
|
+
const retryDue = this.readIssues.some(
|
|
7546
|
+
(issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
|
|
7547
|
+
);
|
|
7548
|
+
const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
|
|
7549
|
+
if (!artifactsChanged && !retryDue) {
|
|
7550
|
+
return true;
|
|
7551
|
+
}
|
|
7552
|
+
if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
|
|
7553
|
+
return false;
|
|
7554
|
+
}
|
|
7555
|
+
this.initializationMode = "reader";
|
|
7556
|
+
this.readerArtifactFingerprint = previousFingerprint;
|
|
7557
|
+
try {
|
|
7558
|
+
this.refreshReaderArtifacts();
|
|
7559
|
+
this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
|
|
7560
|
+
} finally {
|
|
7561
|
+
this.readerArtifactFingerprint = null;
|
|
7562
|
+
this.initializationMode = "writer";
|
|
7563
|
+
}
|
|
7564
|
+
return true;
|
|
7565
|
+
}
|
|
7566
|
+
async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
|
|
7567
|
+
if (mode === "writer") {
|
|
7568
|
+
this.requireActiveLease();
|
|
7569
|
+
}
|
|
7570
|
+
this.readIssues = [];
|
|
7571
|
+
this.readerArtifactRetryAfter.clear();
|
|
7572
|
+
if (this.config.embeddingProvider === "custom") {
|
|
7573
|
+
if (!this.config.customProvider) {
|
|
7574
|
+
throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
|
|
7575
|
+
}
|
|
7576
|
+
this.configuredProviderInfo = createCustomProviderInfo(this.config.customProvider);
|
|
7577
|
+
} else if (this.config.embeddingProvider === "auto") {
|
|
7578
|
+
this.configuredProviderInfo = await tryDetectProvider();
|
|
7579
|
+
} else {
|
|
7580
|
+
this.configuredProviderInfo = await detectEmbeddingProvider(this.config.embeddingProvider, this.config.embeddingModel);
|
|
7581
|
+
}
|
|
7582
|
+
if (!this.configuredProviderInfo) {
|
|
7583
|
+
throw new Error(
|
|
7584
|
+
"No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
|
|
7585
|
+
);
|
|
7586
|
+
}
|
|
7587
|
+
this.logger.info("Initializing indexer", {
|
|
7588
|
+
provider: this.configuredProviderInfo.provider,
|
|
7589
|
+
model: this.configuredProviderInfo.modelInfo.model,
|
|
7590
|
+
scope: this.config.scope,
|
|
7591
|
+
rerankerEnabled: this.config.reranker?.enabled ?? false
|
|
7592
|
+
});
|
|
7593
|
+
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
7594
|
+
if (this.config.reranker?.enabled) {
|
|
7595
|
+
this.reranker = createReranker(this.config.reranker);
|
|
7596
|
+
if (this.reranker.isAvailable()) {
|
|
7597
|
+
this.logger.info("Reranker initialized", {
|
|
7598
|
+
model: this.config.reranker.model,
|
|
7599
|
+
baseUrl: this.config.reranker.baseUrl
|
|
7600
|
+
});
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
7604
|
+
const storePath = path13.join(this.indexPath, "vectors");
|
|
7605
|
+
const vectorMetadataPath = `${storePath}.meta.json`;
|
|
7606
|
+
const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
|
|
7607
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
7608
|
+
let dbIsNew = !existsSync8(dbPath);
|
|
7609
|
+
const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
|
|
7610
|
+
if (mode === "writer") {
|
|
7611
|
+
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7612
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
|
|
7613
|
+
throw new Error(
|
|
7614
|
+
"Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
|
|
7615
|
+
);
|
|
7616
|
+
}
|
|
7617
|
+
for (const recoveredOwner of recoveredOwners) {
|
|
7618
|
+
recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
|
|
7619
|
+
storePath,
|
|
7620
|
+
`${storePath}.meta.json`
|
|
7621
|
+
]);
|
|
7622
|
+
}
|
|
7623
|
+
if (recoveredOwners.length > 0 && this.config.scope === "project") {
|
|
7624
|
+
await this.resetLocalIndexArtifacts();
|
|
6735
7625
|
}
|
|
6736
7626
|
this.store = new VectorStore(storePath, dimensions);
|
|
7627
|
+
if (existsSync8(storePath) || existsSync8(vectorMetadataPath)) {
|
|
7628
|
+
this.store.load();
|
|
7629
|
+
}
|
|
7630
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7631
|
+
try {
|
|
7632
|
+
this.invertedIndex.load();
|
|
7633
|
+
} catch {
|
|
7634
|
+
if (existsSync8(invertedIndexPath)) {
|
|
7635
|
+
await fsPromises2.unlink(invertedIndexPath);
|
|
7636
|
+
}
|
|
7637
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7638
|
+
}
|
|
7639
|
+
try {
|
|
7640
|
+
this.database = new Database(dbPath);
|
|
7641
|
+
} catch (error) {
|
|
7642
|
+
if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
|
|
7643
|
+
throw error;
|
|
7644
|
+
}
|
|
7645
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7646
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7647
|
+
this.database = new Database(dbPath);
|
|
7648
|
+
dbIsNew = true;
|
|
7649
|
+
}
|
|
7650
|
+
} else {
|
|
7651
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7652
|
+
const vectorStoreExists = existsSync8(storePath);
|
|
7653
|
+
const vectorMetadataExists = existsSync8(vectorMetadataPath);
|
|
7654
|
+
const vectorReadFailureMessage = this.getVectorReadIssueMessage();
|
|
7655
|
+
if (vectorStoreExists !== vectorMetadataExists) {
|
|
7656
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage);
|
|
7657
|
+
} else if (vectorStoreExists) {
|
|
7658
|
+
try {
|
|
7659
|
+
this.store.loadStrict();
|
|
7660
|
+
} catch (error) {
|
|
7661
|
+
this.recordReadIssue("vectors", vectorReadFailureMessage, error);
|
|
7662
|
+
this.store = new VectorStore(storePath, dimensions);
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
6737
7665
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
6738
|
-
|
|
6739
|
-
|
|
7666
|
+
if (existsSync8(invertedIndexPath)) {
|
|
7667
|
+
try {
|
|
7668
|
+
this.invertedIndex.load();
|
|
7669
|
+
} catch (error) {
|
|
7670
|
+
this.recordReadIssue(
|
|
7671
|
+
"keyword",
|
|
7672
|
+
this.getKeywordReadIssueMessage(),
|
|
7673
|
+
error
|
|
7674
|
+
);
|
|
7675
|
+
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
7676
|
+
}
|
|
7677
|
+
} else if (this.store.count() > 0) {
|
|
7678
|
+
this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
|
|
7679
|
+
}
|
|
7680
|
+
if (existsSync8(dbPath)) {
|
|
7681
|
+
try {
|
|
7682
|
+
this.database = Database.openReadOnly(dbPath);
|
|
7683
|
+
} catch (error) {
|
|
7684
|
+
this.recordReadIssue(
|
|
7685
|
+
"database",
|
|
7686
|
+
this.getDatabaseReadIssueMessage(),
|
|
7687
|
+
error
|
|
7688
|
+
);
|
|
7689
|
+
this.database = Database.createEmptyReadOnly();
|
|
7690
|
+
}
|
|
7691
|
+
} else {
|
|
7692
|
+
this.database = Database.createEmptyReadOnly();
|
|
7693
|
+
if (this.store.count() > 0) {
|
|
7694
|
+
this.recordReadIssue(
|
|
7695
|
+
"database",
|
|
7696
|
+
`Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
|
|
7697
|
+
);
|
|
7698
|
+
}
|
|
7699
|
+
}
|
|
6740
7700
|
}
|
|
6741
7701
|
if (isGitRepo(this.projectRoot)) {
|
|
6742
7702
|
this.currentBranch = getBranchOrDefault(this.projectRoot);
|
|
@@ -6750,10 +7710,10 @@ var Indexer = class {
|
|
|
6750
7710
|
this.baseBranch = "default";
|
|
6751
7711
|
this.logger.branch("debug", "Not a git repository, using default branch");
|
|
6752
7712
|
}
|
|
6753
|
-
if (
|
|
6754
|
-
await this.
|
|
7713
|
+
if (mode === "writer" && recoveredOwners.length > 0) {
|
|
7714
|
+
await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
|
|
6755
7715
|
}
|
|
6756
|
-
if (dbIsNew && this.store.count() > 0) {
|
|
7716
|
+
if (mode === "writer" && dbIsNew && this.store.count() > 0) {
|
|
6757
7717
|
this.migrateFromLegacyIndex();
|
|
6758
7718
|
}
|
|
6759
7719
|
this.loadFileHashCache();
|
|
@@ -6765,9 +7725,11 @@ var Indexer = class {
|
|
|
6765
7725
|
configuredProviderInfo: this.configuredProviderInfo
|
|
6766
7726
|
});
|
|
6767
7727
|
}
|
|
6768
|
-
if (this.config.indexing.autoGc) {
|
|
7728
|
+
if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
|
|
6769
7729
|
await this.maybeRunAutoGc();
|
|
6770
7730
|
}
|
|
7731
|
+
this.initializationMode = mode;
|
|
7732
|
+
this.readerArtifactFingerprint = readerArtifactFingerprint;
|
|
6771
7733
|
}
|
|
6772
7734
|
async maybeRunAutoGc() {
|
|
6773
7735
|
if (!this.database) return;
|
|
@@ -6784,7 +7746,7 @@ var Indexer = class {
|
|
|
6784
7746
|
}
|
|
6785
7747
|
}
|
|
6786
7748
|
if (shouldRunGc) {
|
|
6787
|
-
const result = await this.
|
|
7749
|
+
const result = await this.healthCheckUnlocked();
|
|
6788
7750
|
if (result.warning) {
|
|
6789
7751
|
this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
|
|
6790
7752
|
} else {
|
|
@@ -6806,7 +7768,7 @@ var Indexer = class {
|
|
|
6806
7768
|
if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
|
|
6807
7769
|
return {
|
|
6808
7770
|
resetCorruptedIndex: true,
|
|
6809
|
-
warning: this.getCorruptedIndexWarning(
|
|
7771
|
+
warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
|
|
6810
7772
|
};
|
|
6811
7773
|
}
|
|
6812
7774
|
throw error;
|
|
@@ -6821,28 +7783,29 @@ var Indexer = class {
|
|
|
6821
7783
|
return;
|
|
6822
7784
|
}
|
|
6823
7785
|
const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
|
|
6824
|
-
const storeBasePath =
|
|
6825
|
-
const storeIndexPath =
|
|
7786
|
+
const storeBasePath = path13.join(this.indexPath, "vectors");
|
|
7787
|
+
const storeIndexPath = storeBasePath;
|
|
6826
7788
|
const storeMetadataPath = `${storeBasePath}.meta.json`;
|
|
6827
|
-
const
|
|
6828
|
-
const
|
|
7789
|
+
const lease = this.requireActiveLease();
|
|
7790
|
+
const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
|
|
7791
|
+
const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
|
|
6829
7792
|
let backedUpIndex = false;
|
|
6830
7793
|
let backedUpMetadata = false;
|
|
6831
7794
|
let rebuiltCount = 0;
|
|
6832
7795
|
let skippedCount = 0;
|
|
6833
|
-
if (
|
|
7796
|
+
if (existsSync8(backupIndexPath)) {
|
|
6834
7797
|
unlinkSync(backupIndexPath);
|
|
6835
7798
|
}
|
|
6836
|
-
if (
|
|
7799
|
+
if (existsSync8(backupMetadataPath)) {
|
|
6837
7800
|
unlinkSync(backupMetadataPath);
|
|
6838
7801
|
}
|
|
6839
7802
|
try {
|
|
6840
|
-
if (
|
|
6841
|
-
|
|
7803
|
+
if (existsSync8(storeIndexPath)) {
|
|
7804
|
+
renameSync2(storeIndexPath, backupIndexPath);
|
|
6842
7805
|
backedUpIndex = true;
|
|
6843
7806
|
}
|
|
6844
|
-
if (
|
|
6845
|
-
|
|
7807
|
+
if (existsSync8(storeMetadataPath)) {
|
|
7808
|
+
renameSync2(storeMetadataPath, backupMetadataPath);
|
|
6846
7809
|
backedUpMetadata = true;
|
|
6847
7810
|
}
|
|
6848
7811
|
store.clear();
|
|
@@ -6862,10 +7825,10 @@ var Indexer = class {
|
|
|
6862
7825
|
rebuiltCount += 1;
|
|
6863
7826
|
}
|
|
6864
7827
|
store.save();
|
|
6865
|
-
if (backedUpIndex &&
|
|
7828
|
+
if (backedUpIndex && existsSync8(backupIndexPath)) {
|
|
6866
7829
|
unlinkSync(backupIndexPath);
|
|
6867
7830
|
}
|
|
6868
|
-
if (backedUpMetadata &&
|
|
7831
|
+
if (backedUpMetadata && existsSync8(backupMetadataPath)) {
|
|
6869
7832
|
unlinkSync(backupMetadataPath);
|
|
6870
7833
|
}
|
|
6871
7834
|
this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
|
|
@@ -6878,17 +7841,17 @@ var Indexer = class {
|
|
|
6878
7841
|
store.clear();
|
|
6879
7842
|
} catch {
|
|
6880
7843
|
}
|
|
6881
|
-
if (
|
|
7844
|
+
if (existsSync8(storeIndexPath)) {
|
|
6882
7845
|
unlinkSync(storeIndexPath);
|
|
6883
7846
|
}
|
|
6884
|
-
if (
|
|
7847
|
+
if (existsSync8(storeMetadataPath)) {
|
|
6885
7848
|
unlinkSync(storeMetadataPath);
|
|
6886
7849
|
}
|
|
6887
|
-
if (backedUpIndex &&
|
|
6888
|
-
|
|
7850
|
+
if (backedUpIndex && existsSync8(backupIndexPath)) {
|
|
7851
|
+
renameSync2(backupIndexPath, storeIndexPath);
|
|
6889
7852
|
}
|
|
6890
|
-
if (backedUpMetadata &&
|
|
6891
|
-
|
|
7853
|
+
if (backedUpMetadata && existsSync8(backupMetadataPath)) {
|
|
7854
|
+
renameSync2(backupMetadataPath, storeMetadataPath);
|
|
6892
7855
|
}
|
|
6893
7856
|
if (backedUpIndex || backedUpMetadata) {
|
|
6894
7857
|
store.load();
|
|
@@ -6902,11 +7865,37 @@ var Indexer = class {
|
|
|
6902
7865
|
}
|
|
6903
7866
|
return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
|
|
6904
7867
|
}
|
|
7868
|
+
async resetLocalIndexArtifacts() {
|
|
7869
|
+
this.store = null;
|
|
7870
|
+
this.invertedIndex = null;
|
|
7871
|
+
this.database?.close();
|
|
7872
|
+
this.database = null;
|
|
7873
|
+
this.indexCompatibility = null;
|
|
7874
|
+
this.initializationMode = "none";
|
|
7875
|
+
this.readIssues = [];
|
|
7876
|
+
this.readerArtifactFingerprint = null;
|
|
7877
|
+
this.writerArtifactFingerprint = null;
|
|
7878
|
+
this.readerArtifactRetryAfter.clear();
|
|
7879
|
+
this.fileHashCache.clear();
|
|
7880
|
+
const resetPaths = [
|
|
7881
|
+
path13.join(this.indexPath, "codebase.db"),
|
|
7882
|
+
path13.join(this.indexPath, "codebase.db-shm"),
|
|
7883
|
+
path13.join(this.indexPath, "codebase.db-wal"),
|
|
7884
|
+
path13.join(this.indexPath, "vectors"),
|
|
7885
|
+
path13.join(this.indexPath, "vectors.usearch"),
|
|
7886
|
+
path13.join(this.indexPath, "vectors.meta.json"),
|
|
7887
|
+
path13.join(this.indexPath, "inverted-index.json"),
|
|
7888
|
+
path13.join(this.indexPath, "file-hashes.json"),
|
|
7889
|
+
path13.join(this.indexPath, "failed-batches.json")
|
|
7890
|
+
];
|
|
7891
|
+
await Promise.all(resetPaths.map((targetPath) => fsPromises2.rm(targetPath, { recursive: true, force: true })));
|
|
7892
|
+
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7893
|
+
}
|
|
6905
7894
|
async tryResetCorruptedIndex(stage, error) {
|
|
6906
7895
|
if (!isSqliteCorruptionError(error)) {
|
|
6907
7896
|
return false;
|
|
6908
7897
|
}
|
|
6909
|
-
const dbPath =
|
|
7898
|
+
const dbPath = path13.join(this.indexPath, "codebase.db");
|
|
6910
7899
|
const warning = this.getCorruptedIndexWarning(dbPath);
|
|
6911
7900
|
const errorMessage = getErrorMessage2(error);
|
|
6912
7901
|
if (this.config.scope === "global") {
|
|
@@ -6922,30 +7911,7 @@ var Indexer = class {
|
|
|
6922
7911
|
dbPath,
|
|
6923
7912
|
error: errorMessage
|
|
6924
7913
|
});
|
|
6925
|
-
this.
|
|
6926
|
-
this.invertedIndex = null;
|
|
6927
|
-
this.database?.close();
|
|
6928
|
-
this.database = null;
|
|
6929
|
-
this.indexCompatibility = null;
|
|
6930
|
-
this.fileHashCache.clear();
|
|
6931
|
-
const resetPaths = [
|
|
6932
|
-
path11.join(this.indexPath, "codebase.db"),
|
|
6933
|
-
path11.join(this.indexPath, "codebase.db-shm"),
|
|
6934
|
-
path11.join(this.indexPath, "codebase.db-wal"),
|
|
6935
|
-
path11.join(this.indexPath, "vectors.usearch"),
|
|
6936
|
-
path11.join(this.indexPath, "inverted-index.json"),
|
|
6937
|
-
path11.join(this.indexPath, "file-hashes.json"),
|
|
6938
|
-
path11.join(this.indexPath, "failed-batches.json"),
|
|
6939
|
-
path11.join(this.indexPath, "indexing.lock"),
|
|
6940
|
-
path11.join(this.indexPath, "vectors")
|
|
6941
|
-
];
|
|
6942
|
-
await Promise.all(resetPaths.map(async (targetPath) => {
|
|
6943
|
-
try {
|
|
6944
|
-
await fsPromises2.rm(targetPath, { recursive: true, force: true });
|
|
6945
|
-
} catch {
|
|
6946
|
-
}
|
|
6947
|
-
}));
|
|
6948
|
-
await fsPromises2.mkdir(this.indexPath, { recursive: true });
|
|
7914
|
+
await this.resetLocalIndexArtifacts();
|
|
6949
7915
|
return true;
|
|
6950
7916
|
}
|
|
6951
7917
|
migrateFromLegacyIndex() {
|
|
@@ -7064,8 +8030,62 @@ var Indexer = class {
|
|
|
7064
8030
|
return this.indexCompatibility;
|
|
7065
8031
|
}
|
|
7066
8032
|
async ensureInitialized() {
|
|
8033
|
+
let initializedReader = false;
|
|
8034
|
+
while (true) {
|
|
8035
|
+
if (this.initializationPromise) {
|
|
8036
|
+
await this.initializationPromise;
|
|
8037
|
+
}
|
|
8038
|
+
if (!this.isInitializedFor("reader")) {
|
|
8039
|
+
await this.initialize();
|
|
8040
|
+
initializedReader = true;
|
|
8041
|
+
continue;
|
|
8042
|
+
}
|
|
8043
|
+
if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
|
|
8044
|
+
if (!this.refreshInactiveWriterArtifacts()) {
|
|
8045
|
+
this.resetLoadedIndexState(true);
|
|
8046
|
+
await this.initialize();
|
|
8047
|
+
initializedReader = true;
|
|
8048
|
+
continue;
|
|
8049
|
+
}
|
|
8050
|
+
}
|
|
8051
|
+
if (this.initializationMode === "reader" && !initializedReader) {
|
|
8052
|
+
this.refreshReaderArtifacts();
|
|
8053
|
+
}
|
|
8054
|
+
const state = this.requireLoadedIndexState();
|
|
8055
|
+
return {
|
|
8056
|
+
...state,
|
|
8057
|
+
readIssues: [...this.readIssues],
|
|
8058
|
+
compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
|
|
8059
|
+
};
|
|
8060
|
+
}
|
|
8061
|
+
}
|
|
8062
|
+
async ensureInitializedUnlocked(recoveredOwners = []) {
|
|
8063
|
+
this.requireActiveLease();
|
|
8064
|
+
if (this.initializationPromise) {
|
|
8065
|
+
await this.initializationPromise;
|
|
8066
|
+
}
|
|
8067
|
+
if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
|
|
8068
|
+
const retireReaderDatabase = this.initializationMode === "reader";
|
|
8069
|
+
this.resetLoadedIndexState(retireReaderDatabase);
|
|
8070
|
+
await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
|
|
8071
|
+
} else {
|
|
8072
|
+
this.refreshLoadedIndexState();
|
|
8073
|
+
}
|
|
8074
|
+
if (this.config.indexing.autoGc) {
|
|
8075
|
+
await this.maybeRunAutoGc();
|
|
8076
|
+
}
|
|
8077
|
+
return this.requireLoadedIndexState();
|
|
8078
|
+
}
|
|
8079
|
+
requireReadableComponents(readIssues, ...components) {
|
|
8080
|
+
const componentSet = new Set(components);
|
|
8081
|
+
const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
|
|
8082
|
+
if (issues.length > 0) {
|
|
8083
|
+
throw new Error(issues.map((issue) => issue.message).join(" "));
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
8086
|
+
requireLoadedIndexState() {
|
|
7067
8087
|
if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
|
|
7068
|
-
|
|
8088
|
+
throw new Error("Index state is not initialized");
|
|
7069
8089
|
}
|
|
7070
8090
|
return {
|
|
7071
8091
|
store: this.store,
|
|
@@ -7089,7 +8109,12 @@ var Indexer = class {
|
|
|
7089
8109
|
return createCostEstimate(files, configuredProviderInfo);
|
|
7090
8110
|
}
|
|
7091
8111
|
async index(onProgress) {
|
|
7092
|
-
|
|
8112
|
+
return this.withIndexMutationLease("index", async (recoveredOwners) => {
|
|
8113
|
+
return this.indexUnlocked(onProgress, recoveredOwners);
|
|
8114
|
+
});
|
|
8115
|
+
}
|
|
8116
|
+
async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
|
|
8117
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
|
|
7093
8118
|
const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
7094
8119
|
const branchCatalogKey = this.getBranchCatalogKey();
|
|
7095
8120
|
const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
|
|
@@ -7099,7 +8124,6 @@ var Indexer = class {
|
|
|
7099
8124
|
`${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
|
|
7100
8125
|
);
|
|
7101
8126
|
}
|
|
7102
|
-
this.acquireIndexingLock();
|
|
7103
8127
|
this.logger.recordIndexingStart();
|
|
7104
8128
|
this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
|
|
7105
8129
|
const startTime = Date.now();
|
|
@@ -7174,6 +8198,7 @@ var Indexer = class {
|
|
|
7174
8198
|
this.logger.debug("Parsed changed files", { parsedCount: parsedFiles.length, parseMs: parseMs.toFixed(2) });
|
|
7175
8199
|
const existingChunks = /* @__PURE__ */ new Map();
|
|
7176
8200
|
const existingChunksByFile = /* @__PURE__ */ new Map();
|
|
8201
|
+
const existingMetadataById = /* @__PURE__ */ new Map();
|
|
7177
8202
|
for (const { key, metadata } of store.getAllMetadata()) {
|
|
7178
8203
|
if (scopedRoots && !this.isFileInCurrentScope(metadata.filePath, scopedRoots)) {
|
|
7179
8204
|
continue;
|
|
@@ -7182,6 +8207,7 @@ var Indexer = class {
|
|
|
7182
8207
|
continue;
|
|
7183
8208
|
}
|
|
7184
8209
|
existingChunks.set(key, metadata.hash);
|
|
8210
|
+
existingMetadataById.set(key, metadata);
|
|
7185
8211
|
const fileChunks = existingChunksByFile.get(metadata.filePath) || /* @__PURE__ */ new Set();
|
|
7186
8212
|
fileChunks.add(key);
|
|
7187
8213
|
existingChunksByFile.set(metadata.filePath, fileChunks);
|
|
@@ -7189,6 +8215,8 @@ var Indexer = class {
|
|
|
7189
8215
|
const currentChunkIds = /* @__PURE__ */ new Set();
|
|
7190
8216
|
const currentFilePaths = /* @__PURE__ */ new Set();
|
|
7191
8217
|
const pendingChunks = [];
|
|
8218
|
+
const gitBlameEnabled = this.config.indexing.gitBlame.enabled && isGitRepo(this.projectRoot);
|
|
8219
|
+
let backfilledBlameMetadata = false;
|
|
7192
8220
|
for (const filePath of unchangedFilePaths) {
|
|
7193
8221
|
currentFilePaths.add(filePath);
|
|
7194
8222
|
const fileChunks = existingChunksByFile.get(filePath);
|
|
@@ -7199,10 +8227,52 @@ var Indexer = class {
|
|
|
7199
8227
|
}
|
|
7200
8228
|
}
|
|
7201
8229
|
const chunkDataBatch = [];
|
|
8230
|
+
if (gitBlameEnabled) {
|
|
8231
|
+
const backfillItems = [];
|
|
8232
|
+
for (const chunkId of currentChunkIds) {
|
|
8233
|
+
const metadata = existingMetadataById.get(chunkId);
|
|
8234
|
+
if (!metadata || hasBlameMetadata(metadata)) {
|
|
8235
|
+
continue;
|
|
8236
|
+
}
|
|
8237
|
+
const chunk = database.getChunk(chunkId);
|
|
8238
|
+
if (!chunk) {
|
|
8239
|
+
continue;
|
|
8240
|
+
}
|
|
8241
|
+
const blame = await getChunkGitBlame(this.projectRoot, chunk.filePath, chunk.startLine, chunk.endLine);
|
|
8242
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
8243
|
+
if (!blameMetadata.blameSha) {
|
|
8244
|
+
continue;
|
|
8245
|
+
}
|
|
8246
|
+
chunkDataBatch.push({
|
|
8247
|
+
...chunk,
|
|
8248
|
+
blameSha: blameMetadata.blameSha,
|
|
8249
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8250
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8251
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8252
|
+
blameSummary: blameMetadata.blameSummary
|
|
8253
|
+
});
|
|
8254
|
+
const embeddingBuffer = database.getEmbedding(chunk.contentHash);
|
|
8255
|
+
if (!embeddingBuffer) {
|
|
8256
|
+
continue;
|
|
8257
|
+
}
|
|
8258
|
+
backfillItems.push({
|
|
8259
|
+
id: chunkId,
|
|
8260
|
+
vector: Array.from(bufferToFloat32Array(embeddingBuffer)),
|
|
8261
|
+
metadata: {
|
|
8262
|
+
...metadata,
|
|
8263
|
+
...blameMetadata
|
|
8264
|
+
}
|
|
8265
|
+
});
|
|
8266
|
+
}
|
|
8267
|
+
if (backfillItems.length > 0) {
|
|
8268
|
+
store.addBatch(backfillItems);
|
|
8269
|
+
backfilledBlameMetadata = true;
|
|
8270
|
+
}
|
|
8271
|
+
}
|
|
7202
8272
|
for (const parsed of parsedFiles) {
|
|
7203
8273
|
currentFilePaths.add(parsed.path);
|
|
7204
8274
|
if (parsed.chunks.length === 0) {
|
|
7205
|
-
const relativePath =
|
|
8275
|
+
const relativePath = path13.relative(this.projectRoot, parsed.path);
|
|
7206
8276
|
stats.parseFailures.push(relativePath);
|
|
7207
8277
|
}
|
|
7208
8278
|
let fileChunkCount = 0;
|
|
@@ -7223,6 +8293,10 @@ var Indexer = class {
|
|
|
7223
8293
|
}
|
|
7224
8294
|
const id = generateChunkId(parsed.path, chunk);
|
|
7225
8295
|
const contentHash = generateChunkHash(chunk);
|
|
8296
|
+
const existingContentHash = existingChunks.get(id);
|
|
8297
|
+
const existingChunk = gitBlameEnabled ? database.getChunk(id) : null;
|
|
8298
|
+
const blame = gitBlameEnabled && existingContentHash !== contentHash ? await getChunkGitBlame(this.projectRoot, parsed.path, chunk.startLine, chunk.endLine) : blameFromChunkData(existingChunk);
|
|
8299
|
+
const blameMetadata = metadataFromBlame(blame);
|
|
7226
8300
|
currentChunkIds.add(id);
|
|
7227
8301
|
chunkDataBatch.push({
|
|
7228
8302
|
chunkId: id,
|
|
@@ -7232,15 +8306,20 @@ var Indexer = class {
|
|
|
7232
8306
|
endLine: chunk.endLine,
|
|
7233
8307
|
nodeType: chunk.chunkType,
|
|
7234
8308
|
name: chunk.name,
|
|
7235
|
-
language: chunk.language
|
|
8309
|
+
language: chunk.language,
|
|
8310
|
+
blameSha: blameMetadata.blameSha,
|
|
8311
|
+
blameAuthor: blameMetadata.blameAuthor,
|
|
8312
|
+
blameAuthorEmail: blameMetadata.blameAuthorEmail,
|
|
8313
|
+
blameCommittedAt: blameMetadata.blameCommittedAt,
|
|
8314
|
+
blameSummary: blameMetadata.blameSummary
|
|
7236
8315
|
});
|
|
7237
|
-
if (
|
|
8316
|
+
if (existingContentHash === contentHash) {
|
|
7238
8317
|
fileChunkCount++;
|
|
7239
8318
|
continue;
|
|
7240
8319
|
}
|
|
7241
|
-
const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((
|
|
7242
|
-
text:
|
|
7243
|
-
tokenCount: estimateTokens(
|
|
8320
|
+
const texts = createEmbeddingTexts(chunk, parsed.path, getSafeEmbeddingChunkTokenLimit(configuredProviderInfo)).map((text3) => ({
|
|
8321
|
+
text: text3,
|
|
8322
|
+
tokenCount: estimateTokens(text3)
|
|
7244
8323
|
}));
|
|
7245
8324
|
const metadata = {
|
|
7246
8325
|
filePath: parsed.path,
|
|
@@ -7249,7 +8328,8 @@ var Indexer = class {
|
|
|
7249
8328
|
chunkType: chunk.chunkType,
|
|
7250
8329
|
name: chunk.name,
|
|
7251
8330
|
language: chunk.language,
|
|
7252
|
-
hash: contentHash
|
|
8331
|
+
hash: contentHash,
|
|
8332
|
+
...blameMetadata
|
|
7253
8333
|
};
|
|
7254
8334
|
pendingChunks.push({
|
|
7255
8335
|
id,
|
|
@@ -7392,6 +8472,11 @@ var Indexer = class {
|
|
|
7392
8472
|
database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
|
|
7393
8473
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7394
8474
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
8475
|
+
const vectorPath = path13.join(this.indexPath, "vectors");
|
|
8476
|
+
const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync8(vectorPath) && existsSync8(`${vectorPath}.meta.json`);
|
|
8477
|
+
if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
|
|
8478
|
+
store.save();
|
|
8479
|
+
}
|
|
7395
8480
|
if (scopedRoots) {
|
|
7396
8481
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7397
8482
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7410,7 +8495,6 @@ var Indexer = class {
|
|
|
7410
8495
|
chunksProcessed: 0,
|
|
7411
8496
|
totalChunks: 0
|
|
7412
8497
|
});
|
|
7413
|
-
this.releaseIndexingLock();
|
|
7414
8498
|
return stats;
|
|
7415
8499
|
}
|
|
7416
8500
|
if (pendingChunks.length === 0) {
|
|
@@ -7419,7 +8503,7 @@ var Indexer = class {
|
|
|
7419
8503
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7420
8504
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7421
8505
|
store.save();
|
|
7422
|
-
|
|
8506
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7423
8507
|
if (scopedRoots) {
|
|
7424
8508
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7425
8509
|
this.clearScopedFailedBatches(scopedRoots);
|
|
@@ -7438,7 +8522,6 @@ var Indexer = class {
|
|
|
7438
8522
|
chunksProcessed: 0,
|
|
7439
8523
|
totalChunks: 0
|
|
7440
8524
|
});
|
|
7441
|
-
this.releaseIndexingLock();
|
|
7442
8525
|
return stats;
|
|
7443
8526
|
}
|
|
7444
8527
|
onProgress?.({
|
|
@@ -7669,7 +8752,7 @@ var Indexer = class {
|
|
|
7669
8752
|
database.clearBranchSymbols(branchCatalogKey);
|
|
7670
8753
|
database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
|
|
7671
8754
|
store.save();
|
|
7672
|
-
|
|
8755
|
+
this.saveInvertedIndex(invertedIndex);
|
|
7673
8756
|
if (scopedRoots) {
|
|
7674
8757
|
this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
|
|
7675
8758
|
} else {
|
|
@@ -7721,7 +8804,6 @@ var Indexer = class {
|
|
|
7721
8804
|
chunksProcessed: stats.indexedChunks,
|
|
7722
8805
|
totalChunks: pendingChunks.length
|
|
7723
8806
|
});
|
|
7724
|
-
this.releaseIndexingLock();
|
|
7725
8807
|
return stats;
|
|
7726
8808
|
}
|
|
7727
8809
|
async getQueryEmbedding(query, provider) {
|
|
@@ -7771,9 +8853,9 @@ var Indexer = class {
|
|
|
7771
8853
|
}
|
|
7772
8854
|
return bestMatch;
|
|
7773
8855
|
}
|
|
7774
|
-
tokenize(
|
|
8856
|
+
tokenize(text3) {
|
|
7775
8857
|
return new Set(
|
|
7776
|
-
|
|
8858
|
+
text3.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((t) => t.length > 1)
|
|
7777
8859
|
);
|
|
7778
8860
|
}
|
|
7779
8861
|
jaccardSimilarity(a, b) {
|
|
@@ -7787,8 +8869,8 @@ var Indexer = class {
|
|
|
7787
8869
|
return intersection / union;
|
|
7788
8870
|
}
|
|
7789
8871
|
async search(query, limit, options) {
|
|
7790
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
7791
|
-
|
|
8872
|
+
const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
8873
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
7792
8874
|
if (!compatibility.compatible) {
|
|
7793
8875
|
throw new Error(
|
|
7794
8876
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
|
|
@@ -7802,6 +8884,7 @@ var Indexer = class {
|
|
|
7802
8884
|
const maxResults = limit ?? this.config.search.maxResults;
|
|
7803
8885
|
const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
|
|
7804
8886
|
const fusionStrategy = this.config.search.fusionStrategy;
|
|
8887
|
+
const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
|
|
7805
8888
|
const rrfK = this.config.search.rrfK;
|
|
7806
8889
|
const rerankTopN = this.config.search.rerankTopN;
|
|
7807
8890
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
@@ -7810,7 +8893,7 @@ var Indexer = class {
|
|
|
7810
8893
|
this.logger.search("debug", "Starting search", {
|
|
7811
8894
|
query,
|
|
7812
8895
|
maxResults,
|
|
7813
|
-
hybridWeight,
|
|
8896
|
+
hybridWeight: effectiveHybridWeight,
|
|
7814
8897
|
fusionStrategy,
|
|
7815
8898
|
rrfK,
|
|
7816
8899
|
rerankTopN,
|
|
@@ -7824,7 +8907,7 @@ var Indexer = class {
|
|
|
7824
8907
|
const semanticResults = store.search(embedding, maxResults * 4);
|
|
7825
8908
|
const vectorMs = performance2.now() - vectorStartTime;
|
|
7826
8909
|
const keywordStartTime = performance2.now();
|
|
7827
|
-
const keywordResults = await this.keywordSearch(query, maxResults * 4);
|
|
8910
|
+
const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
|
|
7828
8911
|
const keywordMs = performance2.now() - keywordStartTime;
|
|
7829
8912
|
let branchChunkIds = null;
|
|
7830
8913
|
if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
|
|
@@ -7861,7 +8944,7 @@ var Indexer = class {
|
|
|
7861
8944
|
rrfK,
|
|
7862
8945
|
rerankTopN,
|
|
7863
8946
|
limit: maxResults,
|
|
7864
|
-
hybridWeight,
|
|
8947
|
+
hybridWeight: effectiveHybridWeight,
|
|
7865
8948
|
prioritizeSourcePaths: sourceIntent
|
|
7866
8949
|
});
|
|
7867
8950
|
const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
|
|
@@ -7955,13 +9038,13 @@ var Indexer = class {
|
|
|
7955
9038
|
content,
|
|
7956
9039
|
score: r.score,
|
|
7957
9040
|
chunkType: r.metadata.chunkType,
|
|
7958
|
-
name: r.metadata.name
|
|
9041
|
+
name: r.metadata.name,
|
|
9042
|
+
blame: blameFromMetadata(r.metadata)
|
|
7959
9043
|
};
|
|
7960
9044
|
})
|
|
7961
9045
|
);
|
|
7962
9046
|
}
|
|
7963
|
-
async keywordSearch(query, limit) {
|
|
7964
|
-
const { store, invertedIndex } = await this.ensureInitialized();
|
|
9047
|
+
async keywordSearch(query, limit, store, invertedIndex) {
|
|
7965
9048
|
const scores = invertedIndex.search(query);
|
|
7966
9049
|
if (scores.size === 0) {
|
|
7967
9050
|
return [];
|
|
@@ -7979,24 +9062,54 @@ var Indexer = class {
|
|
|
7979
9062
|
return results.slice(0, limit);
|
|
7980
9063
|
}
|
|
7981
9064
|
async getStatus() {
|
|
7982
|
-
const { store, configuredProviderInfo, database } = await this.ensureInitialized();
|
|
9065
|
+
const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
7983
9066
|
const failedBatchesCount = this.getFailedBatchesCount();
|
|
9067
|
+
const vectorCount = store.count();
|
|
9068
|
+
const statusReadIssues = [...readIssues];
|
|
9069
|
+
let startupWarning = "";
|
|
9070
|
+
if (!statusReadIssues.some((issue) => issue.component === "database")) {
|
|
9071
|
+
try {
|
|
9072
|
+
startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
|
|
9073
|
+
} catch (error) {
|
|
9074
|
+
const message = this.getDatabaseReadIssueMessage();
|
|
9075
|
+
statusReadIssues.push(this.createReadIssue("database", message));
|
|
9076
|
+
if (!this.readIssues.some((issue) => issue.component === "database")) {
|
|
9077
|
+
this.recordReadIssue("database", message, error);
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
|
|
9082
|
+
const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
|
|
9083
|
+
const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
|
|
7984
9084
|
return {
|
|
7985
|
-
indexed:
|
|
7986
|
-
vectorCount
|
|
9085
|
+
indexed: vectorCount > 0 && !hasBlockingReadIssue,
|
|
9086
|
+
vectorCount,
|
|
7987
9087
|
provider: configuredProviderInfo.provider,
|
|
7988
9088
|
model: configuredProviderInfo.modelInfo.model,
|
|
7989
9089
|
indexPath: this.indexPath,
|
|
7990
9090
|
currentBranch: this.currentBranch,
|
|
7991
9091
|
baseBranch: this.baseBranch,
|
|
7992
|
-
compatibility
|
|
9092
|
+
compatibility,
|
|
7993
9093
|
failedBatchesCount,
|
|
7994
9094
|
failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
|
|
7995
|
-
warning:
|
|
9095
|
+
warning: warning || void 0
|
|
7996
9096
|
};
|
|
7997
9097
|
}
|
|
9098
|
+
async forceIndex(onProgress) {
|
|
9099
|
+
return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
|
|
9100
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9101
|
+
await this.clearIndexUnlocked();
|
|
9102
|
+
return this.indexUnlocked(onProgress, [], true);
|
|
9103
|
+
});
|
|
9104
|
+
}
|
|
7998
9105
|
async clearIndex() {
|
|
7999
|
-
|
|
9106
|
+
await this.withIndexMutationLease("clear", async (recoveredOwners) => {
|
|
9107
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9108
|
+
await this.clearIndexUnlocked();
|
|
9109
|
+
});
|
|
9110
|
+
}
|
|
9111
|
+
async clearIndexUnlocked() {
|
|
9112
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8000
9113
|
if (this.config.scope === "global") {
|
|
8001
9114
|
store.load();
|
|
8002
9115
|
invertedIndex.load();
|
|
@@ -8023,7 +9136,7 @@ var Indexer = class {
|
|
|
8023
9136
|
store.clear();
|
|
8024
9137
|
store.save();
|
|
8025
9138
|
invertedIndex.clear();
|
|
8026
|
-
|
|
9139
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8027
9140
|
this.fileHashCache.clear();
|
|
8028
9141
|
this.saveFileHashCache();
|
|
8029
9142
|
database.clearAllIndexedData();
|
|
@@ -8047,14 +9160,7 @@ var Indexer = class {
|
|
|
8047
9160
|
this.indexCompatibility = compatibility;
|
|
8048
9161
|
return;
|
|
8049
9162
|
}
|
|
8050
|
-
|
|
8051
|
-
if (this.host !== "opencode") {
|
|
8052
|
-
localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
|
|
8053
|
-
}
|
|
8054
|
-
const isLocalProjectIndex = localProjectIndexPaths.some(
|
|
8055
|
-
(localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
|
|
8056
|
-
);
|
|
8057
|
-
if (!isLocalProjectIndex) {
|
|
9163
|
+
if (!this.isLocalProjectIndexPath()) {
|
|
8058
9164
|
throw new Error(
|
|
8059
9165
|
"Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
|
|
8060
9166
|
);
|
|
@@ -8062,7 +9168,7 @@ var Indexer = class {
|
|
|
8062
9168
|
store.clear();
|
|
8063
9169
|
store.save();
|
|
8064
9170
|
invertedIndex.clear();
|
|
8065
|
-
|
|
9171
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8066
9172
|
this.fileHashCache.clear();
|
|
8067
9173
|
this.saveFileHashCache();
|
|
8068
9174
|
database.clearAllIndexedData();
|
|
@@ -8080,7 +9186,13 @@ var Indexer = class {
|
|
|
8080
9186
|
this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
|
|
8081
9187
|
}
|
|
8082
9188
|
async healthCheck() {
|
|
8083
|
-
|
|
9189
|
+
return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
|
|
9190
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9191
|
+
return this.healthCheckUnlocked();
|
|
9192
|
+
});
|
|
9193
|
+
}
|
|
9194
|
+
async healthCheckUnlocked() {
|
|
9195
|
+
const { store, invertedIndex, database } = this.requireLoadedIndexState();
|
|
8084
9196
|
this.logger.gc("info", "Starting health check");
|
|
8085
9197
|
const allMetadata = store.getAllMetadata();
|
|
8086
9198
|
const filePathsToChunkKeys = /* @__PURE__ */ new Map();
|
|
@@ -8093,7 +9205,7 @@ var Indexer = class {
|
|
|
8093
9205
|
const removedChunkKeys = [];
|
|
8094
9206
|
const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
|
|
8095
9207
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
8096
|
-
if (!
|
|
9208
|
+
if (!existsSync8(filePath)) {
|
|
8097
9209
|
chunkKeysByRemovedFile.set(filePath, chunkKeys);
|
|
8098
9210
|
for (const key of chunkKeys) {
|
|
8099
9211
|
removedChunkKeys.push(key);
|
|
@@ -8118,7 +9230,7 @@ var Indexer = class {
|
|
|
8118
9230
|
const removedCount = removedChunkKeys.length;
|
|
8119
9231
|
if (removedCount > 0) {
|
|
8120
9232
|
store.save();
|
|
8121
|
-
|
|
9233
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8122
9234
|
}
|
|
8123
9235
|
let gcOrphanEmbeddings;
|
|
8124
9236
|
let gcOrphanChunks;
|
|
@@ -8133,7 +9245,7 @@ var Indexer = class {
|
|
|
8133
9245
|
if (!await this.tryResetCorruptedIndex("running index health check", error)) {
|
|
8134
9246
|
throw error;
|
|
8135
9247
|
}
|
|
8136
|
-
await this.
|
|
9248
|
+
await this.initializeUnlocked("writer", [], { skipAutoGc: true });
|
|
8137
9249
|
return {
|
|
8138
9250
|
removed: 0,
|
|
8139
9251
|
filePaths: [],
|
|
@@ -8142,7 +9254,7 @@ var Indexer = class {
|
|
|
8142
9254
|
gcOrphanSymbols: 0,
|
|
8143
9255
|
gcOrphanCallEdges: 0,
|
|
8144
9256
|
resetCorruptedIndex: true,
|
|
8145
|
-
warning: this.getCorruptedIndexWarning(
|
|
9257
|
+
warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
|
|
8146
9258
|
};
|
|
8147
9259
|
}
|
|
8148
9260
|
this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
|
|
@@ -8155,7 +9267,13 @@ var Indexer = class {
|
|
|
8155
9267
|
return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
|
|
8156
9268
|
}
|
|
8157
9269
|
async retryFailedBatches() {
|
|
8158
|
-
|
|
9270
|
+
return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
|
|
9271
|
+
await this.ensureInitializedUnlocked(recoveredOwners);
|
|
9272
|
+
return this.retryFailedBatchesUnlocked();
|
|
9273
|
+
});
|
|
9274
|
+
}
|
|
9275
|
+
async retryFailedBatchesUnlocked() {
|
|
9276
|
+
const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
|
|
8159
9277
|
const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
|
|
8160
9278
|
const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
|
|
8161
9279
|
const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
|
|
@@ -8319,7 +9437,7 @@ var Indexer = class {
|
|
|
8319
9437
|
}
|
|
8320
9438
|
if (succeeded > 0) {
|
|
8321
9439
|
store.save();
|
|
8322
|
-
|
|
9440
|
+
this.saveInvertedIndex(invertedIndex);
|
|
8323
9441
|
}
|
|
8324
9442
|
if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
|
|
8325
9443
|
database.deleteMetadata(this.getProjectForceReembedMetadataKey());
|
|
@@ -8347,15 +9465,16 @@ var Indexer = class {
|
|
|
8347
9465
|
}
|
|
8348
9466
|
}
|
|
8349
9467
|
async getDatabaseStats() {
|
|
8350
|
-
const { database } = await this.ensureInitialized();
|
|
9468
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9469
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8351
9470
|
return database.getStats();
|
|
8352
9471
|
}
|
|
8353
9472
|
getLogger() {
|
|
8354
9473
|
return this.logger;
|
|
8355
9474
|
}
|
|
8356
9475
|
async findSimilar(code, limit = this.config.search.maxResults, options) {
|
|
8357
|
-
const { store, provider, database } = await this.ensureInitialized();
|
|
8358
|
-
|
|
9476
|
+
const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
|
|
9477
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8359
9478
|
if (!compatibility.compatible) {
|
|
8360
9479
|
throw new Error(
|
|
8361
9480
|
`${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
|
|
@@ -8462,13 +9581,15 @@ var Indexer = class {
|
|
|
8462
9581
|
content,
|
|
8463
9582
|
score: r.score,
|
|
8464
9583
|
chunkType: r.metadata.chunkType,
|
|
8465
|
-
name: r.metadata.name
|
|
9584
|
+
name: r.metadata.name,
|
|
9585
|
+
blame: blameFromMetadata(r.metadata)
|
|
8466
9586
|
};
|
|
8467
9587
|
})
|
|
8468
9588
|
);
|
|
8469
9589
|
}
|
|
8470
9590
|
async getCallers(targetName, callTypeFilter) {
|
|
8471
|
-
const { database } = await this.ensureInitialized();
|
|
9591
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9592
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8472
9593
|
const seen = /* @__PURE__ */ new Set();
|
|
8473
9594
|
const results = [];
|
|
8474
9595
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8482,7 +9603,8 @@ var Indexer = class {
|
|
|
8482
9603
|
return results;
|
|
8483
9604
|
}
|
|
8484
9605
|
async getCallees(symbolId, callTypeFilter) {
|
|
8485
|
-
const { database } = await this.ensureInitialized();
|
|
9606
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9607
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8486
9608
|
const seen = /* @__PURE__ */ new Set();
|
|
8487
9609
|
const results = [];
|
|
8488
9610
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8496,44 +9618,51 @@ var Indexer = class {
|
|
|
8496
9618
|
return results;
|
|
8497
9619
|
}
|
|
8498
9620
|
async findCallPath(fromName, toName, maxDepth) {
|
|
8499
|
-
const { database } = await this.ensureInitialized();
|
|
9621
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9622
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8500
9623
|
let shortest = [];
|
|
8501
9624
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
8502
|
-
const
|
|
8503
|
-
if (
|
|
8504
|
-
shortest =
|
|
9625
|
+
const path17 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
|
|
9626
|
+
if (path17.length > 0 && (shortest.length === 0 || path17.length < shortest.length)) {
|
|
9627
|
+
shortest = path17;
|
|
8505
9628
|
}
|
|
8506
9629
|
}
|
|
8507
9630
|
return shortest;
|
|
8508
9631
|
}
|
|
8509
9632
|
async getSymbolsForBranch(branch) {
|
|
8510
|
-
const { database } = await this.ensureInitialized();
|
|
9633
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9634
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8511
9635
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8512
9636
|
return database.getSymbolsForBranch(resolvedBranch);
|
|
8513
9637
|
}
|
|
8514
9638
|
async getSymbolsForFiles(filePaths, branch) {
|
|
8515
|
-
const { database } = await this.ensureInitialized();
|
|
9639
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9640
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8516
9641
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8517
9642
|
return database.getSymbolsForFiles(filePaths, resolvedBranch);
|
|
8518
9643
|
}
|
|
8519
9644
|
async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
|
|
8520
|
-
const { database } = await this.ensureInitialized();
|
|
9645
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9646
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8521
9647
|
const branch = this.getBranchCatalogKey();
|
|
8522
9648
|
return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
|
|
8523
9649
|
}
|
|
8524
9650
|
async detectCommunities(branch, symbolIds) {
|
|
8525
|
-
const { database } = await this.ensureInitialized();
|
|
9651
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9652
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8526
9653
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8527
9654
|
return database.detectCommunities(resolvedBranch, symbolIds);
|
|
8528
9655
|
}
|
|
8529
9656
|
async computeCentrality(branch) {
|
|
8530
|
-
const { database } = await this.ensureInitialized();
|
|
9657
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9658
|
+
this.requireReadableComponents(readIssues, "database");
|
|
8531
9659
|
const resolvedBranch = branch ?? this.getBranchCatalogKey();
|
|
8532
9660
|
return database.computeCentrality(resolvedBranch);
|
|
8533
9661
|
}
|
|
8534
9662
|
async getPrImpact(opts) {
|
|
8535
|
-
const { database } = await this.ensureInitialized();
|
|
8536
|
-
|
|
9663
|
+
const { database, readIssues } = await this.ensureInitialized();
|
|
9664
|
+
this.requireReadableComponents(readIssues, "database");
|
|
9665
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
8537
9666
|
const changedFilesResult = await getChangedFiles({
|
|
8538
9667
|
pr: opts.pr,
|
|
8539
9668
|
branch: opts.branch,
|
|
@@ -8555,7 +9684,7 @@ var Indexer = class {
|
|
|
8555
9684
|
"Run index_codebase first to build the call graph and symbol index for this branch."
|
|
8556
9685
|
);
|
|
8557
9686
|
}
|
|
8558
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
9687
|
+
const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
|
|
8559
9688
|
const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
|
|
8560
9689
|
const directIds = directSymbols.map((s) => s.id);
|
|
8561
9690
|
const direction = opts.direction ?? "both";
|
|
@@ -8617,7 +9746,7 @@ var Indexer = class {
|
|
|
8617
9746
|
if (opts.checkConflicts) {
|
|
8618
9747
|
conflictingPRs = [];
|
|
8619
9748
|
try {
|
|
8620
|
-
const { stdout } = await
|
|
9749
|
+
const { stdout } = await execFileAsync3(
|
|
8621
9750
|
"gh",
|
|
8622
9751
|
["pr", "list", "--state", "open", "--json", "number,headRefName", "--limit", "10000"],
|
|
8623
9752
|
{ cwd: this.projectRoot, timeout: 3e4 }
|
|
@@ -8638,7 +9767,7 @@ var Indexer = class {
|
|
|
8638
9767
|
projectRoot: this.projectRoot,
|
|
8639
9768
|
baseBranch: this.baseBranch
|
|
8640
9769
|
});
|
|
8641
|
-
const otherAbsolute = otherChanged.files.map((f) =>
|
|
9770
|
+
const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
|
|
8642
9771
|
const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
|
|
8643
9772
|
const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
|
|
8644
9773
|
const otherLabels = /* @__PURE__ */ new Set();
|
|
@@ -8688,7 +9817,8 @@ var Indexer = class {
|
|
|
8688
9817
|
};
|
|
8689
9818
|
}
|
|
8690
9819
|
async getVisualizationData(options) {
|
|
8691
|
-
const { database, store } = await this.ensureInitialized();
|
|
9820
|
+
const { database, store, readIssues } = await this.ensureInitialized();
|
|
9821
|
+
this.requireReadableComponents(readIssues, "vectors", "database");
|
|
8692
9822
|
const seenSymbols = /* @__PURE__ */ new Map();
|
|
8693
9823
|
const seenEdges = /* @__PURE__ */ new Map();
|
|
8694
9824
|
for (const branchKey of this.getBranchCatalogKeys()) {
|
|
@@ -8701,12 +9831,12 @@ var Indexer = class {
|
|
|
8701
9831
|
if (meta.filePath) filePaths.add(meta.filePath);
|
|
8702
9832
|
}
|
|
8703
9833
|
const directory = options?.directory?.replace(/\/$/, "");
|
|
8704
|
-
const absoluteDirectoryFilter = directory ?
|
|
9834
|
+
const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
|
|
8705
9835
|
for (const filePath of filePaths) {
|
|
8706
9836
|
if (directory) {
|
|
8707
|
-
const absoluteFilePath =
|
|
9837
|
+
const absoluteFilePath = path13.resolve(filePath);
|
|
8708
9838
|
const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
|
|
8709
|
-
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter +
|
|
9839
|
+
const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
|
|
8710
9840
|
if (!matchesRelative && !matchesProjectRelative) {
|
|
8711
9841
|
continue;
|
|
8712
9842
|
}
|
|
@@ -8728,53 +9858,64 @@ var Indexer = class {
|
|
|
8728
9858
|
return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
|
|
8729
9859
|
}
|
|
8730
9860
|
async close() {
|
|
8731
|
-
|
|
9861
|
+
this.database?.close();
|
|
9862
|
+
for (const database of this.retiredDatabases) {
|
|
9863
|
+
database.close();
|
|
9864
|
+
}
|
|
9865
|
+
this.retiredDatabases = [];
|
|
8732
9866
|
this.database = null;
|
|
8733
9867
|
this.store = null;
|
|
8734
9868
|
this.invertedIndex = null;
|
|
8735
9869
|
this.provider = null;
|
|
8736
9870
|
this.reranker = null;
|
|
9871
|
+
this.configuredProviderInfo = null;
|
|
9872
|
+
this.indexCompatibility = null;
|
|
9873
|
+
this.initializationMode = "none";
|
|
9874
|
+
this.readIssues = [];
|
|
9875
|
+
this.readerArtifactFingerprint = null;
|
|
9876
|
+
this.writerArtifactFingerprint = null;
|
|
9877
|
+
this.readerArtifactRetryAfter.clear();
|
|
8737
9878
|
}
|
|
8738
9879
|
};
|
|
8739
9880
|
|
|
8740
9881
|
// src/tools/knowledge-base-paths.ts
|
|
8741
|
-
import * as
|
|
9882
|
+
import * as path14 from "path";
|
|
8742
9883
|
function resolveConfigPathValue(value, baseDir) {
|
|
8743
9884
|
const trimmed = value.trim();
|
|
8744
9885
|
if (!trimmed) {
|
|
8745
9886
|
return trimmed;
|
|
8746
9887
|
}
|
|
8747
|
-
const absolutePath =
|
|
8748
|
-
return
|
|
9888
|
+
const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
|
|
9889
|
+
return path14.normalize(absolutePath);
|
|
8749
9890
|
}
|
|
8750
9891
|
function serializeConfigPathValue(value, baseDir) {
|
|
8751
9892
|
const trimmed = value.trim();
|
|
8752
9893
|
if (!trimmed) {
|
|
8753
9894
|
return trimmed;
|
|
8754
9895
|
}
|
|
8755
|
-
if (!
|
|
8756
|
-
return normalizePathSeparators(
|
|
9896
|
+
if (!path14.isAbsolute(trimmed)) {
|
|
9897
|
+
return normalizePathSeparators(path14.normalize(trimmed));
|
|
8757
9898
|
}
|
|
8758
|
-
const relativePath =
|
|
8759
|
-
if (!relativePath || !relativePath.startsWith("..") && !
|
|
8760
|
-
return normalizePathSeparators(
|
|
9899
|
+
const relativePath = path14.relative(baseDir, trimmed);
|
|
9900
|
+
if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
|
|
9901
|
+
return normalizePathSeparators(path14.normalize(relativePath || "."));
|
|
8761
9902
|
}
|
|
8762
|
-
return
|
|
9903
|
+
return path14.normalize(trimmed);
|
|
8763
9904
|
}
|
|
8764
|
-
function resolveKnowledgeBasePath(value,
|
|
8765
|
-
return
|
|
9905
|
+
function resolveKnowledgeBasePath(value, projectRoot3) {
|
|
9906
|
+
return path14.isAbsolute(value) ? value : path14.resolve(projectRoot3, value);
|
|
8766
9907
|
}
|
|
8767
|
-
function normalizeKnowledgeBasePath2(value,
|
|
8768
|
-
return
|
|
9908
|
+
function normalizeKnowledgeBasePath2(value, projectRoot3) {
|
|
9909
|
+
return path14.normalize(resolveKnowledgeBasePath(value, projectRoot3));
|
|
8769
9910
|
}
|
|
8770
|
-
function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath,
|
|
8771
|
-
const normalizedInput =
|
|
8772
|
-
return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb,
|
|
9911
|
+
function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
|
|
9912
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
9913
|
+
return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
|
|
8773
9914
|
}
|
|
8774
|
-
function findKnowledgeBasePathIndex(knowledgeBases, inputPath,
|
|
8775
|
-
const normalizedInput =
|
|
9915
|
+
function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
|
|
9916
|
+
const normalizedInput = path14.normalize(inputPath);
|
|
8776
9917
|
return knowledgeBases.findIndex(
|
|
8777
|
-
(kb) =>
|
|
9918
|
+
(kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
|
|
8778
9919
|
);
|
|
8779
9920
|
}
|
|
8780
9921
|
|
|
@@ -8874,6 +10015,10 @@ function formatStatus(status) {
|
|
|
8874
10015
|
lines.push(`Failed batches: ${status.failedBatchesPath}`);
|
|
8875
10016
|
}
|
|
8876
10017
|
}
|
|
10018
|
+
if (status.warning) {
|
|
10019
|
+
lines.push("");
|
|
10020
|
+
lines.push(`INDEX WARNING: ${status.warning}`);
|
|
10021
|
+
}
|
|
8877
10022
|
if (status.compatibility && !status.compatibility.compatible) {
|
|
8878
10023
|
lines.push("");
|
|
8879
10024
|
lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
|
|
@@ -8947,16 +10092,57 @@ function formatHealthCheck(result) {
|
|
|
8947
10092
|
}
|
|
8948
10093
|
return lines.join("\n");
|
|
8949
10094
|
}
|
|
10095
|
+
function formatCallGraphCallers(name, callers, relationshipType) {
|
|
10096
|
+
if (callers.length === 0) {
|
|
10097
|
+
return `No callers found for "${name}"${relationshipType ? ` with type ${relationshipType}` : ""}. It may not be called by any tracked function, or the index needs updating.`;
|
|
10098
|
+
}
|
|
10099
|
+
const formatted = callers.map((edge, index) => {
|
|
10100
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
10101
|
+
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]"}`;
|
|
10102
|
+
});
|
|
10103
|
+
return `"${name}" is called by ${callers.length} function(s):
|
|
10104
|
+
|
|
10105
|
+
${formatted.join("\n")}`;
|
|
10106
|
+
}
|
|
10107
|
+
function formatCallGraphCallees(symbolId, callees, relationshipType) {
|
|
10108
|
+
if (callees.length === 0) {
|
|
10109
|
+
return `No callees found for symbol ${symbolId}${relationshipType ? ` with type ${relationshipType}` : ""}. The function may not call any other tracked functions.`;
|
|
10110
|
+
}
|
|
10111
|
+
return callees.map((edge, index) => {
|
|
10112
|
+
const confidence = edge.confidence !== "Direct" ? ` [${edge.confidence.toLowerCase()}]` : "";
|
|
10113
|
+
return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
|
|
10114
|
+
}).join("\n");
|
|
10115
|
+
}
|
|
10116
|
+
function formatCallGraphPath(from, to, path17) {
|
|
10117
|
+
if (path17.length === 0) {
|
|
10118
|
+
return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
|
|
10119
|
+
}
|
|
10120
|
+
const formatted = path17.map((hop, index) => {
|
|
10121
|
+
const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
|
|
10122
|
+
const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
|
|
10123
|
+
return `${prefix} ${hop.symbolName}${location}`;
|
|
10124
|
+
});
|
|
10125
|
+
return `Path (${path17.length} hops):
|
|
10126
|
+
${formatted.join("\n")}`;
|
|
10127
|
+
}
|
|
8950
10128
|
function formatResultHeader(result, index) {
|
|
8951
10129
|
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}`;
|
|
8952
10130
|
}
|
|
10131
|
+
function formatBlame(result) {
|
|
10132
|
+
if (!result.blame) {
|
|
10133
|
+
return "";
|
|
10134
|
+
}
|
|
10135
|
+
const date = new Date(result.blame.committedAt * 1e3).toISOString().slice(0, 10);
|
|
10136
|
+
return `
|
|
10137
|
+
${result.blame.sha.slice(0, 7)} | ${result.blame.author} | ${date} | ${result.blame.summary}`;
|
|
10138
|
+
}
|
|
8953
10139
|
function formatDefinitionLookup(results, query) {
|
|
8954
10140
|
if (results.length === 0) {
|
|
8955
10141
|
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
8956
10142
|
}
|
|
8957
10143
|
const formatted = results.map((r, idx) => {
|
|
8958
10144
|
const header = formatResultHeader(r, idx);
|
|
8959
|
-
return `${header} (score: ${r.score.toFixed(2)})
|
|
10145
|
+
return `${header} (score: ${r.score.toFixed(2)})${formatBlame(r)}
|
|
8960
10146
|
\`\`\`
|
|
8961
10147
|
${truncateContent(r.content)}
|
|
8962
10148
|
\`\`\``;
|
|
@@ -8967,7 +10153,7 @@ function formatSearchResults(results, scoreFormat = "similarity") {
|
|
|
8967
10153
|
const formatted = results.map((r, idx) => {
|
|
8968
10154
|
const header = formatResultHeader(r, idx);
|
|
8969
10155
|
const scoreLabel = scoreFormat === "similarity" ? `(similarity: ${(r.score * 100).toFixed(1)}%)` : `(score: ${r.score.toFixed(2)})`;
|
|
8970
|
-
return `${header} ${scoreLabel}
|
|
10156
|
+
return `${header} ${scoreLabel}${formatBlame(r)}
|
|
8971
10157
|
\`\`\`
|
|
8972
10158
|
${truncateContent(r.content)}
|
|
8973
10159
|
\`\`\``;
|
|
@@ -8976,13 +10162,13 @@ ${truncateContent(r.content)}
|
|
|
8976
10162
|
}
|
|
8977
10163
|
|
|
8978
10164
|
// src/tools/config-state.ts
|
|
8979
|
-
import { existsSync as
|
|
8980
|
-
import * as
|
|
8981
|
-
function normalizeKnowledgeBasePaths(config,
|
|
10165
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
10166
|
+
import * as path15 from "path";
|
|
10167
|
+
function normalizeKnowledgeBasePaths(config, projectRoot3) {
|
|
8982
10168
|
const normalized = { ...config };
|
|
8983
10169
|
if (Array.isArray(normalized.knowledgeBases)) {
|
|
8984
10170
|
normalized.knowledgeBases = normalized.knowledgeBases.map(
|
|
8985
|
-
(kb) => resolveConfigPathValue(kb,
|
|
10171
|
+
(kb) => resolveConfigPathValue(kb, projectRoot3)
|
|
8986
10172
|
);
|
|
8987
10173
|
}
|
|
8988
10174
|
return normalized;
|
|
@@ -8993,21 +10179,21 @@ function toConfigRecord(rawConfig) {
|
|
|
8993
10179
|
}
|
|
8994
10180
|
return { ...rawConfig };
|
|
8995
10181
|
}
|
|
8996
|
-
function getConfigPath(
|
|
8997
|
-
return resolveWritableProjectConfigPath(
|
|
10182
|
+
function getConfigPath(projectRoot3, host = "opencode") {
|
|
10183
|
+
return resolveWritableProjectConfigPath(projectRoot3, host);
|
|
8998
10184
|
}
|
|
8999
|
-
function loadRuntimeConfig(
|
|
9000
|
-
return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(
|
|
10185
|
+
function loadRuntimeConfig(projectRoot3, host = "opencode") {
|
|
10186
|
+
return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot3, host)), projectRoot3);
|
|
9001
10187
|
}
|
|
9002
|
-
function loadEditableConfig(
|
|
9003
|
-
return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(
|
|
10188
|
+
function loadEditableConfig(projectRoot3, host = "opencode") {
|
|
10189
|
+
return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot3, host)), projectRoot3);
|
|
9004
10190
|
}
|
|
9005
|
-
function saveConfig(
|
|
9006
|
-
const configPath = getConfigPath(
|
|
9007
|
-
const configDir =
|
|
9008
|
-
const configBaseDir =
|
|
9009
|
-
if (!
|
|
9010
|
-
|
|
10191
|
+
function saveConfig(projectRoot3, config, host = "opencode") {
|
|
10192
|
+
const configPath = getConfigPath(projectRoot3, host);
|
|
10193
|
+
const configDir = path15.dirname(configPath);
|
|
10194
|
+
const configBaseDir = path15.dirname(configDir);
|
|
10195
|
+
if (!existsSync9(configDir)) {
|
|
10196
|
+
mkdirSync4(configDir, { recursive: true });
|
|
9011
10197
|
}
|
|
9012
10198
|
const serializableConfig = { ...config };
|
|
9013
10199
|
if (Array.isArray(serializableConfig.knowledgeBases)) {
|
|
@@ -9015,16 +10201,34 @@ function saveConfig(projectRoot2, config, host = "opencode") {
|
|
|
9015
10201
|
(kb) => serializeConfigPathValue(kb, configBaseDir)
|
|
9016
10202
|
);
|
|
9017
10203
|
}
|
|
9018
|
-
|
|
10204
|
+
writeFileSync4(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
|
|
9019
10205
|
}
|
|
9020
10206
|
|
|
9021
10207
|
// src/tools/operations.ts
|
|
9022
10208
|
var indexerCache = /* @__PURE__ */ new Map();
|
|
9023
10209
|
var configCache = /* @__PURE__ */ new Map();
|
|
9024
10210
|
var defaultProjectRoots = /* @__PURE__ */ new Map();
|
|
9025
|
-
function
|
|
9026
|
-
if (
|
|
9027
|
-
|
|
10211
|
+
function getIndexBusyResult(error) {
|
|
10212
|
+
if (!isIndexLockContentionError(error)) return null;
|
|
10213
|
+
const owner = error.owner;
|
|
10214
|
+
const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
|
|
10215
|
+
if (error.reason === "legacy-lock") {
|
|
10216
|
+
return {
|
|
10217
|
+
kind: "busy",
|
|
10218
|
+
text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
|
|
10219
|
+
};
|
|
10220
|
+
}
|
|
10221
|
+
if (error.reason === "unknown-owner") {
|
|
10222
|
+
return {
|
|
10223
|
+
kind: "busy",
|
|
10224
|
+
text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
|
|
10225
|
+
};
|
|
10226
|
+
}
|
|
10227
|
+
return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
|
|
10228
|
+
}
|
|
10229
|
+
function getProjectRoot(projectRoot3, host) {
|
|
10230
|
+
if (projectRoot3) {
|
|
10231
|
+
return projectRoot3;
|
|
9028
10232
|
}
|
|
9029
10233
|
const root = defaultProjectRoots.get(host);
|
|
9030
10234
|
if (!root) {
|
|
@@ -9032,53 +10236,56 @@ function getProjectRoot(projectRoot2, host) {
|
|
|
9032
10236
|
}
|
|
9033
10237
|
return root;
|
|
9034
10238
|
}
|
|
9035
|
-
function getIndexerCacheKey(
|
|
9036
|
-
return `${host}::${
|
|
10239
|
+
function getIndexerCacheKey(projectRoot3, host) {
|
|
10240
|
+
return `${host}::${projectRoot3}`;
|
|
9037
10241
|
}
|
|
9038
|
-
function getOrCreateIndexer(
|
|
9039
|
-
const key = getIndexerCacheKey(
|
|
10242
|
+
function getOrCreateIndexer(projectRoot3, host) {
|
|
10243
|
+
const key = getIndexerCacheKey(projectRoot3, host);
|
|
9040
10244
|
const cached = indexerCache.get(key);
|
|
9041
10245
|
if (cached) {
|
|
9042
10246
|
return cached;
|
|
9043
10247
|
}
|
|
9044
|
-
const config = parseConfig(loadRuntimeConfig(
|
|
9045
|
-
const indexer = new Indexer(
|
|
10248
|
+
const config = parseConfig(loadRuntimeConfig(projectRoot3, host));
|
|
10249
|
+
const indexer = new Indexer(projectRoot3, config, host);
|
|
9046
10250
|
indexerCache.set(key, indexer);
|
|
9047
10251
|
configCache.set(key, config);
|
|
9048
10252
|
return indexer;
|
|
9049
10253
|
}
|
|
9050
|
-
function getIndexerForProject(
|
|
9051
|
-
const root = getProjectRoot(
|
|
10254
|
+
function getIndexerForProject(projectRoot3, host = "opencode") {
|
|
10255
|
+
const root = getProjectRoot(projectRoot3, host);
|
|
9052
10256
|
return getOrCreateIndexer(root, host);
|
|
9053
10257
|
}
|
|
9054
|
-
function refreshIndexerForDirectory(
|
|
9055
|
-
const key = getIndexerCacheKey(
|
|
9056
|
-
const indexer = new Indexer(
|
|
10258
|
+
function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot3, host))) {
|
|
10259
|
+
const key = getIndexerCacheKey(projectRoot3, host);
|
|
10260
|
+
const indexer = new Indexer(projectRoot3, config, host);
|
|
9057
10261
|
indexerCache.set(key, indexer);
|
|
9058
10262
|
configCache.set(key, config);
|
|
9059
10263
|
}
|
|
9060
|
-
function shouldForceLocalizeProjectIndex(
|
|
9061
|
-
const root = getProjectRoot(
|
|
9062
|
-
const localIndexPath =
|
|
9063
|
-
if (
|
|
10264
|
+
function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
|
|
10265
|
+
const root = getProjectRoot(projectRoot3, host);
|
|
10266
|
+
const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
|
|
10267
|
+
if (existsSync10(localIndexPath)) {
|
|
9064
10268
|
return false;
|
|
9065
10269
|
}
|
|
9066
10270
|
const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
|
|
9067
10271
|
return inheritedIndexPath !== null;
|
|
9068
10272
|
}
|
|
9069
|
-
async function searchCodebase(
|
|
9070
|
-
const indexer = getIndexerForProject(
|
|
10273
|
+
async function searchCodebase(projectRoot3, host, query, options = {}) {
|
|
10274
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9071
10275
|
return indexer.search(query, options.limit, {
|
|
9072
10276
|
fileType: options.fileType,
|
|
9073
10277
|
directory: options.directory,
|
|
9074
10278
|
chunkType: options.chunkType,
|
|
9075
10279
|
contextLines: options.contextLines,
|
|
9076
10280
|
metadataOnly: options.metadataOnly,
|
|
9077
|
-
definitionIntent: options.definitionIntent
|
|
10281
|
+
definitionIntent: options.definitionIntent,
|
|
10282
|
+
blameAuthor: options.blameAuthor,
|
|
10283
|
+
blameSha: options.blameSha,
|
|
10284
|
+
blameSince: options.blameSince
|
|
9078
10285
|
});
|
|
9079
10286
|
}
|
|
9080
|
-
async function findSimilarCode(
|
|
9081
|
-
const indexer = getIndexerForProject(
|
|
10287
|
+
async function findSimilarCode(projectRoot3, host, code, options = {}) {
|
|
10288
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9082
10289
|
return indexer.findSimilar(code, options.limit, {
|
|
9083
10290
|
fileType: options.fileType,
|
|
9084
10291
|
directory: options.directory,
|
|
@@ -9086,16 +10293,16 @@ async function findSimilarCode(projectRoot2, host, code, options = {}) {
|
|
|
9086
10293
|
excludeFile: options.excludeFile
|
|
9087
10294
|
});
|
|
9088
10295
|
}
|
|
9089
|
-
async function implementationLookup(
|
|
9090
|
-
const indexer = getIndexerForProject(
|
|
10296
|
+
async function implementationLookup(projectRoot3, host, query, options = {}) {
|
|
10297
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9091
10298
|
return indexer.search(query, options.limit, {
|
|
9092
10299
|
fileType: options.fileType,
|
|
9093
10300
|
directory: options.directory,
|
|
9094
10301
|
definitionIntent: true
|
|
9095
10302
|
});
|
|
9096
10303
|
}
|
|
9097
|
-
async function getCallGraphData(
|
|
9098
|
-
const indexer = getIndexerForProject(
|
|
10304
|
+
async function getCallGraphData(projectRoot3, host, params) {
|
|
10305
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9099
10306
|
if (params.direction === "callees") {
|
|
9100
10307
|
if (!params.symbolId) {
|
|
9101
10308
|
return { direction: "callees", callees: [], callers: [] };
|
|
@@ -9106,53 +10313,73 @@ async function getCallGraphData(projectRoot2, host, params) {
|
|
|
9106
10313
|
const callers = await indexer.getCallers(params.name, params.relationshipType);
|
|
9107
10314
|
return { direction: "callers", callers, callees: [] };
|
|
9108
10315
|
}
|
|
9109
|
-
async function getCallGraphPath(
|
|
9110
|
-
const indexer = getIndexerForProject(
|
|
10316
|
+
async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
|
|
10317
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9111
10318
|
return indexer.findCallPath(from, to, maxDepth);
|
|
9112
10319
|
}
|
|
9113
|
-
async function runIndexCodebase(
|
|
9114
|
-
const root = getProjectRoot(
|
|
10320
|
+
async function runIndexCodebase(projectRoot3, host, args, onProgress) {
|
|
10321
|
+
const root = getProjectRoot(projectRoot3, host);
|
|
9115
10322
|
const key = getIndexerCacheKey(root, host);
|
|
9116
|
-
const cachedConfig = configCache.get(key);
|
|
9117
10323
|
let indexer = getIndexerForProject(root, host);
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
chunksProcessed: progress.chunksProcessed,
|
|
9138
|
-
totalChunks: progress.totalChunks,
|
|
9139
|
-
percentage: calculatePercentage(progress)
|
|
10324
|
+
const runtimeConfig = configCache.get(key);
|
|
10325
|
+
try {
|
|
10326
|
+
if (args.estimateOnly) {
|
|
10327
|
+
return { kind: "estimate", estimate: await indexer.estimateCost() };
|
|
10328
|
+
}
|
|
10329
|
+
const runIndex = async (target) => {
|
|
10330
|
+
const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
|
|
10331
|
+
return operation((progress) => {
|
|
10332
|
+
if (onProgress) {
|
|
10333
|
+
return onProgress(formatProgressTitle(progress), {
|
|
10334
|
+
phase: progress.phase,
|
|
10335
|
+
filesProcessed: progress.filesProcessed,
|
|
10336
|
+
totalFiles: progress.totalFiles,
|
|
10337
|
+
chunksProcessed: progress.chunksProcessed,
|
|
10338
|
+
totalChunks: progress.totalChunks,
|
|
10339
|
+
percentage: calculatePercentage(progress)
|
|
10340
|
+
});
|
|
10341
|
+
}
|
|
10342
|
+
return Promise.resolve();
|
|
9140
10343
|
});
|
|
10344
|
+
};
|
|
10345
|
+
let stats;
|
|
10346
|
+
if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
|
|
10347
|
+
const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
|
|
10348
|
+
stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
|
|
10349
|
+
materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
|
|
10350
|
+
refreshIndexerForDirectory(root, host, runtimeConfig);
|
|
10351
|
+
indexer = getIndexerForProject(root, host);
|
|
10352
|
+
return runIndex(indexer);
|
|
10353
|
+
}, { completeRecoveries: false });
|
|
10354
|
+
} else {
|
|
10355
|
+
stats = await runIndex(indexer);
|
|
9141
10356
|
}
|
|
9142
|
-
return
|
|
9143
|
-
})
|
|
9144
|
-
|
|
10357
|
+
return { kind: "stats", stats };
|
|
10358
|
+
} catch (error) {
|
|
10359
|
+
const busyResult = getIndexBusyResult(error);
|
|
10360
|
+
if (!busyResult) throw error;
|
|
10361
|
+
return busyResult;
|
|
10362
|
+
}
|
|
9145
10363
|
}
|
|
9146
|
-
async function getIndexStatus(
|
|
9147
|
-
const indexer = getIndexerForProject(
|
|
10364
|
+
async function getIndexStatus(projectRoot3, host) {
|
|
10365
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9148
10366
|
return indexer.getStatus();
|
|
9149
10367
|
}
|
|
9150
|
-
async function getIndexHealthCheck(
|
|
9151
|
-
const indexer = getIndexerForProject(
|
|
10368
|
+
async function getIndexHealthCheck(projectRoot3, host) {
|
|
10369
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9152
10370
|
return indexer.healthCheck();
|
|
9153
10371
|
}
|
|
9154
|
-
async function
|
|
9155
|
-
|
|
10372
|
+
async function runIndexHealthCheck(projectRoot3, host) {
|
|
10373
|
+
try {
|
|
10374
|
+
return { kind: "health", health: await getIndexHealthCheck(projectRoot3, host) };
|
|
10375
|
+
} catch (error) {
|
|
10376
|
+
const busyResult = getIndexBusyResult(error);
|
|
10377
|
+
if (!busyResult) throw error;
|
|
10378
|
+
return busyResult;
|
|
10379
|
+
}
|
|
10380
|
+
}
|
|
10381
|
+
async function getPrImpact(projectRoot3, host, params) {
|
|
10382
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9156
10383
|
return indexer.getPrImpact({
|
|
9157
10384
|
pr: params.pr,
|
|
9158
10385
|
branch: params.branch,
|
|
@@ -9162,8 +10389,8 @@ async function getPrImpact(projectRoot2, host, params) {
|
|
|
9162
10389
|
direction: params.direction
|
|
9163
10390
|
});
|
|
9164
10391
|
}
|
|
9165
|
-
async function getIndexMetrics(
|
|
9166
|
-
const indexer = getIndexerForProject(
|
|
10392
|
+
async function getIndexMetrics(projectRoot3, host) {
|
|
10393
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9167
10394
|
const logger = indexer.getLogger();
|
|
9168
10395
|
if (!logger.isEnabled()) {
|
|
9169
10396
|
return {
|
|
@@ -9185,8 +10412,8 @@ async function getIndexMetrics(projectRoot2, host) {
|
|
|
9185
10412
|
text: logger.formatMetrics()
|
|
9186
10413
|
};
|
|
9187
10414
|
}
|
|
9188
|
-
async function getIndexLogs(
|
|
9189
|
-
const indexer = getIndexerForProject(
|
|
10415
|
+
async function getIndexLogs(projectRoot3, host, args) {
|
|
10416
|
+
const indexer = getIndexerForProject(projectRoot3, host);
|
|
9190
10417
|
const logger = indexer.getLogger();
|
|
9191
10418
|
if (!logger.isEnabled()) {
|
|
9192
10419
|
return {
|
|
@@ -9208,24 +10435,24 @@ async function getIndexLogs(projectRoot2, host, args) {
|
|
|
9208
10435
|
text: "No logs recorded yet. Logs are captured during indexing and search operations."
|
|
9209
10436
|
};
|
|
9210
10437
|
}
|
|
9211
|
-
const
|
|
10438
|
+
const text3 = logs.map((entry) => {
|
|
9212
10439
|
const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
|
|
9213
10440
|
return `[${entry.timestamp}] [${entry.level.toUpperCase()}] [${entry.category}] ${entry.message}${dataStr}`;
|
|
9214
10441
|
}).join("\n");
|
|
9215
|
-
return { kind: "entries", text:
|
|
10442
|
+
return { kind: "entries", text: text3 };
|
|
9216
10443
|
}
|
|
9217
|
-
function addKnowledgeBase(
|
|
9218
|
-
const root = getProjectRoot(
|
|
10444
|
+
function addKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
|
|
10445
|
+
const root = getProjectRoot(projectRoot3, host);
|
|
9219
10446
|
const inputPath = knowledgeBasePath.trim();
|
|
9220
|
-
const normalizedPath =
|
|
9221
|
-
|
|
10447
|
+
const normalizedPath = path16.resolve(
|
|
10448
|
+
path16.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
|
|
9222
10449
|
);
|
|
9223
|
-
if (!
|
|
10450
|
+
if (!existsSync10(normalizedPath)) {
|
|
9224
10451
|
return `Error: Directory does not exist: ${normalizedPath}`;
|
|
9225
10452
|
}
|
|
9226
10453
|
let realPath;
|
|
9227
10454
|
try {
|
|
9228
|
-
realPath =
|
|
10455
|
+
realPath = realpathSync2(normalizedPath);
|
|
9229
10456
|
} catch {
|
|
9230
10457
|
return `Error: Cannot resolve path: ${normalizedPath}`;
|
|
9231
10458
|
}
|
|
@@ -9254,13 +10481,13 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
|
|
|
9254
10481
|
}
|
|
9255
10482
|
}
|
|
9256
10483
|
for (const dotDir of sensitiveDotDirs) {
|
|
9257
|
-
const sensitiveDir =
|
|
10484
|
+
const sensitiveDir = path16.join(homeDir, dotDir);
|
|
9258
10485
|
if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
|
|
9259
10486
|
return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath}`;
|
|
9260
10487
|
}
|
|
9261
10488
|
}
|
|
9262
10489
|
try {
|
|
9263
|
-
const stat =
|
|
10490
|
+
const stat = statSync4(normalizedPath);
|
|
9264
10491
|
if (!stat.isDirectory()) {
|
|
9265
10492
|
return `Error: Path is not a directory: ${normalizedPath}`;
|
|
9266
10493
|
}
|
|
@@ -9287,8 +10514,8 @@ function addKnowledgeBase(projectRoot2, host, knowledgeBasePath) {
|
|
|
9287
10514
|
Run /index to rebuild the index with the new knowledge base.`;
|
|
9288
10515
|
return result;
|
|
9289
10516
|
}
|
|
9290
|
-
function listKnowledgeBases(
|
|
9291
|
-
const root = getProjectRoot(
|
|
10517
|
+
function listKnowledgeBases(projectRoot3, host) {
|
|
10518
|
+
const root = getProjectRoot(projectRoot3, host);
|
|
9292
10519
|
const config = loadRuntimeConfig(root, host);
|
|
9293
10520
|
const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
|
|
9294
10521
|
if (knowledgeBases.length === 0) {
|
|
@@ -9300,7 +10527,7 @@ function listKnowledgeBases(projectRoot2, host) {
|
|
|
9300
10527
|
for (let i = 0; i < knowledgeBases.length; i++) {
|
|
9301
10528
|
const kb = knowledgeBases[i];
|
|
9302
10529
|
const resolvedPath = resolveKnowledgeBasePath(kb, root);
|
|
9303
|
-
const exists =
|
|
10530
|
+
const exists = existsSync10(resolvedPath);
|
|
9304
10531
|
result += `[${i + 1}] ${kb}
|
|
9305
10532
|
`;
|
|
9306
10533
|
result += ` Resolved: ${resolvedPath}
|
|
@@ -9309,7 +10536,7 @@ function listKnowledgeBases(projectRoot2, host) {
|
|
|
9309
10536
|
`;
|
|
9310
10537
|
if (exists) {
|
|
9311
10538
|
try {
|
|
9312
|
-
const stat =
|
|
10539
|
+
const stat = statSync4(resolvedPath);
|
|
9313
10540
|
result += ` Type: ${stat.isDirectory() ? "Directory" : "File"}
|
|
9314
10541
|
`;
|
|
9315
10542
|
} catch {
|
|
@@ -9317,7 +10544,7 @@ function listKnowledgeBases(projectRoot2, host) {
|
|
|
9317
10544
|
}
|
|
9318
10545
|
result += "\n";
|
|
9319
10546
|
}
|
|
9320
|
-
const hasHostConfig =
|
|
10547
|
+
const hasHostConfig = existsSync10(path16.join(root, getHostProjectConfigRelativePath(host)));
|
|
9321
10548
|
if (hasHostConfig) {
|
|
9322
10549
|
result += `
|
|
9323
10550
|
Config sources: 1 file(s).`;
|
|
@@ -9326,8 +10553,8 @@ Config sources: 1 file(s).`;
|
|
|
9326
10553
|
Config file: ${getConfigPath(root, host)}`;
|
|
9327
10554
|
return result;
|
|
9328
10555
|
}
|
|
9329
|
-
function removeKnowledgeBase(
|
|
9330
|
-
const root = getProjectRoot(
|
|
10556
|
+
function removeKnowledgeBase(projectRoot3, host, knowledgeBasePath) {
|
|
10557
|
+
const root = getProjectRoot(projectRoot3, host);
|
|
9331
10558
|
const config = loadEditableConfig(root, host);
|
|
9332
10559
|
const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
|
|
9333
10560
|
const index = findKnowledgeBasePathIndex(knowledgeBases, knowledgeBasePath, root);
|
|
@@ -9350,17 +10577,9 @@ Run /index to rebuild the index without the removed knowledge base.`;
|
|
|
9350
10577
|
return result;
|
|
9351
10578
|
}
|
|
9352
10579
|
|
|
9353
|
-
// src/pi-
|
|
10580
|
+
// src/pi-call-graph.ts
|
|
10581
|
+
import { Type } from "typebox";
|
|
9354
10582
|
var HOST = "pi";
|
|
9355
|
-
var ChunkType = Type.Union([
|
|
9356
|
-
Type.Literal("function"),
|
|
9357
|
-
Type.Literal("class"),
|
|
9358
|
-
Type.Literal("method"),
|
|
9359
|
-
Type.Literal("interface"),
|
|
9360
|
-
Type.Literal("type"),
|
|
9361
|
-
Type.Literal("module"),
|
|
9362
|
-
Type.Literal("block")
|
|
9363
|
-
]);
|
|
9364
10583
|
var RelationshipType = Type.Union([
|
|
9365
10584
|
Type.Literal("Call"),
|
|
9366
10585
|
Type.Literal("MethodCall"),
|
|
@@ -9369,214 +10588,250 @@ var RelationshipType = Type.Union([
|
|
|
9369
10588
|
Type.Literal("Inherits"),
|
|
9370
10589
|
Type.Literal("Implements")
|
|
9371
10590
|
]);
|
|
9372
|
-
function text(
|
|
9373
|
-
return { content: [{ type: "text", text:
|
|
10591
|
+
function text(text3, details) {
|
|
10592
|
+
return { content: [{ type: "text", text: text3 }], details };
|
|
9374
10593
|
}
|
|
9375
10594
|
function projectRoot(ctx) {
|
|
9376
10595
|
return ctx?.cwd ?? process.cwd();
|
|
9377
10596
|
}
|
|
10597
|
+
function registerPiCallGraphTools(pi) {
|
|
10598
|
+
pi.registerTool({
|
|
10599
|
+
name: "call_graph",
|
|
10600
|
+
label: "Call Graph",
|
|
10601
|
+
description: "Find callers or callees of a function/method in the indexed call graph.",
|
|
10602
|
+
parameters: Type.Object({
|
|
10603
|
+
name: Type.String(),
|
|
10604
|
+
direction: Type.Optional(Type.Union([Type.Literal("callers"), Type.Literal("callees")])),
|
|
10605
|
+
symbolId: Type.Optional(Type.String()),
|
|
10606
|
+
relationshipType: Type.Optional(RelationshipType)
|
|
10607
|
+
}),
|
|
10608
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
10609
|
+
const result = await getCallGraphData(projectRoot(ctx), HOST, params);
|
|
10610
|
+
if (result.direction === "callees") {
|
|
10611
|
+
return text(
|
|
10612
|
+
params.symbolId ? formatCallGraphCallees(params.symbolId, result.callees, params.relationshipType) : "Error: 'symbolId' is required when direction is 'callees'.",
|
|
10613
|
+
result
|
|
10614
|
+
);
|
|
10615
|
+
}
|
|
10616
|
+
return text(formatCallGraphCallers(params.name, result.callers, params.relationshipType), result);
|
|
10617
|
+
}
|
|
10618
|
+
});
|
|
10619
|
+
pi.registerTool({
|
|
10620
|
+
name: "call_graph_path",
|
|
10621
|
+
label: "Call Graph Path",
|
|
10622
|
+
description: "Find a call path between two functions/methods.",
|
|
10623
|
+
parameters: Type.Object({
|
|
10624
|
+
from: Type.String(),
|
|
10625
|
+
to: Type.String(),
|
|
10626
|
+
maxDepth: Type.Optional(Type.Number({ default: 10 }))
|
|
10627
|
+
}),
|
|
10628
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
10629
|
+
const result = await getCallGraphPath(projectRoot(ctx), HOST, params.from, params.to, params.maxDepth);
|
|
10630
|
+
return text(formatCallGraphPath(params.from, params.to, result), result);
|
|
10631
|
+
}
|
|
10632
|
+
});
|
|
10633
|
+
}
|
|
10634
|
+
|
|
10635
|
+
// src/pi-extension.ts
|
|
10636
|
+
var HOST2 = "pi";
|
|
10637
|
+
var ChunkType = Type2.Union([
|
|
10638
|
+
Type2.Literal("function"),
|
|
10639
|
+
Type2.Literal("class"),
|
|
10640
|
+
Type2.Literal("method"),
|
|
10641
|
+
Type2.Literal("interface"),
|
|
10642
|
+
Type2.Literal("type"),
|
|
10643
|
+
Type2.Literal("module"),
|
|
10644
|
+
Type2.Literal("block")
|
|
10645
|
+
]);
|
|
10646
|
+
function text2(text3, details) {
|
|
10647
|
+
return { content: [{ type: "text", text: text3 }], details };
|
|
10648
|
+
}
|
|
10649
|
+
function projectRoot2(ctx) {
|
|
10650
|
+
return ctx?.cwd ?? process.cwd();
|
|
10651
|
+
}
|
|
9378
10652
|
function codebaseIndexPiExtension(pi) {
|
|
9379
10653
|
pi.registerTool({
|
|
9380
10654
|
name: "codebase_search",
|
|
9381
10655
|
label: "Codebase Search",
|
|
9382
10656
|
description: "Semantic search over the indexed codebase. Describe behavior, not syntax.",
|
|
9383
|
-
parameters:
|
|
9384
|
-
query:
|
|
9385
|
-
limit:
|
|
9386
|
-
fileType:
|
|
9387
|
-
directory:
|
|
9388
|
-
chunkType:
|
|
9389
|
-
contextLines:
|
|
10657
|
+
parameters: Type2.Object({
|
|
10658
|
+
query: Type2.String({ description: "Natural language description of what code you're looking for" }),
|
|
10659
|
+
limit: Type2.Optional(Type2.Number({ description: "Maximum results (default: 10)" })),
|
|
10660
|
+
fileType: Type2.Optional(Type2.String({ description: "Filter by extension, e.g. ts, py, rs" })),
|
|
10661
|
+
directory: Type2.Optional(Type2.String({ description: "Filter by directory path" })),
|
|
10662
|
+
chunkType: Type2.Optional(ChunkType),
|
|
10663
|
+
contextLines: Type2.Optional(Type2.Number({ description: "Extra lines around each match" })),
|
|
10664
|
+
blameAuthor: Type2.Optional(Type2.String({ description: "Filter by git blame author name or email" })),
|
|
10665
|
+
blameSha: Type2.Optional(Type2.String({ description: "Filter by git blame commit SHA or prefix" })),
|
|
10666
|
+
blameSince: Type2.Optional(Type2.String({ description: "Filter to chunks last changed on or after this date" }))
|
|
9390
10667
|
}),
|
|
9391
10668
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9392
|
-
const results = await searchCodebase(
|
|
9393
|
-
return
|
|
10669
|
+
const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, params);
|
|
10670
|
+
return text2(formatSearchResults(results), results);
|
|
9394
10671
|
}
|
|
9395
10672
|
});
|
|
9396
10673
|
pi.registerTool({
|
|
9397
10674
|
name: "codebase_peek",
|
|
9398
10675
|
label: "Codebase Peek",
|
|
9399
10676
|
description: "Semantic search returning only metadata (file, lines, symbol) to save tokens.",
|
|
9400
|
-
parameters:
|
|
9401
|
-
query:
|
|
9402
|
-
limit:
|
|
9403
|
-
fileType:
|
|
9404
|
-
directory:
|
|
9405
|
-
chunkType:
|
|
10677
|
+
parameters: Type2.Object({
|
|
10678
|
+
query: Type2.String(),
|
|
10679
|
+
limit: Type2.Optional(Type2.Number()),
|
|
10680
|
+
fileType: Type2.Optional(Type2.String()),
|
|
10681
|
+
directory: Type2.Optional(Type2.String()),
|
|
10682
|
+
chunkType: Type2.Optional(ChunkType),
|
|
10683
|
+
blameAuthor: Type2.Optional(Type2.String()),
|
|
10684
|
+
blameSha: Type2.Optional(Type2.String()),
|
|
10685
|
+
blameSince: Type2.Optional(Type2.String())
|
|
9406
10686
|
}),
|
|
9407
10687
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9408
|
-
const results = await searchCodebase(
|
|
9409
|
-
return
|
|
10688
|
+
const results = await searchCodebase(projectRoot2(ctx), HOST2, params.query, { ...params, metadataOnly: true });
|
|
10689
|
+
return text2(formatSearchResults(results), results);
|
|
9410
10690
|
}
|
|
9411
10691
|
});
|
|
9412
10692
|
pi.registerTool({
|
|
9413
10693
|
name: "find_similar",
|
|
9414
10694
|
label: "Find Similar Code",
|
|
9415
10695
|
description: "Find code similar to a snippet for duplicate detection and pattern discovery.",
|
|
9416
|
-
parameters:
|
|
9417
|
-
code:
|
|
9418
|
-
limit:
|
|
9419
|
-
fileType:
|
|
9420
|
-
directory:
|
|
9421
|
-
chunkType:
|
|
9422
|
-
excludeFile:
|
|
10696
|
+
parameters: Type2.Object({
|
|
10697
|
+
code: Type2.String({ description: "Code snippet to compare" }),
|
|
10698
|
+
limit: Type2.Optional(Type2.Number()),
|
|
10699
|
+
fileType: Type2.Optional(Type2.String()),
|
|
10700
|
+
directory: Type2.Optional(Type2.String()),
|
|
10701
|
+
chunkType: Type2.Optional(ChunkType),
|
|
10702
|
+
excludeFile: Type2.Optional(Type2.String())
|
|
9423
10703
|
}),
|
|
9424
10704
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9425
|
-
const results = await findSimilarCode(
|
|
9426
|
-
return
|
|
10705
|
+
const results = await findSimilarCode(projectRoot2(ctx), HOST2, params.code, params);
|
|
10706
|
+
return text2(formatSearchResults(results, "similarity"), results);
|
|
9427
10707
|
}
|
|
9428
10708
|
});
|
|
9429
10709
|
pi.registerTool({
|
|
9430
10710
|
name: "implementation_lookup",
|
|
9431
10711
|
label: "Implementation Lookup",
|
|
9432
10712
|
description: "Find likely symbol definitions or implementations by name or natural language.",
|
|
9433
|
-
parameters:
|
|
9434
|
-
query:
|
|
9435
|
-
limit:
|
|
9436
|
-
fileType:
|
|
9437
|
-
directory:
|
|
10713
|
+
parameters: Type2.Object({
|
|
10714
|
+
query: Type2.String(),
|
|
10715
|
+
limit: Type2.Optional(Type2.Number()),
|
|
10716
|
+
fileType: Type2.Optional(Type2.String()),
|
|
10717
|
+
directory: Type2.Optional(Type2.String())
|
|
9438
10718
|
}),
|
|
9439
10719
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9440
|
-
const results = await implementationLookup(
|
|
9441
|
-
return
|
|
10720
|
+
const results = await implementationLookup(projectRoot2(ctx), HOST2, params.query, params);
|
|
10721
|
+
return text2(formatDefinitionLookup(results, params.query), results);
|
|
9442
10722
|
}
|
|
9443
10723
|
});
|
|
9444
10724
|
pi.registerTool({
|
|
9445
10725
|
name: "index_codebase",
|
|
9446
10726
|
label: "Index Codebase",
|
|
9447
10727
|
description: "Build or refresh the semantic codebase index.",
|
|
9448
|
-
parameters:
|
|
9449
|
-
force:
|
|
9450
|
-
estimateOnly:
|
|
9451
|
-
verbose:
|
|
10728
|
+
parameters: Type2.Object({
|
|
10729
|
+
force: Type2.Optional(Type2.Boolean({ default: false })),
|
|
10730
|
+
estimateOnly: Type2.Optional(Type2.Boolean({ default: false })),
|
|
10731
|
+
verbose: Type2.Optional(Type2.Boolean({ default: false }))
|
|
9452
10732
|
}),
|
|
9453
10733
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9454
|
-
const result = await runIndexCodebase(
|
|
9455
|
-
|
|
10734
|
+
const result = await runIndexCodebase(projectRoot2(ctx), HOST2, params);
|
|
10735
|
+
if (result.kind === "estimate") return text2(formatCostEstimate(result.estimate), result.estimate);
|
|
10736
|
+
if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
|
|
10737
|
+
return text2(formatIndexStats(result.stats, params.verbose ?? false), result.stats);
|
|
9456
10738
|
}
|
|
9457
10739
|
});
|
|
9458
10740
|
pi.registerTool({
|
|
9459
10741
|
name: "index_status",
|
|
9460
10742
|
label: "Index Status",
|
|
9461
10743
|
description: "Check index health and current status.",
|
|
9462
|
-
parameters:
|
|
10744
|
+
parameters: Type2.Object({}),
|
|
9463
10745
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
9464
|
-
const status = await getIndexStatus(
|
|
9465
|
-
return
|
|
10746
|
+
const status = await getIndexStatus(projectRoot2(ctx), HOST2);
|
|
10747
|
+
return text2(formatStatus(status), status);
|
|
9466
10748
|
}
|
|
9467
10749
|
});
|
|
9468
10750
|
pi.registerTool({
|
|
9469
10751
|
name: "index_health_check",
|
|
9470
10752
|
label: "Index Health Check",
|
|
9471
10753
|
description: "Garbage collect orphaned embeddings/chunks and report health.",
|
|
9472
|
-
parameters:
|
|
10754
|
+
parameters: Type2.Object({}),
|
|
9473
10755
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
9474
|
-
const result = await
|
|
9475
|
-
return
|
|
10756
|
+
const result = await runIndexHealthCheck(projectRoot2(ctx), HOST2);
|
|
10757
|
+
if (result.kind === "busy") return text2(result.text, { code: "INDEX_BUSY" });
|
|
10758
|
+
return text2(formatHealthCheck(result.health), result.health);
|
|
9476
10759
|
}
|
|
9477
10760
|
});
|
|
9478
10761
|
pi.registerTool({
|
|
9479
10762
|
name: "index_metrics",
|
|
9480
10763
|
label: "Index Metrics",
|
|
9481
10764
|
description: "Return collected performance metrics when debug metrics are enabled.",
|
|
9482
|
-
parameters:
|
|
10765
|
+
parameters: Type2.Object({}),
|
|
9483
10766
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
9484
|
-
const result = await getIndexMetrics(
|
|
9485
|
-
return
|
|
10767
|
+
const result = await getIndexMetrics(projectRoot2(ctx), HOST2);
|
|
10768
|
+
return text2(result.text, result);
|
|
9486
10769
|
}
|
|
9487
10770
|
});
|
|
9488
10771
|
pi.registerTool({
|
|
9489
10772
|
name: "index_logs",
|
|
9490
10773
|
label: "Index Logs",
|
|
9491
10774
|
description: "Return recent debug logs when debug logging is enabled.",
|
|
9492
|
-
parameters:
|
|
9493
|
-
limit:
|
|
9494
|
-
category:
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
10775
|
+
parameters: Type2.Object({
|
|
10776
|
+
limit: Type2.Optional(Type2.Number()),
|
|
10777
|
+
category: Type2.Optional(Type2.Union([
|
|
10778
|
+
Type2.Literal("search"),
|
|
10779
|
+
Type2.Literal("embedding"),
|
|
10780
|
+
Type2.Literal("cache"),
|
|
10781
|
+
Type2.Literal("gc"),
|
|
10782
|
+
Type2.Literal("branch"),
|
|
10783
|
+
Type2.Literal("general")
|
|
9501
10784
|
])),
|
|
9502
|
-
level:
|
|
9503
|
-
}),
|
|
9504
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9505
|
-
const result = await getIndexLogs(projectRoot(ctx), HOST, params);
|
|
9506
|
-
return text(result.text, result);
|
|
9507
|
-
}
|
|
9508
|
-
});
|
|
9509
|
-
pi.registerTool({
|
|
9510
|
-
name: "call_graph",
|
|
9511
|
-
label: "Call Graph",
|
|
9512
|
-
description: "Find callers or callees of a function/method in the indexed call graph.",
|
|
9513
|
-
parameters: Type.Object({
|
|
9514
|
-
name: Type.String(),
|
|
9515
|
-
direction: Type.Optional(Type.Union([Type.Literal("callers"), Type.Literal("callees")])),
|
|
9516
|
-
symbolId: Type.Optional(Type.String()),
|
|
9517
|
-
relationshipType: Type.Optional(RelationshipType)
|
|
10785
|
+
level: Type2.Optional(Type2.Union([Type2.Literal("error"), Type2.Literal("warn"), Type2.Literal("info"), Type2.Literal("debug")]))
|
|
9518
10786
|
}),
|
|
9519
10787
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9520
|
-
const result = await
|
|
9521
|
-
return
|
|
9522
|
-
}
|
|
9523
|
-
});
|
|
9524
|
-
pi.registerTool({
|
|
9525
|
-
name: "call_graph_path",
|
|
9526
|
-
label: "Call Graph Path",
|
|
9527
|
-
description: "Find a call path between two functions/methods.",
|
|
9528
|
-
parameters: Type.Object({
|
|
9529
|
-
from: Type.String(),
|
|
9530
|
-
to: Type.String(),
|
|
9531
|
-
maxDepth: Type.Optional(Type.Number({ default: 10 }))
|
|
9532
|
-
}),
|
|
9533
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9534
|
-
const result = await getCallGraphPath(projectRoot(ctx), HOST, params.from, params.to, params.maxDepth);
|
|
9535
|
-
return text(JSON.stringify(result, null, 2), result);
|
|
10788
|
+
const result = await getIndexLogs(projectRoot2(ctx), HOST2, params);
|
|
10789
|
+
return text2(result.text, result);
|
|
9536
10790
|
}
|
|
9537
10791
|
});
|
|
10792
|
+
registerPiCallGraphTools(pi);
|
|
9538
10793
|
pi.registerTool({
|
|
9539
10794
|
name: "pr_impact",
|
|
9540
10795
|
label: "PR Impact",
|
|
9541
10796
|
description: "Analyze PR or branch impact through changed symbols and call graph neighborhoods.",
|
|
9542
|
-
parameters:
|
|
9543
|
-
pr:
|
|
9544
|
-
branch:
|
|
9545
|
-
maxDepth:
|
|
9546
|
-
hubThreshold:
|
|
9547
|
-
checkConflicts:
|
|
9548
|
-
direction:
|
|
10797
|
+
parameters: Type2.Object({
|
|
10798
|
+
pr: Type2.Optional(Type2.Number()),
|
|
10799
|
+
branch: Type2.Optional(Type2.String()),
|
|
10800
|
+
maxDepth: Type2.Optional(Type2.Number({ default: 5 })),
|
|
10801
|
+
hubThreshold: Type2.Optional(Type2.Number({ default: 10 })),
|
|
10802
|
+
checkConflicts: Type2.Optional(Type2.Boolean({ default: false })),
|
|
10803
|
+
direction: Type2.Optional(Type2.Union([Type2.Literal("callers"), Type2.Literal("callees"), Type2.Literal("both")]))
|
|
9549
10804
|
}),
|
|
9550
10805
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9551
|
-
const result = await getPrImpact(
|
|
9552
|
-
return
|
|
10806
|
+
const result = await getPrImpact(projectRoot2(ctx), HOST2, params);
|
|
10807
|
+
return text2(formatPrImpact(result), result);
|
|
9553
10808
|
}
|
|
9554
10809
|
});
|
|
9555
10810
|
pi.registerTool({
|
|
9556
10811
|
name: "knowledge_base_list",
|
|
9557
10812
|
label: "List Knowledge Bases",
|
|
9558
10813
|
description: "List configured knowledge-base paths included in the index.",
|
|
9559
|
-
parameters:
|
|
10814
|
+
parameters: Type2.Object({}),
|
|
9560
10815
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
9561
|
-
return
|
|
10816
|
+
return text2(listKnowledgeBases(projectRoot2(ctx), HOST2));
|
|
9562
10817
|
}
|
|
9563
10818
|
});
|
|
9564
10819
|
pi.registerTool({
|
|
9565
10820
|
name: "knowledge_base_add",
|
|
9566
10821
|
label: "Add Knowledge Base",
|
|
9567
10822
|
description: "Add a knowledge-base path to the codebase index config.",
|
|
9568
|
-
parameters:
|
|
10823
|
+
parameters: Type2.Object({ path: Type2.String({ description: "File or directory path to add" }) }),
|
|
9569
10824
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9570
|
-
return
|
|
10825
|
+
return text2(addKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
|
|
9571
10826
|
}
|
|
9572
10827
|
});
|
|
9573
10828
|
pi.registerTool({
|
|
9574
10829
|
name: "knowledge_base_remove",
|
|
9575
10830
|
label: "Remove Knowledge Base",
|
|
9576
10831
|
description: "Remove a knowledge-base path from the codebase index config.",
|
|
9577
|
-
parameters:
|
|
10832
|
+
parameters: Type2.Object({ path: Type2.String({ description: "File or directory path to remove" }) }),
|
|
9578
10833
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9579
|
-
return
|
|
10834
|
+
return text2(removeKnowledgeBase(projectRoot2(ctx), HOST2, params.path));
|
|
9580
10835
|
}
|
|
9581
10836
|
});
|
|
9582
10837
|
}
|