前言
上周群里突然说要查绩效,每个人每天交一份日报。
我这人记性不太好,到下班点根本想不起来今天到底干了啥。只能打开 git log,翻一遍提交记录,复制粘贴,再润色成领导能看懂的"人话"。一来二去半小时没了,本来能准点走的,结果又坐了半小时。
而且我这 commit message 写得也不咋样,改了一堆文件,到提交那一下脑子是空的,经常就敲个 update: 代码更新 完事。回头自己看都懵。
有天突然反应过来:信息不都在 git log 里吗?我只是缺个帮我总结的人。
DeepSeek 接口挺便宜,百万 token 一块钱。那就让它干这活呗。花了一个下午摸了个小工具,干两件事:
- 提交代码的时候,AI 看一眼 diff,帮我生成 commit message,我确认完再提交
- 提交完,AI 把今天所有 commit 拉一遍,自动生成工作日报
从此不用自己写了。下面说下怎么搞的,顺便记下踩的坑。
一、整体结构
就一个 git-hooks 文件夹,放俩脚本:
bash
项目根目录/
├── git-hooks/
│ ├── commit.js # 提交用的,AI 生成 message + 我确认
│ ├── generate-report.js # 日报的,AI 读 commit 生成日报
│ ├── package.json # 单独管 node-fetch 依赖
│ └── node_modules/
├── .git/hooks/
│ └── post-commit # 提交后自动跑日报脚本
├── package.json # 根目录,加个 npm run commit
└── daily-report.md # 日报输出到这里(加进 .gitignore 了)
没啥花里胡哨的,俩脚本一个钩子,够用了。
二、提交那部分:commit.js
一开始踩的坑
我一开始想的是用 prepare-commit-msg 钩子------AI 生成后直接写进 commit message 文件,然后 Git 打开编辑器让我改。听起来挺优雅,结果一用就翻车:
- Git 配的编辑器路径是 VS Code 的,但我实际用的是 Trae,路径不对,提交直接报错
- 后来用 IDE 的 Git 面板提交,根本不触发编辑器,AI 写完就被拿走了,我想改都没机会
折腾半天,干脆换思路:不挂钩子了,手动跑命令,在终端里确认。
csharp
git add . → AI 生成 message → 终端敲 Y/N → 提交
土是土了点,但稳。
实现
核心代码就这些,其他是错误处理:
javascript
const { execSync } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const readline = require('readline');
const fetch = require('node-fetch');
const AI_API_URL = 'https://api.deepseek.com/v1/chat/completions';
const AI_API_KEY = 'your-api-key';
function ask(rl, question) {
return new Promise(resolve => {
rl.question(question, answer => resolve(answer));
});
}
async function main() {
// 1. 先 git add .
execSync('git add .', { stdio: 'pipe' });
// 2. 拿 diff,概览和详情都给 AI
const diffStat = execSync('git diff --cached --stat', { encoding: 'utf-8' });
const diffDetail = execSync('git diff --cached --unified=3', {
encoding: 'utf-8',
maxBuffer: 1024 * 1024 * 5
});
// 3. 喂给 AI
const prompt = `你是一个专业的开发人员。请根据以下 Git 代码变更,生成一条简洁的中文 commit message。
要求:
1. 格式:类型: 简短描述(如 "修复: xxx" / "新增: xxx" / "优化: xxx")
2. 只输出一行,不要解释
3. 不超过 50 个字
变更文件概览:
${diffStat}
代码变更详情:
${diffDetail}
`;
const response = await fetch(AI_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${AI_API_KEY}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [{ role: 'user', content: prompt }],
max_tokens: 100,
temperature: 0.3
})
});
const data = await response.json();
const aiMsg = data.choices[0].message.content.trim().replace(/^["']|["']$/g, '');
// 4. 终端确认
console.log('AI 生成: ' + aiMsg);
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const choice = await ask(rl, '[Y]使用 / [N]修改 / [其他]取消: ');
let finalMsg = aiMsg;
if (choice.toLowerCase() === 'n') {
finalMsg = (await ask(rl, '改一下: ')).trim();
} else if (choice.toLowerCase() !== 'y' && choice !== '') {
rl.close(); return;
}
rl.close();
// 5. 提交
const tmpFile = path.join(os.tmpdir(), `git-msg-${Date.now()}.txt`);
fs.writeFileSync(tmpFile, finalMsg, 'utf-8');
execSync(`git commit -F "${tmpFile}"`, { stdio: 'inherit' });
fs.unlinkSync(tmpFile);
}
main();
几个细节
为啥用 git commit -F 不用 -m?
message 里万一有个引号、有个 $ 啥的,-m "${msg}" 就炸了。写进临时文件用 -F 读最省心:
javascript
// 别这么写,message 含引号就完蛋
execSync(`git commit -m "${finalMsg}"`);
// 这样写,啥特殊字符都不怕
fs.writeFileSync(tmpFile, finalMsg, 'utf-8');
execSync(`git commit -F "${tmpFile}"`);
temperature: 0.3 是啥意思?
温度低点,AI 输出更稳,不会给我整花活------比如给 commit message 加个标题党,或者后面跟一段"以上是 AI 生成的说明"。
AI 挂了咋办?
接口会抖、会限流,不能让它把提交流程卡死。所以 catch 里直接降级成手动输入:
javascript
catch (err) {
console.error('AI 挂了:', err.message);
const rl = createRl();
aiMsg = (await ask(rl, '自己写吧: ')).trim();
rl.close();
}
工具是来帮忙的,不能变成负担。
三、日报那部分:generate-report.js
思路
每次 git commit 完,post-commit 钩子在后台跑这个脚本。它干三件事:
git log --since="midnight"拿今天所有提交- 让 AI 归总成日报
- 写进
daily-report.md
第 3 步有个坑,单拎出来说。
同一天别重复追加(重点)
第一版我直接 fs.appendFileSync,每提交一次就追加一条。结果某天提交了 5 次,日报文件里就有 5 条同一天的日报,内容还都差不多------这玩意给领导看是要挨骂的。
改成:先把今天的旧块全删掉,再追加一条新的。
javascript
const reportPath = path.resolve(__dirname, '../daily-report.md');
const dateStr = new Date().toLocaleDateString('zh-CN'); // 2026/8/25
const newBlock = `\n\n## 📅 ${dateStr} 工作日报\n\n${aiReport}\n`;
// 读已有内容,没有就空串
let oldContent = '';
try { oldContent = fs.readFileSync(reportPath, 'utf-8'); } catch (e) {}
// 把今天的旧块全部匹配出来删掉
// 日期里的 / 要转义,加 g 标志匹配所有(一天可能有多条旧的)
const escapedDate = dateStr.replace(/\//g, '\\/');
const todayBlockRegex = new RegExp(
'\\n*## 📅 ' + escapedDate + '[\\s\\S]*?(?=\\n## 📅|$)', 'g'
);
const cleanedContent = oldContent.replace(todayBlockRegex, '');
if (cleanedContent !== oldContent) {
// 今天已经有旧的,清掉再追加新的
fs.writeFileSync(reportPath, cleanedContent.trimEnd() + newBlock, 'utf-8');
} else {
// 今天还没写过,直接追加
fs.appendFileSync(reportPath, newBlock, 'utf-8');
}
那个正则拆开看:/\\n*## 📅 2026\\/8\\/25[\\s\\S]*?(?=\\n## 📅|$)/g
\\n*--- 前面的空行也一起带走## 📅 2026/8/25--- 今天的标题[\\s\\S]*?--- 内容,非贪婪(?=\\n## 📅|$)--- 遇到下一天的标题或者文件末尾就停g--- 全局匹配,一天多条旧日报全清掉
这么搞完,一天不管提交多少次,日报文件里始终就一条,内容是最后一次提交后的全量总结。
Prompt
javascript
const prompt = `
你是一个专业的项目经理。请根据以下 Git 提交记录,帮我生成一份工作日报。
要求:
1. 语言简练,结构清晰,分为【今日完成】、【进行中/遇到的问题】两个部分。
2. 将相似的提交合并,不要逐条翻译 commit message。
3. 只输出 Markdown 格式的日报内容,不要输出任何解释性的废话。
Git 提交记录如下:
${logs}
`;
关键是第 2 条------别逐条翻译。不然 AI 就把 5 条 commit 原样翻一遍写成 5 行,那跟我自己复制粘贴有啥区别。让它自己归类,输出才有"日报"的样子。
四、钩子配置
post-commit(后台跑,别挡道)
bash
#!/bin/sh
node "git-hooks/generate-report.js" > git-hooks/report.log 2>&1 &
最后那个 & 一定得加。我一开始没加,结果每次 git commit 都得等 AI 接口返回才能完成提交,卡得我想砸键盘。加个 & 后台跑,提交瞬间完成,日报慢慢生成去。
package.json 加一条
json
{
"scripts": {
"commit": "node git-hooks/commit.js"
}
}
以后提交就一句 npm run commit。
.gitignore 别忘了
gitignore
# 日报是私人物品,别进代码库
daily-report.md
git-hooks/report.log
五、实际跑起来啥样
提交
ini
$ npm run commit
📦 正在暂存变更 (git add .)...
🤖 AI 正在生成 commit message...
──────────────────────────────────────────
📝 AI 生成的 commit message:
新增: 添加AI辅助提交工具及日报更新
──────────────────────────────────────────
是否使用?[Y]使用 / [N]修改 / [其他键]取消: y
[main 6f663da] 新增: 添加AI辅助提交工具及日报更新
4 files changed, 165 insertions(+), 4 deletions(-)
✅ 提交成功
日报
markdown
## 📅 2026/8/25 工作日报
## 今日完成
- 开发并引入AI辅助提交工具,支持自动暂存变更并联动更新日报,提升提交效率
- 重构日报生成逻辑,完善省份映射关系,优化生成准确性
## 进行中/遇到的问题
- 期间存在多次零散代码更新,提交信息不够规范,已通过工具化手段逐步改善提交质量
提交多少次,日报始终就一条,内容是当天全量总结。
六、踩的坑
坑 1:API 地址写半截
javascript
// ❌ 这样调,返回的是网页 HTML,不是 JSON
const AI_API_URL = 'https://api.deepseek.com';
// ✅ 得用完整接口地址
const AI_API_URL = 'https://api.deepseek.com/v1/chat/completions';
报错 invalid json response body,因为请求打到根路径,人家给你返回一网页。
坑 2:post-commit 阻塞提交
bash
# ❌ 前台跑,提交得等 AI 返回
node "git-hooks/generate-report.js"
# ✅ 后台跑,提交秒完成
node "git-hooks/generate-report.js" > git-hooks/report.log 2>&1 &
坑 3:Windows 没 chmod
想给钩子文件加执行权限,结果 PowerShell 压根没这命令。后来才知道,Windows 上 Git 钩子只要文件在、能访问,就会自动执行,不用额外设权限。
坑 4:prepare-commit-msg + 编辑器
前面说过了,钩子写入 message 后让 Git 开编辑器给我改------编辑器路径不对、IDE 面板不触发编辑器,各种不对付。最后还是扔了钩子,改成手动跑 + 终端 readline 交互,最省心。
坑 5:日报重复追加
第一版每提交一次追加一条,一天 5 次提交就有 5 条重复日报。后来用正则 + g 标志先把今天的旧块全清掉,再追加新的,搞定。
七、花了多少钱
DeepSeek 价格:输入 0.001 元/千 token,输出 0.002 元/千 token。
算下来,生成一次 commit message 大概 2000 token,成本 0.004 元。日报一次 500 token 左右,0.001 元。
一天提交 10 次 + 生成 10 次日报,总共 5 分钱。一个月一块五。比一瓶水还便宜。
八、说在最后
这个工具的核心就一句话:
git diff 和 git log 里啥都有,AI 只是个翻译官,把它变成人话。
所以重点不在 AI 有多强,而在:
- 信息喂全乎------概览 + 详情都给,AI 才能总结准
- 给它兜底------AI 挂了就退回手动,别把提交卡死
- 日报别重复------同一天只留一条,幂等性这事儿
- 后台跑------日报生成慢,别挡着提交
代码上面都贴了,改下 API_KEY 就能用。key 去 platform.deepseek.com 注册,充一块钱能跑很久。
领导要查日报是吧,让 AI 写去,咱先走了。