@wrongstack/tools 0.296.4 → 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 +128 -65
- package/dist/bash.js +20 -4
- package/dist/batch-tool-use.js +70 -7
- package/dist/browser/index.js +31 -1
- package/dist/builtin.js +883 -351
- package/dist/codebase-index/index.js +95 -16
- package/dist/codebase-index/project-server-protocol.d.ts +26 -0
- package/dist/codebase-index/project-server.js +76 -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 +562 -516
- package/dist/format.js +131 -65
- package/dist/git.js +22 -2
- package/dist/glob.js +11 -5
- package/dist/grep.js +160 -104
- package/dist/index.js +834 -290
- package/dist/install.js +135 -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 +143 -71
- package/dist/lint.js +128 -65
- package/dist/next-steps.d.ts +23 -0
- package/dist/next-steps.js +4 -0
- package/dist/outdated.js +101 -26
- package/dist/pack.js +883 -351
- package/dist/patch.js +11 -1
- package/dist/plan.js +32 -10
- package/dist/process-registry.d.ts +9 -0
- package/dist/process-registry.js +15 -0
- package/dist/ps-slash.js +15 -0
- package/dist/read.js +104 -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 +128 -65
- package/dist/todo.js +3 -1
- package/dist/tool-tier.js +883 -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 +128 -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/format.js
CHANGED
|
@@ -78,11 +78,12 @@ function createOutputSpool(opts) {
|
|
|
78
78
|
return {
|
|
79
79
|
write(text) {
|
|
80
80
|
if (finalized || !text) return;
|
|
81
|
-
|
|
81
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
82
|
+
totalBytes += textBytes;
|
|
82
83
|
if (!stream && !failed) {
|
|
83
|
-
if (headBytes +
|
|
84
|
+
if (headBytes + textBytes <= threshold) {
|
|
84
85
|
head += text;
|
|
85
|
-
headBytes +=
|
|
86
|
+
headBytes += textBytes;
|
|
86
87
|
return;
|
|
87
88
|
}
|
|
88
89
|
head += text;
|
|
@@ -92,7 +93,7 @@ function createOutputSpool(opts) {
|
|
|
92
93
|
}
|
|
93
94
|
if (stream) {
|
|
94
95
|
if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
|
|
95
|
-
droppedBytes +=
|
|
96
|
+
droppedBytes += textBytes;
|
|
96
97
|
return;
|
|
97
98
|
}
|
|
98
99
|
stream.write(text);
|
|
@@ -114,6 +115,52 @@ function createOutputSpool(opts) {
|
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// src/_win32-resolve.ts
|
|
119
|
+
import * as fs from "node:fs";
|
|
120
|
+
import * as path2 from "node:path";
|
|
121
|
+
function resolveWin32Command(cmd) {
|
|
122
|
+
if (process.platform !== "win32") return cmd;
|
|
123
|
+
if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
|
|
124
|
+
return cmd;
|
|
125
|
+
}
|
|
126
|
+
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
127
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
|
|
128
|
+
for (const dir of pathDirs) {
|
|
129
|
+
const base = path2.join(dir, cmd);
|
|
130
|
+
for (const ext of pathext) {
|
|
131
|
+
const full = `${base}${ext}`;
|
|
132
|
+
try {
|
|
133
|
+
fs.accessSync(full, fs.constants.X_OK);
|
|
134
|
+
return full;
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return cmd;
|
|
140
|
+
}
|
|
141
|
+
var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
|
|
142
|
+
function assertSafeWin32ShellArgs(args) {
|
|
143
|
+
for (const arg of args) {
|
|
144
|
+
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
'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)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function buildWin32CmdShimInvocation(command, args = []) {
|
|
152
|
+
assertSafeWin32ShellArgs([command, ...args]);
|
|
153
|
+
const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
|
|
154
|
+
return {
|
|
155
|
+
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
156
|
+
args: ["/d", "/c", line],
|
|
157
|
+
windowsVerbatimArguments: true
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function quoteWin32CmdArg(arg) {
|
|
161
|
+
return `"${arg}"`;
|
|
162
|
+
}
|
|
163
|
+
|
|
117
164
|
// src/process-registry.ts
|
|
118
165
|
import { spawn } from "node:child_process";
|
|
119
166
|
import * as os from "node:os";
|
|
@@ -445,6 +492,7 @@ var ProcessRegistryImpl = class {
|
|
|
445
492
|
}
|
|
446
493
|
/** Get all tracked processes. */
|
|
447
494
|
list() {
|
|
495
|
+
this._pruneAllStale();
|
|
448
496
|
return Array.from(this.processes.values());
|
|
449
497
|
}
|
|
450
498
|
/** Get processes filtered by name (e.g. 'bash', 'exec'). */
|
|
@@ -475,6 +523,7 @@ var ProcessRegistryImpl = class {
|
|
|
475
523
|
* Combined stats for observability — used by /ps and the TUI status bar.
|
|
476
524
|
*/
|
|
477
525
|
stats() {
|
|
526
|
+
this._pruneAllStale();
|
|
478
527
|
return {
|
|
479
528
|
activeCount: this.activeCount,
|
|
480
529
|
backgroundCount: this.activeBackgroundCount,
|
|
@@ -714,6 +763,19 @@ var ProcessRegistryImpl = class {
|
|
|
714
763
|
this.processes.delete(pid);
|
|
715
764
|
}
|
|
716
765
|
}
|
|
766
|
+
/**
|
|
767
|
+
* Remove every stale entry, not just one PID. `list()`/`stats()` — the
|
|
768
|
+
* surfaces the TUI status bar and `/ps` poll — must prune too: a child
|
|
769
|
+
* whose 'close' event never fires (e.g. Windows grandchildren holding stdio
|
|
770
|
+
* open) would otherwise linger in the registry until someone looks up its
|
|
771
|
+
* exact PID, and PID reuse meanwhile makes `get()`/`kill()` target the
|
|
772
|
+
* wrong process. RAM-leak audit 2026-07-31, MEDIUM.
|
|
773
|
+
*/
|
|
774
|
+
_pruneAllStale() {
|
|
775
|
+
for (const [pid, entry] of this.processes) {
|
|
776
|
+
if (this._isStaleEntry(entry)) this.processes.delete(pid);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
717
779
|
};
|
|
718
780
|
var _registry;
|
|
719
781
|
function getProcessRegistry() {
|
|
@@ -723,60 +785,17 @@ function getProcessRegistry() {
|
|
|
723
785
|
return _registry;
|
|
724
786
|
}
|
|
725
787
|
|
|
726
|
-
// src/_win32-resolve.ts
|
|
727
|
-
import * as fs from "node:fs";
|
|
728
|
-
import * as path2 from "node:path";
|
|
729
|
-
function resolveWin32Command(cmd) {
|
|
730
|
-
if (process.platform !== "win32") return cmd;
|
|
731
|
-
if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
|
|
732
|
-
return cmd;
|
|
733
|
-
}
|
|
734
|
-
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
735
|
-
const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
|
|
736
|
-
for (const dir of pathDirs) {
|
|
737
|
-
const base = path2.join(dir, cmd);
|
|
738
|
-
for (const ext of pathext) {
|
|
739
|
-
const full = `${base}${ext}`;
|
|
740
|
-
try {
|
|
741
|
-
fs.accessSync(full, fs.constants.X_OK);
|
|
742
|
-
return full;
|
|
743
|
-
} catch {
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
return cmd;
|
|
748
|
-
}
|
|
749
|
-
var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
|
|
750
|
-
function assertSafeWin32ShellArgs(args) {
|
|
751
|
-
for (const arg of args) {
|
|
752
|
-
if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
|
|
753
|
-
throw new Error(
|
|
754
|
-
'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)
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function buildWin32CmdShimInvocation(command, args = []) {
|
|
760
|
-
assertSafeWin32ShellArgs([command, ...args]);
|
|
761
|
-
const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
|
|
762
|
-
return {
|
|
763
|
-
command: process.env["COMSPEC"] ?? "cmd.exe",
|
|
764
|
-
args: ["/d", "/c", line],
|
|
765
|
-
windowsVerbatimArguments: true
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
function quoteWin32CmdArg(arg) {
|
|
769
|
-
return `"${arg}"`;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
788
|
// src/_spawn-stream.ts
|
|
773
789
|
var isWin = process.platform === "win32";
|
|
774
790
|
async function* spawnStream(opts) {
|
|
775
791
|
const max = opts.maxBytes ?? 2e5;
|
|
776
792
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
777
793
|
const maxQueue = opts.maxQueueSize ?? 500;
|
|
794
|
+
const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
|
|
778
795
|
let stdout = "";
|
|
779
796
|
let stderr = "";
|
|
797
|
+
let stdoutRetainedBytes = 0;
|
|
798
|
+
let stderrRetainedBytes = 0;
|
|
780
799
|
let pending = "";
|
|
781
800
|
let error;
|
|
782
801
|
const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
|
|
@@ -818,6 +837,7 @@ async function* spawnStream(opts) {
|
|
|
818
837
|
});
|
|
819
838
|
}
|
|
820
839
|
const queue = [];
|
|
840
|
+
let queuedBytes = 0;
|
|
821
841
|
let waiter;
|
|
822
842
|
let paused = false;
|
|
823
843
|
const wake = () => {
|
|
@@ -828,7 +848,7 @@ async function* spawnStream(opts) {
|
|
|
828
848
|
}
|
|
829
849
|
};
|
|
830
850
|
const resume = () => {
|
|
831
|
-
if (paused && queue.length
|
|
851
|
+
if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
|
|
832
852
|
paused = false;
|
|
833
853
|
child.stdout?.resume();
|
|
834
854
|
child.stderr?.resume();
|
|
@@ -838,11 +858,16 @@ async function* spawnStream(opts) {
|
|
|
838
858
|
const s = c.toString();
|
|
839
859
|
stdoutBytes += c.byteLength;
|
|
840
860
|
emitProcessOutput({ pid, stream: "stdout", chunk: c });
|
|
841
|
-
if (
|
|
861
|
+
if (stdoutRetainedBytes < max) {
|
|
862
|
+
const retained = utf8Prefix(s, max - stdoutRetainedBytes);
|
|
863
|
+
stdout += retained;
|
|
864
|
+
stdoutRetainedBytes += Buffer.byteLength(retained, "utf8");
|
|
865
|
+
}
|
|
842
866
|
spool.write(s);
|
|
843
|
-
queue.push({ kind: "out", data: s });
|
|
867
|
+
queue.push({ kind: "out", data: s, bytes: c.byteLength });
|
|
868
|
+
queuedBytes += c.byteLength;
|
|
844
869
|
wake();
|
|
845
|
-
if (!paused && queue.length >= maxQueue) {
|
|
870
|
+
if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
|
|
846
871
|
paused = true;
|
|
847
872
|
child.stdout?.pause();
|
|
848
873
|
child.stderr?.pause();
|
|
@@ -852,11 +877,16 @@ async function* spawnStream(opts) {
|
|
|
852
877
|
const s = c.toString();
|
|
853
878
|
stderrBytes += c.byteLength;
|
|
854
879
|
emitProcessOutput({ pid, stream: "stderr", chunk: c });
|
|
855
|
-
if (
|
|
880
|
+
if (stderrRetainedBytes < max) {
|
|
881
|
+
const retained = utf8Prefix(s, max - stderrRetainedBytes);
|
|
882
|
+
stderr += retained;
|
|
883
|
+
stderrRetainedBytes += Buffer.byteLength(retained, "utf8");
|
|
884
|
+
}
|
|
856
885
|
spool.write(s);
|
|
857
|
-
queue.push({ kind: "err", data: s });
|
|
886
|
+
queue.push({ kind: "err", data: s, bytes: c.byteLength });
|
|
887
|
+
queuedBytes += c.byteLength;
|
|
858
888
|
wake();
|
|
859
|
-
if (!paused && queue.length >= maxQueue) {
|
|
889
|
+
if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
|
|
860
890
|
paused = true;
|
|
861
891
|
child.stdout?.pause();
|
|
862
892
|
child.stderr?.pause();
|
|
@@ -866,7 +896,9 @@ async function* spawnStream(opts) {
|
|
|
866
896
|
child.stderr?.on("data", onErr);
|
|
867
897
|
child.on("error", (e) => {
|
|
868
898
|
error = e.message;
|
|
869
|
-
|
|
899
|
+
const bytes = Buffer.byteLength(e.message, "utf8");
|
|
900
|
+
queue.push({ kind: "error", data: e.message, bytes });
|
|
901
|
+
queuedBytes += bytes;
|
|
870
902
|
wake();
|
|
871
903
|
});
|
|
872
904
|
const completeTelemetry = (code, signal, timedOut = false) => {
|
|
@@ -887,7 +919,13 @@ async function* spawnStream(opts) {
|
|
|
887
919
|
if (typeof pid === "number") registry.unregister(pid);
|
|
888
920
|
const exitCode2 = code ?? (signal ? 1 : 0);
|
|
889
921
|
completeTelemetry(exitCode2, signal ?? void 0);
|
|
890
|
-
queue.push({
|
|
922
|
+
queue.push({
|
|
923
|
+
kind: "close",
|
|
924
|
+
data: "",
|
|
925
|
+
bytes: 0,
|
|
926
|
+
code: exitCode2,
|
|
927
|
+
...signal ? { signal } : {}
|
|
928
|
+
});
|
|
891
929
|
wake();
|
|
892
930
|
});
|
|
893
931
|
const onAbort = () => {
|
|
@@ -899,7 +937,7 @@ async function* spawnStream(opts) {
|
|
|
899
937
|
} catch {
|
|
900
938
|
}
|
|
901
939
|
}
|
|
902
|
-
queue.push({ kind: "close", data: "", code: 124 });
|
|
940
|
+
queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
|
|
903
941
|
completeTelemetry(124, "SIGKILL", true);
|
|
904
942
|
wake();
|
|
905
943
|
};
|
|
@@ -917,6 +955,7 @@ async function* spawnStream(opts) {
|
|
|
917
955
|
});
|
|
918
956
|
}
|
|
919
957
|
const chunk = queue.shift();
|
|
958
|
+
queuedBytes -= chunk.bytes;
|
|
920
959
|
resume();
|
|
921
960
|
if (chunk.kind === "close") {
|
|
922
961
|
if (!spawnFailed) exitCode = chunk.code ?? 0;
|
|
@@ -943,7 +982,7 @@ async function* spawnStream(opts) {
|
|
|
943
982
|
stdout: spooled ? stdout + spoolNote(spooled) : stdout,
|
|
944
983
|
stderr,
|
|
945
984
|
exitCode,
|
|
946
|
-
truncated:
|
|
985
|
+
truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
|
|
947
986
|
error,
|
|
948
987
|
spoolPath: spooled?.path,
|
|
949
988
|
spoolBytes: spooled?.bytes
|
|
@@ -967,6 +1006,20 @@ async function* spawnStream(opts) {
|
|
|
967
1006
|
}
|
|
968
1007
|
}
|
|
969
1008
|
}
|
|
1009
|
+
function utf8Prefix(text, maxBytes) {
|
|
1010
|
+
if (maxBytes <= 0) return "";
|
|
1011
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
1012
|
+
let low = 0;
|
|
1013
|
+
let high = text.length;
|
|
1014
|
+
while (low < high) {
|
|
1015
|
+
const mid = Math.ceil((low + high) / 2);
|
|
1016
|
+
if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
|
|
1017
|
+
else high = mid - 1;
|
|
1018
|
+
}
|
|
1019
|
+
let end = low;
|
|
1020
|
+
if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
|
|
1021
|
+
return text.slice(0, end);
|
|
1022
|
+
}
|
|
970
1023
|
|
|
971
1024
|
// src/_util.ts
|
|
972
1025
|
import * as path3 from "node:path";
|
|
@@ -2534,8 +2587,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
|
|
|
2534
2587
|
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2535
2588
|
}
|
|
2536
2589
|
}
|
|
2537
|
-
function collectFileEvidence(directory, fullPath,
|
|
2538
|
-
const lower =
|
|
2590
|
+
function collectFileEvidence(directory, fullPath, basename5, profiles, state) {
|
|
2591
|
+
const lower = basename5.toLowerCase();
|
|
2539
2592
|
const extension = path4.extname(lower);
|
|
2540
2593
|
for (const profile of profiles) {
|
|
2541
2594
|
const detector = profile.detectors.find(
|
|
@@ -2546,7 +2599,7 @@ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
|
|
|
2546
2599
|
candidate.evidence.push({
|
|
2547
2600
|
kind: detector.kind,
|
|
2548
2601
|
path: fullPath,
|
|
2549
|
-
value:
|
|
2602
|
+
value: basename5,
|
|
2550
2603
|
weight: detector.weight
|
|
2551
2604
|
});
|
|
2552
2605
|
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
@@ -2966,10 +3019,11 @@ var MAX_ARGUMENTS = 128;
|
|
|
2966
3019
|
var MAX_ARGUMENT_LENGTH = 4096;
|
|
2967
3020
|
var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
2968
3021
|
var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
2969
|
-
var GO_MODULE_RE = /^(?:[a-z0-9
|
|
3022
|
+
var GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
|
|
2970
3023
|
var PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
|
|
2971
3024
|
async function planLanguageOperation(options) {
|
|
2972
3025
|
validateOperationOptions(options.operation, options.operationOptions?.packages);
|
|
3026
|
+
validateFilterOption(options.operationOptions?.filter);
|
|
2973
3027
|
const detection = await detectLanguageWorkspaces(options);
|
|
2974
3028
|
const candidates = detection.workspaces.filter(
|
|
2975
3029
|
(workspace) => workspace.capabilities.includes(options.operation)
|
|
@@ -3142,6 +3196,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
|
|
|
3142
3196
|
const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
|
|
3143
3197
|
return relative5 === "" ? 0 : relative5.split(path6.sep).length;
|
|
3144
3198
|
}
|
|
3199
|
+
function validateFilterOption(filter) {
|
|
3200
|
+
if (filter === void 0) return;
|
|
3201
|
+
if (filter.startsWith("-") || filter.includes(" --")) {
|
|
3202
|
+
throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
|
|
3203
|
+
}
|
|
3204
|
+
if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
|
|
3205
|
+
throw new Error("Invalid test filter (oversized or contains a control character)");
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3145
3208
|
function validateOperationOptions(operation, packages) {
|
|
3146
3209
|
if (!operation.startsWith("package-") || !packages) return;
|
|
3147
3210
|
for (const value of packages) {
|
|
@@ -3471,6 +3534,9 @@ var formatTool = {
|
|
|
3471
3534
|
description: "Format source files according to project style (Biome). Can also run in check-only mode.",
|
|
3472
3535
|
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.",
|
|
3473
3536
|
permission: "confirm",
|
|
3537
|
+
// WS-046: gives permission decisions something to key on.
|
|
3538
|
+
// The files being rewritten are the subject; the fixer is how, not what.
|
|
3539
|
+
subjectKey: "files",
|
|
3474
3540
|
mutating: true,
|
|
3475
3541
|
capabilities: ["fs.write", "shell.restricted"],
|
|
3476
3542
|
icon: "code",
|
package/dist/git.js
CHANGED
|
@@ -91,6 +91,10 @@ var gitTool = {
|
|
|
91
91
|
// Conservative: any of these may mutate. The non-mutating commands
|
|
92
92
|
// (status/log/diff/branch/fetch) are still gated on `permission: 'confirm'`
|
|
93
93
|
// and `MUTATING_SUBCOMMANDS` is consulted at runtime for per-call checks.
|
|
94
|
+
// WS-046: gives permission decisions something to key on.
|
|
95
|
+
// The git subcommand (status/commit/push) is what a trust rule needs to
|
|
96
|
+
// distinguish; `git status` and `git push --force` must not share a subject.
|
|
97
|
+
subjectKey: "command",
|
|
94
98
|
mutating: true,
|
|
95
99
|
capabilities: ["fs.write", "shell.restricted"],
|
|
96
100
|
timeoutMs: TIMEOUT_MS,
|
|
@@ -159,6 +163,8 @@ var gitTool = {
|
|
|
159
163
|
truncated: false
|
|
160
164
|
};
|
|
161
165
|
}
|
|
166
|
+
const branchGuard = validateBranchInput(input);
|
|
167
|
+
if (branchGuard) return branchGuard;
|
|
162
168
|
if (input.command === "worktree") {
|
|
163
169
|
const guard = validateWorktreeInput(input, ctx.projectRoot);
|
|
164
170
|
if (guard) return guard;
|
|
@@ -207,6 +213,18 @@ var gitTool = {
|
|
|
207
213
|
return result;
|
|
208
214
|
}
|
|
209
215
|
};
|
|
216
|
+
function validateBranchInput(input) {
|
|
217
|
+
const branch = input.branch;
|
|
218
|
+
if (branch === void 0) return null;
|
|
219
|
+
if (!branch.startsWith("-") && !branch.includes(" --")) return null;
|
|
220
|
+
return {
|
|
221
|
+
command: input.command,
|
|
222
|
+
stdout: "",
|
|
223
|
+
stderr: `unsafe branch name (parsed as a git option): ${branch}`,
|
|
224
|
+
exitCode: 1,
|
|
225
|
+
truncated: false
|
|
226
|
+
};
|
|
227
|
+
}
|
|
210
228
|
function validateWorktreeInput(input, projectRoot) {
|
|
211
229
|
const reject = (stderr) => ({
|
|
212
230
|
command: "worktree",
|
|
@@ -215,7 +233,6 @@ function validateWorktreeInput(input, projectRoot) {
|
|
|
215
233
|
exitCode: 1,
|
|
216
234
|
truncated: false
|
|
217
235
|
});
|
|
218
|
-
if (input.branch?.startsWith("-")) return reject(`unsafe branch name: ${input.branch}`);
|
|
219
236
|
if (input.worktreePath?.startsWith("-")) {
|
|
220
237
|
return reject(`unsafe worktree path: ${input.worktreePath}`);
|
|
221
238
|
}
|
|
@@ -269,7 +286,10 @@ function buildArgs(input) {
|
|
|
269
286
|
...files.length ? ["--", ...files] : []
|
|
270
287
|
];
|
|
271
288
|
case "branch":
|
|
272
|
-
return input.branch ? [
|
|
289
|
+
return input.branch ? [
|
|
290
|
+
"branch",
|
|
291
|
+
...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]
|
|
292
|
+
] : ["branch"];
|
|
273
293
|
case "checkout":
|
|
274
294
|
return [
|
|
275
295
|
"checkout",
|
package/dist/glob.js
CHANGED
|
@@ -101,11 +101,15 @@ function safeResolve(input, ctx) {
|
|
|
101
101
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
102
102
|
}
|
|
103
103
|
async function assertRealInsideRoot(absPath, ctx) {
|
|
104
|
-
|
|
104
|
+
await resolveRealInsideRoot(absPath, ctx);
|
|
105
|
+
}
|
|
106
|
+
async function resolveRealInsideRoot(absPath, ctx) {
|
|
107
|
+
if (ctx.allowOutsideProjectRoot) return absPath;
|
|
105
108
|
const realRoots = await Promise.all(
|
|
106
109
|
allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path2.resolve(r)))
|
|
107
110
|
);
|
|
108
111
|
let probe = absPath;
|
|
112
|
+
const pendingTail = [];
|
|
109
113
|
for (; ; ) {
|
|
110
114
|
let real;
|
|
111
115
|
try {
|
|
@@ -113,13 +117,16 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
113
117
|
} catch (err) {
|
|
114
118
|
if (err.code === "ENOENT") {
|
|
115
119
|
const parent = path2.dirname(probe);
|
|
116
|
-
if (parent === probe) return;
|
|
120
|
+
if (parent === probe) return absPath;
|
|
121
|
+
pendingTail.unshift(path2.basename(probe));
|
|
117
122
|
probe = parent;
|
|
118
123
|
continue;
|
|
119
124
|
}
|
|
120
125
|
throw err;
|
|
121
126
|
}
|
|
122
|
-
if (isInsideAny(real, realRoots))
|
|
127
|
+
if (isInsideAny(real, realRoots)) {
|
|
128
|
+
return pendingTail.length > 0 ? path2.join(real, ...pendingTail) : real;
|
|
129
|
+
}
|
|
123
130
|
throw new Error(
|
|
124
131
|
`Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
|
|
125
132
|
);
|
|
@@ -127,8 +134,7 @@ async function assertRealInsideRoot(absPath, ctx) {
|
|
|
127
134
|
}
|
|
128
135
|
async function safeResolveReal(input, ctx) {
|
|
129
136
|
const abs = safeResolve(input, ctx);
|
|
130
|
-
await
|
|
131
|
-
return abs;
|
|
137
|
+
return await resolveRealInsideRoot(abs, ctx);
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
// src/glob.ts
|