opencode-codebase-index 0.14.0 → 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/dist/cli.cjs CHANGED
@@ -496,7 +496,7 @@ var require_ignore = __commonJS({
496
496
  // path matching.
497
497
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
498
498
  // @returns {TestResult} true if a file is ignored
499
- test(path26, checkUnignored, mode) {
499
+ test(path27, checkUnignored, mode) {
500
500
  let ignored = false;
501
501
  let unignored = false;
502
502
  let matchedRule;
@@ -505,7 +505,7 @@ var require_ignore = __commonJS({
505
505
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
506
506
  return;
507
507
  }
508
- const matched = rule[mode].test(path26);
508
+ const matched = rule[mode].test(path27);
509
509
  if (!matched) {
510
510
  return;
511
511
  }
@@ -526,17 +526,17 @@ var require_ignore = __commonJS({
526
526
  var throwError = (message, Ctor) => {
527
527
  throw new Ctor(message);
528
528
  };
529
- var checkPath = (path26, originalPath, doThrow) => {
530
- if (!isString(path26)) {
529
+ var checkPath = (path27, originalPath, doThrow) => {
530
+ if (!isString(path27)) {
531
531
  return doThrow(
532
532
  `path must be a string, but got \`${originalPath}\``,
533
533
  TypeError
534
534
  );
535
535
  }
536
- if (!path26) {
536
+ if (!path27) {
537
537
  return doThrow(`path must not be empty`, TypeError);
538
538
  }
539
- if (checkPath.isNotRelative(path26)) {
539
+ if (checkPath.isNotRelative(path27)) {
540
540
  const r = "`path.relative()`d";
541
541
  return doThrow(
542
542
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -545,7 +545,7 @@ var require_ignore = __commonJS({
545
545
  }
546
546
  return true;
547
547
  };
548
- var isNotRelative = (path26) => REGEX_TEST_INVALID_PATH.test(path26);
548
+ var isNotRelative = (path27) => REGEX_TEST_INVALID_PATH.test(path27);
549
549
  checkPath.isNotRelative = isNotRelative;
550
550
  checkPath.convert = (p) => p;
551
551
  var Ignore2 = class {
@@ -575,19 +575,19 @@ var require_ignore = __commonJS({
575
575
  }
576
576
  // @returns {TestResult}
577
577
  _test(originalPath, cache, checkUnignored, slices) {
578
- const path26 = originalPath && checkPath.convert(originalPath);
578
+ const path27 = originalPath && checkPath.convert(originalPath);
579
579
  checkPath(
580
- path26,
580
+ path27,
581
581
  originalPath,
582
582
  this._strictPathCheck ? throwError : RETURN_FALSE
583
583
  );
584
- return this._t(path26, cache, checkUnignored, slices);
584
+ return this._t(path27, cache, checkUnignored, slices);
585
585
  }
586
- checkIgnore(path26) {
587
- if (!REGEX_TEST_TRAILING_SLASH.test(path26)) {
588
- return this.test(path26);
586
+ checkIgnore(path27) {
587
+ if (!REGEX_TEST_TRAILING_SLASH.test(path27)) {
588
+ return this.test(path27);
589
589
  }
590
- const slices = path26.split(SLASH2).filter(Boolean);
590
+ const slices = path27.split(SLASH2).filter(Boolean);
591
591
  slices.pop();
592
592
  if (slices.length) {
593
593
  const parent = this._t(
@@ -600,18 +600,18 @@ var require_ignore = __commonJS({
600
600
  return parent;
601
601
  }
602
602
  }
603
- return this._rules.test(path26, false, MODE_CHECK_IGNORE);
603
+ return this._rules.test(path27, false, MODE_CHECK_IGNORE);
604
604
  }
605
- _t(path26, cache, checkUnignored, slices) {
606
- if (path26 in cache) {
607
- return cache[path26];
605
+ _t(path27, cache, checkUnignored, slices) {
606
+ if (path27 in cache) {
607
+ return cache[path27];
608
608
  }
609
609
  if (!slices) {
610
- slices = path26.split(SLASH2).filter(Boolean);
610
+ slices = path27.split(SLASH2).filter(Boolean);
611
611
  }
612
612
  slices.pop();
613
613
  if (!slices.length) {
614
- return cache[path26] = this._rules.test(path26, checkUnignored, MODE_IGNORE);
614
+ return cache[path27] = this._rules.test(path27, checkUnignored, MODE_IGNORE);
615
615
  }
616
616
  const parent = this._t(
617
617
  slices.join(SLASH2) + SLASH2,
@@ -619,29 +619,29 @@ var require_ignore = __commonJS({
619
619
  checkUnignored,
620
620
  slices
621
621
  );
622
- return cache[path26] = parent.ignored ? parent : this._rules.test(path26, checkUnignored, MODE_IGNORE);
622
+ return cache[path27] = parent.ignored ? parent : this._rules.test(path27, checkUnignored, MODE_IGNORE);
623
623
  }
624
- ignores(path26) {
625
- return this._test(path26, this._ignoreCache, false).ignored;
624
+ ignores(path27) {
625
+ return this._test(path27, this._ignoreCache, false).ignored;
626
626
  }
627
627
  createFilter() {
628
- return (path26) => !this.ignores(path26);
628
+ return (path27) => !this.ignores(path27);
629
629
  }
630
630
  filter(paths) {
631
631
  return makeArray(paths).filter(this.createFilter());
632
632
  }
633
633
  // @returns {TestResult}
634
- test(path26) {
635
- return this._test(path26, this._testCache, true);
634
+ test(path27) {
635
+ return this._test(path27, this._testCache, true);
636
636
  }
637
637
  };
638
638
  var factory = (options) => new Ignore2(options);
639
- var isPathValid = (path26) => checkPath(path26 && checkPath.convert(path26), path26, RETURN_FALSE);
639
+ var isPathValid = (path27) => checkPath(path27 && checkPath.convert(path27), path27, RETURN_FALSE);
640
640
  var setupWindows = () => {
641
641
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
642
642
  checkPath.convert = makePosix;
643
643
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
644
- checkPath.isNotRelative = (path26) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path26) || isNotRelative(path26);
644
+ checkPath.isNotRelative = (path27) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path27) || isNotRelative(path27);
645
645
  };
646
646
  if (
647
647
  // Detect `process` so that it can run in browsers.
@@ -665,17 +665,17 @@ __export(cli_exports, {
665
665
  });
666
666
  module.exports = __toCommonJS(cli_exports);
667
667
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
668
- var import_fs15 = require("fs");
669
- var os5 = __toESM(require("os"), 1);
670
- var path25 = __toESM(require("path"), 1);
668
+ var import_fs16 = require("fs");
669
+ var os6 = __toESM(require("os"), 1);
670
+ var path26 = __toESM(require("path"), 1);
671
671
  var import_url2 = require("url");
672
672
 
673
673
  // src/config/constants.ts
674
674
  var DEFAULT_INCLUDE = [
675
- "**/*.{ts,tsx,js,jsx,mjs,cjs}",
675
+ "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}",
676
676
  "**/*.{py,pyi}",
677
- "**/*.{go,rs,java,kt,scala}",
678
- "**/*.{c,cpp,cc,h,hpp}",
677
+ "**/*.{go,rs,java,cs,kt,scala}",
678
+ "**/*.{c,cpp,cc,cxx,h,hpp,hxx}",
679
679
  "**/*.{rb,php,inc,swift}",
680
680
  "**/*.{cls,trigger}",
681
681
  "**/*.{vue,svelte,astro}",
@@ -1058,7 +1058,7 @@ var autoDetectProviders = AUTO_DETECT_PROVIDER_ORDER.filter(
1058
1058
  );
1059
1059
 
1060
1060
  // src/config/host.ts
1061
- var HOST_MODES = ["opencode", "codex", "claude", "pi"];
1061
+ var HOST_MODES = ["opencode", "codex", "claude", "pi", "jcode"];
1062
1062
  function isSupportedHostMode(value) {
1063
1063
  return HOST_MODES.includes(value);
1064
1064
  }
@@ -1113,9 +1113,9 @@ var import_fs = require("fs");
1113
1113
  var path = __toESM(require("path"), 1);
1114
1114
 
1115
1115
  // src/eval/report-formatters.ts
1116
- function assertFiniteNumber(value, path26) {
1116
+ function assertFiniteNumber(value, path27) {
1117
1117
  if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
1118
- throw new Error(`${path26} must be a finite number`);
1118
+ throw new Error(`${path27} must be a finite number`);
1119
1119
  }
1120
1120
  return value;
1121
1121
  }
@@ -1303,13 +1303,13 @@ function buildPerQueryArtifact(perQuery) {
1303
1303
  }
1304
1304
 
1305
1305
  // src/eval/runner.ts
1306
- var import_fs10 = require("fs");
1307
- var path14 = __toESM(require("path"), 1);
1306
+ var import_fs11 = require("fs");
1307
+ var path15 = __toESM(require("path"), 1);
1308
1308
  var import_perf_hooks2 = require("perf_hooks");
1309
1309
 
1310
1310
  // src/indexer/index.ts
1311
- var import_fs7 = require("fs");
1312
- var path11 = __toESM(require("path"), 1);
1311
+ var import_fs8 = require("fs");
1312
+ var path12 = __toESM(require("path"), 1);
1313
1313
  var import_perf_hooks = require("perf_hooks");
1314
1314
  var import_child_process3 = require("child_process");
1315
1315
  var import_util3 = require("util");
@@ -2591,17 +2591,17 @@ function validateExternalUrl(urlString) {
2591
2591
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2592
2592
  return { valid: false, reason: `Blocked protocol: ${parsed.protocol}` };
2593
2593
  }
2594
- const hostname = parsed.hostname.toLowerCase();
2595
- if (BLOCKED_HOSTNAMES.has(hostname)) {
2596
- return { valid: false, reason: `Blocked: cloud metadata service (${hostname})` };
2594
+ const hostname2 = parsed.hostname.toLowerCase();
2595
+ if (BLOCKED_HOSTNAMES.has(hostname2)) {
2596
+ return { valid: false, reason: `Blocked: cloud metadata service (${hostname2})` };
2597
2597
  }
2598
2598
  for (const pattern of BLOCKED_METADATA_IPS) {
2599
- if (pattern.test(hostname)) {
2600
- return { valid: false, reason: `Blocked: cloud metadata IP (${hostname})` };
2599
+ if (pattern.test(hostname2)) {
2600
+ return { valid: false, reason: `Blocked: cloud metadata IP (${hostname2})` };
2601
2601
  }
2602
2602
  }
2603
- if (/^169\.254\./.test(hostname)) {
2604
- return { valid: false, reason: `Blocked: link-local address (${hostname})` };
2603
+ if (/^169\.254\./.test(hostname2)) {
2604
+ return { valid: false, reason: `Blocked: link-local address (${hostname2})` };
2605
2605
  }
2606
2606
  return { valid: true };
2607
2607
  }
@@ -3792,6 +3792,12 @@ function createMockNativeBinding() {
3792
3792
  constructor() {
3793
3793
  throw error;
3794
3794
  }
3795
+ static openReadOnly() {
3796
+ throw error;
3797
+ }
3798
+ static createEmptyReadOnly() {
3799
+ throw error;
3800
+ }
3795
3801
  close() {
3796
3802
  throw error;
3797
3803
  }
@@ -3895,6 +3901,12 @@ var VectorStore = class {
3895
3901
  load() {
3896
3902
  this.inner.load();
3897
3903
  }
3904
+ loadStrict() {
3905
+ this.inner.loadStrict();
3906
+ }
3907
+ hasFingerprint() {
3908
+ return this.inner.hasFingerprint();
3909
+ }
3898
3910
  count() {
3899
3911
  return this.inner.count();
3900
3912
  }
@@ -4177,12 +4189,24 @@ var InvertedIndex = class {
4177
4189
  return this.inner.documentCount();
4178
4190
  }
4179
4191
  };
4180
- var Database = class {
4192
+ var Database = class _Database {
4181
4193
  inner;
4182
4194
  closed = false;
4183
4195
  constructor(dbPath) {
4184
4196
  this.inner = new native.Database(dbPath);
4185
4197
  }
4198
+ static fromNative(inner) {
4199
+ const database = Object.create(_Database.prototype);
4200
+ database.inner = inner;
4201
+ database.closed = false;
4202
+ return database;
4203
+ }
4204
+ static openReadOnly(dbPath) {
4205
+ return _Database.fromNative(native.Database.openReadOnly(dbPath));
4206
+ }
4207
+ static createEmptyReadOnly() {
4208
+ return _Database.fromNative(native.Database.createEmptyReadOnly());
4209
+ }
4186
4210
  throwIfClosed() {
4187
4211
  if (this.closed) {
4188
4212
  throw new Error("Database is closed");
@@ -4651,6 +4675,9 @@ function getHostProjectIndexRelativePath(host) {
4651
4675
  function hasHostProjectConfig(projectRoot, host) {
4652
4676
  return (0, import_fs6.existsSync)(path8.join(projectRoot, getProjectConfigRelativePath(host)));
4653
4677
  }
4678
+ function hasHostGlobalConfig(host) {
4679
+ return (0, import_fs6.existsSync)(getGlobalConfigPath(host));
4680
+ }
4654
4681
  function getGlobalIndexPath(host = "opencode") {
4655
4682
  switch (host) {
4656
4683
  case "opencode":
@@ -4690,6 +4717,9 @@ function resolveGlobalIndexPath(host = "opencode") {
4690
4717
  return hostIndexPath;
4691
4718
  }
4692
4719
  if (host !== "opencode") {
4720
+ if (hasHostGlobalConfig(host)) {
4721
+ return hostIndexPath;
4722
+ }
4693
4723
  const legacyIndexPath = getGlobalIndexPath("opencode");
4694
4724
  if ((0, import_fs6.existsSync)(legacyIndexPath)) {
4695
4725
  return legacyIndexPath;
@@ -4906,6 +4936,422 @@ async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
4906
4936
  }
4907
4937
  }
4908
4938
 
4939
+ // src/indexer/index-lock.ts
4940
+ var import_crypto = require("crypto");
4941
+ var import_fs7 = require("fs");
4942
+ var os4 = __toESM(require("os"), 1);
4943
+ var path11 = __toESM(require("path"), 1);
4944
+ var OWNER_FILE_NAME = "owner.json";
4945
+ var RECLAIM_DIRECTORY_NAME = "reclaiming";
4946
+ var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
4947
+ 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;
4948
+ var VALID_OPERATIONS = /* @__PURE__ */ new Set([
4949
+ "initialize",
4950
+ "index",
4951
+ "force-index",
4952
+ "clear",
4953
+ "health-check",
4954
+ "retry-failed-batches",
4955
+ "recovery"
4956
+ ]);
4957
+ var temporaryCounter = 0;
4958
+ function getErrorCode(error) {
4959
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
4960
+ }
4961
+ function retryTransientFilesystemOperation(operation) {
4962
+ let lastError;
4963
+ for (let attempt = 0; attempt < 3; attempt += 1) {
4964
+ try {
4965
+ operation();
4966
+ return;
4967
+ } catch (error) {
4968
+ lastError = error;
4969
+ const code = getErrorCode(error);
4970
+ if (code !== "EBUSY" && code !== "EPERM") throw error;
4971
+ if (attempt < 2) {
4972
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, (attempt + 1) * 10);
4973
+ }
4974
+ }
4975
+ }
4976
+ throw lastError;
4977
+ }
4978
+ function parseOwner(value) {
4979
+ if (typeof value !== "object" || value === null) return null;
4980
+ const candidate = value;
4981
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
4982
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
4983
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
4984
+ if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
4985
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
4986
+ return candidate;
4987
+ }
4988
+ function parseReclaimOwner(value) {
4989
+ if (typeof value !== "object" || value === null) return null;
4990
+ const candidate = value;
4991
+ if (!Number.isInteger(candidate.pid) || (candidate.pid ?? 0) <= 0) return null;
4992
+ if (typeof candidate.hostname !== "string" || candidate.hostname.length === 0) return null;
4993
+ if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
4994
+ if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
4995
+ if (typeof candidate.expectedOwnerToken !== "string" || !UUID_PATTERN.test(candidate.expectedOwnerToken)) return null;
4996
+ return candidate;
4997
+ }
4998
+ function readJsonDirectory(directoryPath, parser) {
4999
+ try {
5000
+ return parser(JSON.parse((0, import_fs7.readFileSync)(path11.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
5001
+ } catch {
5002
+ return null;
5003
+ }
5004
+ }
5005
+ function readDirectoryOwner(lockPath) {
5006
+ return readJsonDirectory(lockPath, parseOwner);
5007
+ }
5008
+ function readReclaimOwner(markerPath) {
5009
+ return readJsonDirectory(markerPath, parseReclaimOwner);
5010
+ }
5011
+ function readRecoveryOwner(markerPath) {
5012
+ return readDirectoryOwner(markerPath);
5013
+ }
5014
+ function readLegacyOwner(lockPath) {
5015
+ try {
5016
+ const parsed = JSON.parse((0, import_fs7.readFileSync)(lockPath, "utf-8"));
5017
+ if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
5018
+ if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
5019
+ return {
5020
+ pid: Number(parsed.pid),
5021
+ hostname: typeof parsed.hostname === "string" ? parsed.hostname : os4.hostname(),
5022
+ startedAt: parsed.startedAt,
5023
+ operation: typeof parsed.operation === "string" && VALID_OPERATIONS.has(parsed.operation) ? parsed.operation : "index",
5024
+ token: typeof parsed.token === "string" ? parsed.token : "legacy-v0.14.0"
5025
+ };
5026
+ } catch {
5027
+ return null;
5028
+ }
5029
+ }
5030
+ function getOwnerLiveness(owner) {
5031
+ if (owner.hostname !== os4.hostname()) return "unknown";
5032
+ try {
5033
+ process.kill(owner.pid, 0);
5034
+ return "alive";
5035
+ } catch (error) {
5036
+ const code = getErrorCode(error);
5037
+ if (code === "ESRCH") return "dead";
5038
+ if (code === "EPERM") return "alive";
5039
+ return "unknown";
5040
+ }
5041
+ }
5042
+ function sameOwner(left, right) {
5043
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token;
5044
+ }
5045
+ function sameReclaimOwner(left, right) {
5046
+ return left.pid === right.pid && left.hostname === right.hostname && left.token === right.token && left.expectedOwnerToken === right.expectedOwnerToken;
5047
+ }
5048
+ function publishJsonDirectory(finalPath, value) {
5049
+ const candidatePath = `${finalPath}.candidate.${process.pid}.${(0, import_crypto.randomUUID)()}`;
5050
+ (0, import_fs7.mkdirSync)(candidatePath, { mode: 448 });
5051
+ try {
5052
+ (0, import_fs7.writeFileSync)(path11.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
5053
+ encoding: "utf-8",
5054
+ flag: "wx",
5055
+ mode: 384
5056
+ });
5057
+ if ((0, import_fs7.existsSync)(finalPath)) return false;
5058
+ try {
5059
+ (0, import_fs7.renameSync)(candidatePath, finalPath);
5060
+ return true;
5061
+ } catch (error) {
5062
+ if ((0, import_fs7.existsSync)(finalPath)) return false;
5063
+ throw error;
5064
+ }
5065
+ } finally {
5066
+ if ((0, import_fs7.existsSync)(candidatePath)) (0, import_fs7.rmSync)(candidatePath, { recursive: true, force: true });
5067
+ }
5068
+ }
5069
+ function createOwner(operation) {
5070
+ return {
5071
+ pid: process.pid,
5072
+ hostname: os4.hostname(),
5073
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5074
+ operation,
5075
+ token: (0, import_crypto.randomUUID)()
5076
+ };
5077
+ }
5078
+ function recoveryMarkerPath(indexPath, owner) {
5079
+ return path11.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
5080
+ }
5081
+ function publishRecoveryMarker(indexPath, owner) {
5082
+ const markerPath = recoveryMarkerPath(indexPath, owner);
5083
+ if (!publishJsonDirectory(markerPath, owner)) {
5084
+ const markerOwner = readRecoveryOwner(markerPath);
5085
+ if (!markerOwner || !sameOwner(markerOwner, owner)) {
5086
+ throw new IndexLockContentionError(markerPath, markerOwner, "unknown-owner");
5087
+ }
5088
+ }
5089
+ return markerPath;
5090
+ }
5091
+ function getPendingRecoveries(indexPath) {
5092
+ const recoveries = [];
5093
+ const markerNames = (0, import_fs7.readdirSync)(indexPath).filter((name) => {
5094
+ if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
5095
+ return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
5096
+ }).sort();
5097
+ for (const markerName of markerNames) {
5098
+ const markerPath = path11.join(indexPath, markerName);
5099
+ const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
5100
+ let markerStats;
5101
+ try {
5102
+ markerStats = (0, import_fs7.lstatSync)(markerPath);
5103
+ } catch (error) {
5104
+ if (getErrorCode(error) === "ENOENT") continue;
5105
+ throw error;
5106
+ }
5107
+ if (!markerStats.isDirectory()) {
5108
+ throw new IndexLockContentionError(markerPath, null, "unknown-owner");
5109
+ }
5110
+ const owner = readRecoveryOwner(markerPath);
5111
+ if (!owner || owner.token !== markerToken || getOwnerLiveness(owner) !== "dead") {
5112
+ throw new IndexLockContentionError(markerPath, owner, "unknown-owner");
5113
+ }
5114
+ recoveries.push({ owner, markerPath });
5115
+ }
5116
+ recoveries.sort((left, right) => {
5117
+ const byTimestamp = left.owner.startedAt.localeCompare(right.owner.startedAt);
5118
+ return byTimestamp !== 0 ? byTimestamp : left.markerPath.localeCompare(right.markerPath);
5119
+ });
5120
+ return recoveries;
5121
+ }
5122
+ function cleanupDeadPublicationCandidates(indexPath) {
5123
+ const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
5124
+ for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
5125
+ const match = candidatePattern.exec(entry);
5126
+ if (!match) continue;
5127
+ const pid = Number(match[1]);
5128
+ if (!Number.isInteger(pid) || pid <= 0) continue;
5129
+ if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
5130
+ (0, import_fs7.rmSync)(path11.join(indexPath, entry), { recursive: true, force: true });
5131
+ }
5132
+ }
5133
+ function removeDeadReclaimMarker(lockPath, expectedOwner) {
5134
+ const markerPath = path11.join(lockPath, RECLAIM_DIRECTORY_NAME);
5135
+ const marker = readReclaimOwner(markerPath);
5136
+ if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
5137
+ return false;
5138
+ }
5139
+ const currentOwner = readDirectoryOwner(lockPath);
5140
+ if (!currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
5141
+ return false;
5142
+ }
5143
+ const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${(0, import_crypto.randomUUID)()}`;
5144
+ try {
5145
+ (0, import_fs7.renameSync)(markerPath, claimedMarkerPath);
5146
+ } catch (error) {
5147
+ if (getErrorCode(error) === "ENOENT") return false;
5148
+ throw error;
5149
+ }
5150
+ const claimedMarker = readReclaimOwner(claimedMarkerPath);
5151
+ const ownerAfterClaim = readDirectoryOwner(lockPath);
5152
+ if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
5153
+ if (!(0, import_fs7.existsSync)(markerPath) && (0, import_fs7.existsSync)(claimedMarkerPath)) {
5154
+ (0, import_fs7.renameSync)(claimedMarkerPath, markerPath);
5155
+ }
5156
+ return false;
5157
+ }
5158
+ (0, import_fs7.rmSync)(claimedMarkerPath, { recursive: true, force: true });
5159
+ return true;
5160
+ }
5161
+ function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
5162
+ const reclaimPath = path11.join(lockPath, RECLAIM_DIRECTORY_NAME);
5163
+ const reclaimOwner = {
5164
+ pid: process.pid,
5165
+ hostname: os4.hostname(),
5166
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5167
+ token: (0, import_crypto.randomUUID)(),
5168
+ expectedOwnerToken: expectedOwner.token
5169
+ };
5170
+ for (let attempt = 0; attempt < 2; attempt += 1) {
5171
+ if (publishJsonDirectory(reclaimPath, reclaimOwner)) break;
5172
+ if (attempt === 0 && removeDeadReclaimMarker(lockPath, expectedOwner)) continue;
5173
+ return false;
5174
+ }
5175
+ try {
5176
+ const currentReclaimer = readReclaimOwner(reclaimPath);
5177
+ const currentOwner = readDirectoryOwner(lockPath);
5178
+ if (!currentReclaimer || !sameReclaimOwner(currentReclaimer, reclaimOwner) || !currentOwner || !sameOwner(currentOwner, expectedOwner) || getOwnerLiveness(currentOwner) !== "dead") {
5179
+ return false;
5180
+ }
5181
+ publishRecoveryMarker(indexPath, expectedOwner);
5182
+ const ownerBeforeQuarantine = readDirectoryOwner(lockPath);
5183
+ const reclaimerBeforeQuarantine = readReclaimOwner(reclaimPath);
5184
+ if (!ownerBeforeQuarantine || !sameOwner(ownerBeforeQuarantine, expectedOwner) || getOwnerLiveness(ownerBeforeQuarantine) !== "dead" || !reclaimerBeforeQuarantine || !sameReclaimOwner(reclaimerBeforeQuarantine, reclaimOwner)) {
5185
+ return false;
5186
+ }
5187
+ const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
5188
+ (0, import_fs7.renameSync)(lockPath, quarantinePath);
5189
+ (0, import_fs7.rmSync)(quarantinePath, { recursive: true, force: true });
5190
+ return true;
5191
+ } catch (error) {
5192
+ if (getErrorCode(error) === "ENOENT") return false;
5193
+ throw error;
5194
+ }
5195
+ }
5196
+ var IndexLockContentionError = class extends Error {
5197
+ constructor(lockPath, owner, reason) {
5198
+ const ownerDescription = owner ? `PID ${owner.pid} on ${owner.hostname}, operation ${owner.operation}, since ${owner.startedAt}` : "an unreadable owner";
5199
+ super(`Index mutation already in progress: ${ownerDescription}`);
5200
+ this.lockPath = lockPath;
5201
+ this.owner = owner;
5202
+ this.reason = reason;
5203
+ this.name = "IndexLockContentionError";
5204
+ }
5205
+ lockPath;
5206
+ owner;
5207
+ reason;
5208
+ code = "INDEX_BUSY";
5209
+ };
5210
+ function isIndexLockContentionError(error) {
5211
+ return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
5212
+ }
5213
+ function isTransientIndexLockContention(error) {
5214
+ if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
5215
+ return error.reason === "active" || error.reason === "reclaiming";
5216
+ }
5217
+ function acquireIndexLock(indexPath, operation) {
5218
+ (0, import_fs7.mkdirSync)(indexPath, { recursive: true });
5219
+ const canonicalIndexPath = import_fs7.realpathSync.native(indexPath);
5220
+ const lockPath = path11.join(canonicalIndexPath, "indexing.lock");
5221
+ cleanupDeadPublicationCandidates(canonicalIndexPath);
5222
+ for (let attempt = 0; attempt < 6; attempt += 1) {
5223
+ const owner = createOwner(operation);
5224
+ if (publishJsonDirectory(lockPath, owner)) {
5225
+ const lease = {
5226
+ canonicalIndexPath,
5227
+ lockPath,
5228
+ owner,
5229
+ recoveries: []
5230
+ };
5231
+ try {
5232
+ lease.recoveries = getPendingRecoveries(canonicalIndexPath);
5233
+ return lease;
5234
+ } catch (error) {
5235
+ releaseIndexLock(lease);
5236
+ throw error;
5237
+ }
5238
+ }
5239
+ let stats;
5240
+ try {
5241
+ stats = (0, import_fs7.lstatSync)(lockPath);
5242
+ } catch (error) {
5243
+ if (getErrorCode(error) === "ENOENT") continue;
5244
+ throw error;
5245
+ }
5246
+ if (!stats.isDirectory()) {
5247
+ const legacyOwner = readLegacyOwner(lockPath);
5248
+ throw new IndexLockContentionError(lockPath, legacyOwner, "legacy-lock");
5249
+ }
5250
+ const existingOwner = readDirectoryOwner(lockPath);
5251
+ if (!existingOwner) {
5252
+ throw new IndexLockContentionError(lockPath, null, "unknown-owner");
5253
+ }
5254
+ const liveness = getOwnerLiveness(existingOwner);
5255
+ if (liveness !== "dead") {
5256
+ throw new IndexLockContentionError(lockPath, existingOwner, liveness === "alive" ? "active" : "unknown-owner");
5257
+ }
5258
+ if (!reclaimDeadOwner(canonicalIndexPath, lockPath, existingOwner)) {
5259
+ throw new IndexLockContentionError(lockPath, existingOwner, "reclaiming");
5260
+ }
5261
+ }
5262
+ throw new IndexLockContentionError(lockPath, null, "reclaiming");
5263
+ }
5264
+ function releaseIndexLock(lease) {
5265
+ const currentOwner = readDirectoryOwner(lease.lockPath);
5266
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
5267
+ const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
5268
+ try {
5269
+ retryTransientFilesystemOperation(() => (0, import_fs7.renameSync)(lease.lockPath, releasePath));
5270
+ } catch (error) {
5271
+ if (getErrorCode(error) === "ENOENT") return false;
5272
+ throw error;
5273
+ }
5274
+ const claimedOwner = readDirectoryOwner(releasePath);
5275
+ if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
5276
+ if (!(0, import_fs7.existsSync)(lease.lockPath) && (0, import_fs7.existsSync)(releasePath)) {
5277
+ (0, import_fs7.renameSync)(releasePath, lease.lockPath);
5278
+ }
5279
+ return false;
5280
+ }
5281
+ try {
5282
+ retryTransientFilesystemOperation(() => (0, import_fs7.rmSync)(releasePath, { recursive: true, force: true }));
5283
+ } catch (error) {
5284
+ console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
5285
+ }
5286
+ return true;
5287
+ }
5288
+ async function withIndexLock(indexPath, operation, callback, options = {}) {
5289
+ const lease = acquireIndexLock(indexPath, operation);
5290
+ let result;
5291
+ let callbackError;
5292
+ let callbackFailed = false;
5293
+ try {
5294
+ result = await callback(lease);
5295
+ } catch (error) {
5296
+ callbackFailed = true;
5297
+ callbackError = error;
5298
+ }
5299
+ if (!callbackFailed && options.completeRecoveries !== false) {
5300
+ try {
5301
+ completeLeaseRecovery(lease);
5302
+ } catch (error) {
5303
+ callbackFailed = true;
5304
+ callbackError = error;
5305
+ }
5306
+ }
5307
+ let releaseError;
5308
+ try {
5309
+ if (!releaseIndexLock(lease)) {
5310
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
5311
+ }
5312
+ } catch (error) {
5313
+ releaseError = error;
5314
+ }
5315
+ if (releaseError !== void 0) {
5316
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
5317
+ throw releaseError;
5318
+ }
5319
+ if (callbackFailed) throw callbackError;
5320
+ return result;
5321
+ }
5322
+ function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
5323
+ if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
5324
+ temporaryCounter += 1;
5325
+ return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
5326
+ }
5327
+ function removeLeaseTemporaryPath(temporaryPath) {
5328
+ if ((0, import_fs7.existsSync)(temporaryPath)) (0, import_fs7.rmSync)(temporaryPath, { recursive: true, force: true });
5329
+ }
5330
+ function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
5331
+ for (const targetPath of backupTargets) {
5332
+ const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
5333
+ if (!(0, import_fs7.existsSync)(backupPath)) continue;
5334
+ if ((0, import_fs7.existsSync)(targetPath)) (0, import_fs7.rmSync)(targetPath, { recursive: true, force: true });
5335
+ (0, import_fs7.renameSync)(backupPath, targetPath);
5336
+ }
5337
+ const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
5338
+ for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
5339
+ if (!entry.includes(temporaryOwnerMarker)) continue;
5340
+ (0, import_fs7.rmSync)(path11.join(indexPath, entry), { recursive: true, force: true });
5341
+ }
5342
+ }
5343
+ function completeLeaseRecovery(lease) {
5344
+ for (const recovery of lease.recoveries) {
5345
+ const markerOwner = readRecoveryOwner(recovery.markerPath);
5346
+ if (!markerOwner || !sameOwner(markerOwner, recovery.owner)) {
5347
+ throw new Error(`Recovery marker ownership changed: ${recovery.markerPath}`);
5348
+ }
5349
+ }
5350
+ for (const recovery of lease.recoveries) {
5351
+ (0, import_fs7.rmSync)(recovery.markerPath, { recursive: true, force: true });
5352
+ }
5353
+ }
5354
+
4909
5355
  // src/indexer/index.ts
4910
5356
  var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
4911
5357
  var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
@@ -4987,6 +5433,7 @@ function isSqliteCorruptionError(error) {
4987
5433
  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");
4988
5434
  }
4989
5435
  var STARTUP_WARNING_METADATA_KEY = "index.startupWarning";
5436
+ var READER_ARTIFACT_RETRY_INTERVAL_MS = 1e3;
4990
5437
  function metadataFromBlame(blame) {
4991
5438
  if (!blame) {
4992
5439
  return {};
@@ -5184,9 +5631,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
5184
5631
  return true;
5185
5632
  }
5186
5633
  function isPathWithinRoot(filePath, rootPath) {
5187
- const normalizedFilePath = path11.resolve(filePath);
5188
- const normalizedRoot = path11.resolve(rootPath);
5189
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path11.sep}`);
5634
+ const normalizedFilePath = path12.resolve(filePath);
5635
+ const normalizedRoot = path12.resolve(rootPath);
5636
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path12.sep}`);
5190
5637
  }
5191
5638
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5192
5639
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -6252,26 +6699,133 @@ var Indexer = class {
6252
6699
  queryCacheTtlMs = 5 * 60 * 1e3;
6253
6700
  querySimilarityThreshold = 0.85;
6254
6701
  indexCompatibility = null;
6255
- indexingLockPath = "";
6702
+ activeIndexLease = null;
6703
+ initializationPromise = null;
6704
+ initializationMode = "none";
6705
+ readIssues = [];
6706
+ retiredDatabases = [];
6707
+ readerArtifactFingerprint = null;
6708
+ writerArtifactFingerprint = null;
6709
+ readerArtifactRetryAfter = /* @__PURE__ */ new Map();
6256
6710
  constructor(projectRoot, config, host = "opencode") {
6257
6711
  this.projectRoot = projectRoot;
6258
6712
  this.config = config;
6259
6713
  this.host = host;
6260
6714
  this.indexPath = this.getIndexPath();
6261
- this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6262
- this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6263
- this.indexingLockPath = path11.join(this.indexPath, "indexing.lock");
6715
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
6716
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
6264
6717
  this.logger = initializeLogger(config.debug);
6265
6718
  }
6266
6719
  getIndexPath() {
6267
6720
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6268
6721
  }
6722
+ isLocalProjectIndexPath() {
6723
+ const localProjectIndexPaths = [path12.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6724
+ if (this.host !== "opencode") {
6725
+ localProjectIndexPaths.push(path12.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6726
+ }
6727
+ return localProjectIndexPaths.some((localPath) => {
6728
+ if (!(0, import_fs8.existsSync)(localPath) || !(0, import_fs8.existsSync)(this.indexPath)) {
6729
+ return path12.resolve(this.indexPath) === path12.resolve(localPath);
6730
+ }
6731
+ const indexStats = (0, import_fs8.statSync)(this.indexPath);
6732
+ const localStats = (0, import_fs8.statSync)(localPath);
6733
+ return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
6734
+ });
6735
+ }
6736
+ resetLoadedIndexState(retireDatabase = false) {
6737
+ if (this.database) {
6738
+ if (retireDatabase) {
6739
+ this.retiredDatabases.push(this.database);
6740
+ } else {
6741
+ this.database.close();
6742
+ }
6743
+ }
6744
+ this.store = null;
6745
+ this.invertedIndex = null;
6746
+ this.database = null;
6747
+ this.provider = null;
6748
+ this.configuredProviderInfo = null;
6749
+ this.reranker = null;
6750
+ this.indexCompatibility = null;
6751
+ this.initializationMode = "none";
6752
+ this.readIssues = [];
6753
+ this.readerArtifactFingerprint = null;
6754
+ this.writerArtifactFingerprint = null;
6755
+ this.readerArtifactRetryAfter.clear();
6756
+ this.fileHashCache.clear();
6757
+ }
6758
+ refreshLoadedIndexState() {
6759
+ if (!this.store || !this.invertedIndex || !this.configuredProviderInfo) return;
6760
+ this.store.load();
6761
+ this.invertedIndex.load();
6762
+ this.fileHashCache.clear();
6763
+ this.loadFileHashCache();
6764
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
6765
+ this.readIssues = [];
6766
+ this.readerArtifactRetryAfter.clear();
6767
+ }
6768
+ async withIndexMutationLease(operation, callback) {
6769
+ const lease = acquireIndexLock(this.indexPath, operation);
6770
+ this.indexPath = lease.canonicalIndexPath;
6771
+ this.fileHashCachePath = path12.join(this.indexPath, "file-hashes.json");
6772
+ this.failedBatchesPath = path12.join(this.indexPath, "failed-batches.json");
6773
+ this.activeIndexLease = lease;
6774
+ let result;
6775
+ let callbackError;
6776
+ let callbackFailed = false;
6777
+ try {
6778
+ result = await callback(lease.recoveries.map(({ owner }) => owner));
6779
+ } catch (error) {
6780
+ callbackFailed = true;
6781
+ callbackError = error;
6782
+ }
6783
+ if (!callbackFailed) {
6784
+ try {
6785
+ completeLeaseRecovery(lease);
6786
+ this.writerArtifactFingerprint = this.captureReaderArtifactFingerprint();
6787
+ } catch (error) {
6788
+ callbackFailed = true;
6789
+ callbackError = error;
6790
+ }
6791
+ }
6792
+ let releaseError;
6793
+ try {
6794
+ if (!releaseIndexLock(lease)) {
6795
+ releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
6796
+ this.writerArtifactFingerprint = null;
6797
+ if (this.activeIndexLease?.owner.token === lease.owner.token) {
6798
+ this.activeIndexLease = null;
6799
+ }
6800
+ } else if (this.activeIndexLease?.owner.token === lease.owner.token) {
6801
+ this.activeIndexLease = null;
6802
+ }
6803
+ } catch (error) {
6804
+ releaseError = error;
6805
+ this.writerArtifactFingerprint = null;
6806
+ if (!(0, import_fs8.existsSync)(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6807
+ this.activeIndexLease = null;
6808
+ }
6809
+ }
6810
+ if (releaseError !== void 0) {
6811
+ if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
6812
+ throw releaseError;
6813
+ }
6814
+ if (callbackFailed) throw callbackError;
6815
+ return result;
6816
+ }
6817
+ requireActiveLease() {
6818
+ if (!this.activeIndexLease) {
6819
+ throw new Error("Index mutation attempted without an active interprocess lease");
6820
+ }
6821
+ return this.activeIndexLease;
6822
+ }
6269
6823
  loadFileHashCache() {
6270
- if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
6824
+ if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
6271
6825
  return;
6272
6826
  }
6273
6827
  try {
6274
- const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
6828
+ const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
6275
6829
  const parsed = JSON.parse(data);
6276
6830
  this.fileHashCache = new Map(Object.entries(parsed));
6277
6831
  } catch (error) {
@@ -6291,15 +6845,26 @@ var Indexer = class {
6291
6845
  this.atomicWriteSync(this.fileHashCachePath, JSON.stringify(obj));
6292
6846
  }
6293
6847
  atomicWriteSync(targetPath, data) {
6294
- const tempPath = `${targetPath}.tmp`;
6295
- (0, import_fs7.mkdirSync)(path11.dirname(targetPath), { recursive: true });
6296
- (0, import_fs7.writeFileSync)(tempPath, data);
6297
- (0, import_fs7.renameSync)(tempPath, targetPath);
6848
+ const lease = this.requireActiveLease();
6849
+ const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
6850
+ (0, import_fs8.mkdirSync)(path12.dirname(targetPath), { recursive: true });
6851
+ try {
6852
+ (0, import_fs8.writeFileSync)(tempPath, data);
6853
+ (0, import_fs8.renameSync)(tempPath, targetPath);
6854
+ } finally {
6855
+ removeLeaseTemporaryPath(tempPath);
6856
+ }
6857
+ }
6858
+ saveInvertedIndex(invertedIndex) {
6859
+ this.atomicWriteSync(
6860
+ path12.join(this.indexPath, "inverted-index.json"),
6861
+ invertedIndex.serialize()
6862
+ );
6298
6863
  }
6299
6864
  getScopedRoots() {
6300
- const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6865
+ const roots = /* @__PURE__ */ new Set([path12.resolve(this.projectRoot)]);
6301
6866
  for (const kbRoot of this.config.knowledgeBases) {
6302
- roots.add(path11.resolve(this.projectRoot, kbRoot));
6867
+ roots.add(path12.resolve(this.projectRoot, kbRoot));
6303
6868
  }
6304
6869
  return Array.from(roots);
6305
6870
  }
@@ -6308,29 +6873,29 @@ var Indexer = class {
6308
6873
  if (this.config.scope !== "global") {
6309
6874
  return branchName;
6310
6875
  }
6311
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6876
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6312
6877
  return `${projectHash}:${branchName}`;
6313
6878
  }
6314
6879
  getBranchCatalogKeyFor(branchName) {
6315
6880
  if (this.config.scope !== "global") {
6316
6881
  return branchName;
6317
6882
  }
6318
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6883
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6319
6884
  return `${projectHash}:${branchName}`;
6320
6885
  }
6321
6886
  getLegacyBranchCatalogKey() {
6322
6887
  return this.currentBranch || "default";
6323
6888
  }
6324
6889
  getLegacyMigrationMetadataKey() {
6325
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6890
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6326
6891
  return `index.globalBranchMigration.${projectHash}`;
6327
6892
  }
6328
6893
  getProjectEmbeddingStrategyMetadataKey() {
6329
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6894
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6330
6895
  return `index.embeddingStrategyVersion.${projectHash}`;
6331
6896
  }
6332
6897
  getProjectForceReembedMetadataKey() {
6333
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6898
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6334
6899
  return `index.forceReembed.${projectHash}`;
6335
6900
  }
6336
6901
  hasProjectForceReembedPending() {
@@ -6424,7 +6989,7 @@ var Indexer = class {
6424
6989
  if (!this.database) {
6425
6990
  return { chunkIds, symbolIds };
6426
6991
  }
6427
- const projectRootPath = path11.resolve(this.projectRoot);
6992
+ const projectRootPath = path12.resolve(this.projectRoot);
6428
6993
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6429
6994
  ...Array.from(this.fileHashCache.keys()).filter(
6430
6995
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6447,7 +7012,7 @@ var Indexer = class {
6447
7012
  if (this.config.scope !== "global") {
6448
7013
  return this.getBranchCatalogCleanupKeys();
6449
7014
  }
6450
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
7015
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6451
7016
  const keys = /* @__PURE__ */ new Set();
6452
7017
  const projectChunkIdSet = new Set(projectChunkIds);
6453
7018
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -6531,7 +7096,7 @@ var Indexer = class {
6531
7096
  if (!this.database || this.config.scope !== "global") {
6532
7097
  return false;
6533
7098
  }
6534
- const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
7099
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
6535
7100
  const roots = this.getScopedRoots();
6536
7101
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
6537
7102
  return this.database.getAllBranches().some(
@@ -6565,7 +7130,7 @@ var Indexer = class {
6565
7130
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
6566
7131
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
6567
7132
  ]);
6568
- const projectRootPath = path11.resolve(this.projectRoot);
7133
+ const projectRootPath = path12.resolve(this.projectRoot);
6569
7134
  const projectLocalFilePaths = new Set(
6570
7135
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
6571
7136
  );
@@ -6627,34 +7192,27 @@ var Indexer = class {
6627
7192
  database.gcOrphanEmbeddings();
6628
7193
  database.gcOrphanChunks();
6629
7194
  store.save();
6630
- invertedIndex.save();
7195
+ this.saveInvertedIndex(invertedIndex);
6631
7196
  return {
6632
7197
  removedChunkIds: removedChunkIdList,
6633
7198
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
6634
7199
  };
6635
7200
  }
6636
- checkForInterruptedIndexing() {
6637
- return (0, import_fs7.existsSync)(this.indexingLockPath);
6638
- }
6639
- acquireIndexingLock() {
6640
- const lockData = {
6641
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
6642
- pid: process.pid
6643
- };
6644
- (0, import_fs7.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
6645
- }
6646
- releaseIndexingLock() {
6647
- if ((0, import_fs7.existsSync)(this.indexingLockPath)) {
6648
- (0, import_fs7.unlinkSync)(this.indexingLockPath);
7201
+ async recoverFromInterruptedIndexingUnlocked(owners) {
7202
+ for (const owner of owners) {
7203
+ this.logger.warn("Detected interrupted indexing session, recovering...", {
7204
+ pid: owner.pid,
7205
+ hostname: owner.hostname,
7206
+ operation: owner.operation,
7207
+ startedAt: owner.startedAt
7208
+ });
6649
7209
  }
6650
- }
6651
- async recoverFromInterruptedIndexing() {
6652
- this.logger.warn("Detected interrupted indexing session, recovering...");
6653
- if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
6654
- (0, import_fs7.unlinkSync)(this.fileHashCachePath);
7210
+ if (this.config.scope === "global") {
7211
+ if ((0, import_fs8.existsSync)(this.fileHashCachePath)) {
7212
+ (0, import_fs8.unlinkSync)(this.fileHashCachePath);
7213
+ }
7214
+ await this.healthCheckUnlocked();
6655
7215
  }
6656
- await this.healthCheck();
6657
- this.releaseIndexingLock();
6658
7216
  this.logger.info("Recovery complete, next index will re-process all files");
6659
7217
  }
6660
7218
  loadFailedBatches(maxChunkTokens) {
@@ -6670,10 +7228,10 @@ var Indexer = class {
6670
7228
  }
6671
7229
  }
6672
7230
  loadSerializedFailedBatches() {
6673
- if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
7231
+ if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
6674
7232
  return [];
6675
7233
  }
6676
- const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
7234
+ const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
6677
7235
  const parsed = JSON.parse(data);
6678
7236
  return parsed.map((batch) => {
6679
7237
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -6690,15 +7248,15 @@ var Indexer = class {
6690
7248
  }
6691
7249
  saveFailedBatches(batches) {
6692
7250
  if (batches.length === 0) {
6693
- if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
7251
+ if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
6694
7252
  try {
6695
- (0, import_fs7.unlinkSync)(this.failedBatchesPath);
7253
+ (0, import_fs8.unlinkSync)(this.failedBatchesPath);
6696
7254
  } catch {
6697
7255
  }
6698
7256
  }
6699
7257
  return;
6700
7258
  }
6701
- (0, import_fs7.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
7259
+ this.atomicWriteSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
6702
7260
  }
6703
7261
  collectRetryableFailedChunks(currentFileHashes, unchangedFilePaths, maxChunkTokens) {
6704
7262
  const retryableById = /* @__PURE__ */ new Map();
@@ -6878,7 +7436,7 @@ var Indexer = class {
6878
7436
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
6879
7437
  parts.push(`intent_hint: ${intent}`);
6880
7438
  try {
6881
- const fileContent = await import_fs7.promises.readFile(candidate.metadata.filePath, "utf-8");
7439
+ const fileContent = await import_fs8.promises.readFile(candidate.metadata.filePath, "utf-8");
6882
7440
  const lines = fileContent.split("\n");
6883
7441
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
6884
7442
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -6892,6 +7450,222 @@ var Indexer = class {
6892
7450
  return parts.join("\n");
6893
7451
  }
6894
7452
  async initialize() {
7453
+ if (this.initializationPromise) {
7454
+ await this.initializationPromise;
7455
+ }
7456
+ if (this.isInitializedFor("reader")) {
7457
+ return;
7458
+ }
7459
+ await this.initializeOnce("reader", [], { skipAutoGc: true });
7460
+ }
7461
+ async initializeOnce(mode, recoveredOwners, options) {
7462
+ if (this.initializationPromise) {
7463
+ await this.initializationPromise;
7464
+ if (this.isInitializedFor(mode)) {
7465
+ return;
7466
+ }
7467
+ return this.initializeOnce(mode, recoveredOwners, options);
7468
+ }
7469
+ if (this.isInitializedFor(mode)) {
7470
+ return;
7471
+ }
7472
+ const initialization = this.initializeUnlocked(mode, recoveredOwners, options).catch((error) => {
7473
+ this.resetLoadedIndexState();
7474
+ throw error;
7475
+ }).finally(() => {
7476
+ if (this.initializationPromise === initialization) {
7477
+ this.initializationPromise = null;
7478
+ }
7479
+ });
7480
+ this.initializationPromise = initialization;
7481
+ await initialization;
7482
+ }
7483
+ isInitializedFor(mode) {
7484
+ const hasState = Boolean(
7485
+ this.store && this.provider && this.invertedIndex && this.configuredProviderInfo && this.database
7486
+ );
7487
+ if (!hasState) {
7488
+ return false;
7489
+ }
7490
+ return mode === "reader" ? this.initializationMode !== "none" : this.initializationMode === "writer";
7491
+ }
7492
+ recordReadIssue(component, message, error) {
7493
+ this.readIssues.push(this.createReadIssue(component, message));
7494
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7495
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7496
+ }
7497
+ createReadIssue(component, message) {
7498
+ return {
7499
+ component,
7500
+ message,
7501
+ blocking: component !== "keyword"
7502
+ };
7503
+ }
7504
+ getVectorReadIssueMessage() {
7505
+ if (this.config.scope === "global") {
7506
+ return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
7507
+ }
7508
+ if (!this.isLocalProjectIndexPath()) {
7509
+ 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.";
7510
+ }
7511
+ 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.";
7512
+ }
7513
+ getKeywordReadIssueMessage() {
7514
+ if (this.config.scope === "global") {
7515
+ 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.";
7516
+ }
7517
+ if (!this.isLocalProjectIndexPath()) {
7518
+ 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.";
7519
+ }
7520
+ 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.";
7521
+ }
7522
+ getDatabaseReadIssueMessage() {
7523
+ if (this.config.scope === "global") {
7524
+ return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
7525
+ }
7526
+ if (!this.isLocalProjectIndexPath()) {
7527
+ 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.";
7528
+ }
7529
+ return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
7530
+ }
7531
+ getReaderFileFingerprint(filePath, identityOnly = false) {
7532
+ try {
7533
+ const stats = (0, import_fs8.statSync)(filePath);
7534
+ if (identityOnly) {
7535
+ return `${stats.dev}:${stats.ino}`;
7536
+ }
7537
+ return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`;
7538
+ } catch (error) {
7539
+ return `unavailable:${getErrorMessage2(error)}`;
7540
+ }
7541
+ }
7542
+ captureReaderArtifactFingerprint() {
7543
+ const storePath = path12.join(this.indexPath, "vectors");
7544
+ return {
7545
+ vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
7546
+ keyword: this.getReaderFileFingerprint(path12.join(this.indexPath, "inverted-index.json")),
7547
+ database: this.getReaderFileFingerprint(path12.join(this.indexPath, "codebase.db")),
7548
+ databaseIdentity: this.getReaderFileFingerprint(path12.join(this.indexPath, "codebase.db"), true)
7549
+ };
7550
+ }
7551
+ refreshReaderArtifacts() {
7552
+ if (this.initializationMode !== "reader" || !this.configuredProviderInfo) {
7553
+ return;
7554
+ }
7555
+ const previousFingerprint = this.readerArtifactFingerprint;
7556
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7557
+ const issues = new Map(this.readIssues.map((issue) => [issue.component, issue]));
7558
+ const retryDue = (component) => issues.has(component) && Date.now() >= (this.readerArtifactRetryAfter.get(component) ?? 0);
7559
+ const vectorsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors;
7560
+ const keywordChanged = !previousFingerprint || currentFingerprint.keyword !== previousFingerprint.keyword;
7561
+ const databaseChanged = !previousFingerprint || currentFingerprint.database !== previousFingerprint.database;
7562
+ const databaseReplaced = !previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7563
+ if (previousFingerprint && !vectorsChanged && !keywordChanged && !databaseChanged && !Array.from(issues.keys()).some(retryDue)) {
7564
+ return;
7565
+ }
7566
+ const setIssue = (component, message, error) => {
7567
+ if (!issues.has(component)) {
7568
+ this.logger.warn(message, error === void 0 ? void 0 : { error: getErrorMessage2(error) });
7569
+ }
7570
+ issues.set(component, this.createReadIssue(component, message));
7571
+ this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7572
+ };
7573
+ const storePath = path12.join(this.indexPath, "vectors");
7574
+ const vectorMetadataPath = `${storePath}.meta.json`;
7575
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
7576
+ const dbPath = path12.join(this.indexPath, "codebase.db");
7577
+ if (vectorsChanged || retryDue("vectors")) {
7578
+ const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7579
+ const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7580
+ if (vectorStoreExists && vectorMetadataExists) {
7581
+ try {
7582
+ const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
7583
+ store.loadStrict();
7584
+ this.store = store;
7585
+ issues.delete("vectors");
7586
+ this.readerArtifactRetryAfter.delete("vectors");
7587
+ } catch (error) {
7588
+ setIssue("vectors", this.getVectorReadIssueMessage(), error);
7589
+ }
7590
+ } else if (vectorStoreExists !== vectorMetadataExists || issues.has("vectors")) {
7591
+ setIssue("vectors", this.getVectorReadIssueMessage());
7592
+ }
7593
+ }
7594
+ if (keywordChanged || retryDue("keyword") || !(0, import_fs8.existsSync)(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7595
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7596
+ try {
7597
+ const invertedIndex = new InvertedIndex(invertedIndexPath);
7598
+ invertedIndex.load();
7599
+ this.invertedIndex = invertedIndex;
7600
+ issues.delete("keyword");
7601
+ this.readerArtifactRetryAfter.delete("keyword");
7602
+ } catch (error) {
7603
+ setIssue("keyword", this.getKeywordReadIssueMessage(), error);
7604
+ }
7605
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("keyword")) {
7606
+ setIssue("keyword", this.getKeywordReadIssueMessage());
7607
+ }
7608
+ }
7609
+ if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
7610
+ if ((0, import_fs8.existsSync)(dbPath)) {
7611
+ try {
7612
+ const database = Database.openReadOnly(dbPath);
7613
+ if (this.database) {
7614
+ this.retiredDatabases.push(this.database);
7615
+ }
7616
+ this.database = database;
7617
+ issues.delete("database");
7618
+ this.readerArtifactRetryAfter.delete("database");
7619
+ } catch (error) {
7620
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7621
+ }
7622
+ } else if ((this.store?.count() ?? 0) > 0 || issues.has("database")) {
7623
+ setIssue("database", this.getDatabaseReadIssueMessage());
7624
+ }
7625
+ }
7626
+ if (!issues.has("database")) {
7627
+ try {
7628
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
7629
+ } catch (error) {
7630
+ setIssue("database", this.getDatabaseReadIssueMessage(), error);
7631
+ }
7632
+ }
7633
+ this.readIssues = Array.from(issues.values());
7634
+ this.readerArtifactFingerprint = currentFingerprint;
7635
+ }
7636
+ refreshInactiveWriterArtifacts() {
7637
+ if (this.initializationMode !== "writer" || this.activeIndexLease) {
7638
+ return true;
7639
+ }
7640
+ const previousFingerprint = this.writerArtifactFingerprint;
7641
+ const currentFingerprint = this.captureReaderArtifactFingerprint();
7642
+ const retryDue = this.readIssues.some(
7643
+ (issue) => Date.now() >= (this.readerArtifactRetryAfter.get(issue.component) ?? 0)
7644
+ );
7645
+ const artifactsChanged = !previousFingerprint || currentFingerprint.vectors !== previousFingerprint.vectors || currentFingerprint.keyword !== previousFingerprint.keyword || currentFingerprint.database !== previousFingerprint.database || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity;
7646
+ if (!artifactsChanged && !retryDue) {
7647
+ return true;
7648
+ }
7649
+ if (!previousFingerprint || currentFingerprint.databaseIdentity !== previousFingerprint.databaseIdentity) {
7650
+ return false;
7651
+ }
7652
+ this.initializationMode = "reader";
7653
+ this.readerArtifactFingerprint = previousFingerprint;
7654
+ try {
7655
+ this.refreshReaderArtifacts();
7656
+ this.writerArtifactFingerprint = this.readerArtifactFingerprint ?? currentFingerprint;
7657
+ } finally {
7658
+ this.readerArtifactFingerprint = null;
7659
+ this.initializationMode = "writer";
7660
+ }
7661
+ return true;
7662
+ }
7663
+ async initializeUnlocked(mode, recoveredOwners = [], options = {}) {
7664
+ if (mode === "writer") {
7665
+ this.requireActiveLease();
7666
+ }
7667
+ this.readIssues = [];
7668
+ this.readerArtifactRetryAfter.clear();
6895
7669
  if (this.config.embeddingProvider === "custom") {
6896
7670
  if (!this.config.customProvider) {
6897
7671
  throw new Error("embeddingProvider is 'custom' but customProvider config is missing.");
@@ -6923,36 +7697,103 @@ var Indexer = class {
6923
7697
  });
6924
7698
  }
6925
7699
  }
6926
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
6927
7700
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
6928
- const storePath = path11.join(this.indexPath, "vectors");
6929
- this.store = new VectorStore(storePath, dimensions);
6930
- const indexFilePath = path11.join(this.indexPath, "vectors.usearch");
6931
- if ((0, import_fs7.existsSync)(indexFilePath)) {
6932
- this.store.load();
6933
- }
6934
- const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
6935
- this.invertedIndex = new InvertedIndex(invertedIndexPath);
6936
- try {
6937
- this.invertedIndex.load();
6938
- } catch {
6939
- if ((0, import_fs7.existsSync)(invertedIndexPath)) {
6940
- await import_fs7.promises.unlink(invertedIndexPath);
7701
+ const storePath = path12.join(this.indexPath, "vectors");
7702
+ const vectorMetadataPath = `${storePath}.meta.json`;
7703
+ const invertedIndexPath = path12.join(this.indexPath, "inverted-index.json");
7704
+ const dbPath = path12.join(this.indexPath, "codebase.db");
7705
+ let dbIsNew = !(0, import_fs8.existsSync)(dbPath);
7706
+ const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
7707
+ if (mode === "writer") {
7708
+ await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7709
+ if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
7710
+ throw new Error(
7711
+ "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
7712
+ );
7713
+ }
7714
+ for (const recoveredOwner of recoveredOwners) {
7715
+ recoverLeaseArtifacts(this.indexPath, recoveredOwner, [
7716
+ storePath,
7717
+ `${storePath}.meta.json`
7718
+ ]);
7719
+ }
7720
+ if (recoveredOwners.length > 0 && this.config.scope === "project") {
7721
+ await this.resetLocalIndexArtifacts();
7722
+ }
7723
+ this.store = new VectorStore(storePath, dimensions);
7724
+ if ((0, import_fs8.existsSync)(storePath) || (0, import_fs8.existsSync)(vectorMetadataPath)) {
7725
+ this.store.load();
6941
7726
  }
6942
7727
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6943
- }
6944
- const dbPath = path11.join(this.indexPath, "codebase.db");
6945
- let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
6946
- try {
6947
- this.database = new Database(dbPath);
6948
- } catch (error) {
6949
- if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
6950
- throw error;
7728
+ try {
7729
+ this.invertedIndex.load();
7730
+ } catch {
7731
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7732
+ await import_fs8.promises.unlink(invertedIndexPath);
7733
+ }
7734
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7735
+ }
7736
+ try {
7737
+ this.database = new Database(dbPath);
7738
+ } catch (error) {
7739
+ if (!await this.tryResetCorruptedIndex("initializing index database", error)) {
7740
+ throw error;
7741
+ }
7742
+ this.store = new VectorStore(storePath, dimensions);
7743
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7744
+ this.database = new Database(dbPath);
7745
+ dbIsNew = true;
6951
7746
  }
7747
+ } else {
6952
7748
  this.store = new VectorStore(storePath, dimensions);
7749
+ const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7750
+ const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7751
+ const vectorReadFailureMessage = this.getVectorReadIssueMessage();
7752
+ if (vectorStoreExists !== vectorMetadataExists) {
7753
+ this.recordReadIssue("vectors", vectorReadFailureMessage);
7754
+ } else if (vectorStoreExists) {
7755
+ try {
7756
+ this.store.loadStrict();
7757
+ } catch (error) {
7758
+ this.recordReadIssue("vectors", vectorReadFailureMessage, error);
7759
+ this.store = new VectorStore(storePath, dimensions);
7760
+ }
7761
+ }
6953
7762
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
6954
- this.database = new Database(dbPath);
6955
- dbIsNew = true;
7763
+ if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7764
+ try {
7765
+ this.invertedIndex.load();
7766
+ } catch (error) {
7767
+ this.recordReadIssue(
7768
+ "keyword",
7769
+ this.getKeywordReadIssueMessage(),
7770
+ error
7771
+ );
7772
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
7773
+ }
7774
+ } else if (this.store.count() > 0) {
7775
+ this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
7776
+ }
7777
+ if ((0, import_fs8.existsSync)(dbPath)) {
7778
+ try {
7779
+ this.database = Database.openReadOnly(dbPath);
7780
+ } catch (error) {
7781
+ this.recordReadIssue(
7782
+ "database",
7783
+ this.getDatabaseReadIssueMessage(),
7784
+ error
7785
+ );
7786
+ this.database = Database.createEmptyReadOnly();
7787
+ }
7788
+ } else {
7789
+ this.database = Database.createEmptyReadOnly();
7790
+ if (this.store.count() > 0) {
7791
+ this.recordReadIssue(
7792
+ "database",
7793
+ `Index database is missing for the published vectors. ${this.getDatabaseReadIssueMessage()}`
7794
+ );
7795
+ }
7796
+ }
6956
7797
  }
6957
7798
  if (isGitRepo(this.projectRoot)) {
6958
7799
  this.currentBranch = getBranchOrDefault(this.projectRoot);
@@ -6966,10 +7807,10 @@ var Indexer = class {
6966
7807
  this.baseBranch = "default";
6967
7808
  this.logger.branch("debug", "Not a git repository, using default branch");
6968
7809
  }
6969
- if (this.checkForInterruptedIndexing()) {
6970
- await this.recoverFromInterruptedIndexing();
7810
+ if (mode === "writer" && recoveredOwners.length > 0) {
7811
+ await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
6971
7812
  }
6972
- if (dbIsNew && this.store.count() > 0) {
7813
+ if (mode === "writer" && dbIsNew && this.store.count() > 0) {
6973
7814
  this.migrateFromLegacyIndex();
6974
7815
  }
6975
7816
  this.loadFileHashCache();
@@ -6981,9 +7822,11 @@ var Indexer = class {
6981
7822
  configuredProviderInfo: this.configuredProviderInfo
6982
7823
  });
6983
7824
  }
6984
- if (this.config.indexing.autoGc) {
7825
+ if (mode === "writer" && this.config.indexing.autoGc && !options.skipAutoGc) {
6985
7826
  await this.maybeRunAutoGc();
6986
7827
  }
7828
+ this.initializationMode = mode;
7829
+ this.readerArtifactFingerprint = readerArtifactFingerprint;
6987
7830
  }
6988
7831
  async maybeRunAutoGc() {
6989
7832
  if (!this.database) return;
@@ -7000,7 +7843,7 @@ var Indexer = class {
7000
7843
  }
7001
7844
  }
7002
7845
  if (shouldRunGc) {
7003
- const result = await this.healthCheck();
7846
+ const result = await this.healthCheckUnlocked();
7004
7847
  if (result.warning) {
7005
7848
  this.database.setMetadata(STARTUP_WARNING_METADATA_KEY, result.warning);
7006
7849
  } else {
@@ -7022,7 +7865,7 @@ var Indexer = class {
7022
7865
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
7023
7866
  return {
7024
7867
  resetCorruptedIndex: true,
7025
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
7868
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
7026
7869
  };
7027
7870
  }
7028
7871
  throw error;
@@ -7037,28 +7880,29 @@ var Indexer = class {
7037
7880
  return;
7038
7881
  }
7039
7882
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
7040
- const storeBasePath = path11.join(this.indexPath, "vectors");
7041
- const storeIndexPath = `${storeBasePath}.usearch`;
7883
+ const storeBasePath = path12.join(this.indexPath, "vectors");
7884
+ const storeIndexPath = storeBasePath;
7042
7885
  const storeMetadataPath = `${storeBasePath}.meta.json`;
7043
- const backupIndexPath = `${storeIndexPath}.bak`;
7044
- const backupMetadataPath = `${storeMetadataPath}.bak`;
7886
+ const lease = this.requireActiveLease();
7887
+ const backupIndexPath = createLeaseTemporaryPath(storeIndexPath, lease.owner, "bak");
7888
+ const backupMetadataPath = createLeaseTemporaryPath(storeMetadataPath, lease.owner, "bak");
7045
7889
  let backedUpIndex = false;
7046
7890
  let backedUpMetadata = false;
7047
7891
  let rebuiltCount = 0;
7048
7892
  let skippedCount = 0;
7049
- if ((0, import_fs7.existsSync)(backupIndexPath)) {
7050
- (0, import_fs7.unlinkSync)(backupIndexPath);
7893
+ if ((0, import_fs8.existsSync)(backupIndexPath)) {
7894
+ (0, import_fs8.unlinkSync)(backupIndexPath);
7051
7895
  }
7052
- if ((0, import_fs7.existsSync)(backupMetadataPath)) {
7053
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7896
+ if ((0, import_fs8.existsSync)(backupMetadataPath)) {
7897
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
7054
7898
  }
7055
7899
  try {
7056
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
7057
- (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
7900
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7901
+ (0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
7058
7902
  backedUpIndex = true;
7059
7903
  }
7060
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
7061
- (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
7904
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7905
+ (0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
7062
7906
  backedUpMetadata = true;
7063
7907
  }
7064
7908
  store.clear();
@@ -7078,11 +7922,11 @@ var Indexer = class {
7078
7922
  rebuiltCount += 1;
7079
7923
  }
7080
7924
  store.save();
7081
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
7082
- (0, import_fs7.unlinkSync)(backupIndexPath);
7925
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7926
+ (0, import_fs8.unlinkSync)(backupIndexPath);
7083
7927
  }
7084
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
7085
- (0, import_fs7.unlinkSync)(backupMetadataPath);
7928
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7929
+ (0, import_fs8.unlinkSync)(backupMetadataPath);
7086
7930
  }
7087
7931
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
7088
7932
  excludedChunks: excludedSet.size,
@@ -7094,17 +7938,17 @@ var Indexer = class {
7094
7938
  store.clear();
7095
7939
  } catch {
7096
7940
  }
7097
- if ((0, import_fs7.existsSync)(storeIndexPath)) {
7098
- (0, import_fs7.unlinkSync)(storeIndexPath);
7941
+ if ((0, import_fs8.existsSync)(storeIndexPath)) {
7942
+ (0, import_fs8.unlinkSync)(storeIndexPath);
7099
7943
  }
7100
- if ((0, import_fs7.existsSync)(storeMetadataPath)) {
7101
- (0, import_fs7.unlinkSync)(storeMetadataPath);
7944
+ if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7945
+ (0, import_fs8.unlinkSync)(storeMetadataPath);
7102
7946
  }
7103
- if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
7104
- (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
7947
+ if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7948
+ (0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
7105
7949
  }
7106
- if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
7107
- (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
7950
+ if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7951
+ (0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
7108
7952
  }
7109
7953
  if (backedUpIndex || backedUpMetadata) {
7110
7954
  store.load();
@@ -7118,11 +7962,37 @@ var Indexer = class {
7118
7962
  }
7119
7963
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
7120
7964
  }
7965
+ async resetLocalIndexArtifacts() {
7966
+ this.store = null;
7967
+ this.invertedIndex = null;
7968
+ this.database?.close();
7969
+ this.database = null;
7970
+ this.indexCompatibility = null;
7971
+ this.initializationMode = "none";
7972
+ this.readIssues = [];
7973
+ this.readerArtifactFingerprint = null;
7974
+ this.writerArtifactFingerprint = null;
7975
+ this.readerArtifactRetryAfter.clear();
7976
+ this.fileHashCache.clear();
7977
+ const resetPaths = [
7978
+ path12.join(this.indexPath, "codebase.db"),
7979
+ path12.join(this.indexPath, "codebase.db-shm"),
7980
+ path12.join(this.indexPath, "codebase.db-wal"),
7981
+ path12.join(this.indexPath, "vectors"),
7982
+ path12.join(this.indexPath, "vectors.usearch"),
7983
+ path12.join(this.indexPath, "vectors.meta.json"),
7984
+ path12.join(this.indexPath, "inverted-index.json"),
7985
+ path12.join(this.indexPath, "file-hashes.json"),
7986
+ path12.join(this.indexPath, "failed-batches.json")
7987
+ ];
7988
+ await Promise.all(resetPaths.map((targetPath) => import_fs8.promises.rm(targetPath, { recursive: true, force: true })));
7989
+ await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7990
+ }
7121
7991
  async tryResetCorruptedIndex(stage, error) {
7122
7992
  if (!isSqliteCorruptionError(error)) {
7123
7993
  return false;
7124
7994
  }
7125
- const dbPath = path11.join(this.indexPath, "codebase.db");
7995
+ const dbPath = path12.join(this.indexPath, "codebase.db");
7126
7996
  const warning = this.getCorruptedIndexWarning(dbPath);
7127
7997
  const errorMessage = getErrorMessage2(error);
7128
7998
  if (this.config.scope === "global") {
@@ -7138,30 +8008,7 @@ var Indexer = class {
7138
8008
  dbPath,
7139
8009
  error: errorMessage
7140
8010
  });
7141
- this.store = null;
7142
- this.invertedIndex = null;
7143
- this.database?.close();
7144
- this.database = null;
7145
- this.indexCompatibility = null;
7146
- this.fileHashCache.clear();
7147
- const resetPaths = [
7148
- path11.join(this.indexPath, "codebase.db"),
7149
- path11.join(this.indexPath, "codebase.db-shm"),
7150
- path11.join(this.indexPath, "codebase.db-wal"),
7151
- path11.join(this.indexPath, "vectors.usearch"),
7152
- path11.join(this.indexPath, "inverted-index.json"),
7153
- path11.join(this.indexPath, "file-hashes.json"),
7154
- path11.join(this.indexPath, "failed-batches.json"),
7155
- path11.join(this.indexPath, "indexing.lock"),
7156
- path11.join(this.indexPath, "vectors")
7157
- ];
7158
- await Promise.all(resetPaths.map(async (targetPath) => {
7159
- try {
7160
- await import_fs7.promises.rm(targetPath, { recursive: true, force: true });
7161
- } catch {
7162
- }
7163
- }));
7164
- await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
8011
+ await this.resetLocalIndexArtifacts();
7165
8012
  return true;
7166
8013
  }
7167
8014
  migrateFromLegacyIndex() {
@@ -7280,8 +8127,62 @@ var Indexer = class {
7280
8127
  return this.indexCompatibility;
7281
8128
  }
7282
8129
  async ensureInitialized() {
8130
+ let initializedReader = false;
8131
+ while (true) {
8132
+ if (this.initializationPromise) {
8133
+ await this.initializationPromise;
8134
+ }
8135
+ if (!this.isInitializedFor("reader")) {
8136
+ await this.initialize();
8137
+ initializedReader = true;
8138
+ continue;
8139
+ }
8140
+ if (this.initializationMode === "writer" && !this.activeIndexLease && !initializedReader) {
8141
+ if (!this.refreshInactiveWriterArtifacts()) {
8142
+ this.resetLoadedIndexState(true);
8143
+ await this.initialize();
8144
+ initializedReader = true;
8145
+ continue;
8146
+ }
8147
+ }
8148
+ if (this.initializationMode === "reader" && !initializedReader) {
8149
+ this.refreshReaderArtifacts();
8150
+ }
8151
+ const state = this.requireLoadedIndexState();
8152
+ return {
8153
+ ...state,
8154
+ readIssues: [...this.readIssues],
8155
+ compatibility: this.indexCompatibility ?? this.validateIndexCompatibility(state.configuredProviderInfo)
8156
+ };
8157
+ }
8158
+ }
8159
+ async ensureInitializedUnlocked(recoveredOwners = []) {
8160
+ this.requireActiveLease();
8161
+ if (this.initializationPromise) {
8162
+ await this.initializationPromise;
8163
+ }
8164
+ if (recoveredOwners.length > 0 || !this.isInitializedFor("writer")) {
8165
+ const retireReaderDatabase = this.initializationMode === "reader";
8166
+ this.resetLoadedIndexState(retireReaderDatabase);
8167
+ await this.initializeOnce("writer", recoveredOwners, { skipAutoGc: true });
8168
+ } else {
8169
+ this.refreshLoadedIndexState();
8170
+ }
8171
+ if (this.config.indexing.autoGc) {
8172
+ await this.maybeRunAutoGc();
8173
+ }
8174
+ return this.requireLoadedIndexState();
8175
+ }
8176
+ requireReadableComponents(readIssues, ...components) {
8177
+ const componentSet = new Set(components);
8178
+ const issues = readIssues.filter((issue) => issue.blocking && componentSet.has(issue.component));
8179
+ if (issues.length > 0) {
8180
+ throw new Error(issues.map((issue) => issue.message).join(" "));
8181
+ }
8182
+ }
8183
+ requireLoadedIndexState() {
7283
8184
  if (!this.store || !this.provider || !this.invertedIndex || !this.configuredProviderInfo || !this.database) {
7284
- await this.initialize();
8185
+ throw new Error("Index state is not initialized");
7285
8186
  }
7286
8187
  return {
7287
8188
  store: this.store,
@@ -7305,7 +8206,12 @@ var Indexer = class {
7305
8206
  return createCostEstimate(files, configuredProviderInfo);
7306
8207
  }
7307
8208
  async index(onProgress) {
7308
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
8209
+ return this.withIndexMutationLease("index", async (recoveredOwners) => {
8210
+ return this.indexUnlocked(onProgress, recoveredOwners);
8211
+ });
8212
+ }
8213
+ async indexUnlocked(onProgress, recoveredOwners = [], stateReady = false) {
8214
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = stateReady ? this.requireLoadedIndexState() : await this.ensureInitializedUnlocked(recoveredOwners);
7309
8215
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
7310
8216
  const branchCatalogKey = this.getBranchCatalogKey();
7311
8217
  const forceScopedReembed = scopedRoots !== null && database.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -7315,7 +8221,6 @@ var Indexer = class {
7315
8221
  `${this.indexCompatibility?.reason} Run index_codebase with force=true to rebuild the index.`
7316
8222
  );
7317
8223
  }
7318
- this.acquireIndexingLock();
7319
8224
  this.logger.recordIndexingStart();
7320
8225
  this.logger.info("Starting indexing", { projectRoot: this.projectRoot });
7321
8226
  const startTime = Date.now();
@@ -7366,7 +8271,7 @@ var Indexer = class {
7366
8271
  unchangedFilePaths.add(f.path);
7367
8272
  this.logger.recordCacheHit();
7368
8273
  } else {
7369
- const content = await import_fs7.promises.readFile(f.path, "utf-8");
8274
+ const content = await import_fs8.promises.readFile(f.path, "utf-8");
7370
8275
  changedFiles.push({ path: f.path, content, hash: currentHash });
7371
8276
  this.logger.recordCacheMiss();
7372
8277
  }
@@ -7464,7 +8369,7 @@ var Indexer = class {
7464
8369
  for (const parsed of parsedFiles) {
7465
8370
  currentFilePaths.add(parsed.path);
7466
8371
  if (parsed.chunks.length === 0) {
7467
- const relativePath = path11.relative(this.projectRoot, parsed.path);
8372
+ const relativePath = path12.relative(this.projectRoot, parsed.path);
7468
8373
  stats.parseFailures.push(relativePath);
7469
8374
  }
7470
8375
  let fileChunkCount = 0;
@@ -7664,7 +8569,9 @@ var Indexer = class {
7664
8569
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
7665
8570
  database.clearBranchSymbols(branchCatalogKey);
7666
8571
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7667
- if (backfilledBlameMetadata) {
8572
+ const vectorPath = path12.join(this.indexPath, "vectors");
8573
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs8.existsSync)(vectorPath) && (0, import_fs8.existsSync)(`${vectorPath}.meta.json`);
8574
+ if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
7668
8575
  store.save();
7669
8576
  }
7670
8577
  if (scopedRoots) {
@@ -7685,7 +8592,6 @@ var Indexer = class {
7685
8592
  chunksProcessed: 0,
7686
8593
  totalChunks: 0
7687
8594
  });
7688
- this.releaseIndexingLock();
7689
8595
  return stats;
7690
8596
  }
7691
8597
  if (pendingChunks.length === 0) {
@@ -7694,7 +8600,7 @@ var Indexer = class {
7694
8600
  database.clearBranchSymbols(branchCatalogKey);
7695
8601
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7696
8602
  store.save();
7697
- invertedIndex.save();
8603
+ this.saveInvertedIndex(invertedIndex);
7698
8604
  if (scopedRoots) {
7699
8605
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7700
8606
  this.clearScopedFailedBatches(scopedRoots);
@@ -7713,7 +8619,6 @@ var Indexer = class {
7713
8619
  chunksProcessed: 0,
7714
8620
  totalChunks: 0
7715
8621
  });
7716
- this.releaseIndexingLock();
7717
8622
  return stats;
7718
8623
  }
7719
8624
  onProgress?.({
@@ -7944,7 +8849,7 @@ var Indexer = class {
7944
8849
  database.clearBranchSymbols(branchCatalogKey);
7945
8850
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
7946
8851
  store.save();
7947
- invertedIndex.save();
8852
+ this.saveInvertedIndex(invertedIndex);
7948
8853
  if (scopedRoots) {
7949
8854
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
7950
8855
  } else {
@@ -7996,7 +8901,6 @@ var Indexer = class {
7996
8901
  chunksProcessed: stats.indexedChunks,
7997
8902
  totalChunks: pendingChunks.length
7998
8903
  });
7999
- this.releaseIndexingLock();
8000
8904
  return stats;
8001
8905
  }
8002
8906
  async getQueryEmbedding(query, provider) {
@@ -8062,8 +8966,8 @@ var Indexer = class {
8062
8966
  return intersection / union;
8063
8967
  }
8064
8968
  async search(query, limit, options) {
8065
- const { store, provider, database } = await this.ensureInitialized();
8066
- const compatibility = this.checkCompatibility();
8969
+ const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
8970
+ this.requireReadableComponents(readIssues, "vectors", "database");
8067
8971
  if (!compatibility.compatible) {
8068
8972
  throw new Error(
8069
8973
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} A possible solution is to run index_codebase with force=true to rebuild the index.`
@@ -8077,6 +8981,7 @@ var Indexer = class {
8077
8981
  const maxResults = limit ?? this.config.search.maxResults;
8078
8982
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
8079
8983
  const fusionStrategy = this.config.search.fusionStrategy;
8984
+ const effectiveHybridWeight = fusionStrategy === "weighted" && readIssues.some((issue) => issue.component === "keyword") ? 0 : hybridWeight;
8080
8985
  const rrfK = this.config.search.rrfK;
8081
8986
  const rerankTopN = this.config.search.rerankTopN;
8082
8987
  const filterByBranch = options?.filterByBranch ?? true;
@@ -8085,7 +8990,7 @@ var Indexer = class {
8085
8990
  this.logger.search("debug", "Starting search", {
8086
8991
  query,
8087
8992
  maxResults,
8088
- hybridWeight,
8993
+ hybridWeight: effectiveHybridWeight,
8089
8994
  fusionStrategy,
8090
8995
  rrfK,
8091
8996
  rerankTopN,
@@ -8099,7 +9004,7 @@ var Indexer = class {
8099
9004
  const semanticResults = store.search(embedding, maxResults * 4);
8100
9005
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
8101
9006
  const keywordStartTime = import_perf_hooks.performance.now();
8102
- const keywordResults = await this.keywordSearch(query, maxResults * 4);
9007
+ const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
8103
9008
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
8104
9009
  let branchChunkIds = null;
8105
9010
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -8136,7 +9041,7 @@ var Indexer = class {
8136
9041
  rrfK,
8137
9042
  rerankTopN,
8138
9043
  limit: maxResults,
8139
- hybridWeight,
9044
+ hybridWeight: effectiveHybridWeight,
8140
9045
  prioritizeSourcePaths: sourceIntent
8141
9046
  });
8142
9047
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
@@ -8210,7 +9115,7 @@ var Indexer = class {
8210
9115
  let contextEndLine = r.metadata.endLine;
8211
9116
  if (!metadataOnly && this.config.search.includeContext) {
8212
9117
  try {
8213
- const fileContent = await import_fs7.promises.readFile(
9118
+ const fileContent = await import_fs8.promises.readFile(
8214
9119
  r.metadata.filePath,
8215
9120
  "utf-8"
8216
9121
  );
@@ -8236,8 +9141,7 @@ var Indexer = class {
8236
9141
  })
8237
9142
  );
8238
9143
  }
8239
- async keywordSearch(query, limit) {
8240
- const { store, invertedIndex } = await this.ensureInitialized();
9144
+ async keywordSearch(query, limit, store, invertedIndex) {
8241
9145
  const scores = invertedIndex.search(query);
8242
9146
  if (scores.size === 0) {
8243
9147
  return [];
@@ -8255,24 +9159,54 @@ var Indexer = class {
8255
9159
  return results.slice(0, limit);
8256
9160
  }
8257
9161
  async getStatus() {
8258
- const { store, configuredProviderInfo, database } = await this.ensureInitialized();
9162
+ const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
8259
9163
  const failedBatchesCount = this.getFailedBatchesCount();
9164
+ const vectorCount = store.count();
9165
+ const statusReadIssues = [...readIssues];
9166
+ let startupWarning = "";
9167
+ if (!statusReadIssues.some((issue) => issue.component === "database")) {
9168
+ try {
9169
+ startupWarning = database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? "";
9170
+ } catch (error) {
9171
+ const message = this.getDatabaseReadIssueMessage();
9172
+ statusReadIssues.push(this.createReadIssue("database", message));
9173
+ if (!this.readIssues.some((issue) => issue.component === "database")) {
9174
+ this.recordReadIssue("database", message, error);
9175
+ }
9176
+ }
9177
+ }
9178
+ const readWarning = statusReadIssues.map((issue) => issue.message).join(" ");
9179
+ const warning = [readWarning, startupWarning].filter((message) => message.length > 0).join(" ");
9180
+ const hasBlockingReadIssue = statusReadIssues.some((issue) => issue.blocking);
8260
9181
  return {
8261
- indexed: store.count() > 0,
8262
- vectorCount: store.count(),
9182
+ indexed: vectorCount > 0 && !hasBlockingReadIssue,
9183
+ vectorCount,
8263
9184
  provider: configuredProviderInfo.provider,
8264
9185
  model: configuredProviderInfo.modelInfo.model,
8265
9186
  indexPath: this.indexPath,
8266
9187
  currentBranch: this.currentBranch,
8267
9188
  baseBranch: this.baseBranch,
8268
- compatibility: this.indexCompatibility,
9189
+ compatibility,
8269
9190
  failedBatchesCount,
8270
9191
  failedBatchesPath: failedBatchesCount > 0 ? this.failedBatchesPath : void 0,
8271
- warning: database.getMetadata(STARTUP_WARNING_METADATA_KEY) ?? void 0
9192
+ warning: warning || void 0
8272
9193
  };
8273
9194
  }
9195
+ async forceIndex(onProgress) {
9196
+ return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
9197
+ await this.ensureInitializedUnlocked(recoveredOwners);
9198
+ await this.clearIndexUnlocked();
9199
+ return this.indexUnlocked(onProgress, [], true);
9200
+ });
9201
+ }
8274
9202
  async clearIndex() {
8275
- const { store, invertedIndex, database } = await this.ensureInitialized();
9203
+ await this.withIndexMutationLease("clear", async (recoveredOwners) => {
9204
+ await this.ensureInitializedUnlocked(recoveredOwners);
9205
+ await this.clearIndexUnlocked();
9206
+ });
9207
+ }
9208
+ async clearIndexUnlocked() {
9209
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8276
9210
  if (this.config.scope === "global") {
8277
9211
  store.load();
8278
9212
  invertedIndex.load();
@@ -8299,7 +9233,7 @@ var Indexer = class {
8299
9233
  store.clear();
8300
9234
  store.save();
8301
9235
  invertedIndex.clear();
8302
- invertedIndex.save();
9236
+ this.saveInvertedIndex(invertedIndex);
8303
9237
  this.fileHashCache.clear();
8304
9238
  this.saveFileHashCache();
8305
9239
  database.clearAllIndexedData();
@@ -8323,14 +9257,7 @@ var Indexer = class {
8323
9257
  this.indexCompatibility = compatibility;
8324
9258
  return;
8325
9259
  }
8326
- const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8327
- if (this.host !== "opencode") {
8328
- localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8329
- }
8330
- const isLocalProjectIndex = localProjectIndexPaths.some(
8331
- (localPath) => path11.resolve(this.indexPath) === path11.resolve(localPath)
8332
- );
8333
- if (!isLocalProjectIndex) {
9260
+ if (!this.isLocalProjectIndexPath()) {
8334
9261
  throw new Error(
8335
9262
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
8336
9263
  );
@@ -8338,7 +9265,7 @@ var Indexer = class {
8338
9265
  store.clear();
8339
9266
  store.save();
8340
9267
  invertedIndex.clear();
8341
- invertedIndex.save();
9268
+ this.saveInvertedIndex(invertedIndex);
8342
9269
  this.fileHashCache.clear();
8343
9270
  this.saveFileHashCache();
8344
9271
  database.clearAllIndexedData();
@@ -8356,7 +9283,13 @@ var Indexer = class {
8356
9283
  this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
8357
9284
  }
8358
9285
  async healthCheck() {
8359
- const { store, invertedIndex, database } = await this.ensureInitialized();
9286
+ return this.withIndexMutationLease("health-check", async (recoveredOwners) => {
9287
+ await this.ensureInitializedUnlocked(recoveredOwners);
9288
+ return this.healthCheckUnlocked();
9289
+ });
9290
+ }
9291
+ async healthCheckUnlocked() {
9292
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
8360
9293
  this.logger.gc("info", "Starting health check");
8361
9294
  const allMetadata = store.getAllMetadata();
8362
9295
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
@@ -8369,7 +9302,7 @@ var Indexer = class {
8369
9302
  const removedChunkKeys = [];
8370
9303
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
8371
9304
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
8372
- if (!(0, import_fs7.existsSync)(filePath)) {
9305
+ if (!(0, import_fs8.existsSync)(filePath)) {
8373
9306
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
8374
9307
  for (const key of chunkKeys) {
8375
9308
  removedChunkKeys.push(key);
@@ -8394,7 +9327,7 @@ var Indexer = class {
8394
9327
  const removedCount = removedChunkKeys.length;
8395
9328
  if (removedCount > 0) {
8396
9329
  store.save();
8397
- invertedIndex.save();
9330
+ this.saveInvertedIndex(invertedIndex);
8398
9331
  }
8399
9332
  let gcOrphanEmbeddings;
8400
9333
  let gcOrphanChunks;
@@ -8409,7 +9342,7 @@ var Indexer = class {
8409
9342
  if (!await this.tryResetCorruptedIndex("running index health check", error)) {
8410
9343
  throw error;
8411
9344
  }
8412
- await this.ensureInitialized();
9345
+ await this.initializeUnlocked("writer", [], { skipAutoGc: true });
8413
9346
  return {
8414
9347
  removed: 0,
8415
9348
  filePaths: [],
@@ -8418,7 +9351,7 @@ var Indexer = class {
8418
9351
  gcOrphanSymbols: 0,
8419
9352
  gcOrphanCallEdges: 0,
8420
9353
  resetCorruptedIndex: true,
8421
- warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
9354
+ warning: this.getCorruptedIndexWarning(path12.join(this.indexPath, "codebase.db"))
8422
9355
  };
8423
9356
  }
8424
9357
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -8431,7 +9364,13 @@ var Indexer = class {
8431
9364
  return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
8432
9365
  }
8433
9366
  async retryFailedBatches() {
8434
- const { store, provider, invertedIndex, database, configuredProviderInfo } = await this.ensureInitialized();
9367
+ return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
9368
+ await this.ensureInitializedUnlocked(recoveredOwners);
9369
+ return this.retryFailedBatchesUnlocked();
9370
+ });
9371
+ }
9372
+ async retryFailedBatchesUnlocked() {
9373
+ const { store, provider, invertedIndex, database, configuredProviderInfo } = this.requireLoadedIndexState();
8435
9374
  const maxChunkTokens = getSafeEmbeddingChunkTokenLimit(configuredProviderInfo);
8436
9375
  const providerRateLimits = this.getProviderRateLimits(configuredProviderInfo.provider);
8437
9376
  const roots = this.config.scope === "global" ? this.getScopedRoots() : null;
@@ -8595,7 +9534,7 @@ var Indexer = class {
8595
9534
  }
8596
9535
  if (succeeded > 0) {
8597
9536
  store.save();
8598
- invertedIndex.save();
9537
+ this.saveInvertedIndex(invertedIndex);
8599
9538
  }
8600
9539
  if (roots && succeeded > 0 && persistedStillFailing.length === 0 && this.hasProjectForceReembedPending()) {
8601
9540
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
@@ -8623,15 +9562,16 @@ var Indexer = class {
8623
9562
  }
8624
9563
  }
8625
9564
  async getDatabaseStats() {
8626
- const { database } = await this.ensureInitialized();
9565
+ const { database, readIssues } = await this.ensureInitialized();
9566
+ this.requireReadableComponents(readIssues, "database");
8627
9567
  return database.getStats();
8628
9568
  }
8629
9569
  getLogger() {
8630
9570
  return this.logger;
8631
9571
  }
8632
9572
  async findSimilar(code, limit = this.config.search.maxResults, options) {
8633
- const { store, provider, database } = await this.ensureInitialized();
8634
- const compatibility = this.checkCompatibility();
9573
+ const { store, provider, database, readIssues, compatibility } = await this.ensureInitialized();
9574
+ this.requireReadableComponents(readIssues, "vectors", "database");
8635
9575
  if (!compatibility.compatible) {
8636
9576
  throw new Error(
8637
9577
  `${compatibility.reason ?? "Index is incompatible with current embedding provider."} Run index_codebase with force=true to rebuild the index.`
@@ -8721,7 +9661,7 @@ var Indexer = class {
8721
9661
  let content = "";
8722
9662
  if (this.config.search.includeContext) {
8723
9663
  try {
8724
- const fileContent = await import_fs7.promises.readFile(
9664
+ const fileContent = await import_fs8.promises.readFile(
8725
9665
  r.metadata.filePath,
8726
9666
  "utf-8"
8727
9667
  );
@@ -8745,7 +9685,8 @@ var Indexer = class {
8745
9685
  );
8746
9686
  }
8747
9687
  async getCallers(targetName, callTypeFilter) {
8748
- const { database } = await this.ensureInitialized();
9688
+ const { database, readIssues } = await this.ensureInitialized();
9689
+ this.requireReadableComponents(readIssues, "database");
8749
9690
  const seen = /* @__PURE__ */ new Set();
8750
9691
  const results = [];
8751
9692
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8759,7 +9700,8 @@ var Indexer = class {
8759
9700
  return results;
8760
9701
  }
8761
9702
  async getCallees(symbolId, callTypeFilter) {
8762
- const { database } = await this.ensureInitialized();
9703
+ const { database, readIssues } = await this.ensureInitialized();
9704
+ this.requireReadableComponents(readIssues, "database");
8763
9705
  const seen = /* @__PURE__ */ new Set();
8764
9706
  const results = [];
8765
9707
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8773,43 +9715,50 @@ var Indexer = class {
8773
9715
  return results;
8774
9716
  }
8775
9717
  async findCallPath(fromName, toName, maxDepth) {
8776
- const { database } = await this.ensureInitialized();
9718
+ const { database, readIssues } = await this.ensureInitialized();
9719
+ this.requireReadableComponents(readIssues, "database");
8777
9720
  let shortest = [];
8778
9721
  for (const branchKey of this.getBranchCatalogKeys()) {
8779
- const path26 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
8780
- if (path26.length > 0 && (shortest.length === 0 || path26.length < shortest.length)) {
8781
- shortest = path26;
9722
+ const path27 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
9723
+ if (path27.length > 0 && (shortest.length === 0 || path27.length < shortest.length)) {
9724
+ shortest = path27;
8782
9725
  }
8783
9726
  }
8784
9727
  return shortest;
8785
9728
  }
8786
9729
  async getSymbolsForBranch(branch) {
8787
- const { database } = await this.ensureInitialized();
9730
+ const { database, readIssues } = await this.ensureInitialized();
9731
+ this.requireReadableComponents(readIssues, "database");
8788
9732
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8789
9733
  return database.getSymbolsForBranch(resolvedBranch);
8790
9734
  }
8791
9735
  async getSymbolsForFiles(filePaths, branch) {
8792
- const { database } = await this.ensureInitialized();
9736
+ const { database, readIssues } = await this.ensureInitialized();
9737
+ this.requireReadableComponents(readIssues, "database");
8793
9738
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8794
9739
  return database.getSymbolsForFiles(filePaths, resolvedBranch);
8795
9740
  }
8796
9741
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
8797
- const { database } = await this.ensureInitialized();
9742
+ const { database, readIssues } = await this.ensureInitialized();
9743
+ this.requireReadableComponents(readIssues, "database");
8798
9744
  const branch = this.getBranchCatalogKey();
8799
9745
  return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
8800
9746
  }
8801
9747
  async detectCommunities(branch, symbolIds) {
8802
- const { database } = await this.ensureInitialized();
9748
+ const { database, readIssues } = await this.ensureInitialized();
9749
+ this.requireReadableComponents(readIssues, "database");
8803
9750
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8804
9751
  return database.detectCommunities(resolvedBranch, symbolIds);
8805
9752
  }
8806
9753
  async computeCentrality(branch) {
8807
- const { database } = await this.ensureInitialized();
9754
+ const { database, readIssues } = await this.ensureInitialized();
9755
+ this.requireReadableComponents(readIssues, "database");
8808
9756
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
8809
9757
  return database.computeCentrality(resolvedBranch);
8810
9758
  }
8811
9759
  async getPrImpact(opts) {
8812
- const { database } = await this.ensureInitialized();
9760
+ const { database, readIssues } = await this.ensureInitialized();
9761
+ this.requireReadableComponents(readIssues, "database");
8813
9762
  const execFileAsync3 = (0, import_util3.promisify)(import_child_process3.execFile);
8814
9763
  const changedFilesResult = await getChangedFiles({
8815
9764
  pr: opts.pr,
@@ -8832,7 +9781,7 @@ var Indexer = class {
8832
9781
  "Run index_codebase first to build the call graph and symbol index for this branch."
8833
9782
  );
8834
9783
  }
8835
- const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
9784
+ const absoluteChangedFiles = changedFiles.map((f) => path12.resolve(this.projectRoot, f));
8836
9785
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
8837
9786
  const directIds = directSymbols.map((s) => s.id);
8838
9787
  const direction = opts.direction ?? "both";
@@ -8915,7 +9864,7 @@ var Indexer = class {
8915
9864
  projectRoot: this.projectRoot,
8916
9865
  baseBranch: this.baseBranch
8917
9866
  });
8918
- const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
9867
+ const otherAbsolute = otherChanged.files.map((f) => path12.resolve(this.projectRoot, f));
8919
9868
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
8920
9869
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
8921
9870
  const otherLabels = /* @__PURE__ */ new Set();
@@ -8965,7 +9914,8 @@ var Indexer = class {
8965
9914
  };
8966
9915
  }
8967
9916
  async getVisualizationData(options) {
8968
- const { database, store } = await this.ensureInitialized();
9917
+ const { database, store, readIssues } = await this.ensureInitialized();
9918
+ this.requireReadableComponents(readIssues, "vectors", "database");
8969
9919
  const seenSymbols = /* @__PURE__ */ new Map();
8970
9920
  const seenEdges = /* @__PURE__ */ new Map();
8971
9921
  for (const branchKey of this.getBranchCatalogKeys()) {
@@ -8978,12 +9928,12 @@ var Indexer = class {
8978
9928
  if (meta.filePath) filePaths.add(meta.filePath);
8979
9929
  }
8980
9930
  const directory = options?.directory?.replace(/\/$/, "");
8981
- const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
9931
+ const absoluteDirectoryFilter = directory ? path12.resolve(this.projectRoot, directory) : void 0;
8982
9932
  for (const filePath of filePaths) {
8983
9933
  if (directory) {
8984
- const absoluteFilePath = path11.resolve(filePath);
9934
+ const absoluteFilePath = path12.resolve(filePath);
8985
9935
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
8986
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
9936
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path12.sep));
8987
9937
  if (!matchesRelative && !matchesProjectRelative) {
8988
9938
  continue;
8989
9939
  }
@@ -9005,12 +9955,23 @@ var Indexer = class {
9005
9955
  return { symbols: [...seenSymbols.values()], edges: [...seenEdges.values()] };
9006
9956
  }
9007
9957
  async close() {
9008
- await this.database?.close();
9958
+ this.database?.close();
9959
+ for (const database of this.retiredDatabases) {
9960
+ database.close();
9961
+ }
9962
+ this.retiredDatabases = [];
9009
9963
  this.database = null;
9010
9964
  this.store = null;
9011
9965
  this.invertedIndex = null;
9012
9966
  this.provider = null;
9013
9967
  this.reranker = null;
9968
+ this.configuredProviderInfo = null;
9969
+ this.indexCompatibility = null;
9970
+ this.initializationMode = "none";
9971
+ this.readIssues = [];
9972
+ this.readerArtifactFingerprint = null;
9973
+ this.writerArtifactFingerprint = null;
9974
+ this.readerArtifactRetryAfter.clear();
9014
9975
  }
9015
9976
  };
9016
9977
 
@@ -9261,15 +10222,15 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
9261
10222
  }
9262
10223
 
9263
10224
  // src/eval/runner-config.ts
9264
- var import_fs8 = require("fs");
9265
- var os4 = __toESM(require("os"), 1);
9266
- var path13 = __toESM(require("path"), 1);
10225
+ var import_fs9 = require("fs");
10226
+ var os5 = __toESM(require("os"), 1);
10227
+ var path14 = __toESM(require("path"), 1);
9267
10228
 
9268
10229
  // src/config/rebase.ts
9269
- var path12 = __toESM(require("path"), 1);
10230
+ var path13 = __toESM(require("path"), 1);
9270
10231
  function isWithinRoot(rootDir, targetPath) {
9271
- const relativePath = path12.relative(rootDir, targetPath);
9272
- return relativePath === "" || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath);
10232
+ const relativePath = path13.relative(rootDir, targetPath);
10233
+ return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
9273
10234
  }
9274
10235
  function rebasePathEntries(values, fromDir, toDir) {
9275
10236
  if (!Array.isArray(values)) {
@@ -9277,10 +10238,10 @@ function rebasePathEntries(values, fromDir, toDir) {
9277
10238
  }
9278
10239
  return values.filter((value) => typeof value === "string").map((value) => {
9279
10240
  const trimmed = value.trim();
9280
- if (!trimmed || path12.isAbsolute(trimmed)) {
10241
+ if (!trimmed || path13.isAbsolute(trimmed)) {
9281
10242
  return trimmed;
9282
10243
  }
9283
- return normalizePathSeparators(path12.normalize(path12.relative(toDir, path12.resolve(fromDir, trimmed))));
10244
+ return normalizePathSeparators(path13.normalize(path13.relative(toDir, path13.resolve(fromDir, trimmed))));
9284
10245
  }).filter(Boolean);
9285
10246
  }
9286
10247
  function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
@@ -9292,17 +10253,17 @@ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
9292
10253
  if (!trimmed) {
9293
10254
  return trimmed;
9294
10255
  }
9295
- if (path12.isAbsolute(trimmed)) {
10256
+ if (path13.isAbsolute(trimmed)) {
9296
10257
  if (isWithinRoot(sourceRoot, trimmed)) {
9297
- return normalizePathSeparators(path12.normalize(path12.relative(sourceRoot, trimmed) || "."));
10258
+ return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
9298
10259
  }
9299
- return path12.normalize(trimmed);
10260
+ return path13.normalize(trimmed);
9300
10261
  }
9301
- const resolvedFromSource = path12.resolve(sourceRoot, trimmed);
10262
+ const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
9302
10263
  if (isWithinRoot(sourceRoot, resolvedFromSource)) {
9303
- return normalizePathSeparators(path12.normalize(trimmed));
10264
+ return normalizePathSeparators(path13.normalize(trimmed));
9304
10265
  }
9305
- return normalizePathSeparators(path12.normalize(path12.relative(targetRoot, resolvedFromSource)));
10266
+ return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
9306
10267
  }).filter(Boolean);
9307
10268
  }
9308
10269
 
@@ -9340,7 +10301,7 @@ function validateEvalConfigShape(rawConfig, filePath) {
9340
10301
  }
9341
10302
  function parseJsonConfigFile(filePath) {
9342
10303
  try {
9343
- return validateEvalConfigShape(JSON.parse((0, import_fs8.readFileSync)(filePath, "utf-8")), filePath);
10304
+ return validateEvalConfigShape(JSON.parse((0, import_fs9.readFileSync)(filePath, "utf-8")), filePath);
9344
10305
  } catch (error) {
9345
10306
  if (error instanceof Error && error.message.startsWith("Eval config at ")) {
9346
10307
  throw error;
@@ -9350,20 +10311,20 @@ function parseJsonConfigFile(filePath) {
9350
10311
  }
9351
10312
  }
9352
10313
  function toAbsolute(projectRoot, maybeRelative) {
9353
- return path13.isAbsolute(maybeRelative) ? maybeRelative : path13.join(projectRoot, maybeRelative);
10314
+ return path14.isAbsolute(maybeRelative) ? maybeRelative : path14.join(projectRoot, maybeRelative);
9354
10315
  }
9355
10316
  function isProjectScopedConfigPath(configPath) {
9356
- return path13.basename(configPath) === "codebase-index.json" && path13.basename(path13.dirname(configPath)) === ".opencode";
10317
+ return path14.basename(configPath) === "codebase-index.json" && path14.basename(path14.dirname(configPath)) === ".opencode";
9357
10318
  }
9358
10319
  function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfigPath) {
9359
10320
  const config = rawConfig && typeof rawConfig === "object" ? { ...rawConfig } : {};
9360
10321
  const rebaseEntries = (values) => isProjectScopedConfigPath(resolvedConfigPath) ? resolveInheritedKnowledgeBaseEntries(
9361
10322
  values,
9362
- path13.dirname(path13.dirname(resolvedConfigPath)),
10323
+ path14.dirname(path14.dirname(resolvedConfigPath)),
9363
10324
  projectRoot
9364
10325
  ) : rebasePathEntries(
9365
10326
  values,
9366
- path13.dirname(resolvedConfigPath),
10327
+ path14.dirname(resolvedConfigPath),
9367
10328
  projectRoot
9368
10329
  );
9369
10330
  if (Array.isArray(config.knowledgeBases)) {
@@ -9376,7 +10337,7 @@ function normalizeEvalConfigKnowledgeBases(rawConfig, projectRoot, resolvedConfi
9376
10337
  }
9377
10338
  function loadRawConfig(projectRoot, configPath) {
9378
10339
  const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
9379
- if (fromPath && (0, import_fs8.existsSync)(fromPath)) {
10340
+ if (fromPath && (0, import_fs9.existsSync)(fromPath)) {
9380
10341
  return normalizeEvalConfigKnowledgeBases(
9381
10342
  parseJsonConfigFile(fromPath),
9382
10343
  projectRoot,
@@ -9384,15 +10345,15 @@ function loadRawConfig(projectRoot, configPath) {
9384
10345
  );
9385
10346
  }
9386
10347
  const projectConfig = resolveProjectConfigPath(projectRoot);
9387
- if ((0, import_fs8.existsSync)(projectConfig)) {
10348
+ if ((0, import_fs9.existsSync)(projectConfig)) {
9388
10349
  return normalizeEvalConfigKnowledgeBases(
9389
10350
  parseJsonConfigFile(projectConfig),
9390
10351
  projectRoot,
9391
10352
  projectConfig
9392
10353
  );
9393
10354
  }
9394
- const globalConfig = path13.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
9395
- if ((0, import_fs8.existsSync)(globalConfig)) {
10355
+ const globalConfig = path14.join(os5.homedir(), ".config", "opencode", "codebase-index.json");
10356
+ if ((0, import_fs9.existsSync)(globalConfig)) {
9396
10357
  return parseJsonConfigFile(globalConfig);
9397
10358
  }
9398
10359
  return {};
@@ -9401,24 +10362,24 @@ function getIndexRootPath(projectRoot, scope) {
9401
10362
  return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
9402
10363
  }
9403
10364
  function getLocalProjectIndexRoot(projectRoot) {
9404
- return path13.join(projectRoot, ".opencode", "index");
10365
+ return path14.join(projectRoot, ".opencode", "index");
9405
10366
  }
9406
10367
  function getLocalProjectConfigPath(projectRoot) {
9407
- return path13.join(projectRoot, ".opencode", "codebase-index.json");
10368
+ return path14.join(projectRoot, ".opencode", "codebase-index.json");
9408
10369
  }
9409
10370
  function clearIndexRoot(projectRoot, scope) {
9410
10371
  const indexRoot = scope === "global" ? getIndexRootPath(projectRoot, scope) : getLocalProjectIndexRoot(projectRoot);
9411
- if ((0, import_fs8.existsSync)(indexRoot)) {
9412
- (0, import_fs8.rmSync)(indexRoot, { recursive: true, force: true });
10372
+ if ((0, import_fs9.existsSync)(indexRoot)) {
10373
+ (0, import_fs9.rmSync)(indexRoot, { recursive: true, force: true });
9413
10374
  }
9414
10375
  }
9415
10376
  function ensureLocalEvalProjectConfig(projectRoot, configPath) {
9416
10377
  const localConfigPath = getLocalProjectConfigPath(projectRoot);
9417
10378
  const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
9418
- if (!configPath && (0, import_fs8.existsSync)(localConfigPath)) {
10379
+ if (!configPath && (0, import_fs9.existsSync)(localConfigPath)) {
9419
10380
  return localConfigPath;
9420
10381
  }
9421
- if (!(0, import_fs8.existsSync)(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
10382
+ if (!(0, import_fs9.existsSync)(resolvedConfigPath) || resolvedConfigPath === localConfigPath) {
9422
10383
  return resolvedConfigPath;
9423
10384
  }
9424
10385
  const sourceConfig = normalizeEvalConfigKnowledgeBases(
@@ -9426,8 +10387,8 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
9426
10387
  projectRoot,
9427
10388
  resolvedConfigPath
9428
10389
  );
9429
- (0, import_fs8.mkdirSync)(path13.dirname(localConfigPath), { recursive: true });
9430
- (0, import_fs8.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
10390
+ (0, import_fs9.mkdirSync)(path14.dirname(localConfigPath), { recursive: true });
10391
+ (0, import_fs9.writeFileSync)(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
9431
10392
  return localConfigPath;
9432
10393
  }
9433
10394
  function loadParsedConfig(projectRoot, configPath) {
@@ -9460,9 +10421,9 @@ function getEmbeddingCostPer1MTokens(embeddingProvider) {
9460
10421
  }
9461
10422
 
9462
10423
  // src/eval/schema.ts
9463
- var import_fs9 = require("fs");
10424
+ var import_fs10 = require("fs");
9464
10425
  function parseJsonFile(filePath) {
9465
- const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
10426
+ const content = (0, import_fs10.readFileSync)(filePath, "utf-8");
9466
10427
  try {
9467
10428
  return JSON.parse(content);
9468
10429
  } catch (error) {
@@ -9476,23 +10437,23 @@ function isRecord2(value) {
9476
10437
  function isStringArray3(value) {
9477
10438
  return Array.isArray(value) && value.every((item) => typeof item === "string");
9478
10439
  }
9479
- function asPositiveNumber(value, path26) {
10440
+ function asPositiveNumber(value, path27) {
9480
10441
  if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
9481
- throw new Error(`${path26} must be a non-negative number`);
10442
+ throw new Error(`${path27} must be a non-negative number`);
9482
10443
  }
9483
10444
  return value;
9484
10445
  }
9485
- function parseQueryType(value, path26) {
10446
+ function parseQueryType(value, path27) {
9486
10447
  if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
9487
10448
  return value;
9488
10449
  }
9489
10450
  throw new Error(
9490
- `${path26} must be one of: definition, implementation-intent, similarity, keyword-heavy`
10451
+ `${path27} must be one of: definition, implementation-intent, similarity, keyword-heavy`
9491
10452
  );
9492
10453
  }
9493
- function parseExpected(input, path26) {
10454
+ function parseExpected(input, path27) {
9494
10455
  if (!isRecord2(input)) {
9495
- throw new Error(`${path26} must be an object`);
10456
+ throw new Error(`${path27} must be an object`);
9496
10457
  }
9497
10458
  const filePathRaw = input.filePath;
9498
10459
  const acceptableFilesRaw = input.acceptableFiles;
@@ -9501,16 +10462,16 @@ function parseExpected(input, path26) {
9501
10462
  const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
9502
10463
  const acceptableFiles = isStringArray3(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
9503
10464
  if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
9504
- throw new Error(`${path26} must include either expected.filePath or expected.acceptableFiles`);
10465
+ throw new Error(`${path27} must include either expected.filePath or expected.acceptableFiles`);
9505
10466
  }
9506
10467
  if (acceptableFilesRaw !== void 0 && !isStringArray3(acceptableFilesRaw)) {
9507
- throw new Error(`${path26}.acceptableFiles must be an array of strings`);
10468
+ throw new Error(`${path27}.acceptableFiles must be an array of strings`);
9508
10469
  }
9509
10470
  if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
9510
- throw new Error(`${path26}.symbol must be a string when provided`);
10471
+ throw new Error(`${path27}.symbol must be a string when provided`);
9511
10472
  }
9512
10473
  if (branchRaw !== void 0 && typeof branchRaw !== "string") {
9513
- throw new Error(`${path26}.branch must be a string when provided`);
10474
+ throw new Error(`${path27}.branch must be a string when provided`);
9514
10475
  }
9515
10476
  return {
9516
10477
  filePath,
@@ -9520,25 +10481,25 @@ function parseExpected(input, path26) {
9520
10481
  };
9521
10482
  }
9522
10483
  function parseQuery(input, index) {
9523
- const path26 = `queries[${index}]`;
10484
+ const path27 = `queries[${index}]`;
9524
10485
  if (!isRecord2(input)) {
9525
- throw new Error(`${path26} must be an object`);
10486
+ throw new Error(`${path27} must be an object`);
9526
10487
  }
9527
10488
  const id = input.id;
9528
10489
  const query = input.query;
9529
10490
  const queryType = input.queryType;
9530
10491
  const expected = input.expected;
9531
10492
  if (typeof id !== "string" || id.trim().length === 0) {
9532
- throw new Error(`${path26}.id must be a non-empty string`);
10493
+ throw new Error(`${path27}.id must be a non-empty string`);
9533
10494
  }
9534
10495
  if (typeof query !== "string" || query.trim().length === 0) {
9535
- throw new Error(`${path26}.query must be a non-empty string`);
10496
+ throw new Error(`${path27}.query must be a non-empty string`);
9536
10497
  }
9537
10498
  return {
9538
10499
  id,
9539
10500
  query,
9540
- queryType: parseQueryType(queryType, `${path26}.queryType`),
9541
- expected: parseExpected(expected, `${path26}.expected`)
10501
+ queryType: parseQueryType(queryType, `${path27}.queryType`),
10502
+ expected: parseExpected(expected, `${path27}.expected`)
9542
10503
  };
9543
10504
  }
9544
10505
  function parseGoldenDataset(raw, sourceLabel) {
@@ -9721,13 +10682,13 @@ async function runEvaluation(options) {
9721
10682
  };
9722
10683
  const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
9723
10684
  const perQueryArtifact = buildPerQueryArtifact(perQuery);
9724
- writeJson(path14.join(outputDir, "summary.json"), summary);
9725
- writeJson(path14.join(outputDir, "per-query.json"), perQueryArtifact);
10685
+ writeJson(path15.join(outputDir, "summary.json"), summary);
10686
+ writeJson(path15.join(outputDir, "per-query.json"), perQueryArtifact);
9726
10687
  let comparison;
9727
10688
  if (againstPath) {
9728
10689
  const baseline = loadSummary(againstPath);
9729
10690
  comparison = compareSummaries(summary, baseline, againstPath);
9730
- writeJson(path14.join(outputDir, "compare.json"), comparison);
10691
+ writeJson(path15.join(outputDir, "compare.json"), comparison);
9731
10692
  }
9732
10693
  let gate;
9733
10694
  if (options.ciMode) {
@@ -9737,10 +10698,10 @@ async function runEvaluation(options) {
9737
10698
  const budget = loadBudget(budgetPath);
9738
10699
  if (!comparison && budget.baselinePath) {
9739
10700
  const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
9740
- if ((0, import_fs10.existsSync)(resolvedBaseline)) {
10701
+ if ((0, import_fs11.existsSync)(resolvedBaseline)) {
9741
10702
  const baselineSummary = loadSummary(resolvedBaseline);
9742
10703
  comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
9743
- writeJson(path14.join(outputDir, "compare.json"), comparison);
10704
+ writeJson(path15.join(outputDir, "compare.json"), comparison);
9744
10705
  } else if (budget.failOnMissingBaseline) {
9745
10706
  throw new Error(
9746
10707
  `Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
@@ -9750,7 +10711,7 @@ async function runEvaluation(options) {
9750
10711
  gate = evaluateBudgetGate(budget, summary, comparison);
9751
10712
  }
9752
10713
  const markdown = createSummaryMarkdown(summary, comparison, gate);
9753
- writeText(path14.join(outputDir, "summary.md"), markdown);
10714
+ writeText(path15.join(outputDir, "summary.md"), markdown);
9754
10715
  return { outputDir, summary, perQuery, comparison, gate };
9755
10716
  } finally {
9756
10717
  await indexer.close();
@@ -9808,23 +10769,23 @@ async function runSweep(options, sweep) {
9808
10769
  bestByMrrAt10,
9809
10770
  bestByP95Latency
9810
10771
  };
9811
- writeJson(path14.join(outputDir, "compare.json"), aggregate);
10772
+ writeJson(path15.join(outputDir, "compare.json"), aggregate);
9812
10773
  const md = createSummaryMarkdown(
9813
10774
  bestByHitAt5?.summary ?? runs[0].summary,
9814
10775
  bestByHitAt5?.comparison,
9815
10776
  void 0,
9816
10777
  aggregate
9817
10778
  );
9818
- writeText(path14.join(outputDir, "summary.md"), md);
9819
- writeJson(path14.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
10779
+ writeText(path15.join(outputDir, "summary.md"), md);
10780
+ writeJson(path15.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
9820
10781
  return { outputDir, aggregate };
9821
10782
  }
9822
10783
 
9823
10784
  // src/eval/cli.ts
9824
- var path16 = __toESM(require("path"), 1);
10785
+ var path17 = __toESM(require("path"), 1);
9825
10786
 
9826
10787
  // src/eval/cli-parser.ts
9827
- var path15 = __toESM(require("path"), 1);
10788
+ var path16 = __toESM(require("path"), 1);
9828
10789
  function printUsage() {
9829
10790
  console.log(`
9830
10791
  Usage:
@@ -9896,12 +10857,12 @@ function parseEvalArgs(argv, cwd) {
9896
10857
  const arg = argv[i];
9897
10858
  const next = argv[i + 1];
9898
10859
  if (arg === "--project" && next) {
9899
- parsed.projectRoot = path15.resolve(cwd, next);
10860
+ parsed.projectRoot = path16.resolve(cwd, next);
9900
10861
  i += 1;
9901
10862
  continue;
9902
10863
  }
9903
10864
  if (arg === "--config" && next) {
9904
- parsed.configPath = path15.resolve(cwd, next);
10865
+ parsed.configPath = path16.resolve(cwd, next);
9905
10866
  i += 1;
9906
10867
  continue;
9907
10868
  }
@@ -10097,22 +11058,22 @@ async function handleEvalCommand(args, cwd) {
10097
11058
  if (!parsed.againstPath.endsWith(".json")) {
10098
11059
  throw new Error("eval diff --against must point to a summary JSON file");
10099
11060
  }
10100
- const currentSummary = loadSummary(path16.resolve(parsed.projectRoot, currentPath), {
11061
+ const currentSummary = loadSummary(path17.resolve(parsed.projectRoot, currentPath), {
10101
11062
  allowLegacyDiversityMetrics: true
10102
11063
  });
10103
- const baselineSummary = loadSummary(path16.resolve(parsed.projectRoot, parsed.againstPath), {
11064
+ const baselineSummary = loadSummary(path17.resolve(parsed.projectRoot, parsed.againstPath), {
10104
11065
  allowLegacyDiversityMetrics: true
10105
11066
  });
10106
11067
  const comparison = compareSummaries(
10107
11068
  currentSummary,
10108
11069
  baselineSummary,
10109
- path16.resolve(parsed.projectRoot, parsed.againstPath)
11070
+ path17.resolve(parsed.projectRoot, parsed.againstPath)
10110
11071
  );
10111
- const outputDir = createRunDirectory(path16.resolve(parsed.projectRoot, parsed.outputRoot));
11072
+ const outputDir = createRunDirectory(path17.resolve(parsed.projectRoot, parsed.outputRoot));
10112
11073
  const summaryMd = createSummaryMarkdown(currentSummary, comparison);
10113
- writeJson(path16.join(outputDir, "compare.json"), comparison);
10114
- writeText(path16.join(outputDir, "summary.md"), summaryMd);
10115
- writeJson(path16.join(outputDir, "summary.json"), currentSummary);
11074
+ writeJson(path17.join(outputDir, "compare.json"), comparison);
11075
+ writeText(path17.join(outputDir, "summary.md"), summaryMd);
11076
+ writeJson(path17.join(outputDir, "summary.json"), currentSummary);
10116
11077
  console.log(`Eval diff complete. Artifacts: ${outputDir}`);
10117
11078
  return 0;
10118
11079
  }
@@ -10121,7 +11082,7 @@ async function handleEvalCommand(args, cwd) {
10121
11082
 
10122
11083
  // src/mcp-server.ts
10123
11084
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
10124
- var import_fs14 = require("fs");
11085
+ var import_fs15 = require("fs");
10125
11086
 
10126
11087
  // src/mcp-server/register-prompts.ts
10127
11088
  var import_zod = require("zod");
@@ -10312,6 +11273,10 @@ function formatStatus(status) {
10312
11273
  lines.push(`Failed batches: ${status.failedBatchesPath}`);
10313
11274
  }
10314
11275
  }
11276
+ if (status.warning) {
11277
+ lines.push("");
11278
+ lines.push(`INDEX WARNING: ${status.warning}`);
11279
+ }
10315
11280
  if (status.compatibility && !status.compatibility.compatible) {
10316
11281
  lines.push("");
10317
11282
  lines.push(`COMPATIBILITY WARNING: ${status.compatibility.reason}`);
@@ -10417,16 +11382,16 @@ function formatCallGraphCallees(symbolId, callees, relationshipType) {
10417
11382
  return `[${index + 1}] \u2192 ${edge.targetName} (${edge.callType})${confidence} at line ${edge.line}${edge.isResolved ? ` [resolved: ${edge.toSymbolId}]` : " [unresolved]"}`;
10418
11383
  }).join("\n");
10419
11384
  }
10420
- function formatCallGraphPath(from, to, path26) {
10421
- if (path26.length === 0) {
11385
+ function formatCallGraphPath(from, to, path27) {
11386
+ if (path27.length === 0) {
10422
11387
  return `No path found between "${from}" and "${to}". They may be in disconnected components, or the call graph index needs updating.`;
10423
11388
  }
10424
- const formatted = path26.map((hop, index) => {
11389
+ const formatted = path27.map((hop, index) => {
10425
11390
  const prefix = index === 0 ? "[start]" : `--${hop.callType}-->`;
10426
11391
  const location = hop.filePath ? ` (${hop.filePath}:${hop.line})` : "";
10427
11392
  return `${prefix} ${hop.symbolName}${location}`;
10428
11393
  });
10429
- return `Path (${path26.length} hops):
11394
+ return `Path (${path27.length} hops):
10430
11395
  ${formatted.join("\n")}`;
10431
11396
  }
10432
11397
  function formatResultHeader(result, index) {
@@ -10517,12 +11482,12 @@ function formatPrImpact(result) {
10517
11482
  }
10518
11483
 
10519
11484
  // src/tools/operations.ts
10520
- var import_fs13 = require("fs");
10521
- var path20 = __toESM(require("path"), 1);
11485
+ var import_fs14 = require("fs");
11486
+ var path21 = __toESM(require("path"), 1);
10522
11487
 
10523
11488
  // src/config/merger.ts
10524
- var import_fs11 = require("fs");
10525
- var path17 = __toESM(require("path"), 1);
11489
+ var import_fs12 = require("fs");
11490
+ var path18 = __toESM(require("path"), 1);
10526
11491
  var PROJECT_OVERRIDE_KEYS = [
10527
11492
  "embeddingProvider",
10528
11493
  "customProvider",
@@ -10555,8 +11520,8 @@ function mergeUniqueStringArray(values) {
10555
11520
  return [...new Set(values.map((value) => String(value).trim()))];
10556
11521
  }
10557
11522
  function normalizeKnowledgeBasePath(value) {
10558
- let normalized = path17.normalize(String(value).trim());
10559
- const root = path17.parse(normalized).root;
11523
+ let normalized = path18.normalize(String(value).trim());
11524
+ const root = path18.parse(normalized).root;
10560
11525
  while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
10561
11526
  normalized = normalized.slice(0, -1);
10562
11527
  }
@@ -10590,11 +11555,11 @@ function validateConfigLayerShape(rawConfig, filePath) {
10590
11555
  return rawConfig;
10591
11556
  }
10592
11557
  function loadJsonFile(filePath) {
10593
- if (!(0, import_fs11.existsSync)(filePath)) {
11558
+ if (!(0, import_fs12.existsSync)(filePath)) {
10594
11559
  return null;
10595
11560
  }
10596
11561
  try {
10597
- const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
11562
+ const content = (0, import_fs12.readFileSync)(filePath, "utf-8");
10598
11563
  return validateConfigLayerShape(JSON.parse(content), filePath);
10599
11564
  } catch (error) {
10600
11565
  if (error instanceof Error && error.message.startsWith("Config file ")) {
@@ -10609,8 +11574,8 @@ function loadConfigFile(filePath) {
10609
11574
  }
10610
11575
  function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
10611
11576
  const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
10612
- (0, import_fs11.mkdirSync)(path17.dirname(localConfigPath), { recursive: true });
10613
- (0, import_fs11.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
11577
+ (0, import_fs12.mkdirSync)(path18.dirname(localConfigPath), { recursive: true });
11578
+ (0, import_fs12.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
10614
11579
  return localConfigPath;
10615
11580
  }
10616
11581
  function loadProjectConfigLayer(projectRoot, host = "opencode") {
@@ -10620,7 +11585,7 @@ function loadProjectConfigLayer(projectRoot, host = "opencode") {
10620
11585
  return {};
10621
11586
  }
10622
11587
  const normalizedConfig = { ...projectConfig };
10623
- const projectConfigBaseDir = path17.dirname(path17.dirname(projectConfigPath));
11588
+ const projectConfigBaseDir = path18.dirname(path18.dirname(projectConfigPath));
10624
11589
  if (Array.isArray(normalizedConfig.knowledgeBases)) {
10625
11590
  normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
10626
11591
  normalizedConfig.knowledgeBases,
@@ -10684,19 +11649,19 @@ function loadMergedConfig(projectRoot, host = "opencode") {
10684
11649
  }
10685
11650
 
10686
11651
  // src/tools/knowledge-base-paths.ts
10687
- var path18 = __toESM(require("path"), 1);
11652
+ var path19 = __toESM(require("path"), 1);
10688
11653
  function resolveConfigPathValue(value, baseDir) {
10689
11654
  const trimmed = value.trim();
10690
11655
  if (!trimmed) {
10691
11656
  return trimmed;
10692
11657
  }
10693
- const absolutePath = path18.isAbsolute(trimmed) ? trimmed : path18.resolve(baseDir, trimmed);
10694
- return path18.normalize(absolutePath);
11658
+ const absolutePath = path19.isAbsolute(trimmed) ? trimmed : path19.resolve(baseDir, trimmed);
11659
+ return path19.normalize(absolutePath);
10695
11660
  }
10696
11661
 
10697
11662
  // src/tools/config-state.ts
10698
- var import_fs12 = require("fs");
10699
- var path19 = __toESM(require("path"), 1);
11663
+ var import_fs13 = require("fs");
11664
+ var path20 = __toESM(require("path"), 1);
10700
11665
  function normalizeKnowledgeBasePaths(config, projectRoot) {
10701
11666
  const normalized = { ...config };
10702
11667
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -10720,6 +11685,24 @@ function loadRuntimeConfig(projectRoot, host = "opencode") {
10720
11685
  var indexerCache = /* @__PURE__ */ new Map();
10721
11686
  var configCache = /* @__PURE__ */ new Map();
10722
11687
  var defaultProjectRoots = /* @__PURE__ */ new Map();
11688
+ function getIndexBusyResult(error) {
11689
+ if (!isIndexLockContentionError(error)) return null;
11690
+ const owner = error.owner;
11691
+ const ownerText = owner ? `PID ${owner.pid}, operation ${owner.operation}, since ${owner.startedAt}` : "unreadable owner";
11692
+ if (error.reason === "legacy-lock") {
11693
+ return {
11694
+ kind: "busy",
11695
+ text: `INDEX_BUSY: legacy lock format detected (${ownerText}). Verify the PID and remove this lock manually only if it is stale.`
11696
+ };
11697
+ }
11698
+ if (error.reason === "unknown-owner") {
11699
+ return {
11700
+ kind: "busy",
11701
+ text: `INDEX_BUSY: unreadable or remote lock owner (${ownerText}). Automatic recovery was refused; manual verification is required.`
11702
+ };
11703
+ }
11704
+ return { kind: "busy", text: `INDEX_BUSY: another index operation is already in progress (${ownerText}).` };
11705
+ }
10723
11706
  function getProjectRoot(projectRoot, host) {
10724
11707
  if (projectRoot) {
10725
11708
  return projectRoot;
@@ -10764,8 +11747,8 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
10764
11747
  }
10765
11748
  function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
10766
11749
  const root = getProjectRoot(projectRoot, host);
10767
- const localIndexPath = path20.join(root, getHostProjectIndexRelativePath(host));
10768
- if ((0, import_fs13.existsSync)(localIndexPath)) {
11750
+ const localIndexPath = path21.join(root, getHostProjectIndexRelativePath(host));
11751
+ if ((0, import_fs14.existsSync)(localIndexPath)) {
10769
11752
  return false;
10770
11753
  }
10771
11754
  const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
@@ -10821,35 +11804,46 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
10821
11804
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
10822
11805
  const root = getProjectRoot(projectRoot, host);
10823
11806
  const key = getIndexerCacheKey(root, host);
10824
- const cachedConfig = configCache.get(key);
10825
11807
  let indexer = getIndexerForProject(root, host);
10826
- if (args.estimateOnly) {
10827
- return { kind: "estimate", estimate: await indexer.estimateCost() };
10828
- }
10829
- if (args.force) {
10830
- if (shouldForceLocalizeProjectIndex(root, host)) {
10831
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10832
- refreshIndexerForDirectory(root, host, cachedConfig);
10833
- indexer = getIndexerForProject(root, host);
10834
- }
10835
- await indexer.clearIndex();
10836
- refreshIndexerForDirectory(root, host, cachedConfig);
10837
- indexer = getIndexerForProject(root, host);
10838
- }
10839
- const stats = await indexer.index((progress) => {
10840
- if (onProgress) {
10841
- return onProgress(formatProgressTitle(progress), {
10842
- phase: progress.phase,
10843
- filesProcessed: progress.filesProcessed,
10844
- totalFiles: progress.totalFiles,
10845
- chunksProcessed: progress.chunksProcessed,
10846
- totalChunks: progress.totalChunks,
10847
- percentage: calculatePercentage(progress)
11808
+ const runtimeConfig = configCache.get(key);
11809
+ try {
11810
+ if (args.estimateOnly) {
11811
+ return { kind: "estimate", estimate: await indexer.estimateCost() };
11812
+ }
11813
+ const runIndex = async (target) => {
11814
+ const operation = args.force ? target.forceIndex.bind(target) : target.index.bind(target);
11815
+ return operation((progress) => {
11816
+ if (onProgress) {
11817
+ return onProgress(formatProgressTitle(progress), {
11818
+ phase: progress.phase,
11819
+ filesProcessed: progress.filesProcessed,
11820
+ totalFiles: progress.totalFiles,
11821
+ chunksProcessed: progress.chunksProcessed,
11822
+ totalChunks: progress.totalChunks,
11823
+ percentage: calculatePercentage(progress)
11824
+ });
11825
+ }
11826
+ return Promise.resolve();
10848
11827
  });
11828
+ };
11829
+ let stats;
11830
+ if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
11831
+ const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
11832
+ stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
11833
+ materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
11834
+ refreshIndexerForDirectory(root, host, runtimeConfig);
11835
+ indexer = getIndexerForProject(root, host);
11836
+ return runIndex(indexer);
11837
+ }, { completeRecoveries: false });
11838
+ } else {
11839
+ stats = await runIndex(indexer);
10849
11840
  }
10850
- return Promise.resolve();
10851
- });
10852
- return { kind: "stats", stats };
11841
+ return { kind: "stats", stats };
11842
+ } catch (error) {
11843
+ const busyResult = getIndexBusyResult(error);
11844
+ if (!busyResult) throw error;
11845
+ return busyResult;
11846
+ }
10853
11847
  }
10854
11848
  async function getIndexStatus(projectRoot, host) {
10855
11849
  const indexer = getIndexerForProject(projectRoot, host);
@@ -10859,6 +11853,15 @@ async function getIndexHealthCheck(projectRoot, host) {
10859
11853
  const indexer = getIndexerForProject(projectRoot, host);
10860
11854
  return indexer.healthCheck();
10861
11855
  }
11856
+ async function runIndexHealthCheck(projectRoot, host) {
11857
+ try {
11858
+ return { kind: "health", health: await getIndexHealthCheck(projectRoot, host) };
11859
+ } catch (error) {
11860
+ const busyResult = getIndexBusyResult(error);
11861
+ if (!busyResult) throw error;
11862
+ return busyResult;
11863
+ }
11864
+ }
10862
11865
  async function getPrImpact(projectRoot, host, params) {
10863
11866
  const indexer = getIndexerForProject(projectRoot, host);
10864
11867
  return indexer.getPrImpact({
@@ -11017,8 +12020,13 @@ Use Read tool to examine specific files.` }] };
11017
12020
  },
11018
12021
  async (args) => {
11019
12022
  const result = await runIndexCodebase(runtime.projectRoot, runtime.host, args);
11020
- const text = result.kind === "estimate" ? formatCostEstimate(result.estimate) : formatIndexStats(result.stats, args.verbose ?? false);
11021
- return { content: [{ type: "text", text }] };
12023
+ if (result.kind === "estimate") {
12024
+ return { content: [{ type: "text", text: formatCostEstimate(result.estimate) }] };
12025
+ }
12026
+ if (result.kind === "busy") {
12027
+ return { content: [{ type: "text", text: result.text }], isError: true };
12028
+ }
12029
+ return { content: [{ type: "text", text: formatIndexStats(result.stats, args.verbose ?? false) }] };
11022
12030
  }
11023
12031
  );
11024
12032
  server.tool(
@@ -11035,8 +12043,11 @@ Use Read tool to examine specific files.` }] };
11035
12043
  "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
11036
12044
  {},
11037
12045
  async () => {
11038
- const result = await getIndexHealthCheck(runtime.projectRoot, runtime.host);
11039
- return { content: [{ type: "text", text: formatHealthCheck(result) }] };
12046
+ const result = await runIndexHealthCheck(runtime.projectRoot, runtime.host);
12047
+ if (result.kind === "busy") {
12048
+ return { content: [{ type: "text", text: result.text }], isError: true };
12049
+ }
12050
+ return { content: [{ type: "text", text: formatHealthCheck(result.health) }] };
11040
12051
  }
11041
12052
  );
11042
12053
  server.tool(
@@ -11136,8 +12147,8 @@ ${formatSearchResults(results)}` }] };
11136
12147
  maxDepth: import_zod2.z.number().optional().default(10).describe("Maximum traversal depth (default: 10)")
11137
12148
  },
11138
12149
  async (args) => {
11139
- const path26 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
11140
- return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path26) }] };
12150
+ const path27 = await getCallGraphPath(runtime.projectRoot, runtime.host, args.from, args.to, args.maxDepth);
12151
+ return { content: [{ type: "text", text: formatCallGraphPath(args.from, args.to, path27) }] };
11141
12152
  }
11142
12153
  );
11143
12154
  server.tool(
@@ -11173,7 +12184,7 @@ ${formatSearchResults(results)}` }] };
11173
12184
  // src/mcp-server.ts
11174
12185
  var import_meta2 = {};
11175
12186
  function getPackageVersion() {
11176
- const raw = JSON.parse((0, import_fs14.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
12187
+ const raw = JSON.parse((0, import_fs15.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
11177
12188
  if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
11178
12189
  return raw.version;
11179
12190
  }
@@ -11194,18 +12205,37 @@ function createMcpServer(projectRoot, config, host = "opencode") {
11194
12205
  }
11195
12206
 
11196
12207
  // src/utils/auto-index.ts
12208
+ var INITIAL_RETRY_DELAY_MS = 50;
12209
+ var MAX_RETRY_DELAY_MS = 500;
12210
+ var autoIndexStates = /* @__PURE__ */ new WeakMap();
11197
12211
  function getErrorMessage3(error) {
11198
12212
  return error instanceof Error ? error.message : String(error);
11199
12213
  }
11200
- function startAutoIndex(indexer, projectRoot) {
11201
- indexer.initialize().then(() => {
11202
- indexer.index().catch((error) => {
11203
- console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
11204
- });
12214
+ function runAutoIndex(indexer, projectRoot, state) {
12215
+ void indexer.index().then(() => {
12216
+ autoIndexStates.delete(indexer);
11205
12217
  }).catch((error) => {
11206
- console.error(`[codebase-index] Auto-index initialization failed for "${projectRoot}": ${getErrorMessage3(error)}`);
12218
+ if (!isTransientIndexLockContention(error)) {
12219
+ autoIndexStates.delete(indexer);
12220
+ console.error(`[codebase-index] Auto-index failed for "${projectRoot}": ${getErrorMessage3(error)}`);
12221
+ return;
12222
+ }
12223
+ const retryDelayMs = state.retryDelayMs;
12224
+ state.retryDelayMs = Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
12225
+ const retryTimer = setTimeout(() => {
12226
+ runAutoIndex(indexer, projectRoot, state);
12227
+ }, retryDelayMs);
12228
+ retryTimer.unref?.();
11207
12229
  });
11208
12230
  }
12231
+ function startAutoIndex(indexer, projectRoot) {
12232
+ if (autoIndexStates.has(indexer)) return;
12233
+ const state = {
12234
+ retryDelayMs: INITIAL_RETRY_DELAY_MS
12235
+ };
12236
+ autoIndexStates.set(indexer, state);
12237
+ runAutoIndex(indexer, projectRoot, state);
12238
+ }
11209
12239
 
11210
12240
  // node_modules/chokidar/index.js
11211
12241
  var import_node_events = require("events");
@@ -11297,7 +12327,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
11297
12327
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
11298
12328
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
11299
12329
  if (wantBigintFsStats) {
11300
- this._stat = (path26) => statMethod(path26, { bigint: true });
12330
+ this._stat = (path27) => statMethod(path27, { bigint: true });
11301
12331
  } else {
11302
12332
  this._stat = statMethod;
11303
12333
  }
@@ -11322,8 +12352,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
11322
12352
  const par = this.parent;
11323
12353
  const fil = par && par.files;
11324
12354
  if (fil && fil.length > 0) {
11325
- const { path: path26, depth } = par;
11326
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path26));
12355
+ const { path: path27, depth } = par;
12356
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path27));
11327
12357
  const awaited = await Promise.all(slice);
11328
12358
  for (const entry of awaited) {
11329
12359
  if (!entry)
@@ -11363,20 +12393,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
11363
12393
  this.reading = false;
11364
12394
  }
11365
12395
  }
11366
- async _exploreDir(path26, depth) {
12396
+ async _exploreDir(path27, depth) {
11367
12397
  let files;
11368
12398
  try {
11369
- files = await (0, import_promises.readdir)(path26, this._rdOptions);
12399
+ files = await (0, import_promises.readdir)(path27, this._rdOptions);
11370
12400
  } catch (error) {
11371
12401
  this._onError(error);
11372
12402
  }
11373
- return { files, depth, path: path26 };
12403
+ return { files, depth, path: path27 };
11374
12404
  }
11375
- async _formatEntry(dirent, path26) {
12405
+ async _formatEntry(dirent, path27) {
11376
12406
  let entry;
11377
12407
  const basename5 = this._isDirent ? dirent.name : dirent;
11378
12408
  try {
11379
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path26, basename5));
12409
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path27, basename5));
11380
12410
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename5 };
11381
12411
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
11382
12412
  } catch (err) {
@@ -11776,16 +12806,16 @@ var delFromSet = (main2, prop, item) => {
11776
12806
  };
11777
12807
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
11778
12808
  var FsWatchInstances = /* @__PURE__ */ new Map();
11779
- function createFsWatchInstance(path26, options, listener, errHandler, emitRaw) {
12809
+ function createFsWatchInstance(path27, options, listener, errHandler, emitRaw) {
11780
12810
  const handleEvent = (rawEvent, evPath) => {
11781
- listener(path26);
11782
- emitRaw(rawEvent, evPath, { watchedPath: path26 });
11783
- if (evPath && path26 !== evPath) {
11784
- fsWatchBroadcast(sp.resolve(path26, evPath), KEY_LISTENERS, sp.join(path26, evPath));
12811
+ listener(path27);
12812
+ emitRaw(rawEvent, evPath, { watchedPath: path27 });
12813
+ if (evPath && path27 !== evPath) {
12814
+ fsWatchBroadcast(sp.resolve(path27, evPath), KEY_LISTENERS, sp.join(path27, evPath));
11785
12815
  }
11786
12816
  };
11787
12817
  try {
11788
- return (0, import_node_fs.watch)(path26, {
12818
+ return (0, import_node_fs.watch)(path27, {
11789
12819
  persistent: options.persistent
11790
12820
  }, handleEvent);
11791
12821
  } catch (error) {
@@ -11801,12 +12831,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
11801
12831
  listener(val1, val2, val3);
11802
12832
  });
11803
12833
  };
11804
- var setFsWatchListener = (path26, fullPath, options, handlers) => {
12834
+ var setFsWatchListener = (path27, fullPath, options, handlers) => {
11805
12835
  const { listener, errHandler, rawEmitter } = handlers;
11806
12836
  let cont = FsWatchInstances.get(fullPath);
11807
12837
  let watcher;
11808
12838
  if (!options.persistent) {
11809
- watcher = createFsWatchInstance(path26, options, listener, errHandler, rawEmitter);
12839
+ watcher = createFsWatchInstance(path27, options, listener, errHandler, rawEmitter);
11810
12840
  if (!watcher)
11811
12841
  return;
11812
12842
  return watcher.close.bind(watcher);
@@ -11817,7 +12847,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
11817
12847
  addAndConvert(cont, KEY_RAW, rawEmitter);
11818
12848
  } else {
11819
12849
  watcher = createFsWatchInstance(
11820
- path26,
12850
+ path27,
11821
12851
  options,
11822
12852
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
11823
12853
  errHandler,
@@ -11832,7 +12862,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
11832
12862
  cont.watcherUnusable = true;
11833
12863
  if (isWindows && error.code === "EPERM") {
11834
12864
  try {
11835
- const fd = await (0, import_promises2.open)(path26, "r");
12865
+ const fd = await (0, import_promises2.open)(path27, "r");
11836
12866
  await fd.close();
11837
12867
  broadcastErr(error);
11838
12868
  } catch (err) {
@@ -11863,7 +12893,7 @@ var setFsWatchListener = (path26, fullPath, options, handlers) => {
11863
12893
  };
11864
12894
  };
11865
12895
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
11866
- var setFsWatchFileListener = (path26, fullPath, options, handlers) => {
12896
+ var setFsWatchFileListener = (path27, fullPath, options, handlers) => {
11867
12897
  const { listener, rawEmitter } = handlers;
11868
12898
  let cont = FsWatchFileInstances.get(fullPath);
11869
12899
  const copts = cont && cont.options;
@@ -11885,7 +12915,7 @@ var setFsWatchFileListener = (path26, fullPath, options, handlers) => {
11885
12915
  });
11886
12916
  const currmtime = curr.mtimeMs;
11887
12917
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
11888
- foreach(cont.listeners, (listener2) => listener2(path26, curr));
12918
+ foreach(cont.listeners, (listener2) => listener2(path27, curr));
11889
12919
  }
11890
12920
  })
11891
12921
  };
@@ -11915,13 +12945,13 @@ var NodeFsHandler = class {
11915
12945
  * @param listener on fs change
11916
12946
  * @returns closer for the watcher instance
11917
12947
  */
11918
- _watchWithNodeFs(path26, listener) {
12948
+ _watchWithNodeFs(path27, listener) {
11919
12949
  const opts = this.fsw.options;
11920
- const directory = sp.dirname(path26);
11921
- const basename5 = sp.basename(path26);
12950
+ const directory = sp.dirname(path27);
12951
+ const basename5 = sp.basename(path27);
11922
12952
  const parent = this.fsw._getWatchedDir(directory);
11923
12953
  parent.add(basename5);
11924
- const absolutePath = sp.resolve(path26);
12954
+ const absolutePath = sp.resolve(path27);
11925
12955
  const options = {
11926
12956
  persistent: opts.persistent
11927
12957
  };
@@ -11931,12 +12961,12 @@ var NodeFsHandler = class {
11931
12961
  if (opts.usePolling) {
11932
12962
  const enableBin = opts.interval !== opts.binaryInterval;
11933
12963
  options.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
11934
- closer = setFsWatchFileListener(path26, absolutePath, options, {
12964
+ closer = setFsWatchFileListener(path27, absolutePath, options, {
11935
12965
  listener,
11936
12966
  rawEmitter: this.fsw._emitRaw
11937
12967
  });
11938
12968
  } else {
11939
- closer = setFsWatchListener(path26, absolutePath, options, {
12969
+ closer = setFsWatchListener(path27, absolutePath, options, {
11940
12970
  listener,
11941
12971
  errHandler: this._boundHandleError,
11942
12972
  rawEmitter: this.fsw._emitRaw
@@ -11958,7 +12988,7 @@ var NodeFsHandler = class {
11958
12988
  let prevStats = stats;
11959
12989
  if (parent.has(basename5))
11960
12990
  return;
11961
- const listener = async (path26, newStats) => {
12991
+ const listener = async (path27, newStats) => {
11962
12992
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
11963
12993
  return;
11964
12994
  if (!newStats || newStats.mtimeMs === 0) {
@@ -11972,11 +13002,11 @@ var NodeFsHandler = class {
11972
13002
  this.fsw._emit(EV.CHANGE, file, newStats2);
11973
13003
  }
11974
13004
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
11975
- this.fsw._closeFile(path26);
13005
+ this.fsw._closeFile(path27);
11976
13006
  prevStats = newStats2;
11977
13007
  const closer2 = this._watchWithNodeFs(file, listener);
11978
13008
  if (closer2)
11979
- this.fsw._addPathCloser(path26, closer2);
13009
+ this.fsw._addPathCloser(path27, closer2);
11980
13010
  } else {
11981
13011
  prevStats = newStats2;
11982
13012
  }
@@ -12008,7 +13038,7 @@ var NodeFsHandler = class {
12008
13038
  * @param item basename of this item
12009
13039
  * @returns true if no more processing is needed for this entry.
12010
13040
  */
12011
- async _handleSymlink(entry, directory, path26, item) {
13041
+ async _handleSymlink(entry, directory, path27, item) {
12012
13042
  if (this.fsw.closed) {
12013
13043
  return;
12014
13044
  }
@@ -12018,7 +13048,7 @@ var NodeFsHandler = class {
12018
13048
  this.fsw._incrReadyCount();
12019
13049
  let linkPath;
12020
13050
  try {
12021
- linkPath = await (0, import_promises2.realpath)(path26);
13051
+ linkPath = await (0, import_promises2.realpath)(path27);
12022
13052
  } catch (e) {
12023
13053
  this.fsw._emitReady();
12024
13054
  return true;
@@ -12028,12 +13058,12 @@ var NodeFsHandler = class {
12028
13058
  if (dir.has(item)) {
12029
13059
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
12030
13060
  this.fsw._symlinkPaths.set(full, linkPath);
12031
- this.fsw._emit(EV.CHANGE, path26, entry.stats);
13061
+ this.fsw._emit(EV.CHANGE, path27, entry.stats);
12032
13062
  }
12033
13063
  } else {
12034
13064
  dir.add(item);
12035
13065
  this.fsw._symlinkPaths.set(full, linkPath);
12036
- this.fsw._emit(EV.ADD, path26, entry.stats);
13066
+ this.fsw._emit(EV.ADD, path27, entry.stats);
12037
13067
  }
12038
13068
  this.fsw._emitReady();
12039
13069
  return true;
@@ -12063,9 +13093,9 @@ var NodeFsHandler = class {
12063
13093
  return;
12064
13094
  }
12065
13095
  const item = entry.path;
12066
- let path26 = sp.join(directory, item);
13096
+ let path27 = sp.join(directory, item);
12067
13097
  current.add(item);
12068
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path26, item)) {
13098
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path27, item)) {
12069
13099
  return;
12070
13100
  }
12071
13101
  if (this.fsw.closed) {
@@ -12074,8 +13104,8 @@ var NodeFsHandler = class {
12074
13104
  }
12075
13105
  if (item === target || !target && !previous.has(item)) {
12076
13106
  this.fsw._incrReadyCount();
12077
- path26 = sp.join(dir, sp.relative(dir, path26));
12078
- this._addToNodeFs(path26, initialAdd, wh, depth + 1);
13107
+ path27 = sp.join(dir, sp.relative(dir, path27));
13108
+ this._addToNodeFs(path27, initialAdd, wh, depth + 1);
12079
13109
  }
12080
13110
  }).on(EV.ERROR, this._boundHandleError);
12081
13111
  return new Promise((resolve15, reject) => {
@@ -12144,13 +13174,13 @@ var NodeFsHandler = class {
12144
13174
  * @param depth Child path actually targeted for watch
12145
13175
  * @param target Child path actually targeted for watch
12146
13176
  */
12147
- async _addToNodeFs(path26, initialAdd, priorWh, depth, target) {
13177
+ async _addToNodeFs(path27, initialAdd, priorWh, depth, target) {
12148
13178
  const ready = this.fsw._emitReady;
12149
- if (this.fsw._isIgnored(path26) || this.fsw.closed) {
13179
+ if (this.fsw._isIgnored(path27) || this.fsw.closed) {
12150
13180
  ready();
12151
13181
  return false;
12152
13182
  }
12153
- const wh = this.fsw._getWatchHelpers(path26);
13183
+ const wh = this.fsw._getWatchHelpers(path27);
12154
13184
  if (priorWh) {
12155
13185
  wh.filterPath = (entry) => priorWh.filterPath(entry);
12156
13186
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -12166,8 +13196,8 @@ var NodeFsHandler = class {
12166
13196
  const follow = this.fsw.options.followSymlinks;
12167
13197
  let closer;
12168
13198
  if (stats.isDirectory()) {
12169
- const absPath = sp.resolve(path26);
12170
- const targetPath = follow ? await (0, import_promises2.realpath)(path26) : path26;
13199
+ const absPath = sp.resolve(path27);
13200
+ const targetPath = follow ? await (0, import_promises2.realpath)(path27) : path27;
12171
13201
  if (this.fsw.closed)
12172
13202
  return;
12173
13203
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -12177,29 +13207,29 @@ var NodeFsHandler = class {
12177
13207
  this.fsw._symlinkPaths.set(absPath, targetPath);
12178
13208
  }
12179
13209
  } else if (stats.isSymbolicLink()) {
12180
- const targetPath = follow ? await (0, import_promises2.realpath)(path26) : path26;
13210
+ const targetPath = follow ? await (0, import_promises2.realpath)(path27) : path27;
12181
13211
  if (this.fsw.closed)
12182
13212
  return;
12183
13213
  const parent = sp.dirname(wh.watchPath);
12184
13214
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
12185
13215
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
12186
- closer = await this._handleDir(parent, stats, initialAdd, depth, path26, wh, targetPath);
13216
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path27, wh, targetPath);
12187
13217
  if (this.fsw.closed)
12188
13218
  return;
12189
13219
  if (targetPath !== void 0) {
12190
- this.fsw._symlinkPaths.set(sp.resolve(path26), targetPath);
13220
+ this.fsw._symlinkPaths.set(sp.resolve(path27), targetPath);
12191
13221
  }
12192
13222
  } else {
12193
13223
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
12194
13224
  }
12195
13225
  ready();
12196
13226
  if (closer)
12197
- this.fsw._addPathCloser(path26, closer);
13227
+ this.fsw._addPathCloser(path27, closer);
12198
13228
  return false;
12199
13229
  } catch (error) {
12200
13230
  if (this.fsw._handleError(error)) {
12201
13231
  ready();
12202
- return path26;
13232
+ return path27;
12203
13233
  }
12204
13234
  }
12205
13235
  }
@@ -12242,24 +13272,24 @@ function createPattern(matcher) {
12242
13272
  }
12243
13273
  return () => false;
12244
13274
  }
12245
- function normalizePath2(path26) {
12246
- if (typeof path26 !== "string")
13275
+ function normalizePath2(path27) {
13276
+ if (typeof path27 !== "string")
12247
13277
  throw new Error("string expected");
12248
- path26 = sp2.normalize(path26);
12249
- path26 = path26.replace(/\\/g, "/");
13278
+ path27 = sp2.normalize(path27);
13279
+ path27 = path27.replace(/\\/g, "/");
12250
13280
  let prepend = false;
12251
- if (path26.startsWith("//"))
13281
+ if (path27.startsWith("//"))
12252
13282
  prepend = true;
12253
- path26 = path26.replace(DOUBLE_SLASH_RE, "/");
13283
+ path27 = path27.replace(DOUBLE_SLASH_RE, "/");
12254
13284
  if (prepend)
12255
- path26 = "/" + path26;
12256
- return path26;
13285
+ path27 = "/" + path27;
13286
+ return path27;
12257
13287
  }
12258
13288
  function matchPatterns(patterns, testString, stats) {
12259
- const path26 = normalizePath2(testString);
13289
+ const path27 = normalizePath2(testString);
12260
13290
  for (let index = 0; index < patterns.length; index++) {
12261
13291
  const pattern = patterns[index];
12262
- if (pattern(path26, stats)) {
13292
+ if (pattern(path27, stats)) {
12263
13293
  return true;
12264
13294
  }
12265
13295
  }
@@ -12297,19 +13327,19 @@ var toUnix = (string) => {
12297
13327
  }
12298
13328
  return str;
12299
13329
  };
12300
- var normalizePathToUnix = (path26) => toUnix(sp2.normalize(toUnix(path26)));
12301
- var normalizeIgnored = (cwd = "") => (path26) => {
12302
- if (typeof path26 === "string") {
12303
- return normalizePathToUnix(sp2.isAbsolute(path26) ? path26 : sp2.join(cwd, path26));
13330
+ var normalizePathToUnix = (path27) => toUnix(sp2.normalize(toUnix(path27)));
13331
+ var normalizeIgnored = (cwd = "") => (path27) => {
13332
+ if (typeof path27 === "string") {
13333
+ return normalizePathToUnix(sp2.isAbsolute(path27) ? path27 : sp2.join(cwd, path27));
12304
13334
  } else {
12305
- return path26;
13335
+ return path27;
12306
13336
  }
12307
13337
  };
12308
- var getAbsolutePath = (path26, cwd) => {
12309
- if (sp2.isAbsolute(path26)) {
12310
- return path26;
13338
+ var getAbsolutePath = (path27, cwd) => {
13339
+ if (sp2.isAbsolute(path27)) {
13340
+ return path27;
12311
13341
  }
12312
- return sp2.join(cwd, path26);
13342
+ return sp2.join(cwd, path27);
12313
13343
  };
12314
13344
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
12315
13345
  var DirEntry = class {
@@ -12374,10 +13404,10 @@ var WatchHelper = class {
12374
13404
  dirParts;
12375
13405
  followSymlinks;
12376
13406
  statMethod;
12377
- constructor(path26, follow, fsw) {
13407
+ constructor(path27, follow, fsw) {
12378
13408
  this.fsw = fsw;
12379
- const watchPath = path26;
12380
- this.path = path26 = path26.replace(REPLACER_RE, "");
13409
+ const watchPath = path27;
13410
+ this.path = path27 = path27.replace(REPLACER_RE, "");
12381
13411
  this.watchPath = watchPath;
12382
13412
  this.fullWatchPath = sp2.resolve(watchPath);
12383
13413
  this.dirParts = [];
@@ -12517,20 +13547,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12517
13547
  this._closePromise = void 0;
12518
13548
  let paths = unifyPaths(paths_);
12519
13549
  if (cwd) {
12520
- paths = paths.map((path26) => {
12521
- const absPath = getAbsolutePath(path26, cwd);
13550
+ paths = paths.map((path27) => {
13551
+ const absPath = getAbsolutePath(path27, cwd);
12522
13552
  return absPath;
12523
13553
  });
12524
13554
  }
12525
- paths.forEach((path26) => {
12526
- this._removeIgnoredPath(path26);
13555
+ paths.forEach((path27) => {
13556
+ this._removeIgnoredPath(path27);
12527
13557
  });
12528
13558
  this._userIgnored = void 0;
12529
13559
  if (!this._readyCount)
12530
13560
  this._readyCount = 0;
12531
13561
  this._readyCount += paths.length;
12532
- Promise.all(paths.map(async (path26) => {
12533
- const res = await this._nodeFsHandler._addToNodeFs(path26, !_internal, void 0, 0, _origAdd);
13562
+ Promise.all(paths.map(async (path27) => {
13563
+ const res = await this._nodeFsHandler._addToNodeFs(path27, !_internal, void 0, 0, _origAdd);
12534
13564
  if (res)
12535
13565
  this._emitReady();
12536
13566
  return res;
@@ -12552,17 +13582,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12552
13582
  return this;
12553
13583
  const paths = unifyPaths(paths_);
12554
13584
  const { cwd } = this.options;
12555
- paths.forEach((path26) => {
12556
- if (!sp2.isAbsolute(path26) && !this._closers.has(path26)) {
13585
+ paths.forEach((path27) => {
13586
+ if (!sp2.isAbsolute(path27) && !this._closers.has(path27)) {
12557
13587
  if (cwd)
12558
- path26 = sp2.join(cwd, path26);
12559
- path26 = sp2.resolve(path26);
13588
+ path27 = sp2.join(cwd, path27);
13589
+ path27 = sp2.resolve(path27);
12560
13590
  }
12561
- this._closePath(path26);
12562
- this._addIgnoredPath(path26);
12563
- if (this._watched.has(path26)) {
13591
+ this._closePath(path27);
13592
+ this._addIgnoredPath(path27);
13593
+ if (this._watched.has(path27)) {
12564
13594
  this._addIgnoredPath({
12565
- path: path26,
13595
+ path: path27,
12566
13596
  recursive: true
12567
13597
  });
12568
13598
  }
@@ -12626,38 +13656,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12626
13656
  * @param stats arguments to be passed with event
12627
13657
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
12628
13658
  */
12629
- async _emit(event, path26, stats) {
13659
+ async _emit(event, path27, stats) {
12630
13660
  if (this.closed)
12631
13661
  return;
12632
13662
  const opts = this.options;
12633
13663
  if (isWindows)
12634
- path26 = sp2.normalize(path26);
13664
+ path27 = sp2.normalize(path27);
12635
13665
  if (opts.cwd)
12636
- path26 = sp2.relative(opts.cwd, path26);
12637
- const args = [path26];
13666
+ path27 = sp2.relative(opts.cwd, path27);
13667
+ const args = [path27];
12638
13668
  if (stats != null)
12639
13669
  args.push(stats);
12640
13670
  const awf = opts.awaitWriteFinish;
12641
13671
  let pw;
12642
- if (awf && (pw = this._pendingWrites.get(path26))) {
13672
+ if (awf && (pw = this._pendingWrites.get(path27))) {
12643
13673
  pw.lastChange = /* @__PURE__ */ new Date();
12644
13674
  return this;
12645
13675
  }
12646
13676
  if (opts.atomic) {
12647
13677
  if (event === EVENTS.UNLINK) {
12648
- this._pendingUnlinks.set(path26, [event, ...args]);
13678
+ this._pendingUnlinks.set(path27, [event, ...args]);
12649
13679
  setTimeout(() => {
12650
- this._pendingUnlinks.forEach((entry, path27) => {
13680
+ this._pendingUnlinks.forEach((entry, path28) => {
12651
13681
  this.emit(...entry);
12652
13682
  this.emit(EVENTS.ALL, ...entry);
12653
- this._pendingUnlinks.delete(path27);
13683
+ this._pendingUnlinks.delete(path28);
12654
13684
  });
12655
13685
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
12656
13686
  return this;
12657
13687
  }
12658
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path26)) {
13688
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path27)) {
12659
13689
  event = EVENTS.CHANGE;
12660
- this._pendingUnlinks.delete(path26);
13690
+ this._pendingUnlinks.delete(path27);
12661
13691
  }
12662
13692
  }
12663
13693
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -12675,16 +13705,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12675
13705
  this.emitWithAll(event, args);
12676
13706
  }
12677
13707
  };
12678
- this._awaitWriteFinish(path26, awf.stabilityThreshold, event, awfEmit);
13708
+ this._awaitWriteFinish(path27, awf.stabilityThreshold, event, awfEmit);
12679
13709
  return this;
12680
13710
  }
12681
13711
  if (event === EVENTS.CHANGE) {
12682
- const isThrottled = !this._throttle(EVENTS.CHANGE, path26, 50);
13712
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path27, 50);
12683
13713
  if (isThrottled)
12684
13714
  return this;
12685
13715
  }
12686
13716
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
12687
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path26) : path26;
13717
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path27) : path27;
12688
13718
  let stats2;
12689
13719
  try {
12690
13720
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -12715,23 +13745,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12715
13745
  * @param timeout duration of time to suppress duplicate actions
12716
13746
  * @returns tracking object or false if action should be suppressed
12717
13747
  */
12718
- _throttle(actionType, path26, timeout) {
13748
+ _throttle(actionType, path27, timeout) {
12719
13749
  if (!this._throttled.has(actionType)) {
12720
13750
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
12721
13751
  }
12722
13752
  const action = this._throttled.get(actionType);
12723
13753
  if (!action)
12724
13754
  throw new Error("invalid throttle");
12725
- const actionPath = action.get(path26);
13755
+ const actionPath = action.get(path27);
12726
13756
  if (actionPath) {
12727
13757
  actionPath.count++;
12728
13758
  return false;
12729
13759
  }
12730
13760
  let timeoutObject;
12731
13761
  const clear = () => {
12732
- const item = action.get(path26);
13762
+ const item = action.get(path27);
12733
13763
  const count = item ? item.count : 0;
12734
- action.delete(path26);
13764
+ action.delete(path27);
12735
13765
  clearTimeout(timeoutObject);
12736
13766
  if (item)
12737
13767
  clearTimeout(item.timeoutObject);
@@ -12739,7 +13769,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12739
13769
  };
12740
13770
  timeoutObject = setTimeout(clear, timeout);
12741
13771
  const thr = { timeoutObject, clear, count: 0 };
12742
- action.set(path26, thr);
13772
+ action.set(path27, thr);
12743
13773
  return thr;
12744
13774
  }
12745
13775
  _incrReadyCount() {
@@ -12753,44 +13783,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12753
13783
  * @param event
12754
13784
  * @param awfEmit Callback to be called when ready for event to be emitted.
12755
13785
  */
12756
- _awaitWriteFinish(path26, threshold, event, awfEmit) {
13786
+ _awaitWriteFinish(path27, threshold, event, awfEmit) {
12757
13787
  const awf = this.options.awaitWriteFinish;
12758
13788
  if (typeof awf !== "object")
12759
13789
  return;
12760
13790
  const pollInterval = awf.pollInterval;
12761
13791
  let timeoutHandler;
12762
- let fullPath = path26;
12763
- if (this.options.cwd && !sp2.isAbsolute(path26)) {
12764
- fullPath = sp2.join(this.options.cwd, path26);
13792
+ let fullPath = path27;
13793
+ if (this.options.cwd && !sp2.isAbsolute(path27)) {
13794
+ fullPath = sp2.join(this.options.cwd, path27);
12765
13795
  }
12766
13796
  const now = /* @__PURE__ */ new Date();
12767
13797
  const writes = this._pendingWrites;
12768
13798
  function awaitWriteFinishFn(prevStat) {
12769
13799
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
12770
- if (err || !writes.has(path26)) {
13800
+ if (err || !writes.has(path27)) {
12771
13801
  if (err && err.code !== "ENOENT")
12772
13802
  awfEmit(err);
12773
13803
  return;
12774
13804
  }
12775
13805
  const now2 = Number(/* @__PURE__ */ new Date());
12776
13806
  if (prevStat && curStat.size !== prevStat.size) {
12777
- writes.get(path26).lastChange = now2;
13807
+ writes.get(path27).lastChange = now2;
12778
13808
  }
12779
- const pw = writes.get(path26);
13809
+ const pw = writes.get(path27);
12780
13810
  const df = now2 - pw.lastChange;
12781
13811
  if (df >= threshold) {
12782
- writes.delete(path26);
13812
+ writes.delete(path27);
12783
13813
  awfEmit(void 0, curStat);
12784
13814
  } else {
12785
13815
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
12786
13816
  }
12787
13817
  });
12788
13818
  }
12789
- if (!writes.has(path26)) {
12790
- writes.set(path26, {
13819
+ if (!writes.has(path27)) {
13820
+ writes.set(path27, {
12791
13821
  lastChange: now,
12792
13822
  cancelWait: () => {
12793
- writes.delete(path26);
13823
+ writes.delete(path27);
12794
13824
  clearTimeout(timeoutHandler);
12795
13825
  return event;
12796
13826
  }
@@ -12801,8 +13831,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12801
13831
  /**
12802
13832
  * Determines whether user has asked to ignore this path.
12803
13833
  */
12804
- _isIgnored(path26, stats) {
12805
- if (this.options.atomic && DOT_RE.test(path26))
13834
+ _isIgnored(path27, stats) {
13835
+ if (this.options.atomic && DOT_RE.test(path27))
12806
13836
  return true;
12807
13837
  if (!this._userIgnored) {
12808
13838
  const { cwd } = this.options;
@@ -12812,17 +13842,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12812
13842
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
12813
13843
  this._userIgnored = anymatch(list, void 0);
12814
13844
  }
12815
- return this._userIgnored(path26, stats);
13845
+ return this._userIgnored(path27, stats);
12816
13846
  }
12817
- _isntIgnored(path26, stat4) {
12818
- return !this._isIgnored(path26, stat4);
13847
+ _isntIgnored(path27, stat4) {
13848
+ return !this._isIgnored(path27, stat4);
12819
13849
  }
12820
13850
  /**
12821
13851
  * Provides a set of common helpers and properties relating to symlink handling.
12822
13852
  * @param path file or directory pattern being watched
12823
13853
  */
12824
- _getWatchHelpers(path26) {
12825
- return new WatchHelper(path26, this.options.followSymlinks, this);
13854
+ _getWatchHelpers(path27) {
13855
+ return new WatchHelper(path27, this.options.followSymlinks, this);
12826
13856
  }
12827
13857
  // Directory helpers
12828
13858
  // -----------------
@@ -12854,63 +13884,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
12854
13884
  * @param item base path of item/directory
12855
13885
  */
12856
13886
  _remove(directory, item, isDirectory) {
12857
- const path26 = sp2.join(directory, item);
12858
- const fullPath = sp2.resolve(path26);
12859
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path26) || this._watched.has(fullPath);
12860
- if (!this._throttle("remove", path26, 100))
13887
+ const path27 = sp2.join(directory, item);
13888
+ const fullPath = sp2.resolve(path27);
13889
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path27) || this._watched.has(fullPath);
13890
+ if (!this._throttle("remove", path27, 100))
12861
13891
  return;
12862
13892
  if (!isDirectory && this._watched.size === 1) {
12863
13893
  this.add(directory, item, true);
12864
13894
  }
12865
- const wp = this._getWatchedDir(path26);
13895
+ const wp = this._getWatchedDir(path27);
12866
13896
  const nestedDirectoryChildren = wp.getChildren();
12867
- nestedDirectoryChildren.forEach((nested) => this._remove(path26, nested));
13897
+ nestedDirectoryChildren.forEach((nested) => this._remove(path27, nested));
12868
13898
  const parent = this._getWatchedDir(directory);
12869
13899
  const wasTracked = parent.has(item);
12870
13900
  parent.remove(item);
12871
13901
  if (this._symlinkPaths.has(fullPath)) {
12872
13902
  this._symlinkPaths.delete(fullPath);
12873
13903
  }
12874
- let relPath = path26;
13904
+ let relPath = path27;
12875
13905
  if (this.options.cwd)
12876
- relPath = sp2.relative(this.options.cwd, path26);
13906
+ relPath = sp2.relative(this.options.cwd, path27);
12877
13907
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
12878
13908
  const event = this._pendingWrites.get(relPath).cancelWait();
12879
13909
  if (event === EVENTS.ADD)
12880
13910
  return;
12881
13911
  }
12882
- this._watched.delete(path26);
13912
+ this._watched.delete(path27);
12883
13913
  this._watched.delete(fullPath);
12884
13914
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
12885
- if (wasTracked && !this._isIgnored(path26))
12886
- this._emit(eventName, path26);
12887
- this._closePath(path26);
13915
+ if (wasTracked && !this._isIgnored(path27))
13916
+ this._emit(eventName, path27);
13917
+ this._closePath(path27);
12888
13918
  }
12889
13919
  /**
12890
13920
  * Closes all watchers for a path
12891
13921
  */
12892
- _closePath(path26) {
12893
- this._closeFile(path26);
12894
- const dir = sp2.dirname(path26);
12895
- this._getWatchedDir(dir).remove(sp2.basename(path26));
13922
+ _closePath(path27) {
13923
+ this._closeFile(path27);
13924
+ const dir = sp2.dirname(path27);
13925
+ this._getWatchedDir(dir).remove(sp2.basename(path27));
12896
13926
  }
12897
13927
  /**
12898
13928
  * Closes only file-specific watchers
12899
13929
  */
12900
- _closeFile(path26) {
12901
- const closers = this._closers.get(path26);
13930
+ _closeFile(path27) {
13931
+ const closers = this._closers.get(path27);
12902
13932
  if (!closers)
12903
13933
  return;
12904
13934
  closers.forEach((closer) => closer());
12905
- this._closers.delete(path26);
13935
+ this._closers.delete(path27);
12906
13936
  }
12907
- _addPathCloser(path26, closer) {
13937
+ _addPathCloser(path27, closer) {
12908
13938
  if (!closer)
12909
13939
  return;
12910
- let list = this._closers.get(path26);
13940
+ let list = this._closers.get(path27);
12911
13941
  if (!list) {
12912
13942
  list = [];
12913
- this._closers.set(path26, list);
13943
+ this._closers.set(path27, list);
12914
13944
  }
12915
13945
  list.push(closer);
12916
13946
  }
@@ -12940,7 +13970,7 @@ function watch(paths, options = {}) {
12940
13970
  var chokidar_default = { watch, FSWatcher };
12941
13971
 
12942
13972
  // src/watcher/file-watcher.ts
12943
- var path21 = __toESM(require("path"), 1);
13973
+ var path22 = __toESM(require("path"), 1);
12944
13974
  var FileWatcher = class {
12945
13975
  watcher = null;
12946
13976
  projectRoot;
@@ -12951,6 +13981,8 @@ var FileWatcher = class {
12951
13981
  debounceTimer = null;
12952
13982
  debounceMs = 1e3;
12953
13983
  onChanges = null;
13984
+ readyPromise = null;
13985
+ resolveReady = null;
12954
13986
  constructor(projectRoot, config, host = "opencode", options = {}) {
12955
13987
  this.projectRoot = projectRoot;
12956
13988
  this.config = config;
@@ -12966,15 +13998,15 @@ var FileWatcher = class {
12966
13998
  const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
12967
13999
  this.watcher = chokidar_default.watch(watchTargets, {
12968
14000
  ignored: (filePath) => {
12969
- const relativePath = path21.relative(this.projectRoot, filePath);
14001
+ const relativePath = path22.relative(this.projectRoot, filePath);
12970
14002
  if (!relativePath) return false;
12971
14003
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
12972
14004
  return false;
12973
14005
  }
12974
- if (hasFilteredPathSegment(relativePath, path21.sep)) {
14006
+ if (hasFilteredPathSegment(relativePath, path22.sep)) {
12975
14007
  return true;
12976
14008
  }
12977
- if (isRestrictedDirectory(relativePath, path21.sep)) {
14009
+ if (isRestrictedDirectory(relativePath, path22.sep)) {
12978
14010
  return true;
12979
14011
  }
12980
14012
  if (ignoreFilter.ignores(relativePath)) {
@@ -12989,6 +14021,13 @@ var FileWatcher = class {
12989
14021
  pollInterval: 100
12990
14022
  }
12991
14023
  });
14024
+ this.readyPromise = new Promise((resolve15) => {
14025
+ this.resolveReady = resolve15;
14026
+ });
14027
+ this.watcher.once("ready", () => {
14028
+ this.resolveReady?.();
14029
+ this.resolveReady = null;
14030
+ });
12992
14031
  this.watcher.on("error", (error) => {
12993
14032
  const err = error instanceof Error ? error : null;
12994
14033
  if (err?.code === "EPERM" || err?.code === "EACCES") {
@@ -13020,24 +14059,24 @@ var FileWatcher = class {
13020
14059
  this.scheduleFlush();
13021
14060
  }
13022
14061
  isProjectConfigPath(filePath) {
13023
- const relativePath = path21.relative(this.projectRoot, filePath);
13024
- const normalizedRelativePath = path21.normalize(relativePath);
14062
+ const relativePath = path22.relative(this.projectRoot, filePath);
14063
+ const normalizedRelativePath = path22.normalize(relativePath);
13025
14064
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
13026
14065
  }
13027
14066
  isProjectConfigPathOrAncestor(relativePath) {
13028
- const normalizedRelativePath = path21.normalize(relativePath);
14067
+ const normalizedRelativePath = path22.normalize(relativePath);
13029
14068
  return this.getProjectConfigRelativePaths().some(
13030
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path21.sep}`)
14069
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path22.sep}`)
13031
14070
  );
13032
14071
  }
13033
14072
  getProjectConfigRelativePaths() {
13034
14073
  if (this.configPath) {
13035
- return [path21.normalize(path21.relative(this.projectRoot, this.configPath))];
14074
+ return [path22.normalize(path22.relative(this.projectRoot, this.configPath))];
13036
14075
  }
13037
14076
  return [
13038
14077
  resolveProjectConfigPath(this.projectRoot, this.host),
13039
14078
  resolveWritableProjectConfigPath(this.projectRoot, this.host)
13040
- ].map((configPath) => path21.normalize(path21.relative(this.projectRoot, configPath)));
14079
+ ].map((configPath) => path22.normalize(path22.relative(this.projectRoot, configPath)));
13041
14080
  }
13042
14081
  scheduleFlush() {
13043
14082
  if (this.debounceTimer) {
@@ -13052,7 +14091,7 @@ var FileWatcher = class {
13052
14091
  return;
13053
14092
  }
13054
14093
  const changes = Array.from(this.pendingChanges.entries()).map(
13055
- ([path26, type]) => ({ path: path26, type })
14094
+ ([path27, type]) => ({ path: path27, type })
13056
14095
  );
13057
14096
  this.pendingChanges.clear();
13058
14097
  try {
@@ -13061,25 +14100,32 @@ var FileWatcher = class {
13061
14100
  console.error("Error handling file changes:", error);
13062
14101
  }
13063
14102
  }
13064
- stop() {
14103
+ async stop() {
13065
14104
  if (this.debounceTimer) {
13066
14105
  clearTimeout(this.debounceTimer);
13067
14106
  this.debounceTimer = null;
13068
14107
  }
13069
14108
  if (this.watcher) {
13070
- this.watcher.close();
14109
+ const watcher = this.watcher;
13071
14110
  this.watcher = null;
14111
+ await watcher.close();
13072
14112
  }
14113
+ this.resolveReady?.();
14114
+ this.resolveReady = null;
14115
+ this.readyPromise = null;
13073
14116
  this.pendingChanges.clear();
13074
14117
  this.onChanges = null;
13075
14118
  }
13076
14119
  isRunning() {
13077
14120
  return this.watcher !== null;
13078
14121
  }
14122
+ async waitUntilReady() {
14123
+ await (this.readyPromise ?? Promise.resolve());
14124
+ }
13079
14125
  };
13080
14126
 
13081
14127
  // src/watcher/git-head-watcher.ts
13082
- var path22 = __toESM(require("path"), 1);
14128
+ var path23 = __toESM(require("path"), 1);
13083
14129
  var GitHeadWatcher = class {
13084
14130
  watcher = null;
13085
14131
  projectRoot;
@@ -13101,7 +14147,7 @@ var GitHeadWatcher = class {
13101
14147
  this.onBranchChange = handler;
13102
14148
  this.currentBranch = getCurrentBranch(this.projectRoot);
13103
14149
  const headPath = getHeadPath(this.projectRoot);
13104
- const refsPath = path22.join(this.projectRoot, ".git", "refs", "heads");
14150
+ const refsPath = path23.join(this.projectRoot, ".git", "refs", "heads");
13105
14151
  this.watcher = chokidar_default.watch([headPath, refsPath], {
13106
14152
  persistent: true,
13107
14153
  ignoreInitial: true,
@@ -13138,14 +14184,15 @@ var GitHeadWatcher = class {
13138
14184
  getCurrentBranch() {
13139
14185
  return this.currentBranch;
13140
14186
  }
13141
- stop() {
14187
+ async stop() {
13142
14188
  if (this.debounceTimer) {
13143
14189
  clearTimeout(this.debounceTimer);
13144
14190
  this.debounceTimer = null;
13145
14191
  }
13146
14192
  if (this.watcher) {
13147
- this.watcher.close();
14193
+ const watcher = this.watcher;
13148
14194
  this.watcher = null;
14195
+ await watcher.close();
13149
14196
  }
13150
14197
  this.onBranchChange = null;
13151
14198
  }
@@ -13163,6 +14210,8 @@ var BackgroundReindexer = class {
13163
14210
  running = false;
13164
14211
  pending = false;
13165
14212
  stopped = false;
14213
+ retryTimer = null;
14214
+ retryDelayMs = 50;
13166
14215
  request() {
13167
14216
  if (this.stopped) {
13168
14217
  return;
@@ -13173,9 +14222,13 @@ var BackgroundReindexer = class {
13173
14222
  stop() {
13174
14223
  this.stopped = true;
13175
14224
  this.pending = false;
14225
+ if (this.retryTimer) {
14226
+ clearTimeout(this.retryTimer);
14227
+ this.retryTimer = null;
14228
+ }
13176
14229
  }
13177
14230
  drain() {
13178
- if (this.stopped || this.running || !this.pending) {
14231
+ if (this.stopped || this.running || this.retryTimer || !this.pending) {
13179
14232
  return;
13180
14233
  }
13181
14234
  this.pending = false;
@@ -13183,13 +14236,29 @@ var BackgroundReindexer = class {
13183
14236
  void this.run();
13184
14237
  }
13185
14238
  async run() {
14239
+ let retryAfterContention = false;
13186
14240
  try {
13187
14241
  await this.runIndex();
14242
+ this.retryDelayMs = 50;
13188
14243
  } catch (error) {
13189
- console.error("[codebase-index] Background reindex failed:", error);
14244
+ if (isTransientIndexLockContention(error)) {
14245
+ this.pending = true;
14246
+ retryAfterContention = true;
14247
+ } else {
14248
+ console.error("[codebase-index] Background reindex failed:", error);
14249
+ }
13190
14250
  } finally {
13191
14251
  this.running = false;
13192
- this.drain();
14252
+ if (retryAfterContention && !this.stopped) {
14253
+ const delay = this.retryDelayMs;
14254
+ this.retryDelayMs = Math.min(this.retryDelayMs * 2, 500);
14255
+ this.retryTimer = setTimeout(() => {
14256
+ this.retryTimer = null;
14257
+ this.drain();
14258
+ }, delay);
14259
+ } else {
14260
+ this.drain();
14261
+ }
13193
14262
  }
13194
14263
  }
13195
14264
  };
@@ -13223,10 +14292,12 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
13223
14292
  return {
13224
14293
  fileWatcher,
13225
14294
  gitWatcher,
13226
- stop() {
14295
+ whenReady() {
14296
+ return fileWatcher.waitUntilReady();
14297
+ },
14298
+ async stop() {
13227
14299
  backgroundReindexer.stop();
13228
- fileWatcher.stop();
13229
- gitWatcher?.stop();
14300
+ await Promise.all([fileWatcher.stop(), gitWatcher?.stop()]);
13230
14301
  }
13231
14302
  };
13232
14303
  }
@@ -13245,7 +14316,7 @@ function getConfigPaths(projectRoot, host, options) {
13245
14316
 
13246
14317
  // src/tools/visualize/activity.ts
13247
14318
  var import_child_process4 = require("child_process");
13248
- var path23 = __toESM(require("path"), 1);
14319
+ var path24 = __toESM(require("path"), 1);
13249
14320
  function attachRecentActivity(data, projectRoot) {
13250
14321
  const activity = readGitActivity(projectRoot);
13251
14322
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -13407,7 +14478,7 @@ function normalizePath3(filePath) {
13407
14478
  return filePath.replace(/\\/g, "/");
13408
14479
  }
13409
14480
  function toGitRelativePath(projectRoot, filePath) {
13410
- const relativePath = path23.isAbsolute(filePath) ? path23.relative(projectRoot, filePath) : filePath;
14481
+ const relativePath = path24.isAbsolute(filePath) ? path24.relative(projectRoot, filePath) : filePath;
13411
14482
  return normalizePath3(relativePath);
13412
14483
  }
13413
14484
 
@@ -13665,7 +14736,7 @@ render();
13665
14736
  }
13666
14737
 
13667
14738
  // src/tools/visualize/transform.ts
13668
- var path24 = __toESM(require("path"), 1);
14739
+ var path25 = __toESM(require("path"), 1);
13669
14740
 
13670
14741
  // src/tools/visualize/modules.ts
13671
14742
  var MAX_MODULES = 18;
@@ -13925,7 +14996,7 @@ function transformForVisualization(symbols, edges, options = {}) {
13925
14996
  filePath: s.filePath,
13926
14997
  kind: s.kind,
13927
14998
  line: s.startLine,
13928
- directory: path24.dirname(s.filePath),
14999
+ directory: path25.dirname(s.filePath),
13929
15000
  moduleId: "",
13930
15001
  moduleLabel: ""
13931
15002
  }));
@@ -13954,9 +15025,9 @@ function parseArgs(argv) {
13954
15025
  let host = "opencode";
13955
15026
  for (let i = 2; i < argv.length; i++) {
13956
15027
  if (argv[i] === "--project" && argv[i + 1]) {
13957
- project = path25.resolve(argv[++i]);
15028
+ project = path26.resolve(argv[++i]);
13958
15029
  } else if (argv[i] === "--config" && argv[i + 1]) {
13959
- config = path25.resolve(argv[++i]);
15030
+ config = path26.resolve(argv[++i]);
13960
15031
  } else if (argv[i] === "--host" && argv[i + 1]) {
13961
15032
  host = parseHostMode(argv[++i]);
13962
15033
  } else if (argv[i] === "--host") {
@@ -13969,7 +15040,7 @@ function loadCliRawConfig(args) {
13969
15040
  return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
13970
15041
  }
13971
15042
  function isCliEntrypoint(moduleUrl, argvPath) {
13972
- return argvPath !== void 0 && (0, import_fs15.realpathSync)((0, import_url2.fileURLToPath)(moduleUrl)) === (0, import_fs15.realpathSync)(argvPath);
15043
+ return argvPath !== void 0 && (0, import_fs16.realpathSync)((0, import_url2.fileURLToPath)(moduleUrl)) === (0, import_fs16.realpathSync)(argvPath);
13973
15044
  }
13974
15045
  function parseVisualizeArgs(argv, cwd) {
13975
15046
  let project = cwd;
@@ -13979,7 +15050,7 @@ function parseVisualizeArgs(argv, cwd) {
13979
15050
  for (let i = 0; i < argv.length; i++) {
13980
15051
  const arg = argv[i];
13981
15052
  if (arg === "--project" && argv[i + 1]) {
13982
- project = path25.resolve(argv[++i]);
15053
+ project = path26.resolve(argv[++i]);
13983
15054
  } else if (arg === "--max" && argv[i + 1]) {
13984
15055
  maxNodes = Number(argv[++i]);
13985
15056
  } else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
@@ -14016,8 +15087,8 @@ async function handleVisualizeCommand(argv, cwd) {
14016
15087
  console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
14017
15088
  return 1;
14018
15089
  }
14019
- const outputPath = path25.join(os5.tmpdir(), `call-graph-${Date.now()}.html`);
14020
- (0, import_fs15.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
15090
+ const outputPath = path26.join(os6.tmpdir(), `call-graph-${Date.now()}.html`);
15091
+ (0, import_fs16.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
14021
15092
  console.log(`Temporal call graph visualization generated: ${outputPath}`);
14022
15093
  console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
14023
15094
  console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);
@@ -14046,7 +15117,7 @@ async function main() {
14046
15117
  const transport = new import_stdio.StdioServerTransport();
14047
15118
  await server.connect(transport);
14048
15119
  let watcher = null;
14049
- const isHomeDir = path25.resolve(args.project) === path25.resolve(os5.homedir());
15120
+ const isHomeDir = path26.resolve(args.project) === path26.resolve(os6.homedir());
14050
15121
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
14051
15122
  if (config.indexing.autoIndex && isValidProject) {
14052
15123
  const indexer = getIndexerForProject(args.project, args.host);
@@ -14061,14 +15132,25 @@ async function main() {
14061
15132
  args.config ? { configPath: args.config } : {}
14062
15133
  );
14063
15134
  }
14064
- const shutdown = () => {
14065
- watcher?.stop();
14066
- server.close().catch(() => {
14067
- });
14068
- process.exit(0);
15135
+ let shuttingDown = false;
15136
+ const shutdown = async () => {
15137
+ if (shuttingDown) return;
15138
+ shuttingDown = true;
15139
+ try {
15140
+ await watcher?.stop();
15141
+ await server.close();
15142
+ process.exit(0);
15143
+ } catch (error) {
15144
+ console.error("Failed to stop MCP server cleanly:", error);
15145
+ process.exit(1);
15146
+ }
14069
15147
  };
14070
- process.on("SIGINT", shutdown);
14071
- process.on("SIGTERM", shutdown);
15148
+ process.on("SIGINT", () => {
15149
+ void shutdown();
15150
+ });
15151
+ process.on("SIGTERM", () => {
15152
+ void shutdown();
15153
+ });
14072
15154
  }
14073
15155
  function handleMainError(error) {
14074
15156
  if (error instanceof Error && error.message.startsWith("Invalid host mode")) {