@codedology/code-forge 0.1.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.
Files changed (184) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/README.md +127 -0
  4. package/bin/code-forge.mjs +145 -0
  5. package/docs/README.md +37 -0
  6. package/docs/reference/blocks.json +288 -0
  7. package/docs/reference/config.md +162 -0
  8. package/examples/first-use/README.md +11 -0
  9. package/examples/first-use/clamp.acceptance.yml +8 -0
  10. package/examples/first-use/clamp.brief.md +16 -0
  11. package/examples/first-use/coder/src/math.mjs +31 -0
  12. package/examples/first-use/coder/test/clamp.test.mjs +26 -0
  13. package/examples/node-lib/.env.example +3 -0
  14. package/examples/node-lib/.gitattributes +3 -0
  15. package/examples/node-lib/.github/workflows/test.yml +11 -0
  16. package/examples/node-lib/CHANGELOG.md +13 -0
  17. package/examples/node-lib/README.md +17 -0
  18. package/examples/node-lib/gitignore +3 -0
  19. package/examples/node-lib/package.json +19 -0
  20. package/examples/node-lib/src/list.mjs +23 -0
  21. package/examples/node-lib/src/math.mjs +21 -0
  22. package/examples/node-lib/src/strings.mjs +34 -0
  23. package/examples/node-lib/test/list.test.mjs +12 -0
  24. package/examples/node-lib/test/math.test.mjs +13 -0
  25. package/examples/node-lib/test/package.test.mjs +18 -0
  26. package/examples/node-lib/test/strings.test.mjs +28 -0
  27. package/package.json +52 -0
  28. package/schema/code-forge.schema.json +331 -0
  29. package/schema/facts.schema.json +28 -0
  30. package/scripts/first-use.mjs +517 -0
  31. package/scripts/gen-config-doc.mjs +241 -0
  32. package/skill/SKILL.md +79 -0
  33. package/skill/references/adapters/claude-code.md +19 -0
  34. package/skill/references/adapters/solo.md +34 -0
  35. package/skill/references/adapters/subprocess.md +15 -0
  36. package/skill/references/code.md +64 -0
  37. package/skill/references/continuity.md +31 -0
  38. package/skill/references/decisions.md +50 -0
  39. package/skill/references/degraded.md +40 -0
  40. package/skill/references/harden.md +26 -0
  41. package/skill/references/ledger.md +23 -0
  42. package/skill/references/plan.md +43 -0
  43. package/skill/references/proof.md +34 -0
  44. package/skill/references/review.md +42 -0
  45. package/skill/references/rules-core.md +72 -0
  46. package/skill/references/security.md +29 -0
  47. package/skill/scripts/forge +40 -0
  48. package/skill/templates/block-record.md +19 -0
  49. package/skill/templates/facts-sheet.md +22 -0
  50. package/skill/templates/plan.md +53 -0
  51. package/src/cli/author.mjs +87 -0
  52. package/src/cli/block.mjs +262 -0
  53. package/src/cli/doctor.mjs +74 -0
  54. package/src/cli/facts.mjs +124 -0
  55. package/src/cli/gates.mjs +280 -0
  56. package/src/cli/help.mjs +48 -0
  57. package/src/cli/init.mjs +14 -0
  58. package/src/cli/jev.mjs +175 -0
  59. package/src/cli/keys.mjs +170 -0
  60. package/src/cli/ledger.mjs +160 -0
  61. package/src/cli/list.mjs +68 -0
  62. package/src/cli/models.mjs +131 -0
  63. package/src/cli/plan.mjs +70 -0
  64. package/src/cli/proof.mjs +316 -0
  65. package/src/cli/remove.mjs +76 -0
  66. package/src/cli/report.mjs +73 -0
  67. package/src/cli/resolve.mjs +79 -0
  68. package/src/cli/review-file.mjs +152 -0
  69. package/src/cli/run.mjs +159 -0
  70. package/src/cli/s2.mjs +93 -0
  71. package/src/cli/spawn.mjs +104 -0
  72. package/src/cli/upgrade.mjs +103 -0
  73. package/src/cli/validate.mjs +108 -0
  74. package/src/cli/version.mjs +18 -0
  75. package/src/cli/worker.mjs +76 -0
  76. package/src/config/defaults/anthropic.mjs +18 -0
  77. package/src/config/defaults/index.mjs +24 -0
  78. package/src/config/defaults/openai.mjs +16 -0
  79. package/src/config/defaults/xai.mjs +18 -0
  80. package/src/config/known-ids.mjs +171 -0
  81. package/src/config/load.mjs +135 -0
  82. package/src/config/migrate.mjs +46 -0
  83. package/src/config/refresh.mjs +276 -0
  84. package/src/config/schema-compile.mjs +211 -0
  85. package/src/config/schema-paths.mjs +84 -0
  86. package/src/config/secret-patterns.mjs +85 -0
  87. package/src/config/validate.mjs +694 -0
  88. package/src/decide/author-loop.mjs +90 -0
  89. package/src/decide/escalation.mjs +262 -0
  90. package/src/decide/fallback-rules.mjs +172 -0
  91. package/src/decide/jev-client.mjs +216 -0
  92. package/src/decide/questions.mjs +161 -0
  93. package/src/decide/thresholds.mjs +199 -0
  94. package/src/doctor/index.mjs +105 -0
  95. package/src/doctor/local.mjs +162 -0
  96. package/src/doctor/probes.mjs +358 -0
  97. package/src/doctor/rows.mjs +51 -0
  98. package/src/doctor/worker-probe.mjs +125 -0
  99. package/src/engines/alias-map.mjs +78 -0
  100. package/src/engines/builders/claude.mjs +224 -0
  101. package/src/engines/builders/codex.mjs +242 -0
  102. package/src/engines/builders/grok.mjs +132 -0
  103. package/src/engines/builders/index.mjs +35 -0
  104. package/src/engines/builders/validate-params.mjs +34 -0
  105. package/src/engines/codex-home.mjs +230 -0
  106. package/src/engines/isolation.mjs +84 -0
  107. package/src/engines/probe.mjs +116 -0
  108. package/src/engines/provider-cli.mjs +24 -0
  109. package/src/engines/render-rules.mjs +41 -0
  110. package/src/engines/sentinel.mjs +133 -0
  111. package/src/engines/usage-parse.mjs +149 -0
  112. package/src/gates/acceptance.mjs +95 -0
  113. package/src/gates/detect.mjs +187 -0
  114. package/src/gates/invariants.mjs +43 -0
  115. package/src/gates/run.mjs +72 -0
  116. package/src/gates/safe-edit.mjs +266 -0
  117. package/src/gates/scope.mjs +154 -0
  118. package/src/gates/secret-scan.mjs +79 -0
  119. package/src/gates/transcript-grep.mjs +27 -0
  120. package/src/install/agent-env.mjs +76 -0
  121. package/src/install/detect.mjs +121 -0
  122. package/src/install/harnesses.mjs +131 -0
  123. package/src/install/link.mjs +463 -0
  124. package/src/install/wizard/answers.mjs +389 -0
  125. package/src/install/wizard/flags.mjs +248 -0
  126. package/src/install/wizard/project-file.mjs +128 -0
  127. package/src/install/wizard/run.mjs +366 -0
  128. package/src/install/wizard/steps.mjs +201 -0
  129. package/src/keys/backends/constants.mjs +7 -0
  130. package/src/keys/backends/file.mjs +99 -0
  131. package/src/keys/backends/keychain.mjs +81 -0
  132. package/src/keys/backends/security-cli.mjs +105 -0
  133. package/src/keys/onepassword.mjs +46 -0
  134. package/src/keys/store.mjs +347 -0
  135. package/src/ledger/calibration.mjs +109 -0
  136. package/src/ledger/export.mjs +52 -0
  137. package/src/ledger/outcome.mjs +397 -0
  138. package/src/ledger/paths.mjs +30 -0
  139. package/src/ledger/prices.mjs +49 -0
  140. package/src/ledger/report.mjs +250 -0
  141. package/src/ledger/write.mjs +279 -0
  142. package/src/proof/export.mjs +358 -0
  143. package/src/proof/lock.mjs +138 -0
  144. package/src/proof/red-green.mjs +519 -0
  145. package/src/proof/red-parse.mjs +103 -0
  146. package/src/proof/tiers.mjs +62 -0
  147. package/src/review/budget.mjs +241 -0
  148. package/src/review/context.mjs +156 -0
  149. package/src/review/engine.mjs +264 -0
  150. package/src/review/finding.schema.json +47 -0
  151. package/src/review/fixloop.mjs +467 -0
  152. package/src/review/gate-check.mjs +249 -0
  153. package/src/review/lenses/A.md +22 -0
  154. package/src/review/lenses/B.md +23 -0
  155. package/src/review/lenses/full.md +23 -0
  156. package/src/review/lenses/judge.md +21 -0
  157. package/src/review/lenses/quick.md +21 -0
  158. package/src/review/lenses/recheck.md +23 -0
  159. package/src/review/packet.mjs +355 -0
  160. package/src/review/triage.mjs +149 -0
  161. package/src/review/validate-review.mjs +70 -0
  162. package/src/session/author.mjs +228 -0
  163. package/src/session/facts.mjs +855 -0
  164. package/src/session/plan-check.mjs +294 -0
  165. package/src/session/s2.mjs +164 -0
  166. package/src/session/spawn.mjs +610 -0
  167. package/src/state/block.mjs +289 -0
  168. package/src/state/cli-args.mjs +67 -0
  169. package/src/state/paths.mjs +92 -0
  170. package/src/state/registry.mjs +189 -0
  171. package/src/state/run.mjs +266 -0
  172. package/src/state/signer.mjs +132 -0
  173. package/src/util/exec.mjs +315 -0
  174. package/src/util/forbidden.mjs +787 -0
  175. package/src/util/git.mjs +381 -0
  176. package/src/util/log.mjs +100 -0
  177. package/src/util/reaper.mjs +300 -0
  178. package/src/util/redact.mjs +212 -0
  179. package/src/util/tmp.mjs +409 -0
  180. package/src/worker/engine.mjs +338 -0
  181. package/src/worker/loop.mjs +392 -0
  182. package/src/worker/queue.mjs +186 -0
  183. package/src/worker/review-state.mjs +145 -0
  184. package/src/worker/ticket.mjs +195 -0
@@ -0,0 +1,315 @@
1
+ /**
2
+ * The one place in the package that spawns a child process.
3
+ *
4
+ * Rules (see plan §8.2, §8.4):
5
+ * - argv only, never a shell string — `exec("rm -rf /")` is refused before anything spawns.
6
+ * - `shell: true` is refused outright — a shell reintroduces string parsing and injection.
7
+ * - `timeoutMs`, when given, must be a finite positive number; when omitted, the child can run
8
+ * indefinitely (callers that spawn a reviewer/coder CLI are expected to always pass one —
9
+ * this module does not impose a default, since a default here would silently apply to every
10
+ * caller, including ones with a legitimately long-running command).
11
+ * - On timeout the whole process GROUP is sent SIGTERM, then SIGKILL — the SIGKILL goes to the
12
+ * group as soon as the direct child exits (or after a grace period), so a grandchild that
13
+ * traps SIGTERM cannot outlive the call. The result is `failed`, never a partial success.
14
+ * - stdin is `ignore`d by default, so a child that reads stdin when it isn't a TTY (`claude -p`,
15
+ * for one) sees an immediate EOF instead of blocking forever. With `input` (a string or bytes),
16
+ * stdin is a pipe: the content is written verbatim and then the pipe is closed, so the child
17
+ * reads exactly those bytes followed by EOF (plan V3 — closed-book roles get the packet on
18
+ * stdin, never in argv).
19
+ * - Every child is recorded in the pid registry of this process's run root
20
+ * (`<run-root>/pids/<pid>.json`, `./tmp.mjs` + `./reaper.mjs`) and the entry is removed when
21
+ * the call settles. An entry that outlives its run (the parent was killed) is what the reaper
22
+ * uses to find and kill the orphan later. Registry I/O failing never fails the spawn: the
23
+ * registry is hygiene, the child is the caller's work.
24
+ * - This module does not know about the forbidden-command list (`./forbidden.mjs`); callers
25
+ * that spawn on a coder's behalf are expected to check argv against it first (`isForbidden`).
26
+ * `exec` only enforces the structural rules above (argv shape, no shell, valid timeout).
27
+ *
28
+ * ## Process groups and the parent's own death
29
+ *
30
+ * Every child is spawned `detached: true`, i.e. as the leader of its own process group, so a kill
31
+ * reaches its grandchildren (npx wrappers, git hooks, the agent CLI's own subprocesses). The price
32
+ * is that the child is no longer in the terminal's foreground group: Ctrl-C (SIGINT), a SIGTERM
33
+ * or a SIGHUP sent to code-forge would NOT reach it, and it would run on as an orphan. So this
34
+ * module keeps a registry of live child groups and, while any is live:
35
+ * - forwards SIGINT/SIGTERM/SIGHUP received by the parent to every live group; if no other
36
+ * listener handles that signal, it then removes its own handlers and re-raises the signal on
37
+ * the parent, so the parent still dies of it exactly as it would have by default;
38
+ * - on the parent's `exit`, sends SIGTERM to every group still live.
39
+ * The handlers are installed with the first live child and removed with the last, so a process
40
+ * that has no child running keeps Node's default signal behaviour.
41
+ *
42
+ * @typedef {object} ExecResult
43
+ * @property {"ok"|"failed"} result
44
+ * @property {number|null} code
45
+ * @property {NodeJS.Signals|null} signal
46
+ * @property {string} stdout
47
+ * @property {string} stderr
48
+ * @property {boolean} timedOut
49
+ * @property {string} [error]
50
+ */
51
+
52
+ import { spawn } from 'node:child_process';
53
+ import { registerPid, unregisterPid } from './reaper.mjs';
54
+ import { pidsDir } from './tmp.mjs';
55
+
56
+ /** Grace period after SIGTERM before escalating to SIGKILL, in milliseconds. */
57
+ const KILL_GRACE_MS = 2000;
58
+
59
+ /** Default cap per stream (stdout, stderr independently) before a runaway child is killed. */
60
+ const DEFAULT_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
61
+
62
+ /** Signals the parent forwards to every live child group. */
63
+ const FORWARDED_SIGNALS = /** @type {const} */ (['SIGINT', 'SIGTERM', 'SIGHUP']);
64
+
65
+ /** Pids of live children — each is also the id of the child's process group. */
66
+ const liveGroups = new Set();
67
+
68
+ /**
69
+ * Send `signal` to a whole process group; ignore a group that is already gone (ESRCH) or a
70
+ * platform without process groups.
71
+ * @param {number} pid
72
+ * @param {NodeJS.Signals} signal
73
+ * @returns {boolean} whether the group signal was delivered.
74
+ */
75
+ function signalGroup(pid, signal) {
76
+ try {
77
+ process.kill(-pid, signal);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ /** @type {Map<NodeJS.Signals, () => void>} */
85
+ const signalHandlers = new Map();
86
+
87
+ const onParentExit = () => {
88
+ for (const pid of liveGroups) {
89
+ signalGroup(pid, 'SIGTERM');
90
+ }
91
+ };
92
+
93
+ function installParentHandlers() {
94
+ if (signalHandlers.size > 0) return;
95
+ for (const signal of FORWARDED_SIGNALS) {
96
+ const handler = () => {
97
+ for (const pid of liveGroups) {
98
+ signalGroup(pid, signal);
99
+ }
100
+ // Ours is the only listener: Node's default action (terminate) was suppressed by our
101
+ // listener's mere presence, so restore it and re-raise — the parent dies of the signal.
102
+ if (process.listenerCount(signal) === 1) {
103
+ uninstallParentHandlers();
104
+ process.kill(process.pid, signal);
105
+ }
106
+ };
107
+ signalHandlers.set(signal, handler);
108
+ process.on(signal, handler);
109
+ }
110
+ process.on('exit', onParentExit);
111
+ }
112
+
113
+ function uninstallParentHandlers() {
114
+ for (const [signal, handler] of signalHandlers) {
115
+ process.removeListener(signal, handler);
116
+ }
117
+ signalHandlers.clear();
118
+ process.removeListener('exit', onParentExit);
119
+ }
120
+
121
+ /** @param {number} pid */
122
+ function trackChild(pid) {
123
+ liveGroups.add(pid);
124
+ installParentHandlers();
125
+ }
126
+
127
+ /** @param {number} pid */
128
+ function untrackChild(pid) {
129
+ liveGroups.delete(pid);
130
+ if (liveGroups.size === 0) {
131
+ uninstallParentHandlers();
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Spawn `argv[0]` with `argv.slice(1)` as arguments. Never invokes a shell.
137
+ *
138
+ * @param {string[]} argv - The full command line, e.g. `["git", "status"]`.
139
+ * @param {object} [opts]
140
+ * @param {string} [opts.cwd]
141
+ * @param {NodeJS.ProcessEnv} [opts.env]
142
+ * @param {number} [opts.timeoutMs] - Kill the child (and its process group) if it runs longer
143
+ * than this. Must be a finite number > 0 when given.
144
+ * @param {number[]} [opts.okExitCodes] - Exit codes treated as `result: "ok"` (default `[0]`).
145
+ * Some read-only git subcommands (e.g. `diff --no-index`, `merge-base --is-ancestor`) use a
146
+ * non-zero exit code to report a normal "no" answer, not a failure.
147
+ * @param {number} [opts.maxBufferBytes] - Per-stream cap (default 10 MiB). Exceeding it on
148
+ * either stream kills the child and resolves `result: "failed"`; the stream's collected text is
149
+ * cut at exactly `maxBufferBytes` bytes.
150
+ * @param {string | Uint8Array} [opts.input] - Content written to the child's stdin, followed by
151
+ * EOF. Omitted ⇒ stdin is `ignore`.
152
+ * @param {boolean} [opts.shell] - Forbidden. Only exists so passing it throws immediately.
153
+ * @returns {Promise<ExecResult>}
154
+ */
155
+ export function exec(argv, opts = {}) {
156
+ if (!Array.isArray(argv) || argv.length === 0 || !argv.every((a) => typeof a === 'string')) {
157
+ throw new TypeError('exec: argv must be a non-empty array of strings, not a shell string');
158
+ }
159
+ if (opts.shell) {
160
+ throw new TypeError('exec: the "shell" option is forbidden — pass argv as an array instead');
161
+ }
162
+ if (opts.timeoutMs !== undefined && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
163
+ throw new TypeError('exec: timeoutMs must be a finite number greater than 0 when given');
164
+ }
165
+ if (opts.input !== undefined && typeof opts.input !== 'string' && !(opts.input instanceof Uint8Array)) {
166
+ throw new TypeError('exec: input must be a string or a Uint8Array/Buffer when given');
167
+ }
168
+
169
+ const { cwd, env, timeoutMs, input, okExitCodes = [0], maxBufferBytes = DEFAULT_MAX_BUFFER_BYTES } = opts;
170
+ const [command, ...args] = argv;
171
+
172
+ return new Promise((resolve) => {
173
+ const stdinMode = input === undefined ? 'ignore' : 'pipe';
174
+ const child = spawn(command, args, { cwd, env, shell: false, detached: true, stdio: [stdinMode, 'pipe', 'pipe'] });
175
+ const pid = child.pid;
176
+ /** @type {string | null} */
177
+ let registryDir = null;
178
+ if (typeof pid === 'number') {
179
+ trackChild(pid);
180
+ try {
181
+ registryDir = pidsDir();
182
+ registerPid(registryDir, pid, command);
183
+ } catch {
184
+ registryDir = null;
185
+ }
186
+ }
187
+ if (input !== undefined && child.stdin) {
188
+ // A child that exits without reading its stdin makes the write fail with EPIPE; that is
189
+ // the child's business (its exit code says what happened), not an unhandled error here.
190
+ child.stdin.on('error', () => {});
191
+ child.stdin.end(input);
192
+ }
193
+
194
+ /** @type {Buffer[]} */
195
+ const stdoutChunks = [];
196
+ /** @type {Buffer[]} */
197
+ const stderrChunks = [];
198
+ const byteCounts = { stdout: 0, stderr: 0 };
199
+ let timedOut = false;
200
+ let overBuffer = false;
201
+ let settled = false;
202
+ /** @type {NodeJS.Timeout | undefined} */
203
+ let termTimer;
204
+ /** @type {NodeJS.Timeout | undefined} */
205
+ let killTimer;
206
+
207
+ /** Kill the child's whole process group; falls back to the direct child (e.g. on Windows). */
208
+ const killGroup = (/** @type {NodeJS.Signals} */ signal) => {
209
+ if (typeof pid === 'number' && signalGroup(pid, signal)) {
210
+ return;
211
+ }
212
+ child.kill(signal);
213
+ };
214
+
215
+ const stdoutText = () => Buffer.concat(stdoutChunks).toString('utf8');
216
+ const stderrText = () => Buffer.concat(stderrChunks).toString('utf8');
217
+
218
+ /** @param {ExecResult} result */
219
+ const settle = (result) => {
220
+ if (settled) return;
221
+ settled = true;
222
+ if (termTimer) clearTimeout(termTimer);
223
+ if (killTimer) clearTimeout(killTimer);
224
+ if (typeof pid === 'number') {
225
+ untrackChild(pid);
226
+ if (registryDir !== null) {
227
+ try {
228
+ unregisterPid(registryDir, pid);
229
+ } catch {
230
+ // hygiene only — see the module comment
231
+ }
232
+ }
233
+ }
234
+ child.stdin?.destroy();
235
+ child.stdout?.destroy();
236
+ child.stderr?.destroy();
237
+ resolve(result);
238
+ };
239
+
240
+ if (timeoutMs) {
241
+ termTimer = setTimeout(() => {
242
+ timedOut = true;
243
+ killGroup('SIGTERM');
244
+ killTimer = setTimeout(() => {
245
+ killGroup('SIGKILL');
246
+ }, KILL_GRACE_MS);
247
+ }, timeoutMs);
248
+ }
249
+
250
+ /**
251
+ * @param {'stdout'|'stderr'} stream
252
+ * @param {Buffer[]} chunks
253
+ * @param {Buffer} chunk
254
+ */
255
+ const collect = (stream, chunks, chunk) => {
256
+ if (overBuffer) return;
257
+ const room = maxBufferBytes - byteCounts[stream];
258
+ if (chunk.length > room) {
259
+ // A pipe read can merge many child writes into one event, so cut to the exact cap
260
+ // rather than appending the whole event and overshooting it.
261
+ chunks.push(chunk.subarray(0, room));
262
+ byteCounts[stream] = maxBufferBytes;
263
+ overBuffer = true;
264
+ killGroup('SIGKILL');
265
+ return;
266
+ }
267
+ chunks.push(chunk);
268
+ byteCounts[stream] += chunk.length;
269
+ };
270
+
271
+ // Not optional chaining: stdout/stderr are always 'pipe' above, so child.stdout
272
+ // and child.stderr are always real Readable streams, never null — Node only sets a stdio
273
+ // stream to null for 'ignore'/'inherit' on that fd, neither of which is used for fds 1/2.
274
+ child.stdout.on('data', (/** @type {Buffer} */ chunk) => collect('stdout', stdoutChunks, chunk));
275
+ child.stderr.on('data', (/** @type {Buffer} */ chunk) => collect('stderr', stderrChunks, chunk));
276
+
277
+ child.on('error', (err) => {
278
+ settle({
279
+ result: 'failed',
280
+ code: null,
281
+ signal: null,
282
+ stdout: stdoutText(),
283
+ stderr: stderrText(),
284
+ timedOut,
285
+ error: err.message,
286
+ });
287
+ });
288
+
289
+ // Once we've decided to kill (timeout or over-buffer), don't wait for 'close' — a
290
+ // grandchild that inherited the pipes can keep them open indefinitely, and 'close' would
291
+ // then never fire even though the child we care about is dead. 'exit' fires as soon as the
292
+ // child process itself has terminated, which is what "killed after timeout_ms" means. Before
293
+ // settling (which cancels the grace timer), SIGKILL the group: a grandchild that trapped the
294
+ // SIGTERM must not survive just because its parent died first.
295
+ child.on('exit', (code, signal) => {
296
+ if (timedOut || overBuffer) {
297
+ killGroup('SIGKILL');
298
+ settle({
299
+ result: 'failed',
300
+ code,
301
+ signal,
302
+ stdout: stdoutText(),
303
+ stderr: stderrText(),
304
+ timedOut,
305
+ ...(overBuffer ? { error: 'exec: maxBufferBytes exceeded' } : {}),
306
+ });
307
+ }
308
+ });
309
+
310
+ child.on('close', (code, signal) => {
311
+ const result = !timedOut && !overBuffer && code !== null && okExitCodes.includes(code) ? 'ok' : 'failed';
312
+ settle({ result, code, signal, stdout: stdoutText(), stderr: stderrText(), timedOut });
313
+ });
314
+ });
315
+ }