ai-git-tools 2.0.71 → 2.0.72

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-git-tools",
3
- "version": "2.0.71",
3
+ "version": "2.0.72",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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 = 150000) {
13
13
  let lastError = null;
14
14
 
15
15
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -2,24 +2,52 @@ 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
+ // CopilotClient 子程序啟動 + AI 模型回應可能共需 60-120s,設為 150s 保留足夠緩衝
6
+ const AI_TIMEOUT_MS = 150000;
7
+
5
8
  /**
6
9
  * AI 分析器 - 負責程式碼分析和 PR 內容生成
7
10
  */
8
11
  export class AIAnalyzer {
9
12
  constructor(config = {}) {
10
13
  this.model = config.model || 'gpt-4.1';
14
+ this._client = null; // 複用同一個 CopilotClient,避免重複啟動子程序
11
15
  }
12
16
 
13
17
  /**
14
- * 建立 AI 客戶端
18
+ * 取得(或建立)共用的 CopilotClient
15
19
  */
16
- async createClient() {
17
- const client = new CopilotClient();
18
- const session = await client.createSession({
20
+ async _getOrCreateClient() {
21
+ if (!this._client) {
22
+ this._client = new CopilotClient();
23
+ }
24
+ return this._client;
25
+ }
26
+
27
+ /**
28
+ * 建立 AI Session(複用已有的 client)
29
+ */
30
+ async _createSession() {
31
+ const client = await this._getOrCreateClient();
32
+ return client.createSession({
19
33
  model: this.model,
20
- onPermissionRequest: approveAll
34
+ onPermissionRequest: approveAll,
21
35
  });
22
- return { client, session };
36
+ }
37
+
38
+ /**
39
+ * 釋放 CopilotClient 子程序資源
40
+ */
41
+ async close() {
42
+ if (this._client) {
43
+ try {
44
+ await this._client.stop();
45
+ } catch (e) {
46
+ // 忽略關閉錯誤
47
+ } finally {
48
+ this._client = null;
49
+ }
50
+ }
23
51
  }
24
52
 
25
53
  /**
@@ -29,31 +57,23 @@ export class AIAnalyzer {
29
57
  const skillsSummary = getSkillsSummaryForPrompt(PROJECT_SKILLS_CONTEXT);
30
58
  const prompt = this.buildPRPrompt(commits, diff, skillsSummary);
31
59
 
32
- const { client, session } = await this.createClient();
60
+ const session = await this._createSession();
33
61
 
34
- try {
35
- // 使用超時保護 (60 )
36
- const responsePromise = session.sendAndWait({ prompt });
37
- const timeoutPromise = new Promise((_, reject) => {
38
- setTimeout(() => reject(new Error('AI 請求超時 (60 秒)')), 60000);
39
- });
40
-
41
- const response = await Promise.race([responsePromise, timeoutPromise]);
42
- const prContent = response?.data.content?.trim() || '';
62
+ // 使用超時保護(150 秒,含子程序啟動 + AI 回應)
63
+ const responsePromise = session.sendAndWait({ prompt });
64
+ const timeoutPromise = new Promise((_, reject) => {
65
+ setTimeout(() => reject(new Error(`AI 請求超時 (${AI_TIMEOUT_MS / 1000} 秒)`)), AI_TIMEOUT_MS);
66
+ });
43
67
 
44
- if (!prContent) {
45
- throw new Error('AI 未能生成 PR 內容');
46
- }
68
+ const response = await Promise.race([responsePromise, timeoutPromise]);
69
+ const prContent = response?.data.content?.trim() || '';
47
70
 
48
- return this.parsePRContent(prContent);
49
- } finally {
50
- // 確保 client 一定會被關閉
51
- try {
52
- await client.stop();
53
- } catch (e) {
54
- // 忽略關閉錯誤
55
- }
71
+ if (!prContent) {
72
+ throw new Error('AI 未能生成 PR 內容');
56
73
  }
74
+
75
+ return this.parsePRContent(prContent);
76
+ // 注意:不在此 stop() client,改由 close() 統一清理以便複用
57
77
  }
58
78
 
59
79
  /**
@@ -63,15 +83,15 @@ export class AIAnalyzer {
63
83
  const skillsSummary = getSkillsSummaryForPrompt(PROJECT_SKILLS_CONTEXT);
64
84
  const prompt = this.buildAnalysisPrompt(changedFiles, diff, commits, skillsSummary);
65
85
 
66
- const { client, session } = await this.createClient();
86
+ const session = await this._createSession();
67
87
 
68
88
  try {
69
89
  log.info(' 正在使用 AI 深度分析程式碼變更...');
70
-
71
- // 使用超時保護 (60 秒)
90
+
91
+ // 使用超時保護(150 秒)
72
92
  const responsePromise = session.sendAndWait({ prompt });
73
93
  const timeoutPromise = new Promise((_, reject) => {
74
- setTimeout(() => reject(new Error('AI 請求超時 (60 秒)')), 60000);
94
+ setTimeout(() => reject(new Error(`AI 請求超時 (${AI_TIMEOUT_MS / 1000} 秒)`)), AI_TIMEOUT_MS);
75
95
  });
76
96
 
77
97
  const response = await Promise.race([responsePromise, timeoutPromise]);
@@ -103,14 +123,8 @@ export class AIAnalyzer {
103
123
  } catch (error) {
104
124
  log.warning(` AI 分析失敗 (${error.message}),使用基礎分析...\n`);
105
125
  return this.getFallbackAnalysis(changedFiles);
106
- } finally {
107
- // 確保 client 一定會被關閉
108
- try {
109
- await client.stop();
110
- } catch (e) {
111
- // 忽略關閉錯誤
112
- }
113
126
  }
127
+ // 注意:不在此 stop() client,改由 close() 統一清理以便複用
114
128
  }
115
129
 
116
130
  /**
@@ -32,70 +32,75 @@ export class PRWorkflow {
32
32
  * 執行完整工作流程
33
33
  */
34
34
  async execute() {
35
- // 0. 確認 gh CLI 已登入(預覽模式可跳過)
36
- if (!this.config.preview) {
37
- const auth = this.github.checkAuth();
38
- if (!auth.authenticated) {
39
- log.error('GitHub CLI 未登入,請先執行: gh auth login');
40
- 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
+ }
41
43
  }
42
- }
43
44
 
44
- // 1. 驗證環境和分支
45
- const { baseBranch, headBranch } = await this.detectAndValidateBranches();
45
+ // 1. 驗證環境和分支
46
+ const { baseBranch, headBranch } = await this.detectAndValidateBranches();
46
47
 
47
- // 2. 檢查是否有變更
48
- await this.validateChanges(baseBranch, headBranch);
48
+ // 2. 檢查是否有變更
49
+ await this.validateChanges(baseBranch, headBranch);
49
50
 
50
- // 3. 推送到遠端(預覽模式跳過)
51
- if (!this.config.preview) {
52
- await this.pushToRemote(headBranch);
53
- }
51
+ // 3. 推送到遠端(預覽模式跳過)
52
+ if (!this.config.preview) {
53
+ await this.pushToRemote(headBranch);
54
+ }
54
55
 
55
- // 4. 收集變更資訊
56
- const changeData = this.collectChangeData(baseBranch, headBranch);
56
+ // 4. 收集變更資訊
57
+ const changeData = this.collectChangeData(baseBranch, headBranch);
57
58
 
58
- // 5. AI 分析和生成 PR 內容
59
- const prContent = await this.generatePRContent(changeData);
59
+ // 5. AI 分析和生成 PR 內容
60
+ const prContent = await this.generatePRContent(changeData);
60
61
 
61
- // 6. 顯示預覽
62
- this.displayPreview(prContent, changeData.stats);
62
+ // 6. 顯示預覽
63
+ this.displayPreview(prContent, changeData.stats);
63
64
 
64
- // 預覽模式:僅顯示不創建
65
- if (this.config.preview) {
66
- log.info('預覽模式:未創建 PR');
67
- return;
68
- }
65
+ // 預覽模式:僅顯示不創建
66
+ if (this.config.preview) {
67
+ log.info('預覽模式:未創建 PR');
68
+ return;
69
+ }
69
70
 
70
- // 7. 選擇 Reviewers
71
- const reviewers = await this.selectReviewers(changeData);
71
+ // 7. 選擇 Reviewers
72
+ const reviewers = await this.selectReviewers(changeData);
72
73
 
73
- // 8. 確認創建
74
- if (!this.config.noConfirm) {
75
- const confirmed = await this.askConfirmation('是否創建此 Pull Request?');
76
- if (!confirmed) {
77
- log.info('已取消創建 PR');
78
- return;
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
+ }
79
81
  }
80
- }
81
82
 
82
- // 9. 創建 PR
83
- const prUrl = await this.createPR(prContent, baseBranch, headBranch, reviewers);
83
+ // 9. 創建 PR
84
+ const prUrl = await this.createPR(prContent, baseBranch, headBranch, reviewers);
84
85
 
85
- // 10. 添加 Labels(如果啟用)
86
- if (this.config.github.autoLabels === true && prUrl) {
87
- try {
88
- const prNumber = prUrl.split('/').pop();
89
- await this.addLabels(prNumber, {
90
- ...prContent,
91
- stats: changeData.stats,
92
- });
93
- } catch (error) {
94
- log.warning('無法自動添加 Labels: ' + error.message);
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
+ }
95
97
  }
96
- }
97
98
 
98
- this.logger.success('完成!');
99
+ this.logger.success('完成!');
100
+ } finally {
101
+ // 確保 AI 子程序一定被釋放
102
+ await this.ai.close();
103
+ }
99
104
  }
100
105
 
101
106
  /**