ai-git-tools 1.0.5 → 2.0.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/bin/cli.js +24 -45
- package/package.json +1 -1
- package/src/commands/commit-all.js +250 -189
- package/src/commands/commit.js +92 -76
- package/src/commands/init.js +33 -131
- package/src/commands/pr.js +121 -237
- package/src/core/ai-client.js +19 -84
- package/src/core/config-loader.js +165 -133
- package/src/core/git-operations.js +90 -201
- package/src/utils/helpers.js +43 -54
- package/src/utils/logger.js +28 -100
- package/src/commands/workflow.js +0 -36
- package/src/core/github-api.js +0 -139
- package/src/index.js +0 -16
package/src/commands/commit.js
CHANGED
|
@@ -1,120 +1,136 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Commit 命令
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* 基于 scripts/ai-auto-commit.mjs
|
|
4
|
+
* 自动生成 commit message 并执行 commit
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import { AIClient } from '../core/ai-client.js';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { loadCommitConfig } from '../core/config-loader.js';
|
|
10
9
|
import { GitOperations } from '../core/git-operations.js';
|
|
10
|
+
import { AIClient } from '../core/ai-client.js';
|
|
11
11
|
import { Logger } from '../utils/logger.js';
|
|
12
|
-
import {
|
|
13
|
-
handleError,
|
|
14
|
-
validateCommitMessage,
|
|
15
|
-
truncateText,
|
|
16
|
-
getProjectTypePrompt,
|
|
17
|
-
getConventionalCommitsRules,
|
|
18
|
-
} from '../utils/helpers.js';
|
|
12
|
+
import { cleanCommitMessage, validateCommitMessage, handleError, getProjectTypePrompt } from '../utils/helpers.js';
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
export async function commitCommand(options) {
|
|
24
|
-
const logger = new Logger(options.verbose);
|
|
14
|
+
export async function commitCommand() {
|
|
15
|
+
const logger = new Logger();
|
|
25
16
|
|
|
26
17
|
try {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
logger.error('當前目錄不是 Git 倉庫');
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
18
|
+
// 载入配置
|
|
19
|
+
const config = await loadCommitConfig();
|
|
32
20
|
|
|
33
|
-
// 載入配置
|
|
34
|
-
const config = await loadConfig(options);
|
|
35
|
-
|
|
36
21
|
if (config.output.verbose) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
console.log('📋 使用配置:');
|
|
23
|
+
console.log(` AI Model: ${config.ai.model}`);
|
|
24
|
+
console.log(` Max Diff Length: ${config.ai.maxDiffLength}`);
|
|
25
|
+
console.log('');
|
|
40
26
|
}
|
|
41
27
|
|
|
42
|
-
//
|
|
28
|
+
// 检查是否有 staged 变更
|
|
43
29
|
const diff = GitOperations.getStagedDiff();
|
|
44
30
|
|
|
45
31
|
if (!diff.trim()) {
|
|
46
|
-
logger.error('
|
|
47
|
-
|
|
32
|
+
logger.error('没有 staged 的变更');
|
|
33
|
+
console.log('💡 请先使用 git add 来 stage 你的变更');
|
|
48
34
|
process.exit(1);
|
|
49
35
|
}
|
|
50
36
|
|
|
51
|
-
logger.
|
|
37
|
+
logger.step('正在分析变更内容...\n');
|
|
52
38
|
|
|
53
|
-
//
|
|
54
|
-
const truncatedDiff =
|
|
39
|
+
// 截断过长的 diff
|
|
40
|
+
const truncatedDiff =
|
|
41
|
+
diff.length > config.ai.maxDiffLength
|
|
42
|
+
? diff.substring(0, config.ai.maxDiffLength) + '\n\n... [diff 过长已截断]'
|
|
43
|
+
: diff;
|
|
55
44
|
|
|
56
45
|
if (config.output.verbose && diff.length > config.ai.maxDiffLength) {
|
|
57
|
-
logger.
|
|
46
|
+
logger.warning(`Diff 已从 ${diff.length} 字元截断至 ${config.ai.maxDiffLength} 字元\n`);
|
|
58
47
|
}
|
|
59
48
|
|
|
60
49
|
// 使用 AI 生成 commit message
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
let commitMessage = '';
|
|
51
|
+
let lastError = null;
|
|
52
|
+
|
|
53
|
+
for (let attempt = 1; attempt <= config.ai.maxRetries; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
if (config.output.verbose && attempt > 1) {
|
|
56
|
+
console.log(`🔄 重试第 ${attempt}/${config.ai.maxRetries} 次...\n`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const prompt = `${getProjectTypePrompt()}
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
请根据以下 git diff 产生一则 commit message。
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
**Commit Message 规则**:
|
|
64
|
+
1. 使用 Conventional Commits 格式:type(scope): subject
|
|
65
|
+
2. type 必须是:feat/fix/docs/style/refactor/test/chore/perf 其中之一
|
|
66
|
+
3. scope: 影响范围(如 member、report、auth、api、ui、config)
|
|
67
|
+
4. subject 限制在 50 字内,使用繁体中文
|
|
68
|
+
5. 如果变更复杂,加上 body 说明(使用 bullet points)
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
**重要**:
|
|
71
|
+
- 直接输出 commit message 纯文字,不要使用 markdown 程式码区块(\`\`\`)
|
|
72
|
+
- 不要加上任何前缀说明或后缀文字
|
|
73
|
+
- 第一行是标题,如有需要可加上空行后的详细说明
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
feat(
|
|
75
|
+
**范例格式**:
|
|
76
|
+
feat(member): 新增会员管理页面
|
|
73
77
|
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
- 整合
|
|
78
|
+
- 实作会员列表查询功能
|
|
79
|
+
- 新增会员资料编辑表单
|
|
80
|
+
- 整合 Zustand 状态管理
|
|
77
81
|
|
|
78
82
|
git diff:
|
|
79
83
|
${truncatedDiff}`;
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
const response = await AIClient.sendAndWait(prompt, config.ai.model);
|
|
86
|
+
commitMessage = cleanCommitMessage(response);
|
|
87
|
+
|
|
88
|
+
// 验证 commit message
|
|
89
|
+
const validation = validateCommitMessage(commitMessage);
|
|
90
|
+
if (!validation.valid) {
|
|
91
|
+
throw new Error(`无效的 commit message: ${validation.reason}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 成功生成,跳出重试循环
|
|
95
|
+
break;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
lastError = error;
|
|
98
|
+
if (config.output.verbose) {
|
|
99
|
+
logger.warning(`尝试 ${attempt} 失败: ${error.message}\n`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (attempt < config.ai.maxRetries) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
85
107
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
// 所有重试都失败
|
|
109
|
+
if (!commitMessage) {
|
|
110
|
+
logger.error('无法生成有效的 commit message');
|
|
111
|
+
if (lastError && config.output.verbose) {
|
|
112
|
+
console.log(` 最后错误: ${lastError.message}`);
|
|
113
|
+
}
|
|
114
|
+
console.log('\n💡 建议:');
|
|
115
|
+
console.log(' 1. 检查网路连线');
|
|
116
|
+
console.log(' 2. 尝试更换 AI 模型(使用 --model 参数)');
|
|
117
|
+
console.log(' 3. 确认变更内容不会太复杂或太大');
|
|
91
118
|
process.exit(1);
|
|
92
119
|
}
|
|
93
120
|
|
|
94
|
-
logger.
|
|
121
|
+
logger.success('生成的 Commit Message:');
|
|
122
|
+
logger.separator('─', 60);
|
|
123
|
+
console.log(commitMessage);
|
|
124
|
+
logger.separator('─', 60);
|
|
95
125
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
logger.startSpinner('正在執行 commit...');
|
|
102
|
-
await GitOperations.commit(commitMessage);
|
|
103
|
-
logger.succeedSpinner('Commit 完成!');
|
|
104
|
-
|
|
105
|
-
// 顯示最新的 commit
|
|
106
|
-
console.log(chalk.cyan('\n📋 最新 commit:'));
|
|
107
|
-
try {
|
|
108
|
-
const recentCommits = GitOperations.getRecentCommits(1);
|
|
109
|
-
if (recentCommits) {
|
|
110
|
-
console.log(recentCommits);
|
|
111
|
-
}
|
|
112
|
-
} catch (error) {
|
|
113
|
-
// 忽略顯示 commit 的錯誤
|
|
114
|
-
}
|
|
126
|
+
// 执行 commit
|
|
127
|
+
logger.step('\n正在执行 commit...');
|
|
128
|
+
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
129
|
+
stdio: 'inherit',
|
|
130
|
+
});
|
|
115
131
|
|
|
132
|
+
logger.success('Commit 完成!\n');
|
|
116
133
|
} catch (error) {
|
|
117
|
-
logger.failSpinner('操作失敗');
|
|
118
134
|
handleError(error);
|
|
119
135
|
process.exit(1);
|
|
120
136
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -1,163 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Init 命令
|
|
3
|
-
*
|
|
4
|
-
* 初始化配置檔案
|
|
3
|
+
* 初始化配置文件
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import { writeFileSync, existsSync } from 'fs';
|
|
8
7
|
import { resolve } from 'path';
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
import inquirer from 'inquirer';
|
|
11
8
|
import { Logger } from '../utils/logger.js';
|
|
12
|
-
import { handleError } from '../utils/helpers.js';
|
|
13
9
|
|
|
14
|
-
const
|
|
15
|
-
* AI Git Tools
|
|
10
|
+
const DEFAULT_CONFIG = `/**
|
|
11
|
+
* AI Git Tools 配置文件
|
|
16
12
|
*
|
|
17
|
-
*
|
|
18
|
-
* - gitai commit:自動生成單個 commit
|
|
19
|
-
* - gitai commit-all:智能分析並批量 commit
|
|
20
|
-
* - gitai pr:自動生成 PR
|
|
21
|
-
* - gitai workflow:完整工作流程
|
|
13
|
+
* 此文件用于配置 AI 自动化 Git 工具的行为
|
|
22
14
|
*/
|
|
15
|
+
|
|
23
16
|
export default {
|
|
24
|
-
// AI
|
|
17
|
+
// AI 相关配置
|
|
25
18
|
ai: {
|
|
26
|
-
model: 'gpt-4.1', // AI
|
|
27
|
-
maxDiffLength: 8000, // 最大 diff
|
|
28
|
-
maxRetries: 3, //
|
|
19
|
+
model: 'gpt-4.1', // AI 模型
|
|
20
|
+
maxDiffLength: 8000, // 最大 diff 长度
|
|
21
|
+
maxRetries: 3, // 最大重试次数
|
|
29
22
|
},
|
|
30
23
|
|
|
31
|
-
// GitHub
|
|
24
|
+
// GitHub 相关配置
|
|
32
25
|
github: {
|
|
33
|
-
orgName:
|
|
34
|
-
defaultBase: 'auto', //
|
|
35
|
-
autoLabels: true, //
|
|
26
|
+
orgName: 'your-org-name', // GitHub 组织名称
|
|
27
|
+
defaultBase: 'auto', // 默认 base 分支('auto' 为自动侦测)
|
|
28
|
+
autoLabels: true, // 自动添加 Labels
|
|
36
29
|
},
|
|
37
30
|
|
|
38
|
-
//
|
|
31
|
+
// Reviewers 相关配置
|
|
39
32
|
reviewers: {
|
|
40
|
-
autoSelect: false, //
|
|
41
|
-
maxSuggested: 5, //
|
|
42
|
-
gitHistoryDepth: 20, // Git
|
|
43
|
-
excludeAuthors: [], //
|
|
33
|
+
autoSelect: false, // 自动选择 reviewers
|
|
34
|
+
maxSuggested: 5, // 最大建议 reviewers 数量
|
|
35
|
+
gitHistoryDepth: 20, // Git 历史深度
|
|
36
|
+
excludeAuthors: [], // 排除的作者列表
|
|
44
37
|
},
|
|
45
38
|
|
|
46
|
-
//
|
|
39
|
+
// 输出相关配置
|
|
47
40
|
output: {
|
|
48
|
-
verbose: false, //
|
|
49
|
-
saveHistory: false, //
|
|
41
|
+
verbose: false, // 详细输出
|
|
42
|
+
saveHistory: false, // 保存历史记录
|
|
50
43
|
},
|
|
51
44
|
};
|
|
52
45
|
`;
|
|
53
46
|
|
|
54
|
-
/**
|
|
55
|
-
* Init 命令處理器
|
|
56
|
-
*/
|
|
57
47
|
export async function initCommand() {
|
|
58
48
|
const logger = new Logger();
|
|
49
|
+
const configPath = resolve(process.cwd(), '.ai-git-config.mjs');
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
logger.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'.ai-git-config.mjs',
|
|
66
|
-
'ai-git.config.js',
|
|
67
|
-
'ai-git.config.mjs',
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
// 檢查是否已存在配置檔
|
|
71
|
-
const existingConfig = configFiles.find(file =>
|
|
72
|
-
existsSync(resolve(process.cwd(), file))
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
if (existingConfig) {
|
|
76
|
-
const { overwrite } = await inquirer.prompt([
|
|
77
|
-
{
|
|
78
|
-
type: 'confirm',
|
|
79
|
-
name: 'overwrite',
|
|
80
|
-
message: `配置檔 ${existingConfig} 已存在,是否覆蓋?`,
|
|
81
|
-
default: false,
|
|
82
|
-
},
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
if (!overwrite) {
|
|
86
|
-
logger.info('已取消');
|
|
87
|
-
process.exit(0);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 詢問配置檔名稱
|
|
92
|
-
const { configFileName } = await inquirer.prompt([
|
|
93
|
-
{
|
|
94
|
-
type: 'list',
|
|
95
|
-
name: 'configFileName',
|
|
96
|
-
message: '選擇配置檔名稱:',
|
|
97
|
-
choices: [
|
|
98
|
-
{ name: '.ai-git-config.js (推薦)', value: '.ai-git-config.js' },
|
|
99
|
-
{ name: '.ai-git-config.mjs', value: '.ai-git-config.mjs' },
|
|
100
|
-
{ name: 'ai-git.config.js', value: 'ai-git.config.js' },
|
|
101
|
-
{ name: 'ai-git.config.mjs', value: 'ai-git.config.mjs' },
|
|
102
|
-
],
|
|
103
|
-
default: '.ai-git-config.js',
|
|
104
|
-
},
|
|
105
|
-
]);
|
|
106
|
-
|
|
107
|
-
// 詢問基本設定
|
|
108
|
-
const { model, autoReviewers, autoLabels } = await inquirer.prompt([
|
|
109
|
-
{
|
|
110
|
-
type: 'list',
|
|
111
|
-
name: 'model',
|
|
112
|
-
message: '選擇 AI 模型:',
|
|
113
|
-
choices: [
|
|
114
|
-
{ name: 'GPT-4.1 (推薦)', value: 'gpt-4.1' },
|
|
115
|
-
{ name: 'Claude Haiku 4.5', value: 'claude-haiku-4.5' },
|
|
116
|
-
{ name: 'Claude Sonnet 4.5', value: 'claude-sonnet-4.5' },
|
|
117
|
-
],
|
|
118
|
-
default: 'gpt-4.1',
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
type: 'confirm',
|
|
122
|
-
name: 'autoReviewers',
|
|
123
|
-
message: '啟用自動 Reviewer 選擇?',
|
|
124
|
-
default: false,
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
type: 'confirm',
|
|
128
|
-
name: 'autoLabels',
|
|
129
|
-
message: '啟用自動 Label 標記?',
|
|
130
|
-
default: true,
|
|
131
|
-
},
|
|
132
|
-
]);
|
|
133
|
-
|
|
134
|
-
// 生成配置內容
|
|
135
|
-
let configContent = CONFIG_TEMPLATE;
|
|
136
|
-
|
|
137
|
-
if (model !== 'gpt-4.1') {
|
|
138
|
-
configContent = configContent.replace("model: 'gpt-4.1'", `model: '${model}'`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (autoReviewers) {
|
|
142
|
-
configContent = configContent.replace('autoSelect: false', 'autoSelect: true');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!autoLabels) {
|
|
146
|
-
configContent = configContent.replace('autoLabels: true', 'autoLabels: false');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 寫入配置檔
|
|
150
|
-
const configPath = resolve(process.cwd(), configFileName);
|
|
151
|
-
writeFileSync(configPath, configContent, 'utf-8');
|
|
152
|
-
|
|
153
|
-
logger.success(`配置檔已創建: ${configFileName}`);
|
|
154
|
-
logger.info('\n下一步:');
|
|
155
|
-
logger.info(' 1. 編輯配置檔以自訂設定');
|
|
156
|
-
logger.info(' 2. 執行 gitai commit 或 gitai commit-all 開始使用');
|
|
157
|
-
logger.info('\n查看完整文檔: https://github.com/yiso05255/ai-git-tools');
|
|
51
|
+
if (existsSync(configPath)) {
|
|
52
|
+
logger.warning('配置文件已存在: .ai-git-config.mjs');
|
|
53
|
+
console.log('如要重新初始化,请先删除现有配置文件');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
158
56
|
|
|
57
|
+
try {
|
|
58
|
+
writeFileSync(configPath, DEFAULT_CONFIG, 'utf-8');
|
|
59
|
+
logger.success('已创建配置文件: .ai-git-config.mjs');
|
|
60
|
+
console.log('\n请编辑此文件以自定义配置');
|
|
159
61
|
} catch (error) {
|
|
160
|
-
|
|
62
|
+
logger.error(`创建配置文件失败: ${error.message}`);
|
|
161
63
|
process.exit(1);
|
|
162
64
|
}
|
|
163
65
|
}
|