oma-docs - Documentation Drift Detector
Scheduling
Goal
Detect broken references in docs/**/*.md (verify mode) and propose LLM-generated patch proposals for docs affected by recent code changes (sync mode). Both modes run on-demand; sync is always interactive.
Intent signature
- User asks to check if docs are up to date, find broken doc links, verify file paths referenced in docs, or detect documentation drift.
- User asks to update docs after a code change, propose doc patches for a git diff, or sync affected docs.
- A workflow hook checks
docs.auto_verify: trueand runsoma docs verify --jsonat completion.
When to use
- After a refactor, rename, or file deletion, to find stale references in docs.
- Before a release, to confirm that CLI commands, file paths, and config keys in docs still exist.
- After a significant git diff, to discover which docs reference the changed files and may need updating.
- Routine drift check on any docs-heavy repo.
When NOT to use
- Generating docs from scratch for undocumented features → v2 create mode.
- Multilingual translation of docs → use
oma-translator. - Symbol-level semantic drift (function signature changes not reflected in prose) → v2 L3 mode.
- CI-blocking enforcement → v2 block mode (v1 is warn-only).
Expected inputs
verify mode: Optional glob path (default **/*.md), optional --json flag, optional --report-file <path>.
sync mode: Optional git diff range (default --cached, fallback HEAD~1..HEAD).
Expected outputs
verify mode:
- Markdown drift report to stdout (default), or raw JSON with
--json, or full markdown written to file with--report-file. - Exit code 0 if clean, 1 if any broken refs found.
sync mode:
- Per-doc patch proposals drafted by the host LLM from the CLI's candidate-doc list, confirmed per doc (
[y] apply [n] skip [d] diff [s] full proposalstyle). - Docs modified only on explicit user approval;
doc-refs.jsonregenerated after applies.
Dependencies
cli/commands/docs/extract.ts: markdown AST + L2 pattern extractor.cli/commands/docs/resolve.ts: deterministic broken-ref checker.cli/commands/docs/reporter.ts: deterministic markdown/JSON report renderer (no LLM call; host LLM does narrative synthesis).cli/commands/docs/sync-propose.ts: git diff intake, reverse lookup, candidate-doc selector with secret redaction (no LLM call; host LLM drafts patches).docs/generated/doc-refs.json: single-direction reference index (git-tracked, regenerated on every verify run).docs/generated/url-drift.json: lychee-produced URL drift report (written by background lychee spawn; gitignored or tracked at user discretion).lychee: external Rust tool for URL link checking. Detected on PATH; install viabrew install lycheeor see https://github.com/lycheeverse/lychee#installation. Optional but recommended..agents/oma-config.yaml:docs.auto_verify(workflow hook opt-in) anddocs.check_urls(URL checking on/off, default true) toggles.
Control-flow features
- Mode is selected from the first argument:
verifyorsync. - verify: extract → resolve → report (fully deterministic CLI; host LLM adds narrative summary on top of the JSON/markdown output).
- sync: git diff → reverse lookup → candidate list (CLI) → host-LLM patch proposals → interactive accept/reject.
- Branches on
--json,--report-file, LLM availability, and network reachability. - Never blocks workflow completion in v1 (warn-only hook policy).
Structural Flow
Entry
- Read first argument to select mode (
verify|sync). If absent, print help and exit. - Load
oma-config.yamlto checkdocs.auto_verifywhen invoked from a workflow hook. - Confirm required CLI dependencies (
oma docs verify,oma docs sync) are on PATH.
Scenes
- PREPARE: Determine mode, resolve path/diff-range arguments, confirm tool availability.
- ACQUIRE: Run extractor (
extract.ts) to regeneratedoc-refs.jsonfromdocs/**/*.md(verify) or build in-memory reverse index from existingdoc-refs.json(sync). - REASON: Resolve each reference deterministically (verify) or correlate changed files to candidate docs via reverse lookup (sync).
- ACT: Render the deterministic drift report (verify) or list candidate docs with matched refs (sync). Host LLM does any natural-language synthesis or patch drafting on top of this output.
- VERIFY: Confirm output shape is valid (JSON schema check for
--json; structured candidate list for sync). - FINALIZE: Print to stdout, write report file if requested, emit exit code.
Transitions
- verify mode: PREPARE → ACQUIRE (extract) → REASON (resolve) → ACT (report) → FINALIZE.
- sync mode: PREPARE → ACQUIRE (reverse index) → REASON (candidate matching) → ACT (LLM proposals) → VERIFY (interactive) → FINALIZE (apply approved).
- If LLM is unavailable in verify: skip reporter summary, emit raw JSON drift report.
- If LLM is unavailable in sync: emit candidate-list-only output (no patch proposals); user reviews manually.
- If
doc-refs.jsonis stale or missing in sync: run extractor first, then continue.
Failure and recovery
- Extractor parse error on a single doc: skip doc + warn, continue with remaining docs.
- lychee unavailable or URL check incomplete: print install hint, skip URL checking, continue (core check is unaffected).
- Host-LLM context limit exceeded while drafting patches: process candidate docs in smaller batches.
oma docsCLI not found: skip with installation hint (workflow hook: skip silently).doc-refs.jsonwrite failure: abort and report the write error; do not emit partial index.
Exit
- Success (verify): drift report emitted; exit 0 if clean, exit 1 if broken refs found.
- Success (sync): approved patches applied;
doc-refs.jsonregenerated; session summary printed. - Partial success: extractor or resolver errors are explicit in the report; no silent failures.
Logical Operations
Actions
| Action | SSL primitive | Notes |
|--------|---------------|-------|
| Parse CLI args and mode | READ | First arg selects verify or sync |
| Extract refs from docs | CALL_TOOL | extract.ts: remark AST + L2 patterns → doc-refs.json |
| Check broken refs | RESOLVE | resolve.ts: file, url, cli, script, env, config checks |
| Build reverse index | INFER | sync-propose.ts: in-memory map from doc-refs.json |
| Match diff to candidate docs | RESOLVE | sync-propose.ts: git diff + reverse lookup |
| Redact secrets from diff | VALIDATE | Exclude .env*, *.pem, *.key, id_rsa*; sanitize content |
| Generate patch proposals | INFER | Host LLM drafts patches from sync-propose.ts candidate output (no CLI LLM call) |
| Render drift report | RENDER | reporter.ts: markdown (default), JSON (--json), file (--report-file) |
| Apply approved patches | WRITE | git apply on user-confirmed patches only |
| Notify hook summary | NOTIFY | 1-3 line stdout summary for workflow hooks |
Tools and instruments
cli/commands/docs/extract.ts:remark+unifiedmarkdown AST, L2 pattern extraction, escape hatch filter,docs/generated/doc-refs.jsonwriter.cli/commands/docs/resolve.ts: case-sensitive file existence,whichfor CLI tokens,package.jsonscripts lookup, ripgrep/git grep for env vars,oma-config.yamldeep-path check. Per-target dedupe caches (cli by first token, env, config) and per-directory listing cache for file resolution. URL kind is filtered out by the verify command and delegated to lychee.cli/commands/docs/reporter.ts: deterministic markdown + JSON renderer. No LLM call. Friendly summary, severity tagging, fix prioritization are the host LLM's responsibility.cli/commands/docs/sync-propose.ts: git diff intake, reverse index build, secret-pattern + gitignore file exclusion. Returns candidate docs with matched refs only. No LLM call. Patch synthesis is the host LLM's responsibility.- External:
lychee(background URL link checking; install viabrew install lychee).
Host-LLM contract
This skill follows the OMA pattern (mirroring oma-scholar): the CLI emits structured data; the host LLM (the agent runtime that invoked the skill) does any natural-language synthesis or judgment.
After oma docs verify --json:
- Read the JSON drift report.
- Group findings by severity / urgency (host-LLM judgment).
- Suggest fixes per finding, prioritizing files most central to the project.
- If the user asks for natural-language summary, host LLM produces it from the JSON, never from cached prose.
After oma docs sync <range> --json:
- Read the candidate doc list (each entry:
{ doc, changedFiles, matchedRefs }). - For each candidate doc: read the doc itself, read
git diffforchangedFiles, draft a unified-diff patch reflecting the code change. - Present patches to the user for review. Never auto-apply.
- On user approval, apply via
git applyor by writing the doc directly.
Canonical command path
verify mode runs a drift check against the current codebase:
# Default: scan all docs/**/*.md, render markdown to stdout.
# URL link checking is delegated to lychee in the background
# (install: `brew install lychee`). Core check ~8s on a 1k-doc repo.
oma docs verify
# Narrow to a path or glob (uses minimatch)
oma docs verify "docs/**/*.md"
oma docs verify cli/README.md
# Machine-readable output for CI / hooks
oma docs verify --json
# Persist full markdown report to a file
oma docs verify --report-file ./drift-report.md
# Skip URL checking entirely (when lychee is run separately, or as a
# one-off override of docs.check_urls=true in oma-config.yaml)
oma docs verify --no-urls
# Block until lychee finishes (CI scenarios needing complete URL data)
oma docs verify --urls-sync
# Exit code: 0 = clean, 1 = broken refs found in core check.
# URL drift, if any, is reported separately at docs/generated/url-drift.json
# and does NOT affect this exit code.
sync mode proposes patches for docs affected by a git diff (always interactive, never auto-applies):
# Default: staged changes (--cached), fallback HEAD~1..HEAD
oma docs sync
# Explicit range
oma docs sync HEAD~5..HEAD
oma docs sync main..feature-branch
# The CLI emits the candidate-doc list; the host LLM drafts patches and
# confirms per doc ([y] apply / [n] skip / [d] diff / [s] full proposal).
# Sync regenerates docs/generated/doc-refs.json after applying any patches.
Workflow hook (opt-in) runs verify automatically at workflow completion when docs.auto_verify: true in oma-config.yaml:
# Hook command emitted by /scm, /work, /ultrawork
oma docs verify --json
# Hook policy: warn-only in v1; non-zero exit does NOT block workflow completion
Resource scope
| Scope | Resource target |
|-------|-----------------|
| LOCAL_FS read | docs/**/*.md (extractor input), docs/generated/doc-refs.json (index), .env.example, package.json, .agents/oma-config.yaml |
| LOCAL_FS write | docs/generated/doc-refs.json (regenerated each verify run), approved sync patches |
| CODEBASE read-only | Existence checks for file/cli/script/env/config refs; git diff intake |
| PROCESS | git diff, git apply, which, background lychee spawn |
| NETWORK | URL checking delegated to lychee (no internal HEAD fallback; see Guardrail 6) |
Preconditions
docs/directory exists at repo root.cli/commands/docs/is built andomabinary is on PATH (or invoked directly viabun run).- For sync mode: a git diff is available (
--cachedstage or recent commits).
Effects and side effects
- verify: regenerates
docs/generated/doc-refs.json(always overwrites). - sync: modifies docs files only on user approval; regenerates
doc-refs.jsonafter applies. - Both modes: stdout output (summary or full report).
- No
.agents/files are ever modified.
Guardrails
- Never modify
.agents/: CLAUDE.md SSOT protection applies in all modes. - Never auto-apply sync patches: sync is always interactive;
[y]confirm required per doc. - LLM unavailable → graceful degradation: verify falls back to raw JSON; sync falls back to candidate-list-only (no proposals). Neither mode blocks on LLM availability.
- Response language follows
oma-config.yamllanguage: user-facing report text is localized; code, paths, JSON keys, and CLI commands stay in English. - Secret-bearing files excluded from sync output:
.env*,*.pem,*.key,id_rsa*, and gitignored files never appear in candidatechangedFileslists. Host LLM never sees secret file paths. - URL link checking delegated to lychee: when
docs.check_urls=true(default), URL refs are checked bylycheerunning in the background; results land indocs/generated/url-drift.json. Iflycheeis missing, an install hint is printed and URL checking is skipped (no internal HEAD fallback). - No direct LLM API calls from the CLI: the CLI never imports vendor SDKs, never reads API keys, never makes outbound LLM requests. All synthesis, patch drafting, and natural-language framing is the host LLM's responsibility (mirrors
oma-scholar's pattern). This makesoma-docsvendor-agnostic: works identically under Claude Code / Codex / Gemini / Qwen / Antigravity. - Hook is warn-only in v1: broken refs never block workflow completion;
docs.auto_verify: falseby default (explicit opt-in required). - Escape hatch respected:
<!-- oma-docs:ignore-start -->/<!-- oma-docs:ignore-end -->blocks and frontmatteroma-docs: skipare honored; no ref extraction from ignored regions.
v1 scope note
v1 covers verify and sync (broken-only classification, L2 ref extraction). The following are explicitly deferred to v2: create mode (generate missing docs), multilingual sync (deeper oma-translator integration), L3 symbol-level extraction (Tree-sitter/LSP), GitHub Action wrapper, block hook mode.
References
- Design doc:
docs/plans/designs/008-oma-docs.md(full architecture, schema spec, decision log, edge cases). - Schema spec:
doc-refs.jsonv1 schema defined in design doc § doc-refs.json Schema. - Workflow hook integration: design doc § Workflow Hook Integration.
- Migration:
deepinitStep 6 retirement, design doc § Migration: deepinit Step 6. - Adjacent skills:
oma-translator(v2 multilingual),oma-skill-creator(SSL-lite validation).