Use when writing or revising a plan document — file naming, F-block layer tags, the Produces/Consumes rule, the review dispatch and its verdicts, and Continue/List mode resolution.
Installs into .claude/skills of the current project.
Are you the author of Add Plan Authoring?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/brabos-ai-add-plan-authoring)
---
name: add-plan-authoring
description: "Use when writing or revising a plan document — file naming, F-block layer tags, the Produces/Consumes rule, the review dispatch and its verdicts, and Continue/List mode resolution."
---
# Plan Authoring
<!-- uses:
- agent: plan-review-agent
- skill: add-review-discipline
- skill: add-final-report
- skill: add-plan-authoring/references/plan-template.md
- mention: add-build-ledger
- mention: add-framework--done
- mention: add-framework--build
- mention: add-framework--brainstorm
- mention: add-framework--plan
-->
Owns the plan DOCUMENT. What the plan decides is the planning command's job; how it is named,
shaped, reviewed and delivered is here.
## When to Use
- Writing a new plan, or revising one.
- Resolving a plan argument (Continue Mode) or listing plans (List Mode).
- Dispatching the plan reviewer and acting on its verdict.
## When NOT to Use
- Executing a plan. That is the build side — load `add-build-ledger`.
---
## File Naming
Three documents are named here — the plan, the changelog and the intent file. The first two share one timestamp rule:
| Document | Path |
|---|---|
| The plan | `docs/plans/YYYY-MM-DDTHHMMSS-PLAN--<slug>.md` |
| The changelog | `docs/changelog/YYYY-MM-DDTHHMMSS-<verb>-<slug>.md` |
Timestamp in **local time**, `T` between date and time, **no separators inside `HHMMSS`** — Windows
forbids `:` in a filename. Lexicographic sort therefore equals chronological sort.
`<verb>` is `add` | `update` | `refactor` | `remove` | `fix`. **It survives the timestamp**: once the
timestamp leads it no longer affects order, and it is information the files already carry. The date
alone did not sort — a day with several deliveries alphabetised by verb, and the directory holds days
that did.
⛔ **Pre-existing changelogs keep the names they were written with**, exactly as pre-existing plans do
below. Nothing reads a changelog by its filename, so a rename rewrites files for no reader.
**Both writers point here and neither declares a pattern of its own** — `/add-framework--build` at
its STEP 8, `/add-framework--done` at its STEP 4. Two stages declaring one format is how they drift.
```
⛔ ONE CHANGELOG PER DELIVERY:
⛔ DO NOT: Allocate a timestamp for a delivery that already has a changelog
⛔ DO NOT USE: Write on a new path when a file in docs/changelog/ already covers this delivery
✅ DO: Find that file, EDIT it in place, and keep the filename it already has
```
**The timestamp is what makes that rule necessary.** Under the date-only name both writers computed
one path, so the second landed on the first file and the directory ended up holding one changelog per
delivery by accident. Two timestamps differ by the seconds between the two commands. Without the rule
above, one delivery reaches `main` as two files that each tell half of its story.
```
⛔ THERE IS NOTHING TO LOOK UP:
⛔ DO NOT USE: Read or ls on docs/plans/ to find a next number
⛔ DO NOT: Allocate a sequential NNNN
✅ DO: Take the timestamp from the clock
```
**A plan SET allocates its timestamp once, at the umbrella, and every topic reuses it verbatim** —
that is what keeps a set grouped in the directory now that a shared number no longer does:
```
docs/plans/2026-09-07T005046-PLAN--<slug>-000-umbrella.md
docs/plans/2026-09-07T005046-PLAN--<slug>-001-<topic>.md
```
**Companions suffix the plan basename:** `--evidence-v01.md`, `--review-v01.md`, `--ledger.md`.
**A brainstorm SET follows the same two rules** — one timestamp for the whole set, and an `NNN`
ordinal on every member, `-000-umbrella` then `-001-` onward. `/add-framework--brainstorm` owns its
directory and its slug; only the shape is shared. It is recorded here because a reader comparing the
two set conventions would otherwise find the rule for one and not the other, which is how they drift.
### The Intent File
`/add-framework--brainstorm` writes `docs/brainstorming/YYYY-MM-DDTHHMMSS-<slug>-intent.md` at its
handoff, on the `bounded` and `architectural` paths. **Never on `spike`.**
| Path | What exists afterwards |
|---|---|
| `architectural` | The design document AND the intent file, sharing one timestamp so the pair sorts adjacent |
| `bounded` | The intent file alone, with its own timestamp |
| `spike` | Nothing |
**Shape — capped at one screen. If it reads like a document, it is too long.**
```markdown
---
path: bounded # spike | bounded | architectural
topic: <slug>
doc: <design document path, or none>
delivery: confirm # confirm | automatic — the answer to the brainstorm's STEP 7.3 approval
ticket: 0007B # optional — present only when the work came from a ticket; see The Ticket
---
## Decided
- <decision> — <one-line rationale>
## Open
None
```
⛔ **`## Open` reads the literal `None` when everything closed, and that is the normal case.** A
section present but empty is read as ABSENT, and absent means not closed — `/add-framework--plan`
then runs its full questionnaire. Falling the other way would build a plan from decisions nobody made.
⛔ **There is no validation gate for this file.** `add-doc-schemas` is a product-layer skill and does
not reach `workbench/`. The reader’s fallback-to-questionnaire rule is what covers a malformed one, so
the shape above is a contract between two commands rather than something a gate enforces.
**Why this exists at all, when the design document pointer already did:** that pointer exists on the
`architectural` path only. `bounded` writes no design document, so on the one path a size branch
serves there was nothing to read and nothing survived the session.
### The Delivery Mode
**Two values, chosen once, at `/add-framework--brainstorm` STEP 7.3, and carried — never re-asked.**
| Carrier | Written by | Read by |
|---|---|---|
| `delivery:` in the intent file | `/add-framework--brainstorm` 7.3 | `/add-framework--plan` STEP 1.2 |
| `> **Delivery:**` in the plan header | `/add-framework--plan` STEP 5, copied from the intent file | `/add-framework--build` STEP 1.1 |
⛔ **Absent means `confirm`.** No intent file, no field, no header line, a plan written before this
existed, a direct build — every one of them runs with a human confirming each stage. The fall is
toward waiting, never away from it.
#### The stopping rule
Every `[STOP]` in the four stages is one of two kinds:
| Kind | It presents | On `confirm` | On `automatic` |
|---|---|---|---|
| **deciding** | A choice the brainstorm's approval did not cover — an open question, a blocker, an ambiguity, a push | Waits | **Waits** |
| **confirming** | Something the approval already covered — a design, a summary, a report of work already agreed | Waits | Does not wait |
⛔ **"Does not wait" means not waiting, never not printing.** A confirming stop on the automatic path
still emits everything it would have shown — the design, the confirmation screen, the plan preview,
the report — and then continues. Skipping the output would hide what is being decided without the
operator in exactly the mode where they are not there to ask.
```
IF A CONFIRMING STOP IS PASSED ON THE AUTOMATIC PATH:
⛔ DO NOT: Skip the content the stop presents
✅ DO: Print it in full, then continue to the next step
```
⛔ **Classify by STATE, not by marker.** One marker can be both kinds: `/add-framework--build` STEP 9
decides on the first push of a branch with no PR, and only confirms once a PR exists. Each stage
classifies its own stops where they sit, with the state that decides the kind.
```
IF A STOP'S KIND IS NOT OBVIOUS FROM ITS STATE:
⛔ DO NOT: Treat it as confirming to keep the automatic path moving
✅ DO: Treat it as deciding, and wait
```
**Three stops are deciding in every state, and nothing reclassifies them:**
- **`/add-framework--build` STEP 9 on a branch with no PR** — the terminus of the automatic path.
- **The four hard stops** `add-build-ledger` owns.
- **Every stop in `/add-framework--done`.** The close-out is never reached unattended, so it never
runs in the automatic state at all.
### Legacy forms resolve for reading, never for writing
Three forms are on disk and all three RESOLVE: the current `YYYY-MM-DDTHHMMSS-PLAN--`, the legacy
`NNNN-PLAN--`, and `-SELF-PLAN--` from when planning was split by layer. `AGENTS.md` and several
documents cite plans by number. **Only `-PLAN--` with a timestamp is written for a NEW plan.**
---
## The Ticket
<!--
Modelled on the product procedure, add--backlog/references/lifecycle.md, and
kept to the same rules. The native backlog `.cjs` entries and
`add-doc-schemas/references/backlog.md` are PRODUCT
nodes, named in prose on purpose and deliberately NOT declared under
`uses:`: targets resolve inside the declaring artefact's own layer
(scripts/build.js), so a `- script:` entry here would resolve to a
nonexistent internal/script/ node and fail the graph gate. This
repository is those scripts' source and calls them by repository path, as
add-framework--done calls delivered.cjs.
The seven statuses and the two rules below are held equal to the product's by
cli/tests/board-phase-writes.test.js L11.5. Change one side and change both.
-->
**A ticket on `docs/backlog.jsonl` travels through the four stages and moves on the board as the work
does.** Each stage carries one line pointing here; **this section is the only place the rules live.**
What each status MEANS — the nine names, the seven columns, the pairs of a running and a parked status —
is the product's phase model, `add--backlog/references/phases.md`; this section is the internal procedure.
⛔ **The internal pipeline has no feature system.** The product gates its ticket instructions behind the
`board` feature; the four stages here carry theirs unconditionally, because `workbench/` ships to no user and
has nothing to toggle. That asymmetry is a decision, not a gap.
### The rules
```
IF WORK MIGHT HAVE COME FROM A TICKET:
⛔ DO NOT: Search the board for a ticket whose title resembles the work
⛔ DO NOT: Act on a ticket no document names — match by nothing but the declared id
⛔ DO NOT: Create a ticket from any stage — only the user does, through /add-framework--backlog
✅ DO: Act on the id a `ticket:` field or a `> **Ticket:**` line declares, and on nothing else
```
**A wrong inferred match moves someone else's ticket, and nothing downstream can detect it.** A missed
link costs one manual update; a wrong one costs the board's credibility.
**The id is `[0-9]{4}B`** — four digits and the letter `B`, from the shared global counter.
### Where it travels
| Stage | Where | Does | Writes the board |
|---|---|---|---|
| `add-framework--brainstorm` | STEP 1.1 | Resolves the id from the invocation, reads that ticket, and uses its `title`, `tldr`, `notes`, `paths` and `done_when` as exploration input | — |
| | right after `2.2.2` states the path | — | `refining`, on `bounded` and `architectural` only. **A spike writes nothing** |
| | STEP 5.2 | `> **Ticket:** <id>` in the design header — `architectural` only | — |
| | `7.3`, once the intent file is written | `ticket: <id>` in the intent file | `shaped` |
| `add-framework--plan` | STEP 1.2 | Reads `ticket:` from the intent file with `delivery:`, then the ticket | `planning` |
| | STEP 5 | `> **Ticket:**` in the plan header, the ticket's `done_when` as `**Ticket done when:**` | — |
| | STEP 7, before the report — the plan is written and reviewed | — | `planned` |
| `add-framework--build` | STEP 5.1, right after the ledger is opened | — | `doing` and `work_id`, one write |
| | STEP 10, before the report, reading STEP 9's answer | — | `in-review` — **only when a PR was opened or already existed** |
| `add-framework--done` | STEP 8, first, before any deletion — the first point the normal, resume and recovery paths share after the merge | — | `done` |
**After the plan, the plan header is the only carrier.** Build and done read `> **Ticket:**` from the plan
and never from the intent file. A direct build has no plan, so it carries no ticket and touches no board.
**`in-review` depends on STEP 9's answer, and one answer never writes it.** On "yes" the build opens the PR,
and when a PR already exists it pushes to it: both write `in-review`. On "no" the close-out opens the PR and
merges it in one run, so `in-review` would exist for seconds — the ticket goes `doing` → `done`, the same jump
a product hotfix makes.
**The product's `feature` field has no internal counterpart.** There is no feature id here; it stays `null`.
### Reading one ticket
A declared id is answered by the exact detail read: `get <id>` — exact, case-sensitive, no filter,
returning the raw row. Run it BEFORE the phase guard and the `work_id` read: `status` and `work_id`
both come from the detail, never from a summary. No match (`TICKETS_RETURNED=0`, exit 0) means the id
is not on the board: report it and continue with no ticket.
```bash
node framwork/.codeadd/scripts/backlog-cli.cjs get <id>
```
A subject, not an id, is resolved with `search`, which runs over every status and also answers an
exact id; a `list --all` summary remains the board view for choosing, never the source of ticket
detail.
### The two rules that decide whether a write happens
**Neither compares order.** `order` is a sort key and never a position in the flow.
- **The phase check, for an entry write** (`refining`, `planning`, `doing`): skipped when the ticket already
reads that phase's entry status or its exit status — two string comparisons against the two names of one
phase.
- **The `work_id` stop, for every write before the build:** once the ticket's `work_id` equals this run's
plan basename, only `in-review` and `done` may still be written. `refining`, `shaped`, `planning`,
`planned` and `doing` are skipped.
**An exit write keeps the plain rule** — written unless the ticket already reads it.
**Every write reads the ticket first** and does nothing when it already holds the target. That is what makes
a resumed close-out, a re-run build or a re-run plan safe with no guard of its own.
⛔ **The `work_id` stop does not bridge a plan SET.** The internal `work_id` is the plan basename, so several
plans over one ticket carry several values, and the ticket moves back through the phases as each plan runs.
**Deliver a ticket that needs several stages of work as ONE plan with checkpoints on one branch**, not as a
plan set — one plan, one `work_id`, one coherent history. That is how the delivery that introduced these
writes was built, for exactly this reason.
### How a write is made
One call per write, carrying only the fields that change. The record travels on a FILE this stage
writes first (`docs/.tmp-ticket.json` is the usual scratch name, cleaned up in the same step) —
`node framwork/.codeadd/scripts/backlog-commit.cjs update <id> --record-file <scratch.json>`:
| Write | One call, per its record file |
|---|---|
| any status | record `{"status":"<status>"}` |
| `doing` (build) | record `{"status":"doing","work_id":"<plan basename>"}` |
The Node entry also supports stdin when `--record-file` is absent; agents use the file recipe.
**`work_id` is the plan basename** — the internal work's identity in the ledger, the delivery index and
`docs/deliveries/`. A ticket already `doing` under a **different** `work_id` is written over, exactly as the
product procedure does, and the stage reports the `work_id` it replaced. **`doing` and `work_id` travel in ONE
write**, so the two can never disagree about whether the work started.
The entry picks its own route: on `main` it commits directly, on a feature branch it writes through a
locked worktree, so the ticket reaches `main` without touching the branch being built. **That is why the
brainstorm and the plan can write the board**: they write no file on the branch, and the board write never
touches it either.
### Degradations — none of them is a stop
**The board is a side-record.** A build that fails because a ticket could not be moved has inverted the
relationship between the work and the note about the work.
| State | Do |
|---|---|
| No `ticket:` / no `> **Ticket:**` | Nothing. Most work never came from a ticket |
| `BACKLOG_PRESENT=no` | Nothing |
| The id is not on the board | Report it, continue with no ticket |
| `REFUSED=unknown-status` — this repository's definitions file does not define the status | Report which status, continue. Never pick another status by its `order` |
| `DEGRADED=<reason>` | Report what did not happen and the local `SHA`, continue. Never push or rebase by hand |
```
IF A TICKET READ OR WRITE FAILS:
⛔ DO NOT: Stop the stage, fail the build, or refuse the merge
⛔ DO NOT: Report only the last one when a stage made several
✅ DO: Put one line in the stage's final report for EACH write that did not land, and continue
```
**Every outcome reaches the user through the stage's final report** — the write's `SHA`, or what did not
happen. A ticket that silently falls a column behind is the failure this section exists to prevent.
---
## The Plan Preview
**What the plan will say, shown before it is written.** `/add-framework--plan` prints it at the end of
its STEP 4, inside the stop that step already makes — on the automatic path it is the one moment the
operator sees what is being decided without them.
**Shape — five labelled items, in this order:**
```markdown
**Objective:** <one line — the objective the plan will carry>
**Phases:** <one line per phase — its name and what it includes>
**Order:** <the phase order, and WHY it is that order, in one or two lines>
**Risk:** <one line per artefact the plan changes — its name and its risk grade from STEP 3.2>
**Excluded:** <what the plan will NOT do, one line per item>
```
```
IF COMPOSING THE PREVIEW:
⛔ DO NOT: List files, validation levels, F-block ids or F-block detail — then someone reads the
preview instead of the plan
⛔ DO NOT: Run a new query, audit or dispatch for it — it is composed from what STEP 3 produced
⛔ DO NOT: Add a stop for it — STEP 4 already stops
✅ DO: Compose the five items from the analysis in hand, and print them
```
**It is not the plan in miniature.** A reader who needs a file or a check opens the plan; the preview
exists so that nobody has to, to know whether the plan is heading the right way.
## The Short-Plan Shape
**A `bounded` design produces a plan carrying the template’s mandatory sections only.** The five the
template already marks *(optional)* — Current State, Accepted Trade-offs, Risks and Mitigations, the
Combination matrix, and References — are omitted.
⛔ **Nothing else shrinks.** Global Constraints still reads `None` rather than vanishing, every
F-block still carries its layer tag and its validation, and the Produces/Consumes rule binds exactly
as it does on a full plan. **A short plan is a plan with fewer sections, never a plan with weaker
ones** — the sections that prove correctness are not the optional ones.
---
## The Delivered Home
**A plan in flight lives in `docs/plans/`, gitignored and local. A plan that has been closed out lives
in `docs/deliveries/<plan-basename>/`, tracked.** `/add-framework--done` assembles that directory in
its STEP 6 and commits it with the delivery-index entry, so the documents reach `main` before anything
removes the worktree that held them.
**The directory name is the plan's basename without `.md`, verbatim** — the same string
`docs/delivered.jsonl` carries as the entry's `id`. Index line and documents join on one string, with
no lookup table between them.
| Member | Sourced from | Present when |
|---|---|---|
| `plan.md` | `docs/plans/<basename>.md` | always |
| `ledger.md` | `docs/plans/<basename>--ledger.md` | always |
| `design.md` | the `docs/brainstorming/` file the plan's **Context** document table names | the plan cites one |
| `intent.md` | the `docs/brainstorming/` **intent file** the plan's **Context** document table names, resolved the same way | the plan cites one |
| `review.md` | the highest-numbered `docs/plans/<basename>--review-v*.md` | a legacy companion is on disk |
| `evidences/` | `docs/evidence/` files for this plan, original names kept | such files are on disk |
**The first two are load-bearing; their absence is a defect, not a variation.** The plan carries the
reasoning and the ledger carries the rulings, and neither has a second copy anywhere.
⛔ **Every member is a byte-for-byte copy of its source. The filename changes; the contents never do.**
An archive is worth keeping only because it IS the document — a reader years from now cannot tell a
faithful copy from a confident rewrite, and will trust either. Summarising, trimming, reformatting or
reconstructing a document on the way in produces something that reads as the record and is not, which
is worse than an empty directory. The command that assembles it copies the bytes and proves each copy
matches before staging.
⛔ **On a `bounded` delivery the intent file is the ONLY record of what was decided and why**, because
no design document exists. `docs/brainstorming/` is gitignored exactly as `docs/plans/` is, so without
this member that delivery reaches `main` carrying a plan whose reasoning points at a file nobody else
will ever have.
**The last four are conditional, and a directory holding none of them is the normal case.** No
command writes a `--review-v*` companion or anything under `docs/evidence/` any more, so both members
exist to carry what is already on disk from before. `design.md` is absent whenever the plan carried
its decisions inline, which most do.
⛔ **`evidences/` sources from the `docs/evidence/` DIRECTORY, not from the `--evidence-v01.md`
companion named under File Naming above.** The two names read alike and are different things.
⛔ **`docs/evidence/` holds files from several plans at once, matched by an id prefix** — a file is
this plan's when its name begins with the plan's id. **A file that cannot be attributed to a plan is
left where it is and reported.** Never sweep the whole directory into one delivery: the wrong plan's
evidence filed under this one is worse than evidence left behind, because it reads as this delivery's
own record.
**What does NOT move:** `docs/changelog/` and `docs/delivered.jsonl` are already tracked and stay
where they are. A plan that was never closed out is never archived — it stays in `docs/plans/` and is
removed by hand if abandoned.
---
## Layer Tags — One Plan, Both Layers
**Every F-block declares its layer.** That tag is what lets the execution order interleave the layers
instead of committing one half of a working change and waiting.
```
- **F1** [internal] — `workbench/commands/foo.md`: ...
- **F2** [product] — `framwork/.codeadd/scripts/bar.cjs`: ...
```
| Tag | Means | Paths |
|-----|-------|-------|
| `[product]` | Distributed artefacts | `framwork/.codeadd/`, `framwork/provider-map.json`, `cli/`, `mcp/` |
| `[internal]` | Development tooling | `workbench/`, `scripts/`, `AGENTS.md`, the repo root **except `mcp/`** |
⛔ **`mcp/` is at the repository root and is PRODUCT.** It holds the knowledge-graph MCP
server, which `scripts/build.js` copies into the npm package — shipping is what decides the
layer, not depth in the tree. It is the only root directory on the product side, and
`AGENTS.md` carries the same note where it maps the anatomy.
A plan whose F-blocks are all one tag is a single-layer plan. That is normal, not a defect.
---
## The Interface Rule — Produces / Consumes
**Every F-block that hands something to a later one MUST declare it.** The producer states
`Produces:`, the consumer states `Consumes:` with the **same string** plus the producing F-block id.
⛔ **The interface here is NOT a function signature.** It is the `KEY=STATUS` line a script emits, a
sidecar key, a frontmatter field, an injection anchor name — whatever one F-block writes down and a
later one reads.
```
- **F3** [product] — `framwork/.codeadd/scripts/converge-gates.cjs`: adds a sixth delivery gate.
- **Produces:** `converge-gates.cjs` emits `GATE6=pass|fail|skip`
- **F7** [product] — `framwork/.codeadd/commands/add-done.md`: STEP 4 reads the new gate.
- **Consumes:** `GATE6` (F3)
```
**Every `Consumes` MUST name an EARLIER F-block that `Produces` it, using the SAME string.** A
`Consumes` with no matching `Produces` is an F-block built against a name someone still has to invent.
---
## Authoring Rules (READ BEFORE WRITING)
**A plan states WHAT WE WILL DO, not what will be written to the file.**
```
IF TEMPTED TO PASTE THE CONTENT A FILE WILL RECEIVE:
⛔ DO NOT: Write the command body, the skill body, the agent prompt, the fragment text
⛔ DO NOT: Turn the plan into something the build copy-pastes
✅ DO: Name the file, name the change, and POINT at the design doc carrying the contract
```
| Belongs in the plan | Belongs in the design doc it points at |
|---|---|
| Which files change and what changes about them | The contract, the schema, the worked example |
| Why an F-block exists and what it must not lose | The rejected alternatives and their rationale |
| The order of work and its dependencies | The conversation that produced the decision |
| How each F-block is proven correct | — |
If a design doc exists in `docs/brainstorming/`, the plan **references it and does not restate it**.
With no design doc the plan carries the decision inline — still the decision, never the file content.
**`## Objective` is the one section that is COPIED rather than referenced.**
```
IF WRITING OR REVISING A PLAN:
⛔ DO NOT: Re-derive the objective from the design — you will write a better sentence, and a better
sentence is a different objective
⛔ DO NOT: Point at the design for it, the way every other section does — the reviewer, the build
and the close-out all read the plan, and `docs/brainstorming/` is gitignored
⛔ DO NOT: Widen it to cover scope the design did not have
✅ DO: Copy the design's `## Objective` byte for byte, then add what is true once the build is done
```
**It is copied because it is the one thing everything downstream is checked against.** Every other
section can point at the design; this one is read by the reviewer's `Objective fit` dimension, and a
pointer into a gitignored directory is not something a reviewer can read.
⛔ **A plan with NO design document still states an objective**, drawn from the conversation and marked
as such — `[from conversation, no design document]`. Otherwise the `bounded` path, which writes no
design at all, is the one path whose work arrives with nothing to check it against. That is the
opposite of what a short path needs.
**Every F-block MUST be covered by at least one validation level.** An F-block with no proof is a gap
the reviewer cannot see.
**Global Constraints is never omitted.** Every requirement binding the WHOLE plan, one line each, the
**exact value copied verbatim from its source**, with that source cited in parentheses. It is handed
to the reviewer as its attention lens: "keep it consistent" cannot be reviewed. **With no plan-wide
constraints the section reads the single word `None`** — an absent section is a question, `None` is an
assertion.
**Document structure:** load `references/plan-template.md`.
---
## Review Dispatch (BEFORE ANY DELIVERY)
**GATE CHECK:** Does the plan file exist? IF NO → write it first. DO NOT proceed.
```
IF THE PLAN HAS NOT BEEN REVIEWED THIS SESSION:
⛔ DO NOT: Present the plan path, summary, or next-step commands as delivered
✅ DO: Dispatch the reviewer and WAIT for its report
```
**DISPATCH AGENT:** `@plan-review-agent`
- **Capability:** read-only
- **Complexity:** standard
- **Input:** `path` (the plan file), `kind: plan`, `layer` (derived, below)
**Derive `layer` from the F-block tags** — all `[product]` → `product`, all `[internal]` → `internal`,
mixed → `both`. Always sending `both` hands the reviewer a wider lens than the plan needs and loses
the layer-boundary check on a single-layer plan.
### Acting on the Verdict
**`add-review-discipline` owns this.** How many times the reviewer runs, what a caller owes a report
it receives, and the `blocked` path all live there, because three commands need the same answers and
restating them here is what let them drift apart in the first place.
Two things this skill adds on top, both specific to a plan document:
- A `fix-then-ok` whose fixes land in the plan gets a **changelog row** naming what changed.
- The plan is not presented as delivered until the report has come back and been acted on.
⛔ DO NOT invent decisions to clear blockers.
⛔ DO NOT skip review in Continue Mode.
---
## Completion — The Closing Report
**`add-final-report` owns the shape.** The seven blocks, the banned phrasings, the rule that the
report is emitted before any metadata, and the self-check all live there. Load it at the planning
command's last step.
The metadata that follows the report is the plan path, its status, the review verdict, the fixes
applied, and the next-step commands.
---
## Argument Resolution
**Resolve BEFORE loading anything.** Match the argument as a **substring** of the basenames of
`docs/plans/*PLAN--*.md`, excluding `--review-v*`, `--evidence-v*` and `--ledger` companions. A
24-character timestamp prefix is not typeable, so a unique slug fragment is the normal argument; the
full basename always works.
- **Exactly one match** → that is the plan.
- **More than one** → ⛔ STOP. Print every candidate basename and ask which. **NEVER guess.**
- **No match** → list `docs/plans/` and STOP.
**Continue Mode** (argument resolves to a plan): load it, summarize what was already decided, ask what
to adjust, update it with a changelog row, then review and complete it the same way a new plan is.
An update is not delivered before review — and it gets the same single pass a new plan gets, not an
extra one for having been revised.
**List Mode** (no argument): list the plans with their status and ask which to work on.
## Common Rationalizations (BLOCKED)
| Excuse | Reality |
|--------|---------|
| "I'll check docs/plans/ for the next number" | Timestamps. Nothing to look up |
| "Pasting the file body makes the build easier" | It makes the plan unreviewable. Name the change |
| "Global Constraints is empty, I'll drop it" | Write `None`. Absent is a question |
| "This F-block obviously doesn't need a test" | No proof, no coverage, reviewer blind |
| "Two matches, the newer one is obviously it" | STOP and ask. NEVER guess |
## Rules
ALWAYS:
- Take the plan timestamp from the clock
- Tag every F-block with its layer
- Derive the reviewer's `layer` from those tags
- Write `None` when a section is genuinely empty
NEVER:
- Deliver a plan the reviewer has not seen this session
- Invent a decision to clear a blocker
- Guess between two argument matches