@tekyzinc/gsd-t 3.13.10 → 3.13.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 +46 -0
- package/bin/gsd-t-unattended.cjs +32 -1
- package/bin/gsd-t.js +43 -2
- package/commands/gsd-t-unattended-watch.md +9 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [3.13.12] - 2026-04-17
|
|
6
|
+
|
|
7
|
+
### Fixed — Project-local `bin/gsd-t.js` crash on missing `debug-ledger.js` + self-heal sweep
|
|
8
|
+
|
|
9
|
+
A bee-poc relaunch after the v3.13.11 fix still crashed with `MODULE_NOT_FOUND: Cannot find module './debug-ledger.js'` — but the crash came from `bin/gsd-t.js` itself, not from the v3.13.11 supervisor code. Root cause: an older version of `copyBinToolsToProject` copied `bin/gsd-t.js` into registered projects as part of a now-deprecated whitelist. The current `PROJECT_BIN_TOOLS` whitelist (10 `.cjs` files) does not include `gsd-t.js` or its sibling `debug-ledger.js`, so `update-all` stopped maintaining both — but the stale copy persisted in project `bin/` directories, and `bin/gsd-t.js` had a hard require on `./debug-ledger.js` at the top of the file. Any invocation of the stale project-local copy crashed before the first line of real work. bee-poc had the 130 KB stale `bin/gsd-t.js` from this lineage.
|
|
10
|
+
|
|
11
|
+
**Layer 1 — defensive require** (`bin/gsd-t.js:23`):
|
|
12
|
+
The top-level `require('./debug-ledger.js')` is now wrapped in `try/catch` and falls back to a no-op stub exporting every function the real module exports (`readLedger`, `appendEntry`, `getLedgerStats`, `clearLedger`, `compactLedger`, `generateAntiRepetitionPreamble`). Projects with stale copies no longer crash; they degrade to debug-ledger-disabled behavior until `update-all` sweeps the stray away.
|
|
13
|
+
|
|
14
|
+
**Layer 2 — deprecated-stray sweep** (`copyBinToolsToProject`):
|
|
15
|
+
New `DEPRECATED_BIN_STRAYS = ["gsd-t.js"]` list is swept after the normal copy loop. For each entry: if the project has the stray AND its bytes match the source copy in this package, delete it (proves it's an installer artifact, not a user file that happens to share the name). User-owned files with different content are left untouched. Log line: `{project} — cleaned up N stray bin file(s)`. On the next `gsd-t update-all` after v3.13.12 installs, every project that picked up a stale `bin/gsd-t.js` self-heals; subsequent invocations fall through to the global install.
|
|
16
|
+
|
|
17
|
+
**Files**:
|
|
18
|
+
- `bin/gsd-t.js` — defensive require with full no-op stub; `DEPRECATED_BIN_STRAYS` list; post-copy sweep loop; `copyBinToolsToProject` returns `true` when either a copy happened or a stray was cleaned.
|
|
19
|
+
- `test/bin-gsd-t-resilience.test.js` — 3 new tests: (a) loading `bin/gsd-t.js --help` without `debug-ledger.js` does not emit `MODULE_NOT_FOUND`; (b) byte-matching stray is deleted; (c) user-owned file (byte-divergent) is preserved.
|
|
20
|
+
|
|
21
|
+
**Tests**: 1238/1238 pass (was 1235; +3 new assertions). E2E: N/A (no playwright.config.*).
|
|
22
|
+
|
|
23
|
+
**Impact**: installed projects that inherited a stale `bin/gsd-t.js` from older `update-all` passes will self-heal on the next `gsd-t update-all` after installing v3.13.12. bee-poc's 130 KB stale copy is removed on its next pass, after which any invocation path that had been hitting the project-local copy falls through to the global install — no more divergent vendoring.
|
|
24
|
+
|
|
25
|
+
## [3.13.11] - 2026-04-17
|
|
26
|
+
|
|
27
|
+
### Fixed — Unattended supervisor reliability triple-fix (bee-poc 15-min hang fallout)
|
|
28
|
+
|
|
29
|
+
A real bee-poc supervisor relay hung for 15+ minutes on v3.12.15 (pid 70897). Three independent defects surfaced from that incident and are fixed together in this patch. The root cause of the hang itself — a 1-hour worker timeout on the deployed v3.12.15 package — is finally resolved by shipping v3.13.10's D4 work to npm; the two other bugs are fixes for contract-boundary and cosmetic issues the hang exposed.
|
|
30
|
+
|
|
31
|
+
**Bug 1 (P0) — supervisor watchdog visibility on timeout**:
|
|
32
|
+
The spawnSync `timeout` option kills a hung worker after `DEFAULT_WORKER_TIMEOUT_MS` (270 s in v3.13.10+) and maps the result to contract exit code 124, but the event was not legibly surfaced in `run.log`. Operators tailing the log saw an empty iter block with no indication that the watchdog had fired. `runMainLoop` now writes a deterministic `[worker_timeout] iter=N budget=Nms elapsed=Nms` line to `run.log` immediately before the regular iter trailer, so timeout-induced cache misses are self-documenting. The existing `writeState` call still commits `lastExit=124` + a fresh `lastTick` so `/gsd-t-unattended-watch` sees a heartbeat post-timeout.
|
|
33
|
+
|
|
34
|
+
**Note on the deployed-version aspect**: the 1-hour → 270 s worker-timeout reduction shipped in v3.13.10 on GitHub but v3.13.10 was never published to npm (progress.md was in "pending publish" state when the bee-poc run started). bee-poc was running against the installed v3.12.15, which still had `DEFAULT_WORKER_TIMEOUT_MS = 3600000`. Publishing v3.13.11 closes both issues — the timeout reduction reaches bee-poc (and every downstream project) and the new diagnostic line makes future watchdog firings visible.
|
|
35
|
+
|
|
36
|
+
**Bug 2 (P0) — worker cwd invariant**:
|
|
37
|
+
run.log from the bee-poc hang showed a `Shell cwd was reset to /Users/david/projects/GSD-T` line mid-iter — the worker's Bash shell had escaped bee-poc's project directory, and subsequent tool calls silently targeted the wrong repo. `_spawnWorker` already passes `cwd: projectDir` to `platformSpawnWorker` and sets `GSD_T_PROJECT_DIR` on the worker env (correct baseline), but the worker itself had no instruction to re-assert that invariant. The worker prompt now carries an explicit `# CWD Invariant` section that instructs the worker to (a) run `[ "$(pwd)" = "$GSD_T_PROJECT_DIR" ] || cd "$GSD_T_PROJECT_DIR"` as its first Bash call, and (b) scope any directory change inside a subshell (`( cd other && cmd )`) rather than using bare top-level `cd`.
|
|
38
|
+
|
|
39
|
+
**Bug 3 (P2) — IS_STALE determinism**:
|
|
40
|
+
`/gsd-t-unattended-watch` is run by the haiku model and the "tick age > 540 s → append ⚠️ stale" threshold lived in the Step 6a rendering prose. Haiku would occasionally apply the stale flag to ticks in the 330–540 s band by misreading the prose. The threshold math now lives entirely inside Step 2's `node -e` block as a boolean emission (`IS_STALE = tickAgeMs !== null && tickAgeMs > 540000`), and Step 6a just reads the flag. Boundary cases: 539 s = false, 540 s = false (strict greater-than), 541 s = true.
|
|
41
|
+
|
|
42
|
+
**Files**:
|
|
43
|
+
- `bin/gsd-t-unattended.cjs` — worker_timeout run.log append; CWD Invariant section in `_spawnWorker` prompt.
|
|
44
|
+
- `commands/gsd-t-unattended-watch.md` — Step 2 IS_STALE computation + emission; Step 6a reader-only rendering; Notes section updated.
|
|
45
|
+
- `test/unattended-triple-fix-v3-13-11.test.js` — 8 new tests (3 Bug 1 + 3 Bug 2 + 3 Bug 3 — one boundary-math test covers three points, bringing the practical count to 8 assertions across 8 it-blocks, of which 3 exercise the Bug 3 boundaries).
|
|
46
|
+
|
|
47
|
+
**Tests**: 1235/1235 pass (was 1227; +8 new assertions). E2E: N/A (no playwright.config.*).
|
|
48
|
+
|
|
49
|
+
**Impact**: bee-poc-class hangs are self-recoverable in v3.13.11 — a hung worker is bounded at 270 s by the watchdog, the timeout is now visible in run.log, cwd drift is caught by the worker itself on entry, and `/gsd-t-unattended-watch` no longer produces spurious stale warnings under the threshold.
|
|
50
|
+
|
|
5
51
|
## [3.13.10] - 2026-04-17
|
|
6
52
|
|
|
7
53
|
### Added — M39: Fast Unattended + Universal Watch-Progress Tree
|
package/bin/gsd-t-unattended.cjs
CHANGED
|
@@ -975,8 +975,22 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
975
975
|
exitCode = mapHeadlessExitCode(res.status, stdout + "\n" + stderr);
|
|
976
976
|
}
|
|
977
977
|
|
|
978
|
+
// v3.13.11 Bug 1: when the watchdog fires (spawnSync timeout SIGTERM or
|
|
979
|
+
// platform.spawnWorker timedOut flag), make the event explicit in run.log
|
|
980
|
+
// so operators can see WHICH iteration timed out without inferring from
|
|
981
|
+
// exit codes. The marker is prepended to stdout and written in the single
|
|
982
|
+
// per-iter run.log append (no duplicate header).
|
|
983
|
+
let loggedStdout = stdout;
|
|
984
|
+
if (exitCode === 124) {
|
|
985
|
+
const marker =
|
|
986
|
+
`[worker_timeout] iter=${state.iter} budget=${workerTimeoutMs}ms ` +
|
|
987
|
+
`elapsed=${elapsedMs}ms — watchdog SIGTERM delivered, ` +
|
|
988
|
+
`supervisor continues relay per contract §16.\n`;
|
|
989
|
+
loggedStdout = marker + (stdout || "");
|
|
990
|
+
}
|
|
991
|
+
|
|
978
992
|
// Append the full worker output to run.log (never truncate).
|
|
979
|
-
_appendRunLog(dir, state.iter, workerEnd, exitCode,
|
|
993
|
+
_appendRunLog(dir, state.iter, workerEnd, exitCode, loggedStdout, stderr);
|
|
980
994
|
|
|
981
995
|
// Append to token-log.md (Fix 1, v3.12.12) — supervisor workers write rows
|
|
982
996
|
// so the log captures headless/unattended activity, not just interactive spawns.
|
|
@@ -1192,6 +1206,23 @@ function _spawnWorker(state, opts) {
|
|
|
1192
1206
|
[
|
|
1193
1207
|
"You are an unattended worker iteration. CRITICAL: Do NOT check supervisor.pid, do NOT auto-reattach to a watch loop, do NOT schedule any ScheduleWakeup. You ARE the worker spawned by the supervisor. Skip Step 0 (auto-reattach) entirely and go directly to Step 0.1.",
|
|
1194
1208
|
"",
|
|
1209
|
+
"# CWD Invariant (v3.13.11 Bug 2)",
|
|
1210
|
+
"",
|
|
1211
|
+
"Before any other work, assert your current working directory matches the",
|
|
1212
|
+
"supervisor's project directory. A worker that silently drifts to a",
|
|
1213
|
+
"different repo will commit to the wrong tree and corrupt state.json.",
|
|
1214
|
+
"",
|
|
1215
|
+
"First Bash call this turn (mandatory):",
|
|
1216
|
+
"",
|
|
1217
|
+
" [ \"$(pwd)\" = \"$GSD_T_PROJECT_DIR\" ] || cd \"$GSD_T_PROJECT_DIR\"",
|
|
1218
|
+
" pwd # confirm",
|
|
1219
|
+
"",
|
|
1220
|
+
"Thereafter, scope any directory change inside a subshell so a `cd` in",
|
|
1221
|
+
"one Bash call cannot contaminate the next one:",
|
|
1222
|
+
"",
|
|
1223
|
+
" ( cd some/subdir && run-command ) # safe — subshell",
|
|
1224
|
+
" cd some/subdir && run-command # UNSAFE — leaks cwd",
|
|
1225
|
+
"",
|
|
1195
1226
|
"# Team Mode (Intra-Wave Parallelism)",
|
|
1196
1227
|
"",
|
|
1197
1228
|
"Before executing tasks for this iteration, read `.gsd-t/partition.md` to",
|
package/bin/gsd-t.js
CHANGED
|
@@ -20,7 +20,19 @@ const path = require("path");
|
|
|
20
20
|
const os = require("os");
|
|
21
21
|
const readline = require("readline");
|
|
22
22
|
const { execFileSync, spawn: cpSpawn } = require("child_process");
|
|
23
|
-
|
|
23
|
+
let debugLedger;
|
|
24
|
+
try {
|
|
25
|
+
debugLedger = require(path.join(__dirname, "debug-ledger.js"));
|
|
26
|
+
} catch (_) {
|
|
27
|
+
debugLedger = {
|
|
28
|
+
readLedger: () => [],
|
|
29
|
+
appendEntry: () => {},
|
|
30
|
+
getLedgerStats: () => ({ entryCount: 0, sizeBytes: 0, needsCompaction: false, failedHypotheses: [], passCount: 0, failCount: 0 }),
|
|
31
|
+
clearLedger: () => {},
|
|
32
|
+
compactLedger: () => {},
|
|
33
|
+
generateAntiRepetitionPreamble: () => "",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
24
36
|
|
|
25
37
|
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
26
38
|
|
|
@@ -2100,6 +2112,14 @@ const PROJECT_BIN_TOOLS = [
|
|
|
2100
2112
|
"handoff-lock.cjs", "headless-auto-spawn.cjs",
|
|
2101
2113
|
];
|
|
2102
2114
|
|
|
2115
|
+
// Files that older versions of this installer copied into project bin/ but
|
|
2116
|
+
// are no longer part of PROJECT_BIN_TOOLS. On each update-all pass, sweep the
|
|
2117
|
+
// project's bin/ and remove any stray file that still byte-matches the source
|
|
2118
|
+
// copy in this package — that proves it's an old installer artifact, not a
|
|
2119
|
+
// user's own file sharing the same name. User-owned files (byte-divergent)
|
|
2120
|
+
// are left alone.
|
|
2121
|
+
const DEPRECATED_BIN_STRAYS = ["gsd-t.js"];
|
|
2122
|
+
|
|
2103
2123
|
function copyBinToolsToProject(projectDir, projectName) {
|
|
2104
2124
|
const projectBinDir = path.join(projectDir, "bin");
|
|
2105
2125
|
if (!fs.existsSync(projectBinDir)) {
|
|
@@ -2134,11 +2154,31 @@ function copyBinToolsToProject(projectDir, projectName) {
|
|
|
2134
2154
|
}
|
|
2135
2155
|
}
|
|
2136
2156
|
}
|
|
2157
|
+
let cleaned = 0;
|
|
2158
|
+
for (const stray of DEPRECATED_BIN_STRAYS) {
|
|
2159
|
+
const strayPath = path.join(projectBinDir, stray);
|
|
2160
|
+
const srcPath = path.join(PKG_ROOT, "bin", stray);
|
|
2161
|
+
if (!fs.existsSync(strayPath)) continue;
|
|
2162
|
+
if (!fs.existsSync(srcPath)) continue;
|
|
2163
|
+
try {
|
|
2164
|
+
const strayContent = fs.readFileSync(strayPath, "utf8");
|
|
2165
|
+
const srcContent = fs.readFileSync(srcPath, "utf8");
|
|
2166
|
+
if (strayContent === srcContent) {
|
|
2167
|
+
fs.unlinkSync(strayPath);
|
|
2168
|
+
cleaned++;
|
|
2169
|
+
}
|
|
2170
|
+
} catch {
|
|
2171
|
+
// leave the file alone on any read error
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (cleaned > 0) {
|
|
2175
|
+
info(`${projectName} — cleaned up ${cleaned} stray bin file(s)`);
|
|
2176
|
+
}
|
|
2137
2177
|
if (copied > 0) {
|
|
2138
2178
|
info(`${projectName} — copied ${copied} bin tool(s)`);
|
|
2139
2179
|
return true;
|
|
2140
2180
|
}
|
|
2141
|
-
return
|
|
2181
|
+
return cleaned > 0;
|
|
2142
2182
|
}
|
|
2143
2183
|
|
|
2144
2184
|
// One-shot migration: roll the project's progress.md Decision Log into archive
|
|
@@ -3497,6 +3537,7 @@ module.exports = {
|
|
|
3497
3537
|
isNewerVersion,
|
|
3498
3538
|
ensureDir,
|
|
3499
3539
|
copyFile,
|
|
3540
|
+
copyBinToolsToProject,
|
|
3500
3541
|
hasPlaywright,
|
|
3501
3542
|
hasSwagger,
|
|
3502
3543
|
hasApi,
|
|
@@ -103,8 +103,15 @@ if (state) {
|
|
|
103
103
|
const lt = state.lastTick ? Date.parse(state.lastTick) : null;
|
|
104
104
|
const tickAgeMs = lt ? (Date.now() - lt) : null;
|
|
105
105
|
out('TICK_AGE_MS', tickAgeMs);
|
|
106
|
+
// v3.13.11 Bug 3: IS_STALE computed deterministically here (not in Haiku's
|
|
107
|
+
// rendering step) — stale iff tickAgeMs > 540000 (2× 270s tick cadence per
|
|
108
|
+
// unattended-supervisor-contract §3). Below threshold → false. At/above
|
|
109
|
+
// threshold → true. Renderer just reads this flag; no prose interpretation.
|
|
110
|
+
const IS_STALE = tickAgeMs !== null && tickAgeMs > 540000;
|
|
111
|
+
out('IS_STALE', IS_STALE);
|
|
106
112
|
} else {
|
|
107
113
|
out('STATUS', null);
|
|
114
|
+
out('IS_STALE', false);
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
// --- run.log tail (last ~2KB via fd seek, not full read) ---
|
|
@@ -312,7 +319,7 @@ Render two blocks: the workflow-progress header (existing) plus the M38 **event-
|
|
|
312
319
|
Format rules:
|
|
313
320
|
- One extra space after each emoji (per CLAUDE.md markdown table rules — preserves alignment in terminal views).
|
|
314
321
|
- Elapsed formatted as `{H}h{M}m` from `ELAPSED_MS`.
|
|
315
|
-
- Last-tick age formatted as `{S}s` or `{M}m{S}s`. If
|
|
322
|
+
- Last-tick age formatted as `{S}s` or `{M}m{S}s`. If `IS_STALE=true` (computed deterministically in Step 2 as `tickAgeMs > 540000`), append ` ⚠️ stale`. The renderer must NOT interpret the threshold itself — Step 2 is the source of truth; rendering just reads the IS_STALE flag verbatim.
|
|
316
323
|
- Tasks progress bar: `[████████░░░░] 8/12` — filled blocks proportional to done/total.
|
|
317
324
|
- Domain breakdown only shown if ≤6 domains (otherwise too noisy).
|
|
318
325
|
|
|
@@ -395,6 +402,6 @@ After the tool call, end the turn. Do NOT output a "Next Up" hint, do NOT contin
|
|
|
395
402
|
- **Terminal = STOP**: any terminal branch (3, 4, 5) ends the loop. The user relaunches via `/gsd-t-unattended` if needed.
|
|
396
403
|
- **Never spawn subagents**: this is a pure polling command — no Task, no TeamCreate, no observability logging block.
|
|
397
404
|
- **No branch guard, no pre-commit gate, no doc ripple**: this command does not modify any files.
|
|
398
|
-
- **Stale-tick tolerance**:
|
|
405
|
+
- **Stale-tick tolerance**: `IS_STALE=true` iff `tickAgeMs > 540000` (computed deterministically in Step 2). When stale, warn soft (`⚠️ stale`) but still reschedule — the supervisor may be mid-worker. The threshold math lives in Step 2's node block; rendering reads the flag verbatim.
|
|
399
406
|
|
|
400
407
|
$ARGUMENTS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.12",
|
|
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",
|