@tekyzinc/gsd-t 3.29.11 โ†’ 4.0.14

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 (114) hide show
  1. package/CHANGELOG.md +171 -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-complete-milestone.md +6 -12
  7. package/commands/gsd-t-debug.md +47 -533
  8. package/commands/gsd-t-design-decompose.md +24 -497
  9. package/commands/gsd-t-doc-ripple.md +23 -139
  10. package/commands/gsd-t-execute.md +41 -958
  11. package/commands/gsd-t-feature.md +2 -6
  12. package/commands/gsd-t-gap-analysis.md +3 -6
  13. package/commands/gsd-t-help.md +2 -4
  14. package/commands/gsd-t-impact.md +26 -295
  15. package/commands/gsd-t-init-scan-setup.md +7 -7
  16. package/commands/gsd-t-integrate.md +44 -369
  17. package/commands/gsd-t-milestone.md +25 -124
  18. package/commands/gsd-t-partition.md +28 -539
  19. package/commands/gsd-t-plan.md +26 -472
  20. package/commands/gsd-t-prd.md +25 -311
  21. package/commands/gsd-t-quick.md +1 -1
  22. package/commands/gsd-t-resume.md +0 -33
  23. package/commands/gsd-t-scan.md +45 -652
  24. package/commands/gsd-t-verify.md +52 -569
  25. package/commands/gsd-t-wave.md +41 -430
  26. package/package.json +1 -1
  27. package/scripts/gsd-t-calibration-hook.js +3 -1
  28. package/scripts/hooks/pre-commit-journey-coverage +0 -2
  29. package/scripts/hooks/pre-commit-playwright-gate +0 -2
  30. package/templates/CLAUDE-global.md +44 -173
  31. package/templates/CLAUDE-project.md +118 -104
  32. package/templates/prompts/design-verify-subagent.md +3 -0
  33. package/templates/prompts/qa-subagent.md +3 -0
  34. package/templates/prompts/red-team-subagent.md +5 -2
  35. package/templates/workflows/_lib.js +176 -0
  36. package/templates/workflows/gsd-t-debug.workflow.js +86 -0
  37. package/templates/workflows/gsd-t-execute.workflow.js +206 -0
  38. package/templates/workflows/gsd-t-integrate.workflow.js +71 -0
  39. package/templates/workflows/gsd-t-phase.workflow.js +94 -0
  40. package/templates/workflows/gsd-t-quick.workflow.js +72 -0
  41. package/templates/workflows/gsd-t-scan.workflow.js +711 -0
  42. package/templates/workflows/gsd-t-verify.workflow.js +348 -0
  43. package/templates/workflows/gsd-t-wave.workflow.js +43 -0
  44. package/bin/check-headless-sessions.js +0 -140
  45. package/bin/context-budget-audit.cjs +0 -447
  46. package/bin/context-meter-config.cjs +0 -101
  47. package/bin/context-meter-config.test.cjs +0 -101
  48. package/bin/event-stream.cjs +0 -205
  49. package/bin/gsd-t-benchmark-orchestrator.js +0 -437
  50. package/bin/gsd-t-capture-lint.cjs +0 -440
  51. package/bin/gsd-t-economics.cjs +0 -315
  52. package/bin/gsd-t-in-session-usage.cjs +0 -213
  53. package/bin/gsd-t-orchestrator-config.cjs +0 -161
  54. package/bin/gsd-t-orchestrator-queue.cjs +0 -180
  55. package/bin/gsd-t-orchestrator-recover.cjs +0 -231
  56. package/bin/gsd-t-orchestrator-worker.cjs +0 -219
  57. package/bin/gsd-t-orchestrator.js +0 -535
  58. package/bin/gsd-t-parallel-probe.cjs +0 -132
  59. package/bin/gsd-t-ratelimit-probe-worker.cjs +0 -237
  60. package/bin/gsd-t-ratelimit-probe.cjs +0 -648
  61. package/bin/gsd-t-report-tokens.cjs +0 -549
  62. package/bin/gsd-t-stream-feed-client.cjs +0 -151
  63. package/bin/gsd-t-token-backfill.cjs +0 -366
  64. package/bin/gsd-t-token-capture.cjs +0 -321
  65. package/bin/gsd-t-token-dashboard.cjs +0 -353
  66. package/bin/gsd-t-token-regenerate-log.cjs +0 -129
  67. package/bin/gsd-t-tool-attribution.cjs +0 -377
  68. package/bin/gsd-t-tool-cost.cjs +0 -195
  69. package/bin/gsd-t-transcript-tee.cjs +0 -246
  70. package/bin/gsd-t-unattended-heartbeat.cjs +0 -188
  71. package/bin/gsd-t-unattended-platform.cjs +0 -551
  72. package/bin/gsd-t-unattended-platform.js +0 -381
  73. package/bin/gsd-t-unattended-safety.cjs +0 -773
  74. package/bin/gsd-t-unattended-safety.js +0 -788
  75. package/bin/gsd-t-unattended.cjs +0 -2109
  76. package/bin/gsd-t-unattended.js +0 -1367
  77. package/bin/gsd-t-worker-dispatch.cjs +0 -211
  78. package/bin/handoff-lock.cjs +0 -249
  79. package/bin/handoff-lock.js +0 -249
  80. package/bin/headless-auto-spawn.cjs +0 -711
  81. package/bin/headless-auto-spawn.js +0 -373
  82. package/bin/headless-exit-codes.cjs +0 -67
  83. package/bin/live-activity-report.cjs +0 -615
  84. package/bin/log-tail.cjs +0 -81
  85. package/bin/m44-proof-measure.cjs +0 -285
  86. package/bin/m46-iter-proof.cjs +0 -149
  87. package/bin/m46-worker-proof.cjs +0 -201
  88. package/bin/m55-substrate-proof.cjs +0 -134
  89. package/bin/metrics-rollup.js +0 -200
  90. package/bin/model-windows.cjs +0 -99
  91. package/bin/model-windows.test.cjs +0 -75
  92. package/bin/parallelism-report.cjs +0 -535
  93. package/bin/runway-estimator.cjs +0 -242
  94. package/bin/spawn-plan-derive.cjs +0 -163
  95. package/bin/spawn-plan-status-updater.cjs +0 -292
  96. package/bin/spawn-plan-writer.cjs +0 -204
  97. package/bin/supervisor-pid-fingerprint.cjs +0 -126
  98. package/bin/token-budget.cjs +0 -265
  99. package/bin/unattended-watch-format.cjs +0 -178
  100. package/bin/watch-progress.js +0 -155
  101. package/commands/gsd-t-unattended-stop.md +0 -85
  102. package/commands/gsd-t-unattended-watch.md +0 -465
  103. package/commands/gsd-t-unattended.md +0 -476
  104. package/commands/gsd-t-visualize.md +0 -116
  105. package/scripts/gsd-t-context-meter.e2e.test.js +0 -364
  106. package/scripts/gsd-t-context-meter.js +0 -341
  107. package/scripts/gsd-t-context-meter.test.js +0 -471
  108. package/scripts/gsd-t-dashboard-server.js +0 -1203
  109. package/scripts/gsd-t-post-commit-spawn-plan.sh +0 -86
  110. package/scripts/gsd-t-transcript.html +0 -1821
  111. package/scripts/hooks/gsd-t-conversation-capture.js +0 -439
  112. package/scripts/hooks/gsd-t-in-session-usage-hook.js +0 -84
  113. package/scripts/spawn-plan-fmt-tokens.cjs +0 -80
  114. 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