@tekyzinc/gsd-t 3.23.11 → 3.25.10

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 (74) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +7 -0
  3. package/bin/cli-preflight-checks/branch-guard.cjs +110 -0
  4. package/bin/cli-preflight-checks/contracts-stable.cjs +128 -0
  5. package/bin/cli-preflight-checks/deps-installed.cjs +89 -0
  6. package/bin/cli-preflight-checks/manifest-fresh.cjs +98 -0
  7. package/bin/cli-preflight-checks/ports-free.cjs +110 -0
  8. package/bin/cli-preflight-checks/working-tree-state.cjs +149 -0
  9. package/bin/cli-preflight.cjs +265 -0
  10. package/bin/gsd-t-context-brief-kinds/design-verify.cjs +139 -0
  11. package/bin/gsd-t-context-brief-kinds/execute.cjs +205 -0
  12. package/bin/gsd-t-context-brief-kinds/qa.cjs +130 -0
  13. package/bin/gsd-t-context-brief-kinds/red-team.cjs +131 -0
  14. package/bin/gsd-t-context-brief-kinds/scan.cjs +118 -0
  15. package/bin/gsd-t-context-brief-kinds/verify.cjs +157 -0
  16. package/bin/gsd-t-context-brief.cjs +395 -0
  17. package/bin/gsd-t-ratelimit-probe-worker.cjs +236 -0
  18. package/bin/gsd-t-ratelimit-probe.cjs +648 -0
  19. package/bin/gsd-t-verify-gate-judge.cjs +224 -0
  20. package/bin/gsd-t-verify-gate.cjs +612 -0
  21. package/bin/gsd-t.js +45 -1
  22. package/bin/live-activity-report.cjs +615 -0
  23. package/bin/m55-substrate-proof.cjs +134 -0
  24. package/bin/parallel-cli-tee.cjs +206 -0
  25. package/bin/parallel-cli.cjs +478 -0
  26. package/commands/gsd-t-execute.md +31 -0
  27. package/commands/gsd-t-help.md +21 -0
  28. package/commands/gsd-t-verify.md +38 -0
  29. package/docs/architecture.md +194 -0
  30. package/docs/diagrams/.gsd-t/.context-meter-state.json +10 -0
  31. package/docs/diagrams/.gsd-t/context-meter.log +9 -0
  32. package/docs/diagrams/.gsd-t/events/2026-05-08.jsonl +45 -0
  33. package/docs/diagrams/.gsd-t/events/2026-05-09.jsonl +1 -0
  34. package/docs/diagrams/.gsd-t/heartbeat-cd9e7f59-ba5b-406a-9ed6-16762f039e81.jsonl +48 -0
  35. package/docs/diagrams/01-top-level-map-d2.png +0 -0
  36. package/docs/diagrams/01-top-level-map.d2 +77 -0
  37. package/docs/diagrams/01-top-level-map.mmd +48 -0
  38. package/docs/diagrams/01-top-level-map.png +0 -0
  39. package/docs/diagrams/01-top-level-map.svg +126 -0
  40. package/docs/diagrams/02-milestone-lifecycle-d2.png +0 -0
  41. package/docs/diagrams/02-milestone-lifecycle.d2 +38 -0
  42. package/docs/diagrams/02-milestone-lifecycle.mmd +36 -0
  43. package/docs/diagrams/02-milestone-lifecycle.png +0 -0
  44. package/docs/diagrams/02-milestone-lifecycle.svg +114 -0
  45. package/docs/diagrams/03-wave-mode-d2.png +0 -0
  46. package/docs/diagrams/03-wave-mode.d2 +33 -0
  47. package/docs/diagrams/03-wave-mode.mmd +21 -0
  48. package/docs/diagrams/03-wave-mode.png +0 -0
  49. package/docs/diagrams/03-wave-mode.svg +113 -0
  50. package/docs/diagrams/04-design-to-code-d2.png +0 -0
  51. package/docs/diagrams/04-design-to-code.d2 +35 -0
  52. package/docs/diagrams/04-design-to-code.mmd +29 -0
  53. package/docs/diagrams/04-design-to-code.png +0 -0
  54. package/docs/diagrams/04-design-to-code.svg +115 -0
  55. package/docs/diagrams/05-backlog-d2.png +0 -0
  56. package/docs/diagrams/05-backlog.d2 +40 -0
  57. package/docs/diagrams/05-backlog.mmd +20 -0
  58. package/docs/diagrams/05-backlog.png +0 -0
  59. package/docs/diagrams/05-backlog.svg +113 -0
  60. package/docs/diagrams/06-automation-utilities-d2.png +0 -0
  61. package/docs/diagrams/06-automation-utilities.d2 +48 -0
  62. package/docs/diagrams/06-automation-utilities.mmd +47 -0
  63. package/docs/diagrams/06-automation-utilities.png +0 -0
  64. package/docs/diagrams/06-automation-utilities.svg +110 -0
  65. package/docs/diagrams/_theme.d2 +86 -0
  66. package/docs/requirements.md +48 -0
  67. package/docs/workflow-diagram.md +338 -0
  68. package/package.json +1 -1
  69. package/scripts/gsd-t-dashboard-server.js +190 -0
  70. package/scripts/gsd-t-transcript.html +200 -0
  71. package/templates/CLAUDE-global.md +46 -0
  72. package/templates/prompts/design-verify-subagent.md +3 -0
  73. package/templates/prompts/qa-subagent.md +3 -0
  74. package/templates/prompts/red-team-subagent.md +3 -0
@@ -0,0 +1,338 @@
1
+ # GSD-T Workflow Diagram
2
+
3
+ Visual reference for every GSD-T command and how they connect. Generated 2026-05-08, GSD-T v3.24.10.
4
+
5
+ **Visual language** (consistent across all 6 diagrams):
6
+
7
+ | Shape / Color | Role |
8
+ |---|---|
9
+ | 🟦 Blue rectangle | GSD-T command |
10
+ | 🟪 Dashed purple rectangle | Auto-spawned subagent (qa, doc-ripple, debug-loop) |
11
+ | 🟥 Red rectangle | Quality gate (Red Team, Design Verify) |
12
+ | 🟧 Amber diamond | Decision point |
13
+ | 🟫 Pink hexagon | Smart router (`/gsd`) |
14
+ | ⬜ Oval | User input / output endpoint |
15
+ | 🟨 Cylinder | File / artifact |
16
+ | **Solid arrow** | Typical successor / auto-advance |
17
+ | **Dashed arrow** | Optional or conditional path / spawn |
18
+
19
+ The PNGs are rendered from D2 sources alongside each diagram. The Mermaid source is kept below each PNG as a fallback for environments that auto-render Mermaid (GitHub markdown).
20
+
21
+ To regenerate after editing a `.d2` source:
22
+ ```sh
23
+ cd docs/diagrams
24
+ d2 --layout=elk --pad=40 NN-name.d2 NN-name.svg
25
+ # Then convert to PNG via headless Chrome (D2's native PNG export needs Playwright)
26
+ ```
27
+
28
+ ---
29
+
30
+ ## 1. Top-Level Map — Entry Points → Lifecycle
31
+
32
+ ![Top-level map](diagrams/01-top-level-map-d2.png)
33
+
34
+ ```mermaid
35
+ flowchart TD
36
+ USER([User input])
37
+
38
+ USER --> ROUTER{{"/gsd<br/>(smart router)"}}
39
+
40
+ ROUTER -->|"plain text<br/>(auto-route hook)"| ROUTER
41
+ ROUTER -->|continuation| RESUME["/gsd-t-resume"]
42
+ ROUTER -->|conversational| TALK["dialog-only<br/>no spawn"]
43
+ ROUTER -->|workflow| ENTRY{Entry kind?}
44
+
45
+ ENTRY -->|new greenfield| PROJ["/gsd-t-project"]
46
+ ENTRY -->|existing repo onboard| ISS["/gsd-t-init-scan-setup"]
47
+ ENTRY -->|new feature| FEAT["/gsd-t-feature"]
48
+ ENTRY -->|spec vs code| GAP["/gsd-t-gap-analysis"]
49
+ ENTRY -->|small task| QUICK["/gsd-t-quick"]
50
+ ENTRY -->|bug| DEBUG["/gsd-t-debug"]
51
+ ENTRY -->|design-to-code| DD["/gsd-t-design-decompose"]
52
+ ENTRY -->|backlog idea| BADD["/gsd-t-backlog-add"]
53
+ ENTRY -->|status check| STAT["/gsd-t-status"]
54
+
55
+ ISS --> INIT["/gsd-t-init"]
56
+ INIT --> SCAN["/gsd-t-scan"]
57
+ SCAN --> SETUP["/gsd-t-setup"]
58
+ SCAN -.->|techdebt found| PROMO["/gsd-t-promote-debt"]
59
+
60
+ PROJ --> MILE["/gsd-t-milestone"]
61
+ FEAT --> MILE
62
+ GAP -.->|gaps found| MILE
63
+ PROMO --> MILE
64
+ BADD --> BPROMO["/gsd-t-backlog-promote"]
65
+ BPROMO --> MILE
66
+ BPROMO -.->|small| QUICK
67
+ BPROMO -.->|bug| DEBUG
68
+
69
+ MILE --> WAVE_OR_MANUAL{Auto or manual?}
70
+ WAVE_OR_MANUAL -->|hands-off| WAVE["/gsd-t-wave<br/>(full cycle)"]
71
+ WAVE_OR_MANUAL -->|step-by-step| PART["/gsd-t-partition"]
72
+
73
+ DD --> DB["/gsd-t-design-build"]
74
+ DB --> DR["/gsd-t-design-review"]
75
+ DB -.-> DA["/gsd-t-design-audit"]
76
+
77
+ classDef entry fill:#e1f5ff,stroke:#0288d1
78
+ classDef milestone fill:#fff4e1,stroke:#f57c00
79
+ classDef router fill:#f3e5f5,stroke:#7b1fa2
80
+ class PROJ,ISS,FEAT,GAP,QUICK,DEBUG,DD,BADD,STAT entry
81
+ class MILE,WAVE,PART milestone
82
+ class ROUTER,ENTRY,WAVE_OR_MANUAL router
83
+ ```
84
+
85
+ ---
86
+
87
+ ## 2. Milestone Lifecycle — The Core Workflow
88
+
89
+ This is the heart of GSD-T. Every milestone passes through these phases. `/gsd-t-wave` runs them all hands-off; manual mode advances one at a time.
90
+
91
+ ![Milestone lifecycle](diagrams/02-milestone-lifecycle-d2.png)
92
+
93
+ ```mermaid
94
+ flowchart TD
95
+ MILE["/gsd-t-milestone<br/>define deliverable"]
96
+
97
+ MILE --> PART["/gsd-t-partition<br/>milestone → domains + contracts"]
98
+ PART -.->|complex| DISC["conversational mode<br/>(via /gsd)"]
99
+ DISC --> PLAN
100
+
101
+ PART --> PLAN["/gsd-t-plan<br/>atomic task lists per domain"]
102
+ PLAN -.->|risky| IMPACT["/gsd-t-impact<br/>downstream effect analysis"]
103
+ PLAN --> EXEC
104
+ IMPACT --> EXEC["/gsd-t-execute<br/>run tasks (solo or team)"]
105
+
106
+ EXEC -.->|after code| TS["/gsd-t-test-sync<br/>tests ↔ code"]
107
+ EXEC -.->|spawned| QA["/gsd-t-qa<br/>(subagent)"]
108
+ EXEC -.->|spawned| DR2["doc-ripple<br/>(subagent)"]
109
+ EXEC -.->|2 fails| DBGLOOP["headless --debug-loop"]
110
+
111
+ TS --> INTEG["/gsd-t-integrate<br/>wire domains together"]
112
+ INTEG --> VERIFY["/gsd-t-verify<br/>quality gates"]
113
+
114
+ VERIFY --> RT["Red Team<br/>adversarial QA"]
115
+ VERIFY --> DV["Design Verification<br/>(if design contract)"]
116
+
117
+ RT --> CM["/gsd-t-complete-milestone<br/>archive + git tag"]
118
+ DV --> CM
119
+
120
+ CM --> NEXT{More milestones?}
121
+ NEXT -->|yes| MILE
122
+ NEXT -->|no| DONE([Project complete])
123
+
124
+ classDef phase fill:#e8f5e9,stroke:#388e3c
125
+ classDef sub fill:#fff9c4,stroke:#f9a825
126
+ classDef gate fill:#ffebee,stroke:#c62828
127
+ class MILE,PART,PLAN,EXEC,TS,INTEG,VERIFY,CM phase
128
+ class QA,DR2,DBGLOOP,RT,DV,DISC sub
129
+ class IMPACT,NEXT gate
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 3. Wave Mode — Hands-Off Full Cycle
135
+
136
+ `/gsd-t-wave` chains every phase automatically. This is what runs in unattended mode.
137
+
138
+ ![Wave mode](diagrams/03-wave-mode-d2.png)
139
+
140
+ ```mermaid
141
+ flowchart LR
142
+ START([wave triggered]) --> P1[partition]
143
+ P1 --> P2[plan]
144
+ P2 --> P3[impact]
145
+ P3 --> P4[execute]
146
+ P4 --> P5[test-sync]
147
+ P5 --> P6[integrate]
148
+ P6 --> P7[verify]
149
+ P7 --> P8[complete-milestone]
150
+ P8 --> END([milestone shipped])
151
+
152
+ P4 -.spawned.-> SUB1[qa]
153
+ P4 -.spawned.-> SUB2[doc-ripple]
154
+ P4 -.parallel.-> PAR[parallel<br/>task dispatch]
155
+ P7 -.gates.-> GATE1[Red Team]
156
+ P7 -.gates.-> GATE2[Design Verify]
157
+
158
+ classDef phase fill:#e8f5e9,stroke:#388e3c
159
+ classDef sub fill:#fff9c4,stroke:#f9a825
160
+ class P1,P2,P3,P4,P5,P6,P7,P8 phase
161
+ class SUB1,SUB2,PAR,GATE1,GATE2 sub
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 4. Design-to-Code Pipeline
167
+
168
+ Triggered when user provides a Figma URL, screenshots, or "build from this design".
169
+
170
+ ![Design-to-code pipeline](diagrams/04-design-to-code-d2.png)
171
+
172
+ ```mermaid
173
+ flowchart TD
174
+ REQ([Design-to-code request])
175
+
176
+ REQ --> CHECK{Existing<br/>contracts?}
177
+ CHECK -->|"start over"| CLEAN[clean UI assets<br/>inline]
178
+ CHECK -->|none| DD
179
+ CHECK -->|present| DB
180
+
181
+ CLEAN --> DD["/gsd-t-design-decompose<br/>elements → widgets → pages"]
182
+
183
+ DD --> DDOUT[".gsd-t/contracts/design/<br/>elements/*.contract.md<br/>widgets/*.contract.md<br/>pages/*.contract.md<br/>INDEX.md"]
184
+
185
+ DDOUT --> DB["/gsd-t-design-build<br/>two-terminal review"]
186
+
187
+ DB --> T1[Term 1<br/>builder]
188
+ DB --> T2["/gsd-t-design-review<br/>Term 2 reviewer"]
189
+
190
+ T1 <-->|review loop| T2
191
+
192
+ T2 --> SHIP{All gates pass?}
193
+ SHIP -->|no| T1
194
+ SHIP -->|yes| DONE([UI shipped])
195
+
196
+ DONE -.optional.-> DA["/gsd-t-design-audit<br/>compare built vs Figma"]
197
+
198
+ classDef pipe fill:#e1f5ff,stroke:#0288d1
199
+ classDef art fill:#f3e5f5,stroke:#7b1fa2
200
+ class DD,DB,T1,T2,DA,CLEAN pipe
201
+ class DDOUT art
202
+ ```
203
+
204
+ ---
205
+
206
+ ## 5. Backlog Subsystem
207
+
208
+ Lightweight idea capture that feeds into the main workflow.
209
+
210
+ ![Backlog subsystem](diagrams/05-backlog-d2.png)
211
+
212
+ ```mermaid
213
+ flowchart LR
214
+ IDEA([User has idea]) --> BADD["/gsd-t-backlog-add"]
215
+ BADD --> BL[".gsd-t/backlog.md"]
216
+
217
+ BL --> VIEW["/gsd-t-backlog-list<br/>filtered view"]
218
+ BL --> EDIT["/gsd-t-backlog-edit"]
219
+ BL --> MOVE["/gsd-t-backlog-move<br/>reorder priority"]
220
+ BL --> RM["/gsd-t-backlog-remove"]
221
+ BL --> PROMO["/gsd-t-backlog-promote"]
222
+
223
+ PROMO --> ROUTE{Classify}
224
+ ROUTE -->|big| MILE["/gsd-t-milestone"]
225
+ ROUTE -->|small| QUICK["/gsd-t-quick"]
226
+ ROUTE -->|bug| DEBUG["/gsd-t-debug"]
227
+ ROUTE -->|feature| FEAT["/gsd-t-feature"]
228
+
229
+ SETTINGS["/gsd-t-backlog-settings"] -.configures.-> BADD
230
+
231
+ classDef backlog fill:#fff3e0,stroke:#ef6c00
232
+ class BADD,VIEW,EDIT,MOVE,RM,PROMO,SETTINGS backlog
233
+ ```
234
+
235
+ ---
236
+
237
+ ## 6. Automation, Observability & Utilities
238
+
239
+ Commands that operate alongside the main workflow rather than within it.
240
+
241
+ ![Automation and utilities](diagrams/06-automation-utilities-d2.png)
242
+
243
+ ```mermaid
244
+ flowchart TD
245
+ subgraph UNATTENDED [Unattended supervision]
246
+ UN["/gsd-t-unattended<br/>detached supervisor"]
247
+ UNW["/gsd-t-unattended-watch<br/>live status (270s reschedule)"]
248
+ UNS["/gsd-t-unattended-stop<br/>graceful halt"]
249
+ UN -.feeds.-> UNW
250
+ UNS -.signals.-> UN
251
+ end
252
+
253
+ subgraph OBS [Observability]
254
+ VIZ["/gsd-t-visualize<br/>SSE + React Flow"]
255
+ MET["/gsd-t-metrics<br/>ELO + signal dist"]
256
+ STAT["/gsd-t-status<br/>cross-domain progress"]
257
+ HEALTH["/gsd-t-health<br/>validate .gsd-t/"]
258
+ end
259
+
260
+ subgraph SESSION [Session control]
261
+ RES["/gsd-t-resume"]
262
+ PAUSE["/gsd-t-pause"]
263
+ LOG["/gsd-t-log<br/>sync Decision Log"]
264
+ end
265
+
266
+ subgraph MAINT [Maintenance]
267
+ VU["/gsd-t-version-update"]
268
+ VUA["/gsd-t-version-update-all"]
269
+ GC["/gsd-t-global-change"]
270
+ TM["/gsd-t-triage-and-merge"]
271
+ POP["/gsd-t-populate<br/>docs from code"]
272
+ end
273
+
274
+ subgraph HEAD [Headless / CI]
275
+ HEX["headless exec"]
276
+ HQ["headless query"]
277
+ HDL["headless --debug-loop"]
278
+ PAR["parallel<br/>task dispatch"]
279
+ end
280
+
281
+ classDef u fill:#e3f2fd,stroke:#1565c0
282
+ classDef o fill:#f1f8e9,stroke:#558b2f
283
+ classDef s fill:#fce4ec,stroke:#ad1457
284
+ classDef m fill:#ede7f6,stroke:#4527a0
285
+ classDef h fill:#fff8e1,stroke:#ff6f00
286
+ class UN,UNW,UNS u
287
+ class VIZ,MET,STAT,HEALTH o
288
+ class RES,PAUSE,LOG s
289
+ class VU,VUA,GC,TM,POP m
290
+ class HEX,HQ,HDL,PAR h
291
+ ```
292
+
293
+ ---
294
+
295
+ ## 7. Phase Successor Table
296
+
297
+ | Completed phase | Auto-advances to | Also available |
298
+ |-----------------|------------------|----------------|
299
+ | `project` / `feature` | `milestone` | — |
300
+ | `init` / `init-scan-setup` | `scan` / `milestone` | `setup` |
301
+ | `scan` | `promote-debt` | `milestone` |
302
+ | `gap-analysis` | `milestone` | `feature` |
303
+ | `milestone` | `partition` | `wave` (full auto) |
304
+ | `partition` | `plan` | conversational mode (if complex) |
305
+ | `plan` | `execute` | `impact` (if risky) |
306
+ | `impact` | `execute` | — |
307
+ | `execute` | `test-sync` | spawns `qa`, `doc-ripple` |
308
+ | `test-sync` | `verify` | `integrate` (multi-domain) |
309
+ | `integrate` | `verify` | — |
310
+ | `verify` | `complete-milestone` (auto) | spawns Red Team + Design Verify |
311
+ | `complete-milestone` | `status` / next milestone | — |
312
+ | `design-decompose` | `design-build` | `partition` (if domains needed) |
313
+ | `design-build` | (review loop with `design-review`) | `design-audit` |
314
+
315
+ Standalone commands (no successor): `quick`, `debug`, `status`, `help`, `resume`, `pause`, `log`, `health`, `metrics`, `visualize`, all `backlog-*`, `version-update*`, `global-change`, `triage-and-merge`, `populate`, `unattended*`.
316
+
317
+ ---
318
+
319
+ ## 8. Auto-Spawned Subagents
320
+
321
+ These never need manual invocation — they fire as part of code-producing phases:
322
+
323
+ | Subagent | Spawned by | Purpose |
324
+ |----------|------------|---------|
325
+ | `qa` | partition, plan, execute, verify, quick, debug, integrate, complete-milestone | Test generation + gap reporting |
326
+ | `doc-ripple` | execute, integrate, quick, debug, wave | Update downstream docs |
327
+ | Red Team | verify (after QA passes) | Adversarial bug-finding |
328
+ | Design Verification | execute Step 5.25, verify (when design contract exists) | Browser-based pixel comparison |
329
+ | `headless --debug-loop` | execute, test-sync, verify, debug, wave (after 2 failed fix attempts) | Compaction-proof fix loop |
330
+ | `parallel` | execute, wave, integrate, quick, debug (when >1 task passes gates) | Task-level parallel dispatch |
331
+
332
+ ---
333
+
334
+ ## 9. Source
335
+
336
+ - Canonical command list: `commands/` directory
337
+ - Per-command details: `/gsd-t-help {command}` or [commands/gsd-t-help.md](../commands/gsd-t-help.md)
338
+ - Command summaries section in help file is the source of truth — this diagram is derived from it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "3.23.11",
3
+ "version": "3.25.10",
4
4
  "description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
5
5
  "author": "Tekyz, Inc.",
6
6
  "license": "MIT",
@@ -699,6 +699,184 @@ function handleParallelismReport(req, res, projectDir) {
699
699
  res.end(md);
700
700
  }
701
701
 
702
+ // M54 D1 T3 — Live Activity endpoints (additive, read-only)
703
+ // Contract: .gsd-t/contracts/live-activity-contract.md v1.0.0
704
+ // Three endpoints: GET /api/live-activity (5s cache),
705
+ // GET /api/live-activity/<id>/tail (per-id 5s cache, path-traversal guard),
706
+ // GET /api/live-activity/<id>/stream (SSE, uncached, 15s heartbeat).
707
+
708
+ const LIVE_ACTIVITY_CACHE_MS = 5000;
709
+ const _liveActivityCache = { list: { at: 0, body: null }, tail: new Map() };
710
+
711
+ function _loadLiveActivityReporter() {
712
+ try {
713
+ return require(path.join(__dirname, "..", "bin", "live-activity-report.cjs"));
714
+ } catch (err) {
715
+ return { _loadError: err && err.message || String(err) };
716
+ }
717
+ }
718
+
719
+ function isValidActivityId(id) {
720
+ if (typeof id !== "string") return false;
721
+ if (id.length === 0 || id.length > 256) return false;
722
+ if (id.indexOf("..") !== -1) return false;
723
+ if (id.indexOf("/") !== -1) return false;
724
+ if (id.indexOf("\\") !== -1) return false;
725
+ if (id.indexOf("\0") !== -1) return false;
726
+ return true;
727
+ }
728
+
729
+ function handleLiveActivity(req, res, projectDir) {
730
+ const now = Date.now();
731
+ const cache = _liveActivityCache.list;
732
+ if (cache.body && (now - cache.at) < LIVE_ACTIVITY_CACHE_MS) {
733
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Cache-Control": "no-store", "X-Cache": "hit" });
734
+ res.end(cache.body);
735
+ return;
736
+ }
737
+ const reporter = _loadLiveActivityReporter();
738
+ if (reporter._loadError) {
739
+ res.writeHead(500, { "Content-Type": "application/json" });
740
+ res.end(JSON.stringify({ error: "live-activity-report module unavailable", detail: reporter._loadError }));
741
+ return;
742
+ }
743
+ let result;
744
+ try {
745
+ result = reporter.computeLiveActivities({ projectDir });
746
+ } catch (err) {
747
+ // Contract regression — computeLiveActivities must never throw
748
+ res.writeHead(500, { "Content-Type": "application/json" });
749
+ res.end(JSON.stringify({ error: "computeLiveActivities threw", detail: err && err.message || String(err) }));
750
+ return;
751
+ }
752
+ const body = JSON.stringify(result);
753
+ cache.at = now;
754
+ cache.body = body;
755
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Cache-Control": "no-store", "X-Cache": "miss" });
756
+ res.end(body);
757
+ }
758
+
759
+ function handleLiveActivityTail(req, res, projectDir, id) {
760
+ if (!isValidActivityId(id)) {
761
+ res.writeHead(400, { "Content-Type": "application/json" });
762
+ res.end(JSON.stringify({ error: "invalid_id" }));
763
+ return;
764
+ }
765
+ const cacheKey = projectDir + "\0" + id;
766
+ const now = Date.now();
767
+ const cached = _liveActivityCache.tail.get(cacheKey);
768
+ if (cached && (now - cached.at) < LIVE_ACTIVITY_CACHE_MS) {
769
+ res.writeHead(cached.statusCode, cached.headers);
770
+ res.end(cached.body);
771
+ return;
772
+ }
773
+
774
+ // Find the activity to determine kind and tail path
775
+ const reporter = _loadLiveActivityReporter();
776
+ if (reporter._loadError) {
777
+ res.writeHead(500, { "Content-Type": "application/json" });
778
+ res.end(JSON.stringify({ error: "live-activity-report module unavailable" }));
779
+ return;
780
+ }
781
+ let result;
782
+ try {
783
+ result = reporter.computeLiveActivities({ projectDir });
784
+ } catch (err) {
785
+ res.writeHead(500, { "Content-Type": "application/json" });
786
+ res.end(JSON.stringify({ error: "computeLiveActivities threw", detail: err && err.message || String(err) }));
787
+ return;
788
+ }
789
+
790
+ const activity = result.activities.find((a) => a.id === id);
791
+ if (!activity) {
792
+ const notFound = { statusCode: 404, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }, body: JSON.stringify({ error: "not_found" }) };
793
+ _liveActivityCache.tail.set(cacheKey, Object.assign({ at: now }, notFound));
794
+ res.writeHead(404, notFound.headers);
795
+ res.end(notFound.body);
796
+ return;
797
+ }
798
+
799
+ // Read tail content by kind
800
+ let tailContent = "";
801
+ const kind = activity.kind;
802
+ if (kind === "bash" || kind === "spawn") {
803
+ // Last ~64 KB of process stdout — use _readFileTail pattern
804
+ // For bash: the log file path is not directly known without process inspection.
805
+ // Return a JSON snapshot of the activity as a fallback.
806
+ tailContent = JSON.stringify(activity, null, 2);
807
+ } else if (kind === "monitor") {
808
+ tailContent = JSON.stringify(activity, null, 2);
809
+ } else if (kind === "tool") {
810
+ tailContent = JSON.stringify(activity, null, 2);
811
+ } else {
812
+ tailContent = JSON.stringify(activity, null, 2);
813
+ }
814
+
815
+ const hit = { statusCode: 200, headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store", "Access-Control-Allow-Origin": "*" }, body: tailContent };
816
+ _liveActivityCache.tail.set(cacheKey, Object.assign({ at: now }, hit));
817
+ res.writeHead(200, hit.headers);
818
+ res.end(hit.body);
819
+ }
820
+
821
+ function handleLiveActivityStream(req, res, projectDir, id) {
822
+ if (!isValidActivityId(id)) {
823
+ res.writeHead(400, { "Content-Type": "application/json" });
824
+ res.end(JSON.stringify({ error: "invalid_id" }));
825
+ return;
826
+ }
827
+ res.writeHead(200, Object.assign({}, SSE_HEADERS, { "Access-Control-Allow-Origin": "*", "Cache-Control": "no-store" }));
828
+
829
+ // Send an initial data frame
830
+ res.write("data: " + JSON.stringify({ id, status: "stream_opened" }) + "\n\n");
831
+
832
+ // Heartbeat every 15s
833
+ const heartbeat = setInterval(() => {
834
+ if (!res.writableEnded) {
835
+ res.write(": heartbeat\n\n");
836
+ }
837
+ }, KEEPALIVE_MS);
838
+
839
+ // Poll for activity presence; close stream when activity disappears
840
+ const reporter = _loadLiveActivityReporter();
841
+ const poll = setInterval(() => {
842
+ if (res.writableEnded) {
843
+ clearInterval(poll);
844
+ clearInterval(heartbeat);
845
+ return;
846
+ }
847
+ if (reporter._loadError) {
848
+ clearInterval(poll);
849
+ clearInterval(heartbeat);
850
+ res.write("data: " + JSON.stringify({ id, status: "reporter_unavailable" }) + "\n\n");
851
+ res.end();
852
+ return;
853
+ }
854
+ let result;
855
+ try {
856
+ result = reporter.computeLiveActivities({ projectDir });
857
+ } catch (_) {
858
+ return; // silent — will retry next tick
859
+ }
860
+ const still = result.activities.find((a) => a.id === id);
861
+ if (!still) {
862
+ clearInterval(poll);
863
+ clearInterval(heartbeat);
864
+ if (!res.writableEnded) {
865
+ res.write("data: " + JSON.stringify({ id, status: "activity_ended" }) + "\n\n");
866
+ res.end();
867
+ }
868
+ return;
869
+ }
870
+ // Emit a status frame
871
+ res.write("data: " + JSON.stringify({ id, durationMs: still.durationMs, alive: still.alive }) + "\n\n");
872
+ }, 1000);
873
+
874
+ req.on("close", () => {
875
+ clearInterval(poll);
876
+ clearInterval(heartbeat);
877
+ });
878
+ }
879
+
702
880
  // POST /api/unattended-stop — proxies to the existing stop-sentinel flow so
703
881
  // the transcript panel's "Stop Supervisor" button reuses the canonical
704
882
  // kill path. Writes `.gsd-t/.unattended/stop` sentinel; supervisor polls
@@ -887,6 +1065,12 @@ function startServer(port, eventsDir, htmlPath, projectDir, transcriptHtmlPath,
887
1065
  if (url === "/api/parallelism/report") return handleParallelismReport(req, res, projDir);
888
1066
  // M44 D9 — stop-supervisor proxy (POST only; reuses existing sentinel flow)
889
1067
  if (url === "/api/unattended-stop") return handleUnattendedStop(req, res, projDir);
1068
+ // M54 D1 T3 — live-activity endpoints (additive, read-only)
1069
+ if (url === "/api/live-activity") return handleLiveActivity(req, res, projDir);
1070
+ const laTailMatch = url.match(/^\/api\/live-activity\/([^/]+)\/tail$/);
1071
+ if (laTailMatch) return handleLiveActivityTail(req, res, projDir, decodeURIComponent(laTailMatch[1]));
1072
+ const laStreamMatch = url.match(/^\/api\/live-activity\/([^/]+)\/stream$/);
1073
+ if (laStreamMatch) return handleLiveActivityStream(req, res, projDir, decodeURIComponent(laStreamMatch[1]));
890
1074
  // POST /transcript/:spawnId/kill — SIGTERM the recorded workerPid
891
1075
  const killMatch = url.match(/^\/transcript\/([^/]+)\/kill$/);
892
1076
  if (killMatch && req.method === "POST") return handleTranscriptKill(req, res, decodeURIComponent(killMatch[1]), projDir);
@@ -959,6 +1143,12 @@ module.exports = {
959
1143
  handleParallelism,
960
1144
  handleParallelismReport,
961
1145
  handleUnattendedStop,
1146
+ // M54 D1 T3 — live-activity observability
1147
+ handleLiveActivity,
1148
+ handleLiveActivityTail,
1149
+ handleLiveActivityStream,
1150
+ isValidActivityId,
1151
+ _liveActivityCache,
962
1152
  // M49 — idle-TTL exports for tests
963
1153
  _activityTracker,
964
1154
  _wrapSseHandler,