Skip to content
Merged
2 changes: 2 additions & 0 deletions apps/streamdeck/src/__tests__/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ describe('phaseArtifactVerb (shared resolver — recognised verb or undefined)',
expect(phaseArtifactVerb(b({ protocolPhase: 'verify' }))).toBe('view-diff');
expect(phaseArtifactVerb(b({ blockedGate: 'dev-approval' }))).toBe('view-diff');
expect(phaseArtifactVerb(b({ blockedGate: 'pr' }))).toBe('view-diff');
expect(phaseArtifactVerb(b({ blockedGate: 'verify-approval' }))).toBe('view-diff'); // #1431: human reviewing finished work
});
it('gate beats phase (the stronger signal)', () => {
expect(phaseArtifactVerb(b({ blockedGate: 'plan-approval', protocolPhase: 'implement' }))).toBe('open-plan');
Expand All @@ -666,6 +667,7 @@ describe('reviewMode (dial mode from the shared resolver)', () => {
expect(reviewMode(b({ protocolPhase: 'review' }))).toBe('diff');
expect(reviewMode(b({ blockedGate: 'dev-approval' }))).toBe('diff');
expect(reviewMode(b({ blockedGate: 'pr' }))).toBe('diff');
expect(reviewMode(b({ blockedGate: 'verify-approval' }))).toBe('diff'); // #1431: dials navigate the diff while the human reviews finished work
});
it('an unknown phase, no live status, or no builder → none', () => {
expect(reviewMode(b({}))).toBe('none');
Expand Down
2 changes: 1 addition & 1 deletion apps/streamdeck/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export function phaseArtifactVerb(b: OverviewBuilder): string | undefined {
const gate = b.blockedGate ?? '';
if (gate === 'spec-approval') return 'open-spec';
if (gate === 'plan-approval') return 'open-plan';
if (gate === 'dev-approval' || gate === 'pr') return 'view-diff';
if (gate === 'dev-approval' || gate === 'pr' || gate === 'verify-approval') return 'view-diff';
const phase = b.protocolPhase ?? '';
if (phase === 'specify') return 'open-spec';
if (phase === 'plan') return 'open-plan';
Expand Down
3 changes: 1 addition & 2 deletions apps/streamdeck/src/face.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ export type GlyphKey = 'bolt' | 'book' | 'checklist' | 'code' | 'pull-request' |
/**
* Gate id → glyph. The streamdeck twin of `gateIconFor` in `apps/vscode/src/views/builder-row.ts`
* — keep in sync. A blocked builder whose gate isn't mapped falls back to `bell` (see
* `faceForBuilder`), matching the sidebar. `verify-approval` renders here even though the press
* resolver doesn't handle it yet (that gap is BUGFIX #1431).
* `faceForBuilder`), matching the sidebar.
*/
const GATE_ICONS: Record<string, GlyphKey> = {
'spec-approval': 'book',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
id: bugfix-1431
title: stream-deck-phaseartifactverb-
protocol: bugfix
phase: pr
plan_phases: []
current_plan_phase: null
gates:
pr:
status: approved
requested_at: '2026-08-13T01:36:52.822Z'
approved_at: '2026-08-13T07:19:28.351Z'
iteration: 1
build_complete: false
history: []
started_at: '2026-08-13T01:30:28.426Z'
updated_at: '2026-08-13T07:19:28.352Z'
pr_ready_for_human: false
65 changes: 65 additions & 0 deletions codev/state/bugfix-1431_thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Builder thread — bugfix-1431

## Issue #1431 — phaseArtifactVerb missing verify-approval gate mapping

Stream Deck's `phaseArtifactVerb` (apps/streamdeck/src/actions.ts:327) maps the
`spec-approval`, `plan-approval`, `dev-approval`, and `pr` gates but not
`verify-approval`. A builder blocked at `verify-approval` therefore returns
`undefined`, and the Builder Action Automatic press falls back to `open-terminal`
instead of opening the review diff. Meanwhile the #1428 faces already render this
gate (yellow + `verified` glyph + `Verify` label) — so the key looks gate-blocked
but the press opens a terminal: a face/behaviour contradiction.

## Root cause (investigate phase — no code)

Single missing branch in the gate section of `phaseArtifactVerb`. The VS Code
sidebar's `gateIconFor` (apps/vscode/src/views/builder-row.ts) already knows
`verify-approval` → `verified`; the Stream Deck resolver never caught up.

## Fix (architect-scoped: strictly one line + one test + one comment cleanup)

1. actions.ts `phaseArtifactVerb` gate branch: add `verify-approval` returning
`'view-diff'` (same as dev-approval/pr — human reviewing finished work). The
Automatic press then resolves view-diff → open-diff-first via
BuilderAction.resolveVerb (#1414), which is correct and intended.
2. actions.test.ts: assertion beside the existing dev-approval/pr gate cases.
3. face.ts ~line 52: delete the doc-comment clause saying the resolver "doesn't
handle it yet (that gap is BUGFIX #1431)" — this lane closes that gap.

Do NOT touch anything else in face.ts/actions.ts (#1410 just landed a large change;
keep this diff trivially reviewable).

## Fix phase — done

Made the three scoped edits. Regression test confirmed to fail without the fix
(returns `undefined`) and pass with it (`view-diff`). Full suite 125/125,
check-types clean, esbuild build clean (after building the `@cluesmith/codev-sdk`
workspace dep first — the type/build errors before that were the unbuilt dep, not
my change). Committed as `Fix #1431: ...`.

## PR phase

PR #1442 opened (Fixes #1431). CMAP consult needs `--project-id bugfix-1431` —
auto-detect fails because the worktree's `codev/projects/` holds every project.
Running all three (gemini/codex/claude) in the background.

## CMAP verdicts

- gemini = APPROVE (HIGH) — "clean, minimal fix … with unit test coverage."
- claude = APPROVE (HIGH) — "correct one-line gate mapping … backed by a genuine
regression test; scope and hygiene are clean."
- codex = UNAVAILABLE — the codex lane errored on OpenAI billing ("no credits
remaining"), not a review verdict. Environment issue, unrelated to this change.

Two independent APPROVEs, no requested changes. Notifying architect and firing the
pr gate; codex reported honestly as unavailable rather than a fabricated verdict.

## Architect review of PR #1442 (compensating for codex being down)

Flagged a real gap: the fix has a second behavioural effect neither the issue nor
my tests covered — `reviewMode()` derives from `phaseArtifactVerb` (`view-diff` →
`diff`), so a verify-approval-blocked builder's review dials flip from `none`
(dead) to `diff` (navigating the diff). Correct and desirable (symmetric with
dev-approval/pr), but was untested + undocumented. Added a `reviewMode` assertion
beside the dev-approval/pr cases and documented the dial-mode consequence in the PR
body. 125/125 pass. Still exactly in scope. Holding at the pr gate.
Loading