@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.
- package/README.md +126 -14
- package/README.zh-CN.md +79 -4
- package/dist/args.d.ts +8 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +167 -28
- package/dist/args.js.map +1 -1
- package/dist/args.test.d.ts +2 -0
- package/dist/args.test.d.ts.map +1 -0
- package/dist/args.test.js +125 -0
- package/dist/args.test.js.map +1 -0
- package/dist/child-process.d.ts +3 -0
- package/dist/child-process.d.ts.map +1 -1
- package/dist/child-process.js +26 -0
- package/dist/child-process.js.map +1 -1
- package/dist/child-process.test.js +13 -0
- package/dist/child-process.test.js.map +1 -1
- package/dist/cli-exit.test.d.ts +2 -0
- package/dist/cli-exit.test.d.ts.map +1 -0
- package/dist/cli-exit.test.js +84 -0
- package/dist/cli-exit.test.js.map +1 -0
- package/dist/cli.js +16 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/extensions/review.d.ts +2 -0
- package/dist/extensions/review.js +22 -0
- package/dist/extensions/rv-prompts.d.ts +19 -0
- package/dist/extensions/rv-prompts.js +132 -0
- package/dist/index.d.ts +22 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -6
- package/dist/index.js.map +1 -1
- package/dist/json-events.d.ts +71 -0
- package/dist/json-events.d.ts.map +1 -1
- package/dist/json-events.js +250 -0
- package/dist/json-events.js.map +1 -1
- package/dist/json-events.test.js +217 -1
- package/dist/json-events.test.js.map +1 -1
- package/dist/loop.d.ts +36 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +73 -0
- package/dist/loop.js.map +1 -0
- package/dist/loop.test.d.ts +2 -0
- package/dist/loop.test.d.ts.map +1 -0
- package/dist/loop.test.js +83 -0
- package/dist/loop.test.js.map +1 -0
- package/dist/matcher.d.ts +74 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/matcher.js +202 -0
- package/dist/matcher.js.map +1 -0
- package/dist/matcher.test.d.ts +2 -0
- package/dist/matcher.test.d.ts.map +1 -0
- package/dist/matcher.test.js +161 -0
- package/dist/matcher.test.js.map +1 -0
- package/dist/meta-footer.d.ts +15 -1
- package/dist/meta-footer.d.ts.map +1 -1
- package/dist/meta-footer.js +108 -0
- package/dist/meta-footer.js.map +1 -1
- package/dist/meta-footer.test.js +43 -1
- package/dist/meta-footer.test.js.map +1 -1
- package/dist/panel-aggregate.d.ts +18 -0
- package/dist/panel-aggregate.d.ts.map +1 -0
- package/dist/panel-aggregate.js +247 -0
- package/dist/panel-aggregate.js.map +1 -0
- package/dist/panel-aggregate.test.d.ts +2 -0
- package/dist/panel-aggregate.test.d.ts.map +1 -0
- package/dist/panel-aggregate.test.js +461 -0
- package/dist/panel-aggregate.test.js.map +1 -0
- package/dist/panel-cancellation.test.d.ts +2 -0
- package/dist/panel-cancellation.test.d.ts.map +1 -0
- package/dist/panel-cancellation.test.js +24 -0
- package/dist/panel-cancellation.test.js.map +1 -0
- package/dist/panel-cli.test.d.ts +2 -0
- package/dist/panel-cli.test.d.ts.map +1 -0
- package/dist/panel-cli.test.js +239 -0
- package/dist/panel-cli.test.js.map +1 -0
- package/dist/panel-config.d.ts +19 -0
- package/dist/panel-config.d.ts.map +1 -0
- package/dist/panel-config.js +115 -0
- package/dist/panel-config.js.map +1 -0
- package/dist/panel-config.test.d.ts +2 -0
- package/dist/panel-config.test.d.ts.map +1 -0
- package/dist/panel-config.test.js +94 -0
- package/dist/panel-config.test.js.map +1 -0
- package/dist/panel-loop.test.d.ts +2 -0
- package/dist/panel-loop.test.d.ts.map +1 -0
- package/dist/panel-loop.test.js +118 -0
- package/dist/panel-loop.test.js.map +1 -0
- package/dist/panel-tools.test.d.ts +2 -0
- package/dist/panel-tools.test.d.ts.map +1 -0
- package/dist/panel-tools.test.js +12 -0
- package/dist/panel-tools.test.js.map +1 -0
- package/dist/panel-usage.d.ts +6 -0
- package/dist/panel-usage.d.ts.map +1 -0
- package/dist/panel-usage.js +21 -0
- package/dist/panel-usage.js.map +1 -0
- package/dist/panel-view.d.ts +41 -0
- package/dist/panel-view.d.ts.map +1 -0
- package/dist/panel-view.js +90 -0
- package/dist/panel-view.js.map +1 -0
- package/dist/panel-view.test.d.ts +2 -0
- package/dist/panel-view.test.d.ts.map +1 -0
- package/dist/panel-view.test.js +45 -0
- package/dist/panel-view.test.js.map +1 -0
- package/dist/panel.d.ts +20 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +393 -0
- package/dist/panel.js.map +1 -0
- package/dist/presets.d.ts +2 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +10 -0
- package/dist/presets.js.map +1 -1
- package/dist/prompt.d.ts +14 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +61 -15
- package/dist/prompt.js.map +1 -1
- package/dist/prompt.test.d.ts +2 -0
- package/dist/prompt.test.d.ts.map +1 -0
- package/dist/prompt.test.js +37 -0
- package/dist/prompt.test.js.map +1 -0
- package/dist/review-events.d.ts +70 -0
- package/dist/review-events.d.ts.map +1 -0
- package/dist/review-events.js +52 -0
- package/dist/review-events.js.map +1 -0
- package/dist/review-events.test.d.ts +2 -0
- package/dist/review-events.test.d.ts.map +1 -0
- package/dist/review-events.test.js +47 -0
- package/dist/review-events.test.js.map +1 -0
- package/dist/review-result.d.ts +5 -0
- package/dist/review-result.d.ts.map +1 -0
- package/dist/review-result.js +98 -0
- package/dist/review-result.js.map +1 -0
- package/dist/review-result.test.d.ts +2 -0
- package/dist/review-result.test.d.ts.map +1 -0
- package/dist/review-result.test.js +196 -0
- package/dist/review-result.test.js.map +1 -0
- package/dist/review.d.ts +10 -4
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +82 -20
- package/dist/review.js.map +1 -1
- package/dist/review.meta.test.js +2 -2
- package/dist/review.meta.test.js.map +1 -1
- package/dist/skill-protocol.test.d.ts +2 -0
- package/dist/skill-protocol.test.d.ts.map +1 -0
- package/dist/skill-protocol.test.js +35 -0
- package/dist/skill-protocol.test.js.map +1 -0
- package/dist/types.d.ts +151 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -1
- package/extensions/panel-tool.test.ts +30 -0
- package/extensions/panel-tool.ts +169 -0
- package/extensions/review.ts +3 -1
- package/extensions/rv-prompts.test.ts +8 -1
- package/extensions/rv-prompts.ts +6 -4
- package/package.json +14 -5
- package/resources/panel-presets.json +21 -0
- package/resources/review-presets.json +2 -2
- package/resources/system-prompt.md +11 -1
- package/skills/pi-review/SKILL.md +93 -15
- 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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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`
|
|
134
|
+
`/rv` 会向主会话注入**英文任务说明**。新的 Pi 审查会调用原生 `pi_review` 工具,由它以标准化事件模式启动打包的 `pi-review` CLI 并渲染实时 Panel。普通 `/rv @path` 无需额外参数;`--continue`、`--keep-session` 与显式 `--no-stream` 保持单审查 CLI 路径。
|
|
70
135
|
|
|
71
|
-
**Claude Code / Codex 等 Agent
|
|
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`
|
|
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
|
package/dist/args.d.ts.map
CHANGED
|
@@ -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;
|
|
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 (
|
|
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
|
-
|
|
44
|
-
process.exit(2);
|
|
73
|
+
throw new ArgsParseError(`${flag} requires a value`);
|
|
45
74
|
}
|
|
46
75
|
return argv.shift();
|
|
47
76
|
}
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|