@unifan/pi-review-zh 1.0.2 → 1.0.3
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/package.json +1 -1
- package/src/directive.ts +73 -179
- package/src/lean-agents.ts +8 -30
package/package.json
CHANGED
package/src/directive.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build the review directive injected into the main agent
|
|
3
|
-
* `sendMessage` with `display:false` + `triggerTurn:true`).
|
|
4
|
-
*
|
|
5
|
-
* v0.7.0 contract (post-mortem from PR #18689 review):
|
|
6
|
-
* - chatProgress must be "auto" | "off" | "live-card" — anything else is
|
|
7
|
-
* rejected by pi-subagents schema validation.
|
|
8
|
-
* - Every reviewer child declares `cwd` (target workspace) and
|
|
9
|
-
* `outputSchema` so pi-subagents returns `result.structuredOutput`.
|
|
10
|
-
* - "inherit" reviewer models are NOT expanded into concrete model ids.
|
|
11
|
-
* The workflow script leaves `model:` off so the orchestrator keeps the
|
|
12
|
-
* inheritance link.
|
|
13
|
-
* - The gate consumes reviewer `structuredOutput` objects directly, never
|
|
14
|
-
* Markdown code fences.
|
|
15
|
-
* - Step 3 hands off to the `pi_review_report` tool, which is the only
|
|
16
|
-
* authoritative report renderer (deterministic code-side verdict).
|
|
2
|
+
* Build the review directive injected into the main agent.
|
|
17
3
|
*/
|
|
18
4
|
import { writeFileSync } from "node:fs";
|
|
19
5
|
|
|
@@ -31,33 +17,17 @@ import type { ReviewerSpec, ReviewTarget } from "./types.js";
|
|
|
31
17
|
export interface ReviewDirectiveInput {
|
|
32
18
|
target: ReviewTarget;
|
|
33
19
|
reviewers: ReviewerSpec[];
|
|
34
|
-
/** Resolved gate model id (from config.gate.model or --gate-model). */
|
|
35
20
|
gateModel: string;
|
|
36
|
-
/** Optional gate thinking from config (appended as model:thinking). */
|
|
37
21
|
gateThinking?: string;
|
|
38
22
|
threshold: number;
|
|
39
|
-
/** Verdict policy passed to the gate task (code-side authoritative). */
|
|
40
23
|
verdictPolicy?: "strict" | "legacy";
|
|
41
24
|
lite: boolean;
|
|
42
|
-
/** Set false to skip the gate while keeping the full reviewer roster. */
|
|
43
25
|
gateEnabled?: boolean;
|
|
44
26
|
cwd: string;
|
|
45
|
-
/** Absolute path to the plugin-prepared target workspace (reviewer cwd). */
|
|
46
27
|
workspacePath: string;
|
|
47
|
-
/** Absolute path to the run manifest.json. */
|
|
48
28
|
manifestPath: string;
|
|
49
|
-
/** Absolute path to the captured change.diff. */
|
|
50
29
|
diffPath: string;
|
|
51
|
-
/**
|
|
52
|
-
* Absolute path to write the raw workflowScript text. When set, the raw
|
|
53
|
-
* script is persisted here and the directive points the main agent at it
|
|
54
|
-
* (retry path) instead of asking it to re-derive the script from a
|
|
55
|
-
* double-escaped JSON string — see the 2026-08-25 PR 19395 incident where
|
|
56
|
-
* the main agent's copy/unescape of the script produced a syntax error
|
|
57
|
-
* three times and then drifted into hand-debugging.
|
|
58
|
-
*/
|
|
59
30
|
workflowPath?: string;
|
|
60
|
-
/** Optional turnBudget override from config.budgets. */
|
|
61
31
|
budgets?: LeanBudgetSpec;
|
|
62
32
|
}
|
|
63
33
|
|
|
@@ -69,73 +39,74 @@ export function buildReviewDirective(input: ReviewDirectiveInput): string {
|
|
|
69
39
|
const gateModelWithThinking = withThinkingSuffix(gateModel, gateThinking);
|
|
70
40
|
const blocks: string[] = [];
|
|
71
41
|
|
|
72
|
-
blocks.push("#
|
|
42
|
+
blocks.push("# 代码审查流程 (极简 Token 模式)");
|
|
73
43
|
blocks.push("");
|
|
74
44
|
if (target.userContext?.trim()) {
|
|
75
|
-
blocks.push(
|
|
45
|
+
blocks.push(`**用户指令/侧重点:** ${target.userContext.trim()}`);
|
|
76
46
|
blocks.push("");
|
|
77
47
|
}
|
|
78
48
|
blocks.push(
|
|
79
|
-
|
|
49
|
+
`请对本次代码改动 (${target.label}) 进行审查。插件已准备好目标工作区、diff 文件及运行清单。你需要执行一次 workflowScript 启动 ${reviewers.length} 个并发审查专家${lite ? " (极速单兵模式)" : ""}${gateOn ? " + 门禁裁判长" : ""},随后调用 \`pi_review_report\` 工具完成报告渲染与归档。切勿在聊天中直接重复书写未加工的原始问题细节。`,
|
|
80
50
|
);
|
|
81
51
|
blocks.push("");
|
|
82
|
-
blocks.push("##
|
|
52
|
+
blocks.push("## 硬性规则(严禁违反)");
|
|
83
53
|
blocks.push("");
|
|
84
|
-
blocks.push("-
|
|
54
|
+
blocks.push("- **语言要求**:所有面向用户的输出(包括工作流待办清单、状态汇报、问题总结与回复)**必须使用纯正中文**。");
|
|
55
|
+
blocks.push("- 在本次审查中**只能且必须调用一次** `subagent` 工具:即第 2 步的 workflowScript 调用。");
|
|
85
56
|
blocks.push(
|
|
86
57
|
lite
|
|
87
|
-
? "-
|
|
58
|
+
? "- 第 2 步必须是**单次** `subagent({ workflowScript, async:false, ... })` 调用,通过 `runs.all([...])` 并发执行单兵审查专家——严禁多次调用。"
|
|
88
59
|
: gateOn
|
|
89
|
-
? "-
|
|
90
|
-
: "-
|
|
60
|
+
? "- 第 2 步必须是**单次** `subagent({ workflowScript, async:false, ... })` 调用,通过 `runs.all([...])` 并发执行**所有**专家,并通过 `runs.run(\"gate\", ...)` 运行门禁裁判长——严禁每个专家单独调用一次,严禁串行多波次调用。"
|
|
61
|
+
: "- 第 2 步必须是**单次** `subagent({ workflowScript, async:false, ... })` 调用,通过 `runs.all([...])` 并发执行**所有**专家——严禁多波次单独调用。",
|
|
91
62
|
);
|
|
92
63
|
blocks.push(
|
|
93
|
-
"-
|
|
64
|
+
"- **切勿重试失败的子代理**:若某个专家超时、耗尽额度或报错,`runs.all` 会自动收集错误;流程继续进行并在最终报告中标注失败即可。",
|
|
94
65
|
);
|
|
95
|
-
const retryScriptHint = workflowPath ?
|
|
66
|
+
const retryScriptHint = workflowPath ? `使用 Read 工具读取 \`${workflowPath}\`` : "使用 Read 工具读取 workflow.js 文件";
|
|
96
67
|
blocks.push(
|
|
97
|
-
"-
|
|
68
|
+
"- **脚本级解析错误例外**:若 `subagent` 因 `workflowScript` 语法解析失败而拒绝执行(无任何专家启动),允许重试**一次**:" + retryScriptHint + " 并以该文件内容作为 `workflowScript` 重试。切勿手动篡改脚本内容。",
|
|
98
69
|
);
|
|
99
|
-
blocks.push("-
|
|
70
|
+
blocks.push("- **严禁**调用 `subagent` 进行额外的事后验证或重写报告。");
|
|
100
71
|
blocks.push(
|
|
101
|
-
"-
|
|
72
|
+
"- **严禁读取 `.pi-subagents/`(工件、对话日志)或尝试从磁盘拼凑审查发现。** `pi_review_report` 所需数据全部来自第 2 步 workflowScript 的返回值。",
|
|
102
73
|
);
|
|
103
74
|
blocks.push(
|
|
104
|
-
"-
|
|
75
|
+
"- 严格使用下方指定的 `pi-review.*` 代理名称,保持每个子代理的 `toolBudget` / `turnBudget` 与全局的 `async:false` / `context:\"fresh\"` / `timeoutMs`。",
|
|
105
76
|
);
|
|
106
|
-
blocks.push("-
|
|
77
|
+
blocks.push("- 审查专家模型**默认继承**父会话(除非配置显式指定了具体模型)。");
|
|
107
78
|
blocks.push(
|
|
108
|
-
"-
|
|
79
|
+
"- 下方的 `workflowScript` 是一个模板字面量,已包含完整路径与参数。请一字不差地复制使用。",
|
|
109
80
|
);
|
|
110
81
|
blocks.push("");
|
|
111
|
-
blocks.push(
|
|
82
|
+
blocks.push(`**忽略以下误报内容:** ${FALSE_POSITIVE_GUIDANCE}。`);
|
|
112
83
|
blocks.push("");
|
|
113
84
|
|
|
114
85
|
blocks.push(
|
|
115
|
-
"
|
|
86
|
+
"首先,在聊天中输出中文工作流待办清单(Checklist),随后逐步执行,并在完成每步后将 `- [ ]` 标记为 `- [x]`:",
|
|
116
87
|
);
|
|
117
88
|
blocks.push("");
|
|
118
89
|
const todoSteps = [
|
|
119
|
-
|
|
120
|
-
|
|
90
|
+
`确认插件准备的运行清单可读: ${manifestPath}`,
|
|
91
|
+
`确认目标工作区可读: ${workspacePath}`,
|
|
121
92
|
lite
|
|
122
|
-
? "
|
|
93
|
+
? "执行 workflowScript: 启动单兵极速审查专家 (单次 subagent 调用)"
|
|
123
94
|
: gateOn
|
|
124
|
-
?
|
|
125
|
-
:
|
|
126
|
-
"
|
|
95
|
+
? `执行 workflowScript: 启动 ${reviewers.length} 个并发审查专家 + 门禁裁判长 (单次 subagent 调用)`
|
|
96
|
+
: `执行 workflowScript: 启动 ${reviewers.length} 个并发审查专家 (单次 subagent 调用)`,
|
|
97
|
+
"调用 `pi_review_report` 工具提交审查结果并生成中文报告",
|
|
127
98
|
];
|
|
128
99
|
for (const s of todoSteps) blocks.push(`- [ ] ${s}`);
|
|
129
100
|
blocks.push("");
|
|
130
101
|
|
|
131
|
-
// Step 1
|
|
132
|
-
blocks.push("##
|
|
102
|
+
// Step 1
|
|
103
|
+
blocks.push("## 第 1 步 — 确认插件准备的环境(由主代理执行)");
|
|
133
104
|
blocks.push("");
|
|
134
105
|
blocks.push(
|
|
135
|
-
|
|
106
|
+
`插件已经完成了目标仓库准备,提取了准确的 diff 并计算了 SHA-256 哈希,生成了 \`${manifestPath}\` 与 \`${diffPath}\`。`,
|
|
136
107
|
);
|
|
137
108
|
blocks.push("");
|
|
138
|
-
blocks.push("
|
|
109
|
+
blocks.push("请使用单次 `bash` 调用(不使用 `&&` / `||` 复合连接符)验证文件存在:");
|
|
139
110
|
blocks.push("");
|
|
140
111
|
blocks.push("```bash");
|
|
141
112
|
blocks.push(`test -s ${JSON.stringify(diffPath)}`);
|
|
@@ -143,10 +114,10 @@ export function buildReviewDirective(input: ReviewDirectiveInput): string {
|
|
|
143
114
|
blocks.push(`test -d ${JSON.stringify(workspacePath)}`);
|
|
144
115
|
blocks.push("```");
|
|
145
116
|
blocks.push("");
|
|
146
|
-
blocks.push("
|
|
117
|
+
blocks.push("若任何检查失败,停止并通知用户;检查通过则继续执行第 2 步。");
|
|
147
118
|
blocks.push("");
|
|
148
119
|
|
|
149
|
-
// Step 2
|
|
120
|
+
// Step 2
|
|
150
121
|
const script = buildWorkflowScript({
|
|
151
122
|
reviewers,
|
|
152
123
|
gateModelWithThinking,
|
|
@@ -164,49 +135,40 @@ export function buildReviewDirective(input: ReviewDirectiveInput): string {
|
|
|
164
135
|
diffPath,
|
|
165
136
|
});
|
|
166
137
|
|
|
167
|
-
// Parse guard (P0 regression): make sure the generated script is valid JS
|
|
168
|
-
// BEFORE it reaches the main agent. If the template ever regresses — or,
|
|
169
|
-
// critically, if it ever grows a backtick or `${` (which would break the
|
|
170
|
-
// template-literal presentation the main agent copies) — fail here instead
|
|
171
|
-
// of at subagent() time.
|
|
172
138
|
if (/[`$]/.test(script)) {
|
|
173
139
|
throw new Error(
|
|
174
|
-
"pi-review: generated workflowScript contains a backtick or `$` (template-literal conflict)
|
|
140
|
+
"pi-review: generated workflowScript contains a backtick or `$` (template-literal conflict).",
|
|
175
141
|
);
|
|
176
142
|
}
|
|
177
143
|
try {
|
|
178
144
|
new Function(`return (async () => {\n${script}\n})`);
|
|
179
145
|
} catch (err) {
|
|
180
146
|
throw new Error(
|
|
181
|
-
`pi-review: generated workflowScript is not valid JavaScript
|
|
147
|
+
`pi-review: generated workflowScript is not valid JavaScript: ${err instanceof Error ? err.message : String(err)}`,
|
|
182
148
|
);
|
|
183
149
|
}
|
|
184
150
|
|
|
185
|
-
// Persist the raw script text so the main agent has a zero-unescape
|
|
186
|
-
// retry source (see ReviewDirectiveInput.workflowPath).
|
|
187
151
|
if (workflowPath) {
|
|
188
152
|
try {
|
|
189
153
|
writeFileSync(workflowPath, script, "utf-8");
|
|
190
154
|
} catch {
|
|
191
|
-
|
|
155
|
+
/* ignore */
|
|
192
156
|
}
|
|
193
157
|
}
|
|
194
158
|
|
|
195
|
-
blocks.push("##
|
|
159
|
+
blocks.push("## 第 2 步 — 执行代码审查(仅调用一次 subagent workflowScript)");
|
|
196
160
|
blocks.push("");
|
|
197
161
|
blocks.push(
|
|
198
162
|
lite
|
|
199
|
-
? "
|
|
163
|
+
? "该脚本会启动单兵极速审查专家,输出包含 JSON 块的 Markdown 报告。"
|
|
200
164
|
: gateOn
|
|
201
|
-
? "
|
|
202
|
-
: "
|
|
165
|
+
? "该脚本会并发启动各个审查专家,随后将报告汇总给门禁裁判长,裁判长输出裁决报告与 JSON 块。"
|
|
166
|
+
: "该脚本会并发启动各个审查专家,输出 Markdown 报告。",
|
|
203
167
|
);
|
|
204
168
|
blocks.push("");
|
|
205
169
|
blocks.push("```js");
|
|
206
170
|
blocks.push("subagent({");
|
|
207
171
|
blocks.push(" workflowScript: `");
|
|
208
|
-
// The raw script, verbatim (no escaping). The script contains no
|
|
209
|
-
// backticks and no ${, so the template literal is lossless.
|
|
210
172
|
blocks.push(script);
|
|
211
173
|
blocks.push("`,");
|
|
212
174
|
blocks.push(` async: false,`);
|
|
@@ -217,58 +179,35 @@ export function buildReviewDirective(input: ReviewDirectiveInput): string {
|
|
|
217
179
|
blocks.push("```");
|
|
218
180
|
blocks.push("");
|
|
219
181
|
blocks.push(
|
|
220
|
-
|
|
182
|
+
`一字不差地复制上方代码执行。若出现解析错误,请使用 \`Read\` 读取 \`${workflowPath ?? "workflow.js"}\` 内容并重试一次。`,
|
|
221
183
|
);
|
|
222
184
|
blocks.push("");
|
|
223
185
|
|
|
224
|
-
// Step 3
|
|
225
|
-
blocks.push("##
|
|
186
|
+
// Step 3
|
|
187
|
+
blocks.push("## 第 3 步 — 渲染审查报告(调用 `pi_review_report`)");
|
|
226
188
|
blocks.push("");
|
|
227
189
|
blocks.push(
|
|
228
|
-
"
|
|
190
|
+
"以 `{ runId, workflowReturn }` 调用 `pi_review_report` 工具**仅一次**。该工具会自动加载清单、解析裁判长的 JSON 裁决、执行判定规则并输出最终的中文 Markdown 审查报告。切勿自己手动重写发现。",
|
|
229
191
|
);
|
|
230
192
|
blocks.push("");
|
|
231
193
|
|
|
232
|
-
// Parse guard (P0 regression): make sure the generated script is valid JS
|
|
233
|
-
// BEFORE it reaches the main agent. If the template ever regresses (e.g. an
|
|
234
|
-
// unquoted path), fail here with a clear error instead of at subagent() time.
|
|
235
|
-
try {
|
|
236
|
-
new Function(`return (async () => {\n${script}\n})`);
|
|
237
|
-
} catch (err) {
|
|
238
|
-
throw new Error(
|
|
239
|
-
`pi-review: generated workflowScript is not valid JavaScript — refusing to hand it to the main agent. This is a plugin bug; please report it. Underlying error: ${err instanceof Error ? err.message : String(err)}`,
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
194
|
return blocks.join("\n");
|
|
244
195
|
}
|
|
245
196
|
|
|
246
|
-
/**
|
|
247
|
-
* Build the inline workflowScript string. Single-wave: one `runs.all([...])`
|
|
248
|
-
* for reviewers, one `runs.run(\"gate\")`. Every child carries `cwd`,
|
|
249
|
-
* `outputSchema`, `toolBudget`/`turnBudget`; explicit model overrides flow
|
|
250
|
-
* through only when the reviewer config is not `inherit`.
|
|
251
|
-
*/
|
|
252
197
|
export function buildWorkflowScript(input: {
|
|
253
198
|
reviewers: ReviewerSpec[];
|
|
254
199
|
gateModelWithThinking: string;
|
|
255
|
-
/** Raw gate thinking level (fallback branch passes it as a child param). */
|
|
256
200
|
gateThinking?: string;
|
|
257
201
|
gateModel: string;
|
|
258
202
|
budgets: LeanBudgetSpec;
|
|
259
203
|
lite: boolean;
|
|
260
|
-
/** Mirror of the directive-level gate switch (false when lite OR disabled). */
|
|
261
204
|
gateEnabled?: boolean;
|
|
262
205
|
threshold: number;
|
|
263
|
-
/** Verdict policy for the gate task text (strict is code-side default). */
|
|
264
206
|
verdictPolicy?: "strict" | "legacy";
|
|
265
207
|
targetLabel: string;
|
|
266
208
|
userContext?: string;
|
|
267
|
-
/** Absolute target workspace path (reviewer + gate cwd). */
|
|
268
209
|
workspacePath: string;
|
|
269
|
-
/** Absolute run manifest path. */
|
|
270
210
|
manifestPath: string;
|
|
271
|
-
/** Absolute change.diff path. */
|
|
272
211
|
diffPath: string;
|
|
273
212
|
}): string {
|
|
274
213
|
const {
|
|
@@ -289,62 +228,46 @@ export function buildWorkflowScript(input: {
|
|
|
289
228
|
} = input;
|
|
290
229
|
const gateOn = !lite && gateEnabled;
|
|
291
230
|
|
|
292
|
-
// Blanket read-only declaration. pi-subagents classifies each task text for
|
|
293
|
-
// mutation intent: with a generic-object prohibition ("do not write any
|
|
294
|
-
// files") plus "review only"/"return findings only", the task is
|
|
295
|
-
// unambiguously read-only, so a read-only agent (gate: tools read) is never
|
|
296
|
-
// rejected by the implementation-tool contract, and acceptance stays at the
|
|
297
|
-
// lightweight attested level instead of "risky write-capable".
|
|
298
231
|
const READ_ONLY_PREFIX =
|
|
299
|
-
"READ-ONLY
|
|
232
|
+
"只读任务(READ-ONLY)——仅执行审查分析。严禁写入或修改任何文件。仅返回审查发现。所有分析总结、问题描述与建议必须使用纯正中文。";
|
|
300
233
|
|
|
301
234
|
const lines: string[] = [];
|
|
302
|
-
// v0.8: no outputSchema on any child — the structured-output tool
|
|
303
|
-
// contract was too fragile in the field ("Missing structured_output
|
|
304
|
-
// call" after budget wrap-ups). Reviewers return Markdown reports; the
|
|
305
|
-
// gate ends with a fenced JSON verdict block that the report tool
|
|
306
|
-
// extracts. See agents/*.md "Output format" sections.
|
|
307
235
|
lines.push("");
|
|
308
|
-
// Bind the reviewer array to a local FIRST: the gate IIFE and
|
|
309
|
-
// `reviewersShaped` below both reference `reviewers`, and a bare object
|
|
310
|
-
// property (`return { reviewers: ... }`) does NOT create a variable
|
|
311
|
-
// binding — that produced `ReferenceError: reviewers is not defined` at
|
|
312
|
-
// runtime (silently surfaced as a null workflow return).
|
|
313
236
|
lines.push("const reviewers = await runs.all([");
|
|
314
237
|
for (const r of reviewers) {
|
|
315
|
-
const tb = LEAN_BUDGETS.defaultToolBudget;
|
|
238
|
+
const tb = LEAN_BUDGETS.defaultToolBudget;
|
|
316
239
|
const tbForId = r.id === "history-context" ? LEAN_BUDGETS.historyToolBudget : tb;
|
|
317
240
|
const taskParts = [
|
|
318
241
|
READ_ONLY_PREFIX,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"
|
|
323
|
-
"
|
|
324
|
-
"
|
|
242
|
+
`读取 ${JSON.stringify(diffPath)} 作为改动内容——diff 是权威的修改记录,工作区文件仅作上下文参考。若工作区文件与 diff 存在差异,以 diff 为准并在 coverage.limitations 中说明。所有问题描述必须使用中文。`,
|
|
243
|
+
`同时读取 ${JSON.stringify(manifestPath)} 获取改动概要(文档变更状态、文件列表、规则文件路径)。禁止通过外部命令重复拉取。`,
|
|
244
|
+
`你的当前工作区为目标工作区 (${JSON.stringify(workspacePath)})。在此目录下执行必要的 read/grep。`,
|
|
245
|
+
"在额度内完成分析;最终回复必须输出格式规范的 Markdown 审查报告(包含中文 Summary / Findings / Coverage 章节)并停止。所有问题描述、证据引用和总结必须使用纯正中文。",
|
|
246
|
+
"严禁读取 plan.md, progress.md, 以及 .pi-subagents/ 目录下的任何文件或 node_modules。",
|
|
247
|
+
"优先使用 Read/Grep。若使用 bash,仅限简单的单条命令(禁止 &&/||/; 等复合命令)。",
|
|
325
248
|
];
|
|
326
249
|
if (r.id === "claude-md-compliance") {
|
|
327
250
|
taskParts.push(
|
|
328
|
-
|
|
251
|
+
"若 change-profile.rulePaths 为空,返回跳过状态:SKIPPED: no-rules,不提出虚构的违规。",
|
|
329
252
|
);
|
|
330
253
|
}
|
|
331
254
|
if (r.id === "history-context") {
|
|
332
255
|
taskParts.push(
|
|
333
|
-
|
|
256
|
+
"若 change-profile.history.available 为 false,返回跳过状态:SKIPPED: no-history。从文件列表中选取不超过5个路径,仅执行一次 bash: git log -n 5 --oneline -- 文件1 文件2 ...",
|
|
334
257
|
);
|
|
335
258
|
}
|
|
336
259
|
if (r.id === "code-comments") {
|
|
337
260
|
taskParts.push(
|
|
338
|
-
|
|
261
|
+
"若 change-profile.docsOnly 为 true,返回跳过状态:SKIPPED: docs-only。",
|
|
339
262
|
);
|
|
340
263
|
}
|
|
341
264
|
if (r.id === "bugbot" || r.id === "security-review") {
|
|
342
265
|
taskParts.push(
|
|
343
|
-
|
|
266
|
+
"若 change-profile.docsOnly 为 true,返回跳过状态:SKIPPED: docs-only。否则优先从 diff 本身分析,最多只读取 3 个额外上下文文件。",
|
|
344
267
|
);
|
|
345
268
|
}
|
|
346
269
|
if (userContext?.trim()) {
|
|
347
|
-
taskParts.push(
|
|
270
|
+
taskParts.push(`用户需求: ${userContext.trim()}`);
|
|
348
271
|
}
|
|
349
272
|
|
|
350
273
|
const modelClause =
|
|
@@ -354,9 +277,6 @@ export function buildWorkflowScript(input: {
|
|
|
354
277
|
lines.push(" {");
|
|
355
278
|
lines.push(` key: ${JSON.stringify(r.id)},`);
|
|
356
279
|
lines.push(` agent: ${JSON.stringify(leanAgentName(r.id))},`);
|
|
357
|
-
// Task as an array joined at runtime — one short quoted line per
|
|
358
|
-
// instruction. A single JSON.stringify of the whole task produced
|
|
359
|
-
// 900+ char lines, the other fragile copy point.
|
|
360
280
|
lines.push(` task: [`);
|
|
361
281
|
for (const part of taskParts) {
|
|
362
282
|
lines.push(` ${JSON.stringify(part)},`);
|
|
@@ -375,52 +295,34 @@ export function buildWorkflowScript(input: {
|
|
|
375
295
|
lines.push("]);");
|
|
376
296
|
lines.push("");
|
|
377
297
|
|
|
378
|
-
//
|
|
379
|
-
// Top-level statements ONLY: pi-subagents' workflowScript AST walker
|
|
380
|
-
// rejects nested async functions ("Use top-level await, plain helper
|
|
381
|
-
// functions, or explicit Promise chains"). The pre-0.7.4 form
|
|
382
|
-
// `gate: await (async () => { ... })()` therefore never passed upstream
|
|
383
|
-
// validation — every prior failure that survived the copy stage died
|
|
384
|
-
// here (2026-08-26 session: "validation failed before child launch").
|
|
298
|
+
// Gate
|
|
385
299
|
if (gateOn) {
|
|
386
300
|
const gateTaskParts = [
|
|
387
301
|
READ_ONLY_PREFIX,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
302
|
+
`对 ${targetLabel} 的所有专家审查发现进行综合仲裁与去重。所有分析、裁决理由与总结必须使用纯正中文。`,
|
|
303
|
+
`完整 diff 位于 ${JSON.stringify(diffPath)},当前工作区为目标工作区——你可以且应当亲自核验候选问题。`,
|
|
304
|
+
`置信度阈值 ${threshold}:过滤掉最终置信度小于 ${threshold} 的假警报与无意义建议。`,
|
|
305
|
+
`输入为各专家的 Markdown 报告(每个专家包含 ## Summary / ## Findings / ## Coverage)。`,
|
|
306
|
+
`重新评估每个问题的置信度(1–10 分)。对每个 blocker(致命)或 major(严重)候选问题,首先通过阅读 diff 块和目标文件进行核验,并在 disposition 的 reason 中用中文说明核验结果。`,
|
|
307
|
+
`若未经你自己核验证实,严禁将候选问题评分提升至 8 分以上。`,
|
|
308
|
+
`若因缺少上下文或 diff 截断而无法核验某个 blocker/major 问题,切勿静默丢弃:保留原置信度并在 reason 前缀注明 "未核验:",交由人工判断。`,
|
|
309
|
+
`每个问题必须记录在 dispositions 中,包含 decision (kept | dropped | merged), originalConfidence, finalConfidence, sourceReviewers, reason(中文理由)。`,
|
|
396
310
|
verdictPolicy === "legacy"
|
|
397
|
-
?
|
|
398
|
-
:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
`End your report with exactly one fenced json block containing { status, verdict, issues[], dispositions[], reason } — the parent machine-reads that block.`,
|
|
311
|
+
? `裁决规则: 存在任何 blocker 或 >=3 个 major 则判定为 request_changes; 无 blocker/major 则 approve; 否则 comment。`
|
|
312
|
+
: `裁决规则: 存在任何存活的 blocker 或 major 则判定为 request_changes; 仅有 minor/nit 则 comment; 无存活问题则 approve。`,
|
|
313
|
+
`过滤假警报: ${FALSE_POSITIVE_GUIDANCE}。`,
|
|
314
|
+
`在报告末尾必须输出且仅输出一个被 json 代码块包裹的裁决 JSON 对象 { status, verdict, issues[], dispositions[], reason }(reason 与 evidence 必须为中文)——上层工具将机器读取该 JSON。`,
|
|
402
315
|
];
|
|
403
316
|
|
|
404
|
-
// Inline the reviewers' Markdown reports for the gate to arbitrate.
|
|
405
|
-
// (Sync arrow — allowed; only async functions are rejected upstream.)
|
|
406
317
|
lines.push("const reviewerSections = reviewers.map((r) => {");
|
|
407
|
-
lines.push(" const head = '##
|
|
408
|
-
lines.push(" return head + '\\n\\n' + String(r.output || '(
|
|
318
|
+
lines.push(" const head = '## 审查专家: ' + r.key + (r.ok ? '' : ' (执行失败: ' + String(r.error || 'run failed').slice(0, 120) + ')');");
|
|
319
|
+
lines.push(" return head + '\\n\\n' + String(r.output || '(无输出)').slice(0, 6000);");
|
|
409
320
|
lines.push("});");
|
|
410
|
-
// Gate task as an array join (short lines) — same copy-safety rule as
|
|
411
|
-
// the reviewer tasks above.
|
|
412
321
|
lines.push("const gateTask = [");
|
|
413
322
|
for (const part of gateTaskParts) {
|
|
414
323
|
lines.push(` ${JSON.stringify(part)},`);
|
|
415
324
|
}
|
|
416
|
-
lines.push(`].join(" ") + '\\n\\n#
|
|
417
|
-
// Proxy providers often report bare model ids from the child ("MiniMax-M2.7")
|
|
418
|
-
// that fail the launcher's strict model verification against the launch
|
|
419
|
-
// candidate ("CPA/Minimax/MiniMax-M2.7:high") — observed 2026-08-27. The
|
|
420
|
-
// reviewers never hit this (they inherit). So: try the configured model
|
|
421
|
-
// first; on launch failure retry once with an inherited model under a
|
|
422
|
-
// DIFFERENT key (the runtime rejects same-key launches with different
|
|
423
|
-
// params). A second failure rejects as before.
|
|
325
|
+
lines.push(`].join(" ") + '\\n\\n# 专家审查报告汇总 (Markdown)\\n\\n' + reviewerSections.join('\\n\\n---\\n\\n');`);
|
|
424
326
|
lines.push("let gateRun;");
|
|
425
327
|
lines.push("try {");
|
|
426
328
|
lines.push(" gateRun = await runs.run('gate', {");
|
|
@@ -447,7 +349,6 @@ export function buildWorkflowScript(input: {
|
|
|
447
349
|
lines.push(" ok: gateRun.ok,");
|
|
448
350
|
lines.push(" error: gateRun.error,");
|
|
449
351
|
lines.push(" output: gateRun.output,");
|
|
450
|
-
|
|
451
352
|
lines.push("};");
|
|
452
353
|
lines.push("");
|
|
453
354
|
}
|
|
@@ -459,8 +360,6 @@ export function buildWorkflowScript(input: {
|
|
|
459
360
|
} else {
|
|
460
361
|
lines.push(" gate: null,");
|
|
461
362
|
}
|
|
462
|
-
|
|
463
|
-
// ---- reviewer summary shape ----------------------------------------
|
|
464
363
|
lines.push(" reviewersShaped: reviewers.map((r) => ({");
|
|
465
364
|
lines.push(" key: r.key,");
|
|
466
365
|
lines.push(" ok: r.ok,");
|
|
@@ -471,11 +370,6 @@ export function buildWorkflowScript(input: {
|
|
|
471
370
|
return lines.join("\n");
|
|
472
371
|
}
|
|
473
372
|
|
|
474
|
-
/** Map the workflow return value into a normalized `ReviewWorkflowReturn` for the tool. */
|
|
475
373
|
export function buildWorkflowReturnShape() {
|
|
476
374
|
return "{ reviewers, reviewersShaped, gate }";
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// `gateModel` reserved for config validation parity with previous surface.
|
|
480
|
-
export const _LEGACY_PARITY = { gateModel: "" };
|
|
481
|
-
void _LEGACY_PARITY;
|
|
375
|
+
}
|
package/src/lean-agents.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mapping from pi-review reviewer ids → pi-subagents runtime agent names,
|
|
3
3
|
* plus per-child budgets for the token-lean workflowScript directive path.
|
|
4
|
-
*
|
|
5
|
-
* Agents live in `agents/*.md` and are registered via package.json
|
|
6
|
-
* `pi.subagents.agents` so pi-subagents discovers them as package agents
|
|
7
|
-
* (`pi-review.<id>`).
|
|
8
|
-
*
|
|
9
|
-
* Budget model (pi-subagents ≥0.41 workflowScript API): the top-level
|
|
10
|
-
* `subagent({ workflowScript })` call carries `context`/`timeoutMs` only;
|
|
11
|
-
* `turnBudget` and per-reviewer `toolBudget` are injected onto each
|
|
12
|
-
* `runs.all` / `runs.run` child item (child params override workflow
|
|
13
|
-
* defaults). `runs.run` rejects `tasks`/`chain`/`concurrency` but accepts
|
|
14
|
-
* `toolBudget`/`turnBudget`/`model`/`output`.
|
|
15
4
|
*/
|
|
16
5
|
|
|
17
6
|
export const LEAN_AGENT_PACKAGE = "pi-review";
|
|
@@ -30,7 +19,7 @@ export interface ToolBudgetSpec {
|
|
|
30
19
|
}
|
|
31
20
|
|
|
32
21
|
export interface LeanBudgetSpec {
|
|
33
|
-
/** Per-child turn budget, injected onto each runs.all / runs.run item. */
|
|
22
|
+
/** Per-child turn budget, injected onto each runs.all / runs.run child item. */
|
|
34
23
|
turnBudget: { maxTurns: number; graceTurns: number };
|
|
35
24
|
/** Per-child tool budget for the default reviewer (injected per runs.all item). */
|
|
36
25
|
defaultToolBudget: ToolBudgetSpec;
|
|
@@ -43,13 +32,6 @@ export interface LeanBudgetSpec {
|
|
|
43
32
|
timeoutMs: number;
|
|
44
33
|
}
|
|
45
34
|
|
|
46
|
-
/**
|
|
47
|
-
* Defaults (v0.7.1): reviewers 20→26 turns (field runs kept wrapping up
|
|
48
|
-
* partial at 20); the gate 6→16 turns / 5→14 soft tools — it now carries a
|
|
49
|
-
* verification duty on high-severity candidates (read the diff hunk + the
|
|
50
|
-
* touched file) and physically could not verify anything under the old
|
|
51
|
-
* budget. Wall clock 10→17 min to match.
|
|
52
|
-
*/
|
|
53
35
|
export const LEAN_BUDGETS: LeanBudgetSpec = {
|
|
54
36
|
turnBudget: { maxTurns: 26, graceTurns: 2 },
|
|
55
37
|
defaultToolBudget: { soft: 20, hard: 32 },
|
|
@@ -90,16 +72,12 @@ export function withThinkingSuffix(model: string, thinking?: string): string {
|
|
|
90
72
|
}
|
|
91
73
|
|
|
92
74
|
/**
|
|
93
|
-
* Shared false-positive list
|
|
94
|
-
* Wording constraint: this text is embedded verbatim in the gate task, which
|
|
95
|
-
* pi-subagents classifies for read-only vs implementation intent — keep it
|
|
96
|
-
* free of bare write verbs (modify/edit/implement/…) outside explicit
|
|
97
|
-
* prohibitions, or the read-only gate gets rejected at launch.
|
|
75
|
+
* Shared false-positive list in Chinese.
|
|
98
76
|
*/
|
|
99
77
|
export const FALSE_POSITIVE_GUIDANCE = [
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
].join("; ");
|
|
78
|
+
"本次改动未触及的历史遗留代码问题",
|
|
79
|
+
"资深工程师不会指出的吹毛求疵风格琐碎建议",
|
|
80
|
+
"Linter、类型检查器或 CI 构建会自动捕获的问题",
|
|
81
|
+
"泛泛的代码质量建议(如建议补单测或文档),除非项目规则明确强制要求",
|
|
82
|
+
"表面看似 Bug 但实际属于本次改动预期特性的行为",
|
|
83
|
+
].join("; ");
|