@tekyzinc/gsd-t 3.12.13 → 3.12.15
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 +48 -0
- package/bin/gsd-t-unattended.cjs +14 -4
- package/bin/gsd-t.js +32 -3
- package/bin/headless-auto-spawn.cjs +22 -3
- package/bin/headless-auto-spawn.js +12 -1
- package/bin/orchestrator.js +26 -0
- package/commands/gsd-t-complete-milestone.md +12 -1
- package/package.json +1 -1
- package/scripts/gsd-t-design-review-server.js +11 -1
- package/scripts/gsd-t-event-writer.js +7 -4
- package/scripts/gsd-t-heartbeat.js +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [3.12.15] - 2026-04-17
|
|
6
|
+
|
|
7
|
+
### Fixed — Decision Log Trim — stop live progress.md bloat
|
|
8
|
+
|
|
9
|
+
`commands/gsd-t-complete-milestone.md` Step 7 previously instructed "Keep all prior decision log entries — they are valuable context". Because every prior milestone's full log is already frozen into its archive snapshot by Step 4, carrying the same entries forward on the live `.gsd-t/progress.md` produced unbounded file growth (GSD-T's live file had reached 168,921 bytes / 658 lines — 10× the size of a healthy project).
|
|
10
|
+
|
|
11
|
+
**Fix**: Step 7 now explicitly trims the live Decision Log to the just-completed milestone's entries after the archive snapshot is written. The instruction reads:
|
|
12
|
+
|
|
13
|
+
> Delete all decision-log entries older than the just-completed milestone's start date. Those entries are preserved in the milestone archive created in Step 4. Keep only the completion entry plus any entries logged on or after the cutoff — typically the live log is near-empty when the next milestone begins.
|
|
14
|
+
|
|
15
|
+
The archive at `.gsd-t/milestones/{name}-{date}/progress.md` remains the source-of-truth for the full history. A pointer line (`> Prior decision log entries preserved in .gsd-t/milestones/*/progress.md`) is added to the live file so future readers know where to look.
|
|
16
|
+
|
|
17
|
+
**One-time cleanup on the GSD-T repo**: live `.gsd-t/progress.md` trimmed from 168,921 B / 658 lines to 11,733 B / 67 lines (93% reduction), cut at M38 start (2026-04-16 14:25). Historical decision log preserved by copying the pre-trim file into `.gsd-t/milestones/M38-headless-by-default-2026-04-17/progress.md` (Step 4 missed that archive copy when M38 was completed earlier today; fix-forward).
|
|
18
|
+
|
|
19
|
+
**Users with bloated `.gsd-t/progress.md`** can run the same one-time cleanup manually: find the current milestone's `[milestone-defined]` start entry in the Decision Log, copy the pre-trim file to the milestone archive directory (if it's not already there), then delete all Decision Log entries older than that start date. Keep the pointer line at the top of the Decision Log section.
|
|
20
|
+
|
|
21
|
+
**Tests**: Unit 1186/1186 pass (no code paths changed — pure doc/template edits + one-time live file rewrite). E2E N/A.
|
|
22
|
+
|
|
23
|
+
## [3.12.14] - 2026-04-17
|
|
24
|
+
|
|
25
|
+
### Fixed — Telemetry Env-Propagation Regression (Tag All Worker Events)
|
|
26
|
+
|
|
27
|
+
v3.12.12 added `GSD_T_COMMAND`/`GSD_T_PHASE` env-var fallbacks to `scripts/gsd-t-event-writer.js` but two critical call sites were missed — producing mostly-null telemetry in production.
|
|
28
|
+
|
|
29
|
+
**Evidence from bee-poc (50 min observation)**: 908 events, only 1/908 had `command` populated; 836 `tool_call` events had command/phase/trace_id all null; only 2 `.gsd-t/token-log.md` rows (both from the outer supervisor process; 37 inner subagents wrote zero rows); supervisor row showed `model=unknown`.
|
|
30
|
+
|
|
31
|
+
**Root causes**:
|
|
32
|
+
1. `scripts/gsd-t-heartbeat.js::buildEventStreamEntry` — this PostToolUse hook fires on every tool call in every child process (the source of ~90% of events) and hardcoded `{command: null, phase: null, trace_id: null}` into every event it wrote.
|
|
33
|
+
2. Neither the writer nor the heartbeat read `GSD_T_TRACE_ID` or `GSD_T_MODEL` from env — so even when spawners set them, they never appeared on events.
|
|
34
|
+
3. Several spawn sites (orchestrator, `spawnClaudeSession`, `runLedgerCompaction`, design-review claude spawn) never set the GSD_T_* env block at all.
|
|
35
|
+
|
|
36
|
+
**Fixes**:
|
|
37
|
+
- `scripts/gsd-t-event-writer.js::buildEvent` now reads `GSD_T_TRACE_ID` and `GSD_T_MODEL` env fallbacks alongside command/phase.
|
|
38
|
+
- `scripts/gsd-t-heartbeat.js::buildEventStreamEntry` replaced hardcoded null triple with `process.env.GSD_T_COMMAND||null` / `GSD_T_PHASE||null` / `GSD_T_TRACE_ID||null`.
|
|
39
|
+
- `bin/headless-auto-spawn.{cjs,js}` workerEnv sets `GSD_T_COMMAND` + `GSD_T_PHASE` + `GSD_T_PROJECT_DIR`, and conditionally forwards parent `GSD_T_TRACE_ID` / `GSD_T_MODEL`. `appendTokenLog` reads `process.env.GSD_T_MODEL` instead of the `"unknown"` literal.
|
|
40
|
+
- `bin/gsd-t-unattended.cjs::_spawnWorker` workerEnv populates the full GSD_T_* block from `state` + env fallbacks. `_appendTokenLog` reads `process.env.GSD_T_MODEL`.
|
|
41
|
+
- `bin/gsd-t.js` three sites patched: `doHeadlessExec` workerEnv, `spawnClaudeSession` (fallback command=`gsd-t-debug` / phase=`debug`), `runLedgerCompaction` (fallback model=`haiku`). `appendHeadlessTokenLog` reads `process.env.GSD_T_MODEL`.
|
|
42
|
+
- `bin/orchestrator.js` new `_buildOrchestratorEnv(opts, projectDir)` helper threaded through `spawnClaude` (sync) and `spawnClaudeAsync`.
|
|
43
|
+
- `scripts/gsd-t-design-review-server.js` claude spawn now injects the GSD_T_* env block.
|
|
44
|
+
|
|
45
|
+
**Reproduction test**: NEW `test/telemetry-env-propagation.test.js` — 6 tests that exercise the REAL production spawn code paths (not hand-rolled mocks): writer + heartbeat env-fallback unit coverage, `autoSpawnHeadless` real-spawn via env-dump shim at `bin/gsd-t.js`, unattended `platform.spawnWorker` with a real env-dump script. Failed 3/6 before fix as expected; 6/6 pass after.
|
|
46
|
+
|
|
47
|
+
**Tests**: Unit 1186/1186 pass. E2E N/A (no `playwright.config.*` or `cypress.config.*`).
|
|
48
|
+
|
|
49
|
+
**Red Team** (opus, adversarial sweep categories: regression-around-fix + original-bug-variants covering context-meter hook and PostToolUse hook paths): verdict **GRUDGING PASS** — 5 additional claude-worker spawn sites found and patched in this same release; no untagged claude-worker spawn paths remain.
|
|
50
|
+
|
|
51
|
+
**Doc ripple**: `.gsd-t/contracts/event-schema-contract.md` new "Env-Var Fallbacks (v3.12.14)" section with flag/env/caller table; `.gsd-t/contracts/headless-default-contract.md` new "Worker Env Propagation (v3.12.14)" section; `.gsd-t/contracts/unattended-supervisor-contract.md` §14b v1.2.0 Worker Env Propagation + version history entry.
|
|
52
|
+
|
|
5
53
|
## [3.12.13] - 2026-04-17
|
|
6
54
|
|
|
7
55
|
### Fixed — `/` Prefix Strip Sitewide
|
package/bin/gsd-t-unattended.cjs
CHANGED
|
@@ -1087,8 +1087,10 @@ function _appendTokenLog(projectDir, entry) {
|
|
|
1087
1087
|
const note = entry.exitCode === 0
|
|
1088
1088
|
? `supervisor iter=${entry.iter}: ok`
|
|
1089
1089
|
: `supervisor iter=${entry.iter}: exit ${entry.exitCode}`;
|
|
1090
|
+
// v3.12.14: prefer env-var model over the hardcoded "unknown" placeholder.
|
|
1091
|
+
const model = process.env.GSD_T_MODEL || "unknown";
|
|
1090
1092
|
const row =
|
|
1091
|
-
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | supervisor-iter-${entry.iter} |
|
|
1093
|
+
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | supervisor-iter-${entry.iter} | ${model} | ${entry.durationS}s | ${note} | - | - | unknown |\n`;
|
|
1092
1094
|
const gsdtDir = path.join(projectDir, ".gsd-t");
|
|
1093
1095
|
if (!fs.existsSync(gsdtDir)) fs.mkdirSync(gsdtDir, { recursive: true });
|
|
1094
1096
|
if (!fs.existsSync(logPath)) {
|
|
@@ -1119,15 +1121,23 @@ function _appendTokenLog(projectDir, entry) {
|
|
|
1119
1121
|
*/
|
|
1120
1122
|
function _spawnWorker(state, opts) {
|
|
1121
1123
|
const bin = (state && state.claudeBin) || resolveClaudePath();
|
|
1122
|
-
// Inject command/phase so event-stream tool_call
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1124
|
+
// Inject command/phase/trace/model/project-dir so event-stream tool_call
|
|
1125
|
+
// entries (writer CLI + heartbeat hook) are tagged in worker contexts
|
|
1126
|
+
// (Fix 2, v3.12.12; trace/model/project-dir added v3.12.14 for the
|
|
1127
|
+
// null-telemetry regression fix). Supervisor always runs gsd-t-resume
|
|
1128
|
+
// workers; phase is inferred from state when available. Trace/model flow
|
|
1129
|
+
// through from parent process.env when set.
|
|
1125
1130
|
const workerEnv = {
|
|
1126
1131
|
...process.env,
|
|
1127
1132
|
GSD_T_UNATTENDED_WORKER: "1",
|
|
1128
1133
|
GSD_T_COMMAND: "gsd-t-resume",
|
|
1129
1134
|
GSD_T_PHASE: (state && state.phase) || "execute",
|
|
1135
|
+
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || opts.cwd || state.projectDir,
|
|
1130
1136
|
};
|
|
1137
|
+
if (state && state.traceId) workerEnv.GSD_T_TRACE_ID = state.traceId;
|
|
1138
|
+
else if (process.env.GSD_T_TRACE_ID) workerEnv.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
1139
|
+
if (state && state.model) workerEnv.GSD_T_MODEL = state.model;
|
|
1140
|
+
else if (process.env.GSD_T_MODEL) workerEnv.GSD_T_MODEL = process.env.GSD_T_MODEL;
|
|
1131
1141
|
const res = platformSpawnWorker(opts.cwd, opts.timeout, {
|
|
1132
1142
|
bin,
|
|
1133
1143
|
args: [
|
package/bin/gsd-t.js
CHANGED
|
@@ -2743,8 +2743,10 @@ function appendHeadlessTokenLog(projectDir, entry) {
|
|
|
2743
2743
|
try {
|
|
2744
2744
|
const logPath = path.join(projectDir, ".gsd-t", "token-log.md");
|
|
2745
2745
|
const note = entry.exitCode === 0 ? "headless exec: ok" : `headless exec: exit ${entry.exitCode}`;
|
|
2746
|
+
// v3.12.14: prefer env-var model over hardcoded "unknown".
|
|
2747
|
+
const model = process.env.GSD_T_MODEL || "unknown";
|
|
2746
2748
|
const row =
|
|
2747
|
-
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | headless |
|
|
2749
|
+
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | headless | ${model} | ${entry.durationS}s | ${note} | - | - | unknown |\n`;
|
|
2748
2750
|
const gsdtDir = path.join(projectDir, ".gsd-t");
|
|
2749
2751
|
if (!fs.existsSync(gsdtDir)) fs.mkdirSync(gsdtDir, { recursive: true });
|
|
2750
2752
|
if (!fs.existsSync(logPath)) {
|
|
@@ -2879,12 +2881,17 @@ function doHeadlessExec(command, cmdArgs, flags) {
|
|
|
2879
2881
|
let output = "";
|
|
2880
2882
|
let processExitCode = 0;
|
|
2881
2883
|
|
|
2882
|
-
// Inject command/phase env vars so worker
|
|
2883
|
-
// (
|
|
2884
|
+
// Inject command/phase/trace/model/project-dir env vars so worker
|
|
2885
|
+
// event-stream entries (writer CLI + heartbeat hook) are tagged in the
|
|
2886
|
+
// child's context (Fix 2, v3.12.12; trace/model/project-dir added v3.12.14
|
|
2887
|
+
// for the null-telemetry regression fix).
|
|
2884
2888
|
const workerEnv = Object.assign({}, process.env, {
|
|
2885
2889
|
GSD_T_COMMAND: `gsd-t-${command}`,
|
|
2886
2890
|
GSD_T_PHASE: process.env.GSD_T_PHASE || "execute",
|
|
2891
|
+
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || process.cwd(),
|
|
2887
2892
|
});
|
|
2893
|
+
if (process.env.GSD_T_TRACE_ID) workerEnv.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
2894
|
+
if (process.env.GSD_T_MODEL) workerEnv.GSD_T_MODEL = process.env.GSD_T_MODEL;
|
|
2888
2895
|
|
|
2889
2896
|
try {
|
|
2890
2897
|
const result = execFileSync("claude", ["-p", "--dangerously-skip-permissions", prompt], {
|
|
@@ -3169,9 +3176,22 @@ function getEscalationModel(iteration) {
|
|
|
3169
3176
|
*/
|
|
3170
3177
|
function spawnClaudeSession(prompt, model) {
|
|
3171
3178
|
try {
|
|
3179
|
+
// v3.12.14: propagate GSD_T_* env vars so the worker's heartbeat hook +
|
|
3180
|
+
// event-writer entries are tagged with the parent command/phase/trace and
|
|
3181
|
+
// this session's model. Without these, tool_call events from the debug
|
|
3182
|
+
// worker appear as command=null/phase=null.
|
|
3183
|
+
const env = Object.assign({}, process.env, {
|
|
3184
|
+
GSD_T_COMMAND: process.env.GSD_T_COMMAND || "gsd-t-debug",
|
|
3185
|
+
GSD_T_PHASE: process.env.GSD_T_PHASE || "debug",
|
|
3186
|
+
GSD_T_MODEL: model || process.env.GSD_T_MODEL || null,
|
|
3187
|
+
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || process.cwd(),
|
|
3188
|
+
});
|
|
3189
|
+
if (env.GSD_T_MODEL === null) delete env.GSD_T_MODEL;
|
|
3190
|
+
if (process.env.GSD_T_TRACE_ID) env.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
3172
3191
|
return execFileSync("claude", ["-p", prompt, "--model", model], {
|
|
3173
3192
|
encoding: "utf8", timeout: 300000,
|
|
3174
3193
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3194
|
+
env,
|
|
3175
3195
|
});
|
|
3176
3196
|
} catch (e) {
|
|
3177
3197
|
return (e.stdout || "") + (e.stderr || "") || null;
|
|
@@ -3208,8 +3228,17 @@ function runLedgerCompaction(projectDir, jsonMode) {
|
|
|
3208
3228
|
JSON.stringify(entries, null, 2);
|
|
3209
3229
|
let summary = "Compacted — see previous entries.";
|
|
3210
3230
|
try {
|
|
3231
|
+
// v3.12.14: propagate GSD_T_* env for telemetry tagging.
|
|
3232
|
+
const env = Object.assign({}, process.env, {
|
|
3233
|
+
GSD_T_COMMAND: process.env.GSD_T_COMMAND || "gsd-t-debug",
|
|
3234
|
+
GSD_T_PHASE: process.env.GSD_T_PHASE || "debug",
|
|
3235
|
+
GSD_T_MODEL: "haiku",
|
|
3236
|
+
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || projectDir,
|
|
3237
|
+
});
|
|
3238
|
+
if (process.env.GSD_T_TRACE_ID) env.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
3211
3239
|
const out = execFileSync("claude", ["-p", compactPrompt, "--model", "haiku"], {
|
|
3212
3240
|
encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"],
|
|
3241
|
+
env,
|
|
3213
3242
|
});
|
|
3214
3243
|
summary = (out || "").trim() || summary;
|
|
3215
3244
|
} catch (e) {
|
|
@@ -135,12 +135,27 @@ function autoSpawnHeadless(opts) {
|
|
|
135
135
|
const gsdtCli = path.join(projectDir, "bin", "gsd-t.js");
|
|
136
136
|
const childArgs = [gsdtCli, "headless", stripGsdtPrefix(command), ...args, "--log"];
|
|
137
137
|
|
|
138
|
-
// Inject command/phase into worker env so event-stream entries
|
|
139
|
-
// (
|
|
138
|
+
// Inject command/phase/trace/model into worker env so event-stream entries
|
|
139
|
+
// (both the writer CLI and the heartbeat PostToolUse hook) are tagged in
|
|
140
|
+
// the child's context (Fix 2, v3.12.12; trace/model/project-dir added
|
|
141
|
+
// v3.12.14 to close the null-telemetry regression).
|
|
142
|
+
//
|
|
143
|
+
// GSD_T_PHASE defaults to "execute" for primary spawns.
|
|
144
|
+
// GSD_T_TRACE_ID / GSD_T_MODEL are inherited from parent env if set;
|
|
145
|
+
// parents (orchestrator, command files) should set them before spawning.
|
|
146
|
+
// GSD_T_PROJECT_DIR gives the writer a stable target when the child cwd
|
|
147
|
+
// drifts (e.g., temp dirs in subagents).
|
|
140
148
|
const workerEnv = Object.assign({}, process.env, {
|
|
141
149
|
GSD_T_COMMAND: command,
|
|
142
150
|
GSD_T_PHASE: process.env.GSD_T_PHASE || "execute",
|
|
151
|
+
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || projectDir,
|
|
143
152
|
});
|
|
153
|
+
if (process.env.GSD_T_TRACE_ID) {
|
|
154
|
+
workerEnv.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
155
|
+
}
|
|
156
|
+
if (process.env.GSD_T_MODEL) {
|
|
157
|
+
workerEnv.GSD_T_MODEL = process.env.GSD_T_MODEL;
|
|
158
|
+
}
|
|
144
159
|
|
|
145
160
|
const child = spawn("node", childArgs, {
|
|
146
161
|
cwd: projectDir,
|
|
@@ -392,8 +407,12 @@ function appendTokenLog(projectDir, entry) {
|
|
|
392
407
|
try {
|
|
393
408
|
const logPath = path.join(projectDir, ".gsd-t", "token-log.md");
|
|
394
409
|
const note = entry.exitCode === 0 ? "headless spawn: ok" : `headless spawn: exit ${entry.exitCode}`;
|
|
410
|
+
// v3.12.14: prefer env-var model over the old hardcoded "unknown". The
|
|
411
|
+
// parent session should have set GSD_T_MODEL before invoking spawn; fall
|
|
412
|
+
// back to "unknown" if not (graceful).
|
|
413
|
+
const model = process.env.GSD_T_MODEL || "unknown";
|
|
395
414
|
const row =
|
|
396
|
-
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | headless |
|
|
415
|
+
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | headless | ${model} | ${entry.durationS}s | ${note} | - | - | unknown |\n`;
|
|
397
416
|
if (!fs.existsSync(logPath)) {
|
|
398
417
|
// Create with header
|
|
399
418
|
ensureDir(path.dirname(logPath));
|
|
@@ -135,11 +135,22 @@ function autoSpawnHeadless(opts) {
|
|
|
135
135
|
const gsdtCli = path.join(projectDir, "bin", "gsd-t.js");
|
|
136
136
|
const childArgs = [gsdtCli, "headless", stripGsdtPrefix(command), ...args, "--log"];
|
|
137
137
|
|
|
138
|
+
// v3.12.14 — env-var propagation for telemetry tagging (mirror of the
|
|
139
|
+
// authoritative .cjs variant). PRODUCTION consumers import the .cjs; this
|
|
140
|
+
// .js copy is retained only to keep the legacy test green.
|
|
141
|
+
const workerEnv = Object.assign({}, process.env, {
|
|
142
|
+
GSD_T_COMMAND: command,
|
|
143
|
+
GSD_T_PHASE: process.env.GSD_T_PHASE || "execute",
|
|
144
|
+
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || projectDir,
|
|
145
|
+
});
|
|
146
|
+
if (process.env.GSD_T_TRACE_ID) workerEnv.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
147
|
+
if (process.env.GSD_T_MODEL) workerEnv.GSD_T_MODEL = process.env.GSD_T_MODEL;
|
|
148
|
+
|
|
138
149
|
const child = spawn("node", childArgs, {
|
|
139
150
|
cwd: projectDir,
|
|
140
151
|
detached: true,
|
|
141
152
|
stdio: ["ignore", logFd, logFd],
|
|
142
|
-
env:
|
|
153
|
+
env: workerEnv,
|
|
143
154
|
});
|
|
144
155
|
|
|
145
156
|
child.unref();
|
package/bin/orchestrator.js
CHANGED
|
@@ -82,6 +82,25 @@ function isPortInUse(port) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Build the env block for orchestrator-spawned `claude -p` sessions.
|
|
87
|
+
* v3.12.14 — propagates GSD_T_COMMAND/PHASE/TRACE_ID/MODEL/PROJECT_DIR so the
|
|
88
|
+
* worker's heartbeat hook and event-writer entries are tagged. Accepts the
|
|
89
|
+
* opts object (for `label`) that callers already pass and defaults missing
|
|
90
|
+
* fields from the parent process env.
|
|
91
|
+
*/
|
|
92
|
+
function _buildOrchestratorEnv(opts, projectDir) {
|
|
93
|
+
opts = opts || {};
|
|
94
|
+
const env = Object.assign({}, process.env, {
|
|
95
|
+
GSD_T_COMMAND: process.env.GSD_T_COMMAND || "gsd-t-orchestrator",
|
|
96
|
+
GSD_T_PHASE: process.env.GSD_T_PHASE || opts.label || "orchestrator",
|
|
97
|
+
GSD_T_PROJECT_DIR: process.env.GSD_T_PROJECT_DIR || projectDir,
|
|
98
|
+
});
|
|
99
|
+
if (process.env.GSD_T_TRACE_ID) env.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
100
|
+
if (process.env.GSD_T_MODEL) env.GSD_T_MODEL = process.env.GSD_T_MODEL;
|
|
101
|
+
return env;
|
|
102
|
+
}
|
|
103
|
+
|
|
85
104
|
// ─── Orchestrator Class ────────────────────────────────────────────────────
|
|
86
105
|
|
|
87
106
|
class Orchestrator {
|
|
@@ -212,11 +231,15 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
212
231
|
const signalFile = path.join(this.getReviewDir(projectDir), `_sync-done-${Date.now()}.json`);
|
|
213
232
|
let result = { output: "", exitCode: 1, duration: 0 };
|
|
214
233
|
|
|
234
|
+
// v3.12.14: propagate GSD_T_* env so the worker's heartbeat hook and
|
|
235
|
+
// event-writer entries are tagged. opts.label → phase/command hints.
|
|
236
|
+
const env = _buildOrchestratorEnv(opts, projectDir);
|
|
215
237
|
const child = execFile("claude", args, {
|
|
216
238
|
encoding: "utf8",
|
|
217
239
|
timeout: effectiveTimeout,
|
|
218
240
|
cwd: projectDir,
|
|
219
241
|
maxBuffer: 10 * 1024 * 1024,
|
|
242
|
+
env,
|
|
220
243
|
}, (err, stdout, stderr) => {
|
|
221
244
|
this.untrackChild(child.pid);
|
|
222
245
|
const raw = err ? ((err.stdout || "") + (err.stderr || "")) : (stdout || "");
|
|
@@ -264,12 +287,15 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
|
|
|
264
287
|
);
|
|
265
288
|
}
|
|
266
289
|
|
|
290
|
+
// v3.12.14: propagate GSD_T_* env for telemetry tagging in the child.
|
|
291
|
+
const env = _buildOrchestratorEnv(opts, projectDir);
|
|
267
292
|
return new Promise((resolve) => {
|
|
268
293
|
const child = execFile("claude", args, {
|
|
269
294
|
encoding: "utf8",
|
|
270
295
|
timeout: timeout || 120_000,
|
|
271
296
|
cwd: projectDir,
|
|
272
297
|
maxBuffer: 10 * 1024 * 1024,
|
|
298
|
+
env,
|
|
273
299
|
}, (err, stdout, stderr) => {
|
|
274
300
|
this.untrackChild(child.pid);
|
|
275
301
|
const raw = err ? ((err.stdout || "") + (err.stderr || "")) : (stdout || "");
|
|
@@ -351,6 +351,14 @@ Reset `.gsd-t/` for next milestone:
|
|
|
351
351
|
4. Clear `.gsd-t/impact-report.md`, `.gsd-t/test-coverage.md`
|
|
352
352
|
5. Update `.gsd-t/progress.md`:
|
|
353
353
|
|
|
354
|
+
**CRITICAL — Decision Log Trim**: This trim happens AFTER Step 4's archive copy of `.gsd-t/progress.md` is written. The archive snapshot at `.gsd-t/milestones/{name}-{date}/progress.md` is the source-of-truth for full history. The live Decision Log is reset to the just-completed milestone's entries only — everything dated before the milestone's start date is deleted from the live file (it remains in the archive). This prevents the live progress.md from growing without bound.
|
|
355
|
+
|
|
356
|
+
Steps to apply the trim:
|
|
357
|
+
- Identify the just-completed milestone's start date (typically the `[milestone-defined]` entry for this milestone)
|
|
358
|
+
- Delete all Decision Log entries dated BEFORE that start date from the live `.gsd-t/progress.md`
|
|
359
|
+
- Keep entries from the start date onward (they cover the milestone arc + the completion entry you're about to add)
|
|
360
|
+
- Prepend the pointer line shown below
|
|
361
|
+
|
|
354
362
|
```markdown
|
|
355
363
|
# GSD-T Progress
|
|
356
364
|
|
|
@@ -365,8 +373,11 @@ None — ready for next milestone
|
|
|
365
373
|
| {previous} | {version} | {date} | v{version} |
|
|
366
374
|
|
|
367
375
|
## Decision Log
|
|
376
|
+
|
|
377
|
+
> Prior decision log entries preserved in `.gsd-t/milestones/*/progress.md` — see archive snapshots for pre-{next-milestone-name} history.
|
|
378
|
+
|
|
368
379
|
- {date}: [success] Milestone "{name}" completed — {summary of what was built}. v{version}
|
|
369
|
-
{
|
|
380
|
+
{Trim: delete all decision-log entries older than the just-completed milestone's start date. Those entries are preserved in the milestone archive created in Step 4. Keep only the completion entry above plus any entries logged on or after the cutoff — typically the live log is near-empty when the next milestone begins.}
|
|
370
381
|
```
|
|
371
382
|
|
|
372
383
|
## Step 8: Update README.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.15",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -685,12 +685,22 @@ const server = http.createServer((req, res) => {
|
|
|
685
685
|
"Access-Control-Allow-Origin": "*",
|
|
686
686
|
});
|
|
687
687
|
|
|
688
|
+
// v3.12.14: propagate GSD_T_* env for telemetry tagging in the worker.
|
|
689
|
+
const childEnv = {
|
|
690
|
+
...process.env,
|
|
691
|
+
NO_COLOR: "1",
|
|
692
|
+
GSD_T_COMMAND: process.env.GSD_T_COMMAND || "gsd-t-design-review",
|
|
693
|
+
GSD_T_PHASE: process.env.GSD_T_PHASE || "design-review",
|
|
694
|
+
GSD_T_MODEL: model || process.env.GSD_T_MODEL || "unknown",
|
|
695
|
+
};
|
|
696
|
+
if (process.env.GSD_T_TRACE_ID) childEnv.GSD_T_TRACE_ID = process.env.GSD_T_TRACE_ID;
|
|
697
|
+
if (process.env.GSD_T_PROJECT_DIR) childEnv.GSD_T_PROJECT_DIR = process.env.GSD_T_PROJECT_DIR;
|
|
688
698
|
const claude = spawn("claude", [
|
|
689
699
|
"-p", fullPrompt,
|
|
690
700
|
"--model", model,
|
|
691
701
|
"--output-format", "stream-json",
|
|
692
702
|
"--verbose",
|
|
693
|
-
], { env:
|
|
703
|
+
], { env: childEnv });
|
|
694
704
|
|
|
695
705
|
let buf = "";
|
|
696
706
|
let textSent = 0;
|
|
@@ -72,10 +72,13 @@ function nullify(val) {
|
|
|
72
72
|
|
|
73
73
|
function buildEvent(args) {
|
|
74
74
|
// Env-var fallbacks: workers spawned by supervisor/headless-auto-spawn inherit
|
|
75
|
-
// GSD_T_COMMAND
|
|
76
|
-
// worker doesn't pass --command/--phase
|
|
75
|
+
// GSD_T_COMMAND, GSD_T_PHASE, GSD_T_TRACE_ID, GSD_T_MODEL so tool_call events
|
|
76
|
+
// are tagged even when the worker doesn't pass --command/--phase/--trace-id/
|
|
77
|
+
// --model explicitly (Fix 2, v3.12.12; trace/model added v3.12.14).
|
|
77
78
|
const envCommand = process.env.GSD_T_COMMAND || null;
|
|
78
79
|
const envPhase = process.env.GSD_T_PHASE || null;
|
|
80
|
+
const envTraceId = process.env.GSD_T_TRACE_ID || null;
|
|
81
|
+
const envModel = process.env.GSD_T_MODEL || null;
|
|
79
82
|
|
|
80
83
|
return {
|
|
81
84
|
ts: new Date().toISOString(),
|
|
@@ -84,10 +87,10 @@ function buildEvent(args) {
|
|
|
84
87
|
phase: nullify(args["phase"]) || envPhase,
|
|
85
88
|
agent_id: nullify(args["agent-id"]),
|
|
86
89
|
parent_agent_id: nullify(args["parent-id"]),
|
|
87
|
-
trace_id: nullify(args["trace-id"]),
|
|
90
|
+
trace_id: nullify(args["trace-id"]) || envTraceId,
|
|
88
91
|
reasoning: nullify(args["reasoning"]),
|
|
89
92
|
outcome: nullify(args["outcome"]),
|
|
90
|
-
model: nullify(args["model"]),
|
|
93
|
+
model: nullify(args["model"]) || envModel,
|
|
91
94
|
};
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -187,7 +187,17 @@ function shortPath(p) {
|
|
|
187
187
|
|
|
188
188
|
function buildEventStreamEntry(hook) {
|
|
189
189
|
const ts = new Date().toISOString();
|
|
190
|
-
|
|
190
|
+
// Env-var fallbacks: supervisor/headless spawned workers inherit these on
|
|
191
|
+
// their process.env so PostToolUse tool_call events are tagged instead of
|
|
192
|
+
// landing as null/null/null in the stream (v3.12.14 telemetry fix — the
|
|
193
|
+
// writer had these fallbacks since v3.12.12 but the heartbeat hook — source
|
|
194
|
+
// of ~90% of tool_call events — hardcoded nulls).
|
|
195
|
+
const base = {
|
|
196
|
+
ts,
|
|
197
|
+
command: process.env.GSD_T_COMMAND || null,
|
|
198
|
+
phase: process.env.GSD_T_PHASE || null,
|
|
199
|
+
trace_id: process.env.GSD_T_TRACE_ID || null,
|
|
200
|
+
};
|
|
191
201
|
const n = hook.hook_event_name;
|
|
192
202
|
if (n === "SessionStart") {
|
|
193
203
|
return { ...base, event_type: "session_start",
|