@tekyzinc/gsd-t 5.8.10 → 5.10.10

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,63 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [5.10.10] - 2026-08-07
6
+
7
+ ### Added — a project's CLAUDE.md is written from what actually happened in it
8
+
9
+ The template was GSD-T's own CLAUDE.md. It byte-copied into another project with that project's name substituted into GSD-T's own prose — 127 lines describing an npm CLI installer the project was not. Every new project was getting a file about the wrong software.
10
+
11
+ **`/gsd-t-setup` now reads the project's history first.** A rule that keeps getting broken is the rule that most needs writing down, and nobody recalls those on request — they have to be found. Three sources: git, the decision log, and what you typed in past sessions.
12
+
13
+ Session history is large — one project holds 315 MB across 57 sessions. Keeping only what you typed leaves 645 KB; dropping pasted logs leaves 92 complaint-shaped turns. **735 milliseconds, no subagents.**
14
+
15
+ The search terms were corrected against real transcripts. Every obvious guess scored zero — "I never asked you to", "that's the third time". People don't accuse; they restate the requirement. "still not", "you keep", "why did you" are what actually appear.
16
+
17
+ **Then it shows you the rules the project already states, and you tick the ones that can never be broken.** Six sources, ranked by how many agree — repetition is the evidence. Each rule shows where it came from, so you're confirming something you already said rather than recalling it. Capped at 12 on screen; the rest are written to a file, never dropped.
18
+
19
+ On one project the top four are its real inviolable rules with their ids. Three other projects each surface their own true rule first.
20
+
21
+ **The template is replaced** with a real mold: 45 lines, every section omittable with a stated reason, and one rule written into the mold itself — nothing that carries a number which changes on its own. A version, a line count, or "currently in progress" is wrong within a week and belongs in `progress.md`.
22
+
23
+ - `bin/gsd-t-project-history.cjs`: the funnel, three sources, each reporting whether it was there
24
+ - `bin/gsd-t-rule-mine.cjs`: six sources, deduplicated by what the rule claims
25
+ - `templates/CLAUDE-project.md`: replaced
26
+ - `commands/gsd-t-setup.md`: reads history, shows a tick-list, uses the mold
27
+ - `test/m109-project-claude-md.test.js`: 15 tests
28
+
29
+ **Not fixed, and said plainly:** a project CLAUDE.md is still written once and never updated, so a fresh file starts going stale immediately. The fix — giving project files the same marker-block treatment the global file has — is a separate milestone.
30
+
31
+ Run `/gsd-t-setup` inside a project to rewrite its file. It shows you the result and waits for a yes before writing.
32
+
33
+ ## [5.9.10] - 2026-08-07
34
+
35
+ ### Added — projects repair their own install, and fallbacks need approval by name
36
+
37
+ **Every registered project was running a broken install.** Binvoice had 20 of its 38 tools. The verify gate was a stale copy in all 33 projects, so every one of them has been running an outdated quality gate.
38
+
39
+ The cause was one line: a tool missing from the package was skipped in silence, and the pass then reported "copied N tools" — where N looked fine. The report was the concealment. Two more copies of the same defect sat in the utility-script and template installers.
40
+
41
+ **Install self-repair (M108).** Each session now checks its project's tools before any work starts, restores what is missing from the installed package, and refuses to continue if it cannot. Not a workaround: it fixes the failure, then the work proceeds with everything present. Every repair is logged to a shared file, and `gsd-t install-check --report` names any tool that has gone missing in more than one project — which points at the installer rather than the project. A per-project fix that also tells GSD-T what to mend in itself.
42
+
43
+ - `bin/gsd-t-install-check.cjs`: reads the expected tool list from the installer (never a second copy that can go stale), compares, repairs, halts when it can't
44
+ - `scripts/gsd-t-install-heal.js`: runs it at session start, first in the chain; silent when the install is already complete
45
+ - `bin/gsd-t.js`: three silent skips now report every undelivered file; new `install-check` command
46
+ - `test/m108-install-heal.test.js`: 12 tests, including one asserting the silent skip never comes back
47
+
48
+ **Fallback gate (M106).** A fallback — anything that continues after a failure — can no longer be written unless it was approved by name. The rule has been written down since v5.0.14 and kept being broken: a lookup for a post's author failed, the code substituted the last known seller, and posts were recorded as written by someone who never wrote them. That shipped while the rule was live.
49
+
50
+ Two things about the rule changed as a result. The damage is fabricated data **plus a disabled alarm** — author detection broke because a code change broke it, and the substitution meant nobody ever went looking. And consent must be specific: a fallback buried inside a long plan approved as a whole is not approved, because whoever wrote the plan controls the burying.
51
+
52
+ Detection matches the shape of the code, not its wording, so rewording cannot evade it. It runs when code is written, when a plan is reviewed, before a commit, and on demand across existing code. A one-time baseline records what was already there so the gate judges new work only.
53
+
54
+ - `bin/gsd-t-fallback-detect.cjs`, `scripts/gsd-t-fallback-guard.js`, `test/m106-fallback-gate.test.js`
55
+
56
+ **Shorter replies (M107).** A second Claude rewrites replies over 60 words — answer first, no preamble, no backstory, no jargon — before you read them. The rewrite is thrown away and the original kept if it drops a question, a code block, every file path, or invents a number.
57
+
58
+ - `bin/gsd-t-concise-rewrite.cjs`, `scripts/gsd-t-concise-hook.js`, `test/m107-concise-rewrite.test.js`
59
+
60
+ Switch either gate off per project: `.gsd-t/fallback-gate.json` or `.gsd-t/concise.json` with `{"enabled": false}`.
61
+
5
62
  ## [5.8.10] - 2026-08-05
6
63
 
7
64
  ### Changed — the architect now interviews you before it assesses
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v5.8.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v5.10.10** - 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.
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gsd-t-concise-rewrite.cjs
4
+ *
5
+ * M107-D1 — Rewrites a reply into its shortest honest form.
6
+ *
7
+ * [RULE] concise-rewrite-never-changes-a-fact
8
+ * [RULE] concise-rewrite-halts-never-silently-degrades
9
+ * [RULE] concise-rewrite-preserves-questions-and-warnings
10
+ *
11
+ * A fresh Claude, given only the text and the rules, cuts what the writer is
12
+ * attached to. It has no memory of the work, so it has nothing to defend.
13
+ *
14
+ * ─── Usage ──────────────────────────────────────────────────────────────────
15
+ * node gsd-t-concise-rewrite.cjs --text "<reply>" [--project <dir>] [--json]
16
+ * echo "<reply>" | node gsd-t-concise-rewrite.cjs --stdin
17
+ *
18
+ * ─── Exit codes ─────────────────────────────────────────────────────────────
19
+ * 0 rewritten (or skipped because it was already short)
20
+ * 4 the rewrite could not be done — the ORIGINAL is returned, marked
21
+ * 64 bad input
22
+ *
23
+ * On failure it returns the ORIGINAL text and says so. It never returns
24
+ * nothing, never truncates, never guesses. Showing a long reply is a poor
25
+ * outcome; showing a silently mangled one is a wrong one.
26
+ *
27
+ * Zero dependencies beyond the `claude` CLI on PATH.
28
+ */
29
+
30
+ "use strict";
31
+
32
+ const fs = require("fs");
33
+ const path = require("path");
34
+ const { spawnSync } = require("child_process");
35
+
36
+ const EXIT_OK = 0;
37
+ const EXIT_FAILED = 4;
38
+ const EXIT_BAD_INPUT = 64;
39
+
40
+ const DEFAULTS = {
41
+ enabled: true,
42
+ skipUnder: 60, // words — below this, rewriting buys nothing
43
+ model: "sonnet",
44
+ timeoutMs: 45000,
45
+ };
46
+
47
+ const RULES = `You rewrite a reply so it can be read in as little time as possible.
48
+
49
+ The reader is a slow reader. Every extra line costs him real time. Jargon costs
50
+ him more than length — he has to stop and translate it, and then ask for it
51
+ again in plain words.
52
+
53
+ Rewrite the reply below following these rules exactly:
54
+
55
+ 1. ANSWER FIRST. The answer is the first thing. Nothing before it.
56
+ 2. NO PREAMBLE. Cut any sentence that announces a point instead of making it.
57
+ 3. NO BACKSTORY. Cut every explanation of why something failed before, what
58
+ cannot work, how it works today, or what was rejected — unless the reply is
59
+ answering a direct question about that. He asks when he wants it.
60
+ 4. NO JARGON. Plain words. If a technical term is genuinely needed, put the
61
+ plain meaning first and the term in brackets after it.
62
+ 5. CUT ANYTHING NOT ASKED FOR.
63
+ 6. Prefer a short list or a small table over a paragraph.
64
+
65
+ KEEP THESE. They are not optional, and dropping any one of them means the
66
+ rewrite is rejected and thrown away:
67
+
68
+ - 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
+ - Any warning, failure, or thing that went wrong.
73
+ - File paths and links, exactly as written.
74
+ - Code blocks, exactly as written.
75
+ - Specific numbers and names.
76
+
77
+ Never:
78
+ - change a fact or a number
79
+ - drop a warning, a failure, or a question
80
+ - add anything that was not in the original
81
+ - soften a bad outcome
82
+
83
+ Return ONLY the rewritten reply. No commentary about what you changed.`;
84
+
85
+ function readConfig(projectDir) {
86
+ const p = path.join(projectDir, ".gsd-t", "concise.json");
87
+ if (!fs.existsSync(p)) return { ...DEFAULTS };
88
+ try {
89
+ return { ...DEFAULTS, ...JSON.parse(fs.readFileSync(p, "utf8")) };
90
+ } catch (_) {
91
+ // Unreadable config → defaults. Rewriting with defaults is the safe
92
+ // direction; it cannot lose content, only shorten it.
93
+ return { ...DEFAULTS };
94
+ }
95
+ }
96
+
97
+ function wordCount(s) {
98
+ return (String(s).trim().match(/\S+/g) || []).length;
99
+ }
100
+
101
+ /**
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.
105
+ */
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
+ }
114
+
115
+ function checkInvariants(original, rewritten) {
116
+ const a = extractInvariants(original);
117
+ const b = extractInvariants(rewritten);
118
+ const lost = [];
119
+
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
+ }
138
+
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}`;
142
+ const run = spawnSync("claude",
143
+ ["-p", prompt, "--model", cfg.model, "--dangerously-skip-permissions"],
144
+ { encoding: "utf8", timeout: cfg.timeoutMs, maxBuffer: 8 * 1024 * 1024 });
145
+
146
+ if (run.error) return { ok: false, error: run.error.message };
147
+ if (run.status !== 0) return { ok: false, error: `claude exited ${run.status}: ${(run.stderr || "").slice(0, 200)}` };
148
+ const out = (run.stdout || "").trim();
149
+ if (!out) return { ok: false, error: "the rewriter returned nothing" };
150
+ return { ok: true, text: out };
151
+ }
152
+
153
+ function parseArgs(argv) {
154
+ const args = { project: process.cwd() };
155
+ for (let i = 2; i < argv.length; i++) {
156
+ const a = argv[i];
157
+ if (a === "--json") args.json = true;
158
+ else if (a === "--stdin") args.stdin = true;
159
+ else if (a === "--text") args.text = argv[++i];
160
+ else if (a === "--project") args.project = argv[++i];
161
+ }
162
+ return args;
163
+ }
164
+
165
+ function emit(result, asJson) {
166
+ if (asJson) process.stdout.write(JSON.stringify(result, null, 2) + "\n");
167
+ else process.stdout.write(result.text + "\n");
168
+ process.exit(result.exitCode);
169
+ }
170
+
171
+ function main() {
172
+ const args = parseArgs(process.argv);
173
+ const projectDir = path.resolve(args.project);
174
+ const cfg = readConfig(projectDir);
175
+
176
+ let text = args.text;
177
+ if (args.stdin) {
178
+ try { text = fs.readFileSync(0, "utf8"); } catch (_) { text = ""; }
179
+ }
180
+ if (typeof text !== "string" || !text.trim()) {
181
+ emit({ ok: false, exitCode: EXIT_BAD_INPUT, text: "", error: "no text given" }, true);
182
+ }
183
+
184
+ const before = wordCount(text);
185
+
186
+ if (!cfg.enabled) {
187
+ emit({ ok: true, exitCode: EXIT_OK, text, skipped: "switched off", words: before }, args.json);
188
+ }
189
+ if (before < cfg.skipUnder) {
190
+ emit({ ok: true, exitCode: EXIT_OK, text, skipped: "already short", words: before }, args.json);
191
+ }
192
+
193
+ const r = rewrite(text, cfg);
194
+ if (!r.ok) {
195
+ emit({
196
+ ok: false, exitCode: EXIT_FAILED, text,
197
+ error: r.error,
198
+ note: "Rewrite failed — this is the original, unchanged.",
199
+ words: before,
200
+ }, args.json);
201
+ }
202
+
203
+ const lost = checkInvariants(text, r.text);
204
+ if (lost.length > 0) {
205
+ emit({
206
+ ok: false, exitCode: EXIT_FAILED, text,
207
+ error: `the rewrite lost something: ${lost.join("; ")}`,
208
+ note: "Rewrite rejected — this is the original, unchanged.",
209
+ words: before,
210
+ }, args.json);
211
+ }
212
+
213
+ const after = wordCount(r.text);
214
+ emit({
215
+ ok: true, exitCode: EXIT_OK, text: r.text,
216
+ words: before, wordsAfter: after,
217
+ saved: before - after,
218
+ }, args.json);
219
+ }
220
+
221
+ if (require.main === module) main();
222
+
223
+ module.exports = { wordCount, extractInvariants, checkInvariants, readConfig, RULES };