@henols/vice-mcp 0.2.1 → 0.2.2
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/README.md +2 -1
- package/THIRD-PARTY-NOTICES.md +1 -24
- package/{r2000-acme-ident.ts → anno-acme-ident.ts} +13 -13
- package/anno-cli.ts +1465 -0
- package/{r2000-confidence.ts → anno-confidence.ts} +22 -22
- package/anno-coverage.ts +2465 -0
- package/{r2000-d64.ts → anno-d64.ts} +5 -5
- package/anno-derive.ts +590 -0
- package/anno-details.ts +169 -0
- package/anno-enum-gen.ts +533 -0
- package/anno-export-asm.ts +1310 -0
- package/anno-index.ts +150 -0
- package/{r2000-memmap-render.ts → anno-memmap-render.ts} +236 -95
- package/{r2000-regbits-gen.ts → anno-regbits-gen.ts} +20 -15
- package/{r2000-regbits.json → anno-regbits.json} +2 -2
- package/anno-register.ts +240 -0
- package/anno-store.ts +3486 -0
- package/anno-symbols.ts +266 -0
- package/anno-tools.ts +2111 -0
- package/anno-types.ts +1636 -0
- package/block-class.ts +201 -0
- package/build.ts +1 -1
- package/capability-registry.ts +3 -1
- package/disasm-decoder.ts +14 -14
- package/disasm-opcodes.ts +4 -4
- package/disasm-renderer.ts +2 -2
- package/hostpath.ts +1 -1
- package/install-resources.ts +1 -1
- package/package.json +23 -17
- package/prg-image.ts +119 -0
- package/repo-root.ts +20 -5
- package/resources/broker-launch.mjs +8 -4
- package/resources/vice-launcher.sh +3 -3
- package/stock-address.ts +5 -5
- package/stock-cia.ts +2 -2
- package/stock-condition.ts +7 -7
- package/stock-connect.ts +1 -1
- package/stock-dispatch.ts +35 -5
- package/stock-execution.ts +5 -3
- package/stock-input.ts +9 -9
- package/stock-machine.ts +17 -6
- package/stock-protocol.ts +16 -11
- package/stock-registers.ts +54 -29
- package/stock-sprites.ts +3 -3
- package/stock-symbols.ts +9 -9
- package/stock-timing.ts +1 -1
- package/stock-vicii.ts +1 -1
- package/version.ts +1 -1
- package/vice-proxy.ts +68 -46
- package/r2000-cli.ts +0 -1103
- package/r2000-enum-gen.ts +0 -574
- package/r2000-launch.ts +0 -357
- package/r2000-mcp-client.ts +0 -596
- package/r2000-project.ts +0 -190
- package/r2000-symbols.ts +0 -388
- package/r2000-tools.ts +0 -914
- package/r2000-verify.ts +0 -184
package/r2000-launch.ts
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// This module's original header (through 11.1-03) claimed to be the only
|
|
3
|
-
// place in this repo that shells out to regenerator2000. That claim was
|
|
4
|
-
// WRONG and is corrected below (INT-02/D-11.1-05) -- noted here, not
|
|
5
|
-
// deleted outright, because this line is the sentence the correction below
|
|
6
|
-
// is against.
|
|
7
|
-
//
|
|
8
|
-
// Why this seam exists at all (D-06): this directory is the only place a
|
|
9
|
-
// guard test actually runs in CI -- `hostpath-consumers.test.ts`'s
|
|
10
|
-
// closed-consumer-set machinery lives here, and CI's `npm test` never
|
|
11
|
-
// reaches skill-side `*.test.mjs` scripts. A guard test living beside a
|
|
12
|
-
// skill script would be green-by-absence. Phase 11's `r2000_*` MCP surface
|
|
13
|
-
// is also planned to land in this same directory, so this module is the
|
|
14
|
-
// natural first tenant rather than a throwaway.
|
|
15
|
-
//
|
|
16
|
-
// CORRECTED CLAIM (INT-02/D-11.1-05): this is NOT the only file that spawns
|
|
17
|
-
// regenerator2000 -- `r2000-mcp-client.ts` is a second, necessary spawn
|
|
18
|
-
// site, and its own header already said so correctly ("both spawn call
|
|
19
|
-
// sites in this repo") while this file's original claim above said the
|
|
20
|
-
// opposite. What this file IS actually the one authoritative place for:
|
|
21
|
-
// - the `--vice` guard (`FORBIDDEN_R2000_FLAGS`, `assertNoViceFlag()`,
|
|
22
|
-
// `viceFlagRefusalMessage()`) that every spawn site in this repo must
|
|
23
|
-
// call before spawning;
|
|
24
|
-
// - every FIXED argv builder (`buildExportAsmArgs()`, `buildVerifyArgs()`,
|
|
25
|
-
// `buildMcpServerStdioArgs()`, `buildExportLblArgs()`,
|
|
26
|
-
// `buildImportLblArgs()`) -- a future verb gets a new fixed builder
|
|
27
|
-
// here, never a bespoke ad hoc argv assembled at a call site;
|
|
28
|
-
// - the ONE **synchronous**, blocking `spawnSync` of regenerator2000
|
|
29
|
-
// (`runR2000()`), used by every CLI verb (`r2000-cli.ts`) and by
|
|
30
|
-
// `r2000-verify.ts`/`r2000-symbols.ts`.
|
|
31
|
-
// `r2000-mcp-client.ts`'s `withR2000Session()` is the ONE **asynchronous**
|
|
32
|
-
// spawn site (`spawn()`, not `spawnSync()`) -- it exists because a
|
|
33
|
-
// long-lived MCP-over-stdio child session cannot be a blocking call, which
|
|
34
|
-
// `runR2000()` deliberately is. Both sites are safe for the same reason:
|
|
35
|
-
// EVERY spawn call site in this repo calls `assertNoViceFlag(argv)` before
|
|
36
|
-
// spawning, and that is no longer a prose promise -- `r2000-spawn-seam.test.ts`
|
|
37
|
-
// derives the full production-module set, finds every regenerator2000
|
|
38
|
-
// spawn call site in it, and FAILS if any of them spawns without guarding
|
|
39
|
-
// first, or if a third, unguarded site ever appears. `R2000-01`'s
|
|
40
|
-
// guarantee is therefore checked, not merely stated.
|
|
41
|
-
//
|
|
42
|
-
// What NOT to do, named concretely (D-07/D-08):
|
|
43
|
-
// - Never add a caller-supplied argv pass-through parameter (no rest
|
|
44
|
-
// parameter, no field for extra command-line arguments, no field that
|
|
45
|
-
// forwards arbitrary caller-supplied tokens on any builder's options
|
|
46
|
-
// object). Every verb's argv shape is fixed by this file, not by its
|
|
47
|
-
// caller.
|
|
48
|
-
// - Never strip `--vice` silently if a caller somehow supplies it. Stock
|
|
49
|
-
// VICE's binary monitor services *exactly one client*
|
|
50
|
-
// (CLAUDE.md's "Concurrency" constraint) -- a regenerator2000 launched
|
|
51
|
-
// with `--vice <host:port>` becomes a second, unserviced client against
|
|
52
|
-
// an instance the broker already owns, indistinguishable from a wedge
|
|
53
|
-
// with no diagnostic. Silently removing the flag would hide exactly the
|
|
54
|
-
// bug class this guard exists to catch, so the guard throws a named
|
|
55
|
-
// error instead.
|
|
56
|
-
// - Never import `hostpath.ts` or `containerpath.ts` here. regenerator2000
|
|
57
|
-
// runs container-side (D-R4, same side as the MCP proxy), so no path
|
|
58
|
-
// translation ever applies to any argument passed to it -- translating
|
|
59
|
-
// one would be the mirror image of the DERIV-07 screenshot-path trap,
|
|
60
|
-
// where a client-side-derived path was wrongly translated a second
|
|
61
|
-
// time. This absence is asserted structurally by
|
|
62
|
-
// `hostpath-consumers.test.ts` (D-08), not merely stated here.
|
|
63
|
-
import { spawnSync } from "node:child_process";
|
|
64
|
-
|
|
65
|
-
/** Overridable binary name, mirroring `disasm-roundtrip.test.ts`'s `ACME_BIN`
|
|
66
|
-
* convention -- lets tests point at a name guaranteed not to exist on PATH
|
|
67
|
-
* without needing regenerator2000 installed at all. */
|
|
68
|
-
export const R2000_BIN: string = process.env.R2000_BIN ?? "regenerator2000";
|
|
69
|
-
|
|
70
|
-
/** The permanent deny list for regenerator2000 argv, declared once as a
|
|
71
|
-
* named constant -- the same shape `vice.ts` uses for `DENY_LIST`. Today
|
|
72
|
-
* this holds exactly one entry; if a second hazardous flag is ever
|
|
73
|
-
* discovered, it joins this array rather than spawning a parallel check.
|
|
74
|
-
* `assertNoViceFlag()` below actually iterates this array (WR-01) -- it is
|
|
75
|
-
* the single source of truth for the scan, not merely documentation of
|
|
76
|
-
* intent, so adding an entry here is sufficient to enforce it. */
|
|
77
|
-
export const FORBIDDEN_R2000_FLAGS: readonly string[] = ["--vice"];
|
|
78
|
-
|
|
79
|
-
export interface R2000ViceFlagErrorOptions {
|
|
80
|
-
argv: readonly string[];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Thrown by `assertNoViceFlag()`/`runR2000()` when `--vice` (or its
|
|
84
|
-
* `--vice=<value>` single-token form) is found anywhere in a built argv. */
|
|
85
|
-
export class R2000ViceFlagError extends Error {
|
|
86
|
-
argv: readonly string[];
|
|
87
|
-
|
|
88
|
-
constructor(message: string, { argv }: R2000ViceFlagErrorOptions) {
|
|
89
|
-
super(message);
|
|
90
|
-
this.name = "R2000ViceFlagError";
|
|
91
|
-
this.argv = argv;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Renders the pinned refusal text for a `--vice`-bearing argv, mirroring
|
|
97
|
-
* `vice.ts`'s `denyListRefusalMessage()`. States plainly that the flag is
|
|
98
|
-
* permanently forbidden, why (stock VICE's binary monitor serves exactly
|
|
99
|
-
* one client and the broker owns that socket), what a second client would
|
|
100
|
-
* look like (indistinguishable from a wedge), and that the flag was
|
|
101
|
-
* refused rather than silently removed.
|
|
102
|
-
*/
|
|
103
|
-
export function viceFlagRefusalMessage(argv: readonly string[]): string {
|
|
104
|
-
return (
|
|
105
|
-
`--vice is permanently forbidden on any regenerator2000 launch -- stock VICE's binary monitor ` +
|
|
106
|
-
`serves exactly one client and the broker already owns that socket. A second client (this ` +
|
|
107
|
-
`regenerator2000 process connecting via --vice) would be indistinguishable from a wedge, with no ` +
|
|
108
|
-
`diagnostic. The flag was refused, not removed -- caller-supplied argv: [${argv.join(", ")}]`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Scans a finished argv array against every entry of `FORBIDDEN_R2000_FLAGS`
|
|
114
|
-
* and throws `R2000ViceFlagError` if any is found (WR-01: the scan reads
|
|
115
|
-
* the array itself, so a future addition to the deny list is enforced by
|
|
116
|
-
* construction rather than requiring a parallel hand-edit here). Exact-token
|
|
117
|
-
* comparison only -- `arg === flag` or the single-token `flag=<value>` form
|
|
118
|
-
* via `startsWith`. Deliberately does NOT join argv into a string and
|
|
119
|
-
* substring-match: a filename containing the characters `--vice` (e.g.
|
|
120
|
-
* `/tmp/my--vice-notes.a`) must never false-positive.
|
|
121
|
-
*/
|
|
122
|
-
export function assertNoViceFlag(argv: readonly string[]): void {
|
|
123
|
-
for (const arg of argv) {
|
|
124
|
-
for (const flag of FORBIDDEN_R2000_FLAGS) {
|
|
125
|
-
if (arg === flag || arg.startsWith(`${flag}=`)) {
|
|
126
|
-
throw new R2000ViceFlagError(viceFlagRefusalMessage(argv), { argv });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface BuildExportAsmArgsOptions {
|
|
133
|
-
projectPath: string;
|
|
134
|
-
outPath: string;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Fixed argv builder for the "export ACME assembly source" verb. No rest
|
|
138
|
-
* parameter, no extra-args field -- the shape is exactly these four tokens
|
|
139
|
-
* plus the two caller-supplied paths. */
|
|
140
|
-
export function buildExportAsmArgs({ projectPath, outPath }: BuildExportAsmArgsOptions): string[] {
|
|
141
|
-
return ["--headless", "--export_asm", outPath, "--assembler", "acme", projectPath];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export interface BuildVerifyArgsOptions {
|
|
145
|
-
projectPath: string;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Fixed argv builder for the "verify export roundtrip" verb. */
|
|
149
|
-
export function buildVerifyArgs({ projectPath }: BuildVerifyArgsOptions): string[] {
|
|
150
|
-
return ["--verify", "--assembler", "acme", projectPath];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export interface BuildMcpServerStdioArgsOptions {
|
|
154
|
-
projectPath: string;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** Fixed argv builder for the "run as an MCP server over stdio" verb
|
|
158
|
-
* (D-16/D-17). `--mcp-server-stdio` takes no value of its own -- the
|
|
159
|
-
* project path is the positional `[FILE]` argument documented by
|
|
160
|
-
* `regenerator2000 --help`, confirmed at execution time on this host
|
|
161
|
-
* (0.9.20): `Usage: regenerator2000 [OPTIONS] [FILE]`. */
|
|
162
|
-
export function buildMcpServerStdioArgs({ projectPath }: BuildMcpServerStdioArgsOptions): string[] {
|
|
163
|
-
return ["--mcp-server-stdio", projectPath];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export interface BuildExportLblArgsOptions {
|
|
167
|
-
projectPath: string;
|
|
168
|
-
outPath: string;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Fixed argv builder for the "export VICE labels" verb (R2000-14, the
|
|
172
|
-
* live-discovered-symbols-flow-back leg). `--export_lbl <PATH>` takes one
|
|
173
|
-
* value, confirmed at execution time from `--help`; `--headless` is
|
|
174
|
-
* required because this verb produces no TUI output. */
|
|
175
|
-
export function buildExportLblArgs({ projectPath, outPath }: BuildExportLblArgsOptions): string[] {
|
|
176
|
-
return ["--headless", "--export_lbl", outPath, projectPath];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export interface BuildImportLblArgsOptions {
|
|
180
|
-
projectPath: string;
|
|
181
|
-
lblPath: string;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Fixed argv builder for the "import VICE labels" verb (R2000-15, the
|
|
186
|
-
* annotate-then-export-to-VICE leg). `--import_lbl <PATH>` takes one value,
|
|
187
|
-
* confirmed at execution time from `--help`.
|
|
188
|
-
*
|
|
189
|
-
* `--mcp-server-stdio` is NOT optional here -- this is the D-28 trap this
|
|
190
|
-
* builder exists to make unreachable by construction. `main.rs:800-806` is
|
|
191
|
-
* `if headless && !mcp_server { return Ok(()) }`: an argv of
|
|
192
|
-
* `--import_lbl <path> --headless <proj>` imports the labels into memory
|
|
193
|
-
* and then hits that early return WITHOUT ever calling save, so the import
|
|
194
|
-
* is silently discarded -- measured live on this host: two names imported
|
|
195
|
-
* that way, and a subsequent `--export_lbl` read back from disk returned
|
|
196
|
-
* only the pre-existing label. `main.rs:709-711` makes `--mcp-server-stdio`
|
|
197
|
-
* set both `headless` and `mcp_server`, which is what skips that early
|
|
198
|
-
* return and leaves the process alive long enough for a caller to issue an
|
|
199
|
-
* explicit `r2000_save_project` over the resulting stdio session.
|
|
200
|
-
*
|
|
201
|
-
* Deliberately does NOT also add `--headless` to this argv: `--mcp-server-
|
|
202
|
-
* stdio` already implies it (`main.rs:709-711`), and a caller reading this
|
|
203
|
-
* argv should see the minimum token set that makes persistence possible,
|
|
204
|
-
* not a redundant flag alongside it. `--import_lbl` only mutates in-memory
|
|
205
|
-
* state -- the caller is still responsible for issuing `r2000_save_project`
|
|
206
|
-
* over the resulting session before closing it; this builder only gets the
|
|
207
|
-
* import to a point where saving is possible.
|
|
208
|
-
*/
|
|
209
|
-
export function buildImportLblArgs({ projectPath, lblPath }: BuildImportLblArgsOptions): string[] {
|
|
210
|
-
return ["--import_lbl", lblPath, "--mcp-server-stdio", projectPath];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export interface RunR2000Options {
|
|
214
|
-
cwd?: string;
|
|
215
|
-
timeoutMs?: number;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export interface RunR2000Result {
|
|
219
|
-
status: number | null;
|
|
220
|
-
stdout: string;
|
|
221
|
-
stderr: string;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** Fallback used when `R2000_TIMEOUT_MS`'s env override is absent or
|
|
225
|
-
* invalid (WR-10, 10-REVIEW.md:481-511). Exported as its own name, distinct
|
|
226
|
-
* from `R2000_TIMEOUT_MS`, so `parseR2000TimeoutMs()` and its tests can
|
|
227
|
-
* refer to "the default" without re-deriving the literal `120_000`. */
|
|
228
|
-
export const R2000_DEFAULT_TIMEOUT_MS = 120_000;
|
|
229
|
-
|
|
230
|
-
/** One-time stderr warning for a malformed `R2000_TIMEOUT_MS` override,
|
|
231
|
-
* mirroring `repo-root.ts`'s "warn once on stderr rather than throw on a
|
|
232
|
-
* bad env var" convention -- a bad timeout value is an operator mistake,
|
|
233
|
-
* not a reason to crash the process before it has done anything. */
|
|
234
|
-
let warnedBadTimeoutEnv = false;
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Parses a `R2000_TIMEOUT_MS`-shaped raw string into a valid, positive
|
|
238
|
-
* timeout in milliseconds, falling back to `fallbackMs` (with a one-time
|
|
239
|
-
* stderr warning naming the rejected value) for anything non-numeric,
|
|
240
|
-
* `NaN`, zero or negative. Never returns `NaN` -- a bad value must fall
|
|
241
|
-
* back to the default, never propagate into `spawnSync`'s `timeout` option
|
|
242
|
-
* unchecked.
|
|
243
|
-
*
|
|
244
|
-
* Exported and free of any module-load-time side effect so a test can
|
|
245
|
-
* exercise every input shape directly, by calling this function, rather
|
|
246
|
-
* than mutating `process.env.R2000_TIMEOUT_MS` after `R2000_TIMEOUT_MS`
|
|
247
|
-
* has already been evaluated -- exactly the IN-04 mistake (`R2000_BIN` is
|
|
248
|
-
* resolved once at module load; env mutation afterward is a no-op against
|
|
249
|
-
* it) applied to a second env-derived constant.
|
|
250
|
-
*/
|
|
251
|
-
export function parseR2000TimeoutMs(
|
|
252
|
-
raw: string | undefined,
|
|
253
|
-
fallbackMs: number = R2000_DEFAULT_TIMEOUT_MS
|
|
254
|
-
): number {
|
|
255
|
-
if (raw === undefined) return fallbackMs;
|
|
256
|
-
const n = Number(raw);
|
|
257
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
258
|
-
if (!warnedBadTimeoutEnv) {
|
|
259
|
-
warnedBadTimeoutEnv = true;
|
|
260
|
-
console.error(
|
|
261
|
-
`warn: R2000_TIMEOUT_MS="${raw}" is not a positive, finite number -- falling back to the default ` +
|
|
262
|
-
`(${fallbackMs}ms). Set R2000_TIMEOUT_MS to a positive number of milliseconds to override it.`
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
return fallbackMs;
|
|
266
|
-
}
|
|
267
|
-
return n;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/** The default `runR2000()` timeout (WR-10): 120s unless overridden by
|
|
271
|
-
* `R2000_TIMEOUT_MS`, validated by `parseR2000TimeoutMs()` above so a bad
|
|
272
|
-
* override cannot turn into `NaN` inside `spawnSync`'s `timeout` option
|
|
273
|
-
* (an unbounded, non-numeric timeout is the same hazard as no timeout at
|
|
274
|
-
* all). A per-call `opts.timeoutMs` always overrides this module-level
|
|
275
|
-
* default (D-11.1-04) -- this constant only supplies what every one of the
|
|
276
|
-
* seven CLI verbs gets when it does not ask for anything different. */
|
|
277
|
-
export const R2000_TIMEOUT_MS: number = parseR2000TimeoutMs(process.env.R2000_TIMEOUT_MS);
|
|
278
|
-
|
|
279
|
-
/** `spawnSync`'s Node default `maxBuffer` is 1 MiB. That is too small for
|
|
280
|
-
* this module's own reason to exist: `--verify`/`--export_asm` transcripts
|
|
281
|
-
* ARE the payload `acmeVerdict()` and the label parsers exist to parse, so
|
|
282
|
-
* a verbose-but-successful child run must never turn into a truncated
|
|
283
|
-
* buffer and an opaque `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`/`ENOBUFS` stack
|
|
284
|
-
* in place of the parsed verdict this seam was built to produce (WR-10).
|
|
285
|
-
* 32 MiB is comfortably above any transcript measured live against
|
|
286
|
-
* regenerator2000 0.9.20 on this host. */
|
|
287
|
-
export const R2000_MAX_BUFFER = 32 * 1024 * 1024;
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Spawns regenerator2000 with the given argv. Calls `assertNoViceFlag(argv)`
|
|
291
|
-
* as the FIRST statement of this function body, deliberately, so the guard
|
|
292
|
-
* is enforced even if a future edit reorders the rest of the function --
|
|
293
|
-
* exactly the discipline `vice.ts`'s `call()` uses for `DENY_LIST`.
|
|
294
|
-
*
|
|
295
|
-
* Always spawns with an argv ARRAY and never enables a shell interpreter for
|
|
296
|
-
* the child process, so a caller-controlled filename can never be
|
|
297
|
-
* interpreted by a shell.
|
|
298
|
-
*
|
|
299
|
-
* BOUNDED (WR-10, 10-REVIEW.md:481-511): this is a **blocking** `spawnSync`,
|
|
300
|
-
* so an unbounded child owns the whole Node event loop -- no JSON-RPC, no
|
|
301
|
-
* diagnostics, nothing, for as long as the child runs. `timeout` defaults to
|
|
302
|
-
* `R2000_TIMEOUT_MS` (120s, env-overridable) and `maxBuffer` is fixed at
|
|
303
|
-
* `R2000_MAX_BUFFER` (32 MiB); `opts.timeoutMs` still overrides the default
|
|
304
|
-
* per call. Both bounds, when hit, are translated into a named, actionable
|
|
305
|
-
* `Error` naming the limit and the argv -- never a raw re-thrown `spawnSync`
|
|
306
|
-
* error object, which is what WR-10 found: a *successful* verify with a
|
|
307
|
-
* verbose transcript could previously surface as an opaque `ENOBUFS` stack
|
|
308
|
-
* instead of the parsed verdict this module exists to produce.
|
|
309
|
-
*/
|
|
310
|
-
export function runR2000(argv: readonly string[], opts: RunR2000Options = {}): RunR2000Result {
|
|
311
|
-
assertNoViceFlag(argv);
|
|
312
|
-
// Caller override preserved (D-11.1-04): opts.timeoutMs still wins over
|
|
313
|
-
// the module-level default. `timeoutMs` below mirrors the exact
|
|
314
|
-
// expression passed to spawnSync() so the two can never drift and an
|
|
315
|
-
// error message below can name the real bound that was actually applied.
|
|
316
|
-
const timeoutMs = opts.timeoutMs ?? R2000_TIMEOUT_MS;
|
|
317
|
-
const r = spawnSync(R2000_BIN, [...argv], {
|
|
318
|
-
encoding: "utf8",
|
|
319
|
-
cwd: opts.cwd,
|
|
320
|
-
timeout: opts.timeoutMs ?? R2000_TIMEOUT_MS,
|
|
321
|
-
maxBuffer: R2000_MAX_BUFFER,
|
|
322
|
-
});
|
|
323
|
-
if (r.error) {
|
|
324
|
-
const err = r.error as NodeJS.ErrnoException;
|
|
325
|
-
if (err.code === "ENOENT") {
|
|
326
|
-
throw new Error(
|
|
327
|
-
`regenerator2000 was not found on PATH -- install it with \`cargo install regenerator2000\` and ` +
|
|
328
|
-
`ensure \`regenerator2000\` is on $PATH (or set R2000_BIN to its full path).`
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
// Timeout (WR-10): Node's shape has varied across versions -- observed
|
|
332
|
-
// on this host (Node 22) as `error.code === "ETIMEDOUT"` PLUS
|
|
333
|
-
// `r.signal === "SIGTERM"` together, so both are checked rather than
|
|
334
|
-
// relying on either alone.
|
|
335
|
-
if (err.code === "ETIMEDOUT" || r.signal === "SIGTERM") {
|
|
336
|
-
throw new Error(
|
|
337
|
-
`regenerator2000 timed out after ${timeoutMs}ms and was killed (argv: [${argv.join(", ")}]) -- ` +
|
|
338
|
-
`either the child is wedged, or a real run legitimately needs longer than ${timeoutMs}ms. Raise ` +
|
|
339
|
-
`R2000_TIMEOUT_MS (or pass { timeoutMs } to runR2000()) if the latter.`
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
// Max-buffer overrun (WR-10): observed on this host as
|
|
343
|
-
// `error.code === "ENOBUFS"`; matched more broadly against the message
|
|
344
|
-
// too, since Node's own docs describe this failure mode inconsistently
|
|
345
|
-
// across versions as `ENOBUFS` or `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`.
|
|
346
|
-
if (err.code === "ENOBUFS" || /MAXBUFFER|maxBuffer/i.test(err.message ?? "")) {
|
|
347
|
-
const limitMiB = (R2000_MAX_BUFFER / (1024 * 1024)).toFixed(0);
|
|
348
|
-
throw new Error(
|
|
349
|
-
`regenerator2000's combined stdout+stderr exceeded the ${limitMiB} MiB limit (argv: ` +
|
|
350
|
-
`[${argv.join(", ")}]) -- its output was truncated, so any verdict parsed from it would be ` +
|
|
351
|
-
`unreliable. This is a hard failure, never a silently-shortened transcript feeding a pass.`
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
throw r.error;
|
|
355
|
-
}
|
|
356
|
-
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
357
|
-
}
|