"让它检查一下 lint"、"让它把 import 排一下"、"让它把这份 changelog 补上"——这些话每周说五遍,就是自动化该介入的信号。判据不是"这事麻烦",而是它每次都一样。
这篇给一条从"手敲提示词"到"事件触发、无人值守"的升级路径,以及每一步会在哪里翻车。
1. 三个判据:什么值得自动化
判断一个任务该不该写成自动流程,只看三条,三条全中才动手。
| 判据 | 具体问法 | 不满足时的后果 |
|---|---|---|
| 高频 | 一周内会重复三次以上吗 | 写脚本的时间永远收不回来 |
| 步骤固定 | 每次的步骤顺序完全一致吗 | 每次都要改流程,脚本变成负债 |
| 验收标准明确 | 能否用一条机器可判定的条件说"完成" | 无终止条件,跑多久都算"没完成" |
第三条最容易被忽略。"代码更整洁了"不是验收标准,"eslint 零 error 且测试全绿"才是。没有可判定的验收,自动化只会把"人反复催"变成"机器反复跑"。
反过来看,这三条也在筛掉一类任务:缺陷定位、架构取舍、需求澄清。它们步骤不固定,验收靠人判断,硬做成自动流程必然产出要看第二遍的东西。
2. 三层升级:从模板到触发器
三层不是三选一,是一层一层往上加。跳层的结果通常是"脚本里塞了一段没法评审的自然语言"。
| 层 | 形态 | 触发方式 | 人在哪 |
|---|---|---|---|
| 第一层 | 提示模板 | 人手动粘贴 | 每次都在 |
| 第二层 | 脚本封装 | 人执行命令 | 决定跑不跑 |
| 第三层 | 定时/事件 | cron、CI、webhook | 只在告警时介入 |
2.1 第一层:把提示词写成模板
模板的作用是消灭变量。同一件事,每次只换仓库路径和分支名,其余一字不改。
【任务】修复 lint 与类型错误
【范围】仅改动 TARGET_DIR 下文件,不改公共接口签名
【步骤】1. 跑 QUALITY_CMD 收集错误清单
2. 逐个修复,每修完一类就重跑 QUALITY_CMD
3. 全绿后停下,不要顺手重构
【验收】QUALITY_CMD 退出码 0,且未修改 PUBLIC_API_FILE
【禁止】改测试断言来让测试通过;升级依赖大版本
模板里必须有"禁止"一行。没有它,模型会用自己的标准补全你的意图。
2.2 第二层:脚本化
把模板存成文件,用脚本注入变量并调用。
#!/usr/bin/env bash
# fix-lint.sh —— 幂等:可重复执行,结果收敛
set -euo pipefail
DIR="${1:?need target dir}"
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
LOG="logs/fix-lint-$(date +%Y%m%d-%H%M%S).log"
mkdir -p logs
test -z "$(git status --porcelain)" || { echo "worktree dirty"; exit 2; }
git switch -c "auto/fix-lint-$RANDOM" >/dev/null
sed -e "s|TARGET_DIR|$DIR|g" \
-e "s|QUALITY_CMD|npm run lint|g" \
prompts/fix-lint.txt > "$LOG.prompt"
codex exec --sandbox workspace-write \
--prompt-file "$LOG.prompt" 2>&1 | tee "$LOG"
git diff --stat | tee -a "$LOG"
两个细节决定这套能不能长期跑:工作区必须是干净的(否则 diff 分不清谁改的),以及每次在独立分支上跑(保留回滚点)。
2.3 第三层:交给触发器
cron 适合"每天该看一眼"的巡检;CI 适合"每次 push 都该验"的门禁。区别在于失败该拦谁:CI 失败拦合并,cron 失败只是发通知。
3. 检查-修复-验证:可重复的提示骨架
固定三段式,缺一段就会退化成"改完不知道对不对"。
【背景】仓库 PATH,语言与包管理器按仓库现状,质量命令 QUALITY_CMD。
上一轮自动修复已产生提交 BASE_SHA。
【目标】让 QUALITY_CMD 通过,且不引入新的行为变更。
【约束】
- 只改 TARGET_DIR;不动 PUBLIC_API_FILE
- 不改测试断言、不加 eslint-disable、不加 ts-ignore
- 不升级依赖版本
- 每类错误单独一个提交,提交信息格式 fix(lint): <类别>
【验收】
1. QUALITY_CMD 退出码 0
2. git diff --stat 只涉及 TARGET_DIR
3. 测试命令 TEST_CMD 全绿
【失败处理】单类错误修两次仍失败 → 停止该类,写入
logs/blocked-<类别>.md 并继续下一类,最后统一汇报。
最后那段"失败处理"是关键。没写它,模型会在同一个错误上反复试到超时,整轮白跑。
| 段落 | 缺了会怎样 |
|---|---|
| 背景 | 模型靠猜技术栈,命令全错 |
| 目标 | 顺手做重构,diff 不可评审 |
| 约束 | 用 eslint-disable 让检查"通过" |
| 验收 | 无法判断是否该结束 |
| 失败处理 | 卡死在单点,不产出可用结果 |
4. 承载方式:CI 与定时任务
4.1 CI 里跑
name: auto-fix
on:
schedule: [{ cron: "0 3 * * 1-5" }]
workflow_dispatch:
jobs:
fix:
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write }
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: ./scripts/fix-lint.sh src
env: { API_KEY: "${{ secrets.API_KEY }}" }
- run: git push origin HEAD:auto/fix-lint
if: always()
CI 里必须注意三点:密钥来自 secret 而不是仓库文件;产出走 PR 而不是直推主干;if: always() 保证日志被上传,否则失败时什么都看不到。
4.2 定时巡检
# crontab -e
# 每工作日 03:10 跑一次只读巡检,产出报告不产出提交
10 3 * * 1-5 cd /srv/app && ./scripts/audit.sh >> logs/audit.log 2>&1
巡检类任务优先用只读沙箱:它只要输出结论,不需要写权限。写权限留给修复类任务。
5. 幂等与失败告警
幂等 = 跑一次和跑三次结果相同。不幂等的自动化最典型症状是"第二次跑出一堆反向改动"。
| 不幂等的写法 | 幂等的写法 |
|---|---|
| 追加到文件末尾 | 按标记块整体替换 |
git commit 不看已有提交 |
先查 git log --grep=<trace_id> |
| 每次生成新分支名 | 固定分支名 + force-with-lease |
| 无条件下载依赖 | 校验 lockfile 哈希 |
告警要回答三个问题,缺一个就等于没告警:
[ALERT] auto-fix 失败
what : QUALITY_CMD 退出码 1,阻塞类别 type-error
where: 分支 auto/fix-lint-20261109,提交 abc1234
act : 查看 logs/fix-lint-20261109-031000.log 第 40-88 行
失败静默是自动化最常见的死法。脚本 exit 0 吞掉错误、CI 步骤加了 continue-on-error、cron 输出重定向到 /dev/null——三种写法都能让一个坏掉三个月的流程继续"每天成功"。
6. 日志留存:每次运行都要能复盘
落盘内容固定为四件:输入、输出、改动、判定。
logs/
20261109-031000/
prompt.txt # 实际发送的完整提示(含注入后的变量)
stdout.log # 模型完整输出
diff.patch # git diff,可直接 git apply
verdict.json # 退出码、耗时、验收项逐条结果
| 留什么 | 为什么 |
|---|---|
| 注入后的提示 | 变量替换错是最难复现的一类 bug |
| 完整 stdout | 失败时的推理链是唯一线索 |
diff.patch |
能重放、能回滚、能单独评审 |
| 判定结果 | 区分"没跑"和"跑了但没通过" |
留存周期按"下次改这段代码之前还看得到"来定,通常 30 天足够。别只留最后一轮——问题往往在第三轮才暴露。
7. 人的角色:从驱动到审查
自动化上线后,人的工作从"每次说一遍"变成"每天看一遍产出"。这项工作有具体形状:
| 阶段 | 人的动作 | 时间占比变化 |
|---|---|---|
| 之前 | 写提示、盯执行、手动改 | 执行占八成 |
| 之后 | 看 diff、判对错、改模板 | 审查占八成 |
审查时看三件事:diff 是不是只在约定范围内、有没有绕过检查的痕迹(eslint-disable、放宽的断言、skip)、失败处理是否按模板走了。
发现偏差时,改的是模板和约束,不是那一轮的产出。改产出只解决这一次,改模板解决后面每一次。
8. 什么时候自动化反而有害
| 场景 | 为什么有害 | 更合适的做法 |
|---|---|---|
| 判断类任务(定位根因、方案取舍) | 步骤不固定,验收靠人 | 保留人工,只自动化"收集信息"这一段 |
| 需求高频变化 | 模板每周重写,维护成本高于收益 | 等需求稳定两周再抽象 |
| 验收标准说不清 | 无终止条件,跑成无限循环 | 先把验收写成可执行断言 |
| 一次性任务 | 写脚本时间 > 手做时间 | 手做 |
| 涉及不可逆操作(迁移、删数据) | 出错无法回滚 | 只自动化到"生成脚本",执行留给人 |
| 需要跨团队协商 | 协商过程本身是产出 | 自动化只做材料准备 |
一句话边界:自动化放大的是流程的确定性,不是流程的正确性。 流程本身是错的,自动化只会让错误更快、更整齐地发生。
9. 排错:六种"跑起来了但没用"
| 现象 | 原因 | 处理 |
|---|---|---|
| 每天成功但 diff 为空 | 验收条件太松,模型立刻"通过" | 收紧验收,加一条必须产出改动的断言 |
| 第二轮改动把第一轮推翻 | 不幂等,追加式写入 | 改成标记块整体替换,固定分支 |
| 失败但告警没发 | 错误被 exit 0 或 continue-on-error 吞掉 |
检查退出码传递链,去掉静默 |
| 同一错误重复修到超时 | 缺"失败处理"段 | 模板里加上"两次失败即跳过并记录" |
| CI 里命令全错 | 缺背景段,模型猜了技术栈 | 模板注入 QUALITY_CMD 与环境事实 |
| 评审时看不懂 diff | 一次改太多类,提交没分组 | 按类别分组提交,一条提交一类 |
10. 自测
- 判断一个任务该不该自动化的三条判据分别是什么?
- 为什么"代码更整洁了"不能当验收标准?
- 提示骨架里"失败处理"那一段解决什么问题?
- 判断幂等的标准是什么,举一个不幂等的写法。
- 告警必须包含哪三个要素?
- 自动化上线后,发现产出偏差时该改模板还是改那一轮产出,为什么?