ai-git-tools 1.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +353 -0
- package/bin/cli.js +86 -0
- package/package.json +60 -0
- package/src/commands/commit-all.js +266 -0
- package/src/commands/commit.js +115 -0
- package/src/commands/init.js +163 -0
- package/src/commands/pr.js +305 -0
- package/src/commands/workflow.js +36 -0
- package/src/core/ai-client.js +115 -0
- package/src/core/config-loader.js +165 -0
- package/src/core/git-operations.js +263 -0
- package/src/core/github-api.js +139 -0
- package/src/index.js +16 -0
- package/src/utils/helpers.js +88 -0
- package/src/utils/logger.js +123 -0
- package/templates/.ai-git-config.template.js +38 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit All 命令
|
|
3
|
+
*
|
|
4
|
+
* 智能分析所有變更並自動分組提交
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { loadConfig } from '../core/config-loader.js';
|
|
9
|
+
import { AIClient } from '../core/ai-client.js';
|
|
10
|
+
import { GitOperations } from '../core/git-operations.js';
|
|
11
|
+
import { Logger } from '../utils/logger.js';
|
|
12
|
+
import {
|
|
13
|
+
handleError,
|
|
14
|
+
validateCommitMessage,
|
|
15
|
+
formatFileList,
|
|
16
|
+
getProjectTypePrompt,
|
|
17
|
+
getConventionalCommitsRules,
|
|
18
|
+
} from '../utils/helpers.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 使用 AI 分析並分組變更
|
|
22
|
+
*/
|
|
23
|
+
async function analyzeAndGroupChanges(changes, config, logger) {
|
|
24
|
+
logger.startSpinner('AI 正在分析變更並分組...');
|
|
25
|
+
|
|
26
|
+
// 準備變更摘要
|
|
27
|
+
const maxDiffPerFile = Math.floor(config.ai.maxDiffLength / Math.max(changes.length, 1));
|
|
28
|
+
const changeSummary = changes
|
|
29
|
+
.map((change, index) => {
|
|
30
|
+
const diff = GitOperations.getFileDiff(change.filePath, change.isNew);
|
|
31
|
+
const lines = diff.split('\n');
|
|
32
|
+
const truncatedDiff = lines.slice(0, Math.min(50, maxDiffPerFile / 100)).join('\n');
|
|
33
|
+
return `[檔案 ${index}] ${change.filePath}\n${
|
|
34
|
+
change.isNew ? '(新檔案)' : '(已修改)'
|
|
35
|
+
}\n${truncatedDiff}\n`;
|
|
36
|
+
})
|
|
37
|
+
.join('\n---\n\n');
|
|
38
|
+
|
|
39
|
+
const aiClient = new AIClient(config);
|
|
40
|
+
|
|
41
|
+
const prompt = `${getProjectTypePrompt()}
|
|
42
|
+
|
|
43
|
+
請分析以下的檔案變更,並將它們按照功能/目的分組。
|
|
44
|
+
|
|
45
|
+
**分組規則**:
|
|
46
|
+
1. 將相關功能的變更歸類在同一組(例如:同一個功能開發、同一個 bug 修復、相關的重構等)
|
|
47
|
+
2. 每組應該要有明確的主題
|
|
48
|
+
3. 同一個功能的元件、API、樣式應歸為同一組
|
|
49
|
+
4. 設定檔(config)和文件(docs)變更可以獨立成一組
|
|
50
|
+
5. 輸出格式為 JSON 陣列,每個元素包含:
|
|
51
|
+
- group_name: 群組名稱(簡短描述,繁體中文)
|
|
52
|
+
- commit_type: commit 類型(feat/fix/docs/style/refactor/test/chore/perf)
|
|
53
|
+
- commit_scope: commit 影響範圍(如 api、ui、config、auth 等,選填)
|
|
54
|
+
- file_indices: 屬於這組的檔案索引陣列(對應上面的 [檔案 X])
|
|
55
|
+
- description: 這組變更的詳細說明(繁體中文)
|
|
56
|
+
|
|
57
|
+
範例輸出:
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
"group_name": "新增使用者登入功能",
|
|
61
|
+
"commit_type": "feat",
|
|
62
|
+
"commit_scope": "auth",
|
|
63
|
+
"file_indices": [0, 1, 2],
|
|
64
|
+
"description": "實作使用者登入 API 和前端頁面"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"group_name": "修正導航列手機版顯示",
|
|
68
|
+
"commit_type": "fix",
|
|
69
|
+
"commit_scope": "ui",
|
|
70
|
+
"file_indices": [3, 4],
|
|
71
|
+
"description": "修正導航列在手機版的顯示問題"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
檔案變更內容:
|
|
76
|
+
${changeSummary}
|
|
77
|
+
|
|
78
|
+
請只輸出 JSON,不要其他文字。`;
|
|
79
|
+
|
|
80
|
+
const response = await aiClient.sendAndWait(prompt);
|
|
81
|
+
await aiClient.stop();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const groups = AIClient.parseJSON(response);
|
|
85
|
+
logger.succeedSpinner(`AI 分析完成,共分為 ${groups.length} 個群組`);
|
|
86
|
+
return groups;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.failSpinner('AI 分析失敗');
|
|
89
|
+
throw new Error(`無法解析 AI 回應: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 為特定群組生成 commit message
|
|
95
|
+
*/
|
|
96
|
+
async function generateCommitMessage(group, files, config, logger) {
|
|
97
|
+
const filesList = files
|
|
98
|
+
.map(file => {
|
|
99
|
+
const diff = GitOperations.getFileDiff(file.filePath, file.isNew);
|
|
100
|
+
return `檔案: ${file.filePath}\n${diff}`;
|
|
101
|
+
})
|
|
102
|
+
.join('\n\n---\n\n');
|
|
103
|
+
|
|
104
|
+
const aiClient = new AIClient(config);
|
|
105
|
+
|
|
106
|
+
const prompt = `請根據以下資訊生成一則 commit message:
|
|
107
|
+
|
|
108
|
+
群組名稱: ${group.group_name}
|
|
109
|
+
Commit 類型: ${group.commit_type}
|
|
110
|
+
Commit 範圍: ${group.commit_scope || '未指定'}
|
|
111
|
+
說明: ${group.description}
|
|
112
|
+
|
|
113
|
+
檔案變更:
|
|
114
|
+
${filesList}
|
|
115
|
+
|
|
116
|
+
規則:
|
|
117
|
+
- 使用 Conventional Commits 格式:${group.commit_type}${
|
|
118
|
+
group.commit_scope ? `(${group.commit_scope})` : ''
|
|
119
|
+
}: <subject>
|
|
120
|
+
- subject 限制在 50 字內,使用繁體中文
|
|
121
|
+
- 如果變更複雜,可以加上 body(用空行分隔),body 使用 bullet points
|
|
122
|
+
- 只輸出 commit message 本身,不要其他說明
|
|
123
|
+
- 不要包含 markdown code block 標記(不要 \`\`\`)
|
|
124
|
+
- 不要加上任何引導語句
|
|
125
|
+
|
|
126
|
+
輸出格式範例:
|
|
127
|
+
feat(auth): 新增使用者登入功能
|
|
128
|
+
|
|
129
|
+
- 實作登入 API endpoint
|
|
130
|
+
- 新增登入頁面 UI
|
|
131
|
+
- 整合 JWT 認證機制`;
|
|
132
|
+
|
|
133
|
+
const response = await aiClient.sendAndWait(prompt);
|
|
134
|
+
await aiClient.stop();
|
|
135
|
+
|
|
136
|
+
return AIClient.cleanResponse(response);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 執行分組提交
|
|
141
|
+
*/
|
|
142
|
+
async function commitGroup(group, files, config, logger) {
|
|
143
|
+
try {
|
|
144
|
+
console.log(chalk.cyan(`\n📦 處理群組: ${group.group_name}`));
|
|
145
|
+
console.log(` 類型: ${group.commit_type}${group.commit_scope ? `(${group.commit_scope})` : ''}`);
|
|
146
|
+
console.log(` 檔案數量: ${files.length}`);
|
|
147
|
+
|
|
148
|
+
// Reset 所有已 staged 的檔案
|
|
149
|
+
GitOperations.resetStaged();
|
|
150
|
+
|
|
151
|
+
// Add 這組的檔案
|
|
152
|
+
for (const file of files) {
|
|
153
|
+
console.log(` ├─ ${file.filePath}`);
|
|
154
|
+
GitOperations.addFile(file.filePath);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 生成 commit message
|
|
158
|
+
console.log(` └─ 生成 commit message...`);
|
|
159
|
+
const commitMessage = await generateCommitMessage(group, files, config, logger);
|
|
160
|
+
|
|
161
|
+
if (!commitMessage) {
|
|
162
|
+
logger.warn('無法生成 commit message,跳過此群組');
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 驗證 commit message
|
|
167
|
+
const validation = validateCommitMessage(commitMessage);
|
|
168
|
+
if (!validation.valid) {
|
|
169
|
+
logger.warn(`Commit message 無效(${validation.reason}),跳過此群組`);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(chalk.cyan('\n 📝 Commit Message:'));
|
|
174
|
+
logger.code(commitMessage.split('\n').map(line => ` ${line}`).join('\n'));
|
|
175
|
+
|
|
176
|
+
// 執行 commit
|
|
177
|
+
await GitOperations.commit(commitMessage);
|
|
178
|
+
logger.success('Commit 完成!');
|
|
179
|
+
|
|
180
|
+
return true;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger.error(`Commit 失敗: ${error.message}`);
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Commit All 命令處理器
|
|
189
|
+
*/
|
|
190
|
+
export async function commitAllCommand(options) {
|
|
191
|
+
const logger = new Logger(options.verbose);
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
logger.header('智能分析所有變更並自動提交');
|
|
195
|
+
|
|
196
|
+
// 檢查是否在 Git 倉庫中
|
|
197
|
+
if (!GitOperations.isGitRepository()) {
|
|
198
|
+
logger.error('當前目錄不是 Git 倉庫');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 載入配置
|
|
203
|
+
const config = await loadConfig(options);
|
|
204
|
+
|
|
205
|
+
if (config.output.verbose) {
|
|
206
|
+
logger.debug('配置已載入');
|
|
207
|
+
logger.debug(`AI Model: ${config.ai.model}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 獲取所有變更
|
|
211
|
+
logger.startSpinner('掃描變更中...');
|
|
212
|
+
const changes = GitOperations.getAllChanges();
|
|
213
|
+
logger.succeedSpinner(`找到 ${changes.length} 個變更的檔案`);
|
|
214
|
+
|
|
215
|
+
if (changes.length === 0) {
|
|
216
|
+
logger.info('沒有需要提交的變更');
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(chalk.cyan('\n📊 變更的檔案:'));
|
|
221
|
+
console.log(formatFileList(changes));
|
|
222
|
+
|
|
223
|
+
// 使用 AI 分析並分組
|
|
224
|
+
const groups = await analyzeAndGroupChanges(changes, config, logger);
|
|
225
|
+
|
|
226
|
+
if (!groups || groups.length === 0) {
|
|
227
|
+
logger.error('AI 分析失敗或沒有產生分組');
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log(chalk.cyan('\n✅ 分組結果:'));
|
|
232
|
+
groups.forEach((group, index) => {
|
|
233
|
+
console.log(` 群組 ${index + 1}: ${group.group_name} (${group.commit_type})`);
|
|
234
|
+
console.log(` └─ 包含 ${group.file_indices.length} 個檔案`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// 依序提交每個群組
|
|
238
|
+
logger.header('開始執行提交');
|
|
239
|
+
|
|
240
|
+
let successCount = 0;
|
|
241
|
+
for (let i = 0; i < groups.length; i++) {
|
|
242
|
+
const group = groups[i];
|
|
243
|
+
const groupFiles = group.file_indices.map(index => changes[index]);
|
|
244
|
+
|
|
245
|
+
const success = await commitGroup(group, groupFiles, config, logger);
|
|
246
|
+
if (success) {
|
|
247
|
+
successCount++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 顯示摘要
|
|
252
|
+
logger.header(`完成!成功提交 ${successCount}/${groups.length} 個群組`);
|
|
253
|
+
|
|
254
|
+
// 顯示最近的 commits
|
|
255
|
+
console.log(chalk.cyan('\n📋 最近的 commits:'));
|
|
256
|
+
const recentCommits = GitOperations.getRecentCommits(successCount);
|
|
257
|
+
console.log(recentCommits);
|
|
258
|
+
|
|
259
|
+
// Reset 任何剩餘的 staged 檔案
|
|
260
|
+
GitOperations.resetStaged();
|
|
261
|
+
|
|
262
|
+
} catch (error) {
|
|
263
|
+
handleError(error);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit 命令
|
|
3
|
+
*
|
|
4
|
+
* 為已 staged 的變更生成 commit message 並提交
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { loadConfig } from '../core/config-loader.js';
|
|
9
|
+
import { AIClient } from '../core/ai-client.js';
|
|
10
|
+
import { GitOperations } from '../core/git-operations.js';
|
|
11
|
+
import { Logger } from '../utils/logger.js';
|
|
12
|
+
import {
|
|
13
|
+
handleError,
|
|
14
|
+
validateCommitMessage,
|
|
15
|
+
truncateText,
|
|
16
|
+
getProjectTypePrompt,
|
|
17
|
+
getConventionalCommitsRules,
|
|
18
|
+
} from '../utils/helpers.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Commit 命令處理器
|
|
22
|
+
*/
|
|
23
|
+
export async function commitCommand(options) {
|
|
24
|
+
const logger = new Logger(options.verbose);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// 檢查是否在 Git 倉庫中
|
|
28
|
+
if (!GitOperations.isGitRepository()) {
|
|
29
|
+
logger.error('當前目錄不是 Git 倉庫');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 載入配置
|
|
34
|
+
const config = await loadConfig(options);
|
|
35
|
+
|
|
36
|
+
if (config.output.verbose) {
|
|
37
|
+
logger.debug('配置已載入');
|
|
38
|
+
logger.debug(`AI Model: ${config.ai.model}`);
|
|
39
|
+
logger.debug(`Max Diff Length: ${config.ai.maxDiffLength}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 檢查是否有 staged 變更
|
|
43
|
+
const diff = GitOperations.getStagedDiff();
|
|
44
|
+
|
|
45
|
+
if (!diff.trim()) {
|
|
46
|
+
logger.error('沒有 staged 的變更');
|
|
47
|
+
logger.info('請先使用 git add 來 stage 你的變更');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
logger.startSpinner('正在分析變更內容...');
|
|
52
|
+
|
|
53
|
+
// 截斷過長的 diff
|
|
54
|
+
const truncatedDiff = truncateText(diff, config.ai.maxDiffLength);
|
|
55
|
+
|
|
56
|
+
if (config.output.verbose && diff.length > config.ai.maxDiffLength) {
|
|
57
|
+
logger.debug(`Diff 已從 ${diff.length} 字元截斷至 ${config.ai.maxDiffLength} 字元`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 使用 AI 生成 commit message
|
|
61
|
+
const aiClient = new AIClient(config);
|
|
62
|
+
|
|
63
|
+
logger.updateSpinner('AI 正在生成 commit message...');
|
|
64
|
+
|
|
65
|
+
const prompt = `${getProjectTypePrompt()}
|
|
66
|
+
|
|
67
|
+
請根據以下 git diff 產生一則 commit message。
|
|
68
|
+
|
|
69
|
+
${getConventionalCommitsRules()}
|
|
70
|
+
|
|
71
|
+
**範例格式**:
|
|
72
|
+
feat(auth): 新增使用者登入功能
|
|
73
|
+
|
|
74
|
+
- 實作登入 API endpoint
|
|
75
|
+
- 新增登入頁面 UI
|
|
76
|
+
- 整合 JWT 認證機制
|
|
77
|
+
|
|
78
|
+
git diff:
|
|
79
|
+
${truncatedDiff}`;
|
|
80
|
+
|
|
81
|
+
const rawMessage = await aiClient.sendAndWait(prompt);
|
|
82
|
+
await aiClient.stop();
|
|
83
|
+
|
|
84
|
+
const commitMessage = AIClient.cleanResponse(rawMessage);
|
|
85
|
+
|
|
86
|
+
// 驗證 commit message
|
|
87
|
+
const validation = validateCommitMessage(commitMessage);
|
|
88
|
+
if (!validation.valid) {
|
|
89
|
+
logger.failSpinner('生成 commit message 失敗');
|
|
90
|
+
logger.error(validation.reason);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
logger.succeedSpinner('Commit message 生成完成');
|
|
95
|
+
|
|
96
|
+
// 顯示生成的 commit message
|
|
97
|
+
console.log(chalk.cyan('\n📝 生成的 Commit Message:'));
|
|
98
|
+
logger.code(commitMessage);
|
|
99
|
+
|
|
100
|
+
// 執行 commit
|
|
101
|
+
logger.startSpinner('正在執行 commit...');
|
|
102
|
+
await GitOperations.commit(commitMessage);
|
|
103
|
+
logger.succeedSpinner('Commit 完成!');
|
|
104
|
+
|
|
105
|
+
// 顯示最新的 commit
|
|
106
|
+
console.log(chalk.cyan('\n📋 最新 commit:'));
|
|
107
|
+
const recentCommits = GitOperations.getRecentCommits(1);
|
|
108
|
+
console.log(recentCommits);
|
|
109
|
+
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logger.failSpinner('操作失敗');
|
|
112
|
+
handleError(error);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init 命令
|
|
3
|
+
*
|
|
4
|
+
* 初始化配置檔案
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
8
|
+
import { resolve } from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import inquirer from 'inquirer';
|
|
11
|
+
import { Logger } from '../utils/logger.js';
|
|
12
|
+
import { handleError } from '../utils/helpers.js';
|
|
13
|
+
|
|
14
|
+
const CONFIG_TEMPLATE = `/**
|
|
15
|
+
* AI Git Tools 配置檔
|
|
16
|
+
*
|
|
17
|
+
* 此配置檔用於:
|
|
18
|
+
* - gitai commit:自動生成單個 commit
|
|
19
|
+
* - gitai commit-all:智能分析並批量 commit
|
|
20
|
+
* - gitai pr:自動生成 PR
|
|
21
|
+
* - gitai workflow:完整工作流程
|
|
22
|
+
*/
|
|
23
|
+
export default {
|
|
24
|
+
// AI 設定
|
|
25
|
+
ai: {
|
|
26
|
+
model: 'gpt-4.1', // AI 模型:gpt-4.1, claude-haiku-4.5 等
|
|
27
|
+
maxDiffLength: 8000, // 最大 diff 長度(字元)
|
|
28
|
+
maxRetries: 3, // API 失敗時的最大重試次數
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// GitHub 設定(用於 PR 工具)
|
|
32
|
+
github: {
|
|
33
|
+
orgName: null, // GitHub 組織名稱(自動從 git remote 取得)
|
|
34
|
+
defaultBase: 'auto', // 預設目標分支:'auto' | 'main' | 'develop'
|
|
35
|
+
autoLabels: true, // 自動添加 Labels
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Reviewer 設定(用於 PR 工具)
|
|
39
|
+
reviewers: {
|
|
40
|
+
autoSelect: false, // 是否啟用 reviewer 選擇功能
|
|
41
|
+
maxSuggested: 5, // 最多建議的 reviewers 數量
|
|
42
|
+
gitHistoryDepth: 20, // Git 歷史分析深度
|
|
43
|
+
excludeAuthors: [], // 排除特定作者,例如: ['bot@', 'ci-user']
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// 輸出設定
|
|
47
|
+
output: {
|
|
48
|
+
verbose: false, // 顯示詳細輸出
|
|
49
|
+
saveHistory: false, // 儲存操作歷史(未來功能)
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Init 命令處理器
|
|
56
|
+
*/
|
|
57
|
+
export async function initCommand() {
|
|
58
|
+
const logger = new Logger();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
logger.header('初始化 AI Git Tools 配置檔');
|
|
62
|
+
|
|
63
|
+
const configFiles = [
|
|
64
|
+
'.ai-git-config.js',
|
|
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');
|
|
158
|
+
|
|
159
|
+
} catch (error) {
|
|
160
|
+
handleError(error);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|