
1. AI 生成提交消息的真正问题不是“写不出来”而是“写出来没人敢用”先说个我自己的真实经历。有段时间团队里为了省事让每个人提交代码时都用 AI 生成 commit message结果 code review 的时候差点吵起来。倒不是说 AI 写得多烂恰恰相反它写得非常“像样”——格式工整、动词标准、甚至还会自动加 emoji。但问题就出在这个“像样”上commit message 这种东西单纯“语法正确”毫无意义它必须跟你的代码改动、项目阶段、团队约定精确对齐。AI 默认生成的提交消息经常是那种你扫一眼觉得没问题真出问题去翻 git log 时发现完全对不上的状态。这个场景你可能也遇到过修复一个登录 bug 的改动AI 生成的消息是feat: 增加用户登录功能改了一个配置文件的缩进它给你写个refactor: 优化配置文件结构跨模块重构了十几个文件它居然只概括了第一个文件的改动。更气人的是你让它按项目规范来它会用一套看起来很规范但实际上跟你项目毫无关系的模板糊弄你。这就是我写这篇文章的动机。今天想聊的核心不是“怎么让 AI 帮你写提交消息”而是怎么做到真正的定制、可控、项目级优化——让 AI 生成的每一条提交消息都像你们团队里最资深那个工程师手写的一样。先给一个结论凡是只用一句话提示词、没有任何约束和校验的 AI 提交消息方案都是不可用的。真正能落地的方案至少需要四层结构模板层控制格式上下文层喂足信息参数层压制随机性校验层拦住幻觉。下面逐一拆开讲。2. 定制模板把“帮我写提交信息”升级成结构化生成任务2.1 为什么默认 prompt 生成的提交消息总是差点意思你直接跟 AI 说“帮我写一条 commit message”它能给你生成出花来。但问题在于提交消息本身是一种高度格式化的文本它服务于自动化工具如 changelog 生成器、服务于 code review、服务于后期追溯。如果你不把格式要求讲死AI 就会自由发挥而自由发挥的产物通常很“通用”——拿出去没有任何毛病放在哪个项目里也都不违和但放在哪个项目里都不精确。这就好比你去餐厅点菜跟厨师说“随便来道拿手的”端上来必然是经典菜式但如果是给糖尿病患者点的那这菜再好吃也不合格。项目里的提交规范就是这个“忌口清单”你不提前告诉 AI它永远不会主动知道。所以定制的前提是把你的提交规范对象化、模板化而不是靠一句 prompt 去“教育”模型。2.2 模板变量把 diff、分支、issue 全部喂进生成上下文一条合格提交消息的产生依赖的上下文远比大多数人想象的多。除了最基本的 git diff还需要当前分支名、关联的 issue 编号、本次改动的文件清单、可能还有 CI 状态或者需求单描述。这些信息如果 AI 看不到它就只能靠猜猜出来的结果自然不可控。我以前看很多 AI commit 工具的源码发现它们有个通病只把 diff 文本截断后扔给模型。这对小型改动还行一旦 diff 超过几百行模型根本处理不过来最后生成出来的东西就是在开头几行改动上“炒冷饭”。真正可用的方案应该是一个结构化的上下文对象包含commit_context { branch: fix/login-session-expire, diff_stat: 3 files changed, 42 -15, files: [src/auth/session.py, src/auth/utils.py, tests/test_auth.py], issues: [#IN-237], diff_content: truncated_diff, # 超过限制时按文件分段摘要 recent_commits: [fix(api): 修复 token 续期竞态条件], user_hint: 本次改动来自线上 session 过期 bug 修复 }把这些变量塞进模板里之后AI 生成的提交消息就不再是“无源之水”它能看到这次改动的前因后果。2.3 一个可以直接抄的模板文件下面是我在这套方案里一直在用的模板骨架简化版用的是 Jinja2 语法你在构建自己的工具时可以照这个思路来你是一名资深工程师正在为一次代码改动编写 Git 提交消息。 ## 项目约定 - 提交消息必须遵循 Conventional Commits 规范 - 可用的 type 仅为feat, fix, docs, style, refactor, perf, test, build, ci, chore - 可用 scope 包括{{ scopes | join(, ) }} - 语言简体中文主体内容用中文描述type/scope 保留英文 - 首行不超过 72 个字符 ## 当前改动上下文 - 分支{{ branch }} - 改动统计{{ diff_stat }} - 涉及文件{{ files | join(, ) }} - 关联 issue{{ issues | join(, ) if issues else 无 }} - 最近提交风格参考 {{ recent_commits | map(indent, ) | join(\n) }} ## 用户补充说明 {{ user_hint if user_hint else 无 }} ## 本次 diff 内容可能被截断{{ diff_content }}## 输出要求 严格按照以下格式输出不要输出任何其他内容 type(scope): subject body如果必要注意几个细节scope 必须是我显式传入项目里真实存在的模块名单不给模型任何自由发挥的空间最近提交风格是拿来当 few-shot 示例用的这样生成的风格能跟项目最近的历史对齐用户补充说明是一个可选输入让开发者可以在提交前告诉 AI 这次改动的真实意图比如“这是针对线上 session 过期的紧急修复”。2.4 分场景切换模板不是所有提交都长一个样日常提交和紧急修复的提交格式侧重完全不一样。日常提交可以有详细正文紧急修复则应该更简洁但带上 hotfix 标记。我的做法是维护一个模板目录按场景切换default.j2常规提交包含正文和脚注hotfix.j2紧急修复强制在 subject 里带[hotfix]前缀省略正文要求附上 issue 编号merge.j2合并分支只输出简单的 merge 描述不分析 diffdocs.j2只改文档的情况跳过代码分析直接描述文档变更范围切换逻辑也不复杂就根据分支名和 diff_stat 里的文件列表做几个规则判断。比如分支名以hotfix/开头或改动文件里包含src/下超过 20 个文件就自动走 hotfix 模板。这样 AI 提交消息的“定制”就从一句话 prompt 变成了可按场景自动适配的工程系统。3. 可控的关键把 AI 的自由发挥空间压到最低3.1 系统提示词给 AI 划死边界很多人以为“可控”就是调低 temperature但实际上提交消息这种任务的随机性主要不是来自采样参数而是来自模型自己对任务的“理解偏移”。同一个模板GPT-4o 和 Claude 的产出风格可能差异很大同一个模型换一种说法描述需求出来的格式也可能对不上。真正稳妥的做法是在系统提示词层面把这个任务定义成“信息抽取 格式化”而不是“创作”。我经过多轮实验后系统提示词固定成下面这个版本效果稳定很多你是一个提交消息生成器不是聊天助手不是代码顾问。你的唯一任务是从给定的 diff 和上下文信息中提取关键变更并转换为符合规范的提交消息。不要解释你的输出不要添加任何额外建议不要输出 markdown 代码块包裹。如果 diff 为空或无法判断输出 NO_CHANGE_DETECTED。这段提示词有三个关键设计第一明确“不是聊天助手”——消除模型“要多说几句表现自己”的倾向第二“不要添加建议”——避免模型在提交消息里夹带私货第三加了空 diff 的处理分支——避免模型在无内容可分析时强行编造。3.2 采样参数提交消息的 temperature 应该接近零提交消息生成和写文案、写诗不一样它追求的是“稳定复现相同的输出”而不是“每次都有新意”。所以在 API 调用参数上我建议参数推荐值说明temperature0.1 - 0.2越高越容易跑偏0.1 就能满足大部分“格式化提取”任务top_p0.9配合低 temperature 使用进一步降低随机性max_tokens300提交消息不需要长文本防止模型啰嗦presence_penalty0不需要鼓励模型使用新词frequency_penalty0不需要鼓励模型避免重复用词有些模型接口还支持stop参数可以设置成[\n\n]强制模型在输出一个空行后停止生成——这样能有效防止模型在提交消息结束时画蛇添足写一堆解释。你可能会问temperature 0.1 和 0 有没有区别实测下来有。真正设置为 0 时部分模型 API 会直接走贪心解码这时如果提示词稍微不明确模型会在同一处“稳定地犯错”反而难发现而 0.1 左右保留了一点点采样空间至少每次输出会有一点变化不容易被“稳定地骗过去”。对提交消息这个场景我推荐 0.1。3.3 输出后校验用 commitlint 规则做硬性拦截前面说的都是“让 AI 尽量生成对”但如果 AI 真的生成了不合规的结果你怎么办答案是不要靠肉眼检查要在生成之后立刻做一次机器校验。我直接用 commitlint 那套规则来校验 AI 的产出。即使你的工具不是基于 Node.js 的也可以把校验逻辑单独抽出来用// aigit-check.js const commitlint require(commitlint/cli); const rules { type-enum: [2, always, [feat, fix, docs, style, refactor, perf, test, build, ci, chore]], scope-enum: [2, always, [api, web, mobile, core]], subject-max-length: [2, always, 72], body-max-line-length: [2, always, 200], type-case: [2, always, lower-case], scope-case: [2, always, lower-case] }; module.exports { rules };校验不通过时自动把消息打回带着校验错误信息重新生成一次。如果重试两轮仍然失败就放弃 AI 生成直接让开发者手写。这里有一个原则校验失败时宁可退回人工也不能把 AI 的错误产物直接提交否则这个工具就是在给代码仓库制造噪音。3.4 失败与重试AI 不听话时的降级策略我一开始做这套东西的时候就吃过“无限重试”的亏。模型连续失败 6 次每次都生成格式不对的提交消息但工具一直重试最后浪费了将近两分钟的 API 调用时间才被用户手动打断。后来我学乖了设了固定重试策略第一次生成失败带着校验错误信息重试一次并在用户提示词里追加“请参考以下错误修正{error}”第二次生成失败切换成更严格的模板去掉正文要求、强制走最短格式再试一次第三次失败直接输出错误消息“AI 生成失败请手动编写提交消息”并自动打开编辑器让用户手写这个降级策略里最有用的是第二步——切换成“最短格式”。很多时候模型输出的问题都出在正文部分比如正文太长、包含特殊字符、引用格式不对去掉正文之后失败率会大幅下降。它相当于给 AI 留了一条“退路”而这条退路仍然是合规定义的。4. 项目级优化让不同仓库长出各自的提交“基因”4.1 为什么需要项目级配置提交规范从来不是全局统一的东西你会发现一个很现实的问题提交消息规范这东西换一个项目就完全不一样。有的仓库用 Conventional Commits有的仓库就简单一句描述有的项目 scope 分得很细比如 mobile 项目里分成 ios、android、shared有的项目根本没有 scope有的团队要求 commit 必须关联 JIRA issue有的团队完全不 care。如果你搞一套“全局 AI 提交提示词”放到不同项目里使用效果必然像穿了不合脚的鞋——代码里定义了一堆 scope 和 typeAI 生成时用错了也没人知道。这也是为什么我认为提交消息的 AI 优化必须落到项目维度而不是个人全局配置。4.2 配置文件方案把提交规范从“人脑”搬进仓库我的做法是在仓库根目录下维护一个.aigit.yml配置文件由提交消息工具在运行时自动读取。这个设计的好处很直接规范跟着仓库走换电脑、换同事、CI 环境跑都不用重新交代。# .aigit.yml version: 1 repo: types: [feat, fix, docs, style, refactor, perf, test, build, ci, chore] scopes: api: 后端 API 层 web: 前端页面层 mobile: 移动端 core: 核心逻辑层 format: conventional require_issue: true max_subject_length: 72 model: provider: openai model: gpt-4o-mini temperature: 0.1 timeout_seconds: 15 templates: default: config/templates/default.j2 hotfix: config/templates/hotfix.j2 docs: config/templates/docs.j2 hooks: validate: true lint_rules_path: .aigit/commitlint.config.js这个配置文件的价值在于它让技术决策变得显式化。以前这些规范只存在于团队某个老员工的脑子里或 README 的某一段里新人来了一脸懵现在配置文件躺在仓库里每次 AI 生成提交消息时自动读取不会有人记错。4.3 项目级 vs 个人级配置优先级怎么设计项目配置存在之后就涉及到和用户个人配置的关系。我给这套系统定的优先级是命令行参数临时覆盖最高优先级仓库根目录.aigit.yml项目级规范用户目录~/.config/aigit/config.yml个人偏好内置默认值兜底这个优先级设计的逻辑是项目级配置应该约束所有人个人配置只允许改不影响规范的部分比如偏好用哪个模型、API key 放哪。关键字段types、scopes、format、validate禁止个人覆盖否则项目规范就会出现裂缝——团队里几个人 AIGC 生成的提交消息格式五花八门等于又退回手工状态。4.4 与 CI 集成提交消息不合规直接在流水线拦下项目级配置的另一个好处是可以把手动校验变成自动化强制。我在 GitHub Actions 里加了个简单的 job每次 push 和 PR 时检查 commit 消息name: commit-message-lint on: pull_request: types: [opened, synchronize] jobs: lint-commits: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - name: Run aigit validation run: npx aigit validate --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}这样做的效果是哪怕某个开发者本地没装这套 AI 提交工具只要他把不合规的提交推到远端CI 就会直接挡下来。这本质上是在用工程手段确保“AI 提交消息可信”这件事不依赖任何人的自觉性。5. 实测翻车盘点AI 提交消息最容易骗过你的几个瞬间5.1 幻觉的 scope模型编出一个项目里根本不存在的模块名这是我在做项目级提交消息优化时遇到过的最典型问题。有次一个 Java 后端项目代码里只有一个api和一个core模块但 AI 在生成提交消息时愣是写出了一个commonscope因为它觉得“这里应该有个公共模块”。提交消息本身语法完全正确commitlint 也过了——因为当时的 scope 校验规则里没有把 scope 强制成枚举。后来我才意识到scope 校验必须用枚举不能只做“格式正确”校验。从那以后我的配置里scope-enum规则就再也没宽松过。这是“可控”这个诉求里最容易被忽视的一环不是格式符合常规就算控住了必须把边界收缩到项目实际存在的范围内。5.2 breaking change 误判明明改了一行配置AI 非要标 BREAKING CHANGE另一个高频翻车现场是 breaking change 误判。AI 对“breaking change”的理解普遍过于敏感只要 diff 里出现了函数签名变化、配置项改动、环境变量移除哪怕只是一个内部工具函数的参数改名它也会在提交消息里标上BREAKING CHANGE:脚注。这在生产仓库里是个非常危险的信号——它会影响 changelog 生成器的版本号决策严重时导致依赖方升级后误判兼容性。处理方案有两个层面一是提示词里显式声明“只有当改动影响外部 API 或数据库结构时才允许标注 BREAKING CHANGE”并且把“外部 API 和数据库结构”的具体范围在项目配置里列清楚二是在校验规则里对 BREAKING CHANGE 加个“需人工确认”的钩子——检测到该标记时工具会弹出提示让开发者确认是否真的属于破坏性变更。这个钩子能在很大程度上消除 AI 幻觉带来的后续连锁反应。5.3 多文件 diff 时“抓大放小”AI 只挑块头大的文件总结100 个以上文件的超大 diff 是 AI 提交消息工具的照妖镜。绝大多数模型不会耐心去分析每一个文件而是直接挑出几个“看起来重要”的大文件来描述其余小文件改动全部被忽略。结果就是提交消息描述了重构的核心模块但漏掉了一个关键 bug 修复而且往往是修复提交比重构提交更需要让人看见。解决这个问题不能靠提示词因为模型确实受限于上下文窗口。我的做法是在模板生成 context 前做一个预分析按文件类型分组每组单独走一次小型提取再把提取结果合并成摘要。大致流程是——先扫描文件列表对每个有非平凡改动的文件生成一行摘要然后把这些摘要汇总成新的上下文最终才交给生成 main message 的模型。这个“二次摘要”机制虽然增加了两次 API 调用但对于大 diff 场景的效果提升非常明显。5.4 模糊时间词污染提交历史AI 说“优化了登录体验”跟没说一样还有一种翻车不体现在格式校验上但会腐蚀仓库长期可读性AI 生成的提交消息比如fix: 优化了登录体验看似没问题实际信息量为零。模糊形容词优化、提升、改善是语言模型的“惯性滑梯”生成器非常容易滑向这种不用动脑的表达。对 code review 和后期追溯来说一条具体描述到“为 token 续期增加互斥锁修复并发刷新导致旧 token 失效”的消息价值比十条“优化了 xxx 体验”高得多。我的对策是在模板的 subject 生成要求里嵌入了“动词 直接宾语 原因/效果”的最小结构约束并给出正反示例禁止使用优化、提升、改善、修复问题、调整逻辑 这类无具体指代的表述。 要求明确写出改了什么、为什么改。示例 - 正确fix(api): 为 token 续期增加互斥锁修复并发刷新导致旧 token 失效 - 错误fix(api): 优化token刷新逻辑虽然增加了一些字符但这条约束带来的收益远大于成本——提交消息从“让人看得懂”升级成“让人能凭它定位问题”。6. 完整落地方案从零到一接入日常提交流水线6.1 方案选型自研脚本 vs 开源工具 vs 编辑器插件聊了这么多原理和方法最后落到一个实操问题这东西到底要不要自己从零写我的建议是如果团队规模 10 人以下直接用我前面说的思路基于 OpenAI API 或本地模型写一个 200 行的命令行脚本就足够了没必要引入重型框架。如果团队有一定规模或者已经在用 Node.js 技术栈可以基于commitlint生态去扩展一个插件来实现 AI 生成环节。个人开发者的话编辑器插件比如 Cursor 的 AI 提交或者 VS Code 里的一些 AI commit 插件可能是最轻量的入口但它们通常不支持项目级配置定制性有限。我目前在生产环境用的是自己维护的一个小型 CLI 工具结构非常简单一个读取配置的模块一个调用 LLM API 的模块一个校验模块一个 git 集成模块。总共不到 600 行 TypeScript。你不需要把它当成一个正规项目来维护当做一个“团队内部工具”去打磨就够了。6.2 落地步骤初始化配置、接入 git hook、绑定 AI 服务下面是可复制的落地路径按顺序操作即可第一步初始化项目配置在仓库根目录创建.aigit.yml内容参考 4.2 节。这里最关键的是把scopes枚举和types枚举填准确它们决定了 AI 生成时被限制的边界。第二步准备提示词模板目录在仓库里建一个.aigit/templates/目录放入默认模板和 hotfix 模板。模板内容可以直接照抄 2.3 节再根据项目语言和习惯做微调。第三步注册 git hook在prepare-commit-msg钩子里调用你的生成逻辑。这一步很关键因为prepare-commit-msg钩子能拿到 commit message 文件路径并且在 editor 打开前执行这样 AI 生成的结果会直接出现在编辑器中开发者可以手动修改后再提交。# .git/hooks/prepare-commit-msg #!/bin/sh COMMIT_MSG_FILE$1 COMMIT_SOURCE$2 # 仅在不是 merge/commit --amend 的情况下自动生成 if [ $COMMIT_SOURCE ]; then aigit generate --file $COMMIT_MSG_FILE || true fi第四步配置 API 密钥和模型在个人全局配置里填好model相关字段。推荐用gpt-4o-mini或同等定位的小模型因为这个任务复杂度不高但请求频率高用大模型既浪费钱又增加延迟。API 密钥建议放到环境变量里不要写进仓库配置文件。第五步联调验收做一次全流程测试修改几个文件 → 执行git add→git commit→ 确认 AI 提交消息进入编辑器 → 修改后保存 → 完成提交 → 用git log检查结果。6.3 日常使用流程一条命令完成“生成-审查-提交”整个系统跑起来之后日常使用流程极其简单一条命令git add -A aigit commit -m 修复登录接口在 token 过期时返回 500 错误aigit commit后台会先收集上下文调用模型生成一个 draft把 draft 写入模板填充后的编辑器等待你确认或修改确认后执行真正的git commit。整个流程下来从输入命令到提交完成多花的时间不会超过 5 秒不含人工审阅时间但提交消息质量会稳定在团队规范要求的水平上。还有一个我强烈建议加的小功能在提交消息生成后、写入 git 前把“最近提交历史里的相似条目”调出来给开发者看。这能帮助开发者在确认时快速发现新生成的提交消息是否和旧历史存在重复或矛盾避免同一个改动在 git log 里出现两种说法。6.4 效果评估怎么判断这套方案真的在帮团队提效上线这套方案后建议每两周做一次回顾看四个指标提交消息通过 commitlint 校验的比例目标100%因为 AI 生成人工修改CI 拦截三层保障下不合格消息不应该流出提交消息中含模糊词优化、改进、调整、修复问题等的比例目标低于 5%从 git log 追溯到对应需求或 issue 的平均耗时变化目标显著降低开发者每周在提交消息上花费的总时间目标比手工写的耗时减少 50% 以上如果这四个指标都很健康说明这套方案已经脱离“玩具”阶段真正变成了项目里可靠的基础设施。我自己的体会是AI 提交消息这件事技术难度并不在“调用大模型”这一步而在把生成任务嵌入到项目工作流的那些工程细节里。定制、可控、项目级优化这三个词听着像营销话术但落到实践上每一项都需要在模板、参数、校验、配置、CI 这五个环节里做大量具体决策。把这些决策都做完AI 才会从“偶尔靠谱”变成“日常可信”。这篇文章里给的模板和配置都是我在几个真实项目里反复打磨过的你拿去用的时候如果发现某些细节跟你的场景不匹配不用客气直接改。提交消息规范本来就不存在放之四海而皆准的版本——关键在于你愿意为此搭一整套系统让这个规范真正被日常执行而不是只存在于团队的文档里。