From a9ad7bf877ba12b48e666fb3bc19638cfcf5680f Mon Sep 17 00:00:00 2001 From: wh1teAlter <2369332823@qq.com> Date: Thu, 9 Jul 2026 17:38:50 +0800 Subject: [PATCH 1/5] feat: commit-watermark based /llmdoc:update (contract, agents, state) Replace date/working-tree change detection with a durable commit watermark (llmdoc/state/sync.md). /llmdoc:update and the Codex llmdoc-update skill now diff watermark..HEAD (net), support multi-batch --range/--commits/--since/--from, degrade gracefully (non-git, shallow, first-run, rebased, HEAD-behind), and advance the watermark on success. - New tracked watermark: llmdoc/state/sync.md (single load-bearing SHA) - Rewrote commands/update.md + skills/llmdoc-update/SKILL.md: resolution ladder (rev-parse existence check before merge-base; two-direction is-ancestor for HEAD-behind), batch flags, watermark-advance terminal - Agents (Claude .md + Codex .toml): recorder owns/advances the watermark with batch-tip content reads and single-writer; worker never edits index/state - Init seeds the watermark; README (EN/ZH) + plugin manifests -> 2.2.0 Note: this commit intentionally does NOT update the llmdoc/ knowledge docs, so the next /llmdoc:update dogfoods the new capability. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 4 +- .codex-plugin/plugin.json | 6 +- .codex/agents/llmdoc-investigator.toml | 2 + .codex/agents/llmdoc-recorder.toml | 5 + .codex/agents/llmdoc-reflector.toml | 1 + .codex/agents/llmdoc-worker.toml | 1 + README.md | 18 ++- README.zh-CN.md | 18 ++- agents/investigator.md | 5 +- agents/recorder.md | 10 +- agents/reflector.md | 1 + agents/worker.md | 1 + commands/init.md | 2 + commands/update.md | 137 ++++++++++++------ llmdoc/state/sync.md | 11 ++ skills/llmdoc-init/SKILL.md | 2 + skills/llmdoc-update/SKILL.md | 129 +++++++++++------ skills/llmdoc/references/templates.md | 18 +++ skills/llmdoc/references/update-and-memory.md | 28 +++- 20 files changed, 288 insertions(+), 113 deletions(-) create mode 100644 llmdoc/state/sync.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b5c568b..5bf760b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "llmdoc", "source": "./", - "description": "llmdoc workflow for Claude Code: init, update modes, temporary investigation cache, and use" + "description": "llmdoc workflow for Claude Code: init, commit-watermark update detection, update modes, temporary investigation cache, and use" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 8f13ded..d121e2d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "llmdoc", - "description": "llmdoc Claude Code plugin with init, update modes, temporary investigation cache, and use", - "version": "2.1.0", + "description": "llmdoc Claude Code plugin with init, commit-watermark update detection, update modes, temporary investigation cache, and use", + "version": "2.2.0", "author": { "name": "DJJ & Danniel" } diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 61dc1f5..9a31ec6 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "llmdoc", - "version": "2.1.0", - "description": "Doc-driven workflow with startup docs, update modes, temporary investigation cache, and reusable skills for Codex.", + "version": "2.2.0", + "description": "Doc-driven workflow with startup docs, commit-watermark update detection, update modes, temporary investigation cache, and reusable skills for Codex.", "author": { "name": "DJJ & Danniel", "email": "shuaiqijianaho@qq.com", @@ -13,7 +13,7 @@ "interface": { "displayName": "llmdoc", "shortDescription": "Doc-driven coding workflow", - "longDescription": "Use startup docs, guides, update modes, temporary investigation cache, and focused reflections to improve coding quality and reduce repeated work.", + "longDescription": "Use startup docs, guides, commit-watermark update detection, update modes, temporary investigation cache, and focused reflections to improve coding quality and reduce repeated work.", "developerName": "TokenRoll", "category": "Productivity", "capabilities": ["Read", "Write"], diff --git a/.codex/agents/llmdoc-investigator.toml b/.codex/agents/llmdoc-investigator.toml index cac80c8..5e08e04 100644 --- a/.codex/agents/llmdoc-investigator.toml +++ b/.codex/agents/llmdoc-investigator.toml @@ -18,6 +18,8 @@ Then follow this protocol: - Prefer file-level and symbol-level references. - Only add line numbers when needed to prove subtle or disputed behavior. - If asked to write findings to disk, write temporary scratch reports under .llmdoc-tmp/investigations/. +- When invoked for /llmdoc:update, work from the precomputed net-diff path list (git diff --name-status -M -C) instead of re-deriving the change set; consult per-commit history only to explain intent, never as doc content. +- In file reports, record the resolved commit range and watermark in the metadata (Range:, Watermark:) so a report is reused only when the current range is a subset of the report's range. - Do not silently redesign the system. Return evidence, findings, gaps, and recommended next reads. """ nickname_candidates = ["Scout", "Trace", "Atlas"] diff --git a/.codex/agents/llmdoc-recorder.toml b/.codex/agents/llmdoc-recorder.toml index 817c5bd..9f6012a 100644 --- a/.codex/agents/llmdoc-recorder.toml +++ b/.codex/agents/llmdoc-recorder.toml @@ -18,5 +18,10 @@ Your responsibilities: - Promote only recurring, stable lessons into must/, guides/, or reference/. - Do not paste large code blocks. - Prefer file-level and symbol-level references. +- You are the sole writer of llmdoc/index.md and llmdoc/state/sync.md (the commit watermark) for a run. +- Reason from the NET diff and read committed-batch content at the BATCH TIP (git show :), never from HEAD or disk, except the uncommitted working-tree set. Never document an intermediate state absent at the tip. +- Advance watermark-commit only as the terminal step of a complete, successful update that consumed a committed range; rewrite only watermark-commit, watermark-subject, and updated-at/by. +- NEVER advance on a --working-tree-only run, a failed/partial run, a HEAD-behind-watermark run, or while a git operation is in progress or HEAD is detached. +- Do not index .llmdoc-tmp/, and keep llmdoc/state/sync.md out of llmdoc/index.md, startup.md, and MUST; it is machine-managed state, not knowledge. """ nickname_candidates = ["Ledger", "Archive", "Quill"] diff --git a/.codex/agents/llmdoc-reflector.toml b/.codex/agents/llmdoc-reflector.toml index 831f8c1..13c4f00 100644 --- a/.codex/agents/llmdoc-reflector.toml +++ b/.codex/agents/llmdoc-reflector.toml @@ -14,6 +14,7 @@ Your responsibilities: - Write concise reflections to llmdoc/memory/reflections/. - Capture what went wrong, why it happened, what docs or signals were missing, and what may deserve promotion. - Do not rewrite stable architecture docs here. +- You may read per-commit history (git log, git show, git log --first-parent to cut side-branch noise) over the synced range to capture WHY a change happened and spot reversals or rework; on a merge-base-recovered baseline, ignore commits older than the previous watermark. Per-commit history explains why; it never decides which stable docs change. - Leave compact, reusable learning artifacts. """ nickname_candidates = ["Echo", "Pulse", "North"] diff --git a/.codex/agents/llmdoc-worker.toml b/.codex/agents/llmdoc-worker.toml index c06ffb2..6389ac8 100644 --- a/.codex/agents/llmdoc-worker.toml +++ b/.codex/agents/llmdoc-worker.toml @@ -17,5 +17,6 @@ Operate with these rules: - If you enter a new subsystem or hit a failure, re-read relevant docs before expanding code search. - Use guides and reflections proactively to improve quality. - Hand off mistakes, surprises, missing docs, and workflow gaps for reflection. +- Never edit llmdoc/index.md or llmdoc/state/sync.md; recorder is their sole writer. """ nickname_candidates = ["Forge", "Brick", "Delta"] diff --git a/README.md b/README.md index 9d92390..e797fde 100644 --- a/README.md +++ b/README.md @@ -70,21 +70,25 @@ In Codex, use the helper skill `llmdoc-update` for the equivalent workflow. The command keeps tracked `llmdoc/` docs consistent with the current repository. Stable docs should stay compact: either smaller than the source they describe or useful because they explain architecture, implementation intent, boundaries, and stable contracts. -The command selects the lightest mode that can keep docs correct: +Change detection is **commit-based**. A tracked watermark, `llmdoc/state/sync.md`, records the last source commit already reflected in `llmdoc/`. By default the command diffs `watermark..HEAD` (the net change across every commit since the last sync) and advances the watermark on success. Uncommitted working-tree changes are folded in as an additional input but never move the watermark. -- `fast`: default for immediate post-implementation updates when the coordinating assistant has fresh context -- `analysis`: one focused evidence pass for stale context, current-state research, or unclear impact -- `full`: separate investigation, reflection, and recording when risk or process learning justifies the extra independence +It can ingest multiple commit batches in one run — `--range A..B` (repeatable), `--commits SHA,…`, `--since REF`, `--from SHA`, `--working-tree-only` — and degrades gracefully on non-git projects, shallow clones, a first run with no watermark, and rebased/orphaned watermarks. + +The command selects the lightest mode that can keep docs correct, keyed on range size × authorship × risk: + +- `fast`: a small, self-authored range with nameable impacted docs +- `analysis`: a larger range, any non-self-authored commit, or a recovered/first-run baseline — one focused evidence pass +- `full`: large or high-risk ranges, multi-batch backfill, or history-rewrite recovery — separate investigation, reflection, and recording The command: -1. Rebuilds context from llmdoc and the current working tree +1. Reads the watermark and computes the commit range (plus any batch flags and the working tree) 2. Proactively reads relevant guides and reflections -3. Chooses an update mode +3. Chooses an update mode from range size, authorship, and risk 4. Investigates impacted concepts only when the selected mode requires it 5. Writes a reflection under `llmdoc/memory/reflections/` only when there is a workflow lesson or missing-doc signal 6. Updates stable docs and reconciles `llmdoc/memory/doc-gaps.md` -7. Synchronizes `llmdoc/index.md` +7. Synchronizes `llmdoc/index.md` and advances the watermark on success In normal use, the main assistant should proactively ask whether to run `/llmdoc:update` when the task produced durable knowledge or a useful reflection. diff --git a/README.zh-CN.md b/README.zh-CN.md index 676b0cb..142b3f5 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -70,21 +70,25 @@ 这个命令会让 tracked `llmdoc/` 文档和当前仓库保持一致。稳定文档应该保持紧凑:要么比它描述的源码更小,要么能解释源码搜索无法快速提供的架构、实现意图、边界和稳定契约。 -这个命令会选择能保证文档正确的最轻模式: +变更检测是**基于 commit** 的。一个被 git 跟踪的水位线 `llmdoc/state/sync.md` 记录 `llmdoc/` 已反映到的最后一个 source commit。默认对 `水位线..HEAD` 做 diff(自上次同步以来所有提交的净变更),成功后推进水位线。未提交的工作树变更作为附加输入纳入,但永不移动水位线。 -- `fast`:默认模式,适合主 assistant 刚完成实现、上下文仍然新鲜的情况 -- `analysis`:适合上下文变旧、需要调研当前现状、或影响范围不清时的一次聚焦证据 pass -- `full`:适合风险较高、事实有争议、或确实需要独立反思与复核的情况 +它可以在一次运行里吞掉多批 commit —— `--range A..B`(可重复)、`--commits SHA,…`、`--since REF`、`--from SHA`、`--working-tree-only` —— 并在非 git 项目、shallow clone、首次运行无水位线、rebase/孤儿水位线等情况下优雅降级。 + +这个命令会选择能保证文档正确的最轻模式,触发器是 范围大小 × 作者归属 × 风险: + +- `fast`:小范围、自己作者、受影响文档可点名 +- `analysis`:较大范围、含任一他人提交、或恢复/首次运行的 baseline —— 一次聚焦证据 pass +- `full`:大范围或高风险、多批回填、或历史重写恢复 —— 独立的调研、反思与记录 这个命令会: -1. 基于 llmdoc 和当前 working tree 重建上下文 +1. 读水位线并计算 commit 范围(外加任何批次 flag 与工作树) 2. 主动阅读相关 guides 和 reflection -3. 选择 update mode +3. 基于范围大小、作者归属、风险选择 update mode 4. 只在所选模式需要时调研受影响的概念 5. 只有出现工作流教训或缺失文档信号时,才在 `llmdoc/memory/reflections/` 下写 reflection 6. 更新稳定文档,并清账 `llmdoc/memory/doc-gaps.md` -7. 同步 `llmdoc/index.md` +7. 同步 `llmdoc/index.md`,并在成功后推进水位线 在日常使用里,如果任务产生了值得长期保留的知识或反思,主 assistant 应该主动询问是否现在运行 `/llmdoc:update`。 diff --git a/agents/investigator.md b/agents/investigator.md index b5f108a..45394f0 100644 --- a/agents/investigator.md +++ b/agents/investigator.md @@ -28,7 +28,8 @@ Key practices: - **Objective:** Report facts and evidence, not design opinions. - **Split by sink:** `sink=chat` is for direct answers. `sink=file` is for temporary scratch artifacts, usually under `.llmdoc-tmp/investigations/`. - **Temporary means temporary:** `.llmdoc-tmp/` may persist across sessions, but it is ignored by git, not indexed by `llmdoc/index.md`, and can be deleted at any time. -- **Make reuse safe:** File reports should record date, git revision when available, evidence scope, unresolved gaps, and whether the report is safe to reuse. +- **Make reuse safe:** File reports should record date, git revision when available, the resolved commit range and watermark when the task is an update, evidence scope, unresolved gaps, and whether the report is safe to reuse. A report is reusable only if the current resolved range is a subset of the report's range. +- **Update input:** When invoked for `/llmdoc:update`, work from the precomputed net-diff path list (`git diff --name-status -M -C`) instead of re-deriving the change set. Consult per-commit history only to explain intent, never as doc content. - **No long code pastes:** The reader can open source files directly. @@ -73,6 +74,8 @@ Write a markdown file using the same section layout as ``, pl - Date: - Git Revision: +- Range: +- Watermark: - Evidence Scope: - Reuse Conditions: diff --git a/agents/recorder.md b/agents/recorder.md index 5cba813..01ab723 100644 --- a/agents/recorder.md +++ b/agents/recorder.md @@ -28,9 +28,17 @@ Consistency rules: - Correct or remove stable-doc claims that no longer match the current code. - Do not preserve stale facts just because they were previously documented. - Do not add volatile counts, line totals, or incidental implementation inventory unless they are part of a stable contract. -- Do not index `.llmdoc-tmp/`. +- Do not index `.llmdoc-tmp/`, and do not index `llmdoc/state/sync.md` as knowledge. - Reconcile `memory/doc-gaps.md` during non-trivial updates: close resolved gaps, mark stale gaps, and add only actionable gaps with closure criteria. +Commit watermark ownership (`llmdoc/state/sync.md`): + +- You are the sole writer of `llmdoc/index.md` and `llmdoc/state/sync.md` for a run. +- Reason from the NET diff and read committed-batch content at the BATCH TIP (`git show :`), never from HEAD or disk — except the uncommitted working-tree set, which is read from disk. Never document an intermediate state absent at the tip. +- Advance `watermark-commit` only as the terminal step of a complete, successful update that consumed a committed range; rewrite only `watermark-commit`, `watermark-subject`, and `updated-at/by`. +- NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or while a git operation is in progress or HEAD is detached. +- Keep `llmdoc/state/sync.md` out of `llmdoc/index.md`, `startup.md`, and the MUST pack; it is machine-managed state, not knowledge. + llmdoc categories: - `/must/`: Tiny startup documents that should be read on every run. Only recurring, cross-task, stable knowledge belongs here. diff --git a/agents/reflector.md b/agents/reflector.md index f2623ad..09b8d22 100644 --- a/agents/reflector.md +++ b/agents/reflector.md @@ -23,6 +23,7 @@ Reflection rules: - Record what went wrong, why it went wrong, and how to avoid it next time. - Keep each reflection focused on one task. - Do not rewrite stable architecture docs here. +- You may read per-commit history (`git log`, `git show`, `git log --first-parent` to cut side-branch noise) over the synced range to capture WHY a change happened and spot reversals or rework. On a merge-base-recovered baseline, ignore commits older than the previous watermark. Per-commit history explains why; it never decides which stable docs change. # [Task Reflection] diff --git a/agents/worker.md b/agents/worker.md index a5673c3..a2429e6 100644 --- a/agents/worker.md +++ b/agents/worker.md @@ -25,6 +25,7 @@ Key practices: - Add line numbers only when necessary to justify a non-obvious behavior. - Do not pause to discuss with the user. Coordination belongs to the calling assistant. - Do not write reflection files yourself. Hand off facts for `reflector`. +- Never edit `llmdoc/index.md` or `llmdoc/state/sync.md`; `recorder` is their sole writer. - **Objective**: What needs to be accomplished. diff --git a/commands/init.md b/commands/init.md index 3320da9..98a038b 100644 --- a/commands/init.md +++ b/commands/init.md @@ -30,7 +30,9 @@ Why: - `llmdoc/reference/` - `llmdoc/memory/reflections/` - `llmdoc/memory/decisions/` + - `llmdoc/state/sync.md` - `.llmdoc-tmp/investigations/` + - Seed the commit watermark in `llmdoc/state/sync.md` (template in `skills/llmdoc/references/templates.md`): set `watermark-commit` to `$(git rev-parse HEAD)`, so the first `/llmdoc:update` has a valid anchor. Skip this in a non-git project. `llmdoc/state/sync.md` is machine-managed state, not knowledge: never index it or add it to `startup.md`/`must/`. 3. Run investigation. - Use `investigator` for evidence gathering. diff --git a/commands/update.md b/commands/update.md index efe31b1..8459ef4 100644 --- a/commands/update.md +++ b/commands/update.md @@ -1,6 +1,6 @@ --- -description: "Keep tracked llmdoc docs current with the repository using the lightest sufficient update mode." -argument-hint: "[optional summary of what changed]" +description: "Keep tracked llmdoc docs current with the repository using commit-based change detection and the lightest sufficient update mode." +argument-hint: "[summary] [--range A..B ...] [--commits SHA,SHA ...] [--since REF] [--from SHA] [--working-tree-only] [--include-default]" --- # /llmdoc:update @@ -9,6 +9,8 @@ Use this command after a task when the tracked project knowledge, workflow guida The goal is not to archive everything the task discovered. The goal is to keep tracked `llmdoc/` docs consistent with the current repository while staying smaller than the source code or adding architectural explanation the source cannot provide by itself. +Change detection is **commit-based**: it is anchored on a durable watermark (`llmdoc/state/sync.md`) recording the last source commit already reflected in `llmdoc/`, not on the working tree or the date. Uncommitted changes are an additional input; they never move the watermark. + Before executing the workflow, load the `llmdoc` skill. Why: @@ -17,15 +19,69 @@ Why: - the skill explains update modes, memory handling, and when reflection is useful - this command should focus on orchestration, not re-explain the whole system +Invariants, design rationale, and verified git semantics: `llmdoc/architecture/update-orchestration.md`. + +## Sync State + +`llmdoc/state/sync.md` is a tracked, machine-managed markdown file holding one load-bearing field, `watermark-commit` (full 40-hex SHA). It is not project knowledge: never index it, never add it to `startup.md`/`must/`, never count it as active memory. Read the watermark with a single anchored line: + +```sh +W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') +``` + +`recorder` is the only writer of `sync.md`, and only advances it as the terminal step of a successful update (see Watermark Advance). + +## Change Detection (commit range) + +Run all git plumbing without `set -e` (capture each exit code) so a `128` degrades instead of aborting. The resolution ladder order is an invariant — never reorder: + +```sh +# 1. Capability probe +[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ] || { echo "non-git: watermark inactive"; } # → legacy working-tree detection, no watermark read/advance +SHALLOW=$(git rev-parse --is-shallow-repository) +H=$(git rev-parse HEAD) # capture once + +# 2. Read watermark (empty/unknown-schema → first-run: base=HEAD, no backfill unless --from/--since, force ≥ analysis, seed at HEAD) +W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') + +# 3. EXISTENCE before reachability (merge-base --is-ancestor exits 128 fatal on a missing object; guard first) +git rev-parse --verify --quiet "$W^{commit}" >/dev/null || { echo "watermark object missing"; } # rev-parse --verify --quiet exits 1 cleanly on a missing/non-commit object (cat-file -e "$W^{commit}" would fatal 128 on the peel) → first-run baseline=HEAD, warn, do not advance; if $SHALLOW=true suggest: git fetch --unshallow + +# 4. Reachability +git merge-base --is-ancestor "$W" "$H" # rc=0 → valid, RANGE_BASE=$W +# rc≠0 → not ancestor; reverse test: +git merge-base --is-ancestor "$H" "$W" # rc=0 → HEAD BEHIND watermark → REFUSE (reverse range would delete docs); do not advance backward; working-tree input only +# else → true divergence (rebase/squash): RANGE_BASE=$(git merge-base "$W" "$H"); force ≥ analysis + +# 5. Net change set (rename-aware, NUL-safe) +git diff --name-status -M -C -z "$RANGE_BASE" "$H" # R old\tnew → old path cleans stale refs, new path reflects current state +``` + +Batch flags (if any of `--range/--commits/--since/--from` is given, the default `RANGE_BASE..HEAD` is NOT added unless `--include-default`). Each batch resolves to a set of changed **paths**: parse the `--name-status` records — drop the status column, and for a rename `R` take BOTH the old and new path. Union all batch path-sets with the working-tree set, deduplicated (do not `sort -u` raw NUL/status records): + +- `--range A..B` (repeatable): `git diff --name-status -M -C -z A B`. +- `--commits SHA,…`: per commit `git diff --name-status -M -C -z ^ `; **reject merge commits** (parent count > 1 → tell the user to pass `A..B`); **root commit** diffs against the empty tree `git hash-object -t tree /dev/null` (`4b825dc642cb6eb9a060e54bf8d69288fbee4904`). +- `--since REF|date`: for a ref, `..HEAD`; for a date, resolve a boundary commit `B=$(git rev-list -1 --before= HEAD)` and diff `B..HEAD` with the same `git diff --name-status -M -C -z` as every other branch (do NOT `git log --name-only`, which yields a statusless, blank-line-polluted shape that will not union cleanly). +- `--from SHA`: one-off start override for this run only; does not touch `sync.md`. +- `--working-tree-only`: skip the committed range entirely; document only uncommitted changes; never advance the watermark. + +Always fold in, as an ADDITIONAL input tagged `worktree` (never advances the watermark): `git diff --name-only HEAD` (unstaged) ∪ `git diff --name-only --cached` (staged) ∪ `git ls-files --others --exclude-standard` (untracked). + +Empty range (`W == HEAD`, the commonest steady-state run): if `RANGE_BASE..HEAD` is empty AND there are no working-tree changes AND no batch flags, report `already up to date through ` and exit without touching docs, the index, or the watermark. + +Doc-commit loop-breaker: exclude `llmdoc/` from the "is there source work?" test. If `git diff --name-only "$RANGE_BASE".."$H" -- ':(exclude)llmdoc/**'` is empty (range non-empty but doc-only), fast-forward the watermark to HEAD with zero doc work. Always exclude the sync file and `.llmdoc-tmp/` from every diff so a run never self-triggers. + +Content selection: `recorder` reads committed-batch file content at the BATCH TIP (`git show :`), never from HEAD/disk, except for the uncommitted working-tree set (read from disk, which wins for any currently-dirty path). + ## Mode Selection -Choose the lightest mode that can keep the docs correct. +Choose the lightest mode that can keep the docs correct. The trigger is range size × authorship × risk (not context freshness). -- `fast` is the default when the coordinating assistant just implemented the change and has fresh context. Skip mandatory investigation and update stable docs from the task summary, diff, targeted checks, and any still-valid local scratch reports. -- `analysis` is for stale context, unclear impact, current-state research, or handoff work. Use one focused analysis pass, usually `investigator` with `sink=file`, then let `recorder` consume the scratch report and the current working tree. -- `full` is for high-risk rewrites, init-like rebootstrap work, disputed facts, or reflection-heavy failures. Use investigator, reflector, and recorder as separate roles. +- `fast`: small range (≤ ~3 commits), self-authored, impacted docs nameable. `--working-tree-only` defaults here. Skip mandatory investigation; update stable docs from the task summary, diff, targeted checks, and any still-valid scratch reports. +- `analysis`: ~4–15 commits, OR any non-self-authored commit, OR multiple clusters, OR a recovered/derived/first-run baseline. Use one focused `investigator` pass with `sink=file`, then `recorder`. +- `full`: > ~15 commits, multi-batch backfill, history-rewrite recovery, disputed facts, or reflection-heavy failures. Use investigator, reflector, and recorder as separate roles. -Escalate from `fast` to `analysis` when the assistant cannot name the impacted docs confidently. Escalate to `full` when independent review is more valuable than speed. +Hard floors (always force ≥ `analysis`): a merge-base-recovered baseline, a derived/first-run baseline, or any non-self-authored commit. Backfill blast-radius cap: a first-run/`--since` range beyond ~20 commits or ~50 files forces `full` and explicit user confirmation. Thresholds are tunable defaults; report the chosen mode and the signals that triggered it. ## `.llmdoc-tmp/` @@ -33,50 +89,47 @@ Escalate from `fast` to `analysis` when the assistant cannot name the impacted d - Investigator reports may be retained across sessions under `.llmdoc-tmp/investigations/`. - They are ignored by git, not indexed by `llmdoc/index.md`, and may be deleted at any time. -- Reuse them only after checking their recorded git revision, scope, and unresolved gaps against the current repository. +- Reuse them only after checking their recorded git revision, resolved range, scope, and unresolved gaps against the current repository. - Promote only durable conclusions into tracked `llmdoc/` docs. ## Actions 1. Rebuild task context. - - Read `llmdoc/index.md`. - - Read `llmdoc/startup.md` and the files it lists when available. + - Read `llmdoc/index.md`, `llmdoc/startup.md`, and the MUST docs it lists. - Proactively read relevant `llmdoc/guides/` and `llmdoc/memory/reflections/` before planning edits. - - Inspect the current working tree, staged changes, and any explicit change summary from `$ARGUMENTS`. - - Check whether relevant `.llmdoc-tmp/investigations/` reports exist, but treat them as temporary evidence to validate, not as truth. - -2. Select the update mode. - - Prefer `fast` for immediate post-implementation updates by the same assistant. - - Use `analysis` when the current state needs a fresh evidence map. - - Use `full` when the update needs independent reflection or risk review. - -3. Investigate only when the selected mode needs it. - - In `fast`, prefer targeted checks and the coordinating assistant's fresh task summary. - - In `analysis`, use `investigator` for a single focused scratch report under `.llmdoc-tmp/investigations/`. - - In `full`, use separate investigation and reflection steps. - - Prefer targeted questions over broad repo scans in every mode. - -4. Reflect only when reflection has value. - - In `full`, use `reflector` to write the reflection so independent role review is preserved. - - In `fast` or `analysis`, write a task-specific reflection into `llmdoc/memory/reflections/` only when the task exposed a workflow failure, repeated mistake, missing signal, or durable process lesson. - - Do not force a reflection for routine `fast` updates that only keep stable docs current. - -5. Update stable llmdoc with `recorder`. - - Update only the impacted docs. + - Note any explicit change summary from `$ARGUMENTS`. + +2. Resolve sync state and compute the change set. + - Run the resolution ladder above; parse batch flags; union batch impact sets with the working-tree set; apply the loop-breaker. + - Handle degraded cases (non-git, shallow-below-boundary, first-run, orphaned, diverged, HEAD-behind) per the ladder; never fabricate a watermark advance on a degraded run. + +3. Select the update mode from range size × authorship × risk. Honor the hard floors and the backfill cap. + +4. Investigate only when the selected mode needs it. + - Seed the investigator with the resolved net-diff path list. Persist one scratch report under `.llmdoc-tmp/investigations/` recording the resolved `RANGE_BASE..H` range. + - Prefer targeted questions over broad repo scans. + +5. Reflect only when reflection has value. + - Write a reflection into `llmdoc/memory/reflections/` only when the task exposed a workflow failure, repeated mistake, missing signal, or durable process lesson. + - Do not force a reflection for routine `fast` updates. + +6. Update stable llmdoc with `recorder`. + - Update only the impacted docs, against the batch-tip state. - Remove or correct stable-doc claims that no longer match the current code. - - Promote lessons into `must/`, `guides/`, `architecture/`, or `reference/` only when they are stable and likely to recur. - Split documents aggressively instead of appending to a large file. - - Keep tracked docs compact: they should be smaller than the source they describe or explain design and implementation intent that source search does not expose quickly. - - Reconcile `llmdoc/memory/doc-gaps.md`: close resolved gaps, mark stale gaps, and add only actionable new gaps with closure criteria. + - Reconcile `llmdoc/memory/doc-gaps.md`: close resolved gaps, mark stale gaps, add only actionable new gaps with closure criteria. -6. Run the active-memory archive check. - - After any new reflection is written, count active memory files under `llmdoc/memory/`, excluding `llmdoc/memory/lessons-learned.md`, `llmdoc/memory/doc-gaps.md`, and anything under `llmdoc/memory/archive/`. - - If the count is greater than 5, follow `skills/llmdoc/references/lessons-learned.md`: summarize recurring lessons into `llmdoc/memory/lessons-learned.md`, link each lesson to its source memory file, and move summarized raw memory into `llmdoc/memory/archive/YYYY-MM-DD/`. - - Treat hook reminders as best-effort only; this workflow step is the precise archive checkpoint. +7. Run the active-memory archive check. + - After any new reflection is written, count active memory files under `llmdoc/memory/`, excluding `lessons-learned.md`, `doc-gaps.md`, and anything under `archive/` (`llmdoc/state/` is outside `memory/` and is not counted). + - If the count is greater than 5, follow `skills/llmdoc/references/lessons-learned.md`. -7. Synchronize `llmdoc/index.md`. +8. Synchronize `llmdoc/index.md`. - Ensure new and changed docs are discoverable. - - Keep reflections and decisions listed separately from stable docs. - - Do not index `.llmdoc-tmp/`. + - Do not index `.llmdoc-tmp/`, and do not index `llmdoc/state/sync.md` as knowledge. + +9. Advance the watermark (recorder-owned terminal step). + - Only on a complete, successful update that consumed a committed range. + - Advance `watermark-commit` to the captured `H` (default) or the highest unbroken-prefix tip (partial/batch); rewrite only `watermark-commit`, `watermark-subject`, and `updated-at/by`. + - NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or while a git operation is in progress or HEAD is detached. -8. Report the mode used, any scratch report or reflection path, the archive action taken or skipped, and the stable docs that changed. +10. Report the mode used, resolved range(s)/batches and commit count, old → new watermark (or why it did not move), any scratch report or reflection path, the archive action taken or skipped, and the stable docs that changed. diff --git a/llmdoc/state/sync.md b/llmdoc/state/sync.md new file mode 100644 index 0000000..047636d --- /dev/null +++ b/llmdoc/state/sync.md @@ -0,0 +1,11 @@ +# llmdoc sync state + + +- schema: 1 + +- watermark-commit: 7a8fe470627898d46258d8abf50b160a8e198eb8 + +- watermark-subject: docs: simplify Codex install and add Chinese README link +- updated-at: 2026-07-09T00:00:00Z +- updated-by: /llmdoc:update (dogfood seed) diff --git a/skills/llmdoc-init/SKILL.md b/skills/llmdoc-init/SKILL.md index 37dc945..bf4c77a 100644 --- a/skills/llmdoc-init/SKILL.md +++ b/skills/llmdoc-init/SKILL.md @@ -37,7 +37,9 @@ Then execute this workflow: - `llmdoc/reference/` - `llmdoc/memory/reflections/` - `llmdoc/memory/decisions/` + - `llmdoc/state/sync.md` - `.llmdoc-tmp/investigations/` + - Seed the commit watermark in `llmdoc/state/sync.md` (template in `skills/llmdoc/references/templates.md`): set `watermark-commit` to `$(git rev-parse HEAD)`, so the first `llmdoc-update` has a valid anchor. Skip this in a non-git project. `llmdoc/state/sync.md` is machine-managed state, not knowledge: never index it or add it to `startup.md`/`must/`. 3. Run investigation. - Prefer multiple focused investigators over one broad pass. diff --git a/skills/llmdoc-update/SKILL.md b/skills/llmdoc-update/SKILL.md index 0b6050a..21f9ada 100644 --- a/skills/llmdoc-update/SKILL.md +++ b/skills/llmdoc-update/SKILL.md @@ -1,6 +1,6 @@ --- name: llmdoc-update -description: "Codex-native entry skill for keeping tracked llmdoc docs current with the repository. Use this when you want the /llmdoc:update workflow in Codex." +description: "Codex-native entry skill for keeping tracked llmdoc docs current with the repository using commit-based change detection. Use this when you want the /llmdoc:update workflow in Codex." disable-model-invocation: false allowed-tools: Read, Glob, Grep, Bash, Write, Edit, WebSearch, WebFetch --- @@ -11,59 +11,100 @@ This skill is the Codex-native equivalent of `/llmdoc:update`. The update target is tracked `llmdoc/`, not `.llmdoc-tmp/`. Stable docs should stay consistent with the current repository and remain smaller than the source code or provide architectural explanation that source search does not. +Change detection is **commit-based**: it is anchored on a durable watermark (`llmdoc/state/sync.md`) recording the last source commit already reflected in `llmdoc/`, not on the working tree or the date. Uncommitted changes are an additional input; they never move the watermark. + Use it when: - a task changed project knowledge, architecture understanding, or workflow guidance - a useful mistake or missing-doc lesson should be preserved - you want a command-like Codex entrypoint for updating llmdoc +Read the batch flags from `$ARGUMENTS` / the user message: +`[summary] [--range A..B ...] [--commits SHA,SHA ...] [--since REF] [--from SHA] [--working-tree-only] [--include-default]` + +Invariants, design rationale, and verified git semantics: `llmdoc/architecture/update-orchestration.md`. + Before editing stable docs: -- read `llmdoc/index.md` -- read `llmdoc/startup.md` and the MUST docs it lists +- read `llmdoc/index.md`, `llmdoc/startup.md` and the MUST docs it lists - proactively read relevant `llmdoc/guides/` and `llmdoc/memory/reflections/` -- read `skills/llmdoc/references/lessons-learned.md` for the active-memory archive threshold and archive procedure -- check relevant `.llmdoc-tmp/investigations/` reports only as local temporary evidence, validating their git revision, scope, and gaps before reuse +- read `skills/llmdoc/references/lessons-learned.md` for the active-memory archive threshold +- check relevant `.llmdoc-tmp/investigations/` reports only as local temporary evidence (validate their git revision, resolved range, scope, gaps) - align with the user before non-trivial edits -Then execute this workflow: - -1. Rebuild task context. - - Inspect the current working tree, staged changes, and any explicit task summary. - - Prefer targeted investigation over broad repo scans. - -2. Select the lightest update mode that keeps docs correct. - - `fast`: default for immediate post-implementation updates when the coordinating assistant has fresh context. Use the task summary, diff, targeted checks, and any still-valid scratch reports. - - `analysis`: use when current-state research, stale context, or unclear impact requires one focused evidence pass. Persist the report under `.llmdoc-tmp/investigations/`. - - `full`: use when risk, disputed facts, or process learning justify separate investigation, reflection, and recording roles. - -3. Investigate only as needed by the selected mode. - - In `fast`, do targeted checks instead of mandatory broad investigation. - - In `analysis`, write one focused scratch report under `.llmdoc-tmp/investigations/`. - - In `full`, keep investigation and reflection as separate steps. - -4. Reflect only when reflection has value. - - In `full`, use `reflector` to write the reflection so independent role review is preserved. - - In `fast` or `analysis`, write a task-specific reflection under `llmdoc/memory/reflections/` only when there was a workflow failure, repeated mistake, missing signal, or durable process lesson. - - Do not force a reflection for routine `fast` updates. - -5. Update stable llmdoc docs. - - Update only the impacted docs. - - Remove or correct stable-doc claims that no longer match the current code. - - Promote lessons into `must/`, `guides/`, `architecture/`, or `reference/` only when they are stable and likely to recur. - - Split docs aggressively instead of appending to large mixed files. - - Keep `llmdoc/memory/doc-gaps.md` reconciled by closing resolved gaps and adding only actionable new gaps. - -6. Run the active-memory archive check. - - After any new reflection is written, count active memory files under `llmdoc/memory/`, excluding `llmdoc/memory/lessons-learned.md`, `llmdoc/memory/doc-gaps.md`, and anything under `llmdoc/memory/archive/`. - - If the count is greater than 5, follow `skills/llmdoc/references/lessons-learned.md`: summarize recurring lessons into `llmdoc/memory/lessons-learned.md`, link each lesson to its source memory file, and move summarized raw memory into `llmdoc/memory/archive/YYYY-MM-DD/`. - - Treat hook reminders as best-effort only; this workflow step is the precise archive checkpoint. - -7. Synchronize `llmdoc/index.md`. - - Ensure changed docs are discoverable. - - Keep reflections and decisions listed separately from stable docs. - - Do not index `.llmdoc-tmp/`. - -8. Report the mode used, scratch report or reflection path when present, the archive action taken or skipped, and the stable docs that changed. +## Sync State + +`llmdoc/state/sync.md` is a tracked, machine-managed markdown file holding one load-bearing field, `watermark-commit` (full 40-hex SHA). It is not project knowledge: never index it, never add it to `startup.md`/`must/`, never count it as active memory. Read the watermark with a single anchored line: + +```sh +W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') +``` + +`recorder` is the only writer of `sync.md`, and only advances it as the terminal step of a successful update. + +## Change Detection (commit range) + +Run all git plumbing without `set -e` (capture each exit code) so a `128` degrades instead of aborting. The resolution ladder order is an invariant — never reorder: + +```sh +# 1. Capability probe +[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ] || echo "non-git: watermark inactive" # → legacy working-tree detection, no watermark read/advance +SHALLOW=$(git rev-parse --is-shallow-repository) +H=$(git rev-parse HEAD) # capture once + +# 2. Read watermark (empty/unknown-schema → first-run: base=HEAD, no backfill unless --from/--since, force ≥ analysis, seed at HEAD) +W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') + +# 3. EXISTENCE before reachability (merge-base --is-ancestor exits 128 fatal on a missing object; guard first) +git rev-parse --verify --quiet "$W^{commit}" >/dev/null || echo "watermark object missing" # rev-parse --verify --quiet exits 1 cleanly on a missing/non-commit object (cat-file -e "$W^{commit}" would fatal 128 on the peel) → first-run baseline=HEAD, warn, do not advance; if $SHALLOW=true suggest: git fetch --unshallow + +# 4. Reachability +git merge-base --is-ancestor "$W" "$H" # rc=0 → valid, RANGE_BASE=$W +# rc≠0 → not ancestor; reverse test: +git merge-base --is-ancestor "$H" "$W" # rc=0 → HEAD BEHIND watermark → REFUSE (reverse range would delete docs); working-tree input only +# else → true divergence (rebase/squash): RANGE_BASE=$(git merge-base "$W" "$H"); force ≥ analysis + +# 5. Net change set (rename-aware, NUL-safe) +git diff --name-status -M -C -z "$RANGE_BASE" "$H" +``` + +Batch flags (if any of `--range/--commits/--since/--from` is given, the default `RANGE_BASE..HEAD` is NOT added unless `--include-default`). Each batch resolves to a set of changed **paths**: parse the `--name-status` records — drop the status column, and for a rename `R` take BOTH the old and new path. Union all batch path-sets with the working-tree set, deduplicated (do not `sort -u` raw NUL/status records): + +- `--range A..B` (repeatable): `git diff --name-status -M -C -z A B`. +- `--commits SHA,…`: per commit `git diff --name-status -M -C -z ^ `; **reject merge commits** (parent count > 1 → ask the user to pass `A..B`); **root commit** diffs against the empty tree `git hash-object -t tree /dev/null` (`4b825dc642cb6eb9a060e54bf8d69288fbee4904`). +- `--since REF|date`: for a ref, `..HEAD`; for a date, resolve a boundary commit `B=$(git rev-list -1 --before= HEAD)` and diff `B..HEAD` with the same `git diff --name-status -M -C -z` as every other branch (do NOT `git log --name-only`, which yields a statusless, blank-line-polluted shape that will not union cleanly). +- `--from SHA`: one-off start override for this run only; does not touch `sync.md`. +- `--working-tree-only`: skip the committed range entirely; document only uncommitted changes; never advance the watermark. + +Always fold in, as an ADDITIONAL input tagged `worktree` (never advances the watermark): `git diff --name-only HEAD` ∪ `git diff --name-only --cached` ∪ `git ls-files --others --exclude-standard`. + +Empty range (`W == HEAD`, the commonest steady-state run): if `RANGE_BASE..HEAD` is empty AND there are no working-tree changes AND no batch flags, report `already up to date through ` and exit without touching docs, the index, or the watermark. + +Doc-commit loop-breaker: if `git diff --name-only "$RANGE_BASE".."$H" -- ':(exclude)llmdoc/**'` is empty (range non-empty but doc-only), fast-forward the watermark to HEAD with zero doc work. Always exclude the sync file and `.llmdoc-tmp/` from every diff. + +Content selection: `recorder` reads committed-batch file content at the BATCH TIP (`git show :`), never from HEAD/disk, except for the uncommitted working-tree set (read from disk, which wins for any currently-dirty path). + +## Mode Selection + +The trigger is range size × authorship × risk (not context freshness): + +- `fast`: small range (≤ ~3 commits), self-authored, impacted docs nameable. `--working-tree-only` defaults here. Use the task summary, diff, targeted checks, and any still-valid scratch reports. +- `analysis`: ~4–15 commits, OR any non-self-authored commit, OR multiple clusters, OR a recovered/derived/first-run baseline. One focused evidence pass persisted under `.llmdoc-tmp/investigations/`. +- `full`: > ~15 commits, multi-batch backfill, history-rewrite recovery, disputed facts, or reflection-heavy failures — separate investigation, reflection, and recording roles. + +Hard floors (force ≥ `analysis`): a merge-base-recovered baseline, a derived/first-run baseline, or any non-self-authored commit. Backfill blast-radius cap: a first-run/`--since` range beyond ~20 commits or ~50 files forces `full` and explicit user confirmation. + +## Workflow + +1. Rebuild task context (index, startup, MUST docs, relevant guides + reflections; note any `$ARGUMENTS` summary). +2. Resolve sync state and compute the change set (run the ladder; parse batch flags; union with the working-tree set; apply the loop-breaker; handle degraded cases — non-git, shallow, first-run, orphaned, diverged, HEAD-behind — without fabricating a watermark advance). +3. Select the mode from range size × authorship × risk; honor the hard floors and backfill cap. +4. Investigate only as needed, seeded with the resolved net-diff path list; scratch reports record the resolved `RANGE_BASE..H` range. +5. Reflect only when there is a workflow failure, repeated mistake, missing signal, or durable process lesson. Do not force a reflection for routine `fast` updates. +6. Update stable llmdoc docs against the batch-tip state: update only impacted docs, correct stale claims, split aggressively, reconcile `llmdoc/memory/doc-gaps.md`. +7. Run the active-memory archive check (count files under `llmdoc/memory/` excluding `lessons-learned.md`, `doc-gaps.md`, `archive/`; `llmdoc/state/` is not counted). If > 5, follow `skills/llmdoc/references/lessons-learned.md`. +8. Synchronize `llmdoc/index.md`. Do not index `.llmdoc-tmp/`, and do not index `llmdoc/state/sync.md` as knowledge. +9. Advance the watermark (recorder-owned terminal step): only on a complete, successful update that consumed a committed range; rewrite only `watermark-commit`, `watermark-subject`, `updated-at/by`. NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or while a git operation is in progress or HEAD is detached. +10. Report the mode used, resolved range(s)/batches and commit count, old → new watermark (or why it did not move), scratch/reflection paths, the archive action, and the stable docs that changed. At the end of a non-trivial task, proactively consider whether the user should be prompted to run this workflow. diff --git a/skills/llmdoc/references/templates.md b/skills/llmdoc/references/templates.md index a93a09a..c7a5a05 100644 --- a/skills/llmdoc/references/templates.md +++ b/skills/llmdoc/references/templates.md @@ -47,6 +47,24 @@ Escalate to more docs when: Read related guides and reflections before editing when available. ``` +## `state/sync.md` + +Machine-managed commit watermark. Seed at init with `watermark-commit=$(git rev-parse HEAD)`. Keep the `watermark-commit` line isolated by blank lines (minimizes merge-conflict surface). Never index it, never add it to `startup.md`/`must/`, never count it as active memory. + +```md +# llmdoc sync state + + +- schema: 1 + +- watermark-commit: <40-hex SHA of HEAD at init> + +- watermark-subject: +- updated-at: +- updated-by: /llmdoc:init (seed) +``` + ## `overview/project-overview.md` ```md diff --git a/skills/llmdoc/references/update-and-memory.md b/skills/llmdoc/references/update-and-memory.md index 59e52e9..52d6109 100644 --- a/skills/llmdoc/references/update-and-memory.md +++ b/skills/llmdoc/references/update-and-memory.md @@ -6,15 +6,32 @@ When project knowledge changes, use `/llmdoc:update`. Tracked `llmdoc/` docs should describe the current repository. They should be smaller than the source code they describe, or add architectural intent, boundaries, and implementation reasoning that raw source search does not provide quickly. +## Commit watermark + +Change detection is commit-based, anchored on a durable, git-tracked watermark: `llmdoc/state/sync.md`, whose one load-bearing field `watermark-commit` is the last source commit already reflected in `llmdoc/`. + +- Default change set = the NET diff `watermark..HEAD` (`git diff --name-status -M -C -z`): the current state of every path touched since the watermark. +- Multiple batches can be consumed in one run (`--range` repeatable, `--commits`, `--since`, `--from`, `--working-tree-only`); all impact sets are unioned and processed in a single pass. +- Uncommitted working-tree/staged/untracked changes are always an ADDITIONAL input, but they never advance the watermark. +- `recorder` advances the watermark only as the terminal step of a complete, successful update that consumed a committed range. +- `llmdoc/state/sync.md` is machine-managed state, not project knowledge: never index it, never put it in `startup.md`/`must/`, never count it as active memory. + +The resolution ladder, batch flags, degraded-mode handling, and exact git commands are the contract in `commands/update.md` and `skills/llmdoc-update/SKILL.md`. Invariants, design rationale, and verified git semantics: `llmdoc/architecture/update-orchestration.md`. + +### Net diff vs per-commit + +- `recorder` documents CURRENT state, read at the batch tip (`git show :`, never HEAD/disk except for the uncommitted working-tree set). The net diff decides which docs change and what they now say. Never document an intermediate state absent at the tip. +- `reflector`/`investigator` may consult per-commit history (`git log`, `git show`, `--first-parent`) only to explain WHY a change happened, and only when a reflection is being written. Per-commit history never decides which docs change. + ## Update modes -Choose the lightest mode that keeps the docs correct: +Choose the lightest mode that keeps the docs correct. The trigger is range size × authorship × risk, not context freshness: -- `fast`: default after the coordinating assistant has just implemented the change. Use the fresh task summary, diff, targeted checks, and any still-valid local scratch reports. -- `analysis`: use one focused evidence pass when current-state research, stale context, or unclear impact makes the update uncertain. -- `full`: use separate investigation, reflection, and stable-doc maintenance when high risk, disputed facts, or process learning justify the extra independence. +- `fast`: small range (≤ ~3 commits), self-authored, impacted docs nameable. Use the task summary, diff, targeted checks, and any still-valid local scratch reports. +- `analysis`: ~4–15 commits, OR any non-self-authored commit, OR multiple clusters, OR a recovered/derived/first-run baseline. One focused evidence pass. +- `full`: > ~15 commits, multi-batch backfill, history-rewrite recovery, disputed facts, or process learning — separate investigation, reflection, and stable-doc maintenance. -Escalate from `fast` only when the assistant cannot confidently name the impacted docs and facts. +Hard floors force ≥ `analysis`: a merge-base-recovered baseline, a derived/first-run baseline, or any non-self-authored commit. A first-run/`--since` range beyond ~20 commits or ~50 files forces `full` and explicit user confirmation. Freshness of the assistant's context is an input to mode selection, not the source of change detection. The update order is: @@ -89,6 +106,7 @@ Read relevant reflections: - `reflector` writes `llmdoc/memory/reflections/` - `recorder` maintains `llmdoc/memory/decisions/` - `recorder` maintains `llmdoc/memory/doc-gaps.md` +- `recorder` is the sole writer of `llmdoc/state/sync.md` (the commit watermark), advancing it only as the terminal step of a successful update; it is machine-managed state, not memory or knowledge Use `decisions/` for durable design or process decisions. Use `memory/doc-gaps.md` to track missing or weak documentation that should be improved later. From d6ff1bef50d778a234b1462c29a1949858493775 Mon Sep 17 00:00:00 2001 From: wh1teAlter <2369332823@qq.com> Date: Thu, 9 Jul 2026 18:23:37 +0800 Subject: [PATCH 2/5] docs: sync llmdoc knowledge to a9ad7bf via /llmdoc:update Dogfood of the new commit-watermark update capability: an independent cold agent ran /llmdoc:update following commands/update.md against range 7a8fe47..a9ad7bf, mapped the committed source change to the impacted llmdoc/ docs, updated them, and advanced the watermark. - architecture/update-orchestration.md: commit-watermark model, batch flags, batch-tip content rule, range x authorship x risk mode triggers, watermark / sole-writer / worker invariants - reference/repo-surfaces.md: register llmdoc/state/sync.md as state - index.md: mark state/ as non-knowledge, keep sync.md un-indexed - guides/updating-update-workflow.md: new touch-points + watermark checks - memory/doc-gaps.md: close resolved .codex tomls mirror gap, add a watermark-resolution-ladder test gap - llmdoc/state/sync.md: watermark advanced 7a8fe47 -> a9ad7bf Co-Authored-By: Claude Opus 4.8 (1M context) --- llmdoc/architecture/update-orchestration.md | 24 +++++++++++++---- llmdoc/guides/updating-update-workflow.md | 30 +++++++++++++-------- llmdoc/index.md | 5 ++-- llmdoc/memory/doc-gaps.md | 5 +++- llmdoc/reference/repo-surfaces.md | 4 ++- llmdoc/state/sync.md | 8 +++--- 6 files changed, 52 insertions(+), 24 deletions(-) diff --git a/llmdoc/architecture/update-orchestration.md b/llmdoc/architecture/update-orchestration.md index 914284e..68fae0a 100644 --- a/llmdoc/architecture/update-orchestration.md +++ b/llmdoc/architecture/update-orchestration.md @@ -6,21 +6,35 @@ ## Knowledge Layers - `llmdoc/`: tracked project knowledge. It should describe current architecture, implementation intent, boundaries, stable contracts, and recurring workflow rules. +- `llmdoc/state/sync.md`: tracked, machine-managed commit watermark. It is not project knowledge — never index it, never add it to `startup.md`/`must/`, never count it as active memory. - `.llmdoc-tmp/`: local temporary context cache. It may hold investigator reports or hook logs, but it is ignored by git, not indexed, and may be deleted. - Source code and git state: the final authority for volatile facts, counts, and implementation details. +## Commit-Based Change Detection +- Change detection is anchored on a durable, git-tracked watermark (`llmdoc/state/sync.md`, field `watermark-commit`) recording the last source commit already reflected in `llmdoc/` — not on the working tree or the date. +- The default change set is the NET diff `watermark..HEAD` (`git diff --name-status -M -C -z`): the current state of every path touched since the watermark. +- Uncommitted working-tree, staged, and untracked changes are always an ADDITIONAL input; they never move the watermark. +- Batch flags let one run consume multiple ranges: `--range A..B` (repeatable), `--commits`, `--since`, `--from`, `--working-tree-only`, `--include-default`. All resolved impact sets are unioned and processed in a single pass. +- The exact resolution ladder (capability probe → watermark read → existence check → reachability → net diff), batch-flag parsing, the doc-only loop-breaker, and degraded-mode handling (non-git, shallow, first-run, orphaned watermark, divergence, HEAD-behind) are the contract in `commands/update.md` and `skills/llmdoc-update/SKILL.md`. This doc holds the invariants; the command docs hold the verified git semantics. +- `recorder` reads committed-batch content at the BATCH TIP (`git show :`), never from HEAD or disk, except the uncommitted working-tree set (read from disk, which wins for any dirty path). Never document an intermediate state absent at the tip. + ## Update Modes -- `fast`: default for immediate post-task updates by the same coordinating assistant. Use the task summary, diff, targeted checks, and any still-valid scratch reports. -- `analysis`: use when the current state needs fresh evidence, the implementation context is stale, or the impacted docs are unclear. Run one focused investigation and persist the scratch report under `.llmdoc-tmp/investigations/`. -- `full`: use when risk, disputed facts, or process learning justify separate investigator, reflector, and recorder roles. +- The mode trigger is range size × authorship × risk, not context freshness. +- `fast`: small range (≤ ~3 commits), self-authored, impacted docs nameable. `--working-tree-only` defaults here. Use the task summary, diff, targeted checks, and any still-valid scratch reports. +- `analysis`: ~4–15 commits, OR any non-self-authored commit, OR multiple clusters, OR a recovered/derived/first-run baseline. Run one focused investigation and persist the scratch report under `.llmdoc-tmp/investigations/`. +- `full`: > ~15 commits, multi-batch backfill, history-rewrite recovery, disputed facts, or process learning — separate investigator, reflector, and recorder roles. +- Hard floors force ≥ `analysis`: a merge-base-recovered baseline, a derived/first-run baseline, or any non-self-authored commit. A first-run/`--since` range beyond ~20 commits or ~50 files forces `full` and explicit user confirmation. ## Invariants - Stable docs must not describe behavior that no longer exists in the current repository. - Stable docs should be smaller than the source they describe or add architectural explanation that source search does not provide quickly. -- Investigator reports are reusable evidence, not stable memory or source of truth. +- Investigator reports are reusable evidence, not stable memory or source of truth. A file report is reusable only when the current resolved range is a subset of the report's recorded range. - Reflections are written only when there is a workflow failure, repeated mistake, missing signal, or durable process lesson. - `recorder` reconciles `llmdoc/memory/doc-gaps.md` during non-trivial updates. -- `llmdoc/index.md` never indexes `.llmdoc-tmp/`. +- `recorder` is the sole writer of `llmdoc/state/sync.md` and `llmdoc/index.md` for a run. It advances `watermark-commit` only as the terminal step of a complete, successful update that consumed a committed range, rewriting only `watermark-commit`, `watermark-subject`, and `updated-at/by`. +- Never advance the watermark on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or while a git operation is in progress or HEAD is detached. +- `worker` never edits `llmdoc/index.md` or `llmdoc/state/sync.md`. +- `llmdoc/index.md` never indexes `.llmdoc-tmp/`, and never indexes `llmdoc/state/sync.md` as knowledge. ## Routing - Use `must/` for short recurring rules that prevent common mistakes. diff --git a/llmdoc/guides/updating-update-workflow.md b/llmdoc/guides/updating-update-workflow.md index 7b229d9..64e66c5 100644 --- a/llmdoc/guides/updating-update-workflow.md +++ b/llmdoc/guides/updating-update-workflow.md @@ -5,23 +5,31 @@ - Inspect both Claude Code command docs and Codex helper skills before changing behavior. ## Main Steps -1. Update `commands/update.md` when the Claude Code command contract changes. -2. Update `skills/llmdoc-update/SKILL.md` so Codex follows the same contract. -3. Update `agents/investigator.md` if scratch report behavior, metadata, or reuse rules change. -4. Update `agents/recorder.md` if stable-doc consistency, doc-gaps reconciliation, or routing rules change. -5. Update `.codex/agents/*.toml` when project-scoped Codex subagent behavior changes. -6. Update `README.md` and `README.zh-CN.md` so the public workflow summary matches the actual contract. -7. Update plugin manifest versions together for Claude Code and Codex when publishing a behavior change. +1. Update `commands/update.md` when the Claude Code command contract changes. This is the source of truth for the resolution ladder, batch flags, mode selection, and the terminal watermark advance. +2. Update `skills/llmdoc-update/SKILL.md` so Codex follows the same contract, including the identical Change Detection ladder. +3. Update `agents/investigator.md` if scratch report behavior, metadata (including `Range:`/`Watermark:`), net-diff input, or reuse rules change. +4. Update `agents/recorder.md` if stable-doc consistency, doc-gaps reconciliation, routing rules, or the commit-watermark ownership rules change. +5. Update `agents/reflector.md` and `agents/worker.md` when per-commit-history usage or the "recorder is the sole writer of `sync.md`/`index.md`" rule changes. +6. Update `commands/init.md` and `skills/llmdoc-init/SKILL.md` when the `llmdoc/state/sync.md` skeleton path or watermark seeding step changes. +7. Update `skills/llmdoc/references/update-and-memory.md` and `skills/llmdoc/references/templates.md` (the `state/sync.md` template) when the watermark model or its template changes. +8. Update `.codex/agents/*.toml` when project-scoped Codex subagent behavior changes (recorder watermark ownership, worker restrictions, reflector per-commit history, investigator net-diff input). +9. Update `README.md` and `README.zh-CN.md` so the public workflow summary matches the actual contract, including commit-watermark change detection. +10. Update plugin manifest versions together for Claude Code and Codex when publishing a behavior change. ## Verification -- `/llmdoc:update` describes `fast`, `analysis`, and `full` modes. -- Codex `llmdoc-update` describes the same modes. -- Investigator reports are described as temporary context cache, not stable docs. -- Recorder rules require stable docs to match current code and reconcile `doc-gaps.md`. +- `/llmdoc:update` describes commit-watermark change detection, the resolution ladder, batch flags, and `fast`/`analysis`/`full` modes keyed on range size × authorship × risk. +- Codex `llmdoc-update` describes the same detection model and modes. +- `llmdoc/state/sync.md` exists, is seeded at init, and is never indexed or added to `startup.md`/`must/`. +- Only `recorder` writes `sync.md`/`index.md`; `worker` never edits them. +- Investigator reports are described as temporary context cache, not stable docs, and record the resolved range/watermark. +- Recorder rules require stable docs to match current code, reconcile `doc-gaps.md`, and advance the watermark only as the terminal step of a successful committed-range update. - Public README summaries match both Claude Code and Codex behavior. ## Common Failure Points - Changing only the Claude Code command while leaving Codex helper skills stale. +- Advancing the watermark on a `--working-tree-only`, failed, partial, or HEAD-behind run. +- Documenting an intermediate state instead of the batch-tip state. +- Indexing `llmdoc/state/sync.md` or treating it as active memory. - Treating `.llmdoc-tmp/` as durable project memory because reports persist locally. - Requiring reflection for routine updates with no process lesson. - Adding volatile counts or raw inventory to stable docs instead of checking them on demand. diff --git a/llmdoc/index.md b/llmdoc/index.md index 04d4fc4..72b130b 100644 --- a/llmdoc/index.md +++ b/llmdoc/index.md @@ -16,7 +16,7 @@ - `llmdoc/startup.md`: ordered startup reading list - `llmdoc/overview/project-overview.md`: what this repository is and what belongs here - `llmdoc/architecture/init-investigation-orchestration.md`: how `/llmdoc:init` investigation is expected to fan out and converge -- `llmdoc/architecture/update-orchestration.md`: how `/llmdoc:update` chooses fast, analysis, or full update paths +- `llmdoc/architecture/update-orchestration.md`: how `/llmdoc:update` uses the commit watermark to detect changes and chooses fast, analysis, or full update paths - `llmdoc/guides/updating-init-investigation-depth.md`: how to change init depth safely when the workflow is too shallow or too broad - `llmdoc/guides/updating-update-workflow.md`: how to update the update workflow across Claude Code, Codex, agents, and docs - `llmdoc/reference/repo-surfaces.md`: stable map of commands, agents, plugin files, and Codex config surfaces @@ -25,8 +25,9 @@ - Read `startup.md` first on normal work. - Read `architecture/init-investigation-orchestration.md` before changing `/llmdoc:init`, agent fan-out strategy, or Codex agent limits. - Read `guides/updating-init-investigation-depth.md` before tuning investigation breadth or follow-up passes. -- Read `architecture/update-orchestration.md` and `guides/updating-update-workflow.md` before changing `/llmdoc:update`, `llmdoc-update`, investigator scratch behavior, or recorder update rules. +- Read `architecture/update-orchestration.md` and `guides/updating-update-workflow.md` before changing `/llmdoc:update`, `llmdoc-update`, the commit-watermark change detection, investigator scratch behavior, or recorder update rules. - Read `reference/repo-surfaces.md` before moving or renaming public repo surfaces such as commands, agents, plugin files, or `.codex/config.toml`. +- `llmdoc/state/sync.md` is machine-managed commit-watermark state, not knowledge: `recorder` is its sole writer; never index it here or in `startup.md`/`must/`. ## Memory - `llmdoc/memory/reflections/`: task-specific lessons and mistakes diff --git a/llmdoc/memory/doc-gaps.md b/llmdoc/memory/doc-gaps.md index 7075d91..c5a9336 100644 --- a/llmdoc/memory/doc-gaps.md +++ b/llmdoc/memory/doc-gaps.md @@ -3,4 +3,7 @@ ## Open Gaps - There is still no executable test or fixture that validates `/llmdoc:init` fan-out behavior against the documented contract; current confidence comes from prompt and config review. - If runtime behavior diverges again, add a guide or fixture that checks command docs, README summaries, and `.codex/config.toml` together. -- `.codex/agents/llmdoc-investigator.toml` and `.codex/agents/llmdoc-recorder.toml` still need to mirror the new update-mode and temporary-cache rules once the hidden `.codex/` directory is writable in the working environment. +- There is no executable test or fixture exercising the commit-watermark resolution ladder and its degraded modes (non-git, shallow, first-run, orphaned/rebased watermark, HEAD-behind, doc-only loop-breaker). Confidence comes from the `commands/update.md` contract and `skills/llmdoc-update/SKILL.md` mirroring it by review only. Closure: add a fixture or checklist that drives each ladder branch against a scratch repo and asserts the resulting watermark advance (or refusal). + +## Resolved / Stale Gaps +- RESOLVED (commit `a9ad7bf`): `.codex/agents/*.toml` now mirror the update-mode, commit-watermark, net-diff, and temporary-cache rules — recorder carries watermark ownership, investigator carries net-diff input and `Range:`/`Watermark:` metadata, reflector carries per-commit-history usage, worker is barred from editing `sync.md`/`index.md`. diff --git a/llmdoc/reference/repo-surfaces.md b/llmdoc/reference/repo-surfaces.md index f66958d..5a37300 100644 --- a/llmdoc/reference/repo-surfaces.md +++ b/llmdoc/reference/repo-surfaces.md @@ -13,11 +13,13 @@ - `.codex/agents/*.toml`: Project-scoped Codex custom agents. - `.codex-plugin/plugin.json` and `.claude-plugin/plugin.json`: Plugin metadata for Codex and Claude Code. - `README.md` and `README.zh-CN.md`: Public summaries that should reflect actual workflow behavior, not aspirational behavior. +- `llmdoc/state/sync.md`: Tracked, machine-managed commit watermark (`watermark-commit`) recording the last source commit reflected in `llmdoc/`. It is state, not knowledge: `recorder` is its sole writer, and it is never indexed or added to `startup.md`/`must/`. Seeded at init; advanced only as the terminal step of a successful `/llmdoc:update`. ## Sources of Truth - `README.md` (`Public Surface`): English user-facing contract. - `README.zh-CN.md` (`公开接口`): Chinese user-facing contract. - `commands/init.md` (`/llmdoc:init`): Init workflow source of truth. - `commands/update.md` (`/llmdoc:update`): Update workflow source of truth. -- `llmdoc/architecture/update-orchestration.md` (`/llmdoc:update` design): Update mode and knowledge-layer source of truth. +- `llmdoc/architecture/update-orchestration.md` (`/llmdoc:update` design): Update mode, commit-watermark model, and knowledge-layer source of truth. +- `llmdoc/state/sync.md` (`watermark-commit`): Machine-managed change-detection anchor; not a knowledge source of truth. - `.codex/config.toml` (`[agents]`): Codex runtime limit source of truth. diff --git a/llmdoc/state/sync.md b/llmdoc/state/sync.md index 047636d..08d74fe 100644 --- a/llmdoc/state/sync.md +++ b/llmdoc/state/sync.md @@ -4,8 +4,8 @@ - schema: 1 -- watermark-commit: 7a8fe470627898d46258d8abf50b160a8e198eb8 +- watermark-commit: a9ad7bf877ba12b48e666fb3bc19638cfcf5680f -- watermark-subject: docs: simplify Codex install and add Chinese README link -- updated-at: 2026-07-09T00:00:00Z -- updated-by: /llmdoc:update (dogfood seed) +- watermark-subject: feat: commit-watermark based /llmdoc:update (contract, agents, state) +- updated-at: 2026-07-09T09:46:29Z +- updated-by: /llmdoc:update From 80752ead274fa80eb0ba9673de59ed59dcdbbaaa Mon Sep 17 00:00:00 2001 From: wh1teAlter <2369332823@qq.com> Date: Thu, 9 Jul 2026 18:28:36 +0800 Subject: [PATCH 3/5] fix: harden /llmdoc:update contract (found by cold-agent dogfood) A cold agent executing commands/update.md against a real commit range surfaced five contract defects; fixed here in both the Claude command and the Codex skill: - non-git guard was a fall-through (`|| { echo; }` without exit), so a non-git dir still ran later git commands and emitted fatal 128s. Now an explicit early-out gates the whole ladder. - "self-authored" (the hard floor that forces >= analysis) had no operational test. Defined it: git author email vs git config user.email; unknown identity => treat as non-self-authored (conservative). - consolidated the "safe to advance the watermark" preconditions (HEAD attached, no MERGE_HEAD/rebase/cherry-pick/revert in progress) into a named gate at the advance step. - pinned the watermark advance field values (watermark-subject via git log -1 --format=%s, updated-at ISO-8601 UTC, updated-by /llmdoc:update). - cautioned against reformatting the `- watermark-commit: ` anchor line. Co-Authored-By: Claude Opus 4.8 (1M context) --- commands/update.md | 19 ++++++++++++++----- skills/llmdoc-update/SKILL.md | 11 ++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/commands/update.md b/commands/update.md index 8459ef4..d4a5ccd 100644 --- a/commands/update.md +++ b/commands/update.md @@ -36,8 +36,11 @@ W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/nu Run all git plumbing without `set -e` (capture each exit code) so a `128` degrades instead of aborting. The resolution ladder order is an invariant — never reorder: ```sh -# 1. Capability probe -[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ] || { echo "non-git: watermark inactive"; } # → legacy working-tree detection, no watermark read/advance +# 1. Capability probe (gates the whole ladder — steps 2-5 assume a git work tree) +[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ] || { + echo "non-git: watermark inactive" # STOP HERE: use legacy working-tree + staged + summary detection; + return 0 2>/dev/null || exit 0 # do NOT run steps 2-5, do NOT read or advance the watermark +} SHALLOW=$(git rev-parse --is-shallow-repository) H=$(git rev-parse HEAD) # capture once @@ -83,6 +86,8 @@ Choose the lightest mode that can keep the docs correct. The trigger is range si Hard floors (always force ≥ `analysis`): a merge-base-recovered baseline, a derived/first-run baseline, or any non-self-authored commit. Backfill blast-radius cap: a first-run/`--since` range beyond ~20 commits or ~50 files forces `full` and explicit user confirmation. Thresholds are tunable defaults; report the chosen mode and the signals that triggered it. +Self-authored test: a commit is self-authored when its author email (`git log -1 --format=%ae `) equals the current `git config user.email`. If the identity cannot be determined (e.g., a subagent with no git identity, or `user.email` unset), treat commits as non-self-authored — the conservative side that forces ≥ `analysis`. + ## `.llmdoc-tmp/` `.llmdoc-tmp/` is a local temporary context cache, not stable project memory. @@ -128,8 +133,12 @@ Hard floors (always force ≥ `analysis`): a merge-base-recovered baseline, a de - Do not index `.llmdoc-tmp/`, and do not index `llmdoc/state/sync.md` as knowledge. 9. Advance the watermark (recorder-owned terminal step). - - Only on a complete, successful update that consumed a committed range. - - Advance `watermark-commit` to the captured `H` (default) or the highest unbroken-prefix tip (partial/batch); rewrite only `watermark-commit`, `watermark-subject`, and `updated-at/by`. - - NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or while a git operation is in progress or HEAD is detached. + - Safe-to-advance gate — ALL must hold: the update completed successfully and consumed a committed range; HEAD is attached (`git symbolic-ref -q HEAD` succeeds); and no git operation is in progress (none of `git rev-parse --git-path MERGE_HEAD`, `CHERRY_PICK_HEAD`, `REVERT_HEAD`, `rebase-merge`, `rebase-apply` exists). + - If the gate holds, advance `watermark-commit` to the captured `H` (default) or the highest unbroken-prefix tip (partial/batch). Rewrite ONLY these fields, keeping the exact `- watermark-commit: ` line prefix (the reader anchors on it — do not reformat it): + - `watermark-commit`: the new full 40-hex SHA + - `watermark-subject`: `git log -1 --format=%s ` + - `updated-at`: ISO-8601 UTC, e.g. `date -u +%Y-%m-%dT%H:%M:%SZ` + - `updated-by`: `/llmdoc:update` + - NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or when the safe-to-advance gate fails. 10. Report the mode used, resolved range(s)/batches and commit count, old → new watermark (or why it did not move), any scratch report or reflection path, the archive action taken or skipped, and the stable docs that changed. diff --git a/skills/llmdoc-update/SKILL.md b/skills/llmdoc-update/SKILL.md index 21f9ada..27c57c1 100644 --- a/skills/llmdoc-update/SKILL.md +++ b/skills/llmdoc-update/SKILL.md @@ -47,8 +47,11 @@ W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/nu Run all git plumbing without `set -e` (capture each exit code) so a `128` degrades instead of aborting. The resolution ladder order is an invariant — never reorder: ```sh -# 1. Capability probe -[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ] || echo "non-git: watermark inactive" # → legacy working-tree detection, no watermark read/advance +# 1. Capability probe (gates the whole ladder — steps 2-5 assume a git work tree) +[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ] || { + echo "non-git: watermark inactive" # STOP HERE: use legacy working-tree + staged + summary detection; + return 0 2>/dev/null || exit 0 # do NOT run steps 2-5, do NOT read or advance the watermark +} SHALLOW=$(git rev-parse --is-shallow-repository) H=$(git rev-parse HEAD) # capture once @@ -94,6 +97,8 @@ The trigger is range size × authorship × risk (not context freshness): Hard floors (force ≥ `analysis`): a merge-base-recovered baseline, a derived/first-run baseline, or any non-self-authored commit. Backfill blast-radius cap: a first-run/`--since` range beyond ~20 commits or ~50 files forces `full` and explicit user confirmation. +Self-authored test: a commit is self-authored when its author email (`git log -1 --format=%ae `) equals the current `git config user.email`. If the identity cannot be determined (e.g., a subagent with no git identity, or `user.email` unset), treat commits as non-self-authored — the conservative side that forces ≥ `analysis`. + ## Workflow 1. Rebuild task context (index, startup, MUST docs, relevant guides + reflections; note any `$ARGUMENTS` summary). @@ -104,7 +109,7 @@ Hard floors (force ≥ `analysis`): a merge-base-recovered baseline, a derived/f 6. Update stable llmdoc docs against the batch-tip state: update only impacted docs, correct stale claims, split aggressively, reconcile `llmdoc/memory/doc-gaps.md`. 7. Run the active-memory archive check (count files under `llmdoc/memory/` excluding `lessons-learned.md`, `doc-gaps.md`, `archive/`; `llmdoc/state/` is not counted). If > 5, follow `skills/llmdoc/references/lessons-learned.md`. 8. Synchronize `llmdoc/index.md`. Do not index `.llmdoc-tmp/`, and do not index `llmdoc/state/sync.md` as knowledge. -9. Advance the watermark (recorder-owned terminal step): only on a complete, successful update that consumed a committed range; rewrite only `watermark-commit`, `watermark-subject`, `updated-at/by`. NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or while a git operation is in progress or HEAD is detached. +9. Advance the watermark (recorder-owned terminal step). Safe-to-advance gate — ALL must hold: the update completed successfully and consumed a committed range; HEAD is attached (`git symbolic-ref -q HEAD` succeeds); and no git operation is in progress (none of `git rev-parse --git-path MERGE_HEAD`, `CHERRY_PICK_HEAD`, `REVERT_HEAD`, `rebase-merge`, `rebase-apply` exists). If the gate holds, advance `watermark-commit` to the captured `H` (or the highest unbroken-prefix tip). Rewrite ONLY these fields, keeping the exact `- watermark-commit: ` line prefix (the reader anchors on it — do not reformat): `watermark-commit` (new 40-hex), `watermark-subject` (`git log -1 --format=%s `), `updated-at` (ISO-8601 UTC, `date -u +%Y-%m-%dT%H:%M:%SZ`), `updated-by` (`/llmdoc:update`). NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or when the safe-to-advance gate fails. 10. Report the mode used, resolved range(s)/batches and commit count, old → new watermark (or why it did not move), scratch/reflection paths, the archive action, and the stable docs that changed. At the end of a non-trivial task, proactively consider whether the user should be prompted to run this workflow. From 6e7e5214e7817505f9f6e966887485b1c7dffed7 Mon Sep 17 00:00:00 2001 From: wh1teAlter <2369332823@qq.com> Date: Thu, 9 Jul 2026 18:35:00 +0800 Subject: [PATCH 4/5] chore: advance llmdoc watermark to 80752ea (2nd dogfood cycle) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second /llmdoc:update cycle over range a9ad7bf..80752ea (2 commits: the C2 doc-only sync + the C3 contract hardening). Validations: - loop-breaker correctly classified the range as a REAL update, not a doc-only fast-forward (excluding llmdoc/ leaves commands/update.md + the skill). - recorder judged C3 to be contract-internal procedure already covered by the update-orchestration.md invariants, so no llmdoc doc change was warranted — the run is an advance-only cycle. - watermark advanced a9ad7bf -> 80752ea via the hardened step-9 gate (HEAD attached, no git op in progress). Co-Authored-By: Claude Opus 4.8 (1M context) --- llmdoc/state/sync.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llmdoc/state/sync.md b/llmdoc/state/sync.md index 08d74fe..9d70a2d 100644 --- a/llmdoc/state/sync.md +++ b/llmdoc/state/sync.md @@ -4,8 +4,8 @@ - schema: 1 -- watermark-commit: a9ad7bf877ba12b48e666fb3bc19638cfcf5680f +- watermark-commit: 80752ead274fa80eb0ba9673de59ed59dcdbbaaa -- watermark-subject: feat: commit-watermark based /llmdoc:update (contract, agents, state) -- updated-at: 2026-07-09T09:46:29Z +- watermark-subject: fix: harden /llmdoc:update contract (found by cold-agent dogfood) +- updated-at: 2026-07-09T10:33:17Z - updated-by: /llmdoc:update From 2b961fe73c95c4aef2f4203a1d889f13e0e64899 Mon Sep 17 00:00:00 2001 From: wh1teAlter <2369332823@qq.com> Date: Thu, 9 Jul 2026 19:27:01 +0800 Subject: [PATCH 5/5] fix: address Gemini cross-review on the update contract (PR #30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies all 13 inline review comments (7 issues x dual surface + template), each empirically verified against real git behavior: - existence check now gates reachability via explicit if/elif/else — a missing/empty watermark no longer falls through to `merge-base` (which fatals 128 on a missing object). Same fall-through class as the non-git fix. - `-z --name-status` parsing: rename/copy records are STATUS\0OLD\0NEW\0 (3 fields); read two path tokens for R/C or the parser desyncs. - `git rev-parse --git-path` always prints a path and exits 0 — the in-progress-op check must test the resolved path with [ -f ]/[ -d ]. - `--since `: fall back to the empty tree when the boundary commit B is empty (no commit predates the date), else `..HEAD` is fatal. - drop the redundant `git diff --name-only --cached` (git diff HEAD already covers staged changes). - generalize "40-hex SHA" to "full commit SHA" (SHA-256 repos). - tighten the already-synced terminal to the valid-watermark == HEAD case so the forced-RANGE_BASE=HEAD degraded branches don't misfire it. Co-Authored-By: Claude Opus 4.8 (1M context) --- commands/update.md | 48 ++++++++++++++++----------- skills/llmdoc-update/SKILL.md | 44 ++++++++++++++---------- skills/llmdoc/references/templates.md | 2 +- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/commands/update.md b/commands/update.md index d4a5ccd..6782d7c 100644 --- a/commands/update.md +++ b/commands/update.md @@ -23,7 +23,7 @@ Invariants, design rationale, and verified git semantics: `llmdoc/architecture/u ## Sync State -`llmdoc/state/sync.md` is a tracked, machine-managed markdown file holding one load-bearing field, `watermark-commit` (full 40-hex SHA). It is not project knowledge: never index it, never add it to `startup.md`/`must/`, never count it as active memory. Read the watermark with a single anchored line: +`llmdoc/state/sync.md` is a tracked, machine-managed markdown file holding one load-bearing field, `watermark-commit` (the full commit SHA, whatever hash the repo uses — SHA-1 or SHA-256). It is not project knowledge: never index it, never add it to `startup.md`/`must/`, never count it as active memory. Read the watermark with a single anchored line: ```sh W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') @@ -44,33 +44,43 @@ Run all git plumbing without `set -e` (capture each exit code) so a `128` degrad SHALLOW=$(git rev-parse --is-shallow-repository) H=$(git rev-parse HEAD) # capture once -# 2. Read watermark (empty/unknown-schema → first-run: base=HEAD, no backfill unless --from/--since, force ≥ analysis, seed at HEAD) +# 2. Read watermark W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') -# 3. EXISTENCE before reachability (merge-base --is-ancestor exits 128 fatal on a missing object; guard first) -git rev-parse --verify --quiet "$W^{commit}" >/dev/null || { echo "watermark object missing"; } # rev-parse --verify --quiet exits 1 cleanly on a missing/non-commit object (cat-file -e "$W^{commit}" would fatal 128 on the peel) → first-run baseline=HEAD, warn, do not advance; if $SHALLOW=true suggest: git fetch --unshallow - -# 4. Reachability -git merge-base --is-ancestor "$W" "$H" # rc=0 → valid, RANGE_BASE=$W -# rc≠0 → not ancestor; reverse test: -git merge-base --is-ancestor "$H" "$W" # rc=0 → HEAD BEHIND watermark → REFUSE (reverse range would delete docs); do not advance backward; working-tree input only -# else → true divergence (rebase/squash): RANGE_BASE=$(git merge-base "$W" "$H"); force ≥ analysis - -# 5. Net change set (rename-aware, NUL-safe) -git diff --name-status -M -C -z "$RANGE_BASE" "$H" # R old\tnew → old path cleans stale refs, new path reflects current state +# 3-4. Resolve RANGE_BASE with EXPLICIT branching. Existence gates reachability: never run merge-base +# on an empty/missing $W — it would fatal-128, not skip. Each branch is terminal. +if [ -z "$W" ]; then + echo "watermark empty/missing → first-run: base=HEAD (no backfill unless --from/--since), force ≥ analysis, seed at HEAD on success" + RANGE_BASE=$H +elif ! git rev-parse --verify --quiet "$W^{commit}" >/dev/null; then + echo "watermark object missing (GC'd / below shallow boundary) → first-run baseline=HEAD, warn, do NOT advance" + # if $SHALLOW = true, suggest: git fetch --unshallow. (rev-parse --verify --quiet exits 1 cleanly; cat-file -e "$W^{commit}" would fatal 128 on the peel.) + RANGE_BASE=$H +elif git merge-base --is-ancestor "$W" "$H"; then + RANGE_BASE=$W # watermark valid +elif git merge-base --is-ancestor "$H" "$W"; then + echo "HEAD BEHIND watermark → REFUSE (reverse W..HEAD would list future files as deletions and strip docs); working-tree input only, do NOT advance" + RANGE_BASE=$H +else + RANGE_BASE=$(git merge-base "$W" "$H") # true divergence (rebase/squash-merge); force ≥ analysis +fi + +# 5. Net change set (rename-aware, NUL-safe). In -z mode a rename/copy record is STATUS\0OLD\0NEW\0 +# (3 fields; STATUS is R/C); a normal record is STATUS\0PATH\0. +git diff --name-status -M -C -z "$RANGE_BASE" "$H" ``` -Batch flags (if any of `--range/--commits/--since/--from` is given, the default `RANGE_BASE..HEAD` is NOT added unless `--include-default`). Each batch resolves to a set of changed **paths**: parse the `--name-status` records — drop the status column, and for a rename `R` take BOTH the old and new path. Union all batch path-sets with the working-tree set, deduplicated (do not `sort -u` raw NUL/status records): +Batch flags (if any of `--range/--commits/--since/--from` is given, the default `RANGE_BASE..HEAD` is NOT added unless `--include-default`). Each batch resolves to a set of changed **paths**: parse the `--name-status -z` records — drop the status column; in `-z` mode a record is `STATUS\0PATH\0`, but a rename/copy record (status `R`/`C`) is `STATUS\0OLD\0NEW\0`, so read TWO path tokens when the status starts with `R` or `C` (take both) and one otherwise, or the parser desyncs. Union all batch path-sets with the working-tree set, deduplicated (do not `sort -u` raw NUL/status records): - `--range A..B` (repeatable): `git diff --name-status -M -C -z A B`. - `--commits SHA,…`: per commit `git diff --name-status -M -C -z ^ `; **reject merge commits** (parent count > 1 → tell the user to pass `A..B`); **root commit** diffs against the empty tree `git hash-object -t tree /dev/null` (`4b825dc642cb6eb9a060e54bf8d69288fbee4904`). -- `--since REF|date`: for a ref, `..HEAD`; for a date, resolve a boundary commit `B=$(git rev-list -1 --before= HEAD)` and diff `B..HEAD` with the same `git diff --name-status -M -C -z` as every other branch (do NOT `git log --name-only`, which yields a statusless, blank-line-polluted shape that will not union cleanly). +- `--since REF|date`: for a ref, `..HEAD`; for a date, resolve a boundary commit `B=$(git rev-list -1 --before= HEAD)` and diff `B..HEAD` with the same `git diff --name-status -M -C -z` as every other branch. If `B` is empty (no commit predates ``, so the whole history is in range), diff against the empty tree `git hash-object -t tree /dev/null` instead of `B..HEAD` (which would be `..HEAD` and fatal). Do NOT `git log --name-only`, which yields a statusless, blank-line-polluted shape that will not union cleanly. - `--from SHA`: one-off start override for this run only; does not touch `sync.md`. - `--working-tree-only`: skip the committed range entirely; document only uncommitted changes; never advance the watermark. -Always fold in, as an ADDITIONAL input tagged `worktree` (never advances the watermark): `git diff --name-only HEAD` (unstaged) ∪ `git diff --name-only --cached` (staged) ∪ `git ls-files --others --exclude-standard` (untracked). +Always fold in, as an ADDITIONAL input tagged `worktree` (never advances the watermark): `git diff --name-only HEAD` (every tracked path whose on-disk content differs from HEAD — staged and unstaged) ∪ `git ls-files --others --exclude-standard` (untracked). `git diff --name-only HEAD` already covers staged changes, so a separate `--cached` diff is redundant. -Empty range (`W == HEAD`, the commonest steady-state run): if `RANGE_BASE..HEAD` is empty AND there are no working-tree changes AND no batch flags, report `already up to date through ` and exit without touching docs, the index, or the watermark. +Empty range (already synced) — applies ONLY when the watermark was VALID (the `RANGE_BASE=$W` branch) and `W == HEAD`: if there are also no working-tree changes and no batch flags, report `already up to date through ` and exit without touching docs, the index, or the watermark. The degraded branches that force `RANGE_BASE=HEAD` (empty/missing watermark → first-run seed; HEAD-behind → refuse) also produce an empty range but are NOT "already synced" — each is handled by its own branch outcome above, not by this terminal. Doc-commit loop-breaker: exclude `llmdoc/` from the "is there source work?" test. If `git diff --name-only "$RANGE_BASE".."$H" -- ':(exclude)llmdoc/**'` is empty (range non-empty but doc-only), fast-forward the watermark to HEAD with zero doc work. Always exclude the sync file and `.llmdoc-tmp/` from every diff so a run never self-triggers. @@ -133,9 +143,9 @@ Self-authored test: a commit is self-authored when its author email (`git log -1 - Do not index `.llmdoc-tmp/`, and do not index `llmdoc/state/sync.md` as knowledge. 9. Advance the watermark (recorder-owned terminal step). - - Safe-to-advance gate — ALL must hold: the update completed successfully and consumed a committed range; HEAD is attached (`git symbolic-ref -q HEAD` succeeds); and no git operation is in progress (none of `git rev-parse --git-path MERGE_HEAD`, `CHERRY_PICK_HEAD`, `REVERT_HEAD`, `rebase-merge`, `rebase-apply` exists). + - Safe-to-advance gate — ALL must hold: the update completed successfully and consumed a committed range; HEAD is attached (`git symbolic-ref -q HEAD` succeeds); and no git operation is in progress. Test the last one by whether the resolved path EXISTS on disk (`git rev-parse --git-path` always prints a path and exits 0 regardless of existence, so check with `[ -f ]`/`[ -d ]`): none of `[ -f "$(git rev-parse --git-path MERGE_HEAD)" ]`, `CHERRY_PICK_HEAD`, `REVERT_HEAD`, `[ -d "$(git rev-parse --git-path rebase-merge)" ]`, `rebase-apply` may exist. - If the gate holds, advance `watermark-commit` to the captured `H` (default) or the highest unbroken-prefix tip (partial/batch). Rewrite ONLY these fields, keeping the exact `- watermark-commit: ` line prefix (the reader anchors on it — do not reformat it): - - `watermark-commit`: the new full 40-hex SHA + - `watermark-commit`: the new full commit SHA - `watermark-subject`: `git log -1 --format=%s ` - `updated-at`: ISO-8601 UTC, e.g. `date -u +%Y-%m-%dT%H:%M:%SZ` - `updated-by`: `/llmdoc:update` diff --git a/skills/llmdoc-update/SKILL.md b/skills/llmdoc-update/SKILL.md index 27c57c1..8a7faaf 100644 --- a/skills/llmdoc-update/SKILL.md +++ b/skills/llmdoc-update/SKILL.md @@ -34,7 +34,7 @@ Before editing stable docs: ## Sync State -`llmdoc/state/sync.md` is a tracked, machine-managed markdown file holding one load-bearing field, `watermark-commit` (full 40-hex SHA). It is not project knowledge: never index it, never add it to `startup.md`/`must/`, never count it as active memory. Read the watermark with a single anchored line: +`llmdoc/state/sync.md` is a tracked, machine-managed markdown file holding one load-bearing field, `watermark-commit` (the full commit SHA, whatever hash the repo uses — SHA-1 or SHA-256). It is not project knowledge: never index it, never add it to `startup.md`/`must/`, never count it as active memory. Read the watermark with a single anchored line: ```sh W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') @@ -55,33 +55,43 @@ Run all git plumbing without `set -e` (capture each exit code) so a `128` degrad SHALLOW=$(git rev-parse --is-shallow-repository) H=$(git rev-parse HEAD) # capture once -# 2. Read watermark (empty/unknown-schema → first-run: base=HEAD, no backfill unless --from/--since, force ≥ analysis, seed at HEAD) +# 2. Read watermark W=$(awk -F': ' '/^- watermark-commit:/{print $2}' llmdoc/state/sync.md 2>/dev/null | tr -d '[:space:]') -# 3. EXISTENCE before reachability (merge-base --is-ancestor exits 128 fatal on a missing object; guard first) -git rev-parse --verify --quiet "$W^{commit}" >/dev/null || echo "watermark object missing" # rev-parse --verify --quiet exits 1 cleanly on a missing/non-commit object (cat-file -e "$W^{commit}" would fatal 128 on the peel) → first-run baseline=HEAD, warn, do not advance; if $SHALLOW=true suggest: git fetch --unshallow - -# 4. Reachability -git merge-base --is-ancestor "$W" "$H" # rc=0 → valid, RANGE_BASE=$W -# rc≠0 → not ancestor; reverse test: -git merge-base --is-ancestor "$H" "$W" # rc=0 → HEAD BEHIND watermark → REFUSE (reverse range would delete docs); working-tree input only -# else → true divergence (rebase/squash): RANGE_BASE=$(git merge-base "$W" "$H"); force ≥ analysis - -# 5. Net change set (rename-aware, NUL-safe) +# 3-4. Resolve RANGE_BASE with EXPLICIT branching. Existence gates reachability: never run merge-base +# on an empty/missing $W — it would fatal-128, not skip. Each branch is terminal. +if [ -z "$W" ]; then + echo "watermark empty/missing → first-run: base=HEAD (no backfill unless --from/--since), force ≥ analysis, seed at HEAD on success" + RANGE_BASE=$H +elif ! git rev-parse --verify --quiet "$W^{commit}" >/dev/null; then + echo "watermark object missing (GC'd / below shallow boundary) → first-run baseline=HEAD, warn, do NOT advance" + # if $SHALLOW = true, suggest: git fetch --unshallow. (rev-parse --verify --quiet exits 1 cleanly; cat-file -e "$W^{commit}" would fatal 128 on the peel.) + RANGE_BASE=$H +elif git merge-base --is-ancestor "$W" "$H"; then + RANGE_BASE=$W # watermark valid +elif git merge-base --is-ancestor "$H" "$W"; then + echo "HEAD BEHIND watermark → REFUSE (reverse W..HEAD would list future files as deletions and strip docs); working-tree input only, do NOT advance" + RANGE_BASE=$H +else + RANGE_BASE=$(git merge-base "$W" "$H") # true divergence (rebase/squash-merge); force ≥ analysis +fi + +# 5. Net change set (rename-aware, NUL-safe). In -z mode a rename/copy record is STATUS\0OLD\0NEW\0 +# (3 fields; STATUS is R/C); a normal record is STATUS\0PATH\0. git diff --name-status -M -C -z "$RANGE_BASE" "$H" ``` -Batch flags (if any of `--range/--commits/--since/--from` is given, the default `RANGE_BASE..HEAD` is NOT added unless `--include-default`). Each batch resolves to a set of changed **paths**: parse the `--name-status` records — drop the status column, and for a rename `R` take BOTH the old and new path. Union all batch path-sets with the working-tree set, deduplicated (do not `sort -u` raw NUL/status records): +Batch flags (if any of `--range/--commits/--since/--from` is given, the default `RANGE_BASE..HEAD` is NOT added unless `--include-default`). Each batch resolves to a set of changed **paths**: parse the `--name-status -z` records — drop the status column; in `-z` mode a record is `STATUS\0PATH\0`, but a rename/copy record (status `R`/`C`) is `STATUS\0OLD\0NEW\0`, so read TWO path tokens when the status starts with `R` or `C` (take both) and one otherwise, or the parser desyncs. Union all batch path-sets with the working-tree set, deduplicated (do not `sort -u` raw NUL/status records): - `--range A..B` (repeatable): `git diff --name-status -M -C -z A B`. - `--commits SHA,…`: per commit `git diff --name-status -M -C -z ^ `; **reject merge commits** (parent count > 1 → ask the user to pass `A..B`); **root commit** diffs against the empty tree `git hash-object -t tree /dev/null` (`4b825dc642cb6eb9a060e54bf8d69288fbee4904`). -- `--since REF|date`: for a ref, `..HEAD`; for a date, resolve a boundary commit `B=$(git rev-list -1 --before= HEAD)` and diff `B..HEAD` with the same `git diff --name-status -M -C -z` as every other branch (do NOT `git log --name-only`, which yields a statusless, blank-line-polluted shape that will not union cleanly). +- `--since REF|date`: for a ref, `..HEAD`; for a date, resolve a boundary commit `B=$(git rev-list -1 --before= HEAD)` and diff `B..HEAD` with the same `git diff --name-status -M -C -z` as every other branch. If `B` is empty (no commit predates ``, so the whole history is in range), diff against the empty tree `git hash-object -t tree /dev/null` instead of `B..HEAD` (which would be `..HEAD` and fatal). Do NOT `git log --name-only`, which yields a statusless, blank-line-polluted shape that will not union cleanly. - `--from SHA`: one-off start override for this run only; does not touch `sync.md`. - `--working-tree-only`: skip the committed range entirely; document only uncommitted changes; never advance the watermark. -Always fold in, as an ADDITIONAL input tagged `worktree` (never advances the watermark): `git diff --name-only HEAD` ∪ `git diff --name-only --cached` ∪ `git ls-files --others --exclude-standard`. +Always fold in, as an ADDITIONAL input tagged `worktree` (never advances the watermark): `git diff --name-only HEAD` (every tracked path whose on-disk content differs from HEAD — staged and unstaged) ∪ `git ls-files --others --exclude-standard` (untracked). `git diff --name-only HEAD` already covers staged changes, so a separate `--cached` diff is redundant. -Empty range (`W == HEAD`, the commonest steady-state run): if `RANGE_BASE..HEAD` is empty AND there are no working-tree changes AND no batch flags, report `already up to date through ` and exit without touching docs, the index, or the watermark. +Empty range (already synced) — applies ONLY when the watermark was VALID (the `RANGE_BASE=$W` branch) and `W == HEAD`: if there are also no working-tree changes and no batch flags, report `already up to date through ` and exit without touching docs, the index, or the watermark. The degraded branches that force `RANGE_BASE=HEAD` (empty/missing watermark → first-run seed; HEAD-behind → refuse) also produce an empty range but are NOT "already synced" — each is handled by its own branch outcome above, not by this terminal. Doc-commit loop-breaker: if `git diff --name-only "$RANGE_BASE".."$H" -- ':(exclude)llmdoc/**'` is empty (range non-empty but doc-only), fast-forward the watermark to HEAD with zero doc work. Always exclude the sync file and `.llmdoc-tmp/` from every diff. @@ -109,7 +119,7 @@ Self-authored test: a commit is self-authored when its author email (`git log -1 6. Update stable llmdoc docs against the batch-tip state: update only impacted docs, correct stale claims, split aggressively, reconcile `llmdoc/memory/doc-gaps.md`. 7. Run the active-memory archive check (count files under `llmdoc/memory/` excluding `lessons-learned.md`, `doc-gaps.md`, `archive/`; `llmdoc/state/` is not counted). If > 5, follow `skills/llmdoc/references/lessons-learned.md`. 8. Synchronize `llmdoc/index.md`. Do not index `.llmdoc-tmp/`, and do not index `llmdoc/state/sync.md` as knowledge. -9. Advance the watermark (recorder-owned terminal step). Safe-to-advance gate — ALL must hold: the update completed successfully and consumed a committed range; HEAD is attached (`git symbolic-ref -q HEAD` succeeds); and no git operation is in progress (none of `git rev-parse --git-path MERGE_HEAD`, `CHERRY_PICK_HEAD`, `REVERT_HEAD`, `rebase-merge`, `rebase-apply` exists). If the gate holds, advance `watermark-commit` to the captured `H` (or the highest unbroken-prefix tip). Rewrite ONLY these fields, keeping the exact `- watermark-commit: ` line prefix (the reader anchors on it — do not reformat): `watermark-commit` (new 40-hex), `watermark-subject` (`git log -1 --format=%s `), `updated-at` (ISO-8601 UTC, `date -u +%Y-%m-%dT%H:%M:%SZ`), `updated-by` (`/llmdoc:update`). NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or when the safe-to-advance gate fails. +9. Advance the watermark (recorder-owned terminal step). Safe-to-advance gate — ALL must hold: the update completed successfully and consumed a committed range; HEAD is attached (`git symbolic-ref -q HEAD` succeeds); and no git operation is in progress — test by whether the resolved path EXISTS on disk (`git rev-parse --git-path` always prints a path and exits 0 regardless of existence, so check with `[ -f ]`/`[ -d ]`), none of `[ -f "$(git rev-parse --git-path MERGE_HEAD)" ]`, `CHERRY_PICK_HEAD`, `REVERT_HEAD`, `[ -d "$(git rev-parse --git-path rebase-merge)" ]`, `rebase-apply` may exist. If the gate holds, advance `watermark-commit` to the captured `H` (or the highest unbroken-prefix tip). Rewrite ONLY these fields, keeping the exact `- watermark-commit: ` line prefix (the reader anchors on it — do not reformat): `watermark-commit` (new full commit SHA), `watermark-subject` (`git log -1 --format=%s `), `updated-at` (ISO-8601 UTC, `date -u +%Y-%m-%dT%H:%M:%SZ`), `updated-by` (`/llmdoc:update`). NEVER advance on a `--working-tree-only` run, a failed/partial run, a HEAD-behind-watermark run, or when the safe-to-advance gate fails. 10. Report the mode used, resolved range(s)/batches and commit count, old → new watermark (or why it did not move), scratch/reflection paths, the archive action, and the stable docs that changed. At the end of a non-trivial task, proactively consider whether the user should be prompted to run this workflow. diff --git a/skills/llmdoc/references/templates.md b/skills/llmdoc/references/templates.md index c7a5a05..72fee52 100644 --- a/skills/llmdoc/references/templates.md +++ b/skills/llmdoc/references/templates.md @@ -58,7 +58,7 @@ Machine-managed commit watermark. Seed at init with `watermark-commit=$(git rev- - schema: 1 -- watermark-commit: <40-hex SHA of HEAD at init> +- watermark-commit: - watermark-subject: - updated-at: