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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-git-tools",
3
- "version": "2.0.23",
3
+ "version": "2.0.25",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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
- return `[檔案 ${index}] ${change.filePath}\n${
102
- change.isNew ? '(新檔案)' : '(已修改)'
103
- }\n${truncatedDiff}\n`;
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
- return `檔案: ${file.filePath}\n${diff}`;
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
- console.log(` ├─ ${file.filePath}`);
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
- const status = change.isNew ? '新增' : '修改';
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
- console.log('\n📋 最近的 commits:');
368
- execSync(`git log -${successCount} --oneline`, { stdio: 'inherit' });
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 {