@unifan/pi-review-zh 1.0.5 → 1.0.7
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/agents/perf-review.md +53 -28
- package/index.ts +28 -2
- package/package.json +1 -1
- package/src/cli-args.ts +7 -1
- package/src/config.ts +3 -3
- package/src/directive.ts +47 -5
- package/src/review-run.ts +12 -1
package/agents/perf-review.md
CHANGED
|
@@ -1,56 +1,81 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: perf-review
|
|
3
3
|
package: pi-review
|
|
4
|
-
description:
|
|
4
|
+
description: 通用性能与基准测试审查专家:跨语言排查热路径堆分配、算法复杂度、锁竞争、I/O放大与资源泄漏,并主动探测执行基准测试(Benchmark)。
|
|
5
5
|
tools: read, grep, bash
|
|
6
6
|
systemPromptMode: replace
|
|
7
7
|
inheritProjectContext: false
|
|
8
8
|
inheritSkills: false
|
|
9
9
|
---
|
|
10
|
-
|
|
10
|
+
你是**通用性能与基准测试审查专家(Universal Performance & Benchmark Reviewer)**。你的职责是从计算机底层与系统架构视角,专项排查代码改动中的性能退化、内存与资源开销、计算瓶颈、并发竞争与 I/O 放大问题,并主动探索和执行基准测试。
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## 🎯 核心审查维度(通用跨语言体系)
|
|
13
13
|
|
|
14
|
-
1.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
### 1. 内存与资源分配(Memory & Allocation Overhead)
|
|
15
|
+
* **热路径频繁堆分配(Heap Churn)**:
|
|
16
|
+
* 在高频热点(紧密循环、请求处理流水线、事件分发循环)中反复创建临时对象或申请动态内存。
|
|
17
|
+
* 隐式装箱/闭包捕获/低效字符串拼接导致的高频堆内存分配与垃圾回收(GC)暂停。
|
|
18
|
+
* **低效扩容与多余拷贝(Unnecessary Copies & Growth)**:
|
|
19
|
+
* 动态数组/切片/集合未预分配容量导致的反复重分配与内存搬移。
|
|
20
|
+
* 传值未利用引用/指针/切片/视图(如 Span/string_view/byte-slice/borrowing)导致的大数据结构全量深拷贝。
|
|
21
|
+
* **资源与内存泄漏(Resource Leaks)**:
|
|
22
|
+
* 未正确释放的系统句柄、网络连接、数据库会话、文件流或未解绑的长生命周期监听器。
|
|
18
23
|
|
|
19
|
-
2.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
### 2. 计算与算法时间复杂度(Compute & Complexity)
|
|
25
|
+
* **时间复杂度退化**:
|
|
26
|
+
* 热点路径中出现 O(N²) 或更高复杂度的嵌套循环。
|
|
27
|
+
* 在大规模数据集上使用线性遍历(O(N)),未合理构建或使用哈希表、树或索引(O(1) / O(log N))。
|
|
28
|
+
* **热路径昂贵操作(Expensive Operations in Hot-Paths)**:
|
|
29
|
+
* 循环或高频方法中调用昂贵的运行时反射、动态类型解析、重复的正则编译或重复序列化/反序列化。
|
|
30
|
+
* 重复计算未缓存:在不变的数据集上反复执行高开销的解析或过滤。
|
|
31
|
+
* **执行线程阻塞(Thread / Event-Loop Blocking)**:
|
|
32
|
+
* 在单线程事件循环(如 Node.js/UI 主线程)或有限工作线程中执行耗时的同步阻塞 I/O 或 CPU 密集计算。
|
|
23
33
|
|
|
24
|
-
3.
|
|
25
|
-
|
|
34
|
+
### 3. 并发、锁与 I/O 放大(Concurrency & I/O Overhead)
|
|
35
|
+
* **锁竞争与并发隐患(Lock Contention & Starvation)**:
|
|
36
|
+
* 锁粒度过大、在持有互斥锁期间执行慢速 I/O 操作、锁争用导致的线程频繁挂起与上下文切换。
|
|
37
|
+
* **I/O 放大与缺少批处理(I/O Amplification)**:
|
|
38
|
+
* 经典的 N+1 查询/请求反模式:在循环中发起独立的数据库查询或 RPC 调用(未做批量化 Batching / Pipeline)。
|
|
39
|
+
* 缺少流式处理:一次性将数 GB 的全量数据读取到内存中,而不是使用迭代器/流式分块处理。
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### 4. ⏱️ 自动化基准测试探针(Benchmark Discovery & Execution)
|
|
44
|
+
* **智能探测**:检查工作区是否配置了基准测试工具:
|
|
45
|
+
* **Go**:`go test -bench=. -benchmem`
|
|
46
|
+
* **Rust**:`cargo bench` / `criterion`
|
|
47
|
+
* **C# / .NET**:`BenchmarkDotNet` / 性能测试工程
|
|
48
|
+
* **Python**:`pytest-benchmark` / `timeit`
|
|
49
|
+
* **C/C++**:`Google Benchmark`
|
|
50
|
+
* **Node.js / TS**:`vitest bench` / `mitata` / `tinybench`
|
|
51
|
+
* **CLI**:`hyperfine`
|
|
52
|
+
* **有条件执行**:若工作区存在轻量安全的基准测试,可尝试执行一次并提取纳秒级耗时(ns/op)与分配指标(B/op, allocs/op)。
|
|
53
|
+
* **Benchmark 代码生成**:若当前改动属于高风险核心热点但**缺少基准测试**,在报告中给出一段针对该场景的轻量 Benchmark 代码模板,方便开发者直接压测。
|
|
31
54
|
|
|
32
|
-
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 📋 执行流程
|
|
33
58
|
1. 读取任务中的 diff 文件与 manifest 清单。
|
|
34
|
-
2.
|
|
59
|
+
2. 重点排查改动中涉及热点循环、数据结构选型、内存申请与并发/IO 的代码。
|
|
35
60
|
3. 检查并运行基准测试(如适用)。
|
|
36
|
-
4.
|
|
61
|
+
4. 输出结构化的 Markdown 性能报告,并在末尾输出供机器读取的 JSON 块。
|
|
37
62
|
|
|
38
|
-
|
|
63
|
+
---
|
|
39
64
|
|
|
40
|
-
|
|
65
|
+
## 📄 输出格式(必须使用纯正中文撰写)
|
|
41
66
|
|
|
42
67
|
## Summary
|
|
43
|
-
|
|
68
|
+
一句话中文性能总结(评定性能等级:性能提升 / 优秀无影响 / 存在低效瓶颈 / 严重性能退化)。
|
|
44
69
|
|
|
45
70
|
## Findings
|
|
46
|
-
每个发现一行:`- [SEVERITY|category|confidence] 文件路径:行号 —
|
|
71
|
+
每个发现一行:`- [SEVERITY|category|confidence] 文件路径:行号 — 中文问题描述、复杂度/分配分析与优化方案`。无明显性能问题写 `No findings.`。
|
|
47
72
|
|
|
48
73
|
## Benchmark Analysis
|
|
49
|
-
|
|
74
|
+
说明工作区中的基准测试探测情况与运行数据。若无基准测试且改动涉及热点,提供一段建议编写的 Benchmark 代码。
|
|
50
75
|
|
|
51
76
|
## Coverage
|
|
52
77
|
- Files checked: 检查的文件
|
|
53
|
-
- Commands run:
|
|
78
|
+
- Commands run: 执行的基准测试或排查命令
|
|
54
79
|
- Limitations: 局限说明
|
|
55
80
|
|
|
56
81
|
然后在最末尾严格输出一个被 ```json 代码块包裹的 JSON:
|
|
@@ -59,9 +84,9 @@ inheritSkills: false
|
|
|
59
84
|
{
|
|
60
85
|
"status": "ok",
|
|
61
86
|
"issues": [
|
|
62
|
-
{ "file": "
|
|
87
|
+
{ "file": "path/to/file", "line": 100, "category": "perf", "severity": "major", "confidence": 9, "evidence": "在热点循环内存在高频堆分配与 O(N^2) 线性查找,建议改为预分配并在外部构建哈希索引", "fingerprint": "path/to/file:100:perf:a1b2c3" }
|
|
63
88
|
],
|
|
64
89
|
"summary": "中文一句话性能总结",
|
|
65
|
-
"coverage": { "filesChecked": ["
|
|
90
|
+
"coverage": { "filesChecked": ["path/to/file"], "commandsRun": [], "limitations": [] }
|
|
66
91
|
}
|
|
67
92
|
```
|
package/index.ts
CHANGED
|
@@ -25,11 +25,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
25
25
|
registerReviewReportTool(pi);
|
|
26
26
|
registerPiReviewRenderer(pi);
|
|
27
27
|
pi.registerCommand("review", {
|
|
28
|
-
description: "
|
|
28
|
+
description: "启动日常 AI 代码审查 (3 大核心专家 + 门禁裁判长)。--lite 极速单兵,--perf 性能审查,--full 全量会诊。",
|
|
29
29
|
getArgumentCompletions: (prefix: string) => {
|
|
30
30
|
const options = [
|
|
31
|
-
{ value: "--lite", label: "--lite", description: "极速单专家审查 (
|
|
31
|
+
{ value: "--lite", label: "--lite", description: "极速单专家审查 (无门禁,低延迟极省 Token)" },
|
|
32
32
|
{ value: "--perf", label: "--perf", description: "专项性能与基准测试审查 (GC/内存分配/CPU/Benchmark)" },
|
|
33
|
+
{ value: "--full", label: "--full", description: "全量 6 专家深度会诊 (Bugbot/安全/合规/历史/注释/性能 + 门禁)" },
|
|
33
34
|
{ value: "--gate-model", label: "--gate-model", description: "指定当前审查的门禁裁判模型" },
|
|
34
35
|
];
|
|
35
36
|
const trimmed = prefix.trimStart();
|
|
@@ -69,6 +70,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
70
|
input: parsed.input,
|
|
70
71
|
lite: parsed.lite,
|
|
71
72
|
perf: parsed.perf,
|
|
73
|
+
full: parsed.full,
|
|
72
74
|
gateModel: parsed.gateModel,
|
|
73
75
|
});
|
|
74
76
|
if (!prepared) {
|
|
@@ -139,6 +141,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
139
141
|
},
|
|
140
142
|
});
|
|
141
143
|
|
|
144
|
+
pi.registerCommand("review-full", {
|
|
145
|
+
description: "全量 6 专家深度代码审查会诊 (Bugbot/安全/合规/历史/注释/性能 + 门禁总裁判)",
|
|
146
|
+
handler: async (args, ctx) => {
|
|
147
|
+
const notify = (msg: string, level: "info" | "warning" | "error" = "info") => {
|
|
148
|
+
if (ctx.hasUI) ctx.ui.notify(msg, level);
|
|
149
|
+
else console.log(`pi-review: ${msg}`);
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
const { config, legacyWarnings } = loadConfig();
|
|
153
|
+
for (const w of legacyWarnings) notify(`pi-review 提示: ${w}`, "warning");
|
|
154
|
+
pi.sendMessage({ customType: "pi-review", content: args ? `/review-full ${args}` : "/review-full", display: true });
|
|
155
|
+
const prepared = await prepareRun({ cwd: ctx.cwd, input: args, full: true });
|
|
156
|
+
if (!prepared) {
|
|
157
|
+
notify("没有检测到需要审查的内容 (未找到修改、PR 或非 Git 仓库)。", "info");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
pi.sendMessage({ customType: "pi-review-directive", content: prepared.directiveText, display: false }, { triggerTurn: true });
|
|
161
|
+
} catch (err) {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
notify(`全量代码审查启动失败: ${message}`, "error");
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
142
168
|
pi.registerCommand("review-config", {
|
|
143
169
|
description: "编辑代码审查配置 (~/.pi/agent/pi-review.json)",
|
|
144
170
|
handler: async (_args, ctx) => {
|
package/package.json
CHANGED
package/src/cli-args.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ParsedReviewArgs {
|
|
|
11
11
|
lite: boolean;
|
|
12
12
|
/** Dedicated performance review mode: perf-reviewer with benchmark capabilities. */
|
|
13
13
|
perf: boolean;
|
|
14
|
+
/** Full multi-agent review mode: all 6 reviewers + gate. */
|
|
15
|
+
full: boolean;
|
|
14
16
|
/** Override the gate model for this run (otherwise config.gate.model). */
|
|
15
17
|
gateModel?: string;
|
|
16
18
|
}
|
|
@@ -24,7 +26,7 @@ const LEGACY_VALUED_FLAGS = new Set([
|
|
|
24
26
|
|
|
25
27
|
export function parseReviewArgs(raw: string): ParsedReviewArgs {
|
|
26
28
|
const tokens = tokenize(raw);
|
|
27
|
-
const result: ParsedReviewArgs = { noSpawn: false, lite: false, perf: false };
|
|
29
|
+
const result: ParsedReviewArgs = { noSpawn: false, lite: false, perf: false, full: false };
|
|
28
30
|
const inputParts: string[] = [];
|
|
29
31
|
|
|
30
32
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -45,6 +47,10 @@ export function parseReviewArgs(raw: string): ParsedReviewArgs {
|
|
|
45
47
|
result.perf = true;
|
|
46
48
|
continue;
|
|
47
49
|
}
|
|
50
|
+
if (t === "--full") {
|
|
51
|
+
result.full = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
48
54
|
if (t === "--gate-model") {
|
|
49
55
|
const id = tokens[++i];
|
|
50
56
|
if (id) result.gateModel = id;
|
package/src/config.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { PiReviewConfig, ReviewerSpec, RoutingMode, VerdictPolicy } from ".
|
|
|
24
24
|
* reviewers stay on "inherit" to follow the parent session's stronger model.
|
|
25
25
|
* Override via config.json (`gate.model`) or `--gate-model`.
|
|
26
26
|
*/
|
|
27
|
-
export const DEFAULT_GATE_MODEL = "
|
|
27
|
+
export const DEFAULT_GATE_MODEL = "inherit";
|
|
28
28
|
|
|
29
29
|
/** Default reviewer and gate config shipped with the package (v0.7). */
|
|
30
30
|
export const DEFAULT_CONFIG: PiReviewConfig = {
|
|
@@ -55,7 +55,7 @@ export const DEFAULT_CONFIG: PiReviewConfig = {
|
|
|
55
55
|
"history-context": {
|
|
56
56
|
id: "history-context",
|
|
57
57
|
label: "History Context",
|
|
58
|
-
enabled:
|
|
58
|
+
enabled: false,
|
|
59
59
|
model: "inherit",
|
|
60
60
|
},
|
|
61
61
|
"security-review": {
|
|
@@ -67,7 +67,7 @@ export const DEFAULT_CONFIG: PiReviewConfig = {
|
|
|
67
67
|
"code-comments": {
|
|
68
68
|
id: "code-comments",
|
|
69
69
|
label: "Code Comments",
|
|
70
|
-
enabled:
|
|
70
|
+
enabled: false,
|
|
71
71
|
model: "inherit",
|
|
72
72
|
},
|
|
73
73
|
conventions: {
|
package/src/directive.ts
CHANGED
|
@@ -237,9 +237,9 @@ export function buildWorkflowScript(input: {
|
|
|
237
237
|
const READ_ONLY_PREFIX =
|
|
238
238
|
"只读任务(READ-ONLY)——仅执行审查分析与基准测试。严禁修改源码文件。仅返回审查发现。所有分析总结、问题描述与建议必须使用纯正中文。";
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
lines.push("");
|
|
242
|
-
lines.push("
|
|
240
|
+
lines.push("let reviewers;");
|
|
241
|
+
lines.push("try {");
|
|
242
|
+
lines.push(" reviewers = await runs.all([");
|
|
243
243
|
for (const r of reviewers) {
|
|
244
244
|
const tb = LEAN_BUDGETS.defaultToolBudget;
|
|
245
245
|
const tbForId = r.id === "history-context" ? LEAN_BUDGETS.historyToolBudget : tb;
|
|
@@ -303,7 +303,44 @@ export function buildWorkflowScript(input: {
|
|
|
303
303
|
);
|
|
304
304
|
lines.push(" },");
|
|
305
305
|
}
|
|
306
|
-
lines.push("]);");
|
|
306
|
+
lines.push(" ]);");
|
|
307
|
+
lines.push("} catch (firstErr) {");
|
|
308
|
+
lines.push(" // 自动网络重试保护:若首次并发因网络波动超时,自动延迟800ms后重试一次");
|
|
309
|
+
lines.push(" await new Promise((resolve) => setTimeout(resolve, 800));");
|
|
310
|
+
lines.push(" reviewers = await runs.all([");
|
|
311
|
+
for (const r of reviewers) {
|
|
312
|
+
const tb = LEAN_BUDGETS.defaultToolBudget;
|
|
313
|
+
const tbForId = r.id === "history-context" ? LEAN_BUDGETS.historyToolBudget : tb;
|
|
314
|
+
const taskParts = [
|
|
315
|
+
READ_ONLY_PREFIX,
|
|
316
|
+
`读取 ${JSON.stringify(diffPath)} 作为改动内容——diff 是权威的修改记录,工作区文件仅作上下文参考。所有问题描述必须使用中文。`,
|
|
317
|
+
`你的当前工作区为目标工作区 (${JSON.stringify(workspacePath)})。在此目录下执行必要的 read/grep。`,
|
|
318
|
+
"在额度内完成分析;最终回复必须输出格式规范的 Markdown 审查报告(包含中文 Summary / Findings / Coverage 章节)并停止。所有问题描述、证据引用和总结必须使用纯正中文。",
|
|
319
|
+
];
|
|
320
|
+
const modelClause =
|
|
321
|
+
r.model && r.model !== "inherit"
|
|
322
|
+
? `\n model: ${JSON.stringify(r.model)},`
|
|
323
|
+
: "";
|
|
324
|
+
lines.push(" {");
|
|
325
|
+
lines.push(` key: ${JSON.stringify(r.id)},`);
|
|
326
|
+
lines.push(` agent: ${JSON.stringify(leanAgentName(r.id))},`);
|
|
327
|
+
lines.push(` task: [`);
|
|
328
|
+
for (const part of taskParts) {
|
|
329
|
+
lines.push(` ${JSON.stringify(part)},`);
|
|
330
|
+
}
|
|
331
|
+
lines.push(` ].join(" "),`);
|
|
332
|
+
lines.push(` cwd: ${JSON.stringify(workspacePath)},`);
|
|
333
|
+
if (r.thinking) {
|
|
334
|
+
lines.push(` thinking: ${JSON.stringify(r.thinking)},`);
|
|
335
|
+
}
|
|
336
|
+
lines.push(` toolBudget: { soft: ${tbForId.soft}, hard: ${tbForId.hard} },`);
|
|
337
|
+
lines.push(
|
|
338
|
+
` turnBudget: { maxTurns: ${budgets.turnBudget.maxTurns}, graceTurns: ${budgets.turnBudget.graceTurns} },${modelClause}`,
|
|
339
|
+
);
|
|
340
|
+
lines.push(" },");
|
|
341
|
+
}
|
|
342
|
+
lines.push(" ]);");
|
|
343
|
+
lines.push("}");
|
|
307
344
|
lines.push("");
|
|
308
345
|
|
|
309
346
|
// Gate
|
|
@@ -340,7 +377,12 @@ export function buildWorkflowScript(input: {
|
|
|
340
377
|
lines.push(` agent: ${JSON.stringify(LEAN_GATE_AGENT)},`);
|
|
341
378
|
lines.push(" task: gateTask,");
|
|
342
379
|
lines.push(` cwd: ${JSON.stringify(workspacePath)},`);
|
|
343
|
-
|
|
380
|
+
if (gateModel && gateModel !== "inherit") {
|
|
381
|
+
lines.push(` model: ${JSON.stringify(gateModelWithThinking)},`);
|
|
382
|
+
}
|
|
383
|
+
if (gateThinking && gateThinking !== "off" && gateThinking !== "false") {
|
|
384
|
+
lines.push(` thinking: ${JSON.stringify(gateThinking)},`);
|
|
385
|
+
}
|
|
344
386
|
lines.push(` toolBudget: { soft: ${budgets.gateToolBudget.soft}, hard: ${budgets.gateToolBudget.hard} },`);
|
|
345
387
|
lines.push(` turnBudget: { maxTurns: ${budgets.gateTurnBudget.maxTurns}, graceTurns: ${budgets.gateTurnBudget.graceTurns} },`);
|
|
346
388
|
lines.push(" });");
|
package/src/review-run.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface PrepareRunInput {
|
|
|
49
49
|
lite?: boolean;
|
|
50
50
|
/** Support `--perf` performance & benchmark mode. */
|
|
51
51
|
perf?: boolean;
|
|
52
|
+
/** Support `--full` all 6 reviewers mode. */
|
|
53
|
+
full?: boolean;
|
|
52
54
|
/** Optional per-run gate model override. */
|
|
53
55
|
gateModel?: string;
|
|
54
56
|
/** Set false for dry-runs — pruning is a side effect a dry run must not have. */
|
|
@@ -150,7 +152,16 @@ export async function prepareRun(input: PrepareRunInput): Promise<PreparedRun |
|
|
|
150
152
|
? [{ id: "perf-review", label: "Performance Review", enabled: true, model: "inherit" }]
|
|
151
153
|
: input.lite
|
|
152
154
|
? [{ id: "lite-review", label: "Lite Review", enabled: true, model: "inherit" }]
|
|
153
|
-
:
|
|
155
|
+
: input.full
|
|
156
|
+
? [
|
|
157
|
+
{ id: "claude-md-compliance", label: "Claude-MD Compliance", enabled: true, model: "inherit" },
|
|
158
|
+
{ id: "bugbot", label: "Bugbot", enabled: true, model: "inherit" },
|
|
159
|
+
{ id: "security-review", label: "Security Review", enabled: true, model: "inherit" },
|
|
160
|
+
{ id: "history-context", label: "History Context", enabled: true, model: "inherit" },
|
|
161
|
+
{ id: "code-comments", label: "Code Comments", enabled: true, model: "inherit" },
|
|
162
|
+
{ id: "perf-review", label: "Performance Review", enabled: true, model: "inherit" },
|
|
163
|
+
]
|
|
164
|
+
: reviewersForRouting(target, config, profile);
|
|
154
165
|
const skippedReasons = adaptiveSkips(profile);
|
|
155
166
|
// The report tool uses this to reject findings that did not come from this
|
|
156
167
|
// run's roster (stale-artifact contamination guard).
|