ai-git-tools 1.0.5 → 2.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/cli.js +24 -45
- package/package.json +1 -1
- package/src/commands/commit-all.js +224 -163
- package/src/commands/commit.js +87 -71
- package/src/commands/init.js +32 -130
- package/src/commands/pr.js +120 -236
- package/src/core/ai-client.js +18 -83
- package/src/core/config-loader.js +164 -132
- package/src/core/git-operations.js +83 -194
- package/src/utils/helpers.js +39 -50
- 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,42 +1,28 @@
|
|
|
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
|
-
// 檢查是否在 Git 倉庫中
|
|
28
|
-
if (!GitOperations.isGitRepository()) {
|
|
29
|
-
logger.error('當前目錄不是 Git 倉庫');
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
18
|
// 載入配置
|
|
34
|
-
const config = await
|
|
35
|
-
|
|
19
|
+
const config = await loadCommitConfig();
|
|
20
|
+
|
|
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 變更
|
|
@@ -44,77 +30,107 @@ export async function commitCommand(options) {
|
|
|
44
30
|
|
|
45
31
|
if (!diff.trim()) {
|
|
46
32
|
logger.error('沒有 staged 的變更');
|
|
47
|
-
|
|
33
|
+
console.log('💡 請先使用 git add 來 stage 你的變更');
|
|
48
34
|
process.exit(1);
|
|
49
35
|
}
|
|
50
36
|
|
|
51
|
-
logger.
|
|
37
|
+
logger.step('正在分析變更內容...\n');
|
|
52
38
|
|
|
53
39
|
// 截斷過長的 diff
|
|
54
|
-
const truncatedDiff =
|
|
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
|
-
// 使用 AI
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
// 使用 AI 產生 commit message
|
|
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
|
+
}
|
|
64
58
|
|
|
65
|
-
|
|
59
|
+
const prompt = `${getProjectTypePrompt()}
|
|
66
60
|
|
|
67
61
|
請根據以下 git diff 產生一則 commit message。
|
|
68
62
|
|
|
69
|
-
|
|
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)
|
|
69
|
+
|
|
70
|
+
**重要**:
|
|
71
|
+
- 直接輸出 commit message 純文字,不要使用 markdown 程式碼區塊(\`\`\`)
|
|
72
|
+
- 不要加上任何前綴說明或後綴文字
|
|
73
|
+
- 第一行是標題,如有需要可加上空行後的詳細說明
|
|
70
74
|
|
|
71
75
|
**範例格式**:
|
|
72
|
-
feat(
|
|
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.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
logger.code(commitMessage);
|
|
121
|
+
logger.success('產生的 Commit Message:');
|
|
122
|
+
logger.separator('─', 60);
|
|
123
|
+
console.log(commitMessage);
|
|
124
|
+
logger.separator('─', 60);
|
|
99
125
|
|
|
100
126
|
// 執行 commit
|
|
101
|
-
logger.
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
}
|
|
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
|
}
|