@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,465 +0,0 @@
1
- # GSD-T: Unattended Watch โ€” Stateless Watch Tick
2
-
3
- **Model**: haiku (polling a state file is mechanical โ€” no reasoning needed)
4
-
5
- You are running one tick of the unattended supervisor watch loop. This command is **stateless**: every firing re-reads `.gsd-t/.unattended/supervisor.pid` + `state.json` + `run.log` from disk and makes a fresh decision. There is no in-memory state between ticks, and a `/clear` followed by `/gsd-t-resume` during a live unattended run is a no-op.
6
-
7
- The watch loop is the user's visibility surface into the running supervisor. It fires every 270 seconds (inside the 5-minute prompt-cache TTL) and either:
8
- 1. Renders a compact status block + reschedules itself, OR
9
- 2. Prints a terminal report and STOPS rescheduling.
10
-
11
- See `unattended-supervisor-contract.md` ยง8 (Watch Tick Decision Tree), ยง4 (Status Enum), ยง3 (state.json schema).
12
-
13
- ## Decision Tree (Contract ยง8)
14
-
15
- ```
16
- 1. supervisor.pid absent โ†’ finalized cleanly โ†’ final report, STOP
17
- 2. kill -0 {pid} fails โ†’ crashed โ†’ crash diagnostics + log tail, STOP
18
- 3. state.json.status:
19
- - done โ†’ success report, STOP
20
- - failed โ†’ failure summary, STOP
21
- - stopped โ†’ user-stop confirm, STOP
22
- - initializing | running โ†’ render watch block, go to 4
23
- 4. ScheduleWakeup(270, '/gsd-t-unattended-watch', reason='unattended tick {iter}')
24
- ```
25
-
26
- **Critical**: Every branch except `initializing`/`running` is TERMINAL โ€” do NOT reschedule. When you reach a terminal state, print the report and end the turn. Do not add a "Next Up" block โ€” this is a self-rescheduling loop, not a phase workflow.
27
-
28
- ## Step 1: Check State Directory
29
-
30
- Run via Bash:
31
-
32
- ```bash
33
- if [ ! -d ".gsd-t/.unattended" ]; then
34
- echo "No supervisor state directory at .gsd-t/.unattended/ โ€” nothing to watch."
35
- exit 0
36
- fi
37
- ```
38
-
39
- If the directory does not exist, there is no supervisor to watch. Exit 0, do NOT reschedule. End the turn.
40
-
41
- ## Step 2: Read State, PID, and Run.log Tail
42
-
43
- Run a single `node -e` block that gathers everything needed for the decision tree. This reads the last ~2KB of `run.log` via `fs.open` + `fs.read` with a negative offset from end (not `readFileSync`), so the command stays cheap even when `run.log` is hundreds of MB.
44
-
45
- ```bash
46
- node -e "
47
- const fs = require('fs');
48
- const path = require('path');
49
-
50
- const UDIR = '.gsd-t/.unattended';
51
- const PID_FILE = path.join(UDIR, 'supervisor.pid');
52
- const STATE_FILE = path.join(UDIR, 'state.json');
53
- const LOG_FILE = path.join(UDIR, 'run.log');
54
-
55
- function out(k, v) { console.log(k + '=' + JSON.stringify(v ?? null)); }
56
-
57
- // --- PID file (JSON or legacy bare-integer; canonical reader handles both) ---
58
- let pid = null;
59
- let pidFileExists = fs.existsSync(PID_FILE);
60
- if (pidFileExists) {
61
- try {
62
- const { readPidFile } = require('./bin/supervisor-pid-fingerprint.cjs');
63
- const entry = readPidFile(process.cwd());
64
- pid = entry && Number.isInteger(entry.pid) ? entry.pid : null;
65
- } catch (_) { pid = null; }
66
- }
67
- out('PID_FILE_EXISTS', pidFileExists);
68
- out('PID', pid);
69
-
70
- // --- kill -0 liveness ---
71
- let alive = null;
72
- if (pid !== null) {
73
- try { process.kill(pid, 0); alive = true; }
74
- catch (_) { alive = false; }
75
- }
76
- out('ALIVE', alive);
77
-
78
- // --- state.json ---
79
- let state = null;
80
- try { state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); }
81
- catch (_) {
82
- // tolerate transient rename race โ€” retry once
83
- try { state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); }
84
- catch (_) { state = null; }
85
- }
86
- if (state) {
87
- out('STATUS', state.status);
88
- out('SESSION', state.sessionId);
89
- out('MILESTONE', state.milestone);
90
- out('WAVE', state.wave);
91
- out('TASK', state.task);
92
- out('ITER', state.iter);
93
- out('MAX_ITER', state.maxIterations);
94
- out('STARTED_AT', state.startedAt);
95
- out('LAST_TICK', state.lastTick);
96
- out('LAST_EXIT', state.lastExit);
97
- out('LAST_ELAPSED_MS', state.lastElapsedMs);
98
- out('LOG_PATH', state.logPath || LOG_FILE);
99
- // elapsed
100
- const started = state.startedAt ? Date.parse(state.startedAt) : null;
101
- const elapsedMs = started ? (Date.now() - started) : null;
102
- out('ELAPSED_MS', elapsedMs);
103
- // last tick age
104
- const lt = state.lastTick ? Date.parse(state.lastTick) : null;
105
- const tickAgeMs = lt ? (Date.now() - lt) : null;
106
- out('TICK_AGE_MS', tickAgeMs);
107
- // v3.13.11 Bug 3: IS_STALE computed deterministically here (not in Haiku's
108
- // rendering step) โ€” stale iff tickAgeMs > 540000 (2ร— 270s tick cadence per
109
- // unattended-supervisor-contract ยง3). Below threshold โ†’ false. At/above
110
- // threshold โ†’ true. Renderer just reads this flag; no prose interpretation.
111
- const IS_STALE = tickAgeMs !== null && tickAgeMs > 540000;
112
- out('IS_STALE', IS_STALE);
113
- } else {
114
- out('STATUS', null);
115
- out('IS_STALE', false);
116
- }
117
-
118
- // --- run.log tail (last ~2KB via fd seek, not full read) ---
119
- let tailLines = [];
120
- try {
121
- const st = fs.statSync(LOG_FILE);
122
- const size = st.size;
123
- const readLen = Math.min(size, 2048);
124
- const offset = size - readLen;
125
- const fd = fs.openSync(LOG_FILE, 'r');
126
- const buf = Buffer.alloc(readLen);
127
- fs.readSync(fd, buf, 0, readLen, offset);
128
- fs.closeSync(fd);
129
- const text = buf.toString('utf8');
130
- tailLines = text.split('\n').filter(l => l.trim().length > 0);
131
- } catch (_) { tailLines = []; }
132
- // last 50 lines for crash path; last 1 non-empty for normal path
133
- const last50 = tailLines.slice(-50);
134
- const last1 = tailLines.length > 0 ? tailLines[tailLines.length - 1] : '';
135
- out('LOG_TAIL_LAST1', last1);
136
- out('LOG_TAIL_LAST50', last50.join('\n'));
137
-
138
- // --- workflow progress from progress.md and domain task files ---
139
- let phase = 'unknown';
140
- let waveInfo = '';
141
- let waveCurrent = 0;
142
- let waveTotal = 0;
143
- let tasksDone = 0;
144
- let tasksTotal = 0;
145
- let domainSummary = [];
146
- try {
147
- const prog = fs.readFileSync('.gsd-t/progress.md', 'utf8');
148
- // Extract phase from Status line
149
- const statusM = prog.match(/^## Status:.*?(\b(?:DEFINED|PARTITIONED|PLANNED|EXECUTING|EXECUTED|INTEGRATING|VERIFYING|VERIFIED|COMPLETE|IN.PROGRESS)\b)/mi);
150
- if (statusM) phase = statusM[1].trim();
151
- // Extract "Wave N of M" or latest "Wave N" from Decision Log
152
- const waveOfM = prog.match(/Wave\s+(\d+)\s+(?:of|\/)\s+(\d+)/gi);
153
- if (waveOfM && waveOfM.length > 0) {
154
- const last = waveOfM[waveOfM.length - 1];
155
- const parts = last.match(/(\d+)\s+(?:of|\/)\s+(\d+)/);
156
- if (parts) { waveCurrent = parseInt(parts[1], 10); waveTotal = parseInt(parts[2], 10); }
157
- } else {
158
- const waveM = prog.match(/Wave\s+(\d+)/gi);
159
- if (waveM && waveM.length > 0) {
160
- const last = waveM[waveM.length - 1].match(/(\d+)/);
161
- if (last) waveCurrent = parseInt(last[1], 10);
162
- }
163
- }
164
- } catch (_) {}
165
-
166
- // Read domain task files for completion counts and wave totals
167
- try {
168
- const domainsDir = '.gsd-t/domains';
169
- if (fs.existsSync(domainsDir)) {
170
- const domains = fs.readdirSync(domainsDir).filter(d => {
171
- try { return fs.statSync(path.join(domainsDir, d)).isDirectory(); } catch (_) { return false; }
172
- });
173
- let maxWave = 0;
174
- for (const d of domains) {
175
- const tasksFile = path.join(domainsDir, d, 'tasks.md');
176
- if (!fs.existsSync(tasksFile)) continue;
177
- const txt = fs.readFileSync(tasksFile, 'utf8');
178
- const lines = txt.split('\n');
179
- let done = 0, total = 0;
180
- for (const line of lines) {
181
- // Count wave headers to determine total waves
182
- const wH = line.match(/^#+\s*Wave\s+(\d+)/i);
183
- if (wH) { const w = parseInt(wH[1], 10); if (w > maxWave) maxWave = w; }
184
- if (/^[-*]\s*\[x\]/i.test(line)) { done++; total++; }
185
- else if (/^[-*]\s*\[\s?\]/.test(line)) { total++; }
186
- }
187
- if (total > 0) {
188
- tasksDone += done;
189
- tasksTotal += total;
190
- domainSummary.push(d + ':' + done + '/' + total);
191
- }
192
- }
193
- if (maxWave > 0 && waveTotal === 0) waveTotal = maxWave;
194
- }
195
- } catch (_) {}
196
- if (waveCurrent > 0 && waveTotal > 0) waveInfo = 'Wave ' + waveCurrent + ' of ' + waveTotal;
197
- else if (waveCurrent > 0) waveInfo = 'Wave ' + waveCurrent;
198
- out('PHASE', phase);
199
- out('WAVE_INFO', waveInfo);
200
- out('TASKS_DONE', tasksDone);
201
- out('TASKS_TOTAL', tasksTotal);
202
- out('DOMAIN_SUMMARY', domainSummary.join(' | '));
203
- "
204
- ```
205
-
206
- ## Step 3: Branch on PID File Absence (Finalized Cleanly)
207
-
208
- If `PID_FILE_EXISTS=false`:
209
-
210
- The supervisor has finalized cleanly and removed its own PID file.
211
-
212
- ### Step 3a: Reconciliation probe
213
-
214
- Before emitting a report, check whether the supervisor's final `status` agrees with the on-disk milestone evidence. A `status=failed` with a **fresh matching milestone archive** is the M45-class false-failed-marker pathology: the worker completed the milestone (including the archive rename under `.gsd-t/milestones/`) but a polarity-unsound exit-code matcher mapped the worker's clean exit to `1`, which the supervisor then finalized as `status=failed`.
215
-
216
- ```bash
217
- node -e "
218
- const fs = require('fs');
219
- const path = require('path');
220
- const state = JSON.parse(fs.readFileSync('.gsd-t/.unattended/state.json', 'utf8'));
221
- const milestone = (state.milestone || '').toLowerCase();
222
- let reconciled = false;
223
- if (state.status === 'failed' && milestone) {
224
- // Look for a fresh archive directory matching the milestone slug.
225
- // Pattern: .gsd-t/milestones/{slug}-YYYY-MM-DD where slug contains the
226
- // milestone identifier (e.g. 'M45' โ†’ 'm45-conversation-stream-observability-2026-04-23').
227
- try {
228
- const archives = fs.readdirSync('.gsd-t/milestones')
229
- .filter(n => n.toLowerCase().includes(milestone));
230
- if (archives.length > 0) {
231
- // Check the most recent archive's mtime is within the supervisor's run window.
232
- const recent = archives
233
- .map(n => ({ n, mtime: fs.statSync(path.join('.gsd-t/milestones', n)).mtimeMs }))
234
- .sort((a, b) => b.mtime - a.mtime)[0];
235
- const runStart = Date.parse(state.startedAt || 0);
236
- if (recent && recent.mtime >= runStart) {
237
- reconciled = true;
238
- console.log('RECONCILED=true');
239
- console.log('RECONCILED_ARCHIVE=' + recent.n);
240
- }
241
- }
242
- } catch (_) {}
243
- }
244
- if (!reconciled) console.log('RECONCILED=false');
245
- "
246
- ```
247
-
248
- ### Step 3b: Render reconciled or raw report
249
-
250
- If `RECONCILED=true` AND `STATUS=failed`, the archive is the authority โ€” render the reconciled success report:
251
-
252
- ```
253
- โœ… Unattended supervisor finalized โ€” milestone completed (auto-reconciled).
254
-
255
- Session: {SESSION}
256
- Milestone: {MILESTONE} โ€” archived as {RECONCILED_ARCHIVE}
257
- Supervisor flag: status=failed (not trusted โ€” archive present)
258
- Iterations: {ITER} / {MAX_ITER}
259
- Total elapsed: {Hh Mm โ€” formatted from ELAPSED_MS}
260
- Log: {LOG_PATH}
261
- State: .gsd-t/.unattended/state.json
262
-
263
- Note: the worker completed the milestone, but a polarity-unsound exit-code
264
- matcher mapped clean exit โ†’ failed. Treat the archive as the source of truth.
265
- ```
266
-
267
- Otherwise (no reconciliation โ€” `STATUS` is genuinely terminal `done`, `failed` without archive, or `stopped`), render the raw final report:
268
-
269
- ```
270
- โœ… Unattended supervisor finalized cleanly.
271
-
272
- Session: {SESSION}
273
- Milestone: {MILESTONE}
274
- Final status: {STATUS}
275
- Iterations: {ITER} / {MAX_ITER}
276
- Total elapsed: {Hh Mm โ€” formatted from ELAPSED_MS}
277
- Log: {LOG_PATH}
278
- State: .gsd-t/.unattended/state.json
279
- ```
280
-
281
- If `state.json` was also unreadable (null STATUS), emit:
282
-
283
- ```
284
- โš ๏ธ Unattended supervisor PID file absent but state.json unreadable.
285
- Manual inspection required: ls -la .gsd-t/.unattended/
286
- ```
287
-
288
- **STOP** โ€” do NOT reschedule. End the turn.
289
-
290
- ## Step 4: Branch on Dead PID (Crashed)
291
-
292
- If `PID_FILE_EXISTS=true` AND `ALIVE=false`:
293
-
294
- The supervisor process is gone but it did not remove its PID file โ€” this is a crash. Print crash diagnostics including the last 50 lines of `run.log`:
295
-
296
- ```
297
- ๐Ÿ’ฅ Unattended supervisor CRASHED (PID {PID} no longer alive).
298
-
299
- Last known state:
300
- Session: {SESSION}
301
- Status: {STATUS}
302
- Iter: {ITER} / {MAX_ITER}
303
- Last tick: {LAST_TICK} (age {tickAgeMs โ†’ seconds})
304
- Last exit: {LAST_EXIT}
305
-
306
- Last 50 lines of run.log:
307
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
308
- {LOG_TAIL_LAST50}
309
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
310
-
311
- Recovery: inspect .gsd-t/.unattended/run.log and .gsd-t/.unattended/state.json.
312
- Remove the stale PID file with: rm .gsd-t/.unattended/supervisor.pid
313
- Then relaunch with: /gsd-t-unattended
314
- ```
315
-
316
- **STOP** โ€” do NOT reschedule. End the turn.
317
-
318
- ## Step 5: Branch on Terminal Status
319
-
320
- If `PID_FILE_EXISTS=true` AND `ALIVE=true` AND `STATUS` is terminal (`done` | `failed` | `stopped`):
321
-
322
- The supervisor is still alive but has transitioned to a terminal status on its last tick. The next supervisor pass will remove the PID file โ€” we just caught it mid-cleanup. Print the matching report and STOP.
323
-
324
- ### 5a: STATUS=done
325
-
326
- ```
327
- ๐ŸŽ‰ Unattended supervisor COMPLETED the milestone.
328
-
329
- Session: {SESSION}
330
- Milestone: {MILESTONE} โ€” DONE
331
- Iterations: {ITER} / {MAX_ITER}
332
- Total elapsed: {Hh Mm}
333
- Log: {LOG_PATH}
334
-
335
- The supervisor will clean up its PID file momentarily.
336
- ```
337
-
338
- ### 5b: STATUS=failed
339
-
340
- ```
341
- โŒ Unattended supervisor HALTED โ€” status=failed.
342
-
343
- Session: {SESSION}
344
- Milestone: {MILESTONE}
345
- Iterations: {ITER} / {MAX_ITER}
346
- Last exit: {LAST_EXIT}
347
- Total elapsed: {Hh Mm}
348
- Log: {LOG_PATH}
349
-
350
- See run.log for the halt reason. The supervisor finalized state.json before exiting.
351
- ```
352
-
353
- ### 5c: STATUS=stopped
354
-
355
- ```
356
- ๐Ÿ›‘ Unattended supervisor STOPPED by user.
357
-
358
- Session: {SESSION}
359
- Milestone: {MILESTONE}
360
- Iterations: {ITER} / {MAX_ITER}
361
- Total elapsed: {Hh Mm}
362
- Log: {LOG_PATH}
363
-
364
- Stop sentinel was honored between worker iterations. Relaunch with /gsd-t-unattended.
365
- ```
366
-
367
- **STOP** โ€” do NOT reschedule. End the turn.
368
-
369
- ## Step 6: Render Live Watch Block (Non-Terminal)
370
-
371
- If `PID_FILE_EXISTS=true` AND `ALIVE=true` AND `STATUS` is `initializing` or `running`:
372
-
373
- Render two blocks: the workflow-progress header (existing) plus the M38 **event-stream activity block** driven by `.gsd-t/events/*.jsonl` since the last cursor.
374
-
375
- ### 6a: Workflow Progress Header
376
-
377
- Format rules:
378
- - One extra space after each emoji (per CLAUDE.md markdown table rules โ€” preserves alignment in terminal views).
379
- - Elapsed formatted as `{H}h{M}m` from `ELAPSED_MS`.
380
- - Last-tick age formatted as `{S}s` or `{M}m{S}s`. If `IS_STALE=true` (computed deterministically in Step 2 as `tickAgeMs > 540000`), append ` โš ๏ธ stale`. The renderer must NOT interpret the threshold itself โ€” Step 2 is the source of truth; rendering just reads the IS_STALE flag verbatim.
381
- - Tasks progress bar: `[โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘] 8/12` โ€” filled blocks proportional to done/total.
382
- - Domain breakdown only shown if โ‰ค6 domains (otherwise too noisy).
383
-
384
- ```
385
- โš™ Unattended โ€” {MILESTONE} ยท {PHASE}{ ยท {WAVE_INFO}}
386
- ๐Ÿ“‹ Tasks: {TASKS_DONE}/{TASKS_TOTAL} [{progress bar}] ยท elapsed {Hh Mm}
387
- ๐Ÿ”ง {DOMAIN_SUMMARY or "No domains yet (pre-partition)"}
388
- โฑ Iter {ITER}/{MAX_ITER} ยท last tick {tickAge} ยท last exit {LAST_EXIT} ({durationSec}s)
389
- โฐ Next tick in 270s ยท Stop: /gsd-t-unattended-stop
390
- ```
391
-
392
- Progress bar rendering (8 chars wide):
393
- - If `TASKS_TOTAL > 0`: filled = round(TASKS_DONE / TASKS_TOTAL * 8), empty = 8 - filled. Use `โ–ˆ` for filled, `โ–‘` for empty.
394
- - If `TASKS_TOTAL == 0`: show `[โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘]` (pre-partition, no tasks yet).
395
-
396
- Domain summary formatting:
397
- - Each domain: `{name}: {done}/{total}` separated by ` | `
398
- - If domains > 6, show top 3 incomplete + "... +{N} more"
399
- - If no domains exist, show phase-appropriate message: "Partitioning..." / "Planning..." / "No domains yet"
400
-
401
- ### 6b: Event-Stream Activity Block (M38)
402
-
403
- After the workflow header, render the structured activity log by reading
404
- `.gsd-t/events/YYYY-MM-DD.jsonl` since the last cursor (`.gsd-t/.unattended/event-cursor`).
405
- The formatter is pure and lives at `bin/unattended-watch-format.cjs` โ€” invoke it via `node -e`:
406
-
407
- ```bash
408
- node -e "
409
- const es = require('./bin/event-stream.cjs');
410
- const wf = require('./bin/unattended-watch-format.cjs');
411
- const fs = require('fs');
412
- let state = {};
413
- try { state = JSON.parse(fs.readFileSync('.gsd-t/.unattended/state.json','utf8')); } catch(_){}
414
- const { events, newCursor } = es.readSinceCursor('.');
415
- console.log(wf.formatWatchTick({ events, state, withWatchProgress: true }));
416
- es.advanceCursor('.', newCursor);
417
- "
418
- ```
419
-
420
- Output shape (per contract ยง4):
421
-
422
- ```
423
- [unattended supervisor โ€” iter 14, +9m elapsed]
424
- โ–ถ task: m38-h1-T3 (wave wave-1)
425
- ๐Ÿ“ 6 files modified (commands/gsd-t-execute.md, ...)
426
- โœ… test_result: unit 1228/1228 pass
427
- โœ… subagent_verdict: qa pass (0 findings)
428
- โฑ duration: 8m 12s ยท verdict: pass
429
- ```
430
-
431
- Zero-event window output: `[unattended supervisor โ€” iter N, +Tm elapsed] (no new activity since last tick)`.
432
-
433
- The formatter applies the emoji-spacing rule itself; do not add post-processing.
434
-
435
- ## Step 7: Reschedule via ScheduleWakeup (Non-Terminal Only)
436
-
437
- **This step runs ONLY when Step 6 rendered a live watch block.** All terminal branches (Steps 3, 4, 5) must NOT reach this step โ€” they STOP.
438
-
439
- Call the harness `ScheduleWakeup` tool with these exact parameters:
440
-
441
- - `delaySeconds`: `270` (fixed โ€” inside the 5-minute prompt-cache TTL)
442
- - `prompt`: `/gsd-t-unattended-watch`
443
- - `reason`: `unattended tick {ITER}` โ€” substituting the integer `ITER` from Step 2
444
-
445
- Tool invocation pattern (make this real tool call, not a bash command):
446
-
447
- ```
448
- ScheduleWakeup(
449
- delaySeconds: 270,
450
- prompt: "/gsd-t-unattended-watch",
451
- reason: "unattended tick {ITER}"
452
- )
453
- ```
454
-
455
- After the tool call, end the turn. Do NOT output a "Next Up" hint, do NOT continue with further steps, do NOT summarize. The watch block from Step 6 plus the ScheduleWakeup tool call IS the entire turn output.
456
-
457
- ## Notes
458
-
459
- - **Stateless**: every tick re-reads state from disk. No memory between firings.
460
- - **Terminal = STOP**: any terminal branch (3, 4, 5) ends the loop. The user relaunches via `/gsd-t-unattended` if needed.
461
- - **Never spawn subagents**: this is a pure polling command โ€” no Task, no TeamCreate, no observability logging block.
462
- - **No branch guard, no pre-commit gate, no doc ripple**: this command does not modify any files.
463
- - **Stale-tick tolerance**: `IS_STALE=true` iff `tickAgeMs > 540000` (computed deterministically in Step 2). When stale, warn soft (`โš ๏ธ stale`) but still reschedule โ€” the supervisor may be mid-worker. The threshold math lives in Step 2's node block; rendering reads the flag verbatim.
464
-
465
- $ARGUMENTS