Step 03 — Plan: author the brief
← 02 Rehearse offline · Index · next: 04 Do →
Beat: Plan. Human touch point #1. The output is one artifact — brief.md —
the contribution spec the rest of the cycle is measured against. A bundle with no
brief.md is UNPLANNED; once the brief exists it's PLANNED and ready for Do.
Two parts follow: how to use it — the commands, and what a good brief contains — then how it works underneath: the leaves involved, the guards around the beat's exit, and why the brief has the shape it does.
How to use
The Plan leaf is interactive — it opens Claude in your terminal so you and the
planner co-author the brief from a tracker issue. Wyrd configures it to fetch the
GitHub issue thread for you ([tracker].notes_cmd runs a one-line script around
gh issue view and writes notes.json into the bundle) and to read the target's
design corpus — its ADRs, proposals and specs under ../wyrd/docs/design/ — in
place rather than copied (INTEGRATION §2):
pdca flow 779 # opens the planner; you converge on a brief, then the
# driver continues unattended into Do + Check
This command starts one full cycle, not just Plan. There's no standalone
command that runs Plan alone and stops. pdca flow opens the planner's
interactive session, and the moment a brief exists, that same invocation falls
straight through into the unattended Do → Check band — there's no
--plan-only flag to stop it there. pdca run <id> doesn't help either: it
never advances an UNPLANNED bundle at all (there's nothing for the driver to
do without a brief), so it can't trigger Plan in isolation. The nearest thing
to isolating Plan is to author brief.md yourself, outside the harness, and
seed it with pdca init-issue <id> --from-brief path/to/brief.md — but that
bypasses the planner leaf entirely rather than running it.
Under the hood the interactive session itself is the planner leaf from
step 01, writing results/issue_779/brief.md.
Planning a specific id list in one session
Pass several ids to pdca flow to plan + drive exactly that set — e.g. bundles you
seeded from per-bundle triage notes, not a tracker CSV:
pdca flow 778 779
Any UNPLANNED id in the list is briefed in one shared interactive session before
the drive; already-briefed ids skip Plan. Ids the planner chooses to skip are left
alone. --from-csv seeds the Plan source; with no ids, pdca flow --from-csv PATH
plans a batch the planner picks from the export.
Each bundle's tracker thread can be fetched automatically: set [tracker].notes_cmd
in pdca.toml to your scrape tooling (a {id} shell template that writes
issue_<id>/notes.json; $PDCA_BUNDLE is the bundle dir). The harness runs it before
any Plan beat when notes.json is absent, so the planner has the comment thread
without you scraping by hand. It's best-effort — a failure just falls back to the
CSV / asking you. Wyrd's is ./engine/scripts/scrape-notes.sh {id}.
Composing several sources
A good brief often draws on more than the ticket — a linked design doc, an accepted
proposal, a spec section, a CSV row. Declare a list of [[plan.source]] providers in
pdca.toml and each contributes context into the bundle's sources/ dir before Plan,
so the planner briefs from the full picture, not one scrape (issue #102). Built-in
types: github (gh), gitlab (glab), csv, file (a path/glob, {id}
interpolated), and command (the escape hatch — exactly notes_cmd, run with
$PDCA_BUNDLE / $PDCA_SOURCES set). Each is best-effort; the legacy notes_cmd still
runs alongside them — unless a source sets role = "tracker", which makes that source
the tracker thread (it writes notes.json itself) and suppresses notes_cmd so the issue
is sourced once, not fetched and stored twice (issue #132). notes_cmd and a tracker-role
plan.source are mutually exclusive. For example, "the GitHub issue and its linked ADR":
[[plan.source]]
type = "github"
[[plan.source]]
type = "file"
path = "docs/adr/*{id}*.md"
Wyrd deliberately does not wire its design corpus this way: INTEGRATION §2
calls the ADRs, proposals and specs "reference foundation, not bundle
artifacts" — the planner reads them live from the ../wyrd checkout, so nothing
is duplicated into results/.
A second opinion on the brief — plan-advisory leaves (issue #301)
The reviewer in step 05 reads the patch. A wrong brief is
cheaper to catch before a builder, a reviewer and an adversary have each spent a
pass on it, so the same open-list mechanism exists at Plan: each
[[leaves.plan_advisory]] leaf reviews the brief (brief + tracker notes +
sources — no patch exists yet) as an antagonist of the plan — wrong root-cause
framing, an unverifiable success criterion, hidden scope, a prerequisite that
cannot resolve, ignored tracker context. Its findings are - NEEDS-HUMAN —
bullets in plan-advisory-<id>.md; the planner then gets one bounded
revision pass over them, and the bundle records plan-advisory-benefit.json —
the brief's hash before and after, whether it was revised, the finding count —
which surfaces in SUMMARY §10 so Act can judge over cycles whether plan reviews
pay for themselves. A finding left unrevised lands in §6 for you. Always
advisory, never a gate; off by default. Wyrd's:
[[leaves.plan_advisory]]
id = "plan-reviewer"
role = "refute the brief: wrong root cause, untestable success criterion, hidden scope"
mode = "command"
family = "codex"
agent = "plan-reviewer"
model = "gpt-6-astra"
effort = "xhigh"
argv = ["codex", "exec", "--sandbox", "workspace-write", "--skip-git-repo-check"]
[leaves.plan_advisory_selection]
mode = "vendor-complement" # anchor: the PLANNER family (the brief's author)
vendor-complement makes the pairing automatic: the driver runs the single leaf
whose family differs from the planner's (pre-Do there is no builder to anchor
on), so plan validation is cross-vendor without per-brief edits. If no leaf
differs it falls back to the first applicable one and files the lapse as a §6
note.
It earns its keep. The brief below carries a "Plan-review response" section that records what the review changed — two of four findings were about the success criterion itself:
* **The criterion did not falsify the server-error category** it claims. The invariant names
four response categories; legs 1-3 exercised three. **Leg 4 above is new** — a signed
`GET /wyrd-bucket?acl` refused `501` — and the old leg 4 (byte equality) is now leg 5.
Nothing else changed: no leg was weakened, and the honest limit about a real `500` is
stated rather than papered over.
* **Equality with the tarball's `VERSION` has no PR-time gate.** True, and already declared
under Verification posture; what was missing is that it need not wait for a tag. The
`workflow_dispatch` route and the local `cargo xtask dist` comparison are now named there
as sign-off options. The binding criterion was NOT widened to include them — a criterion
no gate can evaluate is worse than a declared deferral.
A success criterion that names four response categories and tests three is the kind of hole a builder would have happily built to — and a Check would have passed. One revision pass, before Do, closed it.
Ordering and routing the batch
When a batch has dependencies, you don't run it by hand in waves — you declare the shape and the driver schedules it. Optional brief fields, set at Plan:
Depends on:/Conflicts with:— the batch runs as dependency waves (09 parallel lanes): a bundle lands in a later wave than its prerequisites and builds on their accepted result (the wave driver folds each wave onto the base the next builds on — no human merge between them).Conflicts withputs two file-overlapping bundles in different waves. (Depends on (merged)/Stacks onstill parse but are deprecated — in the wave model they are justDepends on.)Ordering note:is the free-text sibling of these two — why the scheduling is set as it is (e.g. "depends-on 12 because both edit cache.py"). Not machine-parsed; it just keeps the human's reasoning next to the bare-id fields for the next person (including future-you) reading the brief.Merges with:— an atomic merge group: bundles that must not land on the base separately (a caller-first pair, or issues planned to merge as one unit). Ids only, likeConflicts with. Declare it on every member: the driver only reads the briefs in the run, so a group named only by a bundle outside the run is not seen. It changes no wave. Under the defaultwave_mode = "stack"you merge the PRs yourself and can take a group together, so nothing more happens. Underwave_mode = "merge"the driver merges each PR on its own, so for now it builds no member it can see:pdca flow <ids>refuses the run, and the--from-csvsweep holds the group and its dependents (07 · Waves in execution; real merge-mode support is issue #778).Difficulty:/Do model:— route this bundle's Do to the right backend (step 04):Difficultyfeeds thewhenrouting (and the iterate escalation ladder);Do modelpins a backend by name. So different bundles in one wave can build on different models.
Declaring the constraints is a Plan judgment — the planner sets them from the batch's real
dependency/conflict structure; an unschedulable graph (a cycle, or a dep that is neither in
the batch nor already COMPLETE) is rejected up front — pdca flow exits 2 before building
anything, naming the bundle and the dependency (or the cycle) and the ways out.
pdca waves <ids…> prints the computed wave plan without building.
What a real brief looks like
This is the actual brief the planner produced for Wyrd issue 779 — the S3
gateway sent no Server header, so nothing on the wire said which build a
client was talking to. It is the second child of a split (step 07
tells that story): its sibling, 778, bakes a build identity into the binary;
this slice puts that identity on the wire. Trimmed for length — the real file
is about 320 lines and far more specific in several fields; nothing quoted
below is altered. Read it as a checklist of what a good spec contains for
this change.
# Brief — issue 779 / s3-server-version-header-stamp
> Plan artifact (docs 02 §PLAN). Do reads ONLY this file (plus the peer callsites cited
> under **Citations expected**). The `- **Label:** value` lines are parsed by the driver.
>
> **Child 2 of 2** from the split of #736 (`results/issue_736/split-proposal.md`). #778 is
> already on your base: it bakes the build identity into the binary, exposes it as
> `wyrd_server::version::BUILD_IDENTITY`, and records it as the `version` field of the `s3`
> role's `role started` log event. This slice puts that identity on the wire. It derives
> nothing.
>
> Plan of record: `docs/design/proposals/draft/0017-blackbox-validation-tool.md`
> §Dependencies ("The gateway advertises no version") and §3 ("The matrix must be keyed to
> the server version"). Read in place in the target checkout — never copied here.
- **Slug:** s3-server-version-header-stamp
- **Kind:** enhancement
- **Defect:** The S3 gateway advertises no version on the wire. Verified on `origin/main`
at `a801997`: `grep -rn "header::SERVER" crates/gateway-s3/src/` returns nothing, and the
only header `handle` stamps on every response is `x-amz-request-id`
(`crates/gateway-s3/src/lib.rs:1552-1556`). Real S3 sends `Server: AmazonS3`; sending
nothing is the unusual choice. A client cannot tell last month's deployment from today's,
a captured HTTP exchange does not identify the build that produced it ...
- **Goal:** Every response the S3 front door emits carries `Server: wyrd/<version>`, where
`<version>` is the identity #778 baked in — success, client error, server error and the
streaming-GET head alike.
- **Success criterion:** BINDING (demonstrable by C4-verify at Check — no container, no
cluster, no feature flag): a new integration test spawns the **built `wyrd` binary** as
an `s3` role (... killed on every exit path) and asserts, against that one child
process:
1. **the success plane carries it** — a signed `PUT` that succeeds returns a `Server`
header;
2. **the error plane carries it** — an UNSIGNED request refused `403` returns a `Server`
header. This is the load-bearing leg: an error response is the one most likely to end
up in a bug report, and it is the leg a per-handler implementation would miss;
3. **the streaming-GET head carries it** — a signed `GET` of the object just written
returns a `Server` header. ...
4. **the SERVER-ERROR class carries it** — a **signed** `GET /wyrd-bucket?acl` is refused
`501 NotImplemented` by the subresource denylist ... and that response returns a
`Server` header. ...
5. **the value is the real build identity, not a placeholder** — each of those four
header values is EXACTLY `wyrd/` + V, where V is the `version` field of the same child
process's `role started` JSON event (#778's observable, read from the same stderr
stream). Byte equality against an independently-observed value, not a shape test: this
is what makes the criterion unfakeable, and it is why this slice depends on #778.
- **Falsifiability:** RED is producible on the ordinary developer harness Do is pointed at
— `cargo test -p wyrd-server --test s3_server_version_header`, no Docker, no cluster, no
feature flag. On the reverted tree there is no `Server` header on any response at all
... This instance's `C4-verify` **reverts the production change and keeps the added test**
(`engine/scripts/run-verify.sh:508-517`), so the test must still COMPILE against the
reverted tree: drive `env!("CARGO_BIN_EXE_wyrd")` ... Do MUST NOT reference any symbol
this patch introduces ... from the test file; a test that calls net-new API fails to
compile on the red leg, which the gate correctly scores `UNVERIFIABLE` (exit 77 →
SUMMARY §6) rather than as a proven red. ...
- **Invariant to restore:** *Every response the S3 front door emits identifies the build
that produced it.* Stated over the response CATEGORY — success, client error, server
error and the streaming-GET head alike — not over one handler and not over one status
code. SELF-TEST: this cannot be satisfied by touching a single handler, which is the
point; the gateway already has exactly one place where the property is expressible for
all of them (`handle`'s single stamp point, `crates/gateway-s3/src/lib.rs:1552-1556`,
reached by every request because the router is `Router::new().fallback(handle::<G>)`,
`:205`). ...
- **Repo + branch target:** getwyrd/wyrd @ main
- **Depends on:** 778
- **Conflicts with:** 738, 742
- **Ordering note:** `Depends on: 778` is a genuine build-on dependency, not just
sequencing: leg 5 asserts byte-equality against the `version` field #778 adds to the
`role started` event ... Built on a base without #778 this slice cannot go green. ...
`Conflicts with: 738` — #738 threads `--chunk-size` through `cmd_s3` → `serve_s3_role` →
`serve_s3_dispatch` → `serve_s3`, and this slice edits `serve_s3`'s body (`cli.rs:2377`).
`Conflicts with: 742` — #742's scope (f) rewrites the `release.yml` smoke step this slice
extends, and it is last in the batch, so the overlap is already scheduled apart.
- **Surfaces:** data
- **Difficulty:** medium
- **Scope:** (a) `S3Config` gains a server-identity field, defaulted in `S3Config::new` so
every existing caller keeps compiling ...; (b) `handle` stamps `Server: wyrd/<v>` at its
single stamp point, beside the request id; (c) the composition root supplies the real
value — `serve_s3` sets the field from `wyrd_server::version::BUILD_IDENTITY` ...;
(d) `docs/design/architecture/14-threat-model.md` records the disclosure decision (see
Decision 2); (e) `.github/workflows/release.yml`'s existing installer smoke step gains
the ONE end-to-end check that closes the tracker's "matches `VERSION`" clause ...
**/ out of scope:** any derivation, build script, normalizer or packaging change — #778
owns the identity and it is already on your base ...; a configuration knob to suppress or
coarsen the header (Decision 2 — a follow-up issue if an operator ever asks); ...
- **Repro instruction:** On `origin/main` at `a801997`, in the target checkout:
`grep -rn "header::SERVER\|\"server\"" crates/gateway-s3/src/` → nothing. ...
- **External dependencies:** none.
- **Test file:** `crates/server/tests/s3_server_version_header.rs` — a NEW file, and the
patch's **ONLY** added `*/tests/*.rs`. ... Appending to the existing `s3_http_wire.rs`
would silently degrade the gate to green-only and prove nothing; adding a SECOND
`*/tests/*.rs` would make it a second discriminator that must also go red with production
reverted, so do not.
- **Verification posture:** The BINDING criterion above is the default posture — a flippable
test, red pre-fix and green post-fix at Check. One half IS deferred and is declared here
so it lands as a pre-declared sign-off item rather than a surprise NEEDS-HUMAN: the
tracker's "the wire version matches what `cargo xtask dist` stamps into the tarball's
`VERSION`" clause cannot be observed inside `cargo xtask ci`, because `dist` needs Docker
and a network ...
- **Citations expected:** Do must cite `path:line` on the base for every change. Peer
callsites Do MAY open and should mirror: ... the `x-amz-request-id` stamp at the end of
`handle` ... Stamp the `Server` header there, the same way, for the same reason. ...
- **Prior-art check (triage cycles):** searched by affected path on `origin/main` at
`a801997`. ... `gh pr list --state all --search "Server header"` → no PR, open or closed,
has attempted one. The only prior attempt is #736's own four rounds
(`results/issue_736/iteration-v{1,2,3,4}/`), whose header half was never refuted; this
brief is that half, re-scoped.
- **Disposition hint:** new-feature
Four of those labels — Kind, Goal, Verification posture and
Production reach (not shown) — are not in the template's brief. Wyrd's own
templates add them (Kind and Goal come from its design-proposal form, the
other two sit in its brief template beside a Track field); the driver parses
none of them, the planner, builder and reviewers read them. That is the designed
seam: the template fixes the fields the machine consumes, and a project adds
what its reviewers need. How to add a field without fighting the next
copier update is in step 10.
Fields from the field table in How it works below
that don't appear here: Onto branch, Do model, Merges with,
Depends on (merged) and Stacks on. All optional, and this change didn't need
any of them — it opens its own PR rather than landing on an existing one, its
default builder is the right one, and it is scheduled by a plain dependency
rather than an atomic merge group. External dependencies is declared none
explicitly: the brief even explains why the cargo dependency the build fetches is
not one (a human never installs it). You'll see Do model in
step 04, and External dependencies in
step 01's doctor section — they show up when a
brief actually needs them, not by default.
The Plan can be a pointer
If your project already plans through its own artifacts — an ADR, an enhancement
proposal, a normative spec under change control — you don't restate that here. Use
templates/plan-pointer.md.tpl: a thin brief that references the host document
(- **Planning artifact:** docs/adr/0042-thing.md) and carries only the few fields the
driver parses (slug, success criterion, branch target, test file). Do reads the
referenced artifact as the authoritative plan and cites it; the rest of the cycle is
unchanged. This lets PDCA wrap a host's existing planning process instead of imposing
its own document shape. Wyrd's INTEGRATION §6 says its Plan is "a set of existing
artifacts" — the linked ADR, proposal or spec — and the brief above shows the
hybrid in practice: a full brief whose "Plan of record" is proposal 0017, read in
place, with the brief adding only what that proposal leaves to one Do cycle.
How it works
The leaves Plan uses
Three leaves touch Plan's territory in pdca.toml, but only one of them belongs
to the beat outright:
planner— Plan's leaf, full stop. Interactive (interactive = true): a REPL with the human, and the one leaf the model spec itself calls "Plan." Everything under How to use above is this leaf running.splitter— also interactive, but the driver never dispatches it — the planner runs it on itself, inline, mid-session, when the brief it's co-authoring turns out to be several slices:pdca split <id>draftssplit-proposal.md, thenpdca split <id> --acceptmaterializes the children — both typed by the planner as tool calls in the same REPL the human is already sitting in, never a separate command remembered later. Step 07 covers the full decomposition process, with the split that produced the brief above; the short version is that splitting is owned by Plan, not bolted on after it.sizer— headless, and not invoked by the planner or by anything in this beat directly. It's called automatically by the driver's pre-dispatch policy: once right after Plan exits (a freshly-paid call, before Do), and again before Check (reading its stored verdict for free, no second opinion bought). It judges{band, independent_outcomes, proposed_seams, confidence}from whateverbrief.mdsays at the time, and itsproposed_seamsis what the splitter reads as a starting point when a split does happen. See Entry and exit below for exactly when it fires.
A fourth, the [[leaves.plan_advisory]] pool above, is dispatched by the driver
right after the planner exits and before Do — an antagonist of the brief, not a
co-author of it.
One thing worth knowing: the vendored model spec
(../template/PCDA/quality-cycle/03-cycle-automation.md) still describes "six"
leaves (planner, builder, reviewer, signoff, publisher, act) — sizer,
splitter and the advisory pools are newer than that count and haven't been
folded into the spec's own language yet, even though pdca.toml's [leaves.*]
tables already ship all of them.
Entry and exit: the guards around Plan
Entry. A bundle arrives at Plan as UNPLANNED — no brief.md yet — unless the
tracker already settled the question before anyone wrote one, in which case it's
RESOLVED instead and Plan never touches it (the states are covered fully in
step 00).
Exit is PLANNED — brief.md exists — but reaching PLANNED does not, by
itself, buy the bundle a builder. Before the driver dispatches Do, it runs a
pre-dispatch policy check against the brief it just got. This is the actual
guard layer, and it's separate from anything in pdca.toml's gates: gates
verify the built artifact at Check; this verifies the brief before Do is
even allowed to spend a builder on it. (The same check fires again at Do's own
exit, before Check dispatches — that firing belongs to Do, not Plan, and is
covered in step 04.)
The two guards
Both are evaluated fresh, every beat — never cached, never pinned by a stale marker — so editing the brief or registering a missing row un-holds the bundle on the very next attempt, no re-plan required.
- The dependency guard (
[driver].dependency_guard, defaulthold) — blocking. Checks every backticked token in the brief'sExternal dependenciesfield (above) against the registered[[doctor.checks]]rows from step 01. A token with no matching row stops the beat: the driver raises aPolicyHold, prints the unregistered token(s) plus the fix (register a detect cmd + install hint in pdca.toml, or annotate it (no-check: …)), and exits non-zero —pdca run/pdca flowreport the bundle as held, not done. Registration is only half the contract: the guard then runs the named rows' detectcmds — and only those, never the instance's wider doctor inventory — so a registered row whose cmd exits non-zero holds the same way, quoting that row's ownhint. A dependency can't be discharged by registration alone on a machine that doesn't have it; keep detect cmds cheap and side-effect-free, because they run every beat the policy is consulted, not just onpdca doctor. This isn't a heuristic call: a token either names a registered row or it doesn't, and a detect cmd either exits 0 or it doesn't, so unlike the size guard below,holdhere is real and is the default. It also isn't a new block — it moves an existing one earlier. The same unregistered dependency already refusessignoff --acceptthrough Check's C6 guard (step 05); catching it here spends a human a minute instead of spending a full builder + reviewer + adversary pass first. Setdependency_guard = "warn"to report and proceed anyway, or"off"to disable it — both go under[driver]inpdca.toml(not pre-populated there; add the key yourself to change the default). - The size guard (
[driver].size_guard, defaultoff) — advisory only, never blocking — even set to"hold", which the driver accepts but silently treats as"warn". That's a deliberate, evidence-based choice, not a gap: calibrated over 86 real bundles, the best structural size signal reaches 62% precision — nearly one wrong hold per right one — and a blocking gate at that rate trains people to override it rather than trust it. Withsize_guard = "warn", an oversized brief prints a note naming which signal fired (structural estimate, or the configured[[leaves.sizer]]leaf's verdict) and a remedy —pdca split— and Do dispatches regardless. This is a backstop, not the normal path — see The leaves Plan uses above and step 07 for the default, Plan-owned split flow this only catches when it didn't already happen. (The remedy reads differently on the guard's second firing, at Do's exit — see step 04.)
Where it runs, and why
The check lives inside driver.advance() itself, not a hook at the literal end
of the Plan beat. That's deliberate: there are four separate code paths that can
walk a bundle from PLANNED into Do (pdca flow <id>, pdca flow <ids…>, the
zero-id batch sweep, and pdca run <id> direct), and only one of them is a true
"Plan just finished" hook. Evaluating inside advance() covers all four by
construction instead of by enumeration — the same reason it's the right place
for the second firing at Do's exit too, covered in
step 04.
Why each field is there
A brief is not free-form. Each labelled field is consumed by a later beat, which is why the harness can automate Do and Check at all:
| Field | Consumed by | What would go wrong without it |
|---|---|---|
| Defect + root cause | Do (where to fix), Check C5 (causal adequacy) | The builder treats symptoms, not the cause |
| Success criterion | Check (the validation oracle), §9 sign-off | "Done" is unfalsifiable |
| Falsifiability | Plan itself — a self-check, not machine-enforced (unlike the two guards above) | A criterion nothing can ever make go RED; Do burns a cycle "verifying" the unverifiable |
| Invariant to restore | Do (the property to guarantee), Check C5 | The fix guards the one reported case instead of the actual defect category |
| Repo + branch target | Do (branch from), Publish (where the PR goes) | The fix lands on the wrong branch |
| Onto branch | Do (branch from), Publish (stacks a commit onto the named PR instead of opening a new one) | A redundant PR opens instead of landing on the one already under review |
| Surfaces | Check (routes which runtime gates apply, e.g. an E2E gate only when gui) |
A GUI-only regression ships because no gate was ever pointed at the surface |
| Scope / out-of-scope | Do (stay in lane), reviewer (scope creep is a FAIL) | Scope creep; an un-reviewable diff |
| Repro instruction | Check C2 (red pre-fix) | No way to prove the bug existed |
| External dependencies | the dependency guard at Plan exit (blocking, by default) + Check §6 as a backstop, reconciled against registered [[doctor.checks]] rows (step 01) |
A missing build tool or service surfaces mid-cycle instead of preflighted — or worse, Do silently works around it |
| Test file | Do (ship it here), C4 gate (run it) | The C4 red→green proof has nothing to run |
| Citations expected | reviewer + human sign-off (traceability) | An unreviewable "trust me" diff with no path:line anchor |
| Prior-art check (triage cycles) | Plan itself, sign-off | A duplicate or already-attempted fix reaches Do before anyone checks history |
| Disposition hint | sets expectations for sign-off | — |
Notice how the brief grounds itself in the project's own rules: the target
getwyrd/wyrd @ main is INTEGRATION §2's rule for single-slice work (one trunk,
no maintenance branches to guess at); the plan of record is a proposal in the
target's design corpus, read in place as INTEGRATION §2 asks; and the Test file reasoning quotes the instance's own verifier
(engine/scripts/run-verify.sh) to explain why the test must be a new file.
That's the payoff of step 01: the planner resolves
abstract questions ("which branch? where does the test go? what counts as red?")
against your repo's concretizations instead of guessing.
STOP discipline
Every brief carries the rule that the cycle never ships before sign-off:
Draft only until Check sign-off. Pushing to a feature/draft branch and opening a draft PR MAY happen during the cycle. The PR MUST NOT be marked ready before sign-off accepts.
The brief is now written. State: PLANNED. The driver advances unattended into
the Do beat — step 04.
← 02 Rehearse offline · Index · next: 04 Do →