ai-git-tools 2.0.22 → 2.0.24
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/package.json +1 -1
- package/src/commands/commit-all.js +88 -34
- package/CHANGELOG.md +0 -33
package/package.json
CHANGED
|
@@ -91,7 +91,7 @@ function getAllChanges() {
|
|
|
91
91
|
async function analyzeAndGroupChanges(changes, config) {
|
|
92
92
|
console.log('🤖 正在使用 AI 分析變更並分組...\n');
|
|
93
93
|
|
|
94
|
-
//
|
|
94
|
+
// 準備變更摘要(限制每個檔案的 diff 長度)
|
|
95
95
|
const maxDiffPerFile = Math.floor(config.ai.maxDiffLength / Math.max(changes.length, 1));
|
|
96
96
|
const changeSummary = changes
|
|
97
97
|
.map((change, index) => {
|
|
@@ -112,7 +112,7 @@ async function analyzeAndGroupChanges(changes, config) {
|
|
|
112
112
|
- Tailwind CSS + Styled Components
|
|
113
113
|
- Zustand (客戶端狀態) + SWR (伺服器資料獲取)
|
|
114
114
|
- React Hook Form + Zod (表單處理)
|
|
115
|
-
-
|
|
115
|
+
- 架構:Modified Atomic Design(UI / Page / Feature 三層)
|
|
116
116
|
|
|
117
117
|
**專案目錄結構參考**:
|
|
118
118
|
- pages/ → 頁面路由
|
|
@@ -121,14 +121,14 @@ async function analyzeAndGroupChanges(changes, config) {
|
|
|
121
121
|
- components/[Feature]/ → 功能模組元件
|
|
122
122
|
- store/ → Zustand 狀態管理
|
|
123
123
|
- api/ → API 呼叫
|
|
124
|
-
- utils/ →
|
|
124
|
+
- utils/ → 工具函數
|
|
125
125
|
- styles/ → 全域樣式
|
|
126
126
|
|
|
127
127
|
規則:
|
|
128
128
|
1. 將相關功能的變更歸類在同一組(例如:同一個功能開發、同一個 bug 修復、相關的重構等)
|
|
129
129
|
2. 每組應該要有明確的主題
|
|
130
130
|
3. 同一個功能的元件、API、store、樣式應歸為同一組
|
|
131
|
-
4. 設定檔(config
|
|
131
|
+
4. 設定檔(config)和文件(docs)變更可以獨立成一組
|
|
132
132
|
5. 輸出格式為 JSON 陣列,每個元素包含:
|
|
133
133
|
- group_name: 群組名稱(簡短描述,繁體中文)
|
|
134
134
|
- commit_type: commit 類型(feat/fix/docs/style/refactor/test/chore/perf)
|
|
@@ -159,7 +159,7 @@ ${changeSummary}
|
|
|
159
159
|
|
|
160
160
|
請只輸出 JSON,不要其他文字。`;
|
|
161
161
|
|
|
162
|
-
const response = await AIClient.sendAndWait(prompt, config.ai.model);
|
|
162
|
+
const response = await AIClient.sendAndWait(prompt, config.ai.model, config.ai.maxRetries);
|
|
163
163
|
|
|
164
164
|
try {
|
|
165
165
|
return AIClient.parseJSON(response);
|
|
@@ -171,7 +171,7 @@ ${changeSummary}
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
|
-
*
|
|
174
|
+
* 為特定群組生成 commit message
|
|
175
175
|
*/
|
|
176
176
|
async function generateCommitMessage(group, files, config) {
|
|
177
177
|
const filesList = files
|
|
@@ -181,36 +181,36 @@ async function generateCommitMessage(group, files, config) {
|
|
|
181
181
|
})
|
|
182
182
|
.join('\n\n---\n\n');
|
|
183
183
|
|
|
184
|
-
const prompt =
|
|
184
|
+
const prompt = `請根據以下資訊生成一則 commit message:
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
Commit
|
|
188
|
-
Commit
|
|
189
|
-
|
|
186
|
+
群組名稱: ${group.group_name}
|
|
187
|
+
Commit 類型: ${group.commit_type}
|
|
188
|
+
Commit 範圍: ${group.commit_scope || '未指定'}
|
|
189
|
+
說明: ${group.description}
|
|
190
190
|
|
|
191
191
|
檔案變更:
|
|
192
192
|
${filesList}
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
規則:
|
|
195
195
|
- 使用 Conventional Commits 格式:${group.commit_type}${
|
|
196
196
|
group.commit_scope ? `(${group.commit_scope})` : ''
|
|
197
197
|
}: <subject>
|
|
198
|
-
- subject 限制在 50
|
|
198
|
+
- subject 限制在 50 字內,使用繁體中文
|
|
199
199
|
- 如果變更複雜,可以加上 body(用空行分隔),body 使用 bullet points
|
|
200
|
-
-
|
|
201
|
-
- 不要包含 markdown code block
|
|
202
|
-
-
|
|
200
|
+
- 只輸出 commit message 本身,不要其他說明
|
|
201
|
+
- 不要包含 markdown code block 標記(不要 \`\`\`)
|
|
202
|
+
- 不要加上任何引導語句
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
輸出格式範例:
|
|
205
205
|
feat(auth): 新增使用者登入功能
|
|
206
206
|
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
- 整合 JWT
|
|
207
|
+
- 實作登入 API endpoint
|
|
208
|
+
- 新增登入頁面 UI
|
|
209
|
+
- 整合 JWT 認證機制`;
|
|
210
210
|
|
|
211
|
-
const response = await AIClient.sendAndWait(prompt, config.ai.model);
|
|
211
|
+
const response = await AIClient.sendAndWait(prompt, config.ai.model, config.ai.maxRetries);
|
|
212
212
|
|
|
213
|
-
// 清理可能的 markdown code block
|
|
213
|
+
// 清理可能的 markdown code block 標記
|
|
214
214
|
let commitMessage = response.trim();
|
|
215
215
|
commitMessage = commitMessage
|
|
216
216
|
.replace(/^```[\s\S]*?\n/, '')
|
|
@@ -229,20 +229,21 @@ async function commitGroup(group, files, config) {
|
|
|
229
229
|
console.log(
|
|
230
230
|
` 類型: ${group.commit_type}${group.commit_scope ? `(${group.commit_scope})` : ''}`
|
|
231
231
|
);
|
|
232
|
-
console.log(`
|
|
232
|
+
console.log(` 檔案數量: ${files.length}`);
|
|
233
233
|
|
|
234
234
|
// 先 reset 所有已 staged 的檔案
|
|
235
235
|
try {
|
|
236
236
|
execSync('git reset HEAD -- .', { stdio: 'ignore' });
|
|
237
237
|
} catch (e) {
|
|
238
|
-
//
|
|
238
|
+
// 忽略錯誤(可能沒有 staged 的檔案)
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
// Add
|
|
241
|
+
// Add 這組的檔案
|
|
242
242
|
for (const file of files) {
|
|
243
243
|
console.log(` ├─ ${file.filePath}`);
|
|
244
244
|
try {
|
|
245
|
-
|
|
245
|
+
// 使用 JSON.stringify 來正確處理包含空格或特殊字符的檔案路徑
|
|
246
|
+
execSync(`git add ${JSON.stringify(file.filePath)}`, { encoding: 'utf-8' });
|
|
246
247
|
} catch (addError) {
|
|
247
248
|
console.error(` ⚠️ 無法加入檔案: ${file.filePath}`, addError.message);
|
|
248
249
|
throw addError;
|
|
@@ -278,7 +279,7 @@ async function commitGroup(group, files, config) {
|
|
|
278
279
|
try {
|
|
279
280
|
unlinkSync(tmpFile);
|
|
280
281
|
} catch (e) {
|
|
281
|
-
//
|
|
282
|
+
// 忽略刪除暫存檔案的錯誤
|
|
282
283
|
}
|
|
283
284
|
throw commitError;
|
|
284
285
|
}
|
|
@@ -320,7 +321,7 @@ export async function commitAllCommand() {
|
|
|
320
321
|
process.exit(0);
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
console.log(`📊 找到 ${changes.length}
|
|
324
|
+
console.log(`📊 找到 ${changes.length} 個變更的檔案:\n`);
|
|
324
325
|
changes.forEach((change, index) => {
|
|
325
326
|
const status = change.isNew ? '新增' : '修改';
|
|
326
327
|
console.log(` [${index}] ${status} - ${change.filePath}`);
|
|
@@ -335,13 +336,49 @@ export async function commitAllCommand() {
|
|
|
335
336
|
process.exit(1);
|
|
336
337
|
}
|
|
337
338
|
|
|
339
|
+
// 驗證所有檔案都被包含在分組中
|
|
340
|
+
const groupedIndices = new Set();
|
|
341
|
+
groups.forEach((group) => {
|
|
342
|
+
group.file_indices.forEach((index) => {
|
|
343
|
+
groupedIndices.add(index);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const ungroupedIndices = [];
|
|
348
|
+
for (let i = 0; i < changes.length; i++) {
|
|
349
|
+
if (!groupedIndices.has(i)) {
|
|
350
|
+
ungroupedIndices.push(i);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 如果有檔案未被分組,創建一個 "其他變更" 群組
|
|
355
|
+
if (ungroupedIndices.length > 0) {
|
|
356
|
+
console.log(`\n⚠️ 發現 ${ungroupedIndices.length} 個未分組的檔案,將自動歸類:`);
|
|
357
|
+
ungroupedIndices.forEach((index) => {
|
|
358
|
+
console.log(` - ${changes[index].filePath}`);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
groups.push({
|
|
362
|
+
group_name: '其他變更',
|
|
363
|
+
commit_type: 'chore',
|
|
364
|
+
commit_scope: 'misc',
|
|
365
|
+
file_indices: ungroupedIndices,
|
|
366
|
+
description: '未能自動分類的其他變更',
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
338
370
|
logger.success(`AI 分析完成,共分為 ${groups.length} 個群組:\n`);
|
|
339
371
|
groups.forEach((group, index) => {
|
|
340
372
|
console.log(` 群組 ${index + 1}: ${group.group_name} (${group.commit_type})`);
|
|
341
|
-
console.log(` └─ 包含 ${group.file_indices.length}
|
|
373
|
+
console.log(` └─ 包含 ${group.file_indices.length} 個檔案`);
|
|
374
|
+
if (config.output.verbose) {
|
|
375
|
+
group.file_indices.forEach((fileIndex) => {
|
|
376
|
+
console.log(` - [${fileIndex}] ${changes[fileIndex].filePath}`);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
342
379
|
});
|
|
343
380
|
|
|
344
|
-
// 3.
|
|
381
|
+
// 3. 依序提交每個群組
|
|
345
382
|
logger.separator('=', 60);
|
|
346
383
|
console.log('開始執行提交...');
|
|
347
384
|
logger.separator('=', 60);
|
|
@@ -351,6 +388,14 @@ export async function commitAllCommand() {
|
|
|
351
388
|
const group = groups[i];
|
|
352
389
|
const groupFiles = group.file_indices.map((index) => changes[index]);
|
|
353
390
|
|
|
391
|
+
// 驗證檔案索引是否有效
|
|
392
|
+
const invalidIndices = group.file_indices.filter((idx) => idx >= changes.length);
|
|
393
|
+
if (invalidIndices.length > 0) {
|
|
394
|
+
console.error(`\n❌ 群組 ${i + 1} 包含無效的檔案索引:`, invalidIndices);
|
|
395
|
+
console.log(` 跳過此群組: ${group.group_name}`);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
354
399
|
const success = await commitGroup(group, groupFiles, config);
|
|
355
400
|
if (success) {
|
|
356
401
|
successCount++;
|
|
@@ -359,12 +404,21 @@ export async function commitAllCommand() {
|
|
|
359
404
|
|
|
360
405
|
// 4. 顯示摘要
|
|
361
406
|
logger.separator('=', 60);
|
|
362
|
-
logger.success(`完成!成功提交 ${successCount}/${groups.length}
|
|
407
|
+
logger.success(`完成!成功提交 ${successCount}/${groups.length} 個群組`);
|
|
363
408
|
logger.separator('=', 60);
|
|
364
409
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
410
|
+
if (successCount < groups.length) {
|
|
411
|
+
const failedCount = groups.length - successCount;
|
|
412
|
+
console.log(`\n⚠️ 有 ${failedCount} 個群組提交失敗`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// 顯示最近的幾個 commits
|
|
416
|
+
if (successCount > 0) {
|
|
417
|
+
console.log('\n📋 最近的 commits:');
|
|
418
|
+
execSync(`git log -${successCount} --oneline`, { stdio: 'inherit' });
|
|
419
|
+
} else {
|
|
420
|
+
console.log('\n⚠️ 沒有成功的提交');
|
|
421
|
+
}
|
|
368
422
|
|
|
369
423
|
// Reset 任何剩餘的 staged 檔案
|
|
370
424
|
try {
|
package/CHANGELOG.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [1.0.0] - 2026-02-12
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
- 🎯 Smart commit generation for staged changes
|
|
12
|
-
- 🚀 Batch commit with intelligent grouping
|
|
13
|
-
- 📤 Automatic PR creation with AI-generated content
|
|
14
|
-
- 👥 Smart reviewer suggestions based on Git history
|
|
15
|
-
- 🏷️ Automatic label suggestions
|
|
16
|
-
- ⚙️ Configuration file support
|
|
17
|
-
- 🌐 Cross-project compatibility
|
|
18
|
-
- 📝 Comprehensive documentation
|
|
19
|
-
|
|
20
|
-
### Features
|
|
21
|
-
- \`gitai init\` - Initialize configuration
|
|
22
|
-
- \`gitai commit\` - Single commit generation
|
|
23
|
-
- \`gitai commit-all\` - Batch commit with grouping
|
|
24
|
-
- \`gitai pr\` - PR generation and creation
|
|
25
|
-
- \`gitai workflow\` - Complete workflow automation
|
|
26
|
-
|
|
27
|
-
### Technical Details
|
|
28
|
-
- Built with Commander.js for CLI
|
|
29
|
-
- GitHub Copilot SDK integration
|
|
30
|
-
- Chalk for colorful output
|
|
31
|
-
- Ora for spinners
|
|
32
|
-
- Inquirer for interactive prompts
|
|
33
|
-
- Supports multiple AI models (GPT-4.1, Claude, etc.)
|