diff --git a/README.md b/README.md index c9a5518..fc4c053 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ - 🛡️ **智能降噪过滤**:自动剥离 `go.sum`、`package-lock.json`、`*.pb.go`、`*.gen.go` 等锁文件与自动生成代码,省 Token、防死循环。 - 🎯 **严谨架构师准则**:直奔主题,聚焦并发竞态、死锁、SQL 注入、空指针、资源泄露,并直接提供修复参考代码。 - 📊 **Token 消耗与费用实时估算**:实时捕获并统计输入/输出 Token 消耗,内置 2026 最新官方定价表精准核算每次审查成本(本地离线模型自动标注免费)。 +- ✨ **AI 智能 Commit Message**:一键根据代码变更生成规范的 Conventional Commits 提交说明,支持**中/英/日/韩**等多语言自适应。 - 📋 **团队规范适配(`.airules`)**:在项目根目录放置 `.airules`,AI 自动加载团队专属架构规范并最高优先级执行。 - 🪝 **一键 Pre-commit 钩子**:一行命令挂载 Git 提交前自动拦截,在代码上库前把好第一道质量关。 @@ -175,7 +176,28 @@ git air --provider deepseek --model deepseek-chat --- -### 3. 一键安装 Git Pre-commit 拦截钩子 +### 3. AI 智能生成规范 Commit Message (`git air msg`) + +写代码一时爽,写 Commit 抓耳挠腮?`git-air` 会根据你的暂存区改动,自动生成符合国际 Conventional Commits 规范的提交说明,并支持**多语言智能自适应**: + +```bash +# 1. 自动根据系统语言生成并交互式确认提交 +git air msg + +# 2. 生成英文提交说明(适合开源或国际化项目) +git air msg -l en + +# 3. 生成日语 / 韩语提交说明 +git air msg -l ja +git air msg -l ko + +# 4. 生成后无需确认直接执行 git commit +git air msg -c +``` + +--- + +### 4. 一键安装 Git Pre-commit 拦截钩子 在你的 Git 项目根目录下执行: ```bash @@ -195,8 +217,8 @@ git air hook uninstall ## ⚙️ 配置文件说明 `git-air` 支持多层配置覆盖,优先级从高到低为: -1. **命令行 Flags**(如 `--key`, `--model`, `--provider`, `--strict`, `--no-block`) -2. **环境变量**(`GIT_AIR_API_KEY`, `GIT_AIR_PROVIDER`, `GIT_AIR_MODEL`) +1. **命令行 Flags**(如 `--key`, `--model`, `--provider`, `--lang`, `--strict`, `--no-block`) +2. **环境变量**(`GIT_AIR_API_KEY`, `GIT_AIR_PROVIDER`, `GIT_AIR_MODEL`, `GIT_AIR_COMMIT_LANG`) 3. **项目级配置**(当前 Git 项目根目录下的 `config.yaml` 或 `.git-air.yaml`) 4. **全局用户配置**(`~/.git-air/config.yaml`) @@ -217,9 +239,12 @@ model: "gemini-3.7-flash" # 自定义基础提示词 (可选,留空则使用默认资深架构师准则) # custom_prompt: "" -# 自定义模型单价 (可选,单位: 美元/1M Tokens,用于精准核算自定义中转或企业私有接口费用) +# 自定义模型单价 (可选,单位: 美元/1M Tokens) # price_input: 0.75 # price_output: 3.75 + +# AI Commit Message 生成语言 (可选: auto, zh, en, ja, ko) +# commit_lang: "auto" ``` --- diff --git a/README_EN.md b/README_EN.md index d63d77e..4425e3d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -48,6 +48,7 @@ Completely isolated from your chat history, it acts like a **grumpy but sharp se - 🛡️ **Smart Noise Reduction**: Automatically strips out `go.sum`, `package-lock.json`, `*.pb.go`, and lockfiles to save tokens and eliminate review loops. - 🎯 **Strict Senior Architect Persona**: Zero corporate fluff. Directly flags concurrency deadlocks, SQL injections, nil dereferences, and resource leaks with copy-paste code patches. - 📊 **Real-time Token & Cost Estimation**: Accurately tracks prompt/completion token consumption with built-in official pricing rates (local offline models automatically marked free). +- ✨ **AI Commit Message Generator**: Automatically generates clean, standardized Conventional Commits messages with multi-language support (English, Japanese, Korean, Chinese). - 📋 **Team Rules Support (`.airules`)**: Drop an `.airules` file in your repo root, and the AI will prioritize your team-specific guidelines. - 🪝 **One-Click Pre-commit Hook**: Mounts as a local Git hook in one command to block dangerous bugs before code is committed. @@ -174,7 +175,26 @@ git air --provider deepseek --model deepseek-chat --- -### 3. One-Click Git Pre-commit Hook +### 3. AI-Powered Commit Message Generation (`git air msg`) + +Struggling to write concise commit messages? `git-air` inspects your staged changes and crafts a clean Conventional Commits message with **automatic language detection**: + +```bash +# 1. Generate based on your system locale & interactively commit +git air msg + +# 2. Explicitly specify target language (e.g. English, Japanese, Korean) +git air msg -l en +git air msg -l ja +git air msg -l ko + +# 3. Generate and directly commit without confirmation +git air msg -c +``` + +--- + +### 4. One-Click Git Pre-commit Hook Install the hook inside any Git repository: ```bash @@ -194,8 +214,8 @@ git air hook uninstall ## ⚙️ Configuration Hierarchy `git-air` searches for configurations in the following priority order: -1. **CLI Flags** (`--key`, `--model`, `--provider`, `--prompt`, `--price-input`, `--price-output`, `--strict`, `--no-block`) -2. **Environment Variables** (`GIT_AIR_API_KEY`, `GIT_AIR_PROVIDER`, `GIT_AIR_MODEL`) +1. **CLI Flags** (`--key`, `--model`, `--provider`, `--prompt`, `--lang`, `--price-input`, `--price-output`, `--strict`, `--no-block`) +2. **Environment Variables** (`GIT_AIR_API_KEY`, `GIT_AIR_PROVIDER`, `GIT_AIR_MODEL`, `GIT_AIR_COMMIT_LANG`) 3. **Project Config** (`./config.yaml` or `./.git-air.yaml` in repo root) 4. **Global User Config** (`~/.git-air/config.yaml`) @@ -215,6 +235,9 @@ model: "gemini-3.7-flash" # Custom Token Pricing (Optional, in USD per 1M tokens) # price_input: 0.75 # price_output: 3.75 + +# AI Commit Message Language (Optional: auto, zh, en, ja, ko) +# commit_lang: "auto" ``` --- diff --git a/README_JA.md b/README_JA.md index 5d930c0..092d2ff 100644 --- a/README_JA.md +++ b/README_JA.md @@ -48,6 +48,7 @@ GitHubなどのCI/CD上で動作するボットも優秀ですが、**`git-air` - 🛡️ **スマートなノイズ除去**:`go.sum` や `package-lock.json`、自動生成された `*.pb.go` を自動除外。Token消費を抑え、思考ループを防ぎます。 - 🎯 **厳格なシニアエンジニア基準**:お世辞や無駄口は一切なし。並行処理の競合、デッドロック、SQLインジェクション、NULLポインタ、リソースリークを指摘し、具体的な修正コードを直接提示します。 - 📊 **Token消費量と費用のリアルタイム見積もり**:入出力Token数を正確に集計し、各社最新の公式料金表に基づいて実行コストを即座に計算(ローカル完全オフラインモデルは無料表示)。 +- ✨ **AI コミットメッセージ自動生成**:変更差分から Conventional Commits 準拠の高品質なメッセージを生成(**日本語・英語・韓国語・中国語**に自動適応)。 - 📋 **チーム規約の読み込み(`.airules`)**:リポジトリ直下に `.airules` を配置するだけで、チーム固有のアーキテクチャ規約を最優先で適用。 - 🪝 **ワンクリックで Pre-commit フック化**:コマンド1発でGitフックとして登録。危険なコードのコミットを未然にブロックします。 @@ -64,46 +65,80 @@ $ git air ユーザー認証ハンドラーに Redis キャッシュ層を追加し、Context のタイムアウト伝播を修正。 #### 詳細レビュー結果 -- [BLOCKER] internal/service/user.go:45 - 重大なセキュリティ脆弱性: ループ内で生SQL文字列を直接結合しています。SQLインジェクションのリスクがあります。 - // 推奨修正コード: - db.Where("username = ?", inputName).First(&user) +- [BLOCKER] internal/service/user.go:45 - 重大なセキュリティè# 特定のファイルのみをレビュー +git air internal/service/chat.go -- [WARNING] internal/service/user.go:82 - 潜在的リスク: Redis 接続エラーを握りつぶしています。キャッシュ障害時にDBへ負荷が集中します。 - -- [WARNING] internal/service/user.go:103 - 並行処理の欠陥: 早期リターン時に Mutex の Unlock を忘れています(Mutex Leak)。デッドロックの危険性があります。 - -#### 結論 -- 判定: [REJECT] -- スコア: 60 / 100 - -📊 Token: 入力 1,243 / 出力 412 | ≈ $0.0025 -───────────────────────────────────────────────────────────────── +# 一時的にモデルやProviderを指定して実行 +git air --provider deepseek --model deepseek-chat ``` --- -## 📦 インストール +### 3. AI コミットメッセージ自動生成 (`git air msg`) -### 方法 1: Go Install(推奨) -```bash -go install github.com/unclesam-ly/git-air@latest -``` +コミットメッセージの作成に悩む必要はもうありません。`git-air` がステージングされた差分を分析し、Conventional Commits 準拠のメッセージを**多言語自動適応**で生成します: -### 方法 2: ソースコードからビルド ```bash -git clone https://github.com/unclesam-ly/git-air.git -cd git-air -go build -o git-air . -sudo mv git-air /usr/local/bin/ +# 1. システム言語を自動検出して生成 & 対話型コミット +git air msg + +# 2. 英語で生成(オープンソース・国際プロジェクト向け) +git air msg -l en + +# 3. 日本語 / 韓国語で生成 +git air msg -l ja +git air msg -l ko + +# 4. 確認なしで直接 git commit を実行 +git air msg -c ``` --- -## 🚀 クイックスタート +### 4. ワンタッチで Pre-commit フックを登録 -### 1. モデルと API Key の設定 +Git リポジトリ直下で以下を実行: +```bash +git air hook install +``` +- **コミット自動ブロック機能**:レビューで `[BLOCKER]` 重大欠陥や `[REJECT]` 判定が検出された場合、`git-air` は非ゼロの終了コードで**コミット(`git commit`)を自動的に中断・ブロック**します! +- **厳格モード (`--strict`)**:`[WARNING]` 警告でもコミットをブロックしたい場合は `git air --strict` を使用; +- **一時的なブロック解除**:緊急でコミットを強制したい場合は、Git 標準の `git commit --no-verify` または `git air --no-block` を付与します。 -`git-air` は国内外の主要LLMのエンドポイントと推奨モデルを標準プリセットしています。`--provider` を指定するだけで簡単に切り替えられます: +フックを解除する場合: +```bash +git air hook uninstall +``` + +--- + +## ⚙️ 設定の優先順位 + +1. **コマンドライン引数** (`--key`, `--model`, `--provider`, `--prompt`, `--lang`, `--price-input`, `--price-output`, `--strict`, `--no-block`) +2. **環境変数** (`GIT_AIR_API_KEY`, `GIT_AIR_PROVIDER`, `GIT_AIR_MODEL`, `GIT_AIR_COMMIT_LANG`) +3. **プロジェクト個別設定** (リポジトリ直下の `./config.yaml` または `./.git-air.yaml`) +4. **グローバル設定** (`~/.git-air/config.yaml`) + +### `config.yaml` 設定ファイル例: +```yaml +# プロバイダー: gemini, claude, grok, deepseek, qwen, zhipu, moonshot, siliconflow, ollama, openai, custom +provider: "gemini" +api_key: "YOUR_API_KEY_HERE" +model: "gemini-3.7-flash" + +# カスタム API エンドポイント (任意) +# base_url: "https://api.deepseek.com/v1" + +# カスタム System Prompt (任意) +# custom_prompt: "" + +# カスタム Token 料金設定 (任意、単位: 米ドル / 1M Tokens) +# price_input: 0.75 +# price_output: 3.75 + +# AI コミットメッセージ言語設定 (任意: auto, zh, en, ja, ko) +# commit_lang: "auto" +```LLMのエンドポイントと推奨モデルを標準プリセットしています。`--provider` を指定するだけで簡単に切り替えられます: ```bash # 1. Google Gemini (推奨: 高速・低コスト) diff --git a/cmd/config.go b/cmd/config.go index 9009024..484e646 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -12,13 +12,14 @@ import ( // AppConfig 全局配置结构体 type AppConfig struct { - Provider string `yaml:"provider"` // gemini, deepseek, ollama, openai, custom - APIKey string `yaml:"api_key"` // API Key - BaseURL string `yaml:"base_url"` // 自定义请求地址 - Model string `yaml:"model"` // 模型名称 - CustomPrompt string `yaml:"custom_prompt"` // 用户自定义的 Base System Prompt - PriceInput float64 `yaml:"price_input,omitempty"` // 自定义输入单价 (每 1M tokens 美元,可选) - PriceOutput float64 `yaml:"price_output,omitempty"` // 自定义输出单价 (每 1M tokens 美元,可选) + Provider string `yaml:"provider"` // gemini, deepseek, ollama, openai, custom + APIKey string `yaml:"api_key"` // API Key + BaseURL string `yaml:"base_url"` // 自定义请求地址 + Model string `yaml:"model"` // 模型名称 + CustomPrompt string `yaml:"custom_prompt"` // 用户自定义的 Base System Prompt + PriceInput float64 `yaml:"price_input,omitempty"` // 自定义输入单价 (每 1M tokens 美元,可选) + PriceOutput float64 `yaml:"price_output,omitempty"` // 自定义输出单价 (每 1M tokens 美元,可选) + CommitLang string `yaml:"commit_lang,omitempty"` // Commit Message 生成语言 (auto, zh, en, ja, ko 等) } func getConfigPath() (string, error) { @@ -77,6 +78,9 @@ func LoadConfig() (*AppConfig, error) { if envBaseURL := os.Getenv("GIT_AIR_BASE_URL"); envBaseURL != "" { cfg.BaseURL = envBaseURL } + if envLang := os.Getenv("GIT_AIR_COMMIT_LANG"); envLang != "" { + cfg.CommitLang = envLang + } return cfg, nil } @@ -104,6 +108,7 @@ var configSetCmd = &cobra.Command{ Short: "设置全局配置项", Example: ` git air config set --provider gemini --key YOUR_KEY git air config set --provider deepseek --key YOUR_KEY --model deepseek-chat + git air config set --commit-lang ja git air config set --provider ollama --model qwen2.5-coder`, Run: func(cmd *cobra.Command, args []string) { cfg, err := LoadConfig() @@ -134,6 +139,9 @@ var configSetCmd = &cobra.Command{ if po, _ := cmd.Flags().GetFloat64("price-output"); po > 0 { cfg.PriceOutput = po } + if cl, _ := cmd.Flags().GetString("commit-lang"); cl != "" { + cfg.CommitLang = cl + } if err := SaveConfig(cfg); err != nil { ui.PrintError("保存配置失败: %v", err) return @@ -168,6 +176,11 @@ var configGetCmd = &cobra.Command{ hasPrompt = "是 (已自定义)" } fmt.Printf("CustomPrompt: %s\n", hasPrompt) + commitLang := "auto (自动识别系统语言)" + if cfg.CommitLang != "" { + commitLang = cfg.CommitLang + } + fmt.Printf("CommitLang: %s\n", commitLang) if cfg.PriceInput > 0 || cfg.PriceOutput > 0 { fmt.Printf("CustomPrice: 输入 $%.4f/1M, 输出 $%.4f/1M (已自定义覆盖)\n", cfg.PriceInput, cfg.PriceOutput) } else { @@ -184,6 +197,7 @@ func init() { configSetCmd.Flags().String("prompt", "", "自定义 Base System Prompt") configSetCmd.Flags().Float64("price-input", 0, "自定义每 1M 输入 Token 美元价格") configSetCmd.Flags().Float64("price-output", 0, "自定义每 1M 输出 Token 美元价格") + configSetCmd.Flags().String("commit-lang", "", "Commit Message 语言 (auto, zh, en, ja, ko 等)") configCmd.AddCommand(configSetCmd) configCmd.AddCommand(configGetCmd) diff --git a/cmd/msg.go b/cmd/msg.go new file mode 100644 index 0000000..6025d80 --- /dev/null +++ b/cmd/msg.go @@ -0,0 +1,139 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" + "github.com/unclesam-ly/git-air/internal/git" + "github.com/unclesam-ly/git-air/internal/llm" + "github.com/unclesam-ly/git-air/internal/reviewer" + "github.com/unclesam-ly/git-air/internal/ui" +) + +var msgCmd = &cobra.Command{ + Use: "msg [git diff 参数,如 HEAD~1, main..feature, 文件名等]", + Aliases: []string{"commit-msg", "commit"}, + Short: "根据代码变更智能生成规范的 Conventional Commits 提交信息", + Example: ` git air msg # 根据当前暂存区代码自动生成 + git air msg -l ja # 指定生成日语提交说明 + git air msg -l en # 指定生成英文提交说明 + git air msg -c # 生成后直接执行 git commit`, + Run: func(cmd *cobra.Command, args []string) { + ctx := cmd.Context() + + // 1. 检查并获取 Diff (默认优先获取暂存区) + diff, err := git.GetDiff(ctx, args...) + if err != nil { + ui.PrintError("%v", err) + return + } + + // 2. 加载配置 + cfg, err := LoadConfig() + if err != nil { + ui.PrintError("加载配置失败: %v", err) + return + } + + if flagKey, _ := cmd.Flags().GetString("key"); flagKey != "" { + cfg.APIKey = flagKey + } + if flagModel, _ := cmd.Flags().GetString("model"); flagModel != "" { + cfg.Model = flagModel + } + if flagProvider, _ := cmd.Flags().GetString("provider"); flagProvider != "" { + cfg.Provider = flagProvider + } + + // 3. 初始化大模型客户端 + llmClient, err := llm.NewClient(llm.Config{ + Provider: cfg.Provider, + APIKey: cfg.APIKey, + BaseURL: cfg.BaseURL, + Model: cfg.Model, + PriceInput: cfg.PriceInput, + PriceOutput: cfg.PriceOutput, + }) + if err != nil { + ui.PrintError("初始化模型失败: %v", err) + return + } + + // 4. 确定目标语言 (命令行 Flag > 配置文件 > 自动探测系统语言) + lang, _ := cmd.Flags().GetString("lang") + if lang == "" { + lang = cfg.CommitLang + } + + repoRoot, _ := git.GetRepoRoot(ctx) + rev := reviewer.NewReviewer(llmClient, repoRoot, "") + + targetLang := reviewer.DetectLanguage(lang) + langDesc := "English" + switch targetLang { + case "zh": + langDesc = "简体中文" + case "ja": + langDesc = "日本語" + case "ko": + langDesc = "한국어" + } + + fmt.Printf("\033[1;36m[git-air]\033[0m 正在生成 Conventional Commit Message... \033[0;90m(Language: %s / Engine: %s)\033[0m\n", langDesc, cfg.Model) + fmt.Println("\033[0;90m" + strings.Repeat("─", 65) + "\033[0m") + + commitMsg, err := rev.GenerateCommitMsg(ctx, diff, lang) + if err != nil { + ui.PrintError("生成 Commit Message 失败: %v", err) + return + } + + // 5. 打印生成的提交信息 + fmt.Printf("\033[1;32m%s\033[0m\n", commitMsg) + fmt.Println("\033[0;90m" + strings.Repeat("─", 65) + "\033[0m") + + // 6. 是否自动执行提交 + autoCommit, _ := cmd.Flags().GetBool("commit") + if autoCommit { + executeGitCommit(commitMsg) + return + } + + // 交互式询问是否直接提交 + fmt.Print("? 是否直接以此信息执行 git commit?[Y/n]: ") + reader := bufio.NewReader(os.Stdin) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(strings.ToLower(input)) + + if input == "" || input == "y" || input == "yes" { + executeGitCommit(commitMsg) + } else { + fmt.Println("\033[0;90m已跳过提交。你可以复制上方内容手动提交。\033[0m") + } + }, +} + +func executeGitCommit(msg string) { + cmd := exec.Command("git", "commit", "-m", msg) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + ui.PrintError("执行 git commit 失败: %v", err) + return + } + ui.PrintSuccess("代码提交成功!🎉") +} + +func init() { + msgCmd.Flags().StringP("lang", "l", "", "生成语言 (auto, zh, en, ja, ko)") + msgCmd.Flags().BoolP("commit", "c", false, "生成后无需确认直接执行 git commit") + msgCmd.Flags().StringP("key", "k", "", "临时指定 API Key") + msgCmd.Flags().StringP("model", "m", "", "临时指定模型名称") + msgCmd.Flags().StringP("provider", "p", "", "临时指定 Provider") + + rootCmd.AddCommand(msgCmd) +} diff --git a/config.yaml b/config.yaml index e4800b5..07f0e77 100644 --- a/config.yaml +++ b/config.yaml @@ -37,4 +37,7 @@ custom_prompt: "" # 大模型厂商价格可能动态调整,请以实际价格为准 # 若你的中转平台价格有变或使用私有计费,可在此自定义覆盖: # price_input: 0.75 # 每 1M 输入 Token 美元单价 -# price_output: 3.75 # 每 1M 输出 Token 美元单价 \ No newline at end of file +# price_output: 3.75 # 每 1M 输出 Token 美元单价 + +# AI Commit Message 生成语言 (可选,默认 "auto" 自动跟随系统语言,可选: zh, en, ja, ko) +commit_lang: "auto" \ No newline at end of file diff --git a/internal/reviewer/msg.go b/internal/reviewer/msg.go new file mode 100644 index 0000000..e5d6812 --- /dev/null +++ b/internal/reviewer/msg.go @@ -0,0 +1,97 @@ +package reviewer + +import ( + "fmt" + "os" + "strings" +) + +// DetectLanguage 智能解析目标自然语言 +// 优先级:传入指定语言 -> 系统 LANG/LC_ALL 环境变量 -> 默认 "en" +func DetectLanguage(specifiedLang string) string { + lang := strings.ToLower(strings.TrimSpace(specifiedLang)) + if lang != "" && lang != "auto" { + return NormalizeLang(lang) + } + + // 尝试从系统环境变量中探测 + envLang := os.Getenv("LC_ALL") + if envLang == "" { + envLang = os.Getenv("LANG") + } + if envLang == "" { + envLang = os.Getenv("LC_MESSAGES") + } + + envLang = strings.ToLower(envLang) + if strings.HasPrefix(envLang, "zh") { + return "zh" + } else if strings.HasPrefix(envLang, "ja") { + return "ja" + } else if strings.HasPrefix(envLang, "ko") { + return "ko" + } + + return "en" +} + +// NormalizeLang 规范化语言缩写 +func NormalizeLang(lang string) string { + switch strings.ToLower(lang) { + case "zh", "cn", "chinese", "zh-cn", "zh-tw", "中文": + return "zh" + case "ja", "jp", "japanese", "ja-jp", "日本語", "日文": + return "ja" + case "ko", "kr", "korean", "ko-kr", "한국어", "韩文": + return "ko" + default: + return "en" + } +} + +// BuildCommitMsgPrompt 构建用于生成 Conventional Commits 的专业提示词 +func BuildCommitMsgPrompt(lang string) string { + langName := "English" + exampleSummary := "feat(auth): add Redis token caching and fix mutex deadlock" + exampleBullet1 := "- Introduce Redis caching layer for user authentication" + exampleBullet2 := "- Fix concurrency bug where mutex was not unlocked on early return" + + switch lang { + case "zh": + langName = "简体中文 (Simplified Chinese)" + exampleSummary = "feat(auth): 引入 Redis 缓存并修复 Mutex 死锁隐患" + exampleBullet1 = "- 在用户认证模块新增 Redis Token 缓存机制" + exampleBullet2 = "- 修复提前 return 时未释放互斥锁的并发隐患" + case "ja": + langName = "日本語 (Japanese)" + exampleSummary = "feat(auth): Redisキャッシュを追加し、Mutexのデッドロックを修正" + exampleBullet1 = "- ユーザー認証ハンドラーに Redis トークンキャッシュ層を導入" + exampleBullet2 = "- 早期リターン時に Mutex の Unlock が漏れる並行処理のバグを修正" + case "ko": + langName = "한국어 (Korean)" + exampleSummary = "feat(auth): Redis 캐시 도입 및 Mutex 데드락 버그 수정" + exampleBullet1 = "- 사용자 인증 핸들러에 Redis 토큰 캐시 계층 추가" + exampleBullet2 = "- 조기 반환 시 Mutex가 해제되지 않는 동시성 문제 해결" + } + + return fmt.Sprintf(`你是一位精通 Git 版本控制与 Conventional Commits 规范的资深架构师。 +你的任务是根据开发者提供的 Git Diff 增量代码,生成一条极其规范、精炼、清晰的 Git Commit Message。 + +### 规则要求: +1. 必须使用 Conventional Commits 规范:(): + - type 可选:feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert + - scope 为本次变动的核心模块或文件名(英文小写,如 auth, db, ui, config 等) +2. 【语言要求】:除 type(scope) 以外的主题与说明必须使用【%s】撰写! +3. 输出结构: + - 第一行:精炼的主题行(不超过 60 个字符,不带句号) + - 第二行:留空行 + - 第三行及之后(可选):使用 1~3 个无序列表项 (-) 简述核心改动要点 +4. 严禁任何 Markdown 代码块包裹,严禁任何客套话或多余解释,直接输出纯文本 Commit Message! + +### 示例参考: +%s + +%s +%s +`, langName, exampleSummary, exampleBullet1, exampleBullet2) +} diff --git a/internal/reviewer/msg_test.go b/internal/reviewer/msg_test.go new file mode 100644 index 0000000..5cd0236 --- /dev/null +++ b/internal/reviewer/msg_test.go @@ -0,0 +1,58 @@ +package reviewer + +import ( + "strings" + "testing" +) + +func TestDetectLanguage(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"zh", "zh"}, + {"ZH", "zh"}, + {"zh-CN", "zh"}, + {"中文", "zh"}, + {"ja", "ja"}, + {"ja-JP", "ja"}, + {"Japanese", "ja"}, + {"日本語", "ja"}, + {"ko", "ko"}, + {"ko-KR", "ko"}, + {"한국어", "ko"}, + {"en", "en"}, + {"en-US", "en"}, + {"English", "en"}, + {"fr", "en"}, // 未预置语言回退到 en + } + + for _, tt := range tests { + got := DetectLanguage(tt.input) + if got != tt.expected { + t.Errorf("DetectLanguage(%q) = %q, expected %q", tt.input, got, tt.expected) + } + } +} + +func TestBuildCommitMsgPrompt(t *testing.T) { + langs := []struct { + lang string + expected string + }{ + {"zh", "简体中文"}, + {"ja", "日本語"}, + {"ko", "한국어"}, + {"en", "English"}, + } + + for _, tt := range langs { + prompt := BuildCommitMsgPrompt(tt.lang) + if !strings.Contains(prompt, tt.expected) { + t.Errorf("BuildCommitMsgPrompt(%q) 应包含语言名称 %q", tt.lang, tt.expected) + } + if !strings.Contains(prompt, "Conventional Commits") { + t.Errorf("BuildCommitMsgPrompt(%q) 应包含 Conventional Commits 说明", tt.lang) + } + } +} diff --git a/internal/reviewer/reviewer.go b/internal/reviewer/reviewer.go index d2e3fd4..c751f98 100644 --- a/internal/reviewer/reviewer.go +++ b/internal/reviewer/reviewer.go @@ -40,3 +40,25 @@ func (r *Reviewer) Execute(ctx context.Context, diff string, stream bool, onChun result, err := r.client.Review(ctx, systemPrompt, diff) return result, nil, err } + +// GenerateCommitMsg 根据 Diff 智能生成符合 Conventional Commits 规范的提交信息 +func (r *Reviewer) GenerateCommitMsg(ctx context.Context, diff string, lang string) (string, error) { + if strings.TrimSpace(diff) == "" { + return "", fmt.Errorf("diff 内容为空,无法生成 Commit Message") + } + + targetLang := DetectLanguage(lang) + systemPrompt := BuildCommitMsgPrompt(targetLang) + + msg, err := r.client.Review(ctx, systemPrompt, diff) + if err != nil { + return "", err + } + + // 清理多余的代码块标记或前后空白 + cleanMsg := strings.TrimSpace(msg) + cleanMsg = strings.TrimPrefix(cleanMsg, "```gitcommit") + cleanMsg = strings.TrimPrefix(cleanMsg, "```") + cleanMsg = strings.TrimSuffix(cleanMsg, "```") + return strings.TrimSpace(cleanMsg), nil +}