ai-git-tools 1.0.5 → 2.0.0

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.
@@ -1,120 +1,136 @@
1
1
  /**
2
2
  * Commit 命令
3
- *
4
- * 為已 staged 的變更生成 commit message 並提交
3
+ * 基于 scripts/ai-auto-commit.mjs
4
+ * 自动生成 commit message 并执行 commit
5
5
  */
6
6
 
7
- import chalk from 'chalk';
8
- import { loadConfig } from '../core/config-loader.js';
9
- import { AIClient } from '../core/ai-client.js';
7
+ import { execSync } from 'child_process';
8
+ import { loadCommitConfig } from '../core/config-loader.js';
10
9
  import { GitOperations } from '../core/git-operations.js';
10
+ import { AIClient } from '../core/ai-client.js';
11
11
  import { Logger } from '../utils/logger.js';
12
- import {
13
- handleError,
14
- validateCommitMessage,
15
- truncateText,
16
- getProjectTypePrompt,
17
- getConventionalCommitsRules,
18
- } from '../utils/helpers.js';
12
+ import { cleanCommitMessage, validateCommitMessage, handleError, getProjectTypePrompt } from '../utils/helpers.js';
19
13
 
20
- /**
21
- * Commit 命令處理器
22
- */
23
- export async function commitCommand(options) {
24
- const logger = new Logger(options.verbose);
14
+ export async function commitCommand() {
15
+ const logger = new Logger();
25
16
 
26
17
  try {
27
- // 檢查是否在 Git 倉庫中
28
- if (!GitOperations.isGitRepository()) {
29
- logger.error('當前目錄不是 Git 倉庫');
30
- process.exit(1);
31
- }
18
+ // 载入配置
19
+ const config = await loadCommitConfig();
32
20
 
33
- // 載入配置
34
- const config = await loadConfig(options);
35
-
36
21
  if (config.output.verbose) {
37
- logger.debug('配置已載入');
38
- logger.debug(`AI Model: ${config.ai.model}`);
39
- logger.debug(`Max Diff Length: ${config.ai.maxDiffLength}`);
22
+ console.log('📋 使用配置:');
23
+ console.log(` AI Model: ${config.ai.model}`);
24
+ console.log(` Max Diff Length: ${config.ai.maxDiffLength}`);
25
+ console.log('');
40
26
  }
41
27
 
42
- // 檢查是否有 staged 變更
28
+ // 检查是否有 staged 变更
43
29
  const diff = GitOperations.getStagedDiff();
44
30
 
45
31
  if (!diff.trim()) {
46
- logger.error('沒有 staged 的變更');
47
- logger.info('請先使用 git add stage 你的變更');
32
+ logger.error('没有 staged 的变更');
33
+ console.log('💡 请先使用 git add stage 你的变更');
48
34
  process.exit(1);
49
35
  }
50
36
 
51
- logger.startSpinner('正在分析變更內容...');
37
+ logger.step('正在分析变更内容...\n');
52
38
 
53
- // 截斷過長的 diff
54
- const truncatedDiff = truncateText(diff, config.ai.maxDiffLength);
39
+ // 截断过长的 diff
40
+ const truncatedDiff =
41
+ diff.length > config.ai.maxDiffLength
42
+ ? diff.substring(0, config.ai.maxDiffLength) + '\n\n... [diff 过长已截断]'
43
+ : diff;
55
44
 
56
45
  if (config.output.verbose && diff.length > config.ai.maxDiffLength) {
57
- logger.debug(`Diff 已從 ${diff.length} 字元截斷至 ${config.ai.maxDiffLength} 字元`);
46
+ logger.warning(`Diff 已从 ${diff.length} 字元截断至 ${config.ai.maxDiffLength} 字元\n`);
58
47
  }
59
48
 
60
49
  // 使用 AI 生成 commit message
61
- const aiClient = new AIClient(config);
62
-
63
- logger.updateSpinner('AI 正在生成 commit message...');
50
+ let commitMessage = '';
51
+ let lastError = null;
52
+
53
+ for (let attempt = 1; attempt <= config.ai.maxRetries; attempt++) {
54
+ try {
55
+ if (config.output.verbose && attempt > 1) {
56
+ console.log(`🔄 重试第 ${attempt}/${config.ai.maxRetries} 次...\n`);
57
+ }
58
+
59
+ const prompt = `${getProjectTypePrompt()}
64
60
 
65
- const prompt = `${getProjectTypePrompt()}
61
+ 请根据以下 git diff 产生一则 commit message。
66
62
 
67
- 請根據以下 git diff 產生一則 commit message。
63
+ **Commit Message 规则**:
64
+ 1. 使用 Conventional Commits 格式:type(scope): subject
65
+ 2. type 必须是:feat/fix/docs/style/refactor/test/chore/perf 其中之一
66
+ 3. scope: 影响范围(如 member、report、auth、api、ui、config)
67
+ 4. subject 限制在 50 字内,使用繁体中文
68
+ 5. 如果变更复杂,加上 body 说明(使用 bullet points)
68
69
 
69
- ${getConventionalCommitsRules()}
70
+ **重要**:
71
+ - 直接输出 commit message 纯文字,不要使用 markdown 程式码区块(\`\`\`)
72
+ - 不要加上任何前缀说明或后缀文字
73
+ - 第一行是标题,如有需要可加上空行后的详细说明
70
74
 
71
- **範例格式**:
72
- feat(auth): 新增使用者登入功能
75
+ **范例格式**:
76
+ feat(member): 新增会员管理页面
73
77
 
74
- - 實作登入 API endpoint
75
- - 新增登入頁面 UI
76
- - 整合 JWT 認證機制
78
+ - 实作会员列表查询功能
79
+ - 新增会员资料编辑表单
80
+ - 整合 Zustand 状态管理
77
81
 
78
82
  git diff:
79
83
  ${truncatedDiff}`;
80
84
 
81
- const rawMessage = await aiClient.sendAndWait(prompt);
82
- await aiClient.stop();
83
-
84
- const commitMessage = AIClient.cleanResponse(rawMessage);
85
+ const response = await AIClient.sendAndWait(prompt, config.ai.model);
86
+ commitMessage = cleanCommitMessage(response);
87
+
88
+ // 验证 commit message
89
+ const validation = validateCommitMessage(commitMessage);
90
+ if (!validation.valid) {
91
+ throw new Error(`无效的 commit message: ${validation.reason}`);
92
+ }
93
+
94
+ // 成功生成,跳出重试循环
95
+ break;
96
+ } catch (error) {
97
+ lastError = error;
98
+ if (config.output.verbose) {
99
+ logger.warning(`尝试 ${attempt} 失败: ${error.message}\n`);
100
+ }
101
+
102
+ if (attempt < config.ai.maxRetries) {
103
+ continue;
104
+ }
105
+ }
106
+ }
85
107
 
86
- // 驗證 commit message
87
- const validation = validateCommitMessage(commitMessage);
88
- if (!validation.valid) {
89
- logger.failSpinner('生成 commit message 失敗');
90
- logger.error(validation.reason);
108
+ // 所有重试都失败
109
+ if (!commitMessage) {
110
+ logger.error('无法生成有效的 commit message');
111
+ if (lastError && config.output.verbose) {
112
+ console.log(` 最后错误: ${lastError.message}`);
113
+ }
114
+ console.log('\n💡 建议:');
115
+ console.log(' 1. 检查网路连线');
116
+ console.log(' 2. 尝试更换 AI 模型(使用 --model 参数)');
117
+ console.log(' 3. 确认变更内容不会太复杂或太大');
91
118
  process.exit(1);
92
119
  }
93
120
 
94
- logger.succeedSpinner('Commit message 生成完成');
121
+ logger.success('生成的 Commit Message:');
122
+ logger.separator('─', 60);
123
+ console.log(commitMessage);
124
+ logger.separator('─', 60);
95
125
 
96
- // 顯示生成的 commit message
97
- console.log(chalk.cyan('\n📝 生成的 Commit Message:'));
98
- logger.code(commitMessage);
99
-
100
- // 執行 commit
101
- logger.startSpinner('正在執行 commit...');
102
- await GitOperations.commit(commitMessage);
103
- logger.succeedSpinner('Commit 完成!');
104
-
105
- // 顯示最新的 commit
106
- console.log(chalk.cyan('\n📋 最新 commit:'));
107
- try {
108
- const recentCommits = GitOperations.getRecentCommits(1);
109
- if (recentCommits) {
110
- console.log(recentCommits);
111
- }
112
- } catch (error) {
113
- // 忽略顯示 commit 的錯誤
114
- }
126
+ // 执行 commit
127
+ logger.step('\n正在执行 commit...');
128
+ execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
129
+ stdio: 'inherit',
130
+ });
115
131
 
132
+ logger.success('Commit 完成!\n');
116
133
  } catch (error) {
117
- logger.failSpinner('操作失敗');
118
134
  handleError(error);
119
135
  process.exit(1);
120
136
  }
@@ -1,163 +1,65 @@
1
1
  /**
2
2
  * Init 命令
3
- *
4
- * 初始化配置檔案
3
+ * 初始化配置文件
5
4
  */
6
5
 
7
- import { existsSync, writeFileSync } from 'fs';
6
+ import { writeFileSync, existsSync } from 'fs';
8
7
  import { resolve } from 'path';
9
- import chalk from 'chalk';
10
- import inquirer from 'inquirer';
11
8
  import { Logger } from '../utils/logger.js';
12
- import { handleError } from '../utils/helpers.js';
13
9
 
14
- const CONFIG_TEMPLATE = `/**
15
- * AI Git Tools 配置檔
10
+ const DEFAULT_CONFIG = `/**
11
+ * AI Git Tools 配置文件
16
12
  *
17
- * 此配置檔用於:
18
- * - gitai commit:自動生成單個 commit
19
- * - gitai commit-all:智能分析並批量 commit
20
- * - gitai pr:自動生成 PR
21
- * - gitai workflow:完整工作流程
13
+ * 此文件用于配置 AI 自动化 Git 工具的行为
22
14
  */
15
+
23
16
  export default {
24
- // AI 設定
17
+ // AI 相关配置
25
18
  ai: {
26
- model: 'gpt-4.1', // AI 模型:gpt-4.1, claude-haiku-4.5 等
27
- maxDiffLength: 8000, // 最大 diff 長度(字元)
28
- maxRetries: 3, // API 失敗時的最大重試次數
19
+ model: 'gpt-4.1', // AI 模型
20
+ maxDiffLength: 8000, // 最大 diff 长度
21
+ maxRetries: 3, // 最大重试次数
29
22
  },
30
23
 
31
- // GitHub 設定(用於 PR 工具)
24
+ // GitHub 相关配置
32
25
  github: {
33
- orgName: null, // GitHub 組織名稱(自動從 git remote 取得)
34
- defaultBase: 'auto', // 預設目標分支:'auto' | 'main' | 'develop'
35
- autoLabels: true, // 自動添加 Labels
26
+ orgName: 'your-org-name', // GitHub 组织名称
27
+ defaultBase: 'auto', // 默认 base 分支('auto' 为自动侦测)
28
+ autoLabels: true, // 自动添加 Labels
36
29
  },
37
30
 
38
- // Reviewer 設定(用於 PR 工具)
31
+ // Reviewers 相关配置
39
32
  reviewers: {
40
- autoSelect: false, // 是否啟用 reviewer 選擇功能
41
- maxSuggested: 5, // 最多建議的 reviewers 數量
42
- gitHistoryDepth: 20, // Git 歷史分析深度
43
- excludeAuthors: [], // 排除特定作者,例如: ['bot@', 'ci-user']
33
+ autoSelect: false, // 自动选择 reviewers
34
+ maxSuggested: 5, // 最大建议 reviewers 数量
35
+ gitHistoryDepth: 20, // Git 历史深度
36
+ excludeAuthors: [], // 排除的作者列表
44
37
  },
45
38
 
46
- // 輸出設定
39
+ // 输出相关配置
47
40
  output: {
48
- verbose: false, // 顯示詳細輸出
49
- saveHistory: false, // 儲存操作歷史(未來功能)
41
+ verbose: false, // 详细输出
42
+ saveHistory: false, // 保存历史记录
50
43
  },
51
44
  };
52
45
  `;
53
46
 
54
- /**
55
- * Init 命令處理器
56
- */
57
47
  export async function initCommand() {
58
48
  const logger = new Logger();
49
+ const configPath = resolve(process.cwd(), '.ai-git-config.mjs');
59
50
 
60
- try {
61
- logger.header('初始化 AI Git Tools 配置檔');
62
-
63
- const configFiles = [
64
- '.ai-git-config.js',
65
- '.ai-git-config.mjs',
66
- 'ai-git.config.js',
67
- 'ai-git.config.mjs',
68
- ];
69
-
70
- // 檢查是否已存在配置檔
71
- const existingConfig = configFiles.find(file =>
72
- existsSync(resolve(process.cwd(), file))
73
- );
74
-
75
- if (existingConfig) {
76
- const { overwrite } = await inquirer.prompt([
77
- {
78
- type: 'confirm',
79
- name: 'overwrite',
80
- message: `配置檔 ${existingConfig} 已存在,是否覆蓋?`,
81
- default: false,
82
- },
83
- ]);
84
-
85
- if (!overwrite) {
86
- logger.info('已取消');
87
- process.exit(0);
88
- }
89
- }
90
-
91
- // 詢問配置檔名稱
92
- const { configFileName } = await inquirer.prompt([
93
- {
94
- type: 'list',
95
- name: 'configFileName',
96
- message: '選擇配置檔名稱:',
97
- choices: [
98
- { name: '.ai-git-config.js (推薦)', value: '.ai-git-config.js' },
99
- { name: '.ai-git-config.mjs', value: '.ai-git-config.mjs' },
100
- { name: 'ai-git.config.js', value: 'ai-git.config.js' },
101
- { name: 'ai-git.config.mjs', value: 'ai-git.config.mjs' },
102
- ],
103
- default: '.ai-git-config.js',
104
- },
105
- ]);
106
-
107
- // 詢問基本設定
108
- const { model, autoReviewers, autoLabels } = await inquirer.prompt([
109
- {
110
- type: 'list',
111
- name: 'model',
112
- message: '選擇 AI 模型:',
113
- choices: [
114
- { name: 'GPT-4.1 (推薦)', value: 'gpt-4.1' },
115
- { name: 'Claude Haiku 4.5', value: 'claude-haiku-4.5' },
116
- { name: 'Claude Sonnet 4.5', value: 'claude-sonnet-4.5' },
117
- ],
118
- default: 'gpt-4.1',
119
- },
120
- {
121
- type: 'confirm',
122
- name: 'autoReviewers',
123
- message: '啟用自動 Reviewer 選擇?',
124
- default: false,
125
- },
126
- {
127
- type: 'confirm',
128
- name: 'autoLabels',
129
- message: '啟用自動 Label 標記?',
130
- default: true,
131
- },
132
- ]);
133
-
134
- // 生成配置內容
135
- let configContent = CONFIG_TEMPLATE;
136
-
137
- if (model !== 'gpt-4.1') {
138
- configContent = configContent.replace("model: 'gpt-4.1'", `model: '${model}'`);
139
- }
140
-
141
- if (autoReviewers) {
142
- configContent = configContent.replace('autoSelect: false', 'autoSelect: true');
143
- }
144
-
145
- if (!autoLabels) {
146
- configContent = configContent.replace('autoLabels: true', 'autoLabels: false');
147
- }
148
-
149
- // 寫入配置檔
150
- const configPath = resolve(process.cwd(), configFileName);
151
- writeFileSync(configPath, configContent, 'utf-8');
152
-
153
- logger.success(`配置檔已創建: ${configFileName}`);
154
- logger.info('\n下一步:');
155
- logger.info(' 1. 編輯配置檔以自訂設定');
156
- logger.info(' 2. 執行 gitai commit 或 gitai commit-all 開始使用');
157
- logger.info('\n查看完整文檔: https://github.com/yiso05255/ai-git-tools');
51
+ if (existsSync(configPath)) {
52
+ logger.warning('配置文件已存在: .ai-git-config.mjs');
53
+ console.log('如要重新初始化,请先删除现有配置文件');
54
+ return;
55
+ }
158
56
 
57
+ try {
58
+ writeFileSync(configPath, DEFAULT_CONFIG, 'utf-8');
59
+ logger.success('已创建配置文件: .ai-git-config.mjs');
60
+ console.log('\n请编辑此文件以自定义配置');
159
61
  } catch (error) {
160
- handleError(error);
62
+ logger.error(`创建配置文件失败: ${error.message}`);
161
63
  process.exit(1);
162
64
  }
163
65
  }