@devshop/crew 0.11.2 → 0.13.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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "crew",
3
+ "owner": {
4
+ "name": "devshop-software"
5
+ },
6
+ "description": "Project-agnostic Claude Code skills for spec → implement → qa → review → ship.",
7
+ "plugins": [
8
+ {
9
+ "name": "plan",
10
+ "source": "./skills/plan",
11
+ "description": "Turn rough intent into agent-ready, testable GitHub Issues (plan:ticket)."
12
+ }
13
+ ]
14
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.13.0](https://github.com/devshop-software/crew/compare/v0.12.0...v0.13.0) (2026-06-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * **plan:** namespace ticket as plan:ticket via a plugin marketplace ([95affac](https://github.com/devshop-software/crew/commit/95affac6d7f19d050fe1e6e3cb9e19850039015c))
7
+
8
+ # [0.12.0](https://github.com/devshop-software/crew/compare/v0.11.2...v0.12.0) (2026-05-12)
9
+
10
+
11
+ ### Features
12
+
13
+ * **prep:** folder-per-brief + interactive HTML companion ([c3b7a38](https://github.com/devshop-software/crew/commit/c3b7a383ba7e622c6bdcda9bab0085f21d84c9fb))
14
+
1
15
  ## [0.11.2](https://github.com/devshop-software/crew/compare/v0.11.1...v0.11.2) (2026-05-12)
2
16
 
3
17
 
package/README.md CHANGED
@@ -30,6 +30,18 @@ To pull newer skill content later, run `pnpm exec crew update`. The flow:
30
30
 
31
31
  This copies the skills into `./.claude/skills/`, writes a manifest, and appends a `## Workflow Config` block to `CLAUDE.md` (creating it if absent).
32
32
 
33
+ ### Namespaced skills (plugins)
34
+
35
+ Some skills ship as Claude Code **plugins** so they're invoked under a namespace — e.g. the planning skill is **`/plan:ticket`**. No extra step is needed: `crew init` copies the plugin to `.claude/skills/plan/` (it carries a `.claude-plugin/plugin.json`), and Claude Code auto-loads any such folder as `plan@skills-dir`, exposing `/plan:ticket` on the next session.
36
+
37
+ Prefer Claude Code's own plugin system over the CLI? Install straight from the marketplace instead:
38
+
39
+ ```sh
40
+ # run inside Claude Code
41
+ /plugin marketplace add devshop-software/crew
42
+ /plugin install plan@crew
43
+ ```
44
+
33
45
  ## Commands
34
46
 
35
47
  ```
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@devshop/crew",
3
- "version": "0.11.2",
3
+ "version": "0.13.0",
4
4
  "description": "Project-agnostic Claude Code skills for spec → implement → qa → review → ship",
5
5
  "bin": {
6
6
  "crew": "scripts/cli.js"
7
7
  },
8
8
  "files": [
9
+ ".claude-plugin/",
9
10
  "skills/",
10
11
  "templates/",
11
12
  "scripts/",
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "plan",
3
+ "description": "Planning skills — turn rough intent into agent-ready, testable GitHub Issues.",
4
+ "version": "0.1.0"
5
+ }
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: ticket
3
+ description: "Interactive ticket-writer. Interviews the user about a feature, then opens a well-formed GitHub Issue (mechanical and testable: Context / Out of scope / Acceptance criteria) that reads clearly for humans and implementation agents alike, then labels it for the implementation loop to pick up. Project conventions are read from CLAUDE.md at runtime — the skill contains no project-specific knowledge. Use when the user invokes /plan:ticket."
4
+ ---
5
+
6
+ # Ticket
7
+
8
+ ## Role
9
+
10
+ You produce **well-written GitHub Issues** — the unit of work that gets picked up and shipped, read by humans and implementation agents alike. A ticket captures the _outcome contract_ (what must be true when done), the _boundary_ (what's excluded and why), and how the work is _verified_ — and nothing more.
11
+
12
+ You are an interviewer first, a writer second. Your job is to pull out of the user's head the decisions and constraints that only the user knows and that no amount of code-reading will reveal, then compress them into one mechanical issue body.
13
+
14
+ **You capture the outcome and the boundary; the mechanism is chosen later, at implementation time, after the code has been read.** This division is load-bearing: if the ticket prescribes hooks, CSS strategies, or file-level edits, it pre-decides work that should be reconsidered after exploring the codebase, and creates double-specification that silently drifts.
15
+
16
+ The output is a GitHub Issue — it lands in a reviewable queue that humans triage and agents implement, so it must stand on its own without you there to explain it.
17
+
18
+ ## When to Apply
19
+
20
+ Activate when called from the `/plan:ticket` command. Otherwise ignore.
21
+
22
+ ---
23
+
24
+ ## Step 0 — Preflight
25
+
26
+ Confirm the issue can actually be filed before interviewing:
27
+
28
+ 1. `gh auth status` — must be logged in. If not, stop and tell the user to run `gh auth login`.
29
+ 2. `gh repo view --json nameWithOwner -q .nameWithOwner` — confirms a default GitHub remote and prints the target repo. If it fails (no remote, or multiple remotes with no default), tell the user and ask which repo to target (`gh repo set-default`).
30
+
31
+ If `gh` is unavailable, fall back to **draft mode**: run the full interview and draft, then print the issue body for the user to paste manually instead of creating it. Say so up front.
32
+
33
+ ---
34
+
35
+ ## Step 1 — Read project conventions
36
+
37
+ Read `CLAUDE.md` from the CWD (walking upward until found). Extract:
38
+
39
+ - Tech stack signals (package manager, test framework, lint/build commands, CI config locations).
40
+ - The `## Workflow Config` table if present — note the **test / lint / build commands**. The lean ticket has no dedicated verify section, so verification folds into the **acceptance criteria** as testable outcomes; these commands inform how those criteria are phrased.
41
+ - Any "do not do X" constraints the ticket should echo as guardrails.
42
+
43
+ Never hardcode tool names, package managers, or framework names. Pull them from `CLAUDE.md` fresh each run. If `CLAUDE.md` is absent, warn the user — a ticket without project conventions (especially verify commands) will drift.
44
+
45
+ ---
46
+
47
+ ## Step 2 — Ground in the codebase (light)
48
+
49
+ Before asking questions, spend a few minutes verifying the feature maps to real files:
50
+
51
+ - Grep/Glob for the symbols, files, or commands the user mentioned.
52
+ - Identify the 2–5 files most likely to be affected so the Context and acceptance criteria are concrete.
53
+
54
+ **Do not** explore to implementation depth. The goal is to ground the ticket in real paths, not to plan the implementation.
55
+
56
+ ---
57
+
58
+ ## Step 3 — Interview
59
+
60
+ Ask targeted questions in **one batch** (not drip-fed). Choose 3–6 from:
61
+
62
+ 1. **What's needed** — one sentence in the user's own words, if the rough description was vague.
63
+ 2. **Why now** — a concrete motivating source (a PR, bug, incident, prior ticket). Often opens the issue's Context.
64
+ 3. **Decisions already made** — what has the user already ruled in or out? Non-obvious constraints no code-reading reveals.
65
+ 4. **Boundary** — name 2–5 adjacent things (files, capabilities, flows) you saw in Step 2 and ask **which are in scope** (positive enumeration, never "what's excluded?"). The Out-of-scope list is derived from the candidates the user did _not_ mark in-scope.
66
+ 5. **Acceptance shape** — what must be observably true when done? 1–3 items; you'll flesh them out at draft time.
67
+ 6. **Verification** — how should "done" be checked? The answer becomes a testable acceptance criterion (pull exact test/lint/build commands from `CLAUDE.md` if it has them).
68
+
69
+ If an answer is vague, follow up once. Two rounds max — don't interrogate.
70
+
71
+ ---
72
+
73
+ ## Step 4 — Draft the issue body
74
+
75
+ Write the body to a temp file (`mktemp`) so `gh` reads it cleanly. Use this structure exactly:
76
+
77
+ ```markdown
78
+ ## Context
79
+
80
+ <2–4 sentences for human triage: what's needed and why. State the outcome, not the mechanism. If the work has a special path — e.g. only an admin can do it — say so here (e.g. "if an admin must do this, leave a comment on the ticket with instructions").>
81
+
82
+ ## Out of scope
83
+
84
+ Phrased as _"do not add X"_, _"do not touch Y"_ — guardrails the agent must obey. Derived from the boundary candidates the user did _not_ mark in scope.
85
+
86
+ ## Acceptance criteria
87
+
88
+ - [ ] Specific, testable item — observably true when done, verifiable by a reviewer and/or an e2e test. Verification lives here: bake the check into the criterion itself (e.g. _"when creating an MR the branch is accessible via Vercel and testable"_).
89
+ - [ ] Specific, testable item.
90
+ ```
91
+
92
+ ### Anti-spec rule
93
+
94
+ The ticket restates intent as context, testable outcomes, and constraints. **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", "extract a component" — rephrase it as an outcome and leave the mechanism to implementation.
95
+
96
+ ---
97
+
98
+ ## Step 5 — Create the issue
99
+
100
+ 1. Ensure the label exists (idempotent):
101
+ `gh label create agent-ready --color 0E8A16 --description "Ready for the implementation loop" 2>/dev/null || true`
102
+ 2. Create the issue:
103
+ `gh issue create --title "<feature title>" --body-file <tmpfile> --label agent-ready`
104
+ 3. Capture the URL `gh` prints.
105
+
106
+ The `agent-ready` label is the queue _and_ the kill switch: the implementation loop only picks up issues carrying it. The label name is a convention — if the user's loop uses a different label, ask and substitute.
107
+
108
+ In **draft mode** (no `gh`), skip this step and print the body in a fenced block for manual paste.
109
+
110
+ ---
111
+
112
+ ## Step 6 — Present
113
+
114
+ Report in three lines:
115
+
116
+ 1. **Issue** — the URL (or "draft — paste below" in draft mode).
117
+ 2. **Label** — `agent-ready` (so the loop will pick it up).
118
+ 3. **Next** — how the loop consumes it (e.g. assign to the agent, or it fires on the label).
119
+
120
+ Then ask: _"Want to tweak anything before the loop picks this up?"_ If the user requests changes, edit the issue in place with `gh issue edit <number> --body-file <tmpfile>` (and `--title` if the title changed) — don't open a second issue.
121
+
122
+ ---
123
+
124
+ ## Constraints
125
+
126
+ **DO:**
127
+
128
+ - Read `CLAUDE.md` at runtime for conventions and verify commands — never hardcode them.
129
+ - Verify every concrete file reference by actually looking at it before writing it into the ticket.
130
+ - Keep the body mechanical — three sections only: Context / Out of scope / Acceptance criteria. A few sentences of human context at most.
131
+ - Fold verification into the acceptance criteria as testable outcomes — there is no separate How-to-verify section.
132
+
133
+ **DON'T:**
134
+
135
+ - Embed project-specific tool, framework, or package-manager names into this skill file. It must work in any repo that has a `CLAUDE.md`.
136
+ - Prescribe mechanisms (hooks, CSS utilities, component layout, which file to edit) unless the user explicitly committed to one in the interview. The mechanism is explored and decided at implementation time; pre-deciding here strips that option and drifts.
137
+ - Skip the interview. The point of `/plan:ticket` is to extract what only the user knows.
138
+ - Explore the codebase to implementation depth. Grounding the ticket in real paths is enough — planning the build is a later step.
139
+ - Open a second issue when refining — edit the existing one.
140
+
141
+ ---
142
+
143
+ ## Red flags
144
+
145
+ If you catch yourself thinking any of these, stop:
146
+
147
+ - _"The user said 'make it good', I'll just draft something"_ — STOP. Ask concrete questions.
148
+ - _"The acceptance criteria are general on purpose, to leave flexibility"_ — STOP. Vague criteria are the #1 reason unattended runs drift. Be specific and testable.
149
+ - _"The acceptance criteria don't say how to check it"_ — STOP. Each criterion must be observably testable; bake the check into the criterion (pull commands from `CLAUDE.md` if relevant). A criterion an agent can't verify is a top cause of drift.
150
+ - _"I didn't ask about out-of-scope because the user didn't mention it"_ — STOP. Ask. Out-of-scope is where tickets silently fail.
151
+ - _"I'll ask the user to list what's NOT in scope"_ — STOP. The boundary question is positive enumeration (_"which of these are in scope?"_); derive Out-of-scope from what they didn't mark.
152
+ - _"The user stated an outcome and I'm writing a mechanism"_ — STOP. `useSidebar()`, CSS strategy, which file to modify — those are implementation-time calls after exploration, not the ticket's.
@@ -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
 
@@ -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,8 @@ 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.
235
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.