
1. 为什么我要自己动手做一个提交信息生成插件写代码这件事最让人烦躁的往往不是逻辑本身而是收尾阶段那条提交信息。功能改完了测试也过了结果卡在git commit -m ...这一步盯着光标发呆最后随手敲一句update或者fix bug了事。过两周回头翻提交记录自己都看不懂当时改了什么。市面上确实有一些现成的工具但用下来总有几个不顺手的地方有的要单独开一个终端窗口有的把密钥存在云端让人不放心有的对中文支持一塌糊涂。我平时主力编辑器就是 VSCode一天十几个小时都泡在里面索性就琢磨着做一个插件把这件事彻底解决掉——在源代码管理面板里点一下按钮提交信息自动填好确认无误直接提交。这个项目的核心目标很明确在 VSCode 内部完成从代码变更到提交信息生成的全流程不切换窗口不依赖外部服务密钥本地存储。它适合所有用 Git 做版本控制、又懒得写提交信息的开发者不管你是刚学 Git 的新手还是每天要提交几十次的老手都能直接用。下面我把整个插件的设计思路、核心实现、踩过的坑和实操步骤完整拆一遍你可以照着复现一个属于自己的版本。2. 插件整体设计与技术选型拆解2.1 核心需求到底有哪些在动手之前我先把需求列清楚避免做到一半发现方向跑偏。一个合格的提交信息生成插件至少要满足这几条能拿到当前工作区的变更内容包括已暂存和未暂存的改动最好能区分开因为提交信息通常只针对暂存区。能调用大模型接口生成文本需要一个稳定的 API 调用层支持配置不同的模型和接口地址。能在 VSCode 界面内完成交互不能跳出编辑器最好是在源代码管理面板加一个按钮或者在命令面板注册一个命令。密钥要安全存储API Key 不能明文写在配置文件里要用 VSCode 提供的加密存储能力。生成结果要可编辑不能直接提交得让用户看一眼不满意可以改改完再提交。这五条是底线缺一个体验就会打折扣。比如少了第四条密钥泄露风险就上来了少了第五条生成错了还得重新来一遍反而更麻烦。2.2 为什么选择 VSIX 离线包形式分发VSCode 插件的分发方式主要有两种一种是发布到官方市场用户搜索安装另一种是打包成.vsix离线包手动安装。我最终选择了后者作为主要分发方式原因有几个。第一审核周期不可控。官方市场对涉及外部 API 调用的插件审核比较严格尤其是涉及密钥和网络请求的来回沟通可能要好几周。而.vsix包自己打包自己用几分钟就能搞定。第二内网环境友好。很多公司的开发机是不连外网的或者有严格的网络策略从市场安装插件根本行不通。.vsix包可以直接拷贝到目标机器上用命令安装完全离线。第三版本控制更自由。我可以针对不同团队的需求打包不同配置的版本比如预置不同的接口地址而不需要维护多个市场版本。安装.vsix的命令很简单在 VSCode 里按CtrlShiftP打开命令面板输入Install from VSIX选择文件即可。或者用命令行code --install-extension commit-ai-0.1.0.vsix提示打包.vsix需要用到vsce工具安装命令是npm install -g vscode/vsce打包时在插件根目录执行vsce package即可。如果提示缺少 README 或 repository 字段在package.json里补上就行。2.3 技术栈选型与理由插件本身用 TypeScript 写这是 VSCode 插件开发的标配类型提示完善和 VSCode 的 API 配合得最好。构建工具用的是 esbuild比 Webpack 快很多配置也简单几十行就能搞定。调用大模型接口这部分我没有引入官方的 SDK而是直接用 Node.js 自带的https模块发请求。原因有两个一是官方 SDK 体积大会增加插件包的大小二是不同厂商的接口格式虽然大体相似但细节有差异自己封装一层反而更灵活切换模型只需要改配置。Git 操作这部分我没有直接调用git命令行而是用了 VSCode 内置的 Git 扩展 API。通过vscode.extensions.getExtension(vscode.git)拿到 Git 扩展的实例再通过它暴露的 API 获取仓库信息和变更内容。这样做的好处是不依赖用户本地git命令的路径配置兼容性更好。密钥存储用的是 VSCode 的SecretStorageAPI底层调用的是操作系统的密钥链Windows 上是 Credential ManagermacOS 上是 Keychain比存在配置文件里安全得多。3. 核心细节解析与实操要点3.1 如何准确获取 Git 变更内容获取变更内容是整个插件的基础拿不到准确的 diff后面生成的信息就是空中楼阁。VSCode 的 Git 扩展 API 提供了几个关键对象repository.state.indexChanges已暂存的变更repository.state.workingTreeChanges未暂存的变更repository.diffWithHEAD(path)某个文件相对于 HEAD 的 diff我实际用的是diffWithHEAD方法因为它返回的是完整的 diff 文本包含上下文行信息量最全。但这里有个坑如果变更文件很多diff 文本会非常长直接丢给模型既浪费 token 又可能超出上下文限制。我的处理策略是分层截断优先保留已暂存文件的 diff如果总长度超过阈值我设的是 8000 字符就按文件逐个累加超出部分截断并在末尾加一句提示说明有截断。这样既保证了核心信息不丢又控制了请求体积。async function collectDiff(repository: any): Promisestring { const changes repository.state.indexChanges.length 0 ? repository.state.indexChanges : repository.state.workingTreeChanges; let result ; const MAX_LEN 8000; for (const change of changes) { const diff await repository.diffWithHEAD(change.uri.fsPath); if (result.length diff.length MAX_LEN) { result \n... (diff truncated); break; } result diff \n; } return result; }注意diffWithHEAD是异步方法必须用await否则拿到的是 Promise 对象后续处理会报错。这个坑我踩过一次调试了半天才发现。3.2 提示词工程让模型输出规范的提交信息拿到 diff 之后下一步是构造提示词。提示词的质量直接决定了生成结果的好坏。我试过很多版本最终稳定下来的提示词结构是这样的你是一个专业的代码提交信息生成助手。请根据以下代码变更生成一条符合 Conventional Commits 规范的提交信息。 要求 1. 格式为 type(scope): subject 2. type 从 feat/fix/docs/style/refactor/test/chore 中选择 3. subject 用中文描述不超过 50 字 4. 只输出提交信息本身不要任何解释 代码变更 {diff}这里有几个关键点值得展开说。第一明确指定格式。如果不指定模型可能生成一大段描述性文字还得自己裁剪。第二限定 type 的取值范围。Conventional Commits 规范里的 type 是固定的几个让模型自由发挥容易跑偏。第三要求中文输出。默认情况下模型倾向于输出英文明确要求中文能省去翻译步骤。第四强调只输出结果。不加这一条模型经常会加一句这是为您生成的提交信息之类的废话。实测下来这套提示词的准确率在 90% 以上偶尔需要微调但基本可用。3.3 密钥安全存储的实现细节API Key 的存储我用了SecretStorage代码不复杂但有几个细节要注意// 存储 await context.secrets.store(commitAi.apiKey, apiKey); // 读取 const apiKey await context.secrets.get(commitAi.apiKey); // 删除 await context.secrets.delete(commitAi.apiKey);提示SecretStorage在 Windows 上有长度限制如果密钥特别长超过 2500 字符可能会存储失败。遇到这种情况可以分段存储或者改用配置文件加本地加密的方式。不过一般的大模型 API Key 都在 100 字符以内不用担心这个问题。另外读取密钥时要做好空值判断。如果用户还没配置密钥就点了生成按钮要给出明确的提示引导用户去配置而不是直接报错。3.4 界面交互按钮放哪里最合适VSCode 插件的界面入口主要有几个位置命令面板、状态栏、源代码管理面板的标题栏、编辑器右键菜单。我最终选择了源代码管理面板标题栏因为这是用户执行提交操作时视线自然停留的地方符合操作动线。在package.json里配置菜单项{ contributes: { commands: [ { command: commitAi.generate, title: 生成提交信息, icon: $(sparkle) } ], menus: { scm/title: [ { command: commitAi.generate, group: navigation } ] } } }scm/title就是源代码管理面板的标题栏group设为navigation表示放在导航区域也就是图标按钮那一排。图标用的是 VSCode 内置的$(sparkle)不需要额外准备图片资源。4. 完整实操流程与核心环节实现4.1 项目初始化与依赖安装第一步是创建项目骨架。我习惯用官方脚手架省去手动配置的麻烦npm install -g yo generator-code yo code选择New Extension (TypeScript)然后按提示填写插件名称、标识符、描述等信息。生成的项目结构大致如下commit-ai/ ├── src/ │ └── extension.ts ├── package.json ├── tsconfig.json └── .vscodeignore接下来安装几个必要的依赖npm install --save-dev esbuild types/vscode types/nodeesbuild用于打包types/vscode提供 VSCode API 的类型定义types/node提供 Node.js 内置模块的类型。4.2 核心逻辑的编写主逻辑我拆成了三个模块gitHelper.ts负责获取变更aiClient.ts负责调用接口extension.ts负责串联和注册命令。这样拆分的好处是每个模块职责单一方便单独测试和替换。aiClient.ts的核心代码如下import * as https from https; export async function generateCommitMessage( diff: string, apiKey: string, endpoint: string, model: string ): Promisestring { const prompt buildPrompt(diff); const body JSON.stringify({ model: model, messages: [{ role: user, content: prompt }], temperature: 0.3, max_tokens: 200 }); return new Promise((resolve, reject) { const url new URL(endpoint); const req https.request({ hostname: url.hostname, path: url.pathname, method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey}, Content-Length: Buffer.byteLength(body) } }, (res) { let data ; res.on(data, chunk data chunk); res.on(end, () { try { const json JSON.parse(data); resolve(json.choices[0].message.content.trim()); } catch (e) { reject(new Error(解析响应失败: data)); } }); }); req.on(error, reject); req.write(body); req.end(); }); }这里temperature设成 0.3是为了让输出更稳定减少随机性。max_tokens设成 200因为提交信息本身很短设太大浪费额度。4.3 参数配置与选择依据插件暴露了几个配置项放在package.json的configuration字段里配置项类型默认值说明commitAi.endpointstring空接口地址需用户填写commitAi.modelstring空模型名称需用户填写commitAi.maxDiffLengthnumber8000diff 最大字符数commitAi.languagestringzh-CN生成信息的语言maxDiffLength这个参数我调过好几次。设太小变更多的时候信息不全设太大请求慢还费 token。8000 字符大概对应 2000 个 token 左右对于大多数提交来说足够了。如果某次变更特别大比如重构了整个模块可以临时调高这个值。language参数是为了支持多语言团队。默认中文需要英文提交信息的团队可以改成en-US提示词里对应调整即可。4.4 打包与安装的完整步骤代码写完之后打包成.vsixnpm run compile vsce package如果package.json里缺少publisher字段vsce会报错随便填一个就行比如你的名字或团队名。打包成功后会在根目录生成一个commit-ai-0.1.0.vsix文件。安装到 VSCodecode --install-extension commit-ai-0.1.0.vsix安装完成后重启 VSCode在源代码管理面板就能看到那个闪光图标了。第一次点击会提示配置 API Key配置完再点一次就能生成提交信息。注意如果安装时提示扩展与当前 VSCode 版本不兼容检查package.json里的engines.vscode字段把它改成你当前 VSCode 的版本号或者更低。我一开始写的是^1.80.0但测试机上是 1.75就装不上改成^1.70.0就好了。5. 常见问题与排查技巧实录5.1 生成失败的高频原因排查实际用下来生成失败的原因就那么几类我整理成了一张速查表现象可能原因排查方法提示未配置 API Key密钥没存或存储失败重新执行配置命令检查系统密钥链请求超时网络不通或接口地址错误用 curl 测试接口连通性返回 401API Key 无效或过期检查密钥是否正确是否有多余空格返回 429请求频率超限降低调用频率或更换模型生成内容为空diff 为空或提示词问题确认有变更检查 diff 获取逻辑生成内容乱码编码问题检查响应解析时的字符编码其中最常见的是 401 和超时。401 基本都是密钥问题注意复制密钥时不要带前后空格。超时的话先确认接口地址能不能 ping 通再确认端口对不对。5.2 我踩过的几个坑第一个坑diff 获取为空。有次测试时怎么都拿不到变更内容后来发现是因为文件没有保存。VSCode 的 Git 扩展只识别已保存的文件变更未保存的修改不在 diff 里。解决办法是在获取 diff 之前先执行一次保存所有文件的操作或者提示用户先保存。第二个坑中文乱码。早期版本用res.setEncoding(utf8)处理响应结果某些接口返回的中文还是乱码。后来改成手动拼接 Buffer 再统一转码问题就解决了。具体做法是不设置编码让data事件返回 Buffer最后用Buffer.concat(chunks).toString(utf8)转换。第三个坑打包后插件不生效。本地调试时一切正常打包安装后命令却找不到。排查发现是esbuild的配置问题external字段没有排除vscode模块导致打包时把 VSCode 的 API 也打进去了。在 esbuild 配置里加上external: [vscode]就好了。第四个坑多仓库场景。如果工作区里有多个 Git 仓库vscode.git扩展的repositories数组会有多个元素。我一开始只取了第一个导致在第二个仓库里操作时拿不到变更。正确的做法是根据当前活动编辑器所在的路径匹配对应的仓库。5.3 提升生成质量的几个技巧除了修 bug我还总结了一些提升生成质量的经验。第一提交前先暂存。插件优先读取暂存区的变更如果暂存区为空才读工作区。养成先git add再生成的习惯生成的信息会更聚焦。第二小步提交。一次提交只做一件事diff 越干净生成的信息越准确。如果一次改了好几个不相关的功能生成的信息会变成大杂烩。第三善用 scope。如果项目有明确的模块划分可以在提示词里加上模块列表让模型从列表里选 scope而不是自己编。提示如果对生成结果不满意不要直接改提交信息而是调整提示词重新生成。改提示词是一次投入长期受益的事改结果只是临时应付。6. 后续可以继续扩展的方向这个插件目前只做了最核心的功能但扩展空间还很大。我列几个自己打算后续加上的点供你参考。第一支持多模型切换。现在只支持一种接口格式后续可以抽象出一个适配层支持不同厂商的接口用户可以在配置里选择用哪个模型。实现思路是定义一个统一的接口每个厂商写一个适配器调用时根据配置选择对应的适配器。第二增加提交信息模板。有些团队有固定的提交信息格式要求比如必须关联需求编号。可以做一个模板功能让用户自定义格式生成时把模型输出填充到模板里。第三支持生成变更摘要。除了提交信息还可以生成一段变更摘要用于写周报或者合并请求描述。这个功能复用现有的 diff 获取和接口调用逻辑只需要换一套提示词。第四增加本地缓存。同样的 diff 没必要重复请求可以做一个简单的缓存key 是 diff 的哈希值value 是生成结果。这样重复操作时能省下不少时间和额度。第五支持快捷键。现在只能点按钮加上快捷键会更顺手。在package.json的keybindings字段里配置即可比如绑定CtrlAltC。我在实际使用中最大的体会是工具的价值不在于功能多而在于能不能真正融入日常工作流。这个插件从想法到落地大概花了两个周末但省下的时间早就超过投入了。如果你也受够了写提交信息这件事不妨照着上面的思路自己做一个改造成最适合自己习惯的样子。