From Z Skills
Draft a high-quality plan through iterative adversarial review: research, draft, review, devil's-advocate, refine — repeated until convergence. Output is a plan file ready for /run-plan execution.
How this skill is triggered — by the user, by Claude, or both
Slash command
/zs:draft-plan [output FILE] [rounds N] [auto] [brainstorm|quiz] <description...>[output FILE] [rounds N] [auto] [brainstorm|quiz] <description...>The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Produces high-quality plan documents through iterative adversarial
Produces high-quality plan documents through iterative adversarial
refinement. Multiple agents research, draft, review, poke holes, and refine
until the plan is solid enough to execute with /run-plan.
The insight: /run-plan executes a plan faithfully — so plan quality IS
output quality. Investing in adversarial plan refinement pays off massively
downstream. A weak plan executed perfectly is still a weak result.
Ultrathink throughout.
This skill internally dispatches reviewer + devil's-advocate + refiner sub-agents in parallel. It MUST run in a context that has the Agent (or Task) tool available.
Before doing any other work, verify your tool list contains Agent or Task. If neither is present, STOP and report:
ERROR: /draft-plan requires top-level Agent dispatch capability. This invocation is running as a subagent (no Agent tool — verified by inspecting your tool list). Subagents cannot dispatch sub-subagents (Anthropic design — https://code.claude.com/docs/en/sub-agents).
Re-invoke as one of:
- User slash command:
/draft-plan <description...>- Top-level
Skilltool:Skill(skill="draft-plan", args="<description...>")- Inline orchestration by a top-level Claude that has
AgentDo NOT continue. Single-agent inline degradation produces rubber-stamp findings without the adversarial diversity this skill's value depends on. The CLAUDE.md memory anchor
feedback_multi_agent_skills_top_level.mdis the recurring failure mode this preflight catches.
Do not proceed past this preflight without Agent access.
Before parsing arguments, ensure a worktree exists when main is protected.
Dispatch the canonical helper; if main_protected: true and the caller is
in main, the helper creates a worktree and prints its path on stdout
(empty string when no worktree is required). When non-empty, cd into it
and export ZSKILLS_PATHS_ROOT so downstream path-resolution anchors on
the worktree, not main:
if [ -n "${ZSH_VERSION:-}" ]; then setopt KSH_ARRAYS BASH_REMATCH SH_WORD_SPLIT 2>/dev/null || true; fi
MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
TOPLEVEL=$(git rev-parse --show-toplevel)
HELPER="$ZSKILLS_SKILLS_ROOT/create-worktree/scripts/ensure-worktree.sh"
# Caller-side install-integrity fallback (DA-R3-2): if /update-zskills
# Step 3 ran as per-file-diff and skipped the new file, the helper itself
# is missing. Emit an actionable exit-11 message instead of bash's
# `No such file or directory`.
if [ ! -x "$HELPER" ]; then
echo "draft-plan: ensure-worktree.sh missing at $HELPER — run /update-zskills to repair" >&2
exit 11
fi
# Resolve $OUTPUT_FILE and derive $TRACKING_ID before the worktree preamble
# so the helper's --pipeline-id / positional slug are non-empty. Mirrors
# the Arguments-section detection rule (#603 pattern): first $ARGUMENTS
# token ending in `.md` is the output path; resolve bare names via
# $ZSKILLS_PLANS_DIR. Phase 1's Tracking-fulfillment fence re-derives
# $TRACKING_ID idempotently from the same $OUTPUT_FILE.
if [ -z "${OUTPUT_FILE:-}" ]; then
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-paths.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-paths.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-paths.sh"
fi
for tok in $ARGUMENTS; do
case "$tok" in
*/*.md) OUTPUT_FILE="$tok"; break ;;
*.md) OUTPUT_FILE="$ZSKILLS_PLANS_DIR/$tok"; break ;;
esac
done
# Fall back to a timestamped default so downstream slug computation
# produces a stable, non-empty value rather than fail-closing.
: "${OUTPUT_FILE:=$ZSKILLS_PLANS_DIR/$(date +%Y%m%d-%H%M%S)_PLAN.md}"
fi
TRACKING_ID=$(basename "$OUTPUT_FILE" .md | tr '[:upper:]' '[:lower:]' | tr '_' '-')
WT_PATH=$(bash "$HELPER" \
--prefix draftplan \
--pipeline-id "draft-plan.${TRACKING_ID}" \
--allow-resume \
--purpose "draft-plan auto-worktree; isolate plan drafting from main" \
"${TRACKING_ID}")
RC=$?
if [ "$RC" -ne 0 ]; then
echo "ensure-worktree failed (rc=$RC) for /draft-plan" >&2
# rc=3 (poisoned branch): suggest `/create-worktree resume <slug>` or
# delete the poisoned branch. rc=11: helper install-integrity — run
# `/update-zskills` to repair. Other codes: see helper script header.
exit "$RC"
fi
if [ -n "$WT_PATH" ]; then
cd "$WT_PATH" || { echo "draft-plan: cd $WT_PATH failed" >&2; exit 1; }
# R3-1 path-resolution fix: re-anchor zskills-paths.sh under the
# worktree so subsequent $ZSKILLS_PLANS_DIR / $ZSKILLS_AUDIT_DIR /
# $ZSKILLS_ISSUES_DIR resolutions land in WT, not main. Export (not
# just set) so child shells / re-sourcing inherits it.
export ZSKILLS_PATHS_ROOT="$WT_PATH"
fi
# Empty $WT_PATH means no worktree was created (caller already in one OR
# main not protected); proceed in cwd. $ZSKILLS_PATHS_ROOT stays unset
# in that case, so paths fall back to $CLAUDE_PROJECT_DIR as before.
/draft-plan [output FILE] [rounds N] [auto] [brainstorm|quiz] <description...>
$ZSKILLS_PLANS_DIR/<slug-from-description>.md/land-pr to push the branch, open a PR,
monitor CI, and auto-merge. Without auto, the plan is committed in the
worktree but no PR is opened — caller must land manually. Mirrors the
auto token in /run-plan, /do, /fix-issues.Detection: scan $ARGUMENTS from the start for recognized patterns:
output followed by a path — explicit output file.md — output file (only when it's the
first argument, before the description starts). This avoids false
positives on description words like README.md or CLAUDE.md.
If the token contains /, use as-is; otherwise resolve via
$ZSKILLS_PLANS_DIR/<token> (sourcing
.claude/skills/update-zskills/scripts/zskills-paths.sh from the
orchestrator's bash fence; falls back to plans/ when config silent).rounds followed by a number — max review cyclesauto (whitespace-anchored, case-insensitive) — sets AUTO_FLAG=1
for Phase 6's /land-pr dispatchbrainstorm | quiz (a single mutually-exclusive steering selector,
recognized ONLY as a leading flag token — in the flag cluster before
the description begins, like output/rounds/auto, case-insensitive) —
sets STEERING_MODE to one of brainstorm or quiz (default: empty).
brainstorm loads the interactive brainstorm dialogue
(references/brainstorm.md) before Phase 1; quiz conducts an interactive
requirements interview before drafting by loading (references/quiz.md).
Both are pre-draft interactive interviews occupying the SAME steering seam,
so they are mutually exclusive — requesting both (in either order,
/draft-plan brainstorm quiz … or /draft-plan quiz brainstorm …) is a
fail-loud error (exit 2), not a silent mode drop (#936, #944). A
brainstorm/quiz appearing within the description text is part of
the description, NOT the flag — unlike auto, the selector is NOT detected
match-anywhere, so a description like "build a quiz app" or "Build a
brainstorm app for kids" does NOT trigger either mode (the leading-cluster
scan breaks at the first non-flag token; see the detection note below and
the Examples block) (#914). The exact-match case arms also mean
brainstorming/brainstormed/brainstorms/quizzes do NOT trigger.if [ -n "${ZSH_VERSION:-}" ]; then setopt KSH_ARRAYS BASH_REMATCH SH_WORD_SPLIT 2>/dev/null || true; fi
AUTO_FLAG=0
if [[ "$ARGUMENTS" =~ (^|[[:space:]])[aA][uU][tT][oO]($|[[:space:]]) ]]; then
AUTO_FLAG=1
fi
STEERING_MODE is detected by LEADING-FLAG recognition — NOT auto's
match-anywhere regex. brainstorm/quiz fire only when they appear
in the leading flag cluster, before the first description token. If a
description word "quiz"/"brainstorm" set the mode (the way a description
word "auto" trips AUTO_FLAG today — a pre-existing exposure NOT inherited
here), then an autonomously-dispatched call from /research-and-plan (which
passes the user description verbatim) or the /run-plan delegate
(/draft-plan plans/FOO.md <desc> auto) would hang the pipeline on an
interactive prompt with no human to answer. So the parse consumes the
leading cluster first and only then tests for the steering tokens. Tokenize
the leading cluster: consume, in any order, the output-file token
(either the literal output <path>, OR a bare leading *.md token),
rounds N (consuming the numeric value token after rounds, so the bare
N is not mis-read as the first description token), auto, brainstorm,
and quiz. The first token matching none of these begins the
description, and everything from there (including any later
quiz/brainstorm) is description, not a flag.
brainstorm and quiz are mutually exclusive — both are pre-draft
interactive interviews occupying the SAME steering seam (brainstorm makes
the post-research steering checkpoint skip, "user already steered in the
dialogue"; quiz REPLACES that same checkpoint with its interview loop,
branch (a) of the 3-way steering checkpoint below), and running both at once
is contradictory (two competing interviews, two separate
/tmp/draft-plan-{brainstorm,quiz}-$TRACKING_ID.md notes files both
claiming to be the pre-draft steering). Rather than represent-then-forbid
the illegal "both" state with two booleans, a single STEERING_MODE (one of
"", brainstorm, quiz) makes it unrepresentable: a set_steering
helper sets the mode the first time, and FAILS LOUD (exit 2) if a second,
DIFFERENT steering token is seen. Because both tokens are recognized by the
SAME leading-cluster scan (no token[0]-only anchor for brainstorm), the
conflict is detected regardless of order — /draft-plan brainstorm quiz X
and /draft-plan quiz brainstorm X BOTH exit 2 (#936, #944 — genuine
order-independence; the prior two-parser design only errored on the first
order). A repeated SAME token (brainstorm brainstorm X) is a harmless
no-op:
if [ -n "${ZSH_VERSION:-}" ]; then setopt KSH_ARRAYS BASH_REMATCH SH_WORD_SPLIT 2>/dev/null || true; fi
STEERING_MODE="" # "" | brainstorm | quiz — single mutually-exclusive selector
# set_steering MODE: set STEERING_MODE the first time; fail loud (exit 2) on
# a conflicting second mode; no-op on a repeat of the same mode. The illegal
# "both" state is unrepresentable, so the conflict is order-independent.
set_steering() {
if [ -z "$STEERING_MODE" ]; then
STEERING_MODE="$1"
elif [ "$STEERING_MODE" != "$1" ]; then
echo "ERROR: brainstorm and quiz modes are mutually exclusive" >&2
exit 2
fi
}
# Leading-flag scan: walk the tokens, consuming recognized flags in any
# order. Stop at the first token that is none of them — that begins the
# description. A `quiz`/`brainstorm` AFTER the description start is
# description text, never the flag. (Strip the description first; never match
# raw $ARGUMENTS match-anywhere — that is exactly auto's false-positive.)
expect_value=0 # 1 = the previous token was `output`/`rounds`; this one is its value
for tok in $ARGUMENTS; do
if [ "$expect_value" = "1" ]; then
# The value token after `output` (a path) or `rounds` (a number);
# consume it unconditionally and keep scanning the leading cluster.
expect_value=0
continue
fi
ltok=$(printf '%s' "$tok" | tr '[:upper:]' '[:lower:]')
case "$ltok" in
output) expect_value=1 ;; # output <path> — value consumed next iter
rounds) expect_value=1 ;; # rounds N — value consumed next iter
auto) ;; # recognized flag — consume, keep scanning
brainstorm) set_steering brainstorm ;; # leading brainstorm flag (#944)
quiz) set_steering quiz ;; # leading quiz flag
*.md) ;; # bare leading *.md output token
*/*.md) ;; # path-form leading *.md output token
*) break ;; # first description token — stop
esac
done
Examples:
/draft-plan Add dark mode to the editor/draft-plan THERMAL_PLAN.md Implement thermal domain → writes $ZSKILLS_PLANS_DIR/THERMAL_PLAN.md/draft-plan plans/THERMAL_PLAN.md Implement thermal domain → same/draft-plan output plans/THERMAL_PLAN.md rounds 5 Implement thermal domain/draft-plan rounds 5 Implement thermal domain with multi-domain coupling/draft-plan Fix the README.md formatting → description only, no output file detected/draft-plan brainstorm Add dark mode to the editor → STEERING_MODE=brainstorm (leading flag) — runs the interactive brainstorm dialogue first/draft-plan quiz Add dark mode to the editor → STEERING_MODE=quiz (leading flag) — runs the interactive requirements interview first/draft-plan output p.md quiz rounds 5 Add dark mode → STEERING_MODE=quiz (quiz is in the leading flag cluster, in any order with output/rounds)/draft-plan output p.md brainstorm Add dark mode → STEERING_MODE=brainstorm (brainstorm is in the leading flag cluster — composability parity with quiz; #944 — no longer token[0]-only)/draft-plan output p.md build a quiz app → STEERING_MODE empty — "quiz" is a description word, not the leading flag (NEGATIVE example; the selector is leading-only, never match-anywhere like auto)/draft-plan Build a brainstorm app for kids → STEERING_MODE empty — "brainstorm" is a description word; the leading-cluster scan breaks at the first non-flag token "Build" before it is reached (#914)/draft-plan add dark mode quiz → STEERING_MODE empty — a trailing quiz is description text, not the flag. This is the accepted ergonomic limitation of leading-only parsing: to enable a mode, lead with the flag (/draft-plan quiz add dark mode)./draft-plan brainstorm quiz Add dark mode → ERROR (exit 2) — both brainstorm and quiz are leading-cluster flags and the two modes are mutually exclusive (#936, #944). Fails loud instead of silently dropping one./draft-plan quiz brainstorm Add dark mode → ERROR (exit 2) — same conflict, OTHER order. Because both tokens are recognized by the same leading-cluster scan, the mutual-exclusion error is order-independent (#944 — the prior two-parser design errored only on the brainstorm quiz order and silently swallowed brainstorm here).If the output file already exists, read it first. The old plan IS research
input — it contains the original intent, structure, and possibly partial
progress. Tell the research agents: "An existing plan file exists at
<path>. Read it and incorporate its intent. This is a modernization,
not a blank-slate rewrite." The adversarial review should check that no
intent from the original was lost.
This handles the common case of modernizing old-format plans:
/draft-plan plans/OLD_PLAN.md Modernize with progress tracker and phases
brainstorm flag is present)If STEERING_MODE = brainstorm, Read references/brainstorm.md via the
Read tool and execute its dialogue loop now, before Phase 1 — UNLESS the notes file
/tmp/draft-plan-brainstorm-$TRACKING_ID.md already exists with status: ready (a prior,
completed dialogue — proceed straight to Phase 1 using it as the seed). If the notes file
exists with status: in-progress (a dialogue interrupted by compaction/crash), RESUME it from
its captured state rather than restarting. If STEERING_MODE != brainstorm, ignore the reference file
entirely and proceed straight to Phase 1.
quiz flag is present) — research sequencingWhen STEERING_MODE = quiz and running interactively (the same predicate as the
subagent-skip in "Post-research tracking" below — quiz never fires under a
subagent caller in practice, since the research-and-* callers don't pass
quiz), the conversational requirements interview runs FIRST, before any
research. The order is strictly: quiz interview → (on go-signal) the
existing Phase-1 research fan-out, seeded → Phase 2 draft. No research is
split, moved, or interleaved — the fan-out is simply gated behind the quiz
and seeded with the interview file. Concretely:
STEERING_MODE = quiz and the quiz
has not yet exited.STEERING_MODE = quiz that seam is reached with the
research fan-out still deferred, the directive fires, and the interview
runs to its explicit go-signal./tmp/draft-plan-quiz-$TRACKING_ID.md as priors — "here is what the
interview established with the user; validate and extend it, do not
re-derive it." Also hand the fan-out the interview file's deferred
open-questions list so the Codebase / Prior-art agents resolve them. This
is the same seeding shape brainstorm mode uses (injecting the notes path
into each research prompt) — inject the literal
/tmp/draft-plan-quiz-$TRACKING_ID.md path into the prompts, never a
re-derived one.When STEERING_MODE != quiz, research runs exactly as today — the full Phase-1
fan-out runs immediately, followed by the single-shot post-research
checkpoint. quiz.md is never read and no step.*.quiz marker is written.
The interview mechanics (loop state machine, question-style discipline, go-signal contract, persistence + recovery-read, transcript-capture distillation) live entirely in references/quiz.md; this SKILL.md only orders quiz-before-fan-out and passes the interview file in.
Determine the tracking ID: use the ID passed by the parent skill if this
is a delegated invocation, or derive from the output file slug if standalone
(e.g., plans/FEATURE_PLAN.md → feature-plan). Create the fulfillment
file in the MAIN repo. The pipeline subdir follows Option B layout: when
delegated, parent exports ZSKILLS_PIPELINE_ID via env and we write into
the parent's subdir; when standalone, we own our own subdir:
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
PIPELINE_ID="${ZSKILLS_PIPELINE_ID:-draft-plan.$TRACKING_ID}"
PIPELINE_ID=$(bash "$ZSKILLS_SKILLS_ROOT/create-worktree/scripts/sanitize-pipeline-id.sh" "$PIPELINE_ID")
[ -n "$PIPELINE_ID" ] || { echo "tracking: empty PIPELINE_ID — refusing flat write" >&2; exit 1; }
mkdir -p "$MAIN_ROOT/.zskills/tracking/$PIPELINE_ID"
printf 'skill: draft-plan\nid: %s\noutput: %s\nstatus: started\ndate: %s\n' \
"$TRACKING_ID" "$OUTPUT_FILE" "$(TZ="${TIMEZONE:-UTC}" date -Iseconds)" \
> "$MAIN_ROOT/.zskills/tracking/$PIPELINE_ID/fulfilled.draft-plan.$TRACKING_ID"
Dispatch multiple Explore agents in parallel to investigate the problem space. Each agent gets the full description and a specific research focus:
Brainstorm feed-forward. When STEERING_MODE = brainstorm, inject the literal
path /tmp/draft-plan-brainstorm-$TRACKING_ID.md into EACH research
agent's PROMPT, with an instruction to read it as the primary design
seed (the user already steered the design in the brainstorm dialogue).
Inject the path into the prompts — NOT into the research consolidation
file, which does not exist until after dispatch. The Codebase / Patterns /
Prior-art agents still run to ground the design against the repo; the
Domain agent's "what to build" work is largely pre-answered by the
brainstorm and may be reduced to verification. When STEERING_MODE != brainstorm,
dispatch the agents normally with no notes-file injection.
Codebase agent — find all relevant source files, understand current architecture, identify what exists and what needs to change. Map dependencies, shared infrastructure, and potential conflicts.
Patterns agent — read existing plans in plans/ for format and style
reference. Read CLAUDE.md for constraints (no external solvers, no
bundlers, etc.). Identify conventions and rules that the plan must respect.
Domain agent — research the specific technical domain. For solver work: understand the math, numerical methods, stability requirements. For UI work: understand the interaction model, accessibility, existing patterns. For infrastructure: understand the deployment, CI, hosting.
Prior art agent — check git history for related work, previous attempts, known issues. Read any existing plan files or progress docs that overlap with the description. Find relevant GitHub issues.
Consolidate the research into a single summary and write it to a file
(e.g., /tmp/draft-plan-research-<slug>.md). The <slug> comes from the
output filename if one was provided (e.g., FEATURE_EXPORT
→ /tmp/draft-plan-research-FEATURE_EXPORT.md). If no output
file was given, derive from the description. Do not rely on keeping the
research in memory — context compaction will degrade it across multiple
rounds of adversarial review. The file persists through all phases.
The summary should cover:
Similarly, write each round's review findings to files:
/tmp/draft-plan-review-<slug>-round-N.md — reviewer + devil's advocate findingsScope check — is this too big for one plan? After consolidating research, assess whether you can write a plan where every phase has specific, implementable work items and testable acceptance criteria, in roughly 6 or fewer phases. The question is not "can I list phases" but "can I spec each phase precisely enough that an implementing agent won't have to guess?"
Signs the task is too big for one plan:
Past failure: a Dashboard Blocks plan covered too much — phases were vague, the implementing agent took shortcuts, and each phase had to be broken into 5-10 sub-phases at execution time with no adversarial review.
Common pattern: tasks that add multiple block types alongside new engine
infrastructure (new solver, new domain, new codegen path) should
decompose into infrastructure-first plan + the repetitive per-block
additions via /do delegate phases after the engine lands.
If the task is too big, present this to the user:
This task is too broad for one well-specified plan. It decomposes into N sub-problems: [list them with one-line descriptions]. I recommend using
/research-and-planto handle the decomposition and draft focused sub-plans for each. Proceed?
On approval, invoke /research-and-plan with:
/tmp/draft-plan-research-<slug>.md)
Then exit — do not proceed to Phase 2. /research-and-plan takes
over from here.If the user says no (e.g., "just plan the first part"), narrow the scope per their feedback and proceed to Phase 2 with the focused scope.
After consolidating research into the summary file, create the research step marker:
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
PIPELINE_ID="${ZSKILLS_PIPELINE_ID:-draft-plan.$TRACKING_ID}"
PIPELINE_ID=$(bash "$ZSKILLS_SKILLS_ROOT/create-worktree/scripts/sanitize-pipeline-id.sh" "$PIPELINE_ID")
[ -n "$PIPELINE_ID" ] || { echo "tracking: empty PIPELINE_ID — refusing flat write" >&2; exit 1; }
printf 'completed: %s\n' "$(TZ="${TIMEZONE:-UTC}" date -Iseconds)" \
> "$MAIN_ROOT/.zskills/tracking/$PIPELINE_ID/step.draft-plan.$TRACKING_ID.research"
Skip this steering checkpoint when STEERING_MODE = brainstorm — the user
already steered the design, in depth, during the brainstorm dialogue, so
re-prompting here is redundant; proceed directly to Phase 2. (This is an
ADDITIVE condition; the existing "if running as a subagent, skip" branch
below is unchanged.)
This steering checkpoint is a 3-way branch. The branch sits AFTER the
scope-check decomposition exit above and BEFORE Phase 2:
(a) When STEERING_MODE = quiz and running interactively (the same predicate
as the subagent-skip in branch (c) below — interactive, not a subagent):
emit the conditional-load directive
Read references/quiz.md in full and follow its procedure end-to-end. Do not proceed until you have read that file.
then conduct the quiz loop per that file, exiting to Phase 2 only on the explicit go-signal. Note: per "Quiz mode" above, this branch is reached with the Phase-1 research fan-out still DEFERRED — the interview runs first, and the (seeded) fan-out runs only after the go-signal, before Phase 2.
(b) When STEERING_MODE != quiz (default), the existing single-shot checkpoint,
verbatim and unchanged:
Present the research summary to the user. If running interactively
(user invoked /draft-plan directly), wait for input:
Research complete. Summary written to
/tmp/draft-plan-research-<slug>.md. Here's the overview: [brief summary] [Scope check result — either "this fits in one plan" or the decomposition recommendation above]Anything to add, correct, or steer before I draft the plan?
Incorporate any user feedback, then immediately proceed to Phase 2. Do not stop here. The checkpoint is a pause for steering, not the end of the skill. After the user responds (even if they just say "looks good" or "continue"), move to Phase 2 without being asked again.
(c) If running as a subagent (dispatched by /research-and-plan or
/research-and-go), skip the user checkpoint — proceed directly to
Phase 2. The decomposition was already approved by the user in the
parent skill. (Quiz never reaches this branch in practice since the
research-and-* callers don't pass quiz, but the guard is the same
predicate and must remain — it is load-bearing for those callers.)
These steps run only when STEERING_MODE = quiz (i.e., branch (a) above ran the
quiz to its go-signal). A normal (non-quiz) run does NONE of this.
Transcript-capture merge (R5). After the quiz exits, merge the
distilled requirements into the existing research summary file
/tmp/draft-plan-research-<slug>.md as confirmed requirements (so each
seeded fan-out agent treats the interview-established intent as a given to
validate and extend), and — in Phase 6 — add a "Requirements captured
via quiz" subsection to the plan's ## Plan Quality section. Follow the
transcript-capture distillation spec in
references/quiz.md ("Transcript capture — distill,
don't dump"); do NOT duplicate that spec here — distill decisions plus
rationale, never a raw chat dump.
Optional step.*.quiz tracking marker (convention symmetry with the
research/review/finalize markers; informational only, NOT hook-gated).
Gated behind STEERING_MODE = quiz so a normal run never emits it. This clones the
FULL research-marker fence above — including the MAIN_ROOT +
PIPELINE_ID derivation and the pipeline-id sanitize call — NOT just
the printf, to avoid an empty-PIPELINE_ID flat-marker write:
if [ "${STEERING_MODE:-}" = "quiz" ]; then
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
PIPELINE_ID="${ZSKILLS_PIPELINE_ID:-draft-plan.$TRACKING_ID}"
PIPELINE_ID=$(bash "$ZSKILLS_SKILLS_ROOT/create-worktree/scripts/sanitize-pipeline-id.sh" "$PIPELINE_ID")
[ -n "$PIPELINE_ID" ] || { echo "tracking: empty PIPELINE_ID — refusing flat write" >&2; exit 1; }
printf 'completed: %s\n' "$(TZ="${TIMEZONE:-UTC}" date -Iseconds)" \
> "$MAIN_ROOT/.zskills/tracking/$PIPELINE_ID/step.draft-plan.$TRACKING_ID.quiz"
fi
A single agent produces the initial plan based on the consolidated research
and user feedback. The plan MUST follow a format that /run-plan can
consume:
Every plan file MUST begin with YAML frontmatter so that /run-plan can
track metadata (especially which GitHub issue to close on completion):
---
issue: N # GitHub issue number (omit if not created from an issue)
title: Plan Title
created: YYYY-MM-DD
status: active # active | complete
---
Frontmatter rules:
issue — include ONLY when /draft-plan is invoked from /fix-issues plan
(or any context that supplies a GitHub issue number). When invoked standalone
with no issue context, omit the issue: field entirely.title — the plan title. Because the frontmatter is the authoritative
reference, do NOT duplicate the issue number in the # Plan: <Title> heading.created — use the current date (YYYY-MM-DD).status — always starts as active. /run-plan updates this to
complete when all phases finish, which also signals it to close the
linked GitHub issue (if one is present).Phase heading format. Use ## Phase <N> — <Name> with an em-dash
(—) as the canonical separator. The aggregator (collect.py) also
accepts en-dash (–), colon (:), and hyphen (-) so hand-authored
plans are not silently demoted to Reference (issue #183), but every
template in this skill emits em-dash for consistency.
# Plan: <Title>
## Overview
Brief description of what this plan accomplishes and why.
## Progress Tracker
| Phase | Status | Commit | Notes |
|-------|--------|--------|-------|
| 1 — <name> | ⬚ | | |
| 2 — <name> | ⬚ | | |
| ... | ⬚ | | |
## Phase 1 — <Name>
### Goal
One-sentence summary of what this phase accomplishes.
### Work Items
- [ ] Item 1 — specific, implementable description
- [ ] Item 2 — ...
### Design & Constraints
Verbatim specs: formulas, state equations, algorithms, data structures,
API contracts. Everything an implementing agent needs to write the code
without guessing. If there are formulas, write the formulas. If there are
constraints, state them explicitly.
### Acceptance Criteria
- [ ] Criterion 1 — specific, testable condition
- [ ] Criterion 2 — e.g., "test free vibration x(t) = A cos(ωt) within 1e-6"
### Dependencies
Which phases must be complete before this one can start.
## Phase 2 — <Name>
...
Dispatch two agents simultaneously against the current draft. Each gets the full draft AND the research summary from Phase 1.
Checks for completeness, correctness, and feasibility:
/run-plan be able to parse phases and status from this format?Evidence discipline. When a finding makes an empirical claim (a
file, function, tool, or library has/lacks property X), include a
concrete Verification: line — the exact file:line, grep, schema
quote, or command output that reproduces the evidence. The refiner will
re-run these checks before acting. Structural/judgment findings don't
need a reproducer but should say Verification: judgment — no verifiable anchor explicitly. Never write an empirical-sounding claim without
something the refiner can independently re-check.
Genuinely adversarial — tries to find ways the plan will fail:
The devil's advocate must produce specific, actionable findings — not generic concerns. "Phase 3 might be complex" is useless. "Phase 3 says 'implement the DAE solver' but doesn't specify the index reduction method, matrix factorization approach, or convergence criteria — the agent will have to guess all three" is actionable.
The same evidence discipline from the reviewer section applies:
every empirical claim needs a Verification: line. The devil's advocate
is especially prone to generating plausible-sounding-but-false claims
because its job is pattern-generating failure modes, not verifying them.
Discipline is load-bearing here — the refiner will re-check, and claims
whose evidence doesn't reproduce will not drive fixes.
After both reviewer and devil's advocate agents return their findings, create the review step marker:
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
PIPELINE_ID="${ZSKILLS_PIPELINE_ID:-draft-plan.$TRACKING_ID}"
PIPELINE_ID=$(bash "$ZSKILLS_SKILLS_ROOT/create-worktree/scripts/sanitize-pipeline-id.sh" "$PIPELINE_ID")
# $ROUND is the orchestrator-maintained loop counter (Phase 3 review/refine
# iteration). Default to 1 if unset so the marker is informative even on the
# first round before any explicit counter was exported.
ROUND="${ROUND:-1}"
[ -n "$PIPELINE_ID" ] || { echo "tracking: empty PIPELINE_ID — refusing flat write" >&2; exit 1; }
printf 'round: %s\ncompleted: %s\n' "$ROUND" "$(TZ="${TIMEZONE:-UTC}" date -Iseconds)" \
> "$MAIN_ROOT/.zskills/tracking/$PIPELINE_ID/step.draft-plan.$TRACKING_ID.review"
A single agent receives:
Before touching the draft, the refiner must attempt to reproduce the cited evidence for each finding. The reviewer/DA produce hypotheses; the refiner is the gate that tests them against reality.
For each finding with an empirical claim:
Verification: line and run its check (Read the file, run
the grep, check the schema, run the command).Judgment findings (marked Verification: judgment) skip step 1 and go
straight to fix-or-justify on merit.
Why this exists. Devil's-advocate findings are by role generated to be plausible failure modes, not verified truths. Past failure: a DA claimed a tool's input lacked a field, citing a tangentially-related file; the refiner accepted and rewrote a whole phase on a false premise. A 30-second check of the tool schema would have caught it. Verify-before-fix is the gate that turns "the DA said so" into "I checked."
It produces an improved draft that addresses every finding. For each finding, it must either:
It may NOT ignore findings or defer them. The refiner's output is the new draft for the next round.
The refiner's output must include a disposition table listing each finding with an Evidence column (Verified / Not reproduced / No anchor / Judgment) and the disposition (Fixed / Justified + reason).
Convergence is the orchestrator's judgment, not the refiner's self-call. Do NOT accept "CONVERGED" from the refiner agent as authoritative — the refiner just refined; it is biased toward declaring its own work done. This is a recurring failure mode in practice.
After each round of review + refinement:
Count remaining substantive issues from the refiner's disposition table (Justified-not-fixed entries plus any gaps the refinement introduced).
Check convergence:
Track round history — keep a log of each round's findings and resolutions. This goes into the final plan's quality section.
Add a Plan Quality section to the end of the plan:
## Plan Quality
**Drafting process:** /draft-plan with N rounds of adversarial review
**Convergence:** [Converged at round M / Max rounds reached]
**Remaining concerns:** [None / List of unresolved issues]
### Round History
| Round | Reviewer Findings | Devil's Advocate Findings | Resolved |
|-------|-------------------|---------------------------|----------|
| 1 | 5 issues | 7 issues | 12/12 |
| 2 | 2 issues | 3 issues | 5/5 |
| 3 | 0 issues | 0 issues | Converged|
When STEERING_MODE = quiz, also add a "Requirements captured via quiz"
subsection to this Plan Quality section — a short record (a few lines)
noting the requirements were elicited through a quiz interview and listing
the key decisions/assumptions confirmed. This documents how the plan's
requirements were grounded without reproducing the conversation. Produce it
from the transcript-capture distillation per
references/quiz.md ("Transcript capture — distill,
don't dump"); distill, do NOT dump the raw chat. When STEERING_MODE != quiz, omit
this subsection entirely.
Write the plan file to the output path. The user can't review what they can't read — plans are often too large to meaningfully summarize in chat. Write first, then let the user read the actual file.
Auto-commit the plan file (if in a worktree). When $TOPLEVEL is
a worktree (not main), stage and commit $OUTPUT_FILE so the plan is
captured on the feature branch rather than left as a dirty file in
main. This is the file-write-time backstop that pairs with the
preamble's structural isolation:
# Self-contained; belt-and-suspenders cd in case WT_PATH was set above.
[ -n "${WT_PATH:-}" ] && cd "$WT_PATH" 2>/dev/null || true
MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
TOPLEVEL=$(git rev-parse --show-toplevel)
if [ "$TOPLEVEL" != "$MAIN_ROOT" ]; then
# Re-source path-config under the worktree root so $ZSKILLS_PLANS_DIR
# etc resolve to TOPLEVEL/... rather than $CLAUDE_PROJECT_DIR/... — R3-1.
# Belt-and-suspenders: the preamble already exported this, but the
# fence re-exports in case the preamble code path was bypassed
# (e.g., caller-in-worktree, where WT_PATH was empty but TOPLEVEL is
# already a worktree — we want paths anchored on TOPLEVEL).
export ZSKILLS_PATHS_ROOT="$TOPLEVEL"
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-paths.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-paths.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-paths.sh"
fi
# R3-1 path-remap: if $OUTPUT_FILE was computed BEFORE the preamble set
# $ZSKILLS_PATHS_ROOT (e.g., a stale value cached in the caller's
# argument-resolution block), it may still be MAIN-anchored. Re-anchor
# to TOPLEVEL. This is the load-bearing remap.
case "$OUTPUT_FILE" in
"$MAIN_ROOT"/*) OUTPUT_FILE="$TOPLEVEL/${OUTPUT_FILE#"$MAIN_ROOT/"}" ;;
esac
# Portable FILE_REL (DA-R2-1, R3-6: adapted from warn-config-drift.sh:181-203).
FILE_REL=""
if FILE_REL=$(realpath --relative-to="$TOPLEVEL" "$OUTPUT_FILE" 2>/dev/null) \
&& [ -n "$FILE_REL" ]; then
case "$FILE_REL" in /*) FILE_REL="" ;; esac
fi
if [ -z "$FILE_REL" ]; then
ABS_FILE=$(cd "$(dirname "$OUTPUT_FILE")" 2>/dev/null && pwd)/$(basename "$OUTPUT_FILE")
case "$ABS_FILE" in
"$TOPLEVEL"/*) FILE_REL="${ABS_FILE#"$TOPLEVEL"/}" ;;
*) echo "draft-plan: cannot normalize $OUTPUT_FILE vs $TOPLEVEL" >&2; exit 1 ;;
esac
fi
# Out-of-tree guard (DA-R2-5). After the path-remap above, FILE_REL
# should never be `../*` for legitimate inputs; if it is, fail loud
# (something is wrong with the caller's resolution).
case "$FILE_REL" in
/*|../*) echo "draft-plan: $OUTPUT_FILE is outside worktree $TOPLEVEL" >&2; exit 1 ;;
esac
git -C "$TOPLEVEL" add "$FILE_REL"
STAGED=$(git -C "$TOPLEVEL" diff --cached --name-only)
if [ "$STAGED" != "$FILE_REL" ]; then
echo "draft-plan: unexpected staged set: $STAGED (expected $FILE_REL only)" >&2
exit 1
fi
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
# Per-skill commit subject (R2-F8: case-stmt, not table cell).
BASE=$(basename "$FILE_REL" .md)
case "draft-plan" in
draft-plan) COMMIT_MSG_SUBJECT="docs(plans): draft $BASE" ;;
refine-plan) COMMIT_MSG_SUBJECT="docs(plans): refine $BASE" ;;
draft-tests) COMMIT_MSG_SUBJECT="docs(tests): draft test specs for $BASE" ;;
esac
if [ -n "$COMMIT_CO_AUTHOR" ]; then
git -C "$TOPLEVEL" commit --trailer "Co-Authored-By: $COMMIT_CO_AUTHOR" -m "$COMMIT_MSG_SUBJECT"
else
git -C "$TOPLEVEL" commit -m "$COMMIT_MSG_SUBJECT"
fi
fi
Auto-land via /land-pr (when auto was passed). Issue #581: in
projects with execution.landing: pr + main_protected: true, the
plan file is committed in the worktree (above) but never reaches main
without a /land-pr dispatch. When the user passed the auto
positional token, dispatch /land-pr so the branch pushes, a PR
opens, CI runs, and auto-merge lands the plan on main — making
/draft-plan plans/X.md auto && /run-plan plans/X.md auto work end
to end. Without auto, the worktree commit stands and the caller
lands manually.
if [ -n "${ZSH_VERSION:-}" ]; then setopt KSH_ARRAYS BASH_REMATCH SH_WORD_SPLIT 2>/dev/null || true; fi
if [ "${AUTO_FLAG:-0}" = "1" ] && [ "$TOPLEVEL" != "$MAIN_ROOT" ]; then
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
BRANCH_NAME=$(git -C "$TOPLEVEL" rev-parse --abbrev-ref HEAD)
SLUG="$TRACKING_ID"
PR_TITLE="docs(plans): draft $(basename "$OUTPUT_FILE" .md) via /draft-plan"
BODY_FILE="/tmp/pr-body-draft-plan-${SLUG}-$$.md"
RESULT_FILE="/tmp/land-pr-result-draft-plan-${SLUG}-$$.txt"
cat > "$BODY_FILE" <<BODY
`/draft-plan` produced a plan file at `$OUTPUT_FILE` via $ROUND round(s) of adversarial review (reviewer + devil's-advocate + refiner).
See the plan's `## Plan Quality` section for round history and remaining concerns.
Plan file committed on the draftplan branch (worktree-isolated)
No functional tests — this PR ships a plan document only; CI will run skill-conformance / hook / fixture suites BODY LAND_ARGS="--branch=$BRANCH_NAME --title="$PR_TITLE" --body-file=$BODY_FILE --result-file=$RESULT_FILE --landed-source=draft-plan --worktree-path=$TOPLEVEL --tracking-id=draft-plan.$SLUG --auto"
source). See skills/land-pr/references/caller-loop-pattern.md.if [ -f "$RESULT_FILE" ]; then # zsh portability (#1155): assoc subscripts must be UNQUOTED — zsh uses # the subscript text verbatim, so LP["$KEY"] and ${LP[STATUS]} address # different keys. Keep assignment and lookup styles consistent-unquoted. declare -A LP while IFS='=' read -r KEY VALUE; do case "$KEY" in STATUS|PR_URL|PR_NUMBER|CI_STATUS|PR_STATE|REASON) LP[$KEY]="$VALUE" ;; "") ;; *) ;; # unknown keys ignored (forward-compat) esac done < "$RESULT_FILE" echo "/land-pr: STATUS=${LP[STATUS]:-?} CI_STATUS=${LP[CI_STATUS]:-?} PR=${LP[PR_URL]:-none}" >&2 rm -f "$RESULT_FILE" else echo "WARN: /draft-plan: /land-pr produced no result file at $RESULT_FILE" >&2 fi fi
Plan index — do not touch. The plan index is regenerated from
source-of-truth by /plans rebuild and auto-refreshed by /plans
Mode: Show when the source has changed. No manual update needed
here.
Present the result:
Plan drafted in N rounds (converged / max rounds reached). [Remaining concerns if any]
Written to
plans/THERMAL_PLAN.md— open it up and let me know what to change.Execute with:
/run-plan plans/THERMAL_PLAN.mdOr with scheduling:/run-plan plans/THERMAL_PLAN.md auto every 4h now
After writing the plan file, create the finalize step marker and update the fulfillment file to complete:
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
MAIN_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." && pwd)
PIPELINE_ID="${ZSKILLS_PIPELINE_ID:-draft-plan.$TRACKING_ID}"
PIPELINE_ID=$(bash "$ZSKILLS_SKILLS_ROOT/create-worktree/scripts/sanitize-pipeline-id.sh" "$PIPELINE_ID")
[ -n "$PIPELINE_ID" ] || { echo "tracking: empty PIPELINE_ID — refusing flat write" >&2; exit 1; }
printf 'completed: %s\n' "$(TZ="${TIMEZONE:-UTC}" date -Iseconds)" \
> "$MAIN_ROOT/.zskills/tracking/$PIPELINE_ID/step.draft-plan.$TRACKING_ID.finalize"
printf 'skill: draft-plan\nid: %s\noutput: %s\nstatus: complete\ndate: %s\n' \
"$TRACKING_ID" "$OUTPUT_FILE" "$(TZ="${TIMEZONE:-UTC}" date -Iseconds)" \
> "$MAIN_ROOT/.zskills/tracking/$PIPELINE_ID/fulfilled.draft-plan.$TRACKING_ID"
/run-plan hits them at
implementation time. A comfortable review is a useless review./run-plan./run-plan needs it to track status.
Start all phases as ⬚ (not started).npx claudepluginhub zeveck/zskills-dev --plugin zsProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.