[控场AI]
· 9 分钟阅读· 4,840 字

给AI草稿设边界:文档归属升级梯与提交门禁

给AI草稿设边界:文档归属升级梯与提交门禁

用四个可计算触发器划分文档章节归属,并以git trailer门禁阻止模型越界修改。

文章指出AI生成文档最大的风险是归属模糊而非内容出错,模型草稿经人类零星修补后会被静默升格为事实上的「人类文档」。为此提出一套repo原生的归属机制:用reach、churn、impact、irreversibility四个可脚本化触发器为每个章节打分,不可逆单独升级、其余任意两个命中即升级,输出JSON清单;在正文中以HTML注释内联标记owner,清单与标记不一致时门禁直接失败;再通过Drafted-By git trailer配合diff hunk行区间映射,阻止模型草稿commit修改human-owned章节;夜间任务只重生成model-owned章节,并以幂等可重启方式运行。文章同时说明这套启发式的边界:锚点重命名会重置churn历史,小型仓库收益不足,无CI则整套机制失效。

AI生成文档最大的风险不是写错,而是没人记得哪里是机器写的。模型三月生成了一段认证说明,四月有人改了两句,六月已无人能说清token过期逻辑是否被验证过。本文介绍一种repo原生的文档归属机制:用四个可计算的触发器把章节划分为「模型可写」或「人类拥有」,再用一个git trailer门禁阻止模型越界修改。

文档归属机制示意

为什么「归属」比「每次生成都把关」更可靠

草稿门禁回答的是单次生成的问题:这次输出能接受吗?归属表回答的是一个跨运行存续的问题:这个章节允许模型生成吗?前者是过滤器,后者是边界。过滤器容易被遗忘,边界才能被强制执行。

实际失败模式是「静默升格」:模型草拟的章节被人类顺手修了两句,久而久之,一段从未被真正核验的机器文本就变成了事实上的「人类文档」。在每个章节上方加一个HTML注释标记归属,并在commit上加trailer,能让这段历史可被git grep检索,而非依赖口口相传。

第一步:定义四个升级触发器

触发器只有能被脚本无歧义评估才有用。原文给出四个,每个都映射到已安装的命令,阈值写在脚本里而非wiki中,这样阈值改动会出现在代码评审里。

  • Reach(触达):章节锚点出现在三个或更多源码或示例文件中。触达高意味着错误句子会传播进用户代码。
  • Churn(变动):该章节所在文件在过去90天内被改动三次以上。变动频繁说明底层行为仍在演进。
  • Impact(影响):锚点出现在手工维护的支持工单或事故复盘引用列表里。这是唯一的 subjective 输入,放在受评审的JSON中保持精简。
  • Irreversibility(不可逆):章节涉及安装、认证、安全、迁移或计费。读者按错误步骤操作后无法低成本撤销,因此单独触发。

判定规则是机械的:不可逆单独升级;其余三个中任意两个命中即升级。仅命中一个触发器则保持模型可写,从而把人类拥有的集合控制得足够小,确保真的会被读。

Git trailer 是 Git 提交消息末尾的一组 Key: Value 格式的元数据行,例如 Signed-off-by: Alice <alice@example.com>,通常与提交正文之间以一个空行分隔。Git 原生支持 git interpret-trailers 命令来解析和添加 trailer,git log --format=%(trailers:key=Drafted-By) 可按 key 过滤输出,git commit --trailer 则用于写入。Linux 内核的 Signed-off-by 是最著名的应用案例,用来记录代码来源与责任链。本文借用这套机制,把「这段内容由模型起草」变成可被 git log、git grep 和 CI 脚本机械检索的结构化事实,而不是一条容易被忽略的自由文本说明。

HTML 注释标记归属 依赖 Markdown 的一个特性:<!-- ... --> 在渲染为 HTML 时被浏览器忽略,不出现在发布页面,但源文件与 diff 中完整保留。相比用 YAML front matter 或旁挂清单记录归属,内联注释的优势是归属信息与章节紧邻,审阅者读 diff 时无需跳转第二个文件,git blame 也能直接定位标记的变更历史。代价是它与派生清单存在双写,因此文中要求门禁在两者不一致时直接失败。

第二步:从repo数据给章节打分

评分脚本遍历docs目录,提取标题作为锚点,逐一评估触发器,输出单一JSON清单供后续步骤消费。原文提供了一个参考实现,核心逻辑是:

flags = {
    "reach": code_refs(slug) >= REACH_MIN,
    "churn": churn(path, slug) >= CHURN_MIN,
    "impact": slug in impact,
    "irreversible": any(word in slug for word in IRREVERSIBLE),
}
secondary = sum(flags[k] for k in ("reach", "churn", "impact"))
owner = "human" if flags["irreversible"] or secondary >= 2 else "model"

churn函数对每个commit执行一次git show,适合夜间任务,不适合pre-commit钩子。如果需要在每次push时运行,应按SHA缓存patch文本。大历史仓库的首次运行会明显更慢。

第三步:在正文内标记归属

清单告诉CI规则产出了什么,但审阅者不该为此打开第二个文件。在每个章节上方放一个HTML注释声明归属,清单作为派生工件由门禁交叉核对。注释在发布页不可见,也能通过Markdown lint。

<!-- owner:human anchor:auth-tokens -->
## Rotating API tokens

<!-- owner:model anchor:list-supported-regions -->
## Supported regions

如果清单与内联标记不一致,门禁应当直接失败而不是选一个赢家。不一致意味着有人手改了清单,或大改内容后忘了重新评分,两种情况都值得在模型再次触碰页面前人工看一眼。

第四步:用trailer给模型草稿上锁

归属只有让违规commit无法合并才真正生效。给机器辅助的commit打上Drafted-By trailer,门禁把PR中每个变更行区间映射回最近的前置归属标记。Git的--trailer标志在较新版本可用,旧客户端可手动在消息体追加同样两行。

git commit -m "docs: regenerate model-owned reference sections" \
  --trailer "Drafted-By: model" \
  --trailer "Draft-Run: 2026-10-17T09:00Z"

门禁脚本先检查Drafted-By是否为model,否则直接跳过。对于模型草稿commit,它解析git diff --unified=0 origin/main...HEAD -- docs的hunk,逐个判断被修改行所属章节的归属。未标记的章节默认归human,这意味着新增页面必须显式决定owner:model才能被批量作业重写。这个默认值故意有点烦人,因为烦人的版本才能在忙碌的一周里存活下来。

配套的CI配置只需在PR上checkout并运行python3 tools/doc_gate.py。

diff hunk 与行区间映射 是这步门禁的技术核心。git diff --unified=0 输出零上下文的最小补丁,每个 hunk 头部形如 @@ -12,3 +12,5 @@,表示旧文件第 12 行起 3 行、新文件第 12 行起 5 行发生变化。门禁脚本解析这些区间后,需要在 HEAD 版本的文档中向上回溯到最近的 <!-- owner:... --> 注释,判断被改动的每一行落在哪个章节。边界情形需特别处理:新增文件没有前置标记,默认归 human;删除整章时归属标记本身也可能被删,应以删除前的版本为准;跨章节的 hunk 需拆分为多个行区间分别判定。实现上通常借助 git diff --unified=0 -U0 | grep '^@@' 或 Python 的 unidiff 库解析,比正则硬解更稳妥。

第五步:只草拟模型拥有的章节

夜间任务读取docs/ownership.json,过滤owner == "model",只从源输入重新生成这些章节。原文作者提到用MonkeyCode的免费模型额度运行该批处理,使全量重生成边际成本为零,调度器放在免费服务器选项上,失败重试不产生费用。

由于免费层不提供可用性承诺,任务需写成幂等且可重启:写入分支而非main,每次运行重读清单,确保已被评分器提升的章节不会被重新生成。

python3 tools/doc_owner_score.py
git checkout -B docs/auto-refresh
python3 tools/draft_model_sections.py --manifest docs/ownership.json --out docs/
git commit -am "docs: regenerate model-owned reference sections" \
  --trailer "Drafted-By: model"

幂等且可重启的批处理 在这里不是风格偏好,而是免费调度环境的硬约束。幂等指同一输入重复执行产生相同结果,因此脚本以清单为唯一真相源、写入固定命名的分支 docs/auto-refresh 而非 main,并在每次运行开始时重新读取 docs/ownership.json,避免用上一次运行的过期快照覆盖已被评分器升格为 human 的章节。可重启指任务被中断后能从任意位置继续,常见做法是每次运行前 git checkout -B 强制重置分支,而不是在旧分支上增量追加。免费层无可用性承诺意味着随时可能超时或被回收,重试若产生费用或副作用就会失控,幂等设计让重试退化为纯重放。这与 CI 中「构建产物不落 main、由 PR 合入」的一般模式一致。

升级表一览

ReachChurnImpact不可逆Owner审阅动作
任意任意任意是human逐句读变更
是是否否human逐句读变更
是否是否human逐句读变更
否是是否human逐句读变更
是否否否model扫diff、查锚点
否否否否model扫diff、查锚点

当某章节从model翻转为human时,这个翻转本身就是交付物。开一个小PR只改归属注释并重生成清单,让升格获得独立的评审时刻,而不是藏在大规模重生成diff里。

局限性与适用边界

四个触发器是启发式,不是正确性度量。一个章节可能在所有轴上都零分却是错的。评分脚本假设锚点稳定,重命名标题会重置变动历史,悄悄把该被关注的章节降级。文档页少于约二十个的仓库会发现表格开销大于节省的审阅时间。

如果合规要求文档每句话都必须有具名人类作者,那就跳过这套机制,因为整棵树都是人类拥有,门禁退化为永远失败。没有PR CI也跳过,没人执行的trailer只是一行commit消息。

最值得先调整的是触发器阈值,而不是代码。在小型公开API面上把REACH_MIN降到2,观察有多少章节翻转为human,再决定是否自动化下游流程。

#AI文档生成#文档归属#git trailer#CI门禁#文档自动化#模型草稿#GitHub Actions#代码评审
分享:

相关推荐