Skip to content

【Feat Request】设计提案:llmdoc Update 从「基于日期」迁移到「基于 commit 水位线」 #29

Description

@wh1teAlter

设计提案:llmdoc Update 从「基于日期」迁移到「基于 commit 水位线」

状态 Proposal(提案,未实现)
范围 /llmdoc:update + Codex llmdoc-update 及其全部镜像表面
目标分支 feat_batch_update
创建日期 2026-06-29
验证方式 3 候选方案 → 4 维对抗审查(git 语义 / llmdoc 约束 / 多批正确性 / 迁移并发)→ 综合;多处 git 行为已对本仓库自身历史实测

本文档描述的是尚未落地的设计。它不是 llmdoc/ 稳定文档(稳定文档只能描述当前已存在的行为)。实现后,本文的结论应被拆分迁移到 llmdoc/architecture/update-orchestration.mdcommands/update.md 等真相源,本文转为历史决策记录。


目录

  1. 问题诊断
  2. 目标与非目标
  3. 核心模型:单一 commit 水位线
  4. 状态文件设计
  5. 范围解析算法(校验阶梯)
  6. 多批 commit 支持
  7. net diff vs per-commit
  8. 模式选择重构
  9. 更新工作流步骤
  10. 边界情况表
  11. Agent 契约改动
  12. 受影响文件全清单
  13. 迁移与向后兼容
  14. 被否决的备选
  15. 待拍板的决策
  16. 风险
  17. 验证 / 测试计划

1. 问题诊断:为什么现在是「基于日期」

当前 /llmdoc:update(及 Codex 镜像 llmdoc-update)的变更检测锚定在三类易失信号上:

  • 工作树 + 暂存区 diffcommands/update.md:inspect the current working tree, staged changes)
  • assistant 的新鲜任务记忆fast 模式的前提)
  • 日期戳:scratch report 的 Date:、reflections 的 YYYY-MM-DD-*.md 文件名、memory/archive/YYYY-MM-DD/ 目录、skills/llmdoc/templates/stop.shdate -u 时间戳

根本缺陷:系统没有「文档已同步到哪个 source commit」的持久水位线。后果:

  • 只有「刚改完、上下文还新鲜」时(fast 模式)才可靠;
  • 提交后工作树变干净 → 无法重建「自上次同步以来改了什么」,只能靠人脑/日期推断;
  • 无法在一次 update 里吞掉多批 commit

这就是本提案要解决的核心问题。


2. 目标与非目标

目标

  • 用一个持久、被 git 跟踪的 commit 水位线替换隐式的日期/工作树变更检测。
  • 默认计算 水位线..HEADnet 变更集(跨多个提交的净效果)。
  • 一次 update 运行里支持多批 commit:显式区间、显式提交列表、多个不相邻分组,「一次性 diff 并生成更新 doc」。
  • 成功更新后把水位线推进到一个已提交的 SHA
  • 在非 git / shallow clone / 首跑无水位线 / rebase 后水位线失效等场景优雅降级

非目标

  • 不改变 llmdoc 的知识分层模型(must/ overview/ architecture/ guides/ reference/ memory/ + .llmdoc-tmp/)。
  • 不自动 commit(沿用「只在用户要求时才提交」)。
  • 不引入可执行脚本作为承载契约(保持 markdown-first,见 §14)。
  • 不做并行 recorder fan-out(见 §14)。
  • session-start「落后 N 个提交」提示属于后续增强,不在本提案核心范围(见 §15)。

3. 核心模型:单一 commit 水位线

引入一个被 git 跟踪的水位线(watermark)llmdoc/ 当前已反映的「最后一个 source commit 的 SHA」。每次 update:

读水位线 W → 校验对象存在性与可达性 → 计算 net 变更集(默认 W..HEAD,可叠加多批)
→ 映射到受影响文档 → 选模式跑更新 → 成功后把 W 推进到 HEAD

未提交的工作树变更始终作为附加输入纳入,但永不推进水位线——水位线只记录已提交历史。

硬约束:durable 状态必须进 git,绝不能放 .llmdoc-tmp/(它被 gitignore、不被索引、随时可删)。


4. 状态文件设计

路径

llmdoc/state/sync.md —— 新建 llmdoc/state/ 目录,与 must/ overview/ architecture/ guides/ reference/ memory/ 平级。

为什么是这个位置(对抗审查验证):

  • 不放 llmdoc/memory/skills/llmdoc/templates/stop.sh 的 active-memory 计数会扫 memory/ 下所有文件(只排除 archive/*lessons-learned.mddoc-gaps.md)。放这里会让归档阈值 5 永久虚高,还得打一个脆弱的 stop.sh 补丁。
  • 不用 JSON(如 llmdoc/.sync-state.json):违反 markdown-first,且隐含 jq 依赖。
  • llmdoc/state/ 是独立目录,声明为非知识类:recorder 不往里写文档、index.md 不把它索引为知识。它是「关于文档的同步状态」,不是项目知识。

格式

纯 markdown,一行一事实(用单条 awk/grep 即可解析,永不需要 parser)。唯一承载字段(SHA)用空行与易变字段隔开,避免 git 把它和易变行合成一个多行合并冲突块(对抗复现确认过:相邻易变行会强制多行冲突)。

# llmdoc sync state
<!-- Machine-managed by /llmdoc:update. Do not hand-edit watermark-commit
     except to recover from a rebase. Never add to startup.md / MUST. Never index as knowledge. -->

- schema: 1

- watermark-commit: 7a8fe470627898d46258d8abf50b160a8e198eb8

- repo-root: 15cdb3172a8ed88ab402f19ceee52e146a120508
- watermark-subject: docs: simplify Codex install and add Chinese README link
- updated-at: 2026-06-29T00:00:00Z
- updated-by: /llmdoc:update mode=fast

上面的 SHA 为示例(7a8fe47 为撰写时的 HEAD,15cdb31 为本仓库 root 提交,已实测)。

字段

字段 作用
watermark-commit 唯一承载字段,全 40-hex SHA。读取:awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md | tr -d '[:space:]'
schema 整数格式版本。未知值 ⇒ 当水位线不可读 ⇒ 走 first-run 路径并醒目告警
repo-root 根提交 SHA(git rev-list --max-parents=0 HEAD | tail -1)。仅作身份校验、永不作为控制流闸门;不一致只告警
watermark-subject rebase 后的人工 sanity check 辅助(rebase 通常保留 subject)
updated-at / updated-by 仅调试溯源,永不参与范围计算

不在文件内放滚动 sync-log

对抗审查指出:「newest-first 追加日志」是每次必冲突的生成器,且与 git 历史重复。审计追踪改为依赖 git log -- llmdoc/state/sync.md


5. 范围解析算法(校验阶梯)

所有 git plumbing 不在 set -e 下运行(或逐个捕获退出码),让 128 退出码降级而非中止整个流程。阶梯顺序固定、不可调换

# 1. 能力探测
git rev-parse --is-inside-work-tree            # != true → 非 git:回退旧的工作树检测,
                                               #   不读/不推进水位线,报 "non-git: watermark inactive"
SHALLOW=$(git rev-parse --is-shallow-repository)
H=$(git rev-parse HEAD)                         # 运行开始时只取一次

# 2. 读水位线
W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]')
#    空 / 缺失 / 未知 schema → FIRST-RUN:base=HEAD,不回填(除非 --from/--since),
#    强制 ≥ analysis,结束时在 HEAD 落水位线

# 3. ★ 先查对象存在性(绝不能先 merge-base)
git cat-file -e "$W^{commit}" 2>/dev/null       # rc≠0 → 对象丢失 / GC / 低于 shallow 边界
#    → SHALLOW=true 则提示 `git fetch --unshallow`(不自动 fetch);
#      按 first-run base=HEAD 处理,强制 ≥ analysis,不推进

# 4. 可达性(仅在第 3 步通过后)
git merge-base --is-ancestor "$W" "$H"          # rc=0 → 水位线 VALID,RANGE_BASE=$W
#    rc=1 → 不是祖先,做反向测试:
git merge-base --is-ancestor "$H" "$W"          # rc2=0 → ★ HEAD 落后于水位线
#         → 拒绝:不算 W..HEAD(反向区间会把「未来文件」列为删除,从而抹掉文档);
#           不倒退水位线;告警「HEAD 落后,疑似 reset/checkout 旧历史,确需则用 --from 重锚」;
#           只处理工作树
#    rc2≠0 → 真分叉(rebase / squash-merge):RANGE_BASE=$(git merge-base "$W" "$H"),
#            告警可能与已记录提交重叠(recorder 幂等),强制 ≥ analysis

# 5. 默认 net 变更集(rename 感知、NUL 安全)
git diff --name-status -M -C -z "$RANGE_BASE" "$H"
#    解析 R<score> old\tnew 行:
#      old 路径 → 在 llmdoc/ 中清理过期引用
#      new 路径 → 反映当前状态

对抗审查在本仓库验证出的、三个候选都漏掉的 bug

  1. git merge-base --is-ancestor 对缺失对象退出 128(fatal)而非 1。因此必须git cat-file -e 确认对象存在,否则一个被 GC 的水位线会让整个流程崩溃。
  2. HEAD 落后水位线git reset --hard 到旧提交 / checkout 旧 tag / CI 钉旧 SHA):W..HEAD 变成反向区间,会把「未来才有的文件」列为删除,从而抹掉文档。必须用双向 is-ancestor 检测并拒绝这种情况。

关键 git 语义说明

  • 线性历史下 A..B(two-dot)与 A...B(three-dot)的 --name-only 集合相同;用 two-dot,因为第 4 步已保证/恢复 RANGE_BASEHEAD 的祖先。
  • two-dot net diff 折叠掉「先加后撤」「中间抖动」,正好对应 recorder 只描述现状

6. 多批 commit 支持

/llmdoc:updateargument-hint 扩展为:

[summary] [--range A..B ...] [--commits SHA,SHA ...] [--since REF] [--from SHA] [--working-tree-only] [--include-default]
调用 行为
/llmdoc:update 默认:watermark..HEAD net diff + 工作树
/llmdoc:update --range A..B --range C..D 两个不相邻批次一次性处理:两个 name-status 集合取并集,文档对合并集更新一次
/llmdoc:update --commits abc,def,99a 显式非连续提交列表(merge 提交拒绝、root 提交用空树处理)
/llmdoc:update --since v2.0.0--since 2026-05-01 某 tag/日期以来全部
/llmdoc:update --from <sha> 仅本次重锚起点,不动状态文件
/llmdoc:update --working-tree-only 完全等价今天的旧 fast 行为,永不推进水位线

可自由组合:/llmdoc:update --range A..B --commits zzz 把一个区间批次与一个单提交批次并起来。

处理(单趟、单 recorder,非并行 fan-out)

  1. $ARGUMENTS / 用户消息解析所有批次 flag。
  2. 每批用对应的 git diff --name-status 解析出 impact 路径集:
    • --range A..Bgit diff --name-status -M -C -z A B
    • --commits <sha> → 单提交对其首父;root 提交对空树(见 §10);merge 提交拒绝(见 §10)
    • --since <ref><ref>..HEAD--since <date>git log --since=<date> --name-only --pretty=format: | sort -u
  3. 最终变更集 = 所有批次路径集 ∪ 工作树(unstaged)∪ 暂存(staged)∪ 未跟踪,sort -u。重叠幂等。
  4. 噪声过滤:所有 diff/rev-list 都加 :(exclude)llmdoc/state/sync.md:(exclude).llmdoc-tmp/**,使每次运行重写状态文件不会自触发。
  5. 一次 fast/analysis/full 更新,覆盖整个合并集,只推进一次水位线。

优先级

只要给了 --range/--commits/--since/--from 任一,默认 W..HEAD自动叠加,除非同时给 --include-default

自然语言

「把鉴权重构那几个 commit 和上周 CI 的 commit 一起更新」视作语法糖:assistant 解析成等价 --commits/--since先确认解析出的 SHA,再运行。


7. net diff vs per-commit

两类消费者、两种 git 输入:

recorder 消费 net diff / batch-tip 当前状态

llmdoc/ 匹配仓库现状,从不叙述历史(遵守现有不变量「stable docs 不得描述已不存在的行为」)。

★ 关键纠正(三个候选共有的 blocker):「当前状态」指批次 tip,用 git show <tip>:<path> 读取,只有当 batch tip == HEAD 且工作树干净时才等于磁盘内容。历史/不相邻批次绝不能读 HEAD/磁盘

对抗审查用本仓库 README.md 实测:--commits 456c9ed 若读 HEAD,会把未请求的 7a8fe47 对 README 的改动一起卷进文档。

规则:

  • 同一路径被多批触及 ⇒ 在触及它的最高批次提交处取内容;
  • 批外提交也改过该路径 ⇒ 告警「path X 有批外修改,结果可能过期」,而非静默读 HEAD;
  • 当前 dirty 的路径 ⇒ 磁盘内容优先(最新意图),但标记 uncommitted、不为该路径推进水位线;只有工作树集合才允许从磁盘读。

reflector / investigator 才可消费 per-commit 历史

git log --oneline RANGE_BASE..HEADgit show <sha>git log --first-parent(降侧支噪声)——只用于解释「为什么」(某方案为何被回退、流程教训),永不决定改哪些文档,且仅在真要写 reflection 时使用。

在 merge-base 恢复的 baseline 上,把 reflector 的 per-commit 窗口收窄到不早于旧水位线,避免重新「学习」已被 squash 掉的历史。

写进所有提示与 update-orchestration.md 的不变量

Net diff(批次 tip 处的状态)决定改哪些文档、文档现在该怎么写(recorder)。Per-commit 历史只解释为什么、流程在哪里出问题(reflector,仅在写 reflection 时)。绝不记录 tip 处已不存在的中间状态。


8. 模式选择重构

三模式(fast/analysis/full保留;触发器从「上下文是否新鲜」换成 范围大小 × 作者归属 × 风险。新鲜度变成模式选择的输入,而非变更检测的来源——变更检测始终基于 commit。

模式 触发(可调默认)
fast ≤ 3 个自己作者的提交 且 ≤ 1 个文档簇 且能自信点名受影响文档;--working-tree-only 也默认 fast。保留「提交后立刻更新」的廉价路径
analysis ~4–15 提交,或任一非自己作者的提交(拉取他人代码后无新鲜上下文,作者归属是硬升级信号),或多簇,或 baseline 为恢复/派生/首跑。跑一次聚焦 investigator(sink=file,喂入已解析的 net-diff 路径清单),再 recorder
full > ~15 提交,或多批回填,或历史重写恢复,或事实有争议,或 reflection 重的失败。investigator + reflector + recorder 三角色分离

硬地板(强制 ≥ analysis):merge-base 恢复的 baseline、派生/首跑 baseline、任一非自己作者的提交(「已反映到文档」的假设此时未经证实)。

回填爆炸半径上限:首跑/--since 范围超过 ~20 提交或 ~50 文件 ⇒ 强制 full 且要求用户显式确认(「回填跨 N 提交 / M 文件,继续?」),避免在老仓库上静默产出巨型低价值重写。

阈值以可调默认陈述(「~3」「~15」);assistant 报告所选模式及触发信号;用户可显式覆盖。


9. 更新工作流步骤

  1. 加载 llmdoc skill;读 llmdoc/index.mdstartup.md、MUST 文档、相关 guides/memory/reflections/;校验任何 .llmdoc-tmp/investigations/ 报告是否对当前 revision 仍有效。
  2. 解析同步状态(新):能力探测(is-inside-work-treeis-shallow-repository、一次性取 H=HEAD);用锚定 awkllmdoc/state/sync.md 水位线;跑固定校验阶梯(先 cat-file -e、后 merge-base;HEAD-落后用双向 is-ancestor)。
  3. 计算变更集(新):解析批次 flag;每批用 git diff --name-status -M -C -z 解析(root 用空树、--commits 拒绝 merge);应用 :(exclude)llmdoc/state/sync.md 等 loop-breaker;与工作树+暂存+未跟踪并集;默认批次 RANGE_BASE..H
  4. 处理降级场景(非 git / shallow 低于边界 / 首跑 / 孤儿 / 分叉 / HEAD-落后),见 §10;降级运行绝不伪造水位线推进
  5. 按范围大小 × 作者归属 × 风险选模式;遵守硬地板与回填爆炸半径上限。
  6. 仅按模式需要 investigate,喂入已解析的 net-diff 路径清单;investigator 的 scratch 报告元数据记录解析出的 RANGE_BASE..H 范围。
  7. 仅当存在流程教训时 reflect;reflector 可读 per-commit 历史以捕获「为什么」。
  8. index.md Routing + must/doc-routing.md + reference/repo-surfaces.md 把变更集映射到受影响文档(rename 取 old+new 双路径);recorder 仅更新受影响文档、对批次 tip 状态记录、reconcile memory/doc-gaps.md
  9. 跑 active-memory 归档检查(不变llmdoc/state/memory/ 之外,不计入,无需改 stop.sh)。
  10. 同步 llmdoc/index.md;确保 llmdoc/state/sync.md 被列为知识 / Key Document。
  11. 推进水位线(新,recorder 拥有的终末步):仅在消费了已提交范围的完整成功更新后;若存在任何 in-progress 操作标记或 HEAD detached 则拒绝;按连续前缀规则推进到捕获的 H(默认)或最高未断前缀 tip(部分/批次);working-tree-only/失败/HEAD-落后绝不推进;只重写 watermark-commit + updated-at + watermark-subject
  12. 报告:模式、解析出的范围/批次 + N 个提交、旧→新水位线(或为何没动)、scratch/reflection 路径、归档动作、改动的稳定文档。

10. 边界情况表(已对本仓库验证)

情况 处理
非 git 仓库 is-inside-work-tree != true ⇒ 跳过所有 commit 逻辑,回退旧工作树+暂存+摘要检测,不读/不推进;报 "non-git: watermark inactive"。sync.md 可存在但休眠
shallow clone,水位线低于边界 is-shallow-repository=truecat-file -e $W^{commit} 失败 ⇒ 告警 + 提示 git fetch --unshallow不自动 fetch);按 first-run base=HEAD,强制 ≥ analysis,不推进。可达则正常
首跑 / 无水位线 / schema 不可读 对空/零 SHA diff,git log -1 -- llmdoc/ 派生(已验证它滞后于 HEAD);base=HEAD,本次只记工作树,落水位线=HEAD,强制 ≥ analysis;总是报告所走路径。可选 --from/--since 回填
rebase / squash-merge / 孤儿水位线(对象在、非祖先、HEAD 未落后) cat-file -e 通过、is-ancestor W H=1、反向=1 ⇒ 真分叉:RANGE_BASE=merge-base(W,H),告警可能重叠(recorder 幂等、reflector 跳过旧提交),强制 ≥ analysis,成功后推进到 HEAD
HEAD 落后水位线(reset --hard 旧 / checkout 旧 tag / CI 钉旧 SHA) is-ancestor W H=1 且反向 is-ancestor H W=0 ⇒ 拒绝:不算 W..HEAD(反向区间会抹文档)、不倒退水位线、告警用 --from 重锚、只处理工作树
孤儿水位线(对象 GC/缺失) cat-file -e 失败(捕获退出码,2>/dev/null)⇒ 绝不调 merge-base ⇒ first-run base=HEAD + 醒目告警,强制 analysis,成功后在 HEAD 重写
W == HEAD(已同步) rev-list --count=0 且 net diff 空;若无工作树变更且无 flag ⇒ 报 "already up to date through ",不动文档/水位线直接退出
范围内只动 llmdoc/(之前的文档/状态提交) git diff --name-only RANGE_BASE..HEAD -- ':(exclude)llmdoc/**' 为空 ⇒ 快进水位线到 HEAD、零文档工作,打破文档提交自引用环。混合 source+doc 提交不跳过(存在非 llmdoc 路径)
--commits 含 merge 提交 父数 > 1 ⇒ 拒绝<sha>^! / show --name-only 对 merge 为空,会静默贡献 0 路径),提示改传 A..B
--commits 含 root 提交 git rev-parse --verify --quiet <sha>^ 失败 ⇒ 对空树 diff:git hash-object -t tree /dev/null(= 4b825dc642cb6eb9a060e54bf8d69288fbee4904,已验证返回 root 的全部文件;<sha>^! 对 root 不可靠)
detached HEAD / rebase 中 / merge 中 / cherry-pick / revert 中 HEAD 经 rev-parse HEAD 可解析,范围 math 可算;但若 .git/MERGE_HEADCHERRY_PICK_HEADREVERT_HEADrebase-merge/rebase-apply/(用 git rev-parse --git-path 探测)任一存在或 HEAD plain-detached ⇒ 拒绝推进水位线(detached 落的水位线可能变不可达),告警先完成操作 / 提交到分支
路径在已提交批次与 dirty 工作树都被改 磁盘内容赢(最新意图),标 uncommitted,不为该路径推进水位线。干净的已提交批次路径在批次 tip 读
sync.md 被手改坏 / 未知 schema / 短 SHA 锚定 awk 读、去空白、git rev-parse --verify --quiet 规范化;非全 SHA ⇒ 当不可读走 first-run + 醒目「watermark reset」告警;永不崩溃。写入永远存全 40-hex
并发分支都推进了水位线(合并冲突) 单 SHA 行使冲突最小;解决规则:取后代 SHAis-ancestor A B 判定);若互不为祖先取 merge-base(A,B)(重处理幂等)。repo-root 字段对来自无关历史的水位线告警(advisory,非硬闸)
部分 / 失败更新 不推进。水位线只在完整成功、消费了已提交范围时才动

11. Agent 契约改动

  • agents/investigator.md:update 场景下基于预算 net-diff 路径清单工作(git diff --name-status -M -C);sink=file 元数据加 Range:Watermark:,复用时校验「当前范围是否为报告范围子集」(否则失效);per-commit 仅用于解释意图、不作文档内容。
  • agents/recorder.md:从 net diff 推理、用 git show <tip>:<path>批次 tip内容(除 uncommitted 工作树外绝不读 HEAD/磁盘);不记录 tip 处不存在的中间状态;拥有 llmdoc/state/sync.md,作为成功更新的终末步推进水位线;working-tree-only/失败/部分/HEAD-落后绝不推进;保持 sync.md 不进 index.md 知识目录;index.md 与水位线单写者
  • agents/reflector.md:可读 per-commit 历史(git log/show 跨已同步范围,--first-parent 降噪)以捕获「为什么」、发现回退/返工;merge-base 恢复的 baseline 上忽略旧水位线之前的提交。
  • agents/worker.md:加单写者说明——绝不编辑 index.md / llmdoc/state/sync.md
  • .codex/agents/llmdoc-investigator.toml:镜像 net-diff 路径清单契约 + scratch 范围元数据。
  • .codex/agents/llmdoc-recorder.toml:镜像 sync.md 所有权、批次 tip 内容规则、水位线推进终末步、绝不推进条件、保持不索引规则。
  • .codex/agents/llmdoc-reflector.toml:镜像 per-commit 历史说明(该 toml 确实存在,与 llmdoc-worker.toml 一起共 4 个 toml)。

新增共享不变量(写进所有角色提示与 architecture/update-orchestration.md):durable 同步状态只存于被跟踪的 llmdoc/state/sync.md;水位线记录已提交历史(一个已提交 SHA);未提交工作树变更是附加输入、永不推进它;index.md 与水位线每次运行恰有一个写者。


12. 受影响文件全清单(Claude+Codex 双表面)

guides/updating-update-workflow.md 已规定「改 update 必须跨表面同步」。跨 ~22 个表面的镜像漂移是本提案最大的维护风险,必须一并改。

入口契约

路径 改动
commands/update.md 新增 Sync State 章节(路径/格式/读+推进规则 + 校验阶梯的精确内联 git 命令);argument-hint 加批次 flag;插入 resolve-state / compute-range / advance-watermark 三个 Action;Mode Selection 重锚到范围大小+作者归属;加降级模式 + loop-breaker + 批次 tip 内容规则
skills/llmdoc-update/SKILL.md 为 Codex 镜像以上全部(同 flag、文件、阶梯、步骤),从 $ARGUMENTS / 用户消息读 flag

方法论 / 设计

路径 改动
skills/llmdoc/references/update-and-memory.md 加 commit 水位线模型、llmdoc/state/sync.md 契约、net-diff vs per-commit(批次 tip)规则;Memory ownership 下把 sync.md 归 recorder
llmdoc/architecture/update-orchestration.md 加水位线进 Knowledge Layers + Invariants(durable tracked markdown;绝不进 .llmdoc-tmp/;只推进到已提交 SHA;工作树不推进;可达性门控;单写者;不作知识索引);记录解析阶梯、two-dot 默认、连续前缀推进,以及被否决的备选

Agents

agents/{investigator,recorder,reflector,worker}.md + .codex/agents/llmdoc-{investigator,recorder,reflector}.toml(改动见 §11)。

新文件 + Init 落种子

路径 改动
llmdoc/state/sync.md 新建被跟踪文件;本仓库随引入提交预置指向该提交(dogfood,老用户升级即有水位线)
commands/init.md init 时创建 llmdoc/state/sync.mdwatermark-commit=HEADrepo-root 落种子,使首次 update 已有有效锚点
skills/llmdoc-init/SKILL.md 为 Codex 镜像 init 落种子
skills/llmdoc/references/templates.md llmdoc/state/sync.md 模板块供 init 脚手架

文档 / 索引

路径 改动
llmdoc/guides/updating-update-workflow.md Main Steps 加新 flag、sync.md 语义、水位线推进;mirror 步骤补 reflector + worker toml;Common Failure Points 加(把 git-log 派生当真相、对未提交/HEAD-落后推进、先 merge-base 后 cat-file、为历史批次读 HEAD、two-dot 反向区间);Verification 加(只推进到已提交 SHA、working-tree-only 永不推进、sync.md 不被索引、manifest description 也要改而非仅版本)
llmdoc/reference/repo-surfaces.md 登记 llmdoc/state/sync.md 为被跟踪的 durable 同步状态表面与真相源
llmdoc/index.md llmdoc/state/ 列为非知识、机器管理的状态目录(一行说明)+ Routing「改水位线/范围逻辑前先读 update-orchestration」;加不变量「index 永不把水位线索引为知识」。把 sync.md 列为 Key Document
llmdoc/must/doc-routing.md 加 Read-Next:把 /llmdoc:update 范围/水位线改动路由到 update-orchestration + updating-update-workflow(现有条目已指向,扩展以点名水位线)
llmdoc/memory/doc-gaps.md 关掉「.codex/agents toml 落后于 prose」旧 gap(镜像后);加新 gap:无可执行测试/fixture 验证 range/watermark math(rebase/squash/shallow/非 git/HEAD-落后)

公开面 + 清单

路径 改动
README.md Public Surface + /llmdoc:update 章节:说明 commit-based update、水位线文件、批次 flag、优雅降级
README.zh-CN.md 中文镜像 README.md 改动(公开接口 + /llmdoc:update
.claude-plugin/plugin.json 升版本 更新 description 提到 commit-watermark update(不能只改版本号)
.codex-plugin/plugin.json 升版本 更新 description / longDescription,措辞与 Claude manifest 平行
.gitignore 不变。列出以使决定显式:llmdoc/state/ 保持被跟踪,只忽略 .llmdoc-tmp/

13. 迁移与向后兼容

  1. 新 init going forwardcommands/init.md + skills/llmdoc-init/SKILL.md 在 bootstrap 时落 watermark-commit=HEADrepo-root,新仓库开局即同步,首次 /llmdoc:update 已有有效锚点。
  2. 已有 llmdoc 仓库升级(有 llmdoc/llmdoc/state/sync.md):首次 update 检测到文件缺失 ⇒ 走 first-run = 在当前 HEAD 落种子、不历史回填(前提:现有文档已反映到 HEAD,这正是「维护中的文档」的语义),本次只记工作树变更,再写水位线=HEAD。强制 ≥ analysis(假设未经证实),报告 "seeded baseline at HEAD ; documenting working tree only"。
  3. 可选回填:首跑加 --from <已知好提交>--since <tag>,diff base..HEAD(analysis/full,受爆炸半径上限),追溯吸收后写水位线=HEAD,报告 "backfilled from "。
  4. Dogfood:本仓库随引入提案的提交预置 sync.md 指向该提交,升级用户直接拿到显式水位线,而非依赖首跑派生。
  5. 原子性(鸡生蛋):水位线写入是普通的被跟踪编辑,与文档改动在同一个用户提交里(workflow 从不自动 commit)。它记录运行开始时捕获的 HEAD;下一次运行 旧HEAD..文档提交 全是 llmdoc/ 路径,:(exclude)llmdoc/** loop-breaker 快进、零文档工作。若用户漏改水位线就提交文档,下次靠同样的 llmdoc-only 快进自愈。
  6. 向后兼容:date-stamped scratch/reflection/archive 目录不变--working-tree-only 完整保留旧 fast 行为;唯一新增 durable 物件就是 llmdoc/state/sync.md

14. 被否决的备选(及理由)

  • JSON 状态文件llmdoc/.sync-state.json):破坏 markdown-first,隐含 jq 依赖。
  • git notes / tag 存水位线:不在 PR diff 里可见、push 需额外配置、易丢。
  • 纯派生(把「last commit touching llmdoc/」当真相):已验证滞后 HEAD,且把「碰过文档」与「同步到此源状态」混为一谈。仅作 first-run 的可选回退,不作主路径。
  • llmdoc-range.sh 助手脚本:(1) 破坏 markdown-first,让可执行 shell 成为承载契约;(2) 放 skills/llmdoc/templates/ 会被 init 复制进每个用户仓库,成为无主、版本漂移的代码面;(3) 给最易错的逻辑加未测试代码;(4) 还得双表面镜像(反而增面)。范围 git 命令以文档化的精确命令写在 command + skill 里作单一真相。若未来仍要助手脚本,它不能templates/(用不被复制的位置),必须登记进 reference/repo-surfaces.mdupdating-update-workflow.md 镜像清单,并附带覆盖 rebase/squash/shallow/非 git/HEAD-落后/root/merge 的测试 fixture。
  • 并行 recorder fan-out:对共享 index.md / doc-gaps.md 的无保护并发写。坚持单 recorder 写者。
  • 文件内滚动 sync-log:每次必冲突,且与 git 历史重复。审计靠 git log -- llmdoc/state/sync.md

15. 待拍板的决策(含推荐默认)

  1. 升降级阈值(fast→analysis→full 的提交/文件数)。推荐默认:≤3 个自作者提交 ⇒ fast 可选;~4–15 或任一他人提交 ⇒ analysis;>15 或多批回填或重写恢复 ⇒ full。作可调指引,非硬闸。
  2. 回填爆炸半径确认阈值推荐默认:首跑/--since 超 ~20 提交或 ~50 文件时,处理前向用户显式确认。
  3. 是否为 llmdoc/state/sync.md.gitattributes merge driver(descendant-wins,else merge-base)。推荐默认:初期不配——单 SHA 行格式 + 文档化的「后代赢」规则对常见场景足够;并发分支冲突频发再加(merge driver 需每个 clone 注册 git config,.gitattributes 单独不保证生效,增加摩擦)。
  4. session-start hook 是否提示「文档落后 HEAD N 个提交」推荐默认留后续。若做,rev-list --countis-shallow-repository=false 守卫(否则报 "unavailable (shallow)")、排除 :(exclude)llmdoc/**、所有 git 调用 || true 包裹以免破坏会话。不属核心重构。
  5. 是否保留任何文件内审计日志推荐默认:不留——靠 git log -- llmdoc/state/sync.md(避免每次运行的合并冲突噪声)。

16. 风险

  • 跨 ~22 表面的镜像漂移guide 早就警告的失败模式,现在面更大(flag + 状态文件)。靠扩展 updating-update-workflow.md 清单缓解,这是最大维护风险
  • 批次 tip 内容选择比读 HEAD 复杂:若后来维护者「简化」回读 HEAD/磁盘,不相邻批次正确性 bug 复发(已用 README.md 重叠验证)。必须在 recorder 提示里写成硬不变量。
  • 水位线指向「文档提交之前」的不变量较微妙:未来维护者若让 recorder 编辑后重读 HEAD,或把文档单独提交,可能触发重扫。靠 :(exclude)llmdoc/** loop-breaker 在 update-orchestration.md 与角色提示里显式声明缓解。
  • shallow / CI clone 常见:水位线早于 clone 深度时静默回退工作树检测、可能少记。靠告警 + --unshallow 提示 + 拒绝推进缓解,但用户可能忽略告警。
  • range/watermark math 无可执行测试(rebase/squash/shallow/非 git/HEAD-落后/root/merge):最易错的逻辑未被验证。已登记为新 doc-gap;靠本提案中已实测的 git 命令行为缓解。
  • markdown grep/awk 解析:对单 SHA 行健壮,但若有人重排文件格式则脆弱。靠 schema 字段、机器管理 banner、读时 rev-parse 规范化缓解。
  • repo-root 身份在从根重写历史(filter-repo)时失效:罕见,落入孤儿/分叉路径降级为 base=HEAD。保持 advisory,永不阻塞。

17. 验证 / 测试计划

实现完成后应满足(对应 updating-update-workflow.md 的 Verification 扩展):

  • /llmdoc:update 与 Codex llmdoc-update 描述相同的批次 flag 与水位线语义。
  • 水位线只推进到一个已提交的 SHA--working-tree-only / 失败 / HEAD-落后永不推进
  • llmdoc/state/sync.md 被 git 跟踪、index.md 索引为知识、进 startup/MUST。
  • 校验阶梯顺序为:能力探测 → 读水位线 → cat-file -e → 双向 is-ancestor → 范围。
  • 两个 plugin manifest 的 version 与 description 都更新。
  • 针对以下场景的人工或脚本化冒烟(建议补 fixture,见 doc-gap):
    1. 干净线性 W..HEAD
    2. 多批 --range A..B --range C..D
    3. --commits 含 root / 含 merge(应拒绝);
    4. rebase 后孤儿水位线(merge-base 恢复);
    5. git reset --hard 到旧提交(HEAD-落后,应拒绝);
    6. shallow clone 水位线越界;
    7. 非 git 目录(回退旧行为);
    8. 只动 llmdoc/ 的提交(应快进、零文档工作)。

本提案由多方案对比 + 对抗式边界审查综合而成;多处 git 行为已对本仓库历史实测。实现时建议先用 EnterPlanMode 把「改哪 22 个文件、什么顺序、怎么验证」定成可执行计划。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions