@wrongstack/tools 0.296.2 → 0.296.4

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 (38) hide show
  1. package/dist/browser/artifacts.d.ts +2 -0
  2. package/dist/browser/artifacts.d.ts.map +1 -1
  3. package/dist/browser/index.js +41 -0
  4. package/dist/browser/index.js.map +2 -2
  5. package/dist/builtin.js +1812 -1426
  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/dead-code-scan.d.ts +2 -2
  9. package/dist/codebase-index/dead-code-scan.d.ts.map +1 -1
  10. package/dist/codebase-index/index.d.ts +1 -0
  11. package/dist/codebase-index/index.d.ts.map +1 -1
  12. package/dist/codebase-index/index.js +1827 -1542
  13. package/dist/codebase-index/index.js.map +4 -4
  14. package/dist/codebase-index/project-server-client.d.ts +13 -1
  15. package/dist/codebase-index/project-server-client.d.ts.map +1 -1
  16. package/dist/codebase-index/project-server-endpoint.d.ts +29 -1
  17. package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -1
  18. package/dist/codebase-index/project-server.js +226 -152
  19. package/dist/codebase-index/project-server.js.map +4 -4
  20. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  21. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  22. package/dist/codebase-index/worker.js +214 -145
  23. package/dist/codebase-index/worker.js.map +3 -3
  24. package/dist/codebase-index/writer.d.ts +19 -1
  25. package/dist/codebase-index/writer.d.ts.map +1 -1
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1851 -1462
  29. package/dist/index.js.map +4 -4
  30. package/dist/pack.js +1812 -1426
  31. package/dist/pack.js.map +4 -4
  32. package/dist/read.d.ts +31 -0
  33. package/dist/read.d.ts.map +1 -1
  34. package/dist/read.js +5465 -78
  35. package/dist/read.js.map +4 -4
  36. package/dist/tool-tier.js +1812 -1426
  37. package/dist/tool-tier.js.map +4 -4
  38. package/package.json +3 -3
@@ -14,16 +14,16 @@ var __export = (target, all) => {
14
14
  };
15
15
 
16
16
  // src/codebase-index/languages.ts
17
- import * as path2 from "node:path";
17
+ import * as path6 from "node:path";
18
18
  function detectLang(file) {
19
- const base = path2.basename(file);
19
+ const base = path6.basename(file);
20
20
  const lowerBase = base.toLowerCase();
21
21
  if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
22
22
  return "ts";
23
23
  }
24
24
  const special = SPECIAL_FILENAMES[lowerBase];
25
25
  if (special) return special;
26
- const ext = path2.extname(base).toLowerCase();
26
+ const ext = path6.extname(base).toLowerCase();
27
27
  if (!ext) return null;
28
28
  return EXT_TO_LANG[ext] ?? null;
29
29
  }
@@ -255,11 +255,18 @@ async function parseSymbols(opts) {
255
255
  } else if (ts.isHeritageClause(node)) {
256
256
  for (const t of node.types) {
257
257
  const name = getTypeName(t.expression);
258
- if (name) refs.push({ fromId: 0, toName: name, callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement", line: lineNum });
258
+ if (name)
259
+ refs.push({
260
+ fromId: 0,
261
+ toName: name,
262
+ callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
263
+ line: lineNum
264
+ });
259
265
  }
260
266
  } else if (ts.isImportDeclaration(node)) {
261
- const moduleName = getModuleName(node);
262
- if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
267
+ emitImportSpecifierRefs(node, refs, lineNum);
268
+ } else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
269
+ emitExportSpecifierRefs(node, refs, lineNum);
263
270
  }
264
271
  const scopeIdx = scopeParts.length;
265
272
  pushScopeName(node, scopeParts);
@@ -275,11 +282,6 @@ function getTypeName(name) {
275
282
  if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
276
283
  return "";
277
284
  }
278
- function getModuleName(node) {
279
- const moduleSpecifier = node.moduleSpecifier;
280
- if (ts.isStringLiteral(moduleSpecifier)) return moduleSpecifier.text;
281
- return "";
282
- }
283
285
  function deduplicateRefs(refs) {
284
286
  const seen = /* @__PURE__ */ new Set();
285
287
  return refs.filter((r) => {
@@ -289,6 +291,44 @@ function deduplicateRefs(refs) {
289
291
  return true;
290
292
  });
291
293
  }
294
+ function getImportSpecifierName(spec) {
295
+ return spec.propertyName?.text ?? spec.name.text;
296
+ }
297
+ function emitImportSpecifierRefs(node, refs, lineNum) {
298
+ const clause = node.importClause;
299
+ if (!clause) return;
300
+ if (clause.name) {
301
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
302
+ }
303
+ const bindings = clause.namedBindings;
304
+ if (!bindings) return;
305
+ if (ts.isNamedImports(bindings)) {
306
+ for (const element of bindings.elements) {
307
+ refs.push({
308
+ fromId: 0,
309
+ toName: getImportSpecifierName(element),
310
+ callType: "import",
311
+ line: lineNum
312
+ });
313
+ }
314
+ } else if (ts.isNamespaceImport(bindings)) {
315
+ refs.push({ fromId: 0, toName: bindings.name.text, callType: "import", line: lineNum });
316
+ }
317
+ }
318
+ function emitExportSpecifierRefs(node, refs, lineNum) {
319
+ const clause = node.exportClause;
320
+ if (clause && ts.isNamespaceExport(clause)) {
321
+ refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
322
+ return;
323
+ }
324
+ if (clause && ts.isNamedExports(clause)) {
325
+ for (const element of clause.elements) {
326
+ const originalName = element.propertyName?.text ?? element.name.text;
327
+ refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
328
+ }
329
+ return;
330
+ }
331
+ }
292
332
  var ts, tsLoad, kindMapCache;
293
333
  var init_ts_parser = __esm({
294
334
  "src/codebase-index/ts-parser.ts"() {
@@ -300,21 +340,21 @@ var init_ts_parser = __esm({
300
340
  });
301
341
 
302
342
  // src/_win32-resolve.ts
303
- import * as fs2 from "node:fs";
304
- import * as path3 from "node:path";
343
+ import * as fs6 from "node:fs";
344
+ import * as path7 from "node:path";
305
345
  function resolveWin32Command(cmd) {
306
346
  if (process.platform !== "win32") return cmd;
307
- if (cmd.includes("/") || cmd.includes("\\") || path3.extname(cmd.replace(/\//g, "\\"))) {
347
+ if (cmd.includes("/") || cmd.includes("\\") || path7.extname(cmd.replace(/\//g, "\\"))) {
308
348
  return cmd;
309
349
  }
310
350
  const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
311
- const pathDirs = (process.env["PATH"] ?? "").split(path3.delimiter);
351
+ const pathDirs = (process.env["PATH"] ?? "").split(path7.delimiter);
312
352
  for (const dir of pathDirs) {
313
- const base = path3.join(dir, cmd);
353
+ const base = path7.join(dir, cmd);
314
354
  for (const ext of pathext) {
315
355
  const full = `${base}${ext}`;
316
356
  try {
317
- fs2.accessSync(full, fs2.constants.X_OK);
357
+ fs6.accessSync(full, fs6.constants.X_OK);
318
358
  return full;
319
359
  } catch {
320
360
  }
@@ -351,10 +391,10 @@ __export(go_parser_exports, {
351
391
  detectLang: () => detectLang,
352
392
  parseSymbols: () => parseSymbols2
353
393
  });
354
- import { spawn } from "node:child_process";
355
- import * as os from "node:os";
356
- import * as path4 from "node:path";
357
- import * as fs3 from "node:fs/promises";
394
+ import { spawn as spawn2 } from "node:child_process";
395
+ import * as os2 from "node:os";
396
+ import * as path8 from "node:path";
397
+ import * as fs7 from "node:fs/promises";
358
398
  async function parseSymbols2(opts) {
359
399
  const { file, content, lang } = opts;
360
400
  try {
@@ -426,16 +466,16 @@ async function syncGoParse(filePath, content, lang) {
426
466
  try {
427
467
  let scriptPath = _cachedGoScriptPath;
428
468
  if (!scriptPath) {
429
- const tmpDir = await fs3.mkdtemp(path4.join(os.tmpdir(), "ws-go-parse-"));
430
- scriptPath = path4.join(tmpDir, "parse.go");
431
- await fs3.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
469
+ const tmpDir = await fs7.mkdtemp(path8.join(os2.tmpdir(), "ws-go-parse-"));
470
+ scriptPath = path8.join(tmpDir, "parse.go");
471
+ await fs7.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
432
472
  _cachedGoScriptPath = scriptPath;
433
473
  }
434
474
  const goBinary = resolveWin32Command("go");
435
475
  const goResult = await new Promise(
436
476
  (resolve4, reject) => {
437
477
  let settled = false;
438
- const proc = spawn(goBinary, ["run", scriptPath], {
478
+ const proc = spawn2(goBinary, ["run", scriptPath], {
439
479
  stdio: ["pipe", "pipe", "pipe"],
440
480
  windowsHide: true
441
481
  });
@@ -1038,10 +1078,10 @@ __export(py_parser_exports, {
1038
1078
  detectLang: () => detectLang,
1039
1079
  parseSymbols: () => parseSymbols4
1040
1080
  });
1041
- import { spawn as spawn2 } from "node:child_process";
1042
- import * as fs4 from "node:fs/promises";
1043
- import * as os2 from "node:os";
1044
- import * as path5 from "node:path";
1081
+ import { spawn as spawn3 } from "node:child_process";
1082
+ import * as fs8 from "node:fs/promises";
1083
+ import * as os3 from "node:os";
1084
+ import * as path9 from "node:path";
1045
1085
  async function parseSymbols4(opts) {
1046
1086
  const { file, content, lang } = opts;
1047
1087
  try {
@@ -1063,7 +1103,7 @@ async function resolvePython() {
1063
1103
  function commandIsAvailable(command) {
1064
1104
  return new Promise((resolve4) => {
1065
1105
  let settled = false;
1066
- const proc = spawn2(command, ["--version"], {
1106
+ const proc = spawn3(command, ["--version"], {
1067
1107
  stdio: "ignore",
1068
1108
  windowsHide: true
1069
1109
  });
@@ -1085,7 +1125,7 @@ function commandIsAvailable(command) {
1085
1125
  function spawnPyParser(pyBinary, scriptPath, filePath, content) {
1086
1126
  return new Promise((resolve4, reject) => {
1087
1127
  let settled = false;
1088
- const proc = spawn2(pyBinary, [scriptPath, filePath], {
1128
+ const proc = spawn3(pyBinary, [scriptPath, filePath], {
1089
1129
  stdio: ["pipe", "pipe", "pipe"],
1090
1130
  windowsHide: true
1091
1131
  });
@@ -1119,10 +1159,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
1119
1159
  async function syncPyParse(filePath, content, lang) {
1120
1160
  try {
1121
1161
  if (!_cachedScriptPath) {
1122
- const tmpDir = path5.join(os2.tmpdir(), "ws-py-parse");
1123
- await fs4.mkdir(tmpDir, { recursive: true });
1124
- _cachedScriptPath = path5.join(tmpDir, "parse.py");
1125
- await fs4.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
1162
+ const tmpDir = path9.join(os3.tmpdir(), "ws-py-parse");
1163
+ await fs8.mkdir(tmpDir, { recursive: true });
1164
+ _cachedScriptPath = path9.join(tmpDir, "parse.py");
1165
+ await fs8.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
1126
1166
  }
1127
1167
  cachedPyBinary ??= resolvePython();
1128
1168
  const pyBinary = await cachedPyBinary;
@@ -1376,10 +1416,10 @@ __export(rs_parser_exports, {
1376
1416
  detectLang: () => detectLang,
1377
1417
  parseSymbols: () => parseSymbols5
1378
1418
  });
1379
- import { expectDefined } from "@wrongstack/core/utils";
1380
- import { execFile, spawn as spawn3 } from "node:child_process";
1381
- import * as fs5 from "node:fs/promises";
1382
- import * as path6 from "node:path";
1419
+ import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
1420
+ import { execFile, spawn as spawn4 } from "node:child_process";
1421
+ import * as fs9 from "node:fs/promises";
1422
+ import * as path10 from "node:path";
1383
1423
  async function parseSymbols5(opts) {
1384
1424
  const { file, content, lang } = opts;
1385
1425
  const nativeAvailable = await checkNativeParser();
@@ -1401,7 +1441,7 @@ function checkNativeParser() {
1401
1441
  nativeParserAvailability ??= (async () => {
1402
1442
  try {
1403
1443
  await probe("rustc", ["--version"]);
1404
- const toolsDir = path6.join(process.cwd(), "tools");
1444
+ const toolsDir = path10.join(process.cwd(), "tools");
1405
1445
  await probe(
1406
1446
  "cargo",
1407
1447
  [
@@ -1410,7 +1450,7 @@ function checkNativeParser() {
1410
1450
  "--format-version",
1411
1451
  "1",
1412
1452
  "--manifest-path",
1413
- path6.join(toolsDir, "Cargo.toml")
1453
+ path10.join(toolsDir, "Cargo.toml")
1414
1454
  ]
1415
1455
  );
1416
1456
  return true;
@@ -1422,17 +1462,17 @@ function checkNativeParser() {
1422
1462
  }
1423
1463
  async function tryNativeParse(file, content) {
1424
1464
  try {
1425
- const toolsDir = path6.join(process.cwd(), "tools");
1426
- const crateDir = path6.join(toolsDir, "syn-parser");
1427
- const tmpFile = path6.join(crateDir, "src", "input.rs");
1428
- await fs5.writeFile(tmpFile, content, "utf8");
1465
+ const toolsDir = path10.join(process.cwd(), "tools");
1466
+ const crateDir = path10.join(toolsDir, "syn-parser");
1467
+ const tmpFile = path10.join(crateDir, "src", "input.rs");
1468
+ await fs9.writeFile(tmpFile, content, "utf8");
1429
1469
  const cargoBinary = resolveWin32Command("cargo");
1430
1470
  const result = await new Promise(
1431
1471
  (resolve4, reject) => {
1432
1472
  let settled = false;
1433
- const proc = spawn3(
1473
+ const proc = spawn4(
1434
1474
  cargoBinary,
1435
- ["run", "--manifest-path", path6.join(toolsDir, "Cargo.toml")],
1475
+ ["run", "--manifest-path", path10.join(toolsDir, "Cargo.toml")],
1436
1476
  {
1437
1477
  cwd: process.cwd(),
1438
1478
  stdio: ["pipe", "pipe", "pipe"],
@@ -1491,7 +1531,7 @@ function regexParse(opts) {
1491
1531
  let hi = lineOffsets.length - 1;
1492
1532
  while (lo < hi) {
1493
1533
  const mid = lo + hi + 1 >>> 1;
1494
- if (expectDefined(lineOffsets[mid]) <= offset) lo = mid;
1534
+ if (expectDefined2(lineOffsets[mid]) <= offset) lo = mid;
1495
1535
  else hi = mid - 1;
1496
1536
  }
1497
1537
  return lo + 1;
@@ -1503,7 +1543,7 @@ function regexParse(opts) {
1503
1543
  for (const pattern of RS_PATTERNS) {
1504
1544
  pattern.regex.lastIndex = 0;
1505
1545
  for (let match = pattern.regex.exec(content); match !== null; match = pattern.regex.exec(content)) {
1506
- const name = expectDefined(match[1]);
1546
+ const name = expectDefined2(match[1]);
1507
1547
  const offset = match.index ?? 0;
1508
1548
  const line = lineFromOffset(offset);
1509
1549
  const col = offset - (lineOffsets[line - 1] ?? 0);
@@ -1560,8 +1600,8 @@ __export(json_parser_exports, {
1560
1600
  detectLang: () => detectLang,
1561
1601
  parseSymbols: () => parseSymbols6
1562
1602
  });
1563
- import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
1564
- import * as path7 from "node:path";
1603
+ import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
1604
+ import * as path11 from "node:path";
1565
1605
  function parseSymbols6(opts) {
1566
1606
  const { file, content, lang } = opts;
1567
1607
  try {
@@ -1573,7 +1613,7 @@ function parseSymbols6(opts) {
1573
1613
  function regexParse2(opts) {
1574
1614
  const { file, content, lang } = opts;
1575
1615
  const symbols = [];
1576
- const basename5 = path7.basename(file).toLowerCase();
1616
+ const basename5 = path11.basename(file).toLowerCase();
1577
1617
  const isPackageJson = basename5 === "package.json";
1578
1618
  const isTsconfig = basename5 === "tsconfig.json" || basename5 === "tsconfig.build.json";
1579
1619
  const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
@@ -1588,22 +1628,22 @@ function regexParse2(opts) {
1588
1628
  let hi = lineOffsets.length - 1;
1589
1629
  while (lo < hi) {
1590
1630
  const mid = lo + hi + 1 >>> 1;
1591
- if (expectDefined2(lineOffsets[mid]) <= offset) lo = mid;
1631
+ if (expectDefined3(lineOffsets[mid]) <= offset) lo = mid;
1592
1632
  else hi = mid - 1;
1593
1633
  }
1594
1634
  return lo + 1;
1595
1635
  }
1596
1636
  const rootMatch = content.match(/^\s*\{/m);
1597
1637
  if (rootMatch) {
1598
- const offset = expectDefined2(rootMatch.index);
1638
+ const offset = expectDefined3(rootMatch.index);
1599
1639
  const line = lineFromOffset(offset);
1600
1640
  symbols.push(
1601
1641
  makeSymbol({
1602
- name: path7.basename(file),
1642
+ name: path11.basename(file),
1603
1643
  kind: "object",
1604
1644
  line,
1605
1645
  col: 0,
1606
- signature: `"${path7.basename(file)}" = { ... }`,
1646
+ signature: `"${path11.basename(file)}" = { ... }`,
1607
1647
  file,
1608
1648
  lang
1609
1649
  })
@@ -1611,7 +1651,7 @@ function regexParse2(opts) {
1611
1651
  }
1612
1652
  const topLevelKeyRegex = /^\s*"([^"]+)"\s*:/gm;
1613
1653
  for (let match = topLevelKeyRegex.exec(content); match !== null; match = topLevelKeyRegex.exec(content)) {
1614
- const key = expectDefined2(match[1]);
1654
+ const key = expectDefined3(match[1]);
1615
1655
  const offset = match.index ?? 0;
1616
1656
  const line = lineFromOffset(offset);
1617
1657
  const col = offset - (lineOffsets[line - 1] ?? 0);
@@ -1658,7 +1698,7 @@ function regexParse2(opts) {
1658
1698
  const defsRegex = /"\$defs"\s*:|"\$defs"\s*:/g;
1659
1699
  const defsMatch = defsRegex.exec(content);
1660
1700
  if (defsMatch !== null) {
1661
- const offset = expectDefined2(defsMatch.index);
1701
+ const offset = expectDefined3(defsMatch.index);
1662
1702
  const line = lineFromOffset(offset);
1663
1703
  symbols.push(
1664
1704
  makeSymbol({
@@ -1683,7 +1723,7 @@ function regexParse2(opts) {
1683
1723
  for (let match = pat.exec(content); match !== null; match = pat.exec(content)) {
1684
1724
  const offset = match.index ?? 0;
1685
1725
  const line = lineFromOffset(offset);
1686
- const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined2(match[0]);
1726
+ const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined3(match[0]);
1687
1727
  symbols.push(
1688
1728
  makeSymbol({
1689
1729
  name: key,
@@ -1702,12 +1742,12 @@ function regexParse2(opts) {
1702
1742
  function extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFromOffset) {
1703
1743
  const scriptsBlockRegex = /"scripts"\s*:\s*\{([^}]+)\}/g;
1704
1744
  for (let match = scriptsBlockRegex.exec(content); match !== null; match = scriptsBlockRegex.exec(content)) {
1705
- const blockContent = expectDefined2(match[0]);
1745
+ const blockContent = expectDefined3(match[0]);
1706
1746
  const blockOffset = match.index ?? 0;
1707
1747
  const scriptKeyRegex = /"(\w[\w-]*)"\s*:/g;
1708
1748
  for (let scriptMatch = scriptKeyRegex.exec(blockContent); scriptMatch !== null; scriptMatch = scriptKeyRegex.exec(blockContent)) {
1709
- const key = expectDefined2(scriptMatch[1]);
1710
- const keyOffset = blockOffset + expectDefined2(scriptMatch.index);
1749
+ const key = expectDefined3(scriptMatch[1]);
1750
+ const keyOffset = blockOffset + expectDefined3(scriptMatch.index);
1711
1751
  const line = lineFromOffset(keyOffset);
1712
1752
  symbols.push(
1713
1753
  makeSymbol({
@@ -1726,12 +1766,12 @@ function extractPackageScripts(content, symbols, file, lang, lineOffsets, lineFr
1726
1766
  function extractCompilerOptions(content, symbols, file, lang, lineOffsets, parentLine, lineFromOffset) {
1727
1767
  const optsBlockRegex = /"compilerOptions"\s*:\s*\{([^}]+)\}/g;
1728
1768
  for (let match = optsBlockRegex.exec(content); match !== null; match = optsBlockRegex.exec(content)) {
1729
- const blockContent = expectDefined2(match[0]);
1769
+ const blockContent = expectDefined3(match[0]);
1730
1770
  const blockOffset = match.index ?? 0;
1731
1771
  const optKeyRegex = /"(\w[\w]*)"\s*:/g;
1732
1772
  for (let optMatch = optKeyRegex.exec(blockContent); optMatch !== null; optMatch = optKeyRegex.exec(blockContent)) {
1733
- const key = expectDefined2(optMatch[1]);
1734
- const keyOffset = blockOffset + expectDefined2(optMatch.index);
1773
+ const key = expectDefined3(optMatch[1]);
1774
+ const keyOffset = blockOffset + expectDefined3(optMatch.index);
1735
1775
  const line = lineFromOffset(keyOffset);
1736
1776
  if (line <= parentLine) continue;
1737
1777
  symbols.push(
@@ -1776,7 +1816,7 @@ __export(yaml_parser_exports, {
1776
1816
  detectLang: () => detectLang,
1777
1817
  parseSymbols: () => parseSymbols7
1778
1818
  });
1779
- import { expectDefined as expectDefined3, truncate } from "@wrongstack/core/utils";
1819
+ import { expectDefined as expectDefined4, truncate } from "@wrongstack/core/utils";
1780
1820
  function parseSymbols7(opts) {
1781
1821
  const { file, content, lang } = opts;
1782
1822
  try {
@@ -1798,14 +1838,14 @@ function regexParse3(opts) {
1798
1838
  let hi = lineOffsets.length - 1;
1799
1839
  while (lo < hi) {
1800
1840
  const mid = lo + hi + 1 >>> 1;
1801
- if (expectDefined3(lineOffsets[mid]) <= offset) lo = mid;
1841
+ if (expectDefined4(lineOffsets[mid]) <= offset) lo = mid;
1802
1842
  else hi = mid - 1;
1803
1843
  }
1804
1844
  return lo + 1;
1805
1845
  }
1806
1846
  const anchorRegex = /&(\w[\w-]*)/g;
1807
1847
  for (let match = anchorRegex.exec(content); match !== null; match = anchorRegex.exec(content)) {
1808
- const name = expectDefined3(match[1]);
1848
+ const name = expectDefined4(match[1]);
1809
1849
  const offset = match.index ?? 0;
1810
1850
  const line = lineFromOffset(offset);
1811
1851
  const col = offset - (lineOffsets[line - 1] ?? 0);
@@ -1823,7 +1863,7 @@ function regexParse3(opts) {
1823
1863
  }
1824
1864
  const aliasRegex = /\*(\w[\w-]*)/g;
1825
1865
  for (let match = aliasRegex.exec(content); match !== null; match = aliasRegex.exec(content)) {
1826
- const name = expectDefined3(match[1]);
1866
+ const name = expectDefined4(match[1]);
1827
1867
  const offset = match.index ?? 0;
1828
1868
  const line = lineFromOffset(offset);
1829
1869
  const col = offset - (lineOffsets[line - 1] ?? 0);
@@ -1858,7 +1898,7 @@ function regexParse3(opts) {
1858
1898
  }
1859
1899
  const listItemRegex = /^-(\s+)([^:#\s][^:#\s]*)\s*:/gm;
1860
1900
  for (let match = listItemRegex.exec(content); match !== null; match = listItemRegex.exec(content)) {
1861
- const key = expectDefined3(match[2]);
1901
+ const key = expectDefined4(match[2]);
1862
1902
  const offset = match.index ?? 0;
1863
1903
  const line = lineFromOffset(offset);
1864
1904
  const col = offset - (lineOffsets[line - 1] ?? 0);
@@ -1878,7 +1918,7 @@ function regexParse3(opts) {
1878
1918
  }
1879
1919
  const blockScalarRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:\s*[|>](\s|$)/gm;
1880
1920
  for (let match = blockScalarRegex.exec(content); match !== null; match = blockScalarRegex.exec(content)) {
1881
- const key = expectDefined3(match[2]);
1921
+ const key = expectDefined4(match[2]);
1882
1922
  const offset = match.index ?? 0;
1883
1923
  const line = lineFromOffset(offset);
1884
1924
  const col = offset - (lineOffsets[line - 1] ?? 0);
@@ -1930,10 +1970,12 @@ var init_yaml_parser = __esm({
1930
1970
  }
1931
1971
  });
1932
1972
 
1933
- // src/codebase-index/background-indexer.ts
1934
- import * as fs11 from "node:fs";
1935
- import { fileURLToPath as fileURLToPath3 } from "node:url";
1936
- import { Worker } from "node:worker_threads";
1973
+ // src/codebase-index/project-server-client.ts
1974
+ import { spawn } from "node:child_process";
1975
+ import * as fs4 from "node:fs";
1976
+ import * as net from "node:net";
1977
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1978
+ import { checkUnixSocketPath } from "@wrongstack/core/utils";
1937
1979
 
1938
1980
  // src/codebase-index/circuit-breaker.ts
1939
1981
  var CircuitOpenError = class extends Error {
@@ -2018,182 +2060,18 @@ function resetIndexCircuitBreaker() {
2018
2060
  indexCircuitBreaker.reset();
2019
2061
  }
2020
2062
 
2021
- // src/codebase-index/indexer.ts
2022
- import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
2023
- import { execFile as execFile2 } from "node:child_process";
2024
- import * as fs8 from "node:fs/promises";
2025
- import { availableParallelism } from "node:os";
2026
- import * as path11 from "node:path";
2027
- import {
2028
- DEFAULT_WALK_IGNORE_DIRS,
2029
- indexParallelBatchSize,
2030
- isFrugalPerf
2031
- } from "@wrongstack/core/utils";
2032
-
2033
- // src/codebase-index/gitignore.ts
2034
- import * as fs from "node:fs/promises";
2035
- import * as path from "node:path";
2036
- import { compileGlob } from "@wrongstack/core/utils";
2037
- function globBody(glob) {
2038
- return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
2039
- }
2040
- function compileGitignore(lines) {
2041
- const rules = [];
2042
- for (const raw of lines) {
2043
- let line = raw.replace(/\r$/, "");
2044
- if (!line.trim() || line.trimStart().startsWith("#")) continue;
2045
- line = line.trim();
2046
- let negated = false;
2047
- if (line.startsWith("!")) {
2048
- negated = true;
2049
- line = line.slice(1);
2050
- }
2051
- let dirOnly = false;
2052
- if (line.endsWith("/")) {
2053
- dirOnly = true;
2054
- line = line.slice(0, -1);
2055
- }
2056
- if (!line) continue;
2057
- const anchored = line.startsWith("/") || line.includes("/");
2058
- if (line.startsWith("/")) line = line.slice(1);
2059
- const body = globBody(line);
2060
- const prefix = anchored ? "^" : "(?:^|.*/)";
2061
- rules.push({
2062
- eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
2063
- under: new RegExp(`${prefix}${body}/.*$`),
2064
- negated,
2065
- dirOnly
2066
- });
2067
- }
2068
- return (relPath, isDir) => {
2069
- const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
2070
- let ignored = false;
2071
- for (const r of rules) {
2072
- const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
2073
- if (re.test(p)) ignored = !r.negated;
2074
- }
2075
- return ignored;
2076
- };
2077
- }
2078
- async function loadGitignoreMatcher(projectRoot) {
2079
- let lines = [];
2080
- try {
2081
- const raw = await fs.readFile(path.join(projectRoot, ".gitignore"), "utf8");
2082
- lines = raw.split("\n");
2083
- } catch {
2084
- }
2085
- return compileGitignore(lines);
2086
- }
2087
-
2088
- // src/codebase-index/indexer.ts
2089
- init_languages();
2090
-
2091
- // src/codebase-index/parser-dispatch.ts
2092
- async function parseFileContent(file, content, lang) {
2093
- switch (lang) {
2094
- case "ts":
2095
- case "tsx":
2096
- case "js":
2097
- case "jsx": {
2098
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
2099
- return parseSymbols8({ file, content, lang });
2100
- }
2101
- case "go": {
2102
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
2103
- return parseSymbols8({ file, content, lang: "go" });
2104
- }
2105
- case "py": {
2106
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
2107
- return parseSymbols8({ file, content, lang: "py" });
2108
- }
2109
- case "rs": {
2110
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
2111
- return parseSymbols8({ file, content, lang: "rs" });
2112
- }
2113
- case "json": {
2114
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
2115
- return parseSymbols8({ file, content, lang: "json" });
2116
- }
2117
- case "yaml": {
2118
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
2119
- return parseSymbols8({ file, content, lang: "yaml" });
2120
- }
2121
- default: {
2122
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
2123
- return parseSymbols8({ file, content, lang });
2124
- }
2125
- }
2126
- }
2063
+ // src/codebase-index/project-server-endpoint.ts
2064
+ import { createHash } from "node:crypto";
2065
+ import * as fs3 from "node:fs";
2066
+ import * as os from "node:os";
2067
+ import * as path4 from "node:path";
2068
+ import { fileURLToPath } from "node:url";
2069
+ import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
2127
2070
 
2128
2071
  // src/codebase-index/writer.ts
2129
- import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
2130
- import * as fs7 from "node:fs";
2131
- import * as path10 from "node:path";
2132
-
2133
- // src/codebase-index/schema.ts
2134
- var SCHEMA_VERSION = 3;
2135
-
2136
- // src/codebase-index/lsp-kind.ts
2137
- function lspKindToInternalKind(k) {
2138
- switch (k) {
2139
- case 5 /* Class */:
2140
- return "class";
2141
- case 6 /* Method */:
2142
- return "method";
2143
- case 7 /* Property */:
2144
- case 8 /* Field */:
2145
- return "property";
2146
- case 9 /* Constructor */:
2147
- return "class";
2148
- case 10 /* Enum */:
2149
- return "enum";
2150
- case 11 /* Interface */:
2151
- return "interface";
2152
- case 12 /* Function */:
2153
- return "function";
2154
- case 13 /* Variable */:
2155
- return "var";
2156
- case 14 /* Constant */:
2157
- return "const";
2158
- case 22 /* EnumMember */:
2159
- return "enum";
2160
- case 26 /* TypeParameter */:
2161
- return "type";
2162
- case 3 /* Namespace */:
2163
- return "namespace";
2164
- default:
2165
- return null;
2166
- }
2167
- }
2168
- function internalKindToLspKind(k) {
2169
- switch (k) {
2170
- case "class":
2171
- return 5 /* Class */;
2172
- case "method":
2173
- return 6 /* Method */;
2174
- case "property":
2175
- return 7 /* Property */;
2176
- case "function":
2177
- return 12 /* Function */;
2178
- case "var":
2179
- return 13 /* Variable */;
2180
- case "const":
2181
- return 14 /* Constant */;
2182
- case "let":
2183
- return 13 /* Variable */;
2184
- case "enum":
2185
- return 10 /* Enum */;
2186
- case "interface":
2187
- return 11 /* Interface */;
2188
- case "namespace":
2189
- return 3 /* Namespace */;
2190
- case "type":
2191
- return 26 /* TypeParameter */;
2192
- // parameter and other internal-only kinds have no LSP equivalent
2193
- default:
2194
- return null;
2195
- }
2196
- }
2072
+ import { expectDefined } from "@wrongstack/core/utils";
2073
+ import * as fs2 from "node:fs";
2074
+ import * as path3 from "node:path";
2197
2075
 
2198
2076
  // src/codebase-index/bm25.ts
2199
2077
  var K1 = 1.5;
@@ -2284,33 +2162,98 @@ var Bm25Index = class {
2284
2162
  }
2285
2163
  };
2286
2164
 
2287
- // src/codebase-index/sqlite-runtime.ts
2288
- import { createRequire } from "node:module";
2289
- import { toErrorMessage } from "@wrongstack/core/utils";
2290
- var warningSilenced = false;
2291
- function silenceSqliteExperimentalWarning() {
2292
- if (warningSilenced) return;
2293
- warningSilenced = true;
2294
- const original = process.emitWarning.bind(process);
2295
- process.emitWarning = ((warning, ...rest) => {
2296
- const msg = typeof warning === "string" ? warning : warning?.message ?? "";
2297
- const name = typeof warning === "string" ? String(rest[0] ?? "") : warning?.name ?? "";
2298
- if (/sqlite/i.test(msg) && /experimental/i.test(`${name} ${msg}`)) return;
2299
- original(warning, ...rest);
2300
- });
2301
- }
2302
- var DatabaseSyncCtor;
2303
- function loadDatabaseSync() {
2304
- if (DatabaseSyncCtor) return DatabaseSyncCtor;
2305
- silenceSqliteExperimentalWarning();
2306
- try {
2307
- const req = createRequire(import.meta.url);
2308
- DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
2309
- } catch (err) {
2310
- throw new Error(
2311
- `The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage(err)}`
2312
- );
2313
- }
2165
+ // src/codebase-index/lsp-kind.ts
2166
+ function lspKindToInternalKind(k) {
2167
+ switch (k) {
2168
+ case 5 /* Class */:
2169
+ return "class";
2170
+ case 6 /* Method */:
2171
+ return "method";
2172
+ case 7 /* Property */:
2173
+ case 8 /* Field */:
2174
+ return "property";
2175
+ case 9 /* Constructor */:
2176
+ return "class";
2177
+ case 10 /* Enum */:
2178
+ return "enum";
2179
+ case 11 /* Interface */:
2180
+ return "interface";
2181
+ case 12 /* Function */:
2182
+ return "function";
2183
+ case 13 /* Variable */:
2184
+ return "var";
2185
+ case 14 /* Constant */:
2186
+ return "const";
2187
+ case 22 /* EnumMember */:
2188
+ return "enum";
2189
+ case 26 /* TypeParameter */:
2190
+ return "type";
2191
+ case 3 /* Namespace */:
2192
+ return "namespace";
2193
+ default:
2194
+ return null;
2195
+ }
2196
+ }
2197
+ function internalKindToLspKind(k) {
2198
+ switch (k) {
2199
+ case "class":
2200
+ return 5 /* Class */;
2201
+ case "method":
2202
+ return 6 /* Method */;
2203
+ case "property":
2204
+ return 7 /* Property */;
2205
+ case "function":
2206
+ return 12 /* Function */;
2207
+ case "var":
2208
+ return 13 /* Variable */;
2209
+ case "const":
2210
+ return 14 /* Constant */;
2211
+ case "let":
2212
+ return 13 /* Variable */;
2213
+ case "enum":
2214
+ return 10 /* Enum */;
2215
+ case "interface":
2216
+ return 11 /* Interface */;
2217
+ case "namespace":
2218
+ return 3 /* Namespace */;
2219
+ case "type":
2220
+ return 26 /* TypeParameter */;
2221
+ // parameter and other internal-only kinds have no LSP equivalent
2222
+ default:
2223
+ return null;
2224
+ }
2225
+ }
2226
+
2227
+ // src/codebase-index/schema.ts
2228
+ var SCHEMA_VERSION = 3;
2229
+
2230
+ // src/codebase-index/sqlite-runtime.ts
2231
+ import { createRequire } from "node:module";
2232
+ import { toErrorMessage } from "@wrongstack/core/utils";
2233
+ var warningSilenced = false;
2234
+ function silenceSqliteExperimentalWarning() {
2235
+ if (warningSilenced) return;
2236
+ warningSilenced = true;
2237
+ const original = process.emitWarning.bind(process);
2238
+ process.emitWarning = ((warning, ...rest) => {
2239
+ const msg = typeof warning === "string" ? warning : warning?.message ?? "";
2240
+ const name = typeof warning === "string" ? String(rest[0] ?? "") : warning?.name ?? "";
2241
+ if (/sqlite/i.test(msg) && /experimental/i.test(`${name} ${msg}`)) return;
2242
+ original(warning, ...rest);
2243
+ });
2244
+ }
2245
+ var DatabaseSyncCtor;
2246
+ function loadDatabaseSync() {
2247
+ if (DatabaseSyncCtor) return DatabaseSyncCtor;
2248
+ silenceSqliteExperimentalWarning();
2249
+ try {
2250
+ const req = createRequire(import.meta.url);
2251
+ DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
2252
+ } catch (err) {
2253
+ throw new Error(
2254
+ `The codebase index needs Node's built-in SQLite (node:sqlite), available since Node 22.5. This runtime doesn't provide it: ${toErrorMessage(err)}`
2255
+ );
2256
+ }
2314
2257
  return DatabaseSyncCtor;
2315
2258
  }
2316
2259
  var MAX_LOCK_RETRIES = 3;
@@ -2351,99 +2294,9 @@ function runSqliteWithRetry(fn) {
2351
2294
  throw lastError;
2352
2295
  }
2353
2296
 
2354
- // src/codebase-index/writer-helpers.ts
2355
- import { resolveWstackPaths } from "@wrongstack/core/utils";
2356
- function escapeLike(value) {
2357
- return value.replace(/[\\%_]/g, (char) => `\\${char}`);
2358
- }
2359
- function assignRefsToSymbols(refs, symbols) {
2360
- if (refs.length === 0 || symbols.length === 0) return [];
2361
- const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
2362
- const seen = /* @__PURE__ */ new Set();
2363
- const assigned = [];
2364
- for (const ref of refs) {
2365
- let owner;
2366
- for (const symbol of ordered) {
2367
- if (symbol.line > ref.line) break;
2368
- owner = symbol;
2369
- }
2370
- if (!owner && ref.callType === "import") owner = ordered[0];
2371
- if (!owner || owner.id <= 0) continue;
2372
- const key = `${owner.id}:${ref.toName}:${ref.callType}`;
2373
- if (seen.has(key)) continue;
2374
- seen.add(key);
2375
- assigned.push({ ...ref, fromId: owner.id });
2376
- }
2377
- return assigned;
2378
- }
2379
- function resolveIndexDir(projectRoot, override) {
2380
- return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
2381
- }
2382
- function codebaseIndexDirOverride(ctx) {
2383
- const v = ctx.meta?.["codebaseIndexDir"];
2384
- return typeof v === "string" ? v : void 0;
2385
- }
2386
-
2387
- // src/codebase-index/writer-schema.ts
2388
- var METADATA_TABLE_SQL = `
2389
- CREATE TABLE IF NOT EXISTS metadata (
2390
- key TEXT PRIMARY KEY,
2391
- value TEXT NOT NULL
2392
- );
2393
- `;
2394
- var CORE_TABLES_SQL = `
2395
- CREATE TABLE IF NOT EXISTS files (
2396
- file TEXT PRIMARY KEY,
2397
- lang TEXT NOT NULL,
2398
- mtime_ms INTEGER NOT NULL,
2399
- symbol_count INTEGER NOT NULL DEFAULT 0,
2400
- last_indexed INTEGER NOT NULL
2401
- );
2402
- CREATE TABLE IF NOT EXISTS symbols (
2403
- id INTEGER PRIMARY KEY,
2404
- lang TEXT NOT NULL,
2405
- kind TEXT NOT NULL,
2406
- name TEXT NOT NULL,
2407
- file TEXT NOT NULL,
2408
- line INTEGER NOT NULL,
2409
- col INTEGER NOT NULL,
2410
- signature TEXT NOT NULL DEFAULT '',
2411
- doc_comment TEXT NOT NULL DEFAULT '',
2412
- scope TEXT NOT NULL DEFAULT '',
2413
- text TEXT NOT NULL DEFAULT '',
2414
- file_fk TEXT NOT NULL
2415
- );
2416
- `;
2417
- var SYMBOL_INDEX_SQL = [
2418
- "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
2419
- "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
2420
- "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
2421
- "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
2422
- "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
2423
- "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
2424
- "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
2425
- ];
2426
- var REFS_TABLE_SQL = `
2427
- CREATE TABLE IF NOT EXISTS refs (
2428
- id INTEGER PRIMARY KEY,
2429
- from_id INTEGER NOT NULL,
2430
- to_name TEXT NOT NULL,
2431
- to_id INTEGER,
2432
- call_type TEXT NOT NULL,
2433
- line INTEGER NOT NULL
2434
- );
2435
- `;
2436
- var REFS_INDEX_SQL = [
2437
- "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
2438
- "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
2439
- "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
2440
- "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
2441
- ];
2442
- var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
2443
-
2444
2297
  // src/codebase-index/writer-admin.ts
2445
- import * as fs6 from "node:fs";
2446
- import * as path8 from "node:path";
2298
+ import * as fs from "node:fs";
2299
+ import * as path from "node:path";
2447
2300
  var DB_FILE = "index.db";
2448
2301
  function getAllIndexableWithStatement(stmt) {
2449
2302
  return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
@@ -2502,7 +2355,7 @@ function getAllFileMetasWithStatement(stmt) {
2502
2355
  }
2503
2356
  function getIndexDbSizeBytes(indexDir) {
2504
2357
  try {
2505
- return fs6.statSync(path8.join(indexDir, DB_FILE)).size;
2358
+ return fs.statSync(path.join(indexDir, DB_FILE)).size;
2506
2359
  } catch {
2507
2360
  return 0;
2508
2361
  }
@@ -2569,7 +2422,7 @@ function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
2569
2422
  }
2570
2423
 
2571
2424
  // src/codebase-index/writer-graph-helpers.ts
2572
- import * as path9 from "node:path";
2425
+ import * as path2 from "node:path";
2573
2426
  function derivePackage(filePath) {
2574
2427
  const f = filePath.replace(/\\/g, "/");
2575
2428
  const pkgsIdx = f.indexOf("/packages/");
@@ -2684,16 +2537,16 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
2684
2537
  function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
2685
2538
  if (!moduleName.startsWith(".")) return void 0;
2686
2539
  const normalizedFrom = fromFile.replace(/\\/g, "/");
2687
- const absolute = path9.posix.normalize(
2688
- path9.posix.join(path9.posix.dirname(normalizedFrom), moduleName)
2540
+ const absolute = path2.posix.normalize(
2541
+ path2.posix.join(path2.posix.dirname(normalizedFrom), moduleName)
2689
2542
  );
2690
- const extension = path9.posix.extname(absolute);
2543
+ const extension = path2.posix.extname(absolute);
2691
2544
  const base = extension ? absolute.slice(0, -extension.length) : absolute;
2692
2545
  const candidates = [
2693
2546
  absolute,
2694
2547
  ...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
2695
- ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path9.posix.join(absolute, `index${ext}`)),
2696
- ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path9.posix.join(base, `index${ext}`))
2548
+ ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path2.posix.join(absolute, `index${ext}`)),
2549
+ ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path2.posix.join(base, `index${ext}`))
2697
2550
  ];
2698
2551
  const indexedByPortablePath = new Map(
2699
2552
  [...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
@@ -2913,6 +2766,39 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
2913
2766
  return { nodes, edges };
2914
2767
  }
2915
2768
 
2769
+ // src/codebase-index/writer-helpers.ts
2770
+ import { resolveWstackPaths } from "@wrongstack/core/utils";
2771
+ function escapeLike(value) {
2772
+ return value.replace(/[\\%_]/g, (char) => `\\${char}`);
2773
+ }
2774
+ function assignRefsToSymbols(refs, symbols) {
2775
+ if (refs.length === 0 || symbols.length === 0) return [];
2776
+ const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
2777
+ const seen = /* @__PURE__ */ new Set();
2778
+ const assigned = [];
2779
+ for (const ref of refs) {
2780
+ let owner;
2781
+ for (const symbol of ordered) {
2782
+ if (symbol.line > ref.line) break;
2783
+ owner = symbol;
2784
+ }
2785
+ if (!owner && ref.callType === "import") owner = ordered[0];
2786
+ if (!owner || owner.id <= 0) continue;
2787
+ const key = `${owner.id}:${ref.toName}:${ref.callType}`;
2788
+ if (seen.has(key)) continue;
2789
+ seen.add(key);
2790
+ assigned.push({ ...ref, fromId: owner.id });
2791
+ }
2792
+ return assigned;
2793
+ }
2794
+ function resolveIndexDir(projectRoot, override) {
2795
+ return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
2796
+ }
2797
+ function codebaseIndexDirOverride(ctx) {
2798
+ const v = ctx.meta?.["codebaseIndexDir"];
2799
+ return typeof v === "string" ? v : void 0;
2800
+ }
2801
+
2916
2802
  // src/codebase-index/writer-pragmas.ts
2917
2803
  import { sqliteCachePragmas } from "@wrongstack/core/utils";
2918
2804
  function applyIndexStorePragmas(db) {
@@ -2931,42 +2817,99 @@ function applyIndexStorePragmas(db) {
2931
2817
  }
2932
2818
  }
2933
2819
 
2934
- // src/codebase-index/writer-search-helpers.ts
2935
- function normalizeSearchLimit(limit) {
2936
- return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
2937
- }
2938
- function buildWriterSearchWhere(query, filter) {
2939
- const conditions = [];
2940
- const values = [];
2941
- let effectiveKind = filter?.kind;
2942
- if (filter?.lspKind !== void 0) {
2943
- const mapped = lspKindToInternalKind(filter.lspKind);
2944
- if (mapped !== null) {
2945
- effectiveKind = mapped;
2946
- } else {
2947
- return null;
2948
- }
2949
- }
2950
- if (effectiveKind) {
2951
- conditions.push("kind = ?");
2952
- values.push(effectiveKind);
2953
- }
2954
- if (filter?.lang) {
2955
- conditions.push("lang = ?");
2956
- values.push(filter.lang);
2957
- }
2958
- if (filter?.file) {
2959
- conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
2960
- values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
2961
- }
2962
- if (query.trim()) {
2963
- const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
2964
- conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
2965
- for (const token of tokens) values.push(`%${token}%`);
2966
- }
2967
- return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
2968
- }
2969
- function mapWriterSearchRow(row, lspKind, score = 0, snippet = "") {
2820
+ // src/codebase-index/writer-schema.ts
2821
+ var METADATA_TABLE_SQL = `
2822
+ CREATE TABLE IF NOT EXISTS metadata (
2823
+ key TEXT PRIMARY KEY,
2824
+ value TEXT NOT NULL
2825
+ );
2826
+ `;
2827
+ var CORE_TABLES_SQL = `
2828
+ CREATE TABLE IF NOT EXISTS files (
2829
+ file TEXT PRIMARY KEY,
2830
+ lang TEXT NOT NULL,
2831
+ mtime_ms INTEGER NOT NULL,
2832
+ symbol_count INTEGER NOT NULL DEFAULT 0,
2833
+ last_indexed INTEGER NOT NULL
2834
+ );
2835
+ CREATE TABLE IF NOT EXISTS symbols (
2836
+ id INTEGER PRIMARY KEY,
2837
+ lang TEXT NOT NULL,
2838
+ kind TEXT NOT NULL,
2839
+ name TEXT NOT NULL,
2840
+ file TEXT NOT NULL,
2841
+ line INTEGER NOT NULL,
2842
+ col INTEGER NOT NULL,
2843
+ signature TEXT NOT NULL DEFAULT '',
2844
+ doc_comment TEXT NOT NULL DEFAULT '',
2845
+ scope TEXT NOT NULL DEFAULT '',
2846
+ text TEXT NOT NULL DEFAULT '',
2847
+ file_fk TEXT NOT NULL
2848
+ );
2849
+ `;
2850
+ var SYMBOL_INDEX_SQL = [
2851
+ "CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
2852
+ "CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
2853
+ "CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
2854
+ "CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
2855
+ "CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
2856
+ "CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
2857
+ "CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
2858
+ ];
2859
+ var REFS_TABLE_SQL = `
2860
+ CREATE TABLE IF NOT EXISTS refs (
2861
+ id INTEGER PRIMARY KEY,
2862
+ from_id INTEGER NOT NULL,
2863
+ to_name TEXT NOT NULL,
2864
+ to_id INTEGER,
2865
+ call_type TEXT NOT NULL,
2866
+ line INTEGER NOT NULL
2867
+ );
2868
+ `;
2869
+ var REFS_INDEX_SQL = [
2870
+ "CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
2871
+ "CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
2872
+ "CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
2873
+ "CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
2874
+ ];
2875
+ var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
2876
+
2877
+ // src/codebase-index/writer-search-helpers.ts
2878
+ function normalizeSearchLimit(limit) {
2879
+ return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
2880
+ }
2881
+ function buildWriterSearchWhere(query, filter) {
2882
+ const conditions = [];
2883
+ const values = [];
2884
+ let effectiveKind = filter?.kind;
2885
+ if (filter?.lspKind !== void 0) {
2886
+ const mapped = lspKindToInternalKind(filter.lspKind);
2887
+ if (mapped !== null) {
2888
+ effectiveKind = mapped;
2889
+ } else {
2890
+ return null;
2891
+ }
2892
+ }
2893
+ if (effectiveKind) {
2894
+ conditions.push("kind = ?");
2895
+ values.push(effectiveKind);
2896
+ }
2897
+ if (filter?.lang) {
2898
+ conditions.push("lang = ?");
2899
+ values.push(filter.lang);
2900
+ }
2901
+ if (filter?.file) {
2902
+ conditions.push("replace(file, '\\', '/') LIKE ? ESCAPE '\\'");
2903
+ values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
2904
+ }
2905
+ if (query.trim()) {
2906
+ const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
2907
+ conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
2908
+ for (const token of tokens) values.push(`%${token}%`);
2909
+ }
2910
+ return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
2911
+ }
2912
+ function mapWriterSearchRow(row, lspKind, score = 0, snippet = "") {
2970
2913
  return {
2971
2914
  id: row.id,
2972
2915
  lang: row.lang,
@@ -3119,9 +3062,9 @@ var IndexStore = class _IndexStore {
3119
3062
  }
3120
3063
  constructor(projectRoot, opts = {}) {
3121
3064
  this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
3122
- fs7.mkdirSync(this.indexDir, { recursive: true });
3065
+ fs2.mkdirSync(this.indexDir, { recursive: true });
3123
3066
  const Database = loadDatabaseSync();
3124
- this.db = new Database(path10.join(this.indexDir, DB_FILE2));
3067
+ this.db = new Database(path3.join(this.indexDir, DB_FILE2));
3125
3068
  applyIndexStorePragmas(this.db);
3126
3069
  this.initSchema();
3127
3070
  }
@@ -3139,9 +3082,15 @@ var IndexStore = class _IndexStore {
3139
3082
  DROP TABLE IF EXISTS refs;
3140
3083
  `);
3141
3084
  this.db.exec("DROP TABLE IF EXISTS symbols_fts");
3142
- this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
3085
+ this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
3086
+ String(SCHEMA_VERSION),
3087
+ "version"
3088
+ );
3143
3089
  } else if (storedVersion === null) {
3144
- this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
3090
+ this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run(
3091
+ "version",
3092
+ String(SCHEMA_VERSION)
3093
+ );
3145
3094
  }
3146
3095
  this.db.exec(CORE_TABLES_SQL);
3147
3096
  for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
@@ -3158,7 +3107,9 @@ var IndexStore = class _IndexStore {
3158
3107
  );
3159
3108
  if (symbolCount !== ftsCount) {
3160
3109
  this.db.exec("DELETE FROM symbols_fts");
3161
- const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
3110
+ const rows = this.stmt(
3111
+ "SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
3112
+ ).all();
3162
3113
  bulkInsertFtsWithStatement(
3163
3114
  (sql) => this.stmt(sql),
3164
3115
  _IndexStore.MAX_SQL_VARS,
@@ -3380,7 +3331,9 @@ var IndexStore = class _IndexStore {
3380
3331
  const limitSql = limit !== void 0 ? " LIMIT ?" : "";
3381
3332
  const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
3382
3333
  const binds = limit !== void 0 ? [...values, limit] : values;
3383
- const rows = this.stmt(sql).all(...binds);
3334
+ const rows = this.stmt(sql).all(
3335
+ ...binds
3336
+ );
3384
3337
  return rows.map((row) => mapWriterSearchRow(row, filter?.lspKind));
3385
3338
  }
3386
3339
  /** Shared WHERE builder for {@link search} / empty-query ranked totals. */
@@ -3526,13 +3479,13 @@ var IndexStore = class _IndexStore {
3526
3479
  if (rankDiff !== 0) return rankDiff;
3527
3480
  const scoreDiff = b.score - a.score;
3528
3481
  if (scoreDiff !== 0) return scoreDiff;
3529
- const left = expectDefined4(candidateById.get(a.id));
3530
- const right = expectDefined4(candidateById.get(b.id));
3482
+ const left = expectDefined(candidateById.get(a.id));
3483
+ const right = expectDefined(candidateById.get(b.id));
3531
3484
  return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
3532
3485
  });
3533
3486
  const qTokens = tokenise(query);
3534
3487
  const results = scored.slice(0, limit).map(({ id, score }) => {
3535
- const c = expectDefined4(candidateById.get(id));
3488
+ const c = expectDefined(candidateById.get(id));
3536
3489
  return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
3537
3490
  });
3538
3491
  return { results, total: candidates.length };
@@ -3556,7 +3509,9 @@ var IndexStore = class _IndexStore {
3556
3509
  }
3557
3510
  setLastIndexed(ts2) {
3558
3511
  this.runWithRetry(() => {
3559
- this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
3512
+ this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
3513
+ String(ts2)
3514
+ );
3560
3515
  });
3561
3516
  }
3562
3517
  getMetadata(key) {
@@ -3667,7 +3622,9 @@ var IndexStore = class _IndexStore {
3667
3622
  this.stmt(
3668
3623
  `DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
3669
3624
  ).run(...options.deleteForFiles);
3670
- this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
3625
+ this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(
3626
+ ...options.deleteForFiles
3627
+ );
3671
3628
  }
3672
3629
  const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
3673
3630
  let nextId = this.allocateSymbolIds(totalSymbols);
@@ -3711,11 +3668,7 @@ var IndexStore = class _IndexStore {
3711
3668
  this.ftsAvailable,
3712
3669
  ftsRows
3713
3670
  );
3714
- bulkInsertRefsWithStatement(
3715
- (sql) => this.stmt(sql),
3716
- _IndexStore.MAX_SQL_VARS,
3717
- refsToInsert
3718
- );
3671
+ bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
3719
3672
  const upsertStmt = this.stmt(
3720
3673
  `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
3721
3674
  VALUES (?, ?, ?, ?, ?)
@@ -3744,7 +3697,9 @@ var IndexStore = class _IndexStore {
3744
3697
  */
3745
3698
  deleteRefsForFile(file) {
3746
3699
  this.runWithRetry(() => {
3747
- this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
3700
+ this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
3701
+ file
3702
+ );
3748
3703
  });
3749
3704
  }
3750
3705
  /**
@@ -3899,9 +3854,7 @@ var IndexStore = class _IndexStore {
3899
3854
  * build the full symbol universe for the reachability scan.
3900
3855
  */
3901
3856
  getAllSymbols() {
3902
- return this.stmt(
3903
- "SELECT id, name, file, kind, line FROM symbols ORDER BY id"
3904
- ).all().map((r) => ({ ...r, kind: r.kind }));
3857
+ return this.stmt("SELECT id, name, file, kind, line FROM symbols ORDER BY id").all().map((r) => ({ ...r, kind: r.kind }));
3905
3858
  }
3906
3859
  /**
3907
3860
  * Returns every resolved reference (to_id IS NOT NULL). Used by
@@ -3913,6 +3866,25 @@ var IndexStore = class _IndexStore {
3913
3866
  "SELECT from_id AS fromId, to_id AS toId, call_type AS callType FROM refs WHERE to_id IS NOT NULL"
3914
3867
  ).all();
3915
3868
  }
3869
+ /**
3870
+ * Returns ALL import refs (including unresolved) with their source-file
3871
+ * path and resolved target id. Used by the dead-code scan's file-level
3872
+ * graph traversal to handle barrel-only entry points where no symbol
3873
+ * carries the ref.
3874
+ *
3875
+ * Refs whose `from_id` doesn't match a known symbol (e.g. pure-barrel
3876
+ * files with no declarations) will have `sourceFile === null`.
3877
+ */
3878
+ getAllImportRefs() {
3879
+ return this.stmt(
3880
+ `SELECT s.file AS sourceFile, r.to_name AS toName, r.to_id AS toId,
3881
+ r.call_type AS callType, r.line
3882
+ FROM refs r
3883
+ LEFT JOIN symbols s ON r.from_id = s.id
3884
+ WHERE r.call_type = 'import'
3885
+ ORDER BY r.line`
3886
+ ).all();
3887
+ }
3916
3888
  close() {
3917
3889
  this.stmtCache.clear();
3918
3890
  this.bm25Dirty = true;
@@ -3927,1185 +3899,1296 @@ var indexStorePool = new StorePool(
3927
3899
  (projectRoot, opts) => new IndexStore(projectRoot, opts)
3928
3900
  );
3929
3901
 
3930
- // src/codebase-index/indexer.ts
3931
- var YIELD_EVERY_N = 50;
3932
- function resolveParallelBatch() {
3933
- return indexParallelBatchSize(availableParallelism());
3934
- }
3935
- function yieldEventLoop() {
3936
- return new Promise((resolve4) => setImmediate(resolve4));
3937
- }
3938
- function throwIfAborted(signal) {
3939
- if (!signal?.aborted) return;
3940
- if (signal.reason instanceof Error) throw signal.reason;
3941
- throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
3902
+ // src/codebase-index/project-server-endpoint.ts
3903
+ var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
3904
+ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
3905
+ var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
3906
+ var buildIdCache;
3907
+ function projectIndexServerBuildId(entrypoint) {
3908
+ const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path4.resolve(entrypoint);
3909
+ try {
3910
+ const stat2 = fs3.statSync(file);
3911
+ if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat2.mtimeMs && buildIdCache.size === stat2.size) {
3912
+ return buildIdCache.buildId;
3913
+ }
3914
+ const buildId = createHash("sha256").update(fs3.readFileSync(file)).digest("hex").slice(0, 24);
3915
+ buildIdCache = { file, mtimeMs: stat2.mtimeMs, size: stat2.size, buildId };
3916
+ return buildId;
3917
+ } catch {
3918
+ return `unreadable:${path4.basename(file)}`;
3919
+ }
3942
3920
  }
3943
- function isAbortError(err) {
3944
- return err instanceof DOMException && err.name === "AbortError";
3921
+ function normalizeLocalPath(value) {
3922
+ const resolved = path4.resolve(value);
3923
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
3945
3924
  }
3946
- var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
3947
- var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
3948
- var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
3949
- var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
3950
- var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
3951
- function isWithinProject(projectRoot, file) {
3952
- const rel = path11.relative(projectRoot, file);
3953
- return rel !== "" && !rel.startsWith(`..${path11.sep}`) && rel !== ".." && !path11.isAbsolute(rel);
3925
+ function projectIndexServerKey(projectRoot, indexDir) {
3926
+ const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
3927
+ return createHash("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
3954
3928
  }
3955
- function isMissingPathError(err) {
3956
- const code = err?.code;
3957
- return code === "ENOENT" || code === "ENOTDIR";
3929
+ function projectIndexServerEndpoint(projectRoot, indexDir) {
3930
+ const key = projectIndexServerKey(projectRoot, indexDir);
3931
+ if (process.platform === "win32") {
3932
+ return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
3933
+ }
3934
+ return path4.join(os.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
3958
3935
  }
3959
- function normalizeComparablePath(value) {
3960
- const resolved = path11.resolve(value);
3961
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
3936
+ function projectIndexServerMetadataPath(projectRoot, indexDir) {
3937
+ return path4.join(
3938
+ path4.resolve(resolveIndexDir(projectRoot, indexDir)),
3939
+ PROJECT_INDEX_SERVER_METADATA_FILE
3940
+ );
3962
3941
  }
3963
- function gitOutput(projectRoot, args) {
3964
- return new Promise((resolve4, reject) => {
3965
- execFile2(
3966
- "git",
3967
- ["-C", projectRoot, ...args],
3968
- {
3969
- encoding: "buffer",
3970
- maxBuffer: MAX_GIT_FILE_LIST_BYTES,
3971
- windowsHide: true
3972
- },
3973
- (error, stdout) => {
3974
- if (error) reject(error);
3975
- else resolve4(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
3976
- }
3977
- );
3978
- });
3942
+
3943
+ // src/codebase-index/project-server-protocol.ts
3944
+ var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
3945
+ function encodeProjectServerMessage(message) {
3946
+ return `${JSON.stringify(message)}
3947
+ `;
3979
3948
  }
3980
- async function findGitSourceFiles(projectRoot, ignore, signal) {
3981
- try {
3982
- throwIfAborted(signal);
3983
- const topLevel = (await gitOutput(projectRoot, ["rev-parse", "--show-toplevel"])).toString("utf8").trim();
3984
- if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
3985
- throwIfAborted(signal);
3986
- const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
3987
- const [output, statusOutput] = await Promise.all([
3988
- gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
3989
- gitOutput(projectRoot, [
3990
- "status",
3991
- "--porcelain=v1",
3992
- "-z",
3993
- "--untracked-files=all",
3994
- "--ignored=no"
3995
- ])
3996
- ]);
3997
- throwIfAborted(signal);
3998
- const dirty = /* @__PURE__ */ new Set();
3999
- const deleted = /* @__PURE__ */ new Set();
4000
- const statusRecords = statusOutput.toString("utf8").split("\0");
4001
- for (let i = 0; i < statusRecords.length; i++) {
4002
- const record = statusRecords[i];
4003
- if (!record) continue;
4004
- const status = record.slice(0, 2);
4005
- const changedPath = path11.resolve(projectRoot, record.slice(3));
4006
- dirty.add(changedPath);
4007
- if (status.includes("D")) deleted.add(changedPath);
4008
- if (status.includes("R") || status.includes("C")) {
4009
- const source = statusRecords[++i];
4010
- if (source) dirty.add(path11.resolve(projectRoot, source));
4011
- }
4012
- }
4013
- const files = [];
4014
- for (const relative2 of output.toString("utf8").split("\0")) {
4015
- if (!relative2) continue;
4016
- const portable = relative2.replace(/\\/g, "/");
4017
- if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path11.posix.basename(portable))) {
4018
- continue;
3949
+
3950
+ // src/codebase-index/project-server-client.ts
3951
+ var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
3952
+ var SERVER_START_TIMEOUT_MS = 1e4;
3953
+ var SERVER_CONTROL_TIMEOUT_MS = 5e3;
3954
+ var SERVER_HEALTH_TIMEOUT_MS = 3e3;
3955
+ var SERVER_HEARTBEAT_INTERVAL_MS = 1e4;
3956
+ var StaleProjectIndexServerError = class extends Error {
3957
+ constructor(message, pid) {
3958
+ super(message);
3959
+ this.pid = pid;
3960
+ }
3961
+ pid;
3962
+ name = "StaleProjectIndexServerError";
3963
+ };
3964
+ var connectionStates = /* @__PURE__ */ new Map();
3965
+ var connectionStateListeners = /* @__PURE__ */ new Set();
3966
+ var latestConnectionState = {
3967
+ status: "offline",
3968
+ connected: false
3969
+ };
3970
+ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
3971
+ if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
3972
+ return { kind: "inline-requested" };
3973
+ }
3974
+ let builtUrl = null;
3975
+ for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
3976
+ try {
3977
+ const url = new URL(rel, import.meta.url);
3978
+ if (url.protocol === "file:" && fs4.existsSync(fileURLToPath2(url))) {
3979
+ builtUrl = url;
3980
+ break;
4019
3981
  }
4020
- const full = path11.resolve(projectRoot, relative2);
4021
- if (deleted.has(full)) continue;
4022
- const ext = path11.extname(relative2).toLowerCase();
4023
- if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
3982
+ } catch {
4024
3983
  }
4025
- return {
4026
- files,
4027
- trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
4028
- };
4029
- } catch {
4030
- return null;
4031
3984
  }
4032
- }
4033
- async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
4034
- const gitFiles = await findGitSourceFiles(projectRoot, ignore, signal);
4035
- if (gitFiles) {
4036
- return {
4037
- files: gitFiles.files,
4038
- complete: true,
4039
- errors: [],
4040
- trustedUnchanged: gitFiles.trustedUnchanged
4041
- };
4042
- }
4043
- const results = [];
4044
- const errors = [];
4045
- let complete = true;
4046
- const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
4047
- const indexableExts = new Set(INDEXABLE_EXTENSIONS);
4048
- let dirCount = 0;
4049
- const walk = async (dir) => {
4050
- throwIfAborted(signal);
4051
- if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
4052
- await yieldEventLoop();
4053
- throwIfAborted(signal);
4054
- }
4055
- let entries;
4056
- try {
4057
- entries = await fs8.readdir(dir, { withFileTypes: true });
4058
- } catch (err) {
4059
- complete = false;
4060
- errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
4061
- return;
4062
- }
4063
- dirCount++;
4064
- for (const e of entries) {
4065
- if (ignoreSet.has(e.name)) continue;
4066
- const full = path11.join(dir, e.name);
4067
- const rel = path11.relative(projectRoot, full).replace(/\\/g, "/");
4068
- if (e.isDirectory()) {
4069
- if (isGitIgnored(rel, true)) continue;
4070
- await walk(full);
4071
- } else if (e.isFile()) {
4072
- if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
4073
- const ext = path11.extname(e.name).toLowerCase();
4074
- if (indexableExts.has(ext) || detectLang(full) !== null) {
4075
- results.push(full);
4076
- }
4077
- }
4078
- }
4079
- };
4080
- await walk(projectRoot);
4081
- return { files: results, complete, errors };
4082
- }
4083
- function assignRefsToSymbols2(refs, symbols) {
4084
- if (refs.length === 0 || symbols.length === 0) return [];
4085
- const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
4086
- const seen = /* @__PURE__ */ new Set();
4087
- const assigned = [];
4088
- for (const ref of refs) {
4089
- let owner;
4090
- for (const symbol of ordered) {
4091
- if (symbol.line > ref.line) break;
4092
- owner = symbol;
3985
+ if (builtUrl === null) return { kind: "missing-build" };
3986
+ if (projectRoot !== void 0) {
3987
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
3988
+ const check = checkUnixSocketPath(endpoint);
3989
+ if (!check.ok) {
3990
+ return {
3991
+ kind: "endpoint-invalid",
3992
+ endpoint,
3993
+ byteLength: check.byteLength,
3994
+ maxBytes: check.maxBytes
3995
+ };
4093
3996
  }
4094
- if (!owner && ref.callType === "import") owner = ordered[0];
4095
- if (!owner || owner.id <= 0) continue;
4096
- const key = `${owner.id}:${ref.toName}:${ref.callType}`;
4097
- if (seen.has(key)) continue;
4098
- seen.add(key);
4099
- assigned.push({ ...ref, fromId: owner.id });
4100
3997
  }
4101
- return assigned;
3998
+ return { kind: "available", url: builtUrl };
4102
3999
  }
4103
- async function runIndexer(_ctx, opts) {
4104
- const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
4105
- try {
4106
- return await runIndexerWithStore(store, opts);
4107
- } finally {
4108
- try {
4109
- store.close();
4110
- } catch {
4000
+ function resolveProjectServerUrl() {
4001
+ const availability = resolveProjectIndexDaemonAvailability();
4002
+ return availability.kind === "available" ? availability.url : null;
4003
+ }
4004
+ function projectIndexServerExpectedBuildId() {
4005
+ const override = process.env["WRONGSTACK_INDEX_SERVER_BUILD_ID"]?.trim();
4006
+ if (override) return override;
4007
+ const url = resolveProjectServerUrl();
4008
+ return url ? projectIndexServerBuildId(url) : null;
4009
+ }
4010
+ function isProjectIndexServerAvailable() {
4011
+ return resolveProjectServerUrl() !== null;
4012
+ }
4013
+ function publishConnectionState(endpoint, state) {
4014
+ connectionStates.set(endpoint, state);
4015
+ latestConnectionState = state;
4016
+ for (const listener of connectionStateListeners) listener(state);
4017
+ }
4018
+ function getProjectIndexServerConnectionState(projectRoot, indexDir) {
4019
+ if (projectRoot) {
4020
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
4021
+ const existing = connectionStates.get(endpoint);
4022
+ if (existing) return existing;
4023
+ if (!isProjectIndexServerAvailable()) {
4024
+ return { status: "unavailable", connected: false };
4111
4025
  }
4026
+ return {
4027
+ status: "offline",
4028
+ connected: false,
4029
+ projectRoot,
4030
+ indexDir,
4031
+ endpoint
4032
+ };
4112
4033
  }
4034
+ if (latestConnectionState.endpoint) return latestConnectionState;
4035
+ if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
4036
+ return latestConnectionState;
4113
4037
  }
4114
- async function runIndexerWithStore(store, opts) {
4115
- const { projectRoot, langs, ignore = [], signal } = opts;
4116
- const relationGraphVersion = "2";
4117
- const refResolutionVersion = "2";
4118
- const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
4119
- const needsFullRefResolution = force || store.getMetadata("ref_resolution_version") !== refResolutionVersion;
4120
- const startMs = Date.now();
4121
- const errors = [];
4122
- const langStats = {};
4123
- let filesIndexed = 0;
4124
- let symbolsIndexed = 0;
4125
- const isGitIgnored = await loadGitignoreMatcher(projectRoot);
4126
- let files;
4127
- let discoveredFiles = null;
4128
- let discoveryComplete = true;
4129
- let trustedUnchanged;
4130
- if (opts.files && opts.files.length > 0) {
4131
- files = opts.files.map((f) => path11.resolve(projectRoot, f)).filter((f) => {
4132
- if (!isWithinProject(projectRoot, f)) return false;
4133
- const rel = path11.relative(projectRoot, f).replace(/\\/g, "/");
4134
- return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path11.basename(f)) && !isGitIgnored(rel, false);
4135
- });
4136
- } else {
4137
- const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
4138
- files = discovery.files;
4139
- errors.push(...discovery.errors);
4140
- discoveryComplete = discovery.complete;
4141
- discoveredFiles = new Set(files);
4142
- trustedUnchanged = discovery.trustedUnchanged;
4038
+ function onProjectIndexServerConnectionStateChange(listener) {
4039
+ connectionStateListeners.add(listener);
4040
+ return () => connectionStateListeners.delete(listener);
4041
+ }
4042
+ function remoteError(message, name) {
4043
+ if (name === "LockError") return new LockError(message);
4044
+ if (name === "IndexTimeoutError") return new IndexTimeoutError(message);
4045
+ const error = new Error(message);
4046
+ if (name && name !== "Error") error.name = name;
4047
+ return error;
4048
+ }
4049
+ function isProjectIndexServerHealth(value) {
4050
+ if (!value || typeof value !== "object") return false;
4051
+ const health = value;
4052
+ const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
4053
+ const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
4054
+ 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";
4055
+ }
4056
+ function delay(ms) {
4057
+ return new Promise((resolve4) => {
4058
+ const timer = setTimeout(resolve4, ms);
4059
+ timer.unref?.();
4060
+ });
4061
+ }
4062
+ function cancellationError(signal) {
4063
+ return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
4064
+ }
4065
+ var ProjectServerConnection = class {
4066
+ constructor(projectRoot, indexDir, endpoint) {
4067
+ this.projectRoot = projectRoot;
4068
+ this.indexDir = indexDir;
4069
+ this.endpoint = endpoint;
4070
+ this.transition("offline");
4143
4071
  }
4144
- if (langs && langs.length > 0) {
4145
- const langSet = new Set(langs);
4146
- files = files.filter((f) => {
4147
- const lang = detectLang(f);
4148
- return lang ? langSet.has(lang) : false;
4072
+ projectRoot;
4073
+ indexDir;
4074
+ endpoint;
4075
+ socket = null;
4076
+ buffer = "";
4077
+ info = null;
4078
+ activity = null;
4079
+ health = null;
4080
+ healthCheck = null;
4081
+ connecting = null;
4082
+ connectResolve = null;
4083
+ connectReject = null;
4084
+ nextId = 1;
4085
+ pending = /* @__PURE__ */ new Map();
4086
+ transition(status, options = {}) {
4087
+ const previous = connectionStates.get(this.endpoint);
4088
+ const pid = options.pid ?? (status === "connected" ? this.info?.pid : void 0);
4089
+ 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);
4090
+ publishConnectionState(this.endpoint, {
4091
+ status,
4092
+ connected: status === "connected" || status === "degraded" || status === "unresponsive",
4093
+ projectRoot: this.projectRoot,
4094
+ indexDir: this.indexDir,
4095
+ endpoint: this.endpoint,
4096
+ pid,
4097
+ lastError,
4098
+ ...this.activity ? { activity: this.activity } : {},
4099
+ ...this.health ? { health: this.health } : {}
4149
4100
  });
4150
4101
  }
4151
- if (force) store.clearAll();
4152
- const existingMeta = /* @__PURE__ */ new Map();
4153
- if (!force) {
4154
- for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
4102
+ isConnected() {
4103
+ return this.socket !== null && !this.socket.destroyed && this.info !== null;
4155
4104
  }
4156
- const totalFilesForProgress = files.length;
4157
- let filesPreSkipped = 0;
4158
- if (!force && trustedUnchanged) {
4159
- files = files.filter((file) => {
4160
- const meta = existingMeta.get(file);
4161
- if (!meta || !trustedUnchanged.has(file)) return true;
4162
- langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
4163
- symbolsIndexed += meta.symbolCount;
4164
- filesIndexed++;
4165
- filesPreSkipped++;
4166
- return false;
4105
+ async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
4106
+ await this.ensureConnected(spawnIfMissing);
4107
+ if (this.healthCheck) return this.healthCheck;
4108
+ const startedAt = Date.now();
4109
+ this.healthCheck = this.request({ type: "ping" }, { timeoutMs }).then((server) => {
4110
+ const now = Date.now();
4111
+ this.health = {
4112
+ status: "healthy",
4113
+ checkedAt: now,
4114
+ lastHealthyAt: now,
4115
+ latencyMs: Math.max(0, now - startedAt),
4116
+ missedHeartbeats: 0,
4117
+ ...isProjectIndexServerHealth(server) ? { server } : {}
4118
+ };
4119
+ this.transition("connected", { pid: this.info?.pid });
4120
+ return this.health;
4121
+ }).catch((error) => {
4122
+ if (!this.isConnected()) throw error;
4123
+ if ((this.health?.lastHealthyAt ?? 0) > startedAt) return this.health;
4124
+ const missedHeartbeats = (this.health?.missedHeartbeats ?? 0) + 1;
4125
+ const status = missedHeartbeats >= 3 ? "unresponsive" : "degraded";
4126
+ this.health = {
4127
+ status,
4128
+ checkedAt: Date.now(),
4129
+ lastHealthyAt: this.health?.lastHealthyAt ?? null,
4130
+ latencyMs: null,
4131
+ missedHeartbeats,
4132
+ ...this.health?.server ? { server: this.health.server } : {}
4133
+ };
4134
+ this.transition(status, { pid: this.info?.pid, error });
4135
+ return this.health;
4136
+ }).finally(() => {
4137
+ this.healthCheck = null;
4167
4138
  });
4168
- if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
4139
+ return this.healthCheck;
4169
4140
  }
4170
- const parallelBatch = resolveParallelBatch();
4171
- let filesSinceLastYield = 0;
4172
- for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
4173
- const batchEnd = Math.min(batchStart + parallelBatch, files.length);
4174
- const batchFiles = files.slice(batchStart, batchEnd);
4175
- opts.onProgress?.(filesPreSkipped + batchEnd, totalFilesForProgress);
4176
- filesSinceLastYield += batchFiles.length;
4177
- if (filesSinceLastYield >= YIELD_EVERY_N) {
4178
- filesSinceLastYield = 0;
4179
- await yieldEventLoop();
4180
- if (isFrugalPerf()) {
4181
- await new Promise((r) => setTimeout(r, 8));
4182
- }
4183
- throwIfAborted(signal);
4141
+ markResponsive() {
4142
+ const now = Date.now();
4143
+ this.health = {
4144
+ status: "healthy",
4145
+ checkedAt: now,
4146
+ lastHealthyAt: now,
4147
+ latencyMs: this.health?.latencyMs ?? null,
4148
+ missedHeartbeats: 0,
4149
+ ...this.health?.server ? { server: this.health.server } : {}
4150
+ };
4151
+ }
4152
+ async call(op, args, options) {
4153
+ if (options.signal?.aborted) throw cancellationError(options.signal);
4154
+ await this.ensureConnected(true);
4155
+ if (options.signal?.aborted) throw cancellationError(options.signal);
4156
+ return this.request({ type: "request", op, args }, options);
4157
+ }
4158
+ async shutdownRemote(reason) {
4159
+ try {
4160
+ await this.ensureConnected(false);
4161
+ } catch {
4162
+ return { stopped: false, reason: "not-running" };
4184
4163
  }
4185
- const statOpts = signal ? { signal } : {};
4186
- const statReadParse = await Promise.allSettled(
4187
- batchFiles.map(
4188
- async (file) => {
4189
- let stat2;
4190
- try {
4191
- stat2 = await fs8.stat(file, statOpts);
4192
- } catch (e) {
4193
- if (isAbortError(e)) throw e;
4194
- return {
4195
- file,
4196
- stat: null,
4197
- lang: "",
4198
- parsed: null,
4199
- error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
4200
- missing: isMissingPathError(e)
4201
- };
4202
- }
4203
- if (!stat2.isFile()) return { file, stat: stat2, lang: "", parsed: null };
4204
- const lang = detectLang(file);
4205
- if (!lang) return { file, stat: stat2, lang: "", parsed: null };
4206
- if (stat2.size > MAX_INDEX_FILE_BYTES) {
4207
- return {
4208
- file,
4209
- stat: stat2,
4210
- lang,
4211
- parsed: null,
4212
- error: `file too large (${stat2.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
4213
- };
4214
- }
4215
- const meta = existingMeta.get(file);
4216
- if (!force && meta && meta.mtimeMs === Math.floor(stat2.mtimeMs)) {
4217
- return { file, stat: stat2, lang, parsed: null, skippedMeta: meta };
4218
- }
4219
- let content;
4220
- try {
4221
- content = await fs8.readFile(file, { encoding: "utf8", signal });
4222
- } catch (e) {
4223
- if (isAbortError(e)) throw e;
4224
- return {
4225
- file,
4226
- stat: stat2,
4227
- lang,
4228
- parsed: null,
4229
- error: `read error: ${e instanceof Error ? e.message : String(e)}`
4230
- };
4231
- }
4232
- let parsed;
4233
- try {
4234
- parsed = await parseFileContent(file, content, lang);
4235
- } catch (e) {
4236
- return {
4237
- file,
4238
- stat: stat2,
4239
- lang,
4240
- parsed: null,
4241
- error: `parse error: ${e instanceof Error ? e.message : String(e)}`
4242
- };
4243
- }
4244
- return { file, stat: stat2, lang, parsed, content };
4245
- }
4246
- )
4164
+ const pid = this.info?.pid;
4165
+ try {
4166
+ this.transition("stopping", { pid });
4167
+ await this.request(
4168
+ { type: "shutdown", reason },
4169
+ { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
4170
+ );
4171
+ return { stopped: true, pid };
4172
+ } catch (error) {
4173
+ const forceKilled = this.forceKillKnownServer();
4174
+ return {
4175
+ stopped: forceKilled,
4176
+ pid,
4177
+ reason: forceKilled ? `force-killed after graceful shutdown failed: ${error instanceof Error ? error.message : String(error)}` : error instanceof Error ? error.message : String(error)
4178
+ };
4179
+ } finally {
4180
+ this.close();
4181
+ }
4182
+ }
4183
+ async configure(watchExternal, debounceMs) {
4184
+ await this.ensureConnected(true);
4185
+ const startedAt = Date.now();
4186
+ const result = await this.request(
4187
+ { type: "configure", watchExternal, debounceMs },
4188
+ { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
4247
4189
  );
4248
- const batchEntries = [];
4249
- const deleteForFiles = [];
4250
- for (let fi = 0; fi < statReadParse.length; fi++) {
4251
- const settled = statReadParse[fi];
4252
- const file = expectDefined5(batchFiles[fi]);
4253
- if (settled.status === "rejected") {
4254
- const err = settled.reason;
4255
- if (err instanceof Error && isAbortError(err)) throw err;
4256
- errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
4257
- continue;
4190
+ if (isProjectIndexServerHealth(result.health)) {
4191
+ const now = Date.now();
4192
+ this.health = {
4193
+ status: "healthy",
4194
+ checkedAt: now,
4195
+ lastHealthyAt: now,
4196
+ latencyMs: Math.max(0, now - startedAt),
4197
+ missedHeartbeats: 0,
4198
+ server: result.health
4199
+ };
4200
+ this.transition("connected", { pid: this.info?.pid });
4201
+ }
4202
+ }
4203
+ close() {
4204
+ const socket = this.socket;
4205
+ this.socket = null;
4206
+ this.info = null;
4207
+ this.activity = null;
4208
+ this.health = null;
4209
+ this.connectReject?.(new Error("codebase-index client disconnected"));
4210
+ this.connectResolve = null;
4211
+ this.connectReject = null;
4212
+ if (socket && !socket.destroyed) socket.destroy();
4213
+ this.rejectPending(new Error("codebase-index client disconnected"));
4214
+ this.transition("offline");
4215
+ maybeStopHeartbeatLoop();
4216
+ }
4217
+ request(message, options) {
4218
+ const socket = this.socket;
4219
+ if (!socket || socket.destroyed) {
4220
+ return Promise.reject(new Error("codebase-index server connection is not available"));
4221
+ }
4222
+ const id = this.nextId++;
4223
+ return new Promise((resolve4, reject) => {
4224
+ const timer = setTimeout(() => {
4225
+ const entry = this.pending.get(id);
4226
+ if (!entry) return;
4227
+ this.pending.delete(id);
4228
+ this.write({ type: "cancel", id });
4229
+ const error = new IndexTimeoutError(
4230
+ `Index ${message.type === "request" ? message.op : message.type} exceeded its ${options.timeoutMs}ms watchdog timeout`
4231
+ );
4232
+ this.cleanupPending(entry);
4233
+ entry.reject(error);
4234
+ }, options.timeoutMs);
4235
+ timer.unref?.();
4236
+ const signal = options.signal;
4237
+ const onAbort = signal ? () => {
4238
+ const entry = this.pending.get(id);
4239
+ if (!entry) return;
4240
+ this.pending.delete(id);
4241
+ this.write({ type: "cancel", id });
4242
+ this.cleanupPending(entry);
4243
+ entry.reject(cancellationError(signal));
4244
+ } : void 0;
4245
+ this.pending.set(id, {
4246
+ resolve: resolve4,
4247
+ reject,
4248
+ timer,
4249
+ signal,
4250
+ onAbort,
4251
+ onProgress: options.onProgress
4252
+ });
4253
+ if (signal && onAbort) {
4254
+ signal.addEventListener("abort", onAbort, { once: true });
4255
+ if (signal.aborted) {
4256
+ onAbort();
4257
+ return;
4258
+ }
4258
4259
  }
4259
- const result = settled.value;
4260
- if (result.error) {
4261
- if (result.missing) store.deleteFile(file);
4262
- errors.push(`${file}: ${result.error}`);
4263
- continue;
4260
+ this.write({ ...message, id });
4261
+ });
4262
+ }
4263
+ async ensureConnected(spawnIfMissing) {
4264
+ if (this.socket && !this.socket.destroyed && this.info) return;
4265
+ if (this.connecting) return this.connecting;
4266
+ this.transition("connecting");
4267
+ this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
4268
+ this.transition("error", { error });
4269
+ throw error;
4270
+ }).finally(() => {
4271
+ this.connecting = null;
4272
+ });
4273
+ return this.connecting;
4274
+ }
4275
+ async connectWithElection(spawnIfMissing) {
4276
+ const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
4277
+ let spawned = false;
4278
+ let staleAttempts = 0;
4279
+ let lastError = new Error("codebase-index server unavailable");
4280
+ while (Date.now() < deadline) {
4281
+ try {
4282
+ await this.connectOnce();
4283
+ return;
4284
+ } catch (error) {
4285
+ lastError = error;
4286
+ if (error instanceof StaleProjectIndexServerError) {
4287
+ staleAttempts++;
4288
+ if (!spawnIfMissing) break;
4289
+ if (staleAttempts >= 3) this.forceKillServer(error.pid);
4290
+ spawned = false;
4291
+ await delay(100);
4292
+ continue;
4293
+ }
4264
4294
  }
4265
- const { stat: stat2, lang, parsed } = result;
4266
- if (result.skippedMeta) {
4267
- langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
4268
- symbolsIndexed += result.skippedMeta.symbolCount;
4269
- filesIndexed++;
4270
- continue;
4295
+ if (!spawnIfMissing) break;
4296
+ if (!spawned) {
4297
+ this.spawnDetachedServer();
4298
+ spawned = true;
4271
4299
  }
4272
- if (!lang || !parsed) {
4273
- if (lang) {
4274
- store.upsertFile({
4275
- file,
4276
- lang,
4277
- mtimeMs: Math.floor(stat2.mtimeMs),
4278
- symbolCount: 0,
4279
- lastIndexed: Date.now()
4280
- });
4281
- filesIndexed++;
4300
+ await delay(75);
4301
+ }
4302
+ throw lastError;
4303
+ }
4304
+ connectOnce() {
4305
+ this.socket?.destroy();
4306
+ this.socket = null;
4307
+ this.info = null;
4308
+ this.activity = null;
4309
+ this.health = null;
4310
+ this.buffer = "";
4311
+ return new Promise((resolve4, reject) => {
4312
+ const socket = net.createConnection(this.endpoint);
4313
+ this.socket = socket;
4314
+ socket.setEncoding("utf8");
4315
+ const timer = setTimeout(() => {
4316
+ reject(new Error("codebase-index server handshake timed out"));
4317
+ socket.destroy();
4318
+ }, CONNECT_ATTEMPT_TIMEOUT_MS);
4319
+ timer.unref?.();
4320
+ const finishResolve = () => {
4321
+ clearTimeout(timer);
4322
+ this.connectResolve = null;
4323
+ this.connectReject = null;
4324
+ resolve4();
4325
+ };
4326
+ const finishReject = (error) => {
4327
+ clearTimeout(timer);
4328
+ this.connectResolve = null;
4329
+ this.connectReject = null;
4330
+ reject(error);
4331
+ };
4332
+ this.connectResolve = finishResolve;
4333
+ this.connectReject = finishReject;
4334
+ socket.on("data", (chunk) => this.onData(socket, chunk));
4335
+ socket.on("error", (error) => {
4336
+ if (!this.info) finishReject(error);
4337
+ });
4338
+ socket.on("close", () => this.onClose(socket));
4339
+ });
4340
+ }
4341
+ onData(socket, chunk) {
4342
+ if (socket !== this.socket) return;
4343
+ this.buffer += chunk;
4344
+ while (true) {
4345
+ const newline = this.buffer.indexOf("\n");
4346
+ if (newline < 0) {
4347
+ if (this.buffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
4348
+ socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
4282
4349
  }
4283
- continue;
4350
+ return;
4284
4351
  }
4285
- if (parsed.symbols.length === 0) {
4286
- store.replaceEmptyFile({
4287
- file,
4288
- lang,
4289
- mtimeMs: Math.floor(stat2.mtimeMs),
4290
- symbolCount: 0,
4291
- lastIndexed: Date.now()
4292
- });
4293
- filesIndexed++;
4294
- continue;
4352
+ if (newline > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
4353
+ socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
4354
+ return;
4295
4355
  }
4296
- batchEntries.push({
4297
- file,
4298
- lang,
4299
- symbols: parsed.symbols,
4300
- refs: parsed.refs ?? [],
4301
- mtimeMs: Math.floor(stat2.mtimeMs),
4302
- symbolCount: parsed.symbols.length
4303
- });
4304
- deleteForFiles.push(file);
4305
- }
4306
- if (batchEntries.length > 0) {
4356
+ const line = this.buffer.slice(0, newline);
4357
+ this.buffer = this.buffer.slice(newline + 1);
4358
+ if (!line) continue;
4359
+ let message;
4307
4360
  try {
4308
- store.commitBatch(batchEntries, { deleteForFiles });
4309
- for (const entry of batchEntries) {
4310
- const count = entry.symbols.length;
4311
- symbolsIndexed += count;
4312
- langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
4313
- filesIndexed++;
4314
- }
4315
- } catch (err) {
4316
- const message = err instanceof Error ? err.message : String(err);
4317
- errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
4318
- for (const entry of batchEntries) {
4319
- try {
4320
- store.deleteRefsForFile(entry.file);
4321
- store.deleteSymbolsForFile(entry.file);
4322
- const symbolsWithIds = store.insertSymbols(entry.symbols);
4323
- symbolsIndexed += symbolsWithIds.length;
4324
- langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
4325
- filesIndexed++;
4326
- if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
4327
- const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
4328
- if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
4329
- }
4330
- store.resolveRefsForNames([
4331
- ...entry.symbols.map((symbol) => symbol.name),
4332
- ...entry.refs.map((ref) => ref.toName)
4333
- ]);
4334
- store.upsertFile({
4335
- file: entry.file,
4336
- lang: entry.lang,
4337
- mtimeMs: entry.mtimeMs,
4338
- symbolCount: entry.symbolCount,
4339
- lastIndexed: Date.now()
4340
- });
4341
- } catch (innerErr) {
4342
- errors.push(
4343
- `fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
4344
- );
4345
- }
4346
- }
4361
+ message = JSON.parse(line);
4362
+ } catch {
4363
+ socket.destroy(new Error("invalid codebase-index server response"));
4364
+ return;
4347
4365
  }
4366
+ this.onMessage(message);
4348
4367
  }
4349
4368
  }
4350
- if (discoveredFiles && discoveryComplete) {
4351
- for (const [file_] of existingMeta) {
4352
- if (!discoveredFiles.has(file_)) {
4353
- store.deleteFile(file_);
4369
+ onMessage(message) {
4370
+ if (message.type === "hello") {
4371
+ if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
4372
+ this.rejectStaleServer(
4373
+ message,
4374
+ `codebase-index protocol mismatch: client=${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
4375
+ );
4376
+ return;
4377
+ }
4378
+ const expectedBuildId = projectIndexServerExpectedBuildId();
4379
+ if (expectedBuildId && message.buildId !== expectedBuildId) {
4380
+ this.rejectStaleServer(
4381
+ message,
4382
+ `codebase-index build mismatch: client=${expectedBuildId}, server=${message.buildId ?? "legacy"}`
4383
+ );
4384
+ return;
4354
4385
  }
4386
+ this.info = message;
4387
+ this.markResponsive();
4388
+ this.transition("connected", { pid: message.pid });
4389
+ ensureHeartbeatLoop();
4390
+ this.connectResolve?.();
4391
+ return;
4392
+ }
4393
+ if (message.type === "index-state") {
4394
+ this.activity = message.state;
4395
+ this.markResponsive();
4396
+ this.transition("connected", { pid: this.info?.pid });
4397
+ return;
4398
+ }
4399
+ const entry = this.pending.get(message.id);
4400
+ if (!entry) return;
4401
+ this.markResponsive();
4402
+ const status = connectionStates.get(this.endpoint)?.status;
4403
+ if (status === "degraded" || status === "unresponsive") {
4404
+ this.transition("connected", { pid: this.info?.pid });
4405
+ }
4406
+ if (message.type === "progress") {
4407
+ entry.onProgress?.(message.current, message.total);
4408
+ return;
4409
+ }
4410
+ this.pending.delete(message.id);
4411
+ this.cleanupPending(entry);
4412
+ if (message.ok) entry.resolve(message.result);
4413
+ else entry.reject(remoteError(message.error, message.errorName));
4414
+ }
4415
+ onClose(socket) {
4416
+ if (socket !== this.socket) return;
4417
+ const wasConnected = this.info !== null;
4418
+ this.socket = null;
4419
+ this.info = null;
4420
+ this.activity = null;
4421
+ this.health = null;
4422
+ const error = new Error("codebase-index server connection closed");
4423
+ this.connectReject?.(error);
4424
+ this.connectResolve = null;
4425
+ this.connectReject = null;
4426
+ this.rejectPending(error);
4427
+ if (wasConnected) this.transition("error", { error });
4428
+ maybeStopHeartbeatLoop();
4429
+ }
4430
+ cleanupPending(entry) {
4431
+ clearTimeout(entry.timer);
4432
+ if (entry.signal && entry.onAbort) {
4433
+ entry.signal.removeEventListener("abort", entry.onAbort);
4355
4434
  }
4356
4435
  }
4357
- if (needsFullRefResolution) store.resolveRefs();
4358
- store.setMetadata("ref_resolution_version", refResolutionVersion);
4359
- store.setMetadata("relation_graph_version", relationGraphVersion);
4360
- if (!opts.files || filesIndexed >= 50) store.optimize();
4361
- store.setLastIndexed(Date.now());
4362
- if (!opts.files) store.compactIfNeeded();
4363
- const durationMs = Date.now() - startMs;
4364
- return {
4365
- filesIndexed,
4366
- symbolsIndexed,
4367
- langStats,
4368
- durationMs,
4369
- errors
4370
- };
4371
- }
4372
-
4373
- // src/codebase-index/index-service.ts
4374
- async function indexService(args, hooks = {}) {
4375
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
4376
- try {
4377
- return await runIndexerWithStore(store, {
4378
- projectRoot: args.projectRoot,
4379
- indexDir: args.indexDir,
4380
- files: args.files,
4381
- force: args.force,
4382
- langs: args.langs,
4383
- ignore: args.ignore,
4384
- signal: hooks.signal,
4385
- onProgress: hooks.onProgress
4436
+ rejectPending(error) {
4437
+ const entries = [...this.pending.values()];
4438
+ this.pending.clear();
4439
+ for (const entry of entries) {
4440
+ this.cleanupPending(entry);
4441
+ entry.reject(error);
4442
+ }
4443
+ }
4444
+ write(message) {
4445
+ const socket = this.socket;
4446
+ if (socket && !socket.destroyed) socket.write(encodeProjectServerMessage(message));
4447
+ }
4448
+ rejectStaleServer(message, reason) {
4449
+ const socket = this.socket;
4450
+ if (socket && !socket.destroyed) {
4451
+ socket.write(
4452
+ encodeProjectServerMessage({
4453
+ type: "shutdown",
4454
+ id: 0,
4455
+ reason: "stale-build-replacement"
4456
+ })
4457
+ );
4458
+ const timer = setTimeout(() => socket.destroy(), 25);
4459
+ timer.unref?.();
4460
+ }
4461
+ this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
4462
+ }
4463
+ spawnDetachedServer() {
4464
+ const url = resolveProjectServerUrl();
4465
+ if (!url) throw new Error("built codebase-index project server is unavailable");
4466
+ if (process.platform !== "win32") {
4467
+ try {
4468
+ fs4.rmSync(this.endpoint, { force: true });
4469
+ } catch {
4470
+ }
4471
+ }
4472
+ const args = [fileURLToPath2(url), "--project-root", this.projectRoot];
4473
+ if (this.indexDir) args.push("--index-dir", this.indexDir);
4474
+ const child = spawn(process.execPath, args, {
4475
+ detached: true,
4476
+ stdio: "ignore",
4477
+ windowsHide: true,
4478
+ env: process.env
4386
4479
  });
4387
- } finally {
4388
- indexStorePool.release(store);
4480
+ child.unref();
4389
4481
  }
4390
- }
4391
- function searchService(args) {
4392
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
4393
- try {
4394
- return store.searchRanked(
4395
- args.query,
4396
- {
4397
- kind: args.kind,
4398
- lang: args.lang,
4399
- file: args.file,
4400
- lspKind: args.lspKind
4401
- },
4402
- args.limit
4403
- );
4404
- } finally {
4405
- indexStorePool.release(store);
4482
+ forceKillKnownServer() {
4483
+ const pid = this.info?.pid;
4484
+ return pid ? this.forceKillServer(pid) : false;
4485
+ }
4486
+ forceKillServer(pid) {
4487
+ if (pid === process.pid) return false;
4488
+ try {
4489
+ process.kill(pid);
4490
+ const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
4491
+ try {
4492
+ const metadata = JSON.parse(fs4.readFileSync(metadataPath, "utf8"));
4493
+ if (metadata.pid === pid) fs4.rmSync(metadataPath, { force: true });
4494
+ } catch {
4495
+ }
4496
+ return true;
4497
+ } catch {
4498
+ return false;
4499
+ }
4406
4500
  }
4501
+ };
4502
+ var connections = /* @__PURE__ */ new Map();
4503
+ var heartbeatTimer;
4504
+ function ensureHeartbeatLoop() {
4505
+ if (heartbeatTimer) return;
4506
+ heartbeatTimer = setInterval(() => {
4507
+ for (const connection of connections.values()) {
4508
+ if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
4509
+ });
4510
+ }
4511
+ }, SERVER_HEARTBEAT_INTERVAL_MS);
4512
+ heartbeatTimer.unref?.();
4407
4513
  }
4408
- function statsService(args) {
4409
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
4410
- try {
4411
- return store.getStats();
4412
- } finally {
4413
- indexStorePool.release(store);
4414
- }
4514
+ function maybeStopHeartbeatLoop() {
4515
+ if (!heartbeatTimer) return;
4516
+ if ([...connections.values()].some((connection) => connection.isConnected())) return;
4517
+ clearInterval(heartbeatTimer);
4518
+ heartbeatTimer = void 0;
4415
4519
  }
4416
- function packageGraphService(args) {
4417
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
4418
- try {
4419
- return store.getPackageGraph();
4420
- } finally {
4421
- indexStorePool.release(store);
4520
+ function connectionFor(projectRoot, indexDir) {
4521
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
4522
+ let connection = connections.get(endpoint);
4523
+ if (!connection) {
4524
+ connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
4525
+ connections.set(endpoint, connection);
4422
4526
  }
4527
+ return connection;
4423
4528
  }
4424
- function fileGraphService(args) {
4425
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
4426
- try {
4427
- return store.getFileGraph(args.packageFilter);
4428
- } finally {
4429
- indexStorePool.release(store);
4430
- }
4529
+ function callProjectIndexServer(op, args, options) {
4530
+ return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
4431
4531
  }
4432
- function symbolGraphService(args) {
4433
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
4532
+ function ensureProjectIndexServer(options) {
4533
+ return connectionFor(options.projectRoot, options.indexDir).configure(
4534
+ options.watchExternal,
4535
+ options.debounceMs
4536
+ );
4537
+ }
4538
+ function checkProjectIndexServerHealth(projectRoot, indexDir, options = {}) {
4539
+ return connectionFor(projectRoot, indexDir).checkHealth(
4540
+ false,
4541
+ options.timeoutMs ?? SERVER_HEALTH_TIMEOUT_MS
4542
+ );
4543
+ }
4544
+ async function shutdownProjectIndexServer(projectRoot, indexDir, reason) {
4545
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
4546
+ const connection = connectionFor(projectRoot, indexDir);
4434
4547
  try {
4435
- return store.getSymbolGraph(args.fileFilter);
4548
+ return await connection.shutdownRemote(reason);
4436
4549
  } finally {
4437
- indexStorePool.release(store);
4550
+ connection.close();
4551
+ connections.delete(endpoint);
4552
+ connectionStates.delete(endpoint);
4438
4553
  }
4439
4554
  }
4555
+ function closeProjectIndexServerClients() {
4556
+ for (const connection of connections.values()) connection.close();
4557
+ connections.clear();
4558
+ connectionStates.clear();
4559
+ latestConnectionState = {
4560
+ status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
4561
+ connected: false
4562
+ };
4563
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
4564
+ heartbeatTimer = void 0;
4565
+ }
4440
4566
 
4441
4567
  // src/codebase-index/background-indexer.ts
4442
- init_languages();
4443
-
4444
- // src/codebase-index/project-server-client.ts
4445
- import { spawn as spawn4 } from "node:child_process";
4446
- import * as fs10 from "node:fs";
4447
- import * as net from "node:net";
4448
- import { fileURLToPath as fileURLToPath2 } from "node:url";
4568
+ import * as fs11 from "node:fs";
4569
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
4570
+ import { Worker } from "node:worker_threads";
4449
4571
 
4450
- // src/codebase-index/project-server-endpoint.ts
4451
- import { createHash } from "node:crypto";
4452
- import * as fs9 from "node:fs";
4453
- import * as os3 from "node:os";
4572
+ // src/codebase-index/indexer.ts
4573
+ import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
4574
+ import { execFile as execFile2 } from "node:child_process";
4575
+ import * as fs10 from "node:fs/promises";
4576
+ import { availableParallelism } from "node:os";
4454
4577
  import * as path12 from "node:path";
4455
- import { fileURLToPath } from "node:url";
4456
- var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
4457
- var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
4458
- var buildIdCache;
4459
- function projectIndexServerBuildId(entrypoint) {
4460
- const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path12.resolve(entrypoint);
4461
- try {
4462
- const stat2 = fs9.statSync(file);
4463
- if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat2.mtimeMs && buildIdCache.size === stat2.size) {
4464
- return buildIdCache.buildId;
4578
+ import {
4579
+ DEFAULT_WALK_IGNORE_DIRS,
4580
+ indexParallelBatchSize,
4581
+ isFrugalPerf
4582
+ } from "@wrongstack/core/utils";
4583
+
4584
+ // src/codebase-index/gitignore.ts
4585
+ import * as fs5 from "node:fs/promises";
4586
+ import * as path5 from "node:path";
4587
+ import { compileGlob } from "@wrongstack/core/utils";
4588
+ function globBody(glob) {
4589
+ return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
4590
+ }
4591
+ function compileGitignore(lines) {
4592
+ const rules = [];
4593
+ for (const raw of lines) {
4594
+ let line = raw.replace(/\r$/, "");
4595
+ if (!line.trim() || line.trimStart().startsWith("#")) continue;
4596
+ line = line.trim();
4597
+ let negated = false;
4598
+ if (line.startsWith("!")) {
4599
+ negated = true;
4600
+ line = line.slice(1);
4465
4601
  }
4466
- const buildId = createHash("sha256").update(fs9.readFileSync(file)).digest("hex").slice(0, 24);
4467
- buildIdCache = { file, mtimeMs: stat2.mtimeMs, size: stat2.size, buildId };
4468
- return buildId;
4469
- } catch {
4470
- return `unreadable:${path12.basename(file)}`;
4602
+ let dirOnly = false;
4603
+ if (line.endsWith("/")) {
4604
+ dirOnly = true;
4605
+ line = line.slice(0, -1);
4606
+ }
4607
+ if (!line) continue;
4608
+ const anchored = line.startsWith("/") || line.includes("/");
4609
+ if (line.startsWith("/")) line = line.slice(1);
4610
+ const body = globBody(line);
4611
+ const prefix = anchored ? "^" : "(?:^|.*/)";
4612
+ rules.push({
4613
+ eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
4614
+ under: new RegExp(`${prefix}${body}/.*$`),
4615
+ negated,
4616
+ dirOnly
4617
+ });
4471
4618
  }
4619
+ return (relPath, isDir) => {
4620
+ const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
4621
+ let ignored = false;
4622
+ for (const r of rules) {
4623
+ const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
4624
+ if (re.test(p)) ignored = !r.negated;
4625
+ }
4626
+ return ignored;
4627
+ };
4472
4628
  }
4473
- function normalizeLocalPath(value) {
4474
- const resolved = path12.resolve(value);
4475
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
4476
- }
4477
- function projectIndexServerKey(projectRoot, indexDir) {
4478
- const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
4479
- return createHash("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
4480
- }
4481
- function projectIndexServerEndpoint(projectRoot, indexDir) {
4482
- const key = projectIndexServerKey(projectRoot, indexDir);
4483
- if (process.platform === "win32") {
4484
- return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
4629
+ async function loadGitignoreMatcher(projectRoot) {
4630
+ let lines = [];
4631
+ try {
4632
+ const raw = await fs5.readFile(path5.join(projectRoot, ".gitignore"), "utf8");
4633
+ lines = raw.split("\n");
4634
+ } catch {
4485
4635
  }
4486
- return path12.join(
4487
- os3.tmpdir(),
4488
- `wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`,
4489
- `${key}.sock`
4490
- );
4491
- }
4492
- function projectIndexServerMetadataPath(projectRoot, indexDir) {
4493
- return path12.join(
4494
- path12.resolve(resolveIndexDir(projectRoot, indexDir)),
4495
- PROJECT_INDEX_SERVER_METADATA_FILE
4496
- );
4636
+ return compileGitignore(lines);
4497
4637
  }
4498
4638
 
4499
- // src/codebase-index/project-server-protocol.ts
4500
- var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
4501
- function encodeProjectServerMessage(message) {
4502
- return `${JSON.stringify(message)}
4503
- `;
4504
- }
4639
+ // src/codebase-index/indexer.ts
4640
+ init_languages();
4505
4641
 
4506
- // src/codebase-index/project-server-client.ts
4507
- var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
4508
- var SERVER_START_TIMEOUT_MS = 1e4;
4509
- var SERVER_CONTROL_TIMEOUT_MS = 5e3;
4510
- var SERVER_HEALTH_TIMEOUT_MS = 3e3;
4511
- var SERVER_HEARTBEAT_INTERVAL_MS = 1e4;
4512
- var StaleProjectIndexServerError = class extends Error {
4513
- constructor(message, pid) {
4514
- super(message);
4515
- this.pid = pid;
4516
- }
4517
- pid;
4518
- name = "StaleProjectIndexServerError";
4519
- };
4520
- var connectionStates = /* @__PURE__ */ new Map();
4521
- var connectionStateListeners = /* @__PURE__ */ new Set();
4522
- var latestConnectionState = {
4523
- status: "offline",
4524
- connected: false
4525
- };
4526
- function resolveProjectIndexDaemonAvailability() {
4527
- if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
4528
- return { kind: "inline-requested" };
4529
- }
4530
- for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
4531
- try {
4532
- const url = new URL(rel, import.meta.url);
4533
- if (url.protocol === "file:" && fs10.existsSync(fileURLToPath2(url))) {
4534
- return { kind: "available", url };
4535
- }
4536
- } catch {
4642
+ // src/codebase-index/parser-dispatch.ts
4643
+ async function parseFileContent(file, content, lang) {
4644
+ switch (lang) {
4645
+ case "ts":
4646
+ case "tsx":
4647
+ case "js":
4648
+ case "jsx": {
4649
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
4650
+ return parseSymbols8({ file, content, lang });
4651
+ }
4652
+ case "go": {
4653
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
4654
+ return parseSymbols8({ file, content, lang: "go" });
4655
+ }
4656
+ case "py": {
4657
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
4658
+ return parseSymbols8({ file, content, lang: "py" });
4659
+ }
4660
+ case "rs": {
4661
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
4662
+ return parseSymbols8({ file, content, lang: "rs" });
4663
+ }
4664
+ case "json": {
4665
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
4666
+ return parseSymbols8({ file, content, lang: "json" });
4667
+ }
4668
+ case "yaml": {
4669
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
4670
+ return parseSymbols8({ file, content, lang: "yaml" });
4671
+ }
4672
+ default: {
4673
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
4674
+ return parseSymbols8({ file, content, lang });
4537
4675
  }
4538
4676
  }
4539
- return { kind: "missing-build" };
4540
- }
4541
- function resolveProjectServerUrl() {
4542
- const availability = resolveProjectIndexDaemonAvailability();
4543
- return availability.kind === "available" ? availability.url : null;
4544
- }
4545
- function projectIndexServerExpectedBuildId() {
4546
- const override = process.env["WRONGSTACK_INDEX_SERVER_BUILD_ID"]?.trim();
4547
- if (override) return override;
4548
- const url = resolveProjectServerUrl();
4549
- return url ? projectIndexServerBuildId(url) : null;
4550
4677
  }
4551
- function isProjectIndexServerAvailable() {
4552
- return resolveProjectServerUrl() !== null;
4678
+
4679
+ // src/codebase-index/indexer.ts
4680
+ var YIELD_EVERY_N = 50;
4681
+ function resolveParallelBatch() {
4682
+ return indexParallelBatchSize(availableParallelism());
4553
4683
  }
4554
- function publishConnectionState(endpoint, state) {
4555
- connectionStates.set(endpoint, state);
4556
- latestConnectionState = state;
4557
- for (const listener of connectionStateListeners) listener(state);
4684
+ function yieldEventLoop() {
4685
+ return new Promise((resolve4) => setImmediate(resolve4));
4558
4686
  }
4559
- function getProjectIndexServerConnectionState(projectRoot, indexDir) {
4560
- if (projectRoot) {
4561
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
4562
- const existing = connectionStates.get(endpoint);
4563
- if (existing) return existing;
4564
- if (!isProjectIndexServerAvailable()) {
4565
- return { status: "unavailable", connected: false };
4566
- }
4567
- return {
4568
- status: "offline",
4569
- connected: false,
4570
- projectRoot,
4571
- indexDir,
4572
- endpoint
4573
- };
4574
- }
4575
- if (latestConnectionState.endpoint) return latestConnectionState;
4576
- if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
4577
- return latestConnectionState;
4687
+ function throwIfAborted(signal) {
4688
+ if (!signal?.aborted) return;
4689
+ if (signal.reason instanceof Error) throw signal.reason;
4690
+ throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
4578
4691
  }
4579
- function onProjectIndexServerConnectionStateChange(listener) {
4580
- connectionStateListeners.add(listener);
4581
- return () => connectionStateListeners.delete(listener);
4692
+ function isAbortError(err) {
4693
+ return err instanceof DOMException && err.name === "AbortError";
4582
4694
  }
4583
- function remoteError(message, name) {
4584
- if (name === "LockError") return new LockError(message);
4585
- if (name === "IndexTimeoutError") return new IndexTimeoutError(message);
4586
- const error = new Error(message);
4587
- if (name && name !== "Error") error.name = name;
4588
- return error;
4695
+ var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
4696
+ var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
4697
+ var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
4698
+ var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
4699
+ var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
4700
+ function isWithinProject(projectRoot, file) {
4701
+ const rel = path12.relative(projectRoot, file);
4702
+ return rel !== "" && !rel.startsWith(`..${path12.sep}`) && rel !== ".." && !path12.isAbsolute(rel);
4589
4703
  }
4590
- function isProjectIndexServerHealth(value) {
4591
- if (!value || typeof value !== "object") return false;
4592
- const health = value;
4593
- const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
4594
- const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
4595
- 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";
4704
+ function isMissingPathError(err) {
4705
+ const code = err?.code;
4706
+ return code === "ENOENT" || code === "ENOTDIR";
4596
4707
  }
4597
- function delay(ms) {
4598
- return new Promise((resolve4) => {
4599
- const timer = setTimeout(resolve4, ms);
4600
- timer.unref?.();
4601
- });
4708
+ function normalizeComparablePath(value) {
4709
+ const resolved = path12.resolve(value);
4710
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
4602
4711
  }
4603
- function cancellationError(signal) {
4604
- return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
4712
+ function gitOutput(projectRoot, args) {
4713
+ return new Promise((resolve4, reject) => {
4714
+ execFile2(
4715
+ "git",
4716
+ ["-C", projectRoot, ...args],
4717
+ {
4718
+ encoding: "buffer",
4719
+ maxBuffer: MAX_GIT_FILE_LIST_BYTES,
4720
+ windowsHide: true
4721
+ },
4722
+ (error, stdout) => {
4723
+ if (error) reject(error);
4724
+ else resolve4(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
4725
+ }
4726
+ );
4727
+ });
4605
4728
  }
4606
- var ProjectServerConnection = class {
4607
- constructor(projectRoot, indexDir, endpoint) {
4608
- this.projectRoot = projectRoot;
4609
- this.indexDir = indexDir;
4610
- this.endpoint = endpoint;
4611
- this.transition("offline");
4612
- }
4613
- projectRoot;
4614
- indexDir;
4615
- endpoint;
4616
- socket = null;
4617
- buffer = "";
4618
- info = null;
4619
- activity = null;
4620
- health = null;
4621
- healthCheck = null;
4622
- connecting = null;
4623
- connectResolve = null;
4624
- connectReject = null;
4625
- nextId = 1;
4626
- pending = /* @__PURE__ */ new Map();
4627
- transition(status, options = {}) {
4628
- const previous = connectionStates.get(this.endpoint);
4629
- const pid = options.pid ?? (status === "connected" ? this.info?.pid : void 0);
4630
- 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);
4631
- publishConnectionState(this.endpoint, {
4632
- status,
4633
- connected: status === "connected" || status === "degraded" || status === "unresponsive",
4634
- projectRoot: this.projectRoot,
4635
- indexDir: this.indexDir,
4636
- endpoint: this.endpoint,
4637
- pid,
4638
- lastError,
4639
- ...this.activity ? { activity: this.activity } : {},
4640
- ...this.health ? { health: this.health } : {}
4641
- });
4642
- }
4643
- isConnected() {
4644
- return this.socket !== null && !this.socket.destroyed && this.info !== null;
4645
- }
4646
- async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
4647
- await this.ensureConnected(spawnIfMissing);
4648
- if (this.healthCheck) return this.healthCheck;
4649
- const startedAt = Date.now();
4650
- this.healthCheck = this.request({ type: "ping" }, { timeoutMs }).then((server) => {
4651
- const now = Date.now();
4652
- this.health = {
4653
- status: "healthy",
4654
- checkedAt: now,
4655
- lastHealthyAt: now,
4656
- latencyMs: Math.max(0, now - startedAt),
4657
- missedHeartbeats: 0,
4658
- ...isProjectIndexServerHealth(server) ? { server } : {}
4659
- };
4660
- this.transition("connected", { pid: this.info?.pid });
4661
- return this.health;
4662
- }).catch((error) => {
4663
- if (!this.isConnected()) throw error;
4664
- if ((this.health?.lastHealthyAt ?? 0) > startedAt) return this.health;
4665
- const missedHeartbeats = (this.health?.missedHeartbeats ?? 0) + 1;
4666
- const status = missedHeartbeats >= 3 ? "unresponsive" : "degraded";
4667
- this.health = {
4668
- status,
4669
- checkedAt: Date.now(),
4670
- lastHealthyAt: this.health?.lastHealthyAt ?? null,
4671
- latencyMs: null,
4672
- missedHeartbeats,
4673
- ...this.health?.server ? { server: this.health.server } : {}
4674
- };
4675
- this.transition(status, { pid: this.info?.pid, error });
4676
- return this.health;
4677
- }).finally(() => {
4678
- this.healthCheck = null;
4679
- });
4680
- return this.healthCheck;
4681
- }
4682
- markResponsive() {
4683
- const now = Date.now();
4684
- this.health = {
4685
- status: "healthy",
4686
- checkedAt: now,
4687
- lastHealthyAt: now,
4688
- latencyMs: this.health?.latencyMs ?? null,
4689
- missedHeartbeats: 0,
4690
- ...this.health?.server ? { server: this.health.server } : {}
4691
- };
4692
- }
4693
- async call(op, args, options) {
4694
- if (options.signal?.aborted) throw cancellationError(options.signal);
4695
- await this.ensureConnected(true);
4696
- if (options.signal?.aborted) throw cancellationError(options.signal);
4697
- return this.request({ type: "request", op, args }, options);
4698
- }
4699
- async shutdownRemote(reason) {
4700
- try {
4701
- await this.ensureConnected(false);
4702
- } catch {
4703
- return { stopped: false, reason: "not-running" };
4704
- }
4705
- const pid = this.info?.pid;
4706
- try {
4707
- this.transition("stopping", { pid });
4708
- await this.request(
4709
- { type: "shutdown", reason },
4710
- { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
4711
- );
4712
- return { stopped: true, pid };
4713
- } catch (error) {
4714
- const forceKilled = this.forceKillKnownServer();
4715
- return {
4716
- stopped: forceKilled,
4717
- pid,
4718
- reason: forceKilled ? `force-killed after graceful shutdown failed: ${error instanceof Error ? error.message : String(error)}` : error instanceof Error ? error.message : String(error)
4719
- };
4720
- } finally {
4721
- this.close();
4729
+ async function findGitSourceFiles(projectRoot, ignore, signal) {
4730
+ try {
4731
+ throwIfAborted(signal);
4732
+ const topLevel = (await gitOutput(projectRoot, ["rev-parse", "--show-toplevel"])).toString("utf8").trim();
4733
+ if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
4734
+ throwIfAborted(signal);
4735
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
4736
+ const [output, statusOutput] = await Promise.all([
4737
+ gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
4738
+ gitOutput(projectRoot, [
4739
+ "status",
4740
+ "--porcelain=v1",
4741
+ "-z",
4742
+ "--untracked-files=all",
4743
+ "--ignored=no"
4744
+ ])
4745
+ ]);
4746
+ throwIfAborted(signal);
4747
+ const dirty = /* @__PURE__ */ new Set();
4748
+ const deleted = /* @__PURE__ */ new Set();
4749
+ const statusRecords = statusOutput.toString("utf8").split("\0");
4750
+ for (let i = 0; i < statusRecords.length; i++) {
4751
+ const record = statusRecords[i];
4752
+ if (!record) continue;
4753
+ const status = record.slice(0, 2);
4754
+ const changedPath = path12.resolve(projectRoot, record.slice(3));
4755
+ dirty.add(changedPath);
4756
+ if (status.includes("D")) deleted.add(changedPath);
4757
+ if (status.includes("R") || status.includes("C")) {
4758
+ const source = statusRecords[++i];
4759
+ if (source) dirty.add(path12.resolve(projectRoot, source));
4760
+ }
4722
4761
  }
4723
- }
4724
- async configure(watchExternal, debounceMs) {
4725
- await this.ensureConnected(true);
4726
- const startedAt = Date.now();
4727
- const result = await this.request(
4728
- { type: "configure", watchExternal, debounceMs },
4729
- { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
4730
- );
4731
- if (isProjectIndexServerHealth(result.health)) {
4732
- const now = Date.now();
4733
- this.health = {
4734
- status: "healthy",
4735
- checkedAt: now,
4736
- lastHealthyAt: now,
4737
- latencyMs: Math.max(0, now - startedAt),
4738
- missedHeartbeats: 0,
4739
- server: result.health
4740
- };
4741
- this.transition("connected", { pid: this.info?.pid });
4762
+ const files = [];
4763
+ for (const relative2 of output.toString("utf8").split("\0")) {
4764
+ if (!relative2) continue;
4765
+ const portable = relative2.replace(/\\/g, "/");
4766
+ if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path12.posix.basename(portable))) {
4767
+ continue;
4768
+ }
4769
+ const full = path12.resolve(projectRoot, relative2);
4770
+ if (deleted.has(full)) continue;
4771
+ const ext = path12.extname(relative2).toLowerCase();
4772
+ if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
4742
4773
  }
4774
+ return {
4775
+ files,
4776
+ trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
4777
+ };
4778
+ } catch {
4779
+ return null;
4743
4780
  }
4744
- close() {
4745
- const socket = this.socket;
4746
- this.socket = null;
4747
- this.info = null;
4748
- this.activity = null;
4749
- this.health = null;
4750
- this.connectReject?.(new Error("codebase-index client disconnected"));
4751
- this.connectResolve = null;
4752
- this.connectReject = null;
4753
- if (socket && !socket.destroyed) socket.destroy();
4754
- this.rejectPending(new Error("codebase-index client disconnected"));
4755
- this.transition("offline");
4756
- maybeStopHeartbeatLoop();
4781
+ }
4782
+ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
4783
+ const gitFiles = await findGitSourceFiles(projectRoot, ignore, signal);
4784
+ if (gitFiles) {
4785
+ return {
4786
+ files: gitFiles.files,
4787
+ complete: true,
4788
+ errors: [],
4789
+ trustedUnchanged: gitFiles.trustedUnchanged
4790
+ };
4757
4791
  }
4758
- request(message, options) {
4759
- const socket = this.socket;
4760
- if (!socket || socket.destroyed) {
4761
- return Promise.reject(new Error("codebase-index server connection is not available"));
4792
+ const results = [];
4793
+ const errors = [];
4794
+ let complete = true;
4795
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
4796
+ const indexableExts = new Set(INDEXABLE_EXTENSIONS);
4797
+ let dirCount = 0;
4798
+ const walk = async (dir) => {
4799
+ throwIfAborted(signal);
4800
+ if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
4801
+ await yieldEventLoop();
4802
+ throwIfAborted(signal);
4762
4803
  }
4763
- const id = this.nextId++;
4764
- return new Promise((resolve4, reject) => {
4765
- const timer = setTimeout(() => {
4766
- const entry = this.pending.get(id);
4767
- if (!entry) return;
4768
- this.pending.delete(id);
4769
- this.write({ type: "cancel", id });
4770
- const error = new IndexTimeoutError(
4771
- `Index ${message.type === "request" ? message.op : message.type} exceeded its ${options.timeoutMs}ms watchdog timeout`
4772
- );
4773
- this.cleanupPending(entry);
4774
- entry.reject(error);
4775
- }, options.timeoutMs);
4776
- timer.unref?.();
4777
- const signal = options.signal;
4778
- const onAbort = signal ? () => {
4779
- const entry = this.pending.get(id);
4780
- if (!entry) return;
4781
- this.pending.delete(id);
4782
- this.write({ type: "cancel", id });
4783
- this.cleanupPending(entry);
4784
- entry.reject(cancellationError(signal));
4785
- } : void 0;
4786
- this.pending.set(id, {
4787
- resolve: resolve4,
4788
- reject,
4789
- timer,
4790
- signal,
4791
- onAbort,
4792
- onProgress: options.onProgress
4793
- });
4794
- if (signal && onAbort) {
4795
- signal.addEventListener("abort", onAbort, { once: true });
4796
- if (signal.aborted) {
4797
- onAbort();
4798
- return;
4804
+ let entries;
4805
+ try {
4806
+ entries = await fs10.readdir(dir, { withFileTypes: true });
4807
+ } catch (err) {
4808
+ complete = false;
4809
+ errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
4810
+ return;
4811
+ }
4812
+ dirCount++;
4813
+ for (const e of entries) {
4814
+ if (ignoreSet.has(e.name)) continue;
4815
+ const full = path12.join(dir, e.name);
4816
+ const rel = path12.relative(projectRoot, full).replace(/\\/g, "/");
4817
+ if (e.isDirectory()) {
4818
+ if (isGitIgnored(rel, true)) continue;
4819
+ await walk(full);
4820
+ } else if (e.isFile()) {
4821
+ if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
4822
+ const ext = path12.extname(e.name).toLowerCase();
4823
+ if (indexableExts.has(ext) || detectLang(full) !== null) {
4824
+ results.push(full);
4799
4825
  }
4800
4826
  }
4801
- this.write({ ...message, id });
4827
+ }
4828
+ };
4829
+ await walk(projectRoot);
4830
+ return { files: results, complete, errors };
4831
+ }
4832
+ function assignRefsToSymbols2(refs, symbols) {
4833
+ if (refs.length === 0 || symbols.length === 0) return [];
4834
+ const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
4835
+ const seen = /* @__PURE__ */ new Set();
4836
+ const assigned = [];
4837
+ for (const ref of refs) {
4838
+ let owner;
4839
+ for (const symbol of ordered) {
4840
+ if (symbol.line > ref.line) break;
4841
+ owner = symbol;
4842
+ }
4843
+ if (!owner && ref.callType === "import") owner = ordered[0];
4844
+ if (!owner || owner.id <= 0) continue;
4845
+ const key = `${owner.id}:${ref.toName}:${ref.callType}`;
4846
+ if (seen.has(key)) continue;
4847
+ seen.add(key);
4848
+ assigned.push({ ...ref, fromId: owner.id });
4849
+ }
4850
+ return assigned;
4851
+ }
4852
+ async function runIndexer(_ctx, opts) {
4853
+ const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
4854
+ try {
4855
+ return await runIndexerWithStore(store, opts);
4856
+ } finally {
4857
+ try {
4858
+ store.close();
4859
+ } catch {
4860
+ }
4861
+ }
4862
+ }
4863
+ async function runIndexerWithStore(store, opts) {
4864
+ const { projectRoot, langs, ignore = [], signal } = opts;
4865
+ const relationGraphVersion = "2";
4866
+ const refResolutionVersion = "2";
4867
+ const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
4868
+ const needsFullRefResolution = force || store.getMetadata("ref_resolution_version") !== refResolutionVersion;
4869
+ const startMs = Date.now();
4870
+ const errors = [];
4871
+ const langStats = {};
4872
+ let filesIndexed = 0;
4873
+ let symbolsIndexed = 0;
4874
+ const isGitIgnored = await loadGitignoreMatcher(projectRoot);
4875
+ let files;
4876
+ let discoveredFiles = null;
4877
+ let discoveryComplete = true;
4878
+ let trustedUnchanged;
4879
+ if (opts.files && opts.files.length > 0) {
4880
+ files = opts.files.map((f) => path12.resolve(projectRoot, f)).filter((f) => {
4881
+ if (!isWithinProject(projectRoot, f)) return false;
4882
+ const rel = path12.relative(projectRoot, f).replace(/\\/g, "/");
4883
+ return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path12.basename(f)) && !isGitIgnored(rel, false);
4802
4884
  });
4885
+ } else {
4886
+ const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
4887
+ files = discovery.files;
4888
+ errors.push(...discovery.errors);
4889
+ discoveryComplete = discovery.complete;
4890
+ discoveredFiles = new Set(files);
4891
+ trustedUnchanged = discovery.trustedUnchanged;
4803
4892
  }
4804
- async ensureConnected(spawnIfMissing) {
4805
- if (this.socket && !this.socket.destroyed && this.info) return;
4806
- if (this.connecting) return this.connecting;
4807
- this.transition("connecting");
4808
- this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
4809
- this.transition("error", { error });
4810
- throw error;
4811
- }).finally(() => {
4812
- this.connecting = null;
4893
+ if (langs && langs.length > 0) {
4894
+ const langSet = new Set(langs);
4895
+ files = files.filter((f) => {
4896
+ const lang = detectLang(f);
4897
+ return lang ? langSet.has(lang) : false;
4813
4898
  });
4814
- return this.connecting;
4815
4899
  }
4816
- async connectWithElection(spawnIfMissing) {
4817
- const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
4818
- let spawned = false;
4819
- let staleAttempts = 0;
4820
- let lastError = new Error("codebase-index server unavailable");
4821
- while (Date.now() < deadline) {
4822
- try {
4823
- await this.connectOnce();
4824
- return;
4825
- } catch (error) {
4826
- lastError = error;
4827
- if (error instanceof StaleProjectIndexServerError) {
4828
- staleAttempts++;
4829
- if (!spawnIfMissing) break;
4830
- if (staleAttempts >= 3) this.forceKillServer(error.pid);
4831
- spawned = false;
4832
- await delay(100);
4833
- continue;
4834
- }
4835
- }
4836
- if (!spawnIfMissing) break;
4837
- if (!spawned) {
4838
- this.spawnDetachedServer();
4839
- spawned = true;
4840
- }
4841
- await delay(75);
4842
- }
4843
- throw lastError;
4900
+ if (force) store.clearAll();
4901
+ const existingMeta = /* @__PURE__ */ new Map();
4902
+ if (!force) {
4903
+ for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
4844
4904
  }
4845
- connectOnce() {
4846
- this.socket?.destroy();
4847
- this.socket = null;
4848
- this.info = null;
4849
- this.activity = null;
4850
- this.health = null;
4851
- this.buffer = "";
4852
- return new Promise((resolve4, reject) => {
4853
- const socket = net.createConnection(this.endpoint);
4854
- this.socket = socket;
4855
- socket.setEncoding("utf8");
4856
- const timer = setTimeout(() => {
4857
- reject(new Error("codebase-index server handshake timed out"));
4858
- socket.destroy();
4859
- }, CONNECT_ATTEMPT_TIMEOUT_MS);
4860
- timer.unref?.();
4861
- const finishResolve = () => {
4862
- clearTimeout(timer);
4863
- this.connectResolve = null;
4864
- this.connectReject = null;
4865
- resolve4();
4866
- };
4867
- const finishReject = (error) => {
4868
- clearTimeout(timer);
4869
- this.connectResolve = null;
4870
- this.connectReject = null;
4871
- reject(error);
4872
- };
4873
- this.connectResolve = finishResolve;
4874
- this.connectReject = finishReject;
4875
- socket.on("data", (chunk) => this.onData(socket, chunk));
4876
- socket.on("error", (error) => {
4877
- if (!this.info) finishReject(error);
4878
- });
4879
- socket.on("close", () => this.onClose(socket));
4905
+ const totalFilesForProgress = files.length;
4906
+ let filesPreSkipped = 0;
4907
+ if (!force && trustedUnchanged) {
4908
+ files = files.filter((file) => {
4909
+ const meta = existingMeta.get(file);
4910
+ if (!meta || !trustedUnchanged.has(file)) return true;
4911
+ langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
4912
+ symbolsIndexed += meta.symbolCount;
4913
+ filesIndexed++;
4914
+ filesPreSkipped++;
4915
+ return false;
4880
4916
  });
4917
+ if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
4881
4918
  }
4882
- onData(socket, chunk) {
4883
- if (socket !== this.socket) return;
4884
- this.buffer += chunk;
4885
- while (true) {
4886
- const newline = this.buffer.indexOf("\n");
4887
- if (newline < 0) {
4888
- if (this.buffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
4889
- socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
4919
+ const parallelBatch = resolveParallelBatch();
4920
+ let filesSinceLastYield = 0;
4921
+ for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
4922
+ const batchEnd = Math.min(batchStart + parallelBatch, files.length);
4923
+ const batchFiles = files.slice(batchStart, batchEnd);
4924
+ opts.onProgress?.(filesPreSkipped + batchEnd, totalFilesForProgress);
4925
+ filesSinceLastYield += batchFiles.length;
4926
+ if (filesSinceLastYield >= YIELD_EVERY_N) {
4927
+ filesSinceLastYield = 0;
4928
+ await yieldEventLoop();
4929
+ if (isFrugalPerf()) {
4930
+ await new Promise((r) => setTimeout(r, 8));
4931
+ }
4932
+ throwIfAborted(signal);
4933
+ }
4934
+ const statOpts = signal ? { signal } : {};
4935
+ const statReadParse = await Promise.allSettled(
4936
+ batchFiles.map(
4937
+ async (file) => {
4938
+ let stat2;
4939
+ try {
4940
+ stat2 = await fs10.stat(file, statOpts);
4941
+ } catch (e) {
4942
+ if (isAbortError(e)) throw e;
4943
+ return {
4944
+ file,
4945
+ stat: null,
4946
+ lang: "",
4947
+ parsed: null,
4948
+ error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
4949
+ missing: isMissingPathError(e)
4950
+ };
4951
+ }
4952
+ if (!stat2.isFile()) return { file, stat: stat2, lang: "", parsed: null };
4953
+ const lang = detectLang(file);
4954
+ if (!lang) return { file, stat: stat2, lang: "", parsed: null };
4955
+ if (stat2.size > MAX_INDEX_FILE_BYTES) {
4956
+ return {
4957
+ file,
4958
+ stat: stat2,
4959
+ lang,
4960
+ parsed: null,
4961
+ error: `file too large (${stat2.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
4962
+ };
4963
+ }
4964
+ const meta = existingMeta.get(file);
4965
+ if (!force && meta && meta.mtimeMs === Math.floor(stat2.mtimeMs)) {
4966
+ return { file, stat: stat2, lang, parsed: null, skippedMeta: meta };
4967
+ }
4968
+ let content;
4969
+ try {
4970
+ content = await fs10.readFile(file, { encoding: "utf8", signal });
4971
+ } catch (e) {
4972
+ if (isAbortError(e)) throw e;
4973
+ return {
4974
+ file,
4975
+ stat: stat2,
4976
+ lang,
4977
+ parsed: null,
4978
+ error: `read error: ${e instanceof Error ? e.message : String(e)}`
4979
+ };
4980
+ }
4981
+ let parsed;
4982
+ try {
4983
+ parsed = await parseFileContent(file, content, lang);
4984
+ } catch (e) {
4985
+ return {
4986
+ file,
4987
+ stat: stat2,
4988
+ lang,
4989
+ parsed: null,
4990
+ error: `parse error: ${e instanceof Error ? e.message : String(e)}`
4991
+ };
4992
+ }
4993
+ return { file, stat: stat2, lang, parsed, content };
4890
4994
  }
4891
- return;
4995
+ )
4996
+ );
4997
+ const batchEntries = [];
4998
+ const deleteForFiles = [];
4999
+ for (let fi = 0; fi < statReadParse.length; fi++) {
5000
+ const settled = statReadParse[fi];
5001
+ const file = expectDefined5(batchFiles[fi]);
5002
+ if (settled.status === "rejected") {
5003
+ const err = settled.reason;
5004
+ if (err instanceof Error && isAbortError(err)) throw err;
5005
+ errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
5006
+ continue;
4892
5007
  }
4893
- if (newline > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
4894
- socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
4895
- return;
5008
+ const result = settled.value;
5009
+ if (result.error) {
5010
+ if (result.missing) store.deleteFile(file);
5011
+ errors.push(`${file}: ${result.error}`);
5012
+ continue;
4896
5013
  }
4897
- const line = this.buffer.slice(0, newline);
4898
- this.buffer = this.buffer.slice(newline + 1);
4899
- if (!line) continue;
4900
- let message;
4901
- try {
4902
- message = JSON.parse(line);
4903
- } catch {
4904
- socket.destroy(new Error("invalid codebase-index server response"));
4905
- return;
5014
+ const { stat: stat2, lang, parsed } = result;
5015
+ if (result.skippedMeta) {
5016
+ langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
5017
+ symbolsIndexed += result.skippedMeta.symbolCount;
5018
+ filesIndexed++;
5019
+ continue;
4906
5020
  }
4907
- this.onMessage(message);
4908
- }
4909
- }
4910
- onMessage(message) {
4911
- if (message.type === "hello") {
4912
- if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
4913
- this.rejectStaleServer(
4914
- message,
4915
- `codebase-index protocol mismatch: client=${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
4916
- );
4917
- return;
5021
+ if (!lang || !parsed) {
5022
+ if (lang) {
5023
+ store.upsertFile({
5024
+ file,
5025
+ lang,
5026
+ mtimeMs: Math.floor(stat2.mtimeMs),
5027
+ symbolCount: 0,
5028
+ lastIndexed: Date.now()
5029
+ });
5030
+ filesIndexed++;
5031
+ }
5032
+ continue;
4918
5033
  }
4919
- const expectedBuildId = projectIndexServerExpectedBuildId();
4920
- if (expectedBuildId && message.buildId !== expectedBuildId) {
4921
- this.rejectStaleServer(
4922
- message,
4923
- `codebase-index build mismatch: client=${expectedBuildId}, server=${message.buildId ?? "legacy"}`
4924
- );
4925
- return;
5034
+ if (parsed.symbols.length === 0) {
5035
+ store.replaceEmptyFile({
5036
+ file,
5037
+ lang,
5038
+ mtimeMs: Math.floor(stat2.mtimeMs),
5039
+ symbolCount: 0,
5040
+ lastIndexed: Date.now()
5041
+ });
5042
+ filesIndexed++;
5043
+ continue;
4926
5044
  }
4927
- this.info = message;
4928
- this.markResponsive();
4929
- this.transition("connected", { pid: message.pid });
4930
- ensureHeartbeatLoop();
4931
- this.connectResolve?.();
4932
- return;
4933
- }
4934
- if (message.type === "index-state") {
4935
- this.activity = message.state;
4936
- this.markResponsive();
4937
- this.transition("connected", { pid: this.info?.pid });
4938
- return;
4939
- }
4940
- const entry = this.pending.get(message.id);
4941
- if (!entry) return;
4942
- this.markResponsive();
4943
- const status = connectionStates.get(this.endpoint)?.status;
4944
- if (status === "degraded" || status === "unresponsive") {
4945
- this.transition("connected", { pid: this.info?.pid });
4946
- }
4947
- if (message.type === "progress") {
4948
- entry.onProgress?.(message.current, message.total);
4949
- return;
4950
- }
4951
- this.pending.delete(message.id);
4952
- this.cleanupPending(entry);
4953
- if (message.ok) entry.resolve(message.result);
4954
- else entry.reject(remoteError(message.error, message.errorName));
4955
- }
4956
- onClose(socket) {
4957
- if (socket !== this.socket) return;
4958
- const wasConnected = this.info !== null;
4959
- this.socket = null;
4960
- this.info = null;
4961
- this.activity = null;
4962
- this.health = null;
4963
- const error = new Error("codebase-index server connection closed");
4964
- this.connectReject?.(error);
4965
- this.connectResolve = null;
4966
- this.connectReject = null;
4967
- this.rejectPending(error);
4968
- if (wasConnected) this.transition("error", { error });
4969
- maybeStopHeartbeatLoop();
4970
- }
4971
- cleanupPending(entry) {
4972
- clearTimeout(entry.timer);
4973
- if (entry.signal && entry.onAbort) {
4974
- entry.signal.removeEventListener("abort", entry.onAbort);
4975
- }
4976
- }
4977
- rejectPending(error) {
4978
- const entries = [...this.pending.values()];
4979
- this.pending.clear();
4980
- for (const entry of entries) {
4981
- this.cleanupPending(entry);
4982
- entry.reject(error);
4983
- }
4984
- }
4985
- write(message) {
4986
- const socket = this.socket;
4987
- if (socket && !socket.destroyed) socket.write(encodeProjectServerMessage(message));
4988
- }
4989
- rejectStaleServer(message, reason) {
4990
- const socket = this.socket;
4991
- if (socket && !socket.destroyed) {
4992
- socket.write(
4993
- encodeProjectServerMessage({
4994
- type: "shutdown",
4995
- id: 0,
4996
- reason: "stale-build-replacement"
4997
- })
4998
- );
4999
- const timer = setTimeout(() => socket.destroy(), 25);
5000
- timer.unref?.();
5045
+ batchEntries.push({
5046
+ file,
5047
+ lang,
5048
+ symbols: parsed.symbols,
5049
+ refs: parsed.refs ?? [],
5050
+ mtimeMs: Math.floor(stat2.mtimeMs),
5051
+ symbolCount: parsed.symbols.length
5052
+ });
5053
+ deleteForFiles.push(file);
5001
5054
  }
5002
- this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
5003
- }
5004
- spawnDetachedServer() {
5005
- const url = resolveProjectServerUrl();
5006
- if (!url) throw new Error("built codebase-index project server is unavailable");
5007
- if (process.platform !== "win32") {
5055
+ if (batchEntries.length > 0) {
5008
5056
  try {
5009
- fs10.rmSync(this.endpoint, { force: true });
5010
- } catch {
5057
+ store.commitBatch(batchEntries, { deleteForFiles });
5058
+ for (const entry of batchEntries) {
5059
+ const count = entry.symbols.length;
5060
+ symbolsIndexed += count;
5061
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
5062
+ filesIndexed++;
5063
+ }
5064
+ } catch (err) {
5065
+ const message = err instanceof Error ? err.message : String(err);
5066
+ errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
5067
+ for (const entry of batchEntries) {
5068
+ try {
5069
+ store.deleteRefsForFile(entry.file);
5070
+ store.deleteSymbolsForFile(entry.file);
5071
+ const symbolsWithIds = store.insertSymbols(entry.symbols);
5072
+ symbolsIndexed += symbolsWithIds.length;
5073
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
5074
+ filesIndexed++;
5075
+ if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
5076
+ const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
5077
+ if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
5078
+ }
5079
+ store.resolveRefsForNames([
5080
+ ...entry.symbols.map((symbol) => symbol.name),
5081
+ ...entry.refs.map((ref) => ref.toName)
5082
+ ]);
5083
+ store.upsertFile({
5084
+ file: entry.file,
5085
+ lang: entry.lang,
5086
+ mtimeMs: entry.mtimeMs,
5087
+ symbolCount: entry.symbolCount,
5088
+ lastIndexed: Date.now()
5089
+ });
5090
+ } catch (innerErr) {
5091
+ errors.push(
5092
+ `fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
5093
+ );
5094
+ }
5095
+ }
5011
5096
  }
5012
5097
  }
5013
- const args = [fileURLToPath2(url), "--project-root", this.projectRoot];
5014
- if (this.indexDir) args.push("--index-dir", this.indexDir);
5015
- const child = spawn4(process.execPath, args, {
5016
- detached: true,
5017
- stdio: "ignore",
5018
- windowsHide: true,
5019
- env: process.env
5020
- });
5021
- child.unref();
5022
- }
5023
- forceKillKnownServer() {
5024
- const pid = this.info?.pid;
5025
- return pid ? this.forceKillServer(pid) : false;
5026
5098
  }
5027
- forceKillServer(pid) {
5028
- if (pid === process.pid) return false;
5029
- try {
5030
- process.kill(pid);
5031
- const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
5032
- try {
5033
- const metadata = JSON.parse(fs10.readFileSync(metadataPath, "utf8"));
5034
- if (metadata.pid === pid) fs10.rmSync(metadataPath, { force: true });
5035
- } catch {
5099
+ if (discoveredFiles && discoveryComplete) {
5100
+ for (const [file_] of existingMeta) {
5101
+ if (!discoveredFiles.has(file_)) {
5102
+ store.deleteFile(file_);
5036
5103
  }
5037
- return true;
5038
- } catch {
5039
- return false;
5040
5104
  }
5041
5105
  }
5042
- };
5043
- var connections = /* @__PURE__ */ new Map();
5044
- var heartbeatTimer;
5045
- function ensureHeartbeatLoop() {
5046
- if (heartbeatTimer) return;
5047
- heartbeatTimer = setInterval(() => {
5048
- for (const connection of connections.values()) {
5049
- if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
5050
- });
5051
- }
5052
- }, SERVER_HEARTBEAT_INTERVAL_MS);
5053
- heartbeatTimer.unref?.();
5054
- }
5055
- function maybeStopHeartbeatLoop() {
5056
- if (!heartbeatTimer) return;
5057
- if ([...connections.values()].some((connection) => connection.isConnected())) return;
5058
- clearInterval(heartbeatTimer);
5059
- heartbeatTimer = void 0;
5106
+ if (needsFullRefResolution) store.resolveRefs();
5107
+ store.setMetadata("ref_resolution_version", refResolutionVersion);
5108
+ store.setMetadata("relation_graph_version", relationGraphVersion);
5109
+ if (!opts.files || filesIndexed >= 50) store.optimize();
5110
+ store.setLastIndexed(Date.now());
5111
+ if (!opts.files) store.compactIfNeeded();
5112
+ const durationMs = Date.now() - startMs;
5113
+ return {
5114
+ filesIndexed,
5115
+ symbolsIndexed,
5116
+ langStats,
5117
+ durationMs,
5118
+ errors
5119
+ };
5060
5120
  }
5061
- function connectionFor(projectRoot, indexDir) {
5062
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
5063
- let connection = connections.get(endpoint);
5064
- if (!connection) {
5065
- connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
5066
- connections.set(endpoint, connection);
5121
+
5122
+ // src/codebase-index/index-service.ts
5123
+ async function indexService(args, hooks = {}) {
5124
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
5125
+ try {
5126
+ return await runIndexerWithStore(store, {
5127
+ projectRoot: args.projectRoot,
5128
+ indexDir: args.indexDir,
5129
+ files: args.files,
5130
+ force: args.force,
5131
+ langs: args.langs,
5132
+ ignore: args.ignore,
5133
+ signal: hooks.signal,
5134
+ onProgress: hooks.onProgress
5135
+ });
5136
+ } finally {
5137
+ indexStorePool.release(store);
5067
5138
  }
5068
- return connection;
5069
5139
  }
5070
- function callProjectIndexServer(op, args, options) {
5071
- return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
5140
+ function searchService(args) {
5141
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
5142
+ try {
5143
+ return store.searchRanked(
5144
+ args.query,
5145
+ {
5146
+ kind: args.kind,
5147
+ lang: args.lang,
5148
+ file: args.file,
5149
+ lspKind: args.lspKind
5150
+ },
5151
+ args.limit
5152
+ );
5153
+ } finally {
5154
+ indexStorePool.release(store);
5155
+ }
5072
5156
  }
5073
- function ensureProjectIndexServer(options) {
5074
- return connectionFor(options.projectRoot, options.indexDir).configure(
5075
- options.watchExternal,
5076
- options.debounceMs
5077
- );
5157
+ function statsService(args) {
5158
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
5159
+ try {
5160
+ return store.getStats();
5161
+ } finally {
5162
+ indexStorePool.release(store);
5163
+ }
5078
5164
  }
5079
- function checkProjectIndexServerHealth(projectRoot, indexDir, options = {}) {
5080
- return connectionFor(projectRoot, indexDir).checkHealth(
5081
- false,
5082
- options.timeoutMs ?? SERVER_HEALTH_TIMEOUT_MS
5083
- );
5165
+ function packageGraphService(args) {
5166
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
5167
+ try {
5168
+ return store.getPackageGraph();
5169
+ } finally {
5170
+ indexStorePool.release(store);
5171
+ }
5084
5172
  }
5085
- async function shutdownProjectIndexServer(projectRoot, indexDir, reason) {
5086
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
5087
- const connection = connectionFor(projectRoot, indexDir);
5173
+ function fileGraphService(args) {
5174
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
5088
5175
  try {
5089
- return await connection.shutdownRemote(reason);
5176
+ return store.getFileGraph(args.packageFilter);
5090
5177
  } finally {
5091
- connection.close();
5092
- connections.delete(endpoint);
5093
- connectionStates.delete(endpoint);
5178
+ indexStorePool.release(store);
5094
5179
  }
5095
5180
  }
5096
- function closeProjectIndexServerClients() {
5097
- for (const connection of connections.values()) connection.close();
5098
- connections.clear();
5099
- connectionStates.clear();
5100
- latestConnectionState = {
5101
- status: isProjectIndexServerAvailable() ? "offline" : "unavailable",
5102
- connected: false
5103
- };
5104
- if (heartbeatTimer) clearInterval(heartbeatTimer);
5105
- heartbeatTimer = void 0;
5181
+ function symbolGraphService(args) {
5182
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
5183
+ try {
5184
+ return store.getSymbolGraph(args.fileFilter);
5185
+ } finally {
5186
+ indexStorePool.release(store);
5187
+ }
5106
5188
  }
5107
5189
 
5108
5190
  // src/codebase-index/background-indexer.ts
5191
+ init_languages();
5109
5192
  var DEFAULT_FULL_INDEX_TIMEOUT_MS = 24e4;
5110
5193
  var DEFAULT_INCREMENTAL_TIMEOUT_MS = 6e4;
5111
5194
  var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
@@ -5238,8 +5321,17 @@ async function shutdownCodebaseIndexHost() {
5238
5321
  }
5239
5322
  }
5240
5323
  }
5324
+ var warnedInvalidEndpoints = /* @__PURE__ */ new Set();
5325
+ function warnEndpointInvalidOnce(availability) {
5326
+ if (warnedInvalidEndpoints.has(availability.endpoint)) return;
5327
+ warnedInvalidEndpoints.add(availability.endpoint);
5328
+ process.stderr.write(
5329
+ `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.
5330
+ `
5331
+ );
5332
+ }
5241
5333
  function callIndexOp(op, args, opts) {
5242
- const availability = resolveProjectIndexDaemonAvailability();
5334
+ const availability = resolveProjectIndexDaemonAvailability(args.projectRoot, args.indexDir);
5243
5335
  if (availability.kind === "available") {
5244
5336
  return callProjectIndexServer(op, args, opts);
5245
5337
  }
@@ -5250,6 +5342,14 @@ function callIndexOp(op, args, opts) {
5250
5342
  )
5251
5343
  );
5252
5344
  }
5345
+ if (availability.kind === "endpoint-invalid") {
5346
+ warnEndpointInvalidOnce(availability);
5347
+ return Promise.reject(
5348
+ new Error(
5349
+ `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.`
5350
+ )
5351
+ );
5352
+ }
5253
5353
  const w = ensureWorker();
5254
5354
  if (!w) return callInline(op, args, opts);
5255
5355
  if (opts.signal?.aborted) {
@@ -5523,7 +5623,11 @@ function checkCodebaseIndexServerHealth(projectRoot, indexDir, options = {}) {
5523
5623
  return checkProjectIndexServerHealth(projectRoot, indexDir, options);
5524
5624
  }
5525
5625
  function ensureCodebaseIndexServer(options) {
5526
- if (!isProjectIndexServerAvailable()) return Promise.resolve();
5626
+ const availability = resolveProjectIndexDaemonAvailability(options.projectRoot, options.indexDir);
5627
+ if (availability.kind !== "available") {
5628
+ if (availability.kind === "endpoint-invalid") warnEndpointInvalidOnce(availability);
5629
+ return Promise.resolve();
5630
+ }
5527
5631
  return ensureProjectIndexServer({
5528
5632
  projectRoot: options.projectRoot,
5529
5633
  indexDir: options.indexDir,
@@ -5808,6 +5912,7 @@ var codebaseStatsTool = {
5808
5912
  };
5809
5913
 
5810
5914
  // src/codebase-index/dead-code-scan.ts
5915
+ init_languages();
5811
5916
  import * as fs12 from "node:fs";
5812
5917
  import * as path13 from "node:path";
5813
5918
  var deadCodeScanTool = {
@@ -5876,7 +5981,15 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
5876
5981
  if (rootPkg) {
5877
5982
  addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
5878
5983
  }
5879
- const workspaces = rootPkg ? extractWorkspaceGlobs(rootPkg, projectRoot) : [];
5984
+ let workspaces;
5985
+ if (rootPkg) {
5986
+ workspaces = extractWorkspaceGlobs(rootPkg, projectRoot);
5987
+ if (workspaces.length === 0) {
5988
+ workspaces = extractPnpmWorkspaceDirs(projectRoot);
5989
+ }
5990
+ } else {
5991
+ workspaces = [];
5992
+ }
5880
5993
  for (const wsDir of workspaces) {
5881
5994
  const pkgJsonPath = path13.join(wsDir, "package.json");
5882
5995
  const pkg = tryReadJson(pkgJsonPath);
@@ -5898,77 +6011,189 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
5898
6011
  }
5899
6012
  return [...entries];
5900
6013
  }
6014
+ var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
6015
+ var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path13.sep}${d}${path13.sep}`);
6016
+ function trySourceEquivalent(resolved) {
6017
+ resolved = resolved.replace(/[/\\]/g, path13.sep);
6018
+ for (const marker of BUILD_OUTPUT_DIR_NAMES) {
6019
+ const idx = resolved.indexOf(marker);
6020
+ if (idx === -1) continue;
6021
+ const base = resolved.replace(marker, `${path13.sep}src${path13.sep}`);
6022
+ const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
6023
+ if (candidate !== base && fs12.existsSync(candidate)) {
6024
+ return candidate;
6025
+ }
6026
+ const dtsStripped = base.replace(/\.d\.ts$/, "");
6027
+ const candidateDts = dtsStripped + ".ts";
6028
+ if (candidateDts !== base && candidateDts !== candidate && fs12.existsSync(candidateDts)) {
6029
+ return candidateDts;
6030
+ }
6031
+ const candidateNoExt = base + ".ts";
6032
+ if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs12.existsSync(candidateNoExt)) {
6033
+ return candidateNoExt;
6034
+ }
6035
+ }
6036
+ return null;
6037
+ }
6038
+ function tryAddEntryPath(pkgDir, rawPath, entries) {
6039
+ const resolved = resolveAgainst(pkgDir, rawPath);
6040
+ if (fs12.existsSync(resolved)) entries.add(resolved);
6041
+ const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
6042
+ if (tsResolved !== resolved && fs12.existsSync(tsResolved)) {
6043
+ entries.add(tsResolved);
6044
+ }
6045
+ const srcAlt = trySourceEquivalent(resolved);
6046
+ if (srcAlt) entries.add(srcAlt);
6047
+ }
5901
6048
  function addPkgJsonEntryPoints(pkgDir, pkg, entries) {
5902
6049
  if (typeof pkg.main === "string") {
5903
- const resolved = resolveAgainst(pkgDir, pkg.main);
5904
- if (fs12.existsSync(resolved)) entries.add(resolved);
5905
- const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
5906
- if (tsResolved !== resolved && fs12.existsSync(tsResolved)) {
5907
- entries.add(tsResolved);
5908
- }
6050
+ tryAddEntryPath(pkgDir, pkg.main, entries);
5909
6051
  }
5910
6052
  const bin = pkg.bin;
5911
6053
  if (typeof bin === "string") {
5912
- const resolved = resolveAgainst(pkgDir, bin);
5913
- if (fs12.existsSync(resolved)) entries.add(resolved);
6054
+ tryAddEntryPath(pkgDir, bin, entries);
5914
6055
  } else if (bin && typeof bin === "object") {
5915
6056
  for (const value of Object.values(bin)) {
5916
6057
  if (typeof value === "string") {
5917
- const resolved = resolveAgainst(pkgDir, value);
5918
- if (fs12.existsSync(resolved)) entries.add(resolved);
6058
+ tryAddEntryPath(pkgDir, value, entries);
5919
6059
  }
5920
6060
  }
5921
6061
  }
5922
6062
  for (const key of ["types", "typings"]) {
5923
6063
  if (typeof pkg[key] === "string") {
5924
- const resolved = resolveAgainst(pkgDir, pkg[key]);
5925
- if (fs12.existsSync(resolved)) entries.add(resolved);
6064
+ tryAddEntryPath(pkgDir, pkg[key], entries);
5926
6065
  }
5927
6066
  }
5928
6067
  const exports_ = pkg.exports;
5929
6068
  if (exports_ && typeof exports_ === "object") {
5930
6069
  for (const value of Object.values(exports_)) {
5931
6070
  if (typeof value === "string") {
5932
- const resolved = resolveAgainst(pkgDir, value);
5933
- if (fs12.existsSync(resolved)) entries.add(resolved);
6071
+ tryAddEntryPath(pkgDir, value, entries);
5934
6072
  } else if (value && typeof value === "object") {
5935
- for (const nested of Object.values(
5936
- value
5937
- )) {
6073
+ for (const nested of Object.values(value)) {
5938
6074
  if (typeof nested === "string") {
5939
- const resolved = resolveAgainst(pkgDir, nested);
5940
- if (fs12.existsSync(resolved)) entries.add(resolved);
6075
+ tryAddEntryPath(pkgDir, nested, entries);
5941
6076
  }
5942
6077
  }
5943
6078
  }
5944
6079
  }
5945
6080
  }
5946
6081
  }
6082
+ function expandGlobPattern(entry, projectRoot) {
6083
+ const dirs = [];
6084
+ if (entry.includes("*")) {
6085
+ const base = entry.replace(/\/\*+$/, "");
6086
+ const baseDir = path13.resolve(projectRoot, base);
6087
+ try {
6088
+ const children = fs12.readdirSync(baseDir, { withFileTypes: true });
6089
+ for (const child of children) {
6090
+ if (child.isDirectory()) {
6091
+ dirs.push(path13.join(baseDir, child.name));
6092
+ }
6093
+ }
6094
+ } catch {
6095
+ }
6096
+ } else {
6097
+ dirs.push(path13.resolve(projectRoot, entry));
6098
+ }
6099
+ return dirs;
6100
+ }
5947
6101
  function extractWorkspaceGlobs(pkg, projectRoot) {
5948
6102
  const dirs = [];
5949
6103
  const workspaces = pkg.workspaces;
5950
6104
  if (Array.isArray(workspaces)) {
5951
6105
  for (const entry of workspaces) {
5952
6106
  if (typeof entry === "string") {
5953
- if (entry.includes("*")) {
5954
- const base = entry.replace(/\/\*+$/, "");
5955
- const baseDir = path13.resolve(projectRoot, base);
5956
- try {
5957
- const children = fs12.readdirSync(baseDir, { withFileTypes: true });
5958
- for (const child of children) {
5959
- if (child.isDirectory()) {
5960
- dirs.push(path13.join(baseDir, child.name));
5961
- }
5962
- }
5963
- } catch {
6107
+ dirs.push(...expandGlobPattern(entry, projectRoot));
6108
+ }
6109
+ }
6110
+ }
6111
+ return dirs;
6112
+ }
6113
+ function extractPnpmWorkspaceDirs(projectRoot) {
6114
+ const yamlPath = path13.join(projectRoot, "pnpm-workspace.yaml");
6115
+ if (!fs12.existsSync(yamlPath)) return [];
6116
+ try {
6117
+ const content = fs12.readFileSync(yamlPath, "utf8");
6118
+ const dirs = [];
6119
+ let inPackages = false;
6120
+ const lines = content.split("\n");
6121
+ const itemRe = /^\s+-\s+"([^"]+)"|^\s+-\s+'([^']+)'|^\s+-\s+(\S+)/;
6122
+ for (const line of lines) {
6123
+ const trimmed = line.trim();
6124
+ if (/^packages\s*:\s*$/.test(trimmed)) {
6125
+ inPackages = true;
6126
+ continue;
6127
+ }
6128
+ if (inPackages && trimmed.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) {
6129
+ if (!trimmed.startsWith("-")) {
6130
+ inPackages = false;
6131
+ continue;
6132
+ }
6133
+ }
6134
+ if (inPackages) {
6135
+ const m = itemRe.exec(line);
6136
+ if (m) {
6137
+ const entry = m[1] ?? m[2] ?? m[3];
6138
+ if (entry) {
6139
+ dirs.push(...expandGlobPattern(entry, projectRoot));
5964
6140
  }
5965
- } else {
5966
- dirs.push(path13.resolve(projectRoot, entry));
5967
6141
  }
5968
6142
  }
5969
6143
  }
6144
+ return dirs;
6145
+ } catch {
6146
+ return [];
5970
6147
  }
5971
- return dirs;
6148
+ }
6149
+ function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
6150
+ if (!moduleSpecifier.startsWith(".")) return [];
6151
+ const dir = path13.dirname(importerPath);
6152
+ const base = path13.resolve(dir, moduleSpecifier);
6153
+ const results = [];
6154
+ const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
6155
+ const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
6156
+ const candidates = skipBase ? [stripped] : [base];
6157
+ for (const candidate of candidates) {
6158
+ if (indexedFiles.has(candidate + ".ts")) results.push(candidate + ".ts");
6159
+ if (indexedFiles.has(candidate + ".tsx")) results.push(candidate + ".tsx");
6160
+ if (indexedFiles.has(candidate + ".js")) results.push(candidate + ".js");
6161
+ if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
6162
+ if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
6163
+ if (indexedFiles.has(candidate + ".cjs")) results.push(candidate + ".cjs");
6164
+ if (indexedFiles.has(path13.join(candidate, "index.ts")))
6165
+ results.push(path13.join(candidate, "index.ts"));
6166
+ if (indexedFiles.has(path13.join(candidate, "index.tsx")))
6167
+ results.push(path13.join(candidate, "index.tsx"));
6168
+ if (indexedFiles.has(path13.join(candidate, "index.js")))
6169
+ results.push(path13.join(candidate, "index.js"));
6170
+ if (indexedFiles.has(path13.join(candidate, "index.jsx")))
6171
+ results.push(path13.join(candidate, "index.jsx"));
6172
+ if (indexedFiles.has(path13.join(candidate, "index.mjs")))
6173
+ results.push(path13.join(candidate, "index.mjs"));
6174
+ if (indexedFiles.has(path13.join(candidate, "index.cjs")))
6175
+ results.push(path13.join(candidate, "index.cjs"));
6176
+ }
6177
+ return [...new Set(results)];
6178
+ }
6179
+ function parseNamedExportSymbols(matchText) {
6180
+ const braceStart = matchText.indexOf("{");
6181
+ if (braceStart === -1) return null;
6182
+ const braceEnd = matchText.indexOf("}", braceStart);
6183
+ if (braceEnd === -1) return null;
6184
+ const inner = matchText.slice(braceStart + 1, braceEnd);
6185
+ const symbols = [];
6186
+ for (const part of inner.split(",")) {
6187
+ let s = part.trim();
6188
+ if (!s) continue;
6189
+ s = s.replace(/^type\s+/, "");
6190
+ const asIdx = s.search(/\s+as\s+/);
6191
+ if (asIdx !== -1) {
6192
+ s = s.slice(0, asIdx).trim();
6193
+ }
6194
+ if (s) symbols.push(s);
6195
+ }
6196
+ return symbols;
5972
6197
  }
5973
6198
  function runDeadCodeScan(projectRoot, opts = {}) {
5974
6199
  const store = opts.store ?? indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
@@ -5990,12 +6215,71 @@ function runDeadCodeScan(projectRoot, opts = {}) {
5990
6215
  }
5991
6216
  const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
5992
6217
  const entryFileSet = new Set(discoveredFiles.map((f) => path13.resolve(f)));
6218
+ const indexedFiles = /* @__PURE__ */ new Set();
6219
+ for (const s of allSymbols) indexedFiles.add(s.file);
6220
+ for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
5993
6221
  const seedIds = /* @__PURE__ */ new Set();
5994
6222
  for (const s of allSymbols) {
5995
6223
  if (entryFileSet.has(s.file)) {
5996
6224
  seedIds.add(s.id);
5997
6225
  }
5998
6226
  }
6227
+ const fileToSymbolIds = /* @__PURE__ */ new Map();
6228
+ for (const s of allSymbols) {
6229
+ let byFile = fileToSymbolIds.get(s.file);
6230
+ if (!byFile) {
6231
+ byFile = [];
6232
+ fileToSymbolIds.set(s.file, byFile);
6233
+ }
6234
+ byFile.push(s.id);
6235
+ }
6236
+ const scannedBarrels = /* @__PURE__ */ new Set();
6237
+ const barrelWorkList = [...entryFileSet];
6238
+ while (barrelWorkList.length > 0) {
6239
+ const epFile = barrelWorkList.pop();
6240
+ if (scannedBarrels.has(epFile)) continue;
6241
+ scannedBarrels.add(epFile);
6242
+ try {
6243
+ const content = fs12.readFileSync(epFile, "utf8");
6244
+ const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
6245
+ const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
6246
+ let match;
6247
+ while ((match = reExportRe.exec(strippedContent)) !== null) {
6248
+ const moduleSpec = match[1];
6249
+ const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
6250
+ for (const rf of resolvedFiles) {
6251
+ const fileSyms = fileToSymbolIds.get(rf);
6252
+ if (fileSyms) {
6253
+ const namedSymbols = parseNamedExportSymbols(match[0]);
6254
+ if (namedSymbols) {
6255
+ const nameSet = new Set(namedSymbols);
6256
+ for (const sid of fileSyms) {
6257
+ const sym = symbolById.get(sid);
6258
+ if (sym && nameSet.has(sym.name)) seedIds.add(sid);
6259
+ }
6260
+ } else {
6261
+ for (const sid of fileSyms) seedIds.add(sid);
6262
+ }
6263
+ }
6264
+ if (!scannedBarrels.has(rf)) {
6265
+ barrelWorkList.push(rf);
6266
+ }
6267
+ }
6268
+ }
6269
+ } catch (err) {
6270
+ if (err instanceof Error && err.code !== "ENOENT") {
6271
+ console.warn(
6272
+ JSON.stringify({
6273
+ level: "warn",
6274
+ event: "dead_code_scan_barrel_read_failed",
6275
+ message: err.message,
6276
+ file: epFile,
6277
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6278
+ })
6279
+ );
6280
+ }
6281
+ }
6282
+ }
5999
6283
  const alive = new Set(seedIds);
6000
6284
  const frontier = [...seedIds];
6001
6285
  const visitedEdges = /* @__PURE__ */ new Set();
@@ -6029,8 +6313,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
6029
6313
  dead.push({
6030
6314
  name: s.name,
6031
6315
  kind: s.kind,
6032
- lang: "ts",
6033
- // populated from symbol file metadata
6316
+ lang: detectLang(s.file) ?? "ts",
6034
6317
  file: s.file,
6035
6318
  line: s.line,
6036
6319
  reason
@@ -6055,16 +6338,14 @@ function runDeadCodeScan(projectRoot, opts = {}) {
6055
6338
  deadFiles.push({
6056
6339
  file,
6057
6340
  symbolCount: syms.length,
6058
- lang: syms[0]?.kind ?? "ts"
6341
+ lang: detectLang(file) ?? "ts"
6059
6342
  });
6060
6343
  }
6061
6344
  }
6062
6345
  const deadPackages = [];
6063
6346
  const pkgEntries = findPackageEntries(projectRoot);
6064
6347
  for (const [pkgName, pkgDir] of pkgEntries) {
6065
- const pkgFiles = allSymbols.filter(
6066
- (s) => s.file.startsWith(pkgDir + path13.sep)
6067
- );
6348
+ const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path13.sep));
6068
6349
  if (pkgFiles.length === 0) continue;
6069
6350
  const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
6070
6351
  if (pkgUsed.length === 0) {
@@ -6105,7 +6386,10 @@ function findPackageEntries(projectRoot) {
6105
6386
  pkgMap.set(rootPkg.name, projectRoot);
6106
6387
  }
6107
6388
  if (rootPkg) {
6108
- const wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
6389
+ let wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
6390
+ if (wsDirs.length === 0) {
6391
+ wsDirs = extractPnpmWorkspaceDirs(projectRoot);
6392
+ }
6109
6393
  for (const wsDir of wsDirs) {
6110
6394
  const wsPkg = tryReadJson(path13.join(wsDir, "package.json"));
6111
6395
  if (wsPkg && typeof wsPkg.name === "string") {
@@ -6151,6 +6435,7 @@ export {
6151
6435
  packageGraphService2 as packageGraphService,
6152
6436
  resetIndexCircuitBreaker,
6153
6437
  resolveIndexDir,
6438
+ resolveProjectIndexDaemonAvailability,
6154
6439
  runDeadCodeScan,
6155
6440
  runIndexer,
6156
6441
  runStartupIndex,