@zephyrdeng/pi-review 0.6.2 → 0.7.1

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.
Files changed (163) hide show
  1. package/README.md +126 -14
  2. package/README.zh-CN.md +79 -4
  3. package/dist/args.d.ts +8 -0
  4. package/dist/args.d.ts.map +1 -1
  5. package/dist/args.js +167 -28
  6. package/dist/args.js.map +1 -1
  7. package/dist/args.test.d.ts +2 -0
  8. package/dist/args.test.d.ts.map +1 -0
  9. package/dist/args.test.js +125 -0
  10. package/dist/args.test.js.map +1 -0
  11. package/dist/child-process.d.ts +3 -0
  12. package/dist/child-process.d.ts.map +1 -1
  13. package/dist/child-process.js +26 -0
  14. package/dist/child-process.js.map +1 -1
  15. package/dist/child-process.test.js +13 -0
  16. package/dist/child-process.test.js.map +1 -1
  17. package/dist/cli-exit.test.d.ts +2 -0
  18. package/dist/cli-exit.test.d.ts.map +1 -0
  19. package/dist/cli-exit.test.js +84 -0
  20. package/dist/cli-exit.test.js.map +1 -0
  21. package/dist/cli.js +16 -2
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config.d.ts +1 -0
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +1 -0
  26. package/dist/config.js.map +1 -1
  27. package/dist/extensions/review.d.ts +2 -0
  28. package/dist/extensions/review.js +22 -0
  29. package/dist/extensions/rv-prompts.d.ts +19 -0
  30. package/dist/extensions/rv-prompts.js +132 -0
  31. package/dist/index.d.ts +22 -7
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +15 -6
  34. package/dist/index.js.map +1 -1
  35. package/dist/json-events.d.ts +71 -0
  36. package/dist/json-events.d.ts.map +1 -1
  37. package/dist/json-events.js +250 -0
  38. package/dist/json-events.js.map +1 -1
  39. package/dist/json-events.test.js +217 -1
  40. package/dist/json-events.test.js.map +1 -1
  41. package/dist/loop.d.ts +36 -0
  42. package/dist/loop.d.ts.map +1 -0
  43. package/dist/loop.js +73 -0
  44. package/dist/loop.js.map +1 -0
  45. package/dist/loop.test.d.ts +2 -0
  46. package/dist/loop.test.d.ts.map +1 -0
  47. package/dist/loop.test.js +83 -0
  48. package/dist/loop.test.js.map +1 -0
  49. package/dist/matcher.d.ts +74 -0
  50. package/dist/matcher.d.ts.map +1 -0
  51. package/dist/matcher.js +202 -0
  52. package/dist/matcher.js.map +1 -0
  53. package/dist/matcher.test.d.ts +2 -0
  54. package/dist/matcher.test.d.ts.map +1 -0
  55. package/dist/matcher.test.js +161 -0
  56. package/dist/matcher.test.js.map +1 -0
  57. package/dist/meta-footer.d.ts +15 -1
  58. package/dist/meta-footer.d.ts.map +1 -1
  59. package/dist/meta-footer.js +108 -0
  60. package/dist/meta-footer.js.map +1 -1
  61. package/dist/meta-footer.test.js +43 -1
  62. package/dist/meta-footer.test.js.map +1 -1
  63. package/dist/panel-aggregate.d.ts +18 -0
  64. package/dist/panel-aggregate.d.ts.map +1 -0
  65. package/dist/panel-aggregate.js +247 -0
  66. package/dist/panel-aggregate.js.map +1 -0
  67. package/dist/panel-aggregate.test.d.ts +2 -0
  68. package/dist/panel-aggregate.test.d.ts.map +1 -0
  69. package/dist/panel-aggregate.test.js +461 -0
  70. package/dist/panel-aggregate.test.js.map +1 -0
  71. package/dist/panel-cancellation.test.d.ts +2 -0
  72. package/dist/panel-cancellation.test.d.ts.map +1 -0
  73. package/dist/panel-cancellation.test.js +24 -0
  74. package/dist/panel-cancellation.test.js.map +1 -0
  75. package/dist/panel-cli.test.d.ts +2 -0
  76. package/dist/panel-cli.test.d.ts.map +1 -0
  77. package/dist/panel-cli.test.js +239 -0
  78. package/dist/panel-cli.test.js.map +1 -0
  79. package/dist/panel-config.d.ts +19 -0
  80. package/dist/panel-config.d.ts.map +1 -0
  81. package/dist/panel-config.js +115 -0
  82. package/dist/panel-config.js.map +1 -0
  83. package/dist/panel-config.test.d.ts +2 -0
  84. package/dist/panel-config.test.d.ts.map +1 -0
  85. package/dist/panel-config.test.js +94 -0
  86. package/dist/panel-config.test.js.map +1 -0
  87. package/dist/panel-loop.test.d.ts +2 -0
  88. package/dist/panel-loop.test.d.ts.map +1 -0
  89. package/dist/panel-loop.test.js +118 -0
  90. package/dist/panel-loop.test.js.map +1 -0
  91. package/dist/panel-tools.test.d.ts +2 -0
  92. package/dist/panel-tools.test.d.ts.map +1 -0
  93. package/dist/panel-tools.test.js +12 -0
  94. package/dist/panel-tools.test.js.map +1 -0
  95. package/dist/panel-usage.d.ts +6 -0
  96. package/dist/panel-usage.d.ts.map +1 -0
  97. package/dist/panel-usage.js +21 -0
  98. package/dist/panel-usage.js.map +1 -0
  99. package/dist/panel-view.d.ts +41 -0
  100. package/dist/panel-view.d.ts.map +1 -0
  101. package/dist/panel-view.js +90 -0
  102. package/dist/panel-view.js.map +1 -0
  103. package/dist/panel-view.test.d.ts +2 -0
  104. package/dist/panel-view.test.d.ts.map +1 -0
  105. package/dist/panel-view.test.js +45 -0
  106. package/dist/panel-view.test.js.map +1 -0
  107. package/dist/panel.d.ts +20 -0
  108. package/dist/panel.d.ts.map +1 -0
  109. package/dist/panel.js +393 -0
  110. package/dist/panel.js.map +1 -0
  111. package/dist/presets.d.ts +2 -1
  112. package/dist/presets.d.ts.map +1 -1
  113. package/dist/presets.js +10 -0
  114. package/dist/presets.js.map +1 -1
  115. package/dist/prompt.d.ts +14 -1
  116. package/dist/prompt.d.ts.map +1 -1
  117. package/dist/prompt.js +61 -15
  118. package/dist/prompt.js.map +1 -1
  119. package/dist/prompt.test.d.ts +2 -0
  120. package/dist/prompt.test.d.ts.map +1 -0
  121. package/dist/prompt.test.js +37 -0
  122. package/dist/prompt.test.js.map +1 -0
  123. package/dist/review-events.d.ts +70 -0
  124. package/dist/review-events.d.ts.map +1 -0
  125. package/dist/review-events.js +52 -0
  126. package/dist/review-events.js.map +1 -0
  127. package/dist/review-events.test.d.ts +2 -0
  128. package/dist/review-events.test.d.ts.map +1 -0
  129. package/dist/review-events.test.js +47 -0
  130. package/dist/review-events.test.js.map +1 -0
  131. package/dist/review-result.d.ts +5 -0
  132. package/dist/review-result.d.ts.map +1 -0
  133. package/dist/review-result.js +98 -0
  134. package/dist/review-result.js.map +1 -0
  135. package/dist/review-result.test.d.ts +2 -0
  136. package/dist/review-result.test.d.ts.map +1 -0
  137. package/dist/review-result.test.js +196 -0
  138. package/dist/review-result.test.js.map +1 -0
  139. package/dist/review.d.ts +10 -4
  140. package/dist/review.d.ts.map +1 -1
  141. package/dist/review.js +82 -20
  142. package/dist/review.js.map +1 -1
  143. package/dist/review.meta.test.js +2 -2
  144. package/dist/review.meta.test.js.map +1 -1
  145. package/dist/skill-protocol.test.d.ts +2 -0
  146. package/dist/skill-protocol.test.d.ts.map +1 -0
  147. package/dist/skill-protocol.test.js +35 -0
  148. package/dist/skill-protocol.test.js.map +1 -0
  149. package/dist/types.d.ts +151 -5
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/types.js +13 -0
  152. package/dist/types.js.map +1 -1
  153. package/extensions/panel-tool.test.ts +30 -0
  154. package/extensions/panel-tool.ts +169 -0
  155. package/extensions/review.ts +3 -1
  156. package/extensions/rv-prompts.test.ts +8 -1
  157. package/extensions/rv-prompts.ts +6 -4
  158. package/package.json +14 -5
  159. package/resources/panel-presets.json +21 -0
  160. package/resources/review-presets.json +2 -2
  161. package/resources/system-prompt.md +11 -1
  162. package/skills/pi-review/SKILL.md +93 -15
  163. package/skills/pi-review/references/codex-tools.md +19 -6
package/README.md CHANGED
@@ -18,7 +18,8 @@ Works as a standalone CLI, a Pi package (extension + skill), or integrated into
18
18
 
19
19
  - **Isolated review sessions** — each review runs in a clean child process with no shared state
20
20
  - **Multiple review modes** — code review, multi-lens plan review, adversarial challenge review
21
- - **Structured output** — human-readable ASCII footer on stdout; `PI_REVIEW_META_JSON` on stderr for automation
21
+ - **Structured output** — explicit `status`, structured findings, and a human-readable ASCII footer; one-line `PI_REVIEW_META_JSON` on stderr for automation
22
+ - **Loop review gate** — bounded, isolated review rounds with `pi-review loop --max-rounds <n>`; the host remains the only editor
22
23
  - **Model-agnostic** — use any model available in your Pi installation
23
24
  - **Live streaming** — child review output is forwarded as it arrives (use `--no-stream` to buffer)
24
25
  - **Progress logging for AI hosts** — `--progress-log <path>` writes a live JSON event log so agent hosts that buffer tool stdout (Claude Code, Codex, ...) can still show real-time progress
@@ -102,6 +103,9 @@ pi-review --mode plan -- @docs/architecture.md
102
103
  # Adversarial challenge review
103
104
  pi-review --mode challenge -- @docs/design.md
104
105
 
106
+ # Bounded review-only gate (default: 3 rounds)
107
+ pi-review loop --max-rounds 3 -- @src
108
+
105
109
  # List available models
106
110
  pi-review models
107
111
  ```
@@ -114,6 +118,71 @@ pi-review models
114
118
  | `plan` | Broad plan/architecture review through multiple expert lenses: engineering, product, security, QA, operations, and DX. |
115
119
  | `challenge` | Adversarial review that pressure-tests assumptions, dependencies, reversibility, failure modes, and migration paths. |
116
120
 
121
+ ## Loop Review
122
+
123
+ `pi-review loop` runs a bounded sequence of full, isolated review runs against the current working tree:
124
+
125
+ ```bash
126
+ pi-review loop --max-rounds 3 -- @src
127
+ pi-review loop --mode challenge --max-rounds 2 -- @docs/design.md
128
+ ```
129
+
130
+ Each round is review-only. The process never edits, patches, waits for filesystem changes, or asks the child session to fix findings. It stops immediately on `clean`, `needs_human`, or `blocked`; otherwise it stops when the round budget is exhausted. Every round emits one `PI_REVIEW_META_JSON` line in order, and the final human summary lists each round's status, verdict, duration, and finding counts.
131
+
132
+ This is a **host-driven gate**: if findings remain, the host or human fixes only accepted in-scope findings and invokes `loop` again. For patch-by-patch agent closeout, `--max-rounds 1` gives the host a fix point after each review; use a larger budget only when repeated review of the unchanged tree is intentional. `loop` accepts normal review target/model/progress options but rejects `--keep-session`, `--continue`, and `--name` in v1.
133
+
134
+ ## Panel Review
135
+
136
+ Panel review runs multiple **independent** reviewers in isolated child sessions and aggregates their findings into one gate result. Reviewers cannot see one another's findings, so agreement represents independent discovery.
137
+
138
+ ```bash
139
+ # Single panel review
140
+ pi-review --reviewers 3 --consensus quorum --min-agree 2 -- @src
141
+ # Expert preset (correctness, security, testing lenses)
142
+ pi-review --panel code-experts --consensus majority -- @src
143
+ # Panel loop review (up to reviewer_count × max-rounds reviewer runs + adjudication)
144
+ pi-review loop --reviewers 3 --consensus quorum --max-rounds 2 -- @src
145
+ ```
146
+
147
+ ### Consensus
148
+
149
+ A finding becomes a **confirmed finding** (gate-relevant) only when enough independent reviewers mark the issue **actionable**. Otherwise it stays a non-blocking **advisory**. Multi-reviewer panels default to **quorum** with minimum agreement **2** so panel mode never silently becomes any-finding fail-closed; single-review stays threshold one.
150
+
151
+ | Policy | Threshold |
152
+ |--------|----------|
153
+ | `any` | one actionable reviewer confirms |
154
+ | `quorum` (default) | configured minimum agreement (default 2; `--min-agree`) |
155
+ | `majority` | `floor(reviewers / 2) + 1` |
156
+ | `unanimous` | every reviewer |
157
+
158
+ Singleton (uncorroborated) findings remain visible as **advisories** but do not change clean status or fail the gate. Confirmed actionable clusters produce `has_findings`; no confirmed clusters produce `clean`.
159
+
160
+ ### Aggregation
161
+
162
+ Two-phase matching: deterministic matching on stable anchors (path + normalized summary) first; only ambiguous same-path candidates go to a constrained **semantic adjudicator** (enabled with `--consensus-model`). The adjudicator clusters findings and may not invent findings, drop findings, add evidence, or act as another reviewer — it has no write tools. Low-confidence matches stay separate advisories so uncertain similarity cannot manufacture quorum.
163
+
164
+ ### Cost and failure
165
+
166
+ Reviewer runs = `--reviewers <n>` × `--max-rounds` (loop); one adjudication call may run per round when `--consensus-model` is set. Use `--concurrency <n>` to bound provider/machine pressure (default: reviewer count, never exceeds it). Reviewer runtime failure → `blocked`; unstructured dirty output or unresolved clarification → `needs_human`; never silently clean. Panel review rejects `--keep-session`, `--continue`, and `--name` (reviewers run `--no-session`); the host agent remains the only editor.
167
+
168
+ ### Machine output
169
+
170
+ A panel evaluation emits **one** aggregate `PI_REVIEW_META_JSON` record with additive fields: `strategy: "panel"`, `configuredReviewers`, `successfulReviewers`, `consensusPolicy`, `consensusThreshold`, `panelHealth`, `confirmedClusters`, `advisories`, and per-`reviewers` outcomes. Top-level `findings` contain confirmed clusters only; advisories remain separate. Existing single-review keys remain unchanged, so older consumers can ignore the new fields.
171
+
172
+ ### Live Pi progress and event replay
173
+
174
+ In Pi, `/rv @target` calls the native `pi_review` tool. It renders each reviewer as an independent live row with lifecycle state, active tool, elapsed time, and token usage; expand the tool result with `Ctrl+O` for bounded activity and final findings/provenance.
175
+
176
+ Renderer adapters can consume the stable, versioned event stream directly:
177
+
178
+ ```bash
179
+ pi-review --panel code-experts --output-format events-jsonl -- @src
180
+ ```
181
+
182
+ This mode writes only `ReviewEvent v1` JSONL to stdout. Events have one `runId`, monotonically increasing `seq`, bounded/redacted activity text, and end in exactly one `panel.completed` event containing the same `PanelReviewMeta` as the default CLI path. The reducer is exported as `createPanelViewState()` and `reducePanelEvent()` for deterministic live delivery and replay.
183
+
184
+ Panel reviewers use the hard allowlist `read,grep,find,ls`. Shell and mutation-capable tools are rejected before a reviewer starts. `Ctrl+C` cancels the reviewer and adjudicator process trees, emits cancellation lifecycle events, and produces one blocked final event.
185
+
117
186
  ## Output Format
118
187
 
119
188
  Every review produces Markdown with these sections:
@@ -124,6 +193,14 @@ approve | request_changes | needs_clarification | blocked
124
193
 
125
194
  ## Summary
126
195
  ## Findings
196
+ ### F1: <summary>
197
+ - Severity: critical | high | medium | low
198
+ - Path: <path or none>
199
+ - Actionable: yes | no
200
+ - Evidence: <concrete evidence>
201
+ - Impact: <why it matters>
202
+ - Recommendation: <specific next step>
203
+
127
204
  ## Risks and Blind Spots
128
205
  ## Open Questions
129
206
  ```
@@ -133,12 +210,37 @@ The CLI appends a readable ASCII footer on **stdout**:
133
210
  ```
134
211
  ── pi-review ────────────────────────────
135
212
  Verdict ! REQUEST CHANGES
213
+ Status HAS FINDINGS
136
214
  Mode code
215
+ Findings 1 actionable / 1 total
216
+ Model provider/model
217
+ Thinking xhigh
218
+ Tokens in 17.6K · out 512 · cache 2.0K · reason 0
137
219
  Duration 42.3s
138
220
  ──────────────────────────────────────────
139
221
  ```
140
222
 
141
- For scripts, parse **`PI_REVIEW_META_JSON:`** from **stderr** (same fields as before). Set `PI_REVIEW_META_STDOUT=1` to also emit that JSON line on stdout.
223
+ `Thinking` shows the requested thinking level when set; `Tokens` shows the child session's token usage (`in`/`out`/`cache`/`reason`) parsed from the `--mode json` event stream — available only when `--progress-log` is used (otherwise both lines are omitted).
224
+
225
+ For scripts, parse **`PI_REVIEW_META_JSON:`** from **stderr**. Existing keys remain, with additive fields:
226
+
227
+ ```json
228
+ {"reviewMode":"code","verdict":"request_changes","verdictSource":"parsed","status":"has_findings","findings":[{"id":"F1","severity":"high","path":"src/cli.ts","summary":"Dirty reviews exit zero","actionable":true}],"actionableCount":1,"durationMs":42300,"model":"provider/model","thinking":"xhigh","usage":{"input":18031,"output":512,"cacheRead":2048,"cacheWrite":0,"reasoning":0,"totalTokens":18591,"costTotal":0.05}}
229
+ ```
230
+
231
+ `status` is one of `clean`, `has_findings`, `needs_human`, or `blocked`: `approve` with no actionable findings is `clean`; `request_changes` or actionable findings are `has_findings`; `needs_clarification` is `needs_human`; runtime/fatal failures are `blocked`. Each finding always has `summary` and `actionable`; `id`, `severity`, and `path` are present when parsed. `thinking` and `usage` are additive and present only when available (token usage requires `--progress-log`). The line remains a single additive JSON record, so older consumers can ignore unknown keys. Set `PI_REVIEW_META_STDOUT=1` to emit it on stdout instead.
232
+
233
+ The parser prefers the exact `### F1` shape above but also accepts legacy `###` headings and top-level finding lists. When `Actionable` is missing, findings under `request_changes` default to actionable and other verdicts default to non-actionable. A missing/unrecognized verdict falls back to `needs_clarification` / `needs_human` and includes `parseError`; runtime failures always remain `blocked`.
234
+
235
+ ### Exit codes
236
+
237
+ | Code | Meaning |
238
+ |------|---------|
239
+ | `0` | Final status is `clean` |
240
+ | `1` | Final status is `has_findings` / loop budget exhausted |
241
+ | `2` | CLI usage or argument error |
242
+ | `3` | `needs_human` — clarification or a decision is required |
243
+ | `4` | `blocked` — child/runtime failure or review cannot proceed |
142
244
 
143
245
  ## Session Management
144
246
 
@@ -150,19 +252,17 @@ pi-review --mode challenge --keep-session -- @docs/design.md
150
252
  pi-review --continue <sessionHandle> --mode challenge --model provider/model -- "expand finding 2"
151
253
  ```
152
254
 
153
- ## Live Progress in AI Hosts
255
+ ## Live Progress and Token Usage
256
+
257
+ `pi-review` always runs the child in `--mode json` internally. In streaming mode it forwards **readable text deltas** to stdout live and writes **semantic milestone notices** to stderr — `pi-review: review started`, `pi-review: tool <name> started/finished`, `pi-review: review finished`. Token usage (`input`/`output`/`cache`/`reasoning`) is accumulated by default and shown in the ASCII footer and `PI_REVIEW_META_JSON` — **no `--progress-log` required**.
154
258
 
155
- Agent hosts like Claude Code, Cursor, and Codex typically buffer a Bash tool's stdout until the command exits, so a multi-minute review can look like a silent wait followed by one large dump — even though `pi-review` streams by default. `--progress-log <path>` sidesteps this: it runs the child in `--mode json` and writes its event log to a file in real time, independent of what the calling tool does with stdout.
259
+ Agent hosts like Claude Code, Cursor, and Codex typically buffer a Bash tool's stdout until the command exits. The stderr milestone notices give you progress signals without tailing a file. The final Markdown review + ASCII footer arrive on stdout when the process exits.
156
260
 
157
- The bundled **pi-review** agent skill tells parent agents to use `--progress-log` by default on those hosts (Pi `/rv` stays on default streaming). Details: [`skills/pi-review/SKILL.md`](skills/pi-review/SKILL.md) and [`skills/pi-review/references/codex-tools.md`](skills/pi-review/references/codex-tools.md).
261
+ `--progress-log <path>` is now an **optional** convenience for fine-grained debugging: it tees the raw `--mode json` event stream to a file. It no longer gates token visibility. Details: [`skills/pi-review/SKILL.md`](skills/pi-review/SKILL.md) and [`skills/pi-review/references/codex-tools.md`](skills/pi-review/references/codex-tools.md).
158
262
 
159
263
  ```bash
160
- # Start the review in the background, writing structured progress events to a file
264
+ # Optional: capture the full event log for debugging
161
265
  pi-review --progress-log /tmp/pi-review.jsonl -- @src/foo.ts &
162
-
163
- # Tail it from another process/tool (e.g. Claude Code's Monitor tool) for live updates.
164
- # `message_update` and `tool_execution_update` carry high-frequency token-level deltas —
165
- # excluding them keeps the feed to milestone events (tool start/end, turn end, agent end).
166
266
  tail -f -n +1 /tmp/pi-review.jsonl | jq -c --unbuffered '
167
267
  select(.type != "message_update" and .type != "tool_execution_update")
168
268
  '
@@ -173,7 +273,8 @@ The JSON event schema is pi CLI's own internal format, not a contract `pi-review
173
273
  ## CLI Reference
174
274
 
175
275
  ```
176
- pi-review [options] -- <@files|text...>
276
+ pi-review [review] [options] -- <@files|text...>
277
+ pi-review loop [options] -- <@files|text...>
177
278
  pi-review models [search]
178
279
  ```
179
280
 
@@ -190,6 +291,16 @@ pi-review models [search]
190
291
  | `--name <name>` | Session name (with `--keep-session`) |
191
292
  | `--no-stream` | Buffer child output until exit (default: stream live) |
192
293
  | `--progress-log <path>` | Stream child `--mode json` events to this file (cannot combine with `--no-stream`) |
294
+ | `--max-rounds <n>` | Positive loop review budget (default: `3`; `loop` only) |
295
+ | `--reviewers <n>` | Panel: number of independent reviewers (2-8; activates panel mode) |
296
+ | `--panel <name>` | Panel: named expert-panel preset (cannot combine with `--reviewers`) |
297
+ | `--consensus <policy>` | Panel: `any \| quorum \| majority \| unanimous` (default: `quorum`) |
298
+ | `--min-agree <n>` | Panel: minimum reviewers for quorum (default: `2`; quorum only) |
299
+ | `--consensus-model <model>` | Panel: model for semantic consensus adjudication |
300
+ | `--concurrency <n>` | Panel: bounded reviewer concurrency (default: reviewer count) |
301
+ | `--output-format events-jsonl` | Panel: normalized `ReviewEvent v1` JSONL for renderer adapters |
302
+
303
+ Session flags (`--keep-session`, `--continue`, `--name`) are unsupported by `loop` and panel in v1; invalid combinations print usage and exit `2`.
193
304
 
194
305
  ## Configuration
195
306
 
@@ -200,9 +311,10 @@ Override defaults via environment variables:
200
311
  | `PI_BIN` | Pi executable path (default: `pi`) |
201
312
  | `PI_REVIEW_HOME` | Directory containing `review-presets.json` and `system-prompt.md` |
202
313
  | `PI_REVIEW_PRESETS` | Path to presets JSON file |
314
+ | `PI_REVIEW_PANEL_PRESETS` | Path to panel presets JSON file |
203
315
  | `PI_REVIEW_SYSTEM_PROMPT` | Path to system prompt file |
204
316
  | `PI_REVIEW_SESSION_DIR` | Directory for persisted review sessions |
205
- | `PI_REVIEW_META_STDOUT` | Set to `1`/`true` to also print `PI_REVIEW_META_JSON` on stdout (default: stderr only) |
317
+ | `PI_REVIEW_META_STDOUT` | Set to `1`/`true` to print `PI_REVIEW_META_JSON` on stdout instead of stderr |
206
318
 
207
319
  ## Pi Package Usage
208
320
 
@@ -213,7 +325,7 @@ After installing as a Pi package, use the `/rv` slash command:
213
325
  /rv --mode challenge @docs/design.md
214
326
  ```
215
327
 
216
- The `/rv` command injects a task message for the parent agent (Pi host rules: default streaming, no automatic `--no-stream` or `--progress-log` + the `pi-review` CLI to run). The agent follows the **pi-review** skill and runs an isolated child session. Use plain `/rv @path` in Pi — no extra streaming flags needed.
328
+ The `/rv` command injects a task message for the parent agent. New Pi reviews call the native `pi_review` tool, which launches the packaged `pi-review` CLI in normalized event mode and renders live Panel rows. Use plain `/rv @path` in Pi — no extra streaming flags needed. `--continue`, `--keep-session`, and explicit `--no-stream` retain the single-review CLI path.
217
329
 
218
330
  ```
219
331
  /rv models
@@ -235,7 +347,7 @@ Completions are a hint layer only; execution remains skill-driven. When the mode
235
347
 
236
348
  ## Security
237
349
 
238
- - Each review runs in an isolated child Pi session
350
+ - Each review and every loop round runs in an isolated child Pi session
239
351
  - Default runs use `--no-session` — no child context is stored
240
352
  - `--keep-session` stores only the child review session for explicit follow-up
241
353
  - The review session is read-only: no file edits, patches, commits, or deployments
package/README.zh-CN.md CHANGED
@@ -42,6 +42,7 @@ pi-review -- @src/foo.ts
42
42
  pi-review --model openai/gpt-5.5 -- @src/foo.ts
43
43
  pi-review --mode plan -- @docs/architecture.md
44
44
  pi-review --mode challenge -- @docs/design.md
45
+ pi-review loop --max-rounds 3 -- @src
45
46
  pi-review models
46
47
  ```
47
48
 
@@ -53,6 +54,70 @@ pi-review models
53
54
  | `plan` | 多视角方案 / 架构审查 |
54
55
  | `challenge` | 对抗式审查,压测假设与证据缺口 |
55
56
 
57
+ ## Loop Review
58
+
59
+ `pi-review loop` 会对当前工作树执行有上限的、彼此隔离的只读审查轮次:
60
+
61
+ ```bash
62
+ pi-review loop --max-rounds 3 -- @src
63
+ ```
64
+
65
+ 每轮都是完整 review run;命令不会编辑、打补丁、等待文件变化,也不会让子会话修复问题。遇到 `clean`、`needs_human`、`blocked` 会提前停止;仍有 actionable finding 时,在预算耗尽后以非零状态退出并输出逐轮摘要。宿主 Agent 或人工负责筛选并修复任务范围内的问题,再重新调用命令。需要在每次修复之间留出宿主处理点时,使用 `--max-rounds 1`。
66
+
67
+ `loop` 复用普通审查的 mode/model/progress/target 参数;v1 明确不支持 `--keep-session`、`--continue`、`--name`。
68
+
69
+ ## Panel Review(面板审查)
70
+
71
+ 面板审查让多个**独立**审查者在隔离子会话中并行评审,再聚合为同一个门禁结论。审查者看不到彼此的发现,因此一致代表独立发现。
72
+
73
+ ```bash
74
+ # 单次面板审查
75
+ pi-review --reviewers 3 --consensus quorum --min-agree 2 -- @src
76
+ # 专家预设(正确性 / 安全 / 测试三视角)
77
+ pi-review --panel code-experts --consensus majority -- @src
78
+ # 面板 loop(最多 reviewer 数 × max-rounds 次审查 + 仲裁)
79
+ pi-review loop --reviewers 3 --consensus quorum --max-rounds 2 -- @src
80
+ ```
81
+
82
+ ### 共识(Consensus)
83
+
84
+ 只有当足够多的独立审查者把同一问题标为 **actionable** 时,该 finding 才成为 **confirmed finding**(参与门禁);否则保留为非阻塞的 **advisory**(建议)。多审查者面板默认 **quorum**,最低同意数 **2**,避免面板模式悄悄退化为"任一发现即 fail-closed";单审查仍为阈值 1。
85
+
86
+ | 策略 | 阈值 |
87
+ |------|------|
88
+ | `any` | 1 个 actionable 审查者即确认 |
89
+ | `quorum`(默认) | 配置的最低同意数(默认 2,用 `--min-agree`) |
90
+ | `majority` | `floor(审查者数 / 2) + 1` |
91
+ | `unanimous` | 全体审查者 |
92
+
93
+ 单条(未被交叉印证的)发现作为 **advisory** 可见,但不改变 clean 状态、不会让门禁失败。确认的可操作簇产生 `has_findings`;无确认簇则产生 `clean`。
94
+
95
+ ### 聚合
96
+
97
+ 两阶段匹配:先用稳定锚点(路径 + 归一化摘要)做确定性匹配;只有路径相同、措辞不同的模糊候选才交给受限的**语义仲裁器**(用 `--consensus-model` 启用)。仲裁器只能聚类,不得发明 finding、丢弃 finding、补充证据或充当额外审查者,且没有写工具。低置信匹配保持为独立 advisory,避免靠"相似"制造虚假共识。
98
+
99
+ ### 成本与失败
100
+
101
+ 审查者运行数 = `--reviewers <n>` × `--max-rounds`(loop);启用 `--consensus-model` 时每轮最多再跑一次仲裁。用 `--concurrency <n>` 限制并发(默认等于审查者数,不超过)。审查者运行时失败 → `blocked`;无法解析的脏输出或未决澄清 → `needs_human`;绝不悄悄 clean。面板审查 v1 不支持 `--keep-session`、`--continue`、`--name`(审查者用 `--no-session`);宿主 Agent 仍是唯一可编辑者。
102
+
103
+ ### 机器输出
104
+
105
+ 一次面板评估只输出**一条**聚合 `PI_REVIEW_META_JSON`,新增字段:`strategy: "panel"`、`configuredReviewers`、`successfulReviewers`、`consensusPolicy`、`consensusThreshold`、`panelHealth`、`confirmedClusters`、`advisories` 以及每个 `reviewers` 的结果。顶层 `findings` 只含确认簇;advisory 单独存放。旧字段保留,老消费者可安全忽略新字段。
106
+
107
+ ### Pi 实时进度与事件回放
108
+
109
+ Pi 中执行 `/rv @target` 会调用原生 `pi_review` 工具。每位 reviewer 都有独立实时行,展示生命周期、当前工具、耗时和 token 用量;按 `Ctrl+O` 可展开查看有界活动记录、最终发现和溯源。
110
+
111
+ 渲染器可直接消费版本化事件流:
112
+
113
+ ```bash
114
+ pi-review --panel code-experts --output-format events-jsonl -- @src
115
+ ```
116
+
117
+ 该模式只向 stdout 输出 `ReviewEvent v1` JSONL。每次运行拥有一个 `runId` 和单调递增的 `seq`,活动文本会脱敏和截断,结尾固定为一条携带 `PanelReviewMeta` 的 `panel.completed` 事件。`createPanelViewState()` 与 `reducePanelEvent()` 支持确定性的实时归约和回放。
118
+
119
+ Panel reviewer 固定使用 `read,grep,find,ls` 白名单;shell 与可变更工具会在启动前被拒绝。`Ctrl+C` 会取消 reviewer 与 adjudicator 进程树,输出取消事件并生成一条 blocked 最终事件。
120
+
56
121
  ## Pi 包:`/rv` 命令
57
122
 
58
123
  安装 Pi 包后可在 Pi 里使用 `/rv`:
@@ -66,13 +131,23 @@ pi-review models
66
131
 
67
132
  跟进审查时,`--continue` 与首次 `/rv` 一样可**选填** `--mode`、`--model`(以及后续 CLI 支持的其它选项由 skill 直接调用 CLI 时传入)。
68
133
 
69
- `/rv` 会向主会话注入**英文任务说明**(**仅 Pi 宿主**:默认流式、**不要**自动加 `--no-stream` / `--progress-log`),由主 Agent 调用 `pi-review` CLI。普通 `/rv @path` 无需额外参数。需要整段缓冲时显式 `/rv --no-stream`。
134
+ `/rv` 会向主会话注入**英文任务说明**。新的 Pi 审查会调用原生 `pi_review` 工具,由它以标准化事件模式启动打包的 `pi-review` CLI 并渲染实时 Panel。普通 `/rv @path` 无需额外参数;`--continue`、`--keep-session` 与显式 `--no-stream` 保持单审查 CLI 路径。
70
135
 
71
- **Claude Code / Codex 等 Agent 宿主**:Bash 工具会缓冲 stdout,不能指望“默认流式”在对话里逐字出现;**pi-review skill 默认建议** `--progress-log <path>` + 后台运行 + tail 日志。详见 `skills/pi-review/SKILL.md`、`skills/pi-review/references/codex-tools.md` 与英文 README。
136
+ **Claude Code / Codex 等 Agent 宿主**:`pi-review` 默认把可读文本增量流到 stdout,把**语义化里程碑**写到 stderr(`pi-review: review started` / `pi-review: tool <name> started/finished` / `pi-review: review finished`);token 用量默认就能拿到,无需 `--progress-log`。`--progress-log` 退化为可选的调试用全量事件日志。详见 `skills/pi-review/SKILL.md`、`skills/pi-review/references/codex-tools.md` 与英文 README。
72
137
 
73
138
  ## 输出格式
74
139
 
75
- 审查结果包含 `## Verdict`、`## Summary`、`## Findings` 等章节,并以 **ASCII 页脚**(`── pi-review` 框线 + Verdict/Mode/Duration/Session)结尾;机器可读 JSON 在 **stderr** 一行 `PI_REVIEW_META_JSON:`(需写 stdout 时设 `PI_REVIEW_META_STDOUT=1`)。
140
+ 审查结果包含 `## Verdict`、`## Summary`、`## Findings` 等章节。每条 finding 使用 `F1 + Severity + Path + Actionable + Evidence + Impact + Recommendation` 结构。ASCII 页脚会显示 Verdict、`Status`、finding 数量、Mode、Duration/Session。
141
+
142
+ 机器可读 JSON 仍在 **stderr** 的单行 `PI_REVIEW_META_JSON:` 中,并以新增字段提供:
143
+
144
+ - `status`: `clean | has_findings | needs_human | blocked`
145
+ - `findings`: `{ id?, severity?, path?, summary, actionable }[]`
146
+ - `actionableCount`: 可操作问题数量
147
+
148
+ 旧字段不删除;需改为写入 stdout 时设 `PI_REVIEW_META_STDOUT=1`。解析器优先识别上述 `### F1` 格式,也兼容旧的三级标题和顶层列表;缺少 `Actionable` 时,`request_changes` 下默认可操作,其它 verdict 默认不可操作。无法识别 verdict 时会回退到 `needs_human` 并附带 `parseError`,运行时失败始终保持 `blocked`。
149
+
150
+ 退出码:`0` clean、`1` 最终状态为 `has_findings` / loop 预算耗尽、`2` 参数错误、`3` needs human、`4` blocked / 运行时失败。
76
151
 
77
152
  ## 配置
78
153
 
@@ -88,4 +163,4 @@ pi-review models
88
163
 
89
164
  ## 许可
90
165
 
91
- [MIT](LICENSE) © ZephyrDeng
166
+ [MIT](LICENSE) © ZephyrDeng
package/dist/args.d.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  import type { ParsedArgs } from "./types.js";
2
2
  export declare function usage(exitCode?: number): never;
3
+ export declare const DEFAULT_MAX_ROUNDS = 3;
4
+ export declare class ArgsParseError extends Error {
5
+ readonly exitCode: number;
6
+ constructor(message: string, exitCode?: number);
7
+ }
8
+ /** Pure parser for the review and loop command seam. */
9
+ export declare function parseReviewCommand(input: string[]): ParsedArgs;
10
+ export declare function isPanelActive(options: Pick<ParsedArgs, "reviewers" | "panel">): boolean;
3
11
  export declare function parseArgs(argv: string[]): ParsedArgs;
4
12
  //# sourceMappingURL=args.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,wBAAgB,KAAK,CAAC,QAAQ,SAAI,GAAG,KAAK,CAsCzC;AAUD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAkGpD"}
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,wBAAgB,KAAK,CAAC,QAAQ,SAAI,GAAG,KAAK,CA0DzC;AAED,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,qBAAa,cAAe,SAAQ,KAAK;IACV,QAAQ,CAAC,QAAQ;gBAAlC,OAAO,EAAE,MAAM,EAAW,QAAQ,SAAI;CAInD;AAoBD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,UAAU,CAsH9D;AAsDD,wBAAgB,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,OAAO,CAEvF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAyBpD"}
package/dist/args.js CHANGED
@@ -1,8 +1,10 @@
1
+ import { CONSENSUS_POLICIES, MAX_REVIEWERS, PANEL_READ_ONLY_TOOLS } from "./types.js";
1
2
  import { parseInstallCommand } from "./install-args.js";
2
3
  export function usage(exitCode = 0) {
3
4
  const out = `Usage:
4
5
  pi-review models [search]
5
- pi-review [options] -- <@files|text...>
6
+ pi-review [review] [options] -- <@files|text...>
7
+ pi-review loop [options] -- <@files|text...>
6
8
  pi-review update Update to the latest version
7
9
  pi-review install [options] Pi package + agent skills (one-shot)
8
10
  pi-review install-skill [options] Install skill to AI agents only
@@ -19,18 +21,37 @@ Options:
19
21
  --tools <csv> Override allowed tools
20
22
  --no-stream Buffer child output until exit (default: stream live)
21
23
  --progress-log <path> Stream child --mode json events to this file (cannot combine with --no-stream)
24
+ --max-rounds <n> Loop review budget (default: 3; loop only)
25
+ --reviewers <n> Panel: number of independent reviewers (2-8; activates panel mode)
26
+ --panel <name> Panel: named expert-panel preset (cannot combine with --reviewers)
27
+ --consensus <policy> Panel: any | quorum | majority | unanimous (default: quorum)
28
+ --min-agree <n> Panel: minimum reviewers for quorum (default: 2; quorum only)
29
+ --consensus-model <model> Panel: model for semantic consensus adjudication
30
+ --concurrency <n> Panel: bounded reviewer concurrency (default: reviewer count)
31
+ --output-format <events-jsonl> Panel: emit normalized ReviewEvent v1 JSONL to stdout
22
32
  -h, --help Show help
23
33
 
24
- Session (requires Pi session support):
34
+ Session (single review only; rejected by loop and panel):
25
35
  --keep-session Persist the review session for follow-up
26
36
  --continue <sessionHandle> Continue an existing review session
27
37
  --name <name> Session name (with --keep-session)
28
38
 
39
+ Exit codes:
40
+ 0 clean | 1 findings remain | 2 usage | 3 needs human | 4 blocked/runtime failure
41
+
42
+ Panel cost: reviewer runs = --reviewers <n> times --max-rounds (loop), plus one
43
+ consensus-adjudication call per round when --consensus-model is set. Single-review
44
+ commands (default, no --reviewers/--panel) keep the existing low-cost workflow.
45
+
29
46
  Examples:
30
47
  pi-review models
31
48
  pi-review -- @src/foo.ts
32
49
  pi-review --model openai/gpt-5.5 -- @src/foo.ts
33
50
  pi-review --mode challenge --keep-session -- @design.md
51
+ pi-review --reviewers 3 --consensus quorum --min-agree 2 -- @src
52
+ pi-review --panel code-experts --consensus majority -- @src
53
+ pi-review loop --reviewers 3 --consensus quorum --max-rounds 3 -- @src
54
+ pi-review loop --max-rounds 3 -- @src
34
55
  pi-review install
35
56
  pi-review install --agent claude-code codex -y
36
57
  pi-review install-skill
@@ -38,42 +59,50 @@ Examples:
38
59
  (exitCode === 0 ? process.stdout : process.stderr).write(out);
39
60
  process.exit(exitCode);
40
61
  }
62
+ export const DEFAULT_MAX_ROUNDS = 3;
63
+ export class ArgsParseError extends Error {
64
+ exitCode;
65
+ constructor(message, exitCode = 2) {
66
+ super(message);
67
+ this.exitCode = exitCode;
68
+ this.name = "ArgsParseError";
69
+ }
70
+ }
41
71
  function requireValue(flag, argv) {
42
72
  if (argv.length === 0 || argv[0] === "--") {
43
- process.stderr.write(`pi-review: ${flag} requires a value\n`);
44
- process.exit(2);
73
+ throw new ArgsParseError(`${flag} requires a value`);
45
74
  }
46
75
  return argv.shift();
47
76
  }
48
- export function parseArgs(argv) {
49
- if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help")
50
- usage(0);
51
- if (argv[0] === "models") {
52
- return { command: "models", search: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
77
+ function parsePositiveInteger(flag, value) {
78
+ if (!/^[1-9]\d*$/.test(value)) {
79
+ throw new ArgsParseError(`${flag} must be a positive integer`);
53
80
  }
54
- if (argv[0] === "install") {
55
- return parseInstallCommand(argv.slice(1));
56
- }
57
- if (argv[0] === "install-skill") {
58
- return { command: "install-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
59
- }
60
- if (argv[0] === "uninstall-skill") {
61
- return { command: "uninstall-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
81
+ const parsed = Number(value);
82
+ if (!Number.isSafeInteger(parsed)) {
83
+ throw new ArgsParseError(`${flag} must be a safe positive integer`);
62
84
  }
63
- if (argv[0] === "update") {
64
- return { command: "update", mode: "code", skills: [], payload: [], keepSession: false, stream: true };
85
+ return parsed;
86
+ }
87
+ /** Pure parser for the review and loop command seam. */
88
+ export function parseReviewCommand(input) {
89
+ const argv = [...input];
90
+ let command = "review";
91
+ if (argv[0] === "review" || argv[0] === "loop") {
92
+ command = argv.shift();
65
93
  }
66
94
  let mode = "code";
67
95
  if (argv[0] && !argv[0].startsWith("-")) {
68
96
  mode = argv.shift();
69
97
  }
70
98
  const options = {
71
- command: "review",
99
+ command,
72
100
  mode,
73
101
  skills: [],
74
102
  payload: [],
75
103
  keepSession: false,
76
104
  stream: true,
105
+ ...(command === "loop" ? { maxRounds: DEFAULT_MAX_ROUNDS } : {}),
77
106
  };
78
107
  while (argv.length > 0) {
79
108
  const arg = argv.shift();
@@ -84,8 +113,7 @@ export function parseArgs(argv) {
84
113
  switch (arg) {
85
114
  case "-h":
86
115
  case "--help":
87
- usage(0);
88
- break;
116
+ throw new ArgsParseError("", 0);
89
117
  case "--mode":
90
118
  options.mode = requireValue(arg, argv);
91
119
  break;
@@ -119,10 +147,37 @@ export function parseArgs(argv) {
119
147
  case "--progress-log":
120
148
  options.progressLog = requireValue(arg, argv);
121
149
  break;
150
+ case "--max-rounds":
151
+ options.maxRounds = parsePositiveInteger(arg, requireValue(arg, argv));
152
+ break;
153
+ case "--reviewers":
154
+ options.reviewers = parsePositiveInteger(arg, requireValue(arg, argv));
155
+ break;
156
+ case "--panel":
157
+ options.panel = requireValue(arg, argv);
158
+ break;
159
+ case "--consensus":
160
+ options.consensus = requireValue(arg, argv);
161
+ break;
162
+ case "--min-agree":
163
+ options.minAgree = parsePositiveInteger(arg, requireValue(arg, argv));
164
+ break;
165
+ case "--consensus-model":
166
+ options.consensusModel = requireValue(arg, argv);
167
+ break;
168
+ case "--concurrency":
169
+ options.concurrency = parsePositiveInteger(arg, requireValue(arg, argv));
170
+ break;
171
+ case "--output-format": {
172
+ const format = requireValue(arg, argv);
173
+ if (format !== "events-jsonl")
174
+ throw new ArgsParseError("--output-format must be events-jsonl");
175
+ options.outputFormat = format;
176
+ break;
177
+ }
122
178
  default:
123
179
  if (arg.startsWith("--")) {
124
- process.stderr.write(`pi-review: unknown option: ${arg}\n`);
125
- process.exit(2);
180
+ throw new ArgsParseError(`unknown option: ${arg}`);
126
181
  }
127
182
  options.payload.push(arg, ...argv);
128
183
  argv.length = 0;
@@ -130,13 +185,97 @@ export function parseArgs(argv) {
130
185
  }
131
186
  }
132
187
  if (options.keepSession && options.continueHandle) {
133
- process.stderr.write("pi-review: --keep-session and --continue cannot be used together\n");
134
- process.exit(2);
188
+ throw new ArgsParseError("--keep-session and --continue cannot be used together");
135
189
  }
136
190
  if (options.progressLog && !options.stream) {
137
- process.stderr.write("pi-review: --progress-log cannot be used with --no-stream\n");
138
- process.exit(2);
191
+ throw new ArgsParseError("--progress-log cannot be used with --no-stream");
192
+ }
193
+ if (command !== "loop" && options.maxRounds !== undefined) {
194
+ throw new ArgsParseError("--max-rounds can only be used with loop");
139
195
  }
196
+ if (command === "loop" && (options.keepSession || options.continueHandle || options.name)) {
197
+ throw new ArgsParseError("loop cannot be used with --keep-session, --continue, or --name");
198
+ }
199
+ validatePanelOptions(options);
140
200
  return options;
141
201
  }
202
+ function validatePanelOptions(options) {
203
+ const hasReviewers = options.reviewers !== undefined;
204
+ const reviewerCount = options.reviewers ?? 1;
205
+ const panelActive = reviewerCount > 1 || options.panel !== undefined;
206
+ const anyPanelOption = options.consensus !== undefined ||
207
+ options.minAgree !== undefined ||
208
+ options.consensusModel !== undefined ||
209
+ options.concurrency !== undefined;
210
+ if (hasReviewers && options.reviewers > MAX_REVIEWERS) {
211
+ throw new ArgsParseError(`--reviewers must be between 1 and ${MAX_REVIEWERS}`);
212
+ }
213
+ if (hasReviewers && options.panel) {
214
+ throw new ArgsParseError("--reviewers cannot be used with --panel");
215
+ }
216
+ if (!panelActive && anyPanelOption) {
217
+ throw new ArgsParseError("panel options require --reviewers > 1 or --panel");
218
+ }
219
+ if (!panelActive && options.outputFormat) {
220
+ throw new ArgsParseError("--output-format events-jsonl requires an active panel");
221
+ }
222
+ if (options.command === "loop" && options.outputFormat) {
223
+ throw new ArgsParseError("loop cannot be used with --output-format events-jsonl");
224
+ }
225
+ if (panelActive && (options.keepSession || options.continueHandle || options.name)) {
226
+ throw new ArgsParseError("panel cannot be used with --keep-session, --continue, or --name");
227
+ }
228
+ if (panelActive && options.tools) {
229
+ const disallowed = options.tools.split(",").map((tool) => tool.trim()).filter((tool) => tool && !PANEL_READ_ONLY_TOOLS.includes(tool));
230
+ if (disallowed.length > 0) {
231
+ throw new ArgsParseError(`panel reviewers only allow ${PANEL_READ_ONLY_TOOLS.join(",")}; rejected: ${disallowed.join(",")}`);
232
+ }
233
+ }
234
+ if (options.consensus !== undefined && !CONSENSUS_POLICIES.includes(options.consensus)) {
235
+ throw new ArgsParseError(`unknown consensus policy: ${options.consensus}. Available: ${CONSENSUS_POLICIES.join(", ")}`);
236
+ }
237
+ if (options.minAgree !== undefined) {
238
+ if (options.consensus !== undefined && options.consensus !== "quorum") {
239
+ throw new ArgsParseError("--min-agree is only meaningful with --consensus quorum");
240
+ }
241
+ if (hasReviewers && !options.panel && options.minAgree > options.reviewers) {
242
+ throw new ArgsParseError(`--min-agree ${options.minAgree} cannot exceed reviewer count ${options.reviewers}`);
243
+ }
244
+ }
245
+ if (options.concurrency !== undefined && hasReviewers && !options.panel && options.concurrency > options.reviewers) {
246
+ throw new ArgsParseError(`--concurrency ${options.concurrency} cannot exceed reviewer count ${options.reviewers}`);
247
+ }
248
+ }
249
+ export function isPanelActive(options) {
250
+ return (options.reviewers !== undefined && options.reviewers > 1) || options.panel !== undefined;
251
+ }
252
+ export function parseArgs(argv) {
253
+ if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help")
254
+ usage(0);
255
+ if (argv[0] === "models") {
256
+ return { command: "models", search: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
257
+ }
258
+ if (argv[0] === "install") {
259
+ return parseInstallCommand(argv.slice(1));
260
+ }
261
+ if (argv[0] === "install-skill") {
262
+ return { command: "install-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
263
+ }
264
+ if (argv[0] === "uninstall-skill") {
265
+ return { command: "uninstall-skill", extraArgs: argv.slice(1), mode: "code", skills: [], payload: [], keepSession: false, stream: true };
266
+ }
267
+ if (argv[0] === "update") {
268
+ return { command: "update", mode: "code", skills: [], payload: [], keepSession: false, stream: true };
269
+ }
270
+ try {
271
+ return parseReviewCommand(argv);
272
+ }
273
+ catch (error) {
274
+ if (!(error instanceof ArgsParseError))
275
+ throw error;
276
+ if (error.message)
277
+ process.stderr.write(`pi-review: ${error.message}\n`);
278
+ usage(error.exitCode);
279
+ }
280
+ }
142
281
  //# sourceMappingURL=args.js.map