@unifan/pi-review-zh 1.0.25 → 1.0.26
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 +12 -85
- package/package.json +1 -1
- package/src/orchestration.ts +90 -0
- package/tests/orchestration.test.mjs +52 -0
package/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { Container, type SelectItem, SelectList, Text } from "@earendil-works/pi
|
|
|
26
26
|
import path from "node:path";
|
|
27
27
|
import os from "node:os";
|
|
28
28
|
import { promises as fs } from "node:fs";
|
|
29
|
+
import { buildSubagentOrchestrationPrompt } from "./src/orchestration.js";
|
|
29
30
|
|
|
30
31
|
// 跟踪审查会话来源分支节点(保证单次仅一个活跃审查会话)
|
|
31
32
|
let reviewOriginId: string | undefined = undefined;
|
|
@@ -77,89 +78,6 @@ async function saveSettings(settings: ReviewSettings): Promise<void> {
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
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: "输入参数合法性校验、跨目录边界防护与权限隔离",
|
|
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(
|
|
127
|
-
concurrency: number,
|
|
128
|
-
gateEnabled: boolean,
|
|
129
|
-
targetInstruction: string,
|
|
130
|
-
): string {
|
|
131
|
-
const count = Math.min(6, Math.max(2, concurrency));
|
|
132
|
-
const selected = ALL_EXPERTS.slice(0, count);
|
|
133
|
-
|
|
134
|
-
const listText = selected.map((exp, idx) => `${idx + 1}. **${exp.label}** (\`${exp.id}\`):${exp.desc}`).join("\n");
|
|
135
|
-
const callsExample = selected
|
|
136
|
-
.map((exp) => {
|
|
137
|
-
const fullTask = `${targetInstruction}\n\n【专项排查分工】:${exp.task}`;
|
|
138
|
-
return `subagent({ agent: ${JSON.stringify(exp.id)}, task: ${JSON.stringify(fullTask)} });`;
|
|
139
|
-
})
|
|
140
|
-
.join("\n");
|
|
141
|
-
|
|
142
|
-
const maxBackticks = (callsExample.match(/`+/g) || []).reduce((max, m) => Math.max(max, m.length), 2);
|
|
143
|
-
const fence = "`".repeat(maxBackticks + 1);
|
|
144
|
-
|
|
145
|
-
return `## 🚀 执行方式:多 Subagent 并发专家审查 (当前配置并发数: ${count} 个专家)
|
|
146
|
-
|
|
147
|
-
当前已配置并行启动以下 ${count} 个专家子代理进行分工审查:
|
|
148
|
-
|
|
149
|
-
${listText}
|
|
150
|
-
|
|
151
|
-
### 协作审查执行规范:
|
|
152
|
-
1. **并发调用子代理**:请在当前回合使用 \`subagent\` 工具**同时并行唤起**上述 ${count} 个专家子代理(单回合发起 ${count} 个并发 tool_call,严禁串行逐个调用):
|
|
153
|
-
${fence}js
|
|
154
|
-
${callsExample}
|
|
155
|
-
${fence}
|
|
156
|
-
2. **主审裁判长汇总整理**:当所有专家子代理执行完毕返回发现后,请你作为主审裁判长${gateEnabled ? "(门禁裁决)" : ""}:
|
|
157
|
-
- 全面综合各专家的审查意见,对相同问题进行去重,剔除误报和低置信度内容(注意:项目中合法的内部 GM / Debug 调试工具在确保与正式生产环境隔离的前提下免检)。
|
|
158
|
-
- 严格按照《核心代码审查准则》的 **[P0~P3]** 等级标准排布审查清单。
|
|
159
|
-
- 给出最终综合裁决与一句话中文总评。
|
|
160
|
-
3. **语言强制要求**:所有任务入参、思考分析过程、综合汇报与最终报告必须 100% 为纯正中文,严禁出现任何英文段落或未翻译小标题!`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
81
|
function setReviewWidget(ctx: ExtensionContext, active: boolean) {
|
|
164
82
|
if (!ctx.hasUI) return;
|
|
165
83
|
if (!active) {
|
|
@@ -1062,6 +980,13 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1062
980
|
...runtimeSettings,
|
|
1063
981
|
};
|
|
1064
982
|
|
|
983
|
+
const sessionModel = ctx.model;
|
|
984
|
+
const thinkingLevel = ctx.thinkingLevel ?? pi.getThinkingLevel();
|
|
985
|
+
if (settings.mode === "subagents" && !sessionModel) {
|
|
986
|
+
ctx.ui.notify("当前未选择模型,请先使用 /model 选择模型。", "error");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
1065
990
|
let effectiveFreshSession = useFreshSession;
|
|
1066
991
|
|
|
1067
992
|
if (effectiveFreshSession) {
|
|
@@ -1110,8 +1035,10 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1110
1035
|
|
|
1111
1036
|
let fullPrompt = REVIEW_RUBRIC;
|
|
1112
1037
|
|
|
1113
|
-
if (settings.mode === "subagents") {
|
|
1114
|
-
fullPrompt += `\n\n---\n\n${buildSubagentOrchestrationPrompt(
|
|
1038
|
+
if (settings.mode === "subagents" && sessionModel) {
|
|
1039
|
+
fullPrompt += `\n\n---\n\n${buildSubagentOrchestrationPrompt(
|
|
1040
|
+
settings.concurrency, settings.gateEnabled, prompt, sessionModel, thinkingLevel,
|
|
1041
|
+
)}`;
|
|
1115
1042
|
}
|
|
1116
1043
|
|
|
1117
1044
|
fullPrompt += `\n\n---\n\n## 本次审查任务指示\n\n${prompt}`;
|
package/package.json
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
interface ReviewExpert {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
desc: string;
|
|
5
|
+
task: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const ALL_EXPERTS: ReviewExpert[] = [
|
|
9
|
+
{
|
|
10
|
+
id: "pi-review.bugbot",
|
|
11
|
+
label: "Bug 猎手 (Bugbot)",
|
|
12
|
+
desc: "逻辑缺陷、空指针、边界溢出、死锁与运行时崩溃",
|
|
13
|
+
task: "深入排查本次代码改动中的业务逻辑缺陷、空指针、边界异常、并发竞态与未捕获的运行时异常",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "pi-review.security-review",
|
|
17
|
+
label: "安全专家 (Security)",
|
|
18
|
+
desc: "输入参数合法性校验、跨目录边界防护与权限隔离",
|
|
19
|
+
task: "深入排查本次代码改动中的防御性编码缺陷、外部输入未做类型/范围校验、边界防护不足等健壮性隐患",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "pi-review.perf-review",
|
|
23
|
+
label: "性能探针 (Perf)",
|
|
24
|
+
desc: "循环内GC内存分配、CPU热点消耗、算法复杂度与资源泄露",
|
|
25
|
+
task: "深入排查本次代码改动中的性能退化、高频循环内无谓内存分配 (GC压力) 与算法复杂度",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "pi-review.claude-md-compliance",
|
|
29
|
+
label: "契约合规 (Compliance)",
|
|
30
|
+
desc: "架构契约、设计模式、模块边界与规范遵循",
|
|
31
|
+
task: "排查本次代码改动是否违反项目既有架构契约、模块封装规范与规范指南",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "pi-review.code-comments",
|
|
35
|
+
label: "注释与可读性 (Comments)",
|
|
36
|
+
desc: "注释与代码逻辑倒挂、误导性命名与维护性隐患",
|
|
37
|
+
task: "排查本次代码改动中的可读性隐患、注释与逻辑不符、误导性命名与维护风险",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "pi-review.history-context",
|
|
41
|
+
label: "历史脉络 (History)",
|
|
42
|
+
desc: "结合 Git 历史演进判断意图,防止历史问题回归",
|
|
43
|
+
task: "结合代码演变历史,排查本次改动是否破坏既有历史契约或重现已知缺陷",
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
export function buildSubagentOrchestrationPrompt(
|
|
48
|
+
concurrency: number,
|
|
49
|
+
gateEnabled: boolean,
|
|
50
|
+
targetInstruction: string,
|
|
51
|
+
sessionModel: { provider: string; id: string },
|
|
52
|
+
thinkingLevel: string,
|
|
53
|
+
): string {
|
|
54
|
+
const count = Math.min(6, Math.max(2, concurrency));
|
|
55
|
+
const selected = ALL_EXPERTS.slice(0, count);
|
|
56
|
+
|
|
57
|
+
const listText = selected.map((exp, idx) => `${idx + 1}. **${exp.label}** (\`${exp.id}\`):${exp.desc}`).join("\n");
|
|
58
|
+
// 每次执行显式固定当前会话模型和档位,避免子代理默认配置覆盖会话选择。
|
|
59
|
+
const model = `${sessionModel.provider}/${sessionModel.id}:${thinkingLevel}`;
|
|
60
|
+
const tasks = selected.map((exp) => ({
|
|
61
|
+
key: exp.id,
|
|
62
|
+
agent: exp.id,
|
|
63
|
+
model,
|
|
64
|
+
task: `${targetInstruction}\n\n【专项排查分工】:${exp.task}`,
|
|
65
|
+
}));
|
|
66
|
+
const callsExample = `subagent(${JSON.stringify({
|
|
67
|
+
async: true,
|
|
68
|
+
workflowScript: `const results = await runs.all(${JSON.stringify(tasks)});\nreturn results;`,
|
|
69
|
+
}, null, 2)});`;
|
|
70
|
+
|
|
71
|
+
const maxBackticks = (callsExample.match(/`+/g) || []).reduce((max, m) => Math.max(max, m.length), 2);
|
|
72
|
+
const fence = "`".repeat(maxBackticks + 1);
|
|
73
|
+
|
|
74
|
+
return `## 🚀 执行方式:多 Subagent 并发专家审查 (当前配置并发数: ${count} 个专家)
|
|
75
|
+
|
|
76
|
+
当前已配置并行启动以下 ${count} 个专家子代理进行分工审查:
|
|
77
|
+
|
|
78
|
+
${listText}
|
|
79
|
+
|
|
80
|
+
### 协作审查执行规范:
|
|
81
|
+
1. **并发调用子代理**:先调用 \`subagent({ action: "list", capabilities: true })\`,确认上述专家均可执行且未禁用;再通过下方**一个异步工作流**并行启动全部专家。每个专家必须使用示例中的 \`model\` 参数,继承本次会话模型与思考档位(${thinkingLevel}),不得省略或改用代理默认档位。运行失败时报告具体原因并停止,不得静默更换模型或执行方式:
|
|
82
|
+
${fence}js
|
|
83
|
+
${callsExample}
|
|
84
|
+
${fence}
|
|
85
|
+
2. **主审裁判长汇总整理**:当所有专家子代理执行完毕返回发现后,请你作为主审裁判长${gateEnabled ? "(门禁裁决)" : ""}:
|
|
86
|
+
- 全面综合各专家的审查意见,对相同问题进行去重,剔除误报和低置信度内容(注意:项目中合法的内部 GM / Debug 调试工具在确保与正式生产环境隔离的前提下免检)。
|
|
87
|
+
- 严格按照《核心代码审查准则》的 **[P0~P3]** 等级标准排布审查清单。
|
|
88
|
+
- 给出最终综合裁决与一句话中文总评。
|
|
89
|
+
3. **语言强制要求**:所有任务入参、思考分析过程、综合汇报与最终报告必须 100% 为纯正中文,严禁出现任何英文段落或未翻译小标题!`;
|
|
90
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import ts from 'typescript';
|
|
5
|
+
|
|
6
|
+
const source = await readFile(new URL('../src/orchestration.ts', import.meta.url), 'utf8');
|
|
7
|
+
const compiled = ts.transpileModule(source, {
|
|
8
|
+
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext },
|
|
9
|
+
}).outputText;
|
|
10
|
+
const { buildSubagentOrchestrationPrompt } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
|
|
11
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
12
|
+
|
|
13
|
+
// 执行实际生成的工作流,使用假运行器捕获请求,不启动模型或子代理。
|
|
14
|
+
async function runExample(prompt) {
|
|
15
|
+
const example = prompt.match(/(`{3,})js\n([\s\S]*?)\n\1/);
|
|
16
|
+
assert.ok(example, '应包含可执行的工作流示例');
|
|
17
|
+
let request;
|
|
18
|
+
new Function('subagent', example[2])(value => { request = value; });
|
|
19
|
+
assert.equal(request.async, true);
|
|
20
|
+
let tasks;
|
|
21
|
+
const results = [{ output: '审查完成' }];
|
|
22
|
+
const actual = await new AsyncFunction('runs', request.workflowScript)({
|
|
23
|
+
all: async value => { tasks = value; return results; },
|
|
24
|
+
});
|
|
25
|
+
assert.equal(actual, results);
|
|
26
|
+
return tasks;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test('所有专家在不同并发数下继承会话模型及全部思考档位', async () => {
|
|
30
|
+
for (const thinking of ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']) {
|
|
31
|
+
for (const count of [2, 3, 4, 5, 6]) {
|
|
32
|
+
const prompt = buildSubagentOrchestrationPrompt(count, true, '检查当前差异', { provider: '会话提供方', id: '会话模型' }, thinking);
|
|
33
|
+
const tasks = await runExample(prompt);
|
|
34
|
+
assert.equal(tasks.length, count);
|
|
35
|
+
assert.equal(new Set(tasks.map(task => task.key)).size, count);
|
|
36
|
+
for (const task of tasks) {
|
|
37
|
+
assert.equal(task.model, `会话提供方/会话模型:${thinking}`);
|
|
38
|
+
assert.match(task.task, /检查当前差异/);
|
|
39
|
+
}
|
|
40
|
+
assert.match(prompt, /capabilities: true/);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('用户任务中的引号、换行和代码围栏不破坏工作流或模型参数', async () => {
|
|
46
|
+
const target = '检查 "路径"\n```ts\nconst value = `${input}`;\n```\n反斜线 \\';
|
|
47
|
+
const prompt = buildSubagentOrchestrationPrompt(3, false, target, { provider: '代理', id: '模型/别名' }, 'max');
|
|
48
|
+
const tasks = await runExample(prompt);
|
|
49
|
+
assert.ok(tasks.every(task => task.task.startsWith(target)));
|
|
50
|
+
assert.ok(tasks.every(task => task.model === '代理/模型/别名:max'));
|
|
51
|
+
assert.doesNotMatch(prompt, /(门禁裁决)/);
|
|
52
|
+
});
|