Skip to content

feat(#691): PIR protocol + VSCode Codev workflow integration#763

Merged
waleedkadous merged 65 commits into
mainfrom
feat/691-pir-and-vscode-workflow
May 19, 2026
Merged

feat(#691): PIR protocol + VSCode Codev workflow integration#763
waleedkadous merged 65 commits into
mainfrom
feat/691-pir-and-vscode-workflow

Conversation

@amrmelsayed
Copy link
Copy Markdown
Collaborator

@amrmelsayed amrmelsayed commented May 18, 2026

Summary

This branch delivers the PIR protocol (#691) end-to-end, plus a substantial round of VSCode-extension workflow improvements that grew out of dogfooding PIR, and a handful of supporting fixes. It was built incrementally by multiple agents over many sessions; the description below is organized by theme rather than by commit order so the whole picture is reviewable in one pass.

origin/main was merged in twice during development (codex-sdk cert fix, then a full main catch-up), so this diff is exactly what the branch adds on top of current main.

Closes #691 · Closes #737


1. PIR protocol (#691)

PIR = Plan → Implement → Review, an issue-driven protocol that sits between the lightweight BUGFIX/AIR protocols and full SPIR. It is the right choice when a change either needs design review before coding starts, or needs the running code tested before a PR exists (mobile/UI/cross-platform). The issue body is the implicit spec — there is no spec phase.

Three human gates:

  • plan-approval — human approves the plan before any code is written.
  • dev-approval — human reviews the running worktree (not a PR diff) before a PR is created. (Renamed from the original code-review for naming consistency.)
  • pr — post-PR merge-synchronization gate, matching SPIR's pr-gate pattern. The builder does not auto-merge; it runs gh pr merge only after this gate is approved. No post-merge verify phase.

Review phase is a single advisory pass (max_iterations: 1) — CMAP runs once at the PR, porch-driven (no double-run), and a REQUEST_CHANGES is escalated to the human at the pr gate rather than triggering an iterate-until-APPROVE loop. This keeps PIR's consultation footprint at BUGFIX/AIR parity (a design invariant — porch model precedence is config > protocol, so a project-wide model list must be scoped per-protocol to preserve the cost profile). The review artifact is shaped identically to SPIR's (Summary + Architecture Updates + Lessons Learned) so codev/reviews/ stays semantically consistent across protocols.

Protocol lives in codev/protocols/pir/ and is mirrored into codev-skeleton/protocols/pir/ so other projects get it via the package skeleton. CLAUDE.md / AGENTS.md document PIR, the three-gate set, and the file-resolution guidance (a protocol resolving from the skeleton is the normal case — its absence on disk is not a missing reference).

2. Porch / agent-farm integration

  • spawnPir added; pir and bugfix dispatched separately in the spawn path; 'pir' added to buildAgentName.
  • PIR project IDs aligned with SPIR — dropped the protocol prefix (pir-NN); detectProjectIdFromCwd and the overview/branch recognizers updated to match PIR worktrees and branches; stale pir-<id>-<slug> filename references scrubbed from docs.
  • porch status --json flag for structured gate state.
  • Gate notifications reworked: a generic notifyTerminal primitive replaces architect-bound notifications; porch approve skips the builder wake-up when the caller is the builder; CLI broadcasts overview-changed after mutating commands.
  • Fixes: artifact resolution for prefix-N project IDs; pass the porch project ID (not the builder agent name) to the prompt template; invalidate the Tower overview after a builder is removed.

3. VSCode extension — Codev workflow

These are the actual [Unreleased] entries from packages/vscode/CHANGELOG.md (the authoritative list):

What's new

  • Codev-managed dev server for the current workspace. New Start Dev Server / Stop Dev Server rows in the sidebar's Workspace view run worktree.devCommand for whatever folder the window is rooted at — the main checkout (CLI: afx dev main) or, if you opened a .builders/<id>/ worktree as its own window, that builder. One dev runs at a time across {main + all builders}; starting another prompts to swap. The two rows are mutually exclusive: Start when stopped, Stop when running.
  • Dev server terminals open in the bottom panel (not an editor group). A dev server is a long-running log, not a tab you switch between; type: 'dev' terminals now always use the panel regardless of codev.terminalPosition. Architect (left group) and builder/shell (right) are unchanged.
  • Builder terminal tabs are labeled by issue — a builder's tab now reads Codev: #<issueId> <issueTitle> (matching its sidebar row) instead of the internal builder-<protocol>-<id> agent name. The title is capped at 25 characters on a whole-word boundary (with ); the #<id> prefix is kept whole. Falls back to the agent name when overview data or a builder match is unavailable. Architect / Shell / dev tab names are unchanged.
  • Team view is a per-member snapshot. Expanding a teammate shows Assigned: N and Open PRs: N count summaries plus Last 7d: X merged, Y closed — the full issue/PR lists already live in the Backlog and Pull Requests views. A Refresh button in the Team title bar forces a re-fetch (Team otherwise only updates as a side effect of other Tower activity).
  • A symmetric keyboard-shortcut familyCmd+Alt+C / Ctrl+Alt+C toggles the Codev sidebar (opens & focuses it if hidden, closes it if it's the active view); Cmd/Ctrl+Alt+R and Cmd/Ctrl+Alt+S start / stop the current workspace's dev server. The existing Cmd/Ctrl+K A / D / G (open architect, send message, approve gate) are unchanged.
  • Workspace sidebar gained Spawn Builder and New Shell rows, next to Open Architect and Open Web Interface.
  • Gate-pending toasts with one-click Approve. When a builder reaches a human-approval gate, a toast surfaces it with a per-gate action (View Plan / Run Dev / Review) and an Approve button. The approval dialog now shows the issue + gate context. Silence with the new codev.gateToasts.enabled setting.
  • Inline review comments on plan/spec files via VSCode's Comments API — leave REVIEW(@architect): threads in the editor, not only the text snippet.
  • PIR protocol support — PIR in the Spawn Builder picker, a View Plan action for PIR builders, and context-menu actions scoped per protocol.
  • Open Worktree in New Window — opens .builders/<id>/ as its own VSCode window. Replaces the former "Codev: View Diff" (3.0.3), which couldn't reliably render multi-file worktree diffs; a real worktree window gives native SCM + diffs.
  • Builder right-click menu reordered — primary actions first (open terminal, approve), then worktree actions, then dev actions, grouped so the common ones are at the top.
  • "Needs Attention" is merged into Builders. Blocked builders are flagged inline in the Builders view (bell icon) instead of a separate section, and gate toasts point at the builder's pane. The standalone Needs Attention view is gone.
  • Live item counts in the Builders / Pull Requests / Backlog / Recently Closed view titles; Recently Closed gained a refresh button.
  • Backlog is actionable — "Codev: View Issue" opens an issue in the right pane, assigned-to-me issues are surfaced, and you can click-to-spawn a builder from a backlog issue.
  • Periodic sidebar refresh while visible, every codev.overviewRefreshSeconds (default 60; 0 = event-only, the previous behavior). A shared 30s Tower-side cache throttles GitHub calls across windows.

Bug fixes

  • The sidebar survives SSE event bursts without losing state (last-write-wins in the overview cache).
  • Builder terminal tabs close automatically on cleanup instead of lingering as dead "Process exited" tabs.
  • Tower PTY dimensions sync on terminal open, fixing wrapped/truncated output. (VSCode terminals: TUI render artifacts on first open — initial Pseudoterminal dimensions are ignored #737)
  • Gate-pending toast: the issue title is quoted (no longer reads like an error), and the seen-set persists across reloads so a still-blocked builder doesn't re-toast on every window reload.
  • State-change actions (Approve Gate, Cleanup Builder, …) now wait for Tower and refresh the sidebar immediately, instead of leaving it briefly stale.
  • Approving a gate from the sidebar targets the correct (canonical) gate name and runs porch approve in the right working directory — previously it could mis-resolve for renamed/shared gates or non-root worktrees.
  • Clicking a backlog issue reuses a single editor group for the preview instead of opening a new split group on every click after the first.
  • Team per-member counts (Assigned / Open PRs / the 7-day merged & closed) now show the true totals instead of silently capping at 20 — they read GitHub's search issueCount rather than the length of the (20-node-limited) result list.

4. Database migration (v10) — operational note

The original branch added 'pir' to the builders.type CHECK constraint as local-DB migration v9. main independently shipped a Spec-755 v9 (multi-architect). To avoid the collision, the 'pir' migration was reintroduced as v10 (fix(db) — drift-robust and idempotent: it derives DDL from live sqlite_master, table-recreates only if 'pir' is absent).

No manual action is required in any workspace — the local DB auto-migrates lazily on the first builder-state DB access per process (verified on a fresh spawn in a previously-unused workspace: born fully current). The only workspace that needed a one-off _migrations recovery was the one where the old PIR-branch v9 had already been recorded; that has been done and verified.

5. Other supporting fixes

  • fix(forge) — recently-closed/merged no longer drops genuinely-recent items (bare-date query bug, server-side).
  • Merged the codex-sdk@0.130 cert-revocation fix.

Testing & verification

  • pnpm build clean (core → codev → dashboard); VSCode check-types + lint clean.
  • Full unit suite (2964 tests) green.
  • test(porch) locks in the PIR protocol shape; synthetic v10 migration test PASS.
  • DB migration verified against a real recovered workspace (_migrations 1..10, 'pir' present, spawned_by_architect present, builder rows intact) and a fresh-spawn workspace (born current, no migration needed).

Files

78 files, +5350 / −650 — codev + codev-skeleton (PIR protocol & skeleton), packages/codev (porch + agent-farm), packages/vscode (extension), plus small touches to packages/core, packages/dashboard, packages/types, and the synced CLAUDE.md / AGENTS.md.

PIR (Plan → Implement → Review) is a new sibling protocol for
GitHub-issue-driven work that needs human review at two points:
the approach (before coding) and the implementation (before a PR).

Three phases: plan → implement → review.
Two human gates: plan-approval, code-review.
Artifacts: codev/plans/<id>-<slug>.md and codev/reviews/<id>-<slug>.md
on the builder branch (ship to main with the merged PR).

Structurally SPIR minus the specify phase, with the code-review gate
firing pre-PR rather than post-PR. Consult footprint matches BUGFIX/AIR
(one CMAP-2 at PR creation; human gates do the heavy review).

Closes part of #691.
The artifact resolver matched files by extracting leading digits and
comparing against `projectId.replace(/^0+/, '')`. This worked for numeric
project IDs (SPIR/ASPIR/AIR: "0073") but silently failed for prefix-N IDs
(BUGFIX: "bugfix-237", PIR: "pir-1099") because no file's leading digits
can equal a string starting with letters.

PIR was the first issue-driven protocol with a plan-resolution check
(plan_exists), so it surfaced this latent bug. BUGFIX has the same
project-ID format but no plan/review-resolution checks in its
protocol.json, so it never hit the broken path.

Replaces six instances of the broken regex match with a single
matchesProjectId helper that handles both formats:

- "<prefix>-<N>" matches files starting with "<prefix>-<N>-" or equal to it
- "<digits>" matches files whose leading digits (zero-stripped) equal it

Also fixes hasPreApproval's path-to-projectId regex (it only matched
numeric path components; now extracts both formats).

Adds 40 tests covering the helper directly and the LocalResolver paths
with PIR / BUGFIX project IDs, plus regression guards for the
numeric-ID path that SPIR / ASPIR / AIR depend on.

Refs #691.
…migration v9

Routing (proper separation, not flag-flavored bugfix):
- Extract spawnIssueDrivenBuilder as the shared helper for any issue-driven
  protocol (fetch issue, derive worktree path, init porch, render prompt, …).
  Takes just the prefix as a parameter; derives the human-readable label
  internally.
- spawnBugfix and spawnPir are one-line wrappers that pass their prefix.
- getSpawnMode returns 'bugfix' or 'pir' honestly — no more lumping.
- handlers Record has reachable entries for both: bugfix → spawnBugfix,
  pir → spawnPir. No dead code.
- findExistingBugfixWorktree generalized to findExistingIssueWorktree
  with a protocolPrefix arg; old name kept as a thin backward-compat
  wrapper for the existing test fixture.

DB:
- BuilderType union includes 'pir'
- builders.type CHECK constraint includes 'pir' for fresh installs
- Migration v9 recreates the existing table with the new CHECK constraint
  (SQLite can't ALTER CHECK; idempotent — no-op if 'pir' already present)

CLI help text in both cli.ts files updated to list pir.

The pattern generalizes — any future issue-driven protocol gets a one-line
spawn wrapper and a new dispatch entry, sharing the implementation through
spawnIssueDrivenBuilder.

Refs #691.
Extend the worktree-path regex to match `.builders/pir-<N>[-<slug>]/`
paths and return `pir-<N>` as the porch project ID — same pattern as
bugfix worktrees use today.

Without this, running `porch status` (or any other porch command that
relies on CWD-based project detection) from inside a PIR worktree
falls through to filesystem scanning instead of detecting the project
directly.

Refs #691.
Add a --json flag to `porch status` that emits a single-line JSON
object suitable for consumption by the VSCode Needs Attention view
(and any other tooling needing structured access to gate state).

Object shape:
  {
    "id": string, "title": string, "protocol": string,
    "phase": string, "iteration": number, "build_complete": boolean,
    "gate": string | null,
    "gate_status": "pending" | "approved" | null,
    "gate_requested_at": string | null,
    "gate_approved_at": string | null
  }

Suppresses all chalk/console output when --json is set; writes the
single JSON line to stdout via process.stdout.write so the consumer
gets parseable output even if other code paths log warnings.

Tests cover: object shape, pending-gate case, approved-gate case,
ungated-phase case (gate: null), code-review gate at implement phase,
suppression of human-readable console output.

Refs #691.
…ashboard

The overview API's detectBlocked() and detectBlockedSince() functions
had a hardcoded gate-name allowlist (spec-approval, plan-approval, pr)
that didn't include PIR's code-review gate. Result: when a PIR builder
reached code-review, OverviewBuilder.blocked stayed null — making the
builder invisible to the VSCode Needs Attention tree, the VSCode toast,
the dashboard NeedsAttentionList, and the status bar counter.

Adds 'code-review' to both gate-name allowlists in overview.ts.

Dashboard CSS routing:
- NeedsAttentionList previously had hardcoded kindClass routing
  (spec review → spec class; everything else → plan class). Extracted
  to a gateKindClass() helper that maps known kinds to dedicated
  classes and falls back to plan styling for unknowns.
- Added .attention-kind--code-review CSS using --status-waiting (same
  semantic as PR review — "waiting on a human", not an error).

Refs #691.
BuilderType gained a 'pir' variant in 2d835ef (commit 3 of #691) but
buildAgentName's switch never grew a corresponding case — PIR builder
IDs were falling through to the default. Add the 'pir' branch so PIR
builder IDs are constructed correctly (e.g., 'builder-pir-737' for
issue 737).

Adds one test case mirroring the existing bugfix coverage.

Refs #691.
…imitive

Fills two long-standing UX gaps in porch's gate handling, both visible
across all gated protocols (SPIR, ASPIR, PIR) — not PIR-specific:

1. notifyArchitect (added in Spec 0108) was orphaned: the function existed
   but no porch site ever called it. None of the four gate-pending sites
   in index.ts / next.ts notified the architect, so the architect pane
   only learned of gate state by user narration. Now wired up at every
   site that marks a gate pending.

2. After `porch approve`, the builder's interactive Claude session sat
   idle at the gate with nothing to prompt `porch next` — users had to
   type into the builder pane to make it advance. Now porch sends a
   wake-up message to the builder PTY immediately after approval so the
   builder reads it as its next user input and advances on its own.

Implementation: collapsed `notifyArchitect` and a new builder-wake-up
function into a single `notifyTerminal({ target, message, worktreeDir,
draft? })` primitive. `draft: true` appends `--no-enter` for architect
notifications (typed into the input buffer, not submitted) so the human
sees the text but reviews before acting. `draft: false` submits
immediately so the receiver's Claude session processes the message on
its next turn (used for builder wake-ups).

Message formatting lives in two pure helpers — `gatePendingMessage` and
`gateApprovedMessage` — that the call sites compose with notifyTerminal.

Tests cover target routing, draft vs submit, error swallowing, cwd /
timeout, and both message helpers.
Tree items in the Builders / Needs Attention views previously used a
flat contextValue (`builder`, `blocked-builder`). That gave VSCode no
signal about which protocol the builder runs, so every right-click
menu had to apply to all builders or none.

Now contextValue carries the protocol: `builder-pir`, `builder-bugfix`,
`blocked-builder-spir`, etc. The existing six menu items (Open Terminal,
Open Worktree, Review Diff, Run Setup, Run Dev, Stop Dev) widen their
when-clause regex from `^(builder|blocked-builder)$` to
`^(builder|blocked-builder)-` so they continue matching every builder.

This unlocks per-protocol menu items (e.g., PIR-only View Plan / View
Review) in a follow-up commit — they can scope on `viewItem =~
/^(builder|blocked-builder)-pir$/`.
PIR's plan-approval and code-review gates produce file artifacts on the
builder's branch (codev/plans/pir-<id>-<slug>.md and
codev/reviews/pir-<id>-<slug>.md). At a gate the reviewer needs to read
those files, but the worktree dir isn't open in VSCode by default, so
finding them was awkward.

Adds two commands surfaced as right-click items on PIR builder rows in
the Builders / Needs Attention views:

  - Codev: View Plan File
  - Codev: View Review File

Scoped to PIR via the when-clause `viewItem =~ /^(builder|blocked-
builder)-pir$/` (relying on the contextValue from the previous commit).
Other protocols don't ship plan/review files at gates so the menu items
don't apply to them.

The implementation reads <worktree>/codev/plans|reviews/ and filters to
files prefixed with the builder ID. Without that filter the worktree's
inherited history surfaces every other builder's plan/review file in the
quick-pick. One match → open directly; multiple → quick-pick sorted by
mtime; none → friendly toast.
…ebar

The three state-changing tree commands (approve, cleanup, send-message)
previously fired their CLI work as detached child processes with
`stdio: ignore`. Three consequences:

  1. Errors were silently swallowed. `porch approve` could fail (bad
     gate name, missing flag) and the user would see only "Approving..."
     with no failure feedback.
  2. The sidebar didn't refresh after approve / cleanup. The just-
     approved gate kept the builder in the Needs Attention tree until
     the next SSE tick (looked like the action didn't take); a cleaned-up
     builder lingered in the Builders tree for several seconds — that
     was the bug you noticed earlier.
  3. The architect's conversation history drifted out of sync. Porch
     orchestrates through the architect, but user-initiated VSCode
     actions (approve, cleanup, send) weren't reaching it, so the
     architect's mental model of the workspace lagged behind reality.

Now each command:
  - awaits the CLI via `execFile` (promisified) and surfaces errors
    via showErrorMessage,
  - calls `OverviewCache.refresh()` so the tree updates immediately,
  - fires a one-line `afx send architect` breadcrumb describing what
    the user just did.

The architect-bound `afx send` calls are best-effort (`.catch(noop)`) —
the architect terminal may not be live in all workflows.

`approveGate` and `cleanupBuilder` take the OverviewCache as an
optional second arg so the call sites can opt in to refresh; existing
test setups that constructed these commands without a cache continue
to work.
Adds a passive notification path for gated protocols (SPIR, ASPIR, PIR).
Without it, the only signal a builder needs review is the Needs
Attention tree updating — which requires the user to be looking at the
sidebar. The toast surfaces the gate without forcing attention there.

Behavior:
- Subscribes to OverviewCache changes. On each tick, diffs the
  blocked-builder set against a module-local seen-set keyed by
  (builderId, gateName).
- New entries fire showInformationMessage with a "Review" action that
  opens the architect terminal — porch orchestrates through the
  architect, so the human-driven response starts there. (Artifact-
  specific entry points — View Diff, View Plan File, Run Dev Server —
  remain on right-click of the builder row.)
- Entries leaving the blocked set are pruned so re-blocking later on
  a different gate re-toasts.
- Respects the new `codev.gateToasts.enabled` setting (default true).

No-op for BUGFIX / AIR builders — they have no gates, so they never
enter the blocked set and never trigger toasts.
Three phases (plan / implement / review), two gates (plan-approval on
plan, code-review on implement), review phase terminal (next: null).
Synthesizes a minimal PIR-shaped protocol on disk and runs it through
loadProtocol so the contract is enforced by `pnpm test` regardless of
working-tree state of codev/protocols/pir/.
Adds three pieces to the source-repo CLAUDE.md / AGENTS.md and the
skeleton templates that ship to consumer projects:

1. PIR entry in the Available Protocols list — one-line description
   that disambiguates it from SPIR (lighter, two gates instead of three)
   and BUGFIX/AIR (stronger, has gates).

2. "Use PIR for" selection guide — paired criteria. Either the
   approach needs design review before coding (high blast radius,
   ambiguous root cause) OR the implementation needs to be tested
   before a PR exists (mobile, UI, hardware-adjacent, OAuth). One
   trigger is enough.

3. File Resolution section — documents the four-tier lookup chain
   (.codev → codev → cache → installed skeleton) and warns against
   the recurring failure mode during `codev update` merges, where
   AI agents drop a protocol reference because `codev/protocols/<name>/`
   doesn't exist locally. It resolves from the package skeleton; the
   reference must stay.

4. Protocol Verification section — when in doubt about a protocol
   name, `afx spawn --protocol <name> --help` is the source of truth
   (it resolves via the same four-tier chain).

Skeleton template additions (codev-skeleton/templates/) are a subset
of the source-repo additions — no source-repo-specific paragraphs.
PIR / SPIR / ASPIR gates are explicit human-decision points: the user
reads the plan file at plan-approval, runs the worktree at code-review,
checks production at verify-approval. The architect can't act on any
of these autonomously — approval requires the human
`--a-human-explicitly-approved-this` flag — so pushing gate state into
its conversation history only added noise. The toast (commit c59bb0b)
and the Needs Attention tree already cover the "user needs to know"
path natively.

Symmetrically, the VSCode-side breadcrumbs that fired on every user
approve / cleanup / send (commit 44f1298) were adding noise of the
same shape: the user is already at the keyboard when these fire, and
the architect can pull state via `porch status` / `porch pending` when
asked. Push wasn't earning its slot in the architect's context window.

Net effect:
- notifyTerminal kept, but only the builder wake-up after approval
  uses it (genuinely needed — the builder's idle Claude session has
  no other input event to advance on).
- gatePendingMessage helper removed entirely (dead code).
- Architect notification call sites removed from porch/index.ts (3
  sites) and porch/next.ts (1 site).
- afx-send-architect breadcrumbs removed from approve.ts / cleanup.ts /
  send.ts; OverviewCache.refresh() retained (different concern —
  sidebar responsiveness, not architect awareness).

Side effect: the `code-review`-vs-PR semantic clash dissolves. The
architect never sees `code-review` so it never goes hunting for a PR
that doesn't exist yet.
…lder

When `afx cleanup` ran from a shell, the architect, or any path outside
VSCode's Cleanup command, the removed builder lingered in the sidebar
indefinitely. The OverviewCache in VSCode refreshes on any SSE event
from Tower, but cleanup itself wasn't firing one — the stale entry only
disappeared if some unrelated event happened to trigger an incidental
re-fetch.

Adds `TowerClient.refreshOverview()` (POST /api/overview/refresh, which
already exists server-side — Bugfix #388) and calls it at the end of
`cleanup()`. Tower invalidates its in-memory overview cache and
broadcasts an `overview-changed` SSE event; subscribed clients pick it
up and re-fetch /api/overview.

Best-effort: try/catch wraps the call so cleanup still succeeds when
Tower isn't running (e.g., headless CI cleanup).

The previously-added `cache.refresh()` in VSCode's Cleanup command
becomes a no-op once Tower fires the SSE — but it's retained as a
local-latency optimization (skips the round-trip wait).
… to prompt template

PIR builders launch and immediately fail with:
  Project builder-pir-1298 not found.
  Run 'porch init' to create a new project.

…because the prompt template's `{{project_id}}` was substituted with
the builder agent name (`builder-pir-1298`) instead of the porch
project ID (`pir-1298`).

PIR's prompts pass `{{project_id}}` directly to `porch next` /
`porch done` / `porch approve`, so the mismatch breaks every CLI call.
Porch stores state under `<prefix>-{N}` (`pir-1298`), matching
`initPorchInWorktree`, the worktree dir, the branch name, and
`detectProjectIdFromCwd`'s regex — but the template variable diverged.

BUGFIX has had the same bug all along but didn't surface it because
its builder-prompt.md uses bare `porch next` (no arg), relying on
porch's cwd-based detection. PIR prompts hardcode `{{project_id}}` and
exposed the latent issue.

Fix: hoist `porchProjectId = ${prefix}-${issueNumber}` outside the
if/else (was defined twice, then dropped from the template context)
and pass it as `project_id` to the template.

`buildResumeNotice` still takes `builderId` — that's fine, it ignores
the arg and instructs the builder to run bare `porch next`.
Tower's discovery functions hardcoded prefix-aware regex per protocol
(spir, air, aspir, bugfix). PIR was missing, so:

- `extractProjectIdFromWorktreeName('pir-1298-slug')` returned null,
  which made discoverBuilders push the entry as soft-mode with empty
  protocol and no status.yaml lookup. The VSCode sidebar then rendered
  the row with contextValue `builder-` (no protocol) and the
  PIR-scoped menu items (View Plan File / View Review File) never
  matched their when-clause `^(builder|blocked-builder)-pir$`.

- `worktreeNameToRoleId` fell through to a generic match that happened
  to produce the right ID but only as a fallback. Made explicit for
  consistency with the other protocols.

- `analytics.ts` BRANCH_PROTOCOL_PATTERNS missed PIR, so analytics
  reporting attributed PIR PRs to no protocol.

Adds explicit PIR cases in all three places. PIR worktrees now follow
bugfix's convention: project ID is `pir-<N>` (mirrors what
initPorchInWorktree writes and what `porch next/done/approve` expect).
…tself

When the user types "approve" into the builder's pane, the builder's
Claude session runs `porch approve` as a Bash tool call. The previous
implementation fired notifyTerminal unconditionally — the wake-up
message was delivered to the same builder PTY that just issued the
command, surfacing as the builder's next input ("Gate code-review
approved — please run `porch next` to advance"). Claude then responded
to its own approval message.

The wake-up only serves a purpose when the builder is genuinely idle —
i.e., when porch is invoked from something other than the builder
itself: VSCode's execFile, the architect's Bash tool, a separate shell.
In all three of those cases, cwd is OUTSIDE the builder's worktree. The
builder calling porch on itself has cwd = the worktree.

Compare `process.cwd()` (workspaceRoot) against `artifactRoot` (derived
from the status.yaml path, always resolving to the worktree). Match →
skip wake-up; differ → fire as before.

No behavior change for external approvers (VSCode, architect, separate
shell): wake-up still nudges the idle builder forward.
The VSCode sidebar (and dashboard) didn't reflect builder state changes
without a manual refresh — a builder that reached a gate stayed
"running" in the Builders tree until something unrelated happened to
fire an SSE event and cause an incidental re-fetch.

Symptom: builders that just hit plan-approval / code-review didn't
move into Needs Attention; approved builders stayed blocked; freshly
spawned builders appeared late.

Mirrors the cleanup fix (419c1f3). After porch dispatches a mutating
command (next / done / gate / approve / rollback / verify / init), fire
TowerClient.refreshOverview() which invalidates Tower's overview cache
and broadcasts overview-changed via SSE. VSCode's OverviewCache picks
that up and re-fetches /api/overview, which re-renders the sidebar.

Best-effort: try/catch wraps the Tower call so porch commands still
succeed when Tower isn't running (CI, headless usage).

Read-only commands (pending / status / check) intentionally don't fire
— builders run `porch status` frequently and we don't want to hammer
Tower on every read.
…rdcode 'main'

The View Diff command compared against a hardcoded `main`. Repos using
`master`, `develop`, `trunk`, or any other default name would either
silently produce a wrong diff (if a local `main` happens to exist as a
non-default branch) or fail with "bad revision" (if `main` doesn't
exist at all).

Detect the default branch via `git symbolic-ref --short
refs/remotes/origin/HEAD` which resolves to e.g. `origin/main` or
`origin/master`. Strip the `origin/` prefix and use that as the diff
base.

Falls back to `main` if `origin/HEAD` isn't set locally — same behavior
as before for repos that work today.

Diff title now shows the actual branch name ("Reviewing pir-1298
(master ↔ HEAD)") so reviewers know what they're looking at.
…approve

Two bugs broke the Approve Gate command:

1. **Gate name mismatch.** Tower's overview returned `blocked` as a
   *display label* ("plan review", "code review") via detectBlocked.
   VSCode passed that string straight to `porch approve`, which looks
   up state.gates by the *canonical* name ("plan-approval",
   "code-review"). Porch couldn't find a gate named "plan review" and
   the call failed with "Project pir-1298 not found" (the error path
   is misleading — the project IS found but the unknown gate aborts
   later).

   Fix: add `blockedGate` to OverviewBuilder (canonical name like
   "plan-approval") alongside the existing `blocked` (display label).
   detectBlockedGate populates it. VSCode's approve command uses
   blockedGate when calling porch; sidebar / dashboard keep using
   `blocked` for display.

2. **Wrong cwd.** approveGate (and cleanupBuilder) called execFile
   without setting cwd, so porch ran from VSCode's launch directory
   (usually `/` on macOS) and couldn't find `.builders/<id>/` to load
   the project state. Added `{ cwd: workspacePath }` to both.

Pre-existing display labels stay unchanged — `blocked` is consumed
across the sidebar tree, status bar, dashboard, and toast.
Two sidebar UX changes for blocked builders:

1. **Inline Approve Gate button.** Each blocked-builder row in the
   Builders / Needs Attention views now shows a check icon. Hover
   (or pin always-visible) → click → modal confirmation → approve.
   One-click approval without navigating the command palette.

   The same command is still on the right-click context menu and via
   Cmd+K G (which now also accepts a builder ID if invoked from a
   tree row).

2. **Drop View Review File menu item.** The review markdown is the PR
   body in PIR's review phase — reviewers read it on GitHub, not from
   a local file. The menu item only had value in a narrow window
   between code-review approval and PR creation, which isn't worth
   the contextValue scoping and the file-picker UX. Dropped command,
   menu entry, declaration, and the unused export in view-artifact.ts.
…t to builder pane

Two related sidebar changes:

1. Merge Needs Attention into Builders. Both views displayed the same
   builders — Needs Attention as a subset filtered to blocked,
   Builders as the full list. With N total builders and K blocked,
   the sidebar showed N+K rows with K duplicates.

   Now a single Builders tree:
   - Blocked builders sort to the top, oldest-blocked first.
   - Bell icon + `[2m]` wait-time suffix for blocked rows.
   - Play icon + `[<phase>]` for active rows.
   - Inline Approve button still only renders on blocked-builder-*
     contextValues (unchanged scoping).

   Ordering logic lives in a module-level `orderForDisplay()` helper so
   getChildren stays a three-line "fetch, order, map" flow.

   PRs that previously appeared in Needs Attention are already covered
   by the dedicated Pull Requests view — no functional loss. Status bar
   counter still reads `N builders · K blocked` for at-a-glance triage.

   Removed: codev.needsAttention view declaration, NeedsAttentionProvider
   registration, the provider file itself, and the now-redundant
   `(builders|needsAttention)` regex in every menu when-clause.

2. Toast "Review" button opens the builder pane (was: architect
   terminal). The architect was deliberately taken out of the gate
   loop in ff05315 — opening its terminal from the toast landed
   users in a pane with no gate context. Now Review opens the
   builder's own pane via `codev.openBuilderById`, which is where
   the gate-reached message + interactive Claude live.
CodevPseudoterminal.open() ignored its initialDimensions argument, so
Tower's PTY stayed at node-pty's 80x24 default until the user manually
resized the panel. Claude Code's TUI computed its input-box anchor row
against that wrong height and rendered it mid-screen, overlapping the
streaming response — until a manual resize triggered SIGWINCH and made
claude-code redraw at the real dimensions.

Cache the latest dimensions (seeded from open(), refreshed on every
setDimensions()) and re-send them after every WebSocket auth. Also
covers reconnect-after-resize: the new PTY isn't stuck at 80x24 even
if a resize happened while the connection was down.
PIR previously used `pir-<N>` as its porch project ID, which propagated
into artifact filenames (`codev/plans/pir-1298-fix-foo.md`,
`codev/reviews/pir-1298-fix-foo.md`). That broke consistency with the
long-established SPIR / ASPIR / AIR convention of `<N>-<slug>.md`.

The prefix was an unintentional side effect — I inherited it when
generalizing `spawnBugfix` into `spawnIssueDrivenBuilder` (2d835ef)
without noticing SPIR did it differently. Protocol identity is already
encoded in the worktree dir, branch name, and Tower agent name; the
project ID doesn't need to carry it.

Changes:

- `spawnIssueDrivenBuilder`: for `prefix === 'pir'`, porch project ID
  is now `String(issueNumber)`. BUGFIX is untouched (`bugfix-<N>`
  kept for back-compat with on-disk artifacts).
- `extractProjectIdFromWorktreeName` (Tower overview): PIR worktrees
  resolve to the bare numeric ID, matching SPIR / ASPIR / AIR.
- `detectProjectIdFromCwd` (porch cwd auto-detect): same — PIR moves
  from the bugfix group (`<prefix>-<N>`) to the protocol-numeric
  group (`<N>`).
- PIR prompts: artifact paths now use `{{artifact_name}}` (the SPIR
  idiom that resolves to `<id>-<slug>` via porch's prompt template
  engine — defined in commands/porch/prompts.ts:97). Worktree and
  `afx dev` references keep the `pir-` prefix since those point at
  the worktree dir / agent name, both of which still carry it.
- Tests: added PIR cases to overview and state.

Net effect: PIR now produces `codev/plans/1298-fix-foo.md` and
`codev/reviews/1298-fix-foo.md`, matching SPIR. Worktree dir
(`.builders/pir-1298-foo/`), branch (`builder/pir-1298`), and Tower
agent (`builder-pir-1298`) still carry the protocol prefix for
namespace separation.

Existing PIR builders need cleanup + respawn to pick up the new
project ID (their on-disk status.yaml has the old `pir-N` ID).
BUGFIX is untouched and its on-disk artifacts remain valid.
Follow-up to dc177c8 (PIR project ID aligned with SPIR). The code
paths produce the new shape correctly; this commit fixes the docs,
comments, and tests that still described the old `pir-<id>-<slug>`
layout.

Files updated:

- codev/protocols/pir/protocol.md (+ skeleton mirror): "File Locations"
  block drops the `pir-` prefix on plans/, reviews/, projects/ paths.
- codev/protocols/pir/consult-types/impl-review.md (+ skeleton mirror):
  one stale review-file path.
- packages/vscode/src/commands/view-artifact.ts: comment example
  pir-1298-fix-foo.md → 1298-fix-foo.md (filter logic itself was
  already correct because builder.id is the porch project ID).
- packages/codev/src/commands/porch/artifacts.ts: matchesProjectId
  docstring moves PIR from the prefix-N group to the numeric group;
  examples and inline comment updated.
- packages/codev/src/commands/porch/__tests__/artifacts.test.ts:
  - Renamed the "prefix-N project IDs" describe block to make clear
    the path is now bugfix-only.
  - Replaced PIR-tagged prefix-N tests with bugfix-style equivalents
    (the resolver path is unchanged, only the protocol label is).
  - Added two new tests asserting LocalResolver finds PIR
    plan/review files under the new `<N>-<slug>.md` convention.

Migration for in-flight PIR builders spawned before dc177c8:

  # Option A — clean slate (loses any uncommitted plan draft):
  afx cleanup -p pir-<N> -f
  afx spawn <N> --protocol pir

  # Option B — in-place rename (preserves the plan):
  cd <repo>/.builders/pir-<N>
  mv codev/projects/pir-<N>-<slug> codev/projects/<N>-<slug>
  mv codev/plans/pir-<N>-<slug>.md codev/plans/<N>-<slug>.md
  # then edit codev/projects/<N>-<slug>/status.yaml — change `id: pir-<N>` to `id: <N>`
  git add codev/projects codev/plans
  git commit -m "chore(porch): rename PIR artifacts to SPIR-aligned convention"

No grep matches remain for `codev/plans/pir-`, `codev/reviews/pir-`,
or `pir-<id>-<slug>` outside skeleton/protocols (which is the
authoritative copy of these same docs).
The previous View Diff command tried to render a multi-file diff in
the current VSCode window via `vscode.changes` + `git:` URIs. It
silently failed — title showed "(2 files)" but the body said "No
Changed Files" — because VSCode's Git extension doesn't auto-discover
worktrees that live inside the host workspace's gitignore. Tried
fixing URI shape (absolute path in query, ref-based instead of
empty-ref); didn't help because the Git extension wasn't looking at
the worktree's git database in the first place.

The right shape is a custom TextDocumentContentProvider that resolves
content via `git -C <worktree> show <ref>:<path>` ourselves —
bypassing the Git extension entirely. That's a fair chunk of code and
a separate concern; meanwhile reviewers need something that works.

Replace View Diff with **Open Worktree in New Window**:
`vscode.commands.executeCommand('vscode.openFolder', uri, true)` opens
a new editor window rooted at `.builders/<id>/`. In that window the
Git extension treats the worktree as a primary checkout — SCM panel,
working-tree changes, inline diffs against the branch base all work
natively.

`vscode.openFolder` is editor-agnostic — VSCode, Cursor, Windsurf,
and other VSCode-family editors all expose it identically. No CLI
detection needed.

Trade-off: review happens in a separate window instead of inline.
Acceptable for now given that (a) the inline path didn't work, and
(b) the new-window UX is closer to how reviewers actually work
(focused on the worktree, full SCM panel available).

Removed:
- packages/vscode/src/commands/review-diff.ts (~180 LOC)
- `codev.reviewDiff` command declaration and menu entry

Added:
- packages/vscode/src/commands/open-worktree-window.ts (~75 LOC)
- `codev.openWorktreeWindow` declaration + menu entry in slot
  3_review@1 (replaces View Diff's slot)
PIR was running CMAP-2 twice:

  1. Builder followed review.md step 5 ("Run CMAP-2 Review") which
     shelled out to `consult -m gemini` + `consult -m codex` directly
     — copied from the BUGFIX/AIR prompt pattern.
  2. Porch's `verify` block in protocol.json then re-ran the same
     CMAP-2 when `porch done` was called — the SPIR pattern.

The two paths don't coordinate. Builder's manual consults never
updated porch's verify state, so porch saw verify-incomplete and
fired the consults itself. Visible symptom: after merge + cleanup, a
second consult cycle kicks off (caught in the field on pir-1298).

Pick a single pattern: align with SPIR (porch-driven). SPIR's prompts
explicitly forbid manual consults — "Don't run consult commands
yourself (porch handles consultations)" — precisely to prevent this
duplication mode.

Changes to review.md:

- Goal: clarify that porch runs CMAP, not the builder.
- Step 5 (was "Run CMAP-2 Review"): replaced with "Signal Completion
  to Porch" — calls `porch done` which triggers the verify block.
- Step 6 (was "Address Any REQUEST_CHANGES"): replaced with "Handle
  Reviewer Feedback" — uses SPIR's pattern of reading `porch next`
  output for feedback baked into the task description.
- Step 7 ("Append CMAP Outcome to PR Body"): DELETED. CMAP outputs
  live in `codev/projects/<id>-*/<id>-<model>.txt` (porch state),
  not in the PR body — matches SPIR's behavior for its review-phase
  CMAP-pr verdicts.
- Step 8 (notify architect): builder now reads verdicts from porch
  state files (grep) to compose the architect message, rather than
  capturing from its own consult invocations.
- Step 11 (was "porch done after merge"): merged into step 10 (final
  notification only). The merge is a GitHub action, not a porch
  transition — porch already completed the review phase at the
  successful step-5 `porch done` (whichever iteration got all-APPROVE).
- "What NOT to Do" gains: "Don't run consult commands yourself —
  porch handles consultations via the verify block."
- "Handling Problems" updated: model-unavailable failures are now
  porch's problem, not the builder's manual retry loop.

CMAP outputs are NOT lost. Porch writes them to
codev/projects/<id>-<slug>/<id>-{gemini,codex}.txt — same
persistence as SPIR. Visible to the builder, the architect, and
anyone with the worktree. The only thing not auto-surfaced is the
PR body for GitHub reviewers — that gap exists in SPIR's review-phase
CMAP-pr too, and is acceptable for PIR specifically because human
approval already happened at the pre-PR code-review gate.

Mirror in codev-skeleton/protocols/pir/prompts/review.md.
Adds gutter-based review commenting to markdown files in
codev/plans/ and codev/specs/. Hover any line → "+" appears in the
gutter → click → comment input opens inline → submit writes the
comment to the file as <!-- REVIEW(@architect): <text> --> on the
next line.

Same on-disk format as the existing Codev: Add Review Comment palette
command (commands/review.ts) and the review.json snippet: a single
REVIEW(@architect): marker that review-decorations.ts highlights. The
three entry points are independent code paths that converge on the
same persisted form — reviewers can use whichever fits the moment
(palette, gutter "+", or tab-trigger snippet).

Existing inline markers in a file render as collapsed comment threads
on open: refreshDoc() scans the document for the canonical regex
(<!--\s*REVIEW\s*\(@([^)]+)\)\s*:\s*([\s\S]*?)\s*-->), creates a
CommentThread per match. Threads are read-only with a trash icon in
the header — clicking removes the line from the file. The
onDidChangeTextDocument listener keeps threads in sync with the
underlying source as the user edits.

Scope is limited to codev/plans/<N>-<slug>.md and codev/specs/<N>-<slug>.md
via the ELIGIBLE_PATH_REGEX check in isEligibleDocument() — no UI
clutter in other markdown files (READMEs, etc.) or source code.

Author is hardcoded to "architect" to match the existing palette
command exactly — zero divergence between entry points. The
codev.submitReviewComment / codev.deleteReviewComment commands are
hidden from the command palette (when: false) since they only make
sense when invoked from a comment thread's UI.
The toast's single button used to always open the builder's terminal
pane regardless of which gate the builder was blocked on. Two PIR
gates have a single obvious "best artifact" the user wants right away
once the toast fires:

  plan-approval -> "View Plan" runs codev.viewPlanFile (opens
                   codev/plans/<id>-*.md, same as the right-click
                   menu entry).
  dev-approval  -> "Run Dev"   runs codev.runWorktreeDev (starts
                   the worktree's dev PTY, matches the gate's
                   purpose of testing the running code before PR).

Anything else (spec-approval, code-review, pr, future gates) keeps the
existing "Review" -> builder terminal behavior, where the interactive
Claude that just announced the gate can be talked to directly.

Implementation is a small per-gate map; new specializations just add
an entry to GATE_ACTIONS.
…ding toast

Two surfaces for gate approval, each tuned to its trigger:

**Gate-pending toast (bottom-right, passive notification)**

Adds an [Approve] button alongside the existing per-gate inspection
button (View Plan / Run Dev / Review). Clicking [Approve] invokes
`codev.approveGate` with `{ skipConfirmation: true }` — bypasses the
modal because the toast itself is the context. Fast path: see the
toast → review via the inspection button → approve, all without
leaving the bottom-right corner.

**Approve modal (centered, deliberate action)**

Triggered from the sidebar ✓ icon, Cmd+K G, or command palette.
Keeps modal blocking — spatial proximity to where the user clicked
matters more than non-modal politeness for a deliberate, once-per-
gate action. Toast-style notifications in the bottom-right would
force a diagonal cursor traversal from the left sidebar.

Improvements to the modal:

- Display label: `plan review` / `dev review` (from builder.blocked)
  instead of the kebab-case canonical name (`plan-approval`,
  `dev-approval`). Matches what the sidebar already shows.
- Issue context: `#1334 — fix avatar crop` instead of bare `1334`,
  truncated to 60 chars to keep the dialog compact.
- Per-gate side button via GATE_SIDE_ACTIONS:
    plan-approval → [View Plan]    (opens codev/plans/<id>-*.md)
    dev-approval  → [Open Worktree](opens worktree in new window)
    others        → just [Approve]
  Clicking the side button dismisses the dialog and runs the linked
  command; user can re-trigger Approve afterward.

Before:
  Approve plan-approval for 1334?                 [Approve] [Cancel]

After:
  Approve plan review for #1334 — fix avatar crop?
                                  [View Plan] [Approve] [Cancel]

`approve.ts` gains an `ApproveGateOptions` type and a fourth
parameter `options?: { skipConfirmation?: boolean }`. The
extension.ts command registration plumbs the second positional arg
through. The `runPorchApprove` shell-out is extracted so both the
confirmed-and-skip-confirmation paths share the same code.
Two issues stacking on the per-gate toast/modal action mapping:

**1. gate-toast.ts has always missed for per-gate actions.**

GATE_ACTIONS is keyed by canonical gate names ('plan-approval',
'dev-approval', 'pr'). But the lookup passed `b.blocked` — the
human-facing display label ('plan review', 'dev review',
'PR review'). 'dev review' is not in the map → falls through to the
default `{ label: 'Review', command: 'codev.openBuilderById' }`.

Net effect: since dedicated per-gate actions were introduced, every
gate toast has shown `[Review] [Approve]` instead of the intended
`[View Plan] [Approve]` / `[Run Dev] [Approve]` etc.

Fix: pass both `b.blockedGate` (canonical key, for the GATE_ACTIONS
lookup and the seen-set key) and `b.blocked` (display label, for
the user-facing message text). Adds the `gateLabel` parameter to
`showGateToast`; the existing `gateName` parameter is now strictly
the canonical key.

The seen-set's key now uses the canonical gate name too —
incidentally more stable across display-label changes (display
labels could be re-worded; canonical names are part of the protocol
contract).

**2. dev-approval action drifted between toast and modal.**

approve.ts's GATE_SIDE_ACTIONS had 'dev-approval' → 'Open Worktree'
(open the worktree in a new VSCode window); gate-toast.ts's
GATE_ACTIONS had 'dev-approval' → 'Run Dev' (start the dev server).
Two different labels for the same gate is jarring — a reviewer who
learns one might wonder what they're missing on the other surface.

Unified to **Run Dev** in both places. PIR's distinctive feature is
that the human reviews the *running implementation*, so `afx dev` is
the action that matches the gate's intent. New-window view stays
reachable via the right-click "Open Worktree in New Window" item on
the sidebar.

The two GATE_ACTIONS / GATE_SIDE_ACTIONS maps are now byte-equivalent
for their overlapping keys. Updated approve.ts's docstring to note
they must stay in sync.
The previous pr-gate design removed `gh pr merge` from the builder
entirely. Combined with the architect role's "DO NOT merge PRs
yourself" rule, that left the user with a manual GitHub trip — the
pr gate was a wall, not a handoff.

Restored merge to the builder, but with a structurally safer trigger:
porch gate-approved state, not free-text "merge it" prose in the
builder's pane.

Flow (review.md steps 8-9):

  1. Builder waits at pr gate after CMAP-approve notification
  2. Human approves gate via Cmd+K G or `porch approve <id> pr
     --a-human-explicitly-approved-this`
  3. Porch fires `notifyTerminal` wake-up to the builder
  4. Builder runs `porch next` to verify the gate is genuinely
     approved (defensive — prose can mimic the wake-up text, but
     can't fake porch state)
  5. Builder runs `gh pr merge --merge`
  6. Builder runs `porch done --merged <N>` to record
  7. Sends the cleanup-ready notification, exits

The self-merge bug from pir-1298 is structurally eliminated: the
trigger is porch's binary gate-approved state, which only a
non-Claude caller (user via VSCode execFile or shell) can set. The
builder cannot self-trigger the merge by interpreting typed prose.

"What NOT to Do" updated: merge before gate approval is the new
explicit prohibition. CMAP-APPROVE, "looks good", "merge it" — none
authorize the merge. Only `gate_status: approved` in porch state
does.

builder-prompt.md updated: merge is described as gated-by-porch,
not forbidden-entirely. protocol.md walkthrough + Gates section
reflect the new semantics.

Mirror in codev-skeleton.
Brings the @openai/codex-sdk 0.130 cert-revocation fix (XProtect) and
syncs the PIR branch up to main.
Backlog sidebar improvements so the architect can find and start their
own work without leaving VSCode:

- Issues assigned to the current GitHub user sort to the top of the
  Backlog view with an `account` icon + "assigned to you" description;
  the rest keep the `issues` icon. Single view, no separator rows —
  mirrors how BuildersProvider sorts blocked-first above active.
- Row click now starts work: invokes codev.spawnBuilder with the issue
  number pre-filled, jumping straight to the protocol quick-pick
  (branch prompt skipped on this path). The palette flow is unchanged.
- Right-click context menu (consistent with the Builders view's
  group/when/title conventions): Spawn Builder, Open Issue in Browser,
  Copy Issue Number.

Current-user detection is resolved Tower-side and rides the existing
/api/overview payload (new optional OverviewData.currentUser), so the
VSCode client needs no new fetch path and it stays tunnel-safe.
Resolution goes through a new typed fetchCurrentUser() wrapper in
lib/github.ts (user-identity concept, raw output), keeping overview.ts
consistent with how it fetches PRs/issues/closed/merged — same parallel
Promise.all batch, same per-cwd cache pattern (1h TTL since identity is
session-stable).

currentUser added to both OverviewData interfaces — the server-local
one in overview.ts (what Tower serializes) and the shared one in
@cluesmith/codev-types (what the VSCode client deserializes).
… right pane

Read a backlog issue's body + comments inside VSCode instead of leaving
for a browser. Backlog row click now opens the issue (was: spawn);
spawn moves to a deliberate right-click choice.

- Tower: new forge-backed GET /api/issue → fetchIssue (issue-view
  concept). Stays forge-agnostic and tunnel-safe — the extension never
  shells out to gh; resolution happens Tower-side like every other
  data fetch.
- core: TowerClient.getIssue(number, workspace) mirroring getOverview.
- types: shared IssueView interface (title/body/state/comments),
  mirrors server-side IssueViewResult; exported from the package index.
- vscode: new view-issue.ts — a read-only `codev-issue:` content
  provider renders the issue as markdown; opens via
  markdown.showPreviewToSide so it lands in the right editor column,
  the same placement model builder terminals use (ViewColumn.Two).
- Backlog context menu reordered so the click-action is first
  (Builders-consistency rule): View Issue @1, Spawn Builder @2,
  Open Issue in Browser @3, Copy Issue Number @4. The three
  context-only commands are hidden from the command palette.

Click = read the issue; right-click = spawn / open in browser / copy.
Making spawn an explicit choice also avoids accidental-click spawns
that the architect AI wouldn't be aware of.
… titles

Switch the three active-work list views from registerTreeDataProvider to
createTreeView so their titles can carry a live count — "Builders (3)",
"Pull Requests (2)", "Backlog (17)". Count is recomputed from the
overview cache in the existing combined onDidChange handler (alongside
status-bar + terminal-prune — one subscription, named functions), so it
updates as builders spawn/finish, PRs open/merge, issues enter/leave.

No-data state (disconnected/loading) falls back to the plain base name —
no misleading "(0)"; a genuinely empty list still shows "(0)" once data
has loaded. Recently Closed, Team, Workspace, and Status are unchanged
(still registerTreeDataProvider). Context menus/commands are unaffected
since they key off the view id, not the registration method; the three
TreeView handles are disposed via context.subscriptions.
VSCode had no timer-based overview refresh — it only refetched on SSE
events, connect, or the manual button. On an idle workspace (no porch
activity) an externally-merged PR or new issue stayed invisible
indefinitely. The web dashboard already polls (useOverview), so this
brings VSCode to parity.

New setting codev.overviewRefreshSeconds (default 60, 0 = disabled).
A single named controller in extension.ts refreshes on that cadence
only while a Codev list view is visible (gated on the existing
createTreeView .visible/onDidChangeVisibility handles), pauses when
the sidebar is hidden, and does an immediate refresh on becoming
visible again. Live-reconfigurable via onDidChangeConfiguration; ticks
self-skip while disconnected so a transient blip doesn't empty the
views. No Tower change — the shared server-side 30s cache throttles gh
cost across all windows, and OverviewCache.refresh() is already
last-write-wins so periodic + event-driven refreshes don't flicker.
Recently Closed had no title-bar refresh action (only Builders/Pull
Requests/Backlog did — an omission, not intentional) and no item
count. Add the codev.refreshOverview view/title entry for
codev.recentlyClosed, switch it from registerTreeDataProvider to
createTreeView so its title carries a live "Recently Closed (N)"
count via the existing updateListViewTitles, and include its
visibility handle in the periodic-refresh controller's anyVisible()
+ onDidChangeVisibility wiring so the timer treats it like the other
list views. Brings it to full parity with the other active-work views.
…re-date query)

fetchRecentlyClosed / fetchRecentMergedPRs truncated the 24h-ago
timestamp to a bare date (.split('T')[0]) and the GitHub concept queries
`closed:>$CODEV_SINCE_DATE` / `merged:>$CODEV_SINCE_DATE`. GitHub's `>`
against a bare YYYY-MM-DD excludes the *entire* sinceDate day, so the
effective window collapsed to "since the most recent UTC midnight"
(0–24h wide, ~0 just after 00:00Z). Result: issues/PRs closed earlier
the same UTC day were silently missing from Recently Closed even though
well within 24h (reproduced: an issue closed 3.5h ago returned []).

Send a full ISO-8601 timestamp (seconds precision, retains the Z UTC
designator) instead of a bare date. GitHub search supports
second-precision datetime qualifiers; `>` against a precise timestamp
is exact (verified end-to-end: now returns exactly the items closed
within 24h). Milliseconds are stripped to match GitHub's documented
qualifier format rather than rely on undocumented fractional-second
leniency — important because a rejected qualifier fails silently
(null → empty view), the very symptom being fixed. The precise
client-side 24h filter is unchanged and remains authoritative.

Verified GitHub-only: gitlab/gitea scripts don't pass the date arg
(JS-filter only); linear uses GraphQL `gte` (inclusive, never had the
day-exclusion bug) — the shared-caller change is a no-op for gitlab/
gitea and a precision improvement for linear, no regression.
…loads

Two fixes to the gate-pending toast:

1. The issue title is now wrapped in quotes. A title that reads like an
   error (e.g. "agent/reset returns 504 GATEWAY_TIMEOUT…") was appended
   bare after an em-dash and looked like the toast was reporting a
   failure rather than identifying the blocked issue.

2. The (builderId::gate) seen-set is persisted to workspaceState instead
   of being in-memory closure state. It previously reset on every window
   reload / extension reactivation / Tower reconnect, so a builder still
   blocked on the same gate re-toasted once per reactivation. Hydrated on
   activation, re-saved only when the set changes; prune semantics
   unchanged so a genuine re-block still toasts.
The review prompt promised an iterate-until-APPROVE CMAP loop that
protocol.json (max_iterations: 1) structurally cannot deliver — a builder
hit this: codex returned a substantive REQUEST_CHANGES, the builder fixed
+ rebutted it, and porch advanced to the pr gate at iteration 1 with no
independent re-review and no escalation.

- review.md: rewrote Goal/step 5.3/step 6/step 7/step 8 — CMAP is one
  advisory pass; a REQUEST_CHANGES is fixed-or-rebutted (+ regression
  test) and *escalated* to the human at the pr gate via a conditional
  notification, since PIR will not re-review it.
- protocol.md + builder-prompt.md: state single-pass / no loop / escalate.
- review.md step 9: detect a PR already merged on GitHub by the human
  (don't blind re-merge — record only).
- implement.md + review.md: scope discipline — porch checks are narrow
  structural gates, not a full-suite proof; pre-existing unrelated
  failures are out of scope, not to be quarantined.
- consult-types/pr-review.md: "CMAP-3" -> "CMAP-2" (was also orphaned).
- protocol.md + CLAUDE.md + AGENTS.md: document the CMAP-2 invariant —
  porch model precedence is config > protocol, so a SPIR-tuned global
  porch.consultation.models silently inflates PIR.

codev-skeleton mirror kept byte-identical; CLAUDE.md == AGENTS.md.
Adds a context-aware "Start/Stop Dev Server" to the sidebar Workspace
view and an `afx dev main` CLI target. The dev target is resolved from
the folder the VSCode window is rooted at — the main checkout -> `main`,
a .builders/<id>/ worktree -> that builder — so the existing single-slot
swap machinery now covers main<->worktree, closing the silent EADDRINUSE
/ wrong-target-review footgun when main dev is launched through Codev.
A manually-run `pnpm dev` stays unmanaged by deliberate policy.

- dev.ts: reserved `main` target (local synthetic; builder-lookup
  deliberately untouched so `main` never leaks into send/cleanup/status)
- dev-shared.ts: resolveWorkspaceDevTarget + shared startDevForTarget/
  stopDevForTarget core (one implementation, target-resolution differs)
- run-workspace-dev.ts: context-aware front-end (path-sniffs the open
  folder against the fixed .builders/<id>/ layout)
- run-worktree-dev.ts: refactored to delegate to the shared core
  (behavior unchanged)
- terminal-manager.ts: onDidChangeDevTerminals event so the conditional
  Stop row stays correct across start/stop/swap/cleanup
- workspace.ts: generic "Start/Stop Dev Server" rows, target-aware
  tooltip, Stop shown only while this workspace's dev runs
- extension.ts/package.json: command + menu wiring
- CLAUDE.md/AGENTS.md: docs (synced)
A dev server is a long-running background log, not an interactive
surface you tab between. Route type==='dev' to
vscode.TerminalLocation.Panel always, independent of the
codev.terminalPosition setting. Architect (editor group 1),
builder/shell (group 2), and the global setting are unchanged.
Flat if/else — no nested ternaries.
…rename

- Assigned / Open-PR rows: single click opens the item on GitHub
  (vscode.open the issue/PR URL; no command set when URL absent).
- Team title-bar Refresh button (codev.refreshTeam → provider.refresh):
  Team otherwise only refetches as a side effect of `overview-changed`
  SSE events, so on a quiet workspace it could sit stale/empty.
- "Working on:" → "Assigned:" — the label now matches the data (open
  issues where the member is the GitHub assignee, not an activity
  signal).
- No context menus / right-click — single click = action.
Per-member issue/PR lists duplicated the Backlog / Pull Requests views.
Replace both with single "Assigned: N" / "Open PRs: N" summary rows,
mirroring the "Last 7d:" stat row (informational, no command, shown
only when > 0). Supersedes the per-item click-through from 8e2bb60;
Team Refresh button retained.
Adds a true open/close toggle for the Codev view container using two
complementary when-clause keybindings (sideBarVisible + activeViewlet),
so the same key reveals Codev when hidden/inactive and closes the
sidebar when Codev is the active container. No extension code needed.

Best-per-platform keys: single-stroke Cmd+Alt+C on macOS; the safe
Ctrl+K C chord on Windows/Linux to avoid the Ctrl+Alt=AltGr clash on
international keyboard layouts.
Show exactly one row — Start when this workspace's dev is stopped,
Stop when it's running (play/stop model; the visible control is the
state indicator). Removes the always-Start + additive-Stop behavior
that was mock-driven inertia. Flips live via onDidChangeDevTerminals.
Unifies the Codev action shortcut family onto one cross-platform model
(Cmd+Alt+<L> on macOS, Ctrl+Alt+<L> on Windows/Linux):

  C -> toggle Codev sidebar  (was asymmetric: Ctrl+K C chord on Win/Linux)
  R -> run workspace dev server   (codev.runWorkspaceDev, new)
  S -> stop workspace dev server  (codev.stopWorkspaceDev, new)

Sidebar Win/Linux key moved Ctrl+K C -> Ctrl+Alt+C so all three follow
the same pattern. Verified clear of the macOS system Cmd+Alt set,
VSCode core, and all installed extensions; the only overlap is a
narrow when-scoped Quarto binding (cmd/ctrl+alt+r = quarto.runAllCells,
active only inside Quarto editor contexts) - accepted edge case. C/R/S
are not common AltGr characters, keeping the symmetric Ctrl+Alt form
low-risk on international keyboards. Sidebar toggle keeps its two
when-clause entries (open/close); only the key field changed.
Builder terminal tabs mirror their sidebar row — `Codev: #<issueId>
<issueTitle>` — instead of the internal `builder-<protocol>-<id>`
agent name.

- TerminalManager.friendlyBuilderLabel resolves the builder in
  OverviewCache (injected via the constructor — proper DI, no setter).
- Matches on the trailing id token so the canonical roleId open paths
  pass lines up with OverviewCache's short ids (resolveAgentName expects
  a short target; passing the full roleId never matched).
- Issue title capped at 25 chars on a whole-word boundary (hard-cut
  fallback for a single over-long word); `#<id>` kept whole — VSCode's
  default `tabSizing: 'fit'` doesn't ellipsize a lone wide tab.
- Falls back to the agent name when overview data / a match is
  unavailable. Display-only: identity, cleanup, and click-to-focus key
  off the `builder-<id>` map key and Terminal object, never the title.
Clicking a backlog issue called markdown.showPreviewToSide, which opens
in ViewColumn.Beside — relative to the *active* editor group. After the
first preview opened (and became the active editor), each further click
resolved Beside to a new group (3, 4, …) instead of reusing one, unlike
builder terminals which target the absolute ViewColumn.Two.

Anchor focus to editor group 1 before showing the preview so Beside
resolves to group 2 every time, giving issue previews the same
single-group reuse as builder terminals. Keeps the proven virtual-doc
preview path (no custom-editor swap).
The Team view's per-member counts (Assigned / Open PRs / 7-day merged
& closed) were nodes.length of search(..., first: 20) results, so they
silently capped at 20. Add `issueCount` to all four per-member searches
in buildTeamGraphQLQuery and expose true totals as new *Count fields on
TeamMemberGitHubData (canonical @cluesmith/codev-types + the codev
mirror); the parser falls back to node length if the field is absent.
The Team view renders the *Count fields. Node arrays are unchanged
(still 20-capped, still feed review-blocking / lists) — purely additive.
CHANGELOG: add an [Unreleased] section covering the user-facing
extension changes since 3.0.4 (managed dev server, Team snapshot +
true issueCount-based counts, friendly builder tab titles w/ 25-char
word-boundary truncation, symmetric Cmd/Ctrl+Alt shortcuts, gate
toasts, PIR support, Open Worktree window, backlog actions, …) plus
the backlog-preview single-group fix. Released-version history left
intact; superseded items (View Diff -> Open Worktree, Needs Attention
-> Builders) corrected forward.

README: fix stale Needs-Attention / editor-area claims; refresh the
Features, Commands (+ shortcuts), and Settings tables.
Brings main current (was 63 ahead / 82 behind) ahead of the PR.

Conflict resolution:
- spawn.ts: combined — kept the branch's generic `type: prefix`
  (PIR shared issue-driven spawn) plus main's new
  `spawnedByArchitect: SPAWNING_ARCHITECT_NAME` field.
- db/index.ts: took main's version. The branch's "add 'pir' to
  builders.type CHECK" migration collided with main's v9 (Spec 755
  multi-architect) and is intentionally dropped here, to be
  reintroduced as a deliberate, separately-reviewed v14 migration.
  Fresh installs unaffected (schema.ts already has 'pir' +
  spawned_by_architect); only the existing-DB pir upgrade path is
  deferred to that follow-up.
The branch's original "add 'pir' to builders.type CHECK" was v9 in
ensureLocalDatabase and collided with main's v9 (Spec 755 multi-
architect); the merge took main's v9 and dropped ours. Reintroduce it
as v10 in the same local (builders/architect) runner.

Drift-robust: derives builders_new from the LIVE builders DDL (rewrites
only the table name + injects 'pir' into the type CHECK) so SELECT *
cannot column-mismatch regardless of columns earlier/Spec-755
migrations added (e.g. spawned_by_architect). Idempotent: skips the
recreate when 'pir' is already present (fresh installs; DBs that ran
the old branch v9). Verified: build + 2964 unit tests; synthetic
post-v9/pre-pir upgrade test (drift columns/rows/indexes/trigger
preserved, pir accepted, bogus rejected, idempotent).
@amrmelsayed amrmelsayed requested a review from waleedkadous May 18, 2026 22:54
@amrmelsayed amrmelsayed changed the title feat(#691): PIR protocol + VSCode PIR/Codev workflow integration feat(#691): PIR protocol + VSCode Codev workflow integration May 18, 2026
Copy link
Copy Markdown
Contributor

@waleedkadous waleedkadous left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

@waleedkadous waleedkadous merged commit 0aea92c into main May 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants