@tekyzinc/gsd-t 5.11.12 → 5.11.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,48 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [5.11.14] - 2026-08-09
6
+
7
+ ### Fixed — the shortened reply appeared as a third copy instead of the only one
8
+
9
+ The hook told the model to "replace it with this shorter version". A model
10
+ cannot replace a reply already sent — it can only write another. Read as an
11
+ instruction to restate, it re-emitted the LONG original, so the reader saw the
12
+ full reply, then the short one, then the full one again.
13
+
14
+ The instruction now describes the only action available: your entire next
15
+ message is this text, copied exactly, nothing before or after, and do not repeat
16
+ any part of the previous reply. Stated in words rather than wrapper markers,
17
+ which the model copies through.
18
+
19
+ - `scripts/gsd-t-concise-hook.js`: the block instruction
20
+ - `test/m107-concise-rewrite.test.js`: 3 tests — no "replace", the repeat is forbidden outright, and no markers that could land in the reply
21
+
22
+ ## [5.11.13] - 2026-08-09
23
+
24
+ ### Fixed — the reply shortener discarded 4 of every 6 rewrites
25
+
26
+ A keyword check counted question marks, file paths and numbers, and threw the
27
+ whole rewrite away if a count dropped. Almost every rejection was the same
28
+ thing: the writer had asked *himself* a question ("Now the proof: does it
29
+ fire?"), the rewrite correctly cut it as narration, and the check saw a question
30
+ disappear. So a working shortener never once delivered.
31
+
32
+ Counting punctuation cannot tell an ask from thinking out loud. A reader can.
33
+ The same model now reads its own short version back against two questions — is
34
+ this only what he needs, and is it as short as his rules demand — and **fixes**
35
+ it. The reviewer returns text, never a verdict, so nothing is ever discarded. A
36
+ failed review keeps the first pass's rewrite rather than losing it.
37
+
38
+ Both passes now carry the relevance rule: keep only what changes what he
39
+ decides, does next, or now knows.
40
+
41
+ Measured on the reply that failed 4 of 6 before: **6 of 6 delivered, zero
42
+ discarded, 236 words down to 75-158.** Two Sonnet calls, 12-45s.
43
+
44
+ - `bin/gsd-t-concise-rewrite.cjs`: the check is gone; a review pass replaces it
45
+ - `test/m107-concise-rewrite.test.js`: 6 tests, including that the check cannot come back
46
+
5
47
  ## [5.11.12] - 2026-08-09
6
48
 
7
49
  ### Fixed — a retired command stayed typeable forever
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v5.11.12** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v5.11.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.
@@ -59,16 +59,24 @@ Rewrite the reply below following these rules exactly:
59
59
  answering a direct question about that. He asks when he wants it.
60
60
  4. NO JARGON. Plain words. If a technical term is genuinely needed, put the
61
61
  plain meaning first and the term in brackets after it.
62
- 5. CUT ANYTHING NOT ASKED FOR.
62
+ 5. KEEP ONLY WHAT IS RELEVANT TO HIM. Ask of every sentence: does this change
63
+ what he decides, what he does next, or what he now knows? If not, cut it.
64
+ Work the writer did, steps taken, what was checked, what was ruled out — all
65
+ of that is the writer's business, not his, unless he asked.
63
66
  6. Prefer a short list or a small table over a paragraph.
64
67
 
65
68
  KEEP THESE. They are not optional, and dropping any one of them means the
66
69
  rewrite is rejected and thrown away:
67
70
 
68
71
  - The first line, if it is a dated status banner.
69
- - EVERY question being asked of the reader. If the reply ends by asking him
70
- something, that question MUST appear in your rewrite, as its own line, at
71
- the end. This is the single most common way a rewrite is rejected.
72
+ - EVERY question being asked OF THE READER one he is meant to answer. If the
73
+ reply ends by asking him something, that question MUST appear in your rewrite,
74
+ as its own line, at the end. This is the single most common way a rewrite is
75
+ rejected.
76
+ A question the writer asks HIMSELF is not one of these. "Is that the cause?",
77
+ "Now the proof: does the hook fire?", "So what is slow here?" are thinking out
78
+ loud — cut them like any other narration. The test is simple: would he type an
79
+ answer to it? If not, it is not a question.
72
80
  - Any warning, failure, or thing that went wrong.
73
81
  - File paths and links, exactly as written.
74
82
  - Code blocks, exactly as written.
@@ -99,46 +107,46 @@ function wordCount(s) {
99
107
  }
100
108
 
101
109
  /**
102
- * Facts that must survive the rewrite. If any disappears, the rewrite is
103
- * rejected and the original is returned — a rewrite that drops a warning or a
104
- * question is worse than a long reply.
110
+ * The second pass the rewrite reviewed before it is delivered.
111
+ *
112
+ * A keyword check used to sit here: it counted question marks, file paths and
113
+ * numbers, and threw the whole rewrite away if a count dropped. It discarded
114
+ * 4 of every 6 rewrites, almost always because the writer had asked HIMSELF a
115
+ * question ("Now the proof: does it fire?") which the rewrite correctly cut as
116
+ * narration. Counting punctuation cannot tell an ask from thinking out loud.
117
+ *
118
+ * A reader can. So the same model that wrote the short version now reads it
119
+ * back against one question — is this what David needs, and is it as short as
120
+ * his rules demand — and fixes it. The reviewer returns text, never a verdict,
121
+ * so there is no path on which the work is discarded.
105
122
  */
106
- function extractInvariants(text) {
107
- return {
108
- questions: (text.match(/[^.!?\n]*\?/g) || []).map((q) => q.trim()).filter((q) => q.length > 10),
109
- numbers: text.match(/\b\d[\d,._]*\b/g) || [],
110
- paths: text.match(/[\w./-]+\.(?:js|cjs|mjs|ts|tsx|json|md|py|sh)\b/g) || [],
111
- codeBlocks: (text.match(/```/g) || []).length / 2,
112
- };
113
- }
123
+ const REVIEW = `You are checking a shortened reply before it reaches David.
114
124
 
115
- function checkInvariants(original, rewritten) {
116
- const a = extractInvariants(original);
117
- const b = extractInvariants(rewritten);
118
- const lost = [];
125
+ He is a slow reader. Every extra line costs him real time.
119
126
 
120
- if (a.questions.length > 0 && b.questions.length === 0) {
121
- lost.push("a question to the reader was dropped");
122
- }
123
- const lostPaths = a.paths.filter((p) => !rewritten.includes(p));
124
- if (lostPaths.length > 0 && lostPaths.length === a.paths.length && a.paths.length > 0) {
125
- lost.push("every file path was dropped");
126
- }
127
- if (a.codeBlocks > 0 && b.codeBlocks < a.codeBlocks) {
128
- lost.push("a code block was dropped");
129
- }
130
- // A number appearing in the rewrite that was never in the original means
131
- // something was invented.
132
- const invented = b.numbers.filter((n) => n.length > 2 && !a.numbers.includes(n));
133
- if (invented.length > 0) {
134
- lost.push(`a number appeared that was not in the original: ${invented[0]}`);
135
- }
136
- return lost;
137
- }
127
+ Two questions, both about the SHORT version:
138
128
 
139
- /** Ask a fresh Claude to do the rewrite. */
140
- function rewrite(text, cfg) {
141
- const prompt = `${RULES}\n\n--- REPLY TO REWRITE ---\n${text}`;
129
+ 1. Is this ONLY what he needs? Cut anything that does not change what he
130
+ decides, what he does next, or what he now knows. The work someone did,
131
+ steps taken, what was checked, what was ruled out — his business only if he
132
+ asked.
133
+
134
+ 2. Is it truly concise by his rules? Answer first, nothing before it. No
135
+ preamble, no backstory, no jargon standing in for a plain word. Lists and
136
+ small tables over paragraphs.
137
+
138
+ Then check nothing was lost that he needs:
139
+ - a question he is meant to ANSWER must still be there, as its own line at the
140
+ end. A question the writer asked himself is narration — it should be gone.
141
+ - warnings, failures, and things that went wrong stay.
142
+ - file paths, links, code blocks and specific numbers stay exactly as written.
143
+ - no fact, number or name may change, and nothing may be added.
144
+
145
+ Return the final reply and nothing else. If it is already right, return it
146
+ unchanged. Never return commentary, never return an empty response.`;
147
+
148
+ /** One call to a fresh Claude. Used by both passes. */
149
+ function askClaude(prompt, cfg) {
142
150
  // `--setting-sources project` is what stops the shortener shortening itself.
143
151
  //
144
152
  // A child started with the personal settings inherits the very Stop hook that
@@ -181,6 +189,25 @@ function rewrite(text, cfg) {
181
189
  return { ok: true, text: out };
182
190
  }
183
191
 
192
+ /** Pass 1 — shorten it. */
193
+ function rewrite(text, cfg) {
194
+ return askClaude(`${RULES}\n\n--- REPLY TO REWRITE ---\n${text}`, cfg);
195
+ }
196
+
197
+ /**
198
+ * Pass 2 — read the short version back and fix what pass 1 got wrong.
199
+ *
200
+ * Returns text, never a verdict, so the work is never discarded. If the review
201
+ * itself fails, pass 1's rewrite stands: it was already an improvement, and
202
+ * losing it because a second opinion did not arrive would be the old bug in a
203
+ * new place.
204
+ */
205
+ function review(original, shortened, cfg) {
206
+ const prompt = `${REVIEW}\n\n--- WHAT HE ORIGINALLY WROTE (for reference only) ---\n${original}\n\n--- THE SHORT VERSION TO CHECK AND RETURN ---\n${shortened}`;
207
+ const r = askClaude(prompt, cfg);
208
+ return r.ok ? r.text : shortened;
209
+ }
210
+
184
211
  function parseArgs(argv) {
185
212
  const args = { project: process.cwd() };
186
213
  for (let i = 2; i < argv.length; i++) {
@@ -231,19 +258,14 @@ function main() {
231
258
  }, args.json);
232
259
  }
233
260
 
234
- const lost = checkInvariants(text, r.text);
235
- if (lost.length > 0) {
236
- emit({
237
- ok: false, exitCode: EXIT_FAILED, text,
238
- error: `the rewrite lost something: ${lost.join("; ")}`,
239
- note: "Rewrite rejected — this is the original, unchanged.",
240
- words: before,
241
- }, args.json);
242
- }
261
+ // Second pass: the same model reads its own short version back, against the
262
+ // only two questions that matter — is this what David needs, and is it as
263
+ // short as his rules demand. It returns text, so nothing is ever discarded.
264
+ const finalText = review(text, r.text, cfg);
243
265
 
244
- const after = wordCount(r.text);
266
+ const after = wordCount(finalText);
245
267
  emit({
246
- ok: true, exitCode: EXIT_OK, text: r.text,
268
+ ok: true, exitCode: EXIT_OK, text: finalText,
247
269
  words: before, wordsAfter: after,
248
270
  saved: before - after,
249
271
  }, args.json);
@@ -251,4 +273,4 @@ function main() {
251
273
 
252
274
  if (require.main === module) main();
253
275
 
254
- module.exports = { wordCount, extractInvariants, checkInvariants, readConfig, RULES };
276
+ module.exports = { wordCount, readConfig, rewrite, review, askClaude, RULES, REVIEW };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "5.11.12",
3
+ "version": "5.11.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",
@@ -237,9 +237,17 @@ function main() {
237
237
  const saved = (result.words || 0) - (result.wordsAfter || 0);
238
238
  if (saved < 15) return allow(); // not worth the extra turn
239
239
 
240
+ // "Replace it with this" asks for something the model cannot do — it has no
241
+ // way to unsay a reply, only to write another. Read as an instruction to
242
+ // restate, it re-emitted the long original, so David saw the full reply,
243
+ // then the short one, then the full one again. The instruction has to
244
+ // describe the only real action: emit this text and stop.
240
245
  block(
241
- "Your last reply was longer than it needed to be. Replace it with this " +
242
- "shorter version, exactly as written, and add nothing:\n\n" +
246
+ "STOP. Do not continue your previous reply and do not repeat any part of " +
247
+ "it. Your entire next message is the text below, copied exactly nothing " +
248
+ "before it, nothing after it, no commentary, no heading, no explanation " +
249
+ "that you shortened anything. Everything from the next line onward IS " +
250
+ "your message:\n\n" +
243
251
  result.text
244
252
  );
245
253
  };