Writes implementation-ready specification files with progress-trackable phases. Activates when: writing a spec, creating a spec file, documenting a feature plan, or when user mentions: write spec, create spec, spec format, spec template.
php artisan boost:add-skill sandermuller/boost-skills
Save this content to: .claude/skills/write-spec/SKILL.md
# Write Spec
Writes structured specification files designed for phased implementation with built-in progress tracking. Specs produced by this skill are directly compatible with the `implement-spec` skill.
## Filename placeholder substitution
The spec filename pattern is <!--boost:conv path="spec.filename_pattern" mode="inline"-->specs/{slug}.md<!--boost:conv:end-->. It accepts these placeholders, which you substitute at spec-creation time:
- `{issue_key}` — full Jira key (e.g. `HPB-1234`). Resolved from the user's input, or from the current branch name if it matches one of the configured branch patterns:
```boost:conv
<!--boost:conv path="branches.patterns" mode="yaml"-->none — no branch-pattern issue-key detection<!--boost:conv:end-->
```
The configured Jira project key is <!--boost:conv path="jira.project_key" mode="inline"-->(none — the spec is treated as not-issue-backed)<!--boost:conv:end-->. If no issue is involved (or no project key is configured), the placeholder resolves empty.
- `{slug}` — URL-safe kebab-case from the feature name. Lowercase, alphanumerics + dashes, no consecutive or leading/trailing dashes, recommended cap ~60 chars truncated on word boundary.
- `{date}` — ISO date (`YYYY-MM-DD`).
**Empty-placeholder rule**: if a placeholder resolves empty, the placeholder AND any single adjacent dash are omitted. Example: `specs/{issue_key}-{slug}.md` with no issue + feature name "Set up dark mode" → `specs/set-up-dark-mode.md` (not `specs/-set-up-dark-mode.md`).
## When to Use This Skill
- Writing a new spec from gathered requirements
- Converting loose notes or issues into a formal spec
- **Not** for implementing (`implement-spec` skill)
## Gate: Are Requirements Settled?
Before writing a single line of spec, check for fuzziness. **Judge fuzziness only after loading all available context** — read the linked issue and run the [Research checklist](#research-before-writing--required-checklist) below first. An issue-backed ask like "write a spec for the export feature" is one sentence on its own, but if the linked issue carries a concrete description + acceptance criteria, intent is sharp and the gate does not fire — converting an issue into a spec is a documented use of this skill. Bounce to `interview` only when ambiguity remains *after* the context is in hand.
If **any** of these are true with all context loaded, **bounce the user to the `interview` skill first** — do not power through:
- The ask is underspecified — even after reading the issue — or uses overloaded terms without disambiguation.
- The proposed solution requires picking between two or more genuinely different architectures.
- The issue's acceptance criteria contradict each other or the description.
- You'd be guessing at edge-case behavior, error handling, or scope boundaries.
A short bounce message:
> "I'm seeing ambiguity around [X, Y]. Want me to run the `interview` skill first to grill those out, then write the spec from solid answers?"
If the user insists on writing immediately, capture each unresolved item in `## Open Questions` rather than inventing an answer. A spec built on guessed requirements ships those guesses as bugs.
## Spec File Location
Resolve the spec file path via the filename pattern above. Apply the placeholder-substitution rules above to produce the final path.
### More than one spec directory
A project may hold specs in several directories — <!--boost:conv path="spec.directories" mode="inline"-->specs/<!--boost:conv:end-->. When it does, two rules apply and they pull in opposite directions on purpose:
- **Search every listed directory** before creating or converting a spec. The duplicate check below is only sound if it covers all of them; a search limited to the write target grows two specs for one feature in two places. More than one candidate → stop and ask which is authoritative.
- **Write to one.** The write target is the directory of `spec.filename_pattern`. The list is ordered, and its first entry is the default write target when the project has not configured a pattern of its own.
If the `filename_pattern` prefix is **not** in the configured list, the configuration is inconsistent — there is no defined way to re-root a pattern like `docs/specs/{slug}.md` under a different directory. **Stop and ask** for the correct location; do not silently write the pattern's filename into the first listed directory. `boost doctor --check-conventions` reports the same configuration as an error.
Subdirectories for related specs are fine — if the filename pattern doesn't model the subdir convention, place spec files manually under the resolved parent dir:
```
specs/
├── HPB-1234-wildcard-performance-optimization.md
├── HPB-1267-polymorphic-field-support.md
└── cleanup/
└── deprecated-method-removal.md
```
## Planning Commit Stamp
When the spec is written in a git repository, stamp the commit it was planned against immediately under the spec's `# Title`:
```markdown
<!-- spec:planned-at <full-sha> <YYYY-MM-DD> -->
```
This lets `implement-spec` detect when cited code has drifted before it builds against stale `file:line` references.
- **SHA** — `git rev-parse HEAD` (full SHA, not short — matches the repo's `verified-sha` convention). **Date** — ISO `YYYY-MM-DD`.
- **Not a git repo** — omit the stamp entirely; the drift preflight then no-ops.
- **Dirty worktree** — the spec's `file:line` references come from the *working tree*, but `HEAD` names only the last commit. If `git status --porcelain` is non-empty, append a `+uncommitted` marker so the baseline is flagged approximate: `<!-- spec:planned-at <sha> <date> +uncommitted -->`. Prefer stamping against a clean tree when practical.
## If a Spec File Already Exists
Before writing from a fresh template, check whether a spec for this work already exists at (or under) the resolved path — and under **every** configured spec directory, not only the write target — an earlier `write-spec` run, or a requirements doc the `interview` skill's output was saved into. If one exists, read it and classify before doing anything:
| What you find | Action |
|---|---|
| Already implementation-ready — has a phased `## Implementation` section (or `### Phase N:` task checkboxes) | **Stop.** Don't re-wrap. Ask the user what they want changed (add tasks? a new phase? edit existing?). |
| Requirements-shaped — has problem/overview/edge-cases/acceptance content but no phased Implementation | **Convert in place** (below). |
| Loose notes / neither | Treat as input; fall through to the from-scratch flow. |
| More than one candidate file for the same work | **Stop and ask** which is authoritative — never pick silently. |
**Convert in place** rather than rewriting from a fresh template — rewriting silently drops captured decisions. Carry forward verbatim, unless a later step explicitly supersedes it: `## Overview`, `## Non-Goals`, `## Assumptions` (the user's skim-only sign-off ledger — never drop or wholesale-rewrite it), `## Terminology`, `## STOP Conditions`, `## Open Questions`, `## Resolved Questions`, `## Findings`, the issue link, the `**Personas:**` line, problem statement, edge-cases table, and acceptance criteria. Then **add** the technical sections and Implementation phases. When a later audit corrects an inherited `## Assumptions` or resolves an inherited `## Open Questions` entry, update it in place (and log the decision in `## Resolved Questions`) rather than leaving two contradictory bullets — the skim ledger must show exactly one truth per topic.
**Stamp on conversion**: if the existing spec carries a `spec:planned-at` stamp, refresh it to the current `HEAD` when the conversion materially re-bases the spec's `file:line` references on today's code — otherwise the drift preflight diffs against a stale baseline and reports false drift on a just-revalidated spec. Leave the stamp untouched when you only add sections without re-validating existing references. If no stamp exists and the repo is git, add one (see [Planning Commit Stamp](#planning-commit-stamp)).
## Research Before Writing — Required Checklist
Specs are only as good as the research behind them. Before writing the spec body, do all of the following and reference findings in the spec. **Delegate the bulky steps** — locating code (2) and verifying stated behaviour (4) — **to a read-only research subagent** (e.g. Claude Code's `Explore`), which returns a slim brief instead of loading every file into context; the targeted reads stay inline:
1. **Read the issue** (if any) — description, acceptance criteria, all comments.
2. **Locate relevant code** — grep for the primary domain term(s); list the files you'll touch.
3. **Read the project reference docs**: <!--boost:conv path="spec.research_docs" mode="inline"-->none — gather context from the conversation and codebase<!--boost:conv:end-->. If paths are shown, read the ones matching the feature area (architecture, domain glossary, relationship maps, or similar) so terminology + structural references match project canon. If a path points at a missing file, surface it (`boost doctor --check-conventions` catches this at sync time).
4. **Verify any stated existing behavior** — if the user said "X currently does Y", read the code and confirm. If they're wrong, surface the contradiction before writing the spec around it.
5. **Check terminology against the reference docs** — use canonical names. If the user used a different word, flag it in `## Terminology` so the spec doesn't propagate ambiguity.
6. **Read the persona model and any matching stories** — when the project ships durable quality docs (conventionally a persona model at `docs/quality/personas.md` and persona-tagged stories beside it), read the axes and grep the stories for the feature area. An existing story states what should already be true for that surface, so the spec builds on it instead of restating or contradicting it. The axes are the source for the header's `**Personas:**` line, per the rule below.
Skip steps that genuinely don't apply (e.g. step 4 when there's no claim to verify, step 6 when the project ships no persona model or the change has no persona surface), but never skip 2 and 3 when a codebase and reference docs exist.
## Spec Format
Two formats: **multi-phase** (features with multiple steps) and **single-phase** (cleanup, small refactors).
Both end with the same closing sections: **Open Questions**, **Resolved Questions** (when needed), and **Findings**.
### Multi-Phase Format (Features)
```markdown
# {Feature Name}
<!-- spec:planned-at <full-sha> <YYYY-MM-DD> -->
**Personas:** admin `A2` · viewer `V1,V3` <!-- from the project's persona model; omit when it ships none, or when nothing user-facing is touched -->
## Overview
{2-3 sentences: what this does and why it matters.}
## Non-Goals
<!-- What this spec deliberately leaves out, and what would count as failure even when it works. Write "None." only after you checked. -->
- **Out of scope:** {what this spec does not do} — {why, or where it goes instead}
- **Anti-goal:** {an outcome that counts as failure even when every task passes}
## Assumptions
<!-- The Assumptions Audit (below) fills this ledger — one bullet per AI-introduced inference, kept so the user can sign off by skimming this section alone. Carried forward verbatim in Conversion Mode. -->
---
```
An out-of-scope line stops an implementer from "helpfully" building the adjacent feature. An anti-goal names a failure that the tests cannot catch, for example "the new filter works, but the list page gets slower". Take both from the interview when it ran; otherwise derive them from the issue and confirm them in the Assumptions Audit.
After the overview, non-goals, and assumptions ledger, use numbered top-level sections (`## 1. Data Model`, `## 2. API Design`, etc.) for the technical design. Adapt sections to fit the feature.
After the technical sections, include `## Edge Cases` — a compact table the Edge Case Sweep (below) fills in. Each row names a scenario the feature must handle and how the app handles it.
> For a feature that renders UI, the spec's success measure includes **eye-verify** — the change is best confirmed by *seeing it run* in a real browser (per the `frontend-quality` skill), not by type-check/lint alone; the `## Edge Cases` rows then double as the browser testables.
```markdown
## Edge Cases
| Scenario | Handling |
|----------|----------|
| {Setting/flag combination, interacting state, boundary, or permission edge} | {What the app does — name the phase/task and Tests entry that cover it} |
```
The final sections are always:
```markdown
## Implementation
### Phase 1: {Phase Name} (Priority: {HIGH/MEDIUM/LOW})
**ID:** {slug} · **Depends:** none
- [ ] {Task description} — {brief context}
- [ ] {Task description} — {brief context}
- [ ] Tests — {what to test}
### Phase 2: {Phase Name} (Priority: {HIGH/MEDIUM/LOW})
**ID:** {slug} · **Depends:** {id}, {id}
- [ ] {Task description} — {brief context}
- [ ] Tests — {what to test}
---
## STOP Conditions
Stop and report — do not improvise — if any of these proves false during implementation:
1. **{Load-bearing assumption}** — {why it blocks the phase / what to do instead}.
Use "None." when the spec has no load-bearing assumptions.
---
## Open Questions
1. **{Question}** {Context and options to consider.}
---
<!-- ## Resolved Questions
1. **{Original question?}** **Decision:** {What was decided.} **Rationale:** {Why.}
-->
## Findings
<!-- Notes added during implementation. Do not remove this section. -->
```
### Single-Phase Format (Cleanup, Refactors)
For focused changes that don't need multiple phases.
```markdown
# {Change Name}
<!-- spec:planned-at <full-sha> <YYYY-MM-DD> -->
**Personas:** admin `A2` <!-- from the project's persona model; omit when it ships none, or when nothing user-facing is touched -->
## Overview
{2-3 sentences: what this fixes/improves and why.}
## Non-Goals
<!-- Omit when nothing near this change is deliberately left out. -->
- **Out of scope:** {what this change does not do} — {why}
- **Anti-goal:** {an outcome that counts as failure even when every task passes; omit when none}
## Assumptions
<!-- The Assumptions Audit (below) fills this ledger. Omit the section only when the audit surfaced nothing. -->
---
## 1. Current State
{What the code looks like now. Reference specific files and line numbers.}
## 2. Proposed Changes
{What to change and why. Include code snippets for non-obvious decisions.}
## Edge Cases
| Scenario | Handling |
|----------|----------|
| {Scenario, or `None — change has no edge cases`} | {What the app does} |
## Implementation
- [ ] {Task description} — {brief context}
- [ ] {Task description} — {brief context}
- [ ] Tests — {what to test}
---
## STOP Conditions
Stop and report — do not improvise — if any of these proves false during implementation:
1. **{Load-bearing assumption}** — {why it blocks the change / what to do instead}.
Use "None." when the change has no load-bearing assumptions.
---
## Open Questions
1. **{Question}** {Context and options to consider.}
---
<!-- ## Resolved Questions
1. **{Original question?}** **Decision:** {What was decided.} **Rationale:** {Why.}
-->
## Findings
<!-- Notes added during implementation. Do not remove this section. -->
```
## Implementation Section Rules
### Phases
- `### Phase N: {Name} (Priority: HIGH/MEDIUM/LOW)`. Number phases in dependency (topological) order so the list reads top-to-bottom — but the `Depends:` edges, not the numbers, govern execution order.
- Priorities: `HIGH` = must have, `MEDIUM` = should have, `LOW` = nice to have (skipped by default during implementation).
- Keep phases manageable: ~half a day to a day. Split if more than 8-10 tasks.
- **Every phase boundary must earn itself.** A boundary is justified by either (a) a **dependency edge** — this phase needs a prior phase's output — or (b) a **parallel split** — independent work that can run concurrently. Two phases that are merely sequential with neither are arbitrary grouping — merge them. Don't add a boundary just to get a review: `implement-spec` pauses for the user after *every* wave anyway. To force a phase to be reviewed in isolation before the work that follows, give that later work a dependency edge on it, so the phase lands alone in its own wave. This is the antidote to over-phasing: don't manufacture phases, and where work *is* independent, split it so it can run in parallel.
### Phase Dependencies (the DAG)
Every phase carries one metadata line immediately under its heading:
```
**ID:** {slug} · **Depends:** {comma-separated IDs, or none}
```
- **ID** — an immutable, kebab-case slug unique within the spec (e.g. `migration`, `import-parser`). Always depend on IDs, **never on phase numbers**: numbers renumber when phases are reordered and silently corrupt the graph; IDs don't.
- **Depends** — the IDs of every phase that must complete before this one starts. Write `none` to state affirmatively that the phase is independent; don't omit the field — an explicit `none` reads as "considered and independent", a missing line reads as "forgot". Record **forward edges only** — never the inverse (`Blocks:`); one direction, one source of truth.
- **Independence means write-disjoint, not merely logically unrelated.** Two phases may run concurrently only if they also touch **disjoint files**. If two otherwise-independent phases would edit the same file, add a dependency edge to serialise them (or merge them) — concurrent edits to one file collide. Marking a phase `Depends: none` asserts it shares no written file with any other phase that could be ready at the same time.
- **Never depend on a lower-priority phase.** A `HIGH` phase must not `Depends:` on a `MEDIUM`/`LOW` phase, nor `MEDIUM` on `LOW`. Lower-priority phases are skipped by default at implementation time, which would leave the dependent phase permanently unready. If phase B genuinely needs phase A's output, A is at least as important as B — raise A's priority to match.
- Keep the graph **linearizable**: forward edges, IDs not positions, no cross-spec dependencies. If the graph is complex enough to need a diagram to follow, the spec is too big — split it. Don't add a derived "waves"/DAG-diagram block: the per-phase edges are the single source of truth, and a hand-maintained summary just drifts.
- `implement-spec` reads these edges to compute each **wave** — the set of phases whose dependencies are all met — and may implement a wave's independent phases in parallel. A spec where every phase is `Depends: none` is one fully parallel wave; a straight `a → b → c` chain runs exactly like the old sequential model.
### Tasks
- Use `- [ ]` checkboxes. Each task must be concrete and verifiable — not "improve performance" but "add index lookup for wildcard expansion".
- Always include brief context after `—` so the implementer understands purpose without re-reading the full spec.
- Group related tasks, but keep each checkbox independently completable.
- Always include `- [ ] Tests — {what to test}` per phase.
## Closing Sections
**STOP Conditions** — Placed immediately before `## Open Questions`. Lists each *load-bearing* assumption — one whose falsity invalidates a phase's approach (not a cosmetic default) — as an "if X proves false, stop and report" line for the implementer. It is **derived from** the load-bearing entries in `## Assumptions`, not a competing record: the `## Assumptions` ledger stays the single source of truth (one-truth-per-topic), and STOP Conditions is the actionable view of its load-bearing subset. Use "None." when there are no load-bearing assumptions. The Assumptions Audit (below) populates this section.
**Open Questions** — Numbered, bold question + context. Must be resolved before implementing the affected section. Use "None." when there are no open questions.
**Resolved Questions** — Starts as an HTML comment block. Uncomment when the first question is answered. Format: `1. **{Question?}** **Decision:** {Decided.} **Rationale:** {Why.}` — captures *why* to prevent re-litigating decisions.
**Findings** — Always present, even if empty. Implementation notes go here: design decisions, deviations from spec, discovered issues.
## Personas — Named From the Project's Model
Applies only when the project ships a persona model. Fill the header's `**Personas:**` line from it; never invent user types for a spec. The line is the join between a spec and the durable quality model — the same model tags the stories and scopes the QA testables, so a spec that mints its own vocabulary splits one set of people in two.
Pick the axes that **bite** the change, not the whole grid. Read each candidate's feared failure and keep the axis only when that failure is one this change could realistically cause. Where the model has no row for a persona the change genuinely needs, say so in `## Open Questions` — adding a row belongs to the quality process that owns the model, not to writing a spec.
The labels stay in the spec. Treat them like a class name in issue or ticket text: a testable derived from a persona is written as what the tester does and sees, never as its label.
## Edge Case Sweep — Required Before the Assumptions Audit
Once the spec body has shape (technical sections + draft Implementation phases), sweep for edge cases. Edges left implicit ship as production bugs with no test coverage. The sweep is codebase research, not guesswork.
### Step 1 — Research, don't guess
Read the code around the feature and look for:
- **Setting & flag combinations** — list every configuration option and feature flag in scope; for each, ask what the feature does on both sides of the toggle.
- **Interacting states** — concurrent edits, stale client state vs changed server state, partial saves, optimistic UI vs server rejection.
- **Boundary & lifecycle** — empty or missing related record, first / last / zero / max counts, soft-deleted or orphaned parents, retry / timeout / parallel invocation, partial failure.
- **Permission edges** — actor loses access mid-flow, role downgraded, ownership moved.
- **Persona feared failures** — for each persona on the header's `**Personas:**` line, the failure that persona fears. A feared failure is an assertion, so an untested one is an edge case rather than a nicety.
### Step 2 — Record each edge case
Fill the `## Edge Cases` table (`Scenario | Handling`). Every row must be backed by:
- a `- [ ] Tests —` entry in the relevant phase that covers the scenario, and
- an implementation note in the technical section when the handling is non-obvious.
Record the handling even when you had to choose it yourself — the Assumptions Audit grills every AI-chosen handling next. Reserve `## Open Questions` for edge cases whose handling needs a product or UX decision you cannot make.
## Assumptions Audit — Required Before Finalising
After drafting the spec and running the Edge Case Sweep, audit for every AI-introduced inference, then grill the user one at a time. Goal: the spec is sign-off-ready **without the user reading it end-to-end**.
This audit runs even when the requirements came from the `interview` skill — `write-spec` adds technical sections and Implementation phases, and each is a fresh source of AI assumptions on top of whatever the interview captured.
**Step 1 — Scan the spec.** Flag every statement that meets *any* of these:
- **Magic numbers invented from nothing** — char caps, retry counts, backoff sequences, cache TTLs, delays, pagination sizes.
- **Naming and string content invented** — message templates, cache-key prefixes, error wording, queue names.
- **Behavioural defaults the user did not bless** — retry-vs-fail-fast, silent-skip vs surfaced-error, idempotency.
- **Unverified factual claims** — "this column is already indexed", "this endpoint is fast enough".
- **Deviations from the user's original wording** — surface original intent vs final spec even when an earlier answer let you make the swap.
- **Filled-gap behaviours** — user said "store the answers", you assumed JSON column vs separate rows.
- **Convention-added pieces the user did not ask for** — factory/policy/observer added because that's how the codebase usually wires it.
- **Edge-case handling the AI chose** — every `## Edge Cases` row whose handling you decided rather than confirmed from code or an explicit user statement, plus any retry / conflict / timeout / missing-record / parallel-invocation behaviour the table doesn't cover.
- **"Recommended" choices accepted without engaging with the framing.**
- **MVP trade-offs worth re-flagging** — an earlier accepted shortcut that's load-bearing enough to re-confirm now.
- **Derived non-goals and anti-goals** — every `## Non-Goals` line taken from the issue rather than stated by the user.
- **Unquantified quality words** — "fast", "robust", "intuitive", "scalable", "secure", "user-friendly", or "minimal" with no number or concrete behaviour behind it. Propose the number or behaviour as the assumption ("Fast means under 2 seconds for 1,000 rows"). A word that no test can check is not a requirement.
**Step 2 — Grill one assumption at a time** via `AskUserQuestion`: the assumption in plain language, "Correct as stated" first, 1-2 concrete alternatives, and `Research better` last (`AskUserQuestion` appends its own free-text "Other", so don't add a manual escape-hatch option; `Research better` is not one, because it asks for a defined action). When the user picks `Research better`, research the assumption deeper (code, tests, git history, project docs), then ask again with the options the new evidence supports, and say in one line what the research found. Never batch.
**Step 3 — Record outcomes.** Capture each into a `## Assumptions` section near the top of the spec — the complete audit ledger, one bullet per scanned item regardless of outcome. The Cold-Reader Check (below) adds its minor decisions here too, marked as the assistant's call. This is the contract that lets the user sign off by skimming `## Assumptions` alone. Promote anything that became a real decision into `## Resolved Questions`; route anything still undecided to `## Open Questions`.
**Step 4 — Derive STOP Conditions.** Scan the confirmed ledger for *load-bearing* assumptions — those whose falsity would invalidate a phase's approach (not cosmetic defaults like a message string or a TTL). Restate each as an "if X proves false, stop and report" line in the `## STOP Conditions` section. This gives the implementer an in-flight contract without duplicating the ledger — STOP Conditions is the actionable view of the load-bearing subset, while `## Assumptions` remains the source of truth. Use "None." when nothing is load-bearing.
## Cold-Reader Check — Required Before Finalising
After the Assumptions Audit, test the spec on a reader who has none of the conversation. The author cannot see the gaps that the conversation filled in, and the implementer will not have that conversation.
Run it for every multi-phase spec. For a single-phase spec, run it when the change alters behaviour; skip it for a pure restructure or cleanup.
**Step 1 — Dispatch a fresh read-only subagent** with only the spec path and this brief. Use an agent type that reads whole files (for example Claude Code's `general-purpose`), not one that skims excerpts, and never a fork that inherits this conversation:
> Read this spec and the code it references. You will implement it with no other context. List the questions you would have to answer before or during the work, where a wrong guess would change the result. Rank them, most damaging guess first. Do not answer them, and do not list style nits.
Do not pass the conversation, the interview notes, or your own summary of the spec. When the agent cannot dispatch a subagent, re-read the spec top to bottom as the implementer, and say in the summary that the check ran without a fresh reader.
**Step 2 — Sort each question into one pile:**
- **Already answered** — the spec answers it, but the reader missed it. Make the answer easier to find (move it, or link it from the phase that needs it). Do not add a second copy.
- **Minor** — a wrong guess is cheap to undo. Decide it yourself, write the decision into the spec, and add it to `## Assumptions` marked as the assistant's call, so the user can still overrule it by skimming the ledger.
- **The user's** — a product, UX, or business decision. Ask it in the same format as the Assumptions Audit, Step 2, one at a time.
**Step 3 — Fold the answers back in.** Update the affected sections, `## Assumptions`, and `## STOP Conditions` when an answer is load-bearing. A question that stays unresolved goes to `## Open Questions`. Run the check once, unless the answers changed the spec's approach; wording changes never trigger a second run.
## Writing Guidelines
- **Existing code**: Reference specific files and line numbers when extending or modifying code.
- **Code snippets**: Show code for non-obvious implementation choices, but don't write every line.
- **Conventions**: Follow the project's existing patterns (check sibling files for patterns).
- **Cross-version**: Note any runtime or framework version-specific considerations.
Writes structured specification files designed for phased implementation with built-in progress tracking. Specs produced by this skill are directly compatible with the implement-spec skill.
The spec filename pattern is specs/{slug}.md. It accepts these placeholders, which you substitute at spec-creation time:
{issue_key} — full Jira key (e.g. HPB-1234). Resolved from the user's input, or from the current branch name if it matches one of the configured branch patterns:
<!--boost:conv path="branches.patterns" mode="yaml"-->none — no branch-pattern issue-key detection<!--boost:conv:end-->
The configured Jira project key is (none — the spec is treated as not-issue-backed). If no issue is involved (or no project key is configured), the placeholder resolves empty.
{slug} — URL-safe kebab-case from the feature name. Lowercase, alphanumerics + dashes, no consecutive or leading/trailing dashes, recommended cap ~60 chars truncated on word boundary.{date} — ISO date (YYYY-MM-DD).Empty-placeholder rule: if a placeholder resolves empty, the placeholder AND any single adjacent dash are omitted. Example: specs/{issue_key}-{slug}.md with no issue + feature name "Set up dark mode" → specs/set-up-dark-mode.md (not specs/-set-up-dark-mode.md).
implement-spec skill)Before writing a single line of spec, check for fuzziness. Judge fuzziness only after loading all available context — read the linked issue and run the Research checklist below first. An issue-backed ask like "write a spec for the export feature" is one sentence on its own, but if the linked issue carries a concrete description + acceptance criteria, intent is sharp and the gate does not fire — converting an issue into a spec is a documented use of this skill. Bounce to interview only when ambiguity remains after the context is in hand.
If any of these are true with all context loaded, bounce the user to the interview skill first — do not power through:
A short bounce message:
"I'm seeing ambiguity around [X, Y]. Want me to run the
interviewskill first to grill those out, then write the spec from solid answers?"
If the user insists on writing immediately, capture each unresolved item in ## Open Questions rather than inventing an answer. A spec built on guessed requirements ships those guesses as bugs.
Resolve the spec file path via the filename pattern above. Apply the placeholder-substitution rules above to produce the final path.
A project may hold specs in several directories — specs/. When it does, two rules apply and they pull in opposite directions on purpose:
spec.filename_pattern. The list is ordered, and its first entry is the default write target when the project has not configured a pattern of its own.If the filename_pattern prefix is not in the configured list, the configuration is inconsistent — there is no defined way to re-root a pattern like docs/specs/{slug}.md under a different directory. Stop and ask for the correct location; do not silently write the pattern's filename into the first listed directory. boost doctor --check-conventions reports the same configuration as an error.
Subdirectories for related specs are fine — if the filename pattern doesn't model the subdir convention, place spec files manually under the resolved parent dir:
specs/
├── HPB-1234-wildcard-performance-optimization.md
├── HPB-1267-polymorphic-field-support.md
└── cleanup/
└── deprecated-method-removal.md
When the spec is written in a git repository, stamp the commit it was planned against immediately under the spec's # Title:
<!-- spec:planned-at <full-sha> <YYYY-MM-DD> -->
This lets implement-spec detect when cited code has drifted before it builds against stale file:line references.
git rev-parse HEAD (full SHA, not short — matches the repo's verified-sha convention). Date — ISO YYYY-MM-DD.file:line references come from the working tree, but HEAD names only the last commit. If git status --porcelain is non-empty, append a +uncommitted marker so the baseline is flagged approximate: <!-- spec:planned-at <sha> <date> +uncommitted -->. Prefer stamping against a clean tree when practical.Before writing from a fresh template, check whether a spec for this work already exists at (or under) the resolved path — and under every configured spec directory, not only the write target — an earlier write-spec run, or a requirements doc the interview skill's output was saved into. If one exists, read it and classify before doing anything:
| What you find | Action |
|---|---|
Already implementation-ready — has a phased ## Implementation section (or ### Phase N: task checkboxes) |
Stop. Don't re-wrap. Ask the user what they want changed (add tasks? a new phase? edit existing?). |
| Requirements-shaped — has problem/overview/edge-cases/acceptance content but no phased Implementation | Convert in place (below). |
| Loose notes / neither | Treat as input; fall through to the from-scratch flow. |
| More than one candidate file for the same work | Stop and ask which is authoritative — never pick silently. |
Convert in place rather than rewriting from a fresh template — rewriting silently drops captured decisions. Carry forward verbatim, unless a later step explicitly supersedes it: ## Overview, ## Non-Goals, ## Assumptions (the user's skim-only sign-off ledger — never drop or wholesale-rewrite it), ## Terminology, ## STOP Conditions, ## Open Questions, ## Resolved Questions, ## Findings, the issue link, the **Personas:** line, problem statement, edge-cases table, and acceptance criteria. Then add the technical sections and Implementation phases. When a later audit corrects an inherited ## Assumptions or resolves an inherited ## Open Questions entry, update it in place (and log the decision in ## Resolved Questions) rather than leaving two contradictory bullets — the skim ledger must show exactly one truth per topic.
Stamp on conversion: if the existing spec carries a spec:planned-at stamp, refresh it to the current HEAD when the conversion materially re-bases the spec's file:line references on today's code — otherwise the drift preflight diffs against a stale baseline and reports false drift on a just-revalidated spec. Leave the stamp untouched when you only add sections without re-validating existing references. If no stamp exists and the repo is git, add one (see Planning Commit Stamp).
Specs are only as good as the research behind them. Before writing the spec body, do all of the following and reference findings in the spec. Delegate the bulky steps — locating code (2) and verifying stated behaviour (4) — to a read-only research subagent (e.g. Claude Code's Explore), which returns a slim brief instead of loading every file into context; the targeted reads stay inline:
boost doctor --check-conventions catches this at sync time).## Terminology so the spec doesn't propagate ambiguity.docs/quality/personas.md and persona-tagged stories beside it), read the axes and grep the stories for the feature area. An existing story states what should already be true for that surface, so the spec builds on it instead of restating or contradicting it. The axes are the source for the header's **Personas:** line, per the rule below.Skip steps that genuinely don't apply (e.g. step 4 when there's no claim to verify, step 6 when the project ships no persona model or the change has no persona surface), but never skip 2 and 3 when a codebase and reference docs exist.
Two formats: multi-phase (features with multiple steps) and single-phase (cleanup, small refactors).
Both end with the same closing sections: Open Questions, Resolved Questions (when needed), and Findings.
# {Feature Name}
<!-- spec:planned-at <full-sha> <YYYY-MM-DD> -->
**Personas:** admin `A2` · viewer `V1,V3` <!-- from the project's persona model; omit when it ships none, or when nothing user-facing is touched -->
## Overview
{2-3 sentences: what this does and why it matters.}
## Non-Goals
<!-- What this spec deliberately leaves out, and what would count as failure even when it works. Write "None." only after you checked. -->
- **Out of scope:** {what this spec does not do} — {why, or where it goes instead}
- **Anti-goal:** {an outcome that counts as failure even when every task passes}
## Assumptions
<!-- The Assumptions Audit (below) fills this ledger — one bullet per AI-introduced inference, kept so the user can sign off by skimming this section alone. Carried forward verbatim in Conversion Mode. -->
---
An out-of-scope line stops an implementer from "helpfully" building the adjacent feature. An anti-goal names a failure that the tests cannot catch, for example "the new filter works, but the list page gets slower". Take both from the interview when it ran; otherwise derive them from the issue and confirm them in the Assumptions Audit.
After the overview, non-goals, and assumptions ledger, use numbered top-level sections (## 1. Data Model, ## 2. API Design, etc.) for the technical design. Adapt sections to fit the feature.
After the technical sections, include ## Edge Cases — a compact table the Edge Case Sweep (below) fills in. Each row names a scenario the feature must handle and how the app handles it.
For a feature that renders UI, the spec's success measure includes eye-verify — the change is best confirmed by seeing it run in a real browser (per the
frontend-qualityskill), not by type-check/lint alone; the## Edge Casesrows then double as the browser testables.
## Edge Cases
| Scenario | Handling |
|----------|----------|
| {Setting/flag combination, interacting state, boundary, or permission edge} | {What the app does — name the phase/task and Tests entry that cover it} |
The final sections are always:
## Implementation
### Phase 1: {Phase Name} (Priority: {HIGH/MEDIUM/LOW})
**ID:** {slug} · **Depends:** none
- [ ] {Task description} — {brief context}
- [ ] {Task description} — {brief context}
- [ ] Tests — {what to test}
### Phase 2: {Phase Name} (Priority: {HIGH/MEDIUM/LOW})
**ID:** {slug} · **Depends:** {id}, {id}
- [ ] {Task description} — {brief context}
- [ ] Tests — {what to test}
---
## STOP Conditions
Stop and report — do not improvise — if any of these proves false during implementation:
1. **{Load-bearing assumption}** — {why it blocks the phase / what to do instead}.
Use "None." when the spec has no load-bearing assumptions.
---
## Open Questions
1. **{Question}** {Context and options to consider.}
---
<!-- ## Resolved Questions
1. **{Original question?}** **Decision:** {What was decided.} **Rationale:** {Why.}
-->
## Findings
<!-- Notes added during implementation. Do not remove this section. -->
For focused changes that don't need multiple phases.
# {Change Name}
<!-- spec:planned-at <full-sha> <YYYY-MM-DD> -->
**Personas:** admin `A2` <!-- from the project's persona model; omit when it ships none, or when nothing user-facing is touched -->
## Overview
{2-3 sentences: what this fixes/improves and why.}
## Non-Goals
<!-- Omit when nothing near this change is deliberately left out. -->
- **Out of scope:** {what this change does not do} — {why}
- **Anti-goal:** {an outcome that counts as failure even when every task passes; omit when none}
## Assumptions
<!-- The Assumptions Audit (below) fills this ledger. Omit the section only when the audit surfaced nothing. -->
---
## 1. Current State
{What the code looks like now. Reference specific files and line numbers.}
## 2. Proposed Changes
{What to change and why. Include code snippets for non-obvious decisions.}
## Edge Cases
| Scenario | Handling |
|----------|----------|
| {Scenario, or `None — change has no edge cases`} | {What the app does} |
## Implementation
- [ ] {Task description} — {brief context}
- [ ] {Task description} — {brief context}
- [ ] Tests — {what to test}
---
## STOP Conditions
Stop and report — do not improvise — if any of these proves false during implementation:
1. **{Load-bearing assumption}** — {why it blocks the change / what to do instead}.
Use "None." when the change has no load-bearing assumptions.
---
## Open Questions
1. **{Question}** {Context and options to consider.}
---
<!-- ## Resolved Questions
1. **{Original question?}** **Decision:** {What was decided.} **Rationale:** {Why.}
-->
## Findings
<!-- Notes added during implementation. Do not remove this section. -->
### Phase N: {Name} (Priority: HIGH/MEDIUM/LOW). Number phases in dependency (topological) order so the list reads top-to-bottom — but the Depends: edges, not the numbers, govern execution order.HIGH = must have, MEDIUM = should have, LOW = nice to have (skipped by default during implementation).implement-spec pauses for the user after every wave anyway. To force a phase to be reviewed in isolation before the work that follows, give that later work a dependency edge on it, so the phase lands alone in its own wave. This is the antidote to over-phasing: don't manufacture phases, and where work is independent, split it so it can run in parallel.Every phase carries one metadata line immediately under its heading:
**ID:** {slug} · **Depends:** {comma-separated IDs, or none}
migration, import-parser). Always depend on IDs, never on phase numbers: numbers renumber when phases are reordered and silently corrupt the graph; IDs don't.none to state affirmatively that the phase is independent; don't omit the field — an explicit none reads as "considered and independent", a missing line reads as "forgot". Record forward edges only — never the inverse (Blocks:); one direction, one source of truth.Depends: none asserts it shares no written file with any other phase that could be ready at the same time.HIGH phase must not Depends: on a MEDIUM/LOW phase, nor MEDIUM on LOW. Lower-priority phases are skipped by default at implementation time, which would leave the dependent phase permanently unready. If phase B genuinely needs phase A's output, A is at least as important as B — raise A's priority to match.implement-spec reads these edges to compute each wave — the set of phases whose dependencies are all met — and may implement a wave's independent phases in parallel. A spec where every phase is Depends: none is one fully parallel wave; a straight a → b → c chain runs exactly like the old sequential model.- [ ] checkboxes. Each task must be concrete and verifiable — not "improve performance" but "add index lookup for wildcard expansion".— so the implementer understands purpose without re-reading the full spec.- [ ] Tests — {what to test} per phase.STOP Conditions — Placed immediately before ## Open Questions. Lists each load-bearing assumption — one whose falsity invalidates a phase's approach (not a cosmetic default) — as an "if X proves false, stop and report" line for the implementer. It is derived from the load-bearing entries in ## Assumptions, not a competing record: the ## Assumptions ledger stays the single source of truth (one-truth-per-topic), and STOP Conditions is the actionable view of its load-bearing subset. Use "None." when there are no load-bearing assumptions. The Assumptions Audit (below) populates this section.
Open Questions — Numbered, bold question + context. Must be resolved before implementing the affected section. Use "None." when there are no open questions.
Resolved Questions — Starts as an HTML comment block. Uncomment when the first question is answered. Format: 1. **{Question?}** **Decision:** {Decided.} **Rationale:** {Why.} — captures why to prevent re-litigating decisions.
Findings — Always present, even if empty. Implementation notes go here: design decisions, deviations from spec, discovered issues.
Applies only when the project ships a persona model. Fill the header's **Personas:** line from it; never invent user types for a spec. The line is the join between a spec and the durable quality model — the same model tags the stories and scopes the QA testables, so a spec that mints its own vocabulary splits one set of people in two.
Pick the axes that bite the change, not the whole grid. Read each candidate's feared failure and keep the axis only when that failure is one this change could realistically cause. Where the model has no row for a persona the change genuinely needs, say so in ## Open Questions — adding a row belongs to the quality process that owns the model, not to writing a spec.
The labels stay in the spec. Treat them like a class name in issue or ticket text: a testable derived from a persona is written as what the tester does and sees, never as its label.
Once the spec body has shape (technical sections + draft Implementation phases), sweep for edge cases. Edges left implicit ship as production bugs with no test coverage. The sweep is codebase research, not guesswork.
Read the code around the feature and look for:
**Personas:** line, the failure that persona fears. A feared failure is an assertion, so an untested one is an edge case rather than a nicety.Fill the ## Edge Cases table (Scenario | Handling). Every row must be backed by:
- [ ] Tests — entry in the relevant phase that covers the scenario, andRecord the handling even when you had to choose it yourself — the Assumptions Audit grills every AI-chosen handling next. Reserve ## Open Questions for edge cases whose handling needs a product or UX decision you cannot make.
After drafting the spec and running the Edge Case Sweep, audit for every AI-introduced inference, then grill the user one at a time. Goal: the spec is sign-off-ready without the user reading it end-to-end.
This audit runs even when the requirements came from the interview skill — write-spec adds technical sections and Implementation phases, and each is a fresh source of AI assumptions on top of whatever the interview captured.
Step 1 — Scan the spec. Flag every statement that meets any of these:
## Edge Cases row whose handling you decided rather than confirmed from code or an explicit user statement, plus any retry / conflict / timeout / missing-record / parallel-invocation behaviour the table doesn't cover.## Non-Goals line taken from the issue rather than stated by the user.Step 2 — Grill one assumption at a time via AskUserQuestion: the assumption in plain language, "Correct as stated" first, 1-2 concrete alternatives, and Research better last (AskUserQuestion appends its own free-text "Other", so don't add a manual escape-hatch option; Research better is not one, because it asks for a defined action). When the user picks Research better, research the assumption deeper (code, tests, git history, project docs), then ask again with the options the new evidence supports, and say in one line what the research found. Never batch.
Step 3 — Record outcomes. Capture each into a ## Assumptions section near the top of the spec — the complete audit ledger, one bullet per scanned item regardless of outcome. The Cold-Reader Check (below) adds its minor decisions here too, marked as the assistant's call. This is the contract that lets the user sign off by skimming ## Assumptions alone. Promote anything that became a real decision into ## Resolved Questions; route anything still undecided to ## Open Questions.
Step 4 — Derive STOP Conditions. Scan the confirmed ledger for load-bearing assumptions — those whose falsity would invalidate a phase's approach (not cosmetic defaults like a message string or a TTL). Restate each as an "if X proves false, stop and report" line in the ## STOP Conditions section. This gives the implementer an in-flight contract without duplicating the ledger — STOP Conditions is the actionable view of the load-bearing subset, while ## Assumptions remains the source of truth. Use "None." when nothing is load-bearing.
After the Assumptions Audit, test the spec on a reader who has none of the conversation. The author cannot see the gaps that the conversation filled in, and the implementer will not have that conversation.
Run it for every multi-phase spec. For a single-phase spec, run it when the change alters behaviour; skip it for a pure restructure or cleanup.
Step 1 — Dispatch a fresh read-only subagent with only the spec path and this brief. Use an agent type that reads whole files (for example Claude Code's general-purpose), not one that skims excerpts, and never a fork that inherits this conversation:
Read this spec and the code it references. You will implement it with no other context. List the questions you would have to answer before or during the work, where a wrong guess would change the result. Rank them, most damaging guess first. Do not answer them, and do not list style nits.
Do not pass the conversation, the interview notes, or your own summary of the spec. When the agent cannot dispatch a subagent, re-read the spec top to bottom as the implementer, and say in the summary that the check ran without a fresh reader.
Step 2 — Sort each question into one pile:
## Assumptions marked as the assistant's call, so the user can still overrule it by skimming the ledger.Step 3 — Fold the answers back in. Update the affected sections, ## Assumptions, and ## STOP Conditions when an answer is load-bearing. A question that stays unresolved goes to ## Open Questions. Run the check once, unless the answers changed the spec's approach; wording changes never trigger a second run.
How can I help you explore Laravel packages today?