| name | shipwright-iterate |
| description | Lightweight SDLC for ongoing changes in completed Shipwright projects.
TRIGGER when: user asks to add a feature, fix a bug, change behavior, refactor, update, modify, or improve code in a project that has shipwright_run_config.json with status complete. Also when user describes a bug report, enhancement request, or any code-level change to a finished project.
DO NOT TRIGGER when: user asks about project setup (/shipwright-project), planning (/shipwright-plan), initial build (/shipwright-build), deployment (/shipwright-deploy), running tests (/shipwright-test), or non-code tasks like documentation questions. Also DO NOT TRIGGER when the pipeline is still in_progress โ those changes belong to the current pipeline phase. |
| license | MIT |
| compatibility | Requires uv (Python 3.11+), git repository required, completed Shipwright project |
Shipwright Iterate Skill
Complexity-adaptive change lifecycle for completed Shipwright projects. Detects intent (feature, change, bug), assesses complexity, runs the right amount of process.
How invoked: directly via /shipwright-iterate, or via the suggest_iterate.py UserPromptSubmit hook context.
External review (v0.5.x+): medium+ uses {shared_root}/scripts/tools/external_review.py --mode iterate, check-external-review-keys.py, mark-review-state.py (Branch A/B/C gate).
Phase Index โ Where the prose lives
| Section | Reference |
|---|
| Repo Scout, Mini-Plan | iteration-planning ยท escape-hatch |
| Self-Review, Full Review, Handoff | iteration-reviews |
| Design Check, Testing, Visual, E2E | design-and-testing |
| Reflection, Boundary Probes, Round-Trip, Confidence | reflection ยท boundary-probes ยท round-trip-tests ยท confidence-anti-patterns |
| Context Loading | context-loading |
| Path A / B / C body (+ SIMPLIFY sub-mode) | path-a-feature ยท path-b-change ยท path-c-bug ยท F-debug (BUG systematic-debugging) ยท F-simplify (SIMPLIFY behavior-preserving) |
| Campaign Mode, Escalation, Degraded, Errors | campaign-mode ยท mid-flight-escalation ยท degraded-mode ยท error-handling |
| Repairing a red shared branch (ยงB1b + F11) | main-repair |
Artifact Ownership โ iterate spec, spec.md, shipwright_events.jsonl, ADR, architecture.md, mini-plan | artifact-ownership |
| Phase Timing (Iterate-Rail durations, M-Pre-1) | phase-timing |
| Finalization F-phases | F0 ยท F0.5 ยท F1 ยท F2 ยท F3 ยท F3a ยท F4 ยท F5 ยท F5b ยท F5c ยท F6 ยท F6.5 ยท F7 ยท F7b ยท F11 ยท F12 |
| Risk Taxonomy, Override Classes, Phase Matrix | this file (inline โ NORMATIVE) |
CRITICAL: First Actions
Governing rules: read and follow shared/constitution.md (ALWAYS / ASK FIRST / NEVER). BEFORE any other tools:
A. Print Intro Banner
================================================================================
SHIPWRIGHT-ITERATE: Adaptive Change Lifecycle
================================================================================
Usage: /shipwright-iterate --type feature|change|bug "description"
or: Auto-detected from your prompt (via hook context)
Paths: FEATURE / CHANGE โ [interview]โ[spec]โ[plan]โ[approval]โ[review]โ[design]โbuildโtestโcommit
BUG โ [spec]โreproduceโ[plan]โfixโtestโcommit
Complexity: trivial | small | medium | large (auto-detected, overridable)
In plain words (shared index โ docs/guide.md Appendix A):
ADR: Log of architectural decisions with rationale (why this database, why this pattern)
Conventional Commits: Standardized commit-message format (`feat:`, `fix:`, etc.) so version history is machine-readable
================================================================================
B. Validate Project
Verify shipwright_run_config.json exists with status: "complete" (or iterate_history exists). Otherwise print the "Completed Project Required" notice and stop.
B1. Resumable Iterate Run
Every iterate runs in a worktree under .worktrees/<slug>/. If inside a worktree (git rev-parse --git-common-dir resolves above cwd), resume in place. Otherwise enumerate branches with uv run "{shared_root}/scripts/tools/list_iterate_branches.py" --project-root . (surfaces locked = resumable, stale = housekeeping). Check .shipwright/agent_docs/session_handoff.md for run_id. Offer Resume / Abandon / Complete. Resume/Complete replay-check: if medium+ has no external-review marker AND feedback_iterations > 0, run Step 4 first. If the iterate ADR has no Self-Review: block, run Step 7 before commit.
B1a. Worktree Isolation (unconditional)
uv run "{shared_root}/scripts/tools/setup_iterate_worktree.py" \
--project-root . --slug "<slug>" --run-id "<run_id>"
Creates .worktrees/<slug> off freshly-fetched origin/<default> with branch iterate/<slug>. Parse the JSON; {project_root} for the rest of the run = the helper's project_root field. cd shell into it. Exit codes: 0 ok ยท 2 slug collision ยท 3 fetch failed (STOP unless SHIPWRIGHT_ITERATE_NO_FETCH=1). One iterate = one worktree = one branch = one PR. .worktrees/<slug> is .gitignore'd. Re-hydrate .env* + node_modules/.venv per project shape. Cleanup after PR merge: git worktree remove + git branch -D.
B1b. Shared-Branch Health (one API call)
This run inherits origin/<default>'s state, so check it before building on it โ a broken base otherwise surfaces later as a confusing F0 failure "from another session". Run uv run "{shared_root}/scripts/tools/main_health.py" --project-root "{project_root}". Exit 0 green / 3 running โ continue. 4 unknown โ continue, but report it in the F12 summary: unknown is never green. 2 red โ repair main first, as its own small PR, then continue with this iterate's actual task. 5 escalate โ a finding-class workflow (scanner, bloat) is red: that is a finding, never an auto-repair โ file the card per escalate.keys, then continue. Both per main-repair. The claim is creating the ref iterate/fix-main-<sha12> (a same-SHA git push succeeds for both racers and is therefore no lock); an existing claim means someone else is on it, so just proceed. The same check runs again before the arm at F11 โ merging onto a red base puts the blame for the next red run on the wrong change.
B2. Load Project Context (MANDATORY)
Read all Layer 1 of references/context-loading.md โ CLAUDE.md, conventions.md, decision_log.md, architecture.md, shipwright_sync_config.json, all spec.md, shipwright_test_results.json, shipwright_events.jsonl, git log --oneline -20. Missing files: warn but continue.
C. Generate Run ID
run_id = iterate-{YYYY-MM-DD}-{short-description} (canonical RUN_ID_STRICT form). Propagate through all artifacts. โ Phase Timing: emit mark scope here โ right after the run_id exists and before Repo Scout (ยงE), so the scope node captures it (see phase-timing).
D. Determine Intent Type
Priority: --type flag โ [Shipwright] Detected: ... hook context โ classify_intent.py โ ask user (if confidence < 0.7). A mode: simplify classification (simplify/clean-up vocabulary) selects the behavior-preserving SIMPLIFY sub-mode (Path B โ F-simplify, Spec Impact NONE).
E. Assess Complexity (Two-Stage)
uv run "{plugin_root}/scripts/lib/classify_complexity.py" \
--message "{user_message}" --sync-config "{project_root}/shipwright_sync_config.json" \
--project-root "{project_root}" --run-id "{run_id}"
Parse: estimate, confidence, risk_flags, enforcements, signals (incl. signals.prior_source). Passing --run-id additively persists the session plan (phases/skips/risk_flags/complexity) to .shipwright/agent_docs/iterates/{run_id}.plan.json for the WebUI scoped Plan-Card โ stdout is byte-unchanged. User override: --complexity. Safety floor: risk flags enforce minimums. Fall-through default is history-calibrated, and capped at small: when no scope keyword matches, the estimate is the median final complexity of the last finalized runs (.shipwright/agent_docs/iterates/, prior_source: history) instead of bare trivial; a keyword match always wins (prior_source: keyword); cold start keeps trivial (prior_source: default). The fall-through may inform how LOW to go, never how HIGH. medium is the first tier that buys an iterate spec, a mini-plan, an approval gate and an external LLM plan review, so it must be bought with positive evidence โ a scope keyword, a risk flag, a cross-split, or the Stage-2 Repo Scout โ never with the absence of evidence. The cap is also what breaks the ratchet: the prior is the median of final complexities and a no-keyword run's final complexity is the prior, so prior = median(finals) is self-consistent at any level and carries no information about the change; measured 2026-07-31 the store's 50 retained entries were 84% medium / 14% small / 2% trivial, so the prior returned medium for every no-keyword run (trg-ee7b83e5). That 84% is the history's composition, not the share of runs this cap flips โ the flip-rate is the no-keyword fraction, which nothing recorded until F5c began carrying prior_source in this same change. Under-classification stays cheap because Stage 2 confirms/upgrades and Mid-Flight Escalation exists; over-classification does not, because complexity locks after Stage 2. Quick Scout must keep its diff-driven detector step (references/iteration-planning.md) โ Stage 1 sees no diff, so cross_component and the CI-supply-chain flag are message-only until then. Both F11 verifiers now recompute from the diff and enforce at every complexity, so a missed detection is caught mechanically at finalization rather than silently skipped (iterate-2026-08-01-coverage-gate-recompute-order) โ but being caught at F11 means being blocked after the work is built, so the scout step is what keeps the classification honest up front. Stage 2: Repo Scout โ Quick (trivial/small) or Thorough (medium); see references/iteration-planning.md. After Stage 2 complexity is locked (unless mid-flight escalation).
F. Print Planned Run Summary
Print Run ID / Intent / Complexity (+ reasoning) / Prior source (keyword | history | default) / Risk flags / Phases / Skipping / Safety floor. User can adjust per Override Classes (below).
G. Interview (complexity-gated)
| Complexity | FEATURE | CHANGE | BUG |
|---|
| Trivial | skip | skip | skip (reproduce instead) |
| Small | 1 confirmation Q | 1 confirmation Q | skip (reproduce instead) |
| Medium | 2-3 scoping Qs | 1-2 scoping Qs | skip (reproduce instead) |
| Large | โ escape hatch | โ escape hatch | โ escape hatch |
CRITICAL: Wait for user answers before proceeding to any path step.
Feedback Parsing Protocol (Interview / Approval Gate / any correction): extract ALL items, echo as numbered checklist, wait for user confirmation, track as TodoWrite tasks, no silent dropping. NEVER proceed without all feedback items captured and confirmed.
Canonical Risk Taxonomy
| Risk Flag | Trigger Paths | Min Complexity | Enforces |
|---|
touches_auth | src/middleware.ts, src/lib/supabase/, **/auth/** | small | mandatory review |
touches_rls | supabase/migrations/*rls*, RLS policy changes | small | mandatory review |
touches_middleware | src/middleware.ts, next.config.* | small | mandatory review |
touches_migrations | supabase/migrations/ | small | mandatory review + down.sql |
touches_billing | **/stripe/**, **/payment*/**, webhook handlers | small | mandatory review |
touches_shared_infra | src/lib/, src/components/ui/, layout components | small | full test suite |
cross_split | changes span 2+ planning splits | medium | full review + full test suite |
touches_public_api | API route handlers, exported types | small | mandatory review |
touches_build | the build/dependency graph, JS and Python. JS: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, npm-shrinkwrap.json, next.config.*, vite.config.*, tailwind.config.*, webpack.config.*, rollup.config.*, tsconfig.json. Python: uv.lock, poetry.lock, Pipfile, Pipfile.lock, pyproject.toml, setup.py, setup.cfg, requirements*.txt | small | performance test layer (Lighthouse + bundle gate via /shipwright-test Step 3.8). For a Python change that layer is mostly a no-op by its own skip-rules (no dev_url โ skip Lighthouse, no build artifacts โ skip bundle); what is load-bearing there is the small minimum plus the flag itself, which turns on "Full Code Review โ only if risk flags" at trivial/small. The list was JS-only until iterate-2026-07-31-it5-classification-calibration, so in a Python monorepo a dependency change raised nothing (trg-496e63a7). Scope is JS + Python by decision; Rust/Go/Ruby/PHP inputs are deliberately absent rather than forgotten |
touches_io_boundary | .env*, hooks.json, settings.json, *_config.json, *_state.json; or anchored producer/consumer keywords (parse_env, json.dump(s)?, json.load(s)?, yaml.dump, yaml.safe_load) | small | round-trip test (Boundary Probe sub-step in Build TDD โ see references/boundary-probes.md + references/round-trip-tests.md) |
cross_component | FRAMEWORK cross-component machinery (diff-driven, classify_complexity.CROSS_COMPONENT_FILE_PATTERNS): merge/churn/event-log resolver (integrate_main, ensure_current, churn_merge, gitattributes_*, resolve_churn_conflicts, events_log), Claude-Code hooks + hook fan-out (hooks.json, **/hooks/*.py), pipeline phase validators (verify_phase, get_phase_context), campaign drain (autonomous_loop, campaign_*, campaign-mode.md) | medium | integration coverage โ a real-scenario integration test proving the components compose (reference: shared/tests/test_parallel_merge_cascade_integration.py), recorded as a category:"integration" behavior in the Test Completeness Ledger. NON-dodgeable: the F11 verifier check_integration_coverage RECOMPUTES the flag from the diff, applies at EVERY complexity, and STOPs without it; infra failures (unobtainable diff, git fault) fail closed, and only a non-git context skips. medium here is the CLASSIFICATION escalation floor โ what the flag forces a run to be classified as โ not an enforcement floor for the gate. The two were coupled until iterate-2026-08-01-coverage-gate-recompute-order, which meant the recompute was reached only for runs that had already self-reported into the enforcing band, i.e. never for the missed-detection case it exists to catch. + full test suite |
touches_ci_supplychain | the CI trust boundary (diff-driven, risk_detectors.CI_SUPPLYCHAIN_FILE_PATTERNS): .github/workflows/**, .github/dependabot.y(a)ml, .github/actions/** | small | acknowledgement โ .shipwright/planning/iterate/<run_id>/ci_supplychain_ack.json must name the recorded posture decision the change is consistent with, bound to the run id AND a fingerprint of this diff's CI paths (a stale ack cannot license a later change); write it with shared/scripts/tools/record_ci_supplychain_ack.py and let F6's directory-level add stage it. It sits beside reviews.json because its old home โ iterate_latest inside the DERIVED shipwright_test_results.json โ made it unshippable: committing that file trips check_no_derived_snapshots_committed while omitting it starves this gate (both ERROR), and restore_derived_to_head reverted the ack outright (iterate-2026-07-28-ci-ack-per-run-home). An ack recorded the old way is still honoured under identical validation. NON-dodgeable: the F11 verifier check_ci_supplychain_ack RECOMPUTES the flag from the diff, applies at EVERY complexity, and fails closed when the diff is unobtainable. Mandatory review alone was REJECTED as the enforcement โ webui #285 already ran a full medium iterate with external plan review and still reversed an accepted-risk posture unnoticed. It forces the change to be reasoned about and recorded; it must NEVER be read as "pin everything" (GitHub-owned actions stay on mutable tags by decision, third-party stay SHA-pinned). + mandatory review |
"touches_db" (ordinary query/model edits without schema changes) is NOT a risk flag. touches_build triggers /shipwright-test's Step 3.8 (skip-rules apply: no dev_url โ skip Lighthouse, no build artifacts โ skip bundle).
Override Classes
| Category | Phases | User can skip? |
|---|
| Mandatory | Self-review, unit test, commit, ADR, compliance, test results JSON, iterate_history, Confidence Calibration (medium+), Test Completeness Ledger (medium+), Review Record (small+ โ a pass may be closed not_run, but never left unanswered) | Never skippable |
| Safety-enforced | Full review (when risk flags), full test suite (when shared infra), down.sql (when migrations), Boundary Probe (when touches_io_boundary), Confidence Calibration (small with touches_io_boundary), Test Completeness Ledger (small) | Only with explicit risk acknowledgment |
| Advisory | Design check, mini-plan, design fidelity, E2E update, external LLM review, release prompt, Confidence Calibration (trivial / small without touches_io_boundary), Test Completeness Ledger (trivial โ auto n/a) | Freely skippable |
| Complexity-gated | Iterate spec, context scan depth | Adjustable via "make it medium/small" |
Path A: FEATURE (new functionality)
Full body in references/path-a-feature.md. The Step 1 / Step 6 / Step 7 / Step 7.5 / Step 8 / Step 11a / Step 11b anchors stay inline here for drift-protection tests.
Step 1: Iterate Spec (medium+ only)
Create .shipwright/planning/iterate/{date}-{short-description}.md. Full template in references/path-a-feature.md. The template MUST contain ## Confidence Calibration with the four bullets:
## Confidence Calibration
- **Boundaries touched:** {list from Affected Boundaries}
- **Empirical probes run:** {one-line per probe + finding}
- **Test Completeness Ledger:** {table โ every testable behavior โ `tested`
(evidence) | `untestable` (closed-vocab reason_code); 0 untested-testable}
- **Confidence-pattern check:** {asymptote (depth) + coverage (breadth)}
Step 6: Build (TDD โ Red-Green-Refactor)
Tests first (outcomes, not internal state; one happy + one error path per AC). Implementation, verify wiring, Boundary Probe sub-step when touches_io_boundary. Full body in references/path-a-feature.md. โ Phase Timing: emit mark build at entry. The three governance-rule anchors stay inline so tests/test_skill_step_6_rules_present.py continues to fire:
- Test-Update-Klausel โ when an iterate changes test infrastructure itself (skip semantics, hygiene rules, test conventions, this skill's checklist), it MUST update the skill's reference rules in the same diff.
- Registry-driven SSoT meta-test rule โ when a registry in
shared/scripts/lib/* references files/identifiers on disk, BOTH directions of drift protection MUST exist: forward (every value resolves to a file) AND reverse (every namespace-matched file has a registry entry).
- Silent-skip CI-discipline rule โ
pytest.skip(...) on missing-binary or cross-plugin sys.path-pollution / ImportError paths MUST hard-fail in CI with an actionable install hint. Pattern: if os.environ.get("CI", "").lower() in ("true", "1"): pytest.fail(...) guarding the skip.
Step 7: Self-Review (always)
See references/iteration-reviews.md for the 7-point checklist (item 7: Affected Boundaries). โ Phase Timing: emit mark review at entry. Record every review pass: all six types โ self ยท plan ยท code ยท doubt ยท external_code ยท spec (Stage 1), all under reviews โ close their own row in .shipwright/planning/iterate/{run_id}/reviews.json via shared/scripts/tools/record_review_pass.py (contract + per-pass table: references/iteration-reviews.md โ "Recording each review pass"). The F11 verifier check_review_record STOPs the run while any type is still pending (small+; skipped at trivial), so a pass that did not run is closed EXPLICITLY with a --disposition naming the rule. An empty Review row must always mean "genuinely not run", never "nobody wrote it down".
Step 7.5: Confidence Calibration (mandatory at medium+, also when touches_io_boundary)
"Are you confident?" is unfalsifiable โ replace with empirical probes per references/confidence-anti-patterns.md. Before F0, populate the spec's Confidence Calibration section with: (1) boundaries touched, (2) empirical probes run + finding, (3) the Test Completeness Ledger, (4) confidence-pattern check (asymptote depth + coverage breadth + integration composition โ when cross_component machinery is touched, add a category:"integration" behavior proving the pieces compose; the F11 verifier check_integration_coverage recomputes the flag from the diff, applies at every complexity, and STOPs without it). Override Classes: Mandatory at medium+, Safety-enforced at small with touches_io_boundary, Advisory otherwise.
Test Completeness Ledger (the empirical-completeness gate). Principle: testable โ tested. Enumerate every behavior this diff introduces/changes; classify each as exactly one of tested (cite the test + result) or untestable (cite a reason_code from the closed vocabulary in references/confidence-anti-patterns.md โ requires-prod-credential, requires-external-nondeterministic-service, requires-physical-device, requires-manual-visual-judgment, requires-interactive-tty, covered-by-existing-test). The disposition "could-test-but-didn't" is abolished โ "I should still test X" is a blocking work item, not a spec note. At F5, record the machine-readable block iterate_latest.test_completeness in shipwright_test_results.json (shape in references/F5.md) โ and carry it in the F5c entry too, because that shared file is a derived snapshot the F11 integration rewinds to HEAD, and the gate now refuses a block naming another run (references/F5c.md); the F11 verifier check_test_completeness_ledger STOPs the run if any behavior is testable-but-untested, or an untestable row lacks a valid reason_code, or the enumeration is short of the AC count. Graduated: enforced at small/medium/large; auto n/a (with a one-line justification) at trivial.
Step 8: Full Code Review (conditional)
See references/iteration-reviews.md for trigger rules. A standing grant in CLAUDE.md outranks this โ if the project states that review subagents are requested by default, spawn them and do NOT ask. Only when no such grant exists and a session policy gates spawning: ask for the go-ahead as your FIRST action here โ before Stage 1. Such a policy (e.g. a standing "do not call the Agent tool unless the user requested it") is conditional: one sentence from the operator lifts it for the rest of the session. It is not a capability limit. Ask only when all three hold โ the cascade is required for this diff; the Agent tool is in this agent's capabilities (asking without it wins a "yes" and fails anyway); and permission was not already given earlier in this session (it holds for the whole session โ do not re-ask). A question asked at finalization buys nothing, because the work is already built. If the answer is no, that is blocker #4 โ follow the escalation ladder in references/iteration-reviews.md (external review mandatory; code and doubt recorded not_run with a disposition naming operator-declined). Under --autonomous you cannot block to ask โ but that prevents acquiring permission, not using permission already held; only when it is absent and unobtainable do you record code = not_run naming the mode, run the external review, and surface the ungated pass in the closing run summary (F12) so the operator can lift it next time. This session spawns the cascade itself โ a standalone iterate has the Agent tool, so there is no delegate and nothing to wait for: spec-reviewer (Stage 1, HARD-GATE โ a REJECT blocks Stage 2 until the diff is fixed and re-reviewed) โ code-reviewer (Stage 2) โ doubt-reviewer (Stage 3, conditional, advisory-must-address). It runs before F6 (commit), because a review that lands after the commit reviews something already shipped. It reviews the code diff; artifacts written afterwards by Steps 11a/11b and finalization ship in the same commit unreviewed, so "before F6" buys not after, not reviews everything that ships. Every stage closes its own row: spec from Stage 1, code from Stage 2 (--from code-reviewer), doubt from Stage 3. Stage 1's row is an ordinary reviews key. It was parked in a sibling gates object while the webui refused to read a record carrying a sixth key; that reader now renders review types it does not recognise, so spec was promoted (references/iteration-reviews.md). Records written before the promotion keep spec under gates and are still read from there โ permanently, since they are immutable. The gate enforces the cascade's own ordering: a code row recorded completed while spec is not completed FAILS, because Stage 2 cannot legitimately have run without its HARD-GATE passing first. A completed code/external_code row must also carry evidence a review happened โ findings, a provider, a raw excerpt, or a recorded_by naming an adapter other than none. The runner contract's "delegated" wording (ยง5b) is campaign-only and does not apply here.
Step 11a: Author E2E Spec (always at medium+; if feature+UI at trivial/small)
See references/design-and-testing.md โ "End-to-End Verification โ Authoring".
Step 11b: Execute E2E Spec against Dev Stack (always at medium+)
See references/design-and-testing.md โ "End-to-End Verification โ Execution". Spec-only authoring without execution is forbidden at medium+. Execution is verified at F0.5; the chokepoint is shared/scripts/surface_verification.py.
Path B: CHANGE (modify existing behavior)
See references/path-b-change.md. Same steps as FEATURE; default Spec Impact is MODIFY. Step 7.5 (Confidence Calibration) applies identically โ mandatory at medium+, also at small with touches_io_boundary. SIMPLIFY sub-mode โ when intent-classification returns mode: simplify (simplify / clean up / declutter / streamline / tidy) OR Spec Impact is NONE (behavior-preserving refactor): route through F-simplify โ Behavior-Snapshot ({shared_root}/scripts/tools/behavior_snapshot.py snapshot, refuses a red baseline) โ Simplify (Five Principles + Chesterton-Fence + the shared reducibility catalog DยทAยทXยทCยทSยทMยทPยทT) โ Behavior-Verify (behavior_snapshot.py verify). The reviewer rejects a simplify that ships behavior drift or removed test coverage (fewer lines is not the goal); Spec Impact is forced NONE. The gate is only as strong as coverage, so removed coverage is a hard reject.
Path C: BUG (fix something broken)
BUG intent (intent-classification kind: bug-fix) routes through F-debug BEFORE any fix โ Iron Law: NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST (4 phases: Read Error โ Reproduce โ Recent Changes โ Component-Boundary Instrumentation). The reviewer rejects a fix that patches a symptom rather than the root cause (no root-cause statement / no test pinning it). Then follow references/path-c-bug.md (investigation, reproduce, root-cause, write-failing-test, fix). Step 7.5 (Confidence Calibration) applies identically โ mandatory at medium+, also at small with touches_io_boundary.
5b. Campaign Mode (Autonomous Multi-Iterate)
See references/campaign-mode.md for the full protocol: campaign setup, autonomous interleaved-serial loop (init/next/record/merge/finalize โ build one sub-iterate โ PR โ CI-green โ merge โ next from fresh origin/main; branch_strategy: serial is the default), sub-iterate-runner contract, F12 release prompt. Review steps in autonomous-loop briefing (ADR-029 โ campaign mode ONLY): the sub-iterate-runner contract mandates Step 3.4 (Diff-Driven Risk Re-Check), Step 3.5 (External Plan Review) and Step 3.7 (Code Review Cascade) between Build and Finalization for medium+ iterates. Step 3.4 runs ALWAYS and comes first, because the other two gate on flags a campaign unit cannot otherwise have: the runner classifies once from the sub-iterate spec text and never reaches the Stage-2 Repo Scout, so every diff-driven detector is structurally silent until 3.4 re-decides from the real change set. It also raises the complexity F5c records โ check_integration_coverage reads it there and green-SKIPs below medium โ and STOPs the unit when the diff touches the CI trust boundary, since that acknowledgement is an operator's to write. In campaign mode the runner has no Agent tool, so the internal code-reviewer is delegated back to the orchestrator; that limit is a fact about the runner subagent, not about iterates in general โ a standalone iterate spawns the cascade itself (Step 8). Skipping these review steps silently is a contract violation under ADR-029. Manual sub-iterate stamp (campaign S1): a hand-run sub-iterate โ /shipwright-iterate --campaign <slug> --sub-iterate-id <id> "<sub-iterate spec path>" (or any direct invocation on a campaign sub-iterate spec) โ MUST stamp its work_completed event exactly like the runner does: include "campaign": "<slug>" and "sub_iterate_id": "<id>" in the F5b --event-extras-json (see references/F5b.md); additive metadata, does not replace the FR-gate classification fields.
6. Phase Matrix by Complexity (NORMATIVE)
Single Source of Truth for phase selection. All prose, diagrams, examples MUST be consistent. Large is a "soft boundary" โ force-continue supported with mandatory review + full tests.
| Phase | Trivial | Small | Medium | Large |
|---|
| Repo Scout | quick | quick | thorough | โ escape hatch |
| Interview | skip | 1 confirmation Q | FEATURE: 2-3 Q, CHANGE: 1-2 Q | โ escape hatch |
| Iterate Spec | skip | skip | own file in .shipwright/planning/iterate/ | โ |
| Spec Impact (ADD/MODIFY/REMOVE/NONE) | always (BUG: classify; NONE default) | always (BUG: classify; NONE default) | always (BUG: classify; NONE default) | โ |
| Mini-Plan | skip | FEATURE only | yes + alternative (all types) | โ |
| User Approval | skip | skip | before build | โ |
| External LLM Review | skip | skip | auto | โ |
| Design Check | skip | Tier 1 (text) | Tier 2 (markdown) | โ |
| Build (TDD) | always | always | always | โ |
| Boundary Probe | skip | if touches_io_boundary | if touches_io_boundary | โ |
| Self-Review | always | always | always | โ |
| Confidence Calibration | skip | if touches_io_boundary | always | always |
| Test Completeness Ledger | n/a (auto) | always | always | always |
| Integration Coverage (cross-component) | if cross_component | if cross_component | if cross_component | if cross_component |
| Full Code Review | only if risk flags | only if risk flags | always | โ |
| Review Record (all 6 types closed) | n/a (auto) | always | always | always |
| Browser Verify | if UI | if UI | if UI | โ |
| Smoke Test | if server up | if server up | if server up | โ |
| Unit Test | --related | --related | full suite | โ |
| Integration Test | if CRUD | if CRUD | full suite | โ |
| pgTAP DB Test | if new RLS | if new RLS | full suite | โ |
| E2E Verification (author + execute) | if feature+UI | if feature+UI or touches_io_boundary | always | โ |
| Design Fidelity | skip | if structural UI | if UI | โ |
| Performance Budget | if touches_build | if touches_build | if touches_build OR if UI | โ |
| architecture.md | if structural impact | if structural impact | if structural impact | โ |
| Test Results JSON | always | always | always | โ |
| run_config iterate_history | always | always | always | โ |
| Session Handoff | skip | if needed | if needed | โ |
| Release Prompt | always | always | always | โ |
E2E Verification "always" at medium+ means author AND run, not author OR run. Spec-only authorship counts as no test (see F0.5). Large routes to the escape-hatch pipeline.
Integration Coverage vs the Ledger at trivial โ the two rows above do not contradict each other. The Ledger is auto-n/a at trivial, but Integration Coverage still fires there when the diff touches cross_component machinery. Both hold because check_integration_coverage reads only test_completeness.behaviors โ never status, and (since iterate-2026-08-01-coverage-gate-recompute-order) never the recorded complexity. So a trivial run that touches that machinery records the one category:"integration" behavior in its F5c entry and passes; it does not have to escalate to medium first, and it does not owe the rest of the Ledger. Escalating is the right move only when the change genuinely warrants the fuller process.
7. Mid-Flight Escalation
See references/mid-flight-escalation.md (trivial โ small โ medium โ large transitions, dirty-tree handling, WIP checkpoint commits). The agent can upgrade complexity mid-flight if scope is expanding.
8. Escape Hatch
See references/escape-hatch.md and references/iteration-planning.md (handoff file format and failure behavior). Triggered when complexity = large.
Finalization (all paths)
CRITICAL: F0โF11 (incl. F3a, F5a, F5b, F5c) are MANDATORY. (โ Phase Timing: mark test at F0, mark finalize at F1 โ see phase-timing.)
Order matters. F0.5 / F3 / F3a / F4 / F5 / F5a / F5b / F5c all write tracked artifacts and MUST run before F6 so a single atomic commit stages them. F5b's work_completed event lands in this worktree's shipwright_events.jsonl, so F6 stages it and it ships in the PR (per-tree, PR-committed model โ iterate-2026-05-29-events-jsonl-worktree-commit). F0.5 is the production-time E2E gate. F6.5 (SHA patch) and F7/F7b are SKIPPED in the normal worktree flow โ they exist only for legacy / out-of-band (non-worktree, replay) event recording. Do not reorder. Speed (optional): F1/F3/F4/F5c/F5b MAY be driven in ONE call via finalize_bundle.py (pure orchestrator; F5 before it, F2/F3a/F6 stay manual; whole-bundle re-run is safe) โ see F-finalize-bundle.
F0: Fresh Verification Gate
See F0. Leak-guard (check_iterate_isolation.py --stage f0), then full test suite. STOP on any failure.
F0.5: End-to-End Verification Gate
See F0.5. Mandatory at medium+. Safety-enforced at small with touches_io_boundary or UI. Advisory at trivial.
Four fail-closed conditions enforced by surface_verification.py (orchestrator) + the post-commit audit verify_iterate_finalization.py: (1) surface != "none" AND tests_run == 0; (2) non-zero exit_code after the 3-retry cap; (3) surface == "none" without a justification; (4) surface_verification block missing at medium+ without an opt-out. Non-zero exit at F0.5 = STOP.
Backend-affects-Frontend rule. If the diff touches API routes, store mutations, SSE/WS handlers, message contracts, or any code consumed by the UI โ surface = web is mandatory even when no client/** file changed. The matrix always cell at medium+ subsumes file-path detection. Spec-only authorship counts as no test (tests_run = 0).
F1 .. F12 โ one-line index
| Phase | Reference | One-liner |
|---|
| F1 | F1 | artifact_sync.py --ref "HEAD~1..HEAD"; update specs if drift |
| F2 | F2 | Architecture update; triggers: new route/component/schema/service/write-surface/read-surface/convention |
| F3 | F3 | write_decision_drop.py keyed by run_id; ADR-NNN assigned at /shipwright-changelog release; field cap 1-3 sentences / 500 chars |
| F3a | F3a | Reflection โ append learnings per references/reflection.md |
| F4 | F4 | write_changelog_drop.py โ one bullet per AC under CHANGELOG-unreleased.d/<category>/ |
| F5 | F5 | Latest-run state under iterate_latest in shipwright_test_results.json โ incl. the test_completeness ledger block (small+) |
| F5b | F5b | finalize_iterate.py โ records work_completed (with commit="") into this worktree's events.jsonl BEFORE compliance regen + handoff; F6 stages it (ships in the PR) |
| F5c | F5c | append_iterate_entry.py โ .shipwright/agent_docs/iterates/<run_id>.json atomically; 50-entry retention |
| F6 | F6 | Commit (Conventional Commits). Explicit git add per-path list โ incl. shipwright_events.jsonl when tracked. NEVER -A. Footer: Run-ID: {run_id} + Co-Authored-By: Claude <noreply@anthropic.com> |
| F6.5 | F6.5 | SKIP in worktree flow โ event ships with commit="". Legacy/non-worktree only: finalize_iterate.py attach-commit โฆ |
| F7 | F7 | Legacy/out-of-band record_event.py. Skip unless replaying / non-worktree. ADR-059 FR-gate applies to ALL iterates incl. BUG |
| F7b | F7b | commit_event_followup.py โ seals an out-of-band F7 main-tree append only (not the worktree flow; idempotent noop otherwise) |
| F11 | F11 | Leak-guard (--stage f11), ensure_current.py refresh-if-behind (integrate+regenerate before arm), push + gh pr create against origin/<default>, update handoff, run verify_iterate_finalization.py, then deliver_pr.py โ the delivery ladder: check the PR is this run's, arm fail-soft gh pr merge --auto --squash (iterate/* only; deferred under campaign SHIPWRIGHT_ITERATE_AUTOMERGE=0 โ orchestrator merges each PR in turn, interleaved-serial), and where the host structurally cannot arm, wait for green โ refresh โ re-verify โ merge pinned to the verified commit. Delivered = MERGED + green (no shoot-and-forget); the summary names WHO merged it and how many checks the host ran |
| F12 | F12 | Count pending drops; prompt for /shipwright-changelog once PR merges; print summary banner |
Degraded Mode & Error Handling
See references/degraded-mode.md (no sync config, stale mappings, no visual-guidelines, browser-verify failure, code-reviewer unavailable, external review opt-out, pipeline handoff failure, no designs) โ record degraded conditions in shipwright_test_results.json.degraded[]. See references/error-handling.md (test failures: 3-attempt circuit breaker; pre-commit hook failures: auto-fix, never --no-verify; missing sync config: TBD/conservative; session handoff: see references/iteration-reviews.md).