ai-git-tools 2.0.69 → 2.0.70

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 CHANGED
@@ -2,11 +2,14 @@
2
2
 
3
3
  /**
4
4
  * AI Git Tools CLI
5
- *
5
+ *
6
6
  * AI-powered Git automation for commit messages and PR generation
7
7
  * 完全重寫版本基於 scripts/ 原始實現
8
8
  */
9
9
 
10
+ // 抑制 @github/copilot-sdk 子程序的 node:sqlite 實驗性警告
11
+ process.env.NODE_NO_WARNINGS = '1';
12
+
10
13
  import { Command } from 'commander';
11
14
  import { readFileSync } from 'fs';
12
15
  import { fileURLToPath } from 'url';
@@ -15,14 +18,15 @@ import { commitCommand } from '../src/commands/commit.js';
15
18
  import { commitAllCommand } from '../src/commands/commit-all.js';
16
19
  import { prCommand } from '../src/commands/pr.js';
17
20
  import { initCommand } from '../src/commands/init.js';
18
- import { devFromIssueCommand } from '../src/commands/dev-from-issue.js';
21
+ // import { planIssueCommand } from '../src/commands/plan-issue.js';
22
+ // import { generateCodeCommand } from '../src/commands/generate-code.js';
23
+ import { writeAndTestCommand } from '../src/commands/write-and-test.js';
24
+ import { autoDevCommand } from '../src/commands/auto-dev.js';
19
25
 
20
26
  // 讀取 package.json 獲取版本號
21
27
  const __filename = fileURLToPath(import.meta.url);
22
28
  const __dirname = dirname(__filename);
23
- const packageJson = JSON.parse(
24
- readFileSync(join(__dirname, '../package.json'), 'utf-8')
25
- );
29
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
26
30
 
27
31
  const program = new Command();
28
32
 
@@ -35,7 +39,7 @@ program
35
39
  program
36
40
  .command('init')
37
41
  .description('初始化配置檔案 (.ai-git-config.mjs)')
38
- .action(async (options) => {
42
+ .action(async options => {
39
43
  try {
40
44
  await initCommand(options);
41
45
  process.exit(0);
@@ -52,7 +56,7 @@ program
52
56
  .option('-v, --verbose', '顯示詳細輸出')
53
57
  .option('--max-diff <number>', '最大 diff 長度')
54
58
  .option('--max-retries <number>', '最大重試次數')
55
- .action(async (options) => {
59
+ .action(async options => {
56
60
  try {
57
61
  await commitCommand(options);
58
62
  process.exit(0);
@@ -69,7 +73,7 @@ program
69
73
  .option('-v, --verbose', '顯示詳細輸出')
70
74
  .option('--max-diff <number>', '最大 diff 長度')
71
75
  .option('--max-retries <number>', '最大重試次數')
72
- .action(async (options) => {
76
+ .action(async options => {
73
77
  try {
74
78
  await commitAllCommand(options);
75
79
  process.exit(0);
@@ -88,9 +92,8 @@ program
88
92
  .option('--no-confirm', '跳過確認直接創建')
89
93
  .option('--auto-labels', '自動添加 Labels (預設啟用)')
90
94
  .option('--include-impact', '在 PR 中包含影響範圍分析和注意事項 (預設關閉)')
91
- .option('--auto-review', 'PR 建立後發佈 AI 審查 comment')
92
95
  .option('--force-new', '強制創建新 PR,不更新現有 PR')
93
- .action(async (options) => {
96
+ .action(async options => {
94
97
  try {
95
98
  await prCommand(options);
96
99
  process.exit(0);
@@ -99,22 +102,23 @@ program
99
102
  }
100
103
  });
101
104
 
102
- // Plan Issue 命令(已废棄,功能已併入 dev-from-issue)
103
- // Dev From Issue 命令(plan-issue + generate-code 合體)
105
+ // Auto Dev 整合命令
104
106
  program
105
- .command('dev-from-issue')
106
- .description('AI 讀取 Issue → 生成計畫 → 生成代碼')
107
+ .command('auto-dev')
108
+ .description('一鍵自動化:從 GitHub Issue 到代碼生成、測試與提交')
107
109
  .requiredOption('--issue <number>', 'GitHub Issue 編號')
108
110
  .option('--file <path>', '目標檔案路徑(若無則自動推斷)')
109
111
  .option('--context <description>', '額外說明或補充需求')
112
+ .option('--test-type <type>', '測試類型:auto、unit 或 component(預設 auto)', 'auto')
110
113
  .option('--max-lines <number>', '最大行數限制(預設 500)', '500')
114
+ .option('--max-fixes <number>', '最大自動修復次數(預設 2)', '2')
115
+ .option('--no-confirm', '全自動模式,跳過所有確認')
111
116
  .option('--model <model>', '指定 AI 模型')
112
- .action(async (options) => {
117
+ .action(async options => {
113
118
  try {
114
- await devFromIssueCommand(options);
119
+ await autoDevCommand(options);
115
120
  process.exit(0);
116
121
  } catch (error) {
117
- console.error(`\n[錯誤] ${error.message}`);
118
122
  process.exit(1);
119
123
  }
120
124
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-git-tools",
3
- "version": "2.0.69",
3
+ "version": "2.0.70",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -26,7 +26,6 @@ export default {
26
26
  defaultBase: 'release', // PR 預設目標分支(使用 'release' 自動偵測最新 release 分支,如 release-2025-m11.1)
27
27
  autoLabels: true, // 自動新增 Labels
28
28
  includeImpactAnalysis: false, // 是否在 PR 中包含影響範圍分析和注意事項(使用 --include-impact 啟用)
29
- autoReview: true, // PR 建立後自動發佈 AI 審查 comment
30
29
  },
31
30
 
32
31
  // Reviewers 相關配置
@@ -9,7 +9,7 @@ export class AIClient {
9
9
  /**
10
10
  * 發送 prompt 並等待回應(帶重試機制和超時保護)
11
11
  */
12
- static async sendAndWait(prompt, model = 'gpt-4.1', maxRetries = 3, timeout = 60000) {
12
+ static async sendAndWait(prompt, model = 'gpt-4.1', maxRetries = 3, timeout = 120000) {
13
13
  let lastError = null;
14
14
 
15
15
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -2,24 +2,51 @@ import { CopilotClient, approveAll } from '@github/copilot-sdk';
2
2
  import { CONSTANTS, PROJECT_SKILLS_CONTEXT } from '../utils/constants.js';
3
3
  import { getSkillsSummaryForPrompt, log } from '../utils/helpers.js';
4
4
 
5
+ const AI_TIMEOUT_MS = 120000; // 2 分鐘:子程序啟動 + AI 回應時間
6
+
5
7
  /**
6
8
  * AI 分析器 - 負責程式碼分析和 PR 內容生成
7
9
  */
8
10
  export class AIAnalyzer {
9
11
  constructor(config = {}) {
10
12
  this.model = config.model || 'gpt-4.1';
13
+ this._client = null; // 複用同一個 CopilotClient,避免重複啟動子程序
14
+ }
15
+
16
+ /**
17
+ * 取得(或建立)共用的 CopilotClient
18
+ */
19
+ async _getOrCreateClient() {
20
+ if (!this._client) {
21
+ this._client = new CopilotClient();
22
+ }
23
+ return this._client;
11
24
  }
12
25
 
13
26
  /**
14
- * 建立 AI 客戶端
27
+ * 建立新的 AI Session(複用已有的 client)
15
28
  */
16
- async createClient() {
17
- const client = new CopilotClient();
18
- const session = await client.createSession({
29
+ async _createSession() {
30
+ const client = await this._getOrCreateClient();
31
+ return client.createSession({
19
32
  model: this.model,
20
- onPermissionRequest: approveAll
33
+ onPermissionRequest: approveAll,
21
34
  });
22
- return { client, session };
35
+ }
36
+
37
+ /**
38
+ * 釋放 CopilotClient 子程序資源
39
+ */
40
+ async close() {
41
+ if (this._client) {
42
+ try {
43
+ await this._client.stop();
44
+ } catch (e) {
45
+ // 忽略關閉錯誤
46
+ } finally {
47
+ this._client = null;
48
+ }
49
+ }
23
50
  }
24
51
 
25
52
  /**
@@ -29,13 +56,13 @@ export class AIAnalyzer {
29
56
  const skillsSummary = getSkillsSummaryForPrompt(PROJECT_SKILLS_CONTEXT);
30
57
  const prompt = this.buildPRPrompt(commits, diff, skillsSummary);
31
58
 
32
- const { client, session } = await this.createClient();
59
+ const session = await this._createSession();
33
60
 
34
61
  try {
35
- // 使用超時保護 (60 秒)
62
+ // 使用超時保護(2 分鐘:包含子程序啟動時間)
36
63
  const responsePromise = session.sendAndWait({ prompt });
37
64
  const timeoutPromise = new Promise((_, reject) => {
38
- setTimeout(() => reject(new Error('AI 請求超時 (60 秒)')), 60000);
65
+ setTimeout(() => reject(new Error(`AI 請求超時 (${AI_TIMEOUT_MS / 1000} 秒)`)), AI_TIMEOUT_MS);
39
66
  });
40
67
 
41
68
  const response = await Promise.race([responsePromise, timeoutPromise]);
@@ -46,14 +73,8 @@ export class AIAnalyzer {
46
73
  }
47
74
 
48
75
  return this.parsePRContent(prContent);
49
- } finally {
50
- // 確保 client 一定會被關閉
51
- try {
52
- await client.stop();
53
- } catch (e) {
54
- // 忽略關閉錯誤
55
- }
56
76
  }
77
+ // 注意:不在此處 client.stop(),交由 close() 統一清理以便複用
57
78
  }
58
79
 
59
80
  /**
@@ -63,15 +84,15 @@ export class AIAnalyzer {
63
84
  const skillsSummary = getSkillsSummaryForPrompt(PROJECT_SKILLS_CONTEXT);
64
85
  const prompt = this.buildAnalysisPrompt(changedFiles, diff, commits, skillsSummary);
65
86
 
66
- const { client, session } = await this.createClient();
87
+ const session = await this._createSession();
67
88
 
68
89
  try {
69
90
  log.info(' 正在使用 AI 深度分析程式碼變更...');
70
-
71
- // 使用超時保護 (60 秒)
91
+
92
+ // 使用超時保護(2 分鐘:包含子程序啟動時間)
72
93
  const responsePromise = session.sendAndWait({ prompt });
73
94
  const timeoutPromise = new Promise((_, reject) => {
74
- setTimeout(() => reject(new Error('AI 請求超時 (60 秒)')), 60000);
95
+ setTimeout(() => reject(new Error(`AI 請求超時 (${AI_TIMEOUT_MS / 1000} 秒)`)), AI_TIMEOUT_MS);
75
96
  });
76
97
 
77
98
  const response = await Promise.race([responsePromise, timeoutPromise]);
@@ -103,14 +124,8 @@ export class AIAnalyzer {
103
124
  } catch (error) {
104
125
  log.warning(` AI 分析失敗 (${error.message}),使用基礎分析...\n`);
105
126
  return this.getFallbackAnalysis(changedFiles);
106
- } finally {
107
- // 確保 client 一定會被關閉
108
- try {
109
- await client.stop();
110
- } catch (e) {
111
- // 忽略關閉錯誤
112
- }
113
127
  }
128
+ // 注意:不在此處 client.stop(),交由 close() 統一清理以便複用
114
129
  }
115
130
 
116
131
  /**
@@ -15,7 +15,6 @@ export function parseCliArgs() {
15
15
  interactiveReviewers: undefined,
16
16
  autoLabels: null,
17
17
  includeImpactAnalysis: null,
18
- autoReview: null,
19
18
  };
20
19
 
21
20
  for (let i = 0; i < args.length; i++) {
@@ -44,9 +43,6 @@ export function parseCliArgs() {
44
43
  case '--include-impact':
45
44
  config.includeImpactAnalysis = true;
46
45
  break;
47
- case '--auto-review':
48
- config.autoReview = true;
49
- break;
50
46
  case '--help':
51
47
  showHelp();
52
48
  process.exit(0);
@@ -75,7 +71,7 @@ export async function loadConfig() {
75
71
  // 使用內建預設值
76
72
  config = {
77
73
  ai: { model: 'gpt-4.1', maxDiffLength: 8000, maxRetries: 3 },
78
- github: { defaultBase: 'release', autoLabels: true, includeImpactAnalysis: false, autoReview: false },
74
+ github: { defaultBase: 'release', autoLabels: true, includeImpactAnalysis: false },
79
75
  reviewers: { interactiveReviewers: true, maxSuggested: 5, gitHistoryDepth: 20, excludeAuthors: [] },
80
76
  output: { verbose: false, saveHistory: false },
81
77
  };
@@ -89,7 +85,6 @@ export async function loadConfig() {
89
85
  if (cliConfig.interactiveReviewers !== undefined) config.reviewers.interactiveReviewers = cliConfig.interactiveReviewers;
90
86
  if (cliConfig.autoLabels !== null) config.github.autoLabels = cliConfig.autoLabels;
91
87
  if (cliConfig.includeImpactAnalysis !== null) config.github.includeImpactAnalysis = cliConfig.includeImpactAnalysis;
92
- if (cliConfig.autoReview !== null) config.github.autoReview = cliConfig.autoReview;
93
88
 
94
89
  // 其他 CLI 參數直接加入 config
95
90
  config.baseBranch = cliConfig.baseBranch;
@@ -115,7 +110,6 @@ function showHelp() {
115
110
  --no-confirm 跳過確認直接創建
116
111
  --interactive-reviewers 啟用互動式 reviewer 選擇 (預設啟用)
117
112
  --auto-labels 自動添加 Labels (預設啟用)
118
- --auto-review PR 建立後發佈 AI 審查 comment
119
113
  --help 顯示此說明
120
114
 
121
115
  範例:
@@ -4,7 +4,6 @@ import { GitOperations } from './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
- import { PRReviewer } from '../ai/pr-reviewer.js';
8
7
  import { ReviewerSelector } from '../reviewers/reviewer-selector.js';
9
8
  import { Logger } from '../ui/logger.js';
10
9
  import { PRError, log } from '../utils/helpers.js';
@@ -20,7 +19,6 @@ export class PRWorkflow {
20
19
  this.github = new GitHubAPI(); // 自動從 git remote 偵測組織名稱
21
20
  this.ai = new AIAnalyzer({ model: config.ai.model });
22
21
  this.labelAnalyzer = new LabelAnalyzer();
23
- this.reviewer = new PRReviewer({ model: config.ai.model });
24
22
  this.reviewerSelector = new ReviewerSelector({
25
23
  interactiveReviewers: config.reviewers.interactiveReviewers,
26
24
  maxSuggested: config.reviewers.maxSuggested,
@@ -34,80 +32,75 @@ export class PRWorkflow {
34
32
  * 執行完整工作流程
35
33
  */
36
34
  async execute() {
37
- // 0. 確認 gh CLI 已登入(預覽模式可跳過)
38
- if (!this.config.preview) {
39
- const auth = this.github.checkAuth();
40
- if (!auth.authenticated) {
41
- log.error('GitHub CLI 未登入,請先執行: gh auth login');
42
- throw new Error('GitHub CLI 未登入');
35
+ try {
36
+ // 0. 確認 gh CLI 已登入(預覽模式可跳過)
37
+ if (!this.config.preview) {
38
+ const auth = this.github.checkAuth();
39
+ if (!auth.authenticated) {
40
+ log.error('GitHub CLI 未登入,請先執行: gh auth login');
41
+ throw new Error('GitHub CLI 未登入');
42
+ }
43
43
  }
44
- }
45
44
 
46
- // 1. 驗證環境和分支
47
- const { baseBranch, headBranch } = await this.detectAndValidateBranches();
48
-
49
- // 2. 檢查是否有變更
50
- await this.validateChanges(baseBranch, headBranch);
51
-
52
- // 3. 推送到遠端(預覽模式跳過)
53
- if (!this.config.preview) {
54
- await this.pushToRemote(headBranch);
55
- }
45
+ // 1. 驗證環境和分支
46
+ const { baseBranch, headBranch } = await this.detectAndValidateBranches();
56
47
 
57
- // 4. 收集變更資訊
58
- const changeData = this.collectChangeData(baseBranch, headBranch);
48
+ // 2. 檢查是否有變更
49
+ await this.validateChanges(baseBranch, headBranch);
59
50
 
60
- // 5. AI 分析和生成 PR 內容
61
- const prContent = await this.generatePRContent(changeData);
51
+ // 3. 推送到遠端(預覽模式跳過)
52
+ if (!this.config.preview) {
53
+ await this.pushToRemote(headBranch);
54
+ }
62
55
 
63
- // 6. 顯示預覽
64
- this.displayPreview(prContent, changeData.stats);
56
+ // 4. 收集變更資訊
57
+ const changeData = this.collectChangeData(baseBranch, headBranch);
65
58
 
66
- // 預覽模式:僅顯示不創建
67
- if (this.config.preview) {
68
- log.info('預覽模式:未創建 PR');
69
- return;
70
- }
59
+ // 5. AI 分析和生成 PR 內容
60
+ const prContent = await this.generatePRContent(changeData);
71
61
 
72
- // 7. 選擇 Reviewers
73
- const reviewers = await this.selectReviewers(changeData);
62
+ // 6. 顯示預覽
63
+ this.displayPreview(prContent, changeData.stats);
74
64
 
75
- // 8. 確認創建
76
- if (!this.config.noConfirm) {
77
- const confirmed = await this.askConfirmation('是否創建此 Pull Request?');
78
- if (!confirmed) {
79
- log.info('已取消創建 PR');
65
+ // 預覽模式:僅顯示不創建
66
+ if (this.config.preview) {
67
+ log.info('預覽模式:未創建 PR');
80
68
  return;
81
69
  }
82
- }
83
70
 
84
- // 9. 創建 PR
85
- const prUrl = await this.createPR(prContent, baseBranch, headBranch, reviewers);
71
+ // 7. 選擇 Reviewers
72
+ const reviewers = await this.selectReviewers(changeData);
86
73
 
87
- // 10. 添加 Labels(如果啟用)
88
- if (this.config.github.autoLabels === true && prUrl) {
89
- try {
90
- const prNumber = prUrl.split('/').pop();
91
- await this.addLabels(prNumber, {
92
- ...prContent,
93
- stats: changeData.stats,
94
- });
95
- } catch (error) {
96
- log.warning('無法自動添加 Labels: ' + error.message);
74
+ // 8. 確認創建
75
+ if (!this.config.noConfirm) {
76
+ const confirmed = await this.askConfirmation('是否創建此 Pull Request?');
77
+ if (!confirmed) {
78
+ log.info('已取消創建 PR');
79
+ return;
80
+ }
97
81
  }
98
- }
99
82
 
100
- // 11. AI 自動審查(如果啟用)
101
- if (this.config.github.autoReview === true && prUrl) {
102
- try {
103
- const prNumber = prUrl.split('/').pop();
104
- await this.autoReviewPR(prNumber, changeData);
105
- } catch (error) {
106
- log.warning('AI 審查失敗,跳過: ' + error.message);
83
+ // 9. 創建 PR
84
+ const prUrl = await this.createPR(prContent, baseBranch, headBranch, reviewers);
85
+
86
+ // 10. 添加 Labels(如果啟用)
87
+ if (this.config.github.autoLabels === true && prUrl) {
88
+ try {
89
+ const prNumber = prUrl.split('/').pop();
90
+ await this.addLabels(prNumber, {
91
+ ...prContent,
92
+ stats: changeData.stats,
93
+ });
94
+ } catch (error) {
95
+ log.warning('無法自動添加 Labels: ' + error.message);
96
+ }
107
97
  }
108
- }
109
98
 
110
- this.logger.success('完成!');
99
+ this.logger.success('完成!');
100
+ } finally {
101
+ // 確保 AI 子程序一定被釋放
102
+ await this.ai.close();
103
+ }
111
104
  }
112
105
 
113
106
  /**
@@ -438,19 +431,6 @@ export class PRWorkflow {
438
431
  });
439
432
  }
440
433
 
441
- /**
442
- * AI 自動審查 PR
443
- */
444
- async autoReviewPR(prNumber, changeData) {
445
- log.step(`正在使用 AI 審查程式碼 (${this.config.ai.model})...\n`);
446
- const reviewBody = await this.reviewer.generateReview(
447
- changeData.diff,
448
- changeData.commits,
449
- changeData.changedFiles
450
- );
451
- await this.reviewer.postReviewComment(prNumber, reviewBody);
452
- }
453
-
454
434
  /**
455
435
  * 添加 Labels
456
436
  */
@@ -1,156 +0,0 @@
1
- import { CopilotClient, approveAll } from '@github/copilot-sdk';
2
- import { execSync } from 'child_process';
3
- import { writeFileSync, unlinkSync, existsSync } from 'fs';
4
- import { CONSTANTS, PROJECT_SKILLS_CONTEXT } from '../utils/constants.js';
5
- import { getSkillsSummaryForPrompt, log } from '../utils/helpers.js';
6
-
7
- /**
8
- * PR 審查員 - 負責生成 AI code review 並發布為 PR comment
9
- */
10
- export class PRReviewer {
11
- constructor(config = {}) {
12
- this.model = config.model || 'gpt-4.1';
13
- }
14
-
15
- /**
16
- * 建立 AI 客戶端
17
- */
18
- async createClient() {
19
- const client = new CopilotClient();
20
- const session = await client.createSession({
21
- model: this.model,
22
- onPermissionRequest: approveAll,
23
- });
24
- return { client, session };
25
- }
26
-
27
- /**
28
- * 生成 PR 審查報告
29
- */
30
- async generateReview(diff, commits, changedFiles) {
31
- const skillsSummary = getSkillsSummaryForPrompt(PROJECT_SKILLS_CONTEXT);
32
- const prompt = this.buildReviewPrompt(diff, commits, changedFiles, skillsSummary);
33
-
34
- const { client, session } = await this.createClient();
35
-
36
- try {
37
- const responsePromise = session.sendAndWait({ prompt });
38
- const timeoutPromise = new Promise((_, reject) => {
39
- setTimeout(() => reject(new Error('AI 請求超時 (60 秒)')), 60000);
40
- });
41
-
42
- const response = await Promise.race([responsePromise, timeoutPromise]);
43
- const reviewContent = response?.data.content?.trim() || '';
44
-
45
- if (!reviewContent) {
46
- throw new Error('AI 未能生成審查報告');
47
- }
48
-
49
- return this.wrapReviewBody(reviewContent);
50
- } finally {
51
- try {
52
- await client.stop();
53
- } catch (e) {
54
- // 忽略關閉錯誤
55
- }
56
- }
57
- }
58
-
59
- /**
60
- * 將審查報告發布為 PR comment
61
- */
62
- async postReviewComment(prNumber, reviewBody) {
63
- const bodyFile = '/tmp/pr-review-comment.md';
64
- writeFileSync(bodyFile, reviewBody);
65
-
66
- try {
67
- execSync(`gh pr comment ${prNumber} --body-file "${bodyFile}"`, {
68
- stdio: ['pipe', 'pipe', 'pipe'],
69
- });
70
- log.success(` AI 審查報告已發布至 PR #${prNumber}`);
71
- } finally {
72
- try {
73
- if (existsSync(bodyFile)) unlinkSync(bodyFile);
74
- } catch (e) {
75
- // 忽略刪除錯誤
76
- }
77
- }
78
- }
79
-
80
- /**
81
- * 建立 review prompt
82
- */
83
- buildReviewPrompt(diff, commits, changedFiles, skillsSummary) {
84
- const fileList = changedFiles
85
- .slice(0, CONSTANTS.MAX_FILES_IN_PROMPT)
86
- .join('\n');
87
-
88
- const truncatedDiff = diff.substring(0, CONSTANTS.MAX_DIFF_LENGTH);
89
- const diffTruncated = diff.length > CONSTANTS.MAX_DIFF_LENGTH;
90
-
91
- return `你是一位資深工程師,正在進行嚴格但友善的程式碼審查。
92
- 請根據以下程式碼變更,以繁體中文(台灣正體)輸出審查報告。
93
-
94
- ${skillsSummary}
95
-
96
- **變更檔案**:
97
- ${fileList}
98
- ${changedFiles.length > CONSTANTS.MAX_FILES_IN_PROMPT ? `... 還有 ${changedFiles.length - CONSTANTS.MAX_FILES_IN_PROMPT} 個檔案` : ''}
99
-
100
- **Commit 訊息**:
101
- ${commits.split('\n').slice(0, CONSTANTS.MAX_COMMITS_IN_PROMPT).join('\n')}
102
-
103
- **程式碼變更(diff)**:
104
- \`\`\`diff
105
- ${truncatedDiff}
106
- ${diffTruncated ? '\n... (內容過長已截斷)' : ''}
107
- \`\`\`
108
-
109
- ---
110
-
111
- **輸出格式**(直接輸出以下 Markdown,不要加任何引導語):
112
-
113
- ### 🔴 需要處理的問題
114
- [列出明確的 bug、安全漏洞、錯誤處理缺失等必須修正的項目]
115
- [格式:- **[類型]** \`檔案名稱\` — 具體說明問題]
116
- [若無此類問題,填寫「無」]
117
-
118
- ### 🟡 建議改善項目
119
- [列出效能問題、規範違反、可讀性問題等建議改善項目]
120
- [格式:- **[類型]** \`檔案名稱\` — 具體說明問題與建議]
121
- [若無此類問題,填寫「無」]
122
-
123
- ### ✅ 良好實踐
124
- [列出本次變更中值得肯定的設計或實踐]
125
- [若無特別亮點,填寫「程式碼結構清晰,無明顯問題」]
126
-
127
- ---
128
-
129
- **分類說明**:
130
- - 🔴 需要處理:security(安全)、bug(邏輯錯誤)、error-handling(缺少錯誤處理)、breaking(破壞性變更未說明)
131
- - 🟡 建議改善:performance(效能)、naming(命名規範)、structure(檔案/架構問題)、best-practice(最佳實踐)、test(缺少測試)
132
- - ✅ 良好實踐:列出做得好的地方,鼓勵正確行為
133
-
134
- **審查準則**:
135
- 1. 具體指出問題,盡量包含檔案名稱
136
- 2. 每個問題只列一次,不重複
137
- 3. 若程式碼品質良好,不要強制填充問題
138
- 4. 不要在開頭加引導語,直接輸出 ### 開頭的 Markdown
139
- 5. 全部使用繁體中文(台灣正體)`;
140
- }
141
-
142
- /**
143
- * 包裝最終的 review body(加上頭尾)
144
- */
145
- wrapReviewBody(reviewContent) {
146
- const now = new Date().toISOString().slice(0, 10);
147
- return `## 🤖 AI 自動審查報告
148
-
149
- > ⚡ 審查模型:${this.model} | 審查日期:${now}
150
-
151
- ${reviewContent}
152
-
153
- ---
154
- *此 review 由 AI 自動生成,僅供參考,請人工確認後合併*`;
155
- }
156
- }