@wrongstack/tools 0.293.0 → 0.295.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/_env.d.ts +1 -1
- package/dist/_env.d.ts.map +1 -1
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/_util.d.ts +1 -1
- package/dist/_util.d.ts.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +81 -16
- package/dist/audit.js.map +2 -2
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +66 -52
- package/dist/bash.js.map +3 -3
- package/dist/batch-tool-use.d.ts +1 -1
- package/dist/batch-tool-use.d.ts.map +1 -1
- package/dist/batch-tool-use.js +1 -1
- package/dist/batch-tool-use.js.map +1 -1
- package/dist/browser/index.js +10 -1
- package/dist/browser/index.js.map +2 -2
- package/dist/browser/manager.d.ts +2 -0
- package/dist/browser/manager.d.ts.map +1 -1
- package/dist/browser/tools.d.ts +1 -1
- package/dist/browser/tools.d.ts.map +1 -1
- package/dist/builtin.d.ts +1 -1
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1394 -553
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts +2 -2
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +3 -1
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/generic-parser.d.ts +34 -0
- package/dist/codebase-index/generic-parser.d.ts.map +1 -0
- package/dist/codebase-index/go-parser.d.ts +1 -1
- package/dist/codebase-index/go-parser.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +4 -2
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +1036 -341
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts +6 -1
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/json-parser.d.ts +1 -1
- package/dist/codebase-index/languages.d.ts +25 -0
- package/dist/codebase-index/languages.d.ts.map +1 -0
- package/dist/codebase-index/py-parser.d.ts +9 -1
- package/dist/codebase-index/py-parser.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/rs-parser.d.ts +1 -1
- package/dist/codebase-index/rs-parser.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +9 -2
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/spawn-gate.d.ts +15 -0
- package/dist/codebase-index/spawn-gate.d.ts.map +1 -0
- package/dist/codebase-index/ts-parser.d.ts +2 -2
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +961 -317
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer.d.ts +83 -4
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/codebase-index/yaml-parser.d.ts +1 -1
- package/dist/design.d.ts +5 -2
- package/dist/design.d.ts.map +1 -1
- package/dist/design.js +36 -3
- package/dist/design.js.map +2 -2
- package/dist/diff.d.ts +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +2 -2
- package/dist/diff.js.map +1 -1
- package/dist/document.d.ts +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +1 -1
- package/dist/document.js.map +1 -1
- package/dist/e2e.d.ts +1 -1
- package/dist/e2e.d.ts.map +1 -1
- package/dist/e2e.js +1 -1
- package/dist/e2e.js.map +1 -1
- package/dist/edit.d.ts +1 -1
- package/dist/edit.d.ts.map +1 -1
- package/dist/edit.js +3 -3
- package/dist/edit.js.map +2 -2
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -21
- package/dist/exec.js.map +3 -3
- package/dist/fetch.d.ts +1 -1
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +4 -5
- package/dist/fetch.js.map +2 -2
- package/dist/format.d.ts +1 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +81 -16
- package/dist/format.js.map +2 -2
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +3 -2
- package/dist/git.js.map +2 -2
- package/dist/glob.d.ts +1 -1
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +4 -3
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1657 -705
- package/dist/index.js.map +4 -4
- package/dist/install.d.ts +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +85 -17
- package/dist/install.js.map +2 -2
- package/dist/json.d.ts +1 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -2
- package/dist/json.js.map +2 -2
- package/dist/kanban.d.ts +4 -3
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +94 -27
- package/dist/kanban.js.map +2 -2
- package/dist/languages/execute-tool.d.ts +1 -1
- package/dist/languages/execute-tool.d.ts.map +1 -1
- package/dist/languages/execute.d.ts +1 -1
- package/dist/languages/execute.d.ts.map +1 -1
- package/dist/languages/index.d.ts +1 -1
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +99 -17
- package/dist/languages/index.js.map +2 -2
- package/dist/languages/package-tool.d.ts +3 -4
- package/dist/languages/package-tool.d.ts.map +1 -1
- package/dist/languages/plan.d.ts.map +1 -1
- package/dist/languages/profiles/additional.d.ts.map +1 -1
- package/dist/languages/profiles/primary.d.ts.map +1 -1
- package/dist/languages/tool.d.ts +1 -1
- package/dist/languages/tool.d.ts.map +1 -1
- package/dist/lint.d.ts +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +81 -16
- package/dist/lint.js.map +2 -2
- package/dist/logs.d.ts +1 -1
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +2 -2
- package/dist/logs.js.map +1 -1
- package/dist/memory.d.ts +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +1 -1
- package/dist/memory.js.map +1 -1
- package/dist/mode.d.ts +1 -1
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/outdated.d.ts +1 -1
- package/dist/outdated.d.ts.map +1 -1
- package/dist/outdated.js +101 -19
- package/dist/outdated.js.map +2 -2
- package/dist/pack.d.ts +1 -1
- package/dist/pack.js +1394 -553
- package/dist/pack.js.map +4 -4
- package/dist/patch.d.ts +1 -1
- package/dist/patch.d.ts.map +1 -1
- package/dist/patch.js +2 -2
- package/dist/patch.js.map +1 -1
- package/dist/plan.d.ts +1 -1
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +7 -7
- package/dist/plan.js.map +2 -2
- package/dist/process-guardian.d.ts +7 -0
- package/dist/process-guardian.d.ts.map +1 -1
- package/dist/process-registry-persistent.d.ts +5 -0
- package/dist/process-registry-persistent.d.ts.map +1 -1
- package/dist/process-registry.d.ts +9 -1
- package/dist/process-registry.d.ts.map +1 -1
- package/dist/process-registry.js +18 -3
- package/dist/process-registry.js.map +2 -2
- package/dist/ps-slash.js +54 -15
- package/dist/ps-slash.js.map +2 -2
- package/dist/read.d.ts +1 -1
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +3 -2
- package/dist/read.js.map +2 -2
- package/dist/replace.d.ts +1 -1
- package/dist/replace.d.ts.map +1 -1
- package/dist/replace.js +4 -4
- package/dist/replace.js.map +2 -2
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +2 -2
- package/dist/scaffold.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +4 -4
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +4 -1
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +23 -15
- package/dist/session-kanban.js.map +2 -2
- package/dist/set-working-dir.d.ts +1 -1
- package/dist/set-working-dir.d.ts.map +1 -1
- package/dist/skill.d.ts +1 -1
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +2 -5
- package/dist/skill.js.map +2 -2
- package/dist/spawn-background.d.ts.map +1 -1
- package/dist/task.d.ts +3 -2
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +6 -8
- package/dist/task.js.map +2 -2
- package/dist/test.d.ts +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +81 -16
- package/dist/test.js.map +2 -2
- package/dist/todo.d.ts +2 -1
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +4 -4
- package/dist/todo.js.map +2 -2
- package/dist/tool-help.d.ts +1 -1
- package/dist/tool-help.d.ts.map +1 -1
- package/dist/tool-help.js.map +1 -1
- package/dist/tool-search.d.ts +1 -1
- package/dist/tool-search.d.ts.map +1 -1
- package/dist/tool-search.js.map +1 -1
- package/dist/tool-tier.d.ts +16 -0
- package/dist/tool-tier.d.ts.map +1 -0
- package/dist/tool-tier.js +22233 -0
- package/dist/tool-tier.js.map +7 -0
- package/dist/tool-use.d.ts +1 -1
- package/dist/tool-use.d.ts.map +1 -1
- package/dist/tool-use.js.map +1 -1
- package/dist/tree.d.ts +1 -1
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.d.ts +1 -1
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +81 -16
- package/dist/typecheck.js.map +2 -2
- package/dist/write.d.ts +1 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +3 -2
- package/dist/write.js.map +2 -2
- package/package.json +7 -3
package/dist/builtin.js
CHANGED
|
@@ -17,7 +17,7 @@ var __export = (target, all) => {
|
|
|
17
17
|
import { createWriteStream, mkdirSync } from "node:fs";
|
|
18
18
|
import * as fsp from "node:fs/promises";
|
|
19
19
|
import * as path from "node:path";
|
|
20
|
-
import { wstackGlobalRoot } from "@wrongstack/core";
|
|
20
|
+
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
21
21
|
function toolOutputDir() {
|
|
22
22
|
return path.join(wstackGlobalRoot(), "tool-output");
|
|
23
23
|
}
|
|
@@ -325,7 +325,7 @@ var init_circuit_breaker = __esm({
|
|
|
325
325
|
});
|
|
326
326
|
|
|
327
327
|
// src/_redact-command.ts
|
|
328
|
-
import { expectDefined } from "@wrongstack/core";
|
|
328
|
+
import { expectDefined } from "@wrongstack/core/utils";
|
|
329
329
|
function redactCommand(cmd) {
|
|
330
330
|
let result = cmd;
|
|
331
331
|
for (const pattern of SENSITIVE_FLAG_PATTERNS) {
|
|
@@ -439,7 +439,12 @@ var init_process_registry = __esm({
|
|
|
439
439
|
this.breaker.setEnabled(false);
|
|
440
440
|
}
|
|
441
441
|
register(info) {
|
|
442
|
-
this.processes.set(info.pid, {
|
|
442
|
+
this.processes.set(info.pid, {
|
|
443
|
+
...info,
|
|
444
|
+
killed: false,
|
|
445
|
+
protected: info.protected ?? false,
|
|
446
|
+
background: info.background ?? false
|
|
447
|
+
});
|
|
443
448
|
}
|
|
444
449
|
_isSafeSignalPid(pid) {
|
|
445
450
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
@@ -492,12 +497,21 @@ var init_process_registry = __esm({
|
|
|
492
497
|
}
|
|
493
498
|
return n;
|
|
494
499
|
}
|
|
500
|
+
/** Count of active jobs explicitly launched in background mode. */
|
|
501
|
+
get activeBackgroundCount() {
|
|
502
|
+
let n = 0;
|
|
503
|
+
for (const p of this.processes.values()) {
|
|
504
|
+
if (p.background && !p.killed) n++;
|
|
505
|
+
}
|
|
506
|
+
return n;
|
|
507
|
+
}
|
|
495
508
|
/**
|
|
496
509
|
* Combined stats for observability — used by /ps and the TUI status bar.
|
|
497
510
|
*/
|
|
498
511
|
stats() {
|
|
499
512
|
return {
|
|
500
513
|
activeCount: this.activeCount,
|
|
514
|
+
backgroundCount: this.activeBackgroundCount,
|
|
501
515
|
totalCount: this.processes.size,
|
|
502
516
|
breaker: this.breaker.snapshot()
|
|
503
517
|
};
|
|
@@ -598,7 +612,7 @@ var init_process_registry = __esm({
|
|
|
598
612
|
this.autoKillTimer = setTimeout(() => {
|
|
599
613
|
this.autoKillTimer = null;
|
|
600
614
|
this.autoKillArmedAt = null;
|
|
601
|
-
this.killAll({ force: false });
|
|
615
|
+
this.killAll({ force: false, preserveBackground: true });
|
|
602
616
|
this.breaker.forceReset();
|
|
603
617
|
this._emitBreakerCountdown();
|
|
604
618
|
}, this.autoKillResetMs);
|
|
@@ -638,6 +652,7 @@ var init_process_registry = __esm({
|
|
|
638
652
|
if (!p) return false;
|
|
639
653
|
if (p.killed) return true;
|
|
640
654
|
if (p.protected) return false;
|
|
655
|
+
if (opts.preserveBackground && p.background) return false;
|
|
641
656
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
642
657
|
const isWin5 = os.platform() === "win32";
|
|
643
658
|
if (isWin5) {
|
|
@@ -807,11 +822,11 @@ var init_win32_resolve = __esm({
|
|
|
807
822
|
// src/_spawn-stream.ts
|
|
808
823
|
import { spawn as spawn2 } from "node:child_process";
|
|
809
824
|
import {
|
|
810
|
-
buildChildEnv,
|
|
811
825
|
emitProcessCompleted,
|
|
812
826
|
emitProcessOutput,
|
|
813
827
|
emitProcessStarted
|
|
814
|
-
} from "@wrongstack/core";
|
|
828
|
+
} from "@wrongstack/core/observability";
|
|
829
|
+
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
815
830
|
async function* spawnStream(opts) {
|
|
816
831
|
const max = opts.maxBytes ?? 2e5;
|
|
817
832
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
@@ -1023,7 +1038,7 @@ var init_spawn_stream = __esm({
|
|
|
1023
1038
|
import { createHash } from "node:crypto";
|
|
1024
1039
|
import * as fsp2 from "node:fs/promises";
|
|
1025
1040
|
import * as path3 from "node:path";
|
|
1026
|
-
import * as Core from "@wrongstack/core";
|
|
1041
|
+
import * as Core from "@wrongstack/core/utils";
|
|
1027
1042
|
function sha256hex(content) {
|
|
1028
1043
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1029
1044
|
}
|
|
@@ -1067,16 +1082,16 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
1067
1082
|
const realRoots = await Promise.all(
|
|
1068
1083
|
allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
|
|
1069
1084
|
);
|
|
1070
|
-
let
|
|
1085
|
+
let probe2 = absPath;
|
|
1071
1086
|
for (; ; ) {
|
|
1072
1087
|
let real;
|
|
1073
1088
|
try {
|
|
1074
|
-
real = await fsp2.realpath(
|
|
1089
|
+
real = await fsp2.realpath(probe2);
|
|
1075
1090
|
} catch (err) {
|
|
1076
1091
|
if (err.code === "ENOENT") {
|
|
1077
|
-
const parent = path3.dirname(
|
|
1078
|
-
if (parent ===
|
|
1079
|
-
|
|
1092
|
+
const parent = path3.dirname(probe2);
|
|
1093
|
+
if (parent === probe2) return;
|
|
1094
|
+
probe2 = parent;
|
|
1080
1095
|
continue;
|
|
1081
1096
|
}
|
|
1082
1097
|
throw err;
|
|
@@ -1264,7 +1279,20 @@ function pythonProfile() {
|
|
|
1264
1279
|
]),
|
|
1265
1280
|
ignoredDirectories: IGNORES,
|
|
1266
1281
|
packageManagers: Object.freeze(["pip", "poetry", "pipenv", "uv"]),
|
|
1267
|
-
executables: Object.freeze([
|
|
1282
|
+
executables: Object.freeze([
|
|
1283
|
+
"python",
|
|
1284
|
+
"python3",
|
|
1285
|
+
"pip",
|
|
1286
|
+
"pip3",
|
|
1287
|
+
"poetry",
|
|
1288
|
+
"pipenv",
|
|
1289
|
+
"uv",
|
|
1290
|
+
"ruff",
|
|
1291
|
+
"black",
|
|
1292
|
+
"pytest",
|
|
1293
|
+
"mypy",
|
|
1294
|
+
"pyright"
|
|
1295
|
+
]),
|
|
1268
1296
|
operations: Object.freeze({
|
|
1269
1297
|
syntax: async (ctx) => {
|
|
1270
1298
|
const py = ctx.options.target ? "python3" : "python3";
|
|
@@ -1324,6 +1352,14 @@ function pythonProfile() {
|
|
|
1324
1352
|
network: true
|
|
1325
1353
|
});
|
|
1326
1354
|
},
|
|
1355
|
+
"package-remove": async (ctx) => {
|
|
1356
|
+
const names = packageNames(ctx);
|
|
1357
|
+
return names.length === 0 ? unavailable(ctx, "package-remove", "At least one package name is required.") : processPlan(ctx, "package-remove", "pip", ["uninstall", "--yes", ...names], {
|
|
1358
|
+
parser: "package-text",
|
|
1359
|
+
reason: "Remove validated Python packages.",
|
|
1360
|
+
mutating: true
|
|
1361
|
+
});
|
|
1362
|
+
},
|
|
1327
1363
|
"package-audit": async (ctx) => processPlan(ctx, "package-audit", "pip", ["audit"], {
|
|
1328
1364
|
parser: "pip-audit",
|
|
1329
1365
|
reason: "Audit Python dependencies for vulnerabilities.",
|
|
@@ -1935,6 +1971,19 @@ function typescriptProfile() {
|
|
|
1935
1971
|
network: true
|
|
1936
1972
|
});
|
|
1937
1973
|
},
|
|
1974
|
+
"package-remove": async (ctx) => {
|
|
1975
|
+
const names = packageNames(ctx);
|
|
1976
|
+
if (names.length === 0)
|
|
1977
|
+
return unavailable(ctx, "package-remove", "At least one package name is required.");
|
|
1978
|
+
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1979
|
+
const args = manager === "npm" ? ["uninstall", "--ignore-scripts", ...names] : ["remove", "--ignore-scripts", ...names];
|
|
1980
|
+
return processPlan(ctx, "package-remove", manager, args, {
|
|
1981
|
+
parser: "package-text",
|
|
1982
|
+
reason: `Remove validated packages with ${manager} and lifecycle scripts disabled.`,
|
|
1983
|
+
mutating: true,
|
|
1984
|
+
network: true
|
|
1985
|
+
});
|
|
1986
|
+
},
|
|
1938
1987
|
"package-audit": async (ctx) => {
|
|
1939
1988
|
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1940
1989
|
return processPlan(ctx, "package-audit", manager, ["audit", "--json"], {
|
|
@@ -2387,13 +2436,28 @@ var init_primary = __esm({
|
|
|
2387
2436
|
}),
|
|
2388
2437
|
"package-add": async (ctx) => {
|
|
2389
2438
|
const names = packageNames(ctx);
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2439
|
+
const [spec] = names;
|
|
2440
|
+
const versionAt = spec?.lastIndexOf("@") ?? -1;
|
|
2441
|
+
const packageName = versionAt > 0 ? spec?.slice(0, versionAt) : spec;
|
|
2442
|
+
const packageVersion = versionAt > 0 ? spec?.slice(versionAt + 1) : void 0;
|
|
2443
|
+
return names.length === 0 ? unavailable(ctx, "package-add", "At least one NuGet package is required.") : names.length > 1 ? unavailable(ctx, "package-add", "NuGet package changes run one package at a time.") : processPlan(
|
|
2444
|
+
ctx,
|
|
2445
|
+
"package-add",
|
|
2446
|
+
"dotnet",
|
|
2447
|
+
[
|
|
2448
|
+
"add",
|
|
2449
|
+
"package",
|
|
2450
|
+
packageName ?? "",
|
|
2451
|
+
...packageVersion ? ["--version", packageVersion] : []
|
|
2452
|
+
],
|
|
2453
|
+
{
|
|
2454
|
+
parser: "dotnet-package",
|
|
2455
|
+
reason: "Add validated NuGet packages.",
|
|
2456
|
+
mutating: true,
|
|
2457
|
+
network: true,
|
|
2458
|
+
executesProjectCode: true
|
|
2459
|
+
}
|
|
2460
|
+
);
|
|
2397
2461
|
},
|
|
2398
2462
|
"package-remove": async (ctx) => {
|
|
2399
2463
|
const names = packageNames(ctx);
|
|
@@ -2605,8 +2669,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
|
|
|
2605
2669
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2606
2670
|
}
|
|
2607
2671
|
}
|
|
2608
|
-
function collectFileEvidence(directory, fullPath,
|
|
2609
|
-
const lower =
|
|
2672
|
+
function collectFileEvidence(directory, fullPath, basename11, profiles, state) {
|
|
2673
|
+
const lower = basename11.toLowerCase();
|
|
2610
2674
|
const extension = path4.extname(lower);
|
|
2611
2675
|
for (const profile of profiles) {
|
|
2612
2676
|
const detector = profile.detectors.find(
|
|
@@ -2617,7 +2681,7 @@ function collectFileEvidence(directory, fullPath, basename10, profiles, state) {
|
|
|
2617
2681
|
candidate.evidence.push({
|
|
2618
2682
|
kind: detector.kind,
|
|
2619
2683
|
path: fullPath,
|
|
2620
|
-
value:
|
|
2684
|
+
value: basename11,
|
|
2621
2685
|
weight: detector.weight
|
|
2622
2686
|
});
|
|
2623
2687
|
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
@@ -3483,7 +3547,7 @@ function validateOperationOptions(operation, packages) {
|
|
|
3483
3547
|
if (value.startsWith(".") || value.startsWith("/") || /^[A-Za-z]:/.test(value) || value.includes("://")) {
|
|
3484
3548
|
throw new Error(`Package paths and URLs are not supported: "${value}"`);
|
|
3485
3549
|
}
|
|
3486
|
-
if (!PACKAGE_NAME_RE.test(value) && !COMPOSER_PACKAGE_RE.test(value) && !GO_MODULE_RE.test(value)) {
|
|
3550
|
+
if (!PACKAGE_NAME_RE.test(value) && !COMPOSER_PACKAGE_RE.test(value) && !GO_MODULE_RE.test(value) && !PYTHON_PACKAGE_RE.test(value)) {
|
|
3487
3551
|
throw new Error(`Invalid package identifier "${value}"`);
|
|
3488
3552
|
}
|
|
3489
3553
|
}
|
|
@@ -3505,7 +3569,7 @@ function isInside2(candidate, root) {
|
|
|
3505
3569
|
const relative12 = path6.relative(path6.resolve(root), path6.resolve(candidate));
|
|
3506
3570
|
return relative12 === "" || !relative12.startsWith("..") && !path6.isAbsolute(relative12);
|
|
3507
3571
|
}
|
|
3508
|
-
var MAX_ARGUMENTS, MAX_ARGUMENT_LENGTH, PACKAGE_NAME_RE, COMPOSER_PACKAGE_RE, GO_MODULE_RE;
|
|
3572
|
+
var MAX_ARGUMENTS, MAX_ARGUMENT_LENGTH, PACKAGE_NAME_RE, COMPOSER_PACKAGE_RE, GO_MODULE_RE, PYTHON_PACKAGE_RE;
|
|
3509
3573
|
var init_plan = __esm({
|
|
3510
3574
|
"src/languages/plan.ts"() {
|
|
3511
3575
|
"use strict";
|
|
@@ -3516,6 +3580,7 @@ var init_plan = __esm({
|
|
|
3516
3580
|
PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3517
3581
|
COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3518
3582
|
GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
|
|
3583
|
+
PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
|
|
3519
3584
|
}
|
|
3520
3585
|
});
|
|
3521
3586
|
|
|
@@ -3934,15 +3999,15 @@ async function changedPaths(before, after, beforeSizes, afterSizes) {
|
|
|
3934
3999
|
const beforeSet = new Set(before);
|
|
3935
4000
|
const afterSet = new Set(after);
|
|
3936
4001
|
const changed = /* @__PURE__ */ new Set();
|
|
3937
|
-
for (const
|
|
3938
|
-
if (!beforeSet.has(
|
|
4002
|
+
for (const path34 of after) {
|
|
4003
|
+
if (!beforeSet.has(path34)) changed.add(path34);
|
|
3939
4004
|
}
|
|
3940
|
-
for (const
|
|
3941
|
-
if (!afterSet.has(
|
|
4005
|
+
for (const path34 of before) {
|
|
4006
|
+
if (!afterSet.has(path34)) changed.add(path34);
|
|
3942
4007
|
}
|
|
3943
4008
|
if (beforeSizes && afterSizes) {
|
|
3944
|
-
for (const
|
|
3945
|
-
if (beforeSizes.get(
|
|
4009
|
+
for (const path34 of after) {
|
|
4010
|
+
if (beforeSizes.get(path34) !== afterSizes.get(path34)) changed.add(path34);
|
|
3946
4011
|
}
|
|
3947
4012
|
}
|
|
3948
4013
|
return [...changed].sort();
|
|
@@ -4417,7 +4482,6 @@ var init_package_tool = __esm({
|
|
|
4417
4482
|
const operationOptions2 = {
|
|
4418
4483
|
...input.names ? { packages: input.names } : {},
|
|
4419
4484
|
...input.scope ? { packageScope: input.scope } : {},
|
|
4420
|
-
...input.dryRun !== void 0 ? { allowScripts: input.dryRun } : { allowScripts: false },
|
|
4421
4485
|
...input.allowScripts !== void 0 ? { allowScripts: input.allowScripts } : {}
|
|
4422
4486
|
};
|
|
4423
4487
|
const planResult = await planLanguageOperation({
|
|
@@ -4446,6 +4510,24 @@ var init_package_tool = __esm({
|
|
|
4446
4510
|
};
|
|
4447
4511
|
return;
|
|
4448
4512
|
}
|
|
4513
|
+
if (input.dryRun) {
|
|
4514
|
+
yield {
|
|
4515
|
+
type: "final",
|
|
4516
|
+
output: {
|
|
4517
|
+
status: "passed",
|
|
4518
|
+
language: planResult.workspace.language,
|
|
4519
|
+
workspace: planResult.workspace.root,
|
|
4520
|
+
operation: planResult.plan.operation,
|
|
4521
|
+
mutations: [],
|
|
4522
|
+
vulnerabilities: [],
|
|
4523
|
+
outdated: [],
|
|
4524
|
+
manifestsChanged: [],
|
|
4525
|
+
lockfilesChanged: [],
|
|
4526
|
+
output: `Dry run: ${planResult.plan.command ?? "internal"} ${planResult.plan.args.join(" ")}`.trim()
|
|
4527
|
+
}
|
|
4528
|
+
};
|
|
4529
|
+
return;
|
|
4530
|
+
}
|
|
4449
4531
|
const runner = executePackagePlan({
|
|
4450
4532
|
projectRoot: ctx.projectRoot,
|
|
4451
4533
|
workspace: planResult.workspace,
|
|
@@ -4948,10 +5030,10 @@ import {
|
|
|
4948
5030
|
emitProcessCompleted as emitProcessCompleted2,
|
|
4949
5031
|
emitProcessOutput as emitProcessOutput2,
|
|
4950
5032
|
emitProcessStarted as emitProcessStarted2
|
|
4951
|
-
} from "@wrongstack/core";
|
|
5033
|
+
} from "@wrongstack/core/observability";
|
|
4952
5034
|
|
|
4953
5035
|
// src/_env.ts
|
|
4954
|
-
import { buildChildEnv as buildChildEnv2 } from "@wrongstack/core";
|
|
5036
|
+
import { buildChildEnv as buildChildEnv2 } from "@wrongstack/core/utils";
|
|
4955
5037
|
|
|
4956
5038
|
// src/bash.ts
|
|
4957
5039
|
init_output_spool();
|
|
@@ -4965,8 +5047,9 @@ import * as os3 from "node:os";
|
|
|
4965
5047
|
init_process_registry();
|
|
4966
5048
|
import * as fs6 from "node:fs/promises";
|
|
4967
5049
|
import * as os2 from "node:os";
|
|
5050
|
+
import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
|
|
4968
5051
|
import * as path9 from "node:path";
|
|
4969
|
-
var REGISTRY_FILE = "
|
|
5052
|
+
var REGISTRY_FILE = "process-registry.json";
|
|
4970
5053
|
function toErrorMessage(err) {
|
|
4971
5054
|
return err instanceof Error ? err.message : String(err);
|
|
4972
5055
|
}
|
|
@@ -4984,7 +5067,7 @@ function emitStructuredLog(level, event, message, error) {
|
|
|
4984
5067
|
}
|
|
4985
5068
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
4986
5069
|
var STALE_THRESHOLD_MS = 3e4;
|
|
4987
|
-
var LOCKFILE = ".
|
|
5070
|
+
var LOCKFILE = ".process-registry.lock";
|
|
4988
5071
|
function generateInstanceId() {
|
|
4989
5072
|
const hostname2 = os2.hostname();
|
|
4990
5073
|
const pid = process.pid;
|
|
@@ -5073,12 +5156,18 @@ var PersistentProcessRegistry = class {
|
|
|
5073
5156
|
lockPath;
|
|
5074
5157
|
baseRegistry;
|
|
5075
5158
|
heartbeatInterval = null;
|
|
5159
|
+
cleanupInterval = null;
|
|
5160
|
+
heartbeatRunning = false;
|
|
5161
|
+
cleanupRunning = false;
|
|
5076
5162
|
isShuttingDown = false;
|
|
5163
|
+
onProcessExit = () => {
|
|
5164
|
+
void this.syncToPersistent();
|
|
5165
|
+
};
|
|
5077
5166
|
constructor(baseRegistry) {
|
|
5078
5167
|
this.instanceId = generateInstanceId();
|
|
5079
|
-
const
|
|
5080
|
-
this.registryPath = path9.join(
|
|
5081
|
-
this.lockPath = path9.join(
|
|
5168
|
+
const globalRoot = wstackGlobalRoot3();
|
|
5169
|
+
this.registryPath = path9.join(globalRoot, REGISTRY_FILE);
|
|
5170
|
+
this.lockPath = path9.join(globalRoot, LOCKFILE);
|
|
5082
5171
|
this.baseRegistry = baseRegistry ?? getProcessRegistry();
|
|
5083
5172
|
this.ensureDirectory().catch((err) => {
|
|
5084
5173
|
emitStructuredLog("warn", "process_registry.dir_create_failed", "PersistentProcessRegistry: failed to create .wrongstack directory", err);
|
|
@@ -5097,16 +5186,18 @@ var PersistentProcessRegistry = class {
|
|
|
5097
5186
|
*/
|
|
5098
5187
|
start() {
|
|
5099
5188
|
if (this.heartbeatInterval) return;
|
|
5100
|
-
this.
|
|
5189
|
+
this.isShuttingDown = false;
|
|
5190
|
+
this.heartbeat();
|
|
5101
5191
|
this.heartbeatInterval = setInterval(() => {
|
|
5102
5192
|
this.heartbeat();
|
|
5103
5193
|
}, HEARTBEAT_INTERVAL_MS);
|
|
5104
5194
|
this.heartbeatInterval.unref?.();
|
|
5105
|
-
setInterval(() => {
|
|
5106
|
-
this.
|
|
5107
|
-
}, STALE_THRESHOLD_MS)
|
|
5195
|
+
this.cleanupInterval = setInterval(() => {
|
|
5196
|
+
this.cleanup();
|
|
5197
|
+
}, STALE_THRESHOLD_MS);
|
|
5198
|
+
this.cleanupInterval.unref?.();
|
|
5108
5199
|
this.registerMainProcess();
|
|
5109
|
-
process.on("exit",
|
|
5200
|
+
process.on("exit", this.onProcessExit);
|
|
5110
5201
|
}
|
|
5111
5202
|
/**
|
|
5112
5203
|
* Stop the heartbeat and clean up.
|
|
@@ -5117,7 +5208,12 @@ var PersistentProcessRegistry = class {
|
|
|
5117
5208
|
clearInterval(this.heartbeatInterval);
|
|
5118
5209
|
this.heartbeatInterval = null;
|
|
5119
5210
|
}
|
|
5120
|
-
this.
|
|
5211
|
+
if (this.cleanupInterval) {
|
|
5212
|
+
clearInterval(this.cleanupInterval);
|
|
5213
|
+
this.cleanupInterval = null;
|
|
5214
|
+
}
|
|
5215
|
+
process.off("exit", this.onProcessExit);
|
|
5216
|
+
void this.syncToPersistent();
|
|
5121
5217
|
}
|
|
5122
5218
|
/**
|
|
5123
5219
|
* Register the main WrongStack process as protected.
|
|
@@ -5201,8 +5297,18 @@ var PersistentProcessRegistry = class {
|
|
|
5201
5297
|
* Send heartbeat to mark all this instance's processes as alive.
|
|
5202
5298
|
*/
|
|
5203
5299
|
heartbeat() {
|
|
5204
|
-
if (this.isShuttingDown) return;
|
|
5205
|
-
this.
|
|
5300
|
+
if (this.isShuttingDown || this.heartbeatRunning) return;
|
|
5301
|
+
this.heartbeatRunning = true;
|
|
5302
|
+
void this.syncToPersistent().finally(() => {
|
|
5303
|
+
this.heartbeatRunning = false;
|
|
5304
|
+
});
|
|
5305
|
+
}
|
|
5306
|
+
cleanup() {
|
|
5307
|
+
if (this.isShuttingDown || this.cleanupRunning) return;
|
|
5308
|
+
this.cleanupRunning = true;
|
|
5309
|
+
void this.cleanupStaleEntries().finally(() => {
|
|
5310
|
+
this.cleanupRunning = false;
|
|
5311
|
+
});
|
|
5206
5312
|
}
|
|
5207
5313
|
/**
|
|
5208
5314
|
* Sync this instance's processes to the persistent registry.
|
|
@@ -5918,14 +6024,12 @@ var bashTool = {
|
|
|
5918
6024
|
const detached = !isWin5;
|
|
5919
6025
|
const startedAt = Date.now();
|
|
5920
6026
|
if (input.background) {
|
|
5921
|
-
let buf2 = "";
|
|
5922
|
-
let truncated = false;
|
|
5923
6027
|
const child2 = spawn3(shell, args, {
|
|
5924
6028
|
cwd: ctx.projectRoot,
|
|
5925
6029
|
env,
|
|
5926
6030
|
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
5927
6031
|
// and POSIX shells ignore stdin when given the command inline.
|
|
5928
|
-
stdio: [plan.useStdin ? "pipe" : "ignore", "
|
|
6032
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "ignore", "ignore"],
|
|
5929
6033
|
// win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
|
|
5930
6034
|
// DETACHED_PROCESS (detached: true) is set, so the console-less
|
|
5931
6035
|
// cmd.exe's grandchildren (node, dev servers) each allocate a fresh
|
|
@@ -5944,8 +6048,8 @@ var bashTool = {
|
|
|
5944
6048
|
}
|
|
5945
6049
|
}
|
|
5946
6050
|
const pid2 = child2.pid;
|
|
5947
|
-
|
|
5948
|
-
|
|
6051
|
+
const stdoutBytes2 = 0;
|
|
6052
|
+
const stderrBytes2 = 0;
|
|
5949
6053
|
let telemetryCompleted2 = false;
|
|
5950
6054
|
emitProcessStarted2({
|
|
5951
6055
|
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
@@ -5978,41 +6082,17 @@ var bashTool = {
|
|
|
5978
6082
|
startedAt: Date.now(),
|
|
5979
6083
|
sessionId: ctx.session?.id,
|
|
5980
6084
|
child: child2,
|
|
5981
|
-
processGroupLeader: detached && child2.pid === pid2
|
|
6085
|
+
processGroupLeader: detached && child2.pid === pid2,
|
|
6086
|
+
background: true
|
|
5982
6087
|
});
|
|
5983
6088
|
child2.on("close", () => registry.unregister(pid2));
|
|
5984
6089
|
}
|
|
5985
|
-
const onBgData = (chunk, stream) => {
|
|
5986
|
-
if (stream === "stdout") stdoutBytes2 += chunk.byteLength;
|
|
5987
|
-
else stderrBytes2 += chunk.byteLength;
|
|
5988
|
-
emitProcessOutput2({ pid: pid2, stream, chunk });
|
|
5989
|
-
if (truncated) return;
|
|
5990
|
-
const remain = MAX_OUTPUT - buf2.length;
|
|
5991
|
-
if (remain > 0) {
|
|
5992
|
-
buf2 += chunk.toString().slice(0, remain);
|
|
5993
|
-
}
|
|
5994
|
-
if (buf2.length >= MAX_OUTPUT) {
|
|
5995
|
-
truncated = true;
|
|
5996
|
-
child2.stdout?.off("data", onBgStdout);
|
|
5997
|
-
child2.stderr?.off("data", onBgStderr);
|
|
5998
|
-
}
|
|
5999
|
-
};
|
|
6000
|
-
const onBgStdout = (chunk) => onBgData(chunk, "stdout");
|
|
6001
|
-
const onBgStderr = (chunk) => onBgData(chunk, "stderr");
|
|
6002
|
-
child2.stdout?.on("data", onBgStdout);
|
|
6003
|
-
child2.stderr?.on("data", onBgStderr);
|
|
6004
|
-
const cleanupBackground = () => {
|
|
6005
|
-
child2.stdout?.off("data", onBgStdout);
|
|
6006
|
-
child2.stderr?.off("data", onBgStderr);
|
|
6007
|
-
};
|
|
6008
6090
|
child2.on("error", () => {
|
|
6009
|
-
cleanupBackground();
|
|
6010
6091
|
if (typeof pid2 === "number") registry.unregister(pid2);
|
|
6011
6092
|
registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
|
|
6012
6093
|
completeBackground(1);
|
|
6013
6094
|
});
|
|
6014
6095
|
child2.on("close", (code, signal) => {
|
|
6015
|
-
cleanupBackground();
|
|
6016
6096
|
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
6017
6097
|
completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
6018
6098
|
});
|
|
@@ -6020,7 +6100,7 @@ var bashTool = {
|
|
|
6020
6100
|
yield {
|
|
6021
6101
|
type: "final",
|
|
6022
6102
|
output: {
|
|
6023
|
-
output:
|
|
6103
|
+
output: "",
|
|
6024
6104
|
exit_code: null,
|
|
6025
6105
|
timed_out: false,
|
|
6026
6106
|
pid: pid2
|
|
@@ -6291,6 +6371,7 @@ ${hint}` : ""),
|
|
|
6291
6371
|
for (const entry of registry.bySession(sessionId)) {
|
|
6292
6372
|
if (entry.name !== "bash") continue;
|
|
6293
6373
|
if (entry.child.exitCode !== null) continue;
|
|
6374
|
+
if (entry.background) continue;
|
|
6294
6375
|
if (entry.protected) continue;
|
|
6295
6376
|
registry.kill(entry.pid, { force: true });
|
|
6296
6377
|
}
|
|
@@ -6300,7 +6381,7 @@ ${hint}` : ""),
|
|
|
6300
6381
|
// src/batch-tool-use.ts
|
|
6301
6382
|
import {
|
|
6302
6383
|
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6303
|
-
} from "@wrongstack/core";
|
|
6384
|
+
} from "@wrongstack/core/types";
|
|
6304
6385
|
var batchToolUseTool = {
|
|
6305
6386
|
name: "batch_tool_use",
|
|
6306
6387
|
category: "Meta",
|
|
@@ -6423,7 +6504,7 @@ async function executeSingle(call, ctx, governedExecute) {
|
|
|
6423
6504
|
|
|
6424
6505
|
// src/browser/tools.ts
|
|
6425
6506
|
import * as path12 from "node:path";
|
|
6426
|
-
import { ToolCapabilities } from "@wrongstack/core";
|
|
6507
|
+
import { ToolCapabilities } from "@wrongstack/core/security";
|
|
6427
6508
|
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
6428
6509
|
|
|
6429
6510
|
// src/browser/manager.ts
|
|
@@ -7085,6 +7166,10 @@ var BrowserSessionManager = class {
|
|
|
7085
7166
|
const ids = [...this.sessions.values()].filter((session) => session.ownerId === ownerId).map((session) => session.id);
|
|
7086
7167
|
for (const id of ids) await this.close(id, ownerId).catch(() => void 0);
|
|
7087
7168
|
}
|
|
7169
|
+
/** True when the manager owns no live browser contexts. */
|
|
7170
|
+
isIdle() {
|
|
7171
|
+
return this.sessions.size === 0;
|
|
7172
|
+
}
|
|
7088
7173
|
async dispose() {
|
|
7089
7174
|
for (const session of [...this.sessions.values()]) {
|
|
7090
7175
|
await session.context.close().catch(() => void 0);
|
|
@@ -7266,12 +7351,17 @@ function managerFor(ctx) {
|
|
|
7266
7351
|
if (cleanupSignalByContext.get(ctx) !== runSignal) {
|
|
7267
7352
|
cleanupSignalByContext.set(ctx, runSignal);
|
|
7268
7353
|
const managerForRun = manager;
|
|
7354
|
+
const rootForRun = root;
|
|
7269
7355
|
const ownerForRun = owner(ctx);
|
|
7270
7356
|
ctx.registerAbortHook?.(async () => {
|
|
7271
7357
|
if (cleanupSignalByContext.get(ctx) === runSignal) {
|
|
7272
7358
|
cleanupSignalByContext.delete(ctx);
|
|
7273
7359
|
}
|
|
7274
7360
|
await managerForRun.closeOwner(ownerForRun);
|
|
7361
|
+
if (managerForRun.isIdle() && managers.get(rootForRun) === managerForRun) {
|
|
7362
|
+
managers.delete(rootForRun);
|
|
7363
|
+
await managerForRun.dispose();
|
|
7364
|
+
}
|
|
7275
7365
|
});
|
|
7276
7366
|
}
|
|
7277
7367
|
return manager;
|
|
@@ -7654,7 +7744,7 @@ for (const tool of browserTools) tool.icon = "web";
|
|
|
7654
7744
|
for (const tool of browserTools) tool.timeoutMs ??= 6e4;
|
|
7655
7745
|
|
|
7656
7746
|
// src/codebase-index/writer.ts
|
|
7657
|
-
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
7747
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
7658
7748
|
|
|
7659
7749
|
// src/codebase-index/circuit-breaker.ts
|
|
7660
7750
|
var CircuitOpenError = class extends Error {
|
|
@@ -7741,7 +7831,7 @@ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
|
7741
7831
|
import { createRequire } from "node:module";
|
|
7742
7832
|
import * as fs9 from "node:fs";
|
|
7743
7833
|
import * as path13 from "node:path";
|
|
7744
|
-
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
7834
|
+
import { resolveWstackPaths as resolveWstackPaths2, sqliteCachePragmas } from "@wrongstack/core/utils";
|
|
7745
7835
|
|
|
7746
7836
|
// src/codebase-index/schema.ts
|
|
7747
7837
|
var SCHEMA_VERSION = 3;
|
|
@@ -7797,23 +7887,13 @@ function buildBm25Index(docs) {
|
|
|
7797
7887
|
const tokens = tokenise(d.text);
|
|
7798
7888
|
return { id: d.id, tokens, raw: d.text, len: tokens.length };
|
|
7799
7889
|
});
|
|
7800
|
-
const df = {};
|
|
7801
|
-
for (const doc of documents) {
|
|
7802
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7803
|
-
for (const t of doc.tokens) {
|
|
7804
|
-
if (!seen.has(t)) {
|
|
7805
|
-
df[t] = (df[t] ?? 0) + 1;
|
|
7806
|
-
seen.add(t);
|
|
7807
|
-
}
|
|
7808
|
-
}
|
|
7809
|
-
}
|
|
7810
7890
|
const N = documents.length;
|
|
7811
7891
|
const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
|
|
7812
7892
|
const avgLen = N === 0 ? 0 : totalLen / N;
|
|
7813
|
-
return new Bm25Index(documents,
|
|
7893
|
+
return new Bm25Index(documents, N, avgLen);
|
|
7814
7894
|
}
|
|
7815
7895
|
var Bm25Index = class {
|
|
7816
|
-
constructor(documents,
|
|
7896
|
+
constructor(documents, N, avgLen) {
|
|
7817
7897
|
this.documents = documents;
|
|
7818
7898
|
this.N = N;
|
|
7819
7899
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
@@ -7821,9 +7901,19 @@ var Bm25Index = class {
|
|
|
7821
7901
|
documents;
|
|
7822
7902
|
N;
|
|
7823
7903
|
safeAvgLen;
|
|
7904
|
+
/** Lazily-built id→doc index so getDoc is O(1) instead of an O(D) linear find. */
|
|
7905
|
+
_byId;
|
|
7824
7906
|
score(query, filter) {
|
|
7825
7907
|
const qTokens = tokenise(query);
|
|
7826
7908
|
if (qTokens.length === 0) return [];
|
|
7909
|
+
const dfByTerm = /* @__PURE__ */ new Map();
|
|
7910
|
+
for (const qTerm of qTokens) {
|
|
7911
|
+
let dfVal = 0;
|
|
7912
|
+
for (const candidate of this.documents) {
|
|
7913
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
7914
|
+
}
|
|
7915
|
+
dfByTerm.set(qTerm, dfVal);
|
|
7916
|
+
}
|
|
7827
7917
|
const results = [];
|
|
7828
7918
|
for (const doc of this.documents) {
|
|
7829
7919
|
if (filter && !filter(doc.id)) continue;
|
|
@@ -7834,10 +7924,7 @@ var Bm25Index = class {
|
|
|
7834
7924
|
if (t.startsWith(qTerm)) tf++;
|
|
7835
7925
|
}
|
|
7836
7926
|
if (tf === 0) continue;
|
|
7837
|
-
|
|
7838
|
-
for (const candidate of this.documents) {
|
|
7839
|
-
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
7840
|
-
}
|
|
7927
|
+
const dfVal = dfByTerm.get(qTerm) ?? 0;
|
|
7841
7928
|
if (dfVal === 0) continue;
|
|
7842
7929
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
7843
7930
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -7849,7 +7936,10 @@ var Bm25Index = class {
|
|
|
7849
7936
|
return results;
|
|
7850
7937
|
}
|
|
7851
7938
|
getDoc(id) {
|
|
7852
|
-
|
|
7939
|
+
if (!this._byId) {
|
|
7940
|
+
this._byId = new Map(this.documents.map((d) => [d.id, d]));
|
|
7941
|
+
}
|
|
7942
|
+
return this._byId.get(id);
|
|
7853
7943
|
}
|
|
7854
7944
|
extractSnippet(docId, queryTokens, radius = 40) {
|
|
7855
7945
|
const doc = this.getDoc(docId);
|
|
@@ -8011,6 +8101,25 @@ var IndexStore = class _IndexStore {
|
|
|
8011
8101
|
* and reuse it. Cleared in {@link close} when the connection is torn down.
|
|
8012
8102
|
*/
|
|
8013
8103
|
stmtCache = /* @__PURE__ */ new Map();
|
|
8104
|
+
/**
|
|
8105
|
+
* Cached full-corpus BM25 index for the FTS5-unavailable fallback path.
|
|
8106
|
+
* Built lazily on the first `searchRankedFallback` call and invalidated
|
|
8107
|
+
* (via `bm25Dirty`) whenever the `symbols` table is mutated. Computing
|
|
8108
|
+
* IDF over the full corpus is also more correct than the old per-query
|
|
8109
|
+
* candidate-subset IDF.
|
|
8110
|
+
*
|
|
8111
|
+
* Cache-lifecycle invariants (single source of truth lives at the
|
|
8112
|
+
* `invalidateBm25()` helper — see its docblock for the "every mutation
|
|
8113
|
+
* MUST call this" contract):
|
|
8114
|
+
* - declaration: this field + `bm25Dirty` (here)
|
|
8115
|
+
* - invalidation: `invalidateBm25()` flips the flag and nulls the cache
|
|
8116
|
+
* - build: `getOrBuildBm25()` rebuilds against current `symbols` rows
|
|
8117
|
+
* - teardown: `close()` resets the flag and nulls the cache
|
|
8118
|
+
*/
|
|
8119
|
+
bm25Cache = null;
|
|
8120
|
+
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
8121
|
+
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
8122
|
+
bm25Dirty = true;
|
|
8014
8123
|
/** Prepare-once helper: compile `sql` on first use, reuse thereafter. */
|
|
8015
8124
|
stmt(sql) {
|
|
8016
8125
|
let s = this.stmtCache.get(sql);
|
|
@@ -8058,7 +8167,15 @@ var IndexStore = class _IndexStore {
|
|
|
8058
8167
|
this.db = new Database(path13.join(this.indexDir, DB_FILE));
|
|
8059
8168
|
try {
|
|
8060
8169
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
8061
|
-
this.db.exec("PRAGMA
|
|
8170
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
8171
|
+
this.db.exec("PRAGMA busy_timeout = 15000");
|
|
8172
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
8173
|
+
const cache2 = sqliteCachePragmas();
|
|
8174
|
+
this.db.exec(`PRAGMA cache_size = -${cache2.cacheSizeKiB}`);
|
|
8175
|
+
this.db.exec(`PRAGMA mmap_size = ${cache2.mmapBytes}`);
|
|
8176
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
8177
|
+
this.db.exec("PRAGMA journal_size_limit = 67108864");
|
|
8178
|
+
this.db.exec("PRAGMA wal_autocheckpoint = 1000");
|
|
8062
8179
|
} catch {
|
|
8063
8180
|
}
|
|
8064
8181
|
this.initSchema();
|
|
@@ -8070,7 +8187,7 @@ var IndexStore = class _IndexStore {
|
|
|
8070
8187
|
value TEXT NOT NULL
|
|
8071
8188
|
);
|
|
8072
8189
|
`);
|
|
8073
|
-
const storedRows = this.
|
|
8190
|
+
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
8074
8191
|
const storedVersion = storedRows.length ? Number(storedRows[0]?.value) : null;
|
|
8075
8192
|
if (storedVersion !== null && storedVersion !== SCHEMA_VERSION) {
|
|
8076
8193
|
this.db.exec(`
|
|
@@ -8079,9 +8196,9 @@ var IndexStore = class _IndexStore {
|
|
|
8079
8196
|
DROP TABLE IF EXISTS refs;
|
|
8080
8197
|
`);
|
|
8081
8198
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
8082
|
-
this.
|
|
8199
|
+
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
|
|
8083
8200
|
} else if (storedVersion === null) {
|
|
8084
|
-
this.
|
|
8201
|
+
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
|
|
8085
8202
|
}
|
|
8086
8203
|
this.db.exec(`
|
|
8087
8204
|
CREATE TABLE IF NOT EXISTS files (
|
|
@@ -8112,6 +8229,7 @@ var IndexStore = class _IndexStore {
|
|
|
8112
8229
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
|
|
8113
8230
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
|
|
8114
8231
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
|
|
8232
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)");
|
|
8115
8233
|
this.db.exec(`
|
|
8116
8234
|
CREATE TABLE IF NOT EXISTS refs (
|
|
8117
8235
|
id INTEGER PRIMARY KEY,
|
|
@@ -8132,65 +8250,167 @@ var IndexStore = class _IndexStore {
|
|
|
8132
8250
|
);
|
|
8133
8251
|
this.ftsAvailable = true;
|
|
8134
8252
|
const symbolCount = Number(
|
|
8135
|
-
this.
|
|
8253
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8136
8254
|
);
|
|
8137
8255
|
const ftsCount = Number(
|
|
8138
|
-
this.
|
|
8256
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8139
8257
|
);
|
|
8140
8258
|
if (symbolCount !== ftsCount) {
|
|
8141
8259
|
this.db.exec("DELETE FROM symbols_fts");
|
|
8142
|
-
const
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8260
|
+
const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8261
|
+
this.bulkInsertFts(
|
|
8262
|
+
rows.map((row) => ({
|
|
8263
|
+
id: row.id,
|
|
8264
|
+
text: buildIndexableText(row.name, row.signature, row.doc_comment)
|
|
8265
|
+
}))
|
|
8266
|
+
);
|
|
8267
|
+
this.invalidateBm25();
|
|
8147
8268
|
}
|
|
8148
8269
|
} catch {
|
|
8149
8270
|
this.ftsAvailable = false;
|
|
8150
8271
|
}
|
|
8272
|
+
this.ensureNextSymbolIdSeeded();
|
|
8273
|
+
}
|
|
8274
|
+
// ─── ID allocation & bulk helpers ────────────────────────────────────────────
|
|
8275
|
+
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
8276
|
+
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
8277
|
+
static MAX_SQL_VARS = 900;
|
|
8278
|
+
/**
|
|
8279
|
+
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
8280
|
+
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
8281
|
+
* re-reads and advances the counter atomically.
|
|
8282
|
+
*/
|
|
8283
|
+
ensureNextSymbolIdSeeded() {
|
|
8284
|
+
const existing = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8285
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8286
|
+
);
|
|
8287
|
+
if (existing?.value !== void 0) return;
|
|
8288
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8289
|
+
const next = (maxRows[0]?.m ?? 0) + 1;
|
|
8290
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8291
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8292
|
+
String(next)
|
|
8293
|
+
);
|
|
8294
|
+
}
|
|
8295
|
+
/**
|
|
8296
|
+
* Reserve `count` consecutive symbol ids. MUST run inside BEGIN IMMEDIATE
|
|
8297
|
+
* so concurrent indexers cannot hand out overlapping ranges.
|
|
8298
|
+
*/
|
|
8299
|
+
allocateSymbolIds(count) {
|
|
8300
|
+
if (count <= 0) return this.getMaxSymbolId() + 1;
|
|
8301
|
+
this.ensureNextSymbolIdSeeded();
|
|
8302
|
+
const row = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8303
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8304
|
+
);
|
|
8305
|
+
const start = Math.max(1, Number(row?.value ?? 1) || 1);
|
|
8306
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8307
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8308
|
+
String(start + count)
|
|
8309
|
+
);
|
|
8310
|
+
return start;
|
|
8311
|
+
}
|
|
8312
|
+
/** Multi-row INSERT for symbols — amortizes prepare/bind overhead in large batches. */
|
|
8313
|
+
bulkInsertSymbols(rows) {
|
|
8314
|
+
if (rows.length === 0) return;
|
|
8315
|
+
const cols = 12;
|
|
8316
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8317
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8318
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8319
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
8320
|
+
const stmt = this.stmt(
|
|
8321
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8322
|
+
VALUES ${placeholders}`
|
|
8323
|
+
);
|
|
8324
|
+
const binds = [];
|
|
8325
|
+
for (const r of chunk) {
|
|
8326
|
+
binds.push(
|
|
8327
|
+
r.id,
|
|
8328
|
+
r.lang,
|
|
8329
|
+
r.kind,
|
|
8330
|
+
r.name,
|
|
8331
|
+
r.file,
|
|
8332
|
+
r.line,
|
|
8333
|
+
r.col,
|
|
8334
|
+
r.signature,
|
|
8335
|
+
r.docComment,
|
|
8336
|
+
r.scope,
|
|
8337
|
+
r.text,
|
|
8338
|
+
r.file
|
|
8339
|
+
);
|
|
8340
|
+
}
|
|
8341
|
+
stmt.run(...binds);
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
bulkInsertFts(rows) {
|
|
8345
|
+
if (!this.ftsAvailable || rows.length === 0) return;
|
|
8346
|
+
const cols = 2;
|
|
8347
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8348
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8349
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8350
|
+
const placeholders = chunk.map(() => "(?, ?)").join(", ");
|
|
8351
|
+
const stmt = this.stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders}`);
|
|
8352
|
+
const binds = [];
|
|
8353
|
+
for (const r of chunk) binds.push(r.id, r.text);
|
|
8354
|
+
stmt.run(...binds);
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
bulkInsertRefs(refs) {
|
|
8358
|
+
if (refs.length === 0) return;
|
|
8359
|
+
const cols = 5;
|
|
8360
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8361
|
+
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
8362
|
+
const chunk = refs.slice(i, i + chunkSize);
|
|
8363
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
8364
|
+
const stmt = this.stmt(
|
|
8365
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line) VALUES ${placeholders}`
|
|
8366
|
+
);
|
|
8367
|
+
const binds = [];
|
|
8368
|
+
for (const ref of chunk) {
|
|
8369
|
+
binds.push(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8370
|
+
}
|
|
8371
|
+
stmt.run(...binds);
|
|
8372
|
+
}
|
|
8151
8373
|
}
|
|
8152
8374
|
// ─── Symbol CRUD ─────────────────────────────────────────────────────────────
|
|
8153
8375
|
/**
|
|
8154
8376
|
* Insert symbols, assigning IDs atomically inside `BEGIN IMMEDIATE` /
|
|
8155
|
-
* `COMMIT`.
|
|
8156
|
-
*
|
|
8157
|
-
* processes index concurrently (each would see a different `MAX(id)` and
|
|
8158
|
-
* neither can insert with the other's IDs).
|
|
8377
|
+
* `COMMIT`. Id ranges come from the `next_symbol_id` metadata counter
|
|
8378
|
+
* (O(1)); multi-row INSERT amortizes bind overhead for large files.
|
|
8159
8379
|
*
|
|
8160
8380
|
* @returns The symbols array with `id` fields populated so the caller can
|
|
8161
8381
|
* use them for refs without re-reading from the DB.
|
|
8162
8382
|
*/
|
|
8163
8383
|
insertSymbols(symbols) {
|
|
8384
|
+
this.invalidateBm25();
|
|
8164
8385
|
return this.runWithRetry(() => {
|
|
8165
8386
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8166
8387
|
try {
|
|
8167
|
-
|
|
8168
|
-
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
8169
|
-
const stmt = this.db.prepare(
|
|
8170
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8171
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8172
|
-
);
|
|
8173
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
8388
|
+
let nextId = this.allocateSymbolIds(symbols.length);
|
|
8174
8389
|
const result = [];
|
|
8390
|
+
const bulk = [];
|
|
8391
|
+
const ftsRows = [];
|
|
8175
8392
|
for (const s of symbols) {
|
|
8176
8393
|
const id = nextId++;
|
|
8177
|
-
|
|
8394
|
+
bulk.push({
|
|
8178
8395
|
id,
|
|
8179
|
-
s.lang,
|
|
8180
|
-
s.kind,
|
|
8181
|
-
s.name,
|
|
8182
|
-
s.file,
|
|
8183
|
-
s.line,
|
|
8184
|
-
s.col,
|
|
8185
|
-
s.signature,
|
|
8186
|
-
s.docComment,
|
|
8187
|
-
s.scope,
|
|
8188
|
-
s.text
|
|
8189
|
-
|
|
8190
|
-
)
|
|
8191
|
-
|
|
8396
|
+
lang: s.lang,
|
|
8397
|
+
kind: s.kind,
|
|
8398
|
+
name: s.name,
|
|
8399
|
+
file: s.file,
|
|
8400
|
+
line: s.line,
|
|
8401
|
+
col: s.col,
|
|
8402
|
+
signature: s.signature,
|
|
8403
|
+
docComment: s.docComment,
|
|
8404
|
+
scope: s.scope,
|
|
8405
|
+
text: s.text
|
|
8406
|
+
});
|
|
8407
|
+
if (this.ftsAvailable) {
|
|
8408
|
+
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
8409
|
+
}
|
|
8192
8410
|
result.push({ ...s, id });
|
|
8193
8411
|
}
|
|
8412
|
+
this.bulkInsertSymbols(bulk);
|
|
8413
|
+
this.bulkInsertFts(ftsRows);
|
|
8194
8414
|
this.db.exec("COMMIT");
|
|
8195
8415
|
return result;
|
|
8196
8416
|
} catch (err) {
|
|
@@ -8200,6 +8420,7 @@ var IndexStore = class _IndexStore {
|
|
|
8200
8420
|
});
|
|
8201
8421
|
}
|
|
8202
8422
|
deleteSymbolsForFile(file) {
|
|
8423
|
+
this.invalidateBm25();
|
|
8203
8424
|
this.runWithRetry(() => {
|
|
8204
8425
|
if (this.ftsAvailable) {
|
|
8205
8426
|
this.stmt(
|
|
@@ -8215,17 +8436,20 @@ var IndexStore = class _IndexStore {
|
|
|
8215
8436
|
* dropped the `files` row, leaving its symbols orphaned but still searchable.
|
|
8216
8437
|
*/
|
|
8217
8438
|
deleteFile(file) {
|
|
8439
|
+
this.invalidateBm25();
|
|
8218
8440
|
this.runWithRetry(() => {
|
|
8219
8441
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8220
8442
|
try {
|
|
8221
8443
|
if (this.ftsAvailable) {
|
|
8222
|
-
this.
|
|
8444
|
+
this.stmt(
|
|
8223
8445
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8224
8446
|
).run(file);
|
|
8225
8447
|
}
|
|
8226
|
-
this.
|
|
8227
|
-
|
|
8228
|
-
|
|
8448
|
+
this.stmt(
|
|
8449
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8450
|
+
).run(file);
|
|
8451
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8452
|
+
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
8229
8453
|
this.db.exec("COMMIT");
|
|
8230
8454
|
} catch (err) {
|
|
8231
8455
|
this.db.exec("ROLLBACK");
|
|
@@ -8262,7 +8486,7 @@ var IndexStore = class _IndexStore {
|
|
|
8262
8486
|
};
|
|
8263
8487
|
}
|
|
8264
8488
|
getAllFileMetas() {
|
|
8265
|
-
return this.
|
|
8489
|
+
return this.stmt("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8266
8490
|
file: r.file,
|
|
8267
8491
|
lang: r.lang,
|
|
8268
8492
|
mtimeMs: r.mtime_ms,
|
|
@@ -8271,7 +8495,32 @@ var IndexStore = class _IndexStore {
|
|
|
8271
8495
|
}));
|
|
8272
8496
|
}
|
|
8273
8497
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8274
|
-
search(query, filter) {
|
|
8498
|
+
search(query, filter, opts) {
|
|
8499
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8500
|
+
if (built === null) return [];
|
|
8501
|
+
const { where, values } = built;
|
|
8502
|
+
const limit = typeof opts?.limit === "number" && Number.isFinite(opts.limit) ? Math.max(0, Math.trunc(opts.limit)) : void 0;
|
|
8503
|
+
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
8504
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
|
|
8505
|
+
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
8506
|
+
const rows = this.stmt(sql).all(...binds);
|
|
8507
|
+
return rows.map((r) => ({
|
|
8508
|
+
id: r.id,
|
|
8509
|
+
lang: r.lang,
|
|
8510
|
+
kind: r.kind,
|
|
8511
|
+
name: r.name,
|
|
8512
|
+
file: r.file,
|
|
8513
|
+
line: r.line,
|
|
8514
|
+
col: r.col,
|
|
8515
|
+
signature: r.signature,
|
|
8516
|
+
docComment: r.doc_comment,
|
|
8517
|
+
score: 0,
|
|
8518
|
+
snippet: "",
|
|
8519
|
+
lspKind: filter?.lspKind
|
|
8520
|
+
}));
|
|
8521
|
+
}
|
|
8522
|
+
/** Shared WHERE builder for {@link search} / empty-query ranked totals. */
|
|
8523
|
+
buildSearchWhere(query, filter) {
|
|
8275
8524
|
const conditions = [];
|
|
8276
8525
|
const values = [];
|
|
8277
8526
|
let effectiveKind = filter?.kind;
|
|
@@ -8280,7 +8529,7 @@ var IndexStore = class _IndexStore {
|
|
|
8280
8529
|
if (mapped !== null) {
|
|
8281
8530
|
effectiveKind = mapped;
|
|
8282
8531
|
} else {
|
|
8283
|
-
return
|
|
8532
|
+
return null;
|
|
8284
8533
|
}
|
|
8285
8534
|
}
|
|
8286
8535
|
if (effectiveKind) {
|
|
@@ -8302,23 +8551,15 @@ var IndexStore = class _IndexStore {
|
|
|
8302
8551
|
for (const t of tokens) values.push(`%${t}%`);
|
|
8303
8552
|
}
|
|
8304
8553
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
line: r.line,
|
|
8315
|
-
col: r.col,
|
|
8316
|
-
signature: r.signature,
|
|
8317
|
-
docComment: r.doc_comment,
|
|
8318
|
-
score: 0,
|
|
8319
|
-
snippet: "",
|
|
8320
|
-
lspKind: filter?.lspKind
|
|
8321
|
-
}));
|
|
8554
|
+
return { where, values };
|
|
8555
|
+
}
|
|
8556
|
+
countSearch(query, filter) {
|
|
8557
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8558
|
+
if (built === null) return 0;
|
|
8559
|
+
const row = this.stmt(`SELECT COUNT(*) AS n FROM symbols ${built.where}`).get(
|
|
8560
|
+
...built.values
|
|
8561
|
+
);
|
|
8562
|
+
return Number(row?.n ?? 0);
|
|
8322
8563
|
}
|
|
8323
8564
|
/**
|
|
8324
8565
|
* Ranked search — the one-stop query the codebase-search tool and plug-lsp
|
|
@@ -8359,12 +8600,12 @@ var IndexStore = class _IndexStore {
|
|
|
8359
8600
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8360
8601
|
}
|
|
8361
8602
|
const where = conditions.join(" AND ");
|
|
8362
|
-
const countRows = this.
|
|
8603
|
+
const countRows = this.stmt(
|
|
8363
8604
|
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8364
8605
|
).all(...values);
|
|
8365
8606
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8366
8607
|
if (total === 0) return { results: [], total: 0 };
|
|
8367
|
-
const rows = this.
|
|
8608
|
+
const rows = this.stmt(
|
|
8368
8609
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
8369
8610
|
-bm25(symbols_fts) AS score,
|
|
8370
8611
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
@@ -8397,20 +8638,60 @@ var IndexStore = class _IndexStore {
|
|
|
8397
8638
|
total
|
|
8398
8639
|
};
|
|
8399
8640
|
}
|
|
8641
|
+
/**
|
|
8642
|
+
* Invalidate the cached BM25 index.
|
|
8643
|
+
*
|
|
8644
|
+
* **Contract: every method that mutates `symbols` MUST call this before
|
|
8645
|
+
* returning.** (`refs` mutations do not affect the BM25 fallback because
|
|
8646
|
+
* the corpus is built from `symbols.text` via `getAllIndexable()` and the
|
|
8647
|
+
* BM25 score is filtered by the LIKE-selected candidate set in
|
|
8648
|
+
* `searchRankedFallback`.) Today the call sites are `repairDrift`,
|
|
8649
|
+
* `insertSymbols`, `deleteSymbolsForFile`, `deleteFile`, `clearAll`, and
|
|
8650
|
+
* `commitBatch`. A future mutation that adds a new write path (e.g.
|
|
8651
|
+
* `renameFile`, `updateSignature`) MUST also call this — otherwise the
|
|
8652
|
+
* FTS5-unavailable fallback will serve stale search results. The
|
|
8653
|
+
* `close()` reset at L1820-1821 tears the cache down on store shutdown,
|
|
8654
|
+
* which is the only legitimate place that flips the flag outside this
|
|
8655
|
+
* helper.
|
|
8656
|
+
*
|
|
8657
|
+
* Called *before* `runWithRetry` on purpose: if the write fails all
|
|
8658
|
+
* retries the flag stays set, forcing a rebuild on the next search rather
|
|
8659
|
+
* than trusting a cache that may not reflect the intended mutation.
|
|
8660
|
+
* Do not move this inside the retry closure.
|
|
8661
|
+
*/
|
|
8662
|
+
invalidateBm25() {
|
|
8663
|
+
this.bm25Dirty = true;
|
|
8664
|
+
this.bm25Cache = null;
|
|
8665
|
+
}
|
|
8666
|
+
/**
|
|
8667
|
+
* Return the cached full-corpus BM25 index, rebuilding it only when the
|
|
8668
|
+
* symbols table has been mutated since the last build. The full-corpus IDF
|
|
8669
|
+
* is more correct than the old per-query candidate-subset IDF, and the
|
|
8670
|
+
* amortized build cost drops from O(symbols × tokens) per search to once
|
|
8671
|
+
* per write batch.
|
|
8672
|
+
*
|
|
8673
|
+
* Note: the first call after a long idle (or on a freshly opened store)
|
|
8674
|
+
* pays the full corpus rebuild synchronously on the search path. For a
|
|
8675
|
+
* 5 500+ symbol corpus this is a visible one-time latency spike.
|
|
8676
|
+
*/
|
|
8677
|
+
getOrBuildBm25() {
|
|
8678
|
+
if (this.bm25Cache && !this.bm25Dirty) return this.bm25Cache;
|
|
8679
|
+
const docs = this.getAllIndexable();
|
|
8680
|
+
this.bm25Cache = buildBm25Index(docs);
|
|
8681
|
+
this.bm25Dirty = false;
|
|
8682
|
+
return this.bm25Cache;
|
|
8683
|
+
}
|
|
8400
8684
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
8401
8685
|
searchRankedFallback(query, filter, limit) {
|
|
8402
|
-
const candidates = this.search(query, filter);
|
|
8403
|
-
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8404
8686
|
if (!query.trim()) {
|
|
8405
|
-
|
|
8687
|
+
const total = this.countSearch(query, filter);
|
|
8688
|
+
if (total === 0) return { results: [], total: 0 };
|
|
8689
|
+
return { results: this.search(query, filter, { limit }), total };
|
|
8406
8690
|
}
|
|
8691
|
+
const candidates = this.search(query, filter);
|
|
8692
|
+
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8407
8693
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8408
|
-
const bm25 =
|
|
8409
|
-
candidates.map((c) => ({
|
|
8410
|
-
id: c.id,
|
|
8411
|
-
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8412
|
-
}))
|
|
8413
|
-
);
|
|
8694
|
+
const bm25 = this.getOrBuildBm25();
|
|
8414
8695
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8415
8696
|
const q = query.trim().toLowerCase();
|
|
8416
8697
|
const rank = (id) => {
|
|
@@ -8452,7 +8733,7 @@ var IndexStore = class _IndexStore {
|
|
|
8452
8733
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8453
8734
|
getStats() {
|
|
8454
8735
|
const sizeBytes = this.sizeBytes();
|
|
8455
|
-
const lastRows = this.
|
|
8736
|
+
const lastRows = this.stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8456
8737
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8457
8738
|
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
8458
8739
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
@@ -8477,19 +8758,20 @@ var IndexStore = class _IndexStore {
|
|
|
8477
8758
|
}
|
|
8478
8759
|
setLastIndexed(ts2) {
|
|
8479
8760
|
this.runWithRetry(() => {
|
|
8480
|
-
this.
|
|
8761
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8481
8762
|
});
|
|
8482
8763
|
}
|
|
8483
8764
|
getMetadata(key) {
|
|
8484
|
-
const rows = this.
|
|
8765
|
+
const rows = this.stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8485
8766
|
return rows[0]?.value;
|
|
8486
8767
|
}
|
|
8487
8768
|
setMetadata(key, value) {
|
|
8488
8769
|
this.runWithRetry(() => {
|
|
8489
|
-
this.
|
|
8770
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8490
8771
|
});
|
|
8491
8772
|
}
|
|
8492
8773
|
clearAll() {
|
|
8774
|
+
this.invalidateBm25();
|
|
8493
8775
|
this.runWithRetry(() => {
|
|
8494
8776
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8495
8777
|
try {
|
|
@@ -8516,13 +8798,7 @@ var IndexStore = class _IndexStore {
|
|
|
8516
8798
|
this.runWithRetry(() => {
|
|
8517
8799
|
this.stmt("DELETE FROM refs WHERE from_id = ?").run(fromId);
|
|
8518
8800
|
if (refs.length === 0) return;
|
|
8519
|
-
|
|
8520
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
8521
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
8522
|
-
);
|
|
8523
|
-
for (const ref of refs) {
|
|
8524
|
-
stmt.run(fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8525
|
-
}
|
|
8801
|
+
this.bulkInsertRefs(refs.map((ref) => ({ ...ref, fromId })));
|
|
8526
8802
|
});
|
|
8527
8803
|
}
|
|
8528
8804
|
/**
|
|
@@ -8539,13 +8815,7 @@ var IndexStore = class _IndexStore {
|
|
|
8539
8815
|
insertRefsBatch(refs) {
|
|
8540
8816
|
if (refs.length === 0) return;
|
|
8541
8817
|
this.runWithRetry(() => {
|
|
8542
|
-
|
|
8543
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
8544
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
8545
|
-
);
|
|
8546
|
-
for (const ref of refs) {
|
|
8547
|
-
stmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8548
|
-
}
|
|
8818
|
+
this.bulkInsertRefs(refs);
|
|
8549
8819
|
});
|
|
8550
8820
|
}
|
|
8551
8821
|
/**
|
|
@@ -8570,65 +8840,61 @@ var IndexStore = class _IndexStore {
|
|
|
8570
8840
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
8571
8841
|
return [];
|
|
8572
8842
|
}
|
|
8843
|
+
this.invalidateBm25();
|
|
8573
8844
|
return this.runWithRetry(() => {
|
|
8574
8845
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8575
8846
|
try {
|
|
8576
8847
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
8577
8848
|
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
8578
8849
|
if (this.ftsAvailable) {
|
|
8579
|
-
this.
|
|
8850
|
+
this.stmt(
|
|
8580
8851
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8581
8852
|
).run(...options.deleteForFiles);
|
|
8582
8853
|
}
|
|
8583
|
-
this.
|
|
8854
|
+
this.stmt(
|
|
8584
8855
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8585
8856
|
).run(...options.deleteForFiles);
|
|
8586
|
-
this.
|
|
8857
|
+
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
8587
8858
|
}
|
|
8588
|
-
const
|
|
8589
|
-
let nextId = (
|
|
8590
|
-
const symStmt = this.db.prepare(
|
|
8591
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8592
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8593
|
-
);
|
|
8594
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
8859
|
+
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
8860
|
+
let nextId = this.allocateSymbolIds(totalSymbols);
|
|
8595
8861
|
const allInserted = [];
|
|
8596
8862
|
const refsToInsert = [];
|
|
8863
|
+
const bulkSyms = [];
|
|
8864
|
+
const ftsRows = [];
|
|
8597
8865
|
for (const entry of entries) {
|
|
8598
8866
|
const insertedForEntry = [];
|
|
8599
8867
|
for (const s of entry.symbols) {
|
|
8600
8868
|
const id = nextId++;
|
|
8601
|
-
|
|
8869
|
+
bulkSyms.push({
|
|
8602
8870
|
id,
|
|
8603
|
-
s.lang,
|
|
8604
|
-
s.kind,
|
|
8605
|
-
s.name,
|
|
8606
|
-
s.file,
|
|
8607
|
-
s.line,
|
|
8608
|
-
s.col,
|
|
8609
|
-
s.signature,
|
|
8610
|
-
s.docComment,
|
|
8611
|
-
s.scope,
|
|
8612
|
-
s.text
|
|
8613
|
-
|
|
8614
|
-
)
|
|
8615
|
-
|
|
8871
|
+
lang: s.lang,
|
|
8872
|
+
kind: s.kind,
|
|
8873
|
+
name: s.name,
|
|
8874
|
+
file: s.file,
|
|
8875
|
+
line: s.line,
|
|
8876
|
+
col: s.col,
|
|
8877
|
+
signature: s.signature,
|
|
8878
|
+
docComment: s.docComment,
|
|
8879
|
+
scope: s.scope,
|
|
8880
|
+
text: s.text
|
|
8881
|
+
});
|
|
8882
|
+
if (this.ftsAvailable) {
|
|
8883
|
+
ftsRows.push({
|
|
8884
|
+
id,
|
|
8885
|
+
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
8886
|
+
});
|
|
8887
|
+
}
|
|
8616
8888
|
const inserted = { ...s, id };
|
|
8617
8889
|
allInserted.push(inserted);
|
|
8618
8890
|
insertedForEntry.push(inserted);
|
|
8619
8891
|
}
|
|
8620
8892
|
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8621
8893
|
}
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
);
|
|
8627
|
-
for (const ref of refsToInsert) {
|
|
8628
|
-
refStmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8629
|
-
}
|
|
8630
|
-
}
|
|
8631
|
-
const upsertStmt = this.db.prepare(
|
|
8894
|
+
this.bulkInsertSymbols(bulkSyms);
|
|
8895
|
+
this.bulkInsertFts(ftsRows);
|
|
8896
|
+
this.bulkInsertRefs(refsToInsert);
|
|
8897
|
+
const upsertStmt = this.stmt(
|
|
8632
8898
|
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
8633
8899
|
VALUES (?, ?, ?, ?, ?)
|
|
8634
8900
|
ON CONFLICT(file) DO UPDATE SET
|
|
@@ -8655,7 +8921,7 @@ var IndexStore = class _IndexStore {
|
|
|
8655
8921
|
*/
|
|
8656
8922
|
deleteRefsForFile(file) {
|
|
8657
8923
|
this.runWithRetry(() => {
|
|
8658
|
-
this.
|
|
8924
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8659
8925
|
});
|
|
8660
8926
|
}
|
|
8661
8927
|
/**
|
|
@@ -8668,20 +8934,75 @@ var IndexStore = class _IndexStore {
|
|
|
8668
8934
|
*/
|
|
8669
8935
|
resolveRefs() {
|
|
8670
8936
|
return this.runWithRetry(() => {
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8937
|
+
try {
|
|
8938
|
+
const result = this.stmt(
|
|
8939
|
+
`UPDATE refs
|
|
8940
|
+
SET to_id = s.id
|
|
8941
|
+
FROM (
|
|
8942
|
+
SELECT name, MIN(id) AS id FROM symbols GROUP BY name
|
|
8943
|
+
) AS s
|
|
8944
|
+
WHERE refs.to_id IS NULL
|
|
8945
|
+
AND refs.to_name IS NOT NULL
|
|
8946
|
+
AND refs.to_name = s.name`
|
|
8947
|
+
).run();
|
|
8948
|
+
return result.changes ?? 0;
|
|
8949
|
+
} catch {
|
|
8950
|
+
const result = this.stmt(
|
|
8951
|
+
`UPDATE refs SET to_id = (
|
|
8952
|
+
SELECT id FROM symbols WHERE name = refs.to_name LIMIT 1
|
|
8953
|
+
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
8954
|
+
AND to_name IN (SELECT name FROM symbols)`
|
|
8955
|
+
).run();
|
|
8956
|
+
return result.changes ?? 0;
|
|
8957
|
+
}
|
|
8958
|
+
});
|
|
8959
|
+
}
|
|
8960
|
+
/**
|
|
8961
|
+
* Clear symbols/refs for a file and mark it as indexed with zero symbols.
|
|
8962
|
+
* Used by the indexer for empty-parse results so three writes share one txn.
|
|
8963
|
+
*/
|
|
8964
|
+
replaceEmptyFile(meta) {
|
|
8965
|
+
this.invalidateBm25();
|
|
8966
|
+
this.runWithRetry(() => {
|
|
8967
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8968
|
+
try {
|
|
8969
|
+
if (this.ftsAvailable) {
|
|
8970
|
+
this.stmt(
|
|
8971
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8972
|
+
).run(meta.file);
|
|
8973
|
+
}
|
|
8974
|
+
this.stmt(
|
|
8975
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8976
|
+
).run(meta.file);
|
|
8977
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
8978
|
+
this.stmt(
|
|
8979
|
+
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
8980
|
+
VALUES (?, ?, ?, ?, ?)
|
|
8981
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
8982
|
+
lang = excluded.lang,
|
|
8983
|
+
mtime_ms = excluded.mtime_ms,
|
|
8984
|
+
symbol_count = excluded.symbol_count,
|
|
8985
|
+
last_indexed = excluded.last_indexed`
|
|
8986
|
+
).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
|
|
8987
|
+
this.db.exec("COMMIT");
|
|
8988
|
+
} catch (err) {
|
|
8989
|
+
this.db.exec("ROLLBACK");
|
|
8990
|
+
throw err;
|
|
8991
|
+
}
|
|
8678
8992
|
});
|
|
8679
8993
|
}
|
|
8994
|
+
/** Best-effort query planner refresh after a large reindex. */
|
|
8995
|
+
optimize() {
|
|
8996
|
+
try {
|
|
8997
|
+
this.db.exec("PRAGMA optimize");
|
|
8998
|
+
} catch {
|
|
8999
|
+
}
|
|
9000
|
+
}
|
|
8680
9001
|
/**
|
|
8681
9002
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
8682
9003
|
*/
|
|
8683
9004
|
findRefsTo(symbolId) {
|
|
8684
|
-
return this.
|
|
9005
|
+
return this.stmt(
|
|
8685
9006
|
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE to_id = ? OR to_name = (SELECT name FROM symbols WHERE id = ?)"
|
|
8686
9007
|
).all(symbolId, symbolId).map((r) => ({
|
|
8687
9008
|
id: r.id,
|
|
@@ -8696,7 +9017,7 @@ var IndexStore = class _IndexStore {
|
|
|
8696
9017
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8697
9018
|
*/
|
|
8698
9019
|
findRefsFrom(symbolId) {
|
|
8699
|
-
return this.
|
|
9020
|
+
return this.stmt("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
8700
9021
|
id: r.id,
|
|
8701
9022
|
fromId: r.from_id,
|
|
8702
9023
|
toName: r.to_name,
|
|
@@ -8769,45 +9090,57 @@ var IndexStore = class _IndexStore {
|
|
|
8769
9090
|
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
8770
9091
|
*/
|
|
8771
9092
|
getPackageGraph() {
|
|
8772
|
-
const
|
|
9093
|
+
const fileCounts = this.stmt(
|
|
9094
|
+
"SELECT file, COUNT(*) AS n FROM symbols GROUP BY file"
|
|
9095
|
+
).all();
|
|
8773
9096
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
8774
9097
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
8775
|
-
const
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
fileToPkg.set(s.file, pkg);
|
|
8779
|
-
symbolToPkg.set(s.id, pkg);
|
|
9098
|
+
for (const { file, n } of fileCounts) {
|
|
9099
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9100
|
+
fileToPkg.set(file, pkg);
|
|
8780
9101
|
const node = pkgNodes.get(pkg);
|
|
8781
9102
|
if (node) {
|
|
8782
|
-
node.symbolCount = (node.symbolCount ?? 0) +
|
|
9103
|
+
node.symbolCount = (node.symbolCount ?? 0) + n;
|
|
8783
9104
|
} else {
|
|
8784
9105
|
pkgNodes.set(pkg, {
|
|
8785
9106
|
id: `pkg:${pkg}`,
|
|
8786
9107
|
label: pkg,
|
|
8787
9108
|
kind: "package",
|
|
8788
9109
|
package: pkg,
|
|
8789
|
-
symbolCount:
|
|
9110
|
+
symbolCount: n,
|
|
8790
9111
|
fileCount: 0
|
|
8791
9112
|
});
|
|
8792
9113
|
}
|
|
8793
9114
|
}
|
|
8794
|
-
const files = this.
|
|
9115
|
+
const files = this.stmt("SELECT DISTINCT file FROM files").all();
|
|
8795
9116
|
for (const { file } of files) {
|
|
8796
9117
|
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9118
|
+
fileToPkg.set(file, pkg);
|
|
8797
9119
|
const node = pkgNodes.get(pkg);
|
|
8798
9120
|
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
9121
|
+
else {
|
|
9122
|
+
pkgNodes.set(pkg, {
|
|
9123
|
+
id: `pkg:${pkg}`,
|
|
9124
|
+
label: pkg,
|
|
9125
|
+
kind: "package",
|
|
9126
|
+
package: pkg,
|
|
9127
|
+
symbolCount: 0,
|
|
9128
|
+
fileCount: 1
|
|
9129
|
+
});
|
|
9130
|
+
}
|
|
8799
9131
|
}
|
|
8800
|
-
const refRows = this.
|
|
8801
|
-
`SELECT r.
|
|
9132
|
+
const refRows = this.stmt(
|
|
9133
|
+
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file
|
|
8802
9134
|
FROM refs r
|
|
8803
|
-
|
|
9135
|
+
JOIN symbols sf ON sf.id = r.from_id
|
|
9136
|
+
JOIN symbols st ON st.id = r.to_id
|
|
9137
|
+
WHERE r.to_id IS NOT NULL AND r.call_type != 'import'`
|
|
8804
9138
|
).all();
|
|
8805
9139
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
8806
9140
|
for (const r of refRows) {
|
|
8807
|
-
|
|
8808
|
-
const
|
|
8809
|
-
|
|
8810
|
-
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
9141
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
9142
|
+
const toPkg = fileToPkg.get(r.to_file) ?? _IndexStore.derivePackage(r.to_file) ?? "(root)";
|
|
9143
|
+
if (fromPkg === toPkg) continue;
|
|
8811
9144
|
const key = `${fromPkg}\0${toPkg}`;
|
|
8812
9145
|
let e = edgeMap.get(key);
|
|
8813
9146
|
if (!e) {
|
|
@@ -8817,13 +9150,14 @@ var IndexStore = class _IndexStore {
|
|
|
8817
9150
|
e.weight++;
|
|
8818
9151
|
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8819
9152
|
}
|
|
8820
|
-
const importRows = this.
|
|
8821
|
-
`SELECT r.
|
|
9153
|
+
const importRows = this.stmt(
|
|
9154
|
+
`SELECT r.to_name, s.file AS from_file
|
|
8822
9155
|
FROM refs r
|
|
9156
|
+
JOIN symbols s ON s.id = r.from_id
|
|
8823
9157
|
WHERE r.call_type = 'import'`
|
|
8824
9158
|
).all();
|
|
8825
9159
|
for (const r of importRows) {
|
|
8826
|
-
const fromPkg =
|
|
9160
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
8827
9161
|
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
8828
9162
|
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
8829
9163
|
const key = `${fromPkg}\0${toPkg}`;
|
|
@@ -8860,12 +9194,12 @@ var IndexStore = class _IndexStore {
|
|
|
8860
9194
|
* derived from cross-file symbol references within the package.
|
|
8861
9195
|
*/
|
|
8862
9196
|
getFileGraph(packageFilter) {
|
|
8863
|
-
const allFiles = this.
|
|
9197
|
+
const allFiles = this.stmt("SELECT DISTINCT file FROM symbols").all();
|
|
8864
9198
|
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
8865
9199
|
const localFiles = new Set(pkgFilePaths);
|
|
8866
9200
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
8867
9201
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
8868
|
-
const pkgSyms = this.
|
|
9202
|
+
const pkgSyms = this.stmt(
|
|
8869
9203
|
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
8870
9204
|
).all(...pkgFilePaths);
|
|
8871
9205
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
@@ -8897,7 +9231,7 @@ var IndexStore = class _IndexStore {
|
|
|
8897
9231
|
ensureFileNode(file);
|
|
8898
9232
|
}
|
|
8899
9233
|
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
8900
|
-
const refRows = this.
|
|
9234
|
+
const refRows = this.stmt(
|
|
8901
9235
|
`SELECT r.from_id, r.to_id, r.call_type
|
|
8902
9236
|
FROM refs r
|
|
8903
9237
|
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
@@ -8912,7 +9246,7 @@ var IndexStore = class _IndexStore {
|
|
|
8912
9246
|
}
|
|
8913
9247
|
if (crossRefIds.size > 0) {
|
|
8914
9248
|
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
8915
|
-
const extras = this.
|
|
9249
|
+
const extras = this.stmt(
|
|
8916
9250
|
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
8917
9251
|
).all(...crossRefIds);
|
|
8918
9252
|
for (const x of extras) {
|
|
@@ -8940,7 +9274,7 @@ var IndexStore = class _IndexStore {
|
|
|
8940
9274
|
e.weight++;
|
|
8941
9275
|
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
8942
9276
|
}
|
|
8943
|
-
const importRows = this.
|
|
9277
|
+
const importRows = this.stmt(
|
|
8944
9278
|
`SELECT r.from_id, r.to_name
|
|
8945
9279
|
FROM refs r
|
|
8946
9280
|
WHERE r.call_type = 'import'
|
|
@@ -8987,7 +9321,7 @@ var IndexStore = class _IndexStore {
|
|
|
8987
9321
|
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
8988
9322
|
*/
|
|
8989
9323
|
getSymbolGraph(fileFilter) {
|
|
8990
|
-
const syms = this.
|
|
9324
|
+
const syms = this.stmt(
|
|
8991
9325
|
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
8992
9326
|
).all(fileFilter);
|
|
8993
9327
|
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
@@ -9008,11 +9342,16 @@ var IndexStore = class _IndexStore {
|
|
|
9008
9342
|
external: s.file !== fileFilter
|
|
9009
9343
|
});
|
|
9010
9344
|
const symIds = new Set(syms.map((s) => s.id));
|
|
9011
|
-
const refRows = this.
|
|
9345
|
+
const refRows = this.stmt(
|
|
9012
9346
|
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9013
9347
|
FROM refs r
|
|
9014
|
-
|
|
9015
|
-
|
|
9348
|
+
JOIN symbols s ON s.id = r.from_id
|
|
9349
|
+
WHERE s.file = ?
|
|
9350
|
+
UNION
|
|
9351
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9352
|
+
FROM refs r
|
|
9353
|
+
JOIN symbols s ON s.id = r.to_id
|
|
9354
|
+
WHERE s.file = ?`
|
|
9016
9355
|
).all(fileFilter, fileFilter);
|
|
9017
9356
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
9018
9357
|
for (const r of refRows) {
|
|
@@ -9051,7 +9390,7 @@ var IndexStore = class _IndexStore {
|
|
|
9051
9390
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
9052
9391
|
if (missingIds.length > 0) {
|
|
9053
9392
|
const placeholders = missingIds.map(() => "?").join(",");
|
|
9054
|
-
const extras = this.
|
|
9393
|
+
const extras = this.stmt(
|
|
9055
9394
|
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
9056
9395
|
).all(...missingIds);
|
|
9057
9396
|
for (const s of extras) symById.set(s.id, s);
|
|
@@ -9065,6 +9404,8 @@ var IndexStore = class _IndexStore {
|
|
|
9065
9404
|
}
|
|
9066
9405
|
close() {
|
|
9067
9406
|
this.stmtCache.clear();
|
|
9407
|
+
this.bm25Dirty = true;
|
|
9408
|
+
this.bm25Cache = null;
|
|
9068
9409
|
try {
|
|
9069
9410
|
this.db.close();
|
|
9070
9411
|
} catch {
|
|
@@ -9078,14 +9419,118 @@ import { fileURLToPath } from "node:url";
|
|
|
9078
9419
|
import { Worker } from "node:worker_threads";
|
|
9079
9420
|
|
|
9080
9421
|
// src/codebase-index/indexer.ts
|
|
9081
|
-
import { expectDefined as expectDefined6 } from "@wrongstack/core";
|
|
9422
|
+
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
9082
9423
|
import * as fs14 from "node:fs/promises";
|
|
9083
|
-
import * as
|
|
9424
|
+
import * as path20 from "node:path";
|
|
9084
9425
|
import { availableParallelism } from "node:os";
|
|
9085
|
-
import {
|
|
9426
|
+
import { indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
|
|
9086
9427
|
|
|
9087
9428
|
// src/codebase-index/ts-parser.ts
|
|
9088
9429
|
import * as ts from "@typescript/typescript6";
|
|
9430
|
+
|
|
9431
|
+
// src/codebase-index/languages.ts
|
|
9432
|
+
import * as path14 from "node:path";
|
|
9433
|
+
var EXT_TO_LANG = {
|
|
9434
|
+
// TypeScript / JavaScript (first-class TS compiler API)
|
|
9435
|
+
".ts": "ts",
|
|
9436
|
+
".mts": "ts",
|
|
9437
|
+
".cts": "ts",
|
|
9438
|
+
".tsx": "tsx",
|
|
9439
|
+
".js": "js",
|
|
9440
|
+
".mjs": "js",
|
|
9441
|
+
".cjs": "js",
|
|
9442
|
+
".jsx": "jsx",
|
|
9443
|
+
// First-class native/spawn parsers (+ regex fallback)
|
|
9444
|
+
".go": "go",
|
|
9445
|
+
".py": "py",
|
|
9446
|
+
".pyi": "py",
|
|
9447
|
+
".pyw": "py",
|
|
9448
|
+
".rs": "rs",
|
|
9449
|
+
".json": "json",
|
|
9450
|
+
".jsonc": "json",
|
|
9451
|
+
".yaml": "yaml",
|
|
9452
|
+
".yml": "yaml",
|
|
9453
|
+
// C family
|
|
9454
|
+
".c": "c",
|
|
9455
|
+
".h": "c",
|
|
9456
|
+
".cc": "cpp",
|
|
9457
|
+
".cpp": "cpp",
|
|
9458
|
+
".cxx": "cpp",
|
|
9459
|
+
".hh": "cpp",
|
|
9460
|
+
".hpp": "cpp",
|
|
9461
|
+
".hxx": "cpp",
|
|
9462
|
+
// JVM / .NET
|
|
9463
|
+
".java": "java",
|
|
9464
|
+
".cs": "csharp",
|
|
9465
|
+
".kt": "kotlin",
|
|
9466
|
+
".kts": "kotlin",
|
|
9467
|
+
".scala": "scala",
|
|
9468
|
+
".sc": "scala",
|
|
9469
|
+
// Scripting
|
|
9470
|
+
".php": "php",
|
|
9471
|
+
".rb": "ruby",
|
|
9472
|
+
".swift": "swift",
|
|
9473
|
+
".dart": "dart",
|
|
9474
|
+
".lua": "lua",
|
|
9475
|
+
".r": "r",
|
|
9476
|
+
".R": "r",
|
|
9477
|
+
".pl": "other",
|
|
9478
|
+
".pm": "other",
|
|
9479
|
+
// Systems / functional
|
|
9480
|
+
".zig": "zig",
|
|
9481
|
+
".ex": "elixir",
|
|
9482
|
+
".exs": "elixir",
|
|
9483
|
+
".hs": "haskell",
|
|
9484
|
+
".lhs": "haskell",
|
|
9485
|
+
// Shell / data / docs / web
|
|
9486
|
+
".sh": "shell",
|
|
9487
|
+
".bash": "shell",
|
|
9488
|
+
".zsh": "shell",
|
|
9489
|
+
".ps1": "shell",
|
|
9490
|
+
".sql": "sql",
|
|
9491
|
+
".md": "md",
|
|
9492
|
+
".mdx": "md",
|
|
9493
|
+
".toml": "toml",
|
|
9494
|
+
".html": "html",
|
|
9495
|
+
".htm": "html",
|
|
9496
|
+
".css": "css",
|
|
9497
|
+
".scss": "css",
|
|
9498
|
+
".less": "css",
|
|
9499
|
+
".vue": "vue",
|
|
9500
|
+
".svelte": "svelte",
|
|
9501
|
+
".proto": "proto",
|
|
9502
|
+
".graphql": "graphql",
|
|
9503
|
+
".gql": "graphql"
|
|
9504
|
+
};
|
|
9505
|
+
var INDEXABLE_EXTENSIONS = Object.freeze(
|
|
9506
|
+
[...new Set(Object.keys(EXT_TO_LANG).map((e) => e.toLowerCase()))].sort()
|
|
9507
|
+
);
|
|
9508
|
+
var SPECIAL_FILENAMES = {
|
|
9509
|
+
makefile: "other",
|
|
9510
|
+
gnumakefile: "other",
|
|
9511
|
+
dockerfile: "other",
|
|
9512
|
+
"docker-compose.yml": "yaml",
|
|
9513
|
+
"docker-compose.yaml": "yaml",
|
|
9514
|
+
"cmakelists.txt": "other",
|
|
9515
|
+
gemfile: "ruby",
|
|
9516
|
+
rakefile: "ruby",
|
|
9517
|
+
procfile: "other",
|
|
9518
|
+
justfile: "other"
|
|
9519
|
+
};
|
|
9520
|
+
function detectLang(file) {
|
|
9521
|
+
const base = path14.basename(file);
|
|
9522
|
+
const lowerBase = base.toLowerCase();
|
|
9523
|
+
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
9524
|
+
return "ts";
|
|
9525
|
+
}
|
|
9526
|
+
const special = SPECIAL_FILENAMES[lowerBase];
|
|
9527
|
+
if (special) return special;
|
|
9528
|
+
const ext = path14.extname(base).toLowerCase();
|
|
9529
|
+
if (!ext) return null;
|
|
9530
|
+
return EXT_TO_LANG[ext] ?? null;
|
|
9531
|
+
}
|
|
9532
|
+
|
|
9533
|
+
// src/codebase-index/ts-parser.ts
|
|
9089
9534
|
var KIND_MAP = {
|
|
9090
9535
|
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
9091
9536
|
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
@@ -9112,32 +9557,6 @@ function kindOf(node) {
|
|
|
9112
9557
|
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
9113
9558
|
return KIND_MAP[node.kind] ?? null;
|
|
9114
9559
|
}
|
|
9115
|
-
function extToLang(ext) {
|
|
9116
|
-
switch (ext) {
|
|
9117
|
-
case ".ts":
|
|
9118
|
-
return "ts";
|
|
9119
|
-
case ".tsx":
|
|
9120
|
-
return "tsx";
|
|
9121
|
-
case ".js":
|
|
9122
|
-
return "js";
|
|
9123
|
-
case ".jsx":
|
|
9124
|
-
return "jsx";
|
|
9125
|
-
case ".go":
|
|
9126
|
-
return "go";
|
|
9127
|
-
case ".py":
|
|
9128
|
-
return "py";
|
|
9129
|
-
case ".rs":
|
|
9130
|
-
return "rs";
|
|
9131
|
-
case ".json":
|
|
9132
|
-
return "json";
|
|
9133
|
-
case ".yaml":
|
|
9134
|
-
return "yaml";
|
|
9135
|
-
case ".yml":
|
|
9136
|
-
return "yaml";
|
|
9137
|
-
default:
|
|
9138
|
-
return null;
|
|
9139
|
-
}
|
|
9140
|
-
}
|
|
9141
9560
|
function getSignature(printer, node, sourceFile) {
|
|
9142
9561
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
9143
9562
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
@@ -9260,22 +9679,30 @@ function deduplicateRefs(refs) {
|
|
|
9260
9679
|
return true;
|
|
9261
9680
|
});
|
|
9262
9681
|
}
|
|
9263
|
-
function detectLang(file) {
|
|
9264
|
-
const idx = file.lastIndexOf(".");
|
|
9265
|
-
if (idx < 0) return null;
|
|
9266
|
-
return extToLang(file.slice(idx));
|
|
9267
|
-
}
|
|
9268
9682
|
|
|
9269
9683
|
// src/codebase-index/go-parser.ts
|
|
9270
9684
|
init_win32_resolve();
|
|
9271
9685
|
import { spawn as spawn4 } from "node:child_process";
|
|
9272
9686
|
import * as os5 from "node:os";
|
|
9273
|
-
import * as
|
|
9687
|
+
import * as path15 from "node:path";
|
|
9274
9688
|
import * as fs10 from "node:fs/promises";
|
|
9689
|
+
|
|
9690
|
+
// src/codebase-index/spawn-gate.ts
|
|
9691
|
+
var chain = Promise.resolve();
|
|
9692
|
+
function withSpawnGate(fn) {
|
|
9693
|
+
const run = chain.then(fn, fn);
|
|
9694
|
+
chain = run.then(
|
|
9695
|
+
() => void 0,
|
|
9696
|
+
() => void 0
|
|
9697
|
+
);
|
|
9698
|
+
return run;
|
|
9699
|
+
}
|
|
9700
|
+
|
|
9701
|
+
// src/codebase-index/go-parser.ts
|
|
9275
9702
|
async function parseSymbols2(opts) {
|
|
9276
9703
|
const { file, content, lang } = opts;
|
|
9277
9704
|
try {
|
|
9278
|
-
const parsed = await syncGoParse(file, content, lang);
|
|
9705
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
9279
9706
|
if (parsed.symbols.length > 0) {
|
|
9280
9707
|
return parsed;
|
|
9281
9708
|
}
|
|
@@ -9571,11 +9998,16 @@ func formatType(t ast.Expr) string {
|
|
|
9571
9998
|
}
|
|
9572
9999
|
}
|
|
9573
10000
|
`;
|
|
10001
|
+
var _cachedGoScriptPath = null;
|
|
9574
10002
|
async function syncGoParse(filePath, content, lang) {
|
|
9575
|
-
const tmpDir = await fs10.mkdtemp(path14.join(os5.tmpdir(), "ws-go-parse-"));
|
|
9576
10003
|
try {
|
|
9577
|
-
|
|
9578
|
-
|
|
10004
|
+
let scriptPath = _cachedGoScriptPath;
|
|
10005
|
+
if (!scriptPath) {
|
|
10006
|
+
const tmpDir = await fs10.mkdtemp(path15.join(os5.tmpdir(), "ws-go-parse-"));
|
|
10007
|
+
scriptPath = path15.join(tmpDir, "parse.go");
|
|
10008
|
+
await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
10009
|
+
_cachedGoScriptPath = scriptPath;
|
|
10010
|
+
}
|
|
9579
10011
|
const goBinary = resolveWin32Command("go");
|
|
9580
10012
|
const goResult = await new Promise(
|
|
9581
10013
|
(resolve14, reject) => {
|
|
@@ -9632,24 +10064,313 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9632
10064
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
9633
10065
|
} catch {
|
|
9634
10066
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9635
|
-
} finally {
|
|
9636
|
-
await fs10.rm(tmpDir, { recursive: true, force: true });
|
|
9637
10067
|
}
|
|
9638
10068
|
}
|
|
9639
10069
|
|
|
9640
10070
|
// src/codebase-index/py-parser.ts
|
|
9641
10071
|
init_win32_resolve();
|
|
9642
|
-
import { spawn as spawn5
|
|
10072
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
9643
10073
|
import * as fs11 from "node:fs/promises";
|
|
9644
10074
|
import * as os6 from "node:os";
|
|
9645
|
-
import * as
|
|
10075
|
+
import * as path16 from "node:path";
|
|
10076
|
+
|
|
10077
|
+
// src/codebase-index/generic-parser.ts
|
|
10078
|
+
var C_LIKE = [
|
|
10079
|
+
{ re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10080
|
+
{
|
|
10081
|
+
re: /\b(?:public|private|protected|static|final|async|override|virtual|inline|export)?\s*(?:[\w:<>\[\]\s\*&]+)\s+([A-Za-z_]\w*)\s*\([^;{]*\)\s*(?:const)?\s*[{;]/g,
|
|
10082
|
+
kind: "function"
|
|
10083
|
+
},
|
|
10084
|
+
{ re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
|
|
10085
|
+
];
|
|
10086
|
+
var LANG_PATTERNS = {
|
|
10087
|
+
py: [
|
|
10088
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10089
|
+
{ re: /^class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10090
|
+
{ re: /^([A-Za-z_]\w*)\s*=/gm, kind: "var" }
|
|
10091
|
+
],
|
|
10092
|
+
go: [
|
|
10093
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/gm, kind: "function" },
|
|
10094
|
+
{ re: /^type\s+([A-Za-z_]\w*)\b/gm, kind: "type" },
|
|
10095
|
+
{ re: /^(?:const|var)\s+([A-Za-z_]\w*)\b/gm, kind: "const" },
|
|
10096
|
+
{ re: /^package\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10097
|
+
],
|
|
10098
|
+
rs: [
|
|
10099
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10100
|
+
{ re: /\bstruct\s+([A-Za-z_]\w*)/g, kind: "struct" },
|
|
10101
|
+
{ re: /\benum\s+([A-Za-z_]\w*)/g, kind: "enum" },
|
|
10102
|
+
{ re: /\btrait\s+([A-Za-z_]\w*)/g, kind: "trait" },
|
|
10103
|
+
{ re: /\bimpl(?:\s*<[^>]+>)?\s+([A-Za-z_]\w*)/g, kind: "impl" },
|
|
10104
|
+
{ re: /\b(?:const|static)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10105
|
+
{ re: /\bmod\s+([A-Za-z_]\w*)/g, kind: "mod" }
|
|
10106
|
+
],
|
|
10107
|
+
c: C_LIKE,
|
|
10108
|
+
cpp: C_LIKE,
|
|
10109
|
+
java: [
|
|
10110
|
+
{ re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10111
|
+
{
|
|
10112
|
+
re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10113
|
+
kind: "method"
|
|
10114
|
+
}
|
|
10115
|
+
],
|
|
10116
|
+
csharp: [
|
|
10117
|
+
{ re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10118
|
+
{ re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
|
|
10119
|
+
{
|
|
10120
|
+
re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10121
|
+
kind: "method"
|
|
10122
|
+
}
|
|
10123
|
+
],
|
|
10124
|
+
php: [
|
|
10125
|
+
{ re: /\bfunction\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10126
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10127
|
+
{ re: /\binterface\s+([A-Za-z_]\w*)/g, kind: "interface" },
|
|
10128
|
+
{ re: /\bnamespace\s+([A-Za-z_\\]+)/g, kind: "namespace" }
|
|
10129
|
+
],
|
|
10130
|
+
ruby: [
|
|
10131
|
+
{ re: /^\s*def\s+(?:self\.)?([A-Za-z_]\w*[!?]?)/gm, kind: "function" },
|
|
10132
|
+
{ re: /^\s*class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10133
|
+
{ re: /^\s*module\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10134
|
+
],
|
|
10135
|
+
swift: [
|
|
10136
|
+
{ re: /\b(?:func)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10137
|
+
{ re: /\b(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10138
|
+
],
|
|
10139
|
+
kotlin: [
|
|
10140
|
+
{ re: /\b(?:fun)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10141
|
+
{ re: /\b(?:class|interface|object|enum\s+class|data\s+class)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10142
|
+
],
|
|
10143
|
+
scala: [
|
|
10144
|
+
{ re: /\b(?:def)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10145
|
+
{ re: /\b(?:class|object|trait|enum)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10146
|
+
],
|
|
10147
|
+
shell: [
|
|
10148
|
+
{ re: /^(?:function\s+)?([A-Za-z_]\w*)\s*\(\)\s*\{/gm, kind: "function" },
|
|
10149
|
+
{ re: /^([A-Za-z_][\w]*)\s*\(\)\s*\{/gm, kind: "function" }
|
|
10150
|
+
],
|
|
10151
|
+
sql: [
|
|
10152
|
+
{ re: /\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:TABLE|VIEW|INDEX|FUNCTION|PROCEDURE|TRIGGER)\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z_"][\w."]*)/gi, kind: "type" }
|
|
10153
|
+
],
|
|
10154
|
+
md: [
|
|
10155
|
+
{ re: /^(#{1,6})\s+(.+)$/gm, kind: "namespace" }
|
|
10156
|
+
],
|
|
10157
|
+
toml: [
|
|
10158
|
+
{ re: /^\[([^\]]+)\]/gm, kind: "namespace" }
|
|
10159
|
+
],
|
|
10160
|
+
html: [
|
|
10161
|
+
{ re: /\bid\s*=\s*["']([^"']+)["']/gi, kind: "property" },
|
|
10162
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10163
|
+
],
|
|
10164
|
+
css: [
|
|
10165
|
+
{ re: /^\s*([.#]?[A-Za-z_][\w-]*)\s*\{/gm, kind: "type" },
|
|
10166
|
+
{ re: /@(?:keyframes|media|supports)\s+([^{\s]+)/g, kind: "namespace" }
|
|
10167
|
+
],
|
|
10168
|
+
vue: [
|
|
10169
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10170
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10171
|
+
],
|
|
10172
|
+
svelte: [
|
|
10173
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10174
|
+
],
|
|
10175
|
+
dart: [
|
|
10176
|
+
{ re: /\b(?:class|enum|mixin|extension)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10177
|
+
{ re: /\b([A-Za-z_]\w*)\s*\([^;]*\)\s*(?:async\s*)?\{/g, kind: "function" }
|
|
10178
|
+
],
|
|
10179
|
+
lua: [
|
|
10180
|
+
{ re: /\bfunction\s+([A-Za-z_.:]\w*)/g, kind: "function" },
|
|
10181
|
+
{ re: /\blocal\s+function\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10182
|
+
],
|
|
10183
|
+
r: [
|
|
10184
|
+
{ re: /([A-Za-z.]\w*)\s*<-\s*function\s*\(/g, kind: "function" },
|
|
10185
|
+
{ re: /([A-Za-z.]\w*)\s*=\s*function\s*\(/g, kind: "function" }
|
|
10186
|
+
],
|
|
10187
|
+
proto: [
|
|
10188
|
+
{ re: /\b(?:message|service|enum)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10189
|
+
{ re: /\brpc\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10190
|
+
],
|
|
10191
|
+
graphql: [
|
|
10192
|
+
{ re: /\b(?:type|interface|enum|input|union|scalar)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10193
|
+
{ re: /\b(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10194
|
+
],
|
|
10195
|
+
zig: [
|
|
10196
|
+
{ re: /\b(?:fn|pub\s+fn)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10197
|
+
{ re: /\b(?:const|var)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10198
|
+
{ re: /\b(?:struct|enum|union)\s*\{/g, kind: "type" }
|
|
10199
|
+
],
|
|
10200
|
+
elixir: [
|
|
10201
|
+
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
10202
|
+
{ re: /\bdefmodule\s+([A-Za-z_.]\w*)/g, kind: "namespace" }
|
|
10203
|
+
],
|
|
10204
|
+
haskell: [
|
|
10205
|
+
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
10206
|
+
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10207
|
+
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
10208
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10209
|
+
],
|
|
10210
|
+
other: [
|
|
10211
|
+
{ re: /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10212
|
+
{ re: /^(?:export\s+)?class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10213
|
+
{ re: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)/gm, kind: "const" },
|
|
10214
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10215
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10216
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10217
|
+
{ re: /^(?:target|PHONY)\s*:/gm, kind: "namespace" }
|
|
10218
|
+
]
|
|
10219
|
+
};
|
|
10220
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
10221
|
+
"if",
|
|
10222
|
+
"else",
|
|
10223
|
+
"for",
|
|
10224
|
+
"while",
|
|
10225
|
+
"switch",
|
|
10226
|
+
"case",
|
|
10227
|
+
"return",
|
|
10228
|
+
"break",
|
|
10229
|
+
"continue",
|
|
10230
|
+
"new",
|
|
10231
|
+
"delete",
|
|
10232
|
+
"typeof",
|
|
10233
|
+
"instanceof",
|
|
10234
|
+
"void",
|
|
10235
|
+
"null",
|
|
10236
|
+
"true",
|
|
10237
|
+
"false",
|
|
10238
|
+
"this",
|
|
10239
|
+
"super",
|
|
10240
|
+
"import",
|
|
10241
|
+
"export",
|
|
10242
|
+
"from",
|
|
10243
|
+
"as",
|
|
10244
|
+
"default",
|
|
10245
|
+
"public",
|
|
10246
|
+
"private",
|
|
10247
|
+
"protected",
|
|
10248
|
+
"static",
|
|
10249
|
+
"final",
|
|
10250
|
+
"class",
|
|
10251
|
+
"struct",
|
|
10252
|
+
"enum",
|
|
10253
|
+
"interface",
|
|
10254
|
+
"function",
|
|
10255
|
+
"def",
|
|
10256
|
+
"fn",
|
|
10257
|
+
"func",
|
|
10258
|
+
"var",
|
|
10259
|
+
"let",
|
|
10260
|
+
"const",
|
|
10261
|
+
"package",
|
|
10262
|
+
"namespace",
|
|
10263
|
+
"module",
|
|
10264
|
+
"using",
|
|
10265
|
+
"include",
|
|
10266
|
+
"require",
|
|
10267
|
+
"select",
|
|
10268
|
+
"from",
|
|
10269
|
+
"where",
|
|
10270
|
+
"and",
|
|
10271
|
+
"or",
|
|
10272
|
+
"not",
|
|
10273
|
+
"in",
|
|
10274
|
+
"is",
|
|
10275
|
+
"try",
|
|
10276
|
+
"catch",
|
|
10277
|
+
"finally",
|
|
10278
|
+
"throw",
|
|
10279
|
+
"async",
|
|
10280
|
+
"await",
|
|
10281
|
+
"yield"
|
|
10282
|
+
]);
|
|
10283
|
+
function patternsFor(lang) {
|
|
10284
|
+
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
10285
|
+
}
|
|
10286
|
+
function looksBinary(content) {
|
|
10287
|
+
if (content.includes("\0")) return true;
|
|
10288
|
+
const sample = content.slice(0, 2048);
|
|
10289
|
+
if (sample.length === 0) return false;
|
|
10290
|
+
let bad = 0;
|
|
10291
|
+
for (let i = 0; i < sample.length; i++) {
|
|
10292
|
+
const c = sample.charCodeAt(i);
|
|
10293
|
+
if (c === 9 || c === 10 || c === 13) continue;
|
|
10294
|
+
if (c < 32 || c === 127) bad++;
|
|
10295
|
+
}
|
|
10296
|
+
return bad / sample.length > 0.1;
|
|
10297
|
+
}
|
|
10298
|
+
function lineColAt(content, index) {
|
|
10299
|
+
let line = 1;
|
|
10300
|
+
let lastNl = -1;
|
|
10301
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
10302
|
+
if (content.charCodeAt(i) === 10) {
|
|
10303
|
+
line++;
|
|
10304
|
+
lastNl = i;
|
|
10305
|
+
}
|
|
10306
|
+
}
|
|
10307
|
+
return { line, col: index - lastNl };
|
|
10308
|
+
}
|
|
10309
|
+
var GENERIC_MAX_SYMBOLS_DEFAULT = 500;
|
|
10310
|
+
var GENERIC_MAX_FILE_CHARS = 512 * 1024;
|
|
10311
|
+
function parseGeneric2(opts) {
|
|
10312
|
+
const { file, lang } = opts;
|
|
10313
|
+
const maxSymbols = opts.maxSymbols ?? GENERIC_MAX_SYMBOLS_DEFAULT;
|
|
10314
|
+
const mtimeMs = Date.now();
|
|
10315
|
+
if (!opts.content || looksBinary(opts.content)) {
|
|
10316
|
+
return { file, lang, symbols: [], mtimeMs };
|
|
10317
|
+
}
|
|
10318
|
+
const content = opts.content.length > GENERIC_MAX_FILE_CHARS ? opts.content.slice(0, GENERIC_MAX_FILE_CHARS) : opts.content;
|
|
10319
|
+
const patterns = patternsFor(lang);
|
|
10320
|
+
const symbols = [];
|
|
10321
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10322
|
+
for (const pattern of patterns) {
|
|
10323
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
10324
|
+
re.lastIndex = 0;
|
|
10325
|
+
let match;
|
|
10326
|
+
while ((match = re.exec(content)) !== null) {
|
|
10327
|
+
if (symbols.length >= maxSymbols) break;
|
|
10328
|
+
let name = (match[1] ?? match[2] ?? "").trim();
|
|
10329
|
+
if (lang === "md" && match[2]) name = match[2].trim();
|
|
10330
|
+
if (!name || name.length > 200) continue;
|
|
10331
|
+
name = name.replace(/^#+\s*/, "").replace(/["'`]/g, "");
|
|
10332
|
+
if (!name || KEYWORDS.has(name.toLowerCase())) continue;
|
|
10333
|
+
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
10334
|
+
continue;
|
|
10335
|
+
}
|
|
10336
|
+
const { line, col } = lineColAt(content, match.index);
|
|
10337
|
+
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
10338
|
+
if (seen.has(key)) continue;
|
|
10339
|
+
seen.add(key);
|
|
10340
|
+
const nl = content.indexOf("\n", match.index);
|
|
10341
|
+
const lineText = content.slice(match.index, nl === -1 ? content.length : nl);
|
|
10342
|
+
const signature = (lineText || name).trim().slice(0, 500);
|
|
10343
|
+
symbols.push({
|
|
10344
|
+
id: 0,
|
|
10345
|
+
lang,
|
|
10346
|
+
kind: lang === "md" ? "namespace" : pattern.kind,
|
|
10347
|
+
name: name.slice(0, 200),
|
|
10348
|
+
file,
|
|
10349
|
+
line,
|
|
10350
|
+
col,
|
|
10351
|
+
signature,
|
|
10352
|
+
docComment: "",
|
|
10353
|
+
scope: "",
|
|
10354
|
+
text: `${name} ${signature}`.trim().slice(0, 1e3)
|
|
10355
|
+
});
|
|
10356
|
+
}
|
|
10357
|
+
if (symbols.length >= maxSymbols) break;
|
|
10358
|
+
}
|
|
10359
|
+
return { file, lang, symbols, mtimeMs };
|
|
10360
|
+
}
|
|
9646
10361
|
async function parseSymbols3(opts) {
|
|
10362
|
+
return parseGeneric2(opts);
|
|
10363
|
+
}
|
|
10364
|
+
|
|
10365
|
+
// src/codebase-index/py-parser.ts
|
|
10366
|
+
async function parseSymbols4(opts) {
|
|
9647
10367
|
const { file, content, lang } = opts;
|
|
9648
10368
|
try {
|
|
9649
|
-
|
|
10369
|
+
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
10370
|
+
if (native !== null) return native;
|
|
9650
10371
|
} catch {
|
|
9651
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
9652
10372
|
}
|
|
10373
|
+
return parseGeneric2({ file, content, lang: lang === "py" ? "py" : lang });
|
|
9653
10374
|
}
|
|
9654
10375
|
var PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
9655
10376
|
|
|
@@ -9854,20 +10575,37 @@ visitor.visit(tree)
|
|
|
9854
10575
|
|
|
9855
10576
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
9856
10577
|
`;
|
|
9857
|
-
function resolvePython() {
|
|
10578
|
+
async function resolvePython() {
|
|
9858
10579
|
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
9859
10580
|
for (const name of candidates) {
|
|
9860
10581
|
const resolved = resolveWin32Command(name);
|
|
9861
|
-
|
|
9862
|
-
stdio: "pipe",
|
|
9863
|
-
timeout: 5e3
|
|
9864
|
-
});
|
|
9865
|
-
if (result.error) continue;
|
|
9866
|
-
if (result.status !== 0) continue;
|
|
10582
|
+
if (!await commandIsAvailable(resolved)) continue;
|
|
9867
10583
|
return resolved;
|
|
9868
10584
|
}
|
|
9869
10585
|
return null;
|
|
9870
10586
|
}
|
|
10587
|
+
function commandIsAvailable(command) {
|
|
10588
|
+
return new Promise((resolve14) => {
|
|
10589
|
+
let settled = false;
|
|
10590
|
+
const proc = spawn5(command, ["--version"], {
|
|
10591
|
+
stdio: "ignore",
|
|
10592
|
+
windowsHide: true
|
|
10593
|
+
});
|
|
10594
|
+
const finish = (available) => {
|
|
10595
|
+
if (settled) return;
|
|
10596
|
+
settled = true;
|
|
10597
|
+
clearTimeout(timer);
|
|
10598
|
+
resolve14(available);
|
|
10599
|
+
};
|
|
10600
|
+
const timer = setTimeout(() => {
|
|
10601
|
+
proc.kill("SIGKILL");
|
|
10602
|
+
finish(false);
|
|
10603
|
+
}, 5e3);
|
|
10604
|
+
timer.unref?.();
|
|
10605
|
+
proc.once("error", () => finish(false));
|
|
10606
|
+
proc.once("close", (code) => finish(code === 0));
|
|
10607
|
+
});
|
|
10608
|
+
}
|
|
9871
10609
|
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
9872
10610
|
return new Promise((resolve14, reject) => {
|
|
9873
10611
|
let settled = false;
|
|
@@ -9903,21 +10641,20 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
9903
10641
|
});
|
|
9904
10642
|
}
|
|
9905
10643
|
var _cachedScriptPath = null;
|
|
9906
|
-
var
|
|
10644
|
+
var cachedPyBinary;
|
|
9907
10645
|
async function syncPyParse(filePath, content, lang) {
|
|
9908
10646
|
try {
|
|
9909
10647
|
if (!_cachedScriptPath) {
|
|
9910
|
-
const tmpDir =
|
|
10648
|
+
const tmpDir = path16.join(os6.tmpdir(), "ws-py-parse");
|
|
9911
10649
|
await fs11.mkdir(tmpDir, { recursive: true });
|
|
9912
|
-
_cachedScriptPath =
|
|
10650
|
+
_cachedScriptPath = path16.join(tmpDir, "parse.py");
|
|
9913
10651
|
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
9914
10652
|
}
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
}
|
|
10653
|
+
cachedPyBinary ??= resolvePython();
|
|
10654
|
+
const pyBinary = await cachedPyBinary;
|
|
10655
|
+
if (!pyBinary) return null;
|
|
9919
10656
|
const { code, stdout } = await spawnPyParser(
|
|
9920
|
-
|
|
10657
|
+
pyBinary,
|
|
9921
10658
|
_cachedScriptPath,
|
|
9922
10659
|
filePath,
|
|
9923
10660
|
content
|
|
@@ -9941,31 +10678,40 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
9941
10678
|
}));
|
|
9942
10679
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
9943
10680
|
} catch {
|
|
9944
|
-
return
|
|
10681
|
+
return null;
|
|
9945
10682
|
}
|
|
9946
10683
|
}
|
|
9947
10684
|
|
|
9948
10685
|
// src/codebase-index/rs-parser.ts
|
|
9949
10686
|
init_win32_resolve();
|
|
9950
|
-
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
9951
|
-
import {
|
|
10687
|
+
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
10688
|
+
import { execFile, spawn as spawn6 } from "node:child_process";
|
|
9952
10689
|
import * as fs12 from "node:fs/promises";
|
|
9953
|
-
import * as
|
|
9954
|
-
async function
|
|
10690
|
+
import * as path17 from "node:path";
|
|
10691
|
+
async function parseSymbols5(opts) {
|
|
9955
10692
|
const { file, content, lang } = opts;
|
|
9956
|
-
const nativeAvailable = checkNativeParser();
|
|
10693
|
+
const nativeAvailable = await checkNativeParser();
|
|
9957
10694
|
if (nativeAvailable) {
|
|
9958
|
-
const result = await tryNativeParse(file, content);
|
|
10695
|
+
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
9959
10696
|
if (result) return result;
|
|
9960
10697
|
}
|
|
9961
10698
|
return regexParse({ file, content, lang });
|
|
9962
10699
|
}
|
|
10700
|
+
var nativeParserAvailability;
|
|
10701
|
+
function probe(command, args) {
|
|
10702
|
+
return new Promise((resolve14, reject) => {
|
|
10703
|
+
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
10704
|
+
if (error) reject(error);
|
|
10705
|
+
else resolve14();
|
|
10706
|
+
});
|
|
10707
|
+
});
|
|
10708
|
+
}
|
|
9963
10709
|
function checkNativeParser() {
|
|
9964
|
-
|
|
9965
|
-
execFileSync("rustc", ["--version"], { stdio: "pipe", windowsHide: true });
|
|
9966
|
-
const toolsDir = path16.join(process.cwd(), "tools");
|
|
10710
|
+
nativeParserAvailability ??= (async () => {
|
|
9967
10711
|
try {
|
|
9968
|
-
|
|
10712
|
+
await probe("rustc", ["--version"]);
|
|
10713
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
10714
|
+
await probe(
|
|
9969
10715
|
"cargo",
|
|
9970
10716
|
[
|
|
9971
10717
|
"metadata",
|
|
@@ -9973,23 +10719,21 @@ function checkNativeParser() {
|
|
|
9973
10719
|
"--format-version",
|
|
9974
10720
|
"1",
|
|
9975
10721
|
"--manifest-path",
|
|
9976
|
-
|
|
9977
|
-
]
|
|
9978
|
-
{ stdio: "pipe", windowsHide: true }
|
|
10722
|
+
path17.join(toolsDir, "Cargo.toml")
|
|
10723
|
+
]
|
|
9979
10724
|
);
|
|
9980
10725
|
return true;
|
|
9981
10726
|
} catch {
|
|
9982
10727
|
return false;
|
|
9983
10728
|
}
|
|
9984
|
-
}
|
|
9985
|
-
|
|
9986
|
-
}
|
|
10729
|
+
})();
|
|
10730
|
+
return nativeParserAvailability;
|
|
9987
10731
|
}
|
|
9988
10732
|
async function tryNativeParse(file, content) {
|
|
9989
10733
|
try {
|
|
9990
|
-
const toolsDir =
|
|
9991
|
-
const crateDir =
|
|
9992
|
-
const tmpFile =
|
|
10734
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
10735
|
+
const crateDir = path17.join(toolsDir, "syn-parser");
|
|
10736
|
+
const tmpFile = path17.join(crateDir, "src", "input.rs");
|
|
9993
10737
|
await fs12.writeFile(tmpFile, content, "utf8");
|
|
9994
10738
|
const cargoBinary = resolveWin32Command("cargo");
|
|
9995
10739
|
const result = await new Promise(
|
|
@@ -9997,7 +10741,7 @@ async function tryNativeParse(file, content) {
|
|
|
9997
10741
|
let settled = false;
|
|
9998
10742
|
const proc = spawn6(
|
|
9999
10743
|
cargoBinary,
|
|
10000
|
-
["run", "--manifest-path",
|
|
10744
|
+
["run", "--manifest-path", path17.join(toolsDir, "Cargo.toml")],
|
|
10001
10745
|
{
|
|
10002
10746
|
cwd: process.cwd(),
|
|
10003
10747
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -10111,9 +10855,9 @@ function regexParse(opts) {
|
|
|
10111
10855
|
}
|
|
10112
10856
|
|
|
10113
10857
|
// src/codebase-index/json-parser.ts
|
|
10114
|
-
import { expectDefined as expectDefined4 } from "@wrongstack/core";
|
|
10115
|
-
import * as
|
|
10116
|
-
function
|
|
10858
|
+
import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
|
|
10859
|
+
import * as path18 from "node:path";
|
|
10860
|
+
function parseSymbols6(opts) {
|
|
10117
10861
|
const { file, content, lang } = opts;
|
|
10118
10862
|
try {
|
|
10119
10863
|
return regexParse2({ file, content, lang });
|
|
@@ -10124,9 +10868,9 @@ function parseSymbols5(opts) {
|
|
|
10124
10868
|
function regexParse2(opts) {
|
|
10125
10869
|
const { file, content, lang } = opts;
|
|
10126
10870
|
const symbols = [];
|
|
10127
|
-
const
|
|
10128
|
-
const isPackageJson =
|
|
10129
|
-
const isTsconfig =
|
|
10871
|
+
const basename11 = path18.basename(file).toLowerCase();
|
|
10872
|
+
const isPackageJson = basename11 === "package.json";
|
|
10873
|
+
const isTsconfig = basename11 === "tsconfig.json" || basename11 === "tsconfig.build.json";
|
|
10130
10874
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
10131
10875
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
10132
10876
|
const lines = content.split("\n");
|
|
@@ -10150,11 +10894,11 @@ function regexParse2(opts) {
|
|
|
10150
10894
|
const line = lineFromOffset(offset);
|
|
10151
10895
|
symbols.push(
|
|
10152
10896
|
makeSymbol({
|
|
10153
|
-
name:
|
|
10897
|
+
name: path18.basename(file),
|
|
10154
10898
|
kind: "object",
|
|
10155
10899
|
line,
|
|
10156
10900
|
col: 0,
|
|
10157
|
-
signature: `"${
|
|
10901
|
+
signature: `"${path18.basename(file)}" = { ... }`,
|
|
10158
10902
|
file,
|
|
10159
10903
|
lang
|
|
10160
10904
|
})
|
|
@@ -10316,8 +11060,8 @@ function makeSymbol(opts) {
|
|
|
10316
11060
|
}
|
|
10317
11061
|
|
|
10318
11062
|
// src/codebase-index/yaml-parser.ts
|
|
10319
|
-
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core";
|
|
10320
|
-
function
|
|
11063
|
+
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core/utils";
|
|
11064
|
+
function parseSymbols7(opts) {
|
|
10321
11065
|
const { file, content, lang } = opts;
|
|
10322
11066
|
try {
|
|
10323
11067
|
return regexParse3({ file, content, lang });
|
|
@@ -10466,8 +11210,8 @@ function makeSymbol2(opts) {
|
|
|
10466
11210
|
|
|
10467
11211
|
// src/codebase-index/gitignore.ts
|
|
10468
11212
|
import * as fs13 from "node:fs/promises";
|
|
10469
|
-
import * as
|
|
10470
|
-
import { compileGlob } from "@wrongstack/core";
|
|
11213
|
+
import * as path19 from "node:path";
|
|
11214
|
+
import { compileGlob } from "@wrongstack/core/utils";
|
|
10471
11215
|
function globBody(glob) {
|
|
10472
11216
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
10473
11217
|
}
|
|
@@ -10512,7 +11256,7 @@ function compileGitignore(lines) {
|
|
|
10512
11256
|
async function loadGitignoreMatcher(projectRoot) {
|
|
10513
11257
|
let lines = [];
|
|
10514
11258
|
try {
|
|
10515
|
-
const raw = await fs13.readFile(
|
|
11259
|
+
const raw = await fs13.readFile(path19.join(projectRoot, ".gitignore"), "utf8");
|
|
10516
11260
|
lines = raw.split("\n");
|
|
10517
11261
|
} catch {
|
|
10518
11262
|
}
|
|
@@ -10521,7 +11265,9 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
10521
11265
|
|
|
10522
11266
|
// src/codebase-index/indexer.ts
|
|
10523
11267
|
var YIELD_EVERY_N = 50;
|
|
10524
|
-
|
|
11268
|
+
function resolveParallelBatch() {
|
|
11269
|
+
return indexParallelBatchSize(availableParallelism());
|
|
11270
|
+
}
|
|
10525
11271
|
function yieldEventLoop() {
|
|
10526
11272
|
return new Promise((resolve14) => setImmediate(resolve14));
|
|
10527
11273
|
}
|
|
@@ -10547,8 +11293,8 @@ var DEFAULT_IGNORE = [
|
|
|
10547
11293
|
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10548
11294
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10549
11295
|
function isWithinProject(projectRoot, file) {
|
|
10550
|
-
const rel =
|
|
10551
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
11296
|
+
const rel = path20.relative(projectRoot, file);
|
|
11297
|
+
return rel !== "" && !rel.startsWith(`..${path20.sep}`) && rel !== ".." && !path20.isAbsolute(rel);
|
|
10552
11298
|
}
|
|
10553
11299
|
function isMissingPathError(err) {
|
|
10554
11300
|
const code = err?.code;
|
|
@@ -10559,18 +11305,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10559
11305
|
const errors = [];
|
|
10560
11306
|
let complete = true;
|
|
10561
11307
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
10562
|
-
const
|
|
10563
|
-
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
10564
|
-
{ ext: ".tsx", pat: compileGlob2("**/*.tsx") },
|
|
10565
|
-
{ ext: ".js", pat: compileGlob2("**/*.js") },
|
|
10566
|
-
{ ext: ".jsx", pat: compileGlob2("**/*.jsx") },
|
|
10567
|
-
{ ext: ".go", pat: compileGlob2("**/*.go") },
|
|
10568
|
-
{ ext: ".py", pat: compileGlob2("**/*.py") },
|
|
10569
|
-
{ ext: ".rs", pat: compileGlob2("**/*.rs") },
|
|
10570
|
-
{ ext: ".json", pat: compileGlob2("**/*.json") },
|
|
10571
|
-
{ ext: ".yaml", pat: compileGlob2("**/*.yaml") },
|
|
10572
|
-
{ ext: ".yml", pat: compileGlob2("**/*.yml") }
|
|
10573
|
-
];
|
|
11308
|
+
const indexableExts = new Set(INDEXABLE_EXTENSIONS);
|
|
10574
11309
|
let dirCount = 0;
|
|
10575
11310
|
const walk = async (dir) => {
|
|
10576
11311
|
throwIfAborted(signal);
|
|
@@ -10589,19 +11324,16 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10589
11324
|
dirCount++;
|
|
10590
11325
|
for (const e of entries) {
|
|
10591
11326
|
if (ignoreSet.has(e.name)) continue;
|
|
10592
|
-
const full =
|
|
10593
|
-
const rel =
|
|
11327
|
+
const full = path20.join(dir, e.name);
|
|
11328
|
+
const rel = path20.relative(projectRoot, full).replace(/\\/g, "/");
|
|
10594
11329
|
if (e.isDirectory()) {
|
|
10595
11330
|
if (isGitIgnored(rel, true)) continue;
|
|
10596
11331
|
await walk(full);
|
|
10597
11332
|
} else if (e.isFile()) {
|
|
10598
11333
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10599
|
-
const ext =
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
results.push(full);
|
|
10603
|
-
break;
|
|
10604
|
-
}
|
|
11334
|
+
const ext = path20.extname(e.name).toLowerCase();
|
|
11335
|
+
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
11336
|
+
results.push(full);
|
|
10605
11337
|
}
|
|
10606
11338
|
}
|
|
10607
11339
|
}
|
|
@@ -10610,24 +11342,25 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10610
11342
|
return { files: results, complete, errors };
|
|
10611
11343
|
}
|
|
10612
11344
|
async function parseFile(file, content, lang) {
|
|
10613
|
-
|
|
11345
|
+
const symbolLang = lang;
|
|
11346
|
+
switch (symbolLang) {
|
|
10614
11347
|
case "ts":
|
|
10615
11348
|
case "tsx":
|
|
10616
11349
|
case "js":
|
|
10617
11350
|
case "jsx":
|
|
10618
|
-
return parseSymbols({ file, content, lang });
|
|
11351
|
+
return parseSymbols({ file, content, lang: symbolLang });
|
|
10619
11352
|
case "go":
|
|
10620
11353
|
return parseSymbols2({ file, content, lang: "go" });
|
|
10621
11354
|
case "py":
|
|
10622
|
-
return
|
|
11355
|
+
return parseSymbols4({ file, content, lang: "py" });
|
|
10623
11356
|
case "rs":
|
|
10624
|
-
return
|
|
11357
|
+
return parseSymbols5({ file, content, lang: "rs" });
|
|
10625
11358
|
case "json":
|
|
10626
|
-
return
|
|
11359
|
+
return parseSymbols6({ file, content, lang: "json" });
|
|
10627
11360
|
case "yaml":
|
|
10628
|
-
return
|
|
11361
|
+
return parseSymbols7({ file, content, lang: "yaml" });
|
|
10629
11362
|
default:
|
|
10630
|
-
return { file,
|
|
11363
|
+
return parseSymbols3({ file, content, lang: symbolLang });
|
|
10631
11364
|
}
|
|
10632
11365
|
}
|
|
10633
11366
|
function assignRefsToSymbols2(refs, symbols) {
|
|
@@ -10675,10 +11408,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10675
11408
|
let discoveredFiles = null;
|
|
10676
11409
|
let discoveryComplete = true;
|
|
10677
11410
|
if (opts.files && opts.files.length > 0) {
|
|
10678
|
-
files = opts.files.map((f) =>
|
|
11411
|
+
files = opts.files.map((f) => path20.resolve(projectRoot, f)).filter((f) => {
|
|
10679
11412
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
10680
|
-
const rel =
|
|
10681
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
11413
|
+
const rel = path20.relative(projectRoot, f).replace(/\\/g, "/");
|
|
11414
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path20.basename(f)) && !isGitIgnored(rel, false);
|
|
10682
11415
|
});
|
|
10683
11416
|
} else {
|
|
10684
11417
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -10699,15 +11432,19 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10699
11432
|
if (!force) {
|
|
10700
11433
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
10701
11434
|
}
|
|
11435
|
+
const parallelBatch = resolveParallelBatch();
|
|
10702
11436
|
let filesSinceLastYield = 0;
|
|
10703
|
-
for (let batchStart = 0; batchStart < files.length; batchStart +=
|
|
10704
|
-
const batchEnd = Math.min(batchStart +
|
|
11437
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
|
|
11438
|
+
const batchEnd = Math.min(batchStart + parallelBatch, files.length);
|
|
10705
11439
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
10706
11440
|
opts.onProgress?.(batchEnd, files.length);
|
|
10707
11441
|
filesSinceLastYield += batchFiles.length;
|
|
10708
11442
|
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
10709
11443
|
filesSinceLastYield = 0;
|
|
10710
11444
|
await yieldEventLoop();
|
|
11445
|
+
if (isFrugalPerf()) {
|
|
11446
|
+
await new Promise((r) => setTimeout(r, 8));
|
|
11447
|
+
}
|
|
10711
11448
|
throwIfAborted(signal);
|
|
10712
11449
|
}
|
|
10713
11450
|
const statOpts = signal ? { signal } : {};
|
|
@@ -10811,9 +11548,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10811
11548
|
continue;
|
|
10812
11549
|
}
|
|
10813
11550
|
if (parsed.symbols.length === 0) {
|
|
10814
|
-
store.
|
|
10815
|
-
store.deleteSymbolsForFile(file);
|
|
10816
|
-
store.upsertFile({
|
|
11551
|
+
store.replaceEmptyFile({
|
|
10817
11552
|
file,
|
|
10818
11553
|
lang,
|
|
10819
11554
|
mtimeMs: Math.floor(stat17.mtimeMs),
|
|
@@ -10882,6 +11617,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10882
11617
|
}
|
|
10883
11618
|
store.resolveRefs();
|
|
10884
11619
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
11620
|
+
store.optimize();
|
|
10885
11621
|
const durationMs = Date.now() - startMs;
|
|
10886
11622
|
store.setLastIndexed(Date.now());
|
|
10887
11623
|
return {
|
|
@@ -11124,10 +11860,10 @@ async function callInline(op, args, opts) {
|
|
|
11124
11860
|
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
11125
11861
|
}
|
|
11126
11862
|
}
|
|
11127
|
-
var
|
|
11863
|
+
var chain2 = Promise.resolve();
|
|
11128
11864
|
function withMutex(job) {
|
|
11129
|
-
const run =
|
|
11130
|
-
|
|
11865
|
+
const run = chain2.then(job, job);
|
|
11866
|
+
chain2 = run.then(
|
|
11131
11867
|
() => void 0,
|
|
11132
11868
|
() => void 0
|
|
11133
11869
|
);
|
|
@@ -11476,7 +12212,7 @@ var codebaseStatsTool = {
|
|
|
11476
12212
|
|
|
11477
12213
|
// src/design.ts
|
|
11478
12214
|
import * as fs16 from "node:fs/promises";
|
|
11479
|
-
import * as
|
|
12215
|
+
import * as path21 from "node:path";
|
|
11480
12216
|
import {
|
|
11481
12217
|
applyTokenOverrides,
|
|
11482
12218
|
getDesignKitLoader,
|
|
@@ -11485,23 +12221,24 @@ import {
|
|
|
11485
12221
|
materializeTokens,
|
|
11486
12222
|
recordKitChoice,
|
|
11487
12223
|
recordOverrides,
|
|
12224
|
+
resolveSemanticTune,
|
|
11488
12225
|
runDesignVerify,
|
|
11489
12226
|
setActiveKit,
|
|
11490
12227
|
setDesignOverrides
|
|
11491
|
-
} from "@wrongstack/core";
|
|
12228
|
+
} from "@wrongstack/core/design";
|
|
11492
12229
|
async function resolveReal(p) {
|
|
11493
|
-
const resolved =
|
|
11494
|
-
let
|
|
12230
|
+
const resolved = path21.resolve(p);
|
|
12231
|
+
let probe2 = resolved;
|
|
11495
12232
|
const missing = [];
|
|
11496
12233
|
for (; ; ) {
|
|
11497
12234
|
try {
|
|
11498
|
-
return
|
|
12235
|
+
return path21.resolve(await fs16.realpath(probe2), ...missing);
|
|
11499
12236
|
} catch (err) {
|
|
11500
12237
|
if (err.code === "ENOENT") {
|
|
11501
|
-
const parent =
|
|
11502
|
-
if (parent ===
|
|
11503
|
-
missing.unshift(
|
|
11504
|
-
|
|
12238
|
+
const parent = path21.dirname(probe2);
|
|
12239
|
+
if (parent === probe2) return resolved;
|
|
12240
|
+
missing.unshift(path21.basename(probe2));
|
|
12241
|
+
probe2 = parent;
|
|
11505
12242
|
continue;
|
|
11506
12243
|
}
|
|
11507
12244
|
return resolved;
|
|
@@ -11531,8 +12268,8 @@ var designTool = {
|
|
|
11531
12268
|
properties: {
|
|
11532
12269
|
action: {
|
|
11533
12270
|
type: "string",
|
|
11534
|
-
enum: ["list", "use", "foundations", "set", "materialize", "verify"],
|
|
11535
|
-
description: "list = menu; use = load+pin a kit; foundations = baseline; set = override colors/tokens; materialize = write tokens to a theme file; verify = scan UI for
|
|
12271
|
+
enum: ["list", "use", "foundations", "set", "tune", "materialize", "verify"],
|
|
12272
|
+
description: "list = menu; use = load+pin a kit; foundations = baseline; set = override colors/tokens; tune = high-level knobs (radius/density/font/motion); materialize = write tokens to a theme file; verify = scan UI for token drift. Default: list."
|
|
11536
12273
|
},
|
|
11537
12274
|
kit: {
|
|
11538
12275
|
type: "string",
|
|
@@ -11548,6 +12285,16 @@ var designTool = {
|
|
|
11548
12285
|
description: 'Token overrides for "set"/"use": { "primary": "oklch(\u2026)", "dark.bg": "#111" }. Bare key = both themes; "light."/"dark." prefix = that theme only. Empty value clears an override.',
|
|
11549
12286
|
additionalProperties: { type: "string" }
|
|
11550
12287
|
},
|
|
12288
|
+
tune: {
|
|
12289
|
+
type: "object",
|
|
12290
|
+
description: 'High-level knobs for "tune": { "radius": "lg", "density": "compact", "font": "Space Grotesk", "motion": "snappy" }. radius = none|sm|md|lg|xl|full or a base length; density = compact|cozy|comfortable; motion = snappy|smooth|none. Resolved to concrete token overrides.',
|
|
12291
|
+
properties: {
|
|
12292
|
+
radius: { type: "string" },
|
|
12293
|
+
density: { type: "string" },
|
|
12294
|
+
font: { type: "string" },
|
|
12295
|
+
motion: { type: "string" }
|
|
12296
|
+
}
|
|
12297
|
+
},
|
|
11551
12298
|
out: {
|
|
11552
12299
|
type: "string",
|
|
11553
12300
|
description: "Materialize output path (project-relative). Defaults to a per-stack convention."
|
|
@@ -11645,6 +12392,28 @@ ${body}`
|
|
|
11645
12392
|
These win over kit tokens. Run \`design {action:"materialize"}\` to write them to a theme file.`
|
|
11646
12393
|
};
|
|
11647
12394
|
}
|
|
12395
|
+
if (action === "tune") {
|
|
12396
|
+
const patch = resolveSemanticTune(input.tune ?? {});
|
|
12397
|
+
if (Object.keys(patch).length === 0) {
|
|
12398
|
+
return {
|
|
12399
|
+
action,
|
|
12400
|
+
output: 'No recognized knobs. Pass tune:{ radius:"lg", density:"compact", font:"\u2026", motion:"snappy" }.'
|
|
12401
|
+
};
|
|
12402
|
+
}
|
|
12403
|
+
const merged = await recordOverrides(ctx.projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
|
|
12404
|
+
if (!merged) {
|
|
12405
|
+
return {
|
|
12406
|
+
action,
|
|
12407
|
+
output: 'No active kit. Pick one first: `design {action:"use", kit:"<id>"}`.'
|
|
12408
|
+
};
|
|
12409
|
+
}
|
|
12410
|
+
setDesignOverrides(ctx, merged);
|
|
12411
|
+
return {
|
|
12412
|
+
action,
|
|
12413
|
+
output: `Tuned. Resolved ${Object.keys(patch).length} token override(s): ${Object.entries(patch).map(([k, v]) => `${k}=${v}`).join(", ")}
|
|
12414
|
+
Run \`design {action:"materialize"}\` to write the tuned tokens to your theme file.`
|
|
12415
|
+
};
|
|
12416
|
+
}
|
|
11648
12417
|
if (action === "materialize") {
|
|
11649
12418
|
const active = await loadActiveKit(ctx.projectRoot);
|
|
11650
12419
|
if (!active) {
|
|
@@ -11666,11 +12435,11 @@ These win over kit tokens. Run \`design {action:"materialize"}\` to write them t
|
|
|
11666
12435
|
outPath: input.out
|
|
11667
12436
|
});
|
|
11668
12437
|
const root = await resolveReal(ctx.projectRoot);
|
|
11669
|
-
const absResolved =
|
|
11670
|
-
const absParent = await resolveReal(
|
|
11671
|
-
const abs =
|
|
11672
|
-
const rel =
|
|
11673
|
-
if (rel.startsWith("..") ||
|
|
12438
|
+
const absResolved = path21.isAbsolute(result.path) ? path21.resolve(result.path) : path21.resolve(path21.join(ctx.projectRoot, result.path));
|
|
12439
|
+
const absParent = await resolveReal(path21.dirname(absResolved));
|
|
12440
|
+
const abs = path21.join(absParent, path21.basename(absResolved));
|
|
12441
|
+
const rel = path21.relative(root, abs);
|
|
12442
|
+
if (rel.startsWith("..") || path21.isAbsolute(rel)) {
|
|
11674
12443
|
throw new Error(
|
|
11675
12444
|
`design: materialize path "${result.path}" would escape the project root`
|
|
11676
12445
|
);
|
|
@@ -11694,7 +12463,7 @@ ${result.content}
|
|
|
11694
12463
|
\`\`\``
|
|
11695
12464
|
};
|
|
11696
12465
|
}
|
|
11697
|
-
await fs16.mkdir(
|
|
12466
|
+
await fs16.mkdir(path21.dirname(abs), { recursive: true });
|
|
11698
12467
|
await fs16.writeFile(abs, result.content);
|
|
11699
12468
|
return {
|
|
11700
12469
|
action,
|
|
@@ -11746,8 +12515,8 @@ init_util();
|
|
|
11746
12515
|
import { spawn as spawn7 } from "node:child_process";
|
|
11747
12516
|
import { statSync as statSync2 } from "node:fs";
|
|
11748
12517
|
import * as fs17 from "node:fs/promises";
|
|
11749
|
-
import * as
|
|
11750
|
-
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core";
|
|
12518
|
+
import * as path22 from "node:path";
|
|
12519
|
+
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core/utils";
|
|
11751
12520
|
var diffTool = {
|
|
11752
12521
|
name: "diff",
|
|
11753
12522
|
category: "Filesystem",
|
|
@@ -11830,11 +12599,11 @@ function findGitDir(cwd) {
|
|
|
11830
12599
|
let dir = cwd;
|
|
11831
12600
|
for (let i = 0; i < 20; i++) {
|
|
11832
12601
|
try {
|
|
11833
|
-
const stat17 = statSync2(
|
|
12602
|
+
const stat17 = statSync2(path22.join(dir, ".git"));
|
|
11834
12603
|
if (stat17.isDirectory()) return dir;
|
|
11835
12604
|
} catch {
|
|
11836
12605
|
}
|
|
11837
|
-
const parent =
|
|
12606
|
+
const parent = path22.dirname(dir);
|
|
11838
12607
|
if (parent === dir) break;
|
|
11839
12608
|
dir = parent;
|
|
11840
12609
|
}
|
|
@@ -12048,7 +12817,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
12048
12817
|
// src/e2e.ts
|
|
12049
12818
|
init_util();
|
|
12050
12819
|
import { open, readdir as readdir5 } from "node:fs/promises";
|
|
12051
|
-
import * as
|
|
12820
|
+
import * as path23 from "node:path";
|
|
12052
12821
|
async function readBoundedText(filePath, maxBytes) {
|
|
12053
12822
|
let handle;
|
|
12054
12823
|
try {
|
|
@@ -12098,8 +12867,8 @@ var MAX_PACKAGE_BYTES = 512 * 1024;
|
|
|
12098
12867
|
var MAX_CONFIG_BYTES = 512 * 1024;
|
|
12099
12868
|
var MAX_SPEC_SAMPLES = 20;
|
|
12100
12869
|
function relativePath(root, target) {
|
|
12101
|
-
const value =
|
|
12102
|
-
return value.split(
|
|
12870
|
+
const value = path23.relative(root, target) || ".";
|
|
12871
|
+
return value.split(path23.sep).join("/");
|
|
12103
12872
|
}
|
|
12104
12873
|
function escapeRegExp(value) {
|
|
12105
12874
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -12171,7 +12940,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
12171
12940
|
}
|
|
12172
12941
|
for (const entry of entries) {
|
|
12173
12942
|
signal.throwIfAborted();
|
|
12174
|
-
const absolutePath =
|
|
12943
|
+
const absolutePath = path23.join(current.directory, entry.name);
|
|
12175
12944
|
if (entry.isFile()) {
|
|
12176
12945
|
if (entry.name === "package.json") result.packageFiles.push(absolutePath);
|
|
12177
12946
|
const framework = CONFIG_NAMES.get(entry.name);
|
|
@@ -12232,9 +13001,9 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
12232
13001
|
if (names.has("bun.lock") || names.has("bun.lockb")) return "bun";
|
|
12233
13002
|
if (names.has("package-lock.json") || names.has("npm-shrinkwrap.json")) return "npm";
|
|
12234
13003
|
if (directory === scanRoot) break;
|
|
12235
|
-
const parent =
|
|
12236
|
-
const relativeParent =
|
|
12237
|
-
if (parent === directory || relativeParent.startsWith("..") ||
|
|
13004
|
+
const parent = path23.dirname(directory);
|
|
13005
|
+
const relativeParent = path23.relative(scanRoot, parent);
|
|
13006
|
+
if (parent === directory || relativeParent.startsWith("..") || path23.isAbsolute(relativeParent)) {
|
|
12238
13007
|
break;
|
|
12239
13008
|
}
|
|
12240
13009
|
directory = parent;
|
|
@@ -12279,7 +13048,7 @@ function isSpec(framework, filename) {
|
|
|
12279
13048
|
return /\.(?:spec|test)\.(?:[cm]?[jt]sx?)$/i.test(filename);
|
|
12280
13049
|
}
|
|
12281
13050
|
async function collectSpecs(root, framework, testDirectory, signal) {
|
|
12282
|
-
const roots = testDirectory ? [
|
|
13051
|
+
const roots = testDirectory ? [path23.resolve(root, testDirectory)] : framework === "cypress" ? [path23.join(root, "cypress", "e2e"), path23.join(root, "cypress", "integration")] : [path23.join(root, "tests")];
|
|
12283
13052
|
const samples = [];
|
|
12284
13053
|
let count = 0;
|
|
12285
13054
|
let scanned = 0;
|
|
@@ -12298,7 +13067,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
12298
13067
|
}
|
|
12299
13068
|
for (const entry of entries) {
|
|
12300
13069
|
signal.throwIfAborted();
|
|
12301
|
-
const target =
|
|
13070
|
+
const target = path23.join(directory, entry.name);
|
|
12302
13071
|
if (entry.isDirectory() && !SKIP_DIRECTORIES.has(entry.name)) queue.push(target);
|
|
12303
13072
|
else if (entry.isFile() && isSpec(framework, entry.name)) {
|
|
12304
13073
|
count += 1;
|
|
@@ -12318,7 +13087,7 @@ function nearestPackage(projectRoot, packagesByDirectory, scanRoot) {
|
|
|
12318
13087
|
const found = packagesByDirectory.get(directory);
|
|
12319
13088
|
if (found) return found;
|
|
12320
13089
|
if (directory === scanRoot) return void 0;
|
|
12321
|
-
const parent =
|
|
13090
|
+
const parent = path23.dirname(directory);
|
|
12322
13091
|
if (parent === directory || relativePath(scanRoot, parent).startsWith("..")) return void 0;
|
|
12323
13092
|
directory = parent;
|
|
12324
13093
|
}
|
|
@@ -12329,13 +13098,13 @@ async function discoverE2EProjects(root, options) {
|
|
|
12329
13098
|
const packages = (await Promise.all(scan.packageFiles.map(readPackageInfo))).filter(
|
|
12330
13099
|
(info) => Boolean(info)
|
|
12331
13100
|
);
|
|
12332
|
-
const packagesByDirectory = new Map(packages.map((info) => [
|
|
13101
|
+
const packagesByDirectory = new Map(packages.map((info) => [path23.dirname(info.path), info]));
|
|
12333
13102
|
const candidates = /* @__PURE__ */ new Map();
|
|
12334
13103
|
for (const config of scan.configs) {
|
|
12335
13104
|
if (options.framework && options.framework !== "all" && config.framework !== options.framework) {
|
|
12336
13105
|
continue;
|
|
12337
13106
|
}
|
|
12338
|
-
const projectRoot =
|
|
13107
|
+
const projectRoot = path23.dirname(config.absolutePath);
|
|
12339
13108
|
candidates.set(`${config.framework}:${projectRoot}`, {
|
|
12340
13109
|
framework: config.framework,
|
|
12341
13110
|
root: projectRoot,
|
|
@@ -12343,7 +13112,7 @@ async function discoverE2EProjects(root, options) {
|
|
|
12343
13112
|
});
|
|
12344
13113
|
}
|
|
12345
13114
|
for (const info of packages) {
|
|
12346
|
-
const projectRoot =
|
|
13115
|
+
const projectRoot = path23.dirname(info.path);
|
|
12347
13116
|
for (const framework of frameworkFromPackage(info)) {
|
|
12348
13117
|
if (options.framework && options.framework !== "all" && framework !== options.framework)
|
|
12349
13118
|
continue;
|
|
@@ -12359,10 +13128,10 @@ async function discoverE2EProjects(root, options) {
|
|
|
12359
13128
|
const scripts = matchingScripts(info, candidate.framework);
|
|
12360
13129
|
const manager = await detectPackageManager3(candidate.root, root, info?.packageManager);
|
|
12361
13130
|
const testDirectory = candidate.framework === "playwright" ? staticString(source, "testDir") : void 0;
|
|
12362
|
-
const resolvedTestDirectory = testDirectory ?
|
|
12363
|
-
const relativeTestDirectory = resolvedTestDirectory ?
|
|
13131
|
+
const resolvedTestDirectory = testDirectory ? path23.resolve(candidate.root, testDirectory) : void 0;
|
|
13132
|
+
const relativeTestDirectory = resolvedTestDirectory ? path23.relative(root, resolvedTestDirectory) : void 0;
|
|
12364
13133
|
const unsafeTestDirectory = Boolean(
|
|
12365
|
-
relativeTestDirectory && (relativeTestDirectory.startsWith("..") ||
|
|
13134
|
+
relativeTestDirectory && (relativeTestDirectory.startsWith("..") || path23.isAbsolute(relativeTestDirectory))
|
|
12366
13135
|
);
|
|
12367
13136
|
const specs = options.includeSpecs === false || unsafeTestDirectory ? { count: 0, samples: [], truncated: false } : await collectSpecs(candidate.root, candidate.framework, testDirectory, options.signal);
|
|
12368
13137
|
const warnings = [];
|
|
@@ -12467,10 +13236,10 @@ import {
|
|
|
12467
13236
|
atomicWrite as atomicWrite2,
|
|
12468
13237
|
detectNewlineStyle,
|
|
12469
13238
|
normalizeToLf,
|
|
12470
|
-
ToolValidationError,
|
|
12471
13239
|
toStyle,
|
|
12472
13240
|
unifiedDiff
|
|
12473
|
-
} from "@wrongstack/core";
|
|
13241
|
+
} from "@wrongstack/core/utils";
|
|
13242
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
12474
13243
|
|
|
12475
13244
|
// src/_edit-match.ts
|
|
12476
13245
|
var TIER_LABEL = {
|
|
@@ -12698,7 +13467,7 @@ function prefixSimilarity(a, b) {
|
|
|
12698
13467
|
}
|
|
12699
13468
|
|
|
12700
13469
|
// src/_syntax-check.ts
|
|
12701
|
-
import * as
|
|
13470
|
+
import * as path24 from "node:path";
|
|
12702
13471
|
var TS_LIKE = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
12703
13472
|
var MAX_CHECK_CHARS = 15e5;
|
|
12704
13473
|
var MAX_ERRORS = 5;
|
|
@@ -12722,15 +13491,15 @@ async function checkSyntax(filePath, content, previousContent) {
|
|
|
12722
13491
|
return { errors, preExisting };
|
|
12723
13492
|
}
|
|
12724
13493
|
function isJsoncFile(filePath) {
|
|
12725
|
-
const base =
|
|
13494
|
+
const base = path24.basename(filePath).toLowerCase();
|
|
12726
13495
|
if (base.endsWith(".jsonc")) return true;
|
|
12727
13496
|
if (/^(tsconfig|jsconfig)([.-].*)?\.json$/.test(base)) return true;
|
|
12728
|
-
const dir =
|
|
13497
|
+
const dir = path24.basename(path24.dirname(filePath)).toLowerCase();
|
|
12729
13498
|
return dir === ".vscode";
|
|
12730
13499
|
}
|
|
12731
13500
|
async function parseErrors(filePath, content) {
|
|
12732
13501
|
if (content.length > MAX_CHECK_CHARS) return void 0;
|
|
12733
|
-
const ext =
|
|
13502
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
12734
13503
|
if (ext === ".json" || ext === ".jsonc") {
|
|
12735
13504
|
try {
|
|
12736
13505
|
JSON.parse(content);
|
|
@@ -12757,7 +13526,7 @@ async function parseErrors(filePath, content) {
|
|
|
12757
13526
|
ts2.ScriptKind.JSX
|
|
12758
13527
|
);
|
|
12759
13528
|
const sourceFile = ts2.createSourceFile(
|
|
12760
|
-
|
|
13529
|
+
path24.basename(filePath),
|
|
12761
13530
|
content,
|
|
12762
13531
|
ts2.ScriptTarget.Latest,
|
|
12763
13532
|
/* setParentNodes */
|
|
@@ -12992,7 +13761,7 @@ import {
|
|
|
12992
13761
|
emitProcessCompleted as emitProcessCompleted3,
|
|
12993
13762
|
emitProcessOutput as emitProcessOutput3,
|
|
12994
13763
|
emitProcessStarted as emitProcessStarted3
|
|
12995
|
-
} from "@wrongstack/core";
|
|
13764
|
+
} from "@wrongstack/core/observability";
|
|
12996
13765
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12997
13766
|
init_output_spool();
|
|
12998
13767
|
init_util();
|
|
@@ -13282,7 +14051,7 @@ function levelRank(level) {
|
|
|
13282
14051
|
|
|
13283
14052
|
// src/exec-kill-guard.ts
|
|
13284
14053
|
import * as os7 from "node:os";
|
|
13285
|
-
import * as
|
|
14054
|
+
import * as path25 from "node:path";
|
|
13286
14055
|
var isWin3 = os7.platform() === "win32";
|
|
13287
14056
|
async function checkExecKillCommand(cmd, args) {
|
|
13288
14057
|
if (!cmd) return { blocked: false };
|
|
@@ -13495,7 +14264,7 @@ async function checkKillTarget(target) {
|
|
|
13495
14264
|
reason: `Blocked: kill ${target.signal} '${target.name}' targets a WrongStack process name.`
|
|
13496
14265
|
};
|
|
13497
14266
|
}
|
|
13498
|
-
const currentImage =
|
|
14267
|
+
const currentImage = path25.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
|
|
13499
14268
|
const targetsNodeRuntime = nameLower === "node" || nameLower.startsWith("node");
|
|
13500
14269
|
if (targetsNodeRuntime && currentImage === "node") {
|
|
13501
14270
|
return {
|
|
@@ -14282,7 +15051,7 @@ var execTool = {
|
|
|
14282
15051
|
command: cmd,
|
|
14283
15052
|
args: input.args ?? [],
|
|
14284
15053
|
stdout: "",
|
|
14285
|
-
stderr: `Command "${cmd}" not in allowlist. Add it to your ~/.wrongstack/config.json under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
|
|
15054
|
+
stderr: `Command "${cmd}" not in allowlist. Add it to your active profile config (~/.wrongstack/profiles/<name>/config.json) under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
|
|
14286
15055
|
exitCode: 1,
|
|
14287
15056
|
truncated: false,
|
|
14288
15057
|
allowed: false,
|
|
@@ -14494,18 +15263,17 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
14494
15263
|
}
|
|
14495
15264
|
|
|
14496
15265
|
// src/fetch.ts
|
|
14497
|
-
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
15266
|
+
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core/types";
|
|
14498
15267
|
import TurndownService from "turndown";
|
|
14499
15268
|
|
|
14500
15269
|
// src/_fetch-guard.ts
|
|
14501
15270
|
import * as dns2 from "node:dns/promises";
|
|
14502
15271
|
import * as net3 from "node:net";
|
|
14503
15272
|
import {
|
|
14504
|
-
FetchError,
|
|
14505
|
-
ToolValidationError as ToolValidationError2,
|
|
14506
15273
|
isPrivateIPv4 as isPrivateIPv42,
|
|
14507
15274
|
isPrivateIPv6 as isPrivateIPv62
|
|
14508
|
-
} from "@wrongstack/core";
|
|
15275
|
+
} from "@wrongstack/core/utils";
|
|
15276
|
+
import { FetchError, ToolValidationError as ToolValidationError2 } from "@wrongstack/core/types";
|
|
14509
15277
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
14510
15278
|
var nativeGlobalFetch = globalThis.fetch;
|
|
14511
15279
|
var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
|
|
@@ -15003,7 +15771,8 @@ init_util();
|
|
|
15003
15771
|
import { spawn as spawn9 } from "node:child_process";
|
|
15004
15772
|
import { statSync as statSync3 } from "node:fs";
|
|
15005
15773
|
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
15006
|
-
import { assessCommitSafety
|
|
15774
|
+
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
15775
|
+
import { buildChildEnv as buildChildEnv4 } from "@wrongstack/core/utils";
|
|
15007
15776
|
var TIMEOUT_MS2 = 3e4;
|
|
15008
15777
|
var MAX_OUTPUT3 = 1e5;
|
|
15009
15778
|
var gitTool = {
|
|
@@ -15283,8 +16052,8 @@ function runGit2(args, cwd, signal) {
|
|
|
15283
16052
|
|
|
15284
16053
|
// src/glob.ts
|
|
15285
16054
|
import * as fs20 from "node:fs/promises";
|
|
15286
|
-
import * as
|
|
15287
|
-
import { compileGlob as
|
|
16055
|
+
import * as path26 from "node:path";
|
|
16056
|
+
import { compileGlob as compileGlob2 } from "@wrongstack/core/utils";
|
|
15288
16057
|
|
|
15289
16058
|
// src/_concurrency.ts
|
|
15290
16059
|
async function mapWithConcurrency(items, limit, fn) {
|
|
@@ -15346,7 +16115,7 @@ var globTool = {
|
|
|
15346
16115
|
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
15347
16116
|
const limit = Math.max(1, Math.min(input.limit ?? 1e3, 5e3));
|
|
15348
16117
|
const ignored = await readGitignore(base);
|
|
15349
|
-
const re =
|
|
16118
|
+
const re = compileGlob2(input.pattern);
|
|
15350
16119
|
const results = [];
|
|
15351
16120
|
let truncated = false;
|
|
15352
16121
|
const pushResult = async (full) => {
|
|
@@ -15387,7 +16156,7 @@ var globTool = {
|
|
|
15387
16156
|
if (DEFAULT_IGNORE2.includes(name)) continue;
|
|
15388
16157
|
if (ignored.includes(name)) continue;
|
|
15389
16158
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
15390
|
-
const full =
|
|
16159
|
+
const full = path26.join(dir, name);
|
|
15391
16160
|
if (e.isDirectory()) {
|
|
15392
16161
|
subdirs.push({ full, rel });
|
|
15393
16162
|
} else if (e.isFile()) {
|
|
@@ -15431,7 +16200,7 @@ var globTool = {
|
|
|
15431
16200
|
};
|
|
15432
16201
|
async function readGitignore(dir) {
|
|
15433
16202
|
try {
|
|
15434
|
-
const raw = await fs20.readFile(
|
|
16203
|
+
const raw = await fs20.readFile(path26.join(dir, ".gitignore"), "utf8");
|
|
15435
16204
|
return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
15436
16205
|
} catch {
|
|
15437
16206
|
return [];
|
|
@@ -15439,11 +16208,12 @@ async function readGitignore(dir) {
|
|
|
15439
16208
|
}
|
|
15440
16209
|
|
|
15441
16210
|
// src/grep.ts
|
|
15442
|
-
import { expectDefined as expectDefined7 } from "@wrongstack/core";
|
|
16211
|
+
import { expectDefined as expectDefined7 } from "@wrongstack/core/utils";
|
|
15443
16212
|
import { spawn as spawn10 } from "node:child_process";
|
|
15444
16213
|
import * as fs21 from "node:fs/promises";
|
|
15445
|
-
import * as
|
|
15446
|
-
import {
|
|
16214
|
+
import * as path27 from "node:path";
|
|
16215
|
+
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
16216
|
+
import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3 } from "@wrongstack/core/utils";
|
|
15447
16217
|
|
|
15448
16218
|
// src/_regex.ts
|
|
15449
16219
|
var MAX_PATTERN_LEN = 256;
|
|
@@ -15733,7 +16503,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15733
16503
|
});
|
|
15734
16504
|
}
|
|
15735
16505
|
const re = compiled.regex;
|
|
15736
|
-
const globRe = input.glob ?
|
|
16506
|
+
const globRe = input.glob ? compileGlob3(input.glob) : null;
|
|
15737
16507
|
const matches = [];
|
|
15738
16508
|
const countOnlyFirstHit = mode === "count" && limit === 1;
|
|
15739
16509
|
const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
|
|
@@ -15835,7 +16605,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15835
16605
|
if (stopped) return;
|
|
15836
16606
|
if (DEFAULT_IGNORE3.includes(e.name)) continue;
|
|
15837
16607
|
if (e.isSymbolicLink()) continue;
|
|
15838
|
-
const full =
|
|
16608
|
+
const full = path27.join(dir, e.name);
|
|
15839
16609
|
if (e.isDirectory()) {
|
|
15840
16610
|
subdirs.push(full);
|
|
15841
16611
|
} else if (e.isFile()) {
|
|
@@ -15859,7 +16629,10 @@ init_spawn_stream();
|
|
|
15859
16629
|
init_util();
|
|
15860
16630
|
init_legacy_bridge();
|
|
15861
16631
|
import { join as join21 } from "node:path";
|
|
15862
|
-
import {
|
|
16632
|
+
import {
|
|
16633
|
+
detectEcosystem as detectPackageEcosystem,
|
|
16634
|
+
recordPackageAction
|
|
16635
|
+
} from "@wrongstack/core/coordination";
|
|
15863
16636
|
var installTool = {
|
|
15864
16637
|
name: "install",
|
|
15865
16638
|
category: "Package Management",
|
|
@@ -16045,7 +16818,7 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
16045
16818
|
// src/json.ts
|
|
16046
16819
|
init_util();
|
|
16047
16820
|
import * as fs22 from "node:fs/promises";
|
|
16048
|
-
import { deepMerge as deepMergeCore } from "@wrongstack/core";
|
|
16821
|
+
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
16049
16822
|
var jsonTool = {
|
|
16050
16823
|
name: "json",
|
|
16051
16824
|
category: "Data",
|
|
@@ -16423,56 +17196,56 @@ function jmespathSearch(data, query) {
|
|
|
16423
17196
|
}
|
|
16424
17197
|
function validateJsonSchema(data, schema) {
|
|
16425
17198
|
const errors = [];
|
|
16426
|
-
function check(value, s,
|
|
17199
|
+
function check(value, s, path34) {
|
|
16427
17200
|
if (s["type"]) {
|
|
16428
17201
|
const expectedType = s["type"];
|
|
16429
17202
|
const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
16430
17203
|
if (expectedType === "integer") {
|
|
16431
|
-
if (!Number.isInteger(value)) errors.push(`${
|
|
17204
|
+
if (!Number.isInteger(value)) errors.push(`${path34}: expected integer, got ${actualType}`);
|
|
16432
17205
|
} else if (expectedType !== actualType) {
|
|
16433
|
-
errors.push(`${
|
|
17206
|
+
errors.push(`${path34}: expected ${expectedType}, got ${actualType}`);
|
|
16434
17207
|
}
|
|
16435
17208
|
}
|
|
16436
17209
|
if (typeof value === "string" && s["format"] === "uri" && value) {
|
|
16437
17210
|
try {
|
|
16438
17211
|
new URL(value);
|
|
16439
17212
|
} catch {
|
|
16440
|
-
errors.push(`${
|
|
17213
|
+
errors.push(`${path34}: not a valid URI`);
|
|
16441
17214
|
}
|
|
16442
17215
|
}
|
|
16443
17216
|
if (typeof value === "string" && s["pattern"]) {
|
|
16444
17217
|
const re = new RegExp(s["pattern"]);
|
|
16445
|
-
if (!re.test(value)) errors.push(`${
|
|
17218
|
+
if (!re.test(value)) errors.push(`${path34}: does not match pattern ${s["pattern"]}`);
|
|
16446
17219
|
}
|
|
16447
17220
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
16448
|
-
errors.push(`${
|
|
17221
|
+
errors.push(`${path34}: string too short (min ${s["minLength"]})`);
|
|
16449
17222
|
}
|
|
16450
17223
|
if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
|
|
16451
|
-
errors.push(`${
|
|
17224
|
+
errors.push(`${path34}: string too long (max ${s["maxLength"]})`);
|
|
16452
17225
|
}
|
|
16453
17226
|
if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
|
|
16454
|
-
errors.push(`${
|
|
17227
|
+
errors.push(`${path34}: below minimum ${s["minimum"]}`);
|
|
16455
17228
|
}
|
|
16456
17229
|
if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
|
|
16457
|
-
errors.push(`${
|
|
17230
|
+
errors.push(`${path34}: above maximum ${s["maximum"]}`);
|
|
16458
17231
|
}
|
|
16459
17232
|
if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
|
|
16460
17233
|
for (let i = 0; i < value.length; i++) {
|
|
16461
|
-
check(value[i], s["items"], `${
|
|
17234
|
+
check(value[i], s["items"], `${path34}[${i}]`);
|
|
16462
17235
|
}
|
|
16463
17236
|
}
|
|
16464
17237
|
if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
|
|
16465
17238
|
const props = s["properties"];
|
|
16466
17239
|
for (const [k, propSchema] of Object.entries(props)) {
|
|
16467
|
-
check(value[k], propSchema, `${
|
|
17240
|
+
check(value[k], propSchema, `${path34}.${k}`);
|
|
16468
17241
|
}
|
|
16469
17242
|
}
|
|
16470
17243
|
}
|
|
16471
17244
|
check(data, schema, "$");
|
|
16472
17245
|
return { valid: errors.length === 0, errors };
|
|
16473
17246
|
}
|
|
16474
|
-
function simpleQuery(data,
|
|
16475
|
-
const parts =
|
|
17247
|
+
function simpleQuery(data, path34) {
|
|
17248
|
+
const parts = path34.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
16476
17249
|
let current = data;
|
|
16477
17250
|
for (const part of parts) {
|
|
16478
17251
|
if (current === null || current === void 0) return void 0;
|
|
@@ -16523,7 +17296,8 @@ function toYaml(data, indent = 0) {
|
|
|
16523
17296
|
|
|
16524
17297
|
// src/kanban.ts
|
|
16525
17298
|
import { randomUUID } from "node:crypto";
|
|
16526
|
-
import { deserializeTaskGraph as deserializeTaskGraph2,
|
|
17299
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
17300
|
+
import { loadTasks as loadTasks2 } from "@wrongstack/core/storage";
|
|
16527
17301
|
import {
|
|
16528
17302
|
addCheckToTask,
|
|
16529
17303
|
addColumn,
|
|
@@ -16570,18 +17344,19 @@ import {
|
|
|
16570
17344
|
updateColumn,
|
|
16571
17345
|
updateGoalMetricOnTask,
|
|
16572
17346
|
updateTask,
|
|
16573
|
-
updateTaskAssignment
|
|
17347
|
+
updateTaskAssignment,
|
|
17348
|
+
verifyTaskCompletion
|
|
16574
17349
|
} from "@wrongstack/kanban";
|
|
16575
17350
|
|
|
16576
17351
|
// src/session-kanban.ts
|
|
17352
|
+
import { GlobalMailbox } from "@wrongstack/core/coordination";
|
|
16577
17353
|
import {
|
|
16578
|
-
deserializeTaskGraph,
|
|
16579
|
-
GlobalMailbox,
|
|
16580
17354
|
loadPlan,
|
|
16581
17355
|
loadTasks,
|
|
16582
17356
|
mutatePlan,
|
|
16583
17357
|
mutateTasks
|
|
16584
|
-
} from "@wrongstack/core";
|
|
17358
|
+
} from "@wrongstack/core/storage";
|
|
17359
|
+
import { deserializeTaskGraph } from "@wrongstack/core/tasking";
|
|
16585
17360
|
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
16586
17361
|
import {
|
|
16587
17362
|
createBoard,
|
|
@@ -16802,8 +17577,8 @@ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
|
16802
17577
|
var kanbanTool = {
|
|
16803
17578
|
name: "kanban",
|
|
16804
17579
|
category: "Project",
|
|
16805
|
-
description: "Manage and audit project-scoped multi-kanban boards stored under .wrongstack/kanbans. Managed cards enforce fully specified details and adjacent Backlog \u2192 Todo \u2192 Running \u2192 Review \u2192 Done transitions with persistent comments and evidence. Successful board access records live agent/session presence.",
|
|
16806
|
-
usageHint: "Use this for durable project kanban state. Reassess with get_board whenever evidence changes; agents may add, update, split, merge, reprioritize, or remove tasks so the board remains a live plan. Presence includes active/last-seen session and agent metadata. For managed boards, fully fill card details, use transition_task after every material step, attach truthful evidence, and never use update_task/move_task to bypass lifecycle guards. Worker completion enters Review; only passed acceptance criteria plus review evidence allow Done.",
|
|
17580
|
+
description: "Manage and audit project-scoped multi-kanban boards stored under .wrongstack/kanbans. Managed cards enforce fully specified details and adjacent Backlog \u2192 Todo \u2192 Running \u2192 Review \u2192 Done transitions with persistent comments and evidence. Successful board access records live agent/session presence. Use verify_completion to validate a task against its success criteria and persist the verification report. Use split_atomic to split a task into child tasks with parent.atomic=true in a single atomic board mutation, enforcing subtree verification before the parent can complete.",
|
|
17581
|
+
usageHint: "Use this for durable project kanban state. Reassess with get_board whenever evidence changes; agents may add, update, split, merge, reprioritize, or remove tasks so the board remains a live plan. Presence includes active/last-seen session and agent metadata. For managed boards, fully fill card details, use transition_task after every material step, attach truthful evidence, and never use update_task/move_task to bypass lifecycle guards. Worker completion enters Review; only passed acceptance criteria plus review evidence allow Done. Use verify_completion to generate a verification report (persisted automatically) and split_atomic to atomically create child subtasks with the atomic flag pre-set.",
|
|
16807
17582
|
permission: "confirm",
|
|
16808
17583
|
mutating: true,
|
|
16809
17584
|
capabilities: ["fs.write"],
|
|
@@ -16859,7 +17634,9 @@ var kanbanTool = {
|
|
|
16859
17634
|
"add_check",
|
|
16860
17635
|
"update_check",
|
|
16861
17636
|
"add_note",
|
|
16862
|
-
"add_link"
|
|
17637
|
+
"add_link",
|
|
17638
|
+
"verify_completion",
|
|
17639
|
+
"split_atomic"
|
|
16863
17640
|
]
|
|
16864
17641
|
},
|
|
16865
17642
|
boardId: { type: "string" },
|
|
@@ -16914,6 +17691,7 @@ var kanbanTool = {
|
|
|
16914
17691
|
runTaskId: { type: "string" },
|
|
16915
17692
|
lastResult: { type: "string" },
|
|
16916
17693
|
error: { type: "string" },
|
|
17694
|
+
expectedLeaseId: { type: "string" },
|
|
16917
17695
|
assignmentStatus: {
|
|
16918
17696
|
type: "string",
|
|
16919
17697
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
@@ -16948,6 +17726,7 @@ var kanbanTool = {
|
|
|
16948
17726
|
lastFailureKind: { type: "string" },
|
|
16949
17727
|
dependsOn: { type: "array", items: { type: "string" } },
|
|
16950
17728
|
estimatedHours: { type: "number" },
|
|
17729
|
+
actualHours: { type: "number" },
|
|
16951
17730
|
taskGraph: { type: "object" },
|
|
16952
17731
|
graphId: { type: "string" },
|
|
16953
17732
|
specId: { type: "string" },
|
|
@@ -17256,24 +18035,7 @@ var kanbanTool = {
|
|
|
17256
18035
|
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
17257
18036
|
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
17258
18037
|
}
|
|
17259
|
-
|
|
17260
|
-
titles: input.childTitles,
|
|
17261
|
-
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
17262
|
-
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
17263
|
-
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
17264
|
-
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
17265
|
-
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
17266
|
-
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
17267
|
-
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
17268
|
-
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
17269
|
-
});
|
|
17270
|
-
return result2 ? {
|
|
17271
|
-
ok: true,
|
|
17272
|
-
message: `${result2.children.length} child task(s) created.`,
|
|
17273
|
-
board: result2.board,
|
|
17274
|
-
task: result2.parent,
|
|
17275
|
-
children: result2.children
|
|
17276
|
-
} : fail("Task not found.");
|
|
18038
|
+
return handleSplitTask(projectRoot, input, {});
|
|
17277
18039
|
}
|
|
17278
18040
|
case "merge_tasks": {
|
|
17279
18041
|
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
@@ -17638,6 +18400,39 @@ var kanbanTool = {
|
|
|
17638
18400
|
});
|
|
17639
18401
|
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
17640
18402
|
}
|
|
18403
|
+
case "verify_completion": {
|
|
18404
|
+
if (!input.boardId || !input.taskId) {
|
|
18405
|
+
return fail("verify_completion requires boardId and taskId.");
|
|
18406
|
+
}
|
|
18407
|
+
const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
|
|
18408
|
+
const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
|
|
18409
|
+
verificationReport: verResult.report,
|
|
18410
|
+
successCriteria: verResult.task.successCriteria
|
|
18411
|
+
});
|
|
18412
|
+
if (!persistedBoard) {
|
|
18413
|
+
return {
|
|
18414
|
+
ok: false,
|
|
18415
|
+
verdict: verResult.report.verdict,
|
|
18416
|
+
message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
|
|
18417
|
+
board: verResult.board
|
|
18418
|
+
};
|
|
18419
|
+
}
|
|
18420
|
+
const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
|
|
18421
|
+
const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
|
|
18422
|
+
return {
|
|
18423
|
+
ok: deterministicVerdicts.includes(verResult.report.verdict),
|
|
18424
|
+
verdict: verResult.report.verdict,
|
|
18425
|
+
message: verResult.report.markdownSummary,
|
|
18426
|
+
board: persistedBoard,
|
|
18427
|
+
task: freshTask ?? verResult.task
|
|
18428
|
+
};
|
|
18429
|
+
}
|
|
18430
|
+
case "split_atomic": {
|
|
18431
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
18432
|
+
return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
|
|
18433
|
+
}
|
|
18434
|
+
return handleSplitTask(projectRoot, input, { atomic: true });
|
|
18435
|
+
}
|
|
17641
18436
|
default:
|
|
17642
18437
|
return fail(`Unknown kanban action: ${input.action}`);
|
|
17643
18438
|
}
|
|
@@ -17657,6 +18452,50 @@ function okBoard(board, message = "Board loaded.") {
|
|
|
17657
18452
|
function okTask(board, task, message) {
|
|
17658
18453
|
return { ok: true, message, board, task };
|
|
17659
18454
|
}
|
|
18455
|
+
async function handleSplitTask(projectRoot, input, extraSplitOptions) {
|
|
18456
|
+
const boardId = input.boardId;
|
|
18457
|
+
const taskId = input.taskId;
|
|
18458
|
+
const childTitles = input.childTitles;
|
|
18459
|
+
if (!boardId || !taskId || !childTitles?.length) {
|
|
18460
|
+
return fail("split requires boardId, taskId, and at least one childTitles.");
|
|
18461
|
+
}
|
|
18462
|
+
const {
|
|
18463
|
+
targetColumnId,
|
|
18464
|
+
inheritAssignment,
|
|
18465
|
+
inheritLabels,
|
|
18466
|
+
inheritSuccessCriteria,
|
|
18467
|
+
inheritGoalMetrics,
|
|
18468
|
+
inheritDependencies,
|
|
18469
|
+
chainChildren,
|
|
18470
|
+
rewireDependents
|
|
18471
|
+
} = input;
|
|
18472
|
+
const result = await splitTask(projectRoot, boardId, taskId, {
|
|
18473
|
+
titles: childTitles,
|
|
18474
|
+
...extraSplitOptions,
|
|
18475
|
+
...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
|
|
18476
|
+
...inheritAssignment !== void 0 ? { inheritAssignment } : {},
|
|
18477
|
+
...inheritLabels !== void 0 ? { inheritLabels } : {},
|
|
18478
|
+
...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
|
|
18479
|
+
...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
|
|
18480
|
+
...inheritDependencies !== void 0 ? { inheritDependencies } : {},
|
|
18481
|
+
...chainChildren !== void 0 ? { chainChildren } : {},
|
|
18482
|
+
...rewireDependents !== void 0 ? { rewireDependents } : {}
|
|
18483
|
+
});
|
|
18484
|
+
if (!result) return fail("Task not found.");
|
|
18485
|
+
const freshParent = result.board.tasks?.find((t) => t.id === taskId);
|
|
18486
|
+
if (!freshParent) {
|
|
18487
|
+
return fail(
|
|
18488
|
+
`Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
|
|
18489
|
+
);
|
|
18490
|
+
}
|
|
18491
|
+
return {
|
|
18492
|
+
ok: true,
|
|
18493
|
+
message: `${result.children.length} child task(s) created.`,
|
|
18494
|
+
board: result.board,
|
|
18495
|
+
task: freshParent,
|
|
18496
|
+
children: result.children
|
|
18497
|
+
};
|
|
18498
|
+
}
|
|
17660
18499
|
async function requireBoard(projectRoot, boardId) {
|
|
17661
18500
|
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
17662
18501
|
}
|
|
@@ -17758,7 +18597,8 @@ function taskPatch(input) {
|
|
|
17758
18597
|
labels: input.labels,
|
|
17759
18598
|
assignedAgent: input.agentId,
|
|
17760
18599
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
17761
|
-
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
18600
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
18601
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
|
|
17762
18602
|
};
|
|
17763
18603
|
}
|
|
17764
18604
|
function assignmentInput(input) {
|
|
@@ -17940,7 +18780,7 @@ async function detectLinter(cwd) {
|
|
|
17940
18780
|
|
|
17941
18781
|
// src/logs.ts
|
|
17942
18782
|
import { spawn as spawn11 } from "node:child_process";
|
|
17943
|
-
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core";
|
|
18783
|
+
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core/utils";
|
|
17944
18784
|
init_util();
|
|
17945
18785
|
var logsTool = {
|
|
17946
18786
|
name: "logs",
|
|
@@ -18079,7 +18919,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
18079
18919
|
}
|
|
18080
18920
|
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
18081
18921
|
var MAX_TAIL_LINES = 1e5;
|
|
18082
|
-
async function fileLogs(
|
|
18922
|
+
async function fileLogs(path34, lines, filterRe, stream) {
|
|
18083
18923
|
const { createInterface } = await import("node:readline");
|
|
18084
18924
|
const { createReadStream: createReadStream2 } = await import("node:fs");
|
|
18085
18925
|
const entries = [];
|
|
@@ -18088,7 +18928,7 @@ async function fileLogs(path33, lines, filterRe, stream) {
|
|
|
18088
18928
|
let writeIdx = 0;
|
|
18089
18929
|
let totalLines = 0;
|
|
18090
18930
|
const rl = createInterface({
|
|
18091
|
-
input: createReadStream2(
|
|
18931
|
+
input: createReadStream2(path34),
|
|
18092
18932
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
18093
18933
|
});
|
|
18094
18934
|
for await (const line of rl) {
|
|
@@ -18109,7 +18949,7 @@ async function fileLogs(path33, lines, filterRe, stream) {
|
|
|
18109
18949
|
if (parsed) entries.push(parsed);
|
|
18110
18950
|
}
|
|
18111
18951
|
return {
|
|
18112
|
-
source:
|
|
18952
|
+
source: path34,
|
|
18113
18953
|
entries,
|
|
18114
18954
|
total: entries.length,
|
|
18115
18955
|
truncated: totalLines > effLines,
|
|
@@ -18156,7 +18996,7 @@ function parseLine(line) {
|
|
|
18156
18996
|
init_util();
|
|
18157
18997
|
init_win32_resolve();
|
|
18158
18998
|
import { spawn as spawn12 } from "node:child_process";
|
|
18159
|
-
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core";
|
|
18999
|
+
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core/utils";
|
|
18160
19000
|
var outdatedTool = {
|
|
18161
19001
|
name: "outdated",
|
|
18162
19002
|
category: "Package Management",
|
|
@@ -18341,8 +19181,8 @@ init_util();
|
|
|
18341
19181
|
import { spawn as spawn13 } from "node:child_process";
|
|
18342
19182
|
import * as fs23 from "node:fs/promises";
|
|
18343
19183
|
import * as os8 from "node:os";
|
|
18344
|
-
import * as
|
|
18345
|
-
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core";
|
|
19184
|
+
import * as path28 from "node:path";
|
|
19185
|
+
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core/utils";
|
|
18346
19186
|
var patchTool = {
|
|
18347
19187
|
name: "patch",
|
|
18348
19188
|
category: "Filesystem",
|
|
@@ -18377,9 +19217,9 @@ var patchTool = {
|
|
|
18377
19217
|
for (const t of targets) {
|
|
18378
19218
|
const stripped = stripPathComponents(t, strip);
|
|
18379
19219
|
if (!stripped) continue;
|
|
18380
|
-
const candidate =
|
|
18381
|
-
const rel =
|
|
18382
|
-
if (rel.startsWith("..") ||
|
|
19220
|
+
const candidate = path28.resolve(dir, stripped);
|
|
19221
|
+
const rel = path28.relative(ctx.projectRoot, candidate);
|
|
19222
|
+
if (rel.startsWith("..") || path28.isAbsolute(rel)) {
|
|
18383
19223
|
return {
|
|
18384
19224
|
applied: 0,
|
|
18385
19225
|
rejected: 1,
|
|
@@ -18396,11 +19236,11 @@ var patchTool = {
|
|
|
18396
19236
|
beforeContents.set(target, await readTextForTracking(target));
|
|
18397
19237
|
}
|
|
18398
19238
|
}
|
|
18399
|
-
const tmpDir = await fs23.mkdtemp(
|
|
19239
|
+
const tmpDir = await fs23.mkdtemp(path28.join(os8.tmpdir(), ".wstack_patch_"));
|
|
18400
19240
|
try {
|
|
18401
19241
|
await fs23.chmod(tmpDir, 448).catch(() => {
|
|
18402
19242
|
});
|
|
18403
|
-
const patchFile =
|
|
19243
|
+
const patchFile = path28.join(tmpDir, "in.diff");
|
|
18404
19244
|
await fs23.writeFile(patchFile, input.patch, { mode: 384 });
|
|
18405
19245
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
18406
19246
|
const result = await runPatch(args, dir, opts.signal);
|
|
@@ -18506,11 +19346,11 @@ import {
|
|
|
18506
19346
|
mutatePlan as mutatePlan2,
|
|
18507
19347
|
removePlanItem,
|
|
18508
19348
|
setPlanItemStatus
|
|
18509
|
-
} from "@wrongstack/core";
|
|
19349
|
+
} from "@wrongstack/core/storage";
|
|
18510
19350
|
import {
|
|
18511
|
-
mutateTasks as mutateTasks2
|
|
18512
|
-
|
|
18513
|
-
} from "@wrongstack/core";
|
|
19351
|
+
mutateTasks as mutateTasks2
|
|
19352
|
+
} from "@wrongstack/core/storage";
|
|
19353
|
+
import { formatTaskList } from "@wrongstack/core/utils";
|
|
18514
19354
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
18515
19355
|
var planTool = {
|
|
18516
19356
|
name: "plan",
|
|
@@ -18777,7 +19617,8 @@ function mkResult(plan, ok, message, todos) {
|
|
|
18777
19617
|
// src/read.ts
|
|
18778
19618
|
init_util();
|
|
18779
19619
|
import * as fs24 from "node:fs/promises";
|
|
18780
|
-
import { FsError,
|
|
19620
|
+
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
19621
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
18781
19622
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
18782
19623
|
var readTool = {
|
|
18783
19624
|
name: "read",
|
|
@@ -18983,20 +19824,20 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
18983
19824
|
}
|
|
18984
19825
|
|
|
18985
19826
|
// src/replace.ts
|
|
18986
|
-
import { expectDefined as expectDefined8 } from "@wrongstack/core";
|
|
19827
|
+
import { expectDefined as expectDefined8 } from "@wrongstack/core/utils";
|
|
18987
19828
|
import { spawn as spawn14 } from "node:child_process";
|
|
18988
19829
|
import * as fs25 from "node:fs/promises";
|
|
18989
|
-
import * as
|
|
19830
|
+
import * as path29 from "node:path";
|
|
18990
19831
|
import {
|
|
18991
19832
|
atomicWrite as atomicWrite3,
|
|
18992
19833
|
buildChildEnv as buildChildEnv9,
|
|
18993
|
-
compileGlob as
|
|
19834
|
+
compileGlob as compileGlob4,
|
|
18994
19835
|
detectNewlineStyle as detectNewlineStyle2,
|
|
18995
19836
|
normalizeToLf as normalizeToLf2,
|
|
18996
19837
|
toStyle as toStyle2,
|
|
18997
|
-
ToolValidationError as ToolValidationError6,
|
|
18998
19838
|
unifiedDiff as unifiedDiff2
|
|
18999
|
-
} from "@wrongstack/core";
|
|
19839
|
+
} from "@wrongstack/core/utils";
|
|
19840
|
+
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
19000
19841
|
init_util();
|
|
19001
19842
|
var DEFAULT_IGNORE4 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
19002
19843
|
var replaceTool = {
|
|
@@ -19055,7 +19896,7 @@ var replaceTool = {
|
|
|
19055
19896
|
});
|
|
19056
19897
|
}
|
|
19057
19898
|
const re = compiled.regex;
|
|
19058
|
-
const globRe = input.glob ?
|
|
19899
|
+
const globRe = input.glob ? compileGlob4(input.glob) : null;
|
|
19059
19900
|
const dryRun = input.dry_run ?? true;
|
|
19060
19901
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
19061
19902
|
const fileList = await resolveFiles2(filesInput, ctx, globRe);
|
|
@@ -19075,8 +19916,8 @@ var replaceTool = {
|
|
|
19075
19916
|
} catch {
|
|
19076
19917
|
continue;
|
|
19077
19918
|
}
|
|
19078
|
-
const rel =
|
|
19079
|
-
if (rel.startsWith("..") ||
|
|
19919
|
+
const rel = path29.relative(realRoot, realPath);
|
|
19920
|
+
if (rel.startsWith("..") || path29.isAbsolute(rel)) continue;
|
|
19080
19921
|
const stat17 = await fs25.stat(realPath).catch(() => null);
|
|
19081
19922
|
if (!stat17?.isFile()) continue;
|
|
19082
19923
|
let content;
|
|
@@ -19195,7 +20036,7 @@ function spawnRgFind(pattern, base) {
|
|
|
19195
20036
|
}
|
|
19196
20037
|
async function globNative(pattern, base, extraGlob) {
|
|
19197
20038
|
const results = [];
|
|
19198
|
-
const globRe =
|
|
20039
|
+
const globRe = compileGlob4(pattern);
|
|
19199
20040
|
const walk = async (dir) => {
|
|
19200
20041
|
let entries;
|
|
19201
20042
|
try {
|
|
@@ -19205,7 +20046,7 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19205
20046
|
}
|
|
19206
20047
|
for (const e of entries) {
|
|
19207
20048
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
19208
|
-
const full =
|
|
20049
|
+
const full = path29.join(dir, e.name);
|
|
19209
20050
|
try {
|
|
19210
20051
|
const stat17 = await fs25.lstat(full);
|
|
19211
20052
|
if (stat17.isSymbolicLink()) continue;
|
|
@@ -19232,8 +20073,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19232
20073
|
// src/scaffold.ts
|
|
19233
20074
|
init_util();
|
|
19234
20075
|
import * as fs26 from "node:fs/promises";
|
|
19235
|
-
import * as
|
|
19236
|
-
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
20076
|
+
import * as path30 from "node:path";
|
|
20077
|
+
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
19237
20078
|
var BUILT_IN_TEMPLATES = {
|
|
19238
20079
|
"npm-package": {
|
|
19239
20080
|
description: "Basic npm package with ESM",
|
|
@@ -19382,16 +20223,16 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
19382
20223
|
let filesCreated = 0;
|
|
19383
20224
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
19384
20225
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
19385
|
-
const joinedPath =
|
|
19386
|
-
const root =
|
|
19387
|
-
const target =
|
|
19388
|
-
const rel =
|
|
19389
|
-
if (rel.startsWith("..") ||
|
|
20226
|
+
const joinedPath = path30.join(cwd, resolvedPath);
|
|
20227
|
+
const root = path30.resolve(ctx.projectRoot);
|
|
20228
|
+
const target = path30.resolve(joinedPath);
|
|
20229
|
+
const rel = path30.relative(root, target);
|
|
20230
|
+
if (rel.startsWith("..") || path30.isAbsolute(rel)) {
|
|
19390
20231
|
throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
|
|
19391
20232
|
}
|
|
19392
20233
|
const fullPath = target;
|
|
19393
20234
|
if (!dryRun) {
|
|
19394
|
-
await fs26.mkdir(
|
|
20235
|
+
await fs26.mkdir(path30.dirname(fullPath), { recursive: true });
|
|
19395
20236
|
await atomicWrite4(fullPath, substituteVars(content, name, vars));
|
|
19396
20237
|
}
|
|
19397
20238
|
files.push(resolvedPath);
|
|
@@ -19420,7 +20261,8 @@ function substituteVars(content, name, vars) {
|
|
|
19420
20261
|
}
|
|
19421
20262
|
|
|
19422
20263
|
// src/search.ts
|
|
19423
|
-
import {
|
|
20264
|
+
import { FetchError as FetchError3, ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
20265
|
+
import { expectDefined as expectDefined9 } from "@wrongstack/core/utils";
|
|
19424
20266
|
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
19425
20267
|
var DEFAULT_NUM = 10;
|
|
19426
20268
|
var MAX_RESULTS = 50;
|
|
@@ -19870,15 +20712,13 @@ var setWorkingDirTool = {
|
|
|
19870
20712
|
import {
|
|
19871
20713
|
computeTaskItemProgress,
|
|
19872
20714
|
formatTaskList as formatTaskList2
|
|
19873
|
-
} from "@wrongstack/core";
|
|
19874
|
-
import {
|
|
19875
|
-
mutateTasks as mutateTasks3
|
|
19876
|
-
} from "@wrongstack/core";
|
|
20715
|
+
} from "@wrongstack/core/utils";
|
|
20716
|
+
import { mutateTasks as mutateTasks3 } from "@wrongstack/core/storage";
|
|
19877
20717
|
import {
|
|
19878
20718
|
addPlanItem as addPlanItem2,
|
|
19879
20719
|
mutatePlan as mutatePlan3,
|
|
19880
20720
|
formatPlan as formatPlan2
|
|
19881
|
-
} from "@wrongstack/core";
|
|
20721
|
+
} from "@wrongstack/core/storage";
|
|
19882
20722
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
19883
20723
|
function findTaskIndex(tasks, query) {
|
|
19884
20724
|
const asNum = Number.parseInt(query, 10);
|
|
@@ -20248,7 +21088,7 @@ ${formatTaskList2(file.tasks)}` : file.tasks.length > 0 ? formatTaskList2(file.t
|
|
|
20248
21088
|
init_spawn_stream();
|
|
20249
21089
|
init_util();
|
|
20250
21090
|
init_legacy_bridge();
|
|
20251
|
-
import * as
|
|
21091
|
+
import * as path31 from "node:path";
|
|
20252
21092
|
var testTool = {
|
|
20253
21093
|
name: "test",
|
|
20254
21094
|
category: "Code Quality",
|
|
@@ -20355,7 +21195,7 @@ async function detectRunner(cwd) {
|
|
|
20355
21195
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
20356
21196
|
for (const f of candidates) {
|
|
20357
21197
|
try {
|
|
20358
|
-
await stat17(
|
|
21198
|
+
await stat17(path31.join(cwd, f));
|
|
20359
21199
|
if (f.includes("vitest")) return "vitest";
|
|
20360
21200
|
if (f.includes("jest")) return "jest";
|
|
20361
21201
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -20432,7 +21272,7 @@ function parseResult(runner, result, duration) {
|
|
|
20432
21272
|
}
|
|
20433
21273
|
|
|
20434
21274
|
// src/todo.ts
|
|
20435
|
-
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
21275
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core/storage";
|
|
20436
21276
|
var todoTool = {
|
|
20437
21277
|
name: "todo",
|
|
20438
21278
|
category: "Session",
|
|
@@ -20824,9 +21664,9 @@ var toolUseTool = {
|
|
|
20824
21664
|
|
|
20825
21665
|
// src/tree.ts
|
|
20826
21666
|
init_util();
|
|
20827
|
-
import { expectDefined as expectDefined10 } from "@wrongstack/core";
|
|
21667
|
+
import { expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
20828
21668
|
import * as fs28 from "node:fs/promises";
|
|
20829
|
-
import * as
|
|
21669
|
+
import * as path32 from "node:path";
|
|
20830
21670
|
var DEFAULT_IGNORE5 = [
|
|
20831
21671
|
"node_modules",
|
|
20832
21672
|
".git",
|
|
@@ -20997,7 +21837,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
20997
21837
|
opts.lines.push(opts.prefix + branch + displayName);
|
|
20998
21838
|
if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
|
|
20999
21839
|
const childPrefix = opts.prefix + connector;
|
|
21000
|
-
await walkDir(
|
|
21840
|
+
await walkDir(path32.join(dir, entry.name), depth + 1, {
|
|
21001
21841
|
...opts,
|
|
21002
21842
|
prefix: childPrefix,
|
|
21003
21843
|
isLast
|
|
@@ -21010,7 +21850,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
21010
21850
|
init_spawn_stream();
|
|
21011
21851
|
init_util();
|
|
21012
21852
|
init_legacy_bridge();
|
|
21013
|
-
import * as
|
|
21853
|
+
import * as path33 from "node:path";
|
|
21014
21854
|
var typecheckTool = {
|
|
21015
21855
|
name: "typecheck",
|
|
21016
21856
|
category: "Code Quality",
|
|
@@ -21113,8 +21953,8 @@ async function findTsConfig(cwd) {
|
|
|
21113
21953
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
21114
21954
|
for (const f of candidates) {
|
|
21115
21955
|
try {
|
|
21116
|
-
const s = await stat17(
|
|
21117
|
-
if (s.isFile()) return
|
|
21956
|
+
const s = await stat17(path33.join(cwd, f));
|
|
21957
|
+
if (s.isFile()) return path33.join(cwd, f);
|
|
21118
21958
|
} catch {
|
|
21119
21959
|
}
|
|
21120
21960
|
}
|
|
@@ -21123,7 +21963,8 @@ async function findTsConfig(cwd) {
|
|
|
21123
21963
|
|
|
21124
21964
|
// src/write.ts
|
|
21125
21965
|
import * as fs29 from "node:fs/promises";
|
|
21126
|
-
import {
|
|
21966
|
+
import { ToolValidationError as ToolValidationError8 } from "@wrongstack/core/types";
|
|
21967
|
+
import { atomicWrite as atomicWrite5, unifiedDiff as unifiedDiff3 } from "@wrongstack/core/utils";
|
|
21127
21968
|
init_util();
|
|
21128
21969
|
var writeTool = {
|
|
21129
21970
|
name: "write",
|