@spaceflow/review 0.29.1
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 +533 -0
- package/README.md +124 -0
- package/dist/551.js +9 -0
- package/dist/index.js +5704 -0
- package/package.json +50 -0
- package/src/README.md +364 -0
- package/src/__mocks__/@anthropic-ai/claude-agent-sdk.js +3 -0
- package/src/__mocks__/json-stringify-pretty-compact.ts +4 -0
- package/src/deletion-impact.service.spec.ts +974 -0
- package/src/deletion-impact.service.ts +879 -0
- package/src/dto/mcp.dto.ts +42 -0
- package/src/index.ts +32 -0
- package/src/issue-verify.service.spec.ts +460 -0
- package/src/issue-verify.service.ts +309 -0
- package/src/locales/en/review.json +31 -0
- package/src/locales/index.ts +11 -0
- package/src/locales/zh-cn/review.json +31 -0
- package/src/parse-title-options.spec.ts +251 -0
- package/src/parse-title-options.ts +185 -0
- package/src/review-report/formatters/deletion-impact.formatter.ts +144 -0
- package/src/review-report/formatters/index.ts +4 -0
- package/src/review-report/formatters/json.formatter.ts +8 -0
- package/src/review-report/formatters/markdown.formatter.ts +291 -0
- package/src/review-report/formatters/terminal.formatter.ts +130 -0
- package/src/review-report/index.ts +4 -0
- package/src/review-report/review-report.module.ts +8 -0
- package/src/review-report/review-report.service.ts +58 -0
- package/src/review-report/types.ts +26 -0
- package/src/review-spec/index.ts +3 -0
- package/src/review-spec/review-spec.module.ts +10 -0
- package/src/review-spec/review-spec.service.spec.ts +1543 -0
- package/src/review-spec/review-spec.service.ts +902 -0
- package/src/review-spec/types.ts +143 -0
- package/src/review.command.ts +244 -0
- package/src/review.config.ts +58 -0
- package/src/review.mcp.ts +184 -0
- package/src/review.module.ts +52 -0
- package/src/review.service.spec.ts +3007 -0
- package/src/review.service.ts +2603 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +34 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { extname } from "path";
|
|
2
|
+
import {
|
|
3
|
+
FileSummary,
|
|
4
|
+
ReviewIssue,
|
|
5
|
+
ReviewResult,
|
|
6
|
+
ReviewStats,
|
|
7
|
+
Severity,
|
|
8
|
+
} from "../../review-spec/types";
|
|
9
|
+
import { ReportOptions, ReviewReportFormatter } from "../types";
|
|
10
|
+
|
|
11
|
+
const SEVERITY_COLORS: Record<Severity, string> = {
|
|
12
|
+
off: "\x1b[90m",
|
|
13
|
+
warn: "\x1b[33m",
|
|
14
|
+
error: "\x1b[31m",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const RESET = "\x1b[0m";
|
|
18
|
+
const BOLD = "\x1b[1m";
|
|
19
|
+
const DIM = "\x1b[2m";
|
|
20
|
+
const CYAN = "\x1b[36m";
|
|
21
|
+
const GREEN = "\x1b[32m";
|
|
22
|
+
const YELLOW = "\x1b[33m";
|
|
23
|
+
const RED = "\x1b[31m";
|
|
24
|
+
|
|
25
|
+
export class TerminalFormatter implements ReviewReportFormatter {
|
|
26
|
+
private formatFileSummaries(summaries: FileSummary[], issues: ReviewIssue[]): string {
|
|
27
|
+
if (summaries.length === 0) {
|
|
28
|
+
return "没有需要审查的文件";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const issuesByFile = new Map<string, { resolved: number; unresolved: number }>();
|
|
32
|
+
for (const issue of issues) {
|
|
33
|
+
const stats = issuesByFile.get(issue.file) || { resolved: 0, unresolved: 0 };
|
|
34
|
+
if (issue.fixed) {
|
|
35
|
+
stats.resolved++;
|
|
36
|
+
} else {
|
|
37
|
+
stats.unresolved++;
|
|
38
|
+
}
|
|
39
|
+
issuesByFile.set(issue.file, stats);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const lines: string[] = [];
|
|
43
|
+
for (const fileSummary of summaries) {
|
|
44
|
+
const stats = issuesByFile.get(fileSummary.file) || { resolved: 0, unresolved: 0 };
|
|
45
|
+
const resolvedText = stats.resolved > 0 ? `${GREEN}✅ ${stats.resolved} 已解决${RESET}` : "";
|
|
46
|
+
const unresolvedText =
|
|
47
|
+
stats.unresolved > 0 ? `${YELLOW}❌ ${stats.unresolved} 未解决${RESET}` : "";
|
|
48
|
+
const statsText = [resolvedText, unresolvedText].filter(Boolean).join(" / ");
|
|
49
|
+
|
|
50
|
+
if (statsText) {
|
|
51
|
+
lines.push(`${BOLD}${fileSummary.file}${RESET} (${statsText}): ${fileSummary.summary}`);
|
|
52
|
+
} else {
|
|
53
|
+
lines.push(`${BOLD}${fileSummary.file}${RESET}: ${fileSummary.summary}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
format(result: ReviewResult, _options: ReportOptions = {}): string {
|
|
61
|
+
const lines: string[] = [];
|
|
62
|
+
|
|
63
|
+
lines.push("");
|
|
64
|
+
lines.push(`${BOLD}${CYAN}═══════════════════════════════════════════════════════════${RESET}`);
|
|
65
|
+
lines.push(
|
|
66
|
+
`${BOLD}${CYAN} 🤖 AI 代码审查报告 ${RESET}`,
|
|
67
|
+
);
|
|
68
|
+
lines.push(`${BOLD}${CYAN}═══════════════════════════════════════════════════════════${RESET}`);
|
|
69
|
+
lines.push("");
|
|
70
|
+
|
|
71
|
+
const issues = result.issues;
|
|
72
|
+
|
|
73
|
+
if (issues.length === 0) {
|
|
74
|
+
lines.push(`${GREEN}✅ 未发现问题${RESET}`);
|
|
75
|
+
lines.push("");
|
|
76
|
+
lines.push(this.formatFileSummaries(result.summary, []));
|
|
77
|
+
} else {
|
|
78
|
+
lines.push(`${YELLOW}⚠️ 发现 ${issues.length} 个问题${RESET}`);
|
|
79
|
+
lines.push("");
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < issues.length; i++) {
|
|
82
|
+
const issue = issues[i];
|
|
83
|
+
const color = SEVERITY_COLORS[issue.severity] || SEVERITY_COLORS.error;
|
|
84
|
+
const severityLabel = issue.severity.toUpperCase();
|
|
85
|
+
|
|
86
|
+
lines.push(`${DIM}───────────────────────────────────────────────────────────${RESET}`);
|
|
87
|
+
lines.push(`${BOLD}[${i + 1}/${issues.length}]${RESET} ${color}${severityLabel}${RESET}`);
|
|
88
|
+
lines.push(`${BOLD}📍 位置:${RESET} ${issue.file}:${issue.line}`);
|
|
89
|
+
lines.push(`${BOLD}📋 规则:${RESET} ${issue.ruleId} ${DIM}(${issue.specFile})${RESET}`);
|
|
90
|
+
lines.push(`${BOLD}❓ 问题:${RESET} ${issue.reason}`);
|
|
91
|
+
|
|
92
|
+
if (issue.commit) {
|
|
93
|
+
lines.push(`${BOLD}📝 Commit:${RESET} ${issue.commit}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (issue.suggestion) {
|
|
97
|
+
const ext = extname(issue.file).slice(1) || "";
|
|
98
|
+
lines.push(`${BOLD}💡 建议:${RESET}`);
|
|
99
|
+
lines.push(`${DIM}--- ${ext} ---${RESET}`);
|
|
100
|
+
lines.push(issue.suggestion);
|
|
101
|
+
lines.push(`${DIM}------------${RESET}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
lines.push("");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
lines.push(`${DIM}───────────────────────────────────────────────────────────${RESET}`);
|
|
108
|
+
lines.push("");
|
|
109
|
+
lines.push(`${BOLD}📝 总结${RESET}`);
|
|
110
|
+
lines.push(this.formatFileSummaries(result.summary, issues));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
lines.push("");
|
|
114
|
+
lines.push(`${BOLD}${CYAN}═══════════════════════════════════════════════════════════${RESET}`);
|
|
115
|
+
lines.push("");
|
|
116
|
+
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
formatStats(stats: ReviewStats, prNumber?: number): string {
|
|
121
|
+
const title = prNumber ? `PR #${prNumber} Review 状态统计` : "Review 状态统计";
|
|
122
|
+
const lines = [`\n${BOLD}${CYAN}📊 ${title}:${RESET}`];
|
|
123
|
+
lines.push(` 总问题数: ${stats.total}`);
|
|
124
|
+
lines.push(` ${GREEN}✅ 已修复: ${stats.fixed}${RESET}`);
|
|
125
|
+
lines.push(` ${RED}❌ 无效: ${stats.invalid}${RESET}`);
|
|
126
|
+
lines.push(` ${YELLOW}⚠️ 待处理: ${stats.pending}${RESET}`);
|
|
127
|
+
lines.push(` 修复率: ${stats.fixRate}%`);
|
|
128
|
+
return lines.join("\n");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common";
|
|
2
|
+
import { ReviewResult, ReviewStats } from "../review-spec/types";
|
|
3
|
+
import { JsonFormatter, MarkdownFormatter, TerminalFormatter } from "./formatters";
|
|
4
|
+
import { ParsedReport, ReportFormat, ReportOptions, ReviewReportFormatter } from "./types";
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class ReviewReportService {
|
|
8
|
+
private readonly markdownFormatter = new MarkdownFormatter();
|
|
9
|
+
private readonly terminalFormatter = new TerminalFormatter();
|
|
10
|
+
|
|
11
|
+
private formatters: Map<ReportFormat, ReviewReportFormatter> = new Map([
|
|
12
|
+
["markdown", this.markdownFormatter],
|
|
13
|
+
["json", new JsonFormatter()],
|
|
14
|
+
["terminal", this.terminalFormatter],
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
format(result: ReviewResult, format: ReportFormat = "markdown", options?: ReportOptions): string {
|
|
18
|
+
const formatter = this.formatters.get(format);
|
|
19
|
+
if (!formatter) {
|
|
20
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
21
|
+
}
|
|
22
|
+
return formatter.format(result, options);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
formatMarkdown(result: ReviewResult, options?: ReportOptions): string {
|
|
26
|
+
return this.format(result, "markdown", options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
formatJson(result: ReviewResult, options?: ReportOptions): string {
|
|
30
|
+
return this.format(result, "json", options);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
formatTerminal(result: ReviewResult, options?: ReportOptions): string {
|
|
34
|
+
return this.format(result, "terminal", options);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
parseMarkdown(content: string): ParsedReport | null {
|
|
38
|
+
return this.markdownFormatter.parse(content);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
registerFormatter(format: ReportFormat, formatter: ReviewReportFormatter): void {
|
|
42
|
+
this.formatters.set(format, formatter);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 格式化统计信息为终端输出
|
|
47
|
+
*/
|
|
48
|
+
formatStatsTerminal(stats: ReviewStats, prNumber?: number): string {
|
|
49
|
+
return this.terminalFormatter.formatStats(stats, prNumber);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 格式化统计信息为 Markdown
|
|
54
|
+
*/
|
|
55
|
+
formatStatsMarkdown(stats: ReviewStats, prNumber?: number): string {
|
|
56
|
+
return this.markdownFormatter.formatStats(stats, prNumber);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ReviewIssue, ReviewResult, ReviewStats } from "../review-spec/types";
|
|
2
|
+
|
|
3
|
+
export type ReportFormat = "markdown" | "json" | "terminal";
|
|
4
|
+
|
|
5
|
+
export interface ReportOptions {
|
|
6
|
+
prNumber?: number;
|
|
7
|
+
includeReanalysisCheckbox?: boolean;
|
|
8
|
+
includeJsonData?: boolean;
|
|
9
|
+
reviewCommentMarker?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ParsedReport {
|
|
13
|
+
/** 完整的 ReviewResult 数据 */
|
|
14
|
+
result: ReviewResult;
|
|
15
|
+
/** 是否请求重新分析 */
|
|
16
|
+
hasReanalysisRequest?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ReviewReportFormatter {
|
|
20
|
+
format(result: ReviewResult, options?: ReportOptions): string;
|
|
21
|
+
formatStats?(stats: ReviewStats, prNumber?: number): string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ReviewReportParser {
|
|
25
|
+
parse(content: string): ParsedReport | null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { GitProviderModule } from "@spaceflow/core";
|
|
3
|
+
import { ReviewSpecService } from "./review-spec.service";
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
imports: [GitProviderModule.forFeature()],
|
|
7
|
+
providers: [ReviewSpecService],
|
|
8
|
+
exports: [ReviewSpecService],
|
|
9
|
+
})
|
|
10
|
+
export class ReviewSpecModule {}
|