pi-pr-review 1.16.0 → 1.17.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/CHANGELOG.md +7 -0
- package/README.md +5 -3
- package/extensions/pr-review-subagent.ts +69 -8
- package/extensions/review-table.ts +21 -3
- package/lib/pr-review-publish.ts +51 -1
- package/package.json +1 -1
- package/prompts/pr-review.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.17.0](https://github.com/10ego/pi-pr-review/compare/v1.16.0...v1.17.0) (2026-08-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **review:** configure default review mode ([#122](https://github.com/10ego/pi-pr-review/issues/122)) ([52028be](https://github.com/10ego/pi-pr-review/commit/52028be9fcc91713e54378e3d50f31a3cd9cd642))
|
|
9
|
+
|
|
3
10
|
## [1.16.0](https://github.com/10ego/pi-pr-review/compare/v1.15.16...v1.16.0) (2026-08-28)
|
|
4
11
|
|
|
5
12
|
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ The semantic result is predictable human-readable Markdown in every mode. GitHub
|
|
|
59
59
|
| `/pr-review 123 --deep` | One integrated heavy reviewer for the whole PR. |
|
|
60
60
|
| `/pr-review 123 --include-closed` | Reviews a closed or merged PR without asking first. |
|
|
61
61
|
|
|
62
|
-
`--quick`, `--major-only`, `--balanced`, `--full`, and `--deep` are mutually exclusive.
|
|
62
|
+
`--quick`, `--major-only`, `--balanced`, `--full`, and `--deep` are mutually exclusive. When no mode flag is supplied, `/pr-review` uses the configured default mode; this is `balanced` until changed with `/pr-review-config`.
|
|
63
63
|
|
|
64
64
|
`--deep` trades parallel lens coverage for holistic judgment: a single heavy-tier reviewer receives the complete diff plus repository tools and reviews the change as one story—intent, approach, cross-file behavior, and test fit. It uses the same deadline, artifact, degradation, extraction, and publication machinery as every other mode. Without `--include-closed` or `--review-closed`, Pi asks before reviewing a non-open PR.
|
|
65
65
|
|
|
@@ -115,7 +115,7 @@ The viewer intentionally cannot send prompts, steering, or follow-ups to reviewe
|
|
|
115
115
|
|
|
116
116
|
## Configure models
|
|
117
117
|
|
|
118
|
-
`/pr-review-config` opens an interactive settings menu in the TUI. Use `/pr-review-config show` for a text summary or `key=value` arguments for direct changes.
|
|
118
|
+
`/pr-review-config` opens an interactive settings menu in the TUI. Its **default review mode** selector replaces the former parallelism control: reviewer count and concurrency are derived from the selected mode. Use `/pr-review-config show` for a text summary or `key=value` arguments for direct changes.
|
|
119
119
|
|
|
120
120
|
| Tier | Purpose |
|
|
121
121
|
|---|---|
|
|
@@ -126,6 +126,7 @@ The viewer intentionally cannot send prompts, steering, or follow-ups to reviewe
|
|
|
126
126
|
Common settings:
|
|
127
127
|
|
|
128
128
|
```text
|
|
129
|
+
/pr-review-config default_review_mode=balanced
|
|
129
130
|
/pr-review-config light=provider/model heavy=provider/model:high
|
|
130
131
|
/pr-review-config heavy_fallbacks=provider/backup:high,provider/backup2
|
|
131
132
|
/pr-review-config light_thinking=low medium_thinking=medium heavy_thinking=high
|
|
@@ -147,12 +148,13 @@ Configuration is stored in:
|
|
|
147
148
|
- user scope: `~/.pi/agent/pr-review.json`;
|
|
148
149
|
- project scope: `<repo>/.pi/pr-review.json`, applied only when the project is trusted.
|
|
149
150
|
|
|
150
|
-
A trusted project can override model, tool, and publication settings. Verification profiles are always user-only.
|
|
151
|
+
A trusted project can override the default review mode, model, tool, and publication settings. Explicit `/pr-review --quick|--balanced|--full|--deep` flags override the configured default. Verification profiles are always user-only.
|
|
151
152
|
|
|
152
153
|
Example:
|
|
153
154
|
|
|
154
155
|
```json
|
|
155
156
|
{
|
|
157
|
+
"defaultReviewMode": "balanced",
|
|
156
158
|
"tiers": {
|
|
157
159
|
"light": "provider/fast-model",
|
|
158
160
|
"medium": "provider/balanced-model",
|
|
@@ -91,6 +91,8 @@ import {
|
|
|
91
91
|
resolveAllowStalePublishSetting,
|
|
92
92
|
resolveAutoPostSetting,
|
|
93
93
|
resolveApproveMaxPriorityLevelSetting,
|
|
94
|
+
resolveDefaultReviewModeSetting,
|
|
95
|
+
REVIEW_MODES,
|
|
94
96
|
type ApproveMaxPriorityLevel,
|
|
95
97
|
type ReviewMode,
|
|
96
98
|
} from "../lib/pr-review-publish.ts";
|
|
@@ -175,6 +177,8 @@ const TIER_PURPOSE: Record<Tier, string> = {
|
|
|
175
177
|
};
|
|
176
178
|
|
|
177
179
|
interface PrReviewConfig {
|
|
180
|
+
/** Review topology used when /pr-review has no explicit mode flag. */
|
|
181
|
+
defaultReviewMode: ReviewMode;
|
|
178
182
|
/** Tier label -> model spec (e.g. "anthropic/model", "openai/model:high"). */
|
|
179
183
|
tiers: Partial<Record<Tier, string>>;
|
|
180
184
|
/** Tier label -> ordered fallback model specs used for quota/capacity failures. */
|
|
@@ -258,6 +262,17 @@ function normalizeToolPolicies(
|
|
|
258
262
|
return out;
|
|
259
263
|
}
|
|
260
264
|
|
|
265
|
+
function resolveDefaultReviewModeForContext(ctx: Pick<ExtensionContext, "cwd" | "isProjectTrusted">) {
|
|
266
|
+
const user = readConfigFile(userConfigPath());
|
|
267
|
+
let project: Partial<PrReviewConfig> | undefined;
|
|
268
|
+
try {
|
|
269
|
+
if (ctx.isProjectTrusted()) project = readConfigFile(projectConfigPath(ctx.cwd));
|
|
270
|
+
} catch {
|
|
271
|
+
/* user config only */
|
|
272
|
+
}
|
|
273
|
+
return resolveDefaultReviewModeSetting(user, project);
|
|
274
|
+
}
|
|
275
|
+
|
|
261
276
|
function resolveAutoPostForContext(ctx: Pick<ExtensionContext, "cwd" | "isProjectTrusted">) {
|
|
262
277
|
const user = readConfigFile(userConfigPath());
|
|
263
278
|
let project: Partial<PrReviewConfig> | undefined;
|
|
@@ -315,7 +330,9 @@ function loadConfig(ctx: Pick<ExtensionContext, "cwd" | "isProjectTrusted">): Pr
|
|
|
315
330
|
const allowStale = resolveAllowStalePublishSetting(user, project);
|
|
316
331
|
const allowStaleApprovals = resolveAllowStaleApprovalsSetting(user, project);
|
|
317
332
|
const approveMaxPriority = resolveApproveMaxPriorityLevelSetting(user, project);
|
|
333
|
+
const defaultReviewMode = resolveDefaultReviewModeSetting(user, project);
|
|
318
334
|
return {
|
|
335
|
+
defaultReviewMode: defaultReviewMode.valid ? defaultReviewMode.value : "balanced",
|
|
319
336
|
tiers: { ...(user.tiers ?? {}), ...(project.tiers ?? {}) },
|
|
320
337
|
fallbacks: { ...normalizeFallbacks(user.fallbacks, true), ...normalizeFallbacks(project.fallbacks, true) },
|
|
321
338
|
thinkingLevels: {
|
|
@@ -345,7 +362,9 @@ function readUserConfig(): PrReviewConfig {
|
|
|
345
362
|
const allowStale = resolveAllowStalePublishSetting(raw);
|
|
346
363
|
const allowStaleApprovals = resolveAllowStaleApprovalsSetting(raw);
|
|
347
364
|
const approveMaxPriority = resolveApproveMaxPriorityLevelSetting(raw);
|
|
365
|
+
const defaultReviewMode = resolveDefaultReviewModeSetting(raw);
|
|
348
366
|
return {
|
|
367
|
+
defaultReviewMode: defaultReviewMode.valid ? defaultReviewMode.value : "balanced",
|
|
349
368
|
tiers: { ...(raw.tiers ?? {}) },
|
|
350
369
|
fallbacks: normalizeFallbacks(raw.fallbacks),
|
|
351
370
|
thinkingLevels: normalizeThinkingLevels(raw.thinkingLevels, "User pr-review config"),
|
|
@@ -2072,7 +2091,7 @@ export default function registerPrReviewSubagents(
|
|
|
2072
2091
|
});
|
|
2073
2092
|
|
|
2074
2093
|
pi.registerCommand("pr-review-config", {
|
|
2075
|
-
description: "Open review
|
|
2094
|
+
description: "Open review settings, or set the default mode, models, thinking, and publication policy",
|
|
2076
2095
|
handler: async (args, ctx) => {
|
|
2077
2096
|
// Extension commands execute before input events, so revoke explicitly.
|
|
2078
2097
|
loopCoordinator.clear();
|
|
@@ -2085,7 +2104,7 @@ export default function registerPrReviewSubagents(
|
|
|
2085
2104
|
}
|
|
2086
2105
|
|
|
2087
2106
|
try {
|
|
2088
|
-
// Direct set: `/pr-review-config light=... heavy
|
|
2107
|
+
// Direct set: `/pr-review-config default_review_mode=full light=... heavy=...`
|
|
2089
2108
|
if (parsed.hasChanges) {
|
|
2090
2109
|
const next = applyConfigPatch(readUserConfig(), parsed.patch);
|
|
2091
2110
|
writeUserConfig(next);
|
|
@@ -2145,6 +2164,7 @@ function shouldOpenConfigMenu(args: string, ctx: ExtensionContext): boolean {
|
|
|
2145
2164
|
}
|
|
2146
2165
|
|
|
2147
2166
|
const CONFIG_COMPLETIONS: Array<{ value: string; label: string }> = [
|
|
2167
|
+
{ value: "default_review_mode=", label: "default_review_mode=<quick|balanced|full|deep> — topology used when no mode flag is supplied" },
|
|
2148
2168
|
...TIERS.map((t) => ({ value: `${t}=`, label: `${t}=<model> — ${TIER_PURPOSE[t]}` })),
|
|
2149
2169
|
...TIERS.map((t) => ({
|
|
2150
2170
|
value: `${t}_fallbacks=`,
|
|
@@ -2233,6 +2253,7 @@ function tierFromCompoundKey(key: string): Tier {
|
|
|
2233
2253
|
}
|
|
2234
2254
|
|
|
2235
2255
|
interface ConfigPatch {
|
|
2256
|
+
defaultReviewMode?: ReviewMode;
|
|
2236
2257
|
tiers: Partial<Record<Tier, string | null>>;
|
|
2237
2258
|
fallbacks: Partial<Record<Tier, string[] | null>>;
|
|
2238
2259
|
thinkingLevels: Partial<Record<Tier, ThinkingLevel | null>>;
|
|
@@ -2256,7 +2277,11 @@ function parseConfigArgs(args: string): { patch: ConfigPatch; hasChanges: boolea
|
|
|
2256
2277
|
}
|
|
2257
2278
|
const key = token.slice(0, eq).replace(/^--?/, "").toLowerCase();
|
|
2258
2279
|
const value = token.slice(eq + 1);
|
|
2259
|
-
if (
|
|
2280
|
+
if (key === "default_review_mode" || key === "defaultreviewmode" || key === "default-review-mode") {
|
|
2281
|
+
const normalized = value.toLowerCase();
|
|
2282
|
+
if (REVIEW_MODES.includes(normalized as ReviewMode)) patch.defaultReviewMode = normalized as ReviewMode;
|
|
2283
|
+
else errors.push(`invalid ${key} "${value}" (expected ${REVIEW_MODES.join("|")})`);
|
|
2284
|
+
} else if ((TIERS as string[]).includes(key)) {
|
|
2260
2285
|
patch.tiers[key as Tier] = value === "" || value === "unset" ? null : value;
|
|
2261
2286
|
} else if (isFallbackKey(key)) {
|
|
2262
2287
|
const tier = tierFromCompoundKey(key);
|
|
@@ -2308,11 +2333,12 @@ function parseConfigArgs(args: string): { patch: ConfigPatch; hasChanges: boolea
|
|
|
2308
2333
|
patch.tools = splitCommaList(value);
|
|
2309
2334
|
} else {
|
|
2310
2335
|
errors.push(
|
|
2311
|
-
`unknown key "${key}" (expected light|medium|heavy|<tier>_fallbacks|<tier>_thinking|<tier>_tool_policy|auto_post_reviews|allow_stale_publish|allow_stale_approvals|approve_max_priority_level|tools)`,
|
|
2336
|
+
`unknown key "${key}" (expected default_review_mode|light|medium|heavy|<tier>_fallbacks|<tier>_thinking|<tier>_tool_policy|auto_post_reviews|allow_stale_publish|allow_stale_approvals|approve_max_priority_level|tools)`,
|
|
2312
2337
|
);
|
|
2313
2338
|
}
|
|
2314
2339
|
}
|
|
2315
2340
|
const hasChanges =
|
|
2341
|
+
patch.defaultReviewMode !== undefined ||
|
|
2316
2342
|
Object.keys(patch.tiers).length > 0 ||
|
|
2317
2343
|
Object.keys(patch.fallbacks).length > 0 ||
|
|
2318
2344
|
Object.keys(patch.thinkingLevels).length > 0 ||
|
|
@@ -2327,6 +2353,7 @@ function parseConfigArgs(args: string): { patch: ConfigPatch; hasChanges: boolea
|
|
|
2327
2353
|
|
|
2328
2354
|
function applyConfigPatch(base: PrReviewConfig, patch: ConfigPatch): PrReviewConfig {
|
|
2329
2355
|
const next: PrReviewConfig = {
|
|
2356
|
+
defaultReviewMode: base.defaultReviewMode,
|
|
2330
2357
|
tiers: { ...base.tiers },
|
|
2331
2358
|
fallbacks: normalizeFallbacks(base.fallbacks),
|
|
2332
2359
|
thinkingLevels: normalizeThinkingLevels(base.thinkingLevels, "PR review config"),
|
|
@@ -2339,6 +2366,7 @@ function applyConfigPatch(base: PrReviewConfig, patch: ConfigPatch): PrReviewCon
|
|
|
2339
2366
|
tools: [...base.tools],
|
|
2340
2367
|
deadlines: base.deadlines,
|
|
2341
2368
|
};
|
|
2369
|
+
if (patch.defaultReviewMode !== undefined) next.defaultReviewMode = patch.defaultReviewMode;
|
|
2342
2370
|
for (const tier of TIERS) {
|
|
2343
2371
|
if (tier in patch.tiers) {
|
|
2344
2372
|
const value = patch.tiers[tier];
|
|
@@ -2388,6 +2416,7 @@ function summarizeConfig(
|
|
|
2388
2416
|
} catch {
|
|
2389
2417
|
/* ignore */
|
|
2390
2418
|
}
|
|
2419
|
+
const defaultReviewMode = resolveDefaultReviewModeForContext(ctx);
|
|
2391
2420
|
const autoPost = resolveAutoPostForContext(ctx);
|
|
2392
2421
|
const allowStale = resolveAllowStaleForContext(ctx);
|
|
2393
2422
|
const allowStaleApprovals = resolveAllowStaleApprovalsForContext(ctx);
|
|
@@ -2401,6 +2430,7 @@ function summarizeConfig(
|
|
|
2401
2430
|
(t) =>
|
|
2402
2431
|
`| \`${t}\` | ${user.tiers[t] ? `\`${user.tiers[t]}\`` : "_unset_"} | ${effective.tiers[t] ? `\`${effective.tiers[t]}\`` : "_pi default_"} | ${TIER_PURPOSE[t]} |`,
|
|
2403
2432
|
),
|
|
2433
|
+
`| \`defaultReviewMode\` | \`${user.defaultReviewMode}\` | \`${effective.defaultReviewMode}\` (${defaultReviewMode.source}) | topology used when no explicit mode flag is supplied; default \`balanced\` |`,
|
|
2404
2434
|
`| \`autoPostReviews\` | \`${user.autoPostReviews}\` | \`${effective.autoPostReviews}\` (${autoPost.source}) | automatically post one GitHub review; default \`false\` |`,
|
|
2405
2435
|
`| \`allowStalePublish\` | \`${user.allowStalePublish}\` | \`${effective.allowStalePublish}\` (${allowStale.source}) | permit body-only stale publication with reviewed/current SHAs; default \`true\` |`,
|
|
2406
2436
|
`| \`allowStaleApprovals\` | \`${user.allowStaleApprovals}\` | \`${effective.allowStaleApprovals}\` (${allowStaleApprovals.source}) | permit qualified stale fully parsed reviews to record APPROVE; default \`false\` |`,
|
|
@@ -2420,6 +2450,10 @@ function summarizeConfig(
|
|
|
2420
2450
|
if (projectPath) lines.push(`Project overlay (trusted): \`${projectPath}\``);
|
|
2421
2451
|
const warnings = thinkingWarnings(effective);
|
|
2422
2452
|
if (warnings.length) lines.push("", ...warnings);
|
|
2453
|
+
if (!defaultReviewMode.valid) lines.push(`Default review mode config error: ${defaultReviewMode.error}`);
|
|
2454
|
+
else if (defaultReviewMode.source === "project") {
|
|
2455
|
+
lines.push("Default review mode is controlled by the trusted project overlay; this command edits user config only.");
|
|
2456
|
+
}
|
|
2423
2457
|
if (!autoPost.valid) lines.push(`Automatic posting config error: ${autoPost.error}`);
|
|
2424
2458
|
else if (autoPost.source === "project") {
|
|
2425
2459
|
lines.push("Automatic posting is controlled by the trusted project overlay; this command edits user config only.");
|
|
@@ -2441,7 +2475,8 @@ function summarizeConfig(
|
|
|
2441
2475
|
"## Usage",
|
|
2442
2476
|
"- Open the settings menu: `/pr-review-config`",
|
|
2443
2477
|
"- Print this summary: `/pr-review-config show`",
|
|
2444
|
-
"- Set
|
|
2478
|
+
"- Set the no-flag review topology: `/pr-review-config default_review_mode=balanced`",
|
|
2479
|
+
"- Set models directly: `/pr-review-config light=provider/model heavy=provider/model:high`",
|
|
2445
2480
|
"- Set fallback chain: `/pr-review-config heavy_fallbacks=provider/backup:high,provider/backup2`",
|
|
2446
2481
|
"- Set tier thinking: `/pr-review-config light_thinking=low medium_thinking=medium heavy_thinking=high`",
|
|
2447
2482
|
"- Enable automatic GitHub review posting: `/pr-review-config auto_post_reviews=true`",
|
|
@@ -2570,6 +2605,13 @@ function configMenuItems(cfg: PrReviewConfig, available: string[]): SettingItem[
|
|
|
2570
2605
|
const current = cfg.tools.join(",");
|
|
2571
2606
|
const toolValues = [current, ...TOOLS_PRESETS.filter((p) => p !== current)];
|
|
2572
2607
|
return [
|
|
2608
|
+
{
|
|
2609
|
+
id: "default_review_mode",
|
|
2610
|
+
label: "default review mode",
|
|
2611
|
+
description: "Topology used when /pr-review has no explicit mode flag. Enter/Space cycles values.",
|
|
2612
|
+
currentValue: cfg.defaultReviewMode,
|
|
2613
|
+
values: [...REVIEW_MODES],
|
|
2614
|
+
},
|
|
2573
2615
|
...tierItems,
|
|
2574
2616
|
...fallbackItems,
|
|
2575
2617
|
...thinkingItems,
|
|
@@ -2628,6 +2670,7 @@ async function showConfigMenu(
|
|
|
2628
2670
|
|
|
2629
2671
|
let settingsList: SettingsList;
|
|
2630
2672
|
const refresh = () => {
|
|
2673
|
+
settingsList.updateValue("default_review_mode", draft.defaultReviewMode);
|
|
2631
2674
|
for (const tier of TIERS) {
|
|
2632
2675
|
settingsList.updateValue(tier, draft.tiers[tier] ?? UNSET);
|
|
2633
2676
|
settingsList.updateValue(`${tier}_fallbacks`, draft.fallbacks[tier]?.join(",") || "(none)");
|
|
@@ -2641,7 +2684,13 @@ async function showConfigMenu(
|
|
|
2641
2684
|
};
|
|
2642
2685
|
|
|
2643
2686
|
const persist = (id: string, newValue: string) => {
|
|
2644
|
-
if (
|
|
2687
|
+
if (id === "default_review_mode") {
|
|
2688
|
+
if (!REVIEW_MODES.includes(newValue as ReviewMode)) {
|
|
2689
|
+
ctx.ui.notify(`Invalid review mode: ${newValue}`, "error");
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
draft.defaultReviewMode = newValue as ReviewMode;
|
|
2693
|
+
} else if ((TIERS as string[]).includes(id)) {
|
|
2645
2694
|
if (newValue === "__unset__") delete draft.tiers[id as Tier];
|
|
2646
2695
|
else draft.tiers[id as Tier] = newValue;
|
|
2647
2696
|
} else if (isFallbackKey(id)) {
|
|
@@ -2689,7 +2738,9 @@ async function showConfigMenu(
|
|
|
2689
2738
|
try {
|
|
2690
2739
|
writeUserConfig(draft);
|
|
2691
2740
|
refresh();
|
|
2692
|
-
const shown = id === "
|
|
2741
|
+
const shown = id === "default_review_mode"
|
|
2742
|
+
? draft.defaultReviewMode
|
|
2743
|
+
: id === "tools"
|
|
2693
2744
|
? draft.tools.join(",")
|
|
2694
2745
|
: id === "auto_post_reviews"
|
|
2695
2746
|
? String(draft.autoPostReviews)
|
|
@@ -2706,7 +2757,17 @@ async function showConfigMenu(
|
|
|
2706
2757
|
: isToolPolicyKey(id)
|
|
2707
2758
|
? (draft.toolPolicies[tierFromCompoundKey(id)] ?? INHERIT_TOOL_POLICY)
|
|
2708
2759
|
: (draft.tiers[id as Tier] ?? UNSET);
|
|
2709
|
-
if (id === "
|
|
2760
|
+
if (id === "default_review_mode") {
|
|
2761
|
+
const effective = resolveDefaultReviewModeForContext(ctx);
|
|
2762
|
+
if (effective.source === "project") {
|
|
2763
|
+
ctx.ui.notify(
|
|
2764
|
+
`User defaultReviewMode saved as ${shown}, but trusted project config remains effective at ${effective.value}. Edit ${projectConfigPath(ctx.cwd)}.`,
|
|
2765
|
+
"warning",
|
|
2766
|
+
);
|
|
2767
|
+
} else {
|
|
2768
|
+
ctx.ui.notify(`PR review config: ${id} = ${shown} (effective ${effective.value})`, "info");
|
|
2769
|
+
}
|
|
2770
|
+
} else if (id === "auto_post_reviews") {
|
|
2710
2771
|
const effective = resolveAutoPostForContext(ctx);
|
|
2711
2772
|
if (effective.source === "project") {
|
|
2712
2773
|
ctx.ui.notify(
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
resolveAutoPostSetting,
|
|
40
40
|
canonicalReviewSnapshot,
|
|
41
41
|
resolveApproveMaxPriorityLevelSetting,
|
|
42
|
+
resolveDefaultReviewModeSetting,
|
|
42
43
|
type PublishResult,
|
|
43
44
|
resolveRepositoryBinding,
|
|
44
45
|
resolveReviewHostBinding,
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
type CompletedReviewRecord,
|
|
51
52
|
type CompletedReviewSessionIdentity,
|
|
52
53
|
type ReviewInvocation,
|
|
54
|
+
type ReviewModeResolution,
|
|
53
55
|
} from "../lib/pr-review-publish.ts";
|
|
54
56
|
import { demoteHeadings, mergeExtractedFindings, safeReviewBody, synthesizeReviewArtifact, type ReviewSynthesisArtifact } from "../lib/pr-review-markdown.ts";
|
|
55
57
|
import { resolveReviewDeadlinesForContext } from "../lib/pr-review-deadline-config.ts";
|
|
@@ -373,6 +375,7 @@ function readJsonObject(filePath: string): ConfigReadResult {
|
|
|
373
375
|
}
|
|
374
376
|
|
|
375
377
|
interface PublishingConfigResolution {
|
|
378
|
+
defaultReviewMode: ReviewModeResolution;
|
|
376
379
|
autoPost: AutoPostResolution;
|
|
377
380
|
allowStale: AutoPostResolution;
|
|
378
381
|
allowStaleApprovals: AutoPostResolution;
|
|
@@ -382,7 +385,8 @@ interface PublishingConfigResolution {
|
|
|
382
385
|
function invalidPublishingConfig(source: "user" | "project", error: string): PublishingConfigResolution {
|
|
383
386
|
const invalid = { value: false, valid: false, source, error } as const;
|
|
384
387
|
const invalidLevel = { value: "off" as const, valid: false, source, error } as const;
|
|
385
|
-
|
|
388
|
+
const invalidMode = { value: "balanced" as const, valid: false, source, error } as const;
|
|
389
|
+
return { defaultReviewMode: invalidMode, autoPost: invalid, allowStale: invalid, allowStaleApprovals: invalid, approveMaxPriority: invalidLevel };
|
|
386
390
|
}
|
|
387
391
|
|
|
388
392
|
function resolvePublishingConfig(ctx: ExtensionContext): PublishingConfigResolution {
|
|
@@ -398,6 +402,7 @@ function resolvePublishingConfig(ctx: ExtensionContext): PublishingConfigResolut
|
|
|
398
402
|
/* user config only */
|
|
399
403
|
}
|
|
400
404
|
return {
|
|
405
|
+
defaultReviewMode: resolveDefaultReviewModeSetting(user.value, project?.value),
|
|
401
406
|
autoPost: resolveAutoPostSetting(user.value, project?.value),
|
|
402
407
|
allowStale: resolveAllowStalePublishSetting(user.value, project?.value),
|
|
403
408
|
allowStaleApprovals: resolveAllowStaleApprovalsSetting(user.value, project?.value),
|
|
@@ -911,8 +916,18 @@ export default function registerReviewTable(
|
|
|
911
916
|
return { action: "handled" as const };
|
|
912
917
|
}
|
|
913
918
|
|
|
914
|
-
// Freeze trusted publication config and target binding before review tools or optional PR code can run.
|
|
919
|
+
// Freeze trusted mode/publication config and target binding before review tools or optional PR code can run.
|
|
915
920
|
const publishingConfig = resolvePublishingConfig(ctx);
|
|
921
|
+
const explicitReviewMode = parsed.reviewMode;
|
|
922
|
+
if (explicitReviewMode === undefined && !publishingConfig.defaultReviewMode.valid) {
|
|
923
|
+
ctx.ui.notify(`Invalid /pr-review invocation: ${publishingConfig.defaultReviewMode.error}`, "error");
|
|
924
|
+
return { action: "handled" as const };
|
|
925
|
+
}
|
|
926
|
+
const resolvedReviewMode = explicitReviewMode ?? publishingConfig.defaultReviewMode.value;
|
|
927
|
+
const resolvedParsed = { ...parsed, reviewMode: resolvedReviewMode };
|
|
928
|
+
const transformedInput = explicitReviewMode === undefined
|
|
929
|
+
? `${event.text.trimEnd()} --${resolvedReviewMode}`
|
|
930
|
+
: event.text;
|
|
916
931
|
const deadlineResolution = resolveReviewDeadlinesForContext(ctx);
|
|
917
932
|
const budget = createReviewBudget(deadlineResolution);
|
|
918
933
|
const preflight = {
|
|
@@ -952,7 +967,7 @@ export default function registerReviewTable(
|
|
|
952
967
|
}
|
|
953
968
|
activePreflight = undefined;
|
|
954
969
|
const gate = loopCoordinator.begin(
|
|
955
|
-
|
|
970
|
+
resolvedParsed,
|
|
956
971
|
publishingConfig.autoPost,
|
|
957
972
|
source,
|
|
958
973
|
ctx,
|
|
@@ -969,6 +984,9 @@ export default function registerReviewTable(
|
|
|
969
984
|
persistTelemetry("cleared");
|
|
970
985
|
return { action: "handled" as const };
|
|
971
986
|
}
|
|
987
|
+
if (transformedInput !== event.text) {
|
|
988
|
+
return { action: "transform" as const, text: transformedInput, images: event.images };
|
|
989
|
+
}
|
|
972
990
|
});
|
|
973
991
|
|
|
974
992
|
pi.on("turn_start", (_event, ctx) => {
|
package/lib/pr-review-publish.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { monotonicNow, type MonotonicNow } from "./pr-review-telemetry.ts";
|
|
|
7
7
|
|
|
8
8
|
export type PublishMode = "auto" | "force" | "disabled";
|
|
9
9
|
export type ReviewMode = "quick" | "balanced" | "full" | "deep";
|
|
10
|
+
export const REVIEW_MODES: readonly ReviewMode[] = ["quick", "balanced", "full", "deep"];
|
|
10
11
|
export type AutoPostSource = "default" | "user" | "project";
|
|
11
12
|
export type CompletionAction = "continue_tools" | "accept_final" | "clear_invocation";
|
|
12
13
|
|
|
@@ -30,6 +31,47 @@ export interface ApproveMaxPriorityLevelResolution {
|
|
|
30
31
|
readonly error?: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
export interface ReviewModeResolution {
|
|
35
|
+
readonly value: ReviewMode;
|
|
36
|
+
readonly valid: boolean;
|
|
37
|
+
readonly source: AutoPostSource;
|
|
38
|
+
readonly error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isReviewMode(value: unknown): value is ReviewMode {
|
|
42
|
+
return typeof value === "string" && REVIEW_MODES.includes(value as ReviewMode);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Resolve the default reviewer topology, with a trusted project overlaying user config. */
|
|
46
|
+
export function resolveDefaultReviewModeSetting(
|
|
47
|
+
user: unknown,
|
|
48
|
+
trustedProject?: unknown,
|
|
49
|
+
): ReviewModeResolution {
|
|
50
|
+
if (hasOwn(trustedProject, "defaultReviewMode")) {
|
|
51
|
+
const value = (trustedProject as { defaultReviewMode?: unknown }).defaultReviewMode;
|
|
52
|
+
return isReviewMode(value)
|
|
53
|
+
? { value, valid: true, source: "project" }
|
|
54
|
+
: {
|
|
55
|
+
value: "balanced",
|
|
56
|
+
valid: false,
|
|
57
|
+
source: "project",
|
|
58
|
+
error: `project defaultReviewMode must be one of: ${REVIEW_MODES.join(", ")}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (hasOwn(user, "defaultReviewMode")) {
|
|
62
|
+
const value = (user as { defaultReviewMode?: unknown }).defaultReviewMode;
|
|
63
|
+
return isReviewMode(value)
|
|
64
|
+
? { value, valid: true, source: "user" }
|
|
65
|
+
: {
|
|
66
|
+
value: "balanced",
|
|
67
|
+
valid: false,
|
|
68
|
+
source: "user",
|
|
69
|
+
error: `user defaultReviewMode must be one of: ${REVIEW_MODES.join(", ")}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { value: "balanced", valid: true, source: "default" };
|
|
73
|
+
}
|
|
74
|
+
|
|
33
75
|
function isValidApproveLevel(value: unknown): value is ApproveMaxPriorityLevel {
|
|
34
76
|
return value === "off" || (typeof value === "string" && APPROVE_PRIORITY_LEVELS.includes(value as (typeof APPROVE_PRIORITY_LEVELS)[number]));
|
|
35
77
|
}
|
|
@@ -233,7 +275,15 @@ export function parsePublishMode(input: string): PublishModeParseResult {
|
|
|
233
275
|
return {
|
|
234
276
|
matched: true,
|
|
235
277
|
mode: disabled ? "disabled" : force ? "force" : "auto",
|
|
236
|
-
|
|
278
|
+
...(deep
|
|
279
|
+
? { reviewMode: "deep" as const }
|
|
280
|
+
: full
|
|
281
|
+
? { reviewMode: "full" as const }
|
|
282
|
+
: quick || majorOnly
|
|
283
|
+
? { reviewMode: "quick" as const }
|
|
284
|
+
: balanced
|
|
285
|
+
? { reviewMode: "balanced" as const }
|
|
286
|
+
: {}),
|
|
237
287
|
prNumber: requested,
|
|
238
288
|
allowNonOpen: tokens.includes("--include-closed") || tokens.includes("--review-closed"),
|
|
239
289
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-pr-review",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Parallel AI code review for GitHub pull requests in the Pi coding agent, with model-agnostic tiered subagents, structured findings, optional verification, and host-gated COMMENT or qualified APPROVE publishing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/prompts/pr-review.md
CHANGED
|
@@ -54,7 +54,7 @@ Arguments for this run: `$@`
|
|
|
54
54
|
- `$1` is the PR number (required).
|
|
55
55
|
- `--comment` explicitly requests one GitHub review for this run, even when automatic posting is disabled. A fully parsed, complete Markdown review may qualify for host-gated `APPROVE`; degraded Markdown remains `COMMENT`, and assistant text never directly selects the GitHub event.
|
|
56
56
|
- `--no-comment` suppresses posting for this run, even when automatic posting is enabled.
|
|
57
|
-
- With no review-mode flag,
|
|
57
|
+
- With no review-mode flag in the user's invocation, the host appends the configured default mode before expanding this prompt. The installation default is balanced.
|
|
58
58
|
- `--quick` selects three P0–P2 reviewers; `--major-only` is its compatibility alias.
|
|
59
59
|
- `--balanced` explicitly selects the five-reviewer default.
|
|
60
60
|
- `--full` selects six all-severity reviewers, including conventions/maintainability.
|