goodiffer 1.1.1 → 1.2.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/README.md +121 -3
- package/bin/goodiffer.js +5 -4
- package/package.json +1 -1
- package/src/commands/analyze.js +103 -175
- package/src/commands/report.js +26 -4
- package/src/prompts/codex-review-prompt.js +289 -0
- package/src/prompts/review-prompt.js +6 -1
- package/src/schemas/codex-review-schema.json +222 -0
- package/src/services/ai-client.js +246 -0
- package/src/services/code-context.js +316 -4
- package/src/services/codex-reporter.js +312 -0
- package/src/services/database.js +156 -13
- package/src/services/lsp-service.js +635 -0
- package/src/services/report-generator.js +82 -0
package/README.md
CHANGED
|
@@ -13,7 +13,9 @@ AI-powered git diff analyzer for code review - 基于 AI 的 Git Diff 智能分
|
|
|
13
13
|
- 🔗 检测代码关联性风险
|
|
14
14
|
- 📋 生成可复制的修复提示词,方便在 Claude Code / Codex 中使用
|
|
15
15
|
- 🌐 支持第三方 API 代理
|
|
16
|
-
- 🔮
|
|
16
|
+
- 🔮 支持代码上下文获取 (Tool Use),AI 可按需读取相关源码
|
|
17
|
+
- ⚡ **NEW** LSP 集成 - 提供类型信息、引用查找、符号导航等智能功能
|
|
18
|
+
- 🧠 **NEW** Codex 深度审查 - 基于 GPT-5.2 的 8 维度深度代码审查
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -79,19 +81,135 @@ goodiffer -c abc123 --context # 分析指定 commit,启用上下文
|
|
|
79
81
|
|
|
80
82
|
### 代码上下文模式 (--context)
|
|
81
83
|
|
|
82
|
-
启用 `--context` 选项后,AI
|
|
84
|
+
启用 `--context` 选项后,AI 在分析代码时可以使用以下工具:
|
|
83
85
|
|
|
86
|
+
#### 基础工具
|
|
84
87
|
1. **read_file** - 读取项目中的源文件,了解函数/类的具体实现
|
|
85
88
|
2. **find_definition** - 查找函数、类、变量的定义位置
|
|
86
89
|
3. **search_code** - 在项目中搜索代码模式
|
|
87
90
|
4. **list_files** - 列出目录结构
|
|
88
91
|
|
|
92
|
+
#### LSP 增强工具 (TypeScript/JavaScript)
|
|
93
|
+
5. **get_type_info** - 获取变量、函数的类型信息和文档
|
|
94
|
+
6. **find_references** - 查找符号在整个项目中的所有引用位置
|
|
95
|
+
7. **get_document_symbols** - 获取文件中的所有符号列表(函数、类、变量等)
|
|
96
|
+
8. **go_to_definition** - 使用 LSP 精确定位符号的定义位置
|
|
97
|
+
|
|
98
|
+
**LSP 工具优势:**
|
|
99
|
+
- ✅ 精确的类型分析(支持 TypeScript/JavaScript)
|
|
100
|
+
- ✅ 跨文件引用查找
|
|
101
|
+
- ✅ 快速了解文件结构
|
|
102
|
+
- ✅ 自动回退到正则搜索
|
|
103
|
+
|
|
89
104
|
这使得 AI 能够:
|
|
90
105
|
- 验证被调用函数的实现是否正确
|
|
91
106
|
- 检查类型定义和接口
|
|
92
107
|
- 发现潜在的关联影响
|
|
108
|
+
- 了解代码的调用关系和依赖
|
|
109
|
+
|
|
110
|
+
> **注意**: 此功能需要 Claude 模型(使用 Tool Use API)。LSP 功能自动启用,无需额外配置。
|
|
111
|
+
|
|
112
|
+
### Codex 深度代码审查 (NEW)
|
|
113
|
+
|
|
114
|
+
使用 OpenAI Codex (GPT-5.2) 进行深度代码审查,提供 8 维度质量评估和高推理分析。
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# 使用 Codex 深度审查最近一次 commit
|
|
118
|
+
goodiffer codex
|
|
119
|
+
|
|
120
|
+
# 审查暂存区
|
|
121
|
+
goodiffer codex -s
|
|
122
|
+
|
|
123
|
+
# 审查指定 commit
|
|
124
|
+
goodiffer codex -c abc123
|
|
125
|
+
|
|
126
|
+
# 指定推理强度 (low/medium/high)
|
|
127
|
+
goodiffer codex --reasoning high
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Codex Review 特性
|
|
131
|
+
|
|
132
|
+
**8 维度质量评估:**
|
|
133
|
+
1. **Code Style & Formatting** - 代码风格与格式
|
|
134
|
+
2. **Security & Compliance** - 安全性与合规性
|
|
135
|
+
3. **Error Handling & Logging** - 错误处理与日志
|
|
136
|
+
4. **Readability & Maintainability** - 可读性与可维护性
|
|
137
|
+
5. **Performance & Scalability** - 性能与可扩展性
|
|
138
|
+
6. **Testing & Quality Assurance** - 测试与质量保证
|
|
139
|
+
7. **Documentation & Version Control** - 文档与版本控制
|
|
140
|
+
8. **Accessibility & Internationalization** - 可访问性与国际化
|
|
141
|
+
|
|
142
|
+
**评分系统:**
|
|
143
|
+
- ⭐ **Extraordinary** (90-100) - 卓越质量
|
|
144
|
+
- ✓ **Acceptable** (70-89) - 符合标准
|
|
145
|
+
- ⚠️ **Poor** (0-69) - 需要改进
|
|
146
|
+
|
|
147
|
+
**高推理模式:**
|
|
148
|
+
- 使用 `reasoning: { effort: "high" }` 进行深度分析
|
|
149
|
+
- 提供置信度评分(0.0-1.0)
|
|
150
|
+
- 生成正确性判断(patch is correct / incorrect)
|
|
151
|
+
|
|
152
|
+
**结构化输出:**
|
|
153
|
+
- 使用 JSON Schema 定义输出格式(降低 35% 失败率)
|
|
154
|
+
- 精确的文件/行号引用
|
|
155
|
+
- 优先级分级(P0-P3)
|
|
156
|
+
|
|
157
|
+
**输出示例:**
|
|
158
|
+
```
|
|
159
|
+
╭──────────────────────────────────────────────────────────╮
|
|
160
|
+
│ Codex Deep Code Review Report │
|
|
161
|
+
╰──────────────────────────────────────────────────────────╯
|
|
162
|
+
|
|
163
|
+
📝 Commit: abc1234
|
|
164
|
+
📋 Message: feat: add user authentication
|
|
165
|
+
|
|
166
|
+
📊 Summary: 添加了用户认证功能...
|
|
167
|
+
|
|
168
|
+
═══════════════════════════════════════════════════════════
|
|
169
|
+
|
|
170
|
+
🎯 8-Dimensional Quality Assessment
|
|
171
|
+
|
|
172
|
+
1. Security & Compliance
|
|
173
|
+
🌟 Rating: EXTRAORDINARY | Score: 95/100
|
|
174
|
+
密码加密使用了 bcrypt,符合安全标准
|
|
175
|
+
|
|
176
|
+
2. Error Handling & Logging
|
|
177
|
+
⚠️ Rating: POOR | Score: 60/100
|
|
178
|
+
缺少异常处理和日志记录
|
|
179
|
+
▸ API 调用未处理错误情况
|
|
180
|
+
▸ 缺少关键操作的日志
|
|
181
|
+
|
|
182
|
+
═══════════════════════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
⚖️ Overall Assessment
|
|
185
|
+
|
|
186
|
+
✅ Correctness: PATCH IS CORRECT
|
|
187
|
+
📈 Confidence: 85% (High)
|
|
188
|
+
💡 Explanation: 代码功能正确,但需改进错误处理
|
|
189
|
+
|
|
190
|
+
═══════════════════════════════════════════════════════════
|
|
191
|
+
|
|
192
|
+
🔍 Findings (2)
|
|
193
|
+
|
|
194
|
+
🟠 P1 - URGENT (1)
|
|
195
|
+
|
|
196
|
+
[A] [P1] API 调用缺少错误处理
|
|
197
|
+
|
|
198
|
+
Location: src/auth/api.js:45-52
|
|
199
|
+
Confidence: 90% (Very High)
|
|
200
|
+
Dimension: Error Handling & Logging
|
|
201
|
+
|
|
202
|
+
在 loginUser 函数中直接调用 API 而未处理可能的网络错误...
|
|
203
|
+
|
|
204
|
+
💡 Suggestion: 添加 try-catch 块处理异常
|
|
205
|
+
|
|
206
|
+
📋 修复提示词 (复制到 Claude Code/Codex):
|
|
207
|
+
┌──────────────────────────────────────────────────┐
|
|
208
|
+
│ 在 src/auth/api.js:45 添加 try-catch 处理异常 │
|
|
209
|
+
└──────────────────────────────────────────────────┘
|
|
210
|
+
```
|
|
93
211
|
|
|
94
|
-
>
|
|
212
|
+
> **推荐使用场景**: 关键代码审查、生产环境发布前、安全敏感功能
|
|
95
213
|
|
|
96
214
|
### 配置管理
|
|
97
215
|
|
package/bin/goodiffer.js
CHANGED
|
@@ -11,10 +11,10 @@ import { reportCommand } from '../src/commands/report.js';
|
|
|
11
11
|
|
|
12
12
|
program
|
|
13
13
|
.name('goodiffer')
|
|
14
|
-
.description('AI-powered git diff analyzer
|
|
15
|
-
.version('1.
|
|
14
|
+
.description('AI-powered git diff analyzer with Codex deep code review')
|
|
15
|
+
.version('1.2.1');
|
|
16
16
|
|
|
17
|
-
// 默认命令 -
|
|
17
|
+
// 默认命令 - Codex 深度分析
|
|
18
18
|
program
|
|
19
19
|
.option('-s, --staged', '分析暂存区的更改')
|
|
20
20
|
.option('-c, --commit <sha>', '分析指定的 commit')
|
|
@@ -22,7 +22,7 @@ program
|
|
|
22
22
|
.option('--to <sha>', '结束 commit (与 --from 配合使用)')
|
|
23
23
|
.option('-n <number>', '分析最近 n 条 commit (n <= 10), 或与 -m 配合表示起始位置')
|
|
24
24
|
.option('-m <number>', '与 -n 配合使用,表示结束位置 (m-n <= 10)')
|
|
25
|
-
.option('--
|
|
25
|
+
.option('--reasoning <level>', '推理强度: low, medium, high, none (默认 high)', 'high')
|
|
26
26
|
.option('--no-save', '不保存到数据库')
|
|
27
27
|
.action(async (options) => {
|
|
28
28
|
await analyzeCommand(options);
|
|
@@ -89,6 +89,7 @@ program
|
|
|
89
89
|
.option('--until <date>', '结束日期 (YYYY-MM-DD)')
|
|
90
90
|
.option('-o, --output <path>', '输出文件路径')
|
|
91
91
|
.option('--open', '生成后自动打开')
|
|
92
|
+
.option('-f, --force', '强制重新生成 (忽略缓存)')
|
|
92
93
|
.action(async (options) => {
|
|
93
94
|
await reportCommand(options);
|
|
94
95
|
});
|
package/package.json
CHANGED
package/src/commands/analyze.js
CHANGED
|
@@ -2,25 +2,35 @@ import ora from 'ora';
|
|
|
2
2
|
import { getConfig, isConfigured } from '../utils/config-store.js';
|
|
3
3
|
import { GitService } from '../services/git.js';
|
|
4
4
|
import { AIClient } from '../services/ai-client.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { buildCodexReviewPrompt } from '../prompts/codex-review-prompt.js';
|
|
6
|
+
import { generateCodexReport } from '../services/codex-reporter.js';
|
|
7
7
|
import { getDatabase } from '../services/database.js';
|
|
8
|
-
import { CodeContextService } from '../services/code-context.js';
|
|
9
|
-
import { MCPClientService } from '../services/mcp-client.js';
|
|
10
8
|
import logger from '../utils/logger.js';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
/**
|
|
11
|
+
* 提取统计数据
|
|
12
|
+
*/
|
|
13
|
+
function extractStats(result) {
|
|
14
|
+
const stats = {
|
|
15
|
+
p0: 0,
|
|
16
|
+
p1: 0,
|
|
17
|
+
p2: 0,
|
|
18
|
+
p3: 0,
|
|
19
|
+
risks: 0
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (result.findings) {
|
|
23
|
+
result.findings.forEach(f => {
|
|
24
|
+
const priority = f.priority || 3;
|
|
25
|
+
stats[`p${priority}`]++;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (result.associationRisks) {
|
|
30
|
+
stats.risks = result.associationRisks.length;
|
|
23
31
|
}
|
|
32
|
+
|
|
33
|
+
return stats;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
export async function analyzeCommand(options) {
|
|
@@ -89,70 +99,49 @@ export async function analyzeCommand(options) {
|
|
|
89
99
|
process.exit(1);
|
|
90
100
|
}
|
|
91
101
|
|
|
92
|
-
//
|
|
102
|
+
// 获取项目信息
|
|
93
103
|
const projectName = await git.getProjectName();
|
|
94
104
|
const branch = await git.getCurrentBranch();
|
|
105
|
+
const changedFiles = await git.getChangedFiles(
|
|
106
|
+
options.commit || (options.from ? `${options.from}..${options.to}` : 'HEAD~1')
|
|
107
|
+
);
|
|
95
108
|
|
|
96
109
|
spinner.succeed('获取 Git 信息完成');
|
|
97
110
|
|
|
98
|
-
//
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} else {
|
|
106
|
-
logger.info('已启用代码上下文模式 (本地文件读取)');
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 构建提示词
|
|
111
|
-
const prompt = useContext
|
|
112
|
-
? buildReviewPromptWithTools(commitInfo.message, diff)
|
|
113
|
-
: buildReviewPrompt(commitInfo.message, diff);
|
|
111
|
+
// 构建 Codex review 提示词
|
|
112
|
+
const prompt = buildCodexReviewPrompt(commitInfo.message, diff, {
|
|
113
|
+
repository: projectName,
|
|
114
|
+
baseSha: options.from || 'HEAD~1',
|
|
115
|
+
headSha: options.to || commitInfo.sha,
|
|
116
|
+
changedFiles
|
|
117
|
+
});
|
|
114
118
|
|
|
115
119
|
// 调用 AI 分析
|
|
116
|
-
spinner = ora('
|
|
120
|
+
spinner = ora('Codex 深度分析中...').start();
|
|
117
121
|
|
|
118
122
|
const aiClient = new AIClient(config);
|
|
119
|
-
let
|
|
123
|
+
let result = null;
|
|
120
124
|
|
|
121
125
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (progress.type === 'iteration') {
|
|
137
|
-
spinner.text = `AI 正在分析... (迭代 ${progress.iteration})`;
|
|
138
|
-
} else if (progress.type === 'tool_calls') {
|
|
139
|
-
const toolNames = progress.tools.map(t => t.name).join(', ');
|
|
140
|
-
spinner.text = `AI 正在获取上下文: ${toolNames}`;
|
|
141
|
-
} else if (progress.type === 'tool_result') {
|
|
142
|
-
spinner.text = `AI 正在分析... (已获取 ${progress.tool} 结果)`;
|
|
143
|
-
}
|
|
126
|
+
// 使用 Codex 深度分析
|
|
127
|
+
const reasoningEffort = options.reasoning || 'high';
|
|
128
|
+
|
|
129
|
+
result = await aiClient.analyzeWithCodex(prompt, {
|
|
130
|
+
reasoningEffort: reasoningEffort,
|
|
131
|
+
onProgress: (progress) => {
|
|
132
|
+
if (progress.type === 'info') {
|
|
133
|
+
spinner.text = progress.message;
|
|
134
|
+
} else if (progress.type === 'analyzing') {
|
|
135
|
+
spinner.text = '🧠 Codex 深度分析中...';
|
|
136
|
+
} else if (progress.type === 'complete') {
|
|
137
|
+
spinner.succeed('✅ 分析完成');
|
|
138
|
+
} else if (progress.type === 'error') {
|
|
139
|
+
spinner.fail(`❌ ${progress.message}`);
|
|
144
140
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await codeContext.close();
|
|
148
|
-
} else {
|
|
149
|
-
// 普通分析模式
|
|
150
|
-
response = await aiClient.analyzeStream(prompt, (chunk) => {
|
|
151
|
-
spinner.text = `AI 正在分析... (${response.length} 字符)`;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
154
143
|
} catch (error) {
|
|
155
|
-
spinner.fail('
|
|
144
|
+
spinner.fail('分析失败');
|
|
156
145
|
if (error.message.includes('401')) {
|
|
157
146
|
logger.error('API Key 无效或已过期');
|
|
158
147
|
} else if (error.message.includes('403')) {
|
|
@@ -173,10 +162,8 @@ export async function analyzeCommand(options) {
|
|
|
173
162
|
process.exit(1);
|
|
174
163
|
}
|
|
175
164
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// 生成报告
|
|
179
|
-
generateReport(response, commitInfo);
|
|
165
|
+
// 生成 Codex 格式报告
|
|
166
|
+
generateCodexReport(result, commitInfo);
|
|
180
167
|
|
|
181
168
|
// 保存到数据库 (除非指定 --no-save)
|
|
182
169
|
if (options.save !== false) {
|
|
@@ -189,44 +176,8 @@ export async function analyzeCommand(options) {
|
|
|
189
176
|
// 获取或创建开发者
|
|
190
177
|
const developer = db.getOrCreateDeveloper(author.email, author.name);
|
|
191
178
|
|
|
192
|
-
// 解析 AI 响应
|
|
193
|
-
const parsed = parseAIResponse(response);
|
|
194
|
-
|
|
195
179
|
// 提取统计数据
|
|
196
|
-
|
|
197
|
-
let commitMatch = false;
|
|
198
|
-
let commitMatchReason = '';
|
|
199
|
-
let errorCount = 0;
|
|
200
|
-
let warningCount = 0;
|
|
201
|
-
let infoCount = 0;
|
|
202
|
-
let riskCount = 0;
|
|
203
|
-
let issues = [];
|
|
204
|
-
let associationRisks = [];
|
|
205
|
-
|
|
206
|
-
if (parsed) {
|
|
207
|
-
summary = parsed.summary || '';
|
|
208
|
-
commitMatch = parsed.commitMatch || false;
|
|
209
|
-
commitMatchReason = parsed.commitMatchReason || '';
|
|
210
|
-
|
|
211
|
-
// 支持新格式 findings (按优先级统计)
|
|
212
|
-
if (parsed.findings && Array.isArray(parsed.findings)) {
|
|
213
|
-
issues = parsed.findings;
|
|
214
|
-
errorCount = issues.filter(i => i.priority === 0 || i.priority === 1).length; // P0, P1 算 error
|
|
215
|
-
warningCount = issues.filter(i => i.priority === 2).length; // P2 算 warning
|
|
216
|
-
infoCount = issues.filter(i => i.priority === 3 || i.priority === undefined).length; // P3 算 info
|
|
217
|
-
} else if (parsed.issues && Array.isArray(parsed.issues)) {
|
|
218
|
-
// 兼容旧格式
|
|
219
|
-
issues = parsed.issues;
|
|
220
|
-
errorCount = issues.filter(i => i.level === 'error').length;
|
|
221
|
-
warningCount = issues.filter(i => i.level === 'warning').length;
|
|
222
|
-
infoCount = issues.filter(i => i.level === 'info').length;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (parsed.associationRisks && Array.isArray(parsed.associationRisks)) {
|
|
226
|
-
associationRisks = parsed.associationRisks;
|
|
227
|
-
riskCount = associationRisks.length;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
180
|
+
const stats = extractStats(result);
|
|
230
181
|
|
|
231
182
|
// 保存 review 记录
|
|
232
183
|
const reviewId = db.saveReview({
|
|
@@ -242,18 +193,20 @@ export async function analyzeCommand(options) {
|
|
|
242
193
|
filesChanged: diffStats.filesChanged,
|
|
243
194
|
insertions: diffStats.insertions,
|
|
244
195
|
deletions: diffStats.deletions,
|
|
245
|
-
diffContent: null,
|
|
246
|
-
aiResponse:
|
|
247
|
-
summary,
|
|
248
|
-
commitMatch,
|
|
249
|
-
commitMatchReason,
|
|
250
|
-
errorCount,
|
|
251
|
-
warningCount,
|
|
252
|
-
infoCount,
|
|
253
|
-
riskCount,
|
|
196
|
+
diffContent: null,
|
|
197
|
+
aiResponse: JSON.stringify(result, null, 2),
|
|
198
|
+
summary: result.summary || '',
|
|
199
|
+
commitMatch: result.commitMatch || false,
|
|
200
|
+
commitMatchReason: result.commitMatchReason || '',
|
|
201
|
+
errorCount: stats.p0 + stats.p1,
|
|
202
|
+
warningCount: stats.p2,
|
|
203
|
+
infoCount: stats.p3,
|
|
204
|
+
riskCount: stats.risks,
|
|
254
205
|
modelUsed: config.model,
|
|
255
|
-
issues,
|
|
256
|
-
associationRisks
|
|
206
|
+
issues: result.findings || [],
|
|
207
|
+
associationRisks: result.associationRisks || [],
|
|
208
|
+
dimensions: result.dimensions || [],
|
|
209
|
+
overallAssessment: result.overall_assessment || {}
|
|
257
210
|
});
|
|
258
211
|
|
|
259
212
|
logger.success(`Review #${reviewId} 已保存到数据库`);
|
|
@@ -283,7 +236,6 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
283
236
|
}
|
|
284
237
|
|
|
285
238
|
if (n !== null && m === null) {
|
|
286
|
-
// 只有 -n,表示分析最近 n 条
|
|
287
239
|
if (n <= 0 || n > 10) {
|
|
288
240
|
logger.error('n 必须在 1-10 之间');
|
|
289
241
|
process.exit(1);
|
|
@@ -291,7 +243,6 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
291
243
|
}
|
|
292
244
|
|
|
293
245
|
if (n !== null && m !== null) {
|
|
294
|
-
// 同时有 -n 和 -m,表示第 n 条到第 m 条
|
|
295
246
|
if (n <= 0 || m <= 0) {
|
|
296
247
|
logger.error('n 和 m 必须大于 0');
|
|
297
248
|
process.exit(1);
|
|
@@ -312,10 +263,8 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
312
263
|
// 获取 commits
|
|
313
264
|
let commits;
|
|
314
265
|
if (m !== null) {
|
|
315
|
-
// 第 n 条到第 m 条
|
|
316
266
|
commits = await git.getCommitRange(n, m);
|
|
317
267
|
} else {
|
|
318
|
-
// 最近 n 条
|
|
319
268
|
commits = await git.getRecentCommits(n);
|
|
320
269
|
}
|
|
321
270
|
|
|
@@ -346,10 +295,9 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
346
295
|
const commit = commits[i];
|
|
347
296
|
const shortSha = commit.sha.substring(0, 7);
|
|
348
297
|
|
|
349
|
-
spinner = ora(`[${i + 1}/${commits.length}] 分析 commit ${shortSha}...`).start();
|
|
298
|
+
spinner = ora(`[${i + 1}/${commits.length}] Codex 分析 commit ${shortSha}...`).start();
|
|
350
299
|
|
|
351
300
|
try {
|
|
352
|
-
// 获取 diff
|
|
353
301
|
const diff = await git.getCommitDiff(commit.sha);
|
|
354
302
|
if (!diff || diff.trim() === '') {
|
|
355
303
|
spinner.warn(`[${i + 1}/${commits.length}] commit ${shortSha} 没有代码变更,跳过`);
|
|
@@ -357,14 +305,26 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
357
305
|
}
|
|
358
306
|
|
|
359
307
|
const diffStats = await git.getDiffStats(`${commit.sha}~1`, commit.sha);
|
|
308
|
+
const changedFiles = await git.getChangedFiles(commit.sha);
|
|
309
|
+
|
|
310
|
+
// 构建 Codex prompt
|
|
311
|
+
const prompt = buildCodexReviewPrompt(commit.message, diff, {
|
|
312
|
+
repository: projectName,
|
|
313
|
+
baseSha: `${commit.sha}~1`,
|
|
314
|
+
headSha: commit.sha,
|
|
315
|
+
changedFiles
|
|
316
|
+
});
|
|
360
317
|
|
|
361
|
-
|
|
362
|
-
const prompt = buildReviewPrompt(commit.message, diff);
|
|
363
|
-
let response = '';
|
|
318
|
+
let result = null;
|
|
364
319
|
|
|
365
320
|
try {
|
|
366
|
-
|
|
367
|
-
|
|
321
|
+
result = await aiClient.analyzeWithCodex(prompt, {
|
|
322
|
+
reasoningEffort: options.reasoning || 'high',
|
|
323
|
+
onProgress: (progress) => {
|
|
324
|
+
if (progress.type === 'info') {
|
|
325
|
+
spinner.text = `[${i + 1}/${commits.length}] ${progress.message}`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
368
328
|
});
|
|
369
329
|
} catch (error) {
|
|
370
330
|
spinner.fail(`[${i + 1}/${commits.length}] commit ${shortSha} 分析失败: ${error.message}`);
|
|
@@ -374,7 +334,7 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
374
334
|
spinner.succeed(`[${i + 1}/${commits.length}] commit ${shortSha} 分析完成`);
|
|
375
335
|
|
|
376
336
|
// 生成报告
|
|
377
|
-
|
|
337
|
+
generateCodexReport(result, { sha: commit.sha, message: commit.message });
|
|
378
338
|
|
|
379
339
|
// 保存到数据库
|
|
380
340
|
if (options.save !== false) {
|
|
@@ -382,41 +342,7 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
382
342
|
const db = getDatabase();
|
|
383
343
|
const project = db.getOrCreateProject(projectName, process.cwd());
|
|
384
344
|
const developer = db.getOrCreateDeveloper(commit.author.email, commit.author.name);
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
let summary = '';
|
|
388
|
-
let commitMatch = false;
|
|
389
|
-
let commitMatchReason = '';
|
|
390
|
-
let errorCount = 0;
|
|
391
|
-
let warningCount = 0;
|
|
392
|
-
let infoCount = 0;
|
|
393
|
-
let riskCount = 0;
|
|
394
|
-
let issues = [];
|
|
395
|
-
let associationRisks = [];
|
|
396
|
-
|
|
397
|
-
if (parsed) {
|
|
398
|
-
summary = parsed.summary || '';
|
|
399
|
-
commitMatch = parsed.commitMatch || false;
|
|
400
|
-
commitMatchReason = parsed.commitMatchReason || '';
|
|
401
|
-
|
|
402
|
-
// 支持新格式 findings (按优先级统计)
|
|
403
|
-
if (parsed.findings && Array.isArray(parsed.findings)) {
|
|
404
|
-
issues = parsed.findings;
|
|
405
|
-
errorCount = issues.filter(i => i.priority === 0 || i.priority === 1).length;
|
|
406
|
-
warningCount = issues.filter(i => i.priority === 2).length;
|
|
407
|
-
infoCount = issues.filter(i => i.priority === 3 || i.priority === undefined).length;
|
|
408
|
-
} else if (parsed.issues && Array.isArray(parsed.issues)) {
|
|
409
|
-
issues = parsed.issues;
|
|
410
|
-
errorCount = issues.filter(i => i.level === 'error').length;
|
|
411
|
-
warningCount = issues.filter(i => i.level === 'warning').length;
|
|
412
|
-
infoCount = issues.filter(i => i.level === 'info').length;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (parsed.associationRisks && Array.isArray(parsed.associationRisks)) {
|
|
416
|
-
associationRisks = parsed.associationRisks;
|
|
417
|
-
riskCount = associationRisks.length;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
345
|
+
const stats = extractStats(result);
|
|
420
346
|
|
|
421
347
|
const reviewId = db.saveReview({
|
|
422
348
|
projectId: project.id,
|
|
@@ -432,17 +358,19 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
432
358
|
insertions: diffStats.insertions,
|
|
433
359
|
deletions: diffStats.deletions,
|
|
434
360
|
diffContent: null,
|
|
435
|
-
aiResponse:
|
|
436
|
-
summary,
|
|
437
|
-
commitMatch,
|
|
438
|
-
commitMatchReason,
|
|
439
|
-
errorCount,
|
|
440
|
-
warningCount,
|
|
441
|
-
infoCount,
|
|
442
|
-
riskCount,
|
|
361
|
+
aiResponse: JSON.stringify(result, null, 2),
|
|
362
|
+
summary: result.summary || '',
|
|
363
|
+
commitMatch: result.commitMatch || false,
|
|
364
|
+
commitMatchReason: result.commitMatchReason || '',
|
|
365
|
+
errorCount: stats.p0 + stats.p1,
|
|
366
|
+
warningCount: stats.p2,
|
|
367
|
+
infoCount: stats.p3,
|
|
368
|
+
riskCount: stats.risks,
|
|
443
369
|
modelUsed: config.model,
|
|
444
|
-
issues,
|
|
445
|
-
associationRisks
|
|
370
|
+
issues: result.findings || [],
|
|
371
|
+
associationRisks: result.associationRisks || [],
|
|
372
|
+
dimensions: result.dimensions || [],
|
|
373
|
+
overallAssessment: result.overall_assessment || {}
|
|
446
374
|
});
|
|
447
375
|
|
|
448
376
|
results.push({ commit, reviewId, success: true });
|
package/src/commands/report.js
CHANGED
|
@@ -18,6 +18,7 @@ export async function reportCommand(options) {
|
|
|
18
18
|
const generator = new ReportGenerator();
|
|
19
19
|
|
|
20
20
|
let spinner;
|
|
21
|
+
let usedCache = false;
|
|
21
22
|
|
|
22
23
|
try {
|
|
23
24
|
if (options.developer) {
|
|
@@ -48,10 +49,19 @@ export async function reportCommand(options) {
|
|
|
48
49
|
const outputPath = await generator.generateDeveloperReport(
|
|
49
50
|
options.developer,
|
|
50
51
|
options,
|
|
51
|
-
(msg) => {
|
|
52
|
+
(msg) => {
|
|
53
|
+
spinner.text = msg;
|
|
54
|
+
if (msg.includes('缓存')) {
|
|
55
|
+
usedCache = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
52
58
|
);
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
if (usedCache) {
|
|
61
|
+
spinner.succeed('使用缓存报告 (数据无变化)');
|
|
62
|
+
} else {
|
|
63
|
+
spinner.succeed('报告生成完成');
|
|
64
|
+
}
|
|
55
65
|
console.log();
|
|
56
66
|
logger.success(`报告已保存到: ${outputPath}`);
|
|
57
67
|
|
|
@@ -102,10 +112,19 @@ export async function reportCommand(options) {
|
|
|
102
112
|
const outputPath = await generator.generateProjectReport(
|
|
103
113
|
projectName,
|
|
104
114
|
options,
|
|
105
|
-
(msg) => {
|
|
115
|
+
(msg) => {
|
|
116
|
+
spinner.text = msg;
|
|
117
|
+
if (msg.includes('缓存')) {
|
|
118
|
+
usedCache = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
106
121
|
);
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
if (usedCache) {
|
|
124
|
+
spinner.succeed('使用缓存报告 (数据无变化)');
|
|
125
|
+
} else {
|
|
126
|
+
spinner.succeed('报告生成完成');
|
|
127
|
+
}
|
|
109
128
|
console.log();
|
|
110
129
|
logger.success(`报告已保存到: ${outputPath}`);
|
|
111
130
|
console.log();
|
|
@@ -114,6 +133,9 @@ export async function reportCommand(options) {
|
|
|
114
133
|
console.log(` 项目: ${projectName}`);
|
|
115
134
|
console.log(` Reviews: ${stats.total_reviews}`);
|
|
116
135
|
console.log(` 问题: ${stats.total_errors || 0}E / ${stats.total_warnings || 0}W / ${stats.total_infos || 0}I`);
|
|
136
|
+
if (usedCache) {
|
|
137
|
+
console.log(` 状态: 使用缓存 (无新数据)`);
|
|
138
|
+
}
|
|
117
139
|
|
|
118
140
|
if (options.open) {
|
|
119
141
|
console.log();
|