过去两年我把大量时间花在“追 AI 轮子”上让大模型帮我写整个模块、自动提交代码、自动改 bug最后都被现实教育了。真正留存下来、每天都在给我省时间的反而是一套非常朴素的东西——几个 Python 脚本、Git 钩子、一个定时任务再加一个大模型接口。它没有花哨界面也不追求全自动但每个工作日下午我都能准点下班。这篇文章把这套自动化工作流从设计思路、技术选型到完整代码全部摊开适合所有想在 AI 时代少做重复劳动、又不想把代码库交给“黑盒”的程序员参考。1. 焦虑不是解药把重复劳动交给流程才是AI时代程序员的活路1.1 AI真正改变的是“边际成本”不是“写代码这件事”很多人都在问同一个问题AI 这么强程序员以后吃什么我的答案有点扫兴AI 没有让“写代码”这件事消失它只是把“写重复代码”的边际成本压到了几乎为零。以前写一个 CRUD 接口要一小时现在两分钟以前整理一份提交说明要五分钟现在五秒钟。成本下降是好事但真正致命的是另一件事——判断力。当生成代码变成低成本行为程序员的核心价值就不再是“能写出多少行”而是“知道哪些代码不该写、哪些改动不该合、哪些设计会埋雷”。而这种判断力恰恰不能靠 AI 替你完成只能靠你对自己项目的数据、流程和边界有足够清晰的理解。这也是为什么我一直不建议把 AI 往“全自动改代码”的方向硬推你越早交出判断权就越早失去对系统的掌控。那自动化工作流为什么是解法因为它的定位和“让 AI 写整个模块”完全不同。工作流处理的是确定性的流程谁在什么时机做什么、数据从哪来、结果往哪去每一步都是可以审计的。AI 只负责其中最需要“语义理解”的一环其他环节全部由代码控制。这样你既享受到 AI 的效率又保住了程序员的判断权。1.2 我最后保留下来的是一套不起眼的本地自动化工作流曾经我也试过不少商业效率工具、IDE 里的 AI 插件最后发现两个问题一是我的代码库在很多私有环境里数据出不去二是我需要的是“和我的 Git 工作流深度绑定”的能力而不是一个独立聊天窗口。于是我开始自建目标只有一条让 AI 处理那些“看完一遍就再也记不住”的杂事但所有动作必须有迹可循、可人工接管。这套工作流最终收敛成四个动作提交代码时自动生成规范的提交信息、提交前自动跑一轮代码自检建议、定期把 git 日志整理成 CHANGELOG、每天早上自动生成昨天的个人工作简报。清单听起来不高级但它们解决的是程序员每天最琐碎、最容易被中断的三类场景写提交信息、补文档、汇报工作。这篇文章后面所有内容都是围着这四个动作展开的。A 同学之前问我为什么不把这套东西包装成 CLI 工具发出去我的想法是工作流这东西贴着你的项目习惯才最有价值直接抄配置不如把原理搞懂。下面我从设计约束讲起然后给完整代码最后说坑。2. 先想清楚再动手这套智能工作流的四条铁律与目标场景2.1 四条铁律可回滚、人工确认、成本可控、全程审计自建自动化工作流最怕什么怕自动化变成“失控的主见”。所以我动手前给自己立了四条铁律后面所有代码都是按这四条写的一切自动修改必须可回滚。任何脚本如果会写文件写之前必须先备份。我接受“告诉你它改了什么”不接受“悄悄改完还不留退路”。高风险动作只给建议不直接执行。AI 生成的提交信息、代码审查意见一律落到文件里让你过目。真正执行git commit、git push的永远是你自己。每次调用成本可预估、有上限。大模型接口不是免费的diff 无限长会让一次调用吃掉大量 token。脚本必须做截断、空转保护和失败重试避免“挂一个 cron 结果半夜烧钱”。全过程留日志。每一轮的输入摘要、输出结果、耗时、调用次数都写进日志文件。出问题不是靠猜是靠翻日志。这四条听着像废话但实际执行起来非常考验设计。比如“可回滚”就决定了 CHANGELOG 更新程序和“commit 信息生成”程序的结构完全不同前者需要设计备份逻辑后者只需要输出建议文本。很多自动化项目翻车翻的不是技术是这几条红线没提前划定。2.2 四个目标场景提交信息、代码自检、CHANGELOG、工作简报我把需求拆成四个场景每个场景都有明确的触发时机和产出物场景触发时机产出物谁来执行提交信息生成git commit前pre-commit 钩子output/commit_suggestion.txt建议文本人工确认后git commit -F提交前代码自检pre-commit 钩子自检建议清单写到 stdout 和日志人工查看不阻断提交CHANGELOG 更新手动运行或发版前追加条目到CHANGELOG.md自动备份.bak脚本写人工通过 Git 审核每日工作简报工作日早 8 点的计划任务output/daily_report_YYYYMMDD.md脚本生成人工可以改这四个场景的共性是输入数据都在代码库里不需要我把业务机密交给第三方输出结果都是“辅助决策”最终拍板的环节永远留给我。这也是我认为自动化工作流最健康的形态。确定场景之后我做的第二件事是给每个场景写“完成定义”提交信息必须符合 Conventional Commits 规范CHANGELOG 只追加不重写日报必须包含日期、提交列表、AI 总结三块。没有完成定义后面写提示词会非常痛苦因为模型不知道你管什么叫“好”。3. 技术选型复盘为什么是“Git钩子 Python 大模型API 计划任务”3.1 先给三个方案做个对比如果你去搜“AI 自动化工作流”市面上至少有三种现成路线商业效率工具、IDE 插件生态、自建脚本。我把它们摆在一起比过一轮方案上手成本可控性数据安全可扩展性我的结论商业效率工具低低只给你预设动作数据要过第三方差被产品路线绑架适合不想折腾的轻度用户IDE 插件中低中但和编辑器深度耦合大部分要联网中等适合单人在编辑器内使用自建脚本 Git hooks中高最高全部逻辑自己掌控可做到完全本地高想加什么场景都行适合需要深度绑定工作流的团队和个人这个表格不是我凭空拍的。我实际经历过“商业工具挺好用但某个动作做不了”的卡壳时刻想给提交信息加一个内部单号规则发现工具根本不支持自定义模板。IDE 插件又绑死了编辑器沉淀不下来。最后只能走自建路线。3.2 选型背后的三个判断自建路线里我最先确定的是Git 钩子。因为程序员日常节奏的源头就是 Git 事件提交、推送、合并。把自动化挂在这些事件上不改变任何既有操作习惯哪天不想用了删个文件就行。相比之下后台轮询目录、监听剪贴板之类方案都太绕。其次是Python 而不是 Node 或 Shell。Shell 写短连接脚本没问题但我要做 JSON 解析、重试、日志、编码处理Shell 很快会失去可维护性。Python 在这层是标准答案生态成熟团队里人人能看懂。哪怕你团队是 Java 主力一个独立的小 Python 工程也不会增加多少维护负担。最后是大模型接口的接入方式。我没有绑定任何特定厂商而是直接调用兼容/v1/chat/completions格式的 HTTP 接口用环境变量配置地址、密钥和模型名。这样既可以用本地部署的模型也可以随时切换到任何商用的兼容服务。这个“接口兼容层”是整条链路里最值钱的设计——它让我的代码跟具体供应商解耦了供应商涨价、断服、换模型我只需要改环境变量。4. 工程骨架与环境准备先搭一个能跑的壳4.1 目录结构不用框架但要能一眼看懂整个工程我刻意控制在一个很小的规模里没有上框架也没有搞微服务那一套。目录长这样ai-workflow/ ├── .venv/ # Python 虚拟环境 ├── config.yaml # 提示词与边界参数可选 ├── requirements.txt # 依赖清单 ├── CHANGELOG.md # 产品变更记录脚本会追加 ├── scripts/ │ ├── __init__.py │ ├── utils.py # 日志、subprocess、JSON 解析 │ ├── git_diff.py # 获取并清洗 git diff │ ├── llm_client.py # 带重试和防御的模型客户端 │ ├── commit_message.py # 生成提交信息建议 │ └── changelog_daily.py # 更新 CHANGELOG / 生成日报 ├── logs/ │ ├── workflow.log # 统一运行日志 │ ├── hook.log # Git 钩子输出 │ └── cron.log # 定时任务输出 └── output/ ├── commit_suggestion.txt # 提交信息建议 └── daily_report_YYYYMMDD.md # 每日工作简报没有src包、没有setup.py因为它的运行范围就是“某个项目仓库的本地配套工具”。放在仓库根目录下钩子用绝对路径引用干净利落。4.2 安装依赖与初始化我只需要两个第三方库其余全部用 Python 标准库cd /path/to/your/repo python3 -m venv .venv source .venv/bin/activate pip install requests pyyamlrequests用于 HTTP 调用pyyaml是为了读config.yaml。如果你连 YAML 都不想装可以把配置全部挪到环境变量里代码更少但可读性会差一些。我个人建议保留 YAML把提示词这类需要频繁调优的东西放在文件里而不是埋进代码。4.3 环境变量把“供应商相关”和“业务逻辑”分开运行前需要配置几个环境变量我在.env.example里写好模板# 指向任意兼容 /v1/chat/completions 的服务本地或用云都可以 LLM_BASE_URLhttp://127.0.0.1:8000/v1 LLM_API_KEYyour-api-key LLM_MODELyour-model-name LLM_TEMPERATURE0.2 LLM_TIMEOUT30这里的核心原则是任何和具体供应商挂钩的东西都不许写进业务代码。哪天想换模型改环境变量代码一行不动。这也是后面llm_client.py里所有参数从环境变量读取的原因。5. 完整代码四个脚本的逐段实现与讲解5.1 utils.py 和 git_diff.py干净地拿到“机器能读的变更”自动化工作流的第一步永远不是“问 AI”而是获取干净的输入。git diff原始输出里有大量噪声——二进制文件提示、无意义的锁文件变动、超长 diff 导致 token 爆炸。不处理干净后面模型生成的质量一定差。先看utils.pyimport json import logging import re import subprocess from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent LOG_DIR BASE_DIR / logs OUTPUT_DIR BASE_DIR / output LOG_DIR.mkdir(exist_okTrue) OUTPUT_DIR.mkdir(exist_okTrue) logging.basicConfig( filenameLOG_DIR / workflow.log, levellogging.INFO, format%(asctime)s %(levelname)s %(name)s: %(message)s, ) logger logging.getLogger(ai_workflow) def run_cmd(cmd: list[str], cwd: Path | None None) - str: 执行 shell 命令并返回 stdout。任何失败都直接抛异常。 result subprocess.run(cmd, capture_outputTrue, textTrue, cwdcwd) if result.returncode ! 0: raise RuntimeError(f命令执行失败: { .join(cmd)}\n{result.stderr[:2000]}) return result.stdout def extract_json(text: str) - dict: 从模型输出中尽量稳健地解析出 JSON兼容常见的 json 包裹格式。 text text.strip() if text.startswith(): text text.strip() if text.startswith(json): text text[4:].strip() try: return json.loads(text) except json.JSONDecodeError: pass match re.search(r\{.*\}, text, re.DOTALL) if match: return json.loads(match.group(0)) raise ValueError(f无法从模型输出中解析 JSON: {text[:500]})run_cmd把所有命令执行统一收口好处是日志、异常处理、编码都在一个地方控制。extract_json是我踩过几次坑后的产物模型经常在 JSON 外面包一层 json 标记甚至输出一两句“好的我来帮你分析”之类的废话。这个函数先把包裹剥掉再退而求其次抓取第一个大括号能救回八成解析失败。再看git_diff.pyfrom pathlib import Path from utils import run_cmd, logger MAX_DIFF_CHARS 8000 def get_repo_root() - Path: return Path(run_cmd([git, rev-parse, --show-toplevel]).strip()) def get_changed_files(staged: bool True) - list[str]: if staged: output run_cmd([git, diff, --staged, --name-only]) else: output run_cmd([git, diff, --name-only, HEAD]) return [line.strip() for line in output.splitlines() if line.strip()] def get_git_diff(staged: bool True, max_chars: int MAX_DIFF_CHARS) - str: 获取 diff过滤二进制文件超长时截断到 max_chars。 args [git, diff] if staged: args.append(--staged) args [--, :(exclude)package-lock.json, :(exclude)poetry.lock, :(exclude)*.lock] raw run_cmd(args) lines [] for line in raw.splitlines(): if line.startswith(Binary files) or line.startswith(GIT binary patch): continue lines.append(line) diff_text \n.join(lines) if len(diff_text) max_chars: return diff_text head diff_text[: max_chars // 2] tail diff_text[-max_chars // 2 :] return head \n... [diff 过长已截断] ...\n tail这个模块里有两个细节是我认为最值得说的。第一是pathspec exclude:(exclude)package-lock.json这种写法直接让 Git 在生成 diff 时就跳过锁文件而不是拿到文本后再过滤。锁文件变动对提交信息生成完全无意义不提前排除模型很容易被几十个大版本号变动带偏。第二是超长截断策略我保留头部和尾部各一半中间加一行提示。为什么不是截头因为头部通常有文件路径和变更分布尾部通常有具体实现细节两边都有信息量。真正的完整 diff 不等模型看完而是靠文件的--stat统计来兜底。5.2 llm_client.py调用大模型要做的三件防御写大模型客户端真正的难点不在“调通接口”而在防御。网络抖动要重试、返回格式要校验、超时要兜底。我见过太多自动化脚本只写一次requests.post就挂到生产环境结果一个偶发超时让整个流水线断掉。import os import time import requests from utils import extract_json, logger DEFAULT_TIMEOUT 30 MAX_RETRIES 3 class LLMClient: def __init__(self): self.base_url os.getenv(LLM_BASE_URL, http://127.0.0.1:8000/v1) self.api_key os.getenv(LLM_API_KEY, EMPTY) self.model os.getenv(LLM_MODEL, your-model-name) self.temperature float(os.getenv(LLM_TEMPERATURE, 0.2)) self.timeout int(os.getenv(LLM_TIMEOUT, DEFAULT_TIMEOUT)) def chat(self, messages: list[dict]) - str: 调用兼容 /v1/chat/completions 的服务带指数退避重试。 payload { model: self.model, messages: messages, temperature: self.temperature, } headers {Authorization: fBearer {self.api_key}} for attempt in range(1, MAX_RETRIES 1): try: resp requests.post( f{self.base_url.rstrip(/)}/chat/completions, jsonpayload, headersheaders, timeoutself.timeout, ) resp.raise_for_status() data resp.json() return data[choices][0][message][content] except Exception as exc: logger.warning(模型接口第 %s 次调用失败: %s, attempt, exc) if attempt MAX_RETRIES: raise time.sleep(2 ** attempt) raise RuntimeError(模型调用失败) def chat_json(self, user_prompt: str, system_prompt: str) - dict: content self.chat([ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ]) return extract_json(content)三件防御分别是指数退避重试2 的 N 次方秒等待避免把瞬时故障放大成风暴、超时强制断开30 秒没响应就放弃本此不会让 cron 任务卡死、JSON 兜底解析复用extract_json。另外我把temperature默认压到 0.2因为提交信息和 CHANGELOG 都是“确定性较强的文本生成”不需要太高创造性。温度越高越容易出现我想都没想过的措辞这在文档场景是灾难。5.3 commit_message.py让AI只当参谋不当枪手这个脚本是整个工作流里我用得最勤的一个。每当我要提交代码它会把暂存区的 diff 喂给模型输出一份符合 Conventional Commits 规范的提交信息。但注意——它只负责把建议写进文件绝不替你执行git commit。import sys from pathlib import Path from git_diff import get_changed_files, get_git_diff, commit_brief, get_repo_root from llm_client import LLMClient COMMIT_TYPES [feat, fix, refactor, docs, test, chore, perf, style] SYSTEM_PROMPT 你是一名严谨的代码评审助手。请根据 git diff 生成一条符合 Conventional Commits 规范的提交信息。 只输出 JSON不要输出任何解释格式如下 {type: feat/fix/refactor/docs/test/chore/perf/style 之一, scope: 模块名或空, subject: 不超过72个字符的简短描述, body: 可选的详细说明没有则留空字符串, breaking: 破坏性变更说明没有则留空字符串} 规则 - type 必须从枚举值中选择 - subject 用简洁的陈述句避免空话 - body 控制在 3~5 条以内 - 如果 diff 中明显包含破坏性变更如实写到 breaking def main(): repo_root get_repo_root() files get_changed_files() if not files: print(没有 staged 的变更跳过生成提交信息。) return 0 user_prompt 请基于以下 git diff 生成提交信息\n\n user_prompt commit_brief() \n user_prompt get_git_diff() client LLMClient() suggestion client.chat_json(user_prompt, SYSTEM_PROMPT) if suggestion.get(type) not in COMMIT_TYPES: suggestion[type] chore scope f({suggestion[scope]}) if suggestion.get(scope) else subject suggestion.get(subject, ).strip() body suggestion.get(body, ).strip() breaking suggestion.get(breaking, ).strip() lines [f{suggestion[type]}{scope}: {subject}] if body: lines.extend([, body]) if breaking: lines.extend([, BREAKING CHANGE: breaking]) output_dir repo_root / output output_dir.mkdir(exist_okTrue) target output_dir / commit_suggestion.txt target.write_text(\n.join(lines), encodingutf-8) print(\n.join(lines)) print(f\n建议内容已写入: {target}) print(如果满意直接执行: git commit -F output/commit_suggestion.txt) return 0 if __name__ __main__: sys.exit(main())这里有两个“为什么这么做”的细节第一只生成建议永远不自动 commit。原因很简单提交信息是团队协作的产物里面经常要夹内部单号、评审意见、特殊上下文这些信息模型不可能知道。让它提初稿你改两笔再用效率最高。真要全自动 commit信息质量迟早出事。第二结果不是直接发到 stdout 就完事而是落到固定文件。这个设计是为了配合 Git 钩子。钩子里的 stdout 会被git commit吞掉用户不一定能看到但文件路径是稳定的。配合git commit -F output/commit_suggestion.txt两秒钟完成一次高质量提交。脚本里还做了一次 type 校验模型如果返回枚举之外的 type直接降级成chore。这属于“宁可保守不可乱写”的兜底策略。5.4 changelog_daily.py把日志变成产品文档和工作日报CHANGELOG 和日报看似是两个场景但底层逻辑相同把 git log 的原始信息用模型转换成人类友好、面向读者的文本。区别只在输出格式和落盘位置。import datetime as dt import sys from pathlib import Path from git_diff import get_repo_root from llm_client import LLMClient from utils import run_cmd SYSTEM_PROMPT 你是项目维护者。根据 git log 信息生成符合 Keep a Changelog 风格的条目。 只输出 JSON格式如下 {added: [新功能列表], fixed: [修复列表], changed: [变化列表]} 每项为一行纯文本没有则给空数组不要多余解释。 def collect_commits(since: str | None None, until: str | None None) - str: args [git, log, --prettyformat:%h|%ad|%s, --dateformat:%Y-%m-%d %H:%M] if since: args.append(f--since{since}) if until: args.append(f--until{until}) args.append(HEAD) return run_cmd(args) def build_prompt(kind: str, commits: str) - str: if kind changelog: return 基于以下最近提交记录生成 CHANGELOG 更新条目\n commits return 基于以下提交记录生成工作简报列出完成事项与遗留问题\n commits def update_changelog(repo_root: Path, days: int 10) - None: commits collect_commits() if not commits.strip(): print(没有提交记录。) return client LLMClient() result client.chat_json(build_prompt(changelog, commits), SYSTEM_PROMPT) new_entries [] if result.get(added): new_entries.append(### Added) new_entries.extend(- item for item in result[added]) if result.get(fixed): new_entries.append(### Fixed) new_entries.extend(- item for item in result[fixed]) if result.get(changed): new_entries.append(### Changed) new_entries.extend(- item for item in result[changed]) changelog_path repo_root / CHANGELOG.md backup changelog_path.with_suffix(.md.bak) backup.write_text(changelog_path.read_text(encodingutf-8), encodingutf-8) content changelog_path.read_text(encodingutf-8).rstrip() content f\n\n## {dt.date.today().isoformat()}\n content \n.join(new_entries) \n changelog_path.write_text(content, encodingutf-8) print(fCHANGELOG 已更新备份在 {backup}) def write_daily_report(repo_root: Path) - None: today dt.date.today() yesterday today - dt.timedelta(days1) since f{yesterday} 00:00:00 until f{today} 00:00:00 commits collect_commits(sincesince, untiluntil) if not commits.strip(): print(昨天没有提交记录。) return client LLMClient() summary client.chat_json(build_prompt(daily, commits), SYSTEM_PROMPT) output_dir repo_root / output output_dir.mkdir(exist_okTrue) target output_dir / fdaily_report_{today.strftime(%Y%m%d)}.md target.write_text( f# 工作简报 {today.isoformat()}\n\n## 提交记录\n\n{commits}\n\n## AI 总结\n\n{summary}\n, encodingutf-8, ) print(f日报已生成: {target}) def main(): repo_root get_repo_root() kind sys.argv[1] if len(sys.argv) 1 else changelog if kind changelog: update_changelog(repo_root) elif kind daily: write_daily_report(repo_root) else: print(用法: python changelog_daily.py [changelog|daily]) return 1 return 0 if __name__ __main__: sys.exit(main())update_changelog里最关键的是第一步先备份再修改。我见过太多脚本直接往 CHANGELOG 末尾追加内容结果模型生成了一堆重复条目只能靠 Git 翻历史找回。有了.bak还原就是一条mv命令的事。日报的since/until我一开始写错了默认统计“从今天 00:00 到现在”。后来在早 8 点的 cron 里跑才发现统计到的是过去八小时的提交不是昨天全天的。改成“昨日 00:00:00 到今日 00:00:00”之后才符合认知。这种问题光读代码看不出来必须跑了才知道。6. 接入日常Git钩子与定时任务配置示例6.1 pre-commit只生成建议不拦截紧急提交代码写完只是第一步把它接到日常节奏里才是自动化真正生效的时刻。我用的第一个入口是pre-commit钩子文件放在.git/hooks/pre-commit#!/usr/bin/env bash # 启用: chmod x .git/hooks/pre-commit # 跳过: SKIP_AI_WORKFLOW1 git commit 命令前加上环境变量即可 if [ -n ${SKIP_AI_WORKFLOW:-} ]; then exit 0 fi BASE_DIR$(git rev-parse --show-toplevel) PYTHON${BASE_DIR}/.venv/bin/python if [ ! -x ${PYTHON} ]; then PYTHONpython3 fi cd ${BASE_DIR} || exit 1 ${PYTHON} scripts/commit_message.py logs/hook.log 21这里最重要的设计是SKIP_AI_WORKFLOW环境变量开关。为什么一定要有因为钩子一旦处理时间超过一两秒开发者在紧急修复线上 bug 时会非常烦躁。有了这个开关遇到 hotfix 可以直接跳过而且这个开关是显式的——你明确知道“这次不生成提交建议”而不是钩子悄悄失效。与此同时钩子永远不要在失败时阻断提交。脚本只负责生成建议就算大模型服务挂了也不影响你正常提交代码。6.2 定时任务每天早上生成昨天的简报Git 钩子解决“代码变更时”的自动化定时任务解决“时间到点”的自动化。我把日报生成挂到工作日的早上# 每天早上 8:10 生成昨日工作简报 10 8 * * 1-5 cd /path/to/project /path/to/project/.venv/bin/python /path/to/project/scripts/changelog_daily.py daily /path/to/project/logs/cron.log 21cron 的环境变量区域是空的所以脚本里所有用到的环境变量要么写到~/.profile要么直接在这条命令行里LLM_MODELxxx LLM_BASE_URLxxx前缀式地传。新人最容易在这里翻车本地跑得好好的一挂到 cron 就说找不到模块、找不到密钥。我的建议是先用绝对路径跑一次确认没有问题再把日志重定向写进 crontab。6.3 一键暂停与绕过机制自动化最怕的就是“关不掉”。所以除了SKIP_AI_WORKFLOW之外我还要求所有脚本具备手动运行能力而且手动运行和钩子运行走的是同一条代码路径。万一某天模型供应商接口大改我可以直接手动跑python scripts/commit_message.py诊断而不是被一只看不见的钩子卡住。这里有一个实用小技巧如果你发现钩子里的脚本突然不工作了先去看logs/hook.log那里记了所有失败堆栈。不要对着.git/hooks目录一个个echo浪费时间。7. 运行三个月之后十一个坑与五个调整结论7.1 踩坑清单每一条都是真实运行磨出来的这套工作流跑了三个月遇到的问题远比想象中多。我列一张表每条后面附上我的处理方式现象根因处理模型提交信息全是空话提示词只说了“写提交信息”没给规则在 system prompt 里显式约束格式和语气JSON 解析偶发失败模型输出带 json 包裹或废话前缀写extract_json兜底兼容包裹和纯文本大 diff 把请求打到超时没有限制输入长度用MAX_DIFF_CHARS8000截断配合--stat摘要锁文件变动污染提交信息没排除 package-lock 等pathspec 加:(exclude)*.lockcron 跑出的日报统计错时段since写成了今天 00:00改成“昨日 00:00 到今日 00:00”钩子拖慢提交串行调用模型一次 2~5 秒只生成建议不拦截失败不阻断模型输出不在枚举里type/scope 缺乏防错Python 侧校验非法值降级为chore数据文件编码乱部分环境默认非 UTF-8所有文件读写显式encodingutf-8CHANGELOG 重复追加多次运行没检查是否重复运行前检查当天日期是否已存在已存在则跳过API 密钥写在代码里早期图省事硬编码改为全部环境变量代码仓库不再出现密钥深夜 cron 失败没看见日志无告警给 cron 追加 logs/cron.log 21每天统一翻这张表看起来都是小问题但每一条都真实浪费过我半小时到一个小时。自动化的价值是省时但如果细节没兜住它会以另一种方式把时间吃掉。7.2 我做的五个关键调整运行三个月后我对照最初的四条铁律做了一次复盘收获是五条调整结论第一输入预处理比提示词更重要。一开始我把所有精力都花在调 prompt 上后来发现 diff 清洗干净、排除掉无关文件后模型输出质量直接上了一个台阶。这不是模型的进步是我喂的数据变得更精炼了。第二所有自动生成的文件都可以人工接管。任何 AI 生成的内容都不应该阻塞你原本的工作节奏。提交信息建议不如意直接不采纳日报哪条总结不对改一行再发出去。自动化是提效工具不是流程审批者。第三日志是调试的眼睛。我在utils.py统一收口了日志每次模型调用、每次失败重试都有记录。三个月后回头看很多“灵异现象”靠日志两分钟定位靠猜测永远摸不着。第四温度参数要按场景区分。提交信息和 CHANGELOG 需要保守温度设为 0.2写日报总结时可以调到 0.4让表达更自然一些。同一个 client不同参数一个环境变量就能切换。第五先跑通最小闭环再往上加场景。我最开始只做了“提交信息生成”这一件事跑顺之后才加日报、加 CHANGELOG。每加一个场景都要观察一两周确认它不破坏原有流程。一次贪多最后维护成本会反噬。7.3 给想照做的人的最后建议如果你想在自己的项目里复刻这套工作流我给三个非常具体的建议把scripts目录和requirements.txt当成一个独立的小工具仓库来管理不要和业务代码混在一起。业务代码升级换代这个工具链可以长期稳定运行。依赖就requests和pyyaml两个环境坏了三分钟重建。先只接入 pre-commit 钩子跑两周。你不需要一次性铺开全部场景先把“生成提交信息建议”变成习惯再决定要不要把日报和 CHANGELOG 接上。自动化是给自己用的不是给某个系统用的用不起来的自动化只是负债。最后保持一个原则AI 负责生成你负责判断。所有脚本的输出都只是初稿真正的 commit、真正的发布、真正的对外沟通永远过一遍你的手。这不是效率上的妥协而是在 AI 时代给自己留的那条退路。工具可以换、模型可以换但你对自己工作的掌控力不应该交给任何自动化流程。