pi-pr-review 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Pass a PR number and pi will:
8
8
  2. Derive the **base branch** and **head (merging) branch** automatically from the PR.
9
9
  3. Review the base↔head diff with disciplined passes (overview, convention compliance, bugs, security/perf, readability), best-effort build/test verification, then validate each candidate.
10
10
  4. Return a **full structured review**: overview, strengths, verification, findings at **every** severity (`nit → P3 → P2 → P1 → P0`) with a blocking flag, correctness/security/performance notes, and a verdict.
11
- 5. Optionally post the review + inline PR comments (`--comment`).
11
+ 5. Optionally publish one formal GitHub `COMMENT` review with a top-level body and associated inline comments.
12
12
 
13
13
  **Captures everything, then ranks it.** Unlike a high-signal-only reviewer, it does not discard minor issues — nits, style, naming, missing edge cases, and "worth confirming" observations are all reported as low-severity findings. The **verdict** depends only on *blocking* (P0/P1) findings, so a clean PR is still approved while its nits are still recorded.
14
14
 
@@ -65,15 +65,19 @@ The `/pr-review-config` command maps three labels to models:
65
65
  /pr-review-config show # print the current mapping
66
66
  /pr-review-config light=<spec> medium=<spec> heavy=<spec> # set primary tier models
67
67
  /pr-review-config heavy_fallbacks=<spec>,<spec> # retry chain for quota/rate-limit failures
68
+ /pr-review-config light_tool_policy=none # tier default when a pass omits tool_policy
69
+ /pr-review-config auto_post_reviews=true # opt in to automatic GitHub COMMENT reviews
70
+ /pr-review-config auto_post_reviews=false # disable automatic posting (the default)
68
71
  /pr-review-config medium=unset # clear a tier (back to pi default)
69
72
  /pr-review-config heavy_fallbacks=unset # clear a fallback chain
70
- /pr-review-config tools=read,bash,grep,find,ls # tools granted to each subagent
73
+ /pr-review-config light_tool_policy=unset # restore legacy configured-tool behavior
74
+ /pr-review-config tools=read,bash,grep,find,ls # allowlist used by configured policy
71
75
  ```
72
76
 
73
77
  Running `/pr-review-config` with no arguments in the TUI opens an interactive settings menu that mirrors pi's `/settings` and the NERVous `/nervous:config`:
74
78
 
75
- - One primary-model row and one fallback-model row per tier (`light` / `medium` / `heavy`) plus a `subagent tools` row.
76
- - Press Enter on a primary or fallback row to pick a model from a searchable list (or unset it); Enter/Space on `subagent tools` cycles presets. The menu sets one fallback model at a time; use the `key=value` form for longer fallback chains.
79
+ - One primary-model, fallback-model, and tool-policy row per tier (`light` / `medium` / `heavy`), an automatic-posting toggle, plus a configured-tool allowlist row.
80
+ - Press Enter on a primary or fallback row to pick a model from a searchable list (or unset it); Enter/Space cycles tool policies and allowlist presets. The menu sets one fallback model at a time; use the `key=value` form for longer fallback chains.
77
81
  - Selections apply and persist **immediately**; Esc closes the menu.
78
82
  - Type to search, and tab-completion is available for the `key=value` form.
79
83
 
@@ -98,12 +102,20 @@ Example `pr-review.json`:
98
102
  "medium": ["<backup-balanced-model>"],
99
103
  "heavy": ["<backup-strong-model:high>", "<balanced-model-spec>"]
100
104
  },
105
+ "toolPolicies": {
106
+ "light": "none",
107
+ "medium": "configured",
108
+ "heavy": "configured"
109
+ },
110
+ "autoPostReviews": false,
101
111
  "tools": ["read", "bash", "grep", "find", "ls"]
102
112
  }
103
113
  ```
104
114
 
105
115
  Each tier runs in an **isolated `pi` subprocess** on its configured model. The `review_subagents` batch tool runs independent passes concurrently (default `max_parallel: 4`, capped at 6) and returns ordered per-pass results; the older single-pass `review_subagent` tool remains available as a compatibility fallback. If a tier model fails with a retryable quota/rate-limit/capacity error, the subprocess retries that tier's configured `fallbacks` in order. Non-quota failures do not blindly cycle through fallbacks. If a tier is unset, that subagent falls back to the nearest configured tier, then to your pi default model.
106
116
 
117
+ Tool policy is additive and backward compatible: `none` emits Pi's explicit `--no-tools`; `configured` uses the existing `tools` allowlist. A tool call's optional `tool_policy` overrides `toolPolicies[tier]`, which in turn falls back to legacy `configured` behavior. The shipped `/pr-review` prompt explicitly uses `none` only for overview because its complete evidence is supplied in context. Conventions/maintainability and both heavy specialist passes use `configured` repository-context tools so they can inspect surrounding files when needed. Fallback model attempts keep the original pass policy.
118
+
107
119
  ### 2. The orchestrator / inline-fallback model
108
120
 
109
121
  The orchestrator (which fetches the PR, merges findings, and emits the JSON) and the inline fallback path both run on your pi session model:
@@ -118,21 +130,28 @@ The orchestrator (which fetches the PR, merges findings, and emits the JSON) and
118
130
  Type `/` in the pi editor and pick `pr-review`, or:
119
131
 
120
132
  ```
121
- /pr-review 123 # analysis only prints the JSON report, no GitHub writes
122
- /pr-review 123 --comment # also posts inline review comments to the PR
133
+ /pr-review 123 # use autoPostReviews (false by default)
134
+ /pr-review 123 --comment # force one COMMENT review for this run
135
+ /pr-review 123 --no-comment # suppress posting for this run
123
136
  /pr-review 123 --include-closed # review a closed/merged PR without a confirmation prompt
124
- /pr-review 123 --review-closed --comment # review/comment on a closed/merged PR when GitHub accepts it
137
+ /pr-review 123 --review-closed --comment # review and attempt a body-only COMMENT review
125
138
  ```
126
139
 
127
140
  `123` is the PR number in the current repo.
128
141
 
129
142
  ### Closed or merged PRs
130
143
 
131
- Closed/merged PRs no longer hard-skip. If you run `/pr-review 123` on a non-open PR, the prompt asks whether to continue before producing a review. Use `--include-closed` or `--review-closed` to proceed non-interactively. If `--comment` is also used and GitHub rejects inline comments on the non-open PR, findings should be folded into the summary comment instead.
144
+ Closed/merged PRs no longer hard-skip. If you run `/pr-review 123` on a non-open PR, the prompt asks whether to continue before producing a review. Use `--include-closed` or `--review-closed` to proceed non-interactively. When publication is enabled, the extension preemptively folds inline findings into one body-only formal `COMMENT` review; if GitHub rejects it, publication fails explicitly without creating an issue comment.
145
+
146
+ ### Automatic GitHub review posting
147
+
148
+ `autoPostReviews` is a strict top-level boolean and defaults to `false`. A trusted project `.pi/pr-review.json` value overlays the user value; malformed effective values never enable posting. `/pr-review-config` edits user scope only and displays the effective value/source—if a trusted project overlay wins, edit that project file or use `--no-comment` for the run. Invocation flags are captured before prompt expansion: `--comment` forces posting for one run, `--no-comment` suppresses it, and using both is rejected.
149
+
150
+ Publishing is extension-owned—the model never constructs the write request, and final JSON marked `disposition: "skipped"` is never published. It creates exactly one formal pull-request review containing a top-level body and associated P0–P3 inline comments. The GitHub event is hardcoded to `COMMENT`; the publisher cannot send `APPROVE` or `REQUEST_CHANGES`, even when the review's suggested verdict is `request_changes`. It validates the current head and every inline diff anchor before the single POST, so an invalid open-PR inline comment fails without leaving a partial review. Closed/merged PRs require either `--include-closed`/`--review-closed` or the one-shot affirmative confirmation, then use one body-only `COMMENT` review with inline findings folded into the body. Unknown or unconfirmed non-open lifecycle states fail without posting.
132
151
 
133
152
  ### Duplicate review handling
134
153
 
135
- `--comment` summary comments include a hidden `pi-pr-review` marker with the reviewed `headRefOid`. A later run skips only when it finds a marker for the **current** head SHA. If new commits were pushed after the previous review, the head SHA changes and `/pr-review` reviews the PR again. Older unmarked comments are treated as unknown/stale, not proof that the current head was already reviewed.
154
+ Published reviews include a hidden `pi-pr-review` marker with the reviewed `headRefOid`. A later run skips only when it finds a same-head marker authored by the current authenticated GitHub identity in either formal reviews or legacy issue comments. If new commits were pushed, the head SHA changes and `/pr-review` reviews the PR again. Older or unmarked content is not proof that the current head was reviewed.
136
155
 
137
156
  ### Response format
138
157
 
@@ -144,7 +163,8 @@ Example payload:
144
163
 
145
164
  ```json
146
165
  {
147
- "pr": { "number": 33, "title": "fix(logs): parse date-time log timestamps" },
166
+ "pr": { "number": 33, "title": "fix(logs): parse date-time log timestamps", "head_sha": "0123456789abcdef0123456789abcdef01234567" },
167
+ "disposition": "reviewed",
148
168
  "verification": "`go build ./...` ✅, `go test ./...` ✅ (130 passed)",
149
169
  "overview": "Migrates log timestamps from epoch-ms to RFC3339 to match the endpoint contract.",
150
170
  "strengths": ["Reuses FormatTimestamp instead of ad-hoc formatting; net -3 lines."],
@@ -173,7 +193,7 @@ Example payload:
173
193
 
174
194
  Severity tags: `[P0]` blocking/drop-everything · `[P1]` blocking/urgent · `[P2]` normal · `[P3]` low · `[nit]` trivial/optional. Verdict is `approve` (no blocking findings), `request_changes` (a blocking finding exists), or `comment`.
175
195
 
176
- **Inline-comment ready.** Each finding's `code_location` is diff-anchored — repo-relative `absolute_file_path`, `line_range` on `side` (`RIGHT` for added/context lines, `LEFT` for removed), and `commentable` (whether the lines are inside a diff hunk). The rendered table shows an **Inline** ✎ column for findings that can be posted as GitHub inline review comments. With `--comment`, a summary review is posted plus inline comments (single- or multi-line, using each finding's anchor) for every commentable blocking/P2/P3 finding; nits and off-diff/repo-wide observations fold into the summary.
196
+ **Inline-comment ready.** Each finding's `code_location` is diff-anchored — repo-relative `absolute_file_path`, `line_range` on `side` (`RIGHT` for added/context lines, `LEFT` for removed), and `commentable` (whether the lines are inside a diff hunk). The rendered table shows an **Inline** ✎ column. When publishing is enabled, the extension validates these anchors against current GitHub diff metadata and attaches eligible P0–P3 findings under the single formal review; nits and off-diff observations stay in the top-level body.
177
197
 
178
198
  ## What's in the package
179
199
 
@@ -181,19 +201,26 @@ Severity tags: `[P0]` blocking/drop-everything · `[P1]` blocking/urgent · `[P2
181
201
  pi-pr-review/
182
202
  ├─ package.json # pi manifest: prompts + extensions
183
203
  ├─ prompts/pr-review.md # the /pr-review orchestrator prompt
204
+ ├─ lib/pr-review-policy.ts # pure tool-policy resolution/argv helpers
205
+ ├─ lib/pr-review-publish.ts # safe COMMENT-review payload, validation, and gh publisher
184
206
  ├─ extensions/pr-review-subagent.ts # review_subagents/review_subagent tools + /pr-review-config command
185
- └─ extensions/review-table.ts # renders the final JSON as a table (TUI only)
207
+ ├─ extensions/review-table.ts # renders JSON and triggers trusted configured publishing
208
+ ├─ tests/pr-review-policy.test.ts # focused policy compatibility tests
209
+ └─ tests/pr-review-publish.test.ts # posting gate, payload, marker, and anchor tests
186
210
  ```
187
211
 
188
- ## Security & cost notes
212
+ ## Speed, security & cost notes
189
213
 
190
- - The `review_subagents` batch tool and `review_subagent` fallback spawn isolated `pi` subprocesses (`--mode json -p --no-session`) on your configured tier models. Subagents are read-only reviewers (`read,bash,grep,find,ls` by default) and never post comments or edit files.
191
- - Project-local `pr-review.json` is only read when the project is trusted.
192
- - Tiered review calls multiple models per PR, now concurrently for independent passes. Point `light` at a cheap model for overview/risk scan; reserve `heavy` for the deep passes, and configure per-tier `fallbacks` only for acceptable backup models because retries can increase cost.
214
+ - The four independent review lenses remain intact. Only overview runs context-only with `--no-tools`; medium and both heavy specialists retain configured tools for surrounding-file validation. All subprocesses use `--no-context-files` because the orchestrator supplies the base review context explicitly, and convention excerpts are sent only to the medium pass instead of every model.
215
+ - Tool results include effective `toolPolicy` and `elapsedMs` telemetry (plus per-attempt timing) so repeated representative runs can be compared at p50/p95 without guessing. Restore tools for a custom pass by sending `tool_policy: "configured"`; callers that omit policy retain legacy behavior unless `toolPolicies` config says otherwise.
216
+ - The `review_subagents` batch tool and `review_subagent` fallback spawn isolated `pi` subprocesses (`--mode json -p --no-session`) on your configured tier models. Reviewer prompts prohibit modifications, but a configured allowlist containing `bash` is not technically read-only; use a narrower allowlist if stronger enforcement is required.
217
+ - Project-local `pr-review.json` is only read when the project is trusted. Because project `autoPostReviews: true` causes writes under your authenticated `gh` identity, the effective setting and source are surfaced when publication occurs.
218
+ - GitHub publication uses `gh` only, verifies the current identity and PR head, checks paginated formal reviews and legacy comments for same-head markers, and hardcodes `event: COMMENT`. Ambiguous transport failures are reconciled once and never blindly retried.
219
+ - Tiered review calls multiple models per PR, concurrently for independent passes. Point `light` at a cheap model for overview/risk scan; reserve `heavy` for deep passes, and configure per-tier `fallbacks` only for acceptable backup models because retries can increase cost.
193
220
 
194
221
  ## Design notes
195
222
 
196
- - **Process** mirrors the Claude review workflow (PR-number driven, confirm-before-reviewing closed/merged PRs, skip draft/same-head-already-reviewed, overview + strengths, convention/readability/maintainability, best-effort build/test verification, validate-then-classify, optional comment posting with strict GitHub permalink rules) with bounded parallel multi-model fan-out (configurable light/medium/heavy tiers).
223
+ - **Process** is PR-number driven: confirm non-open PRs, skip draft/same-head-already-reviewed work, fan out four review lenses, verify, validate/classify, emit JSON, then optionally publish one extension-owned formal `COMMENT` review.
197
224
  - **Captures every severity** (`nit → P0`) with a `blocking` flag; the verdict depends only on blocking findings, so nothing minor is lost but a clean PR still gets approved.
198
225
  - **Verification is non-destructive:** any build/test runs in an isolated `git worktree` on the PR head — the prompt never checks out, commits, or pushes in your working tree.
199
226
  - pi has no built-in sub-agents, so tiering is implemented as an extension that spawns isolated `pi` subprocesses per tier; the batch tool gives deterministic parallelism, and the prompt degrades gracefully to single-pass or inline review when the extension is absent.