@tekyzinc/gsd-t 3.29.11 → 4.0.12

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 (108) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/bin/gsd-t-parallel.cjs +64 -13
  3. package/bin/gsd-t.js +48 -376
  4. package/bin/journey-coverage.cjs +6 -3
  5. package/bin/parallel-cli.cjs +13 -1
  6. package/commands/gsd-t-debug.md +47 -533
  7. package/commands/gsd-t-design-decompose.md +24 -497
  8. package/commands/gsd-t-doc-ripple.md +23 -139
  9. package/commands/gsd-t-execute.md +41 -958
  10. package/commands/gsd-t-help.md +0 -2
  11. package/commands/gsd-t-impact.md +26 -295
  12. package/commands/gsd-t-integrate.md +44 -369
  13. package/commands/gsd-t-milestone.md +25 -124
  14. package/commands/gsd-t-partition.md +28 -539
  15. package/commands/gsd-t-plan.md +26 -472
  16. package/commands/gsd-t-prd.md +25 -311
  17. package/commands/gsd-t-quick.md +1 -1
  18. package/commands/gsd-t-resume.md +0 -33
  19. package/commands/gsd-t-verify.md +52 -569
  20. package/commands/gsd-t-wave.md +41 -430
  21. package/package.json +1 -1
  22. package/scripts/gsd-t-calibration-hook.js +3 -1
  23. package/scripts/hooks/pre-commit-journey-coverage +0 -2
  24. package/scripts/hooks/pre-commit-playwright-gate +0 -2
  25. package/templates/CLAUDE-global.md +43 -173
  26. package/templates/CLAUDE-project.md +118 -104
  27. package/templates/prompts/design-verify-subagent.md +3 -0
  28. package/templates/prompts/qa-subagent.md +3 -0
  29. package/templates/prompts/red-team-subagent.md +5 -2
  30. package/templates/workflows/_lib.js +176 -0
  31. package/templates/workflows/gsd-t-debug.workflow.js +86 -0
  32. package/templates/workflows/gsd-t-execute.workflow.js +206 -0
  33. package/templates/workflows/gsd-t-integrate.workflow.js +71 -0
  34. package/templates/workflows/gsd-t-phase.workflow.js +94 -0
  35. package/templates/workflows/gsd-t-quick.workflow.js +72 -0
  36. package/templates/workflows/gsd-t-verify.workflow.js +348 -0
  37. package/templates/workflows/gsd-t-wave.workflow.js +43 -0
  38. package/bin/check-headless-sessions.js +0 -140
  39. package/bin/context-budget-audit.cjs +0 -447
  40. package/bin/context-meter-config.cjs +0 -101
  41. package/bin/context-meter-config.test.cjs +0 -101
  42. package/bin/event-stream.cjs +0 -205
  43. package/bin/gsd-t-benchmark-orchestrator.js +0 -437
  44. package/bin/gsd-t-capture-lint.cjs +0 -440
  45. package/bin/gsd-t-economics.cjs +0 -315
  46. package/bin/gsd-t-in-session-usage.cjs +0 -213
  47. package/bin/gsd-t-orchestrator-config.cjs +0 -161
  48. package/bin/gsd-t-orchestrator-queue.cjs +0 -180
  49. package/bin/gsd-t-orchestrator-recover.cjs +0 -231
  50. package/bin/gsd-t-orchestrator-worker.cjs +0 -219
  51. package/bin/gsd-t-orchestrator.js +0 -535
  52. package/bin/gsd-t-parallel-probe.cjs +0 -132
  53. package/bin/gsd-t-ratelimit-probe-worker.cjs +0 -237
  54. package/bin/gsd-t-ratelimit-probe.cjs +0 -648
  55. package/bin/gsd-t-report-tokens.cjs +0 -549
  56. package/bin/gsd-t-stream-feed-client.cjs +0 -151
  57. package/bin/gsd-t-token-backfill.cjs +0 -366
  58. package/bin/gsd-t-token-capture.cjs +0 -321
  59. package/bin/gsd-t-token-dashboard.cjs +0 -353
  60. package/bin/gsd-t-token-regenerate-log.cjs +0 -129
  61. package/bin/gsd-t-tool-attribution.cjs +0 -377
  62. package/bin/gsd-t-tool-cost.cjs +0 -195
  63. package/bin/gsd-t-transcript-tee.cjs +0 -246
  64. package/bin/gsd-t-unattended-heartbeat.cjs +0 -188
  65. package/bin/gsd-t-unattended-platform.cjs +0 -551
  66. package/bin/gsd-t-unattended-platform.js +0 -381
  67. package/bin/gsd-t-unattended-safety.cjs +0 -773
  68. package/bin/gsd-t-unattended-safety.js +0 -788
  69. package/bin/gsd-t-unattended.cjs +0 -2109
  70. package/bin/gsd-t-unattended.js +0 -1367
  71. package/bin/gsd-t-worker-dispatch.cjs +0 -211
  72. package/bin/handoff-lock.cjs +0 -249
  73. package/bin/handoff-lock.js +0 -249
  74. package/bin/headless-auto-spawn.cjs +0 -711
  75. package/bin/headless-auto-spawn.js +0 -373
  76. package/bin/headless-exit-codes.cjs +0 -67
  77. package/bin/live-activity-report.cjs +0 -615
  78. package/bin/log-tail.cjs +0 -81
  79. package/bin/m44-proof-measure.cjs +0 -285
  80. package/bin/m46-iter-proof.cjs +0 -149
  81. package/bin/m46-worker-proof.cjs +0 -201
  82. package/bin/m55-substrate-proof.cjs +0 -134
  83. package/bin/metrics-rollup.js +0 -200
  84. package/bin/model-windows.cjs +0 -99
  85. package/bin/model-windows.test.cjs +0 -75
  86. package/bin/parallelism-report.cjs +0 -535
  87. package/bin/runway-estimator.cjs +0 -242
  88. package/bin/spawn-plan-derive.cjs +0 -163
  89. package/bin/spawn-plan-status-updater.cjs +0 -292
  90. package/bin/spawn-plan-writer.cjs +0 -204
  91. package/bin/supervisor-pid-fingerprint.cjs +0 -126
  92. package/bin/token-budget.cjs +0 -265
  93. package/bin/unattended-watch-format.cjs +0 -178
  94. package/bin/watch-progress.js +0 -155
  95. package/commands/gsd-t-unattended-stop.md +0 -85
  96. package/commands/gsd-t-unattended-watch.md +0 -465
  97. package/commands/gsd-t-unattended.md +0 -476
  98. package/commands/gsd-t-visualize.md +0 -116
  99. package/scripts/gsd-t-context-meter.e2e.test.js +0 -364
  100. package/scripts/gsd-t-context-meter.js +0 -341
  101. package/scripts/gsd-t-context-meter.test.js +0 -471
  102. package/scripts/gsd-t-dashboard-server.js +0 -1203
  103. package/scripts/gsd-t-post-commit-spawn-plan.sh +0 -86
  104. package/scripts/gsd-t-transcript.html +0 -1821
  105. package/scripts/hooks/gsd-t-conversation-capture.js +0 -439
  106. package/scripts/hooks/gsd-t-in-session-usage-hook.js +0 -84
  107. package/scripts/spawn-plan-fmt-tokens.cjs +0 -80
  108. package/templates/hooks/post-commit-spawn-plan.sh +0 -85
@@ -1,373 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * GSD-T Headless Auto-Spawn — Detached headless continuation
5
- *
6
- * When the runway estimator refuses a run (projected context ≥ stop band),
7
- * the caller invokes `autoSpawnHeadless()` to hand off to a detached child
8
- * process running `gsd-t headless {command} --log`. The interactive session
9
- * never blocks on the child (`child.unref()`), so the user retains their
10
- * terminal and can work on unrelated tasks. On child completion, a macOS
11
- * notification fires (T2). The interactive session surfaces the result via
12
- * a read-back banner on the next `gsd-t-resume` or `gsd-t-status` call (T4).
13
- *
14
- * Zero external dependencies (Node.js built-ins only).
15
- *
16
- * Contract: .gsd-t/contracts/headless-auto-spawn-contract.md v1.0.0
17
- * Consumers: commands/gsd-t-execute|wave|integrate|quick|debug.md (via runway
18
- * estimator handoff), bin/runway-estimator.js (conceptual target).
19
- */
20
-
21
- const fs = require("fs");
22
- const path = require("path");
23
- const { spawn } = require("child_process");
24
- const {
25
- acquireHandoffLock,
26
- releaseHandoffLock,
27
- // waitForLockRelease — NOT consumed here. The child-side wait is
28
- // performed by `commands/gsd-t-resume.md` Step 0 (wired in m36
29
- // watch-loop Task 4); it calls waitForLockRelease(projectDir,
30
- // sessionId) before reading the continue-here file.
31
- } = require("./handoff-lock.js");
32
-
33
- // ── Constants ────────────────────────────────────────────────────────────────
34
-
35
- const SESSIONS_DIR_REL = path.join(".gsd-t", "headless-sessions");
36
- const LOG_DIR_REL = ".gsd-t"; // headless-{id}.log lives directly in .gsd-t
37
-
38
- // ── Exports ──────────────────────────────────────────────────────────────────
39
-
40
- module.exports = {
41
- autoSpawnHeadless,
42
- makeSessionId,
43
- writeSessionFile,
44
- writeContinueHereFile,
45
- markSessionCompleted,
46
- // M43 D4 — channel-separation invariant; see .cjs twin.
47
- shouldSpawnHeadless: () => true,
48
- };
49
-
50
- // M43 D4 — one-shot deprecation banner (legacy `.js` copy mirrors `.cjs`).
51
- let _deprecatedWatchWarned = false;
52
-
53
- // ── autoSpawnHeadless ────────────────────────────────────────────────────────
54
-
55
- /**
56
- * @param {{
57
- * command: string,
58
- * args?: string[],
59
- * continue_from?: string,
60
- * projectDir?: string,
61
- * context?: object,
62
- * sessionContext?: object,
63
- * sessionId?: string,
64
- * watch?: boolean,
65
- * spawnType?: 'primary' | 'validation'
66
- * }} opts
67
- * @returns {{ id: string | null, pid: number | null, logPath: string | null, timestamp: string, mode: 'headless' | 'in-context' }}
68
- */
69
- function autoSpawnHeadless(opts) {
70
- const command = opts.command;
71
- const args = opts.args || [];
72
- const continue_from = opts.continue_from || ".";
73
- const projectDir = opts.projectDir || process.cwd();
74
- const context = opts.context || opts.sessionContext || null;
75
- // M43 D4 — `watch`/`inSession` ignored under v2.0.0 channel-separation.
76
- const legacyWatch = opts.watch === true;
77
- const legacyInSession = opts.inSession === true;
78
- if ((legacyWatch || legacyInSession) && !_deprecatedWatchWarned) {
79
- _deprecatedWatchWarned = true;
80
- try {
81
- process.stderr.write(
82
- "[headless-default] `watch`/`inSession` flag is deprecated under headless-default-contract v2.0.0 — every spawn is headless; caller hint ignored.\n",
83
- );
84
- } catch (_) {
85
- /* best-effort */
86
- }
87
- }
88
- const spawnType = opts.spawnType || "primary";
89
-
90
- if (!command || typeof command !== "string") {
91
- throw new Error("autoSpawnHeadless: `command` is required");
92
- }
93
- if (spawnType !== "primary" && spawnType !== "validation") {
94
- throw new Error(
95
- `autoSpawnHeadless: \`spawnType\` must be 'primary' or 'validation' (got ${JSON.stringify(spawnType)})`,
96
- );
97
- }
98
-
99
- const timestamp = new Date().toISOString();
100
- const id = makeSessionId(command, new Date());
101
- const logPath = path.join(projectDir, LOG_DIR_REL, `headless-${id}.log`);
102
-
103
- ensureDir(path.join(projectDir, LOG_DIR_REL));
104
- ensureDir(path.join(projectDir, SESSIONS_DIR_REL));
105
-
106
- // Handoff-lock gate (m36 gap-fix T2). Only engaged when the caller
107
- // supplies a `sessionId` — existing callers that do not pass one keep
108
- // the pre-m36 behavior unchanged. When engaged, the lock is held
109
- // across writeContinueHereFile + spawn so a child that is already
110
- // waiting via `waitForLockRelease()` (see commands/gsd-t-resume.md
111
- // Step 0, wired by m36 watch-loop T4) cannot read a half-written
112
- // continue-here file. See .gsd-t/contracts/headless-auto-spawn-contract.md
113
- // v1.0.0 (implementation-detail primitive; no contract bump).
114
- const lockSessionId = typeof opts.sessionId === "string" && opts.sessionId
115
- ? opts.sessionId
116
- : null;
117
- let lockHandle = null;
118
- if (lockSessionId) {
119
- lockHandle = acquireHandoffLock(projectDir, lockSessionId);
120
- }
121
-
122
- let pid = 0;
123
- try {
124
- // Open log file descriptor before spawning — child writes directly.
125
- const logFd = fs.openSync(logPath, "a");
126
-
127
- // Headless invocation: `node bin/gsd-t.js headless <command> [args] --log`
128
- // The `gsd-t` CLI entry point is bin/gsd-t.js relative to projectDir.
129
- const gsdtCli = path.join(projectDir, "bin", "gsd-t.js");
130
- const childArgs = [gsdtCli, "headless", stripGsdtPrefix(command), ...args, "--log"];
131
-
132
- // v3.12.14 — env-var propagation for telemetry tagging (mirror of the
133
- // authoritative .cjs variant). PRODUCTION consumers import the .cjs; this
134
- // .js copy is retained only to keep the legacy test green.
135
- const workerEnv = Object.assign({}, process.env, {
136
- GSD_T_COMMAND: command,
137
- GSD_T_PHASE: process.env.GSD_T_PHASE || "execute",
138
- GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || projectDir,
139
- });
140
- if (process.env.GSD_T_TRACE_ID) workerEnv.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
141
- if (process.env.GSD_T_MODEL) workerEnv.GSD_T_MODEL = process.env.GSD_T_MODEL;
142
-
143
- const child = spawn("node", childArgs, {
144
- cwd: projectDir,
145
- detached: true,
146
- stdio: ["ignore", logFd, logFd],
147
- env: workerEnv,
148
- });
149
-
150
- child.unref();
151
- fs.closeSync(logFd);
152
-
153
- pid = child.pid || 0;
154
-
155
- writeSessionFile(projectDir, {
156
- id,
157
- pid,
158
- logPath: path.relative(projectDir, logPath),
159
- startTimestamp: timestamp,
160
- command,
161
- args,
162
- status: "running",
163
- continueFromPath: continue_from,
164
- surfaced: false,
165
- });
166
-
167
- writeContinueHereFile(projectDir, id, context);
168
- } finally {
169
- // Release the lock AFTER the child is confirmed started and
170
- // handoff artifacts are on disk. The finally ensures a spawn
171
- // failure still frees the slot for a retry.
172
- if (lockHandle) {
173
- try {
174
- releaseHandoffLock(lockHandle);
175
- } catch (_) {
176
- /* idempotent best-effort */
177
- }
178
- }
179
- }
180
-
181
- // T2 — install completion watcher. Non-blocking (setImmediate) so the
182
- // caller's return is not delayed. The watcher uses `child.on('exit')` on
183
- // a separately-spawned bridge process; here we defer to fs.watchFile for
184
- // a detached approach that survives even after the parent's `unref()`.
185
- installCompletionWatcher({ projectDir, id, logPath, pid, startTimestamp: timestamp });
186
-
187
- return {
188
- id,
189
- pid,
190
- logPath: path.relative(projectDir, logPath),
191
- timestamp,
192
- mode: "headless",
193
- };
194
- }
195
-
196
- // ── makeSessionId ────────────────────────────────────────────────────────────
197
-
198
- /**
199
- * @param {string} command
200
- * @param {Date} [now]
201
- * @returns {string} e.g., "gsd-t-execute-2026-04-15-01-23-45"
202
- */
203
- function makeSessionId(command, now) {
204
- const d = now || new Date();
205
- const pad = (n) => String(n).padStart(2, "0");
206
- const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
207
- const time = `${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`;
208
- const base = stripGsdtPrefix(command) || command;
209
- return `gsd-t-${base}-${date}-${time}`;
210
- }
211
-
212
- // ── writeSessionFile ─────────────────────────────────────────────────────────
213
-
214
- function writeSessionFile(projectDir, session) {
215
- const fp = path.join(projectDir, SESSIONS_DIR_REL, `${session.id}.json`);
216
- ensureDir(path.dirname(fp));
217
- fs.writeFileSync(fp, JSON.stringify(session, null, 2) + "\n");
218
- return fp;
219
- }
220
-
221
- // ── writeContinueHereFile ────────────────────────────────────────────────────
222
-
223
- function writeContinueHereFile(projectDir, id, context) {
224
- const fp = path.join(projectDir, SESSIONS_DIR_REL, `${id}-context.json`);
225
- const payload = context || buildContextSnapshot(projectDir);
226
- fs.writeFileSync(fp, JSON.stringify(payload, null, 2) + "\n");
227
- return fp;
228
- }
229
-
230
- function buildContextSnapshot(projectDir) {
231
- // Best-effort snapshot of current GSD-T state at handoff time.
232
- const snap = {
233
- capturedAt: new Date().toISOString(),
234
- progress: null,
235
- currentDomain: null,
236
- pendingTasks: [],
237
- lastDecisionLogEntry: null,
238
- currentWave: null,
239
- };
240
- try {
241
- const progressFp = path.join(projectDir, ".gsd-t", "progress.md");
242
- if (fs.existsSync(progressFp)) {
243
- const raw = fs.readFileSync(progressFp, "utf8");
244
- snap.progress = firstNLines(raw, 20);
245
- // Pull the last Decision Log entry (last non-empty bullet line).
246
- const lines = raw.split("\n");
247
- for (let i = lines.length - 1; i >= 0; i--) {
248
- const ln = lines[i].trim();
249
- if (ln.startsWith("- ")) {
250
- snap.lastDecisionLogEntry = ln;
251
- break;
252
- }
253
- }
254
- }
255
- } catch (_) {
256
- /* best-effort */
257
- }
258
- return snap;
259
- }
260
-
261
- function firstNLines(s, n) {
262
- return s.split("\n").slice(0, n).join("\n");
263
- }
264
-
265
- // ── markSessionCompleted ─────────────────────────────────────────────────────
266
-
267
- /**
268
- * T2 completion hook — updates the session file in place.
269
- * @param {string} projectDir
270
- * @param {string} id
271
- * @param {{ exitCode: number, endTimestamp?: string }} result
272
- */
273
- function markSessionCompleted(projectDir, id, result) {
274
- const fp = path.join(projectDir, SESSIONS_DIR_REL, `${id}.json`);
275
- if (!fs.existsSync(fp)) return;
276
- try {
277
- const s = JSON.parse(fs.readFileSync(fp, "utf8"));
278
- s.status = "completed";
279
- s.exitCode = result.exitCode;
280
- s.endTimestamp = result.endTimestamp || new Date().toISOString();
281
- fs.writeFileSync(fp, JSON.stringify(s, null, 2) + "\n");
282
- } catch (_) {
283
- /* ignore */
284
- }
285
- }
286
-
287
- // ── Completion watcher (T2) — macOS notification on exit ─────────────────────
288
-
289
- function installCompletionWatcher(opts) {
290
- const { projectDir, id, pid, startTimestamp } = opts;
291
- if (!pid || pid <= 0) return;
292
-
293
- // Poll-based watcher. We can't hold a reference to the child (it's unref'd
294
- // and detached), so we poll `process.kill(pid, 0)` which throws if the
295
- // process is gone. This is cheap and survives across detachment.
296
- const POLL_MS = 2000;
297
- const MAX_WAIT_MS = 60 * 60 * 1000; // 1 hour safety cap
298
- const startMs = Date.now();
299
-
300
- const timer = setInterval(() => {
301
- let alive = false;
302
- try {
303
- process.kill(pid, 0);
304
- alive = true;
305
- } catch (_) {
306
- alive = false;
307
- }
308
- if (!alive) {
309
- clearInterval(timer);
310
- // Exit code is unknown from a signal-based probe. Best-effort: read
311
- // the log's last lines to guess, otherwise default to 0.
312
- const exitCode = guessExitCodeFromLog(projectDir, id);
313
- markSessionCompleted(projectDir, id, {
314
- exitCode,
315
- endTimestamp: new Date().toISOString(),
316
- });
317
- fireMacNotification({ id, command: extractCommand(id), startTimestamp });
318
- } else if (Date.now() - startMs > MAX_WAIT_MS) {
319
- clearInterval(timer);
320
- }
321
- }, POLL_MS);
322
-
323
- // Let the timer not block the parent's exit.
324
- if (typeof timer.unref === "function") timer.unref();
325
- }
326
-
327
- function guessExitCodeFromLog(projectDir, id) {
328
- try {
329
- const fp = path.join(projectDir, LOG_DIR_REL, `headless-${id}.log`);
330
- if (!fs.existsSync(fp)) return 0;
331
- const raw = fs.readFileSync(fp, "utf8");
332
- if (/exit code[: ]+(\d+)/i.test(raw)) {
333
- const m = raw.match(/exit code[: ]+(\d+)/i);
334
- return parseInt(m[1], 10);
335
- }
336
- return 0;
337
- } catch (_) {
338
- return 0;
339
- }
340
- }
341
-
342
- function extractCommand(id) {
343
- // id format: gsd-t-{command}-{date}-{time}
344
- const m = id.match(/^gsd-t-(.+?)-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}$/);
345
- return m ? m[1] : id;
346
- }
347
-
348
- function fireMacNotification({ id, command }) {
349
- if (process.platform !== "darwin") return;
350
- try {
351
- const { spawn } = require("child_process");
352
- const msg = `GSD-T headless run complete: ${id}`;
353
- const script = `display notification "${msg}" with title "GSD-T" subtitle "${command}"`;
354
- const child = spawn("osascript", ["-e", script], {
355
- detached: true,
356
- stdio: "ignore",
357
- });
358
- child.unref();
359
- } catch (_) {
360
- /* graceful degradation */
361
- }
362
- }
363
-
364
- // ── Helpers ──────────────────────────────────────────────────────────────────
365
-
366
- function ensureDir(d) {
367
- if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
368
- }
369
-
370
- function stripGsdtPrefix(command) {
371
- if (typeof command !== "string") return "";
372
- return command.replace(/^gsd-t-/, "");
373
- }
@@ -1,67 +0,0 @@
1
- /**
2
- * Shared helper: map a `claude -p` process exit code + output to the
3
- * GSD-T headless exit-code contract.
4
- *
5
- * Lives in its own file so non-entry modules (e.g. gsd-t-unattended.cjs)
6
- * can require it without pulling in the full CLI at bin/gsd-t.js. That
7
- * decoupling lets PROJECT_BIN_TOOLS ship the supervisor without also
8
- * vendoring the CLI itself — projects resolve `gsd-t` from the global
9
- * install, not from a project-local copy.
10
- *
11
- * Exit codes (contract):
12
- * 0 — success
13
- * 1 — verification/test failure
14
- * 2 — context budget exceeded
15
- * 3 — non-zero process exit (other)
16
- * 4 — blocked / needs human
17
- * 5 — unknown slash command (claude -p exited 0 but rejected the prompt)
18
- */
19
-
20
- "use strict";
21
-
22
- // Match terminal markers, not narration. A bare "tests failed" substring will
23
- // appear in healthy output ("0 tests failed", "no tests failed", quoted as an
24
- // example in prose). Require either a non-zero count prefix or a structured
25
- // terminal marker (start of line, uppercase-FAIL prefix, Jest-style summary).
26
- // Bug history: M45 worker output contained "tests failed" 6× in narration,
27
- // causing the supervisor to map exit 0 → exit 1 and halt a successful run.
28
-
29
- const NONZERO_FAILURE_COUNT_RE =
30
- /(?:^|\b)([1-9]\d*)\s+(?:tests?|specs?|assertions?|examples?|suites?)\s+failed\b/i;
31
- const STRUCTURED_FAIL_RE = /^FAIL[:\s]/m;
32
- const JEST_SUMMARY_FAIL_RE = /^Tests:\s+\d+\s+failed/im;
33
-
34
- // Verification-phrase matchers: require the phrase at a line boundary or
35
- // preceded by a sentence-start punctuation — not mid-prose. Each phrase is
36
- // distinctive enough that start-of-line / post-punctuation is a reliable
37
- // terminal-marker signal.
38
- const VERIFICATION_FAILED_RE =
39
- /(?:^|[.!?]\s+)(?:verification|verify|quality gate)\s+failed\b/im;
40
-
41
- // Context-budget phrases — same polarity discipline. Tolerant of surrounding
42
- // punctuation (— / :) but requires the phrase at a line boundary.
43
- const CONTEXT_BUDGET_RE =
44
- /(?:^|[.!?]\s+)(?:context budget exceeded|context window exceeded|budget exceeded|token limit)\b/im;
45
-
46
- // Blocker compound: "blocked" within 80 chars of a human-gate phrase, both
47
- // anchored to recognizable boundaries. The 80-char proximity keeps unrelated
48
- // mentions from compounding.
49
- const BLOCKED_HUMAN_RE =
50
- /\bblocked\b[\s\S]{0,80}?\b(?:needs? human|human input|human approval)\b/i;
51
-
52
- function mapHeadlessExitCode(processExitCode, output) {
53
- if (processExitCode !== 0 && processExitCode !== null) return 3;
54
- const raw = output || "";
55
- if (/^unknown command:/im.test(raw)) return 5;
56
- if (CONTEXT_BUDGET_RE.test(raw)) return 2;
57
- if (BLOCKED_HUMAN_RE.test(raw)) return 4;
58
- if (
59
- VERIFICATION_FAILED_RE.test(raw) ||
60
- NONZERO_FAILURE_COUNT_RE.test(raw) ||
61
- STRUCTURED_FAIL_RE.test(raw) ||
62
- JEST_SUMMARY_FAIL_RE.test(raw)
63
- ) return 1;
64
- return 0;
65
- }
66
-
67
- module.exports = { mapHeadlessExitCode };