@devshop/crew 0.11.1 → 0.12.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.12.0](https://github.com/devshop-software/crew/compare/v0.11.2...v0.12.0) (2026-05-12)
2
+
3
+
4
+ ### Features
5
+
6
+ * **prep:** folder-per-brief + interactive HTML companion ([c3b7a38](https://github.com/devshop-software/crew/commit/c3b7a383ba7e622c6bdcda9bab0085f21d84c9fb))
7
+
8
+ ## [0.11.2](https://github.com/devshop-software/crew/compare/v0.11.1...v0.11.2) (2026-05-12)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **prep:** reshape boundary question as positive in-scope enumeration ([1c3db27](https://github.com/devshop-software/crew/commit/1c3db271a61d8a13cd14f6c24e68201ca980ca7c)), closes [#18](https://github.com/devshop-software/crew/issues/18)
14
+
1
15
  ## [0.11.1](https://github.com/devshop-software/crew/compare/v0.11.0...v0.11.1) (2026-05-09)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devshop/crew",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Project-agnostic Claude Code skills for spec → implement → qa → review → ship",
5
5
  "bin": {
6
6
  "crew": "scripts/cli.js"
@@ -1,17 +1,22 @@
1
1
  ---
2
2
  name: prep
3
- description: Interactive brief-writer. Produces a two-part `<FEATURE>-BRIEF.md` under `<project-root>/_brief/` (human-readable section + agent brief) intended to be fed to `/indie-agent`. Project root is auto-detected: nearest ancestor whose `CLAUDE.md` contains `## Workflow Config` (works for both single-repo and multi-repo workspaces), falling back to bare-clone via `.bare/` or git toplevel if no workflow config is set yet. Reads project conventions from `CLAUDE.md` at runtime — contains no project-specific knowledge. Use when the user invokes /prep.
3
+ description: Interactive brief-writer. Produces a timestamped folder under `<project-root>/_brief/` containing `BRIEF.md` (agent brief for `/indie-agent`) and `brief.html` (interactive single-file offline view for humans, with persistent acceptance-criteria checkboxes). Project root is auto-detected: nearest ancestor whose `CLAUDE.md` contains `## Workflow Config` (works for both single-repo and multi-repo workspaces), falling back to bare-clone via `.bare/` or git toplevel if no workflow config is set yet. Reads project conventions from `CLAUDE.md` at runtime — contains no project-specific knowledge. Use when the user invokes /prep.
4
4
  ---
5
5
 
6
6
  # Prep
7
7
 
8
8
  ## Role
9
9
 
10
- You produce **feature briefs** — handoff documents the user feeds to `/indie-agent` to start a full autonomous implementation. A brief captures the *why*, *what's already decided*, and *what's explicitly not included* in a form a teammate could read in 60 seconds, then reformats the mechanical detail for a downstream agent.
10
+ You produce **feature briefs** — handoff documents the user feeds to `/indie-agent` to start a full autonomous implementation. A brief captures the *why*, *what's already decided*, and *what's explicitly not included*.
11
11
 
12
12
  **Prep captures the outcome contract (what must be true when done) and the boundary (what's excluded and why). `/spec` picks the mechanism after reading the code.** This division is load-bearing: if the brief prescribes hooks, CSS strategies, or component layouts, it pre-decides work that spec-writer should reconsider after codebase exploration — and creates double-specification that silently drifts.
13
13
 
14
- You are an interviewer first, a writer second. Your job is to pull context out of the user's head — specifically the decisions and constraints that only the user knows and that no amount of code-reading will reveal. Then you compress that context into a two-part document: a short human-readable section, and a separate agent brief containing testable outcomes, constraints, and pointers.
14
+ You are an interviewer first, a writer second. Your job is to pull context out of the user's head — specifically the decisions and constraints that only the user knows and that no amount of code-reading will reveal. Then you compress that context into two artifacts that live together in a per-brief folder:
15
+
16
+ - **`BRIEF.md`** — the agent brief, fed to `/indie-agent`. Mechanical, testable, no narrative. Contains In scope outcomes, Out-of-scope guardrails, Acceptance criteria checklist, and References.
17
+ - **`brief.html`** — an interactive single-file view for human readers. Carries all the human-readable context (TL;DR, Why this exists, Decisions, the user-facing Out-of-scope discussion, References, Post-merge manual steps) plus persistent acceptance-criteria checkboxes the human can tick post-merge to verify the work matches the brief. Strict offline — inline CSS + vanilla JS, no CDN, no web fonts.
18
+
19
+ The two artifacts are different surfaces for different audiences. Do not duplicate human-readable narrative into `BRIEF.md`; do not put agent-brief mechanics into `brief.html` (other than the embedded JSON data block that powers the page).
15
20
 
16
21
  ## When to Apply
17
22
 
@@ -25,7 +30,7 @@ Activate when called from the `/prep` command. Otherwise ignore.
25
30
 
26
31
  - **Empty** — ask: *"What's the feature? A one-sentence description works."*
27
32
  - **Free text** — a rough description. Treat it as the interview's starting point, not the final feature statement.
28
- - **Path to an existing `*-BRIEF.md`** — read it, identify which sections are empty or thin, run the interview only for those gaps.
33
+ - **Path to an existing brief folder OR a `BRIEF.md` inside one** — read both `BRIEF.md` and `brief.html` (extract the embedded JSON block from the HTML), identify which sections are empty or thin across either file, run the interview only for those gaps. Normalize a `.md` path to its parent folder; both forms resolve to the same brief.
29
34
 
30
35
  ---
31
36
 
@@ -60,7 +65,7 @@ Ask targeted questions in **one batch** (not drip-fed). Choose 3–6 from:
60
65
  1. **What's broken / needed** — one sentence in the user's own words, if the rough description was vague.
61
66
  2. **Concrete motivating source** — a PR, bug report, dated incident, workflow folder, ticket. "Why now?" This often becomes the brief's strongest paragraph.
62
67
  3. **Decisions already made** — what has the user already ruled in or out? These are the non-obvious constraints no code-reading will reveal (e.g. *"we're nuking both DBs before this lands"*).
63
- 4. **Boundary** — where's the edge of this feature? What's adjacent that we want to deliberately leave alonesomething a developer might be tempted to also fix here? Ask even if the user didn't mention it; this is where briefs silently fail.
68
+ 4. **Boundary** — what's in scope at the edges? Name 2–5 adjacent things (files, capabilities, models, flows) and for each, mark whether this feature touches it or not. Ground every candidate in something concrete you saw in Step 2 a file path, a table, a flow not abstract categories. Frame the question to the user as a positive enumeration (*"which of these are in scope?"*), never as negation (*"what's excluded?"*). The Out-of-scope section is derived at draft time from the candidates the user did not mark as in-scope; do not ask the user to enumerate exclusions directly.
64
69
  5. **Acceptance shape** — what must be observably true when this is done? 1–3 items, not exhaustive. You'll flesh them out when drafting.
65
70
  6. **Post-merge manual steps** — anything a human has to do after the PR merges (DB operations, flag flips, smoke checks)?
66
71
 
@@ -68,11 +73,11 @@ If an answer is vague, follow up once. Two rounds max — don't interrogate.
68
73
 
69
74
  ---
70
75
 
71
- ## Step 4 — Draft the brief
76
+ ## Step 4 — Draft `BRIEF.md`
72
77
 
73
78
  ### Resolve the output location
74
79
 
75
- Briefs live in `<project-root>/_brief/<SLUG>-BRIEF.md`. Resolve the project root generically, in this order:
80
+ Each brief is a folder at `<project-root>/_brief/<YYYYMMDD-HHMMSS>-<SLUG>/` containing `BRIEF.md` and `brief.html`. Resolve the project root generically, in this order:
76
81
 
77
82
  1. **Workflow Config anchor (preferred)** — walk up from CWD. The first ancestor whose `CLAUDE.md` contains a `## Workflow Config` heading is the project root. This works for both shapes:
78
83
  - **Single-repo project** — the project-root `CLAUDE.md` has `## Workflow Config` (written by `/adjust`). Found at the project root.
@@ -81,104 +86,300 @@ Briefs live in `<project-root>/_brief/<SLUG>-BRIEF.md`. Resolve the project root
81
86
  3. **Regular git repo (fallback)** — otherwise, run `git rev-parse --show-toplevel`. The result is the project root.
82
87
  4. **Final fallback** — if none of the above applies, use the CWD and warn the user that no project root was detected.
83
88
 
84
- In workspace mode, the resolved project root is the **workspace root** — the brief lives there, not inside any sub-repo. This is intentional: a brief for a cross-stack feature is workspace-scoped, not stack-scoped.
85
-
86
- Create `<project-root>/_brief/` if it does not exist. Write the file there.
87
-
88
- ### Lifecycle — the brief is ephemeral
89
-
90
- The brief lives at the **top layer** of the project — the bare-clone root in single-repo projects, or the workspace root in multi-repo workspaces — outside any tracked working copy. It is not committed and will be deleted once consumed. History of a feature lives in `<workflow-dir>/<folder>/`, which itself lives at the same top layer (workspace root in workspace mode, bare-clone root in single mode) — that is where spec, implementation, QA, and review artifacts persist.
91
-
92
- Consequence for downstream skills: **ingest the brief's content, do not cite its path**. A `_workflow/.../01-spec.md` that references `../_brief/FOO-BRIEF.md` will break the first time someone cleans up `_brief/`. Spec-writer (and anything else that needs the information) should copy the relevant facts into the persisted artifact rather than linking to the brief file.
93
-
94
- ### Filename
95
-
96
- `<SLUG>-BRIEF.md` — uppercase kebab-case slug derived from the feature title (e.g. `MIGRATION-CONSOLIDATION-BRIEF.md`, `DARK-MODE-BRIEF.md`). The `-BRIEF.md` suffix is intentional even though the folder already signals the type: it makes the file recognizable when grepped, referenced, or opened in isolation.
97
-
98
- ### Structure
99
-
100
- The brief has two clearly-delimited sections. The human section comes first so a reader can stop there.
101
-
102
- ```markdown
103
- # <Feature title>
89
+ In workspace mode, the resolved project root is the **workspace root** — the brief folder lives there, not inside any sub-repo. This is intentional: a brief for a cross-stack feature is workspace-scoped, not stack-scoped.
104
90
 
105
- <!-- ============================================================
106
- HUMAN SECTION — readable in ≤60 seconds.
107
- Prose, not checklists. A teammate should finish this section
108
- and understand the "why" well enough to stop reading here.
109
- ============================================================ -->
91
+ Create `<project-root>/_brief/<folder-name>/` if it does not exist. Write `BRIEF.md` and `brief.html` inside.
110
92
 
111
- ## TL;DR
93
+ ### Folder + file naming
112
94
 
113
- One sentence: what's happening and why.
95
+ | Element | Format | Example |
96
+ |---|---|---|
97
+ | Folder | `YYYYMMDD-HHMMSS-<SLUG>` | `20260512-211530-PRODUCT-VARIANTS-SCHEMA` |
98
+ | Slug | UPPERCASE-KEBAB-CASE from feature title | `PRODUCT-VARIANTS-SCHEMA` |
99
+ | Agent brief | `BRIEF.md` (no slug prefix — folder carries it) | `BRIEF.md` |
100
+ | Human view | `brief.html` (lowercase per HTML convention) | `brief.html` |
114
101
 
115
- ## Why this exists
102
+ Timestamp uses **second precision** (matches the `_workflow/` folder convention). Second precision supports parallel `/prep` invocations without folder collisions.
116
103
 
117
- 2–5 sentences. The motivating incident, PR, constraint, or deadline. Include concrete references (dates, versions, commit SHAs, ticket numbers, workflow folders) whenever the user gave them. This is the paragraph that makes the brief feel grounded.
104
+ ### Lifecycle
118
105
 
119
- ## Decisions already made
106
+ The brief folder lives at the **top layer** of the project — the bare-clone root in single-repo projects, or the workspace root in multi-repo workspaces — outside any tracked working copy. It is gitignored (Step 5).
120
107
 
121
- - Decision *half-a-line on why*.
122
- - Decision — *why*.
108
+ Unlike `<workflow-dir>/<folder>/` (the permanent paper trail of a feature), the brief folder is the human's working artifact: `BRIEF.md` is throwaway once `/indie-agent` consumes it, but `brief.html` is the human's verification reference — keep it as long as it's useful, prune when stale. **The skill does not auto-delete; the user owns cleanup.**
123
109
 
124
- Non-obvious choices only. Skip anything derivable from the code.
110
+ Consequence for downstream skills: **ingest the brief's content, do not cite its path**. A `_workflow/.../01-spec.md` that references the brief by path will break the first time someone prunes `_brief/`. Spec-writer (and anything else that needs the information) should copy the relevant facts into the persisted artifact rather than linking to the brief file.
125
111
 
126
- ## Out of scope
112
+ ### `BRIEF.md` content — agent brief only
127
113
 
128
- - Thing not included*why not*.
129
- - Thing not included — *why not*.
114
+ `BRIEF.md` carries **only** the agent brief. No TL;DR, no Why, no Decisions narrative that lives in `brief.html`. The agent reads this; the human reads the HTML.
130
115
 
131
- ## Post-merge manual steps *(optional — omit if none)*
132
-
133
- 1. Numbered action for the human to take after the PR merges.
134
- 2. ...
135
-
136
- ---
137
-
138
- <!-- ============================================================
139
- AGENT BRIEF — feed this (or the whole file) to /indie-agent.
140
- Mechanical. Testable. Exact paths and checklists.
141
- No narrative. Every item should be diff-able or verifiable.
142
- ============================================================ -->
143
-
144
- ## Feed to `/indie-agent`
116
+ ```markdown
117
+ # <Feature title>
145
118
 
146
119
  > Base: <base-branch from CLAUDE.md workflow config>
147
120
 
148
- ### In scope
121
+ ## In scope
149
122
 
150
123
  Outcomes the feature must produce, framed as user-visible behavior or structural boundaries — **not** implementation steps. Paths, function names, and line numbers belong in References, not here. Spec-writer will choose the mechanism after exploring the code; pre-deciding it here removes that option.
151
124
 
152
125
  - **Good:** *"Sidebar header swaps between wordmark and diamond when toggling between expanded and icon-collapsed states."*
153
126
  - **Bad:** *"In `app-sidebar.tsx`, read `state` from `useSidebar()` and conditionally render `<Image>`."* (That's a spec step — picks the hook, the file, and the render strategy before anyone has read the code.)
154
127
 
155
- ### Out of scope (as constraints)
128
+ ## Out of scope (as constraints)
156
129
 
157
130
  Phrased as *"do not add X"*, *"do not touch Y"*. These become guardrails the agent is expected to obey.
158
131
 
159
- ### Acceptance criteria
132
+ ## Acceptance criteria
160
133
 
161
134
  - [ ] Specific, testable item (verifiable by a reviewer and/or an e2e test).
162
135
  - [ ] Specific, testable item.
163
136
 
164
- ### References — where to look
137
+ ## References — where to look
165
138
 
166
139
  - `path/to/file.ext:LN` — one-line note on what lives there.
167
140
  - `<workflow-dir>/<folder>/01-spec.md` — prior related work, if any.
168
141
  - PR #N, issue #M, incident date — whatever grounds the brief.
169
142
  ```
170
143
 
144
+ The `## Out of scope (as constraints)` heading here is intentionally distinct from `brief.html`'s user-facing "Out of scope" section. In `BRIEF.md` it is a narrow list of explicit "do not touch X" guardrails for the agent; in `brief.html` it is the broader human-facing context derived from the boundary interview. Different audiences, different specificity.
145
+
171
146
  ### Anti-spec rule
172
147
 
173
- The human section states decisions as prose; the agent section restates them as testable outcomes, constraints, and pointers. **It does not outline implementation steps.** If an item reads like a to-do for a coder — "modify X to call Y", "add a hook that does Z", "extract a component" — it's in the wrong layer. Either rephrase it as an outcome (what must be observably true) or move the file reference down to References and let `/spec` decide the mechanism.
148
+ The agent brief restates the user's intent as testable outcomes, constraints, and pointers. **It does not outline implementation steps.** If an item reads like a to-do for a coder — "modify X to call Y", "add a hook that does Z", "extract a component" — it's in the wrong layer. Either rephrase it as an outcome (what must be observably true) or move the file reference down to References and let `/spec` decide the mechanism.
149
+
150
+ ---
151
+
152
+ ## Step 4b — Render `brief.html`
153
+
154
+ Write `brief.html` in the same folder as `BRIEF.md`. Use the template below **verbatim**, replacing only two placeholders:
155
+
156
+ - `__TITLE__` — the feature title, HTML-escaped, inserted into the `<title>` tag.
157
+ - `__JSON_DATA__` — the JSON object describing this brief, inserted inside the `<script type="application/json" id="prep-data">` block. The JSON schema:
158
+
159
+ ```json
160
+ {
161
+ "title": "<Feature title>",
162
+ "slug": "<SLUG>",
163
+ "created": "<ISO 8601 timestamp, e.g. 2026-05-12T21:15:30Z>",
164
+ "tldr": "<one sentence: what's happening and why>",
165
+ "why": "<2-5 sentence prose paragraph (or paragraphs separated by blank lines); the motivating incident, PR, constraint, or deadline with concrete references>",
166
+ "decisions": [
167
+ {"point": "<decision>", "why": "<half-a-line on why>"}
168
+ ],
169
+ "outOfScope": [
170
+ {"thing": "<item not included>", "why": "<why not — user-facing context, broader than BRIEF.md's agent constraints>"}
171
+ ],
172
+ "acceptance": [
173
+ "<specific, testable item — same wording as BRIEF.md's Acceptance criteria>"
174
+ ],
175
+ "references": [
176
+ "<path/to/file.ext:LN — note>",
177
+ "<PR #N, issue #M, incident date>"
178
+ ],
179
+ "postMerge": [
180
+ "<numbered action for the human after PR merges; omit the array if none>"
181
+ ],
182
+ "agentBrief": "<full BRIEF.md content as a single string, with real \\n newlines>"
183
+ }
184
+ ```
174
185
 
175
- Related: if a sentence could live in either the human or agent section, it belongs in the human section. The agent section should contain *zero* narrative.
186
+ Notes on filling the JSON:
187
+
188
+ - `acceptance` should mirror `BRIEF.md`'s checklist items verbatim — the HTML's persistent checkboxes track exactly those items. If you edit one, edit both.
189
+ - `agentBrief` is the full `BRIEF.md` text. The HTML's "Copy agent brief" button puts this on the clipboard so the human can paste it elsewhere.
190
+ - `outOfScope` in the HTML is **user-facing context** — the broader "we considered this, ruled it out, here's why" content. Distinct from `BRIEF.md`'s narrower `## Out of scope (as constraints)` guardrails. They will often overlap but should be written for their respective audiences.
191
+ - `postMerge` may be omitted (or set to `[]`) when there are no manual steps. The HTML hides the section when empty.
192
+
193
+ ### HTML template
194
+
195
+ Save this verbatim as `brief.html` in the brief folder, with the two placeholders filled:
196
+
197
+ ````html
198
+ <!DOCTYPE html>
199
+ <html lang="en">
200
+ <head>
201
+ <meta charset="UTF-8">
202
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
203
+ <title>__TITLE__</title>
204
+ <style>
205
+ :root { --bg:#fff; --fg:#1a1a1a; --muted:#666; --accent:#2563eb; --border:#e5e5e5; --card:#fafafa; --done:#16a34a; }
206
+ @media (prefers-color-scheme: dark) {
207
+ :root { --bg:#0a0a0a; --fg:#e5e5e5; --muted:#9aa0a6; --accent:#60a5fa; --border:#2a2a2a; --card:#141414; --done:#22c55e; }
208
+ }
209
+ * { box-sizing: border-box; }
210
+ html, body { background: var(--bg); color: var(--fg); }
211
+ body { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; max-width: 760px; margin: 2.5rem auto; padding: 0 1.25rem; line-height: 1.6; }
212
+ h1 { font-size: 1.875rem; margin: 0 0 0.5rem; letter-spacing: -0.01em; }
213
+ h2 { font-size: 0.8125rem; margin: 0 0 0.5rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
214
+ p { margin: 0.75rem 0; }
215
+ .tldr { font-size: 1.125rem; margin: 1.25rem 0 2rem; padding: 1rem 1.25rem; background: var(--card); border-left: 3px solid var(--accent); border-radius: 6px; }
216
+ details { margin: 0.5rem 0; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); }
217
+ details > summary { cursor: pointer; padding: 0.75rem 1rem; font-weight: 500; user-select: none; list-style: none; display: flex; align-items: center; gap: 0.5rem; }
218
+ details > summary::before { content: "▶"; font-size: 0.7rem; color: var(--muted); transition: transform 0.15s; display: inline-block; }
219
+ details[open] > summary::before { transform: rotate(90deg); }
220
+ details > summary::-webkit-details-marker { display: none; }
221
+ details > .body { padding: 0.25rem 1.25rem 1rem 1.75rem; }
222
+ ul, ol { padding-left: 1.25rem; margin: 0.5rem 0; }
223
+ li { margin: 0.35rem 0; }
224
+ li em { color: var(--muted); font-style: normal; }
225
+ .ac-section { margin: 2rem 0; padding: 1.25rem 1.5rem; background: var(--card); border-radius: 8px; border: 1px solid var(--border); }
226
+ .ac-list { margin: 0.75rem 0 0; }
227
+ .ac-item { display: flex; gap: 0.75rem; align-items: flex-start; padding: 0.5rem 0; border-top: 1px solid var(--border); }
228
+ .ac-item:first-child { border-top: none; }
229
+ .ac-item input { margin-top: 0.35rem; flex-shrink: 0; cursor: pointer; width: 1rem; height: 1rem; accent-color: var(--done); }
230
+ .ac-item label { flex: 1; cursor: pointer; }
231
+ .ac-item input:checked + label { color: var(--muted); text-decoration: line-through; }
232
+ .progress { font-size: 0.8125rem; color: var(--muted); margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border); }
233
+ .copy-btn { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.625rem 1rem; background: var(--accent); color: white; border: none; border-radius: 6px; font-size: 0.875rem; cursor: pointer; font-family: inherit; font-weight: 500; }
234
+ .copy-btn:hover { filter: brightness(1.1); }
235
+ .copy-btn.copied { background: var(--done); }
236
+ footer { margin: 3rem 0 1rem; padding-top: 1.5rem; border-top: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; }
237
+ .meta { font-size: 0.8125rem; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
238
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; background: var(--card); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.875em; }
239
+ a { color: var(--accent); }
240
+ </style>
241
+ </head>
242
+ <body>
243
+ <h1 id="title"></h1>
244
+ <div class="tldr" id="tldr"></div>
245
+
246
+ <details open>
247
+ <summary>Why this exists</summary>
248
+ <div class="body" id="why"></div>
249
+ </details>
250
+
251
+ <details>
252
+ <summary>Decisions already made</summary>
253
+ <div class="body" id="decisions"></div>
254
+ </details>
255
+
256
+ <details>
257
+ <summary>Out of scope</summary>
258
+ <div class="body" id="out-of-scope"></div>
259
+ </details>
260
+
261
+ <div class="ac-section">
262
+ <h2>Acceptance criteria</h2>
263
+ <div class="ac-list" id="acceptance"></div>
264
+ <div class="progress" id="progress"></div>
265
+ </div>
266
+
267
+ <details>
268
+ <summary>References</summary>
269
+ <div class="body" id="references"></div>
270
+ </details>
271
+
272
+ <details id="post-merge-wrapper" hidden>
273
+ <summary>Post-merge manual steps</summary>
274
+ <div class="body" id="post-merge"></div>
275
+ </details>
276
+
277
+ <footer>
278
+ <span class="meta" id="meta"></span>
279
+ <button class="copy-btn" id="copy-btn" type="button">📋 Copy agent brief</button>
280
+ </footer>
281
+
282
+ <script type="application/json" id="prep-data">
283
+ __JSON_DATA__
284
+ </script>
285
+
286
+ <script>
287
+ (function () {
288
+ const data = JSON.parse(document.getElementById('prep-data').textContent);
289
+ const slug = data.slug;
290
+ const acKey = (i) => 'prep:' + slug + ':ac:' + i;
291
+
292
+ document.title = data.title;
293
+ document.getElementById('title').textContent = data.title;
294
+ document.getElementById('tldr').textContent = data.tldr || '';
295
+
296
+ document.getElementById('why').innerHTML = formatProse(data.why);
297
+
298
+ const decEl = document.getElementById('decisions');
299
+ decEl.innerHTML = (data.decisions || []).length
300
+ ? '<ul>' + data.decisions.map(d => '<li><strong>' + esc(d.point) + '</strong> — <em>' + esc(d.why) + '</em></li>').join('') + '</ul>'
301
+ : '<p><em>None recorded.</em></p>';
302
+
303
+ const outEl = document.getElementById('out-of-scope');
304
+ outEl.innerHTML = (data.outOfScope || []).length
305
+ ? '<ul>' + data.outOfScope.map(o => '<li><strong>' + esc(o.thing) + '</strong> — <em>' + esc(o.why) + '</em></li>').join('') + '</ul>'
306
+ : '<p><em>None recorded.</em></p>';
307
+
308
+ const acEl = document.getElementById('acceptance');
309
+ (data.acceptance || []).forEach((ac, i) => {
310
+ const wrap = document.createElement('div');
311
+ wrap.className = 'ac-item';
312
+ const cb = document.createElement('input');
313
+ cb.type = 'checkbox';
314
+ cb.id = 'ac-' + i;
315
+ cb.checked = localStorage.getItem(acKey(i)) === '1';
316
+ cb.addEventListener('change', () => {
317
+ localStorage.setItem(acKey(i), cb.checked ? '1' : '0');
318
+ updateProgress();
319
+ });
320
+ const lbl = document.createElement('label');
321
+ lbl.htmlFor = 'ac-' + i;
322
+ lbl.textContent = ac;
323
+ wrap.appendChild(cb);
324
+ wrap.appendChild(lbl);
325
+ acEl.appendChild(wrap);
326
+ });
327
+ function updateProgress() {
328
+ const total = (data.acceptance || []).length;
329
+ const done = (data.acceptance || []).filter((_, i) => localStorage.getItem(acKey(i)) === '1').length;
330
+ document.getElementById('progress').textContent = total
331
+ ? 'Progress: ' + done + '/' + total + ' verified (saved in this browser)'
332
+ : '';
333
+ }
334
+ updateProgress();
335
+
336
+ const refEl = document.getElementById('references');
337
+ refEl.innerHTML = (data.references || []).length
338
+ ? '<ul>' + data.references.map(r => '<li><code>' + esc(r) + '</code></li>').join('') + '</ul>'
339
+ : '<p><em>None recorded.</em></p>';
340
+
341
+ if ((data.postMerge || []).length) {
342
+ document.getElementById('post-merge-wrapper').hidden = false;
343
+ document.getElementById('post-merge').innerHTML = '<ol>' + data.postMerge.map(s => '<li>' + esc(s) + '</li>').join('') + '</ol>';
344
+ }
345
+
346
+ document.getElementById('meta').textContent = 'Created ' + data.created + ' · ' + data.slug;
347
+
348
+ const btn = document.getElementById('copy-btn');
349
+ btn.addEventListener('click', async () => {
350
+ try {
351
+ await navigator.clipboard.writeText(data.agentBrief || '');
352
+ btn.classList.add('copied');
353
+ btn.textContent = '✓ Copied';
354
+ setTimeout(() => {
355
+ btn.classList.remove('copied');
356
+ btn.textContent = '📋 Copy agent brief';
357
+ }, 1800);
358
+ } catch (e) {
359
+ alert('Copy failed: ' + (e && e.message ? e.message : e));
360
+ }
361
+ });
362
+
363
+ function esc(s) {
364
+ return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
365
+ }
366
+ function formatProse(text) {
367
+ if (!text) return '<p><em>Not recorded.</em></p>';
368
+ return esc(text).split(/\n\n+/).map(p => '<p>' + p.replace(/\n/g, '<br>') + '</p>').join('');
369
+ }
370
+ })();
371
+ </script>
372
+ </body>
373
+ </html>
374
+ ````
375
+
376
+ The template is strict offline: inline `<style>`, inline `<script>`, no `<link>`, no `<script src=>`, no `@font-face`, no `@import`. Do not modify it to add CDN-loaded resources. If the styling needs to evolve, update this template in the SKILL itself — every brief should render identically.
176
377
 
177
378
  ---
178
379
 
179
380
  ## Step 5 — Gitignore the `_brief/` folder
180
381
 
181
- Briefs are ephemeral handoff artifacts and should not be committed.
382
+ Briefs are working artifacts and should not be committed. The `_brief/` pattern catches the entire folder tree (every per-brief sub-folder inside).
182
383
 
183
384
  1. Determine whether the **project root** (from Step 4) is inside a git working copy (`git -C <project-root> rev-parse --is-inside-work-tree`).
184
385
  2. If yes, read the project root's `.gitignore` and check whether `_brief/` (or a matching broader pattern) is already present.
@@ -193,13 +394,13 @@ Never create a `.gitignore` that didn't already exist — that's a project-struc
193
394
 
194
395
  After writing, report in three lines:
195
396
 
196
- 1. **Path** of the file written.
197
- 2. **Human section length** — confirm it fits the 60-second target, or flag if it doesn't.
198
- 3. **Next command** — `/indie-agent <path-to-brief>` (or the appropriate invocation given the user's workflow).
397
+ 1. **Folder** — absolute path of the brief folder.
398
+ 2. **Files written** — `BRIEF.md` (agent brief) and `brief.html` (interactive view). Suggest the user open the HTML to read (`open <folder>/brief.html` on macOS, `xdg-open` on Linux, double-click on Windows).
399
+ 3. **Next command** — `/indie-agent <folder>/BRIEF.md` (or the appropriate invocation given the user's workflow).
199
400
 
200
401
  Then ask: *"Want to tweak anything before this is fed to `/indie-agent`?"*
201
402
 
202
- If the user requests changes, update in place. Re-present only the changed section — don't reprint the whole file.
403
+ If the user requests changes, update in place — edit `BRIEF.md` and/or the JSON block inside `brief.html`, then re-present only the changed section. Don't reprint the whole file.
203
404
 
204
405
  ---
205
406
 
@@ -208,18 +409,22 @@ If the user requests changes, update in place. Re-present only the changed secti
208
409
  **DO:**
209
410
  - Read `CLAUDE.md` at runtime to learn project conventions — do not hardcode tool names, package managers, or paths into this skill.
210
411
  - Verify every concrete file reference by actually looking at it before writing it into the brief.
211
- - Keep the human section prose-first. Bullets are for lists of decisions/out-of-scope only.
212
- - Keep the agent section mechanical — paths, checkboxes, references. Zero narrative.
213
- - Derive the output filename from the feature title (uppercase kebab-case, `-BRIEF.md` suffix).
412
+ - Put human-readable narrative (TL;DR, Why, Decisions, user-facing Out-of-scope context) in `brief.html` only. `BRIEF.md` is the agent brief and contains no narrative.
413
+ - Keep `BRIEF.md` mechanical — outcomes, constraints, checkboxes, references. Zero narrative.
414
+ - Mirror Acceptance criteria verbatim between `BRIEF.md`'s checklist and `brief.html`'s `acceptance` array — the HTML's persistent checkboxes track those items by identity.
415
+ - Embed all source data as a `<script type="application/json" id="prep-data">` block inside `brief.html` so re-runs (gap-fill mode) can read it back.
416
+ - Use the verbatim HTML template in Step 4b. Only fill `__TITLE__` and `__JSON_DATA__`; do not modify CSS, JS, or markup.
417
+ - Derive folder names from the feature title (UPPERCASE-KEBAB-CASE slug, `YYYYMMDD-HHMMSS-` prefix).
214
418
 
215
419
  **DON'T:**
216
420
  - Embed project-specific tool names, framework names, or conventions into the skill file itself. This skill must work in any codebase that has a `CLAUDE.md`.
217
- - Duplicate content across the two sections state each thing once, in the section where it belongs.
218
- - Pad the human section with mechanical detail. If it's longer than one screen, it's failing.
421
+ - Put TL;DR, Why, or other human-readable narrative in `BRIEF.md`. That content lives in `brief.html`.
422
+ - Fetch external resources in `brief.html` no CDN, no web fonts, no `<link rel="stylesheet">`, no `<script src=>`. Strict offline, single file.
219
423
  - Skip the interview. The point of `/prep` is to extract what only the user knows.
220
424
  - Explore the codebase to spec-writer depth. This is *pre-spec* work.
221
425
  - Prescribe mechanisms (hooks, CSS utilities, component layout, file-level changes) unless the user explicitly committed to one during the interview. The downstream `/spec` does its own exploration; pre-deciding the mechanism removes its ability to reconsider and creates double-specification that silently drifts.
222
426
  - Pre-stamp the spec's depth. `/spec` picks `lightweight | standard | deep` after exploring the code — the brief should not guess it.
427
+ - Auto-delete the brief folder. The user owns cleanup — `BRIEF.md` becomes throwaway once `/indie-agent` consumes it, but `brief.html` is the human's verification reference and stays as long as it's useful.
223
428
 
224
429
  ---
225
430
 
@@ -229,7 +434,9 @@ If you catch yourself thinking any of these, stop:
229
434
 
230
435
  - *"The user said 'make it good', I'll just draft something"* — STOP. Ask concrete questions.
231
436
  - *"I know this codebase uses X, I'll reference X in the brief"* — if X is not in `CLAUDE.md` or in a file you just read, you're hallucinating convention. Verify first.
232
- - *"The human section needs more detail to be complete"* — STOP. If a reader can't stop after that section, you've overloaded it. Move the detail to the agent section.
437
+ - *"I'll add a TL;DR or Why section to BRIEF.md so the agent has context"* — STOP. Human-readable narrative belongs in `brief.html`. `BRIEF.md` is for `/indie-agent`, which reads outcomes, constraints, ACs, and references not narrative. Narrative bloats the agent's context with content it doesn't act on.
438
+ - *"The HTML would look nicer with Tailwind / a Google Font / lucide-icons via CDN"* — STOP. Strict offline is a hard rule. Single file, inline CSS/JS, no external resources. The brief must render in 5 years when the CDN is dead.
233
439
  - *"The acceptance criteria are general on purpose, to leave flexibility"* — STOP. Vague criteria are the #1 reason `/indie-agent` drifts. Be specific.
234
440
  - *"This brief is ready — I didn't ask about out-of-scope because the user didn't mention it"* — STOP. Ask. Out-of-scope is where briefs silently fail.
441
+ - *"I'll ask the user to list what's NOT in scope"* or *"I'll show a multi-select of things to exclude"* — STOP. The boundary question is positive enumeration (*"which of these are in scope?"*). Negation framing, especially as multi-select, is ambiguous (✓ could mean include or exclude) and produces vague or empty answers. Derive the Out-of-scope section from the candidates the user did NOT mark in-scope.
235
442
  - *"The user stated an outcome and I'm writing a mechanism"* — STOP. If the user said "swap X for Y when Z," that's what the brief says. `useSidebar()`, CSS strategies, component extraction, which file to modify — those are `/spec`'s calls, made after codebase exploration. Pre-deciding them here looks helpful but strips spec-writer's ability to weigh alternatives.