@unifan/pi-review-zh 1.0.12 → 1.0.13
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/index.ts +322 -40
- package/package.json +2 -2
package/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Pi 交互式 AI 代码审查扩展 (中文增强版)
|
|
3
3
|
*
|
|
4
4
|
* 参考并融合了 Codex 与 pi-agent-extensions (Armin Ronacher @mitsuhiko) 的经典架构:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 采用原生会话与会话分支隔离树技术,支持灵活的双执行引擎:
|
|
6
|
+
* 1. 【单模型原生审查】(默认推荐):0 依赖、0 网络断流、极速 100% 稳定。
|
|
7
|
+
* 2. 【多 Subagent 并发审查】:支持自由设置并发 2、3、4、5、6 个专家子代理协同会诊。
|
|
7
8
|
*
|
|
8
9
|
* 支持审查模式:
|
|
9
10
|
* - 审查当前未提交的改动 (工作区 + 暂存区)
|
|
@@ -23,6 +24,7 @@ import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@e
|
|
|
23
24
|
import { DynamicBorder, BorderedLoader } from "@earendil-works/pi-coding-agent";
|
|
24
25
|
import { Container, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
|
|
25
26
|
import path from "node:path";
|
|
27
|
+
import os from "node:os";
|
|
26
28
|
import { promises as fs } from "node:fs";
|
|
27
29
|
|
|
28
30
|
// 跟踪审查会话来源分支节点(保证单次仅一个活跃审查会话)
|
|
@@ -35,6 +37,117 @@ type ReviewSessionState = {
|
|
|
35
37
|
originId?: string;
|
|
36
38
|
};
|
|
37
39
|
|
|
40
|
+
export interface ReviewSettings {
|
|
41
|
+
/** 模式: "single" (单模型原生审查) | "subagents" (多 Subagent 并发审查) */
|
|
42
|
+
mode: "single" | "subagents";
|
|
43
|
+
/** 并发子代理专家数量 (支持 2, 3, 4, 5, 6) */
|
|
44
|
+
concurrency: number;
|
|
45
|
+
/** 是否启用门禁裁判长总结去重 */
|
|
46
|
+
gateEnabled: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DEFAULT_SETTINGS: ReviewSettings = {
|
|
50
|
+
mode: "single",
|
|
51
|
+
concurrency: 3,
|
|
52
|
+
gateEnabled: true,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "pi-review.json");
|
|
56
|
+
|
|
57
|
+
async function loadSettings(): Promise<ReviewSettings> {
|
|
58
|
+
try {
|
|
59
|
+
const raw = await fs.readFile(CONFIG_PATH, "utf8");
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
return {
|
|
62
|
+
mode: parsed.mode === "subagents" ? "subagents" : "single",
|
|
63
|
+
concurrency: Math.min(6, Math.max(2, typeof parsed.concurrency === "number" ? parsed.concurrency : 3)),
|
|
64
|
+
gateEnabled: parsed.gateEnabled !== false,
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
return { ...DEFAULT_SETTINGS };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function saveSettings(settings: ReviewSettings): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
await fs.mkdir(path.dirname(CONFIG_PATH), { recursive: true });
|
|
74
|
+
await fs.writeFile(CONFIG_PATH, JSON.stringify(settings, null, 2), "utf8");
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error("保存审查设置失败:", err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface ReviewExpert {
|
|
81
|
+
id: string;
|
|
82
|
+
label: string;
|
|
83
|
+
desc: string;
|
|
84
|
+
task: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ALL_EXPERTS: ReviewExpert[] = [
|
|
88
|
+
{
|
|
89
|
+
id: "pi-review.bugbot",
|
|
90
|
+
label: "Bug 猎手 (Bugbot)",
|
|
91
|
+
desc: "逻辑缺陷、空指针、越界越权、死锁与运行时崩溃",
|
|
92
|
+
task: "深入排查本次代码改动中的业务逻辑缺陷、空指针、越界、并发竞态与未捕获的运行时异常",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "pi-review.security-review",
|
|
96
|
+
label: "安全专家 (Security)",
|
|
97
|
+
desc: "未受信任外部输入、SQL注入、路径穿越与越权漏洞",
|
|
98
|
+
task: "深入排查本次代码改动中的安全隐患、外部输入未严格校验、未参数化语句与注入风险",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "pi-review.perf-review",
|
|
102
|
+
label: "性能探针 (Perf)",
|
|
103
|
+
desc: "循环内GC内存分配、CPU热点消耗、算法复杂度与资源泄露",
|
|
104
|
+
task: "深入排查本次代码改动中的性能退化、高频循环内无谓内存分配 (GC压力) 与算法复杂度",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "pi-review.claude-md-compliance",
|
|
108
|
+
label: "契约合规 (Compliance)",
|
|
109
|
+
desc: "架构契约、设计模式、模块边界与规范遵循",
|
|
110
|
+
task: "排查本次代码改动是否违反项目既有架构契约、模块封装规范与规范指南",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "pi-review.code-comments",
|
|
114
|
+
label: "注释与可读性 (Comments)",
|
|
115
|
+
desc: "注释与代码逻辑倒挂、误导性命名与维护性隐患",
|
|
116
|
+
task: "排查本次代码改动中的可读性隐患、注释与逻辑不符、误导性命名与维护风险",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "pi-review.history-context",
|
|
120
|
+
label: "历史脉络 (History)",
|
|
121
|
+
desc: "结合 Git 历史演进判断意图,防止历史问题回归",
|
|
122
|
+
task: "结合代码演变历史,排查本次改动是否破坏既有历史契约或重现已知缺陷",
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
function buildSubagentOrchestrationPrompt(concurrency: number, gateEnabled: boolean): string {
|
|
127
|
+
const count = Math.min(6, Math.max(2, concurrency));
|
|
128
|
+
const selected = ALL_EXPERTS.slice(0, count);
|
|
129
|
+
|
|
130
|
+
const listText = selected.map((exp, idx) => `${idx + 1}. **${exp.label}** (\`${exp.id}\`):${exp.desc}`).join("\n");
|
|
131
|
+
const callsExample = selected
|
|
132
|
+
.map((exp) => ` - \`subagent({ agent: "${exp.id}", task: "${exp.task}" })\``)
|
|
133
|
+
.join("\n");
|
|
134
|
+
|
|
135
|
+
return `## 🚀 执行方式:多 Subagent 并发专家审查 (当前配置并发数: ${count} 个专家)
|
|
136
|
+
|
|
137
|
+
当前已配置并行启动以下 ${count} 个专家子代理进行分工审查:
|
|
138
|
+
|
|
139
|
+
${listText}
|
|
140
|
+
|
|
141
|
+
### 协作审查执行规范:
|
|
142
|
+
1. **并发调用子代理**:请在当前回合使用 \`subagent\` 工具**同时并行唤起**上述 ${count} 个专家子代理(单回合发起 ${count} 个并发 tool_call,严禁串行逐个调用):
|
|
143
|
+
${callsExample}
|
|
144
|
+
2. **主审裁判长汇总整理**:当所有专家子代理执行完毕返回发现后,请你作为主审裁判长${gateEnabled ? "(门禁裁决)" : ""}:
|
|
145
|
+
- 全面综合各专家的审查意见,对相同问题进行去重,剔除误报和低置信度内容。
|
|
146
|
+
- 严格按照《核心代码审查准则》的 **[P0~P3]** 等级标准排布审查清单。
|
|
147
|
+
- 给出最终综合裁决与一句话中文总评。
|
|
148
|
+
3. **语言要求**:所有输出必须为纯正中文。`;
|
|
149
|
+
}
|
|
150
|
+
|
|
38
151
|
function setReviewWidget(ctx: ExtensionContext, active: boolean) {
|
|
39
152
|
if (!ctx.hasUI) return;
|
|
40
153
|
if (!active) {
|
|
@@ -387,8 +500,100 @@ const REVIEW_PRESETS = [
|
|
|
387
500
|
{ value: "pullRequest", label: "审查 Pull Request", description: "(输入 PR 编号或 GitHub URL 本地检出)" },
|
|
388
501
|
{ value: "folder", label: "审查指定目录/文件", description: "(静态快照审查,非 diff)" },
|
|
389
502
|
{ value: "custom", label: "自定义审查要求", description: "(输入针对性的安全/性能侧重点)" },
|
|
503
|
+
{ value: "config", label: "⚙️ 审查配置中心", description: "(切换单模型 / 多Subagent并发 2~6个)" },
|
|
390
504
|
] as const;
|
|
391
505
|
|
|
506
|
+
/**
|
|
507
|
+
* 弹出交互式审查配置界面
|
|
508
|
+
*/
|
|
509
|
+
async function showConfigDialog(ctx: ExtensionContext): Promise<void> {
|
|
510
|
+
const settings = await loadSettings();
|
|
511
|
+
|
|
512
|
+
while (true) {
|
|
513
|
+
const modeDesc = settings.mode === "single" ? "单模型直接审查 (极速 100% 稳定)" : `多 Subagent 并发 (${settings.concurrency} 个专家)`;
|
|
514
|
+
const gateDesc = settings.gateEnabled ? "开启" : "关闭";
|
|
515
|
+
|
|
516
|
+
const menuItems: SelectItem[] = [
|
|
517
|
+
{
|
|
518
|
+
value: "toggle-mode",
|
|
519
|
+
label: `1. 审查引擎: [${settings.mode === "single" ? "单模型直接审查" : "多Subagent并发"}]`,
|
|
520
|
+
description: modeDesc,
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
value: "concurrency",
|
|
524
|
+
label: `2. 并发子代理数: [${settings.concurrency} 个专家]`,
|
|
525
|
+
description: "可自由设置并发 2、3、4、5、6 个专家子代理",
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
value: "gate",
|
|
529
|
+
label: `3. 门禁裁判长去重: [${gateDesc}]`,
|
|
530
|
+
description: "在多专家返回后由主审裁判长汇总去重并定级",
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
value: "save",
|
|
534
|
+
label: "✅ 保存配置并退出",
|
|
535
|
+
description: "将当前设置保存到 ~/.pi/agent/pi-review.json",
|
|
536
|
+
},
|
|
537
|
+
];
|
|
538
|
+
|
|
539
|
+
const choice = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
540
|
+
const container = new Container();
|
|
541
|
+
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
542
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("⚙️ 代码审查配置中心"))));
|
|
543
|
+
|
|
544
|
+
const selectList = new SelectList(menuItems, menuItems.length, {
|
|
545
|
+
selectedPrefix: (text) => theme.fg("accent", text),
|
|
546
|
+
selectedText: (text) => theme.fg("accent", text),
|
|
547
|
+
description: (text) => theme.fg("muted", text),
|
|
548
|
+
scrollInfo: (text) => theme.fg("dim", text),
|
|
549
|
+
noMatch: (text) => theme.fg("warning", text),
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
selectList.onSelect = (item) => done(item.value);
|
|
553
|
+
selectList.onCancel = () => done(null);
|
|
554
|
+
|
|
555
|
+
container.addChild(selectList);
|
|
556
|
+
container.addChild(new Text(theme.fg("dim", "方向键选择 • 回车修改/确认 • ESC 取消")));
|
|
557
|
+
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
render(w) { return container.render(w); },
|
|
561
|
+
invalidate() { container.invalidate(); },
|
|
562
|
+
handleInput(d) { selectList.handleInput(d); tui.requestRender(); },
|
|
563
|
+
};
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
if (!choice || choice === "save") {
|
|
567
|
+
await saveSettings(settings);
|
|
568
|
+
ctx.ui.notify(`已保存审查设置:${settings.mode === "single" ? "单模型直接审查" : `多Subagent模式 (${settings.concurrency} 并发)`}`, "info");
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (choice === "toggle-mode") {
|
|
573
|
+
settings.mode = settings.mode === "single" ? "subagents" : "single";
|
|
574
|
+
} else if (choice === "concurrency") {
|
|
575
|
+
const concurrencyChoice = await ctx.ui.select(
|
|
576
|
+
"请选择并发 Subagent 专家数量 (2 ~ 6 个):",
|
|
577
|
+
[
|
|
578
|
+
"2 个专家 (Bug猎手 + 安全专家 · 轻量低延迟)",
|
|
579
|
+
"3 个专家 (Bug猎手 + 安全专家 + 性能探针 · 均衡推荐)",
|
|
580
|
+
"4 个专家 (+ 契约规范合规)",
|
|
581
|
+
"5 个专家 (+ 注释与代码可读性)",
|
|
582
|
+
"6 个专家 (全量 6 大专家深度会诊)",
|
|
583
|
+
]
|
|
584
|
+
);
|
|
585
|
+
if (concurrencyChoice !== undefined) {
|
|
586
|
+
const num = Number.parseInt(concurrencyChoice.slice(0, 1), 10);
|
|
587
|
+
if (num >= 2 && num <= 6) {
|
|
588
|
+
settings.concurrency = num;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} else if (choice === "gate") {
|
|
592
|
+
settings.gateEnabled = !settings.gateEnabled;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
392
597
|
export default function reviewExtension(pi: ExtensionAPI) {
|
|
393
598
|
pi.on("session_start", (_event, ctx) => {
|
|
394
599
|
applyReviewState(ctx);
|
|
@@ -412,23 +617,28 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
412
617
|
|
|
413
618
|
async function showReviewSelector(ctx: ExtensionContext): Promise<ReviewTarget | null> {
|
|
414
619
|
const smartDefault = await getSmartDefault();
|
|
415
|
-
const
|
|
416
|
-
.sort((a, b) => {
|
|
417
|
-
if (a.value === smartDefault) return -1;
|
|
418
|
-
if (b.value === smartDefault) return 1;
|
|
419
|
-
return 0;
|
|
420
|
-
})
|
|
421
|
-
.map((preset) => ({
|
|
422
|
-
value: preset.value,
|
|
423
|
-
label: preset.label,
|
|
424
|
-
description: preset.description,
|
|
425
|
-
}));
|
|
620
|
+
const currentSettings = await loadSettings();
|
|
426
621
|
|
|
427
622
|
while (true) {
|
|
623
|
+
const modeTag = currentSettings.mode === "single" ? "单模型模式" : `${currentSettings.concurrency} 专家并发`;
|
|
624
|
+
const items: SelectItem[] = REVIEW_PRESETS.slice()
|
|
625
|
+
.sort((a, b) => {
|
|
626
|
+
if (a.value === "config") return 1;
|
|
627
|
+
if (b.value === "config") return -1;
|
|
628
|
+
if (a.value === smartDefault) return -1;
|
|
629
|
+
if (b.value === smartDefault) return 1;
|
|
630
|
+
return 0;
|
|
631
|
+
})
|
|
632
|
+
.map((preset) => ({
|
|
633
|
+
value: preset.value,
|
|
634
|
+
label: preset.label,
|
|
635
|
+
description: preset.value === "config" ? `[当前: ${modeTag}]` : preset.description,
|
|
636
|
+
}));
|
|
637
|
+
|
|
428
638
|
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
429
639
|
const container = new Container();
|
|
430
640
|
container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
|
|
431
|
-
container.addChild(new Text(theme.fg("accent", theme.bold(
|
|
641
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(`请选择代码审查模式 [${modeTag}]`))));
|
|
432
642
|
|
|
433
643
|
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
434
644
|
selectedPrefix: (text) => theme.fg("accent", text),
|
|
@@ -461,6 +671,16 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
461
671
|
|
|
462
672
|
if (!result) return null;
|
|
463
673
|
|
|
674
|
+
if (result === "config") {
|
|
675
|
+
await showConfigDialog(ctx);
|
|
676
|
+
// 重新加载配置并循环展示主菜单
|
|
677
|
+
const updated = await loadSettings();
|
|
678
|
+
currentSettings.mode = updated.mode;
|
|
679
|
+
currentSettings.concurrency = updated.concurrency;
|
|
680
|
+
currentSettings.gateEnabled = updated.gateEnabled;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
|
|
464
684
|
switch (result) {
|
|
465
685
|
case "uncommitted":
|
|
466
686
|
return { type: "uncommitted" };
|
|
@@ -692,12 +912,23 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
692
912
|
};
|
|
693
913
|
}
|
|
694
914
|
|
|
695
|
-
async function executeReview(
|
|
915
|
+
async function executeReview(
|
|
916
|
+
ctx: ExtensionCommandContext,
|
|
917
|
+
target: ReviewTarget,
|
|
918
|
+
useFreshSession: boolean,
|
|
919
|
+
runtimeSettings?: Partial<ReviewSettings>,
|
|
920
|
+
): Promise<void> {
|
|
696
921
|
if (reviewOriginId) {
|
|
697
922
|
ctx.ui.notify("当前已有正在进行的审查会话。请先使用 /end-review 结束。", "warning");
|
|
698
923
|
return;
|
|
699
924
|
}
|
|
700
925
|
|
|
926
|
+
const baseSettings = await loadSettings();
|
|
927
|
+
const settings: ReviewSettings = {
|
|
928
|
+
...baseSettings,
|
|
929
|
+
...runtimeSettings,
|
|
930
|
+
};
|
|
931
|
+
|
|
701
932
|
if (useFreshSession) {
|
|
702
933
|
const originId = ctx.sessionManager.getLeafId() ?? undefined;
|
|
703
934
|
if (!originId) {
|
|
@@ -738,74 +969,113 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
738
969
|
const hint = getUserFacingHint(target);
|
|
739
970
|
const projectGuidelines = await loadProjectReviewGuidelines(ctx.cwd);
|
|
740
971
|
|
|
741
|
-
let fullPrompt =
|
|
972
|
+
let fullPrompt = REVIEW_RUBRIC;
|
|
973
|
+
|
|
974
|
+
if (settings.mode === "subagents") {
|
|
975
|
+
fullPrompt += `\n\n---\n\n${buildSubagentOrchestrationPrompt(settings.concurrency, settings.gateEnabled)}`;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
fullPrompt += `\n\n---\n\n## 本次审查目标与任务指示\n\n${prompt}`;
|
|
742
979
|
|
|
743
980
|
if (projectGuidelines) {
|
|
744
981
|
fullPrompt += `\n\n## 本项目附加规范指南\n\n${projectGuidelines}`;
|
|
745
982
|
}
|
|
746
983
|
|
|
984
|
+
const modeLabel = settings.mode === "single" ? "单模型模式" : `多Subagent并发(${settings.concurrency}专家)`;
|
|
747
985
|
const modeHint = useFreshSession ? " (独立审查分支)" : "";
|
|
748
|
-
ctx.ui.notify(`正在启动代码审查: ${hint}${modeHint}`, "info");
|
|
986
|
+
ctx.ui.notify(`正在启动代码审查: ${hint}${modeHint} [${modeLabel}]`, "info");
|
|
749
987
|
|
|
750
988
|
pi.sendUserMessage(fullPrompt);
|
|
751
989
|
}
|
|
752
990
|
|
|
753
|
-
function parseArgs(args: string | undefined):
|
|
754
|
-
|
|
991
|
+
function parseArgs(args: string | undefined): {
|
|
992
|
+
target: ReviewTarget | { type: "pr"; ref: string } | null;
|
|
993
|
+
settingsOverride: Partial<ReviewSettings>;
|
|
994
|
+
} {
|
|
995
|
+
const settingsOverride: Partial<ReviewSettings> = {};
|
|
996
|
+
if (!args?.trim()) return { target: null, settingsOverride };
|
|
997
|
+
|
|
998
|
+
const rawParts = args.trim().split(/\s+/);
|
|
999
|
+
const parts: string[] = [];
|
|
1000
|
+
|
|
1001
|
+
for (let i = 0; i < rawParts.length; i++) {
|
|
1002
|
+
const p = rawParts[i];
|
|
1003
|
+
if (p === "--subagents" || p === "-s") {
|
|
1004
|
+
settingsOverride.mode = "subagents";
|
|
1005
|
+
} else if (p === "--single") {
|
|
1006
|
+
settingsOverride.mode = "single";
|
|
1007
|
+
} else if (p === "--concurrency" || p === "-c") {
|
|
1008
|
+
const next = rawParts[i + 1];
|
|
1009
|
+
if (next) {
|
|
1010
|
+
const num = Number.parseInt(next, 10);
|
|
1011
|
+
if (num >= 2 && num <= 6) {
|
|
1012
|
+
settingsOverride.concurrency = num;
|
|
1013
|
+
settingsOverride.mode = "subagents";
|
|
1014
|
+
i++;
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
} else {
|
|
1019
|
+
parts.push(p);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
if (parts.length === 0) {
|
|
1024
|
+
return { target: null, settingsOverride };
|
|
1025
|
+
}
|
|
755
1026
|
|
|
756
|
-
const parts = args.trim().split(/\s+/);
|
|
757
1027
|
const subcommand = parts[0]?.toLowerCase();
|
|
758
1028
|
|
|
759
1029
|
switch (subcommand) {
|
|
760
1030
|
case "uncommitted":
|
|
761
1031
|
case "--uncommitted":
|
|
762
1032
|
case "diff":
|
|
763
|
-
return { type: "uncommitted" };
|
|
1033
|
+
return { target: { type: "uncommitted" }, settingsOverride };
|
|
764
1034
|
|
|
765
1035
|
case "branch":
|
|
766
1036
|
case "--branch": {
|
|
767
1037
|
const branch = parts[1];
|
|
768
|
-
if (!branch) return null;
|
|
769
|
-
return { type: "baseBranch", branch };
|
|
1038
|
+
if (!branch) return { target: null, settingsOverride };
|
|
1039
|
+
return { target: { type: "baseBranch", branch }, settingsOverride };
|
|
770
1040
|
}
|
|
771
1041
|
|
|
772
1042
|
case "commit":
|
|
773
1043
|
case "--commit": {
|
|
774
1044
|
const sha = parts[1];
|
|
775
|
-
if (!sha) return null;
|
|
1045
|
+
if (!sha) return { target: null, settingsOverride };
|
|
776
1046
|
const title = parts.slice(2).join(" ") || undefined;
|
|
777
|
-
return { type: "commit", sha, title };
|
|
1047
|
+
return { target: { type: "commit", sha, title }, settingsOverride };
|
|
778
1048
|
}
|
|
779
1049
|
|
|
780
1050
|
case "custom":
|
|
781
1051
|
case "--custom": {
|
|
782
1052
|
const instructions = parts.slice(1).join(" ");
|
|
783
|
-
if (!instructions) return null;
|
|
784
|
-
return { type: "custom", instructions };
|
|
1053
|
+
if (!instructions) return { target: null, settingsOverride };
|
|
1054
|
+
return { target: { type: "custom", instructions }, settingsOverride };
|
|
785
1055
|
}
|
|
786
1056
|
|
|
787
1057
|
case "folder":
|
|
788
1058
|
case "--folder": {
|
|
789
1059
|
const paths = parseReviewPaths(parts.slice(1).join(" "));
|
|
790
|
-
if (paths.length === 0) return null;
|
|
791
|
-
return { type: "folder", paths };
|
|
1060
|
+
if (paths.length === 0) return { target: null, settingsOverride };
|
|
1061
|
+
return { target: { type: "folder", paths }, settingsOverride };
|
|
792
1062
|
}
|
|
793
1063
|
|
|
794
1064
|
case "pr":
|
|
795
1065
|
case "--pr": {
|
|
796
1066
|
const ref = parts[1];
|
|
797
|
-
if (!ref) return null;
|
|
798
|
-
return { type: "pr", ref };
|
|
1067
|
+
if (!ref) return { target: null, settingsOverride };
|
|
1068
|
+
return { target: { type: "pr", ref }, settingsOverride };
|
|
799
1069
|
}
|
|
800
1070
|
|
|
801
1071
|
default:
|
|
802
|
-
return { type: "custom", instructions:
|
|
1072
|
+
return { target: { type: "custom", instructions: parts.join(" ") }, settingsOverride };
|
|
803
1073
|
}
|
|
804
1074
|
}
|
|
805
1075
|
|
|
806
1076
|
// 注册 /review 主命令
|
|
807
1077
|
pi.registerCommand("review", {
|
|
808
|
-
description: "启动 AI 代码审查 (交互式选择:未提交改动/分支对比/指定Commit/PR
|
|
1078
|
+
description: "启动 AI 代码审查 (交互式选择:未提交改动/分支对比/指定Commit/PR/目录/自定义/配置)",
|
|
809
1079
|
handler: async (args, ctx) => {
|
|
810
1080
|
if (!ctx.hasUI) {
|
|
811
1081
|
ctx.ui.notify("代码审查需要交互式终端环境", "error");
|
|
@@ -825,16 +1095,16 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
825
1095
|
|
|
826
1096
|
let target: ReviewTarget | null = null;
|
|
827
1097
|
let fromSelector = false;
|
|
828
|
-
const
|
|
1098
|
+
const { target: parsedTarget, settingsOverride } = parseArgs(args);
|
|
829
1099
|
|
|
830
|
-
if (
|
|
831
|
-
if (
|
|
832
|
-
target = await handlePrCheckout(ctx,
|
|
1100
|
+
if (parsedTarget) {
|
|
1101
|
+
if (parsedTarget.type === "pr") {
|
|
1102
|
+
target = await handlePrCheckout(ctx, parsedTarget.ref);
|
|
833
1103
|
if (!target) {
|
|
834
1104
|
ctx.ui.notify("PR 检出失败,返回主菜单。", "warning");
|
|
835
1105
|
}
|
|
836
1106
|
} else {
|
|
837
|
-
target =
|
|
1107
|
+
target = parsedTarget;
|
|
838
1108
|
}
|
|
839
1109
|
}
|
|
840
1110
|
|
|
@@ -875,15 +1145,27 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
875
1145
|
useFreshSession = choice.startsWith("独立分支审查");
|
|
876
1146
|
}
|
|
877
1147
|
|
|
878
|
-
await executeReview(ctx, target, useFreshSession);
|
|
1148
|
+
await executeReview(ctx, target, useFreshSession, settingsOverride);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
// 注册 /review-config 配置命令
|
|
1155
|
+
pi.registerCommand("review-config", {
|
|
1156
|
+
description: "配置代码审查模式 (切换单模型直接审查 / 多 Subagent 并发 2~6 个专家)",
|
|
1157
|
+
handler: async (_args, ctx) => {
|
|
1158
|
+
if (!ctx.hasUI) {
|
|
1159
|
+
ctx.ui.notify("review-config 需要交互式终端环境", "error");
|
|
879
1160
|
return;
|
|
880
1161
|
}
|
|
1162
|
+
await showConfigDialog(ctx);
|
|
881
1163
|
},
|
|
882
1164
|
});
|
|
883
1165
|
|
|
884
1166
|
// 极速单兵别名命令:/review-lite
|
|
885
1167
|
pi.registerCommand("review-lite", {
|
|
886
|
-
description: "极速代码审查 (
|
|
1168
|
+
description: "极速代码审查 (直接对当前工作区未提交改动做审查,无需弹窗选择)",
|
|
887
1169
|
handler: async (_args, ctx) => {
|
|
888
1170
|
if (!ctx.hasUI) {
|
|
889
1171
|
ctx.ui.notify("代码审查需要交互式终端环境", "error");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unifan/pi-review-zh",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Pi 交互式 AI
|
|
3
|
+
"version": "1.0.13",
|
|
4
|
+
"description": "Pi 交互式 AI 代码审查扩展(支持单模型与多 Subagent 并发 2~6 专家自由切换,Codex 经典架构)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "821869798",
|