feat(#691): PIR protocol + VSCode Codev workflow integration#763
Merged
Conversation
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/mainwas 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 currentmain.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 originalcode-reviewfor naming consistency.)pr— post-PR merge-synchronization gate, matching SPIR's pr-gate pattern. The builder does not auto-merge; it runsgh pr mergeonly 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 aREQUEST_CHANGESis escalated to the human at theprgate 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) socodev/reviews/stays semantically consistent across protocols.Protocol lives in
codev/protocols/pir/and is mirrored intocodev-skeleton/protocols/pir/so other projects get it via the package skeleton.CLAUDE.md/AGENTS.mddocument 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
spawnPiradded;pirandbugfixdispatched separately in the spawn path;'pir'added tobuildAgentName.pir-N→N);detectProjectIdFromCwdand the overview/branch recognizers updated to match PIR worktrees and branches; stalepir-<id>-<slug>filename references scrubbed from docs.porch status --jsonflag for structured gate state.notifyTerminalprimitive replaces architect-bound notifications;porch approveskips the builder wake-up when the caller is the builder; CLI broadcastsoverview-changedafter mutating commands.prefix-Nproject 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 frompackages/vscode/CHANGELOG.md(the authoritative list):What's new
worktree.devCommandfor 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.type: 'dev'terminals now always use the panel regardless ofcodev.terminalPosition. Architect (left group) and builder/shell (right) are unchanged.Codev: #<issueId> <issueTitle>(matching its sidebar row) instead of the internalbuilder-<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.Assigned: NandOpen PRs: Ncount summaries plusLast 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).Cmd+Alt+C/Ctrl+Alt+Ctoggles the Codev sidebar (opens & focuses it if hidden, closes it if it's the active view);Cmd/Ctrl+Alt+RandCmd/Ctrl+Alt+Sstart / stop the current workspace's dev server. The existingCmd/Ctrl+KA / D / G (open architect, send message, approve gate) are unchanged.codev.gateToasts.enabledsetting.REVIEW(@architect):threads in the editor, not only the text snippet..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.codev.overviewRefreshSeconds(default 60;0= event-only, the previous behavior). A shared 30s Tower-side cache throttles GitHub calls across windows.Bug fixes
porch approvein the right working directory — previously it could mis-resolve for renamed/shared gates or non-root worktrees.issueCountrather than the length of the (20-node-limited) result list.4. Database migration (v10) — operational note
The original branch added
'pir'to thebuilders.typeCHECK constraint as local-DB migration v9.mainindependently 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 livesqlite_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
_migrationsrecovery 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).Testing & verification
pnpm buildclean (core → codev → dashboard); VSCodecheck-types+lintclean.test(porch)locks in the PIR protocol shape; synthetic v10 migration test PASS._migrations1..10,'pir'present,spawned_by_architectpresent, 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 topackages/core,packages/dashboard,packages/types, and the syncedCLAUDE.md/AGENTS.md.