@tekyzinc/gsd-t 3.16.12 → 3.18.11

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 (52) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +13 -3
  3. package/bin/gsd-t-depgraph-validate.cjs +140 -0
  4. package/bin/gsd-t-economics.cjs +287 -0
  5. package/bin/gsd-t-file-disjointness.cjs +227 -0
  6. package/bin/gsd-t-in-session-usage.cjs +213 -0
  7. package/bin/gsd-t-orchestrator-config.cjs +100 -3
  8. package/bin/gsd-t-orchestrator.js +2 -1
  9. package/bin/gsd-t-parallel.cjs +382 -0
  10. package/bin/gsd-t-report-tokens.cjs +549 -0
  11. package/bin/gsd-t-task-graph.cjs +366 -0
  12. package/bin/gsd-t-token-capture.cjs +29 -14
  13. package/bin/gsd-t-token-dashboard.cjs +35 -0
  14. package/bin/gsd-t-tool-attribution.cjs +377 -0
  15. package/bin/gsd-t-tool-cost.cjs +195 -0
  16. package/bin/gsd-t-unattended-platform.cjs +7 -1
  17. package/bin/gsd-t-unattended.cjs +2 -0
  18. package/bin/gsd-t.js +155 -5
  19. package/bin/headless-auto-spawn.cjs +69 -49
  20. package/bin/headless-auto-spawn.js +18 -24
  21. package/bin/runway-estimator.cjs +212 -0
  22. package/bin/spawn-plan-derive.cjs +163 -0
  23. package/bin/spawn-plan-status-updater.cjs +292 -0
  24. package/bin/spawn-plan-writer.cjs +204 -0
  25. package/commands/gsd-t-debug.md +26 -7
  26. package/commands/gsd-t-execute.md +36 -28
  27. package/commands/gsd-t-help.md +11 -0
  28. package/commands/gsd-t-integrate.md +27 -7
  29. package/commands/gsd-t-quick.md +30 -13
  30. package/commands/gsd-t-scan.md +5 -5
  31. package/commands/gsd-t-unattended-watch.md +4 -3
  32. package/commands/gsd-t-unattended.md +9 -3
  33. package/commands/gsd-t-verify.md +5 -5
  34. package/commands/gsd-t-wave.md +21 -8
  35. package/commands/gsd.md +45 -3
  36. package/docs/GSD-T-README.md +43 -5
  37. package/docs/architecture.md +423 -3
  38. package/docs/requirements.md +203 -0
  39. package/package.json +1 -1
  40. package/scripts/gsd-t-calibration-hook.js +256 -0
  41. package/scripts/gsd-t-compact-detector.js +223 -0
  42. package/scripts/gsd-t-compaction-scanner.js +305 -0
  43. package/scripts/gsd-t-dashboard-autostart.cjs +172 -0
  44. package/scripts/gsd-t-dashboard-server.js +179 -0
  45. package/scripts/gsd-t-heartbeat.js +50 -2
  46. package/scripts/gsd-t-post-commit-spawn-plan.sh +86 -0
  47. package/scripts/gsd-t-transcript.html +546 -43
  48. package/scripts/hooks/gsd-t-in-session-usage-hook.js +84 -0
  49. package/scripts/spawn-plan-fmt-tokens.cjs +80 -0
  50. package/templates/CLAUDE-global.md +8 -3
  51. package/templates/CLAUDE-project.md +17 -14
  52. package/templates/hooks/post-commit-spawn-plan.sh +85 -0
@@ -70,6 +70,14 @@ The framework has no runtime — it is consumed entirely by Claude Code's slash
70
70
  - **`scripts/gsd-t-dashboard.html`** (194 lines): React 17 + React Flow v11.11.4 + Dagre via CDN (no build step, no npm deps). Dark theme (`#0d1117`). Renders agent hierarchy as directed graph from `parent_agent_id` relationships. Live event feed (max 200, outcome color-coded). Auto-reconnects on SSE disconnect. Port configurable via `?port=` URL param.
71
71
  - **`commands/gsd-t-visualize.md`** (104 lines, 48th command): Starts server via `--detach`, polls `/ping` up to 5s, opens browser cross-platform (win32/darwin/linux). Accepts `stop` argument to shut down server. Step 0 self-spawn with OBSERVABILITY LOGGING.
72
72
 
73
+ ### Transcript Viewer as Primary Surface (M43 D6 — complete v3.16.13)
74
+ - **Dashboard server additions** (`scripts/gsd-t-dashboard-server.js`): two new HTTP routes for the per-spawn viewer. `GET /transcript/:id/usage` → `{spawn_id, rows, truncated}` filtered from `.gsd-t/metrics/token-usage.jsonl` by `row.spawn_id === id` OR (no `spawn_id` column + `row.session_id === id` — the session-id branch covers M43 D1 Branch B in-session rows). `GET /transcript/:id/tool-cost` → proxies to `bin/gsd-t-tool-attribution.cjs::aggregateByTool` (M43 D2); returns 503 `{error: "tool-attribution library not yet available"}` when D2 isn't on disk so D6 could ship before D2 in Wave 2 without crashing callers.
75
+ - **Transcript viewer panel** (`scripts/gsd-t-transcript.html`): collapsible "Tool Cost" sidebar panel that fetches `/transcript/:id/tool-cost` on viewer load and debounces a 2s refresh on each SSE `turn_complete` / `result` frame. Renders top-N tools sorted by attributed tokens with name, call count, tokens, and USD cost. Live badge green while SSE is open, muted otherwise. 503 → friendly "tool attribution not yet wired" row. `window.__gsdtRenderToolCostPanel` exposed for DOM tests.
76
+ - **URL banner** (`bin/headless-auto-spawn.cjs`): every detached spawn prints `▶ Live transcript: http://127.0.0.1:{port}/transcript/{spawn-id}` on stdout. Port sourced from `ensureDashboardRunning().port` with `projectScopedDefaultPort(projectDir)` fallback. Best-effort — banner failure never crashes the spawn.
77
+ - **Dashboard autostart** (`scripts/gsd-t-dashboard-autostart.cjs`, ~160 lines, zero deps): `ensureDashboardRunning({projectDir, port?})` probes the port synchronously via a short-lived subprocess (`_isPortBusySync` issues `net.createServer().listen(port)` host-less — matches the server's IPv6-wildcard bind on macOS dual-stack; specifying `127.0.0.1` would falsely report free). If free, fork-detaches the server with `spawn(…, {detached:true, stdio:'ignore'})` + `child.unref()` + writes `.gsd-t/.dashboard.pid` (hyphen → dot distinguishes this lifecycle from M38's `.gsd-t/dashboard.pid`). Idempotent on repeated invocation. Called at the top of `autoSpawnHeadless` so the banner printed immediately after resolves to a live listener.
78
+ - **Contract**: `.gsd-t/contracts/dashboard-server-contract.md` v1.2.0 — new §HTTP Endpoints entries, §Banner Format, §Autostart sections.
79
+ - **Tests**: `test/m43-dashboard-tool-cost-route.test.js` (9), `test/m43-transcript-panel.test.js` (12), `test/m43-dashboard-autostart.test.js` (6), `test/m43-url-banner.test.js` (3).
80
+
73
81
  ### Headless Mode (M23 — complete)
74
82
  - **doHeadless(args)**: Dispatch function for the `headless` CLI subcommand.
75
83
  - **doHeadlessExec(command, cmdArgs, flags)**: Wraps `claude -p "/gsd-t-{command}"` via `execFileSync`. Verifies claude CLI availability, enforces timeout, writes log file if `--log` requested. Returns structured JSON if `--json` flag set. (M36 Phase 0: prompt form is `/gsd-t-X`, NOT `/gsd-t-X` — non-interactive mode rejects the `/` namespace prefix.)
@@ -433,22 +441,44 @@ Alert thresholds (inline display):
433
441
 
434
442
  `gsd-t-status` displays token breakdown by domain/task/phase. `gsd-t-visualize` consumes the same data for dashboard rendering.
435
443
 
436
- **Token Pipeline (M40 → M41 → M43 D3)**
444
+ **Token Pipeline (M40 → M41 → M43 D3 → M43 D2)**
437
445
 
438
446
  Canonical store: `.gsd-t/metrics/token-usage.jsonl` (append-only JSONL; schema in `.gsd-t/contracts/metrics-schema-contract.md` — v1 M40, v2 M43 additive).
439
447
 
440
448
  ```
441
449
  producers ─┬─► .gsd-t/metrics/token-usage.jsonl ─┬─► gsd-t tokens (dashboard)
442
450
  │ ├─► gsd-t tokens --regenerate-log (→ token-log.md)
443
- └─► M43 D2 tool-attribution (planned)
451
+ ├─► gsd-t tokens --show-tool-costs (adds D2 section)
452
+ │ └─► gsd-t tool-cost (M43 D2)
444
453
  ├── scripts/gsd-t-token-aggregator.js (M40 worker stream-json)
445
454
  ├── bin/gsd-t-token-capture.cjs (M41 recordSpawnRow / captureSpawn)
446
455
  ├── bin/gsd-t-token-backfill.cjs (M41 D3 historical recovery)
447
- └── M43 D1 in-session capture (hook or tee — branch pending)
456
+ └── bin/gsd-t-in-session-usage.cjs (M43 D1 Branch B: Stop-hook trigger + transcript-sourced)
448
457
  ```
449
458
 
450
459
  Under v2, `.gsd-t/token-log.md` is a **regenerated view** (`gsd-t tokens --regenerate-log`), not hand-maintained. Wrapper still appends in real time for live visibility; regeneration is an explicit operator step that requires the JSONL to be fully backfilled first. Regeneration is idempotent and deterministic (sort order: `startedAt` asc → `session_id` asc → `turn_id` asc, numeric when both turn IDs parse).
451
460
 
461
+ **Per-Tool Attribution (M43 D2)**
462
+
463
+ `bin/gsd-t-tool-attribution.cjs` + `bin/gsd-t-tool-cost.cjs` + `gsd-t tool-cost` CLI join per-turn usage rows with tool-call events and attribute each turn's tokens across the tools called in that turn. Contract: `.gsd-t/contracts/tool-attribution-contract.md` v1.0.0 defines the output-byte ratio algorithm with four tie-breakers (zero-byte turn → equal split, missing tool_result → zero weight flagged, no tool calls → no-tool bucket, null turn tokens → skipped).
464
+
465
+ ```
466
+ .gsd-t/metrics/token-usage.jsonl ─┐
467
+ ├─► joinTurnsAndEvents (session_id + ts window)
468
+ .gsd-t/events/YYYY-MM-DD.jsonl ───┘ │
469
+
470
+ attributeTurn → attributions[]
471
+
472
+
473
+ aggregateByTool / aggregateByCommand / aggregateByDomain
474
+
475
+
476
+ gsd-t tool-cost CLI
477
+ (table / json, --group-by, --since, --milestone)
478
+ ```
479
+
480
+ Zero-dep, sync filesystem I/O. Perf gate: 3k turns × 30k events join+aggregate in <3s (measured ~30ms on a dev laptop). The current event schema does not carry `tool_result` bytes, so the zero-byte tie-breaker fires and tools-in-turn split equally. A future event-schema extension that records bytes will activate the ratio with no library change.
481
+
452
482
  ### GSD 2 Tier 3 — Quality Culture & Design (M32 — complete v2.53.10)
453
483
 
454
484
  Three enhancements for project-level quality identity and design consistency.
@@ -558,6 +588,396 @@ Orchestrator Context Gate — v3.0.0 semantics:
558
588
  - `status` — displays `Context: {pct}% of {window} tokens ({band}) — last check {rel}` line
559
589
  - `update-all` — one-shot task-counter retirement migration (deletes legacy files, writes `.gsd-t/.task-counter-retired-v1` marker)
560
590
 
591
+ ## Task-Graph Reader (M44 D1, v3.18.10+)
592
+
593
+ `bin/gsd-t-task-graph.cjs` — a zero-external-dep CommonJS module that parses every `.gsd-t/domains/*/tasks.md` (and falls back to each domain's `scope.md` `## Files Owned` for missing touch lists) into a typed in-memory DAG. This is the **single shared input** for every M44 parallelism domain — D2 `gsd-t parallel`, D3 command-file integration, D4 dep-graph validation, D5 file-disjointness prover, D6 pre-spawn economics — none of which re-parse `tasks.md` themselves.
594
+
595
+ ```
596
+ .gsd-t/domains/<domain>/tasks.md ──┐
597
+ .gsd-t/domains/<domain>/scope.md ──┴──> buildTaskGraph({projectDir})
598
+
599
+
600
+ {nodes, edges, ready, byId, warnings}
601
+
602
+ ┌──────────────────────────────────────┼──────────────────────────────────────┐
603
+ ▼ ▼ ▼
604
+ D2 gsd-t parallel D4 dep-validate (veto) D5 disjoint-prove (veto)
605
+ D3 command-file integ. D6 pre-spawn economics (touches[] overlap check)
606
+ ```
607
+
608
+ **Public API** (`require('./bin/gsd-t-task-graph.cjs')`):
609
+ - `buildTaskGraph({projectDir}) → { nodes, edges, ready, byId, warnings }` — synchronous
610
+ - `getReadyTasks(graph) → TaskNode[]` — node objects whose deps are all DONE
611
+ - `TaskGraphCycleError` — thrown on circular dependency, carries `.cycle: string[]`
612
+
613
+ **Hard rules** (from `task-graph-contract.md` v1.0.0):
614
+ - Zero external deps — Node built-ins only
615
+ - Cycle detection mandatory (iterative three-color DFS) — never produces partial graph on cycle
616
+ - Read-only — never writes to `tasks.md`, `scope.md`, or contracts
617
+ - Mode-agnostic — knows nothing about in-session vs unattended; pure graph emitter
618
+ - Performance budget < 200 ms for 100-domain / 1000-task project (measured 6 ms / 250 tasks)
619
+
620
+ **CLI surface** (`bin/gsd-t.js doGraph` → `doGraphTaskOutput`):
621
+ - `gsd-t graph --output json` — pretty-printed DAG to stdout
622
+ - `gsd-t graph --output table` — column-aligned id/domain/wave/status/ready/deps table
623
+ - `gsd-t graph tasks [json|table]` — explicit subcommand form
624
+ - Pre-existing `graph index|status|query` (codebase entity graph via `graph-indexer`) unchanged
625
+
626
+ **Contract**: `.gsd-t/contracts/task-graph-contract.md` v1.0.0
627
+
628
+ ## Dep-Graph Validation (M44 D4, v3.18.10+)
629
+
630
+ `bin/gsd-t-depgraph-validate.cjs` — a zero-external-dep CommonJS module that runs as the **first pre-spawn gate** between D1's DAG emitter and the parallel dispatcher. Given `graph.ready` (the DAG's candidate set), it filters down to tasks whose declared `deps[]` are all in DONE status, emits one `dep_gate_veto` event per task it removes, and returns the reduced ready set plus the veto list. The caller (D2) decides whether to spawn the smaller batch or fall back to sequential — D4 itself is a pure filter with no spawn authority.
631
+
632
+ ```
633
+ D1 graph (graph.ready, graph.byId)
634
+
635
+
636
+ D4 validateDepGraph ← this module (filter-only; never throws on unmet deps)
637
+ │ │
638
+ ready[] (OK) vetoed[] ──────> .gsd-t/events/YYYY-MM-DD.jsonl (one dep_gate_veto per task)
639
+
640
+
641
+ D5 disjointness check ← next pre-spawn gate
642
+
643
+
644
+ D6 economics ← final pre-spawn gate
645
+
646
+
647
+ D2 parallel dispatch
648
+ ```
649
+
650
+ **Public API** (`require('./bin/gsd-t-depgraph-validate.cjs')`):
651
+ - `validateDepGraph({graph, projectDir}) → { ready: TaskNode[], vetoed: {task, unmet_deps[]}[] }` — synchronous
652
+
653
+ **Veto rule** (locked in `depgraph-validation-contract.md` §3): a dep is satisfied iff `graph.byId[depId]` exists AND `status === 'done'`. Pending / skipped / failed / unknown all veto the dependent. Every vetoed task emits exactly one `dep_gate_veto` JSONL record on the event stream.
654
+
655
+ **Event payload** (`dep_gate_veto`): base `event-schema-contract.md` fields (ts ISO 8601, event_type, command/phase/agent_id/parent_agent_id/trace_id/model set to null when D4 doesn't own them, reasoning="unmet deps: …", outcome="deferred") PLUS additive `task_id`, `domain`, `unmet_deps[]`.
656
+
657
+ **Hard rules** (from `depgraph-validation-contract.md` v1.0.0):
658
+ - Zero external deps — Node built-ins only
659
+ - Never throws on unmet deps, unknown dep ids, or event-log I/O failure (only throws on malformed `opts`)
660
+ - Read-only on `tasks.md` / `scope.md` / contracts — only write surface is appending JSONL lines (events dir created on demand)
661
+ - Synchronous; < 50 ms on realistic 100-domain / 1000-task graphs
662
+ - Mode-agnostic — same call shape in [in-session] and [unattended]; what to do with the reduced set is D2's call
663
+
664
+ **Contract**: `.gsd-t/contracts/depgraph-validation-contract.md` v1.0.0
665
+
666
+ ## File-Disjointness Prover (M44 D5, v3.18.10+)
667
+
668
+ `bin/gsd-t-file-disjointness.cjs` — the pre-spawn gate that, given a candidate parallel set of task nodes from D1's DAG, partitions them into `parallel` / `sequential` / `unprovable` groups based on declared write-target overlap. Mode-agnostic: same function used by the in-session D2 parallel CLI and the unattended D6 economics path.
669
+
670
+ ```
671
+ D1 task-graph nodes (touches[]) safe-default
672
+ │ (unprovable → sequential)
673
+ ▼ ▲
674
+ proveDisjointness({tasks, projectDir}) ────────────────┤
675
+ │ │
676
+ ├─→ resolveTouches() (declared → git-history → none)
677
+ ├─→ groupByOverlap() (union-find on touches[])
678
+
679
+
680
+ { parallel: TaskNode[][], (singletons only in v1.0.0 — no multi-task "parallel clusters")
681
+ sequential: TaskNode[][], (overlap groups + unprovable singletons)
682
+ unprovable: TaskNode[] }
683
+
684
+
685
+ .gsd-t/events/YYYY-MM-DD.jsonl
686
+ { type: "disjointness_fallback", task_id, reason, ts }
687
+ reason ∈ { "unprovable", "write-target-overlap" }
688
+ ```
689
+
690
+ **Public API** (`require('./bin/gsd-t-file-disjointness.cjs')`):
691
+ - `proveDisjointness({tasks, projectDir}) → { parallel, sequential, unprovable }` — synchronous, never throws
692
+
693
+ **Hard rules** (from `file-disjointness-contract.md` v1.0.0):
694
+ - Unprovable is ALWAYS sequential — never assume disjointness
695
+ - Zero external runtime deps; git invoked via `child_process.execSync` in a try/catch
696
+ - Read-only on all domain artifacts; only write surface is the event JSONL append
697
+ - Checks WRITE targets only — read-only file access is never a conflict
698
+ - Git-history fallback bounded to 100 commits (`git log -n 100`)
699
+ - Mode-agnostic — downstream (D2 / D6) decides what to do with sequential + unprovable groups
700
+
701
+ **Contract**: `.gsd-t/contracts/file-disjointness-contract.md` v1.0.0
702
+
703
+ ## Per-CW Attribution (M44 D7, v3.18.10+)
704
+
705
+ Per-Context-Window (CW) attribution lets the optimization report, the per-CW rollup in `gsd-t metrics`, and D6's pre-spawn estimator distinguish multiple CWs within one iter — necessary because Claude Code can compact mid-run, silently splitting one iter into two CWs that pre-D7 metrics treated as a single unit.
706
+
707
+ ```
708
+ spawn site (orchestrator | supervisor | in-session driver)
709
+ │ supplies cw_id (= spawn_id for unattended; = session_id+":"+compaction_index for in-session)
710
+
711
+ bin/gsd-t-token-capture.cjs::recordSpawnRow / captureSpawn
712
+ │ pass-through; serializes cw_id into the JSONL row when supplied
713
+
714
+ .gsd-t/metrics/token-usage.jsonl (schema v2.1.0 — cw_id optional)
715
+
716
+ ▼ consumers (D6 calibration, gsd-t metrics rollup, optimization report)
717
+
718
+ Claude Code SessionStart (source=compact)
719
+ ├──> scripts/gsd-t-compact-detector.js (v1.0.0 — boundary row, unchanged)
720
+ └──> scripts/gsd-t-calibration-hook.js (v1.1.0 — calibration row)
721
+ │ correlates with active spawn from .gsd-t/.unattended/state.json
722
+ │ derives actualCwPct from payload.input_tokens ÷ CW ceiling
723
+
724
+ .gsd-t/metrics/compactions.jsonl
725
+ ({type: "compaction_post_spawn", cw_id, task_id, spawn_id,
726
+ estimatedCwPct, actualCwPct, ts, schemaVersion: 1})
727
+ ```
728
+
729
+ **`bin/gsd-t-token-capture.cjs` extension** — additive optional `cw_id` opt on `recordSpawnRow` and `captureSpawn`. The wrapper does not derive `cw_id`; it only forwards what the caller supplies. When absent (undefined / null / "") the field is omitted from the JSONL row entirely (NOT serialized as `null` or `""`). Pre-D7 callers produce byte-identical output. The serialization branch follows the existing `session_id` / `turn_id` pattern (`if (cw_id != null && cw_id !== '') rec.cw_id = String(cw_id);`).
730
+
731
+ **`scripts/gsd-t-calibration-hook.js`** — zero-external-dep SessionStart hook handler that complements (does not replace) `scripts/gsd-t-compact-detector.js`. Both hooks fire on the same payload; both write to `.gsd-t/metrics/compactions.jsonl`; neither reads or writes the other's rows. The calibration hook silently no-ops when:
732
+ - `payload.source !== "compact"`
733
+ - `<cwd>/.gsd-t/` does not exist (off-switch)
734
+ - `.gsd-t/.unattended/state.json` is missing / unparseable / `status !== "running"`
735
+ - No `spawn_id` derivable from state
736
+ - No `input_tokens` derivable from the compaction payload
737
+
738
+ The hook ALWAYS exits 0 — throwing breaks Claude Code SessionStart. It includes a 1 MiB stdin cap, a path-traversal guard on the output sink, and a non-absolute-cwd guard mirroring the detector. Hard rules (from `compaction-events-contract.md` v1.1.0) specify the `compaction_post_spawn` row schema, the calibration sink coexistence rules, and the CW-ceiling override (`state.cwCeilingTokens` → defaults to `200000` input tokens).
739
+
740
+ **Contracts**: `.gsd-t/contracts/metrics-schema-contract.md` v2.1.0 (`cw_id` field), `.gsd-t/contracts/compaction-events-contract.md` v1.1.0 (calibration event)
741
+
742
+ **Tests**: `test/m44-cw-attribution.test.js` (19 tests covering pass-through with/without `cw_id`, calibration hook with/without active spawn, malformed state, non-compact sources, derivation fallback to `compactMetadata.preTokens`, ceiling overrides, coexistence with v1.0.0 detector rows, and zero-deps verification).
743
+
744
+ ## Pre-Spawn Economics Estimator (M44 D6, v3.18.10+)
745
+
746
+ `bin/gsd-t-economics.cjs` — the pre-spawn gate component that predicts each candidate task's Context-Window (CW) footprint and feeds the D2 parallel-CLI gating math with a per-task recommendation. Zero external deps; loaded once per `projectDir` (sync cached corpus read); never returns `undefined` for numeric fields (global-median fallback is mandatory).
747
+
748
+ **Core function**: `estimateTaskFootprint({taskNode, mode, projectDir})` → `{estimatedCwPct, parallelOk, split, workerCount, matchedRows, confidence}`.
749
+
750
+ **Three-tier lookup** over the in-memory corpus index:
751
+
752
+ 1. **Exact** `command|step|domain` triplet — HIGH (≥5 rows) or MEDIUM (1–4 rows).
753
+ 2. **Fuzzy** — domain-only match, then command-only match → LOW confidence.
754
+ 3. **Global median** — `median(totals)` across every row → FALLBACK confidence. Mandatory floor; guarantees no undefined result.
755
+
756
+ ```
757
+ D1 task graph ┌──────────────────────────────┐
758
+ ┌─ taskNode{cmd,step,dom} ──┤ bin/gsd-t-economics.cjs │
759
+ │ │ estimateTaskFootprint │
760
+ │ │ - corpus cache (per projDir)│
761
+ │ │ - triplet lookup │
762
+ │ │ - fuzzy fallback │
763
+ │ │ - global median fallback │
764
+ │ │ - mode-specific gate math │
765
+ │ └───┬──────────────────────────┘
766
+ │ │ {estimatedCwPct,
767
+ │ │ parallelOk, split,
768
+ │ │ workerCount, matchedRows,
769
+ │ │ confidence}
770
+ │ ↓
771
+ │ D2 gating math (owns final decision)
772
+ │ │
773
+ │ └──→ appends economics_decision
774
+ │ event to .gsd-t/events/*.jsonl
775
+
776
+ └─ corpus sources: .gsd-t/metrics/token-usage.jsonl (v2.1.0)
777
+ .gsd-t/metrics/compactions.jsonl (v1.1.0; calibration signal)
778
+ ```
779
+
780
+ **Mode awareness**: `estimatedCwPct` is mode-agnostic (same tokens → same %). Gates differ:
781
+
782
+ | Mode | `parallelOk` threshold | `split` threshold | Rationale |
783
+ |-------------|------------------------|-------------------|-----------|
784
+ | in-session | `≤ 85%` | always `false` | 85 % matches orchestrator-CW headroom check (D2); over-limit tasks still run with fewer workers. |
785
+ | unattended | `≤ 60%` | `> 60%` → `true` | Per-worker CW gate; heavy tasks are sliced into multiple `claude -p` iters by the caller (D6 recommends, does not slice). |
786
+
787
+ **CW ceiling**: `200_000` input tokens — matches `bin/token-budget.cjs`, `bin/context-meter-config.cjs`, and `bin/runway-estimator.cjs`. Row totals = `inputTokens + outputTokens + cacheReadInputTokens + cacheCreationInputTokens` (a deliberately conservative upper bound; cache-read tokens are ~free in real CW accounting and inflate the estimate).
788
+
789
+ **Calibration** (2026-04-22): run against the live 528-row `token-usage.jsonl` corpus. Per-tier MAE (% of CW ceiling): HIGH 12.89 %, MEDIUM 0.00 % (small-n tautology — 4 keys with 1–4 rows each), LOW 13.08 %, FALLBACK 15.06 %. Current corpus is skewed (523 rows on one triplet); `gsd-t-execute` and `gsd-t-wave` have no corpus signal yet and resolve to FALLBACK. Coverage grows as D7 `cw_id`-tagged rows accumulate from real-world spawns — the estimator's signal improves without code changes.
790
+
791
+ **Hard invariants** (from `.gsd-t/contracts/economics-estimator-contract.md` v1.0.0):
792
+
793
+ 1. D6 is a HINT, not a veto — D2 retains gate authority.
794
+ 2. Corpus loaded ONCE per `projectDir` (cache via `Map`; `_resetCorpusCache()` exposed for tests).
795
+ 3. Never returns `undefined` for numeric fields — global-median fallback is mandatory.
796
+ 4. Mode-aware for gates; mode-agnostic for `estimatedCwPct`.
797
+ 5. Event emission is best-effort; FS failures never fail the estimate.
798
+ 6. Zero external npm runtime deps (Node built-ins only).
799
+
800
+ **Contract**: `.gsd-t/contracts/economics-estimator-contract.md` v1.0.0.
801
+
802
+ **Tests**: `test/m44-economics.test.js` (9 tests covering HIGH/MEDIUM/LOW/FALLBACK tiers, both mode thresholds under and over the boundary, economics_decision event shape, corpus cache identity, and empty-corpus behaviour).
803
+
804
+ ## Parallel CLI (M44 D2, v3.18.10+)
805
+
806
+ `bin/gsd-t-parallel.cjs` — the `gsd-t parallel` subcommand dispatch. Wraps the M40 orchestrator with task-level (not just domain-level) parallelism and layers in mode-aware gating math. Extends, does not replace, `bin/gsd-t-orchestrator.js`; zero external runtime deps.
807
+
808
+ **Surface**:
809
+ - Module: `runParallel({projectDir, mode, milestone, domain, dryRun})` and `runCli(argv, env)`.
810
+ - CLI: `gsd-t parallel [--mode in-session|unattended] [--milestone Mxx] [--domain <name>] [--dry-run] [--help]`.
811
+ - Config extension in `bin/gsd-t-orchestrator-config.cjs`: `computeInSessionHeadroom`, `computeUnattendedGate`, and the exported constants `IN_SESSION_CW_CEILING_PCT=85`, `UNATTENDED_PER_WORKER_CW_PCT=60`, `DEFAULT_SUMMARY_SIZE_PCT=4`.
812
+
813
+ **Dispatch flow**:
814
+
815
+ ```
816
+ gsd-t parallel --mode … [--dry-run]
817
+
818
+ ├──► D1 buildTaskGraph(projectDir) ─────────────┐
819
+ │ (bin/gsd-t-task-graph.cjs) │
820
+ │ │ ready tasks
821
+ ├──► D4 validateDepGraph(graph) ◄───────────────┘
822
+ │ (bin/gsd-t-depgraph-validate.cjs)
823
+ │ → emits gate_veto on unmet deps
824
+
825
+ ├──► D5 proveDisjointness(readyTasks)
826
+ │ (bin/gsd-t-file-disjointness.cjs)
827
+ │ → emits gate_veto on overlap / unprovable
828
+
829
+ ├──► D6 estimateTaskFootprint(task, mode) × N
830
+ │ (bin/gsd-t-economics.cjs)
831
+ │ → per-task {estimatedCwPct, parallelOk, split, confidence}
832
+
833
+ ├──► Mode-aware gating math (config.cjs)
834
+ │ ├─ in-session: computeInSessionHeadroom
835
+ │ │ ctxPct from token-budget.getSessionStatus()
836
+ │ │ → reducedCount; emits parallelism_reduced on N < requested
837
+ │ └─ unattended: computeUnattendedGate(estimatedCwPct)
838
+ │ → emits task_split on > 60%
839
+
840
+ └──► plan{ workerCount, parallelTasks, plan[] }
841
+ → dry-run renders the 6-col table + mode/totals
842
+ → non-dry-run: hands off to M40 orchestrator machinery
843
+ ```
844
+
845
+ **Mode contracts** (from `.gsd-t/contracts/wave-join-contract.md` v1.1.0):
846
+
847
+ | Mode | Threshold | Math | On exceed |
848
+ |------|-----------|------|-----------|
849
+ | in-session | 85% orchestrator-CW | `ctxPct + N × summarySize ≤ 85` | Reduce N; floor=1 (never refuses) |
850
+ | unattended | 60% per-worker CW | `estimatedCwPct ≤ 60` | `split=true`; caller slices |
851
+
852
+ **Integration with the M40 path**: `runParallel` produces a plan object; it does **not** replace `bin/gsd-t-orchestrator.js`. The existing orchestrator machinery owns actual worker spawn, retry policy, wave barriers, merging, and the state-file lifecycle. D2 is the pre-spawn planning layer.
853
+
854
+ **Event schemas** (all written best-effort to `.gsd-t/events/YYYY-MM-DD.jsonl`; failures never break control flow):
855
+ - `gate_veto{type, task_id, gate, reason, ts}` — D4 or D5 rejection.
856
+ - `parallelism_reduced{type, original_count, reduced_count, reason:'in_session_headroom', ts}` — in-session headroom forced a smaller N.
857
+ - `task_split{type, task_id, estimatedCwPct, ts}` — unattended over-threshold.
858
+
859
+ **Hard invariants** (from m44-d2 constraints.md):
860
+ 1. Zero external npm runtime deps.
861
+ 2. `bin/gsd-t-orchestrator.js` is never replaced; only extended via the config module.
862
+ 3. All three pre-existing invariants (disjointness, auto-merge, economics) apply to BOTH modes. No mode flag bypasses any gate.
863
+ 4. In-session mode NEVER throws pause/resume; N=1 is the irreducible floor.
864
+ 5. Adaptive `maxParallel` (`computeAdaptiveMaxParallel`) is untouched — D2 layers on top, not under.
865
+
866
+ **Contract**: `.gsd-t/contracts/wave-join-contract.md` v1.1.0.
867
+
868
+ **Tests**: `test/m44-parallel-cli.test.js` (21 tests covering headroom ok/reduced/floor, unattended gate ok/split/boundary, plan-table format, runParallel in-session fan-out, disjointness gate-veto fallback, mode auto-detect, explicit-mode override, headroom reduction end-to-end with a 5-domain fixture at 70% ctxPct, and CLI arg parsing).
869
+
870
+ ## Command-File Integration (M44 D3, v3.18.10+)
871
+
872
+ Wires the five primary GSD-T command files — `commands/gsd-t-execute.md`, `gsd-t-wave.md`, `gsd-t-integrate.md`, `gsd-t-quick.md`, `gsd-t-debug.md` — to the D2 `gsd-t parallel` CLI so task-level parallel dispatch becomes available from the standard workflow. Purely additive doc-ripple + conditional integration blocks; no new library code, no new spawns. Every existing sequential code path remains intact.
873
+
874
+ **Dispatch decision flow**:
875
+
876
+ ```
877
+ command file bin/gsd-t-parallel.cjs bin/gsd-t-orchestrator.js
878
+ ──────────────── ──────────────────── ────────────────────────
879
+ execute ┐
880
+ wave │ "Optional — Parallel ┌─ D4 depgraph-validate ─┐
881
+ integrate ├─► Dispatch (M44)" block ───► runParallel │ (veto on unmet deps) │
882
+ quick │ (conditional: >1 pending (D2) ├─ D5 disjointness ──────┤──► validated plan
883
+ debug ┘ task + D4/D5/D6 gates │ (veto on overlap) │ ────► M40 orchestrator
884
+ pass). Mode auto-detected ├─ D6 economics (hint) │ (spawns workers,
885
+ from GSD_T_UNATTENDED. └─ mode-aware final │ retries, wave
886
+ Single-task or veto → gate (85% / 60%) │ barriers, state
887
+ silent sequential fallback. │ lifecycle)
888
+ emits gate_veto,
889
+ parallelism_reduced,
890
+ task_split events
891
+ (best-effort JSONL)
892
+ ```
893
+
894
+ **Per-file integration points**:
895
+
896
+ | Command file | Step | Trigger | Behavior when no trigger |
897
+ |-------------|------|---------|-------------------------|
898
+ | `gsd-t-execute.md` | Step 3 (Choose Execution Mode) | >1 pending task + gates pass | Existing Wave Scheduling + Solo/Team Mode sequential path |
899
+ | `gsd-t-wave.md` | Step 3 EXECUTE phase (#5) | Inherited from execute agent | Wave orchestrator does not configure mode |
900
+ | `gsd-t-integrate.md` | Step 3 (Wire Integration Points) | Integrating >1 domain + gates pass | Sequential task-level dispatch |
901
+ | `gsd-t-quick.md` | Step 3 (Execute) | >1 pending task + gates pass (uncommon for quick) | Sequential single-subagent (the common case) |
902
+ | `gsd-t-debug.md` | Step 3 (Debug Solo or Team) | Multi-domain contract-boundary/gap debug | Sequential Solo Mode / Team Mode |
903
+
904
+ **Invariants** (enforced by D2; documented in every D3 integration block):
905
+ 1. **Mode auto-detection** — mode comes from `GSD_T_UNATTENDED=1`; no command file hardcodes `--mode`.
906
+ 2. **Silent fallback** — gate vetoes, unprovable disjointness, and single-task scope ALL drop to sequential without a user prompt.
907
+ 3. **No opt-out flag** — no `--in-session`/`--headless` flag exists (consistent with M43 D4).
908
+ 4. **In-session: never interrupt** — headroom pressure reduces worker count to N=1 floor; never pauses.
909
+ 5. **Unattended: zero-compaction** — D2 enforces by splitting when D6 estimates > 60% per-worker CW.
910
+ 6. **Observability** — D2 owns spawn observability; D3 adds zero new spawns.
911
+
912
+ **Contract**: `.gsd-t/contracts/wave-join-contract.md` v1.1.0 (surface referenced by every integration block).
913
+
914
+ ## Spawn Plan Visibility (M44 D8, v3.18.10+)
915
+
916
+ Right-side two-layer task panel in the dashboard / transcript visualizer.
917
+ Answers exactly one question: *"Of the tasks that were supposed to happen
918
+ in this spawn, which are done, which are in flight, which are pending?"*
919
+ Observability-only; zero added LLM token cost.
920
+
921
+ **Writers (3 chokepoints; all wrapped in try/catch — plan-write failure NEVER blocks the spawn)**:
922
+ - `bin/gsd-t-token-capture.cjs` — `captureSpawn()` calls `writeSpawnPlan`
923
+ before `await spawnFn()` and `markSpawnEnded` in both success/error paths.
924
+ - `bin/headless-auto-spawn.cjs` — `autoSpawnHeadless()` calls
925
+ `writeSpawnPlan` before the detached child launches; `markSessionCompleted`
926
+ now also closes the plan.
927
+ - `commands/gsd-t-resume.md` Step 0 — under `GSD_T_UNATTENDED_WORKER=1`,
928
+ every worker iteration writes a plan at iteration start.
929
+
930
+ **Derivation**: `bin/spawn-plan-derive.cjs` reads `.gsd-t/partition.md` +
931
+ `.gsd-t/domains/*/tasks.md` and emits `{milestone, wave, domains, tasks}`.
932
+ Deterministic projection — no LLM calls, no prompts. Silent-fails to an
933
+ empty slice when partition is absent.
934
+
935
+ **Status updater**: `bin/spawn-plan-status-updater.cjs` — atomic JSON
936
+ patches:
937
+ - `markTaskDone({spawnId, taskId, commit, tokens?, projectDir})`
938
+ - `markSpawnEnded({spawnId, endedReason, projectDir})`
939
+ - `sumTokensForTask(...)` — parses `.gsd-t/token-log.md` rows where
940
+ `Task` column matches the id AND `Datetime-start >= spawn.startedAt`.
941
+
942
+ **Post-commit hook**: `scripts/gsd-t-post-commit-spawn-plan.sh` (+ template
943
+ at `templates/hooks/post-commit-spawn-plan.sh`). Greps commit message for
944
+ all `[M\d+-D\d+-T\d+]` ids; for each active plan, calls `markTaskDone`
945
+ with token attribution. Silent-fail: always `exit 0`.
946
+
947
+ **Reader surface**:
948
+ - `GET /api/spawn-plans` — active plans (where `endedAt === null`),
949
+ newest-first.
950
+ - SSE channel `/api/spawn-plans/stream` — `fs.watch` on
951
+ `.gsd-t/spawns/*.json` emits `{spawnId, plan}` on every change.
952
+ - `scripts/gsd-t-transcript.html` — right-side `<aside class="spawn-panel">`
953
+ with Layer 1 (project) + Layer 2 (active spawn). Status icons
954
+ `☐` pending / `◐` in_progress / `✓` done. Token cells render as
955
+ `in=12.5k out=1.7k $0.42` (k-suffix above 1000, 2-decimal USD); `—` when
956
+ attribution returned null.
957
+
958
+ **Storage**: `.gsd-t/spawns/{spawnId}.json`. Schema v1. Atomic writes
959
+ (temp file + rename). `spawnId` is filesystem-safe
960
+ (`^[A-Za-z0-9._-]{1,200}$`). One ACTIVE plan per spawn; `endedAt === null`
961
+ means in-flight.
962
+
963
+ **Invariants** (from `.gsd-t/domains/m44-d8-spawn-plan-visibility/constraints.md`):
964
+ 1. Writer DERIVES, never decides.
965
+ 2. Spawn must launch even if writer fails.
966
+ 3. Atomic writes only.
967
+ 4. Post-commit hook silent-fail.
968
+ 5. Zero new LLM token cost.
969
+ 6. Additive edits to existing files only.
970
+ 7. No transcript-derivation fallback — missing plan file → *"no active
971
+ spawn plan"*, never reconstruct from transcript heuristics.
972
+
973
+ **Contract**: `.gsd-t/contracts/spawn-plan-contract.md` v1.0.0.
974
+
975
+ **Tests**: `test/m44-d8-spawn-plan-writer.test.js`,
976
+ `test/m44-d8-spawn-plan-status-updater.test.js`,
977
+ `test/m44-d8-post-commit-hook.test.js`,
978
+ `test/m44-d8-dashboard-spawn-plans-endpoint.test.js`,
979
+ `test/m44-d8-transcript-renderer-panel.test.js` — 36 tests total.
980
+
561
981
  ## Planned Architecture Changes (M23-M24)
562
982
 
563
983
  **M23: Headless Mode**