@ghyper9023/pi-dev-workflow 0.4.1 → 0.4.2
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/.pi-dev-output/pi-grill/answers/answer-mpfe77f1-20260521-1913.md +58 -0
- package/.pi-dev-output/pi-grill/answers/answer-mpfh37wu-20260521-2034.md +13 -0
- package/.pi-dev-output/pi-grill/answers/answer-mpfi5q4c-20260521-2104.md +13 -0
- package/.pi-dev-output/pi-grill/answers/answer-mpfizccb-20260521-2127.md +13 -0
- package/.pi-dev-output/pi-grill/answers/answer-mpfjk78k-20260521-2143.md +13 -0
- package/.pi-dev-output/pi-grill/questions/questions-mpfdz1tz-20260521-1907.json +94 -0
- package/.pi-dev-output/pi-plans/20260521-113000-fix-loopcount-timeout.md +215 -0
- package/.pi-dev-output/pi-plans/20260521-1730-grill-input-wrap-back-fix.md +240 -0
- package/.pi-dev-output/pi-plans/20260521-230000-fix-timeout-display-loopcount-gitdiff.md +253 -0
- package/.pi-dev-output/pi-plans/20260521-230500-esc-double-press-confirm-workflow.md +137 -0
- package/.pi-dev-output/pi-plans/20260521-235000-fix-gitdiff-loopcount.md +258 -0
- package/.pi-dev-output/pi-review/html/20260521-2305-review-workflow-index.html +196 -0
- package/.pi-dev-output/pi-review/md/review-20260520-100000.md +91 -0
- package/.pi-dev-output/pi-review/md/review-20260521-140000.md +191 -0
- package/.pi-dev-output/pi-review/md/review-20260521-190000.md +189 -0
- package/.pi-dev-output/pi-review/md/review-20260521-204500.md +241 -0
- package/.pi-dev-output/pi-review/md/review-20260521-214500.md +270 -0
- package/.pi-dev-output/pi-review/md/review-20260521-215158.md +214 -0
- package/.pi-dev-output/pi-review/md/review-20260521-234500.md +201 -0
- package/.pi-dev-output/pi-review/md/review-20260521-235500.md +422 -0
- package/.pi-dev-output/pi-review/md/review-20260522-000000.md +212 -0
- package/.pi-dev-output/pi-review/md/review-20260522-003000.md +377 -0
- package/.pi-dev-output/pi-review/md/review-20260522-003500.md +296 -0
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-113000-fix-loopcount-timeout.json +402 -0
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-1730-grill-input-wrap-back-fix.json +447 -0
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-230000-fix-timeout-display-loopcount-gitdiff.json +708 -0
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-230500-esc-double-press-confirm-workflow.json +365 -0
- package/.pi-dev-output/pi-workflow/checkpoint-20260521-235000-fix-gitdiff-loopcount.json +395 -0
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfhyxc5.json +30 -0
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi2unc.json +49 -0
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi382e.json +59 -0
- package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi5r22.json +76 -0
- package/extensions/dev-prompts.ts +16 -8
- package/extensions/grill-me-agent.ts +23 -7
- package/extensions/ui-helpers.ts +59 -7
- package/extensions/workflow-engine.ts +80 -32
- package/package.json +1 -1
- package/tests/test-loopcount-timeout-fix.mjs +336 -0
- package/themes/oh-my-pi-titanium.json +90 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# 修复 git diff 解析与循环计数 Bug — 实施计划
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
修复两个 Bug:
|
|
6
|
+
1. **git diff 解析**:`f98799d` commit 中将 `getGitDiffChanges()` 的 git diff 解析从正则改为简单的 `split("\t")`,导致无法正确处理非 tab 分隔的输出(如 space-padded 格式),且 agent 输出文本被错误解析为文件路径(如 `checkpoint-${planId}.json`、`[],\t\t\toutputs:`)。
|
|
7
|
+
2. **循环计数偏移**:`executeLoopGroup` 中 `loopCount++` 在 reviewer 完成后才执行,导致第二次循环开始时 widget 仍显示旧的 loopCount,造成 "第 1 次循环" 重复出现。
|
|
8
|
+
|
|
9
|
+
## 文件清单
|
|
10
|
+
|
|
11
|
+
### 修改文件
|
|
12
|
+
| 文件路径 | 改动描述 | 风险等级 |
|
|
13
|
+
|---------|---------|---------|
|
|
14
|
+
| `extensions/workflow-engine.ts` | 修复 `getGitDiffChanges` 解析逻辑;修复 `loopCount` 更新时机 | 中 |
|
|
15
|
+
| `extensions/ui-helpers.ts` | 修复 `buildWidgetLines` 中循环计数的 fallback 逻辑 | 低 |
|
|
16
|
+
|
|
17
|
+
## 实施步骤
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### 步骤 1:修复 `getGitDiffChanges` 中 git diff 输出的解析
|
|
22
|
+
|
|
23
|
+
- **前置条件**:无
|
|
24
|
+
- **改动文件**:`extensions/workflow-engine.ts`(函数 `getGitDiffChanges`)
|
|
25
|
+
- **改动内容**:
|
|
26
|
+
|
|
27
|
+
**问题**:`f98799d` 将原来健壮的正则解析 `/^([MAD])\s+(.+)$/` 改为简单的 `split("\t")`。Git 的 `--name-status` 输出格式在不同环境/版本中可能使用 space-padded 格式(如 `"M path/to/file"`),此时 `split("\t")` 只能得到 `parts.length === 1`,导致解析失败。结果文件变更不会被检测到。
|
|
28
|
+
|
|
29
|
+
**修复方案**:将解析改回使用正则 `^([MAD])\s+(.+)$`,同时保留 tab split 作为后备(兼容两种格式):
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// 原来的正则方式(健壮):
|
|
33
|
+
// git diff --name-status output format: X\tfilepath or "X filepath"
|
|
34
|
+
const statusMatch = trimmed.match(/^([MAD])\s+(.+)$/);
|
|
35
|
+
if (statusMatch) {
|
|
36
|
+
const status = statusMatch[1]!.trim();
|
|
37
|
+
const filePath = statusMatch[2]!.trim();
|
|
38
|
+
if (filePath && !seen.has(filePath) && (status === "M" || status === "A" || status === "D")) {
|
|
39
|
+
seen.add(filePath);
|
|
40
|
+
changes.push({ status: status as "M" | "A" | "D", path: filePath });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// 后备:tab split(兼容部分 git 版本输出的 tab 格式)
|
|
44
|
+
else if (trimmed.includes("\t")) {
|
|
45
|
+
const parts = trimmed.split("\t");
|
|
46
|
+
if (parts.length === 2) {
|
|
47
|
+
const status = parts[0]!.trim();
|
|
48
|
+
const filePath = parts[1]!.trim();
|
|
49
|
+
if (filePath && !seen.has(filePath) && (status === "M" || status === "A" || status === "D")) {
|
|
50
|
+
seen.add(filePath);
|
|
51
|
+
changes.push({ status: status as "M" | "A" | "D", path: filePath });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
同时修复 `git status --porcelain` 部分:当前代码用 `trimmed.slice(0, 2)` 和 `trimmed.slice(3)`,但 `--porcelain` 格式是固定的 2 字符状态码 + 1空格 + path。需要更健壮的处理:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// 原来:statusPrefix = trimmed.slice(0, 2); filePath = trimmed.slice(3).trim();
|
|
61
|
+
// 改为正则(更健壮):
|
|
62
|
+
const statusMatch2 = trimmed.match(/^(..)\s+(.+)$/);
|
|
63
|
+
if (statusMatch2) {
|
|
64
|
+
const statusPrefix = statusMatch2[1]!.trim();
|
|
65
|
+
const filePath = statusMatch2[2]!.trim();
|
|
66
|
+
if (filePath && !seen.has(filePath) && (statusPrefix === "??" || statusPrefix === "A " || statusPrefix.startsWith("A"))) {
|
|
67
|
+
seen.add(filePath);
|
|
68
|
+
changes.push({ status: "A", path: filePath });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **验证方式**:手动执行 `git diff --name-status` 验证输出格式,确认正则能够正确解析。
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### 步骤 2:修复 agent 输出文本刮取逻辑中的脏数据
|
|
78
|
+
|
|
79
|
+
- **前置条件**:无
|
|
80
|
+
- **改动文件**:`extensions/workflow-engine.ts`(函数 `runAgentWithProgress` 中的文本刮取部分)
|
|
81
|
+
- **改动内容**:
|
|
82
|
+
|
|
83
|
+
**问题**:五个 `filePatterns` 正则过于宽松,会从 agent 的自然语言输出中误匹配脏数据。具体来说:
|
|
84
|
+
|
|
85
|
+
1. Pattern `/(?:^|\n)\s*(?:edit|new|delete|read|modify|create|update|add|remove)\s*[::]\s*([^\n]+\.[a-zA-Z0-9_]+)/gim` 可以匹配 agent 文本中类似:
|
|
86
|
+
- "M [],\n\t\t\t\toutputs:"(遇到 "M" 不匹配,但 "remove"或其他匹配?不,这个 pattern 需要前面的动词)
|
|
87
|
+
- 实际上,agent 的输出中可能有类似这样的文本:
|
|
88
|
+
```
|
|
89
|
+
modify: [],\n\t\t\t\toutputs: ...
|
|
90
|
+
```
|
|
91
|
+
或进度消息中的其他文本片段。
|
|
92
|
+
|
|
93
|
+
2. 标记代码块的 pattern `` /`([^`]+\.[a-zA-Z0-9_]+)`/g `` 可能匹配到 `` `checkpoint-xxx.json` `` 或 `` `checkpoint-${planId}.json` `` 这样的模板字符串。
|
|
94
|
+
|
|
95
|
+
**修复方案**:在 `filePatterns` 的每个匹配结果后添加更严格的过滤器,排除明显不是文件路径的字符串:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// 在 filePath 验证后添加额外过滤
|
|
99
|
+
// 过滤器:排除包含不合法路径字符或模板表达式的字符串
|
|
100
|
+
if (filePath.includes("${") || filePath.includes("\\n") || filePath.includes("\\t")) continue; // 排除模板字符串和转义字符
|
|
101
|
+
if (filePath.includes("[]") || filePath.includes("{}")) continue; // 排除数组/对象字面量
|
|
102
|
+
if (filePath.match(/^[\s,;)\]}]+$/)) continue; // 排除纯符号
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
关键修改位置:`runAgentWithProgress` 函数中,`const filePath = m[1]!.trim()` 之后的验证逻辑块。
|
|
106
|
+
|
|
107
|
+
- **验证方式**:用包含 `checkpoint-\${planId}.json` 和 `[],\n\t\t\t\toutputs:` 等脏数据的测试文本运行逻辑,确认不会产生误匹配。
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### 步骤 3:修复循环计数偏移
|
|
112
|
+
|
|
113
|
+
- **前置条件**:步骤 1 和 2 完成
|
|
114
|
+
- **改动文件**:`extensions/workflow-engine.ts`(函数 `executeLoopGroup`)和 `extensions/ui-helpers.ts`(函数 `buildWidgetLines`)
|
|
115
|
+
- **改动内容**:
|
|
116
|
+
|
|
117
|
+
**根本原因**:`executeLoopGroup` 中的循环计数更新顺序有误。当前的顺序是:
|
|
118
|
+
|
|
119
|
+
1. 进入 while 循环(此时 `loopCount` 还未递增)
|
|
120
|
+
2. 重置 sub-step 为 pending
|
|
121
|
+
3. 执行 worker agent
|
|
122
|
+
4. 执行 reviewer agent
|
|
123
|
+
5. `loopCount++` 并 `state.loopCount = loopCount`
|
|
124
|
+
6. 检查是否需要继续循环
|
|
125
|
+
|
|
126
|
+
当 reviewer 发现 critical 问题需要再次循环时,`loopCount` 已经在步骤 5 增加为 1,所以在第二次循环开始时 widget 显示的是 "第 1 次循环"(因为 `state.loopCount = 1`),但实际上用户期望看到的是 "第 2 次循环"(即将开始第 2 轮)。
|
|
127
|
+
|
|
128
|
+
更准确地说,期望的显示行为是:
|
|
129
|
+
- Pending 时:`第 0 次循环`(表示尚未开始)
|
|
130
|
+
- 第 1 次循环执行中:`第 1 次循环`
|
|
131
|
+
- 第 1 次循环完成,需要第 2 次循环,第二次循环执行中:`第 2 次循环`
|
|
132
|
+
- ...
|
|
133
|
+
|
|
134
|
+
**修复方案 A(推荐,最小改动)**:在 while 循环**开始处**(进入新的一轮循环之前)更新 loopCount。
|
|
135
|
+
|
|
136
|
+
将 `loopCount++` 和 `state.loopCount = loopCount` 从 reviewer 完成之后**移到 while 循环最开头**。这样:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
while (loopCount < maxLoops) {
|
|
140
|
+
loopCount++; // 递增计数,表示"即将开始第 N 次循环"
|
|
141
|
+
state.loopCount = loopCount;
|
|
142
|
+
|
|
143
|
+
// 立即更新 UI
|
|
144
|
+
updateWidgetStep(stepIndex, step.label, "running", {
|
|
145
|
+
loopCount,
|
|
146
|
+
maxLoops: step.maxLoops,
|
|
147
|
+
startedAt: _widgetSteps[stepIndex]?.startedAt || Date.now(),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// 重置 sub-step 状态
|
|
151
|
+
setWidgetSubStepStatus(stepIndex, step.loopAgentName!, "pending");
|
|
152
|
+
setWidgetSubStepStatus(stepIndex, step.reviewAgentName!, "pending");
|
|
153
|
+
// ... 后续逻辑 ...
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
同时需要移除原来 reviewer 完成后的 `loopCount++` 和 `state.loopCount = loopCount` 部分(在 `if (reviewSummary?.maxSeverity === "critical" ...)` 判断之前)。
|
|
158
|
+
|
|
159
|
+
**注意**:由于 `loopCount` 现在从 1 开始递增(而不是原来的从 0 开始,在 reviewer 完成后才 ++),所以需要同步修改 `buildWidgetLines` 中的 fallback 逻辑:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// 在 ui-helpers.ts 的 buildWidgetLines 中:
|
|
163
|
+
if (s.maxLoops != null) {
|
|
164
|
+
if (isRunning) {
|
|
165
|
+
// 当 loop-group 开始运行时,loopCount 已经通过 executeLoopGroup 在循环开头设置了,
|
|
166
|
+
// 所以不需要 fallback 显示"第 1 次循环"
|
|
167
|
+
// 直接使用 s.loopCount 的值
|
|
168
|
+
if (s.loopCount == null || s.loopCount === 0) {
|
|
169
|
+
// 安全 fallback(理论上不会走到这里)
|
|
170
|
+
loopStr = dim(theme, ` · 第 1 次循环`);
|
|
171
|
+
}
|
|
172
|
+
} else if (isPending) {
|
|
173
|
+
loopStr = dim(theme, ` · 第 0 次循环`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **验证方式**:
|
|
179
|
+
1. 启动工作流,观察 loop-group 的循环计数显示
|
|
180
|
+
2. 验证第 1 次循环显示 `第 1 次循环`
|
|
181
|
+
3. 当 reviewer 触发再次循环时,验证显示 `第 2 次循环` 而不是 `第 1 次循环`
|
|
182
|
+
4. 验证第 3 次循环显示 `第 3 次循环`
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### 步骤 4:同步修改 loadCheckpoint 恢复时的循环计数
|
|
187
|
+
|
|
188
|
+
- **前置条件**:步骤 3 完成
|
|
189
|
+
- **改动文件**:`extensions/workflow-engine.ts`(`runWorkflow` 函数中恢复 checkpoint 的逻辑)
|
|
190
|
+
- **改动内容**:
|
|
191
|
+
|
|
192
|
+
由于 `loopCount` 的语义发生变化(从"已完成次数"变为"当前正在进行的轮次"),需要确保从 checkpoint 恢复时,`loopCount` 能正确恢复。
|
|
193
|
+
|
|
194
|
+
当前 checkpoint 中的 `loopCounts[step.id]` 存储的是已完成次数(即原来的语义)。如果 loopCount 现在从 1 开始,则恢复时需要确保:
|
|
195
|
+
|
|
196
|
+
- 如果 checkpoint 中 `loopCounts[step.id] = 1`(已完成 1 次),恢复后应显示 `第 1 次循环` 但不重新执行已完成的工作。
|
|
197
|
+
|
|
198
|
+
但检查代码逻辑:checkpoint 恢复时会跳过 `status === "done"` 的步骤,所以 `loopCounts` 只对**未完成**的 loop-group 步骤有效。对于未完成的步骤,`loopCounts` 为 0 或上一次退出时的值。
|
|
199
|
+
|
|
200
|
+
**实际上不需要修改**,因为 `loopCounts[step.id]` 只作为 `while` 循环的起始值(`let loopCount = loopCounts[step.id] ?? 0;`)。在步骤 3 中,我们将 `loopCount++` 移到了 while 开头,所以:
|
|
201
|
+
|
|
202
|
+
- 恢复后 loopCount = previous_loopCount(已完成次数)
|
|
203
|
+
- while 开始执行时立即 ++,变成 previous_loopCount + 1(当前轮次)
|
|
204
|
+
|
|
205
|
+
这恰好是正确的行为。
|
|
206
|
+
|
|
207
|
+
- **验证方式**:从 checkpoint 恢复工作流,确认循环计数正确。
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 依赖关系
|
|
212
|
+
|
|
213
|
+
- 步骤 1 和 2 相互独立,可并行实施
|
|
214
|
+
- 步骤 3 独立于步骤 1、2
|
|
215
|
+
- 步骤 4 依赖步骤 3
|
|
216
|
+
|
|
217
|
+
## 测试策略
|
|
218
|
+
|
|
219
|
+
### 单元测试(手动验证)
|
|
220
|
+
|
|
221
|
+
1. **git diff 解析测试**:
|
|
222
|
+
- 用以下格式模拟 git diff 输出:
|
|
223
|
+
- `M\tpath/to/file.ts`(tab 分隔)
|
|
224
|
+
- `M path/to/file.ts`(空格分隔,多空格)
|
|
225
|
+
- `A\tnewfile.ts`
|
|
226
|
+
- `D\tdeletedfile.ts`
|
|
227
|
+
- 验证所有格式都能正确解析
|
|
228
|
+
|
|
229
|
+
2. **文本刮取过滤器测试**:
|
|
230
|
+
- 用以下文本测试 `filePatterns` 匹配:
|
|
231
|
+
- `checkpoint-${planId}.json`
|
|
232
|
+
- `[],\n\t\t\t\toutputs:`
|
|
233
|
+
- `edit: src/main.rs`(应匹配)
|
|
234
|
+
- `I've modified src/main.rs`(应匹配)
|
|
235
|
+
- `try`(不应匹配)
|
|
236
|
+
- 验证过滤器正确排除脏数据
|
|
237
|
+
|
|
238
|
+
3. **循环计数测试**:
|
|
239
|
+
- 模拟 loop-group 执行流程:
|
|
240
|
+
```
|
|
241
|
+
pending → 第 0 次循环
|
|
242
|
+
running 第一次循环 → 第 1 次循环
|
|
243
|
+
running 第二次循环 → 第 2 次循环
|
|
244
|
+
running 第三次循环 → 第 3 次循环
|
|
245
|
+
```
|
|
246
|
+
- 验证显示值正确
|
|
247
|
+
|
|
248
|
+
### 集成测试
|
|
249
|
+
|
|
250
|
+
1. 运行一个实际工作流,观察 UI 显示
|
|
251
|
+
2. 手动触发 reviewer 发现 bug,观察再次循环时的显示
|
|
252
|
+
|
|
253
|
+
## 注意事项
|
|
254
|
+
|
|
255
|
+
1. **最小改动原则**:只修改有 bug 的逻辑,不重构其他部分
|
|
256
|
+
2. **向后兼容**:checkpoint 文件格式不变,`loopCounts` 字段语义变化需要确保从旧 checkpoint 恢复时行为正确
|
|
257
|
+
3. **git diff 解析**:改回正则解析的同时保留 tab split 后备,兼容多种 git 输出格式
|
|
258
|
+
4. **文本刮取**:添加的过滤器不应影响正常文件路径的匹配(如 `src/main.rs`、`extensions/workflow-engine.ts` 等)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Code Review Report</title>
|
|
7
|
+
<style>
|
|
8
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
9
|
+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f5f5f5;color:#333;line-height:1.6;padding:20px}
|
|
10
|
+
.container{max-width:960px;margin:0 auto}
|
|
11
|
+
h1{font-size:24px;margin-bottom:8px;color:#1a1a1a}
|
|
12
|
+
.summary{background:#fff;border-radius:8px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.1)}
|
|
13
|
+
.summary-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px}
|
|
14
|
+
.summary-item{background:#fafafa;padding:12px;border-radius:6px}
|
|
15
|
+
.summary-item .label{font-size:12px;color:#666;text-transform:uppercase}
|
|
16
|
+
.summary-item .value{font-size:20px;font-weight:700;margin-top:4px}
|
|
17
|
+
.bug-count .value{color:#d32f2f}
|
|
18
|
+
.warn-count .value{color:#f57c00}
|
|
19
|
+
.info-count .value{color:#1976d2}
|
|
20
|
+
.score{font-size:36px;font-weight:700;text-align:center;padding:16px;background:#fafafa;border-radius:6px;margin-top:12px}
|
|
21
|
+
.score.good{color:#2e7d32}
|
|
22
|
+
.score.fair{color:#f57c00}
|
|
23
|
+
.commit-msg{background:#e8f5e9;padding:12px 16px;border-radius:6px;margin-top:12px;font-family:"SF Mono","Consolas",monospace;font-size:13px;color:#2e7d32}
|
|
24
|
+
.file-group{background:#fff;border-radius:8px;margin-bottom:12px;box-shadow:0 1px 3px rgba(0,0,0,.1);overflow:hidden}
|
|
25
|
+
.file-header{display:flex;justify-content:space-between;align-items:center;padding:14px 20px;cursor:pointer;transition:background .15s}
|
|
26
|
+
.file-header:hover{background:#f0f0f0}
|
|
27
|
+
.file-header .path{font-family:"SF Mono","Consolas",monospace;font-size:14px;font-weight:600;color:#1a1a1a}
|
|
28
|
+
.file-header .status{display:flex;gap:8px;align-items:center}
|
|
29
|
+
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600}
|
|
30
|
+
.badge.bug{background:#ffebee;color:#c62828}
|
|
31
|
+
.badge.warn{background:#fff3e0;color:#e65100}
|
|
32
|
+
.badge.info{background:#e3f2fd;color:#1565c0}
|
|
33
|
+
.file-body{display:none;padding:0 20px 20px;border-top:1px solid #eee}
|
|
34
|
+
.file-body.open{display:block}
|
|
35
|
+
.issue{margin:12px 0;padding:12px 16px;border-left:4px solid #ccc;background:#fafafa;border-radius:0 6px 6px 0}
|
|
36
|
+
.issue.bug{border-color:#d32f2f}
|
|
37
|
+
.issue.warn{border-color:#f57c00}
|
|
38
|
+
.issue.info{border-color:#1976d2}
|
|
39
|
+
.issue .tag{font-size:11px;font-weight:700;text-transform:uppercase;margin-bottom:4px}
|
|
40
|
+
.issue.bug .tag{color:#d32f2f}
|
|
41
|
+
.issue.warn .tag{color:#f57c00}
|
|
42
|
+
.issue.info .tag{color:#1976d2}
|
|
43
|
+
.issue .desc{font-size:14px;margin:4px 0}
|
|
44
|
+
.issue .code{background:#272822;color:#f8f8f2;padding:8px 12px;border-radius:4px;font-size:13px;font-family:"SF Mono","Consolas",monospace;margin:6px 0;overflow-x:auto;white-space:pre}
|
|
45
|
+
.diff-stats{font-size:12px;color:#666;margin-left:12px}
|
|
46
|
+
.add{color:#2e7d32}
|
|
47
|
+
.del{color:#c62828}
|
|
48
|
+
.toggle-icon{color:#999;font-size:12px}
|
|
49
|
+
</style>
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<div class="container">
|
|
53
|
+
<h1>🔍 Code Review Report</h1>
|
|
54
|
+
|
|
55
|
+
<div class="summary">
|
|
56
|
+
<p><strong>审查范围:</strong>git diff HEAD(4 files changed, +56, -30)</p>
|
|
57
|
+
|
|
58
|
+
<div class="summary-grid">
|
|
59
|
+
<div class="summary-item bug-count"><div class="label">BUG</div><div class="value">2</div></div>
|
|
60
|
+
<div class="summary-item warn-count"><div class="label">敏感信息</div><div class="value">1</div></div>
|
|
61
|
+
<div class="summary-item info-count"><div class="label">可维护性</div><div class="value">1</div></div>
|
|
62
|
+
<div class="summary-item info-count"><div class="label">规范</div><div class="value">1</div></div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="score fair">评分:55/100</div>
|
|
66
|
+
<p style="margin-top:8px;font-size:14px;color:#666;text-align:center">存在 2 个 BUG(运行时错误),1 个敏感信息变更,扣分严重</p>
|
|
67
|
+
|
|
68
|
+
<div class="commit-msg">
|
|
69
|
+
<strong>💡 commit message 建议:</strong><br>
|
|
70
|
+
<code>feat: 增加 reviewTimeoutMs 独立超时配置,修复 workflow sub-step 状态重置函数名错误</code>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<!-- workflow-engine.ts -->
|
|
75
|
+
<div class="file-group">
|
|
76
|
+
<div class="file-header" onclick="this.nextElementSibling.classList.toggle('open')">
|
|
77
|
+
<span>
|
|
78
|
+
<span class="path">extensions/workflow-engine.ts</span>
|
|
79
|
+
<span class="diff-stats"><span class="add">+27</span> <span class="del">-11</span></span>
|
|
80
|
+
</span>
|
|
81
|
+
<span class="status">
|
|
82
|
+
<span class="badge bug">2 BUG</span>
|
|
83
|
+
<span class="badge warn">1 敏感</span>
|
|
84
|
+
<span class="toggle-icon">▶</span>
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="file-body">
|
|
88
|
+
<div class="issue bug">
|
|
89
|
+
<div class="tag">🛑 BUG · 最高优先级</div>
|
|
90
|
+
<div class="desc"><strong>调用已重命名的函数导致 ReferenceError</strong></div>
|
|
91
|
+
<div class="desc">第 1193-1194 行调用 <code>setWidgetSubStepStatus()</code>,但该函数在第 783 行被重命名为 <code>resetWidgetSubStep()</code>(参数从 (status) 变为固定 "pending" 并增加重置 detail/tools/outputs 逻辑)。旧函数名已被删除,运行时将抛出 <code>ReferenceError: setWidgetSubStepStatus is not defined</code>。</div>
|
|
92
|
+
<div class="code">// 第 1193-1194 行(executeLoopGroup)
|
|
93
|
+
setWidgetSubStepStatus(stepIndex, step.loopAgentName!, "pending");
|
|
94
|
+
setWidgetSubStepStatus(stepIndex, step.reviewAgentName!, "pending");</div>
|
|
95
|
+
<div class="desc"><strong>修复:</strong>改为调用 <code>resetWidgetSubStep(stepIndex, step.loopAgentName!)</code> 等。</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="issue bug">
|
|
99
|
+
<div class="tag">🛑 BUG · 最高优先级</div>
|
|
100
|
+
<div class="desc"><strong>runAgentWithProgress 中仍调用已删除的函数</strong></div>
|
|
101
|
+
<div class="desc">第 1114 行仍调用 <code>setWidgetSubStepStatus(stepIndex, agentName, subStatus)</code>,该函数已被删除。执行单步 agent 完成后也会抛出 ReferenceError。</div>
|
|
102
|
+
<div class="code">// 第 1114 行(runAgentWithProgress 末尾)
|
|
103
|
+
setWidgetSubStepStatus(stepIndex, agentName, subStatus);</div>
|
|
104
|
+
<div class="desc"><strong>修复:</strong>将旧函数逻辑恢复,或改写为直接修改 sub-step 状态。</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="issue warn">
|
|
108
|
+
<div class="tag">⚠️ 敏感信息 · 高优先级</div>
|
|
109
|
+
<div class="desc"><strong>runAgentWithProgress 中 sub-step detail 硬编码中文提醒信息</strong></div>
|
|
110
|
+
<div class="desc">新增的 <code>detail: "超时时间${formatTimeout(timeoutMs)}"</code> 是 UI 展示文本,在 engine 层硬编码中文。当工具函数输出被序列化或用于日志管道时,中文文本可能引起编码问题或混乱。建议使用英文 key(如 <code>timeoutText</code> 或分离 data/ui 层)。</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="issue info">
|
|
114
|
+
<div class="tag">🔧 可维护性 · 中优先级</div>
|
|
115
|
+
<div class="desc"><strong>loop-group 未展示 timeoutMs 但 reviewer 仍有独立超时</strong></div>
|
|
116
|
+
<div class="desc"><code>executeWorkflowBackground</code> 中 loop-group 步骤的 <code>updateWidgetStep</code> 调用传入 <code>timeoutMs: undefined</code>,这合理(loop-group 的 timeout 逻辑在内部)。但 reviewTimeoutMs 并未在 widget 中展示,建议在 sub-step detail 中体现 reviewer 的独立超时。</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<!-- dev-prompts.ts -->
|
|
122
|
+
<div class="file-group">
|
|
123
|
+
<div class="file-header" onclick="this.nextElementSibling.classList.toggle('open')">
|
|
124
|
+
<span>
|
|
125
|
+
<span class="path">extensions/dev-prompts.ts</span>
|
|
126
|
+
<span class="diff-stats"><span class="add">+16</span> <span class="del">-8</span></span>
|
|
127
|
+
</span>
|
|
128
|
+
<span class="status">
|
|
129
|
+
<span class="badge info">1 规范</span>
|
|
130
|
+
<span class="toggle-icon">▶</span>
|
|
131
|
+
</span>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="file-body">
|
|
134
|
+
<div class="issue info">
|
|
135
|
+
<div class="tag">📐 规范 · 低优先级</div>
|
|
136
|
+
<div class="desc"><strong>所有 loop-group 配置的高 timeoutMs 应与 reviewTimeoutMs 有明确注释</strong></div>
|
|
137
|
+
<div class="desc">将 <code>timeoutMs</code> 从 900_000(15min)提升到 1_800_000(30min),并新增 <code>reviewTimeoutMs: 900_000</code>。改动意图明确,配置值合理。但所有步骤的 <code>timeoutMs</code> 和 <code>reviewTimeoutMs</code> 高度重复,建议提取为常量以提高可维护性。</div>
|
|
138
|
+
<div class="code">// 建议提取为命名常量
|
|
139
|
+
const LOOP_TIMEOUT_MS = 1_800_000; // 30min
|
|
140
|
+
const REVIEW_TIMEOUT_MS = 900_000; // 15min
|
|
141
|
+
const TRIM_TIMEOUT_MS = 1_200_000; // 20min</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<!-- ui-helpers.ts -->
|
|
147
|
+
<div class="file-group">
|
|
148
|
+
<div class="file-header" onclick="this.nextElementSibling.classList.toggle('open')">
|
|
149
|
+
<span>
|
|
150
|
+
<span class="path">extensions/ui-helpers.ts</span>
|
|
151
|
+
<span class="diff-stats"><span class="add">+1</span> <span class="del">-4</span></span>
|
|
152
|
+
</span>
|
|
153
|
+
<span class="status">
|
|
154
|
+
<span class="badge info">1 可维护性</span>
|
|
155
|
+
<span class="toggle-icon">▶</span>
|
|
156
|
+
</span>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="file-body">
|
|
159
|
+
<div class="issue info">
|
|
160
|
+
<div class="tag">🔧 可维护性 · 中优先级</div>
|
|
161
|
+
<div class="desc"><strong>formatTimeout 改为 export,移除 running 状态下强制显示「第 1 次循环」的特判</strong></div>
|
|
162
|
+
<div class="desc"><code>formatTimeout</code> 被导出供 workflow-engine 使用,改动合理。移除 isRunning 分支的「第 1 次循环」硬编码,改为由 engine 层通过 <code>updateWidgetStep</code> 及时更新 <code>loopCount</code>,设计更清晰。pending 状态仍显示「第 0 次循环」,语义明确。</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- .gitignore -->
|
|
168
|
+
<div class="file-group">
|
|
169
|
+
<div class="file-header" onclick="this.nextElementSibling.classList.toggle('open')">
|
|
170
|
+
<span>
|
|
171
|
+
<span class="path">.gitignore</span>
|
|
172
|
+
<span class="diff-stats"><span class="del">-4</span></span>
|
|
173
|
+
</span>
|
|
174
|
+
<span class="status">
|
|
175
|
+
<span class="badge warn">1 敏感</span>
|
|
176
|
+
<span class="toggle-icon">▶</span>
|
|
177
|
+
</span>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="file-body">
|
|
180
|
+
<div class="issue warn">
|
|
181
|
+
<div class="tag">⚠️ 敏感信息 · 高优先级</div>
|
|
182
|
+
<div class="desc"><strong>移除 pi-review/ 和 pi-dev-output/ 的 gitignore 规则</strong></div>
|
|
183
|
+
<div class="desc">提交移除了 <code>pi-review/</code> 和 <code>pi-dev-output/</code> 的 ignore 规则。这两个目录是 pi 框架的产物输出目录,包含审查报告、构建产物等。如果属于不应提交到版本控制的生成产物,应保留 ignore 规则。请确认这是有意的行为(例如要将产物纳入版本管理),或是误删。</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
</div>
|
|
189
|
+
<script>
|
|
190
|
+
document.querySelectorAll('.file-body.open').forEach(el=>el.classList.remove('open'));
|
|
191
|
+
document.querySelectorAll('.file-header').forEach(h=>{
|
|
192
|
+
h.addEventListener('click',function(){const b=this.querySelector('.toggle-icon');b.textContent=b.textContent==='▶'?'▼':'▶';});
|
|
193
|
+
});
|
|
194
|
+
</script>
|
|
195
|
+
</body>
|
|
196
|
+
</html>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# 代码审查报告 — workflow-engine 双 Bug 修复
|
|
2
|
+
|
|
3
|
+
**审查时间**: 2026-05-20
|
|
4
|
+
**审查范围**: `extensions/workflow-engine.ts` + `tests/test-workflow-engine-bugs.mjs`
|
|
5
|
+
**审查背景**: 修复 executeLoopGroup 缺少 exitCode 检查 (Bug A) 和 setTimeout cleanupWidget 竞态条件 (Bug B)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 严重等级汇总
|
|
10
|
+
|
|
11
|
+
| 等级 | 数量 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| 🔴 critical | 0 |
|
|
14
|
+
| 🟡 medium | 2 |
|
|
15
|
+
| 🟢 low | 2 |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🟡 中等问题
|
|
20
|
+
|
|
21
|
+
### M1. `tests/test-workflow-engine.mjs` 中 deleteCheckpointFile 导出测试失败(pre-existing)
|
|
22
|
+
|
|
23
|
+
**文件**: `tests/test-workflow-engine.mjs` 第 222 行
|
|
24
|
+
**问题**: 测试断言 `source.includes("export function deleteCheckpointFile")` 但 `workflow-engine.ts` 中并没有 `deleteCheckpointFile` 函数。这不是本次修复引入的问题,而是已有测试的回归。
|
|
25
|
+
|
|
26
|
+
**建议**:
|
|
27
|
+
- 要么在 workflow-engine.ts 中添加 `deleteCheckpointFile` 的导出(如果实际需要)
|
|
28
|
+
- 要么从测试中移除这个断言(如果该函数已被移除或从未存在)
|
|
29
|
+
- 由于本次任务是"只修 bug,不做重构",建议作为独立 issue 处理
|
|
30
|
+
|
|
31
|
+
### M2. `executeLoopGroup` 中 retry 后未递归检查 agentResult
|
|
32
|
+
|
|
33
|
+
**文件**: `extensions/workflow-engine.ts` 第 1204 行
|
|
34
|
+
**问题**: 在非 full-auto 模式下,exitCode 检查分支中 retry 逻辑是:
|
|
35
|
+
```typescript
|
|
36
|
+
agentResult = await runAgentWithProgress(loopAgent, `[RETRY]\n\n${loopTask}`, ...);
|
|
37
|
+
```
|
|
38
|
+
retry 后**没有**再次检查 `agentResult.exitCode`。如果 retry 的 agent 仍然异常退出(例如退出码 1),流程会无检查地进入 review 阶段。这与超时处理的模式不一致——超时处理在 retry 后会检查 `isTimeoutResult(agentResult)` 并 fallback。
|
|
39
|
+
|
|
40
|
+
**严重程度**: 中等 — 边界情况(连续两次退出码非零),但可能导致错误结果被静默接受。
|
|
41
|
+
|
|
42
|
+
**建议**: retry 后添加 exitCode 检查:
|
|
43
|
+
```typescript
|
|
44
|
+
agentResult = await runAgentWithProgress(loopAgent, `[RETRY]\n\n${loopTask}`, ...);
|
|
45
|
+
// retry 后再次检查
|
|
46
|
+
if (agentResult.exitCode !== 0 && !isTimeoutResult(agentResult)) {
|
|
47
|
+
// fallback: 跳过此步骤
|
|
48
|
+
state.status = "skipped";
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🟢 低优先级问题
|
|
56
|
+
|
|
57
|
+
### L1. `cleanupWidget` 中的 `_workflowRunning = false` 与 `executeWorkflowBackground` 的 `_workflowRunning = false` 重复
|
|
58
|
+
|
|
59
|
+
**文件**: `extensions/workflow-engine.ts` 第 803 行 和第 1450 行
|
|
60
|
+
**问题**: `executeWorkflowBackground` 在步骤循环完成后设置 `_workflowRunning = false`(第 1450 行),然后新建的定时器在 5 秒后调用 `cleanupWidget()` 再次设置 `_workflowRunning = false`(第 803 行)。虽然不会影响正确性(重复赋值相同值),但逻辑上冗余。
|
|
61
|
+
|
|
62
|
+
**建议**: 可以移除 `executeWorkflowBackground` 中的 `_workflowRunning = false`,让 `cleanupWidget` 统一管理该变量,避免逻辑分散。
|
|
63
|
+
|
|
64
|
+
### L2. 测试中 `simulateBugAFix` 的 retry 返回语义不精确
|
|
65
|
+
|
|
66
|
+
**文件**: `tests/test-workflow-engine-bugs.mjs` 第 147-174 行
|
|
67
|
+
**问题**: `simulateBugAFix` 函数在非 full-auto 模式下返回 `"retry"`,但这个返回值没有区分"用户选择了跳过"、"用户选择了重试"、"用户选择了取消"三种情况。虽然作为模拟测试够用,但语义不够精确。
|
|
68
|
+
|
|
69
|
+
**建议**: 可以用更精确的返回值(如 `"retry"`、`"skip"`、`"cancel"`),或使用对象形式返回。
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 维护建议
|
|
74
|
+
|
|
75
|
+
1. 修复测试 `test-workflow-engine.mjs` 中 deleteCheckpointFile 的断言——这会导致 CI 失败。
|
|
76
|
+
2. 考虑在 retry 后添加二次 exitCode 检查(M2),以增强鲁棒性。
|
|
77
|
+
3. 整体修复方案符合"最小化改动"要求,代码阅读性和可维护性好。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 测试结果
|
|
82
|
+
|
|
83
|
+
- ✅ `node tests/test-workflow-engine-bugs.mjs` — **50/50 通过**(新测试全部通过)
|
|
84
|
+
- ⚠️ `node tests/test-workflow-engine.mjs` — **73/74 通过**(1 个 pre-existing 失败,非本次改动导致)
|
|
85
|
+
- ✅ `git diff HEAD` 确认变更仅限于计划中指定的范围
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 审查结论
|
|
90
|
+
|
|
91
|
+
本次修复正确地解决了两个 Bug。代码实现与实施计划一致,没有引入回归。存在 2 个中等问题和 2 个低优先级问题,但没有严重问题。
|