ai-git-tools 2.0.77 → 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 +1 -1
- package/src/commands/commit-all.js +23 -12
- package/src/commands/commit.js +27 -7
- package/src/core/ai-client.js +33 -6
- package/src/pr-modules/ai/code-analyzer.js +11 -1
- package/src/pr-modules/core/git-operations.js +2 -2
- package/src/pr-modules/utils/helpers.js +40 -6
- package/src/utils/helpers.js +36 -2
package/package.json
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
188
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
390
|
-
group.file_indices.forEach(
|
|
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(
|
|
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(
|
|
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(
|
|
448
|
+
const groupFiles = group.file_indices.map(index => changes[index]);
|
|
438
449
|
|
|
439
450
|
// 驗證檔案索引是否有效
|
|
440
|
-
const invalidIndices = group.file_indices.filter(
|
|
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}`);
|
package/src/commands/commit.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
|
package/src/core/ai-client.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
|
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
|
}
|
|
@@ -11,9 +11,9 @@ export class GitOperations {
|
|
|
11
11
|
*/
|
|
12
12
|
detectReleaseBranches() {
|
|
13
13
|
try {
|
|
14
|
-
//
|
|
14
|
+
// 嘗試同步遠端(含 --prune 以清除已刪除的遠端分支),失敗就用本機已知的遠端資訊
|
|
15
15
|
try {
|
|
16
|
-
execSync('git fetch origin', { stdio: 'ignore', timeout: 15000 });
|
|
16
|
+
execSync('git fetch --prune origin', { stdio: 'ignore', timeout: 15000 });
|
|
17
17
|
} catch (_) {
|
|
18
18
|
// fetch 失敗,繼續使用已經 cache 的遠端分支
|
|
19
19
|
}
|
|
@@ -4,11 +4,11 @@ import { colors } from './constants.js';
|
|
|
4
4
|
* 日誌工具
|
|
5
5
|
*/
|
|
6
6
|
export const log = {
|
|
7
|
-
info:
|
|
8
|
-
success:
|
|
9
|
-
warning:
|
|
10
|
-
error:
|
|
11
|
-
step:
|
|
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(
|
|
92
|
+
.map(r => ` - [${r.id}] ${r.category}: ${r.desc}`)
|
|
59
93
|
.join('\n');
|
|
60
94
|
const fg = PROJECT_SKILLS_CONTEXT.frontendGuidelines;
|
|
61
95
|
return `
|
package/src/utils/helpers.js
CHANGED
|
@@ -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);
|