feat(cli): 新增 AI 智能 Commit Message 生成与多语言支持
- 新增 `git air msg` 命令,支持根据暂存区或历史 Diff 自动生成 Conventional Commits 提交说明 - 支持中、英、日、韩多语言自适应及通过 CLI/配置文件/环境变量进行定制 - 完善全局配置体系,新增 `commit_lang` 选项及相关单元测试
This commit is contained in:
1 parent
4d9674831b
commit
6eda653db6
9 files changed
+457
-41
No files matched your search
@@ -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>(<scope>): <subject>
|
||||
- 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user