@wrongstack/tools 0.297.0 → 0.298.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/_spawn-stream.d.ts +2 -0
- package/dist/_util.d.ts +29 -1
- package/dist/audit.js +113 -65
- package/dist/bash.js +5 -4
- package/dist/batch-tool-use.js +70 -7
- package/dist/browser/index.js +31 -1
- package/dist/builtin.js +841 -351
- package/dist/codebase-index/index.js +68 -16
- package/dist/codebase-index/project-server-protocol.d.ts +26 -0
- package/dist/codebase-index/project-server.js +60 -16
- package/dist/codebase-index/worker.js +36 -13
- package/dist/codebase-index/writer-search-helpers.d.ts +16 -0
- package/dist/codebase-index/writer.d.ts +13 -1
- package/dist/design.d.ts +1 -1
- package/dist/design.js +4 -3
- package/dist/diff.js +11 -1
- package/dist/e2e.js +9 -6
- package/dist/edit.js +9 -6
- package/dist/exec.js +560 -529
- package/dist/format.js +116 -65
- package/dist/git.js +22 -2
- package/dist/glob.js +11 -5
- package/dist/grep.js +160 -104
- package/dist/index.js +792 -290
- package/dist/install.js +120 -68
- package/dist/json.js +64 -18
- package/dist/kanban-board-inputs.d.ts +21 -4
- package/dist/kanban.js +20 -5
- package/dist/languages/index.js +128 -71
- package/dist/lint.js +113 -65
- package/dist/outdated.js +86 -26
- package/dist/pack.js +841 -351
- package/dist/patch.js +11 -1
- package/dist/plan.js +32 -10
- package/dist/read.js +77 -23
- package/dist/replace.js +11 -3
- package/dist/scaffold.js +9 -6
- package/dist/session-kanban.js +21 -5
- package/dist/task.js +3 -1
- package/dist/test.js +113 -65
- package/dist/todo.js +3 -1
- package/dist/tool-tier.js +841 -351
- package/dist/tool-use.d.ts +1 -1
- package/dist/tool-use.js +28 -4
- package/dist/tree.d.ts +1 -0
- package/dist/tree.js +44 -8
- package/dist/typecheck.js +113 -65
- package/dist/write.js +9 -6
- package/package.json +5 -4
- package/dist/_concurrency.d.ts.map +0 -1
- package/dist/_danger-detect.d.ts.map +0 -1
- package/dist/_edit-match.d.ts.map +0 -1
- package/dist/_env.d.ts.map +0 -1
- package/dist/_fetch-guard.d.ts.map +0 -1
- package/dist/_output-spool.d.ts.map +0 -1
- package/dist/_redact-command.d.ts.map +0 -1
- package/dist/_regex.d.ts.map +0 -1
- package/dist/_session-shell.d.ts.map +0 -1
- package/dist/_shell-pick.d.ts.map +0 -1
- package/dist/_spawn-stream.d.ts.map +0 -1
- package/dist/_syntax-check.d.ts.map +0 -1
- package/dist/_util.d.ts.map +0 -1
- package/dist/_win32-resolve.d.ts.map +0 -1
- package/dist/audit.d.ts.map +0 -1
- package/dist/audit.js.map +0 -7
- package/dist/auto-proceed-loop-guard.d.ts.map +0 -1
- package/dist/auto-proceed-loop-guard.js.map +0 -7
- package/dist/bash-kill-guard.d.ts.map +0 -1
- package/dist/bash.d.ts.map +0 -1
- package/dist/bash.js.map +0 -7
- package/dist/batch-tool-use.d.ts.map +0 -1
- package/dist/batch-tool-use.js.map +0 -7
- package/dist/browser/artifacts.d.ts.map +0 -1
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js.map +0 -7
- package/dist/browser/manager.d.ts.map +0 -1
- package/dist/browser/network-guard-proxy.d.ts.map +0 -1
- package/dist/browser/security.d.ts.map +0 -1
- package/dist/browser/tools.d.ts.map +0 -1
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/builtin.d.ts.map +0 -1
- package/dist/builtin.js.map +0 -7
- package/dist/circuit-breaker.d.ts.map +0 -1
- package/dist/circuit-breaker.js.map +0 -7
- package/dist/codebase-index/background-indexer.d.ts.map +0 -1
- package/dist/codebase-index/bm25.d.ts.map +0 -1
- package/dist/codebase-index/circuit-breaker.d.ts.map +0 -1
- package/dist/codebase-index/codebase-index-tool.d.ts.map +0 -1
- package/dist/codebase-index/codebase-search-tool.d.ts.map +0 -1
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +0 -1
- package/dist/codebase-index/dead-code-scan.d.ts.map +0 -1
- package/dist/codebase-index/generic-parser.d.ts.map +0 -1
- package/dist/codebase-index/gitignore.d.ts.map +0 -1
- package/dist/codebase-index/go-parser.d.ts.map +0 -1
- package/dist/codebase-index/index-service.d.ts.map +0 -1
- package/dist/codebase-index/index.d.ts.map +0 -1
- package/dist/codebase-index/index.js.map +0 -7
- package/dist/codebase-index/indexer.d.ts.map +0 -1
- package/dist/codebase-index/json-parser.d.ts.map +0 -1
- package/dist/codebase-index/languages.d.ts.map +0 -1
- package/dist/codebase-index/lsp-kind.d.ts.map +0 -1
- package/dist/codebase-index/parser-dispatch.d.ts.map +0 -1
- package/dist/codebase-index/project-server-cache.d.ts.map +0 -1
- package/dist/codebase-index/project-server-client.d.ts.map +0 -1
- package/dist/codebase-index/project-server-endpoint.d.ts.map +0 -1
- package/dist/codebase-index/project-server-protocol.d.ts.map +0 -1
- package/dist/codebase-index/project-server.d.ts.map +0 -1
- package/dist/codebase-index/project-server.js.map +0 -7
- package/dist/codebase-index/py-parser.d.ts.map +0 -1
- package/dist/codebase-index/refs-extractor.d.ts.map +0 -1
- package/dist/codebase-index/rs-parser.d.ts.map +0 -1
- package/dist/codebase-index/schema.d.ts.map +0 -1
- package/dist/codebase-index/spawn-gate.d.ts.map +0 -1
- package/dist/codebase-index/sqlite-runtime.d.ts.map +0 -1
- package/dist/codebase-index/ts-parser.d.ts.map +0 -1
- package/dist/codebase-index/worker-protocol.d.ts.map +0 -1
- package/dist/codebase-index/worker.d.ts.map +0 -1
- package/dist/codebase-index/worker.js.map +0 -7
- package/dist/codebase-index/writer-admin.d.ts.map +0 -1
- package/dist/codebase-index/writer-bulk-insert.d.ts.map +0 -1
- package/dist/codebase-index/writer-graph-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-graph-reader.d.ts.map +0 -1
- package/dist/codebase-index/writer-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-pragmas.d.ts.map +0 -1
- package/dist/codebase-index/writer-ref-mapper.d.ts.map +0 -1
- package/dist/codebase-index/writer-schema.d.ts.map +0 -1
- package/dist/codebase-index/writer-search-helpers.d.ts.map +0 -1
- package/dist/codebase-index/writer-store-pool.d.ts.map +0 -1
- package/dist/codebase-index/writer.d.ts.map +0 -1
- package/dist/codebase-index/yaml-parser.d.ts.map +0 -1
- package/dist/design.d.ts.map +0 -1
- package/dist/design.js.map +0 -7
- package/dist/diff.d.ts.map +0 -1
- package/dist/diff.js.map +0 -7
- package/dist/document.d.ts.map +0 -1
- package/dist/document.js.map +0 -7
- package/dist/e2e.d.ts.map +0 -1
- package/dist/e2e.js.map +0 -7
- package/dist/edit.d.ts.map +0 -1
- package/dist/edit.js.map +0 -7
- package/dist/exec-kill-guard.d.ts.map +0 -1
- package/dist/exec.d.ts.map +0 -1
- package/dist/exec.js.map +0 -7
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js.map +0 -7
- package/dist/format.d.ts.map +0 -1
- package/dist/format.js.map +0 -7
- package/dist/git.d.ts.map +0 -1
- package/dist/git.js.map +0 -7
- package/dist/glob.d.ts.map +0 -1
- package/dist/glob.js.map +0 -7
- package/dist/grep.d.ts.map +0 -1
- package/dist/grep.js.map +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -7
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js.map +0 -7
- package/dist/json.d.ts.map +0 -1
- package/dist/json.js.map +0 -7
- package/dist/kanban-board-inputs.d.ts.map +0 -1
- package/dist/kanban-decomposition-actions.d.ts.map +0 -1
- package/dist/kanban-detail-actions.d.ts.map +0 -1
- package/dist/kanban-evidence-bridge.d.ts.map +0 -1
- package/dist/kanban-presence.d.ts.map +0 -1
- package/dist/kanban-split-task-handler.d.ts.map +0 -1
- package/dist/kanban-task-inputs.d.ts.map +0 -1
- package/dist/kanban-tool-results.d.ts.map +0 -1
- package/dist/kanban-tool-schema.d.ts.map +0 -1
- package/dist/kanban-tool-types.d.ts.map +0 -1
- package/dist/kanban.d.ts.map +0 -1
- package/dist/kanban.js.map +0 -7
- package/dist/languages/detect.d.ts.map +0 -1
- package/dist/languages/diagnostics.d.ts.map +0 -1
- package/dist/languages/execute-tool.d.ts.map +0 -1
- package/dist/languages/execute.d.ts.map +0 -1
- package/dist/languages/index.d.ts.map +0 -1
- package/dist/languages/index.js.map +0 -7
- package/dist/languages/legacy-bridge.d.ts.map +0 -1
- package/dist/languages/package-tool.d.ts.map +0 -1
- package/dist/languages/plan.d.ts.map +0 -1
- package/dist/languages/profile-helpers.d.ts.map +0 -1
- package/dist/languages/profiles/additional.d.ts.map +0 -1
- package/dist/languages/profiles/primary.d.ts.map +0 -1
- package/dist/languages/registry.d.ts.map +0 -1
- package/dist/languages/tool.d.ts.map +0 -1
- package/dist/languages/types.d.ts.map +0 -1
- package/dist/lint.d.ts.map +0 -1
- package/dist/lint.js.map +0 -7
- package/dist/logs.d.ts.map +0 -1
- package/dist/logs.js.map +0 -7
- package/dist/memory.d.ts.map +0 -1
- package/dist/memory.js.map +0 -7
- package/dist/mode.d.ts.map +0 -1
- package/dist/mode.js.map +0 -7
- package/dist/next-steps.d.ts.map +0 -1
- package/dist/next-steps.js.map +0 -7
- package/dist/outdated.d.ts.map +0 -1
- package/dist/outdated.js.map +0 -7
- package/dist/pack.d.ts.map +0 -1
- package/dist/pack.js.map +0 -7
- package/dist/patch.d.ts.map +0 -1
- package/dist/patch.js.map +0 -7
- package/dist/plan.d.ts.map +0 -1
- package/dist/plan.js.map +0 -7
- package/dist/process-guardian.d.ts.map +0 -1
- package/dist/process-registry-persistent.d.ts.map +0 -1
- package/dist/process-registry.d.ts.map +0 -1
- package/dist/process-registry.js.map +0 -7
- package/dist/ps-slash.d.ts.map +0 -1
- package/dist/ps-slash.js.map +0 -7
- package/dist/read.d.ts.map +0 -1
- package/dist/read.js.map +0 -7
- package/dist/replace.d.ts.map +0 -1
- package/dist/replace.js.map +0 -7
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/scaffold.js.map +0 -7
- package/dist/search.d.ts.map +0 -1
- package/dist/search.js.map +0 -7
- package/dist/session-kanban.d.ts.map +0 -1
- package/dist/session-kanban.js.map +0 -7
- package/dist/set-working-dir.d.ts.map +0 -1
- package/dist/skill.d.ts.map +0 -1
- package/dist/skill.js.map +0 -7
- package/dist/spawn-background.d.ts.map +0 -1
- package/dist/task.d.ts.map +0 -1
- package/dist/task.js.map +0 -7
- package/dist/test.d.ts.map +0 -1
- package/dist/test.js.map +0 -7
- package/dist/todo.d.ts.map +0 -1
- package/dist/todo.js.map +0 -7
- package/dist/tool-diff.d.ts.map +0 -1
- package/dist/tool-diff.js.map +0 -7
- package/dist/tool-help.d.ts.map +0 -1
- package/dist/tool-help.js.map +0 -7
- package/dist/tool-icon-map.d.ts.map +0 -1
- package/dist/tool-icons.d.ts.map +0 -1
- package/dist/tool-icons.js.map +0 -7
- package/dist/tool-search.d.ts.map +0 -1
- package/dist/tool-search.js.map +0 -7
- package/dist/tool-summary.d.ts.map +0 -1
- package/dist/tool-summary.js.map +0 -7
- package/dist/tool-tier.d.ts.map +0 -1
- package/dist/tool-tier.js.map +0 -7
- package/dist/tool-use.d.ts.map +0 -1
- package/dist/tool-use.js.map +0 -7
- package/dist/tree.d.ts.map +0 -1
- package/dist/tree.js.map +0 -7
- package/dist/typecheck.d.ts.map +0 -1
- package/dist/typecheck.js.map +0 -7
- package/dist/win32.d.ts.map +0 -1
- package/dist/win32.js.map +0 -7
- package/dist/write.d.ts.map +0 -1
- package/dist/write.js.map +0 -7
package/dist/builtin.js
CHANGED
|
@@ -81,11 +81,12 @@ function createOutputSpool(opts) {
|
|
|
81
81
|
return {
|
|
82
82
|
write(text) {
|
|
83
83
|
if (finalized || !text) return;
|
|
84
|
-
|
|
84
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
85
|
+
totalBytes += textBytes;
|
|
85
86
|
if (!stream && !failed) {
|
|
86
|
-
if (headBytes +
|
|
87
|
+
if (headBytes + textBytes <= threshold) {
|
|
87
88
|
head += text;
|
|
88
|
-
headBytes +=
|
|
89
|
+
headBytes += textBytes;
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
91
92
|
head += text;
|
|
@@ -95,7 +96,7 @@ function createOutputSpool(opts) {
|
|
|
95
96
|
}
|
|
96
97
|
if (stream) {
|
|
97
98
|
if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
|
|
98
|
-
droppedBytes +=
|
|
99
|
+
droppedBytes += textBytes;
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
101
102
|
stream.write(text);
|
|
@@ -126,6 +127,73 @@ var init_output_spool = __esm({
|
|
|
126
127
|
}
|
|
127
128
|
});
|
|
128
129
|
|
|
130
|
+
// src/_win32-resolve.ts
|
|
131
|
+
import * as fs from "node:fs";
|
|
132
|
+
import * as path2 from "node:path";
|
|
133
|
+
function resolveWin32Command(cmd) {
|
|
134
|
+
if (process.platform !== "win32") return cmd;
|
|
135
|
+
if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
|
|
136
|
+
return cmd;
|
|
137
|
+
}
|
|
138
|
+
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
139
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
|
|
140
|
+
for (const dir of pathDirs) {
|
|
141
|
+
const base = path2.join(dir, cmd);
|
|
142
|
+
for (const ext of pathext) {
|
|
143
|
+
const full = `${base}${ext}`;
|
|
144
|
+
try {
|
|
145
|
+
fs.accessSync(full, fs.constants.X_OK);
|
|
146
|
+
return full;
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return cmd;
|
|
152
|
+
}
|
|
153
|
+
function resolvePowerShell(cmd) {
|
|
154
|
+
if (process.platform !== "win32") return cmd;
|
|
155
|
+
const lower = cmd.toLowerCase();
|
|
156
|
+
if (lower !== "pwsh" && lower !== "powershell" && lower !== "pwsh.exe" && lower !== "powershell.exe") {
|
|
157
|
+
return resolveWin32Command(cmd);
|
|
158
|
+
}
|
|
159
|
+
const primary = lower.startsWith("pwsh") ? "pwsh.exe" : "powershell.exe";
|
|
160
|
+
const fallback = lower.startsWith("pwsh") ? "powershell.exe" : "pwsh.exe";
|
|
161
|
+
const resolved = resolveWin32Command(primary);
|
|
162
|
+
if (resolved !== primary) {
|
|
163
|
+
const fb = resolveWin32Command(fallback);
|
|
164
|
+
return fb === fallback ? cmd : fb;
|
|
165
|
+
}
|
|
166
|
+
return resolved;
|
|
167
|
+
}
|
|
168
|
+
function assertSafeWin32ShellArgs(args) {
|
|
169
|
+
for (const arg of args) {
|
|
170
|
+
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function buildWin32CmdShimInvocation(command, args = []) {
|
|
178
|
+
assertSafeWin32ShellArgs([command, ...args]);
|
|
179
|
+
const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
|
|
180
|
+
return {
|
|
181
|
+
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
182
|
+
args: ["/d", "/c", line],
|
|
183
|
+
windowsVerbatimArguments: true
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function quoteWin32CmdArg(arg) {
|
|
187
|
+
return `"${arg}"`;
|
|
188
|
+
}
|
|
189
|
+
var WIN32_SHELL_META;
|
|
190
|
+
var init_win32_resolve = __esm({
|
|
191
|
+
"src/_win32-resolve.ts"() {
|
|
192
|
+
"use strict";
|
|
193
|
+
WIN32_SHELL_META = /[&|<>"\r\n\0]/;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
129
197
|
// src/circuit-breaker.ts
|
|
130
198
|
var DEFAULT_MAX_CONSECUTIVE_FAILURES, DEFAULT_SLOW_CALL_THRESHOLD_MS, DEFAULT_MAX_SLOW_CALLS, DEFAULT_WINDOW_MS, DEFAULT_MAX_CALLS_PER_WINDOW, DEFAULT_COOLDOWN_MS, CircuitBreaker;
|
|
131
199
|
var init_circuit_breaker = __esm({
|
|
@@ -767,73 +835,6 @@ var init_process_registry = __esm({
|
|
|
767
835
|
}
|
|
768
836
|
});
|
|
769
837
|
|
|
770
|
-
// src/_win32-resolve.ts
|
|
771
|
-
import * as fs from "node:fs";
|
|
772
|
-
import * as path2 from "node:path";
|
|
773
|
-
function resolveWin32Command(cmd) {
|
|
774
|
-
if (process.platform !== "win32") return cmd;
|
|
775
|
-
if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
|
|
776
|
-
return cmd;
|
|
777
|
-
}
|
|
778
|
-
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
779
|
-
const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
|
|
780
|
-
for (const dir of pathDirs) {
|
|
781
|
-
const base = path2.join(dir, cmd);
|
|
782
|
-
for (const ext of pathext) {
|
|
783
|
-
const full = `${base}${ext}`;
|
|
784
|
-
try {
|
|
785
|
-
fs.accessSync(full, fs.constants.X_OK);
|
|
786
|
-
return full;
|
|
787
|
-
} catch {
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
return cmd;
|
|
792
|
-
}
|
|
793
|
-
function resolvePowerShell(cmd) {
|
|
794
|
-
if (process.platform !== "win32") return cmd;
|
|
795
|
-
const lower = cmd.toLowerCase();
|
|
796
|
-
if (lower !== "pwsh" && lower !== "powershell" && lower !== "pwsh.exe" && lower !== "powershell.exe") {
|
|
797
|
-
return resolveWin32Command(cmd);
|
|
798
|
-
}
|
|
799
|
-
const primary = lower.startsWith("pwsh") ? "pwsh.exe" : "powershell.exe";
|
|
800
|
-
const fallback = lower.startsWith("pwsh") ? "powershell.exe" : "pwsh.exe";
|
|
801
|
-
const resolved = resolveWin32Command(primary);
|
|
802
|
-
if (resolved !== primary) {
|
|
803
|
-
const fb = resolveWin32Command(fallback);
|
|
804
|
-
return fb === fallback ? cmd : fb;
|
|
805
|
-
}
|
|
806
|
-
return resolved;
|
|
807
|
-
}
|
|
808
|
-
function assertSafeWin32ShellArgs(args) {
|
|
809
|
-
for (const arg of args) {
|
|
810
|
-
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
811
|
-
throw new Error(
|
|
812
|
-
'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
|
|
813
|
-
);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
function buildWin32CmdShimInvocation(command, args = []) {
|
|
818
|
-
assertSafeWin32ShellArgs([command, ...args]);
|
|
819
|
-
const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
|
|
820
|
-
return {
|
|
821
|
-
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
822
|
-
args: ["/d", "/c", line],
|
|
823
|
-
windowsVerbatimArguments: true
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
function quoteWin32CmdArg(arg) {
|
|
827
|
-
return `"${arg}"`;
|
|
828
|
-
}
|
|
829
|
-
var WIN32_SHELL_META;
|
|
830
|
-
var init_win32_resolve = __esm({
|
|
831
|
-
"src/_win32-resolve.ts"() {
|
|
832
|
-
"use strict";
|
|
833
|
-
WIN32_SHELL_META = /[&|<>"\r\n\0]/;
|
|
834
|
-
}
|
|
835
|
-
});
|
|
836
|
-
|
|
837
838
|
// src/_spawn-stream.ts
|
|
838
839
|
import { spawn as spawn2 } from "node:child_process";
|
|
839
840
|
import {
|
|
@@ -846,8 +847,11 @@ async function* spawnStream(opts) {
|
|
|
846
847
|
const max = opts.maxBytes ?? 2e5;
|
|
847
848
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
848
849
|
const maxQueue = opts.maxQueueSize ?? 500;
|
|
850
|
+
const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
|
|
849
851
|
let stdout = "";
|
|
850
852
|
let stderr = "";
|
|
853
|
+
let stdoutRetainedBytes = 0;
|
|
854
|
+
let stderrRetainedBytes = 0;
|
|
851
855
|
let pending2 = "";
|
|
852
856
|
let error;
|
|
853
857
|
const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
|
|
@@ -889,6 +893,7 @@ async function* spawnStream(opts) {
|
|
|
889
893
|
});
|
|
890
894
|
}
|
|
891
895
|
const queue = [];
|
|
896
|
+
let queuedBytes = 0;
|
|
892
897
|
let waiter;
|
|
893
898
|
let paused = false;
|
|
894
899
|
const wake = () => {
|
|
@@ -899,7 +904,7 @@ async function* spawnStream(opts) {
|
|
|
899
904
|
}
|
|
900
905
|
};
|
|
901
906
|
const resume = () => {
|
|
902
|
-
if (paused && queue.length
|
|
907
|
+
if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
|
|
903
908
|
paused = false;
|
|
904
909
|
child.stdout?.resume();
|
|
905
910
|
child.stderr?.resume();
|
|
@@ -909,11 +914,16 @@ async function* spawnStream(opts) {
|
|
|
909
914
|
const s = c.toString();
|
|
910
915
|
stdoutBytes += c.byteLength;
|
|
911
916
|
emitProcessOutput({ pid, stream: "stdout", chunk: c });
|
|
912
|
-
if (
|
|
917
|
+
if (stdoutRetainedBytes < max) {
|
|
918
|
+
const retained = utf8Prefix(s, max - stdoutRetainedBytes);
|
|
919
|
+
stdout += retained;
|
|
920
|
+
stdoutRetainedBytes += Buffer.byteLength(retained, "utf8");
|
|
921
|
+
}
|
|
913
922
|
spool.write(s);
|
|
914
|
-
queue.push({ kind: "out", data: s });
|
|
923
|
+
queue.push({ kind: "out", data: s, bytes: c.byteLength });
|
|
924
|
+
queuedBytes += c.byteLength;
|
|
915
925
|
wake();
|
|
916
|
-
if (!paused && queue.length >= maxQueue) {
|
|
926
|
+
if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
|
|
917
927
|
paused = true;
|
|
918
928
|
child.stdout?.pause();
|
|
919
929
|
child.stderr?.pause();
|
|
@@ -923,11 +933,16 @@ async function* spawnStream(opts) {
|
|
|
923
933
|
const s = c.toString();
|
|
924
934
|
stderrBytes += c.byteLength;
|
|
925
935
|
emitProcessOutput({ pid, stream: "stderr", chunk: c });
|
|
926
|
-
if (
|
|
936
|
+
if (stderrRetainedBytes < max) {
|
|
937
|
+
const retained = utf8Prefix(s, max - stderrRetainedBytes);
|
|
938
|
+
stderr += retained;
|
|
939
|
+
stderrRetainedBytes += Buffer.byteLength(retained, "utf8");
|
|
940
|
+
}
|
|
927
941
|
spool.write(s);
|
|
928
|
-
queue.push({ kind: "err", data: s });
|
|
942
|
+
queue.push({ kind: "err", data: s, bytes: c.byteLength });
|
|
943
|
+
queuedBytes += c.byteLength;
|
|
929
944
|
wake();
|
|
930
|
-
if (!paused && queue.length >= maxQueue) {
|
|
945
|
+
if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
|
|
931
946
|
paused = true;
|
|
932
947
|
child.stdout?.pause();
|
|
933
948
|
child.stderr?.pause();
|
|
@@ -937,7 +952,9 @@ async function* spawnStream(opts) {
|
|
|
937
952
|
child.stderr?.on("data", onErr);
|
|
938
953
|
child.on("error", (e) => {
|
|
939
954
|
error = e.message;
|
|
940
|
-
|
|
955
|
+
const bytes = Buffer.byteLength(e.message, "utf8");
|
|
956
|
+
queue.push({ kind: "error", data: e.message, bytes });
|
|
957
|
+
queuedBytes += bytes;
|
|
941
958
|
wake();
|
|
942
959
|
});
|
|
943
960
|
const completeTelemetry = (code, signal, timedOut = false) => {
|
|
@@ -958,7 +975,13 @@ async function* spawnStream(opts) {
|
|
|
958
975
|
if (typeof pid === "number") registry.unregister(pid);
|
|
959
976
|
const exitCode2 = code ?? (signal ? 1 : 0);
|
|
960
977
|
completeTelemetry(exitCode2, signal ?? void 0);
|
|
961
|
-
queue.push({
|
|
978
|
+
queue.push({
|
|
979
|
+
kind: "close",
|
|
980
|
+
data: "",
|
|
981
|
+
bytes: 0,
|
|
982
|
+
code: exitCode2,
|
|
983
|
+
...signal ? { signal } : {}
|
|
984
|
+
});
|
|
962
985
|
wake();
|
|
963
986
|
});
|
|
964
987
|
const onAbort = () => {
|
|
@@ -970,7 +993,7 @@ async function* spawnStream(opts) {
|
|
|
970
993
|
} catch {
|
|
971
994
|
}
|
|
972
995
|
}
|
|
973
|
-
queue.push({ kind: "close", data: "", code: 124 });
|
|
996
|
+
queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
|
|
974
997
|
completeTelemetry(124, "SIGKILL", true);
|
|
975
998
|
wake();
|
|
976
999
|
};
|
|
@@ -988,6 +1011,7 @@ async function* spawnStream(opts) {
|
|
|
988
1011
|
});
|
|
989
1012
|
}
|
|
990
1013
|
const chunk = queue.shift();
|
|
1014
|
+
queuedBytes -= chunk.bytes;
|
|
991
1015
|
resume();
|
|
992
1016
|
if (chunk.kind === "close") {
|
|
993
1017
|
if (!spawnFailed) exitCode = chunk.code ?? 0;
|
|
@@ -1014,7 +1038,7 @@ async function* spawnStream(opts) {
|
|
|
1014
1038
|
stdout: spooled ? stdout + spoolNote(spooled) : stdout,
|
|
1015
1039
|
stderr,
|
|
1016
1040
|
exitCode,
|
|
1017
|
-
truncated:
|
|
1041
|
+
truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
|
|
1018
1042
|
error,
|
|
1019
1043
|
spoolPath: spooled?.path,
|
|
1020
1044
|
spoolBytes: spooled?.bytes
|
|
@@ -1038,13 +1062,27 @@ async function* spawnStream(opts) {
|
|
|
1038
1062
|
}
|
|
1039
1063
|
}
|
|
1040
1064
|
}
|
|
1065
|
+
function utf8Prefix(text, maxBytes) {
|
|
1066
|
+
if (maxBytes <= 0) return "";
|
|
1067
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
1068
|
+
let low = 0;
|
|
1069
|
+
let high = text.length;
|
|
1070
|
+
while (low < high) {
|
|
1071
|
+
const mid = Math.ceil((low + high) / 2);
|
|
1072
|
+
if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
|
|
1073
|
+
else high = mid - 1;
|
|
1074
|
+
}
|
|
1075
|
+
let end = low;
|
|
1076
|
+
if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
|
|
1077
|
+
return text.slice(0, end);
|
|
1078
|
+
}
|
|
1041
1079
|
var isWin;
|
|
1042
1080
|
var init_spawn_stream = __esm({
|
|
1043
1081
|
"src/_spawn-stream.ts"() {
|
|
1044
1082
|
"use strict";
|
|
1045
1083
|
init_output_spool();
|
|
1046
|
-
init_process_registry();
|
|
1047
1084
|
init_win32_resolve();
|
|
1085
|
+
init_process_registry();
|
|
1048
1086
|
isWin = process.platform === "win32";
|
|
1049
1087
|
}
|
|
1050
1088
|
});
|
|
@@ -1058,14 +1096,14 @@ function sha256hex(content) {
|
|
|
1058
1096
|
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1059
1097
|
}
|
|
1060
1098
|
async function detectPackageManager(cwd) {
|
|
1061
|
-
const { stat:
|
|
1099
|
+
const { stat: stat18 } = await import("node:fs/promises");
|
|
1062
1100
|
try {
|
|
1063
|
-
await
|
|
1101
|
+
await stat18(`${cwd}/pnpm-lock.yaml`);
|
|
1064
1102
|
return "pnpm";
|
|
1065
1103
|
} catch {
|
|
1066
1104
|
}
|
|
1067
1105
|
try {
|
|
1068
|
-
await
|
|
1106
|
+
await stat18(`${cwd}/yarn.lock`);
|
|
1069
1107
|
return "yarn";
|
|
1070
1108
|
} catch {
|
|
1071
1109
|
}
|
|
@@ -1093,11 +1131,15 @@ function safeResolve(input, ctx) {
|
|
|
1093
1131
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
1094
1132
|
}
|
|
1095
1133
|
async function assertRealInsideRoot(absPath, ctx) {
|
|
1096
|
-
|
|
1134
|
+
await resolveRealInsideRoot(absPath, ctx);
|
|
1135
|
+
}
|
|
1136
|
+
async function resolveRealInsideRoot(absPath, ctx) {
|
|
1137
|
+
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
1097
1138
|
const realRoots = await Promise.all(
|
|
1098
1139
|
allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path3.resolve(r)))
|
|
1099
1140
|
);
|
|
1100
1141
|
let probe2 = absPath;
|
|
1142
|
+
const pendingTail = [];
|
|
1101
1143
|
for (; ; ) {
|
|
1102
1144
|
let real;
|
|
1103
1145
|
try {
|
|
@@ -1105,13 +1147,16 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
1105
1147
|
} catch (err) {
|
|
1106
1148
|
if (err.code === "ENOENT") {
|
|
1107
1149
|
const parent = path3.dirname(probe2);
|
|
1108
|
-
if (parent === probe2) return;
|
|
1150
|
+
if (parent === probe2) return absPath;
|
|
1151
|
+
pendingTail.unshift(path3.basename(probe2));
|
|
1109
1152
|
probe2 = parent;
|
|
1110
1153
|
continue;
|
|
1111
1154
|
}
|
|
1112
1155
|
throw err;
|
|
1113
1156
|
}
|
|
1114
|
-
if (isInsideAny(real, realRoots))
|
|
1157
|
+
if (isInsideAny(real, realRoots)) {
|
|
1158
|
+
return pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
|
|
1159
|
+
}
|
|
1115
1160
|
throw new Error(
|
|
1116
1161
|
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
1117
1162
|
);
|
|
@@ -1119,8 +1164,7 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
1119
1164
|
}
|
|
1120
1165
|
async function safeResolveReal(input, ctx) {
|
|
1121
1166
|
const abs = safeResolve(input, ctx);
|
|
1122
|
-
await
|
|
1123
|
-
return abs;
|
|
1167
|
+
return await resolveRealInsideRoot(abs, ctx);
|
|
1124
1168
|
}
|
|
1125
1169
|
function truncateMiddle(s, max) {
|
|
1126
1170
|
if (Buffer.byteLength(s, "utf8") <= max) return s;
|
|
@@ -2684,8 +2728,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
|
|
|
2684
2728
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2685
2729
|
}
|
|
2686
2730
|
}
|
|
2687
|
-
function collectFileEvidence(directory, fullPath,
|
|
2688
|
-
const lower =
|
|
2731
|
+
function collectFileEvidence(directory, fullPath, basename13, profiles, state) {
|
|
2732
|
+
const lower = basename13.toLowerCase();
|
|
2689
2733
|
const extension = path4.extname(lower);
|
|
2690
2734
|
for (const profile of profiles) {
|
|
2691
2735
|
const detector = profile.detectors.find(
|
|
@@ -2696,7 +2740,7 @@ function collectFileEvidence(directory, fullPath, basename12, profiles, state) {
|
|
|
2696
2740
|
candidate.evidence.push({
|
|
2697
2741
|
kind: detector.kind,
|
|
2698
2742
|
path: fullPath,
|
|
2699
|
-
value:
|
|
2743
|
+
value: basename13,
|
|
2700
2744
|
weight: detector.weight
|
|
2701
2745
|
});
|
|
2702
2746
|
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
@@ -2824,8 +2868,8 @@ function normalizeLimits(input) {
|
|
|
2824
2868
|
async function canonicalDirectory(input) {
|
|
2825
2869
|
const resolved = path4.resolve(input);
|
|
2826
2870
|
const real = await fs2.realpath(resolved);
|
|
2827
|
-
const
|
|
2828
|
-
if (!
|
|
2871
|
+
const stat18 = await fs2.stat(real);
|
|
2872
|
+
if (!stat18.isDirectory()) throw new Error(`Project root is not a directory: ${input}`);
|
|
2829
2873
|
return real;
|
|
2830
2874
|
}
|
|
2831
2875
|
async function canonicalInside(input, root, label) {
|
|
@@ -3381,6 +3425,7 @@ import * as fs3 from "node:fs/promises";
|
|
|
3381
3425
|
import * as path6 from "node:path";
|
|
3382
3426
|
async function planLanguageOperation(options) {
|
|
3383
3427
|
validateOperationOptions(options.operation, options.operationOptions?.packages);
|
|
3428
|
+
validateFilterOption(options.operationOptions?.filter);
|
|
3384
3429
|
const detection = await detectLanguageWorkspaces(options);
|
|
3385
3430
|
const candidates = detection.workspaces.filter(
|
|
3386
3431
|
(workspace) => workspace.capabilities.includes(options.operation)
|
|
@@ -3553,6 +3598,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
|
|
|
3553
3598
|
const relative12 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
|
|
3554
3599
|
return relative12 === "" ? 0 : relative12.split(path6.sep).length;
|
|
3555
3600
|
}
|
|
3601
|
+
function validateFilterOption(filter) {
|
|
3602
|
+
if (filter === void 0) return;
|
|
3603
|
+
if (filter.startsWith("-") || filter.includes(" --")) {
|
|
3604
|
+
throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
|
|
3605
|
+
}
|
|
3606
|
+
if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
|
|
3607
|
+
throw new Error("Invalid test filter (oversized or contains a control character)");
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3556
3610
|
function validateOperationOptions(operation, packages) {
|
|
3557
3611
|
if (!operation.startsWith("package-") || !packages) return;
|
|
3558
3612
|
for (const value of packages) {
|
|
@@ -3594,7 +3648,7 @@ var init_plan = __esm({
|
|
|
3594
3648
|
MAX_ARGUMENT_LENGTH = 4096;
|
|
3595
3649
|
PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3596
3650
|
COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3597
|
-
GO_MODULE_RE = /^(?:[a-z0-9
|
|
3651
|
+
GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
|
|
3598
3652
|
PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
|
|
3599
3653
|
}
|
|
3600
3654
|
});
|
|
@@ -3714,8 +3768,8 @@ async function executeInternal(options, startedAt) {
|
|
|
3714
3768
|
const target = options.plan.evidence.find((item) => item.kind === "target")?.path;
|
|
3715
3769
|
if (!target) return unavailableResult(options, "Internal syntax plan has no target evidence.");
|
|
3716
3770
|
const safeTarget = await assertContainedFile(target, options.projectRoot);
|
|
3717
|
-
const
|
|
3718
|
-
if (
|
|
3771
|
+
const stat18 = await fs4.stat(safeTarget);
|
|
3772
|
+
if (stat18.size > MAX_INTERNAL_SOURCE_BYTES) {
|
|
3719
3773
|
return unavailableResult(
|
|
3720
3774
|
options,
|
|
3721
3775
|
`Internal syntax target exceeds ${MAX_INTERNAL_SOURCE_BYTES} bytes.`
|
|
@@ -4002,8 +4056,8 @@ async function snapshotPaths(paths) {
|
|
|
4002
4056
|
const existing = [];
|
|
4003
4057
|
for (const candidate of paths) {
|
|
4004
4058
|
try {
|
|
4005
|
-
const
|
|
4006
|
-
if (!
|
|
4059
|
+
const stat18 = await fs4.stat(candidate);
|
|
4060
|
+
if (!stat18.isFile()) continue;
|
|
4007
4061
|
existing.push(candidate);
|
|
4008
4062
|
} catch {
|
|
4009
4063
|
}
|
|
@@ -4031,8 +4085,8 @@ async function snapshotSizes(paths) {
|
|
|
4031
4085
|
const sizes = /* @__PURE__ */ new Map();
|
|
4032
4086
|
for (const candidate of paths) {
|
|
4033
4087
|
try {
|
|
4034
|
-
const
|
|
4035
|
-
if (
|
|
4088
|
+
const stat18 = await fs4.stat(candidate);
|
|
4089
|
+
if (stat18.isFile()) sizes.set(candidate, stat18.size);
|
|
4036
4090
|
} catch {
|
|
4037
4091
|
}
|
|
4038
4092
|
}
|
|
@@ -4192,6 +4246,9 @@ var init_execute_tool = __esm({
|
|
|
4192
4246
|
useInstead: ["language_info", "install", "audit", "outdated"]
|
|
4193
4247
|
},
|
|
4194
4248
|
permission: "confirm",
|
|
4249
|
+
// WS-046: gives permission decisions something to key on.
|
|
4250
|
+
// The action performed (build/test/lint); the cwd is where, not what.
|
|
4251
|
+
subjectKey: "action",
|
|
4195
4252
|
mutating: true,
|
|
4196
4253
|
riskTier: "standard",
|
|
4197
4254
|
capabilities: ["shell.restricted", "fs.write"],
|
|
@@ -4424,6 +4481,9 @@ var init_package_tool = __esm({
|
|
|
4424
4481
|
useInstead: ["language_info", "language", "install", "audit", "outdated"]
|
|
4425
4482
|
},
|
|
4426
4483
|
permission: "confirm",
|
|
4484
|
+
// WS-046: gives permission decisions something to key on.
|
|
4485
|
+
// The package operation performed — install/remove are not interchangeable.
|
|
4486
|
+
subjectKey: "operation",
|
|
4427
4487
|
mutating: true,
|
|
4428
4488
|
riskTier: "destructive",
|
|
4429
4489
|
capabilities: ["shell.restricted", "fs.write", "net.outbound", "package.install"],
|
|
@@ -6475,9 +6535,9 @@ function parseSymbols6(opts) {
|
|
|
6475
6535
|
function regexParse2(opts) {
|
|
6476
6536
|
const { file, content, lang } = opts;
|
|
6477
6537
|
const symbols = [];
|
|
6478
|
-
const
|
|
6479
|
-
const isPackageJson =
|
|
6480
|
-
const isTsconfig =
|
|
6538
|
+
const basename13 = path22.basename(file).toLowerCase();
|
|
6539
|
+
const isPackageJson = basename13 === "package.json";
|
|
6540
|
+
const isTsconfig = basename13 === "tsconfig.json" || basename13 === "tsconfig.build.json";
|
|
6481
6541
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
6482
6542
|
const isOpenApi = content.includes("openapi") || content.includes("swagger");
|
|
6483
6543
|
const lines = content.split("\n");
|
|
@@ -8373,30 +8433,59 @@ ${hint}` : ""),
|
|
|
8373
8433
|
import {
|
|
8374
8434
|
GOVERNED_TOOL_EXECUTOR_META_KEY
|
|
8375
8435
|
} from "@wrongstack/core/types";
|
|
8436
|
+
var BATCHABLE_TOOL_NAMES = [
|
|
8437
|
+
"read",
|
|
8438
|
+
"glob",
|
|
8439
|
+
"grep",
|
|
8440
|
+
"tree",
|
|
8441
|
+
"codebase-stats",
|
|
8442
|
+
"codebase-search",
|
|
8443
|
+
"language_info",
|
|
8444
|
+
"tool_search",
|
|
8445
|
+
"memory_for_file",
|
|
8446
|
+
"memory_for_path",
|
|
8447
|
+
"memory_search"
|
|
8448
|
+
];
|
|
8449
|
+
var BATCHABLE_TOOLS = new Set(BATCHABLE_TOOL_NAMES);
|
|
8450
|
+
var MAX_BATCH_CALLS = 8;
|
|
8451
|
+
var MAX_PARALLEL_BATCH_CALLS = 3;
|
|
8376
8452
|
var batchToolUseTool = {
|
|
8377
8453
|
name: "batch_tool_use",
|
|
8378
8454
|
category: "Meta",
|
|
8379
|
-
description: "Execute a batch of
|
|
8380
|
-
usageHint:
|
|
8455
|
+
description: "Execute a small batch of independent, read-only discovery calls. Process, mutation, browser-action, MCP, delegation, and nested meta tools are not eligible.",
|
|
8456
|
+
usageHint: `BOUNDED READ-ONLY BATCHING:
|
|
8457
|
+
|
|
8458
|
+
- Use only for a small set of independent reads or searches that are all needed now.
|
|
8459
|
+
- Do not wrap normal sequential reasoning or every tool call in a batch.
|
|
8460
|
+
- \`parallel: true\` (default) runs at most three eligible calls concurrently.
|
|
8461
|
+
- \`stop_on_error: true\` makes it fail fast on the first error.
|
|
8462
|
+
- Eligible tools: ${BATCHABLE_TOOL_NAMES.join(", ")}.`,
|
|
8381
8463
|
permission: "confirm",
|
|
8464
|
+
// Scheduling barrier: keep the outer meta-call out of ToolExecutor's
|
|
8465
|
+
// non-mutating fan-out even though every eligible inner tool is read-only.
|
|
8382
8466
|
mutating: true,
|
|
8383
8467
|
timeoutMs: 12e4,
|
|
8384
|
-
capabilities: ["tool.
|
|
8468
|
+
capabilities: ["tool.meta"],
|
|
8385
8469
|
icon: "meta",
|
|
8386
8470
|
inputSchema: {
|
|
8387
8471
|
type: "object",
|
|
8388
8472
|
properties: {
|
|
8389
8473
|
calls: {
|
|
8390
8474
|
type: "array",
|
|
8475
|
+
maxItems: MAX_BATCH_CALLS,
|
|
8391
8476
|
items: {
|
|
8392
8477
|
type: "object",
|
|
8393
8478
|
properties: {
|
|
8394
|
-
tool: {
|
|
8479
|
+
tool: {
|
|
8480
|
+
type: "string",
|
|
8481
|
+
enum: [...BATCHABLE_TOOL_NAMES],
|
|
8482
|
+
description: "An explicitly batch-safe, read-only discovery tool."
|
|
8483
|
+
},
|
|
8395
8484
|
input: { type: "object" }
|
|
8396
8485
|
},
|
|
8397
8486
|
required: ["tool"]
|
|
8398
8487
|
},
|
|
8399
|
-
description:
|
|
8488
|
+
description: `Up to ${MAX_BATCH_CALLS} independent, batch-safe read calls.`
|
|
8400
8489
|
},
|
|
8401
8490
|
stop_on_error: {
|
|
8402
8491
|
type: "boolean",
|
|
@@ -8419,6 +8508,9 @@ var batchToolUseTool = {
|
|
|
8419
8508
|
stop_on_error: false
|
|
8420
8509
|
};
|
|
8421
8510
|
}
|
|
8511
|
+
if (input.calls.length > MAX_BATCH_CALLS) {
|
|
8512
|
+
throw new Error(`batch_tool_use: calls must contain at most ${MAX_BATCH_CALLS} items`);
|
|
8513
|
+
}
|
|
8422
8514
|
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
|
|
8423
8515
|
if (typeof governedExecute !== "function") {
|
|
8424
8516
|
return {
|
|
@@ -8438,8 +8530,11 @@ var batchToolUseTool = {
|
|
|
8438
8530
|
let succeeded = 0;
|
|
8439
8531
|
let failed = 0;
|
|
8440
8532
|
if (input.parallel !== false) {
|
|
8441
|
-
const
|
|
8442
|
-
|
|
8533
|
+
const allResults = await mapWithConcurrency(
|
|
8534
|
+
input.calls,
|
|
8535
|
+
MAX_PARALLEL_BATCH_CALLS,
|
|
8536
|
+
(call) => executeSingle(call, ctx, governedExecute)
|
|
8537
|
+
);
|
|
8443
8538
|
results.push(...allResults);
|
|
8444
8539
|
succeeded = allResults.filter((r) => r.success).length;
|
|
8445
8540
|
failed = allResults.filter((r) => !r.success).length;
|
|
@@ -8464,8 +8559,28 @@ var batchToolUseTool = {
|
|
|
8464
8559
|
};
|
|
8465
8560
|
}
|
|
8466
8561
|
};
|
|
8562
|
+
async function mapWithConcurrency(items, limit, run) {
|
|
8563
|
+
const results = new Array(items.length);
|
|
8564
|
+
let nextIndex = 0;
|
|
8565
|
+
const worker2 = async () => {
|
|
8566
|
+
while (nextIndex < items.length) {
|
|
8567
|
+
const index = nextIndex++;
|
|
8568
|
+
results[index] = await run(items[index]);
|
|
8569
|
+
}
|
|
8570
|
+
};
|
|
8571
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker2()));
|
|
8572
|
+
return results;
|
|
8573
|
+
}
|
|
8467
8574
|
async function executeSingle(call, ctx, governedExecute) {
|
|
8468
8575
|
const start = Date.now();
|
|
8576
|
+
if (call.tool === batchToolUseTool.name) {
|
|
8577
|
+
return {
|
|
8578
|
+
tool: call.tool,
|
|
8579
|
+
success: false,
|
|
8580
|
+
error: "recursive batch_tool_use execution is not allowed",
|
|
8581
|
+
executionMs: Date.now() - start
|
|
8582
|
+
};
|
|
8583
|
+
}
|
|
8469
8584
|
const tool = ctx.tools.find((candidate) => candidate.name === call.tool);
|
|
8470
8585
|
if (!tool) {
|
|
8471
8586
|
return {
|
|
@@ -8475,6 +8590,14 @@ async function executeSingle(call, ctx, governedExecute) {
|
|
|
8475
8590
|
executionMs: Date.now() - start
|
|
8476
8591
|
};
|
|
8477
8592
|
}
|
|
8593
|
+
if (!BATCHABLE_TOOLS.has(tool.name) || tool.mutating) {
|
|
8594
|
+
return {
|
|
8595
|
+
tool: call.tool,
|
|
8596
|
+
success: false,
|
|
8597
|
+
error: `tool "${call.tool}" is not eligible for batch execution; call it directly. Allowed: ${BATCHABLE_TOOL_NAMES.join(", ")}`,
|
|
8598
|
+
executionMs: Date.now() - start
|
|
8599
|
+
};
|
|
8600
|
+
}
|
|
8478
8601
|
try {
|
|
8479
8602
|
const result = await governedExecute(call.tool, call.input);
|
|
8480
8603
|
return {
|
|
@@ -8511,8 +8634,14 @@ import * as path10 from "node:path";
|
|
|
8511
8634
|
import { atomicWrite, ulid } from "@wrongstack/core/utils";
|
|
8512
8635
|
var ARTIFACT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
8513
8636
|
var sweptRoots = /* @__PURE__ */ new Set();
|
|
8637
|
+
var MAX_SWEPT_ROOTS = 128;
|
|
8514
8638
|
function sweepOldArtifacts(root) {
|
|
8515
8639
|
if (sweptRoots.has(root)) return;
|
|
8640
|
+
while (sweptRoots.size >= MAX_SWEPT_ROOTS) {
|
|
8641
|
+
const oldest = sweptRoots.values().next().value;
|
|
8642
|
+
if (oldest === void 0) break;
|
|
8643
|
+
sweptRoots.delete(oldest);
|
|
8644
|
+
}
|
|
8516
8645
|
sweptRoots.add(root);
|
|
8517
8646
|
void (async () => {
|
|
8518
8647
|
const cutoff = Date.now() - ARTIFACT_RETENTION_MS;
|
|
@@ -8535,8 +8664,8 @@ function sweepOldArtifacts(root) {
|
|
|
8535
8664
|
for (const name of names) {
|
|
8536
8665
|
const target = path10.join(dir, name);
|
|
8537
8666
|
try {
|
|
8538
|
-
const
|
|
8539
|
-
if (
|
|
8667
|
+
const stat18 = await fs7.stat(target);
|
|
8668
|
+
if (stat18.isFile() && stat18.mtimeMs < cutoff) {
|
|
8540
8669
|
await fs7.rm(target, { force: true });
|
|
8541
8670
|
removed++;
|
|
8542
8671
|
}
|
|
@@ -8564,14 +8693,14 @@ var BrowserArtifactStore = class {
|
|
|
8564
8693
|
}
|
|
8565
8694
|
async record(id, sessionId, kind, target, mimeType) {
|
|
8566
8695
|
await fs7.chmod(target, 384).catch(() => void 0);
|
|
8567
|
-
const [
|
|
8696
|
+
const [stat18, sha256] = await Promise.all([fs7.stat(target), hashFile(target)]);
|
|
8568
8697
|
const artifact = {
|
|
8569
8698
|
id,
|
|
8570
8699
|
kind,
|
|
8571
8700
|
sensitivity: "sensitive",
|
|
8572
8701
|
path: target,
|
|
8573
8702
|
mimeType,
|
|
8574
|
-
sizeBytes:
|
|
8703
|
+
sizeBytes: stat18.size,
|
|
8575
8704
|
sha256,
|
|
8576
8705
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8577
8706
|
};
|
|
@@ -8941,7 +9070,7 @@ var BrowserSessionManager = class {
|
|
|
8941
9070
|
constructor(options, launcher = defaultLauncher) {
|
|
8942
9071
|
this.launcher = launcher;
|
|
8943
9072
|
this.artifacts = new BrowserArtifactStore(options.artifactRoot);
|
|
8944
|
-
this.allowPrivateHosts = options.allowPrivateHosts ??
|
|
9073
|
+
this.allowPrivateHosts = options.allowPrivateHosts ?? false;
|
|
8945
9074
|
this.allowedPrivateOrigins = options.allowedPrivateOrigins ?? [];
|
|
8946
9075
|
this.networkProxy = new BrowserNetworkGuardProxy({
|
|
8947
9076
|
allowPrivateHosts: this.allowPrivateHosts,
|
|
@@ -9164,8 +9293,8 @@ var BrowserSessionManager = class {
|
|
|
9164
9293
|
if (realRelative.startsWith("..") || path11.isAbsolute(realRelative)) {
|
|
9165
9294
|
throw new Error("browser: upload files must not escape the project root through a symlink");
|
|
9166
9295
|
}
|
|
9167
|
-
const
|
|
9168
|
-
if (!
|
|
9296
|
+
const stat18 = await fs8.stat(realFile);
|
|
9297
|
+
if (!stat18.isFile()) throw new Error(`browser: upload target is not a file: ${file}`);
|
|
9169
9298
|
resolved.push(realFile);
|
|
9170
9299
|
}
|
|
9171
9300
|
await this.runPageOperation(
|
|
@@ -9514,6 +9643,9 @@ var browserScreenshotTool = {
|
|
|
9514
9643
|
description: "Capture a page or element PNG and return sensitive artifact metadata with integrity hash.",
|
|
9515
9644
|
usageHint: "browser_screenshot({ sessionId, fullPage?, selector? })",
|
|
9516
9645
|
permission: "confirm",
|
|
9646
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9647
|
+
// rule must distinguish clicking one control from another.
|
|
9648
|
+
subjectKey: "selector",
|
|
9517
9649
|
mutating: true,
|
|
9518
9650
|
riskTier: "safe",
|
|
9519
9651
|
capabilities: [ToolCapabilities.NET_OUTBOUND],
|
|
@@ -9543,6 +9675,9 @@ function selectorTool(name, description, run) {
|
|
|
9543
9675
|
description,
|
|
9544
9676
|
usageHint: `${name}({ sessionId, selector })`,
|
|
9545
9677
|
permission: "confirm",
|
|
9678
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9679
|
+
// rule must distinguish clicking one control from another.
|
|
9680
|
+
subjectKey: "selector",
|
|
9546
9681
|
mutating: true,
|
|
9547
9682
|
riskTier: "standard",
|
|
9548
9683
|
capabilities: [ToolCapabilities.NET_OUTBOUND],
|
|
@@ -9572,6 +9707,9 @@ var browserTypeTool = {
|
|
|
9572
9707
|
description: "Fill an input in an owned browser session. Use secretEnv instead of text for credentials so values stay out of tool arguments and session audit.",
|
|
9573
9708
|
usageHint: "browser_type({ sessionId, selector, text? | secretEnv? })",
|
|
9574
9709
|
permission: "confirm",
|
|
9710
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9711
|
+
// rule must distinguish clicking one control from another.
|
|
9712
|
+
subjectKey: "selector",
|
|
9575
9713
|
mutating: true,
|
|
9576
9714
|
riskTier: "standard",
|
|
9577
9715
|
capabilities: [ToolCapabilities.NET_OUTBOUND],
|
|
@@ -9606,6 +9744,9 @@ var browserSelectTool = {
|
|
|
9606
9744
|
description: "Select an option in an owned browser session.",
|
|
9607
9745
|
usageHint: "browser_select({ sessionId, selector, value })",
|
|
9608
9746
|
permission: "confirm",
|
|
9747
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9748
|
+
// rule must distinguish clicking one control from another.
|
|
9749
|
+
subjectKey: "selector",
|
|
9609
9750
|
mutating: true,
|
|
9610
9751
|
riskTier: "standard",
|
|
9611
9752
|
capabilities: [ToolCapabilities.NET_OUTBOUND],
|
|
@@ -9634,6 +9775,9 @@ var browserPressTool = {
|
|
|
9634
9775
|
description: "Press a keyboard key in an owned browser session.",
|
|
9635
9776
|
usageHint: "browser_press({ sessionId, key })",
|
|
9636
9777
|
permission: "confirm",
|
|
9778
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9779
|
+
// rule must distinguish clicking one control from another.
|
|
9780
|
+
subjectKey: "key",
|
|
9637
9781
|
mutating: true,
|
|
9638
9782
|
riskTier: "standard",
|
|
9639
9783
|
capabilities: [ToolCapabilities.NET_OUTBOUND],
|
|
@@ -9652,6 +9796,9 @@ var browserDragTool = {
|
|
|
9652
9796
|
description: "Drag one element to another in an owned browser session.",
|
|
9653
9797
|
usageHint: "browser_drag({ sessionId, from, to })",
|
|
9654
9798
|
permission: "confirm",
|
|
9799
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9800
|
+
// rule must distinguish clicking one control from another.
|
|
9801
|
+
subjectKey: "from",
|
|
9655
9802
|
mutating: true,
|
|
9656
9803
|
riskTier: "standard",
|
|
9657
9804
|
capabilities: [ToolCapabilities.NET_OUTBOUND],
|
|
@@ -9701,6 +9848,9 @@ var browserEvaluateTool = {
|
|
|
9701
9848
|
description: "Evaluate bounded JavaScript in the page. Requires confirmation because page code is arbitrary.",
|
|
9702
9849
|
usageHint: "browser_evaluate({ sessionId, expression })",
|
|
9703
9850
|
permission: "confirm",
|
|
9851
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9852
|
+
// rule must distinguish clicking one control from another.
|
|
9853
|
+
subjectKey: "expression",
|
|
9704
9854
|
mutating: true,
|
|
9705
9855
|
riskTier: "destructive",
|
|
9706
9856
|
capabilities: [ToolCapabilities.NET_OUTBOUND, ToolCapabilities.TOOL_MUTATE_ANY],
|
|
@@ -9723,6 +9873,9 @@ var browserUploadTool = {
|
|
|
9723
9873
|
description: "Upload project-local files through a file input in an owned browser session.",
|
|
9724
9874
|
usageHint: "browser_upload({ sessionId, selector, files })",
|
|
9725
9875
|
permission: "confirm",
|
|
9876
|
+
// WS-046: the element/expression acted on, not the sessionId — a trust
|
|
9877
|
+
// rule must distinguish clicking one control from another.
|
|
9878
|
+
subjectKey: "selector",
|
|
9726
9879
|
mutating: true,
|
|
9727
9880
|
riskTier: "destructive",
|
|
9728
9881
|
capabilities: [ToolCapabilities.NET_OUTBOUND, ToolCapabilities.FS_READ],
|
|
@@ -10658,6 +10811,7 @@ var REFS_INDEX_SQL = [
|
|
|
10658
10811
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
10659
10812
|
|
|
10660
10813
|
// src/codebase-index/writer-search-helpers.ts
|
|
10814
|
+
var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
|
|
10661
10815
|
function normalizeSearchLimit(limit) {
|
|
10662
10816
|
return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
|
|
10663
10817
|
}
|
|
@@ -10687,8 +10841,8 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
10687
10841
|
}
|
|
10688
10842
|
if (query.trim()) {
|
|
10689
10843
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
10690
|
-
conditions.push(`(${tokens.map(() => "text LIKE ?").join(" OR ")})`);
|
|
10691
|
-
for (const token of tokens) values.push(`%${token}%`);
|
|
10844
|
+
conditions.push(`(${tokens.map(() => "text LIKE ? ESCAPE '\\'").join(" OR ")})`);
|
|
10845
|
+
for (const token of tokens) values.push(`%${escapeLike(token)}%`);
|
|
10692
10846
|
}
|
|
10693
10847
|
return { where: conditions.length ? `WHERE ${conditions.join(" AND ")}` : "", values };
|
|
10694
10848
|
}
|
|
@@ -10797,6 +10951,7 @@ var StorePool = class {
|
|
|
10797
10951
|
|
|
10798
10952
|
// src/codebase-index/writer.ts
|
|
10799
10953
|
var DB_FILE2 = "index.db";
|
|
10954
|
+
var MAX_STATEMENT_CACHE = 128;
|
|
10800
10955
|
var IndexStore = class _IndexStore {
|
|
10801
10956
|
db;
|
|
10802
10957
|
/** Absolute path to this project's index directory. */
|
|
@@ -10834,12 +10989,31 @@ var IndexStore = class _IndexStore {
|
|
|
10834
10989
|
// Dirty on open so the first getOrBuildBm25() rebuilds against current rows;
|
|
10835
10990
|
// an empty or pre-existing corpus makes a stale IDF table meaningless.
|
|
10836
10991
|
bm25Dirty = true;
|
|
10837
|
-
/**
|
|
10992
|
+
/**
|
|
10993
|
+
* Prepare-once helper: compile `sql` on first use, reuse thereafter.
|
|
10994
|
+
*
|
|
10995
|
+
* Bounded LRU rather than an open Map. The cache is keyed by SQL TEXT, and
|
|
10996
|
+
* the fallback search builder emits one `text LIKE ?` clause per query token
|
|
10997
|
+
* — so the SQL varies with the token count and a stream of differently-sized
|
|
10998
|
+
* queries grew the cache without limit. Sage's store already bounds its
|
|
10999
|
+
* equivalent at 128 (WS-096).
|
|
11000
|
+
*
|
|
11001
|
+
* Re-inserting on a hit keeps the hot fixed-SQL statements (upsertFile,
|
|
11002
|
+
* insertRefs, …) at the young end, so a burst of one-off search SQL evicts
|
|
11003
|
+
* itself rather than the reindex hot path.
|
|
11004
|
+
*/
|
|
10838
11005
|
stmt(sql) {
|
|
10839
|
-
|
|
10840
|
-
if (
|
|
10841
|
-
|
|
10842
|
-
this.stmtCache.set(sql,
|
|
11006
|
+
const cached = this.stmtCache.get(sql);
|
|
11007
|
+
if (cached !== void 0) {
|
|
11008
|
+
this.stmtCache.delete(sql);
|
|
11009
|
+
this.stmtCache.set(sql, cached);
|
|
11010
|
+
return cached;
|
|
11011
|
+
}
|
|
11012
|
+
const s = this.db.prepare(sql);
|
|
11013
|
+
this.stmtCache.set(sql, s);
|
|
11014
|
+
if (this.stmtCache.size > MAX_STATEMENT_CACHE) {
|
|
11015
|
+
const oldest = this.stmtCache.keys().next();
|
|
11016
|
+
if (!oldest.done) this.stmtCache.delete(oldest.value);
|
|
10843
11017
|
}
|
|
10844
11018
|
return s;
|
|
10845
11019
|
}
|
|
@@ -11112,7 +11286,7 @@ var IndexStore = class _IndexStore {
|
|
|
11112
11286
|
const { where, values } = built;
|
|
11113
11287
|
const limit = normalizeSearchLimit(opts?.limit);
|
|
11114
11288
|
const limitSql = limit !== void 0 ? " LIMIT ?" : "";
|
|
11115
|
-
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment
|
|
11289
|
+
const sql = `SELECT id, lang, kind, name, file, line, col, signature, doc_comment FROM symbols ${where}${limitSql}`;
|
|
11116
11290
|
const binds = limit !== void 0 ? [...values, limit] : values;
|
|
11117
11291
|
const rows = this.stmt(sql).all(
|
|
11118
11292
|
...binds
|
|
@@ -11241,11 +11415,13 @@ var IndexStore = class _IndexStore {
|
|
|
11241
11415
|
/** Legacy ranked path: LIKE candidates + in-process BM25 + JS snippets. */
|
|
11242
11416
|
searchRankedFallback(query, filter, limit) {
|
|
11243
11417
|
if (!query.trim()) {
|
|
11244
|
-
const
|
|
11245
|
-
if (
|
|
11246
|
-
return { results: this.search(query, filter, { limit }), total };
|
|
11418
|
+
const total2 = this.countSearch(query, filter);
|
|
11419
|
+
if (total2 === 0) return { results: [], total: 0 };
|
|
11420
|
+
return { results: this.search(query, filter, { limit }), total: total2 };
|
|
11247
11421
|
}
|
|
11248
|
-
const
|
|
11422
|
+
const total = this.countSearch(query, filter);
|
|
11423
|
+
if (total === 0) return { results: [], total: 0 };
|
|
11424
|
+
const candidates = this.search(query, filter, { limit: SEARCH_CANDIDATE_SCAN_CAP });
|
|
11249
11425
|
if (candidates.length === 0) return { results: [], total: 0 };
|
|
11250
11426
|
const candidateById = new Map(candidates.map((c) => [c.id, c]));
|
|
11251
11427
|
const bm25 = this.getOrBuildBm25();
|
|
@@ -11271,7 +11447,7 @@ var IndexStore = class _IndexStore {
|
|
|
11271
11447
|
const c = expectDefined2(candidateById.get(id));
|
|
11272
11448
|
return { ...c, score, snippet: bm25.extractSnippet(id, qTokens) };
|
|
11273
11449
|
});
|
|
11274
|
-
return { results, total
|
|
11450
|
+
return { results, total };
|
|
11275
11451
|
}
|
|
11276
11452
|
getAllIndexable() {
|
|
11277
11453
|
return getAllIndexableWithStatement((sql) => this.stmt(sql));
|
|
@@ -11690,12 +11866,12 @@ var buildIdCache;
|
|
|
11690
11866
|
function projectIndexServerBuildId(entrypoint) {
|
|
11691
11867
|
const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path16.resolve(entrypoint);
|
|
11692
11868
|
try {
|
|
11693
|
-
const
|
|
11694
|
-
if (buildIdCache?.file === file && buildIdCache.mtimeMs ===
|
|
11869
|
+
const stat18 = fs11.statSync(file);
|
|
11870
|
+
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat18.mtimeMs && buildIdCache.size === stat18.size) {
|
|
11695
11871
|
return buildIdCache.buildId;
|
|
11696
11872
|
}
|
|
11697
11873
|
const buildId = createHash4("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
|
|
11698
|
-
buildIdCache = { file, mtimeMs:
|
|
11874
|
+
buildIdCache = { file, mtimeMs: stat18.mtimeMs, size: stat18.size, buildId };
|
|
11699
11875
|
return buildId;
|
|
11700
11876
|
} catch {
|
|
11701
11877
|
return `unreadable:${path16.basename(file)}`;
|
|
@@ -11865,6 +12041,8 @@ var ProjectServerConnection = class {
|
|
|
11865
12041
|
connectResolve = null;
|
|
11866
12042
|
connectReject = null;
|
|
11867
12043
|
nextId = 1;
|
|
12044
|
+
/** WS-027: read from the owner-only metadata file — see currentAuthToken. */
|
|
12045
|
+
authToken;
|
|
11868
12046
|
pending = /* @__PURE__ */ new Map();
|
|
11869
12047
|
transition(status, options = {}) {
|
|
11870
12048
|
const previous = connectionStates.get(this.endpoint);
|
|
@@ -12001,6 +12179,31 @@ var ProjectServerConnection = class {
|
|
|
12001
12179
|
this.transition("offline");
|
|
12002
12180
|
maybeStopHeartbeatLoop();
|
|
12003
12181
|
}
|
|
12182
|
+
/**
|
|
12183
|
+
* WS-027: the per-process token, read from the daemon's owner-only metadata
|
|
12184
|
+
* file — never from the `hello` frame, which the daemon sends to every
|
|
12185
|
+
* socket that connects (the mistake WS-028 found in the SAGE daemon).
|
|
12186
|
+
*
|
|
12187
|
+
* Read lazily and re-read while unknown: the daemon starts listening before
|
|
12188
|
+
* it writes metadata (endpoint ownership has to be won first), and a
|
|
12189
|
+
* respawned daemon mints a new token, so a cached wrong one would be sticky.
|
|
12190
|
+
*/
|
|
12191
|
+
currentAuthToken() {
|
|
12192
|
+
if (this.authToken === void 0) {
|
|
12193
|
+
try {
|
|
12194
|
+
const raw = fs12.readFileSync(
|
|
12195
|
+
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
12196
|
+
"utf8"
|
|
12197
|
+
);
|
|
12198
|
+
const parsed = JSON.parse(raw);
|
|
12199
|
+
if (typeof parsed.authToken === "string" && parsed.authToken.length > 0) {
|
|
12200
|
+
this.authToken = parsed.authToken;
|
|
12201
|
+
}
|
|
12202
|
+
} catch {
|
|
12203
|
+
}
|
|
12204
|
+
}
|
|
12205
|
+
return this.authToken;
|
|
12206
|
+
}
|
|
12004
12207
|
request(message, options) {
|
|
12005
12208
|
const socket = this.socket;
|
|
12006
12209
|
if (!socket || socket.destroyed) {
|
|
@@ -12044,7 +12247,7 @@ var ProjectServerConnection = class {
|
|
|
12044
12247
|
return;
|
|
12045
12248
|
}
|
|
12046
12249
|
}
|
|
12047
|
-
this.write({ ...message, id });
|
|
12250
|
+
this.write({ ...message, id, authToken: this.currentAuthToken() });
|
|
12048
12251
|
});
|
|
12049
12252
|
}
|
|
12050
12253
|
async ensureConnected(spawnIfMissing) {
|
|
@@ -12091,6 +12294,7 @@ var ProjectServerConnection = class {
|
|
|
12091
12294
|
connectOnce() {
|
|
12092
12295
|
this.socket?.destroy();
|
|
12093
12296
|
this.socket = null;
|
|
12297
|
+
this.authToken = void 0;
|
|
12094
12298
|
this.info = null;
|
|
12095
12299
|
this.activity = null;
|
|
12096
12300
|
this.health = null;
|
|
@@ -12700,9 +12904,9 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12700
12904
|
const statReadParse = await Promise.allSettled(
|
|
12701
12905
|
batchFiles.map(
|
|
12702
12906
|
async (file) => {
|
|
12703
|
-
let
|
|
12907
|
+
let stat18;
|
|
12704
12908
|
try {
|
|
12705
|
-
|
|
12909
|
+
stat18 = await fs17.stat(file, statOpts);
|
|
12706
12910
|
} catch (e) {
|
|
12707
12911
|
if (isAbortError(e)) throw e;
|
|
12708
12912
|
return {
|
|
@@ -12714,21 +12918,21 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12714
12918
|
missing: isMissingPathError(e)
|
|
12715
12919
|
};
|
|
12716
12920
|
}
|
|
12717
|
-
if (!
|
|
12921
|
+
if (!stat18.isFile()) return { file, stat: stat18, lang: "", parsed: null };
|
|
12718
12922
|
const lang = detectLang(file);
|
|
12719
|
-
if (!lang) return { file, stat:
|
|
12720
|
-
if (
|
|
12923
|
+
if (!lang) return { file, stat: stat18, lang: "", parsed: null };
|
|
12924
|
+
if (stat18.size > MAX_INDEX_FILE_BYTES) {
|
|
12721
12925
|
return {
|
|
12722
12926
|
file,
|
|
12723
|
-
stat:
|
|
12927
|
+
stat: stat18,
|
|
12724
12928
|
lang,
|
|
12725
12929
|
parsed: null,
|
|
12726
|
-
error: `file too large (${
|
|
12930
|
+
error: `file too large (${stat18.size} bytes; max ${MAX_INDEX_FILE_BYTES})`
|
|
12727
12931
|
};
|
|
12728
12932
|
}
|
|
12729
12933
|
const meta = existingMeta.get(file);
|
|
12730
|
-
if (!force && meta && meta.mtimeMs === Math.floor(
|
|
12731
|
-
return { file, stat:
|
|
12934
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat18.mtimeMs)) {
|
|
12935
|
+
return { file, stat: stat18, lang, parsed: null, skippedMeta: meta };
|
|
12732
12936
|
}
|
|
12733
12937
|
let content;
|
|
12734
12938
|
try {
|
|
@@ -12737,7 +12941,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12737
12941
|
if (isAbortError(e)) throw e;
|
|
12738
12942
|
return {
|
|
12739
12943
|
file,
|
|
12740
|
-
stat:
|
|
12944
|
+
stat: stat18,
|
|
12741
12945
|
lang,
|
|
12742
12946
|
parsed: null,
|
|
12743
12947
|
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
@@ -12749,13 +12953,13 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12749
12953
|
} catch (e) {
|
|
12750
12954
|
return {
|
|
12751
12955
|
file,
|
|
12752
|
-
stat:
|
|
12956
|
+
stat: stat18,
|
|
12753
12957
|
lang,
|
|
12754
12958
|
parsed: null,
|
|
12755
12959
|
error: `parse error: ${e instanceof Error ? e.message : String(e)}`
|
|
12756
12960
|
};
|
|
12757
12961
|
}
|
|
12758
|
-
return { file, stat:
|
|
12962
|
+
return { file, stat: stat18, lang, parsed, content };
|
|
12759
12963
|
}
|
|
12760
12964
|
)
|
|
12761
12965
|
);
|
|
@@ -12776,7 +12980,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12776
12980
|
errors.push(`${file}: ${result.error}`);
|
|
12777
12981
|
continue;
|
|
12778
12982
|
}
|
|
12779
|
-
const { stat:
|
|
12983
|
+
const { stat: stat18, lang, parsed } = result;
|
|
12780
12984
|
if (result.skippedMeta) {
|
|
12781
12985
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
12782
12986
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
@@ -12788,7 +12992,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12788
12992
|
store.upsertFile({
|
|
12789
12993
|
file,
|
|
12790
12994
|
lang,
|
|
12791
|
-
mtimeMs: Math.floor(
|
|
12995
|
+
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
12792
12996
|
symbolCount: 0,
|
|
12793
12997
|
lastIndexed: Date.now()
|
|
12794
12998
|
});
|
|
@@ -12800,7 +13004,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12800
13004
|
store.replaceEmptyFile({
|
|
12801
13005
|
file,
|
|
12802
13006
|
lang,
|
|
12803
|
-
mtimeMs: Math.floor(
|
|
13007
|
+
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
12804
13008
|
symbolCount: 0,
|
|
12805
13009
|
lastIndexed: Date.now()
|
|
12806
13010
|
});
|
|
@@ -12812,7 +13016,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
12812
13016
|
lang,
|
|
12813
13017
|
symbols: parsed.symbols,
|
|
12814
13018
|
refs: parsed.refs ?? [],
|
|
12815
|
-
mtimeMs: Math.floor(
|
|
13019
|
+
mtimeMs: Math.floor(stat18.mtimeMs),
|
|
12816
13020
|
symbolCount: parsed.symbols.length
|
|
12817
13021
|
});
|
|
12818
13022
|
deleteForFiles.push(file);
|
|
@@ -13865,8 +14069,9 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
13865
14069
|
const content = fs19.readFileSync(epFile, "utf8");
|
|
13866
14070
|
const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
|
|
13867
14071
|
const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
|
|
13868
|
-
|
|
13869
|
-
|
|
14072
|
+
for (; ; ) {
|
|
14073
|
+
const match = reExportRe.exec(strippedContent);
|
|
14074
|
+
if (match === null) break;
|
|
13870
14075
|
const moduleSpec = match[1];
|
|
13871
14076
|
const resolvedFiles = resolveModulePath(epFile, moduleSpec, indexedFiles);
|
|
13872
14077
|
for (const rf of resolvedFiles) {
|
|
@@ -14072,6 +14277,9 @@ var designTool = {
|
|
|
14072
14277
|
description: 'Browse, load, customize, and enforce curated frontend/mobile UI design kits. Use BEFORE writing UI code to commit to one coherent, modern, responsive, dark/light, accessible design. Actions: "list" (menu), "use" (load+pin a kit for a stack), "foundations" (baseline), "set" (override kit colors/tokens), "materialize" (write the tokens to a real theme file \u2014 CSS @theme/OKLCH or native), "verify" (scan UI files for off-palette colors).',
|
|
14073
14278
|
usageHint: 'Flow: `design {action:"use", kit:"minimal-clarity", stack:"web"}` \u2192 optionally `design {action:"set", set:{primary:"oklch(62% 0.2 25)"}}` \u2192 `design {action:"materialize"}` to write tokens to disk \u2192 implement against them \u2192 `design {action:"verify"}`.',
|
|
14074
14279
|
permission: "confirm",
|
|
14280
|
+
// WS-046: gives permission decisions something to key on.
|
|
14281
|
+
// The action performed; `out` is optional so it cannot be the subject.
|
|
14282
|
+
subjectKey: "action",
|
|
14075
14283
|
mutating: true,
|
|
14076
14284
|
capabilities: ["fs.write"],
|
|
14077
14285
|
timeoutMs: 15e3,
|
|
@@ -14252,9 +14460,7 @@ Run \`design {action:"materialize"}\` to write the tuned tokens to your theme fi
|
|
|
14252
14460
|
const abs = path25.join(absParent, path25.basename(absResolved));
|
|
14253
14461
|
const rel = path25.relative(root, abs);
|
|
14254
14462
|
if (rel.startsWith("..") || path25.isAbsolute(rel)) {
|
|
14255
|
-
throw new Error(
|
|
14256
|
-
`design: materialize path "${result.path}" would escape the project root`
|
|
14257
|
-
);
|
|
14463
|
+
throw new Error(`design: materialize path "${result.path}" would escape the project root`);
|
|
14258
14464
|
}
|
|
14259
14465
|
let exists = false;
|
|
14260
14466
|
try {
|
|
@@ -14329,6 +14535,7 @@ import { statSync as statSync3 } from "node:fs";
|
|
|
14329
14535
|
import * as fs21 from "node:fs/promises";
|
|
14330
14536
|
import * as path26 from "node:path";
|
|
14331
14537
|
import { buildChildEnv as buildChildEnv3 } from "@wrongstack/core/utils";
|
|
14538
|
+
var MAX_FILE_DUMP_BYTES = 5 * 1024 * 1024;
|
|
14332
14539
|
var diffTool = {
|
|
14333
14540
|
name: "diff",
|
|
14334
14541
|
category: "Filesystem",
|
|
@@ -14336,6 +14543,7 @@ var diffTool = {
|
|
|
14336
14543
|
usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192 show file content with line numbers (NOT a unified diff; no +/- prefixes).\n- `a` and/or `b` \u2192 git-style commit/branch diff (unified format, real +/- prefixes).\n- `staged: true` \u2192 only show staged changes.\n- `mode` can be "unified", "stat", or "side-by-side" (only affects the git-diff path).\n\nNOTE: For a true file-vs-file unified diff, supply `a` and `b` so the tool delegates to `git diff`. The `files`-only path is a line-numbered dump, not a diff.\n\nThis tool has important safety guards against flag injection (see previous security findings).',
|
|
14337
14544
|
permission: "auto",
|
|
14338
14545
|
mutating: false,
|
|
14546
|
+
maxOutputBytes: 262144,
|
|
14339
14547
|
capabilities: ["fs.read"],
|
|
14340
14548
|
icon: "diff",
|
|
14341
14549
|
timeoutMs: 1e4,
|
|
@@ -14411,8 +14619,8 @@ function findGitDir(cwd) {
|
|
|
14411
14619
|
let dir = cwd;
|
|
14412
14620
|
for (let i = 0; i < 20; i++) {
|
|
14413
14621
|
try {
|
|
14414
|
-
const
|
|
14415
|
-
if (
|
|
14622
|
+
const stat18 = statSync3(path26.join(dir, ".git"));
|
|
14623
|
+
if (stat18.isDirectory()) return dir;
|
|
14416
14624
|
} catch {
|
|
14417
14625
|
}
|
|
14418
14626
|
const parent = path26.dirname(dir);
|
|
@@ -14454,10 +14662,18 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
14454
14662
|
};
|
|
14455
14663
|
}
|
|
14456
14664
|
const results = [];
|
|
14665
|
+
let truncated = false;
|
|
14457
14666
|
for (const file of files) {
|
|
14458
14667
|
const absPath = safeResolve(file, ctx);
|
|
14459
|
-
const
|
|
14460
|
-
if (!
|
|
14668
|
+
const stat18 = await fs21.stat(absPath).catch(() => null);
|
|
14669
|
+
if (!stat18?.isFile()) continue;
|
|
14670
|
+
if (stat18.size > MAX_FILE_DUMP_BYTES) {
|
|
14671
|
+
truncated = true;
|
|
14672
|
+
results.push(
|
|
14673
|
+
`--- ${file} (skipped: ${stat18.size} bytes exceeds the ${MAX_FILE_DUMP_BYTES} limit; use the read tool with offset/limit) ---`
|
|
14674
|
+
);
|
|
14675
|
+
continue;
|
|
14676
|
+
}
|
|
14461
14677
|
const content = await fs21.readFile(absPath, "utf8");
|
|
14462
14678
|
const lines = content.split(/\r?\n/);
|
|
14463
14679
|
results.push(formatWithLineNumbers(file, lines));
|
|
@@ -14465,7 +14681,7 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
14465
14681
|
return {
|
|
14466
14682
|
diff: results.join("\n\n"),
|
|
14467
14683
|
files,
|
|
14468
|
-
truncated
|
|
14684
|
+
truncated,
|
|
14469
14685
|
mode: input.mode ?? "unified"
|
|
14470
14686
|
};
|
|
14471
14687
|
}
|
|
@@ -14558,8 +14774,8 @@ async function resolveFiles(filesInput, cwd) {
|
|
|
14558
14774
|
for (const f of files) {
|
|
14559
14775
|
const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
|
|
14560
14776
|
try {
|
|
14561
|
-
const
|
|
14562
|
-
if (
|
|
14777
|
+
const stat18 = await fs22.stat(absPath);
|
|
14778
|
+
if (stat18.isFile()) resolved.push(absPath);
|
|
14563
14779
|
} catch {
|
|
14564
14780
|
}
|
|
14565
14781
|
}
|
|
@@ -15412,7 +15628,7 @@ var editTool = {
|
|
|
15412
15628
|
});
|
|
15413
15629
|
}
|
|
15414
15630
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
15415
|
-
const
|
|
15631
|
+
const stat18 = await fs23.stat(absPath).catch((err) => {
|
|
15416
15632
|
if (err.code === "ENOENT") {
|
|
15417
15633
|
throw new ToolValidationError({
|
|
15418
15634
|
message: `edit: file "${input.path}" does not exist. Use \`write\` instead.`,
|
|
@@ -15422,7 +15638,7 @@ var editTool = {
|
|
|
15422
15638
|
}
|
|
15423
15639
|
throw err;
|
|
15424
15640
|
});
|
|
15425
|
-
if (!
|
|
15641
|
+
if (!stat18.isFile()) {
|
|
15426
15642
|
throw new ToolValidationError({
|
|
15427
15643
|
message: `edit: "${input.path}" is not a regular file`,
|
|
15428
15644
|
field: "path"
|
|
@@ -15452,7 +15668,7 @@ var editTool = {
|
|
|
15452
15668
|
});
|
|
15453
15669
|
}
|
|
15454
15670
|
}
|
|
15455
|
-
if (autoRead && updated.mtimeMs >
|
|
15671
|
+
if (autoRead && updated.mtimeMs > stat18.mtimeMs + mtimeTolerance) {
|
|
15456
15672
|
throw new ToolValidationError({
|
|
15457
15673
|
message: `edit: file "${input.path}" changed while being auto-read. Retry the edit.`,
|
|
15458
15674
|
field: "path",
|
|
@@ -15575,10 +15791,6 @@ import {
|
|
|
15575
15791
|
emitProcessStarted as emitProcessStarted3
|
|
15576
15792
|
} from "@wrongstack/core/observability";
|
|
15577
15793
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils/error";
|
|
15578
|
-
init_output_spool();
|
|
15579
|
-
init_util();
|
|
15580
|
-
init_process_registry();
|
|
15581
|
-
init_win32_resolve();
|
|
15582
15794
|
|
|
15583
15795
|
// src/_danger-detect.ts
|
|
15584
15796
|
var argHas = (args, value) => args.includes(value);
|
|
@@ -15861,6 +16073,11 @@ function levelRank(level) {
|
|
|
15861
16073
|
}
|
|
15862
16074
|
}
|
|
15863
16075
|
|
|
16076
|
+
// src/exec.ts
|
|
16077
|
+
init_output_spool();
|
|
16078
|
+
init_util();
|
|
16079
|
+
init_win32_resolve();
|
|
16080
|
+
|
|
15864
16081
|
// src/exec-kill-guard.ts
|
|
15865
16082
|
import * as os8 from "node:os";
|
|
15866
16083
|
import * as path29 from "node:path";
|
|
@@ -16097,6 +16314,7 @@ async function checkKillTarget(target) {
|
|
|
16097
16314
|
}
|
|
16098
16315
|
|
|
16099
16316
|
// src/exec.ts
|
|
16317
|
+
init_process_registry();
|
|
16100
16318
|
var isWin4 = process.platform === "win32";
|
|
16101
16319
|
var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
16102
16320
|
// JS / TS toolchain
|
|
@@ -16733,7 +16951,17 @@ var BLOCKED_ARG_PATTERNS = {
|
|
|
16733
16951
|
bun: [],
|
|
16734
16952
|
docker: [],
|
|
16735
16953
|
// find -exec/-ok/-execdir execute arbitrary commands
|
|
16736
|
-
find: [
|
|
16954
|
+
find: [
|
|
16955
|
+
/^-exec$/,
|
|
16956
|
+
/^-exec;$/,
|
|
16957
|
+
/^-ok$/,
|
|
16958
|
+
/^-ok;$/,
|
|
16959
|
+
/^-execdir$/,
|
|
16960
|
+
/^-execdir;$/,
|
|
16961
|
+
/^-exec=/,
|
|
16962
|
+
/^-ok=/,
|
|
16963
|
+
/^-execdir=/
|
|
16964
|
+
],
|
|
16737
16965
|
// rm -rf / is catastrophic — block absolute paths, home, dot-dirs,
|
|
16738
16966
|
// and glob patterns that could expand to dangerous targets.
|
|
16739
16967
|
// `rm -rf ./src/*` expands to project files; `rm -rf ../../` escapes upward;
|
|
@@ -16804,6 +17032,14 @@ var execTool = {
|
|
|
16804
17032
|
useInstead: ["bash"]
|
|
16805
17033
|
},
|
|
16806
17034
|
permission: "confirm",
|
|
17035
|
+
// WS-046: without this, every exec call collapsed onto the bare tool name.
|
|
17036
|
+
// Three consequences followed: "always allow" stored a rule that could never
|
|
17037
|
+
// match, pressing "no" once blocked ALL exec for the session, and the
|
|
17038
|
+
// permission cache keyed one decision for every command. `subjectKey:
|
|
17039
|
+
// 'command'` renders the FULL invocation (`command` + `args`) — using the
|
|
17040
|
+
// program alone would make `exec git status` and `exec git push --force` the
|
|
17041
|
+
// same subject, so trusting one would silently authorize the other.
|
|
17042
|
+
subjectKey: "command",
|
|
16807
17043
|
mutating: true,
|
|
16808
17044
|
riskTier: "standard",
|
|
16809
17045
|
timeoutMs: DEFAULT_TIMEOUT_MS3,
|
|
@@ -17020,7 +17256,14 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
|
|
|
17020
17256
|
const pid = child.pid;
|
|
17021
17257
|
if (typeof pid === "number") {
|
|
17022
17258
|
const fullCommand = `${cmd} ${args.join(" ")}`;
|
|
17023
|
-
registry.register({
|
|
17259
|
+
registry.register({
|
|
17260
|
+
pid,
|
|
17261
|
+
name: "exec",
|
|
17262
|
+
command: redactCommand(fullCommand),
|
|
17263
|
+
startedAt: Date.now(),
|
|
17264
|
+
sessionId,
|
|
17265
|
+
child
|
|
17266
|
+
});
|
|
17024
17267
|
}
|
|
17025
17268
|
const timer = setTimeout(() => {
|
|
17026
17269
|
killed = true;
|
|
@@ -17460,6 +17703,9 @@ var formatTool = {
|
|
|
17460
17703
|
description: "Format source files according to project style (Biome). Can also run in check-only mode.",
|
|
17461
17704
|
usageHint: "RUN REGULARLY:\n\n- Use on changed files before committing.\n- `check: true` verifies formatting without making changes (useful in CI-like flows).\nThis project has very consistent formatting expectations. Always ensure your changes are formatted.",
|
|
17462
17705
|
permission: "confirm",
|
|
17706
|
+
// WS-046: gives permission decisions something to key on.
|
|
17707
|
+
// The files being rewritten are the subject; the fixer is how, not what.
|
|
17708
|
+
subjectKey: "files",
|
|
17463
17709
|
mutating: true,
|
|
17464
17710
|
capabilities: ["fs.write", "shell.restricted"],
|
|
17465
17711
|
icon: "code",
|
|
@@ -17564,13 +17810,13 @@ var formatTool = {
|
|
|
17564
17810
|
}
|
|
17565
17811
|
};
|
|
17566
17812
|
async function detectFixer(cwd) {
|
|
17567
|
-
const { stat:
|
|
17813
|
+
const { stat: stat18 } = await import("node:fs/promises");
|
|
17568
17814
|
try {
|
|
17569
|
-
await
|
|
17815
|
+
await stat18(`${cwd}/biome.json`);
|
|
17570
17816
|
return "biome";
|
|
17571
17817
|
} catch {
|
|
17572
17818
|
try {
|
|
17573
|
-
await
|
|
17819
|
+
await stat18(`${cwd}/.prettierrc`);
|
|
17574
17820
|
return "prettier";
|
|
17575
17821
|
} catch {
|
|
17576
17822
|
return "biome";
|
|
@@ -17597,6 +17843,10 @@ var gitTool = {
|
|
|
17597
17843
|
// Conservative: any of these may mutate. The non-mutating commands
|
|
17598
17844
|
// (status/log/diff/branch/fetch) are still gated on `permission: 'confirm'`
|
|
17599
17845
|
// and `MUTATING_SUBCOMMANDS` is consulted at runtime for per-call checks.
|
|
17846
|
+
// WS-046: gives permission decisions something to key on.
|
|
17847
|
+
// The git subcommand (status/commit/push) is what a trust rule needs to
|
|
17848
|
+
// distinguish; `git status` and `git push --force` must not share a subject.
|
|
17849
|
+
subjectKey: "command",
|
|
17600
17850
|
mutating: true,
|
|
17601
17851
|
capabilities: ["fs.write", "shell.restricted"],
|
|
17602
17852
|
timeoutMs: TIMEOUT_MS2,
|
|
@@ -17665,6 +17915,8 @@ var gitTool = {
|
|
|
17665
17915
|
truncated: false
|
|
17666
17916
|
};
|
|
17667
17917
|
}
|
|
17918
|
+
const branchGuard = validateBranchInput(input);
|
|
17919
|
+
if (branchGuard) return branchGuard;
|
|
17668
17920
|
if (input.command === "worktree") {
|
|
17669
17921
|
const guard = validateWorktreeInput(input, ctx.projectRoot);
|
|
17670
17922
|
if (guard) return guard;
|
|
@@ -17713,6 +17965,18 @@ var gitTool = {
|
|
|
17713
17965
|
return result;
|
|
17714
17966
|
}
|
|
17715
17967
|
};
|
|
17968
|
+
function validateBranchInput(input) {
|
|
17969
|
+
const branch = input.branch;
|
|
17970
|
+
if (branch === void 0) return null;
|
|
17971
|
+
if (!branch.startsWith("-") && !branch.includes(" --")) return null;
|
|
17972
|
+
return {
|
|
17973
|
+
command: input.command,
|
|
17974
|
+
stdout: "",
|
|
17975
|
+
stderr: `unsafe branch name (parsed as a git option): ${branch}`,
|
|
17976
|
+
exitCode: 1,
|
|
17977
|
+
truncated: false
|
|
17978
|
+
};
|
|
17979
|
+
}
|
|
17716
17980
|
function validateWorktreeInput(input, projectRoot) {
|
|
17717
17981
|
const reject = (stderr) => ({
|
|
17718
17982
|
command: "worktree",
|
|
@@ -17721,7 +17985,6 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
17721
17985
|
exitCode: 1,
|
|
17722
17986
|
truncated: false
|
|
17723
17987
|
});
|
|
17724
|
-
if (input.branch?.startsWith("-")) return reject(`unsafe branch name: ${input.branch}`);
|
|
17725
17988
|
if (input.worktreePath?.startsWith("-")) {
|
|
17726
17989
|
return reject(`unsafe worktree path: ${input.worktreePath}`);
|
|
17727
17990
|
}
|
|
@@ -17739,8 +18002,8 @@ function findGitDir2(cwd, projectRoot) {
|
|
|
17739
18002
|
let dir = cwd;
|
|
17740
18003
|
for (let i = 0; i < 20; i++) {
|
|
17741
18004
|
try {
|
|
17742
|
-
const
|
|
17743
|
-
if (
|
|
18005
|
+
const stat18 = statSync4(`${dir}/.git`);
|
|
18006
|
+
if (stat18.isDirectory() || stat18.isFile()) return dir;
|
|
17744
18007
|
} catch {
|
|
17745
18008
|
}
|
|
17746
18009
|
if (dir === root) break;
|
|
@@ -17775,7 +18038,10 @@ function buildArgs(input) {
|
|
|
17775
18038
|
...files.length ? ["--", ...files] : []
|
|
17776
18039
|
];
|
|
17777
18040
|
case "branch":
|
|
17778
|
-
return input.branch ? [
|
|
18041
|
+
return input.branch ? [
|
|
18042
|
+
"branch",
|
|
18043
|
+
...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]
|
|
18044
|
+
] : ["branch"];
|
|
17779
18045
|
case "checkout":
|
|
17780
18046
|
return [
|
|
17781
18047
|
"checkout",
|
|
@@ -17868,7 +18134,7 @@ import * as path30 from "node:path";
|
|
|
17868
18134
|
import { compileGlob as compileGlob2, DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS2 } from "@wrongstack/core/utils";
|
|
17869
18135
|
|
|
17870
18136
|
// src/_concurrency.ts
|
|
17871
|
-
async function
|
|
18137
|
+
async function mapWithConcurrency2(items, limit, fn) {
|
|
17872
18138
|
if (items.length === 0) return [];
|
|
17873
18139
|
const effectiveLimit = Math.max(1, Math.min(limit | 0, items.length));
|
|
17874
18140
|
const results = new Array(items.length);
|
|
@@ -18003,10 +18269,10 @@ var globTool = {
|
|
|
18003
18269
|
}
|
|
18004
18270
|
if (truncated) return;
|
|
18005
18271
|
}
|
|
18006
|
-
await
|
|
18272
|
+
await mapWithConcurrency2(matchedFiles, WALK_CONCURRENCY, pushResult);
|
|
18007
18273
|
if (truncated) return;
|
|
18008
18274
|
const remainingSubdirs = truncated ? [] : subdirs;
|
|
18009
|
-
await
|
|
18275
|
+
await mapWithConcurrency2(remainingSubdirs, WALK_CONCURRENCY, ({ full, rel }) => walk(full, rel));
|
|
18010
18276
|
};
|
|
18011
18277
|
await walk(base, "");
|
|
18012
18278
|
results.sort((a, b) => b.mtime - a.mtime);
|
|
@@ -18015,12 +18281,16 @@ var globTool = {
|
|
|
18015
18281
|
};
|
|
18016
18282
|
|
|
18017
18283
|
// src/grep.ts
|
|
18018
|
-
import { expectDefined as expectDefined7 } from "@wrongstack/core/utils";
|
|
18019
18284
|
import { spawn as spawn11 } from "node:child_process";
|
|
18020
18285
|
import * as fs25 from "node:fs/promises";
|
|
18021
18286
|
import * as path31 from "node:path";
|
|
18022
18287
|
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
18023
|
-
import {
|
|
18288
|
+
import {
|
|
18289
|
+
buildChildEnv as buildChildEnv5,
|
|
18290
|
+
compileGlob as compileGlob3,
|
|
18291
|
+
DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS3,
|
|
18292
|
+
expectDefined as expectDefined7
|
|
18293
|
+
} from "@wrongstack/core/utils";
|
|
18024
18294
|
|
|
18025
18295
|
// src/_regex.ts
|
|
18026
18296
|
var MAX_PATTERN_LEN = 256;
|
|
@@ -18073,6 +18343,8 @@ var DEFAULT_IGNORE3 = DEFAULT_WALK_IGNORE_DIRS3;
|
|
|
18073
18343
|
var NATIVE_SCAN_CONCURRENCY = 32;
|
|
18074
18344
|
var NATIVE_READ_CHUNK_BYTES = 64 * 1024;
|
|
18075
18345
|
var NATIVE_MAX_FILE_BYTES = 1e6;
|
|
18346
|
+
var RG_MAX_QUEUE_CHUNKS = 128;
|
|
18347
|
+
var RG_MAX_QUEUE_CHARS = 8 * 1024 * 1024;
|
|
18076
18348
|
var grepTool = {
|
|
18077
18349
|
name: "grep",
|
|
18078
18350
|
category: "Search",
|
|
@@ -18200,9 +18472,19 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
18200
18472
|
const FLUSH_AT = 16;
|
|
18201
18473
|
const MAX_BUF_BYTES = 1e6;
|
|
18202
18474
|
let bufOverflow = false;
|
|
18203
|
-
const child = spawn11("rg", args, {
|
|
18475
|
+
const child = spawn11("rg", args, {
|
|
18476
|
+
signal,
|
|
18477
|
+
env: buildChildEnv5(),
|
|
18478
|
+
// rg diagnostics are not part of the tool result. Ignoring stderr avoids
|
|
18479
|
+
// an unread pipe filling up and pinning the child when rg reports many
|
|
18480
|
+
// filesystem errors.
|
|
18481
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18482
|
+
windowsHide: true
|
|
18483
|
+
});
|
|
18204
18484
|
const queue = [];
|
|
18485
|
+
let queuedChars = 0;
|
|
18205
18486
|
let waiter;
|
|
18487
|
+
let paused = false;
|
|
18206
18488
|
const wake = () => {
|
|
18207
18489
|
if (waiter) {
|
|
18208
18490
|
const w = waiter;
|
|
@@ -18210,93 +18492,133 @@ async function* runRgStream(input, base, mode, limit, signal) {
|
|
|
18210
18492
|
w();
|
|
18211
18493
|
}
|
|
18212
18494
|
};
|
|
18213
|
-
|
|
18214
|
-
queue.
|
|
18495
|
+
const pauseIfFlooded = () => {
|
|
18496
|
+
if (paused || queue.length < RG_MAX_QUEUE_CHUNKS && queuedChars < RG_MAX_QUEUE_CHARS)
|
|
18497
|
+
return;
|
|
18498
|
+
paused = true;
|
|
18499
|
+
child.stdout?.pause();
|
|
18500
|
+
};
|
|
18501
|
+
const resumeIfDrained = () => {
|
|
18502
|
+
if (!paused || queue.length >= RG_MAX_QUEUE_CHUNKS || queuedChars >= RG_MAX_QUEUE_CHARS)
|
|
18503
|
+
return;
|
|
18504
|
+
paused = false;
|
|
18505
|
+
child.stdout?.resume();
|
|
18506
|
+
};
|
|
18507
|
+
const onStdoutData = (c) => {
|
|
18508
|
+
const data = c.toString();
|
|
18509
|
+
queue.push({ kind: "out", data });
|
|
18510
|
+
queuedChars += data.length;
|
|
18215
18511
|
wake();
|
|
18216
|
-
|
|
18217
|
-
|
|
18512
|
+
pauseIfFlooded();
|
|
18513
|
+
};
|
|
18514
|
+
const onError = (e) => {
|
|
18218
18515
|
queue.push({ kind: "error", data: e.message });
|
|
18219
18516
|
wake();
|
|
18220
|
-
}
|
|
18221
|
-
|
|
18517
|
+
};
|
|
18518
|
+
const onClose = () => {
|
|
18222
18519
|
queue.push({ kind: "close", data: "" });
|
|
18223
18520
|
wake();
|
|
18224
|
-
}
|
|
18521
|
+
};
|
|
18522
|
+
child.stdout?.on("data", onStdoutData);
|
|
18523
|
+
child.on("error", onError);
|
|
18524
|
+
child.on("close", onClose);
|
|
18225
18525
|
let pendingBatch = [];
|
|
18226
18526
|
let errored = false;
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18234
|
-
if (c.kind === "error") {
|
|
18235
|
-
errored = true;
|
|
18236
|
-
continue;
|
|
18237
|
-
}
|
|
18238
|
-
if (c.kind === "close") break;
|
|
18239
|
-
buf += c.data;
|
|
18240
|
-
if (buf.length > MAX_BUF_BYTES && !bufOverflow) {
|
|
18241
|
-
bufOverflow = true;
|
|
18242
|
-
buf = buf.slice(-MAX_BUF_BYTES);
|
|
18243
|
-
try {
|
|
18244
|
-
child.kill("SIGTERM");
|
|
18245
|
-
} catch {
|
|
18527
|
+
let closed = false;
|
|
18528
|
+
try {
|
|
18529
|
+
for (; ; ) {
|
|
18530
|
+
while (queue.length === 0) {
|
|
18531
|
+
await new Promise((r) => {
|
|
18532
|
+
waiter = r;
|
|
18533
|
+
});
|
|
18246
18534
|
}
|
|
18247
|
-
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18535
|
+
const c = expectDefined7(queue.shift());
|
|
18536
|
+
queuedChars = Math.max(0, queuedChars - c.data.length);
|
|
18537
|
+
resumeIfDrained();
|
|
18538
|
+
if (c.kind === "error") {
|
|
18539
|
+
errored = true;
|
|
18540
|
+
continue;
|
|
18541
|
+
}
|
|
18542
|
+
if (c.kind === "close") {
|
|
18543
|
+
closed = true;
|
|
18544
|
+
break;
|
|
18545
|
+
}
|
|
18546
|
+
buf += c.data;
|
|
18547
|
+
if (buf.length > MAX_BUF_BYTES && !bufOverflow) {
|
|
18548
|
+
bufOverflow = true;
|
|
18549
|
+
buf = buf.slice(-MAX_BUF_BYTES);
|
|
18550
|
+
try {
|
|
18551
|
+
child.kill("SIGTERM");
|
|
18552
|
+
} catch {
|
|
18553
|
+
}
|
|
18554
|
+
}
|
|
18555
|
+
const idx = buf.lastIndexOf("\n");
|
|
18556
|
+
if (idx === -1) continue;
|
|
18557
|
+
const ready = buf.slice(0, idx);
|
|
18558
|
+
buf = buf.slice(idx + 1);
|
|
18559
|
+
for (const line of ready.split("\n")) {
|
|
18560
|
+
if (!line) continue;
|
|
18561
|
+
totalLines++;
|
|
18562
|
+
if (mode === "count") totalCount += parseRgCountLine(line);
|
|
18563
|
+
if (matches.length < limit) {
|
|
18564
|
+
matches.push(line);
|
|
18565
|
+
pendingBatch.push(line);
|
|
18566
|
+
batchSinceFlush++;
|
|
18567
|
+
}
|
|
18568
|
+
}
|
|
18569
|
+
if (batchSinceFlush >= FLUSH_AT) {
|
|
18570
|
+
yield {
|
|
18571
|
+
type: "partial_output",
|
|
18572
|
+
text: pendingBatch.join("\n"),
|
|
18573
|
+
data: { matches_so_far: matches.length }
|
|
18574
|
+
};
|
|
18575
|
+
pendingBatch = [];
|
|
18576
|
+
batchSinceFlush = 0;
|
|
18577
|
+
}
|
|
18578
|
+
}
|
|
18579
|
+
if (buf.trim()) {
|
|
18580
|
+
for (const line of buf.split("\n")) {
|
|
18581
|
+
if (!line) continue;
|
|
18582
|
+
totalLines++;
|
|
18583
|
+
if (mode === "count") totalCount += parseRgCountLine(line);
|
|
18584
|
+
if (matches.length < limit) {
|
|
18585
|
+
matches.push(line);
|
|
18586
|
+
pendingBatch.push(line);
|
|
18587
|
+
}
|
|
18260
18588
|
}
|
|
18261
18589
|
}
|
|
18262
|
-
if (
|
|
18590
|
+
if (pendingBatch.length > 0) {
|
|
18263
18591
|
yield {
|
|
18264
18592
|
type: "partial_output",
|
|
18265
18593
|
text: pendingBatch.join("\n"),
|
|
18266
18594
|
data: { matches_so_far: matches.length }
|
|
18267
18595
|
};
|
|
18268
|
-
pendingBatch = [];
|
|
18269
|
-
batchSinceFlush = 0;
|
|
18270
|
-
}
|
|
18271
|
-
}
|
|
18272
|
-
if (buf.trim()) {
|
|
18273
|
-
for (const line of buf.split("\n")) {
|
|
18274
|
-
if (!line) continue;
|
|
18275
|
-
totalLines++;
|
|
18276
|
-
if (mode === "count") totalCount += parseRgCountLine(line);
|
|
18277
|
-
if (matches.length < limit) {
|
|
18278
|
-
matches.push(line);
|
|
18279
|
-
pendingBatch.push(line);
|
|
18280
|
-
}
|
|
18281
18596
|
}
|
|
18282
|
-
|
|
18283
|
-
if (pendingBatch.length > 0) {
|
|
18597
|
+
if (errored) throw new Error("rg: spawn error");
|
|
18284
18598
|
yield {
|
|
18285
|
-
type: "
|
|
18286
|
-
|
|
18287
|
-
|
|
18599
|
+
type: "final",
|
|
18600
|
+
output: {
|
|
18601
|
+
matches,
|
|
18602
|
+
count: mode === "count" ? totalCount : totalLines,
|
|
18603
|
+
truncated: totalLines > limit || bufOverflow,
|
|
18604
|
+
used: "rg"
|
|
18605
|
+
}
|
|
18288
18606
|
};
|
|
18289
|
-
}
|
|
18290
|
-
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18296
|
-
|
|
18297
|
-
|
|
18607
|
+
} finally {
|
|
18608
|
+
child.stdout?.off("data", onStdoutData);
|
|
18609
|
+
child.off("error", onError);
|
|
18610
|
+
child.off("close", onClose);
|
|
18611
|
+
child.stdout?.destroy();
|
|
18612
|
+
queue.length = 0;
|
|
18613
|
+
queuedChars = 0;
|
|
18614
|
+
waiter = void 0;
|
|
18615
|
+
if (!closed && child.exitCode === null) {
|
|
18616
|
+
try {
|
|
18617
|
+
child.kill("SIGTERM");
|
|
18618
|
+
} catch {
|
|
18619
|
+
}
|
|
18298
18620
|
}
|
|
18299
|
-
}
|
|
18621
|
+
}
|
|
18300
18622
|
}
|
|
18301
18623
|
function parseRgCountLine(line) {
|
|
18302
18624
|
const idx = line.lastIndexOf(":");
|
|
@@ -18326,8 +18648,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
18326
18648
|
if (globRe && !globRe.test(name) && !globRe.test(full)) return;
|
|
18327
18649
|
if (globRe) globRe.lastIndex = 0;
|
|
18328
18650
|
try {
|
|
18329
|
-
const
|
|
18330
|
-
if (!
|
|
18651
|
+
const stat18 = await fs25.stat(full);
|
|
18652
|
+
if (!stat18.isFile() || stat18.size > maxBytes || stopped || signal.aborted) return;
|
|
18331
18653
|
const file = await fs25.open(full, "r");
|
|
18332
18654
|
try {
|
|
18333
18655
|
let bytesReadTotal = 0;
|
|
@@ -18427,8 +18749,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
18427
18749
|
files.push({ full, name: e.name });
|
|
18428
18750
|
}
|
|
18429
18751
|
}
|
|
18430
|
-
await
|
|
18431
|
-
await
|
|
18752
|
+
await mapWithConcurrency2(files, NATIVE_SCAN_CONCURRENCY, ({ full, name }) => scanFile(full, name));
|
|
18753
|
+
await mapWithConcurrency2(
|
|
18432
18754
|
subdirs,
|
|
18433
18755
|
Math.min(16, NATIVE_SCAN_CONCURRENCY),
|
|
18434
18756
|
({ full, rel }) => walk(full, rel)
|
|
@@ -18447,7 +18769,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
18447
18769
|
init_spawn_stream();
|
|
18448
18770
|
init_util();
|
|
18449
18771
|
init_legacy_bridge();
|
|
18450
|
-
import { join as
|
|
18772
|
+
import { join as join25 } from "node:path";
|
|
18451
18773
|
import {
|
|
18452
18774
|
detectEcosystem as detectPackageEcosystem,
|
|
18453
18775
|
recordPackageAction
|
|
@@ -18458,6 +18780,10 @@ var installTool = {
|
|
|
18458
18780
|
description: "Install, update or manage packages using the detected package manager (pnpm/npm/yarn). Strongly preferred over raw shell commands for dependency management because it is structured and safer.",
|
|
18459
18781
|
usageHint: "ALWAYS USE THIS INSTEAD OF BASH FOR PACKAGE WORK:\n\n- Empty `packages` \u2192 normal `install` (respects lockfile).\n- Provide names \u2192 adds/updates specific packages.\n- `dry_run: true` for safe preview.\n- Set `save` appropriately.\nThis tool has proper capability declaration and is heavily recommended in the security posture of the project.",
|
|
18460
18782
|
permission: "confirm",
|
|
18783
|
+
// WS-046: gives permission decisions something to key on.
|
|
18784
|
+
// What gets installed is the subject — installing a package runs its
|
|
18785
|
+
// lifecycle scripts, so `express` and `evil-pkg` must not be interchangeable.
|
|
18786
|
+
subjectKey: "packages",
|
|
18461
18787
|
mutating: true,
|
|
18462
18788
|
riskTier: "standard",
|
|
18463
18789
|
icon: "package",
|
|
@@ -18627,10 +18953,10 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
18627
18953
|
case "pnpm":
|
|
18628
18954
|
case "yarn":
|
|
18629
18955
|
case "npm":
|
|
18630
|
-
return
|
|
18956
|
+
return join25(cwd, "package.json");
|
|
18631
18957
|
/* v8 ignore next 2 -- pkgManager is always pnpm/yarn/npm; the default is defensive. */
|
|
18632
18958
|
default:
|
|
18633
|
-
return
|
|
18959
|
+
return join25(cwd, "package.json");
|
|
18634
18960
|
}
|
|
18635
18961
|
}
|
|
18636
18962
|
|
|
@@ -18638,6 +18964,24 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
18638
18964
|
init_util();
|
|
18639
18965
|
import * as fs26 from "node:fs/promises";
|
|
18640
18966
|
import { deepMerge as deepMergeCore } from "@wrongstack/core/utils";
|
|
18967
|
+
var MAX_JSON_FILE_BYTES = 16 * 1024 * 1024;
|
|
18968
|
+
var MAX_JSON_FILE_BYTES_HUMAN = "16 MiB";
|
|
18969
|
+
var JsonFileTooLargeError = class extends Error {
|
|
18970
|
+
constructor(filePath, size) {
|
|
18971
|
+
super(
|
|
18972
|
+
`json: "${filePath}" is ${size} bytes \u2014 exceeds the ${MAX_JSON_FILE_BYTES_HUMAN} file-size limit. Extract the relevant portion into a smaller file first, or use a shell tool to query it directly.`
|
|
18973
|
+
);
|
|
18974
|
+
this.name = "JsonFileTooLargeError";
|
|
18975
|
+
}
|
|
18976
|
+
};
|
|
18977
|
+
async function readJsonFileBounded(filePath, ctx) {
|
|
18978
|
+
const resolved = await safeResolveReal(filePath, ctx);
|
|
18979
|
+
const stat18 = await fs26.stat(resolved);
|
|
18980
|
+
if (stat18.size > MAX_JSON_FILE_BYTES) {
|
|
18981
|
+
throw new JsonFileTooLargeError(filePath, stat18.size);
|
|
18982
|
+
}
|
|
18983
|
+
return fs26.readFile(resolved, "utf8");
|
|
18984
|
+
}
|
|
18641
18985
|
var jsonTool = {
|
|
18642
18986
|
name: "json",
|
|
18643
18987
|
category: "Data",
|
|
@@ -18645,6 +18989,7 @@ var jsonTool = {
|
|
|
18645
18989
|
usageHint: 'VERY USEFUL FOR DATA INSPECTION:\n\n- `action: "parse"` (default): read/pretty-print/convert JSON, JSON5, or YAML from `file` or `data`.\n- `action: "query"`: JMESPath-like query (`a.b[0].c`, `items[*].name`, filters, functions).\n- `action: "validate"`: validate data against a JSON Schema (`schema` param).\n- `action: "transform"`: chain multiple JMESPath transforms (`transforms` param).\n- `action: "merge"`: deep merge `base` and `patch` objects (`conflictResolution` param).\nPrefer this over raw `read` + manual parsing when dealing with configuration or data files.',
|
|
18646
18990
|
permission: "auto",
|
|
18647
18991
|
mutating: false,
|
|
18992
|
+
maxOutputBytes: 262144,
|
|
18648
18993
|
timeoutMs: 5e3,
|
|
18649
18994
|
capabilities: ["fs.read"],
|
|
18650
18995
|
icon: "json",
|
|
@@ -18711,9 +19056,15 @@ async function executeParse(input, ctx) {
|
|
|
18711
19056
|
let raw;
|
|
18712
19057
|
if (input.file) {
|
|
18713
19058
|
try {
|
|
18714
|
-
raw = await
|
|
18715
|
-
} catch {
|
|
18716
|
-
return {
|
|
19059
|
+
raw = await readJsonFileBounded(input.file, ctx);
|
|
19060
|
+
} catch (error) {
|
|
19061
|
+
return {
|
|
19062
|
+
data: null,
|
|
19063
|
+
formatted: "",
|
|
19064
|
+
type: "unknown",
|
|
19065
|
+
action: "parse",
|
|
19066
|
+
error: error instanceof JsonFileTooLargeError ? error.message : "Could not read file"
|
|
19067
|
+
};
|
|
18717
19068
|
}
|
|
18718
19069
|
} else if (input.data) {
|
|
18719
19070
|
raw = input.data;
|
|
@@ -18769,10 +19120,16 @@ async function executeQuery(input, ctx) {
|
|
|
18769
19120
|
let parsed;
|
|
18770
19121
|
if (input.file) {
|
|
18771
19122
|
try {
|
|
18772
|
-
const raw = await
|
|
19123
|
+
const raw = await readJsonFileBounded(input.file, ctx);
|
|
18773
19124
|
parsed = JSON.parse(raw);
|
|
18774
|
-
} catch {
|
|
18775
|
-
return {
|
|
19125
|
+
} catch (error) {
|
|
19126
|
+
return {
|
|
19127
|
+
data: null,
|
|
19128
|
+
formatted: "",
|
|
19129
|
+
type: "unknown",
|
|
19130
|
+
action: "query",
|
|
19131
|
+
error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
|
|
19132
|
+
};
|
|
18776
19133
|
}
|
|
18777
19134
|
} else if (input.data) {
|
|
18778
19135
|
try {
|
|
@@ -18811,10 +19168,16 @@ async function executeValidate(input, ctx) {
|
|
|
18811
19168
|
let parsed;
|
|
18812
19169
|
if (input.file) {
|
|
18813
19170
|
try {
|
|
18814
|
-
const raw = await
|
|
19171
|
+
const raw = await readJsonFileBounded(input.file, ctx);
|
|
18815
19172
|
parsed = JSON.parse(raw);
|
|
18816
|
-
} catch {
|
|
18817
|
-
return {
|
|
19173
|
+
} catch (error) {
|
|
19174
|
+
return {
|
|
19175
|
+
data: null,
|
|
19176
|
+
formatted: "",
|
|
19177
|
+
type: "unknown",
|
|
19178
|
+
action: "validate",
|
|
19179
|
+
error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
|
|
19180
|
+
};
|
|
18818
19181
|
}
|
|
18819
19182
|
} else if (input.data) {
|
|
18820
19183
|
try {
|
|
@@ -18853,10 +19216,16 @@ async function executeTransform(input, ctx) {
|
|
|
18853
19216
|
let parsed;
|
|
18854
19217
|
if (input.file) {
|
|
18855
19218
|
try {
|
|
18856
|
-
const raw = await
|
|
19219
|
+
const raw = await readJsonFileBounded(input.file, ctx);
|
|
18857
19220
|
parsed = JSON.parse(raw);
|
|
18858
|
-
} catch {
|
|
18859
|
-
return {
|
|
19221
|
+
} catch (error) {
|
|
19222
|
+
return {
|
|
19223
|
+
data: null,
|
|
19224
|
+
formatted: "",
|
|
19225
|
+
type: "unknown",
|
|
19226
|
+
action: "transform",
|
|
19227
|
+
error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
|
|
19228
|
+
};
|
|
18860
19229
|
}
|
|
18861
19230
|
} else if (input.data) {
|
|
18862
19231
|
try {
|
|
@@ -19460,6 +19829,10 @@ async function handleKanbanDetailAction(projectRoot, input) {
|
|
|
19460
19829
|
}
|
|
19461
19830
|
|
|
19462
19831
|
// src/kanban-board-inputs.ts
|
|
19832
|
+
function agentSettableGate(enforcement) {
|
|
19833
|
+
if (enforcement === void 0 || enforcement === "off") return {};
|
|
19834
|
+
return { completionGate: { enforcement } };
|
|
19835
|
+
}
|
|
19463
19836
|
function boardCreateInput(input, title) {
|
|
19464
19837
|
return {
|
|
19465
19838
|
title,
|
|
@@ -19472,7 +19845,7 @@ function boardCreateInput(input, title) {
|
|
|
19472
19845
|
decomposition: input.atomicityDecomposition ?? "propose"
|
|
19473
19846
|
}
|
|
19474
19847
|
} : {},
|
|
19475
|
-
...input.gateEnforcement
|
|
19848
|
+
...agentSettableGate(input.gateEnforcement)
|
|
19476
19849
|
};
|
|
19477
19850
|
}
|
|
19478
19851
|
function boardUpdatePatch(input) {
|
|
@@ -19486,7 +19859,7 @@ function boardUpdatePatch(input) {
|
|
|
19486
19859
|
decomposition: input.atomicityDecomposition ?? "propose"
|
|
19487
19860
|
}
|
|
19488
19861
|
} : {},
|
|
19489
|
-
...input.gateEnforcement
|
|
19862
|
+
...agentSettableGate(input.gateEnforcement)
|
|
19490
19863
|
};
|
|
19491
19864
|
}
|
|
19492
19865
|
function duplicateBoardOptions(input) {
|
|
@@ -19501,6 +19874,7 @@ function duplicateBoardOptions(input) {
|
|
|
19501
19874
|
|
|
19502
19875
|
// src/kanban-task-inputs.ts
|
|
19503
19876
|
import { randomUUID } from "node:crypto";
|
|
19877
|
+
import { clampSubagentCapabilities } from "@wrongstack/core/security";
|
|
19504
19878
|
function taskInput(input) {
|
|
19505
19879
|
const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
|
|
19506
19880
|
return {
|
|
@@ -19598,6 +19972,10 @@ function taskPatch(input) {
|
|
|
19598
19972
|
...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
|
|
19599
19973
|
};
|
|
19600
19974
|
}
|
|
19975
|
+
function clampRequestedCapabilities(requested) {
|
|
19976
|
+
if (requested === void 0) return void 0;
|
|
19977
|
+
return clampSubagentCapabilities(requested).granted;
|
|
19978
|
+
}
|
|
19601
19979
|
function assignmentInput(input) {
|
|
19602
19980
|
return {
|
|
19603
19981
|
agentId: input.agentId,
|
|
@@ -19608,7 +19986,7 @@ function assignmentInput(input) {
|
|
|
19608
19986
|
fallbackProfile: input.fallbackProfile,
|
|
19609
19987
|
fallbackModels: input.fallbackModels,
|
|
19610
19988
|
tools: input.tools,
|
|
19611
|
-
allowedCapabilities: input.allowedCapabilities,
|
|
19989
|
+
allowedCapabilities: clampRequestedCapabilities(input.allowedCapabilities),
|
|
19612
19990
|
assignee: input.assignee,
|
|
19613
19991
|
leaseId: input.leaseId,
|
|
19614
19992
|
claimedAt: input.claimedAt,
|
|
@@ -19635,7 +20013,7 @@ function assignmentForTaskCreate(input) {
|
|
|
19635
20013
|
...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
|
|
19636
20014
|
...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
|
|
19637
20015
|
...input.tools !== void 0 ? { tools: input.tools } : {},
|
|
19638
|
-
...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
|
|
20016
|
+
...input.allowedCapabilities !== void 0 ? { allowedCapabilities: clampRequestedCapabilities(input.allowedCapabilities) } : {},
|
|
19639
20017
|
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
19640
20018
|
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
19641
20019
|
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
@@ -19846,7 +20224,13 @@ var KANBAN_INPUT_SCHEMA = {
|
|
|
19846
20224
|
moveTasksToColumnId: { type: "string" },
|
|
19847
20225
|
atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
|
|
19848
20226
|
atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
|
|
19849
|
-
gateEnforcement: {
|
|
20227
|
+
gateEnforcement: {
|
|
20228
|
+
type: "string",
|
|
20229
|
+
// WS-023: `'off'` is deliberately absent. The agent whose work this gate
|
|
20230
|
+
// checks must not be able to switch it off; it may only tighten. Turning
|
|
20231
|
+
// a gate off stays a human decision, made through board config.
|
|
20232
|
+
enum: ["strict", "soft"]
|
|
20233
|
+
},
|
|
19850
20234
|
subtasks: {
|
|
19851
20235
|
type: "array",
|
|
19852
20236
|
minItems: 2,
|
|
@@ -19953,7 +20337,9 @@ async function ensureSessionKanbanBoard(projectRoot, sessionId) {
|
|
|
19953
20337
|
description: "Live session work: todos, tasks, and plan items.",
|
|
19954
20338
|
tags: sessionBoardTags(sessionId),
|
|
19955
20339
|
columns: SESSION_KANBAN_COLUMNS,
|
|
19956
|
-
generatedBy: `session-kanban:${sessionId}
|
|
20340
|
+
generatedBy: `session-kanban:${sessionId}`,
|
|
20341
|
+
kind: "session_mirror",
|
|
20342
|
+
retention: { mode: "archive_after_ttl", ttlMs: 7 * 24 * 60 * 60 * 1e3 }
|
|
19957
20343
|
});
|
|
19958
20344
|
}
|
|
19959
20345
|
if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {
|
|
@@ -20987,11 +21373,11 @@ var lintTool = {
|
|
|
20987
21373
|
}
|
|
20988
21374
|
};
|
|
20989
21375
|
async function detectLinter(cwd) {
|
|
20990
|
-
const { stat:
|
|
21376
|
+
const { stat: stat18 } = await import("node:fs/promises");
|
|
20991
21377
|
const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
|
|
20992
21378
|
for (const f of checks) {
|
|
20993
21379
|
try {
|
|
20994
|
-
await
|
|
21380
|
+
await stat18(`${cwd}/${f}`);
|
|
20995
21381
|
if (f.includes("biome")) return "biome";
|
|
20996
21382
|
if (f.includes("eslint")) return "eslint";
|
|
20997
21383
|
if (f.includes("tslint")) return "tslint";
|
|
@@ -21235,6 +21621,9 @@ var outdatedTool = {
|
|
|
21235
21621
|
// tool.confirm_needed flow on every invocation. M-1 originally
|
|
21236
21622
|
// fixed four sibling tools (mcp_control, shellcheck, shellcheck (scan mode),
|
|
21237
21623
|
// search) but missed this one; applying the same contract here.
|
|
21624
|
+
// WS-046: gives permission decisions something to key on.
|
|
21625
|
+
// The working directory scanned.
|
|
21626
|
+
subjectKey: "cwd",
|
|
21238
21627
|
mutating: true,
|
|
21239
21628
|
// Capability is outbound network — the tool only hits the package
|
|
21240
21629
|
// registry over HTTP, never touches the filesystem or runs shell.
|
|
@@ -21416,6 +21805,10 @@ var patchTool = {
|
|
|
21416
21805
|
useInstead: ["edit"]
|
|
21417
21806
|
},
|
|
21418
21807
|
permission: "confirm",
|
|
21808
|
+
// WS-046: gives permission decisions something to key on.
|
|
21809
|
+
// The target directory. The patch body itself is unstable (every diff
|
|
21810
|
+
// differs), so it could never re-match its own stored rule.
|
|
21811
|
+
subjectKey: "directory",
|
|
21419
21812
|
mutating: true,
|
|
21420
21813
|
capabilities: ["fs.write"],
|
|
21421
21814
|
icon: "edit",
|
|
@@ -21482,8 +21875,8 @@ var patchTool = {
|
|
|
21482
21875
|
const before = beforeContents.get(target) ?? null;
|
|
21483
21876
|
const after = await readTextForTracking(target);
|
|
21484
21877
|
if (after === null || after === before) continue;
|
|
21485
|
-
const
|
|
21486
|
-
if (
|
|
21878
|
+
const stat18 = await fs27.stat(target).catch(() => null);
|
|
21879
|
+
if (stat18) ctx.recordRead?.(target, stat18.mtimeMs, "write", sha256hex(after));
|
|
21487
21880
|
ctx.session?.recordFileChange?.({
|
|
21488
21881
|
path: target,
|
|
21489
21882
|
action: before === null ? "created" : "modified",
|
|
@@ -21508,8 +21901,8 @@ var patchTool = {
|
|
|
21508
21901
|
var MAX_TRACKING_BYTES = 5 * 1024 * 1024;
|
|
21509
21902
|
async function readTextForTracking(absPath) {
|
|
21510
21903
|
try {
|
|
21511
|
-
const
|
|
21512
|
-
if (!
|
|
21904
|
+
const stat18 = await fs27.stat(absPath);
|
|
21905
|
+
if (!stat18.isFile() || stat18.size > MAX_TRACKING_BYTES) return null;
|
|
21513
21906
|
const buf = await fs27.readFile(absPath);
|
|
21514
21907
|
if (buf.includes(0)) return null;
|
|
21515
21908
|
return buf.toString("utf8");
|
|
@@ -21539,7 +21932,13 @@ function runPatch(args, cwd, signal) {
|
|
|
21539
21932
|
let stdout = "";
|
|
21540
21933
|
let stderr = "";
|
|
21541
21934
|
const env = { ...buildChildEnv8(), LANG: "C", LC_ALL: "C" };
|
|
21542
|
-
const child = spawn14("patch", args, {
|
|
21935
|
+
const child = spawn14("patch", args, {
|
|
21936
|
+
cwd,
|
|
21937
|
+
signal,
|
|
21938
|
+
env,
|
|
21939
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
21940
|
+
windowsHide: true
|
|
21941
|
+
});
|
|
21543
21942
|
child.stdout?.on("data", (c) => {
|
|
21544
21943
|
stdout += c.toString();
|
|
21545
21944
|
});
|
|
@@ -21560,6 +21959,7 @@ function extractPatchedFiles(output) {
|
|
|
21560
21959
|
}
|
|
21561
21960
|
|
|
21562
21961
|
// src/plan.ts
|
|
21962
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
21563
21963
|
import {
|
|
21564
21964
|
addPlanItem,
|
|
21565
21965
|
clearPlan,
|
|
@@ -21567,20 +21967,20 @@ import {
|
|
|
21567
21967
|
formatPlan,
|
|
21568
21968
|
getPlanTemplate,
|
|
21569
21969
|
mutatePlan as mutatePlan2,
|
|
21970
|
+
mutateTasks as mutateTasks2,
|
|
21570
21971
|
removePlanItem,
|
|
21571
21972
|
setPlanItemStatus
|
|
21572
21973
|
} from "@wrongstack/core/storage";
|
|
21573
|
-
import {
|
|
21574
|
-
mutateTasks as mutateTasks2
|
|
21575
|
-
} from "@wrongstack/core/storage";
|
|
21576
21974
|
import { formatTaskList } from "@wrongstack/core/utils";
|
|
21577
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
21578
21975
|
var planTool = {
|
|
21579
21976
|
name: "plan",
|
|
21580
21977
|
category: "Session",
|
|
21581
21978
|
description: 'Manage a session-persistent strategic plan. The plan is written to disk and survives conversation resumptions within the same session, but is isolated to this session \u2014 other sessions have their own separate plans. Unlike todos (which are per-turn and lost on restart), a plan tracks high-level progress across multiple turns. Use this to outline big-picture work, then promote concrete items into the todo list when ready to execute. By default plans are isolated to this session; use `scope: "project"` to store the plan in a shared project-level file visible to all sessions.',
|
|
21582
21979
|
usageHint: 'RECOMMENDED FOR COMPLEX, MULTI-PHASE WORK:\n\n- Start by creating a high-level plan with `action: "add"` or using templates (`template_use`).\n- Use `promote` to turn a plan item into actionable todos.\n- Use `taskify` to convert a plan item into a structured task (with type/priority/deps).\n- Keep plans at the "why and what" level, and todos at the "how and next step" level.\n- Common templates: "new-feature", "bug-fix", "refactor", "release", "security-audit".\n\nThis tool is excellent for maintaining long-term direction across many turns within a session. Plans survive resume but are not shared across separate sessions.\nUse `scope: "project"` to use a shared project-level plan file.',
|
|
21583
21980
|
permission: "confirm",
|
|
21981
|
+
// WS-046: gives permission decisions something to key on.
|
|
21982
|
+
// The action performed; plan has no single file or path subject.
|
|
21983
|
+
subjectKey: "action",
|
|
21584
21984
|
mutating: true,
|
|
21585
21985
|
capabilities: ["fs.write"],
|
|
21586
21986
|
icon: "plan",
|
|
@@ -21637,7 +22037,10 @@ var planTool = {
|
|
|
21637
22037
|
let planPath;
|
|
21638
22038
|
if (input.scope === "project") {
|
|
21639
22039
|
if (typeof sessionPlanPath === "string") {
|
|
21640
|
-
const lastSep = Math.max(
|
|
22040
|
+
const lastSep = Math.max(
|
|
22041
|
+
sessionPlanPath.lastIndexOf("/"),
|
|
22042
|
+
sessionPlanPath.lastIndexOf("\\")
|
|
22043
|
+
);
|
|
21641
22044
|
planPath = lastSep >= 0 ? sessionPlanPath.slice(0, lastSep + 1) + "backlog.plan.json" : "backlog.plan.json";
|
|
21642
22045
|
}
|
|
21643
22046
|
} else {
|
|
@@ -21674,7 +22077,11 @@ var planTool = {
|
|
|
21674
22077
|
case "start":
|
|
21675
22078
|
case "done": {
|
|
21676
22079
|
if (!input.target) {
|
|
21677
|
-
early = mkResult(
|
|
22080
|
+
early = mkResult(
|
|
22081
|
+
p,
|
|
22082
|
+
false,
|
|
22083
|
+
`${input.action} requires \`target\` (id|index|substring).`
|
|
22084
|
+
);
|
|
21678
22085
|
return p;
|
|
21679
22086
|
}
|
|
21680
22087
|
const next = setPlanItemStatus(
|
|
@@ -21702,7 +22109,11 @@ var planTool = {
|
|
|
21702
22109
|
}
|
|
21703
22110
|
case "promote": {
|
|
21704
22111
|
if (!input.target) {
|
|
21705
|
-
early = mkResult(
|
|
22112
|
+
early = mkResult(
|
|
22113
|
+
p,
|
|
22114
|
+
false,
|
|
22115
|
+
`${input.action} requires \`target\` (id|index|substring).`
|
|
22116
|
+
);
|
|
21706
22117
|
return p;
|
|
21707
22118
|
}
|
|
21708
22119
|
const derived = deriveTodosFromPlanItem(p, input.target, input.subtasks);
|
|
@@ -21745,7 +22156,11 @@ var planTool = {
|
|
|
21745
22156
|
return clearPlan(p);
|
|
21746
22157
|
case "taskify": {
|
|
21747
22158
|
if (!input.target) {
|
|
21748
|
-
early = mkResult(
|
|
22159
|
+
early = mkResult(
|
|
22160
|
+
p,
|
|
22161
|
+
false,
|
|
22162
|
+
"taskify requires `target` (plan item id|index|substring)."
|
|
22163
|
+
);
|
|
21749
22164
|
return p;
|
|
21750
22165
|
}
|
|
21751
22166
|
let itemIdx = -1;
|
|
@@ -21818,7 +22233,11 @@ var planTool = {
|
|
|
21818
22233
|
${formatTaskList(taskFile.tasks)}`
|
|
21819
22234
|
);
|
|
21820
22235
|
} catch (err) {
|
|
21821
|
-
return mkResult(
|
|
22236
|
+
return mkResult(
|
|
22237
|
+
plan,
|
|
22238
|
+
false,
|
|
22239
|
+
`taskify: task not saved \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
22240
|
+
);
|
|
21822
22241
|
}
|
|
21823
22242
|
}
|
|
21824
22243
|
return mkResult(plan, true, `Plan ${input.action} ok.`);
|
|
@@ -21895,9 +22314,9 @@ var readTool = {
|
|
|
21895
22314
|
}
|
|
21896
22315
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
21897
22316
|
const shouldIncludeSymbols = input.includeSymbols === true || input.includeSymbols !== false && ctx.meta[ADVANCED_MODE_META_KEY] === true;
|
|
21898
|
-
let
|
|
22317
|
+
let stat18;
|
|
21899
22318
|
try {
|
|
21900
|
-
|
|
22319
|
+
stat18 = await fs28.stat(absPath);
|
|
21901
22320
|
} catch (err) {
|
|
21902
22321
|
const code = err.code;
|
|
21903
22322
|
if (code === "ENOENT") {
|
|
@@ -21916,7 +22335,7 @@ var readTool = {
|
|
|
21916
22335
|
cause: err
|
|
21917
22336
|
});
|
|
21918
22337
|
}
|
|
21919
|
-
if (!
|
|
22338
|
+
if (!stat18.isFile()) {
|
|
21920
22339
|
throw new FsError({
|
|
21921
22340
|
message: `read: "${input.path}" is not a regular file`,
|
|
21922
22341
|
code: "FS_READ_FAILED",
|
|
@@ -21924,23 +22343,23 @@ var readTool = {
|
|
|
21924
22343
|
context: { reason: "not-a-regular-file" }
|
|
21925
22344
|
});
|
|
21926
22345
|
}
|
|
21927
|
-
if (
|
|
22346
|
+
if (stat18.size > MAX_BYTES2) {
|
|
21928
22347
|
throw new FsError({
|
|
21929
|
-
message: `read: file too large (${
|
|
22348
|
+
message: `read: file too large (${stat18.size} bytes, limit ${MAX_BYTES2})`,
|
|
21930
22349
|
code: "FS_READ_FAILED",
|
|
21931
22350
|
path: absPath,
|
|
21932
|
-
context: { size:
|
|
22351
|
+
context: { size: stat18.size, limit: MAX_BYTES2, reason: "too-large" }
|
|
21933
22352
|
});
|
|
21934
22353
|
}
|
|
21935
22354
|
const offset = Math.max(1, input.offset ?? 1);
|
|
21936
22355
|
const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
|
|
21937
22356
|
const prior = getReadRangeRecord(ctx, absPath);
|
|
21938
22357
|
const requestedEnd = prior ? Math.min(offset + limit - 1, prior.totalLines) : offset + limit - 1;
|
|
21939
|
-
if (input.mode !== "summary" && limit > 0 && prior && coversRange(prior,
|
|
21940
|
-
ctx.recordRead(absPath,
|
|
22358
|
+
if (input.mode !== "summary" && limit > 0 && prior && coversRange(prior, stat18.mtimeMs, offset, requestedEnd)) {
|
|
22359
|
+
ctx.recordRead(absPath, stat18.mtimeMs);
|
|
21941
22360
|
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21942
22361
|
return {
|
|
21943
|
-
text: `[unchanged since previous read: "${input.path}" mtime=${Math.round(
|
|
22362
|
+
text: `[unchanged since previous read: "${input.path}" mtime=${Math.round(stat18.mtimeMs)}; requested lines ${offset}-${requestedEnd} were already shown. Use offset/limit for a new range if needed.]`,
|
|
21944
22363
|
total_lines: prior.totalLines,
|
|
21945
22364
|
encoding: "utf8",
|
|
21946
22365
|
truncated: requestedEnd < prior.totalLines,
|
|
@@ -21958,11 +22377,11 @@ var readTool = {
|
|
|
21958
22377
|
const allLines = text.split(/\r\n|\r|\n/);
|
|
21959
22378
|
const total = allLines.length;
|
|
21960
22379
|
if (input.mode === "summary") {
|
|
21961
|
-
ctx.recordRead(absPath,
|
|
21962
|
-
rememberReadRange(ctx, absPath,
|
|
22380
|
+
ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
|
|
22381
|
+
rememberReadRange(ctx, absPath, stat18.mtimeMs, total, 1, Math.min(total, 200));
|
|
21963
22382
|
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21964
22383
|
return {
|
|
21965
|
-
text: summarizeFile(input.path,
|
|
22384
|
+
text: summarizeFile(input.path, stat18.size, allLines),
|
|
21966
22385
|
total_lines: total,
|
|
21967
22386
|
encoding: "utf8",
|
|
21968
22387
|
truncated: total > 200,
|
|
@@ -21974,8 +22393,8 @@ var readTool = {
|
|
|
21974
22393
|
};
|
|
21975
22394
|
}
|
|
21976
22395
|
if (limit === 0) {
|
|
21977
|
-
ctx.recordRead(absPath,
|
|
21978
|
-
rememberReadRange(ctx, absPath,
|
|
22396
|
+
ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
|
|
22397
|
+
rememberReadRange(ctx, absPath, stat18.mtimeMs, total, 1, 0);
|
|
21979
22398
|
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21980
22399
|
return {
|
|
21981
22400
|
text: "",
|
|
@@ -21987,8 +22406,8 @@ var readTool = {
|
|
|
21987
22406
|
};
|
|
21988
22407
|
}
|
|
21989
22408
|
if (offset > total) {
|
|
21990
|
-
ctx.recordRead(absPath,
|
|
21991
|
-
rememberReadRange(ctx, absPath,
|
|
22409
|
+
ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
|
|
22410
|
+
rememberReadRange(ctx, absPath, stat18.mtimeMs, total, total + 1, total + 1);
|
|
21992
22411
|
const symResult2 = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
21993
22412
|
return {
|
|
21994
22413
|
text: `[offset ${offset} is past end of file "${input.path}" \u2014 file has ${total} line(s). Do not retry this offset.]`,
|
|
@@ -22003,8 +22422,8 @@ var readTool = {
|
|
|
22003
22422
|
const truncated = offset - 1 + slice.length < total;
|
|
22004
22423
|
const width = String(offset + slice.length - 1).length;
|
|
22005
22424
|
const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
|
|
22006
|
-
ctx.recordRead(absPath,
|
|
22007
|
-
rememberReadRange(ctx, absPath,
|
|
22425
|
+
ctx.recordRead(absPath, stat18.mtimeMs, "user", contentHash);
|
|
22426
|
+
rememberReadRange(ctx, absPath, stat18.mtimeMs, total, offset, offset + slice.length - 1);
|
|
22008
22427
|
const symResult = shouldIncludeSymbols ? await fetchSymbolsForFile(absPath, ctx, execOpts?.signal) : void 0;
|
|
22009
22428
|
return {
|
|
22010
22429
|
text: numbered,
|
|
@@ -22110,20 +22529,20 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
|
|
|
22110
22529
|
}
|
|
22111
22530
|
|
|
22112
22531
|
// src/replace.ts
|
|
22113
|
-
import { expectDefined as expectDefined8 } from "@wrongstack/core/utils";
|
|
22114
22532
|
import { spawn as spawn15 } from "node:child_process";
|
|
22115
22533
|
import * as fs29 from "node:fs/promises";
|
|
22116
22534
|
import * as path33 from "node:path";
|
|
22535
|
+
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
22117
22536
|
import {
|
|
22118
22537
|
atomicWrite as atomicWrite3,
|
|
22119
22538
|
buildChildEnv as buildChildEnv9,
|
|
22120
22539
|
compileGlob as compileGlob4,
|
|
22121
22540
|
detectNewlineStyle as detectNewlineStyle2,
|
|
22541
|
+
expectDefined as expectDefined8,
|
|
22122
22542
|
normalizeToLf as normalizeToLf2,
|
|
22123
22543
|
toStyle as toStyle2,
|
|
22124
22544
|
unifiedDiff as unifiedDiff2
|
|
22125
22545
|
} from "@wrongstack/core/utils";
|
|
22126
|
-
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
|
|
22127
22546
|
init_util();
|
|
22128
22547
|
var DEFAULT_IGNORE4 = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
22129
22548
|
var replaceTool = {
|
|
@@ -22132,6 +22551,10 @@ var replaceTool = {
|
|
|
22132
22551
|
description: "Perform a search-and-replace across multiple files using a regex pattern. This is a powerful bulk transformation tool. Dry-run is ON by default \u2014 set `dry_run: false` to apply changes.",
|
|
22133
22552
|
usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1. Run without `dry_run: false` first to see exactly what would change (dry-run is the default).\n2. Review the diff output, then re-run with `dry_run: false` to apply.\n3. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\n4. `replace_all` controls whether only the first match per file or all matches are replaced.\nThis tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.",
|
|
22134
22553
|
permission: "confirm",
|
|
22554
|
+
// WS-046: gives permission decisions something to key on.
|
|
22555
|
+
// The file scope being rewritten, not the pattern: a trust rule should say
|
|
22556
|
+
// where a bulk replace may run.
|
|
22557
|
+
subjectKey: "files",
|
|
22135
22558
|
mutating: true,
|
|
22136
22559
|
capabilities: ["fs.write"],
|
|
22137
22560
|
icon: "edit",
|
|
@@ -22204,8 +22627,8 @@ var replaceTool = {
|
|
|
22204
22627
|
}
|
|
22205
22628
|
const rel = path33.relative(realRoot, realPath);
|
|
22206
22629
|
if (rel.startsWith("..") || path33.isAbsolute(rel)) continue;
|
|
22207
|
-
const
|
|
22208
|
-
if (!
|
|
22630
|
+
const stat18 = await fs29.stat(realPath).catch(() => null);
|
|
22631
|
+
if (!stat18?.isFile()) continue;
|
|
22209
22632
|
let content;
|
|
22210
22633
|
try {
|
|
22211
22634
|
const buf = await fs29.readFile(realPath);
|
|
@@ -22230,7 +22653,7 @@ var replaceTool = {
|
|
|
22230
22653
|
totalReplacements += count;
|
|
22231
22654
|
if (!dryRun) {
|
|
22232
22655
|
const newContent = toStyle2(newContentLf, style);
|
|
22233
|
-
await atomicWrite3(realPath, newContent, { mode:
|
|
22656
|
+
await atomicWrite3(realPath, newContent, { mode: stat18.mode & 511 });
|
|
22234
22657
|
const written = await fs29.stat(realPath).catch(() => null);
|
|
22235
22658
|
if (written) {
|
|
22236
22659
|
ctx.recordRead?.(realPath, written.mtimeMs, "write", sha256hex(newContent));
|
|
@@ -22270,8 +22693,8 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
|
|
|
22270
22693
|
const resolved = [];
|
|
22271
22694
|
for (const p of parts) {
|
|
22272
22695
|
const absPath = safeResolve(p, ctx);
|
|
22273
|
-
const
|
|
22274
|
-
if (
|
|
22696
|
+
const stat18 = await fs29.stat(absPath).catch(() => null);
|
|
22697
|
+
if (stat18?.isFile()) {
|
|
22275
22698
|
resolved.push(absPath);
|
|
22276
22699
|
}
|
|
22277
22700
|
}
|
|
@@ -22291,7 +22714,11 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
22291
22714
|
function checkRg() {
|
|
22292
22715
|
return new Promise((resolve16) => {
|
|
22293
22716
|
try {
|
|
22294
|
-
const p = spawn15("rg", ["--version"], {
|
|
22717
|
+
const p = spawn15("rg", ["--version"], {
|
|
22718
|
+
env: buildChildEnv9(),
|
|
22719
|
+
stdio: "ignore",
|
|
22720
|
+
windowsHide: true
|
|
22721
|
+
});
|
|
22295
22722
|
p.on("error", () => resolve16(false));
|
|
22296
22723
|
p.on("close", (code) => resolve16(code === 0));
|
|
22297
22724
|
} catch {
|
|
@@ -22342,8 +22769,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
22342
22769
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
22343
22770
|
const full = path33.join(dir, e.name);
|
|
22344
22771
|
try {
|
|
22345
|
-
const
|
|
22346
|
-
if (
|
|
22772
|
+
const stat18 = await fs29.lstat(full);
|
|
22773
|
+
if (stat18.isSymbolicLink()) continue;
|
|
22347
22774
|
} catch {
|
|
22348
22775
|
continue;
|
|
22349
22776
|
}
|
|
@@ -22467,6 +22894,7 @@ var scaffoldTool = {
|
|
|
22467
22894
|
usageHint: "PREFERRED FOR SCAFFOLDING:\n\n- Use built-in templates when they match your needs (e.g. react-component, npm-package).\n- Supports `dry_run` so you can preview exactly what will be created.\n- Has the powerful `fs.write.outside-project` capability \u2014 review paths carefully.\nMuch cleaner and safer than manually writing multiple files.",
|
|
22468
22895
|
permission: "confirm",
|
|
22469
22896
|
mutating: true,
|
|
22897
|
+
subjectKey: "name",
|
|
22470
22898
|
capabilities: ["fs.write.outside-project", "fs.write"],
|
|
22471
22899
|
icon: "scaffold",
|
|
22472
22900
|
timeoutMs: 3e4,
|
|
@@ -22541,15 +22969,17 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
22541
22969
|
output: dryRun ? `Would create ${filesCreated} files: ${files.join(", ")}` : `Created ${filesCreated} files: ${files.join(", ")}`
|
|
22542
22970
|
};
|
|
22543
22971
|
}
|
|
22972
|
+
function escapeRegexLiteral(value) {
|
|
22973
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22974
|
+
}
|
|
22544
22975
|
function substituteVars(content, name, vars) {
|
|
22976
|
+
const kebab = name.toLowerCase().replace(/\s+/g, "-");
|
|
22977
|
+
const pascal = name.replace(/(?:^|[-_\s]+)([a-z])/g, (_, c) => c.toUpperCase());
|
|
22545
22978
|
let result = content;
|
|
22546
|
-
result = result.replace(/\{\{name\}\}/g,
|
|
22547
|
-
result = result.replace(
|
|
22548
|
-
/\{\{Name\}\}/g,
|
|
22549
|
-
name.replace(/(?:^|[-_\s]+)([a-z])/g, (_, c) => c.toUpperCase())
|
|
22550
|
-
);
|
|
22979
|
+
result = result.replace(/\{\{name\}\}/g, () => kebab);
|
|
22980
|
+
result = result.replace(/\{\{Name\}\}/g, () => pascal);
|
|
22551
22981
|
for (const [k, v] of Object.entries(vars)) {
|
|
22552
|
-
result = result.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), v);
|
|
22982
|
+
result = result.replace(new RegExp(`\\{\\{${escapeRegexLiteral(k)}\\}\\}`, "g"), () => v);
|
|
22553
22983
|
}
|
|
22554
22984
|
return result;
|
|
22555
22985
|
}
|
|
@@ -23485,11 +23915,11 @@ var testTool = {
|
|
|
23485
23915
|
}
|
|
23486
23916
|
};
|
|
23487
23917
|
async function detectRunner(cwd) {
|
|
23488
|
-
const { stat:
|
|
23918
|
+
const { stat: stat18 } = await import("node:fs/promises");
|
|
23489
23919
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
23490
23920
|
for (const f of candidates) {
|
|
23491
23921
|
try {
|
|
23492
|
-
await
|
|
23922
|
+
await stat18(path35.join(cwd, f));
|
|
23493
23923
|
if (f.includes("vitest")) return "vitest";
|
|
23494
23924
|
if (f.includes("jest")) return "jest";
|
|
23495
23925
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -23886,12 +24316,19 @@ var toolSearchTool = {
|
|
|
23886
24316
|
};
|
|
23887
24317
|
|
|
23888
24318
|
// src/tool-use.ts
|
|
24319
|
+
import {
|
|
24320
|
+
GOVERNED_TOOL_EXECUTOR_META_KEY as GOVERNED_TOOL_EXECUTOR_META_KEY2
|
|
24321
|
+
} from "@wrongstack/core/types";
|
|
23889
24322
|
var toolUseTool = {
|
|
23890
24323
|
name: "tool_use",
|
|
23891
24324
|
category: "Meta",
|
|
23892
24325
|
description: "Directly execute any registered tool by its exact name, bypassing normal discovery. This is a powerful meta-tool intended for cases where the agent has a clear plan and knows precisely which tool to invoke.",
|
|
23893
24326
|
usageHint: "ADVANCED META TOOL \u2014 USE WITH CARE:\n\n- Only use when you are certain of the exact tool name and its expected input shape.\n- Prefer using the normal tool calling mechanism when possible.\n- Very useful in batch-tool-use or when orchestrating complex workflows programmatically.\n- The call still goes through full permission checks and capability validation.",
|
|
23894
24327
|
permission: "confirm",
|
|
24328
|
+
// WS-046: gives permission decisions something to key on.
|
|
24329
|
+
// The tool being invoked through this indirection — the whole point of a
|
|
24330
|
+
// permission decision here is WHICH tool is being reached.
|
|
24331
|
+
subjectKey: "tool",
|
|
23895
24332
|
mutating: true,
|
|
23896
24333
|
timeoutMs: 6e4,
|
|
23897
24334
|
capabilities: ["tool.mutate.any"],
|
|
@@ -23910,7 +24347,7 @@ var toolUseTool = {
|
|
|
23910
24347
|
},
|
|
23911
24348
|
required: ["tool"]
|
|
23912
24349
|
},
|
|
23913
|
-
async execute(input, ctx
|
|
24350
|
+
async execute(input, ctx) {
|
|
23914
24351
|
const start = Date.now();
|
|
23915
24352
|
if (!input?.tool) {
|
|
23916
24353
|
return {
|
|
@@ -23929,6 +24366,14 @@ var toolUseTool = {
|
|
|
23929
24366
|
executionMs: Date.now() - start
|
|
23930
24367
|
};
|
|
23931
24368
|
}
|
|
24369
|
+
if (tool.name === toolUseTool.name) {
|
|
24370
|
+
return {
|
|
24371
|
+
tool: input.tool,
|
|
24372
|
+
success: false,
|
|
24373
|
+
error: "tool_use: recursive meta-tool execution is not allowed",
|
|
24374
|
+
executionMs: Date.now() - start
|
|
24375
|
+
};
|
|
24376
|
+
}
|
|
23932
24377
|
if (tool.permission === "deny") {
|
|
23933
24378
|
return {
|
|
23934
24379
|
tool: input.tool,
|
|
@@ -23937,12 +24382,21 @@ var toolUseTool = {
|
|
|
23937
24382
|
executionMs: Date.now() - start
|
|
23938
24383
|
};
|
|
23939
24384
|
}
|
|
24385
|
+
const governedExecute = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY2];
|
|
24386
|
+
if (typeof governedExecute !== "function") {
|
|
24387
|
+
return {
|
|
24388
|
+
tool: input.tool,
|
|
24389
|
+
success: false,
|
|
24390
|
+
error: "tool_use: governed nested execution is unavailable; call the tool directly",
|
|
24391
|
+
executionMs: Date.now() - start
|
|
24392
|
+
};
|
|
24393
|
+
}
|
|
23940
24394
|
try {
|
|
23941
|
-
const result = await
|
|
24395
|
+
const result = await governedExecute(input.tool, input.input ?? {});
|
|
23942
24396
|
return {
|
|
23943
24397
|
tool: input.tool,
|
|
23944
|
-
success:
|
|
23945
|
-
result,
|
|
24398
|
+
success: result.success,
|
|
24399
|
+
...result.success ? { result: result.result } : { error: result.error ?? "nested tool failed" },
|
|
23946
24400
|
executionMs: Date.now() - start
|
|
23947
24401
|
};
|
|
23948
24402
|
} catch (e) {
|
|
@@ -23958,10 +24412,12 @@ var toolUseTool = {
|
|
|
23958
24412
|
|
|
23959
24413
|
// src/tree.ts
|
|
23960
24414
|
init_util();
|
|
23961
|
-
import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
23962
24415
|
import * as fs32 from "node:fs/promises";
|
|
23963
24416
|
import * as path36 from "node:path";
|
|
24417
|
+
import { DEFAULT_WALK_IGNORE_DIRS as DEFAULT_WALK_IGNORE_DIRS4, expectDefined as expectDefined10 } from "@wrongstack/core/utils";
|
|
23964
24418
|
var DEFAULT_IGNORE5 = [...DEFAULT_WALK_IGNORE_DIRS4, ".wrongstack", ".ssh", ".gnupg", ".aws"];
|
|
24419
|
+
var DEFAULT_MAX_ENTRIES2 = 5e3;
|
|
24420
|
+
var MAX_TREE_OUTPUT_BYTES = 256 * 1024;
|
|
23965
24421
|
var treeTool = {
|
|
23966
24422
|
name: "tree",
|
|
23967
24423
|
category: "Filesystem",
|
|
@@ -24005,6 +24461,12 @@ var treeTool = {
|
|
|
24005
24461
|
show_hidden: {
|
|
24006
24462
|
type: "boolean",
|
|
24007
24463
|
description: "Show hidden files starting with . (default: false)"
|
|
24464
|
+
},
|
|
24465
|
+
max_entries: {
|
|
24466
|
+
type: "integer",
|
|
24467
|
+
description: `Maximum entries retained in the rendered tree (default ${DEFAULT_MAX_ENTRIES2}).`,
|
|
24468
|
+
minimum: 1,
|
|
24469
|
+
maximum: 5e4
|
|
24008
24470
|
}
|
|
24009
24471
|
}
|
|
24010
24472
|
},
|
|
@@ -24018,7 +24480,7 @@ var treeTool = {
|
|
|
24018
24480
|
if (!final) throw new Error("tree: stream ended without final event");
|
|
24019
24481
|
return final;
|
|
24020
24482
|
},
|
|
24021
|
-
async *executeStream(input, ctx) {
|
|
24483
|
+
async *executeStream(input, ctx, opts) {
|
|
24022
24484
|
const basePath = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
|
|
24023
24485
|
const maxDepth = input.depth ?? 3;
|
|
24024
24486
|
const showFiles = input.show_files ?? true;
|
|
@@ -24026,8 +24488,17 @@ var treeTool = {
|
|
|
24026
24488
|
const showHidden = input.show_hidden ?? false;
|
|
24027
24489
|
const exclude = /* @__PURE__ */ new Set([...DEFAULT_IGNORE5, ...input.exclude ?? []]);
|
|
24028
24490
|
const filterGlob = input.glob;
|
|
24491
|
+
const maxEntries = input.max_entries ?? DEFAULT_MAX_ENTRIES2;
|
|
24492
|
+
const signal = opts?.signal ?? ctx.signal ?? new AbortController().signal;
|
|
24029
24493
|
const lines = [basePath];
|
|
24030
24494
|
const totals = { totalFiles: { value: 0 }, totalDirs: { value: 0 } };
|
|
24495
|
+
const retention = {
|
|
24496
|
+
entries: 0,
|
|
24497
|
+
outputBytes: Buffer.byteLength(basePath, "utf8"),
|
|
24498
|
+
maxEntries,
|
|
24499
|
+
maxOutputBytes: MAX_TREE_OUTPUT_BYTES,
|
|
24500
|
+
truncated: false
|
|
24501
|
+
};
|
|
24031
24502
|
const queue = [];
|
|
24032
24503
|
const FLUSH_EVERY = 200;
|
|
24033
24504
|
let lastEmittedTotal = 0;
|
|
@@ -24054,12 +24525,19 @@ var treeTool = {
|
|
|
24054
24525
|
isLast: true,
|
|
24055
24526
|
totalFiles: totals.totalFiles,
|
|
24056
24527
|
totalDirs: totals.totalDirs,
|
|
24057
|
-
onProgress: tickProgress
|
|
24528
|
+
onProgress: tickProgress,
|
|
24529
|
+
retention,
|
|
24530
|
+
signal
|
|
24058
24531
|
});
|
|
24059
24532
|
let walkDone = false;
|
|
24060
|
-
walkPromise.
|
|
24061
|
-
|
|
24062
|
-
|
|
24533
|
+
void walkPromise.then(
|
|
24534
|
+
() => {
|
|
24535
|
+
walkDone = true;
|
|
24536
|
+
},
|
|
24537
|
+
() => {
|
|
24538
|
+
walkDone = true;
|
|
24539
|
+
}
|
|
24540
|
+
);
|
|
24063
24541
|
while (!walkDone || queue.length > 0) {
|
|
24064
24542
|
if (queue.length > 0) {
|
|
24065
24543
|
yield expectDefined10(queue.shift());
|
|
@@ -24082,13 +24560,15 @@ var treeTool = {
|
|
|
24082
24560
|
tree: lines.join("\n"),
|
|
24083
24561
|
total_files: totals.totalFiles.value,
|
|
24084
24562
|
total_dirs: totals.totalDirs.value,
|
|
24085
|
-
truncated:
|
|
24563
|
+
truncated: retention.truncated,
|
|
24086
24564
|
path: basePath
|
|
24087
24565
|
}
|
|
24088
24566
|
};
|
|
24089
24567
|
}
|
|
24090
24568
|
};
|
|
24091
24569
|
async function walkDir(dir, depth, opts) {
|
|
24570
|
+
opts.signal.throwIfAborted();
|
|
24571
|
+
if (opts.retention.truncated) return;
|
|
24092
24572
|
const entries = await fs32.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
24093
24573
|
const filtered = entries.filter((e) => {
|
|
24094
24574
|
if (!opts.showHidden && e.name.startsWith(".")) return false;
|
|
@@ -24108,6 +24588,8 @@ async function walkDir(dir, depth, opts) {
|
|
|
24108
24588
|
return a.name.localeCompare(b.name);
|
|
24109
24589
|
});
|
|
24110
24590
|
for (let i = 0; i < items.length; i++) {
|
|
24591
|
+
opts.signal.throwIfAborted();
|
|
24592
|
+
if (opts.retention.truncated) return;
|
|
24111
24593
|
const entry = items[i];
|
|
24112
24594
|
if (!entry) continue;
|
|
24113
24595
|
const isLast = i === items.length - 1;
|
|
@@ -24116,7 +24598,15 @@ async function walkDir(dir, depth, opts) {
|
|
|
24116
24598
|
const displayName = entry.name + (entry.isDirectory() ? "/" : "");
|
|
24117
24599
|
if (!opts.showDirs && entry.isDirectory()) continue;
|
|
24118
24600
|
if (!opts.showFiles && entry.isFile()) continue;
|
|
24119
|
-
opts.
|
|
24601
|
+
const line = opts.prefix + branch + displayName;
|
|
24602
|
+
const lineBytes = Buffer.byteLength(line, "utf8") + 1;
|
|
24603
|
+
if (opts.retention.entries >= opts.retention.maxEntries || opts.retention.outputBytes + lineBytes > opts.retention.maxOutputBytes) {
|
|
24604
|
+
opts.retention.truncated = true;
|
|
24605
|
+
return;
|
|
24606
|
+
}
|
|
24607
|
+
opts.lines.push(line);
|
|
24608
|
+
opts.retention.entries++;
|
|
24609
|
+
opts.retention.outputBytes += lineBytes;
|
|
24120
24610
|
if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
|
|
24121
24611
|
const childPrefix = opts.prefix + connector;
|
|
24122
24612
|
await walkDir(path36.join(dir, entry.name), depth + 1, {
|
|
@@ -24231,11 +24721,11 @@ var typecheckTool = {
|
|
|
24231
24721
|
}
|
|
24232
24722
|
};
|
|
24233
24723
|
async function findTsConfig(cwd) {
|
|
24234
|
-
const { stat:
|
|
24724
|
+
const { stat: stat18 } = await import("node:fs/promises");
|
|
24235
24725
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
24236
24726
|
for (const f of candidates) {
|
|
24237
24727
|
try {
|
|
24238
|
-
const s = await
|
|
24728
|
+
const s = await stat18(path37.join(cwd, f));
|
|
24239
24729
|
if (s.isFile()) return path37.join(cwd, f);
|
|
24240
24730
|
} catch {
|
|
24241
24731
|
}
|
|
@@ -24310,12 +24800,12 @@ async function prepareWrite(input, ctx) {
|
|
|
24310
24800
|
let existed = false;
|
|
24311
24801
|
let prev = "";
|
|
24312
24802
|
try {
|
|
24313
|
-
const
|
|
24314
|
-
existed =
|
|
24803
|
+
const stat18 = await fs33.stat(absPath);
|
|
24804
|
+
existed = stat18.isFile();
|
|
24315
24805
|
if (existed) {
|
|
24316
24806
|
if (!ctx.hasRead(absPath)) {
|
|
24317
24807
|
prev = await fs33.readFile(absPath, "utf8");
|
|
24318
|
-
ctx.recordRead(absPath,
|
|
24808
|
+
ctx.recordRead(absPath, stat18.mtimeMs, "write", sha256hex(prev));
|
|
24319
24809
|
} else {
|
|
24320
24810
|
prev = await fs33.readFile(absPath, "utf8");
|
|
24321
24811
|
}
|
|
@@ -24332,8 +24822,8 @@ async function finishWrite(input, ctx, prepared, signal) {
|
|
|
24332
24822
|
await atomicWrite5(prepared.absPath, input.content);
|
|
24333
24823
|
const diff = prepared.existed ? unifiedDiff3(prepared.prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
|
|
24334
24824
|
+ (new file, ${input.content.split("\n").length} lines)`;
|
|
24335
|
-
const
|
|
24336
|
-
ctx.recordRead(prepared.absPath,
|
|
24825
|
+
const stat18 = await fs33.stat(prepared.absPath);
|
|
24826
|
+
ctx.recordRead(prepared.absPath, stat18.mtimeMs, "write", sha256hex(input.content));
|
|
24337
24827
|
ctx.session.recordFileChange({
|
|
24338
24828
|
path: prepared.absPath,
|
|
24339
24829
|
action: prepared.existed ? "modified" : "created",
|