The fixed planning methodology for a capable model. Make every design decision grounded in a bundled wiki page (recording a decision-to-page map), then decompose the work into ordered, self-contained tasks, each naming the exact wiki pages that govern it. Runs as loop-implement step 2.
Installs into .claude/skills of the current project.
Are you the author of Wiki Plan?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/choiyounggi-wiki-plan)
---
name: wiki-plan
argument-hint: "[what to build]"
description: The fixed planning methodology for a capable model. Make every design decision grounded in a bundled wiki page (recording a decision-to-page map), then decompose the work into ordered, self-contained tasks, each naming the exact wiki pages that govern it. Runs as loop-implement step 2.
---
# Plan — decompose work into small-model-executable tasks
> **In the dev-loop plugin.** The wiki is bundled with the plugin, not in the
> user's project. Resolve every `INDEX.md`, `wiki/<...>.md`, `AGENTS.md`, and
> `templates/` path in this skill against **`${CLAUDE_PLUGIN_ROOT}/`** (the
> installed plugin root), NOT the current working directory. Read the wiki from
> there; write plan output (`plans/<feature>/`) into the user's project as
> described in Phase A/B/C below.
>
> **How this plugs into the loop.** `loop-implement` calls this skill as its
> **step 2 (Plan)** — mandatory, and the call site never changes. Internally
> this runs three gated phases — **Phase A (Analyze)** → **gate-A** → **Phase B
> (Design)** → **gate-B** → **Phase C (Decompose)** — that produce, in order,
> `analysis.md`, `design.md` (+ `review-verdict.md`), then the wiki-grounded
> `plan.md` and one `tasks/NN-*.md` per task naming the exact wiki pages it
> needs. **`loop-implement` is the single implementer** — it then executes
> those tasks *in order*, loading each task's named pages and running the
> verification loop (Red → Green → run → audit) per task. (`orchestrate` uses
> the same output to fan tasks across sessions, each session running
> `loop-implement`.) Every decision must cite a `wiki/` page or be marked
> `[no-wiki]`.
You are the planner. The implementer will be a small model with no memory of this
planning session. Its entire world per task = one task file + the wiki pages that
file names + the artifacts earlier tasks produced. **Every decision you leave
unmade becomes the small model's guess** — and guesses are where errors enter.
## Rule zero
Decisions live in the plan; execution lives in the tasks. If writing a task makes
you type "choose", "decide", "as appropriate", or "either" — stop, make that
decision now, and write the decision into the task.
## Lite mode (skip full A/B when it would be theater)
Before Phase A, check whether this task qualifies for lite mode — all three
conditions must hold, and all three are machine-checkable, so this is never a
judgment call:
- expected `## Size verdict` (see Phase C step 5) is `small`
- zero `[no-wiki]` decisions expected
- the feature touches no pinned file, no security-sensitive surface, no
migration
If any condition fails, run full Phase A → gate-A → Phase B → gate-B → Phase C.
If all three hold: write a single `analysis.md` with A1/A2 abbreviated (the
`## Requirements` table and `## Ground truth` stay — the baseline pin and the
`Affected files`/`Constraints` evidence are never optional), skip A3/A4's full
treatment, and skip the `plan-reviewer` call entirely. Still emit gate-A and
gate-B (`plan-gate.sh emit` + `gate-check.sh --run`, same as full mode below) —
lite mode changes what you write, not whether the gates run. Because
`research-evidenced` and `reviewer-verdict` have nothing to check in lite mode,
publicly abandon them on the ledger instead of leaving them unmet or deleting
them:
```
ABANDON: research-evidenced lite mode (small, no no-wiki, no pins)
ABANDON: reviewer-verdict lite mode (small, no no-wiki, no pins)
```
A quiet skip is never allowed — every abandonment must appear on the ledger and
in the eventual task report's `GATES:` line. (This is the same abandonment
mechanism `templates/gates.md` already uses.) gaps-emitted is never abandoned:
it runs in lite mode too and simply records nothing when the [no-wiki] count
is the zero the lite verdict assumed. `lint-surveyed` is never abandoned in
lite mode either: it is one format check, and Phase C needs the recorded
command.
## Phase A — Analyze (produces `plans/<feature>/analysis.md`)
Copy `${CLAUDE_PLUGIN_ROOT}/templates/analysis.md` to `plans/<feature>/analysis.md`
and fill it in. Its section headers are parsed by `plan-gate.sh` — keep them
exactly as the template has them.
**A1. Requirements + acceptance examples** (Example Mapping: Rule / Concrete
example / Open question). Start every Rule cell with its id — `R1: ...`,
`R2: ...` — because Phase B Decision rows and Phase C `covers:` lines cite it. While a question is unresolved, mark it literally
`OPEN: <question>` in the Open question cell — the gate fails while any `OPEN:`
token remains, so leaving a question unresolved is what blocks entry to Phase B
(Definition of Ready), not a missing checkbox.
**A2. Ground truth** — evidence, not claims:
- `Baseline: <test command> -> rc=<n>, HEAD <sha>, git status <clean|dirty>` —
record one command that can be copy-pasted and re-run as-is; gate-A re-runs
this exact command.
- `Lint: <command> -> rc=<n>` — one bullet per lint or typecheck command the
project runs (package.json scripts named lint, typecheck, type-check or
check; Makefile lint targets; CI workflow steps that run a linter or type
checker; the `verify` role when it names one), run once now to record its
rc — or one `Lint: none — checked: <search command>` bullet when there is
none. gate-A's `lint-surveyed` checks this format only and never re-runs
the command.
- `### Affected files` — every bullet needs an `evidence:` token backed by a
real search (`<path> — evidence: <search command> -> <n> hits`). A
code-graph hit (`explore` = graphify) may be cited only in the same bullet
as that search, as a lead: `<path> — evidence: graphify explain <Symbol> ->
<N> connections; <search command> -> <n> hits` — the graph chooses where to
look, the search decides what is true
(`wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`).
- `Constraints` — every pinned file, protected span, and CI requirement this
feature touches, each with the command used to check it; if none apply, say
so explicitly (an empty section fails the gate).
**A3. Spikes** — timebox investigation of any load-bearing unknown (XP spike).
Record what was discovered in `## Spikes`. A remaining unknown that blocks
Phase B is escalated to the requester now — never carried forward as a
placeholder.
**A4. Research** (`research` role — resolution order and fallback owned by
`loop-implement`'s Tool profile section, not repeated here) — confirm this
feature's domain has no known best-practice/pitfall you're missing: at least
one search, logged in `## Research` as `Query | Source | Applied`, or the
literal no-useful-results line the template shows. Wiki pages take priority
over research — research targets `[no-wiki]` territory and Spikes. If no
search tool is reachable at all, `ABANDON` the `research-evidenced` gate in the
open (never a silent skip).
**gate-A** — once `analysis.md` is complete:
```
sh ${CLAUDE_PLUGIN_ROOT}/skills/wiki-plan/scripts/plan-gate.sh emit A plans/<feature> .dev-loop/gates/plan-A-<feature>.md
sh ${CLAUDE_PLUGIN_ROOT}/skills/loop-implement/scripts/gate-check.sh --run .dev-loop/gates/plan-A-<feature>.md
```
Gate ids: `baseline-tests-ran`, `affected-files-evidenced`,
`open-questions-resolved`, `constraints-surveyed`, `lint-surveyed`,
`research-evidenced`. Exit 0
(every gate MET or explicitly ABANDONed) before entering Phase B; a failing
gate means fixing `analysis.md`, not editing the gate.
## Phase B — Design (produces `plans/<feature>/design.md`)
Copy `${CLAUDE_PLUGIN_ROOT}/templates/design-doc.md` to `plans/<feature>/design.md`
(note the filename changes from `design-doc.md` to `design.md`). Its `## Decisions`
table now has six columns:
```markdown
| # | Decision | Choice | Wiki basis | Rejected alternative | Testability |
```
**Wiki routing sweep** — read the wiki's `INDEX.md`, then the `index.md` of
every domain the work touches. When `wiki-local/index.md` exists at the project
root, read it after the bundled domain indexes and cite local pages the same
way. For each design decision, find the page that owns it (match "load when"
lines), apply its directives now, and record the decision + page. `Wiki basis`
is a repo-relative path to a page that actually exists in either layer —
`wiki/<domain>/<category>/<page>.md` in the bundled wiki or
`wiki-local/<domain>/<category>/<page>.md` in the project's local layer — not a
page id — or the literal `[no-wiki]` (gate-B's gaps-emitted gate records each
such row as a log.md gap line and an insight-queue candidate for
knowledge-flush — nothing to write by hand); gate-B resolves every
non-`[no-wiki]` path (bundled paths under the wiki root, `wiki-local/` paths
under the project root derived from the plan dir) and fails on any miss.
Example, for a login feature:
| # | Decision | Choice | Wiki basis | Rejected alternative | Testability |
|---|----------|--------|------------|----------------------|-------------|
| D1 | PK type for users | UUIDv7, app-generated | `wiki/databases/schema-design/primary-key-choice.md` | Auto-increment int — leaks row count, harder to shard | any insert path missing an explicit id; covers R1 |
| D2 | Auth mechanism | Session cookie, not token | `wiki/security/authn/session-vs-token.md` | JWT — needless revocation complexity for this scale | session-fixation / logout test; covers R2 |
| D3 | Re-signup after delete | Soft-delete + partial unique index | `wiki/databases/schema-design/soft-delete.md`, `wiki/databases/schema-design/partial-and-expression-indexes.md` | Hard delete — loses audit trail | re-signup integration test; covers R3 |
`Testability` names the test/gate that would catch this decision being wrong —
never leave it blank; a row missing any of the six cells fails
`decision-rows-complete`.
Every row also names the analysis.md Rule ids it covers — `covers R1, R3`, or a
range like `R4-R6` — in any cell, usually `Testability`. A Rule named by no row
was the most frequent blocking plan-reviewer finding, so `requirements-covered`
checks it mechanically: every `R<n>` in `## Requirements` must appear in some
`## Decisions` data row.
**Semantic candidate check** — after the routing sweep, run the bundled-wiki
vector index as a fail-open second path: once for the task as a whole (a
query built from the design's central goal sentence), and once more for each
decision routing left at `[no-wiki]`. Call the MCP tool when present
(`wiki_search(query, k=5, domain?)` / `wiki_page(page_id)` from the
`dev-loop-wiki` server); from a Bash-only context with no MCP tool (the
`task-planner` agent, any subagent), call the CLI form instead —
`bash ${CLAUDE_PLUGIN_ROOT}/scripts/wiki-mcp-launch.sh index search --query
"<text>" --k 5 --json` — resolving `${CLAUDE_PLUGIN_ROOT}` exactly as this
skill's opening note above already resolves it, not a second rule. Both
forms return the same JSON row shape (`page_id`, `path`, `section`,
`snippet`, `score`, `confidence`, `last_verified`). Example:
`bash ${CLAUDE_PLUGIN_ROOT}/scripts/wiki-mcp-launch.sh index search --query
"retry a failing GitHub API call with exponential backoff" --k 5 --json`
returns hits including `backend-common-reliability-timeouts-and-retries` —
open that page and confirm its "When this applies" matches before citing it.
When the `wiki_search` tool is absent from the session or returns an empty list, continue exactly as this step read before the tool existed.
Every hit is a **candidate, never an adoption**: open the hit's own page
(`wiki_page(page_id)`, or Read the JSON row's `path` directly) and read its
"When this applies" section — never re-read the domain `index.md` to
confirm, that cost was already paid by the routing sweep. Adopt the page
into `Wiki basis` only when that section's trigger text describes the same
situation as the decision — never on score alone, and no numeric score
floor gates this step (the 50-case calibration set in
tests/fixtures/wiki-retrieval-calibration.json, run with wiki-index.py eval
--report, found no separable floor: unrelated pages score inside the same
range true hits occupy). A hit whose
trigger does not match is left unadopted: record it as one bullet per
checked decision (`<page id> — <one-line reason it did not match>`) under a
new `### Considered, not adopted` subsection placed directly below the
`## Decisions` table in `design.md` — plain bullets, not table rows, so they
never feed `decision-rows-complete`. Only after this check finds no
matching page does the decision's `Wiki basis` cell take the literal
`[no-wiki]`.
**Pre-review check** — before every `plan-reviewer` call, run
`sh ${CLAUDE_PLUGIN_ROOT}/skills/wiki-plan/scripts/plan-gate.sh check requirements-covered plans/<feature>`.
`fail` lists the uncovered Rules on stderr: fix `design.md` until it prints `ok`
instead of spending a reviewer round on them. This check is not a reviewer call
and does not count toward the 3-call bound.
**Independent review** — call the `plan-reviewer` subagent (Agent tool) with:
the `analysis.md` path (including its `## Research` section), the `design.md`
path, the requester's original goal text, and the wiki root
(`${CLAUDE_PLUGIN_ROOT}`). It is read-only, fresh-context, and returns
`VERDICT: PASS | FAIL` + `FINDINGS:` + `SUMMARY:`. Record its full output under
`design.md`'s `## Review`; if `VERDICT: PASS`, copy just the `VERDICT: PASS`
line into `plans/<feature>/review-verdict.md` (gate-B reads that file, not the
`## Review` section). If `VERDICT: FAIL`, resolve every `blocking` finding and
re-call — bounded at 3 total calls; a 3rd `FAIL` is STOP + escalate to the
requester, not a forced PASS.
**gate-B**:
```
sh ${CLAUDE_PLUGIN_ROOT}/skills/wiki-plan/scripts/plan-gate.sh emit B plans/<feature> .dev-loop/gates/plan-B-<feature>.md
sh ${CLAUDE_PLUGIN_ROOT}/skills/loop-implement/scripts/gate-check.sh --run .dev-loop/gates/plan-B-<feature>.md
```
Gate ids: `groundings-exist`, `decision-rows-complete`, `requirements-covered`, `reviewer-verdict`, `gaps-emitted`.
Exit 0 before entering Phase C.
## Phase C — Decompose (produces `plans/<feature>/plan.md` + `tasks/NN-*.md`)
3. **Order by dependency, not by layer preference.** Data before code that uses
it, contracts before consumers, server before client integration:
schema/migrations → server core (domain logic) → server API surface → client →
integration/e2e verification. Two tasks with no dependency edge may note
`parallel-ok`.
4. **Cut tasks to small-model size.** Every task must satisfy ALL of:
| Criterion | Bound |
|-----------|-------|
| One concern | A reader can title it without "and" |
| Files touched | ≤ 3 created/modified |
| Wiki context | ≤ 4 pages needed |
| Verifiable | Has a command or concrete check that proves it done |
| Self-contained | Doable from the task file + named wiki pages + named prior artifacts — no reading of other task files |
A task that fails a bound gets split; a task too trivial to verify gets merged
into its neighbor.
5. **Write the files** (into the target project, `plans/<feature>/`):
`plans/<feature>/plan.md`:
```markdown
# <feature>
Goal: <1-3 lines + acceptance criteria>
Stack: <language/framework/DB — exact versions where they matter>
## Decisions
| # | Decision | Choice | Wiki basis |
|---|----------|--------|------------|
| D1 | PK type for users | UUIDv7, app-generated | wiki/databases/schema-design/primary-key-choice.md |
## Size verdict
size: small | medium | large
## Task order
| Task | Depends on | Parallel-ok |
```
**`## Size verdict` is REQUIRED, not optional.** Judge it against the step-4
bounds table, aggregated over the whole plan: `small` = ≤3 tasks, `medium` =
≤8 tasks, `large` = >8 tasks OR any single task still breaking a step-4 bound
after you've tried to split it. When the verdict is `large`, the section MUST
also carry a recommended pre-dispatch split: per piece, its `files` and
`outputs` — the exact fields `graph-add.sh` validates, so the split can be
applied as independent graph nodes without another round-trip through you.
`plans/<feature>/tasks/NN-<slug>.md` — one per task:
```markdown
# Task NN: <one-concern title>
## Objective
<what exists when this is done — observable, 1-3 lines>
## Wiki pages (read these first, only these)
- wiki/<path>.md — use for: <which part of this task it governs>
## Inputs
- <artifact from task NN-1: exact file path / interface name>
- Decisions that bind you: D1 (UUIDv7), D3 (...)
## Steps
1. <concrete step — names, paths, signatures spelled out>
## Deliverables
- <exact file paths created/modified>
- if this task touches a pinned file named in analysis.md's `Constraints`,
name the pin update here explicitly (e.g. "bump the cksum pin in
tests/session-prompt-rework.bats") — a task that touches a pin without
declaring its update is a plan defect, not a surprise for the implementer.
## Verify
- <command to run and what output means success; or concrete checklist>
- lint: <one line per Lint bullet — see the Lint gating table below>
- covers: R<n> — the `## Requirements` row (from analysis.md's A1) this
task's verification proves; a task covering no Rule is a scope-creep
signal, and a Rule covered by no task is a coverage gap.
## Out of scope
- <the adjacent thing the next task does — so the implementer stops at the boundary>
```
**Lint gating in every task's `## Verify`.** For each `- Lint:` bullet in
analysis.md's `## Ground truth`, write one lint line into every task's
Verify:
| Ground truth bullet | Task Verify line |
|---|---|
| `Lint: <command> -> rc=0` | `- lint: <command> && echo LINT_OK` — `<command>` copied verbatim; success = `LINT_OK` printed |
| `Lint: <command> -> rc=<non-zero>`, the tool accepts file operands | `- lint: fs=(); for f in <only the Deliverables paths this tool lints>; do [ -f "$f" ] && fs+=("$f"); done; echo "lint files (${#fs[@]}): ${fs[*]-}"; if [ ${#fs[@]} -eq 0 ]; then echo "lint skipped: no lintable Deliverables remain"; else <tool invocation that takes files> "${fs[@]}"; fi && echo LINT_OK` (bash) — the untouched tree already fails, so only this task's files are judged; deleted files and paths with spaces pass through safely, and an empty list is reported, not hidden |
| `Lint: <command> -> rc=<non-zero>`, the tool takes no file operands | `- lint: not gated — baseline rc=<n>, <command> takes no file operands`; loop-implement step 0 records it as `ABANDON: <gate id> baseline rc=<n>, <command> takes no file operands` |
| `Lint: none — checked: ...` | no lint line |
Warnings block only when the recorded command already promotes them (a
lint script running `eslint --max-warnings 0`); the plan never adds a
warning-promotion flag.
6. **Self-check before handing off.** For each task, simulate a Haiku-grade
implementer: reading ONLY that file + its wiki pages, is there any point where
it must choose between two designs, invent a name/type/endpoint, or open an
unnamed file? Each such point is a planning defect — fix the task, don't hope.
Then check the seams: every Input names a Deliverable of an earlier task,
verbatim. Then check the verdict: is the `## Size verdict` present, and is
it consistent with the task table — does its `small`/`medium`/`large` call
actually match the task count and the step-4 bounds you just checked? Then
check coverage both directions: every Rule in analysis.md's `## Requirements`
is named by at least one task's `covers:` line, and every task's `covers:`
line names a Rule that actually exists — an orphan on either side goes back
to Phase A/C for repair before dispatch. Then check lint: every task's
Verify carries one lint line per `- Lint:` bullet, per the Lint gating
table.
## Execution handoff
Tasks are executed one at a time, in `## Task order`, by **`loop-implement`** —
the single implementation loop. It loads each task's named wiki pages, applies
their directives (task D-number decisions win), and runs the verification loop
(tests → audit → judge) before moving to the next task. (In `orchestrate`, each
task is dispatched to its own session, which runs `loop-implement`.)
If execution reports BLOCKED, the fix belongs here: the task was under-specified.
Repair the task file (or a decision in `plan.md`), then re-dispatch — a BLOCKED
report means the plan left a decision to the implementer, not that execution
failed.