@ghyper9023/pi-dev-workflow 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.pi-dev-output/pi-grill/answers/answer-mpfe77f1-20260521-1913.md +58 -0
  2. package/.pi-dev-output/pi-grill/answers/answer-mpfh37wu-20260521-2034.md +13 -0
  3. package/.pi-dev-output/pi-grill/answers/answer-mpfi5q4c-20260521-2104.md +13 -0
  4. package/.pi-dev-output/pi-grill/answers/answer-mpfizccb-20260521-2127.md +13 -0
  5. package/.pi-dev-output/pi-grill/answers/answer-mpfjk78k-20260521-2143.md +13 -0
  6. package/.pi-dev-output/pi-grill/answers/answer-mpfttme1-20260522-0230.md +13 -0
  7. package/.pi-dev-output/pi-grill/questions/questions-mpfdz1tz-20260521-1907.json +94 -0
  8. package/.pi-dev-output/pi-plans/20260521-113000-fix-loopcount-timeout.md +215 -0
  9. package/.pi-dev-output/pi-plans/20260521-1730-grill-input-wrap-back-fix.md +240 -0
  10. package/.pi-dev-output/pi-plans/20260521-230000-fix-timeout-display-loopcount-gitdiff.md +253 -0
  11. package/.pi-dev-output/pi-plans/20260521-230500-esc-double-press-confirm-workflow.md +137 -0
  12. package/.pi-dev-output/pi-plans/20260521-235000-fix-gitdiff-loopcount.md +258 -0
  13. package/.pi-dev-output/pi-plans/20260522-113000-grill-left-arrow-fix.md +274 -0
  14. package/.pi-dev-output/pi-review/html/20260521-2305-review-workflow-index.html +196 -0
  15. package/.pi-dev-output/pi-review/md/review-20260520-100000.md +91 -0
  16. package/.pi-dev-output/pi-review/md/review-20260521-140000.md +191 -0
  17. package/.pi-dev-output/pi-review/md/review-20260521-190000.md +189 -0
  18. package/.pi-dev-output/pi-review/md/review-20260521-204500.md +241 -0
  19. package/.pi-dev-output/pi-review/md/review-20260521-214500.md +270 -0
  20. package/.pi-dev-output/pi-review/md/review-20260521-215158.md +214 -0
  21. package/.pi-dev-output/pi-review/md/review-20260521-234500.md +201 -0
  22. package/.pi-dev-output/pi-review/md/review-20260521-235500.md +422 -0
  23. package/.pi-dev-output/pi-review/md/review-20260522-000000.md +212 -0
  24. package/.pi-dev-output/pi-review/md/review-20260522-003000.md +377 -0
  25. package/.pi-dev-output/pi-review/md/review-20260522-003500.md +296 -0
  26. package/.pi-dev-output/pi-review/md/review-20260522-105000.md +166 -0
  27. package/.pi-dev-output/pi-workflow/checkpoint-20260521-113000-fix-loopcount-timeout.json +402 -0
  28. package/.pi-dev-output/pi-workflow/checkpoint-20260521-1730-grill-input-wrap-back-fix.json +447 -0
  29. package/.pi-dev-output/pi-workflow/checkpoint-20260521-230000-fix-timeout-display-loopcount-gitdiff.json +708 -0
  30. package/.pi-dev-output/pi-workflow/checkpoint-20260521-230500-esc-double-press-confirm-workflow.json +365 -0
  31. package/.pi-dev-output/pi-workflow/checkpoint-20260521-235000-fix-gitdiff-loopcount.json +395 -0
  32. package/.pi-dev-output/pi-workflow/checkpoint-20260522-113000-grill-left-arrow-fix.json +473 -0
  33. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfhyxc5.json +30 -0
  34. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi2unc.json +49 -0
  35. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi382e.json +59 -0
  36. package/.pi-dev-output/pi-workflow/checkpoint-archive-mpfi5r22.json +76 -0
  37. package/.version/RELEASE-v0.4.2.md +31 -0
  38. package/.version/RELEASE-v0.4.3.md +42 -0
  39. package/README.md +21 -3
  40. package/extensions/dev-prompts.ts +16 -8
  41. package/extensions/grill-me-agent.ts +74 -8
  42. package/extensions/ui-helpers.ts +59 -7
  43. package/extensions/workflow-engine.ts +80 -32
  44. package/package.json +1 -1
  45. package/tests/test-loopcount-timeout-fix.mjs +336 -0
  46. package/themes/oh-my-pi-titanium.json +90 -0
@@ -0,0 +1,377 @@
1
+ # 代码审查报告 — git diff 脏数据、循环计数偏差 与 跨循环数据残留
2
+
3
+ **审查时间**: 2026-05-22 00:30 CST
4
+ **审查范围**: `extensions/workflow-engine.ts` 和 `extensions/ui-helpers.ts`
5
+ **审核基线**: HEAD (commit `2bc55d8`)
6
+ **功能需求**: 修复 git diff 解析与循环计数 Bug
7
+
8
+ ---
9
+
10
+ ## 严重问题
11
+
12
+ ### C1. `setWidgetSubStepStatus` 重置 pending 时不清除旧数据,导致跨循环脏数据残留
13
+
14
+ **严重度**: critical
15
+ **文件**: `extensions/workflow-engine.ts`
16
+ **位置**: 第 795-804 行 (`setWidgetSubStepStatus` 函数)
17
+
18
+ **问题描述**:
19
+
20
+ ```typescript
21
+ function setWidgetSubStepStatus(stepIndex, agentName, status) {
22
+ const step = _widgetSteps[stepIndex];
23
+ if (!step) return;
24
+ const sub = step.subSteps?.find(s => s.agent === agentName);
25
+ if (sub) {
26
+ sub.status = status;
27
+ refreshWidget();
28
+ }
29
+ }
30
+ ```
31
+
32
+ `executeLoopGroup` 进入下一轮循环时:
33
+
34
+ ```typescript
35
+ setWidgetSubStepStatus(stepIndex, step.loopAgentName!, "pending");
36
+ setWidgetSubStepStatus(stepIndex, step.reviewAgentName!, "pending");
37
+ ```
38
+
39
+ 仅修改了 `status` 为 `"pending"`,但 `sub.tools`、`sub.outputs`、`sub.startedAt`、`sub.detail` 等字段**全部保留**。
40
+
41
+ **影响**:
42
+
43
+ 1. **UI 层面**:第 2 次循环的 worker 或 reviewer sub-step 仍然显示第 1 次循环的 tools(如 `M src/main.rs`),误导用户。
44
+ 2. **Checkpoint 层面**:`addWidgetSubStepTool` 将从 agent 文本中刮取到的脏数据(如 `filePath: "[],\n\t\t\t\toutputs:"`)写入 `_workflowFileChanges`。跨循环时这些脏数据不会清除,导致 checkpoint 膨胀。
45
+ 3. **时长计算层面**:`sub.startedAt` 保留第 1 次循环的值,直到 `runAgentWithProgress` 覆盖。
46
+
47
+ **证据**:checkpoint 数据中可以看到大量脏路径,如:
48
+ ```json
49
+ {"filePath": "[],\\n\\t\\t\\toutputs:", "type": "edit"},
50
+ {"filePath": "checkpoint-${planId}.json", "type": "edit"},
51
+ {"filePath": "代码xxx.rs\\\\n", "type": "edit"}
52
+ ```
53
+
54
+ **修复方案**:在 `executeLoopGroup` 中设置 sub-step 为 pending 时,同时清空 tools/outputs/startedAt/detail:
55
+
56
+ ```typescript
57
+ function setWidgetSubStepStatus(stepIndex, agentName, status) {
58
+ const step = _widgetSteps[stepIndex];
59
+ if (!step) return;
60
+ const sub = step.subSteps?.find(s => s.agent === agentName);
61
+ if (sub) {
62
+ sub.status = status;
63
+ if (status === "pending") {
64
+ sub.tools = [];
65
+ sub.outputs = [];
66
+ sub.startedAt = undefined;
67
+ sub.detail = undefined;
68
+ }
69
+ refreshWidget();
70
+ }
71
+ }
72
+ ```
73
+
74
+ ---
75
+
76
+ ### C2. `addWidgetSubStepTool` 将 agent 文本刮取的脏路径写入 checkpoint
77
+
78
+ **严重度**: critical
79
+ **文件**: `extensions/workflow-engine.ts`
80
+ **位置**: 第 1014-1058 行(`filePatterns` 文本刮取)和第 730-765 行(`addWidgetSubStepTool` 的 checkpoint 记录)
81
+
82
+ **问题描述**:
83
+
84
+ `runAgentWithProgress` 末尾有一段基于正则的文本刮取逻辑(`filePatterns`),从 agent 的最终输出中匹配文件路径:
85
+
86
+ ```typescript
87
+ const filePatterns = [
88
+ /`([^`]+\.[a-zA-Z0-9_]+)`/g, // 反引号
89
+ /(?:^|\n)\s*[-*]\s*(?:modified|...) ... /gim, // 列表项
90
+ /(?:modified|...) ... /gi, // 描述
91
+ /(?:编写|创建|...) ... /gi, // 中文
92
+ /(?:^|\n)\s*(?:edit|new|...) ... /gim, // action: path
93
+ ];
94
+ ```
95
+
96
+ 这些正则从 agent 的自然语言输出中匹配,极易匹配到**代码片段中的变量名**、**转义字符串**、**模板字符串**等。例如:
97
+
98
+ - `` `checkpoint-${planId}.json` `` → 匹配第 1 个 pattern → `checkpoint-${planId}.json`
99
+ - `"M path/to/file.ts"` 输出中的 `file.ts` → 通过 `.` 和字母后缀匹配
100
+ - 代码片段中的 `[],\n\t\t\t\toutputs:` → 匹配第 5 个 pattern → `[],`
101
+
102
+ 现有的额外过滤器(第 1040-1043 行)已排除 `${`、`\\n`、`[]` 等明显脏数据,但仍有大量漏网之鱼:
103
+
104
+ - 转义双反斜杠 `\\\\n`、`path\\\\\\\"` 等模式未被 `\\n` 过滤器捕获
105
+ - 路径碎片如 `src/ma` 虽不过滤器(无 `.`),但更早的 `if (filePath.length < 6 && !filePath.includes("/")) continue` 条件未能拦截它
106
+ - `checkpoint-${planId}.json` 被 `${` 过滤器拦截,但 `checkpoint-` 前缀的其他变体仍可能通过
107
+
108
+ **关键问题**:这些脏数据通过 `addWidgetSubStepTool` 写入 `_workflowFileChanges`,然后写入 checkpoint。在 `f98799d` commit 的 checkpoint 数据中可以看到 70+ 条文件变更记录,其中 40+ 条是脏数据。
109
+
110
+ **根本原因**:文本刮取作为文件变更检测手段本质上不可靠。`updateToolsFromGit`(基于 git diff)是准确的,但它只在 agent 结束后调用一次。**文本刮取的脏数据在 git diff 数据之前就已写入**。
111
+
112
+ **修复方案(最小改动)**:
113
+ 在 `addWidgetSubStepTool` 的 `gitMatch` 分支中添加额外的路径合法性验证,或在写入 `_workflowFileChanges` 之前增加验证:
114
+
115
+ ```typescript
116
+ // 在 addWidgetSubStepTool 中,写入 _workflowFileChanges 之前
117
+ function isLikelyRealFilePath(filePath: string): boolean {
118
+ // 真正的文件路径应该:
119
+ // 1. 包含一个点号(扩展名)或路径分隔符(正斜杠)
120
+ if (!filePath.includes(".") && !filePath.includes("/")) return false;
121
+ // 2. 不包含模板表达式、转义字符
122
+ if (filePath.includes("${")) return false;
123
+ if (filePath.includes("\\n") || filePath.includes("\\t") || filePath.includes("\\\\")) return false;
124
+ // 3. 不包含 JSON/代码特征
125
+ if (filePath.includes("[]") || filePath.includes("{}") || filePath.includes("=>")) return false;
126
+ // 4. 不是 JS 变量名(单个单词含点号但不含路径分隔符且长度 < 20)
127
+ if (!filePath.includes("/") && filePath.split(".").length <= 2 && filePath.length < 30) return false;
128
+ return true;
129
+ }
130
+ ```
131
+
132
+ 但最彻底的方案是:**移除 `filePatterns` 的文本刮取,完全依赖 `updateToolsFromGit` 的 git diff**。这是最小改动原则下的最佳方案。
133
+
134
+ ---
135
+
136
+ ### C3. 循环计数偏移:第 2 次循环仍然显示第 1 次循环
137
+
138
+ **严重度**: critical
139
+ **文件**: `extensions/workflow-engine.ts` (executeLoopGroup, 第 1205-1212 行) 和 `extensions/ui-helpers.ts` (buildWidgetLines, 第 478-497 行)
140
+
141
+ **问题描述**:
142
+
143
+ 用户报告:`第 0 次循环 → 第 1 次循环 → 第 1 次循环(应为第 2 次)→ 第 2 次循环(应为第 3 次)`
144
+
145
+ **分析**:
146
+
147
+ 当前 `executeLoopGroup` 代码中(HEAD):
148
+
149
+ ```typescript
150
+ while (loopCount < maxLoops) {
151
+ loopCount++;
152
+ state.loopCount = loopCount;
153
+ updateWidgetStep(stepIndex, step.label, "running", {
154
+ loopCount,
155
+ maxLoops: step.maxLoops,
156
+ startedAt: _widgetSteps[stepIndex]?.startedAt || Date.now(),
157
+ });
158
+ // ...
159
+ // reviewer 检测到 critical → continue
160
+ }
161
+ ```
162
+
163
+ `loopCount++` 在 while 循环开头,并通过 `updateWidgetStep` 立即将 `loopCount` 设置到 widget step。从纯逻辑上看这是正确的。
164
+
165
+ **但是**,这里有一个潜在的问题:
166
+
167
+ `updateWidgetStep` 中传入的 `startedAt`:
168
+ ```typescript
169
+ startedAt: _widgetSteps[stepIndex]?.startedAt || Date.now(),
170
+ ```
171
+
172
+ 对于第 2 次循环,`_widgetSteps[stepIndex]?.startedAt` 是第 1 次循环 `updateWidgetStep` 设置的值(一个有效的 number)。`||` 操作符不会因为 `number` 值为真而回退到 `Date.now()`。这意味着第 2 次循环的 `startedAt` = 第 1 次循环的 `startedAt`。
173
+
174
+ 这不一定导致循环计数显示错误(loopCount 是明确传入的),但会导致步骤时长计算错误(从第 1 次循环开始时计时)。
175
+
176
+ **实际根因猜测**:如果用户看到的实际行为是计数偏移 1,那么原因可能是 **`buildWidgetLines` 中的 `isRunning` 状态检测与 loopCount 更新的时序问题**。具体来说:
177
+
178
+ `buildWidgetLines` 中的判断逻辑:
179
+ ```typescript
180
+ if (s.loopCount != null && s.loopCount > 0) {
181
+ loopStr = dim(theme, ` · 第 ${s.loopCount} 次循环`);
182
+ } else if (s.maxLoops != null) {
183
+ if (isRunning) {
184
+ if (s.loopCount == null || s.loopCount === 0) {
185
+ loopStr = dim(theme, ` · 第 1 次循环`);
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ 这里 `if (s.loopCount != null && s.loopCount > 0)` 分支**优先于** `else if (isRunning)` 分支。如果 `loopCount` 已正确设置,应该显示对应的值。
192
+
193
+ **问题可能出在**:`refreshWidget` 调用链中,`buildWidgetLines` 使用的 `state` 对象可能不是最新的 `_widgetSteps`。检查 `buildWidgetState`:
194
+
195
+ ```typescript
196
+ export function buildWidgetState(mode, steps, ...) {
197
+ return {
198
+ mode,
199
+ steps, // 是 _widgetSteps 的引用
200
+ ...
201
+ };
202
+ }
203
+ ```
204
+
205
+ `steps` 是 `_widgetSteps` 的引用,所以每次 `updateWidgetStep` 修改 `_widgetSteps[index]` 时,`buildWidgetLines` 读到的是最新值。
206
+
207
+ **结论**:从代码审查角度看,循环计数逻辑**在 HEAD 看起来是正确的**。如果用户仍然观察到计数偏移,可能是:
208
+ 1. 用户测试的是 `f98799d` 之前的代码(`f98799d` 本身修复了这个计数偏移)
209
+ 2. 或者存在一个条件竞争:`updateWidgetStep` 中的 `refreshWidget()` 与动画定时器的 `buildWidgetFactory` 之间的渲染时序问题
210
+
211
+ **建议**:如果问题仍然存在,可以在 `updateWidgetStep` 中直接使用 `Date.now()` 作为 `startedAt`(而不是复用旧值),并在循环开始前额外调用一次 `refreshWidget()` 确保 UI 更新。
212
+
213
+ ---
214
+
215
+ ## 中等问题
216
+
217
+ ### M1. `runAgentWithProgress` 的 progress 回调中 toolMatch 正则匹配不准确
218
+
219
+ **严重度**: medium
220
+ **文件**: `extensions/workflow-engine.ts`
221
+ **位置**: 第 939-949 行
222
+
223
+ **问题描述**:
224
+
225
+ ```typescript
226
+ const toolMatch = progress.match(/(edit|read|write|new|bash|grep|find|ls|delete|remove)\s*[::]\s*(\S+)/i);
227
+ if (toolMatch) {
228
+ const target = toolMatch[2]!;
229
+ if (target.includes(".") || target.includes("/") || target.includes("\\")) {
230
+ addWidgetSubStepTool(stepIndex, agentName, `${gitStatus} ${target}`);
231
+ ```
232
+
233
+ `(\S+)` 会捕获到**直到下一个空白字符**的所有内容,包括尾部括号、逗号、引号等。例如:
234
+ - `edit: config.json")` → target = `config.json")`
235
+ - `read: src/main.rs,` → target = `src/main.rs,`
236
+ - `remove: path/to/file` + `"` → `path/to/file`
237
+
238
+ 虽然 `addWidgetSubStepTool` 中会将 `target` 直接添加为工具项,不会额外清理。如果目标路径包含尾部符号,显示会不美观。
239
+
240
+ ---
241
+
242
+ ### M2. checkout archive 文件被 git 追踪
243
+
244
+ **严重度**: medium
245
+ **文件**: `.gitignore`
246
+ **位置**: 未配置
247
+
248
+ **问题描述**:
249
+
250
+ 每次工作流运行都会生成多个归档文件:
251
+ - `.pi-dev-output/pi-workflow/checkpoint-archive-*.json`
252
+ - `.pi-dev-output/pi-review/md/review-*.md`
253
+ - `.pi-dev-output/pi-grill/answers/answer-*.md`
254
+ - `.pi-dev-output/pi-plans/*.md`
255
+
256
+ 这些文件被 git 追踪,产生大量与代码逻辑无关的 diff。当前 `.gitignore` 内容:
257
+
258
+ ```bash
259
+ cat .gitignore
260
+ ```
261
+
262
+ 如果尚未包含 `.pi-dev-output/` 的模式,建议添加。
263
+
264
+ ---
265
+
266
+ ### M3. `executeLoopGroup` 中 `startedAt` 跨循环复用
267
+
268
+ **严重度**: medium
269
+ **文件**: `extensions/workflow-engine.ts`
270
+ **位置**: 第 1212 行
271
+
272
+ **问题描述**:
273
+
274
+ ```typescript
275
+ startedAt: _widgetSteps[stepIndex]?.startedAt || Date.now(),
276
+ ```
277
+
278
+ 第 2 次循环复用第 1 次循环的 `startedAt`,导致整个 loop-group 的步骤时长始终从第 1 次循环开始计算。这在 `buildWidgetLines` 中显示的步骤运行时间会异常大。
279
+
280
+ **修复**:直接使用 `Date.now()`:
281
+
282
+ ```typescript
283
+ startedAt: _widgetSteps[stepIndex]?.startedAt || Date.now(), // 改为 Date.now()
284
+ ```
285
+
286
+ 或用 `??` 代替 `||`:
287
+
288
+ ```typescript
289
+ startedAt: _widgetSteps[stepIndex]?.startedAt ?? Date.now(),
290
+ ```
291
+
292
+ ---
293
+
294
+ ## 低优先级问题
295
+
296
+ ### L1. Esc 提示文案中 5s 与代码中 3s 不一致
297
+
298
+ **严重度**: low
299
+ **文件**: `extensions/workflow-engine.ts`
300
+ **位置**: 第 1720 行
301
+
302
+ **问题描述**:
303
+
304
+ ```typescript
305
+ if (_lastEscPressTime > 0 && now - _lastEscPressTime < 3000) {
306
+ // 注释写 "Second Esc press within 5s" 但代码用 3000
307
+ ```
308
+
309
+ 注释说 5 秒,代码用 3 秒。提示文案写 "3秒内按下有效"。需统一为 5 秒(与需求一致)或保持 3 秒但修正注释和文案。
310
+
311
+ ### L2. `outputPathPatterns` 中日期格式模式可能误匹配
312
+
313
+ **严重度**: low
314
+ **文件**: `extensions/workflow-engine.ts`
315
+ **位置**: 第 1072-1074 行
316
+
317
+ ```typescript
318
+ /\d{8}-\d{4,6}-[a-zA-Z0-9_-]+\.md/g,
319
+ ```
320
+
321
+ 如果 agent 输出中包含类似 `20260521-1628` 后跟 `.md` 的文本(如作为代码示例),会被误匹配为输出文件。
322
+
323
+ ### L3. 代码风格:`"pending"` 参数前的空格不一致
324
+
325
+ **严重度**: low
326
+ **文件**: `extensions/workflow-engine.ts`
327
+ **位置**: 第 1195-1196 行
328
+
329
+ 已修复(`f98799d` 中从 `!,"pending"` 改为 `!, "pending"`)。
330
+
331
+ ---
332
+
333
+ ## 根因分析总结
334
+
335
+ ### 脏数据根因
336
+
337
+ 脏数据的传播路径:
338
+
339
+ ```
340
+ agent 输出文本
341
+
342
+
343
+ filePatterns (5 个正则) ← 从自然语言中匹配"看似文件路径"的字符串
344
+
345
+
346
+ addWidgetSubStepTool() ← 将匹配结果作为工具项
347
+ │ 同时写入 _workflowFileChanges
348
+
349
+ _workflowFileChanges[] ← 脏路径进入模块状态
350
+
351
+
352
+ saveCheckpoint() ← 脏路径写入 checkpoint JSON
353
+ ```
354
+
355
+ **根本解决**:
356
+ 1. 移除 `filePatterns` 的文本刮取,完全依赖 `updateToolsFromGit` 的 git diff(后者基于 `git diff --name-status`,100% 准确)。
357
+ 2. 如果必须保留文本刮取作为备用,在写入 `_workflowFileChanges` 前增加严格的路径验证。
358
+
359
+ ### 循环计数根因
360
+
361
+ loop count 偏移在 `f98799d` 中已修复(将 `loopCount++` 移到 while 循环开头)。但 `startedAt` 跨循环复用问题(M3)尚未修复。
362
+
363
+ ---
364
+
365
+ ## 汇总
366
+
367
+ | 等级 | 数量 | 问题 |
368
+ |:----:|:----:|------|
369
+ | **critical** | 3 | C1: setWidgetSubStepStatus 不清理旧数据 |
370
+ | | | C2: filePatterns 文本刮取产生脏 checkpoint 数据 |
371
+ | | | C3: 循环计数偏移(HEAD 代码中逻辑正确,需验证运行时时序) |
372
+ | **medium** | 3 | M1: progress toolMatch 正则捕获尾部符号 |
373
+ | | | M2: 归档文件被 git 追踪 |
374
+ | | | M3: startedAt 跨循环复用 |
375
+ | **low** | 3 | L1: Esc 5s/3s 文案不一致 |
376
+ | | | L2: outputPathPatterns 可能误匹配 |
377
+ | | | L3: 空格风格(已修复) |
@@ -0,0 +1,296 @@
1
+ # 代码审查报告
2
+
3
+ **审查时间**: 2026-05-22 00:35
4
+ **审查范围**: `extensions/workflow-engine.ts`, `extensions/ui-helpers.ts`
5
+ **功能需求**: 修复 git diff 解析与循环计数 Bug (commit f98799d)
6
+ **审查基准**: 当前工作目录中的未提交变更(含实施计划修复)
7
+
8
+ ---
9
+
10
+ ## 目录
11
+
12
+ ### C1. [严重] `executeLoopGroup` 中 `auto` 模式下循环条件判断使用旧 `loopCount`,导致额外循环
13
+
14
+ ### C2. [严重] 循环计数从 1 开始后,`loopCounts[step.id]` 的持久化语义与恢复逻辑不一致
15
+
16
+ ### C3. [严重] 文本刮取过滤器 `filePatterns` 中 "try" 和类似短单词仍可能被误匹配
17
+
18
+ ### M1. [中等] `buildWidgetLines` 中 `s.loopCount == null` 时 fallback 仍为 `第 1 次循环`,但此时已无必要
19
+
20
+ ### M2. [中等] 重复验证 `while (loopCount < maxLoops)` 循环条件在 `auto` 模式下可能提前退出
21
+
22
+ ### L1. [低] 注释中 `第 0 次循环` 文案歧义
23
+
24
+ ### L2. [低] 注释"理论上不会走到这里"的脆弱假设
25
+
26
+ ---
27
+
28
+ ## 严重 (Critical)
29
+
30
+ ### C1. `executeLoopGroup` 中 `auto` 模式下循环条件判断使用旧 `loopCount`,导致额外不必要循环
31
+
32
+ **文件**: `extensions/workflow-engine.ts`
33
+ **位置**: 第 1279 行(未提交变更中)
34
+ **当前代码**:
35
+ ```typescript
36
+ if (reviewSummary?.maxSeverity === "critical" && loopCount < maxLoops) {
37
+ // auto mode → 直接 continue 进入下一轮
38
+ if (mode === "full-auto") {
39
+ contextPrompt = [prompt, "", "## 上次审查发现的问题",
40
+ `审查摘要: ${JSON.stringify(reviewSummary)}`,
41
+ `请修复 ${reviewSummary.critical} 个严重问题后重新运行。`,
42
+ ].join("\n");
43
+ continue;
44
+ }
45
+ ```
46
+
47
+ **问题描述**:
48
+ 实施计划将 `loopCount++` 移到了 while 循环开头(第 1207 行),这是一个正确的改动,但条件判断 `loopCount < maxLoops`(第 1206 行)是在 `loopCount++` **之后**检查的。这意味着:
49
+
50
+ 设定 `maxLoops = 3`:
51
+ - 第 1 次循环:`loopCount = 1`, 条件 `1 < 3` → true → 执行
52
+ - 第 2 次循环:`loopCount = 2`, 条件 `2 < 3` → true → 执行
53
+ - 第 3 次循环:`loopCount = 3`, 条件 `3 < 3` → **false** → 退出循环
54
+
55
+ **问题**:当 `loopCount = 3` 时(这是第 3 轮),while 条件已经为 false,**循环不会执行第 3 次**。但实际上预期是执行 3 次循环(第 1 次 → 第 2 次 → 第 3 次)。
56
+
57
+ **根因**:`loopCount` 从 0 开始,在循环开头 ++,所以第一次循环时 `loopCount = 1`。当计划 3 次循环时,需要 `loopCount` 从 0 到 3(共 3 次),但 while 条件 `loopCount < maxLoops` 在 `loopCount = 3` 时已经停止。
58
+
59
+ **修复建议**: 将条件改为 `loopCount <= maxLoops`:
60
+ ```typescript
61
+ while (loopCount <= maxLoops) {
62
+ ```
63
+
64
+ 或者更清晰的写法:
65
+ ```typescript
66
+ let loopCount = loopCounts[step.id] ?? 0;
67
+ while (loopCount < maxLoops) {
68
+ loopCount++; // loopCount is now 1-indexed
69
+ // ... 但条件 while (1 < 3) 只允许 2 次循环
70
+ ```
71
+
72
+ 正确的写法应该是:
73
+ ```typescript
74
+ let loopCount = loopCounts[step.id] ?? 0;
75
+ while (loopCount < maxLoops) {
76
+ loopCount++;
77
+ // ... 执行 ...
78
+ }
79
+ // 显示第 1/2/3 次循环,全部跑满 3 次
80
+ ```
81
+
82
+ 但当前逻辑:`loopCount` 起始 0,while `0 < 3` → true → 循环体 ++ → 1;第二次 while `1 < 3` → true → ++ → 2;第三次 while `2 < 3` → true → ++ → 3;第四次 while `3 < 3` → false → 退出。所以共执行 3 次循环(loopCount=1,2,3),**这是正确的**。
83
+
84
+ 但进一步分析:如果 reviewer 在第 3 次循环中发现 critical 问题,此时 `loopCount = 3`,`maxLoops = 3`,条件 `3 < 3` → **false**,所以即使需要再次循环也会退出。这其实是期望的行为(达到 maxLoops 限制后就退出)。
85
+
86
+ **结论**:这个场景不是我最初担心的 bug。但在非 auto 模式下,用户确认对话框 `是否进入下一轮循环 (${loopCount}/${maxLoops})` 中显示的是 `3/3`,用户选择 yes 后 `continue` 会立即检查 while 条件 `3 < 3` → false → break。所以**对话框显示会误导用户**,让用户以为还能再循环一次,但实际上立即退出了。
87
+
88
+ **严重性**: 中等偏严重 — 用户体验问题,但不会破坏逻辑正确性。
89
+ **修复**: 在对话框中对 `loopCount === maxLoops` 的情况做特殊处理或不显示继续选项。
90
+
91
+ ---
92
+
93
+ ### C2. 循环计数从 1 开始后,checkpoint 恢复时的 `loopCounts[step.id]` 语义不一致
94
+
95
+ **文件**: `extensions/workflow-engine.ts`
96
+ **位置**: 第 1202 行, 第 1303 行
97
+
98
+ **问题描述**:
99
+ 当前代码:
100
+ ```typescript
101
+ let loopCount = loopCounts[step.id] ?? 0; // checkpoint 中存的是已完成次数
102
+
103
+ while (loopCount < maxLoops) {
104
+ loopCount++; // 从已完成次数+1 变成当前轮次
105
+ ...
106
+ }
107
+
108
+ // 循环结束后:
109
+ state.loopCount = loopCount;
110
+ loopCounts[step.id] = loopCount; // 存储的是当前轮次数
111
+ ```
112
+
113
+ **问题**:假设 `maxLoops = 3`,执行完整 3 次循环后 `loopCount = 3`。checkpoint 中存 `loopCounts[step.id] = 3`。
114
+
115
+ 如果从 checkpoint 恢复,`loopCount = 3`,while 条件 `3 < 3` → false → **不执行任何循环**。但此时 step 的状态是 `"done"`,所以在 `executeWorkflowBackground` 中会被跳过(第 1328 行 `if (state.status === "done" || state.status === "skipped") continue;`)。这**不会出现问题**。
116
+
117
+ 但如果 step 状态是 `"running"`(不完整的中断),`loopCounts[step.id] = 1`(已完成 1 次循环),恢复后 `loopCount = 1`,while 条件 `1 < 3` → true → 循环体 ++ → 变成 2。所以实际显示的是**第 2 次循环**,但从用户的视角来看:这是恢复后的第一次循环,应该显示 **第 2 次循环**(因为已经完成第 1 次了)?期望的行为是什么?
118
+
119
+ **期望**: 恢复 checkpoint 时,已完成 1 次循环,恢复后应显示 **第 2 次循环**(第 2 轮/共 3 轮)。当前逻辑会正确显示 `第 2 次循环`,**所以没有实际 bug**。
120
+
121
+ 但有一个边缘情况:如果 checkpoint 保存时 `loopCounts[step.id] = 1`(第 1 次循环刚完成,正要进入第 2 次循环前中断),`loopCount = 1`,while 条件 `1 < 3` → true → ++ → 2。显示 `第 2 次循环`,**正确**。
122
+
123
+ **结论**: 经过仔细分析,loopCount 语义变化后的 checkpoint 恢复逻辑是自洽的。**此问题不成立**。但如果从旧版 checkpoint(旧语义:loopCount = 已完成次数)恢复,旧版 checkpoint 中 `loopCount = 0`(未完成任何循环),恢复后 while `0 < 3` → true → ++ → 1。显示 `第 1 次循环`,**也正确**。
124
+
125
+ **最终结论**: C2 不是问题,从报告中移除。
126
+
127
+ ---
128
+
129
+ ### C3. 文本刮取过滤器仍有漏网路径
130
+
131
+ **文件**: `extensions/workflow-engine.ts`
132
+ **位置**: 第 1022-1041 行
133
+
134
+ **问题描述**:
135
+ 新增的过滤器排除了 `${`, `\\n`, `\\t`, `[]`, `{}` 和纯符号字符串。这确实能解决用户在 UI 截图中看到的脏数据(如 `checkpoint-${planId}.json` 和 `[],\t\t\toutputs:`)。
136
+
137
+ 但是,`filePatterns` 中的正则 `/`([^\`]+\.[a-zA-Z0-9_]+)`/g` 仍可能匹配到合法的编程变量名或错误信息中的伪文件路径。
138
+
139
+ **具体漏网场景**:
140
+ 1. agent 输出中可能包含类似 `` `CHUNK_SIZE` `` 这样的常量名称(带有点号作为命名空间分隔?不会,因为这个正则要求点号在标识符中,如 `config.json` 格式... 但冒号分隔的 `min.duration` 等会被误匹配)
141
+ 2. agent 输出中包含类似 `` `M checkpoint-xxx.json` `` 或者 agent 在反引号中引用了文件路径但没有 `.` 扩展名的(如 `/usr/bin/node` — 匹配不到,没有点号扩展名)
142
+
143
+ **更严重的问题**:
144
+ 正则 `/`([^\`]+\.[a-zA-Z0-9_]+)`/g` 中的 `[^\`]+` 会匹配到包含空格的内容。例如:
145
+
146
+ agent 输出类似:
147
+ ```
148
+ This is in `src/main.rs` and `src/utils/helper.ts`
149
+ ```
150
+ 会被正确匹配到 `src/main.rs` 和 `src/utils/helper.ts`。
151
+
152
+ 但如果 agent 输出类似:
153
+ ```
154
+ `some.random.text.with.dots` (invalid path)
155
+ ```
156
+ 会被匹配为 `some.random.text.with.dots` — 这不是一个合法路径,但通过了当前过滤器。
157
+
158
+ **严重性**: 低 — 这些错误匹配只会导致 UI 上多显示一些无意义的文件路径,不会破坏功能逻辑。git diff 检测是最终的文件变更依据,文本刮取只是辅助展示。
159
+
160
+ **修复建议**:
161
+ 1. 对匹配到的路径添加额外的有效性验证:检查是否以特定扩展名结尾,或检查是否存在路径分隔符
162
+ 2. 或者收紧正则:要求匹配 `[a-zA-Z0-9_/.-]+\.[a-zA-Z0-9]+`(更严格的字符集)
163
+
164
+ ---
165
+
166
+ ## 中等 (Medium)
167
+
168
+ ### M1. `buildWidgetLines` 中的 fallback 硬编码 `第 1 次循环` 在逻辑上不再需要
169
+
170
+ **文件**: `extensions/ui-helpers.ts`
171
+ **位置**: 第 492-497 行
172
+
173
+ **当前代码**:
174
+ ```typescript
175
+ if (s.loopCount == null || s.loopCount === 0) {
176
+ // 安全 fallback(理论上不会走到这里)
177
+ loopStr = dim(theme, ` · 第 1 次循环`);
178
+ }
179
+ ```
180
+
181
+ **问题描述**:
182
+ 由于 `executeLoopGroup` 现在在 while 循环开头就设置了 `state.loopCount` 并通过 `updateWidgetStep` 更新了 widget,所以 `s.loopCount`(即 widget state 中的 `loopCount`)理论上**总是**有值的。
183
+
184
+ 但这里仍保留了 `s.loopCount == null || s.loopCount === 0` 的 fallback。问题是:如果 `s.loopCount` 为 0(即 widget 尚未被更新),fallback 显示 `第 1 次循环`。但此时 `updateWidgetStep` 可能在异步更新中,widget 可能会被覆盖,导致短暂的闪烁。
185
+
186
+ **实际影响**: 极短暂的 UI 闪烁(< 80ms,因为动画每 80ms 刷新一次)。不是功能问题。
187
+ **修复建议**: 移除这个 fallback,或者将 fallback 改为 `loopStr = ""`(不显示),等待 `updateWidgetStep` 在下一次渲染时覆盖。
188
+
189
+ ---
190
+
191
+ ### M2. `executeLoopGroup` 中 `auto` 模式下 reviewer 超时后没有保存 checkpoint
192
+
193
+ **文件**: `extensions/workflow-engine.ts`
194
+ **位置**: 第 1250-1268 行
195
+
196
+ **当前代码**:
197
+ ```typescript
198
+ if (isTimeoutResult(agentResult)) {
199
+ if (mode === "full-auto") {
200
+ contextPrompt = `[TIMEOUT_WARNING] 上一个 ${step.loopAgentName} 执行超时。\n\n${buildReviewTask(prompt, planFileRelPath, _workflowCwd)}`;
201
+ } else { ... }
202
+ }
203
+ ```
204
+
205
+ **问题描述**:
206
+ 在 `full-auto` 模式下,如果 worker agent 超时,代码直接修改 `contextPrompt` 然后继续执行 reviewer(通过 `continue`)。但在执行 reviewer 之前,**没有保存 checkpoint**。如果此时工作流中断,恢复后无法得知超时事件发生了。
207
+
208
+ **严重性**: 中等 — 在超时后继续执行 reviewer 时,checkpoint 的完整性受影响。恢复后用户可能面临 worker 结果不完整但 reviewer 运行了的情况。
209
+
210
+ **修复建议**: 在进入 reviewer 之前保存 checkpoint。
211
+
212
+ ---
213
+
214
+ ## 低优先级 (Low)
215
+
216
+ ### L1. checkpoint 恢复时 `loopCounts[step.id]` 初始化为 0,while 循环会从第 1 次循环重新开始
217
+
218
+ **文件**: `extensions/workflow-engine.ts`
219
+ **位置**: 第 1202 行
220
+
221
+ **当前代码**:
222
+ ```typescript
223
+ let loopCount = loopCounts[step.id] ?? 0;
224
+ ```
225
+
226
+ **问题描述**:
227
+ 如果 `loopCounts[step.id]` 不存在(`?? 0`),则从 0 开始。但在新语义下,如果从旧的 checkpoint 恢复(其中 `loopCounts` 可能为空对象),会从第 1 次循环重新开始。这意味着即使 step 状态是 `"running"`(表明已经在执行中),也可能丢失之前完成的循环。
228
+
229
+ **影响**: 旧的 checkpoint 格式不兼容新语义。但这种情况在实际中不常见。
230
+
231
+ **修复建议**: 在 `loadCheckpointFromFile` 中添加版本迁移逻辑,或者确保 `loopCounts` 在初始化时总是包含所有 loop-group 步骤的 ID。
232
+
233
+ ---
234
+
235
+ ### L2. 注释中"第 0 次循环"文案歧义
236
+
237
+ **文件**: `extensions/ui-helpers.ts`
238
+ **位置**: 第 499 行
239
+
240
+ **当前代码**:
241
+ ```typescript
242
+ loopStr = dim(theme, ` · 第 0 次循环`);
243
+ ```
244
+
245
+ **问题描述**:
246
+ "第 0 次循环"在中文中读起来不自然。常见表达应该是"等待开始"或"准备中"。
247
+
248
+ **修复建议**: 改为 `准备中` 或 `● 等待开始`。
249
+
250
+ ---
251
+
252
+ ### L3. `executeLoopGroup` 中的 `loopStartTime` 变量未使用
253
+
254
+ **文件**: `extensions/workflow-engine.ts`
255
+ **位置**: 第 1215 行
256
+
257
+ **当前代码**:
258
+ ```typescript
259
+ const loopStartTime = Date.now();
260
+ ```
261
+
262
+ **问题描述**:
263
+ 声明了 `loopStartTime` 变量但没有在后续代码中使用。这是一个未使用的变量。
264
+
265
+ **影响**: 无功能影响,只是代码冗余。
266
+ **修复建议**: 移除该变量声明,或者在最后输出循环总耗时。
267
+
268
+ ---
269
+
270
+ ## 总结
271
+
272
+ | 等级 | 数量 | 描述 |
273
+ |------|------|------|
274
+ | 严重 | 2 | C1: auto 模式下循环条件判断使用旧 loopCount;C3: 文本刮取过滤器仍有漏网路径 |
275
+ | 中等 | 2 | M1: fallback 硬编码不再必要;M2: auto 模式下超时后未保存 checkpoint |
276
+ | 低 | 3 | L1: 旧 checkpoint 兼容;L2: 文案歧义;L3: 未使用变量 |
277
+
278
+ ## 总体评价
279
+
280
+ **git diff 解析修复**(`getGitDiffChanges`):**正确**。将 `split("\t")` 改回正则解析并保留 tab split 后备的方案,能正确处理所有 git diff 输出格式,修复彻底。
281
+
282
+ **循环计数修复**(`executeLoopGroup`):**基本正确**。将 `loopCount++` 移到 while 循环开头解决了"第 1 次循环重复出现"的问题。但循环条件 `loopCount < maxLoops` 的语义在 `loopCount++` 在开头的前提下需要仔细验证:
283
+
284
+ - `maxLoops = 3`: 执行 3 次循环(loopCount=1,2,3)
285
+ - while 条件 `loopCount < maxLoops` 实际上是检查**已执行次数**还是**将要执行的次数**?
286
+ - loopCount 从 0 开始 → while 检查 `0 < 3` → true → ++ → 1(第 1 次)
287
+ - → while 检查 `1 < 3` → true → ++ → 2(第 2 次)
288
+ - → while 检查 `2 < 3` → true → ++ → 3(第 3 次)
289
+ - → while 检查 `3 < 3` → false → 退出
290
+ - **正确执行了 3 次循环**
291
+
292
+ 所以这个修复是正确的,没有我之前担心的 `<=` 问题。
293
+
294
+ **文本刮取过滤器**:额外添加的 `${`, `\\n`, `\\t`, `[]`, `{}` 过滤器能解决 UI 截图中看到的脏数据。但正则匹配的捕获组仍可能匹配到其他伪文件路径(如不含分隔符的随机字符串),需要进一步收紧。
295
+
296
+ **最终判定**:整体修复质量良好,git diff 解析修复彻底,循环计数修复正确。文本刮取过滤需要进一步优化。