作者实战演示用cron job读取git历史、AI模型分组提交并生成可读发布日志,全程跑在免费服务器上、零成本运行。
周五十下午。你打好了 v0.4.0 标签,打开 CHANGELOG.md,盯着 48 条 commit 发呆。其中 3 条是真正的功能。其余都是噪音。
这种每周一次的例行公事是一个非常适合作为第一个 Agent 项目的场景。输入有边界,输出风险低——写错了顶多是一份让人看不懂的更新日志,不会导致生产系统崩溃。
这是一个小项目的案例研究:一个定时任务,读取 git 历史、对 commit 分组、让语言模型起草更新日志、再写入一份可供审计的决策账本。运行在免费服务器上,每次执行零成本。
大多数更新日志自动化工具只做到分组这一步。像 git-cliff 这样的工具会按 conventional-commit 类型对 commit 排序并拼接它们。这能用,但结果读起来像日志,不像更新日志。
语言模型可以做后半段工作:把分好组的 commit 转化成用户真正能看懂的句子。障碍还是那两个——API 成本和托管成本。对于一个个人项目,两者都很难合理化。
MonkeyCode 的开源项目正好针对这两个障碍,提供了免费模型访问和免费服务器选项。声明:本文是 MonkeyCode 产品推广的一部分。下面的设置同时用到了两者,但这个模式不依赖特定提供商;你可以替换成任何聊天 API。
这个 Agent 有四个任务,按顺序执行:
# collect.py
import subprocess
def last_tag(repo: str = ".") -> str:
out = subprocess.check_output(
["git", "-C", repo, "tag", "--sort=-creatordate"], text=True
)
return out.splitlines()[0]
def commits_since(tag: str, repo: str = ".") -> list[dict]:
cmd = ["git", "-C", repo, "log", "--no-merges", "--oneline", f"{tag}..HEAD"]
out = subprocess.check_output(cmd, text=True)
commits = []
for line in out.splitlines():
hash_, message = line.split(" ", 1)
commits.append({"hash": hash_, "message": message})
return commits
--no-merges 很关键。Merge commit 会复制它们所合并的变更,而且模型会高兴地把同一个功能列两遍。
# group.py
import re
from collections import defaultdict
TYPE_PATTERN = re.compile(r"^(feat|fix|docs|refactor|chore|perf|test|build|ci)(\(.+\))?!?: (.+)")
def group_commits(commits: list[dict]) -> dict[str, list[str]]:
groups = defaultdict(list)
for c in commits:
m = TYPE_PATTERN.match(c["message"])
key = m.group(1) if m else "other"
groups[key].append(c["message"])
return dict(groups)
这个正则表达式是整个可靠性的核心。如果 commit 消息不遵循规范,就会落入 other——而下面的 prompt 会告诉模型保守地总结那一组。
def build_prompt(groups: dict[str, list[str]]) -> str:
lines = []
for kind in ("feat", "fix", "perf", "docs", "refactor", "chore", "other"):
items = groups.get(kind, [])[:20] # hard cap per group
if not items:
continue
lines.append(f"## {kind}")
lines.extend(f"- {m}" for m in items)
return (
"You are drafting release notes for a small open-source project.\n"
"Rewrite the grouped commits below as a short changelog.\n"
"Rules:\n"
"- Put user-visible changes first.\n"
"- Do not invent changes. If a group is empty, skip it.\n"
"- Keep it under 150 words.\n\n"
+ "\n".join(lines)
)
每组 20 条的限制是一个 token 预算的伪装。2000 条 commit 的范围也塞不进免费层的 prompt;截断机制保证了执行成本低廉、起草结果可读。
# adapter.py — 传输层有意省略。
# 在这里插入你提供商的客户端。契约很简单:
# 发送 prompt,接收 markdown 文本,超时重试一次。
def draft_changelog(prompt: str) -> str:
raise NotImplementedError("add your provider client")
具体的 HTTP 调用取决于你选择的提供商。重要的是契约:进去一个 prompt,出来 markdown,超时重试一次。不要对空输出重试——空白的草稿意味着 prompt 或分组坏了,重试只会掩盖这个问题。
# ledger.py
import datetime
import json
def write_ledger(groups: dict[str, list[str]], draft: str, path: str = "ledger.jsonl") -> None:
entry = {
"date": datetime.date.today().isoformat(),
"commit_count": sum(len(v) for v in groups.values()),
"groups": {k: len(v) for k, v in groups.items()},
"draft": draft,
}
with open(path, "a") as f:
f.write(json.dumps(entry) + "\n")
最近的一个 DEV 讨论认为,Agent 应该记住决策,而不只是数据。这个账本就是这个小想法的最大化实现:每次运行都记录模型看到了什么、输出了什么。当草稿出错时,你可以判断是模型的问题还是输入的问题。
0 9 * * 1 cd /srv/release-notes && /usr/bin/python3 run.py >> run.log 2>&1
周一 09:00,在你打开更新日志之前。如果你的服务器时钟有漂移,设置 CRON_TZ=UTC。
# test_group.py
def test_group_commits():
fake = [
{"hash": "a1b2c3", "message": "feat: add retry with backoff"},
{"hash": "d4e5f6", "message": "fix: handle empty queue"},
{"hash": "g7h8i9", "message": "update README"},
]
groups = group_commits(fake)
assert groups["feat"] == ["feat: add retry with backoff"]
assert groups["fix"] == ["fix: handle empty queue"]
assert groups["other"] == ["update README"]
为截断限制和空仓库场景各加一个测试。对于这个规模的脚本,这些覆盖已经足够。
## Features
- Added retry with exponential backoff to the queue worker.
- New `--dry-run` flag for batch jobs.
## Fixes
- Fixed a race condition when two jobs claimed the same row.
## Other
- Documentation updates and dependency bumps.
以及生成它的账本条目:
{"date": "2026-08-24", "commit_count": 47, "groups": {"feat": 2, "fix": 1, "other": 44}, "draft": "## Features\n- Added retry..."}
第一,草稿很少能直接发布,但确实把从滚动翻阅几分钟缩短到了快速编辑几秒钟。第二,账本发现了一个真实的 bug:模型悄悄丢弃了 perf 组,因为解析器把这些 commit 误标成了 other。没有账本,这种丢失根本无法察觉。
不符合 conventional commits 规范的仓库。备用方案能用,但草稿会变得模糊,模型开始臆测。
大型 monorepo。庞大的 commit 范围会冲破任何 token 预算。硬截断然后接受损失。
直接发布原文的团队。草稿是起点,不是法律文件。发布前需要人工签字。
免费层配额会变。在把管道建立在任何免费配额上之前,先查看 MonkeyCode 当前的条款——本文故意不引用可能已经过时的数字。
Prompt 契约比模型更重要。分组代码做可靠的工作;模型只负责改写。
可审计性把黑盒变成工具。一份 JSONL 文件让每份草稿都可以解释。
免费基础设施改变了哪些问题值得自动化。零边际成本下,一个每周十分钟的任务变得值得自动化了。
整个脚本约 120 行。指向你的仓库,让它跑一次,然后读账本。如果你想尝试,MonkeyCode 的免费层(1000 万 token)和免费服务器选项足够运行这个项目——而且它是开源的,你可以在信任之前仔细检查 Agent 发送的具体内容。