@wrongstack/tools 0.292.1 → 0.295.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_env.d.ts +1 -1
- package/dist/_env.d.ts.map +1 -1
- package/dist/_fetch-guard.d.ts.map +1 -1
- package/dist/_spawn-stream.d.ts +1 -1
- package/dist/_spawn-stream.d.ts.map +1 -1
- package/dist/_util.d.ts +1 -1
- package/dist/_util.d.ts.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +81 -16
- package/dist/audit.js.map +2 -2
- package/dist/bash.d.ts +1 -1
- package/dist/bash.d.ts.map +1 -1
- package/dist/bash.js +66 -52
- package/dist/bash.js.map +3 -3
- package/dist/batch-tool-use.d.ts +1 -1
- package/dist/batch-tool-use.d.ts.map +1 -1
- package/dist/batch-tool-use.js +1 -1
- package/dist/batch-tool-use.js.map +1 -1
- package/dist/browser/index.js +10 -1
- package/dist/browser/index.js.map +2 -2
- package/dist/browser/manager.d.ts +2 -0
- package/dist/browser/manager.d.ts.map +1 -1
- package/dist/browser/tools.d.ts +1 -1
- package/dist/browser/tools.d.ts.map +1 -1
- package/dist/builtin.d.ts +1 -1
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1394 -553
- package/dist/builtin.js.map +4 -4
- package/dist/codebase-index/background-indexer.d.ts +2 -2
- package/dist/codebase-index/background-indexer.d.ts.map +1 -1
- package/dist/codebase-index/bm25.d.ts +3 -1
- package/dist/codebase-index/bm25.d.ts.map +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts +1 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -1
- package/dist/codebase-index/generic-parser.d.ts +34 -0
- package/dist/codebase-index/generic-parser.d.ts.map +1 -0
- package/dist/codebase-index/go-parser.d.ts +1 -1
- package/dist/codebase-index/go-parser.d.ts.map +1 -1
- package/dist/codebase-index/index.d.ts +4 -2
- package/dist/codebase-index/index.d.ts.map +1 -1
- package/dist/codebase-index/index.js +1036 -341
- package/dist/codebase-index/index.js.map +4 -4
- package/dist/codebase-index/indexer.d.ts +6 -1
- package/dist/codebase-index/indexer.d.ts.map +1 -1
- package/dist/codebase-index/json-parser.d.ts +1 -1
- package/dist/codebase-index/languages.d.ts +25 -0
- package/dist/codebase-index/languages.d.ts.map +1 -0
- package/dist/codebase-index/py-parser.d.ts +9 -1
- package/dist/codebase-index/py-parser.d.ts.map +1 -1
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
- package/dist/codebase-index/rs-parser.d.ts +1 -1
- package/dist/codebase-index/rs-parser.d.ts.map +1 -1
- package/dist/codebase-index/schema.d.ts +9 -2
- package/dist/codebase-index/schema.d.ts.map +1 -1
- package/dist/codebase-index/spawn-gate.d.ts +15 -0
- package/dist/codebase-index/spawn-gate.d.ts.map +1 -0
- package/dist/codebase-index/ts-parser.d.ts +2 -2
- package/dist/codebase-index/ts-parser.d.ts.map +1 -1
- package/dist/codebase-index/worker.js +961 -317
- package/dist/codebase-index/worker.js.map +4 -4
- package/dist/codebase-index/writer.d.ts +83 -4
- package/dist/codebase-index/writer.d.ts.map +1 -1
- package/dist/codebase-index/yaml-parser.d.ts +1 -1
- package/dist/design.d.ts +5 -2
- package/dist/design.d.ts.map +1 -1
- package/dist/design.js +36 -3
- package/dist/design.js.map +2 -2
- package/dist/diff.d.ts +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +2 -2
- package/dist/diff.js.map +1 -1
- package/dist/document.d.ts +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +1 -1
- package/dist/document.js.map +1 -1
- package/dist/e2e.d.ts +1 -1
- package/dist/e2e.d.ts.map +1 -1
- package/dist/e2e.js +1 -1
- package/dist/e2e.js.map +1 -1
- package/dist/edit.d.ts +1 -1
- package/dist/edit.d.ts.map +1 -1
- package/dist/edit.js +3 -3
- package/dist/edit.js.map +2 -2
- package/dist/exec.d.ts +1 -1
- package/dist/exec.d.ts.map +1 -1
- package/dist/exec.js +60 -21
- package/dist/exec.js.map +3 -3
- package/dist/fetch.d.ts +1 -1
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +4 -5
- package/dist/fetch.js.map +2 -2
- package/dist/format.d.ts +1 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +81 -16
- package/dist/format.js.map +2 -2
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +3 -2
- package/dist/git.js.map +2 -2
- package/dist/glob.d.ts +1 -1
- package/dist/glob.d.ts.map +1 -1
- package/dist/glob.js +2 -2
- package/dist/glob.js.map +1 -1
- package/dist/grep.d.ts +1 -1
- package/dist/grep.d.ts.map +1 -1
- package/dist/grep.js +4 -3
- package/dist/grep.js.map +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1657 -705
- package/dist/index.js.map +4 -4
- package/dist/install.d.ts +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +85 -17
- package/dist/install.js.map +2 -2
- package/dist/json.d.ts +1 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -2
- package/dist/json.js.map +2 -2
- package/dist/kanban.d.ts +4 -3
- package/dist/kanban.d.ts.map +1 -1
- package/dist/kanban.js +94 -27
- package/dist/kanban.js.map +2 -2
- package/dist/languages/execute-tool.d.ts +1 -1
- package/dist/languages/execute-tool.d.ts.map +1 -1
- package/dist/languages/execute.d.ts +1 -1
- package/dist/languages/execute.d.ts.map +1 -1
- package/dist/languages/index.d.ts +1 -1
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +99 -17
- package/dist/languages/index.js.map +2 -2
- package/dist/languages/package-tool.d.ts +3 -4
- package/dist/languages/package-tool.d.ts.map +1 -1
- package/dist/languages/plan.d.ts.map +1 -1
- package/dist/languages/profiles/additional.d.ts.map +1 -1
- package/dist/languages/profiles/primary.d.ts.map +1 -1
- package/dist/languages/tool.d.ts +1 -1
- package/dist/languages/tool.d.ts.map +1 -1
- package/dist/lint.d.ts +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +81 -16
- package/dist/lint.js.map +2 -2
- package/dist/logs.d.ts +1 -1
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +2 -2
- package/dist/logs.js.map +1 -1
- package/dist/memory.d.ts +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +1 -1
- package/dist/memory.js.map +1 -1
- package/dist/mode.d.ts +1 -1
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/outdated.d.ts +1 -1
- package/dist/outdated.d.ts.map +1 -1
- package/dist/outdated.js +101 -19
- package/dist/outdated.js.map +2 -2
- package/dist/pack.d.ts +1 -1
- package/dist/pack.js +1394 -553
- package/dist/pack.js.map +4 -4
- package/dist/patch.d.ts +1 -1
- package/dist/patch.d.ts.map +1 -1
- package/dist/patch.js +2 -2
- package/dist/patch.js.map +1 -1
- package/dist/plan.d.ts +1 -1
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +7 -7
- package/dist/plan.js.map +2 -2
- package/dist/process-guardian.d.ts +7 -0
- package/dist/process-guardian.d.ts.map +1 -1
- package/dist/process-registry-persistent.d.ts +5 -0
- package/dist/process-registry-persistent.d.ts.map +1 -1
- package/dist/process-registry.d.ts +9 -1
- package/dist/process-registry.d.ts.map +1 -1
- package/dist/process-registry.js +18 -3
- package/dist/process-registry.js.map +2 -2
- package/dist/ps-slash.js +54 -15
- package/dist/ps-slash.js.map +2 -2
- package/dist/read.d.ts +1 -1
- package/dist/read.d.ts.map +1 -1
- package/dist/read.js +3 -2
- package/dist/read.js.map +2 -2
- package/dist/replace.d.ts +1 -1
- package/dist/replace.d.ts.map +1 -1
- package/dist/replace.js +4 -4
- package/dist/replace.js.map +2 -2
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +2 -2
- package/dist/scaffold.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +4 -4
- package/dist/search.js.map +2 -2
- package/dist/session-kanban.d.ts +4 -1
- package/dist/session-kanban.d.ts.map +1 -1
- package/dist/session-kanban.js +23 -15
- package/dist/session-kanban.js.map +2 -2
- package/dist/set-working-dir.d.ts +1 -1
- package/dist/set-working-dir.d.ts.map +1 -1
- package/dist/skill.d.ts +1 -1
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +2 -5
- package/dist/skill.js.map +2 -2
- package/dist/spawn-background.d.ts.map +1 -1
- package/dist/task.d.ts +3 -2
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +6 -8
- package/dist/task.js.map +2 -2
- package/dist/test.d.ts +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +81 -16
- package/dist/test.js.map +2 -2
- package/dist/todo.d.ts +2 -1
- package/dist/todo.d.ts.map +1 -1
- package/dist/todo.js +4 -4
- package/dist/todo.js.map +2 -2
- package/dist/tool-help.d.ts +1 -1
- package/dist/tool-help.d.ts.map +1 -1
- package/dist/tool-help.js.map +1 -1
- package/dist/tool-search.d.ts +1 -1
- package/dist/tool-search.d.ts.map +1 -1
- package/dist/tool-search.js.map +1 -1
- package/dist/tool-tier.d.ts +16 -0
- package/dist/tool-tier.d.ts.map +1 -0
- package/dist/tool-tier.js +22233 -0
- package/dist/tool-tier.js.map +7 -0
- package/dist/tool-use.d.ts +1 -1
- package/dist/tool-use.d.ts.map +1 -1
- package/dist/tool-use.js.map +1 -1
- package/dist/tree.d.ts +1 -1
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +2 -2
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.d.ts +1 -1
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +81 -16
- package/dist/typecheck.js.map +2 -2
- package/dist/write.d.ts +1 -1
- package/dist/write.d.ts.map +1 -1
- package/dist/write.js +3 -2
- package/dist/write.js.map +2 -2
- package/package.json +7 -3
package/dist/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,7 @@ function emitStructuredLog(level, event, message, error) {
|
|
|
5297
5380
|
}
|
|
5298
5381
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
5299
5382
|
var STALE_THRESHOLD_MS = 3e4;
|
|
5300
|
-
var LOCKFILE = ".
|
|
5383
|
+
var LOCKFILE = ".process-registry.lock";
|
|
5301
5384
|
function generateInstanceId() {
|
|
5302
5385
|
const hostname4 = os2.hostname();
|
|
5303
5386
|
const pid = process.pid;
|
|
@@ -5386,12 +5469,18 @@ var PersistentProcessRegistry = class {
|
|
|
5386
5469
|
lockPath;
|
|
5387
5470
|
baseRegistry;
|
|
5388
5471
|
heartbeatInterval = null;
|
|
5472
|
+
cleanupInterval = null;
|
|
5473
|
+
heartbeatRunning = false;
|
|
5474
|
+
cleanupRunning = false;
|
|
5389
5475
|
isShuttingDown = false;
|
|
5476
|
+
onProcessExit = () => {
|
|
5477
|
+
void this.syncToPersistent();
|
|
5478
|
+
};
|
|
5390
5479
|
constructor(baseRegistry) {
|
|
5391
5480
|
this.instanceId = generateInstanceId();
|
|
5392
|
-
const
|
|
5393
|
-
this.registryPath = path9.join(
|
|
5394
|
-
this.lockPath = path9.join(
|
|
5481
|
+
const globalRoot = wstackGlobalRoot3();
|
|
5482
|
+
this.registryPath = path9.join(globalRoot, REGISTRY_FILE);
|
|
5483
|
+
this.lockPath = path9.join(globalRoot, LOCKFILE);
|
|
5395
5484
|
this.baseRegistry = baseRegistry ?? getProcessRegistry();
|
|
5396
5485
|
this.ensureDirectory().catch((err) => {
|
|
5397
5486
|
emitStructuredLog("warn", "process_registry.dir_create_failed", "PersistentProcessRegistry: failed to create .wrongstack directory", err);
|
|
@@ -5410,16 +5499,18 @@ var PersistentProcessRegistry = class {
|
|
|
5410
5499
|
*/
|
|
5411
5500
|
start() {
|
|
5412
5501
|
if (this.heartbeatInterval) return;
|
|
5413
|
-
this.
|
|
5502
|
+
this.isShuttingDown = false;
|
|
5503
|
+
this.heartbeat();
|
|
5414
5504
|
this.heartbeatInterval = setInterval(() => {
|
|
5415
5505
|
this.heartbeat();
|
|
5416
5506
|
}, HEARTBEAT_INTERVAL_MS);
|
|
5417
5507
|
this.heartbeatInterval.unref?.();
|
|
5418
|
-
setInterval(() => {
|
|
5419
|
-
this.
|
|
5420
|
-
}, STALE_THRESHOLD_MS)
|
|
5508
|
+
this.cleanupInterval = setInterval(() => {
|
|
5509
|
+
this.cleanup();
|
|
5510
|
+
}, STALE_THRESHOLD_MS);
|
|
5511
|
+
this.cleanupInterval.unref?.();
|
|
5421
5512
|
this.registerMainProcess();
|
|
5422
|
-
process.on("exit",
|
|
5513
|
+
process.on("exit", this.onProcessExit);
|
|
5423
5514
|
}
|
|
5424
5515
|
/**
|
|
5425
5516
|
* Stop the heartbeat and clean up.
|
|
@@ -5430,7 +5521,12 @@ var PersistentProcessRegistry = class {
|
|
|
5430
5521
|
clearInterval(this.heartbeatInterval);
|
|
5431
5522
|
this.heartbeatInterval = null;
|
|
5432
5523
|
}
|
|
5433
|
-
this.
|
|
5524
|
+
if (this.cleanupInterval) {
|
|
5525
|
+
clearInterval(this.cleanupInterval);
|
|
5526
|
+
this.cleanupInterval = null;
|
|
5527
|
+
}
|
|
5528
|
+
process.off("exit", this.onProcessExit);
|
|
5529
|
+
void this.syncToPersistent();
|
|
5434
5530
|
}
|
|
5435
5531
|
/**
|
|
5436
5532
|
* Register the main WrongStack process as protected.
|
|
@@ -5514,8 +5610,18 @@ var PersistentProcessRegistry = class {
|
|
|
5514
5610
|
* Send heartbeat to mark all this instance's processes as alive.
|
|
5515
5611
|
*/
|
|
5516
5612
|
heartbeat() {
|
|
5517
|
-
if (this.isShuttingDown) return;
|
|
5518
|
-
this.
|
|
5613
|
+
if (this.isShuttingDown || this.heartbeatRunning) return;
|
|
5614
|
+
this.heartbeatRunning = true;
|
|
5615
|
+
void this.syncToPersistent().finally(() => {
|
|
5616
|
+
this.heartbeatRunning = false;
|
|
5617
|
+
});
|
|
5618
|
+
}
|
|
5619
|
+
cleanup() {
|
|
5620
|
+
if (this.isShuttingDown || this.cleanupRunning) return;
|
|
5621
|
+
this.cleanupRunning = true;
|
|
5622
|
+
void this.cleanupStaleEntries().finally(() => {
|
|
5623
|
+
this.cleanupRunning = false;
|
|
5624
|
+
});
|
|
5519
5625
|
}
|
|
5520
5626
|
/**
|
|
5521
5627
|
* Sync this instance's processes to the persistent registry.
|
|
@@ -6237,14 +6343,12 @@ var bashTool = {
|
|
|
6237
6343
|
const detached = !isWin5;
|
|
6238
6344
|
const startedAt = Date.now();
|
|
6239
6345
|
if (input.background) {
|
|
6240
|
-
let buf2 = "";
|
|
6241
|
-
let truncated = false;
|
|
6242
6346
|
const child2 = spawn3(shell, args, {
|
|
6243
6347
|
cwd: ctx.projectRoot,
|
|
6244
6348
|
env,
|
|
6245
6349
|
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
6246
6350
|
// and POSIX shells ignore stdin when given the command inline.
|
|
6247
|
-
stdio: [plan.useStdin ? "pipe" : "ignore", "
|
|
6351
|
+
stdio: [plan.useStdin ? "pipe" : "ignore", "ignore", "ignore"],
|
|
6248
6352
|
// win32: CreateProcess IGNORES CREATE_NO_WINDOW (windowsHide) when
|
|
6249
6353
|
// DETACHED_PROCESS (detached: true) is set, so the console-less
|
|
6250
6354
|
// cmd.exe's grandchildren (node, dev servers) each allocate a fresh
|
|
@@ -6263,8 +6367,8 @@ var bashTool = {
|
|
|
6263
6367
|
}
|
|
6264
6368
|
}
|
|
6265
6369
|
const pid2 = child2.pid;
|
|
6266
|
-
|
|
6267
|
-
|
|
6370
|
+
const stdoutBytes2 = 0;
|
|
6371
|
+
const stderrBytes2 = 0;
|
|
6268
6372
|
let telemetryCompleted2 = false;
|
|
6269
6373
|
emitProcessStarted2({
|
|
6270
6374
|
...pid2 !== void 0 ? { pid: pid2 } : {},
|
|
@@ -6297,41 +6401,17 @@ var bashTool = {
|
|
|
6297
6401
|
startedAt: Date.now(),
|
|
6298
6402
|
sessionId: ctx.session?.id,
|
|
6299
6403
|
child: child2,
|
|
6300
|
-
processGroupLeader: detached && child2.pid === pid2
|
|
6404
|
+
processGroupLeader: detached && child2.pid === pid2,
|
|
6405
|
+
background: true
|
|
6301
6406
|
});
|
|
6302
6407
|
child2.on("close", () => registry.unregister(pid2));
|
|
6303
6408
|
}
|
|
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
6409
|
child2.on("error", () => {
|
|
6328
|
-
cleanupBackground();
|
|
6329
6410
|
if (typeof pid2 === "number") registry.unregister(pid2);
|
|
6330
6411
|
registry.afterCall(Date.now() - startedAt, true, bypassBreaker);
|
|
6331
6412
|
completeBackground(1);
|
|
6332
6413
|
});
|
|
6333
6414
|
child2.on("close", (code, signal) => {
|
|
6334
|
-
cleanupBackground();
|
|
6335
6415
|
registry.afterCall(Date.now() - startedAt, false, bypassBreaker);
|
|
6336
6416
|
completeBackground(code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
6337
6417
|
});
|
|
@@ -6339,7 +6419,7 @@ var bashTool = {
|
|
|
6339
6419
|
yield {
|
|
6340
6420
|
type: "final",
|
|
6341
6421
|
output: {
|
|
6342
|
-
output:
|
|
6422
|
+
output: "",
|
|
6343
6423
|
exit_code: null,
|
|
6344
6424
|
timed_out: false,
|
|
6345
6425
|
pid: pid2
|
|
@@ -6610,6 +6690,7 @@ ${hint}` : ""),
|
|
|
6610
6690
|
for (const entry of registry.bySession(sessionId)) {
|
|
6611
6691
|
if (entry.name !== "bash") continue;
|
|
6612
6692
|
if (entry.child.exitCode !== null) continue;
|
|
6693
|
+
if (entry.background) continue;
|
|
6613
6694
|
if (entry.protected) continue;
|
|
6614
6695
|
registry.kill(entry.pid, { force: true });
|
|
6615
6696
|
}
|
|
@@ -6619,7 +6700,7 @@ ${hint}` : ""),
|
|
|
6619
6700
|
// src/batch-tool-use.ts
|
|
6620
6701
|
import {
|
|
6621
6702
|
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
6622
|
-
} from "@wrongstack/core";
|
|
6703
|
+
} from "@wrongstack/core/types";
|
|
6623
6704
|
var batchToolUseTool = {
|
|
6624
6705
|
name: "batch_tool_use",
|
|
6625
6706
|
category: "Meta",
|
|
@@ -7399,6 +7480,10 @@ var BrowserSessionManager = class {
|
|
|
7399
7480
|
const ids = [...this.sessions.values()].filter((session) => session.ownerId === ownerId).map((session) => session.id);
|
|
7400
7481
|
for (const id of ids) await this.close(id, ownerId).catch(() => void 0);
|
|
7401
7482
|
}
|
|
7483
|
+
/** True when the manager owns no live browser contexts. */
|
|
7484
|
+
isIdle() {
|
|
7485
|
+
return this.sessions.size === 0;
|
|
7486
|
+
}
|
|
7402
7487
|
async dispose() {
|
|
7403
7488
|
for (const session of [...this.sessions.values()]) {
|
|
7404
7489
|
await session.context.close().catch(() => void 0);
|
|
@@ -7562,7 +7647,7 @@ async function abortable(signal, operation, onAbort) {
|
|
|
7562
7647
|
|
|
7563
7648
|
// src/browser/tools.ts
|
|
7564
7649
|
import * as path12 from "node:path";
|
|
7565
|
-
import { ToolCapabilities } from "@wrongstack/core";
|
|
7650
|
+
import { ToolCapabilities } from "@wrongstack/core/security";
|
|
7566
7651
|
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
7567
7652
|
var managers = /* @__PURE__ */ new Map();
|
|
7568
7653
|
var cleanupSignalByContext = /* @__PURE__ */ new WeakMap();
|
|
@@ -7583,12 +7668,17 @@ function managerFor(ctx) {
|
|
|
7583
7668
|
if (cleanupSignalByContext.get(ctx) !== runSignal) {
|
|
7584
7669
|
cleanupSignalByContext.set(ctx, runSignal);
|
|
7585
7670
|
const managerForRun = manager;
|
|
7671
|
+
const rootForRun = root;
|
|
7586
7672
|
const ownerForRun = owner(ctx);
|
|
7587
7673
|
ctx.registerAbortHook?.(async () => {
|
|
7588
7674
|
if (cleanupSignalByContext.get(ctx) === runSignal) {
|
|
7589
7675
|
cleanupSignalByContext.delete(ctx);
|
|
7590
7676
|
}
|
|
7591
7677
|
await managerForRun.closeOwner(ownerForRun);
|
|
7678
|
+
if (managerForRun.isIdle() && managers.get(rootForRun) === managerForRun) {
|
|
7679
|
+
managers.delete(rootForRun);
|
|
7680
|
+
await managerForRun.dispose();
|
|
7681
|
+
}
|
|
7592
7682
|
});
|
|
7593
7683
|
}
|
|
7594
7684
|
return manager;
|
|
@@ -7976,7 +8066,7 @@ async function shutdownBrowserTools() {
|
|
|
7976
8066
|
}
|
|
7977
8067
|
|
|
7978
8068
|
// src/codebase-index/writer.ts
|
|
7979
|
-
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
8069
|
+
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
7980
8070
|
|
|
7981
8071
|
// src/codebase-index/circuit-breaker.ts
|
|
7982
8072
|
var CircuitOpenError = class extends Error {
|
|
@@ -8066,7 +8156,7 @@ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
|
8066
8156
|
import { createRequire } from "node:module";
|
|
8067
8157
|
import * as fs9 from "node:fs";
|
|
8068
8158
|
import * as path13 from "node:path";
|
|
8069
|
-
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core";
|
|
8159
|
+
import { resolveWstackPaths as resolveWstackPaths2, sqliteCachePragmas } from "@wrongstack/core/utils";
|
|
8070
8160
|
|
|
8071
8161
|
// src/codebase-index/schema.ts
|
|
8072
8162
|
var SCHEMA_VERSION = 3;
|
|
@@ -8122,23 +8212,13 @@ function buildBm25Index(docs) {
|
|
|
8122
8212
|
const tokens = tokenise(d.text);
|
|
8123
8213
|
return { id: d.id, tokens, raw: d.text, len: tokens.length };
|
|
8124
8214
|
});
|
|
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
8215
|
const N = documents.length;
|
|
8136
8216
|
const totalLen = documents.reduce((sum, d) => sum + d.len, 0);
|
|
8137
8217
|
const avgLen = N === 0 ? 0 : totalLen / N;
|
|
8138
|
-
return new Bm25Index(documents,
|
|
8218
|
+
return new Bm25Index(documents, N, avgLen);
|
|
8139
8219
|
}
|
|
8140
8220
|
var Bm25Index = class {
|
|
8141
|
-
constructor(documents,
|
|
8221
|
+
constructor(documents, N, avgLen) {
|
|
8142
8222
|
this.documents = documents;
|
|
8143
8223
|
this.N = N;
|
|
8144
8224
|
this.safeAvgLen = avgLen === 0 ? 1 : avgLen;
|
|
@@ -8146,9 +8226,19 @@ var Bm25Index = class {
|
|
|
8146
8226
|
documents;
|
|
8147
8227
|
N;
|
|
8148
8228
|
safeAvgLen;
|
|
8229
|
+
/** Lazily-built id→doc index so getDoc is O(1) instead of an O(D) linear find. */
|
|
8230
|
+
_byId;
|
|
8149
8231
|
score(query, filter) {
|
|
8150
8232
|
const qTokens = tokenise(query);
|
|
8151
8233
|
if (qTokens.length === 0) return [];
|
|
8234
|
+
const dfByTerm = /* @__PURE__ */ new Map();
|
|
8235
|
+
for (const qTerm of qTokens) {
|
|
8236
|
+
let dfVal = 0;
|
|
8237
|
+
for (const candidate of this.documents) {
|
|
8238
|
+
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
8239
|
+
}
|
|
8240
|
+
dfByTerm.set(qTerm, dfVal);
|
|
8241
|
+
}
|
|
8152
8242
|
const results = [];
|
|
8153
8243
|
for (const doc of this.documents) {
|
|
8154
8244
|
if (filter && !filter(doc.id)) continue;
|
|
@@ -8159,10 +8249,7 @@ var Bm25Index = class {
|
|
|
8159
8249
|
if (t.startsWith(qTerm)) tf++;
|
|
8160
8250
|
}
|
|
8161
8251
|
if (tf === 0) continue;
|
|
8162
|
-
|
|
8163
|
-
for (const candidate of this.documents) {
|
|
8164
|
-
if (candidate.tokens.some((t) => t.startsWith(qTerm))) dfVal++;
|
|
8165
|
-
}
|
|
8252
|
+
const dfVal = dfByTerm.get(qTerm) ?? 0;
|
|
8166
8253
|
if (dfVal === 0) continue;
|
|
8167
8254
|
const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
|
|
8168
8255
|
const lenRatio = B * (doc.len / this.safeAvgLen);
|
|
@@ -8174,7 +8261,10 @@ var Bm25Index = class {
|
|
|
8174
8261
|
return results;
|
|
8175
8262
|
}
|
|
8176
8263
|
getDoc(id) {
|
|
8177
|
-
|
|
8264
|
+
if (!this._byId) {
|
|
8265
|
+
this._byId = new Map(this.documents.map((d) => [d.id, d]));
|
|
8266
|
+
}
|
|
8267
|
+
return this._byId.get(id);
|
|
8178
8268
|
}
|
|
8179
8269
|
extractSnippet(docId, queryTokens, radius = 40) {
|
|
8180
8270
|
const doc = this.getDoc(docId);
|
|
@@ -8336,6 +8426,25 @@ var IndexStore = class _IndexStore {
|
|
|
8336
8426
|
* and reuse it. Cleared in {@link close} when the connection is torn down.
|
|
8337
8427
|
*/
|
|
8338
8428
|
stmtCache = /* @__PURE__ */ new Map();
|
|
8429
|
+
/**
|
|
8430
|
+
* Cached full-corpus BM25 index for the FTS5-unavailable fallback path.
|
|
8431
|
+
* Built lazily on the first `searchRankedFallback` call and invalidated
|
|
8432
|
+
* (via `bm25Dirty`) whenever the `symbols` table is mutated. Computing
|
|
8433
|
+
* IDF over the full corpus is also more correct than the old per-query
|
|
8434
|
+
* candidate-subset IDF.
|
|
8435
|
+
*
|
|
8436
|
+
* Cache-lifecycle invariants (single source of truth lives at the
|
|
8437
|
+
* `invalidateBm25()` helper — see its docblock for the "every mutation
|
|
8438
|
+
* MUST call this" contract):
|
|
8439
|
+
* - declaration: this field + `bm25Dirty` (here)
|
|
8440
|
+
* - invalidation: `invalidateBm25()` flips the flag and nulls the cache
|
|
8441
|
+
* - build: `getOrBuildBm25()` rebuilds against current `symbols` rows
|
|
8442
|
+
* - teardown: `close()` resets the flag and nulls the cache
|
|
8443
|
+
*/
|
|
8444
|
+
bm25Cache = null;
|
|
8445
|
+
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
8446
|
+
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
8447
|
+
bm25Dirty = true;
|
|
8339
8448
|
/** Prepare-once helper: compile `sql` on first use, reuse thereafter. */
|
|
8340
8449
|
stmt(sql) {
|
|
8341
8450
|
let s = this.stmtCache.get(sql);
|
|
@@ -8383,7 +8492,15 @@ var IndexStore = class _IndexStore {
|
|
|
8383
8492
|
this.db = new Database(path13.join(this.indexDir, DB_FILE));
|
|
8384
8493
|
try {
|
|
8385
8494
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
8386
|
-
this.db.exec("PRAGMA
|
|
8495
|
+
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
8496
|
+
this.db.exec("PRAGMA busy_timeout = 15000");
|
|
8497
|
+
this.db.exec("PRAGMA temp_store = MEMORY");
|
|
8498
|
+
const cache2 = sqliteCachePragmas();
|
|
8499
|
+
this.db.exec(`PRAGMA cache_size = -${cache2.cacheSizeKiB}`);
|
|
8500
|
+
this.db.exec(`PRAGMA mmap_size = ${cache2.mmapBytes}`);
|
|
8501
|
+
this.db.exec("PRAGMA foreign_keys = ON");
|
|
8502
|
+
this.db.exec("PRAGMA journal_size_limit = 67108864");
|
|
8503
|
+
this.db.exec("PRAGMA wal_autocheckpoint = 1000");
|
|
8387
8504
|
} catch {
|
|
8388
8505
|
}
|
|
8389
8506
|
this.initSchema();
|
|
@@ -8395,7 +8512,7 @@ var IndexStore = class _IndexStore {
|
|
|
8395
8512
|
value TEXT NOT NULL
|
|
8396
8513
|
);
|
|
8397
8514
|
`);
|
|
8398
|
-
const storedRows = this.
|
|
8515
|
+
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
8399
8516
|
const storedVersion = storedRows.length ? Number(storedRows[0]?.value) : null;
|
|
8400
8517
|
if (storedVersion !== null && storedVersion !== SCHEMA_VERSION) {
|
|
8401
8518
|
this.db.exec(`
|
|
@@ -8404,9 +8521,9 @@ var IndexStore = class _IndexStore {
|
|
|
8404
8521
|
DROP TABLE IF EXISTS refs;
|
|
8405
8522
|
`);
|
|
8406
8523
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
8407
|
-
this.
|
|
8524
|
+
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
|
|
8408
8525
|
} else if (storedVersion === null) {
|
|
8409
|
-
this.
|
|
8526
|
+
this.stmt("INSERT INTO metadata(key, value) VALUES (?, ?)").run("version", String(SCHEMA_VERSION));
|
|
8410
8527
|
}
|
|
8411
8528
|
this.db.exec(`
|
|
8412
8529
|
CREATE TABLE IF NOT EXISTS files (
|
|
@@ -8437,6 +8554,7 @@ var IndexStore = class _IndexStore {
|
|
|
8437
8554
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)");
|
|
8438
8555
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)");
|
|
8439
8556
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)");
|
|
8557
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)");
|
|
8440
8558
|
this.db.exec(`
|
|
8441
8559
|
CREATE TABLE IF NOT EXISTS refs (
|
|
8442
8560
|
id INTEGER PRIMARY KEY,
|
|
@@ -8457,65 +8575,167 @@ var IndexStore = class _IndexStore {
|
|
|
8457
8575
|
);
|
|
8458
8576
|
this.ftsAvailable = true;
|
|
8459
8577
|
const symbolCount = Number(
|
|
8460
|
-
this.
|
|
8578
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols").get()?.n ?? 0
|
|
8461
8579
|
);
|
|
8462
8580
|
const ftsCount = Number(
|
|
8463
|
-
this.
|
|
8581
|
+
this.stmt("SELECT COUNT(*) AS n FROM symbols_fts").get()?.n ?? 0
|
|
8464
8582
|
);
|
|
8465
8583
|
if (symbolCount !== ftsCount) {
|
|
8466
8584
|
this.db.exec("DELETE FROM symbols_fts");
|
|
8467
|
-
const
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8585
|
+
const rows = this.stmt("SELECT id, name, signature, doc_comment FROM symbols ORDER BY id").all();
|
|
8586
|
+
this.bulkInsertFts(
|
|
8587
|
+
rows.map((row) => ({
|
|
8588
|
+
id: row.id,
|
|
8589
|
+
text: buildIndexableText(row.name, row.signature, row.doc_comment)
|
|
8590
|
+
}))
|
|
8591
|
+
);
|
|
8592
|
+
this.invalidateBm25();
|
|
8472
8593
|
}
|
|
8473
8594
|
} catch {
|
|
8474
8595
|
this.ftsAvailable = false;
|
|
8475
8596
|
}
|
|
8597
|
+
this.ensureNextSymbolIdSeeded();
|
|
8598
|
+
}
|
|
8599
|
+
// ─── ID allocation & bulk helpers ────────────────────────────────────────────
|
|
8600
|
+
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
8601
|
+
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
8602
|
+
static MAX_SQL_VARS = 900;
|
|
8603
|
+
/**
|
|
8604
|
+
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
8605
|
+
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
8606
|
+
* re-reads and advances the counter atomically.
|
|
8607
|
+
*/
|
|
8608
|
+
ensureNextSymbolIdSeeded() {
|
|
8609
|
+
const existing = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8610
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8611
|
+
);
|
|
8612
|
+
if (existing?.value !== void 0) return;
|
|
8613
|
+
const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
|
|
8614
|
+
const next = (maxRows[0]?.m ?? 0) + 1;
|
|
8615
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8616
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8617
|
+
String(next)
|
|
8618
|
+
);
|
|
8619
|
+
}
|
|
8620
|
+
/**
|
|
8621
|
+
* Reserve `count` consecutive symbol ids. MUST run inside BEGIN IMMEDIATE
|
|
8622
|
+
* so concurrent indexers cannot hand out overlapping ranges.
|
|
8623
|
+
*/
|
|
8624
|
+
allocateSymbolIds(count) {
|
|
8625
|
+
if (count <= 0) return this.getMaxSymbolId() + 1;
|
|
8626
|
+
this.ensureNextSymbolIdSeeded();
|
|
8627
|
+
const row = this.stmt("SELECT value FROM metadata WHERE key = ?").get(
|
|
8628
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY
|
|
8629
|
+
);
|
|
8630
|
+
const start = Math.max(1, Number(row?.value ?? 1) || 1);
|
|
8631
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
8632
|
+
_IndexStore.NEXT_SYMBOL_ID_KEY,
|
|
8633
|
+
String(start + count)
|
|
8634
|
+
);
|
|
8635
|
+
return start;
|
|
8636
|
+
}
|
|
8637
|
+
/** Multi-row INSERT for symbols — amortizes prepare/bind overhead in large batches. */
|
|
8638
|
+
bulkInsertSymbols(rows) {
|
|
8639
|
+
if (rows.length === 0) return;
|
|
8640
|
+
const cols = 12;
|
|
8641
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8642
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8643
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8644
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
8645
|
+
const stmt = this.stmt(
|
|
8646
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
8647
|
+
VALUES ${placeholders}`
|
|
8648
|
+
);
|
|
8649
|
+
const binds = [];
|
|
8650
|
+
for (const r of chunk) {
|
|
8651
|
+
binds.push(
|
|
8652
|
+
r.id,
|
|
8653
|
+
r.lang,
|
|
8654
|
+
r.kind,
|
|
8655
|
+
r.name,
|
|
8656
|
+
r.file,
|
|
8657
|
+
r.line,
|
|
8658
|
+
r.col,
|
|
8659
|
+
r.signature,
|
|
8660
|
+
r.docComment,
|
|
8661
|
+
r.scope,
|
|
8662
|
+
r.text,
|
|
8663
|
+
r.file
|
|
8664
|
+
);
|
|
8665
|
+
}
|
|
8666
|
+
stmt.run(...binds);
|
|
8667
|
+
}
|
|
8668
|
+
}
|
|
8669
|
+
bulkInsertFts(rows) {
|
|
8670
|
+
if (!this.ftsAvailable || rows.length === 0) return;
|
|
8671
|
+
const cols = 2;
|
|
8672
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8673
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
8674
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
8675
|
+
const placeholders = chunk.map(() => "(?, ?)").join(", ");
|
|
8676
|
+
const stmt = this.stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders}`);
|
|
8677
|
+
const binds = [];
|
|
8678
|
+
for (const r of chunk) binds.push(r.id, r.text);
|
|
8679
|
+
stmt.run(...binds);
|
|
8680
|
+
}
|
|
8681
|
+
}
|
|
8682
|
+
bulkInsertRefs(refs) {
|
|
8683
|
+
if (refs.length === 0) return;
|
|
8684
|
+
const cols = 5;
|
|
8685
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / cols));
|
|
8686
|
+
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
8687
|
+
const chunk = refs.slice(i, i + chunkSize);
|
|
8688
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
8689
|
+
const stmt = this.stmt(
|
|
8690
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line) VALUES ${placeholders}`
|
|
8691
|
+
);
|
|
8692
|
+
const binds = [];
|
|
8693
|
+
for (const ref of chunk) {
|
|
8694
|
+
binds.push(ref.fromId, ref.toName, ref.toId ?? null, ref.callType, ref.line);
|
|
8695
|
+
}
|
|
8696
|
+
stmt.run(...binds);
|
|
8697
|
+
}
|
|
8476
8698
|
}
|
|
8477
8699
|
// ─── Symbol CRUD ─────────────────────────────────────────────────────────────
|
|
8478
8700
|
/**
|
|
8479
8701
|
* 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).
|
|
8702
|
+
* `COMMIT`. Id ranges come from the `next_symbol_id` metadata counter
|
|
8703
|
+
* (O(1)); multi-row INSERT amortizes bind overhead for large files.
|
|
8484
8704
|
*
|
|
8485
8705
|
* @returns The symbols array with `id` fields populated so the caller can
|
|
8486
8706
|
* use them for refs without re-reading from the DB.
|
|
8487
8707
|
*/
|
|
8488
8708
|
insertSymbols(symbols) {
|
|
8709
|
+
this.invalidateBm25();
|
|
8489
8710
|
return this.runWithRetry(() => {
|
|
8490
8711
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8491
8712
|
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;
|
|
8713
|
+
let nextId = this.allocateSymbolIds(symbols.length);
|
|
8499
8714
|
const result = [];
|
|
8715
|
+
const bulk = [];
|
|
8716
|
+
const ftsRows = [];
|
|
8500
8717
|
for (const s of symbols) {
|
|
8501
8718
|
const id = nextId++;
|
|
8502
|
-
|
|
8719
|
+
bulk.push({
|
|
8503
8720
|
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
|
-
|
|
8721
|
+
lang: s.lang,
|
|
8722
|
+
kind: s.kind,
|
|
8723
|
+
name: s.name,
|
|
8724
|
+
file: s.file,
|
|
8725
|
+
line: s.line,
|
|
8726
|
+
col: s.col,
|
|
8727
|
+
signature: s.signature,
|
|
8728
|
+
docComment: s.docComment,
|
|
8729
|
+
scope: s.scope,
|
|
8730
|
+
text: s.text
|
|
8731
|
+
});
|
|
8732
|
+
if (this.ftsAvailable) {
|
|
8733
|
+
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
8734
|
+
}
|
|
8517
8735
|
result.push({ ...s, id });
|
|
8518
8736
|
}
|
|
8737
|
+
this.bulkInsertSymbols(bulk);
|
|
8738
|
+
this.bulkInsertFts(ftsRows);
|
|
8519
8739
|
this.db.exec("COMMIT");
|
|
8520
8740
|
return result;
|
|
8521
8741
|
} catch (err) {
|
|
@@ -8525,6 +8745,7 @@ var IndexStore = class _IndexStore {
|
|
|
8525
8745
|
});
|
|
8526
8746
|
}
|
|
8527
8747
|
deleteSymbolsForFile(file) {
|
|
8748
|
+
this.invalidateBm25();
|
|
8528
8749
|
this.runWithRetry(() => {
|
|
8529
8750
|
if (this.ftsAvailable) {
|
|
8530
8751
|
this.stmt(
|
|
@@ -8540,17 +8761,20 @@ var IndexStore = class _IndexStore {
|
|
|
8540
8761
|
* dropped the `files` row, leaving its symbols orphaned but still searchable.
|
|
8541
8762
|
*/
|
|
8542
8763
|
deleteFile(file) {
|
|
8764
|
+
this.invalidateBm25();
|
|
8543
8765
|
this.runWithRetry(() => {
|
|
8544
8766
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8545
8767
|
try {
|
|
8546
8768
|
if (this.ftsAvailable) {
|
|
8547
|
-
this.
|
|
8769
|
+
this.stmt(
|
|
8548
8770
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8549
8771
|
).run(file);
|
|
8550
8772
|
}
|
|
8551
|
-
this.
|
|
8552
|
-
|
|
8553
|
-
|
|
8773
|
+
this.stmt(
|
|
8774
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
8775
|
+
).run(file);
|
|
8776
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
8777
|
+
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
8554
8778
|
this.db.exec("COMMIT");
|
|
8555
8779
|
} catch (err) {
|
|
8556
8780
|
this.db.exec("ROLLBACK");
|
|
@@ -8587,7 +8811,7 @@ var IndexStore = class _IndexStore {
|
|
|
8587
8811
|
};
|
|
8588
8812
|
}
|
|
8589
8813
|
getAllFileMetas() {
|
|
8590
|
-
return this.
|
|
8814
|
+
return this.stmt("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
|
|
8591
8815
|
file: r.file,
|
|
8592
8816
|
lang: r.lang,
|
|
8593
8817
|
mtimeMs: r.mtime_ms,
|
|
@@ -8596,7 +8820,32 @@ var IndexStore = class _IndexStore {
|
|
|
8596
8820
|
}));
|
|
8597
8821
|
}
|
|
8598
8822
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
8599
|
-
search(query, filter) {
|
|
8823
|
+
search(query, filter, opts) {
|
|
8824
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8825
|
+
if (built === null) return [];
|
|
8826
|
+
const { where, values } = built;
|
|
8827
|
+
const limit = typeof opts?.limit === "number" && Number.isFinite(opts.limit) ? Math.max(0, Math.trunc(opts.limit)) : void 0;
|
|
8828
|
+
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
8829
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment, text FROM symbols ${where}${limitSql}`;
|
|
8830
|
+
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
8831
|
+
const rows = this.stmt(sql).all(...binds);
|
|
8832
|
+
return rows.map((r) => ({
|
|
8833
|
+
id: r.id,
|
|
8834
|
+
lang: r.lang,
|
|
8835
|
+
kind: r.kind,
|
|
8836
|
+
name: r.name,
|
|
8837
|
+
file: r.file,
|
|
8838
|
+
line: r.line,
|
|
8839
|
+
col: r.col,
|
|
8840
|
+
signature: r.signature,
|
|
8841
|
+
docComment: r.doc_comment,
|
|
8842
|
+
score: 0,
|
|
8843
|
+
snippet: "",
|
|
8844
|
+
lspKind: filter?.lspKind
|
|
8845
|
+
}));
|
|
8846
|
+
}
|
|
8847
|
+
/** Shared WHERE builder for {@link search} / empty-query ranked totals. */
|
|
8848
|
+
buildSearchWhere(query, filter) {
|
|
8600
8849
|
const conditions = [];
|
|
8601
8850
|
const values = [];
|
|
8602
8851
|
let effectiveKind = filter?.kind;
|
|
@@ -8605,7 +8854,7 @@ var IndexStore = class _IndexStore {
|
|
|
8605
8854
|
if (mapped !== null) {
|
|
8606
8855
|
effectiveKind = mapped;
|
|
8607
8856
|
} else {
|
|
8608
|
-
return
|
|
8857
|
+
return null;
|
|
8609
8858
|
}
|
|
8610
8859
|
}
|
|
8611
8860
|
if (effectiveKind) {
|
|
@@ -8627,23 +8876,15 @@ var IndexStore = class _IndexStore {
|
|
|
8627
8876
|
for (const t of tokens) values.push(`%${t}%`);
|
|
8628
8877
|
}
|
|
8629
8878
|
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
|
-
}));
|
|
8879
|
+
return { where, values };
|
|
8880
|
+
}
|
|
8881
|
+
countSearch(query, filter) {
|
|
8882
|
+
const built = this.buildSearchWhere(query, filter);
|
|
8883
|
+
if (built === null) return 0;
|
|
8884
|
+
const row = this.stmt(`SELECT COUNT(*) AS n FROM symbols ${built.where}`).get(
|
|
8885
|
+
...built.values
|
|
8886
|
+
);
|
|
8887
|
+
return Number(row?.n ?? 0);
|
|
8647
8888
|
}
|
|
8648
8889
|
/**
|
|
8649
8890
|
* Ranked search — the one-stop query the codebase-search tool and plug-lsp
|
|
@@ -8684,12 +8925,12 @@ var IndexStore = class _IndexStore {
|
|
|
8684
8925
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
8685
8926
|
}
|
|
8686
8927
|
const where = conditions.join(" AND ");
|
|
8687
|
-
const countRows = this.
|
|
8928
|
+
const countRows = this.stmt(
|
|
8688
8929
|
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
8689
8930
|
).all(...values);
|
|
8690
8931
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
8691
8932
|
if (total === 0) return { results: [], total: 0 };
|
|
8692
|
-
const rows = this.
|
|
8933
|
+
const rows = this.stmt(
|
|
8693
8934
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
8694
8935
|
-bm25(symbols_fts) AS score,
|
|
8695
8936
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
@@ -8722,20 +8963,60 @@ var IndexStore = class _IndexStore {
|
|
|
8722
8963
|
total
|
|
8723
8964
|
};
|
|
8724
8965
|
}
|
|
8966
|
+
/**
|
|
8967
|
+
* Invalidate the cached BM25 index.
|
|
8968
|
+
*
|
|
8969
|
+
* **Contract: every method that mutates `symbols` MUST call this before
|
|
8970
|
+
* returning.** (`refs` mutations do not affect the BM25 fallback because
|
|
8971
|
+
* the corpus is built from `symbols.text` via `getAllIndexable()` and the
|
|
8972
|
+
* BM25 score is filtered by the LIKE-selected candidate set in
|
|
8973
|
+
* `searchRankedFallback`.) Today the call sites are `repairDrift`,
|
|
8974
|
+
* `insertSymbols`, `deleteSymbolsForFile`, `deleteFile`, `clearAll`, and
|
|
8975
|
+
* `commitBatch`. A future mutation that adds a new write path (e.g.
|
|
8976
|
+
* `renameFile`, `updateSignature`) MUST also call this — otherwise the
|
|
8977
|
+
* FTS5-unavailable fallback will serve stale search results. The
|
|
8978
|
+
* `close()` reset at L1820-1821 tears the cache down on store shutdown,
|
|
8979
|
+
* which is the only legitimate place that flips the flag outside this
|
|
8980
|
+
* helper.
|
|
8981
|
+
*
|
|
8982
|
+
* Called *before* `runWithRetry` on purpose: if the write fails all
|
|
8983
|
+
* retries the flag stays set, forcing a rebuild on the next search rather
|
|
8984
|
+
* than trusting a cache that may not reflect the intended mutation.
|
|
8985
|
+
* Do not move this inside the retry closure.
|
|
8986
|
+
*/
|
|
8987
|
+
invalidateBm25() {
|
|
8988
|
+
this.bm25Dirty = true;
|
|
8989
|
+
this.bm25Cache = null;
|
|
8990
|
+
}
|
|
8991
|
+
/**
|
|
8992
|
+
* Return the cached full-corpus BM25 index, rebuilding it only when the
|
|
8993
|
+
* symbols table has been mutated since the last build. The full-corpus IDF
|
|
8994
|
+
* is more correct than the old per-query candidate-subset IDF, and the
|
|
8995
|
+
* amortized build cost drops from O(symbols × tokens) per search to once
|
|
8996
|
+
* per write batch.
|
|
8997
|
+
*
|
|
8998
|
+
* Note: the first call after a long idle (or on a freshly opened store)
|
|
8999
|
+
* pays the full corpus rebuild synchronously on the search path. For a
|
|
9000
|
+
* 5 500+ symbol corpus this is a visible one-time latency spike.
|
|
9001
|
+
*/
|
|
9002
|
+
getOrBuildBm25() {
|
|
9003
|
+
if (this.bm25Cache && !this.bm25Dirty) return this.bm25Cache;
|
|
9004
|
+
const docs = this.getAllIndexable();
|
|
9005
|
+
this.bm25Cache = buildBm25Index(docs);
|
|
9006
|
+
this.bm25Dirty = false;
|
|
9007
|
+
return this.bm25Cache;
|
|
9008
|
+
}
|
|
8725
9009
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
8726
9010
|
searchRankedFallback(query, filter, limit) {
|
|
8727
|
-
const candidates = this.search(query, filter);
|
|
8728
|
-
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8729
9011
|
if (!query.trim()) {
|
|
8730
|
-
|
|
9012
|
+
const total = this.countSearch(query, filter);
|
|
9013
|
+
if (total === 0) return { results: [], total: 0 };
|
|
9014
|
+
return { results: this.search(query, filter, { limit }), total };
|
|
8731
9015
|
}
|
|
9016
|
+
const candidates = this.search(query, filter);
|
|
9017
|
+
if (candidates.length === 0) return { results: [], total: 0 };
|
|
8732
9018
|
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
|
-
);
|
|
9019
|
+
const bm25 = this.getOrBuildBm25();
|
|
8739
9020
|
const scored = bm25.score(query, (id) => candidateById.has(id));
|
|
8740
9021
|
const q = query.trim().toLowerCase();
|
|
8741
9022
|
const rank = (id) => {
|
|
@@ -8777,7 +9058,7 @@ var IndexStore = class _IndexStore {
|
|
|
8777
9058
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
8778
9059
|
getStats() {
|
|
8779
9060
|
const sizeBytes = this.sizeBytes();
|
|
8780
|
-
const lastRows = this.
|
|
9061
|
+
const lastRows = this.stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
8781
9062
|
const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
|
|
8782
9063
|
const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
|
|
8783
9064
|
const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
|
|
@@ -8802,19 +9083,20 @@ var IndexStore = class _IndexStore {
|
|
|
8802
9083
|
}
|
|
8803
9084
|
setLastIndexed(ts2) {
|
|
8804
9085
|
this.runWithRetry(() => {
|
|
8805
|
-
this.
|
|
9086
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(String(ts2));
|
|
8806
9087
|
});
|
|
8807
9088
|
}
|
|
8808
9089
|
getMetadata(key) {
|
|
8809
|
-
const rows = this.
|
|
9090
|
+
const rows = this.stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
8810
9091
|
return rows[0]?.value;
|
|
8811
9092
|
}
|
|
8812
9093
|
setMetadata(key, value) {
|
|
8813
9094
|
this.runWithRetry(() => {
|
|
8814
|
-
this.
|
|
9095
|
+
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES(?, ?)").run(key, value);
|
|
8815
9096
|
});
|
|
8816
9097
|
}
|
|
8817
9098
|
clearAll() {
|
|
9099
|
+
this.invalidateBm25();
|
|
8818
9100
|
this.runWithRetry(() => {
|
|
8819
9101
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8820
9102
|
try {
|
|
@@ -8841,13 +9123,7 @@ var IndexStore = class _IndexStore {
|
|
|
8841
9123
|
this.runWithRetry(() => {
|
|
8842
9124
|
this.stmt("DELETE FROM refs WHERE from_id = ?").run(fromId);
|
|
8843
9125
|
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
|
-
}
|
|
9126
|
+
this.bulkInsertRefs(refs.map((ref) => ({ ...ref, fromId })));
|
|
8851
9127
|
});
|
|
8852
9128
|
}
|
|
8853
9129
|
/**
|
|
@@ -8864,13 +9140,7 @@ var IndexStore = class _IndexStore {
|
|
|
8864
9140
|
insertRefsBatch(refs) {
|
|
8865
9141
|
if (refs.length === 0) return;
|
|
8866
9142
|
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
|
-
}
|
|
9143
|
+
this.bulkInsertRefs(refs);
|
|
8874
9144
|
});
|
|
8875
9145
|
}
|
|
8876
9146
|
/**
|
|
@@ -8895,65 +9165,61 @@ var IndexStore = class _IndexStore {
|
|
|
8895
9165
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
8896
9166
|
return [];
|
|
8897
9167
|
}
|
|
9168
|
+
this.invalidateBm25();
|
|
8898
9169
|
return this.runWithRetry(() => {
|
|
8899
9170
|
this.db.exec("BEGIN IMMEDIATE");
|
|
8900
9171
|
try {
|
|
8901
9172
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
8902
9173
|
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
8903
9174
|
if (this.ftsAvailable) {
|
|
8904
|
-
this.
|
|
9175
|
+
this.stmt(
|
|
8905
9176
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8906
9177
|
).run(...options.deleteForFiles);
|
|
8907
9178
|
}
|
|
8908
|
-
this.
|
|
9179
|
+
this.stmt(
|
|
8909
9180
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
8910
9181
|
).run(...options.deleteForFiles);
|
|
8911
|
-
this.
|
|
9182
|
+
this.stmt(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
8912
9183
|
}
|
|
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;
|
|
9184
|
+
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
9185
|
+
let nextId = this.allocateSymbolIds(totalSymbols);
|
|
8920
9186
|
const allInserted = [];
|
|
8921
9187
|
const refsToInsert = [];
|
|
9188
|
+
const bulkSyms = [];
|
|
9189
|
+
const ftsRows = [];
|
|
8922
9190
|
for (const entry of entries) {
|
|
8923
9191
|
const insertedForEntry = [];
|
|
8924
9192
|
for (const s of entry.symbols) {
|
|
8925
9193
|
const id = nextId++;
|
|
8926
|
-
|
|
9194
|
+
bulkSyms.push({
|
|
8927
9195
|
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
|
-
|
|
9196
|
+
lang: s.lang,
|
|
9197
|
+
kind: s.kind,
|
|
9198
|
+
name: s.name,
|
|
9199
|
+
file: s.file,
|
|
9200
|
+
line: s.line,
|
|
9201
|
+
col: s.col,
|
|
9202
|
+
signature: s.signature,
|
|
9203
|
+
docComment: s.docComment,
|
|
9204
|
+
scope: s.scope,
|
|
9205
|
+
text: s.text
|
|
9206
|
+
});
|
|
9207
|
+
if (this.ftsAvailable) {
|
|
9208
|
+
ftsRows.push({
|
|
9209
|
+
id,
|
|
9210
|
+
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
9211
|
+
});
|
|
9212
|
+
}
|
|
8941
9213
|
const inserted = { ...s, id };
|
|
8942
9214
|
allInserted.push(inserted);
|
|
8943
9215
|
insertedForEntry.push(inserted);
|
|
8944
9216
|
}
|
|
8945
9217
|
refsToInsert.push(...assignRefsToSymbols(entry.refs, insertedForEntry));
|
|
8946
9218
|
}
|
|
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(
|
|
9219
|
+
this.bulkInsertSymbols(bulkSyms);
|
|
9220
|
+
this.bulkInsertFts(ftsRows);
|
|
9221
|
+
this.bulkInsertRefs(refsToInsert);
|
|
9222
|
+
const upsertStmt = this.stmt(
|
|
8957
9223
|
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
8958
9224
|
VALUES (?, ?, ?, ?, ?)
|
|
8959
9225
|
ON CONFLICT(file) DO UPDATE SET
|
|
@@ -8980,7 +9246,7 @@ var IndexStore = class _IndexStore {
|
|
|
8980
9246
|
*/
|
|
8981
9247
|
deleteRefsForFile(file) {
|
|
8982
9248
|
this.runWithRetry(() => {
|
|
8983
|
-
this.
|
|
9249
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(file);
|
|
8984
9250
|
});
|
|
8985
9251
|
}
|
|
8986
9252
|
/**
|
|
@@ -8993,20 +9259,75 @@ var IndexStore = class _IndexStore {
|
|
|
8993
9259
|
*/
|
|
8994
9260
|
resolveRefs() {
|
|
8995
9261
|
return this.runWithRetry(() => {
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9262
|
+
try {
|
|
9263
|
+
const result = this.stmt(
|
|
9264
|
+
`UPDATE refs
|
|
9265
|
+
SET to_id = s.id
|
|
9266
|
+
FROM (
|
|
9267
|
+
SELECT name, MIN(id) AS id FROM symbols GROUP BY name
|
|
9268
|
+
) AS s
|
|
9269
|
+
WHERE refs.to_id IS NULL
|
|
9270
|
+
AND refs.to_name IS NOT NULL
|
|
9271
|
+
AND refs.to_name = s.name`
|
|
9272
|
+
).run();
|
|
9273
|
+
return result.changes ?? 0;
|
|
9274
|
+
} catch {
|
|
9275
|
+
const result = this.stmt(
|
|
9276
|
+
`UPDATE refs SET to_id = (
|
|
9277
|
+
SELECT id FROM symbols WHERE name = refs.to_name LIMIT 1
|
|
9278
|
+
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
9279
|
+
AND to_name IN (SELECT name FROM symbols)`
|
|
9280
|
+
).run();
|
|
9281
|
+
return result.changes ?? 0;
|
|
9282
|
+
}
|
|
9003
9283
|
});
|
|
9004
9284
|
}
|
|
9285
|
+
/**
|
|
9286
|
+
* Clear symbols/refs for a file and mark it as indexed with zero symbols.
|
|
9287
|
+
* Used by the indexer for empty-parse results so three writes share one txn.
|
|
9288
|
+
*/
|
|
9289
|
+
replaceEmptyFile(meta) {
|
|
9290
|
+
this.invalidateBm25();
|
|
9291
|
+
this.runWithRetry(() => {
|
|
9292
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
9293
|
+
try {
|
|
9294
|
+
if (this.ftsAvailable) {
|
|
9295
|
+
this.stmt(
|
|
9296
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
9297
|
+
).run(meta.file);
|
|
9298
|
+
}
|
|
9299
|
+
this.stmt(
|
|
9300
|
+
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
9301
|
+
).run(meta.file);
|
|
9302
|
+
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
9303
|
+
this.stmt(
|
|
9304
|
+
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
9305
|
+
VALUES (?, ?, ?, ?, ?)
|
|
9306
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
9307
|
+
lang = excluded.lang,
|
|
9308
|
+
mtime_ms = excluded.mtime_ms,
|
|
9309
|
+
symbol_count = excluded.symbol_count,
|
|
9310
|
+
last_indexed = excluded.last_indexed`
|
|
9311
|
+
).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
|
|
9312
|
+
this.db.exec("COMMIT");
|
|
9313
|
+
} catch (err) {
|
|
9314
|
+
this.db.exec("ROLLBACK");
|
|
9315
|
+
throw err;
|
|
9316
|
+
}
|
|
9317
|
+
});
|
|
9318
|
+
}
|
|
9319
|
+
/** Best-effort query planner refresh after a large reindex. */
|
|
9320
|
+
optimize() {
|
|
9321
|
+
try {
|
|
9322
|
+
this.db.exec("PRAGMA optimize");
|
|
9323
|
+
} catch {
|
|
9324
|
+
}
|
|
9325
|
+
}
|
|
9005
9326
|
/**
|
|
9006
9327
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
9007
9328
|
*/
|
|
9008
9329
|
findRefsTo(symbolId) {
|
|
9009
|
-
return this.
|
|
9330
|
+
return this.stmt(
|
|
9010
9331
|
"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
9332
|
).all(symbolId, symbolId).map((r) => ({
|
|
9012
9333
|
id: r.id,
|
|
@@ -9021,7 +9342,7 @@ var IndexStore = class _IndexStore {
|
|
|
9021
9342
|
* Find all references FROM a given symbol (what does this symbol call/use?).
|
|
9022
9343
|
*/
|
|
9023
9344
|
findRefsFrom(symbolId) {
|
|
9024
|
-
return this.
|
|
9345
|
+
return this.stmt("SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE from_id = ?").all(symbolId).map((r) => ({
|
|
9025
9346
|
id: r.id,
|
|
9026
9347
|
fromId: r.from_id,
|
|
9027
9348
|
toName: r.to_name,
|
|
@@ -9094,45 +9415,57 @@ var IndexStore = class _IndexStore {
|
|
|
9094
9415
|
* symbol resolved in package B). Node metadata includes symbol/file counts.
|
|
9095
9416
|
*/
|
|
9096
9417
|
getPackageGraph() {
|
|
9097
|
-
const
|
|
9418
|
+
const fileCounts = this.stmt(
|
|
9419
|
+
"SELECT file, COUNT(*) AS n FROM symbols GROUP BY file"
|
|
9420
|
+
).all();
|
|
9098
9421
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
9099
9422
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
9100
|
-
const
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
fileToPkg.set(s.file, pkg);
|
|
9104
|
-
symbolToPkg.set(s.id, pkg);
|
|
9423
|
+
for (const { file, n } of fileCounts) {
|
|
9424
|
+
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9425
|
+
fileToPkg.set(file, pkg);
|
|
9105
9426
|
const node = pkgNodes.get(pkg);
|
|
9106
9427
|
if (node) {
|
|
9107
|
-
node.symbolCount = (node.symbolCount ?? 0) +
|
|
9428
|
+
node.symbolCount = (node.symbolCount ?? 0) + n;
|
|
9108
9429
|
} else {
|
|
9109
9430
|
pkgNodes.set(pkg, {
|
|
9110
9431
|
id: `pkg:${pkg}`,
|
|
9111
9432
|
label: pkg,
|
|
9112
9433
|
kind: "package",
|
|
9113
9434
|
package: pkg,
|
|
9114
|
-
symbolCount:
|
|
9435
|
+
symbolCount: n,
|
|
9115
9436
|
fileCount: 0
|
|
9116
9437
|
});
|
|
9117
9438
|
}
|
|
9118
9439
|
}
|
|
9119
|
-
const files = this.
|
|
9440
|
+
const files = this.stmt("SELECT DISTINCT file FROM files").all();
|
|
9120
9441
|
for (const { file } of files) {
|
|
9121
9442
|
const pkg = _IndexStore.derivePackage(file) ?? "(root)";
|
|
9443
|
+
fileToPkg.set(file, pkg);
|
|
9122
9444
|
const node = pkgNodes.get(pkg);
|
|
9123
9445
|
if (node) node.fileCount = (node.fileCount ?? 0) + 1;
|
|
9446
|
+
else {
|
|
9447
|
+
pkgNodes.set(pkg, {
|
|
9448
|
+
id: `pkg:${pkg}`,
|
|
9449
|
+
label: pkg,
|
|
9450
|
+
kind: "package",
|
|
9451
|
+
package: pkg,
|
|
9452
|
+
symbolCount: 0,
|
|
9453
|
+
fileCount: 1
|
|
9454
|
+
});
|
|
9455
|
+
}
|
|
9124
9456
|
}
|
|
9125
|
-
const refRows = this.
|
|
9126
|
-
`SELECT r.
|
|
9457
|
+
const refRows = this.stmt(
|
|
9458
|
+
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file
|
|
9127
9459
|
FROM refs r
|
|
9128
|
-
|
|
9460
|
+
JOIN symbols sf ON sf.id = r.from_id
|
|
9461
|
+
JOIN symbols st ON st.id = r.to_id
|
|
9462
|
+
WHERE r.to_id IS NOT NULL AND r.call_type != 'import'`
|
|
9129
9463
|
).all();
|
|
9130
9464
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
9131
9465
|
for (const r of refRows) {
|
|
9132
|
-
|
|
9133
|
-
const
|
|
9134
|
-
|
|
9135
|
-
if (!fromPkg || !toPkg || fromPkg === toPkg) continue;
|
|
9466
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
9467
|
+
const toPkg = fileToPkg.get(r.to_file) ?? _IndexStore.derivePackage(r.to_file) ?? "(root)";
|
|
9468
|
+
if (fromPkg === toPkg) continue;
|
|
9136
9469
|
const key = `${fromPkg}\0${toPkg}`;
|
|
9137
9470
|
let e = edgeMap.get(key);
|
|
9138
9471
|
if (!e) {
|
|
@@ -9142,13 +9475,14 @@ var IndexStore = class _IndexStore {
|
|
|
9142
9475
|
e.weight++;
|
|
9143
9476
|
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9144
9477
|
}
|
|
9145
|
-
const importRows = this.
|
|
9146
|
-
`SELECT r.
|
|
9478
|
+
const importRows = this.stmt(
|
|
9479
|
+
`SELECT r.to_name, s.file AS from_file
|
|
9147
9480
|
FROM refs r
|
|
9481
|
+
JOIN symbols s ON s.id = r.from_id
|
|
9148
9482
|
WHERE r.call_type = 'import'`
|
|
9149
9483
|
).all();
|
|
9150
9484
|
for (const r of importRows) {
|
|
9151
|
-
const fromPkg =
|
|
9485
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? _IndexStore.derivePackage(r.from_file) ?? "(root)";
|
|
9152
9486
|
const toPkg = _IndexStore.packageFromImport(r.to_name);
|
|
9153
9487
|
if (!fromPkg || !toPkg || fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
9154
9488
|
const key = `${fromPkg}\0${toPkg}`;
|
|
@@ -9185,12 +9519,12 @@ var IndexStore = class _IndexStore {
|
|
|
9185
9519
|
* derived from cross-file symbol references within the package.
|
|
9186
9520
|
*/
|
|
9187
9521
|
getFileGraph(packageFilter) {
|
|
9188
|
-
const allFiles = this.
|
|
9522
|
+
const allFiles = this.stmt("SELECT DISTINCT file FROM symbols").all();
|
|
9189
9523
|
const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
|
|
9190
9524
|
const localFiles = new Set(pkgFilePaths);
|
|
9191
9525
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
9192
9526
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
9193
|
-
const pkgSyms = this.
|
|
9527
|
+
const pkgSyms = this.stmt(
|
|
9194
9528
|
`SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
|
|
9195
9529
|
).all(...pkgFilePaths);
|
|
9196
9530
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
@@ -9222,7 +9556,7 @@ var IndexStore = class _IndexStore {
|
|
|
9222
9556
|
ensureFileNode(file);
|
|
9223
9557
|
}
|
|
9224
9558
|
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
9225
|
-
const refRows = this.
|
|
9559
|
+
const refRows = this.stmt(
|
|
9226
9560
|
`SELECT r.from_id, r.to_id, r.call_type
|
|
9227
9561
|
FROM refs r
|
|
9228
9562
|
WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
@@ -9237,7 +9571,7 @@ var IndexStore = class _IndexStore {
|
|
|
9237
9571
|
}
|
|
9238
9572
|
if (crossRefIds.size > 0) {
|
|
9239
9573
|
const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
|
|
9240
|
-
const extras = this.
|
|
9574
|
+
const extras = this.stmt(
|
|
9241
9575
|
`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
|
|
9242
9576
|
).all(...crossRefIds);
|
|
9243
9577
|
for (const x of extras) {
|
|
@@ -9265,7 +9599,7 @@ var IndexStore = class _IndexStore {
|
|
|
9265
9599
|
e.weight++;
|
|
9266
9600
|
e.types.set(r.call_type, (e.types.get(r.call_type) ?? 0) + 1);
|
|
9267
9601
|
}
|
|
9268
|
-
const importRows = this.
|
|
9602
|
+
const importRows = this.stmt(
|
|
9269
9603
|
`SELECT r.from_id, r.to_name
|
|
9270
9604
|
FROM refs r
|
|
9271
9605
|
WHERE r.call_type = 'import'
|
|
@@ -9312,7 +9646,7 @@ var IndexStore = class _IndexStore {
|
|
|
9312
9646
|
* derived from intra-file and cross-file symbol references (who calls whom).
|
|
9313
9647
|
*/
|
|
9314
9648
|
getSymbolGraph(fileFilter) {
|
|
9315
|
-
const syms = this.
|
|
9649
|
+
const syms = this.stmt(
|
|
9316
9650
|
"SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
|
|
9317
9651
|
).all(fileFilter);
|
|
9318
9652
|
if (syms.length === 0) return { nodes: [], edges: [] };
|
|
@@ -9333,11 +9667,16 @@ var IndexStore = class _IndexStore {
|
|
|
9333
9667
|
external: s.file !== fileFilter
|
|
9334
9668
|
});
|
|
9335
9669
|
const symIds = new Set(syms.map((s) => s.id));
|
|
9336
|
-
const refRows = this.
|
|
9670
|
+
const refRows = this.stmt(
|
|
9337
9671
|
`SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9338
9672
|
FROM refs r
|
|
9339
|
-
|
|
9340
|
-
|
|
9673
|
+
JOIN symbols s ON s.id = r.from_id
|
|
9674
|
+
WHERE s.file = ?
|
|
9675
|
+
UNION
|
|
9676
|
+
SELECT r.from_id, r.to_id, r.to_name, r.call_type, r.line
|
|
9677
|
+
FROM refs r
|
|
9678
|
+
JOIN symbols s ON s.id = r.to_id
|
|
9679
|
+
WHERE s.file = ?`
|
|
9341
9680
|
).all(fileFilter, fileFilter);
|
|
9342
9681
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
9343
9682
|
for (const r of refRows) {
|
|
@@ -9376,7 +9715,7 @@ var IndexStore = class _IndexStore {
|
|
|
9376
9715
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
9377
9716
|
if (missingIds.length > 0) {
|
|
9378
9717
|
const placeholders = missingIds.map(() => "?").join(",");
|
|
9379
|
-
const extras = this.
|
|
9718
|
+
const extras = this.stmt(
|
|
9380
9719
|
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
|
|
9381
9720
|
).all(...missingIds);
|
|
9382
9721
|
for (const s of extras) symById.set(s.id, s);
|
|
@@ -9390,6 +9729,8 @@ var IndexStore = class _IndexStore {
|
|
|
9390
9729
|
}
|
|
9391
9730
|
close() {
|
|
9392
9731
|
this.stmtCache.clear();
|
|
9732
|
+
this.bm25Dirty = true;
|
|
9733
|
+
this.bm25Cache = null;
|
|
9393
9734
|
try {
|
|
9394
9735
|
this.db.close();
|
|
9395
9736
|
} catch {
|
|
@@ -9403,14 +9744,121 @@ import { fileURLToPath } from "node:url";
|
|
|
9403
9744
|
import { Worker } from "node:worker_threads";
|
|
9404
9745
|
|
|
9405
9746
|
// src/codebase-index/indexer.ts
|
|
9406
|
-
import { expectDefined as expectDefined6 } from "@wrongstack/core";
|
|
9747
|
+
import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
|
|
9407
9748
|
import * as fs14 from "node:fs/promises";
|
|
9408
|
-
import * as
|
|
9749
|
+
import * as path20 from "node:path";
|
|
9409
9750
|
import { availableParallelism } from "node:os";
|
|
9410
|
-
import {
|
|
9751
|
+
import { indexParallelBatchSize, isFrugalPerf } from "@wrongstack/core/utils";
|
|
9411
9752
|
|
|
9412
9753
|
// src/codebase-index/ts-parser.ts
|
|
9413
9754
|
import * as ts from "@typescript/typescript6";
|
|
9755
|
+
|
|
9756
|
+
// src/codebase-index/languages.ts
|
|
9757
|
+
import * as path14 from "node:path";
|
|
9758
|
+
var EXT_TO_LANG = {
|
|
9759
|
+
// TypeScript / JavaScript (first-class TS compiler API)
|
|
9760
|
+
".ts": "ts",
|
|
9761
|
+
".mts": "ts",
|
|
9762
|
+
".cts": "ts",
|
|
9763
|
+
".tsx": "tsx",
|
|
9764
|
+
".js": "js",
|
|
9765
|
+
".mjs": "js",
|
|
9766
|
+
".cjs": "js",
|
|
9767
|
+
".jsx": "jsx",
|
|
9768
|
+
// First-class native/spawn parsers (+ regex fallback)
|
|
9769
|
+
".go": "go",
|
|
9770
|
+
".py": "py",
|
|
9771
|
+
".pyi": "py",
|
|
9772
|
+
".pyw": "py",
|
|
9773
|
+
".rs": "rs",
|
|
9774
|
+
".json": "json",
|
|
9775
|
+
".jsonc": "json",
|
|
9776
|
+
".yaml": "yaml",
|
|
9777
|
+
".yml": "yaml",
|
|
9778
|
+
// C family
|
|
9779
|
+
".c": "c",
|
|
9780
|
+
".h": "c",
|
|
9781
|
+
".cc": "cpp",
|
|
9782
|
+
".cpp": "cpp",
|
|
9783
|
+
".cxx": "cpp",
|
|
9784
|
+
".hh": "cpp",
|
|
9785
|
+
".hpp": "cpp",
|
|
9786
|
+
".hxx": "cpp",
|
|
9787
|
+
// JVM / .NET
|
|
9788
|
+
".java": "java",
|
|
9789
|
+
".cs": "csharp",
|
|
9790
|
+
".kt": "kotlin",
|
|
9791
|
+
".kts": "kotlin",
|
|
9792
|
+
".scala": "scala",
|
|
9793
|
+
".sc": "scala",
|
|
9794
|
+
// Scripting
|
|
9795
|
+
".php": "php",
|
|
9796
|
+
".rb": "ruby",
|
|
9797
|
+
".swift": "swift",
|
|
9798
|
+
".dart": "dart",
|
|
9799
|
+
".lua": "lua",
|
|
9800
|
+
".r": "r",
|
|
9801
|
+
".R": "r",
|
|
9802
|
+
".pl": "other",
|
|
9803
|
+
".pm": "other",
|
|
9804
|
+
// Systems / functional
|
|
9805
|
+
".zig": "zig",
|
|
9806
|
+
".ex": "elixir",
|
|
9807
|
+
".exs": "elixir",
|
|
9808
|
+
".hs": "haskell",
|
|
9809
|
+
".lhs": "haskell",
|
|
9810
|
+
// Shell / data / docs / web
|
|
9811
|
+
".sh": "shell",
|
|
9812
|
+
".bash": "shell",
|
|
9813
|
+
".zsh": "shell",
|
|
9814
|
+
".ps1": "shell",
|
|
9815
|
+
".sql": "sql",
|
|
9816
|
+
".md": "md",
|
|
9817
|
+
".mdx": "md",
|
|
9818
|
+
".toml": "toml",
|
|
9819
|
+
".html": "html",
|
|
9820
|
+
".htm": "html",
|
|
9821
|
+
".css": "css",
|
|
9822
|
+
".scss": "css",
|
|
9823
|
+
".less": "css",
|
|
9824
|
+
".vue": "vue",
|
|
9825
|
+
".svelte": "svelte",
|
|
9826
|
+
".proto": "proto",
|
|
9827
|
+
".graphql": "graphql",
|
|
9828
|
+
".gql": "graphql"
|
|
9829
|
+
};
|
|
9830
|
+
var INDEXABLE_EXTENSIONS = Object.freeze(
|
|
9831
|
+
[...new Set(Object.keys(EXT_TO_LANG).map((e) => e.toLowerCase()))].sort()
|
|
9832
|
+
);
|
|
9833
|
+
var SPECIAL_FILENAMES = {
|
|
9834
|
+
makefile: "other",
|
|
9835
|
+
gnumakefile: "other",
|
|
9836
|
+
dockerfile: "other",
|
|
9837
|
+
"docker-compose.yml": "yaml",
|
|
9838
|
+
"docker-compose.yaml": "yaml",
|
|
9839
|
+
"cmakelists.txt": "other",
|
|
9840
|
+
gemfile: "ruby",
|
|
9841
|
+
rakefile: "ruby",
|
|
9842
|
+
procfile: "other",
|
|
9843
|
+
justfile: "other"
|
|
9844
|
+
};
|
|
9845
|
+
function detectLang(file) {
|
|
9846
|
+
const base = path14.basename(file);
|
|
9847
|
+
const lowerBase = base.toLowerCase();
|
|
9848
|
+
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
9849
|
+
return "ts";
|
|
9850
|
+
}
|
|
9851
|
+
const special = SPECIAL_FILENAMES[lowerBase];
|
|
9852
|
+
if (special) return special;
|
|
9853
|
+
const ext = path14.extname(base).toLowerCase();
|
|
9854
|
+
if (!ext) return null;
|
|
9855
|
+
return EXT_TO_LANG[ext] ?? null;
|
|
9856
|
+
}
|
|
9857
|
+
function isIndexablePath(file) {
|
|
9858
|
+
return detectLang(file) !== null;
|
|
9859
|
+
}
|
|
9860
|
+
|
|
9861
|
+
// src/codebase-index/ts-parser.ts
|
|
9414
9862
|
var KIND_MAP = {
|
|
9415
9863
|
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
9416
9864
|
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
@@ -9437,32 +9885,6 @@ function kindOf(node) {
|
|
|
9437
9885
|
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
9438
9886
|
return KIND_MAP[node.kind] ?? null;
|
|
9439
9887
|
}
|
|
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
9888
|
function getSignature(printer, node, sourceFile) {
|
|
9467
9889
|
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
9468
9890
|
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
@@ -9585,22 +10007,30 @@ function deduplicateRefs(refs) {
|
|
|
9585
10007
|
return true;
|
|
9586
10008
|
});
|
|
9587
10009
|
}
|
|
9588
|
-
function detectLang(file) {
|
|
9589
|
-
const idx = file.lastIndexOf(".");
|
|
9590
|
-
if (idx < 0) return null;
|
|
9591
|
-
return extToLang(file.slice(idx));
|
|
9592
|
-
}
|
|
9593
10010
|
|
|
9594
10011
|
// src/codebase-index/go-parser.ts
|
|
9595
10012
|
init_win32_resolve();
|
|
9596
10013
|
import { spawn as spawn4 } from "node:child_process";
|
|
9597
10014
|
import * as os5 from "node:os";
|
|
9598
|
-
import * as
|
|
10015
|
+
import * as path15 from "node:path";
|
|
9599
10016
|
import * as fs10 from "node:fs/promises";
|
|
10017
|
+
|
|
10018
|
+
// src/codebase-index/spawn-gate.ts
|
|
10019
|
+
var chain = Promise.resolve();
|
|
10020
|
+
function withSpawnGate(fn) {
|
|
10021
|
+
const run = chain.then(fn, fn);
|
|
10022
|
+
chain = run.then(
|
|
10023
|
+
() => void 0,
|
|
10024
|
+
() => void 0
|
|
10025
|
+
);
|
|
10026
|
+
return run;
|
|
10027
|
+
}
|
|
10028
|
+
|
|
10029
|
+
// src/codebase-index/go-parser.ts
|
|
9600
10030
|
async function parseSymbols2(opts) {
|
|
9601
10031
|
const { file, content, lang } = opts;
|
|
9602
10032
|
try {
|
|
9603
|
-
const parsed = await syncGoParse(file, content, lang);
|
|
10033
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
9604
10034
|
if (parsed.symbols.length > 0) {
|
|
9605
10035
|
return parsed;
|
|
9606
10036
|
}
|
|
@@ -9896,11 +10326,16 @@ func formatType(t ast.Expr) string {
|
|
|
9896
10326
|
}
|
|
9897
10327
|
}
|
|
9898
10328
|
`;
|
|
10329
|
+
var _cachedGoScriptPath = null;
|
|
9899
10330
|
async function syncGoParse(filePath, content, lang) {
|
|
9900
|
-
const tmpDir = await fs10.mkdtemp(path14.join(os5.tmpdir(), "ws-go-parse-"));
|
|
9901
10331
|
try {
|
|
9902
|
-
|
|
9903
|
-
|
|
10332
|
+
let scriptPath = _cachedGoScriptPath;
|
|
10333
|
+
if (!scriptPath) {
|
|
10334
|
+
const tmpDir = await fs10.mkdtemp(path15.join(os5.tmpdir(), "ws-go-parse-"));
|
|
10335
|
+
scriptPath = path15.join(tmpDir, "parse.go");
|
|
10336
|
+
await fs10.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
10337
|
+
_cachedGoScriptPath = scriptPath;
|
|
10338
|
+
}
|
|
9904
10339
|
const goBinary = resolveWin32Command("go");
|
|
9905
10340
|
const goResult = await new Promise(
|
|
9906
10341
|
(resolve15, reject) => {
|
|
@@ -9940,41 +10375,330 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
9940
10375
|
if (code !== 0 || !stdout.trim()) {
|
|
9941
10376
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9942
10377
|
}
|
|
9943
|
-
const raw = JSON.parse(stdout.trim());
|
|
9944
|
-
const symbols = raw.map((s) => ({
|
|
9945
|
-
id: 0,
|
|
9946
|
-
lang,
|
|
9947
|
-
kind: s.kind,
|
|
9948
|
-
name: s.name,
|
|
9949
|
-
file: filePath,
|
|
9950
|
-
line: s.line,
|
|
9951
|
-
col: s.col,
|
|
9952
|
-
signature: s.signature ?? "",
|
|
9953
|
-
docComment: "",
|
|
9954
|
-
scope: s.scope ?? "",
|
|
9955
|
-
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
9956
|
-
}));
|
|
9957
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
9958
|
-
} catch {
|
|
9959
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
9960
|
-
}
|
|
9961
|
-
|
|
10378
|
+
const raw = JSON.parse(stdout.trim());
|
|
10379
|
+
const symbols = raw.map((s) => ({
|
|
10380
|
+
id: 0,
|
|
10381
|
+
lang,
|
|
10382
|
+
kind: s.kind,
|
|
10383
|
+
name: s.name,
|
|
10384
|
+
file: filePath,
|
|
10385
|
+
line: s.line,
|
|
10386
|
+
col: s.col,
|
|
10387
|
+
signature: s.signature ?? "",
|
|
10388
|
+
docComment: "",
|
|
10389
|
+
scope: s.scope ?? "",
|
|
10390
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
10391
|
+
}));
|
|
10392
|
+
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
10393
|
+
} catch {
|
|
10394
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
10395
|
+
}
|
|
10396
|
+
}
|
|
10397
|
+
|
|
10398
|
+
// src/codebase-index/py-parser.ts
|
|
10399
|
+
init_win32_resolve();
|
|
10400
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
10401
|
+
import * as fs11 from "node:fs/promises";
|
|
10402
|
+
import * as os6 from "node:os";
|
|
10403
|
+
import * as path16 from "node:path";
|
|
10404
|
+
|
|
10405
|
+
// src/codebase-index/generic-parser.ts
|
|
10406
|
+
var C_LIKE = [
|
|
10407
|
+
{ re: /\b(?:class|struct|enum|interface|union)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10408
|
+
{
|
|
10409
|
+
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,
|
|
10410
|
+
kind: "function"
|
|
10411
|
+
},
|
|
10412
|
+
{ re: /\b(?:namespace)\s+([A-Za-z_]\w*)/g, kind: "namespace" }
|
|
10413
|
+
];
|
|
10414
|
+
var LANG_PATTERNS = {
|
|
10415
|
+
py: [
|
|
10416
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10417
|
+
{ re: /^class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10418
|
+
{ re: /^([A-Za-z_]\w*)\s*=/gm, kind: "var" }
|
|
10419
|
+
],
|
|
10420
|
+
go: [
|
|
10421
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/gm, kind: "function" },
|
|
10422
|
+
{ re: /^type\s+([A-Za-z_]\w*)\b/gm, kind: "type" },
|
|
10423
|
+
{ re: /^(?:const|var)\s+([A-Za-z_]\w*)\b/gm, kind: "const" },
|
|
10424
|
+
{ re: /^package\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10425
|
+
],
|
|
10426
|
+
rs: [
|
|
10427
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10428
|
+
{ re: /\bstruct\s+([A-Za-z_]\w*)/g, kind: "struct" },
|
|
10429
|
+
{ re: /\benum\s+([A-Za-z_]\w*)/g, kind: "enum" },
|
|
10430
|
+
{ re: /\btrait\s+([A-Za-z_]\w*)/g, kind: "trait" },
|
|
10431
|
+
{ re: /\bimpl(?:\s*<[^>]+>)?\s+([A-Za-z_]\w*)/g, kind: "impl" },
|
|
10432
|
+
{ re: /\b(?:const|static)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10433
|
+
{ re: /\bmod\s+([A-Za-z_]\w*)/g, kind: "mod" }
|
|
10434
|
+
],
|
|
10435
|
+
c: C_LIKE,
|
|
10436
|
+
cpp: C_LIKE,
|
|
10437
|
+
java: [
|
|
10438
|
+
{ re: /\b(?:class|interface|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10439
|
+
{
|
|
10440
|
+
re: /\b(?:public|private|protected|static|final|abstract|synchronized|native|default|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10441
|
+
kind: "method"
|
|
10442
|
+
}
|
|
10443
|
+
],
|
|
10444
|
+
csharp: [
|
|
10445
|
+
{ re: /\b(?:class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10446
|
+
{ re: /\bnamespace\s+([A-Za-z_.\w]+)/g, kind: "namespace" },
|
|
10447
|
+
{
|
|
10448
|
+
re: /\b(?:public|private|protected|internal|static|async|override|virtual|\s)+\s*[\w.<>,\[\]\s]+\s+([A-Za-z_]\w*)\s*\(/g,
|
|
10449
|
+
kind: "method"
|
|
10450
|
+
}
|
|
10451
|
+
],
|
|
10452
|
+
php: [
|
|
10453
|
+
{ re: /\bfunction\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10454
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10455
|
+
{ re: /\binterface\s+([A-Za-z_]\w*)/g, kind: "interface" },
|
|
10456
|
+
{ re: /\bnamespace\s+([A-Za-z_\\]+)/g, kind: "namespace" }
|
|
10457
|
+
],
|
|
10458
|
+
ruby: [
|
|
10459
|
+
{ re: /^\s*def\s+(?:self\.)?([A-Za-z_]\w*[!?]?)/gm, kind: "function" },
|
|
10460
|
+
{ re: /^\s*class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10461
|
+
{ re: /^\s*module\s+([A-Za-z_]\w*)/gm, kind: "namespace" }
|
|
10462
|
+
],
|
|
10463
|
+
swift: [
|
|
10464
|
+
{ re: /\b(?:func)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10465
|
+
{ re: /\b(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10466
|
+
],
|
|
10467
|
+
kotlin: [
|
|
10468
|
+
{ re: /\b(?:fun)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10469
|
+
{ re: /\b(?:class|interface|object|enum\s+class|data\s+class)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10470
|
+
],
|
|
10471
|
+
scala: [
|
|
10472
|
+
{ re: /\b(?:def)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10473
|
+
{ re: /\b(?:class|object|trait|enum)\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10474
|
+
],
|
|
10475
|
+
shell: [
|
|
10476
|
+
{ re: /^(?:function\s+)?([A-Za-z_]\w*)\s*\(\)\s*\{/gm, kind: "function" },
|
|
10477
|
+
{ re: /^([A-Za-z_][\w]*)\s*\(\)\s*\{/gm, kind: "function" }
|
|
10478
|
+
],
|
|
10479
|
+
sql: [
|
|
10480
|
+
{ 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" }
|
|
10481
|
+
],
|
|
10482
|
+
md: [
|
|
10483
|
+
{ re: /^(#{1,6})\s+(.+)$/gm, kind: "namespace" }
|
|
10484
|
+
],
|
|
10485
|
+
toml: [
|
|
10486
|
+
{ re: /^\[([^\]]+)\]/gm, kind: "namespace" }
|
|
10487
|
+
],
|
|
10488
|
+
html: [
|
|
10489
|
+
{ re: /\bid\s*=\s*["']([^"']+)["']/gi, kind: "property" },
|
|
10490
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10491
|
+
],
|
|
10492
|
+
css: [
|
|
10493
|
+
{ re: /^\s*([.#]?[A-Za-z_][\w-]*)\s*\{/gm, kind: "type" },
|
|
10494
|
+
{ re: /@(?:keyframes|media|supports)\s+([^{\s]+)/g, kind: "namespace" }
|
|
10495
|
+
],
|
|
10496
|
+
vue: [
|
|
10497
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10498
|
+
{ re: /<(?:script|template|style)\b/gi, kind: "namespace" }
|
|
10499
|
+
],
|
|
10500
|
+
svelte: [
|
|
10501
|
+
{ re: /\b(?:function|const|let|var|class|export\s+(?:default\s+)?(?:function|class|const))\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10502
|
+
],
|
|
10503
|
+
dart: [
|
|
10504
|
+
{ re: /\b(?:class|enum|mixin|extension)\s+([A-Za-z_]\w*)/g, kind: "class" },
|
|
10505
|
+
{ re: /\b([A-Za-z_]\w*)\s*\([^;]*\)\s*(?:async\s*)?\{/g, kind: "function" }
|
|
10506
|
+
],
|
|
10507
|
+
lua: [
|
|
10508
|
+
{ re: /\bfunction\s+([A-Za-z_.:]\w*)/g, kind: "function" },
|
|
10509
|
+
{ re: /\blocal\s+function\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10510
|
+
],
|
|
10511
|
+
r: [
|
|
10512
|
+
{ re: /([A-Za-z.]\w*)\s*<-\s*function\s*\(/g, kind: "function" },
|
|
10513
|
+
{ re: /([A-Za-z.]\w*)\s*=\s*function\s*\(/g, kind: "function" }
|
|
10514
|
+
],
|
|
10515
|
+
proto: [
|
|
10516
|
+
{ re: /\b(?:message|service|enum)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10517
|
+
{ re: /\brpc\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10518
|
+
],
|
|
10519
|
+
graphql: [
|
|
10520
|
+
{ re: /\b(?:type|interface|enum|input|union|scalar)\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10521
|
+
{ re: /\b(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/g, kind: "function" }
|
|
10522
|
+
],
|
|
10523
|
+
zig: [
|
|
10524
|
+
{ re: /\b(?:fn|pub\s+fn)\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10525
|
+
{ re: /\b(?:const|var)\s+([A-Za-z_]\w*)/g, kind: "const" },
|
|
10526
|
+
{ re: /\b(?:struct|enum|union)\s*\{/g, kind: "type" }
|
|
10527
|
+
],
|
|
10528
|
+
elixir: [
|
|
10529
|
+
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
10530
|
+
{ re: /\bdefmodule\s+([A-Za-z_.]\w*)/g, kind: "namespace" }
|
|
10531
|
+
],
|
|
10532
|
+
haskell: [
|
|
10533
|
+
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
10534
|
+
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
10535
|
+
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
10536
|
+
{ re: /\bclass\s+([A-Za-z_]\w*)/g, kind: "class" }
|
|
10537
|
+
],
|
|
10538
|
+
other: [
|
|
10539
|
+
{ re: /^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10540
|
+
{ re: /^(?:export\s+)?class\s+([A-Za-z_]\w*)/gm, kind: "class" },
|
|
10541
|
+
{ re: /^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_]\w*)/gm, kind: "const" },
|
|
10542
|
+
{ re: /^(?:async\s+)?def\s+([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10543
|
+
{ re: /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/gm, kind: "function" },
|
|
10544
|
+
{ re: /\bfn\s+([A-Za-z_]\w*)/g, kind: "function" },
|
|
10545
|
+
{ re: /^(?:target|PHONY)\s*:/gm, kind: "namespace" }
|
|
10546
|
+
]
|
|
10547
|
+
};
|
|
10548
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
10549
|
+
"if",
|
|
10550
|
+
"else",
|
|
10551
|
+
"for",
|
|
10552
|
+
"while",
|
|
10553
|
+
"switch",
|
|
10554
|
+
"case",
|
|
10555
|
+
"return",
|
|
10556
|
+
"break",
|
|
10557
|
+
"continue",
|
|
10558
|
+
"new",
|
|
10559
|
+
"delete",
|
|
10560
|
+
"typeof",
|
|
10561
|
+
"instanceof",
|
|
10562
|
+
"void",
|
|
10563
|
+
"null",
|
|
10564
|
+
"true",
|
|
10565
|
+
"false",
|
|
10566
|
+
"this",
|
|
10567
|
+
"super",
|
|
10568
|
+
"import",
|
|
10569
|
+
"export",
|
|
10570
|
+
"from",
|
|
10571
|
+
"as",
|
|
10572
|
+
"default",
|
|
10573
|
+
"public",
|
|
10574
|
+
"private",
|
|
10575
|
+
"protected",
|
|
10576
|
+
"static",
|
|
10577
|
+
"final",
|
|
10578
|
+
"class",
|
|
10579
|
+
"struct",
|
|
10580
|
+
"enum",
|
|
10581
|
+
"interface",
|
|
10582
|
+
"function",
|
|
10583
|
+
"def",
|
|
10584
|
+
"fn",
|
|
10585
|
+
"func",
|
|
10586
|
+
"var",
|
|
10587
|
+
"let",
|
|
10588
|
+
"const",
|
|
10589
|
+
"package",
|
|
10590
|
+
"namespace",
|
|
10591
|
+
"module",
|
|
10592
|
+
"using",
|
|
10593
|
+
"include",
|
|
10594
|
+
"require",
|
|
10595
|
+
"select",
|
|
10596
|
+
"from",
|
|
10597
|
+
"where",
|
|
10598
|
+
"and",
|
|
10599
|
+
"or",
|
|
10600
|
+
"not",
|
|
10601
|
+
"in",
|
|
10602
|
+
"is",
|
|
10603
|
+
"try",
|
|
10604
|
+
"catch",
|
|
10605
|
+
"finally",
|
|
10606
|
+
"throw",
|
|
10607
|
+
"async",
|
|
10608
|
+
"await",
|
|
10609
|
+
"yield"
|
|
10610
|
+
]);
|
|
10611
|
+
function patternsFor(lang) {
|
|
10612
|
+
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
10613
|
+
}
|
|
10614
|
+
function looksBinary(content) {
|
|
10615
|
+
if (content.includes("\0")) return true;
|
|
10616
|
+
const sample = content.slice(0, 2048);
|
|
10617
|
+
if (sample.length === 0) return false;
|
|
10618
|
+
let bad = 0;
|
|
10619
|
+
for (let i = 0; i < sample.length; i++) {
|
|
10620
|
+
const c = sample.charCodeAt(i);
|
|
10621
|
+
if (c === 9 || c === 10 || c === 13) continue;
|
|
10622
|
+
if (c < 32 || c === 127) bad++;
|
|
10623
|
+
}
|
|
10624
|
+
return bad / sample.length > 0.1;
|
|
10625
|
+
}
|
|
10626
|
+
function lineColAt(content, index) {
|
|
10627
|
+
let line = 1;
|
|
10628
|
+
let lastNl = -1;
|
|
10629
|
+
for (let i = 0; i < index && i < content.length; i++) {
|
|
10630
|
+
if (content.charCodeAt(i) === 10) {
|
|
10631
|
+
line++;
|
|
10632
|
+
lastNl = i;
|
|
10633
|
+
}
|
|
10634
|
+
}
|
|
10635
|
+
return { line, col: index - lastNl };
|
|
10636
|
+
}
|
|
10637
|
+
var GENERIC_MAX_SYMBOLS_DEFAULT = 500;
|
|
10638
|
+
var GENERIC_MAX_FILE_CHARS = 512 * 1024;
|
|
10639
|
+
function parseGeneric2(opts) {
|
|
10640
|
+
const { file, lang } = opts;
|
|
10641
|
+
const maxSymbols = opts.maxSymbols ?? GENERIC_MAX_SYMBOLS_DEFAULT;
|
|
10642
|
+
const mtimeMs = Date.now();
|
|
10643
|
+
if (!opts.content || looksBinary(opts.content)) {
|
|
10644
|
+
return { file, lang, symbols: [], mtimeMs };
|
|
10645
|
+
}
|
|
10646
|
+
const content = opts.content.length > GENERIC_MAX_FILE_CHARS ? opts.content.slice(0, GENERIC_MAX_FILE_CHARS) : opts.content;
|
|
10647
|
+
const patterns = patternsFor(lang);
|
|
10648
|
+
const symbols = [];
|
|
10649
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10650
|
+
for (const pattern of patterns) {
|
|
10651
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags.includes("g") ? pattern.re.flags : `${pattern.re.flags}g`);
|
|
10652
|
+
re.lastIndex = 0;
|
|
10653
|
+
let match;
|
|
10654
|
+
while ((match = re.exec(content)) !== null) {
|
|
10655
|
+
if (symbols.length >= maxSymbols) break;
|
|
10656
|
+
let name = (match[1] ?? match[2] ?? "").trim();
|
|
10657
|
+
if (lang === "md" && match[2]) name = match[2].trim();
|
|
10658
|
+
if (!name || name.length > 200) continue;
|
|
10659
|
+
name = name.replace(/^#+\s*/, "").replace(/["'`]/g, "");
|
|
10660
|
+
if (!name || KEYWORDS.has(name.toLowerCase())) continue;
|
|
10661
|
+
if (!/^[A-Za-z_#.@/\w][\w.\-:/#!?]*$/.test(name) && lang !== "md" && lang !== "toml") {
|
|
10662
|
+
continue;
|
|
10663
|
+
}
|
|
10664
|
+
const { line, col } = lineColAt(content, match.index);
|
|
10665
|
+
const key = `${name}\0${line}\0${pattern.kind}`;
|
|
10666
|
+
if (seen.has(key)) continue;
|
|
10667
|
+
seen.add(key);
|
|
10668
|
+
const nl = content.indexOf("\n", match.index);
|
|
10669
|
+
const lineText = content.slice(match.index, nl === -1 ? content.length : nl);
|
|
10670
|
+
const signature = (lineText || name).trim().slice(0, 500);
|
|
10671
|
+
symbols.push({
|
|
10672
|
+
id: 0,
|
|
10673
|
+
lang,
|
|
10674
|
+
kind: lang === "md" ? "namespace" : pattern.kind,
|
|
10675
|
+
name: name.slice(0, 200),
|
|
10676
|
+
file,
|
|
10677
|
+
line,
|
|
10678
|
+
col,
|
|
10679
|
+
signature,
|
|
10680
|
+
docComment: "",
|
|
10681
|
+
scope: "",
|
|
10682
|
+
text: `${name} ${signature}`.trim().slice(0, 1e3)
|
|
10683
|
+
});
|
|
10684
|
+
}
|
|
10685
|
+
if (symbols.length >= maxSymbols) break;
|
|
9962
10686
|
}
|
|
10687
|
+
return { file, lang, symbols, mtimeMs };
|
|
10688
|
+
}
|
|
10689
|
+
async function parseSymbols3(opts) {
|
|
10690
|
+
return parseGeneric2(opts);
|
|
9963
10691
|
}
|
|
9964
10692
|
|
|
9965
10693
|
// src/codebase-index/py-parser.ts
|
|
9966
|
-
|
|
9967
|
-
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
9968
|
-
import * as fs11 from "node:fs/promises";
|
|
9969
|
-
import * as os6 from "node:os";
|
|
9970
|
-
import * as path15 from "node:path";
|
|
9971
|
-
async function parseSymbols3(opts) {
|
|
10694
|
+
async function parseSymbols4(opts) {
|
|
9972
10695
|
const { file, content, lang } = opts;
|
|
9973
10696
|
try {
|
|
9974
|
-
|
|
10697
|
+
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
10698
|
+
if (native !== null) return native;
|
|
9975
10699
|
} catch {
|
|
9976
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
9977
10700
|
}
|
|
10701
|
+
return parseGeneric2({ file, content, lang: lang === "py" ? "py" : lang });
|
|
9978
10702
|
}
|
|
9979
10703
|
var PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
9980
10704
|
|
|
@@ -10179,20 +10903,37 @@ visitor.visit(tree)
|
|
|
10179
10903
|
|
|
10180
10904
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
10181
10905
|
`;
|
|
10182
|
-
function resolvePython() {
|
|
10906
|
+
async function resolvePython() {
|
|
10183
10907
|
const candidates = process.platform === "win32" ? ["python3", "python", "py"] : ["python3", "python"];
|
|
10184
10908
|
for (const name of candidates) {
|
|
10185
10909
|
const resolved = resolveWin32Command(name);
|
|
10186
|
-
|
|
10187
|
-
stdio: "pipe",
|
|
10188
|
-
timeout: 5e3
|
|
10189
|
-
});
|
|
10190
|
-
if (result.error) continue;
|
|
10191
|
-
if (result.status !== 0) continue;
|
|
10910
|
+
if (!await commandIsAvailable(resolved)) continue;
|
|
10192
10911
|
return resolved;
|
|
10193
10912
|
}
|
|
10194
10913
|
return null;
|
|
10195
10914
|
}
|
|
10915
|
+
function commandIsAvailable(command) {
|
|
10916
|
+
return new Promise((resolve15) => {
|
|
10917
|
+
let settled = false;
|
|
10918
|
+
const proc = spawn5(command, ["--version"], {
|
|
10919
|
+
stdio: "ignore",
|
|
10920
|
+
windowsHide: true
|
|
10921
|
+
});
|
|
10922
|
+
const finish = (available) => {
|
|
10923
|
+
if (settled) return;
|
|
10924
|
+
settled = true;
|
|
10925
|
+
clearTimeout(timer);
|
|
10926
|
+
resolve15(available);
|
|
10927
|
+
};
|
|
10928
|
+
const timer = setTimeout(() => {
|
|
10929
|
+
proc.kill("SIGKILL");
|
|
10930
|
+
finish(false);
|
|
10931
|
+
}, 5e3);
|
|
10932
|
+
timer.unref?.();
|
|
10933
|
+
proc.once("error", () => finish(false));
|
|
10934
|
+
proc.once("close", (code) => finish(code === 0));
|
|
10935
|
+
});
|
|
10936
|
+
}
|
|
10196
10937
|
function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
10197
10938
|
return new Promise((resolve15, reject) => {
|
|
10198
10939
|
let settled = false;
|
|
@@ -10228,21 +10969,20 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
10228
10969
|
});
|
|
10229
10970
|
}
|
|
10230
10971
|
var _cachedScriptPath = null;
|
|
10231
|
-
var
|
|
10972
|
+
var cachedPyBinary;
|
|
10232
10973
|
async function syncPyParse(filePath, content, lang) {
|
|
10233
10974
|
try {
|
|
10234
10975
|
if (!_cachedScriptPath) {
|
|
10235
|
-
const tmpDir =
|
|
10976
|
+
const tmpDir = path16.join(os6.tmpdir(), "ws-py-parse");
|
|
10236
10977
|
await fs11.mkdir(tmpDir, { recursive: true });
|
|
10237
|
-
_cachedScriptPath =
|
|
10978
|
+
_cachedScriptPath = path16.join(tmpDir, "parse.py");
|
|
10238
10979
|
await fs11.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
10239
10980
|
}
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
}
|
|
10981
|
+
cachedPyBinary ??= resolvePython();
|
|
10982
|
+
const pyBinary = await cachedPyBinary;
|
|
10983
|
+
if (!pyBinary) return null;
|
|
10244
10984
|
const { code, stdout } = await spawnPyParser(
|
|
10245
|
-
|
|
10985
|
+
pyBinary,
|
|
10246
10986
|
_cachedScriptPath,
|
|
10247
10987
|
filePath,
|
|
10248
10988
|
content
|
|
@@ -10266,31 +11006,40 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
10266
11006
|
}));
|
|
10267
11007
|
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
10268
11008
|
} catch {
|
|
10269
|
-
return
|
|
11009
|
+
return null;
|
|
10270
11010
|
}
|
|
10271
11011
|
}
|
|
10272
11012
|
|
|
10273
11013
|
// src/codebase-index/rs-parser.ts
|
|
10274
11014
|
init_win32_resolve();
|
|
10275
|
-
import { expectDefined as expectDefined3 } from "@wrongstack/core";
|
|
10276
|
-
import {
|
|
11015
|
+
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
11016
|
+
import { execFile, spawn as spawn6 } from "node:child_process";
|
|
10277
11017
|
import * as fs12 from "node:fs/promises";
|
|
10278
|
-
import * as
|
|
10279
|
-
async function
|
|
11018
|
+
import * as path17 from "node:path";
|
|
11019
|
+
async function parseSymbols5(opts) {
|
|
10280
11020
|
const { file, content, lang } = opts;
|
|
10281
|
-
const nativeAvailable = checkNativeParser();
|
|
11021
|
+
const nativeAvailable = await checkNativeParser();
|
|
10282
11022
|
if (nativeAvailable) {
|
|
10283
|
-
const result = await tryNativeParse(file, content);
|
|
11023
|
+
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
10284
11024
|
if (result) return result;
|
|
10285
11025
|
}
|
|
10286
11026
|
return regexParse({ file, content, lang });
|
|
10287
11027
|
}
|
|
11028
|
+
var nativeParserAvailability;
|
|
11029
|
+
function probe(command, args) {
|
|
11030
|
+
return new Promise((resolve15, reject) => {
|
|
11031
|
+
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
11032
|
+
if (error) reject(error);
|
|
11033
|
+
else resolve15();
|
|
11034
|
+
});
|
|
11035
|
+
});
|
|
11036
|
+
}
|
|
10288
11037
|
function checkNativeParser() {
|
|
10289
|
-
|
|
10290
|
-
execFileSync("rustc", ["--version"], { stdio: "pipe", windowsHide: true });
|
|
10291
|
-
const toolsDir = path16.join(process.cwd(), "tools");
|
|
11038
|
+
nativeParserAvailability ??= (async () => {
|
|
10292
11039
|
try {
|
|
10293
|
-
|
|
11040
|
+
await probe("rustc", ["--version"]);
|
|
11041
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
11042
|
+
await probe(
|
|
10294
11043
|
"cargo",
|
|
10295
11044
|
[
|
|
10296
11045
|
"metadata",
|
|
@@ -10298,23 +11047,21 @@ function checkNativeParser() {
|
|
|
10298
11047
|
"--format-version",
|
|
10299
11048
|
"1",
|
|
10300
11049
|
"--manifest-path",
|
|
10301
|
-
|
|
10302
|
-
]
|
|
10303
|
-
{ stdio: "pipe", windowsHide: true }
|
|
11050
|
+
path17.join(toolsDir, "Cargo.toml")
|
|
11051
|
+
]
|
|
10304
11052
|
);
|
|
10305
11053
|
return true;
|
|
10306
11054
|
} catch {
|
|
10307
11055
|
return false;
|
|
10308
11056
|
}
|
|
10309
|
-
}
|
|
10310
|
-
|
|
10311
|
-
}
|
|
11057
|
+
})();
|
|
11058
|
+
return nativeParserAvailability;
|
|
10312
11059
|
}
|
|
10313
11060
|
async function tryNativeParse(file, content) {
|
|
10314
11061
|
try {
|
|
10315
|
-
const toolsDir =
|
|
10316
|
-
const crateDir =
|
|
10317
|
-
const tmpFile =
|
|
11062
|
+
const toolsDir = path17.join(process.cwd(), "tools");
|
|
11063
|
+
const crateDir = path17.join(toolsDir, "syn-parser");
|
|
11064
|
+
const tmpFile = path17.join(crateDir, "src", "input.rs");
|
|
10318
11065
|
await fs12.writeFile(tmpFile, content, "utf8");
|
|
10319
11066
|
const cargoBinary = resolveWin32Command("cargo");
|
|
10320
11067
|
const result = await new Promise(
|
|
@@ -10322,7 +11069,7 @@ async function tryNativeParse(file, content) {
|
|
|
10322
11069
|
let settled = false;
|
|
10323
11070
|
const proc = spawn6(
|
|
10324
11071
|
cargoBinary,
|
|
10325
|
-
["run", "--manifest-path",
|
|
11072
|
+
["run", "--manifest-path", path17.join(toolsDir, "Cargo.toml")],
|
|
10326
11073
|
{
|
|
10327
11074
|
cwd: process.cwd(),
|
|
10328
11075
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -10436,9 +11183,9 @@ function regexParse(opts) {
|
|
|
10436
11183
|
}
|
|
10437
11184
|
|
|
10438
11185
|
// src/codebase-index/json-parser.ts
|
|
10439
|
-
import { expectDefined as expectDefined4 } from "@wrongstack/core";
|
|
10440
|
-
import * as
|
|
10441
|
-
function
|
|
11186
|
+
import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
|
|
11187
|
+
import * as path18 from "node:path";
|
|
11188
|
+
function parseSymbols6(opts) {
|
|
10442
11189
|
const { file, content, lang } = opts;
|
|
10443
11190
|
try {
|
|
10444
11191
|
return regexParse2({ file, content, lang });
|
|
@@ -10449,9 +11196,9 @@ function parseSymbols5(opts) {
|
|
|
10449
11196
|
function regexParse2(opts) {
|
|
10450
11197
|
const { file, content, lang } = opts;
|
|
10451
11198
|
const symbols = [];
|
|
10452
|
-
const
|
|
10453
|
-
const isPackageJson =
|
|
10454
|
-
const isTsconfig =
|
|
11199
|
+
const basename12 = path18.basename(file).toLowerCase();
|
|
11200
|
+
const isPackageJson = basename12 === "package.json";
|
|
11201
|
+
const isTsconfig = basename12 === "tsconfig.json" || basename12 === "tsconfig.build.json";
|
|
10455
11202
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
10456
11203
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
10457
11204
|
const lines = content.split("\n");
|
|
@@ -10475,11 +11222,11 @@ function regexParse2(opts) {
|
|
|
10475
11222
|
const line = lineFromOffset(offset);
|
|
10476
11223
|
symbols.push(
|
|
10477
11224
|
makeSymbol({
|
|
10478
|
-
name:
|
|
11225
|
+
name: path18.basename(file),
|
|
10479
11226
|
kind: "object",
|
|
10480
11227
|
line,
|
|
10481
11228
|
col: 0,
|
|
10482
|
-
signature: `"${
|
|
11229
|
+
signature: `"${path18.basename(file)}" = { ... }`,
|
|
10483
11230
|
file,
|
|
10484
11231
|
lang
|
|
10485
11232
|
})
|
|
@@ -10641,8 +11388,8 @@ function makeSymbol(opts) {
|
|
|
10641
11388
|
}
|
|
10642
11389
|
|
|
10643
11390
|
// src/codebase-index/yaml-parser.ts
|
|
10644
|
-
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core";
|
|
10645
|
-
function
|
|
11391
|
+
import { expectDefined as expectDefined5, truncate } from "@wrongstack/core/utils";
|
|
11392
|
+
function parseSymbols7(opts) {
|
|
10646
11393
|
const { file, content, lang } = opts;
|
|
10647
11394
|
try {
|
|
10648
11395
|
return regexParse3({ file, content, lang });
|
|
@@ -10791,8 +11538,8 @@ function makeSymbol2(opts) {
|
|
|
10791
11538
|
|
|
10792
11539
|
// src/codebase-index/gitignore.ts
|
|
10793
11540
|
import * as fs13 from "node:fs/promises";
|
|
10794
|
-
import * as
|
|
10795
|
-
import { compileGlob } from "@wrongstack/core";
|
|
11541
|
+
import * as path19 from "node:path";
|
|
11542
|
+
import { compileGlob } from "@wrongstack/core/utils";
|
|
10796
11543
|
function globBody(glob) {
|
|
10797
11544
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
10798
11545
|
}
|
|
@@ -10837,7 +11584,7 @@ function compileGitignore(lines) {
|
|
|
10837
11584
|
async function loadGitignoreMatcher(projectRoot) {
|
|
10838
11585
|
let lines = [];
|
|
10839
11586
|
try {
|
|
10840
|
-
const raw = await fs13.readFile(
|
|
11587
|
+
const raw = await fs13.readFile(path19.join(projectRoot, ".gitignore"), "utf8");
|
|
10841
11588
|
lines = raw.split("\n");
|
|
10842
11589
|
} catch {
|
|
10843
11590
|
}
|
|
@@ -10846,7 +11593,9 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
10846
11593
|
|
|
10847
11594
|
// src/codebase-index/indexer.ts
|
|
10848
11595
|
var YIELD_EVERY_N = 50;
|
|
10849
|
-
|
|
11596
|
+
function resolveParallelBatch() {
|
|
11597
|
+
return indexParallelBatchSize(availableParallelism());
|
|
11598
|
+
}
|
|
10850
11599
|
function yieldEventLoop() {
|
|
10851
11600
|
return new Promise((resolve15) => setImmediate(resolve15));
|
|
10852
11601
|
}
|
|
@@ -10872,8 +11621,8 @@ var DEFAULT_IGNORE = [
|
|
|
10872
11621
|
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
10873
11622
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10874
11623
|
function isWithinProject(projectRoot, file) {
|
|
10875
|
-
const rel =
|
|
10876
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
11624
|
+
const rel = path20.relative(projectRoot, file);
|
|
11625
|
+
return rel !== "" && !rel.startsWith(`..${path20.sep}`) && rel !== ".." && !path20.isAbsolute(rel);
|
|
10877
11626
|
}
|
|
10878
11627
|
function isMissingPathError(err) {
|
|
10879
11628
|
const code = err?.code;
|
|
@@ -10884,18 +11633,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10884
11633
|
const errors = [];
|
|
10885
11634
|
let complete = true;
|
|
10886
11635
|
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
|
-
];
|
|
11636
|
+
const indexableExts = new Set(INDEXABLE_EXTENSIONS);
|
|
10899
11637
|
let dirCount = 0;
|
|
10900
11638
|
const walk2 = async (dir) => {
|
|
10901
11639
|
throwIfAborted(signal);
|
|
@@ -10914,19 +11652,16 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10914
11652
|
dirCount++;
|
|
10915
11653
|
for (const e of entries) {
|
|
10916
11654
|
if (ignoreSet.has(e.name)) continue;
|
|
10917
|
-
const full =
|
|
10918
|
-
const rel =
|
|
11655
|
+
const full = path20.join(dir, e.name);
|
|
11656
|
+
const rel = path20.relative(projectRoot, full).replace(/\\/g, "/");
|
|
10919
11657
|
if (e.isDirectory()) {
|
|
10920
11658
|
if (isGitIgnored(rel, true)) continue;
|
|
10921
11659
|
await walk2(full);
|
|
10922
11660
|
} else if (e.isFile()) {
|
|
10923
11661
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
10924
|
-
const ext =
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
results.push(full);
|
|
10928
|
-
break;
|
|
10929
|
-
}
|
|
11662
|
+
const ext = path20.extname(e.name).toLowerCase();
|
|
11663
|
+
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
11664
|
+
results.push(full);
|
|
10930
11665
|
}
|
|
10931
11666
|
}
|
|
10932
11667
|
}
|
|
@@ -10935,24 +11670,25 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
10935
11670
|
return { files: results, complete, errors };
|
|
10936
11671
|
}
|
|
10937
11672
|
async function parseFile(file, content, lang) {
|
|
10938
|
-
|
|
11673
|
+
const symbolLang = lang;
|
|
11674
|
+
switch (symbolLang) {
|
|
10939
11675
|
case "ts":
|
|
10940
11676
|
case "tsx":
|
|
10941
11677
|
case "js":
|
|
10942
11678
|
case "jsx":
|
|
10943
|
-
return parseSymbols({ file, content, lang });
|
|
11679
|
+
return parseSymbols({ file, content, lang: symbolLang });
|
|
10944
11680
|
case "go":
|
|
10945
11681
|
return parseSymbols2({ file, content, lang: "go" });
|
|
10946
11682
|
case "py":
|
|
10947
|
-
return
|
|
11683
|
+
return parseSymbols4({ file, content, lang: "py" });
|
|
10948
11684
|
case "rs":
|
|
10949
|
-
return
|
|
11685
|
+
return parseSymbols5({ file, content, lang: "rs" });
|
|
10950
11686
|
case "json":
|
|
10951
|
-
return
|
|
11687
|
+
return parseSymbols6({ file, content, lang: "json" });
|
|
10952
11688
|
case "yaml":
|
|
10953
|
-
return
|
|
11689
|
+
return parseSymbols7({ file, content, lang: "yaml" });
|
|
10954
11690
|
default:
|
|
10955
|
-
return { file,
|
|
11691
|
+
return parseSymbols3({ file, content, lang: symbolLang });
|
|
10956
11692
|
}
|
|
10957
11693
|
}
|
|
10958
11694
|
function assignRefsToSymbols2(refs, symbols) {
|
|
@@ -11000,10 +11736,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11000
11736
|
let discoveredFiles = null;
|
|
11001
11737
|
let discoveryComplete = true;
|
|
11002
11738
|
if (opts.files && opts.files.length > 0) {
|
|
11003
|
-
files = opts.files.map((f) =>
|
|
11739
|
+
files = opts.files.map((f) => path20.resolve(projectRoot, f)).filter((f) => {
|
|
11004
11740
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
11005
|
-
const rel =
|
|
11006
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
11741
|
+
const rel = path20.relative(projectRoot, f).replace(/\\/g, "/");
|
|
11742
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path20.basename(f)) && !isGitIgnored(rel, false);
|
|
11007
11743
|
});
|
|
11008
11744
|
} else {
|
|
11009
11745
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -11024,15 +11760,19 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11024
11760
|
if (!force) {
|
|
11025
11761
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
11026
11762
|
}
|
|
11763
|
+
const parallelBatch = resolveParallelBatch();
|
|
11027
11764
|
let filesSinceLastYield = 0;
|
|
11028
|
-
for (let batchStart = 0; batchStart < files.length; batchStart +=
|
|
11029
|
-
const batchEnd = Math.min(batchStart +
|
|
11765
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
|
|
11766
|
+
const batchEnd = Math.min(batchStart + parallelBatch, files.length);
|
|
11030
11767
|
const batchFiles = files.slice(batchStart, batchEnd);
|
|
11031
11768
|
opts.onProgress?.(batchEnd, files.length);
|
|
11032
11769
|
filesSinceLastYield += batchFiles.length;
|
|
11033
11770
|
if (filesSinceLastYield >= YIELD_EVERY_N) {
|
|
11034
11771
|
filesSinceLastYield = 0;
|
|
11035
11772
|
await yieldEventLoop();
|
|
11773
|
+
if (isFrugalPerf()) {
|
|
11774
|
+
await new Promise((r) => setTimeout(r, 8));
|
|
11775
|
+
}
|
|
11036
11776
|
throwIfAborted(signal);
|
|
11037
11777
|
}
|
|
11038
11778
|
const statOpts = signal ? { signal } : {};
|
|
@@ -11136,9 +11876,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11136
11876
|
continue;
|
|
11137
11877
|
}
|
|
11138
11878
|
if (parsed.symbols.length === 0) {
|
|
11139
|
-
store.
|
|
11140
|
-
store.deleteSymbolsForFile(file);
|
|
11141
|
-
store.upsertFile({
|
|
11879
|
+
store.replaceEmptyFile({
|
|
11142
11880
|
file,
|
|
11143
11881
|
lang,
|
|
11144
11882
|
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
@@ -11207,6 +11945,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
11207
11945
|
}
|
|
11208
11946
|
store.resolveRefs();
|
|
11209
11947
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
11948
|
+
store.optimize();
|
|
11210
11949
|
const durationMs = Date.now() - startMs;
|
|
11211
11950
|
store.setLastIndexed(Date.now());
|
|
11212
11951
|
return {
|
|
@@ -11497,10 +12236,10 @@ async function callInline(op, args, opts) {
|
|
|
11497
12236
|
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
11498
12237
|
}
|
|
11499
12238
|
}
|
|
11500
|
-
var
|
|
12239
|
+
var chain2 = Promise.resolve();
|
|
11501
12240
|
function withMutex(job) {
|
|
11502
|
-
const run =
|
|
11503
|
-
|
|
12241
|
+
const run = chain2.then(job, job);
|
|
12242
|
+
chain2 = run.then(
|
|
11504
12243
|
() => void 0,
|
|
11505
12244
|
() => void 0
|
|
11506
12245
|
);
|
|
@@ -11514,11 +12253,62 @@ function circuitOpenError() {
|
|
|
11514
12253
|
}
|
|
11515
12254
|
var DEFAULT_DEBOUNCE_MS = 400;
|
|
11516
12255
|
var debounceTimers = /* @__PURE__ */ new Map();
|
|
11517
|
-
|
|
11518
|
-
|
|
12256
|
+
var readyReindexBatches = /* @__PURE__ */ new Map();
|
|
12257
|
+
function debounceKey(projectRoot, indexDir, file) {
|
|
12258
|
+
return JSON.stringify([projectRoot, indexDir ?? "", file]);
|
|
12259
|
+
}
|
|
12260
|
+
function reindexBatchKey(projectRoot, indexDir) {
|
|
12261
|
+
return JSON.stringify([projectRoot, indexDir ?? ""]);
|
|
12262
|
+
}
|
|
12263
|
+
function flushReadyReindexBatch(key) {
|
|
12264
|
+
const batch = readyReindexBatches.get(key);
|
|
12265
|
+
if (!batch) return;
|
|
12266
|
+
readyReindexBatches.delete(key);
|
|
12267
|
+
if (!indexCircuitBreaker.allowRequest()) {
|
|
12268
|
+
const error = circuitOpenError();
|
|
12269
|
+
for (const onError of batch.onErrors) onError(error);
|
|
12270
|
+
return;
|
|
12271
|
+
}
|
|
12272
|
+
void withMutex(
|
|
12273
|
+
() => callIndexOp(
|
|
12274
|
+
"index",
|
|
12275
|
+
{
|
|
12276
|
+
projectRoot: batch.projectRoot,
|
|
12277
|
+
files: [...batch.files].sort(),
|
|
12278
|
+
indexDir: batch.indexDir
|
|
12279
|
+
},
|
|
12280
|
+
{ timeoutMs: batch.timeoutMs }
|
|
12281
|
+
)
|
|
12282
|
+
).then(
|
|
12283
|
+
() => indexCircuitBreaker.recordSuccess(),
|
|
12284
|
+
(err) => {
|
|
12285
|
+
indexCircuitBreaker.recordFailure(err);
|
|
12286
|
+
for (const onError of batch.onErrors) onError(err);
|
|
12287
|
+
}
|
|
12288
|
+
);
|
|
12289
|
+
}
|
|
12290
|
+
function addReadyReindex(opts) {
|
|
12291
|
+
const key = reindexBatchKey(opts.projectRoot, opts.indexDir);
|
|
12292
|
+
const existing = readyReindexBatches.get(key);
|
|
12293
|
+
if (existing) {
|
|
12294
|
+
existing.files.add(opts.file);
|
|
12295
|
+
existing.timeoutMs = Math.max(existing.timeoutMs, opts.timeoutMs);
|
|
12296
|
+
if (opts.onError) existing.onErrors.add(opts.onError);
|
|
12297
|
+
return;
|
|
12298
|
+
}
|
|
12299
|
+
const batch = {
|
|
12300
|
+
projectRoot: opts.projectRoot,
|
|
12301
|
+
indexDir: opts.indexDir,
|
|
12302
|
+
files: /* @__PURE__ */ new Set([opts.file]),
|
|
12303
|
+
timeoutMs: opts.timeoutMs,
|
|
12304
|
+
onErrors: new Set(opts.onError ? [opts.onError] : [])
|
|
12305
|
+
};
|
|
12306
|
+
batch.flush = setImmediate(() => flushReadyReindexBatch(key));
|
|
12307
|
+
batch.flush.unref?.();
|
|
12308
|
+
readyReindexBatches.set(key, batch);
|
|
11519
12309
|
}
|
|
11520
12310
|
function isIndexableFile(filePath) {
|
|
11521
|
-
return
|
|
12311
|
+
return isIndexablePath(filePath);
|
|
11522
12312
|
}
|
|
11523
12313
|
function isRecoverableConstraintError(err) {
|
|
11524
12314
|
if (err instanceof Error) {
|
|
@@ -11578,28 +12368,18 @@ function enqueueReindex(opts) {
|
|
|
11578
12368
|
if (files.length === 0) return;
|
|
11579
12369
|
const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
11580
12370
|
for (const file of files) {
|
|
11581
|
-
const key = debounceKey(opts.indexDir, file);
|
|
12371
|
+
const key = debounceKey(opts.projectRoot, opts.indexDir, file);
|
|
11582
12372
|
const existing = debounceTimers.get(key);
|
|
11583
12373
|
if (existing) clearTimeout(existing);
|
|
11584
12374
|
const timer = setTimeout(() => {
|
|
11585
12375
|
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
|
-
);
|
|
12376
|
+
addReadyReindex({
|
|
12377
|
+
projectRoot: opts.projectRoot,
|
|
12378
|
+
file,
|
|
12379
|
+
indexDir: opts.indexDir,
|
|
12380
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_INCREMENTAL_TIMEOUT_MS,
|
|
12381
|
+
onError: opts.onError
|
|
12382
|
+
});
|
|
11603
12383
|
}, ms);
|
|
11604
12384
|
timer.unref?.();
|
|
11605
12385
|
debounceTimers.set(key, timer);
|
|
@@ -11608,6 +12388,10 @@ function enqueueReindex(opts) {
|
|
|
11608
12388
|
function cancelPendingReindexes() {
|
|
11609
12389
|
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
11610
12390
|
debounceTimers.clear();
|
|
12391
|
+
for (const batch of readyReindexBatches.values()) {
|
|
12392
|
+
if (batch.flush) clearImmediate(batch.flush);
|
|
12393
|
+
}
|
|
12394
|
+
readyReindexBatches.clear();
|
|
11611
12395
|
}
|
|
11612
12396
|
async function searchCodebaseIndex(args, opts = {}) {
|
|
11613
12397
|
return callIndexOp("search", args, {
|
|
@@ -11893,7 +12677,7 @@ var codebaseStatsTool = {
|
|
|
11893
12677
|
|
|
11894
12678
|
// src/design.ts
|
|
11895
12679
|
import * as fs16 from "node:fs/promises";
|
|
11896
|
-
import * as
|
|
12680
|
+
import * as path21 from "node:path";
|
|
11897
12681
|
import {
|
|
11898
12682
|
applyTokenOverrides,
|
|
11899
12683
|
getDesignKitLoader,
|
|
@@ -11902,23 +12686,24 @@ import {
|
|
|
11902
12686
|
materializeTokens,
|
|
11903
12687
|
recordKitChoice,
|
|
11904
12688
|
recordOverrides,
|
|
12689
|
+
resolveSemanticTune,
|
|
11905
12690
|
runDesignVerify,
|
|
11906
12691
|
setActiveKit,
|
|
11907
12692
|
setDesignOverrides
|
|
11908
|
-
} from "@wrongstack/core";
|
|
12693
|
+
} from "@wrongstack/core/design";
|
|
11909
12694
|
async function resolveReal(p) {
|
|
11910
|
-
const resolved =
|
|
11911
|
-
let
|
|
12695
|
+
const resolved = path21.resolve(p);
|
|
12696
|
+
let probe2 = resolved;
|
|
11912
12697
|
const missing = [];
|
|
11913
12698
|
for (; ; ) {
|
|
11914
12699
|
try {
|
|
11915
|
-
return
|
|
12700
|
+
return path21.resolve(await fs16.realpath(probe2), ...missing);
|
|
11916
12701
|
} catch (err) {
|
|
11917
12702
|
if (err.code === "ENOENT") {
|
|
11918
|
-
const parent =
|
|
11919
|
-
if (parent ===
|
|
11920
|
-
missing.unshift(
|
|
11921
|
-
|
|
12703
|
+
const parent = path21.dirname(probe2);
|
|
12704
|
+
if (parent === probe2) return resolved;
|
|
12705
|
+
missing.unshift(path21.basename(probe2));
|
|
12706
|
+
probe2 = parent;
|
|
11922
12707
|
continue;
|
|
11923
12708
|
}
|
|
11924
12709
|
return resolved;
|
|
@@ -11948,8 +12733,8 @@ var designTool = {
|
|
|
11948
12733
|
properties: {
|
|
11949
12734
|
action: {
|
|
11950
12735
|
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
|
|
12736
|
+
enum: ["list", "use", "foundations", "set", "tune", "materialize", "verify"],
|
|
12737
|
+
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
12738
|
},
|
|
11954
12739
|
kit: {
|
|
11955
12740
|
type: "string",
|
|
@@ -11965,6 +12750,16 @@ var designTool = {
|
|
|
11965
12750
|
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
12751
|
additionalProperties: { type: "string" }
|
|
11967
12752
|
},
|
|
12753
|
+
tune: {
|
|
12754
|
+
type: "object",
|
|
12755
|
+
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.',
|
|
12756
|
+
properties: {
|
|
12757
|
+
radius: { type: "string" },
|
|
12758
|
+
density: { type: "string" },
|
|
12759
|
+
font: { type: "string" },
|
|
12760
|
+
motion: { type: "string" }
|
|
12761
|
+
}
|
|
12762
|
+
},
|
|
11968
12763
|
out: {
|
|
11969
12764
|
type: "string",
|
|
11970
12765
|
description: "Materialize output path (project-relative). Defaults to a per-stack convention."
|
|
@@ -12062,6 +12857,28 @@ ${body}`
|
|
|
12062
12857
|
These win over kit tokens. Run \`design {action:"materialize"}\` to write them to a theme file.`
|
|
12063
12858
|
};
|
|
12064
12859
|
}
|
|
12860
|
+
if (action === "tune") {
|
|
12861
|
+
const patch = resolveSemanticTune(input.tune ?? {});
|
|
12862
|
+
if (Object.keys(patch).length === 0) {
|
|
12863
|
+
return {
|
|
12864
|
+
action,
|
|
12865
|
+
output: 'No recognized knobs. Pass tune:{ radius:"lg", density:"compact", font:"\u2026", motion:"snappy" }.'
|
|
12866
|
+
};
|
|
12867
|
+
}
|
|
12868
|
+
const merged = await recordOverrides(ctx.projectRoot, patch, (/* @__PURE__ */ new Date()).toISOString());
|
|
12869
|
+
if (!merged) {
|
|
12870
|
+
return {
|
|
12871
|
+
action,
|
|
12872
|
+
output: 'No active kit. Pick one first: `design {action:"use", kit:"<id>"}`.'
|
|
12873
|
+
};
|
|
12874
|
+
}
|
|
12875
|
+
setDesignOverrides(ctx, merged);
|
|
12876
|
+
return {
|
|
12877
|
+
action,
|
|
12878
|
+
output: `Tuned. Resolved ${Object.keys(patch).length} token override(s): ${Object.entries(patch).map(([k, v]) => `${k}=${v}`).join(", ")}
|
|
12879
|
+
Run \`design {action:"materialize"}\` to write the tuned tokens to your theme file.`
|
|
12880
|
+
};
|
|
12881
|
+
}
|
|
12065
12882
|
if (action === "materialize") {
|
|
12066
12883
|
const active = await loadActiveKit(ctx.projectRoot);
|
|
12067
12884
|
if (!active) {
|
|
@@ -12083,11 +12900,11 @@ These win over kit tokens. Run \`design {action:"materialize"}\` to write them t
|
|
|
12083
12900
|
outPath: input.out
|
|
12084
12901
|
});
|
|
12085
12902
|
const root = await resolveReal(ctx.projectRoot);
|
|
12086
|
-
const absResolved =
|
|
12087
|
-
const absParent = await resolveReal(
|
|
12088
|
-
const abs =
|
|
12089
|
-
const rel =
|
|
12090
|
-
if (rel.startsWith("..") ||
|
|
12903
|
+
const absResolved = path21.isAbsolute(result.path) ? path21.resolve(result.path) : path21.resolve(path21.join(ctx.projectRoot, result.path));
|
|
12904
|
+
const absParent = await resolveReal(path21.dirname(absResolved));
|
|
12905
|
+
const abs = path21.join(absParent, path21.basename(absResolved));
|
|
12906
|
+
const rel = path21.relative(root, abs);
|
|
12907
|
+
if (rel.startsWith("..") || path21.isAbsolute(rel)) {
|
|
12091
12908
|
throw new Error(
|
|
12092
12909
|
`design: materialize path "${result.path}" would escape the project root`
|
|
12093
12910
|
);
|
|
@@ -12111,7 +12928,7 @@ ${result.content}
|
|
|
12111
12928
|
\`\`\``
|
|
12112
12929
|
};
|
|
12113
12930
|
}
|
|
12114
|
-
await fs16.mkdir(
|
|
12931
|
+
await fs16.mkdir(path21.dirname(abs), { recursive: true });
|
|
12115
12932
|
await fs16.writeFile(abs, result.content);
|
|
12116
12933
|
return {
|
|
12117
12934
|
action,
|
|
@@ -12163,8 +12980,8 @@ init_util();
|
|
|
12163
12980
|
import { spawn as spawn7 } from "node:child_process";
|
|
12164
12981
|
import { statSync as statSync2 } from "node:fs";
|
|
12165
12982
|
import * as fs17 from "node:fs/promises";
|
|
12166
|
-
import * as
|
|
12167
|
-
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core";
|
|
12983
|
+
import * as path22 from "node:path";
|
|
12984
|
+
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core/utils";
|
|
12168
12985
|
var diffTool = {
|
|
12169
12986
|
name: "diff",
|
|
12170
12987
|
category: "Filesystem",
|
|
@@ -12247,11 +13064,11 @@ function findGitDir(cwd) {
|
|
|
12247
13064
|
let dir = cwd;
|
|
12248
13065
|
for (let i = 0; i < 20; i++) {
|
|
12249
13066
|
try {
|
|
12250
|
-
const stat18 = statSync2(
|
|
13067
|
+
const stat18 = statSync2(path22.join(dir, ".git"));
|
|
12251
13068
|
if (stat18.isDirectory()) return dir;
|
|
12252
13069
|
} catch {
|
|
12253
13070
|
}
|
|
12254
|
-
const parent =
|
|
13071
|
+
const parent = path22.dirname(dir);
|
|
12255
13072
|
if (parent === dir) break;
|
|
12256
13073
|
dir = parent;
|
|
12257
13074
|
}
|
|
@@ -12465,7 +13282,7 @@ function processFile(content, absPath, _style, _overwrite, target) {
|
|
|
12465
13282
|
// src/e2e.ts
|
|
12466
13283
|
init_util();
|
|
12467
13284
|
import { open, readdir as readdir5 } from "node:fs/promises";
|
|
12468
|
-
import * as
|
|
13285
|
+
import * as path23 from "node:path";
|
|
12469
13286
|
async function readBoundedText(filePath, maxBytes) {
|
|
12470
13287
|
let handle;
|
|
12471
13288
|
try {
|
|
@@ -12515,8 +13332,8 @@ var MAX_PACKAGE_BYTES = 512 * 1024;
|
|
|
12515
13332
|
var MAX_CONFIG_BYTES = 512 * 1024;
|
|
12516
13333
|
var MAX_SPEC_SAMPLES = 20;
|
|
12517
13334
|
function relativePath(root, target) {
|
|
12518
|
-
const value =
|
|
12519
|
-
return value.split(
|
|
13335
|
+
const value = path23.relative(root, target) || ".";
|
|
13336
|
+
return value.split(path23.sep).join("/");
|
|
12520
13337
|
}
|
|
12521
13338
|
function escapeRegExp(value) {
|
|
12522
13339
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -12588,7 +13405,7 @@ async function scanWorkspace(root, maxDepth, signal) {
|
|
|
12588
13405
|
}
|
|
12589
13406
|
for (const entry of entries) {
|
|
12590
13407
|
signal.throwIfAborted();
|
|
12591
|
-
const absolutePath =
|
|
13408
|
+
const absolutePath = path23.join(current.directory, entry.name);
|
|
12592
13409
|
if (entry.isFile()) {
|
|
12593
13410
|
if (entry.name === "package.json") result.packageFiles.push(absolutePath);
|
|
12594
13411
|
const framework = CONFIG_NAMES.get(entry.name);
|
|
@@ -12649,9 +13466,9 @@ async function detectPackageManager3(projectRoot, scanRoot, declared) {
|
|
|
12649
13466
|
if (names.has("bun.lock") || names.has("bun.lockb")) return "bun";
|
|
12650
13467
|
if (names.has("package-lock.json") || names.has("npm-shrinkwrap.json")) return "npm";
|
|
12651
13468
|
if (directory === scanRoot) break;
|
|
12652
|
-
const parent =
|
|
12653
|
-
const relativeParent =
|
|
12654
|
-
if (parent === directory || relativeParent.startsWith("..") ||
|
|
13469
|
+
const parent = path23.dirname(directory);
|
|
13470
|
+
const relativeParent = path23.relative(scanRoot, parent);
|
|
13471
|
+
if (parent === directory || relativeParent.startsWith("..") || path23.isAbsolute(relativeParent)) {
|
|
12655
13472
|
break;
|
|
12656
13473
|
}
|
|
12657
13474
|
directory = parent;
|
|
@@ -12696,7 +13513,7 @@ function isSpec(framework, filename) {
|
|
|
12696
13513
|
return /\.(?:spec|test)\.(?:[cm]?[jt]sx?)$/i.test(filename);
|
|
12697
13514
|
}
|
|
12698
13515
|
async function collectSpecs(root, framework, testDirectory, signal) {
|
|
12699
|
-
const roots = testDirectory ? [
|
|
13516
|
+
const roots = testDirectory ? [path23.resolve(root, testDirectory)] : framework === "cypress" ? [path23.join(root, "cypress", "e2e"), path23.join(root, "cypress", "integration")] : [path23.join(root, "tests")];
|
|
12700
13517
|
const samples = [];
|
|
12701
13518
|
let count = 0;
|
|
12702
13519
|
let scanned = 0;
|
|
@@ -12715,7 +13532,7 @@ async function collectSpecs(root, framework, testDirectory, signal) {
|
|
|
12715
13532
|
}
|
|
12716
13533
|
for (const entry of entries) {
|
|
12717
13534
|
signal.throwIfAborted();
|
|
12718
|
-
const target =
|
|
13535
|
+
const target = path23.join(directory, entry.name);
|
|
12719
13536
|
if (entry.isDirectory() && !SKIP_DIRECTORIES.has(entry.name)) queue.push(target);
|
|
12720
13537
|
else if (entry.isFile() && isSpec(framework, entry.name)) {
|
|
12721
13538
|
count += 1;
|
|
@@ -12735,7 +13552,7 @@ function nearestPackage(projectRoot, packagesByDirectory, scanRoot) {
|
|
|
12735
13552
|
const found = packagesByDirectory.get(directory);
|
|
12736
13553
|
if (found) return found;
|
|
12737
13554
|
if (directory === scanRoot) return void 0;
|
|
12738
|
-
const parent =
|
|
13555
|
+
const parent = path23.dirname(directory);
|
|
12739
13556
|
if (parent === directory || relativePath(scanRoot, parent).startsWith("..")) return void 0;
|
|
12740
13557
|
directory = parent;
|
|
12741
13558
|
}
|
|
@@ -12746,13 +13563,13 @@ async function discoverE2EProjects(root, options) {
|
|
|
12746
13563
|
const packages = (await Promise.all(scan.packageFiles.map(readPackageInfo))).filter(
|
|
12747
13564
|
(info) => Boolean(info)
|
|
12748
13565
|
);
|
|
12749
|
-
const packagesByDirectory = new Map(packages.map((info) => [
|
|
13566
|
+
const packagesByDirectory = new Map(packages.map((info) => [path23.dirname(info.path), info]));
|
|
12750
13567
|
const candidates = /* @__PURE__ */ new Map();
|
|
12751
13568
|
for (const config of scan.configs) {
|
|
12752
13569
|
if (options.framework && options.framework !== "all" && config.framework !== options.framework) {
|
|
12753
13570
|
continue;
|
|
12754
13571
|
}
|
|
12755
|
-
const projectRoot =
|
|
13572
|
+
const projectRoot = path23.dirname(config.absolutePath);
|
|
12756
13573
|
candidates.set(`${config.framework}:${projectRoot}`, {
|
|
12757
13574
|
framework: config.framework,
|
|
12758
13575
|
root: projectRoot,
|
|
@@ -12760,7 +13577,7 @@ async function discoverE2EProjects(root, options) {
|
|
|
12760
13577
|
});
|
|
12761
13578
|
}
|
|
12762
13579
|
for (const info of packages) {
|
|
12763
|
-
const projectRoot =
|
|
13580
|
+
const projectRoot = path23.dirname(info.path);
|
|
12764
13581
|
for (const framework of frameworkFromPackage(info)) {
|
|
12765
13582
|
if (options.framework && options.framework !== "all" && framework !== options.framework)
|
|
12766
13583
|
continue;
|
|
@@ -12776,10 +13593,10 @@ async function discoverE2EProjects(root, options) {
|
|
|
12776
13593
|
const scripts = matchingScripts(info, candidate.framework);
|
|
12777
13594
|
const manager = await detectPackageManager3(candidate.root, root, info?.packageManager);
|
|
12778
13595
|
const testDirectory = candidate.framework === "playwright" ? staticString(source, "testDir") : void 0;
|
|
12779
|
-
const resolvedTestDirectory = testDirectory ?
|
|
12780
|
-
const relativeTestDirectory = resolvedTestDirectory ?
|
|
13596
|
+
const resolvedTestDirectory = testDirectory ? path23.resolve(candidate.root, testDirectory) : void 0;
|
|
13597
|
+
const relativeTestDirectory = resolvedTestDirectory ? path23.relative(root, resolvedTestDirectory) : void 0;
|
|
12781
13598
|
const unsafeTestDirectory = Boolean(
|
|
12782
|
-
relativeTestDirectory && (relativeTestDirectory.startsWith("..") ||
|
|
13599
|
+
relativeTestDirectory && (relativeTestDirectory.startsWith("..") || path23.isAbsolute(relativeTestDirectory))
|
|
12783
13600
|
);
|
|
12784
13601
|
const specs = options.includeSpecs === false || unsafeTestDirectory ? { count: 0, samples: [], truncated: false } : await collectSpecs(candidate.root, candidate.framework, testDirectory, options.signal);
|
|
12785
13602
|
const warnings = [];
|
|
@@ -12884,10 +13701,10 @@ import {
|
|
|
12884
13701
|
atomicWrite as atomicWrite2,
|
|
12885
13702
|
detectNewlineStyle,
|
|
12886
13703
|
normalizeToLf,
|
|
12887
|
-
ToolValidationError,
|
|
12888
13704
|
toStyle,
|
|
12889
13705
|
unifiedDiff
|
|
12890
|
-
} from "@wrongstack/core";
|
|
13706
|
+
} from "@wrongstack/core/utils";
|
|
13707
|
+
import { ToolValidationError } from "@wrongstack/core/types";
|
|
12891
13708
|
|
|
12892
13709
|
// src/_edit-match.ts
|
|
12893
13710
|
var TIER_LABEL = {
|
|
@@ -13115,7 +13932,7 @@ function prefixSimilarity(a, b) {
|
|
|
13115
13932
|
}
|
|
13116
13933
|
|
|
13117
13934
|
// src/_syntax-check.ts
|
|
13118
|
-
import * as
|
|
13935
|
+
import * as path24 from "node:path";
|
|
13119
13936
|
var TS_LIKE = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
13120
13937
|
var MAX_CHECK_CHARS = 15e5;
|
|
13121
13938
|
var MAX_ERRORS = 5;
|
|
@@ -13139,15 +13956,15 @@ async function checkSyntax(filePath, content, previousContent) {
|
|
|
13139
13956
|
return { errors, preExisting };
|
|
13140
13957
|
}
|
|
13141
13958
|
function isJsoncFile(filePath) {
|
|
13142
|
-
const base =
|
|
13959
|
+
const base = path24.basename(filePath).toLowerCase();
|
|
13143
13960
|
if (base.endsWith(".jsonc")) return true;
|
|
13144
13961
|
if (/^(tsconfig|jsconfig)([.-].*)?\.json$/.test(base)) return true;
|
|
13145
|
-
const dir =
|
|
13962
|
+
const dir = path24.basename(path24.dirname(filePath)).toLowerCase();
|
|
13146
13963
|
return dir === ".vscode";
|
|
13147
13964
|
}
|
|
13148
13965
|
async function parseErrors(filePath, content) {
|
|
13149
13966
|
if (content.length > MAX_CHECK_CHARS) return void 0;
|
|
13150
|
-
const ext =
|
|
13967
|
+
const ext = path24.extname(filePath).toLowerCase();
|
|
13151
13968
|
if (ext === ".json" || ext === ".jsonc") {
|
|
13152
13969
|
try {
|
|
13153
13970
|
JSON.parse(content);
|
|
@@ -13174,7 +13991,7 @@ async function parseErrors(filePath, content) {
|
|
|
13174
13991
|
ts2.ScriptKind.JSX
|
|
13175
13992
|
);
|
|
13176
13993
|
const sourceFile = ts2.createSourceFile(
|
|
13177
|
-
|
|
13994
|
+
path24.basename(filePath),
|
|
13178
13995
|
content,
|
|
13179
13996
|
ts2.ScriptTarget.Latest,
|
|
13180
13997
|
/* setParentNodes */
|
|
@@ -13409,7 +14226,7 @@ import {
|
|
|
13409
14226
|
emitProcessCompleted as emitProcessCompleted3,
|
|
13410
14227
|
emitProcessOutput as emitProcessOutput3,
|
|
13411
14228
|
emitProcessStarted as emitProcessStarted3
|
|
13412
|
-
} from "@wrongstack/core";
|
|
14229
|
+
} from "@wrongstack/core/observability";
|
|
13413
14230
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
13414
14231
|
init_output_spool();
|
|
13415
14232
|
init_util();
|
|
@@ -13418,7 +14235,7 @@ init_win32_resolve();
|
|
|
13418
14235
|
|
|
13419
14236
|
// src/exec-kill-guard.ts
|
|
13420
14237
|
import * as os7 from "node:os";
|
|
13421
|
-
import * as
|
|
14238
|
+
import * as path25 from "node:path";
|
|
13422
14239
|
var isWin3 = os7.platform() === "win32";
|
|
13423
14240
|
async function checkExecKillCommand(cmd, args) {
|
|
13424
14241
|
if (!cmd) return { blocked: false };
|
|
@@ -13631,7 +14448,7 @@ async function checkKillTarget(target) {
|
|
|
13631
14448
|
reason: `Blocked: kill ${target.signal} '${target.name}' targets a WrongStack process name.`
|
|
13632
14449
|
};
|
|
13633
14450
|
}
|
|
13634
|
-
const currentImage =
|
|
14451
|
+
const currentImage = path25.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
|
|
13635
14452
|
const targetsNodeRuntime = nameLower === "node" || nameLower.startsWith("node");
|
|
13636
14453
|
if (targetsNodeRuntime && currentImage === "node") {
|
|
13637
14454
|
return {
|
|
@@ -14447,7 +15264,7 @@ var execTool = {
|
|
|
14447
15264
|
command: cmd,
|
|
14448
15265
|
args: input.args ?? [],
|
|
14449
15266
|
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.`,
|
|
15267
|
+
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
15268
|
exitCode: 1,
|
|
14452
15269
|
truncated: false,
|
|
14453
15270
|
allowed: false,
|
|
@@ -14659,18 +15476,17 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
14659
15476
|
}
|
|
14660
15477
|
|
|
14661
15478
|
// src/fetch.ts
|
|
14662
|
-
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
15479
|
+
import { FetchError as FetchError2, ToolError, ToolValidationError as ToolValidationError3 } from "@wrongstack/core/types";
|
|
14663
15480
|
import TurndownService from "turndown";
|
|
14664
15481
|
|
|
14665
15482
|
// src/_fetch-guard.ts
|
|
14666
15483
|
import * as dns2 from "node:dns/promises";
|
|
14667
15484
|
import * as net3 from "node:net";
|
|
14668
15485
|
import {
|
|
14669
|
-
FetchError,
|
|
14670
|
-
ToolValidationError as ToolValidationError2,
|
|
14671
15486
|
isPrivateIPv4 as isPrivateIPv42,
|
|
14672
15487
|
isPrivateIPv6 as isPrivateIPv62
|
|
14673
|
-
} from "@wrongstack/core";
|
|
15488
|
+
} from "@wrongstack/core/utils";
|
|
15489
|
+
import { FetchError, ToolValidationError as ToolValidationError2 } from "@wrongstack/core/types";
|
|
14674
15490
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
14675
15491
|
var nativeGlobalFetch = globalThis.fetch;
|
|
14676
15492
|
var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
|
|
@@ -15168,7 +15984,8 @@ init_util();
|
|
|
15168
15984
|
import { spawn as spawn9 } from "node:child_process";
|
|
15169
15985
|
import { statSync as statSync3 } from "node:fs";
|
|
15170
15986
|
import { dirname as dirname12, resolve as resolve11, sep as sep5 } from "node:path";
|
|
15171
|
-
import { assessCommitSafety
|
|
15987
|
+
import { assessCommitSafety } from "@wrongstack/core/coordination";
|
|
15988
|
+
import { buildChildEnv as buildChildEnv4 } from "@wrongstack/core/utils";
|
|
15172
15989
|
var TIMEOUT_MS2 = 3e4;
|
|
15173
15990
|
var MAX_OUTPUT3 = 1e5;
|
|
15174
15991
|
var gitTool = {
|
|
@@ -15448,8 +16265,8 @@ function runGit2(args, cwd, signal) {
|
|
|
15448
16265
|
|
|
15449
16266
|
// src/glob.ts
|
|
15450
16267
|
import * as fs20 from "node:fs/promises";
|
|
15451
|
-
import * as
|
|
15452
|
-
import { compileGlob as
|
|
16268
|
+
import * as path26 from "node:path";
|
|
16269
|
+
import { compileGlob as compileGlob2 } from "@wrongstack/core/utils";
|
|
15453
16270
|
|
|
15454
16271
|
// src/_concurrency.ts
|
|
15455
16272
|
async function mapWithConcurrency(items, limit, fn) {
|
|
@@ -15511,7 +16328,7 @@ var globTool = {
|
|
|
15511
16328
|
const base = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
|
|
15512
16329
|
const limit = Math.max(1, Math.min(input.limit ?? 1e3, 5e3));
|
|
15513
16330
|
const ignored = await readGitignore(base);
|
|
15514
|
-
const re =
|
|
16331
|
+
const re = compileGlob2(input.pattern);
|
|
15515
16332
|
const results = [];
|
|
15516
16333
|
let truncated = false;
|
|
15517
16334
|
const pushResult = async (full) => {
|
|
@@ -15552,7 +16369,7 @@ var globTool = {
|
|
|
15552
16369
|
if (DEFAULT_IGNORE2.includes(name)) continue;
|
|
15553
16370
|
if (ignored.includes(name)) continue;
|
|
15554
16371
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
15555
|
-
const full =
|
|
16372
|
+
const full = path26.join(dir, name);
|
|
15556
16373
|
if (e.isDirectory()) {
|
|
15557
16374
|
subdirs.push({ full, rel });
|
|
15558
16375
|
} else if (e.isFile()) {
|
|
@@ -15596,7 +16413,7 @@ var globTool = {
|
|
|
15596
16413
|
};
|
|
15597
16414
|
async function readGitignore(dir) {
|
|
15598
16415
|
try {
|
|
15599
|
-
const raw = await fs20.readFile(
|
|
16416
|
+
const raw = await fs20.readFile(path26.join(dir, ".gitignore"), "utf8");
|
|
15600
16417
|
return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
15601
16418
|
} catch {
|
|
15602
16419
|
return [];
|
|
@@ -15604,11 +16421,12 @@ async function readGitignore(dir) {
|
|
|
15604
16421
|
}
|
|
15605
16422
|
|
|
15606
16423
|
// src/grep.ts
|
|
15607
|
-
import { expectDefined as expectDefined7 } from "@wrongstack/core";
|
|
16424
|
+
import { expectDefined as expectDefined7 } from "@wrongstack/core/utils";
|
|
15608
16425
|
import { spawn as spawn10 } from "node:child_process";
|
|
15609
16426
|
import * as fs21 from "node:fs/promises";
|
|
15610
|
-
import * as
|
|
15611
|
-
import {
|
|
16427
|
+
import * as path27 from "node:path";
|
|
16428
|
+
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
16429
|
+
import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob3 } from "@wrongstack/core/utils";
|
|
15612
16430
|
|
|
15613
16431
|
// src/_regex.ts
|
|
15614
16432
|
var MAX_PATTERN_LEN = 256;
|
|
@@ -15898,7 +16716,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
15898
16716
|
});
|
|
15899
16717
|
}
|
|
15900
16718
|
const re = compiled.regex;
|
|
15901
|
-
const globRe = input.glob ?
|
|
16719
|
+
const globRe = input.glob ? compileGlob3(input.glob) : null;
|
|
15902
16720
|
const matches = [];
|
|
15903
16721
|
const countOnlyFirstHit = mode === "count" && limit === 1;
|
|
15904
16722
|
const maxBytes = mode === "content" ? NATIVE_MAX_FILE_BYTES : Math.min(NATIVE_MAX_FILE_BYTES, 256 * 1024);
|
|
@@ -16000,7 +16818,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
16000
16818
|
if (stopped) return;
|
|
16001
16819
|
if (DEFAULT_IGNORE3.includes(e.name)) continue;
|
|
16002
16820
|
if (e.isSymbolicLink()) continue;
|
|
16003
|
-
const full =
|
|
16821
|
+
const full = path27.join(dir, e.name);
|
|
16004
16822
|
if (e.isDirectory()) {
|
|
16005
16823
|
subdirs.push(full);
|
|
16006
16824
|
} else if (e.isFile()) {
|
|
@@ -16024,7 +16842,10 @@ init_spawn_stream();
|
|
|
16024
16842
|
init_util();
|
|
16025
16843
|
init_legacy_bridge();
|
|
16026
16844
|
import { join as join21 } from "node:path";
|
|
16027
|
-
import {
|
|
16845
|
+
import {
|
|
16846
|
+
detectEcosystem as detectPackageEcosystem,
|
|
16847
|
+
recordPackageAction
|
|
16848
|
+
} from "@wrongstack/core/coordination";
|
|
16028
16849
|
var installTool = {
|
|
16029
16850
|
name: "install",
|
|
16030
16851
|
category: "Package Management",
|
|
@@ -16210,7 +17031,7 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
16210
17031
|
// src/json.ts
|
|
16211
17032
|
init_util();
|
|
16212
17033
|
import * as fs22 from "node:fs/promises";
|
|
16213
|
-
import { deepMerge as deepMergeCore } from "@wrongstack/core";
|
|
17034
|
+
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
16214
17035
|
var jsonTool = {
|
|
16215
17036
|
name: "json",
|
|
16216
17037
|
category: "Data",
|
|
@@ -16588,56 +17409,56 @@ function jmespathSearch(data, query) {
|
|
|
16588
17409
|
}
|
|
16589
17410
|
function validateJsonSchema(data, schema) {
|
|
16590
17411
|
const errors = [];
|
|
16591
|
-
function check(value, s,
|
|
17412
|
+
function check(value, s, path35) {
|
|
16592
17413
|
if (s["type"]) {
|
|
16593
17414
|
const expectedType = s["type"];
|
|
16594
17415
|
const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
16595
17416
|
if (expectedType === "integer") {
|
|
16596
|
-
if (!Number.isInteger(value)) errors.push(`${
|
|
17417
|
+
if (!Number.isInteger(value)) errors.push(`${path35}: expected integer, got ${actualType}`);
|
|
16597
17418
|
} else if (expectedType !== actualType) {
|
|
16598
|
-
errors.push(`${
|
|
17419
|
+
errors.push(`${path35}: expected ${expectedType}, got ${actualType}`);
|
|
16599
17420
|
}
|
|
16600
17421
|
}
|
|
16601
17422
|
if (typeof value === "string" && s["format"] === "uri" && value) {
|
|
16602
17423
|
try {
|
|
16603
17424
|
new URL(value);
|
|
16604
17425
|
} catch {
|
|
16605
|
-
errors.push(`${
|
|
17426
|
+
errors.push(`${path35}: not a valid URI`);
|
|
16606
17427
|
}
|
|
16607
17428
|
}
|
|
16608
17429
|
if (typeof value === "string" && s["pattern"]) {
|
|
16609
17430
|
const re = new RegExp(s["pattern"]);
|
|
16610
|
-
if (!re.test(value)) errors.push(`${
|
|
17431
|
+
if (!re.test(value)) errors.push(`${path35}: does not match pattern ${s["pattern"]}`);
|
|
16611
17432
|
}
|
|
16612
17433
|
if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
|
|
16613
|
-
errors.push(`${
|
|
17434
|
+
errors.push(`${path35}: string too short (min ${s["minLength"]})`);
|
|
16614
17435
|
}
|
|
16615
17436
|
if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
|
|
16616
|
-
errors.push(`${
|
|
17437
|
+
errors.push(`${path35}: string too long (max ${s["maxLength"]})`);
|
|
16617
17438
|
}
|
|
16618
17439
|
if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
|
|
16619
|
-
errors.push(`${
|
|
17440
|
+
errors.push(`${path35}: below minimum ${s["minimum"]}`);
|
|
16620
17441
|
}
|
|
16621
17442
|
if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
|
|
16622
|
-
errors.push(`${
|
|
17443
|
+
errors.push(`${path35}: above maximum ${s["maximum"]}`);
|
|
16623
17444
|
}
|
|
16624
17445
|
if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
|
|
16625
17446
|
for (let i = 0; i < value.length; i++) {
|
|
16626
|
-
check(value[i], s["items"], `${
|
|
17447
|
+
check(value[i], s["items"], `${path35}[${i}]`);
|
|
16627
17448
|
}
|
|
16628
17449
|
}
|
|
16629
17450
|
if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
|
|
16630
17451
|
const props = s["properties"];
|
|
16631
17452
|
for (const [k, propSchema] of Object.entries(props)) {
|
|
16632
|
-
check(value[k], propSchema, `${
|
|
17453
|
+
check(value[k], propSchema, `${path35}.${k}`);
|
|
16633
17454
|
}
|
|
16634
17455
|
}
|
|
16635
17456
|
}
|
|
16636
17457
|
check(data, schema, "$");
|
|
16637
17458
|
return { valid: errors.length === 0, errors };
|
|
16638
17459
|
}
|
|
16639
|
-
function simpleQuery(data,
|
|
16640
|
-
const parts =
|
|
17460
|
+
function simpleQuery(data, path35) {
|
|
17461
|
+
const parts = path35.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
16641
17462
|
let current = data;
|
|
16642
17463
|
for (const part of parts) {
|
|
16643
17464
|
if (current === null || current === void 0) return void 0;
|
|
@@ -16688,7 +17509,8 @@ function toYaml(data, indent = 0) {
|
|
|
16688
17509
|
|
|
16689
17510
|
// src/kanban.ts
|
|
16690
17511
|
import { randomUUID } from "node:crypto";
|
|
16691
|
-
import { deserializeTaskGraph as deserializeTaskGraph2,
|
|
17512
|
+
import { deserializeTaskGraph as deserializeTaskGraph2, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
17513
|
+
import { loadTasks as loadTasks2 } from "@wrongstack/core/storage";
|
|
16692
17514
|
import {
|
|
16693
17515
|
addCheckToTask,
|
|
16694
17516
|
addColumn,
|
|
@@ -16735,20 +17557,21 @@ import {
|
|
|
16735
17557
|
updateColumn,
|
|
16736
17558
|
updateGoalMetricOnTask,
|
|
16737
17559
|
updateTask,
|
|
16738
|
-
updateTaskAssignment
|
|
17560
|
+
updateTaskAssignment,
|
|
17561
|
+
verifyTaskCompletion
|
|
16739
17562
|
} from "@wrongstack/kanban";
|
|
16740
17563
|
|
|
16741
17564
|
// src/session-kanban.ts
|
|
16742
17565
|
import { watch } from "node:fs";
|
|
16743
|
-
import { basename as
|
|
17566
|
+
import { basename as basename11, dirname as dirname13 } from "node:path";
|
|
17567
|
+
import { GlobalMailbox } from "@wrongstack/core/coordination";
|
|
16744
17568
|
import {
|
|
16745
|
-
deserializeTaskGraph,
|
|
16746
|
-
GlobalMailbox,
|
|
16747
17569
|
loadPlan,
|
|
16748
17570
|
loadTasks,
|
|
16749
17571
|
mutatePlan,
|
|
16750
17572
|
mutateTasks
|
|
16751
|
-
} from "@wrongstack/core";
|
|
17573
|
+
} from "@wrongstack/core/storage";
|
|
17574
|
+
import { deserializeTaskGraph } from "@wrongstack/core/tasking";
|
|
16752
17575
|
import { resolveWstackPaths as resolveWstackPaths3 } from "@wrongstack/core/utils";
|
|
16753
17576
|
import {
|
|
16754
17577
|
createBoard,
|
|
@@ -17062,8 +17885,10 @@ function projectSessionPlanToKanban(projectRoot, items, sessionId) {
|
|
|
17062
17885
|
"session-plan"
|
|
17063
17886
|
);
|
|
17064
17887
|
}
|
|
17065
|
-
function fireAndForget(work) {
|
|
17066
|
-
void work.catch(() => {
|
|
17888
|
+
function fireAndForget(context, work) {
|
|
17889
|
+
void work.catch((err) => {
|
|
17890
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17891
|
+
console.warn(JSON.stringify({ level: "warn", event: "session-kanban", context, message, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
17067
17892
|
});
|
|
17068
17893
|
}
|
|
17069
17894
|
function broadcastTodoUpdate(context, todos) {
|
|
@@ -17137,7 +17962,10 @@ function attachSessionKanbanMirror(context) {
|
|
|
17137
17962
|
if (currentSessionId === registeredSessionId) return;
|
|
17138
17963
|
if (registeredSessionId) {
|
|
17139
17964
|
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
17140
|
-
fireAndForget(
|
|
17965
|
+
fireAndForget(
|
|
17966
|
+
"cleanup-board",
|
|
17967
|
+
cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId)
|
|
17968
|
+
);
|
|
17141
17969
|
}
|
|
17142
17970
|
registeredSessionId = currentSessionId;
|
|
17143
17971
|
if (registeredSessionId) {
|
|
@@ -17187,7 +18015,7 @@ function attachSessionKanbanMirror(context) {
|
|
|
17187
18015
|
(_event, filename) => {
|
|
17188
18016
|
if (filename?.toString() !== boardFileName) return;
|
|
17189
18017
|
if (boardTimer) clearTimeout(boardTimer);
|
|
17190
|
-
boardTimer = setTimeout(() => fireAndForget(refreshBoard()), 60);
|
|
18018
|
+
boardTimer = setTimeout(() => fireAndForget("refresh-board", refreshBoard()), 60);
|
|
17191
18019
|
}
|
|
17192
18020
|
);
|
|
17193
18021
|
const touchPresence = () => touchKanbanPresence(context.projectRoot, board.id, {
|
|
@@ -17195,9 +18023,9 @@ function attachSessionKanbanMirror(context) {
|
|
|
17195
18023
|
agentId: context.agentId,
|
|
17196
18024
|
agentName: context.agentName
|
|
17197
18025
|
});
|
|
17198
|
-
fireAndForget(touchPresence());
|
|
18026
|
+
fireAndForget("touch-presence", touchPresence());
|
|
17199
18027
|
if (presenceTimer) clearInterval(presenceTimer);
|
|
17200
|
-
presenceTimer = setInterval(() => fireAndForget(touchPresence()), 6e4);
|
|
18028
|
+
presenceTimer = setInterval(() => fireAndForget("touch-presence", touchPresence()), 6e4);
|
|
17201
18029
|
presenceTimer.unref?.();
|
|
17202
18030
|
boardWatcher.on("error", () => {
|
|
17203
18031
|
boardWatcher?.close();
|
|
@@ -17222,11 +18050,11 @@ function attachSessionKanbanMirror(context) {
|
|
|
17222
18050
|
const name = filename?.toString();
|
|
17223
18051
|
const currentPlanPath = context.meta["plan.path"];
|
|
17224
18052
|
const currentTaskPath = context.meta["task.path"];
|
|
17225
|
-
const planName = typeof currentPlanPath === "string" ?
|
|
17226
|
-
const taskName = typeof currentTaskPath === "string" ?
|
|
18053
|
+
const planName = typeof currentPlanPath === "string" ? basename11(currentPlanPath) : "";
|
|
18054
|
+
const taskName = typeof currentTaskPath === "string" ? basename11(currentTaskPath) : "";
|
|
17227
18055
|
if (name && name !== planName && name !== taskName) return;
|
|
17228
18056
|
if (timer) clearTimeout(timer);
|
|
17229
|
-
timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
|
|
18057
|
+
timer = setTimeout(() => fireAndForget("refresh-files", refreshFiles()), 60);
|
|
17230
18058
|
});
|
|
17231
18059
|
watcher.on("error", () => watcher?.close());
|
|
17232
18060
|
} catch {
|
|
@@ -17246,13 +18074,13 @@ function attachSessionKanbanMirror(context) {
|
|
|
17246
18074
|
if (change.kind === "meta_set" && (change.key === "plan.path" || change.key === "task.path")) {
|
|
17247
18075
|
syncActiveSessionRegistration();
|
|
17248
18076
|
configureWatcher();
|
|
17249
|
-
fireAndForget(ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
17250
|
-
fireAndForget(configureBoardWatcher());
|
|
17251
|
-
fireAndForget(refreshFiles());
|
|
18077
|
+
fireAndForget("ensure-board", ensureSessionKanbanBoard(context.projectRoot, sessionId()));
|
|
18078
|
+
fireAndForget("configure-watcher", configureBoardWatcher());
|
|
18079
|
+
fireAndForget("refresh-files", refreshFiles());
|
|
17252
18080
|
}
|
|
17253
18081
|
});
|
|
17254
18082
|
configureWatcher();
|
|
17255
|
-
fireAndForget(configureBoardWatcher());
|
|
18083
|
+
fireAndForget("configure-watcher", configureBoardWatcher());
|
|
17256
18084
|
const detach = () => {
|
|
17257
18085
|
unsubscribe();
|
|
17258
18086
|
if (timer) clearTimeout(timer);
|
|
@@ -17263,7 +18091,10 @@ function attachSessionKanbanMirror(context) {
|
|
|
17263
18091
|
bindings.delete(context);
|
|
17264
18092
|
if (attachedProjectRoot && registeredSessionId) {
|
|
17265
18093
|
releaseActiveSessionBoard(attachedProjectRoot, registeredSessionId);
|
|
17266
|
-
fireAndForget(
|
|
18094
|
+
fireAndForget(
|
|
18095
|
+
"cleanup-board",
|
|
18096
|
+
cleanupSessionKanbanBoardIfEmpty(attachedProjectRoot, registeredSessionId)
|
|
18097
|
+
);
|
|
17267
18098
|
registeredSessionId = "";
|
|
17268
18099
|
}
|
|
17269
18100
|
};
|
|
@@ -17406,8 +18237,8 @@ async function applySessionKanbanTaskToSource(context, task, options = {}) {
|
|
|
17406
18237
|
var kanbanTool = {
|
|
17407
18238
|
name: "kanban",
|
|
17408
18239
|
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.",
|
|
18240
|
+
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.",
|
|
18241
|
+
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
18242
|
permission: "confirm",
|
|
17412
18243
|
mutating: true,
|
|
17413
18244
|
capabilities: ["fs.write"],
|
|
@@ -17463,7 +18294,9 @@ var kanbanTool = {
|
|
|
17463
18294
|
"add_check",
|
|
17464
18295
|
"update_check",
|
|
17465
18296
|
"add_note",
|
|
17466
|
-
"add_link"
|
|
18297
|
+
"add_link",
|
|
18298
|
+
"verify_completion",
|
|
18299
|
+
"split_atomic"
|
|
17467
18300
|
]
|
|
17468
18301
|
},
|
|
17469
18302
|
boardId: { type: "string" },
|
|
@@ -17518,6 +18351,7 @@ var kanbanTool = {
|
|
|
17518
18351
|
runTaskId: { type: "string" },
|
|
17519
18352
|
lastResult: { type: "string" },
|
|
17520
18353
|
error: { type: "string" },
|
|
18354
|
+
expectedLeaseId: { type: "string" },
|
|
17521
18355
|
assignmentStatus: {
|
|
17522
18356
|
type: "string",
|
|
17523
18357
|
enum: ["assigned", "queued", "running", "completed", "failed", "cancelled"]
|
|
@@ -17552,6 +18386,7 @@ var kanbanTool = {
|
|
|
17552
18386
|
lastFailureKind: { type: "string" },
|
|
17553
18387
|
dependsOn: { type: "array", items: { type: "string" } },
|
|
17554
18388
|
estimatedHours: { type: "number" },
|
|
18389
|
+
actualHours: { type: "number" },
|
|
17555
18390
|
taskGraph: { type: "object" },
|
|
17556
18391
|
graphId: { type: "string" },
|
|
17557
18392
|
specId: { type: "string" },
|
|
@@ -17860,24 +18695,7 @@ var kanbanTool = {
|
|
|
17860
18695
|
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
17861
18696
|
return fail("split_task requires boardId, taskId, and childTitles.");
|
|
17862
18697
|
}
|
|
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.");
|
|
18698
|
+
return handleSplitTask(projectRoot, input, {});
|
|
17881
18699
|
}
|
|
17882
18700
|
case "merge_tasks": {
|
|
17883
18701
|
if (!input.boardId || !input.taskIds?.length || !input.title) {
|
|
@@ -18242,6 +19060,39 @@ var kanbanTool = {
|
|
|
18242
19060
|
});
|
|
18243
19061
|
return board ? okBoard(board, "Link added.") : fail("Task not found.");
|
|
18244
19062
|
}
|
|
19063
|
+
case "verify_completion": {
|
|
19064
|
+
if (!input.boardId || !input.taskId) {
|
|
19065
|
+
return fail("verify_completion requires boardId and taskId.");
|
|
19066
|
+
}
|
|
19067
|
+
const verResult = await verifyTaskCompletion(projectRoot, input.boardId, input.taskId);
|
|
19068
|
+
const persistedBoard = await updateTask(projectRoot, input.boardId, input.taskId, {
|
|
19069
|
+
verificationReport: verResult.report,
|
|
19070
|
+
successCriteria: verResult.task.successCriteria
|
|
19071
|
+
});
|
|
19072
|
+
if (!persistedBoard) {
|
|
19073
|
+
return {
|
|
19074
|
+
ok: false,
|
|
19075
|
+
verdict: verResult.report.verdict,
|
|
19076
|
+
message: `Verification succeeded but persist failed: ${verResult.report.markdownSummary}. Board may be stale \u2014 re-run verify_completion.`,
|
|
19077
|
+
board: verResult.board
|
|
19078
|
+
};
|
|
19079
|
+
}
|
|
19080
|
+
const freshTask = persistedBoard.tasks?.find((t) => t.id === input.taskId);
|
|
19081
|
+
const deterministicVerdicts = ["passed", "failed", "needs_human", "incomplete"];
|
|
19082
|
+
return {
|
|
19083
|
+
ok: deterministicVerdicts.includes(verResult.report.verdict),
|
|
19084
|
+
verdict: verResult.report.verdict,
|
|
19085
|
+
message: verResult.report.markdownSummary,
|
|
19086
|
+
board: persistedBoard,
|
|
19087
|
+
task: freshTask ?? verResult.task
|
|
19088
|
+
};
|
|
19089
|
+
}
|
|
19090
|
+
case "split_atomic": {
|
|
19091
|
+
if (!input.boardId || !input.taskId || !input.childTitles?.length) {
|
|
19092
|
+
return fail("split_atomic requires boardId, taskId, and childTitles (at least one).");
|
|
19093
|
+
}
|
|
19094
|
+
return handleSplitTask(projectRoot, input, { atomic: true });
|
|
19095
|
+
}
|
|
18245
19096
|
default:
|
|
18246
19097
|
return fail(`Unknown kanban action: ${input.action}`);
|
|
18247
19098
|
}
|
|
@@ -18261,6 +19112,50 @@ function okBoard(board, message = "Board loaded.") {
|
|
|
18261
19112
|
function okTask(board, task, message) {
|
|
18262
19113
|
return { ok: true, message, board, task };
|
|
18263
19114
|
}
|
|
19115
|
+
async function handleSplitTask(projectRoot, input, extraSplitOptions) {
|
|
19116
|
+
const boardId = input.boardId;
|
|
19117
|
+
const taskId = input.taskId;
|
|
19118
|
+
const childTitles = input.childTitles;
|
|
19119
|
+
if (!boardId || !taskId || !childTitles?.length) {
|
|
19120
|
+
return fail("split requires boardId, taskId, and at least one childTitles.");
|
|
19121
|
+
}
|
|
19122
|
+
const {
|
|
19123
|
+
targetColumnId,
|
|
19124
|
+
inheritAssignment,
|
|
19125
|
+
inheritLabels,
|
|
19126
|
+
inheritSuccessCriteria,
|
|
19127
|
+
inheritGoalMetrics,
|
|
19128
|
+
inheritDependencies,
|
|
19129
|
+
chainChildren,
|
|
19130
|
+
rewireDependents
|
|
19131
|
+
} = input;
|
|
19132
|
+
const result = await splitTask(projectRoot, boardId, taskId, {
|
|
19133
|
+
titles: childTitles,
|
|
19134
|
+
...extraSplitOptions,
|
|
19135
|
+
...targetColumnId !== void 0 ? { columnId: targetColumnId } : {},
|
|
19136
|
+
...inheritAssignment !== void 0 ? { inheritAssignment } : {},
|
|
19137
|
+
...inheritLabels !== void 0 ? { inheritLabels } : {},
|
|
19138
|
+
...inheritSuccessCriteria !== void 0 ? { inheritSuccessCriteria } : {},
|
|
19139
|
+
...inheritGoalMetrics !== void 0 ? { inheritGoalMetrics } : {},
|
|
19140
|
+
...inheritDependencies !== void 0 ? { inheritDependencies } : {},
|
|
19141
|
+
...chainChildren !== void 0 ? { chainChildren } : {},
|
|
19142
|
+
...rewireDependents !== void 0 ? { rewireDependents } : {}
|
|
19143
|
+
});
|
|
19144
|
+
if (!result) return fail("Task not found.");
|
|
19145
|
+
const freshParent = result.board.tasks?.find((t) => t.id === taskId);
|
|
19146
|
+
if (!freshParent) {
|
|
19147
|
+
return fail(
|
|
19148
|
+
`Split succeeded but parent ${taskId} not found in returned board. Children: [${result.children.map((c) => c.id).join(", ")}].`
|
|
19149
|
+
);
|
|
19150
|
+
}
|
|
19151
|
+
return {
|
|
19152
|
+
ok: true,
|
|
19153
|
+
message: `${result.children.length} child task(s) created.`,
|
|
19154
|
+
board: result.board,
|
|
19155
|
+
task: freshParent,
|
|
19156
|
+
children: result.children
|
|
19157
|
+
};
|
|
19158
|
+
}
|
|
18264
19159
|
async function requireBoard(projectRoot, boardId) {
|
|
18265
19160
|
return boardId ? getBoard2(projectRoot, boardId) : null;
|
|
18266
19161
|
}
|
|
@@ -18362,7 +19257,8 @@ function taskPatch(input) {
|
|
|
18362
19257
|
labels: input.labels,
|
|
18363
19258
|
assignedAgent: input.agentId,
|
|
18364
19259
|
...mergedDependsOn(input) ? { dependsOn: mergedDependsOn(input) } : {},
|
|
18365
|
-
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {}
|
|
19260
|
+
...input.estimatedHours !== void 0 ? { estimatedHours: input.estimatedHours } : {},
|
|
19261
|
+
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
|
|
18366
19262
|
};
|
|
18367
19263
|
}
|
|
18368
19264
|
function assignmentInput(input) {
|
|
@@ -18544,7 +19440,7 @@ async function detectLinter(cwd) {
|
|
|
18544
19440
|
|
|
18545
19441
|
// src/logs.ts
|
|
18546
19442
|
import { spawn as spawn11 } from "node:child_process";
|
|
18547
|
-
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core";
|
|
19443
|
+
import { buildChildEnv as buildChildEnv6 } from "@wrongstack/core/utils";
|
|
18548
19444
|
init_util();
|
|
18549
19445
|
var logsTool = {
|
|
18550
19446
|
name: "logs",
|
|
@@ -18683,7 +19579,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
18683
19579
|
}
|
|
18684
19580
|
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
18685
19581
|
var MAX_TAIL_LINES = 1e5;
|
|
18686
|
-
async function fileLogs(
|
|
19582
|
+
async function fileLogs(path35, lines, filterRe, stream) {
|
|
18687
19583
|
const { createInterface } = await import("node:readline");
|
|
18688
19584
|
const { createReadStream: createReadStream2 } = await import("node:fs");
|
|
18689
19585
|
const entries = [];
|
|
@@ -18692,7 +19588,7 @@ async function fileLogs(path34, lines, filterRe, stream) {
|
|
|
18692
19588
|
let writeIdx = 0;
|
|
18693
19589
|
let totalLines = 0;
|
|
18694
19590
|
const rl = createInterface({
|
|
18695
|
-
input: createReadStream2(
|
|
19591
|
+
input: createReadStream2(path35),
|
|
18696
19592
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
18697
19593
|
});
|
|
18698
19594
|
for await (const line of rl) {
|
|
@@ -18713,7 +19609,7 @@ async function fileLogs(path34, lines, filterRe, stream) {
|
|
|
18713
19609
|
if (parsed) entries.push(parsed);
|
|
18714
19610
|
}
|
|
18715
19611
|
return {
|
|
18716
|
-
source:
|
|
19612
|
+
source: path35,
|
|
18717
19613
|
entries,
|
|
18718
19614
|
total: entries.length,
|
|
18719
19615
|
truncated: totalLines > effLines,
|
|
@@ -18760,7 +19656,7 @@ function parseLine(line) {
|
|
|
18760
19656
|
init_util();
|
|
18761
19657
|
init_win32_resolve();
|
|
18762
19658
|
import { spawn as spawn12 } from "node:child_process";
|
|
18763
|
-
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core";
|
|
19659
|
+
import { buildChildEnv as buildChildEnv7 } from "@wrongstack/core/utils";
|
|
18764
19660
|
var outdatedTool = {
|
|
18765
19661
|
name: "outdated",
|
|
18766
19662
|
category: "Package Management",
|
|
@@ -18945,8 +19841,8 @@ init_util();
|
|
|
18945
19841
|
import { spawn as spawn13 } from "node:child_process";
|
|
18946
19842
|
import * as fs23 from "node:fs/promises";
|
|
18947
19843
|
import * as os8 from "node:os";
|
|
18948
|
-
import * as
|
|
18949
|
-
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core";
|
|
19844
|
+
import * as path28 from "node:path";
|
|
19845
|
+
import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core/utils";
|
|
18950
19846
|
var patchTool = {
|
|
18951
19847
|
name: "patch",
|
|
18952
19848
|
category: "Filesystem",
|
|
@@ -18981,9 +19877,9 @@ var patchTool = {
|
|
|
18981
19877
|
for (const t of targets) {
|
|
18982
19878
|
const stripped = stripPathComponents(t, strip);
|
|
18983
19879
|
if (!stripped) continue;
|
|
18984
|
-
const candidate =
|
|
18985
|
-
const rel =
|
|
18986
|
-
if (rel.startsWith("..") ||
|
|
19880
|
+
const candidate = path28.resolve(dir, stripped);
|
|
19881
|
+
const rel = path28.relative(ctx.projectRoot, candidate);
|
|
19882
|
+
if (rel.startsWith("..") || path28.isAbsolute(rel)) {
|
|
18987
19883
|
return {
|
|
18988
19884
|
applied: 0,
|
|
18989
19885
|
rejected: 1,
|
|
@@ -19000,11 +19896,11 @@ var patchTool = {
|
|
|
19000
19896
|
beforeContents.set(target, await readTextForTracking(target));
|
|
19001
19897
|
}
|
|
19002
19898
|
}
|
|
19003
|
-
const tmpDir = await fs23.mkdtemp(
|
|
19899
|
+
const tmpDir = await fs23.mkdtemp(path28.join(os8.tmpdir(), ".wstack_patch_"));
|
|
19004
19900
|
try {
|
|
19005
19901
|
await fs23.chmod(tmpDir, 448).catch(() => {
|
|
19006
19902
|
});
|
|
19007
|
-
const patchFile =
|
|
19903
|
+
const patchFile = path28.join(tmpDir, "in.diff");
|
|
19008
19904
|
await fs23.writeFile(patchFile, input.patch, { mode: 384 });
|
|
19009
19905
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
19010
19906
|
const result = await runPatch(args, dir, opts.signal);
|
|
@@ -19110,11 +20006,11 @@ import {
|
|
|
19110
20006
|
mutatePlan as mutatePlan2,
|
|
19111
20007
|
removePlanItem,
|
|
19112
20008
|
setPlanItemStatus
|
|
19113
|
-
} from "@wrongstack/core";
|
|
20009
|
+
} from "@wrongstack/core/storage";
|
|
19114
20010
|
import {
|
|
19115
|
-
mutateTasks as mutateTasks2
|
|
19116
|
-
|
|
19117
|
-
} from "@wrongstack/core";
|
|
20011
|
+
mutateTasks as mutateTasks2
|
|
20012
|
+
} from "@wrongstack/core/storage";
|
|
20013
|
+
import { formatTaskList } from "@wrongstack/core/utils";
|
|
19118
20014
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
19119
20015
|
var planTool = {
|
|
19120
20016
|
name: "plan",
|
|
@@ -19381,7 +20277,8 @@ function mkResult(plan, ok, message, todos) {
|
|
|
19381
20277
|
// src/read.ts
|
|
19382
20278
|
init_util();
|
|
19383
20279
|
import * as fs24 from "node:fs/promises";
|
|
19384
|
-
import { FsError,
|
|
20280
|
+
import { FsError, ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
20281
|
+
import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
|
|
19385
20282
|
var MAX_BYTES2 = 5 * 1024 * 1024;
|
|
19386
20283
|
var readTool = {
|
|
19387
20284
|
name: "read",
|
|
@@ -19587,20 +20484,20 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
19587
20484
|
}
|
|
19588
20485
|
|
|
19589
20486
|
// src/replace.ts
|
|
19590
|
-
import { expectDefined as expectDefined8 } from "@wrongstack/core";
|
|
20487
|
+
import { expectDefined as expectDefined8 } from "@wrongstack/core/utils";
|
|
19591
20488
|
import { spawn as spawn14 } from "node:child_process";
|
|
19592
20489
|
import * as fs25 from "node:fs/promises";
|
|
19593
|
-
import * as
|
|
20490
|
+
import * as path29 from "node:path";
|
|
19594
20491
|
import {
|
|
19595
20492
|
atomicWrite as atomicWrite3,
|
|
19596
20493
|
buildChildEnv as buildChildEnv9,
|
|
19597
|
-
compileGlob as
|
|
20494
|
+
compileGlob as compileGlob4,
|
|
19598
20495
|
detectNewlineStyle as detectNewlineStyle2,
|
|
19599
20496
|
normalizeToLf as normalizeToLf2,
|
|
19600
20497
|
toStyle as toStyle2,
|
|
19601
|
-
ToolValidationError as ToolValidationError6,
|
|
19602
20498
|
unifiedDiff as unifiedDiff2
|
|
19603
|
-
} from "@wrongstack/core";
|
|
20499
|
+
} from "@wrongstack/core/utils";
|
|
20500
|
+
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
19604
20501
|
init_util();
|
|
19605
20502
|
var DEFAULT_IGNORE4 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
19606
20503
|
var replaceTool = {
|
|
@@ -19659,7 +20556,7 @@ var replaceTool = {
|
|
|
19659
20556
|
});
|
|
19660
20557
|
}
|
|
19661
20558
|
const re = compiled.regex;
|
|
19662
|
-
const globRe = input.glob ?
|
|
20559
|
+
const globRe = input.glob ? compileGlob4(input.glob) : null;
|
|
19663
20560
|
const dryRun = input.dry_run ?? true;
|
|
19664
20561
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
19665
20562
|
const fileList = await resolveFiles2(filesInput, ctx, globRe);
|
|
@@ -19679,8 +20576,8 @@ var replaceTool = {
|
|
|
19679
20576
|
} catch {
|
|
19680
20577
|
continue;
|
|
19681
20578
|
}
|
|
19682
|
-
const rel =
|
|
19683
|
-
if (rel.startsWith("..") ||
|
|
20579
|
+
const rel = path29.relative(realRoot, realPath);
|
|
20580
|
+
if (rel.startsWith("..") || path29.isAbsolute(rel)) continue;
|
|
19684
20581
|
const stat18 = await fs25.stat(realPath).catch(() => null);
|
|
19685
20582
|
if (!stat18?.isFile()) continue;
|
|
19686
20583
|
let content;
|
|
@@ -19799,7 +20696,7 @@ function spawnRgFind(pattern, base) {
|
|
|
19799
20696
|
}
|
|
19800
20697
|
async function globNative(pattern, base, extraGlob) {
|
|
19801
20698
|
const results = [];
|
|
19802
|
-
const globRe =
|
|
20699
|
+
const globRe = compileGlob4(pattern);
|
|
19803
20700
|
const walk2 = async (dir) => {
|
|
19804
20701
|
let entries;
|
|
19805
20702
|
try {
|
|
@@ -19809,7 +20706,7 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19809
20706
|
}
|
|
19810
20707
|
for (const e of entries) {
|
|
19811
20708
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
19812
|
-
const full =
|
|
20709
|
+
const full = path29.join(dir, e.name);
|
|
19813
20710
|
try {
|
|
19814
20711
|
const stat18 = await fs25.lstat(full);
|
|
19815
20712
|
if (stat18.isSymbolicLink()) continue;
|
|
@@ -19836,8 +20733,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
19836
20733
|
// src/scaffold.ts
|
|
19837
20734
|
init_util();
|
|
19838
20735
|
import * as fs26 from "node:fs/promises";
|
|
19839
|
-
import * as
|
|
19840
|
-
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
20736
|
+
import * as path30 from "node:path";
|
|
20737
|
+
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
19841
20738
|
var BUILT_IN_TEMPLATES = {
|
|
19842
20739
|
"npm-package": {
|
|
19843
20740
|
description: "Basic npm package with ESM",
|
|
@@ -19986,16 +20883,16 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
19986
20883
|
let filesCreated = 0;
|
|
19987
20884
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
19988
20885
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
19989
|
-
const joinedPath =
|
|
19990
|
-
const root =
|
|
19991
|
-
const target =
|
|
19992
|
-
const rel =
|
|
19993
|
-
if (rel.startsWith("..") ||
|
|
20886
|
+
const joinedPath = path30.join(cwd, resolvedPath);
|
|
20887
|
+
const root = path30.resolve(ctx.projectRoot);
|
|
20888
|
+
const target = path30.resolve(joinedPath);
|
|
20889
|
+
const rel = path30.relative(root, target);
|
|
20890
|
+
if (rel.startsWith("..") || path30.isAbsolute(rel)) {
|
|
19994
20891
|
throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
|
|
19995
20892
|
}
|
|
19996
20893
|
const fullPath = target;
|
|
19997
20894
|
if (!dryRun) {
|
|
19998
|
-
await fs26.mkdir(
|
|
20895
|
+
await fs26.mkdir(path30.dirname(fullPath), { recursive: true });
|
|
19999
20896
|
await atomicWrite4(fullPath, substituteVars(content, name, vars));
|
|
20000
20897
|
}
|
|
20001
20898
|
files.push(resolvedPath);
|
|
@@ -20024,7 +20921,8 @@ function substituteVars(content, name, vars) {
|
|
|
20024
20921
|
}
|
|
20025
20922
|
|
|
20026
20923
|
// src/search.ts
|
|
20027
|
-
import {
|
|
20924
|
+
import { FetchError as FetchError3, ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
|
|
20925
|
+
import { expectDefined as expectDefined9 } from "@wrongstack/core/utils";
|
|
20028
20926
|
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
20029
20927
|
var DEFAULT_NUM = 10;
|
|
20030
20928
|
var MAX_RESULTS = 50;
|
|
@@ -20474,15 +21372,13 @@ var setWorkingDirTool = {
|
|
|
20474
21372
|
import {
|
|
20475
21373
|
computeTaskItemProgress,
|
|
20476
21374
|
formatTaskList as formatTaskList2
|
|
20477
|
-
} from "@wrongstack/core";
|
|
20478
|
-
import {
|
|
20479
|
-
mutateTasks as mutateTasks3
|
|
20480
|
-
} from "@wrongstack/core";
|
|
21375
|
+
} from "@wrongstack/core/utils";
|
|
21376
|
+
import { mutateTasks as mutateTasks3 } from "@wrongstack/core/storage";
|
|
20481
21377
|
import {
|
|
20482
21378
|
addPlanItem as addPlanItem2,
|
|
20483
21379
|
mutatePlan as mutatePlan3,
|
|
20484
21380
|
formatPlan as formatPlan2
|
|
20485
|
-
} from "@wrongstack/core";
|
|
21381
|
+
} from "@wrongstack/core/storage";
|
|
20486
21382
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
20487
21383
|
function findTaskIndex(tasks, query) {
|
|
20488
21384
|
const asNum = Number.parseInt(query, 10);
|
|
@@ -20852,7 +21748,7 @@ ${formatTaskList2(file.tasks)}` : file.tasks.length > 0 ? formatTaskList2(file.t
|
|
|
20852
21748
|
init_spawn_stream();
|
|
20853
21749
|
init_util();
|
|
20854
21750
|
init_legacy_bridge();
|
|
20855
|
-
import * as
|
|
21751
|
+
import * as path31 from "node:path";
|
|
20856
21752
|
var testTool = {
|
|
20857
21753
|
name: "test",
|
|
20858
21754
|
category: "Code Quality",
|
|
@@ -20959,7 +21855,7 @@ async function detectRunner(cwd) {
|
|
|
20959
21855
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
20960
21856
|
for (const f of candidates) {
|
|
20961
21857
|
try {
|
|
20962
|
-
await stat18(
|
|
21858
|
+
await stat18(path31.join(cwd, f));
|
|
20963
21859
|
if (f.includes("vitest")) return "vitest";
|
|
20964
21860
|
if (f.includes("jest")) return "jest";
|
|
20965
21861
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -21036,7 +21932,7 @@ function parseResult(runner, result, duration) {
|
|
|
21036
21932
|
}
|
|
21037
21933
|
|
|
21038
21934
|
// src/todo.ts
|
|
21039
|
-
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core";
|
|
21935
|
+
import { loadPlan as loadPlan2, loadTasks as loadTasks3, savePlan, saveTasks, setPlanItemStatus as setPlanItemStatus2 } from "@wrongstack/core/storage";
|
|
21040
21936
|
var todoTool = {
|
|
21041
21937
|
name: "todo",
|
|
21042
21938
|
category: "Session",
|
|
@@ -21428,9 +22324,9 @@ var toolUseTool = {
|
|
|
21428
22324
|
|
|
21429
22325
|
// src/tree.ts
|
|
21430
22326
|
init_util();
|
|
21431
|
-
import { expectDefined as expectDefined10 } from "@wrongstack/core";
|
|
22327
|
+
import { expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
21432
22328
|
import * as fs28 from "node:fs/promises";
|
|
21433
|
-
import * as
|
|
22329
|
+
import * as path32 from "node:path";
|
|
21434
22330
|
var DEFAULT_IGNORE5 = [
|
|
21435
22331
|
"node_modules",
|
|
21436
22332
|
".git",
|
|
@@ -21601,7 +22497,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
21601
22497
|
opts.lines.push(opts.prefix + branch + displayName);
|
|
21602
22498
|
if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
|
|
21603
22499
|
const childPrefix = opts.prefix + connector;
|
|
21604
|
-
await walkDir(
|
|
22500
|
+
await walkDir(path32.join(dir, entry.name), depth + 1, {
|
|
21605
22501
|
...opts,
|
|
21606
22502
|
prefix: childPrefix,
|
|
21607
22503
|
isLast
|
|
@@ -21614,7 +22510,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
21614
22510
|
init_spawn_stream();
|
|
21615
22511
|
init_util();
|
|
21616
22512
|
init_legacy_bridge();
|
|
21617
|
-
import * as
|
|
22513
|
+
import * as path33 from "node:path";
|
|
21618
22514
|
var typecheckTool = {
|
|
21619
22515
|
name: "typecheck",
|
|
21620
22516
|
category: "Code Quality",
|
|
@@ -21717,8 +22613,8 @@ async function findTsConfig(cwd) {
|
|
|
21717
22613
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
21718
22614
|
for (const f of candidates) {
|
|
21719
22615
|
try {
|
|
21720
|
-
const s = await stat18(
|
|
21721
|
-
if (s.isFile()) return
|
|
22616
|
+
const s = await stat18(path33.join(cwd, f));
|
|
22617
|
+
if (s.isFile()) return path33.join(cwd, f);
|
|
21722
22618
|
} catch {
|
|
21723
22619
|
}
|
|
21724
22620
|
}
|
|
@@ -21727,7 +22623,8 @@ async function findTsConfig(cwd) {
|
|
|
21727
22623
|
|
|
21728
22624
|
// src/write.ts
|
|
21729
22625
|
import * as fs29 from "node:fs/promises";
|
|
21730
|
-
import {
|
|
22626
|
+
import { ToolValidationError as ToolValidationError8 } from "@wrongstack/core/types";
|
|
22627
|
+
import { atomicWrite as atomicWrite5, unifiedDiff as unifiedDiff3 } from "@wrongstack/core/utils";
|
|
21731
22628
|
init_util();
|
|
21732
22629
|
var writeTool = {
|
|
21733
22630
|
name: "write",
|
|
@@ -21951,7 +22848,7 @@ init_circuit_breaker();
|
|
|
21951
22848
|
init_languages();
|
|
21952
22849
|
|
|
21953
22850
|
// src/memory.ts
|
|
21954
|
-
import { ToolValidationError as ToolValidationError9 } from "@wrongstack/core";
|
|
22851
|
+
import { ToolValidationError as ToolValidationError9 } from "@wrongstack/core/types";
|
|
21955
22852
|
function rememberTool(memory) {
|
|
21956
22853
|
return {
|
|
21957
22854
|
name: "remember",
|
|
@@ -22260,6 +23157,88 @@ var ProcessGuardian = class {
|
|
|
22260
23157
|
isRunning = false;
|
|
22261
23158
|
instanceId;
|
|
22262
23159
|
sigtermCount = 0;
|
|
23160
|
+
processHandlersInstalled = false;
|
|
23161
|
+
onProcessExit = (code) => {
|
|
23162
|
+
console.log(JSON.stringify({
|
|
23163
|
+
level: "info",
|
|
23164
|
+
event: "process_guardian.process_exiting",
|
|
23165
|
+
pid: process.pid,
|
|
23166
|
+
code,
|
|
23167
|
+
instanceId: this.instanceId
|
|
23168
|
+
}));
|
|
23169
|
+
this.stop();
|
|
23170
|
+
};
|
|
23171
|
+
onUncaughtException = (err) => {
|
|
23172
|
+
console.error(JSON.stringify({
|
|
23173
|
+
level: "error",
|
|
23174
|
+
event: "process_guardian.uncaught_exception",
|
|
23175
|
+
error: err.message,
|
|
23176
|
+
stack: err.stack,
|
|
23177
|
+
instanceId: this.instanceId,
|
|
23178
|
+
fatal: true
|
|
23179
|
+
}));
|
|
23180
|
+
this.stop();
|
|
23181
|
+
process.exit(1);
|
|
23182
|
+
};
|
|
23183
|
+
onUnhandledRejection = (reason) => {
|
|
23184
|
+
const err = reason instanceof Error ? { message: reason.message, stack: reason.stack } : { value: String(reason) };
|
|
23185
|
+
console.error(JSON.stringify({
|
|
23186
|
+
level: "error",
|
|
23187
|
+
event: "process_guardian.unhandled_rejection",
|
|
23188
|
+
...err,
|
|
23189
|
+
instanceId: this.instanceId,
|
|
23190
|
+
fatal: true
|
|
23191
|
+
}));
|
|
23192
|
+
this.stop();
|
|
23193
|
+
process.exit(1);
|
|
23194
|
+
};
|
|
23195
|
+
onSigterm = () => {
|
|
23196
|
+
this.sigtermCount++;
|
|
23197
|
+
const threshold = this.config.sigtermThreshold;
|
|
23198
|
+
if (threshold === Infinity) {
|
|
23199
|
+
console.log(JSON.stringify({
|
|
23200
|
+
level: "warn",
|
|
23201
|
+
event: "process_guardian.sigterm_ignored",
|
|
23202
|
+
pid: process.pid,
|
|
23203
|
+
instanceId: this.instanceId,
|
|
23204
|
+
sigtermCount: this.sigtermCount,
|
|
23205
|
+
message: "SIGTERM received but sigtermThreshold is Infinity \u2014 ignoring (use graceful shutdown instead)"
|
|
23206
|
+
}));
|
|
23207
|
+
return;
|
|
23208
|
+
}
|
|
23209
|
+
if (this.sigtermCount < threshold) {
|
|
23210
|
+
console.log(JSON.stringify({
|
|
23211
|
+
level: "warn",
|
|
23212
|
+
event: "process_guardian.sigterm_deferred",
|
|
23213
|
+
pid: process.pid,
|
|
23214
|
+
instanceId: this.instanceId,
|
|
23215
|
+
sigtermCount: this.sigtermCount,
|
|
23216
|
+
threshold,
|
|
23217
|
+
message: `SIGTERM received (${this.sigtermCount}/${threshold}) \u2014 ignoring until threshold is reached`
|
|
23218
|
+
}));
|
|
23219
|
+
return;
|
|
23220
|
+
}
|
|
23221
|
+
console.log(JSON.stringify({
|
|
23222
|
+
level: "warn",
|
|
23223
|
+
event: "process_guardian.sigterm_exiting",
|
|
23224
|
+
pid: process.pid,
|
|
23225
|
+
instanceId: this.instanceId,
|
|
23226
|
+
sigtermCount: this.sigtermCount,
|
|
23227
|
+
threshold,
|
|
23228
|
+
message: `SIGTERM threshold (${threshold}) reached \u2014 shutting down gracefully`
|
|
23229
|
+
}));
|
|
23230
|
+
this.stop();
|
|
23231
|
+
process.exit(0);
|
|
23232
|
+
};
|
|
23233
|
+
onSighup = () => {
|
|
23234
|
+
console.log(JSON.stringify({
|
|
23235
|
+
level: "warn",
|
|
23236
|
+
event: "process_guardian.sighup_received",
|
|
23237
|
+
pid: process.pid,
|
|
23238
|
+
instanceId: this.instanceId,
|
|
23239
|
+
message: "SIGHUP received but ignored - WrongStack continues running"
|
|
23240
|
+
}));
|
|
23241
|
+
};
|
|
22263
23242
|
constructor(config = {}) {
|
|
22264
23243
|
this.registry = getPersistentProcessRegistry();
|
|
22265
23244
|
this.config = {
|
|
@@ -22298,6 +23277,7 @@ var ProcessGuardian = class {
|
|
|
22298
23277
|
* Stop the guardian gracefully.
|
|
22299
23278
|
*/
|
|
22300
23279
|
stop() {
|
|
23280
|
+
this.removeProcessHandlers();
|
|
22301
23281
|
if (!this.isRunning) return;
|
|
22302
23282
|
this.isRunning = false;
|
|
22303
23283
|
if (this.heartbeatTimer) {
|
|
@@ -22377,87 +23357,22 @@ var ProcessGuardian = class {
|
|
|
22377
23357
|
* Set up process-level event handlers.
|
|
22378
23358
|
*/
|
|
22379
23359
|
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
|
-
});
|
|
23360
|
+
if (this.processHandlersInstalled) return;
|
|
23361
|
+
this.processHandlersInstalled = true;
|
|
23362
|
+
process.on("exit", this.onProcessExit);
|
|
23363
|
+
process.on("uncaughtException", this.onUncaughtException);
|
|
23364
|
+
process.on("unhandledRejection", this.onUnhandledRejection);
|
|
23365
|
+
process.on("SIGTERM", this.onSigterm);
|
|
23366
|
+
process.on("SIGHUP", this.onSighup);
|
|
23367
|
+
}
|
|
23368
|
+
removeProcessHandlers() {
|
|
23369
|
+
if (!this.processHandlersInstalled) return;
|
|
23370
|
+
this.processHandlersInstalled = false;
|
|
23371
|
+
process.off("exit", this.onProcessExit);
|
|
23372
|
+
process.off("uncaughtException", this.onUncaughtException);
|
|
23373
|
+
process.off("unhandledRejection", this.onUnhandledRejection);
|
|
23374
|
+
process.off("SIGTERM", this.onSigterm);
|
|
23375
|
+
process.off("SIGHUP", this.onSighup);
|
|
22461
23376
|
}
|
|
22462
23377
|
/**
|
|
22463
23378
|
* Check if a PID is protected by this guardian.
|
|
@@ -22493,6 +23408,7 @@ function getProcessGuardian() {
|
|
|
22493
23408
|
return _guardian;
|
|
22494
23409
|
}
|
|
22495
23410
|
function startProcessGuardian(config) {
|
|
23411
|
+
_guardian?.stop();
|
|
22496
23412
|
const guardian = new ProcessGuardian(config);
|
|
22497
23413
|
guardian.start();
|
|
22498
23414
|
_guardian = guardian;
|
|
@@ -22823,12 +23739,9 @@ function createGlobalPsSlashCommand() {
|
|
|
22823
23739
|
|
|
22824
23740
|
// src/skill.ts
|
|
22825
23741
|
import * as fs30 from "node:fs/promises";
|
|
22826
|
-
import * as
|
|
22827
|
-
import {
|
|
22828
|
-
|
|
22829
|
-
stripFrontmatter,
|
|
22830
|
-
ToolValidationError as ToolValidationError10
|
|
22831
|
-
} from "@wrongstack/core";
|
|
23742
|
+
import * as path34 from "node:path";
|
|
23743
|
+
import { SKILL_LIMITS, stripFrontmatter } from "@wrongstack/core/skills";
|
|
23744
|
+
import { ToolValidationError as ToolValidationError10 } from "@wrongstack/core/types";
|
|
22832
23745
|
var MAX_BODY_CHARS = SKILL_LIMITS.MAX_SKILL_BODY_CHARS;
|
|
22833
23746
|
var MAX_RESOURCE_CHARS = SKILL_LIMITS.MAX_RESOURCE_CHARS;
|
|
22834
23747
|
var MAX_LISTED_RESOURCES = SKILL_LIMITS.MAX_LISTED_RESOURCES;
|
|
@@ -22870,7 +23783,7 @@ function makeSkillTool(skillLoader) {
|
|
|
22870
23783
|
field: "name"
|
|
22871
23784
|
});
|
|
22872
23785
|
}
|
|
22873
|
-
const dir =
|
|
23786
|
+
const dir = path34.dirname(manifest.path);
|
|
22874
23787
|
let loadedResource;
|
|
22875
23788
|
if (input.resource?.trim()) {
|
|
22876
23789
|
loadedResource = await loadResource(dir, input.resource.trim());
|
|
@@ -22920,15 +23833,15 @@ ${listing}`;
|
|
|
22920
23833
|
}
|
|
22921
23834
|
async function loadResource(skillDir, rel) {
|
|
22922
23835
|
const norm = rel.replace(/\\/g, "/");
|
|
22923
|
-
if (
|
|
23836
|
+
if (path34.isAbsolute(rel) || norm.split("/").some((seg) => seg === "..")) {
|
|
22924
23837
|
throw new ToolValidationError10({
|
|
22925
23838
|
message: `skill: invalid resource path "${rel}"`,
|
|
22926
23839
|
field: "resource"
|
|
22927
23840
|
});
|
|
22928
23841
|
}
|
|
22929
|
-
const absPath =
|
|
22930
|
-
const root =
|
|
22931
|
-
if (absPath !== root && !absPath.startsWith(root +
|
|
23842
|
+
const absPath = path34.resolve(skillDir, rel);
|
|
23843
|
+
const root = path34.resolve(skillDir);
|
|
23844
|
+
if (absPath !== root && !absPath.startsWith(root + path34.sep)) {
|
|
22932
23845
|
throw new ToolValidationError10({
|
|
22933
23846
|
message: `skill: resource "${rel}" escapes the skill directory`,
|
|
22934
23847
|
field: "resource"
|
|
@@ -22969,7 +23882,7 @@ async function walk(root, dir, out) {
|
|
|
22969
23882
|
}
|
|
22970
23883
|
for (const e of entries) {
|
|
22971
23884
|
if (out.length >= MAX_LISTED_RESOURCES) return;
|
|
22972
|
-
const fullPath =
|
|
23885
|
+
const fullPath = path34.join(dir, e.name);
|
|
22973
23886
|
let isDir = e.isDirectory();
|
|
22974
23887
|
if (e.isSymbolicLink()) {
|
|
22975
23888
|
try {
|
|
@@ -22985,7 +23898,7 @@ async function walk(root, dir, out) {
|
|
|
22985
23898
|
if (e.name === "SKILL.md" || e.name === "SKILL.save.md") continue;
|
|
22986
23899
|
try {
|
|
22987
23900
|
const stat18 = await fs30.stat(fullPath);
|
|
22988
|
-
const rel =
|
|
23901
|
+
const rel = path34.relative(root, fullPath).split(path34.sep).join("/");
|
|
22989
23902
|
out.push({ path: rel, bytes: stat18.size });
|
|
22990
23903
|
} catch {
|
|
22991
23904
|
}
|
|
@@ -23160,6 +24073,43 @@ var TOOL_ICON_CONFIG = {
|
|
|
23160
24073
|
// gray-light
|
|
23161
24074
|
};
|
|
23162
24075
|
var FALLBACK_ICON = "fallback";
|
|
24076
|
+
|
|
24077
|
+
// src/tool-tier.ts
|
|
24078
|
+
function toolNameSet(tools) {
|
|
24079
|
+
return new Set(tools.flatMap((tool) => tool ? [tool.name] : []));
|
|
24080
|
+
}
|
|
24081
|
+
function selectBuiltinToolsForTier(tier, allTools) {
|
|
24082
|
+
switch (tier) {
|
|
24083
|
+
case "off":
|
|
24084
|
+
return [...allTools];
|
|
24085
|
+
case "minimal":
|
|
24086
|
+
case "light": {
|
|
24087
|
+
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
24088
|
+
return allTools.filter((tool) => tier1Names.has(tool.name));
|
|
24089
|
+
}
|
|
24090
|
+
case "medium": {
|
|
24091
|
+
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
24092
|
+
const tier2Names = toolNameSet(TIER2_TOOLS);
|
|
24093
|
+
return allTools.filter((tool) => tier1Names.has(tool.name) || tier2Names.has(tool.name));
|
|
24094
|
+
}
|
|
24095
|
+
case "aggressive": {
|
|
24096
|
+
const tier1Names = toolNameSet(TIER1_TOOLS);
|
|
24097
|
+
const tier2Names = toolNameSet(TIER2_TOOLS);
|
|
24098
|
+
const tier3Names = toolNameSet(TIER3_TOOLS);
|
|
24099
|
+
return allTools.filter(
|
|
24100
|
+
(tool) => tier1Names.has(tool.name) || tier2Names.has(tool.name) && tool.name !== "task" || tier3Names.has(tool.name) && tool.name !== "setWorkingDir"
|
|
24101
|
+
);
|
|
24102
|
+
}
|
|
24103
|
+
}
|
|
24104
|
+
}
|
|
24105
|
+
function registerBuiltinToolTier(options) {
|
|
24106
|
+
const tools = selectBuiltinToolsForTier(
|
|
24107
|
+
options.tier,
|
|
24108
|
+
options.tools ?? builtinToolsPack.tools ?? []
|
|
24109
|
+
);
|
|
24110
|
+
options.registry.registerAllOrThrow(tools, options.owner ?? builtinToolsPack.name);
|
|
24111
|
+
return tools;
|
|
24112
|
+
}
|
|
23163
24113
|
export {
|
|
23164
24114
|
ADDITIONAL_LANGUAGE_PROFILES,
|
|
23165
24115
|
BrowserArtifactStore,
|
|
@@ -23285,6 +24235,7 @@ export {
|
|
|
23285
24235
|
projectSessionTodosToKanban,
|
|
23286
24236
|
readTool,
|
|
23287
24237
|
redactBrowserText,
|
|
24238
|
+
registerBuiltinToolTier,
|
|
23288
24239
|
relatedMemoryTool,
|
|
23289
24240
|
rememberTool,
|
|
23290
24241
|
replaceTool,
|
|
@@ -23300,6 +24251,7 @@ export {
|
|
|
23300
24251
|
searchCodebaseIndex,
|
|
23301
24252
|
searchMemoryTool,
|
|
23302
24253
|
searchTool,
|
|
24254
|
+
selectBuiltinToolsForTier,
|
|
23303
24255
|
shutdownBrowserTools,
|
|
23304
24256
|
shutdownCodebaseIndexHost,
|
|
23305
24257
|
startProcessGuardian,
|