Imported from minhduc2803/shipyard (
claude/skills/ship-feature/SKILL.md). Install upstream withnpx skills add minhduc2803/shipyard --skill ship-feature. Copyright stays with the author.
Ship Feature (harness lane pipeline)
You are running the autonomous feature pipeline for one lane of the parallel feature harness. The human's only interactive touchpoints are Stage 0 (frontloaded Q&A) and merging the PR on GitHub; everything else runs to completion or to a blocked escalation, reporting progress through the lane's state file (which the watch dashboard renders).
Setup — do this first, every run
LANE_DIR="$(pwd)" # the lane clone IS your cwd — resolved at runtime, never a hardcoded path
HARNESS="@@HARNESS_ROOT@@" # populated to the real harness path when the harness is installed
N="$(cat .harness-lane 2>/dev/null)" # lane number, read from the clone
- If
.harness-laneis missing, you are NOT inside a lane clone. STOP and tell the user to assign one first: run"$HARNESS/bin/lane-assign.sh" "<feature>"— it picks a free lane, boots it, and prints thecd <lane> && claudecommand. Then re-invoke/ship-featurefrom that lane. (If no lane is bootstrapped yet:"$HARNESS/bin/lane-bootstrap.sh" <n>.) - Paths auto-resolve — never hardcode. Your lane clone is the current directory (
$LANE_DIR); the harness path ($HARNESS) is baked in when the harness is installed. Use these everywhere — including the clone path you hand to subagents — so the pipeline is correct no matter where the project lives. - All state updates go through
"$HARNESS/bin/state.sh" "$N" set key=value ...— call it at the start of every stage (this is also the heartbeat). Always setfeature_titleearly so the dashboard is readable. - Integration toggles. Check which integrations are enabled by executing
"$HARNESS/bin/lane-env.sh" "$N" --check tracker|dev_qc|ci_wait(exit 0 = on). Do NOTsource "$HARNESS/bin/_common.sh"yourself — the Bash tool runs zsh, where sourcing it silently mis-resolves to the_templateprofile and every integration reads OFF (thebin/*scripts are immune — they execute it under their bash shebang). SKIP the stages for disabled integrations — Stage 9 (Ticket) only runs iflane-env.sh "$N" --check tracker, Stage 13's dev-QC only if--check dev_qc, and Stage 13's deploy-wait only if--check ci_wait. (When all three are enabled, the full pipeline runs.) - All-OFF sanity guard (HARD). If ALL toggles read OFF, do NOT accept that silently — it is the signature of the env-resolution bug that once skipped ticket/CI-wait/dev-QC while reporting "pipeline complete". Cross-check against the active profile file:
eval "$("$HARNESS/bin/lane-env.sh" "$N")"thengrep -Ec '_ENABLED=1' "$PROFILE_DIR/integrations.env". If the profile file enables any integration but every--checksays OFF, STOP (status=blocked, note the mismatch) instead of running a silently-degraded pipeline. - Heartbeat during long stages. Implementing (Stage 1), CI waits (Stage 10), and the watch/post-merge polls (Stages 12–13) can run many minutes between stage transitions — bump the heartbeat with
"$HARNESS/bin/state.sh" "$N" setafter each commit and on each poll iteration, so the dashboard doesn't false-flag a working lane as STALLED. (The long helper scripts — ci-gate/e2e/up — heartbeat themselves.) - Helper scripts live in
"$HARNESS/bin/". Use them; don't reinvent their logic. - NEVER merge or rebase branches manually. The ONLY merge that ever happens in this flow is
origin/developmentINTO the feature branch, and only throughlane-sync-dev.sh(it fetches fresh, pre-checks migration collisions, and auto-regenerates generated files). There is no other direction: never merge a feature branch into anything locally, never commit ondevelopment, and never touchmain. - NEVER push
origin/developmentororigin/main(HARD).developmentmoves ONLY when a human merges a PR on GitHub. The only branch you ever push is your ownfeat/<slug>— and only after the senior gate's GO (Stage 8). If you ever find yourself typinggit pushwithdevelopmentormainon the line: STOP,status=blocked.
Context recovery — after conversation compaction
Long pipelines outlive the context window. When context is compacted (summarized), re-derive these critical values before continuing:
LANE_DIR="$(pwd)" # lane clone = cwd (runtime)
HARNESS="@@HARNESS_ROOT@@" # baked at install
N="$(cat .harness-lane 2>/dev/null)"
eval "$("$HARNESS/bin/lane-env.sh" "$N")" # re-establish lane env (API_BASE, FE_URL, DB vars) + integration toggles — never `source _common.sh` (breaks under zsh)
Then check your current position:
- Lane state:
"$HARNESS/bin/state.sh" "$N" get— shows current stage, status, feature_title, branch, gate_decision, pr_url, ticket_url, notes. - Git branch:
git rev-parse --abbrev-ref HEAD— which branch you're on. - Integration toggles:
"$HARNESS/bin/lane-env.sh" "$N" --check dev_qc && echo ON || echo OFF(same fortracker,ci_wait) — do NOT skip or run integration stages from memory; always re-check. - Feature slug: from the state's
branchfield (feat/X→X).
Resume from the stage shown in lane state. If state says stage=X status=running, you were mid-stage X when context compacted — re-run that stage from the top (all harness scripts are idempotent).
- MCP preflight (fail fast): confirm this session actually loaded the lane's required playwright MCPs (their
browser_*tools must be available). The required list adapts to the enabled integrations:playwright+playwright-qa-localare ALWAYS required; the dev-QC MCP (@@DEV_QC_MCP@@) is required ONLY if"$HARNESS/bin/lane-env.sh" "$N" --check dev_qc; the tracker MCP (@@TRACKER_MCP@@) ONLY if"$HARNESS/bin/lane-env.sh" "$N" --check tracker. (When all integrations are on, all four are required.) If any required MCP is missing, do NOT start the pipeline and do NOT let Stage 13 discover it after the human already merged: run"$HARNESS/bin/lane-mcp-sync.sh" "$N", then STOP and tell the human to restart this Claude session (project MCPs load only at session start). Catching this at Stage 0 costs a minute; catching it at Stage 13 strands a merged feature unverified.
Hard rules
- Publish ONLY after the senior-gate-reviewer returns
VERDICT: GO. "Publish" = push the feature branch + open/update the PR (Stage 8). Nothing reviewer-visible exists before GO, and nothing else authorizes it. - The fix-loop: any failure in stages 2–7 (gates/preflight, e2e, review, QC, senior gate), any red PR CI that's genuinely yours (Stage 10), and any worth-fixing review comment (Stage 12) → fix on the feature branch, and re-run from Stage 2 through Stage 8 (gates+preflight → e2e → review → QC plan → QC → senior gate → publish/update PR), then the Stage 9 ticket update (parallel) + Stage 10 CI watch. Never skip a gate — the full process applies; no shortcuts because "it's just review feedback".
- EXCEPTION — test-only re-entry (browser-QC fast-path). If the re-entry's change is ENTIRELY test files —
git diff --name-onlysince the last browser-QC'd commit matches only the profile'sTEST_PATHS(read fromprofiles/$PROFILE/profile.env) — the app's runtime behavior/UI is unchanged from the last QC'd pass. Run this EXACT stage set, nothing else:- Always run: Stage 2 (gates + preflight), Stage 7 (senior gate), Stage 8 (publish/update PR), and the Stage 9/10 tail (ticket update + CI watch).
- Run only if e2e spec files are among the changed tests: Stage 3 (boot + e2e on the feature branch). No e2e specs changed → skip it.
- Always SKIP (runtime UI unchanged): Stage 5 (QC plan), Stage 6 (qc-local). Record
QC skipped: test-only changein the notes. If the merged PR ends up entirely test-only, Stage 13's dev-QC may likewise be skipped with that note. - If the diff contains ANY non-test file → this fast-path does NOT apply; take the full path above. The first pass (not a re-entry) always runs full QC.
- EXCEPTION — localized re-entry (scoped-e2e fast-path). On a re-entry whose diff since the last fully-validated commit is SMALL and LOCALIZED — only files inside the feature's own surface, NO migrations, NO contract/generated files, NO shared fixtures/utilities, NO dependency changes — you may shrink Stage 3's e2e to a SCOPED run of the specs covering the touched surface:
"$HARNESS/bin/lane-e2e.sh" "$N" -- <spec files>(scoped runs heartbeat + lock + time-bound like the full suite, and recordstage=e2e-scoped), and browser QC runs SCOPED to the affected QC-Plan scenarios (tell the qc agents exactly which scenario numbers). Know the trade-off: there is no dev-merged full suite anymore — post-merge dev CI + dev-QC (Stage 13) are the integration net. If in doubt whether the change is localized, it isn't — run the full path.
- EXCEPTION — test-only re-entry (browser-QC fast-path). If the re-entry's change is ENTIRELY test files —
- Run long helpers so they can't be killed mid-flight or hang your turn.
lane-ci-gate.sh,lane-up.sh,lane-e2e.shlegitimately run 3–20+ minutes (builds, tests, Playwright, lock waits — locks time out after 30 min with a clear error). NEVER invoke them with the default Bash timeout (2 min kills them mid-flight and strands the lane half-done): userun_in_background: trueand poll the output file until done, or settimeout: 600000for the shorter gates. If a helper does die mid-run (state says one thing,lane-status.sh/ports say another), don't panic: every helper is idempotent — re-run the step (e.g. re-runlane-up.sh <N> --no-buildto revive a stack). (lane-sync-dev.shis quick — a fetch + merge — and needs no special handling.) - Waiting + polling NEVER use a foreground
sleep(the Bash tool blocks it) orScheduleWakeup(that's a/loop-only primitive —/ship-featureis not a/loopsession, so it won't sustain your watch). To pace a poll loop or wait out a timer, background the wait: runsleep <secs>withrun_in_background: true— the harness re-invokes you when it exits, and re-invokes you the moment a backgrounded Agent (Stage 9 ticketer / Stage 13 dev-qc) or helper finishes, so you never busy-poll for background work. If you ever can't sustain a wait/loop in this session, set lane state with an honestnotes=and STOP — never narrate a watch or loop you are not actually running. - e2e: actively poll — never wait on the completion re-invoke alone. A hung suite never fires it, stranding the lane at
stage=e2e-feature. When runninglane-e2e.sh(Stage 3): start itrun_in_background: trueAND background asleep 90beside it. Each wake — finished → parse PASS/FAIL; still running → read the e2e log tail, bump the heartbeat, and re-backgroundsleep 90, UNLESS it's erroring or has run past ~22 min, in which case kill the e2e task and treat it as FAIL → re-enter Stage 2. (lane-e2e.shself-bounds too: it hard-times-out the Playwright run atE2E_TIMEOUT(default 1200s) and exits FAIL, so a hang still surfaces even if a poll is missed.) - Turn-liveness: never let the pipeline die silently. Most "stalled" lanes died one of two ways: (a) a turn ended with NOTHING pending — no backgrounded wait, no running helper, no background agent — so nothing ever re-invoked the session; or (b) a transient API error (rate-limit, 529/overload, connection refused) killed the turn mid-stage. Rules: while the pipeline is anywhere between Stage 1 and Stage 14 (done), every turn you end MUST leave at least one re-invoker pending (a
run_in_backgroundhelper/sleep or a background agent) — check before ending the turn. And on ANY resume after an error or a human nudge ("continue"), do not ask questions: re-derive position from lane state (Context recovery above) and continue the stage. If you truly cannot leave a re-invoker, setnotes="watch needs re-trigger: <what to do>"so the dashboard shows it honestly. - No retry cap — the phase clock is the signal. A failing gate/QC/CI/e2e just re-enters the loop (fix on the feature branch, re-run from Stage 2); there is NO automatic block after N attempts. The dashboard shows how long the lane has sat in its current phase (
stage_since), so the human can spot a stuck or endlessly-looping lane and step in. Reservestatus=blockedfor GENUINE blockers you cannot resolve (an ambiguous merge conflict, a hard/unrecoverable error). - Commit only on the feature branch. Never commit on
development/main. Stage only intended files (this repo collects stray*.png/QA artifacts — nevergit add .). - Keep the lane's state truthful: on any stop, set an accurate
stage/status/notes. - Quality bar (applies to every code change, including fix-loop re-entries and follow-up PRs). Tests are sharp and meaningful — each pins a real behavior/edge case (happy + negative + boundary), none trivial, redundant, or coverage-padding. Comments are minimal — only the non-obvious why, matching the surrounding density; never narrate the what. Investigate before fixing (root cause, not symptom). Prefer reusing/extending existing code over duplicating it.
- One driver per MCP browser server. Each MCP server owns ONE browser; two agents driving the SAME server interleave clicks in one tab. Ownership map:
playwright-qa-local→ the qc-local agent (Stage 6);playwright→ the main session for ad-hoc checks only (never while qc-local runs);@@TRACKER_MCP@@→ the ticketer agent;@@DEV_QC_MCP@@→ the dev-qc agent. Parallel agents on DIFFERENT servers are safe by design; a second concurrent driver on the SAME server is never OK. - Cross-lane etiquette (locks + siblings). Lanes share one machine and one dev site. Waiting on a cross-lane serializer (the build/e2e locks inside the helpers, or any
lane-lock.shlock) is NORMAL — helpers heartbeat while they wait, so you won't look stalled. NEVER free a lock by killing another lane's session or processes, deletingharness/locks/*, or shrinkingLOCK_MAX_HOLD; a dead holder's lock auto-expires on its own. If a lock wait times out: re-try with a longer timeout, or setstatus=blockedwith a note and report. Touch ONLY your own lane's clone, state, and locks you hold. On the shared dev site, use ONLY your lane's QA account (<lane>/.harness-qa.env) and never touch data another lane's account created.
Stages
0 — Intake & frontloaded Q&A (the only interactive part)
Do NOT jump to code. Understand the requirement first.
- Restate + quick scan. Restate the requirement. Do a fast targeted scan of the relevant code (use the
Exploreagent for breadth; the brainstorming skill if the requirement is fuzzy) so your questions are grounded in what actually exists. - Frontloaded Q&A. Ask the human every clarifying question in ONE batch: acceptance criteria, scope / non-goals, UI/UX specifics, data shapes, edge cases, which existing flows it touches. Sibling-surface check (mandatory): if your scan shows the app has N parallel surfaces of the pattern the requirement touches (e.g. several foldered areas, several list pages sharing a component) and the requirement names fewer than N, explicitly ask "this exists in [all N places] — apply to all, or only [the named ones]?" A missed sibling here costs a full second pipeline pass when a reviewer catches it on the PR. Then open a clean per-feature state slot so this run can't write into a previous feature's state even if the human didn't click clear first:
"$HARNESS/bin/state.sh" "$N" init— this preserves prior features' files (they stay browsable in the dashboard) and resets.active→_pending, so the dashboard map reflects THIS run, not the last one. Then mark intake on the fresh slot:"$HARNESS/bin/state.sh" "$N" set feature_title="<short title>" stage=intake status=running. Announce "Questions answered — going autonomous now." After this, don't ask the human anything unless you hit ablockedescalation.
0b — Investigate & plan (autonomous)
state.sh "$N" set stage=plan— now design a real plan and have it independently challenged before you implement.- Investigate (autonomous, thorough). Read the actual code paths, models, existing tests, and conventions the feature touches —
Explore/general-purposesubagents for breadth, then read the key files yourself for depth. Pin down: integration points, data/migration needs, API/contract impact, reuse opportunities, and risks. Use systematic-debugging if the feature is a fix (root-cause first, no symptom patches). - Plan. Produce a concrete implementation plan (the writing-plans skill): approach, files to change, the test strategy (which behaviors/edge cases each test will pin), migration/contract impact, and how each acceptance criterion is met.
- Debate the plan (adversarial review). Spawn a SEPARATE sub-agent (Agent tool —
Planorgeneral-purpose) to critique the plan + investigation: missed requirements, wrong assumptions, a simpler approach, unhandled edge cases, acceptance-criteria gaps. Apply the worthwhile critiques (use receiving-code-review judgment — verify each point, don't blindly accept or reject). Iterate once or twice until the plan holds up. - Write the Q&A answers and the agreed plan to the lane spec file
docs/superpowers/specs/lane<N>-<slug>.md(gitignored) — the acceptance contract the senior gate checks against.
1 — Implement (TDD, to the plan)
- Choose a single-segment slug for the feature — lowercase, hyphens, NO slashes. The slug keys the branch, the state file (
state/laneN/<slug>.json), AND the proof dir (proof/<slug>/); they must match or the dashboard can't join them. Cut the feature branch from development (the PR base):git fetch origin && git checkout -b feat/<slug> origin/development. - Activate per-feature state tracking and capture the canonical slug:
SLUG="$("$HARNESS/bin/state.sh" "$N" activate feat/<slug>)".activatesanitizes the slug (dropsfeat/, turns/and spaces into-), sets the.activepointer, renames the Stage-0_pending.jsonto<slug>.json, and echoes the canonical<slug>. Use that$SLUGfor the proof paths and every agent handoff so state ↔ proof stay joined. - Implement the agreed plan with the test-driven-development skill: failing test → minimal code → green → commit. Frequent small commits.
- Tests must be sharp and meaningful. Each test pins a real behavior or edge case from the plan / acceptance criteria — cover the happy path, the negative/error path, and boundaries. NO trivial or redundant tests: don't assert constants or framework internals, don't re-test the same path twice, don't pad for coverage. A few precise tests that would actually catch a regression beat many shallow ones.
- Comment only when it earns its place. Match the surrounding code's comment density. Comment the non-obvious why (intent, invariants, gotchas, links to context) — never narrate the what the code already says. Delete redundant/boilerplate/restating comments rather than adding them.
- If your stack generates an API contract/client and the API changed, regenerate it so the contract-check gate passes (stacks without a contract gate skip this).
state.sh "$N" set stage=implementing branch=feat/<slug>
2 — Pre-push CI gates + dev preflight (on the feature branch)
"$HARNESS/bin/lane-ci-gate.sh" "$N"— runs the profile's CI gate (lint / test / contract checks) against an isolated per-lane test DB. On failure: read the output, fix on the feature branch, commit, re-run. Loop until green."$HARNESS/bin/lane-sync-dev.sh" "$N" --check feat/<slug>— the dev preflight: fetches and checks the branch against the CURRENTorigin/developmentwithout merging anything.- Exit 5 — migration-number collision: a migration you added reuses a number already on
origin/development(another lane's landed first). It prints the exact rename — do it on the feature branch (git mv, update any in-file references), then re-run Stage 2 (the gates re-validate the renamed migration). - It also prints
DEV_DELTA:/DEV_OVERLAP:— how far development has moved since your branch's merge-base and whether that delta touches your files. Informational: you do NOT sync the branch for it (GitHub merges non-conflicting histories fine); a large overlapping delta is a heads-up that post-merge behavior may differ from what you test locally.
- Exit 5 — migration-number collision: a migration you added reuses a number already on
3 — E2E on the feature branch
lane-e2e.sh doesn't run migrations itself — it tests the already-running stack. To exercise the feature's code and any new schema, boot the lane stack with the feature branch first:
state.sh "$N" set stage=e2e-feature status=running"$HARNESS/bin/lane-up.sh" "$N" --qc— boots with the profile's QC env (--qcappliesQC_BOOT_ENV: mock/stub flags so QC is deterministic), applies the feature branch's own migrations, and reboots the stack athttp://localhost:300<N>. Idempotent; safe to re-run. (The branch was cut fromorigin/development, so this stack IS development + your feature as of the branch point.)"$HARNESS/bin/lane-e2e.sh" "$N"— Playwright e2e under the e2e lock against the now-booted stack. This is the only e2e gate in the flow — there is no dev-merged suite behind it.- On failure: fix on the feature branch, commit, re-run from Stage 2.
- The e2e hook self-heals its two classic environment failures — trust it before diagnosing: (1) it probes the FE's built static assets and auto-reboots
--no-buildif a gate's build left the running stack stale (the old "every page stuck Loading / all selectors fail" wipeout); (2) it resets the lane DB to a fresh migrate+seed before each FULL run, so leftover data from killed runs can't fail pagination/visibility specs (E2E_DB_RESET=0opts out; scoped runs skip the reset by default). If a run still fails wholesale after those, THEN rebuild:"$HARNESS/bin/lane-up.sh" "$N" --qcand re-run. - Iterating on a failing spec: use scoped runs through the harness —
"$HARNESS/bin/lane-e2e.sh" "$N" -- <spec file/filter>— never a bare test-runner invocation in the lane (bare runs skip the cross-lane lock, the hard timeout, and the heartbeat, so the dashboard false-flags STALLED). A scoped green is never the gate; finish with the full suite (unless the localized fast-path applies — see Hard rules). - On success:
state.sh "$N" set stage=e2e-feature-passed status=running
4 — Code review (no open PR yet — use local diff)
- Run the
code-reviewskill at efforthighon the feature diff vsorigin/development— this is the deterministic code-review gate, not an ad-hoc read. The PR isn't open yet, so point it at the local diff:git diff origin/development...feat/<slug>(andgit log origin/development..feat/<slug>for commits). ONLY if thecode-reviewskill is unavailable, fall back to a manual review of that diff (correctness, security, tests, migration/contract safety). The Stage-6qc-localreport covers the user-flow review for the senior gate. - Apply the fixes worth making on the feature branch; if you change code, re-run from Stage 2.
state.sh "$N" set stage=review
5 — QC plan (bound the test scope before any browser QC)
- Author a QC Plan the browser-QC agents (Stage 6 now, Stage 13 after the merge) will execute against — so QC covers everything that matters and nothing that doesn't (no missed scenarios, no wandering into unrelated areas). Derive it from the acceptance points (lane spec) + the real change surface (
git diff origin/development...feat/<slug>and--stat). Three parts:- In-scope scenarios (numbered): each acceptance point with positive AND negative cases; adjacent flows sharing routes/components/data with the change; required state coverage (reload on each stateful screen touched, one logout→re-login, back/forth nav); and the required UI/UX layout checks for every form/screen the feature touches (narrow AND short viewport, expandables open so content exceeds the viewport, fixed chrome not clipped, every control labelled, section headers more prominent than field labels).
- Out-of-scope (explicit): areas NOT to test because the change cannot affect them — this is what stops QC from over-testing.
- Smoke set: login + main nav + ≥3 unaffected major areas.
- Append it to the lane spec under a
## QC Planheading (docs/superpowers/specs/lane<N>-<slug>.md) — the same file the senior gate reads. You are the single writer of this section; the QC agents only propose additions in their reports and you fold them in (Stages 6/13). This keeps the plan race-free yet living. state.sh "$N" set stage=qc-plan status=running
6 — Browser QC via the qc-local agent
- Test-only fast-path: on a fix-loop re-entry whose change is ENTIRELY test files (see the fix-loop rule), SKIP this stage — the app's runtime UI is unchanged — and record
QC skipped: test-only change. Otherwise run it: state.sh "$N" set stage=qc status=running, then launch the qc-local agent (Agent tool,subagent_type: qc-local— FOREGROUND; it gates the pipeline). Give it: lane N + clone path, the feature slug (feat/<slug>→<slug>), the feature title, the acceptance points (lane spec), and the QC Plan (lane spec, Stage 5) as the authoritative scope to execute against. It owns the whole local browser QC:playwright-qa-local, the lane seed account, the planned feature + smoke + reload/re-login coverage, the UI/UX layout pass, upload fixtures (if configured), and proof toproof/<feature-slug>/qc-local/<NN>-<what>.png(the dashboard gallery path). It runs against the lane's feature-branch stack from Stage 3. Do NOT drive the browser yourself at this stage.- Parse its last line:
LOCAL-QC: PASS→ continue.LOCAL-QC: FAIL — <reasons>→ fix on the feature branch → re-run from Stage 2. Keep its report — it is the feature user-flow review for the senior gate. - Fold back discoveries: if its report lists scenarios it found that weren't in the plan (its "Scenarios discovered during QC" section), add them to the
## QC Planin-scope list in the lane spec — so the senior gate, any re-entry, and the post-merge dev-QC all run the updated scope.
7 — Senior GO/NO-GO gate (authorizes the publish)
- Launch the senior-gate-reviewer agent (Agent tool,
subagent_type: senior-gate-reviewer). Give it: lane N + clone path, the requirement + Stage-0 answers (the lane spec file), the feature branch, the Stage-4 code-review findings + resolutions, the Stage-6qc-localreport (the user-flow review), the Stage-2lane-sync-dev.sh --checkoutput, and confirmation that gates/e2e/review/QC passed. The agent inspects the local diff withgit diff origin/development...feat/<slug>— no open PR is required (and none exists yet). - Parse its final line:
VERDICT: GO→ proceed to Stage 8.VERDICT: NO-GO — <fixes>→ fix on the feature branch, re-run from Stage 2. No attempt cap — the loop re-enters; the dashboard's time-on-phase surfaces a lane stuck cycling so the human can step in. Setstatus=blockedonly for a genuine blocker you can't resolve.
state.sh "$N" set stage=gate gate_decision=GO(or NO-GO)
8 — Publish: push branch + open/update PR (GATED — only on GO)
state.sh "$N" set stage=publishing status=running- Re-run the preflight — development may have moved while you were in QC:
"$HARNESS/bin/lane-sync-dev.sh" "$N" --check feat/<slug>. Exit 5 (a collision landed since Stage 2) → renumber on the branch and re-enter from Stage 2. Otherwise proceed. git push -u origin feat/<slug>— first push of the feature branch to remote. All gates have passed before this point; the PR is finalized before reviewers see it.- Open or update the PR based on and targeting
development:gh pr create --base development --fill(orgh pr edit/ the push itself if a prior run already created it). Capture the URL. state.sh "$N" set stage=pr-open pr_url="<url>"— the dashboard shows the PR link from here.
9 — Ticket (parallel — kick off right after Stage 8; NON-blocking)
- Only if
"$HARNESS/bin/lane-env.sh" "$N" --check tracker— otherwise skip this stage entirely (no ticket; leaveticket_urlempty and note 'tracker integration off'). - Immediately after the PR is open, launch the ticketer agent in the background (Agent tool,
subagent_type: ticketer,run_in_background: true). Give it: lane N + clone path, the feature slug, title, PR URL, and a one-paragraph summary of what shipped. On a follow-up run (Stage 15), also give it the parent feature'sticket_urland say "UPDATE this ticket (add the follow-up PR link) — do not create a new one." It creates or updates one ticket in the configured tracker (project/status/assignee from its injected Tracker target line; idempotent — update, never duplicate) and writes the copy-paste HTML task report toproof/<feature-slug>/ticket/REPORT.html(the dashboard shows it as the 🎫 link). - Do NOT wait for it — proceed straight to Stages 10–12 while it runs. When its result arrives, parse the last line
TICKET: <url>and record the evidence:state.sh "$N" set ticket_url="<url>"— the map shows the ticket node ⚠ untilticket_urlis set.TICKET: FAIL — <reason>→ do NOT swallow it: leaveticket_urlempty (⚠ stays) and note it. - If it died without a result, re-run it (idempotent). Don't let the lane finish with the ticket ⚠ unexplained.
10 — CI watch on the PR (non-blocking)
- Check the PR's CI with
gh pr checks/gh. Recordci_status. Green → continue to the report + watch — never idle waiting for green. - Red CI → triage BEFORE the fix-loop (shared CI flakes under multi-lane load; treating every red as your defect wastes cycles):
- If the CI API helper is configured (
"$HARNESS/bin/ci-job.sh" status <branch>— it dies with a clear message when no token is set, in which case fall back togh+ the old judgment), read WHICH job/tests failed:ci-job.sh failures <job#>. - Your tests / your code implicated → real failure: fix on the feature branch, re-enter from Stage 2.
- Infra/flake signature (a job with no test failures, OOM/contention on the shared runner, a hung job with no output, or a test that is green locally on the identical tree) →
ci-job.sh rerun <workflow-id>(re-runs from failed) — ONCE. Still red after the rerun → treat it as real (or escalate with the evidence). Never rerun more than twice, and never push an empty commit to re-trigger CI. - A hung workflow (running way past its normal duration with no output) →
ci-job.sh cancel <workflow-id>then rerun once.
- If the CI API helper is configured (
- Without the helper configured, red CI falls back to the old path: check with
gh, and if you cannot see why it failed, say so innotesand escalate rather than guessing.
11 — Report
- Post a concise report: PR URL, CI status, ticket URL, what shipped, and that the PR now awaits a human merge (the harness never merges). If the background ticketer hasn't reported yet, say so rather than waiting for it.
state.sh "$N" set stage=reported status=running- Do NOT reset the lane. Cleanup is the human's call, from the dashboard (clear tidies the status fields; reset wipes to clean development) — they may still be manually testing.
12 — Watch the PR (until a human merges or closes it)
state.sh "$N" set stage=watching-pr notes="watching PR for comments + base conflicts + the merge"- Loop every ~5 minutes, paced by a backgrounded wait so the turn isn't pinned (see the waiting-primitive rule above): run
sleep 300withrun_in_background: true— the harness re-invokes you when it elapses, and sooner if a background agent finishes. Each iteration:- Collect background agents first (their results arrive between polls): the Stage-9 ticketer (
TICKET: <url>→ recordticket_url; re-run if it died). - Run
"$HARNESS/bin/lane-pr-comments.sh" "$N"— printsPR_STATE:,PR_MERGEABLE:, + any comments newer than the lane's cursor (issue comments, inline review comments, review verdicts), and bumps the heartbeat. PR_STATE: MERGED→ a human merged it: go to Stage 13 (post-merge verification).PR_STATE: CLOSED(unmerged) → the human rejected/abandoned it:state.sh "$N" set stage=done status=passed notes="PR closed unmerged by human"→ STOP.PR_MERGEABLE: CONFLICTING→ the feature branch conflicts with its base. Legacy-PR guard first: check the base —gh pr view <pr_url> --json baseRefName -q .baseRefName. If it is NOTdevelopment(an in-flight PR from the old main-based flow), do NOT auto-merge anything:status=blocked notes="legacy main-based PR conflicts — human decision"and STOP. If the base ISdevelopment, resolve it as real work:"$HARNESS/bin/lane-sync-dev.sh" "$N" feat/<slug>— merges the latestorigin/developmentINTO the feature branch (the only sanctioned merge). Exit 4 → the conflicted merge is left in place on the branch: resolve every conflict thoughtfully — keepdevelopment's behavior for code unrelated to this feature, preserve the feature's intent where they overlap; when genuinely ambiguous, STOP and escalate (status=blocked, note the files) rather than guess. Thengit addONLY the conflicted files,git commit --no-edit, and"$HARNESS/bin/lane-sync-dev.sh" "$N" --continue feat/<slug>(it folds regenerated artifacts in). Never hand-merge generated contract files — the keep-ours driver + regen own them.- Re-enter the pipeline from Stage 2 through Stage 8 (the push updates the PR), then return here and keep watching.
- (
PR_MERGEABLE: UNKNOWNis GitHub still computing — ignore it; it resolves by the next poll.BLOCKED/BEHINDetc. in the parenthesized status are not conflicts — ignore; GitHub merges a behind-but-clean branch fine.)
- If a poll surfaces BOTH new comments worth fixing AND a conflict, handle them in ONE cycle: sync
origin/developmentin first, apply the comment fixes on top, then a single re-entry from Stage 2. - For each new comment, triage AND always reply on its thread (every comment gets a response — no silent handling, so reviewers see it was considered):
- Worth fixing (reviewer-requested change, real bug, test/doc gap): this is a NEW CHANGE — apply it on the feature branch and re-enter the pipeline from Stage 2 through Stage 8 (+ ticket update ∥ CI watch). The full process applies; no shortcuts because "it's just review feedback". After the fix is pushed, reply to the comment confirming resolution — what changed + the commit/PR ref — by writing the reply to a file and posting
gh pr comment <pr_url> --body-file <path>referencing the comment. Never inline--body "..."— bodies carry backticks/file:line/$(...)that bash reads as command substitution inside double quotes, which corrupts the comment and trips an approval prompt. (Usegh pr comment/gh pr review; avoid rawgh apifor replies — those are the pre-authorized commands.) Then come back here and keep watching. - Question / discussion: answer it via
gh pr comment <pr_url> --body-file <path>(same file-not-inline rule) — no code change. - Not worth fixing (out of scope, working as intended, deferred): reply with the reasoning so the reviewer knows why it wasn't actioned (don't just skip it), and note it in
notes. - Sign every reply with a distinct attribution so humans and other agents (the pr-reviewer, other lanes) can tell which automated agent wrote it — and so you recognise your own threads on the next poll (you DO reply to / resolve your own comments; the signature is how they're told apart, not a reason to skip them). End each posted body, on its own line, with:
— 🤖 ship-feature pipeline · lane <N>. (Post viagh pr comment/gh pr review --body-file, never inline.) Keep replies concise and specific; never leave a worth-fixing or not-fixing decision without a reply on the thread.
- Worth fixing (reviewer-requested change, real bug, test/doc gap): this is a NEW CHANGE — apply it on the feature branch and re-enter the pipeline from Stage 2 through Stage 8 (+ ticket update ∥ CI watch). The full process applies; no shortcuts because "it's just review feedback". After the fix is pushed, reply to the comment confirming resolution — what changed + the commit/PR ref — by writing the reply to a file and posting
- Nothing new → background another
sleep 300(run_in_background: true) and end the turn; you'll be re-invoked for the next poll. A PR can sit for days — that's fine. (If you genuinely can't background the wait, hand off honestly per the waiting-primitive rule — set state with a note that the watch needs a re-trigger — rather than faking it.)
- Collect background agents first (their results arrive between polls): the Stage-9 ticketer (
13 — Post-merge verification (the human merged — now prove it on dev)
state.sh "$N" set stage=merged status=running notes="PR merged — verifying on dev"- Dev CI first (only if
"$HARNESS/bin/lane-env.sh" "$N" --check ci_wait): the merge commit onorigin/developmentmust build/deploy. Watch it with the Stage-10 triage rules (ci-job.sh status development/ghcommit statuses; flake → rerun ONCE). Genuinely red on your merge → that IS an issue found on dev: go to Stage 15 with the CI failure as the finding. (ci_wait off → skip straight to dev-QC.) - Dev-QC (only if
"$HARNESS/bin/lane-env.sh" "$N" --check dev_qc): spawn the dev-qc agent in the background (Agent tool,subagent_type: dev-qc,run_in_background: true). Give it: lane N + clone path, the feature slug, the feature title, the acceptance points, the merged PR number/URL (its change surface —gh pr diff <n>), and the QC Plan (lane spec) as the scope to execute against — including any scenarios folded in since. The agent owns the WHOLE dev-QC lifecycle: the deploy-wait for the CURRENTorigin/developmentHEAD (only when CI-wait is enabled), browser QC of the configured dev site via the dev-QC MCP (@@DEV_QC_MCP@@) logged in as the lane's OWN dev-QC account, proof screenshots toproof/<feature-slug>/qc-dev/<NN>-<what>.png, and theqc_devstate field (running|passed|failed). It does NOT touchstage/status— the main session owns those. No cross-lane lock: lanes dev-QC in parallel on their own accounts; mid-run deploys are handled by the agent (cumulative merges + reload-and-retry). - Keep the watch-loop pacing while it runs (backgrounded sleeps; collect the verdict when it lands):
DEV-QC: PASS→ fold any "Scenarios discovered during QC" into the## QC Plan, then Stage 14.DEV-QC: FAIL — <reasons>→ issues found on dev: Stage 15 (follow-up fix PR).DEV-QC: DEFERRED — <reason>→ not a failure: the deploy wasn't live in time. Note it, keep the watch pacing, and re-spawn a fresh dev-qc once CI shows the deploy green. The lane must not finish with dev-QC neither run nor explained.- Agent died with no verdict → check
proof/<slug>/qc-dev/RESULTS.partial.mdfirst (it checkpoints per-scenario), then re-spawn it (idempotent: it resumes from the partial when the deploy sha matches).
- Both toggles off → nothing to verify post-merge: note 'dev verification off' and go to Stage 14.
- Never QC the dev site inline in the main session, and never drive the dev-QC MCP (
@@DEV_QC_MCP@@) yourself while a dev-qc agent is out. If dev-QC's fail looks like ANOTHER lane's feature broke dev (not yours), still report it — open the follow-up only for what your merge caused; escalate the rest innotesfor the human.
14 — Done
- Post the final report: PR merged, dev CI status, dev-QC verdict + proof gallery, ticket URL.
state.sh "$N" set stage=done status=passed notes="merged + verified on dev; lane ready for cleanup"→ STOP (leave the lane for the human to clear/reset from the dashboard; they close the session/topic whenever).
15 — Follow-up fix PR (dev verification found issues — loop until clean or the human closes)
This is a NEW gated pass, not a hotfix shortcut. For the batch of findings from Stage 13:
- Capture the parent's evidence first (before touching state):
PARENT_TICKET="$("$HARNESS/bin/state.sh" "$N" get ticket_url)", the parent slug, and the findings (dev-QC report / CI failure). - Open a fresh state slot + branch keyed to a follow-up slug:
state.sh "$N" init, thengit fetch origin && git checkout -b feat/<slug>-fix<K> origin/development(K = 1, 2, … per follow-up round), thenSLUG="$("$HARNESS/bin/state.sh" "$N" activate feat/<slug>-fix<K>)"andstate.sh "$N" set feature_title="<title> — follow-up <K>" stage=plan status=running. The parent run's state/proof stay browsable in the dashboard. - Write the follow-up's lane spec (
docs/superpowers/specs/lane<N>-<slug>-fix<K>.md): the dev findings ARE the acceptance points (reference the parent spec; carry the parent's## QC Planforward, scoped to the affected scenarios). No new Stage-0 Q&A — but do a real 0b: root-cause each finding (systematic-debugging), plan the fix, debate it if non-trivial. - Run the pipeline from Stage 1 through Stage 12 on the follow-up branch (TDD fix → gates+preflight → e2e → review → QC plan/QC as warranted by the change class → senior gate → publish
--base development→ ticket UPDATE with$PARENT_TICKET∥ CI watch → report → watch). The fast-path rules apply as usual. - When a human merges the follow-up PR → Stage 13 verifies dev again. PASS → Stage 14. FAIL → another Stage-15 round (
-fix<K+1>). The loop only ends at a clean dev verification (Stage 14) or when the human closes the topic/session.
Escalation
Whenever you STOP early (an ambiguous merge conflict, an unexpected/unrecoverable failure), set status=blocked (or failed) with a one-line notes= explaining what the human must decide — the dashboard surfaces it. Then summarize for the human and wait.