@zephyrdeng/pi-review 0.5.1 → 0.6.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.
@@ -0,0 +1,138 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { ModelInfo } from "./rv-completions.js";
5
+
6
+ export type PresetModelEntry = {
7
+ idContains: string;
8
+ thinking?: string;
9
+ versionPrefer?: string;
10
+ };
11
+
12
+ export type ReviewModelPriorities = {
13
+ code: PresetModelEntry[];
14
+ frontend: PresetModelEntry[];
15
+ plan: PresetModelEntry[];
16
+ };
17
+
18
+ export const DEFAULT_REVIEW_MODEL_PRIORITIES: ReviewModelPriorities = {
19
+ code: [
20
+ { idContains: "gpt-5.5", thinking: "xhigh" },
21
+ { idContains: "glm-5.2", thinking: "high" },
22
+ ],
23
+ frontend: [
24
+ { idContains: "kimi", versionPrefer: "2.7" },
25
+ { idContains: "claude-sonnet", versionPrefer: "5" },
26
+ { idContains: "minimax-m3" },
27
+ ],
28
+ plan: [
29
+ { idContains: "claude-opus-4-8" },
30
+ { idContains: "claude-opus-4", versionPrefer: "8" },
31
+ { idContains: "deepseek-v4-pro" },
32
+ { idContains: "deepseek-v4" },
33
+ ],
34
+ };
35
+
36
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
37
+ const DEFAULT_PRIORITIES_PATH = path.resolve(__dirname, "../resources/rv-model-priorities.json");
38
+
39
+ export function loadReviewModelPriorities(
40
+ filePath: string = process.env.PI_REVIEW_RV_PRIORITIES || DEFAULT_PRIORITIES_PATH,
41
+ ): ReviewModelPriorities {
42
+ try {
43
+ const raw = fs.readFileSync(filePath, "utf8");
44
+ const parsed = JSON.parse(raw) as Partial<ReviewModelPriorities>;
45
+ return {
46
+ code: parsed.code?.length ? parsed.code : DEFAULT_REVIEW_MODEL_PRIORITIES.code,
47
+ frontend: parsed.frontend?.length ? parsed.frontend : DEFAULT_REVIEW_MODEL_PRIORITIES.frontend,
48
+ plan: parsed.plan?.length ? parsed.plan : DEFAULT_REVIEW_MODEL_PRIORITIES.plan,
49
+ };
50
+ } catch {
51
+ return DEFAULT_REVIEW_MODEL_PRIORITIES;
52
+ }
53
+ }
54
+
55
+ export type ReviewProfile = "code" | "frontend" | "plan";
56
+
57
+ const FRONTEND_EXTS = new Set([
58
+ "vue", "svelte", "css", "scss", "sass", "less", "styl", "html", "htm",
59
+ ]);
60
+
61
+ export function resolveReviewProfile(mode: string, targetExt?: string): ReviewProfile {
62
+ if (mode === "plan" || mode === "challenge") return "plan";
63
+ if (targetExt && FRONTEND_EXTS.has(targetExt)) return "frontend";
64
+ return "code";
65
+ }
66
+
67
+ function idVersionSortKey(id: string): string {
68
+ return id.toLowerCase();
69
+ }
70
+
71
+ export function matchPresetEntry(
72
+ models: ModelInfo[],
73
+ entry: PresetModelEntry,
74
+ ): ModelInfo | undefined {
75
+ const needle = entry.idContains.toLowerCase();
76
+ let candidates = models.filter((m) => m.id.toLowerCase().includes(needle));
77
+ if (entry.versionPrefer) {
78
+ const vp = entry.versionPrefer.toLowerCase();
79
+ const withVersion = candidates.filter((m) => m.id.toLowerCase().includes(vp));
80
+ if (withVersion.length) candidates = withVersion;
81
+ }
82
+ if (!candidates.length) return undefined;
83
+ candidates.sort((a, b) =>
84
+ idVersionSortKey(b.id).localeCompare(idVersionSortKey(a.id), undefined, { numeric: true }),
85
+ );
86
+ return candidates[0];
87
+ }
88
+
89
+ export type ResolvedPresetModel = {
90
+ model: ModelInfo;
91
+ thinking?: string;
92
+ presetRank: number;
93
+ presetLabel: string;
94
+ };
95
+
96
+ export function resolvePresetOrderedModels(
97
+ models: ModelInfo[],
98
+ profile: ReviewProfile,
99
+ priorities: ReviewModelPriorities,
100
+ ): ResolvedPresetModel[] {
101
+ const rows = priorities[profile];
102
+ const seen = new Set<string>();
103
+ const out: ResolvedPresetModel[] = [];
104
+ for (let i = 0; i < rows.length; i++) {
105
+ const entry = rows[i];
106
+ const matched = matchPresetEntry(models, entry);
107
+ if (!matched || seen.has(matched.label)) continue;
108
+ seen.add(matched.label);
109
+ out.push({
110
+ model: matched,
111
+ thinking: entry.thinking,
112
+ presetRank: i,
113
+ presetLabel: entry.idContains,
114
+ });
115
+ }
116
+ return out;
117
+ }
118
+
119
+ export function rankModelsWithPresets(
120
+ models: ModelInfo[],
121
+ profile: ReviewProfile,
122
+ priorities: ReviewModelPriorities,
123
+ ): ModelInfo[] {
124
+ const preset = resolvePresetOrderedModels(models, profile, priorities);
125
+ const presetLabels = new Set(preset.map((p) => p.model.label));
126
+ const rest = models
127
+ .filter((m) => !presetLabels.has(m.label))
128
+ .sort((a, b) => a.label.localeCompare(b.label));
129
+ return [...preset.map((p) => p.model), ...rest];
130
+ }
131
+
132
+ export function primaryPresetForProfile(
133
+ models: ModelInfo[],
134
+ profile: ReviewProfile,
135
+ priorities: ReviewModelPriorities,
136
+ ): ResolvedPresetModel | undefined {
137
+ return resolvePresetOrderedModels(models, profile, priorities)[0];
138
+ }
@@ -3,6 +3,7 @@ import { describe, it } from "node:test";
3
3
  import {
4
4
  buildPiReviewArgv,
5
5
  buildRvOrchestrationPrompt,
6
+ orchestrationLocaleNote,
6
7
  parseRvArgs,
7
8
  validateRvParsed,
8
9
  } from "./rv-prompts.js";
@@ -42,6 +43,29 @@ describe("parseRvArgs / validateRvParsed", () => {
42
43
  ]);
43
44
  });
44
45
 
46
+ it("parses --thinking and threads it into CLI argv", () => {
47
+ const p = parseRvArgs("--thinking xhigh --model openai/gpt-5.5 @x.md");
48
+ assert.equal(p.thinking, "xhigh");
49
+ assert.equal(p.model, "openai/gpt-5.5");
50
+ assert.deepEqual(buildPiReviewArgv(p, "@x.md"), [
51
+ "pi-review",
52
+ "--model",
53
+ "openai/gpt-5.5",
54
+ "--thinking",
55
+ "xhigh",
56
+ "--",
57
+ "@x.md",
58
+ ]);
59
+ });
60
+
61
+ it("models orchestration includes locale note and model-selection reference", () => {
62
+ const p = parseRvArgs("models");
63
+ const zh = buildRvOrchestrationPrompt(p, "zh");
64
+ assert.match(zh, /用中文向用户总结/);
65
+ assert.match(zh, /model-selection/);
66
+ assert.match(orchestrationLocaleNote("en"), /English/);
67
+ });
68
+
45
69
  it("orchestration prompt forbids default no-stream and progress-log in Pi", () => {
46
70
  const p = parseRvArgs("@src/foo.ts");
47
71
  const prompt = buildRvOrchestrationPrompt(p);
@@ -1,13 +1,17 @@
1
1
  /** /rv → parent-agent task text; child review uses resources/review-presets.json */
2
2
 
3
+ import type { RvLocale } from "./rv-locale.js";
4
+ import { mergeSemanticIntoParsed, stripSemanticPhrases } from "./rv-semantic.js";
5
+
3
6
  export const RV_MODES = ["code", "plan", "challenge"] as const;
4
7
  export type RvMode = (typeof RV_MODES)[number];
5
8
 
6
- const FLAGS_REQUIRING_VALUE = ["--mode", "--model", "--continue"] as const;
9
+ const FLAGS_REQUIRING_VALUE = ["--mode", "--model", "--thinking", "--continue"] as const;
7
10
 
8
11
  export type RvParsed = {
9
12
  mode: string;
10
13
  model?: string;
14
+ thinking?: string;
11
15
  keepSession: boolean;
12
16
  noStream: boolean;
13
17
  continueHandle?: string;
@@ -20,11 +24,13 @@ export type RvValidation =
20
24
  | { ok: false; message: string };
21
25
 
22
26
  export function parseRvArgs(raw: string): RvParsed {
23
- const tokens = raw.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
27
+ const { remainder, apply: semanticApply } = stripSemanticPhrases(raw);
28
+ const tokens = remainder.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
24
29
  const cleaned = tokens.map((t) => t.replace(/^['"]|['"]$/g, ""));
25
30
 
26
31
  let mode = "code";
27
32
  let model: string | undefined;
33
+ let thinking: string | undefined;
28
34
  let keepSession = false;
29
35
  let noStream = false;
30
36
  let continueHandle: string | undefined;
@@ -63,6 +69,15 @@ export function parseRvArgs(raw: string): RvParsed {
63
69
  model = cleaned[++i];
64
70
  continue;
65
71
  }
72
+ if (t === "--thinking") {
73
+ const v = cleaned[i + 1];
74
+ if (!v || v.startsWith("--")) {
75
+ rest.push(t);
76
+ continue;
77
+ }
78
+ thinking = cleaned[++i];
79
+ continue;
80
+ }
66
81
  if (t === "--continue") {
67
82
  const v = cleaned[i + 1];
68
83
  if (!v || v.startsWith("--")) {
@@ -75,15 +90,19 @@ export function parseRvArgs(raw: string): RvParsed {
75
90
  rest.push(t);
76
91
  }
77
92
 
78
- return {
79
- mode,
80
- model,
81
- keepSession,
82
- noStream,
83
- continueHandle,
84
- modelsOnly,
85
- target: rest.join(" ").trim(),
86
- };
93
+ return mergeSemanticIntoParsed(
94
+ {
95
+ mode,
96
+ model,
97
+ thinking,
98
+ keepSession,
99
+ noStream,
100
+ continueHandle,
101
+ modelsOnly,
102
+ target: rest.join(" ").trim(),
103
+ },
104
+ semanticApply,
105
+ );
87
106
  }
88
107
 
89
108
  export function validateRvParsed(parsed: RvParsed): RvValidation {
@@ -108,11 +127,19 @@ export function buildPiReviewArgv(parsed: RvParsed, target: string): string[] {
108
127
  }
109
128
  if (parsed.mode !== "code") parts.push("--mode", parsed.mode);
110
129
  if (parsed.model) parts.push("--model", parsed.model);
130
+ if (parsed.thinking) parts.push("--thinking", parsed.thinking);
111
131
  if (parsed.noStream) parts.push("--no-stream");
112
132
  parts.push("--", target);
113
133
  return parts;
114
134
  }
115
135
 
136
+ export function orchestrationLocaleNote(locale: RvLocale = "en"): string {
137
+ if (locale === "zh") {
138
+ return "用中文向用户总结(模型目录、选型理由、review 结论);技术 id 如 provider/model 可保留英文。";
139
+ }
140
+ return "Summarize for the user in English (catalog, model rationale, review conclusion); keep technical ids such as provider/model as listed.";
141
+ }
142
+
116
143
  function piHostRules(): string {
117
144
  return [
118
145
  "Follow the pi-review skill.",
@@ -124,13 +151,15 @@ function piHostRules(): string {
124
151
  ].join(" ");
125
152
  }
126
153
 
127
- export function buildRvOrchestrationPrompt(parsed: RvParsed): string {
154
+ export function buildRvOrchestrationPrompt(parsed: RvParsed, locale: RvLocale = "en"): string {
155
+ const localeNote = orchestrationLocaleNote(locale);
128
156
  if (parsed.modelsOnly) {
129
157
  return [
130
158
  "Run the pi-review model catalog step.",
131
159
  piHostRules(),
160
+ localeNote,
132
161
  "Execute: pi-review models",
133
- "Summarize briefly: provider count and 2–3 model IDs that fit code review vs plan/challenge.",
162
+ "Summarize briefly: provider count and 2–3 model IDs that fit code vs frontend vs plan/challenge (see pi-review skill references/model-selection.md).",
134
163
  "Do not start a review until the user supplies a target (@path or text).",
135
164
  ].join("\n\n");
136
165
  }
@@ -168,8 +197,10 @@ export function buildRvOrchestrationPrompt(parsed: RvParsed): string {
168
197
  const cliLine = buildPiReviewArgv(parsed, target).join(" ");
169
198
 
170
199
  const modelStep = parsed.model
171
- ? `Use model exactly as given: ${parsed.model} (must appear in pi-review models output).`
172
- : "Run pi-review models first if needed; pick a suitable listed ID or omit --model for Pi default.";
200
+ ? `Use model exactly as given: ${parsed.model}${parsed.thinking ? ` at thinking ${parsed.thinking}` : ""} (model must appear in pi-review models output).`
201
+ : parsed.thinking
202
+ ? `Run pi-review models first if needed; pick using skill references/model-selection.md (code / frontend / plan) or omit --model for Pi default. Use thinking ${parsed.thinking}.`
203
+ : "Run pi-review models first if needed; pick using skill references/model-selection.md (code / frontend / plan) or omit --model for Pi default.";
173
204
 
174
205
  const continueNote = parsed.continueHandle
175
206
  ? "Continuing an existing review session. Optional --mode and --model match the same flags as an initial /rv run."
@@ -180,6 +211,7 @@ export function buildRvOrchestrationPrompt(parsed: RvParsed): string {
180
211
  return [
181
212
  parsed.continueHandle ? "Continue a prior pi-review session." : "Run pi-review for the target below.",
182
213
  piHostRules(),
214
+ localeNote,
183
215
  modeBlock[parsed.mode] ??
184
216
  `Mode: ${parsed.mode}. Follow the preset named in review-presets.json when present.`,
185
217
  modelStep,
@@ -197,6 +229,7 @@ export const RV_COMPLETIONS: { value: string; hint: string }[] = [
197
229
  { value: "--mode plan", hint: "architecture or plan review" },
198
230
  { value: "--mode challenge", hint: "adversarial plan review" },
199
231
  { value: "--model ", hint: "provider/model from pi-review models" },
232
+ { value: "--thinking ", hint: "off|minimal|low|medium|high|xhigh" },
200
233
  { value: "--keep-session", hint: "persist session for follow-up" },
201
234
  { value: "--no-stream", hint: "rare: buffer until exit (not default in Pi)" },
202
235
  { value: "--continue ", hint: "resume session; same optional flags as initial /rv" },
@@ -0,0 +1,23 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { parseRvArgs } from "./rv-prompts.js";
4
+ import { stripSemanticPhrases } from "./rv-semantic.js";
5
+
6
+ describe("stripSemanticPhrases / parseRvArgs", () => {
7
+ it("maps 方案审核 to plan mode", () => {
8
+ const p = parseRvArgs("方案审核 @docs/plan.md");
9
+ assert.equal(p.mode, "plan");
10
+ assert.equal(p.target, "@docs/plan.md");
11
+ });
12
+
13
+ it("maps list models phrase to modelsOnly", () => {
14
+ const p = parseRvArgs("查看模型列表");
15
+ assert.equal(p.modelsOnly, true);
16
+ });
17
+
18
+ it("strips code review before flag parse", () => {
19
+ const { remainder, apply } = stripSemanticPhrases("code review @src/a.ts");
20
+ assert.equal(apply.mode, "code");
21
+ assert.match(remainder, /@src\/a\.ts/);
22
+ });
23
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Semantic /rv phrases (localized labels, not raw CLI flags in the input box).
3
+ */
4
+
5
+ import type { RvParsed } from "./rv-prompts.js";
6
+ import type { RvLocale } from "./rv-locale.js";
7
+ import { rvUi } from "./rv-locale.js";
8
+
9
+ export type SemanticApply = {
10
+ mode?: string;
11
+ keepSession?: boolean;
12
+ modelsOnly?: boolean;
13
+ noStream?: boolean;
14
+ };
15
+
16
+ export type SemanticPhrase = {
17
+ phrases: string[];
18
+ apply: SemanticApply;
19
+ completionValue: (locale: RvLocale) => string;
20
+ completionDesc?: (locale: RvLocale) => string;
21
+ };
22
+
23
+ export const SEMANTIC_PHRASES: SemanticPhrase[] = [
24
+ {
25
+ phrases: ["code review", "代码审核", "代码审查", "审代码"],
26
+ apply: { mode: "code" },
27
+ completionValue: (l) => rvUi(l).modeCode,
28
+ completionDesc: (l) => (l === "zh" ? "聚焦 diff / 正确性 / 测试" : "Diff, correctness, tests"),
29
+ },
30
+ {
31
+ phrases: ["plan review", "方案审核", "方案评审", "架构审核", "审方案"],
32
+ apply: { mode: "plan" },
33
+ completionValue: (l) => rvUi(l).modePlan,
34
+ completionDesc: (l) => (l === "zh" ? "架构 / 设计 / 产品方案" : "Architecture and design"),
35
+ },
36
+ {
37
+ phrases: ["challenge review", "对抗性审核", "对抗审核", "挑战审核"],
38
+ apply: { mode: "challenge" },
39
+ completionValue: (l) => rvUi(l).modeChallenge,
40
+ completionDesc: (l) => (l === "zh" ? "压力测试假设与风险" : "Adversarial plan pressure-test"),
41
+ },
42
+ {
43
+ phrases: ["keep session for follow-up", "保留会话可追问", "保留会话", "keep session"],
44
+ apply: { keepSession: true },
45
+ completionValue: (l) => rvUi(l).keepSession,
46
+ completionDesc: (l) => rvUi(l).keepSessionDesc,
47
+ },
48
+ {
49
+ phrases: ["list models", "查看模型列表", "模型列表", "list available models"],
50
+ apply: { modelsOnly: true },
51
+ completionValue: (l) => rvUi(l).listModels,
52
+ completionDesc: (l) => rvUi(l).listModelsDesc,
53
+ },
54
+ ];
55
+
56
+ export function stripSemanticPhrases(raw: string): { remainder: string; apply: SemanticApply } {
57
+ let remainder = raw;
58
+ const merged: SemanticApply = {};
59
+ const sorted = [...SEMANTIC_PHRASES].flatMap((row) =>
60
+ row.phrases.map((p) => ({ phrase: p, apply: row.apply })),
61
+ );
62
+ sorted.sort((a, b) => b.phrase.length - a.phrase.length);
63
+
64
+ for (const { phrase, apply } of sorted) {
65
+ const re = new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
66
+ if (!re.test(remainder)) continue;
67
+ remainder = remainder.replace(re, " ").replace(/\s+/g, " ").trim();
68
+ if (apply.mode) merged.mode = apply.mode;
69
+ if (apply.keepSession) merged.keepSession = true;
70
+ if (apply.modelsOnly) merged.modelsOnly = true;
71
+ if (apply.noStream) merged.noStream = true;
72
+ }
73
+ return { remainder, apply: merged };
74
+ }
75
+
76
+ export function mergeSemanticIntoParsed(parsed: RvParsed, apply: SemanticApply): RvParsed {
77
+ const out = { ...parsed };
78
+ if (apply.mode) out.mode = apply.mode;
79
+ if (apply.keepSession) out.keepSession = true;
80
+ if (apply.modelsOnly) out.modelsOnly = true;
81
+ if (apply.noStream) out.noStream = true;
82
+ return out;
83
+ }
84
+
85
+ export function semanticCompletionItems(
86
+ locale: RvLocale,
87
+ tail: string,
88
+ headPrefix: string,
89
+ head: string[],
90
+ ): { value: string; label: string; description?: string }[] {
91
+ const present = new Set(head.map((h) => h.toLowerCase()));
92
+ const q = tail.toLowerCase().trim();
93
+ const items: { value: string; label: string; description?: string }[] = [];
94
+ for (const row of SEMANTIC_PHRASES) {
95
+ const value = row.completionValue(locale);
96
+ if (present.has(value.toLowerCase())) continue;
97
+ const matches =
98
+ !q ||
99
+ value.toLowerCase().includes(q) ||
100
+ value.toLowerCase().startsWith(q) ||
101
+ row.phrases.some((p) => p.toLowerCase().startsWith(q) || p.toLowerCase().includes(q));
102
+ if (!matches) continue;
103
+ items.push({
104
+ value: `${headPrefix}${value}`,
105
+ label: value,
106
+ description: row.completionDesc?.(locale),
107
+ });
108
+ }
109
+ return items;
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zephyrdeng/pi-review",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
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>",
@@ -67,8 +67,8 @@
67
67
  "build": "tsc",
68
68
  "check": "tsc --noEmit",
69
69
  "smoke": "node dist/cli.js --help >/dev/null",
70
- "test": "npx --yes tsx --test src/child-process.test.ts src/review.meta.test.ts src/meta-footer.test.ts src/json-events.test.ts src/install-args.test.ts extensions/rv-prompts.test.ts",
71
- "test:rv": "npx --yes tsx --test extensions/rv-prompts.test.ts",
70
+ "test": "npx --yes tsx --test src/child-process.test.ts src/review.meta.test.ts src/meta-footer.test.ts src/json-events.test.ts src/install-args.test.ts extensions/rv-prompts.test.ts extensions/rv-completions.test.ts extensions/rv-model-priorities.test.ts extensions/rv-locale.test.ts extensions/rv-semantic.test.ts",
71
+ "test:rv": "npx --yes tsx --test extensions/rv-prompts.test.ts extensions/rv-completions.test.ts extensions/rv-model-priorities.test.ts extensions/rv-locale.test.ts extensions/rv-semantic.test.ts",
72
72
  "prepublishOnly": "npm run build",
73
73
  "pack:dry": "npm pack --dry-run"
74
74
  },
@@ -0,0 +1,17 @@
1
+ {
2
+ "code": [
3
+ { "idContains": "gpt-5.5", "thinking": "xhigh" },
4
+ { "idContains": "glm-5.2", "thinking": "high" }
5
+ ],
6
+ "frontend": [
7
+ { "idContains": "kimi", "versionPrefer": "2.7" },
8
+ { "idContains": "claude-sonnet", "versionPrefer": "5" },
9
+ { "idContains": "minimax-m3" }
10
+ ],
11
+ "plan": [
12
+ { "idContains": "claude-opus-4-8" },
13
+ { "idContains": "claude-opus-4", "versionPrefer": "8" },
14
+ { "idContains": "deepseek-v4-pro" },
15
+ { "idContains": "deepseek-v4" }
16
+ ]
17
+ }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pi-review
3
- description: Use pi-review to delegate isolated code, diff, repository, architecture, or plan reviews to a fresh Pi session and return only the review conclusion. On Claude Code or Codex, default to --progress-log with a background shell run and tail the log (chat tools buffer stdout). Use when the user asks for Pi review, isolated review, code review, plan review, challenge review, review status/progress, or wants to avoid context pollution.
3
+ description: Use pi-review to delegate isolated code, diff, repository, architecture, or plan reviews to a fresh Pi session and return only the review conclusion. Model selection presets for code, frontend, and plan review (see references/model-selection.md). On Claude Code or Codex, default to --progress-log with a background shell run and tail the log (chat tools buffer stdout). Use when the user asks for Pi review, isolated review, code review, plan review, challenge review, review status/progress, or wants to avoid context pollution.
4
4
  ---
5
5
 
6
6
  # Pi Review
@@ -26,7 +26,8 @@ When resolving this fallback, use the actual directory that contains this `SKILL
26
26
  ## Codex / Claude Code
27
27
 
28
28
  - Map skill mentions of `Bash` to your host shell tool.
29
- - Read **[references/codex-tools.md](./references/codex-tools.md)** (next to this `SKILL.md`) for Codex/Claude Code defaults: `--progress-log` + background run + tail.
29
+ - Read **[references/codex-tools.md](./references/codex-tools.md)** for `--progress-log` + background run + tail.
30
+ - Read **[references/model-selection.md](./references/model-selection.md)** when choosing `--model` after `pi-review models` (code / frontend / plan presets).
30
31
  - Do not use a blocking foreground shell alone for live progress; follow **Default workflow by host** below.
31
32
 
32
33
  ## Default workflow by host
@@ -79,15 +80,17 @@ Do not implement findings from a status check; status is informational only.
79
80
 
80
81
  ## Steps
81
82
 
82
- 1. Prime the model catalog:
83
+ 1. Prime the model catalog and pick a model:
83
84
  ```bash
84
85
  pi-review models [search]
85
86
  ```
86
87
  Rules:
87
88
  - Run this before building the review command.
88
- - If the user named a model, use that provider/model text as `[search]` and verify the exact ID from the output.
89
- - If the user did not name a model, run `pi-review models` and either choose an exact suitable ID from the output or deliberately omit `--model` to use Pi's default.
90
- Completion criterion: the model choice is either an exact listed `provider/model[:thinking]` or an explicit decision to use Pi's default.
89
+ - If the user named a model, use that text as `[search]` and verify the **exact** listed `provider/model` from the output.
90
+ - If the user did not name a model, follow **[references/model-selection.md](./references/model-selection.md)**: infer profile (code / frontend / plan), match the priority list against catalog ids, then set `--model` with optional `:thinking` when supported.
91
+ - On **Claude Code / Codex / Cursor**, state the chosen model in one line (match the user's language) before running the review command.
92
+ - If nothing in the priority list matches, omit `--model` (Pi default) or pick a listed reasoning model with large context—say which and why.
93
+ Completion criterion: exact listed `provider/model[:thinking]` or an explicit default; never invented ids.
91
94
 
92
95
  2. Choose one mode:
93
96
  - default `code` — code, diff, MR, file, or repository review.
@@ -25,6 +25,8 @@ When the **pi-review** skill applies on Codex, Claude Code, or Cursor:
25
25
 
26
26
  See the parent skill: sections *Default workflow by host* and *Streaming vs agent hosts (Claude Code / Codex)*.
27
27
 
28
+ **Model choice:** after `pi-review models`, use **[model-selection.md](./model-selection.md)** (code / frontend / plan presets)—same guidance as Pi `/rv` completions.
29
+
28
30
  Example tail (milestone events only):
29
31
 
30
32
  ```bash
@@ -0,0 +1,72 @@
1
+ # Model selection for pi-review (all hosts)
2
+
3
+ Use this after `pi-review models` (or `pi-review models <search>`). **Never invent** `provider/model` IDs—only IDs that appear in that output.
4
+
5
+ ## Workflow (Claude Code, Codex, Cursor, Pi parent agent)
6
+
7
+ 1. Run `pi-review models` (narrow with search if the user named a vendor or model family).
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`).
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
+ 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.
12
+
13
+ ## Profiles
14
+
15
+ | Profile | When to use |
16
+ |---------|-------------|
17
+ | **code** | Default: diffs, MRs, backend/ general code paths (`@src/...`, `.ts`, `.go`, …). |
18
+ | **frontend** | UI-heavy targets: `.vue`, `.svelte`, `.css`, `.scss`, `.html`, or user says frontend / UI / multimodal. |
19
+ | **plan** | `--mode plan` or `--mode challenge`, or reviewing `.md` / design / architecture docs (not line-by-line code only). |
20
+
21
+ ## Priority lists (same as Pi `/rv` package preset)
22
+
23
+ Configurable on disk for Pi: `resources/rv-model-priorities.json` or `PI_REVIEW_RV_PRIORITIES`. Agents should follow this table unless the user overrides.
24
+
25
+ ### Code review
26
+
27
+ | Order | Match model id containing | Suggested thinking |
28
+ |-------|---------------------------|------------------|
29
+ | 1 | `gpt-5.5` | `xhigh` |
30
+ | 2 | `glm-5.2` | `high` |
31
+
32
+ ### Frontend / multimodal review
33
+
34
+ | Order | Match model id containing | Notes |
35
+ |-------|---------------------------|--------|
36
+ | 1 | `kimi` | Prefer id that also contains `2.7` when multiple Kimi ids exist |
37
+ | 2 | `claude-sonnet` | Prefer id containing `5` when multiple |
38
+ | 3 | `minimax-m3` | Multimodal-friendly |
39
+
40
+ Use **code** mode (`pi-review` default) unless the user asked for plan/challenge.
41
+
42
+ ### Plan / challenge review
43
+
44
+ | Order | Match model id containing |
45
+ |-------|---------------------------|
46
+ | 1 | `claude-opus-4-8` |
47
+ | 2 | `claude-opus-4` (+ prefer `8` in id if several) |
48
+ | 3 | `deepseek-v4-pro` |
49
+ | 4 | `deepseek-v4` |
50
+
51
+ Use `--mode plan` or `--mode challenge` as appropriate.
52
+
53
+ ## Examples (illustrative—always substitute exact catalog strings)
54
+
55
+ ```bash
56
+ # Code
57
+ pi-review --model openai-codex/gpt-5.5:xhigh -- @src/auth.ts
58
+
59
+ # Frontend
60
+ pi-review --model wafer.ai/Kimi-K2.6 -- @src/App.vue
61
+
62
+ # Plan
63
+ pi-review --mode plan --model px:anthropic/claude-opus-4-8 -- @docs/architecture.md
64
+ ```
65
+
66
+ ## User-named model
67
+
68
+ If the user specifies a model, run `pi-review models <fragment>` and use the **exact** listed id. Do not normalize or guess provider prefixes.
69
+
70
+ ## Language
71
+
72
+ Explain your model choice briefly in the **same language** as the user's request (Chinese ↔ English). The review body language is independent; this rule is only for the one-line rationale before/after invoking `pi-review`.