ai-git-tools 2.0.41 → 2.0.44

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.41",
3
+ "version": "2.0.44",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -196,6 +196,9 @@ export async function loadAutodevConfig() {
196
196
  skipPr: false,
197
197
  verbose: false,
198
198
  projectRoot: process.cwd(),
199
+ // AI 設定(可被 autodev 子區塊或全域 ai 區塊覆蓋)
200
+ aiModel: 'gpt-4.1',
201
+ maxRetries: 3,
199
202
  };
200
203
 
201
204
  // 支援 .ai-git-config.js 和 .ai-git-config.mjs
@@ -205,11 +208,14 @@ export async function loadAutodevConfig() {
205
208
  ];
206
209
 
207
210
  let userAutodev = {};
211
+ let userAi = {};
208
212
  for (const configPath of candidates) {
209
213
  if (existsSync(configPath)) {
210
214
  try {
211
215
  const imported = await import(`file://${configPath}`);
212
- userAutodev = (imported.default || {}).autodev || {};
216
+ const root = imported.default || {};
217
+ userAutodev = root.autodev || {};
218
+ userAi = root.ai || {}; // 讀取全域 ai 區塊
213
219
  break;
214
220
  } catch (error) {
215
221
  console.warn(`⚠️ 載入 autodev 配置失敗: ${error.message}`);
@@ -219,6 +225,10 @@ export async function loadAutodevConfig() {
219
225
 
220
226
  return {
221
227
  ...defaults,
228
+ // 全域 ai 區塊優先度低於 autodev 子區塊
229
+ aiModel: userAutodev.aiModel ?? userAi.model ?? defaults.aiModel,
230
+ maxRetries: userAutodev.maxRetries ?? userAi.maxRetries ?? defaults.maxRetries,
231
+ // 其他 autodev 欄位
222
232
  ...userAutodev,
223
233
  };
224
234
  }
@@ -28,23 +28,45 @@ export class CodeGenerator {
28
28
  async generateCodeFiles(issueData, projectRoot = process.cwd()) {
29
29
  const prompt = this._buildPrompt(issueData, projectRoot);
30
30
 
31
- console.log(' 🤖 調用 AI 生成代碼框架...');
31
+ console.log(' 🤖 調用 AI 生成代碼框架(最多等待 3 分鐘)...');
32
32
 
33
33
  let response;
34
34
  try {
35
+ // 代碼生成需要較長時間,使用 180 秒 timeout
35
36
  response = await AIClient.sendAndWait(
36
37
  prompt,
37
38
  this.model,
38
- this.config.maxRetries || 3
39
+ this.config.maxRetries || 3,
40
+ 180_000
39
41
  );
40
42
  } catch (error) {
43
+ // 顯示完整錯誤訊息方便除錯
41
44
  console.warn(` ⚠️ AI 調用失敗:${error.message}`);
45
+ if (error.cause) console.warn(` 原因:${error.cause}`);
46
+ return [];
47
+ }
48
+
49
+ if (!response) {
50
+ console.warn(' ⚠️ AI 回傳空回應');
42
51
  return [];
43
52
  }
44
53
 
45
54
  // 解析 AI 回應
46
55
  const files = this._parseResponse(response);
47
- console.log(` 生成 ${files.length} 個檔案`);
56
+ if (files.length === 0) {
57
+ console.warn(' ⚠️ AI 未回傳任何檔案(可能 JSON 格式有誤)');
58
+ if (process.env.AUTODEV_DEBUG) {
59
+ console.log(' --- AI 原始回應 ---');
60
+ console.log(response.slice(0, 500));
61
+ console.log(' (設定 AUTODEV_DEBUG=1 可看到完整回應)');
62
+ }
63
+ } else {
64
+ console.log(` ✅ AI 規劃生成 ${files.length} 個檔案:`);
65
+ files.forEach((f) => {
66
+ const icon = f.filePath.includes('.test.') || f.filePath.includes('__tests__') ? '🧪' : '📄';
67
+ console.log(` ${icon} ${f.filePath}`);
68
+ });
69
+ }
48
70
  return files;
49
71
  }
50
72
 
@@ -63,7 +85,8 @@ export class CodeGenerator {
63
85
 
64
86
  for (const file of files) {
65
87
  if (file.type === 'skip') {
66
- skipped.push(`${file.filePath} (${file.reason})`);
88
+ console.log(` ⏭️ 跳過 ${file.filePath}${file.reason ? ` (${file.reason})` : ''}`);
89
+ skipped.push(`${file.filePath}${file.reason ? ` (${file.reason})` : ''}`);
67
90
  continue;
68
91
  }
69
92
 
@@ -76,17 +99,21 @@ export class CodeGenerator {
76
99
  mkdirSync(dir, { recursive: true });
77
100
  }
78
101
  } catch (e) {
79
- console.warn(` ⚠️ 無法建立目錄 ${dir}`);
102
+ console.warn(` ⚠️ 無法建立目錄 ${dir}:${e.message}`);
80
103
  skipped.push(file.filePath);
81
104
  continue;
82
105
  }
83
106
 
84
- // 寫入檔案
107
+ // 寫入檔案(即時顯示)
85
108
  try {
109
+ const isTest = file.filePath.includes('.test.') || file.filePath.includes('__tests__');
110
+ const icon = isTest ? '🧪' : '📄';
111
+ const existed = existsSync(fullPath);
86
112
  writeFileSync(fullPath, file.content, 'utf-8');
87
113
  written.push(file.filePath);
114
+ console.log(` ${icon} ${existed ? '更新' : '新建'} ${file.filePath}`);
88
115
  } catch (e) {
89
- console.warn(` ⚠️ 無法寫入 ${file.filePath}`);
116
+ console.warn(` ⚠️ 無法寫入 ${file.filePath}:${e.message}`);
90
117
  skipped.push(file.filePath);
91
118
  }
92
119
  }
@@ -99,7 +126,7 @@ export class CodeGenerator {
99
126
  _buildPrompt(issueData, projectRoot) {
100
127
  const { title, body, labels } = issueData;
101
128
 
102
- return `你是一個資深軟體工程師,負責基於 GitHub Issue 生成代碼框架。
129
+ return `你是一個資深軟體工程師,負責基於 GitHub Issue 生成完整的代碼框架和測試用例。
103
130
 
104
131
  ## Issue 資訊
105
132
  標題:${title}
@@ -109,21 +136,40 @@ ${body}
109
136
 
110
137
  ## 任務
111
138
  1. 分析上方 Issue 的需求
112
- 2. 設計必要的檔案結構和代碼框架
113
- 3. 生成初始實現(可以有 TODO 註釋表示待完成部分)
139
+ 2. **同時**設計兩套檔案:
140
+ a. 實現檔案(src/ 中的原始碼)
141
+ b. 測試檔案(tests/ 或 __tests__/ 中的測試)
142
+ 3. 生成初始實現和初始測試
143
+
144
+ ## 重要:測試檔案生成
145
+ - 對每個實現檔案,都生成對應的測試檔案
146
+ - 測試檔案應放在 \`tests/\` 或 \`src/__tests__/\` 目錄
147
+ - 測試應覆蓋主要功能(可使用 TODO 標記待補充的測試用例)
148
+ - 測試檔案命名:實現檔案 \`.js\` → 測試檔案 \`.test.js\`
149
+ - 例:\`src/utils/helper.js\` → \`tests/utils/helper.test.js\`
114
150
 
115
151
  ## 輸出格式
116
- 請回傳 JSON 陣列,每個物件包含:
152
+ 請回傳 JSON 陣列,包含**源文件和測試文件**:
117
153
  \`\`\`json
118
154
  [
119
155
  {
120
- "filePath": "src/components/MyComponent.tsx",
121
- "content": "// 完整的檔案內容...",
156
+ "filePath": "src/components/MyComponent.jsx",
157
+ "content": "// React 元件實現...",
158
+ "type": "new"
159
+ },
160
+ {
161
+ "filePath": "tests/components/MyComponent.test.jsx",
162
+ "content": "// Jest 測試...",
163
+ "type": "new"
164
+ },
165
+ {
166
+ "filePath": "src/utils/helper.js",
167
+ "content": "// 工具函數...",
122
168
  "type": "new"
123
169
  },
124
170
  {
125
- "filePath": "src/utils/helper.ts",
126
- "content": "// ...",
171
+ "filePath": "tests/utils/helper.test.js",
172
+ "content": "// 單元測試...",
127
173
  "type": "new"
128
174
  }
129
175
  ]
@@ -131,11 +177,12 @@ ${body}
131
177
 
132
178
  只回傳 JSON,不要其他文字。
133
179
 
134
- ## 檔案類型指南
135
- - TypeScript/JavaScript:包含正確的 import/export
136
- - React:使用 functional components + hooks
137
- - 包含基本的 JSDoc 註釋
138
- - 包含 TODO FIXME 註釋提示需要補完的部分
180
+ ## 測試框架指南(假設 Jest)
181
+ - \`describe('名稱', () => { ... })\` 分組
182
+ - \`test('應該...', () => { ... })\` 單一測試
183
+ - 包含 arrange-act-assert 結構
184
+ - TODO 部分用 \`test.todo('待實現')\`
185
+ - 可包含 \`// TODO: 補充邊界情況\` 註釋
139
186
 
140
187
  專案根目錄:${projectRoot}`;
141
188
  }
@@ -196,15 +196,28 @@ export class AutodevWorkflow {
196
196
  return [];
197
197
  }
198
198
 
199
- // 寫入檔案
199
+ // 寫入檔案(每個檔案會即時顯示名稱)
200
200
  const { written, skipped } = await this.codeGenerator.writeFiles(files, projectRoot);
201
- console.log(` ✅ 已寫入 ${written.length} 個檔案`);
201
+
202
202
  if (skipped.length > 0) {
203
203
  console.warn(` ⚠️ 跳過 ${skipped.length} 個檔案`);
204
204
  }
205
205
 
206
- if (options.verbose) {
207
- written.forEach((f) => console.log(` 新建: ${f}`));
206
+ // 顯示 git diff --stat 讓用戶看到實際變動
207
+ if (written.length > 0) {
208
+ console.log(`\n ✅ 共寫入 ${written.length} 個檔案,git 變動摘要:`);
209
+ try {
210
+ const { execSync } = await import('child_process');
211
+ const stat = execSync('git diff --stat HEAD 2>/dev/null || git status --short', {
212
+ cwd: projectRoot,
213
+ encoding: 'utf-8',
214
+ }).trim();
215
+ if (stat) {
216
+ stat.split('\n').forEach((line) => console.log(` ${line}`));
217
+ }
218
+ } catch (_) {
219
+ // git diff 失敗不影響流程
220
+ }
208
221
  }
209
222
 
210
223
  return written;