From 34bf46a4dd30e30659bd444f3724d89f4c0ef372 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:36:08 +1000 Subject: [PATCH 01/12] chore(porch): bugfix-1606 init bugfix --- .../status.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml diff --git a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml new file mode 100644 index 000000000..76ae205f6 --- /dev/null +++ b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml @@ -0,0 +1,14 @@ +id: bugfix-1606 +title: streamdeck-review-dials-silent +protocol: bugfix +phase: investigate +plan_phases: [] +current_plan_phase: null +gates: + pr: + status: pending +iteration: 1 +build_complete: false +history: [] +started_at: '2026-09-04T01:36:08.893Z' +updated_at: '2026-09-04T01:36:08.894Z' From 1bc1e14be20aa2a53a97b657d77bd5cec716bcb3 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:40:23 +1000 Subject: [PATCH 02/12] chore(porch): bugfix-1606 fix phase-transition --- .../bugfix-1606-streamdeck-review-dials-silent/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml index 76ae205f6..cab45ea7f 100644 --- a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml +++ b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml @@ -1,7 +1,7 @@ id: bugfix-1606 title: streamdeck-review-dials-silent protocol: bugfix -phase: investigate +phase: fix plan_phases: [] current_plan_phase: null gates: @@ -11,4 +11,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-09-04T01:36:08.893Z' -updated_at: '2026-09-04T01:36:08.894Z' +updated_at: '2026-09-04T01:40:23.888Z' From 029c2cd92afa9a39ffff5076b2e220bfdee4a8a5 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:45:45 +1000 Subject: [PATCH 03/12] [Bugfix #1606] Fix: revive Review dials for BUGFIX/AIR phases + honest none-mode label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Stream Deck Review dials (Files/Headings, Changes/Blocks) silently no-op for a BUGFIX builder's whole working life (investigate, fix) and for AIR during the pr phase, because phaseArtifactVerb's hardcoded SPIR/PIR phase vocabulary left those ids unrecognized -> reviewMode resolved to 'none' and every dial gesture no-opped. Compounding it, the none-mode strip rendered the diff label (Files · send) while inert -- a gesture that lied about what it did. - reviewMode: recognize investigate/fix/pr as diff mode via a registered DIFF_REVIEW_PHASES set with a GATE_LABELS-style registration note (protocol.json phase metadata carries no review-mode field; the SDK's PHASE_TO_STAGE folds investigate->plan, wrong here, and drives the VSCode tree). phaseArtifactVerb is left untouched so the Builder Action key (open-terminal) and Zoom (view-diff) keep their behavior; only the review dials gain the extra vocabulary. - ReviewNav.renderTo: none mode now renders "No review target" instead of borrowing the diff label. - Tests: reviewMode coverage for the BUGFIX/AIR phase ids and init/no-status; a phaseArtifactVerb guard documenting the deliberate split; a behavioral test (investigate dials drive the diff) and a legibility test (honest none label). --- apps/streamdeck/src/__tests__/actions.test.ts | 52 +++++++++- apps/streamdeck/src/actions.ts | 47 +++++++-- codev/state/bugfix-1606_thread.md | 98 +++++++++++++++++++ 3 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 codev/state/bugfix-1606_thread.md diff --git a/apps/streamdeck/src/__tests__/actions.test.ts b/apps/streamdeck/src/__tests__/actions.test.ts index 7e9bbfc83..12133435a 100644 --- a/apps/streamdeck/src/__tests__/actions.test.ts +++ b/apps/streamdeck/src/__tests__/actions.test.ts @@ -635,6 +635,38 @@ describe('encoders', () => { expect(ctx.canvasSent).toHaveLength(0); }); + it('BUGFIX investigate builder: the review dials drive the diff (silently dead before #1606)', async () => { + const ctx = makeStore(); + // A BUGFIX builder mid-investigate: no gate, phase `investigate`. Before #1606 this + // resolved to reviewMode 'none' and every gesture no-opped for the builder's whole life. + ctx.store.overview = { + builders: [{ id: 'bugfix-1', roleId: null, issueId: '1606', issueTitle: 'Review dials', blocked: null, blockedGate: null, protocolPhase: 'investigate', progress: 10, worktreePath: '/w' }], + pendingPRs: [], backlog: [], recentlyClosed: [], + } as never; + const nav = new DiffFileNav(ctx.store); + await nav.onDialRotate(dial(1) as never); // next file + await nav.onDialDown(); // submit feedback + await nav.onTouchTap(); // first file + expect(ctx.sent.map((s) => s.verb)).toEqual(['diff-next-file', 'feedback-file', 'diff-first-file']); + expect(ctx.canvasSent).toHaveLength(0); // diff mode never touches the canvas channel + }); + + it('legibility: a none-mode builder\'s strip says "No review target", not the diff label (#1606)', () => { + const ctx = makeStore(); + // A porch `init`-phase (or soft/shell/task) builder: genuinely nothing to review. The strip + // must NOT borrow the diff label (`Files · send`) while every gesture is inert — that lie is + // the compounding half of #1606. + ctx.store.overview = { + builders: [{ id: 'task-x', roleId: null, issueId: null, issueTitle: null, blocked: null, blockedGate: null, protocolPhase: 'init', progress: 0, worktreePath: '/w' }], + pendingPRs: [], backlog: [], recentlyClosed: [], + } as never; + const fileAction = { isDial: () => true, setFeedback: vi.fn() }; + new DiffFileNav(ctx.store).onWillAppear({ action: fileAction, payload: {} } as never); + const fb = fileAction.setFeedback.mock.calls.at(-1)?.[0] as Record; + expect(fb.title).toBe('No review target'); + expect(fb.title).not.toBe('Files · send'); // the old lie + }); + it('ScrollNav diff mode: rotate relays byte-for-byte editorScroll, press submits the selection as feedback', async () => { const ctx = makeStore(); // State the mode the assertion depends on: pir-2 (implement, no gate) is the DIFF-mode builder. @@ -939,6 +971,16 @@ describe('phaseArtifactVerb (shared resolver — recognised verb or undefined)', // The two callers diverge exactly here: dial → view-diff, Builder Action → open-terminal. expect(zoomInVerb(b({}))).toBe('view-diff'); }); + it('leaves BUGFIX investigate/fix and the pr PHASE unrecognised — the review dials own that vocabulary (#1606)', () => { + // Deliberate split: `phaseArtifactVerb` stays SPIR/PIR-scoped so the auto-open action key + // keeps opening a terminal (no artifact yet) and Zoom falls back to view-diff. Only + // `reviewMode` maps these phases to diff (see DIFF_REVIEW_PHASES). If this ever changes, + // reconsider the Builder Action key's open-terminal→open-diff-first side effect first. + expect(phaseArtifactVerb(b({ protocolPhase: 'investigate' }))).toBeUndefined(); + expect(phaseArtifactVerb(b({ protocolPhase: 'fix' }))).toBeUndefined(); + expect(phaseArtifactVerb(b({ protocolPhase: 'pr' }))).toBeUndefined(); + expect(zoomInVerb(b({ protocolPhase: 'investigate' }))).toBe('view-diff'); // Zoom already correct for BUGFIX + }); }); describe('reviewMode (dial mode from the shared resolver)', () => { @@ -956,9 +998,17 @@ describe('reviewMode (dial mode from the shared resolver)', () => { 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', () => { + it('BUGFIX investigate/fix and the BUGFIX+AIR pr PHASE → diff (#1606: dead for the builder\'s whole working life before)', () => { + // BUGFIX (`investigate/fix/pr`): the review dials were 'none' until the pr *gate* was + // requested. The pr *phase* (BUGFIX + AIR `implement/pr`) is diffable per the owner ruling. + expect(reviewMode(b({ protocolPhase: 'investigate' }))).toBe('diff'); + expect(reviewMode(b({ protocolPhase: 'fix' }))).toBe('diff'); + expect(reviewMode(b({ protocolPhase: 'pr' }))).toBe('diff'); + }); + it('an unknown phase, no live status, or no builder → none (init/soft/shell/task have nothing to review)', () => { expect(reviewMode(b({}))).toBe('none'); expect(reviewMode(b({ protocolPhase: 'mystery' }))).toBe('none'); + expect(reviewMode(b({ protocolPhase: 'init' }))).toBe('none'); // #1606: porch init phase stays none expect(reviewMode(undefined)).toBe('none'); }); }); diff --git a/apps/streamdeck/src/actions.ts b/apps/streamdeck/src/actions.ts index b662c337e..1c6a39371 100644 --- a/apps/streamdeck/src/actions.ts +++ b/apps/streamdeck/src/actions.ts @@ -548,18 +548,42 @@ export function zoomInVerb(b: OverviewBuilder): string { /** Which artifact form the selected builder's phase implies for the review dials. */ export type ReviewMode = 'diff' | 'canvas' | 'none'; +/** + * Phase ids that review as a DIFF but that `phaseArtifactVerb` deliberately does NOT + * auto-open. A builder in these phases has no spec/plan document to hand off, and (for + * `investigate`) may not have code yet either, so the auto-open action key keeps opening a + * terminal — but the review dials should still drive the (growing) diff rather than lie or + * die (#1606): + * + * - `investigate`, `fix` (BUGFIX): the builder's whole working life reviews as a diff. + * - `pr` (BUGFIX + AIR): by the pr *phase* the work already exists as a diff (owner + * ruling 2026-09-04), even before the pr *gate* is requested (`blockedGate === 'pr'` + * already resolves to diff via `phaseArtifactVerb`). + * + * REGISTRATION NOTE (mirrors Tower's GATE_LABELS pattern, face.ts): `protocol.json` phase + * metadata carries no field expressing review mode (only id/name/type/steps/transition/ + * gate), and the SDK's `PHASE_TO_STAGE` folds `investigate → plan` (canvas — wrong here) + * and drives the VSCode Builders tree, so neither can be reused verbatim. A new protocol + * whose phases review as a diff registers its phase id here (or maps onto + * `phaseArtifactVerb`'s SPIR/PIR vocabulary). An unregistered phase resolves to `none` — + * visibly inert with an honest label, never a silent lie. + */ +const DIFF_REVIEW_PHASES = new Set(['investigate', 'fix', 'pr']); + /** * The review mode for a builder: a builder still writing its spec/plan reviews as a - * canvas (`open-spec` / `open-plan`), one with a diff reviews as a diff (`view-diff`), - * and an unknown/no-status builder has neither. Derived from the shared phase/gate - * resolver so the wire source stays single (`blockedGate` beats `protocolPhase`; never - * guessed) — this is the same resolver family #1404's press keys off. + * canvas (`open-spec` / `open-plan`), one with a diff reviews as a diff (`view-diff`, or a + * `DIFF_REVIEW_PHASES` phase), and an unknown/no-status builder has neither. The + * canvas/diff-verb split derives from the shared phase/gate resolver (`blockedGate` beats + * `protocolPhase`; never guessed) — the same resolver family #1404's press keys off; the + * extra diff phases are the review-dial-only vocabulary `phaseArtifactVerb` omits. */ export function reviewMode(b: OverviewBuilder | undefined): ReviewMode { if (!b) return 'none'; const verb = phaseArtifactVerb(b); if (verb === 'open-spec' || verb === 'open-plan') return 'canvas'; if (verb === 'view-diff') return 'diff'; + if (DIFF_REVIEW_PHASES.has(b.protocolPhase ?? '')) return 'diff'; return 'none'; } @@ -902,11 +926,16 @@ abstract class ReviewNav extends SingletonAction { private renderTo(action: DialAction): void { // Canvas line 1 pairs the rotate axis with the press meaning (`Blocks · Open/Submit`, // `Headings · Cancel`); diff mode pairs its axis with the feedback delivery mode - // (`Files · queue` vs `Files · send`, #1410) so a press is never a surprise. - const label = - this.mode() === 'canvas' - ? `${this.canvas.label} · ${this.canvas.pressLabel}` - : `${this.diff.label} · ${this.store.feedbackMode() === 'queue' ? 'queue' : 'send'}`; + // (`Files · queue` vs `Files · send`, #1410) so a press is never a surprise. `none` + // states its own emptiness (`No review target`) rather than borrowing the diff label — + // every gesture in `none` mode is inert, so the strip must not imply otherwise (#1606). + const mode = this.mode(); + let label = 'No review target'; + if (mode === 'canvas') { + label = `${this.canvas.label} · ${this.canvas.pressLabel}`; + } else if (mode === 'diff') { + label = `${this.diff.label} · ${this.store.feedbackMode() === 'queue' ? 'queue' : 'send'}`; + } const b = this.store.selectedBuilder(); void action.setFeedback({ title: label, diff --git a/codev/state/bugfix-1606_thread.md b/codev/state/bugfix-1606_thread.md new file mode 100644 index 000000000..bae807528 --- /dev/null +++ b/codev/state/bugfix-1606_thread.md @@ -0,0 +1,98 @@ +# bugfix-1606 — streamdeck: Review dials silently dead for BUGFIX/AIR phases + +Issue #1606. Protocol: BUGFIX (strict). Branch: builder/bugfix-1606. + +## INVESTIGATE — findings (root cause confirmed, no code written) + +### Bug +Stream Deck **Review dials** (Files/Headings, Changes/Blocks) silently no-op for a +BUGFIX builder's whole working life (`investigate`, `fix`) and for AIR during the `pr` +phase (until the pr *gate* is requested). Reads as "diff dials sometimes work, sometimes +not" — the intermittency is protocol identity, not flakiness. + +### Root cause +`phaseArtifactVerb` (`apps/streamdeck/src/actions.ts:527`) has a hardcoded phase/gate +vocabulary written against SPIR/PIR: phases `specify|plan|implement|review|verify`, gates +`spec-approval|plan-approval|dev-approval|pr|verify-approval`. `reviewMode` +(`actions.ts:558`) derives from it: `open-spec/open-plan → canvas`, `view-diff → diff`, +`undefined → none`. + +Checked each bundled `protocol.json` (`phases[].id`): +- SPIR `specify/plan/implement/review/verify`, PIR `plan/implement/review` — covered. +- AIR `implement/pr` — the `pr` **phase** id is unrecognized → `none` until `blockedGate==='pr'`. +- BUGFIX `investigate/fix/pr` — `investigate`/`fix` unrecognized → `reviewMode==='none'` + for the builder's whole working life; dials only revive when blocked at the pr gate. +- `init`/no-status (soft/shell/task) → `none` (genuinely nothing to review). + +In `none` mode `ReviewNav.onDialRotate/onDialDown/onTouchTap` all no-op (actions.ts:918-944). + +### Compounding "lie" +`ReviewNav.renderTo` label ternary (actions.ts:906-909) has only canvas vs diff branches; +`none` falls into the diff branch and renders `Files · send` while every gesture no-ops — +violates the dial's "a gesture is never a surprise" contract. + +### Metadata-vs-list decision (issue's stated preference) +Issue prefers deriving mode from `protocol.json` phase metadata over extending a name list. +Verified: `protocol.json` phases carry only `id/name/type/steps/transition/gate` — **no field +expresses review mode**. The SDK's `packages/sdk/src/phase-grouping.ts` `PHASE_TO_STAGE` +folds `investigate → plan`, which for review-mode purposes would wrongly imply **canvas** +(BUGFIX has no spec/plan doc), and it's used by the VSCode Builders tree — can't repurpose +without blast radius. So per the issue's documented fallback ("if metadata can't express it, +extend the list and leave a registration note — the GATE_LABELS pattern"), I'll extend the +recognized diff-phase vocabulary with a registration note. + +### Fix surface (well under 300 LOC; FIX phase) +1. Make the review dials resolve **diff** for phases `investigate`, `fix`, `pr`. Do it in + `reviewMode` (the dial-specific resolver) — **not** in `phaseArtifactVerb** — so + `zoomInVerb` (already `?? 'view-diff'`, so BUGFIX zoom already works) and `BuilderAction` + auto-press (`?? 'open-terminal'`) are untouched. Keeping `phaseArtifactVerb` stable avoids + changing the Builder Action key from open-terminal→open-diff-first during `investigate` + (no diff yet) — out-of-scope blast radius the issue doesn't ask for. Add a registration note. +2. Honest `none` label: add a `none` branch in `renderTo` → `No review target` instead of + the diff label. +3. Keep `init`/no-status → `none` (with the honest label). + +Consumers verified: `reviewMode` also feeds `ScrollNav` (actions.ts:1080/1150) but those +only branch on `=== 'canvas'`; none→diff doesn't change ScrollNav behavior (both non-canvas). + +### Regression test +`apps/streamdeck/src/__tests__/actions.test.ts`: BUGFIX `investigate` builder → +`reviewMode==='diff'` and dial gestures emit diff verbs (fails today: 'none', no verbs); +`fix`/`pr` phases → 'diff'; `none`-mode label renders `No review target` not `Files · send`. + +### Shipping note +`apps/streamdeck` change → plugin version bump + Elgato resubmission (~4-10 working days). +Flagged in issue; batching decision is the architect's. + +Signal: PHASE_COMPLETE. + +## FIX — implemented + +Architect approved the scoped approach (registered diff-phase set in `reviewMode`, +`phaseArtifactVerb` untouched, trade-off flagged in PR body). Release is HELD — no version +bump (fix merges to main, ships later in a batched Elgato resubmission). + +Changes (2 files, ~89 lines incl. tests): +- `apps/streamdeck/src/actions.ts`: + - Added `DIFF_REVIEW_PHASES = {investigate, fix, pr}` with a GATE_LABELS-style registration + note; `reviewMode` now returns `diff` for these phases (after the existing verb-based + canvas/diff resolution, so gates still win). `phaseArtifactVerb` untouched → Builder Action + key (open-terminal) and Zoom (view-diff) behavior unchanged. + - `ReviewNav.renderTo`: `none` mode now renders `No review target` instead of borrowing the + diff label (`Files · send`). Converted the label ternary to if/else. +- `apps/streamdeck/src/__tests__/actions.test.ts`: added reviewMode coverage for + investigate/fix/pr → diff and init/'' → none; a phaseArtifactVerb guard documenting the + deliberate split; a behavioral test (BUGFIX investigate dials emit diff verbs); a legibility + test (none-mode strip says `No review target`, not the lie). + +Verification (from the worktree, apps/streamdeck): +- Full suite: 246 passed (10 files). +- Regression check: reverted both source edits → the 3 behavioral/mode/label tests FAIL + (`none`→ expected `diff`; strip `Files · send`→ expected `No review target`); restored → pass. +- `tsc --noEmit` exit 0 (after building the codev-sdk workspace dep, which had no dist here), + `npm run build` exit 0. + +No skeleton mirror needed (apps/ is app code, single copy; not a codev/↔codev-skeleton framework file). + +Next: PR phase (porch runs the gemini+codex consultation there). Will note here when the PR is +up for the architect's streamdeck-link hardware check. From a2af9ad22c993568e7cbf7103f5003298740d6b0 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:46:37 +1000 Subject: [PATCH 04/12] chore(porch): bugfix-1606 pr phase-transition --- .../bugfix-1606-streamdeck-review-dials-silent/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml index cab45ea7f..d433c0ccb 100644 --- a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml +++ b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml @@ -1,7 +1,7 @@ id: bugfix-1606 title: streamdeck-review-dials-silent protocol: bugfix -phase: fix +phase: pr plan_phases: [] current_plan_phase: null gates: @@ -11,4 +11,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-09-04T01:36:08.893Z' -updated_at: '2026-09-04T01:40:23.888Z' +updated_at: '2026-09-04T01:46:37.507Z' From f73e3f85851513d933ebfb6d8e2adefc98459f8d Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:52:34 +1000 Subject: [PATCH 05/12] [Bugfix #1606] docs: sync plugin README + registration note with the revived dial vocabulary Addresses CMAP (claude) review comments on PR #1607: - README.md: the Review-dial bullets described the diff vocabulary as "implement / review, or blocked at dev-approval / pr" and never mentioned the inert state. Updated to include the BUGFIX/AIR investigate/fix/pr phases and the honest "No review target" strip, so the doc that ships to the Marketplace matches the fixed behavior. - DIFF_REVIEW_PHASES note: `investigate` is registered by name, not owned by BUGFIX (RESEARCH uses it too, and its worktree changes review as a diff). Corrected the comment's attribution. No behavior change. MAINTAIN/EXPERIMENT/SPIKE phases still resolving to none is left for an architect scope decision (out of #1606's investigate/fix/pr scope); the honest label already stops them from lying. --- apps/streamdeck/README.md | 18 +++++++++++------- apps/streamdeck/src/actions.ts | 5 ++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/streamdeck/README.md b/apps/streamdeck/README.md index fc2aa85b8..9f382777e 100644 --- a/apps/streamdeck/README.md +++ b/apps/streamdeck/README.md @@ -266,16 +266,20 @@ builder (+ its phase and position); PR / Spawn show the item + `i/N`. - **Spawn from Backlog** — rotate the backlog; **push** spawns a builder for the selected issue (VSCode prompts for the protocol). - **Review: Files / Headings** — phase-aware coarse review dial. The selected - builder's phase picks the mode. *Diff phase* (implement / review, or blocked at - dev-approval / pr): rotate = next / previous file, **push** = submit the current + builder's phase picks the mode. *Diff phase* (implement / review / verify, the + BUGFIX/AIR investigate / fix / pr phases, or blocked at dev-approval / pr / + verify-approval): rotate = next / previous file, **push** = submit the current file as feedback, **tap** = jump to the first file. *Spec/plan phase* (specify / plan, or blocked at spec-approval / plan-approval): rotate = next / previous heading in the artifact canvas, **push** = open the composer at the focused block, **tap** = - jump to the document top. The touch strip names the live mode and, in diff phase, - the delivery mode (`Files · send` vs `Files · queue`). -- **Review: Changes / Blocks** — phase-aware fine review dial, same mode split. - *Diff phase*: rotate = next / previous change, **push** = submit the current change - as feedback, **tap** = jump to the first change. *Spec/plan phase*: rotate = next / + jump to the document top. When the selected builder has no reviewable artifact (an + `init` / no-live-status builder), both dials go inert and the strip reads + `No review target` rather than a mode label. The touch strip names the live mode and, + in diff phase, the delivery mode (`Files · send` vs `Files · queue`). +- **Review: Changes / Blocks** — phase-aware fine review dial, same mode split + (including the inert `No review target` state on a builder with no reviewable + artifact). *Diff phase*: rotate = next / previous change, **push** = submit the current + change as feedback, **tap** = jump to the first change. *Spec/plan phase*: rotate = next / previous block, **push** = open the composer, **tap** = walk forward to the next commented block. The touch strip names the live mode (`Changes · send` / `Changes · queue`, or `Blocks`). diff --git a/apps/streamdeck/src/actions.ts b/apps/streamdeck/src/actions.ts index 1c6a39371..3f949cf51 100644 --- a/apps/streamdeck/src/actions.ts +++ b/apps/streamdeck/src/actions.ts @@ -555,7 +555,10 @@ export type ReviewMode = 'diff' | 'canvas' | 'none'; * terminal — but the review dials should still drive the (growing) diff rather than lie or * die (#1606): * - * - `investigate`, `fix` (BUGFIX): the builder's whole working life reviews as a diff. + * - `investigate` (BUGFIX; also RESEARCH's diagnosis phase): the builder's whole working + * life reviews as a diff. RESEARCH's `investigate` produces worktree changes too, so diff + * mode is correct there as well — the id is registered by name, not by owning protocol. + * - `fix` (BUGFIX): the whole working life reviews as a diff. * - `pr` (BUGFIX + AIR): by the pr *phase* the work already exists as a diff (owner * ruling 2026-09-04), even before the pr *gate* is requested (`blockedGate === 'pr'` * already resolves to diff via `phaseArtifactVerb`). From 1822e50ffcc9c020f5068ba3fe67a885b76add34 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:53:48 +1000 Subject: [PATCH 06/12] [Bugfix #1606] chore: update builder thread with CMAP verdicts and gate status --- codev/state/bugfix-1606_thread.md | 33 +++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/codev/state/bugfix-1606_thread.md b/codev/state/bugfix-1606_thread.md index bae807528..9fa7dbf20 100644 --- a/codev/state/bugfix-1606_thread.md +++ b/codev/state/bugfix-1606_thread.md @@ -94,5 +94,34 @@ Verification (from the worktree, apps/streamdeck): No skeleton mirror needed (apps/ is app code, single copy; not a codev/↔codev-skeleton framework file). -Next: PR phase (porch runs the gemini+codex consultation there). Will note here when the PR is -up for the architect's streamdeck-link hardware check. +## PR — up + +PR #1607 opened: https://github.com/cluesmith/codev/pull/1607 (Fixes #1606). Body carries the +metadata-vs-registered-list trade-off note per the architect's call. + +CMAP dispatched (gemini/codex/claude, `--type pr`). Note: consult auto-detect failed with +"Multiple projects found" from this worktree — had to pass `--project-id bugfix-1606` explicitly. +Awaiting three verdicts before notifying the architect + running `porch done` (fires the pr gate). + +**@architect: hardware check** — once you approve, this is ready for your streamdeck-link +verification: select a BUGFIX builder (investigate/fix) and confirm the Files/Changes dials now +step the diff and the strip no longer reads `Files · send` when there's nothing to review. + +## CMAP verdicts (3/3) + +- **gemini: APPROVE** (HIGH) — no issues. +- **codex: APPROVE** (HIGH) — no issues. +- **claude: COMMENT** (HIGH) — three points, all addressed or triaged: + 1. Plugin README stale on the dial vocabulary → **fixed** (commit f73e3f8): README now lists + the BUGFIX/AIR investigate/fix/pr phases and the honest `No review target` state. + 2. `DIFF_REVIEW_PHASES` comment attributed `investigate` to BUGFIX alone (RESEARCH uses it + too) → **fixed** (same commit): registered by name, note corrected. + 3. MAINTAIN `maintain` / EXPERIMENT `execute`,`analyze` / SPIKE `spike` still resolve to + `none` (dead dials on a diff, though the honest label now stops them lying). Claude flags + this as an **architect scope call, not a blocker** — out of #1606's investigate/fix/pr + scope. **Raised to the architect** in the gate notification; recommend a follow-up issue + rather than expanding this PR. + +Non-substantial changes (docs + comment, zero behavior change), so CMAP was not re-run. +Sent the single gate notification with all three verdicts; ran `porch done` to fire the pr gate. +Awaiting architect approval (`porch approve bugfix-1606 pr`) before merge. From a03735b3bb0329accfd49e274e4818c1bced15b9 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:53:53 +1000 Subject: [PATCH 07/12] chore(porch): bugfix-1606 pr gate-requested --- .../bugfix-1606-streamdeck-review-dials-silent/status.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml index d433c0ccb..b5e21d250 100644 --- a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml +++ b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml @@ -7,8 +7,10 @@ current_plan_phase: null gates: pr: status: pending + requested_at: '2026-09-04T01:53:53.303Z' iteration: 1 build_complete: false history: [] started_at: '2026-09-04T01:36:08.893Z' -updated_at: '2026-09-04T01:46:37.507Z' +updated_at: '2026-09-04T01:53:53.303Z' +pr_ready_for_human: true From 81b49dd6ec65922cce6d365df0361f5e1319be48 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 11:55:39 +1000 Subject: [PATCH 08/12] [Bugfix #1606] chore: record architect APPROVE + gate-hold status in thread --- codev/state/bugfix-1606_thread.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/codev/state/bugfix-1606_thread.md b/codev/state/bugfix-1606_thread.md index 9fa7dbf20..ba186b41f 100644 --- a/codev/state/bugfix-1606_thread.md +++ b/codev/state/bugfix-1606_thread.md @@ -124,4 +124,17 @@ step the diff and the strip no longer reads `Files · send` when there's nothing Non-substantial changes (docs + comment, zero behavior change), so CMAP was not re-run. Sent the single gate notification with all three verdicts; ran `porch done` to fire the pr gate. -Awaiting architect approval (`porch approve bugfix-1606 pr`) before merge. + +## At the pr gate — holding (2026-09-04) + +Architect review of PR #1607: **APPROVE**. Rulings: +- **Scope call decided**: PR stays as-is. The MAINTAIN/EXPERIMENT/SPIKE phase registrations + (Claude's point 3) are now **follow-up issue #1609** (architect's lane) — do NOT expand this PR. +- The `investigate`-by-id RESEARCH-nuance handling and the `phaseArtifactVerb` split note both + confirmed correct. +- The **pr gate word rests with the owner** (a CMAP/architect APPROVE is not gate authorization). + The owner's decision and the physical-deck hardware check (once he links the deck to this + worktree build) are both in motion. Architect: "hold at the gate." + +**Status: holding at the pr gate. Not running `porch approve` — awaiting the owner's explicit +gate decision.** Nothing actionable for me until then. From dbc1fcc8735df96ec9ca32acac9507e1c0d552bb Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 16:59:32 +1000 Subject: [PATCH 09/12] [Bugfix #1606] Narrow diff-review revival to the pr phase only (owner ruling) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner ruling 2026-09-04: only the `pr` phase should drive the review dials to diff mode. BUGFIX `investigate`/`fix` are work-in-progress (partial or unwritten diff), so they are not a review target and stay `none` — inert, but now with the honest `No review target` label instead of the old `Files · send` lie. - DIFF_REVIEW_PHASES = {pr}; investigate/fix removed and documented as deliberately excluded so they are not re-added without an owner ruling. - Tests: pr phase -> diff (behavioral + unit); investigate/fix -> none (behavioral + unit); guard and none-label tests updated. 248 pass; tsc + build clean. - README + PR body updated to pr-only scope. --- apps/streamdeck/README.md | 18 +++---- apps/streamdeck/src/__tests__/actions.test.ts | 48 ++++++++++++++----- apps/streamdeck/src/actions.ts | 35 +++++++------- codev/state/bugfix-1606_thread.md | 19 ++++++++ 4 files changed, 80 insertions(+), 40 deletions(-) diff --git a/apps/streamdeck/README.md b/apps/streamdeck/README.md index 9f382777e..4f89ea234 100644 --- a/apps/streamdeck/README.md +++ b/apps/streamdeck/README.md @@ -267,15 +267,15 @@ builder (+ its phase and position); PR / Spawn show the item + `i/N`. selected issue (VSCode prompts for the protocol). - **Review: Files / Headings** — phase-aware coarse review dial. The selected builder's phase picks the mode. *Diff phase* (implement / review / verify, the - BUGFIX/AIR investigate / fix / pr phases, or blocked at dev-approval / pr / - verify-approval): rotate = next / previous file, **push** = submit the current - file as feedback, **tap** = jump to the first file. *Spec/plan phase* (specify / - plan, or blocked at spec-approval / plan-approval): rotate = next / previous heading - in the artifact canvas, **push** = open the composer at the focused block, **tap** = - jump to the document top. When the selected builder has no reviewable artifact (an - `init` / no-live-status builder), both dials go inert and the strip reads - `No review target` rather than a mode label. The touch strip names the live mode and, - in diff phase, the delivery mode (`Files · send` vs `Files · queue`). + BUGFIX/AIR pr phase, or blocked at dev-approval / pr / verify-approval): rotate = + next / previous file, **push** = submit the current file as feedback, **tap** = jump + to the first file. *Spec/plan phase* (specify / plan, or blocked at spec-approval / + plan-approval): rotate = next / previous heading in the artifact canvas, **push** = + open the composer at the focused block, **tap** = jump to the document top. When the + selected builder has no reviewable artifact (an `init` / no-live-status builder, or a + work-in-progress BUGFIX investigate / fix phase), both dials go inert and the strip + reads `No review target` rather than a mode label. The touch strip names the live mode + and, in diff phase, the delivery mode (`Files · send` vs `Files · queue`). - **Review: Changes / Blocks** — phase-aware fine review dial, same mode split (including the inert `No review target` state on a builder with no reviewable artifact). *Diff phase*: rotate = next / previous change, **push** = submit the current diff --git a/apps/streamdeck/src/__tests__/actions.test.ts b/apps/streamdeck/src/__tests__/actions.test.ts index 12133435a..fa2f5a6bc 100644 --- a/apps/streamdeck/src/__tests__/actions.test.ts +++ b/apps/streamdeck/src/__tests__/actions.test.ts @@ -635,12 +635,12 @@ describe('encoders', () => { expect(ctx.canvasSent).toHaveLength(0); }); - it('BUGFIX investigate builder: the review dials drive the diff (silently dead before #1606)', async () => { + it('BUGFIX pr-phase builder: the review dials drive the diff (dead before the pr gate fired, #1606)', async () => { const ctx = makeStore(); - // A BUGFIX builder mid-investigate: no gate, phase `investigate`. Before #1606 this - // resolved to reviewMode 'none' and every gesture no-opped for the builder's whole life. + // A BUGFIX/AIR builder in the pr *phase*, not yet blocked at the pr *gate* (blockedGate null). + // Before #1606 this resolved to reviewMode 'none' and every gesture no-opped until the gate. ctx.store.overview = { - builders: [{ id: 'bugfix-1', roleId: null, issueId: '1606', issueTitle: 'Review dials', blocked: null, blockedGate: null, protocolPhase: 'investigate', progress: 10, worktreePath: '/w' }], + builders: [{ id: 'bugfix-1', roleId: null, issueId: '1606', issueTitle: 'Review dials', blocked: null, blockedGate: null, protocolPhase: 'pr', progress: 95, worktreePath: '/w' }], pendingPRs: [], backlog: [], recentlyClosed: [], } as never; const nav = new DiffFileNav(ctx.store); @@ -651,6 +651,21 @@ describe('encoders', () => { expect(ctx.canvasSent).toHaveLength(0); // diff mode never touches the canvas channel }); + it('BUGFIX investigate builder: the review dials stay inert (WIP is not a review target, #1606)', async () => { + const ctx = makeStore(); + // Owner ruling: investigate/fix are work-in-progress, so the dials are none-mode and no-op. + ctx.store.overview = { + builders: [{ id: 'bugfix-1', roleId: null, issueId: '1606', issueTitle: 'Review dials', blocked: null, blockedGate: null, protocolPhase: 'investigate', progress: 10, worktreePath: '/w' }], + pendingPRs: [], backlog: [], recentlyClosed: [], + } as never; + const nav = new DiffFileNav(ctx.store); + await nav.onDialRotate(dial(1) as never); + await nav.onDialDown(); + await nav.onTouchTap(); + expect(ctx.sent).toHaveLength(0); // no diff verbs + expect(ctx.canvasSent).toHaveLength(0); // no canvas commands + }); + it('legibility: a none-mode builder\'s strip says "No review target", not the diff label (#1606)', () => { const ctx = makeStore(); // A porch `init`-phase (or soft/shell/task) builder: genuinely nothing to review. The strip @@ -973,13 +988,14 @@ describe('phaseArtifactVerb (shared resolver — recognised verb or undefined)', }); it('leaves BUGFIX investigate/fix and the pr PHASE unrecognised — the review dials own that vocabulary (#1606)', () => { // Deliberate split: `phaseArtifactVerb` stays SPIR/PIR-scoped so the auto-open action key - // keeps opening a terminal (no artifact yet) and Zoom falls back to view-diff. Only - // `reviewMode` maps these phases to diff (see DIFF_REVIEW_PHASES). If this ever changes, - // reconsider the Builder Action key's open-terminal→open-diff-first side effect first. + // keeps opening a terminal and Zoom falls back to view-diff. Only `reviewMode` maps the pr + // phase to diff (see DIFF_REVIEW_PHASES); investigate/fix stay none as work-in-progress. If + // this ever changes, reconsider the Builder Action key's open-terminal→open-diff-first side + // effect first. expect(phaseArtifactVerb(b({ protocolPhase: 'investigate' }))).toBeUndefined(); expect(phaseArtifactVerb(b({ protocolPhase: 'fix' }))).toBeUndefined(); expect(phaseArtifactVerb(b({ protocolPhase: 'pr' }))).toBeUndefined(); - expect(zoomInVerb(b({ protocolPhase: 'investigate' }))).toBe('view-diff'); // Zoom already correct for BUGFIX + expect(zoomInVerb(b({ protocolPhase: 'pr' }))).toBe('view-diff'); // Zoom already correct for the pr phase }); }); @@ -998,13 +1014,19 @@ describe('reviewMode (dial mode from the shared resolver)', () => { 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('BUGFIX investigate/fix and the BUGFIX+AIR pr PHASE → diff (#1606: dead for the builder\'s whole working life before)', () => { - // BUGFIX (`investigate/fix/pr`): the review dials were 'none' until the pr *gate* was - // requested. The pr *phase* (BUGFIX + AIR `implement/pr`) is diffable per the owner ruling. - expect(reviewMode(b({ protocolPhase: 'investigate' }))).toBe('diff'); - expect(reviewMode(b({ protocolPhase: 'fix' }))).toBe('diff'); + it('the BUGFIX+AIR pr PHASE → diff (#1606: dead between entering the pr phase and the gate firing)', () => { + // The pr *phase* (BUGFIX `investigate/fix/pr`, AIR `implement/pr`) holds a finished diff, so + // the dials go live before the pr *gate* is requested. The pr *gate* already resolved to diff + // via phaseArtifactVerb (blockedGate 'pr', line above) — this covers the phase before it. expect(reviewMode(b({ protocolPhase: 'pr' }))).toBe('diff'); }); + it('BUGFIX work-in-progress phases investigate/fix → none (owner ruling: WIP is not a review target)', () => { + // #1606 owner ruling 2026-09-04: investigate/fix are work-in-progress (partial/no diff yet), + // so the dials stay inert with the honest "No review target" label rather than driving a + // half-written diff. Only the pr phase (finished work) is diffable. + expect(reviewMode(b({ protocolPhase: 'investigate' }))).toBe('none'); + expect(reviewMode(b({ protocolPhase: 'fix' }))).toBe('none'); + }); it('an unknown phase, no live status, or no builder → none (init/soft/shell/task have nothing to review)', () => { expect(reviewMode(b({}))).toBe('none'); expect(reviewMode(b({ protocolPhase: 'mystery' }))).toBe('none'); diff --git a/apps/streamdeck/src/actions.ts b/apps/streamdeck/src/actions.ts index 3f949cf51..7d9504c3d 100644 --- a/apps/streamdeck/src/actions.ts +++ b/apps/streamdeck/src/actions.ts @@ -549,29 +549,28 @@ export function zoomInVerb(b: OverviewBuilder): string { export type ReviewMode = 'diff' | 'canvas' | 'none'; /** - * Phase ids that review as a DIFF but that `phaseArtifactVerb` deliberately does NOT - * auto-open. A builder in these phases has no spec/plan document to hand off, and (for - * `investigate`) may not have code yet either, so the auto-open action key keeps opening a - * terminal — but the review dials should still drive the (growing) diff rather than lie or - * die (#1606): + * Phase ids whose review dials drive a DIFF, but that `phaseArtifactVerb` deliberately does + * NOT auto-open (so the Builder Action key and Zoom keep their behavior — see the guard test): * - * - `investigate` (BUGFIX; also RESEARCH's diagnosis phase): the builder's whole working - * life reviews as a diff. RESEARCH's `investigate` produces worktree changes too, so diff - * mode is correct there as well — the id is registered by name, not by owning protocol. - * - `fix` (BUGFIX): the whole working life reviews as a diff. - * - `pr` (BUGFIX + AIR): by the pr *phase* the work already exists as a diff (owner - * ruling 2026-09-04), even before the pr *gate* is requested (`blockedGate === 'pr'` - * already resolves to diff via `phaseArtifactVerb`). + * - `pr` (BUGFIX + AIR): by the pr *phase* the work already exists as a finished diff + * (owner ruling 2026-09-04), even before the pr *gate* is requested (`blockedGate === 'pr'` + * already resolves to diff via `phaseArtifactVerb`). This closes the window where an AIR / + * BUGFIX builder's dials were dead between entering the pr phase and the gate firing. + * + * Deliberately EXCLUDED, do not re-add without an owner ruling: BUGFIX `investigate` / `fix` + * are work-in-progress (owner ruling 2026-09-04). Their diff is partial or not yet written, so + * they are not a review target; the dials stay `none` (inert, with an honest `No review target` + * label, not the old lie), matching `phaseArtifactVerb`, which opens a terminal there. * * REGISTRATION NOTE (mirrors Tower's GATE_LABELS pattern, face.ts): `protocol.json` phase * metadata carries no field expressing review mode (only id/name/type/steps/transition/ - * gate), and the SDK's `PHASE_TO_STAGE` folds `investigate → plan` (canvas — wrong here) - * and drives the VSCode Builders tree, so neither can be reused verbatim. A new protocol - * whose phases review as a diff registers its phase id here (or maps onto - * `phaseArtifactVerb`'s SPIR/PIR vocabulary). An unregistered phase resolves to `none` — - * visibly inert with an honest label, never a silent lie. + * gate), and the SDK's `PHASE_TO_STAGE` drives the VSCode Builders tree with different + * semantics, so neither can be reused verbatim. A new protocol whose phases review as a + * finished diff registers its phase id here (or maps onto `phaseArtifactVerb`'s SPIR/PIR + * vocabulary). An unregistered phase resolves to `none`, visibly inert with an honest label, + * never a silent lie. */ -const DIFF_REVIEW_PHASES = new Set(['investigate', 'fix', 'pr']); +const DIFF_REVIEW_PHASES = new Set(['pr']); /** * The review mode for a builder: a builder still writing its spec/plan reviews as a diff --git a/codev/state/bugfix-1606_thread.md b/codev/state/bugfix-1606_thread.md index ba186b41f..8828daecc 100644 --- a/codev/state/bugfix-1606_thread.md +++ b/codev/state/bugfix-1606_thread.md @@ -138,3 +138,22 @@ Architect review of PR #1607: **APPROVE**. Rulings: **Status: holding at the pr gate. Not running `porch approve` — awaiting the owner's explicit gate decision.** Nothing actionable for me until then. + +## Owner scope correction: pr-only (2026-09-04, at the gate) + +Owner ruled directly: **only the `pr` phase should be diffable, NOT `investigate`/`fix`** — those +are work-in-progress (partial/no diff yet), so they are not a review target. This supersedes the +issue #1606 Fix text (which had listed investigate/fix) and the architect's earlier "as-is" ruling. + +Narrowed the fix: +- `DIFF_REVIEW_PHASES` = `{pr}` only; rewrote the note to record investigate/fix as deliberately + EXCLUDED (owner ruling), so no future reader re-adds them. +- investigate/fix now resolve to `none` → inert dials with the honest `No review target` label. + The value of the fix there is the honest label, not live dials. +- Tests: `pr` phase → diff (behavioral + unit); investigate/fix → none (behavioral + unit); + guard + none-label tests updated. 248 pass; tsc + build clean. +- README + PR #1607 body updated to pr-only. + +This is a real behavior change from the CMAP'd version (investigate/fix diff→none), but it's a +scope REDUCTION on the same mechanism the reviewers blessed. Notifying the architect; will offer +to re-run CMAP if they want. Awaiting the owner's gate word on the corrected PR. From 816be1138886ff8b475755a4ba67520b2e651c9a Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 19:18:49 +1000 Subject: [PATCH 10/12] chore(porch): bugfix-1606 pr gate-approved --- .../bugfix-1606-streamdeck-review-dials-silent/status.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml index b5e21d250..e8dbf09ae 100644 --- a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml +++ b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml @@ -6,11 +6,12 @@ plan_phases: [] current_plan_phase: null gates: pr: - status: pending + status: approved requested_at: '2026-09-04T01:53:53.303Z' + approved_at: '2026-09-04T09:18:48.942Z' iteration: 1 build_complete: false history: [] started_at: '2026-09-04T01:36:08.893Z' -updated_at: '2026-09-04T01:53:53.303Z' -pr_ready_for_human: true +updated_at: '2026-09-04T09:18:48.942Z' +pr_ready_for_human: false From 2380d9a7739628a94d5de89a574659afee312183 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 19:19:35 +1000 Subject: [PATCH 11/12] chore(porch): bugfix-1606 protocol complete --- .../bugfix-1606-streamdeck-review-dials-silent/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml index e8dbf09ae..544d684c6 100644 --- a/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml +++ b/codev/projects/bugfix-1606-streamdeck-review-dials-silent/status.yaml @@ -1,7 +1,7 @@ id: bugfix-1606 title: streamdeck-review-dials-silent protocol: bugfix -phase: pr +phase: verified plan_phases: [] current_plan_phase: null gates: @@ -13,5 +13,5 @@ iteration: 1 build_complete: false history: [] started_at: '2026-09-04T01:36:08.893Z' -updated_at: '2026-09-04T09:18:48.942Z' +updated_at: '2026-09-04T09:19:35.190Z' pr_ready_for_human: false From c3fea8d59a78fc55c12d3368791cbe50a7e5d8a5 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 4 Sep 2026 19:20:15 +1000 Subject: [PATCH 12/12] [Bugfix #1606] chore: record pr gate approval + protocol-complete hold in thread --- codev/state/bugfix-1606_thread.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/codev/state/bugfix-1606_thread.md b/codev/state/bugfix-1606_thread.md index 8828daecc..d02739cef 100644 --- a/codev/state/bugfix-1606_thread.md +++ b/codev/state/bugfix-1606_thread.md @@ -157,3 +157,16 @@ Narrowed the fix: This is a real behavior change from the CMAP'd version (investigate/fix diff→none), but it's a scope REDUCTION on the same mechanism the reviewers blessed. Notifying the architect; will offer to re-run CMAP if they want. Awaiting the owner's gate word on the corrected PR. + +## pr gate APPROVED + protocol advanced (2026-09-04 09:18) + +Amr approved the pr gate via VS Code at 09:18 on the pr-only code head **dbc1fcc87** (architect +relayed it as `porch approve`, human-approved). The approval landing on the pr-only head also +authenticates the pr-only scope ruling. + +Ran `porch done` → **PROTOCOL COMPLETE** (advanced to the verified/terminal state). Branch in sync +with origin. + +**MERGE IS NOT MINE.** Architect instruction: the merge still needs Amr's explicit per-PR word +plus his admin bypass (branch protection); the architect handles it when Amr's words land. So I am +NOT running `gh pr merge` and NOT running `porch done` again (the post-merge step). **Holding.**