Files
msd-core/docs/reference/plan-md.md
Tom Boucher c5e0371775 feat(#1951): reversibility tagging — gate one-way-door decisions (#2471)
* test(#1951): add failing-first tests for reversibility tagging

Red phase for issue #1951 (reversibility tagging: classify decisions by
undo cost, gate one-way doors behind a checkpoint:decision).

Tests assert, per the issue's acceptance criteria:
- discuss-phase CONTEXT.md template records a **Reversibility:** field with
  a rationale on captured decisions, and states it is optional
- gsd-planner @-references planner-reversibility.md and stays under the
  49152-char agent cap (LARGE_CAP, tests/agent-size-budget.test.cjs)
- a one-way rating inserts a checkpoint:decision before the dependent task;
  reversible inserts none; costly is flagged but never blocks
- the taxonomy defaults to reversible when unsure (checkpoint-fatigue guard)
  and inserting a checkpoint implies autonomous: false
- docs/reference/plan-md.md documents <reversibility> as optional with all
  three ratings
- --no-reversibility-gates parses to REVERSIBILITY_GATES=false, is injected
  into the planner prompt, and is advertised in the command argument-hint
  and help full mode (argument-hint parity)
- the override suppresses the gate but still persists the rating
- cmdVerifyPlanStructure accepts every rating and the absent case
  (additive-validator guarantee, behavioral via runGsdTools)
- parity: thinking-models-planning.md #4 adopts the canonical three-level
  taxonomy and the binary REVERSIBLE/IRREVERSIBLE vocabulary is gone
- no content loss from the planner extraction made to fit under the cap

Prose-contract assertions are Red until the implementation lands. The
behavioral validator assertions pass immediately — regression guards
proving the validator already accepts unknown optional tags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(#1951): reversibility tagging — gate one-way-door decisions

Classify planning decisions by what undoing them would cost, and give a
one-way door a human beat before the agent walks through it (issue #1951,
The Pragmatic Programmer Topic 15 'Reversibility'; Bezos's one-way/two-way
door framing).

Acceptance criteria met:
- discuss-phase records an optional reversibility rating with a rationale
  on <decisions> entries in the phase CONTEXT.md template. Unrated
  decisions are treated as reversible, so existing phases are unaffected.
- a one-way rating makes gsd-planner insert a checkpoint:decision before
  the task that implements the decision, reusing the existing checkpoint
  mechanism -- no new checkpoint machinery.
- reversible ratings trigger no checkpoint; costly ratings are flagged in
  the plan but never block.
- the rating persists on the task as the optional <reversibility rating=>
  element. cmdVerifyPlanStructure accepts every rating and the absent
  case; the structural validator does not reject unknown optional tags.
- --no-reversibility-gates (REVERSIBILITY_GATES=false) suppresses
  checkpoint insertion for intentionally-unattended runs while still
  recording ratings -- the override changes what stops the run, not what
  the plan remembers.

Single taxonomy, not two: references/thinking-models-planning.md #4
already shipped a binary REVERSIBLE/IRREVERSIBLE classification and is
loaded by both gsd-planner and gsd-plan-checker. It is rewritten onto the
canonical three-level vocabulary and now points at planner-reversibility.md
as the taxonomy owner, with a parity test that fails if the surfaces
diverge (DEFECT.GENERATIVE-FIX-DIVERGENCE).

agents/gsd-planner.md sat 47 chars under the 49152 LARGE_CAP, so the
checkpoint DO/DON'T guidance was relocated verbatim into
planner-antipatterns.md -- already @-referenced from the same section for
the same topic, so the planner still loads it and nothing was dropped. A
test guards the relocation against content loss.

Files: gsd-core/references/planner-reversibility.md (NEW, canonical
taxonomy + emission rules + anti-patterns), gsd-planner.md, plan-phase
workflow/command/help (flag wiring + parity), plan-md.md schema,
discuss-phase context template, CONTEXT.md glossary, INVENTORY + manifest,
size baselines, install goldens, plugin skills regen, changeset.

Closes #1951

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#1951): address orthogonal review findings

Two isolated reviewers (correctness + security), neither of which authored
the change. Every finding fixed:

Security — the rationale is untrusted input (ADR-1577). It originates in
conversation and flows CONTEXT.md -> planner -> PLAN.md -> executor, each
hop an LLM reading the previous hop's output, with no validation on the
path. planner-reversibility.md and the discuss-phase template now state
it is data and never instructions, and name the </reversibility>
early-termination hazard explicitly -- a rationale that closes its own
element injects sibling structure the executor reads as real tasks.
Four tests guard it.

Correctness 1 — nothing machine-enforced the feature's own promise: a task
rated one-way with no preceding checkpoint:decision validated as fully
clean, so a planner error silently reopened the gap this feature exists to
close. cmdVerifyPlanStructure now warns on an ungated one-way rating. A
warning, not an error: <reversibility> stays additive and the plan stays
valid. Four tests cover ungated (warns), gated (silent), still-valid, and
reversible/costly never flagged.

Correctness 2 — pass-always test. The --no-reversibility-gates parse test
substring-matched the whole workflow file, and plan-phase.md prose mentions
both tokens in one sentence, so it passed with the bash conditional
deleted: it was testing the documentation, not the parser. Now scoped to
the fenced bash blocks and matched as one physical line, with a negative
control confirming prose alone cannot satisfy it.

Correctness 3 — costly had no itemized emission rule, only one-way did, so
two agents could diverge on whether to tag costly at all.

Correctness 4 — template convention break: the example ratings were bare
while every sibling field uses [...] to signal substitution, inviting an
LLM to copy one-way/costly forward as boilerplate. Now bracketed.

Correctness 5 — latent false-green: .includes('reversible') also matches
inside irreversible/irreversibility, which appear in anti-pattern
prose, so a surface that dropped the real taxonomy entry would still pass.
Now word-boundary matched.

ADR-857 phase-6 ceiling — the first gsd-test run caught plan-phase.md
1216 bytes over its frozen 94519 ceiling (it had 49 bytes of headroom on
next). The ceiling may only rise for privileged host machinery, and
reversibility gating is optional-feature logic, so the wiring was slimmed
to its minimum and the explanatory prose moved to the reference files the
planner already loads. plan-phase.md is now 94400 bytes -- 119 under the
ceiling and 70 bytes SMALLER than on next, so the host loop shrank while
gaining the feature, which is what phase 6 ratchets toward. The tracer
contract (tests/tracer-bullet.test.cjs) is unchanged.

Lint — fixed an unnecessary non-null assertion in verify.cts and a
CRLF-fragile bare \n regex in the new test (DEFECT.WINDOWS-CRLF-TEST-
PORTABILITY, the #1658/#1668/#2206/#2449/#2450 class).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1951): checkpoint fixture must carry the common task elements

The gated-one-way fixture built a checkpoint:decision task from the
abbreviated skeleton in gsd-planner.md, which shows only the
checkpoint-specific elements (<decision>/<context>/<resume-signal>).
cmdVerifyPlanStructure requires <name> and <action> on EVERY task
regardless of type, so the fixture failed validation for reasons that had
nothing to do with reversibility:

  errors: ["Task missing <name> element", "Task 'unnamed' missing <action>"]

Caught by gsd-test on 14d14a39 (2 failures, both this fixture).

The canonical shape is in tests/verify.test.cjs:266 — a checkpoint task
carries <name>/<files>/<action>/<verify> like any other. Fixture corrected
to match. Verified behaviorally against the real gsd-tools CLI across all
four cases: gated one-way (valid, silent), ungated one-way (valid, warns),
costly (valid, silent), absent (valid, silent).

Not a product defect: the validator's every-task contract is intentional
and pre-existing, and docs/reference/plan-md.md scopes its required-element
list to type=auto/tracer only because those are the elements a planner must
author, not because checkpoints are exempt from <name>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1951): backfill changeset pr number to 2471

* fix(#1951): CodeQL incomplete-sanitization + prompt-injection scan collision

Both CI failures were real defects in code this PR added, not false
positives.

CodeQL js/incomplete-sanitization (high), reversibility-tagging.test.cjs:46 —
the namesRating helper built its regex with `rating.replace(/[-]/g, '\\-')`,
which escapes the hyphen but not backslash, so the escape was incomplete.
It was also unnecessary: `-` carries no special meaning outside a character
class. Replaced with a complete metacharacter escape (backslash included).
Word-boundary behavior verified unchanged across all three ratings — notably
that "irreversible" prose still does not satisfy a "reversible" match, which
is the false-green this helper exists to prevent.

Prompt injection scan — the checkpoint fixture used the human-verification
child element inside <verify>. That tag name is a fake-instruction-boundary
pattern in scripts/prompt-injection-scan.sh, and the scan runs over changed
files, so copying the shape from tests/verify.test.cjs (unflagged only
because it is not in this diff) tripped the gate. Switched to the documented
plain-prose <verify> form.

The first attempt at that fix failed the same gate a second time: the
comment explaining the collision quoted the offending tag literally. The
comment now names it in prose instead — the scanner does not care whether a
match is code or commentary, which is the whole point of the
DEFECT.PROMPT-INJECTION-SCAN-COLLISION note in CLAUDE.md.

Verified locally before push: scan reports 0 findings across 57 changed
files, eslint clean, and both fixtures still validate as designed (gated
one-way silent, ungated one-way warns, neither errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1951): record measured cost and halve gsd-tools spawns

The Windows shard 1/3 job timeout was traced to the sharding layer, not to
this PR's assertions — see #2472. Two contributing factors were this file's
own, and are fixed here.

1. tests/test-timings.json had no entry for reversibility-tagging.test.cjs,
   so scripts/run-tests.cjs weighted it at the table's median fallback
   (~315ms) for LPT chunk packing. It actually measures 5595ms — an 18x
   under-weight. Recorded the measured value from the green gsd-test run
   (max across the node22/node24 lanes, per gen-test-timings.cjs's
   convention). Only this one entry: a full regen churns 634 entries of
   run-to-run drift, and the table is explicitly advisory and un-gated, so
   a 637-line diff does not belong in a feature PR.

2. Each verifyPlan() spawns gsd-tools, which dominates this file's cost.
   Spawns cut from 9 to 6 with no coverage lost:
   - the ungated-one-way warning and its stays-valid assertion now share
     one plan instead of building the same plan twice;
   - the reversible/costly never-flagged-as-ungated test was strictly
     subsumed by the additive suite, which already runs those two ratings
     ungated and asserts no /reversibilit/ warning at all — and the gate
     warning's text contains both "reversibility" and "one-way", so the
     broader assertion catches it. It only re-spawned gsd-tools twice to
     prove the same thing.

Both are symptom fixes. The shard imbalance itself (19/11/10 minutes
against a 20-minute cap, from a cost-blind round-robin partition that also
reshuffles downstream files whenever one is inserted) is tracked in #2472.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#1951): checkpoint fixture adopts the #2444 type-branched contract

Surfaced by rebasing onto next, which gained #2444 (branch plan-structure
validation on task type=checkpoint:*) while this PR was in review.

cmdVerifyPlanStructure no longer applies one required-element set to every
task. A checkpoint:decision now requires <name> + <resume-signal> +
<decision> + <options>, and is exempt from the <action>/<verify>/<done>/
<files> set that auto and tracer tasks carry. The gated-one-way fixture
predated that split and failed on the new requirement:

  errors: ["Task 'Task 0: Confirm the on-disk format' missing <options>"]

Fixture rewritten to mirror the checkpoint:decision contract exactly — real
<options> with two <option> children — rather than padding it with fields
checkpoints no longer need. That also drops the plain-prose <verify> the
earlier revision carried purely to dodge the prompt-injection scan; a
checkpoint task has no <verify> requirement at all, so the workaround is
moot.

Verified against the real gsd-tools CLI across all four cases: gated one-way
(valid, silent), ungated one-way (valid, warns), costly (valid, silent),
absent (valid, silent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 10:44:55 -04:00

20 KiB
Raw Blame History

PLAN.md schema reference

A per-plan PLAN.md is GSD 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.


Overview

Plans live inside phase directories at:

.planning/phases/<NN>-<slug>/<NN>-<PP>-PLAN.md

For example: .planning/phases/03-post-feed/03-02-PLAN.md (Phase 3, Plan 2).

Plans are produced by the gsd-planner agent (spawned by /gsd: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

---
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
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 gsd-planner.
depends_on Yes array of plan IDs Plans this plan must wait for. Empty array = wave 1. Example: ["03-01"] means this plan runs after Plan 01 in Phase 3.
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.
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. Any other value (or the field's absence) has no effect on counting.
must_haves Yes object Goal-backward verification criteria. See below.

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):

---
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 gsd-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.

<objective>

States what the plan delivers and why it matters for the project:

<objective>
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.
</objective>

<execution_context>

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:

<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>

These @ paths point at the local GSD install, not at repository files. The prefix shown here (~/.claude/gsd-core/…) is the Claude global-install location; other runtimes and local installs resolve to their own install directory — for example .cursor/gsd-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 — /gsd-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 <context> (below), whose repository-relative @ paths resolve after a git clone.

<context>

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:

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@src/components/UserCard.tsx
</context>

<tasks>

Contains one or more <task> elements. Every task element must carry <name>, <files>, <read_first>, <action>, <verify>, <acceptance_criteria>, and <done> for type="auto" and type="tracer" tasks. Optional <precondition> (see Preconditions) and <reversibility> (see Reversibility) elements may sit between <name> and <files>.


Preconditions

<precondition> is an optional element on <task> (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 (<verify>/<done>/<acceptance_criteria>, after) ↔ invariants (must_haves.truths, across the whole plan).

<task type="auto">
  <name>Add /reveal endpoint handler</name>
  <precondition>server bootstraps and responds to GET /health (from the tracer slice)</precondition>
  <files>server/reveal.ts</files>
  <action>…</action>
  <verify>curl /reveal?path=… opens the OS file manager</verify>
  <done>Endpoint committed and manually verified</done>
</task>

Optional and back-compat: a plan that omits <precondition> on every task behaves exactly as today — the executor skips the check with no visible change. Adding <precondition> 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 <precondition> 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 <precondition> 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 <precondition> 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 <precondition> for intra-plan sequencing — that is what depends_on is for), and the contract triad mapping: see gsd-core/references/planner-preconditions.md.


Reversibility

<reversibility> is an optional element on <task> (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.

<task type="auto">
  <name>Define the on-disk event log format</name>
  <reversibility rating="one-way">Phases 4-6 read this file; changing the
  format after they land requires a migration for every existing project.</reversibility>
  <files>src/event-log.cts</files>
  <action>…</action>
  <verify><automated>npm run test:unit -- event-log</automated></verify>
  <done>Format documented and written by the writer under test</done>
</task>
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 <reversibility> 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: /gsd: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 gsd-core/references/planner-reversibility.md.


Task types

Type Use Autonomy
auto Everything the executor can do independently. Fully autonomous.
tracer The leading thin end-to-end slice a plan starts with by default (tracer-first) — production-quality, wired through every layer, with a real end-to-end <verify>. Fully autonomous; after committing, the executor runs the tracer's <verify> as an early integration gate — autonomous runs halt on failure before expansion, interactive runs present a checkpoint:human-verify.
checkpoint:human-verify Visual or functional verification that requires a human to look at a running UI or service. Pauses execution; presents to the developer; resumes on approval.
checkpoint:decision Implementation choices that arose during execution and require the developer's input. Pauses execution; presents options; resumes on selection.
checkpoint:human-action Truly unavoidable manual steps (account creation, hardware interaction). Used sparingly. Pauses execution; resumes on confirmation.

Plans that contain any checkpoint task must set autonomous: false in frontmatter.


auto task structure

<task type="auto">
  <name>Task 1: Create PostCard component</name>
  <files>src/components/PostCard.tsx</files>
  <read_first>src/components/UserCard.tsx, src/types/post.ts</read_first>
  <action>Create PostCard component accepting a Post prop (id, authorId, content, createdAt,
    reactionCount). Render author avatar using UserAvatar from UserCard pattern. Show timestamp
    using date-fns formatDistanceToNow. Export as named export PostCard.</action>
  <verify>npx tsc --noEmit</verify>
  <acceptance_criteria>
    - src/components/PostCard.tsx exports named export PostCard
    - PostCard.tsx contains "reactionCount" prop usage
    - npx tsc --noEmit exits 0
  </acceptance_criteria>
  <done>PostCard renders post content with author and timestamp</done>
</task>

Required fields for auto tasks

Field Rule
<files> Every file the task creates or modifies. The executor writes only these files.
<read_first> Files the executor must read before touching anything — the file being modified, any source-of-truth pattern file, any file whose types or conventions must be replicated.
<action> Concrete instructions with exact identifiers, file paths, function signatures, and expected values. Never says "align X with Y" without specifying the target state. Never contains fenced code blocks or full implementations.
<verify> A runnable command or check that proves the task succeeded. Must distinguish pass from fail — echo "done" is not valid.
<acceptance_criteria> Verifiable conditions: grep-verifiable strings, command exit codes, observable behaviours. No subjective language ("looks correct", "properly configured"). Negative greps (! grep -Eq 'PAT' file) are file-scoped — region-scope them (sed -n/awk range, then grep) when a sibling task needs the construct elsewhere in the same file (#968).
<done> A short measurable statement of the completed outcome.

Plan quality dimensions

The gsd-plan-checker agent reviews every PLAN.md across 12 dimensions before execution begins. A plan that fails any BLOCKER-severity check is returned to gsd-planner for revision (up to 3 iterations):

Dimension What it checks
1 — Requirement Coverage Every phase requirement ID from ROADMAP.md appears in at least one plan's requirements frontmatter field and has covering task(s).
2 — Task Completeness Every auto task carries all required fields (<files>, <action>, <verify>, <acceptance_criteria>, <done>). No vague or empty fields.
3 — Dependency Correctness depends_on references are valid, acyclic, and consistent with wave numbers. Wave N plan depends only on plans in waves < N.
4 — Key Links Planned Artifacts in must_haves.key_links have corresponding tasks that implement the wiring — not just the artifact creation.
5 — Scope Sanity Plans stay within context budget: 2–3 tasks per plan (4 = warning, 5+ = BLOCKER), ≤ 8–10 files per plan (15+ = BLOCKER).
6 — Verification Derivation must_haves.truths are user-observable behaviours, not implementation details. Artifacts map to truths. Key links cover critical wiring.
7 — Context Compliance Every D-NN decision from CONTEXT.md is addressed by at least one task. No task implements anything from <deferred>.
7b — Scope Reduction Detection Task actions do not silently reduce a locked decision to a "v1", "stub", or "future enhancement" without delivering the full decision scope. Always a BLOCKER when found.
7c — Architectural Tier Compliance Tasks assign capabilities to the correct tier per the RESEARCH.md Architectural Responsibility Map (when present). Security-sensitive capabilities in the wrong tier are BLOCKERs.
8 — Nyquist Compliance When workflow.nyquist_validation is enabled and RESEARCH.md exists, every task has an <automated> verify command, no consecutive window of 3 tasks lacks coverage, and VALIDATION.md is present.
9 — Cross-Plan Data Contracts When plans share data pipelines, their transformations are compatible — no plan strips data that another plan needs in original form.
10 — CLAUDE.md Compliance Plans respect project-specific conventions, forbidden patterns, required tools, and security requirements from ./CLAUDE.md.
11 — Research Resolution When RESEARCH.md exists, its ## Open Questions section is marked (RESOLVED) before planning proceeds.
12 — Pattern Compliance When PATTERNS.md exists, tasks reference the correct analog patterns for each new or modified file.

Wave execution model

Wave numbers are pre-computed during planning. Execute-phase groups plans by wave number and runs each wave's plans in parallel:

Wave 1: Plan 01, Plan 02, Plan 03  (all run simultaneously — no dependencies)
Wave 2: Plan 04                    (waits for Wave 1 to complete)
Wave 3: Plan 05                    (waits for Wave 2 to complete)

Plans within a wave that modify overlapping files must not be in the same wave — the plan-checker's Dimension 3 flags this as a BLOCKER.


Plan output

After a plan executes successfully, the executor writes a SUMMARY.md at:

.planning/phases/<NN>-<slug>/<NN>-<PP>-SUMMARY.md

The SUMMARY.md is the canonical record of what was built. Subsequent plans in the same phase may reference it when they have a genuine dependency on its types or decisions.