goodiffer 1.0.0 → 1.0.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/bin/goodiffer.js +92 -2
- package/package.json +4 -1
- package/src/commands/analyze.js +383 -97
- package/src/commands/developer.js +116 -0
- package/src/commands/history.js +120 -0
- package/src/commands/init.js +76 -86
- package/src/commands/report.js +138 -0
- package/src/commands/stats.js +139 -0
- package/src/index.js +9 -35
- package/src/prompts/report-prompt.js +187 -0
- package/src/prompts/review-prompt.js +26 -46
- package/src/services/database.js +524 -0
- package/src/services/git.js +200 -101
- package/src/services/report-generator.js +298 -0
- package/src/services/reporter.js +96 -97
- package/src/utils/config-store.js +6 -12
- package/src/utils/logger.js +33 -33
package/src/services/reporter.js
CHANGED
|
@@ -1,155 +1,154 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import logger from '../utils/logger.js';
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function generateReport(aiResponse, commitInfo) {
|
|
5
|
+
console.log();
|
|
6
|
+
console.log(chalk.cyan('╭──────────────────────────────────────────────────────────╮'));
|
|
7
|
+
console.log(chalk.cyan('│ ') + chalk.bold.white('Goodiffer Analysis Report') + chalk.cyan(' │'));
|
|
8
|
+
console.log(chalk.cyan('╰──────────────────────────────────────────────────────────╯'));
|
|
9
|
+
console.log();
|
|
10
|
+
|
|
11
|
+
// 显示 commit 信息
|
|
12
|
+
console.log(chalk.blue('📝 Commit:'), commitInfo.message);
|
|
13
|
+
console.log();
|
|
14
|
+
|
|
15
|
+
// 尝试解析 JSON 响应
|
|
16
|
+
let result;
|
|
5
17
|
try {
|
|
6
|
-
//
|
|
7
|
-
|
|
18
|
+
// 提取 JSON 部分 (可能包含在 markdown 代码块中)
|
|
19
|
+
let jsonStr = aiResponse;
|
|
20
|
+
const jsonMatch = aiResponse.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
8
21
|
if (jsonMatch) {
|
|
9
|
-
|
|
22
|
+
jsonStr = jsonMatch[1].trim();
|
|
10
23
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
throw new Error('无法解析 AI 响应');
|
|
19
|
-
} catch (error) {
|
|
20
|
-
logger.error(`解析 AI 响应失败: ${error.message}`);
|
|
21
|
-
return null;
|
|
24
|
+
result = JSON.parse(jsonStr);
|
|
25
|
+
} catch {
|
|
26
|
+
// 如果无法解析为 JSON,直接显示原始响应
|
|
27
|
+
console.log(chalk.yellow('📊 分析结果:'));
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(aiResponse);
|
|
30
|
+
return;
|
|
22
31
|
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function printReport(commitInfo, report) {
|
|
26
|
-
console.log('\n');
|
|
27
32
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Commit 信息
|
|
34
|
-
console.log(chalk.blue('\n📝 Commit: ') + chalk.white(commitInfo.message.split('\n')[0]));
|
|
35
|
-
console.log(chalk.gray(` SHA: ${commitInfo.sha.substring(0, 8)} | ${commitInfo.author} | ${commitInfo.date}`));
|
|
36
|
-
|
|
37
|
-
// Summary
|
|
38
|
-
if (report.summary) {
|
|
39
|
-
console.log(chalk.green('\n📊 Summary: ') + chalk.white(report.summary));
|
|
33
|
+
// 显示摘要
|
|
34
|
+
if (result.summary) {
|
|
35
|
+
console.log(chalk.green('📊 Summary:'), result.summary);
|
|
36
|
+
console.log();
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
//
|
|
43
|
-
if (
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
console.log(
|
|
39
|
+
// 显示 commit 匹配情况
|
|
40
|
+
if (result.commitMatch !== undefined) {
|
|
41
|
+
const matchIcon = result.commitMatch ? chalk.green('✓') : chalk.red('✗');
|
|
42
|
+
console.log(chalk.blue('🎯 Commit 匹配:'), matchIcon, result.commitMatchReason || '');
|
|
43
|
+
console.log();
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
logger.divider();
|
|
50
47
|
|
|
51
|
-
//
|
|
52
|
-
const issues =
|
|
48
|
+
// 按级别分组显示问题
|
|
49
|
+
const issues = result.issues || [];
|
|
53
50
|
const errors = issues.filter(i => i.level === 'error');
|
|
54
51
|
const warnings = issues.filter(i => i.level === 'warning');
|
|
55
52
|
const infos = issues.filter(i => i.level === 'info');
|
|
56
53
|
|
|
54
|
+
// 显示错误
|
|
57
55
|
if (errors.length > 0) {
|
|
58
|
-
console.log(
|
|
59
|
-
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk.red.bold(`🔴 ERRORS (${errors.length})`));
|
|
58
|
+
console.log();
|
|
59
|
+
errors.forEach((issue, index) => {
|
|
60
|
+
printIssue(issue, `E${String(index + 1).padStart(3, '0')}`, chalk.red);
|
|
61
|
+
});
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
// 显示警告
|
|
62
65
|
if (warnings.length > 0) {
|
|
63
|
-
console.log(
|
|
64
|
-
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(chalk.yellow.bold(`🟡 WARNINGS (${warnings.length})`));
|
|
68
|
+
console.log();
|
|
69
|
+
warnings.forEach((issue, index) => {
|
|
70
|
+
printIssue(issue, `W${String(index + 1).padStart(3, '0')}`, chalk.yellow);
|
|
71
|
+
});
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
// 显示信息
|
|
67
75
|
if (infos.length > 0) {
|
|
68
|
-
console.log(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(chalk.blue.bold(`🔵 INFO (${infos.length})`));
|
|
78
|
+
console.log();
|
|
79
|
+
infos.forEach((issue, index) => {
|
|
80
|
+
printIssue(issue, `I${String(index + 1).padStart(3, '0')}`, chalk.blue);
|
|
81
|
+
});
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
//
|
|
77
|
-
const risks =
|
|
84
|
+
// 显示关联风险
|
|
85
|
+
const risks = result.associationRisks || [];
|
|
78
86
|
if (risks.length > 0) {
|
|
79
87
|
logger.divider();
|
|
80
|
-
console.log(
|
|
81
|
-
risks.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const suggestions = report.suggestions || [];
|
|
86
|
-
if (suggestions.length > 0) {
|
|
87
|
-
logger.divider();
|
|
88
|
-
console.log(chalk.cyan.bold('\n💡 建议'));
|
|
89
|
-
suggestions.forEach((s, idx) => {
|
|
90
|
-
console.log(chalk.gray(` ${idx + 1}. `) + chalk.white(s));
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.magenta.bold(`🔗 ASSOCIATION RISKS (${risks.length})`));
|
|
90
|
+
console.log();
|
|
91
|
+
risks.forEach((risk, index) => {
|
|
92
|
+
printRisk(risk, index + 1);
|
|
91
93
|
});
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
//
|
|
96
|
+
// 统计摘要
|
|
95
97
|
logger.divider();
|
|
96
|
-
console.log(
|
|
97
|
-
|
|
98
|
-
chalk.
|
|
99
|
-
chalk.
|
|
100
|
-
chalk.
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
console.log();
|
|
99
|
+
console.log(
|
|
100
|
+
chalk.gray('📈 统计:'),
|
|
101
|
+
chalk.red(`${errors.length} errors`),
|
|
102
|
+
chalk.yellow(`${warnings.length} warnings`),
|
|
103
|
+
chalk.blue(`${infos.length} info`),
|
|
104
|
+
chalk.magenta(`${risks.length} risks`)
|
|
105
|
+
);
|
|
106
|
+
console.log();
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
function printIssue(issue,
|
|
106
|
-
console.log('');
|
|
107
|
-
console.log(chalk.
|
|
108
|
-
console.log(chalk.gray('类型: ') + chalk.white(issue.type));
|
|
109
|
-
console.log(chalk.gray('问题: ') + chalk.white(issue.description));
|
|
109
|
+
function printIssue(issue, id, colorFn) {
|
|
110
|
+
console.log(colorFn(`[${id}]`), chalk.gray(`${issue.file}:${issue.line || '?'}`));
|
|
111
|
+
console.log(chalk.white('问题:'), issue.description);
|
|
110
112
|
|
|
111
113
|
if (issue.code) {
|
|
112
114
|
console.log(chalk.gray('代码:'));
|
|
113
|
-
console.log(chalk.gray('
|
|
114
|
-
issue.code.split('\n').forEach(line => {
|
|
115
|
-
console.log(chalk.gray(' │ ') + chalk.red(line));
|
|
116
|
-
});
|
|
117
|
-
console.log(chalk.gray(' └─'));
|
|
115
|
+
console.log(chalk.gray(' ') + issue.code.split('\n').join('\n '));
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
if (issue.suggestion) {
|
|
121
|
-
console.log(chalk.
|
|
119
|
+
console.log(chalk.green('建议:'), issue.suggestion);
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
if (issue.fixPrompt) {
|
|
125
|
-
console.log(
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(chalk.cyan('📋 修复提示词 (复制到 cc/codex):'));
|
|
126
125
|
console.log(chalk.gray('┌' + '─'.repeat(56) + '┐'));
|
|
127
|
-
issue.fixPrompt.split('\n')
|
|
128
|
-
|
|
129
|
-
console.log(chalk.gray('│ ') +
|
|
126
|
+
const lines = issue.fixPrompt.split('\n');
|
|
127
|
+
lines.forEach(line => {
|
|
128
|
+
console.log(chalk.gray('│ ') + line.padEnd(54) + chalk.gray(' │'));
|
|
130
129
|
});
|
|
131
130
|
console.log(chalk.gray('└' + '─'.repeat(56) + '┘'));
|
|
132
131
|
}
|
|
132
|
+
console.log();
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
function printRisk(risk) {
|
|
136
|
-
console.log('');
|
|
137
|
-
console.log(chalk.white('修改文件:
|
|
138
|
-
console.log(chalk.white('可能影响:
|
|
139
|
-
console.log(chalk.white('风险:
|
|
135
|
+
function printRisk(risk, index) {
|
|
136
|
+
console.log(chalk.magenta(`[R${String(index).padStart(3, '0')}]`));
|
|
137
|
+
console.log(chalk.white('修改文件:'), risk.changedFile);
|
|
138
|
+
console.log(chalk.white('可能影响:'), (risk.relatedFiles || []).join(', '));
|
|
139
|
+
console.log(chalk.white('风险:'), risk.risk);
|
|
140
140
|
|
|
141
141
|
if (risk.checkPrompt) {
|
|
142
|
-
console.log(
|
|
142
|
+
console.log();
|
|
143
|
+
console.log(chalk.cyan('📋 检查提示词:'));
|
|
143
144
|
console.log(chalk.gray('┌' + '─'.repeat(56) + '┐'));
|
|
144
|
-
risk.checkPrompt.split('\n')
|
|
145
|
-
|
|
146
|
-
console.log(chalk.gray('│ ') +
|
|
145
|
+
const lines = risk.checkPrompt.split('\n');
|
|
146
|
+
lines.forEach(line => {
|
|
147
|
+
console.log(chalk.gray('│ ') + line.padEnd(54) + chalk.gray(' │'));
|
|
147
148
|
});
|
|
148
149
|
console.log(chalk.gray('└' + '─'.repeat(56) + '┘'));
|
|
149
150
|
}
|
|
151
|
+
console.log();
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
export default
|
|
153
|
-
parseAIResponse,
|
|
154
|
-
printReport
|
|
155
|
-
};
|
|
154
|
+
export default generateReport;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
2
|
|
|
3
3
|
const config = new Conf({
|
|
4
|
-
projectName: 'goodiffer',
|
|
4
|
+
projectName: 'goodiffer-nodejs',
|
|
5
5
|
schema: {
|
|
6
6
|
provider: {
|
|
7
7
|
type: 'string',
|
|
8
8
|
enum: ['claude', 'openai', 'custom'],
|
|
9
|
-
default: '
|
|
9
|
+
default: 'claude'
|
|
10
10
|
},
|
|
11
11
|
apiHost: {
|
|
12
12
|
type: 'string',
|
|
@@ -36,19 +36,13 @@ export function setConfig(key, value) {
|
|
|
36
36
|
config.set(key, value);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function
|
|
40
|
-
|
|
41
|
-
config.set(key, value);
|
|
42
|
-
}
|
|
39
|
+
export function clearConfig() {
|
|
40
|
+
config.clear();
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
export function isConfigured() {
|
|
46
|
-
const
|
|
47
|
-
return apiKey &&
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function clearConfig() {
|
|
51
|
-
config.clear();
|
|
44
|
+
const cfg = getConfig();
|
|
45
|
+
return cfg.apiKey && cfg.apiHost && cfg.model;
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
export default config;
|
package/src/utils/logger.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
info
|
|
5
|
-
|
|
6
|
-
warn: (msg) => console.log(chalk.yellow('⚠'), msg),
|
|
7
|
-
error: (msg) => console.log(chalk.red('✖'), msg),
|
|
8
|
-
|
|
9
|
-
title: (msg) => console.log(chalk.bold.cyan(`\n${msg}\n`)),
|
|
10
|
-
|
|
11
|
-
box: (title, content) => {
|
|
12
|
-
const width = 50;
|
|
13
|
-
const top = '╭' + '─'.repeat(width - 2) + '╮';
|
|
14
|
-
const bottom = '╰' + '─'.repeat(width - 2) + '╯';
|
|
15
|
-
const line = (text) => '│ ' + text.padEnd(width - 4) + ' │';
|
|
16
|
-
|
|
17
|
-
console.log(chalk.cyan(top));
|
|
18
|
-
console.log(chalk.cyan(line(chalk.bold(title))));
|
|
19
|
-
console.log(chalk.cyan(bottom));
|
|
20
|
-
if (content) {
|
|
21
|
-
console.log(content);
|
|
22
|
-
}
|
|
3
|
+
const logger = {
|
|
4
|
+
info(message) {
|
|
5
|
+
console.log(chalk.blue('ℹ'), message);
|
|
23
6
|
},
|
|
24
7
|
|
|
25
|
-
|
|
8
|
+
success(message) {
|
|
9
|
+
console.log(chalk.green('✔'), message);
|
|
10
|
+
},
|
|
26
11
|
|
|
27
|
-
|
|
28
|
-
console.log(chalk.
|
|
29
|
-
code.split('\n').forEach(line => {
|
|
30
|
-
console.log(chalk.gray('│ ') + chalk.white(line));
|
|
31
|
-
});
|
|
32
|
-
console.log(chalk.gray('└' + '─'.repeat(48) + '┘'));
|
|
12
|
+
warning(message) {
|
|
13
|
+
console.log(chalk.yellow('⚠'), message);
|
|
33
14
|
},
|
|
34
15
|
|
|
35
|
-
|
|
36
|
-
console.log(chalk.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
16
|
+
error(message) {
|
|
17
|
+
console.log(chalk.red('✖'), message);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
title(message) {
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(chalk.bold.cyan(`━━━ ${message} ━━━`));
|
|
23
|
+
console.log();
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
box(title, content) {
|
|
27
|
+
const lines = content.split('\n');
|
|
28
|
+
const maxLen = Math.max(title.length, ...lines.map(l => l.length));
|
|
29
|
+
const border = '─'.repeat(maxLen + 2);
|
|
30
|
+
|
|
31
|
+
console.log(chalk.gray(`┌${border}┐`));
|
|
32
|
+
console.log(chalk.gray('│'), chalk.bold(title.padEnd(maxLen)), chalk.gray('│'));
|
|
33
|
+
console.log(chalk.gray(`├${border}┤`));
|
|
34
|
+
lines.forEach(line => {
|
|
35
|
+
console.log(chalk.gray('│'), line.padEnd(maxLen), chalk.gray('│'));
|
|
40
36
|
});
|
|
41
|
-
console.log(chalk.gray(
|
|
37
|
+
console.log(chalk.gray(`└${border}┘`));
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
divider() {
|
|
41
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
|