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 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
- - 🔮 **NEW** 支持代码上下文获取 (Tool Use),AI 可按需读取相关源码
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
- > **注意**: 此功能需要 Claude 模型(使用 Tool Use API),建议在 Claude Code 环境中使用。
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 for code review')
15
- .version('1.1.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('--context', '启用代码上下文获取 (需要 Claude Code 环境)')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goodiffer",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "AI-powered git diff analyzer for code review - 智能代码审查工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 { buildReviewPrompt, buildReviewPromptWithTools } from '../prompts/review-prompt.js';
6
- import { generateReport } from '../services/reporter.js';
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
- // 解析 AI 响应
13
- function parseAIResponse(response) {
14
- try {
15
- let jsonStr = response;
16
- const jsonMatch = response.match(/```(?:json)?\s*([\s\S]*?)```/);
17
- if (jsonMatch) {
18
- jsonStr = jsonMatch[1].trim();
19
- }
20
- return JSON.parse(jsonStr);
21
- } catch {
22
- return null;
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 useContext = options.context;
100
-
101
- // 如果启用上下文模式,提示信息
102
- if (useContext) {
103
- if (MCPClientService.isInClaudeCode()) {
104
- logger.info('已启用代码上下文模式 (MCP)');
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('AI 正在分析代码...').start();
120
+ spinner = ora('Codex 深度分析中...').start();
117
121
 
118
122
  const aiClient = new AIClient(config);
119
- let response = '';
123
+ let result = null;
120
124
 
121
125
  try {
122
- if (useContext) {
123
- // 启用代码上下文的分析模式
124
- const codeContext = new CodeContextService(process.cwd());
125
- await codeContext.initialize(true); // 尝试启用 MCP
126
-
127
- const tools = CodeContextService.getToolDefinitions();
128
-
129
- response = await aiClient.analyzeWithTools(
130
- prompt,
131
- tools,
132
- async (toolName, toolInput) => {
133
- return await codeContext.executeTool(toolName, toolInput);
134
- },
135
- (progress) => {
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('AI 分析失败');
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
- spinner.succeed('AI 分析完成');
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
- let summary = '';
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, // 不存储 diff 内容以节省空间
246
- aiResponse: response,
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
- response = await aiClient.analyzeStream(prompt, (chunk) => {
367
- spinner.text = `[${i + 1}/${commits.length}] 分析 commit ${shortSha}... (${response.length} 字符)`;
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
- generateReport(response, { sha: commit.sha, message: commit.message });
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 parsed = parseAIResponse(response);
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: response,
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 });
@@ -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) => { spinner.text = msg; }
52
+ (msg) => {
53
+ spinner.text = msg;
54
+ if (msg.includes('缓存')) {
55
+ usedCache = true;
56
+ }
57
+ }
52
58
  );
53
59
 
54
- spinner.succeed('报告生成完成');
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) => { spinner.text = msg; }
115
+ (msg) => {
116
+ spinner.text = msg;
117
+ if (msg.includes('缓存')) {
118
+ usedCache = true;
119
+ }
120
+ }
106
121
  );
107
122
 
108
- spinner.succeed('报告生成完成');
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();