昨晚熬夜写了个 VS Code 插件,彻底解放写 Commit Message 的双手
昨晚写代码写到凌晨,准备提交的时候,看着空荡荡的 commit message 输入框,突然不想动脑子了。
"这是 feat 还是 fix?scope 填 auth 还是 user?message 怎么概括才得体?" ------ 每次都要在大脑里过一遍这些 Conventional Commits 的规则,真的很累。
于是,从昨晚 11 点开始,到今天上午发布上线,花了不到 4 个小时,我vibe coding了一个极其顺手的 Commit Message 生成器。
写完复盘了一下,发现虽然功能简单,但为了"极致的体验",里面用了不少有意思的技术细节。
为什么还要造轮子
其实 VS Code 原生就支持 Copilot 生成 commit message,但我之前的 Copilot 服务因为账号问题用不了了(懂的都懂)。
没了 Copilot,我去市场搜了一圈替代品,发现最大的痛点是:不够智能,也不够灵活。
这就导致了两个问题:
- 没法适应团队规范:很多插件生成的格式是死的,但我们团队要求特定的 type 或 scope 格式,甚至要求用中文/日文写 commit。
- 配置极其繁琐:想用自己的 API Key,得填一堆 Provider ID、Endpoint,非常折腾。
我想要的是:
- 完全的 Prompt 自定义权限:不仅仅是改个前缀,而是能完全控制 AI 的系统提示词(System Prompt)。我想让它用中文写、用emoji、或者严格遵循 Angular 规范,都能通过改 Prompt 实现。
- 不想填一堆配置:给我一个 API Key,一个 Base URL,一个 Model 名字,这就够了。管你是 OpenAI、Claude 还是 Ollama 本地模型,只要兼容 OpenAI 格式,通通能用。
- 懂我的代码:不要傻傻地把几千行 diff 扔给 AI,它会晕;也不要粗暴地截断,它会瞎。
- 要有爽感 :点一下,文字必须像打字机一样一个个蹦出来(流式输出),而不是转圈转半天然后
啪一下甩我脸上。
基于这些需求,我 vibe coding 出了这个插件。
核心设计
1. 统一的 API 抽象
最初我设计了四个 Provider:OpenAI、Claude、Gemini、Custom。后来发现这是过度设计------现在大部分 LLM 服务都兼容 OpenAI 格式,没必要分开。
最终只留了三个配置项:
typescript
interface ProviderConfig {
apiKey: string;
model: string;
baseUrl: string; // https://api.openai.com/v1 或 http://localhost:11434/v1
}
用 OpenAI?填 api.openai.com/v1。用 Claude 代理?换个 baseUrl。用本地 Ollama?localhost:11434/v1。一套代码全搞定。
2. 流式输出
等 AI 生成完再显示结果,体验很差。用户会以为插件卡死了。
解决方案是 SSE(Server-Sent Events)流式输出:
typescript
// 请求时开启流式
body: JSON.stringify({
model: this.config.model,
messages: [{ role: 'user', content: prompt }],
stream: true // 关键
})
然后解析 SSE 格式的响应:
typescript
async function readOpenAICompatibleStream(
body: ReadableStream<Uint8Array>,
onToken: (text: string) => void
): Promise<string> {
const reader = body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let result = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE 事件以双换行分隔
let sepIndex;
while ((sepIndex = buffer.indexOf('\n\n')) !== -1) {
const event = buffer.slice(0, sepIndex);
buffer = buffer.slice(sepIndex + 2);
for (const line of event.split('\n')) {
if (!line.startsWith('data:')) continue;
const payload = line.slice(5).trim();
if (payload === '[DONE]') return result;
const json = JSON.parse(payload);
const delta = json?.choices?.[0]?.delta?.content ?? '';
if (delta) {
result += delta;
onToken(delta); // 每个 token 实时回调
}
}
}
}
return result;
}
每收到一个 token,就往 VS Code 的 commit 输入框里追加,用户能看到文字一个一个蹦出来。
3. 智能 type/scope 推断
Conventional Commits 格式是 type(scope): description。让 AI 自己猜 type 有时候不太准,比如改了测试文件,AI 可能还是写 feat。
我加了一层启发式推断:
typescript
function inferType(files: string[]): string {
const isDocsFile = (f) => f.endsWith('.md') || f.startsWith('docs/');
const isTestFile = (f) => f.includes('test') || /\.(test|spec)\.[jt]sx?$/.test(f);
const isCIFile = (f) => f.startsWith('.github/workflows/');
if (files.every(isDocsFile)) return 'docs';
if (files.every(isTestFile)) return 'test';
if (files.some(isCIFile)) return 'ci';
// ...
return 'feat';
}
把推断结果作为"建议"传给 AI,而不是强制限制。这样 AI 既有参考,又保留了灵活性:
markdown
## Suggestions (optional)
- Suggested type: docs
- Suggested scope: readme
4. 智能 Diff 裁剪
大型重构可能有几千行 diff,直接喂给 AI 会超 token 限制。简单粗暴地截断前 N 个字符,可能会把函数签名截断一半,AI 看不懂。
我的做法是语义裁剪:
typescript
function summarizeDiffBlock(block: string, budget: number): string {
const lines = block.split('\n');
const header = []; // 保留 diff 头部
const important = []; // 保留关键行
// 用正则识别关键行
const signatureLike = /^[+-]?\s*(export\s+)?(function|class|interface)/;
const commentLike = /^[+-]?\s*(\/\/|#|\/\*)/;
for (const line of lines) {
if (signatureLike.test(line) || commentLike.test(line)) {
important.push(line);
}
}
return [...header, ...important].join('\n');
}
优先保留:
- 函数/类签名
- 注释
- import 语句
- hunk 的前 6 行变更
这样即使原始 diff 有 5000 行,裁剪到 500 行后,AI 仍然能理解代码的意图。
5. 单行/多行模式
有人喜欢简洁的单行 commit,有人喜欢带 body 的详细版本。
我加了 outputStyle 配置:
typescript
type OutputStyle = 'headerOnly' | 'headerAndBody';
不同模式下,prompt 模板会动态变化:
handlebars
{{#if header_only}}
- Output ONLY ONE line (header only): no body, no footer
- Keep it concise (max 72 characters)
{{/if}}
{{#if allow_body}}
- Output a header line and optionally a short body
{{/if}}
headerOnly 模式还有个优化:检测到第一个换行符就立即中断流式输出,不浪费后面的 token:
typescript
if (headerOnly) {
const newlineIndex = text.search(/\r?\n/);
if (newlineIndex !== -1) {
abortController.abort(); // 立即停止
return;
}
}
6. 后处理清洗
AI 有时候会输出一些垃圾:
Commit message: feat: ...(带前缀)feat: ...(带 markdown 代码块)"feat: ..."(带引号)
我加了一层后处理:
typescript
function normalizeCommitMessage(raw: string, options: { headerOnly: boolean }): string {
let text = raw;
// 去掉 markdown 代码块
text = text.replace(/```[\s\S]*?```/g, (block) => {
const lines = block.split('\n');
return lines.slice(1, -1).join('\n'); // 只保留内容
});
// 去掉常见前缀
text = text.replace(/^\s*(commit message|message)\s*:\s*/i, '');
// 去掉首尾引号
text = text.replace(/^["'`]+|["'`]+$/g, '');
if (options.headerOnly) {
return text.split('\n').find(l => l.trim()) ?? '';
}
return text;
}
7. 暂存区智能感知
很多时候写完代码忘了 git add 就直接点生成,大部分插件会报错 "No staged changes"。
我觉得工具应该更聪明一点:
- 只有暂存区代码:直接生成(标准流程)。
- 只有工作区代码(未暂存) :直接生成(省去
git add步骤)。 - 都有:弹窗让用户选。
typescript
const hasStaged = await git.hasStagedChanges();
const hasUnstaged = await git.hasUnstagedChanges();
if (!hasStaged && !hasUnstaged) {
return vscode.window.showWarningMessage('No changes found');
}
if (hasStaged && hasUnstaged) {
// 弹窗让用户选
const picked = await vscode.window.showWarningMessage(
'Detected both staged and unstaged changes',
'Use Staged',
'Use Unstaged'
);
// ...
}
这样在这个微小的交互上,又能少点一次鼠标。
打包发布
VS Code 插件用 vsce 打包:
bash
npx vsce package
为了减小包体积,我用了 esbuild 打包,把所有 TypeScript 代码编译成一个 extension.js:
javascript
// esbuild.mjs
await esbuild.build({
entryPoints: ['src/extension.ts'],
bundle: true,
outfile: 'dist/extension.js',
platform: 'node',
external: ['vscode'],
minify: production
});
最终 .vsix 包只有 435 KB,8 个文件。
总结
这个插件的核心思路就是:在对的地方做对的事。
- API 层:统一抽象,不过度设计
- 交互层:流式输出,即时反馈
- 推理层:启发式辅助,而非强制替代
- 数据层:语义裁剪,而非暴力截断
- 输出层:后处理清洗,容错 AI 的奇怪输出
代码不多,但每个模块都解决了一个实际问题。
项目已开源:vscode-ai-commit
VS Code Marketplace 搜索 "AI Commit" 可以直接安装。
如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:
Claude Code Skills (按需加载,意图自动识别,不浪费 token,介绍文章):
- code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)
- 5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活
- first-principles-skill - 第一性原理思考,适合架构设计和技术选型
qwen/gemini/claude - cli 原理学习网站:
-
coding-cli-guide(学习网站)- 学习 qwen-cli 时整理的笔记,40+ 交互式动画演示 AI CLI 内部机制
全栈项目(适合学习现代技术栈): -
prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑
-
chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB
