ai-git-tools 2.0.79 → 2.0.81

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.
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Commit 命令
3
- * 基於 scripts/ai-auto-commit.mjs
4
3
  * 自動產生 commit message 並執行 commit
5
4
  */
6
5
 
@@ -12,146 +11,116 @@ import { Logger } from '../utils/logger.js';
12
11
  import {
13
12
  cleanCommitMessage,
14
13
  validateCommitMessage,
15
- handleError,
16
- getProjectTypePrompt,
17
14
  isCopilotSubscriptionError,
18
15
  } from '../utils/helpers.js';
16
+ import { generateCommitMessagePrompt } from '../ai/prompts/commit-message.js';
19
17
 
20
18
  export async function commitCommand() {
21
19
  const logger = new Logger();
22
20
 
23
- try {
24
- // 載入配置
25
- const config = await loadCommitConfig();
21
+ // 載入配置
22
+ const config = await loadCommitConfig();
26
23
 
27
- if (config.output.verbose) {
28
- console.log('📋 使用配置:');
29
- console.log(` AI Model: ${config.ai.model}`);
30
- console.log(` Max Diff Length: ${config.ai.maxDiffLength}`);
31
- console.log('');
32
- }
24
+ if (config.output.verbose) {
25
+ console.log('📋 使用配置:');
26
+ console.log(` AI Model: ${config.ai.model}`);
27
+ console.log(` Max Diff Length: ${config.ai.maxDiffLength}`);
28
+ console.log('');
29
+ }
33
30
 
34
- // 檢查是否有 staged 變更
35
- const diff = GitOperations.getStagedDiff();
31
+ // 檢查是否有 staged 變更
32
+ const diff = GitOperations.getStagedDiff();
36
33
 
37
- if (!diff.trim()) {
38
- logger.error('沒有 staged 的變更');
39
- console.log('💡 請先使用 git add 來 stage 你的變更');
40
- throw new Error('沒有 staged 的變更');
41
- }
34
+ if (!diff.trim()) {
35
+ logger.error('沒有 staged 的變更');
36
+ console.log('💡 請先使用 git add 來 stage 你的變更');
37
+ throw new Error('沒有 staged 的變更');
38
+ }
42
39
 
43
- logger.step('正在分析變更內容...\n');
40
+ logger.step('正在分析變更內容...\n');
44
41
 
45
- // 截斷過長的 diff
46
- const truncatedDiff =
47
- diff.length > config.ai.maxDiffLength
48
- ? diff.substring(0, config.ai.maxDiffLength) + '\n\n... [diff 過長已截斷]'
49
- : diff;
42
+ // 截斷過長的 diff
43
+ const truncatedDiff =
44
+ diff.length > config.ai.maxDiffLength
45
+ ? diff.substring(0, config.ai.maxDiffLength) + '\n\n... [diff 過長已截斷]'
46
+ : diff;
50
47
 
51
- if (config.output.verbose && diff.length > config.ai.maxDiffLength) {
52
- logger.warning(`Diff 已從 ${diff.length} 字元截斷至 ${config.ai.maxDiffLength} 字元\n`);
53
- }
48
+ if (config.output.verbose && diff.length > config.ai.maxDiffLength) {
49
+ logger.warning(`Diff 已從 ${diff.length} 字元截斷至 ${config.ai.maxDiffLength} 字元\n`);
50
+ }
51
+
52
+ // 使用 AI 產生 commit message
53
+ let commitMessage = '';
54
+ let lastError = null;
54
55
 
55
- // 使用 AI 產生 commit message
56
- let commitMessage = '';
57
- let lastError = null;
58
-
59
- for (let attempt = 1; attempt <= config.ai.maxRetries; attempt++) {
60
- try {
61
- if (config.output.verbose && attempt > 1) {
62
- console.log(`🔄 重試第 ${attempt}/${config.ai.maxRetries} 次...\n`);
63
- }
64
-
65
- const prompt = `${getProjectTypePrompt()}
66
-
67
- 請根據以下 git diff 產生一則 commit message。
68
-
69
- **Commit Message 規則**:
70
- 1. 使用 Conventional Commits 格式:type(scope): subject
71
- 2. type 必須是:feat/fix/docs/style/refactor/test/chore/perf 其中之一
72
- 3. scope: 影響範圍(如 member、report、auth、api、ui、config)
73
- 4. subject 限制在 50 字內,使用繁體中文
74
- 5. 如果變更複雜,加上 body 說明(使用 bullet points)
75
-
76
- **重要**:
77
- - 直接輸出 commit message 純文字,不要使用 markdown 程式碼區塊(\`\`\`)
78
- - 不要加上任何前綴說明或後綴文字
79
- - 第一行是標題,如有需要可加上空行後的詳細說明
80
-
81
- **範例格式**:
82
- feat(member): 新增會員管理頁面
83
-
84
- - 實作會員列表查詢功能
85
- - 新增會員資料編輯表單
86
- - 整合 Zustand 狀態管理
87
-
88
- git diff:
89
- ${truncatedDiff}`;
90
-
91
- const response = await AIClient.sendAndWait(prompt, config.ai.model);
92
- commitMessage = cleanCommitMessage(response);
93
-
94
- // 驗證 commit message
95
- const validation = validateCommitMessage(commitMessage);
96
- if (!validation.valid) {
97
- throw new Error(`無效的 commit message: ${validation.reason}`);
98
- }
99
-
100
- // 成功產生,跳出重試迴圈
101
- break;
102
- } catch (error) {
103
- lastError = error;
104
- if (config.output.verbose) {
105
- logger.warning(`嘗試 ${attempt} 失敗: ${error.message}\n`);
106
- }
107
-
108
- if (attempt < config.ai.maxRetries) {
109
- continue;
110
- }
56
+ for (let attempt = 1; attempt <= config.ai.maxRetries; attempt++) {
57
+ try {
58
+ if (config.output.verbose && attempt > 1) {
59
+ console.log(`🔄 重試第 ${attempt}/${config.ai.maxRetries} 次...\n`);
111
60
  }
112
- }
113
61
 
114
- // 所有重試都失敗
115
- if (!commitMessage) {
116
- logger.error('無法產生有效的 commit 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. 確認變更內容不會太複雜或太大');
62
+ const prompt = generateCommitMessagePrompt(truncatedDiff, config);
63
+
64
+ const response = await AIClient.sendAndWait(prompt, config.ai.model);
65
+ commitMessage = cleanCommitMessage(response);
66
+
67
+ // 驗證 commit message
68
+ const validation = validateCommitMessage(commitMessage);
69
+ if (!validation.valid) {
70
+ throw new Error(`無效的 commit message: ${validation.reason}`);
136
71
  }
137
72
 
138
- throw new Error('無法產生有效的 commit message');
73
+ // 成功產生,跳出重試迴圈
74
+ break;
75
+ } catch (error) {
76
+ lastError = error;
77
+ if (config.output.verbose) {
78
+ logger.warning(`嘗試 ${attempt} 失敗: ${error.message}\n`);
79
+ }
80
+
81
+ if (attempt < config.ai.maxRetries) {
82
+ continue;
83
+ }
84
+ }
85
+ }
86
+
87
+ // 所有重試都失敗
88
+ if (!commitMessage) {
89
+ logger.error('無法產生有效的 commit message');
90
+
91
+ // 區分不同類型的錯誤
92
+ if (isCopilotSubscriptionError(lastError)) {
93
+ console.log('\n🔑 看起來是 GitHub Copilot 授權問題\n');
94
+ console.log('解決方案:');
95
+ console.log(' 1. 確認你的 GitHub 帳號已訂閱 GitHub Copilot');
96
+ console.log(' 2. 驗證 VS Code 中使用的 GitHub 帳號是否有 Copilot 訪問權限');
97
+ console.log(' 3. 嘗試重新登入:');
98
+ console.log(' gh auth logout');
99
+ console.log(' gh auth login');
100
+ console.log(' 4. 若是公司帳號,確保使用公司的 GitHub 帳號登入');
101
+ } else {
102
+ if (config.output.verbose && lastError) {
103
+ console.log(` 最後錯誤: ${lastError.message}`);
104
+ }
105
+ console.log('\n💡 建議:');
106
+ console.log(' 1. 檢查網路連線');
107
+ console.log(' 2. 嘗試更換 AI 模型(使用 --model 參數)');
108
+ console.log(' 3. 確認變更內容不會太複雜或太大');
139
109
  }
140
110
 
141
- logger.success('產生的 Commit Message:');
142
- logger.separator('─', 60);
143
- console.log(commitMessage);
144
- logger.separator('─', 60);
145
-
146
- // 執行 commit
147
- logger.step('\n正在執行 commit...');
148
- execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
149
- stdio: 'inherit',
150
- });
151
-
152
- logger.success('Commit 完成!\n');
153
- } catch (error) {
154
- handleError(error);
155
- throw error;
111
+ throw new Error('無法產生有效的 commit message');
156
112
  }
113
+
114
+ logger.success('產生的 Commit Message:');
115
+ logger.separator('─', 60);
116
+ console.log(commitMessage);
117
+ logger.separator('─', 60);
118
+
119
+ // 執行 commit
120
+ logger.step('\n正在執行 commit...');
121
+ execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
122
+ stdio: 'inherit',
123
+ });
124
+
125
+ logger.success('Commit 完成!\n');
157
126
  }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * model-info 命令
3
+ * 顯示 GitHub Copilot SDK 可用模型資訊
4
+ */
5
+
6
+ import ora from 'ora';
7
+ import { Logger } from '../utils/logger.js';
8
+ import { fetchCopilotModels } from '../core/model-client.js';
9
+ import {
10
+ enrichModel,
11
+ getModelById,
12
+ filterModels,
13
+ formatPrice,
14
+ formatTokenCount,
15
+ } from '../data/copilot-models.js';
16
+
17
+ const logger = new Logger();
18
+
19
+ /**
20
+ * 格式化建議用途陣列為可讀字串
21
+ * @param {string[]} items
22
+ * @returns {string}
23
+ */
24
+ function formatRecommendedFor(items) {
25
+ if (!items || items.length === 0) return '—';
26
+ return items.join('、');
27
+ }
28
+
29
+ /**
30
+ * 格式化支援能力陣列
31
+ * @param {object} supports
32
+ * @returns {string}
33
+ */
34
+ function formatCapabilities(supports = {}) {
35
+ const caps = [];
36
+ if (supports.vision) caps.push('vision');
37
+ if (supports.tool_calls) caps.push('tools');
38
+ if (supports.reasoningEffort || supports.reasoning_effort) caps.push('reasoning');
39
+ if (supports.streaming) caps.push('streaming');
40
+ if (supports.parallel_tool_calls) caps.push('parallel-tools');
41
+ return caps.length > 0 ? caps.join('、') : '—';
42
+ }
43
+
44
+ /**
45
+ * 取得模型狀態圖示
46
+ * @param {string} state
47
+ * @returns {string}
48
+ */
49
+ function policyStateLabel(state) {
50
+ switch (state) {
51
+ case 'enabled':
52
+ return '啟用';
53
+ case 'disabled':
54
+ return '停用';
55
+ case 'unconfigured':
56
+ return '未配置';
57
+ default:
58
+ return state || '—';
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 印出單一模型詳細資訊
64
+ * @param {object} model
65
+ */
66
+ function printModel(model) {
67
+ const billing = model.billing?.token_prices;
68
+
69
+ console.log(`\n ${logger.colors?.cyan ?? ''}${model.id}${logger.colors?.reset ?? ''}`);
70
+ console.log(' ─────────────────────────────────────────────────────────────');
71
+ console.log(` 名稱: ${model.name}`);
72
+ console.log(` 供應商: ${model.provider}`);
73
+ console.log(` 描述: ${model.description}`);
74
+ console.log(` 上下文: ${model.contextWindow}`);
75
+ console.log(` 最大輸出: ${model.maxOutputTokens}`);
76
+ console.log(` 建議用途: ${formatRecommendedFor(model.recommendedFor)}`);
77
+ console.log(` 速度: ${model.speed}`);
78
+ console.log(` 狀態: ${policyStateLabel(model.policy?.state)}`);
79
+ console.log(` 能力: ${formatCapabilities(model.capabilities?.supports)}`);
80
+ if (billing && billing.batch_size > 0) {
81
+ const hasAnyPrice =
82
+ billing.input_price > 0 || billing.output_price > 0 || billing.cache_price > 0;
83
+ if (hasAnyPrice) {
84
+ console.log(` 價格(預估):`);
85
+ console.log(
86
+ ` 輸入: ${formatPrice(billing.input_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
87
+ );
88
+ console.log(
89
+ ` 輸出: ${formatPrice(billing.output_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
90
+ );
91
+ if (billing.cache_price !== undefined) {
92
+ console.log(
93
+ ` 快取: ${formatPrice(billing.cache_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
94
+ );
95
+ }
96
+ }
97
+ }
98
+ if (model.supportedReasoningEfforts && model.supportedReasoningEfforts.length > 0) {
99
+ console.log(
100
+ ` Reasoning: ${model.supportedReasoningEfforts.join('、')}(預設:${model.defaultReasoningEffort || '—'})`
101
+ );
102
+ }
103
+ if (model.notes) {
104
+ console.log(` 備註: ${model.notes}`);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * 印出模型列表(精簡版)
110
+ * @param {object[]} models
111
+ */
112
+ function printModelList(models) {
113
+ console.log();
114
+ for (const model of models) {
115
+ const idPart = `${logger.colors?.cyan ?? ''}${model.id.padEnd(18)}${logger.colors?.reset ?? ''}`;
116
+ console.log(` ${idPart} ${model.description}`);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * model-info 命令入口
122
+ * @param {object} options - commander 解析後的選項
123
+ */
124
+ export async function modelInfoCommand(options) {
125
+ const { json, filter, model: modelId, cache } = options;
126
+ const noCache = cache === false;
127
+
128
+ let sdkModels = [];
129
+ let usedCache = false;
130
+
131
+ const spinner = ora('正在連線到 Copilot 取得模型清單...').start();
132
+
133
+ try {
134
+ sdkModels = await fetchCopilotModels({ noCache });
135
+ spinner.stop();
136
+ } catch (error) {
137
+ spinner.stop();
138
+ if (error.cachedModels) {
139
+ sdkModels = error.cachedModels;
140
+ usedCache = true;
141
+ } else {
142
+ logger.error(`無法取得模型清單:${error.message}`);
143
+ logger.info('請確認已安裝 GitHub CLI 並完成 Copilot 授權(gh auth login)');
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ const models = sdkModels.map(enrichModel);
149
+
150
+ if (json) {
151
+ let output = models;
152
+ if (filter) output = filterModels(filter, output);
153
+ if (modelId) {
154
+ const model = getModelById(modelId, output);
155
+ output = model ? [model] : [];
156
+ }
157
+ console.log(JSON.stringify(output, null, 2));
158
+ return;
159
+ }
160
+
161
+ logger.section('🤖 Copilot 可用模型');
162
+
163
+ if (usedCache) {
164
+ logger.warning('即時取得失敗,顯示快取資料');
165
+ }
166
+
167
+ if (modelId) {
168
+ const model = getModelById(modelId, models);
169
+ if (!model) {
170
+ logger.error(`找不到模型「${modelId}」`);
171
+ logger.info(`可用模型:${models.map(m => m.id).join('、')}`);
172
+ process.exit(1);
173
+ }
174
+ printModel(model);
175
+ console.log();
176
+ return;
177
+ }
178
+
179
+ const filteredModels = filterModels(filter, models);
180
+ if (filteredModels.length === 0) {
181
+ logger.warning('沒有符合條件的模型');
182
+ return;
183
+ }
184
+
185
+ if (filter) {
186
+ printModelList(filteredModels);
187
+ } else {
188
+ for (const model of filteredModels) {
189
+ printModel(model);
190
+ }
191
+ }
192
+
193
+ console.log();
194
+ logger.info(`共 ${filteredModels.length} 個模型,使用 --model <id> 查看詳細資訊`);
195
+ }
@@ -1,16 +1,13 @@
1
1
  /**
2
- * PR 命令 - 完整複製自 scripts/ai-auto-pr.mjs
3
- *
4
- * 使用 pr-modules 的完整邏輯(從 scripts/ai-pr-modules 複製)
5
- * 確保功能與 scripts 版本完全相同
2
+ * PR 命令
3
+ * AI 自動生成 PR 並創建 Pull Request
6
4
  */
7
5
 
8
6
  import { execSync } from 'child_process';
9
7
  import { PRWorkflow } from '../pr-modules/core/workflow.js';
10
- import { loadConfig } from '../pr-modules/core/config-loader.js';
11
- import { handleError } from '../pr-modules/utils/helpers.js';
12
- import { Logger } from '../pr-modules/ui/logger.js';
13
- import { colors } from '../pr-modules/utils/constants.js';
8
+ import { loadConfig } from '../core/config-loader.js';
9
+ import { Logger } from '../utils/logger.js';
10
+ import { colors } from '../utils/constants.js';
14
11
 
15
12
  /**
16
13
  * 檢查 gh CLI 是否已登入且 token 有效,未登入則印出提示並回傳 false
@@ -34,37 +31,31 @@ function checkGHAuth(logger) {
34
31
  }
35
32
 
36
33
  /**
37
- * PR 命令主函數(完全照抄 scripts/ai-auto-pr.mjs)
34
+ * PR 命令主函數
38
35
  */
39
36
  export async function prCommand(options = {}) {
40
37
  const logger = new Logger();
41
38
 
42
- // ── 第一步:確認 gh CLI 已登入 ──────────────────────────
39
+ // 確認 gh CLI 已登入
43
40
  if (!checkGHAuth(logger)) return;
44
41
 
45
- try {
46
- logger.header('AI Auto PR Generator (v2.0 Enhanced)');
47
-
48
- // 載入配置(使用 scripts/ 的配置載入邏輯)
49
-
50
- const config = await loadConfig();
42
+ logger.header('AI Auto PR Generator (v2.0 Enhanced)');
51
43
 
52
- if (config.output.verbose) {
53
- console.log('📋 使用配置:');
54
- console.log(` AI Model: ${config.ai.model}`);
55
- console.log(` Max Diff Length: ${config.ai.maxDiffLength}`);
56
- }
44
+ // 載入配置
45
+ const config = await loadConfig();
57
46
 
58
- // 將命令行選項合併到配置中
59
- if (options.forceNew) {
60
- config.forceNew = true;
61
- }
47
+ if (config.output.verbose) {
48
+ console.log('📋 使用配置:');
49
+ console.log(` AI Model: ${config.ai.model}`);
50
+ console.log(` Max Diff Length: ${config.ai.maxDiffLength}`);
51
+ }
62
52
 
63
- // 執行工作流程(使用 scripts/ 的完整工作流)
64
- const workflow = new PRWorkflow(config);
65
- await workflow.execute();
66
- } catch (error) {
67
- handleError(error);
68
- throw error;
53
+ // 將命令行選項合併到配置中
54
+ if (options.forceNew) {
55
+ config.forceNew = true;
69
56
  }
57
+
58
+ // 執行工作流程
59
+ const workflow = new PRWorkflow(config);
60
+ await workflow.execute();
70
61
  }