ai-git-tools 2.1.8 → 2.1.10

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 CHANGED
@@ -372,6 +372,33 @@ Issue 的基本責任分工如下:`description` 保存原始需求與未來其
372
372
 
373
373
  同一個 Issue 若已經有先前的 notes,後續再次執行更新仍會新增一筆新的 Redmine journal,不會因為已有 notes 而略過;只有 Issue 的需求內容或狀態在產生草稿後被其他人修改時,才會停下來要求重新確認。
374
374
 
375
+ AI 產出的 notes 會固定整理成以下架構:
376
+
377
+ ```markdown
378
+ ## 開發內容
379
+
380
+ ### 需求說明
381
+ 一句話說明這次為什麼修改。
382
+
383
+ ### 實作內容
384
+ - 實際完成事項
385
+ - 實際完成事項
386
+ - 實際完成事項
387
+
388
+ ### 行為規則
389
+ | 情境 | 行為 |
390
+ |---|---|
391
+ | ... | ... |
392
+
393
+ ### 影響範圍
394
+ 受影響的功能、API、頁面或模組。
395
+
396
+ ### 待確認
397
+ - [ ] AI 無法確認的項目
398
+ ```
399
+
400
+ 實作內容最多顯示 6 個項目;沒有待確認事項時不會建立空的 `待確認` 區塊。Git 與流程圖區塊會維持既有格式。
401
+
375
402
  ## ⚙️ 配置
376
403
 
377
404
  配置檔案範例(\`.ai-git-config.js\`):
package/bin/cli.js CHANGED
@@ -99,7 +99,7 @@ registerCommand(program, 'redmine-update', '分析並更新 Redmine Issue 的開
99
99
  { flags: '--output <file>', description: '保存 preview 草稿 JSON' },
100
100
  { flags: '--apply', description: '套用已審核的草稿' },
101
101
  { flags: '--from <file>', description: '指定要套用的 preview 草稿 JSON' },
102
- { flags: '--force', description: '強制略過衝突與重複同步檢查' },
102
+ { flags: '--force', description: '強制略過 Issue 狀態或內容衝突檢查' },
103
103
  ], redmineUpdateCommand);
104
104
 
105
105
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-git-tools",
3
- "version": "2.1.8",
3
+ "version": "2.1.10",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -3,6 +3,8 @@ import { AIClient } from '../core/ai-client.js';
3
3
  const ANALYSIS_FIELDS = [
4
4
  'developmentSummary',
5
5
  'requirementDescription',
6
+ 'implementationDetails',
7
+ 'impactScope',
6
8
  'codeChanges',
7
9
  'apiChanges',
8
10
  'technicalDetails',
@@ -82,11 +84,12 @@ export function buildIssueAnalysisPrompt({ issue, evidence }) {
82
84
  - 這次只分析指定的 Issue #${issue.id},不得把結果指派給其他 Issue。
83
85
  - 只能描述下方 Issue、Git commits、changed files、diff 或 PR evidence 中有明確證據的內容。
84
86
  - 不得捏造 API endpoint、參數、檔案、function、class、測試或部署結果。
87
+ - requirementDescription 必須是一句話,說明這次為什麼修改。
88
+ - implementationDetails 必須整理 3~6 個實際完成事項。
89
+ - behaviorRules 必須使用 scenario/behavior 物件描述不同情境的行為。
90
+ - impactScope 必須描述受到影響的功能、API、頁面或模組。
91
+ - 若能從實際流程推導出可靠的流程,才產生 flowcharts;每張流程圖必須有穩定 id、title、source 與 evidence。
85
92
  - 沒有明確測試或驗證證據時,verification 必須是空陣列。
86
- - 沒有可靠 API 變更證據時,apiChanges 必須是空陣列。
87
- - requirementDescription 應整理指定 Issue 的需求重點;behaviorRules 使用 scenario/behavior 物件陣列。
88
- - 若能從實際流程推導出可靠的複製流程,才產生 flowcharts;否則為空陣列。每張流程圖必須有穩定、描述用途的 id,source 必須以 graph TD 開始,不要加入 Mermaid wrapper 或 code fence。
89
- - developmentSummary、codeChanges、apiChanges、technicalDetails 中的程式路徑、API path、function 與 class 使用雙反引號標示;需要多行程式片段時使用 code block。
90
93
  - 不要產生 suggestedStatus、coverage 或 confidence,也不要決定 Redmine status。
91
94
 
92
95
  ## 指定 Redmine Issue
@@ -100,6 +103,8 @@ ${JSON.stringify(evidence, null, 2)}
100
103
  "issueId": ${issue.id},
101
104
  "developmentSummary": [],
102
105
  "requirementDescription": "",
106
+ "implementationDetails": [],
107
+ "impactScope": "",
103
108
  "behaviorRules": [],
104
109
  "codeChanges": [],
105
110
  "apiChanges": [],
@@ -110,8 +115,7 @@ ${JSON.stringify(evidence, null, 2)}
110
115
  "unresolvedItems": [],
111
116
  "evidence": []
112
117
  }
113
-
114
- developmentSummary、codeChanges、apiChanges、technicalDetails、modifiedFiles、verification、unresolvedItems 與 evidence 必須是字串陣列;requirementDescription 必須是字串;behaviorRules 必須是包含 scenario 與 behavior 字串的物件陣列;flowcharts 必須是包含 id、title、source 與 evidence 的物件陣列。`;
118
+ requirementDescription 與 impactScope 必須是字串;implementationDetails 必須是 3~6 個字串的陣列;behaviorRules 必須是包含 scenario 與 behavior 字串的物件陣列;flowcharts 必須是包含 id、title、source 與 evidence 的物件陣列;其它清單欄位必須是字串陣列。`;
115
119
  }
116
120
 
117
121
  /**
@@ -125,7 +129,7 @@ export function parseIssueAnalysis(content, issueId) {
125
129
  const result = { issueId };
126
130
 
127
131
  for (const field of ANALYSIS_FIELDS) {
128
- result[field] = field === 'requirementDescription'
132
+ result[field] = field === 'requirementDescription' || field === 'impactScope'
129
133
  ? (typeof parsed[field] === 'string' ? parsed[field].trim() : '')
130
134
  : asStringArray(parsed[field]);
131
135
  }
@@ -137,7 +137,7 @@ export async function applyRedmineDraft({ draft, client, force = false, onProgre
137
137
  for (const [index, item] of draft.drafts.entries()) {
138
138
  try {
139
139
  onProgress({ phase: 'apply-issue', index: index + 1, total: draft.drafts.length, issueId: item.issueId });
140
- const current = await client.getIssue(item.issueId, { include: 'allowed_statuses,journals' });
140
+ const current = await client.getIssue(item.issueId, { include: 'allowed_statuses' });
141
141
 
142
142
  const statusChanged = item.originalStatusId !== null && current.status?.id !== item.originalStatusId;
143
143
  const descriptionChanged = current.description !== item.originalDescription;
@@ -11,6 +11,23 @@ function paragraphSection(title, content) {
11
11
  return `### ${title}\n\n${content}\n`;
12
12
  }
13
13
 
14
+ function formatRequirement(content) {
15
+ if (!content) return '';
16
+ return content.replace(/\s+/g, ' ').trim();
17
+ }
18
+
19
+ function getImplementationDetails(analysis = {}) {
20
+ if (analysis.implementationDetails?.length) {
21
+ return analysis.implementationDetails.slice(0, 6);
22
+ }
23
+
24
+ return [
25
+ ...(analysis.codeChanges || []),
26
+ ...(analysis.apiChanges || []),
27
+ ...(analysis.technicalDetails || []),
28
+ ].slice(0, 6);
29
+ }
30
+
14
31
  function formatBehaviorRules(rules = []) {
15
32
  if (!Array.isArray(rules) || rules.length === 0) return '';
16
33
  const rows = rules
@@ -148,59 +165,50 @@ export function formatRedmineDescription(description = '', source = null) {
148
165
 
149
166
  /**
150
167
  * 格式化 Redmine Issue note
151
- * @param {{issueId: number|string, analysis: object, evidence: object, pullRequest?: object|null, syncId?: string}} input
168
+ * @param {{issue?: object, analysis?: object, evidence?: object, pullRequest?: object|null}} input
152
169
  * @returns {string}
153
170
  */
154
171
  export function formatRedmineNote({ issue = {}, analysis = {}, evidence = {}, pullRequest = null }) {
155
172
  const sections = ['## 開發內容', ''];
156
- const development = Array.isArray(analysis.developmentSummary)
157
- ? analysis.developmentSummary.join('\n\n')
158
- : analysis.developmentSummary || '';
159
- const requirementDescription = analysis.requirementDescription || issue.description || '';
160
- const behaviorRules = formatBehaviorRules(analysis.behaviorRules);
161
- const codeChanges = bulletSection('程式修改重點', analysis.codeChanges);
162
- const apiChanges = bulletSection('API 變更', analysis.apiChanges);
163
- const technicalDetails = bulletSection('重要技術細節', analysis.technicalDetails);
164
- const modifiedFiles = bulletSection(
165
- '修改檔案',
166
- analysis.modifiedFiles?.length
167
- ? analysis.modifiedFiles.map(file => `\`\`${file}\`\``)
168
- : evidence.changedFiles?.map(file => `\`\`${file}\`\``)
173
+ const requirementDescription = formatRequirement(
174
+ analysis.requirementDescription || analysis.developmentSummary?.[0] || issue.description || ''
169
175
  );
170
- const verification = bulletSection('驗證結果', analysis.verification);
176
+ const implementationDetails = getImplementationDetails(analysis);
177
+ const behaviorRules = formatBehaviorRules(analysis.behaviorRules);
178
+ const implementation = bulletSection('實作內容', implementationDetails);
179
+ const impactScope = paragraphSection('影響範圍', analysis.impactScope);
171
180
  const unresolved = analysis.unresolvedItems?.length
172
181
  ? `### 待確認\n${analysis.unresolvedItems.map(item => `- [ ] ${item}`).join('\n')}\n`
173
182
  : '';
174
183
  const flowcharts = formatFlowcharts(analysis.flowcharts);
175
184
 
176
185
  sections.push(
177
- development,
178
186
  paragraphSection('需求說明', requirementDescription),
187
+ implementation,
179
188
  behaviorRules,
180
- modifiedFiles,
181
- codeChanges,
182
- apiChanges,
183
- technicalDetails,
184
- flowcharts,
185
- verification
189
+ impactScope,
190
+ unresolved,
191
+ flowcharts
186
192
  );
187
- sections.push(unresolved);
188
193
 
189
194
  if (evidence.currentBranch || evidence.baseBranch || pullRequest) {
190
195
  const branch = evidence.currentBranch || '—';
191
- const branchUrl = evidence.repository
196
+ const branchUrl = evidence.repository && branch !== '—'
192
197
  ? `https://github.com/${evidence.repository}/tree/${encodeURI(branch)}`
193
198
  : '';
199
+ const base = evidence.baseBranch || '—';
200
+ const baseUrl = evidence.repository && base !== '—'
201
+ ? `https://github.com/${evidence.repository}/tree/${encodeURI(base)}`
202
+ : '';
194
203
  const gitLines = [
195
204
  '### Git',
196
205
  '',
197
206
  `- Branch:${branchUrl ? `[${branch}](${branchUrl})` : `\`\`${branch}\`\``}`,
198
- `- Base:${branchUrl ? `[${evidence.baseBranch || '—'}](https://github.com/${evidence.repository}/tree/${encodeURI(evidence.baseBranch || '')})` : evidence.baseBranch || '—'}`,
207
+ `- Base:${baseUrl ? `[${base}](${baseUrl})` : base}`,
199
208
  ];
200
209
  if (pullRequest) {
201
210
  gitLines.push(
202
- `- PR:${pullRequest.url ? `[${pullRequest.url}](${pullRequest.url})` : `#${pullRequest.number}`}`,
203
- `- PR 狀態:${pullRequest.state || '—'}${pullRequest.mergedAt ? `(合併於 ${pullRequest.mergedAt})` : ''}`
211
+ `- PR:${pullRequest.url ? `[${pullRequest.url}](${pullRequest.url})` : `#${pullRequest.number}`}`
204
212
  );
205
213
  }
206
214
  sections.push(