@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.
- package/extensions/review/agents/bugbot.md +2 -2
- package/extensions/review/agents/claude-md-compliance.md +1 -1
- package/extensions/review/agents/code-comments.md +3 -0
- package/extensions/review/agents/conventions.md +1 -1
- package/extensions/review/agents/gate.md +1 -0
- package/extensions/review/agents/history-context.md +1 -1
- package/extensions/review/agents/lite-review.md +3 -3
- package/extensions/review/agents/perf-review.md +5 -1
- package/extensions/review/agents/security-review.md +4 -0
- package/extensions/review/index.ts +22 -16
- package/extensions/review/package.json +1 -1
- package/extensions/review/src/orchestration.ts +5 -4
- package/extensions/workflow/index.ts +4 -8
- package/extensions/workflow/package.json +1 -1
- package/extensions/workflow/skills/03-work/SKILL.md +18 -13
- package/extensions/workflow/skills/04-review/SKILL.md +9 -0
- package/extensions/workflow/src/driver/trigger-matcher.ts +11 -6
- package/extensions/workflow/src/driver/work-loop-driver.ts +305 -107
- package/extensions/workflow/tests/workflow.test.mjs +47 -4
- package/package.json +1 -1
|
@@ -11,9 +11,9 @@ inheritSkills: false
|
|
|
11
11
|
|
|
12
12
|
你是 Bugbot(逻辑 Bug 审查专家)。你的任务是找出**本次修改引入或改动的代码中的真实逻辑缺陷**。
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## 执行计划(结合完整代码上下文,严禁断章取义)
|
|
15
15
|
1. 读取任务中指定的 diff 内容。如果是纯文档变更,输出 `已跳过: 仅文档变更` 且不提出问题。
|
|
16
|
-
2.
|
|
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.
|
|
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.
|
|
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`
|
|
134
|
+
"【极速体检模式】请首先运行 `git diff` 锁定代码变动范围。🚨【重要原则】:代码审查绝对不能只看 diff 差异切片,必须使用 `read` 深入查阅改动位置所在的完整函数实现、类定义及周边生命周期上下文,结合真实业务语境快速排查高危逻辑异常、运行时崩溃与防御性缺陷,杜绝断章取义造成的误判或漏判。注意:项目中内部 GM/Debug 调试工具在确认与生产环境隔离的前提下免检,切勿误判;但若正式生产包可直接触达仍属漏洞。结合完整上下文精准核验后直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
|
|
135
135
|
|
|
136
136
|
const FULL_REVIEW_PROMPT =
|
|
137
|
-
"
|
|
137
|
+
"【全量深度审查模式】请对当前代码改动展开深度的上下文关联审查:🚨【最高核心原则】:严禁仅看 diff 差异碎片断章取义!审查必须先运行 `git diff` 全量锁定改动范围,再针对每一个核心改动点,必须使用 `read` / `grep` 深入查阅改动方法所在的完整类定义、调用方契约、状态机与生命周期等全局上下文,深入排查业务逻辑隐患、边界异常、并发安全、性能GC开销与架构契约。注意:项目中内部 GM 调试面板与测试辅助逻辑在已隔离于正式生产包的前提下免检,切勿误判。结合完整代码脉络与上下文直接输出详尽审查清单。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
|
|
138
138
|
|
|
139
139
|
const UNCOMMITTED_PROMPT =
|
|
140
|
-
"
|
|
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}
|
|
143
|
+
"请审查当前分支相对于基准分支 '{baseBranch}' 的改动(合并基准 commit: {mergeBaseSha})。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!运行 `git diff {mergeBaseSha}` 查看改动后,针对修改点必须使用 `read` / `grep` 结合所在完整函数、类定义与周边调用上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
|
|
144
144
|
|
|
145
145
|
const BASE_BRANCH_PROMPT_FALLBACK =
|
|
146
|
-
"请审查当前分支相对于基准分支 '{branch}'
|
|
146
|
+
"请审查当前分支相对于基准分支 '{branch}' 的改动。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!先使用 `git merge-base` 与 `git diff` 查看改动,再使用 `read` / `grep` 查阅改动点所在的完整函数与周边上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。";
|
|
147
147
|
|
|
148
148
|
const COMMIT_PROMPT_WITH_TITLE =
|
|
149
|
-
'请审查提交 commit {sha} ("{title}")
|
|
149
|
+
'请审查提交 commit {sha} ("{title}") 引入的代码改动。🚨【重要原则】:代码审查不能只看 diff 差异部分,必须结合完整代码上下文一起看!运行 `git show {sha}` 查看差异后,针对修改点必须使用 `read` / `grep` 查阅所在完整函数与周边调用上下文深入分析,直接输出审查发现。严禁输出任何步骤清单。所有输出必须使用纯正中文。';
|
|
150
150
|
|
|
151
151
|
const COMMIT_PROMPT =
|
|
152
|
-
"请审查提交 commit {sha}
|
|
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}
|
|
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}\'
|
|
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.
|
|
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.
|
|
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
|
### 综合裁决
|
|
@@ -78,13 +78,14 @@ export function buildSubagentOrchestrationPrompt(
|
|
|
78
78
|
${listText}
|
|
79
79
|
|
|
80
80
|
### 协作审查执行规范:
|
|
81
|
-
1.
|
|
81
|
+
1. **上下文审查铁律**:所有专家在审查时,**绝对严禁仅看 diff 差异片段断章取义**!必须结合改动位置所在的完整函数实现、类定义、状态机与调用方上下文深入分析,确认在真实全局代码执行流中确实存在问题后才提出。
|
|
82
|
+
2. **并发调用子代理**:先调用 \`subagent({ action: "list", capabilities: true })\`,确认上述专家均可执行且未禁用;再通过下方**一个异步工作流**并行启动全部专家。每个专家必须使用示例中的 \`model\` 参数,继承本次会话模型与思考档位(${thinkingLevel}),不得省略或改用代理默认档位。运行失败时报告具体原因并停止,不得静默更换模型或执行方式:
|
|
82
83
|
${fence}js
|
|
83
84
|
${callsExample}
|
|
84
85
|
${fence}
|
|
85
|
-
|
|
86
|
-
-
|
|
86
|
+
3. **主审裁判长汇总整理**:当所有专家子代理执行完毕返回发现后,请你作为主审裁判长${gateEnabled ? "(门禁裁决)" : ""}:
|
|
87
|
+
- 全面综合各专家的审查意见,重点甄别并剔除脱离上下文仅看 diff 产生的假警报,对相同问题进行去重,剔除误报和低置信度内容(注意:项目中合法的内部 GM / Debug 调试工具在确保与正式生产环境隔离的前提下免检)。
|
|
87
88
|
- 严格按照《核心代码审查准则》的 **[P0~P3]** 等级标准排布审查清单。
|
|
88
89
|
- 给出最终综合裁决与一句话中文总评。
|
|
89
|
-
|
|
90
|
+
4. **语言强制要求**:所有任务入参、思考分析过程、综合汇报与最终报告必须 100% 为纯正中文,严禁出现任何英文段落或未翻译小标题!`;
|
|
90
91
|
}
|
|
@@ -425,21 +425,17 @@ export default function workflowExtension(pi: ExtensionAPI) {
|
|
|
425
425
|
}
|
|
426
426
|
});
|
|
427
427
|
|
|
428
|
-
// 监听整段运行结束 (agent_end)
|
|
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
|
-
|
|
432
|
-
|
|
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);
|
|
@@ -5,41 +5,46 @@ description: "自主循环干活与严格 TDD 编码执行。按 Plan 中的 Imp
|
|
|
5
5
|
|
|
6
6
|
# 03-work: 自主循环干活与严格 TDD 编码执行
|
|
7
7
|
|
|
8
|
-
当计划制定完毕后(已有 `docs/plans/`),执行本技能(`/skill:03-work
|
|
8
|
+
当计划制定完毕后(已有 `docs/plans/`),执行本技能(`/skill:03-work`)直接进入自主编码推进阶段。
|
|
9
9
|
|
|
10
|
-
> ⚡
|
|
11
|
-
>
|
|
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
|
|
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.
|
|
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
|
-
|
|
38
|
+
3. **断点自动同步 (Session Checkpoint & Markdown Checkboxes)**:
|
|
35
39
|
- 每完成一个 Unit 并验证通过后,**必须立即调用**:
|
|
36
40
|
`session_checkpoint({ operation: "save", planPath: "...", completedUnits: ["Unit X"] })`
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
或在计划 Markdown 对应条目勾选 `[x]`。
|
|
42
|
+
- 记录断点后不要停顿,立即开始下一单元。
|
|
43
|
+
4. **Stop-the-line (遇错即停硬门禁)**:
|
|
39
44
|
- 当遇到非预期错误时,优先排查根因自愈。
|
|
40
45
|
- **3次失败熔断阀门**:同一单元若连续 3 次排查修复仍失败,底层引擎强制启动 Stop-The-Line 保护,立即暂停自主循环并如实向用户报告现场,严禁盲目乱试或绕过未通过的测试。
|
|
41
|
-
|
|
42
|
-
- 当所有 Implementation Units
|
|
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.
|
|
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
|
-
//
|
|
20
|
+
// 3. 严格意图的自然语言指令
|
|
16
21
|
const strictPatterns = [
|
|
17
|
-
/^开始(
|
|
22
|
+
/^开始(?:干活|编码|实现|写代码)/,
|
|
18
23
|
/^执行03-work/,
|
|
19
|
-
/^自主(
|
|
20
|
-
/^(
|
|
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
|
-
//
|
|
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
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* - `### Unit 0 — ...`
|
|
81
|
+
* 从计划 Markdown 中解析执行单元 (Implementation Units / 步骤 / 任务)
|
|
82
|
+
* 支持格式:
|
|
83
|
+
* - `### Unit 0 — ...` / `### Unit 0: ...` / `## Unit 0`
|
|
40
84
|
* - `### Implementation Unit 0: ...`
|
|
41
|
-
* -
|
|
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
|
-
//
|
|
99
|
+
// 1. 标题匹配: ### Unit 0 — ... 或 ### 步骤 1: ... 或 ### 任务 1: ...
|
|
52
100
|
const headerMatch = trimmed.match(
|
|
53
|
-
/^#{2,4}\s+(?:Implementation\s+Unit|Unit)\s
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
111
|
+
const checked = /\[[xX]\]|~~/.test(trimmed);
|
|
112
|
+
units.push({ id, title, raw: trimmed, checked });
|
|
61
113
|
continue;
|
|
62
114
|
}
|
|
63
115
|
}
|
|
64
116
|
|
|
65
|
-
//
|
|
117
|
+
// 2. 复选框/列表匹配: - [ ] Unit 0: ... 或 - [x] 步骤 1: ... 或 - [x] 1. ...
|
|
66
118
|
const listMatch = trimmed.match(
|
|
67
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
*
|
|
387
|
+
* 在单轮/整段结束 (agent_end) 时主动评估,无缝跨回合排队注入下一单元 (Native Continuous Loop)
|
|
283
388
|
*/
|
|
284
|
-
async
|
|
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.
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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((
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
this.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
491
|
+
// 6. 派发下一单元指令
|
|
394
492
|
const nextPrompt = this.generatePromptForNextUnit();
|
|
395
493
|
if (!nextPrompt) return;
|
|
396
494
|
|
|
397
495
|
this.cancelTimer();
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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?.("
|
|
414
|
-
|
|
541
|
+
ctx.ui?.notify?.("⏸️ 03-work 自主循环已响应中断信号安全暂停。", "info");
|
|
542
|
+
return;
|
|
415
543
|
}
|
|
416
|
-
}
|
|
417
|
-
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
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",
|