ai-git-tools 2.0.23 → 2.0.25
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 +85 -19
package/package.json
CHANGED
|
@@ -14,8 +14,16 @@ import { handleError } from '../utils/helpers.js';
|
|
|
14
14
|
/**
|
|
15
15
|
* 獲取檔案的變更內容
|
|
16
16
|
*/
|
|
17
|
-
function getFileDiff(filePath, isNew) {
|
|
17
|
+
function getFileDiff(filePath, isNew, isDeleted) {
|
|
18
18
|
try {
|
|
19
|
+
if (isDeleted) {
|
|
20
|
+
// 刪除的檔案:顯示刪除前的內容(前 50 行)
|
|
21
|
+
const diff = execSync(`git show HEAD:"${filePath}"`, {
|
|
22
|
+
encoding: 'utf-8',
|
|
23
|
+
}).toString();
|
|
24
|
+
const lines = diff.split('\n').slice(0, 50);
|
|
25
|
+
return `[已刪除]\n${lines.join('\n')}${lines.length >= 50 ? '\n...' : ''}`;
|
|
26
|
+
}
|
|
19
27
|
if (isNew) {
|
|
20
28
|
// 新檔案:讀取完整內容(前 100 行)
|
|
21
29
|
const content = readFileSync(filePath, 'utf-8');
|
|
@@ -52,11 +60,6 @@ function getAllChanges() {
|
|
|
52
60
|
const statusCode = line.substring(0, 2);
|
|
53
61
|
const filePath = line.substring(3).trim();
|
|
54
62
|
|
|
55
|
-
// 跳過已刪除的檔案
|
|
56
|
-
if (statusCode.includes('D')) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
63
|
// 跳過某些不需要提交的檔案
|
|
61
64
|
if (
|
|
62
65
|
filePath.includes('node_modules/') ||
|
|
@@ -68,11 +71,13 @@ function getAllChanges() {
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
const isNew = statusCode.includes('?') || statusCode.includes('A');
|
|
74
|
+
const isDeleted = statusCode.includes('D');
|
|
71
75
|
const isStaged = statusCode[0] !== ' ' && statusCode[0] !== '?';
|
|
72
76
|
|
|
73
77
|
changes.push({
|
|
74
78
|
filePath,
|
|
75
79
|
isNew,
|
|
80
|
+
isDeleted,
|
|
76
81
|
isStaged,
|
|
77
82
|
statusCode,
|
|
78
83
|
});
|
|
@@ -91,16 +96,17 @@ function getAllChanges() {
|
|
|
91
96
|
async function analyzeAndGroupChanges(changes, config) {
|
|
92
97
|
console.log('🤖 正在使用 AI 分析變更並分組...\n');
|
|
93
98
|
|
|
94
|
-
//
|
|
99
|
+
// 準備變更摘要(限制每個檔案的 diff 長度)
|
|
95
100
|
const maxDiffPerFile = Math.floor(config.ai.maxDiffLength / Math.max(changes.length, 1));
|
|
96
101
|
const changeSummary = changes
|
|
97
102
|
.map((change, index) => {
|
|
98
|
-
const diff = getFileDiff(change.filePath, change.isNew);
|
|
103
|
+
const diff = getFileDiff(change.filePath, change.isNew, change.isDeleted);
|
|
99
104
|
const lines = diff.split('\n');
|
|
100
105
|
const truncatedDiff = lines.slice(0, Math.min(50, maxDiffPerFile / 100)).join('\n');
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
let status = '(已修改)';
|
|
107
|
+
if (change.isNew) status = '(新檔案)';
|
|
108
|
+
if (change.isDeleted) status = '(已刪除)';
|
|
109
|
+
return `[檔案 ${index}] ${change.filePath}\n${status}\n${truncatedDiff}\n`;
|
|
104
110
|
})
|
|
105
111
|
.join('\n---\n\n');
|
|
106
112
|
|
|
@@ -159,7 +165,7 @@ ${changeSummary}
|
|
|
159
165
|
|
|
160
166
|
請只輸出 JSON,不要其他文字。`;
|
|
161
167
|
|
|
162
|
-
const response = await AIClient.sendAndWait(prompt, config.ai.model);
|
|
168
|
+
const response = await AIClient.sendAndWait(prompt, config.ai.model, config.ai.maxRetries);
|
|
163
169
|
|
|
164
170
|
try {
|
|
165
171
|
return AIClient.parseJSON(response);
|
|
@@ -176,8 +182,11 @@ ${changeSummary}
|
|
|
176
182
|
async function generateCommitMessage(group, files, config) {
|
|
177
183
|
const filesList = files
|
|
178
184
|
.map((file) => {
|
|
179
|
-
const diff = getFileDiff(file.filePath, file.isNew);
|
|
180
|
-
|
|
185
|
+
const diff = getFileDiff(file.filePath, file.isNew, file.isDeleted);
|
|
186
|
+
let status = '修改';
|
|
187
|
+
if (file.isNew) status = '新增';
|
|
188
|
+
if (file.isDeleted) status = '刪除';
|
|
189
|
+
return `檔案: ${file.filePath} [${status}]\n${diff}`;
|
|
181
190
|
})
|
|
182
191
|
.join('\n\n---\n\n');
|
|
183
192
|
|
|
@@ -208,7 +217,7 @@ feat(auth): 新增使用者登入功能
|
|
|
208
217
|
- 新增登入頁面 UI
|
|
209
218
|
- 整合 JWT 認證機制`;
|
|
210
219
|
|
|
211
|
-
const response = await AIClient.sendAndWait(prompt, config.ai.model);
|
|
220
|
+
const response = await AIClient.sendAndWait(prompt, config.ai.model, config.ai.maxRetries);
|
|
212
221
|
|
|
213
222
|
// 清理可能的 markdown code block 標記
|
|
214
223
|
let commitMessage = response.trim();
|
|
@@ -240,9 +249,11 @@ async function commitGroup(group, files, config) {
|
|
|
240
249
|
|
|
241
250
|
// Add 這組的檔案
|
|
242
251
|
for (const file of files) {
|
|
243
|
-
|
|
252
|
+
const fileStatus = file.isNew ? '新增' : file.isDeleted ? '刪除' : '修改';
|
|
253
|
+
console.log(` ├─ [${fileStatus}] ${file.filePath}`);
|
|
244
254
|
try {
|
|
245
255
|
// 使用 JSON.stringify 來正確處理包含空格或特殊字符的檔案路徑
|
|
256
|
+
// git add 對於刪除的檔案也能正確處理
|
|
246
257
|
execSync(`git add ${JSON.stringify(file.filePath)}`, { encoding: 'utf-8' });
|
|
247
258
|
} catch (addError) {
|
|
248
259
|
console.error(` ⚠️ 無法加入檔案: ${file.filePath}`, addError.message);
|
|
@@ -323,7 +334,9 @@ export async function commitAllCommand() {
|
|
|
323
334
|
|
|
324
335
|
console.log(`📊 找到 ${changes.length} 個變更的檔案:\n`);
|
|
325
336
|
changes.forEach((change, index) => {
|
|
326
|
-
|
|
337
|
+
let status = '修改';
|
|
338
|
+
if (change.isNew) status = '新增';
|
|
339
|
+
if (change.isDeleted) status = '刪除';
|
|
327
340
|
console.log(` [${index}] ${status} - ${change.filePath}`);
|
|
328
341
|
});
|
|
329
342
|
console.log();
|
|
@@ -336,10 +349,46 @@ export async function commitAllCommand() {
|
|
|
336
349
|
process.exit(1);
|
|
337
350
|
}
|
|
338
351
|
|
|
352
|
+
// 驗證所有檔案都被包含在分組中
|
|
353
|
+
const groupedIndices = new Set();
|
|
354
|
+
groups.forEach((group) => {
|
|
355
|
+
group.file_indices.forEach((index) => {
|
|
356
|
+
groupedIndices.add(index);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const ungroupedIndices = [];
|
|
361
|
+
for (let i = 0; i < changes.length; i++) {
|
|
362
|
+
if (!groupedIndices.has(i)) {
|
|
363
|
+
ungroupedIndices.push(i);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 如果有檔案未被分組,創建一個 "其他變更" 群組
|
|
368
|
+
if (ungroupedIndices.length > 0) {
|
|
369
|
+
console.log(`\n⚠️ 發現 ${ungroupedIndices.length} 個未分組的檔案,將自動歸類:`);
|
|
370
|
+
ungroupedIndices.forEach((index) => {
|
|
371
|
+
console.log(` - ${changes[index].filePath}`);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
groups.push({
|
|
375
|
+
group_name: '其他變更',
|
|
376
|
+
commit_type: 'chore',
|
|
377
|
+
commit_scope: 'misc',
|
|
378
|
+
file_indices: ungroupedIndices,
|
|
379
|
+
description: '未能自動分類的其他變更',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
339
383
|
logger.success(`AI 分析完成,共分為 ${groups.length} 個群組:\n`);
|
|
340
384
|
groups.forEach((group, index) => {
|
|
341
385
|
console.log(` 群組 ${index + 1}: ${group.group_name} (${group.commit_type})`);
|
|
342
386
|
console.log(` └─ 包含 ${group.file_indices.length} 個檔案`);
|
|
387
|
+
if (config.output.verbose) {
|
|
388
|
+
group.file_indices.forEach((fileIndex) => {
|
|
389
|
+
console.log(` - [${fileIndex}] ${changes[fileIndex].filePath}`);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
343
392
|
});
|
|
344
393
|
|
|
345
394
|
// 3. 依序提交每個群組
|
|
@@ -352,6 +401,14 @@ export async function commitAllCommand() {
|
|
|
352
401
|
const group = groups[i];
|
|
353
402
|
const groupFiles = group.file_indices.map((index) => changes[index]);
|
|
354
403
|
|
|
404
|
+
// 驗證檔案索引是否有效
|
|
405
|
+
const invalidIndices = group.file_indices.filter((idx) => idx >= changes.length);
|
|
406
|
+
if (invalidIndices.length > 0) {
|
|
407
|
+
console.error(`\n❌ 群組 ${i + 1} 包含無效的檔案索引:`, invalidIndices);
|
|
408
|
+
console.log(` 跳過此群組: ${group.group_name}`);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
355
412
|
const success = await commitGroup(group, groupFiles, config);
|
|
356
413
|
if (success) {
|
|
357
414
|
successCount++;
|
|
@@ -363,9 +420,18 @@ export async function commitAllCommand() {
|
|
|
363
420
|
logger.success(`完成!成功提交 ${successCount}/${groups.length} 個群組`);
|
|
364
421
|
logger.separator('=', 60);
|
|
365
422
|
|
|
423
|
+
if (successCount < groups.length) {
|
|
424
|
+
const failedCount = groups.length - successCount;
|
|
425
|
+
console.log(`\n⚠️ 有 ${failedCount} 個群組提交失敗`);
|
|
426
|
+
}
|
|
427
|
+
|
|
366
428
|
// 顯示最近的幾個 commits
|
|
367
|
-
|
|
368
|
-
|
|
429
|
+
if (successCount > 0) {
|
|
430
|
+
console.log('\n📋 最近的 commits:');
|
|
431
|
+
execSync(`git log -${successCount} --oneline`, { stdio: 'inherit' });
|
|
432
|
+
} else {
|
|
433
|
+
console.log('\n⚠️ 沒有成功的提交');
|
|
434
|
+
}
|
|
369
435
|
|
|
370
436
|
// Reset 任何剩餘的 staged 檔案
|
|
371
437
|
try {
|