@tekyzinc/gsd-t 4.7.11 → 4.9.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.
@@ -0,0 +1,340 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GSD-T Stop hook — blocks exhausting ANSWER-mode replies before the user reads them.
4
+ *
5
+ * The deterministic enforcement the prose "be concise" rule never had: when the
6
+ * assistant answers a question with stacked process-narration (intent-without-content)
7
+ * preamble, or drops a bare unglossed high-signal jargon token, this hook BLOCKS the
8
+ * stop and forces a rewrite (answer-first, glossed). Tuned CONSERVATIVE: catch only the
9
+ * egregious cases (≥2 stacked narration sentences, or a bare high-signal token) — never
10
+ * police word count, tone, or every acronym.
11
+ *
12
+ * ─── Stdin (Claude Code Stop hook payload) ───────────────────────────────────
13
+ * { "transcript_path": "...", "stop_hook_active": true|false,
14
+ * "session_id": "...", "hook_event_name": "Stop", "cwd": "..." }
15
+ *
16
+ * ─── Block contract (confirmed via Claude Code hooks docs) ────────────────────
17
+ * https://code.claude.com/docs/en/hooks.md
18
+ * A Stop hook blocks by writing `{"decision":"block","reason":"<why>"}` to stdout
19
+ * and exiting 0. `reason` is fed back to Claude as the instruction for what to fix
20
+ * (here: rewrite answer-first / gloss the term). We use THIS structured form (not
21
+ * exit-2-stderr) so the reason is delivered cleanly. Exit 0 with no JSON = allow.
22
+ *
23
+ * ─── Loop-guard ──────────────────────────────────────────────────────────────
24
+ * If `stop_hook_active === true` (this Stop is already a re-entry from a prior
25
+ * block), EXIT 0 immediately — never block twice on the same turn (no infinite
26
+ * rewrite loop).
27
+ *
28
+ * ─── Mode discriminator ──────────────────────────────────────────────────────
29
+ * ACTION-mode (about to change code: latest assistant turn carried a mutating
30
+ * tool_use — Write/Edit/NotebookEdit, or a Bash mutation — OR is a tool_use-only
31
+ * turn with empty text) → intent-first is WANTED → ALLOW.
32
+ * ANSWER-mode (a pure-text reply to a question) → enforce answer-first.
33
+ *
34
+ * ─── FAIL-OPEN (non-negotiable) ──────────────────────────────────────────────
35
+ * ANY internal error / unreadable transcript / malformed payload / no assistant
36
+ * message → EXIT 0 (allow). A broken guard must NEVER gag a legitimate reply.
37
+ *
38
+ * ─── CLI (unit-testable without a transcript) ────────────────────────────────
39
+ * node gsd-t-brevity-guard.js --text "<reply>" --mode answer|action
40
+ * → prints `{"decision":"block","reason":...}` + exit 1 on BLOCK,
41
+ * exit 0 (no output) on ALLOW. (CLI exit 1 ≠ hook exit; hook always exits 0.)
42
+ *
43
+ * ─── INTEGRATE-SEAM (do NOT wire here) ───────────────────────────────────────
44
+ * Add to ~/.claude/settings.json (and the installer settings template):
45
+ * "hooks": { "Stop": [ { "hooks": [ {
46
+ * "type": "command",
47
+ * "command": "node ~/.claude/scripts/hooks/gsd-t-brevity-guard.js"
48
+ * } ] } ] }
49
+ *
50
+ * Zero deps. Pure text analysis. Zero LLM. Never throws.
51
+ */
52
+
53
+ "use strict";
54
+
55
+ const fs = require("fs");
56
+ const path = require("path");
57
+ const os = require("os");
58
+
59
+ // ── Jargon allowlist (exempt acronyms — extend conservatively) ──────────────
60
+ const ALLOWLIST = new Set([
61
+ "GSD-T", "QA", "CLI", "API", "URL", "DB", "JSON", "HTML", "CSS", "HTTP",
62
+ "NDJSON", "DTO", "PDT", "EST", "UTC", "AND", "OR", "NOT",
63
+ ]);
64
+
65
+ // High-signal code-token patterns (bare, unglossed → block on first use).
66
+ const JARGON_PATTERNS = [
67
+ /\bS2-M\d+\b/, // S2-M7
68
+ /\bHC-\d+\b/, // HC-003
69
+ /\bM\d+(?:-D\d+(?:-T\d+)?)?\b/, // M92, M92-D1, M92-D1-T3
70
+ ];
71
+
72
+ // Narration openers: first-person "about to" framing — intent without content.
73
+ const NARRATION_OPENER =
74
+ /^(let me|before i\b|i'?ll\b|i'?m going to|i am going to|first,?\s+let me|i want to|i need to|let's|let us|going to)\b/i;
75
+
76
+ // ── Transcript tail-scan (REUSED from gsd-t-conversation-capture.js) ─────────
77
+ // Pull the most recent assistant turn from a Claude Code transcript JSONL by
78
+ // scanning from the tail. Returns { text, hasMutatingTool, toolOnly } or null.
79
+ // Distinguishes text blocks (the answer) from tool_use blocks (the action signal).
80
+ function _safeTranscriptPath(p) {
81
+ if (typeof p !== "string" || p.length === 0) return null;
82
+ if (!path.isAbsolute(p)) return null;
83
+ const home = process.env.HOME || os.homedir();
84
+ if (!home) return null;
85
+ const allowedRoot = path.resolve(home, ".claude", "projects") + path.sep;
86
+ const resolved = path.resolve(p);
87
+ if (!resolved.startsWith(allowedRoot)) return null;
88
+ return resolved;
89
+ }
90
+
91
+ function _readFileTail(filePath, bytes) {
92
+ let fd = -1;
93
+ try {
94
+ const st = fs.statSync(filePath);
95
+ if (!st.isFile()) return "";
96
+ const size = st.size;
97
+ if (size === 0) return "";
98
+ const want = Math.min(bytes, size);
99
+ const start = size - want;
100
+ fd = fs.openSync(filePath, "r");
101
+ const buf = Buffer.alloc(want);
102
+ fs.readSync(fd, buf, 0, want, start);
103
+ let str = buf.toString("utf8");
104
+ if (start > 0) {
105
+ const nl = str.indexOf("\n");
106
+ if (nl >= 0) str = str.slice(nl + 1);
107
+ }
108
+ return str;
109
+ } catch (_) {
110
+ return "";
111
+ } finally {
112
+ if (fd >= 0) { try { fs.closeSync(fd); } catch (_) { /* noop */ } }
113
+ }
114
+ }
115
+
116
+ const MUTATING_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
117
+ // Bash command is a mutation if it invokes a write-ish verb (heuristic, conservative).
118
+ const BASH_MUTATION =
119
+ /\b(rm|mv|cp|mkdir|touch|tee|chmod|chown|git\s+(commit|add|push|rm|mv|checkout|reset|merge|rebase)|npm\s+(install|i|publish|run)|node\s|>>?|sed\s+-i)\b/;
120
+
121
+ function _isMutatingToolBlock(b) {
122
+ if (!b || b.type !== "tool_use") return false;
123
+ if (MUTATING_TOOLS.has(b.name)) return true;
124
+ if (b.name === "Bash") {
125
+ const cmd = b.input && typeof b.input.command === "string" ? b.input.command : "";
126
+ return BASH_MUTATION.test(cmd);
127
+ }
128
+ return false;
129
+ }
130
+
131
+ function _readAssistantFromTranscript(transcriptPath) {
132
+ const safe = _safeTranscriptPath(transcriptPath);
133
+ if (!safe) return null;
134
+ const tail = _readFileTail(safe, 64 * 1024);
135
+ if (!tail) return null;
136
+ const lines = tail.split("\n");
137
+ for (let i = lines.length - 1; i >= 0; i--) {
138
+ const line = lines[i];
139
+ if (!line) continue;
140
+ let row;
141
+ try { row = JSON.parse(line); } catch (_) { continue; }
142
+ if (!row || row.type !== "assistant") continue;
143
+ if (row.isSidechain === true) continue;
144
+ const msg = row.message;
145
+ if (!msg) continue;
146
+ const blocks = msg.content;
147
+ if (typeof blocks === "string") {
148
+ return { text: blocks, hasMutatingTool: false, toolOnly: false };
149
+ }
150
+ if (!Array.isArray(blocks)) continue;
151
+ const texts = [];
152
+ let hasMutatingTool = false;
153
+ let hasToolUse = false;
154
+ for (const b of blocks) {
155
+ if (b && b.type === "text" && typeof b.text === "string") texts.push(b.text);
156
+ if (b && b.type === "tool_use") {
157
+ hasToolUse = true;
158
+ if (_isMutatingToolBlock(b)) hasMutatingTool = true;
159
+ }
160
+ }
161
+ const text = texts.join("");
162
+ if (text.length === 0 && !hasToolUse) continue; // empty, keep scanning
163
+ return { text, hasMutatingTool, toolOnly: text.trim().length === 0 && hasToolUse };
164
+ }
165
+ return null;
166
+ }
167
+
168
+ // ── Detection (pure) ─────────────────────────────────────────────────────────
169
+
170
+ // Strip the dated status banner (first line, "Day: Mon DD, YYYY ... — GSD-T ...").
171
+ function _stripBanner(text) {
172
+ const nl = text.indexOf("\n");
173
+ const first = nl >= 0 ? text.slice(0, nl) : text;
174
+ if (/^[A-Z][a-z]{2,8}:\s+\w{3}\s+\d{1,2},\s+\d{4}/.test(first.trim())) {
175
+ return nl >= 0 ? text.slice(nl + 1) : "";
176
+ }
177
+ return text;
178
+ }
179
+
180
+ // Remove fenced code blocks and table rows (content, never preamble).
181
+ function _stripStructured(text) {
182
+ const out = [];
183
+ let inFence = false;
184
+ for (const raw of text.split("\n")) {
185
+ const line = raw.trim();
186
+ if (/^```/.test(line)) { inFence = !inFence; continue; }
187
+ if (inFence) continue;
188
+ if (/^\|.*\|/.test(line)) continue; // table row
189
+ out.push(raw);
190
+ }
191
+ return out.join("\n");
192
+ }
193
+
194
+ // Split prose into sentences (coarse — period/!/? or newline boundary).
195
+ function _sentences(text) {
196
+ return text
197
+ .replace(/\s+/g, " ")
198
+ .split(/(?<=[.!?])\s+/)
199
+ .map((s) => s.trim())
200
+ .filter((s) => s.length > 0);
201
+ }
202
+
203
+ // Count narration sentences in the LEADING region (before substantive content).
204
+ // A reply may open with ONE short acknowledgement/framing sentence (e.g. "Important
205
+ // question." / "Fair challenge.") — that's wanted, not preamble. But narration that
206
+ // STACKS after such an ack ("Let me find the record. Before I answer, I should check…")
207
+ // is the egregious case the user flagged. So: tolerate a single short non-narration
208
+ // lead sentence, then count narration; stop at the first substantive (non-narration,
209
+ // non-short-ack) sentence. Block fires at >=2 narration in that leading region.
210
+ function _leadingNarrationCount(prose) {
211
+ const sentences = _sentences(prose);
212
+ let count = 0;
213
+ let ackUsed = false;
214
+ for (const s of sentences) {
215
+ if (NARRATION_OPENER.test(s)) { count++; continue; }
216
+ // A short non-narration sentence (<= ~12 words) is treated as an allowed
217
+ // acknowledgement/framing line ONCE; a second one, or any longer sentence,
218
+ // is real content → the leading region ends.
219
+ const words = s.trim().split(/\s+/).length;
220
+ if (!ackUsed && words <= 12) { ackUsed = true; continue; }
221
+ break;
222
+ }
223
+ return count;
224
+ }
225
+
226
+ // Find a bare unglossed high-signal jargon token (first occurrence, no gloss).
227
+ // A gloss = parenthetical or quote in the SAME sentence containing the token.
228
+ function _findBareJargon(prose) {
229
+ const sentences = _sentences(prose);
230
+ const seen = new Set();
231
+ for (const s of sentences) {
232
+ for (const re of JARGON_PATTERNS) {
233
+ const m = s.match(re);
234
+ if (!m) continue;
235
+ const token = m[0];
236
+ if (seen.has(token)) continue;
237
+ // skip allowlisted ALL-CAPS lookalikes (none of these patterns are, but defensive)
238
+ if (ALLOWLIST.has(token)) continue;
239
+ seen.add(token);
240
+ // Glossed if a parenthetical or quoted phrase appears in the same sentence.
241
+ const hasGloss = /\([^)]*\)/.test(s) || /["“'][^"”']{4,}["”']/.test(s);
242
+ if (!hasGloss) return token;
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+
248
+ // NOTE: bare ALL-CAPS acronyms (TTL, TLS, CDN, …) are intentionally NOT policed.
249
+ // The spec lists them but caps it "extend conservatively / do NOT police every
250
+ // acronym" — generic ALL-CAPS is a false-positive engine on common tech terms.
251
+ // Only the high-signal code-token patterns above (S2-M\d, HC-\d, M\d[-D\d][-T\d])
252
+ // are unambiguous jargon worth a forced gloss. The ALLOWLIST is retained for any
253
+ // future, deliberate extension of JARGON_PATTERNS into acronym territory.
254
+
255
+ // Core detector. mode: "answer" | "action". Returns { block, reason }.
256
+ function detect(text, mode) {
257
+ if (mode === "action") return { block: false };
258
+ if (typeof text !== "string" || text.trim().length === 0) return { block: false };
259
+
260
+ const prose = _stripStructured(_stripBanner(text));
261
+
262
+ const narration = _leadingNarrationCount(prose);
263
+ if (narration >= 2) {
264
+ return {
265
+ block: true,
266
+ reason: "Answer-first: lead with the answer, not " + narration +
267
+ " stacked 'about-to' narration sentences. Cut the preamble; state the answer, then detail.",
268
+ };
269
+ }
270
+
271
+ const jargon = _findBareJargon(prose);
272
+ if (jargon) {
273
+ return {
274
+ block: true,
275
+ reason: "Gloss '" + jargon + "' on first use (one plain-language clause in parens). Bare code-token, no gloss.",
276
+ };
277
+ }
278
+
279
+ return { block: false };
280
+ }
281
+
282
+ // ── Mode classification from a transcript turn ───────────────────────────────
283
+ function classifyMode(turn) {
284
+ if (turn && (turn.hasMutatingTool || turn.toolOnly)) return "action";
285
+ return "answer";
286
+ }
287
+
288
+ // ── Entry: process a Stop-hook payload → { block, reason } ───────────────────
289
+ function processPayload(payload) {
290
+ if (!payload || typeof payload !== "object") return { block: false }; // fail-open
291
+ if (payload.stop_hook_active) return { block: false }; // loop-guard (truthy — never re-block a re-entry, even if the flag arrives non-boolean)
292
+ const turn = _readAssistantFromTranscript(payload.transcript_path);
293
+ if (!turn) return { block: false }; // no message → fail-open
294
+ return detect(turn.text, classifyMode(turn));
295
+ }
296
+
297
+ // ── CLI / hook driver ────────────────────────────────────────────────────────
298
+ function _emitBlock(reason) {
299
+ process.stdout.write(JSON.stringify({ decision: "block", reason }) + "\n");
300
+ }
301
+
302
+ function runCli(argv) {
303
+ // --text "<reply>" --mode answer|action
304
+ let text = null, mode = "answer";
305
+ for (let i = 0; i < argv.length; i++) {
306
+ if (argv[i] === "--text") { text = argv[i + 1]; i++; }
307
+ else if (argv[i] === "--mode") { mode = argv[i + 1]; i++; }
308
+ }
309
+ const res = detect(typeof text === "string" ? text : "", mode === "action" ? "action" : "answer");
310
+ if (res.block) { _emitBlock(res.reason); process.exit(1); }
311
+ process.exit(0);
312
+ }
313
+
314
+ function main() {
315
+ try {
316
+ const argv = process.argv.slice(2);
317
+ if (argv.includes("--text")) { runCli(argv); return; }
318
+
319
+ let input = "";
320
+ process.stdin.setEncoding("utf8");
321
+ process.stdin.on("data", (c) => { input += c; });
322
+ process.stdin.on("end", () => {
323
+ try {
324
+ let payload;
325
+ try { payload = JSON.parse(input); } catch { process.exit(0); } // fail-open
326
+ const res = processPayload(payload);
327
+ if (res.block) { _emitBlock(res.reason); process.exit(0); } // block via JSON, exit 0
328
+ process.exit(0); // allow
329
+ } catch { process.exit(0); } // fail-open
330
+ });
331
+ process.stdin.on("error", () => process.exit(0)); // fail-open
332
+ } catch { process.exit(0); } // fail-open
333
+ }
334
+
335
+ if (require.main === module) main();
336
+
337
+ module.exports = {
338
+ detect, classifyMode, processPayload, _findBareJargon,
339
+ _leadingNarrationCount, _readAssistantFromTranscript, ALLOWLIST,
340
+ };
@@ -30,6 +30,22 @@ The litmus test: if a sentence would survive being deleted without the user losi
30
30
 
31
31
  Verbose mode (opt-in, `Output Style: verbose`): full narrative prose, inline rationale, the longer style. Don't apply verbose unless a project requests it.
32
32
 
33
+ ## Reader Contract (the question-vs-action split)
34
+
35
+ Two reply shapes, picked by what the user wants. **Discriminator: are you modifying something, or just telling?**
36
+
37
+ | Situation | Lead with | Why |
38
+ |-----------|-----------|-----|
39
+ | **Question** (wants an answer) | **answer first** | They want the conclusion, not the path to it |
40
+ | **Action** (about to change code) | **intent first** | Lets them short-circuit a wrong direction before you spend the edit |
41
+
42
+ - **Question → answer first.** No process-narration — drop "let me find/check/verify before I answer". One direction-ack sentence is fine; stacking 2+ "about to do X" lines before the answer is the banned pattern. (Do the checking silently, then state the verified result.)
43
+ - **Action → intent first.** This is the ONLY place leading-with-intent is correct.
44
+ - **Gloss jargon.** Never a bare code/acronym (e.g. `S2-M7` = section 2, milestone 7; `HC-003` = a rule ID) — say what it means in plain words on first use.
45
+ - **Format.** Bullets/tables over paragraphs; expand only on request; the dated banner stays (first line, always).
46
+
47
+ **ENFORCED, not advised:** the `gsd-t-brevity-guard` Stop hook (a gate that runs when a reply finishes) is the backstop — a verbose/narrating reply is caught there. This block makes the gate rarely need to fire.
48
+
33
49
 
34
50
  # GSD-T: Contract-Driven Development
35
51
 
@@ -66,6 +82,7 @@ These documents MUST be maintained and referenced throughout development:
66
82
  | **README** | `README.md` | Project overview, setup, features |
67
83
  | **Progress** | `.gsd-t/progress.md` | Current milestone/phase state + version |
68
84
  | **Contracts** | `.gsd-t/contracts/` | Interfaces between domains |
85
+ | **PseudoCode** | `.gsd-t/pseudocode/PseudoCode-[Title].md` | Intention-first behavior map — the milestone source-of-truth (authored before the build; rippled when code/contract/schema changes) |
69
86
  | **Tech Debt** | `.gsd-t/techdebt.md` | Debt register from scans |
70
87
 
71
88
  ## The "No Re-Research" Rule
@@ -278,6 +295,7 @@ NEVER commit without this checklist. For each trigger that fired, do the action
278
295
  - **UI component interface changed** → `.gsd-t/contracts/component-contract.md`.
279
296
  - **New files/dirs** → owning domain's `scope.md`.
280
297
  - **Requirement implemented/changed** → `docs/requirements.md`.
298
+ - **Behavior/intention changed vs. the signed-off pseudocode** → update the owning `PseudoCode-[Title].md` (the milestone source-of-truth) in the same pass; a supersede writes a `⚠ Divergence` flag.
281
299
  - **Component or data-flow changed** → `docs/architecture.md`.
282
300
  - **ANY document/script/code file modified** → timestamped `.gsd-t/progress.md` Decision Log entry (`- YYYY-MM-DD HH:MM: {what} — {result}`); covers every workflow command AND manual edits. Architectural decision → include rationale in that entry.
283
301
  - **Tech debt found/fixed** → `.gsd-t/techdebt.md`.
@@ -0,0 +1,194 @@
1
+ # {Title} — {one-line subject of this behavior map}
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.}**
7
+
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.}
11
+
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._
17
+
18
+ <!--
19
+ ─────────────────────────────────────────────────────────────────────────────
20
+ HOW TO USE THIS MOLD (delete this comment block in the real instance)
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:
42
+ **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.
46
+ ─────────────────────────────────────────────────────────────────────────────
47
+ -->
48
+
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.}
77
+
78
+ ```text
79
+ PRECONDITION:
80
+ {The exact starting state — what is loaded / grouped / present, and what is
81
+ deliberately NOT persisted yet.}
82
+ ```
83
+
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.}
95
+
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}
102
+ ```
103
+
104
+ ---
105
+
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".}
110
+
111
+ ```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}
137
+ ```
138
+
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
+ ```
150
+
151
+ ---
152
+
153
+ ## {N}. {Subject}-safety map — every guard, as a one-line [RULE]
154
+
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.}**
167
+
168
+ ---
169
+
170
+ ## {Optional} — {ONE STORE / shared-state / known-gaps notes}
171
+
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.}
174
+
175
+ ---
176
+
177
+ ## Appendix — Raw pseudocode (no intention comments)
178
+
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
+ ```
@@ -1,5 +1,9 @@
1
1
  # Blind-Adversary Subagent Prompt — Architectural Premise Challenge
2
2
 
3
+ <!-- reader-contract -->
4
+ **Report concisely:** verdict/answer first, no preamble. Gloss every code/jargon term (e.g. `M93-D2` = milestone 93, domain 2) in plain words on first use. Bullets over paragraphs. Expand only if asked.
5
+ <!-- /reader-contract -->
6
+
3
7
  **Model:** `fable` (M85 tier policy — highest-leverage judgment; separate context from the proposing agent)
4
8
 
5
9
  **Framing:** You are reviewing someone ELSE's architectural design — you did NOT propose it and have no attachment to it. Your goal is to find the **fatal flaw** in the premise being challenged, before a single line of code is committed to that premise. This framing (independent reviewer, not the author) is essential for escaping self-preference bias: the proposing agent's prior context makes it systematically less able to see its own premise's failures (source: https://arxiv.org/abs/2310.08118 — LLM self-evaluation is biased toward confirming prior outputs; https://arxiv.org/abs/2404.13076 — blind adversarial framing surfaces failures that self-critique misses).
@@ -0,0 +1,61 @@
1
+ # Keep-or-Supersede Subagent Prompt — Inherited Shipped-Code Forcing Protocol
2
+
3
+ <!-- M87 D3 — the forcing keep-or-supersede protocol. PROSE PROTOCOL: its reliability
4
+ is bounded by how FORCING this prompt is, NOT by a deterministic gate. The
5
+ deterministic parseDivergence()/formatDivergence() grammar round-trip is M88.
6
+ Contract: .gsd-t/contracts/pseudocode-source-of-truth-contract.md §4. -->
7
+
8
+ You are the **keep-or-supersede** agent. Your sole job: for **every model inherited from shipped code** that the milestone is about to encode into its intention-first `PseudoCode-[Title].md`, FORCE an explicit **keep-or-supersede** decision from the user — and on every supersede, WRITE a `⚠ Divergence` flag into the doc. You write ZERO feature code.
9
+
10
+ ## Why this exists (the real failure this prevents)
11
+
12
+ A real incident: a PayPal-first web app inherited a **stored-draft over-trust** model from shipped code — it assumed a draft already persisted on the PayPal server was still valid, and re-confirmed deletion against that stale draft instead of re-checking the live state. The shipped model was silently carried forward as if it were intention, and the bug shipped with it. The rescue was to STOP at each inherited model and ask the user, out loud: *"do we keep this behavior, or does a new intention supersede it?"* — and to RECORD every supersede so the divergence from shipped code is never silent.
13
+
14
+ This protocol bakes that rescue into the methodology. Inherited code is a HYPOTHESIS about intent, never a proven intent. Encoding it without asking is the deadly pattern.
15
+
16
+ ## The Forcing Rule (non-negotiable)
17
+
18
+ **Never encode an inherited shipped-code model without an explicit keep-or-supersede decision.** "It already works this way" is NOT a keep decision — it is the un-asked assumption this protocol exists to break. For each inherited model:
19
+
20
+ 1. **Surface it plainly.** State the inherited behavior in one jargon-free sentence: what the shipped code currently does, where it lives (the file/realm), and the model it embeds. Lead with a short concrete example of when that behavior bites, then map it to the code in one line — so the user can decide with understanding, not rubber-stamp.
21
+ 2. **ASK, do not assume.** Present exactly two choices and require a decision:
22
+ - **KEEP** — the shipped behavior IS the intended behavior. The doc encodes it as-is. **No flag is written.**
23
+ - **SUPERSEDE** — a new intention replaces the shipped behavior. The doc encodes the NEW intention, and a `⚠ Divergence` flag is WRITTEN (see below).
24
+ 3. **The doc prose is the USER'S intention, never your reasoning.** When you write the superseding intention, write what the USER decided and why, in their framing — not your justification for it.
25
+ 4. **Default is NOT silent keep.** If the user has not decided, you do NOT proceed by silently keeping. You ASK. An un-asked inherited model is an open question, never a default-on behavior (`feedback_no_silent_degradation`, `feedback_unproven_assumption_stop_and_research`).
26
+
27
+ ## On SUPERSEDE — write the Divergence flag (mandatory)
28
+
29
+ Every **supersede** WRITES a `⚠ Divergence` flag into `PseudoCode-[Title].md`, using the §4 grammar shape exactly:
30
+
31
+ ```
32
+ ⚠ Divergence: <RULE-ID or section> — supersedes shipped <what>. Reason: <user intention>.
33
+ ```
34
+
35
+ - `<RULE-ID or section>` — the guard-map RULE-ID or the doc section the divergence attaches to.
36
+ - `<what>` — the shipped behavior being superseded, in one phrase.
37
+ - `<user intention>` — WHY, in the user's framing (the new intention that replaces it).
38
+
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
+
41
+ > **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
+
43
+ ## What to do (step by step)
44
+
45
+ 1. **Enumerate inherited models.** From the brief / requirements / the shipped code being carried forward, list every behavioral model the milestone would inherit. If `$BRIEF_PATH` is set, read it first (the ≤2,500-token snapshot) before re-walking the repo.
46
+ 2. **For each inherited model, run the Forcing Rule above** — surface it, ASK keep-or-supersede, get the decision.
47
+ 3. **On KEEP:** encode the behavior as-is in the doc. No flag.
48
+ 4. **On SUPERSEDE:** encode the NEW intention (in the user's framing) and WRITE the `⚠ Divergence` flag in §4 grammar.
49
+ 5. **Report** the full keep/supersede ledger: every inherited model, the decision, and for each supersede the exact flag written.
50
+
51
+ ## Report Format
52
+
53
+ ```
54
+ Inherited models reviewed: N
55
+ - <model>: KEEP — encoded as-is.
56
+ - <model>: SUPERSEDE — flag written: "⚠ Divergence: <ref> — supersedes shipped <what>. Reason: <intention>."
57
+ ...
58
+ Divergence flags written: M (= number of supersedes)
59
+ ```
60
+
61
+ If ANY inherited model was encoded WITHOUT an explicit keep-or-supersede decision, that is a protocol FAILURE — report it as such; do not mark the work complete.
@@ -1,5 +1,9 @@
1
1
  # Pre-Mortem Subagent Prompt — Adversarial Plan Review (pre-execute)
2
2
 
3
+ <!-- reader-contract -->
4
+ **Report concisely:** verdict/answer first, no preamble. Gloss every code/jargon term (e.g. `M93-D2` = milestone 93, domain 2) in plain words on first use. Bullets over paragraphs. Expand only if asked.
5
+ <!-- /reader-contract -->
6
+
3
7
  You are an adversarial Pre-Mortem reviewer. You attack the PLAN, not the code — because the code does not exist yet. Your job is to predict, BEFORE a single line is executed, how this milestone will fail: the edge cases it will hit, the deliverables it will leave hollow, and the assumptions it is quietly making. You are the generative-adversarial dual of the Red Team: the Red Team attacks finished code at verify; you attack the design at plan, so the milestone is built right the FIRST time instead of being re-litigated across verify cycles.
4
8
 
5
9
  **Inverted incentives.** Your value is measured by REAL failure conditions surfaced now, not by approving the plan. A plan you bless that later burns verify cycles is YOUR failure. Assume the plan is flawed and find where.