@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/index.js
CHANGED
|
@@ -724,7 +724,7 @@ var init_process_registry = __esm({
|
|
|
724
724
|
const p = this.processes.get(pid);
|
|
725
725
|
if (!p) return false;
|
|
726
726
|
if (p.killed) return true;
|
|
727
|
-
if (p.protected) return false;
|
|
727
|
+
if (p.protected && opts.includeProtected !== true) return false;
|
|
728
728
|
if (opts.preserveBackground && p.background) return false;
|
|
729
729
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
730
730
|
const isWin5 = os.platform() === "win32";
|
|
@@ -775,9 +775,13 @@ var init_process_registry = __esm({
|
|
|
775
775
|
killAll(opts = {}) {
|
|
776
776
|
const pids = Array.from(this.processes.keys());
|
|
777
777
|
const killed = [];
|
|
778
|
+
const includeProtected = opts.includeProtected === true;
|
|
778
779
|
for (const pid of pids) {
|
|
779
780
|
const p = this.processes.get(pid);
|
|
780
|
-
if (
|
|
781
|
+
if (!p) continue;
|
|
782
|
+
if (p.protected && !includeProtected) continue;
|
|
783
|
+
if (opts.preserveBackground && p.background) continue;
|
|
784
|
+
if (this.kill(pid, opts)) killed.push(pid);
|
|
781
785
|
}
|
|
782
786
|
return killed;
|
|
783
787
|
}
|
|
@@ -1141,18 +1145,18 @@ async function resolveRealInsideRoot(absPath, ctx) {
|
|
|
1141
1145
|
const realRoots = await Promise.all(
|
|
1142
1146
|
allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
|
|
1143
1147
|
);
|
|
1144
|
-
let
|
|
1148
|
+
let probe = absPath;
|
|
1145
1149
|
const pendingTail = [];
|
|
1146
1150
|
for (; ; ) {
|
|
1147
1151
|
let real;
|
|
1148
1152
|
try {
|
|
1149
|
-
real = await fsp2.realpath(
|
|
1153
|
+
real = await fsp2.realpath(probe);
|
|
1150
1154
|
} catch (err) {
|
|
1151
1155
|
if (err.code === "ENOENT") {
|
|
1152
|
-
const parent = path3.dirname(
|
|
1153
|
-
if (parent ===
|
|
1154
|
-
pendingTail.unshift(path3.basename(
|
|
1155
|
-
|
|
1156
|
+
const parent = path3.dirname(probe);
|
|
1157
|
+
if (parent === probe) return absPath;
|
|
1158
|
+
pendingTail.unshift(path3.basename(probe));
|
|
1159
|
+
probe = parent;
|
|
1156
1160
|
continue;
|
|
1157
1161
|
}
|
|
1158
1162
|
throw err;
|
|
@@ -4971,23 +4975,26 @@ var init_legacy_bridge = __esm({
|
|
|
4971
4975
|
});
|
|
4972
4976
|
|
|
4973
4977
|
// src/codebase-index/languages.ts
|
|
4974
|
-
import * as
|
|
4978
|
+
import * as path13 from "node:path";
|
|
4975
4979
|
function detectLang(file) {
|
|
4976
|
-
const base =
|
|
4980
|
+
const base = path13.basename(file);
|
|
4977
4981
|
const lowerBase = base.toLowerCase();
|
|
4978
4982
|
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
4979
4983
|
return "ts";
|
|
4980
4984
|
}
|
|
4981
4985
|
const special = SPECIAL_FILENAMES[lowerBase];
|
|
4982
4986
|
if (special) return special;
|
|
4983
|
-
const ext =
|
|
4987
|
+
const ext = path13.extname(base).toLowerCase();
|
|
4984
4988
|
if (!ext) return null;
|
|
4985
4989
|
return EXT_TO_LANG[ext] ?? null;
|
|
4986
4990
|
}
|
|
4987
4991
|
function isIndexablePath(file) {
|
|
4988
4992
|
return detectLang(file) !== null;
|
|
4989
4993
|
}
|
|
4990
|
-
|
|
4994
|
+
function languageFamily(lang) {
|
|
4995
|
+
return LANG_FAMILY[lang] ?? "other";
|
|
4996
|
+
}
|
|
4997
|
+
var EXT_TO_LANG, INDEXABLE_EXTENSIONS, SPECIAL_FILENAMES, LANG_FAMILY, LANG_FAMILY_ENTRIES;
|
|
4991
4998
|
var init_languages2 = __esm({
|
|
4992
4999
|
"src/codebase-index/languages.ts"() {
|
|
4993
5000
|
"use strict";
|
|
@@ -5078,6 +5085,52 @@ var init_languages2 = __esm({
|
|
|
5078
5085
|
procfile: "other",
|
|
5079
5086
|
justfile: "other"
|
|
5080
5087
|
};
|
|
5088
|
+
LANG_FAMILY = {
|
|
5089
|
+
// Single-compilation-unit family: a .vue/.svelte script block is JS/TS and
|
|
5090
|
+
// imports from — and is imported by — plain .ts files.
|
|
5091
|
+
ts: "js",
|
|
5092
|
+
tsx: "js",
|
|
5093
|
+
js: "js",
|
|
5094
|
+
jsx: "js",
|
|
5095
|
+
vue: "js",
|
|
5096
|
+
svelte: "js",
|
|
5097
|
+
go: "go",
|
|
5098
|
+
py: "py",
|
|
5099
|
+
rs: "rs",
|
|
5100
|
+
// The JVM resolves across languages: Kotlin and Scala call Java directly.
|
|
5101
|
+
java: "jvm",
|
|
5102
|
+
kotlin: "jvm",
|
|
5103
|
+
scala: "jvm",
|
|
5104
|
+
csharp: "dotnet",
|
|
5105
|
+
// A .h header is consumed by both C and C++ translation units.
|
|
5106
|
+
c: "c",
|
|
5107
|
+
cpp: "c",
|
|
5108
|
+
ruby: "ruby",
|
|
5109
|
+
php: "php",
|
|
5110
|
+
swift: "swift",
|
|
5111
|
+
dart: "dart",
|
|
5112
|
+
elixir: "elixir",
|
|
5113
|
+
haskell: "haskell",
|
|
5114
|
+
zig: "zig",
|
|
5115
|
+
lua: "lua",
|
|
5116
|
+
r: "r",
|
|
5117
|
+
shell: "shell",
|
|
5118
|
+
sql: "sql",
|
|
5119
|
+
json: "data",
|
|
5120
|
+
yaml: "data",
|
|
5121
|
+
toml: "data",
|
|
5122
|
+
html: "web",
|
|
5123
|
+
css: "web",
|
|
5124
|
+
proto: "proto",
|
|
5125
|
+
graphql: "graphql",
|
|
5126
|
+
md: "other",
|
|
5127
|
+
other: "other"
|
|
5128
|
+
};
|
|
5129
|
+
LANG_FAMILY_ENTRIES = Object.freeze(
|
|
5130
|
+
Object.entries(LANG_FAMILY).map(
|
|
5131
|
+
([lang, family]) => Object.freeze([lang, family])
|
|
5132
|
+
)
|
|
5133
|
+
);
|
|
5081
5134
|
}
|
|
5082
5135
|
});
|
|
5083
5136
|
|
|
@@ -5242,7 +5295,7 @@ function getTypeName(name) {
|
|
|
5242
5295
|
function deduplicateRefs(refs) {
|
|
5243
5296
|
const seen = /* @__PURE__ */ new Set();
|
|
5244
5297
|
return refs.filter((r) => {
|
|
5245
|
-
const key = `${r.toName}:${r.callType}:${r.line}`;
|
|
5298
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
5246
5299
|
if (seen.has(key)) return false;
|
|
5247
5300
|
seen.add(key);
|
|
5248
5301
|
return true;
|
|
@@ -5252,10 +5305,16 @@ function getImportSpecifierName(spec) {
|
|
|
5252
5305
|
return spec.propertyName?.text ?? spec.name.text;
|
|
5253
5306
|
}
|
|
5254
5307
|
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
5308
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
5255
5309
|
const clause = node.importClause;
|
|
5256
|
-
if (!clause)
|
|
5310
|
+
if (!clause) {
|
|
5311
|
+
if (module) {
|
|
5312
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
5313
|
+
}
|
|
5314
|
+
return;
|
|
5315
|
+
}
|
|
5257
5316
|
if (clause.name) {
|
|
5258
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5317
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
5259
5318
|
}
|
|
5260
5319
|
const bindings2 = clause.namedBindings;
|
|
5261
5320
|
if (!bindings2) return;
|
|
@@ -5265,26 +5324,40 @@ function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
|
5265
5324
|
fromId: 0,
|
|
5266
5325
|
toName: getImportSpecifierName(element),
|
|
5267
5326
|
callType: "import",
|
|
5268
|
-
line: lineNum
|
|
5327
|
+
line: lineNum,
|
|
5328
|
+
module
|
|
5269
5329
|
});
|
|
5270
5330
|
}
|
|
5271
5331
|
} else if (ts.isNamespaceImport(bindings2)) {
|
|
5272
|
-
refs.push({
|
|
5332
|
+
refs.push({
|
|
5333
|
+
fromId: 0,
|
|
5334
|
+
toName: bindings2.name.text,
|
|
5335
|
+
callType: "import",
|
|
5336
|
+
line: lineNum,
|
|
5337
|
+
module
|
|
5338
|
+
});
|
|
5273
5339
|
}
|
|
5274
5340
|
}
|
|
5341
|
+
function moduleSpecifierOf(node) {
|
|
5342
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
5343
|
+
}
|
|
5275
5344
|
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
5345
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
5276
5346
|
const clause = node.exportClause;
|
|
5277
5347
|
if (clause && ts.isNamespaceExport(clause)) {
|
|
5278
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
5348
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
5279
5349
|
return;
|
|
5280
5350
|
}
|
|
5281
5351
|
if (clause && ts.isNamedExports(clause)) {
|
|
5282
5352
|
for (const element of clause.elements) {
|
|
5283
5353
|
const originalName = element.propertyName?.text ?? element.name.text;
|
|
5284
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
|
|
5354
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
5285
5355
|
}
|
|
5286
5356
|
return;
|
|
5287
5357
|
}
|
|
5358
|
+
if (module) {
|
|
5359
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
5360
|
+
}
|
|
5288
5361
|
}
|
|
5289
5362
|
var ts, tsLoad, kindMapCache;
|
|
5290
5363
|
var init_ts_parser = __esm({
|
|
@@ -5296,6 +5369,82 @@ var init_ts_parser = __esm({
|
|
|
5296
5369
|
}
|
|
5297
5370
|
});
|
|
5298
5371
|
|
|
5372
|
+
// src/codebase-index/parser-output.ts
|
|
5373
|
+
function coerceSymbols(value) {
|
|
5374
|
+
if (!Array.isArray(value)) return [];
|
|
5375
|
+
return value.flatMap((entry) => {
|
|
5376
|
+
const candidate = entry;
|
|
5377
|
+
if (typeof candidate.name !== "string" || typeof candidate.kind !== "string") return [];
|
|
5378
|
+
return [
|
|
5379
|
+
{
|
|
5380
|
+
name: candidate.name,
|
|
5381
|
+
kind: candidate.kind,
|
|
5382
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
5383
|
+
col: typeof candidate.col === "number" ? candidate.col : 0,
|
|
5384
|
+
signature: typeof candidate.signature === "string" ? candidate.signature : "",
|
|
5385
|
+
scope: typeof candidate.scope === "string" ? candidate.scope : ""
|
|
5386
|
+
}
|
|
5387
|
+
];
|
|
5388
|
+
});
|
|
5389
|
+
}
|
|
5390
|
+
function coerceRefs(value, lang) {
|
|
5391
|
+
if (!Array.isArray(value)) return [];
|
|
5392
|
+
return value.flatMap((entry) => {
|
|
5393
|
+
const candidate = entry;
|
|
5394
|
+
if (typeof candidate.toName !== "string" || !candidate.toName) return [];
|
|
5395
|
+
if (typeof candidate.callType !== "string" || !CALL_TYPES.has(candidate.callType)) return [];
|
|
5396
|
+
const module = typeof candidate.module === "string" && candidate.module ? candidate.module : void 0;
|
|
5397
|
+
return [
|
|
5398
|
+
{
|
|
5399
|
+
fromId: 0,
|
|
5400
|
+
toName: candidate.toName,
|
|
5401
|
+
callType: candidate.callType,
|
|
5402
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
5403
|
+
lang,
|
|
5404
|
+
module
|
|
5405
|
+
}
|
|
5406
|
+
];
|
|
5407
|
+
});
|
|
5408
|
+
}
|
|
5409
|
+
function parseParserOutput(stdout, lang) {
|
|
5410
|
+
const trimmed = stdout.trim();
|
|
5411
|
+
if (!trimmed) return { symbols: [], refs: [] };
|
|
5412
|
+
let parsed;
|
|
5413
|
+
try {
|
|
5414
|
+
parsed = JSON.parse(trimmed);
|
|
5415
|
+
} catch {
|
|
5416
|
+
return { symbols: [], refs: [] };
|
|
5417
|
+
}
|
|
5418
|
+
if (Array.isArray(parsed)) return { symbols: coerceSymbols(parsed), refs: [] };
|
|
5419
|
+
const record = parsed;
|
|
5420
|
+
return {
|
|
5421
|
+
symbols: coerceSymbols(record.symbols),
|
|
5422
|
+
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
5423
|
+
};
|
|
5424
|
+
}
|
|
5425
|
+
function dedupeRefs(refs) {
|
|
5426
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5427
|
+
return refs.filter((ref) => {
|
|
5428
|
+
const key = `${ref.toName}:${ref.callType}:${ref.line}:${ref.module ?? ""}`;
|
|
5429
|
+
if (seen.has(key)) return false;
|
|
5430
|
+
seen.add(key);
|
|
5431
|
+
return true;
|
|
5432
|
+
});
|
|
5433
|
+
}
|
|
5434
|
+
var CALL_TYPES;
|
|
5435
|
+
var init_parser_output = __esm({
|
|
5436
|
+
"src/codebase-index/parser-output.ts"() {
|
|
5437
|
+
"use strict";
|
|
5438
|
+
CALL_TYPES = /* @__PURE__ */ new Set([
|
|
5439
|
+
"call",
|
|
5440
|
+
"type_ref",
|
|
5441
|
+
"inherit",
|
|
5442
|
+
"implement",
|
|
5443
|
+
"import"
|
|
5444
|
+
]);
|
|
5445
|
+
}
|
|
5446
|
+
});
|
|
5447
|
+
|
|
5299
5448
|
// src/codebase-index/spawn-gate.ts
|
|
5300
5449
|
function withSpawnGate(fn) {
|
|
5301
5450
|
const run = chain.then(fn, fn);
|
|
@@ -5321,8 +5470,8 @@ __export(go_parser_exports, {
|
|
|
5321
5470
|
});
|
|
5322
5471
|
import { spawn as spawn5 } from "node:child_process";
|
|
5323
5472
|
import * as os6 from "node:os";
|
|
5324
|
-
import * as
|
|
5325
|
-
import * as
|
|
5473
|
+
import * as path20 from "node:path";
|
|
5474
|
+
import * as fs15 from "node:fs/promises";
|
|
5326
5475
|
async function parseSymbols2(opts) {
|
|
5327
5476
|
const { file, content, lang } = opts;
|
|
5328
5477
|
try {
|
|
@@ -5330,7 +5479,8 @@ async function parseSymbols2(opts) {
|
|
|
5330
5479
|
if (parsed.symbols.length > 0) {
|
|
5331
5480
|
return parsed;
|
|
5332
5481
|
}
|
|
5333
|
-
|
|
5482
|
+
const fallback = fallbackParse(file, content, lang);
|
|
5483
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
5334
5484
|
} catch {
|
|
5335
5485
|
return fallbackParse(file, content, lang);
|
|
5336
5486
|
}
|
|
@@ -5394,9 +5544,9 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5394
5544
|
try {
|
|
5395
5545
|
let scriptPath = _cachedGoScriptPath;
|
|
5396
5546
|
if (!scriptPath) {
|
|
5397
|
-
const tmpDir = await
|
|
5398
|
-
scriptPath =
|
|
5399
|
-
await
|
|
5547
|
+
const tmpDir = await fs15.mkdtemp(path20.join(os6.tmpdir(), "ws-go-parse-"));
|
|
5548
|
+
scriptPath = path20.join(tmpDir, "parse.go");
|
|
5549
|
+
await fs15.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
5400
5550
|
_cachedGoScriptPath = scriptPath;
|
|
5401
5551
|
}
|
|
5402
5552
|
const goBinary = resolveWin32Command("go");
|
|
@@ -5438,8 +5588,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5438
5588
|
if (code !== 0 || !stdout.trim()) {
|
|
5439
5589
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
5440
5590
|
}
|
|
5441
|
-
const
|
|
5442
|
-
const symbols =
|
|
5591
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
5592
|
+
const symbols = rawSymbols.map((s) => ({
|
|
5443
5593
|
id: 0,
|
|
5444
5594
|
lang,
|
|
5445
5595
|
kind: s.kind,
|
|
@@ -5452,7 +5602,7 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
5452
5602
|
scope: s.scope ?? "",
|
|
5453
5603
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
5454
5604
|
}));
|
|
5455
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
5605
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
5456
5606
|
} catch {
|
|
5457
5607
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
5458
5608
|
}
|
|
@@ -5462,6 +5612,7 @@ var init_go_parser = __esm({
|
|
|
5462
5612
|
"src/codebase-index/go-parser.ts"() {
|
|
5463
5613
|
"use strict";
|
|
5464
5614
|
init_win32_resolve();
|
|
5615
|
+
init_parser_output();
|
|
5465
5616
|
init_spawn_gate();
|
|
5466
5617
|
init_languages2();
|
|
5467
5618
|
GO_PARSE_SCRIPT = `
|
|
@@ -5475,6 +5626,7 @@ import (
|
|
|
5475
5626
|
"go/token"
|
|
5476
5627
|
"io"
|
|
5477
5628
|
"os"
|
|
5629
|
+
"strconv"
|
|
5478
5630
|
"strings"
|
|
5479
5631
|
)
|
|
5480
5632
|
|
|
@@ -5487,16 +5639,34 @@ type Sym struct {
|
|
|
5487
5639
|
Scope string \`json:"scope"\`
|
|
5488
5640
|
}
|
|
5489
5641
|
|
|
5642
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
5643
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
5644
|
+
type Ref struct {
|
|
5645
|
+
ToName string \`json:"toName"\`
|
|
5646
|
+
CallType string \`json:"callType"\`
|
|
5647
|
+
Line int \`json:"line"\`
|
|
5648
|
+
Module string \`json:"module"\`
|
|
5649
|
+
}
|
|
5650
|
+
|
|
5651
|
+
type Result struct {
|
|
5652
|
+
Symbols []Sym \`json:"symbols"\`
|
|
5653
|
+
Refs []Ref \`json:"refs"\`
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
func emptyResult() string {
|
|
5657
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
5658
|
+
}
|
|
5659
|
+
|
|
5490
5660
|
func main() {
|
|
5491
5661
|
src, err := io.ReadAll(os.Stdin)
|
|
5492
5662
|
if err != nil {
|
|
5493
|
-
fmt.Print(
|
|
5663
|
+
fmt.Print(emptyResult())
|
|
5494
5664
|
return
|
|
5495
5665
|
}
|
|
5496
5666
|
fset := token.NewFileSet()
|
|
5497
5667
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
5498
5668
|
if err != nil {
|
|
5499
|
-
fmt.Print(
|
|
5669
|
+
fmt.Print(emptyResult())
|
|
5500
5670
|
return
|
|
5501
5671
|
}
|
|
5502
5672
|
|
|
@@ -5560,9 +5730,43 @@ func main() {
|
|
|
5560
5730
|
}
|
|
5561
5731
|
}
|
|
5562
5732
|
|
|
5563
|
-
|
|
5733
|
+
refs := []Ref{}
|
|
5734
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
5735
|
+
switch expr := n.(type) {
|
|
5736
|
+
case *ast.CallExpr:
|
|
5737
|
+
line := fset.Position(expr.Pos()).Line
|
|
5738
|
+
switch fun := expr.Fun.(type) {
|
|
5739
|
+
case *ast.Ident:
|
|
5740
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
5741
|
+
case *ast.SelectorExpr:
|
|
5742
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
5743
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
5744
|
+
// and Python extractors' call refs do.
|
|
5745
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
5746
|
+
}
|
|
5747
|
+
case *ast.ImportSpec:
|
|
5748
|
+
if expr.Path != nil {
|
|
5749
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
5750
|
+
line := fset.Position(expr.Pos()).Line
|
|
5751
|
+
// A Go import names a package, not a symbol; the package's
|
|
5752
|
+
// last path segment is the name it is referenced by.
|
|
5753
|
+
name := importPath
|
|
5754
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
5755
|
+
name = importPath[idx+1:]
|
|
5756
|
+
}
|
|
5757
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
return true
|
|
5762
|
+
})
|
|
5763
|
+
|
|
5764
|
+
if syms == nil {
|
|
5765
|
+
syms = []Sym{}
|
|
5766
|
+
}
|
|
5767
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
5564
5768
|
if err != nil {
|
|
5565
|
-
fmt.Print(
|
|
5769
|
+
fmt.Print(emptyResult())
|
|
5566
5770
|
return
|
|
5567
5771
|
}
|
|
5568
5772
|
fmt.Print(string(data))
|
|
@@ -5723,16 +5927,23 @@ function looksBinary(content) {
|
|
|
5723
5927
|
}
|
|
5724
5928
|
return bad / sample.length > 0.1;
|
|
5725
5929
|
}
|
|
5726
|
-
function
|
|
5727
|
-
|
|
5728
|
-
let
|
|
5729
|
-
|
|
5730
|
-
if (content.charCodeAt(i) === 10) {
|
|
5731
|
-
line++;
|
|
5732
|
-
lastNl = i;
|
|
5733
|
-
}
|
|
5930
|
+
function newlineOffsets2(content) {
|
|
5931
|
+
const offsets = [];
|
|
5932
|
+
for (let i = 0; i < content.length; i++) {
|
|
5933
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
5734
5934
|
}
|
|
5735
|
-
return
|
|
5935
|
+
return offsets;
|
|
5936
|
+
}
|
|
5937
|
+
function lineColAt(offsets, index) {
|
|
5938
|
+
let low = 0;
|
|
5939
|
+
let high = offsets.length;
|
|
5940
|
+
while (low < high) {
|
|
5941
|
+
const mid = low + high >>> 1;
|
|
5942
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
5943
|
+
else high = mid;
|
|
5944
|
+
}
|
|
5945
|
+
const lastNl = low > 0 ? offsets[low - 1] : -1;
|
|
5946
|
+
return { line: low + 1, col: index - lastNl };
|
|
5736
5947
|
}
|
|
5737
5948
|
function parseGeneric2(opts) {
|
|
5738
5949
|
const { file, lang } = opts;
|
|
@@ -5745,6 +5956,7 @@ function parseGeneric2(opts) {
|
|
|
5745
5956
|
const patterns = patternsFor(lang);
|
|
5746
5957
|
const symbols = [];
|
|
5747
5958
|
const seen = /* @__PURE__ */ new Set();
|
|
5959
|
+
const nlOffsets = newlineOffsets2(content);
|
|
5748
5960
|
for (const pattern of patterns) {
|
|
5749
5961
|
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
5750
5962
|
re.lastIndex = 0;
|
|
@@ -5758,7 +5970,7 @@ function parseGeneric2(opts) {
|
|
|
5758
5970
|
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
5759
5971
|
continue;
|
|
5760
5972
|
}
|
|
5761
|
-
const { line, col } = lineColAt(
|
|
5973
|
+
const { line, col } = lineColAt(nlOffsets, match.index ?? 0);
|
|
5762
5974
|
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
5763
5975
|
if (seen.has(key)) continue;
|
|
5764
5976
|
seen.add(key);
|
|
@@ -5914,9 +6126,13 @@ var init_generic_parser = __esm({
|
|
|
5914
6126
|
],
|
|
5915
6127
|
elixir: [
|
|
5916
6128
|
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
5917
|
-
|
|
6129
|
+
// Dotted module names must be captured whole: `alias Foo.Bar` resolves
|
|
6130
|
+
// against this symbol, and a `Foo`-only capture never matches it.
|
|
6131
|
+
{ re: /\bdefmodule\s+([A-Z][\w.]*)/g, kind: "namespace" }
|
|
5918
6132
|
],
|
|
5919
6133
|
haskell: [
|
|
6134
|
+
// Target of `import Data.List`.
|
|
6135
|
+
{ re: /^module\s+([A-Z][\w.]*)/gm, kind: "namespace" },
|
|
5920
6136
|
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
5921
6137
|
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
5922
6138
|
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
@@ -6007,9 +6223,9 @@ __export(py_parser_exports, {
|
|
|
6007
6223
|
parseSymbols: () => parseSymbols4
|
|
6008
6224
|
});
|
|
6009
6225
|
import { spawn as spawn6 } from "node:child_process";
|
|
6010
|
-
import * as
|
|
6226
|
+
import * as fs16 from "node:fs/promises";
|
|
6011
6227
|
import * as os7 from "node:os";
|
|
6012
|
-
import * as
|
|
6228
|
+
import * as path21 from "node:path";
|
|
6013
6229
|
async function parseSymbols4(opts) {
|
|
6014
6230
|
const { file, content, lang } = opts;
|
|
6015
6231
|
try {
|
|
@@ -6087,10 +6303,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
6087
6303
|
async function syncPyParse(filePath, content, lang) {
|
|
6088
6304
|
try {
|
|
6089
6305
|
if (!_cachedScriptPath) {
|
|
6090
|
-
const tmpDir =
|
|
6091
|
-
await
|
|
6092
|
-
_cachedScriptPath =
|
|
6093
|
-
await
|
|
6306
|
+
const tmpDir = path21.join(os7.tmpdir(), "ws-py-parse");
|
|
6307
|
+
await fs16.mkdir(tmpDir, { recursive: true });
|
|
6308
|
+
_cachedScriptPath = path21.join(tmpDir, "parse.py");
|
|
6309
|
+
await fs16.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
6094
6310
|
}
|
|
6095
6311
|
cachedPyBinary ??= resolvePython();
|
|
6096
6312
|
const pyBinary = await cachedPyBinary;
|
|
@@ -6104,7 +6320,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
6104
6320
|
if (code !== 0 || !stdout.trim()) {
|
|
6105
6321
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
6106
6322
|
}
|
|
6107
|
-
const raw =
|
|
6323
|
+
const { symbols: raw, refs } = parseParserOutput(stdout, lang);
|
|
6108
6324
|
const symbols = raw.map((s) => ({
|
|
6109
6325
|
id: 0,
|
|
6110
6326
|
lang,
|
|
@@ -6118,7 +6334,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
6118
6334
|
scope: s.scope ?? "",
|
|
6119
6335
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
6120
6336
|
}));
|
|
6121
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
6337
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
6122
6338
|
} catch {
|
|
6123
6339
|
return null;
|
|
6124
6340
|
}
|
|
@@ -6129,6 +6345,7 @@ var init_py_parser = __esm({
|
|
|
6129
6345
|
"use strict";
|
|
6130
6346
|
init_win32_resolve();
|
|
6131
6347
|
init_generic_parser();
|
|
6348
|
+
init_parser_output();
|
|
6132
6349
|
init_spawn_gate();
|
|
6133
6350
|
init_languages2();
|
|
6134
6351
|
PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
@@ -6190,7 +6407,18 @@ class Sym:
|
|
|
6190
6407
|
def is_private(name):
|
|
6191
6408
|
return name.startswith("__") and not name.endswith("__")
|
|
6192
6409
|
|
|
6410
|
+
def leaf_name(node):
|
|
6411
|
+
# Declared name of the callee: \`join\` of \`os.path.join\`. Matches how the
|
|
6412
|
+
# TypeScript and Go extractors record call refs, so resolution behaves the
|
|
6413
|
+
# same across languages.
|
|
6414
|
+
if isinstance(node, ast.Attribute):
|
|
6415
|
+
return node.attr
|
|
6416
|
+
if isinstance(node, ast.Name):
|
|
6417
|
+
return node.id
|
|
6418
|
+
return get_name(node).split(".")[-1]
|
|
6419
|
+
|
|
6193
6420
|
syms = []
|
|
6421
|
+
refs = []
|
|
6194
6422
|
errors = []
|
|
6195
6423
|
|
|
6196
6424
|
try:
|
|
@@ -6198,7 +6426,7 @@ try:
|
|
|
6198
6426
|
tree = ast.parse(source, filename=sys.argv[1])
|
|
6199
6427
|
except Exception as e:
|
|
6200
6428
|
errors.append(str(e))
|
|
6201
|
-
print("[]")
|
|
6429
|
+
print(json.dumps({"symbols": [], "refs": []}))
|
|
6202
6430
|
sys.exit(0)
|
|
6203
6431
|
|
|
6204
6432
|
# Module-level scope
|
|
@@ -6332,7 +6560,42 @@ class ModuleVisitor(ast.NodeVisitor):
|
|
|
6332
6560
|
visitor = ModuleVisitor()
|
|
6333
6561
|
visitor.visit(tree)
|
|
6334
6562
|
|
|
6335
|
-
|
|
6563
|
+
# Refs need a separate full walk: ModuleVisitor deliberately does not descend
|
|
6564
|
+
# into function bodies (it would index locals as symbols), but that is exactly
|
|
6565
|
+
# where the calls are.
|
|
6566
|
+
for node in ast.walk(tree):
|
|
6567
|
+
if isinstance(node, ast.Call):
|
|
6568
|
+
name = leaf_name(node.func)
|
|
6569
|
+
if name:
|
|
6570
|
+
refs.append({"toName": name, "callType": "call", "line": node.lineno})
|
|
6571
|
+
elif isinstance(node, ast.Import):
|
|
6572
|
+
for alias in node.names:
|
|
6573
|
+
refs.append({
|
|
6574
|
+
"toName": alias.name.split(".")[-1],
|
|
6575
|
+
"callType": "import",
|
|
6576
|
+
"line": node.lineno,
|
|
6577
|
+
"module": alias.name,
|
|
6578
|
+
})
|
|
6579
|
+
elif isinstance(node, ast.ImportFrom):
|
|
6580
|
+
# PEP 328: node.level is the number of leading dots. Preserving them is
|
|
6581
|
+
# what lets the resolver walk up from the importing file's package \u2014
|
|
6582
|
+
# dropping them made \`from .foo import X\` indistinguishable from an
|
|
6583
|
+
# absolute \`foo\`.
|
|
6584
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
6585
|
+
for alias in node.names:
|
|
6586
|
+
refs.append({
|
|
6587
|
+
"toName": alias.name,
|
|
6588
|
+
"callType": "import",
|
|
6589
|
+
"line": node.lineno,
|
|
6590
|
+
"module": module,
|
|
6591
|
+
})
|
|
6592
|
+
elif isinstance(node, ast.ClassDef):
|
|
6593
|
+
for base in node.bases:
|
|
6594
|
+
name = leaf_name(base)
|
|
6595
|
+
if name:
|
|
6596
|
+
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
6597
|
+
|
|
6598
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
6336
6599
|
`;
|
|
6337
6600
|
_cachedScriptPath = null;
|
|
6338
6601
|
}
|
|
@@ -6345,107 +6608,10 @@ __export(rs_parser_exports, {
|
|
|
6345
6608
|
parseSymbols: () => parseSymbols5
|
|
6346
6609
|
});
|
|
6347
6610
|
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
6348
|
-
import { execFile, spawn as spawn7 } from "node:child_process";
|
|
6349
|
-
import * as fs16 from "node:fs/promises";
|
|
6350
|
-
import * as path21 from "node:path";
|
|
6351
6611
|
async function parseSymbols5(opts) {
|
|
6352
6612
|
const { file, content, lang } = opts;
|
|
6353
|
-
const nativeAvailable = await checkNativeParser();
|
|
6354
|
-
if (nativeAvailable) {
|
|
6355
|
-
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
6356
|
-
if (result) return result;
|
|
6357
|
-
}
|
|
6358
6613
|
return regexParse({ file, content, lang });
|
|
6359
6614
|
}
|
|
6360
|
-
function probe(command, args) {
|
|
6361
|
-
return new Promise((resolve17, reject) => {
|
|
6362
|
-
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
6363
|
-
if (error) reject(error);
|
|
6364
|
-
else resolve17();
|
|
6365
|
-
});
|
|
6366
|
-
});
|
|
6367
|
-
}
|
|
6368
|
-
function checkNativeParser() {
|
|
6369
|
-
nativeParserAvailability ??= (async () => {
|
|
6370
|
-
try {
|
|
6371
|
-
await probe("rustc", ["--version"]);
|
|
6372
|
-
const toolsDir = path21.join(process.cwd(), "tools");
|
|
6373
|
-
await probe(
|
|
6374
|
-
"cargo",
|
|
6375
|
-
[
|
|
6376
|
-
"metadata",
|
|
6377
|
-
"--no-deps",
|
|
6378
|
-
"--format-version",
|
|
6379
|
-
"1",
|
|
6380
|
-
"--manifest-path",
|
|
6381
|
-
path21.join(toolsDir, "Cargo.toml")
|
|
6382
|
-
]
|
|
6383
|
-
);
|
|
6384
|
-
return true;
|
|
6385
|
-
} catch {
|
|
6386
|
-
return false;
|
|
6387
|
-
}
|
|
6388
|
-
})();
|
|
6389
|
-
return nativeParserAvailability;
|
|
6390
|
-
}
|
|
6391
|
-
async function tryNativeParse(file, content) {
|
|
6392
|
-
try {
|
|
6393
|
-
const toolsDir = path21.join(process.cwd(), "tools");
|
|
6394
|
-
const crateDir = path21.join(toolsDir, "syn-parser");
|
|
6395
|
-
const tmpFile = path21.join(crateDir, "src", "input.rs");
|
|
6396
|
-
await fs16.writeFile(tmpFile, content, "utf8");
|
|
6397
|
-
const cargoBinary = resolveWin32Command("cargo");
|
|
6398
|
-
const result = await new Promise(
|
|
6399
|
-
(resolve17, reject) => {
|
|
6400
|
-
let settled = false;
|
|
6401
|
-
const proc = spawn7(
|
|
6402
|
-
cargoBinary,
|
|
6403
|
-
["run", "--manifest-path", path21.join(toolsDir, "Cargo.toml")],
|
|
6404
|
-
{
|
|
6405
|
-
cwd: process.cwd(),
|
|
6406
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
6407
|
-
windowsHide: true
|
|
6408
|
-
}
|
|
6409
|
-
);
|
|
6410
|
-
proc.on("error", (err) => {
|
|
6411
|
-
if (settled) return;
|
|
6412
|
-
settled = true;
|
|
6413
|
-
reject(err);
|
|
6414
|
-
});
|
|
6415
|
-
let stdout2 = "";
|
|
6416
|
-
proc.stdout?.on("data", (chunk) => {
|
|
6417
|
-
stdout2 += chunk.toString();
|
|
6418
|
-
});
|
|
6419
|
-
proc.stderr?.resume();
|
|
6420
|
-
const timer = setTimeout(() => {
|
|
6421
|
-
if (settled) return;
|
|
6422
|
-
settled = true;
|
|
6423
|
-
proc.kill("SIGKILL");
|
|
6424
|
-
reject(new Error("timeout"));
|
|
6425
|
-
}, 15e3);
|
|
6426
|
-
timer.unref?.();
|
|
6427
|
-
proc.on("close", (c) => {
|
|
6428
|
-
if (settled) return;
|
|
6429
|
-
settled = true;
|
|
6430
|
-
clearTimeout(timer);
|
|
6431
|
-
resolve17({ code: c, stdout: stdout2 });
|
|
6432
|
-
});
|
|
6433
|
-
}
|
|
6434
|
-
);
|
|
6435
|
-
const { code, stdout } = result;
|
|
6436
|
-
if (code === 0 && stdout.trim()) {
|
|
6437
|
-
const symbols = JSON.parse(stdout.trim());
|
|
6438
|
-
return {
|
|
6439
|
-
file,
|
|
6440
|
-
lang: "rs",
|
|
6441
|
-
symbols: symbols.map((s) => ({ ...s, id: 0, lang: "rs" })),
|
|
6442
|
-
mtimeMs: Date.now()
|
|
6443
|
-
};
|
|
6444
|
-
}
|
|
6445
|
-
} catch {
|
|
6446
|
-
}
|
|
6447
|
-
return null;
|
|
6448
|
-
}
|
|
6449
6615
|
function regexParse(opts) {
|
|
6450
6616
|
const { file, content, lang } = opts;
|
|
6451
6617
|
const symbols = [];
|
|
@@ -6501,12 +6667,10 @@ function regexParse(opts) {
|
|
|
6501
6667
|
});
|
|
6502
6668
|
return { file, lang, symbols: deduped, mtimeMs: Date.now() };
|
|
6503
6669
|
}
|
|
6504
|
-
var
|
|
6670
|
+
var RS_PATTERNS;
|
|
6505
6671
|
var init_rs_parser = __esm({
|
|
6506
6672
|
"src/codebase-index/rs-parser.ts"() {
|
|
6507
6673
|
"use strict";
|
|
6508
|
-
init_win32_resolve();
|
|
6509
|
-
init_spawn_gate();
|
|
6510
6674
|
init_languages2();
|
|
6511
6675
|
RS_PATTERNS = [
|
|
6512
6676
|
{ regex: /fn\s+(\w+)\s*\([^)]*\)/g, kind: "function" },
|
|
@@ -8959,7 +9123,9 @@ async function executeSingle(call, ctx, governedExecute) {
|
|
|
8959
9123
|
executionMs: Date.now() - start
|
|
8960
9124
|
};
|
|
8961
9125
|
}
|
|
8962
|
-
const tool = ctx.tools.find(
|
|
9126
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find(
|
|
9127
|
+
(candidate) => candidate.name === call.tool
|
|
9128
|
+
);
|
|
8963
9129
|
if (!tool) {
|
|
8964
9130
|
return {
|
|
8965
9131
|
tool: call.tool,
|
|
@@ -9181,7 +9347,7 @@ function parsePrivateOriginAllowlist(raw) {
|
|
|
9181
9347
|
if (!raw?.trim()) return [];
|
|
9182
9348
|
const origins = /* @__PURE__ */ new Set();
|
|
9183
9349
|
for (const entry of raw.split(",")) {
|
|
9184
|
-
const candidate = entry.trim();
|
|
9350
|
+
const candidate = entry.trim().replace(/^["']+|["']+$/gu, "");
|
|
9185
9351
|
if (!candidate) continue;
|
|
9186
9352
|
const url = parseBrowserUrl(candidate, true);
|
|
9187
9353
|
if (url.pathname !== "/" || url.search || url.hash) {
|
|
@@ -10321,7 +10487,7 @@ async function shutdownBrowserTools() {
|
|
|
10321
10487
|
|
|
10322
10488
|
// src/codebase-index/project-server-client.ts
|
|
10323
10489
|
import { spawn as spawn4 } from "node:child_process";
|
|
10324
|
-
import * as
|
|
10490
|
+
import * as fs13 from "node:fs";
|
|
10325
10491
|
import * as net3 from "node:net";
|
|
10326
10492
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10327
10493
|
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
@@ -10411,23 +10577,23 @@ function resetIndexCircuitBreaker() {
|
|
|
10411
10577
|
|
|
10412
10578
|
// src/codebase-index/project-server-endpoint.ts
|
|
10413
10579
|
import { createHash as createHash4 } from "node:crypto";
|
|
10414
|
-
import * as
|
|
10580
|
+
import * as fs12 from "node:fs";
|
|
10415
10581
|
import * as os5 from "node:os";
|
|
10416
|
-
import * as
|
|
10582
|
+
import * as path17 from "node:path";
|
|
10417
10583
|
import { fileURLToPath } from "node:url";
|
|
10418
10584
|
import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
|
|
10419
10585
|
|
|
10420
10586
|
// src/codebase-index/writer.ts
|
|
10421
10587
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
10422
|
-
import * as
|
|
10423
|
-
import * as
|
|
10588
|
+
import * as fs11 from "node:fs";
|
|
10589
|
+
import * as path16 from "node:path";
|
|
10424
10590
|
|
|
10425
10591
|
// src/codebase-index/bm25.ts
|
|
10426
10592
|
var K1 = 1.5;
|
|
10427
10593
|
var B = 0.75;
|
|
10594
|
+
var TOKENISE_RE = new RegExp("[^\\p{L}\\p{N}$']", "gu");
|
|
10428
10595
|
function tokenise(text) {
|
|
10429
|
-
|
|
10430
|
-
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
10596
|
+
return text.replace(TOKENISE_RE, " ").toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
10431
10597
|
}
|
|
10432
10598
|
function splitName(name) {
|
|
10433
10599
|
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();
|
|
@@ -10511,6 +10677,9 @@ var Bm25Index = class {
|
|
|
10511
10677
|
}
|
|
10512
10678
|
};
|
|
10513
10679
|
|
|
10680
|
+
// src/codebase-index/writer.ts
|
|
10681
|
+
init_languages2();
|
|
10682
|
+
|
|
10514
10683
|
// src/codebase-index/lsp-kind.ts
|
|
10515
10684
|
function lspKindToInternalKind(k) {
|
|
10516
10685
|
switch (k) {
|
|
@@ -10545,7 +10714,7 @@ function lspKindToInternalKind(k) {
|
|
|
10545
10714
|
}
|
|
10546
10715
|
|
|
10547
10716
|
// src/codebase-index/schema.ts
|
|
10548
|
-
var SCHEMA_VERSION =
|
|
10717
|
+
var SCHEMA_VERSION = 4;
|
|
10549
10718
|
|
|
10550
10719
|
// src/codebase-index/sqlite-runtime.ts
|
|
10551
10720
|
import { createRequire } from "node:module";
|
|
@@ -10616,7 +10785,7 @@ function runSqliteWithRetry(fn) {
|
|
|
10616
10785
|
|
|
10617
10786
|
// src/codebase-index/writer-admin.ts
|
|
10618
10787
|
import * as fs9 from "node:fs";
|
|
10619
|
-
import * as
|
|
10788
|
+
import * as path14 from "node:path";
|
|
10620
10789
|
var DB_FILE = "index.db";
|
|
10621
10790
|
function getAllIndexableWithStatement(stmt) {
|
|
10622
10791
|
return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
@@ -10675,7 +10844,7 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
10675
10844
|
}
|
|
10676
10845
|
function getIndexDbSizeBytes(indexDir) {
|
|
10677
10846
|
try {
|
|
10678
|
-
return fs9.statSync(
|
|
10847
|
+
return fs9.statSync(path14.join(indexDir, DB_FILE)).size;
|
|
10679
10848
|
} catch {
|
|
10680
10849
|
return 0;
|
|
10681
10850
|
}
|
|
@@ -10726,49 +10895,345 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
10726
10895
|
}
|
|
10727
10896
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
10728
10897
|
if (refs.length === 0) return;
|
|
10729
|
-
const chunkSize = Math.max(1, Math.floor(maxSqlVars /
|
|
10898
|
+
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 8));
|
|
10730
10899
|
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
10731
10900
|
const chunk = refs.slice(i, i + chunkSize);
|
|
10732
|
-
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
10901
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
10733
10902
|
const insert = stmt(
|
|
10734
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line
|
|
10903
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
10904
|
+
VALUES ${placeholders}`
|
|
10735
10905
|
);
|
|
10736
10906
|
const binds = [];
|
|
10737
10907
|
for (const ref of chunk) {
|
|
10738
|
-
binds.push(
|
|
10908
|
+
binds.push(
|
|
10909
|
+
ref.fromId,
|
|
10910
|
+
ref.toName,
|
|
10911
|
+
ref.toId ?? null,
|
|
10912
|
+
ref.callType,
|
|
10913
|
+
ref.line,
|
|
10914
|
+
ref.lang ?? "",
|
|
10915
|
+
ref.module ?? null,
|
|
10916
|
+
ref.toFile ?? null
|
|
10917
|
+
);
|
|
10739
10918
|
}
|
|
10740
10919
|
insert.run(...binds);
|
|
10741
10920
|
}
|
|
10742
10921
|
}
|
|
10743
10922
|
|
|
10744
|
-
// src/codebase-index/writer-graph-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10923
|
+
// src/codebase-index/writer-graph-reader.ts
|
|
10924
|
+
init_languages2();
|
|
10925
|
+
|
|
10926
|
+
// src/codebase-index/module-roots.ts
|
|
10927
|
+
init_languages2();
|
|
10928
|
+
import * as fs10 from "node:fs/promises";
|
|
10929
|
+
import * as path15 from "node:path";
|
|
10930
|
+
function toPortablePath(file) {
|
|
10931
|
+
return file.replace(/\\/g, "/");
|
|
10932
|
+
}
|
|
10933
|
+
async function readTextIfPresent(file) {
|
|
10934
|
+
try {
|
|
10935
|
+
return await fs10.readFile(file, "utf8");
|
|
10936
|
+
} catch {
|
|
10937
|
+
return void 0;
|
|
10938
|
+
}
|
|
10939
|
+
}
|
|
10940
|
+
function parsePackageJsonName(source) {
|
|
10941
|
+
try {
|
|
10942
|
+
const parsed = JSON.parse(source);
|
|
10943
|
+
return typeof parsed.name === "string" && parsed.name ? parsed.name : void 0;
|
|
10944
|
+
} catch {
|
|
10945
|
+
return void 0;
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
function parseGoModulePath(source) {
|
|
10949
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
10950
|
+
const line = rawLine.replace(/\/\/.*$/, "").trim();
|
|
10951
|
+
const match = /^module\s+(\S+)/.exec(line);
|
|
10952
|
+
if (match?.[1]) return match[1].replace(/^["']|["']$/g, "");
|
|
10953
|
+
}
|
|
10954
|
+
return void 0;
|
|
10955
|
+
}
|
|
10956
|
+
function parseTomlTableName(source, tables) {
|
|
10957
|
+
let current = "";
|
|
10958
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
10959
|
+
const line = rawLine.replace(/#.*$/, "").trim();
|
|
10960
|
+
if (line.startsWith("[[")) {
|
|
10961
|
+
current = "\0";
|
|
10962
|
+
continue;
|
|
10963
|
+
}
|
|
10964
|
+
const table = /^\[([^\]]+)\]$/.exec(line);
|
|
10965
|
+
if (table?.[1]) {
|
|
10966
|
+
current = table[1].trim();
|
|
10967
|
+
continue;
|
|
10968
|
+
}
|
|
10969
|
+
if (!tables.includes(current)) continue;
|
|
10970
|
+
const match = /^name\s*=\s*["']([^"']+)["']/.exec(line);
|
|
10971
|
+
if (match?.[1]) return match[1];
|
|
10972
|
+
}
|
|
10973
|
+
return void 0;
|
|
10974
|
+
}
|
|
10975
|
+
function parsePomArtifactId(source) {
|
|
10976
|
+
const withoutParent = source.replace(/<parent>[\s\S]*?<\/parent>/g, "");
|
|
10977
|
+
return /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(withoutParent)?.[1];
|
|
10978
|
+
}
|
|
10979
|
+
var LANGS_BY_KIND = {
|
|
10980
|
+
npm: ["ts", "tsx", "js", "jsx", "vue", "svelte"],
|
|
10981
|
+
cargo: ["rs"],
|
|
10982
|
+
go: ["go"],
|
|
10983
|
+
python: ["py"],
|
|
10984
|
+
maven: ["java", "kotlin", "scala"],
|
|
10985
|
+
gradle: ["java", "kotlin", "scala"],
|
|
10986
|
+
dotnet: ["csharp"]
|
|
10987
|
+
};
|
|
10988
|
+
function ancestorsOf(dir, stopAt) {
|
|
10989
|
+
const out = [];
|
|
10990
|
+
let current = dir;
|
|
10991
|
+
for (; ; ) {
|
|
10992
|
+
out.push(current);
|
|
10993
|
+
if (current === stopAt || current.length <= stopAt.length) break;
|
|
10994
|
+
const parent = path15.posix.dirname(current);
|
|
10995
|
+
if (parent === current) break;
|
|
10996
|
+
current = parent;
|
|
10997
|
+
}
|
|
10998
|
+
return out;
|
|
10999
|
+
}
|
|
11000
|
+
var MARKER_PROBES = [
|
|
11001
|
+
{
|
|
11002
|
+
kind: "npm",
|
|
11003
|
+
file: "package.json",
|
|
11004
|
+
build: (dir, source) => {
|
|
11005
|
+
const name = parsePackageJsonName(source) ?? path15.posix.basename(dir);
|
|
11006
|
+
return { name, importPath: name, sourceRoots: [dir] };
|
|
11007
|
+
}
|
|
11008
|
+
},
|
|
11009
|
+
{
|
|
11010
|
+
kind: "cargo",
|
|
11011
|
+
file: "Cargo.toml",
|
|
11012
|
+
build: (dir, source) => {
|
|
11013
|
+
const name = parseTomlTableName(source, ["package"]);
|
|
11014
|
+
if (!name) return void 0;
|
|
11015
|
+
return {
|
|
11016
|
+
name: `crate:${name}`,
|
|
11017
|
+
// Rust paths use underscores where crate names often use dashes.
|
|
11018
|
+
importPath: name.replace(/-/g, "_"),
|
|
11019
|
+
sourceRoots: [path15.posix.join(dir, "src")]
|
|
11020
|
+
};
|
|
11021
|
+
}
|
|
11022
|
+
},
|
|
11023
|
+
{
|
|
11024
|
+
kind: "go",
|
|
11025
|
+
file: "go.mod",
|
|
11026
|
+
build: (dir, source) => {
|
|
11027
|
+
const modulePath = parseGoModulePath(source);
|
|
11028
|
+
if (!modulePath) return void 0;
|
|
11029
|
+
return { name: `go:${modulePath}`, importPath: modulePath, sourceRoots: [dir] };
|
|
11030
|
+
}
|
|
11031
|
+
},
|
|
11032
|
+
{
|
|
11033
|
+
kind: "python",
|
|
11034
|
+
file: "pyproject.toml",
|
|
11035
|
+
build: (dir, source) => {
|
|
11036
|
+
const name = parseTomlTableName(source, ["project", "tool.poetry"]) ?? path15.posix.basename(dir);
|
|
11037
|
+
return {
|
|
11038
|
+
name: `py:${name}`,
|
|
11039
|
+
importPath: void 0,
|
|
11040
|
+
// `src/` layout is the packaging-guide default; the root itself covers
|
|
11041
|
+
// the flat layout. Both are probed, missing ones simply never match.
|
|
11042
|
+
sourceRoots: [path15.posix.join(dir, "src"), dir]
|
|
11043
|
+
};
|
|
11044
|
+
}
|
|
11045
|
+
},
|
|
11046
|
+
{
|
|
11047
|
+
kind: "python",
|
|
11048
|
+
file: "setup.py",
|
|
11049
|
+
build: (dir) => ({
|
|
11050
|
+
name: `py:${path15.posix.basename(dir)}`,
|
|
11051
|
+
importPath: void 0,
|
|
11052
|
+
sourceRoots: [path15.posix.join(dir, "src"), dir]
|
|
11053
|
+
})
|
|
11054
|
+
},
|
|
11055
|
+
{
|
|
11056
|
+
kind: "maven",
|
|
11057
|
+
file: "pom.xml",
|
|
11058
|
+
build: (dir, source) => {
|
|
11059
|
+
const artifactId = parsePomArtifactId(source) ?? path15.posix.basename(dir);
|
|
11060
|
+
return {
|
|
11061
|
+
name: `mvn:${artifactId}`,
|
|
11062
|
+
importPath: void 0,
|
|
11063
|
+
sourceRoots: [
|
|
11064
|
+
path15.posix.join(dir, "src/main/java"),
|
|
11065
|
+
path15.posix.join(dir, "src/main/kotlin"),
|
|
11066
|
+
path15.posix.join(dir, "src/main/scala"),
|
|
11067
|
+
path15.posix.join(dir, "src/test/java")
|
|
11068
|
+
]
|
|
11069
|
+
};
|
|
11070
|
+
}
|
|
11071
|
+
},
|
|
11072
|
+
{
|
|
11073
|
+
kind: "gradle",
|
|
11074
|
+
file: "build.gradle",
|
|
11075
|
+
build: (dir) => buildGradleRoot(dir)
|
|
11076
|
+
},
|
|
11077
|
+
{
|
|
11078
|
+
kind: "gradle",
|
|
11079
|
+
file: "build.gradle.kts",
|
|
11080
|
+
build: (dir) => buildGradleRoot(dir)
|
|
11081
|
+
}
|
|
11082
|
+
];
|
|
11083
|
+
function buildGradleRoot(dir) {
|
|
11084
|
+
return {
|
|
11085
|
+
name: `gradle:${path15.posix.basename(dir)}`,
|
|
11086
|
+
importPath: void 0,
|
|
11087
|
+
sourceRoots: [
|
|
11088
|
+
path15.posix.join(dir, "src/main/java"),
|
|
11089
|
+
path15.posix.join(dir, "src/main/kotlin"),
|
|
11090
|
+
path15.posix.join(dir, "src/main/scala")
|
|
11091
|
+
]
|
|
11092
|
+
};
|
|
11093
|
+
}
|
|
11094
|
+
async function probeDotnetRoot(dir) {
|
|
11095
|
+
let entries;
|
|
11096
|
+
try {
|
|
11097
|
+
entries = await fs10.readdir(dir);
|
|
11098
|
+
} catch {
|
|
11099
|
+
return void 0;
|
|
11100
|
+
}
|
|
11101
|
+
const project = entries.find((entry) => entry.toLowerCase().endsWith(".csproj"));
|
|
11102
|
+
if (!project) return void 0;
|
|
11103
|
+
const name = project.slice(0, -".csproj".length);
|
|
11104
|
+
return {
|
|
11105
|
+
dir,
|
|
11106
|
+
kind: "dotnet",
|
|
11107
|
+
name: `csproj:${name}`,
|
|
11108
|
+
importPath: void 0,
|
|
11109
|
+
sourceRoots: [dir]
|
|
11110
|
+
};
|
|
11111
|
+
}
|
|
11112
|
+
async function detectModuleRoots(projectRoot, files) {
|
|
11113
|
+
const root = toPortablePath(projectRoot).replace(/\/+$/, "");
|
|
11114
|
+
const langsByDir = /* @__PURE__ */ new Map();
|
|
11115
|
+
for (const file of files) {
|
|
11116
|
+
const portable = toPortablePath(file);
|
|
11117
|
+
const lang = detectLang(portable);
|
|
11118
|
+
if (!lang) continue;
|
|
11119
|
+
const dir = path15.posix.dirname(portable);
|
|
11120
|
+
let langs = langsByDir.get(dir);
|
|
11121
|
+
if (!langs) {
|
|
11122
|
+
langs = /* @__PURE__ */ new Set();
|
|
11123
|
+
langsByDir.set(dir, langs);
|
|
11124
|
+
}
|
|
11125
|
+
langs.add(lang);
|
|
11126
|
+
}
|
|
11127
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
11128
|
+
for (const [dir, langs] of langsByDir) {
|
|
11129
|
+
for (const ancestor of ancestorsOf(dir, root)) {
|
|
11130
|
+
let merged = candidates.get(ancestor);
|
|
11131
|
+
if (!merged) {
|
|
11132
|
+
merged = /* @__PURE__ */ new Set();
|
|
11133
|
+
candidates.set(ancestor, merged);
|
|
11134
|
+
}
|
|
11135
|
+
for (const lang of langs) merged.add(lang);
|
|
11136
|
+
}
|
|
11137
|
+
}
|
|
11138
|
+
const roots = [];
|
|
11139
|
+
await Promise.all(
|
|
11140
|
+
[...candidates].map(async ([dir, langs]) => {
|
|
11141
|
+
for (const probe of MARKER_PROBES) {
|
|
11142
|
+
if (!LANGS_BY_KIND[probe.kind].some((lang) => langs.has(lang))) continue;
|
|
11143
|
+
const source = await readTextIfPresent(path15.posix.join(dir, probe.file));
|
|
11144
|
+
if (source === void 0) continue;
|
|
11145
|
+
const built = probe.build(dir, source);
|
|
11146
|
+
if (built) roots.push({ dir, kind: probe.kind, ...built });
|
|
11147
|
+
}
|
|
11148
|
+
if (LANGS_BY_KIND.dotnet.some((lang) => langs.has(lang))) {
|
|
11149
|
+
const dotnet = await probeDotnetRoot(dir);
|
|
11150
|
+
if (dotnet) roots.push(dotnet);
|
|
11151
|
+
}
|
|
11152
|
+
})
|
|
11153
|
+
);
|
|
11154
|
+
roots.sort((a, b) => b.dir.length - a.dir.length || a.dir.localeCompare(b.dir));
|
|
11155
|
+
return { projectRoot: root, roots };
|
|
11156
|
+
}
|
|
11157
|
+
function findOwningRoot(structure, file, kinds) {
|
|
11158
|
+
const portable = toPortablePath(file);
|
|
11159
|
+
for (const root of structure.roots) {
|
|
11160
|
+
if (kinds && !kinds.includes(root.kind)) continue;
|
|
11161
|
+
if (portable === root.dir || portable.startsWith(`${root.dir}/`)) return root;
|
|
11162
|
+
}
|
|
11163
|
+
return void 0;
|
|
11164
|
+
}
|
|
11165
|
+
function derivePackageFromLayout(filePath) {
|
|
11166
|
+
const portable = toPortablePath(filePath);
|
|
11167
|
+
const packagesIdx = portable.indexOf("/packages/");
|
|
11168
|
+
if (packagesIdx !== -1) {
|
|
11169
|
+
const segment = portable.slice(packagesIdx + "/packages/".length).split("/")[0];
|
|
11170
|
+
if (segment) return `@wrongstack/${segment}`;
|
|
11171
|
+
}
|
|
11172
|
+
const appsIdx = portable.indexOf("/apps/");
|
|
10755
11173
|
if (appsIdx !== -1) {
|
|
10756
|
-
const
|
|
10757
|
-
|
|
10758
|
-
return seg ? `app:${seg}` : void 0;
|
|
11174
|
+
const segment = portable.slice(appsIdx + "/apps/".length).split("/")[0];
|
|
11175
|
+
if (segment) return `app:${segment}`;
|
|
10759
11176
|
}
|
|
10760
11177
|
return void 0;
|
|
10761
11178
|
}
|
|
10762
|
-
function
|
|
10763
|
-
|
|
10764
|
-
const
|
|
10765
|
-
|
|
11179
|
+
function pythonPackageLabel(structure, file, initDirs) {
|
|
11180
|
+
const portable = toPortablePath(file);
|
|
11181
|
+
const dir = path15.posix.dirname(portable);
|
|
11182
|
+
if (!initDirs.has(dir)) return void 0;
|
|
11183
|
+
const segments = [];
|
|
11184
|
+
let current = dir;
|
|
11185
|
+
while (initDirs.has(current) && current.length > structure.projectRoot.length) {
|
|
11186
|
+
segments.unshift(path15.posix.basename(current));
|
|
11187
|
+
current = path15.posix.dirname(current);
|
|
11188
|
+
}
|
|
11189
|
+
return segments.length > 0 ? `py:${segments.join(".")}` : void 0;
|
|
10766
11190
|
}
|
|
10767
|
-
function
|
|
11191
|
+
function assignPackageLabels(structure, files) {
|
|
11192
|
+
const initDirs = /* @__PURE__ */ new Set();
|
|
11193
|
+
for (const file of files) {
|
|
11194
|
+
const portable = toPortablePath(file);
|
|
11195
|
+
if (path15.posix.basename(portable) === "__init__.py") {
|
|
11196
|
+
initDirs.add(path15.posix.dirname(portable));
|
|
11197
|
+
}
|
|
11198
|
+
}
|
|
11199
|
+
const labels = /* @__PURE__ */ new Map();
|
|
11200
|
+
for (const file of files) {
|
|
11201
|
+
const portable = toPortablePath(file);
|
|
11202
|
+
const lang = detectLang(portable);
|
|
11203
|
+
if (lang === "go") {
|
|
11204
|
+
const owner3 = findOwningRoot(structure, portable, ["go"]);
|
|
11205
|
+
const dir = path15.posix.dirname(portable);
|
|
11206
|
+
if (owner3?.importPath) {
|
|
11207
|
+
const relative13 = path15.posix.relative(owner3.dir, dir);
|
|
11208
|
+
labels.set(file, relative13 ? `${owner3.importPath}/${relative13}` : owner3.importPath);
|
|
11209
|
+
} else {
|
|
11210
|
+
labels.set(file, `go:${path15.posix.relative(structure.projectRoot, dir) || "."}`);
|
|
11211
|
+
}
|
|
11212
|
+
continue;
|
|
11213
|
+
}
|
|
11214
|
+
if (lang === "py") {
|
|
11215
|
+
const dotted = pythonPackageLabel(structure, portable, initDirs);
|
|
11216
|
+
if (dotted) {
|
|
11217
|
+
labels.set(file, dotted);
|
|
11218
|
+
continue;
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
const owner2 = findOwningRoot(structure, portable);
|
|
11222
|
+
const label = owner2?.name ?? derivePackageFromLayout(portable) ?? "(root)";
|
|
11223
|
+
labels.set(file, label);
|
|
11224
|
+
}
|
|
11225
|
+
return labels;
|
|
11226
|
+
}
|
|
11227
|
+
|
|
11228
|
+
// src/codebase-index/writer-graph-helpers.ts
|
|
11229
|
+
function createPackageLabeller(stored) {
|
|
11230
|
+
return (file) => stored.get(file) ?? derivePackageFromLayout(file) ?? "(root)";
|
|
11231
|
+
}
|
|
11232
|
+
function buildPackageGraphNodes(fileCounts, files, packageOf) {
|
|
10768
11233
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
10769
11234
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
10770
11235
|
for (const { file, n } of fileCounts) {
|
|
10771
|
-
const pkg =
|
|
11236
|
+
const pkg = packageOf(file);
|
|
10772
11237
|
fileToPkg.set(file, pkg);
|
|
10773
11238
|
const node = pkgNodes.get(pkg);
|
|
10774
11239
|
if (node) {
|
|
@@ -10785,7 +11250,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
10785
11250
|
}
|
|
10786
11251
|
}
|
|
10787
11252
|
for (const { file } of files) {
|
|
10788
|
-
const pkg =
|
|
11253
|
+
const pkg = packageOf(file);
|
|
10789
11254
|
fileToPkg.set(file, pkg);
|
|
10790
11255
|
const node = pkgNodes.get(pkg);
|
|
10791
11256
|
if (node) {
|
|
@@ -10803,7 +11268,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
10803
11268
|
}
|
|
10804
11269
|
return { pkgNodes, fileToPkg };
|
|
10805
11270
|
}
|
|
10806
|
-
function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
11271
|
+
function buildFileGraphNodeState(pkgSyms, localFiles, packageOf) {
|
|
10807
11272
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
10808
11273
|
const symToFile = /* @__PURE__ */ new Map();
|
|
10809
11274
|
const fileStats = /* @__PURE__ */ new Map();
|
|
@@ -10822,7 +11287,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
10822
11287
|
id: `file:${file}`,
|
|
10823
11288
|
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
10824
11289
|
kind: "file",
|
|
10825
|
-
package:
|
|
11290
|
+
package: packageOf(file),
|
|
10826
11291
|
file,
|
|
10827
11292
|
symbolCount: stats?.count ?? 0,
|
|
10828
11293
|
lang: stats?.lang,
|
|
@@ -10834,7 +11299,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
10834
11299
|
}
|
|
10835
11300
|
return { fileNodes, symToFile, fileStats, ensureFileNode };
|
|
10836
11301
|
}
|
|
10837
|
-
function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
11302
|
+
function buildSymbolGraphNodes(symById, relatedIds, fileFilter, packageOf) {
|
|
10838
11303
|
return [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
10839
11304
|
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
10840
11305
|
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
@@ -10846,7 +11311,7 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
10846
11311
|
symbolId: s.id,
|
|
10847
11312
|
symbolKind: s.kind,
|
|
10848
11313
|
file: s.file,
|
|
10849
|
-
package:
|
|
11314
|
+
package: packageOf(s.file),
|
|
10850
11315
|
lang: s.lang,
|
|
10851
11316
|
line: s.line,
|
|
10852
11317
|
signature: s.signature,
|
|
@@ -10854,29 +11319,6 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
10854
11319
|
external: s.file !== fileFilter
|
|
10855
11320
|
}));
|
|
10856
11321
|
}
|
|
10857
|
-
function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
10858
|
-
if (!moduleName.startsWith(".")) return void 0;
|
|
10859
|
-
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
10860
|
-
const absolute = path14.posix.normalize(
|
|
10861
|
-
path14.posix.join(path14.posix.dirname(normalizedFrom), moduleName)
|
|
10862
|
-
);
|
|
10863
|
-
const extension = path14.posix.extname(absolute);
|
|
10864
|
-
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
10865
|
-
const candidates = [
|
|
10866
|
-
absolute,
|
|
10867
|
-
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
10868
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(absolute, `index${ext}`)),
|
|
10869
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path14.posix.join(base, `index${ext}`))
|
|
10870
|
-
];
|
|
10871
|
-
const indexedByPortablePath = new Map(
|
|
10872
|
-
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
10873
|
-
);
|
|
10874
|
-
for (const candidate of candidates) {
|
|
10875
|
-
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
10876
|
-
if (indexed) return indexed;
|
|
10877
|
-
}
|
|
10878
|
-
return void 0;
|
|
10879
|
-
}
|
|
10880
11322
|
function addWeightedEdge(edgeMap, source, target, callType, weight) {
|
|
10881
11323
|
const key = `${source}\0${target}`;
|
|
10882
11324
|
let edge = edgeMap.get(key);
|
|
@@ -10917,7 +11359,12 @@ function mapWriterRefRow(row) {
|
|
|
10917
11359
|
toName: row.to_name,
|
|
10918
11360
|
toId: row.to_id ?? void 0,
|
|
10919
11361
|
callType: row.call_type,
|
|
10920
|
-
line: row.line
|
|
11362
|
+
line: row.line,
|
|
11363
|
+
// `lang`/`module`/`to_file` are absent from the narrower column lists some
|
|
11364
|
+
// queries select; `undefined` keeps those rows valid Refs.
|
|
11365
|
+
lang: row.lang || void 0,
|
|
11366
|
+
module: row.module ?? void 0,
|
|
11367
|
+
toFile: row.to_file ?? void 0
|
|
10921
11368
|
};
|
|
10922
11369
|
}
|
|
10923
11370
|
|
|
@@ -11065,7 +11512,8 @@ function findRefsFromWithStatement(stmt, symbolId) {
|
|
|
11065
11512
|
function getPackageGraphWithStatement(stmt) {
|
|
11066
11513
|
const fileCounts = stmt("SELECT file, COUNT(*) AS n FROM symbols GROUP BY file").all();
|
|
11067
11514
|
const files = stmt("SELECT DISTINCT file FROM files").all();
|
|
11068
|
-
const
|
|
11515
|
+
const packageOf = readPackageLabeller(stmt);
|
|
11516
|
+
const { pkgNodes, fileToPkg } = buildPackageGraphNodes(fileCounts, files, packageOf);
|
|
11069
11517
|
const refRows = stmt(
|
|
11070
11518
|
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
|
|
11071
11519
|
FROM refs r
|
|
@@ -11076,32 +11524,42 @@ function getPackageGraphWithStatement(stmt) {
|
|
|
11076
11524
|
).all();
|
|
11077
11525
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
11078
11526
|
for (const r of refRows) {
|
|
11079
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
11080
|
-
const toPkg = fileToPkg.get(r.to_file) ??
|
|
11527
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
11528
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
11081
11529
|
if (fromPkg === toPkg) continue;
|
|
11082
11530
|
const n = Number(r.n) || 0;
|
|
11083
11531
|
addWeightedEdge(edgeMap, fromPkg, toPkg, r.call_type, n);
|
|
11084
11532
|
}
|
|
11085
11533
|
const importRows = stmt(
|
|
11086
|
-
`SELECT
|
|
11534
|
+
`SELECT s.file AS from_file,
|
|
11535
|
+
COALESCE(r.to_file, st.file) AS to_file,
|
|
11536
|
+
COUNT(*) AS n
|
|
11087
11537
|
FROM refs r
|
|
11088
11538
|
JOIN symbols s ON s.id = r.from_id
|
|
11539
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
11089
11540
|
WHERE r.call_type = 'import'
|
|
11090
|
-
|
|
11541
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
11542
|
+
GROUP BY s.file, COALESCE(r.to_file, st.file)`
|
|
11091
11543
|
).all();
|
|
11092
11544
|
for (const r of importRows) {
|
|
11093
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
11094
|
-
const toPkg =
|
|
11095
|
-
if (
|
|
11545
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
11546
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
11547
|
+
if (fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
11096
11548
|
const n = Number(r.n) || 0;
|
|
11097
11549
|
addWeightedEdge(edgeMap, fromPkg, toPkg, "import", n);
|
|
11098
11550
|
}
|
|
11099
11551
|
const edges = materializeWeightedEdges(edgeMap, "pkg");
|
|
11100
11552
|
return { nodes: [...pkgNodes.values()], edges };
|
|
11101
11553
|
}
|
|
11554
|
+
function readPackageLabeller(stmt) {
|
|
11555
|
+
const rows = stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
11556
|
+
return createPackageLabeller(new Map(rows.map((row) => [row.file, row.package])));
|
|
11557
|
+
}
|
|
11102
11558
|
function getFileGraphWithStatement(stmt, packageFilter) {
|
|
11103
11559
|
const allFiles = stmt("SELECT DISTINCT file FROM symbols").all();
|
|
11104
|
-
const
|
|
11560
|
+
const packageOf = readPackageLabeller(stmt);
|
|
11561
|
+
const langOf = (file) => detectLang(file) ?? "other";
|
|
11562
|
+
const pkgFilePaths = allFiles.filter((f) => packageOf(f.file) === packageFilter).map((f) => f.file);
|
|
11105
11563
|
const localFiles = new Set(pkgFilePaths);
|
|
11106
11564
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
11107
11565
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
@@ -11110,9 +11568,9 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
11110
11568
|
).all(...pkgFilePaths);
|
|
11111
11569
|
const { fileNodes, symToFile, fileStats, ensureFileNode } = buildFileGraphNodeState(
|
|
11112
11570
|
pkgSyms,
|
|
11113
|
-
localFiles
|
|
11571
|
+
localFiles,
|
|
11572
|
+
packageOf
|
|
11114
11573
|
);
|
|
11115
|
-
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
11116
11574
|
const refRows = stmt(
|
|
11117
11575
|
`SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
|
|
11118
11576
|
FROM refs r
|
|
@@ -11135,7 +11593,7 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
11135
11593
|
for (const x of extras) {
|
|
11136
11594
|
symToFile.set(x.id, x.file);
|
|
11137
11595
|
if (!fileStats.has(x.file)) {
|
|
11138
|
-
fileStats.set(x.file, { count: 0, lang:
|
|
11596
|
+
fileStats.set(x.file, { count: 0, lang: langOf(x.file) });
|
|
11139
11597
|
}
|
|
11140
11598
|
}
|
|
11141
11599
|
}
|
|
@@ -11152,17 +11610,22 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
11152
11610
|
addWeightedEdge(edgeMap, fromFile, toFile, r.call_type, n);
|
|
11153
11611
|
}
|
|
11154
11612
|
const importRows = stmt(
|
|
11155
|
-
`SELECT r.from_id, r.
|
|
11613
|
+
`SELECT r.from_id, COALESCE(r.to_file, st.file) AS to_file, COUNT(*) AS n
|
|
11156
11614
|
FROM refs r
|
|
11615
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
11157
11616
|
WHERE r.call_type = 'import'
|
|
11617
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
11158
11618
|
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
11159
|
-
GROUP BY r.from_id, r.
|
|
11619
|
+
GROUP BY r.from_id, COALESCE(r.to_file, st.file)`
|
|
11160
11620
|
).all(...pkgFilePaths);
|
|
11161
11621
|
for (const r of importRows) {
|
|
11162
11622
|
const fromFile = symToFile.get(r.from_id);
|
|
11163
11623
|
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
11164
|
-
const toFile =
|
|
11624
|
+
const toFile = r.to_file;
|
|
11165
11625
|
if (!toFile || fromFile === toFile) continue;
|
|
11626
|
+
if (!fileStats.has(toFile)) {
|
|
11627
|
+
fileStats.set(toFile, { count: 0, lang: langOf(toFile) });
|
|
11628
|
+
}
|
|
11166
11629
|
ensureFileNode(fromFile);
|
|
11167
11630
|
ensureFileNode(toFile);
|
|
11168
11631
|
const n = Number(r.n) || 0;
|
|
@@ -11212,7 +11675,7 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
11212
11675
|
).all(...missingIds);
|
|
11213
11676
|
for (const s of extras) symById.set(s.id, s);
|
|
11214
11677
|
}
|
|
11215
|
-
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter);
|
|
11678
|
+
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter, readPackageLabeller(stmt));
|
|
11216
11679
|
return { nodes, edges };
|
|
11217
11680
|
}
|
|
11218
11681
|
|
|
@@ -11234,7 +11697,7 @@ function assignRefsToSymbols(refs, symbols) {
|
|
|
11234
11697
|
}
|
|
11235
11698
|
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
11236
11699
|
if (!owner2 || owner2.id <= 0) continue;
|
|
11237
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
11700
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
11238
11701
|
if (seen.has(key)) continue;
|
|
11239
11702
|
seen.add(key);
|
|
11240
11703
|
assigned.push({ ...ref, fromId: owner2.id });
|
|
@@ -11280,7 +11743,11 @@ var CORE_TABLES_SQL = `
|
|
|
11280
11743
|
lang TEXT NOT NULL,
|
|
11281
11744
|
mtime_ms INTEGER NOT NULL,
|
|
11282
11745
|
symbol_count INTEGER NOT NULL DEFAULT 0,
|
|
11283
|
-
last_indexed INTEGER NOT NULL
|
|
11746
|
+
last_indexed INTEGER NOT NULL,
|
|
11747
|
+
-- Code Atlas grouping label, computed at index time from the ecosystem's
|
|
11748
|
+
-- own manifests (package.json, go.mod, Cargo.toml, \u2026). Stored rather than
|
|
11749
|
+
-- re-derived per query because the evidence lives on disk, not in the DB.
|
|
11750
|
+
package TEXT NOT NULL DEFAULT ''
|
|
11284
11751
|
);
|
|
11285
11752
|
CREATE TABLE IF NOT EXISTS symbols (
|
|
11286
11753
|
id INTEGER PRIMARY KEY,
|
|
@@ -11297,6 +11764,9 @@ var CORE_TABLES_SQL = `
|
|
|
11297
11764
|
file_fk TEXT NOT NULL
|
|
11298
11765
|
);
|
|
11299
11766
|
`;
|
|
11767
|
+
var FILE_INDEX_SQL = [
|
|
11768
|
+
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
11769
|
+
];
|
|
11300
11770
|
var SYMBOL_INDEX_SQL = [
|
|
11301
11771
|
"CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
|
|
11302
11772
|
"CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
|
|
@@ -11313,15 +11783,32 @@ var REFS_TABLE_SQL = `
|
|
|
11313
11783
|
to_name TEXT NOT NULL,
|
|
11314
11784
|
to_id INTEGER,
|
|
11315
11785
|
call_type TEXT NOT NULL,
|
|
11316
|
-
line INTEGER NOT NULL
|
|
11786
|
+
line INTEGER NOT NULL,
|
|
11787
|
+
lang TEXT NOT NULL DEFAULT '',
|
|
11788
|
+
module TEXT,
|
|
11789
|
+
to_file TEXT
|
|
11317
11790
|
);
|
|
11318
11791
|
`;
|
|
11319
11792
|
var REFS_INDEX_SQL = [
|
|
11320
11793
|
"CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
|
|
11321
11794
|
"CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
|
|
11322
11795
|
"CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
|
|
11323
|
-
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
|
|
11796
|
+
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)",
|
|
11797
|
+
// Name resolution matches (to_name, lang) pairs; the composite keeps the
|
|
11798
|
+
// language-scoped UPDATE from degrading into a scan of every same-named row.
|
|
11799
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_name_lang ON refs(to_name, lang)",
|
|
11800
|
+
// The post-index module resolution pass groups unresolved import refs by
|
|
11801
|
+
// (module, lang); graph readers then read to_file back.
|
|
11802
|
+
"CREATE INDEX IF NOT EXISTS idx_r_module ON refs(module)",
|
|
11803
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_file ON refs(to_file)"
|
|
11324
11804
|
];
|
|
11805
|
+
var LANG_FAMILY_TABLE_SQL = `
|
|
11806
|
+
CREATE TABLE IF NOT EXISTS lang_family (
|
|
11807
|
+
lang TEXT PRIMARY KEY,
|
|
11808
|
+
family TEXT NOT NULL
|
|
11809
|
+
);
|
|
11810
|
+
`;
|
|
11811
|
+
var LANG_FAMILY_WILDCARD = "*";
|
|
11325
11812
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
11326
11813
|
|
|
11327
11814
|
// src/codebase-index/writer-search-helpers.ts
|
|
@@ -11533,15 +12020,69 @@ var IndexStore = class _IndexStore {
|
|
|
11533
12020
|
}
|
|
11534
12021
|
constructor(projectRoot, opts = {}) {
|
|
11535
12022
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
11536
|
-
|
|
12023
|
+
fs11.mkdirSync(this.indexDir, { recursive: true });
|
|
11537
12024
|
const Database = loadDatabaseSync();
|
|
11538
|
-
this.db = new Database(
|
|
12025
|
+
this.db = new Database(path16.join(this.indexDir, DB_FILE2));
|
|
11539
12026
|
applyIndexStorePragmas(this.db);
|
|
11540
12027
|
this.initSchema();
|
|
11541
12028
|
}
|
|
11542
12029
|
runWithRetry(fn) {
|
|
11543
12030
|
return runSqliteWithRetry(fn);
|
|
11544
12031
|
}
|
|
12032
|
+
/**
|
|
12033
|
+
* Mirror the in-process language→family map into SQLite.
|
|
12034
|
+
*
|
|
12035
|
+
* Rewritten on every open rather than only on schema bumps: the mapping is
|
|
12036
|
+
* static lookup data, so a code-side change (a new language, a language
|
|
12037
|
+
* moving families) must take effect without forcing a full reindex.
|
|
12038
|
+
*/
|
|
12039
|
+
seedLangFamilies() {
|
|
12040
|
+
const insert = this.stmt("INSERT OR REPLACE INTO lang_family(lang, family) VALUES (?, ?)");
|
|
12041
|
+
for (const [lang, family] of LANG_FAMILY_ENTRIES) insert.run(lang, family);
|
|
12042
|
+
insert.run("", LANG_FAMILY_WILDCARD);
|
|
12043
|
+
}
|
|
12044
|
+
/**
|
|
12045
|
+
* Add any column the current schema expects but the on-disk table lacks.
|
|
12046
|
+
*
|
|
12047
|
+
* `CREATE TABLE IF NOT EXISTS` silently keeps an existing table's old shape,
|
|
12048
|
+
* and the version check above only rebuilds on a version *mismatch*. That
|
|
12049
|
+
* leaves a real gap: several wstack processes share this database, and while
|
|
12050
|
+
* a version upgrade is rolling out one of them may still be running the
|
|
12051
|
+
* previous build. That older process sees the newer version number, drops the
|
|
12052
|
+
* tables, and recreates them from *its* DDL — without the newer columns —
|
|
12053
|
+
* while the metadata row still reads the new version. Every later query for
|
|
12054
|
+
* one of those columns then fails with `no such column`, and no amount of
|
|
12055
|
+
* reindexing fixes it, because the version numbers already agree.
|
|
12056
|
+
*
|
|
12057
|
+
* Repairing column-by-column makes the schema self-healing from any of those
|
|
12058
|
+
* states. Table and column names are compile-time literals from this module,
|
|
12059
|
+
* never user input.
|
|
12060
|
+
*/
|
|
12061
|
+
repairMissingColumns() {
|
|
12062
|
+
const expected = [
|
|
12063
|
+
{ table: "files", columns: [["package", "TEXT NOT NULL DEFAULT ''"]] },
|
|
12064
|
+
{
|
|
12065
|
+
table: "refs",
|
|
12066
|
+
columns: [
|
|
12067
|
+
["lang", "TEXT NOT NULL DEFAULT ''"],
|
|
12068
|
+
["module", "TEXT"],
|
|
12069
|
+
["to_file", "TEXT"]
|
|
12070
|
+
]
|
|
12071
|
+
}
|
|
12072
|
+
];
|
|
12073
|
+
for (const { table, columns } of expected) {
|
|
12074
|
+
const present = new Set(
|
|
12075
|
+
this.db.prepare(`PRAGMA table_info(${table})`).all().flatMap(
|
|
12076
|
+
(row) => typeof row.name === "string" ? [row.name] : []
|
|
12077
|
+
)
|
|
12078
|
+
);
|
|
12079
|
+
if (present.size === 0) continue;
|
|
12080
|
+
for (const [name, type] of columns) {
|
|
12081
|
+
if (present.has(name)) continue;
|
|
12082
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${name} ${type}`);
|
|
12083
|
+
}
|
|
12084
|
+
}
|
|
12085
|
+
}
|
|
11545
12086
|
initSchema() {
|
|
11546
12087
|
this.db.exec(METADATA_TABLE_SQL);
|
|
11547
12088
|
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
@@ -11564,9 +12105,13 @@ var IndexStore = class _IndexStore {
|
|
|
11564
12105
|
);
|
|
11565
12106
|
}
|
|
11566
12107
|
this.db.exec(CORE_TABLES_SQL);
|
|
11567
|
-
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
11568
12108
|
this.db.exec(REFS_TABLE_SQL);
|
|
12109
|
+
this.repairMissingColumns();
|
|
12110
|
+
for (const sql of FILE_INDEX_SQL) this.db.exec(sql);
|
|
12111
|
+
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
11569
12112
|
for (const sql of REFS_INDEX_SQL) this.db.exec(sql);
|
|
12113
|
+
this.db.exec(LANG_FAMILY_TABLE_SQL);
|
|
12114
|
+
this.seedLangFamilies();
|
|
11570
12115
|
try {
|
|
11571
12116
|
this.db.exec(SYMBOLS_FTS_SQL);
|
|
11572
12117
|
this.ftsAvailable = true;
|
|
@@ -11601,6 +12146,18 @@ var IndexStore = class _IndexStore {
|
|
|
11601
12146
|
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
11602
12147
|
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
11603
12148
|
static MAX_SQL_VARS = 900;
|
|
12149
|
+
/**
|
|
12150
|
+
* Correlated predicate: the ref in `refs` and the candidate symbol aliased
|
|
12151
|
+
* `sym` belong to the same language family — or the ref carries no language,
|
|
12152
|
+
* in which case the wildcard bind matches everything.
|
|
12153
|
+
*
|
|
12154
|
+
* Each textual occurrence consumes one `?` bind of {@link LANG_FAMILY_WILDCARD}.
|
|
12155
|
+
*/
|
|
12156
|
+
static FAMILY_MATCH_SQL = `(
|
|
12157
|
+
(SELECT family FROM lang_family WHERE lang = refs.lang) = ?
|
|
12158
|
+
OR (SELECT family FROM lang_family WHERE lang = sym.lang)
|
|
12159
|
+
= (SELECT family FROM lang_family WHERE lang = refs.lang)
|
|
12160
|
+
)`;
|
|
11604
12161
|
/**
|
|
11605
12162
|
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
11606
12163
|
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
@@ -11664,9 +12221,12 @@ var IndexStore = class _IndexStore {
|
|
|
11664
12221
|
const placeholders = chunk.map(() => "?").join(",");
|
|
11665
12222
|
const result = this.stmt(
|
|
11666
12223
|
`UPDATE refs
|
|
11667
|
-
SET to_id = (
|
|
12224
|
+
SET to_id = (
|
|
12225
|
+
SELECT MIN(sym.id) FROM symbols sym
|
|
12226
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
12227
|
+
)
|
|
11668
12228
|
WHERE to_name IN (${placeholders})`
|
|
11669
|
-
).run(...chunk);
|
|
12229
|
+
).run(LANG_FAMILY_WILDCARD, ...chunk);
|
|
11670
12230
|
changes += result.changes ?? 0;
|
|
11671
12231
|
}
|
|
11672
12232
|
return changes;
|
|
@@ -11793,6 +12353,115 @@ var IndexStore = class _IndexStore {
|
|
|
11793
12353
|
getAllFileMetas() {
|
|
11794
12354
|
return getAllFileMetasWithStatement((sql) => this.stmt(sql));
|
|
11795
12355
|
}
|
|
12356
|
+
// ─── Project structure & module resolution ──────────────────────────────────
|
|
12357
|
+
/** Store the Code Atlas grouping label for each indexed file. */
|
|
12358
|
+
setFilePackages(entries) {
|
|
12359
|
+
if (entries.size === 0) return;
|
|
12360
|
+
this.runWithRetry(() => {
|
|
12361
|
+
const update = this.stmt("UPDATE files SET package = ? WHERE file = ?");
|
|
12362
|
+
for (const [file, label] of entries) update.run(label, file);
|
|
12363
|
+
});
|
|
12364
|
+
}
|
|
12365
|
+
/**
|
|
12366
|
+
* Every indexed `namespace`/`module` declaration, for ecosystems whose import
|
|
12367
|
+
* specifiers name a namespace rather than a path (C#, PHP, Elixir, Haskell).
|
|
12368
|
+
* Ordered so the resolver's choice among duplicate declarations is stable.
|
|
12369
|
+
*/
|
|
12370
|
+
getNamespaceDeclarations() {
|
|
12371
|
+
return this.stmt(
|
|
12372
|
+
`SELECT name, file FROM symbols WHERE kind = 'namespace' ORDER BY file, id`
|
|
12373
|
+
).all();
|
|
12374
|
+
}
|
|
12375
|
+
/** `file → package` for every indexed file that has a label. */
|
|
12376
|
+
getFilePackages() {
|
|
12377
|
+
const rows = this.stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
12378
|
+
return new Map(rows.map((row) => [row.file, row.package]));
|
|
12379
|
+
}
|
|
12380
|
+
/**
|
|
12381
|
+
* Distinct `(fromFile, lang, module)` triples needing module resolution.
|
|
12382
|
+
*
|
|
12383
|
+
* Distinct rather than per-ref because resolution depends only on these three
|
|
12384
|
+
* values: a file importing the same module twenty times resolves it once.
|
|
12385
|
+
*/
|
|
12386
|
+
getUnresolvedImports(onlyFiles) {
|
|
12387
|
+
const base = `SELECT DISTINCT s.file AS fromFile, r.lang AS lang, r.module AS module
|
|
12388
|
+
FROM refs r
|
|
12389
|
+
JOIN symbols s ON s.id = r.from_id
|
|
12390
|
+
WHERE r.call_type = 'import' AND r.module IS NOT NULL`;
|
|
12391
|
+
if (!onlyFiles?.length) {
|
|
12392
|
+
return this.stmt(base).all();
|
|
12393
|
+
}
|
|
12394
|
+
const out = [];
|
|
12395
|
+
for (let i = 0; i < onlyFiles.length; i += _IndexStore.MAX_SQL_VARS) {
|
|
12396
|
+
const chunk = onlyFiles.slice(i, i + _IndexStore.MAX_SQL_VARS);
|
|
12397
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
12398
|
+
out.push(
|
|
12399
|
+
...this.stmt(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
12400
|
+
);
|
|
12401
|
+
}
|
|
12402
|
+
return out;
|
|
12403
|
+
}
|
|
12404
|
+
/**
|
|
12405
|
+
* Write resolved import targets back onto `refs.to_file`.
|
|
12406
|
+
*
|
|
12407
|
+
* Applied through a temp table and a single UPDATE: one statement per
|
|
12408
|
+
* resolution would mean thousands of round-trips on a first index.
|
|
12409
|
+
*/
|
|
12410
|
+
applyImportResolutions(resolutions) {
|
|
12411
|
+
if (resolutions.length === 0) return 0;
|
|
12412
|
+
return this.runWithRetry(() => {
|
|
12413
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
12414
|
+
this.db.exec(
|
|
12415
|
+
`CREATE TEMP TABLE import_resolution (
|
|
12416
|
+
from_file TEXT NOT NULL,
|
|
12417
|
+
lang TEXT NOT NULL,
|
|
12418
|
+
module TEXT NOT NULL,
|
|
12419
|
+
to_file TEXT NOT NULL
|
|
12420
|
+
)`
|
|
12421
|
+
);
|
|
12422
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / 4));
|
|
12423
|
+
for (let i = 0; i < resolutions.length; i += chunkSize) {
|
|
12424
|
+
const chunk = resolutions.slice(i, i + chunkSize);
|
|
12425
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
12426
|
+
const binds = [];
|
|
12427
|
+
for (const entry of chunk) {
|
|
12428
|
+
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
12429
|
+
}
|
|
12430
|
+
this.stmt(
|
|
12431
|
+
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
12432
|
+
VALUES ${placeholders}`
|
|
12433
|
+
).run(...binds);
|
|
12434
|
+
}
|
|
12435
|
+
this.db.exec(
|
|
12436
|
+
`CREATE INDEX IF NOT EXISTS temp.idx_ir
|
|
12437
|
+
ON import_resolution(module, lang, from_file)`
|
|
12438
|
+
);
|
|
12439
|
+
const result = this.stmt(
|
|
12440
|
+
`UPDATE refs
|
|
12441
|
+
SET to_file = (
|
|
12442
|
+
SELECT ir.to_file
|
|
12443
|
+
FROM temp.import_resolution ir
|
|
12444
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
12445
|
+
WHERE ir.module = refs.module
|
|
12446
|
+
AND ir.lang = refs.lang
|
|
12447
|
+
AND ir.from_file = s.file
|
|
12448
|
+
LIMIT 1
|
|
12449
|
+
)
|
|
12450
|
+
WHERE refs.call_type = 'import'
|
|
12451
|
+
AND refs.module IS NOT NULL
|
|
12452
|
+
AND EXISTS (
|
|
12453
|
+
SELECT 1
|
|
12454
|
+
FROM temp.import_resolution ir
|
|
12455
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
12456
|
+
WHERE ir.module = refs.module
|
|
12457
|
+
AND ir.lang = refs.lang
|
|
12458
|
+
AND ir.from_file = s.file
|
|
12459
|
+
)`
|
|
12460
|
+
).run();
|
|
12461
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
12462
|
+
return result.changes ?? 0;
|
|
12463
|
+
});
|
|
12464
|
+
}
|
|
11796
12465
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
11797
12466
|
search(query, filter, opts) {
|
|
11798
12467
|
const built = this.buildSearchWhere(query, filter);
|
|
@@ -12179,9 +12848,12 @@ var IndexStore = class _IndexStore {
|
|
|
12179
12848
|
* Resolve `to_name` → `to_id` for all refs that have a name but no id.
|
|
12180
12849
|
* Call this after all symbols have been inserted to fill in cross-references.
|
|
12181
12850
|
*
|
|
12182
|
-
*
|
|
12183
|
-
* the
|
|
12184
|
-
*
|
|
12851
|
+
* A match additionally requires the referencing ref and the target symbol to
|
|
12852
|
+
* be in the same {@link LangFamily}. Without that guard a name match is a
|
|
12853
|
+
* cross-language accident waiting to happen — `main`, `New`, `Parse` and
|
|
12854
|
+
* `Config` are declared in most languages at once, and each collision draws a
|
|
12855
|
+
* Code Atlas edge between files that never reference each other. Refs stored
|
|
12856
|
+
* without a language keep the old global behaviour via the `'*'` wildcard row.
|
|
12185
12857
|
*/
|
|
12186
12858
|
resolveRefs() {
|
|
12187
12859
|
return this.runWithRetry(() => {
|
|
@@ -12190,20 +12862,35 @@ var IndexStore = class _IndexStore {
|
|
|
12190
12862
|
`UPDATE refs
|
|
12191
12863
|
SET to_id = s.id
|
|
12192
12864
|
FROM (
|
|
12193
|
-
|
|
12194
|
-
|
|
12865
|
+
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
12866
|
+
FROM symbols sym
|
|
12867
|
+
JOIN lang_family lf ON lf.lang = sym.lang
|
|
12868
|
+
GROUP BY sym.name, lf.family
|
|
12869
|
+
UNION ALL
|
|
12870
|
+
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
12871
|
+
FROM symbols sym
|
|
12872
|
+
GROUP BY sym.name
|
|
12873
|
+
) AS s,
|
|
12874
|
+
lang_family AS rf
|
|
12195
12875
|
WHERE refs.to_id IS NULL
|
|
12196
12876
|
AND refs.to_name IS NOT NULL
|
|
12197
|
-
AND
|
|
12877
|
+
AND rf.lang = refs.lang
|
|
12878
|
+
AND s.name = refs.to_name
|
|
12879
|
+
AND s.family = rf.family`
|
|
12198
12880
|
).run();
|
|
12199
12881
|
return result.changes ?? 0;
|
|
12200
12882
|
} catch {
|
|
12201
12883
|
const result = this.stmt(
|
|
12202
12884
|
`UPDATE refs SET to_id = (
|
|
12203
|
-
SELECT id FROM symbols
|
|
12885
|
+
SELECT sym.id FROM symbols sym
|
|
12886
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
12887
|
+
ORDER BY sym.id LIMIT 1
|
|
12204
12888
|
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
12205
|
-
AND
|
|
12206
|
-
|
|
12889
|
+
AND EXISTS (
|
|
12890
|
+
SELECT 1 FROM symbols sym
|
|
12891
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
12892
|
+
)`
|
|
12893
|
+
).run(LANG_FAMILY_WILDCARD, LANG_FAMILY_WILDCARD);
|
|
12207
12894
|
return result.changes ?? 0;
|
|
12208
12895
|
}
|
|
12209
12896
|
});
|
|
@@ -12392,21 +13079,23 @@ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
|
12392
13079
|
var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
|
|
12393
13080
|
var buildIdCache;
|
|
12394
13081
|
function projectIndexServerBuildId(entrypoint) {
|
|
12395
|
-
const
|
|
13082
|
+
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
13083
|
+
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
13084
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath(cleanHref) : path17.resolve(cleanHref);
|
|
12396
13085
|
try {
|
|
12397
|
-
const stat19 =
|
|
13086
|
+
const stat19 = fs12.statSync(file);
|
|
12398
13087
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat19.mtimeMs && buildIdCache.size === stat19.size) {
|
|
12399
13088
|
return buildIdCache.buildId;
|
|
12400
13089
|
}
|
|
12401
|
-
const buildId = createHash4("sha256").update(
|
|
13090
|
+
const buildId = createHash4("sha256").update(fs12.readFileSync(file)).digest("hex").slice(0, 24);
|
|
12402
13091
|
buildIdCache = { file, mtimeMs: stat19.mtimeMs, size: stat19.size, buildId };
|
|
12403
13092
|
return buildId;
|
|
12404
13093
|
} catch {
|
|
12405
|
-
return `unreadable:${
|
|
13094
|
+
return `unreadable:${path17.basename(file)}`;
|
|
12406
13095
|
}
|
|
12407
13096
|
}
|
|
12408
13097
|
function normalizeLocalPath(value) {
|
|
12409
|
-
const resolved =
|
|
13098
|
+
const resolved = path17.resolve(value);
|
|
12410
13099
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
12411
13100
|
}
|
|
12412
13101
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -12418,11 +13107,11 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
12418
13107
|
if (process.platform === "win32") {
|
|
12419
13108
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
12420
13109
|
}
|
|
12421
|
-
return
|
|
13110
|
+
return path17.join(os5.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
12422
13111
|
}
|
|
12423
13112
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
12424
|
-
return
|
|
12425
|
-
|
|
13113
|
+
return path17.join(
|
|
13114
|
+
path17.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
12426
13115
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
12427
13116
|
);
|
|
12428
13117
|
}
|
|
@@ -12462,7 +13151,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
12462
13151
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
12463
13152
|
try {
|
|
12464
13153
|
const url = new URL(rel, import.meta.url);
|
|
12465
|
-
if (url.protocol === "file:" &&
|
|
13154
|
+
if (url.protocol === "file:" && fs13.existsSync(fileURLToPath2(url))) {
|
|
12466
13155
|
builtUrl = url;
|
|
12467
13156
|
break;
|
|
12468
13157
|
}
|
|
@@ -12719,7 +13408,7 @@ var ProjectServerConnection = class {
|
|
|
12719
13408
|
currentAuthToken() {
|
|
12720
13409
|
if (this.authToken === void 0) {
|
|
12721
13410
|
try {
|
|
12722
|
-
const raw =
|
|
13411
|
+
const raw = fs13.readFileSync(
|
|
12723
13412
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
12724
13413
|
"utf8"
|
|
12725
13414
|
);
|
|
@@ -12984,7 +13673,7 @@ var ProjectServerConnection = class {
|
|
|
12984
13673
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
12985
13674
|
if (process.platform !== "win32") {
|
|
12986
13675
|
try {
|
|
12987
|
-
|
|
13676
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
12988
13677
|
} catch {
|
|
12989
13678
|
}
|
|
12990
13679
|
}
|
|
@@ -13008,8 +13697,8 @@ var ProjectServerConnection = class {
|
|
|
13008
13697
|
process.kill(pid);
|
|
13009
13698
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
13010
13699
|
try {
|
|
13011
|
-
const metadata = JSON.parse(
|
|
13012
|
-
if (metadata.pid === pid)
|
|
13700
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
13701
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
13013
13702
|
} catch {
|
|
13014
13703
|
}
|
|
13015
13704
|
return true;
|
|
@@ -13113,7 +13802,7 @@ import { Worker } from "node:worker_threads";
|
|
|
13113
13802
|
|
|
13114
13803
|
// src/codebase-index/indexer.ts
|
|
13115
13804
|
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
13116
|
-
import { execFile
|
|
13805
|
+
import { execFile } from "node:child_process";
|
|
13117
13806
|
import * as fs17 from "node:fs/promises";
|
|
13118
13807
|
import { availableParallelism } from "node:os";
|
|
13119
13808
|
import * as path23 from "node:path";
|
|
@@ -13124,8 +13813,8 @@ import {
|
|
|
13124
13813
|
} from "@wrongstack/core/utils";
|
|
13125
13814
|
|
|
13126
13815
|
// src/codebase-index/gitignore.ts
|
|
13127
|
-
import * as
|
|
13128
|
-
import * as
|
|
13816
|
+
import * as fs14 from "node:fs/promises";
|
|
13817
|
+
import * as path18 from "node:path";
|
|
13129
13818
|
import { compileGlob } from "@wrongstack/core/utils";
|
|
13130
13819
|
function globBody(glob) {
|
|
13131
13820
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
@@ -13141,48 +13830,474 @@ function compileGitignore(lines) {
|
|
|
13141
13830
|
negated = true;
|
|
13142
13831
|
line = line.slice(1);
|
|
13143
13832
|
}
|
|
13144
|
-
let dirOnly = false;
|
|
13145
|
-
if (line.endsWith("/")) {
|
|
13146
|
-
dirOnly = true;
|
|
13147
|
-
line = line.slice(0, -1);
|
|
13833
|
+
let dirOnly = false;
|
|
13834
|
+
if (line.endsWith("/")) {
|
|
13835
|
+
dirOnly = true;
|
|
13836
|
+
line = line.slice(0, -1);
|
|
13837
|
+
}
|
|
13838
|
+
if (!line) continue;
|
|
13839
|
+
const anchored = line.startsWith("/") || line.includes("/");
|
|
13840
|
+
if (line.startsWith("/")) line = line.slice(1);
|
|
13841
|
+
const body = globBody(line);
|
|
13842
|
+
const prefix = anchored ? "^" : "(?:^|.*/)";
|
|
13843
|
+
rules.push({
|
|
13844
|
+
eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
|
|
13845
|
+
under: new RegExp(`${prefix}${body}/.*$`),
|
|
13846
|
+
negated,
|
|
13847
|
+
dirOnly
|
|
13848
|
+
});
|
|
13849
|
+
}
|
|
13850
|
+
return (relPath, isDir) => {
|
|
13851
|
+
const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
13852
|
+
let ignored = false;
|
|
13853
|
+
for (const r of rules) {
|
|
13854
|
+
const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
|
|
13855
|
+
if (re.test(p)) ignored = !r.negated;
|
|
13856
|
+
}
|
|
13857
|
+
return ignored;
|
|
13858
|
+
};
|
|
13859
|
+
}
|
|
13860
|
+
async function loadGitignoreMatcher(projectRoot) {
|
|
13861
|
+
let lines = [];
|
|
13862
|
+
try {
|
|
13863
|
+
const raw = await fs14.readFile(path18.join(projectRoot, ".gitignore"), "utf8");
|
|
13864
|
+
lines = raw.split("\n");
|
|
13865
|
+
} catch {
|
|
13866
|
+
}
|
|
13867
|
+
return compileGitignore(lines);
|
|
13868
|
+
}
|
|
13869
|
+
|
|
13870
|
+
// src/codebase-index/indexer.ts
|
|
13871
|
+
init_languages2();
|
|
13872
|
+
|
|
13873
|
+
// src/codebase-index/module-resolver.ts
|
|
13874
|
+
init_languages2();
|
|
13875
|
+
import * as path19 from "node:path";
|
|
13876
|
+
var EXTENSIONS = {
|
|
13877
|
+
js: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"],
|
|
13878
|
+
py: [".py", ".pyi"],
|
|
13879
|
+
rs: [".rs"],
|
|
13880
|
+
jvm: [".java", ".kt", ".scala"],
|
|
13881
|
+
c: [".h", ".hpp", ".hh", ".hxx", ".c", ".cpp", ".cc", ".cxx"],
|
|
13882
|
+
ruby: [".rb"],
|
|
13883
|
+
go: [".go"]
|
|
13884
|
+
};
|
|
13885
|
+
var DIRECTORY_ENTRIES = {
|
|
13886
|
+
js: ["index"],
|
|
13887
|
+
py: ["__init__"],
|
|
13888
|
+
rs: ["mod"],
|
|
13889
|
+
ruby: ["index"]
|
|
13890
|
+
};
|
|
13891
|
+
function normalizeNamespace(value) {
|
|
13892
|
+
return value.replace(/::|[\\/]/g, ".").replace(/^\.+|\.+$/g, "").toLowerCase();
|
|
13893
|
+
}
|
|
13894
|
+
var ModuleResolver = class {
|
|
13895
|
+
structure;
|
|
13896
|
+
/** Lowercased portable path → the path as indexed (case is preserved). */
|
|
13897
|
+
byPath;
|
|
13898
|
+
/** Lowercased portable directory → files directly inside it, as indexed. */
|
|
13899
|
+
byDir;
|
|
13900
|
+
/** Normalized namespace → the file declaring it (first by path, stable). */
|
|
13901
|
+
byNamespace;
|
|
13902
|
+
constructor(structure, files, namespaces = []) {
|
|
13903
|
+
this.structure = structure;
|
|
13904
|
+
this.byPath = /* @__PURE__ */ new Map();
|
|
13905
|
+
this.byDir = /* @__PURE__ */ new Map();
|
|
13906
|
+
this.byNamespace = /* @__PURE__ */ new Map();
|
|
13907
|
+
const dirsByKey = /* @__PURE__ */ new Map();
|
|
13908
|
+
for (const file of files) {
|
|
13909
|
+
const portable = toPortablePath(file);
|
|
13910
|
+
const pathKey = portable.toLowerCase();
|
|
13911
|
+
const priorPath = this.byPath.get(pathKey);
|
|
13912
|
+
if (priorPath !== void 0 && priorPath !== file) this.byPath.delete(pathKey);
|
|
13913
|
+
else this.byPath.set(pathKey, file);
|
|
13914
|
+
const dir = path19.posix.dirname(portable);
|
|
13915
|
+
const dirKey = dir.toLowerCase();
|
|
13916
|
+
const knownDir = dirsByKey.get(dirKey);
|
|
13917
|
+
if (knownDir === void 0) {
|
|
13918
|
+
dirsByKey.set(dirKey, dir);
|
|
13919
|
+
this.byDir.set(dirKey, [file]);
|
|
13920
|
+
} else if (knownDir === dir) {
|
|
13921
|
+
this.byDir.get(dirKey)?.push(file);
|
|
13922
|
+
} else {
|
|
13923
|
+
dirsByKey.delete(dirKey);
|
|
13924
|
+
this.byDir.delete(dirKey);
|
|
13925
|
+
}
|
|
13926
|
+
}
|
|
13927
|
+
for (const { name, file } of namespaces) {
|
|
13928
|
+
const lang = detectLang(file);
|
|
13929
|
+
if (!lang) continue;
|
|
13930
|
+
const key = `${languageFamily(lang)}:${normalizeNamespace(name)}`;
|
|
13931
|
+
if (normalizeNamespace(name) && !this.byNamespace.has(key)) {
|
|
13932
|
+
this.byNamespace.set(key, file);
|
|
13933
|
+
}
|
|
13934
|
+
}
|
|
13935
|
+
}
|
|
13936
|
+
/**
|
|
13937
|
+
* Resolve `specifier` as written in `fromFile`.
|
|
13938
|
+
* Returns the indexed target path, or `undefined` when it is external or
|
|
13939
|
+
* cannot be located.
|
|
13940
|
+
*/
|
|
13941
|
+
resolve(fromFile, lang, specifier) {
|
|
13942
|
+
const spec = specifier.trim().replace(/\\/g, "/");
|
|
13943
|
+
if (!spec) return void 0;
|
|
13944
|
+
const from = toPortablePath(fromFile);
|
|
13945
|
+
switch (languageFamily(lang)) {
|
|
13946
|
+
case "js":
|
|
13947
|
+
return this.resolveJs(from, spec);
|
|
13948
|
+
case "go":
|
|
13949
|
+
return this.resolveGo(spec);
|
|
13950
|
+
case "py":
|
|
13951
|
+
return this.resolvePython(from, spec);
|
|
13952
|
+
case "rs":
|
|
13953
|
+
return this.resolveRust(from, spec);
|
|
13954
|
+
case "jvm":
|
|
13955
|
+
return this.resolveJvm(spec);
|
|
13956
|
+
case "c":
|
|
13957
|
+
return this.resolveInclude(from, spec);
|
|
13958
|
+
case "ruby":
|
|
13959
|
+
return this.resolveRuby(from, spec);
|
|
13960
|
+
case "dotnet":
|
|
13961
|
+
case "php":
|
|
13962
|
+
case "elixir":
|
|
13963
|
+
case "haskell":
|
|
13964
|
+
return this.resolveNamespace(lang, spec);
|
|
13965
|
+
default:
|
|
13966
|
+
return void 0;
|
|
13967
|
+
}
|
|
13968
|
+
}
|
|
13969
|
+
/**
|
|
13970
|
+
* Resolve a namespace specifier to the file declaring it.
|
|
13971
|
+
*
|
|
13972
|
+
* Tried whole first, then with the trailing segment dropped: `using Foo.Bar`
|
|
13973
|
+
* names a namespace outright, while PHP's `use App\Models\User` names a
|
|
13974
|
+
* *class* inside `App\Models`, so the prefix is what was declared.
|
|
13975
|
+
*/
|
|
13976
|
+
resolveNamespace(lang, spec) {
|
|
13977
|
+
const family = languageFamily(lang);
|
|
13978
|
+
const normalized = normalizeNamespace(spec);
|
|
13979
|
+
const exact = this.byNamespace.get(`${family}:${normalized}`);
|
|
13980
|
+
if (exact) return exact;
|
|
13981
|
+
const segments = normalized.split(".").filter(Boolean);
|
|
13982
|
+
if (segments.length < 2) return void 0;
|
|
13983
|
+
return this.byNamespace.get(`${family}:${segments.slice(0, -1).join(".")}`);
|
|
13984
|
+
}
|
|
13985
|
+
// ─── Lookup primitives ──────────────────────────────────────────────────────
|
|
13986
|
+
lookup(candidate) {
|
|
13987
|
+
return this.byPath.get(path19.posix.normalize(candidate).toLowerCase());
|
|
13988
|
+
}
|
|
13989
|
+
/**
|
|
13990
|
+
* Try `base` verbatim, then `base` + each extension, then each directory
|
|
13991
|
+
* entry point inside `base`.
|
|
13992
|
+
*/
|
|
13993
|
+
lookupWithExtensions(base, family) {
|
|
13994
|
+
const direct = this.lookup(base);
|
|
13995
|
+
if (direct) return direct;
|
|
13996
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
13997
|
+
const suffix = path19.posix.extname(base);
|
|
13998
|
+
const stem = suffix && extensions.includes(suffix) ? base.slice(0, -suffix.length) : base;
|
|
13999
|
+
for (const ext of extensions) {
|
|
14000
|
+
const hit = this.lookup(`${stem}${ext}`);
|
|
14001
|
+
if (hit) return hit;
|
|
14002
|
+
}
|
|
14003
|
+
for (const entry of DIRECTORY_ENTRIES[family] ?? []) {
|
|
14004
|
+
for (const ext of extensions) {
|
|
14005
|
+
const hit = this.lookup(path19.posix.join(base, `${entry}${ext}`));
|
|
14006
|
+
if (hit) return hit;
|
|
14007
|
+
}
|
|
14008
|
+
}
|
|
14009
|
+
return void 0;
|
|
14010
|
+
}
|
|
14011
|
+
/**
|
|
14012
|
+
* A representative indexed file inside `dir`, for ecosystems whose import
|
|
14013
|
+
* unit is a directory rather than a file (Go packages, JVM wildcard imports).
|
|
14014
|
+
*
|
|
14015
|
+
* The choice is deterministic — a file named after the directory, else the
|
|
14016
|
+
* first by name — so the same import always produces the same edge. Package
|
|
14017
|
+
* grouping is unaffected either way: every file in the directory carries the
|
|
14018
|
+
* same package label, so the package-level edge is exact regardless of which
|
|
14019
|
+
* member represents it.
|
|
14020
|
+
*/
|
|
14021
|
+
representativeIn(dir, family) {
|
|
14022
|
+
const members = this.byDir.get(path19.posix.normalize(dir).toLowerCase());
|
|
14023
|
+
if (!members?.length) return void 0;
|
|
14024
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
14025
|
+
const eligible = members.filter((file) => extensions.includes(path19.posix.extname(toPortablePath(file)).toLowerCase())).sort((a, b) => toPortablePath(a).localeCompare(toPortablePath(b)));
|
|
14026
|
+
if (eligible.length === 0) return void 0;
|
|
14027
|
+
const base = path19.posix.basename(path19.posix.normalize(dir)).toLowerCase();
|
|
14028
|
+
const named = eligible.find(
|
|
14029
|
+
(file) => path19.posix.basename(toPortablePath(file)).split(".")[0]?.toLowerCase() === base
|
|
14030
|
+
);
|
|
14031
|
+
return named ?? eligible[0];
|
|
14032
|
+
}
|
|
14033
|
+
// ─── Per-family resolution ──────────────────────────────────────────────────
|
|
14034
|
+
/** Relative specifiers, then workspace package names and their subpaths. */
|
|
14035
|
+
resolveJs(fromFile, spec) {
|
|
14036
|
+
if (spec.startsWith(".")) {
|
|
14037
|
+
const absolute = path19.posix.join(path19.posix.dirname(fromFile), spec);
|
|
14038
|
+
return this.lookupWithExtensions(absolute, "js");
|
|
14039
|
+
}
|
|
14040
|
+
const owner2 = this.structure.roots.find(
|
|
14041
|
+
(root) => root.kind === "npm" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
14042
|
+
);
|
|
14043
|
+
if (!owner2?.importPath) return void 0;
|
|
14044
|
+
const subpath = spec.slice(owner2.importPath.length).replace(/^\//, "");
|
|
14045
|
+
if (!subpath) {
|
|
14046
|
+
return this.lookupWithExtensions(path19.posix.join(owner2.dir, "src/index"), "js") ?? this.lookupWithExtensions(path19.posix.join(owner2.dir, "index"), "js");
|
|
14047
|
+
}
|
|
14048
|
+
return this.lookupWithExtensions(path19.posix.join(owner2.dir, subpath), "js") ?? this.lookupWithExtensions(path19.posix.join(owner2.dir, "src", subpath), "js");
|
|
14049
|
+
}
|
|
14050
|
+
/** Go import paths are absolute module paths; a package is a directory. */
|
|
14051
|
+
resolveGo(spec) {
|
|
14052
|
+
const owner2 = this.structure.roots.find(
|
|
14053
|
+
(root) => root.kind === "go" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
14054
|
+
);
|
|
14055
|
+
if (!owner2?.importPath) return void 0;
|
|
14056
|
+
const subpath = spec.slice(owner2.importPath.length).replace(/^\//, "");
|
|
14057
|
+
return this.representativeIn(path19.posix.join(owner2.dir, subpath), "go");
|
|
14058
|
+
}
|
|
14059
|
+
/**
|
|
14060
|
+
* `import a.b.c` / `from a.b import c`, plus PEP 328 relative imports whose
|
|
14061
|
+
* leading dots the extractor preserves (`.sibling`, `..parent.mod`).
|
|
14062
|
+
*/
|
|
14063
|
+
resolvePython(fromFile, spec) {
|
|
14064
|
+
const leadingDots = /^\.*/.exec(spec)?.[0].length ?? 0;
|
|
14065
|
+
if (leadingDots > 0) {
|
|
14066
|
+
let base = path19.posix.dirname(fromFile);
|
|
14067
|
+
for (let i = 1; i < leadingDots; i++) base = path19.posix.dirname(base);
|
|
14068
|
+
const rest = spec.slice(leadingDots).split(".").filter(Boolean);
|
|
14069
|
+
return this.lookupWithExtensions(path19.posix.join(base, ...rest), "py");
|
|
14070
|
+
}
|
|
14071
|
+
const segments = spec.split(".").filter(Boolean);
|
|
14072
|
+
if (segments.length === 0) return void 0;
|
|
14073
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "python").flatMap((root) => root.sourceRoots);
|
|
14074
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
14075
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, ...segments), "py");
|
|
14076
|
+
if (hit) return hit;
|
|
14077
|
+
if (segments.length > 1) {
|
|
14078
|
+
const parent = this.lookupWithExtensions(
|
|
14079
|
+
path19.posix.join(base, ...segments.slice(0, -1)),
|
|
14080
|
+
"py"
|
|
14081
|
+
);
|
|
14082
|
+
if (parent) return parent;
|
|
14083
|
+
}
|
|
13148
14084
|
}
|
|
13149
|
-
|
|
13150
|
-
const anchored = line.startsWith("/") || line.includes("/");
|
|
13151
|
-
if (line.startsWith("/")) line = line.slice(1);
|
|
13152
|
-
const body = globBody(line);
|
|
13153
|
-
const prefix = anchored ? "^" : "(?:^|.*/)";
|
|
13154
|
-
rules.push({
|
|
13155
|
-
eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
|
|
13156
|
-
under: new RegExp(`${prefix}${body}/.*$`),
|
|
13157
|
-
negated,
|
|
13158
|
-
dirOnly
|
|
13159
|
-
});
|
|
14085
|
+
return void 0;
|
|
13160
14086
|
}
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
14087
|
+
/** `use crate::a::b`, `use super::x`, `use self::y`, `use other_crate::z`. */
|
|
14088
|
+
resolveRust(fromFile, spec) {
|
|
14089
|
+
const segments = spec.split("::").filter(Boolean);
|
|
14090
|
+
if (segments.length === 0) return void 0;
|
|
14091
|
+
const head = segments[0];
|
|
14092
|
+
if (head === "self" || head === "super") {
|
|
14093
|
+
let base = path19.posix.dirname(fromFile);
|
|
14094
|
+
for (const segment of segments) {
|
|
14095
|
+
if (segment === "super") base = path19.posix.dirname(base);
|
|
14096
|
+
else if (segment !== "self") break;
|
|
14097
|
+
}
|
|
14098
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
14099
|
+
return this.lookupWithExtensions(path19.posix.join(base, ...rest2), "rs");
|
|
14100
|
+
}
|
|
14101
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
14102
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
14103
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
14104
|
+
);
|
|
14105
|
+
if (!crate) {
|
|
14106
|
+
return this.lookupWithExtensions(
|
|
14107
|
+
path19.posix.join(path19.posix.dirname(fromFile), ...segments),
|
|
14108
|
+
"rs"
|
|
14109
|
+
);
|
|
13167
14110
|
}
|
|
13168
|
-
|
|
13169
|
-
|
|
14111
|
+
const rest = segments.slice(1);
|
|
14112
|
+
for (const base of crate.sourceRoots) {
|
|
14113
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path19.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
14114
|
+
const exact = this.lookupWithExtensions(path19.posix.join(base, ...rest), "rs");
|
|
14115
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path19.posix.join(base, "lib"), "rs");
|
|
14116
|
+
if (hit) return hit;
|
|
14117
|
+
}
|
|
14118
|
+
return void 0;
|
|
14119
|
+
}
|
|
14120
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
14121
|
+
resolveJvm(spec) {
|
|
14122
|
+
const segments = spec.split(".").filter(Boolean);
|
|
14123
|
+
if (segments.length === 0) return void 0;
|
|
14124
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
14125
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
14126
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
14127
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
14128
|
+
const target = path19.posix.join(base, ...parts);
|
|
14129
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
14130
|
+
if (hit) return hit;
|
|
14131
|
+
}
|
|
14132
|
+
return void 0;
|
|
14133
|
+
}
|
|
14134
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
14135
|
+
resolveInclude(fromFile, spec) {
|
|
14136
|
+
const relative13 = this.lookupWithExtensions(
|
|
14137
|
+
path19.posix.join(path19.posix.dirname(fromFile), spec),
|
|
14138
|
+
"c"
|
|
14139
|
+
);
|
|
14140
|
+
if (relative13) return relative13;
|
|
14141
|
+
for (const base of [
|
|
14142
|
+
path19.posix.join(this.structure.projectRoot, "include"),
|
|
14143
|
+
this.structure.projectRoot
|
|
14144
|
+
]) {
|
|
14145
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, spec), "c");
|
|
14146
|
+
if (hit) return hit;
|
|
14147
|
+
}
|
|
14148
|
+
return void 0;
|
|
14149
|
+
}
|
|
14150
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
14151
|
+
resolveRuby(fromFile, spec) {
|
|
14152
|
+
const relative13 = this.lookupWithExtensions(
|
|
14153
|
+
path19.posix.join(path19.posix.dirname(fromFile), spec),
|
|
14154
|
+
"ruby"
|
|
14155
|
+
);
|
|
14156
|
+
if (relative13) return relative13;
|
|
14157
|
+
for (const base of [
|
|
14158
|
+
path19.posix.join(this.structure.projectRoot, "lib"),
|
|
14159
|
+
this.structure.projectRoot
|
|
14160
|
+
]) {
|
|
14161
|
+
const hit = this.lookupWithExtensions(path19.posix.join(base, spec), "ruby");
|
|
14162
|
+
if (hit) return hit;
|
|
14163
|
+
}
|
|
14164
|
+
return void 0;
|
|
14165
|
+
}
|
|
14166
|
+
};
|
|
14167
|
+
|
|
14168
|
+
// src/codebase-index/import-extractor.ts
|
|
14169
|
+
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
14170
|
+
var IMPORT_MAX_PER_FILE = 400;
|
|
14171
|
+
var DOTTED_IMPORT = [
|
|
14172
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
14173
|
+
];
|
|
14174
|
+
var LANG_IMPORTS = {
|
|
14175
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
14176
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
14177
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
14178
|
+
go: [
|
|
14179
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
14180
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
14181
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
14182
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
14183
|
+
],
|
|
14184
|
+
py: [
|
|
14185
|
+
{ re: /^[ \t]*import\s+([\w.]+)/gm },
|
|
14186
|
+
{ re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }
|
|
14187
|
+
],
|
|
14188
|
+
rs: [
|
|
14189
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
14190
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
14191
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
14192
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
14193
|
+
],
|
|
14194
|
+
java: DOTTED_IMPORT,
|
|
14195
|
+
kotlin: DOTTED_IMPORT,
|
|
14196
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
14197
|
+
csharp: [
|
|
14198
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
14199
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
14200
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
14201
|
+
],
|
|
14202
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
14203
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
14204
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
14205
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
14206
|
+
php: [
|
|
14207
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
14208
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
14209
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
14210
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
14211
|
+
],
|
|
14212
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
14213
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
14214
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
14215
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
14216
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
14217
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
14218
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
14219
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
14220
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
14221
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
14222
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
14223
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
14224
|
+
html: [
|
|
14225
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
14226
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
14227
|
+
],
|
|
14228
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
14229
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
14230
|
+
};
|
|
14231
|
+
function lastSegment(specifier) {
|
|
14232
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
14233
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
14234
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
14235
|
+
if (last === "*" || last === "_") {
|
|
14236
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
14237
|
+
}
|
|
14238
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
14239
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
14240
|
+
return dotted[dotted.length - 1] ?? last;
|
|
13170
14241
|
}
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
lines = raw.split("\n");
|
|
13176
|
-
} catch {
|
|
14242
|
+
function newlineOffsets(content) {
|
|
14243
|
+
const offsets = [];
|
|
14244
|
+
for (let i = 0; i < content.length; i++) {
|
|
14245
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
13177
14246
|
}
|
|
13178
|
-
return
|
|
14247
|
+
return offsets;
|
|
14248
|
+
}
|
|
14249
|
+
function lineAt(offsets, index) {
|
|
14250
|
+
let low = 0;
|
|
14251
|
+
let high = offsets.length;
|
|
14252
|
+
while (low < high) {
|
|
14253
|
+
const mid = low + high >>> 1;
|
|
14254
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
14255
|
+
else high = mid;
|
|
14256
|
+
}
|
|
14257
|
+
return low + 1;
|
|
14258
|
+
}
|
|
14259
|
+
function hasImportPatterns(lang) {
|
|
14260
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
14261
|
+
}
|
|
14262
|
+
function extractImports(opts) {
|
|
14263
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
14264
|
+
if (!patterns || !opts.content) return [];
|
|
14265
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
14266
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
14267
|
+
const refs = [];
|
|
14268
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14269
|
+
const offsets = newlineOffsets(content);
|
|
14270
|
+
for (const pattern of patterns) {
|
|
14271
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
14272
|
+
for (const match of content.matchAll(re)) {
|
|
14273
|
+
if (refs.length >= limit) return refs;
|
|
14274
|
+
const specifier = match[1]?.trim();
|
|
14275
|
+
if (!specifier) continue;
|
|
14276
|
+
const module = specifier;
|
|
14277
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
14278
|
+
if (!toName) continue;
|
|
14279
|
+
const key = `${module}\0${toName}`;
|
|
14280
|
+
if (seen.has(key)) continue;
|
|
14281
|
+
seen.add(key);
|
|
14282
|
+
refs.push({
|
|
14283
|
+
fromId: 0,
|
|
14284
|
+
toName,
|
|
14285
|
+
callType: "import",
|
|
14286
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
14287
|
+
lang: opts.lang,
|
|
14288
|
+
module
|
|
14289
|
+
});
|
|
14290
|
+
}
|
|
14291
|
+
}
|
|
14292
|
+
return refs;
|
|
13179
14293
|
}
|
|
13180
|
-
|
|
13181
|
-
// src/codebase-index/indexer.ts
|
|
13182
|
-
init_languages2();
|
|
13183
14294
|
|
|
13184
14295
|
// src/codebase-index/parser-dispatch.ts
|
|
13185
14296
|
async function parseFileContent(file, content, lang) {
|
|
14297
|
+
const parsed = await dispatch(file, content, lang);
|
|
14298
|
+
return withRelations(parsed, content, lang);
|
|
14299
|
+
}
|
|
14300
|
+
async function dispatch(file, content, lang) {
|
|
13186
14301
|
switch (lang) {
|
|
13187
14302
|
case "ts":
|
|
13188
14303
|
case "tsx":
|
|
@@ -13217,6 +14332,13 @@ async function parseFileContent(file, content, lang) {
|
|
|
13217
14332
|
}
|
|
13218
14333
|
}
|
|
13219
14334
|
}
|
|
14335
|
+
function withRelations(parsed, content, lang) {
|
|
14336
|
+
let refs = parsed.refs ?? [];
|
|
14337
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
14338
|
+
refs = extractImports({ content, lang });
|
|
14339
|
+
}
|
|
14340
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
14341
|
+
}
|
|
13220
14342
|
|
|
13221
14343
|
// src/codebase-index/indexer.ts
|
|
13222
14344
|
var YIELD_EVERY_N = 50;
|
|
@@ -13253,7 +14375,7 @@ function normalizeComparablePath(value) {
|
|
|
13253
14375
|
}
|
|
13254
14376
|
function gitOutput(projectRoot, args) {
|
|
13255
14377
|
return new Promise((resolve17, reject) => {
|
|
13256
|
-
|
|
14378
|
+
execFile(
|
|
13257
14379
|
"git",
|
|
13258
14380
|
["-C", projectRoot, ...args],
|
|
13259
14381
|
{
|
|
@@ -13384,13 +14506,40 @@ function assignRefsToSymbols2(refs, symbols) {
|
|
|
13384
14506
|
}
|
|
13385
14507
|
if (!owner2 && ref.callType === "import") owner2 = ordered[0];
|
|
13386
14508
|
if (!owner2 || owner2.id <= 0) continue;
|
|
13387
|
-
const key = `${owner2.id}:${ref.toName}:${ref.callType}`;
|
|
14509
|
+
const key = `${owner2.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
13388
14510
|
if (seen.has(key)) continue;
|
|
13389
14511
|
seen.add(key);
|
|
13390
14512
|
assigned.push({ ...ref, fromId: owner2.id });
|
|
13391
14513
|
}
|
|
13392
14514
|
return assigned;
|
|
13393
14515
|
}
|
|
14516
|
+
async function resolveProjectRelations(store, projectRoot, opts) {
|
|
14517
|
+
if (opts.signal?.aborted) return;
|
|
14518
|
+
try {
|
|
14519
|
+
const indexedFiles = store.getAllFileMetas().map((meta) => meta.file);
|
|
14520
|
+
if (indexedFiles.length === 0) return;
|
|
14521
|
+
const structure = await detectModuleRoots(projectRoot, indexedFiles);
|
|
14522
|
+
if (opts.signal?.aborted) return;
|
|
14523
|
+
store.setFilePackages(assignPackageLabels(structure, indexedFiles));
|
|
14524
|
+
const resolver = new ModuleResolver(
|
|
14525
|
+
structure,
|
|
14526
|
+
indexedFiles,
|
|
14527
|
+
store.getNamespaceDeclarations()
|
|
14528
|
+
);
|
|
14529
|
+
const pending2 = store.getUnresolvedImports(opts.onlyFiles);
|
|
14530
|
+
const resolutions = [];
|
|
14531
|
+
for (const entry of pending2) {
|
|
14532
|
+
const toFile = resolver.resolve(entry.fromFile, entry.lang, entry.module);
|
|
14533
|
+
if (toFile && toFile !== entry.fromFile) {
|
|
14534
|
+
resolutions.push({ ...entry, toFile });
|
|
14535
|
+
}
|
|
14536
|
+
}
|
|
14537
|
+
if (opts.signal?.aborted) return;
|
|
14538
|
+
store.applyImportResolutions(resolutions);
|
|
14539
|
+
} catch (err) {
|
|
14540
|
+
opts.errors.push(`relation resolution: ${err instanceof Error ? err.message : String(err)}`);
|
|
14541
|
+
}
|
|
14542
|
+
}
|
|
13394
14543
|
async function runIndexerWithStore(store, opts) {
|
|
13395
14544
|
const { projectRoot, langs, ignore = [], signal } = opts;
|
|
13396
14545
|
const relationGraphVersion = "2";
|
|
@@ -13635,6 +14784,14 @@ async function runIndexerWithStore(store, opts) {
|
|
|
13635
14784
|
}
|
|
13636
14785
|
}
|
|
13637
14786
|
if (needsFullRefResolution) store.resolveRefs();
|
|
14787
|
+
await resolveProjectRelations(store, projectRoot, {
|
|
14788
|
+
// A watcher run re-resolves only what it touched; a full run (or a contract
|
|
14789
|
+
// bump) re-resolves everything, because a newly indexed file can be the
|
|
14790
|
+
// target of imports written long before it.
|
|
14791
|
+
onlyFiles: needsFullRefResolution ? void 0 : opts.files,
|
|
14792
|
+
errors,
|
|
14793
|
+
signal
|
|
14794
|
+
});
|
|
13638
14795
|
store.setMetadata("ref_resolution_version", refResolutionVersion);
|
|
13639
14796
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
13640
14797
|
if (!opts.files || filesIndexed >= 50) store.optimize();
|
|
@@ -15184,17 +16341,17 @@ import {
|
|
|
15184
16341
|
} from "@wrongstack/core/design";
|
|
15185
16342
|
async function resolveReal(p) {
|
|
15186
16343
|
const resolved = path25.resolve(p);
|
|
15187
|
-
let
|
|
16344
|
+
let probe = resolved;
|
|
15188
16345
|
const missing = [];
|
|
15189
16346
|
for (; ; ) {
|
|
15190
16347
|
try {
|
|
15191
|
-
return path25.resolve(await fs20.realpath(
|
|
16348
|
+
return path25.resolve(await fs20.realpath(probe), ...missing);
|
|
15192
16349
|
} catch (err) {
|
|
15193
16350
|
if (err.code === "ENOENT") {
|
|
15194
|
-
const parent = path25.dirname(
|
|
15195
|
-
if (parent ===
|
|
15196
|
-
missing.unshift(path25.basename(
|
|
15197
|
-
|
|
16351
|
+
const parent = path25.dirname(probe);
|
|
16352
|
+
if (parent === probe) return resolved;
|
|
16353
|
+
missing.unshift(path25.basename(probe));
|
|
16354
|
+
probe = parent;
|
|
15198
16355
|
continue;
|
|
15199
16356
|
}
|
|
15200
16357
|
return resolved;
|
|
@@ -15469,7 +16626,7 @@ Replace off-palette colors with kit tokens (or the materialized CSS vars / token
|
|
|
15469
16626
|
|
|
15470
16627
|
// src/diff.ts
|
|
15471
16628
|
init_util();
|
|
15472
|
-
import { spawn as
|
|
16629
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
15473
16630
|
import { statSync as statSync3 } from "node:fs";
|
|
15474
16631
|
import * as fs21 from "node:fs/promises";
|
|
15475
16632
|
import * as path26 from "node:path";
|
|
@@ -15572,7 +16729,7 @@ function runGit(args, cwd, signal) {
|
|
|
15572
16729
|
return new Promise((resolve17) => {
|
|
15573
16730
|
let stdout = "";
|
|
15574
16731
|
let stderr = "";
|
|
15575
|
-
const child =
|
|
16732
|
+
const child = spawn7("git", args, {
|
|
15576
16733
|
cwd,
|
|
15577
16734
|
signal,
|
|
15578
16735
|
env: buildChildEnv3(),
|
|
@@ -15783,7 +16940,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
15783
16940
|
|
|
15784
16941
|
// src/e2e.ts
|
|
15785
16942
|
init_util();
|
|
15786
|
-
import { open, readdir as
|
|
16943
|
+
import { open, readdir as readdir7 } from "node:fs/promises";
|
|
15787
16944
|
import * as path27 from "node:path";
|
|
15788
16945
|
async function readBoundedText(filePath, maxBytes) {
|
|
15789
16946
|
let handle;
|
|
@@ -15901,7 +17058,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
15901
17058
|
}
|
|
15902
17059
|
let entries;
|
|
15903
17060
|
try {
|
|
15904
|
-
entries = await
|
|
17061
|
+
entries = await readdir7(current.directory, { withFileTypes: true });
|
|
15905
17062
|
} catch {
|
|
15906
17063
|
continue;
|
|
15907
17064
|
}
|
|
@@ -15960,7 +17117,7 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
15960
17117
|
while (true) {
|
|
15961
17118
|
const names = /* @__PURE__ */ new Set();
|
|
15962
17119
|
try {
|
|
15963
|
-
for (const entry of await
|
|
17120
|
+
for (const entry of await readdir7(directory)) names.add(entry);
|
|
15964
17121
|
} catch {
|
|
15965
17122
|
}
|
|
15966
17123
|
if (names.has("pnpm-lock.yaml")) return "pnpm";
|
|
@@ -16028,7 +17185,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
16028
17185
|
if (scanned > MAX_SCAN_DIRECTORIES) return { count, samples, truncated: true };
|
|
16029
17186
|
let entries;
|
|
16030
17187
|
try {
|
|
16031
|
-
entries = await
|
|
17188
|
+
entries = await readdir7(directory, { withFileTypes: true });
|
|
16032
17189
|
} catch {
|
|
16033
17190
|
continue;
|
|
16034
17191
|
}
|
|
@@ -16229,7 +17386,7 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
16229
17386
|
const exact = [];
|
|
16230
17387
|
let idx = fileLf.indexOf(oldLf);
|
|
16231
17388
|
while (idx !== -1) {
|
|
16232
|
-
exact.push({ start: idx, end: idx + oldLf.length, startLine:
|
|
17389
|
+
exact.push({ start: idx, end: idx + oldLf.length, startLine: lineAt2(fileLf, idx) });
|
|
16233
17390
|
idx = fileLf.indexOf(oldLf, idx + 1);
|
|
16234
17391
|
}
|
|
16235
17392
|
if (exact.length > 0) return { tier: "exact", matches: exact };
|
|
@@ -16261,7 +17418,7 @@ function findLadderMatches(fileLf, oldLf) {
|
|
|
16261
17418
|
if (normalized.length > 0) return { tier: "whitespace-normalized", matches: normalized };
|
|
16262
17419
|
return fuzzyScan(fileLines, needleLines, offsets);
|
|
16263
17420
|
}
|
|
16264
|
-
function
|
|
17421
|
+
function lineAt2(text, pos) {
|
|
16265
17422
|
if (pos < 512) {
|
|
16266
17423
|
let line2 = 1;
|
|
16267
17424
|
for (let i = 0; i < pos; i++) {
|
|
@@ -16723,7 +17880,7 @@ Compare this against your old_string and retry with the file's actual text.` : "
|
|
|
16723
17880
|
};
|
|
16724
17881
|
|
|
16725
17882
|
// src/exec.ts
|
|
16726
|
-
import { spawn as
|
|
17883
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
16727
17884
|
import {
|
|
16728
17885
|
emitProcessCompleted as emitProcessCompleted3,
|
|
16729
17886
|
emitProcessOutput as emitProcessOutput3,
|
|
@@ -17644,6 +18801,26 @@ var BLOCKED_ARG_PATTERNS = {
|
|
|
17644
18801
|
pnpm: [],
|
|
17645
18802
|
npx: []
|
|
17646
18803
|
};
|
|
18804
|
+
var BLOCKED_OPTION_NAMES = {
|
|
18805
|
+
git: /* @__PURE__ */ new Set([
|
|
18806
|
+
"--exec",
|
|
18807
|
+
"--upload-pack",
|
|
18808
|
+
"--receive-pack",
|
|
18809
|
+
"--exec-path",
|
|
18810
|
+
"--git-dir",
|
|
18811
|
+
"--work-tree",
|
|
18812
|
+
"--namespace",
|
|
18813
|
+
"-c",
|
|
18814
|
+
"--config",
|
|
18815
|
+
"--config-env",
|
|
18816
|
+
"-C"
|
|
18817
|
+
]),
|
|
18818
|
+
find: /* @__PURE__ */ new Set(["-exec", "-ok", "-execdir"])
|
|
18819
|
+
};
|
|
18820
|
+
function optionName(arg) {
|
|
18821
|
+
const eq = arg.indexOf("=");
|
|
18822
|
+
return eq > 0 ? arg.slice(0, eq) : arg;
|
|
18823
|
+
}
|
|
17647
18824
|
var BLOCKED_SUBCOMMANDS = {
|
|
17648
18825
|
docker: /* @__PURE__ */ new Set(["push"]),
|
|
17649
18826
|
podman: /* @__PURE__ */ new Set(["push"]),
|
|
@@ -17681,6 +18858,15 @@ function validateArgs(cmd, args) {
|
|
|
17681
18858
|
const blocked2 = blockedSequences.find((seq) => seq.every((part, idx) => actual[idx] === part));
|
|
17682
18859
|
if (blocked2) return `Blocked subcommand "${blocked2.join(" ")}" for command "${cmd}"`;
|
|
17683
18860
|
}
|
|
18861
|
+
const blockedOptions = BLOCKED_OPTION_NAMES[cmd];
|
|
18862
|
+
if (blockedOptions) {
|
|
18863
|
+
for (const arg of args) {
|
|
18864
|
+
if (arg === "--") break;
|
|
18865
|
+
if (blockedOptions.has(optionName(arg))) {
|
|
18866
|
+
return `Blocked option "${optionName(arg)}" for command "${cmd}"`;
|
|
18867
|
+
}
|
|
18868
|
+
}
|
|
18869
|
+
}
|
|
17684
18870
|
const blocked = BLOCKED_ARG_PATTERNS[cmd];
|
|
17685
18871
|
if (!blocked) return null;
|
|
17686
18872
|
for (const arg of args) {
|
|
@@ -17863,7 +19049,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
17863
19049
|
};
|
|
17864
19050
|
let child;
|
|
17865
19051
|
try {
|
|
17866
|
-
child =
|
|
19052
|
+
child = spawn8(spawnCmd, spawnArgs, {
|
|
17867
19053
|
cwd,
|
|
17868
19054
|
env: buildChildEnv2(sessionId),
|
|
17869
19055
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -18497,7 +19683,7 @@ async function detectFixer(cwd) {
|
|
|
18497
19683
|
|
|
18498
19684
|
// src/git.ts
|
|
18499
19685
|
init_util();
|
|
18500
|
-
import { spawn as
|
|
19686
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
18501
19687
|
import { statSync as statSync4 } from "node:fs";
|
|
18502
19688
|
import { dirname as dirname14, resolve as resolve13, sep as sep6 } from "node:path";
|
|
18503
19689
|
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
@@ -18714,11 +19900,8 @@ function buildArgs(input) {
|
|
|
18714
19900
|
...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]
|
|
18715
19901
|
] : ["branch"];
|
|
18716
19902
|
case "checkout":
|
|
18717
|
-
return [
|
|
18718
|
-
|
|
18719
|
-
...input.branch ? ["--", input.branch] : [],
|
|
18720
|
-
...files.length ? ["--", ...files] : []
|
|
18721
|
-
];
|
|
19903
|
+
if (files.length) return ["checkout", "--", ...files];
|
|
19904
|
+
return input.branch ? ["checkout", input.branch, "--"] : ["checkout"];
|
|
18722
19905
|
case "stash":
|
|
18723
19906
|
return input.message ? ["stash", "push", "-m", input.message] : ["stash", "push"];
|
|
18724
19907
|
case "push":
|
|
@@ -18761,7 +19944,7 @@ function runGit2(args, cwd, signal) {
|
|
|
18761
19944
|
return new Promise((resolve17) => {
|
|
18762
19945
|
let stdout = "";
|
|
18763
19946
|
let stderr = "";
|
|
18764
|
-
const child =
|
|
19947
|
+
const child = spawn9("git", args, {
|
|
18765
19948
|
cwd,
|
|
18766
19949
|
signal,
|
|
18767
19950
|
env: buildChildEnv4(),
|
|
@@ -18823,7 +20006,7 @@ async function mapWithConcurrency2(items, limit, fn) {
|
|
|
18823
20006
|
|
|
18824
20007
|
// src/glob.ts
|
|
18825
20008
|
init_util();
|
|
18826
|
-
var DEFAULT_IGNORE2 = DEFAULT_WALK_IGNORE_DIRS2;
|
|
20009
|
+
var DEFAULT_IGNORE2 = new Set(DEFAULT_WALK_IGNORE_DIRS2);
|
|
18827
20010
|
var WALK_CONCURRENCY = 16;
|
|
18828
20011
|
var globTool = {
|
|
18829
20012
|
name: "glob",
|
|
@@ -18902,7 +20085,7 @@ var globTool = {
|
|
|
18902
20085
|
const matchedFiles = [];
|
|
18903
20086
|
for (const e of entries) {
|
|
18904
20087
|
const name = e.name;
|
|
18905
|
-
if (DEFAULT_IGNORE2.
|
|
20088
|
+
if (DEFAULT_IGNORE2.has(name)) continue;
|
|
18906
20089
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
18907
20090
|
const full = path30.join(dir, name);
|
|
18908
20091
|
if (e.isDirectory()) {
|
|
@@ -18952,7 +20135,7 @@ var globTool = {
|
|
|
18952
20135
|
};
|
|
18953
20136
|
|
|
18954
20137
|
// src/grep.ts
|
|
18955
|
-
import { spawn as
|
|
20138
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
18956
20139
|
import * as fs25 from "node:fs/promises";
|
|
18957
20140
|
import * as path31 from "node:path";
|
|
18958
20141
|
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
@@ -18976,6 +20159,81 @@ var DANGEROUS_PATTERNS = [
|
|
|
18976
20159
|
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
18977
20160
|
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
18978
20161
|
];
|
|
20162
|
+
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
20163
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
20164
|
+
if (pattern[i] !== "(") continue;
|
|
20165
|
+
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
20166
|
+
let depth = 0;
|
|
20167
|
+
let inClass = false;
|
|
20168
|
+
let j = i;
|
|
20169
|
+
for (; j < pattern.length; j++) {
|
|
20170
|
+
const ch = pattern[j];
|
|
20171
|
+
if (ch === "\\") {
|
|
20172
|
+
j++;
|
|
20173
|
+
continue;
|
|
20174
|
+
}
|
|
20175
|
+
if (inClass) {
|
|
20176
|
+
if (ch === "]") inClass = false;
|
|
20177
|
+
continue;
|
|
20178
|
+
}
|
|
20179
|
+
if (ch === "[") {
|
|
20180
|
+
inClass = true;
|
|
20181
|
+
continue;
|
|
20182
|
+
}
|
|
20183
|
+
if (ch === "(") depth++;
|
|
20184
|
+
else if (ch === ")") {
|
|
20185
|
+
depth--;
|
|
20186
|
+
if (depth === 0) break;
|
|
20187
|
+
}
|
|
20188
|
+
}
|
|
20189
|
+
if (j >= pattern.length) return false;
|
|
20190
|
+
const next = pattern[j + 1];
|
|
20191
|
+
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
20192
|
+
let inner = pattern.slice(i + 1, j);
|
|
20193
|
+
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
20194
|
+
const branches = [];
|
|
20195
|
+
let current = "";
|
|
20196
|
+
let d = 0;
|
|
20197
|
+
let cls = false;
|
|
20198
|
+
for (let k = 0; k < inner.length; k++) {
|
|
20199
|
+
const ch = inner[k];
|
|
20200
|
+
if (ch === "\\") {
|
|
20201
|
+
current += ch + (inner[k + 1] ?? "");
|
|
20202
|
+
k++;
|
|
20203
|
+
continue;
|
|
20204
|
+
}
|
|
20205
|
+
if (cls) {
|
|
20206
|
+
if (ch === "]") cls = false;
|
|
20207
|
+
current += ch;
|
|
20208
|
+
continue;
|
|
20209
|
+
}
|
|
20210
|
+
if (ch === "[") {
|
|
20211
|
+
cls = true;
|
|
20212
|
+
current += ch;
|
|
20213
|
+
continue;
|
|
20214
|
+
}
|
|
20215
|
+
if (ch === "(") d++;
|
|
20216
|
+
if (ch === ")") d--;
|
|
20217
|
+
if (ch === "|" && d === 0) {
|
|
20218
|
+
branches.push(current);
|
|
20219
|
+
current = "";
|
|
20220
|
+
continue;
|
|
20221
|
+
}
|
|
20222
|
+
current += ch;
|
|
20223
|
+
}
|
|
20224
|
+
branches.push(current);
|
|
20225
|
+
if (branches.length < 2) continue;
|
|
20226
|
+
for (let a = 0; a < branches.length; a++) {
|
|
20227
|
+
for (let b = a + 1; b < branches.length; b++) {
|
|
20228
|
+
const x = branches[a];
|
|
20229
|
+
const y = branches[b];
|
|
20230
|
+
if (x === "" || y === "") return true;
|
|
20231
|
+
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
20232
|
+
}
|
|
20233
|
+
}
|
|
20234
|
+
}
|
|
20235
|
+
return false;
|
|
20236
|
+
}
|
|
18979
20237
|
function compileUserRegex(pattern, flags) {
|
|
18980
20238
|
if (typeof pattern !== "string") {
|
|
18981
20239
|
return { ok: false, reason: "pattern must be a string" };
|
|
@@ -18994,6 +20252,12 @@ function compileUserRegex(pattern, flags) {
|
|
|
18994
20252
|
};
|
|
18995
20253
|
}
|
|
18996
20254
|
}
|
|
20255
|
+
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
20256
|
+
return {
|
|
20257
|
+
ok: false,
|
|
20258
|
+
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
20259
|
+
};
|
|
20260
|
+
}
|
|
18997
20261
|
try {
|
|
18998
20262
|
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
18999
20263
|
} catch (err) {
|
|
@@ -19010,7 +20274,7 @@ function capSubject(line) {
|
|
|
19010
20274
|
|
|
19011
20275
|
// src/grep.ts
|
|
19012
20276
|
init_util();
|
|
19013
|
-
var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
|
|
20277
|
+
var DEFAULT_IGNORE3 = new Set(DEFAULT_WALK_IGNORE_DIRS3);
|
|
19014
20278
|
var NATIVE_SCAN_CONCURRENCY = 32;
|
|
19015
20279
|
var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
|
|
19016
20280
|
var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
@@ -19083,7 +20347,7 @@ var grepTool = {
|
|
|
19083
20347
|
field: "pattern"
|
|
19084
20348
|
});
|
|
19085
20349
|
}
|
|
19086
|
-
const base = input.path ?
|
|
20350
|
+
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
19087
20351
|
const mode = input.output_mode ?? "content";
|
|
19088
20352
|
const limit = Math.max(1, Math.min(input.limit ?? 200, 2e3));
|
|
19089
20353
|
const validation = compileUserRegex(input.pattern, input.case_insensitive ? "i" : "");
|
|
@@ -19109,7 +20373,7 @@ var grepTool = {
|
|
|
19109
20373
|
async function detectRg(signal) {
|
|
19110
20374
|
return new Promise((resolve17) => {
|
|
19111
20375
|
try {
|
|
19112
|
-
const p =
|
|
20376
|
+
const p = spawn10("rg", ["--version"], { env: buildChildEnv5(), stdio: "ignore", signal, windowsHide: true });
|
|
19113
20377
|
p.on("error", () => resolve17(false));
|
|
19114
20378
|
p.on("close", (code) => resolve17(code === 0));
|
|
19115
20379
|
} catch {
|
|
@@ -19143,7 +20407,7 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
19143
20407
|
const FLUSH_AT = 16;
|
|
19144
20408
|
const MAX_BUF_BYTES = 1e6;
|
|
19145
20409
|
let bufOverflow = false;
|
|
19146
|
-
const child =
|
|
20410
|
+
const child = spawn10("rg", args, {
|
|
19147
20411
|
signal,
|
|
19148
20412
|
env: buildChildEnv5(),
|
|
19149
20413
|
// rg diagnostics are not part of the tool result. Ignoring stderr avoids
|
|
@@ -19408,7 +20672,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
19408
20672
|
const subdirs = [];
|
|
19409
20673
|
for (const e of entries) {
|
|
19410
20674
|
if (stopped) return;
|
|
19411
|
-
if (DEFAULT_IGNORE3.
|
|
20675
|
+
if (DEFAULT_IGNORE3.has(e.name)) continue;
|
|
19412
20676
|
if (e.isSymbolicLink()) continue;
|
|
19413
20677
|
const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
|
|
19414
20678
|
const full = path31.join(dir, e.name);
|
|
@@ -19440,7 +20704,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
19440
20704
|
init_spawn_stream();
|
|
19441
20705
|
init_util();
|
|
19442
20706
|
init_legacy_bridge();
|
|
19443
|
-
import { join as
|
|
20707
|
+
import { join as join24 } from "node:path";
|
|
19444
20708
|
import {
|
|
19445
20709
|
detectEcosystem as detectPackageEcosystem,
|
|
19446
20710
|
recordPackageAction
|
|
@@ -19624,17 +20888,17 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
19624
20888
|
case "pnpm":
|
|
19625
20889
|
case "yarn":
|
|
19626
20890
|
case "npm":
|
|
19627
|
-
return
|
|
20891
|
+
return join24(cwd, "package.json");
|
|
19628
20892
|
/* v8 ignore next 2 -- pkgManager is always pnpm/yarn/npm; the default is defensive. */
|
|
19629
20893
|
default:
|
|
19630
|
-
return
|
|
20894
|
+
return join24(cwd, "package.json");
|
|
19631
20895
|
}
|
|
19632
20896
|
}
|
|
19633
20897
|
|
|
19634
20898
|
// src/json.ts
|
|
19635
|
-
init_util();
|
|
19636
20899
|
import * as fs26 from "node:fs/promises";
|
|
19637
20900
|
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
20901
|
+
init_util();
|
|
19638
20902
|
var MAX_JSON_FILE_BYTES = 16 * 1024 * 1024;
|
|
19639
20903
|
var MAX_JSON_FILE_BYTES_HUMAN = "16 MiB";
|
|
19640
20904
|
var JsonFileTooLargeError = class extends Error {
|
|
@@ -20073,8 +21337,12 @@ function validateJsonSchema(data, schema) {
|
|
|
20073
21337
|
}
|
|
20074
21338
|
}
|
|
20075
21339
|
if (typeof value === "string" && s["pattern"]) {
|
|
20076
|
-
const
|
|
20077
|
-
if (!
|
|
21340
|
+
const compiled = compileUserRegex(s["pattern"], "");
|
|
21341
|
+
if (!compiled.ok) {
|
|
21342
|
+
errors.push(`${path39}: invalid schema pattern \u2014 ${compiled.reason}`);
|
|
21343
|
+
} else if (!compiled.regex.test(capSubject(value))) {
|
|
21344
|
+
errors.push(`${path39}: does not match pattern ${s["pattern"]}`);
|
|
21345
|
+
}
|
|
20078
21346
|
}
|
|
20079
21347
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
20080
21348
|
errors.push(`${path39}: string too short (min ${s["minLength"]})`);
|
|
@@ -22473,7 +23741,7 @@ async function detectLinter(cwd) {
|
|
|
22473
23741
|
}
|
|
22474
23742
|
|
|
22475
23743
|
// src/logs.ts
|
|
22476
|
-
import { spawn as
|
|
23744
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
22477
23745
|
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core/utils";
|
|
22478
23746
|
init_util();
|
|
22479
23747
|
var logsTool = {
|
|
@@ -22580,7 +23848,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
22580
23848
|
clearTimeout(timer);
|
|
22581
23849
|
resolve17(result);
|
|
22582
23850
|
};
|
|
22583
|
-
const child =
|
|
23851
|
+
const child = spawn11("docker", args, { cwd, signal, env: buildChildEnv6(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
22584
23852
|
const timer = setTimeout(() => {
|
|
22585
23853
|
child.kill("SIGTERM");
|
|
22586
23854
|
finish(empty());
|
|
@@ -22689,7 +23957,7 @@ function parseLine(line) {
|
|
|
22689
23957
|
// src/outdated.ts
|
|
22690
23958
|
init_util();
|
|
22691
23959
|
init_win32_resolve();
|
|
22692
|
-
import { spawn as
|
|
23960
|
+
import { spawn as spawn12 } from "node:child_process";
|
|
22693
23961
|
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core/utils";
|
|
22694
23962
|
var outdatedTool = {
|
|
22695
23963
|
name: "outdated",
|
|
@@ -22809,7 +24077,7 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
22809
24077
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
|
|
22810
24078
|
const spawnCmd = shim?.command ?? resolved;
|
|
22811
24079
|
const spawnArgs = shim?.args ?? args;
|
|
22812
|
-
const child =
|
|
24080
|
+
const child = spawn12(spawnCmd, spawnArgs, {
|
|
22813
24081
|
cwd,
|
|
22814
24082
|
signal,
|
|
22815
24083
|
env: buildChildEnv7(),
|
|
@@ -22875,16 +24143,16 @@ function parseOutdatedOutput(json2, exitCode) {
|
|
|
22875
24143
|
|
|
22876
24144
|
// src/patch.ts
|
|
22877
24145
|
init_util();
|
|
22878
|
-
import { spawn as
|
|
24146
|
+
import { spawn as spawn13 } from "node:child_process";
|
|
22879
24147
|
import * as fs27 from "node:fs/promises";
|
|
22880
24148
|
import * as os9 from "node:os";
|
|
22881
24149
|
import * as path32 from "node:path";
|
|
22882
|
-
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core/utils";
|
|
24150
|
+
import { buildChildEnv as buildChildEnv8, toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
22883
24151
|
var patchTool = {
|
|
22884
24152
|
name: "patch",
|
|
22885
24153
|
category: "Filesystem",
|
|
22886
24154
|
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.",
|
|
22887
|
-
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-
|
|
24155
|
+
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.",
|
|
22888
24156
|
selection: {
|
|
22889
24157
|
doNotUseWhen: "you do not already have a unified diff or only need one precise replacement.",
|
|
22890
24158
|
useInstead: ["edit"]
|
|
@@ -22910,31 +24178,50 @@ var patchTool = {
|
|
|
22910
24178
|
},
|
|
22911
24179
|
async execute(input, ctx, opts) {
|
|
22912
24180
|
if (!input?.patch) throw new Error("patch: patch content is required");
|
|
22913
|
-
const dir = input.directory ? safeResolve(input.directory, ctx) : ctx.cwd;
|
|
22914
24181
|
const strip = Math.max(1, input.strip ?? 1);
|
|
22915
24182
|
const dryRun = input.dry_run ?? false;
|
|
24183
|
+
const refuse = (message) => ({
|
|
24184
|
+
applied: 0,
|
|
24185
|
+
rejected: 1,
|
|
24186
|
+
files: [],
|
|
24187
|
+
dry_run: dryRun,
|
|
24188
|
+
message
|
|
24189
|
+
});
|
|
24190
|
+
let dir;
|
|
24191
|
+
try {
|
|
24192
|
+
dir = input.directory ? await safeResolveReal(input.directory, ctx) : ctx.cwd;
|
|
24193
|
+
} catch (err) {
|
|
24194
|
+
return refuse(`patch refused: ${toErrorMessage4(err)}`);
|
|
24195
|
+
}
|
|
24196
|
+
const realRoot = await fs27.realpath(ctx.projectRoot).catch(() => path32.resolve(ctx.projectRoot));
|
|
22916
24197
|
const targets = extractDiffTargets(input.patch);
|
|
22917
24198
|
const resolvedTargets = [];
|
|
22918
24199
|
for (const t of targets) {
|
|
22919
|
-
const stripped = stripPathComponents(t, strip);
|
|
24200
|
+
const stripped = stripPathComponents(t.raw, strip);
|
|
22920
24201
|
if (!stripped) continue;
|
|
24202
|
+
if (path32.isAbsolute(stripped)) {
|
|
24203
|
+
return refuse(`patch refused: target "${t.raw}" strips to absolute path`);
|
|
24204
|
+
}
|
|
22921
24205
|
const candidate = path32.resolve(dir, stripped);
|
|
22922
|
-
|
|
24206
|
+
let real;
|
|
24207
|
+
try {
|
|
24208
|
+
real = await resolveRealInsideRoot(candidate, ctx);
|
|
24209
|
+
} catch (err) {
|
|
24210
|
+
return refuse(`patch refused: target "${t.raw}" ${toErrorMessage4(err)}`);
|
|
24211
|
+
}
|
|
24212
|
+
const rel = path32.relative(realRoot, real);
|
|
22923
24213
|
if (rel.startsWith("..") || path32.isAbsolute(rel)) {
|
|
22924
|
-
return {
|
|
22925
|
-
applied: 0,
|
|
22926
|
-
rejected: 1,
|
|
22927
|
-
files: [],
|
|
22928
|
-
dry_run: dryRun,
|
|
22929
|
-
message: `patch refused: target "${t}" resolves outside project root`
|
|
22930
|
-
};
|
|
24214
|
+
return refuse(`patch refused: target "${t.raw}" resolves outside project root`);
|
|
22931
24215
|
}
|
|
22932
|
-
resolvedTargets.push(
|
|
24216
|
+
resolvedTargets.push({ raw: t.raw, deleted: t.deleted, abs: real });
|
|
22933
24217
|
}
|
|
22934
24218
|
const beforeContents = /* @__PURE__ */ new Map();
|
|
24219
|
+
const beforeExisted = /* @__PURE__ */ new Set();
|
|
22935
24220
|
if (!dryRun) {
|
|
22936
24221
|
for (const target of resolvedTargets) {
|
|
22937
|
-
|
|
24222
|
+
const existed = (await fs27.stat(target.abs).catch(() => null))?.isFile() ?? false;
|
|
24223
|
+
if (existed) beforeExisted.add(target.abs);
|
|
24224
|
+
beforeContents.set(target.abs, await readTextForTracking(target.abs));
|
|
22938
24225
|
}
|
|
22939
24226
|
}
|
|
22940
24227
|
const tmpDir = await fs27.mkdtemp(path32.join(os9.tmpdir(), ".wstack_patch_"));
|
|
@@ -22944,32 +24231,70 @@ var patchTool = {
|
|
|
22944
24231
|
const patchFile = path32.join(tmpDir, "in.diff");
|
|
22945
24232
|
await fs27.writeFile(patchFile, input.patch, { mode: 384 });
|
|
22946
24233
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
22947
|
-
const result = await runPatch(args, dir, opts.signal
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
dry_run: dryRun,
|
|
22954
|
-
message: `patch failed: ${result.stderr || result.stdout}`
|
|
22955
|
-
};
|
|
22956
|
-
}
|
|
22957
|
-
const patched = extractPatchedFiles(result.stdout);
|
|
24234
|
+
const result = await runPatch(args, dir, opts.signal, {
|
|
24235
|
+
patchFile,
|
|
24236
|
+
strip,
|
|
24237
|
+
dryRun
|
|
24238
|
+
});
|
|
24239
|
+
const touched = [];
|
|
22958
24240
|
if (!dryRun) {
|
|
22959
24241
|
for (const target of resolvedTargets) {
|
|
22960
|
-
const
|
|
22961
|
-
const
|
|
24242
|
+
const abs = target.abs;
|
|
24243
|
+
const before = beforeContents.get(abs) ?? null;
|
|
24244
|
+
const stat19 = await fs27.stat(abs).catch(() => null);
|
|
24245
|
+
if (!stat19?.isFile()) {
|
|
24246
|
+
if (beforeExisted.has(abs)) {
|
|
24247
|
+
touched.push(abs);
|
|
24248
|
+
ctx.session?.recordFileChange?.({
|
|
24249
|
+
path: abs,
|
|
24250
|
+
action: "deleted",
|
|
24251
|
+
before,
|
|
24252
|
+
after: null
|
|
24253
|
+
});
|
|
24254
|
+
}
|
|
24255
|
+
continue;
|
|
24256
|
+
}
|
|
24257
|
+
const after = await readTextForTracking(abs);
|
|
22962
24258
|
if (after === null || after === before) continue;
|
|
22963
|
-
|
|
22964
|
-
|
|
24259
|
+
touched.push(abs);
|
|
24260
|
+
ctx.recordRead?.(abs, stat19.mtimeMs, "write", sha256hex(after));
|
|
22965
24261
|
ctx.session?.recordFileChange?.({
|
|
22966
|
-
path:
|
|
24262
|
+
path: abs,
|
|
22967
24263
|
action: before === null ? "created" : "modified",
|
|
22968
24264
|
before,
|
|
22969
24265
|
after
|
|
22970
24266
|
});
|
|
22971
24267
|
}
|
|
22972
24268
|
}
|
|
24269
|
+
if (result.exitCode !== 0) {
|
|
24270
|
+
if (!dryRun) {
|
|
24271
|
+
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(", ")}.` : "";
|
|
24272
|
+
return {
|
|
24273
|
+
applied: touched.length,
|
|
24274
|
+
rejected: 1,
|
|
24275
|
+
// Normalize to relative-to-realRoot for API consistency with the
|
|
24276
|
+
// success path (which returns GNU patch's dir-relative names).
|
|
24277
|
+
// `touched` entries are realpaths from resolveRealInsideRoot, and
|
|
24278
|
+
// realRoot is also a realpath, so path.relative is like-for-like.
|
|
24279
|
+
files: touched.map((p) => path32.relative(realRoot, p) || p),
|
|
24280
|
+
dry_run: dryRun,
|
|
24281
|
+
message: `patch failed: ${result.stderr || result.stdout}${partial}`
|
|
24282
|
+
};
|
|
24283
|
+
}
|
|
24284
|
+
const wouldPatch = extractPatchedFiles(result.stdout);
|
|
24285
|
+
return {
|
|
24286
|
+
applied: wouldPatch.length,
|
|
24287
|
+
rejected: 1,
|
|
24288
|
+
files: wouldPatch,
|
|
24289
|
+
dry_run: dryRun,
|
|
24290
|
+
message: `patch preview: would conflict \u2014 ${result.stderr || result.stdout}`
|
|
24291
|
+
};
|
|
24292
|
+
}
|
|
24293
|
+
const patched = result.engine === "git" ? [
|
|
24294
|
+
...new Set(
|
|
24295
|
+
resolvedTargets.map((target) => path32.relative(dir, target.abs) || target.abs)
|
|
24296
|
+
)
|
|
24297
|
+
] : extractPatchedFiles(result.stdout);
|
|
22973
24298
|
return {
|
|
22974
24299
|
applied: patched.length,
|
|
22975
24300
|
rejected: 0,
|
|
@@ -22997,27 +24322,86 @@ async function readTextForTracking(absPath) {
|
|
|
22997
24322
|
}
|
|
22998
24323
|
function extractDiffTargets(patch) {
|
|
22999
24324
|
const out = [];
|
|
23000
|
-
const
|
|
23001
|
-
|
|
23002
|
-
|
|
23003
|
-
|
|
23004
|
-
|
|
23005
|
-
|
|
23006
|
-
|
|
24325
|
+
const clean = (raw) => {
|
|
24326
|
+
if (!raw) return "";
|
|
24327
|
+
return (raw.length > 4096 ? raw.slice(0, 4096) : raw).trim();
|
|
24328
|
+
};
|
|
24329
|
+
let lastOld;
|
|
24330
|
+
let inHunk = false;
|
|
24331
|
+
let oldLinesLeft = 0;
|
|
24332
|
+
let newLinesLeft = 0;
|
|
24333
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
24334
|
+
const hunkMatch = /^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/.exec(line);
|
|
24335
|
+
if (hunkMatch) {
|
|
24336
|
+
inHunk = true;
|
|
24337
|
+
oldLinesLeft = hunkMatch[1] ? Number(hunkMatch[1]) : 1;
|
|
24338
|
+
newLinesLeft = hunkMatch[2] ? Number(hunkMatch[2]) : 1;
|
|
24339
|
+
lastOld = void 0;
|
|
24340
|
+
continue;
|
|
24341
|
+
}
|
|
24342
|
+
if (inHunk) {
|
|
24343
|
+
const ch = line[0];
|
|
24344
|
+
if (ch === "-") oldLinesLeft--;
|
|
24345
|
+
else if (ch === "+") newLinesLeft--;
|
|
24346
|
+
else if (ch === " " || ch === void 0) {
|
|
24347
|
+
oldLinesLeft--;
|
|
24348
|
+
newLinesLeft--;
|
|
24349
|
+
}
|
|
24350
|
+
if (oldLinesLeft <= 0 && newLinesLeft <= 0) inHunk = false;
|
|
24351
|
+
continue;
|
|
24352
|
+
}
|
|
24353
|
+
const oldMatch = /^---\s+([^\t\r\n]+)/.exec(line);
|
|
24354
|
+
if (oldMatch) {
|
|
24355
|
+
lastOld = clean(oldMatch[1]);
|
|
24356
|
+
continue;
|
|
24357
|
+
}
|
|
24358
|
+
const newMatch = /^\+\+\+\s+([^\t\r\n]+)/.exec(line);
|
|
24359
|
+
if (!newMatch) continue;
|
|
24360
|
+
const newTarget = clean(newMatch[1]);
|
|
24361
|
+
if (newTarget && newTarget !== "/dev/null") {
|
|
24362
|
+
out.push({ raw: newTarget, deleted: false });
|
|
24363
|
+
} else if (lastOld && lastOld !== "/dev/null") {
|
|
24364
|
+
out.push({ raw: lastOld, deleted: true });
|
|
24365
|
+
}
|
|
24366
|
+
lastOld = void 0;
|
|
23007
24367
|
}
|
|
23008
24368
|
return out;
|
|
23009
24369
|
}
|
|
23010
24370
|
function stripPathComponents(p, strip) {
|
|
23011
|
-
const
|
|
23012
|
-
|
|
23013
|
-
|
|
24371
|
+
const s = p.replace(/\\/g, "/");
|
|
24372
|
+
let idx = 0;
|
|
24373
|
+
for (let i = 0; i < strip; i++) {
|
|
24374
|
+
while (idx < s.length && s[idx] !== "/") idx++;
|
|
24375
|
+
let hadSlash = false;
|
|
24376
|
+
while (idx < s.length && s[idx] === "/") {
|
|
24377
|
+
idx++;
|
|
24378
|
+
hadSlash = true;
|
|
24379
|
+
}
|
|
24380
|
+
if (!hadSlash) return void 0;
|
|
24381
|
+
}
|
|
24382
|
+
return s.slice(idx) || void 0;
|
|
24383
|
+
}
|
|
24384
|
+
function runPatch(args, cwd, signal, fallback) {
|
|
24385
|
+
return runPatchProcess("patch", args, cwd, signal).then(async (result) => {
|
|
24386
|
+
if (!result.unavailable) return { ...result, engine: "patch" };
|
|
24387
|
+
const gitArgs = [
|
|
24388
|
+
"apply",
|
|
24389
|
+
"--unsafe-paths",
|
|
24390
|
+
`-p${fallback.strip}`,
|
|
24391
|
+
"--verbose",
|
|
24392
|
+
...fallback.dryRun ? ["--check"] : [],
|
|
24393
|
+
fallback.patchFile
|
|
24394
|
+
];
|
|
24395
|
+
const gitResult = await runPatchProcess("git", gitArgs, cwd, signal);
|
|
24396
|
+
return { ...gitResult, engine: "git" };
|
|
24397
|
+
});
|
|
23014
24398
|
}
|
|
23015
|
-
function
|
|
24399
|
+
function runPatchProcess(command, args, cwd, signal) {
|
|
23016
24400
|
return new Promise((resolve17) => {
|
|
23017
24401
|
let stdout = "";
|
|
23018
24402
|
let stderr = "";
|
|
23019
24403
|
const env = { ...buildChildEnv8(), LANG: "C", LC_ALL: "C" };
|
|
23020
|
-
const child =
|
|
24404
|
+
const child = spawn13(command, args, {
|
|
23021
24405
|
cwd,
|
|
23022
24406
|
signal,
|
|
23023
24407
|
env,
|
|
@@ -23030,13 +24414,24 @@ function runPatch(args, cwd, signal) {
|
|
|
23030
24414
|
child.stderr?.on("data", (c) => {
|
|
23031
24415
|
stderr += c.toString();
|
|
23032
24416
|
});
|
|
23033
|
-
child.on(
|
|
23034
|
-
|
|
24417
|
+
child.on(
|
|
24418
|
+
"close",
|
|
24419
|
+
(code) => resolve17({ exitCode: code ?? 1, stdout, stderr, unavailable: false })
|
|
24420
|
+
);
|
|
24421
|
+
child.on(
|
|
24422
|
+
"error",
|
|
24423
|
+
(e) => resolve17({
|
|
24424
|
+
exitCode: 1,
|
|
24425
|
+
stdout: "",
|
|
24426
|
+
stderr: e.message,
|
|
24427
|
+
unavailable: e.code === "ENOENT"
|
|
24428
|
+
})
|
|
24429
|
+
);
|
|
23035
24430
|
});
|
|
23036
24431
|
}
|
|
23037
24432
|
function extractPatchedFiles(output) {
|
|
23038
24433
|
const files = [];
|
|
23039
|
-
const re = /patching file (.+)/gi;
|
|
24434
|
+
const re = /(?:patching|checking) file (.+)/gi;
|
|
23040
24435
|
for (const m of output.matchAll(re)) {
|
|
23041
24436
|
if (m[1]) files.push(m[1]);
|
|
23042
24437
|
}
|
|
@@ -23345,7 +24740,7 @@ function mkResult(plan, ok, message, todos) {
|
|
|
23345
24740
|
init_util();
|
|
23346
24741
|
import * as fs28 from "node:fs/promises";
|
|
23347
24742
|
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
23348
|
-
import { toErrorMessage as
|
|
24743
|
+
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
23349
24744
|
var ADVANCED_MODE_META_KEY = "tools.read.advancedMode";
|
|
23350
24745
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
23351
24746
|
var readTool = {
|
|
@@ -23413,7 +24808,7 @@ var readTool = {
|
|
|
23413
24808
|
});
|
|
23414
24809
|
}
|
|
23415
24810
|
throw new FsError({
|
|
23416
|
-
message: `read: failed to stat "${input.path}": ${
|
|
24811
|
+
message: `read: failed to stat "${input.path}": ${toErrorMessage5(err)}`,
|
|
23417
24812
|
code: "FS_READ_FAILED",
|
|
23418
24813
|
path: absPath,
|
|
23419
24814
|
context: { errno: code },
|
|
@@ -23614,7 +25009,7 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
23614
25009
|
}
|
|
23615
25010
|
|
|
23616
25011
|
// src/replace.ts
|
|
23617
|
-
import { spawn as
|
|
25012
|
+
import { spawn as spawn14 } from "node:child_process";
|
|
23618
25013
|
import * as fs29 from "node:fs/promises";
|
|
23619
25014
|
import * as path33 from "node:path";
|
|
23620
25015
|
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
@@ -23799,7 +25194,7 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
23799
25194
|
function checkRg() {
|
|
23800
25195
|
return new Promise((resolve17) => {
|
|
23801
25196
|
try {
|
|
23802
|
-
const p =
|
|
25197
|
+
const p = spawn14("rg", ["--version"], {
|
|
23803
25198
|
env: buildChildEnv9(),
|
|
23804
25199
|
stdio: "ignore",
|
|
23805
25200
|
windowsHide: true
|
|
@@ -23813,7 +25208,7 @@ function checkRg() {
|
|
|
23813
25208
|
}
|
|
23814
25209
|
function spawnRgFind(pattern, base) {
|
|
23815
25210
|
const args = ["--files", "--glob", pattern, base];
|
|
23816
|
-
const child =
|
|
25211
|
+
const child = spawn14("rg", args, {
|
|
23817
25212
|
signal: AbortSignal.timeout(3e4),
|
|
23818
25213
|
env: buildChildEnv9(),
|
|
23819
25214
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -24072,7 +25467,7 @@ function substituteVars(content, name, vars) {
|
|
|
24072
25467
|
// src/search.ts
|
|
24073
25468
|
import { FetchError as FetchError3, ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
24074
25469
|
import { expectDefined as expectDefined9 } from "@wrongstack/core/utils";
|
|
24075
|
-
import { toErrorMessage as
|
|
25470
|
+
import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
24076
25471
|
var DEFAULT_NUM = 10;
|
|
24077
25472
|
var MAX_RESULTS = 50;
|
|
24078
25473
|
var TIMEOUT_MS3 = 15e3;
|
|
@@ -24276,7 +25671,7 @@ async function duckduckgoSearch(query, num, signal) {
|
|
|
24276
25671
|
return parseDuckDuckGo(html, num);
|
|
24277
25672
|
} catch (err) {
|
|
24278
25673
|
console.log(
|
|
24279
|
-
JSON.stringify({ level: "debug", event: "search_failed", query, error:
|
|
25674
|
+
JSON.stringify({ level: "debug", event: "search_failed", query, error: toErrorMessage6(err) })
|
|
24280
25675
|
);
|
|
24281
25676
|
return [{ title: "Search unavailable", url: "https://duckduckgo.com/unavailable", snippet: "Could not reach DuckDuckGo", score: 0 }];
|
|
24282
25677
|
}
|
|
@@ -24460,7 +25855,7 @@ function decodeHtmlEntities(text) {
|
|
|
24460
25855
|
|
|
24461
25856
|
// src/set-working-dir.ts
|
|
24462
25857
|
import * as fs31 from "node:fs/promises";
|
|
24463
|
-
import { toErrorMessage as
|
|
25858
|
+
import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
|
|
24464
25859
|
var setWorkingDirTool = {
|
|
24465
25860
|
name: "set_working_dir",
|
|
24466
25861
|
category: "Context",
|
|
@@ -24494,7 +25889,7 @@ var setWorkingDirTool = {
|
|
|
24494
25889
|
} catch (err) {
|
|
24495
25890
|
return {
|
|
24496
25891
|
current: ctx.workingDir,
|
|
24497
|
-
error:
|
|
25892
|
+
error: toErrorMessage7(err)
|
|
24498
25893
|
};
|
|
24499
25894
|
}
|
|
24500
25895
|
try {
|
|
@@ -25229,7 +26624,7 @@ var toolHelpTool = {
|
|
|
25229
26624
|
const format = input.format ?? "short";
|
|
25230
26625
|
const includeExamples = input.include_examples ?? false;
|
|
25231
26626
|
if (input.tool) {
|
|
25232
|
-
const tool = ctx.tools.find((t) => t.name === input.tool);
|
|
26627
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find((t) => t.name === input.tool);
|
|
25233
26628
|
if (!tool) {
|
|
25234
26629
|
return {
|
|
25235
26630
|
tool: input.tool,
|
|
@@ -25254,7 +26649,7 @@ var toolHelpTool = {
|
|
|
25254
26649
|
total: 1
|
|
25255
26650
|
};
|
|
25256
26651
|
}
|
|
25257
|
-
const allTools = ctx.tools.map((t) => ({
|
|
26652
|
+
const allTools = (ctx.catalogTools ?? ctx.tools).map((t) => ({
|
|
25258
26653
|
name: t.name,
|
|
25259
26654
|
description: t.description,
|
|
25260
26655
|
usageHint: t.usageHint ?? "",
|
|
@@ -25362,7 +26757,7 @@ var toolSearchTool = {
|
|
|
25362
26757
|
},
|
|
25363
26758
|
async execute(input, ctx) {
|
|
25364
26759
|
const limit = Math.min(input.limit ?? 20, 100);
|
|
25365
|
-
const tools = ctx.tools;
|
|
26760
|
+
const tools = ctx.catalogTools ?? ctx.tools;
|
|
25366
26761
|
const query = input.query?.toLowerCase() ?? "";
|
|
25367
26762
|
const filtered = tools.filter((t) => {
|
|
25368
26763
|
if (query && !t.name.toLowerCase().includes(query) && !t.description.toLowerCase().includes(query)) {
|
|
@@ -25442,7 +26837,7 @@ var toolUseTool = {
|
|
|
25442
26837
|
executionMs: 0
|
|
25443
26838
|
};
|
|
25444
26839
|
}
|
|
25445
|
-
const tool = ctx.tools.find((t) => t.name === input.tool);
|
|
26840
|
+
const tool = (ctx.catalogTools ?? ctx.tools).find((t) => t.name === input.tool);
|
|
25446
26841
|
if (!tool) {
|
|
25447
26842
|
return {
|
|
25448
26843
|
tool: input.tool,
|
|
@@ -25500,7 +26895,13 @@ init_util();
|
|
|
25500
26895
|
import * as fs32 from "node:fs/promises";
|
|
25501
26896
|
import * as path36 from "node:path";
|
|
25502
26897
|
import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
25503
|
-
var DEFAULT_IGNORE5 =
|
|
26898
|
+
var DEFAULT_IGNORE5 = /* @__PURE__ */ new Set([
|
|
26899
|
+
...DEFAULT_WALK_IGNORE_DIRS4,
|
|
26900
|
+
".wrongstack",
|
|
26901
|
+
".ssh",
|
|
26902
|
+
".gnupg",
|
|
26903
|
+
".aws"
|
|
26904
|
+
]);
|
|
25504
26905
|
var DEFAULT_MAX_ENTRIES2 = 5e3;
|
|
25505
26906
|
var MAX_TREE_OUTPUT_BYTES = 256 * 1024;
|
|
25506
26907
|
var treeTool = {
|
|
@@ -25661,8 +27062,12 @@ async function walkDir(dir, depth, opts) {
|
|
|
25661
27062
|
return true;
|
|
25662
27063
|
});
|
|
25663
27064
|
if (depth > 0) {
|
|
25664
|
-
|
|
25665
|
-
|
|
27065
|
+
let dirCount = 0;
|
|
27066
|
+
let fileCount = 0;
|
|
27067
|
+
for (const e of filtered) {
|
|
27068
|
+
if (e.isDirectory()) dirCount++;
|
|
27069
|
+
else if (e.isFile()) fileCount++;
|
|
27070
|
+
}
|
|
25666
27071
|
opts.totalDirs.value += dirCount;
|
|
25667
27072
|
opts.totalFiles.value += fileCount;
|
|
25668
27073
|
opts.onProgress?.();
|
|
@@ -25852,7 +27257,7 @@ var writeTool = {
|
|
|
25852
27257
|
required: ["path", "content"]
|
|
25853
27258
|
},
|
|
25854
27259
|
async execute(input, ctx, opts) {
|
|
25855
|
-
return
|
|
27260
|
+
return writeFile6(input, ctx, opts?.signal);
|
|
25856
27261
|
},
|
|
25857
27262
|
async *executeStream(input, ctx, opts) {
|
|
25858
27263
|
const prepared = await prepareWrite(input, ctx);
|
|
@@ -25865,7 +27270,7 @@ var writeTool = {
|
|
|
25865
27270
|
yield { type: "final", output: await finishWrite(input, ctx, prepared, opts?.signal) };
|
|
25866
27271
|
}
|
|
25867
27272
|
};
|
|
25868
|
-
async function
|
|
27273
|
+
async function writeFile6(input, ctx, signal) {
|
|
25869
27274
|
return finishWrite(input, ctx, await prepareWrite(input, ctx), signal);
|
|
25870
27275
|
}
|
|
25871
27276
|
async function prepareWrite(input, ctx) {
|
|
@@ -26343,6 +27748,62 @@ ${mode.description}`
|
|
|
26343
27748
|
};
|
|
26344
27749
|
}
|
|
26345
27750
|
|
|
27751
|
+
// src/next-steps-tool.ts
|
|
27752
|
+
import {
|
|
27753
|
+
MAX_PENDING_NEXT_STEPS,
|
|
27754
|
+
writePendingNextSteps
|
|
27755
|
+
} from "@wrongstack/core/agent";
|
|
27756
|
+
var nextStepsTool = {
|
|
27757
|
+
name: "nextsteps",
|
|
27758
|
+
category: "Session",
|
|
27759
|
+
description: "Record the after-task follow-on suggestions for this turn. Equivalent to ending your final message with a <nextsteps> block \u2014 use whichever you prefer. The list is fully replaced on every call (not appended).",
|
|
27760
|
+
usageHint: 'Call this at most once, on the turn you are finishing the work \u2014 not mid-task.\n- Each `text` is the **exact prompt message** that gets submitted back to you when the user picks it. Write agent-directed work ("Run the parser tests and fix any failures"), never a chore for the user to do by hand.\n- Order by priority; 1-4 items. Do not pad with filler or invent work to fill the list.\n- `auto: true` is honored on the first item only. Set it when that prompt is safe to run unattended \u2014 YOLO+auto executes it verbatim, so it must be complete and self-contained.\n- Omit the call entirely while any todo is still `pending` or `in_progress`; suggestions recorded in that state are discarded.\n- If you also write a <nextsteps> block in your message, that block wins.',
|
|
27761
|
+
permission: "auto",
|
|
27762
|
+
mutating: false,
|
|
27763
|
+
// mutates only turn-scoped conversation state — no confirmation needed
|
|
27764
|
+
timeoutMs: 5e3,
|
|
27765
|
+
capabilities: ["session.nextsteps"],
|
|
27766
|
+
icon: "todo",
|
|
27767
|
+
inputSchema: {
|
|
27768
|
+
type: "object",
|
|
27769
|
+
properties: {
|
|
27770
|
+
steps: {
|
|
27771
|
+
type: "array",
|
|
27772
|
+
minItems: 1,
|
|
27773
|
+
maxItems: MAX_PENDING_NEXT_STEPS,
|
|
27774
|
+
items: {
|
|
27775
|
+
type: "object",
|
|
27776
|
+
properties: {
|
|
27777
|
+
text: {
|
|
27778
|
+
type: "string",
|
|
27779
|
+
description: "The exact natural-language prompt message to submit back to the agent when the user selects this item."
|
|
27780
|
+
},
|
|
27781
|
+
auto: {
|
|
27782
|
+
type: "boolean",
|
|
27783
|
+
description: "Safe to run unattended. Honored on the first item only; ignored elsewhere."
|
|
27784
|
+
}
|
|
27785
|
+
},
|
|
27786
|
+
required: ["text"]
|
|
27787
|
+
},
|
|
27788
|
+
description: `The complete suggestion list (1-${MAX_PENDING_NEXT_STEPS} items), highest priority first. Replaces any previous list for this turn.`
|
|
27789
|
+
}
|
|
27790
|
+
},
|
|
27791
|
+
required: ["steps"]
|
|
27792
|
+
},
|
|
27793
|
+
async execute(input, ctx) {
|
|
27794
|
+
if (!Array.isArray(input?.steps)) {
|
|
27795
|
+
throw new Error("nextsteps: steps must be an array");
|
|
27796
|
+
}
|
|
27797
|
+
const steps = input.steps.filter((s) => typeof s?.text === "string").map((s) => s.auto === true ? { text: s.text, auto: true } : { text: s.text });
|
|
27798
|
+
if (steps.length === 0) {
|
|
27799
|
+
throw new Error("nextsteps: steps must contain at least one item with a non-empty text");
|
|
27800
|
+
}
|
|
27801
|
+
writePendingNextSteps(ctx, steps);
|
|
27802
|
+
const accepted = Math.min(steps.length, MAX_PENDING_NEXT_STEPS);
|
|
27803
|
+
return { accepted, auto: steps[0]?.auto === true };
|
|
27804
|
+
}
|
|
27805
|
+
};
|
|
27806
|
+
|
|
26346
27807
|
// src/pack.ts
|
|
26347
27808
|
var builtinToolsPack = {
|
|
26348
27809
|
name: "builtin-tools",
|
|
@@ -26943,6 +28404,11 @@ function createGlobalPsSlashCommand() {
|
|
|
26943
28404
|
// src/skill.ts
|
|
26944
28405
|
import * as fs34 from "node:fs/promises";
|
|
26945
28406
|
import * as path38 from "node:path";
|
|
28407
|
+
import {
|
|
28408
|
+
missingRequiredRuntimeTools,
|
|
28409
|
+
missingRuntimeCapabilities,
|
|
28410
|
+
runtimeToolReferencesFromText
|
|
28411
|
+
} from "@wrongstack/core/agent-catalog";
|
|
26946
28412
|
import { SKILL_LIMITS, stripFrontmatter } from "@wrongstack/core/skills";
|
|
26947
28413
|
import { ToolValidationError as ToolValidationError10 } from "@wrongstack/core/types";
|
|
26948
28414
|
var MAX_BODY_CHARS = SKILL_LIMITS.MAX_SKILL_BODY_CHARS;
|
|
@@ -26986,12 +28452,37 @@ function makeSkillTool(skillLoader) {
|
|
|
26986
28452
|
field: "name"
|
|
26987
28453
|
});
|
|
26988
28454
|
}
|
|
28455
|
+
const availableToolNames = (ctx?.catalogTools ?? ctx?.tools ?? []).map((tool) => tool.name);
|
|
28456
|
+
const missingCapabilities = missingRuntimeCapabilities(
|
|
28457
|
+
manifest.requiredCapabilities,
|
|
28458
|
+
availableToolNames
|
|
28459
|
+
);
|
|
28460
|
+
const missingTools = missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames);
|
|
28461
|
+
if (missingCapabilities.length > 0 || missingTools.length > 0) {
|
|
28462
|
+
throw new ToolValidationError10({
|
|
28463
|
+
message: `skill "${name}" is unavailable in this runtime; ` + [
|
|
28464
|
+
missingCapabilities.length > 0 ? `missing capabilities: ${missingCapabilities.join(", ")}` : "",
|
|
28465
|
+
missingTools.length > 0 ? `missing tools: ${missingTools.join(", ")}` : ""
|
|
28466
|
+
].filter(Boolean).join("; "),
|
|
28467
|
+
field: "name"
|
|
28468
|
+
});
|
|
28469
|
+
}
|
|
26989
28470
|
const dir = path38.dirname(manifest.path);
|
|
26990
28471
|
let loadedResource;
|
|
26991
28472
|
if (input.resource?.trim()) {
|
|
26992
28473
|
loadedResource = await loadResource(dir, input.resource.trim());
|
|
26993
28474
|
}
|
|
26994
28475
|
const raw = await skillLoader.readBody(name);
|
|
28476
|
+
const missingBodyTools = missingRequiredRuntimeTools(
|
|
28477
|
+
runtimeToolReferencesFromText(raw),
|
|
28478
|
+
availableToolNames
|
|
28479
|
+
);
|
|
28480
|
+
if (missingBodyTools.length > 0) {
|
|
28481
|
+
throw new ToolValidationError10({
|
|
28482
|
+
message: `skill "${name}" references unregistered tools: ${missingBodyTools.join(", ")}`,
|
|
28483
|
+
field: "name"
|
|
28484
|
+
});
|
|
28485
|
+
}
|
|
26995
28486
|
const body = stripFrontmatter(raw).trim().slice(0, MAX_BODY_CHARS);
|
|
26996
28487
|
const resources = loadedResource ? [] : await listResources(dir);
|
|
26997
28488
|
try {
|
|
@@ -27050,9 +28541,26 @@ async function loadResource(skillDir, rel) {
|
|
|
27050
28541
|
field: "resource"
|
|
27051
28542
|
});
|
|
27052
28543
|
}
|
|
28544
|
+
let realPath;
|
|
28545
|
+
let realRoot;
|
|
28546
|
+
try {
|
|
28547
|
+
realRoot = await fs34.realpath(root);
|
|
28548
|
+
realPath = await fs34.realpath(absPath);
|
|
28549
|
+
} catch {
|
|
28550
|
+
throw new ToolValidationError10({
|
|
28551
|
+
message: `skill: resource "${rel}" not readable`,
|
|
28552
|
+
field: "resource"
|
|
28553
|
+
});
|
|
28554
|
+
}
|
|
28555
|
+
if (realPath !== realRoot && !realPath.startsWith(realRoot + path38.sep)) {
|
|
28556
|
+
throw new ToolValidationError10({
|
|
28557
|
+
message: `skill: resource "${rel}" resolves outside the skill directory`,
|
|
28558
|
+
field: "resource"
|
|
28559
|
+
});
|
|
28560
|
+
}
|
|
27053
28561
|
let buf;
|
|
27054
28562
|
try {
|
|
27055
|
-
buf = await fs34.readFile(
|
|
28563
|
+
buf = await fs34.readFile(realPath);
|
|
27056
28564
|
} catch {
|
|
27057
28565
|
throw new ToolValidationError10({
|
|
27058
28566
|
message: `skill: resource "${rel}" not readable`,
|
|
@@ -27063,7 +28571,9 @@ async function loadResource(skillDir, rel) {
|
|
|
27063
28571
|
const truncated = raw.length > MAX_RESOURCE_CHARS;
|
|
27064
28572
|
return {
|
|
27065
28573
|
rel: norm,
|
|
27066
|
-
|
|
28574
|
+
// The canonical path — the one actually opened, and the one a follow-up
|
|
28575
|
+
// `bash` invocation should use.
|
|
28576
|
+
absPath: realPath,
|
|
27067
28577
|
content: truncated ? raw.slice(0, MAX_RESOURCE_CHARS) : raw,
|
|
27068
28578
|
bytes: buf.length,
|
|
27069
28579
|
truncated
|
|
@@ -27171,6 +28681,9 @@ var TOOL_ICON_MAP = {
|
|
|
27171
28681
|
// Task management
|
|
27172
28682
|
todo: "todo",
|
|
27173
28683
|
todos: "todo",
|
|
28684
|
+
// After-task suggestions — same family as the todo board, so it reuses the
|
|
28685
|
+
// icon rather than widening ToolIconId (every UI maps that union by hand).
|
|
28686
|
+
nextsteps: "todo",
|
|
27174
28687
|
// Planning
|
|
27175
28688
|
plan: "plan",
|
|
27176
28689
|
planning: "plan",
|
|
@@ -27297,11 +28810,7 @@ function selectBuiltinToolsForTier(tier, allTools) {
|
|
|
27297
28810
|
}
|
|
27298
28811
|
case "aggressive": {
|
|
27299
28812
|
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
27300
|
-
|
|
27301
|
-
const tier3Names = toolNameSet(TIER3_TOOLS);
|
|
27302
|
-
return allTools.filter(
|
|
27303
|
-
(tool) => tier1Names.has(tool.name) || tier2Names.has(tool.name) && tool.name !== "task" || tier3Names.has(tool.name) && tool.name !== "set_working_dir"
|
|
27304
|
-
);
|
|
28813
|
+
return allTools.filter((tool) => tier1Names.has(tool.name));
|
|
27305
28814
|
}
|
|
27306
28815
|
}
|
|
27307
28816
|
}
|
|
@@ -27428,6 +28937,7 @@ export {
|
|
|
27428
28937
|
mirrorSessionPlanToKanban,
|
|
27429
28938
|
mirrorSessionTasksToKanban,
|
|
27430
28939
|
mirrorSessionTodosToKanban,
|
|
28940
|
+
nextStepsTool,
|
|
27431
28941
|
normalizeShell,
|
|
27432
28942
|
onIndexStateChange,
|
|
27433
28943
|
outdatedTool,
|