@wrongstack/tools 0.293.0 → 0.295.1
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/auto-proceed-loop-guard.d.ts +31 -0
- package/dist/auto-proceed-loop-guard.d.ts.map +1 -1
- package/dist/auto-proceed-loop-guard.js +24 -1
- package/dist/auto-proceed-loop-guard.js.map +2 -2
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +105 -75
- 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 +1650 -640
- 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 +1068 -372
- 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 +993 -348
- 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 +99 -44
- 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 +81 -31
- package/dist/glob.js.map +4 -4
- package/dist/grep.d.ts +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +90 -22
- package/dist/grep.js.map +4 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1895 -771
- 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-evidence-bridge.d.ts +21 -0
- package/dist/kanban-evidence-bridge.d.ts.map +1 -0
- package/dist/kanban.d.ts +20 -3
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +256 -32
- package/dist/kanban.js.map +3 -3
- 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 +1650 -640
- 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 +93 -38
- 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 +22402 -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 +3 -15
- package/dist/tree.js.map +2 -2
- 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/win32.d.ts +10 -0
- package/dist/win32.d.ts.map +1 -0
- package/dist/win32.js +67 -0
- package/dist/win32.js.map +7 -0
- 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 +11 -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,8 @@ function emitStructuredLog(level, event, message, error) {
|
|
|
4984
5067
|
}
|
|
4985
5068
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
4986
5069
|
var STALE_THRESHOLD_MS = 3e4;
|
|
4987
|
-
var
|
|
5070
|
+
var LOCK_STALE_MS = 3e4;
|
|
5071
|
+
var LOCKFILE = ".process-registry.lock";
|
|
4988
5072
|
function generateInstanceId() {
|
|
4989
5073
|
const hostname2 = os2.hostname();
|
|
4990
5074
|
const pid = process.pid;
|
|
@@ -5012,21 +5096,26 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
|
5012
5096
|
try {
|
|
5013
5097
|
const content = await fs6.readFile(lockfilePath, "utf-8");
|
|
5014
5098
|
const parts = content.split(":");
|
|
5015
|
-
const
|
|
5016
|
-
const
|
|
5017
|
-
|
|
5099
|
+
const lockPid = parseInt(parts[0] ?? "0", 10);
|
|
5100
|
+
const lockTs = Number(parts[parts.length - 1]);
|
|
5101
|
+
const staleByAge = Number.isFinite(lockTs) && Date.now() - lockTs > LOCK_STALE_MS;
|
|
5102
|
+
let holderDead = false;
|
|
5103
|
+
if (process.platform !== "win32" && Number.isFinite(lockPid) && lockPid > 0) {
|
|
5018
5104
|
try {
|
|
5019
5105
|
process.kill(lockPid, 0);
|
|
5020
5106
|
} catch {
|
|
5021
|
-
|
|
5022
|
-
continue;
|
|
5107
|
+
holderDead = true;
|
|
5023
5108
|
}
|
|
5024
5109
|
}
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5110
|
+
if (holderDead || staleByAge) {
|
|
5111
|
+
await fs6.unlink(lockfilePath).catch(() => {
|
|
5112
|
+
});
|
|
5113
|
+
continue;
|
|
5029
5114
|
}
|
|
5115
|
+
} catch {
|
|
5116
|
+
await fs6.unlink(lockfilePath).catch(() => {
|
|
5117
|
+
});
|
|
5118
|
+
continue;
|
|
5030
5119
|
}
|
|
5031
5120
|
await new Promise((r) => setTimeout(r, 100));
|
|
5032
5121
|
continue;
|
|
@@ -5036,25 +5125,35 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
|
5036
5125
|
}
|
|
5037
5126
|
throw new Error(`Failed to acquire lock after ${timeoutMs}ms`);
|
|
5038
5127
|
}
|
|
5128
|
+
function freshRegistryData() {
|
|
5129
|
+
return {
|
|
5130
|
+
version: 1,
|
|
5131
|
+
instances: /* @__PURE__ */ new Map(),
|
|
5132
|
+
protectedPatterns: ["wrongstack", "node"],
|
|
5133
|
+
lastCleanup: Date.now()
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5039
5136
|
async function readRegistryFile(filePath) {
|
|
5137
|
+
let content;
|
|
5040
5138
|
try {
|
|
5041
|
-
|
|
5042
|
-
const parsed = JSON.parse(content);
|
|
5043
|
-
if (parsed.instances && Array.isArray(parsed.instances)) {
|
|
5044
|
-
parsed.instances = new Map(parsed.instances);
|
|
5045
|
-
}
|
|
5046
|
-
return parsed;
|
|
5139
|
+
content = await fs6.readFile(filePath, "utf-8");
|
|
5047
5140
|
} catch (err) {
|
|
5048
|
-
if (isNodeError(err) && err.code === "ENOENT")
|
|
5049
|
-
return {
|
|
5050
|
-
version: 1,
|
|
5051
|
-
instances: /* @__PURE__ */ new Map(),
|
|
5052
|
-
protectedPatterns: ["wrongstack", "node"],
|
|
5053
|
-
lastCleanup: Date.now()
|
|
5054
|
-
};
|
|
5055
|
-
}
|
|
5141
|
+
if (isNodeError(err) && err.code === "ENOENT") return freshRegistryData();
|
|
5056
5142
|
throw err;
|
|
5057
5143
|
}
|
|
5144
|
+
try {
|
|
5145
|
+
const parsed = JSON.parse(content);
|
|
5146
|
+
if (!parsed || typeof parsed !== "object") return freshRegistryData();
|
|
5147
|
+
const base = freshRegistryData();
|
|
5148
|
+
return {
|
|
5149
|
+
version: 1,
|
|
5150
|
+
instances: Array.isArray(parsed.instances) ? new Map(parsed.instances) : base.instances,
|
|
5151
|
+
protectedPatterns: Array.isArray(parsed.protectedPatterns) ? parsed.protectedPatterns : base.protectedPatterns,
|
|
5152
|
+
lastCleanup: typeof parsed.lastCleanup === "number" ? parsed.lastCleanup : base.lastCleanup
|
|
5153
|
+
};
|
|
5154
|
+
} catch {
|
|
5155
|
+
return freshRegistryData();
|
|
5156
|
+
}
|
|
5058
5157
|
}
|
|
5059
5158
|
async function writeRegistryFile(filePath, data) {
|
|
5060
5159
|
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
@@ -5073,12 +5172,18 @@ var PersistentProcessRegistry = class {
|
|
|
5073
5172
|
lockPath;
|
|
5074
5173
|
baseRegistry;
|
|
5075
5174
|
heartbeatInterval = null;
|
|
5175
|
+
cleanupInterval = null;
|
|
5176
|
+
heartbeatRunning = false;
|
|
5177
|
+
cleanupRunning = false;
|
|
5076
5178
|
isShuttingDown = false;
|
|
5179
|
+
onProcessExit = () => {
|
|
5180
|
+
void this.syncToPersistent();
|
|
5181
|
+
};
|
|
5077
5182
|
constructor(baseRegistry) {
|
|
5078
5183
|
this.instanceId = generateInstanceId();
|
|
5079
|
-
const
|
|
5080
|
-
this.registryPath = path9.join(
|
|
5081
|
-
this.lockPath = path9.join(
|
|
5184
|
+
const globalRoot = wstackGlobalRoot3();
|
|
5185
|
+
this.registryPath = path9.join(globalRoot, REGISTRY_FILE);
|
|
5186
|
+
this.lockPath = path9.join(globalRoot, LOCKFILE);
|
|
5082
5187
|
this.baseRegistry = baseRegistry ?? getProcessRegistry();
|
|
5083
5188
|
this.ensureDirectory().catch((err) => {
|
|
5084
5189
|
emitStructuredLog("warn", "process_registry.dir_create_failed", "PersistentProcessRegistry: failed to create .wrongstack directory", err);
|
|
@@ -5097,16 +5202,18 @@ var PersistentProcessRegistry = class {
|
|
|
5097
5202
|
*/
|
|
5098
5203
|
start() {
|
|
5099
5204
|
if (this.heartbeatInterval) return;
|
|
5100
|
-
this.
|
|
5205
|
+
this.isShuttingDown = false;
|
|
5206
|
+
this.heartbeat();
|
|
5101
5207
|
this.heartbeatInterval = setInterval(() => {
|
|
5102
5208
|
this.heartbeat();
|
|
5103
5209
|
}, HEARTBEAT_INTERVAL_MS);
|
|
5104
5210
|
this.heartbeatInterval.unref?.();
|
|
5105
|
-
setInterval(() => {
|
|
5106
|
-
this.
|
|
5107
|
-
}, STALE_THRESHOLD_MS)
|
|
5211
|
+
this.cleanupInterval = setInterval(() => {
|
|
5212
|
+
this.cleanup();
|
|
5213
|
+
}, STALE_THRESHOLD_MS);
|
|
5214
|
+
this.cleanupInterval.unref?.();
|
|
5108
5215
|
this.registerMainProcess();
|
|
5109
|
-
process.on("exit",
|
|
5216
|
+
process.on("exit", this.onProcessExit);
|
|
5110
5217
|
}
|
|
5111
5218
|
/**
|
|
5112
5219
|
* Stop the heartbeat and clean up.
|
|
@@ -5117,7 +5224,12 @@ var PersistentProcessRegistry = class {
|
|
|
5117
5224
|
clearInterval(this.heartbeatInterval);
|
|
5118
5225
|
this.heartbeatInterval = null;
|
|
5119
5226
|
}
|
|
5120
|
-
this.
|
|
5227
|
+
if (this.cleanupInterval) {
|
|
5228
|
+
clearInterval(this.cleanupInterval);
|
|
5229
|
+
this.cleanupInterval = null;
|
|
5230
|
+
}
|
|
5231
|
+
process.off("exit", this.onProcessExit);
|
|
5232
|
+
void this.syncToPersistent();
|
|
5121
5233
|
}
|
|
5122
5234
|
/**
|
|
5123
5235
|
* Register the main WrongStack process as protected.
|
|
@@ -5201,8 +5313,18 @@ var PersistentProcessRegistry = class {
|
|
|
5201
5313
|
* Send heartbeat to mark all this instance's processes as alive.
|
|
5202
5314
|
*/
|
|
5203
5315
|
heartbeat() {
|
|
5204
|
-
if (this.isShuttingDown) return;
|
|
5205
|
-
this.
|
|
5316
|
+
if (this.isShuttingDown || this.heartbeatRunning) return;
|
|
5317
|
+
this.heartbeatRunning = true;
|
|
5318
|
+
void this.syncToPersistent().finally(() => {
|
|
5319
|
+
this.heartbeatRunning = false;
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
cleanup() {
|
|
5323
|
+
if (this.isShuttingDown || this.cleanupRunning) return;
|
|
5324
|
+
this.cleanupRunning = true;
|
|
5325
|
+
void this.cleanupStaleEntries().finally(() => {
|
|
5326
|
+
this.cleanupRunning = false;
|
|
5327
|
+
});
|
|
5206
5328
|
}
|
|
5207
5329
|
/**
|
|
5208
5330
|
* Sync this instance's processes to the persistent registry.
|
|
@@ -5918,14 +6040,12 @@ var bashTool = {
|
|
|
5918
6040
|
const detached = !isWin5;
|
|
5919
6041
|
const startedAt = Date.now();
|
|
5920
6042
|
if (input.background) {
|
|
5921
|
-
let buf2 = "";
|
|
5922
|
-
let truncated = false;
|
|
5923
6043
|
const child2 = spawn3(shell, args, {
|
|
5924
6044
|
cwd: ctx.projectRoot,
|
|
5925
6045
|
env,
|
|
5926
6046
|
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
5927
6047
|
// and POSIX shells ignore stdin when given the command inline.
|
|
5928
|
-
stdio: [plan.useStdin ? "pipe" : "ignore", "
|
|
6048
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "ignore", "ignore"],
|
|
5929
6049
|
// win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
|
|
5930
6050
|
// DETACHED_PROCESS (detached: true) is set, so the console-less
|
|
5931
6051
|
// cmd.exe's grandchildren (node, dev servers) each allocate a fresh
|
|
@@ -5944,8 +6064,8 @@ var bashTool = {
|
|
|
5944
6064
|
}
|
|
5945
6065
|
}
|
|
5946
6066
|
const pid2 = child2.pid;
|
|
5947
|
-
|
|
5948
|
-
|
|
6067
|
+
const stdoutBytes2 = 0;
|
|
6068
|
+
const stderrBytes2 = 0;
|
|
5949
6069
|
let telemetryCompleted2 = false;
|
|
5950
6070
|
emitProcessStarted2({
|
|
5951
6071
|
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
@@ -5978,41 +6098,17 @@ var bashTool = {
|
|
|
5978
6098
|
startedAt: Date.now(),
|
|
5979
6099
|
sessionId: ctx.session?.id,
|
|
5980
6100
|
child: child2,
|
|
5981
|
-
processGroupLeader: detached && child2.pid === pid2
|
|
6101
|
+
processGroupLeader: detached && child2.pid === pid2,
|
|
6102
|
+
background: true
|
|
5982
6103
|
});
|
|
5983
6104
|
child2.on("close", () => registry.unregister(pid2));
|
|
5984
6105
|
}
|
|
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
6106
|
child2.on("error", () => {
|
|
6009
|
-
cleanupBackground();
|
|
6010
6107
|
if (typeof pid2 === "number") registry.unregister(pid2);
|
|
6011
6108
|
registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
|
|
6012
6109
|
completeBackground(1);
|
|
6013
6110
|
});
|
|
6014
6111
|
child2.on("close", (code, signal) => {
|
|
6015
|
-
cleanupBackground();
|
|
6016
6112
|
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
6017
6113
|
completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
6018
6114
|
});
|
|
@@ -6020,7 +6116,7 @@ var bashTool = {
|
|
|
6020
6116
|
yield {
|
|
6021
6117
|
type: "final",
|
|
6022
6118
|
output: {
|
|
6023
|
-
output:
|
|
6119
|
+
output: "",
|
|
6024
6120
|
exit_code: null,
|
|
6025
6121
|
timed_out: false,
|
|
6026
6122
|
pid: pid2
|
|
@@ -6291,6 +6387,7 @@ ${hint}` : ""),
|
|
|
6291
6387
|
for (const entry of registry.bySession(sessionId)) {
|
|
6292
6388
|
if (entry.name !== "bash") continue;
|
|
6293
6389
|
if (entry.child.exitCode !== null) continue;
|
|
6390
|
+
if (entry.background) continue;
|
|
6294
6391
|
if (entry.protected) continue;
|
|
6295
6392
|
registry.kill(entry.pid, { force: true });
|
|
6296
6393
|
}
|
|
@@ -6300,7 +6397,7 @@ ${hint}` : ""),
|
|
|
6300
6397
|
// src/batch-tool-use.ts
|
|
6301
6398
|
import {
|
|
6302
6399
|
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6303
|
-
} from "@wrongstack/core";
|
|
6400
|
+
} from "@wrongstack/core/types";
|
|
6304
6401
|
var batchToolUseTool = {
|
|
6305
6402
|
name: "batch_tool_use",
|
|
6306
6403
|
category: "Meta",
|
|
@@ -6423,7 +6520,7 @@ async function executeSingle(call, ctx, governedExecute) {
|
|
|
6423
6520
|
|
|
6424
6521
|
// src/browser/tools.ts
|
|
6425
6522
|
import * as path12 from "node:path";
|
|
6426
|
-
import { ToolCapabilities } from "@wrongstack/core";
|
|
6523
|
+
import { ToolCapabilities } from "@wrongstack/core/security";
|
|
6427
6524
|
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
6428
6525
|
|
|
6429
6526
|
// src/browser/manager.ts
|
|
@@ -7085,6 +7182,10 @@ var BrowserSessionManager = class {
|
|
|
7085
7182
|
const ids = [...this.sessions.values()].filter((session) => session.ownerId === ownerId).map((session) => session.id);
|
|
7086
7183
|
for (const id of ids) await this.close(id, ownerId).catch(() => void 0);
|
|
7087
7184
|
}
|
|
7185
|
+
/** True when the manager owns no live browser contexts. */
|
|
7186
|
+
isIdle() {
|
|
7187
|
+
return this.sessions.size === 0;
|
|
7188
|
+
}
|
|
7088
7189
|
async dispose() {
|
|
7089
7190
|
for (const session of [...this.sessions.values()]) {
|
|
7090
7191
|
await session.context.close().catch(() => void 0);
|
|
@@ -7266,12 +7367,17 @@ function managerFor(ctx) {
|
|
|
7266
7367
|
if (cleanupSignalByContext.get(ctx) !== runSignal) {
|
|
7267
7368
|
cleanupSignalByContext.set(ctx, runSignal);
|
|
7268
7369
|
const managerForRun = manager;
|
|
7370
|
+
const rootForRun = root;
|
|
7269
7371
|
const ownerForRun = owner(ctx);
|
|
7270
7372
|
ctx.registerAbortHook?.(async () => {
|
|
7271
7373
|
if (cleanupSignalByContext.get(ctx) === runSignal) {
|
|
7272
7374
|
cleanupSignalByContext.delete(ctx);
|
|
7273
7375
|
}
|
|
7274
7376
|
await managerForRun.closeOwner(ownerForRun);
|
|
7377
|
+
if (managerForRun.isIdle() && managers.get(rootForRun) === managerForRun) {
|
|
7378
|
+
managers.delete(rootForRun);
|
|
7379
|
+
await managerForRun.dispose();
|
|
7380
|
+
}
|
|
7275
7381
|
});
|
|
7276
7382
|
}
|
|
7277
7383
|
return manager;
|
|
@@ -7654,7 +7760,7 @@ for (const tool of browserTools) tool.icon = "web";
|
|
|
7654
7760
|
for (const tool of browserTools) tool.timeoutMs ??= 6e4;
|
|
7655
7761
|
|
|
7656
7762
|
// src/codebase-index/writer.ts
|
|
7657
|
-
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
7763
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
7658
7764
|
|
|
7659
7765
|
// src/codebase-index/circuit-breaker.ts
|
|
7660
7766
|
var CircuitOpenError = class extends Error {
|
|
@@ -7741,7 +7847,7 @@ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
|
7741
7847
|
import { createRequire } from "node:module";
|
|
7742
7848
|
import * as fs9 from "node:fs";
|
|
7743
7849
|
import * as path13 from "node:path";
|
|
7744
|
-
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
7850
|
+
import { resolveWstackPaths as resolveWstackPaths2, sqliteCachePragmas } from "@wrongstack/core/utils";
|
|
7745
7851
|
|
|
7746
7852
|
// src/codebase-index/schema.ts
|
|
7747
7853
|
var SCHEMA_VERSION = 3;
|
|
@@ -7797,23 +7903,13 @@ function buildBm25Index(docs) {
|
|
|
7797
7903
|
const tokens = tokenise(d.text);
|
|
7798
7904
|
return { id: d.id, tokens, raw: d.text, len: tokens.length };
|
|
7799
7905
|
});
|
|
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
7906
|
const N = documents.length;
|
|
7811
7907
|
const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
|
|
7812
7908
|
const avgLen = N === 0 ? 0 : totalLen / N;
|
|
7813
|
-
return new Bm25Index(documents,
|
|
7909
|
+
return new Bm25Index(documents, N, avgLen);
|
|
7814
7910
|
}
|
|
7815
7911
|
var Bm25Index = class {
|
|
7816
|
-
constructor(documents,
|
|
7912
|
+
constructor(documents, N, avgLen) {
|
|
7817
7913
|
this.documents = documents;
|
|
7818
7914
|
this.N = N;
|
|
7819
7915
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
@@ -7821,9 +7917,19 @@ var Bm25Index = class {
|
|
|
7821
7917
|
documents;
|
|
7822
7918
|
N;
|
|
7823
7919
|
safeAvgLen;
|
|
7920
|
+
/** Lazily-built id→doc index so getDoc is O(1) instead of an O(D) linear find. */
|
|
7921
|
+
_byId;
|
|
7824
7922
|
score(query, filter) {
|
|
7825
7923
|
const qTokens = tokenise(query);
|
|
7826
7924
|
if (qTokens.length === 0) return [];
|
|
7925
|
+
const dfByTerm = /* @__PURE__ */ new Map();
|
|
7926
|
+
for (const qTerm of qTokens) {
|
|
7927
|
+
let dfVal = 0;
|
|
7928
|
+
for (const candidate of this.documents) {
|
|
7929
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
7930
|
+
}
|
|
7931
|
+
dfByTerm.set(qTerm, dfVal);
|
|
7932
|
+
}
|
|
7827
7933
|
const results = [];
|
|
7828
7934
|
for (const doc of this.documents) {
|
|
7829
7935
|
if (filter && !filter(doc.id)) continue;
|
|
@@ -7834,10 +7940,7 @@ var Bm25Index = class {
|
|
|
7834
7940
|
if (t.startsWith(qTerm)) tf++;
|
|
7835
7941
|
}
|
|
7836
7942
|
if (tf === 0) continue;
|
|
7837
|
-
|
|
7838
|
-
for (const candidate of this.documents) {
|
|
7839
|
-
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
7840
|
-
}
|
|
7943
|
+
const dfVal = dfByTerm.get(qTerm) ?? 0;
|
|
7841
7944
|
if (dfVal === 0) continue;
|
|
7842
7945
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
7843
7946
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -7849,7 +7952,10 @@ var Bm25Index = class {
|
|
|
7849
7952
|
return results;
|
|
7850
7953
|
}
|
|
7851
7954
|
getDoc(id) {
|
|
7852
|
-
|
|
7955
|
+
if (!this._byId) {
|
|
7956
|
+
this._byId = new Map(this.documents.map((d) => [d.id, d]));
|
|
7957
|
+
}
|
|
7958
|
+
return this._byId.get(id);
|
|
7853
7959
|
}
|
|
7854
7960
|
extractSnippet(docId, queryTokens, radius = 40) {
|
|
7855
7961
|
const doc = this.getDoc(docId);
|
|
@@ -8011,6 +8117,25 @@ var IndexStore = class _IndexStore {
|
|
|
8011
8117
|
* and reuse it. Cleared in {@link close} when the connection is torn down.
|
|
8012
8118
|
*/
|
|
8013
8119
|
stmtCache = /* @__PURE__ */ new Map();
|
|
8120
|
+
/**
|
|
8121
|
+
* Cached full-corpus BM25 index for the FTS5-unavailable fallback path.
|
|
8122
|
+
* Built lazily on the first `searchRankedFallback` call and invalidated
|
|
8123
|
+
* (via `bm25Dirty`) whenever the `symbols` table is mutated. Computing
|
|
8124
|
+
* IDF over the full corpus is also more correct than the old per-query
|
|
8125
|
+
* candidate-subset IDF.
|
|
8126
|
+
*
|
|
8127
|
+
* Cache-lifecycle invariants (single source of truth lives at the
|
|
8128
|
+
* `invalidateBm25()` helper — see its docblock for the "every mutation
|
|
8129
|
+
* MUST call this" contract):
|
|
8130
|
+
* - declaration: this field + `bm25Dirty` (here)
|
|
8131
|
+
* - invalidation: `invalidateBm25()` flips the flag and nulls the cache
|
|
8132
|
+
* - build: `getOrBuildBm25()` rebuilds against current `symbols` rows
|
|
8133
|
+
* - teardown: `close()` resets the flag and nulls the cache
|
|
8134
|
+
*/
|
|
8135
|
+
bm25Cache = null;
|
|
8136
|
+
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
8137
|
+
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
8138
|
+
bm25Dirty = true;
|
|
8014
8139
|
/** Prepare-once helper: compile `sql` on first use, reuse thereafter. */
|
|
8015
8140
|
stmt(sql) {
|
|
8016
8141
|
let s = this.stmtCache.get(sql);
|
|
@@ -8058,7 +8183,15 @@ var IndexStore = class _IndexStore {
|
|
|
8058
8183
|
this.db = new Database(path13.join(this.indexDir, DB_FILE));
|
|
8059
8184
|
try {
|
|
8060
8185
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
8061
|
-
this.db.exec("PRAGMA
|
|
8186
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
8187
|
+
this.db.exec("PRAGMA busy_timeout = 15000");
|
|
8188
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
8189
|
+
const cache2 = sqliteCachePragmas();
|
|
8190
|
+
this.db.exec(`PRAGMA cache_size = -${cache2.cacheSizeKiB}`);
|
|
8191
|
+
this.db.exec(`PRAGMA mmap_size = ${cache2.mmapBytes}`);
|
|
8192
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
8193
|
+
this.db.exec("PRAGMA journal_size_limit = 67108864");
|
|
8194
|
+
this.db.exec("PRAGMA wal_autocheckpoint = 1000");
|
|
8062
8195
|
} catch {
|
|
8063
8196
|
}
|
|
8064
8197
|
this.initSchema();
|
|
@@ -8070,7 +8203,7 @@ var IndexStore = class _IndexStore {
|
|
|
8070
8203
|
value TEXT NOT NULL
|
|
8071
8204
|
);
|
|
8072
8205
|
`);
|
|
8073
|
-
const storedRows = this.
|
|
8206
|
+
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
8074
8207
|
const storedVersion = storedRows.length ? Number(storedRows[0]?.value) : null;
|
|
8075
8208
|
if (storedVersion !== null && storedVersion !== SCHEMA_VERSION) {
|
|
8076
8209
|
this.db.exec(`
|
|
@@ -8079,9 +8212,9 @@ var IndexStore = class _IndexStore {
|
|
|
8079
8212
|
DROP TABLE IF EXISTS refs;
|
|
8080
8213
|
`);
|
|
8081
8214
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
8082
|
-
this.
|
|
8215
|
+
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
|
|
8083
8216
|
} else if (storedVersion === null) {
|
|
8084
|
-
this.
|
|
8217
|
+
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
|
|
8085
8218
|
}
|
|
8086
8219
|
this.db.exec(`
|
|
8087
8220
|
CREATE TABLE IF NOT EXISTS files (
|
|
@@ -8112,6 +8245,7 @@ var IndexStore = class _IndexStore {
|
|
|
8112
8245
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
|
|
8113
8246
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
|
|
8114
8247
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
|
|
8248
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)");
|
|
8115
8249
|
this.db.exec(`
|
|
8116
8250
|
CREATE TABLE IF NOT EXISTS refs (
|
|
8117
8251
|
id INTEGER PRIMARY KEY,
|
|
@@ -8132,65 +8266,167 @@ var IndexStore = class _IndexStore {
|
|
|
8132
8266
|
);
|
|
8133
8267
|
this.ftsAvailable = true;
|
|
8134
8268
|
const symbolCount = Number(
|
|
8135
|
-
this.
|
|
8269
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8136
8270
|
);
|
|
8137
8271
|
const ftsCount = Number(
|
|
8138
|
-
this.
|
|
8272
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8139
8273
|
);
|
|
8140
8274
|
if (symbolCount !== ftsCount) {
|
|
8141
8275
|
this.db.exec("DELETE FROM symbols_fts");
|
|
8142
|
-
const
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8276
|
+
const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8277
|
+
this.bulkInsertFts(
|
|
8278
|
+
rows.map((row) => ({
|
|
8279
|
+
id: row.id,
|
|
8280
|
+
text: buildIndexableText(row.name, row.signature, row.doc_comment)
|
|
8281
|
+
}))
|
|
8282
|
+
);
|
|
8283
|
+
this.invalidateBm25();
|
|
8147
8284
|
}
|
|
8148
8285
|
} catch {
|
|
8149
8286
|
this.ftsAvailable = false;
|
|
8150
8287
|
}
|
|
8288
|
+
this.ensureNextSymbolIdSeeded();
|
|
8289
|
+
}
|
|
8290
|
+
// ─── ID allocation & bulk helpers ────────────────────────────────────────────
|
|
8291
|
+
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
8292
|
+
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
8293
|
+
static MAX_SQL_VARS = 900;
|
|
8294
|
+
/**
|
|
8295
|
+
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
8296
|
+
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
8297
|
+
* re-reads and advances the counter atomically.
|
|
8298
|
+
*/
|
|
8299
|
+
ensureNextSymbolIdSeeded() {
|
|
8300
|
+
const existing = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8301
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8302
|
+
);
|
|
8303
|
+
if (existing?.value !== void 0) return;
|
|
8304
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8305
|
+
const next = (maxRows[0]?.m ?? 0) + 1;
|
|
8306
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8307
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8308
|
+
String(next)
|
|
8309
|
+
);
|
|
8310
|
+
}
|
|
8311
|
+
/**
|
|
8312
|
+
* Reserve `count` consecutive symbol ids. MUST run inside BEGIN IMMEDIATE
|
|
8313
|
+
* so concurrent indexers cannot hand out overlapping ranges.
|
|
8314
|
+
*/
|
|
8315
|
+
allocateSymbolIds(count) {
|
|
8316
|
+
if (count <= 0) return this.getMaxSymbolId() + 1;
|
|
8317
|
+
this.ensureNextSymbolIdSeeded();
|
|
8318
|
+
const row = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8319
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8320
|
+
);
|
|
8321
|
+
const start = Math.max(1, Number(row?.value ?? 1) || 1);
|
|
8322
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8323
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8324
|
+
String(start + count)
|
|
8325
|
+
);
|
|
8326
|
+
return start;
|
|
8327
|
+
}
|
|
8328
|
+
/** Multi-row INSERT for symbols — amortizes prepare/bind overhead in large batches. */
|
|
8329
|
+
bulkInsertSymbols(rows) {
|
|
8330
|
+
if (rows.length === 0) return;
|
|
8331
|
+
const cols = 12;
|
|
8332
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8333
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8334
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8335
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
8336
|
+
const stmt = this.stmt(
|
|
8337
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8338
|
+
VALUES ${placeholders}`
|
|
8339
|
+
);
|
|
8340
|
+
const binds = [];
|
|
8341
|
+
for (const r of chunk) {
|
|
8342
|
+
binds.push(
|
|
8343
|
+
r.id,
|
|
8344
|
+
r.lang,
|
|
8345
|
+
r.kind,
|
|
8346
|
+
r.name,
|
|
8347
|
+
r.file,
|
|
8348
|
+
r.line,
|
|
8349
|
+
r.col,
|
|
8350
|
+
r.signature,
|
|
8351
|
+
r.docComment,
|
|
8352
|
+
r.scope,
|
|
8353
|
+
r.text,
|
|
8354
|
+
r.file
|
|
8355
|
+
);
|
|
8356
|
+
}
|
|
8357
|
+
stmt.run(...binds);
|
|
8358
|
+
}
|
|
8359
|
+
}
|
|
8360
|
+
bulkInsertFts(rows) {
|
|
8361
|
+
if (!this.ftsAvailable || rows.length === 0) return;
|
|
8362
|
+
const cols = 2;
|
|
8363
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8364
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8365
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8366
|
+
const placeholders = chunk.map(() => "(?, ?)").join(", ");
|
|
8367
|
+
const stmt = this.stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders}`);
|
|
8368
|
+
const binds = [];
|
|
8369
|
+
for (const r of chunk) binds.push(r.id, r.text);
|
|
8370
|
+
stmt.run(...binds);
|
|
8371
|
+
}
|
|
8372
|
+
}
|
|
8373
|
+
bulkInsertRefs(refs) {
|
|
8374
|
+
if (refs.length === 0) return;
|
|
8375
|
+
const cols = 5;
|
|
8376
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8377
|
+
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
8378
|
+
const chunk = refs.slice(i, i + chunkSize);
|
|
8379
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
8380
|
+
const stmt = this.stmt(
|
|
8381
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line) VALUES ${placeholders}`
|
|
8382
|
+
);
|
|
8383
|
+
const binds = [];
|
|
8384
|
+
for (const ref of chunk) {
|
|
8385
|
+
binds.push(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8386
|
+
}
|
|
8387
|
+
stmt.run(...binds);
|
|
8388
|
+
}
|
|
8151
8389
|
}
|
|
8152
8390
|
// ─── Symbol CRUD ─────────────────────────────────────────────────────────────
|
|
8153
8391
|
/**
|
|
8154
8392
|
* 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).
|
|
8393
|
+
* `COMMIT`. Id ranges come from the `next_symbol_id` metadata counter
|
|
8394
|
+
* (O(1)); multi-row INSERT amortizes bind overhead for large files.
|
|
8159
8395
|
*
|
|
8160
8396
|
* @returns The symbols array with `id` fields populated so the caller can
|
|
8161
8397
|
* use them for refs without re-reading from the DB.
|
|
8162
8398
|
*/
|
|
8163
8399
|
insertSymbols(symbols) {
|
|
8400
|
+
this.invalidateBm25();
|
|
8164
8401
|
return this.runWithRetry(() => {
|
|
8165
8402
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8166
8403
|
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;
|
|
8404
|
+
let nextId = this.allocateSymbolIds(symbols.length);
|
|
8174
8405
|
const result = [];
|
|
8406
|
+
const bulk = [];
|
|
8407
|
+
const ftsRows = [];
|
|
8175
8408
|
for (const s of symbols) {
|
|
8176
8409
|
const id = nextId++;
|
|
8177
|
-
|
|
8410
|
+
bulk.push({
|
|
8178
8411
|
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
|
-
|
|
8412
|
+
lang: s.lang,
|
|
8413
|
+
kind: s.kind,
|
|
8414
|
+
name: s.name,
|
|
8415
|
+
file: s.file,
|
|
8416
|
+
line: s.line,
|
|
8417
|
+
col: s.col,
|
|
8418
|
+
signature: s.signature,
|
|
8419
|
+
docComment: s.docComment,
|
|
8420
|
+
scope: s.scope,
|
|
8421
|
+
text: s.text
|
|
8422
|
+
});
|
|
8423
|
+
if (this.ftsAvailable) {
|
|
8424
|
+
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
8425
|
+
}
|
|
8192
8426
|
result.push({ ...s, id });
|
|
8193
8427
|
}
|
|
8428
|
+
this.bulkInsertSymbols(bulk);
|
|
8429
|
+
this.bulkInsertFts(ftsRows);
|
|
8194
8430
|
this.db.exec("COMMIT");
|
|
8195
8431
|
return result;
|
|
8196
8432
|
} catch (err) {
|
|
@@ -8200,6 +8436,7 @@ var IndexStore = class _IndexStore {
|
|
|
8200
8436
|
});
|
|
8201
8437
|
}
|
|
8202
8438
|
deleteSymbolsForFile(file) {
|
|
8439
|
+
this.invalidateBm25();
|
|
8203
8440
|
this.runWithRetry(() => {
|
|
8204
8441
|
if (this.ftsAvailable) {
|
|
8205
8442
|
this.stmt(
|
|
@@ -8215,17 +8452,20 @@ var IndexStore = class _IndexStore {
|
|
|
8215
8452
|
* dropped the `files` row, leaving its symbols orphaned but still searchable.
|
|
8216
8453
|
*/
|
|
8217
8454
|
deleteFile(file) {
|
|
8455
|
+
this.invalidateBm25();
|
|
8218
8456
|
this.runWithRetry(() => {
|
|
8219
8457
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8220
8458
|
try {
|
|
8221
8459
|
if (this.ftsAvailable) {
|
|
8222
|
-
this.
|
|
8460
|
+
this.stmt(
|
|
8223
8461
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8224
8462
|
).run(file);
|
|
8225
8463
|
}
|
|
8226
|
-
this.
|
|
8227
|
-
|
|
8228
|
-
|
|
8464
|
+
this.stmt(
|
|
8465
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8466
|
+
).run(file);
|
|
8467
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8468
|
+
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
8229
8469
|
this.db.exec("COMMIT");
|
|
8230
8470
|
} catch (err) {
|
|
8231
8471
|
this.db.exec("ROLLBACK");
|
|
@@ -8262,7 +8502,7 @@ var IndexStore = class _IndexStore {
|
|
|
8262
8502
|
};
|
|
8263
8503
|
}
|
|
8264
8504
|
getAllFileMetas() {
|
|
8265
|
-
return this.
|
|
8505
|
+
return this.stmt("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8266
8506
|
file: r.file,
|
|
8267
8507
|
lang: r.lang,
|
|
8268
8508
|
mtimeMs: r.mtime_ms,
|
|
@@ -8271,7 +8511,32 @@ var IndexStore = class _IndexStore {
|
|
|
8271
8511
|
}));
|
|
8272
8512
|
}
|
|
8273
8513
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8274
|
-
search(query, filter) {
|
|
8514
|
+
search(query, filter, opts) {
|
|
8515
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8516
|
+
if (built === null) return [];
|
|
8517
|
+
const { where, values } = built;
|
|
8518
|
+
const limit = typeof opts?.limit === "number" && Number.isFinite(opts.limit) ? Math.max(0, Math.trunc(opts.limit)) : void 0;
|
|
8519
|
+
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
8520
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
|
|
8521
|
+
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
8522
|
+
const rows = this.stmt(sql).all(...binds);
|
|
8523
|
+
return rows.map((r) => ({
|
|
8524
|
+
id: r.id,
|
|
8525
|
+
lang: r.lang,
|
|
8526
|
+
kind: r.kind,
|
|
8527
|
+
name: r.name,
|
|
8528
|
+
file: r.file,
|
|
8529
|
+
line: r.line,
|
|
8530
|
+
col: r.col,
|
|
8531
|
+
signature: r.signature,
|
|
8532
|
+
docComment: r.doc_comment,
|
|
8533
|
+
score: 0,
|
|
8534
|
+
snippet: "",
|
|
8535
|
+
lspKind: filter?.lspKind
|
|
8536
|
+
}));
|
|
8537
|
+
}
|
|
8538
|
+
/** Shared WHERE builder for {@link search} / empty-query ranked totals. */
|
|
8539
|
+
buildSearchWhere(query, filter) {
|
|
8275
8540
|
const conditions = [];
|
|
8276
8541
|
const values = [];
|
|
8277
8542
|
let effectiveKind = filter?.kind;
|
|
@@ -8280,7 +8545,7 @@ var IndexStore = class _IndexStore {
|
|
|
8280
8545
|
if (mapped !== null) {
|
|
8281
8546
|
effectiveKind = mapped;
|
|
8282
8547
|
} else {
|
|
8283
|
-
return
|
|
8548
|
+
return null;
|
|
8284
8549
|
}
|
|
8285
8550
|
}
|
|
8286
8551
|
if (effectiveKind) {
|
|
@@ -8302,23 +8567,15 @@ var IndexStore = class _IndexStore {
|
|
|
8302
8567
|
for (const t of tokens) values.push(`%${t}%`);
|
|
8303
8568
|
}
|
|
8304
8569
|
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
|
-
}));
|
|
8570
|
+
return { where, values };
|
|
8571
|
+
}
|
|
8572
|
+
countSearch(query, filter) {
|
|
8573
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8574
|
+
if (built === null) return 0;
|
|
8575
|
+
const row = this.stmt(`SELECT COUNT(*) AS n FROM symbols ${built.where}`).get(
|
|
8576
|
+
...built.values
|
|
8577
|
+
);
|
|
8578
|
+
return Number(row?.n ?? 0);
|
|
8322
8579
|
}
|
|
8323
8580
|
/**
|
|
8324
8581
|
* Ranked search — the one-stop query the codebase-search tool and plug-lsp
|
|
@@ -8359,12 +8616,12 @@ var IndexStore = class _IndexStore {
|
|
|
8359
8616
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8360
8617
|
}
|
|
8361
8618
|
const where = conditions.join(" AND ");
|
|
8362
|
-
const countRows = this.
|
|
8619
|
+
const countRows = this.stmt(
|
|
8363
8620
|
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8364
8621
|
).all(...values);
|
|
8365
8622
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8366
8623
|
if (total === 0) return { results: [], total: 0 };
|
|
8367
|
-
const rows = this.
|
|
8624
|
+
const rows = this.stmt(
|
|
8368
8625
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
8369
8626
|
-bm25(symbols_fts) AS score,
|
|
8370
8627
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
@@ -8397,20 +8654,60 @@ var IndexStore = class _IndexStore {
|
|
|
8397
8654
|
total
|
|
8398
8655
|
};
|
|
8399
8656
|
}
|
|
8657
|
+
/**
|
|
8658
|
+
* Invalidate the cached BM25 index.
|
|
8659
|
+
*
|
|
8660
|
+
* **Contract: every method that mutates `symbols` MUST call this before
|
|
8661
|
+
* returning.** (`refs` mutations do not affect the BM25 fallback because
|
|
8662
|
+
* the corpus is built from `symbols.text` via `getAllIndexable()` and the
|
|
8663
|
+
* BM25 score is filtered by the LIKE-selected candidate set in
|
|
8664
|
+
* `searchRankedFallback`.) Today the call sites are `repairDrift`,
|
|
8665
|
+
* `insertSymbols`, `deleteSymbolsForFile`, `deleteFile`, `clearAll`, and
|
|
8666
|
+
* `commitBatch`. A future mutation that adds a new write path (e.g.
|
|
8667
|
+
* `renameFile`, `updateSignature`) MUST also call this — otherwise the
|
|
8668
|
+
* FTS5-unavailable fallback will serve stale search results. The
|
|
8669
|
+
* `close()` reset at L1820-1821 tears the cache down on store shutdown,
|
|
8670
|
+
* which is the only legitimate place that flips the flag outside this
|
|
8671
|
+
* helper.
|
|
8672
|
+
*
|
|
8673
|
+
* Called *before* `runWithRetry` on purpose: if the write fails all
|
|
8674
|
+
* retries the flag stays set, forcing a rebuild on the next search rather
|
|
8675
|
+
* than trusting a cache that may not reflect the intended mutation.
|
|
8676
|
+
* Do not move this inside the retry closure.
|
|
8677
|
+
*/
|
|
8678
|
+
invalidateBm25() {
|
|
8679
|
+
this.bm25Dirty = true;
|
|
8680
|
+
this.bm25Cache = null;
|
|
8681
|
+
}
|
|
8682
|
+
/**
|
|
8683
|
+
* Return the cached full-corpus BM25 index, rebuilding it only when the
|
|
8684
|
+
* symbols table has been mutated since the last build. The full-corpus IDF
|
|
8685
|
+
* is more correct than the old per-query candidate-subset IDF, and the
|
|
8686
|
+
* amortized build cost drops from O(symbols × tokens) per search to once
|
|
8687
|
+
* per write batch.
|
|
8688
|
+
*
|
|
8689
|
+
* Note: the first call after a long idle (or on a freshly opened store)
|
|
8690
|
+
* pays the full corpus rebuild synchronously on the search path. For a
|
|
8691
|
+
* 5 500+ symbol corpus this is a visible one-time latency spike.
|
|
8692
|
+
*/
|
|
8693
|
+
getOrBuildBm25() {
|
|
8694
|
+
if (this.bm25Cache && !this.bm25Dirty) return this.bm25Cache;
|
|
8695
|
+
const docs = this.getAllIndexable();
|
|
8696
|
+
this.bm25Cache = buildBm25Index(docs);
|
|
8697
|
+
this.bm25Dirty = false;
|
|
8698
|
+
return this.bm25Cache;
|
|
8699
|
+
}
|
|
8400
8700
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
8401
8701
|
searchRankedFallback(query, filter, limit) {
|
|
8402
|
-
const candidates = this.search(query, filter);
|
|
8403
|
-
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8404
8702
|
if (!query.trim()) {
|
|
8405
|
-
|
|
8703
|
+
const total = this.countSearch(query, filter);
|
|
8704
|
+
if (total === 0) return { results: [], total: 0 };
|
|
8705
|
+
return { results: this.search(query, filter, { limit }), total };
|
|
8406
8706
|
}
|
|
8707
|
+
const candidates = this.search(query, filter);
|
|
8708
|
+
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8407
8709
|
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
|
-
);
|
|
8710
|
+
const bm25 = this.getOrBuildBm25();
|
|
8414
8711
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8415
8712
|
const q = query.trim().toLowerCase();
|
|
8416
8713
|
const rank = (id) => {
|
|
@@ -8452,7 +8749,7 @@ var IndexStore = class _IndexStore {
|
|
|
8452
8749
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8453
8750
|
getStats() {
|
|
8454
8751
|
const sizeBytes = this.sizeBytes();
|
|
8455
|
-
const lastRows = this.
|
|
8752
|
+
const lastRows = this.stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8456
8753
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8457
8754
|
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
8458
8755
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
@@ -8477,19 +8774,20 @@ var IndexStore = class _IndexStore {
|
|
|
8477
8774
|
}
|
|
8478
8775
|
setLastIndexed(ts2) {
|
|
8479
8776
|
this.runWithRetry(() => {
|
|
8480
|
-
this.
|
|
8777
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8481
8778
|
});
|
|
8482
8779
|
}
|
|
8483
8780
|
getMetadata(key) {
|
|
8484
|
-
const rows = this.
|
|
8781
|
+
const rows = this.stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8485
8782
|
return rows[0]?.value;
|
|
8486
8783
|
}
|
|
8487
8784
|
setMetadata(key, value) {
|
|
8488
8785
|
this.runWithRetry(() => {
|
|
8489
|
-
this.
|
|
8786
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8490
8787
|
});
|
|
8491
8788
|
}
|
|
8492
8789
|
clearAll() {
|
|
8790
|
+
this.invalidateBm25();
|
|
8493
8791
|
this.runWithRetry(() => {
|
|
8494
8792
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8495
8793
|
try {
|
|
@@ -8516,13 +8814,7 @@ var IndexStore = class _IndexStore {
|
|
|
8516
8814
|
this.runWithRetry(() => {
|
|
8517
8815
|
this.stmt("DELETE FROM refs WHERE from_id = ?").run(fromId);
|
|
8518
8816
|
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
|
-
}
|
|
8817
|
+
this.bulkInsertRefs(refs.map((ref) => ({ ...ref, fromId })));
|
|
8526
8818
|
});
|
|
8527
8819
|
}
|
|
8528
8820
|
/**
|
|
@@ -8539,13 +8831,7 @@ var IndexStore = class _IndexStore {
|
|
|
8539
8831
|
insertRefsBatch(refs) {
|
|
8540
8832
|
if (refs.length === 0) return;
|
|
8541
8833
|
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
|
-
}
|
|
8834
|
+
this.bulkInsertRefs(refs);
|
|
8549
8835
|
});
|
|
8550
8836
|
}
|
|
8551
8837
|
/**
|
|
@@ -8570,65 +8856,61 @@ var IndexStore = class _IndexStore {
|
|
|
8570
8856
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
8571
8857
|
return [];
|
|
8572
8858
|
}
|
|
8859
|
+
this.invalidateBm25();
|
|
8573
8860
|
return this.runWithRetry(() => {
|
|
8574
8861
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8575
8862
|
try {
|
|
8576
8863
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
8577
8864
|
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
8578
8865
|
if (this.ftsAvailable) {
|
|
8579
|
-
this.
|
|
8866
|
+
this.stmt(
|
|
8580
8867
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8581
8868
|
).run(...options.deleteForFiles);
|
|
8582
8869
|
}
|
|
8583
|
-
this.
|
|
8870
|
+
this.stmt(
|
|
8584
8871
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8585
8872
|
).run(...options.deleteForFiles);
|
|
8586
|
-
this.
|
|
8873
|
+
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
8587
8874
|
}
|
|
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;
|
|
8875
|
+
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
8876
|
+
let nextId = this.allocateSymbolIds(totalSymbols);
|
|
8595
8877
|
const allInserted = [];
|
|
8596
8878
|
const refsToInsert = [];
|
|
8879
|
+
const bulkSyms = [];
|
|
8880
|
+
const ftsRows = [];
|
|
8597
8881
|
for (const entry of entries) {
|
|
8598
8882
|
const insertedForEntry = [];
|
|
8599
8883
|
for (const s of entry.symbols) {
|
|
8600
8884
|
const id = nextId++;
|
|
8601
|
-
|
|
8885
|
+
bulkSyms.push({
|
|
8602
8886
|
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
|
-
|
|
8887
|
+
lang: s.lang,
|
|
8888
|
+
kind: s.kind,
|
|
8889
|
+
name: s.name,
|
|
8890
|
+
file: s.file,
|
|
8891
|
+
line: s.line,
|
|
8892
|
+
col: s.col,
|
|
8893
|
+
signature: s.signature,
|
|
8894
|
+
docComment: s.docComment,
|
|
8895
|
+
scope: s.scope,
|
|
8896
|
+
text: s.text
|
|
8897
|
+
});
|
|
8898
|
+
if (this.ftsAvailable) {
|
|
8899
|
+
ftsRows.push({
|
|
8900
|
+
id,
|
|
8901
|
+
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
8902
|
+
});
|
|
8903
|
+
}
|
|
8616
8904
|
const inserted = { ...s, id };
|
|
8617
8905
|
allInserted.push(inserted);
|
|
8618
8906
|
insertedForEntry.push(inserted);
|
|
8619
8907
|
}
|
|
8620
8908
|
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8621
8909
|
}
|
|
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(
|
|
8910
|
+
this.bulkInsertSymbols(bulkSyms);
|
|
8911
|
+
this.bulkInsertFts(ftsRows);
|
|
8912
|
+
this.bulkInsertRefs(refsToInsert);
|
|
8913
|
+
const upsertStmt = this.stmt(
|
|
8632
8914
|
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
8633
8915
|
VALUES (?, ?, ?, ?, ?)
|
|
8634
8916
|
ON CONFLICT(file) DO UPDATE SET
|
|
@@ -8655,7 +8937,7 @@ var IndexStore = class _IndexStore {
|
|
|
8655
8937
|
*/
|
|
8656
8938
|
deleteRefsForFile(file) {
|
|
8657
8939
|
this.runWithRetry(() => {
|
|
8658
|
-
this.
|
|
8940
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8659
8941
|
});
|
|
8660
8942
|
}
|
|
8661
8943
|
/**
|
|
@@ -8668,20 +8950,75 @@ var IndexStore = class _IndexStore {
|
|
|
8668
8950
|
*/
|
|
8669
8951
|
resolveRefs() {
|
|
8670
8952
|
return this.runWithRetry(() => {
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8953
|
+
try {
|
|
8954
|
+
const result = this.stmt(
|
|
8955
|
+
`UPDATE refs
|
|
8956
|
+
SET to_id = s.id
|
|
8957
|
+
FROM (
|
|
8958
|
+
SELECT name, MIN(id) AS id FROM symbols GROUP BY name
|
|
8959
|
+
) AS s
|
|
8960
|
+
WHERE refs.to_id IS NULL
|
|
8961
|
+
AND refs.to_name IS NOT NULL
|
|
8962
|
+
AND refs.to_name = s.name`
|
|
8963
|
+
).run();
|
|
8964
|
+
return result.changes ?? 0;
|
|
8965
|
+
} catch {
|
|
8966
|
+
const result = this.stmt(
|
|
8967
|
+
`UPDATE refs SET to_id = (
|
|
8968
|
+
SELECT id FROM symbols WHERE name = refs.to_name LIMIT 1
|
|
8969
|
+
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
8970
|
+
AND to_name IN (SELECT name FROM symbols)`
|
|
8971
|
+
).run();
|
|
8972
|
+
return result.changes ?? 0;
|
|
8973
|
+
}
|
|
8678
8974
|
});
|
|
8679
8975
|
}
|
|
8976
|
+
/**
|
|
8977
|
+
* Clear symbols/refs for a file and mark it as indexed with zero symbols.
|
|
8978
|
+
* Used by the indexer for empty-parse results so three writes share one txn.
|
|
8979
|
+
*/
|
|
8980
|
+
replaceEmptyFile(meta) {
|
|
8981
|
+
this.invalidateBm25();
|
|
8982
|
+
this.runWithRetry(() => {
|
|
8983
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
8984
|
+
try {
|
|
8985
|
+
if (this.ftsAvailable) {
|
|
8986
|
+
this.stmt(
|
|
8987
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8988
|
+
).run(meta.file);
|
|
8989
|
+
}
|
|
8990
|
+
this.stmt(
|
|
8991
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8992
|
+
).run(meta.file);
|
|
8993
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
8994
|
+
this.stmt(
|
|
8995
|
+
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
8996
|
+
VALUES (?, ?, ?, ?, ?)
|
|
8997
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
8998
|
+
lang = excluded.lang,
|
|
8999
|
+
mtime_ms = excluded.mtime_ms,
|
|
9000
|
+
symbol_count = excluded.symbol_count,
|
|
9001
|
+
last_indexed = excluded.last_indexed`
|
|
9002
|
+
).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
|
|
9003
|
+
this.db.exec("COMMIT");
|
|
9004
|
+
} catch (err) {
|
|
9005
|
+
this.db.exec("ROLLBACK");
|
|
9006
|
+
throw err;
|
|
9007
|
+
}
|
|
9008
|
+
});
|
|
9009
|
+
}
|
|
9010
|
+
/** Best-effort query planner refresh after a large reindex. */
|
|
9011
|
+
optimize() {
|
|
9012
|
+
try {
|
|
9013
|
+
this.db.exec("PRAGMA optimize");
|
|
9014
|
+
} catch {
|
|
9015
|
+
}
|
|
9016
|
+
}
|
|
8680
9017
|
/**
|
|
8681
9018
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
8682
9019
|
*/
|
|
8683
9020
|
findRefsTo(symbolId) {
|
|
8684
|
-
return this.
|
|
9021
|
+
return this.stmt(
|
|
8685
9022
|
"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
9023
|
).all(symbolId, symbolId).map((r) => ({
|
|
8687
9024
|
id: r.id,
|
|
@@ -8696,7 +9033,7 @@ var IndexStore = class _IndexStore {
|
|
|
8696
9033
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
8697
9034
|
*/
|
|
8698
9035
|
findRefsFrom(symbolId) {
|
|
8699
|
-
return this.
|
|
9036
|
+
return this.stmt("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
8700
9037
|
id: r.id,
|
|
8701
9038
|
fromId: r.from_id,
|
|
8702
9039
|
toName: r.to_name,
|
|
@@ -8769,61 +9106,77 @@ var IndexStore = class _IndexStore {
|
|
|
8769
9106
|
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
8770
9107
|
*/
|
|
8771
9108
|
getPackageGraph() {
|
|
8772
|
-
const
|
|
9109
|
+
const fileCounts = this.stmt(
|
|
9110
|
+
"SELECT file, COUNT(*) AS n FROM symbols GROUP BY file"
|
|
9111
|
+
).all();
|
|
8773
9112
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
8774
9113
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
8775
|
-
const
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
fileToPkg.set(s.file, pkg);
|
|
8779
|
-
symbolToPkg.set(s.id, pkg);
|
|
9114
|
+
for (const { file, n } of fileCounts) {
|
|
9115
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9116
|
+
fileToPkg.set(file, pkg);
|
|
8780
9117
|
const node = pkgNodes.get(pkg);
|
|
8781
9118
|
if (node) {
|
|
8782
|
-
node.symbolCount = (node.symbolCount ?? 0) +
|
|
9119
|
+
node.symbolCount = (node.symbolCount ?? 0) + n;
|
|
8783
9120
|
} else {
|
|
8784
9121
|
pkgNodes.set(pkg, {
|
|
8785
9122
|
id: `pkg:${pkg}`,
|
|
8786
9123
|
label: pkg,
|
|
8787
9124
|
kind: "package",
|
|
8788
9125
|
package: pkg,
|
|
8789
|
-
symbolCount:
|
|
9126
|
+
symbolCount: n,
|
|
8790
9127
|
fileCount: 0
|
|
8791
9128
|
});
|
|
8792
9129
|
}
|
|
8793
9130
|
}
|
|
8794
|
-
const files = this.
|
|
9131
|
+
const files = this.stmt("SELECT DISTINCT file FROM files").all();
|
|
8795
9132
|
for (const { file } of files) {
|
|
8796
9133
|
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9134
|
+
fileToPkg.set(file, pkg);
|
|
8797
9135
|
const node = pkgNodes.get(pkg);
|
|
8798
9136
|
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
9137
|
+
else {
|
|
9138
|
+
pkgNodes.set(pkg, {
|
|
9139
|
+
id: `pkg:${pkg}`,
|
|
9140
|
+
label: pkg,
|
|
9141
|
+
kind: "package",
|
|
9142
|
+
package: pkg,
|
|
9143
|
+
symbolCount: 0,
|
|
9144
|
+
fileCount: 1
|
|
9145
|
+
});
|
|
9146
|
+
}
|
|
8799
9147
|
}
|
|
8800
|
-
const refRows = this.
|
|
8801
|
-
`SELECT r.
|
|
9148
|
+
const refRows = this.stmt(
|
|
9149
|
+
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
|
|
8802
9150
|
FROM refs r
|
|
8803
|
-
|
|
9151
|
+
JOIN symbols sf ON sf.id = r.from_id
|
|
9152
|
+
JOIN symbols st ON st.id = r.to_id
|
|
9153
|
+
WHERE r.to_id IS NOT NULL AND r.call_type != 'import'
|
|
9154
|
+
GROUP BY r.call_type, sf.file, st.file`
|
|
8804
9155
|
).all();
|
|
8805
9156
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
8806
9157
|
for (const r of refRows) {
|
|
8807
|
-
|
|
8808
|
-
const
|
|
8809
|
-
|
|
8810
|
-
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
9158
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
9159
|
+
const toPkg = fileToPkg.get(r.to_file) ?? _IndexStore.derivePackage(r.to_file) ?? "(root)";
|
|
9160
|
+
if (fromPkg === toPkg) continue;
|
|
8811
9161
|
const key = `${fromPkg}\0${toPkg}`;
|
|
8812
9162
|
let e = edgeMap.get(key);
|
|
8813
9163
|
if (!e) {
|
|
8814
9164
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8815
9165
|
edgeMap.set(key, e);
|
|
8816
9166
|
}
|
|
8817
|
-
|
|
8818
|
-
e.
|
|
9167
|
+
const n = Number(r.n) || 0;
|
|
9168
|
+
e.weight += n;
|
|
9169
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
8819
9170
|
}
|
|
8820
|
-
const importRows = this.
|
|
8821
|
-
`SELECT r.
|
|
9171
|
+
const importRows = this.stmt(
|
|
9172
|
+
`SELECT r.to_name, s.file AS from_file, COUNT(*) AS n
|
|
8822
9173
|
FROM refs r
|
|
8823
|
-
|
|
9174
|
+
JOIN symbols s ON s.id = r.from_id
|
|
9175
|
+
WHERE r.call_type = 'import'
|
|
9176
|
+
GROUP BY r.to_name, s.file`
|
|
8824
9177
|
).all();
|
|
8825
9178
|
for (const r of importRows) {
|
|
8826
|
-
const fromPkg =
|
|
9179
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
8827
9180
|
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
8828
9181
|
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
8829
9182
|
const key = `${fromPkg}\0${toPkg}`;
|
|
@@ -8832,8 +9185,9 @@ var IndexStore = class _IndexStore {
|
|
|
8832
9185
|
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8833
9186
|
edgeMap.set(key, edge);
|
|
8834
9187
|
}
|
|
8835
|
-
|
|
8836
|
-
edge.
|
|
9188
|
+
const n = Number(r.n) || 0;
|
|
9189
|
+
edge.weight += n;
|
|
9190
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + n);
|
|
8837
9191
|
}
|
|
8838
9192
|
const edges = [];
|
|
8839
9193
|
for (const [key, e] of edgeMap) {
|
|
@@ -8860,12 +9214,12 @@ var IndexStore = class _IndexStore {
|
|
|
8860
9214
|
* derived from cross-file symbol references within the package.
|
|
8861
9215
|
*/
|
|
8862
9216
|
getFileGraph(packageFilter) {
|
|
8863
|
-
const allFiles = this.
|
|
9217
|
+
const allFiles = this.stmt("SELECT DISTINCT file FROM symbols").all();
|
|
8864
9218
|
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
8865
9219
|
const localFiles = new Set(pkgFilePaths);
|
|
8866
9220
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
8867
9221
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
8868
|
-
const pkgSyms = this.
|
|
9222
|
+
const pkgSyms = this.stmt(
|
|
8869
9223
|
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
8870
9224
|
).all(...pkgFilePaths);
|
|
8871
9225
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
@@ -8897,12 +9251,13 @@ var IndexStore = class _IndexStore {
|
|
|
8897
9251
|
ensureFileNode(file);
|
|
8898
9252
|
}
|
|
8899
9253
|
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
8900
|
-
const refRows = this.
|
|
8901
|
-
`SELECT r.from_id, r.to_id, r.call_type
|
|
9254
|
+
const refRows = this.stmt(
|
|
9255
|
+
`SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
|
|
8902
9256
|
FROM refs r
|
|
8903
9257
|
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
8904
9258
|
OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
|
|
8905
|
-
AND r.to_id IS NOT NULL
|
|
9259
|
+
AND r.to_id IS NOT NULL
|
|
9260
|
+
GROUP BY r.from_id, r.to_id, r.call_type`
|
|
8906
9261
|
).all(...pkgFilePaths, ...pkgFilePaths);
|
|
8907
9262
|
const knownSymIds = new Set(pkgSyms.map((s) => s.id));
|
|
8908
9263
|
const crossRefIds = /* @__PURE__ */ new Set();
|
|
@@ -8912,7 +9267,7 @@ var IndexStore = class _IndexStore {
|
|
|
8912
9267
|
}
|
|
8913
9268
|
if (crossRefIds.size > 0) {
|
|
8914
9269
|
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
8915
|
-
const extras = this.
|
|
9270
|
+
const extras = this.stmt(
|
|
8916
9271
|
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
8917
9272
|
).all(...crossRefIds);
|
|
8918
9273
|
for (const x of extras) {
|
|
@@ -8937,14 +9292,16 @@ var IndexStore = class _IndexStore {
|
|
|
8937
9292
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8938
9293
|
edgeMap.set(key, e);
|
|
8939
9294
|
}
|
|
8940
|
-
|
|
8941
|
-
e.
|
|
9295
|
+
const n = Number(r.n) || 0;
|
|
9296
|
+
e.weight += n;
|
|
9297
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
8942
9298
|
}
|
|
8943
|
-
const importRows = this.
|
|
8944
|
-
`SELECT r.from_id, r.to_name
|
|
9299
|
+
const importRows = this.stmt(
|
|
9300
|
+
`SELECT r.from_id, r.to_name, COUNT(*) AS n
|
|
8945
9301
|
FROM refs r
|
|
8946
9302
|
WHERE r.call_type = 'import'
|
|
8947
|
-
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
9303
|
+
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
9304
|
+
GROUP BY r.from_id, r.to_name`
|
|
8948
9305
|
).all(...pkgFilePaths);
|
|
8949
9306
|
for (const r of importRows) {
|
|
8950
9307
|
const fromFile = symToFile.get(r.from_id);
|
|
@@ -8959,8 +9316,9 @@ var IndexStore = class _IndexStore {
|
|
|
8959
9316
|
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
8960
9317
|
edgeMap.set(key, edge);
|
|
8961
9318
|
}
|
|
8962
|
-
|
|
8963
|
-
edge.
|
|
9319
|
+
const n = Number(r.n) || 0;
|
|
9320
|
+
edge.weight += n;
|
|
9321
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + n);
|
|
8964
9322
|
}
|
|
8965
9323
|
const edges = [];
|
|
8966
9324
|
for (const [key, e] of edgeMap) {
|
|
@@ -8987,7 +9345,7 @@ var IndexStore = class _IndexStore {
|
|
|
8987
9345
|
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
8988
9346
|
*/
|
|
8989
9347
|
getSymbolGraph(fileFilter) {
|
|
8990
|
-
const syms = this.
|
|
9348
|
+
const syms = this.stmt(
|
|
8991
9349
|
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
8992
9350
|
).all(fileFilter);
|
|
8993
9351
|
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
@@ -9007,17 +9365,25 @@ var IndexStore = class _IndexStore {
|
|
|
9007
9365
|
scope: s.scope,
|
|
9008
9366
|
external: s.file !== fileFilter
|
|
9009
9367
|
});
|
|
9010
|
-
const
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9368
|
+
const refRows = this.stmt(
|
|
9369
|
+
`SELECT from_id, to_id, call_type, COUNT(*) AS n
|
|
9370
|
+
FROM (
|
|
9371
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9372
|
+
FROM refs r
|
|
9373
|
+
JOIN symbols s ON s.id = r.from_id
|
|
9374
|
+
WHERE s.file = ?
|
|
9375
|
+
UNION
|
|
9376
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9377
|
+
FROM refs r
|
|
9378
|
+
JOIN symbols s ON s.id = r.to_id
|
|
9379
|
+
WHERE s.file = ?
|
|
9380
|
+
)
|
|
9381
|
+
WHERE to_id IS NOT NULL
|
|
9382
|
+
GROUP BY from_id, to_id, call_type`
|
|
9016
9383
|
).all(fileFilter, fileFilter);
|
|
9017
9384
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
9018
9385
|
for (const r of refRows) {
|
|
9019
|
-
if (r.to_id
|
|
9020
|
-
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
9386
|
+
if (r.to_id == null) continue;
|
|
9021
9387
|
relatedIds.add(r.from_id);
|
|
9022
9388
|
relatedIds.add(r.to_id);
|
|
9023
9389
|
const key = `${r.from_id}\0${r.to_id}`;
|
|
@@ -9026,8 +9392,9 @@ var IndexStore = class _IndexStore {
|
|
|
9026
9392
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9027
9393
|
edgeMap.set(key, e);
|
|
9028
9394
|
}
|
|
9029
|
-
|
|
9030
|
-
e.
|
|
9395
|
+
const n = Number(r.n) || 0;
|
|
9396
|
+
e.weight += n;
|
|
9397
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
9031
9398
|
}
|
|
9032
9399
|
const edges = [];
|
|
9033
9400
|
for (const [key, e] of edgeMap) {
|
|
@@ -9051,7 +9418,7 @@ var IndexStore = class _IndexStore {
|
|
|
9051
9418
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
9052
9419
|
if (missingIds.length > 0) {
|
|
9053
9420
|
const placeholders = missingIds.map(() => "?").join(",");
|
|
9054
|
-
const extras = this.
|
|
9421
|
+
const extras = this.stmt(
|
|
9055
9422
|
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
9056
9423
|
).all(...missingIds);
|
|
9057
9424
|
for (const s of extras) symById.set(s.id, s);
|
|
@@ -9065,6 +9432,8 @@ var IndexStore = class _IndexStore {
|
|
|
9065
9432
|
}
|
|
9066
9433
|
close() {
|
|
9067
9434
|
this.stmtCache.clear();
|
|
9435
|
+
this.bm25Dirty = true;
|
|
9436
|
+
this.bm25Cache = null;
|
|
9068
9437
|
try {
|
|
9069
9438
|
this.db.close();
|
|
9070
9439
|
} catch {
|
|
@@ -9078,14 +9447,118 @@ import { fileURLToPath } from "node:url";
|
|
|
9078
9447
|
import { Worker } from "node:worker_threads";
|
|
9079
9448
|
|
|
9080
9449
|
// src/codebase-index/indexer.ts
|
|
9081
|
-
import { expectDefined as expectDefined6 } from "@wrongstack/core";
|
|
9450
|
+
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
9082
9451
|
import * as fs14 from "node:fs/promises";
|
|
9083
|
-
import * as
|
|
9452
|
+
import * as path20 from "node:path";
|
|
9084
9453
|
import { availableParallelism } from "node:os";
|
|
9085
|
-
import {
|
|
9454
|
+
import { DEFAULT_WALK_IGNORE_DIRS, indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
|
|
9086
9455
|
|
|
9087
9456
|
// src/codebase-index/ts-parser.ts
|
|
9088
9457
|
import * as ts from "@typescript/typescript6";
|
|
9458
|
+
|
|
9459
|
+
// src/codebase-index/languages.ts
|
|
9460
|
+
import * as path14 from "node:path";
|
|
9461
|
+
var EXT_TO_LANG = {
|
|
9462
|
+
// TypeScript / JavaScript (first-class TS compiler API)
|
|
9463
|
+
".ts": "ts",
|
|
9464
|
+
".mts": "ts",
|
|
9465
|
+
".cts": "ts",
|
|
9466
|
+
".tsx": "tsx",
|
|
9467
|
+
".js": "js",
|
|
9468
|
+
".mjs": "js",
|
|
9469
|
+
".cjs": "js",
|
|
9470
|
+
".jsx": "jsx",
|
|
9471
|
+
// First-class native/spawn parsers (+ regex fallback)
|
|
9472
|
+
".go": "go",
|
|
9473
|
+
".py": "py",
|
|
9474
|
+
".pyi": "py",
|
|
9475
|
+
".pyw": "py",
|
|
9476
|
+
".rs": "rs",
|
|
9477
|
+
".json": "json",
|
|
9478
|
+
".jsonc": "json",
|
|
9479
|
+
".yaml": "yaml",
|
|
9480
|
+
".yml": "yaml",
|
|
9481
|
+
// C family
|
|
9482
|
+
".c": "c",
|
|
9483
|
+
".h": "c",
|
|
9484
|
+
".cc": "cpp",
|
|
9485
|
+
".cpp": "cpp",
|
|
9486
|
+
".cxx": "cpp",
|
|
9487
|
+
".hh": "cpp",
|
|
9488
|
+
".hpp": "cpp",
|
|
9489
|
+
".hxx": "cpp",
|
|
9490
|
+
// JVM / .NET
|
|
9491
|
+
".java": "java",
|
|
9492
|
+
".cs": "csharp",
|
|
9493
|
+
".kt": "kotlin",
|
|
9494
|
+
".kts": "kotlin",
|
|
9495
|
+
".scala": "scala",
|
|
9496
|
+
".sc": "scala",
|
|
9497
|
+
// Scripting
|
|
9498
|
+
".php": "php",
|
|
9499
|
+
".rb": "ruby",
|
|
9500
|
+
".swift": "swift",
|
|
9501
|
+
".dart": "dart",
|
|
9502
|
+
".lua": "lua",
|
|
9503
|
+
".r": "r",
|
|
9504
|
+
".R": "r",
|
|
9505
|
+
".pl": "other",
|
|
9506
|
+
".pm": "other",
|
|
9507
|
+
// Systems / functional
|
|
9508
|
+
".zig": "zig",
|
|
9509
|
+
".ex": "elixir",
|
|
9510
|
+
".exs": "elixir",
|
|
9511
|
+
".hs": "haskell",
|
|
9512
|
+
".lhs": "haskell",
|
|
9513
|
+
// Shell / data / docs / web
|
|
9514
|
+
".sh": "shell",
|
|
9515
|
+
".bash": "shell",
|
|
9516
|
+
".zsh": "shell",
|
|
9517
|
+
".ps1": "shell",
|
|
9518
|
+
".sql": "sql",
|
|
9519
|
+
".md": "md",
|
|
9520
|
+
".mdx": "md",
|
|
9521
|
+
".toml": "toml",
|
|
9522
|
+
".html": "html",
|
|
9523
|
+
".htm": "html",
|
|
9524
|
+
".css": "css",
|
|
9525
|
+
".scss": "css",
|
|
9526
|
+
".less": "css",
|
|
9527
|
+
".vue": "vue",
|
|
9528
|
+
".svelte": "svelte",
|
|
9529
|
+
".proto": "proto",
|
|
9530
|
+
".graphql": "graphql",
|
|
9531
|
+
".gql": "graphql"
|
|
9532
|
+
};
|
|
9533
|
+
var INDEXABLE_EXTENSIONS = Object.freeze(
|
|
9534
|
+
[...new Set(Object.keys(EXT_TO_LANG).map((e) => e.toLowerCase()))].sort()
|
|
9535
|
+
);
|
|
9536
|
+
var SPECIAL_FILENAMES = {
|
|
9537
|
+
makefile: "other",
|
|
9538
|
+
gnumakefile: "other",
|
|
9539
|
+
dockerfile: "other",
|
|
9540
|
+
"docker-compose.yml": "yaml",
|
|
9541
|
+
"docker-compose.yaml": "yaml",
|
|
9542
|
+
"cmakelists.txt": "other",
|
|
9543
|
+
gemfile: "ruby",
|
|
9544
|
+
rakefile: "ruby",
|
|
9545
|
+
procfile: "other",
|
|
9546
|
+
justfile: "other"
|
|
9547
|
+
};
|
|
9548
|
+
function detectLang(file) {
|
|
9549
|
+
const base = path14.basename(file);
|
|
9550
|
+
const lowerBase = base.toLowerCase();
|
|
9551
|
+
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
9552
|
+
return "ts";
|
|
9553
|
+
}
|
|
9554
|
+
const special = SPECIAL_FILENAMES[lowerBase];
|
|
9555
|
+
if (special) return special;
|
|
9556
|
+
const ext = path14.extname(base).toLowerCase();
|
|
9557
|
+
if (!ext) return null;
|
|
9558
|
+
return EXT_TO_LANG[ext] ?? null;
|
|
9559
|
+
}
|
|
9560
|
+
|
|
9561
|
+
// src/codebase-index/ts-parser.ts
|
|
9089
9562
|
var KIND_MAP = {
|
|
9090
9563
|
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
9091
9564
|
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
@@ -9112,32 +9585,6 @@ function kindOf(node) {
|
|
|
9112
9585
|
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
9113
9586
|
return KIND_MAP[node.kind] ?? null;
|
|
9114
9587
|
}
|
|
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
9588
|
function getSignature(printer, node, sourceFile) {
|
|
9142
9589
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
9143
9590
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
@@ -9260,22 +9707,30 @@ function deduplicateRefs(refs) {
|
|
|
9260
9707
|
return true;
|
|
9261
9708
|
});
|
|
9262
9709
|
}
|
|
9263
|
-
function detectLang(file) {
|
|
9264
|
-
const idx = file.lastIndexOf(".");
|
|
9265
|
-
if (idx < 0) return null;
|
|
9266
|
-
return extToLang(file.slice(idx));
|
|
9267
|
-
}
|
|
9268
9710
|
|
|
9269
9711
|
// src/codebase-index/go-parser.ts
|
|
9270
9712
|
init_win32_resolve();
|
|
9271
9713
|
import { spawn as spawn4 } from "node:child_process";
|
|
9272
9714
|
import * as os5 from "node:os";
|
|
9273
|
-
import * as
|
|
9715
|
+
import * as path15 from "node:path";
|
|
9274
9716
|
import * as fs10 from "node:fs/promises";
|
|
9717
|
+
|
|
9718
|
+
// src/codebase-index/spawn-gate.ts
|
|
9719
|
+
var chain = Promise.resolve();
|
|
9720
|
+
function withSpawnGate(fn) {
|
|
9721
|
+
const run = chain.then(fn, fn);
|
|
9722
|
+
chain = run.then(
|
|
9723
|
+
() => void 0,
|
|
9724
|
+
() => void 0
|
|
9725
|
+
);
|
|
9726
|
+
return run;
|
|
9727
|
+
}
|
|
9728
|
+
|
|
9729
|
+
// src/codebase-index/go-parser.ts
|
|
9275
9730
|
async function parseSymbols2(opts) {
|
|
9276
9731
|
const { file, content, lang } = opts;
|
|
9277
9732
|
try {
|
|
9278
|
-
const parsed = await syncGoParse(file, content, lang);
|
|
9733
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
9279
9734
|
if (parsed.symbols.length > 0) {
|
|
9280
9735
|
return parsed;
|
|
9281
9736
|
}
|
|
@@ -9571,11 +10026,16 @@ func formatType(t ast.Expr) string {
|
|
|
9571
10026
|
}
|
|
9572
10027
|
}
|
|
9573
10028
|
`;
|
|
10029
|
+
var _cachedGoScriptPath = null;
|
|
9574
10030
|
async function syncGoParse(filePath, content, lang) {
|
|
9575
|
-
const tmpDir = await fs10.mkdtemp(path14.join(os5.tmpdir(), "ws-go-parse-"));
|
|
9576
10031
|
try {
|
|
9577
|
-
|
|
9578
|
-
|
|
10032
|
+
let scriptPath = _cachedGoScriptPath;
|
|
10033
|
+
if (!scriptPath) {
|
|
10034
|
+
const tmpDir = await fs10.mkdtemp(path15.join(os5.tmpdir(), "ws-go-parse-"));
|
|
10035
|
+
scriptPath = path15.join(tmpDir, "parse.go");
|
|
10036
|
+
await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
10037
|
+
_cachedGoScriptPath = scriptPath;
|
|
10038
|
+
}
|
|
9579
10039
|
const goBinary = resolveWin32Command("go");
|
|
9580
10040
|
const goResult = await new Promise(
|
|
9581
10041
|
(resolve14, reject) => {
|
|
@@ -9632,24 +10092,312 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9632
10092
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
9633
10093
|
} catch {
|
|
9634
10094
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9635
|
-
} finally {
|
|
9636
|
-
await fs10.rm(tmpDir, { recursive: true, force: true });
|
|
9637
10095
|
}
|
|
9638
10096
|
}
|
|
9639
10097
|
|
|
9640
10098
|
// src/codebase-index/py-parser.ts
|
|
9641
10099
|
init_win32_resolve();
|
|
9642
|
-
import { spawn as spawn5
|
|
10100
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
9643
10101
|
import * as fs11 from "node:fs/promises";
|
|
9644
10102
|
import * as os6 from "node:os";
|
|
9645
|
-
import * as
|
|
10103
|
+
import * as path16 from "node:path";
|
|
10104
|
+
|
|
10105
|
+
// src/codebase-index/generic-parser.ts
|
|
10106
|
+
var C_LIKE = [
|
|
10107
|
+
{ re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10108
|
+
{
|
|
10109
|
+
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,
|
|
10110
|
+
kind: "function"
|
|
10111
|
+
},
|
|
10112
|
+
{ re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
|
|
10113
|
+
];
|
|
10114
|
+
var LANG_PATTERNS = {
|
|
10115
|
+
py: [
|
|
10116
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10117
|
+
{ re: /^class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10118
|
+
{ re: /^([A-Za-z_]\w*)\s*=/gm, kind: "var" }
|
|
10119
|
+
],
|
|
10120
|
+
go: [
|
|
10121
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/gm, kind: "function" },
|
|
10122
|
+
{ re: /^type\s+([A-Za-z_]\w*)\b/gm, kind: "type" },
|
|
10123
|
+
{ re: /^(?:const|var)\s+([A-Za-z_]\w*)\b/gm, kind: "const" },
|
|
10124
|
+
{ re: /^package\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10125
|
+
],
|
|
10126
|
+
rs: [
|
|
10127
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10128
|
+
{ re: /\bstruct\s+([A-Za-z_]\w*)/g, kind: "struct" },
|
|
10129
|
+
{ re: /\benum\s+([A-Za-z_]\w*)/g, kind: "enum" },
|
|
10130
|
+
{ re: /\btrait\s+([A-Za-z_]\w*)/g, kind: "trait" },
|
|
10131
|
+
{ re: /\bimpl(?:\s*<[^>]+>)?\s+([A-Za-z_]\w*)/g, kind: "impl" },
|
|
10132
|
+
{ re: /\b(?:const|static)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10133
|
+
{ re: /\bmod\s+([A-Za-z_]\w*)/g, kind: "mod" }
|
|
10134
|
+
],
|
|
10135
|
+
c: C_LIKE,
|
|
10136
|
+
cpp: C_LIKE,
|
|
10137
|
+
java: [
|
|
10138
|
+
{ re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10139
|
+
{
|
|
10140
|
+
re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10141
|
+
kind: "method"
|
|
10142
|
+
}
|
|
10143
|
+
],
|
|
10144
|
+
csharp: [
|
|
10145
|
+
{ re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10146
|
+
{ re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
|
|
10147
|
+
{
|
|
10148
|
+
re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10149
|
+
kind: "method"
|
|
10150
|
+
}
|
|
10151
|
+
],
|
|
10152
|
+
php: [
|
|
10153
|
+
{ re: /\bfunction\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10154
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10155
|
+
{ re: /\binterface\s+([A-Za-z_]\w*)/g, kind: "interface" },
|
|
10156
|
+
{ re: /\bnamespace\s+([A-Za-z_\\]+)/g, kind: "namespace" }
|
|
10157
|
+
],
|
|
10158
|
+
ruby: [
|
|
10159
|
+
{ re: /^\s*def\s+(?:self\.)?([A-Za-z_]\w*[!?]?)/gm, kind: "function" },
|
|
10160
|
+
{ re: /^\s*class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10161
|
+
{ re: /^\s*module\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10162
|
+
],
|
|
10163
|
+
swift: [
|
|
10164
|
+
{ re: /\b(?:func)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10165
|
+
{ re: /\b(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10166
|
+
],
|
|
10167
|
+
kotlin: [
|
|
10168
|
+
{ re: /\b(?:fun)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10169
|
+
{ re: /\b(?:class|interface|object|enum\s+class|data\s+class)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10170
|
+
],
|
|
10171
|
+
scala: [
|
|
10172
|
+
{ re: /\b(?:def)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10173
|
+
{ re: /\b(?:class|object|trait|enum)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10174
|
+
],
|
|
10175
|
+
shell: [
|
|
10176
|
+
{ re: /^(?:function\s+)?([A-Za-z_]\w*)\s*\(\)\s*\{/gm, kind: "function" },
|
|
10177
|
+
{ re: /^([A-Za-z_][\w]*)\s*\(\)\s*\{/gm, kind: "function" }
|
|
10178
|
+
],
|
|
10179
|
+
sql: [
|
|
10180
|
+
{ 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" }
|
|
10181
|
+
],
|
|
10182
|
+
md: [
|
|
10183
|
+
{ re: /^(#{1,6})\s+(.+)$/gm, kind: "namespace" }
|
|
10184
|
+
],
|
|
10185
|
+
toml: [
|
|
10186
|
+
{ re: /^\[([^\]]+)\]/gm, kind: "namespace" }
|
|
10187
|
+
],
|
|
10188
|
+
html: [
|
|
10189
|
+
{ re: /\bid\s*=\s*["']([^"']+)["']/gi, kind: "property" },
|
|
10190
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10191
|
+
],
|
|
10192
|
+
css: [
|
|
10193
|
+
{ re: /^\s*([.#]?[A-Za-z_][\w-]*)\s*\{/gm, kind: "type" },
|
|
10194
|
+
{ re: /@(?:keyframes|media|supports)\s+([^{\s]+)/g, kind: "namespace" }
|
|
10195
|
+
],
|
|
10196
|
+
vue: [
|
|
10197
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10198
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10199
|
+
],
|
|
10200
|
+
svelte: [
|
|
10201
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10202
|
+
],
|
|
10203
|
+
dart: [
|
|
10204
|
+
{ re: /\b(?:class|enum|mixin|extension)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10205
|
+
{ re: /\b([A-Za-z_]\w*)\s*\([^;]*\)\s*(?:async\s*)?\{/g, kind: "function" }
|
|
10206
|
+
],
|
|
10207
|
+
lua: [
|
|
10208
|
+
{ re: /\bfunction\s+([A-Za-z_.:]\w*)/g, kind: "function" },
|
|
10209
|
+
{ re: /\blocal\s+function\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10210
|
+
],
|
|
10211
|
+
r: [
|
|
10212
|
+
{ re: /([A-Za-z.]\w*)\s*<-\s*function\s*\(/g, kind: "function" },
|
|
10213
|
+
{ re: /([A-Za-z.]\w*)\s*=\s*function\s*\(/g, kind: "function" }
|
|
10214
|
+
],
|
|
10215
|
+
proto: [
|
|
10216
|
+
{ re: /\b(?:message|service|enum)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10217
|
+
{ re: /\brpc\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10218
|
+
],
|
|
10219
|
+
graphql: [
|
|
10220
|
+
{ re: /\b(?:type|interface|enum|input|union|scalar)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10221
|
+
{ re: /\b(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10222
|
+
],
|
|
10223
|
+
zig: [
|
|
10224
|
+
{ re: /\b(?:fn|pub\s+fn)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10225
|
+
{ re: /\b(?:const|var)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10226
|
+
{ re: /\b(?:struct|enum|union)\s*\{/g, kind: "type" }
|
|
10227
|
+
],
|
|
10228
|
+
elixir: [
|
|
10229
|
+
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
10230
|
+
{ re: /\bdefmodule\s+([A-Za-z_.]\w*)/g, kind: "namespace" }
|
|
10231
|
+
],
|
|
10232
|
+
haskell: [
|
|
10233
|
+
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
10234
|
+
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10235
|
+
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
10236
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10237
|
+
],
|
|
10238
|
+
other: [
|
|
10239
|
+
{ re: /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10240
|
+
{ re: /^(?:export\s+)?class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10241
|
+
{ re: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)/gm, kind: "const" },
|
|
10242
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10243
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10244
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10245
|
+
{ re: /^(?:target|PHONY)\s*:/gm, kind: "namespace" }
|
|
10246
|
+
]
|
|
10247
|
+
};
|
|
10248
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
10249
|
+
"if",
|
|
10250
|
+
"else",
|
|
10251
|
+
"for",
|
|
10252
|
+
"while",
|
|
10253
|
+
"switch",
|
|
10254
|
+
"case",
|
|
10255
|
+
"return",
|
|
10256
|
+
"break",
|
|
10257
|
+
"continue",
|
|
10258
|
+
"new",
|
|
10259
|
+
"delete",
|
|
10260
|
+
"typeof",
|
|
10261
|
+
"instanceof",
|
|
10262
|
+
"void",
|
|
10263
|
+
"null",
|
|
10264
|
+
"true",
|
|
10265
|
+
"false",
|
|
10266
|
+
"this",
|
|
10267
|
+
"super",
|
|
10268
|
+
"import",
|
|
10269
|
+
"export",
|
|
10270
|
+
"from",
|
|
10271
|
+
"as",
|
|
10272
|
+
"default",
|
|
10273
|
+
"public",
|
|
10274
|
+
"private",
|
|
10275
|
+
"protected",
|
|
10276
|
+
"static",
|
|
10277
|
+
"final",
|
|
10278
|
+
"class",
|
|
10279
|
+
"struct",
|
|
10280
|
+
"enum",
|
|
10281
|
+
"interface",
|
|
10282
|
+
"function",
|
|
10283
|
+
"def",
|
|
10284
|
+
"fn",
|
|
10285
|
+
"func",
|
|
10286
|
+
"var",
|
|
10287
|
+
"let",
|
|
10288
|
+
"const",
|
|
10289
|
+
"package",
|
|
10290
|
+
"namespace",
|
|
10291
|
+
"module",
|
|
10292
|
+
"using",
|
|
10293
|
+
"include",
|
|
10294
|
+
"require",
|
|
10295
|
+
"select",
|
|
10296
|
+
"from",
|
|
10297
|
+
"where",
|
|
10298
|
+
"and",
|
|
10299
|
+
"or",
|
|
10300
|
+
"not",
|
|
10301
|
+
"in",
|
|
10302
|
+
"is",
|
|
10303
|
+
"try",
|
|
10304
|
+
"catch",
|
|
10305
|
+
"finally",
|
|
10306
|
+
"throw",
|
|
10307
|
+
"async",
|
|
10308
|
+
"await",
|
|
10309
|
+
"yield"
|
|
10310
|
+
]);
|
|
10311
|
+
function patternsFor(lang) {
|
|
10312
|
+
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
10313
|
+
}
|
|
10314
|
+
function looksBinary(content) {
|
|
10315
|
+
if (content.includes("\0")) return true;
|
|
10316
|
+
const sample = content.slice(0, 2048);
|
|
10317
|
+
if (sample.length === 0) return false;
|
|
10318
|
+
let bad = 0;
|
|
10319
|
+
for (let i = 0; i < sample.length; i++) {
|
|
10320
|
+
const c = sample.charCodeAt(i);
|
|
10321
|
+
if (c === 9 || c === 10 || c === 13) continue;
|
|
10322
|
+
if (c < 32 || c === 127) bad++;
|
|
10323
|
+
}
|
|
10324
|
+
return bad / sample.length > 0.1;
|
|
10325
|
+
}
|
|
10326
|
+
function lineColAt(content, index) {
|
|
10327
|
+
let line = 1;
|
|
10328
|
+
let lastNl = -1;
|
|
10329
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
10330
|
+
if (content.charCodeAt(i) === 10) {
|
|
10331
|
+
line++;
|
|
10332
|
+
lastNl = i;
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
10335
|
+
return { line, col: index - lastNl };
|
|
10336
|
+
}
|
|
10337
|
+
var GENERIC_MAX_SYMBOLS_DEFAULT = 500;
|
|
10338
|
+
var GENERIC_MAX_FILE_CHARS = 512 * 1024;
|
|
10339
|
+
function parseGeneric2(opts) {
|
|
10340
|
+
const { file, lang } = opts;
|
|
10341
|
+
const maxSymbols = opts.maxSymbols ?? GENERIC_MAX_SYMBOLS_DEFAULT;
|
|
10342
|
+
const mtimeMs = Date.now();
|
|
10343
|
+
if (!opts.content || looksBinary(opts.content)) {
|
|
10344
|
+
return { file, lang, symbols: [], mtimeMs };
|
|
10345
|
+
}
|
|
10346
|
+
const content = opts.content.length > GENERIC_MAX_FILE_CHARS ? opts.content.slice(0, GENERIC_MAX_FILE_CHARS) : opts.content;
|
|
10347
|
+
const patterns = patternsFor(lang);
|
|
10348
|
+
const symbols = [];
|
|
10349
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10350
|
+
for (const pattern of patterns) {
|
|
10351
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
10352
|
+
re.lastIndex = 0;
|
|
10353
|
+
for (const match of content.matchAll(re)) {
|
|
10354
|
+
if (symbols.length >= maxSymbols) break;
|
|
10355
|
+
let name = (match[1] ?? match[2] ?? "").trim();
|
|
10356
|
+
if (lang === "md" && match[2]) name = match[2].trim();
|
|
10357
|
+
if (!name || name.length > 200) continue;
|
|
10358
|
+
name = name.replace(/^#+\s*/, "").replace(/["'`]/g, "");
|
|
10359
|
+
if (!name || KEYWORDS.has(name.toLowerCase())) continue;
|
|
10360
|
+
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
10361
|
+
continue;
|
|
10362
|
+
}
|
|
10363
|
+
const { line, col } = lineColAt(content, match.index);
|
|
10364
|
+
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
10365
|
+
if (seen.has(key)) continue;
|
|
10366
|
+
seen.add(key);
|
|
10367
|
+
const nl = content.indexOf("\n", match.index);
|
|
10368
|
+
const lineText = content.slice(match.index, nl === -1 ? content.length : nl);
|
|
10369
|
+
const signature = (lineText || name).trim().slice(0, 500);
|
|
10370
|
+
symbols.push({
|
|
10371
|
+
id: 0,
|
|
10372
|
+
lang,
|
|
10373
|
+
kind: lang === "md" ? "namespace" : pattern.kind,
|
|
10374
|
+
name: name.slice(0, 200),
|
|
10375
|
+
file,
|
|
10376
|
+
line,
|
|
10377
|
+
col,
|
|
10378
|
+
signature,
|
|
10379
|
+
docComment: "",
|
|
10380
|
+
scope: "",
|
|
10381
|
+
text: `${name} ${signature}`.trim().slice(0, 1e3)
|
|
10382
|
+
});
|
|
10383
|
+
}
|
|
10384
|
+
if (symbols.length >= maxSymbols) break;
|
|
10385
|
+
}
|
|
10386
|
+
return { file, lang, symbols, mtimeMs };
|
|
10387
|
+
}
|
|
9646
10388
|
async function parseSymbols3(opts) {
|
|
10389
|
+
return parseGeneric2(opts);
|
|
10390
|
+
}
|
|
10391
|
+
|
|
10392
|
+
// src/codebase-index/py-parser.ts
|
|
10393
|
+
async function parseSymbols4(opts) {
|
|
9647
10394
|
const { file, content, lang } = opts;
|
|
9648
10395
|
try {
|
|
9649
|
-
|
|
10396
|
+
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
10397
|
+
if (native !== null) return native;
|
|
9650
10398
|
} catch {
|
|
9651
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
9652
10399
|
}
|
|
10400
|
+
return parseGeneric2({ file, content, lang: lang === "py" ? "py" : lang });
|
|
9653
10401
|
}
|
|
9654
10402
|
var PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
9655
10403
|
|
|
@@ -9854,20 +10602,37 @@ visitor.visit(tree)
|
|
|
9854
10602
|
|
|
9855
10603
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
9856
10604
|
`;
|
|
9857
|
-
function resolvePython() {
|
|
10605
|
+
async function resolvePython() {
|
|
9858
10606
|
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
9859
10607
|
for (const name of candidates) {
|
|
9860
10608
|
const resolved = resolveWin32Command(name);
|
|
9861
|
-
|
|
9862
|
-
stdio: "pipe",
|
|
9863
|
-
timeout: 5e3
|
|
9864
|
-
});
|
|
9865
|
-
if (result.error) continue;
|
|
9866
|
-
if (result.status !== 0) continue;
|
|
10609
|
+
if (!await commandIsAvailable(resolved)) continue;
|
|
9867
10610
|
return resolved;
|
|
9868
10611
|
}
|
|
9869
10612
|
return null;
|
|
9870
10613
|
}
|
|
10614
|
+
function commandIsAvailable(command) {
|
|
10615
|
+
return new Promise((resolve14) => {
|
|
10616
|
+
let settled = false;
|
|
10617
|
+
const proc = spawn5(command, ["--version"], {
|
|
10618
|
+
stdio: "ignore",
|
|
10619
|
+
windowsHide: true
|
|
10620
|
+
});
|
|
10621
|
+
const finish = (available) => {
|
|
10622
|
+
if (settled) return;
|
|
10623
|
+
settled = true;
|
|
10624
|
+
clearTimeout(timer);
|
|
10625
|
+
resolve14(available);
|
|
10626
|
+
};
|
|
10627
|
+
const timer = setTimeout(() => {
|
|
10628
|
+
proc.kill("SIGKILL");
|
|
10629
|
+
finish(false);
|
|
10630
|
+
}, 5e3);
|
|
10631
|
+
timer.unref?.();
|
|
10632
|
+
proc.once("error", () => finish(false));
|
|
10633
|
+
proc.once("close", (code) => finish(code === 0));
|
|
10634
|
+
});
|
|
10635
|
+
}
|
|
9871
10636
|
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
9872
10637
|
return new Promise((resolve14, reject) => {
|
|
9873
10638
|
let settled = false;
|
|
@@ -9903,21 +10668,20 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
9903
10668
|
});
|
|
9904
10669
|
}
|
|
9905
10670
|
var _cachedScriptPath = null;
|
|
9906
|
-
var
|
|
10671
|
+
var cachedPyBinary;
|
|
9907
10672
|
async function syncPyParse(filePath, content, lang) {
|
|
9908
10673
|
try {
|
|
9909
10674
|
if (!_cachedScriptPath) {
|
|
9910
|
-
const tmpDir =
|
|
10675
|
+
const tmpDir = path16.join(os6.tmpdir(), "ws-py-parse");
|
|
9911
10676
|
await fs11.mkdir(tmpDir, { recursive: true });
|
|
9912
|
-
_cachedScriptPath =
|
|
10677
|
+
_cachedScriptPath = path16.join(tmpDir, "parse.py");
|
|
9913
10678
|
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
9914
10679
|
}
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
}
|
|
10680
|
+
cachedPyBinary ??= resolvePython();
|
|
10681
|
+
const pyBinary = await cachedPyBinary;
|
|
10682
|
+
if (!pyBinary) return null;
|
|
9919
10683
|
const { code, stdout } = await spawnPyParser(
|
|
9920
|
-
|
|
10684
|
+
pyBinary,
|
|
9921
10685
|
_cachedScriptPath,
|
|
9922
10686
|
filePath,
|
|
9923
10687
|
content
|
|
@@ -9941,31 +10705,40 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
9941
10705
|
}));
|
|
9942
10706
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
9943
10707
|
} catch {
|
|
9944
|
-
return
|
|
10708
|
+
return null;
|
|
9945
10709
|
}
|
|
9946
10710
|
}
|
|
9947
10711
|
|
|
9948
10712
|
// src/codebase-index/rs-parser.ts
|
|
9949
10713
|
init_win32_resolve();
|
|
9950
|
-
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
9951
|
-
import {
|
|
10714
|
+
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
10715
|
+
import { execFile, spawn as spawn6 } from "node:child_process";
|
|
9952
10716
|
import * as fs12 from "node:fs/promises";
|
|
9953
|
-
import * as
|
|
9954
|
-
async function
|
|
10717
|
+
import * as path17 from "node:path";
|
|
10718
|
+
async function parseSymbols5(opts) {
|
|
9955
10719
|
const { file, content, lang } = opts;
|
|
9956
|
-
const nativeAvailable = checkNativeParser();
|
|
10720
|
+
const nativeAvailable = await checkNativeParser();
|
|
9957
10721
|
if (nativeAvailable) {
|
|
9958
|
-
const result = await tryNativeParse(file, content);
|
|
10722
|
+
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
9959
10723
|
if (result) return result;
|
|
9960
10724
|
}
|
|
9961
10725
|
return regexParse({ file, content, lang });
|
|
9962
10726
|
}
|
|
10727
|
+
var nativeParserAvailability;
|
|
10728
|
+
function probe(command, args) {
|
|
10729
|
+
return new Promise((resolve14, reject) => {
|
|
10730
|
+
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
10731
|
+
if (error) reject(error);
|
|
10732
|
+
else resolve14();
|
|
10733
|
+
});
|
|
10734
|
+
});
|
|
10735
|
+
}
|
|
9963
10736
|
function checkNativeParser() {
|
|
9964
|
-
|
|
9965
|
-
execFileSync("rustc", ["--version"], { stdio: "pipe", windowsHide: true });
|
|
9966
|
-
const toolsDir = path16.join(process.cwd(), "tools");
|
|
10737
|
+
nativeParserAvailability ??= (async () => {
|
|
9967
10738
|
try {
|
|
9968
|
-
|
|
10739
|
+
await probe("rustc", ["--version"]);
|
|
10740
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
10741
|
+
await probe(
|
|
9969
10742
|
"cargo",
|
|
9970
10743
|
[
|
|
9971
10744
|
"metadata",
|
|
@@ -9973,23 +10746,21 @@ function checkNativeParser() {
|
|
|
9973
10746
|
"--format-version",
|
|
9974
10747
|
"1",
|
|
9975
10748
|
"--manifest-path",
|
|
9976
|
-
|
|
9977
|
-
]
|
|
9978
|
-
{ stdio: "pipe", windowsHide: true }
|
|
10749
|
+
path17.join(toolsDir, "Cargo.toml")
|
|
10750
|
+
]
|
|
9979
10751
|
);
|
|
9980
10752
|
return true;
|
|
9981
10753
|
} catch {
|
|
9982
10754
|
return false;
|
|
9983
10755
|
}
|
|
9984
|
-
}
|
|
9985
|
-
|
|
9986
|
-
}
|
|
10756
|
+
})();
|
|
10757
|
+
return nativeParserAvailability;
|
|
9987
10758
|
}
|
|
9988
10759
|
async function tryNativeParse(file, content) {
|
|
9989
10760
|
try {
|
|
9990
|
-
const toolsDir =
|
|
9991
|
-
const crateDir =
|
|
9992
|
-
const tmpFile =
|
|
10761
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
10762
|
+
const crateDir = path17.join(toolsDir, "syn-parser");
|
|
10763
|
+
const tmpFile = path17.join(crateDir, "src", "input.rs");
|
|
9993
10764
|
await fs12.writeFile(tmpFile, content, "utf8");
|
|
9994
10765
|
const cargoBinary = resolveWin32Command("cargo");
|
|
9995
10766
|
const result = await new Promise(
|
|
@@ -9997,7 +10768,7 @@ async function tryNativeParse(file, content) {
|
|
|
9997
10768
|
let settled = false;
|
|
9998
10769
|
const proc = spawn6(
|
|
9999
10770
|
cargoBinary,
|
|
10000
|
-
["run", "--manifest-path",
|
|
10771
|
+
["run", "--manifest-path", path17.join(toolsDir, "Cargo.toml")],
|
|
10001
10772
|
{
|
|
10002
10773
|
cwd: process.cwd(),
|
|
10003
10774
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -10111,9 +10882,9 @@ function regexParse(opts) {
|
|
|
10111
10882
|
}
|
|
10112
10883
|
|
|
10113
10884
|
// src/codebase-index/json-parser.ts
|
|
10114
|
-
import { expectDefined as expectDefined4 } from "@wrongstack/core";
|
|
10115
|
-
import * as
|
|
10116
|
-
function
|
|
10885
|
+
import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
|
|
10886
|
+
import * as path18 from "node:path";
|
|
10887
|
+
function parseSymbols6(opts) {
|
|
10117
10888
|
const { file, content, lang } = opts;
|
|
10118
10889
|
try {
|
|
10119
10890
|
return regexParse2({ file, content, lang });
|
|
@@ -10124,9 +10895,9 @@ function parseSymbols5(opts) {
|
|
|
10124
10895
|
function regexParse2(opts) {
|
|
10125
10896
|
const { file, content, lang } = opts;
|
|
10126
10897
|
const symbols = [];
|
|
10127
|
-
const
|
|
10128
|
-
const isPackageJson =
|
|
10129
|
-
const isTsconfig =
|
|
10898
|
+
const basename11 = path18.basename(file).toLowerCase();
|
|
10899
|
+
const isPackageJson = basename11 === "package.json";
|
|
10900
|
+
const isTsconfig = basename11 === "tsconfig.json" || basename11 === "tsconfig.build.json";
|
|
10130
10901
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
10131
10902
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
10132
10903
|
const lines = content.split("\n");
|
|
@@ -10150,11 +10921,11 @@ function regexParse2(opts) {
|
|
|
10150
10921
|
const line = lineFromOffset(offset);
|
|
10151
10922
|
symbols.push(
|
|
10152
10923
|
makeSymbol({
|
|
10153
|
-
name:
|
|
10924
|
+
name: path18.basename(file),
|
|
10154
10925
|
kind: "object",
|
|
10155
10926
|
line,
|
|
10156
10927
|
col: 0,
|
|
10157
|
-
signature: `"${
|
|
10928
|
+
signature: `"${path18.basename(file)}" = { ... }`,
|
|
10158
10929
|
file,
|
|
10159
10930
|
lang
|
|
10160
10931
|
})
|
|
@@ -10316,8 +11087,8 @@ function makeSymbol(opts) {
|
|
|
10316
11087
|
}
|
|
10317
11088
|
|
|
10318
11089
|
// src/codebase-index/yaml-parser.ts
|
|
10319
|
-
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core";
|
|
10320
|
-
function
|
|
11090
|
+
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core/utils";
|
|
11091
|
+
function parseSymbols7(opts) {
|
|
10321
11092
|
const { file, content, lang } = opts;
|
|
10322
11093
|
try {
|
|
10323
11094
|
return regexParse3({ file, content, lang });
|
|
@@ -10466,8 +11237,8 @@ function makeSymbol2(opts) {
|
|
|
10466
11237
|
|
|
10467
11238
|
// src/codebase-index/gitignore.ts
|
|
10468
11239
|
import * as fs13 from "node:fs/promises";
|
|
10469
|
-
import * as
|
|
10470
|
-
import { compileGlob } from "@wrongstack/core";
|
|
11240
|
+
import * as path19 from "node:path";
|
|
11241
|
+
import { compileGlob } from "@wrongstack/core/utils";
|
|
10471
11242
|
function globBody(glob) {
|
|
10472
11243
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
10473
11244
|
}
|
|
@@ -10512,7 +11283,7 @@ function compileGitignore(lines) {
|
|
|
10512
11283
|
async function loadGitignoreMatcher(projectRoot) {
|
|
10513
11284
|
let lines = [];
|
|
10514
11285
|
try {
|
|
10515
|
-
const raw = await fs13.readFile(
|
|
11286
|
+
const raw = await fs13.readFile(path19.join(projectRoot, ".gitignore"), "utf8");
|
|
10516
11287
|
lines = raw.split("\n");
|
|
10517
11288
|
} catch {
|
|
10518
11289
|
}
|
|
@@ -10521,7 +11292,9 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
10521
11292
|
|
|
10522
11293
|
// src/codebase-index/indexer.ts
|
|
10523
11294
|
var YIELD_EVERY_N = 50;
|
|
10524
|
-
|
|
11295
|
+
function resolveParallelBatch() {
|
|
11296
|
+
return indexParallelBatchSize(availableParallelism());
|
|
11297
|
+
}
|
|
10525
11298
|
function yieldEventLoop() {
|
|
10526
11299
|
return new Promise((resolve14) => setImmediate(resolve14));
|
|
10527
11300
|
}
|
|
@@ -10533,22 +11306,12 @@ function throwIfAborted(signal) {
|
|
|
10533
11306
|
function isAbortError(err) {
|
|
10534
11307
|
return err instanceof DOMException && err.name === "AbortError";
|
|
10535
11308
|
}
|
|
10536
|
-
var DEFAULT_IGNORE =
|
|
10537
|
-
"node_modules",
|
|
10538
|
-
".git",
|
|
10539
|
-
"dist",
|
|
10540
|
-
"build",
|
|
10541
|
-
".next",
|
|
10542
|
-
"coverage",
|
|
10543
|
-
".turbo",
|
|
10544
|
-
"__snapshots__",
|
|
10545
|
-
".nyc_output"
|
|
10546
|
-
];
|
|
11309
|
+
var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
|
|
10547
11310
|
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10548
11311
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10549
11312
|
function isWithinProject(projectRoot, file) {
|
|
10550
|
-
const rel =
|
|
10551
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
11313
|
+
const rel = path20.relative(projectRoot, file);
|
|
11314
|
+
return rel !== "" && !rel.startsWith(`..${path20.sep}`) && rel !== ".." && !path20.isAbsolute(rel);
|
|
10552
11315
|
}
|
|
10553
11316
|
function isMissingPathError(err) {
|
|
10554
11317
|
const code = err?.code;
|
|
@@ -10559,18 +11322,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10559
11322
|
const errors = [];
|
|
10560
11323
|
let complete = true;
|
|
10561
11324
|
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
|
-
];
|
|
11325
|
+
const indexableExts = new Set(INDEXABLE_EXTENSIONS);
|
|
10574
11326
|
let dirCount = 0;
|
|
10575
11327
|
const walk = async (dir) => {
|
|
10576
11328
|
throwIfAborted(signal);
|
|
@@ -10589,19 +11341,16 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10589
11341
|
dirCount++;
|
|
10590
11342
|
for (const e of entries) {
|
|
10591
11343
|
if (ignoreSet.has(e.name)) continue;
|
|
10592
|
-
const full =
|
|
10593
|
-
const rel =
|
|
11344
|
+
const full = path20.join(dir, e.name);
|
|
11345
|
+
const rel = path20.relative(projectRoot, full).replace(/\\/g, "/");
|
|
10594
11346
|
if (e.isDirectory()) {
|
|
10595
11347
|
if (isGitIgnored(rel, true)) continue;
|
|
10596
11348
|
await walk(full);
|
|
10597
11349
|
} else if (e.isFile()) {
|
|
10598
11350
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10599
|
-
const ext =
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
results.push(full);
|
|
10603
|
-
break;
|
|
10604
|
-
}
|
|
11351
|
+
const ext = path20.extname(e.name).toLowerCase();
|
|
11352
|
+
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
11353
|
+
results.push(full);
|
|
10605
11354
|
}
|
|
10606
11355
|
}
|
|
10607
11356
|
}
|
|
@@ -10610,24 +11359,25 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10610
11359
|
return { files: results, complete, errors };
|
|
10611
11360
|
}
|
|
10612
11361
|
async function parseFile(file, content, lang) {
|
|
10613
|
-
|
|
11362
|
+
const symbolLang = lang;
|
|
11363
|
+
switch (symbolLang) {
|
|
10614
11364
|
case "ts":
|
|
10615
11365
|
case "tsx":
|
|
10616
11366
|
case "js":
|
|
10617
11367
|
case "jsx":
|
|
10618
|
-
return parseSymbols({ file, content, lang });
|
|
11368
|
+
return parseSymbols({ file, content, lang: symbolLang });
|
|
10619
11369
|
case "go":
|
|
10620
11370
|
return parseSymbols2({ file, content, lang: "go" });
|
|
10621
11371
|
case "py":
|
|
10622
|
-
return
|
|
11372
|
+
return parseSymbols4({ file, content, lang: "py" });
|
|
10623
11373
|
case "rs":
|
|
10624
|
-
return
|
|
11374
|
+
return parseSymbols5({ file, content, lang: "rs" });
|
|
10625
11375
|
case "json":
|
|
10626
|
-
return
|
|
11376
|
+
return parseSymbols6({ file, content, lang: "json" });
|
|
10627
11377
|
case "yaml":
|
|
10628
|
-
return
|
|
11378
|
+
return parseSymbols7({ file, content, lang: "yaml" });
|
|
10629
11379
|
default:
|
|
10630
|
-
return { file,
|
|
11380
|
+
return parseSymbols3({ file, content, lang: symbolLang });
|
|
10631
11381
|
}
|
|
10632
11382
|
}
|
|
10633
11383
|
function assignRefsToSymbols2(refs, symbols) {
|
|
@@ -10675,10 +11425,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10675
11425
|
let discoveredFiles = null;
|
|
10676
11426
|
let discoveryComplete = true;
|
|
10677
11427
|
if (opts.files && opts.files.length > 0) {
|
|
10678
|
-
files = opts.files.map((f) =>
|
|
11428
|
+
files = opts.files.map((f) => path20.resolve(projectRoot, f)).filter((f) => {
|
|
10679
11429
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
10680
|
-
const rel =
|
|
10681
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
11430
|
+
const rel = path20.relative(projectRoot, f).replace(/\\/g, "/");
|
|
11431
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path20.basename(f)) && !isGitIgnored(rel, false);
|
|
10682
11432
|
});
|
|
10683
11433
|
} else {
|
|
10684
11434
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -10699,15 +11449,19 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10699
11449
|
if (!force) {
|
|
10700
11450
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
10701
11451
|
}
|
|
11452
|
+
const parallelBatch = resolveParallelBatch();
|
|
10702
11453
|
let filesSinceLastYield = 0;
|
|
10703
|
-
for (let batchStart = 0; batchStart < files.length; batchStart +=
|
|
10704
|
-
const batchEnd = Math.min(batchStart +
|
|
11454
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
|
|
11455
|
+
const batchEnd = Math.min(batchStart + parallelBatch, files.length);
|
|
10705
11456
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
10706
11457
|
opts.onProgress?.(batchEnd, files.length);
|
|
10707
11458
|
filesSinceLastYield += batchFiles.length;
|
|
10708
11459
|
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
10709
11460
|
filesSinceLastYield = 0;
|
|
10710
11461
|
await yieldEventLoop();
|
|
11462
|
+
if (isFrugalPerf()) {
|
|
11463
|
+
await new Promise((r) => setTimeout(r, 8));
|
|
11464
|
+
}
|
|
10711
11465
|
throwIfAborted(signal);
|
|
10712
11466
|
}
|
|
10713
11467
|
const statOpts = signal ? { signal } : {};
|
|
@@ -10811,9 +11565,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10811
11565
|
continue;
|
|
10812
11566
|
}
|
|
10813
11567
|
if (parsed.symbols.length === 0) {
|
|
10814
|
-
store.
|
|
10815
|
-
store.deleteSymbolsForFile(file);
|
|
10816
|
-
store.upsertFile({
|
|
11568
|
+
store.replaceEmptyFile({
|
|
10817
11569
|
file,
|
|
10818
11570
|
lang,
|
|
10819
11571
|
mtimeMs: Math.floor(stat17.mtimeMs),
|
|
@@ -10882,6 +11634,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
10882
11634
|
}
|
|
10883
11635
|
store.resolveRefs();
|
|
10884
11636
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
11637
|
+
store.optimize();
|
|
10885
11638
|
const durationMs = Date.now() - startMs;
|
|
10886
11639
|
store.setLastIndexed(Date.now());
|
|
10887
11640
|
return {
|
|
@@ -11124,10 +11877,10 @@ async function callInline(op, args, opts) {
|
|
|
11124
11877
|
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
11125
11878
|
}
|
|
11126
11879
|
}
|
|
11127
|
-
var
|
|
11880
|
+
var chain2 = Promise.resolve();
|
|
11128
11881
|
function withMutex(job) {
|
|
11129
|
-
const run =
|
|
11130
|
-
|
|
11882
|
+
const run = chain2.then(job, job);
|
|
11883
|
+
chain2 = run.then(
|
|
11131
11884
|
() => void 0,
|
|
11132
11885
|
() => void 0
|
|
11133
11886
|
);
|
|
@@ -11476,7 +12229,7 @@ var codebaseStatsTool = {
|
|
|
11476
12229
|
|
|
11477
12230
|
// src/design.ts
|
|
11478
12231
|
import * as fs16 from "node:fs/promises";
|
|
11479
|
-
import * as
|
|
12232
|
+
import * as path21 from "node:path";
|
|
11480
12233
|
import {
|
|
11481
12234
|
applyTokenOverrides,
|
|
11482
12235
|
getDesignKitLoader,
|
|
@@ -11485,23 +12238,24 @@ import {
|
|
|
11485
12238
|
materializeTokens,
|
|
11486
12239
|
recordKitChoice,
|
|
11487
12240
|
recordOverrides,
|
|
12241
|
+
resolveSemanticTune,
|
|
11488
12242
|
runDesignVerify,
|
|
11489
12243
|
setActiveKit,
|
|
11490
12244
|
setDesignOverrides
|
|
11491
|
-
} from "@wrongstack/core";
|
|
12245
|
+
} from "@wrongstack/core/design";
|
|
11492
12246
|
async function resolveReal(p) {
|
|
11493
|
-
const resolved =
|
|
11494
|
-
let
|
|
12247
|
+
const resolved = path21.resolve(p);
|
|
12248
|
+
let probe2 = resolved;
|
|
11495
12249
|
const missing = [];
|
|
11496
12250
|
for (; ; ) {
|
|
11497
12251
|
try {
|
|
11498
|
-
return
|
|
12252
|
+
return path21.resolve(await fs16.realpath(probe2), ...missing);
|
|
11499
12253
|
} catch (err) {
|
|
11500
12254
|
if (err.code === "ENOENT") {
|
|
11501
|
-
const parent =
|
|
11502
|
-
if (parent ===
|
|
11503
|
-
missing.unshift(
|
|
11504
|
-
|
|
12255
|
+
const parent = path21.dirname(probe2);
|
|
12256
|
+
if (parent === probe2) return resolved;
|
|
12257
|
+
missing.unshift(path21.basename(probe2));
|
|
12258
|
+
probe2 = parent;
|
|
11505
12259
|
continue;
|
|
11506
12260
|
}
|
|
11507
12261
|
return resolved;
|
|
@@ -11531,8 +12285,8 @@ var designTool = {
|
|
|
11531
12285
|
properties: {
|
|
11532
12286
|
action: {
|
|
11533
12287
|
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
|
|
12288
|
+
enum: ["list", "use", "foundations", "set", "tune", "materialize", "verify"],
|
|
12289
|
+
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
12290
|
},
|
|
11537
12291
|
kit: {
|
|
11538
12292
|
type: "string",
|
|
@@ -11548,6 +12302,16 @@ var designTool = {
|
|
|
11548
12302
|
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
12303
|
additionalProperties: { type: "string" }
|
|
11550
12304
|
},
|
|
12305
|
+
tune: {
|
|
12306
|
+
type: "object",
|
|
12307
|
+
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.',
|
|
12308
|
+
properties: {
|
|
12309
|
+
radius: { type: "string" },
|
|
12310
|
+
density: { type: "string" },
|
|
12311
|
+
font: { type: "string" },
|
|
12312
|
+
motion: { type: "string" }
|
|
12313
|
+
}
|
|
12314
|
+
},
|
|
11551
12315
|
out: {
|
|
11552
12316
|
type: "string",
|
|
11553
12317
|
description: "Materialize output path (project-relative). Defaults to a per-stack convention."
|
|
@@ -11645,6 +12409,28 @@ ${body}`
|
|
|
11645
12409
|
These win over kit tokens. Run \`design {action:"materialize"}\` to write them to a theme file.`
|
|
11646
12410
|
};
|
|
11647
12411
|
}
|
|
12412
|
+
if (action === "tune") {
|
|
12413
|
+
const patch = resolveSemanticTune(input.tune ?? {});
|
|
12414
|
+
if (Object.keys(patch).length === 0) {
|
|
12415
|
+
return {
|
|
12416
|
+
action,
|
|
12417
|
+
output: 'No recognized knobs. Pass tune:{ radius:"lg", density:"compact", font:"\u2026", motion:"snappy" }.'
|
|
12418
|
+
};
|
|
12419
|
+
}
|
|
12420
|
+
const merged = await recordOverrides(ctx.projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
|
|
12421
|
+
if (!merged) {
|
|
12422
|
+
return {
|
|
12423
|
+
action,
|
|
12424
|
+
output: 'No active kit. Pick one first: `design {action:"use", kit:"<id>"}`.'
|
|
12425
|
+
};
|
|
12426
|
+
}
|
|
12427
|
+
setDesignOverrides(ctx, merged);
|
|
12428
|
+
return {
|
|
12429
|
+
action,
|
|
12430
|
+
output: `Tuned. Resolved ${Object.keys(patch).length} token override(s): ${Object.entries(patch).map(([k, v]) => `${k}=${v}`).join(", ")}
|
|
12431
|
+
Run \`design {action:"materialize"}\` to write the tuned tokens to your theme file.`
|
|
12432
|
+
};
|
|
12433
|
+
}
|
|
11648
12434
|
if (action === "materialize") {
|
|
11649
12435
|
const active = await loadActiveKit(ctx.projectRoot);
|
|
11650
12436
|
if (!active) {
|
|
@@ -11666,11 +12452,11 @@ These win over kit tokens. Run \`design {action:"materialize"}\` to write them t
|
|
|
11666
12452
|
outPath: input.out
|
|
11667
12453
|
});
|
|
11668
12454
|
const root = await resolveReal(ctx.projectRoot);
|
|
11669
|
-
const absResolved =
|
|
11670
|
-
const absParent = await resolveReal(
|
|
11671
|
-
const abs =
|
|
11672
|
-
const rel =
|
|
11673
|
-
if (rel.startsWith("..") ||
|
|
12455
|
+
const absResolved = path21.isAbsolute(result.path) ? path21.resolve(result.path) : path21.resolve(path21.join(ctx.projectRoot, result.path));
|
|
12456
|
+
const absParent = await resolveReal(path21.dirname(absResolved));
|
|
12457
|
+
const abs = path21.join(absParent, path21.basename(absResolved));
|
|
12458
|
+
const rel = path21.relative(root, abs);
|
|
12459
|
+
if (rel.startsWith("..") || path21.isAbsolute(rel)) {
|
|
11674
12460
|
throw new Error(
|
|
11675
12461
|
`design: materialize path "${result.path}" would escape the project root`
|
|
11676
12462
|
);
|
|
@@ -11694,7 +12480,7 @@ ${result.content}
|
|
|
11694
12480
|
\`\`\``
|
|
11695
12481
|
};
|
|
11696
12482
|
}
|
|
11697
|
-
await fs16.mkdir(
|
|
12483
|
+
await fs16.mkdir(path21.dirname(abs), { recursive: true });
|
|
11698
12484
|
await fs16.writeFile(abs, result.content);
|
|
11699
12485
|
return {
|
|
11700
12486
|
action,
|
|
@@ -11746,8 +12532,8 @@ init_util();
|
|
|
11746
12532
|
import { spawn as spawn7 } from "node:child_process";
|
|
11747
12533
|
import { statSync as statSync2 } from "node:fs";
|
|
11748
12534
|
import * as fs17 from "node:fs/promises";
|
|
11749
|
-
import * as
|
|
11750
|
-
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core";
|
|
12535
|
+
import * as path22 from "node:path";
|
|
12536
|
+
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core/utils";
|
|
11751
12537
|
var diffTool = {
|
|
11752
12538
|
name: "diff",
|
|
11753
12539
|
category: "Filesystem",
|
|
@@ -11830,11 +12616,11 @@ function findGitDir(cwd) {
|
|
|
11830
12616
|
let dir = cwd;
|
|
11831
12617
|
for (let i = 0; i < 20; i++) {
|
|
11832
12618
|
try {
|
|
11833
|
-
const stat17 = statSync2(
|
|
12619
|
+
const stat17 = statSync2(path22.join(dir, ".git"));
|
|
11834
12620
|
if (stat17.isDirectory()) return dir;
|
|
11835
12621
|
} catch {
|
|
11836
12622
|
}
|
|
11837
|
-
const parent =
|
|
12623
|
+
const parent = path22.dirname(dir);
|
|
11838
12624
|
if (parent === dir) break;
|
|
11839
12625
|
dir = parent;
|
|
11840
12626
|
}
|
|
@@ -12048,7 +12834,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
12048
12834
|
// src/e2e.ts
|
|
12049
12835
|
init_util();
|
|
12050
12836
|
import { open, readdir as readdir5 } from "node:fs/promises";
|
|
12051
|
-
import * as
|
|
12837
|
+
import * as path23 from "node:path";
|
|
12052
12838
|
async function readBoundedText(filePath, maxBytes) {
|
|
12053
12839
|
let handle;
|
|
12054
12840
|
try {
|
|
@@ -12098,8 +12884,8 @@ var MAX_PACKAGE_BYTES = 512 * 1024;
|
|
|
12098
12884
|
var MAX_CONFIG_BYTES = 512 * 1024;
|
|
12099
12885
|
var MAX_SPEC_SAMPLES = 20;
|
|
12100
12886
|
function relativePath(root, target) {
|
|
12101
|
-
const value =
|
|
12102
|
-
return value.split(
|
|
12887
|
+
const value = path23.relative(root, target) || ".";
|
|
12888
|
+
return value.split(path23.sep).join("/");
|
|
12103
12889
|
}
|
|
12104
12890
|
function escapeRegExp(value) {
|
|
12105
12891
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -12171,7 +12957,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
12171
12957
|
}
|
|
12172
12958
|
for (const entry of entries) {
|
|
12173
12959
|
signal.throwIfAborted();
|
|
12174
|
-
const absolutePath =
|
|
12960
|
+
const absolutePath = path23.join(current.directory, entry.name);
|
|
12175
12961
|
if (entry.isFile()) {
|
|
12176
12962
|
if (entry.name === "package.json") result.packageFiles.push(absolutePath);
|
|
12177
12963
|
const framework = CONFIG_NAMES.get(entry.name);
|
|
@@ -12232,9 +13018,9 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
12232
13018
|
if (names.has("bun.lock") || names.has("bun.lockb")) return "bun";
|
|
12233
13019
|
if (names.has("package-lock.json") || names.has("npm-shrinkwrap.json")) return "npm";
|
|
12234
13020
|
if (directory === scanRoot) break;
|
|
12235
|
-
const parent =
|
|
12236
|
-
const relativeParent =
|
|
12237
|
-
if (parent === directory || relativeParent.startsWith("..") ||
|
|
13021
|
+
const parent = path23.dirname(directory);
|
|
13022
|
+
const relativeParent = path23.relative(scanRoot, parent);
|
|
13023
|
+
if (parent === directory || relativeParent.startsWith("..") || path23.isAbsolute(relativeParent)) {
|
|
12238
13024
|
break;
|
|
12239
13025
|
}
|
|
12240
13026
|
directory = parent;
|
|
@@ -12279,7 +13065,7 @@ function isSpec(framework, filename) {
|
|
|
12279
13065
|
return /\.(?:spec|test)\.(?:[cm]?[jt]sx?)$/i.test(filename);
|
|
12280
13066
|
}
|
|
12281
13067
|
async function collectSpecs(root, framework, testDirectory, signal) {
|
|
12282
|
-
const roots = testDirectory ? [
|
|
13068
|
+
const roots = testDirectory ? [path23.resolve(root, testDirectory)] : framework === "cypress" ? [path23.join(root, "cypress", "e2e"), path23.join(root, "cypress", "integration")] : [path23.join(root, "tests")];
|
|
12283
13069
|
const samples = [];
|
|
12284
13070
|
let count = 0;
|
|
12285
13071
|
let scanned = 0;
|
|
@@ -12298,7 +13084,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
12298
13084
|
}
|
|
12299
13085
|
for (const entry of entries) {
|
|
12300
13086
|
signal.throwIfAborted();
|
|
12301
|
-
const target =
|
|
13087
|
+
const target = path23.join(directory, entry.name);
|
|
12302
13088
|
if (entry.isDirectory() && !SKIP_DIRECTORIES.has(entry.name)) queue.push(target);
|
|
12303
13089
|
else if (entry.isFile() && isSpec(framework, entry.name)) {
|
|
12304
13090
|
count += 1;
|
|
@@ -12318,7 +13104,7 @@ function nearestPackage(projectRoot, packagesByDirectory, scanRoot) {
|
|
|
12318
13104
|
const found = packagesByDirectory.get(directory);
|
|
12319
13105
|
if (found) return found;
|
|
12320
13106
|
if (directory === scanRoot) return void 0;
|
|
12321
|
-
const parent =
|
|
13107
|
+
const parent = path23.dirname(directory);
|
|
12322
13108
|
if (parent === directory || relativePath(scanRoot, parent).startsWith("..")) return void 0;
|
|
12323
13109
|
directory = parent;
|
|
12324
13110
|
}
|
|
@@ -12329,13 +13115,13 @@ async function discoverE2EProjects(root, options) {
|
|
|
12329
13115
|
const packages = (await Promise.all(scan.packageFiles.map(readPackageInfo))).filter(
|
|
12330
13116
|
(info) => Boolean(info)
|
|
12331
13117
|
);
|
|
12332
|
-
const packagesByDirectory = new Map(packages.map((info) => [
|
|
13118
|
+
const packagesByDirectory = new Map(packages.map((info) => [path23.dirname(info.path), info]));
|
|
12333
13119
|
const candidates = /* @__PURE__ */ new Map();
|
|
12334
13120
|
for (const config of scan.configs) {
|
|
12335
13121
|
if (options.framework && options.framework !== "all" && config.framework !== options.framework) {
|
|
12336
13122
|
continue;
|
|
12337
13123
|
}
|
|
12338
|
-
const projectRoot =
|
|
13124
|
+
const projectRoot = path23.dirname(config.absolutePath);
|
|
12339
13125
|
candidates.set(`${config.framework}:${projectRoot}`, {
|
|
12340
13126
|
framework: config.framework,
|
|
12341
13127
|
root: projectRoot,
|
|
@@ -12343,7 +13129,7 @@ async function discoverE2EProjects(root, options) {
|
|
|
12343
13129
|
});
|
|
12344
13130
|
}
|
|
12345
13131
|
for (const info of packages) {
|
|
12346
|
-
const projectRoot =
|
|
13132
|
+
const projectRoot = path23.dirname(info.path);
|
|
12347
13133
|
for (const framework of frameworkFromPackage(info)) {
|
|
12348
13134
|
if (options.framework && options.framework !== "all" && framework !== options.framework)
|
|
12349
13135
|
continue;
|
|
@@ -12359,10 +13145,10 @@ async function discoverE2EProjects(root, options) {
|
|
|
12359
13145
|
const scripts = matchingScripts(info, candidate.framework);
|
|
12360
13146
|
const manager = await detectPackageManager3(candidate.root, root, info?.packageManager);
|
|
12361
13147
|
const testDirectory = candidate.framework === "playwright" ? staticString(source, "testDir") : void 0;
|
|
12362
|
-
const resolvedTestDirectory = testDirectory ?
|
|
12363
|
-
const relativeTestDirectory = resolvedTestDirectory ?
|
|
13148
|
+
const resolvedTestDirectory = testDirectory ? path23.resolve(candidate.root, testDirectory) : void 0;
|
|
13149
|
+
const relativeTestDirectory = resolvedTestDirectory ? path23.relative(root, resolvedTestDirectory) : void 0;
|
|
12364
13150
|
const unsafeTestDirectory = Boolean(
|
|
12365
|
-
relativeTestDirectory && (relativeTestDirectory.startsWith("..") ||
|
|
13151
|
+
relativeTestDirectory && (relativeTestDirectory.startsWith("..") || path23.isAbsolute(relativeTestDirectory))
|
|
12366
13152
|
);
|
|
12367
13153
|
const specs = options.includeSpecs === false || unsafeTestDirectory ? { count: 0, samples: [], truncated: false } : await collectSpecs(candidate.root, candidate.framework, testDirectory, options.signal);
|
|
12368
13154
|
const warnings = [];
|
|
@@ -12467,10 +13253,10 @@ import {
|
|
|
12467
13253
|
atomicWrite as atomicWrite2,
|
|
12468
13254
|
detectNewlineStyle,
|
|
12469
13255
|
normalizeToLf,
|
|
12470
|
-
ToolValidationError,
|
|
12471
13256
|
toStyle,
|
|
12472
13257
|
unifiedDiff
|
|
12473
|
-
} from "@wrongstack/core";
|
|
13258
|
+
} from "@wrongstack/core/utils";
|
|
13259
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
12474
13260
|
|
|
12475
13261
|
// src/_edit-match.ts
|
|
12476
13262
|
var TIER_LABEL = {
|
|
@@ -12698,7 +13484,7 @@ function prefixSimilarity(a, b) {
|
|
|
12698
13484
|
}
|
|
12699
13485
|
|
|
12700
13486
|
// src/_syntax-check.ts
|
|
12701
|
-
import * as
|
|
13487
|
+
import * as path24 from "node:path";
|
|
12702
13488
|
var TS_LIKE = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
12703
13489
|
var MAX_CHECK_CHARS = 15e5;
|
|
12704
13490
|
var MAX_ERRORS = 5;
|
|
@@ -12722,15 +13508,15 @@ async function checkSyntax(filePath, content, previousContent) {
|
|
|
12722
13508
|
return { errors, preExisting };
|
|
12723
13509
|
}
|
|
12724
13510
|
function isJsoncFile(filePath) {
|
|
12725
|
-
const base =
|
|
13511
|
+
const base = path24.basename(filePath).toLowerCase();
|
|
12726
13512
|
if (base.endsWith(".jsonc")) return true;
|
|
12727
13513
|
if (/^(tsconfig|jsconfig)([.-].*)?\.json$/.test(base)) return true;
|
|
12728
|
-
const dir =
|
|
13514
|
+
const dir = path24.basename(path24.dirname(filePath)).toLowerCase();
|
|
12729
13515
|
return dir === ".vscode";
|
|
12730
13516
|
}
|
|
12731
13517
|
async function parseErrors(filePath, content) {
|
|
12732
13518
|
if (content.length > MAX_CHECK_CHARS) return void 0;
|
|
12733
|
-
const ext =
|
|
13519
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
12734
13520
|
if (ext === ".json" || ext === ".jsonc") {
|
|
12735
13521
|
try {
|
|
12736
13522
|
JSON.parse(content);
|
|
@@ -12757,7 +13543,7 @@ async function parseErrors(filePath, content) {
|
|
|
12757
13543
|
ts2.ScriptKind.JSX
|
|
12758
13544
|
);
|
|
12759
13545
|
const sourceFile = ts2.createSourceFile(
|
|
12760
|
-
|
|
13546
|
+
path24.basename(filePath),
|
|
12761
13547
|
content,
|
|
12762
13548
|
ts2.ScriptTarget.Latest,
|
|
12763
13549
|
/* setParentNodes */
|
|
@@ -12992,7 +13778,7 @@ import {
|
|
|
12992
13778
|
emitProcessCompleted as emitProcessCompleted3,
|
|
12993
13779
|
emitProcessOutput as emitProcessOutput3,
|
|
12994
13780
|
emitProcessStarted as emitProcessStarted3
|
|
12995
|
-
} from "@wrongstack/core";
|
|
13781
|
+
} from "@wrongstack/core/observability";
|
|
12996
13782
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
12997
13783
|
init_output_spool();
|
|
12998
13784
|
init_util();
|
|
@@ -13282,7 +14068,7 @@ function levelRank(level) {
|
|
|
13282
14068
|
|
|
13283
14069
|
// src/exec-kill-guard.ts
|
|
13284
14070
|
import * as os7 from "node:os";
|
|
13285
|
-
import * as
|
|
14071
|
+
import * as path25 from "node:path";
|
|
13286
14072
|
var isWin3 = os7.platform() === "win32";
|
|
13287
14073
|
async function checkExecKillCommand(cmd, args) {
|
|
13288
14074
|
if (!cmd) return { blocked: false };
|
|
@@ -13495,7 +14281,7 @@ async function checkKillTarget(target) {
|
|
|
13495
14281
|
reason: `Blocked: kill ${target.signal} '${target.name}' targets a WrongStack process name.`
|
|
13496
14282
|
};
|
|
13497
14283
|
}
|
|
13498
|
-
const currentImage =
|
|
14284
|
+
const currentImage = path25.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
|
|
13499
14285
|
const targetsNodeRuntime = nameLower === "node" || nameLower.startsWith("node");
|
|
13500
14286
|
if (targetsNodeRuntime && currentImage === "node") {
|
|
13501
14287
|
return {
|
|
@@ -14282,7 +15068,7 @@ var execTool = {
|
|
|
14282
15068
|
command: cmd,
|
|
14283
15069
|
args: input.args ?? [],
|
|
14284
15070
|
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.`,
|
|
15071
|
+
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
15072
|
exitCode: 1,
|
|
14287
15073
|
truncated: false,
|
|
14288
15074
|
allowed: false,
|
|
@@ -14494,18 +15280,17 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
14494
15280
|
}
|
|
14495
15281
|
|
|
14496
15282
|
// src/fetch.ts
|
|
14497
|
-
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
15283
|
+
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core/types";
|
|
14498
15284
|
import TurndownService from "turndown";
|
|
14499
15285
|
|
|
14500
15286
|
// src/_fetch-guard.ts
|
|
14501
15287
|
import * as dns2 from "node:dns/promises";
|
|
14502
15288
|
import * as net3 from "node:net";
|
|
14503
15289
|
import {
|
|
14504
|
-
FetchError,
|
|
14505
|
-
ToolValidationError as ToolValidationError2,
|
|
14506
15290
|
isPrivateIPv4 as isPrivateIPv42,
|
|
14507
15291
|
isPrivateIPv6 as isPrivateIPv62
|
|
14508
|
-
} from "@wrongstack/core";
|
|
15292
|
+
} from "@wrongstack/core/utils";
|
|
15293
|
+
import { FetchError, ToolValidationError as ToolValidationError2 } from "@wrongstack/core/types";
|
|
14509
15294
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
14510
15295
|
var nativeGlobalFetch = globalThis.fetch;
|
|
14511
15296
|
var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
|
|
@@ -15003,7 +15788,8 @@ init_util();
|
|
|
15003
15788
|
import { spawn as spawn9 } from "node:child_process";
|
|
15004
15789
|
import { statSync as statSync3 } from "node:fs";
|
|
15005
15790
|
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
15006
|
-
import { assessCommitSafety
|
|
15791
|
+
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
15792
|
+
import { buildChildEnv as buildChildEnv4 } from "@wrongstack/core/utils";
|
|
15007
15793
|
var TIMEOUT_MS2 = 3e4;
|
|
15008
15794
|
var MAX_OUTPUT3 = 1e5;
|
|
15009
15795
|
var gitTool = {
|
|
@@ -15283,8 +16069,8 @@ function runGit2(args, cwd, signal) {
|
|
|
15283
16069
|
|
|
15284
16070
|
// src/glob.ts
|
|
15285
16071
|
import * as fs20 from "node:fs/promises";
|
|
15286
|
-
import * as
|
|
15287
|
-
import { compileGlob as
|
|
16072
|
+
import * as path26 from "node:path";
|
|
16073
|
+
import { compileGlob as compileGlob2, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS2 } from "@wrongstack/core/utils";
|
|
15288
16074
|
|
|
15289
16075
|
// src/_concurrency.ts
|
|
15290
16076
|
async function mapWithConcurrency(items, limit, fn) {
|
|
@@ -15305,7 +16091,7 @@ async function mapWithConcurrency(items, limit, fn) {
|
|
|
15305
16091
|
|
|
15306
16092
|
// src/glob.ts
|
|
15307
16093
|
init_util();
|
|
15308
|
-
var DEFAULT_IGNORE2 =
|
|
16094
|
+
var DEFAULT_IGNORE2 = DEFAULT_WALK_IGNORE_DIRS2;
|
|
15309
16095
|
var WALK_CONCURRENCY = 16;
|
|
15310
16096
|
var globTool = {
|
|
15311
16097
|
name: "glob",
|
|
@@ -15345,8 +16131,8 @@ var globTool = {
|
|
|
15345
16131
|
const signal = opts?.signal;
|
|
15346
16132
|
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
15347
16133
|
const limit = Math.max(1, Math.min(input.limit ?? 1e3, 5e3));
|
|
15348
|
-
const
|
|
15349
|
-
const re =
|
|
16134
|
+
const isGitIgnored = await loadGitignoreMatcher(base);
|
|
16135
|
+
const re = compileGlob2(input.pattern);
|
|
15350
16136
|
const results = [];
|
|
15351
16137
|
let truncated = false;
|
|
15352
16138
|
const pushResult = async (full) => {
|
|
@@ -15385,12 +16171,13 @@ var globTool = {
|
|
|
15385
16171
|
for (const e of entries) {
|
|
15386
16172
|
const name = e.name;
|
|
15387
16173
|
if (DEFAULT_IGNORE2.includes(name)) continue;
|
|
15388
|
-
if (ignored.includes(name)) continue;
|
|
15389
16174
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
15390
|
-
const full =
|
|
16175
|
+
const full = path26.join(dir, name);
|
|
15391
16176
|
if (e.isDirectory()) {
|
|
16177
|
+
if (isGitIgnored(rel, true)) continue;
|
|
15392
16178
|
subdirs.push({ full, rel });
|
|
15393
16179
|
} else if (e.isFile()) {
|
|
16180
|
+
if (isGitIgnored(rel, false)) continue;
|
|
15394
16181
|
re.lastIndex = 0;
|
|
15395
16182
|
const relMatch = re.test(rel);
|
|
15396
16183
|
re.lastIndex = 0;
|
|
@@ -15402,10 +16189,12 @@ var globTool = {
|
|
|
15402
16189
|
try {
|
|
15403
16190
|
const st = await fs20.stat(full);
|
|
15404
16191
|
if (st.isDirectory()) {
|
|
16192
|
+
if (isGitIgnored(rel, true)) continue;
|
|
15405
16193
|
const real = await fs20.realpath(full);
|
|
15406
16194
|
await assertRealInsideRoot(real, ctx);
|
|
15407
16195
|
subdirs.push({ full, rel });
|
|
15408
16196
|
} else if (st.isFile()) {
|
|
16197
|
+
if (isGitIgnored(rel, false)) continue;
|
|
15409
16198
|
const real = await fs20.realpath(full);
|
|
15410
16199
|
await assertRealInsideRoot(real, ctx);
|
|
15411
16200
|
re.lastIndex = 0;
|
|
@@ -15429,21 +16218,14 @@ var globTool = {
|
|
|
15429
16218
|
return { files: results.map((r) => r.rel), truncated };
|
|
15430
16219
|
}
|
|
15431
16220
|
};
|
|
15432
|
-
async function readGitignore(dir) {
|
|
15433
|
-
try {
|
|
15434
|
-
const raw = await fs20.readFile(path25.join(dir, ".gitignore"), "utf8");
|
|
15435
|
-
return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
15436
|
-
} catch {
|
|
15437
|
-
return [];
|
|
15438
|
-
}
|
|
15439
|
-
}
|
|
15440
16221
|
|
|
15441
16222
|
// src/grep.ts
|
|
15442
|
-
import { expectDefined as expectDefined7 } from "@wrongstack/core";
|
|
16223
|
+
import { expectDefined as expectDefined7 } from "@wrongstack/core/utils";
|
|
15443
16224
|
import { spawn as spawn10 } from "node:child_process";
|
|
15444
16225
|
import * as fs21 from "node:fs/promises";
|
|
15445
|
-
import * as
|
|
15446
|
-
import {
|
|
16226
|
+
import * as path27 from "node:path";
|
|
16227
|
+
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
16228
|
+
import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS3 } from "@wrongstack/core/utils";
|
|
15447
16229
|
|
|
15448
16230
|
// src/_regex.ts
|
|
15449
16231
|
var MAX_PATTERN_LEN = 256;
|
|
@@ -15492,7 +16274,7 @@ function capSubject(line) {
|
|
|
15492
16274
|
|
|
15493
16275
|
// src/grep.ts
|
|
15494
16276
|
init_util();
|
|
15495
|
-
var DEFAULT_IGNORE3 =
|
|
16277
|
+
var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
|
|
15496
16278
|
var NATIVE_SCAN_CONCURRENCY = 32;
|
|
15497
16279
|
var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
|
|
15498
16280
|
var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
@@ -15609,6 +16391,10 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
15609
16391
|
for (const ignored of DEFAULT_IGNORE3) {
|
|
15610
16392
|
args.push("--glob", `!${ignored}/**`, "--glob", `!**/${ignored}/**`);
|
|
15611
16393
|
}
|
|
16394
|
+
const gitignorePath = path27.join(base, ".gitignore");
|
|
16395
|
+
if (await fs21.access(gitignorePath).then(() => true, () => false)) {
|
|
16396
|
+
args.push("--ignore-file", gitignorePath);
|
|
16397
|
+
}
|
|
15612
16398
|
if (input.glob) args.push("--glob", input.glob);
|
|
15613
16399
|
args.push("--", input.pattern, base);
|
|
15614
16400
|
const matches = [];
|
|
@@ -15733,7 +16519,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15733
16519
|
});
|
|
15734
16520
|
}
|
|
15735
16521
|
const re = compiled.regex;
|
|
15736
|
-
const globRe = input.glob ?
|
|
16522
|
+
const globRe = input.glob ? compileGlob3(input.glob) : null;
|
|
16523
|
+
const isGitIgnored = await loadGitignoreMatcher(base);
|
|
15737
16524
|
const matches = [];
|
|
15738
16525
|
const countOnlyFirstHit = mode === "count" && limit === 1;
|
|
15739
16526
|
const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
|
|
@@ -15821,7 +16608,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15821
16608
|
} catch {
|
|
15822
16609
|
}
|
|
15823
16610
|
};
|
|
15824
|
-
const walk = async (dir) => {
|
|
16611
|
+
const walk = async (dir, relPrefix) => {
|
|
15825
16612
|
if (stopped || signal.aborted) return;
|
|
15826
16613
|
let entries;
|
|
15827
16614
|
try {
|
|
@@ -15835,17 +16622,24 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15835
16622
|
if (stopped) return;
|
|
15836
16623
|
if (DEFAULT_IGNORE3.includes(e.name)) continue;
|
|
15837
16624
|
if (e.isSymbolicLink()) continue;
|
|
15838
|
-
const
|
|
16625
|
+
const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
|
|
16626
|
+
const full = path27.join(dir, e.name);
|
|
15839
16627
|
if (e.isDirectory()) {
|
|
15840
|
-
|
|
16628
|
+
if (isGitIgnored(rel, true)) continue;
|
|
16629
|
+
subdirs.push({ full, rel });
|
|
15841
16630
|
} else if (e.isFile()) {
|
|
16631
|
+
if (isGitIgnored(rel, false)) continue;
|
|
15842
16632
|
files.push({ full, name: e.name });
|
|
15843
16633
|
}
|
|
15844
16634
|
}
|
|
15845
16635
|
await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
|
|
15846
|
-
await mapWithConcurrency(
|
|
16636
|
+
await mapWithConcurrency(
|
|
16637
|
+
subdirs,
|
|
16638
|
+
Math.min(16, NATIVE_SCAN_CONCURRENCY),
|
|
16639
|
+
({ full, rel }) => walk(full, rel)
|
|
16640
|
+
);
|
|
15847
16641
|
};
|
|
15848
|
-
await walk(base);
|
|
16642
|
+
await walk(base, "");
|
|
15849
16643
|
return {
|
|
15850
16644
|
matches,
|
|
15851
16645
|
count: total,
|
|
@@ -15859,7 +16653,10 @@ init_spawn_stream();
|
|
|
15859
16653
|
init_util();
|
|
15860
16654
|
init_legacy_bridge();
|
|
15861
16655
|
import { join as join21 } from "node:path";
|
|
15862
|
-
import {
|
|
16656
|
+
import {
|
|
16657
|
+
detectEcosystem as detectPackageEcosystem,
|
|
16658
|
+
recordPackageAction
|
|
16659
|
+
} from "@wrongstack/core/coordination";
|
|
15863
16660
|
var installTool = {
|
|
15864
16661
|
name: "install",
|
|
15865
16662
|
category: "Package Management",
|
|
@@ -16045,7 +16842,7 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
16045
16842
|
// src/json.ts
|
|
16046
16843
|
init_util();
|
|
16047
16844
|
import * as fs22 from "node:fs/promises";
|
|
16048
|
-
import { deepMerge as deepMergeCore } from "@wrongstack/core";
|
|
16845
|
+
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
16049
16846
|
var jsonTool = {
|
|
16050
16847
|
name: "json",
|
|
16051
16848
|
category: "Data",
|
|
@@ -16423,56 +17220,56 @@ function jmespathSearch(data, query) {
|
|
|
16423
17220
|
}
|
|
16424
17221
|
function validateJsonSchema(data, schema) {
|
|
16425
17222
|
const errors = [];
|
|
16426
|
-
function check(value, s,
|
|
17223
|
+
function check(value, s, path34) {
|
|
16427
17224
|
if (s["type"]) {
|
|
16428
17225
|
const expectedType = s["type"];
|
|
16429
17226
|
const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
16430
17227
|
if (expectedType === "integer") {
|
|
16431
|
-
if (!Number.isInteger(value)) errors.push(`${
|
|
17228
|
+
if (!Number.isInteger(value)) errors.push(`${path34}: expected integer, got ${actualType}`);
|
|
16432
17229
|
} else if (expectedType !== actualType) {
|
|
16433
|
-
errors.push(`${
|
|
17230
|
+
errors.push(`${path34}: expected ${expectedType}, got ${actualType}`);
|
|
16434
17231
|
}
|
|
16435
17232
|
}
|
|
16436
17233
|
if (typeof value === "string" && s["format"] === "uri" && value) {
|
|
16437
17234
|
try {
|
|
16438
17235
|
new URL(value);
|
|
16439
17236
|
} catch {
|
|
16440
|
-
errors.push(`${
|
|
17237
|
+
errors.push(`${path34}: not a valid URI`);
|
|
16441
17238
|
}
|
|
16442
17239
|
}
|
|
16443
17240
|
if (typeof value === "string" && s["pattern"]) {
|
|
16444
17241
|
const re = new RegExp(s["pattern"]);
|
|
16445
|
-
if (!re.test(value)) errors.push(`${
|
|
17242
|
+
if (!re.test(value)) errors.push(`${path34}: does not match pattern ${s["pattern"]}`);
|
|
16446
17243
|
}
|
|
16447
17244
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
16448
|
-
errors.push(`${
|
|
17245
|
+
errors.push(`${path34}: string too short (min ${s["minLength"]})`);
|
|
16449
17246
|
}
|
|
16450
17247
|
if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
|
|
16451
|
-
errors.push(`${
|
|
17248
|
+
errors.push(`${path34}: string too long (max ${s["maxLength"]})`);
|
|
16452
17249
|
}
|
|
16453
17250
|
if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
|
|
16454
|
-
errors.push(`${
|
|
17251
|
+
errors.push(`${path34}: below minimum ${s["minimum"]}`);
|
|
16455
17252
|
}
|
|
16456
17253
|
if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
|
|
16457
|
-
errors.push(`${
|
|
17254
|
+
errors.push(`${path34}: above maximum ${s["maximum"]}`);
|
|
16458
17255
|
}
|
|
16459
17256
|
if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
|
|
16460
17257
|
for (let i = 0; i < value.length; i++) {
|
|
16461
|
-
check(value[i], s["items"], `${
|
|
17258
|
+
check(value[i], s["items"], `${path34}[${i}]`);
|
|
16462
17259
|
}
|
|
16463
17260
|
}
|
|
16464
17261
|
if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
|
|
16465
17262
|
const props = s["properties"];
|
|
16466
17263
|
for (const [k, propSchema] of Object.entries(props)) {
|
|
16467
|
-
check(value[k], propSchema, `${
|
|
17264
|
+
check(value[k], propSchema, `${path34}.${k}`);
|
|
16468
17265
|
}
|
|
16469
17266
|
}
|
|
16470
17267
|
}
|
|
16471
17268
|
check(data, schema, "$");
|
|
16472
17269
|
return { valid: errors.length === 0, errors };
|
|
16473
17270
|
}
|
|
16474
|
-
function simpleQuery(data,
|
|
16475
|
-
const parts =
|
|
17271
|
+
function simpleQuery(data, path34) {
|
|
17272
|
+
const parts = path34.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
16476
17273
|
let current = data;
|
|
16477
17274
|
for (const part of parts) {
|
|
16478
17275
|
if (current === null || current === void 0) return void 0;
|
|
@@ -16523,7 +17320,8 @@ function toYaml(data, indent = 0) {
|
|
|
16523
17320
|
|
|
16524
17321
|
// src/kanban.ts
|
|
16525
17322
|
import { randomUUID } from "node:crypto";
|
|
16526
|
-
import { deserializeTaskGraph as deserializeTaskGraph2,
|
|
17323
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
17324
|
+
import { loadTasks as loadTasks2 } from "@wrongstack/core/storage";
|
|
16527
17325
|
import {
|
|
16528
17326
|
addCheckToTask,
|
|
16529
17327
|
addColumn,
|
|
@@ -16570,18 +17368,45 @@ import {
|
|
|
16570
17368
|
updateColumn,
|
|
16571
17369
|
updateGoalMetricOnTask,
|
|
16572
17370
|
updateTask,
|
|
16573
|
-
updateTaskAssignment
|
|
17371
|
+
updateTaskAssignment,
|
|
17372
|
+
verifyTaskCompletion,
|
|
17373
|
+
finalizeTaskCompletion,
|
|
17374
|
+
assessTaskAtomicity,
|
|
17375
|
+
proposeTaskDecomposition
|
|
16574
17376
|
} from "@wrongstack/kanban";
|
|
16575
17377
|
|
|
17378
|
+
// src/kanban-evidence-bridge.ts
|
|
17379
|
+
import { recordCompletedWorkEvidence } from "@wrongstack/core/utils";
|
|
17380
|
+
function kanbanEvidenceKey(boardId, taskId) {
|
|
17381
|
+
return `kanban:${boardId}:${taskId}`;
|
|
17382
|
+
}
|
|
17383
|
+
function kanbanEvidencePointer(boardId, taskId) {
|
|
17384
|
+
return `kanban://${boardId}/${taskId}#verificationReport`;
|
|
17385
|
+
}
|
|
17386
|
+
function recordKanbanVerificationEvidence(ctx, report) {
|
|
17387
|
+
try {
|
|
17388
|
+
const passed = report.checks.filter((check) => check.status === "passed").length;
|
|
17389
|
+
const completedAt = Date.parse(report.completedAt);
|
|
17390
|
+
recordCompletedWorkEvidence(ctx, {
|
|
17391
|
+
key: kanbanEvidenceKey(report.boardId, report.taskId),
|
|
17392
|
+
source: "verification",
|
|
17393
|
+
summary: `${report.taskTitle} \u2014 verification ${report.verdict} (${passed}/${report.checks.length} checks)`,
|
|
17394
|
+
...Number.isFinite(completedAt) ? { completedAt } : {},
|
|
17395
|
+
evidence: kanbanEvidencePointer(report.boardId, report.taskId)
|
|
17396
|
+
});
|
|
17397
|
+
} catch {
|
|
17398
|
+
}
|
|
17399
|
+
}
|
|
17400
|
+
|
|
16576
17401
|
// src/session-kanban.ts
|
|
17402
|
+
import { GlobalMailbox } from "@wrongstack/core/coordination";
|
|
16577
17403
|
import {
|
|
16578
|
-
deserializeTaskGraph,
|
|
16579
|
-
GlobalMailbox,
|
|
16580
17404
|
loadPlan,
|
|
16581
17405
|
loadTasks,
|
|
16582
17406
|
mutatePlan,
|
|
16583
17407
|
mutateTasks
|
|
16584
|
-
} from "@wrongstack/core";
|
|
17408
|
+
} from "@wrongstack/core/storage";
|
|
17409
|
+
import { deserializeTaskGraph } from "@wrongstack/core/tasking";
|
|
16585
17410
|
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
16586
17411
|
import {
|
|
16587
17412
|
createBoard,
|
|
@@ -16802,8 +17627,8 @@ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
|
16802
17627
|
var kanbanTool = {
|
|
16803
17628
|
name: "kanban",
|
|
16804
17629
|
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.",
|
|
17630
|
+
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.",
|
|
17631
|
+
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
17632
|
permission: "confirm",
|
|
16808
17633
|
mutating: true,
|
|
16809
17634
|
capabilities: ["fs.write"],
|
|
@@ -16859,7 +17684,11 @@ var kanbanTool = {
|
|
|
16859
17684
|
"add_check",
|
|
16860
17685
|
"update_check",
|
|
16861
17686
|
"add_note",
|
|
16862
|
-
"add_link"
|
|
17687
|
+
"add_link",
|
|
17688
|
+
"verify_completion",
|
|
17689
|
+
"split_atomic",
|
|
17690
|
+
"assess_atomicity",
|
|
17691
|
+
"propose_decomposition"
|
|
16863
17692
|
]
|
|
16864
17693
|
},
|
|
16865
17694
|
boardId: { type: "string" },
|
|
@@ -16914,6 +17743,7 @@ var kanbanTool = {
|
|
|
16914
17743
|
runTaskId: { type: "string" },
|
|
16915
17744
|
lastResult: { type: "string" },
|
|
16916
17745
|
error: { type: "string" },
|
|
17746
|
+
expectedLeaseId: { type: "string" },
|
|
16917
17747
|
assignmentStatus: {
|
|
16918
17748
|
type: "string",
|
|
16919
17749
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
@@ -16948,6 +17778,7 @@ var kanbanTool = {
|
|
|
16948
17778
|
lastFailureKind: { type: "string" },
|
|
16949
17779
|
dependsOn: { type: "array", items: { type: "string" } },
|
|
16950
17780
|
estimatedHours: { type: "number" },
|
|
17781
|
+
actualHours: { type: "number" },
|
|
16951
17782
|
taskGraph: { type: "object" },
|
|
16952
17783
|
graphId: { type: "string" },
|
|
16953
17784
|
specId: { type: "string" },
|
|
@@ -16993,7 +17824,24 @@ var kanbanTool = {
|
|
|
16993
17824
|
includeCompletedTasks: { type: "boolean" },
|
|
16994
17825
|
preserveAssignment: { type: "boolean" },
|
|
16995
17826
|
preserveDependencies: { type: "boolean" },
|
|
16996
|
-
moveTasksToColumnId: { type: "string" }
|
|
17827
|
+
moveTasksToColumnId: { type: "string" },
|
|
17828
|
+
atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
|
|
17829
|
+
atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
|
|
17830
|
+
gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
|
|
17831
|
+
subtasks: {
|
|
17832
|
+
type: "array",
|
|
17833
|
+
minItems: 2,
|
|
17834
|
+
items: {
|
|
17835
|
+
type: "object",
|
|
17836
|
+
properties: {
|
|
17837
|
+
title: { type: "string" },
|
|
17838
|
+
description: { type: "string" },
|
|
17839
|
+
successCriteria: { type: "array", items: { type: "string" } },
|
|
17840
|
+
dependsOnIndex: { type: "array", items: { type: "number" } }
|
|
17841
|
+
},
|
|
17842
|
+
required: ["title"]
|
|
17843
|
+
}
|
|
17844
|
+
}
|
|
16997
17845
|
},
|
|
16998
17846
|
required: ["action"]
|
|
16999
17847
|
},
|
|
@@ -17033,7 +17881,14 @@ var kanbanTool = {
|
|
|
17033
17881
|
title: input.title,
|
|
17034
17882
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
17035
17883
|
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17036
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
17884
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
17885
|
+
...input.atomicityMode !== void 0 ? {
|
|
17886
|
+
atomicity: {
|
|
17887
|
+
mode: input.atomicityMode,
|
|
17888
|
+
decomposition: input.atomicityDecomposition ?? "propose"
|
|
17889
|
+
}
|
|
17890
|
+
} : {},
|
|
17891
|
+
...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
|
|
17037
17892
|
});
|
|
17038
17893
|
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
17039
17894
|
}
|
|
@@ -17042,7 +17897,14 @@ var kanbanTool = {
|
|
|
17042
17897
|
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
17043
17898
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
17044
17899
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
17045
|
-
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
17900
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17901
|
+
...input.atomicityMode !== void 0 ? {
|
|
17902
|
+
atomicity: {
|
|
17903
|
+
mode: input.atomicityMode,
|
|
17904
|
+
decomposition: input.atomicityDecomposition ?? "propose"
|
|
17905
|
+
}
|
|
17906
|
+
} : {},
|
|
17907
|
+
...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
|
|
17046
17908
|
});
|
|
17047
17909
|
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
17048
17910
|
}
|
|
@@ -17250,30 +18112,18 @@ var kanbanTool = {
|
|
|
17250
18112
|
case "add_task": {
|
|
17251
18113
|
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
17252
18114
|
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
17253
|
-
|
|
18115
|
+
if (!result2) return fail("Board not found.");
|
|
18116
|
+
return okTask(
|
|
18117
|
+
result2.board,
|
|
18118
|
+
result2.task,
|
|
18119
|
+
`Task added.${atomicityNudge(result2.task)}`
|
|
18120
|
+
);
|
|
17254
18121
|
}
|
|
17255
18122
|
case "split_task": {
|
|
17256
18123
|
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
17257
18124
|
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
17258
18125
|
}
|
|
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.");
|
|
18126
|
+
return handleSplitTask(projectRoot, input, {});
|
|
17277
18127
|
}
|
|
17278
18128
|
case "merge_tasks": {
|
|
17279
18129
|
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
@@ -17347,6 +18197,19 @@ var kanbanTool = {
|
|
|
17347
18197
|
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
17348
18198
|
);
|
|
17349
18199
|
}
|
|
18200
|
+
if (input.lifecycleStage === "done") {
|
|
18201
|
+
const boardBefore = await getBoard2(projectRoot, input.boardId);
|
|
18202
|
+
const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
|
|
18203
|
+
if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
|
|
18204
|
+
const preGate = await verifyTaskCompletion(projectRoot, input.boardId, taskBefore.id, {
|
|
18205
|
+
persist: false
|
|
18206
|
+
});
|
|
18207
|
+
await updateTask(projectRoot, input.boardId, taskBefore.id, {
|
|
18208
|
+
verificationReport: preGate.report,
|
|
18209
|
+
successCriteria: preGate.task.successCriteria
|
|
18210
|
+
});
|
|
18211
|
+
}
|
|
18212
|
+
}
|
|
17350
18213
|
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
17351
18214
|
to: input.lifecycleStage,
|
|
17352
18215
|
actor: input.author,
|
|
@@ -17361,6 +18224,9 @@ var kanbanTool = {
|
|
|
17361
18224
|
} : {},
|
|
17362
18225
|
patch: taskPatch(input)
|
|
17363
18226
|
});
|
|
18227
|
+
if (result2 && input.lifecycleStage === "done" && result2.task.verificationReport) {
|
|
18228
|
+
recordKanbanVerificationEvidence(ctx, result2.task.verificationReport);
|
|
18229
|
+
}
|
|
17364
18230
|
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
17365
18231
|
}
|
|
17366
18232
|
case "move_task": {
|
|
@@ -17474,7 +18340,31 @@ var kanbanTool = {
|
|
|
17474
18340
|
// is atomic inside updateTaskAssignment's mutateBoard lock.
|
|
17475
18341
|
input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
|
|
17476
18342
|
);
|
|
17477
|
-
|
|
18343
|
+
if (!board) return fail("Task not found.");
|
|
18344
|
+
if (assignmentStatus === "completed") {
|
|
18345
|
+
const envGate = readEnvGateEnforcement();
|
|
18346
|
+
const finalized = await finalizeTaskCompletion(projectRoot, board.id, input.taskId, {
|
|
18347
|
+
...board.completionGate === void 0 && envGate !== void 0 ? { enforcement: envGate } : {},
|
|
18348
|
+
...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
|
|
18349
|
+
});
|
|
18350
|
+
if (finalized) {
|
|
18351
|
+
if (finalized.gate.report) {
|
|
18352
|
+
recordKanbanVerificationEvidence(ctx, finalized.gate.report);
|
|
18353
|
+
}
|
|
18354
|
+
const gateSummary = {
|
|
18355
|
+
enforcement: finalized.gate.enforcement,
|
|
18356
|
+
allowed: finalized.gate.allowed,
|
|
18357
|
+
verdict: finalized.gate.verdict,
|
|
18358
|
+
issues: finalized.gate.issues.map((issue) => issue.message)
|
|
18359
|
+
};
|
|
18360
|
+
const gateMessage = finalized.gate.allowed ? `Completion gate ${finalized.gate.verdict === "skipped" ? "skipped" : "passed"}; task completed.` : finalized.gate.enforcement === "strict" ? `Completion gate BLOCKED (verdict: ${finalized.gate.verdict}); task parked in review. Issues: ${gateSummary.issues.join(" | ")}` : `Completion gate failed softly (verdict: ${finalized.gate.verdict}); task completed with warnings. Issues: ${gateSummary.issues.join(" | ")}`;
|
|
18361
|
+
return {
|
|
18362
|
+
...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
|
|
18363
|
+
gate: gateSummary
|
|
18364
|
+
};
|
|
18365
|
+
}
|
|
18366
|
+
}
|
|
18367
|
+
return okBoard(board, "Assignment updated.");
|
|
17478
18368
|
}
|
|
17479
18369
|
case "heartbeat_assignment": {
|
|
17480
18370
|
if (!input.boardId || !input.taskId) {
|
|
@@ -17638,6 +18528,83 @@ var kanbanTool = {
|
|
|
17638
18528
|
});
|
|
17639
18529
|
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
17640
18530
|
}
|
|
18531
|
+
case "assess_atomicity": {
|
|
18532
|
+
if (!input.boardId || !input.taskId) {
|
|
18533
|
+
return fail("assess_atomicity requires boardId and taskId.");
|
|
18534
|
+
}
|
|
18535
|
+
const result2 = await assessTaskAtomicity(projectRoot, input.boardId, input.taskId, {
|
|
18536
|
+
assessedBy: "agent",
|
|
18537
|
+
...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
|
|
18538
|
+
});
|
|
18539
|
+
if (!result2) return fail("Task not found.");
|
|
18540
|
+
const failing = result2.assessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason);
|
|
18541
|
+
const guidance = result2.assessment.verdict === "needs_decomposition" ? ` This task should be split before dispatch \u2014 call propose_decomposition with 2+ subtasks (each with one verifiable success criterion). Reasons: ${failing.join(" | ")}` : result2.assessment.verdict === "composite" ? " Container task: work happens in its children; it is verified via subtask aggregation." : "";
|
|
18542
|
+
return okTask(
|
|
18543
|
+
result2.board,
|
|
18544
|
+
result2.task,
|
|
18545
|
+
`Atomicity verdict: ${result2.assessment.verdict} (score ${result2.assessment.score}).${guidance}`
|
|
18546
|
+
);
|
|
18547
|
+
}
|
|
18548
|
+
case "propose_decomposition": {
|
|
18549
|
+
if (!input.boardId || !input.taskId || !input.subtasks?.length) {
|
|
18550
|
+
return fail("propose_decomposition requires boardId, taskId, and subtasks (2+).");
|
|
18551
|
+
}
|
|
18552
|
+
if (input.subtasks.length < 2) {
|
|
18553
|
+
return fail("propose_decomposition requires at least two subtasks.");
|
|
18554
|
+
}
|
|
18555
|
+
const invalid = input.subtasks.find(
|
|
18556
|
+
(subtask) => typeof subtask?.title !== "string" || !subtask.title.trim()
|
|
18557
|
+
);
|
|
18558
|
+
if (invalid) return fail("Every proposed subtask needs a non-blank title.");
|
|
18559
|
+
const result2 = await proposeTaskDecomposition(
|
|
18560
|
+
projectRoot,
|
|
18561
|
+
input.boardId,
|
|
18562
|
+
input.taskId,
|
|
18563
|
+
{
|
|
18564
|
+
subtasks: input.subtasks,
|
|
18565
|
+
...input.note !== void 0 ? { rationale: input.note } : {},
|
|
18566
|
+
...ctx.agentId !== void 0 ? { proposedBy: ctx.agentId } : {}
|
|
18567
|
+
},
|
|
18568
|
+
ctx.agentId !== void 0 ? { actor: ctx.agentId } : {}
|
|
18569
|
+
);
|
|
18570
|
+
if (!result2) return fail("Task not found.");
|
|
18571
|
+
const message = result2.proposal.status === "applied" ? `Decomposition applied: ${result2.proposal.appliedChildTaskIds?.length ?? 0} child tasks created (parent marked atomic).` : 'Decomposition proposal recorded \u2014 awaiting approval (board policy is "propose"). It can be approved from the WebUI or via update_task.';
|
|
18572
|
+
return okTask(result2.board, result2.task, message);
|
|
18573
|
+
}
|
|
18574
|
+
case "verify_completion": {
|
|
18575
|
+
if (!input.boardId || !input.taskId) {
|
|
18576
|
+
return fail("verify_completion requires boardId and taskId.");
|
|
18577
|
+
}
|
|
18578
|
+
const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
|
|
18579
|
+
const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
|
|
18580
|
+
verificationReport: verResult.report,
|
|
18581
|
+
successCriteria: verResult.task.successCriteria
|
|
18582
|
+
});
|
|
18583
|
+
if (!persistedBoard) {
|
|
18584
|
+
return {
|
|
18585
|
+
ok: false,
|
|
18586
|
+
verdict: verResult.report.verdict,
|
|
18587
|
+
message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
|
|
18588
|
+
board: verResult.board
|
|
18589
|
+
};
|
|
18590
|
+
}
|
|
18591
|
+
recordKanbanVerificationEvidence(ctx, verResult.report);
|
|
18592
|
+
const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
|
|
18593
|
+
const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
|
|
18594
|
+
return {
|
|
18595
|
+
ok: deterministicVerdicts.includes(verResult.report.verdict),
|
|
18596
|
+
verdict: verResult.report.verdict,
|
|
18597
|
+
message: verResult.report.markdownSummary,
|
|
18598
|
+
board: persistedBoard,
|
|
18599
|
+
task: freshTask ?? verResult.task
|
|
18600
|
+
};
|
|
18601
|
+
}
|
|
18602
|
+
case "split_atomic": {
|
|
18603
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
18604
|
+
return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
|
|
18605
|
+
}
|
|
18606
|
+
return handleSplitTask(projectRoot, input, { atomic: true });
|
|
18607
|
+
}
|
|
17641
18608
|
default:
|
|
17642
18609
|
return fail(`Unknown kanban action: ${input.action}`);
|
|
17643
18610
|
}
|
|
@@ -17648,6 +18615,15 @@ var kanbanTool = {
|
|
|
17648
18615
|
}
|
|
17649
18616
|
}
|
|
17650
18617
|
};
|
|
18618
|
+
function atomicityNudge(task) {
|
|
18619
|
+
if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
|
|
18620
|
+
const reasons = task.atomicityAssessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason).join(" | ");
|
|
18621
|
+
return ` Atomicity: needs_decomposition (score ${task.atomicityAssessment.score}) \u2014 call propose_decomposition with 2+ subtasks before dispatch. Reasons: ${reasons}`;
|
|
18622
|
+
}
|
|
18623
|
+
function readEnvGateEnforcement() {
|
|
18624
|
+
const raw = process.env["WRONGSTACK_KANBAN_GATE"]?.trim().toLowerCase();
|
|
18625
|
+
return raw === "strict" || raw === "soft" || raw === "off" ? raw : void 0;
|
|
18626
|
+
}
|
|
17651
18627
|
function fail(message) {
|
|
17652
18628
|
return { ok: false, message };
|
|
17653
18629
|
}
|
|
@@ -17657,6 +18633,50 @@ function okBoard(board, message = "Board loaded.") {
|
|
|
17657
18633
|
function okTask(board, task, message) {
|
|
17658
18634
|
return { ok: true, message, board, task };
|
|
17659
18635
|
}
|
|
18636
|
+
async function handleSplitTask(projectRoot, input, extraSplitOptions) {
|
|
18637
|
+
const boardId = input.boardId;
|
|
18638
|
+
const taskId = input.taskId;
|
|
18639
|
+
const childTitles = input.childTitles;
|
|
18640
|
+
if (!boardId || !taskId || !childTitles?.length) {
|
|
18641
|
+
return fail("split requires boardId, taskId, and at least one childTitles.");
|
|
18642
|
+
}
|
|
18643
|
+
const {
|
|
18644
|
+
targetColumnId,
|
|
18645
|
+
inheritAssignment,
|
|
18646
|
+
inheritLabels,
|
|
18647
|
+
inheritSuccessCriteria,
|
|
18648
|
+
inheritGoalMetrics,
|
|
18649
|
+
inheritDependencies,
|
|
18650
|
+
chainChildren,
|
|
18651
|
+
rewireDependents
|
|
18652
|
+
} = input;
|
|
18653
|
+
const result = await splitTask(projectRoot, boardId, taskId, {
|
|
18654
|
+
titles: childTitles,
|
|
18655
|
+
...extraSplitOptions,
|
|
18656
|
+
...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
|
|
18657
|
+
...inheritAssignment !== void 0 ? { inheritAssignment } : {},
|
|
18658
|
+
...inheritLabels !== void 0 ? { inheritLabels } : {},
|
|
18659
|
+
...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
|
|
18660
|
+
...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
|
|
18661
|
+
...inheritDependencies !== void 0 ? { inheritDependencies } : {},
|
|
18662
|
+
...chainChildren !== void 0 ? { chainChildren } : {},
|
|
18663
|
+
...rewireDependents !== void 0 ? { rewireDependents } : {}
|
|
18664
|
+
});
|
|
18665
|
+
if (!result) return fail("Task not found.");
|
|
18666
|
+
const freshParent = result.board.tasks?.find((t) => t.id === taskId);
|
|
18667
|
+
if (!freshParent) {
|
|
18668
|
+
return fail(
|
|
18669
|
+
`Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
|
|
18670
|
+
);
|
|
18671
|
+
}
|
|
18672
|
+
return {
|
|
18673
|
+
ok: true,
|
|
18674
|
+
message: `${result.children.length} child task(s) created.`,
|
|
18675
|
+
board: result.board,
|
|
18676
|
+
task: freshParent,
|
|
18677
|
+
children: result.children
|
|
18678
|
+
};
|
|
18679
|
+
}
|
|
17660
18680
|
async function requireBoard(projectRoot, boardId) {
|
|
17661
18681
|
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
17662
18682
|
}
|
|
@@ -17758,7 +18778,8 @@ function taskPatch(input) {
|
|
|
17758
18778
|
labels: input.labels,
|
|
17759
18779
|
assignedAgent: input.agentId,
|
|
17760
18780
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
17761
|
-
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
18781
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
18782
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
|
|
17762
18783
|
};
|
|
17763
18784
|
}
|
|
17764
18785
|
function assignmentInput(input) {
|
|
@@ -17940,7 +18961,7 @@ async function detectLinter(cwd) {
|
|
|
17940
18961
|
|
|
17941
18962
|
// src/logs.ts
|
|
17942
18963
|
import { spawn as spawn11 } from "node:child_process";
|
|
17943
|
-
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core";
|
|
18964
|
+
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core/utils";
|
|
17944
18965
|
init_util();
|
|
17945
18966
|
var logsTool = {
|
|
17946
18967
|
name: "logs",
|
|
@@ -18079,7 +19100,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
18079
19100
|
}
|
|
18080
19101
|
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
18081
19102
|
var MAX_TAIL_LINES = 1e5;
|
|
18082
|
-
async function fileLogs(
|
|
19103
|
+
async function fileLogs(path34, lines, filterRe, stream) {
|
|
18083
19104
|
const { createInterface } = await import("node:readline");
|
|
18084
19105
|
const { createReadStream: createReadStream2 } = await import("node:fs");
|
|
18085
19106
|
const entries = [];
|
|
@@ -18088,7 +19109,7 @@ async function fileLogs(path33, lines, filterRe, stream) {
|
|
|
18088
19109
|
let writeIdx = 0;
|
|
18089
19110
|
let totalLines = 0;
|
|
18090
19111
|
const rl = createInterface({
|
|
18091
|
-
input: createReadStream2(
|
|
19112
|
+
input: createReadStream2(path34),
|
|
18092
19113
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
18093
19114
|
});
|
|
18094
19115
|
for await (const line of rl) {
|
|
@@ -18109,7 +19130,7 @@ async function fileLogs(path33, lines, filterRe, stream) {
|
|
|
18109
19130
|
if (parsed) entries.push(parsed);
|
|
18110
19131
|
}
|
|
18111
19132
|
return {
|
|
18112
|
-
source:
|
|
19133
|
+
source: path34,
|
|
18113
19134
|
entries,
|
|
18114
19135
|
total: entries.length,
|
|
18115
19136
|
truncated: totalLines > effLines,
|
|
@@ -18156,7 +19177,7 @@ function parseLine(line) {
|
|
|
18156
19177
|
init_util();
|
|
18157
19178
|
init_win32_resolve();
|
|
18158
19179
|
import { spawn as spawn12 } from "node:child_process";
|
|
18159
|
-
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core";
|
|
19180
|
+
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core/utils";
|
|
18160
19181
|
var outdatedTool = {
|
|
18161
19182
|
name: "outdated",
|
|
18162
19183
|
category: "Package Management",
|
|
@@ -18341,8 +19362,8 @@ init_util();
|
|
|
18341
19362
|
import { spawn as spawn13 } from "node:child_process";
|
|
18342
19363
|
import * as fs23 from "node:fs/promises";
|
|
18343
19364
|
import * as os8 from "node:os";
|
|
18344
|
-
import * as
|
|
18345
|
-
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core";
|
|
19365
|
+
import * as path28 from "node:path";
|
|
19366
|
+
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core/utils";
|
|
18346
19367
|
var patchTool = {
|
|
18347
19368
|
name: "patch",
|
|
18348
19369
|
category: "Filesystem",
|
|
@@ -18377,9 +19398,9 @@ var patchTool = {
|
|
|
18377
19398
|
for (const t of targets) {
|
|
18378
19399
|
const stripped = stripPathComponents(t, strip);
|
|
18379
19400
|
if (!stripped) continue;
|
|
18380
|
-
const candidate =
|
|
18381
|
-
const rel =
|
|
18382
|
-
if (rel.startsWith("..") ||
|
|
19401
|
+
const candidate = path28.resolve(dir, stripped);
|
|
19402
|
+
const rel = path28.relative(ctx.projectRoot, candidate);
|
|
19403
|
+
if (rel.startsWith("..") || path28.isAbsolute(rel)) {
|
|
18383
19404
|
return {
|
|
18384
19405
|
applied: 0,
|
|
18385
19406
|
rejected: 1,
|
|
@@ -18396,11 +19417,11 @@ var patchTool = {
|
|
|
18396
19417
|
beforeContents.set(target, await readTextForTracking(target));
|
|
18397
19418
|
}
|
|
18398
19419
|
}
|
|
18399
|
-
const tmpDir = await fs23.mkdtemp(
|
|
19420
|
+
const tmpDir = await fs23.mkdtemp(path28.join(os8.tmpdir(), ".wstack_patch_"));
|
|
18400
19421
|
try {
|
|
18401
19422
|
await fs23.chmod(tmpDir, 448).catch(() => {
|
|
18402
19423
|
});
|
|
18403
|
-
const patchFile =
|
|
19424
|
+
const patchFile = path28.join(tmpDir, "in.diff");
|
|
18404
19425
|
await fs23.writeFile(patchFile, input.patch, { mode: 384 });
|
|
18405
19426
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
18406
19427
|
const result = await runPatch(args, dir, opts.signal);
|
|
@@ -18506,11 +19527,11 @@ import {
|
|
|
18506
19527
|
mutatePlan as mutatePlan2,
|
|
18507
19528
|
removePlanItem,
|
|
18508
19529
|
setPlanItemStatus
|
|
18509
|
-
} from "@wrongstack/core";
|
|
19530
|
+
} from "@wrongstack/core/storage";
|
|
18510
19531
|
import {
|
|
18511
|
-
mutateTasks as mutateTasks2
|
|
18512
|
-
|
|
18513
|
-
} from "@wrongstack/core";
|
|
19532
|
+
mutateTasks as mutateTasks2
|
|
19533
|
+
} from "@wrongstack/core/storage";
|
|
19534
|
+
import { formatTaskList } from "@wrongstack/core/utils";
|
|
18514
19535
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
18515
19536
|
var planTool = {
|
|
18516
19537
|
name: "plan",
|
|
@@ -18777,7 +19798,8 @@ function mkResult(plan, ok, message, todos) {
|
|
|
18777
19798
|
// src/read.ts
|
|
18778
19799
|
init_util();
|
|
18779
19800
|
import * as fs24 from "node:fs/promises";
|
|
18780
|
-
import { FsError,
|
|
19801
|
+
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
19802
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
18781
19803
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
18782
19804
|
var readTool = {
|
|
18783
19805
|
name: "read",
|
|
@@ -18983,20 +20005,20 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
18983
20005
|
}
|
|
18984
20006
|
|
|
18985
20007
|
// src/replace.ts
|
|
18986
|
-
import { expectDefined as expectDefined8 } from "@wrongstack/core";
|
|
20008
|
+
import { expectDefined as expectDefined8 } from "@wrongstack/core/utils";
|
|
18987
20009
|
import { spawn as spawn14 } from "node:child_process";
|
|
18988
20010
|
import * as fs25 from "node:fs/promises";
|
|
18989
|
-
import * as
|
|
20011
|
+
import * as path29 from "node:path";
|
|
18990
20012
|
import {
|
|
18991
20013
|
atomicWrite as atomicWrite3,
|
|
18992
20014
|
buildChildEnv as buildChildEnv9,
|
|
18993
|
-
compileGlob as
|
|
20015
|
+
compileGlob as compileGlob4,
|
|
18994
20016
|
detectNewlineStyle as detectNewlineStyle2,
|
|
18995
20017
|
normalizeToLf as normalizeToLf2,
|
|
18996
20018
|
toStyle as toStyle2,
|
|
18997
|
-
ToolValidationError as ToolValidationError6,
|
|
18998
20019
|
unifiedDiff as unifiedDiff2
|
|
18999
|
-
} from "@wrongstack/core";
|
|
20020
|
+
} from "@wrongstack/core/utils";
|
|
20021
|
+
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
19000
20022
|
init_util();
|
|
19001
20023
|
var DEFAULT_IGNORE4 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
19002
20024
|
var replaceTool = {
|
|
@@ -19055,7 +20077,7 @@ var replaceTool = {
|
|
|
19055
20077
|
});
|
|
19056
20078
|
}
|
|
19057
20079
|
const re = compiled.regex;
|
|
19058
|
-
const globRe = input.glob ?
|
|
20080
|
+
const globRe = input.glob ? compileGlob4(input.glob) : null;
|
|
19059
20081
|
const dryRun = input.dry_run ?? true;
|
|
19060
20082
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
19061
20083
|
const fileList = await resolveFiles2(filesInput, ctx, globRe);
|
|
@@ -19075,8 +20097,8 @@ var replaceTool = {
|
|
|
19075
20097
|
} catch {
|
|
19076
20098
|
continue;
|
|
19077
20099
|
}
|
|
19078
|
-
const rel =
|
|
19079
|
-
if (rel.startsWith("..") ||
|
|
20100
|
+
const rel = path29.relative(realRoot, realPath);
|
|
20101
|
+
if (rel.startsWith("..") || path29.isAbsolute(rel)) continue;
|
|
19080
20102
|
const stat17 = await fs25.stat(realPath).catch(() => null);
|
|
19081
20103
|
if (!stat17?.isFile()) continue;
|
|
19082
20104
|
let content;
|
|
@@ -19195,7 +20217,7 @@ function spawnRgFind(pattern, base) {
|
|
|
19195
20217
|
}
|
|
19196
20218
|
async function globNative(pattern, base, extraGlob) {
|
|
19197
20219
|
const results = [];
|
|
19198
|
-
const globRe =
|
|
20220
|
+
const globRe = compileGlob4(pattern);
|
|
19199
20221
|
const walk = async (dir) => {
|
|
19200
20222
|
let entries;
|
|
19201
20223
|
try {
|
|
@@ -19205,7 +20227,7 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19205
20227
|
}
|
|
19206
20228
|
for (const e of entries) {
|
|
19207
20229
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
19208
|
-
const full =
|
|
20230
|
+
const full = path29.join(dir, e.name);
|
|
19209
20231
|
try {
|
|
19210
20232
|
const stat17 = await fs25.lstat(full);
|
|
19211
20233
|
if (stat17.isSymbolicLink()) continue;
|
|
@@ -19232,8 +20254,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19232
20254
|
// src/scaffold.ts
|
|
19233
20255
|
init_util();
|
|
19234
20256
|
import * as fs26 from "node:fs/promises";
|
|
19235
|
-
import * as
|
|
19236
|
-
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
20257
|
+
import * as path30 from "node:path";
|
|
20258
|
+
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
19237
20259
|
var BUILT_IN_TEMPLATES = {
|
|
19238
20260
|
"npm-package": {
|
|
19239
20261
|
description: "Basic npm package with ESM",
|
|
@@ -19382,16 +20404,16 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
19382
20404
|
let filesCreated = 0;
|
|
19383
20405
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
19384
20406
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
19385
|
-
const joinedPath =
|
|
19386
|
-
const root =
|
|
19387
|
-
const target =
|
|
19388
|
-
const rel =
|
|
19389
|
-
if (rel.startsWith("..") ||
|
|
20407
|
+
const joinedPath = path30.join(cwd, resolvedPath);
|
|
20408
|
+
const root = path30.resolve(ctx.projectRoot);
|
|
20409
|
+
const target = path30.resolve(joinedPath);
|
|
20410
|
+
const rel = path30.relative(root, target);
|
|
20411
|
+
if (rel.startsWith("..") || path30.isAbsolute(rel)) {
|
|
19390
20412
|
throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
|
|
19391
20413
|
}
|
|
19392
20414
|
const fullPath = target;
|
|
19393
20415
|
if (!dryRun) {
|
|
19394
|
-
await fs26.mkdir(
|
|
20416
|
+
await fs26.mkdir(path30.dirname(fullPath), { recursive: true });
|
|
19395
20417
|
await atomicWrite4(fullPath, substituteVars(content, name, vars));
|
|
19396
20418
|
}
|
|
19397
20419
|
files.push(resolvedPath);
|
|
@@ -19420,7 +20442,8 @@ function substituteVars(content, name, vars) {
|
|
|
19420
20442
|
}
|
|
19421
20443
|
|
|
19422
20444
|
// src/search.ts
|
|
19423
|
-
import {
|
|
20445
|
+
import { FetchError as FetchError3, ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
20446
|
+
import { expectDefined as expectDefined9 } from "@wrongstack/core/utils";
|
|
19424
20447
|
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
19425
20448
|
var DEFAULT_NUM = 10;
|
|
19426
20449
|
var MAX_RESULTS = 50;
|
|
@@ -19870,15 +20893,13 @@ var setWorkingDirTool = {
|
|
|
19870
20893
|
import {
|
|
19871
20894
|
computeTaskItemProgress,
|
|
19872
20895
|
formatTaskList as formatTaskList2
|
|
19873
|
-
} from "@wrongstack/core";
|
|
19874
|
-
import {
|
|
19875
|
-
mutateTasks as mutateTasks3
|
|
19876
|
-
} from "@wrongstack/core";
|
|
20896
|
+
} from "@wrongstack/core/utils";
|
|
20897
|
+
import { mutateTasks as mutateTasks3 } from "@wrongstack/core/storage";
|
|
19877
20898
|
import {
|
|
19878
20899
|
addPlanItem as addPlanItem2,
|
|
19879
20900
|
mutatePlan as mutatePlan3,
|
|
19880
20901
|
formatPlan as formatPlan2
|
|
19881
|
-
} from "@wrongstack/core";
|
|
20902
|
+
} from "@wrongstack/core/storage";
|
|
19882
20903
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
19883
20904
|
function findTaskIndex(tasks, query) {
|
|
19884
20905
|
const asNum = Number.parseInt(query, 10);
|
|
@@ -20248,7 +21269,7 @@ ${formatTaskList2(file.tasks)}` : file.tasks.length > 0 ? formatTaskList2(file.t
|
|
|
20248
21269
|
init_spawn_stream();
|
|
20249
21270
|
init_util();
|
|
20250
21271
|
init_legacy_bridge();
|
|
20251
|
-
import * as
|
|
21272
|
+
import * as path31 from "node:path";
|
|
20252
21273
|
var testTool = {
|
|
20253
21274
|
name: "test",
|
|
20254
21275
|
category: "Code Quality",
|
|
@@ -20355,7 +21376,7 @@ async function detectRunner(cwd) {
|
|
|
20355
21376
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
20356
21377
|
for (const f of candidates) {
|
|
20357
21378
|
try {
|
|
20358
|
-
await stat17(
|
|
21379
|
+
await stat17(path31.join(cwd, f));
|
|
20359
21380
|
if (f.includes("vitest")) return "vitest";
|
|
20360
21381
|
if (f.includes("jest")) return "jest";
|
|
20361
21382
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -20432,7 +21453,7 @@ function parseResult(runner, result, duration) {
|
|
|
20432
21453
|
}
|
|
20433
21454
|
|
|
20434
21455
|
// src/todo.ts
|
|
20435
|
-
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
21456
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core/storage";
|
|
20436
21457
|
var todoTool = {
|
|
20437
21458
|
name: "todo",
|
|
20438
21459
|
category: "Session",
|
|
@@ -20824,22 +21845,10 @@ var toolUseTool = {
|
|
|
20824
21845
|
|
|
20825
21846
|
// src/tree.ts
|
|
20826
21847
|
init_util();
|
|
20827
|
-
import { expectDefined as expectDefined10 } from "@wrongstack/core";
|
|
21848
|
+
import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
20828
21849
|
import * as fs28 from "node:fs/promises";
|
|
20829
|
-
import * as
|
|
20830
|
-
var DEFAULT_IGNORE5 = [
|
|
20831
|
-
"node_modules",
|
|
20832
|
-
".git",
|
|
20833
|
-
"dist",
|
|
20834
|
-
"build",
|
|
20835
|
-
".next",
|
|
20836
|
-
"coverage",
|
|
20837
|
-
"__pycache__",
|
|
20838
|
-
".wrongstack",
|
|
20839
|
-
".ssh",
|
|
20840
|
-
".gnupg",
|
|
20841
|
-
".aws"
|
|
20842
|
-
];
|
|
21850
|
+
import * as path32 from "node:path";
|
|
21851
|
+
var DEFAULT_IGNORE5 = [...DEFAULT_WALK_IGNORE_DIRS4, ".wrongstack", ".ssh", ".gnupg", ".aws"];
|
|
20843
21852
|
var treeTool = {
|
|
20844
21853
|
name: "tree",
|
|
20845
21854
|
category: "Filesystem",
|
|
@@ -20997,7 +22006,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
20997
22006
|
opts.lines.push(opts.prefix + branch + displayName);
|
|
20998
22007
|
if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
|
|
20999
22008
|
const childPrefix = opts.prefix + connector;
|
|
21000
|
-
await walkDir(
|
|
22009
|
+
await walkDir(path32.join(dir, entry.name), depth + 1, {
|
|
21001
22010
|
...opts,
|
|
21002
22011
|
prefix: childPrefix,
|
|
21003
22012
|
isLast
|
|
@@ -21010,7 +22019,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
21010
22019
|
init_spawn_stream();
|
|
21011
22020
|
init_util();
|
|
21012
22021
|
init_legacy_bridge();
|
|
21013
|
-
import * as
|
|
22022
|
+
import * as path33 from "node:path";
|
|
21014
22023
|
var typecheckTool = {
|
|
21015
22024
|
name: "typecheck",
|
|
21016
22025
|
category: "Code Quality",
|
|
@@ -21113,8 +22122,8 @@ async function findTsConfig(cwd) {
|
|
|
21113
22122
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
21114
22123
|
for (const f of candidates) {
|
|
21115
22124
|
try {
|
|
21116
|
-
const s = await stat17(
|
|
21117
|
-
if (s.isFile()) return
|
|
22125
|
+
const s = await stat17(path33.join(cwd, f));
|
|
22126
|
+
if (s.isFile()) return path33.join(cwd, f);
|
|
21118
22127
|
} catch {
|
|
21119
22128
|
}
|
|
21120
22129
|
}
|
|
@@ -21123,7 +22132,8 @@ async function findTsConfig(cwd) {
|
|
|
21123
22132
|
|
|
21124
22133
|
// src/write.ts
|
|
21125
22134
|
import * as fs29 from "node:fs/promises";
|
|
21126
|
-
import {
|
|
22135
|
+
import { ToolValidationError as ToolValidationError8 } from "@wrongstack/core/types";
|
|
22136
|
+
import { atomicWrite as atomicWrite5, unifiedDiff as unifiedDiff3 } from "@wrongstack/core/utils";
|
|
21127
22137
|
init_util();
|
|
21128
22138
|
var writeTool = {
|
|
21129
22139
|
name: "write",
|