@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 +43 -0
- package/README.md +1 -1
- package/bin/gsd-t-pseudocode-style.cjs +409 -0
- package/bin/gsd-t.js +27 -0
- package/commands/gsd-t-architect.md +32 -5
- package/commands/gsd-t-doc-ripple.md +1 -1
- package/commands/gsd-t-milestone.md +2 -0
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +46 -1
- package/templates/PseudoCode-spec.md +100 -157
- package/templates/prompts/keep-or-supersede-subagent.md +2 -0
- package/templates/workflows/gsd-t-phase.workflow.js +14 -2
- package/templates/workflows/gsd-t-verify.workflow.js +46 -1
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.
|
|
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
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
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
|
|
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}
|
|
1
|
+
# {Title}
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
```text
|
|
6
|
+
{Where it starts — a 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
|
-
|
|
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
|
|
20
|
+
HOW TO WRITE THIS (delete this comment block in the real instance)
|
|
21
21
|
─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
98
|
-
|
|
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
|
-
##
|
|
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
|
-
{
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
##
|
|
116
|
+
## ⚠ Divergence
|
|
154
117
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
##
|
|
124
|
+
## Why this shape
|
|
171
125
|
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
##
|
|
133
|
+
## Where it lives
|
|
178
134
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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/),
|
|
918
|
-
`
|
|
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)";
|