[Bugfix #872] Fix: emit canonical pr_ready_for_human signal across all protocols#874
Conversation
…l protocols PR #844 gated the dashboard's Needs Attention list on `b.blocked === 'PR review'`, which silently dropped BUGFIX PRs because BUGFIX has no `pr` gate (it transitions straight to phase `verified` after CMAP). The derivation worked for AIR/SPIR/ ASPIR/PIR but never for BUGFIX, and the only way to find out was to ship the regression — which we did in v3.1.3. This change adds an explicit `pr_ready_for_human: boolean` field to status.yaml that porch sets when transitioning out of the CMAP-emitting state for the PR-creating phase, for ALL bundled protocols: - SPIR/ASPIR/PIR review (build_verify, gate=pr) → next.ts handleVerifyApproved - AIR pr (once-phase, gate=pr) → index.ts done() auto-request - BUGFIX pr (once-phase, no gate, terminal) → index.ts advanceProtocolPhase The PR-creating phase is identified by either `gate === 'pr'` or a `consultation` block in protocol.json (preserved on ProtocolPhase via `hasConsultation`). The new `isPrCreatingPhase` helper centralizes this so adding a new protocol with a CMAP-emitting PR phase just means landing either marker. The field is reset to false when the human acts: `pr` gate approval (covers all four protocols with a gate) and rollback. BUGFIX has no gate so the field stays true until cleanup deletes the worktree — which matches the cohort's mental model (a BUGFIX builder is done after the PR is created). The consumer side (overview.ts) reads the field if present, else falls back to the v3.1.3 derivation plus a BUGFIX-specific case (`phase === 'verified' && protocol === 'bugfix'`) so in-flight BUGFIX builders from before this change become visible immediately. The dashboard NeedsAttentionList now gates on the canonical `b.prReady` boolean instead of the protocol-specific `b.blocked === 'PR review'` check. Fixes #872
…ocols Adds pr-ready-872.test.ts with 8 regression tests covering the state-machine touchpoints for SPIR/ASPIR/PIR (build_verify review), AIR (once-phase pr with gate), and BUGFIX (once-phase pr without gate — the v3.1.3 regression). Covers the REQUEST_CHANGES path (field stays false during rebuttal cycle) and the reset transitions (pr-gate approval + rollback). Extends overview.test.ts with 6 tests for derivePrReady: explicit field wins over derivation, fallback fires for legacy state files (SPIR-shape and the BUGFIX gap the canonical signal closes), and post-merge SPIR `verified` does NOT fire the BUGFIX-specific fallback case. Updates the existing NeedsAttentionList tests to use the new `prReady` API and adds two new tests: BUGFIX PR visibility (the v3.1.3 regression) and blockedSince→createdAt fallback for prReady builders without a gate.
CMAP Review (3-way, iter 1)All three reviewers APPROVE with HIGH confidence.
KEY_ISSUES from all three: none. Claude flagged one non-blocking observation: the condition Review files committed in |
Architect integration reviewRan a 3-way architect-side CMAP per the dual-CMAP discipline. The builder's own CMAP was unanimous APPROVE but the architect-side caught two real bugs both other reviewers missed individually. Posting findings here so the PR record reflects them. Verdicts: Gemini REQUEST_CHANGES (HIGH) · Codex REQUEST_CHANGES (HIGH) · Claude APPROVE (HIGH). The state-machine three-tier design ( Required fix 1:
|
…allback Architect-side CMAP on iter-1 caught two real defects that the builder-side CMAP missed individually (classic dual-CMAP-catches-more pattern). Both are fixed here. **Fix 1: isPrCreatingPhase overmatched RESEARCH** (Codex finding). The iter-1 classifier triggered on any phase carrying a consultation block, but RESEARCH's investigate / critique phases also carry consultation blocks for non-PR purposes (type: "investigation" / "critique"). Consequences: a research builder would have been marked pr_ready_for_human: true even with no PR involved, and approve() only clears the flag for gate=pr, so a non-PR consultation phase advancing via a different gate would leak the flag. Narrowed the marker to `consultation.on === 'review'`, which BUGFIX and AIR both carry today and RESEARCH does not. Renamed ProtocolPhase.hasConsultation to hasPrConsultation to reflect the narrower semantics. **Fix 2: NeedsAttentionList dropped BUGFIX builders on cache miss** (Gemini finding). The builder loop early-exited on `!b.blocked || !b.blockedSince` before checking prReady, and BUGFIX builders always have both as null (no `pr` gate). With a realistic BUGFIX shape, if the PR was missing from the cached prs array the builder would silently disappear — the exact defensive case this PR is supposed to cover. Restructured the loop so prReady is checked BEFORE the gate-blocked early-out. prReady builders are emitted with kind='PR review' and a waitingSince fallback chain (blockedSince → startedAt → now) since gateless protocols don't supply blockedSince.
…stic BUGFIX shape Adds 6 isPrCreatingPhase classifier tests covering the two markers (gate==='pr' / consultation.on==='review') across AIR, BUGFIX, SPIR-shape, RESEARCH investigate (must be false), RESEARCH critique (must be false), and non-PR phases. Without the iter-2 narrowing fix the two RESEARCH cases would fail — pins the regression so it can't return silently. Updates the iter-1 'still surfaces builder when PR missing from prs' test into two cleanly separated cases: a realistic BUGFIX shape (blocked=null, blockedSince=null) that would have failed against the iter-1 NeedsAttentionList loop, and the AIR-style gated variant that preserves the original coverage. The BUGFIX assertion also pins the waitingSince fallback chain (blockedSince → startedAt) so silent fallback regressions surface.
CMAP Review (3-way, iter 2)Re-ran 3-way CMAP after addressing both architect findings. All three reviewers APPROVE with HIGH confidence.
KEY_ISSUES from all three: none. What changed in iter-2
|
|
Approved. Both fixes are surgical and well-commented — the inline note in Please merge with |
v3.1.4 PR #874 wired BUGFIX's pr_ready_for_human=true to the advanceProtocolPhase terminal-exit setter because BUGFIX had no `pr` gate. That set-point only fires when the builder calls `porch done` post-merge — by which point the human has already acted. BUGFIX PRs sat at `phase: pr, pr_ready_for_human: false` indefinitely, never surfacing in Needs Attention. This adds "gate": "pr" to BUGFIX's pr phase (same shape as AIR), so the canonical signal fires via the gate-request setter at the right moment — when CMAP completes and the PR is waiting for a reviewer. The BUGFIX-no-gate branches in advanceProtocolPhase, handleVerifyApproved, and isPrCreatingPhase are now redundant and removed; hasPrConsultation field dropped from ProtocolPhase. The derivePrReady BUGFIX fallback in overview.ts is intentionally left in place as graceful-degradation for in-flight v3.1.4 projects. Fixes #887
Cross-reference the defensive prReady builder-fallback against the workspace's recently-merged PR set. After a PR merges, pendingPRs (open PRs only) correctly omits it, but in-flight builders that crossed the v3.1.4 → v3.1.5 line-453 setter boundary keep stale pr_ready_for_human in status.yaml. The iter-2 cache-miss defense (PR #874) couldn't distinguish "PR missing because cache miss" from "PR missing because merged" and surfaced both — emitting a stale "PR review" row for work already shipped. Adds OverviewData.recentlyMergedIssueIds (derived from the already- fetched mergedPRs list via parseLinkedIssue) and plumbs it into NeedsAttentionList.buildItems. The defensive emit now skips when the builder's issueId is in the merged set. Fixes #901
Summary
Fixes #872.
PR #844 (v3.1.3) gated Needs Attention on
b.blocked === 'PR review', a protocol-specific derivation that worked for AIR/SPIR/ASPIR/PIR but silently dropped BUGFIX PRs (BUGFIX has noprgate — it transitions straight tophase: verifiedafter CMAP). This change makes the signal canonical: porch writespr_ready_for_human: booleantostatus.yamlwhen transitioning out of the CMAP-emitting state for the PR-creating phase, for ALL five bundled protocols.Root Cause
"CMAP complete for the PR-creating phase" had one definition in porch's state machine but two surface manifestations depending on whether the protocol had a
prgate. Consumers had to know the protocol-specific shape, and the only way to discover you got it wrong was to ship a regression — which #844 did.Fix
State machine (3 touchpoints, one canonical write each):
next.tshandleVerifyApproved— SPIR/ASPIR/PIRreview(build_verify, gate=pr): set true at gate-pendingindex.tsdone()— AIRpr(once-phase, gate=pr): set true when auto-requesting theprgateindex.tsadvanceProtocolPhase— BUGFIXpr(once-phase, no gate, terminal): set true on transition toverifiedPR-creating phase identification: new
isPrCreatingPhase(protocol, phaseId)helper returns true when the phase hasgate === 'pr'OR aconsultationblock in protocol.json (preserved onProtocolPhase.hasConsultation). Both markers are picked up retroactively — no protocol.json edits required.Reset to false on
pr-gate approval (covers all four gate-protocols) and on rollback. BUGFIX has no gate, so the field stays true until cleanup deletes the worktree — matching the model that a BUGFIX builder is done after PR creation.Consumer (
overview.ts): newderivePrReadyreads the explicit field if present, else falls back toblocked === 'PR review' || (phase === 'verified' && protocol === 'bugfix'). The BUGFIX-specific fallback closes the v3.1.3 gap for in-flight builders whose status.yaml pre-dates this field. NewprReadyfield onBuilderOverviewexposes the signal.Dashboard (
NeedsAttentionList.tsx): gates onb.prReadyinstead ofb.blocked === 'PR review'. Falls back topr.createdAtfor prReady builders without a gate (BUGFIX shape — noblockedSince).Test Plan
pr-ready-872.test.tscovering the field lifecycle for SPIR/ASPIR/PIR (build_verify review), AIR (once-phase pr with gate), BUGFIX (once-phase pr without gate — the regression case), the REQUEST_CHANGES rebuttal cycle, pr-gate approval reset, and rollback reset.overview.test.tsforderivePrReady: explicit-true, explicit-false (trumps fallback), legacy SPIR/AIR fallback, BUGFIX gap closure, and non-BUGFIXverifiedno-fire.NeedsAttentionList.test.tsxfor BUGFIX visibility (the regression) andblockedSince→createdAtfallback for gateless prReady builders.pnpm build).OverviewBuilder.prReadyfallback derives the same signal until porch writes the explicit field.Files
packages/codev/src/commands/porch/{types,protocol,next,index}.tspackages/codev/src/agent-farm/servers/overview.ts,packages/types/src/api.ts,packages/dashboard/src/components/NeedsAttentionList.tsxpackages/codev/src/commands/porch/__tests__/pr-ready-872.test.ts(new),packages/codev/src/agent-farm/__tests__/overview.test.ts,packages/dashboard/__tests__/NeedsAttentionList.test.tsxprReady: falseto satisfy the new shared type (BuilderCard.test.tsx,spec-823-builder-attribution.test.ts)Notes
packages/dashboard/__tests__/scrollController.test.ts(terminal scroll behavior) — completely unrelated to this work and fails identically on this branch in isolation.