@tekyzinc/gsd-t 4.9.11 → 4.9.13

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.13] - 2026-06-25 (M93 — Reader Contract every turn, retire the selective gate — patch)
6
+
7
+ ### Changed — concise output is now enforced on EVERY reply, not by pattern-matching
8
+
9
+ 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.
10
+
11
+ - **Retired the brevity-guard Stop hook.** `gsd-t update-all` now *removes* it from any install that still has it.
12
+ - **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.
13
+ - 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).
14
+
15
+ ## [4.9.12] - 2026-06-23 (M93 brevity-guard live-tuning — patch)
16
+
17
+ ### Changed — brevity guard catches more, false-blocks less
18
+
19
+ Live use surfaced two gaps in the M93 brevity-guard Stop hook (it missed a real verbose reply, then would have false-blocked a clean one):
20
+
21
+ - **Interleaved preamble detection** — the guard previously only caught process-narration *stacked at the very front*. It now also catches narration/meta-commentary *scattered through the opening* (ack → narrate → a little content → narrate again → over-explain), which is the real exhausting shape. Conservative: scans only the first 6 sentences, needs ≥2 hits.
22
+ - **Narrowed live-reply jargon scope** — the hook no longer forces a gloss on a bare milestone code like `M92` (established conversational shorthand once context exists — it was false-blocking clean answers). It still forces glosses on the genuinely opaque IDs (`HC-003`, `S2-M7`). The stricter rule stays in the document lint (`gsd-t-jargon-lint.cjs`), where a reader has no conversation for context.
23
+
5
24
  ## [4.9.11] - 2026-06-23 (M93 — Brevity Guard: enforce concise, answer-first output — patch)
6
25
 
7
26
  ### Added — conciseness is now enforced, not just requested
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v4.9.11** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v4.9.13** - 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.11",
3
+ "version": "4.9.13",
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 */ }
@@ -63,15 +63,28 @@ const ALLOWLIST = new Set([
63
63
  ]);
64
64
 
65
65
  // High-signal code-token patterns (bare, unglossed → block on first use).
66
+ // LIVE-REPLY scope only: limited to the genuinely opaque IDs the user can't decode
67
+ // (contract / hard-constraint / stage-milestone codes). A bare `M92` milestone ref
68
+ // is established conversational shorthand once context exists — forcing a gloss on
69
+ // it every turn is over-strict and false-blocks clean answers. The stricter
70
+ // `M\d+-D\d+`-and-bare-`M\d+` rule stays in the DOC lint (bin/gsd-t-jargon-lint.cjs),
71
+ // where a reader has no conversational context. (M93 live-tuning, 2026-06-23.)
66
72
  const JARGON_PATTERNS = [
67
- /\bS2-M\d+\b/, // S2-M7
68
- /\bHC-\d+\b/, // HC-003
69
- /\bM\d+(?:-D\d+(?:-T\d+)?)?\b/, // M92, M92-D1, M92-D1-T3
73
+ /\bS2-M\d+\b/, // S2-M7 (stage-milestone code)
74
+ /\bHC-\d+\b/, // HC-003 (hard-constraint / contract id)
70
75
  ];
71
76
 
72
77
  // Narration openers: first-person "about to" framing — intent without content.
73
78
  const NARRATION_OPENER =
74
- /^(let me|before i\b|i'?ll\b|i'?m going to|i am going to|first,?\s+let me|i want to|i need to|let's|let us|going to)\b/i;
79
+ /^(let me|before i\b|i'?ll\b|i'?m going to|i am going to|first,?\s+let me|i want to|i need to|let's|let us|going to|i need to be|i have to|i should|i want to be|let me untangle|i'?m going to verify)\b/i;
80
+
81
+ // Meta-commentary / hedge openers — sentences that talk ABOUT the answer instead
82
+ // of giving it ("the honest correction:", "here's the real picture", "good catch —
83
+ // and I…", "where does that leave you"). These don't match NARRATION_OPENER but are
84
+ // the same preamble disease — over-explaining around the answer. Counted in the
85
+ // interleaved scan, NOT the leading scan (they appear mid-reply).
86
+ const META_OPENER =
87
+ /^(good (catch|question|point|instinct)|fair (challenge|point)|the honest|here'?s (the|what)|so,? (where|to|that|let)|that'?s (a |the )?(real|fair|honest)|the (real |accurate )?(picture|version|correction|nuance)|to (untangle|be (clear|precise|careful|honest))|i (conflated|misspoke|need to be|want to be))\b/i;
75
88
 
76
89
  // ── Transcript tail-scan (REUSED from gsd-t-conversation-capture.js) ─────────
77
90
  // Pull the most recent assistant turn from a Claude Code transcript JSONL by
@@ -223,6 +236,21 @@ function _leadingNarrationCount(prose) {
223
236
  return count;
224
237
  }
225
238
 
239
+ // Count narration + meta-commentary sentences INTERLEAVED in the opening region
240
+ // (the first WINDOW sentences), regardless of contiguity. Catches the real failure
241
+ // shape the leading-only scan misses: an ack, then narration, then a content
242
+ // sentence, then MORE narration ("Good catch… Let me untangle… What I said was X…
243
+ // Let me verify…"). Conservative: only scans the opening window, needs >=2 hits.
244
+ const INTERLEAVED_WINDOW = 6;
245
+ function _interleavedPreambleCount(prose) {
246
+ const sentences = _sentences(prose).slice(0, INTERLEAVED_WINDOW);
247
+ let count = 0;
248
+ for (const s of sentences) {
249
+ if (NARRATION_OPENER.test(s) || META_OPENER.test(s)) count++;
250
+ }
251
+ return count;
252
+ }
253
+
226
254
  // Find a bare unglossed high-signal jargon token (first occurrence, no gloss).
227
255
  // A gloss = parenthetical or quote in the SAME sentence containing the token.
228
256
  function _findBareJargon(prose) {
@@ -268,6 +296,18 @@ function detect(text, mode) {
268
296
  };
269
297
  }
270
298
 
299
+ // Interleaved preamble: narration/meta-commentary scattered through the opening
300
+ // (not just stacked at the front). The real exhausting shape: ack → narrate →
301
+ // a bit of content → narrate again → over-explain.
302
+ const interleaved = _interleavedPreambleCount(prose);
303
+ if (interleaved >= 2) {
304
+ return {
305
+ block: true,
306
+ reason: "Answer-first: the opening has " + interleaved +
307
+ " preamble/meta sentences (narration or 'here's the real picture'-style framing) around the answer. Lead with the answer; cut the meta-commentary.",
308
+ };
309
+ }
310
+
271
311
  const jargon = _findBareJargon(prose);
272
312
  if (jargon) {
273
313
  return {
@@ -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