ai-git-tools 2.0.17 → 2.0.19
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/README.md +12 -17
- package/bin/cli.js +0 -3
- package/package.json +1 -1
- package/src/commands/init.js +2 -2
- package/src/commands/pr.js +0 -2
- package/src/core/config-loader.js +0 -9
- package/src/pr-modules/core/config-loader.js +20 -16
- package/src/pr-modules/core/github-api.js +6 -11
- package/src/pr-modules/core/workflow.js +28 -19
- package/src/pr-modules/reviewers/reviewer-selector.js +20 -12
package/README.md
CHANGED
|
@@ -157,25 +157,21 @@ npx ai-git-tools ca --verbose
|
|
|
157
157
|
npx ai-git-tools pr [選項]
|
|
158
158
|
|
|
159
159
|
選項:
|
|
160
|
-
-b, --base <branch> 目標分支
|
|
161
|
-
-h, --head <branch> 來源分支
|
|
160
|
+
-b, --base <branch> 目標分支 (預設: 配置檔的 defaultBase 或自動偵測)
|
|
162
161
|
-m, --model <model> 指定 AI 模型
|
|
163
|
-
--draft 創建草稿 PR
|
|
164
162
|
--preview 僅預覽,不創建 PR
|
|
165
163
|
--no-confirm 跳過確認直接創建
|
|
166
|
-
--
|
|
167
|
-
--auto-labels 自動添加 Labels
|
|
168
|
-
--no-labels 不添加 Labels
|
|
169
|
-
--org <name> GitHub 組織名稱
|
|
164
|
+
--interactive-reviewers 啟用互動式 reviewer 選擇 (預設啟用)
|
|
165
|
+
--auto-labels 自動添加 Labels (預設啟用)
|
|
170
166
|
\`\`\`
|
|
171
167
|
|
|
172
168
|
**範例:**
|
|
173
169
|
|
|
174
170
|
\`\`\`bash
|
|
175
171
|
npx ai-git-tools pr
|
|
176
|
-
npx ai-git-tools pr --
|
|
177
|
-
npx ai-git-tools pr --base main --auto-reviewers
|
|
172
|
+
npx ai-git-tools pr --base release-2025-m12.1
|
|
178
173
|
npx ai-git-tools pr --preview
|
|
174
|
+
npx ai-git-tools pr --no-confirm
|
|
179
175
|
\`\`\`
|
|
180
176
|
|
|
181
177
|
### \`gitai workflow\` (別名: \`wf\`)
|
|
@@ -188,17 +184,16 @@ npx ai-git-tools workflow [選項]
|
|
|
188
184
|
選項:
|
|
189
185
|
-m, --model <model> 指定 AI 模型
|
|
190
186
|
-v, --verbose 顯示詳細輸出
|
|
191
|
-
-b, --base <branch> PR 目標分支
|
|
192
|
-
--
|
|
193
|
-
--auto-
|
|
194
|
-
--auto-labels 自動添加 Labels
|
|
187
|
+
-b, --base <branch> PR 目標分支 (預設: 配置檔 defaultBase 或自動偵測)
|
|
188
|
+
--preview 僅預覽 PR,不創建
|
|
189
|
+
--auto-labels 自動添加 Labels (預設啟用)
|
|
195
190
|
\`\`\`
|
|
196
191
|
|
|
197
192
|
**範例:**
|
|
198
193
|
|
|
199
194
|
\`\`\`bash
|
|
200
195
|
npx ai-git-tools workflow
|
|
201
|
-
npx ai-git-tools wf --
|
|
196
|
+
npx ai-git-tools wf --preview
|
|
202
197
|
\`\`\`
|
|
203
198
|
|
|
204
199
|
## ⚙️ 配置
|
|
@@ -222,10 +217,10 @@ export default {
|
|
|
222
217
|
|
|
223
218
|
// Reviewer 設定
|
|
224
219
|
reviewers: {
|
|
225
|
-
interactiveReviewers:
|
|
220
|
+
interactiveReviewers: true, // true=啟用互動式選擇,false=不添加任何 reviewers
|
|
226
221
|
maxSuggested: 5, // 最多建議人數
|
|
227
222
|
gitHistoryDepth: 20, // Git 歷史分析深度
|
|
228
|
-
excludeAuthors: [], //
|
|
223
|
+
excludeAuthors: [], // 排除特定作者(最高優先順序,即使手動選擇也會被過濾)
|
|
229
224
|
},
|
|
230
225
|
|
|
231
226
|
// 輸出設定
|
|
@@ -271,7 +266,7 @@ npx ai-git-tools commit-all
|
|
|
271
266
|
### 場景 3:快速發 PR
|
|
272
267
|
|
|
273
268
|
\`\`\`bash
|
|
274
|
-
npx ai-git-tools pr --auto-
|
|
269
|
+
npx ai-git-tools pr --auto-labels
|
|
275
270
|
# ✅ 自動生成完整的 PR 標題和描述
|
|
276
271
|
# ✅ 建議合適的 reviewers
|
|
277
272
|
# ✅ 添加相關的 labels
|
package/bin/cli.js
CHANGED
|
@@ -51,13 +51,10 @@ program
|
|
|
51
51
|
.command('pr')
|
|
52
52
|
.description('AI 自動生成 PR 並創建 Pull Request')
|
|
53
53
|
.option('--base <branch>', '指定目標分支')
|
|
54
|
-
.option('--head <branch>', '指定來源分支')
|
|
55
54
|
.option('--model <model>', '指定 AI 模型')
|
|
56
55
|
.option('--org <org-name>', '指定 GitHub 組織名稱')
|
|
57
|
-
.option('--draft', '創建草稿 PR')
|
|
58
56
|
.option('--preview', '僅預覽 PR 內容,不實際創建')
|
|
59
57
|
.option('--no-confirm', '跳過確認直接創建')
|
|
60
|
-
.option('--auto-reviewers', '自動選擇 reviewers')
|
|
61
58
|
.option('--auto-labels', '自動添加 Labels')
|
|
62
59
|
.option('--no-labels', '不添加 Labels')
|
|
63
60
|
.action(prCommand);
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -23,8 +23,8 @@ export default {
|
|
|
23
23
|
|
|
24
24
|
// GitHub 相關配置
|
|
25
25
|
github: {
|
|
26
|
-
orgName: '', // GitHub 組織名稱(留空則自動從 git remote
|
|
27
|
-
defaultBase: 'release', //
|
|
26
|
+
orgName: '', // GitHub 組織名稱(留空則自動從 git remote 取得)
|
|
27
|
+
defaultBase: 'release', // PR分支(使用 'release' 自動偵測最新 release 分支,如 release-2025-m11.1)
|
|
28
28
|
autoLabels: true, // 自動新增 Labels
|
|
29
29
|
},
|
|
30
30
|
|
package/src/commands/pr.js
CHANGED
|
@@ -119,27 +119,18 @@ export function parsePRCliArgs() {
|
|
|
119
119
|
case '--base':
|
|
120
120
|
config.baseBranch = args[++i];
|
|
121
121
|
break;
|
|
122
|
-
case '--head':
|
|
123
|
-
config.headBranch = args[++i];
|
|
124
|
-
break;
|
|
125
122
|
case '--model':
|
|
126
123
|
config.model = args[++i];
|
|
127
124
|
break;
|
|
128
125
|
case '--org':
|
|
129
126
|
config.orgName = args[++i];
|
|
130
127
|
break;
|
|
131
|
-
case '--draft':
|
|
132
|
-
config.draft = true;
|
|
133
|
-
break;
|
|
134
128
|
case '--preview':
|
|
135
129
|
config.preview = true;
|
|
136
130
|
break;
|
|
137
131
|
case '--no-confirm':
|
|
138
132
|
config.noConfirm = true;
|
|
139
133
|
break;
|
|
140
|
-
case '--auto-reviewers':
|
|
141
|
-
config.autoReviewers = true;
|
|
142
|
-
break;
|
|
143
134
|
case '--auto-labels':
|
|
144
135
|
config.autoLabels = true;
|
|
145
136
|
break;
|
|
@@ -10,10 +10,9 @@ export function parseCliArgs() {
|
|
|
10
10
|
baseBranch: null,
|
|
11
11
|
headBranch: null,
|
|
12
12
|
model: null,
|
|
13
|
-
draft: false,
|
|
14
13
|
preview: false,
|
|
15
14
|
noConfirm: false,
|
|
16
|
-
|
|
15
|
+
interactiveReviewers: undefined,
|
|
17
16
|
autoLabels: null,
|
|
18
17
|
};
|
|
19
18
|
|
|
@@ -28,9 +27,6 @@ export function parseCliArgs() {
|
|
|
28
27
|
case '--model':
|
|
29
28
|
config.model = args[++i];
|
|
30
29
|
break;
|
|
31
|
-
case '--draft':
|
|
32
|
-
config.draft = true;
|
|
33
|
-
break;
|
|
34
30
|
case '--preview':
|
|
35
31
|
config.preview = true;
|
|
36
32
|
break;
|
|
@@ -82,14 +78,12 @@ export async function loadConfig() {
|
|
|
82
78
|
|
|
83
79
|
// 合併配置(CLI 參數優先)
|
|
84
80
|
if (cliConfig.model) config.ai.model = cliConfig.model;
|
|
85
|
-
if (cliConfig.
|
|
86
|
-
if (cliConfig.
|
|
87
|
-
if (cliConfig.autoLabels) config.github.autoLabels = cliConfig.autoLabels;
|
|
81
|
+
if (cliConfig.interactiveReviewers !== undefined) config.reviewers.interactiveReviewers = cliConfig.interactiveReviewers;
|
|
82
|
+
if (cliConfig.autoLabels !== null) config.github.autoLabels = cliConfig.autoLabels;
|
|
88
83
|
|
|
89
84
|
// 其他 CLI 參數直接加入 config
|
|
90
85
|
config.baseBranch = cliConfig.baseBranch;
|
|
91
86
|
config.headBranch = cliConfig.headBranch;
|
|
92
|
-
config.draft = cliConfig.draft;
|
|
93
87
|
config.preview = cliConfig.preview;
|
|
94
88
|
config.noConfirm = cliConfig.noConfirm;
|
|
95
89
|
|
|
@@ -105,19 +99,29 @@ function showHelp() {
|
|
|
105
99
|
npx ai-git-tools pr [選項]
|
|
106
100
|
|
|
107
101
|
選項:
|
|
108
|
-
--base <branch> 指定目標分支 (預設:
|
|
109
|
-
--head <branch> 指定來源分支 (預設: 當前分支)
|
|
102
|
+
--base <branch> 指定目標分支 (預設: 使用配置檔的 defaultBase 或自動偵測)
|
|
110
103
|
--model <model> 指定 AI 模型 (預設: gpt-4.1)
|
|
111
|
-
--draft 創建草稿 PR
|
|
112
104
|
--preview 僅預覽 PR 內容,不實際創建
|
|
113
105
|
--no-confirm 跳過確認直接創建
|
|
114
|
-
--interactive-reviewers 啟用互動式 reviewer
|
|
115
|
-
--auto-labels 自動添加 Labels
|
|
106
|
+
--interactive-reviewers 啟用互動式 reviewer 選擇 (預設啟用)
|
|
107
|
+
--auto-labels 自動添加 Labels (預設啟用)
|
|
116
108
|
--help 顯示此說明
|
|
117
109
|
|
|
118
110
|
範例:
|
|
119
111
|
npx ai-git-tools pr
|
|
120
|
-
npx ai-git-tools pr --base
|
|
121
|
-
npx ai-git-tools pr --
|
|
112
|
+
npx ai-git-tools pr --base release-2025-m12.1
|
|
113
|
+
npx ai-git-tools pr --preview
|
|
114
|
+
npx ai-git-tools pr --no-confirm
|
|
115
|
+
|
|
116
|
+
配置檔範例 (.ai-git-config.js):
|
|
117
|
+
export default {
|
|
118
|
+
github: {
|
|
119
|
+
defaultBase: 'release-2025-m12.1', // 指定預設目標分支
|
|
120
|
+
autoLabels: true,
|
|
121
|
+
},
|
|
122
|
+
reviewers: {
|
|
123
|
+
interactiveReviewers: true, // 啟用互動式選擇 reviewers
|
|
124
|
+
},
|
|
125
|
+
};
|
|
122
126
|
`);
|
|
123
127
|
}
|
|
@@ -7,9 +7,9 @@ import { colors } from '../utils/constants.js';
|
|
|
7
7
|
* GitHub API 操作封裝
|
|
8
8
|
*/
|
|
9
9
|
export class GitHubAPI {
|
|
10
|
-
constructor(
|
|
11
|
-
//
|
|
12
|
-
this.orgName =
|
|
10
|
+
constructor() {
|
|
11
|
+
// 自動從 git remote 偵測組織名稱
|
|
12
|
+
this.orgName = this.detectOrgFromRemote();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -211,7 +211,7 @@ export class GitHubAPI {
|
|
|
211
211
|
* 創建或更新 PR
|
|
212
212
|
*/
|
|
213
213
|
async createOrUpdatePR(params) {
|
|
214
|
-
const { title, body, baseBranch, headBranch, reviewers
|
|
214
|
+
const { title, body, baseBranch, headBranch, reviewers } = params;
|
|
215
215
|
const bodyFile = '/tmp/pr-body.md';
|
|
216
216
|
writeFileSync(bodyFile, body);
|
|
217
217
|
|
|
@@ -227,7 +227,6 @@ export class GitHubAPI {
|
|
|
227
227
|
// PR 不存在
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
const draftFlag = config.draft ? '--draft' : '';
|
|
231
230
|
const escapedTitle = title.replace(/"/g, '\\"');
|
|
232
231
|
|
|
233
232
|
if (existingPRUrl) {
|
|
@@ -259,18 +258,14 @@ export class GitHubAPI {
|
|
|
259
258
|
} else if (this.orgName) {
|
|
260
259
|
headRef = `${this.orgName}:${headBranch}`;
|
|
261
260
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (draftFlag) {
|
|
265
|
-
createCmd += ` ${draftFlag}`;
|
|
266
|
-
}
|
|
261
|
+
const createCmd = `gh pr create --base "${baseBranch}" --head "${headRef}" --title "${escapedTitle}" --body-file "${bodyFile}"`;
|
|
267
262
|
|
|
268
263
|
try {
|
|
269
264
|
const result = execSync(createCmd, {
|
|
270
265
|
encoding: 'utf-8',
|
|
271
266
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
272
267
|
});
|
|
273
|
-
log.success(
|
|
268
|
+
log.success('Pull Request 創建成功!');
|
|
274
269
|
|
|
275
270
|
// 提取 PR URL
|
|
276
271
|
const prUrl = result
|
|
@@ -99,20 +99,28 @@ export class PRWorkflow {
|
|
|
99
99
|
|
|
100
100
|
// 自動偵測 base branch
|
|
101
101
|
if (!baseBranch) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (latestRelease) {
|
|
107
|
-
baseBranch = latestRelease;
|
|
108
|
-
this.displayDetectedBranches(allBranches, latestRelease);
|
|
109
|
-
log.success(`自動選擇最新分支: ${baseBranch}`);
|
|
110
|
-
log.info(`提示: 使用 --base <分支名> 指定其他分支\n`);
|
|
102
|
+
// 優先使用配置檔中的 defaultBase
|
|
103
|
+
if (this.config.github?.defaultBase) {
|
|
104
|
+
baseBranch = this.config.github.defaultBase;
|
|
105
|
+
log.success(`使用配置檔指定的分支: ${baseBranch}\n`);
|
|
111
106
|
} else {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
// 如果沒有配置,則自動偵測最新的 release 分支
|
|
108
|
+
log.step('正在偵測 release 分支...\n');
|
|
109
|
+
const allBranches = this.git.detectReleaseBranches();
|
|
110
|
+
const latestRelease = this.git.findLatestReleaseBranch();
|
|
111
|
+
|
|
112
|
+
if (latestRelease) {
|
|
113
|
+
baseBranch = latestRelease;
|
|
114
|
+
this.displayDetectedBranches(allBranches, latestRelease);
|
|
115
|
+
log.success(`自動選擇最新分支: ${baseBranch}`);
|
|
116
|
+
log.info(`提示: 使用 --base <分支名> 指定其他分支\n`);
|
|
117
|
+
} else {
|
|
118
|
+
throw new PRError('未偵測到任何 release 分支', 'NO_RELEASE_BRANCH', [
|
|
119
|
+
'使用 --base 參數指定目標分支',
|
|
120
|
+
'或在 .ai-git-config.js 中設置 github.defaultBase',
|
|
121
|
+
'確認遠端分支存在: git branch -r | grep release',
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
116
124
|
}
|
|
117
125
|
}
|
|
118
126
|
|
|
@@ -316,6 +324,13 @@ export class PRWorkflow {
|
|
|
316
324
|
return null;
|
|
317
325
|
}
|
|
318
326
|
|
|
327
|
+
// 如果禁用互動式選擇,不添加任何 reviewers
|
|
328
|
+
if (!this.config.reviewers.interactiveReviewers) {
|
|
329
|
+
console.log('ℹ️ 已禁用 reviewer 選擇(interactiveReviewers: false)\n');
|
|
330
|
+
log.info('提示: 你可以在創建 PR 後手動添加 reviewers\n');
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
319
334
|
// 獲取當前用戶
|
|
320
335
|
const currentUser = this.git.getCurrentUser();
|
|
321
336
|
|
|
@@ -324,12 +339,6 @@ export class PRWorkflow {
|
|
|
324
339
|
changeData.changedFiles
|
|
325
340
|
);
|
|
326
341
|
|
|
327
|
-
// 如果禁用互動式選擇,直接自動選擇
|
|
328
|
-
if (!this.config.reviewers.interactiveReviewers) {
|
|
329
|
-
console.log('🤖 自動選擇 reviewers(基於 Git 歷史)\n');
|
|
330
|
-
return this.reviewerSelector.autoSelectReviewers(suggestedReviewers, currentUser);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
342
|
// 抓取 GitHub 團隊資訊(只在互動模式需要)
|
|
334
343
|
const teams = await this.github.fetchTeams();
|
|
335
344
|
|
|
@@ -178,16 +178,30 @@ export class ReviewerSelector {
|
|
|
178
178
|
}
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
+
// 應用 excludeAuthors 過濾(優先順序最高)
|
|
182
|
+
const filteredIndividuals = selectedIndividuals.filter((login) => {
|
|
183
|
+
const shouldExclude = this.excludeAuthors.some((excluded) => {
|
|
184
|
+
const normalizedExcluded = excluded.toLowerCase();
|
|
185
|
+
return login.toLowerCase().includes(normalizedExcluded);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (shouldExclude) {
|
|
189
|
+
log.warning(`已排除 @${login}(在 excludeAuthors 列表中)`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return !shouldExclude;
|
|
193
|
+
});
|
|
194
|
+
|
|
181
195
|
console.log('');
|
|
182
196
|
if (selectedTeams.length > 0) {
|
|
183
197
|
log.success(`已選擇團隊: ${selectedTeams.join(', ')}`);
|
|
184
198
|
}
|
|
185
|
-
if (
|
|
186
|
-
log.success(`已選擇個人: ${
|
|
199
|
+
if (filteredIndividuals.length > 0) {
|
|
200
|
+
log.success(`已選擇個人: ${filteredIndividuals.map((u) => `@${u}`).join(', ')}`);
|
|
187
201
|
}
|
|
188
202
|
console.log('');
|
|
189
203
|
|
|
190
|
-
return { teams: selectedTeams, individuals:
|
|
204
|
+
return { teams: selectedTeams, individuals: filteredIndividuals };
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
/**
|
|
@@ -217,16 +231,10 @@ export class ReviewerSelector {
|
|
|
217
231
|
}
|
|
218
232
|
|
|
219
233
|
/**
|
|
220
|
-
* 選擇 Reviewers
|
|
234
|
+
* 選擇 Reviewers(互動模式)
|
|
221
235
|
*/
|
|
222
236
|
async select(teamsData, suggestedReviewers, currentUser) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return this.selectInteractive(teamsData, suggestedReviewers, currentUser);
|
|
226
|
-
} else {
|
|
227
|
-
// interactiveReviewers: false 表示自動選擇(基於 Git 歷史)
|
|
228
|
-
console.log('🤖 自動選擇 reviewers(基於 Git 歷史)\n');
|
|
229
|
-
return this.autoSelectReviewers(suggestedReviewers, currentUser);
|
|
230
|
-
}
|
|
237
|
+
// 此方法現在只在 interactiveReviewers: true 時被調用
|
|
238
|
+
return this.selectInteractive(teamsData, suggestedReviewers, currentUser);
|
|
231
239
|
}
|
|
232
240
|
}
|