@wrongstack/tools 0.296.3 → 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.
package/dist/builtin.js CHANGED
@@ -4893,16 +4893,16 @@ var init_legacy_bridge = __esm({
4893
4893
  });
4894
4894
 
4895
4895
  // src/codebase-index/languages.ts
4896
- import * as path14 from "node:path";
4896
+ import * as path18 from "node:path";
4897
4897
  function detectLang(file) {
4898
- const base = path14.basename(file);
4898
+ const base = path18.basename(file);
4899
4899
  const lowerBase = base.toLowerCase();
4900
4900
  if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
4901
4901
  return "ts";
4902
4902
  }
4903
4903
  const special = SPECIAL_FILENAMES[lowerBase];
4904
4904
  if (special) return special;
4905
- const ext = path14.extname(base).toLowerCase();
4905
+ const ext = path18.extname(base).toLowerCase();
4906
4906
  if (!ext) return null;
4907
4907
  return EXT_TO_LANG[ext] ?? null;
4908
4908
  }
@@ -5238,10 +5238,10 @@ __export(go_parser_exports, {
5238
5238
  detectLang: () => detectLang,
5239
5239
  parseSymbols: () => parseSymbols2
5240
5240
  });
5241
- import { spawn as spawn4 } from "node:child_process";
5242
- import * as os5 from "node:os";
5243
- import * as path15 from "node:path";
5244
- import * as fs10 from "node:fs/promises";
5241
+ import { spawn as spawn5 } from "node:child_process";
5242
+ import * as os6 from "node:os";
5243
+ import * as path19 from "node:path";
5244
+ import * as fs14 from "node:fs/promises";
5245
5245
  async function parseSymbols2(opts) {
5246
5246
  const { file, content, lang } = opts;
5247
5247
  try {
@@ -5313,16 +5313,16 @@ async function syncGoParse(filePath, content, lang) {
5313
5313
  try {
5314
5314
  let scriptPath = _cachedGoScriptPath;
5315
5315
  if (!scriptPath) {
5316
- const tmpDir = await fs10.mkdtemp(path15.join(os5.tmpdir(), "ws-go-parse-"));
5317
- scriptPath = path15.join(tmpDir, "parse.go");
5318
- await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
5316
+ const tmpDir = await fs14.mkdtemp(path19.join(os6.tmpdir(), "ws-go-parse-"));
5317
+ scriptPath = path19.join(tmpDir, "parse.go");
5318
+ await fs14.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
5319
5319
  _cachedGoScriptPath = scriptPath;
5320
5320
  }
5321
5321
  const goBinary = resolveWin32Command("go");
5322
5322
  const goResult = await new Promise(
5323
5323
  (resolve16, reject) => {
5324
5324
  let settled = false;
5325
- const proc = spawn4(goBinary, ["run", scriptPath], {
5325
+ const proc = spawn5(goBinary, ["run", scriptPath], {
5326
5326
  stdio: ["pipe", "pipe", "pipe"],
5327
5327
  windowsHide: true
5328
5328
  });
@@ -5925,10 +5925,10 @@ __export(py_parser_exports, {
5925
5925
  detectLang: () => detectLang,
5926
5926
  parseSymbols: () => parseSymbols4
5927
5927
  });
5928
- import { spawn as spawn5 } from "node:child_process";
5929
- import * as fs11 from "node:fs/promises";
5930
- import * as os6 from "node:os";
5931
- import * as path16 from "node:path";
5928
+ import { spawn as spawn6 } from "node:child_process";
5929
+ import * as fs15 from "node:fs/promises";
5930
+ import * as os7 from "node:os";
5931
+ import * as path20 from "node:path";
5932
5932
  async function parseSymbols4(opts) {
5933
5933
  const { file, content, lang } = opts;
5934
5934
  try {
@@ -5950,7 +5950,7 @@ async function resolvePython() {
5950
5950
  function commandIsAvailable(command) {
5951
5951
  return new Promise((resolve16) => {
5952
5952
  let settled = false;
5953
- const proc = spawn5(command, ["--version"], {
5953
+ const proc = spawn6(command, ["--version"], {
5954
5954
  stdio: "ignore",
5955
5955
  windowsHide: true
5956
5956
  });
@@ -5972,7 +5972,7 @@ function commandIsAvailable(command) {
5972
5972
  function spawnPyParser(pyBinary, scriptPath, filePath, content) {
5973
5973
  return new Promise((resolve16, reject) => {
5974
5974
  let settled = false;
5975
- const proc = spawn5(pyBinary, [scriptPath, filePath], {
5975
+ const proc = spawn6(pyBinary, [scriptPath, filePath], {
5976
5976
  stdio: ["pipe", "pipe", "pipe"],
5977
5977
  windowsHide: true
5978
5978
  });
@@ -6006,10 +6006,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
6006
6006
  async function syncPyParse(filePath, content, lang) {
6007
6007
  try {
6008
6008
  if (!_cachedScriptPath) {
6009
- const tmpDir = path16.join(os6.tmpdir(), "ws-py-parse");
6010
- await fs11.mkdir(tmpDir, { recursive: true });
6011
- _cachedScriptPath = path16.join(tmpDir, "parse.py");
6012
- await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
6009
+ const tmpDir = path20.join(os7.tmpdir(), "ws-py-parse");
6010
+ await fs15.mkdir(tmpDir, { recursive: true });
6011
+ _cachedScriptPath = path20.join(tmpDir, "parse.py");
6012
+ await fs15.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
6013
6013
  }
6014
6014
  cachedPyBinary ??= resolvePython();
6015
6015
  const pyBinary = await cachedPyBinary;
@@ -6263,10 +6263,10 @@ __export(rs_parser_exports, {
6263
6263
  detectLang: () => detectLang,
6264
6264
  parseSymbols: () => parseSymbols5
6265
6265
  });
6266
- import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
6267
- import { execFile, spawn as spawn6 } from "node:child_process";
6268
- import * as fs12 from "node:fs/promises";
6269
- import * as path17 from "node:path";
6266
+ import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
6267
+ import { execFile, spawn as spawn7 } from "node:child_process";
6268
+ import * as fs16 from "node:fs/promises";
6269
+ import * as path21 from "node:path";
6270
6270
  async function parseSymbols5(opts) {
6271
6271
  const { file, content, lang } = opts;
6272
6272
  const nativeAvailable = await checkNativeParser();
@@ -6288,7 +6288,7 @@ function checkNativeParser() {
6288
6288
  nativeParserAvailability ??= (async () => {
6289
6289
  try {
6290
6290
  await probe("rustc", ["--version"]);
6291
- const toolsDir = path17.join(process.cwd(), "tools");
6291
+ const toolsDir = path21.join(process.cwd(), "tools");
6292
6292
  await probe(
6293
6293
  "cargo",
6294
6294
  [
@@ -6297,7 +6297,7 @@ function checkNativeParser() {
6297
6297
  "--format-version",
6298
6298
  "1",
6299
6299
  "--manifest-path",
6300
- path17.join(toolsDir, "Cargo.toml")
6300
+ path21.join(toolsDir, "Cargo.toml")
6301
6301
  ]
6302
6302
  );
6303
6303
  return true;
@@ -6309,17 +6309,17 @@ function checkNativeParser() {
6309
6309
  }
6310
6310
  async function tryNativeParse(file, content) {
6311
6311
  try {
6312
- const toolsDir = path17.join(process.cwd(), "tools");
6313
- const crateDir = path17.join(toolsDir, "syn-parser");
6314
- const tmpFile = path17.join(crateDir, "src", "input.rs");
6315
- await fs12.writeFile(tmpFile, content, "utf8");
6312
+ const toolsDir = path21.join(process.cwd(), "tools");
6313
+ const crateDir = path21.join(toolsDir, "syn-parser");
6314
+ const tmpFile = path21.join(crateDir, "src", "input.rs");
6315
+ await fs16.writeFile(tmpFile, content, "utf8");
6316
6316
  const cargoBinary = resolveWin32Command("cargo");
6317
6317
  const result = await new Promise(
6318
6318
  (resolve16, reject) => {
6319
6319
  let settled = false;
6320
- const proc = spawn6(
6320
+ const proc = spawn7(
6321
6321
  cargoBinary,
6322
- ["run", "--manifest-path", path17.join(toolsDir, "Cargo.toml")],
6322
+ ["run", "--manifest-path", path21.join(toolsDir, "Cargo.toml")],
6323
6323
  {
6324
6324
  cwd: process.cwd(),
6325
6325
  stdio: ["pipe", "pipe", "pipe"],
@@ -6378,7 +6378,7 @@ function regexParse(opts) {
6378
6378
  let hi = lineOffsets2.length - 1;
6379
6379
  while (lo < hi) {
6380
6380
  const mid = lo + hi + 1 >>> 1;
6381
- if (expectDefined2(lineOffsets2[mid]) <= offset) lo = mid;
6381
+ if (expectDefined3(lineOffsets2[mid]) <= offset) lo = mid;
6382
6382
  else hi = mid - 1;
6383
6383
  }
6384
6384
  return lo + 1;
@@ -6390,7 +6390,7 @@ function regexParse(opts) {
6390
6390
  for (const pattern of RS_PATTERNS) {
6391
6391
  pattern.regex.lastIndex = 0;
6392
6392
  for (let match = pattern.regex.exec(content); match !== null; match = pattern.regex.exec(content)) {
6393
- const name = expectDefined2(match[1]);
6393
+ const name = expectDefined3(match[1]);
6394
6394
  const offset = match.index ?? 0;
6395
6395
  const line = lineFromOffset(offset);
6396
6396
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6447,8 +6447,8 @@ __export(json_parser_exports, {
6447
6447
  detectLang: () => detectLang,
6448
6448
  parseSymbols: () => parseSymbols6
6449
6449
  });
6450
- import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
6451
- import * as path18 from "node:path";
6450
+ import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
6451
+ import * as path22 from "node:path";
6452
6452
  function parseSymbols6(opts) {
6453
6453
  const { file, content, lang } = opts;
6454
6454
  try {
@@ -6460,7 +6460,7 @@ function parseSymbols6(opts) {
6460
6460
  function regexParse2(opts) {
6461
6461
  const { file, content, lang } = opts;
6462
6462
  const symbols = [];
6463
- const basename12 = path18.basename(file).toLowerCase();
6463
+ const basename12 = path22.basename(file).toLowerCase();
6464
6464
  const isPackageJson = basename12 === "package.json";
6465
6465
  const isTsconfig = basename12 === "tsconfig.json" || basename12 === "tsconfig.build.json";
6466
6466
  const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
@@ -6475,22 +6475,22 @@ function regexParse2(opts) {
6475
6475
  let hi = lineOffsets2.length - 1;
6476
6476
  while (lo < hi) {
6477
6477
  const mid = lo + hi + 1 >>> 1;
6478
- if (expectDefined3(lineOffsets2[mid]) <= offset) lo = mid;
6478
+ if (expectDefined4(lineOffsets2[mid]) <= offset) lo = mid;
6479
6479
  else hi = mid - 1;
6480
6480
  }
6481
6481
  return lo + 1;
6482
6482
  }
6483
6483
  const rootMatch = content.match(/^\s*\{/m);
6484
6484
  if (rootMatch) {
6485
- const offset = expectDefined3(rootMatch.index);
6485
+ const offset = expectDefined4(rootMatch.index);
6486
6486
  const line = lineFromOffset(offset);
6487
6487
  symbols.push(
6488
6488
  makeSymbol({
6489
- name: path18.basename(file),
6489
+ name: path22.basename(file),
6490
6490
  kind: "object",
6491
6491
  line,
6492
6492
  col: 0,
6493
- signature: `"${path18.basename(file)}" = { ... }`,
6493
+ signature: `"${path22.basename(file)}" = { ... }`,
6494
6494
  file,
6495
6495
  lang
6496
6496
  })
@@ -6498,7 +6498,7 @@ function regexParse2(opts) {
6498
6498
  }
6499
6499
  const topLevelKeyRegex = /^\s*"([^"]+)"\s*:/gm;
6500
6500
  for (let match = topLevelKeyRegex.exec(content); match !== null; match = topLevelKeyRegex.exec(content)) {
6501
- const key = expectDefined3(match[1]);
6501
+ const key = expectDefined4(match[1]);
6502
6502
  const offset = match.index ?? 0;
6503
6503
  const line = lineFromOffset(offset);
6504
6504
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6545,7 +6545,7 @@ function regexParse2(opts) {
6545
6545
  const defsRegex = /"\$defs"\s*:|"\$defs"\s*:/g;
6546
6546
  const defsMatch = defsRegex.exec(content);
6547
6547
  if (defsMatch !== null) {
6548
- const offset = expectDefined3(defsMatch.index);
6548
+ const offset = expectDefined4(defsMatch.index);
6549
6549
  const line = lineFromOffset(offset);
6550
6550
  symbols.push(
6551
6551
  makeSymbol({
@@ -6570,7 +6570,7 @@ function regexParse2(opts) {
6570
6570
  for (let match = pat.exec(content); match !== null; match = pat.exec(content)) {
6571
6571
  const offset = match.index ?? 0;
6572
6572
  const line = lineFromOffset(offset);
6573
- const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined3(match[0]);
6573
+ const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined4(match[0]);
6574
6574
  symbols.push(
6575
6575
  makeSymbol({
6576
6576
  name: key,
@@ -6589,12 +6589,12 @@ function regexParse2(opts) {
6589
6589
  function extractPackageScripts(content, symbols, file, lang, lineOffsets2, lineFromOffset) {
6590
6590
  const scriptsBlockRegex = /"scripts"\s*:\s*\{([^}]+)\}/g;
6591
6591
  for (let match = scriptsBlockRegex.exec(content); match !== null; match = scriptsBlockRegex.exec(content)) {
6592
- const blockContent = expectDefined3(match[0]);
6592
+ const blockContent = expectDefined4(match[0]);
6593
6593
  const blockOffset = match.index ?? 0;
6594
6594
  const scriptKeyRegex = /"(\w[\w-]*)"\s*:/g;
6595
6595
  for (let scriptMatch = scriptKeyRegex.exec(blockContent); scriptMatch !== null; scriptMatch = scriptKeyRegex.exec(blockContent)) {
6596
- const key = expectDefined3(scriptMatch[1]);
6597
- const keyOffset = blockOffset + expectDefined3(scriptMatch.index);
6596
+ const key = expectDefined4(scriptMatch[1]);
6597
+ const keyOffset = blockOffset + expectDefined4(scriptMatch.index);
6598
6598
  const line = lineFromOffset(keyOffset);
6599
6599
  symbols.push(
6600
6600
  makeSymbol({
@@ -6613,12 +6613,12 @@ function extractPackageScripts(content, symbols, file, lang, lineOffsets2, lineF
6613
6613
  function extractCompilerOptions(content, symbols, file, lang, lineOffsets2, parentLine, lineFromOffset) {
6614
6614
  const optsBlockRegex = /"compilerOptions"\s*:\s*\{([^}]+)\}/g;
6615
6615
  for (let match = optsBlockRegex.exec(content); match !== null; match = optsBlockRegex.exec(content)) {
6616
- const blockContent = expectDefined3(match[0]);
6616
+ const blockContent = expectDefined4(match[0]);
6617
6617
  const blockOffset = match.index ?? 0;
6618
6618
  const optKeyRegex = /"(\w[\w]*)"\s*:/g;
6619
6619
  for (let optMatch = optKeyRegex.exec(blockContent); optMatch !== null; optMatch = optKeyRegex.exec(blockContent)) {
6620
- const key = expectDefined3(optMatch[1]);
6621
- const keyOffset = blockOffset + expectDefined3(optMatch.index);
6620
+ const key = expectDefined4(optMatch[1]);
6621
+ const keyOffset = blockOffset + expectDefined4(optMatch.index);
6622
6622
  const line = lineFromOffset(keyOffset);
6623
6623
  if (line <= parentLine) continue;
6624
6624
  symbols.push(
@@ -6663,7 +6663,7 @@ __export(yaml_parser_exports, {
6663
6663
  detectLang: () => detectLang,
6664
6664
  parseSymbols: () => parseSymbols7
6665
6665
  });
6666
- import { expectDefined as expectDefined4, truncate } from "@wrongstack/core/utils";
6666
+ import { expectDefined as expectDefined5, truncate } from "@wrongstack/core/utils";
6667
6667
  function parseSymbols7(opts) {
6668
6668
  const { file, content, lang } = opts;
6669
6669
  try {
@@ -6685,14 +6685,14 @@ function regexParse3(opts) {
6685
6685
  let hi = lineOffsets2.length - 1;
6686
6686
  while (lo < hi) {
6687
6687
  const mid = lo + hi + 1 >>> 1;
6688
- if (expectDefined4(lineOffsets2[mid]) <= offset) lo = mid;
6688
+ if (expectDefined5(lineOffsets2[mid]) <= offset) lo = mid;
6689
6689
  else hi = mid - 1;
6690
6690
  }
6691
6691
  return lo + 1;
6692
6692
  }
6693
6693
  const anchorRegex = /&(\w[\w-]*)/g;
6694
6694
  for (let match = anchorRegex.exec(content); match !== null; match = anchorRegex.exec(content)) {
6695
- const name = expectDefined4(match[1]);
6695
+ const name = expectDefined5(match[1]);
6696
6696
  const offset = match.index ?? 0;
6697
6697
  const line = lineFromOffset(offset);
6698
6698
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6710,7 +6710,7 @@ function regexParse3(opts) {
6710
6710
  }
6711
6711
  const aliasRegex = /\*(\w[\w-]*)/g;
6712
6712
  for (let match = aliasRegex.exec(content); match !== null; match = aliasRegex.exec(content)) {
6713
- const name = expectDefined4(match[1]);
6713
+ const name = expectDefined5(match[1]);
6714
6714
  const offset = match.index ?? 0;
6715
6715
  const line = lineFromOffset(offset);
6716
6716
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6745,7 +6745,7 @@ function regexParse3(opts) {
6745
6745
  }
6746
6746
  const listItemRegex = /^-(\s+)([^:#\s][^:#\s]*)\s*:/gm;
6747
6747
  for (let match = listItemRegex.exec(content); match !== null; match = listItemRegex.exec(content)) {
6748
- const key = expectDefined4(match[2]);
6748
+ const key = expectDefined5(match[2]);
6749
6749
  const offset = match.index ?? 0;
6750
6750
  const line = lineFromOffset(offset);
6751
6751
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -6765,7 +6765,7 @@ function regexParse3(opts) {
6765
6765
  }
6766
6766
  const blockScalarRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:\s*[|>](\s|$)/gm;
6767
6767
  for (let match = blockScalarRegex.exec(content); match !== null; match = blockScalarRegex.exec(content)) {
6768
- const key = expectDefined4(match[2]);
6768
+ const key = expectDefined5(match[2]);
6769
6769
  const offset = match.index ?? 0;
6770
6770
  const line = lineFromOffset(offset);
6771
6771
  const col = offset - (lineOffsets2[line - 1] ?? 0);
@@ -9770,10 +9770,12 @@ var browserTools = [
9770
9770
  for (const tool of browserTools) tool.icon = "web";
9771
9771
  for (const tool of browserTools) tool.timeoutMs ??= 6e4;
9772
9772
 
9773
- // src/codebase-index/background-indexer.ts
9774
- import * as fs18 from "node:fs";
9775
- import { fileURLToPath as fileURLToPath3 } from "node:url";
9776
- import { Worker } from "node:worker_threads";
9773
+ // src/codebase-index/project-server-client.ts
9774
+ import { spawn as spawn4 } from "node:child_process";
9775
+ import * as fs12 from "node:fs";
9776
+ import * as net3 from "node:net";
9777
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
9778
+ import { checkUnixSocketPath } from "@wrongstack/core/utils";
9777
9779
 
9778
9780
  // src/codebase-index/circuit-breaker.ts
9779
9781
  var CircuitOpenError = class extends Error {
@@ -9855,117 +9857,18 @@ var IndexCircuitBreaker = class {
9855
9857
  };
9856
9858
  var indexCircuitBreaker = new IndexCircuitBreaker();
9857
9859
 
9858
- // src/codebase-index/indexer.ts
9859
- import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
9860
- import { execFile as execFile2 } from "node:child_process";
9861
- import * as fs15 from "node:fs/promises";
9862
- import { availableParallelism } from "node:os";
9863
- import * as path22 from "node:path";
9864
- import {
9865
- DEFAULT_WALK_IGNORE_DIRS,
9866
- indexParallelBatchSize,
9867
- isFrugalPerf
9868
- } from "@wrongstack/core/utils";
9869
-
9870
- // src/codebase-index/gitignore.ts
9871
- import * as fs9 from "node:fs/promises";
9872
- import * as path13 from "node:path";
9873
- import { compileGlob } from "@wrongstack/core/utils";
9874
- function globBody(glob) {
9875
- return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
9876
- }
9877
- function compileGitignore(lines) {
9878
- const rules = [];
9879
- for (const raw of lines) {
9880
- let line = raw.replace(/\r$/, "");
9881
- if (!line.trim() || line.trimStart().startsWith("#")) continue;
9882
- line = line.trim();
9883
- let negated = false;
9884
- if (line.startsWith("!")) {
9885
- negated = true;
9886
- line = line.slice(1);
9887
- }
9888
- let dirOnly = false;
9889
- if (line.endsWith("/")) {
9890
- dirOnly = true;
9891
- line = line.slice(0, -1);
9892
- }
9893
- if (!line) continue;
9894
- const anchored = line.startsWith("/") || line.includes("/");
9895
- if (line.startsWith("/")) line = line.slice(1);
9896
- const body = globBody(line);
9897
- const prefix = anchored ? "^" : "(?:^|.*/)";
9898
- rules.push({
9899
- eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
9900
- under: new RegExp(`${prefix}${body}/.*$`),
9901
- negated,
9902
- dirOnly
9903
- });
9904
- }
9905
- return (relPath, isDir) => {
9906
- const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
9907
- let ignored = false;
9908
- for (const r of rules) {
9909
- const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
9910
- if (re.test(p)) ignored = !r.negated;
9911
- }
9912
- return ignored;
9913
- };
9914
- }
9915
- async function loadGitignoreMatcher(projectRoot) {
9916
- let lines = [];
9917
- try {
9918
- const raw = await fs9.readFile(path13.join(projectRoot, ".gitignore"), "utf8");
9919
- lines = raw.split("\n");
9920
- } catch {
9921
- }
9922
- return compileGitignore(lines);
9923
- }
9924
-
9925
- // src/codebase-index/indexer.ts
9926
- init_languages2();
9927
-
9928
- // src/codebase-index/parser-dispatch.ts
9929
- async function parseFileContent(file, content, lang) {
9930
- switch (lang) {
9931
- case "ts":
9932
- case "tsx":
9933
- case "js":
9934
- case "jsx": {
9935
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
9936
- return parseSymbols8({ file, content, lang });
9937
- }
9938
- case "go": {
9939
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
9940
- return parseSymbols8({ file, content, lang: "go" });
9941
- }
9942
- case "py": {
9943
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
9944
- return parseSymbols8({ file, content, lang: "py" });
9945
- }
9946
- case "rs": {
9947
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
9948
- return parseSymbols8({ file, content, lang: "rs" });
9949
- }
9950
- case "json": {
9951
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
9952
- return parseSymbols8({ file, content, lang: "json" });
9953
- }
9954
- case "yaml": {
9955
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
9956
- return parseSymbols8({ file, content, lang: "yaml" });
9957
- }
9958
- default: {
9959
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
9960
- return parseSymbols8({ file, content, lang });
9961
- }
9962
- }
9963
- }
9860
+ // src/codebase-index/project-server-endpoint.ts
9861
+ import { createHash as createHash4 } from "node:crypto";
9862
+ import * as fs11 from "node:fs";
9863
+ import * as os5 from "node:os";
9864
+ import * as path16 from "node:path";
9865
+ import { fileURLToPath } from "node:url";
9866
+ import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
9964
9867
 
9965
9868
  // src/codebase-index/writer.ts
9966
- import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
9967
- import * as fs14 from "node:fs";
9968
- import * as path21 from "node:path";
9869
+ import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
9870
+ import * as fs10 from "node:fs";
9871
+ import * as path15 from "node:path";
9969
9872
 
9970
9873
  // src/codebase-index/bm25.ts
9971
9874
  var K1 = 1.5;
@@ -10160,8 +10063,8 @@ function runSqliteWithRetry(fn) {
10160
10063
  }
10161
10064
 
10162
10065
  // src/codebase-index/writer-admin.ts
10163
- import * as fs13 from "node:fs";
10164
- import * as path19 from "node:path";
10066
+ import * as fs9 from "node:fs";
10067
+ import * as path13 from "node:path";
10165
10068
  var DB_FILE = "index.db";
10166
10069
  function getAllIndexableWithStatement(stmt) {
10167
10070
  return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
@@ -10220,7 +10123,7 @@ function getAllFileMetasWithStatement(stmt) {
10220
10123
  }
10221
10124
  function getIndexDbSizeBytes(indexDir) {
10222
10125
  try {
10223
- return fs13.statSync(path19.join(indexDir, DB_FILE)).size;
10126
+ return fs9.statSync(path13.join(indexDir, DB_FILE)).size;
10224
10127
  } catch {
10225
10128
  return 0;
10226
10129
  }
@@ -10287,7 +10190,7 @@ function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
10287
10190
  }
10288
10191
 
10289
10192
  // src/codebase-index/writer-graph-helpers.ts
10290
- import * as path20 from "node:path";
10193
+ import * as path14 from "node:path";
10291
10194
  function derivePackage(filePath) {
10292
10195
  const f = filePath.replace(/\\/g, "/");
10293
10196
  const pkgsIdx = f.indexOf("/packages/");
@@ -10402,16 +10305,16 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
10402
10305
  function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
10403
10306
  if (!moduleName.startsWith(".")) return void 0;
10404
10307
  const normalizedFrom = fromFile.replace(/\\/g, "/");
10405
- const absolute = path20.posix.normalize(
10406
- path20.posix.join(path20.posix.dirname(normalizedFrom), moduleName)
10308
+ const absolute = path14.posix.normalize(
10309
+ path14.posix.join(path14.posix.dirname(normalizedFrom), moduleName)
10407
10310
  );
10408
- const extension = path20.posix.extname(absolute);
10311
+ const extension = path14.posix.extname(absolute);
10409
10312
  const base = extension ? absolute.slice(0, -extension.length) : absolute;
10410
10313
  const candidates = [
10411
10314
  absolute,
10412
10315
  ...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
10413
- ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path20.posix.join(absolute, `index${ext}`)),
10414
- ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path20.posix.join(base, `index${ext}`))
10316
+ ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(absolute, `index${ext}`)),
10317
+ ...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(base, `index${ext}`))
10415
10318
  ];
10416
10319
  const indexedByPortablePath = new Map(
10417
10320
  [...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
@@ -10927,9 +10830,9 @@ var IndexStore = class _IndexStore {
10927
10830
  }
10928
10831
  constructor(projectRoot, opts = {}) {
10929
10832
  this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
10930
- fs14.mkdirSync(this.indexDir, { recursive: true });
10833
+ fs10.mkdirSync(this.indexDir, { recursive: true });
10931
10834
  const Database = loadDatabaseSync();
10932
- this.db = new Database(path21.join(this.indexDir, DB_FILE2));
10835
+ this.db = new Database(path15.join(this.indexDir, DB_FILE2));
10933
10836
  applyIndexStorePragmas(this.db);
10934
10837
  this.initSchema();
10935
10838
  }
@@ -11344,13 +11247,13 @@ var IndexStore = class _IndexStore {
11344
11247
  if (rankDiff !== 0) return rankDiff;
11345
11248
  const scoreDiff = b.score - a.score;
11346
11249
  if (scoreDiff !== 0) return scoreDiff;
11347
- const left = expectDefined5(candidateById.get(a.id));
11348
- const right = expectDefined5(candidateById.get(b.id));
11250
+ const left = expectDefined2(candidateById.get(a.id));
11251
+ const right = expectDefined2(candidateById.get(b.id));
11349
11252
  return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
11350
11253
  });
11351
11254
  const qTokens = tokenise(query);
11352
11255
  const results = scored.slice(0, limit).map(({ id, score }) => {
11353
- const c = expectDefined5(candidateById.get(id));
11256
+ const c = expectDefined2(candidateById.get(id));
11354
11257
  return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
11355
11258
  });
11356
11259
  return { results, total: candidates.length };
@@ -11764,1134 +11667,1247 @@ var indexStorePool = new StorePool(
11764
11667
  (projectRoot, opts) => new IndexStore(projectRoot, opts)
11765
11668
  );
11766
11669
 
11767
- // src/codebase-index/indexer.ts
11768
- var YIELD_EVERY_N = 50;
11769
- function resolveParallelBatch() {
11770
- return indexParallelBatchSize(availableParallelism());
11771
- }
11772
- function yieldEventLoop() {
11773
- return new Promise((resolve16) => setImmediate(resolve16));
11670
+ // src/codebase-index/project-server-endpoint.ts
11671
+ var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
11672
+ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
11673
+ var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
11674
+ var buildIdCache;
11675
+ function projectIndexServerBuildId(entrypoint) {
11676
+ const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path16.resolve(entrypoint);
11677
+ try {
11678
+ const stat17 = fs11.statSync(file);
11679
+ if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat17.mtimeMs && buildIdCache.size === stat17.size) {
11680
+ return buildIdCache.buildId;
11681
+ }
11682
+ const buildId = createHash4("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
11683
+ buildIdCache = { file, mtimeMs: stat17.mtimeMs, size: stat17.size, buildId };
11684
+ return buildId;
11685
+ } catch {
11686
+ return `unreadable:${path16.basename(file)}`;
11687
+ }
11774
11688
  }
11775
- function throwIfAborted(signal) {
11776
- if (!signal?.aborted) return;
11777
- if (signal.reason instanceof Error) throw signal.reason;
11778
- throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
11689
+ function normalizeLocalPath(value) {
11690
+ const resolved = path16.resolve(value);
11691
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
11779
11692
  }
11780
- function isAbortError(err) {
11781
- return err instanceof DOMException && err.name === "AbortError";
11693
+ function projectIndexServerKey(projectRoot, indexDir) {
11694
+ const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
11695
+ return createHash4("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
11782
11696
  }
11783
- var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
11784
- var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
11785
- var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
11786
- var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
11787
- var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
11788
- function isWithinProject(projectRoot, file) {
11789
- const rel = path22.relative(projectRoot, file);
11790
- return rel !== "" && !rel.startsWith(`..${path22.sep}`) && rel !== ".." && !path22.isAbsolute(rel);
11697
+ function projectIndexServerEndpoint(projectRoot, indexDir) {
11698
+ const key = projectIndexServerKey(projectRoot, indexDir);
11699
+ if (process.platform === "win32") {
11700
+ return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
11701
+ }
11702
+ return path16.join(os5.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
11791
11703
  }
11792
- function isMissingPathError(err) {
11793
- const code = err?.code;
11794
- return code === "ENOENT" || code === "ENOTDIR";
11704
+ function projectIndexServerMetadataPath(projectRoot, indexDir) {
11705
+ return path16.join(
11706
+ path16.resolve(resolveIndexDir(projectRoot, indexDir)),
11707
+ PROJECT_INDEX_SERVER_METADATA_FILE
11708
+ );
11795
11709
  }
11796
- function normalizeComparablePath(value) {
11797
- const resolved = path22.resolve(value);
11798
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
11710
+
11711
+ // src/codebase-index/project-server-protocol.ts
11712
+ var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
11713
+ function encodeProjectServerMessage(message) {
11714
+ return `${JSON.stringify(message)}
11715
+ `;
11799
11716
  }
11800
- function gitOutput(projectRoot, args) {
11801
- return new Promise((resolve16, reject) => {
11802
- execFile2(
11803
- "git",
11804
- ["-C", projectRoot, ...args],
11805
- {
11806
- encoding: "buffer",
11807
- maxBuffer: MAX_GIT_FILE_LIST_BYTES,
11808
- windowsHide: true
11809
- },
11810
- (error, stdout) => {
11811
- if (error) reject(error);
11812
- else resolve16(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
11717
+
11718
+ // src/codebase-index/project-server-client.ts
11719
+ var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
11720
+ var SERVER_START_TIMEOUT_MS = 1e4;
11721
+ var SERVER_CONTROL_TIMEOUT_MS = 5e3;
11722
+ var SERVER_HEALTH_TIMEOUT_MS = 3e3;
11723
+ var SERVER_HEARTBEAT_INTERVAL_MS = 1e4;
11724
+ var StaleProjectIndexServerError = class extends Error {
11725
+ constructor(message, pid) {
11726
+ super(message);
11727
+ this.pid = pid;
11728
+ }
11729
+ pid;
11730
+ name = "StaleProjectIndexServerError";
11731
+ };
11732
+ var connectionStates = /* @__PURE__ */ new Map();
11733
+ var connectionStateListeners = /* @__PURE__ */ new Set();
11734
+ var latestConnectionState = {
11735
+ status: "offline",
11736
+ connected: false
11737
+ };
11738
+ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
11739
+ if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
11740
+ return { kind: "inline-requested" };
11741
+ }
11742
+ let builtUrl = null;
11743
+ for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
11744
+ try {
11745
+ const url = new URL(rel, import.meta.url);
11746
+ if (url.protocol === "file:" && fs12.existsSync(fileURLToPath2(url))) {
11747
+ builtUrl = url;
11748
+ break;
11813
11749
  }
11814
- );
11815
- });
11750
+ } catch {
11751
+ }
11752
+ }
11753
+ if (builtUrl === null) return { kind: "missing-build" };
11754
+ if (projectRoot !== void 0) {
11755
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
11756
+ const check = checkUnixSocketPath(endpoint);
11757
+ if (!check.ok) {
11758
+ return {
11759
+ kind: "endpoint-invalid",
11760
+ endpoint,
11761
+ byteLength: check.byteLength,
11762
+ maxBytes: check.maxBytes
11763
+ };
11764
+ }
11765
+ }
11766
+ return { kind: "available", url: builtUrl };
11816
11767
  }
11817
- async function findGitSourceFiles(projectRoot, ignore, signal) {
11818
- try {
11819
- throwIfAborted(signal);
11820
- const topLevel = (await gitOutput(projectRoot, ["rev-parse", "--show-toplevel"])).toString("utf8").trim();
11821
- if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
11822
- throwIfAborted(signal);
11823
- const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
11824
- const [output, statusOutput] = await Promise.all([
11825
- gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
11826
- gitOutput(projectRoot, [
11827
- "status",
11828
- "--porcelain=v1",
11829
- "-z",
11830
- "--untracked-files=all",
11831
- "--ignored=no"
11832
- ])
11833
- ]);
11834
- throwIfAborted(signal);
11835
- const dirty = /* @__PURE__ */ new Set();
11836
- const deleted = /* @__PURE__ */ new Set();
11837
- const statusRecords = statusOutput.toString("utf8").split("\0");
11838
- for (let i = 0; i < statusRecords.length; i++) {
11839
- const record = statusRecords[i];
11840
- if (!record) continue;
11841
- const status = record.slice(0, 2);
11842
- const changedPath = path22.resolve(projectRoot, record.slice(3));
11843
- dirty.add(changedPath);
11844
- if (status.includes("D")) deleted.add(changedPath);
11845
- if (status.includes("R") || status.includes("C")) {
11846
- const source = statusRecords[++i];
11847
- if (source) dirty.add(path22.resolve(projectRoot, source));
11848
- }
11849
- }
11850
- const files = [];
11851
- for (const relative12 of output.toString("utf8").split("\0")) {
11852
- if (!relative12) continue;
11853
- const portable = relative12.replace(/\\/g, "/");
11854
- if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path22.posix.basename(portable))) {
11855
- continue;
11856
- }
11857
- const full = path22.resolve(projectRoot, relative12);
11858
- if (deleted.has(full)) continue;
11859
- const ext = path22.extname(relative12).toLowerCase();
11860
- if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
11768
+ function resolveProjectServerUrl() {
11769
+ const availability = resolveProjectIndexDaemonAvailability();
11770
+ return availability.kind === "available" ? availability.url : null;
11771
+ }
11772
+ function projectIndexServerExpectedBuildId() {
11773
+ const override = process.env["WRONGSTACK_INDEX_SERVER_BUILD_ID"]?.trim();
11774
+ if (override) return override;
11775
+ const url = resolveProjectServerUrl();
11776
+ return url ? projectIndexServerBuildId(url) : null;
11777
+ }
11778
+ function isProjectIndexServerAvailable() {
11779
+ return resolveProjectServerUrl() !== null;
11780
+ }
11781
+ function publishConnectionState(endpoint, state) {
11782
+ connectionStates.set(endpoint, state);
11783
+ latestConnectionState = state;
11784
+ for (const listener of connectionStateListeners) listener(state);
11785
+ }
11786
+ function getProjectIndexServerConnectionState(projectRoot, indexDir) {
11787
+ if (projectRoot) {
11788
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
11789
+ const existing = connectionStates.get(endpoint);
11790
+ if (existing) return existing;
11791
+ if (!isProjectIndexServerAvailable()) {
11792
+ return { status: "unavailable", connected: false };
11861
11793
  }
11862
11794
  return {
11863
- files,
11864
- trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
11795
+ status: "offline",
11796
+ connected: false,
11797
+ projectRoot,
11798
+ indexDir,
11799
+ endpoint
11865
11800
  };
11866
- } catch {
11867
- return null;
11868
11801
  }
11802
+ if (latestConnectionState.endpoint) return latestConnectionState;
11803
+ if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
11804
+ return latestConnectionState;
11869
11805
  }
11870
- async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
11871
- const gitFiles = await findGitSourceFiles(projectRoot, ignore, signal);
11872
- if (gitFiles) {
11873
- return {
11874
- files: gitFiles.files,
11875
- complete: true,
11876
- errors: [],
11877
- trustedUnchanged: gitFiles.trustedUnchanged
11878
- };
11879
- }
11880
- const results = [];
11881
- const errors = [];
11882
- let complete = true;
11883
- const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
11884
- const indexableExts = new Set(INDEXABLE_EXTENSIONS);
11885
- let dirCount = 0;
11886
- const walk = async (dir) => {
11887
- throwIfAborted(signal);
11888
- if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
11889
- await yieldEventLoop();
11890
- throwIfAborted(signal);
11891
- }
11892
- let entries;
11893
- try {
11894
- entries = await fs15.readdir(dir, { withFileTypes: true });
11895
- } catch (err) {
11896
- complete = false;
11897
- errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
11898
- return;
11899
- }
11900
- dirCount++;
11901
- for (const e of entries) {
11902
- if (ignoreSet.has(e.name)) continue;
11903
- const full = path22.join(dir, e.name);
11904
- const rel = path22.relative(projectRoot, full).replace(/\\/g, "/");
11905
- if (e.isDirectory()) {
11906
- if (isGitIgnored(rel, true)) continue;
11907
- await walk(full);
11908
- } else if (e.isFile()) {
11909
- if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
11910
- const ext = path22.extname(e.name).toLowerCase();
11911
- if (indexableExts.has(ext) || detectLang(full) !== null) {
11912
- results.push(full);
11913
- }
11914
- }
11915
- }
11916
- };
11917
- await walk(projectRoot);
11918
- return { files: results, complete, errors };
11806
+ function onProjectIndexServerConnectionStateChange(listener) {
11807
+ connectionStateListeners.add(listener);
11808
+ return () => connectionStateListeners.delete(listener);
11919
11809
  }
11920
- function assignRefsToSymbols2(refs, symbols) {
11921
- if (refs.length === 0 || symbols.length === 0) return [];
11922
- const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
11923
- const seen = /* @__PURE__ */ new Set();
11924
- const assigned = [];
11925
- for (const ref of refs) {
11926
- let owner2;
11927
- for (const symbol of ordered) {
11928
- if (symbol.line > ref.line) break;
11929
- owner2 = symbol;
11930
- }
11931
- if (!owner2 && ref.callType === "import") owner2 = ordered[0];
11932
- if (!owner2 || owner2.id <= 0) continue;
11933
- const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
11934
- if (seen.has(key)) continue;
11935
- seen.add(key);
11936
- assigned.push({ ...ref, fromId: owner2.id });
11937
- }
11938
- return assigned;
11810
+ function remoteError(message, name) {
11811
+ if (name === "LockError") return new LockError(message);
11812
+ if (name === "IndexTimeoutError") return new IndexTimeoutError(message);
11813
+ const error = new Error(message);
11814
+ if (name && name !== "Error") error.name = name;
11815
+ return error;
11939
11816
  }
11940
- async function runIndexerWithStore(store, opts) {
11941
- const { projectRoot, langs, ignore = [], signal } = opts;
11942
- const relationGraphVersion = "2";
11943
- const refResolutionVersion = "2";
11944
- const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
11945
- const needsFullRefResolution = force || store.getMetadata("ref_resolution_version") !== refResolutionVersion;
11946
- const startMs = Date.now();
11947
- const errors = [];
11948
- const langStats = {};
11949
- let filesIndexed = 0;
11950
- let symbolsIndexed = 0;
11951
- const isGitIgnored = await loadGitignoreMatcher(projectRoot);
11952
- let files;
11953
- let discoveredFiles = null;
11954
- let discoveryComplete = true;
11955
- let trustedUnchanged;
11956
- if (opts.files && opts.files.length > 0) {
11957
- files = opts.files.map((f) => path22.resolve(projectRoot, f)).filter((f) => {
11958
- if (!isWithinProject(projectRoot, f)) return false;
11959
- const rel = path22.relative(projectRoot, f).replace(/\\/g, "/");
11960
- return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path22.basename(f)) && !isGitIgnored(rel, false);
11961
- });
11962
- } else {
11963
- const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
11964
- files = discovery.files;
11965
- errors.push(...discovery.errors);
11966
- discoveryComplete = discovery.complete;
11967
- discoveredFiles = new Set(files);
11968
- trustedUnchanged = discovery.trustedUnchanged;
11817
+ function isProjectIndexServerHealth(value) {
11818
+ if (!value || typeof value !== "object") return false;
11819
+ const health = value;
11820
+ const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
11821
+ const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
11822
+ 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";
11823
+ }
11824
+ function delay(ms) {
11825
+ return new Promise((resolve16) => {
11826
+ const timer = setTimeout(resolve16, ms);
11827
+ timer.unref?.();
11828
+ });
11829
+ }
11830
+ function cancellationError(signal) {
11831
+ return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
11832
+ }
11833
+ var ProjectServerConnection = class {
11834
+ constructor(projectRoot, indexDir, endpoint) {
11835
+ this.projectRoot = projectRoot;
11836
+ this.indexDir = indexDir;
11837
+ this.endpoint = endpoint;
11838
+ this.transition("offline");
11969
11839
  }
11970
- if (langs && langs.length > 0) {
11971
- const langSet = new Set(langs);
11972
- files = files.filter((f) => {
11973
- const lang = detectLang(f);
11974
- return lang ? langSet.has(lang) : false;
11840
+ projectRoot;
11841
+ indexDir;
11842
+ endpoint;
11843
+ socket = null;
11844
+ buffer = "";
11845
+ info = null;
11846
+ activity = null;
11847
+ health = null;
11848
+ healthCheck = null;
11849
+ connecting = null;
11850
+ connectResolve = null;
11851
+ connectReject = null;
11852
+ nextId = 1;
11853
+ pending = /* @__PURE__ */ new Map();
11854
+ transition(status, options = {}) {
11855
+ const previous = connectionStates.get(this.endpoint);
11856
+ const pid = options.pid ?? (status === "connected" ? this.info?.pid : void 0);
11857
+ 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);
11858
+ publishConnectionState(this.endpoint, {
11859
+ status,
11860
+ connected: status === "connected" || status === "degraded" || status === "unresponsive",
11861
+ projectRoot: this.projectRoot,
11862
+ indexDir: this.indexDir,
11863
+ endpoint: this.endpoint,
11864
+ pid,
11865
+ lastError,
11866
+ ...this.activity ? { activity: this.activity } : {},
11867
+ ...this.health ? { health: this.health } : {}
11975
11868
  });
11976
11869
  }
11977
- if (force) store.clearAll();
11978
- const existingMeta = /* @__PURE__ */ new Map();
11979
- if (!force) {
11980
- for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
11981
- }
11982
- const totalFilesForProgress = files.length;
11983
- let filesPreSkipped = 0;
11984
- if (!force && trustedUnchanged) {
11985
- files = files.filter((file) => {
11986
- const meta = existingMeta.get(file);
11987
- if (!meta || !trustedUnchanged.has(file)) return true;
11988
- langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
11989
- symbolsIndexed += meta.symbolCount;
11990
- filesIndexed++;
11991
- filesPreSkipped++;
11992
- return false;
11993
- });
11994
- if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
11870
+ isConnected() {
11871
+ return this.socket !== null && !this.socket.destroyed && this.info !== null;
11995
11872
  }
11996
- const parallelBatch = resolveParallelBatch();
11997
- let filesSinceLastYield = 0;
11998
- for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
11999
- const batchEnd = Math.min(batchStart + parallelBatch, files.length);
12000
- const batchFiles = files.slice(batchStart, batchEnd);
12001
- opts.onProgress?.(filesPreSkipped + batchEnd, totalFilesForProgress);
12002
- filesSinceLastYield += batchFiles.length;
12003
- if (filesSinceLastYield >= YIELD_EVERY_N) {
12004
- filesSinceLastYield = 0;
12005
- await yieldEventLoop();
12006
- if (isFrugalPerf()) {
12007
- await new Promise((r) => setTimeout(r, 8));
12008
- }
12009
- throwIfAborted(signal);
11873
+ async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
11874
+ await this.ensureConnected(spawnIfMissing);
11875
+ if (this.healthCheck) return this.healthCheck;
11876
+ const startedAt = Date.now();
11877
+ this.healthCheck = this.request({ type: "ping" }, { timeoutMs }).then((server) => {
11878
+ const now = Date.now();
11879
+ this.health = {
11880
+ status: "healthy",
11881
+ checkedAt: now,
11882
+ lastHealthyAt: now,
11883
+ latencyMs: Math.max(0, now - startedAt),
11884
+ missedHeartbeats: 0,
11885
+ ...isProjectIndexServerHealth(server) ? { server } : {}
11886
+ };
11887
+ this.transition("connected", { pid: this.info?.pid });
11888
+ return this.health;
11889
+ }).catch((error) => {
11890
+ if (!this.isConnected()) throw error;
11891
+ if ((this.health?.lastHealthyAt ?? 0) > startedAt) return this.health;
11892
+ const missedHeartbeats = (this.health?.missedHeartbeats ?? 0) + 1;
11893
+ const status = missedHeartbeats >= 3 ? "unresponsive" : "degraded";
11894
+ this.health = {
11895
+ status,
11896
+ checkedAt: Date.now(),
11897
+ lastHealthyAt: this.health?.lastHealthyAt ?? null,
11898
+ latencyMs: null,
11899
+ missedHeartbeats,
11900
+ ...this.health?.server ? { server: this.health.server } : {}
11901
+ };
11902
+ this.transition(status, { pid: this.info?.pid, error });
11903
+ return this.health;
11904
+ }).finally(() => {
11905
+ this.healthCheck = null;
11906
+ });
11907
+ return this.healthCheck;
11908
+ }
11909
+ markResponsive() {
11910
+ const now = Date.now();
11911
+ this.health = {
11912
+ status: "healthy",
11913
+ checkedAt: now,
11914
+ lastHealthyAt: now,
11915
+ latencyMs: this.health?.latencyMs ?? null,
11916
+ missedHeartbeats: 0,
11917
+ ...this.health?.server ? { server: this.health.server } : {}
11918
+ };
11919
+ }
11920
+ async call(op, args, options) {
11921
+ if (options.signal?.aborted) throw cancellationError(options.signal);
11922
+ await this.ensureConnected(true);
11923
+ if (options.signal?.aborted) throw cancellationError(options.signal);
11924
+ return this.request({ type: "request", op, args }, options);
11925
+ }
11926
+ async shutdownRemote(reason) {
11927
+ try {
11928
+ await this.ensureConnected(false);
11929
+ } catch {
11930
+ return { stopped: false, reason: "not-running" };
12010
11931
  }
12011
- const statOpts = signal ? { signal } : {};
12012
- const statReadParse = await Promise.allSettled(
12013
- batchFiles.map(
12014
- async (file) => {
12015
- let stat17;
12016
- try {
12017
- stat17 = await fs15.stat(file, statOpts);
12018
- } catch (e) {
12019
- if (isAbortError(e)) throw e;
12020
- return {
12021
- file,
12022
- stat: null,
12023
- lang: "",
12024
- parsed: null,
12025
- error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
12026
- missing: isMissingPathError(e)
12027
- };
12028
- }
12029
- if (!stat17.isFile()) return { file, stat: stat17, lang: "", parsed: null };
12030
- const lang = detectLang(file);
12031
- if (!lang) return { file, stat: stat17, lang: "", parsed: null };
12032
- if (stat17.size > MAX_INDEX_FILE_BYTES) {
12033
- return {
12034
- file,
12035
- stat: stat17,
12036
- lang,
12037
- parsed: null,
12038
- error: `file too large (${stat17.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
12039
- };
12040
- }
12041
- const meta = existingMeta.get(file);
12042
- if (!force && meta && meta.mtimeMs === Math.floor(stat17.mtimeMs)) {
12043
- return { file, stat: stat17, lang, parsed: null, skippedMeta: meta };
12044
- }
12045
- let content;
12046
- try {
12047
- content = await fs15.readFile(file, { encoding: "utf8", signal });
12048
- } catch (e) {
12049
- if (isAbortError(e)) throw e;
12050
- return {
12051
- file,
12052
- stat: stat17,
12053
- lang,
12054
- parsed: null,
12055
- error: `read error: ${e instanceof Error ? e.message : String(e)}`
12056
- };
12057
- }
12058
- let parsed;
12059
- try {
12060
- parsed = await parseFileContent(file, content, lang);
12061
- } catch (e) {
12062
- return {
12063
- file,
12064
- stat: stat17,
12065
- lang,
12066
- parsed: null,
12067
- error: `parse error: ${e instanceof Error ? e.message : String(e)}`
12068
- };
12069
- }
12070
- return { file, stat: stat17, lang, parsed, content };
12071
- }
12072
- )
11932
+ const pid = this.info?.pid;
11933
+ try {
11934
+ this.transition("stopping", { pid });
11935
+ await this.request(
11936
+ { type: "shutdown", reason },
11937
+ { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
11938
+ );
11939
+ return { stopped: true, pid };
11940
+ } catch (error) {
11941
+ const forceKilled = this.forceKillKnownServer();
11942
+ return {
11943
+ stopped: forceKilled,
11944
+ pid,
11945
+ reason: forceKilled ? `force-killed after graceful shutdown failed: ${error instanceof Error ? error.message : String(error)}` : error instanceof Error ? error.message : String(error)
11946
+ };
11947
+ } finally {
11948
+ this.close();
11949
+ }
11950
+ }
11951
+ async configure(watchExternal, debounceMs) {
11952
+ await this.ensureConnected(true);
11953
+ const startedAt = Date.now();
11954
+ const result = await this.request(
11955
+ { type: "configure", watchExternal, debounceMs },
11956
+ { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
12073
11957
  );
12074
- const batchEntries = [];
12075
- const deleteForFiles = [];
12076
- for (let fi = 0; fi < statReadParse.length; fi++) {
12077
- const settled = statReadParse[fi];
12078
- const file = expectDefined6(batchFiles[fi]);
12079
- if (settled.status === "rejected") {
12080
- const err = settled.reason;
12081
- if (err instanceof Error && isAbortError(err)) throw err;
12082
- errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
12083
- continue;
11958
+ if (isProjectIndexServerHealth(result.health)) {
11959
+ const now = Date.now();
11960
+ this.health = {
11961
+ status: "healthy",
11962
+ checkedAt: now,
11963
+ lastHealthyAt: now,
11964
+ latencyMs: Math.max(0, now - startedAt),
11965
+ missedHeartbeats: 0,
11966
+ server: result.health
11967
+ };
11968
+ this.transition("connected", { pid: this.info?.pid });
11969
+ }
11970
+ }
11971
+ close() {
11972
+ const socket = this.socket;
11973
+ this.socket = null;
11974
+ this.info = null;
11975
+ this.activity = null;
11976
+ this.health = null;
11977
+ this.connectReject?.(new Error("codebase-index client disconnected"));
11978
+ this.connectResolve = null;
11979
+ this.connectReject = null;
11980
+ if (socket && !socket.destroyed) socket.destroy();
11981
+ this.rejectPending(new Error("codebase-index client disconnected"));
11982
+ this.transition("offline");
11983
+ maybeStopHeartbeatLoop();
11984
+ }
11985
+ request(message, options) {
11986
+ const socket = this.socket;
11987
+ if (!socket || socket.destroyed) {
11988
+ return Promise.reject(new Error("codebase-index server connection is not available"));
11989
+ }
11990
+ const id = this.nextId++;
11991
+ return new Promise((resolve16, reject) => {
11992
+ const timer = setTimeout(() => {
11993
+ const entry = this.pending.get(id);
11994
+ if (!entry) return;
11995
+ this.pending.delete(id);
11996
+ this.write({ type: "cancel", id });
11997
+ const error = new IndexTimeoutError(
11998
+ `Index ${message.type === "request" ? message.op : message.type} exceeded its ${options.timeoutMs}ms watchdog timeout`
11999
+ );
12000
+ this.cleanupPending(entry);
12001
+ entry.reject(error);
12002
+ }, options.timeoutMs);
12003
+ timer.unref?.();
12004
+ const signal = options.signal;
12005
+ const onAbort = signal ? () => {
12006
+ const entry = this.pending.get(id);
12007
+ if (!entry) return;
12008
+ this.pending.delete(id);
12009
+ this.write({ type: "cancel", id });
12010
+ this.cleanupPending(entry);
12011
+ entry.reject(cancellationError(signal));
12012
+ } : void 0;
12013
+ this.pending.set(id, {
12014
+ resolve: resolve16,
12015
+ reject,
12016
+ timer,
12017
+ signal,
12018
+ onAbort,
12019
+ onProgress: options.onProgress
12020
+ });
12021
+ if (signal && onAbort) {
12022
+ signal.addEventListener("abort", onAbort, { once: true });
12023
+ if (signal.aborted) {
12024
+ onAbort();
12025
+ return;
12026
+ }
12084
12027
  }
12085
- const result = settled.value;
12086
- if (result.error) {
12087
- if (result.missing) store.deleteFile(file);
12088
- errors.push(`${file}: ${result.error}`);
12089
- continue;
12028
+ this.write({ ...message, id });
12029
+ });
12030
+ }
12031
+ async ensureConnected(spawnIfMissing) {
12032
+ if (this.socket && !this.socket.destroyed && this.info) return;
12033
+ if (this.connecting) return this.connecting;
12034
+ this.transition("connecting");
12035
+ this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
12036
+ this.transition("error", { error });
12037
+ throw error;
12038
+ }).finally(() => {
12039
+ this.connecting = null;
12040
+ });
12041
+ return this.connecting;
12042
+ }
12043
+ async connectWithElection(spawnIfMissing) {
12044
+ const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
12045
+ let spawned = false;
12046
+ let staleAttempts = 0;
12047
+ let lastError = new Error("codebase-index server unavailable");
12048
+ while (Date.now() < deadline) {
12049
+ try {
12050
+ await this.connectOnce();
12051
+ return;
12052
+ } catch (error) {
12053
+ lastError = error;
12054
+ if (error instanceof StaleProjectIndexServerError) {
12055
+ staleAttempts++;
12056
+ if (!spawnIfMissing) break;
12057
+ if (staleAttempts >= 3) this.forceKillServer(error.pid);
12058
+ spawned = false;
12059
+ await delay(100);
12060
+ continue;
12061
+ }
12090
12062
  }
12091
- const { stat: stat17, lang, parsed } = result;
12092
- if (result.skippedMeta) {
12093
- langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
12094
- symbolsIndexed += result.skippedMeta.symbolCount;
12095
- filesIndexed++;
12096
- continue;
12063
+ if (!spawnIfMissing) break;
12064
+ if (!spawned) {
12065
+ this.spawnDetachedServer();
12066
+ spawned = true;
12097
12067
  }
12098
- if (!lang || !parsed) {
12099
- if (lang) {
12100
- store.upsertFile({
12101
- file,
12102
- lang,
12103
- mtimeMs: Math.floor(stat17.mtimeMs),
12104
- symbolCount: 0,
12105
- lastIndexed: Date.now()
12106
- });
12107
- filesIndexed++;
12068
+ await delay(75);
12069
+ }
12070
+ throw lastError;
12071
+ }
12072
+ connectOnce() {
12073
+ this.socket?.destroy();
12074
+ this.socket = null;
12075
+ this.info = null;
12076
+ this.activity = null;
12077
+ this.health = null;
12078
+ this.buffer = "";
12079
+ return new Promise((resolve16, reject) => {
12080
+ const socket = net3.createConnection(this.endpoint);
12081
+ this.socket = socket;
12082
+ socket.setEncoding("utf8");
12083
+ const timer = setTimeout(() => {
12084
+ reject(new Error("codebase-index server handshake timed out"));
12085
+ socket.destroy();
12086
+ }, CONNECT_ATTEMPT_TIMEOUT_MS);
12087
+ timer.unref?.();
12088
+ const finishResolve = () => {
12089
+ clearTimeout(timer);
12090
+ this.connectResolve = null;
12091
+ this.connectReject = null;
12092
+ resolve16();
12093
+ };
12094
+ const finishReject = (error) => {
12095
+ clearTimeout(timer);
12096
+ this.connectResolve = null;
12097
+ this.connectReject = null;
12098
+ reject(error);
12099
+ };
12100
+ this.connectResolve = finishResolve;
12101
+ this.connectReject = finishReject;
12102
+ socket.on("data", (chunk) => this.onData(socket, chunk));
12103
+ socket.on("error", (error) => {
12104
+ if (!this.info) finishReject(error);
12105
+ });
12106
+ socket.on("close", () => this.onClose(socket));
12107
+ });
12108
+ }
12109
+ onData(socket, chunk) {
12110
+ if (socket !== this.socket) return;
12111
+ this.buffer += chunk;
12112
+ while (true) {
12113
+ const newline = this.buffer.indexOf("\n");
12114
+ if (newline < 0) {
12115
+ if (this.buffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
12116
+ socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
12108
12117
  }
12109
- continue;
12118
+ return;
12110
12119
  }
12111
- if (parsed.symbols.length === 0) {
12112
- store.replaceEmptyFile({
12113
- file,
12114
- lang,
12115
- mtimeMs: Math.floor(stat17.mtimeMs),
12116
- symbolCount: 0,
12117
- lastIndexed: Date.now()
12118
- });
12119
- filesIndexed++;
12120
- continue;
12120
+ if (newline > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
12121
+ socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
12122
+ return;
12121
12123
  }
12122
- batchEntries.push({
12123
- file,
12124
- lang,
12125
- symbols: parsed.symbols,
12126
- refs: parsed.refs ?? [],
12127
- mtimeMs: Math.floor(stat17.mtimeMs),
12128
- symbolCount: parsed.symbols.length
12129
- });
12130
- deleteForFiles.push(file);
12131
- }
12132
- if (batchEntries.length > 0) {
12124
+ const line = this.buffer.slice(0, newline);
12125
+ this.buffer = this.buffer.slice(newline + 1);
12126
+ if (!line) continue;
12127
+ let message;
12133
12128
  try {
12134
- store.commitBatch(batchEntries, { deleteForFiles });
12135
- for (const entry of batchEntries) {
12136
- const count = entry.symbols.length;
12137
- symbolsIndexed += count;
12138
- langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
12139
- filesIndexed++;
12140
- }
12141
- } catch (err) {
12142
- const message = err instanceof Error ? err.message : String(err);
12143
- errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
12144
- for (const entry of batchEntries) {
12145
- try {
12146
- store.deleteRefsForFile(entry.file);
12147
- store.deleteSymbolsForFile(entry.file);
12148
- const symbolsWithIds = store.insertSymbols(entry.symbols);
12149
- symbolsIndexed += symbolsWithIds.length;
12150
- langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
12151
- filesIndexed++;
12152
- if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
12153
- const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
12154
- if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
12155
- }
12156
- store.resolveRefsForNames([
12157
- ...entry.symbols.map((symbol) => symbol.name),
12158
- ...entry.refs.map((ref) => ref.toName)
12159
- ]);
12160
- store.upsertFile({
12161
- file: entry.file,
12162
- lang: entry.lang,
12163
- mtimeMs: entry.mtimeMs,
12164
- symbolCount: entry.symbolCount,
12165
- lastIndexed: Date.now()
12166
- });
12167
- } catch (innerErr) {
12168
- errors.push(
12169
- `fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
12170
- );
12171
- }
12172
- }
12129
+ message = JSON.parse(line);
12130
+ } catch {
12131
+ socket.destroy(new Error("invalid codebase-index server response"));
12132
+ return;
12133
+ }
12134
+ this.onMessage(message);
12135
+ }
12136
+ }
12137
+ onMessage(message) {
12138
+ if (message.type === "hello") {
12139
+ if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
12140
+ this.rejectStaleServer(
12141
+ message,
12142
+ `codebase-index protocol mismatch: client=${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
12143
+ );
12144
+ return;
12173
12145
  }
12146
+ const expectedBuildId = projectIndexServerExpectedBuildId();
12147
+ if (expectedBuildId && message.buildId !== expectedBuildId) {
12148
+ this.rejectStaleServer(
12149
+ message,
12150
+ `codebase-index build mismatch: client=${expectedBuildId}, server=${message.buildId ?? "legacy"}`
12151
+ );
12152
+ return;
12153
+ }
12154
+ this.info = message;
12155
+ this.markResponsive();
12156
+ this.transition("connected", { pid: message.pid });
12157
+ ensureHeartbeatLoop();
12158
+ this.connectResolve?.();
12159
+ return;
12160
+ }
12161
+ if (message.type === "index-state") {
12162
+ this.activity = message.state;
12163
+ this.markResponsive();
12164
+ this.transition("connected", { pid: this.info?.pid });
12165
+ return;
12166
+ }
12167
+ const entry = this.pending.get(message.id);
12168
+ if (!entry) return;
12169
+ this.markResponsive();
12170
+ const status = connectionStates.get(this.endpoint)?.status;
12171
+ if (status === "degraded" || status === "unresponsive") {
12172
+ this.transition("connected", { pid: this.info?.pid });
12173
+ }
12174
+ if (message.type === "progress") {
12175
+ entry.onProgress?.(message.current, message.total);
12176
+ return;
12177
+ }
12178
+ this.pending.delete(message.id);
12179
+ this.cleanupPending(entry);
12180
+ if (message.ok) entry.resolve(message.result);
12181
+ else entry.reject(remoteError(message.error, message.errorName));
12182
+ }
12183
+ onClose(socket) {
12184
+ if (socket !== this.socket) return;
12185
+ const wasConnected = this.info !== null;
12186
+ this.socket = null;
12187
+ this.info = null;
12188
+ this.activity = null;
12189
+ this.health = null;
12190
+ const error = new Error("codebase-index server connection closed");
12191
+ this.connectReject?.(error);
12192
+ this.connectResolve = null;
12193
+ this.connectReject = null;
12194
+ this.rejectPending(error);
12195
+ if (wasConnected) this.transition("error", { error });
12196
+ maybeStopHeartbeatLoop();
12197
+ }
12198
+ cleanupPending(entry) {
12199
+ clearTimeout(entry.timer);
12200
+ if (entry.signal && entry.onAbort) {
12201
+ entry.signal.removeEventListener("abort", entry.onAbort);
12174
12202
  }
12175
12203
  }
12176
- if (discoveredFiles && discoveryComplete) {
12177
- for (const [file_] of existingMeta) {
12178
- if (!discoveredFiles.has(file_)) {
12179
- store.deleteFile(file_);
12180
- }
12204
+ rejectPending(error) {
12205
+ const entries = [...this.pending.values()];
12206
+ this.pending.clear();
12207
+ for (const entry of entries) {
12208
+ this.cleanupPending(entry);
12209
+ entry.reject(error);
12181
12210
  }
12182
12211
  }
12183
- if (needsFullRefResolution) store.resolveRefs();
12184
- store.setMetadata("ref_resolution_version", refResolutionVersion);
12185
- store.setMetadata("relation_graph_version", relationGraphVersion);
12186
- if (!opts.files || filesIndexed >= 50) store.optimize();
12187
- store.setLastIndexed(Date.now());
12188
- if (!opts.files) store.compactIfNeeded();
12189
- const durationMs = Date.now() - startMs;
12190
- return {
12191
- filesIndexed,
12192
- symbolsIndexed,
12193
- langStats,
12194
- durationMs,
12195
- errors
12196
- };
12197
- }
12198
-
12199
- // src/codebase-index/index-service.ts
12200
- async function indexService(args, hooks = {}) {
12201
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12202
- try {
12203
- return await runIndexerWithStore(store, {
12204
- projectRoot: args.projectRoot,
12205
- indexDir: args.indexDir,
12206
- files: args.files,
12207
- force: args.force,
12208
- langs: args.langs,
12209
- ignore: args.ignore,
12210
- signal: hooks.signal,
12211
- onProgress: hooks.onProgress
12212
+ write(message) {
12213
+ const socket = this.socket;
12214
+ if (socket && !socket.destroyed) socket.write(encodeProjectServerMessage(message));
12215
+ }
12216
+ rejectStaleServer(message, reason) {
12217
+ const socket = this.socket;
12218
+ if (socket && !socket.destroyed) {
12219
+ socket.write(
12220
+ encodeProjectServerMessage({
12221
+ type: "shutdown",
12222
+ id: 0,
12223
+ reason: "stale-build-replacement"
12224
+ })
12225
+ );
12226
+ const timer = setTimeout(() => socket.destroy(), 25);
12227
+ timer.unref?.();
12228
+ }
12229
+ this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
12230
+ }
12231
+ spawnDetachedServer() {
12232
+ const url = resolveProjectServerUrl();
12233
+ if (!url) throw new Error("built codebase-index project server is unavailable");
12234
+ if (process.platform !== "win32") {
12235
+ try {
12236
+ fs12.rmSync(this.endpoint, { force: true });
12237
+ } catch {
12238
+ }
12239
+ }
12240
+ const args = [fileURLToPath2(url), "--project-root", this.projectRoot];
12241
+ if (this.indexDir) args.push("--index-dir", this.indexDir);
12242
+ const child = spawn4(process.execPath, args, {
12243
+ detached: true,
12244
+ stdio: "ignore",
12245
+ windowsHide: true,
12246
+ env: process.env
12212
12247
  });
12213
- } finally {
12214
- indexStorePool.release(store);
12248
+ child.unref();
12215
12249
  }
12216
- }
12217
- function searchService(args) {
12218
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12219
- try {
12220
- return store.searchRanked(
12221
- args.query,
12222
- {
12223
- kind: args.kind,
12224
- lang: args.lang,
12225
- file: args.file,
12226
- lspKind: args.lspKind
12227
- },
12228
- args.limit
12229
- );
12230
- } finally {
12231
- indexStorePool.release(store);
12250
+ forceKillKnownServer() {
12251
+ const pid = this.info?.pid;
12252
+ return pid ? this.forceKillServer(pid) : false;
12232
12253
  }
12233
- }
12234
- function statsService(args) {
12235
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12236
- try {
12237
- return store.getStats();
12238
- } finally {
12239
- indexStorePool.release(store);
12254
+ forceKillServer(pid) {
12255
+ if (pid === process.pid) return false;
12256
+ try {
12257
+ process.kill(pid);
12258
+ const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
12259
+ try {
12260
+ const metadata = JSON.parse(fs12.readFileSync(metadataPath, "utf8"));
12261
+ if (metadata.pid === pid) fs12.rmSync(metadataPath, { force: true });
12262
+ } catch {
12263
+ }
12264
+ return true;
12265
+ } catch {
12266
+ return false;
12267
+ }
12240
12268
  }
12269
+ };
12270
+ var connections = /* @__PURE__ */ new Map();
12271
+ var heartbeatTimer;
12272
+ function ensureHeartbeatLoop() {
12273
+ if (heartbeatTimer) return;
12274
+ heartbeatTimer = setInterval(() => {
12275
+ for (const connection of connections.values()) {
12276
+ if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
12277
+ });
12278
+ }
12279
+ }, SERVER_HEARTBEAT_INTERVAL_MS);
12280
+ heartbeatTimer.unref?.();
12241
12281
  }
12242
- function packageGraphService(args) {
12243
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12244
- try {
12245
- return store.getPackageGraph();
12246
- } finally {
12247
- indexStorePool.release(store);
12248
- }
12282
+ function maybeStopHeartbeatLoop() {
12283
+ if (!heartbeatTimer) return;
12284
+ if ([...connections.values()].some((connection) => connection.isConnected())) return;
12285
+ clearInterval(heartbeatTimer);
12286
+ heartbeatTimer = void 0;
12249
12287
  }
12250
- function fileGraphService(args) {
12251
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12252
- try {
12253
- return store.getFileGraph(args.packageFilter);
12254
- } finally {
12255
- indexStorePool.release(store);
12288
+ function connectionFor(projectRoot, indexDir) {
12289
+ const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12290
+ let connection = connections.get(endpoint);
12291
+ if (!connection) {
12292
+ connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
12293
+ connections.set(endpoint, connection);
12256
12294
  }
12295
+ return connection;
12257
12296
  }
12258
- function symbolGraphService(args) {
12259
- const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12260
- try {
12261
- return store.getSymbolGraph(args.fileFilter);
12262
- } finally {
12263
- indexStorePool.release(store);
12264
- }
12297
+ function callProjectIndexServer(op, args, options) {
12298
+ return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
12265
12299
  }
12266
12300
 
12267
- // src/codebase-index/project-server-client.ts
12268
- import { spawn as spawn7 } from "node:child_process";
12269
- import * as fs17 from "node:fs";
12270
- import * as net3 from "node:net";
12271
- import { fileURLToPath as fileURLToPath2 } from "node:url";
12301
+ // src/codebase-index/background-indexer.ts
12302
+ import * as fs18 from "node:fs";
12303
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
12304
+ import { Worker } from "node:worker_threads";
12272
12305
 
12273
- // src/codebase-index/project-server-endpoint.ts
12274
- import { createHash as createHash4 } from "node:crypto";
12275
- import * as fs16 from "node:fs";
12276
- import * as os7 from "node:os";
12306
+ // src/codebase-index/indexer.ts
12307
+ import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
12308
+ import { execFile as execFile2 } from "node:child_process";
12309
+ import * as fs17 from "node:fs/promises";
12310
+ import { availableParallelism } from "node:os";
12277
12311
  import * as path23 from "node:path";
12278
- import { fileURLToPath } from "node:url";
12279
- var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
12280
- var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
12281
- var buildIdCache;
12282
- function projectIndexServerBuildId(entrypoint) {
12283
- const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path23.resolve(entrypoint);
12284
- try {
12285
- const stat17 = fs16.statSync(file);
12286
- if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat17.mtimeMs && buildIdCache.size === stat17.size) {
12287
- return buildIdCache.buildId;
12288
- }
12289
- const buildId = createHash4("sha256").update(fs16.readFileSync(file)).digest("hex").slice(0, 24);
12290
- buildIdCache = { file, mtimeMs: stat17.mtimeMs, size: stat17.size, buildId };
12291
- return buildId;
12292
- } catch {
12293
- return `unreadable:${path23.basename(file)}`;
12294
- }
12295
- }
12296
- function normalizeLocalPath(value) {
12297
- const resolved = path23.resolve(value);
12298
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
12299
- }
12300
- function projectIndexServerKey(projectRoot, indexDir) {
12301
- const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
12302
- return createHash4("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
12312
+ import {
12313
+ DEFAULT_WALK_IGNORE_DIRS,
12314
+ indexParallelBatchSize,
12315
+ isFrugalPerf
12316
+ } from "@wrongstack/core/utils";
12317
+
12318
+ // src/codebase-index/gitignore.ts
12319
+ import * as fs13 from "node:fs/promises";
12320
+ import * as path17 from "node:path";
12321
+ import { compileGlob } from "@wrongstack/core/utils";
12322
+ function globBody(glob) {
12323
+ return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
12303
12324
  }
12304
- function projectIndexServerEndpoint(projectRoot, indexDir) {
12305
- const key = projectIndexServerKey(projectRoot, indexDir);
12306
- if (process.platform === "win32") {
12307
- return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
12325
+ function compileGitignore(lines) {
12326
+ const rules = [];
12327
+ for (const raw of lines) {
12328
+ let line = raw.replace(/\r$/, "");
12329
+ if (!line.trim() || line.trimStart().startsWith("#")) continue;
12330
+ line = line.trim();
12331
+ let negated = false;
12332
+ if (line.startsWith("!")) {
12333
+ negated = true;
12334
+ line = line.slice(1);
12335
+ }
12336
+ let dirOnly = false;
12337
+ if (line.endsWith("/")) {
12338
+ dirOnly = true;
12339
+ line = line.slice(0, -1);
12340
+ }
12341
+ if (!line) continue;
12342
+ const anchored = line.startsWith("/") || line.includes("/");
12343
+ if (line.startsWith("/")) line = line.slice(1);
12344
+ const body = globBody(line);
12345
+ const prefix = anchored ? "^" : "(?:^|.*/)";
12346
+ rules.push({
12347
+ eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
12348
+ under: new RegExp(`${prefix}${body}/.*$`),
12349
+ negated,
12350
+ dirOnly
12351
+ });
12308
12352
  }
12309
- return path23.join(
12310
- os7.tmpdir(),
12311
- `wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`,
12312
- `${key}.sock`
12313
- );
12353
+ return (relPath, isDir) => {
12354
+ const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
12355
+ let ignored = false;
12356
+ for (const r of rules) {
12357
+ const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
12358
+ if (re.test(p)) ignored = !r.negated;
12359
+ }
12360
+ return ignored;
12361
+ };
12314
12362
  }
12315
- function projectIndexServerMetadataPath(projectRoot, indexDir) {
12316
- return path23.join(
12317
- path23.resolve(resolveIndexDir(projectRoot, indexDir)),
12318
- PROJECT_INDEX_SERVER_METADATA_FILE
12319
- );
12363
+ async function loadGitignoreMatcher(projectRoot) {
12364
+ let lines = [];
12365
+ try {
12366
+ const raw = await fs13.readFile(path17.join(projectRoot, ".gitignore"), "utf8");
12367
+ lines = raw.split("\n");
12368
+ } catch {
12369
+ }
12370
+ return compileGitignore(lines);
12320
12371
  }
12321
12372
 
12322
- // src/codebase-index/project-server-protocol.ts
12323
- var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
12324
- function encodeProjectServerMessage(message) {
12325
- return `${JSON.stringify(message)}
12326
- `;
12327
- }
12373
+ // src/codebase-index/indexer.ts
12374
+ init_languages2();
12328
12375
 
12329
- // src/codebase-index/project-server-client.ts
12330
- var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
12331
- var SERVER_START_TIMEOUT_MS = 1e4;
12332
- var SERVER_CONTROL_TIMEOUT_MS = 5e3;
12333
- var SERVER_HEALTH_TIMEOUT_MS = 3e3;
12334
- var SERVER_HEARTBEAT_INTERVAL_MS = 1e4;
12335
- var StaleProjectIndexServerError = class extends Error {
12336
- constructor(message, pid) {
12337
- super(message);
12338
- this.pid = pid;
12339
- }
12340
- pid;
12341
- name = "StaleProjectIndexServerError";
12342
- };
12343
- var connectionStates = /* @__PURE__ */ new Map();
12344
- var connectionStateListeners = /* @__PURE__ */ new Set();
12345
- var latestConnectionState = {
12346
- status: "offline",
12347
- connected: false
12348
- };
12349
- function resolveProjectIndexDaemonAvailability() {
12350
- if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
12351
- return { kind: "inline-requested" };
12352
- }
12353
- for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
12354
- try {
12355
- const url = new URL(rel, import.meta.url);
12356
- if (url.protocol === "file:" && fs17.existsSync(fileURLToPath2(url))) {
12357
- return { kind: "available", url };
12358
- }
12359
- } catch {
12376
+ // src/codebase-index/parser-dispatch.ts
12377
+ async function parseFileContent(file, content, lang) {
12378
+ switch (lang) {
12379
+ case "ts":
12380
+ case "tsx":
12381
+ case "js":
12382
+ case "jsx": {
12383
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
12384
+ return parseSymbols8({ file, content, lang });
12385
+ }
12386
+ case "go": {
12387
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
12388
+ return parseSymbols8({ file, content, lang: "go" });
12389
+ }
12390
+ case "py": {
12391
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
12392
+ return parseSymbols8({ file, content, lang: "py" });
12393
+ }
12394
+ case "rs": {
12395
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
12396
+ return parseSymbols8({ file, content, lang: "rs" });
12397
+ }
12398
+ case "json": {
12399
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
12400
+ return parseSymbols8({ file, content, lang: "json" });
12401
+ }
12402
+ case "yaml": {
12403
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
12404
+ return parseSymbols8({ file, content, lang: "yaml" });
12405
+ }
12406
+ default: {
12407
+ const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
12408
+ return parseSymbols8({ file, content, lang });
12360
12409
  }
12361
12410
  }
12362
- return { kind: "missing-build" };
12363
- }
12364
- function resolveProjectServerUrl() {
12365
- const availability = resolveProjectIndexDaemonAvailability();
12366
- return availability.kind === "available" ? availability.url : null;
12367
- }
12368
- function projectIndexServerExpectedBuildId() {
12369
- const override = process.env["WRONGSTACK_INDEX_SERVER_BUILD_ID"]?.trim();
12370
- if (override) return override;
12371
- const url = resolveProjectServerUrl();
12372
- return url ? projectIndexServerBuildId(url) : null;
12373
- }
12374
- function isProjectIndexServerAvailable() {
12375
- return resolveProjectServerUrl() !== null;
12376
12411
  }
12377
- function publishConnectionState(endpoint, state) {
12378
- connectionStates.set(endpoint, state);
12379
- latestConnectionState = state;
12380
- for (const listener of connectionStateListeners) listener(state);
12412
+
12413
+ // src/codebase-index/indexer.ts
12414
+ var YIELD_EVERY_N = 50;
12415
+ function resolveParallelBatch() {
12416
+ return indexParallelBatchSize(availableParallelism());
12381
12417
  }
12382
- function getProjectIndexServerConnectionState(projectRoot, indexDir) {
12383
- if (projectRoot) {
12384
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12385
- const existing = connectionStates.get(endpoint);
12386
- if (existing) return existing;
12387
- if (!isProjectIndexServerAvailable()) {
12388
- return { status: "unavailable", connected: false };
12389
- }
12390
- return {
12391
- status: "offline",
12392
- connected: false,
12393
- projectRoot,
12394
- indexDir,
12395
- endpoint
12396
- };
12397
- }
12398
- if (latestConnectionState.endpoint) return latestConnectionState;
12399
- if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
12400
- return latestConnectionState;
12418
+ function yieldEventLoop() {
12419
+ return new Promise((resolve16) => setImmediate(resolve16));
12401
12420
  }
12402
- function onProjectIndexServerConnectionStateChange(listener) {
12403
- connectionStateListeners.add(listener);
12404
- return () => connectionStateListeners.delete(listener);
12421
+ function throwIfAborted(signal) {
12422
+ if (!signal?.aborted) return;
12423
+ if (signal.reason instanceof Error) throw signal.reason;
12424
+ throw new Error(typeof signal.reason === "string" ? signal.reason : "Indexing cancelled");
12405
12425
  }
12406
- function remoteError(message, name) {
12407
- if (name === "LockError") return new LockError(message);
12408
- if (name === "IndexTimeoutError") return new IndexTimeoutError(message);
12409
- const error = new Error(message);
12410
- if (name && name !== "Error") error.name = name;
12411
- return error;
12426
+ function isAbortError(err) {
12427
+ return err instanceof DOMException && err.name === "AbortError";
12412
12428
  }
12413
- function isProjectIndexServerHealth(value) {
12414
- if (!value || typeof value !== "object") return false;
12415
- const health = value;
12416
- const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
12417
- const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
12418
- 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";
12429
+ var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
12430
+ var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
12431
+ var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
12432
+ var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
12433
+ var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
12434
+ function isWithinProject(projectRoot, file) {
12435
+ const rel = path23.relative(projectRoot, file);
12436
+ return rel !== "" && !rel.startsWith(`..${path23.sep}`) && rel !== ".." && !path23.isAbsolute(rel);
12419
12437
  }
12420
- function delay(ms) {
12421
- return new Promise((resolve16) => {
12422
- const timer = setTimeout(resolve16, ms);
12423
- timer.unref?.();
12424
- });
12438
+ function isMissingPathError(err) {
12439
+ const code = err?.code;
12440
+ return code === "ENOENT" || code === "ENOTDIR";
12425
12441
  }
12426
- function cancellationError(signal) {
12427
- return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
12442
+ function normalizeComparablePath(value) {
12443
+ const resolved = path23.resolve(value);
12444
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
12428
12445
  }
12429
- var ProjectServerConnection = class {
12430
- constructor(projectRoot, indexDir, endpoint) {
12431
- this.projectRoot = projectRoot;
12432
- this.indexDir = indexDir;
12433
- this.endpoint = endpoint;
12434
- this.transition("offline");
12435
- }
12436
- projectRoot;
12437
- indexDir;
12438
- endpoint;
12439
- socket = null;
12440
- buffer = "";
12441
- info = null;
12442
- activity = null;
12443
- health = null;
12444
- healthCheck = null;
12445
- connecting = null;
12446
- connectResolve = null;
12447
- connectReject = null;
12448
- nextId = 1;
12449
- pending = /* @__PURE__ */ new Map();
12450
- transition(status, options = {}) {
12451
- const previous = connectionStates.get(this.endpoint);
12452
- const pid = options.pid ?? (status === "connected" ? this.info?.pid : void 0);
12453
- 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);
12454
- publishConnectionState(this.endpoint, {
12455
- status,
12456
- connected: status === "connected" || status === "degraded" || status === "unresponsive",
12457
- projectRoot: this.projectRoot,
12458
- indexDir: this.indexDir,
12459
- endpoint: this.endpoint,
12460
- pid,
12461
- lastError,
12462
- ...this.activity ? { activity: this.activity } : {},
12463
- ...this.health ? { health: this.health } : {}
12464
- });
12465
- }
12466
- isConnected() {
12467
- return this.socket !== null && !this.socket.destroyed && this.info !== null;
12468
- }
12469
- async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
12470
- await this.ensureConnected(spawnIfMissing);
12471
- if (this.healthCheck) return this.healthCheck;
12472
- const startedAt = Date.now();
12473
- this.healthCheck = this.request({ type: "ping" }, { timeoutMs }).then((server) => {
12474
- const now = Date.now();
12475
- this.health = {
12476
- status: "healthy",
12477
- checkedAt: now,
12478
- lastHealthyAt: now,
12479
- latencyMs: Math.max(0, now - startedAt),
12480
- missedHeartbeats: 0,
12481
- ...isProjectIndexServerHealth(server) ? { server } : {}
12482
- };
12483
- this.transition("connected", { pid: this.info?.pid });
12484
- return this.health;
12485
- }).catch((error) => {
12486
- if (!this.isConnected()) throw error;
12487
- if ((this.health?.lastHealthyAt ?? 0) > startedAt) return this.health;
12488
- const missedHeartbeats = (this.health?.missedHeartbeats ?? 0) + 1;
12489
- const status = missedHeartbeats >= 3 ? "unresponsive" : "degraded";
12490
- this.health = {
12491
- status,
12492
- checkedAt: Date.now(),
12493
- lastHealthyAt: this.health?.lastHealthyAt ?? null,
12494
- latencyMs: null,
12495
- missedHeartbeats,
12496
- ...this.health?.server ? { server: this.health.server } : {}
12497
- };
12498
- this.transition(status, { pid: this.info?.pid, error });
12499
- return this.health;
12500
- }).finally(() => {
12501
- this.healthCheck = null;
12502
- });
12503
- return this.healthCheck;
12504
- }
12505
- markResponsive() {
12506
- const now = Date.now();
12507
- this.health = {
12508
- status: "healthy",
12509
- checkedAt: now,
12510
- lastHealthyAt: now,
12511
- latencyMs: this.health?.latencyMs ?? null,
12512
- missedHeartbeats: 0,
12513
- ...this.health?.server ? { server: this.health.server } : {}
12446
+ function gitOutput(projectRoot, args) {
12447
+ return new Promise((resolve16, reject) => {
12448
+ execFile2(
12449
+ "git",
12450
+ ["-C", projectRoot, ...args],
12451
+ {
12452
+ encoding: "buffer",
12453
+ maxBuffer: MAX_GIT_FILE_LIST_BYTES,
12454
+ windowsHide: true
12455
+ },
12456
+ (error, stdout) => {
12457
+ if (error) reject(error);
12458
+ else resolve16(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
12459
+ }
12460
+ );
12461
+ });
12462
+ }
12463
+ async function findGitSourceFiles(projectRoot, ignore, signal) {
12464
+ try {
12465
+ throwIfAborted(signal);
12466
+ const topLevel = (await gitOutput(projectRoot, ["rev-parse", "--show-toplevel"])).toString("utf8").trim();
12467
+ if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
12468
+ throwIfAborted(signal);
12469
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
12470
+ const [output, statusOutput] = await Promise.all([
12471
+ gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
12472
+ gitOutput(projectRoot, [
12473
+ "status",
12474
+ "--porcelain=v1",
12475
+ "-z",
12476
+ "--untracked-files=all",
12477
+ "--ignored=no"
12478
+ ])
12479
+ ]);
12480
+ throwIfAborted(signal);
12481
+ const dirty = /* @__PURE__ */ new Set();
12482
+ const deleted = /* @__PURE__ */ new Set();
12483
+ const statusRecords = statusOutput.toString("utf8").split("\0");
12484
+ for (let i = 0; i < statusRecords.length; i++) {
12485
+ const record = statusRecords[i];
12486
+ if (!record) continue;
12487
+ const status = record.slice(0, 2);
12488
+ const changedPath = path23.resolve(projectRoot, record.slice(3));
12489
+ dirty.add(changedPath);
12490
+ if (status.includes("D")) deleted.add(changedPath);
12491
+ if (status.includes("R") || status.includes("C")) {
12492
+ const source = statusRecords[++i];
12493
+ if (source) dirty.add(path23.resolve(projectRoot, source));
12494
+ }
12495
+ }
12496
+ const files = [];
12497
+ for (const relative12 of output.toString("utf8").split("\0")) {
12498
+ if (!relative12) continue;
12499
+ const portable = relative12.replace(/\\/g, "/");
12500
+ if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path23.posix.basename(portable))) {
12501
+ continue;
12502
+ }
12503
+ const full = path23.resolve(projectRoot, relative12);
12504
+ if (deleted.has(full)) continue;
12505
+ const ext = path23.extname(relative12).toLowerCase();
12506
+ if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
12507
+ }
12508
+ return {
12509
+ files,
12510
+ trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
12514
12511
  };
12512
+ } catch {
12513
+ return null;
12515
12514
  }
12516
- async call(op, args, options) {
12517
- if (options.signal?.aborted) throw cancellationError(options.signal);
12518
- await this.ensureConnected(true);
12519
- if (options.signal?.aborted) throw cancellationError(options.signal);
12520
- return this.request({ type: "request", op, args }, options);
12515
+ }
12516
+ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
12517
+ const gitFiles = await findGitSourceFiles(projectRoot, ignore, signal);
12518
+ if (gitFiles) {
12519
+ return {
12520
+ files: gitFiles.files,
12521
+ complete: true,
12522
+ errors: [],
12523
+ trustedUnchanged: gitFiles.trustedUnchanged
12524
+ };
12521
12525
  }
12522
- async shutdownRemote(reason) {
12523
- try {
12524
- await this.ensureConnected(false);
12525
- } catch {
12526
- return { stopped: false, reason: "not-running" };
12526
+ const results = [];
12527
+ const errors = [];
12528
+ let complete = true;
12529
+ const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
12530
+ const indexableExts = new Set(INDEXABLE_EXTENSIONS);
12531
+ let dirCount = 0;
12532
+ const walk = async (dir) => {
12533
+ throwIfAborted(signal);
12534
+ if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
12535
+ await yieldEventLoop();
12536
+ throwIfAborted(signal);
12527
12537
  }
12528
- const pid = this.info?.pid;
12538
+ let entries;
12529
12539
  try {
12530
- this.transition("stopping", { pid });
12531
- await this.request(
12532
- { type: "shutdown", reason },
12533
- { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
12534
- );
12535
- return { stopped: true, pid };
12536
- } catch (error) {
12537
- const forceKilled = this.forceKillKnownServer();
12538
- return {
12539
- stopped: forceKilled,
12540
- pid,
12541
- reason: forceKilled ? `force-killed after graceful shutdown failed: ${error instanceof Error ? error.message : String(error)}` : error instanceof Error ? error.message : String(error)
12542
- };
12543
- } finally {
12544
- this.close();
12540
+ entries = await fs17.readdir(dir, { withFileTypes: true });
12541
+ } catch (err) {
12542
+ complete = false;
12543
+ errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
12544
+ return;
12545
12545
  }
12546
- }
12547
- async configure(watchExternal, debounceMs) {
12548
- await this.ensureConnected(true);
12549
- const startedAt = Date.now();
12550
- const result = await this.request(
12551
- { type: "configure", watchExternal, debounceMs },
12552
- { timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
12553
- );
12554
- if (isProjectIndexServerHealth(result.health)) {
12555
- const now = Date.now();
12556
- this.health = {
12557
- status: "healthy",
12558
- checkedAt: now,
12559
- lastHealthyAt: now,
12560
- latencyMs: Math.max(0, now - startedAt),
12561
- missedHeartbeats: 0,
12562
- server: result.health
12563
- };
12564
- this.transition("connected", { pid: this.info?.pid });
12546
+ dirCount++;
12547
+ for (const e of entries) {
12548
+ if (ignoreSet.has(e.name)) continue;
12549
+ const full = path23.join(dir, e.name);
12550
+ const rel = path23.relative(projectRoot, full).replace(/\\/g, "/");
12551
+ if (e.isDirectory()) {
12552
+ if (isGitIgnored(rel, true)) continue;
12553
+ await walk(full);
12554
+ } else if (e.isFile()) {
12555
+ if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
12556
+ const ext = path23.extname(e.name).toLowerCase();
12557
+ if (indexableExts.has(ext) || detectLang(full) !== null) {
12558
+ results.push(full);
12559
+ }
12560
+ }
12561
+ }
12562
+ };
12563
+ await walk(projectRoot);
12564
+ return { files: results, complete, errors };
12565
+ }
12566
+ function assignRefsToSymbols2(refs, symbols) {
12567
+ if (refs.length === 0 || symbols.length === 0) return [];
12568
+ const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
12569
+ const seen = /* @__PURE__ */ new Set();
12570
+ const assigned = [];
12571
+ for (const ref of refs) {
12572
+ let owner2;
12573
+ for (const symbol of ordered) {
12574
+ if (symbol.line > ref.line) break;
12575
+ owner2 = symbol;
12565
12576
  }
12577
+ if (!owner2 && ref.callType === "import") owner2 = ordered[0];
12578
+ if (!owner2 || owner2.id <= 0) continue;
12579
+ const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
12580
+ if (seen.has(key)) continue;
12581
+ seen.add(key);
12582
+ assigned.push({ ...ref, fromId: owner2.id });
12566
12583
  }
12567
- close() {
12568
- const socket = this.socket;
12569
- this.socket = null;
12570
- this.info = null;
12571
- this.activity = null;
12572
- this.health = null;
12573
- this.connectReject?.(new Error("codebase-index client disconnected"));
12574
- this.connectResolve = null;
12575
- this.connectReject = null;
12576
- if (socket && !socket.destroyed) socket.destroy();
12577
- this.rejectPending(new Error("codebase-index client disconnected"));
12578
- this.transition("offline");
12579
- maybeStopHeartbeatLoop();
12584
+ return assigned;
12585
+ }
12586
+ async function runIndexerWithStore(store, opts) {
12587
+ const { projectRoot, langs, ignore = [], signal } = opts;
12588
+ const relationGraphVersion = "2";
12589
+ const refResolutionVersion = "2";
12590
+ const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
12591
+ const needsFullRefResolution = force || store.getMetadata("ref_resolution_version") !== refResolutionVersion;
12592
+ const startMs = Date.now();
12593
+ const errors = [];
12594
+ const langStats = {};
12595
+ let filesIndexed = 0;
12596
+ let symbolsIndexed = 0;
12597
+ const isGitIgnored = await loadGitignoreMatcher(projectRoot);
12598
+ let files;
12599
+ let discoveredFiles = null;
12600
+ let discoveryComplete = true;
12601
+ let trustedUnchanged;
12602
+ if (opts.files && opts.files.length > 0) {
12603
+ files = opts.files.map((f) => path23.resolve(projectRoot, f)).filter((f) => {
12604
+ if (!isWithinProject(projectRoot, f)) return false;
12605
+ const rel = path23.relative(projectRoot, f).replace(/\\/g, "/");
12606
+ return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path23.basename(f)) && !isGitIgnored(rel, false);
12607
+ });
12608
+ } else {
12609
+ const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
12610
+ files = discovery.files;
12611
+ errors.push(...discovery.errors);
12612
+ discoveryComplete = discovery.complete;
12613
+ discoveredFiles = new Set(files);
12614
+ trustedUnchanged = discovery.trustedUnchanged;
12580
12615
  }
12581
- request(message, options) {
12582
- const socket = this.socket;
12583
- if (!socket || socket.destroyed) {
12584
- return Promise.reject(new Error("codebase-index server connection is not available"));
12585
- }
12586
- const id = this.nextId++;
12587
- return new Promise((resolve16, reject) => {
12588
- const timer = setTimeout(() => {
12589
- const entry = this.pending.get(id);
12590
- if (!entry) return;
12591
- this.pending.delete(id);
12592
- this.write({ type: "cancel", id });
12593
- const error = new IndexTimeoutError(
12594
- `Index ${message.type === "request" ? message.op : message.type} exceeded its ${options.timeoutMs}ms watchdog timeout`
12595
- );
12596
- this.cleanupPending(entry);
12597
- entry.reject(error);
12598
- }, options.timeoutMs);
12599
- timer.unref?.();
12600
- const signal = options.signal;
12601
- const onAbort = signal ? () => {
12602
- const entry = this.pending.get(id);
12603
- if (!entry) return;
12604
- this.pending.delete(id);
12605
- this.write({ type: "cancel", id });
12606
- this.cleanupPending(entry);
12607
- entry.reject(cancellationError(signal));
12608
- } : void 0;
12609
- this.pending.set(id, {
12610
- resolve: resolve16,
12611
- reject,
12612
- timer,
12613
- signal,
12614
- onAbort,
12615
- onProgress: options.onProgress
12616
- });
12617
- if (signal && onAbort) {
12618
- signal.addEventListener("abort", onAbort, { once: true });
12619
- if (signal.aborted) {
12620
- onAbort();
12621
- return;
12622
- }
12623
- }
12624
- this.write({ ...message, id });
12616
+ if (langs && langs.length > 0) {
12617
+ const langSet = new Set(langs);
12618
+ files = files.filter((f) => {
12619
+ const lang = detectLang(f);
12620
+ return lang ? langSet.has(lang) : false;
12625
12621
  });
12626
12622
  }
12627
- async ensureConnected(spawnIfMissing) {
12628
- if (this.socket && !this.socket.destroyed && this.info) return;
12629
- if (this.connecting) return this.connecting;
12630
- this.transition("connecting");
12631
- this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
12632
- this.transition("error", { error });
12633
- throw error;
12634
- }).finally(() => {
12635
- this.connecting = null;
12623
+ if (force) store.clearAll();
12624
+ const existingMeta = /* @__PURE__ */ new Map();
12625
+ if (!force) {
12626
+ for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
12627
+ }
12628
+ const totalFilesForProgress = files.length;
12629
+ let filesPreSkipped = 0;
12630
+ if (!force && trustedUnchanged) {
12631
+ files = files.filter((file) => {
12632
+ const meta = existingMeta.get(file);
12633
+ if (!meta || !trustedUnchanged.has(file)) return true;
12634
+ langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
12635
+ symbolsIndexed += meta.symbolCount;
12636
+ filesIndexed++;
12637
+ filesPreSkipped++;
12638
+ return false;
12636
12639
  });
12637
- return this.connecting;
12640
+ if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
12638
12641
  }
12639
- async connectWithElection(spawnIfMissing) {
12640
- const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
12641
- let spawned = false;
12642
- let staleAttempts = 0;
12643
- let lastError = new Error("codebase-index server unavailable");
12644
- while (Date.now() < deadline) {
12645
- try {
12646
- await this.connectOnce();
12647
- return;
12648
- } catch (error) {
12649
- lastError = error;
12650
- if (error instanceof StaleProjectIndexServerError) {
12651
- staleAttempts++;
12652
- if (!spawnIfMissing) break;
12653
- if (staleAttempts >= 3) this.forceKillServer(error.pid);
12654
- spawned = false;
12655
- await delay(100);
12656
- continue;
12642
+ const parallelBatch = resolveParallelBatch();
12643
+ let filesSinceLastYield = 0;
12644
+ for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
12645
+ const batchEnd = Math.min(batchStart + parallelBatch, files.length);
12646
+ const batchFiles = files.slice(batchStart, batchEnd);
12647
+ opts.onProgress?.(filesPreSkipped + batchEnd, totalFilesForProgress);
12648
+ filesSinceLastYield += batchFiles.length;
12649
+ if (filesSinceLastYield >= YIELD_EVERY_N) {
12650
+ filesSinceLastYield = 0;
12651
+ await yieldEventLoop();
12652
+ if (isFrugalPerf()) {
12653
+ await new Promise((r) => setTimeout(r, 8));
12654
+ }
12655
+ throwIfAborted(signal);
12656
+ }
12657
+ const statOpts = signal ? { signal } : {};
12658
+ const statReadParse = await Promise.allSettled(
12659
+ batchFiles.map(
12660
+ async (file) => {
12661
+ let stat17;
12662
+ try {
12663
+ stat17 = await fs17.stat(file, statOpts);
12664
+ } catch (e) {
12665
+ if (isAbortError(e)) throw e;
12666
+ return {
12667
+ file,
12668
+ stat: null,
12669
+ lang: "",
12670
+ parsed: null,
12671
+ error: `stat error: ${e instanceof Error ? e.message : String(e)}`,
12672
+ missing: isMissingPathError(e)
12673
+ };
12674
+ }
12675
+ if (!stat17.isFile()) return { file, stat: stat17, lang: "", parsed: null };
12676
+ const lang = detectLang(file);
12677
+ if (!lang) return { file, stat: stat17, lang: "", parsed: null };
12678
+ if (stat17.size > MAX_INDEX_FILE_BYTES) {
12679
+ return {
12680
+ file,
12681
+ stat: stat17,
12682
+ lang,
12683
+ parsed: null,
12684
+ error: `file too large (${stat17.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
12685
+ };
12686
+ }
12687
+ const meta = existingMeta.get(file);
12688
+ if (!force && meta && meta.mtimeMs === Math.floor(stat17.mtimeMs)) {
12689
+ return { file, stat: stat17, lang, parsed: null, skippedMeta: meta };
12690
+ }
12691
+ let content;
12692
+ try {
12693
+ content = await fs17.readFile(file, { encoding: "utf8", signal });
12694
+ } catch (e) {
12695
+ if (isAbortError(e)) throw e;
12696
+ return {
12697
+ file,
12698
+ stat: stat17,
12699
+ lang,
12700
+ parsed: null,
12701
+ error: `read error: ${e instanceof Error ? e.message : String(e)}`
12702
+ };
12703
+ }
12704
+ let parsed;
12705
+ try {
12706
+ parsed = await parseFileContent(file, content, lang);
12707
+ } catch (e) {
12708
+ return {
12709
+ file,
12710
+ stat: stat17,
12711
+ lang,
12712
+ parsed: null,
12713
+ error: `parse error: ${e instanceof Error ? e.message : String(e)}`
12714
+ };
12715
+ }
12716
+ return { file, stat: stat17, lang, parsed, content };
12657
12717
  }
12718
+ )
12719
+ );
12720
+ const batchEntries = [];
12721
+ const deleteForFiles = [];
12722
+ for (let fi = 0; fi < statReadParse.length; fi++) {
12723
+ const settled = statReadParse[fi];
12724
+ const file = expectDefined6(batchFiles[fi]);
12725
+ if (settled.status === "rejected") {
12726
+ const err = settled.reason;
12727
+ if (err instanceof Error && isAbortError(err)) throw err;
12728
+ errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
12729
+ continue;
12658
12730
  }
12659
- if (!spawnIfMissing) break;
12660
- if (!spawned) {
12661
- this.spawnDetachedServer();
12662
- spawned = true;
12731
+ const result = settled.value;
12732
+ if (result.error) {
12733
+ if (result.missing) store.deleteFile(file);
12734
+ errors.push(`${file}: ${result.error}`);
12735
+ continue;
12663
12736
  }
12664
- await delay(75);
12665
- }
12666
- throw lastError;
12667
- }
12668
- connectOnce() {
12669
- this.socket?.destroy();
12670
- this.socket = null;
12671
- this.info = null;
12672
- this.activity = null;
12673
- this.health = null;
12674
- this.buffer = "";
12675
- return new Promise((resolve16, reject) => {
12676
- const socket = net3.createConnection(this.endpoint);
12677
- this.socket = socket;
12678
- socket.setEncoding("utf8");
12679
- const timer = setTimeout(() => {
12680
- reject(new Error("codebase-index server handshake timed out"));
12681
- socket.destroy();
12682
- }, CONNECT_ATTEMPT_TIMEOUT_MS);
12683
- timer.unref?.();
12684
- const finishResolve = () => {
12685
- clearTimeout(timer);
12686
- this.connectResolve = null;
12687
- this.connectReject = null;
12688
- resolve16();
12689
- };
12690
- const finishReject = (error) => {
12691
- clearTimeout(timer);
12692
- this.connectResolve = null;
12693
- this.connectReject = null;
12694
- reject(error);
12695
- };
12696
- this.connectResolve = finishResolve;
12697
- this.connectReject = finishReject;
12698
- socket.on("data", (chunk) => this.onData(socket, chunk));
12699
- socket.on("error", (error) => {
12700
- if (!this.info) finishReject(error);
12737
+ const { stat: stat17, lang, parsed } = result;
12738
+ if (result.skippedMeta) {
12739
+ langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
12740
+ symbolsIndexed += result.skippedMeta.symbolCount;
12741
+ filesIndexed++;
12742
+ continue;
12743
+ }
12744
+ if (!lang || !parsed) {
12745
+ if (lang) {
12746
+ store.upsertFile({
12747
+ file,
12748
+ lang,
12749
+ mtimeMs: Math.floor(stat17.mtimeMs),
12750
+ symbolCount: 0,
12751
+ lastIndexed: Date.now()
12752
+ });
12753
+ filesIndexed++;
12754
+ }
12755
+ continue;
12756
+ }
12757
+ if (parsed.symbols.length === 0) {
12758
+ store.replaceEmptyFile({
12759
+ file,
12760
+ lang,
12761
+ mtimeMs: Math.floor(stat17.mtimeMs),
12762
+ symbolCount: 0,
12763
+ lastIndexed: Date.now()
12764
+ });
12765
+ filesIndexed++;
12766
+ continue;
12767
+ }
12768
+ batchEntries.push({
12769
+ file,
12770
+ lang,
12771
+ symbols: parsed.symbols,
12772
+ refs: parsed.refs ?? [],
12773
+ mtimeMs: Math.floor(stat17.mtimeMs),
12774
+ symbolCount: parsed.symbols.length
12701
12775
  });
12702
- socket.on("close", () => this.onClose(socket));
12703
- });
12704
- }
12705
- onData(socket, chunk) {
12706
- if (socket !== this.socket) return;
12707
- this.buffer += chunk;
12708
- while (true) {
12709
- const newline = this.buffer.indexOf("\n");
12710
- if (newline < 0) {
12711
- if (this.buffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
12712
- socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
12776
+ deleteForFiles.push(file);
12777
+ }
12778
+ if (batchEntries.length > 0) {
12779
+ try {
12780
+ store.commitBatch(batchEntries, { deleteForFiles });
12781
+ for (const entry of batchEntries) {
12782
+ const count = entry.symbols.length;
12783
+ symbolsIndexed += count;
12784
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
12785
+ filesIndexed++;
12786
+ }
12787
+ } catch (err) {
12788
+ const message = err instanceof Error ? err.message : String(err);
12789
+ errors.push(`commitBatch failed: ${message} \u2014 falling back to per-file writes`);
12790
+ for (const entry of batchEntries) {
12791
+ try {
12792
+ store.deleteRefsForFile(entry.file);
12793
+ store.deleteSymbolsForFile(entry.file);
12794
+ const symbolsWithIds = store.insertSymbols(entry.symbols);
12795
+ symbolsIndexed += symbolsWithIds.length;
12796
+ langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
12797
+ filesIndexed++;
12798
+ if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
12799
+ const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
12800
+ if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
12801
+ }
12802
+ store.resolveRefsForNames([
12803
+ ...entry.symbols.map((symbol) => symbol.name),
12804
+ ...entry.refs.map((ref) => ref.toName)
12805
+ ]);
12806
+ store.upsertFile({
12807
+ file: entry.file,
12808
+ lang: entry.lang,
12809
+ mtimeMs: entry.mtimeMs,
12810
+ symbolCount: entry.symbolCount,
12811
+ lastIndexed: Date.now()
12812
+ });
12813
+ } catch (innerErr) {
12814
+ errors.push(
12815
+ `fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
12816
+ );
12817
+ }
12713
12818
  }
12714
- return;
12715
- }
12716
- if (newline > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
12717
- socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
12718
- return;
12719
- }
12720
- const line = this.buffer.slice(0, newline);
12721
- this.buffer = this.buffer.slice(newline + 1);
12722
- if (!line) continue;
12723
- let message;
12724
- try {
12725
- message = JSON.parse(line);
12726
- } catch {
12727
- socket.destroy(new Error("invalid codebase-index server response"));
12728
- return;
12729
12819
  }
12730
- this.onMessage(message);
12731
12820
  }
12732
12821
  }
12733
- onMessage(message) {
12734
- if (message.type === "hello") {
12735
- if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
12736
- this.rejectStaleServer(
12737
- message,
12738
- `codebase-index protocol mismatch: client=${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
12739
- );
12740
- return;
12741
- }
12742
- const expectedBuildId = projectIndexServerExpectedBuildId();
12743
- if (expectedBuildId && message.buildId !== expectedBuildId) {
12744
- this.rejectStaleServer(
12745
- message,
12746
- `codebase-index build mismatch: client=${expectedBuildId}, server=${message.buildId ?? "legacy"}`
12747
- );
12748
- return;
12822
+ if (discoveredFiles && discoveryComplete) {
12823
+ for (const [file_] of existingMeta) {
12824
+ if (!discoveredFiles.has(file_)) {
12825
+ store.deleteFile(file_);
12749
12826
  }
12750
- this.info = message;
12751
- this.markResponsive();
12752
- this.transition("connected", { pid: message.pid });
12753
- ensureHeartbeatLoop();
12754
- this.connectResolve?.();
12755
- return;
12756
- }
12757
- if (message.type === "index-state") {
12758
- this.activity = message.state;
12759
- this.markResponsive();
12760
- this.transition("connected", { pid: this.info?.pid });
12761
- return;
12762
- }
12763
- const entry = this.pending.get(message.id);
12764
- if (!entry) return;
12765
- this.markResponsive();
12766
- const status = connectionStates.get(this.endpoint)?.status;
12767
- if (status === "degraded" || status === "unresponsive") {
12768
- this.transition("connected", { pid: this.info?.pid });
12769
- }
12770
- if (message.type === "progress") {
12771
- entry.onProgress?.(message.current, message.total);
12772
- return;
12773
- }
12774
- this.pending.delete(message.id);
12775
- this.cleanupPending(entry);
12776
- if (message.ok) entry.resolve(message.result);
12777
- else entry.reject(remoteError(message.error, message.errorName));
12778
- }
12779
- onClose(socket) {
12780
- if (socket !== this.socket) return;
12781
- const wasConnected = this.info !== null;
12782
- this.socket = null;
12783
- this.info = null;
12784
- this.activity = null;
12785
- this.health = null;
12786
- const error = new Error("codebase-index server connection closed");
12787
- this.connectReject?.(error);
12788
- this.connectResolve = null;
12789
- this.connectReject = null;
12790
- this.rejectPending(error);
12791
- if (wasConnected) this.transition("error", { error });
12792
- maybeStopHeartbeatLoop();
12793
- }
12794
- cleanupPending(entry) {
12795
- clearTimeout(entry.timer);
12796
- if (entry.signal && entry.onAbort) {
12797
- entry.signal.removeEventListener("abort", entry.onAbort);
12798
- }
12799
- }
12800
- rejectPending(error) {
12801
- const entries = [...this.pending.values()];
12802
- this.pending.clear();
12803
- for (const entry of entries) {
12804
- this.cleanupPending(entry);
12805
- entry.reject(error);
12806
- }
12807
- }
12808
- write(message) {
12809
- const socket = this.socket;
12810
- if (socket && !socket.destroyed) socket.write(encodeProjectServerMessage(message));
12811
- }
12812
- rejectStaleServer(message, reason) {
12813
- const socket = this.socket;
12814
- if (socket && !socket.destroyed) {
12815
- socket.write(
12816
- encodeProjectServerMessage({
12817
- type: "shutdown",
12818
- id: 0,
12819
- reason: "stale-build-replacement"
12820
- })
12821
- );
12822
- const timer = setTimeout(() => socket.destroy(), 25);
12823
- timer.unref?.();
12824
12827
  }
12825
- this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
12826
12828
  }
12827
- spawnDetachedServer() {
12828
- const url = resolveProjectServerUrl();
12829
- if (!url) throw new Error("built codebase-index project server is unavailable");
12830
- if (process.platform !== "win32") {
12831
- try {
12832
- fs17.rmSync(this.endpoint, { force: true });
12833
- } catch {
12834
- }
12835
- }
12836
- const args = [fileURLToPath2(url), "--project-root", this.projectRoot];
12837
- if (this.indexDir) args.push("--index-dir", this.indexDir);
12838
- const child = spawn7(process.execPath, args, {
12839
- detached: true,
12840
- stdio: "ignore",
12841
- windowsHide: true,
12842
- env: process.env
12829
+ if (needsFullRefResolution) store.resolveRefs();
12830
+ store.setMetadata("ref_resolution_version", refResolutionVersion);
12831
+ store.setMetadata("relation_graph_version", relationGraphVersion);
12832
+ if (!opts.files || filesIndexed >= 50) store.optimize();
12833
+ store.setLastIndexed(Date.now());
12834
+ if (!opts.files) store.compactIfNeeded();
12835
+ const durationMs = Date.now() - startMs;
12836
+ return {
12837
+ filesIndexed,
12838
+ symbolsIndexed,
12839
+ langStats,
12840
+ durationMs,
12841
+ errors
12842
+ };
12843
+ }
12844
+
12845
+ // src/codebase-index/index-service.ts
12846
+ async function indexService(args, hooks = {}) {
12847
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12848
+ try {
12849
+ return await runIndexerWithStore(store, {
12850
+ projectRoot: args.projectRoot,
12851
+ indexDir: args.indexDir,
12852
+ files: args.files,
12853
+ force: args.force,
12854
+ langs: args.langs,
12855
+ ignore: args.ignore,
12856
+ signal: hooks.signal,
12857
+ onProgress: hooks.onProgress
12843
12858
  });
12844
- child.unref();
12859
+ } finally {
12860
+ indexStorePool.release(store);
12845
12861
  }
12846
- forceKillKnownServer() {
12847
- const pid = this.info?.pid;
12848
- return pid ? this.forceKillServer(pid) : false;
12862
+ }
12863
+ function searchService(args) {
12864
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12865
+ try {
12866
+ return store.searchRanked(
12867
+ args.query,
12868
+ {
12869
+ kind: args.kind,
12870
+ lang: args.lang,
12871
+ file: args.file,
12872
+ lspKind: args.lspKind
12873
+ },
12874
+ args.limit
12875
+ );
12876
+ } finally {
12877
+ indexStorePool.release(store);
12849
12878
  }
12850
- forceKillServer(pid) {
12851
- if (pid === process.pid) return false;
12852
- try {
12853
- process.kill(pid);
12854
- const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
12855
- try {
12856
- const metadata = JSON.parse(fs17.readFileSync(metadataPath, "utf8"));
12857
- if (metadata.pid === pid) fs17.rmSync(metadataPath, { force: true });
12858
- } catch {
12859
- }
12860
- return true;
12861
- } catch {
12862
- return false;
12863
- }
12879
+ }
12880
+ function statsService(args) {
12881
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12882
+ try {
12883
+ return store.getStats();
12884
+ } finally {
12885
+ indexStorePool.release(store);
12864
12886
  }
12865
- };
12866
- var connections = /* @__PURE__ */ new Map();
12867
- var heartbeatTimer;
12868
- function ensureHeartbeatLoop() {
12869
- if (heartbeatTimer) return;
12870
- heartbeatTimer = setInterval(() => {
12871
- for (const connection of connections.values()) {
12872
- if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
12873
- });
12874
- }
12875
- }, SERVER_HEARTBEAT_INTERVAL_MS);
12876
- heartbeatTimer.unref?.();
12877
12887
  }
12878
- function maybeStopHeartbeatLoop() {
12879
- if (!heartbeatTimer) return;
12880
- if ([...connections.values()].some((connection) => connection.isConnected())) return;
12881
- clearInterval(heartbeatTimer);
12882
- heartbeatTimer = void 0;
12888
+ function packageGraphService(args) {
12889
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12890
+ try {
12891
+ return store.getPackageGraph();
12892
+ } finally {
12893
+ indexStorePool.release(store);
12894
+ }
12883
12895
  }
12884
- function connectionFor(projectRoot, indexDir) {
12885
- const endpoint = projectIndexServerEndpoint(projectRoot, indexDir);
12886
- let connection = connections.get(endpoint);
12887
- if (!connection) {
12888
- connection = new ProjectServerConnection(projectRoot, indexDir, endpoint);
12889
- connections.set(endpoint, connection);
12896
+ function fileGraphService(args) {
12897
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12898
+ try {
12899
+ return store.getFileGraph(args.packageFilter);
12900
+ } finally {
12901
+ indexStorePool.release(store);
12890
12902
  }
12891
- return connection;
12892
12903
  }
12893
- function callProjectIndexServer(op, args, options) {
12894
- return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
12904
+ function symbolGraphService(args) {
12905
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
12906
+ try {
12907
+ return store.getSymbolGraph(args.fileFilter);
12908
+ } finally {
12909
+ indexStorePool.release(store);
12910
+ }
12895
12911
  }
12896
12912
 
12897
12913
  // src/codebase-index/background-indexer.ts
@@ -13002,8 +13018,17 @@ function terminateWorker(reason) {
13002
13018
  if (w) void w.terminate().catch(() => {
13003
13019
  });
13004
13020
  }
13021
+ var warnedInvalidEndpoints = /* @__PURE__ */ new Set();
13022
+ function warnEndpointInvalidOnce(availability) {
13023
+ if (warnedInvalidEndpoints.has(availability.endpoint)) return;
13024
+ warnedInvalidEndpoints.add(availability.endpoint);
13025
+ process.stderr.write(
13026
+ `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.
13027
+ `
13028
+ );
13029
+ }
13005
13030
  function callIndexOp(op, args, opts) {
13006
- const availability = resolveProjectIndexDaemonAvailability();
13031
+ const availability = resolveProjectIndexDaemonAvailability(args.projectRoot, args.indexDir);
13007
13032
  if (availability.kind === "available") {
13008
13033
  return callProjectIndexServer(op, args, opts);
13009
13034
  }
@@ -13014,6 +13039,14 @@ function callIndexOp(op, args, opts) {
13014
13039
  )
13015
13040
  );
13016
13041
  }
13042
+ if (availability.kind === "endpoint-invalid") {
13043
+ warnEndpointInvalidOnce(availability);
13044
+ return Promise.reject(
13045
+ new Error(
13046
+ `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.`
13047
+ )
13048
+ );
13049
+ }
13017
13050
  const w = ensureWorker();
13018
13051
  if (!w) return callInline(op, args, opts);
13019
13052
  if (opts.signal?.aborted) {