@spaceflow/review 0.67.0 → 0.69.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 +33 -0
- package/dist/index.js +66 -43
- package/package.json +1 -1
- package/src/issue-verify.service.ts +2 -0
- package/src/review-report/formatters/markdown.formatter.ts +25 -15
- package/src/review-report/formatters/terminal.formatter.ts +26 -18
- package/src/review-spec/types.ts +2 -2
- package/src/review.service.ts +29 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.68.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.67.0...@spaceflow/review@0.68.0) (2026-03-04)
|
|
4
|
+
|
|
5
|
+
### 代码重构
|
|
6
|
+
|
|
7
|
+
* **review:** 区分 ☹️ 和 👎 reaction 的语义,☹️ 标记无效,👎 标记未解决 ([f1419fe](https://github.com/Lydanne/spaceflow/commit/f1419fe47448a80f373ffac082ac3a2e9320d200))
|
|
8
|
+
|
|
9
|
+
### 其他修改
|
|
10
|
+
|
|
11
|
+
* **review-summary:** released version 0.35.0 [no ci] ([4f2607d](https://github.com/Lydanne/spaceflow/commit/4f2607def2725946f32eccc4aa4e687a3cdd9bab))
|
|
12
|
+
* **scripts:** released version 0.28.0 [no ci] ([55db5cf](https://github.com/Lydanne/spaceflow/commit/55db5cfa1dc0a1e318085caa0cfd9f91b06dcb21))
|
|
13
|
+
* **shell:** released version 0.28.0 [no ci] ([01f180f](https://github.com/Lydanne/spaceflow/commit/01f180f2508e75524a33e66fea580a738adc689f))
|
|
14
|
+
|
|
15
|
+
## [0.67.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.66.0...@spaceflow/review@0.67.0) (2026-03-03)
|
|
16
|
+
|
|
17
|
+
### 新特性
|
|
18
|
+
|
|
19
|
+
* **shared:** 新增获取扩展依赖函数,过滤内部包 ([86f4864](https://github.com/Lydanne/spaceflow/commit/86f48646c790285797b53c63a463d5134f7ac6b6))
|
|
20
|
+
|
|
21
|
+
### 代码重构
|
|
22
|
+
|
|
23
|
+
* **cli:** 重构外部扩展包读取逻辑,使用专用函数 ([d15054d](https://github.com/Lydanne/spaceflow/commit/d15054d81335a3b0c55db1acd169a2d9edf32a94))
|
|
24
|
+
* **core:** 统一依赖获取函数命名,将 getDependencies 重命名为 getExtensionDependencies ([ff78202](https://github.com/Lydanne/spaceflow/commit/ff78202be9975ce6721e4ecbbd6b1e02f8a57b70))
|
|
25
|
+
|
|
26
|
+
### 其他修改
|
|
27
|
+
|
|
28
|
+
* **cli:** released version 0.40.0 [no ci] ([bec5724](https://github.com/Lydanne/spaceflow/commit/bec5724745e8d33a06a00be452ec647bd82be934))
|
|
29
|
+
* **core:** released version 0.26.0 [no ci] ([cc597b0](https://github.com/Lydanne/spaceflow/commit/cc597b000bae00885de32eb2ad4287805de7ce5a))
|
|
30
|
+
* **publish:** released version 0.50.0 [no ci] ([8020040](https://github.com/Lydanne/spaceflow/commit/802004073c276705868ffbef4d5daae6a0068d39))
|
|
31
|
+
* **review-summary:** released version 0.34.0 [no ci] ([3565bc4](https://github.com/Lydanne/spaceflow/commit/3565bc426469fa8215d70fb3bbd2a9ee9c128916))
|
|
32
|
+
* **scripts:** released version 0.27.0 [no ci] ([8c4111a](https://github.com/Lydanne/spaceflow/commit/8c4111a73c472fab084030d547656ba5784e05b2))
|
|
33
|
+
* **shared:** released version 0.8.0 [no ci] ([c3fe353](https://github.com/Lydanne/spaceflow/commit/c3fe3536ba0adca45f610fa2e8697bfccf842efe))
|
|
34
|
+
* **shell:** released version 0.27.0 [no ci] ([c4501c2](https://github.com/Lydanne/spaceflow/commit/c4501c284de780c97a5a1cfc5b413ae8393acb91))
|
|
35
|
+
|
|
3
36
|
## [0.66.0](https://github.com/Lydanne/spaceflow/compare/@spaceflow/review@0.65.0...@spaceflow/review@0.66.0) (2026-03-03)
|
|
4
37
|
|
|
5
38
|
### 新特性
|
package/dist/index.js
CHANGED
|
@@ -1169,24 +1169,28 @@ class MarkdownFormatter {
|
|
|
1169
1169
|
if (summaries.length === 0) {
|
|
1170
1170
|
return "没有需要审查的文件";
|
|
1171
1171
|
}
|
|
1172
|
-
// 🟢 已修复 | 🔴
|
|
1172
|
+
// 🟢 已修复 | 🔴 error数量 | 🟡 warn数量 | ⚪ 已解决(非代码修复)
|
|
1173
1173
|
const issuesByFile = new Map();
|
|
1174
1174
|
for (const issue of issues){
|
|
1175
1175
|
if (issue.valid === "false") continue;
|
|
1176
1176
|
const stats = issuesByFile.get(issue.file) || {
|
|
1177
|
+
total: 0,
|
|
1177
1178
|
fixed: 0,
|
|
1178
|
-
|
|
1179
|
-
|
|
1179
|
+
errorCount: 0,
|
|
1180
|
+
warnCount: 0,
|
|
1180
1181
|
resolved: 0
|
|
1181
1182
|
};
|
|
1183
|
+
stats.total++;
|
|
1182
1184
|
if (issue.fixed) {
|
|
1183
1185
|
stats.fixed++;
|
|
1184
|
-
}
|
|
1186
|
+
}
|
|
1187
|
+
if (issue.resolved) {
|
|
1185
1188
|
stats.resolved++;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1189
|
+
}
|
|
1190
|
+
if (issue.severity === "error") {
|
|
1191
|
+
stats.errorCount++;
|
|
1188
1192
|
} else {
|
|
1189
|
-
stats.
|
|
1193
|
+
stats.warnCount++;
|
|
1190
1194
|
}
|
|
1191
1195
|
issuesByFile.set(issue.file, stats);
|
|
1192
1196
|
}
|
|
@@ -1202,18 +1206,18 @@ class MarkdownFormatter {
|
|
|
1202
1206
|
const fileSummaryLines = [];
|
|
1203
1207
|
for (const fileSummary of summaries){
|
|
1204
1208
|
const stats = issuesByFile.get(fileSummary.file) || {
|
|
1209
|
+
total: 0,
|
|
1205
1210
|
fixed: 0,
|
|
1206
|
-
|
|
1207
|
-
|
|
1211
|
+
errorCount: 0,
|
|
1212
|
+
warnCount: 0,
|
|
1208
1213
|
resolved: 0
|
|
1209
1214
|
};
|
|
1210
|
-
|
|
1211
|
-
totalAll += fileTotal;
|
|
1215
|
+
totalAll += stats.total;
|
|
1212
1216
|
totalFixed += stats.fixed;
|
|
1213
|
-
totalPendingErrors += stats.
|
|
1214
|
-
totalPendingWarns += stats.
|
|
1217
|
+
totalPendingErrors += stats.errorCount;
|
|
1218
|
+
totalPendingWarns += stats.warnCount;
|
|
1215
1219
|
totalResolved += stats.resolved;
|
|
1216
|
-
lines.push(`| \`${fileSummary.file}\` | ${
|
|
1220
|
+
lines.push(`| \`${fileSummary.file}\` | ${stats.total} | ${stats.fixed} | ${stats.errorCount} | ${stats.warnCount} | ${stats.resolved} |`);
|
|
1217
1221
|
// 收集问题总结用于折叠块展示
|
|
1218
1222
|
if (fileSummary.summary.trim()) {
|
|
1219
1223
|
fileSummaryLines.push(`### 💡 \`${fileSummary.file}\``);
|
|
@@ -1386,19 +1390,23 @@ class TerminalFormatter {
|
|
|
1386
1390
|
for (const issue of issues){
|
|
1387
1391
|
if (issue.valid === "false") continue;
|
|
1388
1392
|
const stats = issuesByFile.get(issue.file) || {
|
|
1393
|
+
total: 0,
|
|
1389
1394
|
fixed: 0,
|
|
1390
|
-
|
|
1391
|
-
|
|
1395
|
+
errorCount: 0,
|
|
1396
|
+
warnCount: 0,
|
|
1392
1397
|
resolved: 0
|
|
1393
1398
|
};
|
|
1399
|
+
stats.total++;
|
|
1394
1400
|
if (issue.fixed) {
|
|
1395
1401
|
stats.fixed++;
|
|
1396
|
-
}
|
|
1402
|
+
}
|
|
1403
|
+
if (issue.resolved) {
|
|
1397
1404
|
stats.resolved++;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1405
|
+
}
|
|
1406
|
+
if (issue.severity === "error") {
|
|
1407
|
+
stats.errorCount++;
|
|
1400
1408
|
} else {
|
|
1401
|
-
stats.
|
|
1409
|
+
stats.warnCount++;
|
|
1402
1410
|
}
|
|
1403
1411
|
issuesByFile.set(issue.file, stats);
|
|
1404
1412
|
}
|
|
@@ -1411,21 +1419,21 @@ class TerminalFormatter {
|
|
|
1411
1419
|
const lines = [];
|
|
1412
1420
|
for (const fileSummary of summaries){
|
|
1413
1421
|
const stats = issuesByFile.get(fileSummary.file) || {
|
|
1422
|
+
total: 0,
|
|
1414
1423
|
fixed: 0,
|
|
1415
|
-
|
|
1416
|
-
|
|
1424
|
+
errorCount: 0,
|
|
1425
|
+
warnCount: 0,
|
|
1417
1426
|
resolved: 0
|
|
1418
1427
|
};
|
|
1419
|
-
|
|
1420
|
-
totalAll += fileTotal;
|
|
1428
|
+
totalAll += stats.total;
|
|
1421
1429
|
totalFixed += stats.fixed;
|
|
1422
|
-
totalPendingErrors += stats.
|
|
1423
|
-
totalPendingWarns += stats.
|
|
1430
|
+
totalPendingErrors += stats.errorCount;
|
|
1431
|
+
totalPendingWarns += stats.warnCount;
|
|
1424
1432
|
totalResolved += stats.resolved;
|
|
1425
|
-
const totalText =
|
|
1433
|
+
const totalText = stats.total > 0 ? `${BOLD}${stats.total} 问题${RESET}` : "";
|
|
1426
1434
|
const fixedText = stats.fixed > 0 ? `${GREEN}🟢 ${stats.fixed} 已修复${RESET}` : "";
|
|
1427
|
-
const errorText = stats.
|
|
1428
|
-
const warnText = stats.
|
|
1435
|
+
const errorText = stats.errorCount > 0 ? `${RED}🔴 ${stats.errorCount} error${RESET}` : "";
|
|
1436
|
+
const warnText = stats.warnCount > 0 ? `${YELLOW}🟡 ${stats.warnCount} warn${RESET}` : "";
|
|
1429
1437
|
const resolvedText = stats.resolved > 0 ? `⚪ ${stats.resolved} 已解决` : "";
|
|
1430
1438
|
const statsText = [
|
|
1431
1439
|
totalText,
|
|
@@ -2515,7 +2523,7 @@ class ReviewService {
|
|
|
2515
2523
|
existingResult.issues = await this.fillIssueAuthors(existingResult.issues, commits, owner, repo, verbose);
|
|
2516
2524
|
// 3. 同步已解决的评论状态
|
|
2517
2525
|
await this.syncResolvedComments(owner, repo, prNumber, existingResult);
|
|
2518
|
-
// 4. 同步评论 reactions
|
|
2526
|
+
// 4. 同步评论 reactions(👍/👎/☹️)
|
|
2519
2527
|
await this.syncReactionsToIssues(owner, repo, prNumber, existingResult, verbose);
|
|
2520
2528
|
// 5. LLM 验证历史问题是否已修复
|
|
2521
2529
|
try {
|
|
@@ -2602,18 +2610,20 @@ class ReviewService {
|
|
|
2602
2610
|
specs = await this.loadSpecs(specSources, verbose);
|
|
2603
2611
|
fileContents = await this.getFileContents(owner, repo, changedFiles, commits, headSha, prNumber, verbose);
|
|
2604
2612
|
}
|
|
2605
|
-
return this.issueVerifyService.verifyIssueFixes(issues, fileContents, specs, llmMode, verbose, context.verifyConcurrency);
|
|
2613
|
+
return await this.issueVerifyService.verifyIssueFixes(issues, fileContents, specs, llmMode, verbose, context.verifyConcurrency);
|
|
2606
2614
|
}
|
|
2607
2615
|
/**
|
|
2608
2616
|
* 计算问题状态统计
|
|
2609
2617
|
*/ calculateIssueStats(issues) {
|
|
2610
2618
|
const total = issues.length;
|
|
2611
|
-
const
|
|
2612
|
-
const
|
|
2613
|
-
const
|
|
2614
|
-
const
|
|
2615
|
-
const
|
|
2616
|
-
const
|
|
2619
|
+
const validIssue = issues.filter((i)=>i.valid !== "false");
|
|
2620
|
+
const validTotal = validIssue.length;
|
|
2621
|
+
const fixed = validIssue.filter((i)=>i.fixed).length;
|
|
2622
|
+
const resolved = validIssue.filter((i)=>i.resolved).length;
|
|
2623
|
+
const invalid = total - validTotal;
|
|
2624
|
+
const pending = validTotal - fixed;
|
|
2625
|
+
const fixRate = validTotal > 0 ? Math.round(fixed / validTotal * 100 * 10) / 10 : 0;
|
|
2626
|
+
const resolveRate = validTotal > 0 ? Math.round(resolved / validTotal * 100 * 10) / 10 : 0;
|
|
2617
2627
|
return {
|
|
2618
2628
|
total,
|
|
2619
2629
|
fixed,
|
|
@@ -3431,7 +3441,7 @@ ${fileChanges || "无"}`;
|
|
|
3431
3441
|
}
|
|
3432
3442
|
// 获取已解决的评论,同步 resolve 状态(在更新 review 之前)
|
|
3433
3443
|
await this.syncResolvedComments(owner, repo, prNumber, result);
|
|
3434
|
-
// 获取评论的 reactions
|
|
3444
|
+
// 获取评论的 reactions,同步状态(☹️ 标记无效,👎 标记未解决)
|
|
3435
3445
|
await this.syncReactionsToIssues(owner, repo, prNumber, result, verbose);
|
|
3436
3446
|
// 查找已有的 AI 评论(Issue Comment),可能存在多个重复评论
|
|
3437
3447
|
if (shouldLog(verbose, 2)) {
|
|
@@ -3619,7 +3629,8 @@ ${fileChanges || "无"}`;
|
|
|
3619
3629
|
* 从旧的 AI review 评论中获取 reactions 和回复,同步到 result.issues
|
|
3620
3630
|
* - 存储所有 reactions 到 issue.reactions 字段
|
|
3621
3631
|
* - 存储评论回复到 issue.replies 字段
|
|
3622
|
-
* - 如果评论有
|
|
3632
|
+
* - 如果评论有 ☹️ (confused) reaction,将对应的问题标记为无效
|
|
3633
|
+
* - 如果评论有 👎 (-1) reaction,将对应的问题标记为未解决
|
|
3623
3634
|
*/ async syncReactionsToIssues(owner, repo, prNumber, result, verbose) {
|
|
3624
3635
|
try {
|
|
3625
3636
|
const reviews = await this.gitProvider.listPullReviews(owner, repo, prNumber);
|
|
@@ -3710,12 +3721,22 @@ ${fileChanges || "无"}`;
|
|
|
3710
3721
|
content,
|
|
3711
3722
|
users
|
|
3712
3723
|
}));
|
|
3713
|
-
// 检查是否有评审人的
|
|
3724
|
+
// 检查是否有评审人的 ☹️ (confused) reaction,标记为无效
|
|
3725
|
+
const confusedUsers = reactionMap.get("confused") || [];
|
|
3726
|
+
const reviewerConfused = confusedUsers.filter((u)=>reviewers.has(u));
|
|
3727
|
+
if (reviewerConfused.length > 0 && matchedIssue.valid !== "false") {
|
|
3728
|
+
matchedIssue.valid = "false";
|
|
3729
|
+
console.log(`☹️ 问题已标记为无效: ${matchedIssue.file}:${matchedIssue.line} (by 评审人: ${reviewerConfused.join(", ")})`);
|
|
3730
|
+
}
|
|
3731
|
+
// 检查是否有评审人的 👎 (-1) reaction,标记为未解决
|
|
3714
3732
|
const thumbsDownUsers = reactionMap.get("-1") || [];
|
|
3715
3733
|
const reviewerThumbsDown = thumbsDownUsers.filter((u)=>reviewers.has(u));
|
|
3716
|
-
if (reviewerThumbsDown.length > 0 && matchedIssue.
|
|
3717
|
-
matchedIssue.
|
|
3718
|
-
|
|
3734
|
+
if (reviewerThumbsDown.length > 0 && (matchedIssue.resolved || matchedIssue.fixed)) {
|
|
3735
|
+
matchedIssue.resolved = undefined;
|
|
3736
|
+
matchedIssue.resolvedBy = undefined;
|
|
3737
|
+
matchedIssue.fixed = undefined;
|
|
3738
|
+
matchedIssue.fixedBy = undefined;
|
|
3739
|
+
console.log(`👎 问题已标记为未解决: ${matchedIssue.file}:${matchedIssue.line} (by 评审人: ${reviewerThumbsDown.join(", ")})`);
|
|
3719
3740
|
}
|
|
3720
3741
|
} catch {
|
|
3721
3742
|
// 单个评论获取 reactions 失败,继续处理其他评论
|
|
@@ -4303,6 +4324,7 @@ class IssueVerifyService {
|
|
|
4303
4324
|
}
|
|
4304
4325
|
verifiedIssues.push({
|
|
4305
4326
|
...issue,
|
|
4327
|
+
resolved: new Date().toISOString(),
|
|
4306
4328
|
fixed: new Date().toISOString(),
|
|
4307
4329
|
valid: FALSE,
|
|
4308
4330
|
reason: "文件已删除"
|
|
@@ -4386,6 +4408,7 @@ class IssueVerifyService {
|
|
|
4386
4408
|
console.log(` ✅ 已修复: ${result.reason}`);
|
|
4387
4409
|
}
|
|
4388
4410
|
updatedIssue.fixed = new Date().toISOString();
|
|
4411
|
+
updatedIssue.resolved = new Date().toISOString();
|
|
4389
4412
|
} else if (!result.valid) {
|
|
4390
4413
|
if (shouldLog(verbose, 1)) {
|
|
4391
4414
|
console.log(` ❌ 无效问题: ${result.reason}`);
|
package/package.json
CHANGED
|
@@ -107,6 +107,7 @@ export class IssueVerifyService {
|
|
|
107
107
|
}
|
|
108
108
|
verifiedIssues.push({
|
|
109
109
|
...issue,
|
|
110
|
+
resolved: new Date().toISOString(),
|
|
110
111
|
fixed: new Date().toISOString(),
|
|
111
112
|
valid: FALSE,
|
|
112
113
|
reason: "文件已删除",
|
|
@@ -209,6 +210,7 @@ export class IssueVerifyService {
|
|
|
209
210
|
console.log(` ✅ 已修复: ${result.reason}`);
|
|
210
211
|
}
|
|
211
212
|
updatedIssue.fixed = new Date().toISOString();
|
|
213
|
+
updatedIssue.resolved = new Date().toISOString();
|
|
212
214
|
} else if (!result.valid) {
|
|
213
215
|
if (shouldLog(verbose, 1)) {
|
|
214
216
|
console.log(` ❌ 无效问题: ${result.reason}`);
|
|
@@ -130,27 +130,37 @@ export class MarkdownFormatter implements ReviewReportFormatter, ReviewReportPar
|
|
|
130
130
|
return "没有需要审查的文件";
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
// 🟢 已修复 | 🔴
|
|
133
|
+
// 🟢 已修复 | 🔴 error数量 | 🟡 warn数量 | ⚪ 已解决(非代码修复)
|
|
134
134
|
const issuesByFile = new Map<
|
|
135
135
|
string,
|
|
136
|
-
{
|
|
136
|
+
{
|
|
137
|
+
fixed: number;
|
|
138
|
+
errorCount: number;
|
|
139
|
+
warnCount: number;
|
|
140
|
+
resolved: number;
|
|
141
|
+
total: number;
|
|
142
|
+
}
|
|
137
143
|
>();
|
|
138
144
|
for (const issue of issues) {
|
|
139
145
|
if (issue.valid === "false") continue;
|
|
140
146
|
const stats = issuesByFile.get(issue.file) || {
|
|
147
|
+
total: 0,
|
|
141
148
|
fixed: 0,
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
errorCount: 0,
|
|
150
|
+
warnCount: 0,
|
|
144
151
|
resolved: 0,
|
|
145
152
|
};
|
|
153
|
+
stats.total++;
|
|
146
154
|
if (issue.fixed) {
|
|
147
155
|
stats.fixed++;
|
|
148
|
-
}
|
|
156
|
+
}
|
|
157
|
+
if (issue.resolved) {
|
|
149
158
|
stats.resolved++;
|
|
150
|
-
}
|
|
151
|
-
|
|
159
|
+
}
|
|
160
|
+
if (issue.severity === "error") {
|
|
161
|
+
stats.errorCount++;
|
|
152
162
|
} else {
|
|
153
|
-
stats.
|
|
163
|
+
stats.warnCount++;
|
|
154
164
|
}
|
|
155
165
|
issuesByFile.set(issue.file, stats);
|
|
156
166
|
}
|
|
@@ -169,20 +179,20 @@ export class MarkdownFormatter implements ReviewReportFormatter, ReviewReportPar
|
|
|
169
179
|
const fileSummaryLines: string[] = [];
|
|
170
180
|
for (const fileSummary of summaries) {
|
|
171
181
|
const stats = issuesByFile.get(fileSummary.file) || {
|
|
182
|
+
total: 0,
|
|
172
183
|
fixed: 0,
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
errorCount: 0,
|
|
185
|
+
warnCount: 0,
|
|
175
186
|
resolved: 0,
|
|
176
187
|
};
|
|
177
|
-
|
|
178
|
-
totalAll += fileTotal;
|
|
188
|
+
totalAll += stats.total;
|
|
179
189
|
totalFixed += stats.fixed;
|
|
180
|
-
totalPendingErrors += stats.
|
|
181
|
-
totalPendingWarns += stats.
|
|
190
|
+
totalPendingErrors += stats.errorCount;
|
|
191
|
+
totalPendingWarns += stats.warnCount;
|
|
182
192
|
totalResolved += stats.resolved;
|
|
183
193
|
|
|
184
194
|
lines.push(
|
|
185
|
-
`| \`${fileSummary.file}\` | ${
|
|
195
|
+
`| \`${fileSummary.file}\` | ${stats.total} | ${stats.fixed} | ${stats.errorCount} | ${stats.warnCount} | ${stats.resolved} |`,
|
|
186
196
|
);
|
|
187
197
|
|
|
188
198
|
// 收集问题总结用于折叠块展示
|
|
@@ -31,24 +31,34 @@ export class TerminalFormatter implements ReviewReportFormatter {
|
|
|
31
31
|
// 🟢 已修复 | 🔴 待处理error | 🟡 待处理warn | ⚪ 已解决(非代码修复)
|
|
32
32
|
const issuesByFile = new Map<
|
|
33
33
|
string,
|
|
34
|
-
{
|
|
34
|
+
{
|
|
35
|
+
fixed: number;
|
|
36
|
+
errorCount: number;
|
|
37
|
+
warnCount: number;
|
|
38
|
+
resolved: number;
|
|
39
|
+
total: number;
|
|
40
|
+
}
|
|
35
41
|
>();
|
|
36
42
|
for (const issue of issues) {
|
|
37
43
|
if (issue.valid === "false") continue;
|
|
38
44
|
const stats = issuesByFile.get(issue.file) || {
|
|
45
|
+
total: 0,
|
|
39
46
|
fixed: 0,
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
errorCount: 0,
|
|
48
|
+
warnCount: 0,
|
|
42
49
|
resolved: 0,
|
|
43
50
|
};
|
|
51
|
+
stats.total++;
|
|
44
52
|
if (issue.fixed) {
|
|
45
53
|
stats.fixed++;
|
|
46
|
-
}
|
|
54
|
+
}
|
|
55
|
+
if (issue.resolved) {
|
|
47
56
|
stats.resolved++;
|
|
48
|
-
}
|
|
49
|
-
|
|
57
|
+
}
|
|
58
|
+
if (issue.severity === "error") {
|
|
59
|
+
stats.errorCount++;
|
|
50
60
|
} else {
|
|
51
|
-
stats.
|
|
61
|
+
stats.warnCount++;
|
|
52
62
|
}
|
|
53
63
|
issuesByFile.set(issue.file, stats);
|
|
54
64
|
}
|
|
@@ -63,24 +73,22 @@ export class TerminalFormatter implements ReviewReportFormatter {
|
|
|
63
73
|
const lines: string[] = [];
|
|
64
74
|
for (const fileSummary of summaries) {
|
|
65
75
|
const stats = issuesByFile.get(fileSummary.file) || {
|
|
76
|
+
total: 0,
|
|
66
77
|
fixed: 0,
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
errorCount: 0,
|
|
79
|
+
warnCount: 0,
|
|
69
80
|
resolved: 0,
|
|
70
81
|
};
|
|
71
|
-
|
|
72
|
-
totalAll += fileTotal;
|
|
82
|
+
totalAll += stats.total;
|
|
73
83
|
totalFixed += stats.fixed;
|
|
74
|
-
totalPendingErrors += stats.
|
|
75
|
-
totalPendingWarns += stats.
|
|
84
|
+
totalPendingErrors += stats.errorCount;
|
|
85
|
+
totalPendingWarns += stats.warnCount;
|
|
76
86
|
totalResolved += stats.resolved;
|
|
77
87
|
|
|
78
|
-
const totalText =
|
|
88
|
+
const totalText = stats.total > 0 ? `${BOLD}${stats.total} 问题${RESET}` : "";
|
|
79
89
|
const fixedText = stats.fixed > 0 ? `${GREEN}🟢 ${stats.fixed} 已修复${RESET}` : "";
|
|
80
|
-
const errorText =
|
|
81
|
-
|
|
82
|
-
const warnText =
|
|
83
|
-
stats.pendingWarns > 0 ? `${YELLOW}🟡 ${stats.pendingWarns} warn${RESET}` : "";
|
|
90
|
+
const errorText = stats.errorCount > 0 ? `${RED}🔴 ${stats.errorCount} error${RESET}` : "";
|
|
91
|
+
const warnText = stats.warnCount > 0 ? `${YELLOW}🟡 ${stats.warnCount} warn${RESET}` : "";
|
|
84
92
|
const resolvedText = stats.resolved > 0 ? `⚪ ${stats.resolved} 已解决` : "";
|
|
85
93
|
const statsText = [totalText, fixedText, errorText, warnText, resolvedText]
|
|
86
94
|
.filter(Boolean)
|
package/src/review-spec/types.ts
CHANGED
|
@@ -125,9 +125,9 @@ export interface ReviewStats {
|
|
|
125
125
|
invalid: number;
|
|
126
126
|
/** 待处理数 */
|
|
127
127
|
pending: number;
|
|
128
|
-
/** 修复率 (0-100),仅计算代码修复:fixed /
|
|
128
|
+
/** 修复率 (0-100),仅计算代码修复:fixed / validTotal */
|
|
129
129
|
fixRate: number;
|
|
130
|
-
/** 解决率 (0-100)
|
|
130
|
+
/** 解决率 (0-100),计算已解决:resolved / validTotal */
|
|
131
131
|
resolveRate: number;
|
|
132
132
|
}
|
|
133
133
|
|
package/src/review.service.ts
CHANGED
|
@@ -956,7 +956,7 @@ export class ReviewService {
|
|
|
956
956
|
// 3. 同步已解决的评论状态
|
|
957
957
|
await this.syncResolvedComments(owner, repo, prNumber, existingResult);
|
|
958
958
|
|
|
959
|
-
// 4. 同步评论 reactions
|
|
959
|
+
// 4. 同步评论 reactions(👍/👎/☹️)
|
|
960
960
|
await this.syncReactionsToIssues(owner, repo, prNumber, existingResult, verbose);
|
|
961
961
|
|
|
962
962
|
// 5. LLM 验证历史问题是否已修复
|
|
@@ -1079,7 +1079,7 @@ export class ReviewService {
|
|
|
1079
1079
|
);
|
|
1080
1080
|
}
|
|
1081
1081
|
|
|
1082
|
-
return this.issueVerifyService.verifyIssueFixes(
|
|
1082
|
+
return await this.issueVerifyService.verifyIssueFixes(
|
|
1083
1083
|
issues,
|
|
1084
1084
|
fileContents,
|
|
1085
1085
|
specs,
|
|
@@ -1094,12 +1094,14 @@ export class ReviewService {
|
|
|
1094
1094
|
*/
|
|
1095
1095
|
protected calculateIssueStats(issues: ReviewIssue[]): ReviewStats {
|
|
1096
1096
|
const total = issues.length;
|
|
1097
|
-
const
|
|
1098
|
-
const
|
|
1099
|
-
const
|
|
1100
|
-
const
|
|
1101
|
-
const
|
|
1102
|
-
const
|
|
1097
|
+
const validIssue = issues.filter((i) => i.valid !== "false");
|
|
1098
|
+
const validTotal = validIssue.length;
|
|
1099
|
+
const fixed = validIssue.filter((i) => i.fixed).length;
|
|
1100
|
+
const resolved = validIssue.filter((i) => i.resolved).length;
|
|
1101
|
+
const invalid = total - validTotal;
|
|
1102
|
+
const pending = validTotal - fixed;
|
|
1103
|
+
const fixRate = validTotal > 0 ? Math.round((fixed / validTotal) * 100 * 10) / 10 : 0;
|
|
1104
|
+
const resolveRate = validTotal > 0 ? Math.round((resolved / validTotal) * 100 * 10) / 10 : 0;
|
|
1103
1105
|
return { total, fixed, resolved, invalid, pending, fixRate, resolveRate };
|
|
1104
1106
|
}
|
|
1105
1107
|
|
|
@@ -2078,7 +2080,7 @@ ${fileChanges || "无"}`;
|
|
|
2078
2080
|
// 获取已解决的评论,同步 resolve 状态(在更新 review 之前)
|
|
2079
2081
|
await this.syncResolvedComments(owner, repo, prNumber, result);
|
|
2080
2082
|
|
|
2081
|
-
// 获取评论的 reactions
|
|
2083
|
+
// 获取评论的 reactions,同步状态(☹️ 标记无效,👎 标记未解决)
|
|
2082
2084
|
await this.syncReactionsToIssues(owner, repo, prNumber, result, verbose);
|
|
2083
2085
|
|
|
2084
2086
|
// 查找已有的 AI 评论(Issue Comment),可能存在多个重复评论
|
|
@@ -2307,7 +2309,8 @@ ${fileChanges || "无"}`;
|
|
|
2307
2309
|
* 从旧的 AI review 评论中获取 reactions 和回复,同步到 result.issues
|
|
2308
2310
|
* - 存储所有 reactions 到 issue.reactions 字段
|
|
2309
2311
|
* - 存储评论回复到 issue.replies 字段
|
|
2310
|
-
* - 如果评论有
|
|
2312
|
+
* - 如果评论有 ☹️ (confused) reaction,将对应的问题标记为无效
|
|
2313
|
+
* - 如果评论有 👎 (-1) reaction,将对应的问题标记为未解决
|
|
2311
2314
|
*/
|
|
2312
2315
|
protected async syncReactionsToIssues(
|
|
2313
2316
|
owner: string,
|
|
@@ -2429,13 +2432,25 @@ ${fileChanges || "无"}`;
|
|
|
2429
2432
|
content,
|
|
2430
2433
|
users,
|
|
2431
2434
|
}));
|
|
2432
|
-
// 检查是否有评审人的
|
|
2435
|
+
// 检查是否有评审人的 ☹️ (confused) reaction,标记为无效
|
|
2436
|
+
const confusedUsers = reactionMap.get("confused") || [];
|
|
2437
|
+
const reviewerConfused = confusedUsers.filter((u) => reviewers.has(u));
|
|
2438
|
+
if (reviewerConfused.length > 0 && matchedIssue.valid !== "false") {
|
|
2439
|
+
matchedIssue.valid = "false";
|
|
2440
|
+
console.log(
|
|
2441
|
+
`☹️ 问题已标记为无效: ${matchedIssue.file}:${matchedIssue.line} (by 评审人: ${reviewerConfused.join(", ")})`,
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
// 检查是否有评审人的 👎 (-1) reaction,标记为未解决
|
|
2433
2445
|
const thumbsDownUsers = reactionMap.get("-1") || [];
|
|
2434
2446
|
const reviewerThumbsDown = thumbsDownUsers.filter((u) => reviewers.has(u));
|
|
2435
|
-
if (reviewerThumbsDown.length > 0 && matchedIssue.
|
|
2436
|
-
matchedIssue.
|
|
2447
|
+
if (reviewerThumbsDown.length > 0 && (matchedIssue.resolved || matchedIssue.fixed)) {
|
|
2448
|
+
matchedIssue.resolved = undefined;
|
|
2449
|
+
matchedIssue.resolvedBy = undefined;
|
|
2450
|
+
matchedIssue.fixed = undefined;
|
|
2451
|
+
matchedIssue.fixedBy = undefined;
|
|
2437
2452
|
console.log(
|
|
2438
|
-
`👎
|
|
2453
|
+
`👎 问题已标记为未解决: ${matchedIssue.file}:${matchedIssue.line} (by 评审人: ${reviewerThumbsDown.join(", ")})`,
|
|
2439
2454
|
);
|
|
2440
2455
|
}
|
|
2441
2456
|
} catch {
|