diff --git a/apps/streamdeck/src/__tests__/actions.test.ts b/apps/streamdeck/src/__tests__/actions.test.ts index 329c7979a..c23eece72 100644 --- a/apps/streamdeck/src/__tests__/actions.test.ts +++ b/apps/streamdeck/src/__tests__/actions.test.ts @@ -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'); @@ -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'); diff --git a/apps/streamdeck/src/actions.ts b/apps/streamdeck/src/actions.ts index 97ccbe036..6d59fc0ee 100644 --- a/apps/streamdeck/src/actions.ts +++ b/apps/streamdeck/src/actions.ts @@ -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'; diff --git a/apps/streamdeck/src/face.ts b/apps/streamdeck/src/face.ts index 1a2776717..50bcd1eb1 100644 --- a/apps/streamdeck/src/face.ts +++ b/apps/streamdeck/src/face.ts @@ -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 = { 'spec-approval': 'book', diff --git a/codev/projects/bugfix-1431-stream-deck-phaseartifactverb-/status.yaml b/codev/projects/bugfix-1431-stream-deck-phaseartifactverb-/status.yaml new file mode 100644 index 000000000..9e2e81a9c --- /dev/null +++ b/codev/projects/bugfix-1431-stream-deck-phaseartifactverb-/status.yaml @@ -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 diff --git a/codev/state/bugfix-1431_thread.md b/codev/state/bugfix-1431_thread.md new file mode 100644 index 000000000..567e33c44 --- /dev/null +++ b/codev/state/bugfix-1431_thread.md @@ -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.