goodiffer 1.2.0 → 1.3.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/README.md +121 -3
- package/bin/goodiffer.js +5 -5
- package/package.json +1 -1
- package/src/commands/analyze.js +104 -177
- package/src/commands/report.js +4 -3
- package/src/prompts/codex-review-prompt.js +328 -0
- package/src/prompts/report-prompt.js +136 -28
- 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 +369 -0
- package/src/services/lsp-service.js +635 -0
- package/src/services/report-generator.js +127 -7
- package/src/prompts/review-prompt.js +0 -224
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);
|
|
@@ -88,7 +88,7 @@ program
|
|
|
88
88
|
.option('--since <date>', '开始日期 (YYYY-MM-DD)')
|
|
89
89
|
.option('--until <date>', '结束日期 (YYYY-MM-DD)')
|
|
90
90
|
.option('-o, --output <path>', '输出文件路径')
|
|
91
|
-
.option('--open', '
|
|
91
|
+
.option('--no-open', '不自动打开浏览器')
|
|
92
92
|
.option('-f, --force', '强制重新生成 (忽略缓存)')
|
|
93
93
|
.action(async (options) => {
|
|
94
94
|
await reportCommand(options);
|
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,71 +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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
}
|
|
144
|
-
},
|
|
145
|
-
10 // 增加最大迭代次数到 10
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
await codeContext.close();
|
|
149
|
-
} else {
|
|
150
|
-
// 普通分析模式
|
|
151
|
-
response = await aiClient.analyzeStream(prompt, (chunk) => {
|
|
152
|
-
spinner.text = `AI 正在分析... (${response.length} 字符)`;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
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}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
155
143
|
} catch (error) {
|
|
156
|
-
spinner.fail('
|
|
144
|
+
spinner.fail('分析失败');
|
|
157
145
|
if (error.message.includes('401')) {
|
|
158
146
|
logger.error('API Key 无效或已过期');
|
|
159
147
|
} else if (error.message.includes('403')) {
|
|
@@ -174,10 +162,8 @@ export async function analyzeCommand(options) {
|
|
|
174
162
|
process.exit(1);
|
|
175
163
|
}
|
|
176
164
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// 生成报告
|
|
180
|
-
generateReport(response, commitInfo);
|
|
165
|
+
// 生成 Codex 格式报告
|
|
166
|
+
generateCodexReport(result, commitInfo);
|
|
181
167
|
|
|
182
168
|
// 保存到数据库 (除非指定 --no-save)
|
|
183
169
|
if (options.save !== false) {
|
|
@@ -190,44 +176,8 @@ export async function analyzeCommand(options) {
|
|
|
190
176
|
// 获取或创建开发者
|
|
191
177
|
const developer = db.getOrCreateDeveloper(author.email, author.name);
|
|
192
178
|
|
|
193
|
-
// 解析 AI 响应
|
|
194
|
-
const parsed = parseAIResponse(response);
|
|
195
|
-
|
|
196
179
|
// 提取统计数据
|
|
197
|
-
|
|
198
|
-
let commitMatch = false;
|
|
199
|
-
let commitMatchReason = '';
|
|
200
|
-
let errorCount = 0;
|
|
201
|
-
let warningCount = 0;
|
|
202
|
-
let infoCount = 0;
|
|
203
|
-
let riskCount = 0;
|
|
204
|
-
let issues = [];
|
|
205
|
-
let associationRisks = [];
|
|
206
|
-
|
|
207
|
-
if (parsed) {
|
|
208
|
-
summary = parsed.summary || '';
|
|
209
|
-
commitMatch = parsed.commitMatch || false;
|
|
210
|
-
commitMatchReason = parsed.commitMatchReason || '';
|
|
211
|
-
|
|
212
|
-
// 支持新格式 findings (按优先级统计)
|
|
213
|
-
if (parsed.findings && Array.isArray(parsed.findings)) {
|
|
214
|
-
issues = parsed.findings;
|
|
215
|
-
errorCount = issues.filter(i => i.priority === 0 || i.priority === 1).length; // P0, P1 算 error
|
|
216
|
-
warningCount = issues.filter(i => i.priority === 2).length; // P2 算 warning
|
|
217
|
-
infoCount = issues.filter(i => i.priority === 3 || i.priority === undefined).length; // P3 算 info
|
|
218
|
-
} else if (parsed.issues && Array.isArray(parsed.issues)) {
|
|
219
|
-
// 兼容旧格式
|
|
220
|
-
issues = parsed.issues;
|
|
221
|
-
errorCount = issues.filter(i => i.level === 'error').length;
|
|
222
|
-
warningCount = issues.filter(i => i.level === 'warning').length;
|
|
223
|
-
infoCount = issues.filter(i => i.level === 'info').length;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (parsed.associationRisks && Array.isArray(parsed.associationRisks)) {
|
|
227
|
-
associationRisks = parsed.associationRisks;
|
|
228
|
-
riskCount = associationRisks.length;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
180
|
+
const stats = extractStats(result);
|
|
231
181
|
|
|
232
182
|
// 保存 review 记录
|
|
233
183
|
const reviewId = db.saveReview({
|
|
@@ -243,18 +193,20 @@ export async function analyzeCommand(options) {
|
|
|
243
193
|
filesChanged: diffStats.filesChanged,
|
|
244
194
|
insertions: diffStats.insertions,
|
|
245
195
|
deletions: diffStats.deletions,
|
|
246
|
-
diffContent: null,
|
|
247
|
-
aiResponse:
|
|
248
|
-
summary,
|
|
249
|
-
commitMatch,
|
|
250
|
-
commitMatchReason,
|
|
251
|
-
errorCount,
|
|
252
|
-
warningCount,
|
|
253
|
-
infoCount,
|
|
254
|
-
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,
|
|
255
205
|
modelUsed: config.model,
|
|
256
|
-
issues,
|
|
257
|
-
associationRisks
|
|
206
|
+
issues: result.findings || [],
|
|
207
|
+
associationRisks: result.associationRisks || [],
|
|
208
|
+
dimensions: result.dimensions || [],
|
|
209
|
+
overallAssessment: result.overall_assessment || {}
|
|
258
210
|
});
|
|
259
211
|
|
|
260
212
|
logger.success(`Review #${reviewId} 已保存到数据库`);
|
|
@@ -284,7 +236,6 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
284
236
|
}
|
|
285
237
|
|
|
286
238
|
if (n !== null && m === null) {
|
|
287
|
-
// 只有 -n,表示分析最近 n 条
|
|
288
239
|
if (n <= 0 || n > 10) {
|
|
289
240
|
logger.error('n 必须在 1-10 之间');
|
|
290
241
|
process.exit(1);
|
|
@@ -292,7 +243,6 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
292
243
|
}
|
|
293
244
|
|
|
294
245
|
if (n !== null && m !== null) {
|
|
295
|
-
// 同时有 -n 和 -m,表示第 n 条到第 m 条
|
|
296
246
|
if (n <= 0 || m <= 0) {
|
|
297
247
|
logger.error('n 和 m 必须大于 0');
|
|
298
248
|
process.exit(1);
|
|
@@ -313,10 +263,8 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
313
263
|
// 获取 commits
|
|
314
264
|
let commits;
|
|
315
265
|
if (m !== null) {
|
|
316
|
-
// 第 n 条到第 m 条
|
|
317
266
|
commits = await git.getCommitRange(n, m);
|
|
318
267
|
} else {
|
|
319
|
-
// 最近 n 条
|
|
320
268
|
commits = await git.getRecentCommits(n);
|
|
321
269
|
}
|
|
322
270
|
|
|
@@ -347,10 +295,9 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
347
295
|
const commit = commits[i];
|
|
348
296
|
const shortSha = commit.sha.substring(0, 7);
|
|
349
297
|
|
|
350
|
-
spinner = ora(`[${i + 1}/${commits.length}] 分析 commit ${shortSha}...`).start();
|
|
298
|
+
spinner = ora(`[${i + 1}/${commits.length}] Codex 分析 commit ${shortSha}...`).start();
|
|
351
299
|
|
|
352
300
|
try {
|
|
353
|
-
// 获取 diff
|
|
354
301
|
const diff = await git.getCommitDiff(commit.sha);
|
|
355
302
|
if (!diff || diff.trim() === '') {
|
|
356
303
|
spinner.warn(`[${i + 1}/${commits.length}] commit ${shortSha} 没有代码变更,跳过`);
|
|
@@ -358,14 +305,26 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
358
305
|
}
|
|
359
306
|
|
|
360
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
|
+
});
|
|
361
317
|
|
|
362
|
-
|
|
363
|
-
const prompt = buildReviewPrompt(commit.message, diff);
|
|
364
|
-
let response = '';
|
|
318
|
+
let result = null;
|
|
365
319
|
|
|
366
320
|
try {
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
}
|
|
369
328
|
});
|
|
370
329
|
} catch (error) {
|
|
371
330
|
spinner.fail(`[${i + 1}/${commits.length}] commit ${shortSha} 分析失败: ${error.message}`);
|
|
@@ -375,7 +334,7 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
375
334
|
spinner.succeed(`[${i + 1}/${commits.length}] commit ${shortSha} 分析完成`);
|
|
376
335
|
|
|
377
336
|
// 生成报告
|
|
378
|
-
|
|
337
|
+
generateCodexReport(result, { sha: commit.sha, message: commit.message });
|
|
379
338
|
|
|
380
339
|
// 保存到数据库
|
|
381
340
|
if (options.save !== false) {
|
|
@@ -383,41 +342,7 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
383
342
|
const db = getDatabase();
|
|
384
343
|
const project = db.getOrCreateProject(projectName, process.cwd());
|
|
385
344
|
const developer = db.getOrCreateDeveloper(commit.author.email, commit.author.name);
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
let summary = '';
|
|
389
|
-
let commitMatch = false;
|
|
390
|
-
let commitMatchReason = '';
|
|
391
|
-
let errorCount = 0;
|
|
392
|
-
let warningCount = 0;
|
|
393
|
-
let infoCount = 0;
|
|
394
|
-
let riskCount = 0;
|
|
395
|
-
let issues = [];
|
|
396
|
-
let associationRisks = [];
|
|
397
|
-
|
|
398
|
-
if (parsed) {
|
|
399
|
-
summary = parsed.summary || '';
|
|
400
|
-
commitMatch = parsed.commitMatch || false;
|
|
401
|
-
commitMatchReason = parsed.commitMatchReason || '';
|
|
402
|
-
|
|
403
|
-
// 支持新格式 findings (按优先级统计)
|
|
404
|
-
if (parsed.findings && Array.isArray(parsed.findings)) {
|
|
405
|
-
issues = parsed.findings;
|
|
406
|
-
errorCount = issues.filter(i => i.priority === 0 || i.priority === 1).length;
|
|
407
|
-
warningCount = issues.filter(i => i.priority === 2).length;
|
|
408
|
-
infoCount = issues.filter(i => i.priority === 3 || i.priority === undefined).length;
|
|
409
|
-
} else if (parsed.issues && Array.isArray(parsed.issues)) {
|
|
410
|
-
issues = parsed.issues;
|
|
411
|
-
errorCount = issues.filter(i => i.level === 'error').length;
|
|
412
|
-
warningCount = issues.filter(i => i.level === 'warning').length;
|
|
413
|
-
infoCount = issues.filter(i => i.level === 'info').length;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (parsed.associationRisks && Array.isArray(parsed.associationRisks)) {
|
|
417
|
-
associationRisks = parsed.associationRisks;
|
|
418
|
-
riskCount = associationRisks.length;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
345
|
+
const stats = extractStats(result);
|
|
421
346
|
|
|
422
347
|
const reviewId = db.saveReview({
|
|
423
348
|
projectId: project.id,
|
|
@@ -433,17 +358,19 @@ async function analyzeMultipleCommits(options, config, git) {
|
|
|
433
358
|
insertions: diffStats.insertions,
|
|
434
359
|
deletions: diffStats.deletions,
|
|
435
360
|
diffContent: null,
|
|
436
|
-
aiResponse:
|
|
437
|
-
summary,
|
|
438
|
-
commitMatch,
|
|
439
|
-
commitMatchReason,
|
|
440
|
-
errorCount,
|
|
441
|
-
warningCount,
|
|
442
|
-
infoCount,
|
|
443
|
-
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,
|
|
444
369
|
modelUsed: config.model,
|
|
445
|
-
issues,
|
|
446
|
-
associationRisks
|
|
370
|
+
issues: result.findings || [],
|
|
371
|
+
associationRisks: result.associationRisks || [],
|
|
372
|
+
dimensions: result.dimensions || [],
|
|
373
|
+
overallAssessment: result.overall_assessment || {}
|
|
447
374
|
});
|
|
448
375
|
|
|
449
376
|
results.push({ commit, reviewId, success: true });
|
package/src/commands/report.js
CHANGED
|
@@ -65,7 +65,8 @@ export async function reportCommand(options) {
|
|
|
65
65
|
console.log();
|
|
66
66
|
logger.success(`报告已保存到: ${outputPath}`);
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// 自动在浏览器中打开报告
|
|
69
|
+
if (options.open !== false) {
|
|
69
70
|
await open(outputPath);
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -137,9 +138,9 @@ export async function reportCommand(options) {
|
|
|
137
138
|
console.log(` 状态: 使用缓存 (无新数据)`);
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
// 自动在浏览器中打开报告
|
|
142
|
+
if (options.open !== false) {
|
|
141
143
|
console.log();
|
|
142
|
-
logger.info('正在打开报告...');
|
|
143
144
|
await open(outputPath);
|
|
144
145
|
}
|
|
145
146
|
}
|