ai-git-tools 2.1.10 → 2.1.12
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-git-tools",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.12",
|
|
4
4
|
"description": "AI-powered Git automation tools for commit messages and PR generation",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"chalk": "^5.3.0",
|
|
45
45
|
"commander": "^12.0.0",
|
|
46
46
|
"inquirer": "^9.2.0",
|
|
47
|
+
"opencc-js": "^1.4.2",
|
|
47
48
|
"ora": "^8.0.1"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { buildRedmineConfig } from '../redmine/config.js';
|
|
13
13
|
import { RedmineClient } from '../redmine/redmine-client.js';
|
|
14
14
|
import { Logger } from '../utils/logger.js';
|
|
15
|
+
import { toTraditionalChinese } from '../utils/traditional-chinese.js';
|
|
15
16
|
import {
|
|
16
17
|
applyRedmineDraft,
|
|
17
18
|
generateRedmineDraft,
|
|
@@ -32,7 +33,7 @@ function printProgress(progress) {
|
|
|
32
33
|
logger.step(`[${progress.index}/${progress.total}] 讀取 Redmine Issue #${progress.issueId}...`);
|
|
33
34
|
break;
|
|
34
35
|
case 'issue-read':
|
|
35
|
-
logger.success(`Issue #${progress.issueId} 已取得:${progress.subject || '無標題'}`);
|
|
36
|
+
logger.success(`Issue #${progress.issueId} 已取得:${toTraditionalChinese(progress.subject || '無標題')}`);
|
|
36
37
|
break;
|
|
37
38
|
case 'analyze-issue':
|
|
38
39
|
logger.step(`[${progress.index}/${progress.total}] 使用 Copilot 分析 Issue #${progress.issueId}...`);
|
|
@@ -161,7 +162,7 @@ export async function redmineUpdateCommand(options = {}) {
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
if (pullRequest) {
|
|
164
|
-
logger.success(`已找到 PR #${pullRequest.number}:${pullRequest.title || '無標題'}`);
|
|
165
|
+
logger.success(`已找到 PR #${pullRequest.number}:${toTraditionalChinese(pullRequest.title || '無標題')}`);
|
|
165
166
|
console.log(`🔗 ${pullRequest.url}`);
|
|
166
167
|
} else {
|
|
167
168
|
logger.info('目前 branch 沒有找到對應的 GitHub PR,略過 PR 資訊');
|
package/src/core/ai-client.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { CopilotClient, approveAll } from '@github/copilot-sdk';
|
|
7
|
+
import { toTraditionalChinese } from '../utils/traditional-chinese.js';
|
|
7
8
|
|
|
8
9
|
export class AIClient {
|
|
9
10
|
/**
|
|
@@ -75,7 +76,7 @@ export class AIClient {
|
|
|
75
76
|
const response = await Promise.race([responsePromise, timeoutPromise]);
|
|
76
77
|
|
|
77
78
|
const content = response?.data?.content || '';
|
|
78
|
-
return content.trim();
|
|
79
|
+
return toTraditionalChinese(content.trim());
|
|
79
80
|
} catch (error) {
|
|
80
81
|
lastError = error;
|
|
81
82
|
if (attempt < maxRetries) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AIClient } from '../core/ai-client.js';
|
|
2
|
+
import { normalizeAnalysisLanguage } from '../utils/traditional-chinese.js';
|
|
2
3
|
|
|
3
4
|
const ANALYSIS_FIELDS = [
|
|
4
5
|
'developmentSummary',
|
|
@@ -17,9 +18,11 @@ const ANALYSIS_FIELDS = [
|
|
|
17
18
|
function normalizeFlowcharts(value) {
|
|
18
19
|
const flowcharts = Array.isArray(value)
|
|
19
20
|
? value
|
|
20
|
-
: value
|
|
21
|
+
: typeof value === 'string'
|
|
21
22
|
? [{ source: value }]
|
|
22
|
-
:
|
|
23
|
+
: value
|
|
24
|
+
? [value]
|
|
25
|
+
: [];
|
|
23
26
|
|
|
24
27
|
const usedIds = new Set();
|
|
25
28
|
return flowcharts
|
|
@@ -39,7 +42,9 @@ function normalizeFlowcharts(value) {
|
|
|
39
42
|
return {
|
|
40
43
|
id: uniqueId,
|
|
41
44
|
title,
|
|
42
|
-
source: typeof flowchart.source
|
|
45
|
+
source: typeof (flowchart.source || flowchart.mermaid) === 'string'
|
|
46
|
+
? (flowchart.source || flowchart.mermaid).trim()
|
|
47
|
+
: '',
|
|
43
48
|
evidence: asStringArray(flowchart.evidence),
|
|
44
49
|
};
|
|
45
50
|
})
|
|
@@ -82,13 +87,15 @@ export function buildIssueAnalysisPrompt({ issue, evidence }) {
|
|
|
82
87
|
|
|
83
88
|
重要規則:
|
|
84
89
|
- 這次只分析指定的 Issue #${issue.id},不得把結果指派給其他 Issue。
|
|
90
|
+
- 所有自然語言欄位必須使用台灣繁體中文,禁止輸出簡體中文。
|
|
85
91
|
- 只能描述下方 Issue、Git commits、changed files、diff 或 PR evidence 中有明確證據的內容。
|
|
86
92
|
- 不得捏造 API endpoint、參數、檔案、function、class、測試或部署結果。
|
|
87
93
|
- requirementDescription 必須是一句話,說明這次為什麼修改。
|
|
88
94
|
- implementationDetails 必須整理 3~6 個實際完成事項。
|
|
89
95
|
- behaviorRules 必須使用 scenario/behavior 物件描述不同情境的行為。
|
|
90
96
|
- impactScope 必須描述受到影響的功能、API、頁面或模組。
|
|
91
|
-
-
|
|
97
|
+
- 只要 Issue 或 Git evidence 能推導出頁面、API、驗證或狀態之間的實際步驟,flowcharts 必須至少產生一張 Mermaid 流程圖,不得因為已有文字摘要而省略。
|
|
98
|
+
- 只有在證據不足以描述任何流程時,flowcharts 才能回傳空陣列;每張流程圖必須有穩定 id、title、source 與 evidence。
|
|
92
99
|
- 沒有明確測試或驗證證據時,verification 必須是空陣列。
|
|
93
100
|
- 不要產生 suggestedStatus、coverage 或 confidence,也不要決定 Redmine status。
|
|
94
101
|
|
|
@@ -125,7 +132,7 @@ requirementDescription 與 impactScope 必須是字串;implementationDetails
|
|
|
125
132
|
* @returns {object}
|
|
126
133
|
*/
|
|
127
134
|
export function parseIssueAnalysis(content, issueId) {
|
|
128
|
-
const parsed = AIClient.parseJSON(content);
|
|
135
|
+
const parsed = normalizeAnalysisLanguage(AIClient.parseJSON(content));
|
|
129
136
|
const result = { issueId };
|
|
130
137
|
|
|
131
138
|
for (const field of ANALYSIS_FIELDS) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import { normalizeAnalysisLanguage, toTraditionalChinese } from '../utils/traditional-chinese.js';
|
|
3
4
|
|
|
4
5
|
function bulletSection(title, items) {
|
|
5
6
|
if (!Array.isArray(items) || items.length === 0) return '';
|
|
@@ -169,18 +170,20 @@ export function formatRedmineDescription(description = '', source = null) {
|
|
|
169
170
|
* @returns {string}
|
|
170
171
|
*/
|
|
171
172
|
export function formatRedmineNote({ issue = {}, analysis = {}, evidence = {}, pullRequest = null }) {
|
|
173
|
+
const localizedIssue = normalizeAnalysisLanguage(issue);
|
|
174
|
+
const localizedAnalysis = normalizeAnalysisLanguage(analysis);
|
|
172
175
|
const sections = ['## 開發內容', ''];
|
|
173
176
|
const requirementDescription = formatRequirement(
|
|
174
|
-
|
|
177
|
+
localizedAnalysis.requirementDescription || localizedAnalysis.developmentSummary?.[0] || localizedIssue.description || ''
|
|
175
178
|
);
|
|
176
|
-
const implementationDetails = getImplementationDetails(
|
|
177
|
-
const behaviorRules = formatBehaviorRules(
|
|
179
|
+
const implementationDetails = getImplementationDetails(localizedAnalysis);
|
|
180
|
+
const behaviorRules = formatBehaviorRules(localizedAnalysis.behaviorRules);
|
|
178
181
|
const implementation = bulletSection('實作內容', implementationDetails);
|
|
179
|
-
const impactScope = paragraphSection('影響範圍',
|
|
180
|
-
const unresolved =
|
|
181
|
-
? `### 待確認\n${
|
|
182
|
+
const impactScope = paragraphSection('影響範圍', localizedAnalysis.impactScope);
|
|
183
|
+
const unresolved = localizedAnalysis.unresolvedItems?.length
|
|
184
|
+
? `### 待確認\n${localizedAnalysis.unresolvedItems.map(item => `- [ ] ${item}`).join('\n')}\n`
|
|
182
185
|
: '';
|
|
183
|
-
const flowcharts = formatFlowcharts(
|
|
186
|
+
const flowcharts = formatFlowcharts(localizedAnalysis.flowcharts);
|
|
184
187
|
|
|
185
188
|
sections.push(
|
|
186
189
|
paragraphSection('需求說明', requirementDescription),
|
|
@@ -217,8 +220,8 @@ export function formatRedmineNote({ issue = {}, analysis = {}, evidence = {}, pu
|
|
|
217
220
|
);
|
|
218
221
|
}
|
|
219
222
|
|
|
220
|
-
if (
|
|
221
|
-
sections.push(`分析依據:${
|
|
223
|
+
if (localizedAnalysis.evidence?.length) {
|
|
224
|
+
sections.push(`分析依據:${localizedAnalysis.evidence.join('、')}`, '');
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
return sections.filter((section, index) => section || index === 1).join('\n').trim();
|
|
@@ -295,8 +298,8 @@ export function formatRedminePreview(drafts = []) {
|
|
|
295
298
|
for (const draft of drafts) {
|
|
296
299
|
lines.push(
|
|
297
300
|
chalk.bold.cyan(`Issue #${draft.issueId}`),
|
|
298
|
-
`${chalk.dim('目前狀態:')} ${draft.currentStatusName || draft.originalStatusName || '—'}`,
|
|
299
|
-
`${chalk.green('更新狀態:')} ${draft.statusName || '未選擇'}`,
|
|
301
|
+
`${chalk.dim('目前狀態:')} ${toTraditionalChinese(draft.currentStatusName || draft.originalStatusName || '—')}`,
|
|
302
|
+
`${chalk.green('更新狀態:')} ${toTraditionalChinese(draft.statusName || '未選擇')}`,
|
|
300
303
|
draft.doneRatio !== undefined ? `${chalk.green('完成百分比:')} ${draft.doneRatio}%` : '',
|
|
301
304
|
draft.dueDate ? `${chalk.green('完成日期:')} ${draft.dueDate}` : '',
|
|
302
305
|
draft.customFields?.length
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import OpenCC from 'opencc-js';
|
|
2
|
+
|
|
3
|
+
const convertToTraditional = OpenCC.Converter({ from: 'cn', to: 'tw' });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 將文字轉換為台灣繁體中文
|
|
7
|
+
* @param {string} value
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
export function toTraditionalChinese(value) {
|
|
11
|
+
return typeof value === 'string' ? convertToTraditional(value) : value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 遞迴將資料中的所有字串轉換為台灣繁體中文
|
|
16
|
+
* @param {unknown} value
|
|
17
|
+
* @returns {unknown}
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeAnalysisLanguage(value) {
|
|
20
|
+
if (typeof value === 'string') return toTraditionalChinese(value);
|
|
21
|
+
if (Array.isArray(value)) return value.map(normalizeAnalysisLanguage);
|
|
22
|
+
if (value && typeof value === 'object') {
|
|
23
|
+
return Object.fromEntries(
|
|
24
|
+
Object.entries(value).map(([key, item]) => [key, normalizeAnalysisLanguage(item)])
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|