@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.
- package/CHANGELOG.md +61 -0
- package/README.md +13 -3
- package/bin/gsd-t-depgraph-validate.cjs +140 -0
- package/bin/gsd-t-economics.cjs +287 -0
- package/bin/gsd-t-file-disjointness.cjs +227 -0
- package/bin/gsd-t-in-session-usage.cjs +213 -0
- package/bin/gsd-t-orchestrator-config.cjs +100 -3
- package/bin/gsd-t-orchestrator.js +2 -1
- package/bin/gsd-t-parallel.cjs +382 -0
- package/bin/gsd-t-report-tokens.cjs +549 -0
- package/bin/gsd-t-task-graph.cjs +366 -0
- package/bin/gsd-t-token-capture.cjs +29 -14
- package/bin/gsd-t-token-dashboard.cjs +35 -0
- package/bin/gsd-t-tool-attribution.cjs +377 -0
- package/bin/gsd-t-tool-cost.cjs +195 -0
- package/bin/gsd-t-unattended-platform.cjs +7 -1
- package/bin/gsd-t-unattended.cjs +2 -0
- package/bin/gsd-t.js +155 -5
- package/bin/headless-auto-spawn.cjs +69 -49
- package/bin/headless-auto-spawn.js +18 -24
- package/bin/runway-estimator.cjs +212 -0
- package/bin/spawn-plan-derive.cjs +163 -0
- package/bin/spawn-plan-status-updater.cjs +292 -0
- package/bin/spawn-plan-writer.cjs +204 -0
- package/commands/gsd-t-debug.md +26 -7
- package/commands/gsd-t-execute.md +36 -28
- package/commands/gsd-t-help.md +11 -0
- package/commands/gsd-t-integrate.md +27 -7
- package/commands/gsd-t-quick.md +30 -13
- package/commands/gsd-t-scan.md +5 -5
- package/commands/gsd-t-unattended-watch.md +4 -3
- package/commands/gsd-t-unattended.md +9 -3
- package/commands/gsd-t-verify.md +5 -5
- package/commands/gsd-t-wave.md +21 -8
- package/commands/gsd.md +45 -3
- package/docs/GSD-T-README.md +43 -5
- package/docs/architecture.md +423 -3
- package/docs/requirements.md +203 -0
- package/package.json +1 -1
- package/scripts/gsd-t-calibration-hook.js +256 -0
- package/scripts/gsd-t-compact-detector.js +223 -0
- package/scripts/gsd-t-compaction-scanner.js +305 -0
- package/scripts/gsd-t-dashboard-autostart.cjs +172 -0
- package/scripts/gsd-t-dashboard-server.js +179 -0
- package/scripts/gsd-t-heartbeat.js +50 -2
- package/scripts/gsd-t-post-commit-spawn-plan.sh +86 -0
- package/scripts/gsd-t-transcript.html +546 -43
- package/scripts/hooks/gsd-t-in-session-usage-hook.js +84 -0
- package/scripts/spawn-plan-fmt-tokens.cjs +80 -0
- package/templates/CLAUDE-global.md +8 -3
- package/templates/CLAUDE-project.md +17 -14
- package/templates/hooks/post-commit-spawn-plan.sh +85 -0
package/docs/architecture.md
CHANGED
|
@@ -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
|
-
│
|
|
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
|
|
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**
|