@tekyzinc/gsd-t 4.9.12 → 4.9.14

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 CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.9.14] - 2026-06-25 (workflow runCli retry — reliability patch)
6
+
7
+ ### Fixed — transient helper-call flakes no longer false-block workflows
8
+
9
+ `runCli` (the inline helper every `*.workflow.js` uses to run a GSD-T CLI command) delegates the call to a haiku helper agent, not a subprocess — so a return can transiently come back missing its parsed result, surfacing later as a cryptic `classify failed: no envelope` or a fail-closed gate block even though the CLI itself succeeded. Caught live during the M94 plan phase: the traceability gate passed when run directly (exit 0) but the workflow's confirm-call came back unparsed and failed closed.
10
+
11
+ - `runCli` now **retries once** when JSON was expected but no parsed result returned (covers both the throw path and a malformed return the loose schema let through). A real CLI failure that returned valid JSON (`ok:false` with a present envelope) is **not** retried — a true result, not a transient miss.
12
+ - Hardens every phase/wave/integrate/debug workflow's CLI calls (preflight, verify-gate, traceability, brief, build-coverage, test-data).
13
+
14
+ ## [4.9.13] - 2026-06-25 (M93 — Reader Contract every turn, retire the selective gate — patch)
15
+
16
+ ### Changed — concise output is now enforced on EVERY reply, not by pattern-matching
17
+
18
+ The deterministic brevity-guard Stop hook kept missing verbose replies: a gate that matches a hardcoded list of known-bad opener phrases can only ever be **selective** — every new phrasing slips through (whack-a-mole). Per the user, the fix is to stop detecting and always apply the standard.
19
+
20
+ - **Retired the brevity-guard Stop hook.** `gsd-t update-all` now *removes* it from any install that still has it.
21
+ - **Reader Contract injected every turn** by the `UserPromptSubmit` hook (`scripts/gsd-t-auto-route.js`) — applies to ALL reader-facing output in every project: assume the first draft is wordy and rewrite it tight; answer first (intent-first only when about to change code); gloss jargon; bullets over paragraphs. Includes generic before→after examples that teach the shape.
22
+ - This is wording-independent (no enumeration of bad phrasings) and universal (the standard is in front of the model each turn, not a backstop guessing which replies are bad).
23
+
5
24
  ## [4.9.12] - 2026-06-23 (M93 brevity-guard live-tuning — patch)
6
25
 
7
26
  ### Changed — brevity guard catches more, false-blocks less
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v4.9.12** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v4.9.14** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
4
4
 
5
5
  **Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
6
6
  **Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
package/bin/gsd-t.js CHANGED
@@ -395,48 +395,39 @@ function addHeartbeatHook(hooks, event, cmd) {
395
395
  return true;
396
396
  }
397
397
 
398
- // ─── Brevity Guard (M93) ────────────────────────────────────────────────────
399
- // A BLOCKING Stop hook (not async it must run synchronously to block a verbose
400
- // answer-mode reply). Enforces the Reader Contract: answer-first, no preamble,
401
- // jargon glossed. Fail-open by design (the script never blocks on error).
398
+ // ─── Reader Contract (M93) ──────────────────────────────────────────────────
399
+ // RETIRED the deterministic brevity-guard Stop hook: a pattern-matching gate can
400
+ // only ever be SELECTIVE (it catches the phrasings it enumerates and misses every
401
+ // new one the whack-a-mole the user hit). Replaced by the every-turn Reader
402
+ // Contract injected by scripts/gsd-t-auto-route.js (the UserPromptSubmit hook):
403
+ // the standard is put in front of the model each turn, applied to ALL replies,
404
+ // instead of a backstop guessing which ones are bad. This function now REMOVES
405
+ // the old Stop hook from any install that still has it.
402
406
 
403
407
  const BREVITY_GUARD_SCRIPT = "gsd-t-brevity-guard.js";
404
408
 
405
- function installBrevityGuard() {
406
- ensureDir(SCRIPTS_DIR);
407
- const src = path.join(PKG_SCRIPTS, BREVITY_GUARD_SCRIPT);
408
- const dest = path.join(SCRIPTS_DIR, BREVITY_GUARD_SCRIPT);
409
- if (!fs.existsSync(src)) { warn("Brevity-guard script not found in package — skipping"); return; }
410
-
411
- const srcContent = fs.readFileSync(src, "utf8");
412
- const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : "";
413
- if (normalizeEol(srcContent) !== normalizeEol(destContent)) {
414
- copyFile(src, dest, BREVITY_GUARD_SCRIPT);
415
- } else {
416
- info("Brevity-guard script unchanged");
417
- }
418
-
409
+ function uninstallBrevityGuardHook() {
419
410
  const parsed = readSettingsJson();
420
411
  if (parsed === null && fs.existsSync(SETTINGS_JSON)) {
421
- warn("settings.json has invalid JSON — cannot configure brevity-guard hook");
412
+ warn("settings.json has invalid JSON — cannot remove brevity-guard hook");
422
413
  return;
423
414
  }
424
415
  const settings = parsed || {};
425
- if (!settings.hooks) settings.hooks = {};
426
- if (!settings.hooks.Stop) settings.hooks.Stop = [];
427
-
428
- const already = settings.hooks.Stop.some((entry) =>
429
- entry.hooks && entry.hooks.some((h) => h.command && h.command.includes(BREVITY_GUARD_SCRIPT))
416
+ if (!settings.hooks || !Array.isArray(settings.hooks.Stop)) {
417
+ info("Reader Contract active every turn (no legacy brevity-guard hook to remove)");
418
+ return;
419
+ }
420
+ const before = settings.hooks.Stop.length;
421
+ settings.hooks.Stop = settings.hooks.Stop.filter((entry) =>
422
+ !(entry.hooks && entry.hooks.some((h) => h.command && h.command.includes(BREVITY_GUARD_SCRIPT)))
430
423
  );
431
- if (already) { info("Brevity-guard hook already configured"); return; }
432
-
433
- // Blocking (no async) so the Stop-hook block decision is honored.
434
- const cmd = `node "${dest.replace(/\\/g, "\\\\")}"`;
435
- settings.hooks.Stop.push({ matcher: "", hooks: [{ type: "command", command: cmd }] });
436
-
424
+ if (settings.hooks.Stop.length === before) {
425
+ info("Reader Contract active every turn (no legacy brevity-guard hook present)");
426
+ return;
427
+ }
437
428
  if (!isSymlink(SETTINGS_JSON)) {
438
429
  fs.writeFileSync(SETTINGS_JSON, JSON.stringify(settings, null, 2));
439
- success("Brevity-guard Stop hook configured in settings.json");
430
+ success("Removed legacy brevity-guard Stop hook Reader Contract now applies every turn");
440
431
  } else {
441
432
  warn("Skipping settings.json write — target is a symlink");
442
433
  }
@@ -1660,8 +1651,8 @@ async function doInstall(opts = {}) {
1660
1651
  heading("Heartbeat (Real-time Events)");
1661
1652
  installHeartbeat();
1662
1653
 
1663
- heading("Brevity Guard (M93 — concise replies)");
1664
- installBrevityGuard();
1654
+ heading("Reader Contract (M93 — concise replies, every turn)");
1655
+ uninstallBrevityGuardHook();
1665
1656
 
1666
1657
  heading("Update Check (Session Start)");
1667
1658
  installUpdateCheck();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.9.12",
3
+ "version": "4.9.14",
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",
@@ -26,6 +26,21 @@
26
26
  const fs = require("fs");
27
27
  const path = require("path");
28
28
 
29
+ // Reader Contract — emitted every turn so the standard is in front of the model
30
+ // each response (not a selective after-the-fact gate). Concise + jargon-free by
31
+ // its own rule. Generic before/after examples teach the shape.
32
+ const READER_CONTRACT = [
33
+ "[GSD-T READER CONTRACT] Before sending ANY reply, assume your first draft is too wordy and rewrite it tight. Rules:",
34
+ "• Answer FIRST. No preamble, no restating the question, no narrating what you're about to do (\"let me check…\"). Do the work silently, then give the result.",
35
+ "• Exception — when you're about to CHANGE code/files: state intent in one line first, so the user can stop a wrong direction.",
36
+ "• Gloss every code/jargon term in plain words on first use. No bare IDs or acronyms the reader must decode.",
37
+ "• Bullets/tables over paragraphs. Cut hedging and meta-commentary. Expand only if asked.",
38
+ "EXAMPLES (before → after):",
39
+ "• \"That's a great question, and it touches on something subtle. Let me look into how the cache works before I answer…\" → \"The cache lives in memory, cleared on restart.\"",
40
+ "• \"There are a few moving parts here. First, I want to make sure I understand the goal, because X has a gotcha…\" → \"Set X in .env. Gotcha: also add the localhost redirect URI or it rejects.\"",
41
+ "• \"Good catch — I conflated two things. Here's the honest correction: the files actually stack rather than overwrite…\" → \"You're right — files stack, they don't overwrite.\"",
42
+ ].join("\n");
43
+
29
44
  /**
30
45
  * Resolve the active model profile from .gsd-t/model-profile.json.
31
46
  *
@@ -123,6 +138,12 @@ process.stdin.on("end", () => {
123
138
  // [GSD-T NOW] format is date-guard-invariant: NEVER alter it.
124
139
  process.stdout.write(`[GSD-T NOW] ${liveTimestamp()}\n`);
125
140
 
141
+ // Reader Contract — injected EVERY turn, EVERY project (M93). The deterministic
142
+ // brevity gate was retired (it could only ever be selective); this puts the
143
+ // standard in front of the model each turn instead. Assume the first draft is
144
+ // wordy; rewrite it tight before sending.
145
+ process.stdout.write(READER_CONTRACT + "\n");
146
+
126
147
  // Parse stdin ONCE; both the profile token and auto-route reuse it.
127
148
  let data = null;
128
149
  try { data = JSON.parse(input); } catch (_) { /* tolerated — gates below skip */ }
@@ -44,7 +44,7 @@ Two reply shapes, picked by what the user wants. **Discriminator: are you modify
44
44
  - **Gloss jargon.** Never a bare code/acronym (e.g. `S2-M7` = section 2, milestone 7; `HC-003` = a rule ID) — say what it means in plain words on first use.
45
45
  - **Format.** Bullets/tables over paragraphs; expand only on request; the dated banner stays (first line, always).
46
46
 
47
- **ENFORCED, not advised:** the `gsd-t-brevity-guard` Stop hook (a gate that runs when a reply finishes) is the backstop a verbose/narrating reply is caught there. This block makes the gate rarely need to fire.
47
+ **Applied to EVERY reply, not selectively.** Assume your first draft is too wordy and rewrite it tight before sending — do not wait to be told. The `[GSD-T READER CONTRACT]` block (injected every turn by the UserPromptSubmit hook) restates this each response. (The old pattern-matching brevity Stop hook was retired: a gate that matches known-bad phrasings can only ever be selective and misses every new wording. The fix is to hold the standard for ALL output, every turn.)
48
48
 
49
49
 
50
50
  # GSD-T: Contract-Driven Development
@@ -102,7 +102,21 @@ async function runCli(projectDir, subcmd, argv, localBin, label, parseJson = tru
102
102
  ].join("\n");
103
103
  const opts = { label, schema: _CLI_ENVELOPE_SCHEMA, model: "haiku" };
104
104
  if (phaseNameOpt) opts.phase = phaseNameOpt;
105
- const r = await agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
105
+ // The CLI is run by a haiku helper agent, not a subprocess, so a return can transiently
106
+ // come back missing its parsed result. Retry ONCE on a missing/unparsed result before
107
+ // giving up — a genuine CLI failure fails both attempts (real error survives the retry),
108
+ // while a transient helper miss is recovered. Only retry when JSON was expected (parseJson)
109
+ // and the parsed result is absent; never retry on a clean exit that simply returned no JSON.
110
+ const runOnce = () => agent(prompt, opts).catch((e) => ({ ok: false, exitCode: -1, envelope: null, stderr: String(e && e.message), via: "error" }));
111
+ let r = await runOnce();
112
+ // Retry once when JSON was expected but no parsed result came back — covers both the
113
+ // throw path (via="error") and a malformed return the loose schema let through (ok=false
114
+ // with the result absent). A real CLI failure that returned valid JSON (envelope present,
115
+ // ok=false) is NOT retried — that is a true result, not a transient miss.
116
+ const missingResult = (x) => !x || (parseJson && (x.envelope === undefined || x.envelope === null) && x.ok !== true);
117
+ if (missingResult(r)) {
118
+ r = await runOnce();
119
+ }
106
120
  return r || { ok: false, exitCode: -1, envelope: null, via: "error" };
107
121
  }
108
122
  async function runPreflight(projectDir, label = "preflight", phaseNameOpt) { return runCli(projectDir, "preflight", ["--json"], "cli-preflight.cjs", label, true, phaseNameOpt); }