@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/index.js
CHANGED
|
@@ -84,7 +84,7 @@ var init_win32_resolve = __esm({
|
|
|
84
84
|
import { createWriteStream, mkdirSync } from "node:fs";
|
|
85
85
|
import * as fsp from "node:fs/promises";
|
|
86
86
|
import * as path2 from "node:path";
|
|
87
|
-
import { wstackGlobalRoot } from "@wrongstack/core";
|
|
87
|
+
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
88
88
|
function toolOutputDir() {
|
|
89
89
|
return path2.join(wstackGlobalRoot(), "tool-output");
|
|
90
90
|
}
|
|
@@ -392,7 +392,7 @@ var init_circuit_breaker = __esm({
|
|
|
392
392
|
});
|
|
393
393
|
|
|
394
394
|
// src/_redact-command.ts
|
|
395
|
-
import { expectDefined } from "@wrongstack/core";
|
|
395
|
+
import { expectDefined } from "@wrongstack/core/utils";
|
|
396
396
|
function redactCommand(cmd) {
|
|
397
397
|
let result = cmd;
|
|
398
398
|
for (const pattern of SENSITIVE_FLAG_PATTERNS) {
|
|
@@ -509,7 +509,12 @@ var init_process_registry = __esm({
|
|
|
509
509
|
this.breaker.setEnabled(false);
|
|
510
510
|
}
|
|
511
511
|
register(info) {
|
|
512
|
-
this.processes.set(info.pid, {
|
|
512
|
+
this.processes.set(info.pid, {
|
|
513
|
+
...info,
|
|
514
|
+
killed: false,
|
|
515
|
+
protected: info.protected ?? false,
|
|
516
|
+
background: info.background ?? false
|
|
517
|
+
});
|
|
513
518
|
}
|
|
514
519
|
_isSafeSignalPid(pid) {
|
|
515
520
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
@@ -562,12 +567,21 @@ var init_process_registry = __esm({
|
|
|
562
567
|
}
|
|
563
568
|
return n;
|
|
564
569
|
}
|
|
570
|
+
/** Count of active jobs explicitly launched in background mode. */
|
|
571
|
+
get activeBackgroundCount() {
|
|
572
|
+
let n = 0;
|
|
573
|
+
for (const p of this.processes.values()) {
|
|
574
|
+
if (p.background && !p.killed) n++;
|
|
575
|
+
}
|
|
576
|
+
return n;
|
|
577
|
+
}
|
|
565
578
|
/**
|
|
566
579
|
* Combined stats for observability — used by /ps and the TUI status bar.
|
|
567
580
|
*/
|
|
568
581
|
stats() {
|
|
569
582
|
return {
|
|
570
583
|
activeCount: this.activeCount,
|
|
584
|
+
backgroundCount: this.activeBackgroundCount,
|
|
571
585
|
totalCount: this.processes.size,
|
|
572
586
|
breaker: this.breaker.snapshot()
|
|
573
587
|
};
|
|
@@ -668,7 +682,7 @@ var init_process_registry = __esm({
|
|
|
668
682
|
this.autoKillTimer = setTimeout(() => {
|
|
669
683
|
this.autoKillTimer = null;
|
|
670
684
|
this.autoKillArmedAt = null;
|
|
671
|
-
this.killAll({ force: false });
|
|
685
|
+
this.killAll({ force: false, preserveBackground: true });
|
|
672
686
|
this.breaker.forceReset();
|
|
673
687
|
this._emitBreakerCountdown();
|
|
674
688
|
}, this.autoKillResetMs);
|
|
@@ -708,6 +722,7 @@ var init_process_registry = __esm({
|
|
|
708
722
|
if (!p) return false;
|
|
709
723
|
if (p.killed) return true;
|
|
710
724
|
if (p.protected) return false;
|
|
725
|
+
if (opts.preserveBackground && p.background) return false;
|
|
711
726
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
712
727
|
const isWin5 = os.platform() === "win32";
|
|
713
728
|
if (isWin5) {
|
|
@@ -810,11 +825,11 @@ var init_process_registry = __esm({
|
|
|
810
825
|
// src/_spawn-stream.ts
|
|
811
826
|
import { spawn as spawn2 } from "node:child_process";
|
|
812
827
|
import {
|
|
813
|
-
buildChildEnv,
|
|
814
828
|
emitProcessCompleted,
|
|
815
829
|
emitProcessOutput,
|
|
816
830
|
emitProcessStarted
|
|
817
|
-
} from "@wrongstack/core";
|
|
831
|
+
} from "@wrongstack/core/observability";
|
|
832
|
+
import { buildChildEnv } from "@wrongstack/core/utils";
|
|
818
833
|
async function* spawnStream(opts) {
|
|
819
834
|
const max = opts.maxBytes ?? 2e5;
|
|
820
835
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
@@ -1026,7 +1041,7 @@ var init_spawn_stream = __esm({
|
|
|
1026
1041
|
import { createHash } from "node:crypto";
|
|
1027
1042
|
import * as fsp2 from "node:fs/promises";
|
|
1028
1043
|
import * as path3 from "node:path";
|
|
1029
|
-
import * as Core from "@wrongstack/core";
|
|
1044
|
+
import * as Core from "@wrongstack/core/utils";
|
|
1030
1045
|
function sha256hex(content) {
|
|
1031
1046
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1032
1047
|
}
|
|
@@ -1070,16 +1085,16 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
1070
1085
|
const realRoots = await Promise.all(
|
|
1071
1086
|
allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
|
|
1072
1087
|
);
|
|
1073
|
-
let
|
|
1088
|
+
let probe2 = absPath;
|
|
1074
1089
|
for (; ; ) {
|
|
1075
1090
|
let real;
|
|
1076
1091
|
try {
|
|
1077
|
-
real = await fsp2.realpath(
|
|
1092
|
+
real = await fsp2.realpath(probe2);
|
|
1078
1093
|
} catch (err) {
|
|
1079
1094
|
if (err.code === "ENOENT") {
|
|
1080
|
-
const parent = path3.dirname(
|
|
1081
|
-
if (parent ===
|
|
1082
|
-
|
|
1095
|
+
const parent = path3.dirname(probe2);
|
|
1096
|
+
if (parent === probe2) return;
|
|
1097
|
+
probe2 = parent;
|
|
1083
1098
|
continue;
|
|
1084
1099
|
}
|
|
1085
1100
|
throw err;
|
|
@@ -1267,7 +1282,20 @@ function pythonProfile() {
|
|
|
1267
1282
|
]),
|
|
1268
1283
|
ignoredDirectories: IGNORES,
|
|
1269
1284
|
packageManagers: Object.freeze(["pip", "poetry", "pipenv", "uv"]),
|
|
1270
|
-
executables: Object.freeze([
|
|
1285
|
+
executables: Object.freeze([
|
|
1286
|
+
"python",
|
|
1287
|
+
"python3",
|
|
1288
|
+
"pip",
|
|
1289
|
+
"pip3",
|
|
1290
|
+
"poetry",
|
|
1291
|
+
"pipenv",
|
|
1292
|
+
"uv",
|
|
1293
|
+
"ruff",
|
|
1294
|
+
"black",
|
|
1295
|
+
"pytest",
|
|
1296
|
+
"mypy",
|
|
1297
|
+
"pyright"
|
|
1298
|
+
]),
|
|
1271
1299
|
operations: Object.freeze({
|
|
1272
1300
|
syntax: async (ctx) => {
|
|
1273
1301
|
const py = ctx.options.target ? "python3" : "python3";
|
|
@@ -1327,6 +1355,14 @@ function pythonProfile() {
|
|
|
1327
1355
|
network: true
|
|
1328
1356
|
});
|
|
1329
1357
|
},
|
|
1358
|
+
"package-remove": async (ctx) => {
|
|
1359
|
+
const names = packageNames(ctx);
|
|
1360
|
+
return names.length === 0 ? unavailable(ctx, "package-remove", "At least one package name is required.") : processPlan(ctx, "package-remove", "pip", ["uninstall", "--yes", ...names], {
|
|
1361
|
+
parser: "package-text",
|
|
1362
|
+
reason: "Remove validated Python packages.",
|
|
1363
|
+
mutating: true
|
|
1364
|
+
});
|
|
1365
|
+
},
|
|
1330
1366
|
"package-audit": async (ctx) => processPlan(ctx, "package-audit", "pip", ["audit"], {
|
|
1331
1367
|
parser: "pip-audit",
|
|
1332
1368
|
reason: "Audit Python dependencies for vulnerabilities.",
|
|
@@ -1938,6 +1974,19 @@ function typescriptProfile() {
|
|
|
1938
1974
|
network: true
|
|
1939
1975
|
});
|
|
1940
1976
|
},
|
|
1977
|
+
"package-remove": async (ctx) => {
|
|
1978
|
+
const names = packageNames(ctx);
|
|
1979
|
+
if (names.length === 0)
|
|
1980
|
+
return unavailable(ctx, "package-remove", "At least one package name is required.");
|
|
1981
|
+
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1982
|
+
const args = manager === "npm" ? ["uninstall", "--ignore-scripts", ...names] : ["remove", "--ignore-scripts", ...names];
|
|
1983
|
+
return processPlan(ctx, "package-remove", manager, args, {
|
|
1984
|
+
parser: "package-text",
|
|
1985
|
+
reason: `Remove validated packages with ${manager} and lifecycle scripts disabled.`,
|
|
1986
|
+
mutating: true,
|
|
1987
|
+
network: true
|
|
1988
|
+
});
|
|
1989
|
+
},
|
|
1941
1990
|
"package-audit": async (ctx) => {
|
|
1942
1991
|
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1943
1992
|
return processPlan(ctx, "package-audit", manager, ["audit", "--json"], {
|
|
@@ -2390,13 +2439,28 @@ var init_primary = __esm({
|
|
|
2390
2439
|
}),
|
|
2391
2440
|
"package-add": async (ctx) => {
|
|
2392
2441
|
const names = packageNames(ctx);
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2442
|
+
const [spec] = names;
|
|
2443
|
+
const versionAt = spec?.lastIndexOf("@") ?? -1;
|
|
2444
|
+
const packageName = versionAt > 0 ? spec?.slice(0, versionAt) : spec;
|
|
2445
|
+
const packageVersion = versionAt > 0 ? spec?.slice(versionAt + 1) : void 0;
|
|
2446
|
+
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(
|
|
2447
|
+
ctx,
|
|
2448
|
+
"package-add",
|
|
2449
|
+
"dotnet",
|
|
2450
|
+
[
|
|
2451
|
+
"add",
|
|
2452
|
+
"package",
|
|
2453
|
+
packageName ?? "",
|
|
2454
|
+
...packageVersion ? ["--version", packageVersion] : []
|
|
2455
|
+
],
|
|
2456
|
+
{
|
|
2457
|
+
parser: "dotnet-package",
|
|
2458
|
+
reason: "Add validated NuGet packages.",
|
|
2459
|
+
mutating: true,
|
|
2460
|
+
network: true,
|
|
2461
|
+
executesProjectCode: true
|
|
2462
|
+
}
|
|
2463
|
+
);
|
|
2400
2464
|
},
|
|
2401
2465
|
"package-remove": async (ctx) => {
|
|
2402
2466
|
const names = packageNames(ctx);
|
|
@@ -2608,8 +2672,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
|
|
|
2608
2672
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2609
2673
|
}
|
|
2610
2674
|
}
|
|
2611
|
-
function collectFileEvidence(directory, fullPath,
|
|
2612
|
-
const lower =
|
|
2675
|
+
function collectFileEvidence(directory, fullPath, basename12, profiles, state) {
|
|
2676
|
+
const lower = basename12.toLowerCase();
|
|
2613
2677
|
const extension = path4.extname(lower);
|
|
2614
2678
|
for (const profile of profiles) {
|
|
2615
2679
|
const detector = profile.detectors.find(
|
|
@@ -2620,7 +2684,7 @@ function collectFileEvidence(directory, fullPath, basename11, profiles, state) {
|
|
|
2620
2684
|
candidate.evidence.push({
|
|
2621
2685
|
kind: detector.kind,
|
|
2622
2686
|
path: fullPath,
|
|
2623
|
-
value:
|
|
2687
|
+
value: basename12,
|
|
2624
2688
|
weight: detector.weight
|
|
2625
2689
|
});
|
|
2626
2690
|
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
@@ -3486,7 +3550,7 @@ function validateOperationOptions(operation, packages) {
|
|
|
3486
3550
|
if (value.startsWith(".") || value.startsWith("/") || /^[A-Za-z]:/.test(value) || value.includes("://")) {
|
|
3487
3551
|
throw new Error(`Package paths and URLs are not supported: "${value}"`);
|
|
3488
3552
|
}
|
|
3489
|
-
if (!PACKAGE_NAME_RE.test(value) && !COMPOSER_PACKAGE_RE.test(value) && !GO_MODULE_RE.test(value)) {
|
|
3553
|
+
if (!PACKAGE_NAME_RE.test(value) && !COMPOSER_PACKAGE_RE.test(value) && !GO_MODULE_RE.test(value) && !PYTHON_PACKAGE_RE.test(value)) {
|
|
3490
3554
|
throw new Error(`Invalid package identifier "${value}"`);
|
|
3491
3555
|
}
|
|
3492
3556
|
}
|
|
@@ -3508,7 +3572,7 @@ function isInside2(candidate, root) {
|
|
|
3508
3572
|
const relative13 = path6.relative(path6.resolve(root), path6.resolve(candidate));
|
|
3509
3573
|
return relative13 === "" || !relative13.startsWith("..") && !path6.isAbsolute(relative13);
|
|
3510
3574
|
}
|
|
3511
|
-
var MAX_ARGUMENTS, MAX_ARGUMENT_LENGTH, PACKAGE_NAME_RE, COMPOSER_PACKAGE_RE, GO_MODULE_RE;
|
|
3575
|
+
var MAX_ARGUMENTS, MAX_ARGUMENT_LENGTH, PACKAGE_NAME_RE, COMPOSER_PACKAGE_RE, GO_MODULE_RE, PYTHON_PACKAGE_RE;
|
|
3512
3576
|
var init_plan = __esm({
|
|
3513
3577
|
"src/languages/plan.ts"() {
|
|
3514
3578
|
"use strict";
|
|
@@ -3519,6 +3583,7 @@ var init_plan = __esm({
|
|
|
3519
3583
|
PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3520
3584
|
COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3521
3585
|
GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
|
|
3586
|
+
PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
|
|
3522
3587
|
}
|
|
3523
3588
|
});
|
|
3524
3589
|
|
|
@@ -3937,15 +4002,15 @@ async function changedPaths(before, after, beforeSizes, afterSizes) {
|
|
|
3937
4002
|
const beforeSet = new Set(before);
|
|
3938
4003
|
const afterSet = new Set(after);
|
|
3939
4004
|
const changed = /* @__PURE__ */ new Set();
|
|
3940
|
-
for (const
|
|
3941
|
-
if (!beforeSet.has(
|
|
4005
|
+
for (const path35 of after) {
|
|
4006
|
+
if (!beforeSet.has(path35)) changed.add(path35);
|
|
3942
4007
|
}
|
|
3943
|
-
for (const
|
|
3944
|
-
if (!afterSet.has(
|
|
4008
|
+
for (const path35 of before) {
|
|
4009
|
+
if (!afterSet.has(path35)) changed.add(path35);
|
|
3945
4010
|
}
|
|
3946
4011
|
if (beforeSizes && afterSizes) {
|
|
3947
|
-
for (const
|
|
3948
|
-
if (beforeSizes.get(
|
|
4012
|
+
for (const path35 of after) {
|
|
4013
|
+
if (beforeSizes.get(path35) !== afterSizes.get(path35)) changed.add(path35);
|
|
3949
4014
|
}
|
|
3950
4015
|
}
|
|
3951
4016
|
return [...changed].sort();
|
|
@@ -4420,7 +4485,6 @@ var init_package_tool = __esm({
|
|
|
4420
4485
|
const operationOptions2 = {
|
|
4421
4486
|
...input.names ? { packages: input.names } : {},
|
|
4422
4487
|
...input.scope ? { packageScope: input.scope } : {},
|
|
4423
|
-
...input.dryRun !== void 0 ? { allowScripts: input.dryRun } : { allowScripts: false },
|
|
4424
4488
|
...input.allowScripts !== void 0 ? { allowScripts: input.allowScripts } : {}
|
|
4425
4489
|
};
|
|
4426
4490
|
const planResult = await planLanguageOperation({
|
|
@@ -4449,6 +4513,24 @@ var init_package_tool = __esm({
|
|
|
4449
4513
|
};
|
|
4450
4514
|
return;
|
|
4451
4515
|
}
|
|
4516
|
+
if (input.dryRun) {
|
|
4517
|
+
yield {
|
|
4518
|
+
type: "final",
|
|
4519
|
+
output: {
|
|
4520
|
+
status: "passed",
|
|
4521
|
+
language: planResult.workspace.language,
|
|
4522
|
+
workspace: planResult.workspace.root,
|
|
4523
|
+
operation: planResult.plan.operation,
|
|
4524
|
+
mutations: [],
|
|
4525
|
+
vulnerabilities: [],
|
|
4526
|
+
outdated: [],
|
|
4527
|
+
manifestsChanged: [],
|
|
4528
|
+
lockfilesChanged: [],
|
|
4529
|
+
output: `Dry run: ${planResult.plan.command ?? "internal"} ${planResult.plan.args.join(" ")}`.trim()
|
|
4530
|
+
}
|
|
4531
|
+
};
|
|
4532
|
+
return;
|
|
4533
|
+
}
|
|
4452
4534
|
const runner = executePackagePlan({
|
|
4453
4535
|
projectRoot: ctx.projectRoot,
|
|
4454
4536
|
workspace: planResult.workspace,
|
|
@@ -5261,10 +5343,10 @@ import {
|
|
|
5261
5343
|
emitProcessCompleted as emitProcessCompleted2,
|
|
5262
5344
|
emitProcessOutput as emitProcessOutput2,
|
|
5263
5345
|
emitProcessStarted as emitProcessStarted2
|
|
5264
|
-
} from "@wrongstack/core";
|
|
5346
|
+
} from "@wrongstack/core/observability";
|
|
5265
5347
|
|
|
5266
5348
|
// src/_env.ts
|
|
5267
|
-
import { buildChildEnv as buildChildEnv2 } from "@wrongstack/core";
|
|
5349
|
+
import { buildChildEnv as buildChildEnv2 } from "@wrongstack/core/utils";
|
|
5268
5350
|
|
|
5269
5351
|
// src/bash.ts
|
|
5270
5352
|
init_output_spool();
|
|
@@ -5278,8 +5360,9 @@ import * as os3 from "node:os";
|
|
|
5278
5360
|
init_process_registry();
|
|
5279
5361
|
import * as fs6 from "node:fs/promises";
|
|
5280
5362
|
import * as os2 from "node:os";
|
|
5363
|
+
import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
|
|
5281
5364
|
import * as path9 from "node:path";
|
|
5282
|
-
var REGISTRY_FILE = "
|
|
5365
|
+
var REGISTRY_FILE = "process-registry.json";
|
|
5283
5366
|
function toErrorMessage(err) {
|
|
5284
5367
|
return err instanceof Error ? err.message : String(err);
|
|
5285
5368
|
}
|
|
@@ -5297,7 +5380,8 @@ function emitStructuredLog(level, event, message, error) {
|
|
|
5297
5380
|
}
|
|
5298
5381
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
5299
5382
|
var STALE_THRESHOLD_MS = 3e4;
|
|
5300
|
-
var
|
|
5383
|
+
var LOCK_STALE_MS = 3e4;
|
|
5384
|
+
var LOCKFILE = ".process-registry.lock";
|
|
5301
5385
|
function generateInstanceId() {
|
|
5302
5386
|
const hostname4 = os2.hostname();
|
|
5303
5387
|
const pid = process.pid;
|
|
@@ -5325,21 +5409,26 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
|
5325
5409
|
try {
|
|
5326
5410
|
const content = await fs6.readFile(lockfilePath, "utf-8");
|
|
5327
5411
|
const parts = content.split(":");
|
|
5328
|
-
const
|
|
5329
|
-
const
|
|
5330
|
-
|
|
5412
|
+
const lockPid = parseInt(parts[0] ?? "0", 10);
|
|
5413
|
+
const lockTs = Number(parts[parts.length - 1]);
|
|
5414
|
+
const staleByAge = Number.isFinite(lockTs) && Date.now() - lockTs > LOCK_STALE_MS;
|
|
5415
|
+
let holderDead = false;
|
|
5416
|
+
if (process.platform !== "win32" && Number.isFinite(lockPid) && lockPid > 0) {
|
|
5331
5417
|
try {
|
|
5332
5418
|
process.kill(lockPid, 0);
|
|
5333
5419
|
} catch {
|
|
5334
|
-
|
|
5335
|
-
continue;
|
|
5420
|
+
holderDead = true;
|
|
5336
5421
|
}
|
|
5337
5422
|
}
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5423
|
+
if (holderDead || staleByAge) {
|
|
5424
|
+
await fs6.unlink(lockfilePath).catch(() => {
|
|
5425
|
+
});
|
|
5426
|
+
continue;
|
|
5342
5427
|
}
|
|
5428
|
+
} catch {
|
|
5429
|
+
await fs6.unlink(lockfilePath).catch(() => {
|
|
5430
|
+
});
|
|
5431
|
+
continue;
|
|
5343
5432
|
}
|
|
5344
5433
|
await new Promise((r) => setTimeout(r, 100));
|
|
5345
5434
|
continue;
|
|
@@ -5349,25 +5438,35 @@ async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
|
5349
5438
|
}
|
|
5350
5439
|
throw new Error(`Failed to acquire lock after ${timeoutMs}ms`);
|
|
5351
5440
|
}
|
|
5441
|
+
function freshRegistryData() {
|
|
5442
|
+
return {
|
|
5443
|
+
version: 1,
|
|
5444
|
+
instances: /* @__PURE__ */ new Map(),
|
|
5445
|
+
protectedPatterns: ["wrongstack", "node"],
|
|
5446
|
+
lastCleanup: Date.now()
|
|
5447
|
+
};
|
|
5448
|
+
}
|
|
5352
5449
|
async function readRegistryFile(filePath) {
|
|
5450
|
+
let content;
|
|
5353
5451
|
try {
|
|
5354
|
-
|
|
5355
|
-
const parsed = JSON.parse(content);
|
|
5356
|
-
if (parsed.instances && Array.isArray(parsed.instances)) {
|
|
5357
|
-
parsed.instances = new Map(parsed.instances);
|
|
5358
|
-
}
|
|
5359
|
-
return parsed;
|
|
5452
|
+
content = await fs6.readFile(filePath, "utf-8");
|
|
5360
5453
|
} catch (err) {
|
|
5361
|
-
if (isNodeError(err) && err.code === "ENOENT")
|
|
5362
|
-
return {
|
|
5363
|
-
version: 1,
|
|
5364
|
-
instances: /* @__PURE__ */ new Map(),
|
|
5365
|
-
protectedPatterns: ["wrongstack", "node"],
|
|
5366
|
-
lastCleanup: Date.now()
|
|
5367
|
-
};
|
|
5368
|
-
}
|
|
5454
|
+
if (isNodeError(err) && err.code === "ENOENT") return freshRegistryData();
|
|
5369
5455
|
throw err;
|
|
5370
5456
|
}
|
|
5457
|
+
try {
|
|
5458
|
+
const parsed = JSON.parse(content);
|
|
5459
|
+
if (!parsed || typeof parsed !== "object") return freshRegistryData();
|
|
5460
|
+
const base = freshRegistryData();
|
|
5461
|
+
return {
|
|
5462
|
+
version: 1,
|
|
5463
|
+
instances: Array.isArray(parsed.instances) ? new Map(parsed.instances) : base.instances,
|
|
5464
|
+
protectedPatterns: Array.isArray(parsed.protectedPatterns) ? parsed.protectedPatterns : base.protectedPatterns,
|
|
5465
|
+
lastCleanup: typeof parsed.lastCleanup === "number" ? parsed.lastCleanup : base.lastCleanup
|
|
5466
|
+
};
|
|
5467
|
+
} catch {
|
|
5468
|
+
return freshRegistryData();
|
|
5469
|
+
}
|
|
5371
5470
|
}
|
|
5372
5471
|
async function writeRegistryFile(filePath, data) {
|
|
5373
5472
|
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
@@ -5386,12 +5485,18 @@ var PersistentProcessRegistry = class {
|
|
|
5386
5485
|
lockPath;
|
|
5387
5486
|
baseRegistry;
|
|
5388
5487
|
heartbeatInterval = null;
|
|
5488
|
+
cleanupInterval = null;
|
|
5489
|
+
heartbeatRunning = false;
|
|
5490
|
+
cleanupRunning = false;
|
|
5389
5491
|
isShuttingDown = false;
|
|
5492
|
+
onProcessExit = () => {
|
|
5493
|
+
void this.syncToPersistent();
|
|
5494
|
+
};
|
|
5390
5495
|
constructor(baseRegistry) {
|
|
5391
5496
|
this.instanceId = generateInstanceId();
|
|
5392
|
-
const
|
|
5393
|
-
this.registryPath = path9.join(
|
|
5394
|
-
this.lockPath = path9.join(
|
|
5497
|
+
const globalRoot = wstackGlobalRoot3();
|
|
5498
|
+
this.registryPath = path9.join(globalRoot, REGISTRY_FILE);
|
|
5499
|
+
this.lockPath = path9.join(globalRoot, LOCKFILE);
|
|
5395
5500
|
this.baseRegistry = baseRegistry ?? getProcessRegistry();
|
|
5396
5501
|
this.ensureDirectory().catch((err) => {
|
|
5397
5502
|
emitStructuredLog("warn", "process_registry.dir_create_failed", "PersistentProcessRegistry: failed to create .wrongstack directory", err);
|
|
@@ -5410,16 +5515,18 @@ var PersistentProcessRegistry = class {
|
|
|
5410
5515
|
*/
|
|
5411
5516
|
start() {
|
|
5412
5517
|
if (this.heartbeatInterval) return;
|
|
5413
|
-
this.
|
|
5518
|
+
this.isShuttingDown = false;
|
|
5519
|
+
this.heartbeat();
|
|
5414
5520
|
this.heartbeatInterval = setInterval(() => {
|
|
5415
5521
|
this.heartbeat();
|
|
5416
5522
|
}, HEARTBEAT_INTERVAL_MS);
|
|
5417
5523
|
this.heartbeatInterval.unref?.();
|
|
5418
|
-
setInterval(() => {
|
|
5419
|
-
this.
|
|
5420
|
-
}, STALE_THRESHOLD_MS)
|
|
5524
|
+
this.cleanupInterval = setInterval(() => {
|
|
5525
|
+
this.cleanup();
|
|
5526
|
+
}, STALE_THRESHOLD_MS);
|
|
5527
|
+
this.cleanupInterval.unref?.();
|
|
5421
5528
|
this.registerMainProcess();
|
|
5422
|
-
process.on("exit",
|
|
5529
|
+
process.on("exit", this.onProcessExit);
|
|
5423
5530
|
}
|
|
5424
5531
|
/**
|
|
5425
5532
|
* Stop the heartbeat and clean up.
|
|
@@ -5430,7 +5537,12 @@ var PersistentProcessRegistry = class {
|
|
|
5430
5537
|
clearInterval(this.heartbeatInterval);
|
|
5431
5538
|
this.heartbeatInterval = null;
|
|
5432
5539
|
}
|
|
5433
|
-
this.
|
|
5540
|
+
if (this.cleanupInterval) {
|
|
5541
|
+
clearInterval(this.cleanupInterval);
|
|
5542
|
+
this.cleanupInterval = null;
|
|
5543
|
+
}
|
|
5544
|
+
process.off("exit", this.onProcessExit);
|
|
5545
|
+
void this.syncToPersistent();
|
|
5434
5546
|
}
|
|
5435
5547
|
/**
|
|
5436
5548
|
* Register the main WrongStack process as protected.
|
|
@@ -5514,8 +5626,18 @@ var PersistentProcessRegistry = class {
|
|
|
5514
5626
|
* Send heartbeat to mark all this instance's processes as alive.
|
|
5515
5627
|
*/
|
|
5516
5628
|
heartbeat() {
|
|
5517
|
-
if (this.isShuttingDown) return;
|
|
5518
|
-
this.
|
|
5629
|
+
if (this.isShuttingDown || this.heartbeatRunning) return;
|
|
5630
|
+
this.heartbeatRunning = true;
|
|
5631
|
+
void this.syncToPersistent().finally(() => {
|
|
5632
|
+
this.heartbeatRunning = false;
|
|
5633
|
+
});
|
|
5634
|
+
}
|
|
5635
|
+
cleanup() {
|
|
5636
|
+
if (this.isShuttingDown || this.cleanupRunning) return;
|
|
5637
|
+
this.cleanupRunning = true;
|
|
5638
|
+
void this.cleanupStaleEntries().finally(() => {
|
|
5639
|
+
this.cleanupRunning = false;
|
|
5640
|
+
});
|
|
5519
5641
|
}
|
|
5520
5642
|
/**
|
|
5521
5643
|
* Sync this instance's processes to the persistent registry.
|
|
@@ -6237,14 +6359,12 @@ var bashTool = {
|
|
|
6237
6359
|
const detached = !isWin5;
|
|
6238
6360
|
const startedAt = Date.now();
|
|
6239
6361
|
if (input.background) {
|
|
6240
|
-
let buf2 = "";
|
|
6241
|
-
let truncated = false;
|
|
6242
6362
|
const child2 = spawn3(shell, args, {
|
|
6243
6363
|
cwd: ctx.projectRoot,
|
|
6244
6364
|
env,
|
|
6245
6365
|
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
6246
6366
|
// and POSIX shells ignore stdin when given the command inline.
|
|
6247
|
-
stdio: [plan.useStdin ? "pipe" : "ignore", "
|
|
6367
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "ignore", "ignore"],
|
|
6248
6368
|
// win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
|
|
6249
6369
|
// DETACHED_PROCESS (detached: true) is set, so the console-less
|
|
6250
6370
|
// cmd.exe's grandchildren (node, dev servers) each allocate a fresh
|
|
@@ -6263,8 +6383,8 @@ var bashTool = {
|
|
|
6263
6383
|
}
|
|
6264
6384
|
}
|
|
6265
6385
|
const pid2 = child2.pid;
|
|
6266
|
-
|
|
6267
|
-
|
|
6386
|
+
const stdoutBytes2 = 0;
|
|
6387
|
+
const stderrBytes2 = 0;
|
|
6268
6388
|
let telemetryCompleted2 = false;
|
|
6269
6389
|
emitProcessStarted2({
|
|
6270
6390
|
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
@@ -6297,41 +6417,17 @@ var bashTool = {
|
|
|
6297
6417
|
startedAt: Date.now(),
|
|
6298
6418
|
sessionId: ctx.session?.id,
|
|
6299
6419
|
child: child2,
|
|
6300
|
-
processGroupLeader: detached && child2.pid === pid2
|
|
6420
|
+
processGroupLeader: detached && child2.pid === pid2,
|
|
6421
|
+
background: true
|
|
6301
6422
|
});
|
|
6302
6423
|
child2.on("close", () => registry.unregister(pid2));
|
|
6303
6424
|
}
|
|
6304
|
-
const onBgData = (chunk, stream) => {
|
|
6305
|
-
if (stream === "stdout") stdoutBytes2 += chunk.byteLength;
|
|
6306
|
-
else stderrBytes2 += chunk.byteLength;
|
|
6307
|
-
emitProcessOutput2({ pid: pid2, stream, chunk });
|
|
6308
|
-
if (truncated) return;
|
|
6309
|
-
const remain = MAX_OUTPUT - buf2.length;
|
|
6310
|
-
if (remain > 0) {
|
|
6311
|
-
buf2 += chunk.toString().slice(0, remain);
|
|
6312
|
-
}
|
|
6313
|
-
if (buf2.length >= MAX_OUTPUT) {
|
|
6314
|
-
truncated = true;
|
|
6315
|
-
child2.stdout?.off("data", onBgStdout);
|
|
6316
|
-
child2.stderr?.off("data", onBgStderr);
|
|
6317
|
-
}
|
|
6318
|
-
};
|
|
6319
|
-
const onBgStdout = (chunk) => onBgData(chunk, "stdout");
|
|
6320
|
-
const onBgStderr = (chunk) => onBgData(chunk, "stderr");
|
|
6321
|
-
child2.stdout?.on("data", onBgStdout);
|
|
6322
|
-
child2.stderr?.on("data", onBgStderr);
|
|
6323
|
-
const cleanupBackground = () => {
|
|
6324
|
-
child2.stdout?.off("data", onBgStdout);
|
|
6325
|
-
child2.stderr?.off("data", onBgStderr);
|
|
6326
|
-
};
|
|
6327
6425
|
child2.on("error", () => {
|
|
6328
|
-
cleanupBackground();
|
|
6329
6426
|
if (typeof pid2 === "number") registry.unregister(pid2);
|
|
6330
6427
|
registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
|
|
6331
6428
|
completeBackground(1);
|
|
6332
6429
|
});
|
|
6333
6430
|
child2.on("close", (code, signal) => {
|
|
6334
|
-
cleanupBackground();
|
|
6335
6431
|
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
6336
6432
|
completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
6337
6433
|
});
|
|
@@ -6339,7 +6435,7 @@ var bashTool = {
|
|
|
6339
6435
|
yield {
|
|
6340
6436
|
type: "final",
|
|
6341
6437
|
output: {
|
|
6342
|
-
output:
|
|
6438
|
+
output: "",
|
|
6343
6439
|
exit_code: null,
|
|
6344
6440
|
timed_out: false,
|
|
6345
6441
|
pid: pid2
|
|
@@ -6610,6 +6706,7 @@ ${hint}` : ""),
|
|
|
6610
6706
|
for (const entry of registry.bySession(sessionId)) {
|
|
6611
6707
|
if (entry.name !== "bash") continue;
|
|
6612
6708
|
if (entry.child.exitCode !== null) continue;
|
|
6709
|
+
if (entry.background) continue;
|
|
6613
6710
|
if (entry.protected) continue;
|
|
6614
6711
|
registry.kill(entry.pid, { force: true });
|
|
6615
6712
|
}
|
|
@@ -6619,7 +6716,7 @@ ${hint}` : ""),
|
|
|
6619
6716
|
// src/batch-tool-use.ts
|
|
6620
6717
|
import {
|
|
6621
6718
|
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6622
|
-
} from "@wrongstack/core";
|
|
6719
|
+
} from "@wrongstack/core/types";
|
|
6623
6720
|
var batchToolUseTool = {
|
|
6624
6721
|
name: "batch_tool_use",
|
|
6625
6722
|
category: "Meta",
|
|
@@ -7399,6 +7496,10 @@ var BrowserSessionManager = class {
|
|
|
7399
7496
|
const ids = [...this.sessions.values()].filter((session) => session.ownerId === ownerId).map((session) => session.id);
|
|
7400
7497
|
for (const id of ids) await this.close(id, ownerId).catch(() => void 0);
|
|
7401
7498
|
}
|
|
7499
|
+
/** True when the manager owns no live browser contexts. */
|
|
7500
|
+
isIdle() {
|
|
7501
|
+
return this.sessions.size === 0;
|
|
7502
|
+
}
|
|
7402
7503
|
async dispose() {
|
|
7403
7504
|
for (const session of [...this.sessions.values()]) {
|
|
7404
7505
|
await session.context.close().catch(() => void 0);
|
|
@@ -7562,7 +7663,7 @@ async function abortable(signal, operation, onAbort) {
|
|
|
7562
7663
|
|
|
7563
7664
|
// src/browser/tools.ts
|
|
7564
7665
|
import * as path12 from "node:path";
|
|
7565
|
-
import { ToolCapabilities } from "@wrongstack/core";
|
|
7666
|
+
import { ToolCapabilities } from "@wrongstack/core/security";
|
|
7566
7667
|
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
7567
7668
|
var managers = /* @__PURE__ */ new Map();
|
|
7568
7669
|
var cleanupSignalByContext = /* @__PURE__ */ new WeakMap();
|
|
@@ -7583,12 +7684,17 @@ function managerFor(ctx) {
|
|
|
7583
7684
|
if (cleanupSignalByContext.get(ctx) !== runSignal) {
|
|
7584
7685
|
cleanupSignalByContext.set(ctx, runSignal);
|
|
7585
7686
|
const managerForRun = manager;
|
|
7687
|
+
const rootForRun = root;
|
|
7586
7688
|
const ownerForRun = owner(ctx);
|
|
7587
7689
|
ctx.registerAbortHook?.(async () => {
|
|
7588
7690
|
if (cleanupSignalByContext.get(ctx) === runSignal) {
|
|
7589
7691
|
cleanupSignalByContext.delete(ctx);
|
|
7590
7692
|
}
|
|
7591
7693
|
await managerForRun.closeOwner(ownerForRun);
|
|
7694
|
+
if (managerForRun.isIdle() && managers.get(rootForRun) === managerForRun) {
|
|
7695
|
+
managers.delete(rootForRun);
|
|
7696
|
+
await managerForRun.dispose();
|
|
7697
|
+
}
|
|
7592
7698
|
});
|
|
7593
7699
|
}
|
|
7594
7700
|
return manager;
|
|
@@ -7976,7 +8082,7 @@ async function shutdownBrowserTools() {
|
|
|
7976
8082
|
}
|
|
7977
8083
|
|
|
7978
8084
|
// src/codebase-index/writer.ts
|
|
7979
|
-
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
8085
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
7980
8086
|
|
|
7981
8087
|
// src/codebase-index/circuit-breaker.ts
|
|
7982
8088
|
var CircuitOpenError = class extends Error {
|
|
@@ -8066,7 +8172,7 @@ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
|
8066
8172
|
import { createRequire } from "node:module";
|
|
8067
8173
|
import * as fs9 from "node:fs";
|
|
8068
8174
|
import * as path13 from "node:path";
|
|
8069
|
-
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
8175
|
+
import { resolveWstackPaths as resolveWstackPaths2, sqliteCachePragmas } from "@wrongstack/core/utils";
|
|
8070
8176
|
|
|
8071
8177
|
// src/codebase-index/schema.ts
|
|
8072
8178
|
var SCHEMA_VERSION = 3;
|
|
@@ -8122,23 +8228,13 @@ function buildBm25Index(docs) {
|
|
|
8122
8228
|
const tokens = tokenise(d.text);
|
|
8123
8229
|
return { id: d.id, tokens, raw: d.text, len: tokens.length };
|
|
8124
8230
|
});
|
|
8125
|
-
const df = {};
|
|
8126
|
-
for (const doc of documents) {
|
|
8127
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8128
|
-
for (const t of doc.tokens) {
|
|
8129
|
-
if (!seen.has(t)) {
|
|
8130
|
-
df[t] = (df[t] ?? 0) + 1;
|
|
8131
|
-
seen.add(t);
|
|
8132
|
-
}
|
|
8133
|
-
}
|
|
8134
|
-
}
|
|
8135
8231
|
const N = documents.length;
|
|
8136
8232
|
const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
|
|
8137
8233
|
const avgLen = N === 0 ? 0 : totalLen / N;
|
|
8138
|
-
return new Bm25Index(documents,
|
|
8234
|
+
return new Bm25Index(documents, N, avgLen);
|
|
8139
8235
|
}
|
|
8140
8236
|
var Bm25Index = class {
|
|
8141
|
-
constructor(documents,
|
|
8237
|
+
constructor(documents, N, avgLen) {
|
|
8142
8238
|
this.documents = documents;
|
|
8143
8239
|
this.N = N;
|
|
8144
8240
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
@@ -8146,9 +8242,19 @@ var Bm25Index = class {
|
|
|
8146
8242
|
documents;
|
|
8147
8243
|
N;
|
|
8148
8244
|
safeAvgLen;
|
|
8245
|
+
/** Lazily-built id→doc index so getDoc is O(1) instead of an O(D) linear find. */
|
|
8246
|
+
_byId;
|
|
8149
8247
|
score(query, filter) {
|
|
8150
8248
|
const qTokens = tokenise(query);
|
|
8151
8249
|
if (qTokens.length === 0) return [];
|
|
8250
|
+
const dfByTerm = /* @__PURE__ */ new Map();
|
|
8251
|
+
for (const qTerm of qTokens) {
|
|
8252
|
+
let dfVal = 0;
|
|
8253
|
+
for (const candidate of this.documents) {
|
|
8254
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
8255
|
+
}
|
|
8256
|
+
dfByTerm.set(qTerm, dfVal);
|
|
8257
|
+
}
|
|
8152
8258
|
const results = [];
|
|
8153
8259
|
for (const doc of this.documents) {
|
|
8154
8260
|
if (filter && !filter(doc.id)) continue;
|
|
@@ -8159,10 +8265,7 @@ var Bm25Index = class {
|
|
|
8159
8265
|
if (t.startsWith(qTerm)) tf++;
|
|
8160
8266
|
}
|
|
8161
8267
|
if (tf === 0) continue;
|
|
8162
|
-
|
|
8163
|
-
for (const candidate of this.documents) {
|
|
8164
|
-
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
8165
|
-
}
|
|
8268
|
+
const dfVal = dfByTerm.get(qTerm) ?? 0;
|
|
8166
8269
|
if (dfVal === 0) continue;
|
|
8167
8270
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
8168
8271
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -8174,7 +8277,10 @@ var Bm25Index = class {
|
|
|
8174
8277
|
return results;
|
|
8175
8278
|
}
|
|
8176
8279
|
getDoc(id) {
|
|
8177
|
-
|
|
8280
|
+
if (!this._byId) {
|
|
8281
|
+
this._byId = new Map(this.documents.map((d) => [d.id, d]));
|
|
8282
|
+
}
|
|
8283
|
+
return this._byId.get(id);
|
|
8178
8284
|
}
|
|
8179
8285
|
extractSnippet(docId, queryTokens, radius = 40) {
|
|
8180
8286
|
const doc = this.getDoc(docId);
|
|
@@ -8336,6 +8442,25 @@ var IndexStore = class _IndexStore {
|
|
|
8336
8442
|
* and reuse it. Cleared in {@link close} when the connection is torn down.
|
|
8337
8443
|
*/
|
|
8338
8444
|
stmtCache = /* @__PURE__ */ new Map();
|
|
8445
|
+
/**
|
|
8446
|
+
* Cached full-corpus BM25 index for the FTS5-unavailable fallback path.
|
|
8447
|
+
* Built lazily on the first `searchRankedFallback` call and invalidated
|
|
8448
|
+
* (via `bm25Dirty`) whenever the `symbols` table is mutated. Computing
|
|
8449
|
+
* IDF over the full corpus is also more correct than the old per-query
|
|
8450
|
+
* candidate-subset IDF.
|
|
8451
|
+
*
|
|
8452
|
+
* Cache-lifecycle invariants (single source of truth lives at the
|
|
8453
|
+
* `invalidateBm25()` helper — see its docblock for the "every mutation
|
|
8454
|
+
* MUST call this" contract):
|
|
8455
|
+
* - declaration: this field + `bm25Dirty` (here)
|
|
8456
|
+
* - invalidation: `invalidateBm25()` flips the flag and nulls the cache
|
|
8457
|
+
* - build: `getOrBuildBm25()` rebuilds against current `symbols` rows
|
|
8458
|
+
* - teardown: `close()` resets the flag and nulls the cache
|
|
8459
|
+
*/
|
|
8460
|
+
bm25Cache = null;
|
|
8461
|
+
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
8462
|
+
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
8463
|
+
bm25Dirty = true;
|
|
8339
8464
|
/** Prepare-once helper: compile `sql` on first use, reuse thereafter. */
|
|
8340
8465
|
stmt(sql) {
|
|
8341
8466
|
let s = this.stmtCache.get(sql);
|
|
@@ -8383,7 +8508,15 @@ var IndexStore = class _IndexStore {
|
|
|
8383
8508
|
this.db = new Database(path13.join(this.indexDir, DB_FILE));
|
|
8384
8509
|
try {
|
|
8385
8510
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
8386
|
-
this.db.exec("PRAGMA
|
|
8511
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
8512
|
+
this.db.exec("PRAGMA busy_timeout = 15000");
|
|
8513
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
8514
|
+
const cache2 = sqliteCachePragmas();
|
|
8515
|
+
this.db.exec(`PRAGMA cache_size = -${cache2.cacheSizeKiB}`);
|
|
8516
|
+
this.db.exec(`PRAGMA mmap_size = ${cache2.mmapBytes}`);
|
|
8517
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
8518
|
+
this.db.exec("PRAGMA journal_size_limit = 67108864");
|
|
8519
|
+
this.db.exec("PRAGMA wal_autocheckpoint = 1000");
|
|
8387
8520
|
} catch {
|
|
8388
8521
|
}
|
|
8389
8522
|
this.initSchema();
|
|
@@ -8395,7 +8528,7 @@ var IndexStore = class _IndexStore {
|
|
|
8395
8528
|
value TEXT NOT NULL
|
|
8396
8529
|
);
|
|
8397
8530
|
`);
|
|
8398
|
-
const storedRows = this.
|
|
8531
|
+
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
8399
8532
|
const storedVersion = storedRows.length ? Number(storedRows[0]?.value) : null;
|
|
8400
8533
|
if (storedVersion !== null && storedVersion !== SCHEMA_VERSION) {
|
|
8401
8534
|
this.db.exec(`
|
|
@@ -8404,9 +8537,9 @@ var IndexStore = class _IndexStore {
|
|
|
8404
8537
|
DROP TABLE IF EXISTS refs;
|
|
8405
8538
|
`);
|
|
8406
8539
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
8407
|
-
this.
|
|
8540
|
+
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
|
|
8408
8541
|
} else if (storedVersion === null) {
|
|
8409
|
-
this.
|
|
8542
|
+
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
|
|
8410
8543
|
}
|
|
8411
8544
|
this.db.exec(`
|
|
8412
8545
|
CREATE TABLE IF NOT EXISTS files (
|
|
@@ -8437,6 +8570,7 @@ var IndexStore = class _IndexStore {
|
|
|
8437
8570
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
|
|
8438
8571
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
|
|
8439
8572
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
|
|
8573
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)");
|
|
8440
8574
|
this.db.exec(`
|
|
8441
8575
|
CREATE TABLE IF NOT EXISTS refs (
|
|
8442
8576
|
id INTEGER PRIMARY KEY,
|
|
@@ -8457,65 +8591,167 @@ var IndexStore = class _IndexStore {
|
|
|
8457
8591
|
);
|
|
8458
8592
|
this.ftsAvailable = true;
|
|
8459
8593
|
const symbolCount = Number(
|
|
8460
|
-
this.
|
|
8594
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8461
8595
|
);
|
|
8462
8596
|
const ftsCount = Number(
|
|
8463
|
-
this.
|
|
8597
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8464
8598
|
);
|
|
8465
8599
|
if (symbolCount !== ftsCount) {
|
|
8466
8600
|
this.db.exec("DELETE FROM symbols_fts");
|
|
8467
|
-
const
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8601
|
+
const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8602
|
+
this.bulkInsertFts(
|
|
8603
|
+
rows.map((row) => ({
|
|
8604
|
+
id: row.id,
|
|
8605
|
+
text: buildIndexableText(row.name, row.signature, row.doc_comment)
|
|
8606
|
+
}))
|
|
8607
|
+
);
|
|
8608
|
+
this.invalidateBm25();
|
|
8472
8609
|
}
|
|
8473
8610
|
} catch {
|
|
8474
8611
|
this.ftsAvailable = false;
|
|
8475
8612
|
}
|
|
8613
|
+
this.ensureNextSymbolIdSeeded();
|
|
8614
|
+
}
|
|
8615
|
+
// ─── ID allocation & bulk helpers ────────────────────────────────────────────
|
|
8616
|
+
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
8617
|
+
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
8618
|
+
static MAX_SQL_VARS = 900;
|
|
8619
|
+
/**
|
|
8620
|
+
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
8621
|
+
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
8622
|
+
* re-reads and advances the counter atomically.
|
|
8623
|
+
*/
|
|
8624
|
+
ensureNextSymbolIdSeeded() {
|
|
8625
|
+
const existing = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8626
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8627
|
+
);
|
|
8628
|
+
if (existing?.value !== void 0) return;
|
|
8629
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8630
|
+
const next = (maxRows[0]?.m ?? 0) + 1;
|
|
8631
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8632
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8633
|
+
String(next)
|
|
8634
|
+
);
|
|
8635
|
+
}
|
|
8636
|
+
/**
|
|
8637
|
+
* Reserve `count` consecutive symbol ids. MUST run inside BEGIN IMMEDIATE
|
|
8638
|
+
* so concurrent indexers cannot hand out overlapping ranges.
|
|
8639
|
+
*/
|
|
8640
|
+
allocateSymbolIds(count) {
|
|
8641
|
+
if (count <= 0) return this.getMaxSymbolId() + 1;
|
|
8642
|
+
this.ensureNextSymbolIdSeeded();
|
|
8643
|
+
const row = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8644
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8645
|
+
);
|
|
8646
|
+
const start = Math.max(1, Number(row?.value ?? 1) || 1);
|
|
8647
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8648
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8649
|
+
String(start + count)
|
|
8650
|
+
);
|
|
8651
|
+
return start;
|
|
8652
|
+
}
|
|
8653
|
+
/** Multi-row INSERT for symbols — amortizes prepare/bind overhead in large batches. */
|
|
8654
|
+
bulkInsertSymbols(rows) {
|
|
8655
|
+
if (rows.length === 0) return;
|
|
8656
|
+
const cols = 12;
|
|
8657
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8658
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8659
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8660
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
8661
|
+
const stmt = this.stmt(
|
|
8662
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8663
|
+
VALUES ${placeholders}`
|
|
8664
|
+
);
|
|
8665
|
+
const binds = [];
|
|
8666
|
+
for (const r of chunk) {
|
|
8667
|
+
binds.push(
|
|
8668
|
+
r.id,
|
|
8669
|
+
r.lang,
|
|
8670
|
+
r.kind,
|
|
8671
|
+
r.name,
|
|
8672
|
+
r.file,
|
|
8673
|
+
r.line,
|
|
8674
|
+
r.col,
|
|
8675
|
+
r.signature,
|
|
8676
|
+
r.docComment,
|
|
8677
|
+
r.scope,
|
|
8678
|
+
r.text,
|
|
8679
|
+
r.file
|
|
8680
|
+
);
|
|
8681
|
+
}
|
|
8682
|
+
stmt.run(...binds);
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
bulkInsertFts(rows) {
|
|
8686
|
+
if (!this.ftsAvailable || rows.length === 0) return;
|
|
8687
|
+
const cols = 2;
|
|
8688
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8689
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8690
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8691
|
+
const placeholders = chunk.map(() => "(?, ?)").join(", ");
|
|
8692
|
+
const stmt = this.stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders}`);
|
|
8693
|
+
const binds = [];
|
|
8694
|
+
for (const r of chunk) binds.push(r.id, r.text);
|
|
8695
|
+
stmt.run(...binds);
|
|
8696
|
+
}
|
|
8697
|
+
}
|
|
8698
|
+
bulkInsertRefs(refs) {
|
|
8699
|
+
if (refs.length === 0) return;
|
|
8700
|
+
const cols = 5;
|
|
8701
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8702
|
+
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
8703
|
+
const chunk = refs.slice(i, i + chunkSize);
|
|
8704
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
8705
|
+
const stmt = this.stmt(
|
|
8706
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line) VALUES ${placeholders}`
|
|
8707
|
+
);
|
|
8708
|
+
const binds = [];
|
|
8709
|
+
for (const ref of chunk) {
|
|
8710
|
+
binds.push(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8711
|
+
}
|
|
8712
|
+
stmt.run(...binds);
|
|
8713
|
+
}
|
|
8476
8714
|
}
|
|
8477
8715
|
// ─── Symbol CRUD ─────────────────────────────────────────────────────────────
|
|
8478
8716
|
/**
|
|
8479
8717
|
* Insert symbols, assigning IDs atomically inside `BEGIN IMMEDIATE` /
|
|
8480
|
-
* `COMMIT`.
|
|
8481
|
-
*
|
|
8482
|
-
* processes index concurrently (each would see a different `MAX(id)` and
|
|
8483
|
-
* neither can insert with the other's IDs).
|
|
8718
|
+
* `COMMIT`. Id ranges come from the `next_symbol_id` metadata counter
|
|
8719
|
+
* (O(1)); multi-row INSERT amortizes bind overhead for large files.
|
|
8484
8720
|
*
|
|
8485
8721
|
* @returns The symbols array with `id` fields populated so the caller can
|
|
8486
8722
|
* use them for refs without re-reading from the DB.
|
|
8487
8723
|
*/
|
|
8488
8724
|
insertSymbols(symbols) {
|
|
8725
|
+
this.invalidateBm25();
|
|
8489
8726
|
return this.runWithRetry(() => {
|
|
8490
8727
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8491
8728
|
try {
|
|
8492
|
-
|
|
8493
|
-
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
8494
|
-
const stmt = this.db.prepare(
|
|
8495
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8496
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8497
|
-
);
|
|
8498
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
8729
|
+
let nextId = this.allocateSymbolIds(symbols.length);
|
|
8499
8730
|
const result = [];
|
|
8731
|
+
const bulk = [];
|
|
8732
|
+
const ftsRows = [];
|
|
8500
8733
|
for (const s of symbols) {
|
|
8501
8734
|
const id = nextId++;
|
|
8502
|
-
|
|
8735
|
+
bulk.push({
|
|
8503
8736
|
id,
|
|
8504
|
-
s.lang,
|
|
8505
|
-
s.kind,
|
|
8506
|
-
s.name,
|
|
8507
|
-
s.file,
|
|
8508
|
-
s.line,
|
|
8509
|
-
s.col,
|
|
8510
|
-
s.signature,
|
|
8511
|
-
s.docComment,
|
|
8512
|
-
s.scope,
|
|
8513
|
-
s.text
|
|
8514
|
-
|
|
8515
|
-
)
|
|
8516
|
-
|
|
8737
|
+
lang: s.lang,
|
|
8738
|
+
kind: s.kind,
|
|
8739
|
+
name: s.name,
|
|
8740
|
+
file: s.file,
|
|
8741
|
+
line: s.line,
|
|
8742
|
+
col: s.col,
|
|
8743
|
+
signature: s.signature,
|
|
8744
|
+
docComment: s.docComment,
|
|
8745
|
+
scope: s.scope,
|
|
8746
|
+
text: s.text
|
|
8747
|
+
});
|
|
8748
|
+
if (this.ftsAvailable) {
|
|
8749
|
+
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
8750
|
+
}
|
|
8517
8751
|
result.push({ ...s, id });
|
|
8518
8752
|
}
|
|
8753
|
+
this.bulkInsertSymbols(bulk);
|
|
8754
|
+
this.bulkInsertFts(ftsRows);
|
|
8519
8755
|
this.db.exec("COMMIT");
|
|
8520
8756
|
return result;
|
|
8521
8757
|
} catch (err) {
|
|
@@ -8525,6 +8761,7 @@ var IndexStore = class _IndexStore {
|
|
|
8525
8761
|
});
|
|
8526
8762
|
}
|
|
8527
8763
|
deleteSymbolsForFile(file) {
|
|
8764
|
+
this.invalidateBm25();
|
|
8528
8765
|
this.runWithRetry(() => {
|
|
8529
8766
|
if (this.ftsAvailable) {
|
|
8530
8767
|
this.stmt(
|
|
@@ -8540,17 +8777,20 @@ var IndexStore = class _IndexStore {
|
|
|
8540
8777
|
* dropped the `files` row, leaving its symbols orphaned but still searchable.
|
|
8541
8778
|
*/
|
|
8542
8779
|
deleteFile(file) {
|
|
8780
|
+
this.invalidateBm25();
|
|
8543
8781
|
this.runWithRetry(() => {
|
|
8544
8782
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8545
8783
|
try {
|
|
8546
8784
|
if (this.ftsAvailable) {
|
|
8547
|
-
this.
|
|
8785
|
+
this.stmt(
|
|
8548
8786
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8549
8787
|
).run(file);
|
|
8550
8788
|
}
|
|
8551
|
-
this.
|
|
8552
|
-
|
|
8553
|
-
|
|
8789
|
+
this.stmt(
|
|
8790
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8791
|
+
).run(file);
|
|
8792
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8793
|
+
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
8554
8794
|
this.db.exec("COMMIT");
|
|
8555
8795
|
} catch (err) {
|
|
8556
8796
|
this.db.exec("ROLLBACK");
|
|
@@ -8587,7 +8827,7 @@ var IndexStore = class _IndexStore {
|
|
|
8587
8827
|
};
|
|
8588
8828
|
}
|
|
8589
8829
|
getAllFileMetas() {
|
|
8590
|
-
return this.
|
|
8830
|
+
return this.stmt("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8591
8831
|
file: r.file,
|
|
8592
8832
|
lang: r.lang,
|
|
8593
8833
|
mtimeMs: r.mtime_ms,
|
|
@@ -8596,7 +8836,32 @@ var IndexStore = class _IndexStore {
|
|
|
8596
8836
|
}));
|
|
8597
8837
|
}
|
|
8598
8838
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8599
|
-
search(query, filter) {
|
|
8839
|
+
search(query, filter, opts) {
|
|
8840
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8841
|
+
if (built === null) return [];
|
|
8842
|
+
const { where, values } = built;
|
|
8843
|
+
const limit = typeof opts?.limit === "number" && Number.isFinite(opts.limit) ? Math.max(0, Math.trunc(opts.limit)) : void 0;
|
|
8844
|
+
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
8845
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
|
|
8846
|
+
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
8847
|
+
const rows = this.stmt(sql).all(...binds);
|
|
8848
|
+
return rows.map((r) => ({
|
|
8849
|
+
id: r.id,
|
|
8850
|
+
lang: r.lang,
|
|
8851
|
+
kind: r.kind,
|
|
8852
|
+
name: r.name,
|
|
8853
|
+
file: r.file,
|
|
8854
|
+
line: r.line,
|
|
8855
|
+
col: r.col,
|
|
8856
|
+
signature: r.signature,
|
|
8857
|
+
docComment: r.doc_comment,
|
|
8858
|
+
score: 0,
|
|
8859
|
+
snippet: "",
|
|
8860
|
+
lspKind: filter?.lspKind
|
|
8861
|
+
}));
|
|
8862
|
+
}
|
|
8863
|
+
/** Shared WHERE builder for {@link search} / empty-query ranked totals. */
|
|
8864
|
+
buildSearchWhere(query, filter) {
|
|
8600
8865
|
const conditions = [];
|
|
8601
8866
|
const values = [];
|
|
8602
8867
|
let effectiveKind = filter?.kind;
|
|
@@ -8605,7 +8870,7 @@ var IndexStore = class _IndexStore {
|
|
|
8605
8870
|
if (mapped !== null) {
|
|
8606
8871
|
effectiveKind = mapped;
|
|
8607
8872
|
} else {
|
|
8608
|
-
return
|
|
8873
|
+
return null;
|
|
8609
8874
|
}
|
|
8610
8875
|
}
|
|
8611
8876
|
if (effectiveKind) {
|
|
@@ -8627,23 +8892,15 @@ var IndexStore = class _IndexStore {
|
|
|
8627
8892
|
for (const t of tokens) values.push(`%${t}%`);
|
|
8628
8893
|
}
|
|
8629
8894
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
line: r.line,
|
|
8640
|
-
col: r.col,
|
|
8641
|
-
signature: r.signature,
|
|
8642
|
-
docComment: r.doc_comment,
|
|
8643
|
-
score: 0,
|
|
8644
|
-
snippet: "",
|
|
8645
|
-
lspKind: filter?.lspKind
|
|
8646
|
-
}));
|
|
8895
|
+
return { where, values };
|
|
8896
|
+
}
|
|
8897
|
+
countSearch(query, filter) {
|
|
8898
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8899
|
+
if (built === null) return 0;
|
|
8900
|
+
const row = this.stmt(`SELECT COUNT(*) AS n FROM symbols ${built.where}`).get(
|
|
8901
|
+
...built.values
|
|
8902
|
+
);
|
|
8903
|
+
return Number(row?.n ?? 0);
|
|
8647
8904
|
}
|
|
8648
8905
|
/**
|
|
8649
8906
|
* Ranked search — the one-stop query the codebase-search tool and plug-lsp
|
|
@@ -8684,12 +8941,12 @@ var IndexStore = class _IndexStore {
|
|
|
8684
8941
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8685
8942
|
}
|
|
8686
8943
|
const where = conditions.join(" AND ");
|
|
8687
|
-
const countRows = this.
|
|
8944
|
+
const countRows = this.stmt(
|
|
8688
8945
|
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8689
8946
|
).all(...values);
|
|
8690
8947
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8691
8948
|
if (total === 0) return { results: [], total: 0 };
|
|
8692
|
-
const rows = this.
|
|
8949
|
+
const rows = this.stmt(
|
|
8693
8950
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
8694
8951
|
-bm25(symbols_fts) AS score,
|
|
8695
8952
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
@@ -8722,20 +8979,60 @@ var IndexStore = class _IndexStore {
|
|
|
8722
8979
|
total
|
|
8723
8980
|
};
|
|
8724
8981
|
}
|
|
8982
|
+
/**
|
|
8983
|
+
* Invalidate the cached BM25 index.
|
|
8984
|
+
*
|
|
8985
|
+
* **Contract: every method that mutates `symbols` MUST call this before
|
|
8986
|
+
* returning.** (`refs` mutations do not affect the BM25 fallback because
|
|
8987
|
+
* the corpus is built from `symbols.text` via `getAllIndexable()` and the
|
|
8988
|
+
* BM25 score is filtered by the LIKE-selected candidate set in
|
|
8989
|
+
* `searchRankedFallback`.) Today the call sites are `repairDrift`,
|
|
8990
|
+
* `insertSymbols`, `deleteSymbolsForFile`, `deleteFile`, `clearAll`, and
|
|
8991
|
+
* `commitBatch`. A future mutation that adds a new write path (e.g.
|
|
8992
|
+
* `renameFile`, `updateSignature`) MUST also call this — otherwise the
|
|
8993
|
+
* FTS5-unavailable fallback will serve stale search results. The
|
|
8994
|
+
* `close()` reset at L1820-1821 tears the cache down on store shutdown,
|
|
8995
|
+
* which is the only legitimate place that flips the flag outside this
|
|
8996
|
+
* helper.
|
|
8997
|
+
*
|
|
8998
|
+
* Called *before* `runWithRetry` on purpose: if the write fails all
|
|
8999
|
+
* retries the flag stays set, forcing a rebuild on the next search rather
|
|
9000
|
+
* than trusting a cache that may not reflect the intended mutation.
|
|
9001
|
+
* Do not move this inside the retry closure.
|
|
9002
|
+
*/
|
|
9003
|
+
invalidateBm25() {
|
|
9004
|
+
this.bm25Dirty = true;
|
|
9005
|
+
this.bm25Cache = null;
|
|
9006
|
+
}
|
|
9007
|
+
/**
|
|
9008
|
+
* Return the cached full-corpus BM25 index, rebuilding it only when the
|
|
9009
|
+
* symbols table has been mutated since the last build. The full-corpus IDF
|
|
9010
|
+
* is more correct than the old per-query candidate-subset IDF, and the
|
|
9011
|
+
* amortized build cost drops from O(symbols × tokens) per search to once
|
|
9012
|
+
* per write batch.
|
|
9013
|
+
*
|
|
9014
|
+
* Note: the first call after a long idle (or on a freshly opened store)
|
|
9015
|
+
* pays the full corpus rebuild synchronously on the search path. For a
|
|
9016
|
+
* 5 500+ symbol corpus this is a visible one-time latency spike.
|
|
9017
|
+
*/
|
|
9018
|
+
getOrBuildBm25() {
|
|
9019
|
+
if (this.bm25Cache && !this.bm25Dirty) return this.bm25Cache;
|
|
9020
|
+
const docs = this.getAllIndexable();
|
|
9021
|
+
this.bm25Cache = buildBm25Index(docs);
|
|
9022
|
+
this.bm25Dirty = false;
|
|
9023
|
+
return this.bm25Cache;
|
|
9024
|
+
}
|
|
8725
9025
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
8726
9026
|
searchRankedFallback(query, filter, limit) {
|
|
8727
|
-
const candidates = this.search(query, filter);
|
|
8728
|
-
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8729
9027
|
if (!query.trim()) {
|
|
8730
|
-
|
|
9028
|
+
const total = this.countSearch(query, filter);
|
|
9029
|
+
if (total === 0) return { results: [], total: 0 };
|
|
9030
|
+
return { results: this.search(query, filter, { limit }), total };
|
|
8731
9031
|
}
|
|
9032
|
+
const candidates = this.search(query, filter);
|
|
9033
|
+
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8732
9034
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
8733
|
-
const bm25 =
|
|
8734
|
-
candidates.map((c) => ({
|
|
8735
|
-
id: c.id,
|
|
8736
|
-
text: buildIndexableText(c.name, c.signature, c.docComment)
|
|
8737
|
-
}))
|
|
8738
|
-
);
|
|
9035
|
+
const bm25 = this.getOrBuildBm25();
|
|
8739
9036
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8740
9037
|
const q = query.trim().toLowerCase();
|
|
8741
9038
|
const rank = (id) => {
|
|
@@ -8777,7 +9074,7 @@ var IndexStore = class _IndexStore {
|
|
|
8777
9074
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8778
9075
|
getStats() {
|
|
8779
9076
|
const sizeBytes = this.sizeBytes();
|
|
8780
|
-
const lastRows = this.
|
|
9077
|
+
const lastRows = this.stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8781
9078
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8782
9079
|
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
8783
9080
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
@@ -8802,19 +9099,20 @@ var IndexStore = class _IndexStore {
|
|
|
8802
9099
|
}
|
|
8803
9100
|
setLastIndexed(ts2) {
|
|
8804
9101
|
this.runWithRetry(() => {
|
|
8805
|
-
this.
|
|
9102
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8806
9103
|
});
|
|
8807
9104
|
}
|
|
8808
9105
|
getMetadata(key) {
|
|
8809
|
-
const rows = this.
|
|
9106
|
+
const rows = this.stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8810
9107
|
return rows[0]?.value;
|
|
8811
9108
|
}
|
|
8812
9109
|
setMetadata(key, value) {
|
|
8813
9110
|
this.runWithRetry(() => {
|
|
8814
|
-
this.
|
|
9111
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8815
9112
|
});
|
|
8816
9113
|
}
|
|
8817
9114
|
clearAll() {
|
|
9115
|
+
this.invalidateBm25();
|
|
8818
9116
|
this.runWithRetry(() => {
|
|
8819
9117
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8820
9118
|
try {
|
|
@@ -8841,13 +9139,7 @@ var IndexStore = class _IndexStore {
|
|
|
8841
9139
|
this.runWithRetry(() => {
|
|
8842
9140
|
this.stmt("DELETE FROM refs WHERE from_id = ?").run(fromId);
|
|
8843
9141
|
if (refs.length === 0) return;
|
|
8844
|
-
|
|
8845
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
8846
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
8847
|
-
);
|
|
8848
|
-
for (const ref of refs) {
|
|
8849
|
-
stmt.run(fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8850
|
-
}
|
|
9142
|
+
this.bulkInsertRefs(refs.map((ref) => ({ ...ref, fromId })));
|
|
8851
9143
|
});
|
|
8852
9144
|
}
|
|
8853
9145
|
/**
|
|
@@ -8864,13 +9156,7 @@ var IndexStore = class _IndexStore {
|
|
|
8864
9156
|
insertRefsBatch(refs) {
|
|
8865
9157
|
if (refs.length === 0) return;
|
|
8866
9158
|
this.runWithRetry(() => {
|
|
8867
|
-
|
|
8868
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line)
|
|
8869
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
8870
|
-
);
|
|
8871
|
-
for (const ref of refs) {
|
|
8872
|
-
stmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8873
|
-
}
|
|
9159
|
+
this.bulkInsertRefs(refs);
|
|
8874
9160
|
});
|
|
8875
9161
|
}
|
|
8876
9162
|
/**
|
|
@@ -8895,65 +9181,61 @@ var IndexStore = class _IndexStore {
|
|
|
8895
9181
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
8896
9182
|
return [];
|
|
8897
9183
|
}
|
|
9184
|
+
this.invalidateBm25();
|
|
8898
9185
|
return this.runWithRetry(() => {
|
|
8899
9186
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8900
9187
|
try {
|
|
8901
9188
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
8902
9189
|
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
8903
9190
|
if (this.ftsAvailable) {
|
|
8904
|
-
this.
|
|
9191
|
+
this.stmt(
|
|
8905
9192
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8906
9193
|
).run(...options.deleteForFiles);
|
|
8907
9194
|
}
|
|
8908
|
-
this.
|
|
9195
|
+
this.stmt(
|
|
8909
9196
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8910
9197
|
).run(...options.deleteForFiles);
|
|
8911
|
-
this.
|
|
9198
|
+
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
8912
9199
|
}
|
|
8913
|
-
const
|
|
8914
|
-
let nextId = (
|
|
8915
|
-
const symStmt = this.db.prepare(
|
|
8916
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8917
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8918
|
-
);
|
|
8919
|
-
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
9200
|
+
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
9201
|
+
let nextId = this.allocateSymbolIds(totalSymbols);
|
|
8920
9202
|
const allInserted = [];
|
|
8921
9203
|
const refsToInsert = [];
|
|
9204
|
+
const bulkSyms = [];
|
|
9205
|
+
const ftsRows = [];
|
|
8922
9206
|
for (const entry of entries) {
|
|
8923
9207
|
const insertedForEntry = [];
|
|
8924
9208
|
for (const s of entry.symbols) {
|
|
8925
9209
|
const id = nextId++;
|
|
8926
|
-
|
|
9210
|
+
bulkSyms.push({
|
|
8927
9211
|
id,
|
|
8928
|
-
s.lang,
|
|
8929
|
-
s.kind,
|
|
8930
|
-
s.name,
|
|
8931
|
-
s.file,
|
|
8932
|
-
s.line,
|
|
8933
|
-
s.col,
|
|
8934
|
-
s.signature,
|
|
8935
|
-
s.docComment,
|
|
8936
|
-
s.scope,
|
|
8937
|
-
s.text
|
|
8938
|
-
|
|
8939
|
-
)
|
|
8940
|
-
|
|
9212
|
+
lang: s.lang,
|
|
9213
|
+
kind: s.kind,
|
|
9214
|
+
name: s.name,
|
|
9215
|
+
file: s.file,
|
|
9216
|
+
line: s.line,
|
|
9217
|
+
col: s.col,
|
|
9218
|
+
signature: s.signature,
|
|
9219
|
+
docComment: s.docComment,
|
|
9220
|
+
scope: s.scope,
|
|
9221
|
+
text: s.text
|
|
9222
|
+
});
|
|
9223
|
+
if (this.ftsAvailable) {
|
|
9224
|
+
ftsRows.push({
|
|
9225
|
+
id,
|
|
9226
|
+
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
9227
|
+
});
|
|
9228
|
+
}
|
|
8941
9229
|
const inserted = { ...s, id };
|
|
8942
9230
|
allInserted.push(inserted);
|
|
8943
9231
|
insertedForEntry.push(inserted);
|
|
8944
9232
|
}
|
|
8945
9233
|
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8946
9234
|
}
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
);
|
|
8952
|
-
for (const ref of refsToInsert) {
|
|
8953
|
-
refStmt.run(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8954
|
-
}
|
|
8955
|
-
}
|
|
8956
|
-
const upsertStmt = this.db.prepare(
|
|
9235
|
+
this.bulkInsertSymbols(bulkSyms);
|
|
9236
|
+
this.bulkInsertFts(ftsRows);
|
|
9237
|
+
this.bulkInsertRefs(refsToInsert);
|
|
9238
|
+
const upsertStmt = this.stmt(
|
|
8957
9239
|
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
8958
9240
|
VALUES (?, ?, ?, ?, ?)
|
|
8959
9241
|
ON CONFLICT(file) DO UPDATE SET
|
|
@@ -8980,7 +9262,7 @@ var IndexStore = class _IndexStore {
|
|
|
8980
9262
|
*/
|
|
8981
9263
|
deleteRefsForFile(file) {
|
|
8982
9264
|
this.runWithRetry(() => {
|
|
8983
|
-
this.
|
|
9265
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8984
9266
|
});
|
|
8985
9267
|
}
|
|
8986
9268
|
/**
|
|
@@ -8993,20 +9275,75 @@ var IndexStore = class _IndexStore {
|
|
|
8993
9275
|
*/
|
|
8994
9276
|
resolveRefs() {
|
|
8995
9277
|
return this.runWithRetry(() => {
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9278
|
+
try {
|
|
9279
|
+
const result = this.stmt(
|
|
9280
|
+
`UPDATE refs
|
|
9281
|
+
SET to_id = s.id
|
|
9282
|
+
FROM (
|
|
9283
|
+
SELECT name, MIN(id) AS id FROM symbols GROUP BY name
|
|
9284
|
+
) AS s
|
|
9285
|
+
WHERE refs.to_id IS NULL
|
|
9286
|
+
AND refs.to_name IS NOT NULL
|
|
9287
|
+
AND refs.to_name = s.name`
|
|
9288
|
+
).run();
|
|
9289
|
+
return result.changes ?? 0;
|
|
9290
|
+
} catch {
|
|
9291
|
+
const result = this.stmt(
|
|
9292
|
+
`UPDATE refs SET to_id = (
|
|
9293
|
+
SELECT id FROM symbols WHERE name = refs.to_name LIMIT 1
|
|
9294
|
+
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
9295
|
+
AND to_name IN (SELECT name FROM symbols)`
|
|
9296
|
+
).run();
|
|
9297
|
+
return result.changes ?? 0;
|
|
9298
|
+
}
|
|
9003
9299
|
});
|
|
9004
9300
|
}
|
|
9301
|
+
/**
|
|
9302
|
+
* Clear symbols/refs for a file and mark it as indexed with zero symbols.
|
|
9303
|
+
* Used by the indexer for empty-parse results so three writes share one txn.
|
|
9304
|
+
*/
|
|
9305
|
+
replaceEmptyFile(meta) {
|
|
9306
|
+
this.invalidateBm25();
|
|
9307
|
+
this.runWithRetry(() => {
|
|
9308
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
9309
|
+
try {
|
|
9310
|
+
if (this.ftsAvailable) {
|
|
9311
|
+
this.stmt(
|
|
9312
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
9313
|
+
).run(meta.file);
|
|
9314
|
+
}
|
|
9315
|
+
this.stmt(
|
|
9316
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
9317
|
+
).run(meta.file);
|
|
9318
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
9319
|
+
this.stmt(
|
|
9320
|
+
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
9321
|
+
VALUES (?, ?, ?, ?, ?)
|
|
9322
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
9323
|
+
lang = excluded.lang,
|
|
9324
|
+
mtime_ms = excluded.mtime_ms,
|
|
9325
|
+
symbol_count = excluded.symbol_count,
|
|
9326
|
+
last_indexed = excluded.last_indexed`
|
|
9327
|
+
).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
|
|
9328
|
+
this.db.exec("COMMIT");
|
|
9329
|
+
} catch (err) {
|
|
9330
|
+
this.db.exec("ROLLBACK");
|
|
9331
|
+
throw err;
|
|
9332
|
+
}
|
|
9333
|
+
});
|
|
9334
|
+
}
|
|
9335
|
+
/** Best-effort query planner refresh after a large reindex. */
|
|
9336
|
+
optimize() {
|
|
9337
|
+
try {
|
|
9338
|
+
this.db.exec("PRAGMA optimize");
|
|
9339
|
+
} catch {
|
|
9340
|
+
}
|
|
9341
|
+
}
|
|
9005
9342
|
/**
|
|
9006
9343
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
9007
9344
|
*/
|
|
9008
9345
|
findRefsTo(symbolId) {
|
|
9009
|
-
return this.
|
|
9346
|
+
return this.stmt(
|
|
9010
9347
|
"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 = ?)"
|
|
9011
9348
|
).all(symbolId, symbolId).map((r) => ({
|
|
9012
9349
|
id: r.id,
|
|
@@ -9021,7 +9358,7 @@ var IndexStore = class _IndexStore {
|
|
|
9021
9358
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
9022
9359
|
*/
|
|
9023
9360
|
findRefsFrom(symbolId) {
|
|
9024
|
-
return this.
|
|
9361
|
+
return this.stmt("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
9025
9362
|
id: r.id,
|
|
9026
9363
|
fromId: r.from_id,
|
|
9027
9364
|
toName: r.to_name,
|
|
@@ -9094,61 +9431,77 @@ var IndexStore = class _IndexStore {
|
|
|
9094
9431
|
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
9095
9432
|
*/
|
|
9096
9433
|
getPackageGraph() {
|
|
9097
|
-
const
|
|
9434
|
+
const fileCounts = this.stmt(
|
|
9435
|
+
"SELECT file, COUNT(*) AS n FROM symbols GROUP BY file"
|
|
9436
|
+
).all();
|
|
9098
9437
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
9099
9438
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
9100
|
-
const
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
fileToPkg.set(s.file, pkg);
|
|
9104
|
-
symbolToPkg.set(s.id, pkg);
|
|
9439
|
+
for (const { file, n } of fileCounts) {
|
|
9440
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9441
|
+
fileToPkg.set(file, pkg);
|
|
9105
9442
|
const node = pkgNodes.get(pkg);
|
|
9106
9443
|
if (node) {
|
|
9107
|
-
node.symbolCount = (node.symbolCount ?? 0) +
|
|
9444
|
+
node.symbolCount = (node.symbolCount ?? 0) + n;
|
|
9108
9445
|
} else {
|
|
9109
9446
|
pkgNodes.set(pkg, {
|
|
9110
9447
|
id: `pkg:${pkg}`,
|
|
9111
9448
|
label: pkg,
|
|
9112
9449
|
kind: "package",
|
|
9113
9450
|
package: pkg,
|
|
9114
|
-
symbolCount:
|
|
9451
|
+
symbolCount: n,
|
|
9115
9452
|
fileCount: 0
|
|
9116
9453
|
});
|
|
9117
9454
|
}
|
|
9118
9455
|
}
|
|
9119
|
-
const files = this.
|
|
9456
|
+
const files = this.stmt("SELECT DISTINCT file FROM files").all();
|
|
9120
9457
|
for (const { file } of files) {
|
|
9121
9458
|
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9459
|
+
fileToPkg.set(file, pkg);
|
|
9122
9460
|
const node = pkgNodes.get(pkg);
|
|
9123
9461
|
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
9462
|
+
else {
|
|
9463
|
+
pkgNodes.set(pkg, {
|
|
9464
|
+
id: `pkg:${pkg}`,
|
|
9465
|
+
label: pkg,
|
|
9466
|
+
kind: "package",
|
|
9467
|
+
package: pkg,
|
|
9468
|
+
symbolCount: 0,
|
|
9469
|
+
fileCount: 1
|
|
9470
|
+
});
|
|
9471
|
+
}
|
|
9124
9472
|
}
|
|
9125
|
-
const refRows = this.
|
|
9126
|
-
`SELECT r.
|
|
9473
|
+
const refRows = this.stmt(
|
|
9474
|
+
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
|
|
9127
9475
|
FROM refs r
|
|
9128
|
-
|
|
9476
|
+
JOIN symbols sf ON sf.id = r.from_id
|
|
9477
|
+
JOIN symbols st ON st.id = r.to_id
|
|
9478
|
+
WHERE r.to_id IS NOT NULL AND r.call_type != 'import'
|
|
9479
|
+
GROUP BY r.call_type, sf.file, st.file`
|
|
9129
9480
|
).all();
|
|
9130
9481
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
9131
9482
|
for (const r of refRows) {
|
|
9132
|
-
|
|
9133
|
-
const
|
|
9134
|
-
|
|
9135
|
-
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
9483
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
9484
|
+
const toPkg = fileToPkg.get(r.to_file) ?? _IndexStore.derivePackage(r.to_file) ?? "(root)";
|
|
9485
|
+
if (fromPkg === toPkg) continue;
|
|
9136
9486
|
const key = `${fromPkg}\0${toPkg}`;
|
|
9137
9487
|
let e = edgeMap.get(key);
|
|
9138
9488
|
if (!e) {
|
|
9139
9489
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9140
9490
|
edgeMap.set(key, e);
|
|
9141
9491
|
}
|
|
9142
|
-
|
|
9143
|
-
e.
|
|
9492
|
+
const n = Number(r.n) || 0;
|
|
9493
|
+
e.weight += n;
|
|
9494
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
9144
9495
|
}
|
|
9145
|
-
const importRows = this.
|
|
9146
|
-
`SELECT r.
|
|
9496
|
+
const importRows = this.stmt(
|
|
9497
|
+
`SELECT r.to_name, s.file AS from_file, COUNT(*) AS n
|
|
9147
9498
|
FROM refs r
|
|
9148
|
-
|
|
9499
|
+
JOIN symbols s ON s.id = r.from_id
|
|
9500
|
+
WHERE r.call_type = 'import'
|
|
9501
|
+
GROUP BY r.to_name, s.file`
|
|
9149
9502
|
).all();
|
|
9150
9503
|
for (const r of importRows) {
|
|
9151
|
-
const fromPkg =
|
|
9504
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
9152
9505
|
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
9153
9506
|
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
9154
9507
|
const key = `${fromPkg}\0${toPkg}`;
|
|
@@ -9157,8 +9510,9 @@ var IndexStore = class _IndexStore {
|
|
|
9157
9510
|
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9158
9511
|
edgeMap.set(key, edge);
|
|
9159
9512
|
}
|
|
9160
|
-
|
|
9161
|
-
edge.
|
|
9513
|
+
const n = Number(r.n) || 0;
|
|
9514
|
+
edge.weight += n;
|
|
9515
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + n);
|
|
9162
9516
|
}
|
|
9163
9517
|
const edges = [];
|
|
9164
9518
|
for (const [key, e] of edgeMap) {
|
|
@@ -9185,12 +9539,12 @@ var IndexStore = class _IndexStore {
|
|
|
9185
9539
|
* derived from cross-file symbol references within the package.
|
|
9186
9540
|
*/
|
|
9187
9541
|
getFileGraph(packageFilter) {
|
|
9188
|
-
const allFiles = this.
|
|
9542
|
+
const allFiles = this.stmt("SELECT DISTINCT file FROM symbols").all();
|
|
9189
9543
|
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
9190
9544
|
const localFiles = new Set(pkgFilePaths);
|
|
9191
9545
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
9192
9546
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
9193
|
-
const pkgSyms = this.
|
|
9547
|
+
const pkgSyms = this.stmt(
|
|
9194
9548
|
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
9195
9549
|
).all(...pkgFilePaths);
|
|
9196
9550
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
@@ -9222,12 +9576,13 @@ var IndexStore = class _IndexStore {
|
|
|
9222
9576
|
ensureFileNode(file);
|
|
9223
9577
|
}
|
|
9224
9578
|
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
9225
|
-
const refRows = this.
|
|
9226
|
-
`SELECT r.from_id, r.to_id, r.call_type
|
|
9579
|
+
const refRows = this.stmt(
|
|
9580
|
+
`SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
|
|
9227
9581
|
FROM refs r
|
|
9228
9582
|
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
9229
9583
|
OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
|
|
9230
|
-
AND r.to_id IS NOT NULL
|
|
9584
|
+
AND r.to_id IS NOT NULL
|
|
9585
|
+
GROUP BY r.from_id, r.to_id, r.call_type`
|
|
9231
9586
|
).all(...pkgFilePaths, ...pkgFilePaths);
|
|
9232
9587
|
const knownSymIds = new Set(pkgSyms.map((s) => s.id));
|
|
9233
9588
|
const crossRefIds = /* @__PURE__ */ new Set();
|
|
@@ -9237,7 +9592,7 @@ var IndexStore = class _IndexStore {
|
|
|
9237
9592
|
}
|
|
9238
9593
|
if (crossRefIds.size > 0) {
|
|
9239
9594
|
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
9240
|
-
const extras = this.
|
|
9595
|
+
const extras = this.stmt(
|
|
9241
9596
|
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
9242
9597
|
).all(...crossRefIds);
|
|
9243
9598
|
for (const x of extras) {
|
|
@@ -9262,14 +9617,16 @@ var IndexStore = class _IndexStore {
|
|
|
9262
9617
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9263
9618
|
edgeMap.set(key, e);
|
|
9264
9619
|
}
|
|
9265
|
-
|
|
9266
|
-
e.
|
|
9620
|
+
const n = Number(r.n) || 0;
|
|
9621
|
+
e.weight += n;
|
|
9622
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
9267
9623
|
}
|
|
9268
|
-
const importRows = this.
|
|
9269
|
-
`SELECT r.from_id, r.to_name
|
|
9624
|
+
const importRows = this.stmt(
|
|
9625
|
+
`SELECT r.from_id, r.to_name, COUNT(*) AS n
|
|
9270
9626
|
FROM refs r
|
|
9271
9627
|
WHERE r.call_type = 'import'
|
|
9272
|
-
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
9628
|
+
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
9629
|
+
GROUP BY r.from_id, r.to_name`
|
|
9273
9630
|
).all(...pkgFilePaths);
|
|
9274
9631
|
for (const r of importRows) {
|
|
9275
9632
|
const fromFile = symToFile.get(r.from_id);
|
|
@@ -9284,8 +9641,9 @@ var IndexStore = class _IndexStore {
|
|
|
9284
9641
|
edge = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9285
9642
|
edgeMap.set(key, edge);
|
|
9286
9643
|
}
|
|
9287
|
-
|
|
9288
|
-
edge.
|
|
9644
|
+
const n = Number(r.n) || 0;
|
|
9645
|
+
edge.weight += n;
|
|
9646
|
+
edge.types.set("import", (edge.types.get("import") ?? 0) + n);
|
|
9289
9647
|
}
|
|
9290
9648
|
const edges = [];
|
|
9291
9649
|
for (const [key, e] of edgeMap) {
|
|
@@ -9312,7 +9670,7 @@ var IndexStore = class _IndexStore {
|
|
|
9312
9670
|
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
9313
9671
|
*/
|
|
9314
9672
|
getSymbolGraph(fileFilter) {
|
|
9315
|
-
const syms = this.
|
|
9673
|
+
const syms = this.stmt(
|
|
9316
9674
|
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
9317
9675
|
).all(fileFilter);
|
|
9318
9676
|
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
@@ -9332,17 +9690,25 @@ var IndexStore = class _IndexStore {
|
|
|
9332
9690
|
scope: s.scope,
|
|
9333
9691
|
external: s.file !== fileFilter
|
|
9334
9692
|
});
|
|
9335
|
-
const
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9693
|
+
const refRows = this.stmt(
|
|
9694
|
+
`SELECT from_id, to_id, call_type, COUNT(*) AS n
|
|
9695
|
+
FROM (
|
|
9696
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9697
|
+
FROM refs r
|
|
9698
|
+
JOIN symbols s ON s.id = r.from_id
|
|
9699
|
+
WHERE s.file = ?
|
|
9700
|
+
UNION
|
|
9701
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9702
|
+
FROM refs r
|
|
9703
|
+
JOIN symbols s ON s.id = r.to_id
|
|
9704
|
+
WHERE s.file = ?
|
|
9705
|
+
)
|
|
9706
|
+
WHERE to_id IS NOT NULL
|
|
9707
|
+
GROUP BY from_id, to_id, call_type`
|
|
9341
9708
|
).all(fileFilter, fileFilter);
|
|
9342
9709
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
9343
9710
|
for (const r of refRows) {
|
|
9344
|
-
if (r.to_id
|
|
9345
|
-
if (!symIds.has(r.from_id) && !symIds.has(r.to_id)) continue;
|
|
9711
|
+
if (r.to_id == null) continue;
|
|
9346
9712
|
relatedIds.add(r.from_id);
|
|
9347
9713
|
relatedIds.add(r.to_id);
|
|
9348
9714
|
const key = `${r.from_id}\0${r.to_id}`;
|
|
@@ -9351,8 +9717,9 @@ var IndexStore = class _IndexStore {
|
|
|
9351
9717
|
e = { weight: 0, types: /* @__PURE__ */ new Map() };
|
|
9352
9718
|
edgeMap.set(key, e);
|
|
9353
9719
|
}
|
|
9354
|
-
|
|
9355
|
-
e.
|
|
9720
|
+
const n = Number(r.n) || 0;
|
|
9721
|
+
e.weight += n;
|
|
9722
|
+
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + n);
|
|
9356
9723
|
}
|
|
9357
9724
|
const edges = [];
|
|
9358
9725
|
for (const [key, e] of edgeMap) {
|
|
@@ -9376,7 +9743,7 @@ var IndexStore = class _IndexStore {
|
|
|
9376
9743
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
9377
9744
|
if (missingIds.length > 0) {
|
|
9378
9745
|
const placeholders = missingIds.map(() => "?").join(",");
|
|
9379
|
-
const extras = this.
|
|
9746
|
+
const extras = this.stmt(
|
|
9380
9747
|
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
9381
9748
|
).all(...missingIds);
|
|
9382
9749
|
for (const s of extras) symById.set(s.id, s);
|
|
@@ -9390,6 +9757,8 @@ var IndexStore = class _IndexStore {
|
|
|
9390
9757
|
}
|
|
9391
9758
|
close() {
|
|
9392
9759
|
this.stmtCache.clear();
|
|
9760
|
+
this.bm25Dirty = true;
|
|
9761
|
+
this.bm25Cache = null;
|
|
9393
9762
|
try {
|
|
9394
9763
|
this.db.close();
|
|
9395
9764
|
} catch {
|
|
@@ -9403,14 +9772,121 @@ import { fileURLToPath } from "node:url";
|
|
|
9403
9772
|
import { Worker } from "node:worker_threads";
|
|
9404
9773
|
|
|
9405
9774
|
// src/codebase-index/indexer.ts
|
|
9406
|
-
import { expectDefined as expectDefined6 } from "@wrongstack/core";
|
|
9775
|
+
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
9407
9776
|
import * as fs14 from "node:fs/promises";
|
|
9408
|
-
import * as
|
|
9777
|
+
import * as path20 from "node:path";
|
|
9409
9778
|
import { availableParallelism } from "node:os";
|
|
9410
|
-
import {
|
|
9779
|
+
import { DEFAULT_WALK_IGNORE_DIRS, indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
|
|
9411
9780
|
|
|
9412
9781
|
// src/codebase-index/ts-parser.ts
|
|
9413
9782
|
import * as ts from "@typescript/typescript6";
|
|
9783
|
+
|
|
9784
|
+
// src/codebase-index/languages.ts
|
|
9785
|
+
import * as path14 from "node:path";
|
|
9786
|
+
var EXT_TO_LANG = {
|
|
9787
|
+
// TypeScript / JavaScript (first-class TS compiler API)
|
|
9788
|
+
".ts": "ts",
|
|
9789
|
+
".mts": "ts",
|
|
9790
|
+
".cts": "ts",
|
|
9791
|
+
".tsx": "tsx",
|
|
9792
|
+
".js": "js",
|
|
9793
|
+
".mjs": "js",
|
|
9794
|
+
".cjs": "js",
|
|
9795
|
+
".jsx": "jsx",
|
|
9796
|
+
// First-class native/spawn parsers (+ regex fallback)
|
|
9797
|
+
".go": "go",
|
|
9798
|
+
".py": "py",
|
|
9799
|
+
".pyi": "py",
|
|
9800
|
+
".pyw": "py",
|
|
9801
|
+
".rs": "rs",
|
|
9802
|
+
".json": "json",
|
|
9803
|
+
".jsonc": "json",
|
|
9804
|
+
".yaml": "yaml",
|
|
9805
|
+
".yml": "yaml",
|
|
9806
|
+
// C family
|
|
9807
|
+
".c": "c",
|
|
9808
|
+
".h": "c",
|
|
9809
|
+
".cc": "cpp",
|
|
9810
|
+
".cpp": "cpp",
|
|
9811
|
+
".cxx": "cpp",
|
|
9812
|
+
".hh": "cpp",
|
|
9813
|
+
".hpp": "cpp",
|
|
9814
|
+
".hxx": "cpp",
|
|
9815
|
+
// JVM / .NET
|
|
9816
|
+
".java": "java",
|
|
9817
|
+
".cs": "csharp",
|
|
9818
|
+
".kt": "kotlin",
|
|
9819
|
+
".kts": "kotlin",
|
|
9820
|
+
".scala": "scala",
|
|
9821
|
+
".sc": "scala",
|
|
9822
|
+
// Scripting
|
|
9823
|
+
".php": "php",
|
|
9824
|
+
".rb": "ruby",
|
|
9825
|
+
".swift": "swift",
|
|
9826
|
+
".dart": "dart",
|
|
9827
|
+
".lua": "lua",
|
|
9828
|
+
".r": "r",
|
|
9829
|
+
".R": "r",
|
|
9830
|
+
".pl": "other",
|
|
9831
|
+
".pm": "other",
|
|
9832
|
+
// Systems / functional
|
|
9833
|
+
".zig": "zig",
|
|
9834
|
+
".ex": "elixir",
|
|
9835
|
+
".exs": "elixir",
|
|
9836
|
+
".hs": "haskell",
|
|
9837
|
+
".lhs": "haskell",
|
|
9838
|
+
// Shell / data / docs / web
|
|
9839
|
+
".sh": "shell",
|
|
9840
|
+
".bash": "shell",
|
|
9841
|
+
".zsh": "shell",
|
|
9842
|
+
".ps1": "shell",
|
|
9843
|
+
".sql": "sql",
|
|
9844
|
+
".md": "md",
|
|
9845
|
+
".mdx": "md",
|
|
9846
|
+
".toml": "toml",
|
|
9847
|
+
".html": "html",
|
|
9848
|
+
".htm": "html",
|
|
9849
|
+
".css": "css",
|
|
9850
|
+
".scss": "css",
|
|
9851
|
+
".less": "css",
|
|
9852
|
+
".vue": "vue",
|
|
9853
|
+
".svelte": "svelte",
|
|
9854
|
+
".proto": "proto",
|
|
9855
|
+
".graphql": "graphql",
|
|
9856
|
+
".gql": "graphql"
|
|
9857
|
+
};
|
|
9858
|
+
var INDEXABLE_EXTENSIONS = Object.freeze(
|
|
9859
|
+
[...new Set(Object.keys(EXT_TO_LANG).map((e) => e.toLowerCase()))].sort()
|
|
9860
|
+
);
|
|
9861
|
+
var SPECIAL_FILENAMES = {
|
|
9862
|
+
makefile: "other",
|
|
9863
|
+
gnumakefile: "other",
|
|
9864
|
+
dockerfile: "other",
|
|
9865
|
+
"docker-compose.yml": "yaml",
|
|
9866
|
+
"docker-compose.yaml": "yaml",
|
|
9867
|
+
"cmakelists.txt": "other",
|
|
9868
|
+
gemfile: "ruby",
|
|
9869
|
+
rakefile: "ruby",
|
|
9870
|
+
procfile: "other",
|
|
9871
|
+
justfile: "other"
|
|
9872
|
+
};
|
|
9873
|
+
function detectLang(file) {
|
|
9874
|
+
const base = path14.basename(file);
|
|
9875
|
+
const lowerBase = base.toLowerCase();
|
|
9876
|
+
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
9877
|
+
return "ts";
|
|
9878
|
+
}
|
|
9879
|
+
const special = SPECIAL_FILENAMES[lowerBase];
|
|
9880
|
+
if (special) return special;
|
|
9881
|
+
const ext = path14.extname(base).toLowerCase();
|
|
9882
|
+
if (!ext) return null;
|
|
9883
|
+
return EXT_TO_LANG[ext] ?? null;
|
|
9884
|
+
}
|
|
9885
|
+
function isIndexablePath(file) {
|
|
9886
|
+
return detectLang(file) !== null;
|
|
9887
|
+
}
|
|
9888
|
+
|
|
9889
|
+
// src/codebase-index/ts-parser.ts
|
|
9414
9890
|
var KIND_MAP = {
|
|
9415
9891
|
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
9416
9892
|
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
@@ -9437,32 +9913,6 @@ function kindOf(node) {
|
|
|
9437
9913
|
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
9438
9914
|
return KIND_MAP[node.kind] ?? null;
|
|
9439
9915
|
}
|
|
9440
|
-
function extToLang(ext) {
|
|
9441
|
-
switch (ext) {
|
|
9442
|
-
case ".ts":
|
|
9443
|
-
return "ts";
|
|
9444
|
-
case ".tsx":
|
|
9445
|
-
return "tsx";
|
|
9446
|
-
case ".js":
|
|
9447
|
-
return "js";
|
|
9448
|
-
case ".jsx":
|
|
9449
|
-
return "jsx";
|
|
9450
|
-
case ".go":
|
|
9451
|
-
return "go";
|
|
9452
|
-
case ".py":
|
|
9453
|
-
return "py";
|
|
9454
|
-
case ".rs":
|
|
9455
|
-
return "rs";
|
|
9456
|
-
case ".json":
|
|
9457
|
-
return "json";
|
|
9458
|
-
case ".yaml":
|
|
9459
|
-
return "yaml";
|
|
9460
|
-
case ".yml":
|
|
9461
|
-
return "yaml";
|
|
9462
|
-
default:
|
|
9463
|
-
return null;
|
|
9464
|
-
}
|
|
9465
|
-
}
|
|
9466
9916
|
function getSignature(printer, node, sourceFile) {
|
|
9467
9917
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
9468
9918
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
@@ -9585,22 +10035,30 @@ function deduplicateRefs(refs) {
|
|
|
9585
10035
|
return true;
|
|
9586
10036
|
});
|
|
9587
10037
|
}
|
|
9588
|
-
function detectLang(file) {
|
|
9589
|
-
const idx = file.lastIndexOf(".");
|
|
9590
|
-
if (idx < 0) return null;
|
|
9591
|
-
return extToLang(file.slice(idx));
|
|
9592
|
-
}
|
|
9593
10038
|
|
|
9594
10039
|
// src/codebase-index/go-parser.ts
|
|
9595
10040
|
init_win32_resolve();
|
|
9596
10041
|
import { spawn as spawn4 } from "node:child_process";
|
|
9597
10042
|
import * as os5 from "node:os";
|
|
9598
|
-
import * as
|
|
10043
|
+
import * as path15 from "node:path";
|
|
9599
10044
|
import * as fs10 from "node:fs/promises";
|
|
10045
|
+
|
|
10046
|
+
// src/codebase-index/spawn-gate.ts
|
|
10047
|
+
var chain = Promise.resolve();
|
|
10048
|
+
function withSpawnGate(fn) {
|
|
10049
|
+
const run = chain.then(fn, fn);
|
|
10050
|
+
chain = run.then(
|
|
10051
|
+
() => void 0,
|
|
10052
|
+
() => void 0
|
|
10053
|
+
);
|
|
10054
|
+
return run;
|
|
10055
|
+
}
|
|
10056
|
+
|
|
10057
|
+
// src/codebase-index/go-parser.ts
|
|
9600
10058
|
async function parseSymbols2(opts) {
|
|
9601
10059
|
const { file, content, lang } = opts;
|
|
9602
10060
|
try {
|
|
9603
|
-
const parsed = await syncGoParse(file, content, lang);
|
|
10061
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
9604
10062
|
if (parsed.symbols.length > 0) {
|
|
9605
10063
|
return parsed;
|
|
9606
10064
|
}
|
|
@@ -9896,11 +10354,16 @@ func formatType(t ast.Expr) string {
|
|
|
9896
10354
|
}
|
|
9897
10355
|
}
|
|
9898
10356
|
`;
|
|
10357
|
+
var _cachedGoScriptPath = null;
|
|
9899
10358
|
async function syncGoParse(filePath, content, lang) {
|
|
9900
|
-
const tmpDir = await fs10.mkdtemp(path14.join(os5.tmpdir(), "ws-go-parse-"));
|
|
9901
10359
|
try {
|
|
9902
|
-
|
|
9903
|
-
|
|
10360
|
+
let scriptPath = _cachedGoScriptPath;
|
|
10361
|
+
if (!scriptPath) {
|
|
10362
|
+
const tmpDir = await fs10.mkdtemp(path15.join(os5.tmpdir(), "ws-go-parse-"));
|
|
10363
|
+
scriptPath = path15.join(tmpDir, "parse.go");
|
|
10364
|
+
await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
10365
|
+
_cachedGoScriptPath = scriptPath;
|
|
10366
|
+
}
|
|
9904
10367
|
const goBinary = resolveWin32Command("go");
|
|
9905
10368
|
const goResult = await new Promise(
|
|
9906
10369
|
(resolve15, reject) => {
|
|
@@ -9957,24 +10420,312 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9957
10420
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
9958
10421
|
} catch {
|
|
9959
10422
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9960
|
-
} finally {
|
|
9961
|
-
await fs10.rm(tmpDir, { recursive: true, force: true });
|
|
9962
10423
|
}
|
|
9963
10424
|
}
|
|
9964
10425
|
|
|
9965
10426
|
// src/codebase-index/py-parser.ts
|
|
9966
10427
|
init_win32_resolve();
|
|
9967
|
-
import { spawn as spawn5
|
|
10428
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
9968
10429
|
import * as fs11 from "node:fs/promises";
|
|
9969
10430
|
import * as os6 from "node:os";
|
|
9970
|
-
import * as
|
|
10431
|
+
import * as path16 from "node:path";
|
|
10432
|
+
|
|
10433
|
+
// src/codebase-index/generic-parser.ts
|
|
10434
|
+
var C_LIKE = [
|
|
10435
|
+
{ re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10436
|
+
{
|
|
10437
|
+
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,
|
|
10438
|
+
kind: "function"
|
|
10439
|
+
},
|
|
10440
|
+
{ re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
|
|
10441
|
+
];
|
|
10442
|
+
var LANG_PATTERNS = {
|
|
10443
|
+
py: [
|
|
10444
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10445
|
+
{ re: /^class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10446
|
+
{ re: /^([A-Za-z_]\w*)\s*=/gm, kind: "var" }
|
|
10447
|
+
],
|
|
10448
|
+
go: [
|
|
10449
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/gm, kind: "function" },
|
|
10450
|
+
{ re: /^type\s+([A-Za-z_]\w*)\b/gm, kind: "type" },
|
|
10451
|
+
{ re: /^(?:const|var)\s+([A-Za-z_]\w*)\b/gm, kind: "const" },
|
|
10452
|
+
{ re: /^package\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10453
|
+
],
|
|
10454
|
+
rs: [
|
|
10455
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10456
|
+
{ re: /\bstruct\s+([A-Za-z_]\w*)/g, kind: "struct" },
|
|
10457
|
+
{ re: /\benum\s+([A-Za-z_]\w*)/g, kind: "enum" },
|
|
10458
|
+
{ re: /\btrait\s+([A-Za-z_]\w*)/g, kind: "trait" },
|
|
10459
|
+
{ re: /\bimpl(?:\s*<[^>]+>)?\s+([A-Za-z_]\w*)/g, kind: "impl" },
|
|
10460
|
+
{ re: /\b(?:const|static)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10461
|
+
{ re: /\bmod\s+([A-Za-z_]\w*)/g, kind: "mod" }
|
|
10462
|
+
],
|
|
10463
|
+
c: C_LIKE,
|
|
10464
|
+
cpp: C_LIKE,
|
|
10465
|
+
java: [
|
|
10466
|
+
{ re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10467
|
+
{
|
|
10468
|
+
re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10469
|
+
kind: "method"
|
|
10470
|
+
}
|
|
10471
|
+
],
|
|
10472
|
+
csharp: [
|
|
10473
|
+
{ re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10474
|
+
{ re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
|
|
10475
|
+
{
|
|
10476
|
+
re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10477
|
+
kind: "method"
|
|
10478
|
+
}
|
|
10479
|
+
],
|
|
10480
|
+
php: [
|
|
10481
|
+
{ re: /\bfunction\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10482
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10483
|
+
{ re: /\binterface\s+([A-Za-z_]\w*)/g, kind: "interface" },
|
|
10484
|
+
{ re: /\bnamespace\s+([A-Za-z_\\]+)/g, kind: "namespace" }
|
|
10485
|
+
],
|
|
10486
|
+
ruby: [
|
|
10487
|
+
{ re: /^\s*def\s+(?:self\.)?([A-Za-z_]\w*[!?]?)/gm, kind: "function" },
|
|
10488
|
+
{ re: /^\s*class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10489
|
+
{ re: /^\s*module\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10490
|
+
],
|
|
10491
|
+
swift: [
|
|
10492
|
+
{ re: /\b(?:func)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10493
|
+
{ re: /\b(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10494
|
+
],
|
|
10495
|
+
kotlin: [
|
|
10496
|
+
{ re: /\b(?:fun)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10497
|
+
{ re: /\b(?:class|interface|object|enum\s+class|data\s+class)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10498
|
+
],
|
|
10499
|
+
scala: [
|
|
10500
|
+
{ re: /\b(?:def)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10501
|
+
{ re: /\b(?:class|object|trait|enum)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10502
|
+
],
|
|
10503
|
+
shell: [
|
|
10504
|
+
{ re: /^(?:function\s+)?([A-Za-z_]\w*)\s*\(\)\s*\{/gm, kind: "function" },
|
|
10505
|
+
{ re: /^([A-Za-z_][\w]*)\s*\(\)\s*\{/gm, kind: "function" }
|
|
10506
|
+
],
|
|
10507
|
+
sql: [
|
|
10508
|
+
{ 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" }
|
|
10509
|
+
],
|
|
10510
|
+
md: [
|
|
10511
|
+
{ re: /^(#{1,6})\s+(.+)$/gm, kind: "namespace" }
|
|
10512
|
+
],
|
|
10513
|
+
toml: [
|
|
10514
|
+
{ re: /^\[([^\]]+)\]/gm, kind: "namespace" }
|
|
10515
|
+
],
|
|
10516
|
+
html: [
|
|
10517
|
+
{ re: /\bid\s*=\s*["']([^"']+)["']/gi, kind: "property" },
|
|
10518
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10519
|
+
],
|
|
10520
|
+
css: [
|
|
10521
|
+
{ re: /^\s*([.#]?[A-Za-z_][\w-]*)\s*\{/gm, kind: "type" },
|
|
10522
|
+
{ re: /@(?:keyframes|media|supports)\s+([^{\s]+)/g, kind: "namespace" }
|
|
10523
|
+
],
|
|
10524
|
+
vue: [
|
|
10525
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10526
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10527
|
+
],
|
|
10528
|
+
svelte: [
|
|
10529
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10530
|
+
],
|
|
10531
|
+
dart: [
|
|
10532
|
+
{ re: /\b(?:class|enum|mixin|extension)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10533
|
+
{ re: /\b([A-Za-z_]\w*)\s*\([^;]*\)\s*(?:async\s*)?\{/g, kind: "function" }
|
|
10534
|
+
],
|
|
10535
|
+
lua: [
|
|
10536
|
+
{ re: /\bfunction\s+([A-Za-z_.:]\w*)/g, kind: "function" },
|
|
10537
|
+
{ re: /\blocal\s+function\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10538
|
+
],
|
|
10539
|
+
r: [
|
|
10540
|
+
{ re: /([A-Za-z.]\w*)\s*<-\s*function\s*\(/g, kind: "function" },
|
|
10541
|
+
{ re: /([A-Za-z.]\w*)\s*=\s*function\s*\(/g, kind: "function" }
|
|
10542
|
+
],
|
|
10543
|
+
proto: [
|
|
10544
|
+
{ re: /\b(?:message|service|enum)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10545
|
+
{ re: /\brpc\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10546
|
+
],
|
|
10547
|
+
graphql: [
|
|
10548
|
+
{ re: /\b(?:type|interface|enum|input|union|scalar)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10549
|
+
{ re: /\b(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10550
|
+
],
|
|
10551
|
+
zig: [
|
|
10552
|
+
{ re: /\b(?:fn|pub\s+fn)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10553
|
+
{ re: /\b(?:const|var)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10554
|
+
{ re: /\b(?:struct|enum|union)\s*\{/g, kind: "type" }
|
|
10555
|
+
],
|
|
10556
|
+
elixir: [
|
|
10557
|
+
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
10558
|
+
{ re: /\bdefmodule\s+([A-Za-z_.]\w*)/g, kind: "namespace" }
|
|
10559
|
+
],
|
|
10560
|
+
haskell: [
|
|
10561
|
+
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
10562
|
+
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10563
|
+
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
10564
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10565
|
+
],
|
|
10566
|
+
other: [
|
|
10567
|
+
{ re: /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10568
|
+
{ re: /^(?:export\s+)?class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10569
|
+
{ re: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)/gm, kind: "const" },
|
|
10570
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10571
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10572
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10573
|
+
{ re: /^(?:target|PHONY)\s*:/gm, kind: "namespace" }
|
|
10574
|
+
]
|
|
10575
|
+
};
|
|
10576
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
10577
|
+
"if",
|
|
10578
|
+
"else",
|
|
10579
|
+
"for",
|
|
10580
|
+
"while",
|
|
10581
|
+
"switch",
|
|
10582
|
+
"case",
|
|
10583
|
+
"return",
|
|
10584
|
+
"break",
|
|
10585
|
+
"continue",
|
|
10586
|
+
"new",
|
|
10587
|
+
"delete",
|
|
10588
|
+
"typeof",
|
|
10589
|
+
"instanceof",
|
|
10590
|
+
"void",
|
|
10591
|
+
"null",
|
|
10592
|
+
"true",
|
|
10593
|
+
"false",
|
|
10594
|
+
"this",
|
|
10595
|
+
"super",
|
|
10596
|
+
"import",
|
|
10597
|
+
"export",
|
|
10598
|
+
"from",
|
|
10599
|
+
"as",
|
|
10600
|
+
"default",
|
|
10601
|
+
"public",
|
|
10602
|
+
"private",
|
|
10603
|
+
"protected",
|
|
10604
|
+
"static",
|
|
10605
|
+
"final",
|
|
10606
|
+
"class",
|
|
10607
|
+
"struct",
|
|
10608
|
+
"enum",
|
|
10609
|
+
"interface",
|
|
10610
|
+
"function",
|
|
10611
|
+
"def",
|
|
10612
|
+
"fn",
|
|
10613
|
+
"func",
|
|
10614
|
+
"var",
|
|
10615
|
+
"let",
|
|
10616
|
+
"const",
|
|
10617
|
+
"package",
|
|
10618
|
+
"namespace",
|
|
10619
|
+
"module",
|
|
10620
|
+
"using",
|
|
10621
|
+
"include",
|
|
10622
|
+
"require",
|
|
10623
|
+
"select",
|
|
10624
|
+
"from",
|
|
10625
|
+
"where",
|
|
10626
|
+
"and",
|
|
10627
|
+
"or",
|
|
10628
|
+
"not",
|
|
10629
|
+
"in",
|
|
10630
|
+
"is",
|
|
10631
|
+
"try",
|
|
10632
|
+
"catch",
|
|
10633
|
+
"finally",
|
|
10634
|
+
"throw",
|
|
10635
|
+
"async",
|
|
10636
|
+
"await",
|
|
10637
|
+
"yield"
|
|
10638
|
+
]);
|
|
10639
|
+
function patternsFor(lang) {
|
|
10640
|
+
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
10641
|
+
}
|
|
10642
|
+
function looksBinary(content) {
|
|
10643
|
+
if (content.includes("\0")) return true;
|
|
10644
|
+
const sample = content.slice(0, 2048);
|
|
10645
|
+
if (sample.length === 0) return false;
|
|
10646
|
+
let bad = 0;
|
|
10647
|
+
for (let i = 0; i < sample.length; i++) {
|
|
10648
|
+
const c = sample.charCodeAt(i);
|
|
10649
|
+
if (c === 9 || c === 10 || c === 13) continue;
|
|
10650
|
+
if (c < 32 || c === 127) bad++;
|
|
10651
|
+
}
|
|
10652
|
+
return bad / sample.length > 0.1;
|
|
10653
|
+
}
|
|
10654
|
+
function lineColAt(content, index) {
|
|
10655
|
+
let line = 1;
|
|
10656
|
+
let lastNl = -1;
|
|
10657
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
10658
|
+
if (content.charCodeAt(i) === 10) {
|
|
10659
|
+
line++;
|
|
10660
|
+
lastNl = i;
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10663
|
+
return { line, col: index - lastNl };
|
|
10664
|
+
}
|
|
10665
|
+
var GENERIC_MAX_SYMBOLS_DEFAULT = 500;
|
|
10666
|
+
var GENERIC_MAX_FILE_CHARS = 512 * 1024;
|
|
10667
|
+
function parseGeneric2(opts) {
|
|
10668
|
+
const { file, lang } = opts;
|
|
10669
|
+
const maxSymbols = opts.maxSymbols ?? GENERIC_MAX_SYMBOLS_DEFAULT;
|
|
10670
|
+
const mtimeMs = Date.now();
|
|
10671
|
+
if (!opts.content || looksBinary(opts.content)) {
|
|
10672
|
+
return { file, lang, symbols: [], mtimeMs };
|
|
10673
|
+
}
|
|
10674
|
+
const content = opts.content.length > GENERIC_MAX_FILE_CHARS ? opts.content.slice(0, GENERIC_MAX_FILE_CHARS) : opts.content;
|
|
10675
|
+
const patterns = patternsFor(lang);
|
|
10676
|
+
const symbols = [];
|
|
10677
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10678
|
+
for (const pattern of patterns) {
|
|
10679
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
10680
|
+
re.lastIndex = 0;
|
|
10681
|
+
for (const match of content.matchAll(re)) {
|
|
10682
|
+
if (symbols.length >= maxSymbols) break;
|
|
10683
|
+
let name = (match[1] ?? match[2] ?? "").trim();
|
|
10684
|
+
if (lang === "md" && match[2]) name = match[2].trim();
|
|
10685
|
+
if (!name || name.length > 200) continue;
|
|
10686
|
+
name = name.replace(/^#+\s*/, "").replace(/["'`]/g, "");
|
|
10687
|
+
if (!name || KEYWORDS.has(name.toLowerCase())) continue;
|
|
10688
|
+
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
10689
|
+
continue;
|
|
10690
|
+
}
|
|
10691
|
+
const { line, col } = lineColAt(content, match.index);
|
|
10692
|
+
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
10693
|
+
if (seen.has(key)) continue;
|
|
10694
|
+
seen.add(key);
|
|
10695
|
+
const nl = content.indexOf("\n", match.index);
|
|
10696
|
+
const lineText = content.slice(match.index, nl === -1 ? content.length : nl);
|
|
10697
|
+
const signature = (lineText || name).trim().slice(0, 500);
|
|
10698
|
+
symbols.push({
|
|
10699
|
+
id: 0,
|
|
10700
|
+
lang,
|
|
10701
|
+
kind: lang === "md" ? "namespace" : pattern.kind,
|
|
10702
|
+
name: name.slice(0, 200),
|
|
10703
|
+
file,
|
|
10704
|
+
line,
|
|
10705
|
+
col,
|
|
10706
|
+
signature,
|
|
10707
|
+
docComment: "",
|
|
10708
|
+
scope: "",
|
|
10709
|
+
text: `${name} ${signature}`.trim().slice(0, 1e3)
|
|
10710
|
+
});
|
|
10711
|
+
}
|
|
10712
|
+
if (symbols.length >= maxSymbols) break;
|
|
10713
|
+
}
|
|
10714
|
+
return { file, lang, symbols, mtimeMs };
|
|
10715
|
+
}
|
|
9971
10716
|
async function parseSymbols3(opts) {
|
|
10717
|
+
return parseGeneric2(opts);
|
|
10718
|
+
}
|
|
10719
|
+
|
|
10720
|
+
// src/codebase-index/py-parser.ts
|
|
10721
|
+
async function parseSymbols4(opts) {
|
|
9972
10722
|
const { file, content, lang } = opts;
|
|
9973
10723
|
try {
|
|
9974
|
-
|
|
10724
|
+
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
10725
|
+
if (native !== null) return native;
|
|
9975
10726
|
} catch {
|
|
9976
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
9977
10727
|
}
|
|
10728
|
+
return parseGeneric2({ file, content, lang: lang === "py" ? "py" : lang });
|
|
9978
10729
|
}
|
|
9979
10730
|
var PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
9980
10731
|
|
|
@@ -10179,20 +10930,37 @@ visitor.visit(tree)
|
|
|
10179
10930
|
|
|
10180
10931
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
10181
10932
|
`;
|
|
10182
|
-
function resolvePython() {
|
|
10933
|
+
async function resolvePython() {
|
|
10183
10934
|
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
10184
10935
|
for (const name of candidates) {
|
|
10185
10936
|
const resolved = resolveWin32Command(name);
|
|
10186
|
-
|
|
10187
|
-
stdio: "pipe",
|
|
10188
|
-
timeout: 5e3
|
|
10189
|
-
});
|
|
10190
|
-
if (result.error) continue;
|
|
10191
|
-
if (result.status !== 0) continue;
|
|
10937
|
+
if (!await commandIsAvailable(resolved)) continue;
|
|
10192
10938
|
return resolved;
|
|
10193
10939
|
}
|
|
10194
10940
|
return null;
|
|
10195
10941
|
}
|
|
10942
|
+
function commandIsAvailable(command) {
|
|
10943
|
+
return new Promise((resolve15) => {
|
|
10944
|
+
let settled = false;
|
|
10945
|
+
const proc = spawn5(command, ["--version"], {
|
|
10946
|
+
stdio: "ignore",
|
|
10947
|
+
windowsHide: true
|
|
10948
|
+
});
|
|
10949
|
+
const finish = (available) => {
|
|
10950
|
+
if (settled) return;
|
|
10951
|
+
settled = true;
|
|
10952
|
+
clearTimeout(timer);
|
|
10953
|
+
resolve15(available);
|
|
10954
|
+
};
|
|
10955
|
+
const timer = setTimeout(() => {
|
|
10956
|
+
proc.kill("SIGKILL");
|
|
10957
|
+
finish(false);
|
|
10958
|
+
}, 5e3);
|
|
10959
|
+
timer.unref?.();
|
|
10960
|
+
proc.once("error", () => finish(false));
|
|
10961
|
+
proc.once("close", (code) => finish(code === 0));
|
|
10962
|
+
});
|
|
10963
|
+
}
|
|
10196
10964
|
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
10197
10965
|
return new Promise((resolve15, reject) => {
|
|
10198
10966
|
let settled = false;
|
|
@@ -10228,21 +10996,20 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
10228
10996
|
});
|
|
10229
10997
|
}
|
|
10230
10998
|
var _cachedScriptPath = null;
|
|
10231
|
-
var
|
|
10999
|
+
var cachedPyBinary;
|
|
10232
11000
|
async function syncPyParse(filePath, content, lang) {
|
|
10233
11001
|
try {
|
|
10234
11002
|
if (!_cachedScriptPath) {
|
|
10235
|
-
const tmpDir =
|
|
11003
|
+
const tmpDir = path16.join(os6.tmpdir(), "ws-py-parse");
|
|
10236
11004
|
await fs11.mkdir(tmpDir, { recursive: true });
|
|
10237
|
-
_cachedScriptPath =
|
|
11005
|
+
_cachedScriptPath = path16.join(tmpDir, "parse.py");
|
|
10238
11006
|
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
10239
11007
|
}
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
}
|
|
11008
|
+
cachedPyBinary ??= resolvePython();
|
|
11009
|
+
const pyBinary = await cachedPyBinary;
|
|
11010
|
+
if (!pyBinary) return null;
|
|
10244
11011
|
const { code, stdout } = await spawnPyParser(
|
|
10245
|
-
|
|
11012
|
+
pyBinary,
|
|
10246
11013
|
_cachedScriptPath,
|
|
10247
11014
|
filePath,
|
|
10248
11015
|
content
|
|
@@ -10266,31 +11033,40 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
10266
11033
|
}));
|
|
10267
11034
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
10268
11035
|
} catch {
|
|
10269
|
-
return
|
|
11036
|
+
return null;
|
|
10270
11037
|
}
|
|
10271
11038
|
}
|
|
10272
11039
|
|
|
10273
11040
|
// src/codebase-index/rs-parser.ts
|
|
10274
11041
|
init_win32_resolve();
|
|
10275
|
-
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
10276
|
-
import {
|
|
11042
|
+
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
11043
|
+
import { execFile, spawn as spawn6 } from "node:child_process";
|
|
10277
11044
|
import * as fs12 from "node:fs/promises";
|
|
10278
|
-
import * as
|
|
10279
|
-
async function
|
|
11045
|
+
import * as path17 from "node:path";
|
|
11046
|
+
async function parseSymbols5(opts) {
|
|
10280
11047
|
const { file, content, lang } = opts;
|
|
10281
|
-
const nativeAvailable = checkNativeParser();
|
|
11048
|
+
const nativeAvailable = await checkNativeParser();
|
|
10282
11049
|
if (nativeAvailable) {
|
|
10283
|
-
const result = await tryNativeParse(file, content);
|
|
11050
|
+
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
10284
11051
|
if (result) return result;
|
|
10285
11052
|
}
|
|
10286
11053
|
return regexParse({ file, content, lang });
|
|
10287
11054
|
}
|
|
11055
|
+
var nativeParserAvailability;
|
|
11056
|
+
function probe(command, args) {
|
|
11057
|
+
return new Promise((resolve15, reject) => {
|
|
11058
|
+
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
11059
|
+
if (error) reject(error);
|
|
11060
|
+
else resolve15();
|
|
11061
|
+
});
|
|
11062
|
+
});
|
|
11063
|
+
}
|
|
10288
11064
|
function checkNativeParser() {
|
|
10289
|
-
|
|
10290
|
-
execFileSync("rustc", ["--version"], { stdio: "pipe", windowsHide: true });
|
|
10291
|
-
const toolsDir = path16.join(process.cwd(), "tools");
|
|
11065
|
+
nativeParserAvailability ??= (async () => {
|
|
10292
11066
|
try {
|
|
10293
|
-
|
|
11067
|
+
await probe("rustc", ["--version"]);
|
|
11068
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
11069
|
+
await probe(
|
|
10294
11070
|
"cargo",
|
|
10295
11071
|
[
|
|
10296
11072
|
"metadata",
|
|
@@ -10298,23 +11074,21 @@ function checkNativeParser() {
|
|
|
10298
11074
|
"--format-version",
|
|
10299
11075
|
"1",
|
|
10300
11076
|
"--manifest-path",
|
|
10301
|
-
|
|
10302
|
-
]
|
|
10303
|
-
{ stdio: "pipe", windowsHide: true }
|
|
11077
|
+
path17.join(toolsDir, "Cargo.toml")
|
|
11078
|
+
]
|
|
10304
11079
|
);
|
|
10305
11080
|
return true;
|
|
10306
11081
|
} catch {
|
|
10307
11082
|
return false;
|
|
10308
11083
|
}
|
|
10309
|
-
}
|
|
10310
|
-
|
|
10311
|
-
}
|
|
11084
|
+
})();
|
|
11085
|
+
return nativeParserAvailability;
|
|
10312
11086
|
}
|
|
10313
11087
|
async function tryNativeParse(file, content) {
|
|
10314
11088
|
try {
|
|
10315
|
-
const toolsDir =
|
|
10316
|
-
const crateDir =
|
|
10317
|
-
const tmpFile =
|
|
11089
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
11090
|
+
const crateDir = path17.join(toolsDir, "syn-parser");
|
|
11091
|
+
const tmpFile = path17.join(crateDir, "src", "input.rs");
|
|
10318
11092
|
await fs12.writeFile(tmpFile, content, "utf8");
|
|
10319
11093
|
const cargoBinary = resolveWin32Command("cargo");
|
|
10320
11094
|
const result = await new Promise(
|
|
@@ -10322,7 +11096,7 @@ async function tryNativeParse(file, content) {
|
|
|
10322
11096
|
let settled = false;
|
|
10323
11097
|
const proc = spawn6(
|
|
10324
11098
|
cargoBinary,
|
|
10325
|
-
["run", "--manifest-path",
|
|
11099
|
+
["run", "--manifest-path", path17.join(toolsDir, "Cargo.toml")],
|
|
10326
11100
|
{
|
|
10327
11101
|
cwd: process.cwd(),
|
|
10328
11102
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -10436,9 +11210,9 @@ function regexParse(opts) {
|
|
|
10436
11210
|
}
|
|
10437
11211
|
|
|
10438
11212
|
// src/codebase-index/json-parser.ts
|
|
10439
|
-
import { expectDefined as expectDefined4 } from "@wrongstack/core";
|
|
10440
|
-
import * as
|
|
10441
|
-
function
|
|
11213
|
+
import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
|
|
11214
|
+
import * as path18 from "node:path";
|
|
11215
|
+
function parseSymbols6(opts) {
|
|
10442
11216
|
const { file, content, lang } = opts;
|
|
10443
11217
|
try {
|
|
10444
11218
|
return regexParse2({ file, content, lang });
|
|
@@ -10449,9 +11223,9 @@ function parseSymbols5(opts) {
|
|
|
10449
11223
|
function regexParse2(opts) {
|
|
10450
11224
|
const { file, content, lang } = opts;
|
|
10451
11225
|
const symbols = [];
|
|
10452
|
-
const
|
|
10453
|
-
const isPackageJson =
|
|
10454
|
-
const isTsconfig =
|
|
11226
|
+
const basename12 = path18.basename(file).toLowerCase();
|
|
11227
|
+
const isPackageJson = basename12 === "package.json";
|
|
11228
|
+
const isTsconfig = basename12 === "tsconfig.json" || basename12 === "tsconfig.build.json";
|
|
10455
11229
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
10456
11230
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
10457
11231
|
const lines = content.split("\n");
|
|
@@ -10475,11 +11249,11 @@ function regexParse2(opts) {
|
|
|
10475
11249
|
const line = lineFromOffset(offset);
|
|
10476
11250
|
symbols.push(
|
|
10477
11251
|
makeSymbol({
|
|
10478
|
-
name:
|
|
11252
|
+
name: path18.basename(file),
|
|
10479
11253
|
kind: "object",
|
|
10480
11254
|
line,
|
|
10481
11255
|
col: 0,
|
|
10482
|
-
signature: `"${
|
|
11256
|
+
signature: `"${path18.basename(file)}" = { ... }`,
|
|
10483
11257
|
file,
|
|
10484
11258
|
lang
|
|
10485
11259
|
})
|
|
@@ -10641,8 +11415,8 @@ function makeSymbol(opts) {
|
|
|
10641
11415
|
}
|
|
10642
11416
|
|
|
10643
11417
|
// src/codebase-index/yaml-parser.ts
|
|
10644
|
-
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core";
|
|
10645
|
-
function
|
|
11418
|
+
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core/utils";
|
|
11419
|
+
function parseSymbols7(opts) {
|
|
10646
11420
|
const { file, content, lang } = opts;
|
|
10647
11421
|
try {
|
|
10648
11422
|
return regexParse3({ file, content, lang });
|
|
@@ -10791,8 +11565,8 @@ function makeSymbol2(opts) {
|
|
|
10791
11565
|
|
|
10792
11566
|
// src/codebase-index/gitignore.ts
|
|
10793
11567
|
import * as fs13 from "node:fs/promises";
|
|
10794
|
-
import * as
|
|
10795
|
-
import { compileGlob } from "@wrongstack/core";
|
|
11568
|
+
import * as path19 from "node:path";
|
|
11569
|
+
import { compileGlob } from "@wrongstack/core/utils";
|
|
10796
11570
|
function globBody(glob) {
|
|
10797
11571
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
10798
11572
|
}
|
|
@@ -10837,7 +11611,7 @@ function compileGitignore(lines) {
|
|
|
10837
11611
|
async function loadGitignoreMatcher(projectRoot) {
|
|
10838
11612
|
let lines = [];
|
|
10839
11613
|
try {
|
|
10840
|
-
const raw = await fs13.readFile(
|
|
11614
|
+
const raw = await fs13.readFile(path19.join(projectRoot, ".gitignore"), "utf8");
|
|
10841
11615
|
lines = raw.split("\n");
|
|
10842
11616
|
} catch {
|
|
10843
11617
|
}
|
|
@@ -10846,7 +11620,9 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
10846
11620
|
|
|
10847
11621
|
// src/codebase-index/indexer.ts
|
|
10848
11622
|
var YIELD_EVERY_N = 50;
|
|
10849
|
-
|
|
11623
|
+
function resolveParallelBatch() {
|
|
11624
|
+
return indexParallelBatchSize(availableParallelism());
|
|
11625
|
+
}
|
|
10850
11626
|
function yieldEventLoop() {
|
|
10851
11627
|
return new Promise((resolve15) => setImmediate(resolve15));
|
|
10852
11628
|
}
|
|
@@ -10858,22 +11634,12 @@ function throwIfAborted(signal) {
|
|
|
10858
11634
|
function isAbortError(err) {
|
|
10859
11635
|
return err instanceof DOMException && err.name === "AbortError";
|
|
10860
11636
|
}
|
|
10861
|
-
var DEFAULT_IGNORE =
|
|
10862
|
-
"node_modules",
|
|
10863
|
-
".git",
|
|
10864
|
-
"dist",
|
|
10865
|
-
"build",
|
|
10866
|
-
".next",
|
|
10867
|
-
"coverage",
|
|
10868
|
-
".turbo",
|
|
10869
|
-
"__snapshots__",
|
|
10870
|
-
".nyc_output"
|
|
10871
|
-
];
|
|
11637
|
+
var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
|
|
10872
11638
|
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10873
11639
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10874
11640
|
function isWithinProject(projectRoot, file) {
|
|
10875
|
-
const rel =
|
|
10876
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
11641
|
+
const rel = path20.relative(projectRoot, file);
|
|
11642
|
+
return rel !== "" && !rel.startsWith(`..${path20.sep}`) && rel !== ".." && !path20.isAbsolute(rel);
|
|
10877
11643
|
}
|
|
10878
11644
|
function isMissingPathError(err) {
|
|
10879
11645
|
const code = err?.code;
|
|
@@ -10884,18 +11650,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10884
11650
|
const errors = [];
|
|
10885
11651
|
let complete = true;
|
|
10886
11652
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
10887
|
-
const
|
|
10888
|
-
{ ext: ".ts", pat: compileGlob2("**/*.ts") },
|
|
10889
|
-
{ ext: ".tsx", pat: compileGlob2("**/*.tsx") },
|
|
10890
|
-
{ ext: ".js", pat: compileGlob2("**/*.js") },
|
|
10891
|
-
{ ext: ".jsx", pat: compileGlob2("**/*.jsx") },
|
|
10892
|
-
{ ext: ".go", pat: compileGlob2("**/*.go") },
|
|
10893
|
-
{ ext: ".py", pat: compileGlob2("**/*.py") },
|
|
10894
|
-
{ ext: ".rs", pat: compileGlob2("**/*.rs") },
|
|
10895
|
-
{ ext: ".json", pat: compileGlob2("**/*.json") },
|
|
10896
|
-
{ ext: ".yaml", pat: compileGlob2("**/*.yaml") },
|
|
10897
|
-
{ ext: ".yml", pat: compileGlob2("**/*.yml") }
|
|
10898
|
-
];
|
|
11653
|
+
const indexableExts = new Set(INDEXABLE_EXTENSIONS);
|
|
10899
11654
|
let dirCount = 0;
|
|
10900
11655
|
const walk2 = async (dir) => {
|
|
10901
11656
|
throwIfAborted(signal);
|
|
@@ -10914,19 +11669,16 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10914
11669
|
dirCount++;
|
|
10915
11670
|
for (const e of entries) {
|
|
10916
11671
|
if (ignoreSet.has(e.name)) continue;
|
|
10917
|
-
const full =
|
|
10918
|
-
const rel =
|
|
11672
|
+
const full = path20.join(dir, e.name);
|
|
11673
|
+
const rel = path20.relative(projectRoot, full).replace(/\\/g, "/");
|
|
10919
11674
|
if (e.isDirectory()) {
|
|
10920
11675
|
if (isGitIgnored(rel, true)) continue;
|
|
10921
11676
|
await walk2(full);
|
|
10922
11677
|
} else if (e.isFile()) {
|
|
10923
11678
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10924
|
-
const ext =
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
results.push(full);
|
|
10928
|
-
break;
|
|
10929
|
-
}
|
|
11679
|
+
const ext = path20.extname(e.name).toLowerCase();
|
|
11680
|
+
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
11681
|
+
results.push(full);
|
|
10930
11682
|
}
|
|
10931
11683
|
}
|
|
10932
11684
|
}
|
|
@@ -10935,24 +11687,25 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10935
11687
|
return { files: results, complete, errors };
|
|
10936
11688
|
}
|
|
10937
11689
|
async function parseFile(file, content, lang) {
|
|
10938
|
-
|
|
11690
|
+
const symbolLang = lang;
|
|
11691
|
+
switch (symbolLang) {
|
|
10939
11692
|
case "ts":
|
|
10940
11693
|
case "tsx":
|
|
10941
11694
|
case "js":
|
|
10942
11695
|
case "jsx":
|
|
10943
|
-
return parseSymbols({ file, content, lang });
|
|
11696
|
+
return parseSymbols({ file, content, lang: symbolLang });
|
|
10944
11697
|
case "go":
|
|
10945
11698
|
return parseSymbols2({ file, content, lang: "go" });
|
|
10946
11699
|
case "py":
|
|
10947
|
-
return
|
|
11700
|
+
return parseSymbols4({ file, content, lang: "py" });
|
|
10948
11701
|
case "rs":
|
|
10949
|
-
return
|
|
11702
|
+
return parseSymbols5({ file, content, lang: "rs" });
|
|
10950
11703
|
case "json":
|
|
10951
|
-
return
|
|
11704
|
+
return parseSymbols6({ file, content, lang: "json" });
|
|
10952
11705
|
case "yaml":
|
|
10953
|
-
return
|
|
11706
|
+
return parseSymbols7({ file, content, lang: "yaml" });
|
|
10954
11707
|
default:
|
|
10955
|
-
return { file,
|
|
11708
|
+
return parseSymbols3({ file, content, lang: symbolLang });
|
|
10956
11709
|
}
|
|
10957
11710
|
}
|
|
10958
11711
|
function assignRefsToSymbols2(refs, symbols) {
|
|
@@ -11000,10 +11753,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11000
11753
|
let discoveredFiles = null;
|
|
11001
11754
|
let discoveryComplete = true;
|
|
11002
11755
|
if (opts.files && opts.files.length > 0) {
|
|
11003
|
-
files = opts.files.map((f) =>
|
|
11756
|
+
files = opts.files.map((f) => path20.resolve(projectRoot, f)).filter((f) => {
|
|
11004
11757
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
11005
|
-
const rel =
|
|
11006
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
11758
|
+
const rel = path20.relative(projectRoot, f).replace(/\\/g, "/");
|
|
11759
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path20.basename(f)) && !isGitIgnored(rel, false);
|
|
11007
11760
|
});
|
|
11008
11761
|
} else {
|
|
11009
11762
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -11024,15 +11777,19 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11024
11777
|
if (!force) {
|
|
11025
11778
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
11026
11779
|
}
|
|
11780
|
+
const parallelBatch = resolveParallelBatch();
|
|
11027
11781
|
let filesSinceLastYield = 0;
|
|
11028
|
-
for (let batchStart = 0; batchStart < files.length; batchStart +=
|
|
11029
|
-
const batchEnd = Math.min(batchStart +
|
|
11782
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
|
|
11783
|
+
const batchEnd = Math.min(batchStart + parallelBatch, files.length);
|
|
11030
11784
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
11031
11785
|
opts.onProgress?.(batchEnd, files.length);
|
|
11032
11786
|
filesSinceLastYield += batchFiles.length;
|
|
11033
11787
|
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
11034
11788
|
filesSinceLastYield = 0;
|
|
11035
11789
|
await yieldEventLoop();
|
|
11790
|
+
if (isFrugalPerf()) {
|
|
11791
|
+
await new Promise((r) => setTimeout(r, 8));
|
|
11792
|
+
}
|
|
11036
11793
|
throwIfAborted(signal);
|
|
11037
11794
|
}
|
|
11038
11795
|
const statOpts = signal ? { signal } : {};
|
|
@@ -11136,9 +11893,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11136
11893
|
continue;
|
|
11137
11894
|
}
|
|
11138
11895
|
if (parsed.symbols.length === 0) {
|
|
11139
|
-
store.
|
|
11140
|
-
store.deleteSymbolsForFile(file);
|
|
11141
|
-
store.upsertFile({
|
|
11896
|
+
store.replaceEmptyFile({
|
|
11142
11897
|
file,
|
|
11143
11898
|
lang,
|
|
11144
11899
|
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
@@ -11207,6 +11962,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11207
11962
|
}
|
|
11208
11963
|
store.resolveRefs();
|
|
11209
11964
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
11965
|
+
store.optimize();
|
|
11210
11966
|
const durationMs = Date.now() - startMs;
|
|
11211
11967
|
store.setLastIndexed(Date.now());
|
|
11212
11968
|
return {
|
|
@@ -11497,10 +12253,10 @@ async function callInline(op, args, opts) {
|
|
|
11497
12253
|
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
11498
12254
|
}
|
|
11499
12255
|
}
|
|
11500
|
-
var
|
|
12256
|
+
var chain2 = Promise.resolve();
|
|
11501
12257
|
function withMutex(job) {
|
|
11502
|
-
const run =
|
|
11503
|
-
|
|
12258
|
+
const run = chain2.then(job, job);
|
|
12259
|
+
chain2 = run.then(
|
|
11504
12260
|
() => void 0,
|
|
11505
12261
|
() => void 0
|
|
11506
12262
|
);
|
|
@@ -11514,11 +12270,62 @@ function circuitOpenError() {
|
|
|
11514
12270
|
}
|
|
11515
12271
|
var DEFAULT_DEBOUNCE_MS = 400;
|
|
11516
12272
|
var debounceTimers = /* @__PURE__ */ new Map();
|
|
11517
|
-
|
|
11518
|
-
|
|
12273
|
+
var readyReindexBatches = /* @__PURE__ */ new Map();
|
|
12274
|
+
function debounceKey(projectRoot, indexDir, file) {
|
|
12275
|
+
return JSON.stringify([projectRoot, indexDir ?? "", file]);
|
|
12276
|
+
}
|
|
12277
|
+
function reindexBatchKey(projectRoot, indexDir) {
|
|
12278
|
+
return JSON.stringify([projectRoot, indexDir ?? ""]);
|
|
12279
|
+
}
|
|
12280
|
+
function flushReadyReindexBatch(key) {
|
|
12281
|
+
const batch = readyReindexBatches.get(key);
|
|
12282
|
+
if (!batch) return;
|
|
12283
|
+
readyReindexBatches.delete(key);
|
|
12284
|
+
if (!indexCircuitBreaker.allowRequest()) {
|
|
12285
|
+
const error = circuitOpenError();
|
|
12286
|
+
for (const onError of batch.onErrors) onError(error);
|
|
12287
|
+
return;
|
|
12288
|
+
}
|
|
12289
|
+
void withMutex(
|
|
12290
|
+
() => callIndexOp(
|
|
12291
|
+
"index",
|
|
12292
|
+
{
|
|
12293
|
+
projectRoot: batch.projectRoot,
|
|
12294
|
+
files: [...batch.files].sort(),
|
|
12295
|
+
indexDir: batch.indexDir
|
|
12296
|
+
},
|
|
12297
|
+
{ timeoutMs: batch.timeoutMs }
|
|
12298
|
+
)
|
|
12299
|
+
).then(
|
|
12300
|
+
() => indexCircuitBreaker.recordSuccess(),
|
|
12301
|
+
(err) => {
|
|
12302
|
+
indexCircuitBreaker.recordFailure(err);
|
|
12303
|
+
for (const onError of batch.onErrors) onError(err);
|
|
12304
|
+
}
|
|
12305
|
+
);
|
|
12306
|
+
}
|
|
12307
|
+
function addReadyReindex(opts) {
|
|
12308
|
+
const key = reindexBatchKey(opts.projectRoot, opts.indexDir);
|
|
12309
|
+
const existing = readyReindexBatches.get(key);
|
|
12310
|
+
if (existing) {
|
|
12311
|
+
existing.files.add(opts.file);
|
|
12312
|
+
existing.timeoutMs = Math.max(existing.timeoutMs, opts.timeoutMs);
|
|
12313
|
+
if (opts.onError) existing.onErrors.add(opts.onError);
|
|
12314
|
+
return;
|
|
12315
|
+
}
|
|
12316
|
+
const batch = {
|
|
12317
|
+
projectRoot: opts.projectRoot,
|
|
12318
|
+
indexDir: opts.indexDir,
|
|
12319
|
+
files: /* @__PURE__ */ new Set([opts.file]),
|
|
12320
|
+
timeoutMs: opts.timeoutMs,
|
|
12321
|
+
onErrors: new Set(opts.onError ? [opts.onError] : [])
|
|
12322
|
+
};
|
|
12323
|
+
batch.flush = setImmediate(() => flushReadyReindexBatch(key));
|
|
12324
|
+
batch.flush.unref?.();
|
|
12325
|
+
readyReindexBatches.set(key, batch);
|
|
11519
12326
|
}
|
|
11520
12327
|
function isIndexableFile(filePath) {
|
|
11521
|
-
return
|
|
12328
|
+
return isIndexablePath(filePath);
|
|
11522
12329
|
}
|
|
11523
12330
|
function isRecoverableConstraintError(err) {
|
|
11524
12331
|
if (err instanceof Error) {
|
|
@@ -11578,28 +12385,18 @@ function enqueueReindex(opts) {
|
|
|
11578
12385
|
if (files.length === 0) return;
|
|
11579
12386
|
const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
11580
12387
|
for (const file of files) {
|
|
11581
|
-
const key = debounceKey(opts.indexDir, file);
|
|
12388
|
+
const key = debounceKey(opts.projectRoot, opts.indexDir, file);
|
|
11582
12389
|
const existing = debounceTimers.get(key);
|
|
11583
12390
|
if (existing) clearTimeout(existing);
|
|
11584
12391
|
const timer = setTimeout(() => {
|
|
11585
12392
|
debounceTimers.delete(key);
|
|
11586
|
-
|
|
11587
|
-
opts.
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
{ projectRoot: opts.projectRoot, files: [file], indexDir: opts.indexDir },
|
|
11594
|
-
{ timeoutMs: opts.timeoutMs ?? DEFAULT_INCREMENTAL_TIMEOUT_MS }
|
|
11595
|
-
)
|
|
11596
|
-
).then(
|
|
11597
|
-
() => indexCircuitBreaker.recordSuccess(),
|
|
11598
|
-
(err) => {
|
|
11599
|
-
indexCircuitBreaker.recordFailure(err);
|
|
11600
|
-
opts.onError?.(err);
|
|
11601
|
-
}
|
|
11602
|
-
);
|
|
12393
|
+
addReadyReindex({
|
|
12394
|
+
projectRoot: opts.projectRoot,
|
|
12395
|
+
file,
|
|
12396
|
+
indexDir: opts.indexDir,
|
|
12397
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_INCREMENTAL_TIMEOUT_MS,
|
|
12398
|
+
onError: opts.onError
|
|
12399
|
+
});
|
|
11603
12400
|
}, ms);
|
|
11604
12401
|
timer.unref?.();
|
|
11605
12402
|
debounceTimers.set(key, timer);
|
|
@@ -11608,6 +12405,10 @@ function enqueueReindex(opts) {
|
|
|
11608
12405
|
function cancelPendingReindexes() {
|
|
11609
12406
|
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
11610
12407
|
debounceTimers.clear();
|
|
12408
|
+
for (const batch of readyReindexBatches.values()) {
|
|
12409
|
+
if (batch.flush) clearImmediate(batch.flush);
|
|
12410
|
+
}
|
|
12411
|
+
readyReindexBatches.clear();
|
|
11611
12412
|
}
|
|
11612
12413
|
async function searchCodebaseIndex(args, opts = {}) {
|
|
11613
12414
|
return callIndexOp("search", args, {
|
|
@@ -11893,7 +12694,7 @@ var codebaseStatsTool = {
|
|
|
11893
12694
|
|
|
11894
12695
|
// src/design.ts
|
|
11895
12696
|
import * as fs16 from "node:fs/promises";
|
|
11896
|
-
import * as
|
|
12697
|
+
import * as path21 from "node:path";
|
|
11897
12698
|
import {
|
|
11898
12699
|
applyTokenOverrides,
|
|
11899
12700
|
getDesignKitLoader,
|
|
@@ -11902,23 +12703,24 @@ import {
|
|
|
11902
12703
|
materializeTokens,
|
|
11903
12704
|
recordKitChoice,
|
|
11904
12705
|
recordOverrides,
|
|
12706
|
+
resolveSemanticTune,
|
|
11905
12707
|
runDesignVerify,
|
|
11906
12708
|
setActiveKit,
|
|
11907
12709
|
setDesignOverrides
|
|
11908
|
-
} from "@wrongstack/core";
|
|
12710
|
+
} from "@wrongstack/core/design";
|
|
11909
12711
|
async function resolveReal(p) {
|
|
11910
|
-
const resolved =
|
|
11911
|
-
let
|
|
12712
|
+
const resolved = path21.resolve(p);
|
|
12713
|
+
let probe2 = resolved;
|
|
11912
12714
|
const missing = [];
|
|
11913
12715
|
for (; ; ) {
|
|
11914
12716
|
try {
|
|
11915
|
-
return
|
|
12717
|
+
return path21.resolve(await fs16.realpath(probe2), ...missing);
|
|
11916
12718
|
} catch (err) {
|
|
11917
12719
|
if (err.code === "ENOENT") {
|
|
11918
|
-
const parent =
|
|
11919
|
-
if (parent ===
|
|
11920
|
-
missing.unshift(
|
|
11921
|
-
|
|
12720
|
+
const parent = path21.dirname(probe2);
|
|
12721
|
+
if (parent === probe2) return resolved;
|
|
12722
|
+
missing.unshift(path21.basename(probe2));
|
|
12723
|
+
probe2 = parent;
|
|
11922
12724
|
continue;
|
|
11923
12725
|
}
|
|
11924
12726
|
return resolved;
|
|
@@ -11948,8 +12750,8 @@ var designTool = {
|
|
|
11948
12750
|
properties: {
|
|
11949
12751
|
action: {
|
|
11950
12752
|
type: "string",
|
|
11951
|
-
enum: ["list", "use", "foundations", "set", "materialize", "verify"],
|
|
11952
|
-
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
|
|
12753
|
+
enum: ["list", "use", "foundations", "set", "tune", "materialize", "verify"],
|
|
12754
|
+
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."
|
|
11953
12755
|
},
|
|
11954
12756
|
kit: {
|
|
11955
12757
|
type: "string",
|
|
@@ -11965,6 +12767,16 @@ var designTool = {
|
|
|
11965
12767
|
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.',
|
|
11966
12768
|
additionalProperties: { type: "string" }
|
|
11967
12769
|
},
|
|
12770
|
+
tune: {
|
|
12771
|
+
type: "object",
|
|
12772
|
+
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.',
|
|
12773
|
+
properties: {
|
|
12774
|
+
radius: { type: "string" },
|
|
12775
|
+
density: { type: "string" },
|
|
12776
|
+
font: { type: "string" },
|
|
12777
|
+
motion: { type: "string" }
|
|
12778
|
+
}
|
|
12779
|
+
},
|
|
11968
12780
|
out: {
|
|
11969
12781
|
type: "string",
|
|
11970
12782
|
description: "Materialize output path (project-relative). Defaults to a per-stack convention."
|
|
@@ -12062,6 +12874,28 @@ ${body}`
|
|
|
12062
12874
|
These win over kit tokens. Run \`design {action:"materialize"}\` to write them to a theme file.`
|
|
12063
12875
|
};
|
|
12064
12876
|
}
|
|
12877
|
+
if (action === "tune") {
|
|
12878
|
+
const patch = resolveSemanticTune(input.tune ?? {});
|
|
12879
|
+
if (Object.keys(patch).length === 0) {
|
|
12880
|
+
return {
|
|
12881
|
+
action,
|
|
12882
|
+
output: 'No recognized knobs. Pass tune:{ radius:"lg", density:"compact", font:"\u2026", motion:"snappy" }.'
|
|
12883
|
+
};
|
|
12884
|
+
}
|
|
12885
|
+
const merged = await recordOverrides(ctx.projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
|
|
12886
|
+
if (!merged) {
|
|
12887
|
+
return {
|
|
12888
|
+
action,
|
|
12889
|
+
output: 'No active kit. Pick one first: `design {action:"use", kit:"<id>"}`.'
|
|
12890
|
+
};
|
|
12891
|
+
}
|
|
12892
|
+
setDesignOverrides(ctx, merged);
|
|
12893
|
+
return {
|
|
12894
|
+
action,
|
|
12895
|
+
output: `Tuned. Resolved ${Object.keys(patch).length} token override(s): ${Object.entries(patch).map(([k, v]) => `${k}=${v}`).join(", ")}
|
|
12896
|
+
Run \`design {action:"materialize"}\` to write the tuned tokens to your theme file.`
|
|
12897
|
+
};
|
|
12898
|
+
}
|
|
12065
12899
|
if (action === "materialize") {
|
|
12066
12900
|
const active = await loadActiveKit(ctx.projectRoot);
|
|
12067
12901
|
if (!active) {
|
|
@@ -12083,11 +12917,11 @@ These win over kit tokens. Run \`design {action:"materialize"}\` to write them t
|
|
|
12083
12917
|
outPath: input.out
|
|
12084
12918
|
});
|
|
12085
12919
|
const root = await resolveReal(ctx.projectRoot);
|
|
12086
|
-
const absResolved =
|
|
12087
|
-
const absParent = await resolveReal(
|
|
12088
|
-
const abs =
|
|
12089
|
-
const rel =
|
|
12090
|
-
if (rel.startsWith("..") ||
|
|
12920
|
+
const absResolved = path21.isAbsolute(result.path) ? path21.resolve(result.path) : path21.resolve(path21.join(ctx.projectRoot, result.path));
|
|
12921
|
+
const absParent = await resolveReal(path21.dirname(absResolved));
|
|
12922
|
+
const abs = path21.join(absParent, path21.basename(absResolved));
|
|
12923
|
+
const rel = path21.relative(root, abs);
|
|
12924
|
+
if (rel.startsWith("..") || path21.isAbsolute(rel)) {
|
|
12091
12925
|
throw new Error(
|
|
12092
12926
|
`design: materialize path "${result.path}" would escape the project root`
|
|
12093
12927
|
);
|
|
@@ -12111,7 +12945,7 @@ ${result.content}
|
|
|
12111
12945
|
\`\`\``
|
|
12112
12946
|
};
|
|
12113
12947
|
}
|
|
12114
|
-
await fs16.mkdir(
|
|
12948
|
+
await fs16.mkdir(path21.dirname(abs), { recursive: true });
|
|
12115
12949
|
await fs16.writeFile(abs, result.content);
|
|
12116
12950
|
return {
|
|
12117
12951
|
action,
|
|
@@ -12163,8 +12997,8 @@ init_util();
|
|
|
12163
12997
|
import { spawn as spawn7 } from "node:child_process";
|
|
12164
12998
|
import { statSync as statSync2 } from "node:fs";
|
|
12165
12999
|
import * as fs17 from "node:fs/promises";
|
|
12166
|
-
import * as
|
|
12167
|
-
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core";
|
|
13000
|
+
import * as path22 from "node:path";
|
|
13001
|
+
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core/utils";
|
|
12168
13002
|
var diffTool = {
|
|
12169
13003
|
name: "diff",
|
|
12170
13004
|
category: "Filesystem",
|
|
@@ -12247,11 +13081,11 @@ function findGitDir(cwd) {
|
|
|
12247
13081
|
let dir = cwd;
|
|
12248
13082
|
for (let i = 0; i < 20; i++) {
|
|
12249
13083
|
try {
|
|
12250
|
-
const stat18 = statSync2(
|
|
13084
|
+
const stat18 = statSync2(path22.join(dir, ".git"));
|
|
12251
13085
|
if (stat18.isDirectory()) return dir;
|
|
12252
13086
|
} catch {
|
|
12253
13087
|
}
|
|
12254
|
-
const parent =
|
|
13088
|
+
const parent = path22.dirname(dir);
|
|
12255
13089
|
if (parent === dir) break;
|
|
12256
13090
|
dir = parent;
|
|
12257
13091
|
}
|
|
@@ -12465,7 +13299,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
12465
13299
|
// src/e2e.ts
|
|
12466
13300
|
init_util();
|
|
12467
13301
|
import { open, readdir as readdir5 } from "node:fs/promises";
|
|
12468
|
-
import * as
|
|
13302
|
+
import * as path23 from "node:path";
|
|
12469
13303
|
async function readBoundedText(filePath, maxBytes) {
|
|
12470
13304
|
let handle;
|
|
12471
13305
|
try {
|
|
@@ -12515,8 +13349,8 @@ var MAX_PACKAGE_BYTES = 512 * 1024;
|
|
|
12515
13349
|
var MAX_CONFIG_BYTES = 512 * 1024;
|
|
12516
13350
|
var MAX_SPEC_SAMPLES = 20;
|
|
12517
13351
|
function relativePath(root, target) {
|
|
12518
|
-
const value =
|
|
12519
|
-
return value.split(
|
|
13352
|
+
const value = path23.relative(root, target) || ".";
|
|
13353
|
+
return value.split(path23.sep).join("/");
|
|
12520
13354
|
}
|
|
12521
13355
|
function escapeRegExp(value) {
|
|
12522
13356
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -12588,7 +13422,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
12588
13422
|
}
|
|
12589
13423
|
for (const entry of entries) {
|
|
12590
13424
|
signal.throwIfAborted();
|
|
12591
|
-
const absolutePath =
|
|
13425
|
+
const absolutePath = path23.join(current.directory, entry.name);
|
|
12592
13426
|
if (entry.isFile()) {
|
|
12593
13427
|
if (entry.name === "package.json") result.packageFiles.push(absolutePath);
|
|
12594
13428
|
const framework = CONFIG_NAMES.get(entry.name);
|
|
@@ -12649,9 +13483,9 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
12649
13483
|
if (names.has("bun.lock") || names.has("bun.lockb")) return "bun";
|
|
12650
13484
|
if (names.has("package-lock.json") || names.has("npm-shrinkwrap.json")) return "npm";
|
|
12651
13485
|
if (directory === scanRoot) break;
|
|
12652
|
-
const parent =
|
|
12653
|
-
const relativeParent =
|
|
12654
|
-
if (parent === directory || relativeParent.startsWith("..") ||
|
|
13486
|
+
const parent = path23.dirname(directory);
|
|
13487
|
+
const relativeParent = path23.relative(scanRoot, parent);
|
|
13488
|
+
if (parent === directory || relativeParent.startsWith("..") || path23.isAbsolute(relativeParent)) {
|
|
12655
13489
|
break;
|
|
12656
13490
|
}
|
|
12657
13491
|
directory = parent;
|
|
@@ -12696,7 +13530,7 @@ function isSpec(framework, filename) {
|
|
|
12696
13530
|
return /\.(?:spec|test)\.(?:[cm]?[jt]sx?)$/i.test(filename);
|
|
12697
13531
|
}
|
|
12698
13532
|
async function collectSpecs(root, framework, testDirectory, signal) {
|
|
12699
|
-
const roots = testDirectory ? [
|
|
13533
|
+
const roots = testDirectory ? [path23.resolve(root, testDirectory)] : framework === "cypress" ? [path23.join(root, "cypress", "e2e"), path23.join(root, "cypress", "integration")] : [path23.join(root, "tests")];
|
|
12700
13534
|
const samples = [];
|
|
12701
13535
|
let count = 0;
|
|
12702
13536
|
let scanned = 0;
|
|
@@ -12715,7 +13549,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
12715
13549
|
}
|
|
12716
13550
|
for (const entry of entries) {
|
|
12717
13551
|
signal.throwIfAborted();
|
|
12718
|
-
const target =
|
|
13552
|
+
const target = path23.join(directory, entry.name);
|
|
12719
13553
|
if (entry.isDirectory() && !SKIP_DIRECTORIES.has(entry.name)) queue.push(target);
|
|
12720
13554
|
else if (entry.isFile() && isSpec(framework, entry.name)) {
|
|
12721
13555
|
count += 1;
|
|
@@ -12735,7 +13569,7 @@ function nearestPackage(projectRoot, packagesByDirectory, scanRoot) {
|
|
|
12735
13569
|
const found = packagesByDirectory.get(directory);
|
|
12736
13570
|
if (found) return found;
|
|
12737
13571
|
if (directory === scanRoot) return void 0;
|
|
12738
|
-
const parent =
|
|
13572
|
+
const parent = path23.dirname(directory);
|
|
12739
13573
|
if (parent === directory || relativePath(scanRoot, parent).startsWith("..")) return void 0;
|
|
12740
13574
|
directory = parent;
|
|
12741
13575
|
}
|
|
@@ -12746,13 +13580,13 @@ async function discoverE2EProjects(root, options) {
|
|
|
12746
13580
|
const packages = (await Promise.all(scan.packageFiles.map(readPackageInfo))).filter(
|
|
12747
13581
|
(info) => Boolean(info)
|
|
12748
13582
|
);
|
|
12749
|
-
const packagesByDirectory = new Map(packages.map((info) => [
|
|
13583
|
+
const packagesByDirectory = new Map(packages.map((info) => [path23.dirname(info.path), info]));
|
|
12750
13584
|
const candidates = /* @__PURE__ */ new Map();
|
|
12751
13585
|
for (const config of scan.configs) {
|
|
12752
13586
|
if (options.framework && options.framework !== "all" && config.framework !== options.framework) {
|
|
12753
13587
|
continue;
|
|
12754
13588
|
}
|
|
12755
|
-
const projectRoot =
|
|
13589
|
+
const projectRoot = path23.dirname(config.absolutePath);
|
|
12756
13590
|
candidates.set(`${config.framework}:${projectRoot}`, {
|
|
12757
13591
|
framework: config.framework,
|
|
12758
13592
|
root: projectRoot,
|
|
@@ -12760,7 +13594,7 @@ async function discoverE2EProjects(root, options) {
|
|
|
12760
13594
|
});
|
|
12761
13595
|
}
|
|
12762
13596
|
for (const info of packages) {
|
|
12763
|
-
const projectRoot =
|
|
13597
|
+
const projectRoot = path23.dirname(info.path);
|
|
12764
13598
|
for (const framework of frameworkFromPackage(info)) {
|
|
12765
13599
|
if (options.framework && options.framework !== "all" && framework !== options.framework)
|
|
12766
13600
|
continue;
|
|
@@ -12776,10 +13610,10 @@ async function discoverE2EProjects(root, options) {
|
|
|
12776
13610
|
const scripts = matchingScripts(info, candidate.framework);
|
|
12777
13611
|
const manager = await detectPackageManager3(candidate.root, root, info?.packageManager);
|
|
12778
13612
|
const testDirectory = candidate.framework === "playwright" ? staticString(source, "testDir") : void 0;
|
|
12779
|
-
const resolvedTestDirectory = testDirectory ?
|
|
12780
|
-
const relativeTestDirectory = resolvedTestDirectory ?
|
|
13613
|
+
const resolvedTestDirectory = testDirectory ? path23.resolve(candidate.root, testDirectory) : void 0;
|
|
13614
|
+
const relativeTestDirectory = resolvedTestDirectory ? path23.relative(root, resolvedTestDirectory) : void 0;
|
|
12781
13615
|
const unsafeTestDirectory = Boolean(
|
|
12782
|
-
relativeTestDirectory && (relativeTestDirectory.startsWith("..") ||
|
|
13616
|
+
relativeTestDirectory && (relativeTestDirectory.startsWith("..") || path23.isAbsolute(relativeTestDirectory))
|
|
12783
13617
|
);
|
|
12784
13618
|
const specs = options.includeSpecs === false || unsafeTestDirectory ? { count: 0, samples: [], truncated: false } : await collectSpecs(candidate.root, candidate.framework, testDirectory, options.signal);
|
|
12785
13619
|
const warnings = [];
|
|
@@ -12884,10 +13718,10 @@ import {
|
|
|
12884
13718
|
atomicWrite as atomicWrite2,
|
|
12885
13719
|
detectNewlineStyle,
|
|
12886
13720
|
normalizeToLf,
|
|
12887
|
-
ToolValidationError,
|
|
12888
13721
|
toStyle,
|
|
12889
13722
|
unifiedDiff
|
|
12890
|
-
} from "@wrongstack/core";
|
|
13723
|
+
} from "@wrongstack/core/utils";
|
|
13724
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
12891
13725
|
|
|
12892
13726
|
// src/_edit-match.ts
|
|
12893
13727
|
var TIER_LABEL = {
|
|
@@ -13115,7 +13949,7 @@ function prefixSimilarity(a, b) {
|
|
|
13115
13949
|
}
|
|
13116
13950
|
|
|
13117
13951
|
// src/_syntax-check.ts
|
|
13118
|
-
import * as
|
|
13952
|
+
import * as path24 from "node:path";
|
|
13119
13953
|
var TS_LIKE = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
13120
13954
|
var MAX_CHECK_CHARS = 15e5;
|
|
13121
13955
|
var MAX_ERRORS = 5;
|
|
@@ -13139,15 +13973,15 @@ async function checkSyntax(filePath, content, previousContent) {
|
|
|
13139
13973
|
return { errors, preExisting };
|
|
13140
13974
|
}
|
|
13141
13975
|
function isJsoncFile(filePath) {
|
|
13142
|
-
const base =
|
|
13976
|
+
const base = path24.basename(filePath).toLowerCase();
|
|
13143
13977
|
if (base.endsWith(".jsonc")) return true;
|
|
13144
13978
|
if (/^(tsconfig|jsconfig)([.-].*)?\.json$/.test(base)) return true;
|
|
13145
|
-
const dir =
|
|
13979
|
+
const dir = path24.basename(path24.dirname(filePath)).toLowerCase();
|
|
13146
13980
|
return dir === ".vscode";
|
|
13147
13981
|
}
|
|
13148
13982
|
async function parseErrors(filePath, content) {
|
|
13149
13983
|
if (content.length > MAX_CHECK_CHARS) return void 0;
|
|
13150
|
-
const ext =
|
|
13984
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
13151
13985
|
if (ext === ".json" || ext === ".jsonc") {
|
|
13152
13986
|
try {
|
|
13153
13987
|
JSON.parse(content);
|
|
@@ -13174,7 +14008,7 @@ async function parseErrors(filePath, content) {
|
|
|
13174
14008
|
ts2.ScriptKind.JSX
|
|
13175
14009
|
);
|
|
13176
14010
|
const sourceFile = ts2.createSourceFile(
|
|
13177
|
-
|
|
14011
|
+
path24.basename(filePath),
|
|
13178
14012
|
content,
|
|
13179
14013
|
ts2.ScriptTarget.Latest,
|
|
13180
14014
|
/* setParentNodes */
|
|
@@ -13409,7 +14243,7 @@ import {
|
|
|
13409
14243
|
emitProcessCompleted as emitProcessCompleted3,
|
|
13410
14244
|
emitProcessOutput as emitProcessOutput3,
|
|
13411
14245
|
emitProcessStarted as emitProcessStarted3
|
|
13412
|
-
} from "@wrongstack/core";
|
|
14246
|
+
} from "@wrongstack/core/observability";
|
|
13413
14247
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
13414
14248
|
init_output_spool();
|
|
13415
14249
|
init_util();
|
|
@@ -13418,7 +14252,7 @@ init_win32_resolve();
|
|
|
13418
14252
|
|
|
13419
14253
|
// src/exec-kill-guard.ts
|
|
13420
14254
|
import * as os7 from "node:os";
|
|
13421
|
-
import * as
|
|
14255
|
+
import * as path25 from "node:path";
|
|
13422
14256
|
var isWin3 = os7.platform() === "win32";
|
|
13423
14257
|
async function checkExecKillCommand(cmd, args) {
|
|
13424
14258
|
if (!cmd) return { blocked: false };
|
|
@@ -13631,7 +14465,7 @@ async function checkKillTarget(target) {
|
|
|
13631
14465
|
reason: `Blocked: kill ${target.signal} '${target.name}' targets a WrongStack process name.`
|
|
13632
14466
|
};
|
|
13633
14467
|
}
|
|
13634
|
-
const currentImage =
|
|
14468
|
+
const currentImage = path25.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
|
|
13635
14469
|
const targetsNodeRuntime = nameLower === "node" || nameLower.startsWith("node");
|
|
13636
14470
|
if (targetsNodeRuntime && currentImage === "node") {
|
|
13637
14471
|
return {
|
|
@@ -14447,7 +15281,7 @@ var execTool = {
|
|
|
14447
15281
|
command: cmd,
|
|
14448
15282
|
args: input.args ?? [],
|
|
14449
15283
|
stdout: "",
|
|
14450
|
-
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.`,
|
|
15284
|
+
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.`,
|
|
14451
15285
|
exitCode: 1,
|
|
14452
15286
|
truncated: false,
|
|
14453
15287
|
allowed: false,
|
|
@@ -14659,18 +15493,17 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
14659
15493
|
}
|
|
14660
15494
|
|
|
14661
15495
|
// src/fetch.ts
|
|
14662
|
-
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
15496
|
+
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core/types";
|
|
14663
15497
|
import TurndownService from "turndown";
|
|
14664
15498
|
|
|
14665
15499
|
// src/_fetch-guard.ts
|
|
14666
15500
|
import * as dns2 from "node:dns/promises";
|
|
14667
15501
|
import * as net3 from "node:net";
|
|
14668
15502
|
import {
|
|
14669
|
-
FetchError,
|
|
14670
|
-
ToolValidationError as ToolValidationError2,
|
|
14671
15503
|
isPrivateIPv4 as isPrivateIPv42,
|
|
14672
15504
|
isPrivateIPv6 as isPrivateIPv62
|
|
14673
|
-
} from "@wrongstack/core";
|
|
15505
|
+
} from "@wrongstack/core/utils";
|
|
15506
|
+
import { FetchError, ToolValidationError as ToolValidationError2 } from "@wrongstack/core/types";
|
|
14674
15507
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
14675
15508
|
var nativeGlobalFetch = globalThis.fetch;
|
|
14676
15509
|
var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
|
|
@@ -15168,7 +16001,8 @@ init_util();
|
|
|
15168
16001
|
import { spawn as spawn9 } from "node:child_process";
|
|
15169
16002
|
import { statSync as statSync3 } from "node:fs";
|
|
15170
16003
|
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
15171
|
-
import { assessCommitSafety
|
|
16004
|
+
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
16005
|
+
import { buildChildEnv as buildChildEnv4 } from "@wrongstack/core/utils";
|
|
15172
16006
|
var TIMEOUT_MS2 = 3e4;
|
|
15173
16007
|
var MAX_OUTPUT3 = 1e5;
|
|
15174
16008
|
var gitTool = {
|
|
@@ -15448,8 +16282,8 @@ function runGit2(args, cwd, signal) {
|
|
|
15448
16282
|
|
|
15449
16283
|
// src/glob.ts
|
|
15450
16284
|
import * as fs20 from "node:fs/promises";
|
|
15451
|
-
import * as
|
|
15452
|
-
import { compileGlob as
|
|
16285
|
+
import * as path26 from "node:path";
|
|
16286
|
+
import { compileGlob as compileGlob2, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS2 } from "@wrongstack/core/utils";
|
|
15453
16287
|
|
|
15454
16288
|
// src/_concurrency.ts
|
|
15455
16289
|
async function mapWithConcurrency(items, limit, fn) {
|
|
@@ -15470,7 +16304,7 @@ async function mapWithConcurrency(items, limit, fn) {
|
|
|
15470
16304
|
|
|
15471
16305
|
// src/glob.ts
|
|
15472
16306
|
init_util();
|
|
15473
|
-
var DEFAULT_IGNORE2 =
|
|
16307
|
+
var DEFAULT_IGNORE2 = DEFAULT_WALK_IGNORE_DIRS2;
|
|
15474
16308
|
var WALK_CONCURRENCY = 16;
|
|
15475
16309
|
var globTool = {
|
|
15476
16310
|
name: "glob",
|
|
@@ -15510,8 +16344,8 @@ var globTool = {
|
|
|
15510
16344
|
const signal = opts?.signal;
|
|
15511
16345
|
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
15512
16346
|
const limit = Math.max(1, Math.min(input.limit ?? 1e3, 5e3));
|
|
15513
|
-
const
|
|
15514
|
-
const re =
|
|
16347
|
+
const isGitIgnored = await loadGitignoreMatcher(base);
|
|
16348
|
+
const re = compileGlob2(input.pattern);
|
|
15515
16349
|
const results = [];
|
|
15516
16350
|
let truncated = false;
|
|
15517
16351
|
const pushResult = async (full) => {
|
|
@@ -15550,12 +16384,13 @@ var globTool = {
|
|
|
15550
16384
|
for (const e of entries) {
|
|
15551
16385
|
const name = e.name;
|
|
15552
16386
|
if (DEFAULT_IGNORE2.includes(name)) continue;
|
|
15553
|
-
if (ignored.includes(name)) continue;
|
|
15554
16387
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
15555
|
-
const full =
|
|
16388
|
+
const full = path26.join(dir, name);
|
|
15556
16389
|
if (e.isDirectory()) {
|
|
16390
|
+
if (isGitIgnored(rel, true)) continue;
|
|
15557
16391
|
subdirs.push({ full, rel });
|
|
15558
16392
|
} else if (e.isFile()) {
|
|
16393
|
+
if (isGitIgnored(rel, false)) continue;
|
|
15559
16394
|
re.lastIndex = 0;
|
|
15560
16395
|
const relMatch = re.test(rel);
|
|
15561
16396
|
re.lastIndex = 0;
|
|
@@ -15567,10 +16402,12 @@ var globTool = {
|
|
|
15567
16402
|
try {
|
|
15568
16403
|
const st = await fs20.stat(full);
|
|
15569
16404
|
if (st.isDirectory()) {
|
|
16405
|
+
if (isGitIgnored(rel, true)) continue;
|
|
15570
16406
|
const real = await fs20.realpath(full);
|
|
15571
16407
|
await assertRealInsideRoot(real, ctx);
|
|
15572
16408
|
subdirs.push({ full, rel });
|
|
15573
16409
|
} else if (st.isFile()) {
|
|
16410
|
+
if (isGitIgnored(rel, false)) continue;
|
|
15574
16411
|
const real = await fs20.realpath(full);
|
|
15575
16412
|
await assertRealInsideRoot(real, ctx);
|
|
15576
16413
|
re.lastIndex = 0;
|
|
@@ -15594,21 +16431,14 @@ var globTool = {
|
|
|
15594
16431
|
return { files: results.map((r) => r.rel), truncated };
|
|
15595
16432
|
}
|
|
15596
16433
|
};
|
|
15597
|
-
async function readGitignore(dir) {
|
|
15598
|
-
try {
|
|
15599
|
-
const raw = await fs20.readFile(path25.join(dir, ".gitignore"), "utf8");
|
|
15600
|
-
return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
15601
|
-
} catch {
|
|
15602
|
-
return [];
|
|
15603
|
-
}
|
|
15604
|
-
}
|
|
15605
16434
|
|
|
15606
16435
|
// src/grep.ts
|
|
15607
|
-
import { expectDefined as expectDefined7 } from "@wrongstack/core";
|
|
16436
|
+
import { expectDefined as expectDefined7 } from "@wrongstack/core/utils";
|
|
15608
16437
|
import { spawn as spawn10 } from "node:child_process";
|
|
15609
16438
|
import * as fs21 from "node:fs/promises";
|
|
15610
|
-
import * as
|
|
15611
|
-
import {
|
|
16439
|
+
import * as path27 from "node:path";
|
|
16440
|
+
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
16441
|
+
import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS3 } from "@wrongstack/core/utils";
|
|
15612
16442
|
|
|
15613
16443
|
// src/_regex.ts
|
|
15614
16444
|
var MAX_PATTERN_LEN = 256;
|
|
@@ -15657,7 +16487,7 @@ function capSubject(line) {
|
|
|
15657
16487
|
|
|
15658
16488
|
// src/grep.ts
|
|
15659
16489
|
init_util();
|
|
15660
|
-
var DEFAULT_IGNORE3 =
|
|
16490
|
+
var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
|
|
15661
16491
|
var NATIVE_SCAN_CONCURRENCY = 32;
|
|
15662
16492
|
var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
|
|
15663
16493
|
var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
@@ -15774,6 +16604,10 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
15774
16604
|
for (const ignored of DEFAULT_IGNORE3) {
|
|
15775
16605
|
args.push("--glob", `!${ignored}/**`, "--glob", `!**/${ignored}/**`);
|
|
15776
16606
|
}
|
|
16607
|
+
const gitignorePath = path27.join(base, ".gitignore");
|
|
16608
|
+
if (await fs21.access(gitignorePath).then(() => true, () => false)) {
|
|
16609
|
+
args.push("--ignore-file", gitignorePath);
|
|
16610
|
+
}
|
|
15777
16611
|
if (input.glob) args.push("--glob", input.glob);
|
|
15778
16612
|
args.push("--", input.pattern, base);
|
|
15779
16613
|
const matches = [];
|
|
@@ -15898,7 +16732,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15898
16732
|
});
|
|
15899
16733
|
}
|
|
15900
16734
|
const re = compiled.regex;
|
|
15901
|
-
const globRe = input.glob ?
|
|
16735
|
+
const globRe = input.glob ? compileGlob3(input.glob) : null;
|
|
16736
|
+
const isGitIgnored = await loadGitignoreMatcher(base);
|
|
15902
16737
|
const matches = [];
|
|
15903
16738
|
const countOnlyFirstHit = mode === "count" && limit === 1;
|
|
15904
16739
|
const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
|
|
@@ -15986,7 +16821,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15986
16821
|
} catch {
|
|
15987
16822
|
}
|
|
15988
16823
|
};
|
|
15989
|
-
const walk2 = async (dir) => {
|
|
16824
|
+
const walk2 = async (dir, relPrefix) => {
|
|
15990
16825
|
if (stopped || signal.aborted) return;
|
|
15991
16826
|
let entries;
|
|
15992
16827
|
try {
|
|
@@ -16000,17 +16835,24 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
16000
16835
|
if (stopped) return;
|
|
16001
16836
|
if (DEFAULT_IGNORE3.includes(e.name)) continue;
|
|
16002
16837
|
if (e.isSymbolicLink()) continue;
|
|
16003
|
-
const
|
|
16838
|
+
const rel = relPrefix ? `${relPrefix}/${e.name}` : e.name;
|
|
16839
|
+
const full = path27.join(dir, e.name);
|
|
16004
16840
|
if (e.isDirectory()) {
|
|
16005
|
-
|
|
16841
|
+
if (isGitIgnored(rel, true)) continue;
|
|
16842
|
+
subdirs.push({ full, rel });
|
|
16006
16843
|
} else if (e.isFile()) {
|
|
16844
|
+
if (isGitIgnored(rel, false)) continue;
|
|
16007
16845
|
files.push({ full, name: e.name });
|
|
16008
16846
|
}
|
|
16009
16847
|
}
|
|
16010
16848
|
await mapWithConcurrency(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
|
|
16011
|
-
await mapWithConcurrency(
|
|
16849
|
+
await mapWithConcurrency(
|
|
16850
|
+
subdirs,
|
|
16851
|
+
Math.min(16, NATIVE_SCAN_CONCURRENCY),
|
|
16852
|
+
({ full, rel }) => walk2(full, rel)
|
|
16853
|
+
);
|
|
16012
16854
|
};
|
|
16013
|
-
await walk2(base);
|
|
16855
|
+
await walk2(base, "");
|
|
16014
16856
|
return {
|
|
16015
16857
|
matches,
|
|
16016
16858
|
count: total,
|
|
@@ -16024,7 +16866,10 @@ init_spawn_stream();
|
|
|
16024
16866
|
init_util();
|
|
16025
16867
|
init_legacy_bridge();
|
|
16026
16868
|
import { join as join21 } from "node:path";
|
|
16027
|
-
import {
|
|
16869
|
+
import {
|
|
16870
|
+
detectEcosystem as detectPackageEcosystem,
|
|
16871
|
+
recordPackageAction
|
|
16872
|
+
} from "@wrongstack/core/coordination";
|
|
16028
16873
|
var installTool = {
|
|
16029
16874
|
name: "install",
|
|
16030
16875
|
category: "Package Management",
|
|
@@ -16210,7 +17055,7 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
16210
17055
|
// src/json.ts
|
|
16211
17056
|
init_util();
|
|
16212
17057
|
import * as fs22 from "node:fs/promises";
|
|
16213
|
-
import { deepMerge as deepMergeCore } from "@wrongstack/core";
|
|
17058
|
+
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
16214
17059
|
var jsonTool = {
|
|
16215
17060
|
name: "json",
|
|
16216
17061
|
category: "Data",
|
|
@@ -16588,56 +17433,56 @@ function jmespathSearch(data, query) {
|
|
|
16588
17433
|
}
|
|
16589
17434
|
function validateJsonSchema(data, schema) {
|
|
16590
17435
|
const errors = [];
|
|
16591
|
-
function check(value, s,
|
|
17436
|
+
function check(value, s, path35) {
|
|
16592
17437
|
if (s["type"]) {
|
|
16593
17438
|
const expectedType = s["type"];
|
|
16594
17439
|
const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
16595
17440
|
if (expectedType === "integer") {
|
|
16596
|
-
if (!Number.isInteger(value)) errors.push(`${
|
|
17441
|
+
if (!Number.isInteger(value)) errors.push(`${path35}: expected integer, got ${actualType}`);
|
|
16597
17442
|
} else if (expectedType !== actualType) {
|
|
16598
|
-
errors.push(`${
|
|
17443
|
+
errors.push(`${path35}: expected ${expectedType}, got ${actualType}`);
|
|
16599
17444
|
}
|
|
16600
17445
|
}
|
|
16601
17446
|
if (typeof value === "string" && s["format"] === "uri" && value) {
|
|
16602
17447
|
try {
|
|
16603
17448
|
new URL(value);
|
|
16604
17449
|
} catch {
|
|
16605
|
-
errors.push(`${
|
|
17450
|
+
errors.push(`${path35}: not a valid URI`);
|
|
16606
17451
|
}
|
|
16607
17452
|
}
|
|
16608
17453
|
if (typeof value === "string" && s["pattern"]) {
|
|
16609
17454
|
const re = new RegExp(s["pattern"]);
|
|
16610
|
-
if (!re.test(value)) errors.push(`${
|
|
17455
|
+
if (!re.test(value)) errors.push(`${path35}: does not match pattern ${s["pattern"]}`);
|
|
16611
17456
|
}
|
|
16612
17457
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
16613
|
-
errors.push(`${
|
|
17458
|
+
errors.push(`${path35}: string too short (min ${s["minLength"]})`);
|
|
16614
17459
|
}
|
|
16615
17460
|
if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
|
|
16616
|
-
errors.push(`${
|
|
17461
|
+
errors.push(`${path35}: string too long (max ${s["maxLength"]})`);
|
|
16617
17462
|
}
|
|
16618
17463
|
if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
|
|
16619
|
-
errors.push(`${
|
|
17464
|
+
errors.push(`${path35}: below minimum ${s["minimum"]}`);
|
|
16620
17465
|
}
|
|
16621
17466
|
if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
|
|
16622
|
-
errors.push(`${
|
|
17467
|
+
errors.push(`${path35}: above maximum ${s["maximum"]}`);
|
|
16623
17468
|
}
|
|
16624
17469
|
if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
|
|
16625
17470
|
for (let i = 0; i < value.length; i++) {
|
|
16626
|
-
check(value[i], s["items"], `${
|
|
17471
|
+
check(value[i], s["items"], `${path35}[${i}]`);
|
|
16627
17472
|
}
|
|
16628
17473
|
}
|
|
16629
17474
|
if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
|
|
16630
17475
|
const props = s["properties"];
|
|
16631
17476
|
for (const [k, propSchema] of Object.entries(props)) {
|
|
16632
|
-
check(value[k], propSchema, `${
|
|
17477
|
+
check(value[k], propSchema, `${path35}.${k}`);
|
|
16633
17478
|
}
|
|
16634
17479
|
}
|
|
16635
17480
|
}
|
|
16636
17481
|
check(data, schema, "$");
|
|
16637
17482
|
return { valid: errors.length === 0, errors };
|
|
16638
17483
|
}
|
|
16639
|
-
function simpleQuery(data,
|
|
16640
|
-
const parts =
|
|
17484
|
+
function simpleQuery(data, path35) {
|
|
17485
|
+
const parts = path35.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
16641
17486
|
let current = data;
|
|
16642
17487
|
for (const part of parts) {
|
|
16643
17488
|
if (current === null || current === void 0) return void 0;
|
|
@@ -16688,7 +17533,8 @@ function toYaml(data, indent = 0) {
|
|
|
16688
17533
|
|
|
16689
17534
|
// src/kanban.ts
|
|
16690
17535
|
import { randomUUID } from "node:crypto";
|
|
16691
|
-
import { deserializeTaskGraph as deserializeTaskGraph2,
|
|
17536
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
17537
|
+
import { loadTasks as loadTasks2 } from "@wrongstack/core/storage";
|
|
16692
17538
|
import {
|
|
16693
17539
|
addCheckToTask,
|
|
16694
17540
|
addColumn,
|
|
@@ -16735,20 +17581,47 @@ import {
|
|
|
16735
17581
|
updateColumn,
|
|
16736
17582
|
updateGoalMetricOnTask,
|
|
16737
17583
|
updateTask,
|
|
16738
|
-
updateTaskAssignment
|
|
17584
|
+
updateTaskAssignment,
|
|
17585
|
+
verifyTaskCompletion,
|
|
17586
|
+
finalizeTaskCompletion,
|
|
17587
|
+
assessTaskAtomicity,
|
|
17588
|
+
proposeTaskDecomposition
|
|
16739
17589
|
} from "@wrongstack/kanban";
|
|
16740
17590
|
|
|
17591
|
+
// src/kanban-evidence-bridge.ts
|
|
17592
|
+
import { recordCompletedWorkEvidence } from "@wrongstack/core/utils";
|
|
17593
|
+
function kanbanEvidenceKey(boardId, taskId) {
|
|
17594
|
+
return `kanban:${boardId}:${taskId}`;
|
|
17595
|
+
}
|
|
17596
|
+
function kanbanEvidencePointer(boardId, taskId) {
|
|
17597
|
+
return `kanban://${boardId}/${taskId}#verificationReport`;
|
|
17598
|
+
}
|
|
17599
|
+
function recordKanbanVerificationEvidence(ctx, report) {
|
|
17600
|
+
try {
|
|
17601
|
+
const passed = report.checks.filter((check) => check.status === "passed").length;
|
|
17602
|
+
const completedAt = Date.parse(report.completedAt);
|
|
17603
|
+
recordCompletedWorkEvidence(ctx, {
|
|
17604
|
+
key: kanbanEvidenceKey(report.boardId, report.taskId),
|
|
17605
|
+
source: "verification",
|
|
17606
|
+
summary: `${report.taskTitle} \u2014 verification ${report.verdict} (${passed}/${report.checks.length} checks)`,
|
|
17607
|
+
...Number.isFinite(completedAt) ? { completedAt } : {},
|
|
17608
|
+
evidence: kanbanEvidencePointer(report.boardId, report.taskId)
|
|
17609
|
+
});
|
|
17610
|
+
} catch {
|
|
17611
|
+
}
|
|
17612
|
+
}
|
|
17613
|
+
|
|
16741
17614
|
// src/session-kanban.ts
|
|
16742
17615
|
import { watch } from "node:fs";
|
|
16743
|
-
import { basename as
|
|
17616
|
+
import { basename as basename11, dirname as dirname13 } from "node:path";
|
|
17617
|
+
import { GlobalMailbox } from "@wrongstack/core/coordination";
|
|
16744
17618
|
import {
|
|
16745
|
-
deserializeTaskGraph,
|
|
16746
|
-
GlobalMailbox,
|
|
16747
17619
|
loadPlan,
|
|
16748
17620
|
loadTasks,
|
|
16749
17621
|
mutatePlan,
|
|
16750
17622
|
mutateTasks
|
|
16751
|
-
} from "@wrongstack/core";
|
|
17623
|
+
} from "@wrongstack/core/storage";
|
|
17624
|
+
import { deserializeTaskGraph } from "@wrongstack/core/tasking";
|
|
16752
17625
|
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
16753
17626
|
import {
|
|
16754
17627
|
createBoard,
|
|
@@ -17062,8 +17935,10 @@ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
|
17062
17935
|
"session-plan"
|
|
17063
17936
|
);
|
|
17064
17937
|
}
|
|
17065
|
-
function fireAndForget(work) {
|
|
17066
|
-
void work.catch(() => {
|
|
17938
|
+
function fireAndForget(context, work) {
|
|
17939
|
+
void work.catch((err) => {
|
|
17940
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17941
|
+
console.warn(JSON.stringify({ level: "warn", event: "session-kanban", context, message, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
17067
17942
|
});
|
|
17068
17943
|
}
|
|
17069
17944
|
function broadcastTodoUpdate(context, todos) {
|
|
@@ -17137,7 +18012,10 @@ function attachSessionKanbanMirror(context) {
|
|
|
17137
18012
|
if (currentSessionId === registeredSessionId) return;
|
|
17138
18013
|
if (registeredSessionId) {
|
|
17139
18014
|
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
17140
|
-
fireAndForget(
|
|
18015
|
+
fireAndForget(
|
|
18016
|
+
"cleanup-board",
|
|
18017
|
+
cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId)
|
|
18018
|
+
);
|
|
17141
18019
|
}
|
|
17142
18020
|
registeredSessionId = currentSessionId;
|
|
17143
18021
|
if (registeredSessionId) {
|
|
@@ -17187,7 +18065,7 @@ function attachSessionKanbanMirror(context) {
|
|
|
17187
18065
|
(_event, filename) => {
|
|
17188
18066
|
if (filename?.toString() !== boardFileName) return;
|
|
17189
18067
|
if (boardTimer) clearTimeout(boardTimer);
|
|
17190
|
-
boardTimer = setTimeout(() => fireAndForget(refreshBoard()), 60);
|
|
18068
|
+
boardTimer = setTimeout(() => fireAndForget("refresh-board", refreshBoard()), 60);
|
|
17191
18069
|
}
|
|
17192
18070
|
);
|
|
17193
18071
|
const touchPresence = () => touchKanbanPresence(context.projectRoot, board.id, {
|
|
@@ -17195,9 +18073,9 @@ function attachSessionKanbanMirror(context) {
|
|
|
17195
18073
|
agentId: context.agentId,
|
|
17196
18074
|
agentName: context.agentName
|
|
17197
18075
|
});
|
|
17198
|
-
fireAndForget(touchPresence());
|
|
18076
|
+
fireAndForget("touch-presence", touchPresence());
|
|
17199
18077
|
if (presenceTimer) clearInterval(presenceTimer);
|
|
17200
|
-
presenceTimer = setInterval(() => fireAndForget(touchPresence()), 6e4);
|
|
18078
|
+
presenceTimer = setInterval(() => fireAndForget("touch-presence", touchPresence()), 6e4);
|
|
17201
18079
|
presenceTimer.unref?.();
|
|
17202
18080
|
boardWatcher.on("error", () => {
|
|
17203
18081
|
boardWatcher?.close();
|
|
@@ -17222,11 +18100,11 @@ function attachSessionKanbanMirror(context) {
|
|
|
17222
18100
|
const name = filename?.toString();
|
|
17223
18101
|
const currentPlanPath = context.meta["plan.path"];
|
|
17224
18102
|
const currentTaskPath = context.meta["task.path"];
|
|
17225
|
-
const planName = typeof currentPlanPath === "string" ?
|
|
17226
|
-
const taskName = typeof currentTaskPath === "string" ?
|
|
18103
|
+
const planName = typeof currentPlanPath === "string" ? basename11(currentPlanPath) : "";
|
|
18104
|
+
const taskName = typeof currentTaskPath === "string" ? basename11(currentTaskPath) : "";
|
|
17227
18105
|
if (name && name !== planName && name !== taskName) return;
|
|
17228
18106
|
if (timer) clearTimeout(timer);
|
|
17229
|
-
timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
|
|
18107
|
+
timer = setTimeout(() => fireAndForget("refresh-files", refreshFiles()), 60);
|
|
17230
18108
|
});
|
|
17231
18109
|
watcher.on("error", () => watcher?.close());
|
|
17232
18110
|
} catch {
|
|
@@ -17246,13 +18124,13 @@ function attachSessionKanbanMirror(context) {
|
|
|
17246
18124
|
if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path")) {
|
|
17247
18125
|
syncActiveSessionRegistration();
|
|
17248
18126
|
configureWatcher();
|
|
17249
|
-
fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
17250
|
-
fireAndForget(configureBoardWatcher());
|
|
17251
|
-
fireAndForget(refreshFiles());
|
|
18127
|
+
fireAndForget("ensure-board", ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
18128
|
+
fireAndForget("configure-watcher", configureBoardWatcher());
|
|
18129
|
+
fireAndForget("refresh-files", refreshFiles());
|
|
17252
18130
|
}
|
|
17253
18131
|
});
|
|
17254
18132
|
configureWatcher();
|
|
17255
|
-
fireAndForget(configureBoardWatcher());
|
|
18133
|
+
fireAndForget("configure-watcher", configureBoardWatcher());
|
|
17256
18134
|
const detach = () => {
|
|
17257
18135
|
unsubscribe();
|
|
17258
18136
|
if (timer) clearTimeout(timer);
|
|
@@ -17263,7 +18141,10 @@ function attachSessionKanbanMirror(context) {
|
|
|
17263
18141
|
bindings.delete(context);
|
|
17264
18142
|
if (attachedProjectRoot && registeredSessionId) {
|
|
17265
18143
|
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
17266
|
-
fireAndForget(
|
|
18144
|
+
fireAndForget(
|
|
18145
|
+
"cleanup-board",
|
|
18146
|
+
cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId)
|
|
18147
|
+
);
|
|
17267
18148
|
registeredSessionId = "";
|
|
17268
18149
|
}
|
|
17269
18150
|
};
|
|
@@ -17406,8 +18287,8 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
17406
18287
|
var kanbanTool = {
|
|
17407
18288
|
name: "kanban",
|
|
17408
18289
|
category: "Project",
|
|
17409
|
-
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.",
|
|
17410
|
-
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.",
|
|
18290
|
+
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.",
|
|
18291
|
+
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.",
|
|
17411
18292
|
permission: "confirm",
|
|
17412
18293
|
mutating: true,
|
|
17413
18294
|
capabilities: ["fs.write"],
|
|
@@ -17463,7 +18344,11 @@ var kanbanTool = {
|
|
|
17463
18344
|
"add_check",
|
|
17464
18345
|
"update_check",
|
|
17465
18346
|
"add_note",
|
|
17466
|
-
"add_link"
|
|
18347
|
+
"add_link",
|
|
18348
|
+
"verify_completion",
|
|
18349
|
+
"split_atomic",
|
|
18350
|
+
"assess_atomicity",
|
|
18351
|
+
"propose_decomposition"
|
|
17467
18352
|
]
|
|
17468
18353
|
},
|
|
17469
18354
|
boardId: { type: "string" },
|
|
@@ -17518,6 +18403,7 @@ var kanbanTool = {
|
|
|
17518
18403
|
runTaskId: { type: "string" },
|
|
17519
18404
|
lastResult: { type: "string" },
|
|
17520
18405
|
error: { type: "string" },
|
|
18406
|
+
expectedLeaseId: { type: "string" },
|
|
17521
18407
|
assignmentStatus: {
|
|
17522
18408
|
type: "string",
|
|
17523
18409
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
@@ -17552,6 +18438,7 @@ var kanbanTool = {
|
|
|
17552
18438
|
lastFailureKind: { type: "string" },
|
|
17553
18439
|
dependsOn: { type: "array", items: { type: "string" } },
|
|
17554
18440
|
estimatedHours: { type: "number" },
|
|
18441
|
+
actualHours: { type: "number" },
|
|
17555
18442
|
taskGraph: { type: "object" },
|
|
17556
18443
|
graphId: { type: "string" },
|
|
17557
18444
|
specId: { type: "string" },
|
|
@@ -17597,7 +18484,24 @@ var kanbanTool = {
|
|
|
17597
18484
|
includeCompletedTasks: { type: "boolean" },
|
|
17598
18485
|
preserveAssignment: { type: "boolean" },
|
|
17599
18486
|
preserveDependencies: { type: "boolean" },
|
|
17600
|
-
moveTasksToColumnId: { type: "string" }
|
|
18487
|
+
moveTasksToColumnId: { type: "string" },
|
|
18488
|
+
atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
|
|
18489
|
+
atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
|
|
18490
|
+
gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
|
|
18491
|
+
subtasks: {
|
|
18492
|
+
type: "array",
|
|
18493
|
+
minItems: 2,
|
|
18494
|
+
items: {
|
|
18495
|
+
type: "object",
|
|
18496
|
+
properties: {
|
|
18497
|
+
title: { type: "string" },
|
|
18498
|
+
description: { type: "string" },
|
|
18499
|
+
successCriteria: { type: "array", items: { type: "string" } },
|
|
18500
|
+
dependsOnIndex: { type: "array", items: { type: "number" } }
|
|
18501
|
+
},
|
|
18502
|
+
required: ["title"]
|
|
18503
|
+
}
|
|
18504
|
+
}
|
|
17601
18505
|
},
|
|
17602
18506
|
required: ["action"]
|
|
17603
18507
|
},
|
|
@@ -17637,7 +18541,14 @@ var kanbanTool = {
|
|
|
17637
18541
|
title: input.title,
|
|
17638
18542
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
17639
18543
|
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
17640
|
-
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {}
|
|
18544
|
+
...input.generatedBy !== void 0 ? { generatedBy: input.generatedBy } : {},
|
|
18545
|
+
...input.atomicityMode !== void 0 ? {
|
|
18546
|
+
atomicity: {
|
|
18547
|
+
mode: input.atomicityMode,
|
|
18548
|
+
decomposition: input.atomicityDecomposition ?? "propose"
|
|
18549
|
+
}
|
|
18550
|
+
} : {},
|
|
18551
|
+
...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
|
|
17641
18552
|
});
|
|
17642
18553
|
return { ok: true, message: `Board created: ${board.title}`, board };
|
|
17643
18554
|
}
|
|
@@ -17646,7 +18557,14 @@ var kanbanTool = {
|
|
|
17646
18557
|
const board = await updateBoard2(projectRoot, input.boardId, {
|
|
17647
18558
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
17648
18559
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
17649
|
-
...input.tags !== void 0 ? { tags: input.tags } : {}
|
|
18560
|
+
...input.tags !== void 0 ? { tags: input.tags } : {},
|
|
18561
|
+
...input.atomicityMode !== void 0 ? {
|
|
18562
|
+
atomicity: {
|
|
18563
|
+
mode: input.atomicityMode,
|
|
18564
|
+
decomposition: input.atomicityDecomposition ?? "propose"
|
|
18565
|
+
}
|
|
18566
|
+
} : {},
|
|
18567
|
+
...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
|
|
17650
18568
|
});
|
|
17651
18569
|
return board ? okBoard(board, "Board updated.") : fail("Board not found.");
|
|
17652
18570
|
}
|
|
@@ -17854,30 +18772,18 @@ var kanbanTool = {
|
|
|
17854
18772
|
case "add_task": {
|
|
17855
18773
|
if (!input.boardId || !input.title) return fail("add_task requires boardId and title.");
|
|
17856
18774
|
const result2 = await addTask(projectRoot, input.boardId, taskInput(input));
|
|
17857
|
-
|
|
18775
|
+
if (!result2) return fail("Board not found.");
|
|
18776
|
+
return okTask(
|
|
18777
|
+
result2.board,
|
|
18778
|
+
result2.task,
|
|
18779
|
+
`Task added.${atomicityNudge(result2.task)}`
|
|
18780
|
+
);
|
|
17858
18781
|
}
|
|
17859
18782
|
case "split_task": {
|
|
17860
18783
|
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
17861
18784
|
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
17862
18785
|
}
|
|
17863
|
-
|
|
17864
|
-
titles: input.childTitles,
|
|
17865
|
-
...input.targetColumnId !== void 0 ? { columnId: input.targetColumnId } : {},
|
|
17866
|
-
...input.inheritAssignment !== void 0 ? { inheritAssignment: input.inheritAssignment } : {},
|
|
17867
|
-
...input.inheritLabels !== void 0 ? { inheritLabels: input.inheritLabels } : {},
|
|
17868
|
-
...input.inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria: input.inheritSuccessCriteria } : {},
|
|
17869
|
-
...input.inheritGoalMetrics !== void 0 ? { inheritGoalMetrics: input.inheritGoalMetrics } : {},
|
|
17870
|
-
...input.inheritDependencies !== void 0 ? { inheritDependencies: input.inheritDependencies } : {},
|
|
17871
|
-
...input.chainChildren !== void 0 ? { chainChildren: input.chainChildren } : {},
|
|
17872
|
-
...input.rewireDependents !== void 0 ? { rewireDependents: input.rewireDependents } : {}
|
|
17873
|
-
});
|
|
17874
|
-
return result2 ? {
|
|
17875
|
-
ok: true,
|
|
17876
|
-
message: `${result2.children.length} child task(s) created.`,
|
|
17877
|
-
board: result2.board,
|
|
17878
|
-
task: result2.parent,
|
|
17879
|
-
children: result2.children
|
|
17880
|
-
} : fail("Task not found.");
|
|
18786
|
+
return handleSplitTask(projectRoot, input, {});
|
|
17881
18787
|
}
|
|
17882
18788
|
case "merge_tasks": {
|
|
17883
18789
|
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
@@ -17951,6 +18857,19 @@ var kanbanTool = {
|
|
|
17951
18857
|
"transition_task requires boardId, taskId, lifecycleStage, author, and transitionComment."
|
|
17952
18858
|
);
|
|
17953
18859
|
}
|
|
18860
|
+
if (input.lifecycleStage === "done") {
|
|
18861
|
+
const boardBefore = await getBoard2(projectRoot, input.boardId);
|
|
18862
|
+
const taskBefore = boardBefore ? await getTask(projectRoot, input.boardId, input.taskId) : null;
|
|
18863
|
+
if (boardBefore && taskBefore && !taskBefore.verificationReport && (taskBefore.atomic || Boolean(taskBefore.successCriteria?.length))) {
|
|
18864
|
+
const preGate = await verifyTaskCompletion(projectRoot, input.boardId, taskBefore.id, {
|
|
18865
|
+
persist: false
|
|
18866
|
+
});
|
|
18867
|
+
await updateTask(projectRoot, input.boardId, taskBefore.id, {
|
|
18868
|
+
verificationReport: preGate.report,
|
|
18869
|
+
successCriteria: preGate.task.successCriteria
|
|
18870
|
+
});
|
|
18871
|
+
}
|
|
18872
|
+
}
|
|
17954
18873
|
const result2 = await transitionTask(projectRoot, input.boardId, input.taskId, {
|
|
17955
18874
|
to: input.lifecycleStage,
|
|
17956
18875
|
actor: input.author,
|
|
@@ -17965,6 +18884,9 @@ var kanbanTool = {
|
|
|
17965
18884
|
} : {},
|
|
17966
18885
|
patch: taskPatch(input)
|
|
17967
18886
|
});
|
|
18887
|
+
if (result2 && input.lifecycleStage === "done" && result2.task.verificationReport) {
|
|
18888
|
+
recordKanbanVerificationEvidence(ctx, result2.task.verificationReport);
|
|
18889
|
+
}
|
|
17968
18890
|
return result2 ? okTask(result2.board, result2.task, `Task advanced to ${result2.transition.to}.`) : fail("Board or task not found.");
|
|
17969
18891
|
}
|
|
17970
18892
|
case "move_task": {
|
|
@@ -18078,7 +19000,31 @@ var kanbanTool = {
|
|
|
18078
19000
|
// is atomic inside updateTaskAssignment's mutateBoard lock.
|
|
18079
19001
|
input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
|
|
18080
19002
|
);
|
|
18081
|
-
|
|
19003
|
+
if (!board) return fail("Task not found.");
|
|
19004
|
+
if (assignmentStatus === "completed") {
|
|
19005
|
+
const envGate = readEnvGateEnforcement();
|
|
19006
|
+
const finalized = await finalizeTaskCompletion(projectRoot, board.id, input.taskId, {
|
|
19007
|
+
...board.completionGate === void 0 && envGate !== void 0 ? { enforcement: envGate } : {},
|
|
19008
|
+
...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
|
|
19009
|
+
});
|
|
19010
|
+
if (finalized) {
|
|
19011
|
+
if (finalized.gate.report) {
|
|
19012
|
+
recordKanbanVerificationEvidence(ctx, finalized.gate.report);
|
|
19013
|
+
}
|
|
19014
|
+
const gateSummary = {
|
|
19015
|
+
enforcement: finalized.gate.enforcement,
|
|
19016
|
+
allowed: finalized.gate.allowed,
|
|
19017
|
+
verdict: finalized.gate.verdict,
|
|
19018
|
+
issues: finalized.gate.issues.map((issue) => issue.message)
|
|
19019
|
+
};
|
|
19020
|
+
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(" | ")}`;
|
|
19021
|
+
return {
|
|
19022
|
+
...okTask(finalized.board, finalized.task, `Assignment updated. ${gateMessage}`),
|
|
19023
|
+
gate: gateSummary
|
|
19024
|
+
};
|
|
19025
|
+
}
|
|
19026
|
+
}
|
|
19027
|
+
return okBoard(board, "Assignment updated.");
|
|
18082
19028
|
}
|
|
18083
19029
|
case "heartbeat_assignment": {
|
|
18084
19030
|
if (!input.boardId || !input.taskId) {
|
|
@@ -18242,6 +19188,83 @@ var kanbanTool = {
|
|
|
18242
19188
|
});
|
|
18243
19189
|
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
18244
19190
|
}
|
|
19191
|
+
case "assess_atomicity": {
|
|
19192
|
+
if (!input.boardId || !input.taskId) {
|
|
19193
|
+
return fail("assess_atomicity requires boardId and taskId.");
|
|
19194
|
+
}
|
|
19195
|
+
const result2 = await assessTaskAtomicity(projectRoot, input.boardId, input.taskId, {
|
|
19196
|
+
assessedBy: "agent",
|
|
19197
|
+
...ctx.agentId !== void 0 ? { eventContext: { actor: ctx.agentId } } : {}
|
|
19198
|
+
});
|
|
19199
|
+
if (!result2) return fail("Task not found.");
|
|
19200
|
+
const failing = result2.assessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason);
|
|
19201
|
+
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." : "";
|
|
19202
|
+
return okTask(
|
|
19203
|
+
result2.board,
|
|
19204
|
+
result2.task,
|
|
19205
|
+
`Atomicity verdict: ${result2.assessment.verdict} (score ${result2.assessment.score}).${guidance}`
|
|
19206
|
+
);
|
|
19207
|
+
}
|
|
19208
|
+
case "propose_decomposition": {
|
|
19209
|
+
if (!input.boardId || !input.taskId || !input.subtasks?.length) {
|
|
19210
|
+
return fail("propose_decomposition requires boardId, taskId, and subtasks (2+).");
|
|
19211
|
+
}
|
|
19212
|
+
if (input.subtasks.length < 2) {
|
|
19213
|
+
return fail("propose_decomposition requires at least two subtasks.");
|
|
19214
|
+
}
|
|
19215
|
+
const invalid = input.subtasks.find(
|
|
19216
|
+
(subtask) => typeof subtask?.title !== "string" || !subtask.title.trim()
|
|
19217
|
+
);
|
|
19218
|
+
if (invalid) return fail("Every proposed subtask needs a non-blank title.");
|
|
19219
|
+
const result2 = await proposeTaskDecomposition(
|
|
19220
|
+
projectRoot,
|
|
19221
|
+
input.boardId,
|
|
19222
|
+
input.taskId,
|
|
19223
|
+
{
|
|
19224
|
+
subtasks: input.subtasks,
|
|
19225
|
+
...input.note !== void 0 ? { rationale: input.note } : {},
|
|
19226
|
+
...ctx.agentId !== void 0 ? { proposedBy: ctx.agentId } : {}
|
|
19227
|
+
},
|
|
19228
|
+
ctx.agentId !== void 0 ? { actor: ctx.agentId } : {}
|
|
19229
|
+
);
|
|
19230
|
+
if (!result2) return fail("Task not found.");
|
|
19231
|
+
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.';
|
|
19232
|
+
return okTask(result2.board, result2.task, message);
|
|
19233
|
+
}
|
|
19234
|
+
case "verify_completion": {
|
|
19235
|
+
if (!input.boardId || !input.taskId) {
|
|
19236
|
+
return fail("verify_completion requires boardId and taskId.");
|
|
19237
|
+
}
|
|
19238
|
+
const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
|
|
19239
|
+
const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
|
|
19240
|
+
verificationReport: verResult.report,
|
|
19241
|
+
successCriteria: verResult.task.successCriteria
|
|
19242
|
+
});
|
|
19243
|
+
if (!persistedBoard) {
|
|
19244
|
+
return {
|
|
19245
|
+
ok: false,
|
|
19246
|
+
verdict: verResult.report.verdict,
|
|
19247
|
+
message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
|
|
19248
|
+
board: verResult.board
|
|
19249
|
+
};
|
|
19250
|
+
}
|
|
19251
|
+
recordKanbanVerificationEvidence(ctx, verResult.report);
|
|
19252
|
+
const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
|
|
19253
|
+
const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
|
|
19254
|
+
return {
|
|
19255
|
+
ok: deterministicVerdicts.includes(verResult.report.verdict),
|
|
19256
|
+
verdict: verResult.report.verdict,
|
|
19257
|
+
message: verResult.report.markdownSummary,
|
|
19258
|
+
board: persistedBoard,
|
|
19259
|
+
task: freshTask ?? verResult.task
|
|
19260
|
+
};
|
|
19261
|
+
}
|
|
19262
|
+
case "split_atomic": {
|
|
19263
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
19264
|
+
return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
|
|
19265
|
+
}
|
|
19266
|
+
return handleSplitTask(projectRoot, input, { atomic: true });
|
|
19267
|
+
}
|
|
18245
19268
|
default:
|
|
18246
19269
|
return fail(`Unknown kanban action: ${input.action}`);
|
|
18247
19270
|
}
|
|
@@ -18252,6 +19275,15 @@ var kanbanTool = {
|
|
|
18252
19275
|
}
|
|
18253
19276
|
}
|
|
18254
19277
|
};
|
|
19278
|
+
function atomicityNudge(task) {
|
|
19279
|
+
if (task.atomicityAssessment?.verdict !== "needs_decomposition") return "";
|
|
19280
|
+
const reasons = task.atomicityAssessment.criteria.filter((entry) => entry.score < 1).map((entry) => entry.reason).join(" | ");
|
|
19281
|
+
return ` Atomicity: needs_decomposition (score ${task.atomicityAssessment.score}) \u2014 call propose_decomposition with 2+ subtasks before dispatch. Reasons: ${reasons}`;
|
|
19282
|
+
}
|
|
19283
|
+
function readEnvGateEnforcement() {
|
|
19284
|
+
const raw = process.env["WRONGSTACK_KANBAN_GATE"]?.trim().toLowerCase();
|
|
19285
|
+
return raw === "strict" || raw === "soft" || raw === "off" ? raw : void 0;
|
|
19286
|
+
}
|
|
18255
19287
|
function fail(message) {
|
|
18256
19288
|
return { ok: false, message };
|
|
18257
19289
|
}
|
|
@@ -18261,6 +19293,50 @@ function okBoard(board, message = "Board loaded.") {
|
|
|
18261
19293
|
function okTask(board, task, message) {
|
|
18262
19294
|
return { ok: true, message, board, task };
|
|
18263
19295
|
}
|
|
19296
|
+
async function handleSplitTask(projectRoot, input, extraSplitOptions) {
|
|
19297
|
+
const boardId = input.boardId;
|
|
19298
|
+
const taskId = input.taskId;
|
|
19299
|
+
const childTitles = input.childTitles;
|
|
19300
|
+
if (!boardId || !taskId || !childTitles?.length) {
|
|
19301
|
+
return fail("split requires boardId, taskId, and at least one childTitles.");
|
|
19302
|
+
}
|
|
19303
|
+
const {
|
|
19304
|
+
targetColumnId,
|
|
19305
|
+
inheritAssignment,
|
|
19306
|
+
inheritLabels,
|
|
19307
|
+
inheritSuccessCriteria,
|
|
19308
|
+
inheritGoalMetrics,
|
|
19309
|
+
inheritDependencies,
|
|
19310
|
+
chainChildren,
|
|
19311
|
+
rewireDependents
|
|
19312
|
+
} = input;
|
|
19313
|
+
const result = await splitTask(projectRoot, boardId, taskId, {
|
|
19314
|
+
titles: childTitles,
|
|
19315
|
+
...extraSplitOptions,
|
|
19316
|
+
...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
|
|
19317
|
+
...inheritAssignment !== void 0 ? { inheritAssignment } : {},
|
|
19318
|
+
...inheritLabels !== void 0 ? { inheritLabels } : {},
|
|
19319
|
+
...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
|
|
19320
|
+
...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
|
|
19321
|
+
...inheritDependencies !== void 0 ? { inheritDependencies } : {},
|
|
19322
|
+
...chainChildren !== void 0 ? { chainChildren } : {},
|
|
19323
|
+
...rewireDependents !== void 0 ? { rewireDependents } : {}
|
|
19324
|
+
});
|
|
19325
|
+
if (!result) return fail("Task not found.");
|
|
19326
|
+
const freshParent = result.board.tasks?.find((t) => t.id === taskId);
|
|
19327
|
+
if (!freshParent) {
|
|
19328
|
+
return fail(
|
|
19329
|
+
`Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
|
|
19330
|
+
);
|
|
19331
|
+
}
|
|
19332
|
+
return {
|
|
19333
|
+
ok: true,
|
|
19334
|
+
message: `${result.children.length} child task(s) created.`,
|
|
19335
|
+
board: result.board,
|
|
19336
|
+
task: freshParent,
|
|
19337
|
+
children: result.children
|
|
19338
|
+
};
|
|
19339
|
+
}
|
|
18264
19340
|
async function requireBoard(projectRoot, boardId) {
|
|
18265
19341
|
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
18266
19342
|
}
|
|
@@ -18362,7 +19438,8 @@ function taskPatch(input) {
|
|
|
18362
19438
|
labels: input.labels,
|
|
18363
19439
|
assignedAgent: input.agentId,
|
|
18364
19440
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
18365
|
-
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
19441
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
19442
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
|
|
18366
19443
|
};
|
|
18367
19444
|
}
|
|
18368
19445
|
function assignmentInput(input) {
|
|
@@ -18544,7 +19621,7 @@ async function detectLinter(cwd) {
|
|
|
18544
19621
|
|
|
18545
19622
|
// src/logs.ts
|
|
18546
19623
|
import { spawn as spawn11 } from "node:child_process";
|
|
18547
|
-
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core";
|
|
19624
|
+
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core/utils";
|
|
18548
19625
|
init_util();
|
|
18549
19626
|
var logsTool = {
|
|
18550
19627
|
name: "logs",
|
|
@@ -18683,7 +19760,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
18683
19760
|
}
|
|
18684
19761
|
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
18685
19762
|
var MAX_TAIL_LINES = 1e5;
|
|
18686
|
-
async function fileLogs(
|
|
19763
|
+
async function fileLogs(path35, lines, filterRe, stream) {
|
|
18687
19764
|
const { createInterface } = await import("node:readline");
|
|
18688
19765
|
const { createReadStream: createReadStream2 } = await import("node:fs");
|
|
18689
19766
|
const entries = [];
|
|
@@ -18692,7 +19769,7 @@ async function fileLogs(path34, lines, filterRe, stream) {
|
|
|
18692
19769
|
let writeIdx = 0;
|
|
18693
19770
|
let totalLines = 0;
|
|
18694
19771
|
const rl = createInterface({
|
|
18695
|
-
input: createReadStream2(
|
|
19772
|
+
input: createReadStream2(path35),
|
|
18696
19773
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
18697
19774
|
});
|
|
18698
19775
|
for await (const line of rl) {
|
|
@@ -18713,7 +19790,7 @@ async function fileLogs(path34, lines, filterRe, stream) {
|
|
|
18713
19790
|
if (parsed) entries.push(parsed);
|
|
18714
19791
|
}
|
|
18715
19792
|
return {
|
|
18716
|
-
source:
|
|
19793
|
+
source: path35,
|
|
18717
19794
|
entries,
|
|
18718
19795
|
total: entries.length,
|
|
18719
19796
|
truncated: totalLines > effLines,
|
|
@@ -18760,7 +19837,7 @@ function parseLine(line) {
|
|
|
18760
19837
|
init_util();
|
|
18761
19838
|
init_win32_resolve();
|
|
18762
19839
|
import { spawn as spawn12 } from "node:child_process";
|
|
18763
|
-
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core";
|
|
19840
|
+
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core/utils";
|
|
18764
19841
|
var outdatedTool = {
|
|
18765
19842
|
name: "outdated",
|
|
18766
19843
|
category: "Package Management",
|
|
@@ -18945,8 +20022,8 @@ init_util();
|
|
|
18945
20022
|
import { spawn as spawn13 } from "node:child_process";
|
|
18946
20023
|
import * as fs23 from "node:fs/promises";
|
|
18947
20024
|
import * as os8 from "node:os";
|
|
18948
|
-
import * as
|
|
18949
|
-
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core";
|
|
20025
|
+
import * as path28 from "node:path";
|
|
20026
|
+
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core/utils";
|
|
18950
20027
|
var patchTool = {
|
|
18951
20028
|
name: "patch",
|
|
18952
20029
|
category: "Filesystem",
|
|
@@ -18981,9 +20058,9 @@ var patchTool = {
|
|
|
18981
20058
|
for (const t of targets) {
|
|
18982
20059
|
const stripped = stripPathComponents(t, strip);
|
|
18983
20060
|
if (!stripped) continue;
|
|
18984
|
-
const candidate =
|
|
18985
|
-
const rel =
|
|
18986
|
-
if (rel.startsWith("..") ||
|
|
20061
|
+
const candidate = path28.resolve(dir, stripped);
|
|
20062
|
+
const rel = path28.relative(ctx.projectRoot, candidate);
|
|
20063
|
+
if (rel.startsWith("..") || path28.isAbsolute(rel)) {
|
|
18987
20064
|
return {
|
|
18988
20065
|
applied: 0,
|
|
18989
20066
|
rejected: 1,
|
|
@@ -19000,11 +20077,11 @@ var patchTool = {
|
|
|
19000
20077
|
beforeContents.set(target, await readTextForTracking(target));
|
|
19001
20078
|
}
|
|
19002
20079
|
}
|
|
19003
|
-
const tmpDir = await fs23.mkdtemp(
|
|
20080
|
+
const tmpDir = await fs23.mkdtemp(path28.join(os8.tmpdir(), ".wstack_patch_"));
|
|
19004
20081
|
try {
|
|
19005
20082
|
await fs23.chmod(tmpDir, 448).catch(() => {
|
|
19006
20083
|
});
|
|
19007
|
-
const patchFile =
|
|
20084
|
+
const patchFile = path28.join(tmpDir, "in.diff");
|
|
19008
20085
|
await fs23.writeFile(patchFile, input.patch, { mode: 384 });
|
|
19009
20086
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
19010
20087
|
const result = await runPatch(args, dir, opts.signal);
|
|
@@ -19110,11 +20187,11 @@ import {
|
|
|
19110
20187
|
mutatePlan as mutatePlan2,
|
|
19111
20188
|
removePlanItem,
|
|
19112
20189
|
setPlanItemStatus
|
|
19113
|
-
} from "@wrongstack/core";
|
|
20190
|
+
} from "@wrongstack/core/storage";
|
|
19114
20191
|
import {
|
|
19115
|
-
mutateTasks as mutateTasks2
|
|
19116
|
-
|
|
19117
|
-
} from "@wrongstack/core";
|
|
20192
|
+
mutateTasks as mutateTasks2
|
|
20193
|
+
} from "@wrongstack/core/storage";
|
|
20194
|
+
import { formatTaskList } from "@wrongstack/core/utils";
|
|
19118
20195
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
19119
20196
|
var planTool = {
|
|
19120
20197
|
name: "plan",
|
|
@@ -19381,7 +20458,8 @@ function mkResult(plan, ok, message, todos) {
|
|
|
19381
20458
|
// src/read.ts
|
|
19382
20459
|
init_util();
|
|
19383
20460
|
import * as fs24 from "node:fs/promises";
|
|
19384
|
-
import { FsError,
|
|
20461
|
+
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
20462
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
19385
20463
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
19386
20464
|
var readTool = {
|
|
19387
20465
|
name: "read",
|
|
@@ -19587,20 +20665,20 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
19587
20665
|
}
|
|
19588
20666
|
|
|
19589
20667
|
// src/replace.ts
|
|
19590
|
-
import { expectDefined as expectDefined8 } from "@wrongstack/core";
|
|
20668
|
+
import { expectDefined as expectDefined8 } from "@wrongstack/core/utils";
|
|
19591
20669
|
import { spawn as spawn14 } from "node:child_process";
|
|
19592
20670
|
import * as fs25 from "node:fs/promises";
|
|
19593
|
-
import * as
|
|
20671
|
+
import * as path29 from "node:path";
|
|
19594
20672
|
import {
|
|
19595
20673
|
atomicWrite as atomicWrite3,
|
|
19596
20674
|
buildChildEnv as buildChildEnv9,
|
|
19597
|
-
compileGlob as
|
|
20675
|
+
compileGlob as compileGlob4,
|
|
19598
20676
|
detectNewlineStyle as detectNewlineStyle2,
|
|
19599
20677
|
normalizeToLf as normalizeToLf2,
|
|
19600
20678
|
toStyle as toStyle2,
|
|
19601
|
-
ToolValidationError as ToolValidationError6,
|
|
19602
20679
|
unifiedDiff as unifiedDiff2
|
|
19603
|
-
} from "@wrongstack/core";
|
|
20680
|
+
} from "@wrongstack/core/utils";
|
|
20681
|
+
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
19604
20682
|
init_util();
|
|
19605
20683
|
var DEFAULT_IGNORE4 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
19606
20684
|
var replaceTool = {
|
|
@@ -19659,7 +20737,7 @@ var replaceTool = {
|
|
|
19659
20737
|
});
|
|
19660
20738
|
}
|
|
19661
20739
|
const re = compiled.regex;
|
|
19662
|
-
const globRe = input.glob ?
|
|
20740
|
+
const globRe = input.glob ? compileGlob4(input.glob) : null;
|
|
19663
20741
|
const dryRun = input.dry_run ?? true;
|
|
19664
20742
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
19665
20743
|
const fileList = await resolveFiles2(filesInput, ctx, globRe);
|
|
@@ -19679,8 +20757,8 @@ var replaceTool = {
|
|
|
19679
20757
|
} catch {
|
|
19680
20758
|
continue;
|
|
19681
20759
|
}
|
|
19682
|
-
const rel =
|
|
19683
|
-
if (rel.startsWith("..") ||
|
|
20760
|
+
const rel = path29.relative(realRoot, realPath);
|
|
20761
|
+
if (rel.startsWith("..") || path29.isAbsolute(rel)) continue;
|
|
19684
20762
|
const stat18 = await fs25.stat(realPath).catch(() => null);
|
|
19685
20763
|
if (!stat18?.isFile()) continue;
|
|
19686
20764
|
let content;
|
|
@@ -19799,7 +20877,7 @@ function spawnRgFind(pattern, base) {
|
|
|
19799
20877
|
}
|
|
19800
20878
|
async function globNative(pattern, base, extraGlob) {
|
|
19801
20879
|
const results = [];
|
|
19802
|
-
const globRe =
|
|
20880
|
+
const globRe = compileGlob4(pattern);
|
|
19803
20881
|
const walk2 = async (dir) => {
|
|
19804
20882
|
let entries;
|
|
19805
20883
|
try {
|
|
@@ -19809,7 +20887,7 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19809
20887
|
}
|
|
19810
20888
|
for (const e of entries) {
|
|
19811
20889
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
19812
|
-
const full =
|
|
20890
|
+
const full = path29.join(dir, e.name);
|
|
19813
20891
|
try {
|
|
19814
20892
|
const stat18 = await fs25.lstat(full);
|
|
19815
20893
|
if (stat18.isSymbolicLink()) continue;
|
|
@@ -19836,8 +20914,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19836
20914
|
// src/scaffold.ts
|
|
19837
20915
|
init_util();
|
|
19838
20916
|
import * as fs26 from "node:fs/promises";
|
|
19839
|
-
import * as
|
|
19840
|
-
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
20917
|
+
import * as path30 from "node:path";
|
|
20918
|
+
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
19841
20919
|
var BUILT_IN_TEMPLATES = {
|
|
19842
20920
|
"npm-package": {
|
|
19843
20921
|
description: "Basic npm package with ESM",
|
|
@@ -19986,16 +21064,16 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
19986
21064
|
let filesCreated = 0;
|
|
19987
21065
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
19988
21066
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
19989
|
-
const joinedPath =
|
|
19990
|
-
const root =
|
|
19991
|
-
const target =
|
|
19992
|
-
const rel =
|
|
19993
|
-
if (rel.startsWith("..") ||
|
|
21067
|
+
const joinedPath = path30.join(cwd, resolvedPath);
|
|
21068
|
+
const root = path30.resolve(ctx.projectRoot);
|
|
21069
|
+
const target = path30.resolve(joinedPath);
|
|
21070
|
+
const rel = path30.relative(root, target);
|
|
21071
|
+
if (rel.startsWith("..") || path30.isAbsolute(rel)) {
|
|
19994
21072
|
throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
|
|
19995
21073
|
}
|
|
19996
21074
|
const fullPath = target;
|
|
19997
21075
|
if (!dryRun) {
|
|
19998
|
-
await fs26.mkdir(
|
|
21076
|
+
await fs26.mkdir(path30.dirname(fullPath), { recursive: true });
|
|
19999
21077
|
await atomicWrite4(fullPath, substituteVars(content, name, vars));
|
|
20000
21078
|
}
|
|
20001
21079
|
files.push(resolvedPath);
|
|
@@ -20024,7 +21102,8 @@ function substituteVars(content, name, vars) {
|
|
|
20024
21102
|
}
|
|
20025
21103
|
|
|
20026
21104
|
// src/search.ts
|
|
20027
|
-
import {
|
|
21105
|
+
import { FetchError as FetchError3, ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
21106
|
+
import { expectDefined as expectDefined9 } from "@wrongstack/core/utils";
|
|
20028
21107
|
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
20029
21108
|
var DEFAULT_NUM = 10;
|
|
20030
21109
|
var MAX_RESULTS = 50;
|
|
@@ -20474,15 +21553,13 @@ var setWorkingDirTool = {
|
|
|
20474
21553
|
import {
|
|
20475
21554
|
computeTaskItemProgress,
|
|
20476
21555
|
formatTaskList as formatTaskList2
|
|
20477
|
-
} from "@wrongstack/core";
|
|
20478
|
-
import {
|
|
20479
|
-
mutateTasks as mutateTasks3
|
|
20480
|
-
} from "@wrongstack/core";
|
|
21556
|
+
} from "@wrongstack/core/utils";
|
|
21557
|
+
import { mutateTasks as mutateTasks3 } from "@wrongstack/core/storage";
|
|
20481
21558
|
import {
|
|
20482
21559
|
addPlanItem as addPlanItem2,
|
|
20483
21560
|
mutatePlan as mutatePlan3,
|
|
20484
21561
|
formatPlan as formatPlan2
|
|
20485
|
-
} from "@wrongstack/core";
|
|
21562
|
+
} from "@wrongstack/core/storage";
|
|
20486
21563
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
20487
21564
|
function findTaskIndex(tasks, query) {
|
|
20488
21565
|
const asNum = Number.parseInt(query, 10);
|
|
@@ -20852,7 +21929,7 @@ ${formatTaskList2(file.tasks)}` : file.tasks.length > 0 ? formatTaskList2(file.t
|
|
|
20852
21929
|
init_spawn_stream();
|
|
20853
21930
|
init_util();
|
|
20854
21931
|
init_legacy_bridge();
|
|
20855
|
-
import * as
|
|
21932
|
+
import * as path31 from "node:path";
|
|
20856
21933
|
var testTool = {
|
|
20857
21934
|
name: "test",
|
|
20858
21935
|
category: "Code Quality",
|
|
@@ -20959,7 +22036,7 @@ async function detectRunner(cwd) {
|
|
|
20959
22036
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
20960
22037
|
for (const f of candidates) {
|
|
20961
22038
|
try {
|
|
20962
|
-
await stat18(
|
|
22039
|
+
await stat18(path31.join(cwd, f));
|
|
20963
22040
|
if (f.includes("vitest")) return "vitest";
|
|
20964
22041
|
if (f.includes("jest")) return "jest";
|
|
20965
22042
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -21036,7 +22113,7 @@ function parseResult(runner, result, duration) {
|
|
|
21036
22113
|
}
|
|
21037
22114
|
|
|
21038
22115
|
// src/todo.ts
|
|
21039
|
-
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
22116
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core/storage";
|
|
21040
22117
|
var todoTool = {
|
|
21041
22118
|
name: "todo",
|
|
21042
22119
|
category: "Session",
|
|
@@ -21428,22 +22505,10 @@ var toolUseTool = {
|
|
|
21428
22505
|
|
|
21429
22506
|
// src/tree.ts
|
|
21430
22507
|
init_util();
|
|
21431
|
-
import { expectDefined as expectDefined10 } from "@wrongstack/core";
|
|
22508
|
+
import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
21432
22509
|
import * as fs28 from "node:fs/promises";
|
|
21433
|
-
import * as
|
|
21434
|
-
var DEFAULT_IGNORE5 = [
|
|
21435
|
-
"node_modules",
|
|
21436
|
-
".git",
|
|
21437
|
-
"dist",
|
|
21438
|
-
"build",
|
|
21439
|
-
".next",
|
|
21440
|
-
"coverage",
|
|
21441
|
-
"__pycache__",
|
|
21442
|
-
".wrongstack",
|
|
21443
|
-
".ssh",
|
|
21444
|
-
".gnupg",
|
|
21445
|
-
".aws"
|
|
21446
|
-
];
|
|
22510
|
+
import * as path32 from "node:path";
|
|
22511
|
+
var DEFAULT_IGNORE5 = [...DEFAULT_WALK_IGNORE_DIRS4, ".wrongstack", ".ssh", ".gnupg", ".aws"];
|
|
21447
22512
|
var treeTool = {
|
|
21448
22513
|
name: "tree",
|
|
21449
22514
|
category: "Filesystem",
|
|
@@ -21601,7 +22666,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
21601
22666
|
opts.lines.push(opts.prefix + branch + displayName);
|
|
21602
22667
|
if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
|
|
21603
22668
|
const childPrefix = opts.prefix + connector;
|
|
21604
|
-
await walkDir(
|
|
22669
|
+
await walkDir(path32.join(dir, entry.name), depth + 1, {
|
|
21605
22670
|
...opts,
|
|
21606
22671
|
prefix: childPrefix,
|
|
21607
22672
|
isLast
|
|
@@ -21614,7 +22679,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
21614
22679
|
init_spawn_stream();
|
|
21615
22680
|
init_util();
|
|
21616
22681
|
init_legacy_bridge();
|
|
21617
|
-
import * as
|
|
22682
|
+
import * as path33 from "node:path";
|
|
21618
22683
|
var typecheckTool = {
|
|
21619
22684
|
name: "typecheck",
|
|
21620
22685
|
category: "Code Quality",
|
|
@@ -21717,8 +22782,8 @@ async function findTsConfig(cwd) {
|
|
|
21717
22782
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
21718
22783
|
for (const f of candidates) {
|
|
21719
22784
|
try {
|
|
21720
|
-
const s = await stat18(
|
|
21721
|
-
if (s.isFile()) return
|
|
22785
|
+
const s = await stat18(path33.join(cwd, f));
|
|
22786
|
+
if (s.isFile()) return path33.join(cwd, f);
|
|
21722
22787
|
} catch {
|
|
21723
22788
|
}
|
|
21724
22789
|
}
|
|
@@ -21727,7 +22792,8 @@ async function findTsConfig(cwd) {
|
|
|
21727
22792
|
|
|
21728
22793
|
// src/write.ts
|
|
21729
22794
|
import * as fs29 from "node:fs/promises";
|
|
21730
|
-
import {
|
|
22795
|
+
import { ToolValidationError as ToolValidationError8 } from "@wrongstack/core/types";
|
|
22796
|
+
import { atomicWrite as atomicWrite5, unifiedDiff as unifiedDiff3 } from "@wrongstack/core/utils";
|
|
21731
22797
|
init_util();
|
|
21732
22798
|
var writeTool = {
|
|
21733
22799
|
name: "write",
|
|
@@ -21951,7 +23017,7 @@ init_circuit_breaker();
|
|
|
21951
23017
|
init_languages();
|
|
21952
23018
|
|
|
21953
23019
|
// src/memory.ts
|
|
21954
|
-
import { ToolValidationError as ToolValidationError9 } from "@wrongstack/core";
|
|
23020
|
+
import { ToolValidationError as ToolValidationError9 } from "@wrongstack/core/types";
|
|
21955
23021
|
function rememberTool(memory) {
|
|
21956
23022
|
return {
|
|
21957
23023
|
name: "remember",
|
|
@@ -22260,6 +23326,88 @@ var ProcessGuardian = class {
|
|
|
22260
23326
|
isRunning = false;
|
|
22261
23327
|
instanceId;
|
|
22262
23328
|
sigtermCount = 0;
|
|
23329
|
+
processHandlersInstalled = false;
|
|
23330
|
+
onProcessExit = (code) => {
|
|
23331
|
+
console.log(JSON.stringify({
|
|
23332
|
+
level: "info",
|
|
23333
|
+
event: "process_guardian.process_exiting",
|
|
23334
|
+
pid: process.pid,
|
|
23335
|
+
code,
|
|
23336
|
+
instanceId: this.instanceId
|
|
23337
|
+
}));
|
|
23338
|
+
this.stop();
|
|
23339
|
+
};
|
|
23340
|
+
onUncaughtException = (err) => {
|
|
23341
|
+
console.error(JSON.stringify({
|
|
23342
|
+
level: "error",
|
|
23343
|
+
event: "process_guardian.uncaught_exception",
|
|
23344
|
+
error: err.message,
|
|
23345
|
+
stack: err.stack,
|
|
23346
|
+
instanceId: this.instanceId,
|
|
23347
|
+
fatal: true
|
|
23348
|
+
}));
|
|
23349
|
+
this.stop();
|
|
23350
|
+
process.exit(1);
|
|
23351
|
+
};
|
|
23352
|
+
onUnhandledRejection = (reason) => {
|
|
23353
|
+
const err = reason instanceof Error ? { message: reason.message, stack: reason.stack } : { value: String(reason) };
|
|
23354
|
+
console.error(JSON.stringify({
|
|
23355
|
+
level: "error",
|
|
23356
|
+
event: "process_guardian.unhandled_rejection",
|
|
23357
|
+
...err,
|
|
23358
|
+
instanceId: this.instanceId,
|
|
23359
|
+
fatal: true
|
|
23360
|
+
}));
|
|
23361
|
+
this.stop();
|
|
23362
|
+
process.exit(1);
|
|
23363
|
+
};
|
|
23364
|
+
onSigterm = () => {
|
|
23365
|
+
this.sigtermCount++;
|
|
23366
|
+
const threshold = this.config.sigtermThreshold;
|
|
23367
|
+
if (threshold === Infinity) {
|
|
23368
|
+
console.log(JSON.stringify({
|
|
23369
|
+
level: "warn",
|
|
23370
|
+
event: "process_guardian.sigterm_ignored",
|
|
23371
|
+
pid: process.pid,
|
|
23372
|
+
instanceId: this.instanceId,
|
|
23373
|
+
sigtermCount: this.sigtermCount,
|
|
23374
|
+
message: "SIGTERM received but sigtermThreshold is Infinity \u2014 ignoring (use graceful shutdown instead)"
|
|
23375
|
+
}));
|
|
23376
|
+
return;
|
|
23377
|
+
}
|
|
23378
|
+
if (this.sigtermCount < threshold) {
|
|
23379
|
+
console.log(JSON.stringify({
|
|
23380
|
+
level: "warn",
|
|
23381
|
+
event: "process_guardian.sigterm_deferred",
|
|
23382
|
+
pid: process.pid,
|
|
23383
|
+
instanceId: this.instanceId,
|
|
23384
|
+
sigtermCount: this.sigtermCount,
|
|
23385
|
+
threshold,
|
|
23386
|
+
message: `SIGTERM received (${this.sigtermCount}/${threshold}) \u2014 ignoring until threshold is reached`
|
|
23387
|
+
}));
|
|
23388
|
+
return;
|
|
23389
|
+
}
|
|
23390
|
+
console.log(JSON.stringify({
|
|
23391
|
+
level: "warn",
|
|
23392
|
+
event: "process_guardian.sigterm_exiting",
|
|
23393
|
+
pid: process.pid,
|
|
23394
|
+
instanceId: this.instanceId,
|
|
23395
|
+
sigtermCount: this.sigtermCount,
|
|
23396
|
+
threshold,
|
|
23397
|
+
message: `SIGTERM threshold (${threshold}) reached \u2014 shutting down gracefully`
|
|
23398
|
+
}));
|
|
23399
|
+
this.stop();
|
|
23400
|
+
process.exit(0);
|
|
23401
|
+
};
|
|
23402
|
+
onSighup = () => {
|
|
23403
|
+
console.log(JSON.stringify({
|
|
23404
|
+
level: "warn",
|
|
23405
|
+
event: "process_guardian.sighup_received",
|
|
23406
|
+
pid: process.pid,
|
|
23407
|
+
instanceId: this.instanceId,
|
|
23408
|
+
message: "SIGHUP received but ignored - WrongStack continues running"
|
|
23409
|
+
}));
|
|
23410
|
+
};
|
|
22263
23411
|
constructor(config = {}) {
|
|
22264
23412
|
this.registry = getPersistentProcessRegistry();
|
|
22265
23413
|
this.config = {
|
|
@@ -22298,6 +23446,7 @@ var ProcessGuardian = class {
|
|
|
22298
23446
|
* Stop the guardian gracefully.
|
|
22299
23447
|
*/
|
|
22300
23448
|
stop() {
|
|
23449
|
+
this.removeProcessHandlers();
|
|
22301
23450
|
if (!this.isRunning) return;
|
|
22302
23451
|
this.isRunning = false;
|
|
22303
23452
|
if (this.heartbeatTimer) {
|
|
@@ -22377,87 +23526,22 @@ var ProcessGuardian = class {
|
|
|
22377
23526
|
* Set up process-level event handlers.
|
|
22378
23527
|
*/
|
|
22379
23528
|
setupProcessHandlers() {
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
|
|
22385
|
-
|
|
22386
|
-
|
|
22387
|
-
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
|
|
22395
|
-
|
|
22396
|
-
instanceId: this.instanceId,
|
|
22397
|
-
fatal: true
|
|
22398
|
-
}));
|
|
22399
|
-
this.stop();
|
|
22400
|
-
process.exit(1);
|
|
22401
|
-
});
|
|
22402
|
-
process.on("unhandledRejection", (reason) => {
|
|
22403
|
-
const err = reason instanceof Error ? { message: reason.message, stack: reason.stack } : { value: String(reason) };
|
|
22404
|
-
console.error(JSON.stringify({
|
|
22405
|
-
level: "error",
|
|
22406
|
-
event: "process_guardian.unhandled_rejection",
|
|
22407
|
-
...err,
|
|
22408
|
-
instanceId: this.instanceId,
|
|
22409
|
-
fatal: true
|
|
22410
|
-
}));
|
|
22411
|
-
this.stop();
|
|
22412
|
-
process.exit(1);
|
|
22413
|
-
});
|
|
22414
|
-
process.on("SIGTERM", () => {
|
|
22415
|
-
this.sigtermCount++;
|
|
22416
|
-
const threshold = this.config.sigtermThreshold;
|
|
22417
|
-
if (threshold === Infinity) {
|
|
22418
|
-
console.log(JSON.stringify({
|
|
22419
|
-
level: "warn",
|
|
22420
|
-
event: "process_guardian.sigterm_ignored",
|
|
22421
|
-
pid: process.pid,
|
|
22422
|
-
instanceId: this.instanceId,
|
|
22423
|
-
sigtermCount: this.sigtermCount,
|
|
22424
|
-
message: "SIGTERM received but sigtermThreshold is Infinity \u2014 ignoring (use graceful shutdown instead)"
|
|
22425
|
-
}));
|
|
22426
|
-
return;
|
|
22427
|
-
}
|
|
22428
|
-
if (this.sigtermCount < threshold) {
|
|
22429
|
-
console.log(JSON.stringify({
|
|
22430
|
-
level: "warn",
|
|
22431
|
-
event: "process_guardian.sigterm_deferred",
|
|
22432
|
-
pid: process.pid,
|
|
22433
|
-
instanceId: this.instanceId,
|
|
22434
|
-
sigtermCount: this.sigtermCount,
|
|
22435
|
-
threshold,
|
|
22436
|
-
message: `SIGTERM received (${this.sigtermCount}/${threshold}) \u2014 ignoring until threshold is reached`
|
|
22437
|
-
}));
|
|
22438
|
-
return;
|
|
22439
|
-
}
|
|
22440
|
-
console.log(JSON.stringify({
|
|
22441
|
-
level: "warn",
|
|
22442
|
-
event: "process_guardian.sigterm_exiting",
|
|
22443
|
-
pid: process.pid,
|
|
22444
|
-
instanceId: this.instanceId,
|
|
22445
|
-
sigtermCount: this.sigtermCount,
|
|
22446
|
-
threshold,
|
|
22447
|
-
message: `SIGTERM threshold (${threshold}) reached \u2014 shutting down gracefully`
|
|
22448
|
-
}));
|
|
22449
|
-
this.stop();
|
|
22450
|
-
process.exit(0);
|
|
22451
|
-
});
|
|
22452
|
-
process.on("SIGHUP", () => {
|
|
22453
|
-
console.log(JSON.stringify({
|
|
22454
|
-
level: "warn",
|
|
22455
|
-
event: "process_guardian.sighup_received",
|
|
22456
|
-
pid: process.pid,
|
|
22457
|
-
instanceId: this.instanceId,
|
|
22458
|
-
message: "SIGHUP received but ignored - WrongStack continues running"
|
|
22459
|
-
}));
|
|
22460
|
-
});
|
|
23529
|
+
if (this.processHandlersInstalled) return;
|
|
23530
|
+
this.processHandlersInstalled = true;
|
|
23531
|
+
process.on("exit", this.onProcessExit);
|
|
23532
|
+
process.on("uncaughtException", this.onUncaughtException);
|
|
23533
|
+
process.on("unhandledRejection", this.onUnhandledRejection);
|
|
23534
|
+
process.on("SIGTERM", this.onSigterm);
|
|
23535
|
+
process.on("SIGHUP", this.onSighup);
|
|
23536
|
+
}
|
|
23537
|
+
removeProcessHandlers() {
|
|
23538
|
+
if (!this.processHandlersInstalled) return;
|
|
23539
|
+
this.processHandlersInstalled = false;
|
|
23540
|
+
process.off("exit", this.onProcessExit);
|
|
23541
|
+
process.off("uncaughtException", this.onUncaughtException);
|
|
23542
|
+
process.off("unhandledRejection", this.onUnhandledRejection);
|
|
23543
|
+
process.off("SIGTERM", this.onSigterm);
|
|
23544
|
+
process.off("SIGHUP", this.onSighup);
|
|
22461
23545
|
}
|
|
22462
23546
|
/**
|
|
22463
23547
|
* Check if a PID is protected by this guardian.
|
|
@@ -22493,6 +23577,7 @@ function getProcessGuardian() {
|
|
|
22493
23577
|
return _guardian;
|
|
22494
23578
|
}
|
|
22495
23579
|
function startProcessGuardian(config) {
|
|
23580
|
+
_guardian?.stop();
|
|
22496
23581
|
const guardian = new ProcessGuardian(config);
|
|
22497
23582
|
guardian.start();
|
|
22498
23583
|
_guardian = guardian;
|
|
@@ -22823,12 +23908,9 @@ function createGlobalPsSlashCommand() {
|
|
|
22823
23908
|
|
|
22824
23909
|
// src/skill.ts
|
|
22825
23910
|
import * as fs30 from "node:fs/promises";
|
|
22826
|
-
import * as
|
|
22827
|
-
import {
|
|
22828
|
-
|
|
22829
|
-
stripFrontmatter,
|
|
22830
|
-
ToolValidationError as ToolValidationError10
|
|
22831
|
-
} from "@wrongstack/core";
|
|
23911
|
+
import * as path34 from "node:path";
|
|
23912
|
+
import { SKILL_LIMITS, stripFrontmatter } from "@wrongstack/core/skills";
|
|
23913
|
+
import { ToolValidationError as ToolValidationError10 } from "@wrongstack/core/types";
|
|
22832
23914
|
var MAX_BODY_CHARS = SKILL_LIMITS.MAX_SKILL_BODY_CHARS;
|
|
22833
23915
|
var MAX_RESOURCE_CHARS = SKILL_LIMITS.MAX_RESOURCE_CHARS;
|
|
22834
23916
|
var MAX_LISTED_RESOURCES = SKILL_LIMITS.MAX_LISTED_RESOURCES;
|
|
@@ -22870,7 +23952,7 @@ function makeSkillTool(skillLoader) {
|
|
|
22870
23952
|
field: "name"
|
|
22871
23953
|
});
|
|
22872
23954
|
}
|
|
22873
|
-
const dir =
|
|
23955
|
+
const dir = path34.dirname(manifest.path);
|
|
22874
23956
|
let loadedResource;
|
|
22875
23957
|
if (input.resource?.trim()) {
|
|
22876
23958
|
loadedResource = await loadResource(dir, input.resource.trim());
|
|
@@ -22920,15 +24002,15 @@ ${listing}`;
|
|
|
22920
24002
|
}
|
|
22921
24003
|
async function loadResource(skillDir, rel) {
|
|
22922
24004
|
const norm = rel.replace(/\\/g, "/");
|
|
22923
|
-
if (
|
|
24005
|
+
if (path34.isAbsolute(rel) || norm.split("/").some((seg) => seg === "..")) {
|
|
22924
24006
|
throw new ToolValidationError10({
|
|
22925
24007
|
message: `skill: invalid resource path "${rel}"`,
|
|
22926
24008
|
field: "resource"
|
|
22927
24009
|
});
|
|
22928
24010
|
}
|
|
22929
|
-
const absPath =
|
|
22930
|
-
const root =
|
|
22931
|
-
if (absPath !== root && !absPath.startsWith(root +
|
|
24011
|
+
const absPath = path34.resolve(skillDir, rel);
|
|
24012
|
+
const root = path34.resolve(skillDir);
|
|
24013
|
+
if (absPath !== root && !absPath.startsWith(root + path34.sep)) {
|
|
22932
24014
|
throw new ToolValidationError10({
|
|
22933
24015
|
message: `skill: resource "${rel}" escapes the skill directory`,
|
|
22934
24016
|
field: "resource"
|
|
@@ -22969,7 +24051,7 @@ async function walk(root, dir, out) {
|
|
|
22969
24051
|
}
|
|
22970
24052
|
for (const e of entries) {
|
|
22971
24053
|
if (out.length >= MAX_LISTED_RESOURCES) return;
|
|
22972
|
-
const fullPath =
|
|
24054
|
+
const fullPath = path34.join(dir, e.name);
|
|
22973
24055
|
let isDir = e.isDirectory();
|
|
22974
24056
|
if (e.isSymbolicLink()) {
|
|
22975
24057
|
try {
|
|
@@ -22985,7 +24067,7 @@ async function walk(root, dir, out) {
|
|
|
22985
24067
|
if (e.name === "SKILL.md" || e.name === "SKILL.save.md") continue;
|
|
22986
24068
|
try {
|
|
22987
24069
|
const stat18 = await fs30.stat(fullPath);
|
|
22988
|
-
const rel =
|
|
24070
|
+
const rel = path34.relative(root, fullPath).split(path34.sep).join("/");
|
|
22989
24071
|
out.push({ path: rel, bytes: stat18.size });
|
|
22990
24072
|
} catch {
|
|
22991
24073
|
}
|
|
@@ -23160,6 +24242,43 @@ var TOOL_ICON_CONFIG = {
|
|
|
23160
24242
|
// gray-light
|
|
23161
24243
|
};
|
|
23162
24244
|
var FALLBACK_ICON = "fallback";
|
|
24245
|
+
|
|
24246
|
+
// src/tool-tier.ts
|
|
24247
|
+
function toolNameSet(tools) {
|
|
24248
|
+
return new Set(tools.flatMap((tool) => tool ? [tool.name] : []));
|
|
24249
|
+
}
|
|
24250
|
+
function selectBuiltinToolsForTier(tier, allTools) {
|
|
24251
|
+
switch (tier) {
|
|
24252
|
+
case "off":
|
|
24253
|
+
return [...allTools];
|
|
24254
|
+
case "minimal":
|
|
24255
|
+
case "light": {
|
|
24256
|
+
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
24257
|
+
return allTools.filter((tool) => tier1Names.has(tool.name));
|
|
24258
|
+
}
|
|
24259
|
+
case "medium": {
|
|
24260
|
+
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
24261
|
+
const tier2Names = toolNameSet(TIER2_TOOLS);
|
|
24262
|
+
return allTools.filter((tool) => tier1Names.has(tool.name) || tier2Names.has(tool.name));
|
|
24263
|
+
}
|
|
24264
|
+
case "aggressive": {
|
|
24265
|
+
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
24266
|
+
const tier2Names = toolNameSet(TIER2_TOOLS);
|
|
24267
|
+
const tier3Names = toolNameSet(TIER3_TOOLS);
|
|
24268
|
+
return allTools.filter(
|
|
24269
|
+
(tool) => tier1Names.has(tool.name) || tier2Names.has(tool.name) && tool.name !== "task" || tier3Names.has(tool.name) && tool.name !== "setWorkingDir"
|
|
24270
|
+
);
|
|
24271
|
+
}
|
|
24272
|
+
}
|
|
24273
|
+
}
|
|
24274
|
+
function registerBuiltinToolTier(options) {
|
|
24275
|
+
const tools = selectBuiltinToolsForTier(
|
|
24276
|
+
options.tier,
|
|
24277
|
+
options.tools ?? builtinToolsPack.tools ?? []
|
|
24278
|
+
);
|
|
24279
|
+
options.registry.registerAllOrThrow(tools, options.owner ?? builtinToolsPack.name);
|
|
24280
|
+
return tools;
|
|
24281
|
+
}
|
|
23163
24282
|
export {
|
|
23164
24283
|
ADDITIONAL_LANGUAGE_PROFILES,
|
|
23165
24284
|
BrowserArtifactStore,
|
|
@@ -23258,6 +24377,8 @@ export {
|
|
|
23258
24377
|
isIndexableFile,
|
|
23259
24378
|
isIndexing,
|
|
23260
24379
|
jsonTool,
|
|
24380
|
+
kanbanEvidenceKey,
|
|
24381
|
+
kanbanEvidencePointer,
|
|
23261
24382
|
kanbanTool,
|
|
23262
24383
|
languageInfoTool,
|
|
23263
24384
|
languagePackageTool,
|
|
@@ -23284,7 +24405,9 @@ export {
|
|
|
23284
24405
|
projectSessionTasksToKanban,
|
|
23285
24406
|
projectSessionTodosToKanban,
|
|
23286
24407
|
readTool,
|
|
24408
|
+
recordKanbanVerificationEvidence,
|
|
23287
24409
|
redactBrowserText,
|
|
24410
|
+
registerBuiltinToolTier,
|
|
23288
24411
|
relatedMemoryTool,
|
|
23289
24412
|
rememberTool,
|
|
23290
24413
|
replaceTool,
|
|
@@ -23300,6 +24423,7 @@ export {
|
|
|
23300
24423
|
searchCodebaseIndex,
|
|
23301
24424
|
searchMemoryTool,
|
|
23302
24425
|
searchTool,
|
|
24426
|
+
selectBuiltinToolsForTier,
|
|
23303
24427
|
shutdownBrowserTools,
|
|
23304
24428
|
shutdownCodebaseIndexHost,
|
|
23305
24429
|
startProcessGuardian,
|