@wrongstack/tools 0.296.2 → 0.296.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/artifacts.d.ts +2 -0
- package/dist/browser/artifacts.d.ts.map +1 -1
- package/dist/browser/index.js +41 -0
- package/dist/browser/index.js.map +2 -2
- package/dist/builtin.js +1812 -1426
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/dead-code-scan.d.ts +2 -2
- package/dist/codebase-index/dead-code-scan.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +1 -0
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +1827 -1542
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/project-server-client.d.ts +13 -1
- package/dist/codebase-index/project-server-client.d.ts.map +1 -1
- package/dist/codebase-index/project-server-endpoint.d.ts +29 -1
- package/dist/codebase-index/project-server-endpoint.d.ts.map +1 -1
- package/dist/codebase-index/project-server.js +226 -152
- package/dist/codebase-index/project-server.js.map +4 -4
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +214 -145
- package/dist/codebase-index/worker.js.map +3 -3
- package/dist/codebase-index/writer.d.ts +19 -1
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1851 -1462
- package/dist/index.js.map +4 -4
- package/dist/pack.js +1812 -1426
- package/dist/pack.js.map +4 -4
- package/dist/read.d.ts +31 -0
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +5465 -78
- package/dist/read.js.map +4 -4
- package/dist/tool-tier.js +1812 -1426
- package/dist/tool-tier.js.map +4 -4
- package/package.json +3 -3
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
|
|
4896
|
+
import * as path18 from "node:path";
|
|
4897
4897
|
function detectLang(file) {
|
|
4898
|
-
const base =
|
|
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 =
|
|
4905
|
+
const ext = path18.extname(base).toLowerCase();
|
|
4906
4906
|
if (!ext) return null;
|
|
4907
4907
|
return EXT_TO_LANG[ext] ?? null;
|
|
4908
4908
|
}
|
|
@@ -5131,11 +5131,18 @@ async function parseSymbols(opts) {
|
|
|
5131
5131
|
} else if (ts.isHeritageClause(node)) {
|
|
5132
5132
|
for (const t of node.types) {
|
|
5133
5133
|
const name = getTypeName(t.expression);
|
|
5134
|
-
if (name)
|
|
5134
|
+
if (name)
|
|
5135
|
+
refs.push({
|
|
5136
|
+
fromId: 0,
|
|
5137
|
+
toName: name,
|
|
5138
|
+
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
5139
|
+
line: lineNum
|
|
5140
|
+
});
|
|
5135
5141
|
}
|
|
5136
5142
|
} else if (ts.isImportDeclaration(node)) {
|
|
5137
|
-
|
|
5138
|
-
|
|
5143
|
+
emitImportSpecifierRefs(node, refs, lineNum);
|
|
5144
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
5145
|
+
emitExportSpecifierRefs(node, refs, lineNum);
|
|
5139
5146
|
}
|
|
5140
5147
|
const scopeIdx = scopeParts.length;
|
|
5141
5148
|
pushScopeName(node, scopeParts);
|
|
@@ -5151,11 +5158,6 @@ function getTypeName(name) {
|
|
|
5151
5158
|
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
5152
5159
|
return "";
|
|
5153
5160
|
}
|
|
5154
|
-
function getModuleName(node) {
|
|
5155
|
-
const moduleSpecifier = node.moduleSpecifier;
|
|
5156
|
-
if (ts.isStringLiteral(moduleSpecifier)) return moduleSpecifier.text;
|
|
5157
|
-
return "";
|
|
5158
|
-
}
|
|
5159
5161
|
function deduplicateRefs(refs) {
|
|
5160
5162
|
const seen = /* @__PURE__ */ new Set();
|
|
5161
5163
|
return refs.filter((r) => {
|
|
@@ -5165,6 +5167,44 @@ function deduplicateRefs(refs) {
|
|
|
5165
5167
|
return true;
|
|
5166
5168
|
});
|
|
5167
5169
|
}
|
|
5170
|
+
function getImportSpecifierName(spec) {
|
|
5171
|
+
return spec.propertyName?.text ?? spec.name.text;
|
|
5172
|
+
}
|
|
5173
|
+
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
5174
|
+
const clause = node.importClause;
|
|
5175
|
+
if (!clause) return;
|
|
5176
|
+
if (clause.name) {
|
|
5177
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5178
|
+
}
|
|
5179
|
+
const bindings = clause.namedBindings;
|
|
5180
|
+
if (!bindings) return;
|
|
5181
|
+
if (ts.isNamedImports(bindings)) {
|
|
5182
|
+
for (const element of bindings.elements) {
|
|
5183
|
+
refs.push({
|
|
5184
|
+
fromId: 0,
|
|
5185
|
+
toName: getImportSpecifierName(element),
|
|
5186
|
+
callType: "import",
|
|
5187
|
+
line: lineNum
|
|
5188
|
+
});
|
|
5189
|
+
}
|
|
5190
|
+
} else if (ts.isNamespaceImport(bindings)) {
|
|
5191
|
+
refs.push({ fromId: 0, toName: bindings.name.text, callType: "import", line: lineNum });
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
5195
|
+
const clause = node.exportClause;
|
|
5196
|
+
if (clause && ts.isNamespaceExport(clause)) {
|
|
5197
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5198
|
+
return;
|
|
5199
|
+
}
|
|
5200
|
+
if (clause && ts.isNamedExports(clause)) {
|
|
5201
|
+
for (const element of clause.elements) {
|
|
5202
|
+
const originalName = element.propertyName?.text ?? element.name.text;
|
|
5203
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
|
|
5204
|
+
}
|
|
5205
|
+
return;
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5168
5208
|
var ts, tsLoad, kindMapCache;
|
|
5169
5209
|
var init_ts_parser = __esm({
|
|
5170
5210
|
"src/codebase-index/ts-parser.ts"() {
|
|
@@ -5198,10 +5238,10 @@ __export(go_parser_exports, {
|
|
|
5198
5238
|
detectLang: () => detectLang,
|
|
5199
5239
|
parseSymbols: () => parseSymbols2
|
|
5200
5240
|
});
|
|
5201
|
-
import { spawn as
|
|
5202
|
-
import * as
|
|
5203
|
-
import * as
|
|
5204
|
-
import * as
|
|
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";
|
|
5205
5245
|
async function parseSymbols2(opts) {
|
|
5206
5246
|
const { file, content, lang } = opts;
|
|
5207
5247
|
try {
|
|
@@ -5273,16 +5313,16 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5273
5313
|
try {
|
|
5274
5314
|
let scriptPath = _cachedGoScriptPath;
|
|
5275
5315
|
if (!scriptPath) {
|
|
5276
|
-
const tmpDir = await
|
|
5277
|
-
scriptPath =
|
|
5278
|
-
await
|
|
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");
|
|
5279
5319
|
_cachedGoScriptPath = scriptPath;
|
|
5280
5320
|
}
|
|
5281
5321
|
const goBinary = resolveWin32Command("go");
|
|
5282
5322
|
const goResult = await new Promise(
|
|
5283
5323
|
(resolve16, reject) => {
|
|
5284
5324
|
let settled = false;
|
|
5285
|
-
const proc =
|
|
5325
|
+
const proc = spawn5(goBinary, ["run", scriptPath], {
|
|
5286
5326
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5287
5327
|
windowsHide: true
|
|
5288
5328
|
});
|
|
@@ -5885,10 +5925,10 @@ __export(py_parser_exports, {
|
|
|
5885
5925
|
detectLang: () => detectLang,
|
|
5886
5926
|
parseSymbols: () => parseSymbols4
|
|
5887
5927
|
});
|
|
5888
|
-
import { spawn as
|
|
5889
|
-
import * as
|
|
5890
|
-
import * as
|
|
5891
|
-
import * as
|
|
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";
|
|
5892
5932
|
async function parseSymbols4(opts) {
|
|
5893
5933
|
const { file, content, lang } = opts;
|
|
5894
5934
|
try {
|
|
@@ -5910,7 +5950,7 @@ async function resolvePython() {
|
|
|
5910
5950
|
function commandIsAvailable(command) {
|
|
5911
5951
|
return new Promise((resolve16) => {
|
|
5912
5952
|
let settled = false;
|
|
5913
|
-
const proc =
|
|
5953
|
+
const proc = spawn6(command, ["--version"], {
|
|
5914
5954
|
stdio: "ignore",
|
|
5915
5955
|
windowsHide: true
|
|
5916
5956
|
});
|
|
@@ -5932,7 +5972,7 @@ function commandIsAvailable(command) {
|
|
|
5932
5972
|
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
5933
5973
|
return new Promise((resolve16, reject) => {
|
|
5934
5974
|
let settled = false;
|
|
5935
|
-
const proc =
|
|
5975
|
+
const proc = spawn6(pyBinary, [scriptPath, filePath], {
|
|
5936
5976
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5937
5977
|
windowsHide: true
|
|
5938
5978
|
});
|
|
@@ -5966,10 +6006,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
5966
6006
|
async function syncPyParse(filePath, content, lang) {
|
|
5967
6007
|
try {
|
|
5968
6008
|
if (!_cachedScriptPath) {
|
|
5969
|
-
const tmpDir =
|
|
5970
|
-
await
|
|
5971
|
-
_cachedScriptPath =
|
|
5972
|
-
await
|
|
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");
|
|
5973
6013
|
}
|
|
5974
6014
|
cachedPyBinary ??= resolvePython();
|
|
5975
6015
|
const pyBinary = await cachedPyBinary;
|
|
@@ -6223,10 +6263,10 @@ __export(rs_parser_exports, {
|
|
|
6223
6263
|
detectLang: () => detectLang,
|
|
6224
6264
|
parseSymbols: () => parseSymbols5
|
|
6225
6265
|
});
|
|
6226
|
-
import { expectDefined as
|
|
6227
|
-
import { execFile, spawn as
|
|
6228
|
-
import * as
|
|
6229
|
-
import * as
|
|
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";
|
|
6230
6270
|
async function parseSymbols5(opts) {
|
|
6231
6271
|
const { file, content, lang } = opts;
|
|
6232
6272
|
const nativeAvailable = await checkNativeParser();
|
|
@@ -6248,7 +6288,7 @@ function checkNativeParser() {
|
|
|
6248
6288
|
nativeParserAvailability ??= (async () => {
|
|
6249
6289
|
try {
|
|
6250
6290
|
await probe("rustc", ["--version"]);
|
|
6251
|
-
const toolsDir =
|
|
6291
|
+
const toolsDir = path21.join(process.cwd(), "tools");
|
|
6252
6292
|
await probe(
|
|
6253
6293
|
"cargo",
|
|
6254
6294
|
[
|
|
@@ -6257,7 +6297,7 @@ function checkNativeParser() {
|
|
|
6257
6297
|
"--format-version",
|
|
6258
6298
|
"1",
|
|
6259
6299
|
"--manifest-path",
|
|
6260
|
-
|
|
6300
|
+
path21.join(toolsDir, "Cargo.toml")
|
|
6261
6301
|
]
|
|
6262
6302
|
);
|
|
6263
6303
|
return true;
|
|
@@ -6269,17 +6309,17 @@ function checkNativeParser() {
|
|
|
6269
6309
|
}
|
|
6270
6310
|
async function tryNativeParse(file, content) {
|
|
6271
6311
|
try {
|
|
6272
|
-
const toolsDir =
|
|
6273
|
-
const crateDir =
|
|
6274
|
-
const tmpFile =
|
|
6275
|
-
await
|
|
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");
|
|
6276
6316
|
const cargoBinary = resolveWin32Command("cargo");
|
|
6277
6317
|
const result = await new Promise(
|
|
6278
6318
|
(resolve16, reject) => {
|
|
6279
6319
|
let settled = false;
|
|
6280
|
-
const proc =
|
|
6320
|
+
const proc = spawn7(
|
|
6281
6321
|
cargoBinary,
|
|
6282
|
-
["run", "--manifest-path",
|
|
6322
|
+
["run", "--manifest-path", path21.join(toolsDir, "Cargo.toml")],
|
|
6283
6323
|
{
|
|
6284
6324
|
cwd: process.cwd(),
|
|
6285
6325
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -6338,7 +6378,7 @@ function regexParse(opts) {
|
|
|
6338
6378
|
let hi = lineOffsets2.length - 1;
|
|
6339
6379
|
while (lo < hi) {
|
|
6340
6380
|
const mid = lo + hi + 1 >>> 1;
|
|
6341
|
-
if (
|
|
6381
|
+
if (expectDefined3(lineOffsets2[mid]) <= offset) lo = mid;
|
|
6342
6382
|
else hi = mid - 1;
|
|
6343
6383
|
}
|
|
6344
6384
|
return lo + 1;
|
|
@@ -6350,7 +6390,7 @@ function regexParse(opts) {
|
|
|
6350
6390
|
for (const pattern of RS_PATTERNS) {
|
|
6351
6391
|
pattern.regex.lastIndex = 0;
|
|
6352
6392
|
for (let match = pattern.regex.exec(content); match !== null; match = pattern.regex.exec(content)) {
|
|
6353
|
-
const name =
|
|
6393
|
+
const name = expectDefined3(match[1]);
|
|
6354
6394
|
const offset = match.index ?? 0;
|
|
6355
6395
|
const line = lineFromOffset(offset);
|
|
6356
6396
|
const col = offset - (lineOffsets2[line - 1] ?? 0);
|
|
@@ -6407,8 +6447,8 @@ __export(json_parser_exports, {
|
|
|
6407
6447
|
detectLang: () => detectLang,
|
|
6408
6448
|
parseSymbols: () => parseSymbols6
|
|
6409
6449
|
});
|
|
6410
|
-
import { expectDefined as
|
|
6411
|
-
import * as
|
|
6450
|
+
import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
|
|
6451
|
+
import * as path22 from "node:path";
|
|
6412
6452
|
function parseSymbols6(opts) {
|
|
6413
6453
|
const { file, content, lang } = opts;
|
|
6414
6454
|
try {
|
|
@@ -6420,7 +6460,7 @@ function parseSymbols6(opts) {
|
|
|
6420
6460
|
function regexParse2(opts) {
|
|
6421
6461
|
const { file, content, lang } = opts;
|
|
6422
6462
|
const symbols = [];
|
|
6423
|
-
const basename12 =
|
|
6463
|
+
const basename12 = path22.basename(file).toLowerCase();
|
|
6424
6464
|
const isPackageJson = basename12 === "package.json";
|
|
6425
6465
|
const isTsconfig = basename12 === "tsconfig.json" || basename12 === "tsconfig.build.json";
|
|
6426
6466
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
@@ -6435,22 +6475,22 @@ function regexParse2(opts) {
|
|
|
6435
6475
|
let hi = lineOffsets2.length - 1;
|
|
6436
6476
|
while (lo < hi) {
|
|
6437
6477
|
const mid = lo + hi + 1 >>> 1;
|
|
6438
|
-
if (
|
|
6478
|
+
if (expectDefined4(lineOffsets2[mid]) <= offset) lo = mid;
|
|
6439
6479
|
else hi = mid - 1;
|
|
6440
6480
|
}
|
|
6441
6481
|
return lo + 1;
|
|
6442
6482
|
}
|
|
6443
6483
|
const rootMatch = content.match(/^\s*\{/m);
|
|
6444
6484
|
if (rootMatch) {
|
|
6445
|
-
const offset =
|
|
6485
|
+
const offset = expectDefined4(rootMatch.index);
|
|
6446
6486
|
const line = lineFromOffset(offset);
|
|
6447
6487
|
symbols.push(
|
|
6448
6488
|
makeSymbol({
|
|
6449
|
-
name:
|
|
6489
|
+
name: path22.basename(file),
|
|
6450
6490
|
kind: "object",
|
|
6451
6491
|
line,
|
|
6452
6492
|
col: 0,
|
|
6453
|
-
signature: `"${
|
|
6493
|
+
signature: `"${path22.basename(file)}" = { ... }`,
|
|
6454
6494
|
file,
|
|
6455
6495
|
lang
|
|
6456
6496
|
})
|
|
@@ -6458,7 +6498,7 @@ function regexParse2(opts) {
|
|
|
6458
6498
|
}
|
|
6459
6499
|
const topLevelKeyRegex = /^\s*"([^"]+)"\s*:/gm;
|
|
6460
6500
|
for (let match = topLevelKeyRegex.exec(content); match !== null; match = topLevelKeyRegex.exec(content)) {
|
|
6461
|
-
const key =
|
|
6501
|
+
const key = expectDefined4(match[1]);
|
|
6462
6502
|
const offset = match.index ?? 0;
|
|
6463
6503
|
const line = lineFromOffset(offset);
|
|
6464
6504
|
const col = offset - (lineOffsets2[line - 1] ?? 0);
|
|
@@ -6505,7 +6545,7 @@ function regexParse2(opts) {
|
|
|
6505
6545
|
const defsRegex = /"\$defs"\s*:|"\$defs"\s*:/g;
|
|
6506
6546
|
const defsMatch = defsRegex.exec(content);
|
|
6507
6547
|
if (defsMatch !== null) {
|
|
6508
|
-
const offset =
|
|
6548
|
+
const offset = expectDefined4(defsMatch.index);
|
|
6509
6549
|
const line = lineFromOffset(offset);
|
|
6510
6550
|
symbols.push(
|
|
6511
6551
|
makeSymbol({
|
|
@@ -6530,7 +6570,7 @@ function regexParse2(opts) {
|
|
|
6530
6570
|
for (let match = pat.exec(content); match !== null; match = pat.exec(content)) {
|
|
6531
6571
|
const offset = match.index ?? 0;
|
|
6532
6572
|
const line = lineFromOffset(offset);
|
|
6533
|
-
const key = match[0]?.match(/"([^"]+)"/)?.[1] ??
|
|
6573
|
+
const key = match[0]?.match(/"([^"]+)"/)?.[1] ?? expectDefined4(match[0]);
|
|
6534
6574
|
symbols.push(
|
|
6535
6575
|
makeSymbol({
|
|
6536
6576
|
name: key,
|
|
@@ -6549,12 +6589,12 @@ function regexParse2(opts) {
|
|
|
6549
6589
|
function extractPackageScripts(content, symbols, file, lang, lineOffsets2, lineFromOffset) {
|
|
6550
6590
|
const scriptsBlockRegex = /"scripts"\s*:\s*\{([^}]+)\}/g;
|
|
6551
6591
|
for (let match = scriptsBlockRegex.exec(content); match !== null; match = scriptsBlockRegex.exec(content)) {
|
|
6552
|
-
const blockContent =
|
|
6592
|
+
const blockContent = expectDefined4(match[0]);
|
|
6553
6593
|
const blockOffset = match.index ?? 0;
|
|
6554
6594
|
const scriptKeyRegex = /"(\w[\w-]*)"\s*:/g;
|
|
6555
6595
|
for (let scriptMatch = scriptKeyRegex.exec(blockContent); scriptMatch !== null; scriptMatch = scriptKeyRegex.exec(blockContent)) {
|
|
6556
|
-
const key =
|
|
6557
|
-
const keyOffset = blockOffset +
|
|
6596
|
+
const key = expectDefined4(scriptMatch[1]);
|
|
6597
|
+
const keyOffset = blockOffset + expectDefined4(scriptMatch.index);
|
|
6558
6598
|
const line = lineFromOffset(keyOffset);
|
|
6559
6599
|
symbols.push(
|
|
6560
6600
|
makeSymbol({
|
|
@@ -6573,12 +6613,12 @@ function extractPackageScripts(content, symbols, file, lang, lineOffsets2, lineF
|
|
|
6573
6613
|
function extractCompilerOptions(content, symbols, file, lang, lineOffsets2, parentLine, lineFromOffset) {
|
|
6574
6614
|
const optsBlockRegex = /"compilerOptions"\s*:\s*\{([^}]+)\}/g;
|
|
6575
6615
|
for (let match = optsBlockRegex.exec(content); match !== null; match = optsBlockRegex.exec(content)) {
|
|
6576
|
-
const blockContent =
|
|
6616
|
+
const blockContent = expectDefined4(match[0]);
|
|
6577
6617
|
const blockOffset = match.index ?? 0;
|
|
6578
6618
|
const optKeyRegex = /"(\w[\w]*)"\s*:/g;
|
|
6579
6619
|
for (let optMatch = optKeyRegex.exec(blockContent); optMatch !== null; optMatch = optKeyRegex.exec(blockContent)) {
|
|
6580
|
-
const key =
|
|
6581
|
-
const keyOffset = blockOffset +
|
|
6620
|
+
const key = expectDefined4(optMatch[1]);
|
|
6621
|
+
const keyOffset = blockOffset + expectDefined4(optMatch.index);
|
|
6582
6622
|
const line = lineFromOffset(keyOffset);
|
|
6583
6623
|
if (line <= parentLine) continue;
|
|
6584
6624
|
symbols.push(
|
|
@@ -6623,7 +6663,7 @@ __export(yaml_parser_exports, {
|
|
|
6623
6663
|
detectLang: () => detectLang,
|
|
6624
6664
|
parseSymbols: () => parseSymbols7
|
|
6625
6665
|
});
|
|
6626
|
-
import { expectDefined as
|
|
6666
|
+
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core/utils";
|
|
6627
6667
|
function parseSymbols7(opts) {
|
|
6628
6668
|
const { file, content, lang } = opts;
|
|
6629
6669
|
try {
|
|
@@ -6645,14 +6685,14 @@ function regexParse3(opts) {
|
|
|
6645
6685
|
let hi = lineOffsets2.length - 1;
|
|
6646
6686
|
while (lo < hi) {
|
|
6647
6687
|
const mid = lo + hi + 1 >>> 1;
|
|
6648
|
-
if (
|
|
6688
|
+
if (expectDefined5(lineOffsets2[mid]) <= offset) lo = mid;
|
|
6649
6689
|
else hi = mid - 1;
|
|
6650
6690
|
}
|
|
6651
6691
|
return lo + 1;
|
|
6652
6692
|
}
|
|
6653
6693
|
const anchorRegex = /&(\w[\w-]*)/g;
|
|
6654
6694
|
for (let match = anchorRegex.exec(content); match !== null; match = anchorRegex.exec(content)) {
|
|
6655
|
-
const name =
|
|
6695
|
+
const name = expectDefined5(match[1]);
|
|
6656
6696
|
const offset = match.index ?? 0;
|
|
6657
6697
|
const line = lineFromOffset(offset);
|
|
6658
6698
|
const col = offset - (lineOffsets2[line - 1] ?? 0);
|
|
@@ -6670,7 +6710,7 @@ function regexParse3(opts) {
|
|
|
6670
6710
|
}
|
|
6671
6711
|
const aliasRegex = /\*(\w[\w-]*)/g;
|
|
6672
6712
|
for (let match = aliasRegex.exec(content); match !== null; match = aliasRegex.exec(content)) {
|
|
6673
|
-
const name =
|
|
6713
|
+
const name = expectDefined5(match[1]);
|
|
6674
6714
|
const offset = match.index ?? 0;
|
|
6675
6715
|
const line = lineFromOffset(offset);
|
|
6676
6716
|
const col = offset - (lineOffsets2[line - 1] ?? 0);
|
|
@@ -6705,7 +6745,7 @@ function regexParse3(opts) {
|
|
|
6705
6745
|
}
|
|
6706
6746
|
const listItemRegex = /^-(\s+)([^:#\s][^:#\s]*)\s*:/gm;
|
|
6707
6747
|
for (let match = listItemRegex.exec(content); match !== null; match = listItemRegex.exec(content)) {
|
|
6708
|
-
const key =
|
|
6748
|
+
const key = expectDefined5(match[2]);
|
|
6709
6749
|
const offset = match.index ?? 0;
|
|
6710
6750
|
const line = lineFromOffset(offset);
|
|
6711
6751
|
const col = offset - (lineOffsets2[line - 1] ?? 0);
|
|
@@ -6725,7 +6765,7 @@ function regexParse3(opts) {
|
|
|
6725
6765
|
}
|
|
6726
6766
|
const blockScalarRegex = /^(\s*)([^:#\s][^:#\s]*)\s*:\s*[|>](\s|$)/gm;
|
|
6727
6767
|
for (let match = blockScalarRegex.exec(content); match !== null; match = blockScalarRegex.exec(content)) {
|
|
6728
|
-
const key =
|
|
6768
|
+
const key = expectDefined5(match[2]);
|
|
6729
6769
|
const offset = match.index ?? 0;
|
|
6730
6770
|
const line = lineFromOffset(offset);
|
|
6731
6771
|
const col = offset - (lineOffsets2[line - 1] ?? 0);
|
|
@@ -8454,9 +8494,50 @@ import { createReadStream } from "node:fs";
|
|
|
8454
8494
|
import * as fs7 from "node:fs/promises";
|
|
8455
8495
|
import * as path10 from "node:path";
|
|
8456
8496
|
import { atomicWrite, ulid } from "@wrongstack/core/utils";
|
|
8497
|
+
var ARTIFACT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
8498
|
+
var sweptRoots = /* @__PURE__ */ new Set();
|
|
8499
|
+
function sweepOldArtifacts(root) {
|
|
8500
|
+
if (sweptRoots.has(root)) return;
|
|
8501
|
+
sweptRoots.add(root);
|
|
8502
|
+
void (async () => {
|
|
8503
|
+
const cutoff = Date.now() - ARTIFACT_RETENTION_MS;
|
|
8504
|
+
let sessionDirs;
|
|
8505
|
+
try {
|
|
8506
|
+
const entries = await fs7.readdir(root, { withFileTypes: true });
|
|
8507
|
+
sessionDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
8508
|
+
} catch {
|
|
8509
|
+
return;
|
|
8510
|
+
}
|
|
8511
|
+
for (const sessionDir of sessionDirs) {
|
|
8512
|
+
const dir = path10.join(root, sessionDir);
|
|
8513
|
+
let names;
|
|
8514
|
+
try {
|
|
8515
|
+
names = await fs7.readdir(dir);
|
|
8516
|
+
} catch {
|
|
8517
|
+
continue;
|
|
8518
|
+
}
|
|
8519
|
+
let removed = 0;
|
|
8520
|
+
for (const name of names) {
|
|
8521
|
+
const target = path10.join(dir, name);
|
|
8522
|
+
try {
|
|
8523
|
+
const stat17 = await fs7.stat(target);
|
|
8524
|
+
if (stat17.isFile() && stat17.mtimeMs < cutoff) {
|
|
8525
|
+
await fs7.rm(target, { force: true });
|
|
8526
|
+
removed++;
|
|
8527
|
+
}
|
|
8528
|
+
} catch {
|
|
8529
|
+
}
|
|
8530
|
+
}
|
|
8531
|
+
if (removed === names.length && names.length > 0) {
|
|
8532
|
+
await fs7.rmdir(dir).catch(() => void 0);
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
})();
|
|
8536
|
+
}
|
|
8457
8537
|
var BrowserArtifactStore = class {
|
|
8458
8538
|
constructor(root) {
|
|
8459
8539
|
this.root = root;
|
|
8540
|
+
sweepOldArtifacts(root);
|
|
8460
8541
|
}
|
|
8461
8542
|
root;
|
|
8462
8543
|
async write(sessionId, kind, extension, mimeType, content) {
|
|
@@ -9689,10 +9770,12 @@ var browserTools = [
|
|
|
9689
9770
|
for (const tool of browserTools) tool.icon = "web";
|
|
9690
9771
|
for (const tool of browserTools) tool.timeoutMs ??= 6e4;
|
|
9691
9772
|
|
|
9692
|
-
// src/codebase-index/
|
|
9693
|
-
import
|
|
9694
|
-
import
|
|
9695
|
-
import
|
|
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";
|
|
9696
9779
|
|
|
9697
9780
|
// src/codebase-index/circuit-breaker.ts
|
|
9698
9781
|
var CircuitOpenError = class extends Error {
|
|
@@ -9774,153 +9857,18 @@ var IndexCircuitBreaker = class {
|
|
|
9774
9857
|
};
|
|
9775
9858
|
var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
9776
9859
|
|
|
9777
|
-
// src/codebase-index/
|
|
9778
|
-
import {
|
|
9779
|
-
import
|
|
9780
|
-
import * as
|
|
9781
|
-
import
|
|
9782
|
-
import
|
|
9783
|
-
import {
|
|
9784
|
-
DEFAULT_WALK_IGNORE_DIRS,
|
|
9785
|
-
indexParallelBatchSize,
|
|
9786
|
-
isFrugalPerf
|
|
9787
|
-
} from "@wrongstack/core/utils";
|
|
9788
|
-
|
|
9789
|
-
// src/codebase-index/gitignore.ts
|
|
9790
|
-
import * as fs9 from "node:fs/promises";
|
|
9791
|
-
import * as path13 from "node:path";
|
|
9792
|
-
import { compileGlob } from "@wrongstack/core/utils";
|
|
9793
|
-
function globBody(glob) {
|
|
9794
|
-
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
9795
|
-
}
|
|
9796
|
-
function compileGitignore(lines) {
|
|
9797
|
-
const rules = [];
|
|
9798
|
-
for (const raw of lines) {
|
|
9799
|
-
let line = raw.replace(/\r$/, "");
|
|
9800
|
-
if (!line.trim() || line.trimStart().startsWith("#")) continue;
|
|
9801
|
-
line = line.trim();
|
|
9802
|
-
let negated = false;
|
|
9803
|
-
if (line.startsWith("!")) {
|
|
9804
|
-
negated = true;
|
|
9805
|
-
line = line.slice(1);
|
|
9806
|
-
}
|
|
9807
|
-
let dirOnly = false;
|
|
9808
|
-
if (line.endsWith("/")) {
|
|
9809
|
-
dirOnly = true;
|
|
9810
|
-
line = line.slice(0, -1);
|
|
9811
|
-
}
|
|
9812
|
-
if (!line) continue;
|
|
9813
|
-
const anchored = line.startsWith("/") || line.includes("/");
|
|
9814
|
-
if (line.startsWith("/")) line = line.slice(1);
|
|
9815
|
-
const body = globBody(line);
|
|
9816
|
-
const prefix = anchored ? "^" : "(?:^|.*/)";
|
|
9817
|
-
rules.push({
|
|
9818
|
-
eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
|
|
9819
|
-
under: new RegExp(`${prefix}${body}/.*$`),
|
|
9820
|
-
negated,
|
|
9821
|
-
dirOnly
|
|
9822
|
-
});
|
|
9823
|
-
}
|
|
9824
|
-
return (relPath, isDir) => {
|
|
9825
|
-
const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
9826
|
-
let ignored = false;
|
|
9827
|
-
for (const r of rules) {
|
|
9828
|
-
const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
|
|
9829
|
-
if (re.test(p)) ignored = !r.negated;
|
|
9830
|
-
}
|
|
9831
|
-
return ignored;
|
|
9832
|
-
};
|
|
9833
|
-
}
|
|
9834
|
-
async function loadGitignoreMatcher(projectRoot) {
|
|
9835
|
-
let lines = [];
|
|
9836
|
-
try {
|
|
9837
|
-
const raw = await fs9.readFile(path13.join(projectRoot, ".gitignore"), "utf8");
|
|
9838
|
-
lines = raw.split("\n");
|
|
9839
|
-
} catch {
|
|
9840
|
-
}
|
|
9841
|
-
return compileGitignore(lines);
|
|
9842
|
-
}
|
|
9843
|
-
|
|
9844
|
-
// src/codebase-index/indexer.ts
|
|
9845
|
-
init_languages2();
|
|
9846
|
-
|
|
9847
|
-
// src/codebase-index/parser-dispatch.ts
|
|
9848
|
-
async function parseFileContent(file, content, lang) {
|
|
9849
|
-
switch (lang) {
|
|
9850
|
-
case "ts":
|
|
9851
|
-
case "tsx":
|
|
9852
|
-
case "js":
|
|
9853
|
-
case "jsx": {
|
|
9854
|
-
const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
9855
|
-
return parseSymbols8({ file, content, lang });
|
|
9856
|
-
}
|
|
9857
|
-
case "go": {
|
|
9858
|
-
const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
9859
|
-
return parseSymbols8({ file, content, lang: "go" });
|
|
9860
|
-
}
|
|
9861
|
-
case "py": {
|
|
9862
|
-
const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
9863
|
-
return parseSymbols8({ file, content, lang: "py" });
|
|
9864
|
-
}
|
|
9865
|
-
case "rs": {
|
|
9866
|
-
const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
9867
|
-
return parseSymbols8({ file, content, lang: "rs" });
|
|
9868
|
-
}
|
|
9869
|
-
case "json": {
|
|
9870
|
-
const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
9871
|
-
return parseSymbols8({ file, content, lang: "json" });
|
|
9872
|
-
}
|
|
9873
|
-
case "yaml": {
|
|
9874
|
-
const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
|
|
9875
|
-
return parseSymbols8({ file, content, lang: "yaml" });
|
|
9876
|
-
}
|
|
9877
|
-
default: {
|
|
9878
|
-
const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
9879
|
-
return parseSymbols8({ file, content, lang });
|
|
9880
|
-
}
|
|
9881
|
-
}
|
|
9882
|
-
}
|
|
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";
|
|
9883
9867
|
|
|
9884
9868
|
// src/codebase-index/writer.ts
|
|
9885
|
-
import { expectDefined as
|
|
9886
|
-
import * as
|
|
9887
|
-
import * as
|
|
9888
|
-
|
|
9889
|
-
// src/codebase-index/schema.ts
|
|
9890
|
-
var SCHEMA_VERSION = 3;
|
|
9891
|
-
|
|
9892
|
-
// src/codebase-index/lsp-kind.ts
|
|
9893
|
-
function lspKindToInternalKind(k) {
|
|
9894
|
-
switch (k) {
|
|
9895
|
-
case 5 /* Class */:
|
|
9896
|
-
return "class";
|
|
9897
|
-
case 6 /* Method */:
|
|
9898
|
-
return "method";
|
|
9899
|
-
case 7 /* Property */:
|
|
9900
|
-
case 8 /* Field */:
|
|
9901
|
-
return "property";
|
|
9902
|
-
case 9 /* Constructor */:
|
|
9903
|
-
return "class";
|
|
9904
|
-
case 10 /* Enum */:
|
|
9905
|
-
return "enum";
|
|
9906
|
-
case 11 /* Interface */:
|
|
9907
|
-
return "interface";
|
|
9908
|
-
case 12 /* Function */:
|
|
9909
|
-
return "function";
|
|
9910
|
-
case 13 /* Variable */:
|
|
9911
|
-
return "var";
|
|
9912
|
-
case 14 /* Constant */:
|
|
9913
|
-
return "const";
|
|
9914
|
-
case 22 /* EnumMember */:
|
|
9915
|
-
return "enum";
|
|
9916
|
-
case 26 /* TypeParameter */:
|
|
9917
|
-
return "type";
|
|
9918
|
-
case 3 /* Namespace */:
|
|
9919
|
-
return "namespace";
|
|
9920
|
-
default:
|
|
9921
|
-
return null;
|
|
9922
|
-
}
|
|
9923
|
-
}
|
|
9869
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
9870
|
+
import * as fs10 from "node:fs";
|
|
9871
|
+
import * as path15 from "node:path";
|
|
9924
9872
|
|
|
9925
9873
|
// src/codebase-index/bm25.ts
|
|
9926
9874
|
var K1 = 1.5;
|
|
@@ -10011,6 +9959,42 @@ var Bm25Index = class {
|
|
|
10011
9959
|
}
|
|
10012
9960
|
};
|
|
10013
9961
|
|
|
9962
|
+
// src/codebase-index/lsp-kind.ts
|
|
9963
|
+
function lspKindToInternalKind(k) {
|
|
9964
|
+
switch (k) {
|
|
9965
|
+
case 5 /* Class */:
|
|
9966
|
+
return "class";
|
|
9967
|
+
case 6 /* Method */:
|
|
9968
|
+
return "method";
|
|
9969
|
+
case 7 /* Property */:
|
|
9970
|
+
case 8 /* Field */:
|
|
9971
|
+
return "property";
|
|
9972
|
+
case 9 /* Constructor */:
|
|
9973
|
+
return "class";
|
|
9974
|
+
case 10 /* Enum */:
|
|
9975
|
+
return "enum";
|
|
9976
|
+
case 11 /* Interface */:
|
|
9977
|
+
return "interface";
|
|
9978
|
+
case 12 /* Function */:
|
|
9979
|
+
return "function";
|
|
9980
|
+
case 13 /* Variable */:
|
|
9981
|
+
return "var";
|
|
9982
|
+
case 14 /* Constant */:
|
|
9983
|
+
return "const";
|
|
9984
|
+
case 22 /* EnumMember */:
|
|
9985
|
+
return "enum";
|
|
9986
|
+
case 26 /* TypeParameter */:
|
|
9987
|
+
return "type";
|
|
9988
|
+
case 3 /* Namespace */:
|
|
9989
|
+
return "namespace";
|
|
9990
|
+
default:
|
|
9991
|
+
return null;
|
|
9992
|
+
}
|
|
9993
|
+
}
|
|
9994
|
+
|
|
9995
|
+
// src/codebase-index/schema.ts
|
|
9996
|
+
var SCHEMA_VERSION = 3;
|
|
9997
|
+
|
|
10014
9998
|
// src/codebase-index/sqlite-runtime.ts
|
|
10015
9999
|
import { createRequire } from "node:module";
|
|
10016
10000
|
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
@@ -10078,99 +10062,9 @@ function runSqliteWithRetry(fn) {
|
|
|
10078
10062
|
throw lastError;
|
|
10079
10063
|
}
|
|
10080
10064
|
|
|
10081
|
-
// src/codebase-index/writer-helpers.ts
|
|
10082
|
-
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
|
|
10083
|
-
function escapeLike(value) {
|
|
10084
|
-
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
10085
|
-
}
|
|
10086
|
-
function assignRefsToSymbols(refs, symbols) {
|
|
10087
|
-
if (refs.length === 0 || symbols.length === 0) return [];
|
|
10088
|
-
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
10089
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10090
|
-
const assigned = [];
|
|
10091
|
-
for (const ref of refs) {
|
|
10092
|
-
let owner2;
|
|
10093
|
-
for (const symbol of ordered) {
|
|
10094
|
-
if (symbol.line > ref.line) break;
|
|
10095
|
-
owner2 = symbol;
|
|
10096
|
-
}
|
|
10097
|
-
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10098
|
-
if (!owner2 || owner2.id <= 0) continue;
|
|
10099
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
10100
|
-
if (seen.has(key)) continue;
|
|
10101
|
-
seen.add(key);
|
|
10102
|
-
assigned.push({ ...ref, fromId: owner2.id });
|
|
10103
|
-
}
|
|
10104
|
-
return assigned;
|
|
10105
|
-
}
|
|
10106
|
-
function resolveIndexDir(projectRoot, override) {
|
|
10107
|
-
return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
|
|
10108
|
-
}
|
|
10109
|
-
function codebaseIndexDirOverride(ctx) {
|
|
10110
|
-
const v = ctx.meta?.["codebaseIndexDir"];
|
|
10111
|
-
return typeof v === "string" ? v : void 0;
|
|
10112
|
-
}
|
|
10113
|
-
|
|
10114
|
-
// src/codebase-index/writer-schema.ts
|
|
10115
|
-
var METADATA_TABLE_SQL = `
|
|
10116
|
-
CREATE TABLE IF NOT EXISTS metadata (
|
|
10117
|
-
key TEXT PRIMARY KEY,
|
|
10118
|
-
value TEXT NOT NULL
|
|
10119
|
-
);
|
|
10120
|
-
`;
|
|
10121
|
-
var CORE_TABLES_SQL = `
|
|
10122
|
-
CREATE TABLE IF NOT EXISTS files (
|
|
10123
|
-
file TEXT PRIMARY KEY,
|
|
10124
|
-
lang TEXT NOT NULL,
|
|
10125
|
-
mtime_ms INTEGER NOT NULL,
|
|
10126
|
-
symbol_count INTEGER NOT NULL DEFAULT 0,
|
|
10127
|
-
last_indexed INTEGER NOT NULL
|
|
10128
|
-
);
|
|
10129
|
-
CREATE TABLE IF NOT EXISTS symbols (
|
|
10130
|
-
id INTEGER PRIMARY KEY,
|
|
10131
|
-
lang TEXT NOT NULL,
|
|
10132
|
-
kind TEXT NOT NULL,
|
|
10133
|
-
name TEXT NOT NULL,
|
|
10134
|
-
file TEXT NOT NULL,
|
|
10135
|
-
line INTEGER NOT NULL,
|
|
10136
|
-
col INTEGER NOT NULL,
|
|
10137
|
-
signature TEXT NOT NULL DEFAULT '',
|
|
10138
|
-
doc_comment TEXT NOT NULL DEFAULT '',
|
|
10139
|
-
scope TEXT NOT NULL DEFAULT '',
|
|
10140
|
-
text TEXT NOT NULL DEFAULT '',
|
|
10141
|
-
file_fk TEXT NOT NULL
|
|
10142
|
-
);
|
|
10143
|
-
`;
|
|
10144
|
-
var SYMBOL_INDEX_SQL = [
|
|
10145
|
-
"CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
|
|
10146
|
-
"CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
|
|
10147
|
-
"CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
|
|
10148
|
-
"CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
|
|
10149
|
-
"CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
|
|
10150
|
-
"CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
|
|
10151
|
-
"CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
|
|
10152
|
-
];
|
|
10153
|
-
var REFS_TABLE_SQL = `
|
|
10154
|
-
CREATE TABLE IF NOT EXISTS refs (
|
|
10155
|
-
id INTEGER PRIMARY KEY,
|
|
10156
|
-
from_id INTEGER NOT NULL,
|
|
10157
|
-
to_name TEXT NOT NULL,
|
|
10158
|
-
to_id INTEGER,
|
|
10159
|
-
call_type TEXT NOT NULL,
|
|
10160
|
-
line INTEGER NOT NULL
|
|
10161
|
-
);
|
|
10162
|
-
`;
|
|
10163
|
-
var REFS_INDEX_SQL = [
|
|
10164
|
-
"CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
|
|
10165
|
-
"CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
|
|
10166
|
-
"CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
|
|
10167
|
-
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
|
|
10168
|
-
];
|
|
10169
|
-
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
10170
|
-
|
|
10171
10065
|
// src/codebase-index/writer-admin.ts
|
|
10172
|
-
import * as
|
|
10173
|
-
import * as
|
|
10066
|
+
import * as fs9 from "node:fs";
|
|
10067
|
+
import * as path13 from "node:path";
|
|
10174
10068
|
var DB_FILE = "index.db";
|
|
10175
10069
|
function getAllIndexableWithStatement(stmt) {
|
|
10176
10070
|
return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
@@ -10229,7 +10123,7 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
10229
10123
|
}
|
|
10230
10124
|
function getIndexDbSizeBytes(indexDir) {
|
|
10231
10125
|
try {
|
|
10232
|
-
return
|
|
10126
|
+
return fs9.statSync(path13.join(indexDir, DB_FILE)).size;
|
|
10233
10127
|
} catch {
|
|
10234
10128
|
return 0;
|
|
10235
10129
|
}
|
|
@@ -10296,7 +10190,7 @@ function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
|
10296
10190
|
}
|
|
10297
10191
|
|
|
10298
10192
|
// src/codebase-index/writer-graph-helpers.ts
|
|
10299
|
-
import * as
|
|
10193
|
+
import * as path14 from "node:path";
|
|
10300
10194
|
function derivePackage(filePath) {
|
|
10301
10195
|
const f = filePath.replace(/\\/g, "/");
|
|
10302
10196
|
const pkgsIdx = f.indexOf("/packages/");
|
|
@@ -10411,16 +10305,16 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
10411
10305
|
function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
10412
10306
|
if (!moduleName.startsWith(".")) return void 0;
|
|
10413
10307
|
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
10414
|
-
const absolute =
|
|
10415
|
-
|
|
10308
|
+
const absolute = path14.posix.normalize(
|
|
10309
|
+
path14.posix.join(path14.posix.dirname(normalizedFrom), moduleName)
|
|
10416
10310
|
);
|
|
10417
|
-
const extension =
|
|
10311
|
+
const extension = path14.posix.extname(absolute);
|
|
10418
10312
|
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
10419
10313
|
const candidates = [
|
|
10420
10314
|
absolute,
|
|
10421
10315
|
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
10422
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) =>
|
|
10423
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((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}`))
|
|
10424
10318
|
];
|
|
10425
10319
|
const indexedByPortablePath = new Map(
|
|
10426
10320
|
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
@@ -10640,6 +10534,39 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
10640
10534
|
return { nodes, edges };
|
|
10641
10535
|
}
|
|
10642
10536
|
|
|
10537
|
+
// src/codebase-index/writer-helpers.ts
|
|
10538
|
+
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
|
|
10539
|
+
function escapeLike(value) {
|
|
10540
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
10541
|
+
}
|
|
10542
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
10543
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
10544
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
10545
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10546
|
+
const assigned = [];
|
|
10547
|
+
for (const ref of refs) {
|
|
10548
|
+
let owner2;
|
|
10549
|
+
for (const symbol of ordered) {
|
|
10550
|
+
if (symbol.line > ref.line) break;
|
|
10551
|
+
owner2 = symbol;
|
|
10552
|
+
}
|
|
10553
|
+
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10554
|
+
if (!owner2 || owner2.id <= 0) continue;
|
|
10555
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
10556
|
+
if (seen.has(key)) continue;
|
|
10557
|
+
seen.add(key);
|
|
10558
|
+
assigned.push({ ...ref, fromId: owner2.id });
|
|
10559
|
+
}
|
|
10560
|
+
return assigned;
|
|
10561
|
+
}
|
|
10562
|
+
function resolveIndexDir(projectRoot, override) {
|
|
10563
|
+
return override ?? resolveWstackPaths2({ projectRoot }).projectCodebaseIndex;
|
|
10564
|
+
}
|
|
10565
|
+
function codebaseIndexDirOverride(ctx) {
|
|
10566
|
+
const v = ctx.meta?.["codebaseIndexDir"];
|
|
10567
|
+
return typeof v === "string" ? v : void 0;
|
|
10568
|
+
}
|
|
10569
|
+
|
|
10643
10570
|
// src/codebase-index/writer-pragmas.ts
|
|
10644
10571
|
import { sqliteCachePragmas } from "@wrongstack/core/utils";
|
|
10645
10572
|
function applyIndexStorePragmas(db) {
|
|
@@ -10658,6 +10585,63 @@ function applyIndexStorePragmas(db) {
|
|
|
10658
10585
|
}
|
|
10659
10586
|
}
|
|
10660
10587
|
|
|
10588
|
+
// src/codebase-index/writer-schema.ts
|
|
10589
|
+
var METADATA_TABLE_SQL = `
|
|
10590
|
+
CREATE TABLE IF NOT EXISTS metadata (
|
|
10591
|
+
key TEXT PRIMARY KEY,
|
|
10592
|
+
value TEXT NOT NULL
|
|
10593
|
+
);
|
|
10594
|
+
`;
|
|
10595
|
+
var CORE_TABLES_SQL = `
|
|
10596
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
10597
|
+
file TEXT PRIMARY KEY,
|
|
10598
|
+
lang TEXT NOT NULL,
|
|
10599
|
+
mtime_ms INTEGER NOT NULL,
|
|
10600
|
+
symbol_count INTEGER NOT NULL DEFAULT 0,
|
|
10601
|
+
last_indexed INTEGER NOT NULL
|
|
10602
|
+
);
|
|
10603
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
10604
|
+
id INTEGER PRIMARY KEY,
|
|
10605
|
+
lang TEXT NOT NULL,
|
|
10606
|
+
kind TEXT NOT NULL,
|
|
10607
|
+
name TEXT NOT NULL,
|
|
10608
|
+
file TEXT NOT NULL,
|
|
10609
|
+
line INTEGER NOT NULL,
|
|
10610
|
+
col INTEGER NOT NULL,
|
|
10611
|
+
signature TEXT NOT NULL DEFAULT '',
|
|
10612
|
+
doc_comment TEXT NOT NULL DEFAULT '',
|
|
10613
|
+
scope TEXT NOT NULL DEFAULT '',
|
|
10614
|
+
text TEXT NOT NULL DEFAULT '',
|
|
10615
|
+
file_fk TEXT NOT NULL
|
|
10616
|
+
);
|
|
10617
|
+
`;
|
|
10618
|
+
var SYMBOL_INDEX_SQL = [
|
|
10619
|
+
"CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
|
|
10620
|
+
"CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
|
|
10621
|
+
"CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
|
|
10622
|
+
"CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
|
|
10623
|
+
"CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
|
|
10624
|
+
"CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
|
|
10625
|
+
"CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
|
|
10626
|
+
];
|
|
10627
|
+
var REFS_TABLE_SQL = `
|
|
10628
|
+
CREATE TABLE IF NOT EXISTS refs (
|
|
10629
|
+
id INTEGER PRIMARY KEY,
|
|
10630
|
+
from_id INTEGER NOT NULL,
|
|
10631
|
+
to_name TEXT NOT NULL,
|
|
10632
|
+
to_id INTEGER,
|
|
10633
|
+
call_type TEXT NOT NULL,
|
|
10634
|
+
line INTEGER NOT NULL
|
|
10635
|
+
);
|
|
10636
|
+
`;
|
|
10637
|
+
var REFS_INDEX_SQL = [
|
|
10638
|
+
"CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
|
|
10639
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
|
|
10640
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
|
|
10641
|
+
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
|
|
10642
|
+
];
|
|
10643
|
+
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
10644
|
+
|
|
10661
10645
|
// src/codebase-index/writer-search-helpers.ts
|
|
10662
10646
|
function normalizeSearchLimit(limit) {
|
|
10663
10647
|
return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
|
|
@@ -10846,9 +10830,9 @@ var IndexStore = class _IndexStore {
|
|
|
10846
10830
|
}
|
|
10847
10831
|
constructor(projectRoot, opts = {}) {
|
|
10848
10832
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
10849
|
-
|
|
10833
|
+
fs10.mkdirSync(this.indexDir, { recursive: true });
|
|
10850
10834
|
const Database = loadDatabaseSync();
|
|
10851
|
-
this.db = new Database(
|
|
10835
|
+
this.db = new Database(path15.join(this.indexDir, DB_FILE2));
|
|
10852
10836
|
applyIndexStorePragmas(this.db);
|
|
10853
10837
|
this.initSchema();
|
|
10854
10838
|
}
|
|
@@ -10866,9 +10850,15 @@ var IndexStore = class _IndexStore {
|
|
|
10866
10850
|
DROP TABLE IF EXISTS refs;
|
|
10867
10851
|
`);
|
|
10868
10852
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
10869
|
-
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
|
|
10853
|
+
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
|
|
10854
|
+
String(SCHEMA_VERSION),
|
|
10855
|
+
"version"
|
|
10856
|
+
);
|
|
10870
10857
|
} else if (storedVersion === null) {
|
|
10871
|
-
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run(
|
|
10858
|
+
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run(
|
|
10859
|
+
"version",
|
|
10860
|
+
String(SCHEMA_VERSION)
|
|
10861
|
+
);
|
|
10872
10862
|
}
|
|
10873
10863
|
this.db.exec(CORE_TABLES_SQL);
|
|
10874
10864
|
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
@@ -10885,7 +10875,9 @@ var IndexStore = class _IndexStore {
|
|
|
10885
10875
|
);
|
|
10886
10876
|
if (symbolCount !== ftsCount) {
|
|
10887
10877
|
this.db.exec("DELETE FROM symbols_fts");
|
|
10888
|
-
const rows = this.stmt(
|
|
10878
|
+
const rows = this.stmt(
|
|
10879
|
+
"SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
|
|
10880
|
+
).all();
|
|
10889
10881
|
bulkInsertFtsWithStatement(
|
|
10890
10882
|
(sql) => this.stmt(sql),
|
|
10891
10883
|
_IndexStore.MAX_SQL_VARS,
|
|
@@ -11107,7 +11099,9 @@ var IndexStore = class _IndexStore {
|
|
|
11107
11099
|
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
11108
11100
|
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
|
|
11109
11101
|
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
11110
|
-
const rows = this.stmt(sql).all(
|
|
11102
|
+
const rows = this.stmt(sql).all(
|
|
11103
|
+
...binds
|
|
11104
|
+
);
|
|
11111
11105
|
return rows.map((row) => mapWriterSearchRow(row, filter?.lspKind));
|
|
11112
11106
|
}
|
|
11113
11107
|
/** Shared WHERE builder for {@link search} / empty-query ranked totals. */
|
|
@@ -11253,13 +11247,13 @@ var IndexStore = class _IndexStore {
|
|
|
11253
11247
|
if (rankDiff !== 0) return rankDiff;
|
|
11254
11248
|
const scoreDiff = b.score - a.score;
|
|
11255
11249
|
if (scoreDiff !== 0) return scoreDiff;
|
|
11256
|
-
const left =
|
|
11257
|
-
const right =
|
|
11250
|
+
const left = expectDefined2(candidateById.get(a.id));
|
|
11251
|
+
const right = expectDefined2(candidateById.get(b.id));
|
|
11258
11252
|
return left.name.localeCompare(right.name) || left.file.localeCompare(right.file) || left.line - right.line || left.col - right.col || left.id - right.id;
|
|
11259
11253
|
});
|
|
11260
11254
|
const qTokens = tokenise(query);
|
|
11261
11255
|
const results = scored.slice(0, limit).map(({ id, score }) => {
|
|
11262
|
-
const c =
|
|
11256
|
+
const c = expectDefined2(candidateById.get(id));
|
|
11263
11257
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
11264
11258
|
});
|
|
11265
11259
|
return { results, total: candidates.length };
|
|
@@ -11283,7 +11277,9 @@ var IndexStore = class _IndexStore {
|
|
|
11283
11277
|
}
|
|
11284
11278
|
setLastIndexed(ts2) {
|
|
11285
11279
|
this.runWithRetry(() => {
|
|
11286
|
-
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
|
|
11280
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
|
|
11281
|
+
String(ts2)
|
|
11282
|
+
);
|
|
11287
11283
|
});
|
|
11288
11284
|
}
|
|
11289
11285
|
getMetadata(key) {
|
|
@@ -11394,7 +11390,9 @@ var IndexStore = class _IndexStore {
|
|
|
11394
11390
|
this.stmt(
|
|
11395
11391
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
11396
11392
|
).run(...options.deleteForFiles);
|
|
11397
|
-
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(
|
|
11393
|
+
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(
|
|
11394
|
+
...options.deleteForFiles
|
|
11395
|
+
);
|
|
11398
11396
|
}
|
|
11399
11397
|
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
11400
11398
|
let nextId = this.allocateSymbolIds(totalSymbols);
|
|
@@ -11438,11 +11436,7 @@ var IndexStore = class _IndexStore {
|
|
|
11438
11436
|
this.ftsAvailable,
|
|
11439
11437
|
ftsRows
|
|
11440
11438
|
);
|
|
11441
|
-
bulkInsertRefsWithStatement(
|
|
11442
|
-
(sql) => this.stmt(sql),
|
|
11443
|
-
_IndexStore.MAX_SQL_VARS,
|
|
11444
|
-
refsToInsert
|
|
11445
|
-
);
|
|
11439
|
+
bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
|
|
11446
11440
|
const upsertStmt = this.stmt(
|
|
11447
11441
|
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
11448
11442
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -11471,7 +11465,9 @@ var IndexStore = class _IndexStore {
|
|
|
11471
11465
|
*/
|
|
11472
11466
|
deleteRefsForFile(file) {
|
|
11473
11467
|
this.runWithRetry(() => {
|
|
11474
|
-
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
11468
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
11469
|
+
file
|
|
11470
|
+
);
|
|
11475
11471
|
});
|
|
11476
11472
|
}
|
|
11477
11473
|
/**
|
|
@@ -11626,9 +11622,7 @@ var IndexStore = class _IndexStore {
|
|
|
11626
11622
|
* build the full symbol universe for the reachability scan.
|
|
11627
11623
|
*/
|
|
11628
11624
|
getAllSymbols() {
|
|
11629
|
-
return this.stmt(
|
|
11630
|
-
"SELECT id, name, file, kind, line FROM symbols ORDER BY id"
|
|
11631
|
-
).all().map((r) => ({ ...r, kind: r.kind }));
|
|
11625
|
+
return this.stmt("SELECT id, name, file, kind, line FROM symbols ORDER BY id").all().map((r) => ({ ...r, kind: r.kind }));
|
|
11632
11626
|
}
|
|
11633
11627
|
/**
|
|
11634
11628
|
* Returns every resolved reference (to_id IS NOT NULL). Used by
|
|
@@ -11640,6 +11634,25 @@ var IndexStore = class _IndexStore {
|
|
|
11640
11634
|
"SELECT from_id AS fromId, to_id AS toId, call_type AS callType FROM refs WHERE to_id IS NOT NULL"
|
|
11641
11635
|
).all();
|
|
11642
11636
|
}
|
|
11637
|
+
/**
|
|
11638
|
+
* Returns ALL import refs (including unresolved) with their source-file
|
|
11639
|
+
* path and resolved target id. Used by the dead-code scan's file-level
|
|
11640
|
+
* graph traversal to handle barrel-only entry points where no symbol
|
|
11641
|
+
* carries the ref.
|
|
11642
|
+
*
|
|
11643
|
+
* Refs whose `from_id` doesn't match a known symbol (e.g. pure-barrel
|
|
11644
|
+
* files with no declarations) will have `sourceFile === null`.
|
|
11645
|
+
*/
|
|
11646
|
+
getAllImportRefs() {
|
|
11647
|
+
return this.stmt(
|
|
11648
|
+
`SELECT s.file AS sourceFile, r.to_name AS toName, r.to_id AS toId,
|
|
11649
|
+
r.call_type AS callType, r.line
|
|
11650
|
+
FROM refs r
|
|
11651
|
+
LEFT JOIN symbols s ON r.from_id = s.id
|
|
11652
|
+
WHERE r.call_type = 'import'
|
|
11653
|
+
ORDER BY r.line`
|
|
11654
|
+
).all();
|
|
11655
|
+
}
|
|
11643
11656
|
close() {
|
|
11644
11657
|
this.stmtCache.clear();
|
|
11645
11658
|
this.bm25Dirty = true;
|
|
@@ -11654,1134 +11667,1247 @@ var indexStorePool = new StorePool(
|
|
|
11654
11667
|
(projectRoot, opts) => new IndexStore(projectRoot, opts)
|
|
11655
11668
|
);
|
|
11656
11669
|
|
|
11657
|
-
// src/codebase-index/
|
|
11658
|
-
var
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
function
|
|
11663
|
-
|
|
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
|
+
}
|
|
11664
11688
|
}
|
|
11665
|
-
function
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
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;
|
|
11669
11692
|
}
|
|
11670
|
-
function
|
|
11671
|
-
|
|
11693
|
+
function projectIndexServerKey(projectRoot, indexDir) {
|
|
11694
|
+
const resolvedIndexDir = normalizeLocalPath(resolveIndexDir(projectRoot, indexDir));
|
|
11695
|
+
return createHash4("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
|
|
11672
11696
|
}
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
11678
|
-
|
|
11679
|
-
const rel = path22.relative(projectRoot, file);
|
|
11680
|
-
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`);
|
|
11681
11703
|
}
|
|
11682
|
-
function
|
|
11683
|
-
|
|
11684
|
-
|
|
11704
|
+
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
11705
|
+
return path16.join(
|
|
11706
|
+
path16.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
11707
|
+
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
11708
|
+
);
|
|
11685
11709
|
}
|
|
11686
|
-
|
|
11687
|
-
|
|
11688
|
-
|
|
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
|
+
`;
|
|
11689
11716
|
}
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
])
|
|
11723
|
-
]);
|
|
11724
|
-
throwIfAborted(signal);
|
|
11725
|
-
const dirty = /* @__PURE__ */ new Set();
|
|
11726
|
-
const deleted = /* @__PURE__ */ new Set();
|
|
11727
|
-
const statusRecords = statusOutput.toString("utf8").split("\0");
|
|
11728
|
-
for (let i = 0; i < statusRecords.length; i++) {
|
|
11729
|
-
const record = statusRecords[i];
|
|
11730
|
-
if (!record) continue;
|
|
11731
|
-
const status = record.slice(0, 2);
|
|
11732
|
-
const changedPath = path22.resolve(projectRoot, record.slice(3));
|
|
11733
|
-
dirty.add(changedPath);
|
|
11734
|
-
if (status.includes("D")) deleted.add(changedPath);
|
|
11735
|
-
if (status.includes("R") || status.includes("C")) {
|
|
11736
|
-
const source = statusRecords[++i];
|
|
11737
|
-
if (source) dirty.add(path22.resolve(projectRoot, source));
|
|
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;
|
|
11738
11749
|
}
|
|
11750
|
+
} catch {
|
|
11739
11751
|
}
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
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
|
+
};
|
|
11751
11764
|
}
|
|
11752
|
-
return {
|
|
11753
|
-
files,
|
|
11754
|
-
trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
|
|
11755
|
-
};
|
|
11756
|
-
} catch {
|
|
11757
|
-
return null;
|
|
11758
11765
|
}
|
|
11766
|
+
return { kind: "available", url: builtUrl };
|
|
11759
11767
|
}
|
|
11760
|
-
|
|
11761
|
-
const
|
|
11762
|
-
|
|
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 };
|
|
11793
|
+
}
|
|
11763
11794
|
return {
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11795
|
+
status: "offline",
|
|
11796
|
+
connected: false,
|
|
11797
|
+
projectRoot,
|
|
11798
|
+
indexDir,
|
|
11799
|
+
endpoint
|
|
11768
11800
|
};
|
|
11769
11801
|
}
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
11774
|
-
const indexableExts = new Set(INDEXABLE_EXTENSIONS);
|
|
11775
|
-
let dirCount = 0;
|
|
11776
|
-
const walk = async (dir) => {
|
|
11777
|
-
throwIfAborted(signal);
|
|
11778
|
-
if (dirCount > 0 && dirCount % YIELD_EVERY_N === 0) {
|
|
11779
|
-
await yieldEventLoop();
|
|
11780
|
-
throwIfAborted(signal);
|
|
11781
|
-
}
|
|
11782
|
-
let entries;
|
|
11783
|
-
try {
|
|
11784
|
-
entries = await fs15.readdir(dir, { withFileTypes: true });
|
|
11785
|
-
} catch (err) {
|
|
11786
|
-
complete = false;
|
|
11787
|
-
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11788
|
-
return;
|
|
11789
|
-
}
|
|
11790
|
-
dirCount++;
|
|
11791
|
-
for (const e of entries) {
|
|
11792
|
-
if (ignoreSet.has(e.name)) continue;
|
|
11793
|
-
const full = path22.join(dir, e.name);
|
|
11794
|
-
const rel = path22.relative(projectRoot, full).replace(/\\/g, "/");
|
|
11795
|
-
if (e.isDirectory()) {
|
|
11796
|
-
if (isGitIgnored(rel, true)) continue;
|
|
11797
|
-
await walk(full);
|
|
11798
|
-
} else if (e.isFile()) {
|
|
11799
|
-
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
11800
|
-
const ext = path22.extname(e.name).toLowerCase();
|
|
11801
|
-
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
11802
|
-
results.push(full);
|
|
11803
|
-
}
|
|
11804
|
-
}
|
|
11805
|
-
}
|
|
11806
|
-
};
|
|
11807
|
-
await walk(projectRoot);
|
|
11808
|
-
return { files: results, complete, errors };
|
|
11802
|
+
if (latestConnectionState.endpoint) return latestConnectionState;
|
|
11803
|
+
if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
|
|
11804
|
+
return latestConnectionState;
|
|
11809
11805
|
}
|
|
11810
|
-
function
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
const seen = /* @__PURE__ */ new Set();
|
|
11814
|
-
const assigned = [];
|
|
11815
|
-
for (const ref of refs) {
|
|
11816
|
-
let owner2;
|
|
11817
|
-
for (const symbol of ordered) {
|
|
11818
|
-
if (symbol.line > ref.line) break;
|
|
11819
|
-
owner2 = symbol;
|
|
11820
|
-
}
|
|
11821
|
-
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
11822
|
-
if (!owner2 || owner2.id <= 0) continue;
|
|
11823
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
11824
|
-
if (seen.has(key)) continue;
|
|
11825
|
-
seen.add(key);
|
|
11826
|
-
assigned.push({ ...ref, fromId: owner2.id });
|
|
11827
|
-
}
|
|
11828
|
-
return assigned;
|
|
11806
|
+
function onProjectIndexServerConnectionStateChange(listener) {
|
|
11807
|
+
connectionStateListeners.add(listener);
|
|
11808
|
+
return () => connectionStateListeners.delete(listener);
|
|
11829
11809
|
}
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
const
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
const
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
}
|
|
11860
|
-
if (langs && langs.length > 0) {
|
|
11861
|
-
const langSet = new Set(langs);
|
|
11862
|
-
files = files.filter((f) => {
|
|
11863
|
-
const lang = detectLang(f);
|
|
11864
|
-
return lang ? langSet.has(lang) : false;
|
|
11865
|
-
});
|
|
11866
|
-
}
|
|
11867
|
-
if (force) store.clearAll();
|
|
11868
|
-
const existingMeta = /* @__PURE__ */ new Map();
|
|
11869
|
-
if (!force) {
|
|
11870
|
-
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
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;
|
|
11816
|
+
}
|
|
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");
|
|
11871
11839
|
}
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
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 } : {}
|
|
11883
11868
|
});
|
|
11884
|
-
if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
|
|
11885
11869
|
}
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
|
|
11870
|
+
isConnected() {
|
|
11871
|
+
return this.socket !== null && !this.socket.destroyed && this.info !== null;
|
|
11872
|
+
}
|
|
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" };
|
|
11931
|
+
}
|
|
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 }
|
|
11957
|
+
);
|
|
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
|
+
}
|
|
11898
12027
|
}
|
|
11899
|
-
|
|
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
|
+
}
|
|
12062
|
+
}
|
|
12063
|
+
if (!spawnIfMissing) break;
|
|
12064
|
+
if (!spawned) {
|
|
12065
|
+
this.spawnDetachedServer();
|
|
12066
|
+
spawned = true;
|
|
12067
|
+
}
|
|
12068
|
+
await delay(75);
|
|
11900
12069
|
}
|
|
11901
|
-
|
|
11902
|
-
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
let parsed;
|
|
11949
|
-
try {
|
|
11950
|
-
parsed = await parseFileContent(file, content, lang);
|
|
11951
|
-
} catch (e) {
|
|
11952
|
-
return {
|
|
11953
|
-
file,
|
|
11954
|
-
stat: stat17,
|
|
11955
|
-
lang,
|
|
11956
|
-
parsed: null,
|
|
11957
|
-
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
11958
|
-
};
|
|
11959
|
-
}
|
|
11960
|
-
return { file, stat: stat17, lang, parsed, content };
|
|
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"));
|
|
11961
12117
|
}
|
|
11962
|
-
|
|
11963
|
-
);
|
|
11964
|
-
const batchEntries = [];
|
|
11965
|
-
const deleteForFiles = [];
|
|
11966
|
-
for (let fi = 0; fi < statReadParse.length; fi++) {
|
|
11967
|
-
const settled = statReadParse[fi];
|
|
11968
|
-
const file = expectDefined6(batchFiles[fi]);
|
|
11969
|
-
if (settled.status === "rejected") {
|
|
11970
|
-
const err = settled.reason;
|
|
11971
|
-
if (err instanceof Error && isAbortError(err)) throw err;
|
|
11972
|
-
errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11973
|
-
continue;
|
|
12118
|
+
return;
|
|
11974
12119
|
}
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
errors.push(`${file}: ${result.error}`);
|
|
11979
|
-
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;
|
|
11980
12123
|
}
|
|
11981
|
-
const
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
12124
|
+
const line = this.buffer.slice(0, newline);
|
|
12125
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
12126
|
+
if (!line) continue;
|
|
12127
|
+
let message;
|
|
12128
|
+
try {
|
|
12129
|
+
message = JSON.parse(line);
|
|
12130
|
+
} catch {
|
|
12131
|
+
socket.destroy(new Error("invalid codebase-index server response"));
|
|
12132
|
+
return;
|
|
11987
12133
|
}
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
}
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
continue;
|
|
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;
|
|
12000
12145
|
}
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
});
|
|
12009
|
-
filesIndexed++;
|
|
12010
|
-
continue;
|
|
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;
|
|
12011
12153
|
}
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
symbolCount: parsed.symbols.length
|
|
12019
|
-
});
|
|
12020
|
-
deleteForFiles.push(file);
|
|
12154
|
+
this.info = message;
|
|
12155
|
+
this.markResponsive();
|
|
12156
|
+
this.transition("connected", { pid: message.pid });
|
|
12157
|
+
ensureHeartbeatLoop();
|
|
12158
|
+
this.connectResolve?.();
|
|
12159
|
+
return;
|
|
12021
12160
|
}
|
|
12022
|
-
if (
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
const symbolsWithIds = store.insertSymbols(entry.symbols);
|
|
12039
|
-
symbolsIndexed += symbolsWithIds.length;
|
|
12040
|
-
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
12041
|
-
filesIndexed++;
|
|
12042
|
-
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
12043
|
-
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
12044
|
-
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
12045
|
-
}
|
|
12046
|
-
store.resolveRefsForNames([
|
|
12047
|
-
...entry.symbols.map((symbol) => symbol.name),
|
|
12048
|
-
...entry.refs.map((ref) => ref.toName)
|
|
12049
|
-
]);
|
|
12050
|
-
store.upsertFile({
|
|
12051
|
-
file: entry.file,
|
|
12052
|
-
lang: entry.lang,
|
|
12053
|
-
mtimeMs: entry.mtimeMs,
|
|
12054
|
-
symbolCount: entry.symbolCount,
|
|
12055
|
-
lastIndexed: Date.now()
|
|
12056
|
-
});
|
|
12057
|
-
} catch (innerErr) {
|
|
12058
|
-
errors.push(
|
|
12059
|
-
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
12060
|
-
);
|
|
12061
|
-
}
|
|
12062
|
-
}
|
|
12063
|
-
}
|
|
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;
|
|
12064
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));
|
|
12065
12182
|
}
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
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);
|
|
12071
12202
|
}
|
|
12072
12203
|
}
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
return {
|
|
12081
|
-
filesIndexed,
|
|
12082
|
-
symbolsIndexed,
|
|
12083
|
-
langStats,
|
|
12084
|
-
durationMs,
|
|
12085
|
-
errors
|
|
12086
|
-
};
|
|
12087
|
-
}
|
|
12088
|
-
|
|
12089
|
-
// src/codebase-index/index-service.ts
|
|
12090
|
-
async function indexService(args, hooks = {}) {
|
|
12091
|
-
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
12092
|
-
try {
|
|
12093
|
-
return await runIndexerWithStore(store, {
|
|
12094
|
-
projectRoot: args.projectRoot,
|
|
12095
|
-
indexDir: args.indexDir,
|
|
12096
|
-
files: args.files,
|
|
12097
|
-
force: args.force,
|
|
12098
|
-
langs: args.langs,
|
|
12099
|
-
ignore: args.ignore,
|
|
12100
|
-
signal: hooks.signal,
|
|
12101
|
-
onProgress: hooks.onProgress
|
|
12102
|
-
});
|
|
12103
|
-
} finally {
|
|
12104
|
-
indexStorePool.release(store);
|
|
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);
|
|
12210
|
+
}
|
|
12105
12211
|
}
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
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
|
|
12247
|
+
});
|
|
12248
|
+
child.unref();
|
|
12122
12249
|
}
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
try {
|
|
12127
|
-
return store.getStats();
|
|
12128
|
-
} finally {
|
|
12129
|
-
indexStorePool.release(store);
|
|
12250
|
+
forceKillKnownServer() {
|
|
12251
|
+
const pid = this.info?.pid;
|
|
12252
|
+
return pid ? this.forceKillServer(pid) : false;
|
|
12130
12253
|
}
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
|
|
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
|
+
}
|
|
12138
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?.();
|
|
12139
12281
|
}
|
|
12140
|
-
function
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
|
|
12144
|
-
|
|
12145
|
-
indexStorePool.release(store);
|
|
12146
|
-
}
|
|
12282
|
+
function maybeStopHeartbeatLoop() {
|
|
12283
|
+
if (!heartbeatTimer) return;
|
|
12284
|
+
if ([...connections.values()].some((connection) => connection.isConnected())) return;
|
|
12285
|
+
clearInterval(heartbeatTimer);
|
|
12286
|
+
heartbeatTimer = void 0;
|
|
12147
12287
|
}
|
|
12148
|
-
function
|
|
12149
|
-
const
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
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);
|
|
12154
12294
|
}
|
|
12295
|
+
return connection;
|
|
12296
|
+
}
|
|
12297
|
+
function callProjectIndexServer(op, args, options) {
|
|
12298
|
+
return connectionFor(args.projectRoot, args.indexDir).call(op, args, options);
|
|
12155
12299
|
}
|
|
12156
12300
|
|
|
12157
|
-
// src/codebase-index/
|
|
12158
|
-
import
|
|
12159
|
-
import
|
|
12160
|
-
import
|
|
12161
|
-
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";
|
|
12162
12305
|
|
|
12163
|
-
// src/codebase-index/
|
|
12164
|
-
import {
|
|
12165
|
-
import
|
|
12166
|
-
import * as
|
|
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";
|
|
12167
12311
|
import * as path23 from "node:path";
|
|
12168
|
-
import {
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
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(/\$$/, "");
|
|
12324
|
+
}
|
|
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);
|
|
12178
12335
|
}
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
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
|
+
});
|
|
12184
12352
|
}
|
|
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
|
+
};
|
|
12185
12362
|
}
|
|
12186
|
-
function
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
return createHash4("sha256").update(resolvedIndexDir).digest("hex").slice(0, 24);
|
|
12193
|
-
}
|
|
12194
|
-
function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
12195
|
-
const key = projectIndexServerKey(projectRoot, indexDir);
|
|
12196
|
-
if (process.platform === "win32") {
|
|
12197
|
-
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
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 {
|
|
12198
12369
|
}
|
|
12199
|
-
return
|
|
12200
|
-
os7.tmpdir(),
|
|
12201
|
-
`wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`,
|
|
12202
|
-
`${key}.sock`
|
|
12203
|
-
);
|
|
12204
|
-
}
|
|
12205
|
-
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
12206
|
-
return path23.join(
|
|
12207
|
-
path23.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
12208
|
-
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
12209
|
-
);
|
|
12370
|
+
return compileGitignore(lines);
|
|
12210
12371
|
}
|
|
12211
12372
|
|
|
12212
|
-
// src/codebase-index/
|
|
12213
|
-
|
|
12214
|
-
function encodeProjectServerMessage(message) {
|
|
12215
|
-
return `${JSON.stringify(message)}
|
|
12216
|
-
`;
|
|
12217
|
-
}
|
|
12373
|
+
// src/codebase-index/indexer.ts
|
|
12374
|
+
init_languages2();
|
|
12218
12375
|
|
|
12219
|
-
// src/codebase-index/
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
this.pid = pid;
|
|
12229
|
-
}
|
|
12230
|
-
pid;
|
|
12231
|
-
name = "StaleProjectIndexServerError";
|
|
12232
|
-
};
|
|
12233
|
-
var connectionStates = /* @__PURE__ */ new Map();
|
|
12234
|
-
var connectionStateListeners = /* @__PURE__ */ new Set();
|
|
12235
|
-
var latestConnectionState = {
|
|
12236
|
-
status: "offline",
|
|
12237
|
-
connected: false
|
|
12238
|
-
};
|
|
12239
|
-
function resolveProjectIndexDaemonAvailability() {
|
|
12240
|
-
if (process.env["WRONGSTACK_INDEX_INLINE"] || process.env["WRONGSTACK_INDEX_SERVER"] === "0") {
|
|
12241
|
-
return { kind: "inline-requested" };
|
|
12242
|
-
}
|
|
12243
|
-
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
12244
|
-
try {
|
|
12245
|
-
const url = new URL(rel, import.meta.url);
|
|
12246
|
-
if (url.protocol === "file:" && fs17.existsSync(fileURLToPath2(url))) {
|
|
12247
|
-
return { kind: "available", url };
|
|
12248
|
-
}
|
|
12249
|
-
} 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 });
|
|
12250
12385
|
}
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
}
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
}
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
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 });
|
|
12409
|
+
}
|
|
12410
|
+
}
|
|
12263
12411
|
}
|
|
12264
|
-
|
|
12265
|
-
|
|
12412
|
+
|
|
12413
|
+
// src/codebase-index/indexer.ts
|
|
12414
|
+
var YIELD_EVERY_N = 50;
|
|
12415
|
+
function resolveParallelBatch() {
|
|
12416
|
+
return indexParallelBatchSize(availableParallelism());
|
|
12266
12417
|
}
|
|
12267
|
-
function
|
|
12268
|
-
|
|
12269
|
-
latestConnectionState = state;
|
|
12270
|
-
for (const listener of connectionStateListeners) listener(state);
|
|
12418
|
+
function yieldEventLoop() {
|
|
12419
|
+
return new Promise((resolve16) => setImmediate(resolve16));
|
|
12271
12420
|
}
|
|
12272
|
-
function
|
|
12273
|
-
if (
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
if (existing) return existing;
|
|
12277
|
-
if (!isProjectIndexServerAvailable()) {
|
|
12278
|
-
return { status: "unavailable", connected: false };
|
|
12279
|
-
}
|
|
12280
|
-
return {
|
|
12281
|
-
status: "offline",
|
|
12282
|
-
connected: false,
|
|
12283
|
-
projectRoot,
|
|
12284
|
-
indexDir,
|
|
12285
|
-
endpoint
|
|
12286
|
-
};
|
|
12287
|
-
}
|
|
12288
|
-
if (latestConnectionState.endpoint) return latestConnectionState;
|
|
12289
|
-
if (!isProjectIndexServerAvailable()) return { status: "unavailable", connected: false };
|
|
12290
|
-
return latestConnectionState;
|
|
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");
|
|
12291
12425
|
}
|
|
12292
|
-
function
|
|
12293
|
-
|
|
12294
|
-
return () => connectionStateListeners.delete(listener);
|
|
12426
|
+
function isAbortError(err) {
|
|
12427
|
+
return err instanceof DOMException && err.name === "AbortError";
|
|
12295
12428
|
}
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
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);
|
|
12302
12437
|
}
|
|
12303
|
-
function
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
const memory = health.memory && typeof health.memory === "object" ? health.memory : void 0;
|
|
12307
|
-
const activity = health.activity && typeof health.activity === "object" ? health.activity : void 0;
|
|
12308
|
-
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";
|
|
12438
|
+
function isMissingPathError(err) {
|
|
12439
|
+
const code = err?.code;
|
|
12440
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
12309
12441
|
}
|
|
12310
|
-
function
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
timer.unref?.();
|
|
12314
|
-
});
|
|
12442
|
+
function normalizeComparablePath(value) {
|
|
12443
|
+
const resolved = path23.resolve(value);
|
|
12444
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
12315
12445
|
}
|
|
12316
|
-
function
|
|
12317
|
-
return
|
|
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
|
+
});
|
|
12318
12462
|
}
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
lastError,
|
|
12352
|
-
...this.activity ? { activity: this.activity } : {},
|
|
12353
|
-
...this.health ? { health: this.health } : {}
|
|
12354
|
-
});
|
|
12355
|
-
}
|
|
12356
|
-
isConnected() {
|
|
12357
|
-
return this.socket !== null && !this.socket.destroyed && this.info !== null;
|
|
12358
|
-
}
|
|
12359
|
-
async checkHealth(spawnIfMissing = false, timeoutMs = SERVER_HEALTH_TIMEOUT_MS) {
|
|
12360
|
-
await this.ensureConnected(spawnIfMissing);
|
|
12361
|
-
if (this.healthCheck) return this.healthCheck;
|
|
12362
|
-
const startedAt = Date.now();
|
|
12363
|
-
this.healthCheck = this.request({ type: "ping" }, { timeoutMs }).then((server) => {
|
|
12364
|
-
const now = Date.now();
|
|
12365
|
-
this.health = {
|
|
12366
|
-
status: "healthy",
|
|
12367
|
-
checkedAt: now,
|
|
12368
|
-
lastHealthyAt: now,
|
|
12369
|
-
latencyMs: Math.max(0, now - startedAt),
|
|
12370
|
-
missedHeartbeats: 0,
|
|
12371
|
-
...isProjectIndexServerHealth(server) ? { server } : {}
|
|
12372
|
-
};
|
|
12373
|
-
this.transition("connected", { pid: this.info?.pid });
|
|
12374
|
-
return this.health;
|
|
12375
|
-
}).catch((error) => {
|
|
12376
|
-
if (!this.isConnected()) throw error;
|
|
12377
|
-
if ((this.health?.lastHealthyAt ?? 0) > startedAt) return this.health;
|
|
12378
|
-
const missedHeartbeats = (this.health?.missedHeartbeats ?? 0) + 1;
|
|
12379
|
-
const status = missedHeartbeats >= 3 ? "unresponsive" : "degraded";
|
|
12380
|
-
this.health = {
|
|
12381
|
-
status,
|
|
12382
|
-
checkedAt: Date.now(),
|
|
12383
|
-
lastHealthyAt: this.health?.lastHealthyAt ?? null,
|
|
12384
|
-
latencyMs: null,
|
|
12385
|
-
missedHeartbeats,
|
|
12386
|
-
...this.health?.server ? { server: this.health.server } : {}
|
|
12387
|
-
};
|
|
12388
|
-
this.transition(status, { pid: this.info?.pid, error });
|
|
12389
|
-
return this.health;
|
|
12390
|
-
}).finally(() => {
|
|
12391
|
-
this.healthCheck = null;
|
|
12392
|
-
});
|
|
12393
|
-
return this.healthCheck;
|
|
12394
|
-
}
|
|
12395
|
-
markResponsive() {
|
|
12396
|
-
const now = Date.now();
|
|
12397
|
-
this.health = {
|
|
12398
|
-
status: "healthy",
|
|
12399
|
-
checkedAt: now,
|
|
12400
|
-
lastHealthyAt: now,
|
|
12401
|
-
latencyMs: this.health?.latencyMs ?? null,
|
|
12402
|
-
missedHeartbeats: 0,
|
|
12403
|
-
...this.health?.server ? { server: this.health.server } : {}
|
|
12404
|
-
};
|
|
12405
|
-
}
|
|
12406
|
-
async call(op, args, options) {
|
|
12407
|
-
if (options.signal?.aborted) throw cancellationError(options.signal);
|
|
12408
|
-
await this.ensureConnected(true);
|
|
12409
|
-
if (options.signal?.aborted) throw cancellationError(options.signal);
|
|
12410
|
-
return this.request({ type: "request", op, args }, options);
|
|
12411
|
-
}
|
|
12412
|
-
async shutdownRemote(reason) {
|
|
12413
|
-
try {
|
|
12414
|
-
await this.ensureConnected(false);
|
|
12415
|
-
} catch {
|
|
12416
|
-
return { stopped: false, reason: "not-running" };
|
|
12417
|
-
}
|
|
12418
|
-
const pid = this.info?.pid;
|
|
12419
|
-
try {
|
|
12420
|
-
this.transition("stopping", { pid });
|
|
12421
|
-
await this.request(
|
|
12422
|
-
{ type: "shutdown", reason },
|
|
12423
|
-
{ timeoutMs: SERVER_CONTROL_TIMEOUT_MS }
|
|
12424
|
-
);
|
|
12425
|
-
return { stopped: true, pid };
|
|
12426
|
-
} catch (error) {
|
|
12427
|
-
const forceKilled = this.forceKillKnownServer();
|
|
12428
|
-
return {
|
|
12429
|
-
stopped: forceKilled,
|
|
12430
|
-
pid,
|
|
12431
|
-
reason: forceKilled ? `force-killed after graceful shutdown failed: ${error instanceof Error ? error.message : String(error)}` : error instanceof Error ? error.message : String(error)
|
|
12432
|
-
};
|
|
12433
|
-
} finally {
|
|
12434
|
-
this.close();
|
|
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
|
+
}
|
|
12435
12495
|
}
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
const
|
|
12446
|
-
|
|
12447
|
-
status: "healthy",
|
|
12448
|
-
checkedAt: now,
|
|
12449
|
-
lastHealthyAt: now,
|
|
12450
|
-
latencyMs: Math.max(0, now - startedAt),
|
|
12451
|
-
missedHeartbeats: 0,
|
|
12452
|
-
server: result.health
|
|
12453
|
-
};
|
|
12454
|
-
this.transition("connected", { pid: this.info?.pid });
|
|
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);
|
|
12455
12507
|
}
|
|
12508
|
+
return {
|
|
12509
|
+
files,
|
|
12510
|
+
trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
|
|
12511
|
+
};
|
|
12512
|
+
} catch {
|
|
12513
|
+
return null;
|
|
12456
12514
|
}
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
this.rejectPending(new Error("codebase-index client disconnected"));
|
|
12468
|
-
this.transition("offline");
|
|
12469
|
-
maybeStopHeartbeatLoop();
|
|
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
|
+
};
|
|
12470
12525
|
}
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
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);
|
|
12475
12537
|
}
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
if (
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
entry.reject(cancellationError(signal));
|
|
12498
|
-
} : void 0;
|
|
12499
|
-
this.pending.set(id, {
|
|
12500
|
-
resolve: resolve16,
|
|
12501
|
-
reject,
|
|
12502
|
-
timer,
|
|
12503
|
-
signal,
|
|
12504
|
-
onAbort,
|
|
12505
|
-
onProgress: options.onProgress
|
|
12506
|
-
});
|
|
12507
|
-
if (signal && onAbort) {
|
|
12508
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
12509
|
-
if (signal.aborted) {
|
|
12510
|
-
onAbort();
|
|
12511
|
-
return;
|
|
12538
|
+
let entries;
|
|
12539
|
+
try {
|
|
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
|
+
}
|
|
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);
|
|
12512
12559
|
}
|
|
12513
12560
|
}
|
|
12514
|
-
|
|
12515
|
-
|
|
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;
|
|
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 });
|
|
12516
12583
|
}
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
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);
|
|
12526
12607
|
});
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
while (Date.now() < deadline) {
|
|
12535
|
-
try {
|
|
12536
|
-
await this.connectOnce();
|
|
12537
|
-
return;
|
|
12538
|
-
} catch (error) {
|
|
12539
|
-
lastError = error;
|
|
12540
|
-
if (error instanceof StaleProjectIndexServerError) {
|
|
12541
|
-
staleAttempts++;
|
|
12542
|
-
if (!spawnIfMissing) break;
|
|
12543
|
-
if (staleAttempts >= 3) this.forceKillServer(error.pid);
|
|
12544
|
-
spawned = false;
|
|
12545
|
-
await delay(100);
|
|
12546
|
-
continue;
|
|
12547
|
-
}
|
|
12548
|
-
}
|
|
12549
|
-
if (!spawnIfMissing) break;
|
|
12550
|
-
if (!spawned) {
|
|
12551
|
-
this.spawnDetachedServer();
|
|
12552
|
-
spawned = true;
|
|
12553
|
-
}
|
|
12554
|
-
await delay(75);
|
|
12555
|
-
}
|
|
12556
|
-
throw lastError;
|
|
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;
|
|
12557
12615
|
}
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
this.health = null;
|
|
12564
|
-
this.buffer = "";
|
|
12565
|
-
return new Promise((resolve16, reject) => {
|
|
12566
|
-
const socket = net3.createConnection(this.endpoint);
|
|
12567
|
-
this.socket = socket;
|
|
12568
|
-
socket.setEncoding("utf8");
|
|
12569
|
-
const timer = setTimeout(() => {
|
|
12570
|
-
reject(new Error("codebase-index server handshake timed out"));
|
|
12571
|
-
socket.destroy();
|
|
12572
|
-
}, CONNECT_ATTEMPT_TIMEOUT_MS);
|
|
12573
|
-
timer.unref?.();
|
|
12574
|
-
const finishResolve = () => {
|
|
12575
|
-
clearTimeout(timer);
|
|
12576
|
-
this.connectResolve = null;
|
|
12577
|
-
this.connectReject = null;
|
|
12578
|
-
resolve16();
|
|
12579
|
-
};
|
|
12580
|
-
const finishReject = (error) => {
|
|
12581
|
-
clearTimeout(timer);
|
|
12582
|
-
this.connectResolve = null;
|
|
12583
|
-
this.connectReject = null;
|
|
12584
|
-
reject(error);
|
|
12585
|
-
};
|
|
12586
|
-
this.connectResolve = finishResolve;
|
|
12587
|
-
this.connectReject = finishReject;
|
|
12588
|
-
socket.on("data", (chunk) => this.onData(socket, chunk));
|
|
12589
|
-
socket.on("error", (error) => {
|
|
12590
|
-
if (!this.info) finishReject(error);
|
|
12591
|
-
});
|
|
12592
|
-
socket.on("close", () => this.onClose(socket));
|
|
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;
|
|
12593
12621
|
});
|
|
12594
12622
|
}
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
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;
|
|
12639
|
+
});
|
|
12640
|
+
if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
|
|
12641
|
+
}
|
|
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 };
|
|
12603
12717
|
}
|
|
12604
|
-
|
|
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;
|
|
12605
12730
|
}
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12731
|
+
const result = settled.value;
|
|
12732
|
+
if (result.error) {
|
|
12733
|
+
if (result.missing) store.deleteFile(file);
|
|
12734
|
+
errors.push(`${file}: ${result.error}`);
|
|
12735
|
+
continue;
|
|
12609
12736
|
}
|
|
12610
|
-
const
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
} catch {
|
|
12617
|
-
socket.destroy(new Error("invalid codebase-index server response"));
|
|
12618
|
-
return;
|
|
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;
|
|
12619
12743
|
}
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
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;
|
|
12631
12756
|
}
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
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;
|
|
12639
12767
|
}
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
this.markResponsive();
|
|
12650
|
-
this.transition("connected", { pid: this.info?.pid });
|
|
12651
|
-
return;
|
|
12652
|
-
}
|
|
12653
|
-
const entry = this.pending.get(message.id);
|
|
12654
|
-
if (!entry) return;
|
|
12655
|
-
this.markResponsive();
|
|
12656
|
-
const status = connectionStates.get(this.endpoint)?.status;
|
|
12657
|
-
if (status === "degraded" || status === "unresponsive") {
|
|
12658
|
-
this.transition("connected", { pid: this.info?.pid });
|
|
12659
|
-
}
|
|
12660
|
-
if (message.type === "progress") {
|
|
12661
|
-
entry.onProgress?.(message.current, message.total);
|
|
12662
|
-
return;
|
|
12663
|
-
}
|
|
12664
|
-
this.pending.delete(message.id);
|
|
12665
|
-
this.cleanupPending(entry);
|
|
12666
|
-
if (message.ok) entry.resolve(message.result);
|
|
12667
|
-
else entry.reject(remoteError(message.error, message.errorName));
|
|
12668
|
-
}
|
|
12669
|
-
onClose(socket) {
|
|
12670
|
-
if (socket !== this.socket) return;
|
|
12671
|
-
const wasConnected = this.info !== null;
|
|
12672
|
-
this.socket = null;
|
|
12673
|
-
this.info = null;
|
|
12674
|
-
this.activity = null;
|
|
12675
|
-
this.health = null;
|
|
12676
|
-
const error = new Error("codebase-index server connection closed");
|
|
12677
|
-
this.connectReject?.(error);
|
|
12678
|
-
this.connectResolve = null;
|
|
12679
|
-
this.connectReject = null;
|
|
12680
|
-
this.rejectPending(error);
|
|
12681
|
-
if (wasConnected) this.transition("error", { error });
|
|
12682
|
-
maybeStopHeartbeatLoop();
|
|
12683
|
-
}
|
|
12684
|
-
cleanupPending(entry) {
|
|
12685
|
-
clearTimeout(entry.timer);
|
|
12686
|
-
if (entry.signal && entry.onAbort) {
|
|
12687
|
-
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
12688
|
-
}
|
|
12689
|
-
}
|
|
12690
|
-
rejectPending(error) {
|
|
12691
|
-
const entries = [...this.pending.values()];
|
|
12692
|
-
this.pending.clear();
|
|
12693
|
-
for (const entry of entries) {
|
|
12694
|
-
this.cleanupPending(entry);
|
|
12695
|
-
entry.reject(error);
|
|
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
|
|
12775
|
+
});
|
|
12776
|
+
deleteForFiles.push(file);
|
|
12696
12777
|
}
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
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
|
+
}
|
|
12818
|
+
}
|
|
12819
|
+
}
|
|
12714
12820
|
}
|
|
12715
|
-
this.connectReject?.(new StaleProjectIndexServerError(reason, message.pid));
|
|
12716
12821
|
}
|
|
12717
|
-
|
|
12718
|
-
const
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
try {
|
|
12722
|
-
fs17.rmSync(this.endpoint, { force: true });
|
|
12723
|
-
} catch {
|
|
12822
|
+
if (discoveredFiles && discoveryComplete) {
|
|
12823
|
+
for (const [file_] of existingMeta) {
|
|
12824
|
+
if (!discoveredFiles.has(file_)) {
|
|
12825
|
+
store.deleteFile(file_);
|
|
12724
12826
|
}
|
|
12725
12827
|
}
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12828
|
+
}
|
|
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
|
|
12733
12858
|
});
|
|
12734
|
-
|
|
12859
|
+
} finally {
|
|
12860
|
+
indexStorePool.release(store);
|
|
12735
12861
|
}
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
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);
|
|
12739
12878
|
}
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
if (metadata.pid === pid) fs17.rmSync(metadataPath, { force: true });
|
|
12748
|
-
} catch {
|
|
12749
|
-
}
|
|
12750
|
-
return true;
|
|
12751
|
-
} catch {
|
|
12752
|
-
return false;
|
|
12753
|
-
}
|
|
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);
|
|
12754
12886
|
}
|
|
12755
|
-
};
|
|
12756
|
-
var connections = /* @__PURE__ */ new Map();
|
|
12757
|
-
var heartbeatTimer;
|
|
12758
|
-
function ensureHeartbeatLoop() {
|
|
12759
|
-
if (heartbeatTimer) return;
|
|
12760
|
-
heartbeatTimer = setInterval(() => {
|
|
12761
|
-
for (const connection of connections.values()) {
|
|
12762
|
-
if (connection.isConnected()) void connection.checkHealth(false).catch(() => {
|
|
12763
|
-
});
|
|
12764
|
-
}
|
|
12765
|
-
}, SERVER_HEARTBEAT_INTERVAL_MS);
|
|
12766
|
-
heartbeatTimer.unref?.();
|
|
12767
12887
|
}
|
|
12768
|
-
function
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
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
|
+
}
|
|
12773
12895
|
}
|
|
12774
|
-
function
|
|
12775
|
-
const
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
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);
|
|
12780
12902
|
}
|
|
12781
|
-
return connection;
|
|
12782
12903
|
}
|
|
12783
|
-
function
|
|
12784
|
-
|
|
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
|
+
}
|
|
12785
12911
|
}
|
|
12786
12912
|
|
|
12787
12913
|
// src/codebase-index/background-indexer.ts
|
|
@@ -12892,8 +13018,17 @@ function terminateWorker(reason) {
|
|
|
12892
13018
|
if (w) void w.terminate().catch(() => {
|
|
12893
13019
|
});
|
|
12894
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
|
+
}
|
|
12895
13030
|
function callIndexOp(op, args, opts) {
|
|
12896
|
-
const availability = resolveProjectIndexDaemonAvailability();
|
|
13031
|
+
const availability = resolveProjectIndexDaemonAvailability(args.projectRoot, args.indexDir);
|
|
12897
13032
|
if (availability.kind === "available") {
|
|
12898
13033
|
return callProjectIndexServer(op, args, opts);
|
|
12899
13034
|
}
|
|
@@ -12904,6 +13039,14 @@ function callIndexOp(op, args, opts) {
|
|
|
12904
13039
|
)
|
|
12905
13040
|
);
|
|
12906
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
|
+
}
|
|
12907
13050
|
const w = ensureWorker();
|
|
12908
13051
|
if (!w) return callInline(op, args, opts);
|
|
12909
13052
|
if (opts.signal?.aborted) {
|
|
@@ -13349,6 +13492,7 @@ var codebaseStatsTool = {
|
|
|
13349
13492
|
};
|
|
13350
13493
|
|
|
13351
13494
|
// src/codebase-index/dead-code-scan.ts
|
|
13495
|
+
init_languages2();
|
|
13352
13496
|
import * as fs19 from "node:fs";
|
|
13353
13497
|
import * as path24 from "node:path";
|
|
13354
13498
|
var deadCodeScanTool = {
|
|
@@ -13417,7 +13561,15 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
|
|
|
13417
13561
|
if (rootPkg) {
|
|
13418
13562
|
addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
|
|
13419
13563
|
}
|
|
13420
|
-
|
|
13564
|
+
let workspaces;
|
|
13565
|
+
if (rootPkg) {
|
|
13566
|
+
workspaces = extractWorkspaceGlobs(rootPkg, projectRoot);
|
|
13567
|
+
if (workspaces.length === 0) {
|
|
13568
|
+
workspaces = extractPnpmWorkspaceDirs(projectRoot);
|
|
13569
|
+
}
|
|
13570
|
+
} else {
|
|
13571
|
+
workspaces = [];
|
|
13572
|
+
}
|
|
13421
13573
|
for (const wsDir of workspaces) {
|
|
13422
13574
|
const pkgJsonPath = path24.join(wsDir, "package.json");
|
|
13423
13575
|
const pkg = tryReadJson(pkgJsonPath);
|
|
@@ -13439,77 +13591,189 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
|
|
|
13439
13591
|
}
|
|
13440
13592
|
return [...entries];
|
|
13441
13593
|
}
|
|
13594
|
+
var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
|
|
13595
|
+
var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path24.sep}${d}${path24.sep}`);
|
|
13596
|
+
function trySourceEquivalent(resolved) {
|
|
13597
|
+
resolved = resolved.replace(/[/\\]/g, path24.sep);
|
|
13598
|
+
for (const marker of BUILD_OUTPUT_DIR_NAMES) {
|
|
13599
|
+
const idx = resolved.indexOf(marker);
|
|
13600
|
+
if (idx === -1) continue;
|
|
13601
|
+
const base = resolved.replace(marker, `${path24.sep}src${path24.sep}`);
|
|
13602
|
+
const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
13603
|
+
if (candidate !== base && fs19.existsSync(candidate)) {
|
|
13604
|
+
return candidate;
|
|
13605
|
+
}
|
|
13606
|
+
const dtsStripped = base.replace(/\.d\.ts$/, "");
|
|
13607
|
+
const candidateDts = dtsStripped + ".ts";
|
|
13608
|
+
if (candidateDts !== base && candidateDts !== candidate && fs19.existsSync(candidateDts)) {
|
|
13609
|
+
return candidateDts;
|
|
13610
|
+
}
|
|
13611
|
+
const candidateNoExt = base + ".ts";
|
|
13612
|
+
if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs19.existsSync(candidateNoExt)) {
|
|
13613
|
+
return candidateNoExt;
|
|
13614
|
+
}
|
|
13615
|
+
}
|
|
13616
|
+
return null;
|
|
13617
|
+
}
|
|
13618
|
+
function tryAddEntryPath(pkgDir, rawPath, entries) {
|
|
13619
|
+
const resolved = resolveAgainst(pkgDir, rawPath);
|
|
13620
|
+
if (fs19.existsSync(resolved)) entries.add(resolved);
|
|
13621
|
+
const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
13622
|
+
if (tsResolved !== resolved && fs19.existsSync(tsResolved)) {
|
|
13623
|
+
entries.add(tsResolved);
|
|
13624
|
+
}
|
|
13625
|
+
const srcAlt = trySourceEquivalent(resolved);
|
|
13626
|
+
if (srcAlt) entries.add(srcAlt);
|
|
13627
|
+
}
|
|
13442
13628
|
function addPkgJsonEntryPoints(pkgDir, pkg, entries) {
|
|
13443
13629
|
if (typeof pkg.main === "string") {
|
|
13444
|
-
|
|
13445
|
-
if (fs19.existsSync(resolved)) entries.add(resolved);
|
|
13446
|
-
const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
13447
|
-
if (tsResolved !== resolved && fs19.existsSync(tsResolved)) {
|
|
13448
|
-
entries.add(tsResolved);
|
|
13449
|
-
}
|
|
13630
|
+
tryAddEntryPath(pkgDir, pkg.main, entries);
|
|
13450
13631
|
}
|
|
13451
13632
|
const bin = pkg.bin;
|
|
13452
13633
|
if (typeof bin === "string") {
|
|
13453
|
-
|
|
13454
|
-
if (fs19.existsSync(resolved)) entries.add(resolved);
|
|
13634
|
+
tryAddEntryPath(pkgDir, bin, entries);
|
|
13455
13635
|
} else if (bin && typeof bin === "object") {
|
|
13456
13636
|
for (const value of Object.values(bin)) {
|
|
13457
13637
|
if (typeof value === "string") {
|
|
13458
|
-
|
|
13459
|
-
if (fs19.existsSync(resolved)) entries.add(resolved);
|
|
13638
|
+
tryAddEntryPath(pkgDir, value, entries);
|
|
13460
13639
|
}
|
|
13461
13640
|
}
|
|
13462
13641
|
}
|
|
13463
13642
|
for (const key of ["types", "typings"]) {
|
|
13464
13643
|
if (typeof pkg[key] === "string") {
|
|
13465
|
-
|
|
13466
|
-
if (fs19.existsSync(resolved)) entries.add(resolved);
|
|
13644
|
+
tryAddEntryPath(pkgDir, pkg[key], entries);
|
|
13467
13645
|
}
|
|
13468
13646
|
}
|
|
13469
13647
|
const exports_ = pkg.exports;
|
|
13470
13648
|
if (exports_ && typeof exports_ === "object") {
|
|
13471
13649
|
for (const value of Object.values(exports_)) {
|
|
13472
13650
|
if (typeof value === "string") {
|
|
13473
|
-
|
|
13474
|
-
if (fs19.existsSync(resolved)) entries.add(resolved);
|
|
13651
|
+
tryAddEntryPath(pkgDir, value, entries);
|
|
13475
13652
|
} else if (value && typeof value === "object") {
|
|
13476
|
-
for (const nested of Object.values(
|
|
13477
|
-
value
|
|
13478
|
-
)) {
|
|
13653
|
+
for (const nested of Object.values(value)) {
|
|
13479
13654
|
if (typeof nested === "string") {
|
|
13480
|
-
|
|
13481
|
-
if (fs19.existsSync(resolved)) entries.add(resolved);
|
|
13655
|
+
tryAddEntryPath(pkgDir, nested, entries);
|
|
13482
13656
|
}
|
|
13483
13657
|
}
|
|
13484
13658
|
}
|
|
13485
13659
|
}
|
|
13486
13660
|
}
|
|
13487
13661
|
}
|
|
13662
|
+
function expandGlobPattern(entry, projectRoot) {
|
|
13663
|
+
const dirs = [];
|
|
13664
|
+
if (entry.includes("*")) {
|
|
13665
|
+
const base = entry.replace(/\/\*+$/, "");
|
|
13666
|
+
const baseDir = path24.resolve(projectRoot, base);
|
|
13667
|
+
try {
|
|
13668
|
+
const children = fs19.readdirSync(baseDir, { withFileTypes: true });
|
|
13669
|
+
for (const child of children) {
|
|
13670
|
+
if (child.isDirectory()) {
|
|
13671
|
+
dirs.push(path24.join(baseDir, child.name));
|
|
13672
|
+
}
|
|
13673
|
+
}
|
|
13674
|
+
} catch {
|
|
13675
|
+
}
|
|
13676
|
+
} else {
|
|
13677
|
+
dirs.push(path24.resolve(projectRoot, entry));
|
|
13678
|
+
}
|
|
13679
|
+
return dirs;
|
|
13680
|
+
}
|
|
13488
13681
|
function extractWorkspaceGlobs(pkg, projectRoot) {
|
|
13489
13682
|
const dirs = [];
|
|
13490
13683
|
const workspaces = pkg.workspaces;
|
|
13491
13684
|
if (Array.isArray(workspaces)) {
|
|
13492
13685
|
for (const entry of workspaces) {
|
|
13493
13686
|
if (typeof entry === "string") {
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13687
|
+
dirs.push(...expandGlobPattern(entry, projectRoot));
|
|
13688
|
+
}
|
|
13689
|
+
}
|
|
13690
|
+
}
|
|
13691
|
+
return dirs;
|
|
13692
|
+
}
|
|
13693
|
+
function extractPnpmWorkspaceDirs(projectRoot) {
|
|
13694
|
+
const yamlPath = path24.join(projectRoot, "pnpm-workspace.yaml");
|
|
13695
|
+
if (!fs19.existsSync(yamlPath)) return [];
|
|
13696
|
+
try {
|
|
13697
|
+
const content = fs19.readFileSync(yamlPath, "utf8");
|
|
13698
|
+
const dirs = [];
|
|
13699
|
+
let inPackages = false;
|
|
13700
|
+
const lines = content.split("\n");
|
|
13701
|
+
const itemRe = /^\s+-\s+"([^"]+)"|^\s+-\s+'([^']+)'|^\s+-\s+(\S+)/;
|
|
13702
|
+
for (const line of lines) {
|
|
13703
|
+
const trimmed = line.trim();
|
|
13704
|
+
if (/^packages\s*:\s*$/.test(trimmed)) {
|
|
13705
|
+
inPackages = true;
|
|
13706
|
+
continue;
|
|
13707
|
+
}
|
|
13708
|
+
if (inPackages && trimmed.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
13709
|
+
if (!trimmed.startsWith("-")) {
|
|
13710
|
+
inPackages = false;
|
|
13711
|
+
continue;
|
|
13712
|
+
}
|
|
13713
|
+
}
|
|
13714
|
+
if (inPackages) {
|
|
13715
|
+
const m = itemRe.exec(line);
|
|
13716
|
+
if (m) {
|
|
13717
|
+
const entry = m[1] ?? m[2] ?? m[3];
|
|
13718
|
+
if (entry) {
|
|
13719
|
+
dirs.push(...expandGlobPattern(entry, projectRoot));
|
|
13505
13720
|
}
|
|
13506
|
-
} else {
|
|
13507
|
-
dirs.push(path24.resolve(projectRoot, entry));
|
|
13508
13721
|
}
|
|
13509
13722
|
}
|
|
13510
13723
|
}
|
|
13724
|
+
return dirs;
|
|
13725
|
+
} catch {
|
|
13726
|
+
return [];
|
|
13511
13727
|
}
|
|
13512
|
-
|
|
13728
|
+
}
|
|
13729
|
+
function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
|
|
13730
|
+
if (!moduleSpecifier.startsWith(".")) return [];
|
|
13731
|
+
const dir = path24.dirname(importerPath);
|
|
13732
|
+
const base = path24.resolve(dir, moduleSpecifier);
|
|
13733
|
+
const results = [];
|
|
13734
|
+
const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
13735
|
+
const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
|
|
13736
|
+
const candidates = skipBase ? [stripped] : [base];
|
|
13737
|
+
for (const candidate of candidates) {
|
|
13738
|
+
if (indexedFiles.has(candidate + ".ts")) results.push(candidate + ".ts");
|
|
13739
|
+
if (indexedFiles.has(candidate + ".tsx")) results.push(candidate + ".tsx");
|
|
13740
|
+
if (indexedFiles.has(candidate + ".js")) results.push(candidate + ".js");
|
|
13741
|
+
if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
|
|
13742
|
+
if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
|
|
13743
|
+
if (indexedFiles.has(candidate + ".cjs")) results.push(candidate + ".cjs");
|
|
13744
|
+
if (indexedFiles.has(path24.join(candidate, "index.ts")))
|
|
13745
|
+
results.push(path24.join(candidate, "index.ts"));
|
|
13746
|
+
if (indexedFiles.has(path24.join(candidate, "index.tsx")))
|
|
13747
|
+
results.push(path24.join(candidate, "index.tsx"));
|
|
13748
|
+
if (indexedFiles.has(path24.join(candidate, "index.js")))
|
|
13749
|
+
results.push(path24.join(candidate, "index.js"));
|
|
13750
|
+
if (indexedFiles.has(path24.join(candidate, "index.jsx")))
|
|
13751
|
+
results.push(path24.join(candidate, "index.jsx"));
|
|
13752
|
+
if (indexedFiles.has(path24.join(candidate, "index.mjs")))
|
|
13753
|
+
results.push(path24.join(candidate, "index.mjs"));
|
|
13754
|
+
if (indexedFiles.has(path24.join(candidate, "index.cjs")))
|
|
13755
|
+
results.push(path24.join(candidate, "index.cjs"));
|
|
13756
|
+
}
|
|
13757
|
+
return [...new Set(results)];
|
|
13758
|
+
}
|
|
13759
|
+
function parseNamedExportSymbols(matchText) {
|
|
13760
|
+
const braceStart = matchText.indexOf("{");
|
|
13761
|
+
if (braceStart === -1) return null;
|
|
13762
|
+
const braceEnd = matchText.indexOf("}", braceStart);
|
|
13763
|
+
if (braceEnd === -1) return null;
|
|
13764
|
+
const inner = matchText.slice(braceStart + 1, braceEnd);
|
|
13765
|
+
const symbols = [];
|
|
13766
|
+
for (const part of inner.split(",")) {
|
|
13767
|
+
let s = part.trim();
|
|
13768
|
+
if (!s) continue;
|
|
13769
|
+
s = s.replace(/^type\s+/, "");
|
|
13770
|
+
const asIdx = s.search(/\s+as\s+/);
|
|
13771
|
+
if (asIdx !== -1) {
|
|
13772
|
+
s = s.slice(0, asIdx).trim();
|
|
13773
|
+
}
|
|
13774
|
+
if (s) symbols.push(s);
|
|
13775
|
+
}
|
|
13776
|
+
return symbols;
|
|
13513
13777
|
}
|
|
13514
13778
|
function runDeadCodeScan(projectRoot, opts = {}) {
|
|
13515
13779
|
const store = opts.store ?? indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
|
|
@@ -13531,12 +13795,71 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
13531
13795
|
}
|
|
13532
13796
|
const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
|
|
13533
13797
|
const entryFileSet = new Set(discoveredFiles.map((f) => path24.resolve(f)));
|
|
13798
|
+
const indexedFiles = /* @__PURE__ */ new Set();
|
|
13799
|
+
for (const s of allSymbols) indexedFiles.add(s.file);
|
|
13800
|
+
for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
|
|
13534
13801
|
const seedIds = /* @__PURE__ */ new Set();
|
|
13535
13802
|
for (const s of allSymbols) {
|
|
13536
13803
|
if (entryFileSet.has(s.file)) {
|
|
13537
13804
|
seedIds.add(s.id);
|
|
13538
13805
|
}
|
|
13539
13806
|
}
|
|
13807
|
+
const fileToSymbolIds = /* @__PURE__ */ new Map();
|
|
13808
|
+
for (const s of allSymbols) {
|
|
13809
|
+
let byFile = fileToSymbolIds.get(s.file);
|
|
13810
|
+
if (!byFile) {
|
|
13811
|
+
byFile = [];
|
|
13812
|
+
fileToSymbolIds.set(s.file, byFile);
|
|
13813
|
+
}
|
|
13814
|
+
byFile.push(s.id);
|
|
13815
|
+
}
|
|
13816
|
+
const scannedBarrels = /* @__PURE__ */ new Set();
|
|
13817
|
+
const barrelWorkList = [...entryFileSet];
|
|
13818
|
+
while (barrelWorkList.length > 0) {
|
|
13819
|
+
const epFile = barrelWorkList.pop();
|
|
13820
|
+
if (scannedBarrels.has(epFile)) continue;
|
|
13821
|
+
scannedBarrels.add(epFile);
|
|
13822
|
+
try {
|
|
13823
|
+
const content = fs19.readFileSync(epFile, "utf8");
|
|
13824
|
+
const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
|
|
13825
|
+
const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
|
|
13826
|
+
let match;
|
|
13827
|
+
while ((match = reExportRe.exec(strippedContent)) !== null) {
|
|
13828
|
+
const moduleSpec = match[1];
|
|
13829
|
+
const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
|
|
13830
|
+
for (const rf of resolvedFiles) {
|
|
13831
|
+
const fileSyms = fileToSymbolIds.get(rf);
|
|
13832
|
+
if (fileSyms) {
|
|
13833
|
+
const namedSymbols = parseNamedExportSymbols(match[0]);
|
|
13834
|
+
if (namedSymbols) {
|
|
13835
|
+
const nameSet = new Set(namedSymbols);
|
|
13836
|
+
for (const sid of fileSyms) {
|
|
13837
|
+
const sym = symbolById.get(sid);
|
|
13838
|
+
if (sym && nameSet.has(sym.name)) seedIds.add(sid);
|
|
13839
|
+
}
|
|
13840
|
+
} else {
|
|
13841
|
+
for (const sid of fileSyms) seedIds.add(sid);
|
|
13842
|
+
}
|
|
13843
|
+
}
|
|
13844
|
+
if (!scannedBarrels.has(rf)) {
|
|
13845
|
+
barrelWorkList.push(rf);
|
|
13846
|
+
}
|
|
13847
|
+
}
|
|
13848
|
+
}
|
|
13849
|
+
} catch (err) {
|
|
13850
|
+
if (err instanceof Error && err.code !== "ENOENT") {
|
|
13851
|
+
console.warn(
|
|
13852
|
+
JSON.stringify({
|
|
13853
|
+
level: "warn",
|
|
13854
|
+
event: "dead_code_scan_barrel_read_failed",
|
|
13855
|
+
message: err.message,
|
|
13856
|
+
file: epFile,
|
|
13857
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
13858
|
+
})
|
|
13859
|
+
);
|
|
13860
|
+
}
|
|
13861
|
+
}
|
|
13862
|
+
}
|
|
13540
13863
|
const alive = new Set(seedIds);
|
|
13541
13864
|
const frontier = [...seedIds];
|
|
13542
13865
|
const visitedEdges = /* @__PURE__ */ new Set();
|
|
@@ -13570,8 +13893,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
13570
13893
|
dead.push({
|
|
13571
13894
|
name: s.name,
|
|
13572
13895
|
kind: s.kind,
|
|
13573
|
-
lang: "ts",
|
|
13574
|
-
// populated from symbol file metadata
|
|
13896
|
+
lang: detectLang(s.file) ?? "ts",
|
|
13575
13897
|
file: s.file,
|
|
13576
13898
|
line: s.line,
|
|
13577
13899
|
reason
|
|
@@ -13596,16 +13918,14 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
13596
13918
|
deadFiles.push({
|
|
13597
13919
|
file,
|
|
13598
13920
|
symbolCount: syms.length,
|
|
13599
|
-
lang:
|
|
13921
|
+
lang: detectLang(file) ?? "ts"
|
|
13600
13922
|
});
|
|
13601
13923
|
}
|
|
13602
13924
|
}
|
|
13603
13925
|
const deadPackages = [];
|
|
13604
13926
|
const pkgEntries = findPackageEntries(projectRoot);
|
|
13605
13927
|
for (const [pkgName, pkgDir] of pkgEntries) {
|
|
13606
|
-
const pkgFiles = allSymbols.filter(
|
|
13607
|
-
(s) => s.file.startsWith(pkgDir + path24.sep)
|
|
13608
|
-
);
|
|
13928
|
+
const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path24.sep));
|
|
13609
13929
|
if (pkgFiles.length === 0) continue;
|
|
13610
13930
|
const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
|
|
13611
13931
|
if (pkgUsed.length === 0) {
|
|
@@ -13646,7 +13966,10 @@ function findPackageEntries(projectRoot) {
|
|
|
13646
13966
|
pkgMap.set(rootPkg.name, projectRoot);
|
|
13647
13967
|
}
|
|
13648
13968
|
if (rootPkg) {
|
|
13649
|
-
|
|
13969
|
+
let wsDirs = extractWorkspaceGlobs(rootPkg, projectRoot);
|
|
13970
|
+
if (wsDirs.length === 0) {
|
|
13971
|
+
wsDirs = extractPnpmWorkspaceDirs(projectRoot);
|
|
13972
|
+
}
|
|
13650
13973
|
for (const wsDir of wsDirs) {
|
|
13651
13974
|
const wsPkg = tryReadJson(path24.join(wsDir, "package.json"));
|
|
13652
13975
|
if (wsPkg && typeof wsPkg.name === "string") {
|
|
@@ -14263,7 +14586,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
14263
14586
|
|
|
14264
14587
|
// src/e2e.ts
|
|
14265
14588
|
init_util();
|
|
14266
|
-
import { open, readdir as
|
|
14589
|
+
import { open, readdir as readdir6 } from "node:fs/promises";
|
|
14267
14590
|
import * as path27 from "node:path";
|
|
14268
14591
|
async function readBoundedText(filePath, maxBytes) {
|
|
14269
14592
|
let handle;
|
|
@@ -14381,7 +14704,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
14381
14704
|
}
|
|
14382
14705
|
let entries;
|
|
14383
14706
|
try {
|
|
14384
|
-
entries = await
|
|
14707
|
+
entries = await readdir6(current.directory, { withFileTypes: true });
|
|
14385
14708
|
} catch {
|
|
14386
14709
|
continue;
|
|
14387
14710
|
}
|
|
@@ -14440,7 +14763,7 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
14440
14763
|
while (true) {
|
|
14441
14764
|
const names = /* @__PURE__ */ new Set();
|
|
14442
14765
|
try {
|
|
14443
|
-
for (const entry of await
|
|
14766
|
+
for (const entry of await readdir6(directory)) names.add(entry);
|
|
14444
14767
|
} catch {
|
|
14445
14768
|
}
|
|
14446
14769
|
if (names.has("pnpm-lock.yaml")) return "pnpm";
|
|
@@ -14508,7 +14831,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
14508
14831
|
if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };
|
|
14509
14832
|
let entries;
|
|
14510
14833
|
try {
|
|
14511
|
-
entries = await
|
|
14834
|
+
entries = await readdir6(directory, { withFileTypes: true });
|
|
14512
14835
|
} catch {
|
|
14513
14836
|
continue;
|
|
14514
14837
|
}
|
|
@@ -17217,7 +17540,7 @@ async function detectFixer(cwd) {
|
|
|
17217
17540
|
init_util();
|
|
17218
17541
|
import { spawn as spawn10 } from "node:child_process";
|
|
17219
17542
|
import { statSync as statSync4 } from "node:fs";
|
|
17220
|
-
import { dirname as
|
|
17543
|
+
import { dirname as dirname14, resolve as resolve13, sep as sep6 } from "node:path";
|
|
17221
17544
|
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
17222
17545
|
import { buildChildEnv as buildChildEnv4 } from "@wrongstack/core/utils";
|
|
17223
17546
|
var TIMEOUT_MS2 = 3e4;
|
|
@@ -17379,7 +17702,7 @@ function findGitDir2(cwd, projectRoot) {
|
|
|
17379
17702
|
} catch {
|
|
17380
17703
|
}
|
|
17381
17704
|
if (dir === root) break;
|
|
17382
|
-
const parent =
|
|
17705
|
+
const parent = dirname14(dir);
|
|
17383
17706
|
if (parent === dir) break;
|
|
17384
17707
|
dir = parent;
|
|
17385
17708
|
}
|
|
@@ -21477,12 +21800,13 @@ init_util();
|
|
|
21477
21800
|
import * as fs28 from "node:fs/promises";
|
|
21478
21801
|
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
21479
21802
|
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
21803
|
+
var ADVANCED_MODE_META_KEY = "tools.read.advancedMode";
|
|
21480
21804
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
21481
21805
|
var readTool = {
|
|
21482
21806
|
name: "read",
|
|
21483
21807
|
category: "Filesystem",
|
|
21484
|
-
description: "Read the contents of a file with line numbers. This is the primary way to inspect source code, configuration, or any text file before making changes. Lines are returned 1-indexed with a ` N| ` prefix for easy reference in edits.",
|
|
21485
|
-
usageHint: "FOUNDATIONAL TOOL \u2014 call this before almost any edit operation.\n\nBest practices:\n- Always read a file before using `edit`, `replace`, or `write` on it (the system often requires it for safety).\n- Use `offset` + `limit` for very large files instead of reading everything at once.\n- Default limit is generous (2000 lines) but can be increased.\n- The output format is designed to be directly usable as context for `edit` operations.",
|
|
21808
|
+
description: "Read the contents of a file with line numbers. This is the primary way to inspect source code, configuration, or any text file before making changes. Lines are returned 1-indexed with a ` N| ` prefix for easy reference in edits. When advanced mode is on or `includeSymbols` is set, the result also includes a `symbols` field listing codebase-index symbol names, kinds, and line numbers for the file (not file content).",
|
|
21809
|
+
usageHint: "FOUNDATIONAL TOOL \u2014 call this before almost any edit operation.\n\nBest practices:\n- Always read a file before using `edit`, `replace`, or `write` on it (the system often requires it for safety).\n- Use `offset` + `limit` for very large files instead of reading everything at once.\n- Default limit is generous (2000 lines) but can be increased.\n- The output format is designed to be directly usable as context for `edit` operations.\n- Set `includeSymbols: true` to also receive the codebase-index symbol listing for the file.\n- Enable advanced mode (`ctx.meta['tools.read.advancedMode'] = true`) to auto-inject symbols on every read.",
|
|
21486
21810
|
selection: {
|
|
21487
21811
|
doNotUseWhen: "you need to search many files for matching content.",
|
|
21488
21812
|
useInstead: ["grep"]
|
|
@@ -21512,11 +21836,15 @@ var readTool = {
|
|
|
21512
21836
|
type: "string",
|
|
21513
21837
|
enum: ["content", "summary"],
|
|
21514
21838
|
description: "Return full line-numbered content (default) or a compact file summary with imports/exports/symbols."
|
|
21839
|
+
},
|
|
21840
|
+
includeSymbols: {
|
|
21841
|
+
type: "boolean",
|
|
21842
|
+
description: "When true, include the codebase-index symbol list for this file as a structured `symbols` field in the result. Overrides the advanced-mode meta flag per-call."
|
|
21515
21843
|
}
|
|
21516
21844
|
},
|
|
21517
21845
|
required: ["path"]
|
|
21518
21846
|
},
|
|
21519
|
-
async execute(input, ctx) {
|
|
21847
|
+
async execute(input, ctx, execOpts) {
|
|
21520
21848
|
if (!input?.path) {
|
|
21521
21849
|
throw new ToolValidationError5({
|
|
21522
21850
|
message: "read: path is required",
|
|
@@ -21524,6 +21852,7 @@ var readTool = {
|
|
|
21524
21852
|
});
|
|
21525
21853
|
}
|
|
21526
21854
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
21855
|
+
const shouldIncludeSymbols = input.includeSymbols === true || input.includeSymbols !== false && ctx.meta[ADVANCED_MODE_META_KEY] === true;
|
|
21527
21856
|
let stat17;
|
|
21528
21857
|
try {
|
|
21529
21858
|
stat17 = await fs28.stat(absPath);
|
|
@@ -21567,13 +21896,15 @@ var readTool = {
|
|
|
21567
21896
|
const requestedEnd = prior ? Math.min(offset + limit - 1, prior.totalLines) : offset + limit - 1;
|
|
21568
21897
|
if (input.mode !== "summary" && limit > 0 && prior && coversRange(prior, stat17.mtimeMs, offset, requestedEnd)) {
|
|
21569
21898
|
ctx.recordRead(absPath, stat17.mtimeMs);
|
|
21899
|
+
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21570
21900
|
return {
|
|
21571
21901
|
text: `[unchanged since previous read: "${input.path}" mtime=${Math.round(stat17.mtimeMs)}; requested lines ${offset}-${requestedEnd} were already shown. Use offset/limit for a new range if needed.]`,
|
|
21572
21902
|
total_lines: prior.totalLines,
|
|
21573
21903
|
encoding: "utf8",
|
|
21574
21904
|
truncated: requestedEnd < prior.totalLines,
|
|
21575
21905
|
cached: true,
|
|
21576
|
-
note: "Repeated read suppressed to save tokens."
|
|
21906
|
+
note: mergeSymbolNote("Repeated read suppressed to save tokens.", symResult2?.note),
|
|
21907
|
+
...symResult2?.symbols ? { symbols: symResult2.symbols } : {}
|
|
21577
21908
|
};
|
|
21578
21909
|
}
|
|
21579
21910
|
const buf = await fs28.readFile(absPath);
|
|
@@ -21587,27 +21918,43 @@ var readTool = {
|
|
|
21587
21918
|
if (input.mode === "summary") {
|
|
21588
21919
|
ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
|
|
21589
21920
|
rememberReadRange(ctx, absPath, stat17.mtimeMs, total, 1, Math.min(total, 200));
|
|
21921
|
+
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21590
21922
|
return {
|
|
21591
21923
|
text: summarizeFile(input.path, stat17.size, allLines),
|
|
21592
21924
|
total_lines: total,
|
|
21593
21925
|
encoding: "utf8",
|
|
21594
21926
|
truncated: total > 200,
|
|
21595
|
-
note:
|
|
21927
|
+
note: mergeSymbolNote(
|
|
21928
|
+
"Summary mode returned compact structure instead of full file content.",
|
|
21929
|
+
symResult2?.note
|
|
21930
|
+
),
|
|
21931
|
+
...symResult2?.symbols ? { symbols: symResult2.symbols } : {}
|
|
21596
21932
|
};
|
|
21597
21933
|
}
|
|
21598
21934
|
if (limit === 0) {
|
|
21599
21935
|
ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
|
|
21600
21936
|
rememberReadRange(ctx, absPath, stat17.mtimeMs, total, 1, 0);
|
|
21601
|
-
|
|
21937
|
+
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21938
|
+
return {
|
|
21939
|
+
text: "",
|
|
21940
|
+
total_lines: total,
|
|
21941
|
+
encoding: "utf8",
|
|
21942
|
+
truncated: total > 0,
|
|
21943
|
+
...symResult2?.symbols ? { symbols: symResult2.symbols } : {},
|
|
21944
|
+
...symResult2?.note ? { note: symResult2.note } : {}
|
|
21945
|
+
};
|
|
21602
21946
|
}
|
|
21603
21947
|
if (offset > total) {
|
|
21604
21948
|
ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
|
|
21605
21949
|
rememberReadRange(ctx, absPath, stat17.mtimeMs, total, total + 1, total + 1);
|
|
21950
|
+
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21606
21951
|
return {
|
|
21607
21952
|
text: `[offset ${offset} is past end of file "${input.path}" \u2014 file has ${total} line(s). Do not retry this offset.]`,
|
|
21608
21953
|
total_lines: total,
|
|
21609
21954
|
encoding: "utf8",
|
|
21610
|
-
truncated: false
|
|
21955
|
+
truncated: false,
|
|
21956
|
+
...symResult2?.symbols ? { symbols: symResult2.symbols } : {},
|
|
21957
|
+
...symResult2?.note ? { note: symResult2.note } : {}
|
|
21611
21958
|
};
|
|
21612
21959
|
}
|
|
21613
21960
|
const slice = allLines.slice(offset - 1, offset - 1 + limit);
|
|
@@ -21616,14 +21963,53 @@ var readTool = {
|
|
|
21616
21963
|
const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
|
|
21617
21964
|
ctx.recordRead(absPath, stat17.mtimeMs, "user", contentHash);
|
|
21618
21965
|
rememberReadRange(ctx, absPath, stat17.mtimeMs, total, offset, offset + slice.length - 1);
|
|
21966
|
+
const symResult = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21619
21967
|
return {
|
|
21620
21968
|
text: numbered,
|
|
21621
21969
|
total_lines: total,
|
|
21622
21970
|
encoding: "utf8",
|
|
21623
|
-
truncated
|
|
21971
|
+
truncated,
|
|
21972
|
+
...symResult?.symbols ? { symbols: symResult.symbols } : {},
|
|
21973
|
+
...symResult?.note ? { note: symResult.note } : {}
|
|
21624
21974
|
};
|
|
21625
21975
|
}
|
|
21626
21976
|
};
|
|
21977
|
+
async function fetchSymbolsForFile(absPath, ctx, signal) {
|
|
21978
|
+
try {
|
|
21979
|
+
const state = getIndexState();
|
|
21980
|
+
if (!state.ready) return {};
|
|
21981
|
+
const { results, total } = await searchCodebaseIndex(
|
|
21982
|
+
{
|
|
21983
|
+
projectRoot: ctx.projectRoot,
|
|
21984
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
21985
|
+
query: "",
|
|
21986
|
+
file: absPath,
|
|
21987
|
+
limit: 500
|
|
21988
|
+
},
|
|
21989
|
+
{ signal }
|
|
21990
|
+
);
|
|
21991
|
+
if (results.length === 0) return {};
|
|
21992
|
+
const sorted = results.map((r) => ({
|
|
21993
|
+
name: r.name,
|
|
21994
|
+
kind: r.kind,
|
|
21995
|
+
line: r.line,
|
|
21996
|
+
col: r.col,
|
|
21997
|
+
signature: r.signature
|
|
21998
|
+
})).sort((a, b) => a.line - b.line || a.col - b.col);
|
|
21999
|
+
const result = { symbols: sorted };
|
|
22000
|
+
if (total > results.length) {
|
|
22001
|
+
result.note = `Symbol listing truncated to ${results.length} of ${total} entries.`;
|
|
22002
|
+
}
|
|
22003
|
+
return result;
|
|
22004
|
+
} catch {
|
|
22005
|
+
return {};
|
|
22006
|
+
}
|
|
22007
|
+
}
|
|
22008
|
+
function mergeSymbolNote(note, symNote) {
|
|
22009
|
+
if (!symNote) return note;
|
|
22010
|
+
if (!note) return symNote;
|
|
22011
|
+
return `${note} ${symNote}`;
|
|
22012
|
+
}
|
|
21627
22013
|
var READ_RANGES_META_KEY = "tools.read.ranges.v1";
|
|
21628
22014
|
function getReadRanges(ctx) {
|
|
21629
22015
|
const existing = ctx.meta[READ_RANGES_META_KEY];
|