@spaceflow/review 0.60.0 → 0.62.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.61.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.60.0...@spaceflow/review@0.61.0) (2026-03-03)
4
+
5
+ ### 代码重构
6
+
7
+ * **review:** 优化无效问题统计逻辑,排除已修复和已解决的问题 ([1de7b2a](https://github.com/Lydanne/spaceflow/commit/1de7b2a23fcc3ff73f679fc219342e111d96acf7))
8
+
9
+ ### 其他修改
10
+
11
+ * **review-summary:** released version 0.28.0 [no ci] ([e131ed8](https://github.com/Lydanne/spaceflow/commit/e131ed83528ef8b45b3e3edaac5dab3389812323))
12
+ * **scripts:** released version 0.25.0 [no ci] ([88292c0](https://github.com/Lydanne/spaceflow/commit/88292c07b7787bcd4492c8b88cfb516b3e81d9be))
13
+ * **shell:** released version 0.25.0 [no ci] ([fc78e10](https://github.com/Lydanne/spaceflow/commit/fc78e10a0bab04d575867732b922a2a1989a594e))
14
+
15
+ ## [0.60.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.59.0...@spaceflow/review@0.60.0) (2026-03-02)
16
+
17
+ ### 代码重构
18
+
19
+ * **review:** 优化问题验证逻辑,将 resolved 状态纳入有效性判断 ([1e2302d](https://github.com/Lydanne/spaceflow/commit/1e2302d81cd0f653d606483ef6c9138143ca6d60))
20
+
21
+ ### 代码格式
22
+
23
+ * 格式化代码并更新 Prettier 忽略规则 ([baed10e](https://github.com/Lydanne/spaceflow/commit/baed10e7cd91fda1285d7e2e0019d291cb563055))
24
+
25
+ ### 其他修改
26
+
27
+ * **core:** released version 0.23.0 [no ci] ([07a2d7d](https://github.com/Lydanne/spaceflow/commit/07a2d7d51223aeb98161f91fa931b4cb63b03cda))
28
+ * **publish:** released version 0.47.0 [no ci] ([3f59345](https://github.com/Lydanne/spaceflow/commit/3f593450066c9138adac5b101edb8057a5de1ff6))
29
+ * **review-summary:** released version 0.27.0 [no ci] ([90ac2a4](https://github.com/Lydanne/spaceflow/commit/90ac2a44706ddb2dd231ea57d3734c7445565ee9))
30
+ * **scripts:** released version 0.24.0 [no ci] ([717de65](https://github.com/Lydanne/spaceflow/commit/717de6571faa2cb24f04b7493e7fd6d8404f2bd5))
31
+ * **shell:** released version 0.24.0 [no ci] ([5694d19](https://github.com/Lydanne/spaceflow/commit/5694d193f9207e41e840d9ebaa5a43e3527e6af8))
32
+
3
33
  ## [0.59.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.58.0...@spaceflow/review@0.59.0) (2026-03-02)
4
34
 
5
35
  ### 新特性
package/dist/index.js CHANGED
@@ -1169,34 +1169,69 @@ class MarkdownFormatter {
1169
1169
  if (summaries.length === 0) {
1170
1170
  return "没有需要审查的文件";
1171
1171
  }
1172
+ // 🟢 已修复 | 🔴 待处理error | 🟡 待处理warn | ⚪ 已解决(非代码修复)
1172
1173
  const issuesByFile = new Map();
1173
1174
  for (const issue of issues){
1174
1175
  if (issue.valid === "false") continue;
1175
1176
  const stats = issuesByFile.get(issue.file) || {
1176
- resolved: 0,
1177
- errors: 0,
1178
- warns: 0
1177
+ fixed: 0,
1178
+ pendingErrors: 0,
1179
+ pendingWarns: 0,
1180
+ resolved: 0
1179
1181
  };
1180
1182
  if (issue.fixed) {
1183
+ stats.fixed++;
1184
+ } else if (issue.resolved) {
1181
1185
  stats.resolved++;
1182
1186
  } else if (issue.severity === "error") {
1183
- stats.errors++;
1187
+ stats.pendingErrors++;
1184
1188
  } else {
1185
- stats.warns++;
1189
+ stats.pendingWarns++;
1186
1190
  }
1187
1191
  issuesByFile.set(issue.file, stats);
1188
1192
  }
1189
1193
  const lines = [];
1190
- lines.push("| 文件 | 🟢 | 🔴 | 🟡 | 总结 |");
1191
- lines.push("|------|----|----|----|----|");
1194
+ lines.push("| 文件 | 总数 | 🟢 | 🔴 | 🟡 | |");
1195
+ lines.push("|------|------|----|----|----|-----|");
1196
+ // 汇总统计
1197
+ let totalAll = 0;
1198
+ let totalFixed = 0;
1199
+ let totalPendingErrors = 0;
1200
+ let totalPendingWarns = 0;
1201
+ let totalResolved = 0;
1202
+ const fileSummaryLines = [];
1192
1203
  for (const fileSummary of summaries){
1193
1204
  const stats = issuesByFile.get(fileSummary.file) || {
1194
- resolved: 0,
1195
- errors: 0,
1196
- warns: 0
1205
+ fixed: 0,
1206
+ pendingErrors: 0,
1207
+ pendingWarns: 0,
1208
+ resolved: 0
1197
1209
  };
1198
- const summaryText = fileSummary.summary.split("\n").filter((line)=>line.trim()).join("<br>");
1199
- lines.push(`| \`${fileSummary.file}\` | ${stats.resolved} | ${stats.errors} | ${stats.warns} | ${summaryText} |`);
1210
+ const fileTotal = stats.fixed + stats.pendingErrors + stats.pendingWarns + stats.resolved;
1211
+ totalAll += fileTotal;
1212
+ totalFixed += stats.fixed;
1213
+ totalPendingErrors += stats.pendingErrors;
1214
+ totalPendingWarns += stats.pendingWarns;
1215
+ totalResolved += stats.resolved;
1216
+ lines.push(`| \`${fileSummary.file}\` | ${fileTotal} | ${stats.fixed} | ${stats.pendingErrors} | ${stats.pendingWarns} | ${stats.resolved} |`);
1217
+ // 收集问题总结用于折叠块展示
1218
+ if (fileSummary.summary.trim()) {
1219
+ fileSummaryLines.push(`### \`${fileSummary.file}\``);
1220
+ fileSummaryLines.push(`${fileSummary.summary.trim()}`);
1221
+ fileSummaryLines.push("");
1222
+ }
1223
+ }
1224
+ // 添加汇总行
1225
+ if (summaries.length > 1) {
1226
+ lines.push(`| **总计** | **${totalAll}** | **${totalFixed}** | **${totalPendingErrors}** | **${totalPendingWarns}** | **${totalResolved}** |`);
1227
+ }
1228
+ // 问题总结放到折叠块中
1229
+ if (fileSummaryLines.length > 0) {
1230
+ lines.push("");
1231
+ lines.push("<details>");
1232
+ lines.push("<summary>📝 问题总结</summary>\n");
1233
+ lines.push(...fileSummaryLines);
1234
+ lines.push("</details>");
1200
1235
  }
1201
1236
  return lines.join("\n");
1202
1237
  }
@@ -1310,8 +1345,8 @@ class MarkdownFormatter {
1310
1345
  `|------|------|`
1311
1346
  ];
1312
1347
  lines.push(`| 总问题数 | ${stats.total} |`);
1313
- lines.push(`| 已修复 | ${stats.fixed} |`);
1314
- lines.push(`| 🟢 已解决 | ${stats.resolved} |`);
1348
+ lines.push(`| 🟢 已修复 | ${stats.fixed} |`);
1349
+ lines.push(`| 已解决 | ${stats.resolved} |`);
1315
1350
  lines.push(`| ❌ 无效 | ${stats.invalid} |`);
1316
1351
  lines.push(`| ⚠️ 待处理 | ${stats.pending} |`);
1317
1352
  lines.push(`| 修复率 | ${stats.fixRate}% |`);
@@ -1346,36 +1381,58 @@ class TerminalFormatter {
1346
1381
  if (summaries.length === 0) {
1347
1382
  return "没有需要审查的文件";
1348
1383
  }
1384
+ // 🟢 已修复 | 🔴 待处理error | 🟡 待处理warn | ⚪ 已解决(非代码修复)
1349
1385
  const issuesByFile = new Map();
1350
1386
  for (const issue of issues){
1387
+ if (issue.valid === "false") continue;
1351
1388
  const stats = issuesByFile.get(issue.file) || {
1352
- resolved: 0,
1353
- errors: 0,
1354
- warns: 0
1389
+ fixed: 0,
1390
+ pendingErrors: 0,
1391
+ pendingWarns: 0,
1392
+ resolved: 0
1355
1393
  };
1356
1394
  if (issue.fixed) {
1395
+ stats.fixed++;
1396
+ } else if (issue.resolved) {
1357
1397
  stats.resolved++;
1358
1398
  } else if (issue.severity === "error") {
1359
- stats.errors++;
1399
+ stats.pendingErrors++;
1360
1400
  } else {
1361
- stats.warns++;
1401
+ stats.pendingWarns++;
1362
1402
  }
1363
1403
  issuesByFile.set(issue.file, stats);
1364
1404
  }
1405
+ // 汇总统计
1406
+ let totalAll = 0;
1407
+ let totalFixed = 0;
1408
+ let totalPendingErrors = 0;
1409
+ let totalPendingWarns = 0;
1410
+ let totalResolved = 0;
1365
1411
  const lines = [];
1366
1412
  for (const fileSummary of summaries){
1367
1413
  const stats = issuesByFile.get(fileSummary.file) || {
1368
- resolved: 0,
1369
- errors: 0,
1370
- warns: 0
1414
+ fixed: 0,
1415
+ pendingErrors: 0,
1416
+ pendingWarns: 0,
1417
+ resolved: 0
1371
1418
  };
1372
- const resolvedText = stats.resolved > 0 ? `${GREEN}✅ ${stats.resolved} 已解决${RESET}` : "";
1373
- const errorText = stats.errors > 0 ? `${RED}🔴 ${stats.errors} error${RESET}` : "";
1374
- const warnText = stats.warns > 0 ? `${YELLOW}🟡 ${stats.warns} warn${RESET}` : "";
1419
+ const fileTotal = stats.fixed + stats.pendingErrors + stats.pendingWarns + stats.resolved;
1420
+ totalAll += fileTotal;
1421
+ totalFixed += stats.fixed;
1422
+ totalPendingErrors += stats.pendingErrors;
1423
+ totalPendingWarns += stats.pendingWarns;
1424
+ totalResolved += stats.resolved;
1425
+ const totalText = fileTotal > 0 ? `${BOLD}${fileTotal} 问题${RESET}` : "";
1426
+ const fixedText = stats.fixed > 0 ? `${GREEN}🟢 ${stats.fixed} 已修复${RESET}` : "";
1427
+ const errorText = stats.pendingErrors > 0 ? `${RED}🔴 ${stats.pendingErrors} error${RESET}` : "";
1428
+ const warnText = stats.pendingWarns > 0 ? `${YELLOW}🟡 ${stats.pendingWarns} warn${RESET}` : "";
1429
+ const resolvedText = stats.resolved > 0 ? `⚪ ${stats.resolved} 已解决` : "";
1375
1430
  const statsText = [
1376
- resolvedText,
1431
+ totalText,
1432
+ fixedText,
1377
1433
  errorText,
1378
- warnText
1434
+ warnText,
1435
+ resolvedText
1379
1436
  ].filter(Boolean).join(" / ");
1380
1437
  if (statsText) {
1381
1438
  lines.push(`${BOLD}${fileSummary.file}${RESET} (${statsText}): ${fileSummary.summary}`);
@@ -1383,6 +1440,18 @@ class TerminalFormatter {
1383
1440
  lines.push(`${BOLD}${fileSummary.file}${RESET}: ${fileSummary.summary}`);
1384
1441
  }
1385
1442
  }
1443
+ // 添加汇总行
1444
+ if (summaries.length > 1) {
1445
+ lines.push("");
1446
+ const summaryParts = [
1447
+ `${BOLD}总计: ${totalAll} 问题${RESET}`
1448
+ ];
1449
+ if (totalFixed > 0) summaryParts.push(`${GREEN}🟢 ${totalFixed} 已修复${RESET}`);
1450
+ if (totalPendingErrors > 0) summaryParts.push(`${RED}🔴 ${totalPendingErrors} error${RESET}`);
1451
+ if (totalPendingWarns > 0) summaryParts.push(`${YELLOW}🟡 ${totalPendingWarns} warn${RESET}`);
1452
+ if (totalResolved > 0) summaryParts.push(`⚪ ${totalResolved} 已解决`);
1453
+ lines.push(summaryParts.join(" / "));
1454
+ }
1386
1455
  return lines.join("\n");
1387
1456
  }
1388
1457
  format(result, _options = {}) {
@@ -1437,8 +1506,8 @@ class TerminalFormatter {
1437
1506
  `\n${BOLD}${CYAN}📊 ${title}:${RESET}`
1438
1507
  ];
1439
1508
  lines.push(` 总问题数: ${stats.total}`);
1440
- lines.push(` ${GREEN} 已修复: ${stats.fixed}${RESET}`);
1441
- lines.push(` ${GREEN}🟢 已解决: ${stats.resolved}${RESET}`);
1509
+ lines.push(` ${GREEN}🟢 已修复: ${stats.fixed}${RESET}`);
1510
+ lines.push(` 已解决: ${stats.resolved}`);
1442
1511
  lines.push(` ${RED}❌ 无效: ${stats.invalid}${RESET}`);
1443
1512
  lines.push(` ${YELLOW}⚠️ 待处理: ${stats.pending}${RESET}`);
1444
1513
  lines.push(` 修复率: ${stats.fixRate}%`);
@@ -2242,6 +2311,8 @@ class ReviewService {
2242
2311
  if (shouldLog(verbose, 1)) {
2243
2312
  console.log(`📋 已有评论中存在 ${existingIssues.length} 个问题`);
2244
2313
  }
2314
+ // 先同步最新的 resolved 状态,确保后续 invalidate/verify 能正确跳过已解决的问题
2315
+ await this.syncResolvedComments(owner, repo, prNumber, existingResult);
2245
2316
  // 如果文件有变更,将该文件的历史问题标记为无效
2246
2317
  // 简化策略:避免复杂的行号更新逻辑
2247
2318
  const reviewConf = this.config.getPluginConfig("review");
@@ -2451,7 +2522,7 @@ class ReviewService {
2451
2522
  const total = issues.length;
2452
2523
  const fixed = issues.filter((i)=>i.fixed).length;
2453
2524
  const resolved = issues.filter((i)=>i.resolved && !i.fixed).length;
2454
- const invalid = issues.filter((i)=>i.valid === "false").length;
2525
+ const invalid = issues.filter((i)=>i.valid === "false" && !i.fixed && !i.resolved).length;
2455
2526
  const pending = total - fixed - resolved - invalid;
2456
2527
  const fixRate = total > 0 ? Math.round(fixed / total * 100 * 10) / 10 : 0;
2457
2528
  const resolveRate = total > 0 ? Math.round((fixed + resolved) / total * 100 * 10) / 10 : 0;
@@ -3267,7 +3338,7 @@ ${fileChanges || "无"}`;
3267
3338
  console.warn("⚠️ 更新 PR 标题失败:", error);
3268
3339
  }
3269
3340
  }
3270
- // 获取已解决的评论,同步 fixed 状态(在更新 review 之前)
3341
+ // 获取已解决的评论,同步 resolve 状态(在更新 review 之前)
3271
3342
  await this.syncResolvedComments(owner, repo, prNumber, result);
3272
3343
  // 获取评论的 reactions,同步 valid 状态(👎 标记为无效)
3273
3344
  await this.syncReactionsToIssues(owner, repo, prNumber, result, verbose);
@@ -3703,12 +3774,16 @@ ${fileChanges || "无"}`;
3703
3774
  /**
3704
3775
  * 构建行级评论 Review 的 body(marker + 本轮统计 + 上轮回顾)
3705
3776
  */ buildLineReviewBody(issues, round, allIssues) {
3706
- const errorCount = issues.filter((i)=>i.severity === "error").length;
3707
- const warnCount = issues.filter((i)=>i.severity === "warn").length;
3777
+ // 只统计待处理的问题(未修复且未解决)
3778
+ const pendingIssues = issues.filter((i)=>!i.fixed && !i.resolved && i.valid !== "false");
3779
+ const pendingErrors = pendingIssues.filter((i)=>i.severity === "error").length;
3780
+ const pendingWarns = pendingIssues.filter((i)=>i.severity === "warn").length;
3708
3781
  const fileCount = new Set(issues.map((i)=>i.file)).size;
3782
+ const totalPending = pendingErrors + pendingWarns;
3709
3783
  const badges = [];
3710
- if (errorCount > 0) badges.push(`🔴 ${errorCount}`);
3711
- if (warnCount > 0) badges.push(`🟡 ${warnCount}`);
3784
+ if (totalPending > 0) badges.push(`⚠️ ${totalPending}`);
3785
+ if (pendingErrors > 0) badges.push(`🔴 ${pendingErrors}`);
3786
+ if (pendingWarns > 0) badges.push(`🟡 ${pendingWarns}`);
3712
3787
  const parts = [
3713
3788
  REVIEW_LINE_COMMENTS_MARKER
3714
3789
  ];
@@ -3724,14 +3799,14 @@ ${fileChanges || "无"}`;
3724
3799
  if (prevIssues.length > 0) {
3725
3800
  const prevFixed = prevIssues.filter((i)=>i.fixed).length;
3726
3801
  const prevResolved = prevIssues.filter((i)=>i.resolved && !i.fixed).length;
3727
- const prevInvalid = prevIssues.filter((i)=>i.valid === "false").length;
3802
+ const prevInvalid = prevIssues.filter((i)=>i.valid === "false" && !i.fixed && !i.resolved).length;
3728
3803
  const prevPending = prevIssues.length - prevFixed - prevResolved - prevInvalid;
3729
3804
  parts.push("");
3730
3805
  parts.push(`<details><summary>📊 Round ${round - 1} 回顾 (${prevIssues.length} 个问题)</summary>\n`);
3731
3806
  parts.push(`| 状态 | 数量 |`);
3732
3807
  parts.push(`|------|------|`);
3733
- if (prevFixed > 0) parts.push(`| 已修复 | ${prevFixed} |`);
3734
- if (prevResolved > 0) parts.push(`| 🟢 已解决 | ${prevResolved} |`);
3808
+ if (prevFixed > 0) parts.push(`| 🟢 已修复 | ${prevFixed} |`);
3809
+ if (prevResolved > 0) parts.push(`| 已解决 | ${prevResolved} |`);
3735
3810
  if (prevInvalid > 0) parts.push(`| ❌ 无效 | ${prevInvalid} |`);
3736
3811
  if (prevPending > 0) parts.push(`| ⚠️ 待处理 | ${prevPending} |`);
3737
3812
  parts.push(`\n</details>`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaceflow/review",
3
- "version": "0.60.0",
3
+ "version": "0.62.0",
4
4
  "description": "Spaceflow 代码审查插件,使用 LLM 对 PR 代码进行自动审查",
5
5
  "license": "MIT",
6
6
  "author": "Lydanne",
@@ -130,35 +130,85 @@ export class MarkdownFormatter implements ReviewReportFormatter, ReviewReportPar
130
130
  return "没有需要审查的文件";
131
131
  }
132
132
 
133
- const issuesByFile = new Map<string, { resolved: number; errors: number; warns: number }>();
133
+ // 🟢 已修复 | 🔴 待处理error | 🟡 待处理warn | 已解决(非代码修复)
134
+ const issuesByFile = new Map<
135
+ string,
136
+ { fixed: number; pendingErrors: number; pendingWarns: number; resolved: number }
137
+ >();
134
138
  for (const issue of issues) {
135
139
  if (issue.valid === "false") continue;
136
- const stats = issuesByFile.get(issue.file) || { resolved: 0, errors: 0, warns: 0 };
140
+ const stats = issuesByFile.get(issue.file) || {
141
+ fixed: 0,
142
+ pendingErrors: 0,
143
+ pendingWarns: 0,
144
+ resolved: 0,
145
+ };
137
146
  if (issue.fixed) {
147
+ stats.fixed++;
148
+ } else if (issue.resolved) {
138
149
  stats.resolved++;
139
150
  } else if (issue.severity === "error") {
140
- stats.errors++;
151
+ stats.pendingErrors++;
141
152
  } else {
142
- stats.warns++;
153
+ stats.pendingWarns++;
143
154
  }
144
155
  issuesByFile.set(issue.file, stats);
145
156
  }
146
157
 
147
158
  const lines: string[] = [];
148
- lines.push("| 文件 | 🟢 | 🔴 | 🟡 | 总结 |");
149
- lines.push("|------|----|----|----|----|");
159
+ lines.push("| 文件 | 总数 | 🟢 | 🔴 | 🟡 | |");
160
+ lines.push("|------|------|----|----|----|-----|");
161
+
162
+ // 汇总统计
163
+ let totalAll = 0;
164
+ let totalFixed = 0;
165
+ let totalPendingErrors = 0;
166
+ let totalPendingWarns = 0;
167
+ let totalResolved = 0;
150
168
 
169
+ const fileSummaryLines: string[] = [];
151
170
  for (const fileSummary of summaries) {
152
- const stats = issuesByFile.get(fileSummary.file) || { resolved: 0, errors: 0, warns: 0 };
153
- const summaryText = fileSummary.summary
154
- .split("\n")
155
- .filter((line) => line.trim())
156
- .join("<br>");
171
+ const stats = issuesByFile.get(fileSummary.file) || {
172
+ fixed: 0,
173
+ pendingErrors: 0,
174
+ pendingWarns: 0,
175
+ resolved: 0,
176
+ };
177
+ const fileTotal = stats.fixed + stats.pendingErrors + stats.pendingWarns + stats.resolved;
178
+ totalAll += fileTotal;
179
+ totalFixed += stats.fixed;
180
+ totalPendingErrors += stats.pendingErrors;
181
+ totalPendingWarns += stats.pendingWarns;
182
+ totalResolved += stats.resolved;
183
+
184
+ lines.push(
185
+ `| \`${fileSummary.file}\` | ${fileTotal} | ${stats.fixed} | ${stats.pendingErrors} | ${stats.pendingWarns} | ${stats.resolved} |`,
186
+ );
187
+
188
+ // 收集问题总结用于折叠块展示
189
+ if (fileSummary.summary.trim()) {
190
+ fileSummaryLines.push(`### \`${fileSummary.file}\``);
191
+ fileSummaryLines.push(`${fileSummary.summary.trim()}`);
192
+ fileSummaryLines.push("");
193
+ }
194
+ }
195
+
196
+ // 添加汇总行
197
+ if (summaries.length > 1) {
157
198
  lines.push(
158
- `| \`${fileSummary.file}\` | ${stats.resolved} | ${stats.errors} | ${stats.warns} | ${summaryText} |`,
199
+ `| **总计** | **${totalAll}** | **${totalFixed}** | **${totalPendingErrors}** | **${totalPendingWarns}** | **${totalResolved}** |`,
159
200
  );
160
201
  }
161
202
 
203
+ // 问题总结放到折叠块中
204
+ if (fileSummaryLines.length > 0) {
205
+ lines.push("");
206
+ lines.push("<details>");
207
+ lines.push("<summary>📝 问题总结</summary>\n");
208
+ lines.push(...fileSummaryLines);
209
+ lines.push("</details>");
210
+ }
211
+
162
212
  return lines.join("\n");
163
213
  }
164
214
 
@@ -289,8 +339,8 @@ export class MarkdownFormatter implements ReviewReportFormatter, ReviewReportPar
289
339
  const title = prNumber ? `PR #${prNumber} Review 状态统计` : "Review 状态统计";
290
340
  const lines = [`## 📊 ${title}\n`, `| 指标 | 数量 |`, `|------|------|`];
291
341
  lines.push(`| 总问题数 | ${stats.total} |`);
292
- lines.push(`| 已修复 | ${stats.fixed} |`);
293
- lines.push(`| 🟢 已解决 | ${stats.resolved} |`);
342
+ lines.push(`| 🟢 已修复 | ${stats.fixed} |`);
343
+ lines.push(`| 已解决 | ${stats.resolved} |`);
294
344
  lines.push(`| ❌ 无效 | ${stats.invalid} |`);
295
345
  lines.push(`| ⚠️ 待处理 | ${stats.pending} |`);
296
346
  lines.push(`| 修复率 | ${stats.fixRate}% |`);
@@ -28,26 +28,63 @@ export class TerminalFormatter implements ReviewReportFormatter {
28
28
  return "没有需要审查的文件";
29
29
  }
30
30
 
31
- const issuesByFile = new Map<string, { resolved: number; errors: number; warns: number }>();
31
+ // 🟢 已修复 | 🔴 待处理error | 🟡 待处理warn | 已解决(非代码修复)
32
+ const issuesByFile = new Map<
33
+ string,
34
+ { fixed: number; pendingErrors: number; pendingWarns: number; resolved: number }
35
+ >();
32
36
  for (const issue of issues) {
33
- const stats = issuesByFile.get(issue.file) || { resolved: 0, errors: 0, warns: 0 };
37
+ if (issue.valid === "false") continue;
38
+ const stats = issuesByFile.get(issue.file) || {
39
+ fixed: 0,
40
+ pendingErrors: 0,
41
+ pendingWarns: 0,
42
+ resolved: 0,
43
+ };
34
44
  if (issue.fixed) {
45
+ stats.fixed++;
46
+ } else if (issue.resolved) {
35
47
  stats.resolved++;
36
48
  } else if (issue.severity === "error") {
37
- stats.errors++;
49
+ stats.pendingErrors++;
38
50
  } else {
39
- stats.warns++;
51
+ stats.pendingWarns++;
40
52
  }
41
53
  issuesByFile.set(issue.file, stats);
42
54
  }
43
55
 
56
+ // 汇总统计
57
+ let totalAll = 0;
58
+ let totalFixed = 0;
59
+ let totalPendingErrors = 0;
60
+ let totalPendingWarns = 0;
61
+ let totalResolved = 0;
62
+
44
63
  const lines: string[] = [];
45
64
  for (const fileSummary of summaries) {
46
- const stats = issuesByFile.get(fileSummary.file) || { resolved: 0, errors: 0, warns: 0 };
47
- const resolvedText = stats.resolved > 0 ? `${GREEN}✅ ${stats.resolved} 已解决${RESET}` : "";
48
- const errorText = stats.errors > 0 ? `${RED}🔴 ${stats.errors} error${RESET}` : "";
49
- const warnText = stats.warns > 0 ? `${YELLOW}🟡 ${stats.warns} warn${RESET}` : "";
50
- const statsText = [resolvedText, errorText, warnText].filter(Boolean).join(" / ");
65
+ const stats = issuesByFile.get(fileSummary.file) || {
66
+ fixed: 0,
67
+ pendingErrors: 0,
68
+ pendingWarns: 0,
69
+ resolved: 0,
70
+ };
71
+ const fileTotal = stats.fixed + stats.pendingErrors + stats.pendingWarns + stats.resolved;
72
+ totalAll += fileTotal;
73
+ totalFixed += stats.fixed;
74
+ totalPendingErrors += stats.pendingErrors;
75
+ totalPendingWarns += stats.pendingWarns;
76
+ totalResolved += stats.resolved;
77
+
78
+ const totalText = fileTotal > 0 ? `${BOLD}${fileTotal} 问题${RESET}` : "";
79
+ const fixedText = stats.fixed > 0 ? `${GREEN}🟢 ${stats.fixed} 已修复${RESET}` : "";
80
+ const errorText =
81
+ stats.pendingErrors > 0 ? `${RED}🔴 ${stats.pendingErrors} error${RESET}` : "";
82
+ const warnText =
83
+ stats.pendingWarns > 0 ? `${YELLOW}🟡 ${stats.pendingWarns} warn${RESET}` : "";
84
+ const resolvedText = stats.resolved > 0 ? `⚪ ${stats.resolved} 已解决` : "";
85
+ const statsText = [totalText, fixedText, errorText, warnText, resolvedText]
86
+ .filter(Boolean)
87
+ .join(" / ");
51
88
 
52
89
  if (statsText) {
53
90
  lines.push(`${BOLD}${fileSummary.file}${RESET} (${statsText}): ${fileSummary.summary}`);
@@ -56,6 +93,17 @@ export class TerminalFormatter implements ReviewReportFormatter {
56
93
  }
57
94
  }
58
95
 
96
+ // 添加汇总行
97
+ if (summaries.length > 1) {
98
+ lines.push("");
99
+ const summaryParts = [`${BOLD}总计: ${totalAll} 问题${RESET}`];
100
+ if (totalFixed > 0) summaryParts.push(`${GREEN}🟢 ${totalFixed} 已修复${RESET}`);
101
+ if (totalPendingErrors > 0) summaryParts.push(`${RED}🔴 ${totalPendingErrors} error${RESET}`);
102
+ if (totalPendingWarns > 0) summaryParts.push(`${YELLOW}🟡 ${totalPendingWarns} warn${RESET}`);
103
+ if (totalResolved > 0) summaryParts.push(`⚪ ${totalResolved} 已解决`);
104
+ lines.push(summaryParts.join(" / "));
105
+ }
106
+
59
107
  return lines.join("\n");
60
108
  }
61
109
 
@@ -123,8 +171,8 @@ export class TerminalFormatter implements ReviewReportFormatter {
123
171
  const title = prNumber ? `PR #${prNumber} Review 状态统计` : "Review 状态统计";
124
172
  const lines = [`\n${BOLD}${CYAN}📊 ${title}:${RESET}`];
125
173
  lines.push(` 总问题数: ${stats.total}`);
126
- lines.push(` ${GREEN} 已修复: ${stats.fixed}${RESET}`);
127
- lines.push(` ${GREEN}🟢 已解决: ${stats.resolved}${RESET}`);
174
+ lines.push(` ${GREEN}🟢 已修复: ${stats.fixed}${RESET}`);
175
+ lines.push(` 已解决: ${stats.resolved}`);
128
176
  lines.push(` ${RED}❌ 无效: ${stats.invalid}${RESET}`);
129
177
  lines.push(` ${YELLOW}⚠️ 待处理: ${stats.pending}${RESET}`);
130
178
  lines.push(` 修复率: ${stats.fixRate}%`);
@@ -678,6 +678,9 @@ export class ReviewService {
678
678
  console.log(`📋 已有评论中存在 ${existingIssues.length} 个问题`);
679
679
  }
680
680
 
681
+ // 先同步最新的 resolved 状态,确保后续 invalidate/verify 能正确跳过已解决的问题
682
+ await this.syncResolvedComments(owner, repo, prNumber, existingResult);
683
+
681
684
  // 如果文件有变更,将该文件的历史问题标记为无效
682
685
  // 简化策略:避免复杂的行号更新逻辑
683
686
  const reviewConf = this.config.getPluginConfig<ReviewConfig>("review");
@@ -978,7 +981,7 @@ export class ReviewService {
978
981
  const total = issues.length;
979
982
  const fixed = issues.filter((i) => i.fixed).length;
980
983
  const resolved = issues.filter((i) => i.resolved && !i.fixed).length;
981
- const invalid = issues.filter((i) => i.valid === "false").length;
984
+ const invalid = issues.filter((i) => i.valid === "false" && !i.fixed && !i.resolved).length;
982
985
  const pending = total - fixed - resolved - invalid;
983
986
  const fixRate = total > 0 ? Math.round((fixed / total) * 100 * 10) / 10 : 0;
984
987
  const resolveRate = total > 0 ? Math.round(((fixed + resolved) / total) * 100 * 10) / 10 : 0;
@@ -1953,7 +1956,7 @@ ${fileChanges || "无"}`;
1953
1956
  }
1954
1957
  }
1955
1958
 
1956
- // 获取已解决的评论,同步 fixed 状态(在更新 review 之前)
1959
+ // 获取已解决的评论,同步 resolve 状态(在更新 review 之前)
1957
1960
  await this.syncResolvedComments(owner, repo, prNumber, result);
1958
1961
 
1959
1962
  // 获取评论的 reactions,同步 valid 状态(👎 标记为无效)
@@ -2503,13 +2506,17 @@ ${fileChanges || "无"}`;
2503
2506
  round: number,
2504
2507
  allIssues: ReviewIssue[],
2505
2508
  ): string {
2506
- const errorCount = issues.filter((i) => i.severity === "error").length;
2507
- const warnCount = issues.filter((i) => i.severity === "warn").length;
2509
+ // 只统计待处理的问题(未修复且未解决)
2510
+ const pendingIssues = issues.filter((i) => !i.fixed && !i.resolved && i.valid !== "false");
2511
+ const pendingErrors = pendingIssues.filter((i) => i.severity === "error").length;
2512
+ const pendingWarns = pendingIssues.filter((i) => i.severity === "warn").length;
2508
2513
  const fileCount = new Set(issues.map((i) => i.file)).size;
2509
2514
 
2515
+ const totalPending = pendingErrors + pendingWarns;
2510
2516
  const badges: string[] = [];
2511
- if (errorCount > 0) badges.push(`🔴 ${errorCount}`);
2512
- if (warnCount > 0) badges.push(`🟡 ${warnCount}`);
2517
+ if (totalPending > 0) badges.push(`⚠️ ${totalPending}`);
2518
+ if (pendingErrors > 0) badges.push(`🔴 ${pendingErrors}`);
2519
+ if (pendingWarns > 0) badges.push(`🟡 ${pendingWarns}`);
2513
2520
 
2514
2521
  const parts: string[] = [REVIEW_LINE_COMMENTS_MARKER];
2515
2522
  parts.push(`### 🚀 Spaceflow Review · Round ${round}`);
@@ -2527,7 +2534,9 @@ ${fileChanges || "无"}`;
2527
2534
  if (prevIssues.length > 0) {
2528
2535
  const prevFixed = prevIssues.filter((i) => i.fixed).length;
2529
2536
  const prevResolved = prevIssues.filter((i) => i.resolved && !i.fixed).length;
2530
- const prevInvalid = prevIssues.filter((i) => i.valid === "false").length;
2537
+ const prevInvalid = prevIssues.filter(
2538
+ (i) => i.valid === "false" && !i.fixed && !i.resolved,
2539
+ ).length;
2531
2540
  const prevPending = prevIssues.length - prevFixed - prevResolved - prevInvalid;
2532
2541
  parts.push("");
2533
2542
  parts.push(
@@ -2535,8 +2544,8 @@ ${fileChanges || "无"}`;
2535
2544
  );
2536
2545
  parts.push(`| 状态 | 数量 |`);
2537
2546
  parts.push(`|------|------|`);
2538
- if (prevFixed > 0) parts.push(`| 已修复 | ${prevFixed} |`);
2539
- if (prevResolved > 0) parts.push(`| 🟢 已解决 | ${prevResolved} |`);
2547
+ if (prevFixed > 0) parts.push(`| 🟢 已修复 | ${prevFixed} |`);
2548
+ if (prevResolved > 0) parts.push(`| 已解决 | ${prevResolved} |`);
2540
2549
  if (prevInvalid > 0) parts.push(`| ❌ 无效 | ${prevInvalid} |`);
2541
2550
  if (prevPending > 0) parts.push(`| ⚠️ 待处理 | ${prevPending} |`);
2542
2551
  parts.push(`\n</details>`);