specpipe 1.0.0

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.
Files changed (60) hide show
  1. package/README.md +1319 -0
  2. package/bin/devkit.js +3 -0
  3. package/package.json +61 -0
  4. package/src/cli.js +76 -0
  5. package/src/commands/check.js +33 -0
  6. package/src/commands/diff.js +84 -0
  7. package/src/commands/init-adopt.js +54 -0
  8. package/src/commands/init-agents.js +118 -0
  9. package/src/commands/init-global.js +102 -0
  10. package/src/commands/init.js +311 -0
  11. package/src/commands/list.js +54 -0
  12. package/src/commands/remove.js +133 -0
  13. package/src/commands/upgrade.js +215 -0
  14. package/src/lib/agent-guards.js +100 -0
  15. package/src/lib/agent-install.js +161 -0
  16. package/src/lib/agents.js +280 -0
  17. package/src/lib/claude-global.js +183 -0
  18. package/src/lib/detector.js +93 -0
  19. package/src/lib/hasher.js +21 -0
  20. package/src/lib/installer.js +213 -0
  21. package/src/lib/logger.js +16 -0
  22. package/src/lib/manifest.js +102 -0
  23. package/src/lib/reconcile.js +56 -0
  24. package/templates/.claude/CLAUDE.md +79 -0
  25. package/templates/.claude/hooks/comment-guard.js +126 -0
  26. package/templates/.claude/hooks/file-guard.js +216 -0
  27. package/templates/.claude/hooks/glob-guard.js +104 -0
  28. package/templates/.claude/hooks/path-guard.sh +118 -0
  29. package/templates/.claude/hooks/self-review.sh +27 -0
  30. package/templates/.claude/hooks/sensitive-guard.sh +227 -0
  31. package/templates/.claude/settings.json +68 -0
  32. package/templates/docs/WORKFLOW.md +325 -0
  33. package/templates/docs/specs/.gitkeep +0 -0
  34. package/templates/hooks/specpipe-read-guard.sh +42 -0
  35. package/templates/hooks/specpipe-shell-guard.sh +65 -0
  36. package/templates/rules/specpipe-guards.md +40 -0
  37. package/templates/scripts/test-hooks.sh +66 -0
  38. package/templates/skills/sp-build/SKILL.md +776 -0
  39. package/templates/skills/sp-challenge/SKILL.md +255 -0
  40. package/templates/skills/sp-commit/SKILL.md +174 -0
  41. package/templates/skills/sp-explore/SKILL.md +730 -0
  42. package/templates/skills/sp-fix/SKILL.md +266 -0
  43. package/templates/skills/sp-humanize/SKILL.md +212 -0
  44. package/templates/skills/sp-investigate/SKILL.md +648 -0
  45. package/templates/skills/sp-md-render/SKILL.md +200 -0
  46. package/templates/skills/sp-md-render/components.md +415 -0
  47. package/templates/skills/sp-md-render/template.html +283 -0
  48. package/templates/skills/sp-plan/SKILL.md +947 -0
  49. package/templates/skills/sp-review/SKILL.md +268 -0
  50. package/templates/skills/sp-scaffold/SKILL.md +237 -0
  51. package/templates/skills/sp-scaffold/references/ARCHITECTURE.md.tmpl +228 -0
  52. package/templates/skills/sp-scaffold/references/DESIGN.md.tmpl +113 -0
  53. package/templates/skills/sp-scaffold/references/adr/NNNN-template.md +92 -0
  54. package/templates/skills/sp-scaffold/references/stack-profiles/react.md +36 -0
  55. package/templates/skills/sp-spec-render/SKILL.md +254 -0
  56. package/templates/skills/sp-spec-render/components.md +418 -0
  57. package/templates/skills/sp-spec-render/examples/user-auth.html +749 -0
  58. package/templates/skills/sp-spec-render/examples/user-auth.md +114 -0
  59. package/templates/skills/sp-spec-render/template.html +222 -0
  60. package/templates/skills/sp-voices/SKILL.md +1184 -0
@@ -0,0 +1,254 @@
1
+ ---
2
+ description: |
3
+ Render spec markdown (single or multi-spec feature) into a self-contained,
4
+ scannable HTML file: sidebar TOC with scroll-spy, story cards with P-badges,
5
+ collapsible Given/When/Then for each AS, constraint callouts, dark/light theme,
6
+ search filter.
7
+
8
+ Use when asked to "render spec", "xem spec đẹp", "spec html", "tạo view cho spec",
9
+ "preview spec", or after /sp-plan finishes writing or updating a spec.
10
+
11
+ This skill is user-invoked, not auto-called. /sp-plan only writes the spec
12
+ markdown — it suggests this command at the end so the user chooses whether
13
+ to generate the HTML view.
14
+
15
+ Proactively suggest this skill when the user is reading a long spec markdown
16
+ in chat ("hard to scan", "lots to read") or after a Mode C update has made
17
+ any existing <feature>.html stale.
18
+
19
+ Skip for files that are not specs (investigation, explore doc, RFC, retro,
20
+ README) — those go to the generic sibling skill [[sp-md-render]] instead.
21
+
22
+ Idempotent: re-rendering overwrites the previous .html. Safe to run anytime.
23
+ allowed-tools: Read, Write, Bash, Glob, Grep
24
+ ---
25
+
26
+ Render `docs/specs/<feature>/<feature>.md` (plus sub-specs if any) into a self-contained `<feature>.html`.
27
+
28
+ The source `.md` is the truth. The HTML is a **view layer**: regenerable, never hand-edited. Target output token cost ≈ source markdown size × 1.2 — `template.html` and `components.md` are read-only inputs (cached across calls), and CSS/JS never enters the output token stream.
29
+
30
+ ---
31
+
32
+ ## Inputs
33
+
34
+ `$ARGUMENTS` may be:
35
+
36
+ - **Feature slug** (`customer-onboarding`) — resolves to `docs/specs/<slug>/<slug>.md`
37
+ - **Path to a root spec** (`docs/specs/billing/billing.md`) — renders that directory
38
+ - **Path to a directory** (`docs/specs/billing/`) — renders that directory
39
+ - **`--all`** — bulk re-render every `docs/specs/*/<name>/<name>.md`
40
+ - **Empty** — list all `docs/specs/*/` and prompt the user to pick
41
+
42
+ In every case, output is written to `<spec-dir>/<feature>.html` and overwrites any existing file.
43
+
44
+ ---
45
+
46
+ ## Skill files (read once before generating)
47
+
48
+ Resolved relative to this `SKILL.md`:
49
+
50
+ - `template.html` — HTML skeleton with embedded CSS, JS, and SVG sprite. Contains `{{PLACEHOLDER}}` strings and `<!-- TOC_ENTRIES -->`, `<!-- CONTENT_START -->`, `<!-- CONTENT_END -->` slots. **Read once, never modified.**
51
+ - `components.md` — Catalog of 11 HTML snippets to copy verbatim. **Read once.**
52
+ - `examples/user-auth.md` + `examples/user-auth.html` — Reference input/output pair. Read one pair to calibrate quality before rendering.
53
+
54
+ **Read all three before producing any output.** Do not invent CSS classes, do not generate component markup from scratch, do not add new `<style>` or `<script>` tags.
55
+
56
+ ---
57
+
58
+ ## Workflow
59
+
60
+ ### Step 1 — Resolve inputs
61
+
62
+ 1. Parse `$ARGUMENTS` to determine the spec directory.
63
+ 2. List files: `ls docs/specs/<feature>/*.md` — distinguish the root spec (`<feature>.md`) from sub-specs (`<feature>-*.md`).
64
+ 3. List `snapshots/` if present (for the Snapshots section).
65
+ 4. Read the root spec fully. Read each sub-spec fully.
66
+ 5. Read `template.html`, `components.md`, and one example pair.
67
+
68
+ **Classify layout:**
69
+
70
+ | # | Condition | Layout |
71
+ |---|-----------|--------|
72
+ | L1 | Root spec only, no sub-specs | **Single-spec** — each `## H2` becomes a section; sidebar groups: "Overview" + "Reference" |
73
+ | L2 | Root + 1 or more sub-specs | **Multi-spec** — each sub-spec becomes a section with `.subspec-head`; sidebar groups by sub-spec name |
74
+
75
+ ### Step 2 — Parse each spec.md
76
+
77
+ For each `.md`, extract:
78
+
79
+ - **Frontmatter:** `**Created**`, `**Last updated**`, `**Status**`, `**Snapshot limit**` (regex the `**Key:** value` lines at the top of the file).
80
+ - **Overview** paragraph (the text under `## Overview`).
81
+ - **Sub-specs table** (root only, optional) — list rows `[name](./path)` + scope.
82
+ - **Data Model** section (root, optional).
83
+ - **Stories** — each `### S-NNN: Title (Pn)` produces a story object with:
84
+ - `id` (S-NNN, namespaced by sub-spec slug in multi-spec layout)
85
+ - `priority` (P0/P1/P2 from the `(Pn)` suffix)
86
+ - `title`
87
+ - `description` (the paragraph after `**Description:**`)
88
+ - `applies_constraints` (the `C-NNN` list after `**Applies Constraints:**`, if the story has that line — optional)
89
+ - `acceptance_scenarios[]` — each `AS-NNN:` produces an object with `id`, `desc` (the 1-line summary after the ID), `given`, `when`, `then`, `data` (optional), `setup` (optional)
90
+ - **Constraints** — each constraint (a line `C-NNN:` OR a bullet `- C-NNN:` / `- **C-NNN:**`) becomes a callout. A **cross-surface invariant** carries indented `scope:` / `surfaces:` / `coverage:` sub-lines (e.g. `coverage: createIntent → AS-008, pay → GAP-001`) — capture them so the callout can show per-surface coverage. If a single spec has more than 10, group them collapsed by sub-spec.
91
+ - **What Already Exists** — paragraph or bullet list.
92
+ - **Not in Scope** — bullet list.
93
+ - **Change Log** — table of entries.
94
+
95
+ **Counts:**
96
+
97
+ - Story count, AS count, sub-spec count → used for the topbar meta.
98
+ - Status class: `Draft` → `.status.draft`, `Active` → `.status.active`, `Deprecated` → `.status.deprecated`.
99
+
100
+ ### Step 3 — Build output buffer
101
+
102
+ Copy `template.html` content into an in-memory string buffer. Replace placeholders (values from Step 2):
103
+
104
+ | Placeholder | Value | Notes |
105
+ |-------------|-------|-------|
106
+ | `{{LANG}}` | ISO 639-1 code | Detect from the source prose. Default `vi` if Vietnamese dominates, `en` for English. |
107
+ | `{{FEATURE}}` | feature slug | Appears twice: in `<title>` and `<h1>`. |
108
+ | `{{SUBTITLE}}` | 1-line description | Pulled from the root Overview, max 200 chars. |
109
+ | `{{VERSION}}` | from frontmatter, or snapshot count + 1 | Default `1` if absent. |
110
+ | `{{LAST_UPDATED}}` | ISO date from frontmatter | |
111
+ | `{{UPDATED_LABEL}}` | "updated" / "cập nhật" | Match `{{LANG}}`. |
112
+ | `{{META_EXTRA}}` | `<span class="sep">·</span><span>N specs</span><span class="sep">·</span><span>N stories</span><span class="sep">·</span><span>N AS</span>` | Multi-spec includes `N specs`; single-spec omits it. |
113
+ | `{{STATUS}}` | `Active` / `Draft` / `Deprecated` | |
114
+ | `{{STATUS_CLASS}}` | `active` / `draft` / `deprecated` | Lowercase. |
115
+ | `{{TOC_LABEL}}` | "Mục lục" / "Contents" | |
116
+ | `{{SEARCH_PLACEHOLDER}}` | "Tìm story…" / "Search story…" | |
117
+ | `{{SKIP_LABEL}}` | "Bỏ qua menu" / "Skip to content" | |
118
+ | `{{THEME_TIP}}` | "Đổi theme" / "Toggle theme" | |
119
+
120
+ ### Step 4 — Render TOC
121
+
122
+ Replace `<!-- TOC_ENTRIES -->` with entries. Use §2 in `components.md`.
123
+
124
+ **Single-spec layout:**
125
+
126
+ ```
127
+ [Overview] TL;DR · Overview · Data Model (if any)
128
+ [Stories] 1 entry per story with P-badge
129
+ [Reference] Constraints · What Already Exists · Not in Scope · Change Log · Snapshots
130
+ ```
131
+
132
+ **Multi-spec layout:**
133
+
134
+ ```
135
+ [Overview] TL;DR · Sub-specs · Shared Data Model
136
+ [<Sub-spec 1 name>] count "5/19" | 1 entry per story (P-badge + S-NNN + short title)
137
+ [<Sub-spec 2 name>] ...
138
+ ...
139
+ [Root]
140
+ [Reference] Constraints · Not in Scope · Change Log
141
+ ```
142
+
143
+ Story link text format: `<span class="p p0">P0</span>S-NNN · <short title, max ~30 chars>`
144
+
145
+ ### Step 5 — Render body
146
+
147
+ Replace the slot between `<!-- CONTENT_START -->` and `<!-- CONTENT_END -->` with the body, in this order:
148
+
149
+ 1. **TL;DR card** (§3) — mandatory, generated from overview + story list. Max 10 bullets.
150
+ 2. **Sub-specs table** (multi-spec only, §10b) — if the root has a Sub-specs table.
151
+ 3. **Shared Data Model** (multi-spec) or **Data Model** (single, §10c) — collapsed by default for long tables.
152
+ 4. **Per sub-spec** (multi-spec) or **Stories** section (single):
153
+ - Sub-spec header: `<h2 class="subspec-head">` with an eyebrow `SUB-SPEC` / `STEP 1..4` / `ROOT` (§7).
154
+ - Brief overview paragraph below the header.
155
+ - Story cards (§4) in the order from spec.md. Within each story, the first AS is `open` and the rest are collapsed (§5). If the story has `**Applies Constraints:**`, render the bound `C-NNN` chips on the card (§4).
156
+ 5. **Constraints** (§6):
157
+ - Single spec → flat list of callouts.
158
+ - A **cross-surface invariant** (has `scope:`/`surfaces:`/`coverage:`) renders that per-surface coverage block inside its callout (§6a) — so the reader sees each money/effect surface mapped to its own `AS-NNN`/`GAP-NNN`.
159
+ - Multi-spec → grouped by sub-spec; each group is a `<details class="collapsible">`. The Root group defaults to `open`, sub-spec groups default closed.
160
+ 6. **What Already Exists** (§10a) — plain prose section.
161
+ 7. **Not in Scope** (§10d) — bullet list with `<b>` around each item name.
162
+ 8. **Change Log** (§8) — collapsed table. Multi-spec adds a `Spec` column.
163
+ 9. **Snapshots** (§9) — collapsed list, only if the directory contains files. Skip the section entirely when empty.
164
+
165
+ ### Step 6 — Write file
166
+
167
+ Write the buffer to `<spec-dir>/<feature>.html`. **One Write call, no Edit loop** — repeated Edits cause drift and waste tokens.
168
+
169
+ ### Step 7 — Verify
170
+
171
+ Re-read the written file and check:
172
+
173
+ - [ ] No leftover `{{PLACEHOLDER}}` strings.
174
+ - [ ] No leftover `<!-- TOC_ENTRIES -->`, `<!-- CONTENT_START -->`, `<!-- CONTENT_END -->`.
175
+ - [ ] Every TOC `href="#x"` resolves to an element with `id="x"` in the body (sample 5 random IDs).
176
+ - [ ] Count of `<article class="story">` matches the number of stories parsed.
177
+ - [ ] Count of `<details class="as">` matches the number of AS parsed.
178
+
179
+ If anything is off, fix with targeted Edits — do not rewrite the whole file.
180
+
181
+ Report to the user:
182
+
183
+ ```
184
+ ✓ Rendered <feature> → <path>
185
+ <N specs> · <N stories> · <N AS> · status: <Draft|Active|Deprecated>
186
+ Open: open <path>
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Rules
192
+
193
+ 1. **Source is truth.** The HTML is an idempotent regenerable view. Never hand-edit HTML.
194
+ 2. **One Write call.** Build the full buffer in memory, then write once.
195
+ 3. **No new CSS or components.** Use only the classes in the template and the snippets in `components.md`.
196
+ 4. **Never paraphrase technical content.** Field names, routes, code identifiers stay verbatim, wrapped in `<code>`.
197
+ 5. **Compact AS bodies.** Given/When/Then are 1–2 lines each. Long-form detail belongs in spec.md, not the HTML.
198
+ 6. **Local IDs per sub-spec.** Story ID is local per sub-spec (signup:S-001, store:S-001 OK). The HTML element `id` is namespaced by sub-spec slug to avoid duplicates across the rendered file.
199
+ 7. **TL;DR is mandatory.** Even for short specs.
200
+ 8. **Constraints / Snapshots / Change Log collapsed by default.** The Root constraint group may open by default in multi-spec layout; everything else stays closed.
201
+
202
+ ---
203
+
204
+ ## Edge cases
205
+
206
+ - **Empty story list** → render TL;DR + frontmatter + a placeholder "No stories yet." Do not error.
207
+ - **No `snapshots/` directory** → skip the Snapshots section entirely; do not render the header.
208
+ - **Missing Status in frontmatter** → default to `Draft`.
209
+ - **Missing Last updated** → use `$(date +%Y-%m-%d)`.
210
+ - **Story without AS** → render the story card with a "No acceptance scenarios yet." stub. Do not skip the story.
211
+ - **AS without explicit Given/When/Then** (P2 prose style) → render the flow description instead of the dl/dt grid. See §5b.
212
+ - **Sub-spec link path does not resolve** → log a warning, skip that sub-spec, still render the rest.
213
+ - **Source `.md` contains embedded HTML** → pass through inside `story-desc` if safe.
214
+
215
+ ---
216
+
217
+ ## Anti-patterns
218
+
219
+ - ❌ Edit loop to generate the HTML piece by piece — causes drift, wastes tokens. Build the buffer, then Write once.
220
+ - ❌ Adding a new `<style>` block or inline CSS attribute on an element. All CSS comes from the template.
221
+ - ❌ Generating an inline SVG path for every callout. Use the `.callout .ico` class with the SVG snippet in `components.md`.
222
+ - ❌ Translating identifiers, routes, function names. Code and paths stay verbatim.
223
+ - ❌ Bloating AS bodies with long prose. An AS is a contract test, not a tutorial.
224
+ - ❌ Rendering when `<feature>.md` does not exist — STOP and tell the user to run `/sp-plan` first.
225
+
226
+ ---
227
+
228
+ ## Relationship with /sp-plan
229
+
230
+ This skill is decoupled from `/sp-plan`. `/sp-plan` writes the spec markdown and ends — it never calls this skill automatically. At the end of Phase 4 (new spec) and Mode C C5 (spec update), `/sp-plan` displays a 1–2 line hint telling the user to run `/sp-spec-render <feature>` if they want the HTML view.
231
+
232
+ This gives the user explicit control:
233
+
234
+ - Some users only want the markdown (faster, no extra tokens spent rendering).
235
+ - Some users render once and never again until they actually want to scan the spec.
236
+ - Some users re-render after every update to keep an HTML tab open as their primary reading surface.
237
+
238
+ Manual invocation cases:
239
+
240
+ - After `/sp-plan` finishes — generate or refresh the HTML view.
241
+ - After hand-editing `<feature>.md` for a typo (no spec semantics changed, but the HTML is now stale).
242
+ - Bulk re-render across the repo after updating `template.html` or `components.md` in this skill.
243
+ - Rendering specs written before this skill existed.
244
+
245
+ ---
246
+
247
+ ## Bulk re-render
248
+
249
+ If `$ARGUMENTS` is `--all`:
250
+
251
+ 1. `ls docs/specs/*/` — for each directory containing `<name>/<name>.md`, render it.
252
+ 2. Report totals: `Rendered N specs in X seconds.`
253
+
254
+ Useful after updating `template.html` or `components.md` in this skill.
@@ -0,0 +1,418 @@
1
+ # components.md — Catalog for sp-spec-render
2
+
3
+ 11 HTML snippets. The AI copies them verbatim and fills in content. **Never invent CSS classes.**
4
+
5
+ Conventions: `{{X}}` is a placeholder string. `[...]` is a description (does not appear in output). `// comment` is a note and does not appear in output.
6
+
7
+ ---
8
+
9
+ ## §1 — Topbar meta extra
10
+
11
+ Content of `{{META_EXTRA}}` in the template, inserted after `{{LAST_UPDATED}}`:
12
+
13
+ **Single-spec:**
14
+ ```html
15
+ <span class="sep">·</span><span>{{N_STORIES}} stories</span><span class="sep">·</span><span>{{N_AS}} AS</span>
16
+ ```
17
+
18
+ **Multi-spec:**
19
+ ```html
20
+ <span class="sep">·</span><span>{{N_SPECS}} specs</span><span class="sep">·</span><span>{{N_STORIES}} stories</span><span class="sep">·</span><span>{{N_AS}} AS</span>
21
+ ```
22
+
23
+ ---
24
+
25
+ ## §2 — Sidebar TOC
26
+
27
+ Replace `<!-- TOC_ENTRIES -->`. Structure depends on layout.
28
+
29
+ **Single-spec:**
30
+ ```html
31
+ <div class="toc-group">Overview</div>
32
+ <a href="#tldr" data-target="tldr">TL;DR</a>
33
+ <a href="#overview" data-target="overview">Overview</a>
34
+ <a href="#data-model" data-target="data-model">Data Model</a> // skip if the spec has no data model
35
+
36
+ <div class="toc-group">Stories <span class="count">{{N_STORIES}}/{{N_AS}}</span></div>
37
+ <a href="#s-001" data-target="s-001"><span class="p p0">P0</span>S-001 · {{short title, max 30 chars}}</a>
38
+ // ... 1 entry per story, badge matching priority
39
+
40
+ <div class="toc-group">Reference</div>
41
+ <a href="#constraints" data-target="constraints">Constraints</a>
42
+ <a href="#existing" data-target="existing">What Already Exists</a>
43
+ <a href="#not-in-scope" data-target="not-in-scope">Not in Scope</a>
44
+ <a href="#changelog" data-target="changelog">Change Log</a>
45
+ <a href="#snapshots" data-target="snapshots">Snapshots</a> // skip if the directory is empty
46
+ ```
47
+
48
+ **Multi-spec:**
49
+ ```html
50
+ <div class="toc-group">Overview</div>
51
+ <a href="#tldr" data-target="tldr">TL;DR</a>
52
+ <a href="#subspecs" data-target="subspecs">Sub-specs ({{N_SPECS}})</a>
53
+ <a href="#data-model" data-target="data-model">Shared Data Model</a>
54
+
55
+ <div class="toc-group">{{Sub-spec display name}} <span class="count">{{N_STORIES_IN_SUB}}/{{N_AS_IN_SUB}}</span></div>
56
+ <a href="#{{subslug}}-s-001" data-target="{{subslug}}-s-001"><span class="p p0">P0</span>S-001 · {{short title}}</a>
57
+ // ... per sub-spec
58
+
59
+ <div class="toc-group">Reference</div>
60
+ <a href="#constraints" data-target="constraints">Constraints ({{TOTAL_C}})</a>
61
+ <a href="#not-in-scope" data-target="not-in-scope">Not in Scope</a>
62
+ <a href="#changelog" data-target="changelog">Change Log</a>
63
+ ```
64
+
65
+ **Rules:**
66
+ - Story title in the TOC is at most ~30 chars. Truncate at a word boundary, not mid-word.
67
+ - HTML `id` is namespaced by sub-spec slug: `signup-s-001`, `store-s-001`. The sub-spec slug is the part after `<feature>-` in the filename.
68
+ - Group name uses a readable display name: `onboarding-step-store.md` → group `Step 1 — Store`. If you cannot infer a clean name, fall back to the filename.
69
+
70
+ ---
71
+
72
+ ## §3 — TL;DR card
73
+
74
+ Mandatory. Generated from the Overview + story list, max ~10 bullets.
75
+
76
+ ```html
77
+ <section class="tldr" id="tldr">
78
+ <div class="tldr-label">TL;DR</div>
79
+ <p>{{1–2 sentence summary with <b>key decisions</b> inline}}</p>
80
+ <ul>
81
+ <li><b>{{S-NNN or Sub-spec name}}</b> {{title}} — {{key behavior}} ({{N}} AS, {{Pn}})</li>
82
+ // ...
83
+ </ul>
84
+ </section>
85
+ ```
86
+
87
+ **Rules:**
88
+ - Opening sentence states the phase/scope decision (e.g. "Phase 1, single-tab, email-only.").
89
+ - Bullets: 1 per story (single-spec) or 1 per sub-spec (multi-spec).
90
+ - Bold the ID and the key concept. Plain prose for the rest.
91
+
92
+ ---
93
+
94
+ ## §4 — Story card
95
+
96
+ Each `### S-NNN: Title (Pn)` becomes one `<article>`.
97
+
98
+ ```html
99
+ <article class="story" id="{{namespaced-id}}">
100
+ <header class="story-head">
101
+ <span class="badge {{p0|p1|p2}}">{{P0|P1|P2}}</span>
102
+ <span class="id">{{S-NNN}}</span>
103
+ <span class="title">{{Story title}}</span>
104
+ <span class="badge count">{{N}} AS</span>
105
+ </header>
106
+ <div class="story-body">
107
+ <p class="story-desc">{{description, wrap identifiers in <code>}}</p>
108
+ {{ if the story has **Applies Constraints**:
109
+ <p class="story-desc"><b>Applies:</b> {{one <code>C-NNN</code> per bound constraint, space-separated}}</p>
110
+ }}
111
+ <div class="as-list">
112
+ // §5 AS entries here
113
+ </div>
114
+ </div>
115
+ </article>
116
+ ```
117
+
118
+ **Rules:**
119
+ - `id` is namespaced by sub-spec slug in multi-spec layout: `signup-s-001`. Single-spec: `s-001`.
120
+ - `badge p0/p1/p2` lowercase to match the priority.
121
+ - Description is the paragraph after `**Description:**` in the spec; preserve technical identifiers as `<code>`.
122
+ - If a story has no AS, still render the card. Inside `as-list`, put `<p style="color:var(--fg-muted);font-size:13px">No acceptance scenarios yet.</p>`.
123
+
124
+ ---
125
+
126
+ ## §5 — Acceptance Scenario
127
+
128
+ Each `AS-NNN` becomes a `<details>`. The first AS of each story is `open`; the rest start collapsed.
129
+
130
+ ### §5a — Given/When/Then style (typical for P0, P1)
131
+
132
+ ```html
133
+ <details class="as"{{ open if this is the first AS in the story}}>
134
+ <summary class="as-head">
135
+ <span class="id">{{AS-NNN}}</span>
136
+ <span class="desc">{{1-line description from the spec}}</span>
137
+ <span class="chev">›</span>
138
+ </summary>
139
+ <div class="as-body">
140
+ <dl class="gwt">
141
+ <dt>Given</dt><dd>{{state, wrap identifiers in <code>}}</dd>
142
+ <dt>When</dt><dd>{{action}}</dd>
143
+ <dt>Then</dt><dd>{{expected}}</dd>
144
+ </dl>
145
+ {{ if has Data:
146
+ <div class="as-data"><b>Data:</b> {{data}}</div>
147
+ }}
148
+ {{ if has Setup:
149
+ <div class="as-data"><b>Setup:</b> {{setup}}</div>
150
+ }}
151
+ </div>
152
+ </details>
153
+ ```
154
+
155
+ ### §5b — Prose flow style (P2 or when the spec does not split Given/When/Then)
156
+
157
+ ```html
158
+ <details class="as">
159
+ <summary class="as-head">
160
+ <span class="id">{{AS-NNN}}</span>
161
+ <span class="desc">{{short title}}</span>
162
+ <span class="chev">›</span>
163
+ </summary>
164
+ <div class="as-body">
165
+ <p class="as-prose">{{flow description + expected behavior}}</p>
166
+ </div>
167
+ </details>
168
+ ```
169
+
170
+ **Rules:**
171
+ - The `desc` in the summary is a 1-line summary, not the full Given/When/Then.
172
+ - Wrap `<code>` around every identifier: HTTP code, route, field name, function name, env var.
173
+ - Drop "Setup" if it duplicates Given. Drop "Data" if it is trivial.
174
+ - Do not bloat the AS body. An AS is a contract test — `Given X. When Y. Then Z.` is enough.
175
+
176
+ ---
177
+
178
+ ## §6 — Constraint callout
179
+
180
+ ### §6a — Single callout (single-spec, or per-sub-spec when grouped)
181
+
182
+ ```html
183
+ <div class="callout">
184
+ <svg class="ico" aria-hidden="true"><use href="#i-warn"/></svg>
185
+ <div>
186
+ <p class="callout-title">{{C-NNN · short name}}</p>
187
+ <p>{{constraint body, wrap identifiers in <code>}}</p>
188
+ {{ if cross-surface invariant (has scope/surfaces/coverage):
189
+ <p><b>Cross-surface invariant.</b> Scope: {{<code>S-NNN</code> …}}. Surfaces: {{<code>surface</code> …}}. Coverage: {{per surface, <code>surface</code> → <code>AS-NNN</code> | <code>GAP-NNN</code>}}.</p>
190
+ }}
191
+ </div>
192
+ </div>
193
+ ```
194
+
195
+ // Cross-surface invariant block: reuses the callout's plain `<p>` (no new class, no inline style).
196
+ // Render it ONLY for a constraint carrying scope/surfaces/coverage; omit for ordinary constraints.
197
+
198
+ ### §6b — Grouped constraints (multi-spec, more than 5 constraints per sub-spec)
199
+
200
+ ```html
201
+ <details class="collapsible"{{ open if this is the Root group}}>
202
+ <summary><b>{{Sub-spec display name}} (C-{{start}}..C-{{end}})</b> <span class="count">{{N}} invariants</span></summary>
203
+ <div><div class="constraints">
204
+ // §6a callouts here
205
+ </div></div>
206
+ </details>
207
+ ```
208
+
209
+ **Rules:**
210
+ - `.callout-title` uses the `C-NNN · short name` format. The short name is 2–4 words summarizing the constraint.
211
+ - Single-spec, or fewer than 5 constraints per sub-spec → flat §6a list.
212
+ - Multi-spec with many constraints → grouped §6b. Root group `open`, sub-spec groups closed.
213
+ - If a group has more than 8 callouts → optionally merge a few into a single `C-XXX..C-YYY` callout to avoid a wall of yellow. Each merged callout must still represent one coherent idea.
214
+
215
+ ---
216
+
217
+ ## §7 — Sub-spec section header (multi-spec only)
218
+
219
+ ```html
220
+ <h2 class="subspec-head" id="{{subspec-slug}}"><span class="ix">{{EYEBROW}}</span>{{Display Title}}</h2>
221
+ <p style="color:var(--fg-muted);font-size:13.5px;margin-top:-4px">{{1-line overview of the sub-spec}}</p>
222
+ ```
223
+
224
+ **Eyebrow values:**
225
+ - `SUB-SPEC` — generic sub-spec
226
+ - `STEP 1`, `STEP 2`, `STEP 3`, `STEP 4` — wizard step sub-specs
227
+ - `ROOT` — cross-cutting / root spec stories
228
+ - A custom eyebrow is fine when the sub-spec name suggests a clear role
229
+
230
+ **Display title:** from the `## Overview` of the sub-spec, or the filename. Keep it short (≤60 chars).
231
+
232
+ ---
233
+
234
+ ## §8 — Change Log (collapsed)
235
+
236
+ ### §8a — Single-spec
237
+
238
+ ```html
239
+ <h2 id="changelog">Change Log</h2>
240
+ <details class="collapsible">
241
+ <summary>{{N}} entries <span class="count">expand</span></summary>
242
+ <div>
243
+ <table class="changelog">
244
+ <thead><tr><th>Date</th><th>Change</th><th>Ref</th></tr></thead>
245
+ <tbody>
246
+ <tr><td class="date">{{YYYY-MM-DD}}</td><td>{{change}}</td><td>{{ref or —}}</td></tr>
247
+ </tbody>
248
+ </table>
249
+ </div>
250
+ </details>
251
+ ```
252
+
253
+ ### §8b — Multi-spec (adds a Spec column)
254
+
255
+ ```html
256
+ <h2 id="changelog">Change Log</h2>
257
+ <details class="collapsible">
258
+ <summary>{{N}} entries across {{N_SPECS}} specs <span class="count">expand</span></summary>
259
+ <div>
260
+ <table class="changelog">
261
+ <thead><tr><th>Date</th><th>Spec</th><th>Change</th><th>Ref</th></tr></thead>
262
+ <tbody>
263
+ <tr><td class="date">{{date}}</td><td>{{spec name}}</td><td>{{change}}</td><td>{{ref or —}}</td></tr>
264
+ </tbody>
265
+ </table>
266
+ </div>
267
+ </details>
268
+ ```
269
+
270
+ **Rules:**
271
+ - Sort entries by date DESC (newest first).
272
+ - Empty `Ref` → render as `—`.
273
+
274
+ ---
275
+
276
+ ## §9 — Snapshots (collapsed)
277
+
278
+ ```html
279
+ <h2 id="snapshots">Snapshots</h2>
280
+ <details class="collapsible">
281
+ <summary>{{N}} snapshots <span class="count">history</span></summary>
282
+ <div>
283
+ <div class="snapshots-list">
284
+ <div class="snapshot-row">
285
+ <span class="date">{{YYYY-MM-DD}}</span>
286
+ <a href="snapshots/{{filename}}">snapshots/{{filename}}</a>
287
+ <span class="reason">{{M-code if known}}{{: short reason}}</span>
288
+ </div>
289
+ // ...
290
+ </div>
291
+ </div>
292
+ </details>
293
+ ```
294
+
295
+ **Rules:**
296
+ - Sort by date DESC.
297
+ - Read the `**Reason:**` line inside each snapshot file to fill `reason` (M1/M2/.../M6 + short).
298
+ - If the `snapshots/` directory does not exist or is empty → **skip the entire section**, do not render the header.
299
+
300
+ ---
301
+
302
+ ## §10 — Plain sections
303
+
304
+ ### §10a — Overview / What Already Exists
305
+
306
+ ```html
307
+ <h2 id="{{slug}}">{{Heading}}</h2>
308
+ <p>{{prose, wrap identifiers in <code>}}</p>
309
+ ```
310
+
311
+ Skip the section if the spec does not have it.
312
+
313
+ ### §10b — Sub-specs table (multi-spec only)
314
+
315
+ ```html
316
+ <h2 id="subspecs">Sub-specs</h2>
317
+ <table class="subspec-table">
318
+ <thead><tr><th>Sub-spec</th><th>Scope</th></tr></thead>
319
+ <tbody>
320
+ <tr><td>{{sub-spec name}}</td><td>{{scope description, wrap identifiers in <code>}}</td></tr>
321
+ </tbody>
322
+ </table>
323
+ ```
324
+
325
+ Pulled from the Sub-specs table in the root spec.
326
+
327
+ ### §10c — Data Model (collapsed)
328
+
329
+ ```html
330
+ <h2 id="data-model">{{Shared Data Model | Data Model}}</h2>
331
+ <details class="collapsible">
332
+ <summary>{{N}} tables <span class="count">click expand</span></summary>
333
+ <div>
334
+ <h3 style="margin:8px 0 6px;font-size:13px;color:var(--fg-muted)"><code>{{table_name}}</code> — {{new | extend}}</h3>
335
+ <p style="font-size:13px;color:var(--fg-muted)">{{column summary in 1–2 lines, mention key columns + indices}}</p>
336
+ // repeat per table
337
+ </div>
338
+ </details>
339
+ ```
340
+
341
+ **Rules:**
342
+ - Compress each table into one prose paragraph. Do not render the full schema table — readers go to spec.md for that.
343
+ - Highlight key columns and indices in the prose.
344
+
345
+ ### §10d — Not in Scope
346
+
347
+ ```html
348
+ <h2 id="not-in-scope">Not in Scope</h2>
349
+ <div class="nis">
350
+ <ul>
351
+ <li><b>{{Item name}}</b> — {{reason / defer note}}</li>
352
+ // ...
353
+ </ul>
354
+ </div>
355
+ ```
356
+
357
+ ---
358
+
359
+ ## §11 — Inline code and identifier rules
360
+
361
+ **Hard rule:** every technical identifier is wrapped in `<code>`. Applies in story descriptions, AS bodies, callouts, prose, and the TL;DR.
362
+
363
+ - `AS-001`, `S-003`, `P0` → `<code>`
364
+ - File paths: `<code>src/middleware/auth.ts</code>`
365
+ - Routes: `<code>POST /api/login</code>`
366
+ - Field / variable names: `<code>sid</code>`, `<code>HttpOnly</code>`, `<code>email_verified_at</code>`
367
+ - Env vars: `<code>NODE_ENV</code>`, `<code>PLUGIN_LATEST_VERSION</code>`
368
+ - Enum values: `<code>'active'</code>`, `<code>"finished"</code>`
369
+ - HTTP status: `<code>200</code>`, `<code>4xx</code>`
370
+ - Error codes: `<code>EMAIL_EXISTS</code>`, `<code>RATE_LIMIT</code>`
371
+
372
+ **Do NOT** wrap `<code>` around:
373
+
374
+ - Personal names, product names, team names
375
+ - Abstract references to tables ("the user table") — only wrap when writing the literal table name
376
+ - Numbers in natural prose ("3 retries", "24 hours")
377
+
378
+ ---
379
+
380
+ ## Global rules
381
+
382
+ 1. **One component per chunk.** Do not nest callouts inside stories inside collapsibles.
383
+ 2. **First AS of each story is `open`**, the rest collapsed.
384
+ 3. **Constraint groups collapsed by default.** Root group `open` in multi-spec.
385
+ 4. **Change Log + Snapshots + Data Model always collapsed.**
386
+ 5. **TL;DR is mandatory.** Even for short specs.
387
+ 6. **Never invent CSS classes.** Use only classes defined in the template.
388
+ 7. **Never generate `<style>` or `<script>`.** They already exist in the template.
389
+ 8. **One Write call.** Build the full buffer, then Write once. Edit loops cause drift and waste tokens.
390
+ 9. **Identifiers always `<code>`.** Do not let `AS-001` run as plaintext inside prose.
391
+ 10. **Sort time DESC** for Change Log and Snapshots (newest first).
392
+
393
+ ---
394
+
395
+ ## Build flow (summary)
396
+
397
+ ```
398
+ 1. Read template.html + components.md + one example pair (cached)
399
+ 2. Read spec.md (+ sub-specs if any)
400
+ 3. Parse: frontmatter, sections, stories, AS, constraints, snapshots
401
+ 4. Classify single vs multi-spec
402
+ 5. Fill ~12 placeholder strings (§1 META_EXTRA, langs, etc.)
403
+ 6. Render TOC (§2) — 1 line per heading or story
404
+ 7. Render body section by section:
405
+ - TL;DR (§3) — always first
406
+ - Sub-specs table (§10b) — multi-spec only
407
+ - Data Model (§10c) — collapsed
408
+ - Per sub-spec: subspec-head (§7) + overview + stories (§4) + AS (§5)
409
+ - Constraints (§6) — flat or grouped
410
+ - What Already Exists (§10a) — prose
411
+ - Not in Scope (§10d) — list
412
+ - Change Log (§8) — collapsed
413
+ - Snapshots (§9) — collapsed (skip if empty)
414
+ 8. Write spec.html (once)
415
+ 9. Verify: no leftover placeholders, every TOC id matches the body
416
+ ```
417
+
418
+ Output token cost ≈ spec.md size × 1.2. CSS/JS/sprite never appear in the AI output stream after the initial template build.