ARTICLE DETAIL

资讯详情

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

VSCode Commit AI:用AI自动生成规范Git提交信息

VSCode Commit AI:用AI自动生成规范Git提交信息 1. 为什么提交信息值得单独做一个工具写了十年代码我见过太多仓库的提交历史长这样fix、update、修改、提交、aaa、111。过三个月回头看没人知道那次到底改了什么。更麻烦的是排查线上问题时想通过git log定位某次改动结果满屏都是无意义的字符串只能一个个 diff 点开看。VSCode Commit AI这个项目要解决的就是这件事在 VS Code 里点一下让 AI 读取当前暂存区的改动自动生成一条符合规范的提交信息直接填进提交框。它不是一个独立应用而是一个 VS Code 扩展把写提交信息这个高频但低价值的动作交给模型处理。适合谁用三类人最受益。一是团队协作里需要统一提交规范但总有人偷懒的二是接手老项目、需要频繁提交小步改动做重构的三是英文表达不熟练、写不出地道 commit message 的。哪怕你只是个人项目养成好习惯后回看历史也会舒服很多。这篇文章我会把这个扩展从设计思路、核心实现、实操配置到踩坑排查完整拆一遍。你不需要有 AI 背景只要会用 Git 和 VS Code 就能跟上。涉及模型调用的部分我会讲清楚参数怎么选、prompt 怎么写这些才是决定生成质量的关键。2. 整体设计与方案选型拆解2.1 扩展形态为什么选 VS Code 而不是命令行工具很多人第一反应是写个 CLIgit commit前挂个 hook 调用。但实际用下来VS Code 扩展的体验明显更好原因有几个。提交信息本质上是写代码时的上下文产物。你在编辑器里刚改完文件注意力还在代码上这时候在侧边栏或命令面板点一下就能生成比切到终端敲命令顺手得多。VS Code 扩展能直接拿到当前工作区的 Git 状态、暂存区 diff、甚至光标所在文件这些上下文对生成准确的提交信息至关重要。另一个原因是可视化。生成结果直接填进源代码管理面板的输入框你能立刻看到、编辑、再提交形成生成-审阅-微调的闭环。CLI 工具要么把结果打到 stdout 让你复制要么直接提交中间缺少这个确认环节风险更高。提示扩展和 hook 并不冲突。我的做法是扩展负责生成同时配一个commit-msghook 做格式校验兜底双保险。2.2 读取 diff 的策略暂存区优先全量兜底生成提交信息的前提是拿到这次要提交什么。这里有个容易踩的坑如果直接读工作区所有改动会把没git add的文件也算进去生成的描述和实际提交内容对不上。合理的策略是分两层。第一层读暂存区 diffgit diff --cached这是即将提交的内容最准确。如果暂存区为空说明用户还没 add这时候有两种处理要么提示请先暂存文件要么退而求其次读工作区 diff 并明确告知用户。我倾向于前者因为提交信息必须和暂存内容严格对应否则就是误导。diff 内容还需要做截断。一个几百行的改动全塞给模型既慢又贵还可能超出上下文窗口。常见做法是按文件聚合每个文件只取前若干行改动或者按 token 数截断。这里要保留的是改了什么类型的文件、动了哪些函数、增删了多少行这类结构性信息而不是每一行代码的细节。2.3 模型选型本地还是云端这是绕不开的决策。云端模型各类通用大模型 API生成质量高、对自然语言理解好但需要网络请求、有成本、代码会离开本机。本地模型如通过 Ollama 跑的小参数模型隐私好、免费但对 diff 的理解能力弱一些生成的英文可能不够地道。我的建议是分场景。公司内部项目、涉及敏感业务逻辑的优先本地模型或走内网部署的模型服务。个人开源项目、对隐私不敏感的用云端 API 省心。扩展设计上最好把模型调用抽象成一层接口让用户自己填 endpoint 和 key这样两种方案都能覆盖。参数方面temperature建议设低一点0.2 到 0.4 之间。提交信息需要的是稳定、准确不是创意。max_tokens给 100 到 200 足够一条 commit message 本来就不该长。2.4 输出格式Conventional Commits 是事实标准生成什么样的格式直接决定工具好不好用。目前社区最认的是Conventional Commits规范格式是type(scope): description。type 常见取值有feat、fix、docs、style、refactor、test、chore等。为什么选它因为它机器可读。后续可以用工具自动生成 CHANGELOG、判断版本号该升 major 还是 minor。而且它强制你思考这次改动属于哪一类本身就是一种约束。prompt 里要明确告诉模型先判断 type再提取 scope影响的模块最后写一句不超过 72 字符的描述用祈使句、现在时、首字母小写、结尾不加句号。这些细节不写清楚模型就会自由发挥生成一堆风格不统一的句子。3. 核心细节解析与实操要点3.1 扩展的目录结构与关键文件一个能跑的 VS Code 扩展骨架其实很轻。核心是package.json里的contributes字段它声明了扩展往编辑器里注入什么。对于这个项目至少要注册一条命令比如commitAI.generate再把它挂到源代码管理面板的标题栏菜单上。commit-ai/ ├── package.json # 扩展清单声明命令、菜单、配置项 ├── src/ │ ├── extension.ts # 激活入口注册命令 │ ├── git.ts # 封装 git diff 读取逻辑 │ ├── ai.ts # 模型调用与 prompt 组装 │ └── config.ts # 读取用户配置 ├── tsconfig.json └── README.mdpackage.json里几个关键点。activationEvents建议用onCommand:commitAI.generate按需激活不拖慢启动。contributes.configuration里暴露commitAI.apiKey、commitAI.model、commitAI.language等配置项让用户能在设置界面直接改不用动代码。3.2 用 VS Code 内置 Git API 还是自己调命令行VS Code 提供了vscode.git扩展 API能直接拿到仓库对象、暂存区改动、当前分支名。用它比自己在 Node 里 spawngit命令优雅得多不用处理路径、权限、编码这些破事。const gitExtension vscode.extensions.getExtension(vscode.git)?.exports; const api gitExtension.getAPI(1); const repo api.repositories[0]; const staged repo.state.indexChanges; // 暂存区文件列表但内置 API 拿到的 diff 有时不够细比如它给的是文件级状态具体行级 diff 还得自己调git diff --cached。所以实际项目里常见的是混合方案用 API 拿仓库和文件列表用命令行拿精确 diff。这样既稳又全。注意调用命令行时一定要指定cwd为仓库根目录并且处理 Windows 和 Unix 的路径分隔符差异否则在跨平台时会莫名其妙失败。3.3 prompt 工程决定生成质量的核心模型再强prompt 写得烂也白搭。我调过很多版最后稳定下来的结构是这样的系统提示词定角色和规则用户消息塞 diff 和上下文。系统提示词大致是你是一个 Git 提交信息生成助手。根据提供的代码改动生成一条符合 Conventional Commits 规范的提交信息。只输出提交信息本身不要解释不要加引号不要用 markdown 代码块包裹。用户消息里要包含当前分支名有时能推断出任务类型、改动文件列表、截断后的 diff、以及用户配置的语言偏好。如果用户设了中文就要求生成中文描述但 type 和 scope 保持英文。这里有个细节few-shot 示例非常有效。在 prompt 里塞两三个输入输出示例模型生成的格式会稳定很多。比如给一个改了登录逻辑对应fix(auth): 修复token过期后未刷新的例子模型就知道 scope 该怎么提取。3.4 配置项设计让用户能调但不至于懵配置项太多是灾难太少又不够用。我建议暴露这几个就够配置项类型默认值说明commitAI.providerstringopenai模型服务商决定调用格式commitAI.apiKeystring空密钥建议用 SecretStorage 存commitAI.modelstringgpt-4o-mini模型名commitAI.languagestringzh生成语言zh 或 encommitAI.maxDiffLinesnumber200diff 截断行数apiKey千万别明文存在settings.json里那玩意会被同步到云端。用context.secrets.store()存进系统密钥链读取时异步取。这是很多新手扩展容易忽略的安全点。4. 实操过程与核心环节实现4.1 从零搭建扩展的开发环境先把地基打好。装 Node.js 18 以上版本然后全局装yo和generator-code用官方脚手架生成项目骨架省得手写一堆配置。npm install -g yo generator-code yo code # 选择 New Extension (TypeScript)生成后按 F5 会弹出一个扩展开发宿主窗口这就是调试环境。在里面打开任意 Git 仓库就能测试你的命令。改代码后按 CtrlR 重载宿主窗口即可不用反复重启。调试时有个技巧在extension.ts的激活函数里打console.log输出会显示在宿主窗口的调试控制台里。别用vscode.window.showInformationMessage调试弹窗会打断操作流。4.2 读取暂存区 diff 的完整实现这是整个扩展的数据源头必须写扎实。核心逻辑是判断暂存区是否有内容有就取暂存 diff没有就提示用户。import { exec } from child_process; import { promisify } from util; const execAsync promisify(exec); async function getStagedDiff(repoPath: string, maxLines: number): Promisestring { try { const { stdout } await execAsync(git diff --cached --unified3, { cwd: repoPath, maxBuffer: 1024 * 1024 * 10, }); if (!stdout.trim()) { throw new Error(暂存区为空请先 git add 要提交的文件); } const lines stdout.split(\n); if (lines.length maxLines) { return lines.slice(0, maxLines).join(\n) \n...(diff 已截断); } return stdout; } catch (err) { throw new Error(读取 diff 失败: ${(err as Error).message}); } }--unified3控制上下文行数3 行是默认值够模型理解改动位置又不至于太长。maxBuffer要调大大仓库的 diff 可能超过默认的 1MB 限制不设会直接报错。提示如果仓库用了 Git LFSgit diff对大文件可能返回指针而非内容这时候生成的提交信息会不准。可以在配置里加个开关遇到 LFS 文件时只报文件名不报内容。4.3 调用模型并解析返回拿到 diff 后组装请求。以兼容 OpenAI 格式的接口为例用fetch直接发就行不必引第三方 SDK减少依赖体积。async function generateMessage(diff: string, config: Config): Promisestring { const systemPrompt 你是 Git 提交信息生成助手。根据代码改动生成一条 Conventional Commits 格式的提交信息。 格式type(scope): description type 取值feat/fix/docs/style/refactor/test/chore description 用${config.language zh ? 中文 : 英文}祈使句不超过 72 字符结尾不加句号。 只输出提交信息本身。; const res await fetch(${config.endpoint}/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${config.apiKey}, }, body: JSON.stringify({ model: config.model, temperature: 0.3, max_tokens: 150, messages: [ { role: system, content: systemPrompt }, { role: user, content: 改动如下\n${diff} }, ], }), }); const data await res.json(); return data.choices[0].message.content.trim(); }返回结果要做清洗。模型有时会自作主张加反引号、加提交信息前缀、或者输出多行。用正则把首尾的引号和代码块标记去掉只取第一行有效内容。4.4 把结果写回源代码管理输入框生成完要填进 VS Code 的提交框。通过 Git API 的repo.inputBox.value直接赋值即可。repo.inputBox.value generatedMessage;如果用户已经手动输入了内容别直接覆盖弹个确认框问一下。这个细节很关键我见过有人辛苦写了一半被扩展冲掉直接卸载。命令注册和菜单挂载在package.json里配好{ contributes: { commands: [ { command: commitAI.generate, title: AI 生成提交信息, icon: $(sparkle) } ], menus: { scm/title: [ { command: commitAI.generate, group: navigation } ] } } }这样源代码管理面板标题栏就会出现一个小图标点一下触发。也可以绑定快捷键在keybindings里配ctrlaltg之类不冲突的组合。4.5 参数选择背后的计算逻辑maxDiffLines设多少合适这取决于模型的上下文窗口和成本。假设模型上下文 8k token系统提示词加示例占 500 token留给 diff 的约 7000 token。代码平均每行 10 个 token那大概能放 700 行。但为了控制成本和延迟200 行是个平衡点——大多数单次提交的改动不会超过这个量。temperature为什么是 0.3 而不是 0完全为 0 时模型容易陷入重复和死板偶尔会生成奇怪的措辞。0.3 保留一点灵活性又不至于跑偏。这个值我实测过 0.1 到 0.7 的范围0.3 附近最稳。5. 常见问题与排查技巧实录5.1 生成结果为空或报错最常见的原因是 API key 没配或配错。排查顺序先看设置里 key 是否填了再看 endpoint 地址对不对有些服务商要带/v1有些不带最后看网络能否通。如果用的是需要特定请求头的服务检查 header 是否完整。另一个隐蔽原因是 diff 为空。用户以为改了文件其实没保存或者改了但没git add。扩展里要把这个错误明确提示出来别笼统报生成失败。5.2 生成的提交信息太长或格式不对模型不听话八成是 prompt 约束不够。检查三处系统提示词里有没有明确不超过 72 字符、有没有给 few-shot 示例、max_tokens是不是设太大了。把max_tokens压到 100模型想写长都没空间。如果 type 总是判断错比如把文档改动标成feat可以在 prompt 里把每个 type 的适用场景列清楚。模型对明确规则的理解比模糊描述好得多。5.3 中文生成出现中英混杂这是中文用户的典型痛点。模型经常生成fix(auth): 修复token过期问题这种半中半英。解决办法是在 prompt 里明确type 和 scope 保持英文description 部分全部用中文专有名词如 API、token 可保留英文。给个正例和反例对比效果立竿见影。5.4 大仓库下响应慢diff 太大是主因。除了截断还可以按文件类型过滤比如忽略package-lock.json、*.min.js、图片等自动生成或二进制文件。这些文件的改动对理解提交意图没帮助反而占满上下文。const IGNORE_PATTERNS [/package-lock\.json$/, /\.min\.(js|css)$/, /\.(png|jpg|svg)$/];在读取 diff 后按文件切分过滤掉匹配的文件段再拼接。实测在大型前端仓库里这一招能把 diff 体积砍掉一半以上。5.5 常见问题速查表现象可能原因解决方向命令点了没反应扩展未激活或命令未注册检查 activationEvents 和 contributes.commands提示暂存区为空文件没 add先 git add或改用工作区 diff 模式请求超时网络或 endpoint 错误检查地址、代理设置、服务商状态生成内容带引号模型输出未清洗加正则去除首尾引号和代码块标记中文变英文language 配置未生效确认配置读取路径和默认值覆盖了手写内容未做覆盖确认加 inputBox 非空判断和确认弹窗注意调试模型调用时别把完整 diff 和 key 打到日志里。日志可能被收集或同步泄露代码和密钥。要打就只打长度和文件数这类元信息。6. 让工具真正融入日常提交习惯工具做出来只是第一步能不能坚持用才是关键。我的经验是把它嵌进固定动作里改完代码git add然后顺手按快捷键生成扫一眼没问题就提交。形成肌肉记忆后写提交信息这件事几乎不占脑力。还有几个可以继续打磨的方向。一是支持多候选一次生成三条让用户挑适合改动复杂、type 不好判断的场景。二是记住用户对生成结果的修改比如你总把chore改成refactor下次可以把这个偏好喂回 prompt。三是和 CHANGELOG 生成打通提交规范了发版时自动生成变更日志就是顺手的事。我在实际使用中最大的体会是提交信息的质量反映的是你对这次改动想清楚了没有。AI 能帮你把话说漂亮但这次到底改了什么、为什么改还得你自己心里有数。工具是放大器不是替代品。把它当成一个帮你保持规范的助手而不是替你思考的拐杖用起来才踏实。
返回列表