# PLAN.md schema reference A per-plan `PLAN.md` is MSD Core's executable unit of work — a structured document that tells an executor agent exactly what to build and how to verify it was built correctly. This page documents its structure. See [docs index](../README.md). --- ## Overview Plans live inside phase directories at: ``` .planning/phases/-/--PLAN.md ``` For example: `.planning/phases/03-post-feed/03-02-PLAN.md` (Phase 3, Plan 2). Plans are produced by the `msd-planner` agent (spawned by `/msd-plan-phase`) and consumed by `execute-phase`. A phase typically contains between one and four plans; plans within a phase are assigned to execution waves so that independent work runs in parallel. --- ## YAML frontmatter Every PLAN.md opens with a YAML frontmatter block between `---` delimiters. ### Annotated example ```yaml --- phase: 03-post-feed plan: 02 type: execute wave: 2 depends_on: ["03-01"] files_modified: - src/components/PostFeed.tsx - src/components/PostCard.tsx - src/app/feed/page.tsx files_deleted: - src/components/LegacyFeed.tsx autonomous: true requirements: ["FEED-01", "FEED-03"] user_setup: [] must_haves: truths: - "User can scroll through posts from followed accounts" - "Each post shows author avatar, name, timestamp, and content" - "Empty state appears when no posts exist" artifacts: - path: "src/components/PostFeed.tsx" provides: "Scrollable post list" min_lines: 40 - path: "src/components/PostCard.tsx" provides: "Individual post card" exports: ["PostCard"] key_links: - from: "src/components/PostFeed.tsx" to: "src/app/api/feed/route.ts" via: "fetch in useEffect — calls /api/feed endpoint" pattern: "fetch.*api/feed" --- ``` ### Frontmatter field reference | Field | Required | Type | Purpose | |---|---|---|---| | `phase` | Yes | string | Phase identifier, e.g. `03-post-feed`. | | `plan` | Yes | string | Plan number within the phase, e.g. `02`. | | `type` | Yes | `execute` or `tdd` | `execute` for standard plans; `tdd` for test-driven plans where tests are written before implementation. | | `wave` | Yes | integer | Execution wave. Plans in wave 1 run in parallel (no dependencies). Plans in wave 2+ wait for all plans in the previous wave to complete. Pre-computed at plan time by `msd-planner`. | | `depends_on` | Yes | array of plan IDs | Plans this plan must wait for. Empty array = wave 1. Accepts three forms, resolved in order: the full plan id (`"03-01-auth-hardening"`), the canonical phase-plan prefix (`"03-01"`), or the bare plan number (`"01"`, #3897) — which resolves to the sibling plan in the **same phase** whose canonical id ends `-01`. The bare form is in-phase only; it never resolves across phases. If two plans in the same phase share a bare form, the first one (by sorted plan-file order) wins — deterministic, but arbitrary when the collision happens, so prefer the full or canonical form when phase has any short-form collision risk. Example: `["03-01"]` means this plan runs after Plan 01 in Phase 3; from within Phase 3 itself, `["01"]` means the same thing. | | `files_modified` | Yes | array of paths | Every file this plan creates or modifies. Used by the plan-checker to detect same-wave file conflicts and by execute-phase for merge tracking. | | `files_deleted` | No | array of paths | Every file this plan deliberately **removes**. The post-wave cleanup gauntlet blocks the merge of any executor branch whose diff deletes a file — a net against a mass-deletion accident — and this field is the opt-in that names the exceptions. Matching is exact per path after separator normalization: a declared path merges, an undeclared one still blocks that plan's entry (and only that entry). There are no globs and no directory prefixes, so a declaration can never authorize more than it literally lists. Omit the field and the guard's original unconditional block stays in force, which is why absence is always the safe default (#3003). Counts toward same-wave conflict detection alongside `files_modified`: a plan deleting a file another plan in the same wave is editing is the sharpest conflict there is — one branch removes what the other is writing — so the two plans are pushed into different waves regardless of which side holds the deletion. | | `coupling_justified` | No | array of `"plan-id: reason"` strings | One entry per deliberately coupled same-wave peer, e.g. `["03-02: both append independent config keys"]` — declares that the coupling with that plan through a shared mutable resource (config key, table, migration, env var) is deliberate and order-independent. The plan-checker's Dimension 3b recognizes the declaration and does not flag the pair, so intentionally coupled plans can pass verification without serializing waves. The `"plan-id: reason"` shape is a prompt-level convention read by the checker, not a schema — the plan parser (`src/plan-document.cts`) neither validates nor rejects the field, so a typo'd plan-id silently exempts nothing (#3724). | | `autonomous` | Yes | boolean | `true` when all tasks are type `auto`. `false` when the plan contains any `checkpoint:*` task that requires human interaction. | | `requirements` | Yes | array of IDs | Requirement IDs from ROADMAP.md that this plan addresses. Every phase requirement ID must appear in at least one plan's `requirements` field. Empty arrays are a BLOCKER. | | `user_setup` | No | array of objects | External-service setup steps that Claude cannot automate (account creation, secret retrieval, dashboard configuration). When present, execute-phase generates a `USER-SETUP.md` checklist for the developer. | | `status` | No | `superseded` | Marks a plan that was deliberately reassigned or abandoned mid-phase and will never be executed. A `status: superseded` plan is excluded from the phase's plan and summary counts, so it never holds the phase below 100%. See [Superseded plans](#superseded-plans). Any other value (or the field's absence) has no effect on counting. | | `estimate` | No | object | Projected execution cost: `{tokens, raw_tokens, tasks, confidence}` (#2631, [ADR-2629](../adr/2629-phase-effort-estimation-calibration.md)). `tokens` is an `estimateTokens`-scale projection with the project's calibration factor **already applied** (which is why the plan-checker passes `--calibrated` to `estimate-check` — re-applying it would square the correction); `confidence` (`low`/`med`/`high`) is **derived from the calibration sample count, never self-rated**. Additive and optional — a plan without it behaves exactly as before. A plan estimated above `workflow.smart_zone_tokens` is flagged with a split recommendation at plan time; the flag is advisory and never blocks. | | `must_haves` | Yes | object | Goal-backward verification criteria. See below. | | `agent_hint` | No | string | Per-plan specialist executor routing (#1689). Name of a subagent that shares the `msd-executor` execution contract (reads `execute-plan.md`, atomic-commit protocol). When the named agent resolves on the active runtime (an agent file exists in the runtime's agent dir), `execute-phase` dispatches it instead of `msd-executor`. Unset/unresolved → `msd-executor`, byte-identical to today. Default-on via `workflow.agent_hint_routing`; set `false` to disable. See [Per-plan executor routing](#per-plan-executor-routing). | | `gap_closure` | Only in gap-closure mode | string, exact match | Must be exactly the literal lowercase `true` — validated as a string comparison, not a YAML boolean, so `True`, `TRUE`, `yes`, and `1` are all rejected. Required on every plan generated by `/msd-plan-phase --gaps`, checked by the `plan-gap-closure` schema (`src/frontmatter.cts`) rather than `plan`. `/msd-execute-phase --gaps-only` filters strictly on this field, so an omitted or wrong-valued `gap_closure` on a gap-closure plan means it is silently skipped — zero executors spawned, no error (#2847). Standard and reviews-mode plans validate against the unmodified `plan` schema, which neither requires nor checks this field (nothing rejects it as an extra field either, if present). | ### Per-plan executor routing A plan can opt into a **specialist executor** by setting `agent_hint:` to the name of a subagent that shares the `msd-executor` execution contract — it reads `execute-plan.md`, follows the atomic-commit protocol, and carries Read/Edit/Write/Bash. A Flutter specialist, for example: ```yaml --- agent_hint: well-me-flutter-engineer --- ``` At dispatch, `execute-phase` resolves the hint against the **active runtime's agent directory** (both project-local and user-global, across the runtime's filename variants — `.md`, `.agent.md`, `.toml`, …) and dispatches the named subagent via `subagent_type`. If the field is absent, blank, or the named agent does not resolve, the plan dispatches to `msd-executor` — byte-identical to behavior without the field. Routing is gated by `workflow.agent_hint_routing` (default-on; see [CONFIGURATION](../CONFIGURATION.md#workflow-toggles)). The specialist agent is an ordinary agent file (e.g. `agents/well-me-flutter-engineer.md` on Claude Code); there is no separate registration manifest. ### Superseded plans A phase reads complete when every `*-PLAN.md` has a matching `*-SUMMARY.md`. When a plan is reassigned or dropped mid-phase — its work folded into a later plan — it will never gain a summary, and without a marker it would pin the phase below 100% forever (the plan-level analogue of a retired phase). Add `status: superseded` to that plan's frontmatter to exclude it from **both** the plan count (denominator) and the summary count (numerator): ```yaml --- phase: 05-api plan: "12" type: execute status: superseded --- ``` A phase with 13 plans, two of them `superseded`, then reads `11/11 → complete` — no fabricated summary required. The match is case-insensitive. Plans without the marker are counted exactly as before. --- ## `must_haves` field `must_haves` captures what must be observably true for the phase goal to be achieved. It is derived during planning and verified after execution by the `msd-verifier` agent. ### Sub-fields | Sub-field | Type | Purpose | |---|---|---| | `truths` | array of strings | Observable behaviours from the user's perspective. Each must be verifiable. Example: `"User can send a message"`, not `"WebSocket library installed"`. | | `artifacts` | array of objects | Files that must exist with substantive implementation (not stubs). | | `artifacts[].path` | string | File path relative to project root. | | `artifacts[].provides` | string | What capability this file delivers. | | `artifacts[].min_lines` | integer (optional) | Minimum line count to be considered non-stub. | | `artifacts[].exports` | array of strings (optional) | Expected named exports to verify. | | `artifacts[].contains` | string (optional) | Regex or literal pattern that must appear in the file. | | `key_links` | array of objects | Critical connections between artifacts — the wiring that makes the system work end-to-end. | | `key_links[].from` | string | Source file (relative path from project root). Must be a literal file path — describe components or symbols in `via:`. | | `key_links[].to` | string | Target file (relative path from project root). Must be a literal file path — describe endpoints, modules, or APIs in `via:`. | | `key_links[].via` | string | Description of how they connect, including any endpoint, component, or symbol name (e.g. `fetch in useEffect — calls /api/feed`, `Prisma query via prisma.message`, `import`). | | `key_links[].pattern` | string (optional) | Regex to verify the connection exists in source. | --- ## Body structure After frontmatter, the plan body uses named XML-style blocks read by the executor agent. ### `` States what the plan delivers and why it matters for the project: ```xml Implement the post feed as a scrollable card list. Purpose: Core display feature for the social feed phase. Output: PostFeed and PostCard components wired to /api/feed. ``` ### `` Lists the workflow files associated with executing the plan. Always includes the execute-plan workflow; adds the checkpoints reference when the plan contains checkpoint tasks: ```xml @~/.claude/msd-core/workflows/execute-plan.md @~/.claude/msd-core/templates/summary.md ``` These `@` paths point at the local MSD install, not at repository files. The prefix shown here (`~/.claude/msd-core/…`) is the Claude global-install location; other runtimes and local installs resolve to their own install directory — for example `.cursor/msd-core/…`, or an absolute project path for a `--local` install. Because the prefix is install-relative, this block is not clone-portable: a committed plan carries whichever prefix the authoring install had. Execution does not depend on it — `/msd-execute-phase` loads the execute-plan workflow from its own installed copy — so the block records the execution context rather than resolvable repository references. Contrast `` (below), whose repository-relative `@` paths resolve after a `git clone`. ### `` References source files the executor needs to read. Includes project-level planning docs and any source files whose patterns or types the plan must replicate. Prior plan `SUMMARY.md` files are included only when there is a genuine dependency (imported types, shared decision) — not reflexively: ```xml @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @src/components/UserCard.tsx ``` ### `` Contains one or more `` elements. Every task element must carry ``, ``, ``, ``, ``, ``, and `` for `type="auto"` and `type="tracer"` tasks. Optional `` (see [Preconditions](#preconditions)) and `` (see [Reversibility](#reversibility)) elements may sit between `` and ``. --- ## Preconditions `` is an **optional** element on `` (issue #1949, *The Pragmatic Programmer* Topic 23 — Design by Contract). It states, in a single line of runnable/checkable prose, what must already be true for the task to begin safely. It closes the front-of-task side of the contract triad — preconditions (before) ↔ postconditions (``/``/``, after) ↔ invariants (`must_haves.truths`, across the whole plan). ```xml Add /reveal endpoint handler server bootstraps and responds to GET /health (from the tracer slice) server/reveal.ts … curl /reveal?path=… opens the OS file manager Endpoint committed and manually verified ``` **Optional and back-compat:** a plan that omits `` on every task behaves exactly as today — the executor skips the check with no visible change. Adding `` to a task tells the executor to assert it before any other task work (read-only checks only: file existence, env var presence, idempotent health pings; no side-effecting checks — halt and surface a checkpoint if one seems required) and halt (returning a `checkpoint:human-verify`, no partial commit) on an unmet precondition. Plans that include `` pass `verify plan-structure` unchanged — the structural validator checks for the presence of required tags and does not reject unknown optional tags. **Emission cases** (planner-side): emit `` only when a task relies on state the plan's own `depends_on` ordering does not already guarantee. Three cases cover every legitimate use: 1. **External service setup** (`user_setup` frontmatter) — the consuming task ties a specific setup step to itself so the executor halts if the setup was skipped. 2. **Prior-phase artifact dependency** — a generated schema, a migration's dist output, a contract file from an earlier phase. Cross-phase `depends_on` does not cross phase boundaries, so `` is the explicit pointer. 3. **Environment variable / runtime configuration** — a tool, API, or script the task invokes requires an env var or runtime config that exists *now*, not at plan time. Full emission rules, anti-patterns ("the system is ready" is not checkable; do not use `` for intra-plan sequencing — that is what `depends_on` is for), and the contract triad mapping: see `msd-core/references/planner-preconditions.md`. --- ## Reversibility `` is an **optional** element on `` (issue #1951, *The Pragmatic Programmer* Topic 15 — "Reversibility"). It records how costly the decision the task implements would be to undo, so a one-way-door choice gets a human beat before the agent walks through it. The `rating` attribute carries the classification; the body carries a one-line rationale. ```xml Define the on-disk event log format Phases 4-6 read this file; changing the format after they land requires a migration for every existing project. src/event-log.cts … npm run test:unit -- event-log Format documented and written by the writer under test ``` | Rating | Meaning | Effect on the plan | |---|---|---| | `reversible` | Undo is local and cheap. | None. This is the default when no rating is given. | | `costly` | Undo touches many call sites or needs a coordinated change. | Flagged in the plan so the reader sees the weight. Never blocks. | | `one-way` | Undo requires a migration, breaks a published contract, or is impossible. | The planner inserts a `checkpoint:decision` immediately **before** the dependent task. | **Optional and back-compat:** a plan that omits `` on every task behaves exactly as today — no flag, no checkpoint. Plans that include it pass `verify plan-structure` unchanged; the structural validator checks for the presence of required tags and does not reject unknown optional tags. **Autonomy:** inserting a `checkpoint:decision` means the plan contains a checkpoint, so its frontmatter must set `autonomous: false`. **Override:** `/msd-plan-phase --no-reversibility-gates` (`REVERSIBILITY_GATES=false`) suppresses checkpoint insertion for intentionally-unattended runs. Ratings are still recorded and `costly` items are still flagged — the override changes what stops the run, not what the plan remembers. Full taxonomy, emission rules, and anti-patterns (chiefly: rating everything `one-way` produces checkpoint fatigue; prefer *removing* irreversibility over gating it): see `msd-core/references/planner-reversibility.md`. --- ## Auto-select `auto_select` is an **optional** attribute on a `` element (issue #4095). It names the `id` of the `