@zephyrdeng/pi-review 0.7.0 → 0.8.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.
- package/README.md +51 -7
- package/README.zh-CN.md +45 -5
- package/dist/args.d.ts +4 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +82 -5
- package/dist/args.js.map +1 -1
- package/dist/args.test.js +72 -1
- package/dist/args.test.js.map +1 -1
- package/dist/child-process.d.ts +6 -0
- package/dist/child-process.d.ts.map +1 -1
- package/dist/child-process.js +48 -0
- package/dist/child-process.js.map +1 -1
- package/dist/child-process.test.js +55 -1
- package/dist/child-process.test.js.map +1 -1
- package/dist/cli-exit.test.js +14 -5
- package/dist/cli-exit.test.js.map +1 -1
- package/dist/cli.js +8 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/install-args.test.js +1 -1
- package/dist/install-args.test.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +2 -3
- package/dist/install.js.map +1 -1
- package/dist/json-events.d.ts +34 -2
- package/dist/json-events.d.ts.map +1 -1
- package/dist/json-events.js +156 -49
- package/dist/json-events.js.map +1 -1
- package/dist/json-events.test.js +176 -4
- package/dist/json-events.test.js.map +1 -1
- package/dist/loop.d.ts +28 -2
- package/dist/loop.d.ts.map +1 -1
- package/dist/loop.js +45 -3
- package/dist/loop.js.map +1 -1
- package/dist/loop.test.js +24 -1
- package/dist/loop.test.js.map +1 -1
- package/dist/matcher.test.js +1 -1
- package/dist/matcher.test.js.map +1 -1
- package/dist/meta-footer.d.ts +4 -8
- package/dist/meta-footer.d.ts.map +1 -1
- package/dist/meta-footer.js +19 -5
- package/dist/meta-footer.js.map +1 -1
- package/dist/meta-footer.test.js +11 -4
- package/dist/meta-footer.test.js.map +1 -1
- package/dist/panel-aggregate.test.js +1 -1
- package/dist/panel-aggregate.test.js.map +1 -1
- 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 +55 -0
- package/dist/panel-cancellation.test.js.map +1 -0
- package/dist/panel-cli-ui.test.d.ts +2 -0
- package/dist/panel-cli-ui.test.d.ts.map +1 -0
- package/dist/panel-cli-ui.test.js +91 -0
- package/dist/panel-cli-ui.test.js.map +1 -0
- package/dist/panel-cli.test.js +76 -27
- package/dist/panel-cli.test.js.map +1 -1
- package/dist/panel-config.d.ts +38 -0
- package/dist/panel-config.d.ts.map +1 -1
- package/dist/panel-config.js +137 -4
- package/dist/panel-config.js.map +1 -1
- package/dist/panel-config.test.js +84 -4
- package/dist/panel-config.test.js.map +1 -1
- package/dist/panel-loop.test.js +1 -1
- package/dist/panel-loop.test.js.map +1 -1
- 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-ui.d.ts +20 -0
- package/dist/panel-ui.d.ts.map +1 -0
- package/dist/panel-ui.js +84 -0
- package/dist/panel-ui.js.map +1 -0
- package/dist/panel-ui.test.d.ts +2 -0
- package/dist/panel-ui.test.d.ts.map +1 -0
- package/dist/panel-ui.test.js +122 -0
- package/dist/panel-ui.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 +42 -0
- package/dist/panel-view.d.ts.map +1 -0
- package/dist/panel-view.js +124 -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 +112 -0
- package/dist/panel-view.test.js.map +1 -0
- package/dist/panel.d.ts +22 -2
- package/dist/panel.d.ts.map +1 -1
- package/dist/panel.js +184 -60
- package/dist/panel.js.map +1 -1
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +34 -3
- package/dist/prompt.js.map +1 -1
- package/dist/prompt.test.js +21 -2
- package/dist/prompt.test.js.map +1 -1
- 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.test.js +1 -1
- package/dist/review-result.test.js.map +1 -1
- package/dist/review.js +3 -3
- package/dist/review.js.map +1 -1
- package/dist/review.meta.test.js +1 -1
- package/dist/review.meta.test.js.map +1 -1
- package/dist/skill-protocol.test.js +1 -1
- package/dist/skill-protocol.test.js.map +1 -1
- package/dist/skill-update.test.d.ts +2 -0
- package/dist/skill-update.test.d.ts.map +1 -0
- package/dist/skill-update.test.js +23 -0
- package/dist/skill-update.test.js.map +1 -0
- package/dist/skill.d.ts +22 -0
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +109 -22
- package/dist/skill.js.map +1 -1
- package/dist/types.d.ts +28 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/ui-client.d.ts +13 -0
- package/dist/ui-client.d.ts.map +1 -0
- package/dist/ui-client.js +161 -0
- package/dist/ui-client.js.map +1 -0
- package/dist/ui-client.test.d.ts +2 -0
- package/dist/ui-client.test.d.ts.map +1 -0
- package/dist/ui-client.test.js +48 -0
- package/dist/ui-client.test.js.map +1 -0
- package/dist/ui-html.d.ts +7 -0
- package/dist/ui-html.d.ts.map +1 -0
- package/dist/ui-html.js +52 -0
- package/dist/ui-html.js.map +1 -0
- package/dist/ui-html.test.d.ts +2 -0
- package/dist/ui-html.test.d.ts.map +1 -0
- package/dist/ui-html.test.js +26 -0
- package/dist/ui-html.test.js.map +1 -0
- package/dist/ui-server-main.d.ts +3 -0
- package/dist/ui-server-main.d.ts.map +1 -0
- package/dist/ui-server-main.js +128 -0
- package/dist/ui-server-main.js.map +1 -0
- package/dist/ui-server-main.test.d.ts +2 -0
- package/dist/ui-server-main.test.d.ts.map +1 -0
- package/dist/ui-server-main.test.js +123 -0
- package/dist/ui-server-main.test.js.map +1 -0
- package/dist/ui-server.d.ts +42 -0
- package/dist/ui-server.d.ts.map +1 -0
- package/dist/ui-server.js +233 -0
- package/dist/ui-server.js.map +1 -0
- package/dist/ui-server.test.d.ts +2 -0
- package/dist/ui-server.test.d.ts.map +1 -0
- package/dist/ui-server.test.js +236 -0
- package/dist/ui-server.test.js.map +1 -0
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +48 -19
- package/dist/update.js.map +1 -1
- package/extensions/panel-tool.test.ts +388 -0
- package/extensions/panel-tool.ts +390 -0
- package/extensions/review.test.ts +79 -0
- package/extensions/review.ts +191 -45
- package/extensions/rv-completions.test.ts +177 -18
- package/extensions/rv-completions.ts +490 -60
- package/extensions/rv-interactive.test.ts +562 -0
- package/extensions/rv-interactive.ts +616 -0
- package/extensions/rv-locale.test.ts +12 -4
- package/extensions/rv-locale.ts +44 -11
- package/extensions/rv-model-picker.test.ts +172 -0
- package/extensions/rv-model-picker.ts +313 -0
- package/extensions/rv-model-priorities.test.ts +29 -9
- package/extensions/rv-model-priorities.ts +23 -8
- package/extensions/rv-prompts.test.ts +281 -2
- package/extensions/rv-prompts.ts +480 -32
- package/extensions/rv-resolve.test.ts +189 -0
- package/extensions/rv-resolve.ts +313 -0
- package/extensions/rv-semantic.test.ts +1 -1
- package/extensions/rv-semantic.ts +38 -4
- package/package.json +18 -6
- package/resources/review-presets.json +2 -2
- package/resources/rv-model-priorities.json +18 -9
- package/skills/pi-review/SKILL.md +84 -60
- package/skills/pi-review/references/codex-tools.md +18 -11
- package/skills/pi-review/references/model-selection.md +49 -28
- package/dist/extensions/review.d.ts +0 -2
- package/dist/extensions/review.js +0 -22
- package/dist/extensions/rv-prompts.d.ts +0 -19
- package/dist/extensions/rv-prompts.js +0 -132
package/extensions/rv-prompts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** /rv → parent-agent task text;
|
|
1
|
+
/** /rv* → parent-agent task text; strategy lives on the command, target stays natural language. */
|
|
2
2
|
|
|
3
3
|
import type { RvLocale } from "./rv-locale.js";
|
|
4
4
|
import { mergeSemanticIntoParsed, stripSemanticPhrases } from "./rv-semantic.js";
|
|
@@ -6,9 +6,35 @@ import { mergeSemanticIntoParsed, stripSemanticPhrases } from "./rv-semantic.js"
|
|
|
6
6
|
export const RV_MODES = ["code", "plan", "challenge"] as const;
|
|
7
7
|
export type RvMode = (typeof RV_MODES)[number];
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** Slash-command strategy. Target text after the command is always natural language. */
|
|
10
|
+
export const RV_STRATEGIES = ["panel", "loop", "models"] as const;
|
|
11
|
+
export type RvStrategy = (typeof RV_STRATEGIES)[number];
|
|
12
|
+
|
|
13
|
+
const RV_ARG_TOKEN_RE = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g;
|
|
14
|
+
|
|
15
|
+
const FLAGS_REQUIRING_VALUE = [
|
|
16
|
+
"--mode",
|
|
17
|
+
"--model",
|
|
18
|
+
"--thinking",
|
|
19
|
+
"--continue",
|
|
20
|
+
"--max-rounds",
|
|
21
|
+
"--until",
|
|
22
|
+
"--reviewers",
|
|
23
|
+
"--panel",
|
|
24
|
+
"--reviewer-model",
|
|
25
|
+
"--consensus",
|
|
26
|
+
"--min-agree",
|
|
27
|
+
"--consensus-model",
|
|
28
|
+
"--concurrency",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
export const RV_CONSENSUS_POLICIES = ["any", "quorum", "majority", "unanimous"] as const;
|
|
32
|
+
export type RvConsensusPolicy = (typeof RV_CONSENSUS_POLICIES)[number];
|
|
33
|
+
export const RV_MAX_REVIEWERS = 8;
|
|
10
34
|
|
|
11
35
|
export type RvParsed = {
|
|
36
|
+
/** Selected by the slash command (/rv, /rv-loop, /rv-models), not by rewriting the target. */
|
|
37
|
+
strategy: RvStrategy;
|
|
12
38
|
mode: string;
|
|
13
39
|
model?: string;
|
|
14
40
|
thinking?: string;
|
|
@@ -16,16 +42,82 @@ export type RvParsed = {
|
|
|
16
42
|
noStream: boolean;
|
|
17
43
|
continueHandle?: string;
|
|
18
44
|
modelsOnly: boolean;
|
|
45
|
+
/** Optional loop budget when strategy is loop. */
|
|
46
|
+
maxRounds?: number;
|
|
47
|
+
/** Set when --max-rounds was present but not a strict positive safe integer. */
|
|
48
|
+
maxRoundsError?: string;
|
|
49
|
+
/** Loop stop goal. Only `clean` is supported. */
|
|
50
|
+
until?: "clean";
|
|
51
|
+
untilError?: string;
|
|
52
|
+
/** Panel width: independent reviewers for one gate (and each loop round). */
|
|
53
|
+
reviewers?: number;
|
|
54
|
+
reviewersError?: string;
|
|
55
|
+
/** Named panel preset (e.g. code-experts). Cannot combine with --reviewers. */
|
|
56
|
+
panel?: string;
|
|
57
|
+
/** Per-reviewer model overrides: id=provider/model (repeatable). */
|
|
58
|
+
reviewerModels?: string[];
|
|
59
|
+
reviewerModelsError?: string;
|
|
60
|
+
consensus?: RvConsensusPolicy;
|
|
61
|
+
consensusError?: string;
|
|
62
|
+
minAgree?: number;
|
|
63
|
+
minAgreeError?: string;
|
|
64
|
+
consensusModel?: string;
|
|
65
|
+
concurrency?: number;
|
|
66
|
+
concurrencyError?: string;
|
|
67
|
+
/** Set when a known value-taking flag was present without a value. */
|
|
68
|
+
missingValueError?: string;
|
|
69
|
+
/** Natural-language review request exactly as the user typed it. */
|
|
19
70
|
target: string;
|
|
20
71
|
};
|
|
21
72
|
|
|
73
|
+
/** Same strict rule as CLI `parsePositiveInteger`: /^[1-9]\d*$/ and safe integer. */
|
|
74
|
+
export function parseStrictPositiveSafeInteger(flag: string, value: string): number {
|
|
75
|
+
if (!/^[1-9]\d*$/.test(value)) {
|
|
76
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
77
|
+
}
|
|
78
|
+
const parsed = Number(value);
|
|
79
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
80
|
+
throw new Error(`${flag} must be a safe positive integer`);
|
|
81
|
+
}
|
|
82
|
+
return parsed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** /rv-loop host fix-point default (skill closeout), not the CLI loop default of 3. */
|
|
86
|
+
export const RV_LOOP_DEFAULT_MAX_ROUNDS = 1;
|
|
87
|
+
/** Host-cycle budget when /rv-loop --until clean is set without an explicit --max-rounds. */
|
|
88
|
+
export const RV_LOOP_UNTIL_CLEAN_DEFAULT_MAX_ROUNDS = 10;
|
|
89
|
+
|
|
90
|
+
/** Shared clean-goal definition for /rv-loop --until clean. */
|
|
91
|
+
export const RV_CLEAN_GOAL = {
|
|
92
|
+
id: "clean" as const,
|
|
93
|
+
summary:
|
|
94
|
+
"status=clean: no gate-blocking findings (single: no actionable findings; panel: no confirmed actionable clusters; advisories may remain)",
|
|
95
|
+
hostProtocol: [
|
|
96
|
+
"Until-clean host protocol (you are the only editor):",
|
|
97
|
+
"1. Goal: status=clean and exit 0. Advisories may remain; confirmed/actionable findings do not.",
|
|
98
|
+
"2. Run one review gate (CLI may still use --max-rounds as a hard ceiling inside that process).",
|
|
99
|
+
"3. If clean → stop and close out with proof.",
|
|
100
|
+
"4. If has_findings → fix only accepted in-scope blockers, rerun proof, then re-invoke /rv-loop --until clean.",
|
|
101
|
+
"5. If needs_human/blocked → escalate immediately; never claim clean.",
|
|
102
|
+
"6. Hard budget: stop after max-rounds host cycles (default 10 with --until clean) and report remaining findings.",
|
|
103
|
+
"7. Never pretend the child can edit. Never loop forever on an unchanged tree.",
|
|
104
|
+
].join("\n"),
|
|
105
|
+
} as const;
|
|
106
|
+
|
|
22
107
|
export type RvValidation =
|
|
23
108
|
| { ok: true }
|
|
24
109
|
| { ok: false; message: string };
|
|
25
110
|
|
|
26
|
-
export function parseRvArgs(raw: string): RvParsed {
|
|
27
|
-
|
|
28
|
-
|
|
111
|
+
export function parseRvArgs(raw: string, strategy: RvStrategy = "panel"): RvParsed {
|
|
112
|
+
// `--` is the completion-friendly boundary between options and free text.
|
|
113
|
+
// Semantic shortcuts only apply before it, preserving the target typed last.
|
|
114
|
+
const rawTokens: string[] = raw.match(RV_ARG_TOKEN_RE) ?? [];
|
|
115
|
+
const terminatorIndex = rawTokens.indexOf("--");
|
|
116
|
+
const optionRaw = (terminatorIndex >= 0 ? rawTokens.slice(0, terminatorIndex) : rawTokens).join(" ");
|
|
117
|
+
const explicitTarget = (terminatorIndex >= 0 ? rawTokens.slice(terminatorIndex + 1) : [])
|
|
118
|
+
.map((t) => t.replace(/^['"]|['"]$/g, ""));
|
|
119
|
+
const { remainder, apply: semanticApply } = stripSemanticPhrases(optionRaw);
|
|
120
|
+
const tokens = remainder.match(RV_ARG_TOKEN_RE) ?? [];
|
|
29
121
|
const cleaned = tokens.map((t) => t.replace(/^['"]|['"]$/g, ""));
|
|
30
122
|
|
|
31
123
|
let mode = "code";
|
|
@@ -34,15 +126,50 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
34
126
|
let keepSession = false;
|
|
35
127
|
let noStream = false;
|
|
36
128
|
let continueHandle: string | undefined;
|
|
37
|
-
let modelsOnly =
|
|
129
|
+
let modelsOnly = strategy === "models";
|
|
130
|
+
let maxRounds: number | undefined;
|
|
131
|
+
let maxRoundsError: string | undefined;
|
|
132
|
+
let until: "clean" | undefined;
|
|
133
|
+
let untilError: string | undefined;
|
|
134
|
+
let reviewers: number | undefined;
|
|
135
|
+
let reviewersError: string | undefined;
|
|
136
|
+
let panel: string | undefined;
|
|
137
|
+
let reviewerModels: string[] | undefined;
|
|
138
|
+
let reviewerModelsError: string | undefined;
|
|
139
|
+
let consensus: RvConsensusPolicy | undefined;
|
|
140
|
+
let consensusError: string | undefined;
|
|
141
|
+
let minAgree: number | undefined;
|
|
142
|
+
let minAgreeError: string | undefined;
|
|
143
|
+
let consensusModel: string | undefined;
|
|
144
|
+
let concurrency: number | undefined;
|
|
145
|
+
let concurrencyError: string | undefined;
|
|
146
|
+
let missingValueError: string | undefined;
|
|
38
147
|
const rest: string[] = [];
|
|
148
|
+
const markMissingValue = (flag: string) => {
|
|
149
|
+
missingValueError ??= `Missing value for ${flag}.`;
|
|
150
|
+
};
|
|
39
151
|
|
|
40
152
|
for (let i = 0; i < cleaned.length; i++) {
|
|
41
153
|
const t = cleaned[i];
|
|
42
|
-
|
|
154
|
+
// Conventional option terminator: everything after `--` is target text.
|
|
155
|
+
if (t === "--") {
|
|
156
|
+
rest.push(...cleaned.slice(i + 1));
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
// Backward compatible: `/rv models` still works, but `/rv-models` is preferred.
|
|
160
|
+
if (strategy === "panel" && t === "models" && rest.length === 0 && i === cleaned.length - 1) {
|
|
43
161
|
modelsOnly = true;
|
|
44
162
|
continue;
|
|
45
163
|
}
|
|
164
|
+
// An @path is always review-target syntax, never a flag value. Reject the
|
|
165
|
+
// preceding flag as missing instead of swallowing the target as its value.
|
|
166
|
+
if ((FLAGS_REQUIRING_VALUE as readonly string[]).includes(t)) {
|
|
167
|
+
const next = cleaned[i + 1];
|
|
168
|
+
if (!next || next.startsWith("--") || next.startsWith("@")) {
|
|
169
|
+
markMissingValue(t);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
46
173
|
if (t === "--keep-session") {
|
|
47
174
|
keepSession = true;
|
|
48
175
|
continue;
|
|
@@ -54,7 +181,7 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
54
181
|
if (t === "--mode") {
|
|
55
182
|
const m = cleaned[i + 1];
|
|
56
183
|
if (!m || m.startsWith("--")) {
|
|
57
|
-
|
|
184
|
+
markMissingValue(t);
|
|
58
185
|
continue;
|
|
59
186
|
}
|
|
60
187
|
mode = cleaned[++i];
|
|
@@ -63,7 +190,7 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
63
190
|
if (t === "--model") {
|
|
64
191
|
const v = cleaned[i + 1];
|
|
65
192
|
if (!v || v.startsWith("--")) {
|
|
66
|
-
|
|
193
|
+
markMissingValue(t);
|
|
67
194
|
continue;
|
|
68
195
|
}
|
|
69
196
|
model = cleaned[++i];
|
|
@@ -72,7 +199,7 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
72
199
|
if (t === "--thinking") {
|
|
73
200
|
const v = cleaned[i + 1];
|
|
74
201
|
if (!v || v.startsWith("--")) {
|
|
75
|
-
|
|
202
|
+
markMissingValue(t);
|
|
76
203
|
continue;
|
|
77
204
|
}
|
|
78
205
|
thinking = cleaned[++i];
|
|
@@ -81,17 +208,150 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
81
208
|
if (t === "--continue") {
|
|
82
209
|
const v = cleaned[i + 1];
|
|
83
210
|
if (!v || v.startsWith("--")) {
|
|
84
|
-
|
|
211
|
+
markMissingValue(t);
|
|
85
212
|
continue;
|
|
86
213
|
}
|
|
87
214
|
continueHandle = cleaned[++i];
|
|
88
215
|
continue;
|
|
89
216
|
}
|
|
217
|
+
if (t === "--max-rounds") {
|
|
218
|
+
const v = cleaned[i + 1];
|
|
219
|
+
if (!v || v.startsWith("--")) {
|
|
220
|
+
markMissingValue(t);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const raw = cleaned[++i]!;
|
|
224
|
+
try {
|
|
225
|
+
maxRounds = parseStrictPositiveSafeInteger("--max-rounds", raw);
|
|
226
|
+
maxRoundsError = undefined;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
maxRounds = undefined;
|
|
229
|
+
maxRoundsError = error instanceof Error ? error.message : "--max-rounds must be a positive integer";
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (t === "--until") {
|
|
234
|
+
const v = cleaned[i + 1];
|
|
235
|
+
if (!v || v.startsWith("--")) {
|
|
236
|
+
markMissingValue(t);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const raw = cleaned[++i]!;
|
|
240
|
+
if (raw === "clean") {
|
|
241
|
+
until = "clean";
|
|
242
|
+
untilError = undefined;
|
|
243
|
+
} else {
|
|
244
|
+
until = undefined;
|
|
245
|
+
untilError = `--until only supports clean (${RV_CLEAN_GOAL.summary})`;
|
|
246
|
+
}
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (t === "--reviewers") {
|
|
250
|
+
const v = cleaned[i + 1];
|
|
251
|
+
if (!v || v.startsWith("--")) {
|
|
252
|
+
markMissingValue(t);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const raw = cleaned[++i]!;
|
|
256
|
+
try {
|
|
257
|
+
reviewers = parseStrictPositiveSafeInteger("--reviewers", raw);
|
|
258
|
+
reviewersError = undefined;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
reviewers = undefined;
|
|
261
|
+
reviewersError = error instanceof Error ? error.message : "--reviewers must be a positive integer";
|
|
262
|
+
}
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (t === "--panel") {
|
|
266
|
+
const v = cleaned[i + 1];
|
|
267
|
+
if (!v || v.startsWith("--")) {
|
|
268
|
+
markMissingValue(t);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
panel = cleaned[++i];
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (t === "--reviewer-model") {
|
|
275
|
+
const v = cleaned[i + 1];
|
|
276
|
+
if (!v || v.startsWith("--")) {
|
|
277
|
+
markMissingValue(t);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const raw = cleaned[++i]!;
|
|
281
|
+
if (!/^[A-Za-z0-9_.-]+\S*=\S+$/.test(raw) && !/^[A-Za-z0-9_.-]+=\S+$/.test(raw)) {
|
|
282
|
+
reviewerModelsError = "--reviewer-model must look like id=provider/model (e.g. r1=openai/gpt-5.6-sol)";
|
|
283
|
+
} else {
|
|
284
|
+
reviewerModels = [...(reviewerModels ?? []), raw];
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (t === "--consensus") {
|
|
289
|
+
const v = cleaned[i + 1];
|
|
290
|
+
if (!v || v.startsWith("--")) {
|
|
291
|
+
markMissingValue(t);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const raw = cleaned[++i]!;
|
|
295
|
+
if ((RV_CONSENSUS_POLICIES as readonly string[]).includes(raw)) {
|
|
296
|
+
consensus = raw as RvConsensusPolicy;
|
|
297
|
+
consensusError = undefined;
|
|
298
|
+
} else {
|
|
299
|
+
consensus = undefined;
|
|
300
|
+
consensusError = `--consensus must be one of ${RV_CONSENSUS_POLICIES.join(" | ")}`;
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (t === "--min-agree") {
|
|
305
|
+
const v = cleaned[i + 1];
|
|
306
|
+
if (!v || v.startsWith("--")) {
|
|
307
|
+
markMissingValue(t);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const raw = cleaned[++i]!;
|
|
311
|
+
try {
|
|
312
|
+
minAgree = parseStrictPositiveSafeInteger("--min-agree", raw);
|
|
313
|
+
minAgreeError = undefined;
|
|
314
|
+
} catch (error) {
|
|
315
|
+
minAgree = undefined;
|
|
316
|
+
minAgreeError = error instanceof Error ? error.message : "--min-agree must be a positive integer";
|
|
317
|
+
}
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (t === "--consensus-model") {
|
|
321
|
+
const v = cleaned[i + 1];
|
|
322
|
+
if (!v || v.startsWith("--")) {
|
|
323
|
+
markMissingValue(t);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
consensusModel = cleaned[++i];
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (t === "--concurrency") {
|
|
330
|
+
const v = cleaned[i + 1];
|
|
331
|
+
if (!v || v.startsWith("--")) {
|
|
332
|
+
markMissingValue(t);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const raw = cleaned[++i]!;
|
|
336
|
+
try {
|
|
337
|
+
concurrency = parseStrictPositiveSafeInteger("--concurrency", raw);
|
|
338
|
+
concurrencyError = undefined;
|
|
339
|
+
} catch (error) {
|
|
340
|
+
concurrency = undefined;
|
|
341
|
+
concurrencyError = error instanceof Error ? error.message : "--concurrency must be a positive integer";
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
90
345
|
rest.push(t);
|
|
91
346
|
}
|
|
92
347
|
|
|
348
|
+
// Text after the explicit separator must not be interpreted as strategy syntax.
|
|
349
|
+
rest.push(...explicitTarget);
|
|
350
|
+
|
|
351
|
+
const effectiveStrategy: RvStrategy = modelsOnly ? "models" : strategy;
|
|
93
352
|
return mergeSemanticIntoParsed(
|
|
94
353
|
{
|
|
354
|
+
strategy: effectiveStrategy,
|
|
95
355
|
mode,
|
|
96
356
|
model,
|
|
97
357
|
thinking,
|
|
@@ -99,6 +359,23 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
99
359
|
noStream,
|
|
100
360
|
continueHandle,
|
|
101
361
|
modelsOnly,
|
|
362
|
+
...(maxRounds !== undefined ? { maxRounds } : {}),
|
|
363
|
+
...(maxRoundsError ? { maxRoundsError } : {}),
|
|
364
|
+
...(until ? { until } : {}),
|
|
365
|
+
...(untilError ? { untilError } : {}),
|
|
366
|
+
...(reviewers !== undefined ? { reviewers } : {}),
|
|
367
|
+
...(reviewersError ? { reviewersError } : {}),
|
|
368
|
+
...(panel ? { panel } : {}),
|
|
369
|
+
...(reviewerModels ? { reviewerModels } : {}),
|
|
370
|
+
...(reviewerModelsError ? { reviewerModelsError } : {}),
|
|
371
|
+
...(consensus ? { consensus } : {}),
|
|
372
|
+
...(consensusError ? { consensusError } : {}),
|
|
373
|
+
...(minAgree !== undefined ? { minAgree } : {}),
|
|
374
|
+
...(minAgreeError ? { minAgreeError } : {}),
|
|
375
|
+
...(consensusModel ? { consensusModel } : {}),
|
|
376
|
+
...(concurrency !== undefined ? { concurrency } : {}),
|
|
377
|
+
...(concurrencyError ? { concurrencyError } : {}),
|
|
378
|
+
...(missingValueError ? { missingValueError } : {}),
|
|
102
379
|
target: rest.join(" ").trim(),
|
|
103
380
|
},
|
|
104
381
|
semanticApply,
|
|
@@ -106,9 +383,52 @@ export function parseRvArgs(raw: string): RvParsed {
|
|
|
106
383
|
}
|
|
107
384
|
|
|
108
385
|
export function validateRvParsed(parsed: RvParsed): RvValidation {
|
|
386
|
+
if (parsed.strategy === "loop" && (parsed.keepSession || parsed.continueHandle)) {
|
|
387
|
+
return { ok: false, message: "/rv-loop does not support --keep-session or --continue." };
|
|
388
|
+
}
|
|
109
389
|
if (parsed.keepSession && parsed.continueHandle) {
|
|
110
390
|
return { ok: false, message: "--keep-session and --continue cannot be used together." };
|
|
111
391
|
}
|
|
392
|
+
if (parsed.missingValueError) return { ok: false, message: parsed.missingValueError };
|
|
393
|
+
if (parsed.maxRoundsError) return { ok: false, message: parsed.maxRoundsError };
|
|
394
|
+
if (parsed.untilError) return { ok: false, message: parsed.untilError };
|
|
395
|
+
if (parsed.reviewersError) return { ok: false, message: parsed.reviewersError };
|
|
396
|
+
if (parsed.reviewerModelsError) return { ok: false, message: parsed.reviewerModelsError };
|
|
397
|
+
if (parsed.consensusError) return { ok: false, message: parsed.consensusError };
|
|
398
|
+
if (parsed.minAgreeError) return { ok: false, message: parsed.minAgreeError };
|
|
399
|
+
if (parsed.concurrencyError) return { ok: false, message: parsed.concurrencyError };
|
|
400
|
+
if (parsed.maxRounds !== undefined && parsed.strategy !== "loop") {
|
|
401
|
+
return { ok: false, message: "--max-rounds is only valid with /rv-loop." };
|
|
402
|
+
}
|
|
403
|
+
if (parsed.until !== undefined && parsed.strategy !== "loop") {
|
|
404
|
+
return { ok: false, message: "--until is only valid with /rv-loop." };
|
|
405
|
+
}
|
|
406
|
+
if (parsed.reviewers !== undefined && parsed.panel) {
|
|
407
|
+
return { ok: false, message: "--reviewers cannot be used with --panel" };
|
|
408
|
+
}
|
|
409
|
+
if (parsed.reviewers !== undefined && (parsed.reviewers < 1 || parsed.reviewers > RV_MAX_REVIEWERS)) {
|
|
410
|
+
return { ok: false, message: `--reviewers must be between 1 and ${RV_MAX_REVIEWERS}` };
|
|
411
|
+
}
|
|
412
|
+
const hasPanelOnlyOptions =
|
|
413
|
+
parsed.consensus !== undefined ||
|
|
414
|
+
parsed.minAgree !== undefined ||
|
|
415
|
+
parsed.consensusModel !== undefined ||
|
|
416
|
+
parsed.concurrency !== undefined ||
|
|
417
|
+
(parsed.reviewerModels?.length ?? 0) > 0;
|
|
418
|
+
if (parsed.reviewers === 1 && hasPanelOnlyOptions) {
|
|
419
|
+
return { ok: false, message: "panel options require --reviewers > 1 or --panel" };
|
|
420
|
+
}
|
|
421
|
+
if (parsed.minAgree !== undefined) {
|
|
422
|
+
if (parsed.consensus && parsed.consensus !== "quorum") {
|
|
423
|
+
return { ok: false, message: "--min-agree is only valid with --consensus quorum" };
|
|
424
|
+
}
|
|
425
|
+
if (parsed.reviewers !== undefined && parsed.minAgree > parsed.reviewers) {
|
|
426
|
+
return { ok: false, message: `--min-agree ${parsed.minAgree} cannot exceed reviewers ${parsed.reviewers}` };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (parsed.concurrency !== undefined && parsed.reviewers !== undefined && parsed.concurrency > parsed.reviewers) {
|
|
430
|
+
return { ok: false, message: `--concurrency ${parsed.concurrency} cannot exceed reviewers ${parsed.reviewers}` };
|
|
431
|
+
}
|
|
112
432
|
for (const flag of FLAGS_REQUIRING_VALUE) {
|
|
113
433
|
if (parsed.target === flag) {
|
|
114
434
|
return { ok: false, message: `Missing value for ${flag}.` };
|
|
@@ -117,10 +437,31 @@ export function validateRvParsed(parsed: RvParsed): RvValidation {
|
|
|
117
437
|
return { ok: true };
|
|
118
438
|
}
|
|
119
439
|
|
|
440
|
+
/** Shell-safe single argument for Execute: lines shown to the parent agent. */
|
|
441
|
+
export function shellQuote(value: string): string {
|
|
442
|
+
if (value === "") return "''";
|
|
443
|
+
// Prefer single quotes; only escape embedded single quotes.
|
|
444
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
445
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
446
|
+
}
|
|
447
|
+
|
|
120
448
|
/** Same optional flags for initial review and --continue follow-up (all optional). */
|
|
121
449
|
export function buildPiReviewArgv(parsed: RvParsed, target: string): string[] {
|
|
122
450
|
const parts = ["pi-review"];
|
|
123
|
-
if (parsed.
|
|
451
|
+
if (parsed.strategy === "loop") {
|
|
452
|
+
parts.push("loop");
|
|
453
|
+
if (parsed.until === "clean") {
|
|
454
|
+
parts.push("--until", "clean");
|
|
455
|
+
// Until-clean always carries an explicit hard budget (never unlimited).
|
|
456
|
+
parts.push(
|
|
457
|
+
"--max-rounds",
|
|
458
|
+
String(parsed.maxRounds ?? RV_LOOP_UNTIL_CLEAN_DEFAULT_MAX_ROUNDS),
|
|
459
|
+
);
|
|
460
|
+
} else {
|
|
461
|
+
// Host fix-point default for /rv-loop is one gate; CLI `pi-review loop` still defaults to 3.
|
|
462
|
+
parts.push("--max-rounds", String(parsed.maxRounds ?? RV_LOOP_DEFAULT_MAX_ROUNDS));
|
|
463
|
+
}
|
|
464
|
+
} else if (parsed.continueHandle) {
|
|
124
465
|
parts.push("--continue", parsed.continueHandle);
|
|
125
466
|
} else if (parsed.keepSession) {
|
|
126
467
|
parts.push("--keep-session");
|
|
@@ -129,10 +470,44 @@ export function buildPiReviewArgv(parsed: RvParsed, target: string): string[] {
|
|
|
129
470
|
if (parsed.model) parts.push("--model", parsed.model);
|
|
130
471
|
if (parsed.thinking) parts.push("--thinking", parsed.thinking);
|
|
131
472
|
if (parsed.noStream) parts.push("--no-stream");
|
|
473
|
+
if (parsed.panel) parts.push("--panel", parsed.panel);
|
|
474
|
+
if (parsed.reviewers !== undefined) parts.push("--reviewers", String(parsed.reviewers));
|
|
475
|
+
for (const mapping of parsed.reviewerModels ?? []) parts.push("--reviewer-model", mapping);
|
|
476
|
+
if (parsed.consensus) parts.push("--consensus", parsed.consensus);
|
|
477
|
+
if (parsed.minAgree !== undefined) parts.push("--min-agree", String(parsed.minAgree));
|
|
478
|
+
if (parsed.consensusModel) parts.push("--consensus-model", parsed.consensusModel);
|
|
479
|
+
if (parsed.concurrency !== undefined) parts.push("--concurrency", String(parsed.concurrency));
|
|
132
480
|
parts.push("--", target);
|
|
133
481
|
return parts;
|
|
134
482
|
}
|
|
135
483
|
|
|
484
|
+
/** Format argv as a shell-safe command line for orchestration prompts. */
|
|
485
|
+
export function formatPiReviewCommandLine(parsed: RvParsed, target: string): string {
|
|
486
|
+
return buildPiReviewArgv(parsed, target).map(shellQuote).join(" ");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Build the native pi_review tool call instruction without dropping panel config. */
|
|
490
|
+
export function buildPiReviewToolCallInstruction(parsed: RvParsed, target: string): string {
|
|
491
|
+
const fields: string[] = [
|
|
492
|
+
`target=${JSON.stringify(target)}`,
|
|
493
|
+
`mode=${JSON.stringify(parsed.mode)}`,
|
|
494
|
+
];
|
|
495
|
+
if (parsed.panel) fields.push(`panel=${JSON.stringify(parsed.panel)}`);
|
|
496
|
+
if (parsed.reviewers !== undefined) fields.push(`reviewers=${parsed.reviewers}`);
|
|
497
|
+
if (parsed.reviewerModels?.length) fields.push(`reviewerModels=${JSON.stringify(parsed.reviewerModels)}`);
|
|
498
|
+
if (parsed.consensus) fields.push(`consensus=${JSON.stringify(parsed.consensus)}`);
|
|
499
|
+
if (parsed.minAgree !== undefined) fields.push(`minAgree=${parsed.minAgree}`);
|
|
500
|
+
if (parsed.consensusModel) fields.push(`consensusModel=${JSON.stringify(parsed.consensusModel)}`);
|
|
501
|
+
if (parsed.concurrency !== undefined) fields.push(`concurrency=${parsed.concurrency}`);
|
|
502
|
+
if (parsed.model) fields.push(`model=${JSON.stringify(parsed.model)}`);
|
|
503
|
+
if (parsed.thinking) fields.push(`thinking=${JSON.stringify(parsed.thinking)}`);
|
|
504
|
+
// Default panel when /rv has no explicit width/preset: keep code-experts.
|
|
505
|
+
if (parsed.strategy === "panel" && !parsed.panel && parsed.reviewers === undefined) {
|
|
506
|
+
fields.push(`panel=${JSON.stringify("code-experts")}`);
|
|
507
|
+
}
|
|
508
|
+
return `Call pi_review with ${fields.join(", ")}. Treat target as a natural-language review request. Do not expand directory targets into multi-file lists. Do not drop panel/reviewers/reviewerModels/consensus fields.`;
|
|
509
|
+
}
|
|
510
|
+
|
|
136
511
|
export function orchestrationLocaleNote(locale: RvLocale = "en"): string {
|
|
137
512
|
if (locale === "zh") {
|
|
138
513
|
return "用中文向用户总结(模型目录、选型理由、review 结论);技术 id 如 provider/model 可保留英文。";
|
|
@@ -140,27 +515,38 @@ export function orchestrationLocaleNote(locale: RvLocale = "en"): string {
|
|
|
140
515
|
return "Summarize for the user in English (catalog, model rationale, review conclusion); keep technical ids such as provider/model as listed.";
|
|
141
516
|
}
|
|
142
517
|
|
|
143
|
-
function piHostRules(): string {
|
|
518
|
+
function piHostRules(strategy: RvStrategy): string {
|
|
144
519
|
return [
|
|
145
520
|
"Follow the pi-review skill.",
|
|
146
|
-
"Host: Pi interactive session (/rv).",
|
|
147
|
-
"
|
|
521
|
+
"Host: Pi interactive session (/rv*).",
|
|
522
|
+
"Slash commands select strategy only: /rv = panel review, /rv-loop = loop closeout, /rv-models = model catalog.",
|
|
523
|
+
"Everything after the command is a natural-language review request. Path mentions like @src are fine as text.",
|
|
524
|
+
"Do not expand directory targets into multi-file lists. Strategy matching beyond the slash command (mode defaults, model choice, panel preset, path-vs-file handling) lives in the pi-review skill and CLI.",
|
|
525
|
+
strategy === "panel"
|
|
526
|
+
? "For /rv panel runs, call the pi_review custom tool. Exception: explicit --reviewers 1 is a non-panel single review and must use the shell CLI path; never replace it with the default code-experts panel."
|
|
527
|
+
: strategy === "loop"
|
|
528
|
+
? "For /rv-loop, follow the Loop closeout protocol in the pi-review skill. Use the shell CLI loop path; do not pretend the child can edit files."
|
|
529
|
+
: "For /rv-models, only list models; do not start a review.",
|
|
148
530
|
"CLI defaults: stream child output live. Do NOT add --no-stream or --progress-log unless the user explicitly asked.",
|
|
149
531
|
"Do not edit, patch, commit, or implement findings unless the user asks separately.",
|
|
150
|
-
"
|
|
532
|
+
"For pi_review panel runs, report the tool's rendered panel result and status. For CLI fallback, loop, and --continue runs, show the ASCII pi-review footer (lines starting with ── pi-review). Do not paste PI_REVIEW_META_JSON to the user; use Session from a CLI footer for /rv --continue.",
|
|
151
533
|
].join(" ");
|
|
152
534
|
}
|
|
153
535
|
|
|
154
|
-
export function buildRvOrchestrationPrompt(
|
|
536
|
+
export function buildRvOrchestrationPrompt(
|
|
537
|
+
parsed: RvParsed,
|
|
538
|
+
locale: RvLocale = "en",
|
|
539
|
+
resolutionNotes: string[] = [],
|
|
540
|
+
): string {
|
|
155
541
|
const localeNote = orchestrationLocaleNote(locale);
|
|
156
|
-
if (parsed.modelsOnly) {
|
|
542
|
+
if (parsed.modelsOnly || parsed.strategy === "models") {
|
|
157
543
|
return [
|
|
158
544
|
"Run the pi-review model catalog step.",
|
|
159
|
-
piHostRules(),
|
|
545
|
+
piHostRules("models"),
|
|
160
546
|
localeNote,
|
|
161
547
|
"Execute: pi-review models",
|
|
162
548
|
"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).",
|
|
163
|
-
"Do not start a review until the user supplies a target
|
|
549
|
+
"Do not start a review until the user supplies a natural-language target via /rv or /rv-loop.",
|
|
164
550
|
].join("\n\n");
|
|
165
551
|
}
|
|
166
552
|
|
|
@@ -169,11 +555,14 @@ export function buildRvOrchestrationPrompt(parsed: RvParsed, locale: RvLocale =
|
|
|
169
555
|
|
|
170
556
|
if (!target && !parsed.continueHandle) {
|
|
171
557
|
return [
|
|
172
|
-
"/rv needs a review target.",
|
|
173
|
-
"Usage:
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
|
|
558
|
+
parsed.strategy === "loop" ? "/rv-loop needs a review target." : "/rv needs a review target.",
|
|
559
|
+
"Usage:",
|
|
560
|
+
" /rv [--mode code|plan|challenge] [--model provider/model] [--keep-session] <natural-language target>",
|
|
561
|
+
" /rv-loop [--mode ...] [--model ...] [--max-rounds n] <natural-language target>",
|
|
562
|
+
" /rv-models",
|
|
563
|
+
" /rv --continue <handle> [--mode ...] [--model ...] [follow-up text]",
|
|
564
|
+
"Examples: /rv @src | /rv review the auth changes | /rv-loop fix until clean @src | /rv-models",
|
|
565
|
+
].join("\n");
|
|
177
566
|
}
|
|
178
567
|
|
|
179
568
|
const modeBlock: Record<string, string> = {
|
|
@@ -194,10 +583,14 @@ export function buildRvOrchestrationPrompt(parsed: RvParsed, locale: RvLocale =
|
|
|
194
583
|
].join(" "),
|
|
195
584
|
};
|
|
196
585
|
|
|
197
|
-
const cliLine =
|
|
586
|
+
const cliLine = formatPiReviewCommandLine(parsed, target);
|
|
587
|
+
|
|
588
|
+
const resolutionStep = resolutionNotes.length
|
|
589
|
+
? `Resolved user shortcuts against the live model catalog:\n${resolutionNotes.map((note) => `- ${note}`).join("\n")}`
|
|
590
|
+
: "";
|
|
198
591
|
|
|
199
592
|
const modelStep = parsed.model
|
|
200
|
-
? `Use model exactly as
|
|
593
|
+
? `Use model exactly as resolved: ${parsed.model}${parsed.thinking ? ` at thinking ${parsed.thinking}` : ""}. Prefer this exact catalog id; do not invent a different provider/model.`
|
|
201
594
|
: parsed.thinking
|
|
202
595
|
? `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
596
|
: "Run pi-review models first if needed; pick using skill references/model-selection.md (code / frontend / plan) or omit --model for Pi default.";
|
|
@@ -208,22 +601,69 @@ export function buildRvOrchestrationPrompt(parsed: RvParsed, locale: RvLocale =
|
|
|
208
601
|
? "Use --keep-session so the user can follow up with /rv --continue <handle>."
|
|
209
602
|
: "";
|
|
210
603
|
|
|
604
|
+
const singleReviewer = parsed.strategy === "panel" && parsed.reviewers === 1 && !parsed.panel;
|
|
605
|
+
const strategyNote =
|
|
606
|
+
parsed.strategy === "loop"
|
|
607
|
+
? parsed.until === "clean"
|
|
608
|
+
? [
|
|
609
|
+
"Strategy: loop until clean (host-driven).",
|
|
610
|
+
`Clean goal: ${RV_CLEAN_GOAL.summary}.`,
|
|
611
|
+
`Hard budget: --max-rounds ${parsed.maxRounds ?? RV_LOOP_UNTIL_CLEAN_DEFAULT_MAX_ROUNDS} (never unlimited).`,
|
|
612
|
+
RV_CLEAN_GOAL.hostProtocol,
|
|
613
|
+
"CLI child remains review-only; you own every fix and re-invocation.",
|
|
614
|
+
].join("\n")
|
|
615
|
+
: `Strategy: loop closeout. Follow the Loop closeout protocol. /rv-loop defaults to --max-rounds ${RV_LOOP_DEFAULT_MAX_ROUNDS} (host fix point after each gate); explicit value here: ${parsed.maxRounds ?? RV_LOOP_DEFAULT_MAX_ROUNDS}. Note: bare CLI "pi-review loop" still defaults to 3.`
|
|
616
|
+
: singleReviewer
|
|
617
|
+
? "Strategy: explicit single-reviewer, non-panel review via shell CLI. Do not call pi_review and do not substitute code-experts."
|
|
618
|
+
: "Strategy: panel review via pi_review (code-experts by default unless reviewers/panel are set).";
|
|
619
|
+
|
|
620
|
+
// Shell path for loop/continue/keep-session/no-stream. Native tool path for ordinary /rv panels.
|
|
621
|
+
// Always preserve panel width, per-reviewer models, and consensus fields.
|
|
622
|
+
const untilCleanHostLoop =
|
|
623
|
+
parsed.strategy === "loop" && parsed.until === "clean"
|
|
624
|
+
? [
|
|
625
|
+
"Host until-clean cycle (required):",
|
|
626
|
+
"A) Execute the shell CLI command below once (one process / one or more internal review rounds).",
|
|
627
|
+
"B) Read the ASCII footer Stop/Exit and status.",
|
|
628
|
+
"C) If Stop=clean and Exit=0 → done. Report clean goal met.",
|
|
629
|
+
"D) If has_findings / exit 1 → fix accepted in-scope blockers only, run proof, then re-execute the same command (count as next host cycle).",
|
|
630
|
+
"E) If needs_human/blocked → stop and escalate.",
|
|
631
|
+
`F) Stop after ${parsed.maxRounds ?? RV_LOOP_UNTIL_CLEAN_DEFAULT_MAX_ROUNDS} host cycles even if not clean; never infinite-loop on an unchanged tree.`,
|
|
632
|
+
].join("\n")
|
|
633
|
+
: "";
|
|
634
|
+
|
|
635
|
+
const execution =
|
|
636
|
+
parsed.strategy === "loop" || parsed.continueHandle || parsed.keepSession || parsed.noStream || singleReviewer
|
|
637
|
+
? `Execute exactly (already shell-quoted; do not re-quote or drop flags):\n${cliLine}`
|
|
638
|
+
: buildPiReviewToolCallInstruction(parsed, target);
|
|
639
|
+
|
|
211
640
|
return [
|
|
212
|
-
parsed.continueHandle
|
|
213
|
-
|
|
641
|
+
parsed.continueHandle
|
|
642
|
+
? "Continue a prior pi-review session."
|
|
643
|
+
: parsed.strategy === "loop"
|
|
644
|
+
? parsed.until === "clean"
|
|
645
|
+
? "Run a pi-review until-clean closeout for the target below."
|
|
646
|
+
: "Run a pi-review loop closeout for the target below."
|
|
647
|
+
: singleReviewer
|
|
648
|
+
? "Run a single-reviewer non-panel pi-review for the target below."
|
|
649
|
+
: "Run pi-review for the target below.",
|
|
650
|
+
piHostRules(parsed.strategy),
|
|
214
651
|
localeNote,
|
|
652
|
+
strategyNote,
|
|
653
|
+
untilCleanHostLoop,
|
|
215
654
|
modeBlock[parsed.mode] ??
|
|
216
655
|
`Mode: ${parsed.mode}. Follow the preset named in review-presets.json when present.`,
|
|
656
|
+
resolutionStep,
|
|
217
657
|
modelStep,
|
|
218
658
|
continueNote,
|
|
219
|
-
|
|
659
|
+
execution,
|
|
220
660
|
]
|
|
221
661
|
.filter(Boolean)
|
|
222
662
|
.join("\n\n");
|
|
223
663
|
}
|
|
224
664
|
|
|
225
665
|
export const RV_COMPLETIONS: { value: string; hint: string }[] = [
|
|
226
|
-
{ value: "@", hint: "
|
|
666
|
+
{ value: "@", hint: "path mention after @ (e.g. @src or @src/foo.ts); passed as natural-language target text" },
|
|
227
667
|
{ value: "models", hint: "list pi-review models only" },
|
|
228
668
|
{ value: "--mode code", hint: "code / diff / MR review" },
|
|
229
669
|
{ value: "--mode plan", hint: "architecture or plan review" },
|
|
@@ -233,4 +673,12 @@ export const RV_COMPLETIONS: { value: string; hint: string }[] = [
|
|
|
233
673
|
{ value: "--keep-session", hint: "persist session for follow-up" },
|
|
234
674
|
{ value: "--no-stream", hint: "rare: buffer until exit (not default in Pi)" },
|
|
235
675
|
{ value: "--continue ", hint: "resume session; same optional flags as initial /rv" },
|
|
236
|
-
|
|
676
|
+
{ value: "--max-rounds ", hint: "loop hard budget / review gates (not reviewer count)" },
|
|
677
|
+
{ value: "--until clean", hint: "loop goal: host re-review until clean gate (hard-capped by --max-rounds)" },
|
|
678
|
+
{ value: "--reviewers ", hint: "panel width: independent reviewers per gate (1-8)" },
|
|
679
|
+
{ value: "--panel ", hint: "named panel preset (e.g. code-experts)" },
|
|
680
|
+
{ value: "--reviewer-model ", hint: "per-reviewer model: r1=provider/model (repeatable)" },
|
|
681
|
+
{ value: "--consensus ", hint: "any|quorum|majority|unanimous" },
|
|
682
|
+
{ value: "--min-agree ", hint: "quorum threshold (quorum only)" },
|
|
683
|
+
{ value: "--concurrency ", hint: "bound parallel reviewers" },
|
|
684
|
+
];
|