@wrongstack/tools 0.296.3 → 0.297.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.
Files changed (55) hide show
  1. package/dist/audit.js +15 -0
  2. package/dist/audit.js.map +2 -2
  3. package/dist/bash.js +15 -0
  4. package/dist/bash.js.map +2 -2
  5. package/dist/builtin.js +1291 -1216
  6. package/dist/builtin.js.map +4 -4
  7. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  8. package/dist/codebase-index/index.d.ts +1 -0
  9. package/dist/codebase-index/index.d.ts.map +1 -1
  10. package/dist/codebase-index/index.js +1259 -1196
  11. package/dist/codebase-index/index.js.map +4 -4
  12. package/dist/codebase-index/project-server-client.d.ts +13 -1
  13. package/dist/codebase-index/project-server-client.d.ts.map +1 -1
  14. package/dist/codebase-index/project-server-endpoint.d.ts +29 -1
  15. package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -1
  16. package/dist/codebase-index/project-server.js +20 -5
  17. package/dist/codebase-index/project-server.js.map +2 -2
  18. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  19. package/dist/exec.js +15 -0
  20. package/dist/exec.js.map +2 -2
  21. package/dist/format.js +15 -0
  22. package/dist/format.js.map +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1260 -1182
  26. package/dist/index.js.map +4 -4
  27. package/dist/install.js +15 -0
  28. package/dist/install.js.map +2 -2
  29. package/dist/languages/index.js +15 -0
  30. package/dist/languages/index.js.map +2 -2
  31. package/dist/lint.js +15 -0
  32. package/dist/lint.js.map +2 -2
  33. package/dist/next-steps.d.ts +23 -0
  34. package/dist/next-steps.d.ts.map +1 -1
  35. package/dist/next-steps.js +4 -0
  36. package/dist/next-steps.js.map +2 -2
  37. package/dist/outdated.js +15 -0
  38. package/dist/outdated.js.map +2 -2
  39. package/dist/pack.js +1291 -1216
  40. package/dist/pack.js.map +4 -4
  41. package/dist/process-registry.d.ts +9 -0
  42. package/dist/process-registry.d.ts.map +1 -1
  43. package/dist/process-registry.js +15 -0
  44. package/dist/process-registry.js.map +2 -2
  45. package/dist/ps-slash.js +15 -0
  46. package/dist/ps-slash.js.map +2 -2
  47. package/dist/read.js +67 -9
  48. package/dist/read.js.map +2 -2
  49. package/dist/test.js +15 -0
  50. package/dist/test.js.map +2 -2
  51. package/dist/tool-tier.js +1291 -1216
  52. package/dist/tool-tier.js.map +4 -4
  53. package/dist/typecheck.js +15 -0
  54. package/dist/typecheck.js.map +2 -2
  55. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -549,6 +549,7 @@ var init_process_registry = __esm({
549
549
  }
550
550
  /** Get all tracked processes. */
551
551
  list() {
552
+ this._pruneAllStale();
552
553
  return Array.from(this.processes.values());
553
554
  }
554
555
  /** Get processes filtered by name (e.g. 'bash', 'exec'). */
@@ -579,6 +580,7 @@ var init_process_registry = __esm({
579
580
  * Combined stats for observability — used by /ps and the TUI status bar.
580
581
  */
581
582
  stats() {
583
+ this._pruneAllStale();
582
584
  return {
583
585
  activeCount: this.activeCount,
584
586
  backgroundCount: this.activeBackgroundCount,
@@ -818,6 +820,19 @@ var init_process_registry = __esm({
818
820
  this.processes.delete(pid);
819
821
  }
820
822
  }
823
+ /**
824
+ * Remove every stale entry, not just one PID. `list()`/`stats()` — the
825
+ * surfaces the TUI status bar and `/ps` poll — must prune too: a child
826
+ * whose 'close' event never fires (e.g. Windows grandchildren holding stdio
827
+ * open) would otherwise linger in the registry until someone looks up its
828
+ * exact PID, and PID reuse meanwhile makes `get()`/`kill()` target the
829
+ * wrong process. RAM-leak audit 2026-07-31, MEDIUM.
830
+ */
831
+ _pruneAllStale() {
832
+ for (const [pid, entry] of this.processes) {
833
+ if (this._isStaleEntry(entry)) this.processes.delete(pid);
834
+ }
835
+ }
821
836
  };
822
837
  }
823
838
  });
@@ -4896,16 +4911,16 @@ var init_legacy_bridge = __esm({
4896
4911
  });
4897
4912
 
4898
4913
  // src/codebase-index/languages.ts
4899
- import * as path14 from "node:path";
4914
+ import * as path18 from "node:path";
4900
4915
  function detectLang(file) {
4901
- const base = path14.basename(file);
4916
+ const base = path18.basename(file);
4902
4917
  const lowerBase = base.toLowerCase();
4903
4918
  if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
4904
4919
  return "ts";
4905
4920
  }
4906
4921
  const special = SPECIAL_FILENAMES[lowerBase];
4907
4922
  if (special) return special;
4908
- const ext = path14.extname(base).toLowerCase();
4923
+ const ext = path18.extname(base).toLowerCase();
4909
4924
  if (!ext) return null;
4910
4925
  return EXT_TO_LANG[ext] ?? null;
4911
4926
  }
@@ -5244,10 +5259,10 @@ __export(go_parser_exports, {
5244
5259
  detectLang: () => detectLang,
5245
5260
  parseSymbols: () => parseSymbols2
5246
5261
  });
5247
- import { spawn as spawn4 } from "node:child_process";
5248
- import * as os5 from "node:os";
5249
- import * as path15 from "node:path";
5250
- import * as fs10 from "node:fs/promises";
5262
+ import { spawn as spawn5 } from "node:child_process";
5263
+ import * as os6 from "node:os";
5264
+ import * as path19 from "node:path";
5265
+ import * as fs14 from "node:fs/promises";
5251
5266
  async function parseSymbols2(opts) {
5252
5267
  const { file, content, lang } = opts;
5253
5268
  try {
@@ -5319,16 +5334,16 @@ async function syncGoParse(filePath, content, lang) {
5319
5334
  try {
5320
5335
  let scriptPath = _cachedGoScriptPath;
5321
5336
  if (!scriptPath) {
5322
- const tmpDir = await fs10.mkdtemp(path15.join(os5.tmpdir(), "ws-go-parse-"));
5323
- scriptPath = path15.join(tmpDir, "parse.go");
5324
- await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
5337
+ const tmpDir = await fs14.mkdtemp(path19.join(os6.tmpdir(), "ws-go-parse-"));
5338
+ scriptPath = path19.join(tmpDir, "parse.go");
5339
+ await fs14.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
5325
5340
  _cachedGoScriptPath = scriptPath;
5326
5341
  }
5327
5342
  const goBinary = resolveWin32Command("go");
5328
5343
  const goResult = await new Promise(
5329
5344
  (resolve17, reject) => {
5330
5345
  let settled = false;
5331
- const proc = spawn4(goBinary, ["run", scriptPath], {
5346
+ const proc = spawn5(goBinary, ["run", scriptPath], {
5332
5347
  stdio: ["pipe", "pipe", "pipe"],
5333
5348
  windowsHide: true
5334
5349
  });
@@ -5931,10 +5946,10 @@ __export(py_parser_exports, {
5931
5946
  detectLang: () => detectLang,
5932
5947
  parseSymbols: () => parseSymbols4
5933
5948
  });
5934
- import { spawn as spawn5 } from "node:child_process";
5935
- import * as fs11 from "node:fs/promises";
5936
- import * as os6 from "node:os";
5937
- import * as path16 from "node:path";
5949
+ import { spawn as spawn6 } from "node:child_process";
5950
+ import * as fs15 from "node:fs/promises";
5951
+ import * as os7 from "node:os";
5952
+ import * as path20 from "node:path";
5938
5953
  async function parseSymbols4(opts) {
5939
5954
  const { file, content, lang } = opts;
5940
5955
  try {
@@ -5956,7 +5971,7 @@ async function resolvePython() {
5956
5971
  function commandIsAvailable(command) {
5957
5972
  return new Promise((resolve17) => {
5958
5973
  let settled = false;
5959
- const proc = spawn5(command, ["--version"], {
5974
+ const proc = spawn6(command, ["--version"], {
5960
5975
  stdio: "ignore",
5961
5976
  windowsHide: true
5962
5977
  });
@@ -5978,7 +5993,7 @@ function commandIsAvailable(command) {
5978
5993
  function spawnPyParser(pyBinary, scriptPath, filePath, content) {
5979
5994
  return new Promise((resolve17, reject) => {
5980
5995
  let settled = false;
5981
- const proc = spawn5(pyBinary, [scriptPath, filePath], {
5996
+ const proc = spawn6(pyBinary, [scriptPath, filePath], {
5982
5997
  stdio: ["pipe", "pipe", "pipe"],
5983
5998
  windowsHide: true
5984
5999
  });
@@ -6012,10 +6027,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
6012
6027
  async function syncPyParse(filePath, content, lang) {
6013
6028
  try {
6014
6029
  if (!_cachedScriptPath) {
6015
- const tmpDir = path16.join(os6.tmpdir(), "ws-py-parse");
6016
- await fs11.mkdir(tmpDir, { recursive: true });
6017
- _cachedScriptPath = path16.join(tmpDir, "parse.py");
6018
- await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
6030
+ const tmpDir = path20.join(os7.tmpdir(), "ws-py-parse");
6031
+ await fs15.mkdir(tmpDir, { recursive: true });
6032
+ _cachedScriptPath = path20.join(tmpDir, "parse.py");
6033
+ await fs15.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
6019
6034
  }
6020
6035
  cachedPyBinary ??= resolvePython();
6021
6036
  const pyBinary = await cachedPyBinary;
@@ -6269,10 +6284,10 @@ __export(rs_parser_exports, {
6269
6284
  detectLang: () => detectLang,
6270
6285
  parseSymbols: () => parseSymbols5
6271
6286
  });
6272
- import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
6273
- import { execFile, spawn as spawn6 } from "node:child_process";
6274
- import * as fs12 from "node:fs/promises";
6275
- import * as path17 from "node:path";
6287
+ import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
6288
+ import { execFile, spawn as spawn7 } from "node:child_process";
6289
+ import * as fs16 from "node:fs/promises";
6290
+ import * as path21 from "node:path";
6276
6291
  async function parseSymbols5(opts) {
6277
6292
  const { file, content, lang } = opts;
6278
6293
  const nativeAvailable = await checkNativeParser();
@@ -6294,7 +6309,7 @@ function checkNativeParser() {
6294
6309
  nativeParserAvailability ??= (async () => {
6295
6310
  try {
6296
6311
  await probe("rustc", ["--version"]);
6297
- const toolsDir = path17.join(process.cwd(), "tools");
6312
+ const toolsDir = path21.join(process.cwd(), "tools");
6298
6313
  await probe(
6299
6314
  "cargo",
6300
6315
  [
@@ -6303,7 +6318,7 @@ function checkNativeParser() {
6303
6318
  "--format-version",
6304
6319
  "1",
6305
6320
  "--manifest-path",
6306
- path17.join(toolsDir, "Cargo.toml")
6321
+ path21.join(toolsDir, "Cargo.toml")
6307
6322
  ]
6308
6323
  );
6309
6324
  return true;
@@ -6315,17 +6330,17 @@ function checkNativeParser() {
6315
6330
  }
6316
6331
  async function tryNativeParse(file, content) {
6317
6332
  try {
6318
- const toolsDir = path17.join(process.cwd(), "tools");
6319
- const crateDir = path17.join(toolsDir, "syn-parser");
6320
- const tmpFile = path17.join(crateDir, "src", "input.rs");
6321
- await fs12.writeFile(tmpFile, content, "utf8");
6333
+ const toolsDir = path21.join(process.cwd(), "tools");
6334
+ const crateDir = path21.join(toolsDir, "syn-parser");
6335
+ const tmpFile = path21.join(crateDir, "src", "input.rs");
6336
+ await fs16.writeFile(tmpFile, content, "utf8");
6322
6337
  const cargoBinary = resolveWin32Command("cargo");
6323
6338
  const result = await new Promise(
6324
6339
  (resolve17, reject) => {
6325
6340
  let settled = false;
6326
- const proc = spawn6(
6341
+ const proc = spawn7(
6327
6342
  cargoBinary,
6328
- ["run", "--manifest-path", path17.join(toolsDir, "Cargo.toml")],
6343
+ ["run", "--manifest-path", path21.join(toolsDir, "Cargo.toml")],
6329
6344
  {
6330
6345
  cwd: process.cwd(),
6331
6346
  stdio: ["pipe", "pipe", "pipe"],
@@ -6384,7 +6399,7 @@ function regexParse(opts) {
6384
6399
  let hi = lineOffsets2.length - 1;
6385
6400
  while (lo < hi) {
6386
6401
  const mid = lo + hi + 1 >>> 1;
6387
- if (expectDefined2(lineOffsets2[mid]) <= offset) lo = mid;
6402
+ if (expectDefined3(lineOffsets2[mid]) <= offset) lo = mid;
6388
6403
  else hi = mid - 1;
6389
6404
  }
6390
6405
  return lo + 1;
@@ -6396,7 +6411,7 @@ function regexParse(opts) {
6396
6411
  for (const pattern of RS_PATTERNS) {
6397
6412
  pattern.regex.lastIndex = 0;
6398
6413
  for (let match = pattern.regex.exec(content); match !== null; match = pattern.regex.exec(content)) {
6399
- const name = expectDefined2(match[1]);
6414
+ const name = expectDefined3(match[1]);
6400
6415
  const offset = match.index ?? 0;
6401
6416
  const line = lineFromOffset(offset);
6402
6417
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6453,8 +6468,8 @@ __export(json_parser_exports, {
6453
6468
  detectLang: () => detectLang,
6454
6469
  parseSymbols: () => parseSymbols6
6455
6470
  });
6456
- import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
6457
- import * as path18 from "node:path";
6471
+ import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
6472
+ import * as path22 from "node:path";
6458
6473
  function parseSymbols6(opts) {
6459
6474
  const { file, content, lang } = opts;
6460
6475
  try {
@@ -6466,7 +6481,7 @@ function parseSymbols6(opts) {
6466
6481
  function regexParse2(opts) {
6467
6482
  const { file, content, lang } = opts;
6468
6483
  const symbols = [];
6469
- const basename13 = path18.basename(file).toLowerCase();
6484
+ const basename13 = path22.basename(file).toLowerCase();
6470
6485
  const isPackageJson = basename13 === "package.json";
6471
6486
  const isTsconfig = basename13 === "tsconfig.json" || basename13 === "tsconfig.build.json";
6472
6487
  const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
@@ -6481,22 +6496,22 @@ function regexParse2(opts) {
6481
6496
  let hi = lineOffsets2.length - 1;
6482
6497
  while (lo < hi) {
6483
6498
  const mid = lo + hi + 1 >>> 1;
6484
- if (expectDefined3(lineOffsets2[mid]) <= offset) lo = mid;
6499
+ if (expectDefined4(lineOffsets2[mid]) <= offset) lo = mid;
6485
6500
  else hi = mid - 1;
6486
6501
  }
6487
6502
  return lo + 1;
6488
6503
  }
6489
6504
  const rootMatch = content.match(/^\s*\{/m);
6490
6505
  if (rootMatch) {
6491
- const offset = expectDefined3(rootMatch.index);
6506
+ const offset = expectDefined4(rootMatch.index);
6492
6507
  const line = lineFromOffset(offset);
6493
6508
  symbols.push(
6494
6509
  makeSymbol({
6495
- name: path18.basename(file),
6510
+ name: path22.basename(file),
6496
6511
  kind: "object",
6497
6512
  line,
6498
6513
  col: 0,
6499
- signature: `"${path18.basename(file)}" = { ... }`,
6514
+ signature: `"${path22.basename(file)}" = { ... }`,
6500
6515
  file,
6501
6516
  lang
6502
6517
  })
@@ -6504,7 +6519,7 @@ function regexParse2(opts) {
6504
6519
  }
6505
6520
  const topLevelKeyRegex = /^\s*"([^"]+)"\s*:/gm;
6506
6521
  for (let match = topLevelKeyRegex.exec(content); match !== null; match = topLevelKeyRegex.exec(content)) {
6507
- const key = expectDefined3(match[1]);
6522
+ const key = expectDefined4(match[1]);
6508
6523
  const offset = match.index ?? 0;
6509
6524
  const line = lineFromOffset(offset);
6510
6525
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6551,7 +6566,7 @@ function regexParse2(opts) {
6551
6566
  const defsRegex = /"\$defs"\s*:|"\$defs"\s*:/g;
6552
6567
  const defsMatch = defsRegex.exec(content);
6553
6568
  if (defsMatch !== null) {
6554
- const offset = expectDefined3(defsMatch.index);
6569
+ const offset = expectDefined4(defsMatch.index);
6555
6570
  const line = lineFromOffset(offset);
6556
6571
  symbols.push(
6557
6572
  makeSymbol({
@@ -6576,7 +6591,7 @@ function regexParse2(opts) {
6576
6591
  for (let match = pat.exec(content); match !== null; match = pat.exec(content)) {
6577
6592
  const offset = match.index ?? 0;
6578
6593
  const line = lineFromOffset(offset);
6579
- const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined3(match[0]);
6594
+ const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined4(match[0]);
6580
6595
  symbols.push(
6581
6596
  makeSymbol({
6582
6597
  name: key,
@@ -6595,12 +6610,12 @@ function regexParse2(opts) {
6595
6610
  function extractPackageScripts(content, symbols, file, lang, lineOffsets2, lineFromOffset) {
6596
6611
  const scriptsBlockRegex = /"scripts"\s*:\s*\{([^}]+)\}/g;
6597
6612
  for (let match = scriptsBlockRegex.exec(content); match !== null; match = scriptsBlockRegex.exec(content)) {
6598
- const blockContent = expectDefined3(match[0]);
6613
+ const blockContent = expectDefined4(match[0]);
6599
6614
  const blockOffset = match.index ?? 0;
6600
6615
  const scriptKeyRegex = /"(\w[\w-]*)"\s*:/g;
6601
6616
  for (let scriptMatch = scriptKeyRegex.exec(blockContent); scriptMatch !== null; scriptMatch = scriptKeyRegex.exec(blockContent)) {
6602
- const key = expectDefined3(scriptMatch[1]);
6603
- const keyOffset = blockOffset + expectDefined3(scriptMatch.index);
6617
+ const key = expectDefined4(scriptMatch[1]);
6618
+ const keyOffset = blockOffset + expectDefined4(scriptMatch.index);
6604
6619
  const line = lineFromOffset(keyOffset);
6605
6620
  symbols.push(
6606
6621
  makeSymbol({
@@ -6619,12 +6634,12 @@ function extractPackageScripts(content, symbols, file, lang, lineOffsets2, lineF
6619
6634
  function extractCompilerOptions(content, symbols, file, lang, lineOffsets2, parentLine, lineFromOffset) {
6620
6635
  const optsBlockRegex = /"compilerOptions"\s*:\s*\{([^}]+)\}/g;
6621
6636
  for (let match = optsBlockRegex.exec(content); match !== null; match = optsBlockRegex.exec(content)) {
6622
- const blockContent = expectDefined3(match[0]);
6637
+ const blockContent = expectDefined4(match[0]);
6623
6638
  const blockOffset = match.index ?? 0;
6624
6639
  const optKeyRegex = /"(\w[\w]*)"\s*:/g;
6625
6640
  for (let optMatch = optKeyRegex.exec(blockContent); optMatch !== null; optMatch = optKeyRegex.exec(blockContent)) {
6626
- const key = expectDefined3(optMatch[1]);
6627
- const keyOffset = blockOffset + expectDefined3(optMatch.index);
6641
+ const key = expectDefined4(optMatch[1]);
6642
+ const keyOffset = blockOffset + expectDefined4(optMatch.index);
6628
6643
  const line = lineFromOffset(keyOffset);
6629
6644
  if (line <= parentLine) continue;
6630
6645
  symbols.push(
@@ -6669,7 +6684,7 @@ __export(yaml_parser_exports, {
6669
6684
  detectLang: () => detectLang,
6670
6685
  parseSymbols: () => parseSymbols7
6671
6686
  });
6672
- import { expectDefined as expectDefined4, truncate } from "@wrongstack/core/utils";
6687
+ import { expectDefined as expectDefined5, truncate } from "@wrongstack/core/utils";
6673
6688
  function parseSymbols7(opts) {
6674
6689
  const { file, content, lang } = opts;
6675
6690
  try {
@@ -6691,14 +6706,14 @@ function regexParse3(opts) {
6691
6706
  let hi = lineOffsets2.length - 1;
6692
6707
  while (lo < hi) {
6693
6708
  const mid = lo + hi + 1 >>> 1;
6694
- if (expectDefined4(lineOffsets2[mid]) <= offset) lo = mid;
6709
+ if (expectDefined5(lineOffsets2[mid]) <= offset) lo = mid;
6695
6710
  else hi = mid - 1;
6696
6711
  }
6697
6712
  return lo + 1;
6698
6713
  }
6699
6714
  const anchorRegex = /&(\w[\w-]*)/g;
6700
6715
  for (let match = anchorRegex.exec(content); match !== null; match = anchorRegex.exec(content)) {
6701
- const name = expectDefined4(match[1]);
6716
+ const name = expectDefined5(match[1]);
6702
6717
  const offset = match.index ?? 0;
6703
6718
  const line = lineFromOffset(offset);
6704
6719
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6716,7 +6731,7 @@ function regexParse3(opts) {
6716
6731
  }
6717
6732
  const aliasRegex = /\*(\w[\w-]*)/g;
6718
6733
  for (let match = aliasRegex.exec(content); match !== null; match = aliasRegex.exec(content)) {
6719
- const name = expectDefined4(match[1]);
6734
+ const name = expectDefined5(match[1]);
6720
6735
  const offset = match.index ?? 0;
6721
6736
  const line = lineFromOffset(offset);
6722
6737
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6751,7 +6766,7 @@ function regexParse3(opts) {
6751
6766
  }
6752
6767
  const listItemRegex = /^-(\s+)([^:#\s][^:#\s]*)\s*:/gm;
6753
6768
  for (let match = listItemRegex.exec(content); match !== null; match = listItemRegex.exec(content)) {
6754
- const key = expectDefined4(match[2]);
6769
+ const key = expectDefined5(match[2]);
6755
6770
  const offset = match.index ?? 0;
6756
6771
  const line = lineFromOffset(offset);
6757
6772
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6771,7 +6786,7 @@ function regexParse3(opts) {
6771
6786
  }
6772
6787
  const blockScalarRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:\s*[|>](\s|$)/gm;
6773
6788
  for (let match = blockScalarRegex.exec(content); match !== null; match = blockScalarRegex.exec(content)) {
6774
- const key = expectDefined4(match[2]);
6789
+ const key = expectDefined5(match[2]);
6775
6790
  const offset = match.index ?? 0;
6776
6791
  const line = lineFromOffset(offset);
6777
6792
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -10095,10 +10110,12 @@ async function shutdownBrowserTools() {
10095
10110
  await Promise.all(active.map((manager) => manager.dispose()));
10096
10111
  }
10097
10112
 
10098
- // src/codebase-index/background-indexer.ts
10099
- import * as fs18 from "node:fs";
10100
- import { fileURLToPath as fileURLToPath3 } from "node:url";
10101
- import { Worker } from "node:worker_threads";
10113
+ // src/codebase-index/project-server-client.ts
10114
+ import { spawn as spawn4 } from "node:child_process";
10115
+ import * as fs12 from "node:fs";
10116
+ import * as net3 from "node:net";
10117
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
10118
+ import { checkUnixSocketPath } from "@wrongstack/core/utils";
10102
10119
 
10103
10120
  // src/codebase-index/circuit-breaker.ts
10104
10121
  var CircuitOpenError = class extends Error {
@@ -10183,117 +10200,18 @@ function resetIndexCircuitBreaker() {
10183
10200
  indexCircuitBreaker.reset();
10184
10201
  }
10185
10202
 
10186
- // src/codebase-index/indexer.ts
10187
- import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
10188
- import { execFile as execFile2 } from "node:child_process";
10189
- import * as fs15 from "node:fs/promises";
10190
- import { availableParallelism } from "node:os";
10191
- import * as path22 from "node:path";
10192
- import {
10193
- DEFAULT_WALK_IGNORE_DIRS,
10194
- indexParallelBatchSize,
10195
- isFrugalPerf
10196
- } from "@wrongstack/core/utils";
10197
-
10198
- // src/codebase-index/gitignore.ts
10199
- import * as fs9 from "node:fs/promises";
10200
- import * as path13 from "node:path";
10201
- import { compileGlob } from "@wrongstack/core/utils";
10202
- function globBody(glob) {
10203
- return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
10204
- }
10205
- function compileGitignore(lines) {
10206
- const rules = [];
10207
- for (const raw of lines) {
10208
- let line = raw.replace(/\r$/, "");
10209
- if (!line.trim() || line.trimStart().startsWith("#")) continue;
10210
- line = line.trim();
10211
- let negated = false;
10212
- if (line.startsWith("!")) {
10213
- negated = true;
10214
- line = line.slice(1);
10215
- }
10216
- let dirOnly = false;
10217
- if (line.endsWith("/")) {
10218
- dirOnly = true;
10219
- line = line.slice(0, -1);
10220
- }
10221
- if (!line) continue;
10222
- const anchored = line.startsWith("/") || line.includes("/");
10223
- if (line.startsWith("/")) line = line.slice(1);
10224
- const body = globBody(line);
10225
- const prefix = anchored ? "^" : "(?:^|.*/)";
10226
- rules.push({
10227
- eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
10228
- under: new RegExp(`${prefix}${body}/.*$`),
10229
- negated,
10230
- dirOnly
10231
- });
10232
- }
10233
- return (relPath, isDir) => {
10234
- const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
10235
- let ignored = false;
10236
- for (const r of rules) {
10237
- const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
10238
- if (re.test(p)) ignored = !r.negated;
10239
- }
10240
- return ignored;
10241
- };
10242
- }
10243
- async function loadGitignoreMatcher(projectRoot) {
10244
- let lines = [];
10245
- try {
10246
- const raw = await fs9.readFile(path13.join(projectRoot, ".gitignore"), "utf8");
10247
- lines = raw.split("\n");
10248
- } catch {
10249
- }
10250
- return compileGitignore(lines);
10251
- }
10252
-
10253
- // src/codebase-index/indexer.ts
10254
- init_languages2();
10255
-
10256
- // src/codebase-index/parser-dispatch.ts
10257
- async function parseFileContent(file, content, lang) {
10258
- switch (lang) {
10259
- case "ts":
10260
- case "tsx":
10261
- case "js":
10262
- case "jsx": {
10263
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
10264
- return parseSymbols8({ file, content, lang });
10265
- }
10266
- case "go": {
10267
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
10268
- return parseSymbols8({ file, content, lang: "go" });
10269
- }
10270
- case "py": {
10271
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
10272
- return parseSymbols8({ file, content, lang: "py" });
10273
- }
10274
- case "rs": {
10275
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
10276
- return parseSymbols8({ file, content, lang: "rs" });
10277
- }
10278
- case "json": {
10279
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
10280
- return parseSymbols8({ file, content, lang: "json" });
10281
- }
10282
- case "yaml": {
10283
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
10284
- return parseSymbols8({ file, content, lang: "yaml" });
10285
- }
10286
- default: {
10287
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
10288
- return parseSymbols8({ file, content, lang });
10289
- }
10290
- }
10291
- }
10203
+ // src/codebase-index/project-server-endpoint.ts
10204
+ import { createHash as createHash4 } from "node:crypto";
10205
+ import * as fs11 from "node:fs";
10206
+ import * as os5 from "node:os";
10207
+ import * as path16 from "node:path";
10208
+ import { fileURLToPath } from "node:url";
10209
+ import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
10292
10210
 
10293
10211
  // src/codebase-index/writer.ts
10294
- import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
10295
- import * as fs14 from "node:fs";
10296
- import * as path21 from "node:path";
10212
+ import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
10213
+ import * as fs10 from "node:fs";
10214
+ import * as path15 from "node:path";
10297
10215
 
10298
10216
  // src/codebase-index/bm25.ts
10299
10217
  var K1 = 1.5;
@@ -10488,8 +10406,8 @@ function runSqliteWithRetry(fn) {
10488
10406
  }
10489
10407
 
10490
10408
  // src/codebase-index/writer-admin.ts
10491
- import * as fs13 from "node:fs";
10492
- import * as path19 from "node:path";
10409
+ import * as fs9 from "node:fs";
10410
+ import * as path13 from "node:path";
10493
10411
  var DB_FILE = "index.db";
10494
10412
  function getAllIndexableWithStatement(stmt) {
10495
10413
  return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
@@ -10548,7 +10466,7 @@ function getAllFileMetasWithStatement(stmt) {
10548
10466
  }
10549
10467
  function getIndexDbSizeBytes(indexDir) {
10550
10468
  try {
10551
- return fs13.statSync(path19.join(indexDir, DB_FILE)).size;
10469
+ return fs9.statSync(path13.join(indexDir, DB_FILE)).size;
10552
10470
  } catch {
10553
10471
  return 0;
10554
10472
  }
@@ -10615,7 +10533,7 @@ function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
10615
10533
  }
10616
10534
 
10617
10535
  // src/codebase-index/writer-graph-helpers.ts
10618
- import * as path20 from "node:path";
10536
+ import * as path14 from "node:path";
10619
10537
  function derivePackage(filePath) {
10620
10538
  const f = filePath.replace(/\\/g, "/");
10621
10539
  const pkgsIdx = f.indexOf("/packages/");
@@ -10730,16 +10648,16 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
10730
10648
  function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
10731
10649
  if (!moduleName.startsWith(".")) return void 0;
10732
10650
  const normalizedFrom = fromFile.replace(/\\/g, "/");
10733
- const absolute = path20.posix.normalize(
10734
- path20.posix.join(path20.posix.dirname(normalizedFrom), moduleName)
10651
+ const absolute = path14.posix.normalize(
10652
+ path14.posix.join(path14.posix.dirname(normalizedFrom), moduleName)
10735
10653
  );
10736
- const extension = path20.posix.extname(absolute);
10654
+ const extension = path14.posix.extname(absolute);
10737
10655
  const base = extension ? absolute.slice(0, -extension.length) : absolute;
10738
10656
  const candidates = [
10739
10657
  absolute,
10740
10658
  ...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
10741
- ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path20.posix.join(absolute, `index${ext}`)),
10742
- ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path20.posix.join(base, `index${ext}`))
10659
+ ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(absolute, `index${ext}`)),
10660
+ ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(base, `index${ext}`))
10743
10661
  ];
10744
10662
  const indexedByPortablePath = new Map(
10745
10663
  [...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
@@ -11255,9 +11173,9 @@ var IndexStore = class _IndexStore {
11255
11173
  }
11256
11174
  constructor(projectRoot, opts = {}) {
11257
11175
  this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
11258
- fs14.mkdirSync(this.indexDir, { recursive: true });
11176
+ fs10.mkdirSync(this.indexDir, { recursive: true });
11259
11177
  const Database = loadDatabaseSync();
11260
- this.db = new Database(path21.join(this.indexDir, DB_FILE2));
11178
+ this.db = new Database(path15.join(this.indexDir, DB_FILE2));
11261
11179
  applyIndexStorePragmas(this.db);
11262
11180
  this.initSchema();
11263
11181
  }
@@ -11672,13 +11590,13 @@ var IndexStore = class _IndexStore {
11672
11590
  if (rankDiff !== 0) return rankDiff;
11673
11591
  const scoreDiff = b.score - a.score;
11674
11592
  if (scoreDiff !== 0) return scoreDiff;
11675
- const left = expectDefined5(candidateById.get(a.id));
11676
- const right = expectDefined5(candidateById.get(b.id));
11593
+ const left = expectDefined2(candidateById.get(a.id));
11594
+ const right = expectDefined2(candidateById.get(b.id));
11677
11595
  return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
11678
11596
  });
11679
11597
  const qTokens = tokenise(query);
11680
11598
  const results = scored.slice(0, limit).map(({ id, score }) => {
11681
- const c = expectDefined5(candidateById.get(id));
11599
+ const c = expectDefined2(candidateById.get(id));
11682
11600
  return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
11683
11601
  });
11684
11602
  return { results, total: candidates.length };
@@ -12092,710 +12010,212 @@ var indexStorePool = new StorePool(
12092
12010
  (projectRoot, opts) => new IndexStore(projectRoot, opts)
12093
12011
  );
12094
12012
 
12095
- // src/codebase-index/indexer.ts
12096
- var YIELD_EVERY_N = 50;
12097
- function resolveParallelBatch() {
12098
- return indexParallelBatchSize(availableParallelism());
12099
- }
12100
- function yieldEventLoop() {
12101
- return new Promise((resolve17) => setImmediate(resolve17));
12013
+ // src/codebase-index/project-server-endpoint.ts
12014
+ var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
12015
+ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
12016
+ var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
12017
+ var buildIdCache;
12018
+ function projectIndexServerBuildId(entrypoint) {
12019
+ const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path16.resolve(entrypoint);
12020
+ try {
12021
+ const stat18 = fs11.statSync(file);
12022
+ if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat18.mtimeMs && buildIdCache.size === stat18.size) {
12023
+ return buildIdCache.buildId;
12024
+ }
12025
+ const buildId = createHash4("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
12026
+ buildIdCache = { file, mtimeMs: stat18.mtimeMs, size: stat18.size, buildId };
12027
+ return buildId;
12028
+ } catch {
12029
+ return `unreadable:${path16.basename(file)}`;
12030
+ }
12102
12031
  }
12103
- function throwIfAborted(signal) {
12104
- if (!signal?.aborted) return;
12105
- if (signal.reason instanceof Error) throw signal.reason;
12106
- throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
12032
+ function normalizeLocalPath(value) {
12033
+ const resolved = path16.resolve(value);
12034
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
12107
12035
  }
12108
- function isAbortError(err) {
12109
- return err instanceof DOMException && err.name === "AbortError";
12036
+ function projectIndexServerKey(projectRoot, indexDir) {
12037
+ const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
12038
+ return createHash4("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
12110
12039
  }
12111
- var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
12112
- var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
12113
- var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
12114
- var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
12115
- var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
12116
- function isWithinProject(projectRoot, file) {
12117
- const rel = path22.relative(projectRoot, file);
12118
- return rel !== "" && !rel.startsWith(`..${path22.sep}`) && rel !== ".." && !path22.isAbsolute(rel);
12040
+ function projectIndexServerEndpoint(projectRoot, indexDir) {
12041
+ const key = projectIndexServerKey(projectRoot, indexDir);
12042
+ if (process.platform === "win32") {
12043
+ return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
12044
+ }
12045
+ return path16.join(os5.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
12119
12046
  }
12120
- function isMissingPathError(err) {
12121
- const code = err?.code;
12122
- return code === "ENOENT" || code === "ENOTDIR";
12047
+ function projectIndexServerMetadataPath(projectRoot, indexDir) {
12048
+ return path16.join(
12049
+ path16.resolve(resolveIndexDir(projectRoot, indexDir)),
12050
+ PROJECT_INDEX_SERVER_METADATA_FILE
12051
+ );
12123
12052
  }
12124
- function normalizeComparablePath(value) {
12125
- const resolved = path22.resolve(value);
12126
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
12053
+
12054
+ // src/codebase-index/project-server-protocol.ts
12055
+ var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
12056
+ function encodeProjectServerMessage(message) {
12057
+ return `${JSON.stringify(message)}
12058
+ `;
12127
12059
  }
12128
- function gitOutput(projectRoot, args) {
12129
- return new Promise((resolve17, reject) => {
12130
- execFile2(
12131
- "git",
12132
- ["-C", projectRoot, ...args],
12133
- {
12134
- encoding: "buffer",
12135
- maxBuffer: MAX_GIT_FILE_LIST_BYTES,
12136
- windowsHide: true
12137
- },
12138
- (error, stdout) => {
12139
- if (error) reject(error);
12140
- else resolve17(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
12141
- }
12142
- );
12143
- });
12144
- }
12145
- async function findGitSourceFiles(projectRoot, ignore, signal) {
12146
- try {
12147
- throwIfAborted(signal);
12148
- const topLevel = (await gitOutput(projectRoot, ["rev-parse", "--show-toplevel"])).toString("utf8").trim();
12149
- if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
12150
- throwIfAborted(signal);
12151
- const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
12152
- const [output, statusOutput] = await Promise.all([
12153
- gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
12154
- gitOutput(projectRoot, [
12155
- "status",
12156
- "--porcelain=v1",
12157
- "-z",
12158
- "--untracked-files=all",
12159
- "--ignored=no"
12160
- ])
12161
- ]);
12162
- throwIfAborted(signal);
12163
- const dirty = /* @__PURE__ */ new Set();
12164
- const deleted = /* @__PURE__ */ new Set();
12165
- const statusRecords = statusOutput.toString("utf8").split("\0");
12166
- for (let i = 0; i < statusRecords.length; i++) {
12167
- const record = statusRecords[i];
12168
- if (!record) continue;
12169
- const status = record.slice(0, 2);
12170
- const changedPath = path22.resolve(projectRoot, record.slice(3));
12171
- dirty.add(changedPath);
12172
- if (status.includes("D")) deleted.add(changedPath);
12173
- if (status.includes("R") || status.includes("C")) {
12174
- const source = statusRecords[++i];
12175
- if (source) dirty.add(path22.resolve(projectRoot, source));
12060
+
12061
+ // src/codebase-index/project-server-client.ts
12062
+ var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
12063
+ var SERVER_START_TIMEOUT_MS = 1e4;
12064
+ var SERVER_CONTROL_TIMEOUT_MS = 5e3;
12065
+ var SERVER_HEALTH_TIMEOUT_MS = 3e3;
12066
+ var SERVER_HEARTBEAT_INTERVAL_MS = 1e4;
12067
+ var StaleProjectIndexServerError = class extends Error {
12068
+ constructor(message, pid) {
12069
+ super(message);
12070
+ this.pid = pid;
12071
+ }
12072
+ pid;
12073
+ name = "StaleProjectIndexServerError";
12074
+ };
12075
+ var connectionStates = /* @__PURE__ */ new Map();
12076
+ var connectionStateListeners = /* @__PURE__ */ new Set();
12077
+ var latestConnectionState = {
12078
+ status: "offline",
12079
+ connected: false
12080
+ };
12081
+ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
12082
+ if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
12083
+ return { kind: "inline-requested" };
12084
+ }
12085
+ let builtUrl = null;
12086
+ for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
12087
+ try {
12088
+ const url = new URL(rel, import.meta.url);
12089
+ if (url.protocol === "file:" && fs12.existsSync(fileURLToPath2(url))) {
12090
+ builtUrl = url;
12091
+ break;
12176
12092
  }
12093
+ } catch {
12177
12094
  }
12178
- const files = [];
12179
- for (const relative13 of output.toString("utf8").split("\0")) {
12180
- if (!relative13) continue;
12181
- const portable = relative13.replace(/\\/g, "/");
12182
- if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path22.posix.basename(portable))) {
12183
- continue;
12184
- }
12185
- const full = path22.resolve(projectRoot, relative13);
12186
- if (deleted.has(full)) continue;
12187
- const ext = path22.extname(relative13).toLowerCase();
12188
- if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
12095
+ }
12096
+ if (builtUrl === null) return { kind: "missing-build" };
12097
+ if (projectRoot !== void 0) {
12098
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12099
+ const check = checkUnixSocketPath(endpoint);
12100
+ if (!check.ok) {
12101
+ return {
12102
+ kind: "endpoint-invalid",
12103
+ endpoint,
12104
+ byteLength: check.byteLength,
12105
+ maxBytes: check.maxBytes
12106
+ };
12189
12107
  }
12190
- return {
12191
- files,
12192
- trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
12193
- };
12194
- } catch {
12195
- return null;
12196
12108
  }
12109
+ return { kind: "available", url: builtUrl };
12197
12110
  }
12198
- async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
12199
- const gitFiles = await findGitSourceFiles(projectRoot, ignore, signal);
12200
- if (gitFiles) {
12111
+ function resolveProjectServerUrl() {
12112
+ const availability = resolveProjectIndexDaemonAvailability();
12113
+ return availability.kind === "available" ? availability.url : null;
12114
+ }
12115
+ function projectIndexServerExpectedBuildId() {
12116
+ const override = process.env["WRONGSTACK_INDEX_SERVER_BUILD_ID"]?.trim();
12117
+ if (override) return override;
12118
+ const url = resolveProjectServerUrl();
12119
+ return url ? projectIndexServerBuildId(url) : null;
12120
+ }
12121
+ function isProjectIndexServerAvailable() {
12122
+ return resolveProjectServerUrl() !== null;
12123
+ }
12124
+ function publishConnectionState(endpoint, state) {
12125
+ connectionStates.set(endpoint, state);
12126
+ latestConnectionState = state;
12127
+ for (const listener of connectionStateListeners) listener(state);
12128
+ }
12129
+ function getProjectIndexServerConnectionState(projectRoot, indexDir) {
12130
+ if (projectRoot) {
12131
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12132
+ const existing = connectionStates.get(endpoint);
12133
+ if (existing) return existing;
12134
+ if (!isProjectIndexServerAvailable()) {
12135
+ return { status: "unavailable", connected: false };
12136
+ }
12201
12137
  return {
12202
- files: gitFiles.files,
12203
- complete: true,
12204
- errors: [],
12205
- trustedUnchanged: gitFiles.trustedUnchanged
12138
+ status: "offline",
12139
+ connected: false,
12140
+ projectRoot,
12141
+ indexDir,
12142
+ endpoint
12206
12143
  };
12207
12144
  }
12208
- const results = [];
12209
- const errors = [];
12210
- let complete = true;
12211
- const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
12212
- const indexableExts = new Set(INDEXABLE_EXTENSIONS);
12213
- let dirCount = 0;
12214
- const walk2 = async (dir) => {
12215
- throwIfAborted(signal);
12216
- if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
12217
- await yieldEventLoop();
12218
- throwIfAborted(signal);
12219
- }
12220
- let entries;
12221
- try {
12222
- entries = await fs15.readdir(dir, { withFileTypes: true });
12223
- } catch (err) {
12224
- complete = false;
12225
- errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
12226
- return;
12227
- }
12228
- dirCount++;
12229
- for (const e of entries) {
12230
- if (ignoreSet.has(e.name)) continue;
12231
- const full = path22.join(dir, e.name);
12232
- const rel = path22.relative(projectRoot, full).replace(/\\/g, "/");
12233
- if (e.isDirectory()) {
12234
- if (isGitIgnored(rel, true)) continue;
12235
- await walk2(full);
12236
- } else if (e.isFile()) {
12237
- if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
12238
- const ext = path22.extname(e.name).toLowerCase();
12239
- if (indexableExts.has(ext) || detectLang(full) !== null) {
12240
- results.push(full);
12241
- }
12242
- }
12243
- }
12244
- };
12245
- await walk2(projectRoot);
12246
- return { files: results, complete, errors };
12145
+ if (latestConnectionState.endpoint) return latestConnectionState;
12146
+ if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
12147
+ return latestConnectionState;
12247
12148
  }
12248
- function assignRefsToSymbols2(refs, symbols) {
12249
- if (refs.length === 0 || symbols.length === 0) return [];
12250
- const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
12251
- const seen = /* @__PURE__ */ new Set();
12252
- const assigned = [];
12253
- for (const ref of refs) {
12254
- let owner2;
12255
- for (const symbol of ordered) {
12256
- if (symbol.line > ref.line) break;
12257
- owner2 = symbol;
12258
- }
12259
- if (!owner2 && ref.callType === "import") owner2 = ordered[0];
12260
- if (!owner2 || owner2.id <= 0) continue;
12261
- const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
12262
- if (seen.has(key)) continue;
12263
- seen.add(key);
12264
- assigned.push({ ...ref, fromId: owner2.id });
12265
- }
12266
- return assigned;
12149
+ function onProjectIndexServerConnectionStateChange(listener) {
12150
+ connectionStateListeners.add(listener);
12151
+ return () => connectionStateListeners.delete(listener);
12267
12152
  }
12268
- async function runIndexerWithStore(store, opts) {
12269
- const { projectRoot, langs, ignore = [], signal } = opts;
12270
- const relationGraphVersion = "2";
12271
- const refResolutionVersion = "2";
12272
- const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
12273
- const needsFullRefResolution = force || store.getMetadata("ref_resolution_version") !== refResolutionVersion;
12274
- const startMs = Date.now();
12275
- const errors = [];
12276
- const langStats = {};
12277
- let filesIndexed = 0;
12278
- let symbolsIndexed = 0;
12279
- const isGitIgnored = await loadGitignoreMatcher(projectRoot);
12280
- let files;
12281
- let discoveredFiles = null;
12282
- let discoveryComplete = true;
12283
- let trustedUnchanged;
12284
- if (opts.files && opts.files.length > 0) {
12285
- files = opts.files.map((f) => path22.resolve(projectRoot, f)).filter((f) => {
12286
- if (!isWithinProject(projectRoot, f)) return false;
12287
- const rel = path22.relative(projectRoot, f).replace(/\\/g, "/");
12288
- return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path22.basename(f)) && !isGitIgnored(rel, false);
12289
- });
12290
- } else {
12291
- const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
12292
- files = discovery.files;
12293
- errors.push(...discovery.errors);
12294
- discoveryComplete = discovery.complete;
12295
- discoveredFiles = new Set(files);
12296
- trustedUnchanged = discovery.trustedUnchanged;
12153
+ function remoteError(message, name) {
12154
+ if (name === "LockError") return new LockError(message);
12155
+ if (name === "IndexTimeoutError") return new IndexTimeoutError(message);
12156
+ const error = new Error(message);
12157
+ if (name && name !== "Error") error.name = name;
12158
+ return error;
12159
+ }
12160
+ function isProjectIndexServerHealth(value) {
12161
+ if (!value || typeof value !== "object") return false;
12162
+ const health = value;
12163
+ const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
12164
+ const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
12165
+ return typeof health.checkedAt === "number" && typeof health.uptimeMs === "number" && typeof memory?.rss === "number" && typeof memory.heapUsed === "number" && typeof memory.heapTotal === "number" && typeof memory.external === "number" && typeof health.clients === "number" && typeof health.activeRequests === "number" && typeof health.activeWrites === "number" && typeof health.queuedWrites === "number" && typeof health.pendingExternalFiles === "number" && typeof health.watchingExternal === "boolean" && typeof activity?.indexing === "boolean" && typeof activity.currentFile === "number" && typeof activity.totalFiles === "number" && typeof activity.generation === "number";
12166
+ }
12167
+ function delay(ms) {
12168
+ return new Promise((resolve17) => {
12169
+ const timer = setTimeout(resolve17, ms);
12170
+ timer.unref?.();
12171
+ });
12172
+ }
12173
+ function cancellationError(signal) {
12174
+ return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
12175
+ }
12176
+ var ProjectServerConnection = class {
12177
+ constructor(projectRoot, indexDir, endpoint) {
12178
+ this.projectRoot = projectRoot;
12179
+ this.indexDir = indexDir;
12180
+ this.endpoint = endpoint;
12181
+ this.transition("offline");
12297
12182
  }
12298
- if (langs && langs.length > 0) {
12299
- const langSet = new Set(langs);
12300
- files = files.filter((f) => {
12301
- const lang = detectLang(f);
12302
- return lang ? langSet.has(lang) : false;
12183
+ projectRoot;
12184
+ indexDir;
12185
+ endpoint;
12186
+ socket = null;
12187
+ buffer = "";
12188
+ info = null;
12189
+ activity = null;
12190
+ health = null;
12191
+ healthCheck = null;
12192
+ connecting = null;
12193
+ connectResolve = null;
12194
+ connectReject = null;
12195
+ nextId = 1;
12196
+ pending = /* @__PURE__ */ new Map();
12197
+ transition(status, options = {}) {
12198
+ const previous = connectionStates.get(this.endpoint);
12199
+ const pid = options.pid ?? (status === "connected" ? this.info?.pid : void 0);
12200
+ const lastError = options.error === void 0 ? status === "error" || status === "degraded" || status === "unresponsive" ? previous?.lastError : void 0 : options.error instanceof Error ? options.error.message : String(options.error);
12201
+ publishConnectionState(this.endpoint, {
12202
+ status,
12203
+ connected: status === "connected" || status === "degraded" || status === "unresponsive",
12204
+ projectRoot: this.projectRoot,
12205
+ indexDir: this.indexDir,
12206
+ endpoint: this.endpoint,
12207
+ pid,
12208
+ lastError,
12209
+ ...this.activity ? { activity: this.activity } : {},
12210
+ ...this.health ? { health: this.health } : {}
12303
12211
  });
12304
12212
  }
12305
- if (force) store.clearAll();
12306
- const existingMeta = /* @__PURE__ */ new Map();
12307
- if (!force) {
12308
- for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
12213
+ isConnected() {
12214
+ return this.socket !== null && !this.socket.destroyed && this.info !== null;
12309
12215
  }
12310
- const totalFilesForProgress = files.length;
12311
- let filesPreSkipped = 0;
12312
- if (!force && trustedUnchanged) {
12313
- files = files.filter((file) => {
12314
- const meta = existingMeta.get(file);
12315
- if (!meta || !trustedUnchanged.has(file)) return true;
12316
- langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
12317
- symbolsIndexed += meta.symbolCount;
12318
- filesIndexed++;
12319
- filesPreSkipped++;
12320
- return false;
12321
- });
12322
- if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
12323
- }
12324
- const parallelBatch = resolveParallelBatch();
12325
- let filesSinceLastYield = 0;
12326
- for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
12327
- const batchEnd = Math.min(batchStart + parallelBatch, files.length);
12328
- const batchFiles = files.slice(batchStart, batchEnd);
12329
- opts.onProgress?.(filesPreSkipped + batchEnd, totalFilesForProgress);
12330
- filesSinceLastYield += batchFiles.length;
12331
- if (filesSinceLastYield >= YIELD_EVERY_N) {
12332
- filesSinceLastYield = 0;
12333
- await yieldEventLoop();
12334
- if (isFrugalPerf()) {
12335
- await new Promise((r) => setTimeout(r, 8));
12336
- }
12337
- throwIfAborted(signal);
12338
- }
12339
- const statOpts = signal ? { signal } : {};
12340
- const statReadParse = await Promise.allSettled(
12341
- batchFiles.map(
12342
- async (file) => {
12343
- let stat18;
12344
- try {
12345
- stat18 = await fs15.stat(file, statOpts);
12346
- } catch (e) {
12347
- if (isAbortError(e)) throw e;
12348
- return {
12349
- file,
12350
- stat: null,
12351
- lang: "",
12352
- parsed: null,
12353
- error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
12354
- missing: isMissingPathError(e)
12355
- };
12356
- }
12357
- if (!stat18.isFile()) return { file, stat: stat18, lang: "", parsed: null };
12358
- const lang = detectLang(file);
12359
- if (!lang) return { file, stat: stat18, lang: "", parsed: null };
12360
- if (stat18.size > MAX_INDEX_FILE_BYTES) {
12361
- return {
12362
- file,
12363
- stat: stat18,
12364
- lang,
12365
- parsed: null,
12366
- error: `file too large (${stat18.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
12367
- };
12368
- }
12369
- const meta = existingMeta.get(file);
12370
- if (!force && meta && meta.mtimeMs === Math.floor(stat18.mtimeMs)) {
12371
- return { file, stat: stat18, lang, parsed: null, skippedMeta: meta };
12372
- }
12373
- let content;
12374
- try {
12375
- content = await fs15.readFile(file, { encoding: "utf8", signal });
12376
- } catch (e) {
12377
- if (isAbortError(e)) throw e;
12378
- return {
12379
- file,
12380
- stat: stat18,
12381
- lang,
12382
- parsed: null,
12383
- error: `read error: ${e instanceof Error ? e.message : String(e)}`
12384
- };
12385
- }
12386
- let parsed;
12387
- try {
12388
- parsed = await parseFileContent(file, content, lang);
12389
- } catch (e) {
12390
- return {
12391
- file,
12392
- stat: stat18,
12393
- lang,
12394
- parsed: null,
12395
- error: `parse error: ${e instanceof Error ? e.message : String(e)}`
12396
- };
12397
- }
12398
- return { file, stat: stat18, lang, parsed, content };
12399
- }
12400
- )
12401
- );
12402
- const batchEntries = [];
12403
- const deleteForFiles = [];
12404
- for (let fi = 0; fi < statReadParse.length; fi++) {
12405
- const settled = statReadParse[fi];
12406
- const file = expectDefined6(batchFiles[fi]);
12407
- if (settled.status === "rejected") {
12408
- const err = settled.reason;
12409
- if (err instanceof Error && isAbortError(err)) throw err;
12410
- errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
12411
- continue;
12412
- }
12413
- const result = settled.value;
12414
- if (result.error) {
12415
- if (result.missing) store.deleteFile(file);
12416
- errors.push(`${file}: ${result.error}`);
12417
- continue;
12418
- }
12419
- const { stat: stat18, lang, parsed } = result;
12420
- if (result.skippedMeta) {
12421
- langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
12422
- symbolsIndexed += result.skippedMeta.symbolCount;
12423
- filesIndexed++;
12424
- continue;
12425
- }
12426
- if (!lang || !parsed) {
12427
- if (lang) {
12428
- store.upsertFile({
12429
- file,
12430
- lang,
12431
- mtimeMs: Math.floor(stat18.mtimeMs),
12432
- symbolCount: 0,
12433
- lastIndexed: Date.now()
12434
- });
12435
- filesIndexed++;
12436
- }
12437
- continue;
12438
- }
12439
- if (parsed.symbols.length === 0) {
12440
- store.replaceEmptyFile({
12441
- file,
12442
- lang,
12443
- mtimeMs: Math.floor(stat18.mtimeMs),
12444
- symbolCount: 0,
12445
- lastIndexed: Date.now()
12446
- });
12447
- filesIndexed++;
12448
- continue;
12449
- }
12450
- batchEntries.push({
12451
- file,
12452
- lang,
12453
- symbols: parsed.symbols,
12454
- refs: parsed.refs ?? [],
12455
- mtimeMs: Math.floor(stat18.mtimeMs),
12456
- symbolCount: parsed.symbols.length
12457
- });
12458
- deleteForFiles.push(file);
12459
- }
12460
- if (batchEntries.length > 0) {
12461
- try {
12462
- store.commitBatch(batchEntries, { deleteForFiles });
12463
- for (const entry of batchEntries) {
12464
- const count = entry.symbols.length;
12465
- symbolsIndexed += count;
12466
- langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
12467
- filesIndexed++;
12468
- }
12469
- } catch (err) {
12470
- const message = err instanceof Error ? err.message : String(err);
12471
- errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
12472
- for (const entry of batchEntries) {
12473
- try {
12474
- store.deleteRefsForFile(entry.file);
12475
- store.deleteSymbolsForFile(entry.file);
12476
- const symbolsWithIds = store.insertSymbols(entry.symbols);
12477
- symbolsIndexed += symbolsWithIds.length;
12478
- langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
12479
- filesIndexed++;
12480
- if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
12481
- const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
12482
- if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
12483
- }
12484
- store.resolveRefsForNames([
12485
- ...entry.symbols.map((symbol) => symbol.name),
12486
- ...entry.refs.map((ref) => ref.toName)
12487
- ]);
12488
- store.upsertFile({
12489
- file: entry.file,
12490
- lang: entry.lang,
12491
- mtimeMs: entry.mtimeMs,
12492
- symbolCount: entry.symbolCount,
12493
- lastIndexed: Date.now()
12494
- });
12495
- } catch (innerErr) {
12496
- errors.push(
12497
- `fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
12498
- );
12499
- }
12500
- }
12501
- }
12502
- }
12503
- }
12504
- if (discoveredFiles && discoveryComplete) {
12505
- for (const [file_] of existingMeta) {
12506
- if (!discoveredFiles.has(file_)) {
12507
- store.deleteFile(file_);
12508
- }
12509
- }
12510
- }
12511
- if (needsFullRefResolution) store.resolveRefs();
12512
- store.setMetadata("ref_resolution_version", refResolutionVersion);
12513
- store.setMetadata("relation_graph_version", relationGraphVersion);
12514
- if (!opts.files || filesIndexed >= 50) store.optimize();
12515
- store.setLastIndexed(Date.now());
12516
- if (!opts.files) store.compactIfNeeded();
12517
- const durationMs = Date.now() - startMs;
12518
- return {
12519
- filesIndexed,
12520
- symbolsIndexed,
12521
- langStats,
12522
- durationMs,
12523
- errors
12524
- };
12525
- }
12526
-
12527
- // src/codebase-index/index-service.ts
12528
- async function indexService(args, hooks = {}) {
12529
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12530
- try {
12531
- return await runIndexerWithStore(store, {
12532
- projectRoot: args.projectRoot,
12533
- indexDir: args.indexDir,
12534
- files: args.files,
12535
- force: args.force,
12536
- langs: args.langs,
12537
- ignore: args.ignore,
12538
- signal: hooks.signal,
12539
- onProgress: hooks.onProgress
12540
- });
12541
- } finally {
12542
- indexStorePool.release(store);
12543
- }
12544
- }
12545
- function searchService(args) {
12546
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12547
- try {
12548
- return store.searchRanked(
12549
- args.query,
12550
- {
12551
- kind: args.kind,
12552
- lang: args.lang,
12553
- file: args.file,
12554
- lspKind: args.lspKind
12555
- },
12556
- args.limit
12557
- );
12558
- } finally {
12559
- indexStorePool.release(store);
12560
- }
12561
- }
12562
- function statsService(args) {
12563
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12564
- try {
12565
- return store.getStats();
12566
- } finally {
12567
- indexStorePool.release(store);
12568
- }
12569
- }
12570
- function packageGraphService(args) {
12571
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12572
- try {
12573
- return store.getPackageGraph();
12574
- } finally {
12575
- indexStorePool.release(store);
12576
- }
12577
- }
12578
- function fileGraphService(args) {
12579
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12580
- try {
12581
- return store.getFileGraph(args.packageFilter);
12582
- } finally {
12583
- indexStorePool.release(store);
12584
- }
12585
- }
12586
- function symbolGraphService(args) {
12587
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12588
- try {
12589
- return store.getSymbolGraph(args.fileFilter);
12590
- } finally {
12591
- indexStorePool.release(store);
12592
- }
12593
- }
12594
-
12595
- // src/codebase-index/background-indexer.ts
12596
- init_languages2();
12597
-
12598
- // src/codebase-index/project-server-client.ts
12599
- import { spawn as spawn7 } from "node:child_process";
12600
- import * as fs17 from "node:fs";
12601
- import * as net3 from "node:net";
12602
- import { fileURLToPath as fileURLToPath2 } from "node:url";
12603
-
12604
- // src/codebase-index/project-server-endpoint.ts
12605
- import { createHash as createHash4 } from "node:crypto";
12606
- import * as fs16 from "node:fs";
12607
- import * as os7 from "node:os";
12608
- import * as path23 from "node:path";
12609
- import { fileURLToPath } from "node:url";
12610
- var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
12611
- var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
12612
- var buildIdCache;
12613
- function projectIndexServerBuildId(entrypoint) {
12614
- const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path23.resolve(entrypoint);
12615
- try {
12616
- const stat18 = fs16.statSync(file);
12617
- if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat18.mtimeMs && buildIdCache.size === stat18.size) {
12618
- return buildIdCache.buildId;
12619
- }
12620
- const buildId = createHash4("sha256").update(fs16.readFileSync(file)).digest("hex").slice(0, 24);
12621
- buildIdCache = { file, mtimeMs: stat18.mtimeMs, size: stat18.size, buildId };
12622
- return buildId;
12623
- } catch {
12624
- return `unreadable:${path23.basename(file)}`;
12625
- }
12626
- }
12627
- function normalizeLocalPath(value) {
12628
- const resolved = path23.resolve(value);
12629
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
12630
- }
12631
- function projectIndexServerKey(projectRoot, indexDir) {
12632
- const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
12633
- return createHash4("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
12634
- }
12635
- function projectIndexServerEndpoint(projectRoot, indexDir) {
12636
- const key = projectIndexServerKey(projectRoot, indexDir);
12637
- if (process.platform === "win32") {
12638
- return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
12639
- }
12640
- return path23.join(
12641
- os7.tmpdir(),
12642
- `wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`,
12643
- `${key}.sock`
12644
- );
12645
- }
12646
- function projectIndexServerMetadataPath(projectRoot, indexDir) {
12647
- return path23.join(
12648
- path23.resolve(resolveIndexDir(projectRoot, indexDir)),
12649
- PROJECT_INDEX_SERVER_METADATA_FILE
12650
- );
12651
- }
12652
-
12653
- // src/codebase-index/project-server-protocol.ts
12654
- var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
12655
- function encodeProjectServerMessage(message) {
12656
- return `${JSON.stringify(message)}
12657
- `;
12658
- }
12659
-
12660
- // src/codebase-index/project-server-client.ts
12661
- var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
12662
- var SERVER_START_TIMEOUT_MS = 1e4;
12663
- var SERVER_CONTROL_TIMEOUT_MS = 5e3;
12664
- var SERVER_HEALTH_TIMEOUT_MS = 3e3;
12665
- var SERVER_HEARTBEAT_INTERVAL_MS = 1e4;
12666
- var StaleProjectIndexServerError = class extends Error {
12667
- constructor(message, pid) {
12668
- super(message);
12669
- this.pid = pid;
12670
- }
12671
- pid;
12672
- name = "StaleProjectIndexServerError";
12673
- };
12674
- var connectionStates = /* @__PURE__ */ new Map();
12675
- var connectionStateListeners = /* @__PURE__ */ new Set();
12676
- var latestConnectionState = {
12677
- status: "offline",
12678
- connected: false
12679
- };
12680
- function resolveProjectIndexDaemonAvailability() {
12681
- if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
12682
- return { kind: "inline-requested" };
12683
- }
12684
- for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
12685
- try {
12686
- const url = new URL(rel, import.meta.url);
12687
- if (url.protocol === "file:" && fs17.existsSync(fileURLToPath2(url))) {
12688
- return { kind: "available", url };
12689
- }
12690
- } catch {
12691
- }
12692
- }
12693
- return { kind: "missing-build" };
12694
- }
12695
- function resolveProjectServerUrl() {
12696
- const availability = resolveProjectIndexDaemonAvailability();
12697
- return availability.kind === "available" ? availability.url : null;
12698
- }
12699
- function projectIndexServerExpectedBuildId() {
12700
- const override = process.env["WRONGSTACK_INDEX_SERVER_BUILD_ID"]?.trim();
12701
- if (override) return override;
12702
- const url = resolveProjectServerUrl();
12703
- return url ? projectIndexServerBuildId(url) : null;
12704
- }
12705
- function isProjectIndexServerAvailable() {
12706
- return resolveProjectServerUrl() !== null;
12707
- }
12708
- function publishConnectionState(endpoint, state) {
12709
- connectionStates.set(endpoint, state);
12710
- latestConnectionState = state;
12711
- for (const listener of connectionStateListeners) listener(state);
12712
- }
12713
- function getProjectIndexServerConnectionState(projectRoot, indexDir) {
12714
- if (projectRoot) {
12715
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12716
- const existing = connectionStates.get(endpoint);
12717
- if (existing) return existing;
12718
- if (!isProjectIndexServerAvailable()) {
12719
- return { status: "unavailable", connected: false };
12720
- }
12721
- return {
12722
- status: "offline",
12723
- connected: false,
12724
- projectRoot,
12725
- indexDir,
12726
- endpoint
12727
- };
12728
- }
12729
- if (latestConnectionState.endpoint) return latestConnectionState;
12730
- if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
12731
- return latestConnectionState;
12732
- }
12733
- function onProjectIndexServerConnectionStateChange(listener) {
12734
- connectionStateListeners.add(listener);
12735
- return () => connectionStateListeners.delete(listener);
12736
- }
12737
- function remoteError(message, name) {
12738
- if (name === "LockError") return new LockError(message);
12739
- if (name === "IndexTimeoutError") return new IndexTimeoutError(message);
12740
- const error = new Error(message);
12741
- if (name && name !== "Error") error.name = name;
12742
- return error;
12743
- }
12744
- function isProjectIndexServerHealth(value) {
12745
- if (!value || typeof value !== "object") return false;
12746
- const health = value;
12747
- const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
12748
- const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
12749
- return typeof health.checkedAt === "number" && typeof health.uptimeMs === "number" && typeof memory?.rss === "number" && typeof memory.heapUsed === "number" && typeof memory.heapTotal === "number" && typeof memory.external === "number" && typeof health.clients === "number" && typeof health.activeRequests === "number" && typeof health.activeWrites === "number" && typeof health.queuedWrites === "number" && typeof health.pendingExternalFiles === "number" && typeof health.watchingExternal === "boolean" && typeof activity?.indexing === "boolean" && typeof activity.currentFile === "number" && typeof activity.totalFiles === "number" && typeof activity.generation === "number";
12750
- }
12751
- function delay(ms) {
12752
- return new Promise((resolve17) => {
12753
- const timer = setTimeout(resolve17, ms);
12754
- timer.unref?.();
12755
- });
12756
- }
12757
- function cancellationError(signal) {
12758
- return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
12759
- }
12760
- var ProjectServerConnection = class {
12761
- constructor(projectRoot, indexDir, endpoint) {
12762
- this.projectRoot = projectRoot;
12763
- this.indexDir = indexDir;
12764
- this.endpoint = endpoint;
12765
- this.transition("offline");
12766
- }
12767
- projectRoot;
12768
- indexDir;
12769
- endpoint;
12770
- socket = null;
12771
- buffer = "";
12772
- info = null;
12773
- activity = null;
12774
- health = null;
12775
- healthCheck = null;
12776
- connecting = null;
12777
- connectResolve = null;
12778
- connectReject = null;
12779
- nextId = 1;
12780
- pending = /* @__PURE__ */ new Map();
12781
- transition(status, options = {}) {
12782
- const previous = connectionStates.get(this.endpoint);
12783
- const pid = options.pid ?? (status === "connected" ? this.info?.pid : void 0);
12784
- const lastError = options.error === void 0 ? status === "error" || status === "degraded" || status === "unresponsive" ? previous?.lastError : void 0 : options.error instanceof Error ? options.error.message : String(options.error);
12785
- publishConnectionState(this.endpoint, {
12786
- status,
12787
- connected: status === "connected" || status === "degraded" || status === "unresponsive",
12788
- projectRoot: this.projectRoot,
12789
- indexDir: this.indexDir,
12790
- endpoint: this.endpoint,
12791
- pid,
12792
- lastError,
12793
- ...this.activity ? { activity: this.activity } : {},
12794
- ...this.health ? { health: this.health } : {}
12795
- });
12796
- }
12797
- isConnected() {
12798
- return this.socket !== null && !this.socket.destroyed && this.info !== null;
12216
+ /** Safe LRU candidate: no request, connect, or health probe is in flight. */
12217
+ isEvictable() {
12218
+ return this.pending.size === 0 && this.connecting === null && this.healthCheck === null;
12799
12219
  }
12800
12220
  async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
12801
12221
  await this.ensureConnected(spawnIfMissing);
@@ -12895,371 +12315,1007 @@ var ProjectServerConnection = class {
12895
12315
  this.transition("connected", { pid: this.info?.pid });
12896
12316
  }
12897
12317
  }
12898
- close() {
12899
- const socket = this.socket;
12318
+ close() {
12319
+ const socket = this.socket;
12320
+ this.socket = null;
12321
+ this.info = null;
12322
+ this.activity = null;
12323
+ this.health = null;
12324
+ this.connectReject?.(new Error("codebase-index client disconnected"));
12325
+ this.connectResolve = null;
12326
+ this.connectReject = null;
12327
+ if (socket && !socket.destroyed) socket.destroy();
12328
+ this.rejectPending(new Error("codebase-index client disconnected"));
12329
+ this.transition("offline");
12330
+ maybeStopHeartbeatLoop();
12331
+ }
12332
+ request(message, options) {
12333
+ const socket = this.socket;
12334
+ if (!socket || socket.destroyed) {
12335
+ return Promise.reject(new Error("codebase-index server connection is not available"));
12336
+ }
12337
+ const id = this.nextId++;
12338
+ return new Promise((resolve17, reject) => {
12339
+ const timer = setTimeout(() => {
12340
+ const entry = this.pending.get(id);
12341
+ if (!entry) return;
12342
+ this.pending.delete(id);
12343
+ this.write({ type: "cancel", id });
12344
+ const error = new IndexTimeoutError(
12345
+ `Index ${message.type === "request" ? message.op : message.type} exceeded its ${options.timeoutMs}ms watchdog timeout`
12346
+ );
12347
+ this.cleanupPending(entry);
12348
+ entry.reject(error);
12349
+ }, options.timeoutMs);
12350
+ timer.unref?.();
12351
+ const signal = options.signal;
12352
+ const onAbort = signal ? () => {
12353
+ const entry = this.pending.get(id);
12354
+ if (!entry) return;
12355
+ this.pending.delete(id);
12356
+ this.write({ type: "cancel", id });
12357
+ this.cleanupPending(entry);
12358
+ entry.reject(cancellationError(signal));
12359
+ } : void 0;
12360
+ this.pending.set(id, {
12361
+ resolve: resolve17,
12362
+ reject,
12363
+ timer,
12364
+ signal,
12365
+ onAbort,
12366
+ onProgress: options.onProgress
12367
+ });
12368
+ if (signal && onAbort) {
12369
+ signal.addEventListener("abort", onAbort, { once: true });
12370
+ if (signal.aborted) {
12371
+ onAbort();
12372
+ return;
12373
+ }
12374
+ }
12375
+ this.write({ ...message, id });
12376
+ });
12377
+ }
12378
+ async ensureConnected(spawnIfMissing) {
12379
+ if (this.socket && !this.socket.destroyed && this.info) return;
12380
+ if (this.connecting) return this.connecting;
12381
+ this.transition("connecting");
12382
+ this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
12383
+ this.transition("error", { error });
12384
+ throw error;
12385
+ }).finally(() => {
12386
+ this.connecting = null;
12387
+ });
12388
+ return this.connecting;
12389
+ }
12390
+ async connectWithElection(spawnIfMissing) {
12391
+ const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
12392
+ let spawned = false;
12393
+ let staleAttempts = 0;
12394
+ let lastError = new Error("codebase-index server unavailable");
12395
+ while (Date.now() < deadline) {
12396
+ try {
12397
+ await this.connectOnce();
12398
+ return;
12399
+ } catch (error) {
12400
+ lastError = error;
12401
+ if (error instanceof StaleProjectIndexServerError) {
12402
+ staleAttempts++;
12403
+ if (!spawnIfMissing) break;
12404
+ if (staleAttempts >= 3) this.forceKillServer(error.pid);
12405
+ spawned = false;
12406
+ await delay(100);
12407
+ continue;
12408
+ }
12409
+ }
12410
+ if (!spawnIfMissing) break;
12411
+ if (!spawned) {
12412
+ this.spawnDetachedServer();
12413
+ spawned = true;
12414
+ }
12415
+ await delay(75);
12416
+ }
12417
+ throw lastError;
12418
+ }
12419
+ connectOnce() {
12420
+ this.socket?.destroy();
12421
+ this.socket = null;
12422
+ this.info = null;
12423
+ this.activity = null;
12424
+ this.health = null;
12425
+ this.buffer = "";
12426
+ return new Promise((resolve17, reject) => {
12427
+ const socket = net3.createConnection(this.endpoint);
12428
+ this.socket = socket;
12429
+ socket.setEncoding("utf8");
12430
+ const timer = setTimeout(() => {
12431
+ reject(new Error("codebase-index server handshake timed out"));
12432
+ socket.destroy();
12433
+ }, CONNECT_ATTEMPT_TIMEOUT_MS);
12434
+ timer.unref?.();
12435
+ const finishResolve = () => {
12436
+ clearTimeout(timer);
12437
+ this.connectResolve = null;
12438
+ this.connectReject = null;
12439
+ resolve17();
12440
+ };
12441
+ const finishReject = (error) => {
12442
+ clearTimeout(timer);
12443
+ this.connectResolve = null;
12444
+ this.connectReject = null;
12445
+ reject(error);
12446
+ };
12447
+ this.connectResolve = finishResolve;
12448
+ this.connectReject = finishReject;
12449
+ socket.on("data", (chunk) => this.onData(socket, chunk));
12450
+ socket.on("error", (error) => {
12451
+ if (!this.info) finishReject(error);
12452
+ });
12453
+ socket.on("close", () => this.onClose(socket));
12454
+ });
12455
+ }
12456
+ onData(socket, chunk) {
12457
+ if (socket !== this.socket) return;
12458
+ this.buffer += chunk;
12459
+ while (true) {
12460
+ const newline = this.buffer.indexOf("\n");
12461
+ if (newline < 0) {
12462
+ if (this.buffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
12463
+ socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
12464
+ }
12465
+ return;
12466
+ }
12467
+ if (newline > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
12468
+ socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
12469
+ return;
12470
+ }
12471
+ const line = this.buffer.slice(0, newline);
12472
+ this.buffer = this.buffer.slice(newline + 1);
12473
+ if (!line) continue;
12474
+ let message;
12475
+ try {
12476
+ message = JSON.parse(line);
12477
+ } catch {
12478
+ socket.destroy(new Error("invalid codebase-index server response"));
12479
+ return;
12480
+ }
12481
+ this.onMessage(message);
12482
+ }
12483
+ }
12484
+ onMessage(message) {
12485
+ if (message.type === "hello") {
12486
+ if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
12487
+ this.rejectStaleServer(
12488
+ message,
12489
+ `codebase-index protocol mismatch: client=${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
12490
+ );
12491
+ return;
12492
+ }
12493
+ const expectedBuildId = projectIndexServerExpectedBuildId();
12494
+ if (expectedBuildId && message.buildId !== expectedBuildId) {
12495
+ this.rejectStaleServer(
12496
+ message,
12497
+ `codebase-index build mismatch: client=${expectedBuildId}, server=${message.buildId ?? "legacy"}`
12498
+ );
12499
+ return;
12500
+ }
12501
+ this.info = message;
12502
+ this.markResponsive();
12503
+ this.transition("connected", { pid: message.pid });
12504
+ ensureHeartbeatLoop();
12505
+ this.connectResolve?.();
12506
+ return;
12507
+ }
12508
+ if (message.type === "index-state") {
12509
+ this.activity = message.state;
12510
+ this.markResponsive();
12511
+ this.transition("connected", { pid: this.info?.pid });
12512
+ return;
12513
+ }
12514
+ const entry = this.pending.get(message.id);
12515
+ if (!entry) return;
12516
+ this.markResponsive();
12517
+ const status = connectionStates.get(this.endpoint)?.status;
12518
+ if (status === "degraded" || status === "unresponsive") {
12519
+ this.transition("connected", { pid: this.info?.pid });
12520
+ }
12521
+ if (message.type === "progress") {
12522
+ entry.onProgress?.(message.current, message.total);
12523
+ return;
12524
+ }
12525
+ this.pending.delete(message.id);
12526
+ this.cleanupPending(entry);
12527
+ if (message.ok) entry.resolve(message.result);
12528
+ else entry.reject(remoteError(message.error, message.errorName));
12529
+ }
12530
+ onClose(socket) {
12531
+ if (socket !== this.socket) return;
12532
+ const wasConnected = this.info !== null;
12900
12533
  this.socket = null;
12901
12534
  this.info = null;
12902
12535
  this.activity = null;
12903
12536
  this.health = null;
12904
- this.connectReject?.(new Error("codebase-index client disconnected"));
12537
+ const error = new Error("codebase-index server connection closed");
12538
+ this.connectReject?.(error);
12905
12539
  this.connectResolve = null;
12906
12540
  this.connectReject = null;
12907
- if (socket && !socket.destroyed) socket.destroy();
12908
- this.rejectPending(new Error("codebase-index client disconnected"));
12909
- this.transition("offline");
12541
+ this.rejectPending(error);
12542
+ if (wasConnected) this.transition("error", { error });
12910
12543
  maybeStopHeartbeatLoop();
12911
12544
  }
12912
- request(message, options) {
12913
- const socket = this.socket;
12914
- if (!socket || socket.destroyed) {
12915
- return Promise.reject(new Error("codebase-index server connection is not available"));
12545
+ cleanupPending(entry) {
12546
+ clearTimeout(entry.timer);
12547
+ if (entry.signal && entry.onAbort) {
12548
+ entry.signal.removeEventListener("abort", entry.onAbort);
12916
12549
  }
12917
- const id = this.nextId++;
12918
- return new Promise((resolve17, reject) => {
12919
- const timer = setTimeout(() => {
12920
- const entry = this.pending.get(id);
12921
- if (!entry) return;
12922
- this.pending.delete(id);
12923
- this.write({ type: "cancel", id });
12924
- const error = new IndexTimeoutError(
12925
- `Index ${message.type === "request" ? message.op : message.type} exceeded its ${options.timeoutMs}ms watchdog timeout`
12926
- );
12927
- this.cleanupPending(entry);
12928
- entry.reject(error);
12929
- }, options.timeoutMs);
12550
+ }
12551
+ rejectPending(error) {
12552
+ const entries = [...this.pending.values()];
12553
+ this.pending.clear();
12554
+ for (const entry of entries) {
12555
+ this.cleanupPending(entry);
12556
+ entry.reject(error);
12557
+ }
12558
+ }
12559
+ write(message) {
12560
+ const socket = this.socket;
12561
+ if (socket && !socket.destroyed) socket.write(encodeProjectServerMessage(message));
12562
+ }
12563
+ rejectStaleServer(message, reason) {
12564
+ const socket = this.socket;
12565
+ if (socket && !socket.destroyed) {
12566
+ socket.write(
12567
+ encodeProjectServerMessage({
12568
+ type: "shutdown",
12569
+ id: 0,
12570
+ reason: "stale-build-replacement"
12571
+ })
12572
+ );
12573
+ const timer = setTimeout(() => socket.destroy(), 25);
12930
12574
  timer.unref?.();
12931
- const signal = options.signal;
12932
- const onAbort = signal ? () => {
12933
- const entry = this.pending.get(id);
12934
- if (!entry) return;
12935
- this.pending.delete(id);
12936
- this.write({ type: "cancel", id });
12937
- this.cleanupPending(entry);
12938
- entry.reject(cancellationError(signal));
12939
- } : void 0;
12940
- this.pending.set(id, {
12941
- resolve: resolve17,
12942
- reject,
12943
- timer,
12944
- signal,
12945
- onAbort,
12946
- onProgress: options.onProgress
12947
- });
12948
- if (signal && onAbort) {
12949
- signal.addEventListener("abort", onAbort, { once: true });
12950
- if (signal.aborted) {
12951
- onAbort();
12952
- return;
12953
- }
12575
+ }
12576
+ this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
12577
+ }
12578
+ spawnDetachedServer() {
12579
+ const url = resolveProjectServerUrl();
12580
+ if (!url) throw new Error("built codebase-index project server is unavailable");
12581
+ if (process.platform !== "win32") {
12582
+ try {
12583
+ fs12.rmSync(this.endpoint, { force: true });
12584
+ } catch {
12954
12585
  }
12955
- this.write({ ...message, id });
12586
+ }
12587
+ const args = [fileURLToPath2(url), "--project-root", this.projectRoot];
12588
+ if (this.indexDir) args.push("--index-dir", this.indexDir);
12589
+ const child = spawn4(process.execPath, args, {
12590
+ detached: true,
12591
+ stdio: "ignore",
12592
+ windowsHide: true,
12593
+ env: process.env
12956
12594
  });
12595
+ child.unref();
12957
12596
  }
12958
- async ensureConnected(spawnIfMissing) {
12959
- if (this.socket && !this.socket.destroyed && this.info) return;
12960
- if (this.connecting) return this.connecting;
12961
- this.transition("connecting");
12962
- this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
12963
- this.transition("error", { error });
12964
- throw error;
12965
- }).finally(() => {
12966
- this.connecting = null;
12597
+ forceKillKnownServer() {
12598
+ const pid = this.info?.pid;
12599
+ return pid ? this.forceKillServer(pid) : false;
12600
+ }
12601
+ forceKillServer(pid) {
12602
+ if (pid === process.pid) return false;
12603
+ try {
12604
+ process.kill(pid);
12605
+ const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
12606
+ try {
12607
+ const metadata = JSON.parse(fs12.readFileSync(metadataPath, "utf8"));
12608
+ if (metadata.pid === pid) fs12.rmSync(metadataPath, { force: true });
12609
+ } catch {
12610
+ }
12611
+ return true;
12612
+ } catch {
12613
+ return false;
12614
+ }
12615
+ }
12616
+ };
12617
+ var connections = /* @__PURE__ */ new Map();
12618
+ var MAX_CACHED_CONNECTIONS = 8;
12619
+ var heartbeatTimer;
12620
+ function forgetConnection(endpoint, connection) {
12621
+ if (connections.get(endpoint) === connection) connections.delete(endpoint);
12622
+ connection.close();
12623
+ connectionStates.delete(endpoint);
12624
+ if (latestConnectionState.endpoint !== endpoint) return;
12625
+ latestConnectionState = [...connectionStates.values()].at(-1) ?? {
12626
+ status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
12627
+ connected: false
12628
+ };
12629
+ }
12630
+ function trimConnectionCache(protectedConnection) {
12631
+ if (connections.size <= MAX_CACHED_CONNECTIONS) return;
12632
+ for (const [endpoint, connection] of connections) {
12633
+ if (connections.size <= MAX_CACHED_CONNECTIONS) break;
12634
+ if (connection === protectedConnection || !connection.isEvictable()) continue;
12635
+ forgetConnection(endpoint, connection);
12636
+ }
12637
+ }
12638
+ function ensureHeartbeatLoop() {
12639
+ if (heartbeatTimer) return;
12640
+ heartbeatTimer = setInterval(() => {
12641
+ for (const connection of connections.values()) {
12642
+ if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
12643
+ });
12644
+ }
12645
+ }, SERVER_HEARTBEAT_INTERVAL_MS);
12646
+ heartbeatTimer.unref?.();
12647
+ }
12648
+ function maybeStopHeartbeatLoop() {
12649
+ if (!heartbeatTimer) return;
12650
+ if ([...connections.values()].some((connection) => connection.isConnected())) return;
12651
+ clearInterval(heartbeatTimer);
12652
+ heartbeatTimer = void 0;
12653
+ }
12654
+ function connectionFor(projectRoot, indexDir) {
12655
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12656
+ let connection = connections.get(endpoint);
12657
+ if (!connection) {
12658
+ connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
12659
+ connections.set(endpoint, connection);
12660
+ } else {
12661
+ connections.delete(endpoint);
12662
+ connections.set(endpoint, connection);
12663
+ }
12664
+ trimConnectionCache(connection);
12665
+ return connection;
12666
+ }
12667
+ function callProjectIndexServer(op, args, options) {
12668
+ return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
12669
+ }
12670
+ function ensureProjectIndexServer(options) {
12671
+ return connectionFor(options.projectRoot, options.indexDir).configure(
12672
+ options.watchExternal,
12673
+ options.debounceMs
12674
+ );
12675
+ }
12676
+ function checkProjectIndexServerHealth(projectRoot, indexDir, options = {}) {
12677
+ return connectionFor(projectRoot, indexDir).checkHealth(
12678
+ false,
12679
+ options.timeoutMs ?? SERVER_HEALTH_TIMEOUT_MS
12680
+ );
12681
+ }
12682
+ async function shutdownProjectIndexServer(projectRoot, indexDir, reason) {
12683
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12684
+ const connection = connectionFor(projectRoot, indexDir);
12685
+ try {
12686
+ return await connection.shutdownRemote(reason);
12687
+ } finally {
12688
+ connection.close();
12689
+ connections.delete(endpoint);
12690
+ connectionStates.delete(endpoint);
12691
+ }
12692
+ }
12693
+ function closeProjectIndexServerClients() {
12694
+ for (const connection of connections.values()) connection.close();
12695
+ connections.clear();
12696
+ connectionStates.clear();
12697
+ latestConnectionState = {
12698
+ status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
12699
+ connected: false
12700
+ };
12701
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
12702
+ heartbeatTimer = void 0;
12703
+ }
12704
+
12705
+ // src/codebase-index/background-indexer.ts
12706
+ import * as fs18 from "node:fs";
12707
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
12708
+ import { Worker } from "node:worker_threads";
12709
+
12710
+ // src/codebase-index/indexer.ts
12711
+ import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
12712
+ import { execFile as execFile2 } from "node:child_process";
12713
+ import * as fs17 from "node:fs/promises";
12714
+ import { availableParallelism } from "node:os";
12715
+ import * as path23 from "node:path";
12716
+ import {
12717
+ DEFAULT_WALK_IGNORE_DIRS,
12718
+ indexParallelBatchSize,
12719
+ isFrugalPerf
12720
+ } from "@wrongstack/core/utils";
12721
+
12722
+ // src/codebase-index/gitignore.ts
12723
+ import * as fs13 from "node:fs/promises";
12724
+ import * as path17 from "node:path";
12725
+ import { compileGlob } from "@wrongstack/core/utils";
12726
+ function globBody(glob) {
12727
+ return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
12728
+ }
12729
+ function compileGitignore(lines) {
12730
+ const rules = [];
12731
+ for (const raw of lines) {
12732
+ let line = raw.replace(/\r$/, "");
12733
+ if (!line.trim() || line.trimStart().startsWith("#")) continue;
12734
+ line = line.trim();
12735
+ let negated = false;
12736
+ if (line.startsWith("!")) {
12737
+ negated = true;
12738
+ line = line.slice(1);
12739
+ }
12740
+ let dirOnly = false;
12741
+ if (line.endsWith("/")) {
12742
+ dirOnly = true;
12743
+ line = line.slice(0, -1);
12744
+ }
12745
+ if (!line) continue;
12746
+ const anchored = line.startsWith("/") || line.includes("/");
12747
+ if (line.startsWith("/")) line = line.slice(1);
12748
+ const body = globBody(line);
12749
+ const prefix = anchored ? "^" : "(?:^|.*/)";
12750
+ rules.push({
12751
+ eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
12752
+ under: new RegExp(`${prefix}${body}/.*$`),
12753
+ negated,
12754
+ dirOnly
12967
12755
  });
12968
- return this.connecting;
12969
12756
  }
12970
- async connectWithElection(spawnIfMissing) {
12971
- const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
12972
- let spawned = false;
12973
- let staleAttempts = 0;
12974
- let lastError = new Error("codebase-index server unavailable");
12975
- while (Date.now() < deadline) {
12976
- try {
12977
- await this.connectOnce();
12978
- return;
12979
- } catch (error) {
12980
- lastError = error;
12981
- if (error instanceof StaleProjectIndexServerError) {
12982
- staleAttempts++;
12983
- if (!spawnIfMissing) break;
12984
- if (staleAttempts >= 3) this.forceKillServer(error.pid);
12985
- spawned = false;
12986
- await delay(100);
12987
- continue;
12988
- }
12989
- }
12990
- if (!spawnIfMissing) break;
12991
- if (!spawned) {
12992
- this.spawnDetachedServer();
12993
- spawned = true;
12994
- }
12995
- await delay(75);
12757
+ return (relPath, isDir) => {
12758
+ const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
12759
+ let ignored = false;
12760
+ for (const r of rules) {
12761
+ const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
12762
+ if (re.test(p)) ignored = !r.negated;
12996
12763
  }
12997
- throw lastError;
12764
+ return ignored;
12765
+ };
12766
+ }
12767
+ async function loadGitignoreMatcher(projectRoot) {
12768
+ let lines = [];
12769
+ try {
12770
+ const raw = await fs13.readFile(path17.join(projectRoot, ".gitignore"), "utf8");
12771
+ lines = raw.split("\n");
12772
+ } catch {
12998
12773
  }
12999
- connectOnce() {
13000
- this.socket?.destroy();
13001
- this.socket = null;
13002
- this.info = null;
13003
- this.activity = null;
13004
- this.health = null;
13005
- this.buffer = "";
13006
- return new Promise((resolve17, reject) => {
13007
- const socket = net3.createConnection(this.endpoint);
13008
- this.socket = socket;
13009
- socket.setEncoding("utf8");
13010
- const timer = setTimeout(() => {
13011
- reject(new Error("codebase-index server handshake timed out"));
13012
- socket.destroy();
13013
- }, CONNECT_ATTEMPT_TIMEOUT_MS);
13014
- timer.unref?.();
13015
- const finishResolve = () => {
13016
- clearTimeout(timer);
13017
- this.connectResolve = null;
13018
- this.connectReject = null;
13019
- resolve17();
13020
- };
13021
- const finishReject = (error) => {
13022
- clearTimeout(timer);
13023
- this.connectResolve = null;
13024
- this.connectReject = null;
13025
- reject(error);
13026
- };
13027
- this.connectResolve = finishResolve;
13028
- this.connectReject = finishReject;
13029
- socket.on("data", (chunk) => this.onData(socket, chunk));
13030
- socket.on("error", (error) => {
13031
- if (!this.info) finishReject(error);
13032
- });
13033
- socket.on("close", () => this.onClose(socket));
13034
- });
12774
+ return compileGitignore(lines);
12775
+ }
12776
+
12777
+ // src/codebase-index/indexer.ts
12778
+ init_languages2();
12779
+
12780
+ // src/codebase-index/parser-dispatch.ts
12781
+ async function parseFileContent(file, content, lang) {
12782
+ switch (lang) {
12783
+ case "ts":
12784
+ case "tsx":
12785
+ case "js":
12786
+ case "jsx": {
12787
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
12788
+ return parseSymbols8({ file, content, lang });
12789
+ }
12790
+ case "go": {
12791
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
12792
+ return parseSymbols8({ file, content, lang: "go" });
12793
+ }
12794
+ case "py": {
12795
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
12796
+ return parseSymbols8({ file, content, lang: "py" });
12797
+ }
12798
+ case "rs": {
12799
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
12800
+ return parseSymbols8({ file, content, lang: "rs" });
12801
+ }
12802
+ case "json": {
12803
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
12804
+ return parseSymbols8({ file, content, lang: "json" });
12805
+ }
12806
+ case "yaml": {
12807
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
12808
+ return parseSymbols8({ file, content, lang: "yaml" });
12809
+ }
12810
+ default: {
12811
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
12812
+ return parseSymbols8({ file, content, lang });
12813
+ }
13035
12814
  }
13036
- onData(socket, chunk) {
13037
- if (socket !== this.socket) return;
13038
- this.buffer += chunk;
13039
- while (true) {
13040
- const newline = this.buffer.indexOf("\n");
13041
- if (newline < 0) {
13042
- if (this.buffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
13043
- socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
13044
- }
13045
- return;
13046
- }
13047
- if (newline > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
13048
- socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
13049
- return;
12815
+ }
12816
+
12817
+ // src/codebase-index/indexer.ts
12818
+ var YIELD_EVERY_N = 50;
12819
+ function resolveParallelBatch() {
12820
+ return indexParallelBatchSize(availableParallelism());
12821
+ }
12822
+ function yieldEventLoop() {
12823
+ return new Promise((resolve17) => setImmediate(resolve17));
12824
+ }
12825
+ function throwIfAborted(signal) {
12826
+ if (!signal?.aborted) return;
12827
+ if (signal.reason instanceof Error) throw signal.reason;
12828
+ throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
12829
+ }
12830
+ function isAbortError(err) {
12831
+ return err instanceof DOMException && err.name === "AbortError";
12832
+ }
12833
+ var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
12834
+ var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
12835
+ var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
12836
+ var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
12837
+ var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
12838
+ function isWithinProject(projectRoot, file) {
12839
+ const rel = path23.relative(projectRoot, file);
12840
+ return rel !== "" && !rel.startsWith(`..${path23.sep}`) && rel !== ".." && !path23.isAbsolute(rel);
12841
+ }
12842
+ function isMissingPathError(err) {
12843
+ const code = err?.code;
12844
+ return code === "ENOENT" || code === "ENOTDIR";
12845
+ }
12846
+ function normalizeComparablePath(value) {
12847
+ const resolved = path23.resolve(value);
12848
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
12849
+ }
12850
+ function gitOutput(projectRoot, args) {
12851
+ return new Promise((resolve17, reject) => {
12852
+ execFile2(
12853
+ "git",
12854
+ ["-C", projectRoot, ...args],
12855
+ {
12856
+ encoding: "buffer",
12857
+ maxBuffer: MAX_GIT_FILE_LIST_BYTES,
12858
+ windowsHide: true
12859
+ },
12860
+ (error, stdout) => {
12861
+ if (error) reject(error);
12862
+ else resolve17(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
13050
12863
  }
13051
- const line = this.buffer.slice(0, newline);
13052
- this.buffer = this.buffer.slice(newline + 1);
13053
- if (!line) continue;
13054
- let message;
13055
- try {
13056
- message = JSON.parse(line);
13057
- } catch {
13058
- socket.destroy(new Error("invalid codebase-index server response"));
13059
- return;
12864
+ );
12865
+ });
12866
+ }
12867
+ async function findGitSourceFiles(projectRoot, ignore, signal) {
12868
+ try {
12869
+ throwIfAborted(signal);
12870
+ const topLevel = (await gitOutput(projectRoot, ["rev-parse", "--show-toplevel"])).toString("utf8").trim();
12871
+ if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
12872
+ throwIfAborted(signal);
12873
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
12874
+ const [output, statusOutput] = await Promise.all([
12875
+ gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
12876
+ gitOutput(projectRoot, [
12877
+ "status",
12878
+ "--porcelain=v1",
12879
+ "-z",
12880
+ "--untracked-files=all",
12881
+ "--ignored=no"
12882
+ ])
12883
+ ]);
12884
+ throwIfAborted(signal);
12885
+ const dirty = /* @__PURE__ */ new Set();
12886
+ const deleted = /* @__PURE__ */ new Set();
12887
+ const statusRecords = statusOutput.toString("utf8").split("\0");
12888
+ for (let i = 0; i < statusRecords.length; i++) {
12889
+ const record = statusRecords[i];
12890
+ if (!record) continue;
12891
+ const status = record.slice(0, 2);
12892
+ const changedPath = path23.resolve(projectRoot, record.slice(3));
12893
+ dirty.add(changedPath);
12894
+ if (status.includes("D")) deleted.add(changedPath);
12895
+ if (status.includes("R") || status.includes("C")) {
12896
+ const source = statusRecords[++i];
12897
+ if (source) dirty.add(path23.resolve(projectRoot, source));
13060
12898
  }
13061
- this.onMessage(message);
13062
12899
  }
13063
- }
13064
- onMessage(message) {
13065
- if (message.type === "hello") {
13066
- if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
13067
- this.rejectStaleServer(
13068
- message,
13069
- `codebase-index protocol mismatch: client=${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
13070
- );
13071
- return;
13072
- }
13073
- const expectedBuildId = projectIndexServerExpectedBuildId();
13074
- if (expectedBuildId && message.buildId !== expectedBuildId) {
13075
- this.rejectStaleServer(
13076
- message,
13077
- `codebase-index build mismatch: client=${expectedBuildId}, server=${message.buildId ?? "legacy"}`
13078
- );
13079
- return;
12900
+ const files = [];
12901
+ for (const relative13 of output.toString("utf8").split("\0")) {
12902
+ if (!relative13) continue;
12903
+ const portable = relative13.replace(/\\/g, "/");
12904
+ if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path23.posix.basename(portable))) {
12905
+ continue;
13080
12906
  }
13081
- this.info = message;
13082
- this.markResponsive();
13083
- this.transition("connected", { pid: message.pid });
13084
- ensureHeartbeatLoop();
13085
- this.connectResolve?.();
13086
- return;
12907
+ const full = path23.resolve(projectRoot, relative13);
12908
+ if (deleted.has(full)) continue;
12909
+ const ext = path23.extname(relative13).toLowerCase();
12910
+ if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
12911
+ }
12912
+ return {
12913
+ files,
12914
+ trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
12915
+ };
12916
+ } catch {
12917
+ return null;
12918
+ }
12919
+ }
12920
+ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
12921
+ const gitFiles = await findGitSourceFiles(projectRoot, ignore, signal);
12922
+ if (gitFiles) {
12923
+ return {
12924
+ files: gitFiles.files,
12925
+ complete: true,
12926
+ errors: [],
12927
+ trustedUnchanged: gitFiles.trustedUnchanged
12928
+ };
12929
+ }
12930
+ const results = [];
12931
+ const errors = [];
12932
+ let complete = true;
12933
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
12934
+ const indexableExts = new Set(INDEXABLE_EXTENSIONS);
12935
+ let dirCount = 0;
12936
+ const walk2 = async (dir) => {
12937
+ throwIfAborted(signal);
12938
+ if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
12939
+ await yieldEventLoop();
12940
+ throwIfAborted(signal);
13087
12941
  }
13088
- if (message.type === "index-state") {
13089
- this.activity = message.state;
13090
- this.markResponsive();
13091
- this.transition("connected", { pid: this.info?.pid });
12942
+ let entries;
12943
+ try {
12944
+ entries = await fs17.readdir(dir, { withFileTypes: true });
12945
+ } catch (err) {
12946
+ complete = false;
12947
+ errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
13092
12948
  return;
13093
12949
  }
13094
- const entry = this.pending.get(message.id);
13095
- if (!entry) return;
13096
- this.markResponsive();
13097
- const status = connectionStates.get(this.endpoint)?.status;
13098
- if (status === "degraded" || status === "unresponsive") {
13099
- this.transition("connected", { pid: this.info?.pid });
12950
+ dirCount++;
12951
+ for (const e of entries) {
12952
+ if (ignoreSet.has(e.name)) continue;
12953
+ const full = path23.join(dir, e.name);
12954
+ const rel = path23.relative(projectRoot, full).replace(/\\/g, "/");
12955
+ if (e.isDirectory()) {
12956
+ if (isGitIgnored(rel, true)) continue;
12957
+ await walk2(full);
12958
+ } else if (e.isFile()) {
12959
+ if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
12960
+ const ext = path23.extname(e.name).toLowerCase();
12961
+ if (indexableExts.has(ext) || detectLang(full) !== null) {
12962
+ results.push(full);
12963
+ }
12964
+ }
13100
12965
  }
13101
- if (message.type === "progress") {
13102
- entry.onProgress?.(message.current, message.total);
13103
- return;
12966
+ };
12967
+ await walk2(projectRoot);
12968
+ return { files: results, complete, errors };
12969
+ }
12970
+ function assignRefsToSymbols2(refs, symbols) {
12971
+ if (refs.length === 0 || symbols.length === 0) return [];
12972
+ const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
12973
+ const seen = /* @__PURE__ */ new Set();
12974
+ const assigned = [];
12975
+ for (const ref of refs) {
12976
+ let owner2;
12977
+ for (const symbol of ordered) {
12978
+ if (symbol.line > ref.line) break;
12979
+ owner2 = symbol;
13104
12980
  }
13105
- this.pending.delete(message.id);
13106
- this.cleanupPending(entry);
13107
- if (message.ok) entry.resolve(message.result);
13108
- else entry.reject(remoteError(message.error, message.errorName));
12981
+ if (!owner2 && ref.callType === "import") owner2 = ordered[0];
12982
+ if (!owner2 || owner2.id <= 0) continue;
12983
+ const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
12984
+ if (seen.has(key)) continue;
12985
+ seen.add(key);
12986
+ assigned.push({ ...ref, fromId: owner2.id });
13109
12987
  }
13110
- onClose(socket) {
13111
- if (socket !== this.socket) return;
13112
- const wasConnected = this.info !== null;
13113
- this.socket = null;
13114
- this.info = null;
13115
- this.activity = null;
13116
- this.health = null;
13117
- const error = new Error("codebase-index server connection closed");
13118
- this.connectReject?.(error);
13119
- this.connectResolve = null;
13120
- this.connectReject = null;
13121
- this.rejectPending(error);
13122
- if (wasConnected) this.transition("error", { error });
13123
- maybeStopHeartbeatLoop();
12988
+ return assigned;
12989
+ }
12990
+ async function runIndexerWithStore(store, opts) {
12991
+ const { projectRoot, langs, ignore = [], signal } = opts;
12992
+ const relationGraphVersion = "2";
12993
+ const refResolutionVersion = "2";
12994
+ const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
12995
+ const needsFullRefResolution = force || store.getMetadata("ref_resolution_version") !== refResolutionVersion;
12996
+ const startMs = Date.now();
12997
+ const errors = [];
12998
+ const langStats = {};
12999
+ let filesIndexed = 0;
13000
+ let symbolsIndexed = 0;
13001
+ const isGitIgnored = await loadGitignoreMatcher(projectRoot);
13002
+ let files;
13003
+ let discoveredFiles = null;
13004
+ let discoveryComplete = true;
13005
+ let trustedUnchanged;
13006
+ if (opts.files && opts.files.length > 0) {
13007
+ files = opts.files.map((f) => path23.resolve(projectRoot, f)).filter((f) => {
13008
+ if (!isWithinProject(projectRoot, f)) return false;
13009
+ const rel = path23.relative(projectRoot, f).replace(/\\/g, "/");
13010
+ return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path23.basename(f)) && !isGitIgnored(rel, false);
13011
+ });
13012
+ } else {
13013
+ const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
13014
+ files = discovery.files;
13015
+ errors.push(...discovery.errors);
13016
+ discoveryComplete = discovery.complete;
13017
+ discoveredFiles = new Set(files);
13018
+ trustedUnchanged = discovery.trustedUnchanged;
13124
13019
  }
13125
- cleanupPending(entry) {
13126
- clearTimeout(entry.timer);
13127
- if (entry.signal && entry.onAbort) {
13128
- entry.signal.removeEventListener("abort", entry.onAbort);
13129
- }
13020
+ if (langs && langs.length > 0) {
13021
+ const langSet = new Set(langs);
13022
+ files = files.filter((f) => {
13023
+ const lang = detectLang(f);
13024
+ return lang ? langSet.has(lang) : false;
13025
+ });
13130
13026
  }
13131
- rejectPending(error) {
13132
- const entries = [...this.pending.values()];
13133
- this.pending.clear();
13134
- for (const entry of entries) {
13135
- this.cleanupPending(entry);
13136
- entry.reject(error);
13137
- }
13027
+ if (force) store.clearAll();
13028
+ const existingMeta = /* @__PURE__ */ new Map();
13029
+ if (!force) {
13030
+ for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
13138
13031
  }
13139
- write(message) {
13140
- const socket = this.socket;
13141
- if (socket && !socket.destroyed) socket.write(encodeProjectServerMessage(message));
13032
+ const totalFilesForProgress = files.length;
13033
+ let filesPreSkipped = 0;
13034
+ if (!force && trustedUnchanged) {
13035
+ files = files.filter((file) => {
13036
+ const meta = existingMeta.get(file);
13037
+ if (!meta || !trustedUnchanged.has(file)) return true;
13038
+ langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
13039
+ symbolsIndexed += meta.symbolCount;
13040
+ filesIndexed++;
13041
+ filesPreSkipped++;
13042
+ return false;
13043
+ });
13044
+ if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
13142
13045
  }
13143
- rejectStaleServer(message, reason) {
13144
- const socket = this.socket;
13145
- if (socket && !socket.destroyed) {
13146
- socket.write(
13147
- encodeProjectServerMessage({
13148
- type: "shutdown",
13149
- id: 0,
13150
- reason: "stale-build-replacement"
13151
- })
13152
- );
13153
- const timer = setTimeout(() => socket.destroy(), 25);
13154
- timer.unref?.();
13046
+ const parallelBatch = resolveParallelBatch();
13047
+ let filesSinceLastYield = 0;
13048
+ for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
13049
+ const batchEnd = Math.min(batchStart + parallelBatch, files.length);
13050
+ const batchFiles = files.slice(batchStart, batchEnd);
13051
+ opts.onProgress?.(filesPreSkipped + batchEnd, totalFilesForProgress);
13052
+ filesSinceLastYield += batchFiles.length;
13053
+ if (filesSinceLastYield >= YIELD_EVERY_N) {
13054
+ filesSinceLastYield = 0;
13055
+ await yieldEventLoop();
13056
+ if (isFrugalPerf()) {
13057
+ await new Promise((r) => setTimeout(r, 8));
13058
+ }
13059
+ throwIfAborted(signal);
13060
+ }
13061
+ const statOpts = signal ? { signal } : {};
13062
+ const statReadParse = await Promise.allSettled(
13063
+ batchFiles.map(
13064
+ async (file) => {
13065
+ let stat18;
13066
+ try {
13067
+ stat18 = await fs17.stat(file, statOpts);
13068
+ } catch (e) {
13069
+ if (isAbortError(e)) throw e;
13070
+ return {
13071
+ file,
13072
+ stat: null,
13073
+ lang: "",
13074
+ parsed: null,
13075
+ error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
13076
+ missing: isMissingPathError(e)
13077
+ };
13078
+ }
13079
+ if (!stat18.isFile()) return { file, stat: stat18, lang: "", parsed: null };
13080
+ const lang = detectLang(file);
13081
+ if (!lang) return { file, stat: stat18, lang: "", parsed: null };
13082
+ if (stat18.size > MAX_INDEX_FILE_BYTES) {
13083
+ return {
13084
+ file,
13085
+ stat: stat18,
13086
+ lang,
13087
+ parsed: null,
13088
+ error: `file too large (${stat18.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
13089
+ };
13090
+ }
13091
+ const meta = existingMeta.get(file);
13092
+ if (!force && meta && meta.mtimeMs === Math.floor(stat18.mtimeMs)) {
13093
+ return { file, stat: stat18, lang, parsed: null, skippedMeta: meta };
13094
+ }
13095
+ let content;
13096
+ try {
13097
+ content = await fs17.readFile(file, { encoding: "utf8", signal });
13098
+ } catch (e) {
13099
+ if (isAbortError(e)) throw e;
13100
+ return {
13101
+ file,
13102
+ stat: stat18,
13103
+ lang,
13104
+ parsed: null,
13105
+ error: `read error: ${e instanceof Error ? e.message : String(e)}`
13106
+ };
13107
+ }
13108
+ let parsed;
13109
+ try {
13110
+ parsed = await parseFileContent(file, content, lang);
13111
+ } catch (e) {
13112
+ return {
13113
+ file,
13114
+ stat: stat18,
13115
+ lang,
13116
+ parsed: null,
13117
+ error: `parse error: ${e instanceof Error ? e.message : String(e)}`
13118
+ };
13119
+ }
13120
+ return { file, stat: stat18, lang, parsed, content };
13121
+ }
13122
+ )
13123
+ );
13124
+ const batchEntries = [];
13125
+ const deleteForFiles = [];
13126
+ for (let fi = 0; fi < statReadParse.length; fi++) {
13127
+ const settled = statReadParse[fi];
13128
+ const file = expectDefined6(batchFiles[fi]);
13129
+ if (settled.status === "rejected") {
13130
+ const err = settled.reason;
13131
+ if (err instanceof Error && isAbortError(err)) throw err;
13132
+ errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
13133
+ continue;
13134
+ }
13135
+ const result = settled.value;
13136
+ if (result.error) {
13137
+ if (result.missing) store.deleteFile(file);
13138
+ errors.push(`${file}: ${result.error}`);
13139
+ continue;
13140
+ }
13141
+ const { stat: stat18, lang, parsed } = result;
13142
+ if (result.skippedMeta) {
13143
+ langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
13144
+ symbolsIndexed += result.skippedMeta.symbolCount;
13145
+ filesIndexed++;
13146
+ continue;
13147
+ }
13148
+ if (!lang || !parsed) {
13149
+ if (lang) {
13150
+ store.upsertFile({
13151
+ file,
13152
+ lang,
13153
+ mtimeMs: Math.floor(stat18.mtimeMs),
13154
+ symbolCount: 0,
13155
+ lastIndexed: Date.now()
13156
+ });
13157
+ filesIndexed++;
13158
+ }
13159
+ continue;
13160
+ }
13161
+ if (parsed.symbols.length === 0) {
13162
+ store.replaceEmptyFile({
13163
+ file,
13164
+ lang,
13165
+ mtimeMs: Math.floor(stat18.mtimeMs),
13166
+ symbolCount: 0,
13167
+ lastIndexed: Date.now()
13168
+ });
13169
+ filesIndexed++;
13170
+ continue;
13171
+ }
13172
+ batchEntries.push({
13173
+ file,
13174
+ lang,
13175
+ symbols: parsed.symbols,
13176
+ refs: parsed.refs ?? [],
13177
+ mtimeMs: Math.floor(stat18.mtimeMs),
13178
+ symbolCount: parsed.symbols.length
13179
+ });
13180
+ deleteForFiles.push(file);
13155
13181
  }
13156
- this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
13157
- }
13158
- spawnDetachedServer() {
13159
- const url = resolveProjectServerUrl();
13160
- if (!url) throw new Error("built codebase-index project server is unavailable");
13161
- if (process.platform !== "win32") {
13182
+ if (batchEntries.length > 0) {
13162
13183
  try {
13163
- fs17.rmSync(this.endpoint, { force: true });
13164
- } catch {
13184
+ store.commitBatch(batchEntries, { deleteForFiles });
13185
+ for (const entry of batchEntries) {
13186
+ const count = entry.symbols.length;
13187
+ symbolsIndexed += count;
13188
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
13189
+ filesIndexed++;
13190
+ }
13191
+ } catch (err) {
13192
+ const message = err instanceof Error ? err.message : String(err);
13193
+ errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
13194
+ for (const entry of batchEntries) {
13195
+ try {
13196
+ store.deleteRefsForFile(entry.file);
13197
+ store.deleteSymbolsForFile(entry.file);
13198
+ const symbolsWithIds = store.insertSymbols(entry.symbols);
13199
+ symbolsIndexed += symbolsWithIds.length;
13200
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
13201
+ filesIndexed++;
13202
+ if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
13203
+ const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
13204
+ if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
13205
+ }
13206
+ store.resolveRefsForNames([
13207
+ ...entry.symbols.map((symbol) => symbol.name),
13208
+ ...entry.refs.map((ref) => ref.toName)
13209
+ ]);
13210
+ store.upsertFile({
13211
+ file: entry.file,
13212
+ lang: entry.lang,
13213
+ mtimeMs: entry.mtimeMs,
13214
+ symbolCount: entry.symbolCount,
13215
+ lastIndexed: Date.now()
13216
+ });
13217
+ } catch (innerErr) {
13218
+ errors.push(
13219
+ `fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
13220
+ );
13221
+ }
13222
+ }
13165
13223
  }
13166
13224
  }
13167
- const args = [fileURLToPath2(url), "--project-root", this.projectRoot];
13168
- if (this.indexDir) args.push("--index-dir", this.indexDir);
13169
- const child = spawn7(process.execPath, args, {
13170
- detached: true,
13171
- stdio: "ignore",
13172
- windowsHide: true,
13173
- env: process.env
13174
- });
13175
- child.unref();
13176
- }
13177
- forceKillKnownServer() {
13178
- const pid = this.info?.pid;
13179
- return pid ? this.forceKillServer(pid) : false;
13180
13225
  }
13181
- forceKillServer(pid) {
13182
- if (pid === process.pid) return false;
13183
- try {
13184
- process.kill(pid);
13185
- const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
13186
- try {
13187
- const metadata = JSON.parse(fs17.readFileSync(metadataPath, "utf8"));
13188
- if (metadata.pid === pid) fs17.rmSync(metadataPath, { force: true });
13189
- } catch {
13226
+ if (discoveredFiles && discoveryComplete) {
13227
+ for (const [file_] of existingMeta) {
13228
+ if (!discoveredFiles.has(file_)) {
13229
+ store.deleteFile(file_);
13190
13230
  }
13191
- return true;
13192
- } catch {
13193
- return false;
13194
13231
  }
13195
13232
  }
13196
- };
13197
- var connections = /* @__PURE__ */ new Map();
13198
- var heartbeatTimer;
13199
- function ensureHeartbeatLoop() {
13200
- if (heartbeatTimer) return;
13201
- heartbeatTimer = setInterval(() => {
13202
- for (const connection of connections.values()) {
13203
- if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
13204
- });
13205
- }
13206
- }, SERVER_HEARTBEAT_INTERVAL_MS);
13207
- heartbeatTimer.unref?.();
13208
- }
13209
- function maybeStopHeartbeatLoop() {
13210
- if (!heartbeatTimer) return;
13211
- if ([...connections.values()].some((connection) => connection.isConnected())) return;
13212
- clearInterval(heartbeatTimer);
13213
- heartbeatTimer = void 0;
13233
+ if (needsFullRefResolution) store.resolveRefs();
13234
+ store.setMetadata("ref_resolution_version", refResolutionVersion);
13235
+ store.setMetadata("relation_graph_version", relationGraphVersion);
13236
+ if (!opts.files || filesIndexed >= 50) store.optimize();
13237
+ store.setLastIndexed(Date.now());
13238
+ if (!opts.files) store.compactIfNeeded();
13239
+ const durationMs = Date.now() - startMs;
13240
+ return {
13241
+ filesIndexed,
13242
+ symbolsIndexed,
13243
+ langStats,
13244
+ durationMs,
13245
+ errors
13246
+ };
13214
13247
  }
13215
- function connectionFor(projectRoot, indexDir) {
13216
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
13217
- let connection = connections.get(endpoint);
13218
- if (!connection) {
13219
- connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
13220
- connections.set(endpoint, connection);
13248
+
13249
+ // src/codebase-index/index-service.ts
13250
+ async function indexService(args, hooks = {}) {
13251
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13252
+ try {
13253
+ return await runIndexerWithStore(store, {
13254
+ projectRoot: args.projectRoot,
13255
+ indexDir: args.indexDir,
13256
+ files: args.files,
13257
+ force: args.force,
13258
+ langs: args.langs,
13259
+ ignore: args.ignore,
13260
+ signal: hooks.signal,
13261
+ onProgress: hooks.onProgress
13262
+ });
13263
+ } finally {
13264
+ indexStorePool.release(store);
13221
13265
  }
13222
- return connection;
13223
13266
  }
13224
- function callProjectIndexServer(op, args, options) {
13225
- return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
13267
+ function searchService(args) {
13268
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13269
+ try {
13270
+ return store.searchRanked(
13271
+ args.query,
13272
+ {
13273
+ kind: args.kind,
13274
+ lang: args.lang,
13275
+ file: args.file,
13276
+ lspKind: args.lspKind
13277
+ },
13278
+ args.limit
13279
+ );
13280
+ } finally {
13281
+ indexStorePool.release(store);
13282
+ }
13226
13283
  }
13227
- function ensureProjectIndexServer(options) {
13228
- return connectionFor(options.projectRoot, options.indexDir).configure(
13229
- options.watchExternal,
13230
- options.debounceMs
13231
- );
13284
+ function statsService(args) {
13285
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13286
+ try {
13287
+ return store.getStats();
13288
+ } finally {
13289
+ indexStorePool.release(store);
13290
+ }
13232
13291
  }
13233
- function checkProjectIndexServerHealth(projectRoot, indexDir, options = {}) {
13234
- return connectionFor(projectRoot, indexDir).checkHealth(
13235
- false,
13236
- options.timeoutMs ?? SERVER_HEALTH_TIMEOUT_MS
13237
- );
13292
+ function packageGraphService(args) {
13293
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13294
+ try {
13295
+ return store.getPackageGraph();
13296
+ } finally {
13297
+ indexStorePool.release(store);
13298
+ }
13238
13299
  }
13239
- async function shutdownProjectIndexServer(projectRoot, indexDir, reason) {
13240
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
13241
- const connection = connectionFor(projectRoot, indexDir);
13300
+ function fileGraphService(args) {
13301
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13242
13302
  try {
13243
- return await connection.shutdownRemote(reason);
13303
+ return store.getFileGraph(args.packageFilter);
13244
13304
  } finally {
13245
- connection.close();
13246
- connections.delete(endpoint);
13247
- connectionStates.delete(endpoint);
13305
+ indexStorePool.release(store);
13248
13306
  }
13249
13307
  }
13250
- function closeProjectIndexServerClients() {
13251
- for (const connection of connections.values()) connection.close();
13252
- connections.clear();
13253
- connectionStates.clear();
13254
- latestConnectionState = {
13255
- status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
13256
- connected: false
13257
- };
13258
- if (heartbeatTimer) clearInterval(heartbeatTimer);
13259
- heartbeatTimer = void 0;
13308
+ function symbolGraphService(args) {
13309
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
13310
+ try {
13311
+ return store.getSymbolGraph(args.fileFilter);
13312
+ } finally {
13313
+ indexStorePool.release(store);
13314
+ }
13260
13315
  }
13261
13316
 
13262
13317
  // src/codebase-index/background-indexer.ts
13318
+ init_languages2();
13263
13319
  var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
13264
13320
  var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
13265
13321
  var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
@@ -13392,8 +13448,17 @@ async function shutdownCodebaseIndexHost() {
13392
13448
  }
13393
13449
  }
13394
13450
  }
13451
+ var warnedInvalidEndpoints = /* @__PURE__ */ new Set();
13452
+ function warnEndpointInvalidOnce(availability) {
13453
+ if (warnedInvalidEndpoints.has(availability.endpoint)) return;
13454
+ warnedInvalidEndpoints.add(availability.endpoint);
13455
+ process.stderr.write(
13456
+ `codebase-index: socket path is ${availability.byteLength} bytes, over this platform's ${availability.maxBytes}-byte sun_path limit (${availability.endpoint}). Subsequent calls will reject until TMPDIR is shortened to restore the shared daemon.
13457
+ `
13458
+ );
13459
+ }
13395
13460
  function callIndexOp(op, args, opts) {
13396
- const availability = resolveProjectIndexDaemonAvailability();
13461
+ const availability = resolveProjectIndexDaemonAvailability(args.projectRoot, args.indexDir);
13397
13462
  if (availability.kind === "available") {
13398
13463
  return callProjectIndexServer(op, args, opts);
13399
13464
  }
@@ -13404,6 +13469,14 @@ function callIndexOp(op, args, opts) {
13404
13469
  )
13405
13470
  );
13406
13471
  }
13472
+ if (availability.kind === "endpoint-invalid") {
13473
+ warnEndpointInvalidOnce(availability);
13474
+ return Promise.reject(
13475
+ new Error(
13476
+ `codebase-index: socket path is ${availability.byteLength} bytes, over this platform's ${availability.maxBytes}-byte sun_path limit (${availability.endpoint}). Set a shorter TMPDIR to relocate the shared daemon, or set WRONGSTACK_INDEX_INLINE=1 to explicitly opt into a process-local index.`
13477
+ )
13478
+ );
13479
+ }
13407
13480
  const w = ensureWorker();
13408
13481
  if (!w) return callInline(op, args, opts);
13409
13482
  if (opts.signal?.aborted) {
@@ -13677,7 +13750,11 @@ function checkCodebaseIndexServerHealth(projectRoot, indexDir, options = {}) {
13677
13750
  return checkProjectIndexServerHealth(projectRoot, indexDir, options);
13678
13751
  }
13679
13752
  function ensureCodebaseIndexServer(options) {
13680
- if (!isProjectIndexServerAvailable()) return Promise.resolve();
13753
+ const availability = resolveProjectIndexDaemonAvailability(options.projectRoot, options.indexDir);
13754
+ if (availability.kind !== "available") {
13755
+ if (availability.kind === "endpoint-invalid") warnEndpointInvalidOnce(availability);
13756
+ return Promise.resolve();
13757
+ }
13681
13758
  return ensureProjectIndexServer({
13682
13759
  projectRoot: options.projectRoot,
13683
13760
  indexDir: options.indexDir,
@@ -26450,6 +26527,7 @@ export {
26450
26527
  resetIndexCircuitBreaker,
26451
26528
  resetPersistentProcessRegistry,
26452
26529
  resolvePinnedBrowserTarget,
26530
+ resolveProjectIndexDaemonAvailability,
26453
26531
  resolveSessionShell,
26454
26532
  runDeadCodeScan,
26455
26533
  runStartupIndex,