@wrongstack/tools 0.299.0 → 0.301.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.js +6 -2
- package/dist/bash.js +6 -2
- package/dist/batch-tool-use.js +3 -1
- package/dist/browser/index.js +1 -1
- package/dist/builtin.d.ts +3 -2
- package/dist/builtin.js +1781 -376
- package/dist/codebase-index/bm25.d.ts +7 -1
- package/dist/codebase-index/import-extractor.d.ts +39 -0
- package/dist/codebase-index/index.js +1418 -267
- package/dist/codebase-index/languages.d.ts +24 -0
- package/dist/codebase-index/module-resolver.d.ts +78 -0
- package/dist/codebase-index/module-roots.d.ts +81 -0
- package/dist/codebase-index/parser-output.d.ts +29 -0
- package/dist/codebase-index/project-server.js +1402 -249
- package/dist/codebase-index/rs-parser.d.ts +22 -0
- package/dist/codebase-index/schema.d.ts +24 -1
- package/dist/codebase-index/worker.js +1401 -250
- package/dist/codebase-index/writer-graph-helpers.d.ts +17 -5
- package/dist/codebase-index/writer-ref-mapper.d.ts +3 -0
- package/dist/codebase-index/writer-schema.d.ts +15 -3
- package/dist/codebase-index/writer.d.ts +76 -3
- package/dist/exec.js +35 -2
- package/dist/format.js +6 -2
- package/dist/git.js +2 -5
- package/dist/glob.js +2 -2
- package/dist/grep.js +118 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1925 -415
- package/dist/install.js +6 -2
- package/dist/json.js +132 -2
- package/dist/languages/index.js +6 -2
- package/dist/lint.js +6 -2
- package/dist/logs.js +81 -0
- package/dist/next-steps-tool.d.ts +26 -0
- package/dist/outdated.js +6 -2
- package/dist/pack.js +1781 -376
- package/dist/patch.js +206 -45
- package/dist/process-registry.d.ts +6 -0
- package/dist/process-registry.js +6 -2
- package/dist/ps-slash.js +6 -2
- package/dist/read.js +1410 -257
- package/dist/replace.js +81 -0
- package/dist/skill.js +51 -2
- package/dist/test.js +6 -2
- package/dist/tool-help.js +2 -2
- package/dist/tool-search.js +1 -1
- package/dist/tool-tier.d.ts +1 -1
- package/dist/tool-tier.js +1786 -397
- package/dist/tool-use.js +1 -1
- package/dist/tree.js +13 -3
- package/dist/typecheck.js +6 -2
- package/package.json +3 -3
- package/dist/codebase-index/refs-extractor.d.ts +0 -11
package/dist/tool-tier.js
CHANGED
|
@@ -721,7 +721,7 @@ var init_process_registry = __esm({
|
|
|
721
721
|
const p = this.processes.get(pid);
|
|
722
722
|
if (!p) return false;
|
|
723
723
|
if (p.killed) return true;
|
|
724
|
-
if (p.protected) return false;
|
|
724
|
+
if (p.protected && opts.includeProtected !== true) return false;
|
|
725
725
|
if (opts.preserveBackground && p.background) return false;
|
|
726
726
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
727
727
|
const isWin5 = os.platform() === "win32";
|
|
@@ -772,9 +772,13 @@ var init_process_registry = __esm({
|
|
|
772
772
|
killAll(opts = {}) {
|
|
773
773
|
const pids = Array.from(this.processes.keys());
|
|
774
774
|
const killed = [];
|
|
775
|
+
const includeProtected = opts.includeProtected === true;
|
|
775
776
|
for (const pid of pids) {
|
|
776
777
|
const p = this.processes.get(pid);
|
|
777
|
-
if (
|
|
778
|
+
if (!p) continue;
|
|
779
|
+
if (p.protected && !includeProtected) continue;
|
|
780
|
+
if (opts.preserveBackground && p.background) continue;
|
|
781
|
+
if (this.kill(pid, opts)) killed.push(pid);
|
|
778
782
|
}
|
|
779
783
|
return killed;
|
|
780
784
|
}
|
|
@@ -1138,18 +1142,18 @@ async function resolveRealInsideRoot(absPath, ctx) {
|
|
|
1138
1142
|
const realRoots = await Promise.all(
|
|
1139
1143
|
allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
|
|
1140
1144
|
);
|
|
1141
|
-
let
|
|
1145
|
+
let probe = absPath;
|
|
1142
1146
|
const pendingTail = [];
|
|
1143
1147
|
for (; ; ) {
|
|
1144
1148
|
let real;
|
|
1145
1149
|
try {
|
|
1146
|
-
real = await fsp2.realpath(
|
|
1150
|
+
real = await fsp2.realpath(probe);
|
|
1147
1151
|
} catch (err) {
|
|
1148
1152
|
if (err.code === "ENOENT") {
|
|
1149
|
-
const parent = path3.dirname(
|
|
1150
|
-
if (parent ===
|
|
1151
|
-
pendingTail.unshift(path3.basename(
|
|
1152
|
-
|
|
1153
|
+
const parent = path3.dirname(probe);
|
|
1154
|
+
if (parent === probe) return absPath;
|
|
1155
|
+
pendingTail.unshift(path3.basename(probe));
|
|
1156
|
+
probe = parent;
|
|
1153
1157
|
continue;
|
|
1154
1158
|
}
|
|
1155
1159
|
throw err;
|
|
@@ -4968,20 +4972,23 @@ var init_legacy_bridge = __esm({
|
|
|
4968
4972
|
});
|
|
4969
4973
|
|
|
4970
4974
|
// src/codebase-index/languages.ts
|
|
4971
|
-
import * as
|
|
4975
|
+
import * as path13 from "node:path";
|
|
4972
4976
|
function detectLang(file) {
|
|
4973
|
-
const base =
|
|
4977
|
+
const base = path13.basename(file);
|
|
4974
4978
|
const lowerBase = base.toLowerCase();
|
|
4975
4979
|
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
4976
4980
|
return "ts";
|
|
4977
4981
|
}
|
|
4978
4982
|
const special = SPECIAL_FILENAMES[lowerBase];
|
|
4979
4983
|
if (special) return special;
|
|
4980
|
-
const ext =
|
|
4984
|
+
const ext = path13.extname(base).toLowerCase();
|
|
4981
4985
|
if (!ext) return null;
|
|
4982
4986
|
return EXT_TO_LANG[ext] ?? null;
|
|
4983
4987
|
}
|
|
4984
|
-
|
|
4988
|
+
function languageFamily(lang) {
|
|
4989
|
+
return LANG_FAMILY[lang] ?? "other";
|
|
4990
|
+
}
|
|
4991
|
+
var EXT_TO_LANG, INDEXABLE_EXTENSIONS, SPECIAL_FILENAMES, LANG_FAMILY, LANG_FAMILY_ENTRIES;
|
|
4985
4992
|
var init_languages2 = __esm({
|
|
4986
4993
|
"src/codebase-index/languages.ts"() {
|
|
4987
4994
|
"use strict";
|
|
@@ -5072,6 +5079,52 @@ var init_languages2 = __esm({
|
|
|
5072
5079
|
procfile: "other",
|
|
5073
5080
|
justfile: "other"
|
|
5074
5081
|
};
|
|
5082
|
+
LANG_FAMILY = {
|
|
5083
|
+
// Single-compilation-unit family: a .vue/.svelte script block is JS/TS and
|
|
5084
|
+
// imports from — and is imported by — plain .ts files.
|
|
5085
|
+
ts: "js",
|
|
5086
|
+
tsx: "js",
|
|
5087
|
+
js: "js",
|
|
5088
|
+
jsx: "js",
|
|
5089
|
+
vue: "js",
|
|
5090
|
+
svelte: "js",
|
|
5091
|
+
go: "go",
|
|
5092
|
+
py: "py",
|
|
5093
|
+
rs: "rs",
|
|
5094
|
+
// The JVM resolves across languages: Kotlin and Scala call Java directly.
|
|
5095
|
+
java: "jvm",
|
|
5096
|
+
kotlin: "jvm",
|
|
5097
|
+
scala: "jvm",
|
|
5098
|
+
csharp: "dotnet",
|
|
5099
|
+
// A .h header is consumed by both C and C++ translation units.
|
|
5100
|
+
c: "c",
|
|
5101
|
+
cpp: "c",
|
|
5102
|
+
ruby: "ruby",
|
|
5103
|
+
php: "php",
|
|
5104
|
+
swift: "swift",
|
|
5105
|
+
dart: "dart",
|
|
5106
|
+
elixir: "elixir",
|
|
5107
|
+
haskell: "haskell",
|
|
5108
|
+
zig: "zig",
|
|
5109
|
+
lua: "lua",
|
|
5110
|
+
r: "r",
|
|
5111
|
+
shell: "shell",
|
|
5112
|
+
sql: "sql",
|
|
5113
|
+
json: "data",
|
|
5114
|
+
yaml: "data",
|
|
5115
|
+
toml: "data",
|
|
5116
|
+
html: "web",
|
|
5117
|
+
css: "web",
|
|
5118
|
+
proto: "proto",
|
|
5119
|
+
graphql: "graphql",
|
|
5120
|
+
md: "other",
|
|
5121
|
+
other: "other"
|
|
5122
|
+
};
|
|
5123
|
+
LANG_FAMILY_ENTRIES = Object.freeze(
|
|
5124
|
+
Object.entries(LANG_FAMILY).map(
|
|
5125
|
+
([lang, family]) => Object.freeze([lang, family])
|
|
5126
|
+
)
|
|
5127
|
+
);
|
|
5075
5128
|
}
|
|
5076
5129
|
});
|
|
5077
5130
|
|
|
@@ -5236,7 +5289,7 @@ function getTypeName(name) {
|
|
|
5236
5289
|
function deduplicateRefs(refs) {
|
|
5237
5290
|
const seen = /* @__PURE__ */ new Set();
|
|
5238
5291
|
return refs.filter((r) => {
|
|
5239
|
-
const key = `${r.toName}:${r.callType}:${r.line}`;
|
|
5292
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
5240
5293
|
if (seen.has(key)) return false;
|
|
5241
5294
|
seen.add(key);
|
|
5242
5295
|
return true;
|
|
@@ -5246,10 +5299,16 @@ function getImportSpecifierName(spec) {
|
|
|
5246
5299
|
return spec.propertyName?.text ?? spec.name.text;
|
|
5247
5300
|
}
|
|
5248
5301
|
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
5302
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
5249
5303
|
const clause = node.importClause;
|
|
5250
|
-
if (!clause)
|
|
5304
|
+
if (!clause) {
|
|
5305
|
+
if (module) {
|
|
5306
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
5307
|
+
}
|
|
5308
|
+
return;
|
|
5309
|
+
}
|
|
5251
5310
|
if (clause.name) {
|
|
5252
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5311
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
5253
5312
|
}
|
|
5254
5313
|
const bindings = clause.namedBindings;
|
|
5255
5314
|
if (!bindings) return;
|
|
@@ -5259,26 +5318,40 @@ function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
|
5259
5318
|
fromId: 0,
|
|
5260
5319
|
toName: getImportSpecifierName(element),
|
|
5261
5320
|
callType: "import",
|
|
5262
|
-
line: lineNum
|
|
5321
|
+
line: lineNum,
|
|
5322
|
+
module
|
|
5263
5323
|
});
|
|
5264
5324
|
}
|
|
5265
5325
|
} else if (ts.isNamespaceImport(bindings)) {
|
|
5266
|
-
refs.push({
|
|
5326
|
+
refs.push({
|
|
5327
|
+
fromId: 0,
|
|
5328
|
+
toName: bindings.name.text,
|
|
5329
|
+
callType: "import",
|
|
5330
|
+
line: lineNum,
|
|
5331
|
+
module
|
|
5332
|
+
});
|
|
5267
5333
|
}
|
|
5268
5334
|
}
|
|
5335
|
+
function moduleSpecifierOf(node) {
|
|
5336
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
5337
|
+
}
|
|
5269
5338
|
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
5339
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
5270
5340
|
const clause = node.exportClause;
|
|
5271
5341
|
if (clause && ts.isNamespaceExport(clause)) {
|
|
5272
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5342
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
5273
5343
|
return;
|
|
5274
5344
|
}
|
|
5275
5345
|
if (clause && ts.isNamedExports(clause)) {
|
|
5276
5346
|
for (const element of clause.elements) {
|
|
5277
5347
|
const originalName = element.propertyName?.text ?? element.name.text;
|
|
5278
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
|
|
5348
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
5279
5349
|
}
|
|
5280
5350
|
return;
|
|
5281
5351
|
}
|
|
5352
|
+
if (module) {
|
|
5353
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
5354
|
+
}
|
|
5282
5355
|
}
|
|
5283
5356
|
var ts, tsLoad, kindMapCache;
|
|
5284
5357
|
var init_ts_parser = __esm({
|
|
@@ -5290,6 +5363,82 @@ var init_ts_parser = __esm({
|
|
|
5290
5363
|
}
|
|
5291
5364
|
});
|
|
5292
5365
|
|
|
5366
|
+
// src/codebase-index/parser-output.ts
|
|
5367
|
+
function coerceSymbols(value) {
|
|
5368
|
+
if (!Array.isArray(value)) return [];
|
|
5369
|
+
return value.flatMap((entry) => {
|
|
5370
|
+
const candidate = entry;
|
|
5371
|
+
if (typeof candidate.name !== "string" || typeof candidate.kind !== "string") return [];
|
|
5372
|
+
return [
|
|
5373
|
+
{
|
|
5374
|
+
name: candidate.name,
|
|
5375
|
+
kind: candidate.kind,
|
|
5376
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
5377
|
+
col: typeof candidate.col === "number" ? candidate.col : 0,
|
|
5378
|
+
signature: typeof candidate.signature === "string" ? candidate.signature : "",
|
|
5379
|
+
scope: typeof candidate.scope === "string" ? candidate.scope : ""
|
|
5380
|
+
}
|
|
5381
|
+
];
|
|
5382
|
+
});
|
|
5383
|
+
}
|
|
5384
|
+
function coerceRefs(value, lang) {
|
|
5385
|
+
if (!Array.isArray(value)) return [];
|
|
5386
|
+
return value.flatMap((entry) => {
|
|
5387
|
+
const candidate = entry;
|
|
5388
|
+
if (typeof candidate.toName !== "string" || !candidate.toName) return [];
|
|
5389
|
+
if (typeof candidate.callType !== "string" || !CALL_TYPES.has(candidate.callType)) return [];
|
|
5390
|
+
const module = typeof candidate.module === "string" && candidate.module ? candidate.module : void 0;
|
|
5391
|
+
return [
|
|
5392
|
+
{
|
|
5393
|
+
fromId: 0,
|
|
5394
|
+
toName: candidate.toName,
|
|
5395
|
+
callType: candidate.callType,
|
|
5396
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
5397
|
+
lang,
|
|
5398
|
+
module
|
|
5399
|
+
}
|
|
5400
|
+
];
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
function parseParserOutput(stdout, lang) {
|
|
5404
|
+
const trimmed = stdout.trim();
|
|
5405
|
+
if (!trimmed) return { symbols: [], refs: [] };
|
|
5406
|
+
let parsed;
|
|
5407
|
+
try {
|
|
5408
|
+
parsed = JSON.parse(trimmed);
|
|
5409
|
+
} catch {
|
|
5410
|
+
return { symbols: [], refs: [] };
|
|
5411
|
+
}
|
|
5412
|
+
if (Array.isArray(parsed)) return { symbols: coerceSymbols(parsed), refs: [] };
|
|
5413
|
+
const record = parsed;
|
|
5414
|
+
return {
|
|
5415
|
+
symbols: coerceSymbols(record.symbols),
|
|
5416
|
+
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
function dedupeRefs(refs) {
|
|
5420
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5421
|
+
return refs.filter((ref) => {
|
|
5422
|
+
const key = `${ref.toName}:${ref.callType}:${ref.line}:${ref.module ?? ""}`;
|
|
5423
|
+
if (seen.has(key)) return false;
|
|
5424
|
+
seen.add(key);
|
|
5425
|
+
return true;
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
var CALL_TYPES;
|
|
5429
|
+
var init_parser_output = __esm({
|
|
5430
|
+
"src/codebase-index/parser-output.ts"() {
|
|
5431
|
+
"use strict";
|
|
5432
|
+
CALL_TYPES = /* @__PURE__ */ new Set([
|
|
5433
|
+
"call",
|
|
5434
|
+
"type_ref",
|
|
5435
|
+
"inherit",
|
|
5436
|
+
"implement",
|
|
5437
|
+
"import"
|
|
5438
|
+
]);
|
|
5439
|
+
}
|
|
5440
|
+
});
|
|
5441
|
+
|
|
5293
5442
|
// src/codebase-index/spawn-gate.ts
|
|
5294
5443
|
function withSpawnGate(fn) {
|
|
5295
5444
|
const run = chain.then(fn, fn);
|
|
@@ -5315,8 +5464,8 @@ __export(go_parser_exports, {
|
|
|
5315
5464
|
});
|
|
5316
5465
|
import { spawn as spawn5 } from "node:child_process";
|
|
5317
5466
|
import * as os6 from "node:os";
|
|
5318
|
-
import * as
|
|
5319
|
-
import * as
|
|
5467
|
+
import * as path20 from "node:path";
|
|
5468
|
+
import * as fs15 from "node:fs/promises";
|
|
5320
5469
|
async function parseSymbols2(opts) {
|
|
5321
5470
|
const { file, content, lang } = opts;
|
|
5322
5471
|
try {
|
|
@@ -5324,7 +5473,8 @@ async function parseSymbols2(opts) {
|
|
|
5324
5473
|
if (parsed.symbols.length > 0) {
|
|
5325
5474
|
return parsed;
|
|
5326
5475
|
}
|
|
5327
|
-
|
|
5476
|
+
const fallback = fallbackParse(file, content, lang);
|
|
5477
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
5328
5478
|
} catch {
|
|
5329
5479
|
return fallbackParse(file, content, lang);
|
|
5330
5480
|
}
|
|
@@ -5388,9 +5538,9 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5388
5538
|
try {
|
|
5389
5539
|
let scriptPath = _cachedGoScriptPath;
|
|
5390
5540
|
if (!scriptPath) {
|
|
5391
|
-
const tmpDir = await
|
|
5392
|
-
scriptPath =
|
|
5393
|
-
await
|
|
5541
|
+
const tmpDir = await fs15.mkdtemp(path20.join(os6.tmpdir(), "ws-go-parse-"));
|
|
5542
|
+
scriptPath = path20.join(tmpDir, "parse.go");
|
|
5543
|
+
await fs15.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
5394
5544
|
_cachedGoScriptPath = scriptPath;
|
|
5395
5545
|
}
|
|
5396
5546
|
const goBinary = resolveWin32Command("go");
|
|
@@ -5432,8 +5582,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5432
5582
|
if (code !== 0 || !stdout.trim()) {
|
|
5433
5583
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
5434
5584
|
}
|
|
5435
|
-
const
|
|
5436
|
-
const symbols =
|
|
5585
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
5586
|
+
const symbols = rawSymbols.map((s) => ({
|
|
5437
5587
|
id: 0,
|
|
5438
5588
|
lang,
|
|
5439
5589
|
kind: s.kind,
|
|
@@ -5446,7 +5596,7 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5446
5596
|
scope: s.scope ?? "",
|
|
5447
5597
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
5448
5598
|
}));
|
|
5449
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
5599
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
5450
5600
|
} catch {
|
|
5451
5601
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
5452
5602
|
}
|
|
@@ -5456,6 +5606,7 @@ var init_go_parser = __esm({
|
|
|
5456
5606
|
"src/codebase-index/go-parser.ts"() {
|
|
5457
5607
|
"use strict";
|
|
5458
5608
|
init_win32_resolve();
|
|
5609
|
+
init_parser_output();
|
|
5459
5610
|
init_spawn_gate();
|
|
5460
5611
|
init_languages2();
|
|
5461
5612
|
GO_PARSE_SCRIPT = `
|
|
@@ -5469,6 +5620,7 @@ import (
|
|
|
5469
5620
|
"go/token"
|
|
5470
5621
|
"io"
|
|
5471
5622
|
"os"
|
|
5623
|
+
"strconv"
|
|
5472
5624
|
"strings"
|
|
5473
5625
|
)
|
|
5474
5626
|
|
|
@@ -5481,16 +5633,34 @@ type Sym struct {
|
|
|
5481
5633
|
Scope string \`json:"scope"\`
|
|
5482
5634
|
}
|
|
5483
5635
|
|
|
5636
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
5637
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
5638
|
+
type Ref struct {
|
|
5639
|
+
ToName string \`json:"toName"\`
|
|
5640
|
+
CallType string \`json:"callType"\`
|
|
5641
|
+
Line int \`json:"line"\`
|
|
5642
|
+
Module string \`json:"module"\`
|
|
5643
|
+
}
|
|
5644
|
+
|
|
5645
|
+
type Result struct {
|
|
5646
|
+
Symbols []Sym \`json:"symbols"\`
|
|
5647
|
+
Refs []Ref \`json:"refs"\`
|
|
5648
|
+
}
|
|
5649
|
+
|
|
5650
|
+
func emptyResult() string {
|
|
5651
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
5652
|
+
}
|
|
5653
|
+
|
|
5484
5654
|
func main() {
|
|
5485
5655
|
src, err := io.ReadAll(os.Stdin)
|
|
5486
5656
|
if err != nil {
|
|
5487
|
-
fmt.Print(
|
|
5657
|
+
fmt.Print(emptyResult())
|
|
5488
5658
|
return
|
|
5489
5659
|
}
|
|
5490
5660
|
fset := token.NewFileSet()
|
|
5491
5661
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
5492
5662
|
if err != nil {
|
|
5493
|
-
fmt.Print(
|
|
5663
|
+
fmt.Print(emptyResult())
|
|
5494
5664
|
return
|
|
5495
5665
|
}
|
|
5496
5666
|
|
|
@@ -5554,9 +5724,43 @@ func main() {
|
|
|
5554
5724
|
}
|
|
5555
5725
|
}
|
|
5556
5726
|
|
|
5557
|
-
|
|
5727
|
+
refs := []Ref{}
|
|
5728
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
5729
|
+
switch expr := n.(type) {
|
|
5730
|
+
case *ast.CallExpr:
|
|
5731
|
+
line := fset.Position(expr.Pos()).Line
|
|
5732
|
+
switch fun := expr.Fun.(type) {
|
|
5733
|
+
case *ast.Ident:
|
|
5734
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
5735
|
+
case *ast.SelectorExpr:
|
|
5736
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
5737
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
5738
|
+
// and Python extractors' call refs do.
|
|
5739
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
5740
|
+
}
|
|
5741
|
+
case *ast.ImportSpec:
|
|
5742
|
+
if expr.Path != nil {
|
|
5743
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
5744
|
+
line := fset.Position(expr.Pos()).Line
|
|
5745
|
+
// A Go import names a package, not a symbol; the package's
|
|
5746
|
+
// last path segment is the name it is referenced by.
|
|
5747
|
+
name := importPath
|
|
5748
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
5749
|
+
name = importPath[idx+1:]
|
|
5750
|
+
}
|
|
5751
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
return true
|
|
5756
|
+
})
|
|
5757
|
+
|
|
5758
|
+
if syms == nil {
|
|
5759
|
+
syms = []Sym{}
|
|
5760
|
+
}
|
|
5761
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
5558
5762
|
if err != nil {
|
|
5559
|
-
fmt.Print(
|
|
5763
|
+
fmt.Print(emptyResult())
|
|
5560
5764
|
return
|
|
5561
5765
|
}
|
|
5562
5766
|
fmt.Print(string(data))
|
|
@@ -5717,16 +5921,23 @@ function looksBinary(content) {
|
|
|
5717
5921
|
}
|
|
5718
5922
|
return bad / sample.length > 0.1;
|
|
5719
5923
|
}
|
|
5720
|
-
function
|
|
5721
|
-
|
|
5722
|
-
let
|
|
5723
|
-
|
|
5724
|
-
if (content.charCodeAt(i) === 10) {
|
|
5725
|
-
line++;
|
|
5726
|
-
lastNl = i;
|
|
5727
|
-
}
|
|
5924
|
+
function newlineOffsets2(content) {
|
|
5925
|
+
const offsets = [];
|
|
5926
|
+
for (let i = 0; i < content.length; i++) {
|
|
5927
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
5728
5928
|
}
|
|
5729
|
-
return
|
|
5929
|
+
return offsets;
|
|
5930
|
+
}
|
|
5931
|
+
function lineColAt(offsets, index) {
|
|
5932
|
+
let low = 0;
|
|
5933
|
+
let high = offsets.length;
|
|
5934
|
+
while (low < high) {
|
|
5935
|
+
const mid = low + high >>> 1;
|
|
5936
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
5937
|
+
else high = mid;
|
|
5938
|
+
}
|
|
5939
|
+
const lastNl = low > 0 ? offsets[low - 1] : -1;
|
|
5940
|
+
return { line: low + 1, col: index - lastNl };
|
|
5730
5941
|
}
|
|
5731
5942
|
function parseGeneric2(opts) {
|
|
5732
5943
|
const { file, lang } = opts;
|
|
@@ -5739,6 +5950,7 @@ function parseGeneric2(opts) {
|
|
|
5739
5950
|
const patterns = patternsFor(lang);
|
|
5740
5951
|
const symbols = [];
|
|
5741
5952
|
const seen = /* @__PURE__ */ new Set();
|
|
5953
|
+
const nlOffsets = newlineOffsets2(content);
|
|
5742
5954
|
for (const pattern of patterns) {
|
|
5743
5955
|
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
5744
5956
|
re.lastIndex = 0;
|
|
@@ -5752,7 +5964,7 @@ function parseGeneric2(opts) {
|
|
|
5752
5964
|
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
5753
5965
|
continue;
|
|
5754
5966
|
}
|
|
5755
|
-
const { line, col } = lineColAt(
|
|
5967
|
+
const { line, col } = lineColAt(nlOffsets, match.index ?? 0);
|
|
5756
5968
|
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
5757
5969
|
if (seen.has(key)) continue;
|
|
5758
5970
|
seen.add(key);
|
|
@@ -5908,9 +6120,13 @@ var init_generic_parser = __esm({
|
|
|
5908
6120
|
],
|
|
5909
6121
|
elixir: [
|
|
5910
6122
|
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
5911
|
-
|
|
6123
|
+
// Dotted module names must be captured whole: `alias Foo.Bar` resolves
|
|
6124
|
+
// against this symbol, and a `Foo`-only capture never matches it.
|
|
6125
|
+
{ re: /\bdefmodule\s+([A-Z][\w.]*)/g, kind: "namespace" }
|
|
5912
6126
|
],
|
|
5913
6127
|
haskell: [
|
|
6128
|
+
// Target of `import Data.List`.
|
|
6129
|
+
{ re: /^module\s+([A-Z][\w.]*)/gm, kind: "namespace" },
|
|
5914
6130
|
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
5915
6131
|
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
5916
6132
|
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
@@ -6001,9 +6217,9 @@ __export(py_parser_exports, {
|
|
|
6001
6217
|
parseSymbols: () => parseSymbols4
|
|
6002
6218
|
});
|
|
6003
6219
|
import { spawn as spawn6 } from "node:child_process";
|
|
6004
|
-
import * as
|
|
6220
|
+
import * as fs16 from "node:fs/promises";
|
|
6005
6221
|
import * as os7 from "node:os";
|
|
6006
|
-
import * as
|
|
6222
|
+
import * as path21 from "node:path";
|
|
6007
6223
|
async function parseSymbols4(opts) {
|
|
6008
6224
|
const { file, content, lang } = opts;
|
|
6009
6225
|
try {
|
|
@@ -6081,10 +6297,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
6081
6297
|
async function syncPyParse(filePath, content, lang) {
|
|
6082
6298
|
try {
|
|
6083
6299
|
if (!_cachedScriptPath) {
|
|
6084
|
-
const tmpDir =
|
|
6085
|
-
await
|
|
6086
|
-
_cachedScriptPath =
|
|
6087
|
-
await
|
|
6300
|
+
const tmpDir = path21.join(os7.tmpdir(), "ws-py-parse");
|
|
6301
|
+
await fs16.mkdir(tmpDir, { recursive: true });
|
|
6302
|
+
_cachedScriptPath = path21.join(tmpDir, "parse.py");
|
|
6303
|
+
await fs16.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
6088
6304
|
}
|
|
6089
6305
|
cachedPyBinary ??= resolvePython();
|
|
6090
6306
|
const pyBinary = await cachedPyBinary;
|
|
@@ -6098,7 +6314,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
6098
6314
|
if (code !== 0 || !stdout.trim()) {
|
|
6099
6315
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
6100
6316
|
}
|
|
6101
|
-
const raw =
|
|
6317
|
+
const { symbols: raw, refs } = parseParserOutput(stdout, lang);
|
|
6102
6318
|
const symbols = raw.map((s) => ({
|
|
6103
6319
|
id: 0,
|
|
6104
6320
|
lang,
|
|
@@ -6112,7 +6328,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
6112
6328
|
scope: s.scope ?? "",
|
|
6113
6329
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
6114
6330
|
}));
|
|
6115
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
6331
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
6116
6332
|
} catch {
|
|
6117
6333
|
return null;
|
|
6118
6334
|
}
|
|
@@ -6123,6 +6339,7 @@ var init_py_parser = __esm({
|
|
|
6123
6339
|
"use strict";
|
|
6124
6340
|
init_win32_resolve();
|
|
6125
6341
|
init_generic_parser();
|
|
6342
|
+
init_parser_output();
|
|
6126
6343
|
init_spawn_gate();
|
|
6127
6344
|
init_languages2();
|
|
6128
6345
|
PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
@@ -6184,7 +6401,18 @@ class Sym:
|
|
|
6184
6401
|
def is_private(name):
|
|
6185
6402
|
return name.startswith("__") and not name.endswith("__")
|
|
6186
6403
|
|
|
6404
|
+
def leaf_name(node):
|
|
6405
|
+
# Declared name of the callee: \`join\` of \`os.path.join\`. Matches how the
|
|
6406
|
+
# TypeScript and Go extractors record call refs, so resolution behaves the
|
|
6407
|
+
# same across languages.
|
|
6408
|
+
if isinstance(node, ast.Attribute):
|
|
6409
|
+
return node.attr
|
|
6410
|
+
if isinstance(node, ast.Name):
|
|
6411
|
+
return node.id
|
|
6412
|
+
return get_name(node).split(".")[-1]
|
|
6413
|
+
|
|
6187
6414
|
syms = []
|
|
6415
|
+
refs = []
|
|
6188
6416
|
errors = []
|
|
6189
6417
|
|
|
6190
6418
|
try:
|
|
@@ -6192,7 +6420,7 @@ try:
|
|
|
6192
6420
|
tree = ast.parse(source, filename=sys.argv[1])
|
|
6193
6421
|
except Exception as e:
|
|
6194
6422
|
errors.append(str(e))
|
|
6195
|
-
print("[]")
|
|
6423
|
+
print(json.dumps({"symbols": [], "refs": []}))
|
|
6196
6424
|
sys.exit(0)
|
|
6197
6425
|
|
|
6198
6426
|
# Module-level scope
|
|
@@ -6326,7 +6554,42 @@ class ModuleVisitor(ast.NodeVisitor):
|
|
|
6326
6554
|
visitor = ModuleVisitor()
|
|
6327
6555
|
visitor.visit(tree)
|
|
6328
6556
|
|
|
6329
|
-
|
|
6557
|
+
# Refs need a separate full walk: ModuleVisitor deliberately does not descend
|
|
6558
|
+
# into function bodies (it would index locals as symbols), but that is exactly
|
|
6559
|
+
# where the calls are.
|
|
6560
|
+
for node in ast.walk(tree):
|
|
6561
|
+
if isinstance(node, ast.Call):
|
|
6562
|
+
name = leaf_name(node.func)
|
|
6563
|
+
if name:
|
|
6564
|
+
refs.append({"toName": name, "callType": "call", "line": node.lineno})
|
|
6565
|
+
elif isinstance(node, ast.Import):
|
|
6566
|
+
for alias in node.names:
|
|
6567
|
+
refs.append({
|
|
6568
|
+
"toName": alias.name.split(".")[-1],
|
|
6569
|
+
"callType": "import",
|
|
6570
|
+
"line": node.lineno,
|
|
6571
|
+
"module": alias.name,
|
|
6572
|
+
})
|
|
6573
|
+
elif isinstance(node, ast.ImportFrom):
|
|
6574
|
+
# PEP 328: node.level is the number of leading dots. Preserving them is
|
|
6575
|
+
# what lets the resolver walk up from the importing file's package \u2014
|
|
6576
|
+
# dropping them made \`from .foo import X\` indistinguishable from an
|
|
6577
|
+
# absolute \`foo\`.
|
|
6578
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
6579
|
+
for alias in node.names:
|
|
6580
|
+
refs.append({
|
|
6581
|
+
"toName": alias.name,
|
|
6582
|
+
"callType": "import",
|
|
6583
|
+
"line": node.lineno,
|
|
6584
|
+
"module": module,
|
|
6585
|
+
})
|
|
6586
|
+
elif isinstance(node, ast.ClassDef):
|
|
6587
|
+
for base in node.bases:
|
|
6588
|
+
name = leaf_name(base)
|
|
6589
|
+
if name:
|
|
6590
|
+
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
6591
|
+
|
|
6592
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
6330
6593
|
`;
|
|
6331
6594
|
_cachedScriptPath = null;
|
|
6332
6595
|
}
|
|
@@ -6339,107 +6602,10 @@ __export(rs_parser_exports, {
|
|
|
6339
6602
|
parseSymbols: () => parseSymbols5
|
|
6340
6603
|
});
|
|
6341
6604
|
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
6342
|
-
import { execFile, spawn as spawn7 } from "node:child_process";
|
|
6343
|
-
import * as fs16 from "node:fs/promises";
|
|
6344
|
-
import * as path21 from "node:path";
|
|
6345
6605
|
async function parseSymbols5(opts) {
|
|
6346
6606
|
const { file, content, lang } = opts;
|
|
6347
|
-
const nativeAvailable = await checkNativeParser();
|
|
6348
|
-
if (nativeAvailable) {
|
|
6349
|
-
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
6350
|
-
if (result) return result;
|
|
6351
|
-
}
|
|
6352
6607
|
return regexParse({ file, content, lang });
|
|
6353
6608
|
}
|
|
6354
|
-
function probe(command, args) {
|
|
6355
|
-
return new Promise((resolve16, reject) => {
|
|
6356
|
-
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
6357
|
-
if (error) reject(error);
|
|
6358
|
-
else resolve16();
|
|
6359
|
-
});
|
|
6360
|
-
});
|
|
6361
|
-
}
|
|
6362
|
-
function checkNativeParser() {
|
|
6363
|
-
nativeParserAvailability ??= (async () => {
|
|
6364
|
-
try {
|
|
6365
|
-
await probe("rustc", ["--version"]);
|
|
6366
|
-
const toolsDir = path21.join(process.cwd(), "tools");
|
|
6367
|
-
await probe(
|
|
6368
|
-
"cargo",
|
|
6369
|
-
[
|
|
6370
|
-
"metadata",
|
|
6371
|
-
"--no-deps",
|
|
6372
|
-
"--format-version",
|
|
6373
|
-
"1",
|
|
6374
|
-
"--manifest-path",
|
|
6375
|
-
path21.join(toolsDir, "Cargo.toml")
|
|
6376
|
-
]
|
|
6377
|
-
);
|
|
6378
|
-
return true;
|
|
6379
|
-
} catch {
|
|
6380
|
-
return false;
|
|
6381
|
-
}
|
|
6382
|
-
})();
|
|
6383
|
-
return nativeParserAvailability;
|
|
6384
|
-
}
|
|
6385
|
-
async function tryNativeParse(file, content) {
|
|
6386
|
-
try {
|
|
6387
|
-
const toolsDir = path21.join(process.cwd(), "tools");
|
|
6388
|
-
const crateDir = path21.join(toolsDir, "syn-parser");
|
|
6389
|
-
const tmpFile = path21.join(crateDir, "src", "input.rs");
|
|
6390
|
-
await fs16.writeFile(tmpFile, content, "utf8");
|
|
6391
|
-
const cargoBinary = resolveWin32Command("cargo");
|
|
6392
|
-
const result = await new Promise(
|
|
6393
|
-
(resolve16, reject) => {
|
|
6394
|
-
let settled = false;
|
|
6395
|
-
const proc = spawn7(
|
|
6396
|
-
cargoBinary,
|
|
6397
|
-
["run", "--manifest-path", path21.join(toolsDir, "Cargo.toml")],
|
|
6398
|
-
{
|
|
6399
|
-
cwd: process.cwd(),
|
|
6400
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
6401
|
-
windowsHide: true
|
|
6402
|
-
}
|
|
6403
|
-
);
|
|
6404
|
-
proc.on("error", (err) => {
|
|
6405
|
-
if (settled) return;
|
|
6406
|
-
settled = true;
|
|
6407
|
-
reject(err);
|
|
6408
|
-
});
|
|
6409
|
-
let stdout2 = "";
|
|
6410
|
-
proc.stdout?.on("data", (chunk) => {
|
|
6411
|
-
stdout2 += chunk.toString();
|
|
6412
|
-
});
|
|
6413
|
-
proc.stderr?.resume();
|
|
6414
|
-
const timer = setTimeout(() => {
|
|
6415
|
-
if (settled) return;
|
|
6416
|
-
settled = true;
|
|
6417
|
-
proc.kill("SIGKILL");
|
|
6418
|
-
reject(new Error("timeout"));
|
|
6419
|
-
}, 15e3);
|
|
6420
|
-
timer.unref?.();
|
|
6421
|
-
proc.on("close", (c) => {
|
|
6422
|
-
if (settled) return;
|
|
6423
|
-
settled = true;
|
|
6424
|
-
clearTimeout(timer);
|
|
6425
|
-
resolve16({ code: c, stdout: stdout2 });
|
|
6426
|
-
});
|
|
6427
|
-
}
|
|
6428
|
-
);
|
|
6429
|
-
const { code, stdout } = result;
|
|
6430
|
-
if (code === 0 && stdout.trim()) {
|
|
6431
|
-
const symbols = JSON.parse(stdout.trim());
|
|
6432
|
-
return {
|
|
6433
|
-
file,
|
|
6434
|
-
lang: "rs",
|
|
6435
|
-
symbols: symbols.map((s) => ({ ...s, id: 0, lang: "rs" })),
|
|
6436
|
-
mtimeMs: Date.now()
|
|
6437
|
-
};
|
|
6438
|
-
}
|
|
6439
|
-
} catch {
|
|
6440
|
-
}
|
|
6441
|
-
return null;
|
|
6442
|
-
}
|
|
6443
6609
|
function regexParse(opts) {
|
|
6444
6610
|
const { file, content, lang } = opts;
|
|
6445
6611
|
const symbols = [];
|
|
@@ -6495,12 +6661,10 @@ function regexParse(opts) {
|
|
|
6495
6661
|
});
|
|
6496
6662
|
return { file, lang, symbols: deduped, mtimeMs: Date.now() };
|
|
6497
6663
|
}
|
|
6498
|
-
var
|
|
6664
|
+
var RS_PATTERNS;
|
|
6499
6665
|
var init_rs_parser = __esm({
|
|
6500
6666
|
"src/codebase-index/rs-parser.ts"() {
|
|
6501
6667
|
"use strict";
|
|
6502
|
-
init_win32_resolve();
|
|
6503
|
-
init_spawn_gate();
|
|
6504
6668
|
init_languages2();
|
|
6505
6669
|
RS_PATTERNS = [
|
|
6506
6670
|
{ regex: /fn\s+(\w+)\s*\([^)]*\)/g, kind: "function" },
|
|
@@ -8595,7 +8759,9 @@ async function executeSingle(call, ctx, governedExecute) {
|
|
|
8595
8759
|
executionMs: Date.now() - start
|
|
8596
8760
|
};
|
|
8597
8761
|
}
|
|
8598
|
-
const tool = ctx.tools.find(
|
|
8762
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find(
|
|
8763
|
+
(candidate) => candidate.name === call.tool
|
|
8764
|
+
);
|
|
8599
8765
|
if (!tool) {
|
|
8600
8766
|
return {
|
|
8601
8767
|
tool: call.tool,
|
|
@@ -8822,7 +8988,7 @@ function parsePrivateOriginAllowlist(raw) {
|
|
|
8822
8988
|
if (!raw?.trim()) return [];
|
|
8823
8989
|
const origins = /* @__PURE__ */ new Set();
|
|
8824
8990
|
for (const entry of raw.split(",")) {
|
|
8825
|
-
const candidate = entry.trim();
|
|
8991
|
+
const candidate = entry.trim().replace(/^["']+|["']+$/gu, "");
|
|
8826
8992
|
if (!candidate) continue;
|
|
8827
8993
|
const url = parseBrowserUrl(candidate, true);
|
|
8828
8994
|
if (url.pathname !== "/" || url.search || url.hash) {
|
|
@@ -9954,7 +10120,7 @@ for (const tool of browserTools) tool.timeoutMs ??= 6e4;
|
|
|
9954
10120
|
|
|
9955
10121
|
// src/codebase-index/project-server-client.ts
|
|
9956
10122
|
import { spawn as spawn4 } from "node:child_process";
|
|
9957
|
-
import * as
|
|
10123
|
+
import * as fs13 from "node:fs";
|
|
9958
10124
|
import * as net3 from "node:net";
|
|
9959
10125
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9960
10126
|
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
@@ -10041,23 +10207,23 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
10041
10207
|
|
|
10042
10208
|
// src/codebase-index/project-server-endpoint.ts
|
|
10043
10209
|
import { createHash as createHash4 } from "node:crypto";
|
|
10044
|
-
import * as
|
|
10210
|
+
import * as fs12 from "node:fs";
|
|
10045
10211
|
import * as os5 from "node:os";
|
|
10046
|
-
import * as
|
|
10212
|
+
import * as path17 from "node:path";
|
|
10047
10213
|
import { fileURLToPath } from "node:url";
|
|
10048
10214
|
import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
|
|
10049
10215
|
|
|
10050
10216
|
// src/codebase-index/writer.ts
|
|
10051
10217
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
10052
|
-
import * as
|
|
10053
|
-
import * as
|
|
10218
|
+
import * as fs11 from "node:fs";
|
|
10219
|
+
import * as path16 from "node:path";
|
|
10054
10220
|
|
|
10055
10221
|
// src/codebase-index/bm25.ts
|
|
10056
10222
|
var K1 = 1.5;
|
|
10057
10223
|
var B = 0.75;
|
|
10224
|
+
var TOKENISE_RE = new RegExp("[^\\p{L}\\p{N}$']", "gu");
|
|
10058
10225
|
function tokenise(text) {
|
|
10059
|
-
|
|
10060
|
-
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
10226
|
+
return text.replace(TOKENISE_RE, " ").toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
10061
10227
|
}
|
|
10062
10228
|
function splitName(name) {
|
|
10063
10229
|
return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([\p{L}])(\d)/gu, "$1 $2").replace(/(\d)([\p{L}])/gu, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
@@ -10141,6 +10307,9 @@ var Bm25Index = class {
|
|
|
10141
10307
|
}
|
|
10142
10308
|
};
|
|
10143
10309
|
|
|
10310
|
+
// src/codebase-index/writer.ts
|
|
10311
|
+
init_languages2();
|
|
10312
|
+
|
|
10144
10313
|
// src/codebase-index/lsp-kind.ts
|
|
10145
10314
|
function lspKindToInternalKind(k) {
|
|
10146
10315
|
switch (k) {
|
|
@@ -10175,7 +10344,7 @@ function lspKindToInternalKind(k) {
|
|
|
10175
10344
|
}
|
|
10176
10345
|
|
|
10177
10346
|
// src/codebase-index/schema.ts
|
|
10178
|
-
var SCHEMA_VERSION =
|
|
10347
|
+
var SCHEMA_VERSION = 4;
|
|
10179
10348
|
|
|
10180
10349
|
// src/codebase-index/sqlite-runtime.ts
|
|
10181
10350
|
import { createRequire } from "node:module";
|
|
@@ -10246,7 +10415,7 @@ function runSqliteWithRetry(fn) {
|
|
|
10246
10415
|
|
|
10247
10416
|
// src/codebase-index/writer-admin.ts
|
|
10248
10417
|
import * as fs9 from "node:fs";
|
|
10249
|
-
import * as
|
|
10418
|
+
import * as path14 from "node:path";
|
|
10250
10419
|
var DB_FILE = "index.db";
|
|
10251
10420
|
function getAllIndexableWithStatement(stmt) {
|
|
10252
10421
|
return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
@@ -10305,7 +10474,7 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
10305
10474
|
}
|
|
10306
10475
|
function getIndexDbSizeBytes(indexDir) {
|
|
10307
10476
|
try {
|
|
10308
|
-
return fs9.statSync(
|
|
10477
|
+
return fs9.statSync(path14.join(indexDir, DB_FILE)).size;
|
|
10309
10478
|
} catch {
|
|
10310
10479
|
return 0;
|
|
10311
10480
|
}
|
|
@@ -10356,49 +10525,345 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
10356
10525
|
}
|
|
10357
10526
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
10358
10527
|
if (refs.length === 0) return;
|
|
10359
|
-
const chunkSize = Math.max(1, Math.floor(maxSqlVars /
|
|
10528
|
+
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 8));
|
|
10360
10529
|
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
10361
10530
|
const chunk = refs.slice(i, i + chunkSize);
|
|
10362
|
-
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
10531
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
10363
10532
|
const insert = stmt(
|
|
10364
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line
|
|
10533
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
10534
|
+
VALUES ${placeholders}`
|
|
10365
10535
|
);
|
|
10366
10536
|
const binds = [];
|
|
10367
10537
|
for (const ref of chunk) {
|
|
10368
|
-
binds.push(
|
|
10538
|
+
binds.push(
|
|
10539
|
+
ref.fromId,
|
|
10540
|
+
ref.toName,
|
|
10541
|
+
ref.toId ?? null,
|
|
10542
|
+
ref.callType,
|
|
10543
|
+
ref.line,
|
|
10544
|
+
ref.lang ?? "",
|
|
10545
|
+
ref.module ?? null,
|
|
10546
|
+
ref.toFile ?? null
|
|
10547
|
+
);
|
|
10369
10548
|
}
|
|
10370
10549
|
insert.run(...binds);
|
|
10371
10550
|
}
|
|
10372
10551
|
}
|
|
10373
10552
|
|
|
10374
|
-
// src/codebase-index/writer-graph-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10553
|
+
// src/codebase-index/writer-graph-reader.ts
|
|
10554
|
+
init_languages2();
|
|
10555
|
+
|
|
10556
|
+
// src/codebase-index/module-roots.ts
|
|
10557
|
+
init_languages2();
|
|
10558
|
+
import * as fs10 from "node:fs/promises";
|
|
10559
|
+
import * as path15 from "node:path";
|
|
10560
|
+
function toPortablePath(file) {
|
|
10561
|
+
return file.replace(/\\/g, "/");
|
|
10562
|
+
}
|
|
10563
|
+
async function readTextIfPresent(file) {
|
|
10564
|
+
try {
|
|
10565
|
+
return await fs10.readFile(file, "utf8");
|
|
10566
|
+
} catch {
|
|
10567
|
+
return void 0;
|
|
10568
|
+
}
|
|
10569
|
+
}
|
|
10570
|
+
function parsePackageJsonName(source) {
|
|
10571
|
+
try {
|
|
10572
|
+
const parsed = JSON.parse(source);
|
|
10573
|
+
return typeof parsed.name === "string" && parsed.name ? parsed.name : void 0;
|
|
10574
|
+
} catch {
|
|
10575
|
+
return void 0;
|
|
10576
|
+
}
|
|
10577
|
+
}
|
|
10578
|
+
function parseGoModulePath(source) {
|
|
10579
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
10580
|
+
const line = rawLine.replace(/\/\/.*$/, "").trim();
|
|
10581
|
+
const match = /^module\s+(\S+)/.exec(line);
|
|
10582
|
+
if (match?.[1]) return match[1].replace(/^["']|["']$/g, "");
|
|
10583
|
+
}
|
|
10584
|
+
return void 0;
|
|
10585
|
+
}
|
|
10586
|
+
function parseTomlTableName(source, tables) {
|
|
10587
|
+
let current = "";
|
|
10588
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
10589
|
+
const line = rawLine.replace(/#.*$/, "").trim();
|
|
10590
|
+
if (line.startsWith("[[")) {
|
|
10591
|
+
current = "\0";
|
|
10592
|
+
continue;
|
|
10593
|
+
}
|
|
10594
|
+
const table = /^\[([^\]]+)\]$/.exec(line);
|
|
10595
|
+
if (table?.[1]) {
|
|
10596
|
+
current = table[1].trim();
|
|
10597
|
+
continue;
|
|
10598
|
+
}
|
|
10599
|
+
if (!tables.includes(current)) continue;
|
|
10600
|
+
const match = /^name\s*=\s*["']([^"']+)["']/.exec(line);
|
|
10601
|
+
if (match?.[1]) return match[1];
|
|
10602
|
+
}
|
|
10603
|
+
return void 0;
|
|
10604
|
+
}
|
|
10605
|
+
function parsePomArtifactId(source) {
|
|
10606
|
+
const withoutParent = source.replace(/<parent>[\s\S]*?<\/parent>/g, "");
|
|
10607
|
+
return /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(withoutParent)?.[1];
|
|
10608
|
+
}
|
|
10609
|
+
var LANGS_BY_KIND = {
|
|
10610
|
+
npm: ["ts", "tsx", "js", "jsx", "vue", "svelte"],
|
|
10611
|
+
cargo: ["rs"],
|
|
10612
|
+
go: ["go"],
|
|
10613
|
+
python: ["py"],
|
|
10614
|
+
maven: ["java", "kotlin", "scala"],
|
|
10615
|
+
gradle: ["java", "kotlin", "scala"],
|
|
10616
|
+
dotnet: ["csharp"]
|
|
10617
|
+
};
|
|
10618
|
+
function ancestorsOf(dir, stopAt) {
|
|
10619
|
+
const out = [];
|
|
10620
|
+
let current = dir;
|
|
10621
|
+
for (; ; ) {
|
|
10622
|
+
out.push(current);
|
|
10623
|
+
if (current === stopAt || current.length <= stopAt.length) break;
|
|
10624
|
+
const parent = path15.posix.dirname(current);
|
|
10625
|
+
if (parent === current) break;
|
|
10626
|
+
current = parent;
|
|
10627
|
+
}
|
|
10628
|
+
return out;
|
|
10629
|
+
}
|
|
10630
|
+
var MARKER_PROBES = [
|
|
10631
|
+
{
|
|
10632
|
+
kind: "npm",
|
|
10633
|
+
file: "package.json",
|
|
10634
|
+
build: (dir, source) => {
|
|
10635
|
+
const name = parsePackageJsonName(source) ?? path15.posix.basename(dir);
|
|
10636
|
+
return { name, importPath: name, sourceRoots: [dir] };
|
|
10637
|
+
}
|
|
10638
|
+
},
|
|
10639
|
+
{
|
|
10640
|
+
kind: "cargo",
|
|
10641
|
+
file: "Cargo.toml",
|
|
10642
|
+
build: (dir, source) => {
|
|
10643
|
+
const name = parseTomlTableName(source, ["package"]);
|
|
10644
|
+
if (!name) return void 0;
|
|
10645
|
+
return {
|
|
10646
|
+
name: `crate:${name}`,
|
|
10647
|
+
// Rust paths use underscores where crate names often use dashes.
|
|
10648
|
+
importPath: name.replace(/-/g, "_"),
|
|
10649
|
+
sourceRoots: [path15.posix.join(dir, "src")]
|
|
10650
|
+
};
|
|
10651
|
+
}
|
|
10652
|
+
},
|
|
10653
|
+
{
|
|
10654
|
+
kind: "go",
|
|
10655
|
+
file: "go.mod",
|
|
10656
|
+
build: (dir, source) => {
|
|
10657
|
+
const modulePath = parseGoModulePath(source);
|
|
10658
|
+
if (!modulePath) return void 0;
|
|
10659
|
+
return { name: `go:${modulePath}`, importPath: modulePath, sourceRoots: [dir] };
|
|
10660
|
+
}
|
|
10661
|
+
},
|
|
10662
|
+
{
|
|
10663
|
+
kind: "python",
|
|
10664
|
+
file: "pyproject.toml",
|
|
10665
|
+
build: (dir, source) => {
|
|
10666
|
+
const name = parseTomlTableName(source, ["project", "tool.poetry"]) ?? path15.posix.basename(dir);
|
|
10667
|
+
return {
|
|
10668
|
+
name: `py:${name}`,
|
|
10669
|
+
importPath: void 0,
|
|
10670
|
+
// `src/` layout is the packaging-guide default; the root itself covers
|
|
10671
|
+
// the flat layout. Both are probed, missing ones simply never match.
|
|
10672
|
+
sourceRoots: [path15.posix.join(dir, "src"), dir]
|
|
10673
|
+
};
|
|
10674
|
+
}
|
|
10675
|
+
},
|
|
10676
|
+
{
|
|
10677
|
+
kind: "python",
|
|
10678
|
+
file: "setup.py",
|
|
10679
|
+
build: (dir) => ({
|
|
10680
|
+
name: `py:${path15.posix.basename(dir)}`,
|
|
10681
|
+
importPath: void 0,
|
|
10682
|
+
sourceRoots: [path15.posix.join(dir, "src"), dir]
|
|
10683
|
+
})
|
|
10684
|
+
},
|
|
10685
|
+
{
|
|
10686
|
+
kind: "maven",
|
|
10687
|
+
file: "pom.xml",
|
|
10688
|
+
build: (dir, source) => {
|
|
10689
|
+
const artifactId = parsePomArtifactId(source) ?? path15.posix.basename(dir);
|
|
10690
|
+
return {
|
|
10691
|
+
name: `mvn:${artifactId}`,
|
|
10692
|
+
importPath: void 0,
|
|
10693
|
+
sourceRoots: [
|
|
10694
|
+
path15.posix.join(dir, "src/main/java"),
|
|
10695
|
+
path15.posix.join(dir, "src/main/kotlin"),
|
|
10696
|
+
path15.posix.join(dir, "src/main/scala"),
|
|
10697
|
+
path15.posix.join(dir, "src/test/java")
|
|
10698
|
+
]
|
|
10699
|
+
};
|
|
10700
|
+
}
|
|
10701
|
+
},
|
|
10702
|
+
{
|
|
10703
|
+
kind: "gradle",
|
|
10704
|
+
file: "build.gradle",
|
|
10705
|
+
build: (dir) => buildGradleRoot(dir)
|
|
10706
|
+
},
|
|
10707
|
+
{
|
|
10708
|
+
kind: "gradle",
|
|
10709
|
+
file: "build.gradle.kts",
|
|
10710
|
+
build: (dir) => buildGradleRoot(dir)
|
|
10711
|
+
}
|
|
10712
|
+
];
|
|
10713
|
+
function buildGradleRoot(dir) {
|
|
10714
|
+
return {
|
|
10715
|
+
name: `gradle:${path15.posix.basename(dir)}`,
|
|
10716
|
+
importPath: void 0,
|
|
10717
|
+
sourceRoots: [
|
|
10718
|
+
path15.posix.join(dir, "src/main/java"),
|
|
10719
|
+
path15.posix.join(dir, "src/main/kotlin"),
|
|
10720
|
+
path15.posix.join(dir, "src/main/scala")
|
|
10721
|
+
]
|
|
10722
|
+
};
|
|
10723
|
+
}
|
|
10724
|
+
async function probeDotnetRoot(dir) {
|
|
10725
|
+
let entries;
|
|
10726
|
+
try {
|
|
10727
|
+
entries = await fs10.readdir(dir);
|
|
10728
|
+
} catch {
|
|
10729
|
+
return void 0;
|
|
10730
|
+
}
|
|
10731
|
+
const project = entries.find((entry) => entry.toLowerCase().endsWith(".csproj"));
|
|
10732
|
+
if (!project) return void 0;
|
|
10733
|
+
const name = project.slice(0, -".csproj".length);
|
|
10734
|
+
return {
|
|
10735
|
+
dir,
|
|
10736
|
+
kind: "dotnet",
|
|
10737
|
+
name: `csproj:${name}`,
|
|
10738
|
+
importPath: void 0,
|
|
10739
|
+
sourceRoots: [dir]
|
|
10740
|
+
};
|
|
10741
|
+
}
|
|
10742
|
+
async function detectModuleRoots(projectRoot, files) {
|
|
10743
|
+
const root = toPortablePath(projectRoot).replace(/\/+$/, "");
|
|
10744
|
+
const langsByDir = /* @__PURE__ */ new Map();
|
|
10745
|
+
for (const file of files) {
|
|
10746
|
+
const portable = toPortablePath(file);
|
|
10747
|
+
const lang = detectLang(portable);
|
|
10748
|
+
if (!lang) continue;
|
|
10749
|
+
const dir = path15.posix.dirname(portable);
|
|
10750
|
+
let langs = langsByDir.get(dir);
|
|
10751
|
+
if (!langs) {
|
|
10752
|
+
langs = /* @__PURE__ */ new Set();
|
|
10753
|
+
langsByDir.set(dir, langs);
|
|
10754
|
+
}
|
|
10755
|
+
langs.add(lang);
|
|
10756
|
+
}
|
|
10757
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
10758
|
+
for (const [dir, langs] of langsByDir) {
|
|
10759
|
+
for (const ancestor of ancestorsOf(dir, root)) {
|
|
10760
|
+
let merged = candidates.get(ancestor);
|
|
10761
|
+
if (!merged) {
|
|
10762
|
+
merged = /* @__PURE__ */ new Set();
|
|
10763
|
+
candidates.set(ancestor, merged);
|
|
10764
|
+
}
|
|
10765
|
+
for (const lang of langs) merged.add(lang);
|
|
10766
|
+
}
|
|
10767
|
+
}
|
|
10768
|
+
const roots = [];
|
|
10769
|
+
await Promise.all(
|
|
10770
|
+
[...candidates].map(async ([dir, langs]) => {
|
|
10771
|
+
for (const probe of MARKER_PROBES) {
|
|
10772
|
+
if (!LANGS_BY_KIND[probe.kind].some((lang) => langs.has(lang))) continue;
|
|
10773
|
+
const source = await readTextIfPresent(path15.posix.join(dir, probe.file));
|
|
10774
|
+
if (source === void 0) continue;
|
|
10775
|
+
const built = probe.build(dir, source);
|
|
10776
|
+
if (built) roots.push({ dir, kind: probe.kind, ...built });
|
|
10777
|
+
}
|
|
10778
|
+
if (LANGS_BY_KIND.dotnet.some((lang) => langs.has(lang))) {
|
|
10779
|
+
const dotnet = await probeDotnetRoot(dir);
|
|
10780
|
+
if (dotnet) roots.push(dotnet);
|
|
10781
|
+
}
|
|
10782
|
+
})
|
|
10783
|
+
);
|
|
10784
|
+
roots.sort((a, b) => b.dir.length - a.dir.length || a.dir.localeCompare(b.dir));
|
|
10785
|
+
return { projectRoot: root, roots };
|
|
10786
|
+
}
|
|
10787
|
+
function findOwningRoot(structure, file, kinds) {
|
|
10788
|
+
const portable = toPortablePath(file);
|
|
10789
|
+
for (const root of structure.roots) {
|
|
10790
|
+
if (kinds && !kinds.includes(root.kind)) continue;
|
|
10791
|
+
if (portable === root.dir || portable.startsWith(`${root.dir}/`)) return root;
|
|
10792
|
+
}
|
|
10793
|
+
return void 0;
|
|
10794
|
+
}
|
|
10795
|
+
function derivePackageFromLayout(filePath) {
|
|
10796
|
+
const portable = toPortablePath(filePath);
|
|
10797
|
+
const packagesIdx = portable.indexOf("/packages/");
|
|
10798
|
+
if (packagesIdx !== -1) {
|
|
10799
|
+
const segment = portable.slice(packagesIdx + "/packages/".length).split("/")[0];
|
|
10800
|
+
if (segment) return `@wrongstack/${segment}`;
|
|
10801
|
+
}
|
|
10802
|
+
const appsIdx = portable.indexOf("/apps/");
|
|
10385
10803
|
if (appsIdx !== -1) {
|
|
10386
|
-
const
|
|
10387
|
-
|
|
10388
|
-
return seg ? `app:${seg}` : void 0;
|
|
10804
|
+
const segment = portable.slice(appsIdx + "/apps/".length).split("/")[0];
|
|
10805
|
+
if (segment) return `app:${segment}`;
|
|
10389
10806
|
}
|
|
10390
10807
|
return void 0;
|
|
10391
10808
|
}
|
|
10392
|
-
function
|
|
10393
|
-
|
|
10394
|
-
const
|
|
10395
|
-
|
|
10809
|
+
function pythonPackageLabel(structure, file, initDirs) {
|
|
10810
|
+
const portable = toPortablePath(file);
|
|
10811
|
+
const dir = path15.posix.dirname(portable);
|
|
10812
|
+
if (!initDirs.has(dir)) return void 0;
|
|
10813
|
+
const segments = [];
|
|
10814
|
+
let current = dir;
|
|
10815
|
+
while (initDirs.has(current) && current.length > structure.projectRoot.length) {
|
|
10816
|
+
segments.unshift(path15.posix.basename(current));
|
|
10817
|
+
current = path15.posix.dirname(current);
|
|
10818
|
+
}
|
|
10819
|
+
return segments.length > 0 ? `py:${segments.join(".")}` : void 0;
|
|
10396
10820
|
}
|
|
10397
|
-
function
|
|
10821
|
+
function assignPackageLabels(structure, files) {
|
|
10822
|
+
const initDirs = /* @__PURE__ */ new Set();
|
|
10823
|
+
for (const file of files) {
|
|
10824
|
+
const portable = toPortablePath(file);
|
|
10825
|
+
if (path15.posix.basename(portable) === "__init__.py") {
|
|
10826
|
+
initDirs.add(path15.posix.dirname(portable));
|
|
10827
|
+
}
|
|
10828
|
+
}
|
|
10829
|
+
const labels = /* @__PURE__ */ new Map();
|
|
10830
|
+
for (const file of files) {
|
|
10831
|
+
const portable = toPortablePath(file);
|
|
10832
|
+
const lang = detectLang(portable);
|
|
10833
|
+
if (lang === "go") {
|
|
10834
|
+
const owner3 = findOwningRoot(structure, portable, ["go"]);
|
|
10835
|
+
const dir = path15.posix.dirname(portable);
|
|
10836
|
+
if (owner3?.importPath) {
|
|
10837
|
+
const relative12 = path15.posix.relative(owner3.dir, dir);
|
|
10838
|
+
labels.set(file, relative12 ? `${owner3.importPath}/${relative12}` : owner3.importPath);
|
|
10839
|
+
} else {
|
|
10840
|
+
labels.set(file, `go:${path15.posix.relative(structure.projectRoot, dir) || "."}`);
|
|
10841
|
+
}
|
|
10842
|
+
continue;
|
|
10843
|
+
}
|
|
10844
|
+
if (lang === "py") {
|
|
10845
|
+
const dotted = pythonPackageLabel(structure, portable, initDirs);
|
|
10846
|
+
if (dotted) {
|
|
10847
|
+
labels.set(file, dotted);
|
|
10848
|
+
continue;
|
|
10849
|
+
}
|
|
10850
|
+
}
|
|
10851
|
+
const owner2 = findOwningRoot(structure, portable);
|
|
10852
|
+
const label = owner2?.name ?? derivePackageFromLayout(portable) ?? "(root)";
|
|
10853
|
+
labels.set(file, label);
|
|
10854
|
+
}
|
|
10855
|
+
return labels;
|
|
10856
|
+
}
|
|
10857
|
+
|
|
10858
|
+
// src/codebase-index/writer-graph-helpers.ts
|
|
10859
|
+
function createPackageLabeller(stored) {
|
|
10860
|
+
return (file) => stored.get(file) ?? derivePackageFromLayout(file) ?? "(root)";
|
|
10861
|
+
}
|
|
10862
|
+
function buildPackageGraphNodes(fileCounts, files, packageOf) {
|
|
10398
10863
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
10399
10864
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
10400
10865
|
for (const { file, n } of fileCounts) {
|
|
10401
|
-
const pkg =
|
|
10866
|
+
const pkg = packageOf(file);
|
|
10402
10867
|
fileToPkg.set(file, pkg);
|
|
10403
10868
|
const node = pkgNodes.get(pkg);
|
|
10404
10869
|
if (node) {
|
|
@@ -10415,7 +10880,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
10415
10880
|
}
|
|
10416
10881
|
}
|
|
10417
10882
|
for (const { file } of files) {
|
|
10418
|
-
const pkg =
|
|
10883
|
+
const pkg = packageOf(file);
|
|
10419
10884
|
fileToPkg.set(file, pkg);
|
|
10420
10885
|
const node = pkgNodes.get(pkg);
|
|
10421
10886
|
if (node) {
|
|
@@ -10433,7 +10898,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
10433
10898
|
}
|
|
10434
10899
|
return { pkgNodes, fileToPkg };
|
|
10435
10900
|
}
|
|
10436
|
-
function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
10901
|
+
function buildFileGraphNodeState(pkgSyms, localFiles, packageOf) {
|
|
10437
10902
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
10438
10903
|
const symToFile = /* @__PURE__ */ new Map();
|
|
10439
10904
|
const fileStats = /* @__PURE__ */ new Map();
|
|
@@ -10452,7 +10917,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
10452
10917
|
id: `file:${file}`,
|
|
10453
10918
|
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
10454
10919
|
kind: "file",
|
|
10455
|
-
package:
|
|
10920
|
+
package: packageOf(file),
|
|
10456
10921
|
file,
|
|
10457
10922
|
symbolCount: stats?.count ?? 0,
|
|
10458
10923
|
lang: stats?.lang,
|
|
@@ -10464,7 +10929,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
10464
10929
|
}
|
|
10465
10930
|
return { fileNodes, symToFile, fileStats, ensureFileNode };
|
|
10466
10931
|
}
|
|
10467
|
-
function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
10932
|
+
function buildSymbolGraphNodes(symById, relatedIds, fileFilter, packageOf) {
|
|
10468
10933
|
return [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
10469
10934
|
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
10470
10935
|
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
@@ -10476,7 +10941,7 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
10476
10941
|
symbolId: s.id,
|
|
10477
10942
|
symbolKind: s.kind,
|
|
10478
10943
|
file: s.file,
|
|
10479
|
-
package:
|
|
10944
|
+
package: packageOf(s.file),
|
|
10480
10945
|
lang: s.lang,
|
|
10481
10946
|
line: s.line,
|
|
10482
10947
|
signature: s.signature,
|
|
@@ -10484,29 +10949,6 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
10484
10949
|
external: s.file !== fileFilter
|
|
10485
10950
|
}));
|
|
10486
10951
|
}
|
|
10487
|
-
function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
10488
|
-
if (!moduleName.startsWith(".")) return void 0;
|
|
10489
|
-
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
10490
|
-
const absolute = path14.posix.normalize(
|
|
10491
|
-
path14.posix.join(path14.posix.dirname(normalizedFrom), moduleName)
|
|
10492
|
-
);
|
|
10493
|
-
const extension = path14.posix.extname(absolute);
|
|
10494
|
-
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
10495
|
-
const candidates = [
|
|
10496
|
-
absolute,
|
|
10497
|
-
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
10498
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(absolute, `index${ext}`)),
|
|
10499
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(base, `index${ext}`))
|
|
10500
|
-
];
|
|
10501
|
-
const indexedByPortablePath = new Map(
|
|
10502
|
-
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
10503
|
-
);
|
|
10504
|
-
for (const candidate of candidates) {
|
|
10505
|
-
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
10506
|
-
if (indexed) return indexed;
|
|
10507
|
-
}
|
|
10508
|
-
return void 0;
|
|
10509
|
-
}
|
|
10510
10952
|
function addWeightedEdge(edgeMap, source, target, callType, weight) {
|
|
10511
10953
|
const key = `${source}\0${target}`;
|
|
10512
10954
|
let edge = edgeMap.get(key);
|
|
@@ -10547,7 +10989,12 @@ function mapWriterRefRow(row) {
|
|
|
10547
10989
|
toName: row.to_name,
|
|
10548
10990
|
toId: row.to_id ?? void 0,
|
|
10549
10991
|
callType: row.call_type,
|
|
10550
|
-
line: row.line
|
|
10992
|
+
line: row.line,
|
|
10993
|
+
// `lang`/`module`/`to_file` are absent from the narrower column lists some
|
|
10994
|
+
// queries select; `undefined` keeps those rows valid Refs.
|
|
10995
|
+
lang: row.lang || void 0,
|
|
10996
|
+
module: row.module ?? void 0,
|
|
10997
|
+
toFile: row.to_file ?? void 0
|
|
10551
10998
|
};
|
|
10552
10999
|
}
|
|
10553
11000
|
|
|
@@ -10695,7 +11142,8 @@ function findRefsFromWithStatement(stmt, symbolId) {
|
|
|
10695
11142
|
function getPackageGraphWithStatement(stmt) {
|
|
10696
11143
|
const fileCounts = stmt("SELECT file, COUNT(*) AS n FROM symbols GROUP BY file").all();
|
|
10697
11144
|
const files = stmt("SELECT DISTINCT file FROM files").all();
|
|
10698
|
-
const
|
|
11145
|
+
const packageOf = readPackageLabeller(stmt);
|
|
11146
|
+
const { pkgNodes, fileToPkg } = buildPackageGraphNodes(fileCounts, files, packageOf);
|
|
10699
11147
|
const refRows = stmt(
|
|
10700
11148
|
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
|
|
10701
11149
|
FROM refs r
|
|
@@ -10706,32 +11154,42 @@ function getPackageGraphWithStatement(stmt) {
|
|
|
10706
11154
|
).all();
|
|
10707
11155
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
10708
11156
|
for (const r of refRows) {
|
|
10709
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
10710
|
-
const toPkg = fileToPkg.get(r.to_file) ??
|
|
11157
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
11158
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
10711
11159
|
if (fromPkg === toPkg) continue;
|
|
10712
11160
|
const n = Number(r.n) || 0;
|
|
10713
11161
|
addWeightedEdge(edgeMap, fromPkg, toPkg, r.call_type, n);
|
|
10714
11162
|
}
|
|
10715
11163
|
const importRows = stmt(
|
|
10716
|
-
`SELECT
|
|
11164
|
+
`SELECT s.file AS from_file,
|
|
11165
|
+
COALESCE(r.to_file, st.file) AS to_file,
|
|
11166
|
+
COUNT(*) AS n
|
|
10717
11167
|
FROM refs r
|
|
10718
11168
|
JOIN symbols s ON s.id = r.from_id
|
|
11169
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
10719
11170
|
WHERE r.call_type = 'import'
|
|
10720
|
-
|
|
11171
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
11172
|
+
GROUP BY s.file, COALESCE(r.to_file, st.file)`
|
|
10721
11173
|
).all();
|
|
10722
11174
|
for (const r of importRows) {
|
|
10723
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
10724
|
-
const toPkg =
|
|
10725
|
-
if (
|
|
11175
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
11176
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
11177
|
+
if (fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
10726
11178
|
const n = Number(r.n) || 0;
|
|
10727
11179
|
addWeightedEdge(edgeMap, fromPkg, toPkg, "import", n);
|
|
10728
11180
|
}
|
|
10729
11181
|
const edges = materializeWeightedEdges(edgeMap, "pkg");
|
|
10730
11182
|
return { nodes: [...pkgNodes.values()], edges };
|
|
10731
11183
|
}
|
|
11184
|
+
function readPackageLabeller(stmt) {
|
|
11185
|
+
const rows = stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
11186
|
+
return createPackageLabeller(new Map(rows.map((row) => [row.file, row.package])));
|
|
11187
|
+
}
|
|
10732
11188
|
function getFileGraphWithStatement(stmt, packageFilter) {
|
|
10733
11189
|
const allFiles = stmt("SELECT DISTINCT file FROM symbols").all();
|
|
10734
|
-
const
|
|
11190
|
+
const packageOf = readPackageLabeller(stmt);
|
|
11191
|
+
const langOf = (file) => detectLang(file) ?? "other";
|
|
11192
|
+
const pkgFilePaths = allFiles.filter((f) => packageOf(f.file) === packageFilter).map((f) => f.file);
|
|
10735
11193
|
const localFiles = new Set(pkgFilePaths);
|
|
10736
11194
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
10737
11195
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
@@ -10740,9 +11198,9 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
10740
11198
|
).all(...pkgFilePaths);
|
|
10741
11199
|
const { fileNodes, symToFile, fileStats, ensureFileNode } = buildFileGraphNodeState(
|
|
10742
11200
|
pkgSyms,
|
|
10743
|
-
localFiles
|
|
11201
|
+
localFiles,
|
|
11202
|
+
packageOf
|
|
10744
11203
|
);
|
|
10745
|
-
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
10746
11204
|
const refRows = stmt(
|
|
10747
11205
|
`SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
|
|
10748
11206
|
FROM refs r
|
|
@@ -10765,7 +11223,7 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
10765
11223
|
for (const x of extras) {
|
|
10766
11224
|
symToFile.set(x.id, x.file);
|
|
10767
11225
|
if (!fileStats.has(x.file)) {
|
|
10768
|
-
fileStats.set(x.file, { count: 0, lang:
|
|
11226
|
+
fileStats.set(x.file, { count: 0, lang: langOf(x.file) });
|
|
10769
11227
|
}
|
|
10770
11228
|
}
|
|
10771
11229
|
}
|
|
@@ -10782,17 +11240,22 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
10782
11240
|
addWeightedEdge(edgeMap, fromFile, toFile, r.call_type, n);
|
|
10783
11241
|
}
|
|
10784
11242
|
const importRows = stmt(
|
|
10785
|
-
`SELECT r.from_id, r.
|
|
11243
|
+
`SELECT r.from_id, COALESCE(r.to_file, st.file) AS to_file, COUNT(*) AS n
|
|
10786
11244
|
FROM refs r
|
|
11245
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
10787
11246
|
WHERE r.call_type = 'import'
|
|
11247
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
10788
11248
|
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
10789
|
-
GROUP BY r.from_id, r.
|
|
11249
|
+
GROUP BY r.from_id, COALESCE(r.to_file, st.file)`
|
|
10790
11250
|
).all(...pkgFilePaths);
|
|
10791
11251
|
for (const r of importRows) {
|
|
10792
11252
|
const fromFile = symToFile.get(r.from_id);
|
|
10793
11253
|
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
10794
|
-
const toFile =
|
|
11254
|
+
const toFile = r.to_file;
|
|
10795
11255
|
if (!toFile || fromFile === toFile) continue;
|
|
11256
|
+
if (!fileStats.has(toFile)) {
|
|
11257
|
+
fileStats.set(toFile, { count: 0, lang: langOf(toFile) });
|
|
11258
|
+
}
|
|
10796
11259
|
ensureFileNode(fromFile);
|
|
10797
11260
|
ensureFileNode(toFile);
|
|
10798
11261
|
const n = Number(r.n) || 0;
|
|
@@ -10842,7 +11305,7 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
10842
11305
|
).all(...missingIds);
|
|
10843
11306
|
for (const s of extras) symById.set(s.id, s);
|
|
10844
11307
|
}
|
|
10845
|
-
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter);
|
|
11308
|
+
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter, readPackageLabeller(stmt));
|
|
10846
11309
|
return { nodes, edges };
|
|
10847
11310
|
}
|
|
10848
11311
|
|
|
@@ -10864,7 +11327,7 @@ function assignRefsToSymbols(refs, symbols) {
|
|
|
10864
11327
|
}
|
|
10865
11328
|
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
10866
11329
|
if (!owner2 || owner2.id <= 0) continue;
|
|
10867
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
11330
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
10868
11331
|
if (seen.has(key)) continue;
|
|
10869
11332
|
seen.add(key);
|
|
10870
11333
|
assigned.push({ ...ref, fromId: owner2.id });
|
|
@@ -10910,7 +11373,11 @@ var CORE_TABLES_SQL = `
|
|
|
10910
11373
|
lang TEXT NOT NULL,
|
|
10911
11374
|
mtime_ms INTEGER NOT NULL,
|
|
10912
11375
|
symbol_count INTEGER NOT NULL DEFAULT 0,
|
|
10913
|
-
last_indexed INTEGER NOT NULL
|
|
11376
|
+
last_indexed INTEGER NOT NULL,
|
|
11377
|
+
-- Code Atlas grouping label, computed at index time from the ecosystem's
|
|
11378
|
+
-- own manifests (package.json, go.mod, Cargo.toml, \u2026). Stored rather than
|
|
11379
|
+
-- re-derived per query because the evidence lives on disk, not in the DB.
|
|
11380
|
+
package TEXT NOT NULL DEFAULT ''
|
|
10914
11381
|
);
|
|
10915
11382
|
CREATE TABLE IF NOT EXISTS symbols (
|
|
10916
11383
|
id INTEGER PRIMARY KEY,
|
|
@@ -10927,6 +11394,9 @@ var CORE_TABLES_SQL = `
|
|
|
10927
11394
|
file_fk TEXT NOT NULL
|
|
10928
11395
|
);
|
|
10929
11396
|
`;
|
|
11397
|
+
var FILE_INDEX_SQL = [
|
|
11398
|
+
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
11399
|
+
];
|
|
10930
11400
|
var SYMBOL_INDEX_SQL = [
|
|
10931
11401
|
"CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
|
|
10932
11402
|
"CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
|
|
@@ -10943,15 +11413,32 @@ var REFS_TABLE_SQL = `
|
|
|
10943
11413
|
to_name TEXT NOT NULL,
|
|
10944
11414
|
to_id INTEGER,
|
|
10945
11415
|
call_type TEXT NOT NULL,
|
|
10946
|
-
line INTEGER NOT NULL
|
|
11416
|
+
line INTEGER NOT NULL,
|
|
11417
|
+
lang TEXT NOT NULL DEFAULT '',
|
|
11418
|
+
module TEXT,
|
|
11419
|
+
to_file TEXT
|
|
10947
11420
|
);
|
|
10948
11421
|
`;
|
|
10949
11422
|
var REFS_INDEX_SQL = [
|
|
10950
11423
|
"CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
|
|
10951
11424
|
"CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
|
|
10952
11425
|
"CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
|
|
10953
|
-
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
|
|
11426
|
+
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)",
|
|
11427
|
+
// Name resolution matches (to_name, lang) pairs; the composite keeps the
|
|
11428
|
+
// language-scoped UPDATE from degrading into a scan of every same-named row.
|
|
11429
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_name_lang ON refs(to_name, lang)",
|
|
11430
|
+
// The post-index module resolution pass groups unresolved import refs by
|
|
11431
|
+
// (module, lang); graph readers then read to_file back.
|
|
11432
|
+
"CREATE INDEX IF NOT EXISTS idx_r_module ON refs(module)",
|
|
11433
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_file ON refs(to_file)"
|
|
10954
11434
|
];
|
|
11435
|
+
var LANG_FAMILY_TABLE_SQL = `
|
|
11436
|
+
CREATE TABLE IF NOT EXISTS lang_family (
|
|
11437
|
+
lang TEXT PRIMARY KEY,
|
|
11438
|
+
family TEXT NOT NULL
|
|
11439
|
+
);
|
|
11440
|
+
`;
|
|
11441
|
+
var LANG_FAMILY_WILDCARD = "*";
|
|
10955
11442
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
10956
11443
|
|
|
10957
11444
|
// src/codebase-index/writer-search-helpers.ts
|
|
@@ -11163,15 +11650,69 @@ var IndexStore = class _IndexStore {
|
|
|
11163
11650
|
}
|
|
11164
11651
|
constructor(projectRoot, opts = {}) {
|
|
11165
11652
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
11166
|
-
|
|
11653
|
+
fs11.mkdirSync(this.indexDir, { recursive: true });
|
|
11167
11654
|
const Database = loadDatabaseSync();
|
|
11168
|
-
this.db = new Database(
|
|
11655
|
+
this.db = new Database(path16.join(this.indexDir, DB_FILE2));
|
|
11169
11656
|
applyIndexStorePragmas(this.db);
|
|
11170
11657
|
this.initSchema();
|
|
11171
11658
|
}
|
|
11172
11659
|
runWithRetry(fn) {
|
|
11173
11660
|
return runSqliteWithRetry(fn);
|
|
11174
11661
|
}
|
|
11662
|
+
/**
|
|
11663
|
+
* Mirror the in-process language→family map into SQLite.
|
|
11664
|
+
*
|
|
11665
|
+
* Rewritten on every open rather than only on schema bumps: the mapping is
|
|
11666
|
+
* static lookup data, so a code-side change (a new language, a language
|
|
11667
|
+
* moving families) must take effect without forcing a full reindex.
|
|
11668
|
+
*/
|
|
11669
|
+
seedLangFamilies() {
|
|
11670
|
+
const insert = this.stmt("INSERT OR REPLACE INTO lang_family(lang, family) VALUES (?, ?)");
|
|
11671
|
+
for (const [lang, family] of LANG_FAMILY_ENTRIES) insert.run(lang, family);
|
|
11672
|
+
insert.run("", LANG_FAMILY_WILDCARD);
|
|
11673
|
+
}
|
|
11674
|
+
/**
|
|
11675
|
+
* Add any column the current schema expects but the on-disk table lacks.
|
|
11676
|
+
*
|
|
11677
|
+
* `CREATE TABLE IF NOT EXISTS` silently keeps an existing table's old shape,
|
|
11678
|
+
* and the version check above only rebuilds on a version *mismatch*. That
|
|
11679
|
+
* leaves a real gap: several wstack processes share this database, and while
|
|
11680
|
+
* a version upgrade is rolling out one of them may still be running the
|
|
11681
|
+
* previous build. That older process sees the newer version number, drops the
|
|
11682
|
+
* tables, and recreates them from *its* DDL — without the newer columns —
|
|
11683
|
+
* while the metadata row still reads the new version. Every later query for
|
|
11684
|
+
* one of those columns then fails with `no such column`, and no amount of
|
|
11685
|
+
* reindexing fixes it, because the version numbers already agree.
|
|
11686
|
+
*
|
|
11687
|
+
* Repairing column-by-column makes the schema self-healing from any of those
|
|
11688
|
+
* states. Table and column names are compile-time literals from this module,
|
|
11689
|
+
* never user input.
|
|
11690
|
+
*/
|
|
11691
|
+
repairMissingColumns() {
|
|
11692
|
+
const expected = [
|
|
11693
|
+
{ table: "files", columns: [["package", "TEXT NOT NULL DEFAULT ''"]] },
|
|
11694
|
+
{
|
|
11695
|
+
table: "refs",
|
|
11696
|
+
columns: [
|
|
11697
|
+
["lang", "TEXT NOT NULL DEFAULT ''"],
|
|
11698
|
+
["module", "TEXT"],
|
|
11699
|
+
["to_file", "TEXT"]
|
|
11700
|
+
]
|
|
11701
|
+
}
|
|
11702
|
+
];
|
|
11703
|
+
for (const { table, columns } of expected) {
|
|
11704
|
+
const present = new Set(
|
|
11705
|
+
this.db.prepare(`PRAGMA table_info(${table})`).all().flatMap(
|
|
11706
|
+
(row) => typeof row.name === "string" ? [row.name] : []
|
|
11707
|
+
)
|
|
11708
|
+
);
|
|
11709
|
+
if (present.size === 0) continue;
|
|
11710
|
+
for (const [name, type] of columns) {
|
|
11711
|
+
if (present.has(name)) continue;
|
|
11712
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${name} ${type}`);
|
|
11713
|
+
}
|
|
11714
|
+
}
|
|
11715
|
+
}
|
|
11175
11716
|
initSchema() {
|
|
11176
11717
|
this.db.exec(METADATA_TABLE_SQL);
|
|
11177
11718
|
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
@@ -11194,9 +11735,13 @@ var IndexStore = class _IndexStore {
|
|
|
11194
11735
|
);
|
|
11195
11736
|
}
|
|
11196
11737
|
this.db.exec(CORE_TABLES_SQL);
|
|
11197
|
-
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
11198
11738
|
this.db.exec(REFS_TABLE_SQL);
|
|
11739
|
+
this.repairMissingColumns();
|
|
11740
|
+
for (const sql of FILE_INDEX_SQL) this.db.exec(sql);
|
|
11741
|
+
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
11199
11742
|
for (const sql of REFS_INDEX_SQL) this.db.exec(sql);
|
|
11743
|
+
this.db.exec(LANG_FAMILY_TABLE_SQL);
|
|
11744
|
+
this.seedLangFamilies();
|
|
11200
11745
|
try {
|
|
11201
11746
|
this.db.exec(SYMBOLS_FTS_SQL);
|
|
11202
11747
|
this.ftsAvailable = true;
|
|
@@ -11231,6 +11776,18 @@ var IndexStore = class _IndexStore {
|
|
|
11231
11776
|
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
11232
11777
|
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
11233
11778
|
static MAX_SQL_VARS = 900;
|
|
11779
|
+
/**
|
|
11780
|
+
* Correlated predicate: the ref in `refs` and the candidate symbol aliased
|
|
11781
|
+
* `sym` belong to the same language family — or the ref carries no language,
|
|
11782
|
+
* in which case the wildcard bind matches everything.
|
|
11783
|
+
*
|
|
11784
|
+
* Each textual occurrence consumes one `?` bind of {@link LANG_FAMILY_WILDCARD}.
|
|
11785
|
+
*/
|
|
11786
|
+
static FAMILY_MATCH_SQL = `(
|
|
11787
|
+
(SELECT family FROM lang_family WHERE lang = refs.lang) = ?
|
|
11788
|
+
OR (SELECT family FROM lang_family WHERE lang = sym.lang)
|
|
11789
|
+
= (SELECT family FROM lang_family WHERE lang = refs.lang)
|
|
11790
|
+
)`;
|
|
11234
11791
|
/**
|
|
11235
11792
|
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
11236
11793
|
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
@@ -11294,9 +11851,12 @@ var IndexStore = class _IndexStore {
|
|
|
11294
11851
|
const placeholders = chunk.map(() => "?").join(",");
|
|
11295
11852
|
const result = this.stmt(
|
|
11296
11853
|
`UPDATE refs
|
|
11297
|
-
SET to_id = (
|
|
11854
|
+
SET to_id = (
|
|
11855
|
+
SELECT MIN(sym.id) FROM symbols sym
|
|
11856
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
11857
|
+
)
|
|
11298
11858
|
WHERE to_name IN (${placeholders})`
|
|
11299
|
-
).run(...chunk);
|
|
11859
|
+
).run(LANG_FAMILY_WILDCARD, ...chunk);
|
|
11300
11860
|
changes += result.changes ?? 0;
|
|
11301
11861
|
}
|
|
11302
11862
|
return changes;
|
|
@@ -11417,11 +11977,120 @@ var IndexStore = class _IndexStore {
|
|
|
11417
11977
|
).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
|
|
11418
11978
|
});
|
|
11419
11979
|
}
|
|
11420
|
-
getFileMeta(file) {
|
|
11421
|
-
return getFileMetaWithStatement((sql) => this.stmt(sql), file);
|
|
11980
|
+
getFileMeta(file) {
|
|
11981
|
+
return getFileMetaWithStatement((sql) => this.stmt(sql), file);
|
|
11982
|
+
}
|
|
11983
|
+
getAllFileMetas() {
|
|
11984
|
+
return getAllFileMetasWithStatement((sql) => this.stmt(sql));
|
|
11985
|
+
}
|
|
11986
|
+
// ─── Project structure & module resolution ──────────────────────────────────
|
|
11987
|
+
/** Store the Code Atlas grouping label for each indexed file. */
|
|
11988
|
+
setFilePackages(entries) {
|
|
11989
|
+
if (entries.size === 0) return;
|
|
11990
|
+
this.runWithRetry(() => {
|
|
11991
|
+
const update = this.stmt("UPDATE files SET package = ? WHERE file = ?");
|
|
11992
|
+
for (const [file, label] of entries) update.run(label, file);
|
|
11993
|
+
});
|
|
11994
|
+
}
|
|
11995
|
+
/**
|
|
11996
|
+
* Every indexed `namespace`/`module` declaration, for ecosystems whose import
|
|
11997
|
+
* specifiers name a namespace rather than a path (C#, PHP, Elixir, Haskell).
|
|
11998
|
+
* Ordered so the resolver's choice among duplicate declarations is stable.
|
|
11999
|
+
*/
|
|
12000
|
+
getNamespaceDeclarations() {
|
|
12001
|
+
return this.stmt(
|
|
12002
|
+
`SELECT name, file FROM symbols WHERE kind = 'namespace' ORDER BY file, id`
|
|
12003
|
+
).all();
|
|
12004
|
+
}
|
|
12005
|
+
/** `file → package` for every indexed file that has a label. */
|
|
12006
|
+
getFilePackages() {
|
|
12007
|
+
const rows = this.stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
12008
|
+
return new Map(rows.map((row) => [row.file, row.package]));
|
|
11422
12009
|
}
|
|
11423
|
-
|
|
11424
|
-
|
|
12010
|
+
/**
|
|
12011
|
+
* Distinct `(fromFile, lang, module)` triples needing module resolution.
|
|
12012
|
+
*
|
|
12013
|
+
* Distinct rather than per-ref because resolution depends only on these three
|
|
12014
|
+
* values: a file importing the same module twenty times resolves it once.
|
|
12015
|
+
*/
|
|
12016
|
+
getUnresolvedImports(onlyFiles) {
|
|
12017
|
+
const base = `SELECT DISTINCT s.file AS fromFile, r.lang AS lang, r.module AS module
|
|
12018
|
+
FROM refs r
|
|
12019
|
+
JOIN symbols s ON s.id = r.from_id
|
|
12020
|
+
WHERE r.call_type = 'import' AND r.module IS NOT NULL`;
|
|
12021
|
+
if (!onlyFiles?.length) {
|
|
12022
|
+
return this.stmt(base).all();
|
|
12023
|
+
}
|
|
12024
|
+
const out = [];
|
|
12025
|
+
for (let i = 0; i < onlyFiles.length; i += _IndexStore.MAX_SQL_VARS) {
|
|
12026
|
+
const chunk = onlyFiles.slice(i, i + _IndexStore.MAX_SQL_VARS);
|
|
12027
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
12028
|
+
out.push(
|
|
12029
|
+
...this.stmt(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
12030
|
+
);
|
|
12031
|
+
}
|
|
12032
|
+
return out;
|
|
12033
|
+
}
|
|
12034
|
+
/**
|
|
12035
|
+
* Write resolved import targets back onto `refs.to_file`.
|
|
12036
|
+
*
|
|
12037
|
+
* Applied through a temp table and a single UPDATE: one statement per
|
|
12038
|
+
* resolution would mean thousands of round-trips on a first index.
|
|
12039
|
+
*/
|
|
12040
|
+
applyImportResolutions(resolutions) {
|
|
12041
|
+
if (resolutions.length === 0) return 0;
|
|
12042
|
+
return this.runWithRetry(() => {
|
|
12043
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
12044
|
+
this.db.exec(
|
|
12045
|
+
`CREATE TEMP TABLE import_resolution (
|
|
12046
|
+
from_file TEXT NOT NULL,
|
|
12047
|
+
lang TEXT NOT NULL,
|
|
12048
|
+
module TEXT NOT NULL,
|
|
12049
|
+
to_file TEXT NOT NULL
|
|
12050
|
+
)`
|
|
12051
|
+
);
|
|
12052
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / 4));
|
|
12053
|
+
for (let i = 0; i < resolutions.length; i += chunkSize) {
|
|
12054
|
+
const chunk = resolutions.slice(i, i + chunkSize);
|
|
12055
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
12056
|
+
const binds = [];
|
|
12057
|
+
for (const entry of chunk) {
|
|
12058
|
+
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
12059
|
+
}
|
|
12060
|
+
this.stmt(
|
|
12061
|
+
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
12062
|
+
VALUES ${placeholders}`
|
|
12063
|
+
).run(...binds);
|
|
12064
|
+
}
|
|
12065
|
+
this.db.exec(
|
|
12066
|
+
`CREATE INDEX IF NOT EXISTS temp.idx_ir
|
|
12067
|
+
ON import_resolution(module, lang, from_file)`
|
|
12068
|
+
);
|
|
12069
|
+
const result = this.stmt(
|
|
12070
|
+
`UPDATE refs
|
|
12071
|
+
SET to_file = (
|
|
12072
|
+
SELECT ir.to_file
|
|
12073
|
+
FROM temp.import_resolution ir
|
|
12074
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
12075
|
+
WHERE ir.module = refs.module
|
|
12076
|
+
AND ir.lang = refs.lang
|
|
12077
|
+
AND ir.from_file = s.file
|
|
12078
|
+
LIMIT 1
|
|
12079
|
+
)
|
|
12080
|
+
WHERE refs.call_type = 'import'
|
|
12081
|
+
AND refs.module IS NOT NULL
|
|
12082
|
+
AND EXISTS (
|
|
12083
|
+
SELECT 1
|
|
12084
|
+
FROM temp.import_resolution ir
|
|
12085
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
12086
|
+
WHERE ir.module = refs.module
|
|
12087
|
+
AND ir.lang = refs.lang
|
|
12088
|
+
AND ir.from_file = s.file
|
|
12089
|
+
)`
|
|
12090
|
+
).run();
|
|
12091
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
12092
|
+
return result.changes ?? 0;
|
|
12093
|
+
});
|
|
11425
12094
|
}
|
|
11426
12095
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
11427
12096
|
search(query, filter, opts) {
|
|
@@ -11809,9 +12478,12 @@ var IndexStore = class _IndexStore {
|
|
|
11809
12478
|
* Resolve `to_name` → `to_id` for all refs that have a name but no id.
|
|
11810
12479
|
* Call this after all symbols have been inserted to fill in cross-references.
|
|
11811
12480
|
*
|
|
11812
|
-
*
|
|
11813
|
-
* the
|
|
11814
|
-
*
|
|
12481
|
+
* A match additionally requires the referencing ref and the target symbol to
|
|
12482
|
+
* be in the same {@link LangFamily}. Without that guard a name match is a
|
|
12483
|
+
* cross-language accident waiting to happen — `main`, `New`, `Parse` and
|
|
12484
|
+
* `Config` are declared in most languages at once, and each collision draws a
|
|
12485
|
+
* Code Atlas edge between files that never reference each other. Refs stored
|
|
12486
|
+
* without a language keep the old global behaviour via the `'*'` wildcard row.
|
|
11815
12487
|
*/
|
|
11816
12488
|
resolveRefs() {
|
|
11817
12489
|
return this.runWithRetry(() => {
|
|
@@ -11820,20 +12492,35 @@ var IndexStore = class _IndexStore {
|
|
|
11820
12492
|
`UPDATE refs
|
|
11821
12493
|
SET to_id = s.id
|
|
11822
12494
|
FROM (
|
|
11823
|
-
|
|
11824
|
-
|
|
12495
|
+
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
12496
|
+
FROM symbols sym
|
|
12497
|
+
JOIN lang_family lf ON lf.lang = sym.lang
|
|
12498
|
+
GROUP BY sym.name, lf.family
|
|
12499
|
+
UNION ALL
|
|
12500
|
+
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
12501
|
+
FROM symbols sym
|
|
12502
|
+
GROUP BY sym.name
|
|
12503
|
+
) AS s,
|
|
12504
|
+
lang_family AS rf
|
|
11825
12505
|
WHERE refs.to_id IS NULL
|
|
11826
12506
|
AND refs.to_name IS NOT NULL
|
|
11827
|
-
AND
|
|
12507
|
+
AND rf.lang = refs.lang
|
|
12508
|
+
AND s.name = refs.to_name
|
|
12509
|
+
AND s.family = rf.family`
|
|
11828
12510
|
).run();
|
|
11829
12511
|
return result.changes ?? 0;
|
|
11830
12512
|
} catch {
|
|
11831
12513
|
const result = this.stmt(
|
|
11832
12514
|
`UPDATE refs SET to_id = (
|
|
11833
|
-
SELECT id FROM symbols
|
|
12515
|
+
SELECT sym.id FROM symbols sym
|
|
12516
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
12517
|
+
ORDER BY sym.id LIMIT 1
|
|
11834
12518
|
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
11835
|
-
AND
|
|
11836
|
-
|
|
12519
|
+
AND EXISTS (
|
|
12520
|
+
SELECT 1 FROM symbols sym
|
|
12521
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
12522
|
+
)`
|
|
12523
|
+
).run(LANG_FAMILY_WILDCARD, LANG_FAMILY_WILDCARD);
|
|
11837
12524
|
return result.changes ?? 0;
|
|
11838
12525
|
}
|
|
11839
12526
|
});
|
|
@@ -12022,21 +12709,23 @@ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
|
12022
12709
|
var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
|
|
12023
12710
|
var buildIdCache;
|
|
12024
12711
|
function projectIndexServerBuildId(entrypoint) {
|
|
12025
|
-
const
|
|
12712
|
+
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
12713
|
+
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
12714
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath(cleanHref) : path17.resolve(cleanHref);
|
|
12026
12715
|
try {
|
|
12027
|
-
const stat18 =
|
|
12716
|
+
const stat18 = fs12.statSync(file);
|
|
12028
12717
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat18.mtimeMs && buildIdCache.size === stat18.size) {
|
|
12029
12718
|
return buildIdCache.buildId;
|
|
12030
12719
|
}
|
|
12031
|
-
const buildId = createHash4("sha256").update(
|
|
12720
|
+
const buildId = createHash4("sha256").update(fs12.readFileSync(file)).digest("hex").slice(0, 24);
|
|
12032
12721
|
buildIdCache = { file, mtimeMs: stat18.mtimeMs, size: stat18.size, buildId };
|
|
12033
12722
|
return buildId;
|
|
12034
12723
|
} catch {
|
|
12035
|
-
return `unreadable:${
|
|
12724
|
+
return `unreadable:${path17.basename(file)}`;
|
|
12036
12725
|
}
|
|
12037
12726
|
}
|
|
12038
12727
|
function normalizeLocalPath(value) {
|
|
12039
|
-
const resolved =
|
|
12728
|
+
const resolved = path17.resolve(value);
|
|
12040
12729
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
12041
12730
|
}
|
|
12042
12731
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -12048,11 +12737,11 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
12048
12737
|
if (process.platform === "win32") {
|
|
12049
12738
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
12050
12739
|
}
|
|
12051
|
-
return
|
|
12740
|
+
return path17.join(os5.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
12052
12741
|
}
|
|
12053
12742
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
12054
|
-
return
|
|
12055
|
-
|
|
12743
|
+
return path17.join(
|
|
12744
|
+
path17.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
12056
12745
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
12057
12746
|
);
|
|
12058
12747
|
}
|
|
@@ -12092,7 +12781,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
12092
12781
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
12093
12782
|
try {
|
|
12094
12783
|
const url = new URL(rel, import.meta.url);
|
|
12095
|
-
if (url.protocol === "file:" &&
|
|
12784
|
+
if (url.protocol === "file:" && fs13.existsSync(fileURLToPath2(url))) {
|
|
12096
12785
|
builtUrl = url;
|
|
12097
12786
|
break;
|
|
12098
12787
|
}
|
|
@@ -12349,7 +13038,7 @@ var ProjectServerConnection = class {
|
|
|
12349
13038
|
currentAuthToken() {
|
|
12350
13039
|
if (this.authToken === void 0) {
|
|
12351
13040
|
try {
|
|
12352
|
-
const raw =
|
|
13041
|
+
const raw = fs13.readFileSync(
|
|
12353
13042
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
12354
13043
|
"utf8"
|
|
12355
13044
|
);
|
|
@@ -12614,7 +13303,7 @@ var ProjectServerConnection = class {
|
|
|
12614
13303
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
12615
13304
|
if (process.platform !== "win32") {
|
|
12616
13305
|
try {
|
|
12617
|
-
|
|
13306
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
12618
13307
|
} catch {
|
|
12619
13308
|
}
|
|
12620
13309
|
}
|
|
@@ -12638,8 +13327,8 @@ var ProjectServerConnection = class {
|
|
|
12638
13327
|
process.kill(pid);
|
|
12639
13328
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
12640
13329
|
try {
|
|
12641
|
-
const metadata = JSON.parse(
|
|
12642
|
-
if (metadata.pid === pid)
|
|
13330
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
13331
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
12643
13332
|
} catch {
|
|
12644
13333
|
}
|
|
12645
13334
|
return true;
|
|
@@ -12709,7 +13398,7 @@ import { Worker } from "node:worker_threads";
|
|
|
12709
13398
|
|
|
12710
13399
|
// src/codebase-index/indexer.ts
|
|
12711
13400
|
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
12712
|
-
import { execFile
|
|
13401
|
+
import { execFile } from "node:child_process";
|
|
12713
13402
|
import * as fs17 from "node:fs/promises";
|
|
12714
13403
|
import { availableParallelism } from "node:os";
|
|
12715
13404
|
import * as path23 from "node:path";
|
|
@@ -12720,8 +13409,8 @@ import {
|
|
|
12720
13409
|
} from "@wrongstack/core/utils";
|
|
12721
13410
|
|
|
12722
13411
|
// src/codebase-index/gitignore.ts
|
|
12723
|
-
import * as
|
|
12724
|
-
import * as
|
|
13412
|
+
import * as fs14 from "node:fs/promises";
|
|
13413
|
+
import * as path18 from "node:path";
|
|
12725
13414
|
import { compileGlob } from "@wrongstack/core/utils";
|
|
12726
13415
|
function globBody(glob) {
|
|
12727
13416
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
@@ -12767,7 +13456,7 @@ function compileGitignore(lines) {
|
|
|
12767
13456
|
async function loadGitignoreMatcher(projectRoot) {
|
|
12768
13457
|
let lines = [];
|
|
12769
13458
|
try {
|
|
12770
|
-
const raw = await
|
|
13459
|
+
const raw = await fs14.readFile(path18.join(projectRoot, ".gitignore"), "utf8");
|
|
12771
13460
|
lines = raw.split("\n");
|
|
12772
13461
|
} catch {
|
|
12773
13462
|
}
|
|
@@ -12777,8 +13466,434 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
12777
13466
|
// src/codebase-index/indexer.ts
|
|
12778
13467
|
init_languages2();
|
|
12779
13468
|
|
|
13469
|
+
// src/codebase-index/module-resolver.ts
|
|
13470
|
+
init_languages2();
|
|
13471
|
+
import * as path19 from "node:path";
|
|
13472
|
+
var EXTENSIONS = {
|
|
13473
|
+
js: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"],
|
|
13474
|
+
py: [".py", ".pyi"],
|
|
13475
|
+
rs: [".rs"],
|
|
13476
|
+
jvm: [".java", ".kt", ".scala"],
|
|
13477
|
+
c: [".h", ".hpp", ".hh", ".hxx", ".c", ".cpp", ".cc", ".cxx"],
|
|
13478
|
+
ruby: [".rb"],
|
|
13479
|
+
go: [".go"]
|
|
13480
|
+
};
|
|
13481
|
+
var DIRECTORY_ENTRIES = {
|
|
13482
|
+
js: ["index"],
|
|
13483
|
+
py: ["__init__"],
|
|
13484
|
+
rs: ["mod"],
|
|
13485
|
+
ruby: ["index"]
|
|
13486
|
+
};
|
|
13487
|
+
function normalizeNamespace(value) {
|
|
13488
|
+
return value.replace(/::|[\\/]/g, ".").replace(/^\.+|\.+$/g, "").toLowerCase();
|
|
13489
|
+
}
|
|
13490
|
+
var ModuleResolver = class {
|
|
13491
|
+
structure;
|
|
13492
|
+
/** Lowercased portable path → the path as indexed (case is preserved). */
|
|
13493
|
+
byPath;
|
|
13494
|
+
/** Lowercased portable directory → files directly inside it, as indexed. */
|
|
13495
|
+
byDir;
|
|
13496
|
+
/** Normalized namespace → the file declaring it (first by path, stable). */
|
|
13497
|
+
byNamespace;
|
|
13498
|
+
constructor(structure, files, namespaces = []) {
|
|
13499
|
+
this.structure = structure;
|
|
13500
|
+
this.byPath = /* @__PURE__ */ new Map();
|
|
13501
|
+
this.byDir = /* @__PURE__ */ new Map();
|
|
13502
|
+
this.byNamespace = /* @__PURE__ */ new Map();
|
|
13503
|
+
const dirsByKey = /* @__PURE__ */ new Map();
|
|
13504
|
+
for (const file of files) {
|
|
13505
|
+
const portable = toPortablePath(file);
|
|
13506
|
+
const pathKey = portable.toLowerCase();
|
|
13507
|
+
const priorPath = this.byPath.get(pathKey);
|
|
13508
|
+
if (priorPath !== void 0 && priorPath !== file) this.byPath.delete(pathKey);
|
|
13509
|
+
else this.byPath.set(pathKey, file);
|
|
13510
|
+
const dir = path19.posix.dirname(portable);
|
|
13511
|
+
const dirKey = dir.toLowerCase();
|
|
13512
|
+
const knownDir = dirsByKey.get(dirKey);
|
|
13513
|
+
if (knownDir === void 0) {
|
|
13514
|
+
dirsByKey.set(dirKey, dir);
|
|
13515
|
+
this.byDir.set(dirKey, [file]);
|
|
13516
|
+
} else if (knownDir === dir) {
|
|
13517
|
+
this.byDir.get(dirKey)?.push(file);
|
|
13518
|
+
} else {
|
|
13519
|
+
dirsByKey.delete(dirKey);
|
|
13520
|
+
this.byDir.delete(dirKey);
|
|
13521
|
+
}
|
|
13522
|
+
}
|
|
13523
|
+
for (const { name, file } of namespaces) {
|
|
13524
|
+
const lang = detectLang(file);
|
|
13525
|
+
if (!lang) continue;
|
|
13526
|
+
const key = `${languageFamily(lang)}:${normalizeNamespace(name)}`;
|
|
13527
|
+
if (normalizeNamespace(name) && !this.byNamespace.has(key)) {
|
|
13528
|
+
this.byNamespace.set(key, file);
|
|
13529
|
+
}
|
|
13530
|
+
}
|
|
13531
|
+
}
|
|
13532
|
+
/**
|
|
13533
|
+
* Resolve `specifier` as written in `fromFile`.
|
|
13534
|
+
* Returns the indexed target path, or `undefined` when it is external or
|
|
13535
|
+
* cannot be located.
|
|
13536
|
+
*/
|
|
13537
|
+
resolve(fromFile, lang, specifier) {
|
|
13538
|
+
const spec = specifier.trim().replace(/\\/g, "/");
|
|
13539
|
+
if (!spec) return void 0;
|
|
13540
|
+
const from = toPortablePath(fromFile);
|
|
13541
|
+
switch (languageFamily(lang)) {
|
|
13542
|
+
case "js":
|
|
13543
|
+
return this.resolveJs(from, spec);
|
|
13544
|
+
case "go":
|
|
13545
|
+
return this.resolveGo(spec);
|
|
13546
|
+
case "py":
|
|
13547
|
+
return this.resolvePython(from, spec);
|
|
13548
|
+
case "rs":
|
|
13549
|
+
return this.resolveRust(from, spec);
|
|
13550
|
+
case "jvm":
|
|
13551
|
+
return this.resolveJvm(spec);
|
|
13552
|
+
case "c":
|
|
13553
|
+
return this.resolveInclude(from, spec);
|
|
13554
|
+
case "ruby":
|
|
13555
|
+
return this.resolveRuby(from, spec);
|
|
13556
|
+
case "dotnet":
|
|
13557
|
+
case "php":
|
|
13558
|
+
case "elixir":
|
|
13559
|
+
case "haskell":
|
|
13560
|
+
return this.resolveNamespace(lang, spec);
|
|
13561
|
+
default:
|
|
13562
|
+
return void 0;
|
|
13563
|
+
}
|
|
13564
|
+
}
|
|
13565
|
+
/**
|
|
13566
|
+
* Resolve a namespace specifier to the file declaring it.
|
|
13567
|
+
*
|
|
13568
|
+
* Tried whole first, then with the trailing segment dropped: `using Foo.Bar`
|
|
13569
|
+
* names a namespace outright, while PHP's `use App\Models\User` names a
|
|
13570
|
+
* *class* inside `App\Models`, so the prefix is what was declared.
|
|
13571
|
+
*/
|
|
13572
|
+
resolveNamespace(lang, spec) {
|
|
13573
|
+
const family = languageFamily(lang);
|
|
13574
|
+
const normalized = normalizeNamespace(spec);
|
|
13575
|
+
const exact = this.byNamespace.get(`${family}:${normalized}`);
|
|
13576
|
+
if (exact) return exact;
|
|
13577
|
+
const segments = normalized.split(".").filter(Boolean);
|
|
13578
|
+
if (segments.length < 2) return void 0;
|
|
13579
|
+
return this.byNamespace.get(`${family}:${segments.slice(0, -1).join(".")}`);
|
|
13580
|
+
}
|
|
13581
|
+
// ─── Lookup primitives ──────────────────────────────────────────────────────
|
|
13582
|
+
lookup(candidate) {
|
|
13583
|
+
return this.byPath.get(path19.posix.normalize(candidate).toLowerCase());
|
|
13584
|
+
}
|
|
13585
|
+
/**
|
|
13586
|
+
* Try `base` verbatim, then `base` + each extension, then each directory
|
|
13587
|
+
* entry point inside `base`.
|
|
13588
|
+
*/
|
|
13589
|
+
lookupWithExtensions(base, family) {
|
|
13590
|
+
const direct = this.lookup(base);
|
|
13591
|
+
if (direct) return direct;
|
|
13592
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
13593
|
+
const suffix = path19.posix.extname(base);
|
|
13594
|
+
const stem = suffix && extensions.includes(suffix) ? base.slice(0, -suffix.length) : base;
|
|
13595
|
+
for (const ext of extensions) {
|
|
13596
|
+
const hit = this.lookup(`${stem}${ext}`);
|
|
13597
|
+
if (hit) return hit;
|
|
13598
|
+
}
|
|
13599
|
+
for (const entry of DIRECTORY_ENTRIES[family] ?? []) {
|
|
13600
|
+
for (const ext of extensions) {
|
|
13601
|
+
const hit = this.lookup(path19.posix.join(base, `${entry}${ext}`));
|
|
13602
|
+
if (hit) return hit;
|
|
13603
|
+
}
|
|
13604
|
+
}
|
|
13605
|
+
return void 0;
|
|
13606
|
+
}
|
|
13607
|
+
/**
|
|
13608
|
+
* A representative indexed file inside `dir`, for ecosystems whose import
|
|
13609
|
+
* unit is a directory rather than a file (Go packages, JVM wildcard imports).
|
|
13610
|
+
*
|
|
13611
|
+
* The choice is deterministic — a file named after the directory, else the
|
|
13612
|
+
* first by name — so the same import always produces the same edge. Package
|
|
13613
|
+
* grouping is unaffected either way: every file in the directory carries the
|
|
13614
|
+
* same package label, so the package-level edge is exact regardless of which
|
|
13615
|
+
* member represents it.
|
|
13616
|
+
*/
|
|
13617
|
+
representativeIn(dir, family) {
|
|
13618
|
+
const members = this.byDir.get(path19.posix.normalize(dir).toLowerCase());
|
|
13619
|
+
if (!members?.length) return void 0;
|
|
13620
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
13621
|
+
const eligible = members.filter((file) => extensions.includes(path19.posix.extname(toPortablePath(file)).toLowerCase())).sort((a, b) => toPortablePath(a).localeCompare(toPortablePath(b)));
|
|
13622
|
+
if (eligible.length === 0) return void 0;
|
|
13623
|
+
const base = path19.posix.basename(path19.posix.normalize(dir)).toLowerCase();
|
|
13624
|
+
const named = eligible.find(
|
|
13625
|
+
(file) => path19.posix.basename(toPortablePath(file)).split(".")[0]?.toLowerCase() === base
|
|
13626
|
+
);
|
|
13627
|
+
return named ?? eligible[0];
|
|
13628
|
+
}
|
|
13629
|
+
// ─── Per-family resolution ──────────────────────────────────────────────────
|
|
13630
|
+
/** Relative specifiers, then workspace package names and their subpaths. */
|
|
13631
|
+
resolveJs(fromFile, spec) {
|
|
13632
|
+
if (spec.startsWith(".")) {
|
|
13633
|
+
const absolute = path19.posix.join(path19.posix.dirname(fromFile), spec);
|
|
13634
|
+
return this.lookupWithExtensions(absolute, "js");
|
|
13635
|
+
}
|
|
13636
|
+
const owner2 = this.structure.roots.find(
|
|
13637
|
+
(root) => root.kind === "npm" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
13638
|
+
);
|
|
13639
|
+
if (!owner2?.importPath) return void 0;
|
|
13640
|
+
const subpath = spec.slice(owner2.importPath.length).replace(/^\//, "");
|
|
13641
|
+
if (!subpath) {
|
|
13642
|
+
return this.lookupWithExtensions(path19.posix.join(owner2.dir, "src/index"), "js") ?? this.lookupWithExtensions(path19.posix.join(owner2.dir, "index"), "js");
|
|
13643
|
+
}
|
|
13644
|
+
return this.lookupWithExtensions(path19.posix.join(owner2.dir, subpath), "js") ?? this.lookupWithExtensions(path19.posix.join(owner2.dir, "src", subpath), "js");
|
|
13645
|
+
}
|
|
13646
|
+
/** Go import paths are absolute module paths; a package is a directory. */
|
|
13647
|
+
resolveGo(spec) {
|
|
13648
|
+
const owner2 = this.structure.roots.find(
|
|
13649
|
+
(root) => root.kind === "go" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
13650
|
+
);
|
|
13651
|
+
if (!owner2?.importPath) return void 0;
|
|
13652
|
+
const subpath = spec.slice(owner2.importPath.length).replace(/^\//, "");
|
|
13653
|
+
return this.representativeIn(path19.posix.join(owner2.dir, subpath), "go");
|
|
13654
|
+
}
|
|
13655
|
+
/**
|
|
13656
|
+
* `import a.b.c` / `from a.b import c`, plus PEP 328 relative imports whose
|
|
13657
|
+
* leading dots the extractor preserves (`.sibling`, `..parent.mod`).
|
|
13658
|
+
*/
|
|
13659
|
+
resolvePython(fromFile, spec) {
|
|
13660
|
+
const leadingDots = /^\.*/.exec(spec)?.[0].length ?? 0;
|
|
13661
|
+
if (leadingDots > 0) {
|
|
13662
|
+
let base = path19.posix.dirname(fromFile);
|
|
13663
|
+
for (let i = 1; i < leadingDots; i++) base = path19.posix.dirname(base);
|
|
13664
|
+
const rest = spec.slice(leadingDots).split(".").filter(Boolean);
|
|
13665
|
+
return this.lookupWithExtensions(path19.posix.join(base, ...rest), "py");
|
|
13666
|
+
}
|
|
13667
|
+
const segments = spec.split(".").filter(Boolean);
|
|
13668
|
+
if (segments.length === 0) return void 0;
|
|
13669
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "python").flatMap((root) => root.sourceRoots);
|
|
13670
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
13671
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, ...segments), "py");
|
|
13672
|
+
if (hit) return hit;
|
|
13673
|
+
if (segments.length > 1) {
|
|
13674
|
+
const parent = this.lookupWithExtensions(
|
|
13675
|
+
path19.posix.join(base, ...segments.slice(0, -1)),
|
|
13676
|
+
"py"
|
|
13677
|
+
);
|
|
13678
|
+
if (parent) return parent;
|
|
13679
|
+
}
|
|
13680
|
+
}
|
|
13681
|
+
return void 0;
|
|
13682
|
+
}
|
|
13683
|
+
/** `use crate::a::b`, `use super::x`, `use self::y`, `use other_crate::z`. */
|
|
13684
|
+
resolveRust(fromFile, spec) {
|
|
13685
|
+
const segments = spec.split("::").filter(Boolean);
|
|
13686
|
+
if (segments.length === 0) return void 0;
|
|
13687
|
+
const head = segments[0];
|
|
13688
|
+
if (head === "self" || head === "super") {
|
|
13689
|
+
let base = path19.posix.dirname(fromFile);
|
|
13690
|
+
for (const segment of segments) {
|
|
13691
|
+
if (segment === "super") base = path19.posix.dirname(base);
|
|
13692
|
+
else if (segment !== "self") break;
|
|
13693
|
+
}
|
|
13694
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
13695
|
+
return this.lookupWithExtensions(path19.posix.join(base, ...rest2), "rs");
|
|
13696
|
+
}
|
|
13697
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
13698
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
13699
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
13700
|
+
);
|
|
13701
|
+
if (!crate) {
|
|
13702
|
+
return this.lookupWithExtensions(
|
|
13703
|
+
path19.posix.join(path19.posix.dirname(fromFile), ...segments),
|
|
13704
|
+
"rs"
|
|
13705
|
+
);
|
|
13706
|
+
}
|
|
13707
|
+
const rest = segments.slice(1);
|
|
13708
|
+
for (const base of crate.sourceRoots) {
|
|
13709
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path19.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
13710
|
+
const exact = this.lookupWithExtensions(path19.posix.join(base, ...rest), "rs");
|
|
13711
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path19.posix.join(base, "lib"), "rs");
|
|
13712
|
+
if (hit) return hit;
|
|
13713
|
+
}
|
|
13714
|
+
return void 0;
|
|
13715
|
+
}
|
|
13716
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
13717
|
+
resolveJvm(spec) {
|
|
13718
|
+
const segments = spec.split(".").filter(Boolean);
|
|
13719
|
+
if (segments.length === 0) return void 0;
|
|
13720
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
13721
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
13722
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
13723
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
13724
|
+
const target = path19.posix.join(base, ...parts);
|
|
13725
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
13726
|
+
if (hit) return hit;
|
|
13727
|
+
}
|
|
13728
|
+
return void 0;
|
|
13729
|
+
}
|
|
13730
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
13731
|
+
resolveInclude(fromFile, spec) {
|
|
13732
|
+
const relative12 = this.lookupWithExtensions(
|
|
13733
|
+
path19.posix.join(path19.posix.dirname(fromFile), spec),
|
|
13734
|
+
"c"
|
|
13735
|
+
);
|
|
13736
|
+
if (relative12) return relative12;
|
|
13737
|
+
for (const base of [
|
|
13738
|
+
path19.posix.join(this.structure.projectRoot, "include"),
|
|
13739
|
+
this.structure.projectRoot
|
|
13740
|
+
]) {
|
|
13741
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, spec), "c");
|
|
13742
|
+
if (hit) return hit;
|
|
13743
|
+
}
|
|
13744
|
+
return void 0;
|
|
13745
|
+
}
|
|
13746
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
13747
|
+
resolveRuby(fromFile, spec) {
|
|
13748
|
+
const relative12 = this.lookupWithExtensions(
|
|
13749
|
+
path19.posix.join(path19.posix.dirname(fromFile), spec),
|
|
13750
|
+
"ruby"
|
|
13751
|
+
);
|
|
13752
|
+
if (relative12) return relative12;
|
|
13753
|
+
for (const base of [
|
|
13754
|
+
path19.posix.join(this.structure.projectRoot, "lib"),
|
|
13755
|
+
this.structure.projectRoot
|
|
13756
|
+
]) {
|
|
13757
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, spec), "ruby");
|
|
13758
|
+
if (hit) return hit;
|
|
13759
|
+
}
|
|
13760
|
+
return void 0;
|
|
13761
|
+
}
|
|
13762
|
+
};
|
|
13763
|
+
|
|
13764
|
+
// src/codebase-index/import-extractor.ts
|
|
13765
|
+
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
13766
|
+
var IMPORT_MAX_PER_FILE = 400;
|
|
13767
|
+
var DOTTED_IMPORT = [
|
|
13768
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
13769
|
+
];
|
|
13770
|
+
var LANG_IMPORTS = {
|
|
13771
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
13772
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
13773
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
13774
|
+
go: [
|
|
13775
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
13776
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
13777
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
13778
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
13779
|
+
],
|
|
13780
|
+
py: [
|
|
13781
|
+
{ re: /^[ \t]*import\s+([\w.]+)/gm },
|
|
13782
|
+
{ re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }
|
|
13783
|
+
],
|
|
13784
|
+
rs: [
|
|
13785
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
13786
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
13787
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
13788
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
13789
|
+
],
|
|
13790
|
+
java: DOTTED_IMPORT,
|
|
13791
|
+
kotlin: DOTTED_IMPORT,
|
|
13792
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
13793
|
+
csharp: [
|
|
13794
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
13795
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
13796
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
13797
|
+
],
|
|
13798
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
13799
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
13800
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
13801
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
13802
|
+
php: [
|
|
13803
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
13804
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
13805
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
13806
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
13807
|
+
],
|
|
13808
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
13809
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
13810
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
13811
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
13812
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
13813
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
13814
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
13815
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
13816
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
13817
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
13818
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
13819
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
13820
|
+
html: [
|
|
13821
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
13822
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
13823
|
+
],
|
|
13824
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
13825
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
13826
|
+
};
|
|
13827
|
+
function lastSegment(specifier) {
|
|
13828
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
13829
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
13830
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
13831
|
+
if (last === "*" || last === "_") {
|
|
13832
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
13833
|
+
}
|
|
13834
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
13835
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
13836
|
+
return dotted[dotted.length - 1] ?? last;
|
|
13837
|
+
}
|
|
13838
|
+
function newlineOffsets(content) {
|
|
13839
|
+
const offsets = [];
|
|
13840
|
+
for (let i = 0; i < content.length; i++) {
|
|
13841
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
13842
|
+
}
|
|
13843
|
+
return offsets;
|
|
13844
|
+
}
|
|
13845
|
+
function lineAt(offsets, index) {
|
|
13846
|
+
let low = 0;
|
|
13847
|
+
let high = offsets.length;
|
|
13848
|
+
while (low < high) {
|
|
13849
|
+
const mid = low + high >>> 1;
|
|
13850
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
13851
|
+
else high = mid;
|
|
13852
|
+
}
|
|
13853
|
+
return low + 1;
|
|
13854
|
+
}
|
|
13855
|
+
function hasImportPatterns(lang) {
|
|
13856
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
13857
|
+
}
|
|
13858
|
+
function extractImports(opts) {
|
|
13859
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
13860
|
+
if (!patterns || !opts.content) return [];
|
|
13861
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
13862
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
13863
|
+
const refs = [];
|
|
13864
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13865
|
+
const offsets = newlineOffsets(content);
|
|
13866
|
+
for (const pattern of patterns) {
|
|
13867
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
13868
|
+
for (const match of content.matchAll(re)) {
|
|
13869
|
+
if (refs.length >= limit) return refs;
|
|
13870
|
+
const specifier = match[1]?.trim();
|
|
13871
|
+
if (!specifier) continue;
|
|
13872
|
+
const module = specifier;
|
|
13873
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
13874
|
+
if (!toName) continue;
|
|
13875
|
+
const key = `${module}\0${toName}`;
|
|
13876
|
+
if (seen.has(key)) continue;
|
|
13877
|
+
seen.add(key);
|
|
13878
|
+
refs.push({
|
|
13879
|
+
fromId: 0,
|
|
13880
|
+
toName,
|
|
13881
|
+
callType: "import",
|
|
13882
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
13883
|
+
lang: opts.lang,
|
|
13884
|
+
module
|
|
13885
|
+
});
|
|
13886
|
+
}
|
|
13887
|
+
}
|
|
13888
|
+
return refs;
|
|
13889
|
+
}
|
|
13890
|
+
|
|
12780
13891
|
// src/codebase-index/parser-dispatch.ts
|
|
12781
13892
|
async function parseFileContent(file, content, lang) {
|
|
13893
|
+
const parsed = await dispatch(file, content, lang);
|
|
13894
|
+
return withRelations(parsed, content, lang);
|
|
13895
|
+
}
|
|
13896
|
+
async function dispatch(file, content, lang) {
|
|
12782
13897
|
switch (lang) {
|
|
12783
13898
|
case "ts":
|
|
12784
13899
|
case "tsx":
|
|
@@ -12813,6 +13928,13 @@ async function parseFileContent(file, content, lang) {
|
|
|
12813
13928
|
}
|
|
12814
13929
|
}
|
|
12815
13930
|
}
|
|
13931
|
+
function withRelations(parsed, content, lang) {
|
|
13932
|
+
let refs = parsed.refs ?? [];
|
|
13933
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
13934
|
+
refs = extractImports({ content, lang });
|
|
13935
|
+
}
|
|
13936
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
13937
|
+
}
|
|
12816
13938
|
|
|
12817
13939
|
// src/codebase-index/indexer.ts
|
|
12818
13940
|
var YIELD_EVERY_N = 50;
|
|
@@ -12849,7 +13971,7 @@ function normalizeComparablePath(value) {
|
|
|
12849
13971
|
}
|
|
12850
13972
|
function gitOutput(projectRoot, args) {
|
|
12851
13973
|
return new Promise((resolve16, reject) => {
|
|
12852
|
-
|
|
13974
|
+
execFile(
|
|
12853
13975
|
"git",
|
|
12854
13976
|
["-C", projectRoot, ...args],
|
|
12855
13977
|
{
|
|
@@ -12980,13 +14102,40 @@ function assignRefsToSymbols2(refs, symbols) {
|
|
|
12980
14102
|
}
|
|
12981
14103
|
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
12982
14104
|
if (!owner2 || owner2.id <= 0) continue;
|
|
12983
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
14105
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
12984
14106
|
if (seen.has(key)) continue;
|
|
12985
14107
|
seen.add(key);
|
|
12986
14108
|
assigned.push({ ...ref, fromId: owner2.id });
|
|
12987
14109
|
}
|
|
12988
14110
|
return assigned;
|
|
12989
14111
|
}
|
|
14112
|
+
async function resolveProjectRelations(store, projectRoot, opts) {
|
|
14113
|
+
if (opts.signal?.aborted) return;
|
|
14114
|
+
try {
|
|
14115
|
+
const indexedFiles = store.getAllFileMetas().map((meta) => meta.file);
|
|
14116
|
+
if (indexedFiles.length === 0) return;
|
|
14117
|
+
const structure = await detectModuleRoots(projectRoot, indexedFiles);
|
|
14118
|
+
if (opts.signal?.aborted) return;
|
|
14119
|
+
store.setFilePackages(assignPackageLabels(structure, indexedFiles));
|
|
14120
|
+
const resolver = new ModuleResolver(
|
|
14121
|
+
structure,
|
|
14122
|
+
indexedFiles,
|
|
14123
|
+
store.getNamespaceDeclarations()
|
|
14124
|
+
);
|
|
14125
|
+
const pending2 = store.getUnresolvedImports(opts.onlyFiles);
|
|
14126
|
+
const resolutions = [];
|
|
14127
|
+
for (const entry of pending2) {
|
|
14128
|
+
const toFile = resolver.resolve(entry.fromFile, entry.lang, entry.module);
|
|
14129
|
+
if (toFile && toFile !== entry.fromFile) {
|
|
14130
|
+
resolutions.push({ ...entry, toFile });
|
|
14131
|
+
}
|
|
14132
|
+
}
|
|
14133
|
+
if (opts.signal?.aborted) return;
|
|
14134
|
+
store.applyImportResolutions(resolutions);
|
|
14135
|
+
} catch (err) {
|
|
14136
|
+
opts.errors.push(`relation resolution: ${err instanceof Error ? err.message : String(err)}`);
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
12990
14139
|
async function runIndexerWithStore(store, opts) {
|
|
12991
14140
|
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
12992
14141
|
const relationGraphVersion = "2";
|
|
@@ -13231,6 +14380,14 @@ async function runIndexerWithStore(store, opts) {
|
|
|
13231
14380
|
}
|
|
13232
14381
|
}
|
|
13233
14382
|
if (needsFullRefResolution) store.resolveRefs();
|
|
14383
|
+
await resolveProjectRelations(store, projectRoot, {
|
|
14384
|
+
// A watcher run re-resolves only what it touched; a full run (or a contract
|
|
14385
|
+
// bump) re-resolves everything, because a newly indexed file can be the
|
|
14386
|
+
// target of imports written long before it.
|
|
14387
|
+
onlyFiles: needsFullRefResolution ? void 0 : opts.files,
|
|
14388
|
+
errors,
|
|
14389
|
+
signal
|
|
14390
|
+
});
|
|
13234
14391
|
store.setMetadata("ref_resolution_version", refResolutionVersion);
|
|
13235
14392
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
13236
14393
|
if (!opts.files || filesIndexed >= 50) store.optimize();
|
|
@@ -14637,17 +15794,17 @@ import {
|
|
|
14637
15794
|
} from "@wrongstack/core/design";
|
|
14638
15795
|
async function resolveReal(p) {
|
|
14639
15796
|
const resolved = path25.resolve(p);
|
|
14640
|
-
let
|
|
15797
|
+
let probe = resolved;
|
|
14641
15798
|
const missing = [];
|
|
14642
15799
|
for (; ; ) {
|
|
14643
15800
|
try {
|
|
14644
|
-
return path25.resolve(await fs20.realpath(
|
|
15801
|
+
return path25.resolve(await fs20.realpath(probe), ...missing);
|
|
14645
15802
|
} catch (err) {
|
|
14646
15803
|
if (err.code === "ENOENT") {
|
|
14647
|
-
const parent = path25.dirname(
|
|
14648
|
-
if (parent ===
|
|
14649
|
-
missing.unshift(path25.basename(
|
|
14650
|
-
|
|
15804
|
+
const parent = path25.dirname(probe);
|
|
15805
|
+
if (parent === probe) return resolved;
|
|
15806
|
+
missing.unshift(path25.basename(probe));
|
|
15807
|
+
probe = parent;
|
|
14651
15808
|
continue;
|
|
14652
15809
|
}
|
|
14653
15810
|
return resolved;
|
|
@@ -14922,7 +16079,7 @@ Replace off-palette colors with kit tokens (or the materialized CSS vars / token
|
|
|
14922
16079
|
|
|
14923
16080
|
// src/diff.ts
|
|
14924
16081
|
init_util();
|
|
14925
|
-
import { spawn as
|
|
16082
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
14926
16083
|
import { statSync as statSync3 } from "node:fs";
|
|
14927
16084
|
import * as fs21 from "node:fs/promises";
|
|
14928
16085
|
import * as path26 from "node:path";
|
|
@@ -15025,7 +16182,7 @@ function runGit(args, cwd, signal) {
|
|
|
15025
16182
|
return new Promise((resolve16) => {
|
|
15026
16183
|
let stdout = "";
|
|
15027
16184
|
let stderr = "";
|
|
15028
|
-
const child =
|
|
16185
|
+
const child = spawn7("git", args, {
|
|
15029
16186
|
cwd,
|
|
15030
16187
|
signal,
|
|
15031
16188
|
env: buildChildEnv3(),
|
|
@@ -15236,7 +16393,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
15236
16393
|
|
|
15237
16394
|
// src/e2e.ts
|
|
15238
16395
|
init_util();
|
|
15239
|
-
import { open, readdir as
|
|
16396
|
+
import { open, readdir as readdir7 } from "node:fs/promises";
|
|
15240
16397
|
import * as path27 from "node:path";
|
|
15241
16398
|
async function readBoundedText(filePath, maxBytes) {
|
|
15242
16399
|
let handle;
|
|
@@ -15354,7 +16511,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
15354
16511
|
}
|
|
15355
16512
|
let entries;
|
|
15356
16513
|
try {
|
|
15357
|
-
entries = await
|
|
16514
|
+
entries = await readdir7(current.directory, { withFileTypes: true });
|
|
15358
16515
|
} catch {
|
|
15359
16516
|
continue;
|
|
15360
16517
|
}
|
|
@@ -15413,7 +16570,7 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
15413
16570
|
while (true) {
|
|
15414
16571
|
const names = /* @__PURE__ */ new Set();
|
|
15415
16572
|
try {
|
|
15416
|
-
for (const entry of await
|
|
16573
|
+
for (const entry of await readdir7(directory)) names.add(entry);
|
|
15417
16574
|
} catch {
|
|
15418
16575
|
}
|
|
15419
16576
|
if (names.has("pnpm-lock.yaml")) return "pnpm";
|
|
@@ -15481,7 +16638,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
15481
16638
|
if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };
|
|
15482
16639
|
let entries;
|
|
15483
16640
|
try {
|
|
15484
|
-
entries = await
|
|
16641
|
+
entries = await readdir7(directory, { withFileTypes: true });
|
|
15485
16642
|
} catch {
|
|
15486
16643
|
continue;
|
|
15487
16644
|
}
|
|
@@ -15682,7 +16839,7 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
15682
16839
|
const exact = [];
|
|
15683
16840
|
let idx = fileLf.indexOf(oldLf);
|
|
15684
16841
|
while (idx !== -1) {
|
|
15685
|
-
exact.push({ start: idx, end: idx + oldLf.length, startLine:
|
|
16842
|
+
exact.push({ start: idx, end: idx + oldLf.length, startLine: lineAt2(fileLf, idx) });
|
|
15686
16843
|
idx = fileLf.indexOf(oldLf, idx + 1);
|
|
15687
16844
|
}
|
|
15688
16845
|
if (exact.length > 0) return { tier: "exact", matches: exact };
|
|
@@ -15714,7 +16871,7 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
15714
16871
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
15715
16872
|
return fuzzyScan(fileLines, needleLines, offsets);
|
|
15716
16873
|
}
|
|
15717
|
-
function
|
|
16874
|
+
function lineAt2(text, pos) {
|
|
15718
16875
|
if (pos < 512) {
|
|
15719
16876
|
let line2 = 1;
|
|
15720
16877
|
for (let i = 0; i < pos; i++) {
|
|
@@ -16176,7 +17333,7 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
16176
17333
|
};
|
|
16177
17334
|
|
|
16178
17335
|
// src/exec.ts
|
|
16179
|
-
import { spawn as
|
|
17336
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
16180
17337
|
import {
|
|
16181
17338
|
emitProcessCompleted as emitProcessCompleted3,
|
|
16182
17339
|
emitProcessOutput as emitProcessOutput3,
|
|
@@ -17393,6 +18550,26 @@ var BLOCKED_ARG_PATTERNS = {
|
|
|
17393
18550
|
pnpm: [],
|
|
17394
18551
|
npx: []
|
|
17395
18552
|
};
|
|
18553
|
+
var BLOCKED_OPTION_NAMES = {
|
|
18554
|
+
git: /* @__PURE__ */ new Set([
|
|
18555
|
+
"--exec",
|
|
18556
|
+
"--upload-pack",
|
|
18557
|
+
"--receive-pack",
|
|
18558
|
+
"--exec-path",
|
|
18559
|
+
"--git-dir",
|
|
18560
|
+
"--work-tree",
|
|
18561
|
+
"--namespace",
|
|
18562
|
+
"-c",
|
|
18563
|
+
"--config",
|
|
18564
|
+
"--config-env",
|
|
18565
|
+
"-C"
|
|
18566
|
+
]),
|
|
18567
|
+
find: /* @__PURE__ */ new Set(["-exec", "-ok", "-execdir"])
|
|
18568
|
+
};
|
|
18569
|
+
function optionName(arg) {
|
|
18570
|
+
const eq = arg.indexOf("=");
|
|
18571
|
+
return eq > 0 ? arg.slice(0, eq) : arg;
|
|
18572
|
+
}
|
|
17396
18573
|
var BLOCKED_SUBCOMMANDS = {
|
|
17397
18574
|
docker: /* @__PURE__ */ new Set(["push"]),
|
|
17398
18575
|
podman: /* @__PURE__ */ new Set(["push"]),
|
|
@@ -17430,6 +18607,15 @@ function validateArgs(cmd, args) {
|
|
|
17430
18607
|
const blocked2 = blockedSequences.find((seq) => seq.every((part, idx) => actual[idx] === part));
|
|
17431
18608
|
if (blocked2) return `Blocked subcommand "${blocked2.join(" ")}" for command "${cmd}"`;
|
|
17432
18609
|
}
|
|
18610
|
+
const blockedOptions = BLOCKED_OPTION_NAMES[cmd];
|
|
18611
|
+
if (blockedOptions) {
|
|
18612
|
+
for (const arg of args) {
|
|
18613
|
+
if (arg === "--") break;
|
|
18614
|
+
if (blockedOptions.has(optionName(arg))) {
|
|
18615
|
+
return `Blocked option "${optionName(arg)}" for command "${cmd}"`;
|
|
18616
|
+
}
|
|
18617
|
+
}
|
|
18618
|
+
}
|
|
17433
18619
|
const blocked = BLOCKED_ARG_PATTERNS[cmd];
|
|
17434
18620
|
if (!blocked) return null;
|
|
17435
18621
|
for (const arg of args) {
|
|
@@ -17612,7 +18798,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
17612
18798
|
};
|
|
17613
18799
|
let child;
|
|
17614
18800
|
try {
|
|
17615
|
-
child =
|
|
18801
|
+
child = spawn8(spawnCmd, spawnArgs, {
|
|
17616
18802
|
cwd,
|
|
17617
18803
|
env: buildChildEnv2(sessionId),
|
|
17618
18804
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -18246,7 +19432,7 @@ async function detectFixer(cwd) {
|
|
|
18246
19432
|
|
|
18247
19433
|
// src/git.ts
|
|
18248
19434
|
init_util();
|
|
18249
|
-
import { spawn as
|
|
19435
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
18250
19436
|
import { statSync as statSync4 } from "node:fs";
|
|
18251
19437
|
import { dirname as dirname14, resolve as resolve13, sep as sep6 } from "node:path";
|
|
18252
19438
|
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
@@ -18463,11 +19649,8 @@ function buildArgs(input) {
|
|
|
18463
19649
|
...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]
|
|
18464
19650
|
] : ["branch"];
|
|
18465
19651
|
case "checkout":
|
|
18466
|
-
return [
|
|
18467
|
-
|
|
18468
|
-
...input.branch ? ["--", input.branch] : [],
|
|
18469
|
-
...files.length ? ["--", ...files] : []
|
|
18470
|
-
];
|
|
19652
|
+
if (files.length) return ["checkout", "--", ...files];
|
|
19653
|
+
return input.branch ? ["checkout", input.branch, "--"] : ["checkout"];
|
|
18471
19654
|
case "stash":
|
|
18472
19655
|
return input.message ? ["stash", "push", "-m", input.message] : ["stash", "push"];
|
|
18473
19656
|
case "push":
|
|
@@ -18510,7 +19693,7 @@ function runGit2(args, cwd, signal) {
|
|
|
18510
19693
|
return new Promise((resolve16) => {
|
|
18511
19694
|
let stdout = "";
|
|
18512
19695
|
let stderr = "";
|
|
18513
|
-
const child =
|
|
19696
|
+
const child = spawn9("git", args, {
|
|
18514
19697
|
cwd,
|
|
18515
19698
|
signal,
|
|
18516
19699
|
env: buildChildEnv4(),
|
|
@@ -18572,7 +19755,7 @@ async function mapWithConcurrency2(items, limit, fn) {
|
|
|
18572
19755
|
|
|
18573
19756
|
// src/glob.ts
|
|
18574
19757
|
init_util();
|
|
18575
|
-
var DEFAULT_IGNORE2 = DEFAULT_WALK_IGNORE_DIRS2;
|
|
19758
|
+
var DEFAULT_IGNORE2 = new Set(DEFAULT_WALK_IGNORE_DIRS2);
|
|
18576
19759
|
var WALK_CONCURRENCY = 16;
|
|
18577
19760
|
var globTool = {
|
|
18578
19761
|
name: "glob",
|
|
@@ -18651,7 +19834,7 @@ var globTool = {
|
|
|
18651
19834
|
const matchedFiles = [];
|
|
18652
19835
|
for (const e of entries) {
|
|
18653
19836
|
const name = e.name;
|
|
18654
|
-
if (DEFAULT_IGNORE2.
|
|
19837
|
+
if (DEFAULT_IGNORE2.has(name)) continue;
|
|
18655
19838
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
18656
19839
|
const full = path30.join(dir, name);
|
|
18657
19840
|
if (e.isDirectory()) {
|
|
@@ -18701,7 +19884,7 @@ var globTool = {
|
|
|
18701
19884
|
};
|
|
18702
19885
|
|
|
18703
19886
|
// src/grep.ts
|
|
18704
|
-
import { spawn as
|
|
19887
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
18705
19888
|
import * as fs25 from "node:fs/promises";
|
|
18706
19889
|
import * as path31 from "node:path";
|
|
18707
19890
|
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
@@ -18725,6 +19908,81 @@ var DANGEROUS_PATTERNS = [
|
|
|
18725
19908
|
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
18726
19909
|
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
18727
19910
|
];
|
|
19911
|
+
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
19912
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
19913
|
+
if (pattern[i] !== "(") continue;
|
|
19914
|
+
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
19915
|
+
let depth = 0;
|
|
19916
|
+
let inClass = false;
|
|
19917
|
+
let j = i;
|
|
19918
|
+
for (; j < pattern.length; j++) {
|
|
19919
|
+
const ch = pattern[j];
|
|
19920
|
+
if (ch === "\\") {
|
|
19921
|
+
j++;
|
|
19922
|
+
continue;
|
|
19923
|
+
}
|
|
19924
|
+
if (inClass) {
|
|
19925
|
+
if (ch === "]") inClass = false;
|
|
19926
|
+
continue;
|
|
19927
|
+
}
|
|
19928
|
+
if (ch === "[") {
|
|
19929
|
+
inClass = true;
|
|
19930
|
+
continue;
|
|
19931
|
+
}
|
|
19932
|
+
if (ch === "(") depth++;
|
|
19933
|
+
else if (ch === ")") {
|
|
19934
|
+
depth--;
|
|
19935
|
+
if (depth === 0) break;
|
|
19936
|
+
}
|
|
19937
|
+
}
|
|
19938
|
+
if (j >= pattern.length) return false;
|
|
19939
|
+
const next = pattern[j + 1];
|
|
19940
|
+
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
19941
|
+
let inner = pattern.slice(i + 1, j);
|
|
19942
|
+
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
19943
|
+
const branches = [];
|
|
19944
|
+
let current = "";
|
|
19945
|
+
let d = 0;
|
|
19946
|
+
let cls = false;
|
|
19947
|
+
for (let k = 0; k < inner.length; k++) {
|
|
19948
|
+
const ch = inner[k];
|
|
19949
|
+
if (ch === "\\") {
|
|
19950
|
+
current += ch + (inner[k + 1] ?? "");
|
|
19951
|
+
k++;
|
|
19952
|
+
continue;
|
|
19953
|
+
}
|
|
19954
|
+
if (cls) {
|
|
19955
|
+
if (ch === "]") cls = false;
|
|
19956
|
+
current += ch;
|
|
19957
|
+
continue;
|
|
19958
|
+
}
|
|
19959
|
+
if (ch === "[") {
|
|
19960
|
+
cls = true;
|
|
19961
|
+
current += ch;
|
|
19962
|
+
continue;
|
|
19963
|
+
}
|
|
19964
|
+
if (ch === "(") d++;
|
|
19965
|
+
if (ch === ")") d--;
|
|
19966
|
+
if (ch === "|" && d === 0) {
|
|
19967
|
+
branches.push(current);
|
|
19968
|
+
current = "";
|
|
19969
|
+
continue;
|
|
19970
|
+
}
|
|
19971
|
+
current += ch;
|
|
19972
|
+
}
|
|
19973
|
+
branches.push(current);
|
|
19974
|
+
if (branches.length < 2) continue;
|
|
19975
|
+
for (let a = 0; a < branches.length; a++) {
|
|
19976
|
+
for (let b = a + 1; b < branches.length; b++) {
|
|
19977
|
+
const x = branches[a];
|
|
19978
|
+
const y = branches[b];
|
|
19979
|
+
if (x === "" || y === "") return true;
|
|
19980
|
+
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
19981
|
+
}
|
|
19982
|
+
}
|
|
19983
|
+
}
|
|
19984
|
+
return false;
|
|
19985
|
+
}
|
|
18728
19986
|
function compileUserRegex(pattern, flags) {
|
|
18729
19987
|
if (typeof pattern !== "string") {
|
|
18730
19988
|
return { ok: false, reason: "pattern must be a string" };
|
|
@@ -18743,6 +20001,12 @@ function compileUserRegex(pattern, flags) {
|
|
|
18743
20001
|
};
|
|
18744
20002
|
}
|
|
18745
20003
|
}
|
|
20004
|
+
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
20005
|
+
return {
|
|
20006
|
+
ok: false,
|
|
20007
|
+
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
20008
|
+
};
|
|
20009
|
+
}
|
|
18746
20010
|
try {
|
|
18747
20011
|
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
18748
20012
|
} catch (err) {
|
|
@@ -18759,7 +20023,7 @@ function capSubject(line) {
|
|
|
18759
20023
|
|
|
18760
20024
|
// src/grep.ts
|
|
18761
20025
|
init_util();
|
|
18762
|
-
var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
|
|
20026
|
+
var DEFAULT_IGNORE3 = new Set(DEFAULT_WALK_IGNORE_DIRS3);
|
|
18763
20027
|
var NATIVE_SCAN_CONCURRENCY = 32;
|
|
18764
20028
|
var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
|
|
18765
20029
|
var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
@@ -18832,7 +20096,7 @@ var grepTool = {
|
|
|
18832
20096
|
field: "pattern"
|
|
18833
20097
|
});
|
|
18834
20098
|
}
|
|
18835
|
-
const base = input.path ?
|
|
20099
|
+
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
18836
20100
|
const mode = input.output_mode ?? "content";
|
|
18837
20101
|
const limit = Math.max(1, Math.min(input.limit ?? 200, 2e3));
|
|
18838
20102
|
const validation = compileUserRegex(input.pattern, input.case_insensitive ? "i" : "");
|
|
@@ -18858,7 +20122,7 @@ var grepTool = {
|
|
|
18858
20122
|
async function detectRg(signal) {
|
|
18859
20123
|
return new Promise((resolve16) => {
|
|
18860
20124
|
try {
|
|
18861
|
-
const p =
|
|
20125
|
+
const p = spawn10("rg", ["--version"], { env: buildChildEnv5(), stdio: "ignore", signal, windowsHide: true });
|
|
18862
20126
|
p.on("error", () => resolve16(false));
|
|
18863
20127
|
p.on("close", (code) => resolve16(code === 0));
|
|
18864
20128
|
} catch {
|
|
@@ -18892,7 +20156,7 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
18892
20156
|
const FLUSH_AT = 16;
|
|
18893
20157
|
const MAX_BUF_BYTES = 1e6;
|
|
18894
20158
|
let bufOverflow = false;
|
|
18895
|
-
const child =
|
|
20159
|
+
const child = spawn10("rg", args, {
|
|
18896
20160
|
signal,
|
|
18897
20161
|
env: buildChildEnv5(),
|
|
18898
20162
|
// rg diagnostics are not part of the tool result. Ignoring stderr avoids
|
|
@@ -19157,7 +20421,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
19157
20421
|
const subdirs = [];
|
|
19158
20422
|
for (const e of entries) {
|
|
19159
20423
|
if (stopped) return;
|
|
19160
|
-
if (DEFAULT_IGNORE3.
|
|
20424
|
+
if (DEFAULT_IGNORE3.has(e.name)) continue;
|
|
19161
20425
|
if (e.isSymbolicLink()) continue;
|
|
19162
20426
|
const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
|
|
19163
20427
|
const full = path31.join(dir, e.name);
|
|
@@ -19189,7 +20453,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
19189
20453
|
init_spawn_stream();
|
|
19190
20454
|
init_util();
|
|
19191
20455
|
init_legacy_bridge();
|
|
19192
|
-
import { join as
|
|
20456
|
+
import { join as join24 } from "node:path";
|
|
19193
20457
|
import {
|
|
19194
20458
|
detectEcosystem as detectPackageEcosystem,
|
|
19195
20459
|
recordPackageAction
|
|
@@ -19373,17 +20637,17 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
19373
20637
|
case "pnpm":
|
|
19374
20638
|
case "yarn":
|
|
19375
20639
|
case "npm":
|
|
19376
|
-
return
|
|
20640
|
+
return join24(cwd, "package.json");
|
|
19377
20641
|
/* v8 ignore next 2 -- pkgManager is always pnpm/yarn/npm; the default is defensive. */
|
|
19378
20642
|
default:
|
|
19379
|
-
return
|
|
20643
|
+
return join24(cwd, "package.json");
|
|
19380
20644
|
}
|
|
19381
20645
|
}
|
|
19382
20646
|
|
|
19383
20647
|
// src/json.ts
|
|
19384
|
-
init_util();
|
|
19385
20648
|
import * as fs26 from "node:fs/promises";
|
|
19386
20649
|
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
20650
|
+
init_util();
|
|
19387
20651
|
var MAX_JSON_FILE_BYTES = 16 * 1024 * 1024;
|
|
19388
20652
|
var MAX_JSON_FILE_BYTES_HUMAN = "16 MiB";
|
|
19389
20653
|
var JsonFileTooLargeError = class extends Error {
|
|
@@ -19822,8 +21086,12 @@ function validateJsonSchema(data, schema) {
|
|
|
19822
21086
|
}
|
|
19823
21087
|
}
|
|
19824
21088
|
if (typeof value === "string" && s["pattern"]) {
|
|
19825
|
-
const
|
|
19826
|
-
if (!
|
|
21089
|
+
const compiled = compileUserRegex(s["pattern"], "");
|
|
21090
|
+
if (!compiled.ok) {
|
|
21091
|
+
errors.push(`${path38}: invalid schema pattern \u2014 ${compiled.reason}`);
|
|
21092
|
+
} else if (!compiled.regex.test(capSubject(value))) {
|
|
21093
|
+
errors.push(`${path38}: does not match pattern ${s["pattern"]}`);
|
|
21094
|
+
}
|
|
19827
21095
|
}
|
|
19828
21096
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
19829
21097
|
errors.push(`${path38}: string too short (min ${s["minLength"]})`);
|
|
@@ -21808,7 +23076,7 @@ async function detectLinter(cwd) {
|
|
|
21808
23076
|
}
|
|
21809
23077
|
|
|
21810
23078
|
// src/logs.ts
|
|
21811
|
-
import { spawn as
|
|
23079
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
21812
23080
|
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core/utils";
|
|
21813
23081
|
init_util();
|
|
21814
23082
|
var logsTool = {
|
|
@@ -21915,7 +23183,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
21915
23183
|
clearTimeout(timer);
|
|
21916
23184
|
resolve16(result);
|
|
21917
23185
|
};
|
|
21918
|
-
const child =
|
|
23186
|
+
const child = spawn11("docker", args, { cwd, signal, env: buildChildEnv6(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
21919
23187
|
const timer = setTimeout(() => {
|
|
21920
23188
|
child.kill("SIGTERM");
|
|
21921
23189
|
finish(empty());
|
|
@@ -22024,7 +23292,7 @@ function parseLine(line) {
|
|
|
22024
23292
|
// src/outdated.ts
|
|
22025
23293
|
init_util();
|
|
22026
23294
|
init_win32_resolve();
|
|
22027
|
-
import { spawn as
|
|
23295
|
+
import { spawn as spawn12 } from "node:child_process";
|
|
22028
23296
|
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core/utils";
|
|
22029
23297
|
var outdatedTool = {
|
|
22030
23298
|
name: "outdated",
|
|
@@ -22144,7 +23412,7 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
22144
23412
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
22145
23413
|
const spawnCmd = shim?.command ?? resolved;
|
|
22146
23414
|
const spawnArgs = shim?.args ?? args;
|
|
22147
|
-
const child =
|
|
23415
|
+
const child = spawn12(spawnCmd, spawnArgs, {
|
|
22148
23416
|
cwd,
|
|
22149
23417
|
signal,
|
|
22150
23418
|
env: buildChildEnv7(),
|
|
@@ -22210,16 +23478,16 @@ function parseOutdatedOutput(json2, exitCode) {
|
|
|
22210
23478
|
|
|
22211
23479
|
// src/patch.ts
|
|
22212
23480
|
init_util();
|
|
22213
|
-
import { spawn as
|
|
23481
|
+
import { spawn as spawn13 } from "node:child_process";
|
|
22214
23482
|
import * as fs27 from "node:fs/promises";
|
|
22215
23483
|
import * as os9 from "node:os";
|
|
22216
23484
|
import * as path32 from "node:path";
|
|
22217
|
-
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core/utils";
|
|
23485
|
+
import { buildChildEnv as buildChildEnv8, toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
22218
23486
|
var patchTool = {
|
|
22219
23487
|
name: "patch",
|
|
22220
23488
|
category: "Filesystem",
|
|
22221
23489
|
description: "Apply a unified diff (patch) to the project. This is the correct tool when you have a diff that needs to be applied precisely, including handling of rejects.",
|
|
22222
|
-
usageHint: "Best used when you already have a diff (from generation, external source, or previous step).\n- Use `dry_run: true` to see what would happen without modifying files.\n-
|
|
23490
|
+
usageHint: "Best used when you already have a diff (from generation, external source, or previous step).\n- Use `dry_run: true` to see what would happen without modifying files.\n- Applied with `--merge`: a conflicting hunk writes git-style conflict\n markers (<<<<<<< / ======= / >>>>>>>) INTO the file and reports failure.\n It does NOT create .rej/.orig files. `files` lists what changed on disk\n even when the patch failed, so read those back before retrying.\nOften cleaner than many small `edit` operations for larger changes.",
|
|
22223
23491
|
selection: {
|
|
22224
23492
|
doNotUseWhen: "you do not already have a unified diff or only need one precise replacement.",
|
|
22225
23493
|
useInstead: ["edit"]
|
|
@@ -22245,31 +23513,50 @@ var patchTool = {
|
|
|
22245
23513
|
},
|
|
22246
23514
|
async execute(input, ctx, opts) {
|
|
22247
23515
|
if (!input?.patch) throw new Error("patch: patch content is required");
|
|
22248
|
-
const dir = input.directory ? safeResolve(input.directory, ctx) : ctx.cwd;
|
|
22249
23516
|
const strip = Math.max(1, input.strip ?? 1);
|
|
22250
23517
|
const dryRun = input.dry_run ?? false;
|
|
23518
|
+
const refuse = (message) => ({
|
|
23519
|
+
applied: 0,
|
|
23520
|
+
rejected: 1,
|
|
23521
|
+
files: [],
|
|
23522
|
+
dry_run: dryRun,
|
|
23523
|
+
message
|
|
23524
|
+
});
|
|
23525
|
+
let dir;
|
|
23526
|
+
try {
|
|
23527
|
+
dir = input.directory ? await safeResolveReal(input.directory, ctx) : ctx.cwd;
|
|
23528
|
+
} catch (err) {
|
|
23529
|
+
return refuse(`patch refused: ${toErrorMessage4(err)}`);
|
|
23530
|
+
}
|
|
23531
|
+
const realRoot = await fs27.realpath(ctx.projectRoot).catch(() => path32.resolve(ctx.projectRoot));
|
|
22251
23532
|
const targets = extractDiffTargets(input.patch);
|
|
22252
23533
|
const resolvedTargets = [];
|
|
22253
23534
|
for (const t of targets) {
|
|
22254
|
-
const stripped = stripPathComponents(t, strip);
|
|
23535
|
+
const stripped = stripPathComponents(t.raw, strip);
|
|
22255
23536
|
if (!stripped) continue;
|
|
23537
|
+
if (path32.isAbsolute(stripped)) {
|
|
23538
|
+
return refuse(`patch refused: target "${t.raw}" strips to absolute path`);
|
|
23539
|
+
}
|
|
22256
23540
|
const candidate = path32.resolve(dir, stripped);
|
|
22257
|
-
|
|
23541
|
+
let real;
|
|
23542
|
+
try {
|
|
23543
|
+
real = await resolveRealInsideRoot(candidate, ctx);
|
|
23544
|
+
} catch (err) {
|
|
23545
|
+
return refuse(`patch refused: target "${t.raw}" ${toErrorMessage4(err)}`);
|
|
23546
|
+
}
|
|
23547
|
+
const rel = path32.relative(realRoot, real);
|
|
22258
23548
|
if (rel.startsWith("..") || path32.isAbsolute(rel)) {
|
|
22259
|
-
return {
|
|
22260
|
-
applied: 0,
|
|
22261
|
-
rejected: 1,
|
|
22262
|
-
files: [],
|
|
22263
|
-
dry_run: dryRun,
|
|
22264
|
-
message: `patch refused: target "${t}" resolves outside project root`
|
|
22265
|
-
};
|
|
23549
|
+
return refuse(`patch refused: target "${t.raw}" resolves outside project root`);
|
|
22266
23550
|
}
|
|
22267
|
-
resolvedTargets.push(
|
|
23551
|
+
resolvedTargets.push({ raw: t.raw, deleted: t.deleted, abs: real });
|
|
22268
23552
|
}
|
|
22269
23553
|
const beforeContents = /* @__PURE__ */ new Map();
|
|
23554
|
+
const beforeExisted = /* @__PURE__ */ new Set();
|
|
22270
23555
|
if (!dryRun) {
|
|
22271
23556
|
for (const target of resolvedTargets) {
|
|
22272
|
-
|
|
23557
|
+
const existed = (await fs27.stat(target.abs).catch(() => null))?.isFile() ?? false;
|
|
23558
|
+
if (existed) beforeExisted.add(target.abs);
|
|
23559
|
+
beforeContents.set(target.abs, await readTextForTracking(target.abs));
|
|
22273
23560
|
}
|
|
22274
23561
|
}
|
|
22275
23562
|
const tmpDir = await fs27.mkdtemp(path32.join(os9.tmpdir(), ".wstack_patch_"));
|
|
@@ -22279,32 +23566,70 @@ var patchTool = {
|
|
|
22279
23566
|
const patchFile = path32.join(tmpDir, "in.diff");
|
|
22280
23567
|
await fs27.writeFile(patchFile, input.patch, { mode: 384 });
|
|
22281
23568
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
22282
|
-
const result = await runPatch(args, dir, opts.signal
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
dry_run: dryRun,
|
|
22289
|
-
message: `patch failed: ${result.stderr || result.stdout}`
|
|
22290
|
-
};
|
|
22291
|
-
}
|
|
22292
|
-
const patched = extractPatchedFiles(result.stdout);
|
|
23569
|
+
const result = await runPatch(args, dir, opts.signal, {
|
|
23570
|
+
patchFile,
|
|
23571
|
+
strip,
|
|
23572
|
+
dryRun
|
|
23573
|
+
});
|
|
23574
|
+
const touched = [];
|
|
22293
23575
|
if (!dryRun) {
|
|
22294
23576
|
for (const target of resolvedTargets) {
|
|
22295
|
-
const
|
|
22296
|
-
const
|
|
23577
|
+
const abs = target.abs;
|
|
23578
|
+
const before = beforeContents.get(abs) ?? null;
|
|
23579
|
+
const stat18 = await fs27.stat(abs).catch(() => null);
|
|
23580
|
+
if (!stat18?.isFile()) {
|
|
23581
|
+
if (beforeExisted.has(abs)) {
|
|
23582
|
+
touched.push(abs);
|
|
23583
|
+
ctx.session?.recordFileChange?.({
|
|
23584
|
+
path: abs,
|
|
23585
|
+
action: "deleted",
|
|
23586
|
+
before,
|
|
23587
|
+
after: null
|
|
23588
|
+
});
|
|
23589
|
+
}
|
|
23590
|
+
continue;
|
|
23591
|
+
}
|
|
23592
|
+
const after = await readTextForTracking(abs);
|
|
22297
23593
|
if (after === null || after === before) continue;
|
|
22298
|
-
|
|
22299
|
-
|
|
23594
|
+
touched.push(abs);
|
|
23595
|
+
ctx.recordRead?.(abs, stat18.mtimeMs, "write", sha256hex(after));
|
|
22300
23596
|
ctx.session?.recordFileChange?.({
|
|
22301
|
-
path:
|
|
23597
|
+
path: abs,
|
|
22302
23598
|
action: before === null ? "created" : "modified",
|
|
22303
23599
|
before,
|
|
22304
23600
|
after
|
|
22305
23601
|
});
|
|
22306
23602
|
}
|
|
22307
23603
|
}
|
|
23604
|
+
if (result.exitCode !== 0) {
|
|
23605
|
+
if (!dryRun) {
|
|
23606
|
+
const partial = touched.length > 0 ? ` ${touched.length} file(s) were still modified on disk and have been recorded for rewind: ${touched.map((p) => path32.relative(realRoot, p) || p).join(", ")}.` : "";
|
|
23607
|
+
return {
|
|
23608
|
+
applied: touched.length,
|
|
23609
|
+
rejected: 1,
|
|
23610
|
+
// Normalize to relative-to-realRoot for API consistency with the
|
|
23611
|
+
// success path (which returns GNU patch's dir-relative names).
|
|
23612
|
+
// `touched` entries are realpaths from resolveRealInsideRoot, and
|
|
23613
|
+
// realRoot is also a realpath, so path.relative is like-for-like.
|
|
23614
|
+
files: touched.map((p) => path32.relative(realRoot, p) || p),
|
|
23615
|
+
dry_run: dryRun,
|
|
23616
|
+
message: `patch failed: ${result.stderr || result.stdout}${partial}`
|
|
23617
|
+
};
|
|
23618
|
+
}
|
|
23619
|
+
const wouldPatch = extractPatchedFiles(result.stdout);
|
|
23620
|
+
return {
|
|
23621
|
+
applied: wouldPatch.length,
|
|
23622
|
+
rejected: 1,
|
|
23623
|
+
files: wouldPatch,
|
|
23624
|
+
dry_run: dryRun,
|
|
23625
|
+
message: `patch preview: would conflict \u2014 ${result.stderr || result.stdout}`
|
|
23626
|
+
};
|
|
23627
|
+
}
|
|
23628
|
+
const patched = result.engine === "git" ? [
|
|
23629
|
+
...new Set(
|
|
23630
|
+
resolvedTargets.map((target) => path32.relative(dir, target.abs) || target.abs)
|
|
23631
|
+
)
|
|
23632
|
+
] : extractPatchedFiles(result.stdout);
|
|
22308
23633
|
return {
|
|
22309
23634
|
applied: patched.length,
|
|
22310
23635
|
rejected: 0,
|
|
@@ -22332,27 +23657,86 @@ async function readTextForTracking(absPath) {
|
|
|
22332
23657
|
}
|
|
22333
23658
|
function extractDiffTargets(patch) {
|
|
22334
23659
|
const out = [];
|
|
22335
|
-
const
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
|
|
23660
|
+
const clean = (raw) => {
|
|
23661
|
+
if (!raw) return "";
|
|
23662
|
+
return (raw.length > 4096 ? raw.slice(0, 4096) : raw).trim();
|
|
23663
|
+
};
|
|
23664
|
+
let lastOld;
|
|
23665
|
+
let inHunk = false;
|
|
23666
|
+
let oldLinesLeft = 0;
|
|
23667
|
+
let newLinesLeft = 0;
|
|
23668
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
23669
|
+
const hunkMatch = /^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/.exec(line);
|
|
23670
|
+
if (hunkMatch) {
|
|
23671
|
+
inHunk = true;
|
|
23672
|
+
oldLinesLeft = hunkMatch[1] ? Number(hunkMatch[1]) : 1;
|
|
23673
|
+
newLinesLeft = hunkMatch[2] ? Number(hunkMatch[2]) : 1;
|
|
23674
|
+
lastOld = void 0;
|
|
23675
|
+
continue;
|
|
23676
|
+
}
|
|
23677
|
+
if (inHunk) {
|
|
23678
|
+
const ch = line[0];
|
|
23679
|
+
if (ch === "-") oldLinesLeft--;
|
|
23680
|
+
else if (ch === "+") newLinesLeft--;
|
|
23681
|
+
else if (ch === " " || ch === void 0) {
|
|
23682
|
+
oldLinesLeft--;
|
|
23683
|
+
newLinesLeft--;
|
|
23684
|
+
}
|
|
23685
|
+
if (oldLinesLeft <= 0 && newLinesLeft <= 0) inHunk = false;
|
|
23686
|
+
continue;
|
|
23687
|
+
}
|
|
23688
|
+
const oldMatch = /^---\s+([^\t\r\n]+)/.exec(line);
|
|
23689
|
+
if (oldMatch) {
|
|
23690
|
+
lastOld = clean(oldMatch[1]);
|
|
23691
|
+
continue;
|
|
23692
|
+
}
|
|
23693
|
+
const newMatch = /^\+\+\+\s+([^\t\r\n]+)/.exec(line);
|
|
23694
|
+
if (!newMatch) continue;
|
|
23695
|
+
const newTarget = clean(newMatch[1]);
|
|
23696
|
+
if (newTarget && newTarget !== "/dev/null") {
|
|
23697
|
+
out.push({ raw: newTarget, deleted: false });
|
|
23698
|
+
} else if (lastOld && lastOld !== "/dev/null") {
|
|
23699
|
+
out.push({ raw: lastOld, deleted: true });
|
|
23700
|
+
}
|
|
23701
|
+
lastOld = void 0;
|
|
22342
23702
|
}
|
|
22343
23703
|
return out;
|
|
22344
23704
|
}
|
|
22345
23705
|
function stripPathComponents(p, strip) {
|
|
22346
|
-
const
|
|
22347
|
-
|
|
22348
|
-
|
|
23706
|
+
const s = p.replace(/\\/g, "/");
|
|
23707
|
+
let idx = 0;
|
|
23708
|
+
for (let i = 0; i < strip; i++) {
|
|
23709
|
+
while (idx < s.length && s[idx] !== "/") idx++;
|
|
23710
|
+
let hadSlash = false;
|
|
23711
|
+
while (idx < s.length && s[idx] === "/") {
|
|
23712
|
+
idx++;
|
|
23713
|
+
hadSlash = true;
|
|
23714
|
+
}
|
|
23715
|
+
if (!hadSlash) return void 0;
|
|
23716
|
+
}
|
|
23717
|
+
return s.slice(idx) || void 0;
|
|
23718
|
+
}
|
|
23719
|
+
function runPatch(args, cwd, signal, fallback) {
|
|
23720
|
+
return runPatchProcess("patch", args, cwd, signal).then(async (result) => {
|
|
23721
|
+
if (!result.unavailable) return { ...result, engine: "patch" };
|
|
23722
|
+
const gitArgs = [
|
|
23723
|
+
"apply",
|
|
23724
|
+
"--unsafe-paths",
|
|
23725
|
+
`-p${fallback.strip}`,
|
|
23726
|
+
"--verbose",
|
|
23727
|
+
...fallback.dryRun ? ["--check"] : [],
|
|
23728
|
+
fallback.patchFile
|
|
23729
|
+
];
|
|
23730
|
+
const gitResult = await runPatchProcess("git", gitArgs, cwd, signal);
|
|
23731
|
+
return { ...gitResult, engine: "git" };
|
|
23732
|
+
});
|
|
22349
23733
|
}
|
|
22350
|
-
function
|
|
23734
|
+
function runPatchProcess(command, args, cwd, signal) {
|
|
22351
23735
|
return new Promise((resolve16) => {
|
|
22352
23736
|
let stdout = "";
|
|
22353
23737
|
let stderr = "";
|
|
22354
23738
|
const env = { ...buildChildEnv8(), LANG: "C", LC_ALL: "C" };
|
|
22355
|
-
const child =
|
|
23739
|
+
const child = spawn13(command, args, {
|
|
22356
23740
|
cwd,
|
|
22357
23741
|
signal,
|
|
22358
23742
|
env,
|
|
@@ -22365,13 +23749,24 @@ function runPatch(args, cwd, signal) {
|
|
|
22365
23749
|
child.stderr?.on("data", (c) => {
|
|
22366
23750
|
stderr += c.toString();
|
|
22367
23751
|
});
|
|
22368
|
-
child.on(
|
|
22369
|
-
|
|
23752
|
+
child.on(
|
|
23753
|
+
"close",
|
|
23754
|
+
(code) => resolve16({ exitCode: code ?? 1, stdout, stderr, unavailable: false })
|
|
23755
|
+
);
|
|
23756
|
+
child.on(
|
|
23757
|
+
"error",
|
|
23758
|
+
(e) => resolve16({
|
|
23759
|
+
exitCode: 1,
|
|
23760
|
+
stdout: "",
|
|
23761
|
+
stderr: e.message,
|
|
23762
|
+
unavailable: e.code === "ENOENT"
|
|
23763
|
+
})
|
|
23764
|
+
);
|
|
22370
23765
|
});
|
|
22371
23766
|
}
|
|
22372
23767
|
function extractPatchedFiles(output) {
|
|
22373
23768
|
const files = [];
|
|
22374
|
-
const re = /patching file (.+)/gi;
|
|
23769
|
+
const re = /(?:patching|checking) file (.+)/gi;
|
|
22375
23770
|
for (const m of output.matchAll(re)) {
|
|
22376
23771
|
if (m[1]) files.push(m[1]);
|
|
22377
23772
|
}
|
|
@@ -22680,7 +24075,7 @@ function mkResult(plan, ok, message, todos) {
|
|
|
22680
24075
|
init_util();
|
|
22681
24076
|
import * as fs28 from "node:fs/promises";
|
|
22682
24077
|
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
22683
|
-
import { toErrorMessage as
|
|
24078
|
+
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
22684
24079
|
var ADVANCED_MODE_META_KEY = "tools.read.advancedMode";
|
|
22685
24080
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
22686
24081
|
var readTool = {
|
|
@@ -22748,7 +24143,7 @@ var readTool = {
|
|
|
22748
24143
|
});
|
|
22749
24144
|
}
|
|
22750
24145
|
throw new FsError({
|
|
22751
|
-
message: `read: failed to stat "${input.path}": ${
|
|
24146
|
+
message: `read: failed to stat "${input.path}": ${toErrorMessage5(err)}`,
|
|
22752
24147
|
code: "FS_READ_FAILED",
|
|
22753
24148
|
path: absPath,
|
|
22754
24149
|
context: { errno: code },
|
|
@@ -22949,7 +24344,7 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
22949
24344
|
}
|
|
22950
24345
|
|
|
22951
24346
|
// src/replace.ts
|
|
22952
|
-
import { spawn as
|
|
24347
|
+
import { spawn as spawn14 } from "node:child_process";
|
|
22953
24348
|
import * as fs29 from "node:fs/promises";
|
|
22954
24349
|
import * as path33 from "node:path";
|
|
22955
24350
|
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
@@ -23134,7 +24529,7 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
23134
24529
|
function checkRg() {
|
|
23135
24530
|
return new Promise((resolve16) => {
|
|
23136
24531
|
try {
|
|
23137
|
-
const p =
|
|
24532
|
+
const p = spawn14("rg", ["--version"], {
|
|
23138
24533
|
env: buildChildEnv9(),
|
|
23139
24534
|
stdio: "ignore",
|
|
23140
24535
|
windowsHide: true
|
|
@@ -23148,7 +24543,7 @@ function checkRg() {
|
|
|
23148
24543
|
}
|
|
23149
24544
|
function spawnRgFind(pattern, base) {
|
|
23150
24545
|
const args = ["--files", "--glob", pattern, base];
|
|
23151
|
-
const child =
|
|
24546
|
+
const child = spawn14("rg", args, {
|
|
23152
24547
|
signal: AbortSignal.timeout(3e4),
|
|
23153
24548
|
env: buildChildEnv9(),
|
|
23154
24549
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -23407,7 +24802,7 @@ function substituteVars(content, name, vars) {
|
|
|
23407
24802
|
// src/search.ts
|
|
23408
24803
|
import { FetchError as FetchError3, ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
23409
24804
|
import { expectDefined as expectDefined9 } from "@wrongstack/core/utils";
|
|
23410
|
-
import { toErrorMessage as
|
|
24805
|
+
import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
23411
24806
|
var DEFAULT_NUM = 10;
|
|
23412
24807
|
var MAX_RESULTS = 50;
|
|
23413
24808
|
var TIMEOUT_MS3 = 15e3;
|
|
@@ -23611,7 +25006,7 @@ async function duckduckgoSearch(query, num, signal) {
|
|
|
23611
25006
|
return parseDuckDuckGo(html, num);
|
|
23612
25007
|
} catch (err) {
|
|
23613
25008
|
console.log(
|
|
23614
|
-
JSON.stringify({ level: "debug", event: "search_failed", query, error:
|
|
25009
|
+
JSON.stringify({ level: "debug", event: "search_failed", query, error: toErrorMessage6(err) })
|
|
23615
25010
|
);
|
|
23616
25011
|
return [{ title: "Search unavailable", url: "https://duckduckgo.com/unavailable", snippet: "Could not reach DuckDuckGo", score: 0 }];
|
|
23617
25012
|
}
|
|
@@ -23795,7 +25190,7 @@ function decodeHtmlEntities(text) {
|
|
|
23795
25190
|
|
|
23796
25191
|
// src/set-working-dir.ts
|
|
23797
25192
|
import * as fs31 from "node:fs/promises";
|
|
23798
|
-
import { toErrorMessage as
|
|
25193
|
+
import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
|
|
23799
25194
|
var setWorkingDirTool = {
|
|
23800
25195
|
name: "set_working_dir",
|
|
23801
25196
|
category: "Context",
|
|
@@ -23829,7 +25224,7 @@ var setWorkingDirTool = {
|
|
|
23829
25224
|
} catch (err) {
|
|
23830
25225
|
return {
|
|
23831
25226
|
current: ctx.workingDir,
|
|
23832
|
-
error:
|
|
25227
|
+
error: toErrorMessage7(err)
|
|
23833
25228
|
};
|
|
23834
25229
|
}
|
|
23835
25230
|
try {
|
|
@@ -24564,7 +25959,7 @@ var toolHelpTool = {
|
|
|
24564
25959
|
const format = input.format ?? "short";
|
|
24565
25960
|
const includeExamples = input.include_examples ?? false;
|
|
24566
25961
|
if (input.tool) {
|
|
24567
|
-
const tool = ctx.tools.find((t) => t.name === input.tool);
|
|
25962
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find((t) => t.name === input.tool);
|
|
24568
25963
|
if (!tool) {
|
|
24569
25964
|
return {
|
|
24570
25965
|
tool: input.tool,
|
|
@@ -24589,7 +25984,7 @@ var toolHelpTool = {
|
|
|
24589
25984
|
total: 1
|
|
24590
25985
|
};
|
|
24591
25986
|
}
|
|
24592
|
-
const allTools = ctx.tools.map((t) => ({
|
|
25987
|
+
const allTools = (ctx.catalogTools ?? ctx.tools).map((t) => ({
|
|
24593
25988
|
name: t.name,
|
|
24594
25989
|
description: t.description,
|
|
24595
25990
|
usageHint: t.usageHint ?? "",
|
|
@@ -24697,7 +26092,7 @@ var toolSearchTool = {
|
|
|
24697
26092
|
},
|
|
24698
26093
|
async execute(input, ctx) {
|
|
24699
26094
|
const limit = Math.min(input.limit ?? 20, 100);
|
|
24700
|
-
const tools = ctx.tools;
|
|
26095
|
+
const tools = ctx.catalogTools ?? ctx.tools;
|
|
24701
26096
|
const query = input.query?.toLowerCase() ?? "";
|
|
24702
26097
|
const filtered = tools.filter((t) => {
|
|
24703
26098
|
if (query && !t.name.toLowerCase().includes(query) && !t.description.toLowerCase().includes(query)) {
|
|
@@ -24777,7 +26172,7 @@ var toolUseTool = {
|
|
|
24777
26172
|
executionMs: 0
|
|
24778
26173
|
};
|
|
24779
26174
|
}
|
|
24780
|
-
const tool = ctx.tools.find((t) => t.name === input.tool);
|
|
26175
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find((t) => t.name === input.tool);
|
|
24781
26176
|
if (!tool) {
|
|
24782
26177
|
return {
|
|
24783
26178
|
tool: input.tool,
|
|
@@ -24835,7 +26230,13 @@ init_util();
|
|
|
24835
26230
|
import * as fs32 from "node:fs/promises";
|
|
24836
26231
|
import * as path36 from "node:path";
|
|
24837
26232
|
import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
24838
|
-
var DEFAULT_IGNORE5 =
|
|
26233
|
+
var DEFAULT_IGNORE5 = /* @__PURE__ */ new Set([
|
|
26234
|
+
...DEFAULT_WALK_IGNORE_DIRS4,
|
|
26235
|
+
".wrongstack",
|
|
26236
|
+
".ssh",
|
|
26237
|
+
".gnupg",
|
|
26238
|
+
".aws"
|
|
26239
|
+
]);
|
|
24839
26240
|
var DEFAULT_MAX_ENTRIES2 = 5e3;
|
|
24840
26241
|
var MAX_TREE_OUTPUT_BYTES = 256 * 1024;
|
|
24841
26242
|
var treeTool = {
|
|
@@ -24996,8 +26397,12 @@ async function walkDir(dir, depth, opts) {
|
|
|
24996
26397
|
return true;
|
|
24997
26398
|
});
|
|
24998
26399
|
if (depth > 0) {
|
|
24999
|
-
|
|
25000
|
-
|
|
26400
|
+
let dirCount = 0;
|
|
26401
|
+
let fileCount = 0;
|
|
26402
|
+
for (const e of filtered) {
|
|
26403
|
+
if (e.isDirectory()) dirCount++;
|
|
26404
|
+
else if (e.isFile()) fileCount++;
|
|
26405
|
+
}
|
|
25001
26406
|
opts.totalDirs.value += dirCount;
|
|
25002
26407
|
opts.totalFiles.value += fileCount;
|
|
25003
26408
|
opts.onProgress?.();
|
|
@@ -25187,7 +26592,7 @@ var writeTool = {
|
|
|
25187
26592
|
required: ["path", "content"]
|
|
25188
26593
|
},
|
|
25189
26594
|
async execute(input, ctx, opts) {
|
|
25190
|
-
return
|
|
26595
|
+
return writeFile6(input, ctx, opts?.signal);
|
|
25191
26596
|
},
|
|
25192
26597
|
async *executeStream(input, ctx, opts) {
|
|
25193
26598
|
const prepared = await prepareWrite(input, ctx);
|
|
@@ -25200,7 +26605,7 @@ var writeTool = {
|
|
|
25200
26605
|
yield { type: "final", output: await finishWrite(input, ctx, prepared, opts?.signal) };
|
|
25201
26606
|
}
|
|
25202
26607
|
};
|
|
25203
|
-
async function
|
|
26608
|
+
async function writeFile6(input, ctx, signal) {
|
|
25204
26609
|
return finishWrite(input, ctx, await prepareWrite(input, ctx), signal);
|
|
25205
26610
|
}
|
|
25206
26611
|
async function prepareWrite(input, ctx) {
|
|
@@ -25320,18 +26725,6 @@ var TIER2_TOOLS = [
|
|
|
25320
26725
|
auditTool,
|
|
25321
26726
|
designTool
|
|
25322
26727
|
];
|
|
25323
|
-
var TIER3_TOOLS = [
|
|
25324
|
-
outdatedTool,
|
|
25325
|
-
logsTool,
|
|
25326
|
-
documentTool,
|
|
25327
|
-
scaffoldTool,
|
|
25328
|
-
deadCodeScanTool,
|
|
25329
|
-
toolSearchTool,
|
|
25330
|
-
toolUseTool,
|
|
25331
|
-
batchToolUseTool,
|
|
25332
|
-
toolHelpTool,
|
|
25333
|
-
setWorkingDirTool
|
|
25334
|
-
];
|
|
25335
26728
|
var builtinTools = [
|
|
25336
26729
|
...browserTools,
|
|
25337
26730
|
e2ePlanTool,
|
|
@@ -25408,11 +26801,7 @@ function selectBuiltinToolsForTier(tier, allTools) {
|
|
|
25408
26801
|
}
|
|
25409
26802
|
case "aggressive": {
|
|
25410
26803
|
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
25411
|
-
|
|
25412
|
-
const tier3Names = toolNameSet(TIER3_TOOLS);
|
|
25413
|
-
return allTools.filter(
|
|
25414
|
-
(tool) => tier1Names.has(tool.name) || tier2Names.has(tool.name) && tool.name !== "task" || tier3Names.has(tool.name) && tool.name !== "set_working_dir"
|
|
25415
|
-
);
|
|
26804
|
+
return allTools.filter((tool) => tier1Names.has(tool.name));
|
|
25416
26805
|
}
|
|
25417
26806
|
}
|
|
25418
26807
|
}
|