ai-git-tools 2.1.14 → 2.1.15
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 +28 -10
- package/bin/cli.js +8 -2
- package/package.json +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/redmine-subtasks.js +27 -3
- package/src/core/ai-client.js +1 -1
- package/src/core/config-loader.js +2 -2
- package/src/pr-modules/ai/code-analyzer.js +1 -1
- package/src/redmine/issue-analyzer.js +1 -1
- package/src/redmine/subtask-analyzer.js +767 -133
- package/src/redmine/subtask-formatters.js +346 -3
- package/src/redmine/subtask-sync.js +167 -26
package/README.md
CHANGED
|
@@ -147,7 +147,7 @@ npx ai-git-tools commit [選項]
|
|
|
147
147
|
|
|
148
148
|
\`\`\`bash
|
|
149
149
|
npx ai-git-tools commit
|
|
150
|
-
npx ai-git-tools commit --model
|
|
150
|
+
npx ai-git-tools commit --model gpt-5.6-luna
|
|
151
151
|
npx ai-git-tools commit --verbose
|
|
152
152
|
\`\`\`
|
|
153
153
|
|
|
@@ -238,7 +238,7 @@ npx ai-git-tools model-info [選項]
|
|
|
238
238
|
|
|
239
239
|
\`\`\`bash
|
|
240
240
|
npx ai-git-tools model-info
|
|
241
|
-
npx ai-git-tools model-info --model
|
|
241
|
+
npx ai-git-tools model-info --model gpt-5.6-luna
|
|
242
242
|
npx ai-git-tools model-info --filter claude
|
|
243
243
|
npx ai-git-tools model-info --json
|
|
244
244
|
\`\`\`
|
|
@@ -403,20 +403,38 @@ AI 產出的 notes 會固定整理成以下架構:
|
|
|
403
403
|
|
|
404
404
|
### `ai-git-tools redmine-subtasks`
|
|
405
405
|
|
|
406
|
-
|
|
406
|
+
這是 Frontend 專用的主 Issue 拆分命令。它會先判斷 Frontend scope,再依頁面或使用者可感知的完整功能整理成少量大項子任務,最後在人工審核後透過 Redmine API 建立 children。以 Issue #18793 為例:
|
|
407
407
|
|
|
408
408
|
```bash
|
|
409
409
|
# 只讀取與分析,不會建立子任務
|
|
410
|
-
ai-git-tools redmine-subtasks
|
|
411
|
-
--issue 18793 \
|
|
412
|
-
--preview \
|
|
413
|
-
--output redmine-subtasks.json
|
|
410
|
+
ai-git-tools redmine-subtasks --issue 18793 --preview --output redmine-subtasks.json
|
|
414
411
|
|
|
415
412
|
# 確認草稿中的 title 與 content 後才建立子任務
|
|
416
413
|
ai-git-tools redmine-subtasks --apply --from redmine-subtasks.json
|
|
417
414
|
```
|
|
418
415
|
|
|
419
|
-
|
|
416
|
+
可以明確指定範圍;目前只接受 `frontend`:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
ai-git-tools redmine-subtasks --issue 18793 --scope frontend --preview
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
預設會產生 2~8 個功能/頁面層級的大項;`--max-subtasks <number>` 可設定 1~12 的上限。工具不會把單一 endpoint、component、hook、util、function、欄位或測試案例拆成獨立 Issue;明確服務多個頁面的共用 Frontend 基礎設施才可成為前置大項。
|
|
423
|
+
|
|
424
|
+
如果主 Issue 同時包含 Frontend 與 Backend、Mobile、QA、DevOps 需求,預覽會顯示 Frontend scope decision,只建立 Frontend 子任務,並把排除內容與依據列出。沒有 Frontend 需求時不會保存可套用的空草稿;範圍不明時必須先補充需求。
|
|
425
|
+
|
|
426
|
+
每個子任務會產生 schema v2 的完整開發內容,包括:
|
|
427
|
+
|
|
428
|
+
- 一句話需求說明、功能需求、包含/不包含範圍、依賴與開發順序。
|
|
429
|
+
- route、存取角色與 evidence;沒有明確 route 時保留未提供,不會猜測路徑。
|
|
430
|
+
- 每個 list、detail、create、edit、view、dashboard、modal 畫面各自列出 screen 與 ASCII wireframe,不會把多個畫面合併成一張模糊示意圖。
|
|
431
|
+
- 使用者流程與 loading、empty、success、error、permission、validation、disabled、unsaved 等有依據的狀態。
|
|
432
|
+
- 每個可操作子任務的 Mermaid 流程圖;無法從需求確認流程時會保留 `not-supported` 狀態與原因,不製造假流程。
|
|
433
|
+
- 有 API 依據時,逐筆保存 method、base URL、path、用途、auth、headers、path/query/body、request example、response status/body、response example、error、pagination、filter、sort 與 completeness。
|
|
434
|
+
- API 只提供 path 或 method 時標記為 partial;沒有 API 依據時維持空的 `apiContract`/`requestResponse`,並列為待確認事項,不使用一般 REST 慣例補欄位。
|
|
435
|
+
- validation、error handling、permission、authentication、authorization、responsive、accessibility、i18n、performance、security、analytics、browser compatibility、cache、polling、feature flag 與 Frontend test plan;未提及的項目使用空值,不會自動套用最佳實務。
|
|
436
|
+
|
|
437
|
+
內容會區分 `explicit`(Issue 明確要求)、`repository`(安全提供的專案上下文)與 `derived`(為了讓需求可驗收的最小推導)。無法確認的決策會放入 `unresolvedItems`,不會被寫成確定需求。`title` 會寫入 Redmine `subject`,完整 Frontend content 會寫入 `description`。
|
|
420
438
|
|
|
421
439
|
需求能推導出使用流程時,content 會包含 Redmine Mermaid macro:
|
|
422
440
|
|
|
@@ -430,7 +448,7 @@ flowchart TD
|
|
|
430
448
|
|
|
431
449
|
子任務涉及頁面但主 Issue 沒有明確畫面設計時,content 會附上純文字 wireframe,只描述資訊區塊、主要操作與已知狀態,不猜測顏色、尺寸或像素。沒有可靠流程或畫面需求時,對應區段會省略,未決事項會保留為待確認項目。
|
|
432
450
|
|
|
433
|
-
apply 前會重新讀取主 Issue
|
|
451
|
+
這個流程使用 schema v2 draft。舊版 v1 草稿不可直接套用,請重新執行 preview。apply 前會重新讀取主 Issue;若 subject、description、project 或 tracker 已變更,預設會停止建立,確認變更仍可套用時才使用 `--force`。`--force` 只略過 parent snapshot conflict,不會略過 schema、payload 或 duplicate validation。既有子任務若有相同 managed key 或完全相同 title,會略過以避免重複建立;children summary 沒有 description 時,工具會再讀取 child detail 判斷 managed key。多筆建立採逐筆處理,部分成功不會回滾已建立的子任務。
|
|
434
452
|
|
|
435
453
|
此命令只建立主 Issue 底下的子任務,不會修改主 Issue 的 status、description、notes、done ratio 或完成欄位。API key 沿用上方的 `REDMINE_API_KEY` 環境變數,不會放入 CLI 參數、草稿或輸出內容;AI 產出的拆分結果仍必須由使用者審核。
|
|
436
454
|
|
|
@@ -442,7 +460,7 @@ apply 前會重新讀取主 Issue。若 subject、description、project 或 trac
|
|
|
442
460
|
export default {
|
|
443
461
|
// AI 設定
|
|
444
462
|
ai: {
|
|
445
|
-
model: '
|
|
463
|
+
model: 'gpt-5.6-luna', // AI 模型
|
|
446
464
|
maxDiffLength: 8000, // 最大 diff 長度
|
|
447
465
|
maxRetries: 3, // 失敗重試次數
|
|
448
466
|
},
|
package/bin/cli.js
CHANGED
|
@@ -103,8 +103,14 @@ registerCommand(program, 'redmine-update', '分析並更新 Redmine Issue 的開
|
|
|
103
103
|
{ flags: '--force', description: '強制略過 Issue 狀態或內容衝突檢查' },
|
|
104
104
|
], redmineUpdateCommand);
|
|
105
105
|
|
|
106
|
-
registerCommand(program, 'redmine-subtasks', '分析主 Issue
|
|
107
|
-
{
|
|
106
|
+
registerCommand(program, 'redmine-subtasks', '分析主 Issue 的 Frontend 需求並建立功能/頁面子任務', [
|
|
107
|
+
{
|
|
108
|
+
flags: '--issue <id>',
|
|
109
|
+
description: '指定 Redmine 主 Issue ID(僅允許一個)',
|
|
110
|
+
argParser: (value, previous = []) => [...previous, value],
|
|
111
|
+
defaultValue: [],
|
|
112
|
+
},
|
|
113
|
+
{ flags: '--scope <scope>', description: '分析範圍(目前只支援 frontend)' },
|
|
108
114
|
{ flags: '--max-subtasks <number>', description: '子任務大項上限(預設:8)' },
|
|
109
115
|
{ flags: '--preview', description: '產生並顯示拆分預覽,不修改 Redmine' },
|
|
110
116
|
{ flags: '--output <file>', description: '保存 preview 草稿 JSON' },
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { dirname } from 'path';
|
|
2
3
|
import { loadConfig } from '../core/config-loader.js';
|
|
3
4
|
import { RedmineClient } from '../redmine/redmine-client.js';
|
|
4
5
|
import { buildRedmineConfig } from '../redmine/config.js';
|
|
@@ -27,11 +28,20 @@ export function validateRedmineSubtaskOptions(options = {}) {
|
|
|
27
28
|
if (options.from && !options.apply) throw new Error('--from 只能搭配 --apply 使用');
|
|
28
29
|
if (options.output && !options.preview) throw new Error('--output 只能搭配 --preview 使用');
|
|
29
30
|
if (options.force && !options.apply) throw new Error('--force 只能搭配 --apply 使用');
|
|
31
|
+
if (options.scope !== undefined && options.scope !== 'frontend') {
|
|
32
|
+
throw new Error('目前 redmine-subtasks 只支援 frontend scope');
|
|
33
|
+
}
|
|
30
34
|
if (!options.apply && !options.preview) {
|
|
31
35
|
throw new Error('請使用 --preview 產生草稿,確認後再使用 --apply --from <file>');
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
const issueIds = normalizeIssueOptions(options.issue);
|
|
39
|
+
if (issueIds.length > 1) {
|
|
40
|
+
throw new Error('redmine-subtasks 只允許指定一個 parent Issue');
|
|
41
|
+
}
|
|
42
|
+
if (options.apply && issueIds.length > 0) {
|
|
43
|
+
throw new Error('--issue 只能搭配 --preview 使用,apply 請以 draft 的 parent 為準');
|
|
44
|
+
}
|
|
35
45
|
if (!options.apply && issueIds.length === 0) throw new Error('preview 模式需要 --issue <id>');
|
|
36
46
|
const invalidIssueIds = issueIds.filter(id => !/^[1-9]\d*$/.test(String(id)));
|
|
37
47
|
if (invalidIssueIds.length > 0) throw new Error(`Issue ID 必須是正整數:${invalidIssueIds.join('、')}`);
|
|
@@ -57,8 +67,12 @@ export function formatSubtaskProgress(progress = {}) {
|
|
|
57
67
|
return `主 Issue #${progress.parentIssueId} 已讀取:${progress.subject || '無標題'}`;
|
|
58
68
|
case 'analyze-subtasks':
|
|
59
69
|
return `分析 Issue #${progress.parentIssueId} 的功能/頁面大項...`;
|
|
70
|
+
case 'scope-analysis':
|
|
71
|
+
return `分析 Issue #${progress.parentIssueId} 的 Frontend scope...`;
|
|
60
72
|
case 'subtasks-analyzed':
|
|
61
73
|
return `已產生 ${progress.count} 個子任務候選`;
|
|
74
|
+
case 'screen-enrichment':
|
|
75
|
+
return `[${progress.index}/${progress.total}] 整理 screen wireframe...`;
|
|
62
76
|
case 'create-child':
|
|
63
77
|
return `[${progress.index}/${progress.total}] 建立子任務:${progress.title || '無標題'}`;
|
|
64
78
|
case 'preview':
|
|
@@ -85,7 +99,10 @@ function printApplyResults(results) {
|
|
|
85
99
|
} else if (result.skipped) {
|
|
86
100
|
console.log(`⚠️ 子任務「${result.title}」略過:已有相同子任務`);
|
|
87
101
|
} else if (result.blocked) {
|
|
88
|
-
|
|
102
|
+
const reason = result.reason === 'child-detail-unavailable'
|
|
103
|
+
? `無法讀取既有子任務內容(${result.childIds?.join('、') || '未知'}),為避免重複建立而停止`
|
|
104
|
+
: '已變更,略過全部建立';
|
|
105
|
+
console.log(`❌ 主 Issue #${result.parentIssueId} ${reason}`);
|
|
89
106
|
} else {
|
|
90
107
|
console.log(`❌ 子任務「${result.title}」建立失敗:${result.error}`);
|
|
91
108
|
}
|
|
@@ -128,14 +145,21 @@ export async function redmineSubtasksCommand(options = {}) {
|
|
|
128
145
|
parentIssueId,
|
|
129
146
|
client,
|
|
130
147
|
maxSubtasks: options.maxSubtasks === undefined ? 8 : Number(options.maxSubtasks),
|
|
148
|
+
repositoryContext: options.repositoryContext || config.frontend?.repositoryContext || {},
|
|
131
149
|
model: config.ai.model,
|
|
132
150
|
maxRetries: config.ai.maxRetries,
|
|
133
151
|
onProgress: printProgress,
|
|
134
152
|
});
|
|
135
|
-
console.log(formatSubtaskPreview({
|
|
153
|
+
console.log(formatSubtaskPreview({
|
|
154
|
+
parent: draft.parent,
|
|
155
|
+
candidates: draft.subtasks,
|
|
156
|
+
analysisScope: draft.analysisScope,
|
|
157
|
+
scopeDecision: draft.scopeDecision,
|
|
158
|
+
}));
|
|
136
159
|
printProgress({ phase: 'preview' });
|
|
137
160
|
|
|
138
161
|
if (options.output) {
|
|
162
|
+
mkdirSync(dirname(options.output), { recursive: true });
|
|
139
163
|
writeFileSync(options.output, serializeSubtaskDraft(draft), 'utf-8');
|
|
140
164
|
console.log(`預覽草稿已保存:${options.output}`);
|
|
141
165
|
console.log(`請使用 --apply --from ${options.output} 套用。`);
|
package/src/core/ai-client.js
CHANGED
|
@@ -98,7 +98,7 @@ export class AIClient {
|
|
|
98
98
|
/**
|
|
99
99
|
* 發送 prompt 並等待回應(帶重試機制和超時保護)
|
|
100
100
|
*/
|
|
101
|
-
static async sendAndWait(prompt, model = '
|
|
101
|
+
static async sendAndWait(prompt, model = 'gpt-5.6-luna', maxRetries = 3, timeout = 150000) {
|
|
102
102
|
AIClient.assertSupportedNodeVersion();
|
|
103
103
|
let lastError = null;
|
|
104
104
|
|
|
@@ -12,7 +12,7 @@ import { resolve } from 'path';
|
|
|
12
12
|
*/
|
|
13
13
|
const DEFAULT_CONFIG = {
|
|
14
14
|
ai: {
|
|
15
|
-
model: '
|
|
15
|
+
model: 'gpt-5.6-luna',
|
|
16
16
|
maxDiffLength: 8000,
|
|
17
17
|
maxRetries: 3,
|
|
18
18
|
},
|
|
@@ -193,7 +193,7 @@ function showHelp() {
|
|
|
193
193
|
|
|
194
194
|
選項:
|
|
195
195
|
--base <branch> 指定目標分支 (預設: 使用配置檔的 defaultBase 或自動偵測)
|
|
196
|
-
--model <model> 指定 AI 模型 (預設:
|
|
196
|
+
--model <model> 指定 AI 模型 (預設: gpt-5.6-luna)
|
|
197
197
|
--preview 僅預覽 PR 內容,不實際創建
|
|
198
198
|
--no-confirm 跳過確認直接創建
|
|
199
199
|
--auto-labels 自動添加 Labels (預設啟用)
|