@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.
- package/README.md +10 -1
- package/extensions/review.ts +95 -3
- package/extensions/rv-completions.test.ts +218 -0
- package/extensions/rv-completions.ts +489 -0
- package/extensions/rv-locale.test.ts +17 -0
- package/extensions/rv-locale.ts +81 -0
- package/extensions/rv-model-priorities.test.ts +69 -0
- package/extensions/rv-model-priorities.ts +138 -0
- package/extensions/rv-prompts.test.ts +24 -0
- package/extensions/rv-prompts.ts +48 -15
- package/extensions/rv-semantic.test.ts +23 -0
- package/extensions/rv-semantic.ts +110 -0
- package/package.json +3 -3
- package/resources/rv-model-priorities.json +17 -0
- package/skills/pi-review/SKILL.md +9 -6
- package/skills/pi-review/references/codex-tools.md +2 -0
- package/skills/pi-review/references/model-selection.md +72 -0
|
@@ -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);
|
package/extensions/rv-prompts.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
-
:
|
|
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.
|
|
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)**
|
|
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
|
|
89
|
-
- If the user did not name a model,
|
|
90
|
-
|
|
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`.
|