ai-git-tools 2.0.48 → 2.0.50
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/bin/cli.js +0 -22
- package/package.json +2 -2
- package/src/commands/commit-all.js +0 -79
- package/src/commands/init.js +0 -38
- package/src/commands/pr.js +0 -63
- package/src/core/ai-client.js +15 -28
- package/src/core/config-loader.js +0 -53
- package/src/pr-modules/core/github-api.js +0 -84
- package/src/commands/autodev.js +0 -62
- package/src/dev-modules/ai/code-generator.js +0 -288
- package/src/dev-modules/core/autodev-workflow.js +0 -305
- package/src/dev-modules/core/issue-parser.js +0 -144
- package/src/dev-modules/test/executor-base.js +0 -74
- package/src/dev-modules/test/executor-factory.js +0 -29
- package/src/dev-modules/test/jest-executor.js +0 -107
- package/src/dev-modules/test/mocha-executor.js +0 -95
- package/src/dev-modules/test/result-formatter.js +0 -90
- package/src/dev-modules/test/test-detector.js +0 -170
- package/src/dev-modules/test/vitest-executor.js +0 -90
package/bin/cli.js
CHANGED
|
@@ -15,7 +15,6 @@ import { commitCommand } from '../src/commands/commit.js';
|
|
|
15
15
|
import { commitAllCommand } from '../src/commands/commit-all.js';
|
|
16
16
|
import { prCommand } from '../src/commands/pr.js';
|
|
17
17
|
import { initCommand } from '../src/commands/init.js';
|
|
18
|
-
import { autodevCommand } from '../src/commands/autodev.js';
|
|
19
18
|
|
|
20
19
|
// 讀取 package.json 獲取版本號
|
|
21
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -97,25 +96,4 @@ program
|
|
|
97
96
|
}
|
|
98
97
|
});
|
|
99
98
|
|
|
100
|
-
// AutoDev 命令
|
|
101
|
-
program
|
|
102
|
-
.command('autodev <issue>')
|
|
103
|
-
.description('全自動開發流程:Issue → 測試 → commit-all → PR')
|
|
104
|
-
.option('--dry-run', '乾運行:顯示計劃但不執行')
|
|
105
|
-
.option('-v, --verbose', '詳細輸出')
|
|
106
|
-
.option('--framework <name>', '強制指定測試框架(jest / vitest / mocha)')
|
|
107
|
-
.option('--skip-commit', '跳過自動 commit')
|
|
108
|
-
.option('--skip-pr', '跳過自動 PR 建立')
|
|
109
|
-
.option('--skip-all', '只執行測試並發佈評論')
|
|
110
|
-
.option('--commit-only', '執行測試 + commit,不建立 PR')
|
|
111
|
-
.option('--skip-tests', '無測試可執行時仍繼續 commit/PR(不強制停止)')
|
|
112
|
-
.action(async (issue, options) => {
|
|
113
|
-
try {
|
|
114
|
-
await autodevCommand(issue, options);
|
|
115
|
-
process.exit(0);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
99
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-git-tools",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.50",
|
|
4
4
|
"description": "AI-powered Git automation tools for commit messages and PR generation",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"CHANGELOG.md"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@github/copilot-sdk": "0.1.
|
|
42
|
+
"@github/copilot-sdk": "0.1.32",
|
|
43
43
|
"commander": "^12.0.0",
|
|
44
44
|
"chalk": "^5.3.0",
|
|
45
45
|
"ora": "^8.0.1",
|
|
@@ -459,82 +459,3 @@ export async function commitAllCommand() {
|
|
|
459
459
|
throw error;
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
|
-
|
|
463
|
-
// ─────────────────────────────────────────────────────────────
|
|
464
|
-
// 程序化 API(供 ai autodev 呼叫,不影響 CLI 行為)
|
|
465
|
-
// ─────────────────────────────────────────────────────────────
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* 程序化執行 commit-all 邏輯,供其他模組呼叫
|
|
469
|
-
* 不會呼叫 process.exit(),而是回傳結果物件
|
|
470
|
-
*
|
|
471
|
-
* @param {Object} [options]
|
|
472
|
-
* @param {boolean} [options.verbose]
|
|
473
|
-
* @returns {Promise<{ success: boolean, commitCount: number, message: string }>}
|
|
474
|
-
*/
|
|
475
|
-
export async function commitAllProgrammatic(options = {}) {
|
|
476
|
-
const logger = new Logger();
|
|
477
|
-
|
|
478
|
-
try {
|
|
479
|
-
const config = await loadCommitConfig();
|
|
480
|
-
if (options.verbose) {
|
|
481
|
-
config.output.verbose = true;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const changes = getAllChanges();
|
|
485
|
-
if (changes.length === 0) {
|
|
486
|
-
return { success: true, commitCount: 0, message: '沒有需要提交的變更' };
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const groups = await analyzeAndGroupChanges(changes, config);
|
|
490
|
-
if (!groups || groups.length === 0) {
|
|
491
|
-
return { success: false, commitCount: 0, message: 'AI 分析失敗,無法產生分組' };
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// 補入未分組的檔案
|
|
495
|
-
const groupedIndices = new Set(groups.flatMap((g) => g.file_indices));
|
|
496
|
-
const ungrouped = [...Array(changes.length).keys()].filter((i) => !groupedIndices.has(i));
|
|
497
|
-
if (ungrouped.length > 0) {
|
|
498
|
-
groups.push({
|
|
499
|
-
group_name: '其他變更',
|
|
500
|
-
commit_type: 'chore',
|
|
501
|
-
commit_scope: 'misc',
|
|
502
|
-
file_indices: ungrouped,
|
|
503
|
-
description: '未能自動分類的其他變更',
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
let successCount = 0;
|
|
508
|
-
for (let i = 0; i < groups.length; i++) {
|
|
509
|
-
const group = groups[i];
|
|
510
|
-
const groupFiles = group.file_indices.map((index) => changes[index]);
|
|
511
|
-
const ok = await commitGroup(group, groupFiles, config);
|
|
512
|
-
if (ok) successCount++;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// 清理 staged
|
|
516
|
-
try {
|
|
517
|
-
execSync('git reset HEAD -- .', { stdio: 'ignore' });
|
|
518
|
-
} catch (_) { /* 忽略 */ }
|
|
519
|
-
|
|
520
|
-
if (successCount === 0) {
|
|
521
|
-
return { success: false, commitCount: 0, message: '所有群組提交失敗' };
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// 取得最新 commit hash
|
|
525
|
-
let commitHash = '';
|
|
526
|
-
try {
|
|
527
|
-
commitHash = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
|
|
528
|
-
} catch (_) { /* 忽略 */ }
|
|
529
|
-
|
|
530
|
-
return {
|
|
531
|
-
success: true,
|
|
532
|
-
commitCount: successCount,
|
|
533
|
-
commitHash,
|
|
534
|
-
message: `成功提交 ${successCount}/${groups.length} 個群組`,
|
|
535
|
-
};
|
|
536
|
-
} catch (error) {
|
|
537
|
-
logger.error(`commitAllProgrammatic 失敗:${error.message}`);
|
|
538
|
-
return { success: false, commitCount: 0, message: error.message };
|
|
539
|
-
}
|
|
540
|
-
}
|
package/src/commands/init.js
CHANGED
|
@@ -40,44 +40,6 @@ export default {
|
|
|
40
40
|
output: {
|
|
41
41
|
verbose: true, // 詳細輸出
|
|
42
42
|
},
|
|
43
|
-
|
|
44
|
-
// AutoDev 自動開發流程配置
|
|
45
|
-
// 使用方式:ai autodev <issue編號>
|
|
46
|
-
autodev: {
|
|
47
|
-
// AI 模型(可獨立於 ai.model 單獨設定,建議使用快速模型)
|
|
48
|
-
// 支援:'gpt-4.1'、'gpt-4o'、'claude-haiku-4.5'、'claude-sonnet-4.5'
|
|
49
|
-
// 注意:claude 系列使用 extended thinking,回應較慢(約 5 分鐘)
|
|
50
|
-
aiModel: 'gpt-4.1',
|
|
51
|
-
|
|
52
|
-
// 重試次數上限(AI 呼叫失敗時)
|
|
53
|
-
maxRetries: 3,
|
|
54
|
-
|
|
55
|
-
// 測試框架(null = 自動偵測,或指定 'jest' / 'vitest' / 'mocha')
|
|
56
|
-
framework: null,
|
|
57
|
-
|
|
58
|
-
// 測試檔案搜尋路徑(glob pattern),留空使用框架預設規則
|
|
59
|
-
testPaths: [
|
|
60
|
-
'tests/**/*.test.js',
|
|
61
|
-
'tests/**/*.spec.js',
|
|
62
|
-
'src/**/__tests__/**/*.js',
|
|
63
|
-
],
|
|
64
|
-
|
|
65
|
-
// 測試執行 timeout(毫秒),預設 60 秒
|
|
66
|
-
testTimeout: 60000,
|
|
67
|
-
|
|
68
|
-
// 目標專案根目錄(null = 當前工作目錄)
|
|
69
|
-
projectRoot: null,
|
|
70
|
-
|
|
71
|
-
// 無測試可執行時,是否仍繼續 commit/PR(預設 false = 停止)
|
|
72
|
-
// 等同 CLI 的 --skip-tests 旗標
|
|
73
|
-
skipTests: false,
|
|
74
|
-
|
|
75
|
-
// 跳過自動 commit(預設 false)
|
|
76
|
-
skipCommit: false,
|
|
77
|
-
|
|
78
|
-
// 跳過自動建立 PR(預設 false)
|
|
79
|
-
skipPr: false,
|
|
80
|
-
},
|
|
81
43
|
};
|
|
82
44
|
`;
|
|
83
45
|
|
package/src/commands/pr.js
CHANGED
|
@@ -62,66 +62,3 @@ export async function prCommand() {
|
|
|
62
62
|
throw error;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
// ─────────────────────────────────────────────────────────────
|
|
67
|
-
// 程序化 API(供 ai autodev 呼叫,不影響 CLI 行為)
|
|
68
|
-
// ─────────────────────────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 程序化執行 PR 建立流程,供其他模組呼叫
|
|
72
|
-
* 不互動(noConfirm = true),直接建立 PR 並回傳結果
|
|
73
|
-
*
|
|
74
|
-
* @param {Object} [options]
|
|
75
|
-
* @param {string} [options.base] - 目標分支
|
|
76
|
-
* @param {string} [options.head] - 來源分支
|
|
77
|
-
* @param {boolean} [options.verbose]
|
|
78
|
-
* @param {Object} [options.testResults] - 注入測試結果到 PR 描述(保留供未來擴充)
|
|
79
|
-
* @returns {Promise<{ success: boolean, prUrl: string|null, prNumber: number|null, message: string }>}
|
|
80
|
-
*/
|
|
81
|
-
export async function prProgrammatic(options = {}) {
|
|
82
|
-
const logger = new Logger();
|
|
83
|
-
|
|
84
|
-
if (!checkGHAuth(logger)) {
|
|
85
|
-
return { success: false, prUrl: null, prNumber: null, message: 'GitHub CLI 未登入' };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const config = await loadConfig();
|
|
90
|
-
|
|
91
|
-
// 程序化呼叫:跳過互動確認
|
|
92
|
-
config.noConfirm = true;
|
|
93
|
-
if (options.base) config.baseBranch = options.base;
|
|
94
|
-
if (options.head) config.headBranch = options.head;
|
|
95
|
-
if (options.verbose) config.output.verbose = true;
|
|
96
|
-
|
|
97
|
-
const workflow = new PRWorkflow(config);
|
|
98
|
-
|
|
99
|
-
// 攔截 PRWorkflow 建立的 PR URL
|
|
100
|
-
let prUrl = null;
|
|
101
|
-
let prNumber = null;
|
|
102
|
-
const originalCreatePR = workflow.createPR?.bind(workflow);
|
|
103
|
-
if (originalCreatePR) {
|
|
104
|
-
workflow.createPR = async (...args) => {
|
|
105
|
-
const url = await originalCreatePR(...args);
|
|
106
|
-
prUrl = url;
|
|
107
|
-
if (url) {
|
|
108
|
-
const match = url.match(/\/pull\/(\d+)/);
|
|
109
|
-
if (match) prNumber = parseInt(match[1], 10);
|
|
110
|
-
}
|
|
111
|
-
return url;
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
await workflow.execute();
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
success: true,
|
|
119
|
-
prUrl,
|
|
120
|
-
prNumber,
|
|
121
|
-
message: prUrl ? `PR 已建立:${prUrl}` : 'PR 執行完成',
|
|
122
|
-
};
|
|
123
|
-
} catch (error) {
|
|
124
|
-
logger.error(`prProgrammatic 失敗:${error.message}`);
|
|
125
|
-
return { success: false, prUrl: null, prNumber: null, message: error.message };
|
|
126
|
-
}
|
|
127
|
-
}
|
package/src/core/ai-client.js
CHANGED
|
@@ -7,52 +7,39 @@ import { CopilotClient } from '@github/copilot-sdk';
|
|
|
7
7
|
|
|
8
8
|
export class AIClient {
|
|
9
9
|
/**
|
|
10
|
-
* 發送 prompt
|
|
11
|
-
*
|
|
12
|
-
* @param {string} prompt
|
|
13
|
-
* @param {string} model
|
|
14
|
-
* @param {number} maxRetries
|
|
15
|
-
* @param {number} timeout - 毫秒,會直接傳給 SDK 的 sendAndWait
|
|
10
|
+
* 發送 prompt 並等待回應(帶重試機制和超時保護)
|
|
16
11
|
*/
|
|
17
|
-
static async sendAndWait(prompt, model = '
|
|
12
|
+
static async sendAndWait(prompt, model = 'gpt-4.1', maxRetries = 3, timeout = 60000) {
|
|
18
13
|
let lastError = null;
|
|
19
14
|
|
|
20
15
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
21
16
|
const client = new CopilotClient();
|
|
22
|
-
let session = null;
|
|
23
17
|
try {
|
|
24
|
-
session = await client.createSession({ model });
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
session.
|
|
28
|
-
|
|
18
|
+
const session = await client.createSession({ model });
|
|
19
|
+
|
|
20
|
+
// 使用 Promise.race 實現超時控制
|
|
21
|
+
const responsePromise = session.sendAndWait({ prompt });
|
|
22
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
23
|
+
setTimeout(() => reject(new Error(`AI 請求超時 (${timeout}ms)`)), timeout);
|
|
29
24
|
});
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
const response = await session.sendAndWait({ prompt }, timeout);
|
|
33
|
-
|
|
34
|
-
// response 為 undefined 表示 session 結束但沒有助理回訊(通常是錯誤)
|
|
35
|
-
if (!response) {
|
|
36
|
-
throw new Error(`AI 未回傳任何訊息(model: ${model})`);
|
|
37
|
-
}
|
|
26
|
+
const response = await Promise.race([responsePromise, timeoutPromise]);
|
|
38
27
|
|
|
39
28
|
const content = response?.data?.content || '';
|
|
40
|
-
if (!content) {
|
|
41
|
-
throw new Error('AI 回傳空內容');
|
|
42
|
-
}
|
|
43
29
|
return content.trim();
|
|
44
|
-
|
|
45
30
|
} catch (error) {
|
|
46
31
|
lastError = error;
|
|
47
32
|
if (attempt < maxRetries) {
|
|
48
|
-
console.log(`⚠️ AI 請求失敗,重試第 ${attempt}/${maxRetries}
|
|
33
|
+
console.log(`⚠️ AI 請求失敗,重試第 ${attempt}/${maxRetries} 次...`);
|
|
49
34
|
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
|
|
50
35
|
}
|
|
51
36
|
} finally {
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
// 確保每次都關閉 client,無論成功或失敗
|
|
38
|
+
try {
|
|
39
|
+
await client.stop();
|
|
40
|
+
} catch (e) {
|
|
41
|
+
// 忽略關閉錯誤
|
|
54
42
|
}
|
|
55
|
-
try { await client.stop(); } catch (_e) { /* 忽略 */ }
|
|
56
43
|
}
|
|
57
44
|
}
|
|
58
45
|
|
|
@@ -179,56 +179,3 @@ export async function loadPRConfig() {
|
|
|
179
179
|
|
|
180
180
|
return config;
|
|
181
181
|
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 載入 AutoDev 配置
|
|
185
|
-
* 讀取 .ai-git-config.js(或 .mjs)的 autodev 區塊
|
|
186
|
-
* 所有欄位皆選填,未設定時使用合理預設值
|
|
187
|
-
*
|
|
188
|
-
* @returns {Promise<Object>}
|
|
189
|
-
*/
|
|
190
|
-
export async function loadAutodevConfig() {
|
|
191
|
-
const defaults = {
|
|
192
|
-
framework: null, // null = 自動偵測
|
|
193
|
-
testPaths: [], // 空 = 自動發現
|
|
194
|
-
testTimeout: 60_000, // 1 分鐘
|
|
195
|
-
skipCommit: false,
|
|
196
|
-
skipPr: false,
|
|
197
|
-
skipTests: false,
|
|
198
|
-
verbose: false,
|
|
199
|
-
projectRoot: process.cwd(),
|
|
200
|
-
// AI 設定(可被 autodev 子區塊或全域 ai 區塊覆蓋)
|
|
201
|
-
aiModel: 'claude-sonnet-4.5',
|
|
202
|
-
maxRetries: 3,
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
// 支援 .ai-git-config.js 和 .ai-git-config.mjs
|
|
206
|
-
const candidates = [
|
|
207
|
-
resolve(process.cwd(), '.ai-git-config.js'),
|
|
208
|
-
resolve(process.cwd(), '.ai-git-config.mjs'),
|
|
209
|
-
];
|
|
210
|
-
|
|
211
|
-
let userAutodev = {};
|
|
212
|
-
let userAi = {};
|
|
213
|
-
for (const configPath of candidates) {
|
|
214
|
-
if (existsSync(configPath)) {
|
|
215
|
-
try {
|
|
216
|
-
const imported = await import(`file://${configPath}`);
|
|
217
|
-
const root = imported.default || {};
|
|
218
|
-
userAutodev = root.autodev || {};
|
|
219
|
-
userAi = root.ai || {}; // 讀取全域 ai 區塊
|
|
220
|
-
break;
|
|
221
|
-
} catch (error) {
|
|
222
|
-
console.warn(`⚠️ 載入 autodev 配置失敗: ${error.message}`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
...defaults,
|
|
229
|
-
...userAutodev,
|
|
230
|
-
// 這兩個需要跨區塊計算,放在展開之後才能保有正確優先序
|
|
231
|
-
aiModel: userAutodev.aiModel ?? userAi.model ?? defaults.aiModel,
|
|
232
|
-
maxRetries: userAutodev.maxRetries ?? userAi.maxRetries ?? defaults.maxRetries,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
@@ -394,88 +394,4 @@ export class GitHubAPI {
|
|
|
394
394
|
log.error('無法添加團隊 reviewers,請手動操作');
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
|
-
|
|
398
|
-
// ─────────────────────────────────────────────────────────────
|
|
399
|
-
// Issue 評論功能(供 ai autodev 使用)
|
|
400
|
-
// ─────────────────────────────────────────────────────────────
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* 在 Issue 上發佈評論
|
|
404
|
-
* @param {string} owner
|
|
405
|
-
* @param {string} repo
|
|
406
|
-
* @param {number} issueNumber
|
|
407
|
-
* @param {string} body - Markdown 內容
|
|
408
|
-
* @returns {{ id: number, url: string }}
|
|
409
|
-
*/
|
|
410
|
-
postCommentOnIssue(owner, repo, issueNumber, body) {
|
|
411
|
-
const tmpFile = `/tmp/ai-autodev-comment-${Date.now()}.json`;
|
|
412
|
-
try {
|
|
413
|
-
const payload = JSON.stringify({ body });
|
|
414
|
-
writeFileSync(tmpFile, payload, 'utf-8');
|
|
415
|
-
const result = execSync(
|
|
416
|
-
`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --input "${tmpFile}"`,
|
|
417
|
-
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
418
|
-
);
|
|
419
|
-
const data = JSON.parse(result);
|
|
420
|
-
return { id: data.id, url: data.html_url };
|
|
421
|
-
} finally {
|
|
422
|
-
this.safeUnlink(tmpFile);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* 更新已存在的 Issue 評論
|
|
428
|
-
* @param {string} owner
|
|
429
|
-
* @param {string} repo
|
|
430
|
-
* @param {number} commentId
|
|
431
|
-
* @param {string} body
|
|
432
|
-
* @returns {{ id: number, url: string }}
|
|
433
|
-
*/
|
|
434
|
-
editIssueComment(owner, repo, commentId, body) {
|
|
435
|
-
const tmpFile = `/tmp/ai-autodev-edit-${Date.now()}.json`;
|
|
436
|
-
try {
|
|
437
|
-
const payload = JSON.stringify({ body });
|
|
438
|
-
writeFileSync(tmpFile, payload, 'utf-8');
|
|
439
|
-
const result = execSync(
|
|
440
|
-
`gh api repos/${owner}/${repo}/issues/comments/${commentId} --input "${tmpFile}" -X PATCH`,
|
|
441
|
-
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
442
|
-
);
|
|
443
|
-
const data = JSON.parse(result);
|
|
444
|
-
return { id: data.id, url: data.html_url };
|
|
445
|
-
} finally {
|
|
446
|
-
this.safeUnlink(tmpFile);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* 尋找 Issue 上含有特定 marker 的評論(用於重複執行時更新而非重建)
|
|
452
|
-
* @param {string} owner
|
|
453
|
-
* @param {string} repo
|
|
454
|
-
* @param {number} issueNumber
|
|
455
|
-
* @param {string} marker - 識別字串
|
|
456
|
-
* @returns {{ id: number }|null}
|
|
457
|
-
*/
|
|
458
|
-
findCommentWithMarker(owner, repo, issueNumber, marker) {
|
|
459
|
-
try {
|
|
460
|
-
const result = execSync(
|
|
461
|
-
`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --jq '.[] | {id, body}'`,
|
|
462
|
-
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
463
|
-
);
|
|
464
|
-
// 每行一個 JSON 物件
|
|
465
|
-
const lines = result.trim().split('\n').filter(Boolean);
|
|
466
|
-
for (const line of lines) {
|
|
467
|
-
try {
|
|
468
|
-
const obj = JSON.parse(line);
|
|
469
|
-
if (obj.body && obj.body.includes(marker)) {
|
|
470
|
-
return { id: obj.id };
|
|
471
|
-
}
|
|
472
|
-
} catch (_) {
|
|
473
|
-
// 忽略解析錯誤
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return null;
|
|
477
|
-
} catch (_) {
|
|
478
|
-
return null;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
397
|
}
|
package/src/commands/autodev.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AutoDev 命令
|
|
3
|
-
* 完整自動化開發流程:Issue 解析 → 測試 → 發佈評論 → commit-all → PR
|
|
4
|
-
*
|
|
5
|
-
* 使用方式:
|
|
6
|
-
* ai autodev <issue>
|
|
7
|
-
* ai autodev 123
|
|
8
|
-
* ai autodev https://github.com/org/repo/issues/123
|
|
9
|
-
* ai autodev 123 --dry-run
|
|
10
|
-
* ai autodev 123 --skip-pr
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { AutodevWorkflow } from '../dev-modules/core/autodev-workflow.js';
|
|
14
|
-
import { loadAutodevConfig } from '../core/config-loader.js';
|
|
15
|
-
import { handleError } from '../utils/helpers.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* AutoDev 命令主函數
|
|
19
|
-
* @param {string|number} issueInput - Issue 編號或 URL
|
|
20
|
-
* @param {Object} cliOptions - 來自 commander 的選項
|
|
21
|
-
*/
|
|
22
|
-
export async function autodevCommand(issueInput, cliOptions = {}) {
|
|
23
|
-
if (!issueInput) {
|
|
24
|
-
console.error('❌ 請提供 Issue 編號或 URL');
|
|
25
|
-
console.error(' 用法:ai autodev <issue>');
|
|
26
|
-
console.error(' 範例:ai autodev 123');
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
// 載入設定
|
|
32
|
-
const config = await loadAutodevConfig();
|
|
33
|
-
|
|
34
|
-
// 合併 CLI 選項(CLI 優先)
|
|
35
|
-
const options = {
|
|
36
|
-
dryRun: cliOptions.dryRun ?? false,
|
|
37
|
-
verbose: cliOptions.verbose ?? config.verbose ?? false,
|
|
38
|
-
framework: cliOptions.framework ?? config.framework ?? null,
|
|
39
|
-
skipCommit: cliOptions.skipCommit ?? config.skipCommit ?? false,
|
|
40
|
-
skipPr: cliOptions.skipPr ?? config.skipPr ?? false,
|
|
41
|
-
skipAll: cliOptions.skipAll ?? false,
|
|
42
|
-
commitOnly: cliOptions.commitOnly ?? false,
|
|
43
|
-
skipTests: cliOptions.skipTests ?? config.skipTests ?? false,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// 永遠顯示啟動設定,方便確認
|
|
47
|
-
console.log('📋 autodev 啟動設定:');
|
|
48
|
-
console.log(` AI 模型 : ${config.aiModel}`);
|
|
49
|
-
console.log(` 重試次數 : ${config.maxRetries}`);
|
|
50
|
-
console.log(` 測試框架 : ${options.framework ?? '自動偵測'}`);
|
|
51
|
-
console.log(` Commit : ${options.skipAll || options.skipCommit ? '跳過' : '自動'}`);
|
|
52
|
-
console.log(` PR : ${options.skipAll || options.skipPr || options.commitOnly ? '跳過' : '自動'}`);
|
|
53
|
-
console.log(` 跳過測試 : ${options.skipTests ? '是(--skip-tests)' : '否(0 個測試時停止)'}`);
|
|
54
|
-
if (options.dryRun) console.log(' ⚠️ 乾運行模式(不實際執行)');
|
|
55
|
-
|
|
56
|
-
const workflow = new AutodevWorkflow(config);
|
|
57
|
-
await workflow.execute(issueInput, options);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
handleError(error);
|
|
60
|
-
throw error;
|
|
61
|
-
}
|
|
62
|
-
}
|