Installs into .claude/skills of the current project.
Are you the author of R Build?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/rfxn-r-build)
---
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 from the main worktree. The
block refuses a submodule, a detached HEAD, and a plan the worktrees
would not see as committed (they and the scope guard read the committed
copy), and records the base branch in a file so step 5 never pastes a
branch name into shell. A retry (Option 2) or a resume (Option 3) in the
same session reuses the session id, so it first clears any leftover
worktree and branch for this phase, keeping its result files and 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
inwt="$(git rev-parse --is-inside-work-tree 2>/dev/null)" # false or fails in .git/modules, a bare repo, or a separate git dir
[ "$inwt" = true ] && [ -z "$(git rev-parse --show-prefix)" ] \
|| { echo "rdf: $root is not a worktree toplevel (submodule, bare repo, or separate git dir); refusing worktree setup" >&2; exit 1; }
base="$(git branch --show-current)"
[ -n "$base" ] || { echo "rdf: HEAD is detached; check out the base branch first" >&2; exit 1; }
plan="$(rdf_active_plan_path)" || { echo "rdf: no active plan" >&2; exit 1; }
if [ -n "$(git status --porcelain -- "$plan" 2>/dev/null)" ]; then # a plan outside the repo has no status
echo "rdf: commit the plan first; phase worktrees read the committed copy" >&2; exit 1
fi
command mkdir -p "${root}/.rdf/work-output"
printf '%s\n' "$base" > "${root}/.rdf/work-output/base-branch-${RDF_SESSION_ID}"
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)"
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 --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. 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:
```
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; }
```
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/`). After the last dispatch, return to the main worktree
(`cd "$root"`, `root` derived as above): progress tracking and Section 7
read `.rdf/work-output/` relative to it, and the running dispatchers
keep their launch directories.
- Each gets the standard dispatch payload plus:
PARALLEL_BATCH: true
PROJECT_ROOT: {worktree path}
PROJECT_ROOT_MAIN: {main worktree toplevel}
- 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 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="$(command cat "${root}/.rdf/work-output/base-branch-${RDF_SESSION_ID}")" || exit 1
wt="${root}/.worktrees/rdf-phase-{N}-${RDF_SESSION_ID}"; br="rdf/phase-{N}-${RDF_SESSION_ID}"
[ -n "$base" ] && [ "$(git branch --show-current)" = "$base" ] \
|| { echo "rdf: main worktree is not on the recorded base branch '$base'" >&2; exit 1; }
st="$(git -C "$wt" status --porcelain)" || { echo "rdf: phase worktree $wt is missing or unreadable" >&2; exit 1; }
if [ -n "$st" ]; then
git -C "$wt" status --short >&2
echo "rdf: $br has uncommitted changes in its worktree" >&2; exit 1
fi
n="$(git rev-list --count "refs/heads/${base}..refs/heads/${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 "refs/heads/${base}"; then
git -C "$wt" rebase --abort 2>/dev/null || true # nothing to abort if the rebase never started
echo "rdf: rebase conflict on $br" >&2; exit 1
fi
git merge --ff-only "refs/heads/${br}"
```
This produces a clean linear history — phase commits appear in
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. Any failure here stops the merge loop:
report it and 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` ("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
- Dispatcher not in its phase worktree, or PASS with no commits on the
phase branch → recommendation: pause (check where the controller was
when it dispatched before retrying)
- Uncommitted changes left in a phase worktree → recommendation: pause
(inspect them there; discard and retry the phase rather than
committing them — a commit made now skips the phase's gates)
- 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