@unifan/pi-unifan-zh 1.0.39 → 1.0.41

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.
@@ -11,9 +11,9 @@ inheritSkills: false
11
11
 
12
12
  你是 Bugbot(逻辑 Bug 审查专家)。你的任务是找出**本次修改引入或改动的代码中的真实逻辑缺陷**。
13
13
 
14
- ## 执行计划(保持简短高效)
14
+ ## 执行计划(结合完整代码上下文,严禁断章取义)
15
15
  1. 读取任务中指定的 diff 内容。如果是纯文档变更,输出 `已跳过: 仅文档变更` 且不提出问题。
16
- 2. 重点**从 diff 本身进行分析**。最多只读取 2~3 个必要的上下文文件。需要澄清符号时才执行简短的 `git log -n 5` / `git blame -L`。
16
+ 2. **严禁仅看 diff 差异切片断章取义**:先通过 diff 锁定变动范围,然后**必须使用 `read` / `grep` 查阅改动点所在的完整函数实现、类定义与上下游调用方上下文**。仅看 diff 碎片极易产生误报(如漏看外部前置校验与类状态机防护),必须在完整代码上下文中核实逻辑缺陷确实成立后才提出。
17
17
  3. 输出你的 Markdown 中文审查报告(格式见下方)作为最终回复并停止。尽量在 3~5 轮内完成。
18
18
 
19
19
  ## 审查范围
@@ -13,7 +13,7 @@ inheritSkills: false
13
13
 
14
14
  ## 执行计划
15
15
  1. 读取任务中的 diff 内容与规则文件路径。若无规则文件或纯文档变更,输出 `已跳过: 无规则文件` 或 `已跳过: 纯文档变更`。
16
- 2. 对照规则文件与本次修改,检查是否有明确违反架构禁令或命名约定的行为。
16
+ 2. 使用 `read` / `grep` 结合所属模块与完整代码上下文,对照规则文件与本次修改进行核实,严禁仅看 diff 局部切片断章取义。
17
17
  3. 输出 Markdown 中文审查报告。
18
18
 
19
19
  ## 输出格式(所有内容必须 100% 使用纯正中文)
@@ -11,6 +11,9 @@ inheritSkills: false
11
11
 
12
12
  你是**注释与可读性审查官(Comments Reviewer)**。你的任务是审查**改动中的注释是否过时误导、遗留的高危 TODO 以及被注释掉的残留废弃代码**。
13
13
 
14
+ ## 执行要求
15
+ - **结合完整上下文**:审查注释与实际逻辑是否一致时,必须使用 `read` 工具阅读所在函数或模块的完整实现,严禁仅看 diff 局部切片产生误解。
16
+
14
17
  ## 输出格式(必须 100% 使用中文输出)
15
18
  ## 审查概述
16
19
  中文一句话概述。
@@ -12,7 +12,7 @@ inheritSkills: false
12
12
  你是**编码规范审查专家(Conventions Reviewer)**。你的职责是仅依据项目中明确存在的代码规范文档对本次改动进行审查。
13
13
 
14
14
  ## 执行计划
15
- 1. 读取任务中指定的 diff 内容。严禁主观凭空捏造规范。
15
+ 1. 读取任务中指定的 diff 内容,并结合 `read` 查阅完整上下文核实代码规范。严禁主观凭空捏造规范,严禁仅看 diff 局部切片断章取义。
16
16
  2. 若项目中不存在显式规范文档,直接输出 `已跳过: 无规范文档` 且不提出问题。
17
17
  3. 输出 Markdown 中文报告作为你的最终回复并停止。
18
18
 
@@ -21,6 +21,7 @@ inheritSkills: false
21
21
  - 10: 证据确凿且经过你亲自核验的致命缺陷
22
22
 
23
23
  ## 仲裁与核验职责
24
+ - **严格核验上下文,过滤断章取义误报**:使用 `read` 工具抽查争议代码所在文件的完整上下文。若发现某个专家仅凭 diff 局部代码切片产生误判(例如上游调用方已有前置校验与守卫拦截、或者所谓“性能隐患”实为仅执行一次的初始化冷路径),**必须坚决予以剔除(置信度降至 1~3 分)并在裁决理由中明确说明上下文依据**。
24
25
  - 合并不同专家提出的重复问题(相同文件/行号/分类),保留最高评分。
25
26
  - 排除低置信度(<8)的无意义假警报。
26
27
  - 严格过滤内部 GM/调试(Debug)指令误报:在确认其通过编译开关或运行环境隔离于正式发布包的前提下,严禁判为缺陷;但若在正式发布包中外部可直接访问,仍属严重缺陷。
@@ -13,7 +13,7 @@ inheritSkills: false
13
13
 
14
14
  ## 执行计划
15
15
  1. 读取 diff 内容。若无历史记录可用或纯文档修改,写 `已跳过: 无历史记录` 或 `已跳过: 纯文档变更`。
16
- 2. 针对改动核心函数,使用 `git log -n 5` 或 `git blame -L` 快速确认历史意图。
16
+ 2. 针对改动核心函数,使用 `read` 查阅完整函数上下文,并结合 `git log -n 5` 或 `git blame -L` 快速确认历史意图,严禁仅看 diff 差异切片单点臆断。
17
17
  3. 输出 Markdown 中文报告。
18
18
 
19
19
  ## 输出格式(所有内容必须 100% 使用纯正中文)
@@ -11,10 +11,10 @@ inheritSkills: false
11
11
 
12
12
  你是**极速单兵审查官(Lite Reviewer)**。你的任务是对本次改动做一次快速、全面、高精准度的体检(涵盖逻辑 Bug、安全隐患与规范一致性)。追求快而准。
13
13
 
14
- ## 执行计划(严格极简,2~3轮内强制完成)
14
+ ## 执行计划(严格极简,2~3轮内高效完成)
15
15
  1. 读取任务中的 diff 内容。纯文档变更直接报告 `已跳过: 仅文档变更`。
16
- 2. 重点聚焦 diff 本身代码逻辑。若上下文足够直接撰写报告;若确需核验,最多仅读 1 个相关文件或单次 grep。
17
- 3. **必须在第 2~3 轮内输出最终 Markdown 中文报告并立即停止**!严禁进行漫游探索!
16
+ 2. **结合上下文精准核验**:审查绝不能仅停留在 diff 差异表面。针对改动的核心函数与关键逻辑,**必须使用 `read` 查阅其所在的完整函数实现与周边生命周期上下文**,结合真实代码语境排查高危缺陷,严禁脱离上下文断章取义。
17
+ 3. **必须在第 2~3 轮内输出最终 Markdown 中文报告并立即停止**!精准定位,严禁无目的漫游探索!
18
18
 
19
19
  ## 重点关注
20
20
  逻辑致命缺陷与防御性隐患第一,明确的规范冲突第二。不提废话风格建议。特别注意:游戏与客户端工程中的内部 GM 工具面板、Debug 调试指令及测试辅助代码,在已通过宏隔离于正式发布包的前提下免检,切勿误判。
@@ -11,6 +11,10 @@ inheritSkills: false
11
11
 
12
12
  你是**通用性能与基准测试审查专家**。你的职责是从计算机底层与系统架构视角,专项排查代码改动中的性能退化、内存与资源开销、计算瓶颈、并发竞争与 I/O 放大问题,并主动探索和执行基准测试。
13
13
 
14
+ ## ⚠️ 核心审查原则:结合代码完整上下文(严禁仅看 diff 差异切片)
15
+ - **绝不脱离上下文**:绝对严禁仅凭 diff 片段断章取义!排查性能瓶颈、内存分配或时间复杂度时,**必须使用 `read` / `grep` 查看完整的函数实现、类生命周期、数据规模预估与调用频次**。
16
+ - **避免虚假警报**:避免将仅在初始化时执行一次的单次操作误判为热路径性能退化;避免在数据集极小且固定的场景下过度推崇复杂的数据结构(如在少于 10 个元素的集合上过度设计缓存或复杂索引)。必须结合调用方上下文与实际运行频率做出专业评估。
17
+
14
18
  ## 🎯 核心审查维度(通用跨语言体系)
15
19
 
16
20
  ### 1. 内存与资源分配
@@ -58,7 +62,7 @@ inheritSkills: false
58
62
 
59
63
  ## 📋 执行流程
60
64
  1. 读取任务中的 diff 内容与清单。
61
- 2. 重点排查改动中涉及热点循环、数据结构选型、内存申请与并发/IO 的代码。
65
+ 2. 使用 `read` / `grep` 查看改动涉及函数的完整上下文与调用链路,重点排查热点循环、数据结构选型、内存申请与并发/IO 的代码。
62
66
  3. 检查并运行基准测试(如适用)。
63
67
  4. 输出结构化的 Markdown 中文性能报告,并在末尾输出供机器读取的 JSON 块。
64
68
 
@@ -13,6 +13,10 @@ inheritSkills: false
13
13
 
14
14
  你是**代码健壮性与合规审查专家**。你的任务是审查本次代码改动是否存在防御性不足、边界校验缺失或配置安全隐患。
15
15
 
16
+ ## ⚠️ 核心审查原则:结合代码完整上下文(严禁仅看 diff 差异切片)
17
+ - **绝不脱离上下文**:绝对严禁仅凭 diff 片段断章取义!报告安全隐患或输入校验缺失前,**必须使用 `read` / `grep` 查看完整的函数上下文、类定义、调用方前置守卫(Guard Clause)、中间件校验以及上游数据来源**。
18
+ - **避免虚假警报**:如果上游调用方、路由中间件或构造器已完成了类型检查、合法性过滤或鉴权保护,严禁在内部实现中误报“缺少校验”;只有在完整数据流经路径上确无任何防护且可造成实际威胁时才判定为缺陷。
19
+
16
20
  ## 审查重点
17
21
  1. **配置合规**:排查是否存在意外提交的私有凭证、明文密钥或不安全的默认配置。
18
22
  2. **输入与边界**:排查外部输入数据是否缺乏合法性校验、类型断言或长度/范围限制。
@@ -131,31 +131,31 @@ type ReviewTarget =
131
131
 
132
132
  // 针对不同审查目标的中文提示词
133
133
  const LITE_REVIEW_PROMPT =
134
- "【极速体检模式】请首先运行 `git diff` 获取代码改动,并针对改动涉及的核心函数按需使用 `read` 查阅周边上下文(如所在完整方法实现与生命周期),快速排查高危逻辑异常、运行时崩溃与防御性缺陷。注意:项目中内部 GM/Debug 调试工具在确认与生产环境隔离的前提下免检,切勿误判;但若正式生产包可直接触达仍属漏洞。结合上下文精准核验后直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
134
+ "【极速体检模式】请首先运行 `git diff` 锁定代码变动范围。🚨【重要原则】:代码审查绝对不能只看 diff 差异切片,必须使用 `read` 深入查阅改动位置所在的完整函数实现、类定义及周边生命周期上下文,结合真实业务语境快速排查高危逻辑异常、运行时崩溃与防御性缺陷,杜绝断章取义造成的误判或漏判。注意:项目中内部 GM/Debug 调试工具在确认与生产环境隔离的前提下免检,切勿误判;但若正式生产包可直接触达仍属漏洞。结合完整上下文精准核验后直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
135
135
 
136
136
  const FULL_REVIEW_PROMPT =
137
- "【全量深度审查模式】请对当前代码改动展开深度的上下文关联审查:首先运行 `git diff` 全量查阅改动,必须使用 `read` / `grep` 深入查阅改动方法所在的完整类定义、调用方契约、状态机与生命周期等关键上下文,深入排查业务逻辑隐患、边界异常、并发安全、性能GC开销与架构契约。注意:项目中内部 GM 调试面板与测试辅助逻辑在已隔离于正式生产包的前提下免检,切勿误判。结合完整代码脉络直接输出详尽审查清单。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
137
+ "【全量深度审查模式】请对当前代码改动展开深度的上下文关联审查:🚨【最高核心原则】:严禁仅看 diff 差异碎片断章取义!审查必须先运行 `git diff` 全量锁定改动范围,再针对每一个核心改动点,必须使用 `read` / `grep` 深入查阅改动方法所在的完整类定义、调用方契约、状态机与生命周期等全局上下文,深入排查业务逻辑隐患、边界异常、并发安全、性能GC开销与架构契约。注意:项目中内部 GM 调试面板与测试辅助逻辑在已隔离于正式生产包的前提下免检,切勿误判。结合完整代码脉络与上下文直接输出详尽审查清单。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
138
138
 
139
139
  const UNCOMMITTED_PROMPT =
140
- "请审查当前代码的所有改动(包含暂存区、未暂存区以及新增文件)。先运行 `git diff` 与 `git status` 获取改动,再针对关键改动按需使用 `read` / `grep` 查阅周边上下文代码与调用链路,排查真实业务缺陷与边界异常(注意:项目中内部 GM/Debug 调试指令在与生产环境隔离的前提下属于正常研发代码,勿误判),结合完整上下文直接输出审查发现。严禁输出任何步骤清单或待办列表。所有输出必须使用纯正中文。";
140
+ "请审查当前代码的所有改动(包含暂存区、未暂存区以及新增文件)。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合代码的上下文一起看!请先运行 `git diff` 与 `git status` 获取改动范围,再针对关键改动必须使用 `read` / `grep` 查阅周边上下文代码、所在函数的完整实现与调用链路,排查真实业务缺陷与边界异常(注意:项目中内部 GM/Debug 调试指令在与生产环境隔离的前提下属于正常研发代码,勿误判),结合完整上下文直接输出审查发现。严禁输出任何步骤清单或待办列表。所有输出必须使用纯正中文。";
141
141
 
142
142
  const BASE_BRANCH_PROMPT_WITH_MERGE_BASE =
143
- "请审查当前分支相对于基准分支 '{baseBranch}' 的改动(合并基准 commit: {mergeBaseSha})。运行 `git diff {mergeBaseSha}` 查看改动,并按需使用 `read` / `grep` 结合周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
143
+ "请审查当前分支相对于基准分支 '{baseBranch}' 的改动(合并基准 commit: {mergeBaseSha})。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!运行 `git diff {mergeBaseSha}` 查看改动后,针对修改点必须使用 `read` / `grep` 结合所在完整函数、类定义与周边调用上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
144
144
 
145
145
  const BASE_BRANCH_PROMPT_FALLBACK =
146
- "请审查当前分支相对于基准分支 '{branch}' 的改动。先使用 `git merge-base` 与 `git diff` 查看改动,并按需使用 `read` / `grep` 结合周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
146
+ "请审查当前分支相对于基准分支 '{branch}' 的改动。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!先使用 `git merge-base` 与 `git diff` 查看改动,再使用 `read` / `grep` 查阅改动点所在的完整函数与周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
147
147
 
148
148
  const COMMIT_PROMPT_WITH_TITLE =
149
- '请审查提交 commit {sha} ("{title}") 引入的代码改动。运行 `git show {sha}` 查看差异,并按需使用 `read` / `grep` 结合周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。';
149
+ '请审查提交 commit {sha} ("{title}") 引入的代码改动。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!运行 `git show {sha}` 查看差异后,针对修改点必须使用 `read` / `grep` 查阅所在完整函数与周边调用上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。';
150
150
 
151
151
  const COMMIT_PROMPT =
152
- "请审查提交 commit {sha} 引入的代码改动。运行 `git show {sha}` 查看差异,并按需使用 `read` / `grep` 结合周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
152
+ "请审查提交 commit {sha} 引入的代码改动。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!运行 `git show {sha}` 查看差异后,针对修改点必须使用 `read` / `grep` 查阅所在完整函数与周边调用上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
153
153
 
154
154
  const PULL_REQUEST_PROMPT =
155
- '请审查 Pull Request #{prNumber} ("{title}") 相对于基准分支 \'{baseBranch}\' 的改动(合并基准 commit: {mergeBaseSha})。运行 `git diff {mergeBaseSha}` 查看改动,并按需使用 `read` / `grep` 结合周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。';
155
+ '请审查 Pull Request #{prNumber} ("{title}") 相对于基准分支 \'{baseBranch}\' 的改动(合并基准 commit: {mergeBaseSha})。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!运行 `git diff {mergeBaseSha}` 查看改动后,针对修改点必须使用 `read` / `grep` 查阅所在完整函数与周边调用上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。';
156
156
 
157
157
  const PULL_REQUEST_PROMPT_FALLBACK =
158
- '请审查 Pull Request #{prNumber} ("{title}") 相对于基准分支 \'{baseBranch}\' 的改动。先运行 `git diff` 查看改动,并按需使用 `read` / `grep` 结合周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。';
158
+ '请审查 Pull Request #{prNumber} ("{title}") 相对于基准分支 \'{baseBranch}\' 的改动。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!先运行 `git diff` 查看改动后,针对修改点必须使用 `read` / `grep` 查阅所在完整函数与周边调用上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。';
159
159
 
160
160
  const FOLDER_REVIEW_PROMPT =
161
161
  "请对以下目录/文件路径的代码进行快照审查:{paths}。直接读取这些文件并结合上下文直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
@@ -166,7 +166,13 @@ const REVIEW_RUBRIC = `> 💡【工程环境与上下文声明】:
166
166
  >
167
167
  > 🚨【最高执行原则】:
168
168
  > 1. **全流程纯中文**:思维链(Thinking)、推理分析与最终报告必须 100% 使用纯正中文,严禁包含任何英文段落或英文小标题!
169
- > 2. **结合上下文,拒绝断章取义**:仅看 diff 的片段容易产生误判。请**先运行 \`git diff\` 锁定变动,再针对改动涉及的关键方法、类生命周期或调用方,按需使用 \`read\` 或 \`grep\` 查阅必要的上下文代码**(精准核验,切勿无目的漫游遍历),结合真实完整的业务上下文做出准确裁决!
169
+ > 2. **结合完整上下文,严禁只看 diff 差异切片**:
170
+ > - 仅看 diff 局部的 +/- 增删碎片极易脱离语境断章取义,造成重大误判(如误把外部已保护的入参判为缺少校验、未看到完整函数的生命周期流转、或忽视既有架构的已有防护)。
171
+ > - **必须遵循“Diff 锁定改动范围 -> Read/Grep 深入查阅上下文”的严谨审查流程**:
172
+ > - 步骤一:先运行 \`git diff\` 或 \`git show\` 锁定被修改的文件与行号范围;
173
+ > - 步骤二:对每一个核心修改点,**必须使用 \`read\` 查阅其所在的完整函数实现、类定义、状态机与生命周期**;
174
+ > - 步骤三:按需使用 \`grep\` 追踪上下游调用方契约与数据来源;
175
+ > - 步骤四:只有结合完整上下文验证成立、证据确凿的真实缺陷,才允许记录到审查清单中!坚决杜绝因仅看 diff 片段产生的假警报与误报。
170
176
  > 3. **直接审查,拒绝繁琐步骤**:严禁打印任何工作流待办清单(Checklist)或环境测试命令!
171
177
 
172
178
  # 核心代码审查准则
@@ -174,16 +180,16 @@ const REVIEW_RUBRIC = `> 💡【工程环境与上下文声明】:
174
180
  你是一名资深技术专家,正在审查提交的代码改动。请结合完整代码上下文,直击本质,拦截真实缺陷:
175
181
 
176
182
  ## 重点排查范围
177
- 1. **代码正确性与边界**:结合函数整体逻辑与调用方,排查业务逻辑缺陷、空指针/未定义引用、边界越界、生命周期异常、未捕获的运行时崩溃。
183
+ 1. **代码正确性与边界**:结合函数完整上下文与上下游调用方,排查业务逻辑缺陷、空指针/未定义引用、边界越界、生命周期异常、未捕获的运行时崩溃。
178
184
  2. **并发与状态安全**:结合上下文状态机与生命周期,排查竞态条件、死锁隐患、异步缺少等待、脏状态残留。
179
- 3. **性能与内存开销**:高频主循环内的大量堆内存分配 (GC 压力)、不必要的深拷贝、高复杂度算法、资源句柄泄露。
180
- 4. **输入验证与安全边界**:外部未受信任输入是否缺乏合法性检查、参数未做参数化转义、类型断言与长度/范围限制,是否存在跨目录访问或越权风险。
185
+ 3. **性能与内存开销**:结合调用频次与上下文数据规模,排查高频主循环内的大量堆内存分配 (GC 压力)、不必要的深拷贝、高复杂度算法、资源句柄泄露。
186
+ 4. **输入验证与安全边界**:结合数据流转来源排查外部未受信任输入是否缺乏合法性检查、参数未做参数化转义、类型断言与长度/范围限制,是否存在跨目录访问或越权风险。
181
187
  5. **只关注本次改动**:严禁将改动前既有的历史代码当作本次改动的缺陷。
182
188
 
183
189
  ## 严格过滤误报
184
190
  - ❌ 严禁提出吹毛求疵、纯属个人审美的风格建议。
185
191
  - ❌ 严禁提出 Linter / 类型检查器会自动捕获的浅层格式建议。
186
- - ❌ 严禁脱离上下文进行无端猜测,每条问题必须有明确的代码与上下文事实证据。
192
+ - ❌ 严禁脱离上下文进行无端猜测,每条问题必须有明确的代码与完整上下文事实证据。
187
193
  - 💡 **开发与测试工具免检准则**:在游戏、客户端与业务系统中,\`GM\`(Game Master/内部调试面板)、\`Debug\` 指令、测试出战模拟等属于受信任的研发辅助逻辑,**在通过编译宏(如 \`#if UNITY_EDITOR || DEVELOPMENT_BUILD\`)或环境判断已隔离于正式生产环境的前提下,严禁将其误判为安全漏洞或缺陷**;但若此类入口在正式生产发布包中无防护可被客户端或外部请求触达,仍属 P0 安全隐患,必须报告。
188
194
 
189
195
  ## 缺陷等级标记 [P0~P3]
@@ -196,8 +202,8 @@ const REVIEW_RUBRIC = `> 💡【工程环境与上下文声明】:
196
202
  ### 审查发现清单
197
203
  每个问题按以下格式列出(若无存活缺陷,明确输出:\`未发现存活的代码缺陷,代码质量良好,建议合并。\`):
198
204
  - **[P0|P1|P2|P3] 简短标题**:\`文件路径:行号\`
199
- - **缺陷说明**:结合上下文简明说明后果与触发场景。
200
- - **代码证据**:引用关键代码及上下文。
205
+ - **缺陷说明**:结合完整代码上下文简明说明后果与触发场景。
206
+ - **代码证据**:引用关键代码及所在上下文。
201
207
  - **修改建议**:给出最小化修复思路或代码替换块(可使用 \`\`\`suggestion)。
202
208
 
203
209
  ### 综合裁决
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unifan/pi-review-zh",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "description": "Pi 交互式 AI 代码审查扩展(支持单模型与多 Subagent 并发 2~6 专家自由切换,Codex 经典架构)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -78,13 +78,14 @@ export function buildSubagentOrchestrationPrompt(
78
78
  ${listText}
79
79
 
80
80
  ### 协作审查执行规范:
81
- 1. **并发调用子代理**:先调用 \`subagent({ action: "list", capabilities: true })\`,确认上述专家均可执行且未禁用;再通过下方**一个异步工作流**并行启动全部专家。每个专家必须使用示例中的 \`model\` 参数,继承本次会话模型与思考档位(${thinkingLevel}),不得省略或改用代理默认档位。运行失败时报告具体原因并停止,不得静默更换模型或执行方式:
81
+ 1. **上下文审查铁律**:所有专家在审查时,**绝对严禁仅看 diff 差异片段断章取义**!必须结合改动位置所在的完整函数实现、类定义、状态机与调用方上下文深入分析,确认在真实全局代码执行流中确实存在问题后才提出。
82
+ 2. **并发调用子代理**:先调用 \`subagent({ action: "list", capabilities: true })\`,确认上述专家均可执行且未禁用;再通过下方**一个异步工作流**并行启动全部专家。每个专家必须使用示例中的 \`model\` 参数,继承本次会话模型与思考档位(${thinkingLevel}),不得省略或改用代理默认档位。运行失败时报告具体原因并停止,不得静默更换模型或执行方式:
82
83
  ${fence}js
83
84
  ${callsExample}
84
85
  ${fence}
85
- 2. **主审裁判长汇总整理**:当所有专家子代理执行完毕返回发现后,请你作为主审裁判长${gateEnabled ? "(门禁裁决)" : ""}:
86
- - 全面综合各专家的审查意见,对相同问题进行去重,剔除误报和低置信度内容(注意:项目中合法的内部 GM / Debug 调试工具在确保与正式生产环境隔离的前提下免检)。
86
+ 3. **主审裁判长汇总整理**:当所有专家子代理执行完毕返回发现后,请你作为主审裁判长${gateEnabled ? "(门禁裁决)" : ""}:
87
+ - 全面综合各专家的审查意见,重点甄别并剔除脱离上下文仅看 diff 产生的假警报,对相同问题进行去重,剔除误报和低置信度内容(注意:项目中合法的内部 GM / Debug 调试工具在确保与正式生产环境隔离的前提下免检)。
87
88
  - 严格按照《核心代码审查准则》的 **[P0~P3]** 等级标准排布审查清单。
88
89
  - 给出最终综合裁决与一句话中文总评。
89
- 3. **语言强制要求**:所有任务入参、思考分析过程、综合汇报与最终报告必须 100% 为纯正中文,严禁出现任何英文段落或未翻译小标题!`;
90
+ 4. **语言强制要求**:所有任务入参、思考分析过程、综合汇报与最终报告必须 100% 为纯正中文,严禁出现任何英文段落或未翻译小标题!`;
90
91
  }
@@ -425,21 +425,17 @@ export default function workflowExtension(pi: ExtensionAPI) {
425
425
  }
426
426
  });
427
427
 
428
- // 监听整段运行结束 (agent_end):检测 abort 信号或中止消息并挂起,防止 runaway 循环
428
+ // 监听整段运行结束 (agent_end):在 agent_end 中无缝驱动下一单元 (Native Continuous Loop)
429
429
  pi.on("agent_end", async (event, ctx) => {
430
430
  if (workDriverInstance && workDriverInstance.getStatus().isActive) {
431
- const hasAborted =
432
- ctx.signal?.aborted ||
433
- event.messages.some((m: any) => m?.role === "assistant" && m?.stopReason === "aborted");
434
- if (hasAborted) {
431
+ await workDriverInstance.onAgentEnd(event, ctx, pi);
432
+ if (!workDriverInstance.getStatus().isActive) {
435
433
  detachEscapeListener();
436
- await workDriverInstance.pause("检测到会话中断信号 (Ctrl+C / Esc)");
437
- ctx.ui?.setStatus?.("workflow", undefined);
438
434
  }
439
435
  }
440
436
  });
441
437
 
442
- // 核心事件循环:每个回合结束后,若仍有未完成单元,自动注入下一回合实现“不做完不停机”
438
+ // 核心事件循环兜底:每个回合彻底结算后,若仍有未完成单元,提供二次兜底保障
443
439
  pi.on("agent_settled", async (_event, ctx) => {
444
440
  if (workDriverInstance && workDriverInstance.getStatus().isActive) {
445
441
  await workDriverInstance.onAgentSettled(ctx, pi);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unifan/pi-workflow-zh",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Pi 复合工程工作流引擎(状态感知 / 断点续跑 / 输出压缩 / 自动化工具集)",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -5,41 +5,46 @@ description: "自主循环干活与严格 TDD 编码执行。按 Plan 中的 Imp
5
5
 
6
6
  # 03-work: 自主循环干活与严格 TDD 编码执行
7
7
 
8
- 当计划制定完毕后(已有 `docs/plans/`),执行本技能(`/skill:03-work` 或口述“执行03”、“开始干活”)直接进入自主编码推进阶段。
8
+ 当计划制定完毕后(已有 `docs/plans/`),执行本技能(`/skill:03-work`)直接进入自主编码推进阶段。
9
9
 
10
- > ⚡ **原生自主循环驱动机制 (Autonomous Driving Loop)**:
11
- > 无需记忆任何多余命令!进入 `03-work` 后,工作流扩展会自动在底层接管事件循环,按计划顺序逐个推进 Implementation Units。**每个单元完成后自动跨回合无缝驱动下一个单元,不做完所有规划单元绝不停机!**
10
+ > ⚡ **连续自主推进机制 (Autonomous Continuous Loop)**:
11
+ > 进入 `03-work` 后,必须按计划顺序逐个推进所有 Implementation Units。**严禁完成一个步骤就停下等待用户!每个单元完成后立即自主推进下一个单元,不做完所有规划单元绝不停机!**
12
12
 
13
13
  ---
14
14
 
15
15
  ## 🎯 启动与交互方式
16
16
 
17
17
  - **启动自主干活**:
18
- 直接运行 `/skill:03-work`(或在 00-next 引导下确认执行)。扩展自动定位 `docs/plans/` 下最新计划,解析所有 Implementation Units,并从上次断点处无缝接力。
18
+ 直接运行 `/skill:03-work`(亦可在 00-next 引导下确认执行)。自动定位 `docs/plans/` 下最新计划,智能解析所有 Implementation Units(或步骤/任务),并从上次断点处接力推进。
19
19
  - **随时暂停**:
20
20
  干活过程中,若需打断,直接在输入框输入 `暂停`、`停止`(或按下终端 `Esc` 键),扩展将安全保存现场断点并挂起循环。
21
21
  - **恢复干活**:
22
- 随时再次调用 `/skill:03-work` 或发送 `继续`,即可从断点处再次恢复自主循环推进。
22
+ 随时再次调用 `/skill:03-work`,即可从断点处再次恢复自主循环推进。
23
23
  - **查看状态**:
24
24
  随时运行 `/workflow`,即可查看当前 03-work 自主推进进度看板。
25
25
 
26
26
  ---
27
27
 
28
- ## 🛡️ 核心工程纪律与硬门禁
28
+ ## 🛡️ 核心工程纪律与连续推进铁律
29
29
 
30
- 1. **严格 TDD (红-绿-重构)**:
30
+ 1. **连续自主推进(严禁完成单步即停)**:
31
+ - 当任务目标或计划中包含多个步骤时,**绝对严禁每完成一步就停下向用户汇报或等待用户输入**!
32
+ - 每完成一个 Unit 并验证通过、保存断点后,**必须立即自主推进下一个待办 Unit**,连续执行直到计划中的所有 Implementation Units 全部完成并通过验证!
33
+ - 即使单次回合达到上下文或工具调用限制而结束,底层工作流扩展也会在 `agent_end` 自动注入续跑指令无缝接力下一单元。
34
+ 2. **严格 TDD (红-绿-重构)**:
31
35
  - 编写或补充对应当前 Unit 的测试用例(RED)。
32
36
  - 实现让测试通过的最简业务代码(GREEN)。
33
37
  - 运行该单元的确定性验收命令,确保 100% 绿灯。
34
- 2. **断点自动同步 (Session Checkpoint)**:
38
+ 3. **断点自动同步 (Session Checkpoint & Markdown Checkboxes)**:
35
39
  - 每完成一个 Unit 并验证通过后,**必须立即调用**:
36
40
  `session_checkpoint({ operation: "save", planPath: "...", completedUnits: ["Unit X"] })`
37
- - 底层引擎检测到断点更新后,会自动跨回合调度下一单元,模型绝不中途停下向用户求确认。
38
- 3. **Stop-the-line (遇错即停硬门禁)**:
41
+ 或在计划 Markdown 对应条目勾选 `[x]`。
42
+ - 记录断点后不要停顿,立即开始下一单元。
43
+ 4. **Stop-the-line (遇错即停硬门禁)**:
39
44
  - 当遇到非预期错误时,优先排查根因自愈。
40
45
  - **3次失败熔断阀门**:同一单元若连续 3 次排查修复仍失败,底层引擎强制启动 Stop-The-Line 保护,立即暂停自主循环并如实向用户报告现场,严禁盲目乱试或绕过未通过的测试。
41
- 4. **全量闭环与自动转交**:
42
- - 当所有 Implementation Units 均已通过验收命令时,自主循环自动宣告完成,并**自动流转至 `/skill:04-review` 进行全量代码审查与回归验证**。
46
+ 5. **全量闭环与自动转交**:
47
+ - 当所有 Implementation Units 均已通过验收命令时,自主循环宣告完成,并**自动流转至 `/skill:04-review` 进行全量代码审查与回归验证**。
43
48
 
44
49
  ---
45
50
 
@@ -55,6 +60,6 @@ description: "自主循环干活与严格 TDD 编码执行。按 Plan 中的 Imp
55
60
  │ [保存断点 SAVE] <── [验证通过 GREEN] <── [编写实现代码]
56
61
  │ │
57
62
  │ ▼
58
- └── (还有待办 Unit?) ──> 是: 引擎底层自动注入下一回合 (跨回合自主续跑)
63
+ └── (还有待办 Unit?) ──> 是: 立即自主执行下一个 Unit (连续推进,绝不停机等待)
59
64
  └── 否: 全部完成!自动流转至 04-review 审查
60
65
  ```
@@ -7,6 +7,15 @@ description: "代码质量、Spec 规格与安全性审查。检查 Git 变更
7
7
 
8
8
  在编码实现完成(`03-work` 完成)后,使用本技能对所有变更进行严谨的质量把关与回归验证。
9
9
 
10
+ ## ⚠️ 核心审查原则:结合代码完整上下文(严禁仅看 diff 差异切片)
11
+
12
+ 1. **Diff 锁定改动范围**:首先通过 `git diff` 识别所有发生增删改的代码行与受影响的文件清单。
13
+ 2. **Read/Grep 深入上下文**:对每一个变动点,**绝对严禁仅凭局部 diff 片段断章取义**。必须使用 `read` / `grep` 查阅变动位置所在的完整函数实现、类定义、调用方前置守卫与全局数据流。
14
+ 3. **结合全貌精准判定**:
15
+ - 核实上游调用方是否已有前置校验与守卫拦截,避免误报“缺少安全防范”。
16
+ - 核实调用频次与数据规模,避免把仅执行一次的冷路径误判为“性能瓶颈”。
17
+ - 确认变更在全局执行流中确有逻辑缺陷或性能风险后,方可提出缺陷。
18
+
10
19
  ## 审查五大维度
11
20
 
12
21
  1. **Spec 规格一致性**:
@@ -7,17 +7,22 @@ export function isExplicit03WorkTrigger(prompt: string): boolean {
7
7
  const trimmed = prompt.trim();
8
8
  const lower = trimmed.toLowerCase();
9
9
 
10
- // 1. Direct skill invocation or slash command
10
+ // 1. Pi 展开后的 Skill XML 标记: <skill name="03-work" ...>
11
+ if (lower.includes('<skill name="03-work"') || lower.startsWith('<skill name="03-work"')) {
12
+ return true;
13
+ }
14
+
15
+ // 2. 规范技能调用
11
16
  if (lower.startsWith("/skill:03-work")) {
12
17
  return true;
13
18
  }
14
19
 
15
- // 2. Strict keywords and natural language commands to start/resume 03 work
20
+ // 3. 严格意图的自然语言指令
16
21
  const strictPatterns = [
17
- /^开始(干活|编码|实现|写代码)/,
22
+ /^开始(?:干活|编码|实现|写代码)/,
18
23
  /^执行03-work/,
19
- /^自主(干活|工作|编码)/,
20
- /^(继续|恢复)(干活|工作|work)/i,
24
+ /^自主(?:干活|工作|编码)/,
25
+ /^(?:继续|恢复)(?:干活|工作|work)/i,
21
26
  /^resume\s+work/i,
22
27
  ];
23
28
 
@@ -25,7 +30,7 @@ export function isExplicit03WorkTrigger(prompt: string): boolean {
25
30
  return true;
26
31
  }
27
32
 
28
- // 3. Autonomous continuation follow-up prompt generated by the driver itself
33
+ // 4. 自主循环驱动引擎内部生成的跨回合续跑提示词
29
34
  if (trimmed.startsWith("【03-work 自主循环驱动引擎")) {
30
35
  return true;
31
36
  }
@@ -8,6 +8,50 @@ export interface PlanUnit {
8
8
  id: string; // e.g. "Unit 0"
9
9
  title: string; // e.g. "Unit 0 — 探针:面板焦点 / 键盘 / IME"
10
10
  raw: string;
11
+ checked?: boolean;
12
+ }
13
+
14
+ /**
15
+ * 规范化单元标识符(如将 "Unit 0: DB Schema"、"unit 0"、"0" 等统一映射至标准 Unit ID)
16
+ */
17
+ export function normalizeUnitId(rawId: string, knownUnits?: PlanUnit[]): string {
18
+ if (!rawId) return "";
19
+ const trimmed = rawId
20
+ .trim()
21
+ .replace(/^#+\s*/, "")
22
+ .replace(/^[-*+]\s*(?:\[[ xX]\]\s*)?/, "")
23
+ .replace(/^\d+\.\s*(?:\[[ xX]\]\s*)?/, "");
24
+
25
+ if (knownUnits && knownUnits.length > 0) {
26
+ const exact = knownUnits.find((u) => u.id.toLowerCase() === trimmed.toLowerCase());
27
+ if (exact) return exact.id;
28
+
29
+ const starts = knownUnits.find((u) => trimmed.toLowerCase().startsWith(u.id.toLowerCase()));
30
+ if (starts) return starts.id;
31
+
32
+ const numMatch = trimmed.match(/(\d+)/);
33
+ if (numMatch) {
34
+ const found = knownUnits.find((u) => {
35
+ const uNum = u.id.match(/(\d+)/);
36
+ return uNum && uNum[1] === numMatch[1];
37
+ });
38
+ if (found) return found.id;
39
+ }
40
+ }
41
+
42
+ const unitMatch = trimmed.match(/^(?:Implementation\s+Unit|Unit)\s*([0-9A-Za-z_-]+)/i);
43
+ if (unitMatch) return `Unit ${unitMatch[1]}`;
44
+
45
+ const zhMatch = trimmed.match(/^(?:步骤|单元|任务|阶段)\s*([0-9A-Za-z_-]+|[一二三四五六七八九十]+)/i);
46
+ if (zhMatch) return `步骤 ${zhMatch[1]}`;
47
+
48
+ const enMatch = trimmed.match(/^(?:Step|Task|Phase)\s*([0-9A-Za-z_-]+)/i);
49
+ if (enMatch) return `Unit ${enMatch[1]}`;
50
+
51
+ const numOnly = trimmed.match(/^([0-9]+)$/);
52
+ if (numOnly) return `Unit ${numOnly[1]}`;
53
+
54
+ return trimmed;
11
55
  }
12
56
 
13
57
  export interface WorkStatus {
@@ -34,11 +78,15 @@ export interface WorkStartResult {
34
78
  }
35
79
 
36
80
  /**
37
- * Parses Implementation Units from plan markdown.
38
- * Supports:
39
- * - `### Unit 0 — ...` or `### Unit 0: ...` or `## Unit 0`
81
+ * 从计划 Markdown 中解析执行单元 (Implementation Units / 步骤 / 任务)
82
+ * 支持格式:
83
+ * - `### Unit 0 — ...` / `### Unit 0: ...` / `## Unit 0`
40
84
  * - `### Implementation Unit 0: ...`
41
- * - `- [ ] Unit 0: ...`
85
+ * - `### 步骤 1: ...` / `### 步骤一: ...` / `### 单元 1: ...` / `### 任务 1: ...`
86
+ * - `### Step 1: ...` / `### Task 1: ...`
87
+ * - `- [ ] Unit 0: ...` / `- [x] Unit 0: ...`
88
+ * - `- [ ] 步骤 1: ...` / `- [x] 步骤 1: ...`
89
+ * - `- [ ] 1. xxx` / `- [x] 1. xxx`
42
90
  */
43
91
  export function parsePlanUnits(markdown: string): PlanUnit[] {
44
92
  const units: PlanUnit[] = [];
@@ -48,30 +96,41 @@ export function parsePlanUnits(markdown: string): PlanUnit[] {
48
96
  for (const line of lines) {
49
97
  const trimmed = line.trim();
50
98
 
51
- // Header match: ### Unit 0 — ... or ### Implementation Unit 1: ...
99
+ // 1. 标题匹配: ### Unit 0 — ... 或 ### 步骤 1: ... 或 ### 任务 1: ...
52
100
  const headerMatch = trimmed.match(
53
- /^#{2,4}\s+(?:Implementation\s+Unit|Unit)\s+([0-9A-Za-z_-]+)(?:\s*[:—–-]\s*(.*))?$/i,
101
+ /^#{2,4}\s+(?:Implementation\s+Unit|Unit|步骤|单元|任务|阶段|Step|Task|Phase)\s*([0-9A-Za-z_-]+|[一二三四五六七八九十]+)(?:\s*[:—–-]\s*(.*))?$/i,
54
102
  );
55
103
  if (headerMatch) {
56
- const id = `Unit ${headerMatch[1]}`;
57
- if (!seen.has(id.toLowerCase())) {
58
- seen.add(id.toLowerCase());
104
+ const rawKey = headerMatch[1];
105
+ const isZh = /^(?:步骤|单元|任务|阶段)/.test(trimmed.replace(/^#{2,4}\s+/, ""));
106
+ const id = isZh ? `步骤 ${rawKey}` : `Unit ${rawKey}`;
107
+ const lowerId = id.toLowerCase();
108
+ if (!seen.has(lowerId)) {
109
+ seen.add(lowerId);
59
110
  const title = headerMatch[2] ? `${id} — ${headerMatch[2].trim()}` : id;
60
- units.push({ id, title, raw: trimmed });
111
+ const checked = /\[[xX]\]|~~/.test(trimmed);
112
+ units.push({ id, title, raw: trimmed, checked });
61
113
  continue;
62
114
  }
63
115
  }
64
116
 
65
- // List match: - [ ] Unit 0: ... or - [x] Unit 0
117
+ // 2. 复选框/列表匹配: - [ ] Unit 0: ... 或 - [x] 步骤 1: ... 或 - [x] 1. ...
66
118
  const listMatch = trimmed.match(
67
- /^-\s+\[[ xX]\]\s+(?:Implementation\s+Unit|Unit)\s+([0-9A-Za-z_-]+)(?:\s*[:—–-]\s*(.*))?$/i,
119
+ /^(?:-\s+|\d+\.\s+)\[([ xX])\]\s+(?:(?:Implementation\s+Unit|Unit|步骤|单元|任务|阶段|Step|Task|Phase)\s*)?([0-9A-Za-z_-]+|[一二三四五六七八九十]+)(?:[:.、—–-]\s*(.*))?$/i,
68
120
  );
69
121
  if (listMatch) {
70
- const id = `Unit ${listMatch[1]}`;
71
- if (!seen.has(id.toLowerCase())) {
72
- seen.add(id.toLowerCase());
73
- const title = listMatch[2] ? `${id} — ${listMatch[2].trim()}` : id;
74
- units.push({ id, title, raw: trimmed });
122
+ const isChecked = listMatch[1].toLowerCase() === "x";
123
+ const rawKey = listMatch[2];
124
+ const isZh = /(?:步骤|单元|任务|阶段)/.test(trimmed);
125
+ const id = isZh ? `步骤 ${rawKey}` : `Unit ${rawKey}`;
126
+ const lowerId = id.toLowerCase();
127
+ if (!seen.has(lowerId)) {
128
+ seen.add(lowerId);
129
+ const title = listMatch[3] ? `${id} — ${listMatch[3].trim()}` : id;
130
+ units.push({ id, title, raw: trimmed, checked: isChecked });
131
+ } else if (isChecked) {
132
+ const existing = units.find((u) => u.id.toLowerCase() === lowerId);
133
+ if (existing) existing.checked = true;
75
134
  }
76
135
  }
77
136
  }
@@ -116,6 +175,7 @@ export class WorkLoopDriver {
116
175
  private maxRuns = 50; // Safety threshold against infinite runaway loops
117
176
  private timer: NodeJS.Timeout | null = null;
118
177
  private lastMessage = "";
178
+ private continuationDispatched = false;
119
179
 
120
180
  constructor(repoRoot?: string) {
121
181
  this.repoRoot = repoRoot || process.cwd();
@@ -149,6 +209,62 @@ export class WorkLoopDriver {
149
209
  };
150
210
  }
151
211
 
212
+ /**
213
+ * 同步最新进度:同时读取 Markdown 计划文档中的已勾选复选框 (- [x]) 与 Session Checkpoint JSON
214
+ */
215
+ async syncProgress(): Promise<void> {
216
+ if (!this.planPath) return;
217
+
218
+ // 1. 同步计划 Markdown 文档中的已勾选任务
219
+ try {
220
+ const mdContent = await readFile(this.planPath, "utf-8");
221
+ const freshUnits = parsePlanUnits(mdContent);
222
+ if (freshUnits.length > 0) {
223
+ this.allUnits = freshUnits;
224
+ }
225
+ for (const u of freshUnits) {
226
+ if (u.checked) {
227
+ this.completedUnits.add(u.id);
228
+ }
229
+ }
230
+ } catch {}
231
+
232
+ // 2. 同步 Checkpoint JSON 断点
233
+ try {
234
+ const cpResult = await executeSessionCheckpoint({
235
+ operation: "load",
236
+ repoRoot: this.repoRoot,
237
+ planPath: this.planPath,
238
+ planSlug: this.planSlug || undefined,
239
+ });
240
+
241
+ if (cpResult.checkpoint) {
242
+ const cp = cpResult.checkpoint;
243
+ if (cp.completedUnits) {
244
+ for (const raw of cp.completedUnits) {
245
+ const norm = normalizeUnitId(raw, this.allUnits);
246
+ this.completedUnits.add(norm);
247
+ }
248
+ }
249
+
250
+ if (cp.failedUnit) {
251
+ this.failedUnit = normalizeUnitId(cp.failedUnit, this.allUnits);
252
+ this.consecutiveFailures += 1;
253
+ this.lastFailureError = cp.error || "测试执行未通过";
254
+ this.failureHistory.push({
255
+ unit: cp.failedUnit,
256
+ error: cp.error,
257
+ timestamp: new Date().toISOString(),
258
+ });
259
+ } else {
260
+ this.consecutiveFailures = 0;
261
+ this.failedUnit = null;
262
+ this.lastFailureError = null;
263
+ }
264
+ }
265
+ } catch {}
266
+ }
267
+
152
268
  /**
153
269
  * Starts autonomous work loop on a target plan.
154
270
  */
@@ -196,21 +312,10 @@ export class WorkLoopDriver {
196
312
  this.failedUnit = null;
197
313
  this.lastFailureError = null;
198
314
  this.failureHistory = [];
199
-
200
- // Load existing checkpoint
201
- const cpResult = await executeSessionCheckpoint({
202
- operation: "load",
203
- repoRoot: this.repoRoot,
204
- planPath: targetPlan,
205
- planSlug: this.planSlug,
206
- });
207
-
208
315
  this.completedUnits.clear();
209
- if (cpResult.checkpoint && cpResult.checkpoint.completedUnits) {
210
- for (const u of cpResult.checkpoint.completedUnits) {
211
- this.completedUnits.add(u);
212
- }
213
- }
316
+
317
+ // 同步断点与已勾选单元
318
+ await this.syncProgress();
214
319
 
215
320
  const status = this.getStatus();
216
321
  if (status.remainingUnits.length === 0) {
@@ -272,95 +377,88 @@ export class WorkLoopDriver {
272
377
  `请严格执行以下闭环操作,完成后自动推进,严禁询问确认:`,
273
378
  `1. 遵循 TDD (红-绿-重构):编写/补充针对 ${status.currentUnit} 的测试用例。`,
274
379
  `2. 编写最小化实现代码,执行该单元的确定性验证命令,确保全绿灯。`,
275
- `3. 验证通过后,【必须立即调用】session_checkpoint({ operation: "save", planPath: "${this.planPath}", completedUnits: ["${status.currentUnit}"] }) 保存断点。`,
380
+ `3. 验证通过后,【必须立即调用】session_checkpoint({ operation: "save", planPath: "${this.planPath}", completedUnits: ["${status.currentUnit}"] }) 保存断点,或在计划文档对应项勾选 [x]。`,
276
381
  `4. 若遇到非预期报错,排查根因。若重试仍失败,调用 session_checkpoint({ operation: "fail", planPath: "${this.planPath}", failedUnit: "${status.currentUnit}", error: "..." })。`,
277
382
  `5. 执行完毕后若无致命异常,请直接准备推进下一单元。`,
278
383
  ].join("\n");
279
384
  }
280
385
 
281
386
  /**
282
- * Hooks into agent_settled to evaluate completion and trigger next turn if needed.
387
+ * 在单轮/整段结束 (agent_end) 时主动评估,无缝跨回合排队注入下一单元 (Native Continuous Loop)
283
388
  */
284
- async onAgentSettled(ctx: ExtensionContext, pi: ExtensionAPI): Promise<void> {
389
+ async onAgentEnd(
390
+ event: { messages: any[] },
391
+ ctx: ExtensionContext,
392
+ pi: ExtensionAPI,
393
+ ): Promise<void> {
285
394
  if (!this.isActive || !this.planPath) return;
286
395
 
287
- // 1. Check if user aborted (Esc pressed, abort signal, or aborted assistant message)
288
- if (ctx.signal?.aborted) {
289
- await this.pause("检测到用户中断信号 (Esc/Abort)");
396
+ // 1. 检查中断信号
397
+ const hasAborted =
398
+ ctx.signal?.aborted ||
399
+ event.messages.some((m: any) => m?.role === "assistant" && m?.stopReason === "aborted");
400
+ if (hasAborted) {
401
+ await this.pause("检测到会话中断信号 (Ctrl+C / Esc)");
290
402
  ctx.ui?.setStatus?.("workflow", undefined);
291
403
  ctx.ui?.notify?.("⏸️ 03-work 自主循环已响应中断信号安全暂停。", "info");
292
404
  return;
293
405
  }
294
406
 
295
- try {
296
- const branch = ctx.sessionManager?.getBranch?.() ?? [];
297
- const lastMsgEntry = branch
407
+ // 记录本轮开始前的当前目标
408
+ const currentTarget = this.getStatus().currentUnit;
409
+
410
+ // 2. 同步磁盘上的断点与 Markdown 勾选项
411
+ await this.syncProgress();
412
+
413
+ // 容错处理:若助手正常结束了本轮任务且未报告错误,但既未调用 session_checkpoint 也未修改 [x]
414
+ // 自动将当前单元标记为完成并同步断点,防止死锁停留在同一单元
415
+ if (currentTarget && !this.completedUnits.has(currentTarget)) {
416
+ const lastAssistant = event.messages
298
417
  .slice()
299
418
  .reverse()
300
- .find((e: any) => e?.type === "message" && e?.message?.role === "assistant");
301
- const lastAssistantMsg = (lastMsgEntry as any)?.message;
302
- if (lastAssistantMsg?.stopReason === "aborted") {
303
- await this.pause("检测到会话中断 (stopReason: aborted)");
304
- ctx.ui?.setStatus?.("workflow", undefined);
305
- ctx.ui?.notify?.("⏸️ 03-work 自主循环已响应中断信号安全暂停。", "info");
306
- return;
307
- }
308
- } catch {}
309
-
310
- // 2. Re-read checkpoint from disk to see what the agent achieved in this turn
311
- const cpResult = await executeSessionCheckpoint({
312
- operation: "load",
313
- repoRoot: this.repoRoot,
314
- planPath: this.planPath,
315
- planSlug: this.planSlug || undefined,
316
- });
317
-
318
- if (cpResult.checkpoint) {
319
- const cp = cpResult.checkpoint;
320
- if (cp.completedUnits) {
321
- for (const u of cp.completedUnits) {
322
- this.completedUnits.add(u);
323
- }
419
+ .find((m: any) => m?.role === "assistant");
420
+
421
+ const isAbortOrError =
422
+ lastAssistant?.stopReason === "aborted" ||
423
+ lastAssistant?.stopReason === "error" ||
424
+ Boolean(this.failedUnit);
425
+
426
+ if (!isAbortOrError) {
427
+ this.completedUnits.add(currentTarget);
428
+ // 异步回写 Checkpoint 保存断点,保证断点不丢
429
+ executeSessionCheckpoint({
430
+ operation: "save",
431
+ repoRoot: this.repoRoot,
432
+ planPath: this.planPath,
433
+ planSlug: this.planSlug || undefined,
434
+ completedUnits: Array.from(this.completedUnits),
435
+ }).catch(() => {});
324
436
  }
437
+ }
325
438
 
326
- // Check Stop-The-Line failure gate
327
- if (cp.failedUnit) {
328
- this.failedUnit = cp.failedUnit;
329
- this.consecutiveFailures += 1;
330
- this.lastFailureError = cp.error || "未知测试失败";
331
- this.failureHistory.push({
332
- unit: cp.failedUnit,
333
- error: cp.error,
334
- timestamp: new Date().toISOString(),
335
- });
336
- if (this.consecutiveFailures >= 3) {
337
- await this.pause(
338
- `硬门禁触发:${cp.failedUnit} 连续排查修复未果 (${this.lastFailureError}),触发 Stop-The-Line 遇错即停保护。`,
339
- );
340
- ctx.ui?.notify?.(
341
- `🛑 03-work 遇到连续失败,已启动遇错即停硬门禁保护,请排查原因。`,
342
- "error",
343
- );
344
- return;
345
- }
346
- } else {
347
- this.consecutiveFailures = 0;
348
- this.failedUnit = null;
349
- this.lastFailureError = null;
350
- }
439
+ // 检查遇错即停门禁
440
+ if (this.consecutiveFailures >= 3) {
441
+ await this.pause(
442
+ `硬门禁触发:${this.failedUnit || "当前单元"} 连续排查修复未果 (${this.lastFailureError}),触发 Stop-The-Line 遇错即停保护。`,
443
+ );
444
+ ctx.ui?.notify?.(
445
+ `🛑 03-work 遇到连续失败,已启动遇错即停硬门禁保护,请排查原因。`,
446
+ "error",
447
+ );
448
+ return;
351
449
  }
352
450
 
353
- // 3. Check if all units are finished
451
+ // 3. 检查是否全部单元均已达成
354
452
  const status = this.getStatus();
355
453
  if (status.remainingUnits.length === 0) {
356
454
  this.isActive = false;
455
+ this.cancelTimer();
357
456
  ctx.ui?.setStatus?.("workflow", undefined);
358
457
  ctx.ui?.notify?.(
359
458
  `🎉 03-work 全部 ${this.allUnits.length} 个规划单元已全部实现并验证通过!自动流转至 04-review 审查阶段。`,
360
459
  "info",
361
460
  );
362
461
 
363
- // Automatically transition to 04-review
364
462
  try {
365
463
  pi.sendUserMessage(
366
464
  "【03-work 全部单元已圆满完成】所有 Implementation Units 测试与验收命令均已绿灯通过。现在自动启动 /skill:04-review 进行最终质量审查与回归核验。",
@@ -372,7 +470,7 @@ export class WorkLoopDriver {
372
470
  return;
373
471
  }
374
472
 
375
- // 4. Safety runaway cap
473
+ // 4. 防失控超轮次保护
376
474
  this.currentRunCount += 1;
377
475
  if (this.currentRunCount > this.maxRuns) {
378
476
  await this.pause(`自主续跑轮次已达安全上限 (${this.maxRuns} 轮),避免无限死循环。`);
@@ -383,37 +481,137 @@ export class WorkLoopDriver {
383
481
  return;
384
482
  }
385
483
 
386
- // 5. Update footer status
484
+ // 5. 更新底部状态栏
387
485
  const percent = Math.round((status.completedUnits.length / status.allUnits.length) * 100);
388
486
  ctx.ui?.setStatus?.(
389
487
  "workflow",
390
488
  `🔄 03-work 自主干活 [${status.completedUnits.length}/${status.allUnits.length} - ${percent}%] 下一个: ${status.currentUnit}`,
391
489
  );
392
490
 
393
- // 6. Schedule next autonomous turn
491
+ // 6. 派发下一单元指令
394
492
  const nextPrompt = this.generatePromptForNextUnit();
395
493
  if (!nextPrompt) return;
396
494
 
397
495
  this.cancelTimer();
398
- // Give a brief 100ms pause so Pi's event loop settles cleanly, then dispatch follow-up
399
- this.timer = setTimeout(() => {
400
- try {
401
- this.timer = null;
496
+ // 在 agent_end 中调用 sendUserMessage({ deliverAs: "followUp" }),
497
+ // Pi 底层将把消息放入队列并在 _handlePostAgentRun 中直接循环 continue(),无缝自主续跑!
498
+ try {
499
+ pi.sendUserMessage(nextPrompt, {
500
+ deliverAs: "followUp",
501
+ expandPromptTemplates: true,
502
+ });
503
+ this.continuationDispatched = true;
504
+ } catch {
505
+ this.continuationDispatched = false;
506
+ // 若环境不支持在 agent_end 立即派发,使用延时兜底
507
+ this.timer = setTimeout(() => {
402
508
  if (!this.isActive) return;
403
-
404
509
  pi.sendUserMessage(nextPrompt, {
405
510
  deliverAs: "followUp",
406
511
  expandPromptTemplates: true,
407
512
  });
408
- } catch (err) {
409
- console.error("[03-work driver] setTimeout callback failed:", err);
410
- this.isActive = false;
411
- this.cancelTimer();
513
+ }, 100);
514
+ this.timer.unref?.();
515
+ }
516
+ }
517
+
518
+ /**
519
+ * 在 agent_settled 时提供兜底保障,确保如果 agent_end 未能成功排队时继续调度
520
+ */
521
+ async onAgentSettled(ctx: ExtensionContext, pi: ExtensionAPI): Promise<void> {
522
+ if (!this.isActive || !this.planPath) return;
523
+
524
+ // 1. 检查中断信号 (Esc / ctx.signal / 会话内最后一条 assistant aborted)
525
+ if (ctx.signal?.aborted) {
526
+ await this.pause("检测到用户中断信号 (Esc/Abort)");
527
+ ctx.ui?.setStatus?.("workflow", undefined);
528
+ return;
529
+ }
530
+
531
+ try {
532
+ const branch = ctx.sessionManager?.getBranch?.() ?? [];
533
+ const lastMsgEntry = branch
534
+ .slice()
535
+ .reverse()
536
+ .find((e: any) => e?.type === "message" && e?.message?.role === "assistant");
537
+ const lastAssistantMsg = (lastMsgEntry as any)?.message;
538
+ if (lastAssistantMsg?.stopReason === "aborted") {
539
+ await this.pause("检测到会话中断 (stopReason: aborted)");
412
540
  ctx.ui?.setStatus?.("workflow", undefined);
413
- ctx.ui?.notify?.("⚠️ 03-work 自主循环遇到内部错误已暂停,请查看日志。", "warning");
414
- this.lastMessage = `续跑调度异常: ${err instanceof Error ? err.message : String(err)}`;
541
+ ctx.ui?.notify?.("⏸️ 03-work 自主循环已响应中断信号安全暂停。", "info");
542
+ return;
415
543
  }
416
- }, 100);
417
- this.timer.unref?.();
544
+ } catch {}
545
+
546
+ // 2. 同步进度
547
+ await this.syncProgress();
548
+
549
+ // 3. 检查遇错即停门禁
550
+ if (this.consecutiveFailures >= 3) {
551
+ await this.pause(
552
+ `硬门禁触发:${this.failedUnit || "当前单元"} 连续排查修复未果 (${this.lastFailureError}),触发 Stop-The-Line 遇错即停保护。`,
553
+ );
554
+ ctx.ui?.notify?.(
555
+ `🛑 03-work 遇到连续失败,已启动遇错即停硬门禁保护,请排查原因。`,
556
+ "error",
557
+ );
558
+ return;
559
+ }
560
+
561
+ // 4. 检查是否全部单元完成
562
+ const status = this.getStatus();
563
+ if (status.remainingUnits.length === 0) {
564
+ this.isActive = false;
565
+ this.cancelTimer();
566
+ ctx.ui?.setStatus?.("workflow", undefined);
567
+ ctx.ui?.notify?.(
568
+ `🎉 03-work 全部 ${this.allUnits.length} 个规划单元已全部实现并验证通过!自动流转至 04-review 审查阶段。`,
569
+ "info",
570
+ );
571
+
572
+ try {
573
+ pi.sendUserMessage(
574
+ "【03-work 全部单元已圆满完成】所有 Implementation Units 测试与验收命令均已绿灯通过。现在自动启动 /skill:04-review 进行最终质量审查与回归核验。",
575
+ { deliverAs: "followUp", expandPromptTemplates: true },
576
+ );
577
+ } catch {}
578
+ return;
579
+ }
580
+
581
+ // 5. 超轮次保护
582
+ this.currentRunCount += 1;
583
+ if (this.currentRunCount > this.maxRuns) {
584
+ await this.pause(`自主续跑轮次已达安全上限 (${this.maxRuns} 轮),避免无限死循环。`);
585
+ ctx.ui?.setStatus?.("workflow", undefined);
586
+ ctx.ui?.notify?.("⚠️ 03-work 自主续跑已达安全轮次上限已暂停。", "warning");
587
+ return;
588
+ }
589
+
590
+ // 如果在 agent_end 中已经成功派发了 continuation,则无需在 settled 中重复发送
591
+ if (this.continuationDispatched) {
592
+ this.continuationDispatched = false;
593
+ return;
594
+ }
595
+
596
+ // 6. 如果当前仍处于活跃状态且计时器未启动,补发一次续跑调度 (兜底)
597
+ if (this.isActive && !this.timer) {
598
+ const nextPrompt = this.generatePromptForNextUnit();
599
+ if (!nextPrompt) return;
600
+
601
+ this.timer = setTimeout(() => {
602
+ try {
603
+ this.timer = null;
604
+ if (!this.isActive) return;
605
+
606
+ pi.sendUserMessage(nextPrompt, {
607
+ deliverAs: "followUp",
608
+ expandPromptTemplates: true,
609
+ });
610
+ } catch (err) {
611
+ console.error("[03-work driver] onAgentSettled fallback failed:", err);
612
+ }
613
+ }, 100);
614
+ this.timer.unref?.();
615
+ }
418
616
  }
419
617
  }
@@ -70,7 +70,12 @@ const { default: workflowExtension } = await import(
70
70
  pathToFileURL(path.join(tempBuildDir, "index.js"))
71
71
  );
72
72
 
73
- const { parsePlanUnits, WorkLoopDriver, isExplicit03WorkTrigger } = await import(
73
+ const {
74
+ parsePlanUnits,
75
+ WorkLoopDriver,
76
+ isExplicit03WorkTrigger,
77
+ normalizeUnitId,
78
+ } = await import(
74
79
  pathToFileURL(path.join(tempBuildDir, "src/driver/work-loop-driver.js"))
75
80
  );
76
81
 
@@ -324,8 +329,25 @@ Done.
324
329
  assert.equal(units[0].title, "Unit 0 — 探针:面板焦点 / 键盘 / IME (阻塞所有 UI 工作)");
325
330
  assert.equal(units[1].id, "Unit 1");
326
331
  assert.equal(units[2].id, "Unit 2");
332
+ assert.equal(units[2].checked, false);
327
333
  assert.equal(units[3].id, "Unit 3");
334
+ assert.equal(units[3].checked, true);
328
335
  assert.equal(units[4].id, "Unit 4");
336
+
337
+ // Test Chinese step headers and list items
338
+ const chinesePlanMd = `
339
+ # 中文任务规划
340
+ ### 步骤 1: 基础设施配置
341
+ - [x] 步骤 2: 核心协议实现
342
+ - [ ] 步骤 3: 全量单元测试与回归
343
+ `;
344
+ const zhUnits = parsePlanUnits(chinesePlanMd);
345
+ assert.equal(zhUnits.length, 3);
346
+ assert.equal(zhUnits[0].id, "步骤 1");
347
+ assert.equal(zhUnits[1].id, "步骤 2");
348
+ assert.equal(zhUnits[1].checked, true);
349
+ assert.equal(zhUnits[2].id, "步骤 3");
350
+ assert.equal(zhUnits[2].checked, false);
329
351
  });
330
352
 
331
353
  test("WorkLoopDriver: autonomous lifecycle and prompt generation", async () => {
@@ -470,21 +492,27 @@ test("WorkLoopDriver: records failure history and triggers Stop-The-Line valve o
470
492
  driver.cancelTimer();
471
493
  });
472
494
 
473
- test("isExplicit03WorkTrigger: accurately identifies 03-work invocations vs harmless inputs", async () => {
474
- // Should match explicit 03 triggers
495
+ test("isExplicit03WorkTrigger: accurately identifies canonical 03-work invocations vs harmless inputs", async () => {
496
+ // Should match canonical /skill:03-work, Pi's expanded XML, strict natural language, and engine follow-up
475
497
  assert.equal(isExplicit03WorkTrigger("/skill:03-work"), true);
498
+ assert.equal(isExplicit03WorkTrigger('<skill name="03-work" location="extensions/workflow/skills/03-work/SKILL.md">'), true);
476
499
  assert.equal(isExplicit03WorkTrigger("开始干活"), true);
477
500
  assert.equal(isExplicit03WorkTrigger("开始实现"), true);
501
+ assert.equal(isExplicit03WorkTrigger("开始编码"), true);
502
+ assert.equal(isExplicit03WorkTrigger("开始写代码"), true);
478
503
  assert.equal(isExplicit03WorkTrigger("执行03-work"), true);
479
504
  assert.equal(isExplicit03WorkTrigger("继续干活"), true);
480
505
  assert.equal(isExplicit03WorkTrigger("恢复干活"), true);
481
506
  assert.equal(isExplicit03WorkTrigger("resume work"), true);
482
507
  assert.equal(isExplicit03WorkTrigger("【03-work 自主循环驱动引擎 · 自动化续跑指令】"), true);
483
508
 
484
- // Alias /skill:03 or plain numbers must NEVER trigger (strictly require full skill name /skill:03-work)
509
+ // Plain numbers or informal abbreviations must NEVER trigger (strictly require canonical name)
485
510
  assert.equal(isExplicit03WorkTrigger("/skill:03"), false);
486
511
  assert.equal(isExplicit03WorkTrigger("03"), false);
487
512
  assert.equal(isExplicit03WorkTrigger("/03"), false);
513
+ assert.equal(isExplicit03WorkTrigger("03-work"), false);
514
+ assert.equal(isExplicit03WorkTrigger("/03-work"), false);
515
+ assert.equal(isExplicit03WorkTrigger("/work"), false);
488
516
 
489
517
  // Must NEVER match other skills or normal dialogue (prevents false auto-starts!)
490
518
  assert.equal(isExplicit03WorkTrigger("00"), false);
@@ -504,6 +532,21 @@ test("isExplicit03WorkTrigger: accurately identifies 03-work invocations vs harm
504
532
  assert.equal(isExplicit03WorkTrigger(" "), false);
505
533
  });
506
534
 
535
+ test("normalizeUnitId: correctly normalizes unit representations to standard IDs", async () => {
536
+ const knownUnits = [
537
+ { id: "Unit 0", title: "Unit 0: DB Schema", raw: "### Unit 0: DB Schema" },
538
+ { id: "Unit 1", title: "Unit 1: API Endpoint", raw: "### Unit 1: API Endpoint" },
539
+ ];
540
+
541
+ assert.equal(normalizeUnitId("Unit 0: DB Schema", knownUnits), "Unit 0");
542
+ assert.equal(normalizeUnitId("unit 0", knownUnits), "Unit 0");
543
+ assert.equal(normalizeUnitId("0", knownUnits), "Unit 0");
544
+ assert.equal(normalizeUnitId("1", knownUnits), "Unit 1");
545
+ assert.equal(normalizeUnitId("### Unit 0 — Alpha"), "Unit 0");
546
+ assert.equal(normalizeUnitId("步骤 2: 业务逻辑"), "步骤 2");
547
+ assert.equal(normalizeUnitId(""), "");
548
+ });
549
+
507
550
  test("workflow-dashboard: derives all 5 steps and renders goal-style status dashboard", async () => {
508
551
  const mockState = {
509
552
  repoRoot: "/test/repo",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unifan/pi-unifan-zh",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Pi Coding Agent 中文扩展全家桶与独立插件集",
5
5
  "type": "module",
6
6
  "main": "extensions/sessions/index.ts",