@tekyzinc/gsd-t 5.7.10 → 5.9.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 +58 -0
- package/README.md +2 -2
- package/bin/gsd-t-concise-rewrite.cjs +223 -0
- package/bin/gsd-t-fallback-detect.cjs +624 -0
- package/bin/gsd-t-install-check.cjs +342 -0
- package/bin/gsd-t-verify-gate.cjs +2 -0
- package/bin/gsd-t.js +193 -5
- package/commands/gsd-t-architect.md +261 -18
- package/commands/gsd-t-help.md +4 -2
- package/package.json +1 -1
- package/scripts/gsd-t-concise-hook.js +200 -0
- package/scripts/gsd-t-fallback-guard.js +237 -0
- package/scripts/gsd-t-install-heal.js +183 -0
- package/templates/CLAUDE-global.md +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [5.9.10] - 2026-08-07
|
|
6
|
+
|
|
7
|
+
### Added — projects repair their own install, and fallbacks need approval by name
|
|
8
|
+
|
|
9
|
+
**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.
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
**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.
|
|
14
|
+
|
|
15
|
+
- `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
|
|
16
|
+
- `scripts/gsd-t-install-heal.js`: runs it at session start, first in the chain; silent when the install is already complete
|
|
17
|
+
- `bin/gsd-t.js`: three silent skips now report every undelivered file; new `install-check` command
|
|
18
|
+
- `test/m108-install-heal.test.js`: 12 tests, including one asserting the silent skip never comes back
|
|
19
|
+
|
|
20
|
+
**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.
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
- `bin/gsd-t-fallback-detect.cjs`, `scripts/gsd-t-fallback-guard.js`, `test/m106-fallback-gate.test.js`
|
|
27
|
+
|
|
28
|
+
**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.
|
|
29
|
+
|
|
30
|
+
- `bin/gsd-t-concise-rewrite.cjs`, `scripts/gsd-t-concise-hook.js`, `test/m107-concise-rewrite.test.js`
|
|
31
|
+
|
|
32
|
+
Switch either gate off per project: `.gsd-t/fallback-gate.json` or `.gsd-t/concise.json` with `{"enabled": false}`.
|
|
33
|
+
|
|
34
|
+
## [5.8.10] - 2026-08-05
|
|
35
|
+
|
|
36
|
+
### Changed — the architect now interviews you before it assesses
|
|
37
|
+
|
|
38
|
+
The architect sometimes focused on the wrong thing, or skipped something because it decided the thing was out of scope. Rather than patch that from the description, 16 real architect runs across 10 sessions were audited. They contained **21 corrections the user had to make — and 13 of those were facts the user already held and would have given for free.** Only 4 of 16 runs needed no correction.
|
|
39
|
+
|
|
40
|
+
| Failure | n | What it sounded like |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Re-derived an already-settled rule | 8 | *"That rule was also implemented, at least I thought it was last week."* |
|
|
43
|
+
| Asserted real-world behavior the user knew was false | 5 | *"I believe you're wrong. When scrolling the feed…"* — it had measured a saved page and described it as the live one |
|
|
44
|
+
| Weighed a trade-off the user had already closed | 3 | *"Stop telling me rather or two. Just tell me how we get to one."* |
|
|
45
|
+
| Output the user could not read | 3 | *"Try again without any jargon. I don't understand the grid."* |
|
|
46
|
+
| Analyzed an adjacent thing | 2 | *"Either we're having a miscommunication or you didn't find all the bugs."* |
|
|
47
|
+
| Excluded a dimension held in scope | 2 | *"Now consider capture performance in the context of these changes."* |
|
|
48
|
+
|
|
49
|
+
The top two — 13 of 21 — share one cause: **the architect reasoned where it should have asked.**
|
|
50
|
+
|
|
51
|
+
- `commands/gsd-t-architect.md`: a grounding loop now runs before the Six-Stage Pass — read the code and the standing rules → **interview** → **research** → loop, **max 3 cycles** (1-2 expected). The interview leads by showing its read of *how the thing works today* as a plain-English flow for the user to confirm or correct, which kills a wrong premise in one line instead of after a wasted pass. Two questions are mandatory every run, aimed at the top two failures: what is already settled and not open for re-litigation (echoing the harvested rules back so a stale one gets corrected cheaply), and what is true at runtime that the code cannot show. The rest branch by target type — bug-hunt, performance, plan-review, subsystem-audit, security. It gathers the evidence it can and explicitly names what it cannot get from the repo.
|
|
52
|
+
- **Research is gated on confidence and runs after the interview, never before** — so it cannot anchor the questions on an external solution shape, and so no search is spent on a question the user would answer in one line. The audit found no correction of the form "you didn't know how others solve this," so research is real but secondary to asking.
|
|
53
|
+
- **When external practice contradicts a standing project rule, the rule wins and the conflict is named in one line.** The narrow case where a fallback is genuinely warranted requires all three of: the primary path fails a high percentage of the time, the cause is outside our control, and completing the workflow is critical. Otherwise the answer is a halt.
|
|
54
|
+
- **At the 3-cycle cap the architect asks whether to halt or proceed** with the uncertainty flagged. It does not decide that alone.
|
|
55
|
+
- The interview runs in the main session and the pass in a fresh subagent — a subagent cannot ask anything mid-run, and relaying questions out as pings had already produced babysitting. The confirmed grounding is threaded into the subagent so it never re-derives what was just settled.
|
|
56
|
+
- Eight standing checks distilled from the corrections now run every pass: audit *existing* fallbacks as a root-cause candidate, no data left in limbo, delete the legacy path in the same change, two mechanisms doing one job means route to one rather than present a trade-off, state a verification path, production-data impact before a migration or merge, label any claim resting on a snapshot, and cover the always-in-scope dimensions. Output constraints are hard: plain English in the lead, no file:line grids, no shorthand.
|
|
57
|
+
- `.gsd-t/contracts/architects-oversight-contract.md` → v1.1.0: adds §0 (the grounding loop) and guards G-8..G-11.
|
|
58
|
+
- Two new flags: `--no-interview`, `--no-research`.
|
|
59
|
+
- **Fixed in passing:** the command still pointed at the pre-M99 graph path `.gsd-t/graph.db` instead of `.gsd-t/graphDB/graph.db` — the same false negative recorded in 5.2.11, which had been silently disabling the architect's own reuse check.
|
|
60
|
+
|
|
61
|
+
The measure of a run is now what happens after it: a run succeeds if the build it directed needed few follow-ups. A well-formed report that led to rework is a failed run. Suite 3120/0/13-skip.
|
|
62
|
+
|
|
5
63
|
## [5.7.10] - 2026-08-03
|
|
6
64
|
|
|
7
65
|
### Added — one session per working tree, enforced (M105)
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GSD-T: Contract-Driven Development for Claude Code
|
|
2
2
|
|
|
3
|
-
**v5.
|
|
3
|
+
**v5.9.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.
|
|
@@ -200,7 +200,7 @@ This will replace changed command files, back up your CLAUDE.md if customized, a
|
|
|
200
200
|
| `/gsd-t-partition` | Decompose into domains + contracts | In wave |
|
|
201
201
|
| `/gsd-t-plan` | Create atomic task lists per domain (tasks auto-split to fit one context window) | In wave |
|
|
202
202
|
| `/gsd-t-impact` | Analyze downstream effects | In wave |
|
|
203
|
-
| `/gsd-t-architect` |
|
|
203
|
+
| `/gsd-t-architect` | Interviews you first (shows its read of current behavior for confirmation, researches when unsure — max 3 cycles), then runs the Architect's Oversight Six-Stage Pass — simplest solution + reuse + traps, as plain-English pseudocode (plan-only; `--build` to auto-build, `--no-interview`/`--no-research` to skip a stage) | Manual |
|
|
204
204
|
| `/gsd-t-execute` | Run tasks — task-level fresh dispatch, worktree isolation, adaptive replanning | In wave |
|
|
205
205
|
| `/gsd-t-test-sync` | Sync tests with code changes | In wave |
|
|
206
206
|
| `/gsd-t-qa` | QA agent — test generation, execution, gap reporting | Auto-spawned |
|
|
@@ -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 };
|