@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unifan/pi-review-zh",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Pi AI 并发代码审查扩展(多专家子代理并发 + 门禁裁判系统,中文增强版)",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/directive.ts CHANGED
@@ -1,19 +1,5 @@
1
1
  /**
2
- * Build the review directive injected into the main agent (hidden, via
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("# Code review (token-lean)");
42
+ blocks.push("# 代码审查流程 (极简 Token 模式)");
73
43
  blocks.push("");
74
44
  if (target.userContext?.trim()) {
75
- blocks.push(`**User request:** ${target.userContext.trim()}`);
45
+ blocks.push(`**用户指令/侧重点:** ${target.userContext.trim()}`);
76
46
  blocks.push("");
77
47
  }
78
48
  blocks.push(
79
- `Review the change (${target.label}). The plugin has already prepared the target workspace, diff and run manifest. Run one workflowScript that fans out ${reviewers.length} reviewer${reviewers.length === 1 ? "" : "s"}${lite ? " (lite)" : ""}${gateOn ? " + inline gate" : ""}, then call the \`pi_review_report\` tool to finalize the report. Do not re-write or summarize findings in chat.`,
49
+ `请对本次代码改动 (${target.label}) 进行审查。插件已准备好目标工作区、diff 文件及运行清单。你需要执行一次 workflowScript 启动 ${reviewers.length} 个并发审查专家${lite ? " (极速单兵模式)" : ""}${gateOn ? " + 门禁裁判长" : ""},随后调用 \`pi_review_report\` 工具完成报告渲染与归档。切勿在聊天中直接重复书写未加工的原始问题细节。`,
80
50
  );
81
51
  blocks.push("");
82
- blocks.push("## Hard rules (do not violate)");
52
+ blocks.push("## 硬性规则(严禁违反)");
83
53
  blocks.push("");
84
- blocks.push("- Call `subagent` **exactly one** time in this whole review: the Step 2 workflowScript call.");
54
+ blocks.push("- **语言要求**:所有面向用户的输出(包括工作流待办清单、状态汇报、问题总结与回复)**必须使用纯正中文**。");
55
+ blocks.push("- 在本次审查中**只能且必须调用一次** `subagent` 工具:即第 2 步的 workflowScript 调用。");
85
56
  blocks.push(
86
57
  lite
87
- ? "- Step 2 must be a **single** `subagent({ workflowScript, async:false, ... })` that fans out the lite-reviewer via `runs.all([...])` — never more than one call."
58
+ ? "- 2 步必须是**单次** `subagent({ workflowScript, async:false, ... })` 调用,通过 `runs.all([...])` 并发执行单兵审查专家——严禁多次调用。"
88
59
  : gateOn
89
- ? "- Step 2 must be a **single** `subagent({ workflowScript, async:false, ... })` that fans out **all** reviewers via `runs.all([...])` and runs the inline gate via `runs.run(\"gate\", ...)` — never one call per reviewer, never serial waves."
90
- : "- Step 2 must be a **single** `subagent({ workflowScript, async:false, ... })` that fans out **all** reviewers via `runs.all([...])` (gate disabled in config) — never one call per reviewer, never serial waves.",
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
- "- **Do not retry** or re-spawn if a reviewer times out, hits its turnBudget, returns partial output, or fails — `runs.all` collects failures as `{ ok:false }`; the script continues and you mark failures in the report.",
64
+ "- **切勿重试失败的子代理**:若某个专家超时、耗尽额度或报错,`runs.all` 会自动收集错误;流程继续进行并在最终报告中标注失败即可。",
94
65
  );
95
- const retryScriptHint = workflowPath ? `Read-tool \`${workflowPath}\`` : "the Read tool on the workflow.js file";
66
+ const retryScriptHint = workflowPath ? `使用 Read 工具读取 \`${workflowPath}\`` : "使用 Read 工具读取 workflow.js 文件";
96
67
  blocks.push(
97
- "- **Exception (script-level failure):** if the `subagent` call is rejected because the `workflowScript` **fails to parse** (no reviewer ever started — e.g. a syntax error in the script literal), retry **once**: use the " + retryScriptHint + " and repeat the call with exactly that file content as `workflowScript`. Do **not** hand-edit, re-quote, or fix the script text yourself — if the retry also fails, stop and notify the user. Do not retry any reviewer that already started and failed.",
68
+ "- **脚本级解析错误例外**:若 `subagent` `workflowScript` 语法解析失败而拒绝执行(无任何专家启动),允许重试**一次**:" + retryScriptHint + " 并以该文件内容作为 `workflowScript` 重试。切勿手动篡改脚本内容。",
98
69
  );
99
- blocks.push("- **Do not** call `subagent` for verification, re-review, or rewriting the report.");
70
+ blocks.push("- **严禁**调用 `subagent` 进行额外的事后验证或重写报告。");
100
71
  blocks.push(
101
- "- **Never read `.pi-subagents/` (artifacts, transcripts, run metadata) or reconstruct findings from disk.** Findings for `pi_review_report` come exclusively from the workflow return value of the Step 2 call files left there by earlier runs describe OTHER reviews (a real incident had a failed workflow followed by stale-artifact findings presented as the current PR's).",
72
+ "- **严禁读取 `.pi-subagents/`(工件、对话日志)或尝试从磁盘拼凑审查发现。** `pi_review_report` 所需数据全部来自第 2 workflowScript 的返回值。",
102
73
  );
103
74
  blocks.push(
104
- "- Use the exact `pi-review.*` agents below — do not substitute builtin `reviewer`. Keep per-child `toolBudget` / `turnBudget` and the top-level `async:false` / `context:\"fresh\"` / `timeoutMs`.",
75
+ "- 严格使用下方指定的 `pi-review.*` 代理名称,保持每个子代理的 `toolBudget` / `turnBudget` 与全局的 `async:false` / `context:\"fresh\"` / `timeoutMs`。",
105
76
  );
106
- blocks.push("- Reviewer models **inherit** the parent session (omit per-child `model` unless the reviewer config sets an explicit model).");
77
+ blocks.push("- 审查专家模型**默认继承**父会话(除非配置显式指定了具体模型)。");
107
78
  blocks.push(
108
- "- The `workflowScript` value below is a **template literal (backticks)** whose content is the exact text of the run's `workflow.js`. Copy its content verbatim — every character matters (paths, `outputSchema` JSON, budgets are already generated). Do not re-format, re-indent, unescape, or shorten it; the backtick form is unescaped by design so a straight copy is a valid script.",
79
+ "- 下方的 `workflowScript` 是一个模板字面量,已包含完整路径与参数。请一字不差地复制使用。",
109
80
  );
110
81
  blocks.push("");
111
- blocks.push(`**Skip these false positives:** ${FALSE_POSITIVE_GUIDANCE}.`);
82
+ blocks.push(`**忽略以下误报内容:** ${FALSE_POSITIVE_GUIDANCE}。`);
112
83
  blocks.push("");
113
84
 
114
85
  blocks.push(
115
- "First, post the workflow as a markdown checklist into chat, then work through it — flip each `- [ ]` to `- [x]` as you finish.",
86
+ "首先,在聊天中输出中文工作流待办清单(Checklist),随后逐步执行,并在完成每步后将 `- [ ]` 标记为 `- [x]`:",
116
87
  );
117
88
  blocks.push("");
118
89
  const todoSteps = [
119
- `Confirm the plugin-prepared manifest is readable: ${manifestPath}`,
120
- `Confirm the target workspace is readable: ${workspacePath}`,
90
+ `确认插件准备的运行清单可读: ${manifestPath}`,
91
+ `确认目标工作区可读: ${workspacePath}`,
121
92
  lite
122
- ? "Run one workflowScript: the lite-reviewer (one subagent call)"
93
+ ? "执行 workflowScript: 启动单兵极速审查专家 (单次 subagent 调用)"
123
94
  : gateOn
124
- ? `Run one workflowScript: ${reviewers.length} parallel reviewers + inline gate (one subagent call)`
125
- : `Run one workflowScript: ${reviewers.length} parallel reviewers, no gate (one subagent call)`,
126
- "Call `pi_review_report` once with the workflow return value (never re-parse findings)",
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 — confirm the plugin-prepared manifest (no LLM-obtained diff).
132
- blocks.push("## Step 1 — Confirm the plugin-prepared run (you, the main agent)");
102
+ // Step 1
103
+ blocks.push("## 1 确认插件准备的环境(由主代理执行)");
133
104
  blocks.push("");
134
105
  blocks.push(
135
- `The extension has **already** cloned/checked out the target repo, fetched an accurate diff, computed SHA-256 of the diff, and written \`${manifestPath}\` plus \`${diffPath}\`.`,
106
+ `插件已经完成了目标仓库准备,提取了准确的 diff 并计算了 SHA-256 哈希,生成了 \`${manifestPath}\` \`${diffPath}\`。`,
136
107
  );
137
108
  blocks.push("");
138
- blocks.push("Verify with a single `bash` call with **no `&&` / `||` chains** and no network calls. Use one `test` per file (no compound operators):");
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("If any check fails, stop and notify the user. Otherwise continue.");
117
+ blocks.push("若任何检查失败,停止并通知用户;检查通过则继续执行第 2 步。");
147
118
  blocks.push("");
148
119
 
149
- // Step 2 — single workflowScript call.
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) — the directive presents it inside backticks, so this would corrupt the main agent's copy. This is a plugin bug; please report it.",
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 — 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)}`,
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
- // Directive still works from the template literal below.
155
+ /* ignore */
192
156
  }
193
157
  }
194
158
 
195
- blocks.push("## Step 2 — Run the review (exactly one subagent workflowScript call)");
159
+ blocks.push("## 2 执行代码审查(仅调用一次 subagent workflowScript");
196
160
  blocks.push("");
197
161
  blocks.push(
198
162
  lite
199
- ? "The script fans out the single lite-reviewer, which returns a Markdown report ending in a fenced JSON block."
163
+ ? "该脚本会启动单兵极速审查专家,输出包含 JSON 块的 Markdown 报告。"
200
164
  : gateOn
201
- ? "The script fans out the lean reviewers in parallel (Markdown reports), then feeds their reports to the gate, which returns a Markdown synthesis ending in a fenced JSON verdict block."
202
- : "The script fans out the lean reviewers in parallel (gate disabled in config); each returns a Markdown report.",
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
- `Copy the SUBAGENT CALL above verbatim (the workflowScript template-literal content is the exact text of \`${workflowPath ?? "workflow.js"}\`). If the call is rejected with a script parse error, \`Read\` the workflow.js file and repeat the call with that content — one retry only, no hand-editing.`,
182
+ `一字不差地复制上方代码执行。若出现解析错误,请使用 \`Read\` 读取 \`${workflowPath ?? "workflow.js"}\` 内容并重试一次。`,
221
183
  );
222
184
  blocks.push("");
223
185
 
224
- // Step 3 — tool call.
225
- blocks.push("## Step 3 — Render the report (call `pi_review_report`)");
186
+ // Step 3
187
+ blocks.push("## 3 渲染审查报告(调用 `pi_review_report`)");
226
188
  blocks.push("");
227
189
  blocks.push(
228
- "Call the `pi_review_report` tool **exactly once** with `{ runId, workflowReturn }`. The tool loads the manifest, extracts + validates the gate's fenced JSON verdict block, runs the deterministic verdict rules, and renders the final markdown + persists a session entry. Do not re-write findings yourself.",
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 task — review only. Do not write any files. Do not edit files. Return findings 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; // resolved below per-id
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
- `Read ${JSON.stringify(diffPath)} as the change — the diff is the authoritative change record; workspace files are context only. When a workspace file disagrees with the diff, trust the diff and note the discrepancy in coverage.limitations.`,
320
- `Also read ${JSON.stringify(manifestPath)} for change-profile (docsOnly, file list, rule file paths). Do not re-fetch via gh/git.`,
321
- `Your cwd is the target workspace (${JSON.stringify(workspacePath)}). Run all read/grep/git from there.`,
322
- "Stay within budgets; finish with your Markdown report (Summary / Findings / Coverage) as your final message and stop.",
323
- "Do not read plan.md, progress.md, anything under .pi-subagents/ (artifacts and transcripts included), or node_modules.",
324
- "Prefer Read/Grep. If you use bash, only simple allowlisted commands (no &&/||/; compounds).",
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
- `If change-profile.rulePaths is empty, return status: skipped with empty issues — do not invent rule violations.`,
251
+ "若 change-profile.rulePaths 为空,返回跳过状态:SKIPPED: no-rules,不提出虚构的违规。",
329
252
  );
330
253
  }
331
254
  if (r.id === "history-context") {
332
255
  taskParts.push(
333
- `If change-profile.history.available is false, return status: skipped with empty issues. Take ≤5 paths from the file list and run ONE bash: git log -n 5 --oneline -- <file1> <file2> ...`,
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
- `If change-profile.docsOnly is true, return status: skipped with empty issues.`,
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
- `If change-profile.docsOnly is true, return status: skipped with empty issues. Otherwise prefer diff-only; at most 3 extra file reads.`,
266
+ "若 change-profile.docsOnly true,返回跳过状态:SKIPPED: docs-only。否则优先从 diff 本身分析,最多只读取 3 个额外上下文文件。",
344
267
  );
345
268
  }
346
269
  if (userContext?.trim()) {
347
- taskParts.push(`User request: ${userContext.trim()}`);
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
- // ---- gate ----------------------------------------------------------
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
- `Synthesize reviewer findings for ${targetLabel}.`,
389
- `The full diff is at ${JSON.stringify(diffPath)} and your cwd is the target workspace — you CAN and SHOULD verify candidates yourself.`,
390
- `Threshold ${threshold}: drop candidates with finalConfidence < ${threshold}.`,
391
- `Inputs are the reviewers' Markdown reports (## Summary / ## Findings / ## Coverage sections, one per reviewer).`,
392
- `Re-score every candidate 1–10. For each blocker/major candidate, first try to verify it by reading the diff hunk and the touched file in the workspace; state what you checked in the disposition reason.`,
393
- `Never raise a candidate above 8 without your own verification evidence from the diff or workspace files.`,
394
- `If you cannot verify a blocker/major candidate (missing context, truncated diff), do NOT silently drop it: keep it at the reviewer's original confidence, prefix the reason with "unverified:", and let the human decide — the parent's report tool floors unverified blocker/major candidates at the threshold so they stay visible.`,
395
- `Every candidate must appear in dispositions with decision (kept | dropped | merged), originalConfidence, finalConfidence, sourceReviewers, reason.`,
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
- ? `Verdict (legacy): request_changes if any blocker OR >=3 majors; approve if no blocker/major; else comment.`
398
- : `Verdict (strict): request_changes if any surviving blocker or major; comment if only minor/nit; approve if no surviving issues.`,
399
- `The parent re-applies verdict in code; this is a recommendation.`,
400
- `Skip false positives: ${FALSE_POSITIVE_GUIDANCE}.`,
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 = '## Reviewer: ' + r.key + (r.ok ? '' : ' (FAILED: ' + String(r.error || 'run failed').slice(0, 120) + ')');");
408
- lines.push(" return head + '\\n\\n' + String(r.output || '(no output)').slice(0, 6000);");
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# Reviewer reports (Markdown)\\n\\n' + reviewerSections.join('\\n\\n---\\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
+ }
@@ -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 (injected once into the directive).
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
- "Pre-existing issues on lines untouched by this change",
101
- "Pedantic nitpicks a senior engineer would not call out",
102
- "Issues a linter, typechecker, or CI would catch",
103
- "Generic quality (missing tests/docs) unless a project rule explicitly requires it",
104
- "Something that looks like a bug but is intentional given the change",
105
- ].join("; ");
78
+ "本次改动未触及的历史遗留代码问题",
79
+ "资深工程师不会指出的吹毛求疵风格琐碎建议",
80
+ "Linter、类型检查器或 CI 构建会自动捕获的问题",
81
+ "泛泛的代码质量建议(如建议补单测或文档),除非项目规则明确强制要求",
82
+ "表面看似 Bug 但实际属于本次改动预期特性的行为",
83
+ ].join("; ");