ai-git-tools 2.0.78 → 2.0.80
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/README.md +2 -0
- package/bin/cli.js +39 -61
- package/package.json +1 -1
- package/src/ai/prompts/commit-message.js +42 -0
- package/src/ai/prompts/pr-content.js +151 -0
- package/src/commands/commit-all.js +23 -12
- package/src/commands/commit.js +92 -103
- package/src/commands/pr.js +22 -31
- package/src/commands/usage.js +557 -0
- package/src/core/ai-client.js +33 -6
- package/src/core/config-loader.js +151 -113
- package/src/core/git-operations.js +122 -39
- package/src/pr-modules/ai/code-analyzer.js +19 -141
- package/src/pr-modules/ai/label-analyzer.js +1 -1
- package/src/pr-modules/core/github-api.js +56 -5
- package/src/pr-modules/core/workflow.js +25 -5
- package/src/pr-modules/reviewers/reviewer-selector.js +2 -2
- package/src/pr-modules/ui/interactive-select.js +1 -1
- package/src/utils/cli-helpers.js +51 -0
- package/src/utils/constants.js +49 -0
- package/src/utils/helpers.js +74 -3
- package/src/utils/logger.js +13 -11
- package/src/{pr-modules/utils/constants.js → utils/project-skills.js} +26 -49
- package/src/pr-modules/core/config-loader.js +0 -132
- package/src/pr-modules/core/git-operations.js +0 -248
- package/src/pr-modules/ui/logger.js +0 -40
- package/src/pr-modules/utils/helpers.js +0 -75
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { CopilotClient, approveAll } from '@github/copilot-sdk';
|
|
2
|
-
import { CONSTANTS
|
|
3
|
-
import {
|
|
2
|
+
import { CONSTANTS } from '../../utils/constants.js';
|
|
3
|
+
import { log } from '../../utils/logger.js';
|
|
4
|
+
import { isCopilotAuthError } from '../../utils/helpers.js';
|
|
5
|
+
import {
|
|
6
|
+
generatePRContentPrompt,
|
|
7
|
+
generateImpactAnalysisPrompt,
|
|
8
|
+
} from '../../ai/prompts/pr-content.js';
|
|
4
9
|
|
|
5
10
|
// 讓 CopilotClient 啟動的 Node 子程序繼承此設定,靜音 SQLite ExperimentalWarning
|
|
6
11
|
process.env.NODE_NO_WARNINGS = '1';
|
|
@@ -65,7 +70,7 @@ export class AIAnalyzer {
|
|
|
65
70
|
* 生成 PR 內容(失敗時自動重建 client 重試一次)
|
|
66
71
|
*/
|
|
67
72
|
async generatePRContent(commits, diff) {
|
|
68
|
-
const prompt =
|
|
73
|
+
const prompt = generatePRContentPrompt(commits, diff);
|
|
69
74
|
|
|
70
75
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
71
76
|
try {
|
|
@@ -106,6 +111,16 @@ export class AIAnalyzer {
|
|
|
106
111
|
log.info(` 建議改用更快的模型:ai-git-tools pr --model gpt-5.4\n`);
|
|
107
112
|
throw new Error(`AI 生成超時:模型 ${this.model} 回應過慢,請加 --model gpt-5.4 重試`);
|
|
108
113
|
}
|
|
114
|
+
|
|
115
|
+
// 檢測 Copilot 授權錯誤
|
|
116
|
+
if (isCopilotAuthError(error)) {
|
|
117
|
+
log.error('看起來是 GitHub Copilot 授權問題');
|
|
118
|
+
log.error(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
|
|
119
|
+
log.error(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 存取權限');
|
|
120
|
+
log.error(' 3. 嘗試重新登入: gh auth logout && gh auth login');
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
|
|
109
124
|
throw error;
|
|
110
125
|
}
|
|
111
126
|
}
|
|
@@ -118,8 +133,7 @@ export class AIAnalyzer {
|
|
|
118
133
|
* 分析程式碼影響範圍
|
|
119
134
|
*/
|
|
120
135
|
async analyzeImpact(changedFiles, diff, commits) {
|
|
121
|
-
const
|
|
122
|
-
const prompt = this.buildAnalysisPrompt(changedFiles, diff, commits, skillsSummary);
|
|
136
|
+
const prompt = generateImpactAnalysisPrompt(changedFiles, diff, commits);
|
|
123
137
|
|
|
124
138
|
const session = await this._createSession();
|
|
125
139
|
|
|
@@ -168,143 +182,7 @@ export class AIAnalyzer {
|
|
|
168
182
|
// 注意:不在此 stop() client,改由 close() 統一清理以便複用
|
|
169
183
|
}
|
|
170
184
|
|
|
171
|
-
/**
|
|
172
|
-
* 建立 PR 生成 Prompt
|
|
173
|
-
*/
|
|
174
|
-
buildPRPrompt(commits, diff) {
|
|
175
|
-
return `你是一個專業的前端工程師,熟悉 Next.js、React 效能優化和團隊開發規範。
|
|
176
|
-
請根據以下 commit 訊息和程式碼變更,直接輸出一個清晰的 Pull Request 標題和描述。
|
|
177
|
-
|
|
178
|
-
**輸出格式**(不要加任何引導語,直接輸出以下內容):
|
|
179
|
-
|
|
180
|
-
# [type]: [PR 標題]
|
|
181
|
-
|
|
182
|
-
> type 必須是以下之一:feat / fix / refactor / style / docs / test / chore / perf
|
|
183
|
-
> **重要**:如果有新增任何功能、新增檔案、新增 API、新增組件,優先使用 **feat**
|
|
184
|
-
|
|
185
|
-
## 📝 變更摘要
|
|
186
|
-
[簡述這個 PR 的主要目的和影響範圍,2-3 句話]
|
|
187
|
-
|
|
188
|
-
## 🎯 主要變更
|
|
189
|
-
- [變更項目 1]
|
|
190
|
-
- [變更項目 2]
|
|
191
|
-
- [變更項目 3]
|
|
192
|
-
|
|
193
|
-
## 🔀 變更類型
|
|
194
|
-
- [ ] ✨ 新功能 (feat)
|
|
195
|
-
- [ ] 🐛 Bug 修復 (fix)
|
|
196
|
-
- [ ] ♻️ 重構 (refactor)
|
|
197
|
-
- [ ] 💄 樣式調整 (style)
|
|
198
|
-
- [ ] 📝 文件更新 (docs)
|
|
199
|
-
- [ ] ⚡ 效能改進 (perf)
|
|
200
|
-
- [ ] 🔧 其他 (chore)
|
|
201
|
-
|
|
202
|
-
> 根據 diff 和 commit 自動勾選(可複選),[ ] 改為 [x];有新增檔案或功能必勾 ✨ feat
|
|
203
|
-
|
|
204
|
-
## 🧪 測試方法
|
|
205
|
-
1. [具體的測試步驟 1]
|
|
206
|
-
2. [具體的測試步驟 2]
|
|
207
|
-
3. [具體的測試步驟 3]
|
|
208
|
-
|
|
209
|
-
## 💥 Breaking Changes
|
|
210
|
-
[如果有破壞性變更請詳細說明,沒有則填寫「無」]
|
|
211
|
-
|
|
212
|
-
## 📌 注意事項
|
|
213
|
-
[需要特別注意的事項]
|
|
214
|
-
|
|
215
|
-
## 📸 截圖
|
|
216
|
-
[如果是 UI 變更,提醒需要截圖]
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## ⚠️ 風險與注意事項
|
|
221
|
-
**Risk Level**: \`LOW\` / \`MEDIUM\` / \`HIGH\`
|
|
222
|
-
|
|
223
|
-
[說明潛在風險、破壞性變更(breaking changes)、需要特別小心的地方;沒有則填「無」]
|
|
224
185
|
|
|
225
|
-
## 👀 Reviewer 重點
|
|
226
|
-
- [請 reviewer 特別關注的邏輯或設計決策 1]
|
|
227
|
-
- [請 reviewer 特別關注的邏輯或設計決策 2]
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
**規則**:直接輸出 # [type]: [標題],繁體中文(台灣正體),type 符合 Conventional Commits;新增檔案/功能優先 feat,可複選多種類型;Risk Level:HIGH=核心流程,MEDIUM=影響現有功能,LOW=新增或重構;Reviewer 重點列 1-3 個值得仔細看的地方。
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
**Commit 訊息**:
|
|
236
|
-
${commits}
|
|
237
|
-
|
|
238
|
-
**程式碼變更**:
|
|
239
|
-
${diff}`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 建立影響分析 Prompt
|
|
244
|
-
*/
|
|
245
|
-
buildAnalysisPrompt(changedFiles, diff, commits, skillsSummary) {
|
|
246
|
-
return `你是一個資深的程式碼審查專家,精通 React/Next.js 效能優化與前端架構設計。
|
|
247
|
-
請分析以下程式碼變更,提供專業的影響範圍分析與規範合規檢查。
|
|
248
|
-
|
|
249
|
-
${skillsSummary}
|
|
250
|
-
|
|
251
|
-
**變更檔案列表**:
|
|
252
|
-
${changedFiles.slice(0, CONSTANTS.MAX_FILES_IN_PROMPT).join('\n')}
|
|
253
|
-
${
|
|
254
|
-
changedFiles.length > CONSTANTS.MAX_FILES_IN_PROMPT
|
|
255
|
-
? `... 還有 ${changedFiles.length - CONSTANTS.MAX_FILES_IN_PROMPT} 個檔案`
|
|
256
|
-
: ''
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
**Commit 訊息**:
|
|
260
|
-
${commits.split('\n').slice(0, CONSTANTS.MAX_COMMITS_IN_PROMPT).join('\n')}
|
|
261
|
-
|
|
262
|
-
**程式碼變更內容**:
|
|
263
|
-
\`\`\`diff
|
|
264
|
-
${diff.substring(0, CONSTANTS.MAX_DIFF_LENGTH)}
|
|
265
|
-
${diff.length > CONSTANTS.MAX_DIFF_LENGTH ? '\n... (內容過長已截斷)' : ''}
|
|
266
|
-
\`\`\`
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
請以 JSON 格式輸出分析結果(不要加任何其他文字,只輸出 JSON):
|
|
271
|
-
|
|
272
|
-
\`\`\`json
|
|
273
|
-
{
|
|
274
|
-
"blastRadius": {
|
|
275
|
-
"modules": ["影響的模組1", "影響的模組2"],
|
|
276
|
-
"impacts": ["影響層面1", "影響層面2"],
|
|
277
|
-
"riskLevel": "低|中|高",
|
|
278
|
-
"riskReasons": ["風險原因1", "風險原因2"],
|
|
279
|
-
"externalBehaviors": ["對外行為變更說明"]
|
|
280
|
-
},
|
|
281
|
-
"warnings": [
|
|
282
|
-
{
|
|
283
|
-
"level": "⚠️|ℹ️",
|
|
284
|
-
"message": "問題描述",
|
|
285
|
-
"suggestion": "改善建議"
|
|
286
|
-
}
|
|
287
|
-
]
|
|
288
|
-
}
|
|
289
|
-
\`\`\`
|
|
290
|
-
|
|
291
|
-
**分析重點**:
|
|
292
|
-
|
|
293
|
-
1. **影響範圍 (blastRadius)**:
|
|
294
|
-
- 真實分析程式碼變更內容,不要只看檔案路徑
|
|
295
|
-
- 識別實際影響的模組
|
|
296
|
-
- 分析影響層面(API、資料庫、UI、商業邏輯、效能、安全性等)
|
|
297
|
-
- 評估風險等級,並說明原因
|
|
298
|
-
- 識別對外行為變更
|
|
299
|
-
|
|
300
|
-
2. **規範警告 (warnings)**:
|
|
301
|
-
- 檢查是否有安全風險
|
|
302
|
-
- 檢查是否缺少錯誤處理
|
|
303
|
-
- 檢查是否有效能問題
|
|
304
|
-
- 檢查是否違反最佳實踐
|
|
305
|
-
|
|
306
|
-
**回應格式**:只輸出有效的 JSON,不要有任何前綴或後綴文字。`;
|
|
307
|
-
}
|
|
308
186
|
|
|
309
187
|
/**
|
|
310
188
|
* 解析 AI 生成的 PR 內容
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
2
|
import { writeFileSync, unlinkSync, existsSync } from 'fs';
|
|
3
|
-
import { log } from '
|
|
4
|
-
import { colors } from '
|
|
3
|
+
import { log } from '../../utils/logger.js';
|
|
4
|
+
import { colors } from '../../utils/constants.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* GitHub API 操作封裝
|
|
@@ -63,10 +63,46 @@ export class GitHubAPI {
|
|
|
63
63
|
*/
|
|
64
64
|
checkAuth() {
|
|
65
65
|
try {
|
|
66
|
-
execSync('gh api user --jq .login', {
|
|
67
|
-
|
|
66
|
+
const login = execSync('gh api user --jq .login', {
|
|
67
|
+
encoding: 'utf-8',
|
|
68
|
+
stdio: 'pipe',
|
|
69
|
+
}).trim();
|
|
70
|
+
return { authenticated: true, login };
|
|
68
71
|
} catch (error) {
|
|
69
|
-
return { authenticated: false };
|
|
72
|
+
return { authenticated: false, login: null };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 檢查目前認證使用者對此倉庫的權限
|
|
78
|
+
* @returns {{permission: string|null, canCreatePR: boolean, owner: string|null, repo: string|null}}
|
|
79
|
+
*/
|
|
80
|
+
checkRepositoryPermission() {
|
|
81
|
+
try {
|
|
82
|
+
const remoteUrl = execSync('git remote get-url origin', {
|
|
83
|
+
encoding: 'utf-8',
|
|
84
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
85
|
+
}).trim();
|
|
86
|
+
|
|
87
|
+
// 解析 owner/repo,支援 HTTPS 與 SSH
|
|
88
|
+
const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
89
|
+
if (!match) {
|
|
90
|
+
return { permission: null, canCreatePR: false, owner: null, repo: null };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const [, owner, repo] = match;
|
|
94
|
+
const viewerJson = execSync(
|
|
95
|
+
`gh repo view ${owner}/${repo} --json viewerPermission`,
|
|
96
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
97
|
+
);
|
|
98
|
+
const { viewerPermission } = JSON.parse(viewerJson);
|
|
99
|
+
|
|
100
|
+
// ADMIN / MAINTAIN / WRITE 通常可以建立 PR;READ / TRIAGE 不行
|
|
101
|
+
const canCreatePR = ['ADMIN', 'MAINTAIN', 'WRITE'].includes(viewerPermission);
|
|
102
|
+
|
|
103
|
+
return { permission: viewerPermission, canCreatePR, owner, repo };
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return { permission: null, canCreatePR: false, owner: null, repo: null };
|
|
70
106
|
}
|
|
71
107
|
}
|
|
72
108
|
|
|
@@ -271,6 +307,21 @@ export class GitHubAPI {
|
|
|
271
307
|
return prUrl;
|
|
272
308
|
} catch (error) {
|
|
273
309
|
this.safeUnlink(bodyFile);
|
|
310
|
+
|
|
311
|
+
// 偵測常見權限錯誤並給出清楚提示
|
|
312
|
+
const stderr = error.stderr?.toString?.() || error.message || '';
|
|
313
|
+
if (stderr.includes('must be a collaborator')) {
|
|
314
|
+
const auth = this.checkAuth();
|
|
315
|
+
const perm = this.checkRepositoryPermission();
|
|
316
|
+
throw new Error(
|
|
317
|
+
`建立 PR 失敗:帳號 ${auth.login || '目前使用者'} 不是 ${perm.owner}/${perm.repo} 的協作者(權限:${perm.permission})。\n` +
|
|
318
|
+
'解決方式:\n' +
|
|
319
|
+
` 1. 請 ${perm.owner} 到 Settings > Collaborators 將你加入(需要 Write 權限)\n` +
|
|
320
|
+
' 2. 或執行 gh auth login 切換到有權限的帳號\n' +
|
|
321
|
+
' 3. 若是從 fork 開發,請推送分支到自己的 fork 後再建立 PR'
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
274
325
|
throw error;
|
|
275
326
|
}
|
|
276
327
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createInterface } from 'readline';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
-
import { GitOperations } from '
|
|
3
|
+
import { GitOperations } from '../../core/git-operations.js';
|
|
4
4
|
import { GitHubAPI } from './github-api.js';
|
|
5
5
|
import { AIAnalyzer } from '../ai/code-analyzer.js';
|
|
6
6
|
import { LabelAnalyzer } from '../ai/label-analyzer.js';
|
|
7
7
|
import { ReviewerSelector } from '../reviewers/reviewer-selector.js';
|
|
8
|
-
import { Logger } from '
|
|
9
|
-
import { PRError
|
|
10
|
-
import { CONSTANTS, colors } from '
|
|
8
|
+
import { Logger, log } from '../../utils/logger.js';
|
|
9
|
+
import { PRError } from '../../utils/helpers.js';
|
|
10
|
+
import { CONSTANTS, colors } from '../../utils/constants.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* PR 工作流程編排
|
|
@@ -15,7 +15,7 @@ import { CONSTANTS, colors } from '../utils/constants.js';
|
|
|
15
15
|
export class PRWorkflow {
|
|
16
16
|
constructor(config) {
|
|
17
17
|
this.config = config;
|
|
18
|
-
this.git =
|
|
18
|
+
this.git = GitOperations;
|
|
19
19
|
this.github = new GitHubAPI(); // 自動從 git remote 偵測組織名稱
|
|
20
20
|
this.ai = new AIAnalyzer({ model: config.ai.model });
|
|
21
21
|
this.labelAnalyzer = new LabelAnalyzer();
|
|
@@ -40,6 +40,26 @@ export class PRWorkflow {
|
|
|
40
40
|
log.error('GitHub CLI 未登入,請先執行: gh auth login');
|
|
41
41
|
throw new Error('GitHub CLI 未登入');
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
// 0.1 確認對當前倉庫有建立 PR 的權限
|
|
45
|
+
const perm = this.github.checkRepositoryPermission();
|
|
46
|
+
if (!perm.canCreatePR) {
|
|
47
|
+
log.error(`目前帳號 ${auth.login} 對 ${perm.owner}/${perm.repo} 的權限為「${perm.permission}」,無法建立 Pull Request`);
|
|
48
|
+
log.info('解決方案:');
|
|
49
|
+
console.log(` 1. 請倉庫擁有者將 ${auth.login} 加入 Collaborator(需要 Write 以上權限)`);
|
|
50
|
+
console.log(' 2. 或切換到有權限的帳號:gh auth login');
|
|
51
|
+
console.log(' 3. 如果是 fork 的倉庫,請推送分支到自己的 fork 後再建立 PR');
|
|
52
|
+
console.log('');
|
|
53
|
+
throw new PRError(
|
|
54
|
+
`權限不足:${auth.login} 無法在 ${perm.owner}/${perm.repo} 建立 PR`,
|
|
55
|
+
'INSUFFICIENT_PERMISSION',
|
|
56
|
+
[
|
|
57
|
+
`請 ${perm.owner} 將 ${auth.login} 加入 Collaborator`,
|
|
58
|
+
'執行 gh auth login 切換到有權限的帳號',
|
|
59
|
+
'若是 fork,請推送到自己的 fork 再建立 PR',
|
|
60
|
+
]
|
|
61
|
+
);
|
|
62
|
+
}
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
// 0.5 提前預熱 AI client(異步非阻塞),讓 subprocess 在 git 操作期間並行啟動
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
-
import { CONSTANTS, colors } from '
|
|
3
|
-
import { log } from '
|
|
2
|
+
import { CONSTANTS, colors } from '../../utils/constants.js';
|
|
3
|
+
import { log } from '../../utils/logger.js';
|
|
4
4
|
import { InteractiveSelect } from '../ui/interactive-select.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 命令註冊輔助函式
|
|
3
|
+
* 統一處理命令註冊、錯誤處理與 process exit
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { handleError } from './helpers.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 註冊一個 CLI 命令,統一包裝 try/catch 與退出碼
|
|
10
|
+
* @param {import('commander').Command} program
|
|
11
|
+
* @param {string} name
|
|
12
|
+
* @param {string} description
|
|
13
|
+
* @param {Array<{flags: string, description: string, defaultValue?: any}>} options
|
|
14
|
+
* @param {Function} action
|
|
15
|
+
*/
|
|
16
|
+
export function registerCommand(program, name, description, options = [], action) {
|
|
17
|
+
const command = program.command(name).description(description);
|
|
18
|
+
|
|
19
|
+
for (const option of options) {
|
|
20
|
+
if (option.defaultValue !== undefined) {
|
|
21
|
+
command.option(option.flags, option.description, option.defaultValue);
|
|
22
|
+
} else {
|
|
23
|
+
command.option(option.flags, option.description);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
command.action(async (options) => {
|
|
28
|
+
try {
|
|
29
|
+
await action(options);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
handleError(error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 簡化版的命令 action 包裝器(用於已自行設定 option 的場景)
|
|
40
|
+
*/
|
|
41
|
+
export function wrapAction(action) {
|
|
42
|
+
return async (options) => {
|
|
43
|
+
try {
|
|
44
|
+
await action(options);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
handleError(error);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 常數定義
|
|
3
|
+
* 統一供所有命令與核心模組使用
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const CONSTANTS = {
|
|
7
|
+
// API 與緩衝區限制
|
|
8
|
+
MAX_DIFF_LENGTH: 8000,
|
|
9
|
+
MAX_BUFFER_SIZE: 10 * 1024 * 1024,
|
|
10
|
+
|
|
11
|
+
// 顯示限制
|
|
12
|
+
MAX_COMMIT_PREVIEW: 5,
|
|
13
|
+
MAX_FILES_IN_PROMPT: 50,
|
|
14
|
+
MAX_COMMITS_IN_PROMPT: 10,
|
|
15
|
+
DIFF_CONTEXT_LINES: 50,
|
|
16
|
+
|
|
17
|
+
// Reviewer 相關
|
|
18
|
+
MAX_SUGGESTED_REVIEWERS: 5,
|
|
19
|
+
AUTO_REVIEWERS_COUNT: 2,
|
|
20
|
+
GIT_HISTORY_COMMITS: 20,
|
|
21
|
+
|
|
22
|
+
// GitHub API 延遲
|
|
23
|
+
GITHUB_API_DELAY: 500,
|
|
24
|
+
GITHUB_SYNC_DELAY: 2000,
|
|
25
|
+
GITHUB_TEAM_SYNC_DELAY: 1000,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// 顏色輸出
|
|
29
|
+
export const colors = {
|
|
30
|
+
reset: '\x1b[0m',
|
|
31
|
+
bright: '\x1b[1m',
|
|
32
|
+
green: '\x1b[32m',
|
|
33
|
+
yellow: '\x1b[33m',
|
|
34
|
+
blue: '\x1b[34m',
|
|
35
|
+
red: '\x1b[31m',
|
|
36
|
+
cyan: '\x1b[36m',
|
|
37
|
+
magenta: '\x1b[35m',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ANSI 游標控制
|
|
41
|
+
export const cursor = {
|
|
42
|
+
hide: '\x1B[?25l',
|
|
43
|
+
show: '\x1B[?25h',
|
|
44
|
+
up: (n = 1) => `\x1B[${n}A`,
|
|
45
|
+
down: (n = 1) => `\x1B[${n}B`,
|
|
46
|
+
clearLine: '\x1B[2K\r',
|
|
47
|
+
saveCursor: '\x1B[s',
|
|
48
|
+
restoreCursor: '\x1B[u',
|
|
49
|
+
};
|
package/src/utils/helpers.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helper 工具函式
|
|
3
|
-
*
|
|
3
|
+
* 統一供所有命令使用
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { colors } from './constants.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 自訂錯誤類別(PR 流程使用)
|
|
10
|
+
*/
|
|
11
|
+
export class PRError extends Error {
|
|
12
|
+
constructor(message, code, suggestions = [], diagnosticCommand = null) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'PRError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.suggestions = suggestions;
|
|
17
|
+
this.diagnosticCommand = diagnosticCommand;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
/**
|
|
7
22
|
* 清理 commit message
|
|
8
23
|
* 移除 markdown 程式碼區塊標記
|
|
@@ -43,19 +58,75 @@ export function validateCommitMessage(message) {
|
|
|
43
58
|
return { valid: true };
|
|
44
59
|
}
|
|
45
60
|
|
|
61
|
+
/**
|
|
62
|
+
* 檢測 Copilot 授權錯誤(code-analyzer 使用別名)
|
|
63
|
+
*/
|
|
64
|
+
export const isCopilotAuthError = isCopilotSubscriptionError;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 檢測 Copilot 授權錯誤
|
|
68
|
+
*/
|
|
69
|
+
export function isCopilotSubscriptionError(error) {
|
|
70
|
+
if (!error) return false;
|
|
71
|
+
|
|
72
|
+
const message = (error.message || error.toString() || '').toLowerCase();
|
|
73
|
+
const originalError = (error.originalError?.message || '').toLowerCase();
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
error.isCopilotAuth === true ||
|
|
77
|
+
message.includes('permission') ||
|
|
78
|
+
message.includes('unauthorized') ||
|
|
79
|
+
message.includes('forbidden') ||
|
|
80
|
+
message.includes('not authorized') ||
|
|
81
|
+
originalError.includes('permission') ||
|
|
82
|
+
originalError.includes('unauthorized')
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
46
86
|
/**
|
|
47
87
|
* 錯誤處理
|
|
48
88
|
*/
|
|
49
89
|
export function handleError(error) {
|
|
90
|
+
// 先檢查是否為 Copilot 授權相關的錯誤
|
|
91
|
+
if (isCopilotSubscriptionError(error)) {
|
|
92
|
+
console.log('\n🔐 看起來是 GitHub Copilot 授權問題\n');
|
|
93
|
+
console.log('解決方案:');
|
|
94
|
+
console.log(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
|
|
95
|
+
console.log(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 訪問權限');
|
|
96
|
+
console.log(' 3. 嘗試重新登入:');
|
|
97
|
+
console.log(' gh auth logout');
|
|
98
|
+
console.log(' gh auth login');
|
|
99
|
+
console.log(' 4. 若是公司帳號,確保使用公司的 GitHub 帳號登入');
|
|
100
|
+
console.log('');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (error instanceof PRError) {
|
|
105
|
+
console.error(`\n❌ 錯誤: ${error.message}`);
|
|
106
|
+
|
|
107
|
+
if (error.suggestions && error.suggestions.length > 0) {
|
|
108
|
+
console.log(`\n${colors.cyan}💡 建議解決方案:${colors.reset}`);
|
|
109
|
+
error.suggestions.forEach((suggestion, index) => {
|
|
110
|
+
console.log(` ${index + 1}. ${suggestion}`);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (error.diagnosticCommand) {
|
|
115
|
+
console.log(`\n${colors.yellow}🔍 診斷命令:${colors.reset}`);
|
|
116
|
+
console.log(` ${error.diagnosticCommand}`);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
50
121
|
console.error('\n❌ 錯誤:', error.message);
|
|
51
|
-
|
|
122
|
+
|
|
52
123
|
if (error.suggestions && error.suggestions.length > 0) {
|
|
53
124
|
console.log('\n💡 建議解決方案:');
|
|
54
125
|
error.suggestions.forEach((suggestion, index) => {
|
|
55
126
|
console.log(` ${index + 1}. ${suggestion}`);
|
|
56
127
|
});
|
|
57
128
|
}
|
|
58
|
-
|
|
129
|
+
|
|
59
130
|
if (error.stack && process.env.VERBOSE) {
|
|
60
131
|
console.error('\n堆疊追蹤:');
|
|
61
132
|
console.error(error.stack);
|
package/src/utils/logger.js
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logger 工具
|
|
3
|
-
*
|
|
3
|
+
* 統一供所有命令使用
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
reset: '\x1b[0m',
|
|
8
|
-
bright: '\x1b[1m',
|
|
9
|
-
green: '\x1b[32m',
|
|
10
|
-
yellow: '\x1b[33m',
|
|
11
|
-
blue: '\x1b[34m',
|
|
12
|
-
red: '\x1b[31m',
|
|
13
|
-
cyan: '\x1b[36m',
|
|
14
|
-
magenta: '\x1b[35m',
|
|
15
|
-
};
|
|
6
|
+
import { colors } from './constants.js';
|
|
16
7
|
|
|
17
8
|
export class Logger {
|
|
18
9
|
info(msg) {
|
|
@@ -49,3 +40,14 @@ export class Logger {
|
|
|
49
40
|
console.log('═'.repeat(60));
|
|
50
41
|
}
|
|
51
42
|
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 簡易日誌輔助物件(用於不需要實例化的場景)
|
|
46
|
+
*/
|
|
47
|
+
export const log = {
|
|
48
|
+
info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
|
|
49
|
+
success: (msg) => console.log(`${colors.green}✅${colors.reset} ${msg}`),
|
|
50
|
+
warning: (msg) => console.log(`${colors.yellow}⚠️${colors.reset} ${msg}`),
|
|
51
|
+
error: (msg) => console.log(`${colors.red}❌${colors.reset} ${msg}`),
|
|
52
|
+
step: (msg) => console.log(`${colors.cyan}▶${colors.reset} ${msg}`),
|
|
53
|
+
};
|
|
@@ -1,53 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 專案規範知識(供 AI prompt 使用)
|
|
3
|
+
* 集中管理 skills 相關的 prompt 內容
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
|
-
export const CONSTANTS = {
|
|
6
|
-
// API 與緩衝區限制
|
|
7
|
-
MAX_DIFF_LENGTH: 8000,
|
|
8
|
-
MAX_BUFFER_SIZE: 10 * 1024 * 1024,
|
|
9
|
-
|
|
10
|
-
// 顯示限制
|
|
11
|
-
MAX_COMMIT_PREVIEW: 5,
|
|
12
|
-
MAX_FILES_IN_PROMPT: 50,
|
|
13
|
-
MAX_COMMITS_IN_PROMPT: 10,
|
|
14
|
-
DIFF_CONTEXT_LINES: 50,
|
|
15
|
-
|
|
16
|
-
// Reviewer 相關
|
|
17
|
-
MAX_SUGGESTED_REVIEWERS: 5,
|
|
18
|
-
AUTO_REVIEWERS_COUNT: 2,
|
|
19
|
-
GIT_HISTORY_COMMITS: 20,
|
|
20
|
-
|
|
21
|
-
// GitHub API 延遲
|
|
22
|
-
GITHUB_API_DELAY: 500,
|
|
23
|
-
GITHUB_SYNC_DELAY: 2000,
|
|
24
|
-
GITHUB_TEAM_SYNC_DELAY: 1000,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// 顏色輸出
|
|
28
|
-
export const colors = {
|
|
29
|
-
reset: '\x1b[0m',
|
|
30
|
-
bright: '\x1b[1m',
|
|
31
|
-
green: '\x1b[32m',
|
|
32
|
-
yellow: '\x1b[33m',
|
|
33
|
-
blue: '\x1b[34m',
|
|
34
|
-
red: '\x1b[31m',
|
|
35
|
-
cyan: '\x1b[36m',
|
|
36
|
-
magenta: '\x1b[35m',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// ANSI 游標控制
|
|
40
|
-
export const cursor = {
|
|
41
|
-
hide: '\x1B[?25l',
|
|
42
|
-
show: '\x1B[?25h',
|
|
43
|
-
up: (n = 1) => `\x1B[${n}A`,
|
|
44
|
-
down: (n = 1) => `\x1B[${n}B`,
|
|
45
|
-
clearLine: '\x1B[2K\r',
|
|
46
|
-
saveCursor: '\x1B[s',
|
|
47
|
-
restoreCursor: '\x1B[u',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// 專案規範知識(來自 .github/skills/)
|
|
51
6
|
export const PROJECT_SKILLS_CONTEXT = {
|
|
52
7
|
techStack: {
|
|
53
8
|
framework: 'Next.js 12+ (Pages Router)',
|
|
@@ -57,7 +12,6 @@ export const PROJECT_SKILLS_CONTEXT = {
|
|
|
57
12
|
serverState: 'SWR',
|
|
58
13
|
forms: 'React Hook Form + Zod',
|
|
59
14
|
},
|
|
60
|
-
// 來自 .github/skills/react-best-practices/SKILL.md
|
|
61
15
|
reactBestPractices: [
|
|
62
16
|
{ id: 'async-parallel', category: '消除 Waterfall', desc: '使用 Promise.all() 處理獨立操作' },
|
|
63
17
|
{ id: 'async-suspense-boundaries', category: '消除 Waterfall', desc: '使用 Suspense 串流內容' },
|
|
@@ -93,7 +47,6 @@ export const PROJECT_SKILLS_CONTEXT = {
|
|
|
93
47
|
{ id: 'js-early-exit', category: 'JS 效能', desc: '提早返回函式' },
|
|
94
48
|
{ id: 'js-set-map-lookups', category: 'JS 效能', desc: '使用 Set/Map 做 O(1) 查找' },
|
|
95
49
|
],
|
|
96
|
-
// 來自 .github/skills/frontend-guidelines/SKILL.md
|
|
97
50
|
frontendGuidelines: {
|
|
98
51
|
architecture: 'Modified Atomic Design(UI / Page / Feature 三層)',
|
|
99
52
|
naming: {
|
|
@@ -113,3 +66,27 @@ export const PROJECT_SKILLS_CONTEXT = {
|
|
|
113
66
|
},
|
|
114
67
|
},
|
|
115
68
|
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 取得 skills 規則的文字摘要(供 AI prompt 使用)
|
|
72
|
+
*/
|
|
73
|
+
export function getSkillsSummaryForPrompt(skillsContext = PROJECT_SKILLS_CONTEXT) {
|
|
74
|
+
const rbp = skillsContext.reactBestPractices
|
|
75
|
+
.map(r => ` - [${r.id}] ${r.category}: ${r.desc}`)
|
|
76
|
+
.join('\n');
|
|
77
|
+
const fg = skillsContext.frontendGuidelines;
|
|
78
|
+
return `
|
|
79
|
+
## 專案規範(來自 .github/skills/)
|
|
80
|
+
|
|
81
|
+
### React Best Practices 規則(共 ${skillsContext.reactBestPractices.length} 條核心規則)
|
|
82
|
+
${rbp}
|
|
83
|
+
|
|
84
|
+
### Frontend Guidelines 規範
|
|
85
|
+
- 架構: ${fg.architecture}
|
|
86
|
+
- 元件命名: ${fg.naming.component}
|
|
87
|
+
- 工具檔案: ${fg.naming.utility}
|
|
88
|
+
- 變數: ${fg.naming.variable} / 常數: ${fg.naming.constant} / 布林值: ${fg.naming.boolean}
|
|
89
|
+
- Import 順序: ${fg.importOrder}
|
|
90
|
+
- 狀態管理: 客戶端=${fg.stateManagement.client} | 伺服器=${fg.stateManagement.server} | 表單=${fg.stateManagement.form}
|
|
91
|
+
`;
|
|
92
|
+
}
|