r-build · diff
git:20260924.f296d55 to git:20260925.02580c9
65 added, 29 removed. Audit A to A.
---
name: r-build
description: >
Use when a plan exists and you want it built: dispatches the plan-execution orchestrator (engineer/QA/review gates) per phase at the right tier.
---
You are the build command. You prepare context and dispatch the
dispatcher subagent to execute an implementation plan.
## Invocation
`/rdf:r-build [N | N-M | --parallel] [--worktree] [--max N] [--consistency-warn-only "<reason>"]`
Arguments:
N — single phase (existing behavior)
N-M — range of phases (e.g., 1-4) — dispatch in parallel
--parallel — auto-batch all pending phases by dependency graph
--worktree — force git worktree isolation (overrides auto-derivation)
--max N — maximum concurrent dispatchers (default: 4)
--consistency-warn-only "<reason>" — downgrade a consistency-gate structural
block to a warning; requires a stated reason (see Section 1)
(no args) — next pending phase, serial (existing behavior)
## Protocol
### 1. Locate and Validate the Active Plan
- Source `~/.rdf/state/rdf-bus.sh` and call `rdf_session_init`. If the helper
is absent (helpers not yet bootstrapped — fresh plugin install before its
first session restart), stop and tell the user: `/rdf:r-build` needs the
`~/.rdf/state` helpers — restart your session so the RDF plugin can
bootstrap them, or run `rdf deploy claude-code` from a checkout.
- `plan_path="$(rdf_active_plan_path)"` — resolves via three-tier
fallback (session pointer → un-suffixed pointer → root PLAN.md).
- If `$plan_path` is empty, report error and stop:
"No active plan found. Create one with /rdf:r-plan or write it manually,
then run /rdf:r-plan --resume <path> to set the pointer."
- `tier="$(rdf_active_tier)"` — resolve the plan's task-class tier
(`full` when no `**Tier:**` marker); carried into the Section 5
dispatch payload so the dispatcher can cap gates.
- Read `$plan_path` as the plan input from here forward. Validate
the plan against
[reference/plan-schema.md](../reference/plan-schema.md). Apply
Plan-Version Awareness first, then Rules 1-9 in order. On the first
violation, print the rule number and the exact failure message from
the schema doc, then stop without dispatching.
Rule 9 (test-count self-consistency) is conditional — it fires only
when a phase contains an explicit test-count assertion. Apply the same
trigger check as `/rdf:r-plan` Step 2.7: scan each phase's `**Test**`
field and step prose for an integer adjacent to a test noun; if found
and not marked `TODO`, verify the declared count matches the `@test`
references in the phase body. On mismatch, stop with the Rule 9
failure message before dispatching.
/rdf:r-plan Step 2.7 should have caught any schema violations before the
plan was committed. If validation fails here, the plan was authored
manually, edited after planning, or the pre-reviewer pass was skipped
— either fix the plan and re-run /rdf:r-build, or re-run /rdf:r-plan to
regenerate.
- **Consistency micro-gate (spec↔plan↔tasks).** After schema validation,
run the deterministic cross-check:
```bash
bash ~/.rdf/state/rdf-consistency.sh check "$plan_path"
```
(If a source spec is known, pass it as the second argument for goal /
edge-case coverage.) Exit 2 = structural break (File-Map/phase mismatch
or phase-count mismatch) → print the findings and STOP without
dispatching (fix via `/rdf:r-plan`). Exit 1 = advisory drift (uncovered
goal/edge case, missing `**Goals:**` field, tier-size) → surface the
findings and proceed. Exit 0 = clean. Spec Kit `/analyze` precedent;
mechanical checks only.
**Escape hatch.** For a rare legitimate structural false-positive (an
intentional File-Map entry a reviewer approved), re-run with
`~/.rdf/state/rdf-consistency.sh check --warn-only "$plan_path"` to downgrade
the exit-2 block to an exit-1 warning. Using `--warn-only` REQUIRES the
operator to state the reason in the `/rdf:r-build` invocation (e.g.
`/rdf:r-build --consistency-warn-only "reason: File-Map intentionally lists
the generated foo.sh"`) — exit-2-with-no-override is too brittle for
real plans.
### 2. Identify Target Phase
- If `$ARGUMENTS` contains a number N: target Phase N
- If Phase N does not exist in the active plan, report error and stop
- If Phase N has `Status: complete`, warn and ask for confirmation
- If no argument: scan phases in order, target first with
`Status: pending`
- If all phases are complete, report "All phases complete. Use
/rdf:r-ship for release workflow." and stop
- If a phase has `Status: in-progress`, warn that a phase is
already in progress and ask for confirmation before restarting it
### 2b. Identify Parallel Batch
If arguments contain a range (N-M) or --parallel:
1. Read Phase Dependencies from the active plan preamble
- Parse structured list: `- Phase N: none` or `- Phase N: [deps]`
- If no structured list found, fall back to serial with warning:
"Phase Dependencies section not in structured format. Running
serially. Use the format: `- Phase N: none` or `- Phase N: [1, 2]`"
2. Determine target phases:
- Range (N-M): phases N through M inclusive
- --parallel: all phases with Status: pending
- If any phase has Status: in-progress, warn and ask:
"Phase {N} is in-progress (may be from a prior session).
Include in parallel batch? [Y/skip/abort]"
3. Compute parallel batches from dependency graph:
- A phase is eligible for a batch when all its dependencies are
complete (already done or in a prior batch)
- Phases with `none` dependencies are eligible for the first batch
- Group eligible phases into batches, respecting --max ceiling
- Result: ordered list of batches, each batch is a set of
independent phases
4. Validate file ownership within each batch:
- For each batch: collect all files from all phases in the batch
- Parse the **Files:** field from each phase: extract paths from
lines matching `Create: \`path\``, `Modify: \`path\``, or
`Delete: \`path\``. This is a best-effort heuristic — if paths
cannot be extracted (free-form text), skip validation with
warning: "Cannot parse file list for Phase {N}. Proceeding
without file ownership validation."
- If any file appears in more than one phase: split conflicting
phases into separate batches, warn:
"Phases {N} and {M} both modify {file}. Serializing."
- Re-validate after splitting
5. Derive isolation level per batch:
- For each phase in the batch, derive scope classification
(same rules as dispatcher: file list + description + governance)
- Batch isolation = highest scope in the batch:
scope:multi-file or below → parallel-agent (file-gated)
scope:cross-cutting or sensitive → parallel-worktree
- If --worktree flag is set: force parallel-worktree regardless
6. Present dispatch plan to user:
"Parallel dispatch plan:
Batch 1 (parallel, {isolation}): Phases {list}
Batch 2 (serial): Phase {N} (depends: batch 1)
Batch 3 (parallel, {isolation}): Phases {list}
Batch 4 (serial): Phase {N} (depends: all)
{total phases} phases in {total batches} batches. Proceed? [Y/n]"
Wait for user confirmation.
### 3. Create Task List
Read all phases from the active plan (`$plan_path`) and create a task for each one:
```
For each phase in the active plan, in phase order, one TaskCreate per message:
TaskCreate:
subject: "Phase {N}: {description}"
activeForm: "Building Phase {N}: {short desc}"
Mark already-complete phases as completed immediately.
Mark target phase as in_progress before dispatching.
Mark target phase as completed when dispatcher returns PASS.
```
Issue each `TaskCreate` in its own message — see
[reference/progress-tracking.md](../reference/progress-tracking.md).
Do NOT batch phase TaskCreates in a single message: parallel
execution produces non-deterministic display order (e.g. Phase 1,
7, 6, 5, 3, 4, 2 instead of 1-7).
### 4. Load Governance Context
- Read `.rdf/governance/index.md`
- If governance index does not exist, warn: "No governance found.
Run /rdf:r-init to generate governance, or proceed without it."
- Load all governance files unconditionally:
- conventions.md, constraints.md, verification.md,
anti-patterns.md, architecture.md
- Read the current operational mode (if `.rdf/governance/index.md`
has a Mode field other than "development")
### 5. Assemble Dispatch Payload
Build the dispatch prompt for the dispatcher subagent:
```
PHASE: <N>
DESCRIPTION: <phase description from the active plan>
MODE: <execution mode tag: serial-context | serial-agent | parallel-agent>
TIER: <rdf_active_tier — full|quick-plan|bugfix, read at Section 1>
FILES: <file list from the active plan>
ACCEPT: <acceptance criteria from the active plan>
PLAN_PHASE_COUNT: <total phases in the active plan>
PARALLEL_BATCH: <true if dispatched as part of parallel batch, false otherwise>
GOVERNANCE:
index: .rdf/governance/index.md
conventions: .rdf/governance/conventions.md
constraints: .rdf/governance/constraints.md
verification: .rdf/governance/verification.md
anti-patterns: .rdf/governance/anti-patterns.md
architecture: .rdf/governance/architecture.md
OPERATIONAL_MODE: <mode or "development">
PROJECT_ROOT: <absolute path to project root>
```
### 6. Dispatch Dispatcher Subagent
Dispatch the `rdf-dispatcher` subagent with the assembled payload.
The dispatcher handles all execution from here: TDD cycles, engineer
dispatches, quality gates, commit strategy.
### 6b. Dispatch Parallel Batch
Before any worktree creation, source `~/.rdf/state/rdf-bus.sh` and call
`rdf_session_init` to ensure `RDF_SESSION_ID` is set. Each Bash call is a
fresh shell, so every block below that uses `${RDF_SESSION_ID}` starts with
`source ~/.rdf/state/rdf-bus.sh && rdf_session_init` in the same call.
Worktree paths and branch names use the full session id for collision-free
identification across concurrent sessions on the same repository (a resumed
session can reuse its id — see session-safety.md).
For each batch in the dispatch plan (sequential between batches,
parallel within each batch):
**File-gated dispatch (parallel-agent):**
1. Create task per phase in the batch
2. Dispatch N rdf-dispatcher subagents simultaneously
- Each gets the standard dispatch payload (Section 5) plus:
PARALLEL_BATCH: true
- Each operates on the shared working tree
- File ownership enforced by the plan (validated in 2b.4)
3. Wait for all subagents in the batch to complete
4. Collect results: PASS or FAIL per phase
**Worktree dispatch (parallel-worktree):**
1. Create task per phase in the batch
- 2. For each phase, create a git worktree. A retry (Option 2) or a resume
+ 2. Record `{base-branch}` — `git branch --show-current` in the main
+ worktree (stop if HEAD is detached); step 5 merges every phase onto it.
+ For each phase, create a git worktree. A retry (Option 2) or a resume
(Option 3) in the same session reuses the session id, so first clear
any leftover worktree and branch for this phase, logging the branch tip
so a failed attempt's commits stay recoverable (`git branch <name> <tip>`):
```
source ~/.rdf/state/rdf-bus.sh && rdf_session_init
[ -n "$RDF_SESSION_ID" ] || { echo "rdf: no session id; refusing worktree setup" >&2; exit 1; }
root="$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" || exit 1
+ cd "$root" || exit 1
wt="${root}/.worktrees/rdf-phase-{N}-${RDF_SESSION_ID}"; br="rdf/phase-{N}-${RDF_SESSION_ID}"
if tip=$(git rev-parse --verify -q "refs/heads/$br"); then
echo "removing leftover $br (tip $tip)"
git worktree remove --force "$wt" 2>/dev/null || true # worktree may already be gone
git branch -D "$br"
fi
git worktree add "$wt" -b "$br" HEAD
```
(RDF_SESSION_ID is the full session id; prevents cross-session collisions)
After the batch's `git worktree add` calls, activate the phase scope
guard from the main worktree (idempotent; once per batch is enough):
```
source ~/.rdf/state/rdf-bus.sh
rdf_phase_hook_install "$(git rev-parse --show-toplevel)" \
|| echo "warn: phase scope guard (layer 1) not installed; post-merge scope check still applies" >&2
```
The helper copies `~/.rdf/state/git-hooks/pre-commit` (falling back
to `state/git-hooks/pre-commit` when nothing is deployed) into the repo's
`rdf-hooks/` and adds an `includeIf "onbranch:rdf/phase-**"` include,
so the hook runs on phase branches only and chains the project's own
hooks; rc 2 means git < 2.23. The hook enforces phase scope
(Files ∪ Tests-may-touch) at `git commit` time. See `plan-schema.md`
Rule 8 and dispatcher.md "Worktree Pre-Commit Hook Installation".
- 3. Before each `Task` dispatch, the controller MUST change directory
- into the target worktree:
+ 3. Dispatch each phase's `rdf-dispatcher` from inside its worktree. A
+ subagent starts in the controller's current directory, fixed at launch
+ (a later `cd` does not move it), so `cd` there first — by absolute path,
+ since a relative `.worktrees/…` does not resolve once the controller
+ sits in another phase's worktree:
```
- cd .worktrees/rdf-phase-{N}-${RDF_SESSION_ID}
+ source ~/.rdf/state/rdf-bus.sh && rdf_session_init
+ root="$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" || exit 1
+ cd "${root}/.worktrees/rdf-phase-{N}-${RDF_SESSION_ID}" || exit 1
+ [ "$(git branch --show-current)" = "rdf/phase-{N}-${RDF_SESSION_ID}" ] \
+ || { echo "rdf: not on rdf/phase-{N}-${RDF_SESSION_ID}; refusing dispatch" >&2; exit 1; }
```
- The `Task` tool inherits the controller's CWD; the SDK picks an
- adjacent repo non-deterministically when CWD has no `.git/`
- (see workspace CLAUDE.md "Worktree CWD"). This `cd` is the
- actual mechanism that closes the non-deterministic-CWD class.
- For parallel dispatches in a batch, the controller serializes
- the `cd → Task` pair per-phase (parallelism comes from the
- `Task` calls returning before the subagent finishes, not from
- simultaneous `cd`s).
-
- Then dispatch N rdf-dispatcher subagents simultaneously:
+ then dispatch that phase, and repeat the `cd → dispatch` pair for the
+ next one — parallelism comes from the dispatches running in the
+ background, not from simultaneous `cd`s. The `cd` also closes the
+ non-deterministic-CWD class (the SDK picks an adjacent repo when CWD has
+ no `.git/`; workspace CLAUDE.md "Worktree CWD").
- Each gets the standard dispatch payload plus:
PARALLEL_BATCH: true
PROJECT_ROOT: {worktree path}
PROJECT_ROOT_MAIN: {main worktree toplevel}
- - Each dispatched with isolation: "worktree"
+ - Never add the Agent tool's `isolation: "worktree"` on top: the
+ harness would make a second worktree from origin's default branch
+ (not local HEAD) on a `worktree-agent-*` branch, so the phase builds
+ on stale pushed code and commits outside the phase scope guard.
4. Wait for all subagents in the batch to complete
- 5. Merge completed worktrees in plan order:
- For each completed phase (in plan order, N ascending):
- git rebase {base-branch} rdf/phase-{N}-${RDF_SESSION_ID}
- Where {base-branch} is the branch HEAD was on when worktrees were
- created (captured at step 2 of worktree dispatch).
- git merge --ff-only rdf/phase-{N}-${RDF_SESSION_ID}
+ 5. Merge completed phases in plan order (N ascending) from the main
+ worktree — a controller left in a phase worktree would merge into that
+ phase branch. Rebase inside the phase worktree: the branch is checked
+ out there, so `git rebase <base> <branch>` from the main worktree fails.
+ ```
+ source ~/.rdf/state/rdf-bus.sh && rdf_session_init
+ root="$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" || exit 1
+ cd "$root" || exit 1
+ base="{base-branch}"
+ wt="${root}/.worktrees/rdf-phase-{N}-${RDF_SESSION_ID}"; br="rdf/phase-{N}-${RDF_SESSION_ID}"
+ [ "$(git branch --show-current)" = "$base" ] || { echo "rdf: main worktree is not on $base" >&2; exit 1; }
+ n="$(git rev-list --count "${base}..${br}")" || exit 1
+ [ "$n" -gt 0 ] || { echo "rdf: $br has no commits; the phase did not land on its branch" >&2; exit 1; }
+ if ! git -C "$wt" rebase "$base"; then
+ git -C "$wt" rebase --abort
+ echo "rdf: rebase conflict on $br" >&2; exit 1
+ fi
+ git merge --ff-only "$br"
+ ```
This produces a clean linear history — phase commits appear in
- plan order with no merge commit artifacts.
- If rebase conflict: stop, report conflicting phases, enter
- failure handling (Section 8)
- 6. Clean up worktrees:
- git worktree remove .worktrees/rdf-phase-{N}-${RDF_SESSION_ID}
- git branch -d rdf/phase-{N}-${RDF_SESSION_ID}
+ plan order with no merge commit artifacts. A phase that returned PASS
+ with no commits on its branch committed somewhere else: treat it as a
+ FAIL, never as an empty phase. If rebase conflict: stop, report
+ conflicting phases, enter failure handling (Section 8)
+ 6. Clean up each merged phase from the main worktree. The dispatcher's
+ result files sit in the worktree's ignored `.rdf/`, which
+ `git worktree remove` deletes without warning — copy them out first:
+ ```
+ source ~/.rdf/state/rdf-bus.sh && rdf_session_init
+ root="$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" || exit 1
+ cd "$root" || exit 1
+ wt="${root}/.worktrees/rdf-phase-{N}-${RDF_SESSION_ID}"; br="rdf/phase-{N}-${RDF_SESSION_ID}"
+ command mkdir -p "${root}/.rdf/work-output"
+ for f in "${wt}/.rdf/work-output/phase-{N}-"*"-${RDF_SESSION_ID}.md"; do
+ [ -f "$f" ] && command cp -- "$f" "${root}/.rdf/work-output/"
+ done
+ git worktree remove "$wt" && git branch -d "$br"
+ ```
+ If `git worktree remove` refuses (modified or untracked files), show
+ `git -C "$wt" status --short` and resolve before any `--force` —
+ uncommitted work in a phase worktree is a FAIL signal, not debris.
7. Collect results: PASS or FAIL per phase
**Constraint:** Worktree dispatch MUST be invoked from a top-level
session, not from a subagent. Subagents inherit `RDF_SESSION_ID`
from their parent and would create colliding worktree paths.
- The `PARALLEL_BATCH` downgrade in `dispatcher.md` (lines 45-49)
- already prevents nested parallel dispatch; this is its
+ The `PARALLEL_BATCH` downgrade in `dispatcher.md` ("Nested parallel
+ downgrade") already prevents nested parallel dispatch; this is its
worktree-specific explicit form.
**Post-batch quality gate (worktree dispatch only):**
After merging all worktrees in a batch, run a batch-level QA check:
- Dispatch rdf-qa subagent across the combined diff of all phases
in the batch
- This catches semantic conflicts that file ownership validation
cannot detect (e.g., two phases both adding the same function name
to different files)
- If QA finds issues: enter failure handling (Section 8)
**Progress tracking:**
Write batch progress to `.rdf/work-output/build-progress-${RDF_SESSION_ID}.md`
(derived via `rdf_scoped_filename` from `~/.rdf/state/rdf-bus.sh`):
DISPATCH_MODE: parallel
TOTAL_PHASES: {N}
TOTAL_BATCHES: {N}
CURRENT_BATCH: {N}
BATCH_PHASES: [list]
COMPLETED_PHASES: [list]
FAILED_PHASES: [list]
After each batch completes, update and proceed to the next batch.
After all batches complete, proceed to end-of-plan sentinel
(unchanged — runs on cumulative diff from phase 1 to HEAD).
### 7. Report Result
After the dispatcher returns:
- Read the dispatcher's status output from .rdf/work-output/
- Report phase result to the user: PASS (phase complete) or FAIL
(with failure context and which gate failed)
- If PASS and more phases remain:
> **Phase {N} complete** — {description}
> Next: Phase {N+1} — {description}. Run `/rdf:r-build` to continue.
- If PASS and all phases are complete:
- If PLAN_PHASE_COUNT >= 3: the dispatcher runs end-of-plan sentinel
automatically (this is dispatcher-internal — the build command
does not dispatch it separately)
> **All {N} phases complete.** End-of-plan review: {verdict}.
> Run `/rdf:r-ship` to begin the release workflow.
- If PLAN_PHASE_COUNT < 3:
> **All {N} phases complete.**
> Run `/rdf:r-ship` to begin the release workflow.
### 8. Parallel Failure Handling
When one or more phases in a parallel batch fail:
1. Wait for all phases in the batch to complete (do not interrupt
running phases on peer failure)
2. Classify each failure:
- Gate 1 (engineer self-report) failure → recommendation: retry
- Gate 2 (QA) failure → recommendation: retry
- Gate 3 (sentinel) MUST-FIX → recommendation: retry
- Gate 3 (sentinel) architectural concern → recommendation: pause
- Merge conflict → recommendation: serialize conflicting phases
- Post-batch QA failure → recommendation: pause
3. Present to user with Recommendation:
"Batch {N} results:
[PASS] Phase 1: Replace dispatcher gate selection
[FAIL] Phase 2: Update agent finding vocabularies (Gate 2: lint)
[PASS] Phase 3: Simplify r-plan metadata
[PASS] Phase 4: Simplify r-build dispatch payload
Phase 2 failed at Gate 2 (QA lint error).
**Recommendation: retry** — lint errors are typically fixable.
Options:
[1] Merge phases 1, 3, 4 now — retry phase 2 after merge
[2] Retry phase 2 in isolation (attempt 2/3)
[3] Pause — review failure before proceeding"
4. Execute user's choice:
- Option 1: Merge successful phases in plan order, then dispatch
phase 2 serially against new HEAD
- Option 2: Re-dispatch phase 2's dispatcher subagent (same
isolation level, max 3 total attempts); worktree dispatch clears the
failed attempt's worktree and branch first (step 2)
- Option 3: Write progress to
`build-progress-${RDF_SESSION_ID}.md`, stop. User can resume
with `/rdf:r-build --parallel` (which calls `rdf_session_init`
and reads the scoped progress file); in the same session (or after
`claude --resume <id>`) the id matches, and step 2 clears the paused
phase's leftover worktree before re-dispatching it.
## Constraints
- Never execute plan phases directly — always dispatch to dispatcher
- Never modify the active plan — the dispatcher updates phase status
- If governance is missing, dispatch anyway (dispatcher degrades
gracefully) but warn the user
- Respect the plan's execution mode tags — pass them through unchanged
- Parallel dispatch is additive — /rdf:r-build N (single phase) works unchanged
- Nested parallel downgrade: dispatchers in parallel batches receive PARALLEL_BATCH: true