Product Interview
Protocol
Input
- Feature: a feature name/description or an existing
meta/specs/NNN-slug/path.- Match first: before writing, match the feature against existing folder slugs in
meta/specs/. Never mint a new NNN before this check. - Exactly one match: use that folder; Step 5 updates its spec in place.
- Several plausible matches: list them via
AskUserQuestion; never glob-and-pick. - No match: resolve the next number plus a slug from the feature name as the new
NNN-slug. - Stable home: resolve
NNN-slughere so a mid-interviewgenerate-mockupscall has a destination. - Lazy creation: whichever writes first creates the folder — mockups at Step 2 or spec.md at Step 5.
- Match first: before writing, match the feature against existing folder slugs in
Resumability — check before Step 0
On re-entry to an existing feature, read what exists on disk first; the spec encodes where a prior session stopped — resume there rather than re-interviewing settled ground:
- No folder / no
spec.md(per the Input check) → nothing written; run Steps 0–1, start fresh. spec.mdexists but core sections are missing or placeholder → an interrupted prior session; re-read what's there and rejoin the interview (Step 2) at the gaps — resume from the file, don't reconstruct from memory.- Spec complete but the Gate anchor greps hit (
Status: opendecisions / clarification markers) → a parked investigation, not damage; resume Step 2 at the open branches or markers only. ### Files touchedpresent and plan.md'sBase SHA:is set → keep the outline frozen; handle the change in the activeexecute-plansession.### Files touchedpresent and plan.md is absent or itsBase SHA:is unset →tech-designalready designed on this WHAT; confirm the reopen, revise any affected locked decision per the template's Revising rule, and apply Step 5's header flip.
Step 0 still runs on every re-entry — the lens loads per session.
Step 0 — Load the job lens
Invoke the jtbd skill via the Skill tool. Use its lens — the job-story format and job-fit judgment — to frame every product/scope question; skip its Steps and Job-frame output.
Step 1 — Read context first
Before asking anything, silently explore:
- Root
CLAUDE.mdand any product or design documents it says to read for this task - Existing UX in the affected area (screens, flows, components)
- Related features and any prior spec this feature builds on
- Use shipped specs as decision history; verify current behavior in code
- A light possibility scan: what the target surface/platform allows at all, and what data the codebase already carries — possibility only, never how-to-build, never current code as a ceiling (see the Codebase is context rule)
Don't ask what current code or the active spec already answers. Treat project conventions as constraints to follow, not decisions to re-litigate.
Step 2 — Interview: product, then UX
Resolve the product layer before the UX layer as the default, but treat them as one decision tree: when a UX branch blocks or would overturn a product choice, resolve that branch first (the dependency rule below governs). A UX answer that overturns an already-locked product choice follows the reversal rule below. Surface what the user is assuming, not just what they request. When the Step-1 read surfaces a load-bearing question the user didn't ask — an existing feature this overlaps, a UX pattern to reuse or deliberately diverge from — raise it, saying it came from the codebase; route technical finds to Open Questions tagged (for tech-design). Draft the job-story as the product layer resolves — it anchors UX option scoring and the spec's Background. A slot you can't fill is a question to ask, not a blank to guess.
Manage the decision space as follows:
- Start the tree: once you can name two or more branches, show a compact nested list and ask, "Here's what I think we need to figure out — does this match?"
- Format each node:
- [branch] — [resolved: choice] | [open] | [deferred: why] | [blocked by branch], followed by[hard|ask|ours]when applicable. - Order: list blocking branches first and resolve one branch at a time.
- Maintain: update the tree inline as branches split, collapse, or resolve.
- Finish: continue until every branch is resolved or explicitly deferred.
- Flat interview: for one or two flat questions, skip the tree and ask directly.
- Unknown branches: ask open-ended questions until you can name the branches; aim to show the tree within 2–3 rounds.
- Long interview: summarize current clarity and offer to continue or proceed.
Run each branch explore → stretch → verify (in order) — name the ideal before checking what's real, so a constraint never caps a choice the user hasn't reached for yet.
- Explore / stretch: for a non-trivial or ambiguous UX branch, name and score 2+ options by job-fit before locking; an obvious single-UX branch skips this. Escalate to parallel subagents (and any available design skills) only for high-stakes or high-ambiguity UX.
- Sketch, then gate visual review: for every feature that changes a user-facing screen, component, interaction, or visual state, sketch each UI branch or option in ASCII, then ask once via
AskUserQuestionbefore Step 2 ends: "How should we validate this UI before locking the UX?" The user chooses; recommend one option and state why:- Keep ASCII — use the sketches without a rendered artifact. Recommend this when one obvious direction follows an established pattern and the sketches make its behavior clear.
- Preview rendered mockups — render the one preferred direction across the relevant screens or states. Recommend this when the direction is settled but a new or complex UI is easier to judge visually.
- Compare rendered directions — render 2+ viable directions for one unresolved visual choice side by side, then ask the user to pick. Recommend this when the directions have meaningful layout, hierarchy, or interaction tradeoffs.
- Run mockups: on Preview or Compare, invoke the
generate-mockupsskill via the Skill tool with the matching PREVIEW or COMPARE intent, the resolvedmeta/specs/NNN-slug/mockups/path, and the design context you know. It derives High or Approximate fidelity after grounding; don't promise High fidelity in the question. - Record the result: at Step 5, record
ASCII (user-approved), or the reviewed mockup link plus the approved preview or comparison result, in the UX section. - Skip: omit this checkpoint only when the feature changes no user-facing UI.
- Verify (just-in-time):
- Trigger: when an unchecked claim could invalidate the agreed scope if false, dispatch a subagent to verify it against the source of truth (code, docs, or SDK). A claim that would only redirect the branch does not qualify.
- Scope: verify only whether the product surface can support the agreed behavior.
- Route: put implementation and capacity questions in Open Questions tagged
(for tech-design).
- Hit a wall? Tag it.
[hard]= outside our control (external SDK / platform) → law; stamp its assumption (e.g. "given the SDK has no programmatic redirect") so it reopens if the dependency changes.[ask]= cross-team, movable by request.[ours]= our code, we change freely.[hard]is a real constraint;[ask]/[ours]are guidance — if either forces a worse UX, challenge it (or flag the ask) first, then record the user's final call and move on. - Don't lock a UI pattern whose surface feasibility is unverified — verify possibility first, or lock it "pending feasibility."
Completeness lens (verify nothing is missing — these are a lens, not a required structure):
- Product / scope — the job to be done, in the jtbd job-story format (loaded at Step 0); who it's for; what's in, what's out; success criteria.
- UX and behavior — happy path, error states, empty states, user flows, the surfaces/screens touched.
- Acceptance criteria — observable, testable conditions for "done."
- Constraints — compatibility, platform limits, dependencies, boundaries.
- Clarity — resolve remaining ambiguity or contradictions.
When a load-bearing assumption surfaces, test it once ("Does this constraint actually exist?" / "What's the simplest version still worth shipping?"). Challenge the framing, not the person. If a stated requirement seems materially wrong (product value, UX harm), say so with reasoning; record the user's final call, not yours.
If a later answer or feasibility finding overturns a choice the user already locked this session, re-confirm via AskUserQuestion and record the overturned choice in the surviving decision's Rejected field, citing what killed it — the why must reach the record.
Record each resolved choice as a D-NNN-XX decision block (see the Spec.md template — it defines the id format) with Status, Chosen, Rejected, Rationale. Classify anything unresolved by exactly one rule:
- A framed-but-unresolved decision → a
D-NNN-XXblock withStatus: open. - A blocking unknown inside any section → an inline
[NEEDS CLARIFICATION: ...]marker. - Non-blocking notes → Open Questions (these do NOT block the gate).
Deferring a decision is itself a decision: propose it, the user confirms, and it lands as a locked D-NNN-XX whose Chosen is the deferral (alternatives marked deferred in Rejected) — a confirmed deferral never blocks the gate.
The lock gate greps exactly two forms — see Gate anchors below. Anything blocking must carry one of them, or it will not block.
Step 3 — Pre-confirm verification gate
Once Step 2's branches are resolved or deferred, and before the Step-4 summary, run this required gate over the resolved UX elements that are load-bearing (an AC, another decision, or user-facing behavior rests on them) — two passes, two grains. The gate always runs; on a trivial feature it may be near-empty (nothing load-bearing beyond the Existing-patterns check) — record that and move on.
Pass 1 — per element. For each load-bearing element:
- States — error / empty / edge: what happens when data is missing, the call fails, or a value hits a boundary?
- Expectation-Fidelity — does what the element implies (its label, control, default, or placement) match what actually happens? Flag every mismatch.
- Surface-Obligations — any obligation the surface itself imposes (e.g. accessibility/compliance on a regulated surface), where it demands it.
Pass 2 — the assembled whole. Do the cleared elements coexist and work together on the real surface(s)? Check combination-possibility + cross-element interference, and re-verify the load-bearing facts the interview leaned on. Surface/platform possibility is validated here — launch subagents in parallel, one per applicable area below (Existing-patterns always runs), each carrying the flows it must validate:
| Area | When relevant | Verify |
|---|---|---|
| UI components | Feature uses specific components/libraries | Component exists, supports the interaction, composition constraints |
| External data/APIs | UX depends on external data | Data is available, fields exist |
| Platform constraints | Feature rides a platform (Shopify, extension, etc.) | The UX is permitted by the platform |
| Existing patterns | Always | The affected area's existing UX patterns and conventions |
Scope: possibility, not capacity. Constraint depth — rate limits, quotas, throughput, batch caps — is tech-design's constraint recon (its Step 2B); don't duplicate it here.
Handle verification results in order:
- Receive: each subagent returns
exists(yes/no), capabilities, gotchas, andblocks: <the decision or flow it invalidates> | none.noneis valid. - Record: put each load-bearing possibility verdict in the relevant
D-NNN-XXRationale or the Constraints section, not only in conversation. - Resolve: feed every Pass-1 miss or Pass-2
blockshit back into the tree. Re-explore it with the user; never narrow the UX silently. - After one follow-up round: proceed with each resolved finding. Classify each remaining finding by Step 2's rule as an open decision, clarification marker, or Open Question, then move on.
Step 4 — Pre-write summary
Before writing, summarize the contract in chat in this exact shape — enough to spot a wrong turn without reproducing every AC:
**Contract summary (pre-write):**
- Scope: [one line]
- Decisions: D-NNN-XX [title] → [Chosen] (one line per decision)
- Visual review: [not applicable — no UI change | ASCII (user-approved) | mockups: path — review outcome]
- Constraints: [one line each]
- ACs: [n] ([x] code-gated, [y] human-gated)
- Step-3 gate: [clean | each finding and how it resolved]
**Assumptions I'm carrying (never discussed):**
- [assumption] — [what rests on it]
(Write None — everything load-bearing was discussed when the assumptions list is empty.) Then use AskUserQuestion to collect the choice: "Write the draft" / "Adjust first" / "Find gaps first". Recommended: write the draft. The full verbatim contract — the numbered AC list with gating tags and every D-NNN-XX block — belongs in the file, not chat: Step 5 writes it as Status: Draft for the user to review. Reviewers verify diffs against that AC text, so it must be exact in the file.
On Find gaps first — opt-in, at most once, for a complex feature or when you lack the domain depth to spot missing cases — invoke the find-gaps skill over the assembled contract. Absence-hunting only: its lens is what the contract never mentions — missing scope, AC-coverage holes; never re-check verdicts the Step-3 gate settled (checkers verify what's written; find-gaps hunts what's not). Product/UX gaps only — not a technical-gap hunt (tech-design's job); fence every lens to the WHAT layer and send technical gaps to Open Questions tagged (for tech-design). Applied gaps re-enter Step 2; a new flow on an external surface re-runs the Step-3 gate on the delta. Then re-summarize and re-ask without the Find-gaps option — it runs at most once.
Step 5 — Write / update the spec
Write the spec in this order:
- Path: use
meta/specs/NNN-<topic-slug>/spec.mdwith theNNN-slugresolved at Input. Create the folder unless a Step-2 mockup already created it. - Existing spec: if Input resolved an existing spec, update it in place. Append or modify sections and revise decisions per the template's Revising rule.
- Counters: continue both counters. The next
D-NNN-XXuses the highest existingXXacross product and technical decisions plus 1; new ACs continue their counter likewise. - Reopen: before plan.md's
Base SHA:is set, a decision or AC edit on a spec with### Files touchedresets the header toStatus: Draft. The existing outline was verified against the old WHAT, and the Draft header routestech-designthrough a scoped redesign. - Handoff: tell the user the path and ask them to review the file. The spec remains
Status: Draftand uncommitted; Step 6 owns revisions and commit.
This skill writes the WHAT sections; tech-design later appends technical Decisions + the Structure Outline (and appends to Constraints / Accepted risks what its recon proves); execute-plan appends the Completion record at ship. For the full file shape, see the Spec.md template at the end of this file.
Step 6 — Review, commit, route
Stop here once every product/UX branch is resolved or deferred and the spec is written. Step 5 sent the user to read the file — this step turns that review into approval, then handles commit and routing as two tightly-coupled AskUserQuestion rounds.
Q1 — Draft look right? If so, commit?
- Ask: offer "Commit now" (recommended) / "Skip commit for now" / "Adjust the draft first".
- Approve: either Commit or Skip approves the draft; proceed to Q2.
- Adjust: edit the Draft in place and re-ask Q1. If the edit overturns a locked decision or changes an AC on a spec with a populated outline, apply Step 5's header-flip rule.
- Commit: stage only spec.md; the commit is the durable trace of approval:
git add meta/specs/NNN-slug/spec.md && git commit -m "spec(NNN-slug): discovery — product/UX decisions + ACs"
- Folder guard: use the slug resolved at Input. If
git status --porcelain meta/specs/NNN-slug/shows another changed file, leave it unstaged and tell the user. - Skip commit: leave spec.md uncommitted and say so.
tech-designStep 6 will stage it; re-offer the commit at every session boundary before then.
Q2 — Where next?
tech-design— default: the WHAT is locked and the feature needs implementation decisions before sequencing.grill-me— ifStatus: opendecisions or clarification markers remain, or a load-bearing assumption wasn't pressure-tested.write-plandirectly — only for a trivial change with one obvious implementation.
The WHAT must be locked (both Gate anchor greps clean) before tech-design will proceed.
Rules
- One question per round. Tightly coupled follow-ups are fine; shotgunning unrelated questions is not. Presenting/updating the tree counts as part of the round.
- Always use
AskUserQuestionfor questions with distinct choices — with your recommendation and why. Plain text only for genuinely open-ended questions. - Product + UX only. Technical approach, data shapes, and file layout are
tech-design's job — route them to Open Questions tagged(for tech-design)and move on — tech-design reads them at its discovery step. - Codebase is context, not constraint. Existing code shows what IS, not what MUST BE; the user may intentionally diverge. A wall is law only when it's outside our control (external SDK / platform) — tag
[hard], stamp its assumption; anything we or a teammate can change ([ours]our code,[ask]cross-team) is guidance — challenge it before it narrows the vision. - Proportional effort — load-bearing only. Spend a subagent, verification, or UX-exploration round only where a decision rests on the answer; skip passing mentions and obvious single-UX branches. Match effort to stakes.
- Anchor questions in what you read. When a source raises a question, name its actual file or symbol and the claim that needs confirmation.
- Play back concrete scenarios, not abstract questions. Confirm behavior by walking one specific case in the shape
[trigger]: [what happens] — right?("Save fails offline: the draft stays and a retry shows — right?") — a wrong detail draws the correction an abstract question won't. - Keep project conventions out of the spec. Loaded project instructions own them; record a
D-NNN-XXonly for a feature-specific choice that is costly to reverse. - The spec is the feature's build contract + record — it settles at ship; post-ship product/UX evolution belongs to future specs and durable docs, not retroactive edits here.
- Only spec.md and plan.md mint ids. spec.md mints
D-NNN-XX/AC-NNN-XX; plan.md mintsF-NNN-XX(execute-plan's job; format in write-plan's Plan anchors). All other artifacts — interview notes, triage/backlog files, research — cite existing ids and never mint their own.
Spec.md template
Other skills inline only their own sections and point here:
# SPEC-NNN: [Feature name]
- **Status:** Draft <!-- Draft → Locked → Shipped. Set Draft: product-interview; → Locked: tech-design Step 6 (iff lock greps clean); → Shipped: execute-plan ship gate. The trivial route (product-interview → write-plan directly) skips tech-design and legitimately ships from Draft. Locked = zero open decisions and zero clarification markers. The lock gates grep per-decision markers, not this line; write-plan's stale-outline gate is the one gate that reads it (see Gate anchors). -->
- **Created:** [YYYY-MM-DD]
- **Source:** [origin — roadmap item, request, prior spec it builds on]
## Background
[Who needs this and why. The one-line job, in the jtbd job-story format (loaded at Step 0). The layer/scope boundary in a sentence. One short paragraph.]
## Requirements
[The WHAT, as observable rules — the densest, most load-bearing content. Enumerate edge cases per rule. No IDs: nothing downstream cites requirements — ACs are the citable contract.]
- [the rule] — edge cases: [list]
- [the rule] — edge cases: [list]
## UX
[Flows and states: happy path, error, empty. Surfaces/screens touched. Low fidelity is fine — ASCII mocks or bullet flows. Record the visual/structural options explored, not just the chosen one — keep each rejected layout/flow (mock or one line) with why it lost. When `generate-mockups` produced a page, link it (`mockups/…`) with any notes and stamp it *directional, not final*; the design system / `meta/DESIGN.md` wins if they disagree. Backend-only features: the externally observable contract — which fields/behavior a consumer sees; field types, nullability, and shapes belong to tech-design's outline, which takes precedence.]
## Out of scope
[Only Out-of-scope — don't restate In-scope (that duplicates Requirements). Annotate coupling.]
- [excluded item] — [coupling note]
## Acceptance Criteria
[Observable, testable "done" conditions — the contract an independent reviewer checks the diff against; the implementer never self-certifies.
**Numbering & rigor:** ids are `AC-NNN-XX` — `NNN` = this spec's folder number, `XX` = a zero-padded two-digit counter starting 01; plan tasks and tests cite the ids; scale rigor to scope.
**Gating tag (MANDATORY):** every AC carries exactly one — code-gated (machine-checkable against the diff) or human-gated with the concrete how (routed to Post-ship verification at ship). Tags are provisional at discovery; tech-design confirms or flips each once the approach is chosen — a tag-only edit, exempt from the supersession protocol.
**Revising:** ACs are the live contract — revise in place. While plan.md is absent or its Base SHA unset: a plain edit to final shape, no marker (if plan.md exists, the Revising rule's stale-plan route applies). Once Base SHA is set: add a trailing *(revised per D-NNN-XX)* marker — its presence always means changed mid-build; the why lives in the superseding decision.
**One physical line per AC:** ID, behavior, gating tag, and any *(revised per D-NNN-XX)* marker all on that line; the gates select by line.]
- **AC-NNN-01:** [observable behavior] — [code-gated]
- **AC-NNN-02:** [observable behavior] — [human-gated: how to verify, concretely]
## Decisions
[Inline, atomic `D-NNN-XX` blocks — the durable why.
**Id format:** `NNN` = this spec's folder number; `XX` = a zero-padded two-digit counter starting 01 — ONE counter per spec, shared by product (this skill) and tech (tech-design) decisions. Ids are unique across the repo (the folder number guarantees it) and never renumbered.
**Type marker:** each heading carries `[product]` or `[tech]` after the colon — advisory for readers and routing, no gate greps it.
**Citing:** cite the full id (`per D-NNN-07`), never a line number.
**Revising:** while plan.md is absent or its `Base SHA:` is unset (planning stage — anchor form in Plan anchors, `skills/write-plan/SKILL.md`), edit blocks in place to their final shape — fold an overturned Chosen into Rejected with what killed it, so the why survives in the one block; if plan.md already exists, tell the user the plan may be stale and route through write-plan's existing-plan guard. Once Base SHA is set (build started), supersede, never edit the body. Another spec's block is always superseded, never edited, at any stage.]
### D-NNN-01: [product] [decision title]
- **Status:** locked <!-- open | locked | superseded — lowercase, load-bearing (see Gate anchors). Unresolved decision = open; any open blocks downstream. -->
- **Chosen:** [the choice]
- **Rejected:** [alt — why it lost]; [alt — *deferred*, not rejected forever — why]
- **Rationale:** [the constraint that drove it; cite a verified fact if load-bearing]
- **Supersedes:** —
- **Superseded-by:** — <!-- set when Status flips to superseded; the ONLY edits ever made to a superseded block are Status + this line -->
## Structure Outline
<!-- WRITTEN BY tech-design — leave empty at discovery. Design snapshot: written `Status: Draft` for review, FROZEN once `Status: Locked`; before build, replaced only by a tech-design re-run. During build, deviations live as [Implementation] entries in plan.md's Execution Log; after ship, code is the source of truth for structure. -->
<!-- Section format lives in skills/tech-design/SKILL.md (Step 3); it ends with a "### Files touched" heading — load-bearing: write-plan's outline-present gate greps it (see Gate anchors below). -->
## Constraints
[Fixed boundaries: compatibility, performance, platform limits, dependencies. Append-by-both: discovery seeds it; tech-design appends the load-bearing numbers its recon proves. Tag each wall `[hard]` (outside our control — stamp the assumption) · `[ask]` (cross-team, movable) · `[ours]` (our code) — advisory annotations, no gate greps them.]
- [constraint] — `[hard|ask|ours]`
## Accepted risks (knowingly carried)
[Append-by-both: discovery seeds it; tech-design appends the risks the user accepts.]
- [risk we choose to live with] — [why acceptable]
## Open Questions
[NON-BLOCKING notes only — this section does not block the lock gate; blockers must be open-status decisions or inline clarification markers (see Gate anchors). Implementation questions for tech-design land here, tagged `(for tech-design)`. Omit this section entirely when empty.]
- [non-blocking question] — [why it can wait]
---
## Completion record
<!-- WRITTEN BY execute-plan at the ship gate — leave absent until then. Settles the spec: outcome stamped onto the contract. -->
**Shipped:** [date] · **Status:** Complete | Partial
### Criteria results
| AC | Result |
|---|---|
| AC-NNN-01 | PASS / PARTIAL / FAIL — [1-line evidence] |
### Post-ship verification
<!-- WRITTEN BY execute-plan at ship: manual test cases for the whole feature; each `- [ ]` as steps → expected result, human-gated ACs led by `AC-NNN-XX:`. "None — nothing manually observable" if none. -->
- [ ] [steps] → [expected result]
- [ ] AC-NNN-XX: [steps] → [expected result]
### Deferred / what this does NOT close
- [deferred debt or known limitation, with severity] — or "None"
### Review filter stats
<!-- one line aggregating the Wave Reviews tallies: review findings the fix-verify-loop pre-gate dropped + findings demoted, across all waves — so what the filter rejected stays visible -->
- [N dropped by pre-gate, M demoted, across all waves] — or "None"
Gate anchors (load-bearing — exact forms matter)
These live OUTSIDE the template so they are never copied into a spec instance. Downstream gates (tech-design Step 1, write-plan Step 1) block on:
grep -nE '^[[:space:]]*-[[:space:]]*\*\*Status:\*\*[[:space:]]*open' spec.md # any hit ⇒ blocked
grep -n '\[NEEDS CLARIFICATION:' spec.md # any hit ⇒ blocked
grep -n '^### Files touched' spec.md # write-plan Step 1 only: no hit ⇒ outline missing
grep -nE '^[[:space:]]*-[[:space:]]*\*\*Status:\*\*[[:space:]]*Draft' spec.md # write-plan Step 1 only: + outline present ⇒ stale outline (reopened)
Rules that keep these greps sound — breaking any of them silently breaks the pipeline:
- POSIX ERE only (
[[:space:]], never\s) — gates run through varying grep builds. - Case split is load-bearing: header Status values are Capitalized (
Draft/Locked/Shipped); decision Status values are lowercase (open/locked/superseded). That asymmetry is what keeps the header line out of the decision-gate regex. Never normalize one to the other. - Clarification markers are always written with the colon (
[NEEDS CLARIFICATION: ...]). The ban is by location, not intent: the colon form must NEVER appear in the canonical template body, or any text destined for a spec instance, where the gate would catch it; an illustrative: ...placeholder in this rules block or interview prose, as here, is fine — the gate reads spec.md, never SKILL.md. - Each AC is ONE physical line —
- **AC-NNN-XX:** behavior — [tag], keeping any*(revised per D-NNN-XX)*marker on that same line (a long AC stays on one line; the gates care about line count, not length). Both AC selections (execute-plan Step 4 / Seat A code-gated, Step 5.3 human-gated) grep the AC line, then filter for the tag — a tag wrapped onto a continuation line silently drops the AC from review or post-ship verification. - plan.md-side anchors (typed log tags, promotion marker, deferred tags) are defined beside the canonical plan template in
skills/write-plan/SKILL.md.