@tekyzinc/gsd-t 5.4.11 → 5.5.11

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,49 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [5.5.11] - 2026-07-29
6
+
7
+ ### Fixed — the new style gate would have failed 27 untouched documents in three projects
8
+
9
+ Caught by running the gate live in a downstream project immediately after propagation rather than trusting the "copied 1 bin tool(s)" report. v5.5.10 shipped the gate to all 33 registered projects, but the grandfather list that exempts pre-v1.2.0 documents existed only in the GSD-T repo. binvoice (19 documents), NiceNote (6) and IssueRecorder (2) had none — so the next `gsd-t-verify` run in those projects would have failed on documents nobody touched, written to a then-valid standard.
10
+
11
+ The gate now establishes its own starting line: on the `--dir` path, when the list file is absent and the directory already holds documents, it writes the list once from the existing set and reports `seeded: { reason: "seeded-pre-existing", count, docs }`.
12
+
13
+ This is **not a fallback** — it masks no failure and continues past none; it defines where the gate begins. Three properties keep it honest: written **once** (a self-re-seeding list would silently absolve every newly-drifted document, which is the banned behavior), keyed on the file being absent, and always surfaced naming every document covered. A document created after the seed is gated normally; a single `--doc` call never seeds; an un-writable directory returns exit 64 with the reason rather than a silent mass-pass or mass-fail.
14
+
15
+ - `bin/gsd-t-pseudocode-style.cjs`: `seedGrandfatherList()` + envelope `seeded` field.
16
+ - `test/pseudocode-style-gate.test.js`: 4 new tests (15 → 19) — seed happens, seed never re-runs, `--doc` never seeds, un-writable directory exits 64.
17
+ - `.gsd-t/contracts/pseudocode-source-of-truth-contract.md`: §1.1.5 documents the seed and why it is not a fallback.
18
+
19
+ Verified live: binvoice's 19 documents now report `exitCode 0`, 19 seeded, 0 violations. Suite 3052/0/13-skip.
20
+
21
+ ## [5.5.10] - 2026-07-29
22
+
23
+ ### Added — PseudoCode style is governed: the flow IS the document
24
+
25
+ A PseudoCode document exists for one job — letting the user approve **direction before code**. The corpus had drifted into transliterated source (`loadStore(storePath):`, `spawnSync`, `MODULE_NOT_FOUND`, `→ 409`) buried under paragraphs of preamble, which is a document the reader must translate line-by-line. Root cause: contract §1 governed what a doc *contains* (the five section elements) and nothing governed how it *reads*.
26
+
27
+ New contract §1.1 fixes the readable half with two rules, not one:
28
+
29
+ - **Shape** — the flow is a nested decision tree in plain English: 2-space indent per level, one thing per line, a question line ending `:` answered by indented `Yes:` / `No:` (or named outcomes) beneath it. Banned in the flow: function-call syntax, code keywords (`if`/`return`/`throw`/`catch`/`tx:`), bare status codes or SCREAMING_SNAKE constants, and any paragraph.
30
+ - **Words** — the technical term is *kept* and rides **alongside** plain words rather than replacing them: plain first, term in parentheses, glossed once per `##` section — `Zoom's webhook (its automatic ping to us) arrives at /zoom/events`. Concrete real names (Zoom, the Save button, the invoices table) need no gloss; abstract category-nouns (webhook, payload, endpoint, token, cache) do.
31
+
32
+ Document shape is now `# Title` → one sentence of purpose → the flow → `---` → everything else. The `[RULE]` guard map, divergence flags, Six-Stage answers, and every file/function pointer move below the divider; code identifiers never appear in the flow.
33
+
34
+ - `bin/gsd-t-pseudocode-style.cjs`: NEW deterministic gate (exit 0 clean / 4 violations / 64 bad input). Checks the flow block only, so the §2 `[RULE]` grammar is never style-checked and the two gates cannot fight. Added to both `GLOBAL_BIN_TOOLS` and `PROJECT_BIN_TOOLS`.
35
+ - `bin/gsd-t.js`: new `pseudocode-style` subcommand dispatch.
36
+ - `templates/workflows/gsd-t-verify.workflow.js`: gate wired FAIL-blocking beside the guard-map gate — no discovery agent needed, it globs the §7 directory itself.
37
+ - `test/pseudocode-style-gate.test.js`: NEW, 15 tests written as a discrimination bar — every banned form must fail individually (9 distinct violations fire on a doctored doc), the converted reference must pass, the below-divider guard map must be exempt, and a doc with no flow block fails rather than passing vacuously.
38
+ - `.gsd-t/contracts/pseudocode-source-of-truth-contract.md`: v1.1.5 → **v1.2.0** (additive; §2/§3/§4/§5/§7 grammars untouched).
39
+ - `templates/PseudoCode-spec.md`: mold rewritten flow-first.
40
+ - `.gsd-t/pseudocode/PseudoCode-BrokenGraphHalts.md`: converted as the worked reference — same behavior, same six `[RULE]`s, only the reading changed.
41
+ - Authoring instructions rippled to all five producing sites: `commands/gsd-t-architect.md`, `commands/gsd-t-milestone.md`, `commands/gsd-t-doc-ripple.md`, `templates/workflows/gsd-t-phase.workflow.js` (2 spots), `templates/prompts/keep-or-supersede-subagent.md`.
42
+ - `templates/CLAUDE-global.md`: new § PseudoCode Style (mirrored to the live `~/.claude/CLAUDE.md`).
43
+ - `.gsd-t/contracts/architects-oversight-contract.md`: A-FAIL-1 no longer describes the old CURRENT/PROPOSED style.
44
+ - `.gsd-t/contracts/graph-metrics-contract.md`: `doMetrics` line citation 4915 → 4927 (the new dispatch case shifted `bin/gsd-t.js`; the existing M99 contract-line test caught it).
45
+
46
+ The seven pre-v1.2.0 documents are grandfathered via `.gsd-t/pseudocode/.style-grandfathered` and reported as a logged skip **with a named reason**, never a silent pass. Removing a name is how a document opts into the gate. Suite 3048/0/13-skip.
47
+
5
48
  ## [5.4.11] - 2026-07-27
6
49
 
7
50
  ### Fixed — schema-id gate never reached projects (propagation gap)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v5.4.11** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v5.5.11** - 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,409 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gsd-t-pseudocode-style — the §1.1 flow-line style gate.
4
+ *
5
+ * Contract: .gsd-t/contracts/pseudocode-source-of-truth-contract.md §1.1
6
+ *
7
+ * WHY THIS EXISTS
8
+ * ---------------
9
+ * §1 governed what a PseudoCode doc CONTAINS; nothing governed how it READS.
10
+ * The corpus drifted into transliterated source code (`loadStore(storePath):`,
11
+ * `spawnSync`, `MODULE_NOT_FOUND`, `→ 409`) wrapped in paragraphs of preamble —
12
+ * a document the reader has to translate line-by-line, which defeats its only
13
+ * purpose (approving DIRECTION before code). This gate holds the readable half:
14
+ * the flow reads as a nested decision tree in plain English, and a technical
15
+ * term rides ALONGSIDE plain words in parentheses instead of replacing them.
16
+ *
17
+ * SCOPE — the flow block only (§1.1.5)
18
+ * ------------------------------------
19
+ * Checks fenced blocks ABOVE the first `---` divider, plus the flow blocks under
20
+ * `## What it does today` / `## What changes`. Everything below the divider is
21
+ * exempt: that is where the §2 `[RULE]` guard map, divergence flags, Six-Stage
22
+ * answers, and file/function pointers legitimately live. So this gate can never
23
+ * fight the §2 grammar.
24
+ *
25
+ * Deterministic code, zero LLM judgment (same split as §2: an LLM may PRODUCE a
26
+ * doc, code GATES it). Never throws — bad input returns exitCode 64.
27
+ *
28
+ * Input: --doc <path> [--json] | --dir <pseudocode dir> [--json]
29
+ * Output: JSON envelope { ok, exitCode, violations: [...] }
30
+ * Exit: 0 clean · 4 style violations · 64 bad input
31
+ */
32
+
33
+ "use strict";
34
+
35
+ const fs = require("fs");
36
+ const path = require("path");
37
+
38
+ /** Basename-per-line opt-out list for docs authored before contract v1.2.0. */
39
+ const GRANDFATHER_FILE = ".style-grandfathered";
40
+
41
+ /**
42
+ * Category-nouns that force a mental translation when they appear bare.
43
+ * A term here must carry a parenthetical gloss on FIRST use within its `##`
44
+ * section (§1.1.3). Concrete real names (Zoom, /zoom/events, the invoices
45
+ * table) are deliberately absent — they are specific, so nothing must be
46
+ * decoded.
47
+ */
48
+ const JARGON_TERMS = [
49
+ "webhook", "payload", "endpoint", "handler", "middleware", "token",
50
+ "cache", "mutex", "idempotent", "serialize", "deserialize", "marshal",
51
+ "envelope", "socket", "daemon", "cron", "regex", "schema", "hash",
52
+ ];
53
+
54
+ /** Code keywords that mean the author transliterated source instead of behavior. */
55
+ const CODE_KEYWORDS = [
56
+ "return", "throw", "catch", "finally", "await", "async", "yield",
57
+ "const", "let", "var", "function", "class", "import", "export",
58
+ "elif", "endif", "null", "undefined", "void",
59
+ ];
60
+
61
+ /**
62
+ * Split a doc into the region the gate checks and the exempt region.
63
+ * @param {string} text
64
+ * @returns {{ flowRegion: string, flowStartLine: number, extraRegions: Array<{text:string,startLine:number,section:string}> }}
65
+ */
66
+ function splitRegions(text) {
67
+ const lines = text.split("\n");
68
+
69
+ // The header region ends at the first `---` divider that sits OUTSIDE a fence.
70
+ let inFence = false;
71
+ let dividerIdx = lines.length;
72
+ for (let i = 0; i < lines.length; i++) {
73
+ const l = lines[i];
74
+ if (/^\s*```/.test(l)) { inFence = !inFence; continue; }
75
+ if (!inFence && /^---\s*$/.test(l)) { dividerIdx = i; break; }
76
+ }
77
+
78
+ const flowRegion = lines.slice(0, dividerIdx).join("\n");
79
+
80
+ // Below the divider, only the two named before/after flow sections are checked.
81
+ const extraRegions = [];
82
+ const FLOW_SECTION = /^##\s+(What it does today|What changes)\s*$/i;
83
+ let cur = null;
84
+ inFence = false;
85
+ for (let i = dividerIdx; i < lines.length; i++) {
86
+ const l = lines[i];
87
+ if (/^\s*```/.test(l)) { inFence = !inFence; if (cur) cur.lines.push(l); continue; }
88
+ if (!inFence && /^##\s+/.test(l)) {
89
+ if (cur) extraRegions.push({ text: cur.lines.join("\n"), startLine: cur.startLine, section: cur.section });
90
+ const m = l.match(FLOW_SECTION);
91
+ cur = m ? { lines: [], startLine: i + 2, section: m[1] } : null;
92
+ continue;
93
+ }
94
+ if (cur) cur.lines.push(l);
95
+ }
96
+ if (cur) extraRegions.push({ text: cur.lines.join("\n"), startLine: cur.startLine, section: cur.section });
97
+
98
+ return { flowRegion, flowStartLine: 1, extraRegions };
99
+ }
100
+
101
+ /**
102
+ * Pull the fenced code blocks out of a region — the flow lines live inside them.
103
+ * @returns {Array<{lines: string[], startLine: number}>}
104
+ */
105
+ function fencedBlocks(regionText, regionStartLine) {
106
+ const out = [];
107
+ const lines = regionText.split("\n");
108
+ let cur = null;
109
+ for (let i = 0; i < lines.length; i++) {
110
+ if (/^\s*```/.test(lines[i])) {
111
+ if (cur) { out.push(cur); cur = null; }
112
+ else cur = { lines: [], startLine: regionStartLine + i + 1 };
113
+ continue;
114
+ }
115
+ if (cur) cur.lines.push(lines[i]);
116
+ }
117
+ if (cur) out.push(cur); // unterminated fence — still check what we got
118
+ return out;
119
+ }
120
+
121
+ /** Strip parenthetical glosses and trailing `# comment` before keyword scanning. */
122
+ function stripGlosses(line) {
123
+ return line.replace(/\([^)]*\)/g, " ").replace(/#.*$/, " ");
124
+ }
125
+
126
+ /**
127
+ * Check one flow line for the §1.1.2 banned forms.
128
+ * @returns {string[]} rule names broken
129
+ */
130
+ function checkLine(rawLine, glossedTerms) {
131
+ const broken = [];
132
+ const line = rawLine.trimEnd();
133
+ if (!line.trim()) return broken;
134
+
135
+ const bare = stripGlosses(line);
136
+
137
+ // Function-call syntax: `name(...)` or `obj.method(...)`.
138
+ if (/[A-Za-z_][A-Za-z0-9_.]*\s*\([^)]*\)\s*:?/.test(line.replace(/\([^)]*\)/g, (m, off) => {
139
+ // A parenthetical gloss follows a space and starts with a lowercase word or
140
+ // "the"/"its" — that is prose, not a call. Keep call-shaped ones for the test.
141
+ const before = line.slice(0, off);
142
+ return /[A-Za-z0-9_.]$/.test(before) ? m : " ";
143
+ }))) {
144
+ broken.push("no-function-call-syntax");
145
+ }
146
+
147
+ // Code keywords as standalone words.
148
+ for (const kw of CODE_KEYWORDS) {
149
+ if (new RegExp(`(^|[^A-Za-z0-9_])${kw}([^A-Za-z0-9_]|$)`, "i").test(bare)) {
150
+ broken.push(`no-code-keyword:${kw}`);
151
+ break;
152
+ }
153
+ }
154
+ // `if`/`else` only count when they open a clause — "Is it valid:" is the form we want.
155
+ if (/(^|[^A-Za-z0-9_])(if|else)\s/i.test(bare)) broken.push("no-code-keyword:if/else");
156
+ if (/(^|\s)tx\s*:/i.test(bare)) broken.push("no-code-keyword:tx");
157
+
158
+ // Arrow-to-status and SCREAMING_SNAKE error constants, unless glossed.
159
+ if (/→\s*\d{3}\b/.test(bare) || /\b(?:HTTP\s*)?\b[45]\d\d\b(?=\s*$)/.test(bare.trim())) {
160
+ broken.push("no-bare-status-code");
161
+ }
162
+ if (/\b[A-Z][A-Z0-9]*_[A-Z0-9_]{2,}\b/.test(bare)) broken.push("no-bare-error-constant");
163
+
164
+ // Un-glossed category-noun on first use in this section.
165
+ for (const term of JARGON_TERMS) {
166
+ const re = new RegExp(`(^|[^A-Za-z])${term}s?([^A-Za-z]|$)`, "i");
167
+ if (!re.test(line)) continue;
168
+ const hasGloss = new RegExp(`${term}s?\\s*\\([^)]+\\)`, "i").test(line);
169
+ if (hasGloss) { glossedTerms.add(term); continue; }
170
+ if (!glossedTerms.has(term)) broken.push(`gloss-on-first-use:${term}`);
171
+ }
172
+
173
+ return broken;
174
+ }
175
+
176
+ /**
177
+ * Prose-paragraph detection: a flow line with no question colon, no outcome
178
+ * prefix, and sentence-length wording is a paragraph the user asked to remove.
179
+ */
180
+ function looksLikeParagraph(line) {
181
+ const t = line.trim();
182
+ if (t.length < 90) return false;
183
+ if (/:\s*$/.test(t)) return false; // question line
184
+ if (/^(Yes|No|[A-Z][A-Za-z ]{0,20}):/.test(t)) return false; // outcome line
185
+ return /[.!?]\s+[A-Z]/.test(t); // 2+ sentences
186
+ }
187
+
188
+ /**
189
+ * Gate one doc.
190
+ * @returns {{ ok, exitCode, doc, violations, skipped?, reason? }}
191
+ */
192
+ function gateDoc(docPath, grandfathered) {
193
+ let text;
194
+ try {
195
+ text = fs.readFileSync(docPath, "utf8");
196
+ } catch (e) {
197
+ return { ok: false, exitCode: 64, doc: docPath, reason: `cannot read doc: ${e && e.message}`, violations: [] };
198
+ }
199
+
200
+ const base = path.basename(docPath);
201
+ if (grandfathered.has(base)) {
202
+ // A logged skip WITH A REASON — never a silent pass (feedback_no_silent_degradation).
203
+ return { ok: true, exitCode: 0, doc: docPath, skipped: true, reason: "grandfathered", violations: [] };
204
+ }
205
+
206
+ const { flowRegion, flowStartLine, extraRegions } = splitRegions(text);
207
+ const violations = [];
208
+
209
+ const regions = [
210
+ { blocks: fencedBlocks(flowRegion, flowStartLine - 1), section: "(flow)" },
211
+ ...extraRegions.map((r) => ({ blocks: fencedBlocks(r.text, r.startLine - 1), section: r.section })),
212
+ ];
213
+
214
+ let sawAnyFlowLine = false;
215
+ for (const region of regions) {
216
+ // Gloss scope is the `##` section (§1.1.3) — reset per region.
217
+ const glossedTerms = new Set();
218
+ for (const block of region.blocks) {
219
+ for (let i = 0; i < block.lines.length; i++) {
220
+ const raw = block.lines[i];
221
+ const lineNo = block.startLine + i;
222
+ if (raw.trim()) sawAnyFlowLine = true;
223
+ for (const rule of checkLine(raw, glossedTerms)) {
224
+ violations.push({ doc: docPath, line: lineNo, section: region.section, rule, text: raw.trim().slice(0, 120) });
225
+ }
226
+ if (looksLikeParagraph(raw)) {
227
+ violations.push({ doc: docPath, line: lineNo, section: region.section, rule: "no-paragraph-in-flow", text: raw.trim().slice(0, 120) });
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ // Non-vacuity: a doc with no flow lines at all cannot pass by having nothing
234
+ // to check (the §2/§3 vacuous-pass class).
235
+ if (!sawAnyFlowLine) {
236
+ return {
237
+ ok: false, exitCode: 4, doc: docPath, violations: [
238
+ { doc: docPath, line: 1, section: "(flow)", rule: "no-flow-block", text: "no fenced flow block found above the first --- divider" },
239
+ ],
240
+ };
241
+ }
242
+
243
+ return { ok: violations.length === 0, exitCode: violations.length === 0 ? 0 : 4, doc: docPath, violations };
244
+ }
245
+
246
+ /** Read the grandfather list from the doc's own directory. */
247
+ function loadGrandfathered(dir) {
248
+ const out = new Set();
249
+ try {
250
+ const raw = fs.readFileSync(path.join(dir, GRANDFATHER_FILE), "utf8");
251
+ for (const l of raw.split("\n")) {
252
+ const t = l.trim();
253
+ if (t && !t.startsWith("#")) out.add(t);
254
+ }
255
+ } catch { /* absent list = nothing grandfathered (see seedGrandfatherList) */ }
256
+ return out;
257
+ }
258
+
259
+ /**
260
+ * First-run seed for a project that already had PseudoCode docs before this gate
261
+ * existed.
262
+ *
263
+ * WHY THIS IS NOT A FALLBACK (No-Fallback-Ever doctrine):
264
+ * this does not continue past a failure or mask one. It establishes the gate's
265
+ * STARTING LINE. The gate governs docs authored under contract §1.1; docs that
266
+ * predate it were written to a different (then-valid) standard, and retroactively
267
+ * failing them would make an unrelated verify run fail in a downstream project for
268
+ * work nobody touched. That is a false failure, not a caught defect.
269
+ *
270
+ * The seed is: WRITTEN ONCE (never rewritten — a list that re-seeded itself would
271
+ * silently absolve every newly-drifted doc, which IS the banned behavior), keyed on
272
+ * the list file being ABSENT, and always SURFACED with reason "seeded-pre-existing"
273
+ * naming every doc it covers. A doc created AFTER the seed is gated normally.
274
+ *
275
+ * @returns {{ seeded: boolean, names: string[], error?: string }}
276
+ */
277
+ function seedGrandfatherList(dir, docBasenames) {
278
+ const listPath = path.join(dir, GRANDFATHER_FILE);
279
+ if (fs.existsSync(listPath)) return { seeded: false, names: [] };
280
+ if (docBasenames.length === 0) return { seeded: false, names: [] };
281
+
282
+ const body = [
283
+ "# Docs that predate the §1.1 flow-line style gate (contract v1.2.0).",
284
+ "# Seeded automatically on this gate's FIRST run in this project, so pre-existing",
285
+ "# docs are not retroactively failed. Each is reported as a logged skip WITH A",
286
+ "# REASON — never a silent pass.",
287
+ "#",
288
+ "# This file is written ONCE and never re-seeded: a doc created after this point",
289
+ "# is gated normally. Removing a name here is how a doc opts INTO the gate —",
290
+ "# convert it to the §1.1 flow style in the same change.",
291
+ ...docBasenames,
292
+ "",
293
+ ].join("\n");
294
+
295
+ try {
296
+ fs.writeFileSync(listPath, body, "utf8");
297
+ } catch (e) {
298
+ // Cannot write (read-only checkout, permissions) — surface it, do NOT
299
+ // silently fail 20 docs and do NOT silently pass them either.
300
+ return { seeded: false, names: [], error: `cannot seed ${GRANDFATHER_FILE}: ${e && e.message}` };
301
+ }
302
+ return { seeded: true, names: docBasenames.slice() };
303
+ }
304
+
305
+ /**
306
+ * Run the gate over one doc or a whole directory (§7 discovery glob).
307
+ * Never throws.
308
+ */
309
+ function run({ doc, dir }) {
310
+ if (!doc && !dir) {
311
+ return { ok: false, exitCode: 64, reason: "missing --doc and/or --dir", violations: [] };
312
+ }
313
+
314
+ const docs = [];
315
+ if (doc) docs.push(doc);
316
+ if (dir) {
317
+ let entries;
318
+ try {
319
+ entries = fs.readdirSync(dir);
320
+ } catch (e) {
321
+ return { ok: false, exitCode: 64, reason: `cannot read dir: ${e && e.message}`, violations: [] };
322
+ }
323
+ for (const e of entries) {
324
+ if (/^PseudoCode-.*\.md$/.test(e) && e !== "PseudoCode-spec.md") docs.push(path.join(dir, e));
325
+ }
326
+ if (docs.length === 0) {
327
+ // Legitimately nothing to gate — surfaced, not silent (§7 pairing outcomes).
328
+ return { ok: true, exitCode: 0, docsChecked: 0, skips: [{ reason: "no-pseudocode-docs" }], violations: [] };
329
+ }
330
+ }
331
+
332
+ // First-run seed: a project whose docs predate this gate gets its starting line
333
+ // established once, surfaced with a reason. Only on the --dir path (the whole-set
334
+ // view); a single --doc call never seeds.
335
+ let seedInfo = null;
336
+ if (dir) {
337
+ seedInfo = seedGrandfatherList(dir, docs.map((d) => path.basename(d)).sort());
338
+ if (seedInfo.error) {
339
+ return { ok: false, exitCode: 64, reason: seedInfo.error, violations: [] };
340
+ }
341
+ }
342
+
343
+ const results = [];
344
+ const violations = [];
345
+ const skips = [];
346
+ let worstExit = 0;
347
+ for (const d of docs) {
348
+ const r = gateDoc(d, loadGrandfathered(path.dirname(d)));
349
+ results.push({ doc: d, ok: r.ok, exitCode: r.exitCode, skipped: !!r.skipped, reason: r.reason });
350
+ if (r.skipped) skips.push({ doc: d, reason: r.reason });
351
+ for (const v of r.violations) violations.push(v);
352
+ if (r.exitCode > worstExit) worstExit = r.exitCode;
353
+ }
354
+
355
+ const envelope = {
356
+ ok: worstExit === 0,
357
+ exitCode: worstExit,
358
+ docsChecked: docs.length,
359
+ results,
360
+ skips,
361
+ violations,
362
+ };
363
+ if (seedInfo && seedInfo.seeded) {
364
+ // Surfaced, never silent: name the migration and every doc it covers.
365
+ envelope.seeded = { reason: "seeded-pre-existing", count: seedInfo.names.length, docs: seedInfo.names };
366
+ }
367
+ return envelope;
368
+ }
369
+
370
+ function parseArgs(argv) {
371
+ const o = { doc: null, dir: null, help: false };
372
+ for (let i = 0; i < argv.length; i++) {
373
+ const a = argv[i];
374
+ if (a === "-h" || a === "--help") o.help = true;
375
+ else if (a === "--doc") o.doc = argv[++i];
376
+ else if (a === "--dir") o.dir = argv[++i];
377
+ else if (a === "--json") { /* JSON is the only output */ }
378
+ }
379
+ return o;
380
+ }
381
+
382
+ const HELP = `Usage: gsd-t pseudocode-style (--doc <PseudoCode-[Title].md> | --dir <dir>) [--json]
383
+
384
+ Gates the §1.1 flow-line style of a PseudoCode doc: the flow reads as a nested
385
+ decision tree in plain English, technical terms ride alongside plain words in
386
+ parentheses, and code identifiers stay below the first --- divider.
387
+
388
+ --doc PATH gate one doc.
389
+ --dir PATH gate every PseudoCode-*.md in a directory (§7 discovery).
390
+
391
+ Exit: 0 clean · 4 style violations · 64 bad input.
392
+ Pre-v1.2.0 docs listed in <dir>/${GRANDFATHER_FILE} are skipped WITH a reason.`;
393
+
394
+ function main() {
395
+ const o = parseArgs(process.argv.slice(2));
396
+ if (o.help) { process.stdout.write(HELP + "\n"); process.exit(0); }
397
+ let res;
398
+ try {
399
+ res = run(o);
400
+ } catch (e) {
401
+ res = { ok: false, exitCode: 64, reason: `gate-error: ${e && e.message}`, violations: [] };
402
+ }
403
+ process.stdout.write(JSON.stringify(res, null, 2) + "\n");
404
+ process.exit(res.exitCode);
405
+ }
406
+
407
+ if (require.main === module) main();
408
+
409
+ module.exports = { run, gateDoc, checkLine, splitRegions, fencedBlocks, looksLikeParagraph, JARGON_TERMS, CODE_KEYWORDS };
package/bin/gsd-t.js CHANGED
@@ -1563,6 +1563,12 @@ const GLOBAL_BIN_TOOLS = [
1563
1563
  // this by absolute path, so it MUST ship wherever the verify gate ships or the
1564
1564
  // schema-id check throws ENOENT. Same class as the M99 store-resolver omission below.
1565
1565
  "gsd-t-schema-id-check.cjs",
1566
+ // v5.5.10 — PseudoCode §1.1 flow-line style gate (contract v1.2.0). The verify
1567
+ // workflow fires it on the same doc set as the guard-map gate, so it must ship
1568
+ // wherever verify ships. Also in PROJECT_BIN_TOOLS below — a tool wired into a
1569
+ // caller but absent from BOTH lists is dead in every project
1570
+ // ([[project_global_bin_propagation_gap]], 4 prior occurrences).
1571
+ "gsd-t-pseudocode-style.cjs",
1566
1572
  ];
1567
1573
 
1568
1574
  function installGlobalBinTools() {
@@ -2979,6 +2985,12 @@ const PROJECT_BIN_TOOLS = [
2979
2985
  // divergence-grammar = §4 parse/format round-trip (G4).
2980
2986
  "gsd-t-guard-map.cjs", "gsd-t-guard-map-derive.cjs", "gsd-t-milestone-state.cjs",
2981
2987
  "gsd-t-rule-consume.cjs", "gsd-t-divergence-grammar.cjs",
2988
+ // Contract v1.2.0 (2026-07-29) — the §1.1 flow-line STYLE gate, sibling of the
2989
+ // guard-map gate above. guard-map holds what a PseudoCode doc CONTAINS; this
2990
+ // holds how it READS (nested decision tree in plain English, technical terms
2991
+ // glossed alongside, code identifiers below the divider). Propagated so the
2992
+ // verify workflow's project-local runCli resolves it downstream.
2993
+ "gsd-t-pseudocode-style.cjs",
2982
2994
  // M93 — jargon-gloss lint for written docs (the file surface the brevity-guard
2983
2995
  // Stop hook can't reach). Propagated so a project's doc checks can invoke it.
2984
2996
  "gsd-t-jargon-lint.cjs",
@@ -5328,6 +5340,21 @@ if (require.main === module) {
5328
5340
  });
5329
5341
  process.exit(res.status == null ? 1 : res.status);
5330
5342
  }
5343
+ case "pseudocode-style": {
5344
+ // Contract v1.2.0 §1.1 — `gsd-t pseudocode-style (--doc <f> | --dir <d>)` thin
5345
+ // dispatcher to the PseudoCode flow-line STYLE gate. Sibling of `guard-map`:
5346
+ // that one gates what a doc CONTAINS, this one gates how it READS.
5347
+ const { spawnSync } = require("child_process");
5348
+ const js = path.join(__dirname, "gsd-t-pseudocode-style.cjs");
5349
+ if (!require("node:fs").existsSync(js)) {
5350
+ error(`gsd-t-pseudocode-style.cjs not found at ${js} — reinstall GSD-T (contract v1.2.0 §1.1)`);
5351
+ process.exit(1);
5352
+ }
5353
+ const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
5354
+ stdio: "inherit",
5355
+ });
5356
+ process.exit(res.status == null ? 1 : res.status);
5357
+ }
5331
5358
  case "architectural-trigger": {
5332
5359
  // M90 D1 — `gsd-t architectural-trigger <subcommand>` thin dispatcher to
5333
5360
  // the architectural-assumption trigger (divergence-sampling + extend-signal; §2).
@@ -99,11 +99,38 @@ the user, do not paper over it with a guess.
99
99
 
100
100
  ## Step 3: The subagent produces the output
101
101
 
102
- **A — Plain-English pseudocode** (the artifact), in the house style title + one-line purpose,
103
- `CURRENT` (what it does today) and `PROPOSED` (the simplest fix) blocks, `# plain comment` inline,
104
- a summary table, near-zero preamble. For each CURRENT block, say **why it does what it does now**
105
- — and flag explicitly if it's a "got complicated over time" accretion (mechanisms stacked by
106
- successive fixes). Unless `--chat-only`, write it to `.gsd-t/pseudocode/PseudoCode-<Target>.md`.
102
+ **A — Plain-English pseudocode** (the artifact), in the house style defined by contract
103
+ `.gsd-t/contracts/pseudocode-source-of-truth-contract.md` **§1.1** and the mold
104
+ `templates/PseudoCode-spec.md`. **THE FLOW IS THE DOCUMENT:**
105
+
106
+ ```
107
+ # Title
108
+ One sentence of purpose (two max).
109
+ The flow.
110
+ ---
111
+ Everything else.
112
+ ```
113
+
114
+ The flow is a **nested decision tree in plain English**, 2-space indent per level, one thing per
115
+ line, questions ending in `:` answered by indented `Yes:` / `No:` (or named outcomes) beneath
116
+ them. **No function-call syntax, no `if`/`return`/`throw`/`tx:`, no bare status codes or
117
+ SCREAMING_SNAKE constants, no paragraphs inside the flow.** A technical term rides **alongside**
118
+ plain words in parentheses rather than replacing them — plain first, glossed once per `##`
119
+ section: `Zoom's webhook (its automatic ping to us) arrives at /zoom/events`. Concrete real names
120
+ (Zoom, the Save button, the invoices table, `/zoom/events`) need no gloss; bare category-nouns
121
+ (webhook, payload, endpoint, token, cache) do.
122
+
123
+ Below the divider: `## What it does today` / `## What changes` (each its own flow in the same
124
+ style), `## The rules` (the `[RULE]` guard map), `## ⚠ Divergence`, `## Why this shape` (the
125
+ Six-Stage answers in plain sentences), `## Where it lives` (file pointers — these live HERE, never
126
+ in the flow). For each "what it does today" flow, say **why it does what it does now** — and flag
127
+ explicitly if it's a "got complicated over time" accretion (mechanisms stacked by successive
128
+ fixes).
129
+
130
+ Worked reference: `.gsd-t/pseudocode/PseudoCode-BrokenGraphHalts.md`. Unless `--chat-only`, write
131
+ the artifact to `.gsd-t/pseudocode/PseudoCode-<Target>.md`, then **self-check it** with
132
+ `gsd-t pseudocode-style --doc <the file>` — a non-zero exit means the style is wrong; fix it
133
+ before presenting. (The same gate is FAIL-blocking in verify.)
107
134
 
108
135
  **B — Session summary** (always printed, even under `--build`):
109
136
  - **Simply Stated** (REQUIRED FIRST LINE — the clarity gate) — the verdict + the single most
@@ -8,7 +8,7 @@ You are the doc-ripple agent. You identify and update all downstream documents a
8
8
  preflight → brief (kind=doc-ripple) → doc-ripple agent (opus, with phase protocol)
9
9
  ```
10
10
 
11
- The agent identifies the full blast radius of recent code changes and updates every affected document in one pass: `docs/requirements.md`, `docs/architecture.md`, `docs/workflows.md`, `.gsd-t/contracts/`, `PseudoCode-[Title].md` (the intention-first behavior map — when the implemented behavior diverges from its pseudocode, ripple the change or write a `⚠ Divergence` flag per the source-of-truth contract §4), owning `scope.md`, `README.md`, and (for this repo) the 4 command-reference files when a command interface changed.
11
+ The agent identifies the full blast radius of recent code changes and updates every affected document in one pass: `docs/requirements.md`, `docs/architecture.md`, `docs/workflows.md`, `.gsd-t/contracts/`, `PseudoCode-[Title].md` (the intention-first behavior map — when the implemented behavior diverges from its pseudocode, ripple the change or write a `⚠ Divergence` flag per the source-of-truth contract §4; **any edit keeps the §1.1 flow style** — the flow stays a nested plain-English decision tree, technical terms glossed alongside plain words, code identifiers below the `---` divider; self-check with `gsd-t pseudocode-style --doc <the file>`), owning `scope.md`, `README.md`, and (for this repo) the 4 command-reference files when a command interface changed.
12
12
 
13
13
  ## Step 1: Determine the change set
14
14
 
@@ -38,6 +38,8 @@ The agent then **presents this approach to the user for SIGN-OFF**. The user sig
38
38
 
39
39
  Only after the approach is signed off, the agent authors the detailed `PseudoCode-[Title].md` at exemplar granularity (the five section elements of contract §1: Intention, Mechanism, one-breath summary table, Guard map, Divergence flags, Appendix). `[Title]` is the SUBJECT the doc represents (e.g. `PseudoCode-PayPal.md`), never a milestone id; a milestone may produce several. Per-milestone docs live at `.gsd-t/pseudocode/PseudoCode-[Title].md` (contract §7).
40
40
 
41
+ **STYLE IS GOVERNED — contract §1.1, mold `templates/PseudoCode-spec.md`, worked reference `.gsd-t/pseudocode/PseudoCode-BrokenGraphHalts.md`.** The document is `# Title` → one sentence of purpose → **the flow** → `---` → everything else. The flow is a **nested decision tree in plain English** (2-space indent, one thing per line, questions ending `:` answered by indented `Yes:` / `No:` beneath). **No function-call syntax, no `if`/`return`/`throw`/`tx:`, no bare status codes or SCREAMING_SNAKE constants, no paragraphs inside the flow.** A technical term rides **alongside** plain words in parentheses rather than replacing them — plain first, glossed once per `##` section (`Zoom's webhook (its automatic ping to us) arrives at /zoom/events`); concrete real names need no gloss. The guard map, divergence flags, Six-Stage answers, and every file/function pointer sit **below the divider**. After writing, self-check with `gsd-t pseudocode-style --doc <the file>` — non-zero means fix it before presenting. The same gate is FAIL-blocking in verify.
42
+
41
43
  ### Default-ON; skip is a LOGGED decision
42
44
 
43
45
  The two-altitude flow is **default-ON**. Skipping the detailed-doc altitude is a **LOGGED decision** in `.gsd-t/progress.md` (Decision Log entry naming WHY it was skipped) — **never a silent default-off** (`feedback_no_silent_degradation`). A skip that is not logged is a process failure.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "5.4.11",
3
+ "version": "5.5.11",
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",
@@ -464,12 +464,57 @@ See memory pointer: `feedback_auto_research_external_gaps`.
464
464
  - **When a similar-but-not-reusable thing already exists:** (a) same WHAT (job) or same HOW (surface)? Same WHAT → generalize; merely similar → build new. (b) If generalize: **extract a shared core, do NOT mutate** the working original (old callers keep identical behavior). (c) If blast-radius/stability forbids touching it → build new, **but register a "reuse-candidate" link in the graph** pointing at the twin, so the duplication is visible and re-decided on next touch. **Never build a silent rogue twin** — sprawl disables Stage 3's reuse-check for everyone after you.
465
465
  - **A wrong forecast self-corrects:** the graph's similarity check surfaces a LOW-forecast function that got reused anyway at the next Stage 3. So the forecast need only be directionally right — the graph rescues the misses. That is what removes the paralysis.
466
466
 
467
- **§Plain-English proof (the artifact you can always review, never must):** the Six-Stage answers are written into the milestone's **PseudoCode document** in plain, jargon-free language (style: `.gsd-t/pseudocode/` — title + one-line purpose, `CURRENT`/`PROPOSED` blocks, `# plain comment` inline, a summary table; near-zero preamble). Jargon is where unexamined complexity hides — a layman-legible sentence has nowhere for a pointless operation to survive. The pseudocode IS the audit, and it lets the user approve *direction before code* as the senior reviewer, not a rubber-stamp.
467
+ **§Plain-English proof (the artifact you can always review, never must):** the Six-Stage answers are written into the milestone's **PseudoCode document** in plain, jargon-free language. Jargon is where unexamined complexity hides — a layman-legible sentence has nowhere for a pointless operation to survive. The pseudocode IS the audit, and it lets the user approve *direction before code* as the senior reviewer, not a rubber-stamp. **Style is governed, not freestyled — see § PseudoCode Style below.**
468
468
 
469
469
  **§Jargonless output (co-equal with brevity, NOT a trade-off):** short and clear are different axes; jargon is short, so brevity rules alone reward it. Every reply, plan, options-prompt, and mid-work narration glosses jargon in plain words on first use. The crux: individual shorthand may be decodable, but **several mashed into one sentence become unintelligible** — never force the reader toward an "I don't understand" escape hatch; if that option would help, the sentence already failed. Enforced by the Reader Contract (injected every turn).
470
470
 
471
471
  **§Enforcement (three layers — same shape as Unproven-Assumption):** (1) this doctrine = the *definition* (reference, always available); (2) a **PreToolUse hook on Write/Edit** = the *trigger* — injects a one-line reminder pointing here at the build moment, so it can't be missed under load; (3) the **plan/milestone workflow gate** = the *execution* — the Six-Stage Pass runs as blocking `agent()` steps with graph/doc evidence, and pseudocode-completeness is a verify check. Injecting the doctrine ≠ executing it; the workflow does the doing.
472
472
 
473
+ ### PseudoCode Style — the flow IS the document (MANDATORY)
474
+
475
+ **Contract:** `.gsd-t/contracts/pseudocode-source-of-truth-contract.md` §1.1 · **Mold:** `templates/PseudoCode-spec.md` · **Worked reference:** `.gsd-t/pseudocode/PseudoCode-BrokenGraphHalts.md`
476
+
477
+ A PseudoCode document exists for ONE job: letting the user approve **direction before code**. A doc the reader must translate line-by-line cannot do that job, so how it READS is governed, not a matter of taste.
478
+
479
+ ```
480
+ # Title
481
+ One sentence of purpose (two max).
482
+ The flow.
483
+ ---
484
+ Everything else.
485
+ ```
486
+
487
+ **The flow — a nested decision tree in plain English.** Not prose. Not transliterated code.
488
+
489
+ ```text
490
+ Settings screen
491
+ Enter a value in the Profile URL field — click Save
492
+ Is the URL the right shape:
493
+ Yes: Open the page it points at
494
+ Is that page a real profile page:
495
+ Yes: Save it, show the green check
496
+ No: Show "that page isn't a profile"
497
+ No: Show "that doesn't look like a profile URL"
498
+ ```
499
+
500
+ - 2-space indent per level; **one thing per line**; a question line ends with `:` and is answered by indented `Yes:` / `No:` (or named outcomes — `Found:` / `Expired:` / `Never seen:`) directly beneath it.
501
+ - **BANNED in the flow:** function-call syntax (`loadStore(path):`), code keywords (`if`/`return`/`throw`/`catch`/`tx:`), bare status codes (`→ 409`) or SCREAMING_SNAKE constants (`MODULE_NOT_FOUND`), and any paragraph. A line needing a paragraph means the flow is under-decomposed — split it.
502
+
503
+ **Words — the technical term rides ALONGSIDE plain words, never replaces them.** Keep the term (it's load-bearing and greppable); put the plain meaning first and the term in parentheses:
504
+
505
+ | Write this | Not this |
506
+ |---|---|
507
+ | `Zoom's webhook (its automatic ping to us) arrives at /zoom/events` | `A webhook fires` |
508
+ | `Read the message it sent (the payload) — pull out the meeting id` | `Parse the payload` |
509
+ | `Refuse it as a duplicate (409)` | `→ 409` |
510
+
511
+ - **Gloss once per `##` section**, then use the term bare; a new section re-glosses so a reader landing mid-file still follows.
512
+ - **Concrete real names need no gloss** — `Zoom`, `the Save button`, `the invoices table`, `/zoom/events`. Only abstract category-nouns (webhook, payload, endpoint, handler, token, cache) require the parenthetical.
513
+
514
+ **Below the divider** — and ONLY below it: `## What it does today` / `## What changes` (each its own flow, same style), `## The rules` (the `[RULE]` guard map), `## ⚠ Divergence`, `## Why this shape` (the Six-Stage answers in plain sentences), `## Where it lives` (file/function pointers). **Code identifiers live here, never in the flow.**
515
+
516
+ **§Enforcement:** `gsd-t pseudocode-style (--doc <f> | --dir <d>)` — deterministic, exit 0 clean / 4 violations / 64 bad input. FAIL-blocking in `gsd-t-verify` alongside the guard-map gate. It checks the flow block ONLY, so the `[RULE]` guard map below the divider is never style-checked. Self-check any pseudocode you write BEFORE presenting it. Docs predating contract v1.2.0 are listed in `.gsd-t/pseudocode/.style-grandfathered` and skip with a named reason; removing a name is how a doc opts in.
517
+
473
518
  ### No-Fallback-Ever Doctrine (governed, enforced — the strongest sibling of No-Silent-Degradation)
474
519
 
475
520
  **Pretend every project is a vampire and the word "fallback" is garlic.** A fallback — **anything that continues after a failure** (a `catch` that keeps going, `|| default`, a silent degrade, a secondary path, a "try X else Y" where Y masks X failing) — is **BANNED unless the user explicitly approves it first, OR you can cite a confirmed, reproducible case where the straight-line path provably fails and ONLY a fallback catches it.** No trivial exception. This is a Destructive-Action-Guard-class STOP: before writing any such branch, **halt and ask the user.**
@@ -1,194 +1,137 @@
1
- # {Title} — {one-line subject of this behavior map}
1
+ # {Title}
2
2
 
3
- **Pseudocode + intention for how {the subject} behaves, end to end.**
4
- Covers WHERE the system makes each decision, WHEN it refuses to, and how the
5
- result reaches every consumer. **{Scope line — what this is, and the one thing
6
- it deliberately does NOT do.}**
3
+ {One sentence: what happens, in plain words. Two max. Usually one is enough.}
7
4
 
8
- > **⚠ {THE LOAD-BEARING DIRECTIVE} ({Author}, {date}).** {The single sentence a
9
- > reader must not miss the WHY behind the whole map, stated as the user's
10
- > intention. Prose here is the USER's directive, never agent reasoning.}
5
+ ```text
6
+ {Where it startsa screen, an arriving message, a scheduled moment}
7
+ {The first thing that happens}
8
+ {Is <the question being decided>}:
9
+ Yes: {what happens}
10
+ {Is <the next question>}:
11
+ Yes: {what happens}
12
+ No: {what happens instead}
13
+ No: {what happens instead}
14
+ ```
11
15
 
12
- _Forward-looking behavior map for **{milestone / scope id}**. Not yet built —
13
- grounds itself in the EXISTING contracts it must respect:
14
- `{existing-contract-a}.md`, `{existing-contract-b}.md`, and the frozen
15
- `{Schema}` (`{field}` / `{field}` / `{field}`). Companion to
16
- `PseudoCode-{Other}.md`. See `{roadmap ref}` for the milestone scope._
16
+ ---
17
17
 
18
18
  <!--
19
19
  ─────────────────────────────────────────────────────────────────────────────
20
- HOW TO USE THIS MOLD (delete this comment block in the real instance)
20
+ HOW TO WRITE THIS (delete this comment block in the real instance)
21
21
  ─────────────────────────────────────────────────────────────────────────────
22
- • Name the instance PseudoCode-[Title].md where [Title] is the SUBJECT
23
- (PseudoCode-PayPal.md), never a milestone id. Only THIS blank mold keeps the
24
- `-spec` suffix.
25
- • Author the TWO ALTITUDES in order:
26
- 1. HIGH-LEVEL APPROACH what / why / when, the actors, a one-breath
27
- summary table. NO field-level detail. SIGN THIS OFF FIRST.
28
- 2. DETAILED — the full numbered `##` section set below, at
29
- exemplar granularity (one section per decision boundary).
30
- Every section carries the FIVE SECTION ELEMENTS (see the contract §1):
31
- Intention prose · Mechanism pseudocode · one-breath summary ·
32
- [RULE] guard map · Divergence flags · Appendix.
33
- Each `> **Intention.**` prose block sits ABOVE its fenced pseudocode and is
34
- dated + attributed; the prose is the USER's WHY, never agent reasoning.
35
- The Mechanism block grounds in EXISTING contracts/schema and DEFERS concrete
36
- identifiers to plan-time-against-the-real-schema.
37
- Guard map: render EVERY invariant as a one-line `[RULE] <invariant>` (or the
38
- tagged `<invariant> [RULE <tag>]` form). One marker = one rule.
39
- Divergence: wherever a NEW intention supersedes shipped code, write an
40
- explicit `⚠ Divergence:` flag. Keep = no flag.
41
- Cite each implementing plan task back with:
22
+
23
+ THE FLOW IS THE DOCUMENT. Everything else is an appendix below the divider
24
+ that the reader never has to open. Do not put explanation above the flow.
25
+
26
+ ── SHAPE ───────────────────────────────────────────────────────────────────
27
+ A nested decision tree, indented 2 spaces per level. Not prose. Not code.
28
+ Each line is one thing that happens, or one question being decided.
29
+ A question line ends with a colon and is answered by indented
30
+ `Yes:` / `No:` lines directly beneath it (or named outcomes when there are
31
+ more than two `Found:` / `Expired:` / `Never seen:`).
32
+ NO function-call syntax. Write `Save it against the client's record`, never
33
+ `saveForClient(clientId, record)`. No `if`/`return`/`throw`/`tx:`/`→`.
34
+ NO paragraphs inside the flow block. If a line needs a paragraph, the flow
35
+ is wrong split it into more lines.
36
+
37
+ ── WORDS ───────────────────────────────────────────────────────────────────
38
+ Plain English carries the sentence; the technical name rides ALONGSIDE it in
39
+ parentheses. The reader must never have to translate a line in their head.
40
+
41
+ GOOD: Zoom's webhook (its automatic ping to us) arrives at /zoom/events
42
+ GOOD: Read the message it sent (the payload) — pull out the meeting id
43
+ GOOD: Save it in the invoices table
44
+ BAD: A webhook fires (which webhook? whose?)
45
+ BAD: Parse the payload (untranslated jargon)
46
+ BAD: Return 401 (means nothing on its own)
47
+
48
+ • Name the REAL thing. "Zoom", "the Save button", "the invoices table",
49
+ "/zoom/events" are concrete and welcome. Bare category-nouns — webhook,
50
+ payload, endpoint, handler, token, cache — are not, unless glossed.
51
+ • GLOSS ONCE PER SECTION. First use in a section carries the plain
52
+ explanation in parentheses; later lines in that same section use it bare.
53
+ A new `##` section re-glosses, so a reader landing mid-file still follows.
54
+ • Plain FIRST, term in parentheses SECOND. Not the reverse.
55
+
56
+ ── FILE STRUCTURE ──────────────────────────────────────────────────────────
57
+ # Title
58
+ One-sentence purpose.
59
+ The flow.
60
+ ---
61
+ Everything else.
62
+
63
+ Below the divider, in any order, only what this subject actually has:
64
+ • `## What it does today` / `## What changes` — the before/after, each as
65
+ its own flow in the same style.
66
+ • `## The rules` — the guard map. Every invariant as a one-line `[RULE]`
67
+ (grammar owned by the contract §2 — do not re-derive it here).
68
+ • `## ⚠ Divergence` — flags wherever this supersedes shipped behavior.
69
+ Keeping existing behavior = no flag. (Contract §4.)
70
+ • `## Why this shape` — the Architect's Six-Stage answers, in plain words.
71
+ • `## Where it lives` — file and function pointers, if useful. These belong
72
+ HERE, not in the flow.
73
+
74
+ ── NAMING + CITATION ───────────────────────────────────────────────────────
75
+ • Name the file `PseudoCode-[Title].md` where [Title] is the SUBJECT
76
+ (`PseudoCode-ProfileUrlSave.md`), never a milestone id. Only this blank
77
+ mold keeps the `-spec` suffix.
78
+ • A milestone may produce several files — one per coherent subject.
79
+ • Plan tasks cite a section back with:
42
80
  **PseudoCode-Section**: {Title}#<github-slug-of-the-## heading>
43
- Grammars are owned by the contract:
44
- `.gsd-t/contracts/pseudocode-source-of-truth-contract.md`
45
- (§2 guard-map, §3 section-citation, §4 divergence). Do NOT re-derive them here.
81
+ Grammars live in `.gsd-t/contracts/pseudocode-source-of-truth-contract.md`
82
+ (§1.1 flow style · §2 guard-map · §3 citation · §4 divergence). Do NOT
83
+ re-derive them here.
46
84
  ─────────────────────────────────────────────────────────────────────────────
47
85
  -->
48
86
 
49
- ---
50
-
51
- <!-- ═══════════════ ALTITUDE 1 — HIGH-LEVEL APPROACH (sign off FIRST) ═══════════════ -->
52
-
53
- ## The one {call / decision}, in one breath
54
-
55
- > **High-Level Approach ({Author}, {date}).** {What this does, why, and when —
56
- > the actors and the single decision, in plain language. No field-level detail.
57
- > This altitude is signed off BEFORE the Detailed sections below are written.}
58
-
59
- | {Call / decision} | Lives in | Decides | Runs only when… |
60
- |-------------------|----------|---------|-----------------|
61
- | **{The call}** | **{realm — Server / Extension / …}** | {what it turns the input into} | {the precondition that triggers it} |
62
- | **{The read / fallback}** | **{realm}** | {the question it answers} | {seller- / event-initiated trigger} |
63
-
64
- > **SCOPE ({Author}, {date}).** {The deliberate OUT-of-scope boundary — what this
65
- > map does NOT do. State the one status / call / surface it touches and the ones
66
- > it refuses to touch. This keeps the Detailed sections honest.}
67
-
68
- ---
69
-
70
- <!-- ═══════════════ ALTITUDE 2 — DETAILED (exemplar granularity) ═══════════════ -->
71
-
72
- ## 0. Where this picks up — {precondition / what exists before}
73
-
74
- > **Intention ({Author}, {date}).** {What state the system is in when this map
75
- > begins — the precondition. If this SUPERSEDES a shipped model, say so here and
76
- > add the ⚠ Divergence flag below.}
87
+ ## What it does today
77
88
 
78
89
  ```text
79
- PRECONDITION:
80
- {The exact starting state — what is loaded / grouped / present, and what is
81
- deliberately NOT persisted yet.}
90
+ {The current flow, same nested style. Delete this section for new behavior
91
+ that has no "today".}
82
92
  ```
83
93
 
84
- > **Divergence from shipped {what} (plan-time reconcile):** {what the shipped
85
- > code does today} vs. {what THIS intention does instead}. {What becomes dead
86
- > code / what is reused.} Flag for the {milestone} plan.
87
- > *(Keep = delete this flag. Supersede = keep it, per contract §4.)*
88
-
89
- ---
90
-
91
- ## 1. {First boundary} — {the trigger → the actor that owns it}
92
-
93
- > **Intention.** {WHY this step exists and what the user wants it to do — the
94
- > directive. One short paragraph, the user's voice.}
94
+ ## What changes
95
95
 
96
96
  ```text
97
- {ACTOR} on {trigger}:
98
- {step}
99
- {the call / the guard}
100
- on {outcome}: {what happens}
101
- on {failure}: {the safe fallback — never a crash}
97
+ {The new flow, same nested style. The reader should be able to diff this
98
+ against the section above by eye.}
102
99
  ```
103
100
 
104
101
  ---
105
102
 
106
- ## 2. {Second boundary} — `{the operation}` (★ {why this one is load-bearing})
107
-
108
- > **Intention.** {The WHY. Name the one invariant this section exists to protect
109
- > — e.g. "every guard makes a double-click HARMLESS".}
103
+ ## The rules
110
104
 
111
105
  ```text
112
- {operationName}({inputs}):
113
-
114
- # ── GATE 1 {what it validates} (RULE) ────────────────────────────────
115
- # Intention: {why this gate exists, in the user's voice}.
116
- {load / lock}
117
- if {precondition fails}: → {status / error} # {what it protects}
118
-
119
- # ── GATE 2 — {backstop} (RULE, backstop) ───────────────────────────────
120
- # Intention: {why — name it a backstop if upstream already blocks it}.
121
- if {impossible condition}: → {status}
122
-
123
- # ── STEP 3 — {the side-effecting call} ───────────────────── ★ {marker}
124
- # Intention: {what crosses the boundary and what is deferred to real schema}.
125
- {the call} # defer concrete ids to plan-time
126
-
127
- # ── STEP 4 — PERSIST + {atomic effect} (RULE, one tx) ──────────────────
128
- # Intention: {the record is born / the state flips — atomically}.
129
- in ONE tx:
130
- {write}
131
- {flip state}
132
- return {success shape}
133
-
134
- # ── FAILURE — never half-{do the thing} (RULE) ─────────────────────────
135
- # Intention: a failed {op} persists NOTHING — safe retry.
136
- on {failure} (any point): nothing persisted ; → {status}
106
+ {invariant in one line} [RULE] {the invariant}
107
+ {what can never happen} [RULE] {the prohibition}
108
+ {what happens on failure} [RULE] {the safe-failure invariant}
137
109
  ```
138
110
 
139
- ---
140
-
141
- ## 3. {Further boundaries as needed} — {one `##` section per decision}
142
-
143
- > **Intention.** {Add as many numbered `##` sections as the subject has distinct
144
- > decision boundaries — match the exemplars' granularity, one section per real
145
- > decision, not one giant section.}
146
-
147
- ```text
148
- {the mechanism for this boundary}
149
- ```
111
+ {One short paragraph, plain words: the one thing that must never happen, and
112
+ why repeating any step is harmless.}
150
113
 
151
114
  ---
152
115
 
153
- ## {N}. {Subject}-safety map — every guard, as a one-line [RULE]
116
+ ## Divergence
154
117
 
155
- ```text
156
- GATE: {condition} {status} [RULE] {the invariant in one line}
157
- GATE: {lock / serialize} [RULE] {what it prevents}
158
- {deterministic guard} [RULE — {tag}] {invariant, tagged form}
159
- {never-do-this} [RULE] {the prohibition}
160
- {born / set at this exact point} [RULE] {the lifecycle invariant}
161
- on {failure}: persist NOTHING [RULE] {the safe-failure invariant}
162
- ```
163
-
164
- **{One paragraph restating the whole safety story in prose: the record is born
165
- WHEN, the lock means WHAT, the one thing that can never happen, and why every
166
- retry / double-action is harmless.}**
118
+ {`⚠ Divergence: <section or RULE-ID> — supersedes shipped <what>. Reason: <the
119
+ intention>.` for each supersede. Delete this whole section if nothing is
120
+ superseded.}
167
121
 
168
122
  ---
169
123
 
170
- ## {Optional} {ONE STORE / shared-state / known-gaps notes}
124
+ ## Why this shape
171
125
 
172
- > {Optional sections the exemplars carry: a shared-store note, a "Known gaps /
173
- > status (as of {version})" list. Include only if the subject has them.}
126
+ {The Architect's Six-Stage answers, in plain jargon-free sentences. What the
127
+ objective is · what it conflicts with · what already exists that we reuse ·
128
+ why this is the simplest version · whether it will be reused again · what
129
+ could go wrong.}
174
130
 
175
131
  ---
176
132
 
177
- ## Appendix Raw pseudocode (no intention comments)
133
+ ## Where it lives
178
134
 
179
- ```text
180
- # ════════════════════════════════════════════════════════════════════════════
181
- # {REALM A} {what it covers}
182
- # ════════════════════════════════════════════════════════════════════════════
183
- {the §1–§N mechanism, intention prose STRIPPED — the build's quick-reference}
184
-
185
- # ════════════════════════════════════════════════════════════════════════════
186
- # {REALM B} — {what it covers}
187
- # ════════════════════════════════════════════════════════════════════════════
188
- {operationName}({inputs}):
189
- {guard} # {one-line reason}
190
- {the call} # ★ {the load-bearing call}
191
- tx: {write} ; {flip state}
192
- return {success}
193
- on fail: persist NOTHING ; → {status} # safe retry
194
- ```
135
+ | Step in the flow | File |
136
+ |------------------|------|
137
+ | {the flow line} | `{path}` |
@@ -38,6 +38,8 @@ Every **supersede** WRITES a `⚠ Divergence` flag into `PseudoCode-[Title].md`,
38
38
 
39
39
  **KEEP writes no flag.** One supersede → exactly one flag. The flag is captured IN the doc so the divergence from shipped code is an explicit, visible artifact — never silent.
40
40
 
41
+ **WHERE the flag goes (contract §1.1.4):** under the `## ⚠ Divergence` heading **below the first `---` divider** — never inside the flow block at the top of the document. The flow is the readable decision tree the user approves; divergence flags, the guard map, and file pointers all live below the divider. (The `gsd-t pseudocode-style` gate checks the flow block only, so a flag placed correctly is never style-checked — one placed in the flow will fail it.)
42
+
41
43
  > **M87/M88 split.** In M87 this flag is WRITTEN by this prose protocol (the ASK + the write). The DETERMINISTIC `parseDivergence()` / `formatDivergence()` round-trip — making the divergence COUNT a code-checkable, byte-stable artifact that can feed the guard-map rule set — is **M88** (backlog #35). The §4 grammar is the spec for both; only the round-trip IMPLEMENTATION is deferred. Here, your obligation is: ask, and on supersede, write a well-formed flag.
42
44
 
43
45
  ## What to do (step by step)
@@ -869,6 +869,7 @@ ${STATED_CLAIMS_INSTRUCTION}`,
869
869
  TWO-ALTITUDE INTENTION-FIRST FLOW (M87, default-ON — contract pseudocode-source-of-truth-contract.md §1). Author the milestone at two altitudes, IN ORDER:
870
870
  ALTITUDE 1 — HIGH-LEVEL APPROACH (signed off FIRST): emit the high-level approach pseudocode — what/why/when (the user's intention, never agent reasoning), the actors, and a one-breath summary ("one call in one breath"). PRESENT this approach to the user for SIGN-OFF. The detailed doc is authored ONLY AFTER the approach is approved — the sign-off is the checkpoint between the two altitudes. (This is a PROSE flow: describe the checkpoint; do NOT assert a machine-checkable DEFINED-state predicate — that is M88.)
871
871
  ALTITUDE 2 — DETAILED doc: only after the approach is signed off, author .gsd-t/pseudocode/PseudoCode-[Title].md at exemplar granularity (the §1 section set: Intention, Mechanism, one-breath table, Guard map, Divergence flags, Appendix). [Title] = the SUBJECT (e.g. PseudoCode-PayPal.md), never a milestone id; a milestone may produce several.
872
+ STYLE (contract §1.1, GOVERNED + machine-gated): "# Title" → one sentence of purpose → THE FLOW → "---" → everything else. The flow is a NESTED DECISION TREE IN PLAIN ENGLISH — 2-space indent per level, one thing per line, a question line ending ":" answered by indented "Yes:" / "No:" (or named outcomes) directly beneath it. BANNED in the flow: function-call syntax (loadStore(path):), code keywords (if/return/throw/catch/tx:), bare status codes (→ 409) or SCREAMING_SNAKE constants (MODULE_NOT_FOUND), and any paragraph — a line needing a paragraph means the flow is under-decomposed, so split it. A technical term rides ALONGSIDE plain words in parentheses rather than replacing them: plain FIRST, term second, glossed once per "##" section — "Zoom's webhook (its automatic ping to us) arrives at /zoom/events", "Read the message it sent (the payload) — pull out the meeting id". Concrete real names (Zoom, the Save button, the invoices table, /zoom/events) need no gloss; abstract category-nouns (webhook, payload, endpoint, handler, token, cache) do. The guard map, divergence flags, Six-Stage answers, and every file/function pointer sit BELOW the divider. Mold: templates/PseudoCode-spec.md. Worked reference: .gsd-t/pseudocode/PseudoCode-BrokenGraphHalts.md. Self-check with: gsd-t pseudocode-style --doc <the file> (non-zero = fix before presenting; the same gate is FAIL-blocking in verify).
872
873
  DEFAULT-ON; skip is a LOGGED decision in progress.md naming WHY — NEVER a silent default-off (feedback_no_silent_degradation).
873
874
  KEEP-OR-SUPERSEDE: before encoding any model inherited from shipped code, run the keep-or-supersede protocol (templates/prompts/keep-or-supersede-subagent.md) — per inherited model ASK keep or supersede; each supersede WRITES a ⚠ Divergence flag (§4 grammar) into the doc. Keep = no flag.
874
875
 
@@ -914,8 +915,19 @@ const _architectPassLine = _ARCHITECT_PHASES.has(phaseName)
914
915
  `ARCHITECT'S OVERSIGHT — run this Six-Stage Pass BEFORE finalizing the ${phaseName}, IN ORDER.`,
915
916
  `Each stage can KILL/reshape the plan. Answer each with EVIDENCE (a grep, a Read, or the`,
916
917
  `graph slice above) — never conviction. Record the answers in plain, jargon-free language`,
917
- `in the milestone's PseudoCode document (.gsd-t/pseudocode/), house style: title + one-line`,
918
- `purpose, CURRENT/PROPOSED blocks, "# plain comment" inline, a summary table.`,
918
+ `in the milestone's PseudoCode document (.gsd-t/pseudocode/), in the GOVERNED style of`,
919
+ `contract pseudocode-source-of-truth-contract.md §1.1: "# Title" one sentence of purpose`,
920
+ `→ THE FLOW → "---" → everything else. The flow is a NESTED DECISION TREE IN PLAIN ENGLISH`,
921
+ `(2-space indent, one thing per line, a question line ending ":" answered by indented`,
922
+ `"Yes:" / "No:" beneath it). BANNED in the flow: function-call syntax, if/return/throw/tx:,`,
923
+ `bare status codes or SCREAMING_SNAKE constants, and any paragraph. A technical term rides`,
924
+ `ALONGSIDE plain words in parentheses rather than replacing them — plain first, glossed once`,
925
+ `per "##" section (e.g. "Zoom's webhook (its automatic ping to us) arrives at /zoom/events");`,
926
+ `concrete real names (Zoom, the Save button, the invoices table) need no gloss. The Six-Stage`,
927
+ `answers go under "## Why this shape" BELOW the divider, alongside the guard map, divergence`,
928
+ `flags, and any file/function pointers — code identifiers NEVER appear in the flow.`,
929
+ `Self-check with: gsd-t pseudocode-style --doc <the file> (non-zero = fix before presenting;`,
930
+ `the same gate is FAIL-blocking in verify).`,
919
931
  ` 1. OBJECTIVE — what is the core objective, and why is it the objective?`,
920
932
  ` 2. CONFLICT — does it conflict with another objective? must a prior one be re-examined?`,
921
933
  ` 3. REUSE — have I already done any piece of this? Can I reuse the PROCESS, or the OUTPUT`,
@@ -39,7 +39,7 @@ export const meta = {
39
39
  { title: "Auto-Research Gate", detail: "M89 §7 ENFORCE: scan for status=uncited markers (A4 — no silent guess)" },
40
40
  { title: "CI-Parity", detail: "M57 build-coverage + ci-parity (FAIL-blocking)" },
41
41
  { title: "Test-Data Purge", detail: "M58 test-data --purge (FAIL-blocking)" },
42
- { title: "Guard-Map Gate", detail: "M87 [RULE] guard-map gate (FAIL-blocking, §7 discovery)" },
42
+ { title: "Guard-Map Gate", detail: "M87 [RULE] guard-map gate + §1.1 flow-line style gate (FAIL-blocking, §7 discovery)" },
43
43
  { title: "Orthogonal Triad", detail: "code-review ultra ∥ Red Team ∥ QA" },
44
44
  { title: "Synthesis", detail: "merge without collapsing categories" },
45
45
  ],
@@ -721,6 +721,51 @@ if (guardMapDiscovery.docsFound === 0 || (guardMapDiscovery.firePairs || []).len
721
721
  log(`M87 guard-map gate green (${guardMapResults.length} doc+map pair(s) fired) — proceeding to orthogonal triad`);
722
722
  }
723
723
 
724
+ // ─────────────────────────────────────────────────────────────────────────────
725
+ // PseudoCode §1.1 flow-line STYLE gate (contract v1.2.0, FAIL-blocking)
726
+ //
727
+ // Sibling of the guard-map gate above: guard-map holds what a doc CONTAINS, this
728
+ // holds how it READS. A PseudoCode doc exists so the user can approve DIRECTION
729
+ // before code; a doc written as transliterated source (`loadStore(path):`,
730
+ // `MODULE_NOT_FOUND`, `→ 409`) buried under preamble cannot do that job.
731
+ //
732
+ // Needs no discovery agent — it globs the dir itself (contract §7 reused) and is
733
+ // pure deterministic code. Pre-v1.2.0 docs are grandfathered via
734
+ // .gsd-t/pseudocode/.style-grandfathered as a logged skip WITH A REASON.
735
+ // ─────────────────────────────────────────────────────────────────────────────
736
+ const styleGate = await runCli(
737
+ projectDir,
738
+ "pseudocode-style",
739
+ ["--dir", `${projectDir}/.gsd-t/pseudocode`, "--json"],
740
+ "gsd-t-pseudocode-style.cjs",
741
+ "pseudocode-style",
742
+ true,
743
+ "Guard-Map Gate"
744
+ );
745
+ const styleEnv = styleGate.envelope || {};
746
+ if (styleGate.exitCode === 64) {
747
+ // Bad input (missing dir) — a project with no .gsd-t/pseudocode/ has nothing to
748
+ // gate. Surfaced, not silent.
749
+ log(`PseudoCode style gate: SKIP — ${styleEnv.reason || "no .gsd-t/pseudocode directory"}`);
750
+ } else if (!styleGate.ok) {
751
+ const v = styleEnv.violations || [];
752
+ const named = v.slice(0, 8).map((x) => `${(x.doc || "").split("/").pop()}:${x.line} ${x.rule}`).join("; ")
753
+ || styleEnv.reason || "(violation detail unavailable)";
754
+ log(`PseudoCode style gate FAIL exitCode=${styleGate.exitCode} — ${v.length} violation(s): ${named} — halting before triad`);
755
+ return {
756
+ status: "pseudocode-style-gate-failed",
757
+ overallVerdict: "VERIFY-FAILED",
758
+ reason: `PseudoCode §1.1 flow-line style violations (${v.length}): ${named}. The flow must read as a nested decision tree in plain English — technical terms glossed alongside plain words, code identifiers below the --- divider. See templates/PseudoCode-spec.md and .gsd-t/pseudocode/PseudoCode-BrokenGraphHalts.md for the worked reference.`,
759
+ pseudocodeStyle: styleEnv,
760
+ guardMap: { discovery: guardMapDiscovery, results: guardMapResults },
761
+ verifyGate: vg.envelope,
762
+ autoResearchGate: arGate,
763
+ };
764
+ } else {
765
+ const skipped = (styleEnv.skips || []).length;
766
+ log(`PseudoCode style gate: PASS (${styleEnv.docsChecked || 0} doc(s), ${skipped} grandfathered) — proceeding to orthogonal triad`);
767
+ }
768
+
724
769
  phase("Orthogonal Triad");
725
770
 
726
771
  const briefRef = brief.briefPath || "(brief generation failed — re-walk repo)";