ai-git-tools 2.0.78 → 2.0.79

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.78",
3
+ "version": "2.0.79",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ import { readFileSync, writeFileSync, unlinkSync } from 'fs';
9
9
  import { loadCommitConfig } from '../core/config-loader.js';
10
10
  import { AIClient } from '../core/ai-client.js';
11
11
  import { Logger } from '../utils/logger.js';
12
- import { handleError } from '../utils/helpers.js';
12
+ import { handleError, isCopilotSubscriptionError } from '../utils/helpers.js';
13
13
 
14
14
  /**
15
15
  * 獲取檔案的變更內容
@@ -54,7 +54,7 @@ function getAllChanges() {
54
54
  }
55
55
 
56
56
  const changes = [];
57
- const lines = status.split('\n').filter((line) => line.trim());
57
+ const lines = status.split('\n').filter(line => line.trim());
58
58
 
59
59
  for (const line of lines) {
60
60
  const statusCode = line.substring(0, 2);
@@ -184,8 +184,19 @@ ${changeSummary}
184
184
  try {
185
185
  return AIClient.parseJSON(response);
186
186
  } catch (error) {
187
- console.error('❌ 無法解析 AI 回應:', error.message);
188
- console.log('原始回應:', response);
187
+ // 區分不同類型的錯誤
188
+ if (isCopilotSubscriptionError(error)) {
189
+ console.error('\n🔑 看起來是 GitHub Copilot 授權問題');
190
+ console.error('\n解決方案:');
191
+ console.error(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
192
+ console.error(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 存取權限');
193
+ console.error(' 3. 嘗試重新登入:');
194
+ console.error(' gh auth logout');
195
+ console.error(' gh auth login');
196
+ } else {
197
+ console.error('❌ 無法解析 AI 回應:', error.message);
198
+ console.log('原始回應:', response);
199
+ }
189
200
  return null;
190
201
  }
191
202
  }
@@ -197,7 +208,7 @@ async function generateCommitMessage(group, files, config) {
197
208
  // 每個檔案最多 2000 字元,避免單一群組內大量 diff 超出限制
198
209
  const MAX_DIFF_PER_FILE = 2000;
199
210
  const filesList = files
200
- .map((file) => {
211
+ .map(file => {
201
212
  const diff = getFileDiff(file.filePath, file.isNew, file.isDeleted);
202
213
  const truncatedDiff =
203
214
  diff.length > MAX_DIFF_PER_FILE
@@ -295,7 +306,7 @@ async function commitGroup(group, files, config) {
295
306
 
296
307
  console.log(`\n 📝 Commit Message:`);
297
308
  console.log(` ${'─'.repeat(50)}`);
298
- commitMessage.split('\n').forEach((line) => {
309
+ commitMessage.split('\n').forEach(line => {
299
310
  console.log(` ${line}`);
300
311
  });
301
312
  console.log(` ${'─'.repeat(50)}`);
@@ -386,8 +397,8 @@ export async function commitAllCommand() {
386
397
 
387
398
  // 驗證所有檔案都被包含在分組中
388
399
  const groupedIndices = new Set();
389
- groups.forEach((group) => {
390
- group.file_indices.forEach((index) => {
400
+ groups.forEach(group => {
401
+ group.file_indices.forEach(index => {
391
402
  groupedIndices.add(index);
392
403
  });
393
404
  });
@@ -402,7 +413,7 @@ export async function commitAllCommand() {
402
413
  // 如果有檔案未被分組,創建一個 "其他變更" 群組
403
414
  if (ungroupedIndices.length > 0) {
404
415
  console.log(`\n⚠️ 發現 ${ungroupedIndices.length} 個未分組的檔案,將自動歸類:`);
405
- ungroupedIndices.forEach((index) => {
416
+ ungroupedIndices.forEach(index => {
406
417
  console.log(` - ${changes[index].filePath}`);
407
418
  });
408
419
 
@@ -420,7 +431,7 @@ export async function commitAllCommand() {
420
431
  console.log(` 群組 ${index + 1}: ${group.group_name} (${group.commit_type})`);
421
432
  console.log(` └─ 包含 ${group.file_indices.length} 個檔案`);
422
433
  if (config.output.verbose) {
423
- group.file_indices.forEach((fileIndex) => {
434
+ group.file_indices.forEach(fileIndex => {
424
435
  console.log(` - [${fileIndex}] ${changes[fileIndex].filePath}`);
425
436
  });
426
437
  }
@@ -434,10 +445,10 @@ export async function commitAllCommand() {
434
445
  let successCount = 0;
435
446
  for (let i = 0; i < groups.length; i++) {
436
447
  const group = groups[i];
437
- const groupFiles = group.file_indices.map((index) => changes[index]);
448
+ const groupFiles = group.file_indices.map(index => changes[index]);
438
449
 
439
450
  // 驗證檔案索引是否有效
440
- const invalidIndices = group.file_indices.filter((idx) => idx >= changes.length);
451
+ const invalidIndices = group.file_indices.filter(idx => idx >= changes.length);
441
452
  if (invalidIndices.length > 0) {
442
453
  console.error(`\n❌ 群組 ${i + 1} 包含無效的檔案索引:`, invalidIndices);
443
454
  console.log(` 跳過此群組: ${group.group_name}`);
@@ -9,7 +9,13 @@ import { loadCommitConfig } from '../core/config-loader.js';
9
9
  import { GitOperations } from '../core/git-operations.js';
10
10
  import { AIClient } from '../core/ai-client.js';
11
11
  import { Logger } from '../utils/logger.js';
12
- import { cleanCommitMessage, validateCommitMessage, handleError, getProjectTypePrompt } from '../utils/helpers.js';
12
+ import {
13
+ cleanCommitMessage,
14
+ validateCommitMessage,
15
+ handleError,
16
+ getProjectTypePrompt,
17
+ isCopilotSubscriptionError,
18
+ } from '../utils/helpers.js';
13
19
 
14
20
  export async function commitCommand() {
15
21
  const logger = new Logger();
@@ -108,13 +114,27 @@ ${truncatedDiff}`;
108
114
  // 所有重試都失敗
109
115
  if (!commitMessage) {
110
116
  logger.error('無法產生有效的 commit message');
111
- if (lastError && config.output.verbose) {
112
- console.log(` 最後錯誤: ${lastError.message}`);
117
+
118
+ // 區分不同類型的錯誤
119
+ if (isCopilotSubscriptionError(lastError)) {
120
+ console.log('\n🔑 看起來是 GitHub Copilot 授權問題\n');
121
+ console.log('解決方案:');
122
+ console.log(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
123
+ console.log(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 存取權限');
124
+ console.log(' 3. 嘗試重新登入:');
125
+ console.log(' gh auth logout');
126
+ console.log(' gh auth login');
127
+ console.log(' 4. 若是公司帳號,確保使用公司的 GitHub 帳號登入');
128
+ } else {
129
+ if (config.output.verbose && lastError) {
130
+ console.log(` 最後錯誤: ${lastError.message}`);
131
+ }
132
+ console.log('\n💡 建議:');
133
+ console.log(' 1. 檢查網路連線');
134
+ console.log(' 2. 嘗試更換 AI 模型(使用 --model 參數)');
135
+ console.log(' 3. 確認變更內容不會太複雜或太大');
113
136
  }
114
- console.log('\n💡 建議:');
115
- console.log(' 1. 檢查網路連線');
116
- console.log(' 2. 嘗試更換 AI 模型(使用 --model 參數)');
117
- console.log(' 3. 確認變更內容不會太複雜或太大');
137
+
118
138
  throw new Error('無法產生有效的 commit message');
119
139
  }
120
140
 
@@ -6,6 +6,26 @@
6
6
  import { CopilotClient, approveAll } from '@github/copilot-sdk';
7
7
 
8
8
  export class AIClient {
9
+ /**
10
+ * 檢測是否為 Copilot 授權相關的錯誤
11
+ */
12
+ static isCopilotAuthError(error) {
13
+ const message = error?.message || error?.toString() || '';
14
+ const lowerMessage = message.toLowerCase();
15
+
16
+ return (
17
+ lowerMessage.includes('permission') ||
18
+ lowerMessage.includes('unauthorized') ||
19
+ lowerMessage.includes('forbidden') ||
20
+ lowerMessage.includes('not authorized') ||
21
+ lowerMessage.includes('authentication failed') ||
22
+ lowerMessage.includes('access denied') ||
23
+ lowerMessage.includes('you do not have access') ||
24
+ lowerMessage.includes('copilot') ||
25
+ message.includes('EACCES')
26
+ );
27
+ }
28
+
9
29
  /**
10
30
  * 發送 prompt 並等待回應(帶重試機制和超時保護)
11
31
  */
@@ -15,11 +35,11 @@ export class AIClient {
15
35
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
16
36
  const client = new CopilotClient();
17
37
  try {
18
- const session = await client.createSession({
38
+ const session = await client.createSession({
19
39
  model,
20
- onPermissionRequest: approveAll
40
+ onPermissionRequest: approveAll,
21
41
  });
22
-
42
+
23
43
  // 使用 Promise.race 實現超時控制
24
44
  const responsePromise = session.sendAndWait({ prompt });
25
45
  const timeoutPromise = new Promise((_, reject) => {
@@ -34,7 +54,7 @@ export class AIClient {
34
54
  lastError = error;
35
55
  if (attempt < maxRetries) {
36
56
  console.log(`⚠️ AI 請求失敗,重試第 ${attempt}/${maxRetries} 次...`);
37
- await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
57
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
38
58
  }
39
59
  } finally {
40
60
  // 確保每次都關閉 client,無論成功或失敗
@@ -46,7 +66,11 @@ export class AIClient {
46
66
  }
47
67
  }
48
68
 
49
- throw new Error(`AI 請求失敗: ${lastError?.message || '未知錯誤'}`);
69
+ // 區分不同類型的錯誤
70
+ const errorObj = new Error(`AI 請求失敗: ${lastError?.message || '未知錯誤'}`);
71
+ errorObj.originalError = lastError;
72
+ errorObj.isCopilotAuth = AIClient.isCopilotAuthError(lastError);
73
+ throw errorObj;
50
74
  }
51
75
 
52
76
  /**
@@ -54,7 +78,10 @@ export class AIClient {
54
78
  */
55
79
  static parseJSON(content) {
56
80
  // 移除可能的 markdown code block 標記
57
- const jsonContent = content.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
81
+ const jsonContent = content
82
+ .replace(/```json\n?/g, '')
83
+ .replace(/```\n?/g, '')
84
+ .trim();
58
85
 
59
86
  try {
60
87
  return JSON.parse(jsonContent);
@@ -1,6 +1,6 @@
1
1
  import { CopilotClient, approveAll } from '@github/copilot-sdk';
2
2
  import { CONSTANTS, PROJECT_SKILLS_CONTEXT } from '../utils/constants.js';
3
- import { getSkillsSummaryForPrompt, log } from '../utils/helpers.js';
3
+ import { getSkillsSummaryForPrompt, log, isCopilotAuthError } from '../utils/helpers.js';
4
4
 
5
5
  // 讓 CopilotClient 啟動的 Node 子程序繼承此設定,靜音 SQLite ExperimentalWarning
6
6
  process.env.NODE_NO_WARNINGS = '1';
@@ -106,6 +106,16 @@ export class AIAnalyzer {
106
106
  log.info(` 建議改用更快的模型:ai-git-tools pr --model gpt-5.4\n`);
107
107
  throw new Error(`AI 生成超時:模型 ${this.model} 回應過慢,請加 --model gpt-5.4 重試`);
108
108
  }
109
+
110
+ // 檢測 Copilot 授權錯誤
111
+ if (isCopilotAuthError(error)) {
112
+ log.error('看起來是 GitHub Copilot 授權問題');
113
+ log.error(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
114
+ log.error(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 存取權限');
115
+ log.error(' 3. 嘗試重新登入: gh auth logout && gh auth login');
116
+ throw error;
117
+ }
118
+
109
119
  throw error;
110
120
  }
111
121
  }
@@ -4,11 +4,11 @@ import { colors } from './constants.js';
4
4
  * 日誌工具
5
5
  */
6
6
  export const log = {
7
- info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
8
- success: (msg) => console.log(`${colors.green}✅${colors.reset} ${msg}`),
9
- warning: (msg) => console.log(`${colors.yellow}⚠️${colors.reset} ${msg}`),
10
- error: (msg) => console.log(`${colors.red}❌${colors.reset} ${msg}`),
11
- step: (msg) => console.log(`${colors.cyan}▶${colors.reset} ${msg}`),
7
+ info: msg => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
8
+ success: msg => console.log(`${colors.green}✅${colors.reset} ${msg}`),
9
+ warning: msg => console.log(`${colors.yellow}⚠️${colors.reset} ${msg}`),
10
+ error: msg => console.log(`${colors.red}❌${colors.reset} ${msg}`),
11
+ step: msg => console.log(`${colors.cyan}▶${colors.reset} ${msg}`),
12
12
  };
13
13
 
14
14
  /**
@@ -24,10 +24,44 @@ export class PRError extends Error {
24
24
  }
25
25
  }
26
26
 
27
+ /**
28
+ * 檢測 Copilot 授權錯誤
29
+ */
30
+ export function isCopilotAuthError(error) {
31
+ const message = (error?.message || error?.toString() || '').toLowerCase();
32
+ const originalMessage = (error?.originalError?.message || '').toLowerCase();
33
+
34
+ return (
35
+ message.includes('permission') ||
36
+ message.includes('unauthorized') ||
37
+ message.includes('forbidden') ||
38
+ message.includes('not authorized') ||
39
+ message.includes('authentication failed') ||
40
+ message.includes('access denied') ||
41
+ message.includes('you do not have access') ||
42
+ message.includes('copilot') ||
43
+ originalMessage.includes('permission') ||
44
+ originalMessage.includes('unauthorized')
45
+ );
46
+ }
47
+
27
48
  /**
28
49
  * 錯誤處理器
29
50
  */
30
51
  export function handleError(error) {
52
+ // 先檢查是否為 Copilot 授權相關的錯誤
53
+ if (isCopilotAuthError(error)) {
54
+ log.error('看起來是 GitHub Copilot 授權問題\n');
55
+ console.log(`${colors.cyan}解決方案:${colors.reset}`);
56
+ console.log(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
57
+ console.log(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 存取權限');
58
+ console.log(' 3. 嘗試重新登入:');
59
+ console.log(' gh auth logout');
60
+ console.log(' gh auth login');
61
+ console.log(' 4. 若是公司帳號,確保使用公司的 GitHub 帳號登入\n');
62
+ return;
63
+ }
64
+
31
65
  if (error instanceof PRError) {
32
66
  log.error(error.message);
33
67
 
@@ -55,7 +89,7 @@ export function handleError(error) {
55
89
  */
56
90
  export function getSkillsSummaryForPrompt(PROJECT_SKILLS_CONTEXT) {
57
91
  const rbp = PROJECT_SKILLS_CONTEXT.reactBestPractices
58
- .map((r) => ` - [${r.id}] ${r.category}: ${r.desc}`)
92
+ .map(r => ` - [${r.id}] ${r.category}: ${r.desc}`)
59
93
  .join('\n');
60
94
  const fg = PROJECT_SKILLS_CONTEXT.frontendGuidelines;
61
95
  return `
@@ -43,19 +43,53 @@ export function validateCommitMessage(message) {
43
43
  return { valid: true };
44
44
  }
45
45
 
46
+ /**
47
+ * 檢測 Copilot 授權錯誤
48
+ */
49
+ export function isCopilotSubscriptionError(error) {
50
+ if (!error) return false;
51
+
52
+ const message = (error.message || error.toString() || '').toLowerCase();
53
+ const originalError = (error.originalError?.message || '').toLowerCase();
54
+
55
+ return (
56
+ error.isCopilotAuth === true ||
57
+ message.includes('permission') ||
58
+ message.includes('unauthorized') ||
59
+ message.includes('forbidden') ||
60
+ message.includes('not authorized') ||
61
+ originalError.includes('permission') ||
62
+ originalError.includes('unauthorized')
63
+ );
64
+ }
65
+
46
66
  /**
47
67
  * 錯誤處理
48
68
  */
49
69
  export function handleError(error) {
50
70
  console.error('\n❌ 錯誤:', error.message);
51
-
71
+
72
+ // 檢查是否為 Copilot 授權相關的錯誤
73
+ if (isCopilotSubscriptionError(error)) {
74
+ console.log('\n🔐 看起來是 GitHub Copilot 授權問題\n');
75
+ console.log('解決方案:');
76
+ console.log(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
77
+ console.log(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 訪問權限');
78
+ console.log(' 3. 嘗試重新登入:');
79
+ console.log(' gh auth logout');
80
+ console.log(' gh auth login');
81
+ console.log(' 4. 若是公司帳號,確保使用公司的 GitHub 帳號登入');
82
+ console.log('');
83
+ return;
84
+ }
85
+
52
86
  if (error.suggestions && error.suggestions.length > 0) {
53
87
  console.log('\n💡 建議解決方案:');
54
88
  error.suggestions.forEach((suggestion, index) => {
55
89
  console.log(` ${index + 1}. ${suggestion}`);
56
90
  });
57
91
  }
58
-
92
+
59
93
  if (error.stack && process.env.VERBOSE) {
60
94
  console.error('\n堆疊追蹤:');
61
95
  console.error(error.stack);