@tekyzinc/gsd-t 3.11.11 → 3.12.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -0
- package/README.md +4 -10
- package/bin/event-stream.cjs +205 -0
- package/bin/gsd-t-unattended.cjs +142 -2
- package/bin/gsd-t-unattended.js +87 -1
- package/bin/gsd-t.js +131 -124
- package/bin/headless-auto-spawn.cjs +102 -6
- package/bin/headless-auto-spawn.js +44 -4
- package/bin/scan-data-collector.js +39 -11
- package/bin/token-budget.cjs +43 -126
- package/bin/unattended-watch-format.cjs +154 -0
- package/commands/gsd-t-backlog-list.md +0 -38
- package/commands/gsd-t-complete-milestone.md +2 -30
- package/commands/gsd-t-debug.md +18 -122
- package/commands/gsd-t-doc-ripple.md +0 -21
- package/commands/gsd-t-execute.md +58 -117
- package/commands/gsd-t-help.md +3 -59
- package/commands/gsd-t-integrate.md +18 -78
- package/commands/gsd-t-quick.md +22 -80
- package/commands/gsd-t-resume.md +2 -2
- package/commands/gsd-t-scan.md +15 -1
- package/commands/gsd-t-status.md +2 -32
- package/commands/gsd-t-unattended-watch.md +37 -1
- package/commands/gsd-t-verify.md +14 -2
- package/commands/gsd-t-wave.md +22 -91
- package/commands/gsd.md +43 -4
- package/docs/GSD-T-README.md +2 -6
- package/docs/architecture.md +10 -8
- package/docs/infrastructure.md +8 -14
- package/docs/methodology.md +10 -4
- package/docs/prd-harness-evolution.md +1 -1
- package/docs/requirements.md +28 -12
- package/package.json +2 -2
- package/scripts/context-meter/threshold.js +25 -46
- package/scripts/context-meter/threshold.test.js +52 -80
- package/scripts/gsd-t-agent-dashboard-server.js +4 -4
- package/scripts/gsd-t-agent-dashboard.html +699 -380
- package/scripts/gsd-t-context-meter.e2e.test.js +4 -3
- package/scripts/gsd-t-context-meter.js +1 -1
- package/scripts/gsd-t-context-meter.test.js +58 -50
- package/scripts/gsd-t-event-writer.js +8 -2
- package/templates/CLAUDE-global.md +7 -25
- package/templates/CLAUDE-project.md +22 -23
- package/bin/qa-calibrator.js +0 -194
- package/bin/runway-estimator.cjs +0 -242
- package/bin/runway-estimator.js +0 -242
- package/bin/token-optimizer.cjs +0 -471
- package/bin/token-optimizer.js +0 -471
- package/bin/token-telemetry.cjs +0 -246
- package/bin/token-telemetry.js +0 -246
- package/commands/gsd-t-audit.md +0 -196
- package/commands/gsd-t-brainstorm.md +0 -201
- package/commands/gsd-t-discuss.md +0 -178
- package/commands/gsd-t-optimization-apply.md +0 -91
- package/commands/gsd-t-optimization-reject.md +0 -94
- package/commands/gsd-t-prompt.md +0 -137
- package/commands/gsd-t-reflect.md +0 -130
- package/scripts/context-meter/count-tokens-client.js +0 -221
- package/scripts/context-meter/count-tokens-client.test.js +0 -308
- package/scripts/context-meter/test-injector.js +0 -55
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,117 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [3.12.12] - 2026-04-17
|
|
6
|
+
|
|
7
|
+
### Fixed — Token-Log Observability for Headless/Unattended Workers
|
|
8
|
+
|
|
9
|
+
**Background**: M38 headless-by-default left `.gsd-t/token-log.md` blind to all supervisor and headless-exec worker activity. Rows were only written by interactive `T_START/T_END` bash blocks in command files. All event-stream `tool_call` entries from workers had `command: null`, `phase: null`, `trace_id: null`.
|
|
10
|
+
|
|
11
|
+
#### Fix 1: headless worker spawns append to token-log.md
|
|
12
|
+
|
|
13
|
+
Three spawn paths now write rows to `{projectDir}/.gsd-t/token-log.md`:
|
|
14
|
+
|
|
15
|
+
- **`bin/headless-auto-spawn.cjs`** — `installCompletionWatcher` appends a row when the detached child exits (poll-based, graceful — never halts on write failure). Creates the file with the canonical header if it does not exist. Migrates files created before this fix (adds header if missing).
|
|
16
|
+
- **`bin/gsd-t-unattended.cjs`** — supervisor worker loop appends a row after each `_spawnWorker` call completes, recording iteration number, duration, exit code. New `_appendTokenLog` helper follows the same schema as interactive command observability blocks.
|
|
17
|
+
- **`bin/gsd-t.js` `doHeadlessExec`** — `gsd-t headless <command>` invocations append a row synchronously after the `claude -p` process exits.
|
|
18
|
+
|
|
19
|
+
Row format matches the existing token-log schema:
|
|
20
|
+
`| Datetime-start | Datetime-end | Command | Step | Model | Duration(s) | Notes | Domain | Task | Ctx% |`
|
|
21
|
+
Tokens are logged as `unknown` (no API access in worker contexts); duration is wall-clock.
|
|
22
|
+
|
|
23
|
+
#### Fix 2: command/phase propagate to event-stream entries in worker contexts
|
|
24
|
+
|
|
25
|
+
Env-var approach chosen (cleaner, no call-site changes needed):
|
|
26
|
+
|
|
27
|
+
- **`scripts/gsd-t-event-writer.js` `buildEvent`** — reads `GSD_T_COMMAND` and `GSD_T_PHASE` env vars as defaults when `--command`/`--phase` flags are absent. Explicit flags always win.
|
|
28
|
+
- **`bin/headless-auto-spawn.cjs`** — sets `GSD_T_COMMAND={command}` and `GSD_T_PHASE={phase}` on every detached child's env before spawn.
|
|
29
|
+
- **`bin/gsd-t-unattended.cjs` `_spawnWorker`** — sets `GSD_T_COMMAND=gsd-t-resume` and `GSD_T_PHASE={state.phase||execute}` on each `claude -p` worker env.
|
|
30
|
+
- **`bin/gsd-t.js` `doHeadlessExec`** — sets `GSD_T_COMMAND=gsd-t-{command}` on the `execFileSync` env.
|
|
31
|
+
|
|
32
|
+
Result: all `tool_call` events in worker contexts are tagged with the originating command and phase instead of `null`.
|
|
33
|
+
|
|
34
|
+
## [3.12.11] - 2026-04-17
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- **Installer owns global PostToolUse context-meter hook** — the hook command now targets the globally-installed npm package (`$(npm root -g)/@tekyzinc/gsd-t/scripts/gsd-t-context-meter.js`) instead of `$CLAUDE_PROJECT_DIR/scripts/...`. The old path caused `Cannot find module` errors in every non-GSD-T project and when `CLAUDE_PROJECT_DIR` was unset.
|
|
38
|
+
- **Auto-migration of stale hook entries** — `install`, `update`, `update-all`, and `init` now detect and replace any PostToolUse entry whose command matches the prior `$CLAUDE_PROJECT_DIR`-based pattern, upgrading it in-place to the canonical global form.
|
|
39
|
+
- **Existence guard** — the hook command is wrapped in a `bash -c '[ -f ... ] && node ... || true'` guard so it silently exits 0 when the package is not present (non-GSD-T projects, uninstalled state).
|
|
40
|
+
- **Uninstall removes the hook** — `gsd-t uninstall` now removes any PostToolUse hook containing `gsd-t-context-meter` from `~/.claude/settings.json`, leaving all other hooks intact.
|
|
41
|
+
|
|
42
|
+
## [3.12.10] - 2026-04-17
|
|
43
|
+
|
|
44
|
+
### M38: Headless-by-Default + Meter Reduction
|
|
45
|
+
|
|
46
|
+
**Background**: M37 was right that the context meter needed to do more — but escalating to a MANDATORY STOP banner in the interactive session was the wrong fix. M38 removes the cause instead of bandaging the symptom: headless spawning is now the default for all primary workflow subagents, so the parent context grows much slower and the single-band meter threshold is sufficient. Seven commands removed. Five contracts folded. Net result: same "work never stops" UX achieved by structure instead of instrumentation.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- **`bin/event-stream.cjs`** — new module for structured JSONL event emission to `.gsd-t/events/YYYY-MM-DD.jsonl`. Emits `task_start`, `task_complete`, `subagent_verdict`, `file_changed`, `test_result`, `error`, `retry` event types. Used by unattended supervisor and watch tick.
|
|
50
|
+
- **`bin/headless-auto-spawn.cjs`** `watch` + `spawnType` parameters — propagation rules: `spawnType:'validation'` always headless regardless of `--watch`; `spawnType:'primary'` + `watch:true` returns `{mode:'in-context'}` for live streaming.
|
|
51
|
+
- **`.gsd-t/contracts/headless-default-contract.md`** v1.0.0 — defines the headless spawn primitive, Conversion Map (7 primary commands converted), `--watch` flag spec, validation-spawn enforcement, and migration path. Folds headless-auto-spawn-contract v1.0.0.
|
|
52
|
+
- **`.gsd-t/contracts/unattended-event-stream-contract.md`** v1.0.0 — JSONL event schema, watch tick activity log format, supervisor emission requirements.
|
|
53
|
+
- **`test/headless-default.test.js`** — 11 tests covering the 4-cell propagation matrix (primary/validation × watch/no-watch) + regression coverage.
|
|
54
|
+
- **`test/event-stream.test.js`**, **`test/unattended-watch.test.js`**, **`test/router-intent.test.js`** — new test files for M38 components.
|
|
55
|
+
- **`commands/gsd.md`** intent classifier — handles conversational requests directly (workflow → existing command; conversational → respond; ambiguous → default to conversation).
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
- **7 command files** (`gsd-t-execute`, `gsd-t-wave`, `gsd-t-integrate`, `gsd-t-quick`, `gsd-t-debug`, `gsd-t-scan`, `gsd-t-verify`) — converted to `autoSpawnHeadless({spawnType:'primary', watch:$WATCH_FLAG})` pattern. Validation spawns (QA, Red Team, Design Verification) always headless.
|
|
59
|
+
- **`bin/gsd-t-unattended.{cjs,js}`** — rejects `--watch` flag with clear error. Emits structured JSONL events at every phase boundary.
|
|
60
|
+
- **`.gsd-t/contracts/context-meter-contract.md`** v1.3.0 — drops three-band model, dead-meter detection, stale-band logic, Universal Auto-Pause elevation. Single-band model: one threshold (default 85%), one action (silent headless handoff). Replaces v1.2.0.
|
|
61
|
+
- **`.gsd-t/contracts/unattended-supervisor-contract.md`** v1.1.0 — adds §9 Event Stream Emission requirement: supervisor MUST emit structured events; watch tick MUST read events and format activity log.
|
|
62
|
+
- **`templates/CLAUDE-global.md`** — Universal Auto-Pause Rule section removed; Context Meter section updated to single-band description.
|
|
63
|
+
- **5 loop commands** (`gsd-t-execute`, `gsd-t-wave`, `gsd-t-integrate`, `gsd-t-quick`, `gsd-t-debug`) — Step 0.2 Universal Auto-Pause enforcement stripped.
|
|
64
|
+
- **`scripts/gsd-t-context-meter.test.js`** — rewritten for single-band model.
|
|
65
|
+
- **`test/filesystem.test.js`** — command count updated from 61 to 54.
|
|
66
|
+
- **`docs/requirements.md`** — REQ-073..078 updated to `SUPERSEDED by REQ-08X (M38)` with replacement pointers. REQ-088..093 added (M38 requirements).
|
|
67
|
+
- **`docs/methodology.md`** §3–§5 — historical framing added; deleted machinery marked as superseded by M38.
|
|
68
|
+
- **`docs/prd-harness-evolution.md`** — Status updated to `HISTORICAL — M31 shipped; M32/M33 SUPERSEDED by M38`.
|
|
69
|
+
- **`docs/architecture.md`**, **`docs/workflows.md`**, **`docs/infrastructure.md`**, **`GSD-T-README.md`** — updated to reflect headless-by-default spawn path, event stream, simplified meter.
|
|
70
|
+
|
|
71
|
+
### Removed
|
|
72
|
+
- **7 commands deleted**: `gsd-t-optimization-apply`, `gsd-t-optimization-reject`, `gsd-t-reflect`, `gsd-t-audit` (self-improvement loop), `gsd-t-prompt`, `gsd-t-brainstorm`, `gsd-t-discuss` (conversational — router intent classifier handles these)
|
|
73
|
+
- **`bin/runway-estimator.cjs`** + **`bin/token-telemetry.cjs`** — deleted; replaced by headless-by-default approach
|
|
74
|
+
- **`bin/qa-calibrator.js`** + **`bin/token-optimizer.js`** — deleted with self-improvement loop
|
|
75
|
+
- **5 contracts folded/deleted**: `runway-estimator-contract.md`, `token-telemetry-contract.md`, `headless-auto-spawn-contract.md`, `qa-calibration-contract.md`, `harness-audit-contract.md`
|
|
76
|
+
- **`test/runway-estimator.test.js`**, **`test/token-telemetry.test.js`**, **`test/qa-calibrator.test.js`**, **`test/token-optimizer.test.js`** — deleted with removed modules
|
|
77
|
+
|
|
78
|
+
### Migration Notes
|
|
79
|
+
- **Spawn pattern**: replace `autoSpawnHeadless()` (no args) with `autoSpawnHeadless({spawnType:'primary', watch:$WATCH_FLAG})` in any downstream command files that call the spawn primitive directly.
|
|
80
|
+
- **Context meter**: if you depend on the three-band model (`normal`/`warn`/`stop`) or dead-meter detection in `token-budget.cjs`, those fields are removed. `getSessionStatus()` returns `{pct, threshold}` only.
|
|
81
|
+
- **Deleted contracts**: any downstream references to `runway-estimator-contract.md`, `token-telemetry-contract.md`, or `headless-auto-spawn-contract.md` should point to `headless-default-contract.md` v1.0.0 instead.
|
|
82
|
+
- **Deleted commands**: `gsd-t-prompt`, `gsd-t-brainstorm`, `gsd-t-discuss` — use plain text messages to Claude instead; the router classifier handles conversational requests. `gsd-t-optimization-apply/reject`, `gsd-t-reflect`, `gsd-t-audit` — removed; the self-improvement backlog is no longer maintained.
|
|
83
|
+
|
|
84
|
+
### Testing
|
|
85
|
+
- 1176/1177 tests pass. 1 pre-existing failure (`scan.test.js:287`) carried forward — scan-data-collector regex drift vs current prose format, unrelated to M38 scope.
|
|
86
|
+
|
|
87
|
+
## [3.11.12] - 2026-04-16
|
|
88
|
+
|
|
89
|
+
### Added — M38 Partition + Plan + Domain H1 Progress
|
|
90
|
+
|
|
91
|
+
**Background**: M38 (Headless-by-Default + Meter Reduction) partitioned into 5 domains across 2 waves. Domain H1 (headless-spawn-default) executed through T6 by unattended supervisor Iter 2 but did not commit. This checkin captures all M38 setup work + H1 in-flight progress + Scan #11 regeneration.
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
- **5 M38 domain directories** under `.gsd-t/domains/` (m38-headless-spawn-default, m38-meter-reduction, m38-unattended-event-stream, m38-router-conversational, m38-cleanup-and-docs) with scope.md, constraints.md, tasks.md each — 35 atomic tasks total
|
|
95
|
+
- **2 new contracts**: `.gsd-t/contracts/headless-default-contract.md` (v1.0.0 DRAFT, folds 3 M35 contracts), `.gsd-t/contracts/unattended-event-stream-contract.md` (v1.0.0 DRAFT)
|
|
96
|
+
- **`test/headless-default.test.js`** — 11 tests covering the 4-cell propagation matrix (primary/validation × watch/no-watch) + existing regression coverage
|
|
97
|
+
- **`bin/gsd-t.js`** `unattended` passthrough subcommand — dispatches to `bin/gsd-t-unattended.cjs` so defense-in-depth `--watch` rejection reaches the supervisor rejection logic
|
|
98
|
+
- **M38 Scan #11** artifacts under `.gsd-t/scan/` (architecture, business-rules, contract-drift, quality, security, test-baseline + scan-report.html)
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
- **`bin/headless-auto-spawn.{cjs,js}`** — added `watch` + `spawnType` parameters; propagation rules implemented (validation spawns always headless, primary+watch returns `{mode: 'in-context'}`)
|
|
102
|
+
- **7 command files** converted to `autoSpawnHeadless({...spawnType: 'primary', watch: $WATCH_FLAG})` pattern: `gsd-t-execute`, `gsd-t-wave`, `gsd-t-integrate`, `gsd-t-quick`, `gsd-t-debug`, `gsd-t-scan`, `gsd-t-verify`
|
|
103
|
+
- **`bin/gsd-t-unattended.{cjs,js}`** — rejects `--watch` flag with clear error (validation-spawn enforcement in unattended context)
|
|
104
|
+
- **`bin/gsd-t.js`** `installContextMeter()` — removed `test-injector.js` skip (file deleted; no longer needed)
|
|
105
|
+
- **`.gsd-t/contracts/integration-points.md`** — M38 dependency graph + 5 checkpoints (M38-CP1 → M38-CP5) + file ownership map
|
|
106
|
+
- **`.gsd-t/progress.md`** — M38 partition + plan entries added to Decision Log
|
|
107
|
+
- **`docs/architecture.md` + `docs/infrastructure.md`** — Scan #11 staleness callouts added (TD-103 doc-ripple candidate noted)
|
|
108
|
+
|
|
109
|
+
### Removed
|
|
110
|
+
- **`scripts/context-meter/count-tokens-client.{js,test.js}`** — retired with v3.11.11 local-estimator switch (count_tokens API no longer called)
|
|
111
|
+
- **`scripts/context-meter/test-injector.js`** — test-only infrastructure, no longer referenced
|
|
112
|
+
|
|
113
|
+
### Testing
|
|
114
|
+
- 1234/1242 tests pass. 8 pre-existing failures carried forward: 7 stranded context-meter tests (TD-102, owned by M38-MR) + 1 scan.test.js live-state test (unrelated). No regressions from H1 work.
|
|
115
|
+
|
|
5
116
|
## [3.11.10] - 2026-04-16
|
|
6
117
|
|
|
7
118
|
### Added — Universal Context Auto-Pause (M37)
|
package/README.md
CHANGED
|
@@ -14,14 +14,10 @@ A methodology for reliable, parallelizable development using Claude Code with op
|
|
|
14
14
|
**Self-learning rule engine** — declarative rules in rules.jsonl detect failure patterns from task metrics. Candidate patches progress through a 5-stage lifecycle (candidate, applied, measured, promoted, graduated) with >55% improvement gates before becoming permanent methodology artifacts.
|
|
15
15
|
**Cross-project learning** — proven rules propagate to `~/.claude/metrics/` and sync across all registered projects via `update-all`. Rules validated in 3+ projects become universal; 5+ projects qualify for npm distribution. Cross-project signal comparison and global ELO rankings available via `gsd-t-metrics --cross-project` and `gsd-t-status`.
|
|
16
16
|
**Stack Rules Engine** — auto-detects project tech stack (React, TypeScript, Node API, Python, Go, Rust) from manifest files and injects mandatory best-practice rules into subagent prompts at execute-time. Universal security rules always apply; stack-specific rules layer on top. Includes **design-to-code** rules for pixel-perfect frontend implementation from Figma, screenshots, or design images — with Figma MCP integration, design token extraction, stack capability evaluation, and mandatory visual verification: every screen is rendered in a real browser, screenshotted at mobile/tablet/desktop, and compared pixel-by-pixel against the Figma design. Auto-bootstraps during partition when design references are detected. Extensible: drop a `.md` file in `templates/stacks/` to add a new stack.
|
|
17
|
-
**
|
|
18
|
-
**Runway-Protected Execution (M35, v2.76.10)** — three-band token budget (`normal` < 70%, `warn` 70–85%, `stop` ≥ 85%) with **zero silent quality degradation**. No model downgrades, no phase skips under pressure. Instead:
|
|
17
|
+
**Headless-by-Default Spawn (M38, v3.12.10)** — long-running workflow commands (execute, wave, integrate, debug repair loops) spawn detached by default via the unattended supervisor. The interactive session prints a launch banner, logs the event-stream path, and exits. Pass `--watch` to keep a live status block in the session (270s `ScheduleWakeup` ticks, cache-window-safe). The supervisor emits JSONL events to `.gsd-t/events/YYYY-MM-DD.jsonl` at every phase boundary — shared by watch command and dashboard. See `.gsd-t/contracts/headless-default-contract.md` v1.0.0 and `unattended-event-stream-contract.md` v1.0.0.
|
|
19
18
|
- **Surgical model selection** — `bin/model-selector.js` assigns haiku/sonnet/opus per phase via a declarative rules table; `/advisor` escalation path with convention-based fallback.
|
|
20
|
-
- **
|
|
21
|
-
|
|
22
|
-
- **Per-spawn token telemetry** — `.gsd-t/token-metrics.jsonl` records one 18-field row per Task subagent spawn. Feeds the runway estimator and the retrospective optimization backlog.
|
|
23
|
-
- **Optimization backlog** — `bin/token-optimizer.js` runs at `complete-milestone` and appends model-tier recalibration recommendations (`demote`, `escalate`, `runway-tune`, `investigate`) to `.gsd-t/optimization-backlog.md`. **Never auto-applies.** User promotes via `/user:gsd-t-optimization-apply {ID}` or dismisses via `/user:gsd-t-optimization-reject {ID} [--reason "..."]` with a 5-milestone cooldown.
|
|
24
|
-
**Context Meter (M34)** — real-time context window measurement via the Anthropic `count_tokens` API. A PostToolUse hook streams the current transcript to `count_tokens`, writes the exact input-token count and threshold band to `.gsd-t/.context-meter-state.json`, and `token-budget.getSessionStatus()` reads that state file as the authoritative context-burn signal. Replaces the v2.74.12 task-counter proxy. Requires an `ANTHROPIC_API_KEY` — `gsd-t doctor` hard-gates on it. See the Context Meter Setup section below.
|
|
19
|
+
- **Per-spawn token telemetry** — `.gsd-t/token-metrics.jsonl` records one 18-field row per Task subagent spawn.
|
|
20
|
+
**Context Meter (M34/M38)** — PostToolUse hook writes `.gsd-t/.context-meter-state.json` via local token estimation. Single-band model (`context-meter-contract.md` v1.3.0): one threshold (default 85%), one action — hand off to a detached headless spawn. The meter informs spawn-time routing, not in-flight pauses.
|
|
25
21
|
**Quality North Star** — projects define a `## Quality North Star` section in CLAUDE.md (1–3 sentences, e.g., "This is a published npm library. Every public API must be intuitive and backward-compatible."). `gsd-t-init` auto-detects preset (library/web-app/cli) from package.json signals; `gsd-t-setup` configures it for existing projects. Subagents read it as a quality lens; absent = silent skip (backward compatible).
|
|
26
22
|
**Design Brief Artifact** — during partition, UI/frontend projects (React, Vue, Svelte, Flutter, Tailwind) automatically get `.gsd-t/contracts/design-brief.md` with color palette, typography, spacing system, component patterns, and tone/voice. Non-UI projects skip silently. User-customized briefs are preserved. Referenced in plan phase for visual consistency.
|
|
27
23
|
**Design Verification Agent** — after QA passes on design-to-code projects, a dedicated verification agent opens a browser with both the built frontend AND the original design (Figma page, design image, or MCP screenshot) side-by-side for direct visual comparison. Produces a structured element-by-element comparison table (30+ rows) with specific design values vs. implementation values and MATCH/DEVIATION verdicts. An artifact gate enforces that the comparison table exists — missing it blocks completion. Separation of concerns: coding agents code, verification agents verify. Wired into execute (Step 5.25) and quick (Step 5.25). Only fires when `.gsd-t/contracts/design-contract.md` exists — non-design projects are unaffected.
|
|
@@ -37,7 +33,7 @@ A methodology for reliable, parallelizable development using Claude Code with op
|
|
|
37
33
|
npx @tekyzinc/gsd-t install
|
|
38
34
|
```
|
|
39
35
|
|
|
40
|
-
This installs
|
|
36
|
+
This installs 49 GSD-T commands + 5 utility commands (54 total) to `~/.claude/commands/` and the global CLAUDE.md to `~/.claude/CLAUDE.md`. Works on Windows, Mac, and Linux.
|
|
41
37
|
|
|
42
38
|
### Start Using It
|
|
43
39
|
|
|
@@ -192,7 +188,6 @@ This will replace changed command files, back up your CLAUDE.md if customized, a
|
|
|
192
188
|
| `/user:gsd-t-status` | Cross-domain progress view with token breakdown by domain/task/phase | Manual |
|
|
193
189
|
| `/user:gsd-t-resume` | Restore context, continue | Manual |
|
|
194
190
|
| `/user:gsd-t-quick` | Fast task with GSD-T guarantees | Manual |
|
|
195
|
-
| `/user:gsd-t-reflect` | Generate retrospective from event stream, propose memory updates | Manual |
|
|
196
191
|
| `/user:gsd-t-visualize` | Launch browser dashboard — SSE server + React Flow agent visualization | Manual |
|
|
197
192
|
| `/user:gsd-t-debug` | Systematic debugging with state | Manual |
|
|
198
193
|
| `/user:gsd-t-metrics` | View task telemetry, process ELO, signal distribution, domain health, and cross-project comparison (`--cross-project`) | Manual |
|
|
@@ -202,7 +197,6 @@ This will replace changed command files, back up your CLAUDE.md if customized, a
|
|
|
202
197
|
| `/user:gsd-t-version-update` | Update GSD-T to latest version | Manual |
|
|
203
198
|
| `/user:gsd-t-version-update-all` | Update GSD-T + all registered projects | Manual |
|
|
204
199
|
| `/user:gsd-t-triage-and-merge` | Auto-review, merge, and publish GitHub branches | Manual |
|
|
205
|
-
| `/user:gsd-t-audit` | Harness self-audit — analyze cost/benefit of enforcement components | Manual |
|
|
206
200
|
| `/user:gsd-t-design-audit` | Compare built screen against Figma design — structured deviation report | Manual |
|
|
207
201
|
| `/user:gsd-t-design-build` | Build from design contracts with two-terminal review (Term 1 builder) | Manual |
|
|
208
202
|
| `/user:gsd-t-design-review` | Independent review agent for design build (Term 2 reviewer) | Auto |
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/event-stream.cjs
|
|
3
|
+
*
|
|
4
|
+
* Structured event stream for the unattended supervisor watch-tick (M38 ES).
|
|
5
|
+
* Workers append events to `.gsd-t/events/YYYY-MM-DD.jsonl`; watch tick reads
|
|
6
|
+
* new events since the persisted cursor.
|
|
7
|
+
*
|
|
8
|
+
* Contract: `.gsd-t/contracts/unattended-event-stream-contract.md` v1.0.0.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
|
|
16
|
+
const EVENTS_DIR_REL = path.join(".gsd-t", "events");
|
|
17
|
+
const CURSOR_PATH_REL = path.join(".gsd-t", ".unattended", "event-cursor");
|
|
18
|
+
|
|
19
|
+
function eventsDir(projectDir) {
|
|
20
|
+
return path.join(projectDir, EVENTS_DIR_REL);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cursorPath(projectDir) {
|
|
24
|
+
return path.join(projectDir, CURSOR_PATH_REL);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function todayFileDate(now) {
|
|
28
|
+
const d = now || new Date();
|
|
29
|
+
const y = d.getUTCFullYear();
|
|
30
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
31
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
32
|
+
return `${y}-${m}-${day}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function eventFileFor(projectDir, fileDate) {
|
|
36
|
+
return path.join(eventsDir(projectDir), `${fileDate}.jsonl`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Append one event to today's jsonl file. Atomic via append-only write with
|
|
41
|
+
* a newline-terminated record. Never throws — failures are logged to stderr.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} projectDir
|
|
44
|
+
* @param {object} eventObj — `type`, `iter`, `source`, plus type-specific fields
|
|
45
|
+
* @returns {boolean} true on success, false on failure
|
|
46
|
+
*/
|
|
47
|
+
function appendEvent(projectDir, eventObj) {
|
|
48
|
+
try {
|
|
49
|
+
if (!eventObj || typeof eventObj !== "object") return false;
|
|
50
|
+
const ev = Object.assign({}, eventObj);
|
|
51
|
+
if (typeof ev.ts !== "string" || !ev.ts) {
|
|
52
|
+
ev.ts = new Date().toISOString();
|
|
53
|
+
}
|
|
54
|
+
const dir = eventsDir(projectDir);
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
const fileDate = todayFileDate(new Date(ev.ts));
|
|
57
|
+
const file = eventFileFor(projectDir, fileDate);
|
|
58
|
+
const line = JSON.stringify(ev) + "\n";
|
|
59
|
+
fs.appendFileSync(file, line, "utf8");
|
|
60
|
+
return true;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
try {
|
|
63
|
+
process.stderr.write(`[event-stream] appendEvent failed: ${err.message}\n`);
|
|
64
|
+
} catch (_) { /* ignore */ }
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readCursor(projectDir) {
|
|
70
|
+
try {
|
|
71
|
+
const raw = fs.readFileSync(cursorPath(projectDir), "utf8");
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
if (parsed && typeof parsed.fileDate === "string" && Number.isFinite(parsed.offset)) {
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
} catch (_) { /* missing or malformed → treated as uninitialized */ }
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeCursor(projectDir, cursor) {
|
|
81
|
+
try {
|
|
82
|
+
const p = cursorPath(projectDir);
|
|
83
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
84
|
+
const tmp = `${p}.tmp`;
|
|
85
|
+
fs.writeFileSync(tmp, JSON.stringify(cursor), "utf8");
|
|
86
|
+
fs.renameSync(tmp, p);
|
|
87
|
+
return true;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
try {
|
|
90
|
+
process.stderr.write(`[event-stream] writeCursor failed: ${err.message}\n`);
|
|
91
|
+
} catch (_) { /* ignore */ }
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function fileSize(p) {
|
|
97
|
+
try {
|
|
98
|
+
return fs.statSync(p).size;
|
|
99
|
+
} catch (_) {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseJsonlSlice(buf) {
|
|
105
|
+
const text = buf.toString("utf8");
|
|
106
|
+
const lines = text.split("\n");
|
|
107
|
+
const hasPartialTail = text.length > 0 && !text.endsWith("\n");
|
|
108
|
+
const consumable = hasPartialTail ? lines.slice(0, -1) : lines;
|
|
109
|
+
const events = [];
|
|
110
|
+
for (const line of consumable) {
|
|
111
|
+
if (!line) continue;
|
|
112
|
+
try {
|
|
113
|
+
events.push(JSON.parse(line));
|
|
114
|
+
} catch (_) {
|
|
115
|
+
try {
|
|
116
|
+
process.stderr.write(`[event-stream] skipping malformed line\n`);
|
|
117
|
+
} catch (__) { /* ignore */ }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const consumedBytes = hasPartialTail
|
|
121
|
+
? Buffer.byteLength(consumable.join("\n") + (consumable.length ? "\n" : ""), "utf8")
|
|
122
|
+
: buf.length;
|
|
123
|
+
return { events, consumedBytes };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readSlice(filePath, startOffset) {
|
|
127
|
+
const size = fileSize(filePath);
|
|
128
|
+
if (size <= startOffset) return { events: [], consumedBytes: 0, fileSize: size };
|
|
129
|
+
const fd = fs.openSync(filePath, "r");
|
|
130
|
+
try {
|
|
131
|
+
const length = size - startOffset;
|
|
132
|
+
const buf = Buffer.alloc(length);
|
|
133
|
+
fs.readSync(fd, buf, 0, length, startOffset);
|
|
134
|
+
const { events, consumedBytes } = parseJsonlSlice(buf);
|
|
135
|
+
return { events, consumedBytes, fileSize: size };
|
|
136
|
+
} finally {
|
|
137
|
+
try { fs.closeSync(fd); } catch (_) { /* ignore */ }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read all events from cursor to EOF. Handles day-boundary per contract §3:
|
|
143
|
+
* if cursor points to a past file date, read remaining events from that file,
|
|
144
|
+
* then read today's file from byte 0; advance cursor to today's EOF.
|
|
145
|
+
*
|
|
146
|
+
* If cursor is missing, initialize to today's file at current EOF (no backlog
|
|
147
|
+
* surfaced on first read — matches contract §3 initial-cursor behavior).
|
|
148
|
+
*
|
|
149
|
+
* @param {string} projectDir
|
|
150
|
+
* @returns {{events: object[], newCursor: {fileDate: string, offset: number}}}
|
|
151
|
+
*/
|
|
152
|
+
function readSinceCursor(projectDir) {
|
|
153
|
+
const today = todayFileDate(new Date());
|
|
154
|
+
let cursor = readCursor(projectDir);
|
|
155
|
+
if (!cursor) {
|
|
156
|
+
const todayFile = eventFileFor(projectDir, today);
|
|
157
|
+
const size = fileSize(todayFile);
|
|
158
|
+
const newCursor = { fileDate: today, offset: size };
|
|
159
|
+
writeCursor(projectDir, newCursor);
|
|
160
|
+
return { events: [], newCursor };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const result = [];
|
|
164
|
+
let { fileDate, offset } = cursor;
|
|
165
|
+
|
|
166
|
+
if (fileDate !== today) {
|
|
167
|
+
const prev = eventFileFor(projectDir, fileDate);
|
|
168
|
+
if (fs.existsSync(prev)) {
|
|
169
|
+
const slice = readSlice(prev, offset);
|
|
170
|
+
result.push(...slice.events);
|
|
171
|
+
}
|
|
172
|
+
fileDate = today;
|
|
173
|
+
offset = 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const todayFile = eventFileFor(projectDir, today);
|
|
177
|
+
if (fs.existsSync(todayFile)) {
|
|
178
|
+
const slice = readSlice(todayFile, offset);
|
|
179
|
+
result.push(...slice.events);
|
|
180
|
+
offset = offset + slice.consumedBytes;
|
|
181
|
+
} else {
|
|
182
|
+
return { events: result, newCursor: { fileDate, offset } };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { events: result, newCursor: { fileDate, offset } };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Persist the cursor returned by readSinceCursor.
|
|
190
|
+
* @param {string} projectDir
|
|
191
|
+
* @param {{fileDate: string, offset: number}} newCursor
|
|
192
|
+
*/
|
|
193
|
+
function advanceCursor(projectDir, newCursor) {
|
|
194
|
+
if (!newCursor || typeof newCursor.fileDate !== "string" || !Number.isFinite(newCursor.offset)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return writeCursor(projectDir, newCursor);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
appendEvent,
|
|
202
|
+
readSinceCursor,
|
|
203
|
+
advanceCursor,
|
|
204
|
+
_internal: { todayFileDate, eventsDir, cursorPath, readCursor, writeCursor },
|
|
205
|
+
};
|
package/bin/gsd-t-unattended.cjs
CHANGED
|
@@ -55,6 +55,13 @@ const {
|
|
|
55
55
|
notify,
|
|
56
56
|
} = require("./gsd-t-unattended-platform.cjs");
|
|
57
57
|
|
|
58
|
+
// Event stream (M38 ES) — additive, non-blocking. `_emit` swallows its own
|
|
59
|
+
// errors per unattended-event-stream-contract.md §6.
|
|
60
|
+
const { appendEvent: _esAppendEvent } = require("./event-stream.cjs");
|
|
61
|
+
function _emit(projectDir, ev) {
|
|
62
|
+
try { _esAppendEvent(projectDir, ev); } catch (_) { /* never halt the loop */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
59
66
|
|
|
60
67
|
const CONTRACT_VERSION = "1.0.0";
|
|
@@ -476,7 +483,32 @@ function finalizeState(state, dir, terminalStatus) {
|
|
|
476
483
|
*/
|
|
477
484
|
function doUnattended(argv, deps) {
|
|
478
485
|
deps = deps || {};
|
|
479
|
-
const
|
|
486
|
+
const rawArgv = argv || [];
|
|
487
|
+
|
|
488
|
+
// --watch rejection (headless-default-contract §2) — unattended is detached
|
|
489
|
+
// by definition; passing --watch is a category error. Refuse fast so the
|
|
490
|
+
// user sees a clear message before any state.json / PID work happens.
|
|
491
|
+
if (
|
|
492
|
+
Array.isArray(rawArgv) &&
|
|
493
|
+
rawArgv.some(
|
|
494
|
+
(a) => typeof a === "string" && (a === "--watch" || a.startsWith("--watch=")),
|
|
495
|
+
)
|
|
496
|
+
) {
|
|
497
|
+
// eslint-disable-next-line no-console
|
|
498
|
+
console.error(
|
|
499
|
+
"[gsd-t-unattended] --watch is incompatible with unattended.\n" +
|
|
500
|
+
"Unattended supervisor is detached by definition.\n" +
|
|
501
|
+
"Run /user:gsd-t-unattended-watch from your interactive session to see live activity.",
|
|
502
|
+
);
|
|
503
|
+
return {
|
|
504
|
+
ok: false,
|
|
505
|
+
dryRun: false,
|
|
506
|
+
exitCode: 2,
|
|
507
|
+
reason: "--watch is incompatible with unattended",
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const opts = parseArgs(rawArgv);
|
|
480
512
|
const projectDir = path.resolve(opts.project || ".");
|
|
481
513
|
|
|
482
514
|
// ── Resolve injection points (real impls by default) ─────────────────────
|
|
@@ -866,6 +898,16 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
866
898
|
state.lastWorkerStartedAt = workerStart.toISOString();
|
|
867
899
|
writeState(state, dir);
|
|
868
900
|
|
|
901
|
+
_emit(projectDir, {
|
|
902
|
+
ts: workerStart.toISOString(),
|
|
903
|
+
iter: state.iter,
|
|
904
|
+
type: "task_start",
|
|
905
|
+
source: "supervisor",
|
|
906
|
+
milestone: state.milestone || "",
|
|
907
|
+
wave: state.wave || "",
|
|
908
|
+
task: state.nextTask || "",
|
|
909
|
+
});
|
|
910
|
+
|
|
869
911
|
let res;
|
|
870
912
|
try {
|
|
871
913
|
res = spawnWorker(state, {
|
|
@@ -897,12 +939,46 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
897
939
|
// Append the full worker output to run.log (never truncate).
|
|
898
940
|
_appendRunLog(dir, state.iter, workerEnd, exitCode, stdout, stderr);
|
|
899
941
|
|
|
942
|
+
// Append to token-log.md (Fix 1, v3.12.12) — supervisor workers write rows
|
|
943
|
+
// so the log captures headless/unattended activity, not just interactive spawns.
|
|
944
|
+
_appendTokenLog(projectDir, {
|
|
945
|
+
dtStart: workerStart.toISOString().slice(0, 16).replace("T", " "),
|
|
946
|
+
dtEnd: workerEnd.toISOString().slice(0, 16).replace("T", " "),
|
|
947
|
+
command: "gsd-t-resume",
|
|
948
|
+
durationS: Math.round(elapsedMs / 1000),
|
|
949
|
+
exitCode,
|
|
950
|
+
iter: state.iter,
|
|
951
|
+
});
|
|
952
|
+
|
|
900
953
|
// Post-spawn state update
|
|
901
954
|
state.lastExit = exitCode;
|
|
902
955
|
state.lastWorkerFinishedAt = workerEnd.toISOString();
|
|
903
956
|
state.lastElapsedMs = elapsedMs;
|
|
904
957
|
writeState(state, dir);
|
|
905
958
|
|
|
959
|
+
// Event-stream: task_complete on success, error on non-zero.
|
|
960
|
+
const durationS = Math.round(elapsedMs / 1000);
|
|
961
|
+
if (exitCode === 0) {
|
|
962
|
+
_emit(projectDir, {
|
|
963
|
+
ts: workerEnd.toISOString(),
|
|
964
|
+
iter: state.iter,
|
|
965
|
+
type: "task_complete",
|
|
966
|
+
source: "supervisor",
|
|
967
|
+
task: state.nextTask || "",
|
|
968
|
+
verdict: "pass",
|
|
969
|
+
duration_s: durationS,
|
|
970
|
+
});
|
|
971
|
+
} else {
|
|
972
|
+
_emit(projectDir, {
|
|
973
|
+
ts: workerEnd.toISOString(),
|
|
974
|
+
iter: state.iter,
|
|
975
|
+
type: "error",
|
|
976
|
+
source: "supervisor",
|
|
977
|
+
error: `worker exit ${exitCode}`,
|
|
978
|
+
recoverable: exitCode !== 4 && exitCode !== 5,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
|
|
906
982
|
// ── POST-WORKER HOOK (contract §12) ────────────────────────────────────
|
|
907
983
|
// Read the tail of run.log for pattern detection. ~200 lines is enough
|
|
908
984
|
// to span the last several iteration blocks for the gutter detector.
|
|
@@ -941,6 +1017,13 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
941
1017
|
break;
|
|
942
1018
|
}
|
|
943
1019
|
// Not yet done — continue relay.
|
|
1020
|
+
_emit(projectDir, {
|
|
1021
|
+
iter: state.iter,
|
|
1022
|
+
type: "retry",
|
|
1023
|
+
source: "supervisor",
|
|
1024
|
+
attempt: state.iter,
|
|
1025
|
+
reason: "milestone_incomplete",
|
|
1026
|
+
});
|
|
944
1027
|
continue;
|
|
945
1028
|
}
|
|
946
1029
|
if (exitCode === 4) {
|
|
@@ -957,9 +1040,23 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
957
1040
|
}
|
|
958
1041
|
if (exitCode === 124) {
|
|
959
1042
|
// Timeout — continue unless the iter cap is hit on the next check.
|
|
1043
|
+
_emit(projectDir, {
|
|
1044
|
+
iter: state.iter,
|
|
1045
|
+
type: "retry",
|
|
1046
|
+
source: "supervisor",
|
|
1047
|
+
attempt: state.iter,
|
|
1048
|
+
reason: "timeout",
|
|
1049
|
+
});
|
|
960
1050
|
continue;
|
|
961
1051
|
}
|
|
962
1052
|
// Non-terminal (1/2/3) — continue the relay.
|
|
1053
|
+
_emit(projectDir, {
|
|
1054
|
+
iter: state.iter,
|
|
1055
|
+
type: "retry",
|
|
1056
|
+
source: "supervisor",
|
|
1057
|
+
attempt: state.iter,
|
|
1058
|
+
reason: `exit_${exitCode}`,
|
|
1059
|
+
});
|
|
963
1060
|
}
|
|
964
1061
|
|
|
965
1062
|
// If we exited because the user dropped a stop sentinel and no terminal
|
|
@@ -974,6 +1071,41 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
974
1071
|
return state;
|
|
975
1072
|
}
|
|
976
1073
|
|
|
1074
|
+
// ── _appendTokenLog (Fix 1, v3.12.12) ───────────────────────────────────────
|
|
1075
|
+
|
|
1076
|
+
const _TOKEN_LOG_HEADER =
|
|
1077
|
+
"| Datetime-start | Datetime-end | Command | Step | Model | Duration(s) | Notes | Domain | Task | Ctx% |\n" +
|
|
1078
|
+
"|---|---|---|---|---|---|---|---|---|---|\n";
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Append one row to {projectDir}/.gsd-t/token-log.md for a supervisor worker
|
|
1082
|
+
* iteration. Matches the schema used by interactive command-file observability.
|
|
1083
|
+
*/
|
|
1084
|
+
function _appendTokenLog(projectDir, entry) {
|
|
1085
|
+
try {
|
|
1086
|
+
const logPath = path.join(projectDir, ".gsd-t", "token-log.md");
|
|
1087
|
+
const note = entry.exitCode === 0
|
|
1088
|
+
? `supervisor iter=${entry.iter}: ok`
|
|
1089
|
+
: `supervisor iter=${entry.iter}: exit ${entry.exitCode}`;
|
|
1090
|
+
const row =
|
|
1091
|
+
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | supervisor-iter-${entry.iter} | unknown | ${entry.durationS}s | ${note} | - | - | unknown |\n`;
|
|
1092
|
+
const gsdtDir = path.join(projectDir, ".gsd-t");
|
|
1093
|
+
if (!fs.existsSync(gsdtDir)) fs.mkdirSync(gsdtDir, { recursive: true });
|
|
1094
|
+
if (!fs.existsSync(logPath)) {
|
|
1095
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${_TOKEN_LOG_HEADER}${row}`);
|
|
1096
|
+
} else {
|
|
1097
|
+
const existing = fs.readFileSync(logPath, "utf8");
|
|
1098
|
+
if (!existing.includes("| Datetime-start |")) {
|
|
1099
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${_TOKEN_LOG_HEADER}${existing}${row}`);
|
|
1100
|
+
} else {
|
|
1101
|
+
fs.appendFileSync(logPath, row);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
} catch (_) {
|
|
1105
|
+
/* best-effort — never halt the supervisor loop */
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
977
1109
|
// ── _spawnWorker ────────────────────────────────────────────────────────────
|
|
978
1110
|
|
|
979
1111
|
/**
|
|
@@ -987,7 +1119,15 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
987
1119
|
*/
|
|
988
1120
|
function _spawnWorker(state, opts) {
|
|
989
1121
|
const bin = (state && state.claudeBin) || resolveClaudePath();
|
|
990
|
-
|
|
1122
|
+
// Inject command/phase so event-stream tool_call entries are tagged in worker
|
|
1123
|
+
// contexts (Fix 2, v3.12.12). Supervisor always runs gsd-t-resume workers;
|
|
1124
|
+
// phase is inferred from state when available.
|
|
1125
|
+
const workerEnv = {
|
|
1126
|
+
...process.env,
|
|
1127
|
+
GSD_T_UNATTENDED_WORKER: "1",
|
|
1128
|
+
GSD_T_COMMAND: "gsd-t-resume",
|
|
1129
|
+
GSD_T_PHASE: (state && state.phase) || "execute",
|
|
1130
|
+
};
|
|
991
1131
|
const res = platformSpawnWorker(opts.cwd, opts.timeout, {
|
|
992
1132
|
bin,
|
|
993
1133
|
args: [
|