Use BEFORE writing any implementation plan for behavior-changing work — plan mode, a direct feature/fix ask, or pre-flight for a dev-pipeline ticket. Surfaces the load-bearing design decisions to the engineer while the plan is forming and emits the Decision Ledger the plan gates require. Not for typo/mechanical fixes.
Installs into .claude/skills of the current project.
Are you the author of Plan Interview?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/manoldonev-plan-interview)
---
name: plan-interview
description: Use BEFORE writing any implementation plan for behavior-changing work — plan mode, a direct feature/fix ask, or pre-flight for a dev-pipeline ticket. Surfaces the load-bearing design decisions to the engineer while the plan is forming and emits the Decision Ledger the plan gates require. Not for typo/mechanical fixes.
---
# Plan Interview
You are about to write an implementation plan. Before the plan exists as a document, the engineer co-authors its load-bearing decisions — one at a time, with grounded recommendations — so the plan records choices already made instead of asking for a post-factum rubber stamp.
Load `interviewing-baseline` first (Skill tool) — it owns the loop rules and the Decision Ledger contract. This file only adds what is specific to plan-time design elicitation.
## Scope
You elicit **design decisions from the engineer** (plan-authoring). You do NOT:
- Elicit requirements from a reporter — that's `intake-interviewer`.
- Decompose into sub-issues — that's `intake-orchestrator`.
- Stress-test a finished plan — that's `grill-me` (this skill's convergence walk borrows grill-me's semantics; it doesn't replace the user-initiated skill).
## Protocol
1. **Explore first.** Read the issue/spec, the affected code, the repo's ADRs/decision docs (wherever CLAUDE.md routes), and the Product-Essence Brief if one exists (`.claude/pipeline-state/{issue}-brief.md`). Every codebase-answerable question is answered here and recorded as `codebase-derived` — never asked.
**Fan-out (pipeline pre-flight only).** When this run is pre-flight for a lane ticket
(`/plan-interview <issue>`), launch the intake fan-out *at the start*, before exploring, so it
runs while you explore. It never reads your work, so starting early costs nothing.
0. **Tell the operator first, and let them opt out.** Before anything else in pre-flight,
before exploring and before launching, give one notice and one question (`AskUserQuestion`,
the run option first). The notice says, in plain words:
- the fan-out runs by default on lane tickets;
- what it does: a sealed writer sets four angles and a pre-mortem; five lenses work them
blind, alternating Opus and Fable; every claim faces a refuter from the other family and
is dropped unless confirmed; what survives joins this interview as evidence;
- why: on consumer tickets whose intake decision was later reversed, intake with the pool
caught decisions plain intake missed;
- what it costs: it runs alongside exploration for up to 30 min, and questions start when
it returns; it uses Fable where the account has it (subscription usage) and Opus where it
does not;
- what is recorded: the receipt's `## Fan-out` shows what it added over plain intake.
The options are **Run the fan-out (default)** and **Skip it for this ticket**; the
operator may add a reason. On a skip, launch nothing and record exactly one line in the
receipt's `## Fan-out`: `Fan-out: skipped — by the operator at the pre-flight notice: <their
reason, or "no reason given">`. Ask once: an answer is not reopened later in the session.
A session with no `Workflow` tool asks nothing: it says the fan-out cannot run here and
records step 1's failed form.
1. **Check for the `Workflow` tool.** If this session has none, record
`Fan-out: failed — Workflow tool unavailable in this session` and go on without it.
2. **Stage the script** the way intake-orchestrator stages `intake-review.mjs` (cross-plugin,
by name; `$SKILL_DIR` is this skill's base directory and `$SCRATCHPAD` the session's
scratchpad):
```bash
SRC=$(find "$SKILL_DIR/../../../.." -path '*/review-toolkit/*' -path '*/workflows/intake-fanout.mjs' -not -path '*/fixtures/*' 2>/dev/null | sort -V | tail -1)
cp "$SRC" "$SCRATCHPAD/intake-fanout.mjs"
```
3. **Launch it in the background** with `Workflow({ scriptPath, args })`. The args are
`{ issue, issueBody, readRoot, checkouts, protocol, probeDir, config }`:
- `protocol`: the absolute paths of this skill's `SKILL.md` and of
`interviewing-baseline/SKILL.md`. Every agent works under them, as in the measured run.
- `checkouts`: every other repo the change meets that has a local checkout (the frontend
that calls this backend, a client, a sibling service), each as `{ path, role }` with
`role` saying what it is, e.g. "The admin frontend that calls this backend". Find them
from the ticket and the repo's docs before launching: the measured run gave every agent
the consumer's checkout, and one of its two catches came from reading it. A consumer
with no reachable checkout goes in the receipt as `Checkouts: <repo> not available`.
- `config` carries only `reviewers.modelOverrides` and `reviewers.tierMap`.
The notice in step 0 is the announcement; say nothing more at launch. The script stops
waiting after 30 minutes on its own and returns the failed form.
4. **Ad-hoc and plan-mode runs** skip it silently: no notice, and their plans carry no
`## Fan-out`.
**Snapshot before you read the pool.** When exploring is done, write your register as it stands
to `.claude/pipeline-state/{issue}-snapshot.md`. That is what plain intake would have gone in
with. The snapshot stays out of the conversation: it is internal, never shown to the engineer
as a draft (P8 holds), and it reaches the record only inside the receipt's `## Fan-out`. Only
then read the fan-out's result, waiting for its completion notice if it is still
running. No question goes to the engineer before the pool is in, and none arrives between two
questions.
**Consume the pool like explore-first findings.** Verify what you need from each item's
pointer. An item becomes a register row, option, probed fact or question **only if it passes
the materiality bar in step 2**. Pool evidence may overturn a snapshot claim it contradicts.
Then write the receipt's `## Fan-out` section (schema in `interviewing-baseline`, "Fan-out"):
every item tagged against the snapshot with the angle that produced it, the tally, the
refuter family, the `Lenses:` line from the result's `lenses` (key, model, kept/made, or
`failed`), and the snapshot embedded verbatim, indented four spaces. If the result is `failed` or `partial`, say what failed
in the section; never drop it silently.
2. **Build the decision register.** Admission = **materiality**. A decision enters the register only if it changes:
*What the user gets* — the categories that go first because they are the ones an engineering-shaped register silently drops:
- **states and transitions** — loading, empty, partial, error, not-found, and what moves between them,
- **copy the user reads** — labels, messages, empty-state and error text; "we'll word it later" is a decision deferred, not a decision avoided,
- **first paint** — what is on screen before the data arrives, and whether anything shifts when it does,
- **degraded and missing dependencies** — what the user sees when the thing this depends on is absent, stale, or empty,
- **composition and placement** — what else is on the surface, what it sits next to, what is suppressed when there is too little to show.
*How it is built* — material for the same reason, and lower only because they are the ones that get asked anyway:
- observable behavior (flows, status codes, emitted events),
- API or data contracts (endpoint shapes, DTO fields, wire formats),
- data invariants (uniqueness, soft-delete semantics — every natural key needs a conscious uniqueness decision),
- scope boundaries (what this PR explicitly does not do),
- tenancy/security posture (multi-tenant scoping filters per the repo's CLAUDE.md rules, guards, credential handling),
- migration/rollout (existing rows, backfill, ordering across workspace packages).
Everything below that bar is decided silently and does not enter the register. Material design decisions are near-always groundable, so a recommendation is expected on each question.
**The ticket's candidate fixes are input, not the option set.** Build the options for the decision that picks the mechanism yourself. When the root cause lies in how a tool, library or platform mode behaves rather than in this repo's code, at least one option replaces that primitive instead of working around it. Find it on the tool's own surface (`--help`, its docs), check this repo's history with it (`git log`, earlier tickets), and state what it costs. The row's Resolution names every option weighed, the ticket's and yours, so a reviewer can see what was compared.
3. **Enumerate the surfaces, then check the register against them.** The register is whatever you chose to admit, so "the register is empty" grades the interview against itself. Independently of it, list the surfaces and states this ticket implies — every screen, route, state and artifact a user or operator ends up looking at. Then walk that list: each entry is either **decided** by a register row, or **explicitly out of scope with a reason**. An entry that is neither is a missing register row — add it and interview it.
The list is not scratch work. Pre-flight for a pipeline ticket, it is the receipt's mandated `## Surface Inventory` section — schema in `interviewing-baseline`, enforced by `ledger-lint.sh --receipt`. In a plan-mode plan, write the same section beside the ledger; the section is receipt-only to the lint, so nothing there will fail you for omitting it, which is exactly why it is on you. Genuinely surface-free work states the explicit empty form and moves on.
4. **Pick the traversal.**
- **Flat walk** — ≤ 4 uncoupled decisions: walk the register in any order.
- **Convergence walk** — ≥ 5 decisions, or any coupling (one answer changes another decision's options): resolve in dependency order, parents first, grill-me style. Both traversals share the same exit criterion.
5. **Interview** per the baseline loop rules (≤ 2 material questions per turn, recommendation first, "your call" → `user-delegated`).
6. **Emit the Decision Ledger** and exit only when the register is empty *and* every surface is accounted for — each material decision carrying a non-`assumed` provenance, each surface decided or scoped out. Trivial work exits immediately with both explicit empty forms. Pre-flight for a pipeline ticket, the receipt also needs its checks and, on a design-provider repo, its frames — see "Checks and design frames" below.
## Where the ledger lands
- **Plan-mode / ad-hoc session:** a `## Decision Ledger` section in the plan file itself, before `ExitPlanMode` is called (this plugin's `exitplan-ledger-gate.sh` hook lints for it and blocks the exit if it's missing or malformed).
- **Pipeline pre-flight** (`/plan-interview <issue>` before an autonomous pipeline run on `<issue>`): write `.claude/pipeline-state/{issue}-ledger.md` — same location convention as the Product-Essence Brief; it survives worktree cleanup, and the pipeline's scheduler commits it as the lane branch's first commit, hands it to the BUILD session as binding decisions, and reads its checks and design frames from that commit. The interview always happens in the interactive session, never inside the autonomous run.
## Checks and design frames (pipeline pre-flight only)
The receipt carries a mandated `## Fan-out` section (step 1's fan-out; schema in
`interviewing-baseline`, "Fan-out"). It also carries the two sections the scheduler reads from the record's first commit —
schema and empty forms in `interviewing-baseline` ("Checks", "Design frames"), enforced by
`ledger-lint.sh --receipt`. Elicit both before writing it:
- **`## Checks`** — start from the repo's verification recipe (its CLAUDE.md, the config's
`commands.*`) and add what this ticket needs beyond it: the selftest it touches, a script its
acceptance criteria name. The configured lanes run anyway, so list only what they would miss,
one command per line. When they miss nothing, write the empty form.
- **`## Design frames`** — on a repo with `design.provider` set. Walk the design handoff with the
engineer: one row per screen and state the ticket renders, with the frame id. For each row, agree
the `must-show` value now — a data-test id or a copy string taken from the frame. It is the
record's only per-row assertion, so it should be the element that proves the right state rendered
(the empty-state copy on an empty row, not the page title every state shares). A ticket that
renders nothing new disarms with `Design: none — <reason>`.
## Duplicate scan (pipeline pre-flight only)
A pre-flight ledger is the last thing written before a ticket becomes eligible to run, so it is
the last place a duplicate can still be caught cheaply. Before writing it:
```bash
bash "${CLAUDE_PLUGIN_ROOT}/skills/plan-interview/tools/dup-scan.sh" --issue <issue>
```
- **`0`** — nothing above the threshold. Record nothing.
- **`10`** — ranked candidates. Judge each one yourself and record one Decision Ledger row per
candidate: the same work, overlapping but distinct (sequence it if they touch the same files —
a candidate carrying the *claimed* label is already being built), or unrelated. The scan
proposes; you and the engineer decide. **Never close a ticket on its output.**
<!-- LOCKSTEP-BEGIN dup-scan-rc2 -->
- **`2`** — the scan could not run. Hard-stop: report the rc and the reason.
<!-- LOCKSTEP-END dup-scan-rc2 -->
Hand nothing off: the ledger does not go out on an unscanned ticket.
Under `tracker.type: jira` it prints a not-applicable line and exits `0` — no queue label, no
corpus. The ad-hoc / plan-mode path above scans nothing: there is no ticket to be a duplicate of.
## Escalation
Stop and present uncertainty (what you understood / what's blocking / options / a clear question) when:
- the user's answers contradict each other or the codebase, and rechecking doesn't resolve it;
- the register keeps growing past ~10 material decisions — the ticket is likely decomposition-worthy; suggest `intake-orchestrator`;
- the register is implausibly **thin** for the scope. A ticket with a page of acceptance criteria about rendered output, answered by a handful of transport and build decisions, has not been under-scoped — it has been mis-scoped, and the surfaces in step 3 are where the missing rows are. Say so and widen; a thin register is the failure this skill is most likely to exit satisfied on, because its exit criterion is met.
## What NOT to do
- Don't quiz — every question elicits a decision and carries a grounded recommendation; none tests the engineer.
- Don't ask codebase-answerable questions (baseline rule 1).
- Don't pad the register with immaterial choices to look thorough — 0 material decisions is a legitimate outcome; emit the explicit empty form and move on.
- Don't re-litigate ledger entries later in the session; a recorded resolution is binding for the plan.
- Don't write the plan first and interview after — the interview precedes and shapes the plan.