ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用AI大模型自动生成规范Git提交信息:Commit AI插件实践

用AI大模型自动生成规范Git提交信息:Commit AI插件实践 干开发这么多年提交信息大概是项目里最容易被糊弄但又最容易埋坑的地方。我见过太多仓库的提交历史清一色写着 “update”“fix bug”等真要回滚一个功能时根本分不清哪个提交对应哪次改动。后来也试过用 commitlint 这类工具去约束格式效果有但解决不了内容质量。真正让我把这件事签收下来的是把 VSCode 插件跟 AI 大模型组合起来做了一个叫 Commit AI 的小工具它自己读 git diff、分析这次改动到底做了什么、按规范输出提交信息我再人工确认一遍后提交。这篇帖子就从需求出发讲讲这套工具的设计思路、关键实现和我在实际使用过程中踩过的坑。如果你经常写 Git 提交信息尤其是对自己英文表述不太自信、或者经常对着改动不知道 commit 怎么写合适这个思路可以直接照搬。整个工具不复杂核心代码几百行本质上就是“git diff 大模型 规范输出”的管道。我会尽量把每个环节的设计原因和配置参数说透让你看完能自己在项目里落地。1. 为什么我最终选了 AI 生成提交信息1.1 传统提交信息的痛点在哪里先说一个我自己的反面案例。有一回我在埋点模块里删了一个老接口的兼容逻辑顺手改了读取配置的方式提交时只写了一句 “refactor config”。三周之后性能问题排查翻这段历史的时候既不知道改了哪里也不知道当时为什么改最后只能对着 git blame 一行一行看时间直接翻倍。这不是个别现象。大部分团队里提交信息质量低的原因很朴素提交者自己已经知道改了啥默认“写清楚”是在给未来的陌生人打工于是能省则省对非母语开发者来说用英文把改动意图说清楚本身就存在表达成本接近提交时人往往处于“改完收工”的状态注意力早就飘走了根本没心思组织语言。于是仓库历史就变成了一堆 “fix bug”、“update file”、“代码优化”。这种信息的检索价值几乎为零。等你想做版本回溯、自动生成 changelog、定位某个 feature 是哪个 MR 引入的时候全都抓瞎。1.2 模板和脚本为什么救不了内容质量不少人想过用工程手段去卡。最常见的是 commitlint husky把提交信息格式锁死不满足 Conventional Commits 就不让提交。这个方案我在多个项目里用过体验是格式规范了内容依旧很水。commitlint 只会告诉你 “subject 要用 imperative mood”它不会告诉你这行代码到底是在引入新接口还是在修复一个跨时区的日期 bug。也有人写脚本根据 nginx 日志、文件类型或日志关键字去拼提交信息比如检测到修改了 api 目录就自动带上 “feat(api)”。这种规则在极少场景有效本质上是在做模式匹配不是理解。一旦改动跨越多个模块或者一个 diff 里既有新功能又有修复脚本拼出来的东西又碎又假基本不可用。所以最终结论是需要具备“理解代码含义”能力的方案而不是“匹配关键词”的方案。这正好是大模型擅长的事。它能读懂 diff能提取改动意图还能按指定格式输出。把这件事交给 AI 做并不是因为 AI 写得比我好多少而是它能稳定地、不带情绪地把这项工作完成而我只需要花几秒钟确认或修改。2. 项目整体设计与核心架构2.1 从 diff 到提交信息的处理管道Commit AI 插件的整体处理链路很直接可以用一句话概括把 git diff 文本和一套约束规则拼成一个 prompt发给大模型拿到结构化结果后回填到侧边栏或提交输入框。展开成步骤大概是用户在当前项目点击命令面板输入 “Commit AI: Generate Commit Message”插件调用 git 命令获取工作区里的未暂存 diff如果存在暂存区内容则优先使用git diff --staged把 diff 内容、项目路径、附加的定制规则一起渲染成 prompt 模板调用配置好的大模型接口通常走 OpenAI 兼容的 chat completions 协议解析返回结果把 subject 和 body 拆开填到 VSCode 提交框里用户人工确认、润色之后执行 commit或者继续git commit --amend二次修订。整个过程看起来简单但每一步都有细节。真正实现时我认为最容易出问题的是第 2 步的 diff 选取策略。很多类似的工具默认只拿未暂存 diff这会导致用户执行了git add之后生成的信息反而漏掉内容体验非常割裂。我最终的做法是优先合并暂存区和未暂存区两个 diff按“暂存区 diff 为主未暂存区 diff 作为增量参考”来处理这样用户无论有没有 add生成结果都相对稳定。2.2 技术选型为什么做成 VSCode 插件而不是命令行做这个工具前我也考虑过直接用 Python 脚本加 CLI 参数毕竟看起来更轻。但实际比下来还是 VSCode 插件最贴合使用场景。原因是提交动作本来就发生在编辑器里。开发者 90% 的时间都在 VSCode 内工作写完代码切到源代码管理面板就可以看见改动列表。如果工具做成 CLI我就得在提交前先打开终端、跑一段命令、再把结果复制回提交框多了一步跳转抵触感马上就来了。而 VSCode 插件可以直接把生成的提交信息填到 Git 提交输入框甚至可以在编辑器的状态栏显示生成状态交互路径最短。另外 VSCode Extension API 提供了一些很实用的设施workspace.getConfiguration能管理插件配置跟用户的其他 VSCode 设置自然融合SecretStorage可以安全保存 API 密钥不用把 key 写进项目文件window.withProgress能显示生成过程中的 loading 状态比命令行安静等待舒服很多命令面板集成是现成的不需要额外的快捷键学习成本。插件本身用 TypeScript 开发跑在 Node.js 20 左右的运行时上打包分发用vscode/vsce。完整的本地开发启动流程是VSCode 里按 F5 启动 Extension Development Host这个宿主环境会加载插件源码可以对着测试仓库实时调试。等你不再需要调试再打.vsix包发布或者内网分发。2.3 输出格式锁定 Conventional Commits生成提交信息的质量一半取决于 prompt另一半取决于输出格式的约束。我并没有让模型自由发挥而是锁定了 Conventional Commits 规范。它的基本形态是feat(scope): 描述 正文可以补充细节 BREAKING CHANGE: 说明破坏性变更不用这个规范的代价我体验过模型喜欢写很长的自然语言标题或者在同一行里塞下三个模块的改动提交历史瞬间变成散文集。用 Conventional Commits 之后至少具备几个立刻可见的好处subject 自动限制在 50 字符左右信息密度更集中feat、fix、refactor、docs、test这些 type 让历史一眼可扫scope 让跨模块提交有了归类维度BREAKING CHANGEfooter 可以作为自动化版本号的依据。我在 prompt 里一般还会要求模型在 subject 里尽量使用中文描述名称因为代码里的变量名和函数名是英文但提交历史面向的是整个团队使用中文能显著降低沟通成本。这属于团队约定你可以通过配置项自由切换。3. 配置与关键细节实现3.1 环境准备与插件安装如果你想直接使用编译好的产物流程很短。先确认 VSCode 版本在 1.86 以上Node.js 版本不低于 18然后安装打包依赖并打包npm install -g vscode/vsce npm install vsce package打包后会生成一个.vsix文件正式安装用code --install-extension vscode-commit-ai-0.1.0.vsix如果你希望通过插件市场分发优先上传到 Open VSX 或企业的私有扩展市场走内部统一升级。在自己的团队里我建议保留本地打包路线因为插件涉及大模型调用很多团队会有内部安全审计源代码包比黑盒安装更容易过审。安装完成后插件会在命令面板注册四条命令Commit AI: Generate Commit Message生成一条新的提交信息Commit AI: Generate and Commit生成后直接执行提交Commit AI: Regenerate对当前 diff 重新生成一次换语气或换结构Commit AI: Clear Message清空当前提交框手动重来。3.2 大模型服务的接入与鉴权插件本身不自带模型它只负责把 diff 加工成 prompt并解析模型返回的结果。这样设计的好处是模型可以随时替换不同团队的合规要求也能适配。我实现的时候兼容了常见的 OpenAI chat completions 协议所以只要模型服务暴露一个/v1/chat/completions接口理论上都能接入。默认配置长这样{ commitAi.baseUrl: https://your-endpoint.example.com/v1, commitAi.model: qwen2.5-coder-7b-instruct, commitAi.temperature: 0.1, commitAi.maxTokens: 1024, commitAi.language: zh-CN, commitAi.commitStyle: conventional }这里有一个很重要的安全细节不要把 API Key 写在项目的.vscode/settings.json里尤其当项目存在于多人协作的 Git 仓库一旦提交就相当于公开了密钥。我建议通过环境变量注入或者使用 VSCode 的SecretStorage存储机制。插件读取密钥的顺序是环境变量COMMIT_AI_KEY优先其次读取commitAi.apiKey配置项最后读取已保存的 SecretStorage 内容。绝大多数情况下我推荐第一种。如果你的模型服务部署在企业内网直接把baseUrl指到内网地址即可。这样可以确保 diff 数据完全不出内网对代码保密要求高的团队来说这是最稳妥的姿势。3.3 提示词设计决定生成质量的核心我把大模型当做一个“代码审查员”而不是“文案生成器”。这决定了 prompt 的整体语气和约束。结合版本迭代下面这份是我最终还在用的简化版提示词你是一位资深代码审查员。请根据我提供的 git diff生成 符合 Conventional Commits 规范的 git 提交信息。 要求 1. subject 控制在 50 字符以内格式为 type(scope): 描述 2. 通常使用中文描述保持简洁 3. 如果 diff 涉及多个改动点在 body 里用短横线逐一列出 4. 如果存在破坏性变更必须在 footer 添加 BREAKING CHANGE 说明 5. 只能基于 diff 内容推断禁止猜测 diff 之外的改动 6. 如果 diff 为空仅输出 NO_DIFF。 以下是 git diff 内容 diff ${diffText} /diff几个关键设计点我展开说一下第 2 条把主体语言锁成中文避免模型因为在英文 prompt 环境下默认输出英文描述第 3 条让 body 有结构化列表生成的提交信息看起来更清爽而不是一段流水账第 5 条尤其重要。模型特别喜欢“脑补”当 diff 只改了一个变量名它有可能会写“优化性能并修复潜在崩溃”这种过度解读比写流水账更危险第 6 条是一个兜底指令用于在 diff 为空时让模型返回固定标记省掉一次无效请求。如果你希望让模型生成的提交信息风格更贴近团队历史可以在 prompt 末尾追加 few-shot 示例比如把自己的 2 到 3 条高质量历史提交贴进去模型会有样学样输出风格立刻会发生变化。这个技巧调试一次就知道效果比反复调温度参数明显得多。4. 实操过程与核心参数4.1 从写代码到提交信息落地的完整链路我们用一个非常小的改动来演示完整流程。假设我在一个用户模块里做了一次异步改造修改了src/user.js文件git diff 大致如下diff --git a/src/user.js b/src/user.js index 100644..100644 --- a/src/user.js b/src/user.js -3,7 3,7 import { findUserById } from ./repository/user; -const user findUserById(id); const user await asyncFindUserById(id); if (!user) { throw new UserNotFoundError(id); }此时插件读到的 diff 内容就是文本本身。经过 prompt 加工后模型给出的结果可能是fix(user): 修正用户查询为空时的异常处理 - 将同步查询改为异步查询 - 补充用户不存在时的 UserNotFoundError 抛出 - 避免调用方对空值进行额外判断这个结果可以直接用。用户看到信息后可以手动点开提交编辑框把第 2 行改成更符合实际语义的表述再做提交。整个过程不超过十秒比对着 diff 自己组织语言快不少。实际使用里我还会经常用到git commit --amend。因为有时候生成完信息之后我突然想起某一行注释没写清楚又补了一个小改动这时候不想新增一条杂乱的提交会在暂存补充改动后直接 amend 上一次提交。插件工作流和这个命令配合得很好生成一次、精简一次、amend 一次提交历史依然保持得干净。4.2 Token 估算与 diff 分片策略把整个仓库的大 diff 一次性扔给大模型是最容易翻车的操作。我之前试过把 40 个文件的改动一次性塞进去结果模型不仅响应慢还把高优先级的修复跟无关重构混在一起提交信息变成了一锅粥。所以必须做 diff 分片。我的经验值是单次请求的 diff 文本量控制在 1500 行以内大约对应 2000 到 2500 个 token。估算规则很简单代码行大多以 ASCII 为主大约每 4 个字符对应 1 个 token中文字符占用更多如果是描述性文本按每 2 到 3 个字符对应 1 个 token 估算2000 行代码 diff 大约会产生 200KB 的 diff 文本这时候必须拆分。分片策略上我倾向于先按文件拆分因为单个文件的改动语义通常比较内聚。过于巨大的文件再按 hunk 拆分。插件内部会先执行git diff --stat拿到每个文件的改动行数再决定分片顺序const statOutput execSync(git diff --stat); const fileStats parseDiffStat(statOutput); // 返回 FilePath - addedLines const MAX_DIFF_LINES 1500; const chunks []; let currentChunk []; for (const file of changedFiles) { if (currentLines file.addedLines MAX_DIFF_LINES currentChunk.length 0) { chunks.push(currentChunk); currentChunk []; } currentChunk.push(file); } chunks.push(currentChunk);拆完分片后每个分片各自生成提交信息最后再由同一个模型把所有分片的信息汇总成一条完整的提交信息。这个方案比单次请求稳定太多代价是耗时变长了一点但换来的是提交信息里每个关键改动都幸存得清清楚楚。还有一个小技巧在发送大模型请求之前先用git diff --check检查有没有明显的空白错误等低级问题。这个动作属于顺手做可以帮你在提交前拦截一排 tab 混空格的问题也算个省心的副作用。4.3 人工确认环节如何设计AI 生成的提交信息再漂亮它也只是参考不能完全替代人的判断。所以插件交互里我把“确认”这一步设计得非常重生成结果默认不会直接执行提交而是先填到 VSCode 的 Git 提交输入框让用户一眼看到要提交的内容。这一步有一个人机交互的细节提交框里的 subject 和 body 之间插件会自动用空行隔开。Commit 面板的解析器会把多行文本继续作为提交信息而不是把它当作注释丢掉。用户可以用鼠标直接点进文本域编辑也可以按快捷键打开一个纯文本提交编辑器改完保存后再回到面板提交。如果你习惯了自动提交也有Generate and Commit命令一次搞定。但我真心建议头几周不要用自动提交给自己留一个校验缓冲期。等你对模型的稳定输出风格建立起信任再切到自动化不迟。5. 常见问题与排查技巧5.1 高频问题速查表用了一段这个工具之后我把常见问题整理成了一张速查表团队里新同学遇到问题可以先查这个。现象可能原因处理方式生成信息全是英文prompt 语言约束被忽略将 language 配置改为 zh-CN或在 prompt 里明确“使用中文”模型生成的改动点与实际不符diff 过大引发模型脑补限制单请求 1500 行用分片策略生成后再合并响应超时diff 文本太长或模型服务较慢提高请求超时时间同时设置 maxTokens 适当调小中文显示乱码Windows 终端编码不是 UTF-8在设置里强制插件的 child_process 输出为 UTF-8关掉自动换行点击生成完全无反应git 仓库没有未暂存改动或者 diff 为空检查是否已 add 文件git status 看一下API Key 报鉴权失败key 存在旧配置里或未设置用环境变量注入后重启 VSCode并删除配置文件里的明文 key生成的信息太长压根没法做 subjectmaxTokens 设得太大且模型偏好长篇在 prompt 强调 subject 不超过 50 字符把 maxTokens 调到 1024提交信息时说“找不到文件”插件运行在工作区子目录git 命令基于根目录插件先获取 workspaceFolder 根路径所有 git 命令都基于该路径执行第一列每一条下面都比字面上更容易针对真正排查的时候配合插件日志看最稳。插件会在.vscode/commit-ai.log落一份请求日志记录了每次请求的 token 数、耗时和错误码定位问题比肉眼猜快得多。5.2 我踩过的几个坑第一个坑是安全边界。早期版本为了给模型更多上下文我把整个文件内容都塞进 prompt想让模型“看得更清楚”。结果当然是又慢又贵更严重的是部分文件可能包含密钥、内部路径、甚至未发布的业务逻辑这种数据外发给外部模型服务合规风险很大。现在插件严格只发送 diff 片段并且会过滤掉包含明显密钥模式的行。不是持有多少数据的问题而是最小化数据接触面这一条是我强烈建议遵守的。第二个坑是 Windows 下中文乱码。我的插件用 Node.js child_process 执行 git 命令当仓库路径里出现中文或者提交信息是中文时Windows 默认代码页容易搞乱输出。最终解决方案是在创建子进程的时候强制指定编码import { execSync } from child_process; const stdout execSync(git diff, { encoding: utf8, env: { ...process.env, LC_ALL: en_US.UTF-8 }, });同时把仓库自身的.gitattributes里声明文本文件统一使用 UTF-8双管齐下才彻底解决。第三个坑是“改了文件但忘了 add”。git diff 默认只看未暂存改动如果你刚写完新文件还没git add某些场景下 diff 可能为空插件会给出误导性的“没有改动”。我后来在插件里加了一步前置提示检测到暂存区为空但工作区有改动时直接提醒用户先git add避免生成结果的上下文少了一截。第四个坑是模型输出格式不稳定。理论上要求模型输出 JSON但总会遇到多输出几个字符的情况。我为此写了一个很宽容的解析逻辑先从原字符串里提取第一个花括号包裹的 JSON 片段解析失败再退回去做 markdown code block 的正则匹配还不行的话就让用户手动粘贴。硬解析虽然看着“蠢”但在大模型输出上往往比严格解析更可靠。6. 从这一个小工具继续延伸的方向如果只看“自动写提交信息”这一点Commit AI 已经完成了它的核心使命。但实际上这套“diff 文本 结构化 prompt 人工确认”的思路可以很容易迁移到别的地方。我做的时候顺手验证过几个方向一是代码审查辅助。同一个模型输入从 git diff 换成 pull request 的完整变更prompt 改成“指出三个高风险改动点给出修改建议”输出就能变成一份轻量级 review 意见。这不是什么新发明但真的节省了我逐文件看完再写批注的时间。二是自动生成变更日志。因为提交信息已经按 Conventional Commits 规范化后续可以写一个脚本在 CI 里解析提交历史直接产出changelog.md不需要再手动去翻 commit。这个链路的价值会随着仓库历史增长越来越大。三是学习仓库历史风格做定制。我给插件加过一个 off 开关读取最近的 20 条历史提交作为 few-shot 示例拼在 prompt 尾部。这样新加入的代码在提交风格上会自然贴近团队既有语气而不是每次都由模型自由发挥。把这个功能打开之后团队里每个人生成的提交信息气质变得相当统一这对自动化依赖提交文本的团队特别友好。四是继续打磨安全水位。我目前只是过滤了密钥模式下一步可以考虑让用户配置敏感路径白名单凡是这些目录下的改动一律不提送模型只用来本地做摘要判断。这个方向值得做细。说到底工具存在的意义不是替开发做决定而是把那些重复的、低技术含量的工作拆掉把省下来的精力留给真正需要判断的事。提交信息只是一个入口当你明确感受到 AI 可以把这些琐事接走之后你会开始到处找下一个可以被接走的工作。这大概也是这几个月我最大的体会真正好用的工具不是功能多而是它默默地、稳定地替你把一件事做完然后让你几乎忘了它的存在。
返回列表