@zephyrdeng/pi-review 0.9.0 → 0.10.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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/json-events.d.ts +1 -0
  4. package/dist/json-events.d.ts.map +1 -1
  5. package/dist/json-events.js +12 -2
  6. package/dist/json-events.js.map +1 -1
  7. package/dist/json-events.test.js +22 -0
  8. package/dist/json-events.test.js.map +1 -1
  9. package/dist/meta-footer.d.ts.map +1 -1
  10. package/dist/meta-footer.js +6 -1
  11. package/dist/meta-footer.js.map +1 -1
  12. package/dist/meta-footer.test.js +42 -1
  13. package/dist/meta-footer.test.js.map +1 -1
  14. package/dist/panel-aggregate.d.ts +7 -0
  15. package/dist/panel-aggregate.d.ts.map +1 -1
  16. package/dist/panel-aggregate.js +19 -1
  17. package/dist/panel-aggregate.js.map +1 -1
  18. package/dist/panel-aggregate.test.js +56 -1
  19. package/dist/panel-aggregate.test.js.map +1 -1
  20. package/dist/panel-cli.test.js +42 -0
  21. package/dist/panel-cli.test.js.map +1 -1
  22. package/dist/panel-view.d.ts.map +1 -1
  23. package/dist/panel-view.js +9 -3
  24. package/dist/panel-view.js.map +1 -1
  25. package/dist/panel-view.test.js +62 -0
  26. package/dist/panel-view.test.js.map +1 -1
  27. package/dist/panel.d.ts.map +1 -1
  28. package/dist/panel.js +9 -4
  29. package/dist/panel.js.map +1 -1
  30. package/dist/review-events.d.ts +1 -0
  31. package/dist/review-events.d.ts.map +1 -1
  32. package/dist/types.d.ts +6 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/ui-client.js +1 -5
  35. package/dist/ui-client.js.map +1 -1
  36. package/dist/ui-server.d.ts +1 -1
  37. package/dist/ui-server.d.ts.map +1 -1
  38. package/extensions/panel-tool.test.ts +42 -0
  39. package/extensions/panel-tool.ts +3 -1
  40. package/extensions/rv-interactive.test.ts +179 -8
  41. package/extensions/rv-interactive.ts +64 -61
  42. package/package.json +1 -1
  43. package/skills/pi-review/SKILL.md +7 -6
  44. package/skills/pi-review/references/codex-tools.md +3 -3
  45. package/skills/pi-review/references/model-selection.md +2 -2
@@ -430,77 +430,60 @@ export async function runRvInteractiveWizard(
430
430
  const labels = rankedModelLabels(models, mode);
431
431
 
432
432
  if (ids.length > 0) {
433
- const assignEach = await ui.confirm(
434
- zh ? "为每位 reviewer 选择模型?" : "Pick a model for each reviewer?",
435
- zh
436
- ? `将依次为 ${ids.join(", ")} 弹出模型列表(方向键选择,回车确认)。选「同一模型」可共用。`
437
- : `You will pick models for ${ids.join(", ")} from a list (arrows + enter). Choose "Same model for all" to share.`,
433
+ // Branch on stable keys, not display-label substrings, so copy edits in
434
+ // either language can never silently reroute the wizard.
435
+ const assignmentOptions: Array<{ key: "per-reviewer" | "shared" | "pi-default"; label: string }> = [
436
+ { key: "per-reviewer", label: zh ? "每位 reviewer 分别选择模型" : "Pick a model for each reviewer" },
437
+ { key: "shared", label: zh ? "所有 reviewer 使用同一模型" : "Same model for all reviewers" },
438
+ { key: "pi-default", label: zh ? "使用 Pi 默认模型(运行时解析实际模型)" : "Use Pi's default model (resolved at runtime)" },
439
+ ];
440
+ const assignmentChoice = await mustSelect(
441
+ ui,
442
+ zh ? "模型分配方式" : "Model assignment",
443
+ assignmentOptions.map((option) => option.label),
438
444
  );
439
-
440
- if (assignEach) {
441
- const sameFirst = await mustSelect(
442
- ui,
443
- zh ? "模型分配方式" : "Model assignment",
444
- [
445
- zh ? "每位 reviewer 分别选择" : "Pick separately for each reviewer",
446
- zh ? "所有 reviewer 使用同一模型" : "Same model for all reviewers",
447
- ],
448
- );
449
- if (sameFirst === CANCEL) return undefined;
450
-
451
- if (sameFirst.includes("Same") || sameFirst.includes("同一")) {
452
- if (labels.length === 0) {
453
- ui.notify(zh ? "当前没有可用模型目录,请先 /rv-models" : "No model catalog available; run /rv-models first", "warning");
454
- } else {
455
- const pick = await pickModelLabel(ui, models, mode, locale, zh ? "共用模型" : "Shared model", false);
456
- if (pick === CANCEL) return undefined;
457
- if (pick !== undefined) {
458
- const m = modelByLabel(models, pick);
459
- const thinkingPick = await mustSelect(
460
- ui,
461
- zh ? `思考强度(${pick})` : `Thinking (${pick})`,
462
- thinkingOptions(m, locale),
463
- );
464
- if (thinkingPick === CANCEL) return undefined;
465
- const thinking = stripSkip(thinkingPick, locale);
466
- const token = thinking ? `${pick}:${thinking}` : pick;
467
- for (const id of ids) reviewerModels.push(`${id}=${token}`);
468
- }
469
- }
445
+ if (assignmentChoice === CANCEL) return undefined;
446
+ const assignment = assignmentOptions.find((option) => option.label === assignmentChoice)?.key ?? "pi-default";
447
+
448
+ if (assignment === "per-reviewer") {
449
+ for (const id of ids) {
450
+ if (labels.length === 0) break;
451
+ const pick = await pickModelLabel(ui, models, mode, locale, zh ? `模型 · ${id}` : `Model · ${id}`, false);
452
+ if (pick === CANCEL) return undefined;
453
+ if (pick === undefined) continue; // defensive: allowSkip=false never returns undefined here
454
+ const m = modelByLabel(models, pick);
455
+ const thinkingPick = await mustSelect(
456
+ ui,
457
+ zh ? `思考强度 · ${id}` : `Thinking · ${id}`,
458
+ thinkingOptions(m, locale),
459
+ );
460
+ if (thinkingPick === CANCEL) return undefined;
461
+ const thinking = stripSkip(thinkingPick, locale);
462
+ reviewerModels.push(`${id}=${thinking ? `${pick}:${thinking}` : pick}`);
463
+ }
464
+ } else if (assignment === "shared") {
465
+ if (labels.length === 0) {
466
+ ui.notify(zh ? "当前没有可用模型目录,请先 /rv-models" : "No model catalog available; run /rv-models first", "warning");
470
467
  } else {
471
- for (const id of ids) {
472
- if (labels.length === 0) break;
473
- const pick = await pickModelLabel(ui, models, mode, locale, zh ? `模型 · ${id}` : `Model · ${id}`, false);
474
- if (pick === CANCEL) return undefined;
475
- if (pick === undefined) continue; // defensive: allowSkip=false never returns undefined here
468
+ const pick = await pickModelLabel(ui, models, mode, locale, zh ? "共用模型" : "Shared model", false);
469
+ if (pick === CANCEL) return undefined;
470
+ if (pick !== undefined) {
476
471
  const m = modelByLabel(models, pick);
477
472
  const thinkingPick = await mustSelect(
478
473
  ui,
479
- zh ? `思考强度 · ${id}` : `Thinking · ${id}`,
474
+ zh ? `思考强度(${pick})` : `Thinking (${pick})`,
480
475
  thinkingOptions(m, locale),
481
476
  );
482
477
  if (thinkingPick === CANCEL) return undefined;
483
478
  const thinking = stripSkip(thinkingPick, locale);
484
- reviewerModels.push(`${id}=${thinking ? `${pick}:${thinking}` : pick}`);
479
+ const token = thinking ? `${pick}:${thinking}` : pick;
480
+ for (const id of ids) reviewerModels.push(`${id}=${token}`);
485
481
  }
486
482
  }
487
- } else if (!seed.model && labels.length > 0) {
488
- // Shared default model when not assigning per-reviewer
489
- const pick = await pickModelLabel(ui, models, mode, locale, zh ? "默认模型(全体 reviewer)" : "Default model (all reviewers)", true);
490
- if (pick === CANCEL) return undefined;
491
- if (pick !== undefined) {
492
- const m = modelByLabel(models, pick);
493
- const thinkingPick = await mustSelect(
494
- ui,
495
- zh ? "默认思考强度" : "Default thinking",
496
- thinkingOptions(m, locale),
497
- );
498
- if (thinkingPick === CANCEL) return undefined;
499
- const thinking = stripSkip(thinkingPick, locale);
500
- seed.model = pick;
501
- if (thinking) seed.thinking = thinking;
502
- }
503
483
  }
484
+ // else: Pi default for every reviewer — no reviewerModels, no further prompt.
485
+ // The confirm summary below still shows each reviewer's resolved model
486
+ // (falling back to the shared --model flag, then a "Pi default" placeholder).
504
487
  } else if (!seed.model && labels.length > 0) {
505
488
  // Single-reviewer path
506
489
  const pick = await pickModelLabel(ui, models, mode, locale, zh ? "模型" : "Model", true);
@@ -543,6 +526,23 @@ export async function runRvInteractiveWizard(
543
526
  }
544
527
 
545
528
  // 7) Confirm summary
529
+ // Per-reviewer effective model for the confirm summary: explicit override →
530
+ // shared --model (if set) → "Pi default" placeholder. Mirrors what the CLI
531
+ // actually resolves at runtime (resolveReviewerModelThinking).
532
+ const piDefaultLabel = zh ? "Pi 默认" : "Pi default";
533
+ const reviewerModelOverrides = new Map(
534
+ reviewerModels.map((entry) => {
535
+ const eq = entry.indexOf("=");
536
+ return [entry.slice(0, eq), entry.slice(eq + 1)] as const;
537
+ }),
538
+ );
539
+ const reviewerModelSummary = ids.map((id) => {
540
+ const override = reviewerModelOverrides.get(id);
541
+ if (override) return `${id}=${override}`;
542
+ if (seed.model) return `${id}=${seed.model}${seed.thinking ? `:${seed.thinking}` : ""}`;
543
+ return `${id}=${piDefaultLabel}`;
544
+ });
545
+
546
546
  const summaryLines = [
547
547
  `${zh ? "策略" : "Strategy"}: ${strategy}`,
548
548
  `${zh ? "目标" : "Target"}: ${target}`,
@@ -559,8 +559,11 @@ export async function runRvInteractiveWizard(
559
559
  : zh
560
560
  ? "单 reviewer"
561
561
  : "single reviewer",
562
- seed.model ? `model: ${seed.model}${seed.thinking ? `:${seed.thinking}` : ""}` : "",
563
- reviewerModels.length ? `reviewer-models: ${reviewerModels.join(", ")}` : "",
562
+ // Multi-reviewer paths show one resolved model per reviewer id; single-reviewer
563
+ // and shared-model paths keep the existing single `model:` line.
564
+ ids.length > 0
565
+ ? `${zh ? "reviewer 模型" : "reviewer models"}: ${reviewerModelSummary.join(", ")}`
566
+ : `model: ${seed.model ? `${seed.model}${seed.thinking ? `:${seed.thinking}` : ""}` : piDefaultLabel}`,
564
567
  consensus ? `consensus: ${consensus}${minAgree ? ` min-agree=${minAgree}` : ""}` : "",
565
568
  ].filter(Boolean);
566
569
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zephyrdeng/pi-review",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Isolated AI-powered code and plan reviews from the command line",
5
5
  "type": "module",
6
6
  "author": "ZephyrDeng <zephyrTang@aliyun.com>",
@@ -27,12 +27,12 @@ node ../../bin/pi-review.js --help
27
27
  |------|-------------------------|
28
28
  | **Pi** (`/rv`) | New panel reviews call the native `pi_review` API tool, shown to users as **Pi Review Panel**, which launches the packaged CLI in event mode and renders live reviewer state. Continuations, kept sessions, `/rv-loop`, and explicit `--no-stream` use the shell CLI. |
29
29
  | **Pi terminal** | Foreground `pi-review`; text deltas stream live by default. |
30
- | **Claude Code / Codex / Cursor** and similar agent hosts | These hosts buffer a shell tool's output until the command exits, so a foreground run looks like a silent wait. **Panel review (`--reviewers`/`--panel`) default: `--ui web --ui-url-file <path>` + background run** — hand the user the printed dashboard URL so they watch live progress in a browser instead of the chat. **Single review / `loop` default: `--progress-log <path>` + background run + tail the log** (`--ui web` requires an active panel and rejects `loop`). See [references/codex-tools.md](./references/codex-tools.md). Map skill mentions of `Bash` to your host shell tool. |
30
+ | **Claude Code / Codex / Cursor** and similar agent hosts | These hosts buffer a shell tool's output until the command exits, so a foreground run looks like a silent wait. **Panel review (`--reviewers`/`--panel`) default: `--ui web --ui-url-file <path>` + background run** — get the dashboard in front of the user: on Claude Code the CLI auto-open lands in the system default browser; on Codex open the `--ui-url-file` URL in the host's built-in browser (the CLI cannot reach a GUI there); hand over the URL as fallback either way. **Single review / `loop` default: `--progress-log <path>` + background run + tail the log** (`--ui web` requires an active panel and rejects `loop`). See [references/codex-tools.md](./references/codex-tools.md). Map skill mentions of `Bash` to your host shell tool. |
31
31
  | Scripts / CI | Foreground is fine; add `--no-stream` only when the caller must buffer until exit (cannot combine with `--progress-log`). |
32
32
 
33
33
  `--progress-log <path>` tees the raw `--mode json` event stream to a file for outside observation and debugging; it is never a prerequisite for metrics. Token usage accumulates by default, and `pi-review` writes milestone notices (`pi-review: review started`, `pi-review: tool <name> started/finished`, `pi-review: review finished`) to stderr.
34
34
 
35
- `--ui web` starts a loopback-only browser dashboard for panel review and auto-opens it in the default browser (`--no-ui-open` disables the auto-open); `--ui-url-file <path>` writes its URL atomically so a buffered-output host can read it without parsing stderr. After completion the page counts down 60s, then closes itself and stops the dashboard server (any interaction cancels the countdown). It never substitutes for panel semantics — findings, gate status, and exit code come from the same `PI_REVIEW_META_JSON` as always.
35
+ `--ui web` starts a loopback-only browser dashboard for panel review and auto-opens it in the default browser (`--no-ui-open` disables the auto-open); `--ui-url-file <path>` writes its URL atomically so a buffered-output host can read it without parsing stderr. Open the dashboard as wide as the host allows: Claude Code rides the auto-open (system default browser), Codex opens the url-file URL in its built-in browser, and Pi renders panels natively so `/rv` needs no `--ui web`. After completion the page counts down 60s, then closes itself and stops the dashboard server (any interaction cancels the countdown). It never substitutes for panel semantics — findings, gate status, and exit code come from the same `PI_REVIEW_META_JSON` as always.
36
36
 
37
37
  ## Run metrics
38
38
 
@@ -40,7 +40,7 @@ Quote metrics from the run output; never estimate or invent them.
40
40
 
41
41
  - **ASCII footer** (the `── pi-review` / `── pi-review panel` block): Duration, Tokens as `in · out · cache · reason` plus total, and Cost — the provider-reported value, or `n/a` when the provider reports none. On Pi `/rv` the panel tool result is **plain text / ASCII only** (Pi does not render markdown in tool results) — same classic chrome as the CLI, no `###` headings or fenced code blocks.
42
42
  - **Pi Review Panel**: each reviewer row shows **role persona · lifecycle · model · thinking · elapsed · tokens**. Lifecycle is `queued` / `running` / `completed` / `failed` / `cancelled`. Expanded view adds bounded activity, plain-text findings, and the ASCII footer.
43
- - Machine output: parse `PI_REVIEW_META_JSON:` from **stderr** (or set `PI_REVIEW_META_STDOUT=1` to emit it on stdout). Show users the plain review text and ASCII footer, not raw JSON, unless they ask for machine output. Do **not** rewrap the tool result into markdown tables or `###` report sections.
43
+ - Machine output: parse `PI_REVIEW_META_JSON:` from **stderr** (or set `PI_REVIEW_META_STDOUT=1` to emit it on stdout). Show users the plain review text and ASCII footer, not raw JSON, unless they ask for machine output. Do **not** rewrap the tool result into markdown tables or `###` report sections. On panel runs the top-level `model` is the reviewers' shared effective model (configured, else provider-reported) or the literal sentinel `"mixed"` when reviewers ran on different models; per-reviewer entries carry their own `model`/`responseModel`.
44
44
 
45
45
  ## Pi host (`/rv*`)
46
46
 
@@ -185,11 +185,12 @@ pi-review --reviewers 3 --ui web --ui-url-file /tmp/pi-review-ui-url.txt -- @src
185
185
  ```
186
186
  Rules:
187
187
  - Run this before building the review command. **Never invent a `provider/model` id** — only ids from the catalog output.
188
- - Follow **[references/model-selection.md](./references/model-selection.md)** for everything else: profile inference (code / frontend / plan), priority lists, user-named-model resolution, and thinking aliases (`max`/`最高` → `xhigh`, with fallback to the nearest supported level).
188
+ - **Cross-model review**: identify the model this host session itself runs on (its id is in your session context), then prefer reviewer models from a different model family — same-family reviewers share the author model's blind spots. On a panel, keep the host's family to a minority of reviewers.
189
+ - Follow **[references/model-selection.md](./references/model-selection.md)** for everything else: profile inference (code / frontend / plan), priority lists, cross-model resolution, user-named-model resolution, and thinking aliases (`max`/`最高` → `xhigh`, with fallback to the nearest supported level).
189
190
  - Ambiguous match across model families: show the top candidates and ask; do not guess.
190
- - No priority match: omit `--model` (Pi default) or pick a listed reasoning model with a large context window — say which and why.
191
+ - User named no model: propose 2–3 exact catalog ids (cross-model first, one line of why each, "Pi default" as an explicit option) and ask the user to pick before running.
191
192
  - On **Claude Code / Codex / Cursor**, state the chosen model in one line (match the user's language) before running the review command.
192
- Completion criterion: exact listed `provider/model[:thinking]` or an explicit default; never invented ids.
193
+ Completion criterion: an exact listed `provider/model[:thinking]` (or Pi default) that the user named or picked from your proposal; never invented ids.
193
194
 
194
195
  2. Choose one mode:
195
196
  - default `code` — code, diff, MR, file, or repository review.
@@ -8,7 +8,7 @@ Skills may use Claude Code tool names. On **Codex** or **Claude Code**, map them
8
8
  | `Read`, `Write`, `Edit` | Native file tools |
9
9
  | `Skill` | Load and follow the skill instructions directly |
10
10
 
11
- General Codex subagent and worktree mappings live in the **using-superpowers** skill (`references/codex-tools.md`) when that skill is installed; this file is **pi-review–specific** and ships with the pi-review package.
11
+ This file is **pi-review–specific** and ships with the pi-review package.
12
12
 
13
13
  ## Long-running `pi-review` (default on Codex / Claude Code)
14
14
 
@@ -18,8 +18,8 @@ Native shell tools on these hosts **buffer stdout/stderr until the command exits
18
18
 
19
19
  `--reviewers <n>` or `--panel <name>` activates panel review. On Codex, Claude Code, or Cursor, default to the dashboard instead of a tailed log:
20
20
 
21
- 1. Run `pi-review --reviewers <n> --ui web --ui-url-file <path> -- <target>` (or `--panel <name>`) in the **background**. The dashboard auto-opens in the user's default browser once the server is ready; pass `--no-ui-open` only when the user asked not to open a browser (CI, remote shells).
22
- 2. Read `<path>` shortly after launch — it is written atomically as soon as the dashboard server is ready, well before reviewers finish (poll a few times a second for a couple of seconds if the file is not there yet). Share that URL with the user as a fallback: *"Dashboard opened in your browser (URL: `<url>`). I'll report back when the review completes."*
21
+ 1. Run `pi-review --reviewers <n> --ui web --ui-url-file <path> -- <target>` (or `--panel <name>`) in the **background**. Pass `--no-ui-open` only when the user asked not to open a browser (CI, remote shells).
22
+ 2. Read `<path>` shortly after launch — it is written atomically as soon as the dashboard server is ready, well before reviewers finish (poll a few times a second for a couple of seconds if the file is not there yet). Then get the dashboard in front of the user: on **Claude Code** the CLI auto-open already lands in the system default browser; on **Codex** the CLI cannot reach a GUI browser, so open the URL from `<path>` in the host's built-in browser yourself. Share the URL either way as a fallback: *"Dashboard opened in your browser (URL: `<url>`). I'll report back when the review completes."*
23
23
  3. Wait for the background job. On exit, show the Markdown review body and ASCII `── pi-review` footer as usual (skill step 4) — the dashboard is a presentation layer only; findings, gate status, and exit code always come from `PI_REVIEW_META_JSON`.
24
24
  4. `--ui web` requires an active panel and rejects `loop` — for a single review or a panel `loop`, fall back to the progress-log workflow below.
25
25
 
@@ -6,11 +6,11 @@ Use this after `pi-review models` (or `pi-review models <search>`). **Never inve
6
6
 
7
7
  1. Run `pi-review models` (narrow with search if the user named a vendor or model family).
8
8
  2. Infer **review profile** from the target (see table below).
9
- 3. Walk the **priority list** for that profile; pick the **first** row whose `idContains` matches some listed model id (case-insensitive substring). If several ids match one row, prefer the **newest** id (higher version suffix, e.g. `2.7` over `2.5`, `4-8` over `4-6`).
9
+ 3. **Cross-model review**: identify the host session's own model — the model that wrote or will fix the code under review; its id is in your own session context. Walk the **priority list** for that profile skipping rows in the host's model family, and pick the **first** remaining row whose `idContains` matches some listed model id (case-insensitive substring). Same-family reviewers share the author model's blind spots; return to the host's own family only when no other family matches the catalog. On a panel, spread reviewer models so the host's family stays a minority. If several ids match one row, prefer the **newest** id (higher version suffix, e.g. `2.7` over `2.5`, `4-8` over `4-6`).
10
10
  4. Set `--model <exact-listed-provider/model>` from the catalog. Add `:thinking` only if that model supports it in Pi and the preset suggests it (see thinking column).
11
11
  - User “max / 最高档” maps to Pi thinking `xhigh` when supported; otherwise fall back to the highest available level.
12
12
  - User “low / off / medium / high” maps to that exact level when supported — **do not upgrade** a user-specified low/off to high/xhigh.
13
- 5. If **no** priority row matches anything in the catalog, either omit `--model` (Pi default) or pick the best **reasoning** model with a large context window from the list—state the choice in one line to the user.
13
+ 5. When the user named no model, the walk above yields a **proposal, not a decision**: present 2–3 exact catalog ids (cross-model candidates first, one line of why each, plus “Pi default” as an explicit option) and ask the user to pick before running the review. A user-named model skips the ask and resolves per **User-named model** below. If **no** priority row matches anything in the catalog, propose the best **reasoning** models with a large context window from the list the same way.
14
14
 
15
15
  ## Profiles
16
16