Drift — documentation falling behind its source of truth

Drift is the gap between what the docs say and what is true. It has several sources and they need different guards, because "true" means a different thing in each.

Source of truth What drifts How it is caught
Code A renamed symbol, a changed signature, a removed flag, a new required argument Diff-triggered, on push or in CI
The live site A price, a plan name, a limit, a feature that shipped or was withdrawn Scheduled, against the public page
Another company A partner's API, a competitor's limit, an external link Scheduled, and on a known breaking change
The docs themselves A translation behind its source, a page nobody has reviewed, a leftover promise Scheduled, on an age threshold

Only the first is genuinely automatable end to end. The rest produce proposals — a wrong price or a claim about another company is never rewritten without a human. That boundary is in ../../docs-manage/references/fix-playbooks.md and it holds here.


Code → docs#

The one route where an automated rewrite is defensible: both sides are in the repository, the diff says exactly what changed, and the change is reviewable before it ships.

The pipeline#

  1. Clean up first. Remove any leftover working directories from previous runs before doing anything else. They are always garbage, and a stale one produces a confusing partial result.
  2. Offer the hook once. On first run, offer to install a pre-push hook so this happens automatically. On later runs, do not ask again.
  3. Detect what changed. Compute the diff against the remote merge-base. If no non-documentation files changed, exit cleanly — this is the common case and it must be fast and silent. Also detect whether the docs live in a regular folder or a submodule; the apply step differs.
  4. Check the search dependency is reachable. Semantic search over the docs is what makes this route work. If it is unreachable, abort with one clear line — never block the push silently.
  5. Cluster the changed files into semantic groups, each with a hypothesis about which documentation might have drifted. On failure, fall back to grouping by top-level directory.
  6. Search per cluster, in parallel isolated working directories. Each search returns candidate pages with a confidence score.
  7. Rewrite only above the confidence threshold (default 0.6). Below it, report the candidate rather than editing it.
  8. Curate before applying. One pass over all proposed edits together: resolve conflicts between clusters, normalise style, drop speculative edits, and — critically — verify that every concrete claim is grounded in the diff, the repository's own metadata, or the existing page. A generated install command that appears nowhere in the repository is the failure mode this step exists to catch.
  9. Apply atomically. For a regular folder: apply, stage, amend the commit. For a submodule: apply in the submodule, commit there, ask before pushing to its remote, and only then fast-forward and amend the parent.
  10. Clean up unconditionally on success or a no-op; keep the working directories on a failure or a pending consent, so the state can be inspected.
  11. Report as structured output: status, commits, pending pushes, and anything needing human review.

Guardrails specific to this route#

  • Warn, do not block, by default. Exit non-zero only when the repository's own configuration explicitly asks for it.
  • Never amend an empty edit list. "No doc edits needed" and exit.
  • Cap the diff passed to the clustering step (50 KB is a sane default); truncate larger ones with a note rather than silently.
  • Cap how much of a page one pass may rewrite (0.4 is a sane default). An edit beyond that is not a drift fix, it is a rewrite, and it needs a human.
  • A timed-out cluster is skipped and logged, never a reason to abort the others.
  • Pushing to a submodule's remote is a public action. The local commit needs no consent; the push is asked for by name, with the SHA, the remote and the branch stated. Only a CI configuration set deliberately may override this.
  • No fabricated commands, URLs, versions or limits. When unsure, link the README rather than guessing the install line.

Site → docs#

The docs quote a price, a plan name, a quota. The live page changes. Nobody updates the eleven mentions scattered through the documentation.

Schedule this against the live public page, not a constants file — the whole value is that one side is what a customer actually sees. The comparison method, the four verdicts, and the traps (the pricing page missing from the sitemap; the per-plan table sitting after the add-on price; "no facts observed" being when invention is most likely) are all in ../../docs-analyze/references/external-checks.md.

The automation's job is only to run it on a schedule and route the result. Natural triggers:

  • Immediately after any pricing or packaging change — the run that pays for the whole thing.
  • Before a launch or campaign that drives people into the docs.
  • Quarterly as a floor; quotas and limits drift more quietly than headline prices, because nobody announces them.

The output is always a proposal, never an applied edit. A wrong number in the docs may mean the live page changed without a decision anyone signed off, and an automatic rewrite erases the evidence.


Outside world → docs#

Claims about third parties decay with no commit on your side, no failing test, and no incident. Nothing in your own workflow will ever surface it, which is exactly why it needs a schedule.

Run it quarterly as a floor, and on a trigger when a partner ships a breaking change — that run is scoped to the one vendor and is much faster than a sweep. The verification method, the verdicts, and the "unverifiable is its own verdict" rule are in ../../docs-analyze/references/external-checks.md.

Route the result as a proposal. Where the check finds a dead external link, that fix is mechanical enough to propose as a pull request; where it finds a contradicted claim about another company, it is a sentence for a human to approve.


Docs → themselves#

Age-based and parity-based drift, which needs no external source at all:

  • Stale pages. A page untouched past a threshold — a shorter one for the pages everyone reads, a longer one for the rest. The event carries the page; the handler files it into normal triage.
  • Translations behind their source. A translation more than a set number of days behind is flagged, and past a larger threshold the page itself should carry an outdated-content banner until it is caught up. A stale translation is worse than none because it is trusted.
  • Leftover promises. A "coming soon" past its date, a past date presented as future, a TODO left in a published page.

These are the cheapest automations in the whole skill and the ones most worth having, because nothing else will ever surface them. Route them as filed issues rather than messages: they are not urgent, and they need to survive being ignored for a week.

Deprecated content is flagged for a banner and a migration path — never for deletion, automatic or otherwise.