Skip to content

feat(review): /ca:review can review an inbound pull request#503

Merged
SUaDtL merged 1 commit into
mainfrom
feat/80-review-inbound-pr
Jul 26, 2026
Merged

feat(review): /ca:review can review an inbound pull request#503
SUaDtL merged 1 commit into
mainfrom
feat/80-review-inbound-pr

Conversation

@SUaDtL

@SUaDtL SUaDtL commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

What

/ca:review #123 reviews an inbound GitHub pull request through the existing fleet. Closes #80.

Why

The reviewer fleet only ever ran on the diff you just wrote. /ca:watch watches a PR's CI but never runs the fleet against its diff. For a tool that markets itself as a team gate, the half of the value that comes from reviewing code you did not write was missing.

Same fleet, same path matrix, same finding-triagecheckpoint-aggregator funnel. As the issue puts it: "plumbing a new input into an existing pipeline, not net-new logic."

An argument, not a new command

This follows the principle you set when ruling on #382: the target becomes a parameter, not a new surface.

The scope resolver already took an argument, the fleet is scope-agnostic, and every phase downstream operates on a diff regardless of where it came from. A /ca:review-pr would be a whole public surface — the catalog, three host projections, the README counts, the site sidebar — whose only distinguishing feature is where the diff was fetched from.

Command count unchanged at 40, confirmed by check_badge_consistency.py.

Three guards

Each closes a way this could go wrong quietly:

  1. A missing or unauthenticated gh STOPs rather than falling back to the working diff — which would report a verdict on the wrong change under the PR's name.
  2. The diff is resolved once. The fleet runs in parallel; a PR updated mid-review would otherwise have different reviewers reading different code, and a triage reconciling findings across two versions.
  3. Posting is a separate, confirmed step, and never uses --approve or --request-changes. A review comment on someone else's PR notifies subscribers and cannot be un-sent; those two flags carry merge authority this command does not have. It produces a finding list, not a maintainer's decision.

The hard gate also refuses to check out the PR branch — reviewing an inbound change means reading its diff, not adopting its code into a working tree the hooks trust.

Verification

test_ci_impact, test_badge_consistency, test_public_pi_docs, test_public_codex_docs, test_pi_parity, test_codex_parity_fixture, test_build_surface, test_routing_and_cleanup_surface — all OK. Site-voice gate clean. Surface rebuilt to all three hosts.

Versions

ca-pi 0.1.31 → 0.1.32; ca (2.9.1) and ca-codex (0.3.0) carry no tag yet, so their entries join the open sections. Pi release guard confirms manifest, changelog, and generated root metadata advanced together.

Closes #80

https://claude.ai/code/session_01Y8UPz5RZwgnda3NbAVfd6L

The reviewer fleet only ever ran on the diff you just wrote. `/ca:watch`
watches a PR's CI but never runs the fleet against its diff, so for a tool that
markets itself as a team gate, the half of the value that comes from reviewing
code you did NOT write was missing (#80).

`/ca:review #123` now fetches that PR's diff and runs the same fleet, the same
path matrix, and the same finding-triage -> checkpoint-aggregator funnel.

Deliberately an ARGUMENT, not a /ca:review-pr command. The scope resolver
already took one, the fleet is scope-agnostic, and every phase downstream
operates on a diff regardless of where it came from - so a second command would
be a whole public surface (the catalog, three host projections, the README
counts, the site sidebar) whose only distinguishing feature is where the diff
was fetched from. The command count is unchanged at 40, which check_badge_
consistency confirms.

Three guards ride with it, each closing a way this could go wrong quietly:

  - a missing or unauthenticated `gh` STOPs rather than falling back to the
    working diff, which would report a verdict on the wrong change under the
    PR's name;
  - the diff is resolved ONCE, because the fleet runs in parallel and a PR
    updated mid-review would otherwise have different reviewers reading
    different code and a triage reconciling two versions;
  - posting is a separate, confirmed step that never uses --approve or
    --request-changes. A review comment on someone else's PR notifies
    subscribers and cannot be un-sent, and those two flags carry merge
    authority this command does not have.

The hard gate also refuses to CHECK OUT the PR branch: reviewing an inbound
change means reading its diff, not adopting its code into a working tree the
hooks trust.

ca-pi advances 0.1.31 -> 0.1.32; ca (2.9.1) and ca-codex (0.3.0) carry no tag
yet, so their entries join the open sections.

Closes #80

Claude-Session: https://claude.ai/code/session_01Y8UPz5RZwgnda3NbAVfd6L
@SUaDtL
SUaDtL merged commit f8f33e2 into main Jul 26, 2026
75 of 77 checks passed
@SUaDtL
SUaDtL deleted the feat/80-review-inbound-pr branch July 26, 2026 16:05
SUaDtL added a commit that referenced this pull request Jul 26, 2026
The CI failure this addresses arrived as `expected undefined to be defined`
with no cause, and the test's own diagnostic - "no job handle, the contained
launch never returned" - actively misdescribed it. The launch DID return. It
returned a refusal.

Diagnosis first, because the obvious reading was wrong. `launch` is declared
`Promise<Snapshot | undefined>` and `undefined` is a DESIGNED outcome: all five
refusal paths transition the job to a terminal state before returning, so the
runtime is fail-closed and never owns an untracked process. The registration
race I feared when filing #504 does not exist, and that AC is withdrawn.

What is wrong is that path 5 - the `#openTree` spawn - discards its error in a
bare `catch {}`, so a real environment failure is indistinguishable from a
policy refusal.

Fixing that properly means extending the published activity event
(`{kind, id, label, state}` has no detail field) or changing one path's refusal
semantics, both of which deserve a decision rather than being slipped in behind
a flake fix. So this takes the part that needs NO contract change: the live test
wraps the tree opener, captures the throw, and reports it.

Verified by mutation rather than by inspection - pointing shellPath at a
nonexistent binary now fails with

  launch refused because the spawn threw: ENOENT: no such file or directory,
  lstat '...no-such-shell.exe'

instead of the bare assertion. The diagnostic also distinguishes the two cases:
a spawn that threw, versus a refusal that never attempted one.

This does not fix the flake. It makes the next occurrence explain itself, which
is what was missing when it cost a re-run cycle on #503.

Refs #504

Claude-Session: https://claude.ai/code/session_01Y8UPz5RZwgnda3NbAVfd6L

Co-authored-by: SUaDtL <SUaDtL@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Inbound PR review (/ca:review-pr <number>)

1 participant