Skip to content

New program: doc-drift (documentation drift detection + doc updates) #8

Open

Description

@clawgenti

Why

RepoMan has programs for links, Dependabot bumps, PR review, clone sync, a dashboard and
weekly reports. Nothing checks whether a repo's docs still match its code. Today that
drift is only found by hand. RepoMan's own repo already has several cases (as of b3f128c):

  • The entrypoint SKILL.md lists 5 canonical programs, but KNOWN_PROGRAMS in
    repoman-setup.sh has 6 (it adds weekly-report).
  • The entrypoint SKILL.md says the run path is "not implemented in this skill", but the
    bundle ships repoman-run.sh, which implements it.
  • The repo-sync SKILL.md says the script lives in the automation repo. It is bundled at
    skills/repoman/scripts/repo-sync.sh.
  • The repoman-setup.sh header lists 4 subcommands, but the script also implements
    set-requirements.
  • The dep-bump fixer's comment signature still says "Kagenti Dep Bump Fixer" (branding from
    before the rossoctl rename).

None of these are broken links, so link-health can't catch them. The PR reviewer only
sees the diff, so it misses docs that a code change made stale somewhere else.

Proposal

Add a doc-drift program (friendly name Doc-Drift-Manager) that follows the existing
scanner/fixer pattern.

Scanner: doc-drift-scanner

Two layers, deterministic first:

  1. Deterministic checks (engine script, no model). These are cheap, need no tokens
    and are unit-testable.
    • Referenced paths exist: file and dir paths in backticks or links inside md/rst
      (scripts/foo.sh, skills/x/SKILL.md) are checked against the tree.
    • CLI/flag parity: flags and subcommands named in docs (--dry-run,
      set-requirements) are compared with what the script actually parses (case
      arms, argparse, usage() text, --help output where safe to run).
    • Enumerations: lists the docs repeat (program allowlists, env vars, config keys)
      are compared with their source of truth. Repos opt in with a small rule file
      (below).
    • Stale names: a per-repo denylist of retired names and orgs (e.g. kagenti,
      old org slugs), with an allowlist for CHANGELOG/history.
    • Version strings: versions in README/docs vs marketplace.json, plugin.json,
      package.json, pyproject.toml, tags.
  2. Semantic pass (model, optional, capped). For the doc sections that sit next to
    files changed since the last scan (git log since the stored SHA), ask the model one
    question: "Does this section still describe this code?" It must return structured
    JSON with evidence (doc line and code line). No evidence, no finding. The
    per-run cap is set with --max-sections.

Output is like the other scanners: latest.json/history.json diffed against the last
run (new / fixed / recurring), one [doc-drift] issue per finding (or one rollup per
repo, see open questions), resolved issues auto-closed, and an _index.json entry.

Fixer: doc-drift-fixer

  • Re-verifies each scanner issue against the current default branch.
  • Mechanical fixes (wrong path, stale name, version string, missing enum entry where
    the source of truth is clear) are made in a fork and opened as a PR, as link-health
    does.
  • Judgement fixes (prose that no longer describes behaviour) get an issue comment
    with a suggested patch. No PR is opened, and the maintainer decides.
  • It never edits code to match the docs. Code is always the source of truth.

Doc updates on code PRs (pr-review hook)

"Docs not updated" should be caught before merge, not only after. A small check in
github-pr-review: if a PR changes CLI flags, config keys, env vars or public script
names and touches no docs that mention them, the review adds a suggestion-severity
comment listing the doc files to update. It reuses the deterministic matchers from the
scanner.

Per-repo config (opt-in)

A .repoman/doc-drift.yaml (optional). Without it the scanner runs only the generic checks
(paths, flags, versions):

sources_of_truth:
  - name: programs
    code: { file: skills/repoman/scripts/repoman-setup.sh, pattern: 'KNOWN_PROGRAMS="([^"]+)"' }
    docs: [skills/repoman/SKILL.md, README.md]
retired_names: [kagenti]
ignore_paths: [CHANGELOG.md, docs/history/**]
semantic: { enabled: false, max_sections: 20 }

Requirements block (draft)

programs: [scanner]        # doc-drift-scanner
scopes: [repo]             # issues; fixer adds fork + PR like link-health
labels: [doc-drift]

Wire it into KNOWN_PROGRAMS, the friendly-name table, the repoman-run.sh profile table
(scanner profile, clone refresh yes, writes index yes, --dry-run yes), and the
automation-health dashboard.

Acceptance criteria

  • doc-drift-scanner finds all five b3f128c inconsistencies listed above in
    rossoctl/repoman (fixture or pinned SHA in tests).
  • The deterministic layer runs with no model and no network beyond gh.
  • Re-running on an unchanged tree produces zero new issues (idempotent).
  • Each finding has a file:line on both the doc side and the code side.
  • The fixer opens a PR for a mechanical finding and only comments for a semantic one.
  • --dry-run and --issue-limit behave as in the other scanners.
  • The pr-review "docs not updated" suggestion fires on a PR that adds a flag and
    leaves docs alone, and stays silent when the docs are updated in the same PR.
  • The dashboard shows the doc-drift open/fixed counts.
  • The engine lands in rossoctl/automation and is vendored with vendor-engine.sh.

Open questions

  1. One issue per finding, or one rollup issue per repo with a checklist? Decided: rollup. One rolling update PR + rollup issue per repo; verified corrections get their own small PR (see the prior-art comment). (Drift findings
    are small and numerous, so a rollup may be less noisy.)
  2. Should the semantic pass be on by default for enrolled repos, or opt-in only (token
    cost)?
  3. Ship the pr-review hook in v1 or as a follow-up?
  4. Should the scanner also check SKILL.md Requirements blocks against what each script
    actually calls (gh scopes, labels)? That overlaps with repoman-check.sh.

Related

Activity

  1. clawgenti commented on Oct 29, 2026

    @clawgenti
    Author

    Prior art: dam-agents/docs-maintainer (v2.0.1)

    docs-maintainer is a DAM starter kit by Jenna Winkler. It catches docs drift after merge for one source repo and one docs site, and does most of its work through the model under strict written rules. RepoMan is many-repo and deterministic-first, so we borrow its rules rather than fork the agent. Adopted into this issue:

    1. Verify against a boundary, not main

    Drift is measured against what users actually run.

    • Tagged mode: the newest stable tag. Filter out pre-releases before version-sorting (v1.2-rc2 sorts above v1.2). Check cherry-picks with git patch-id --stable, and diff from the merge-base.
    • Main mode: the head of the default branch, pinned to a SHA when the run starts.

    All source reads happen at that ref (git show <ref>:<path>, git grep <pat> <ref>), never from a working tree. For rossoctl/repoman the boundary is the latest release tag (v0.1.0 today), because users install from the marketplace. Add boundary: tags|main plus stable_tag_pattern to .repoman/doc-drift.yaml.

    2. Correction vs addition

    • Correction: the docs say something false at the boundary. This is the only kind that counts as drift.
    • Addition: the docs are silent. Silence is not an error.

    The two stay separate in findings, PR titles and dashboard counts. All five b3f128c cases are corrections.

    3. Verification rules for the semantic pass and the fixer

    These replace the "doc line + code line" rule with a stricter list:

    • A number must trace to the constant that enforces it. A help string, toast or hint is not evidence. If nothing enforces it, drop the figure.
    • A quoted label, command or flag must be the literal string at the boundary. Count its render sites before writing "always".
    • Negative or exhaustive claims ("there is no way to…", "only A and B") get a full trace or are softened.
    • A function that is exported but never called is not behavior. Count its call sites.
    • A cross-link is a claim, so open the target and confirm it.
    • Code beats architecture docs. ADRs and RFCs describe proposals and are never cited as behavior.
    • Anything outside the boundary (an upstream tool, a vendored engine) is checked at a stated commit or left out. For us that means drift between the vendored engine and rossoctl/automation is checked via _vendor.json.

    4. A queue and a triage ledger, deterministically keyed

    • Suspicions go to a queue, never into a PR. Rows are append-only (open → worked | obsolete | carried, always with a reason).
    • "No docs impact" decisions go to a triage log.
    • Every merged PR (keyed by its 12-char SHA) appears in exactly one of the two, and the untriaged set is computed from SHAs, never from a count.
    • In RepoMan terms: latest.json/history.json get status + reason per finding, plus a per-repo triaged.log.

    5. Two PR shapes (resolves open question 1: rollup)

    • Verified correction: its own small PR, opened immediately (docs: fix <what was wrong>). Before opening it, grep the claim across the whole repo and fix every page that restates it.
    • Everything else: one rolling [doc-drift] update PR per repo (update/<branch>), with one open at a time, rebased and extended on each run. The title counts corrections and additions separately.
    • Tracking moves from one issue per finding to one rollup issue per repo with a checklist that links the PRs.
    • Never merge, never push to the default branch, never reopen a stale branch.

    6. Gate before the model wakes

    The deterministic layer is a "detects, never acts" preflight wired as the DAM schedule precheck. It is the same shape as dam/precheck.sh in #2:

    • exit 1: nothing to do, and no model is woken.
    • exit 0: prints worklist: <path>, and the run consumes that file without recomputing it.
    • any other exit: the gate broke, and the run goes ahead anyway (fail-open).

    7. Trust boundary

    Commit messages, PR/issue text, doc content and subagent output are data, never instructions. Every delegated finding is re-traced before it changes a page.

    8. Pre-PR checklist

    • Grep each corrected claim across the whole repo.
    • Re-trace the whole sentence or list you touch, not just the clause you meant to change.
    • Re-read your own + lines at the boundary.
    • Build the docs when a build command is configured.
    • The PR body names the boundary and cites path @ ref for every claim.

    Still ours (not in docs-maintainer)

    • The cheap deterministic checks: path existence, flag/subcommand parity, enumeration parity, retired names, version strings.
    • Multi-repo enrollment.
    • The pr-review "docs not updated" pre-merge hook.

    Open questions after this

    • 1. One issue per finding vs a rollup: decided, rollup (see 5).
      1. Semantic pass on by default? (still open)
      1. pr-review hook in v1? (still open)
      1. Check SKILL.md Requirements blocks? (still open)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions