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.
- package/README.md +1319 -0
- package/bin/devkit.js +3 -0
- package/package.json +61 -0
- package/src/cli.js +76 -0
- package/src/commands/check.js +33 -0
- package/src/commands/diff.js +84 -0
- package/src/commands/init-adopt.js +54 -0
- package/src/commands/init-agents.js +118 -0
- package/src/commands/init-global.js +102 -0
- package/src/commands/init.js +311 -0
- package/src/commands/list.js +54 -0
- package/src/commands/remove.js +133 -0
- package/src/commands/upgrade.js +215 -0
- package/src/lib/agent-guards.js +100 -0
- package/src/lib/agent-install.js +161 -0
- package/src/lib/agents.js +280 -0
- package/src/lib/claude-global.js +183 -0
- package/src/lib/detector.js +93 -0
- package/src/lib/hasher.js +21 -0
- package/src/lib/installer.js +213 -0
- package/src/lib/logger.js +16 -0
- package/src/lib/manifest.js +102 -0
- package/src/lib/reconcile.js +56 -0
- package/templates/.claude/CLAUDE.md +79 -0
- package/templates/.claude/hooks/comment-guard.js +126 -0
- package/templates/.claude/hooks/file-guard.js +216 -0
- package/templates/.claude/hooks/glob-guard.js +104 -0
- package/templates/.claude/hooks/path-guard.sh +118 -0
- package/templates/.claude/hooks/self-review.sh +27 -0
- package/templates/.claude/hooks/sensitive-guard.sh +227 -0
- package/templates/.claude/settings.json +68 -0
- package/templates/docs/WORKFLOW.md +325 -0
- package/templates/docs/specs/.gitkeep +0 -0
- package/templates/hooks/specpipe-read-guard.sh +42 -0
- package/templates/hooks/specpipe-shell-guard.sh +65 -0
- package/templates/rules/specpipe-guards.md +40 -0
- package/templates/scripts/test-hooks.sh +66 -0
- package/templates/skills/sp-build/SKILL.md +776 -0
- package/templates/skills/sp-challenge/SKILL.md +255 -0
- package/templates/skills/sp-commit/SKILL.md +174 -0
- package/templates/skills/sp-explore/SKILL.md +730 -0
- package/templates/skills/sp-fix/SKILL.md +266 -0
- package/templates/skills/sp-humanize/SKILL.md +212 -0
- package/templates/skills/sp-investigate/SKILL.md +648 -0
- package/templates/skills/sp-md-render/SKILL.md +200 -0
- package/templates/skills/sp-md-render/components.md +415 -0
- package/templates/skills/sp-md-render/template.html +283 -0
- package/templates/skills/sp-plan/SKILL.md +947 -0
- package/templates/skills/sp-review/SKILL.md +268 -0
- package/templates/skills/sp-scaffold/SKILL.md +237 -0
- package/templates/skills/sp-scaffold/references/ARCHITECTURE.md.tmpl +228 -0
- package/templates/skills/sp-scaffold/references/DESIGN.md.tmpl +113 -0
- package/templates/skills/sp-scaffold/references/adr/NNNN-template.md +92 -0
- package/templates/skills/sp-scaffold/references/stack-profiles/react.md +36 -0
- package/templates/skills/sp-spec-render/SKILL.md +254 -0
- package/templates/skills/sp-spec-render/components.md +418 -0
- package/templates/skills/sp-spec-render/examples/user-auth.html +749 -0
- package/templates/skills/sp-spec-render/examples/user-auth.md +114 -0
- package/templates/skills/sp-spec-render/template.html +222 -0
- 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.
|