clean-docs is a source-bound documentation engine and CLI for maintainers whose code changes faster than its documentation. It identifies stale claims and provides a local, deterministic path from source change to repaired, verified docs; models may phrase facts, but they never choose the facts or gate results.
Start here for the product map, runnable drift tutorial, real-repository postmortem, and deterministic-boundary explanation.
A stale sentence does not fail loudly. It keeps a straight face after the code has moved on, leaving reviewers no mechanical way to know which claim became false. Human review and general-purpose agents can improve wording, but neither makes the relationship between a claim and its source reproducible in CI.
clean-docs exists to make that relationship explicit. Source owns the facts; the packaged standard owns their form. clean-docs audits tracked Markdown, binds claims to source evidence, repairs declared regions, and fails CI when either the facts or the documentation contract drift.
Static adapters cover Python, TypeScript, OpenAPI, JSON Schema, package metadata, and MCP tools without importing repository code. Declared commands and versioned plugins run in disposable copies with bounded I/O and minimal environments.
The same verified graph produces llms.txt, named context bundles, grounded release facts, and task evaluations for people and agents. Local receipts make those checks inspectable without telemetry.
derive previews changes unless you pass --write. audit, check, verify, and release never change documentation.
Repository sources such as code, schemas, commands, package metadata, and API metadata become typed
evidence through static extraction or declared, bounded execution. Source bindings connect that
evidence to documentation regions, claims, and symbols. The clean-docs engine combines those
bindings with the packaged writing standard, then produces three outcomes: repaired documentation,
a read-only CI gate that rejects stale changes, and verified context projections such as llms.txt,
context bundles, and release facts. Models may phrase supplied evidence outside this deterministic
path; they do not choose facts or decide whether the gate passes.
Create an isolated environment, install the project, and audit the current repository:
git clone https://github.com/owieschon/clean-docs.git && cd clean-docs
python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
clean-docs auditaudit inventories tracked Markdown without .clean-docs.yml, enforces corpus rules, and scans tracked product files for repository residue.
A successful audit prints the number of checked documents and exits 0. Release wheels use a
separate local-artifact procedure because they do not install from a development checkout.
Bootstrap the source bindings once, inspect the patch, and verify the protected baseline:
clean-docs init --no-model
git diff
clean-docs check
clean-docs verifyThe final command prints a clean-docs.outcome.v1 receipt with "ok": true. Commit the manifest,
generated documentation, and source change together only after that result appears.
init adds a compact source-surface summary, .clean-docs.yml, and a checked llms.txt projection. It discovers nested package manifests, proposes at most eight canonical documents, and caps machine-readable plan details while retaining the full-plan digest and counts. Run clean-docs inventory for the complete detected catalog. The summary carries a hidden catalog digest, so check catches a source replacement even when its surface count stays the same.
Catalog coverage detects additions, removals, and replacements. It does not validate individual prose claims or track every member of a discovered collection. Bind a claim to its specific source when a source edit must make that prose fail. After bound source drift, use this repair sequence:
clean-docs check # exits 1 and names the stale binding
clean-docs drive # repairs recognized bound regions
clean-docs project # refreshes projections that include the repaired document
clean-docs verify # exits 0 only when the repository is currentCommit the generated files with the source they describe. drive does not refresh projections, so a
document included in llms.txt or another projection requires the separate project step.
If clean-docs cannot identify substantive authored purpose prose, init reports the affected page instead of marking metadata, a status line, or a feature fragment as a valid contract. If existing hygiene findings block adoption, follow the mature-repository baseline procedure. The explicit baseline protects existing debt by exact fingerprint and still fails on new or resolved findings.
Use the CLI reference to look up each command and whether it writes. The support guide defines supported runtimes, upgrades, diagnostics, and local outcome receipts.
This table is derived from the binding types accepted by the manifest validator:
| binding | required | verifies |
|---|---|---|
| region | id, type, doc, region, extractor, source, renderer | Generated content matches source evidence |
| claim | id, type, doc, anchor, command, assertion | Observed command value matches the assertion |
| symbol | id, type, doc, anchor, source | A source path or Python symbol still exists |
Create .clean-docs.yml at the repository root and declare the source for each protected fact:
version: 1
bindings:
- id: actions
type: region
doc: README.md
region: actions
extractor: python-literal
source: {path: src/actions.py, symbol: ACTIONS}
renderer: markdown-table
columns: [name, tier]Mark the generated destination in the document:
<!-- clean-docs:begin actions -->
<!-- clean-docs:end actions -->The source assignment may be a list of dictionaries or a dictionary whose values are records. Constructor calls are read as keyword records. clean-docs parses the syntax tree and does not execute the module.
Repositories do not configure a standard path. clean-docs bundles a versioned policy pack compiled from STANDARD.md. CI fails if the authored standard changes without rebuilding that pack.
Reproduce the pinned public-repository proof with:
PYTHONPATH=src python3 scripts/dogfood_public_repos.py
PYTHONPATH=src python3 scripts/dogfood_bootstrap_repos.pyThe binding proof checks source drift and recovery at two fixed commits; the bootstrap proof initializes pinned Python and TypeScript repositories, verifies each baseline, and requires empty reruns without executing target code.
Self-hosting uses python3 scripts/trusted_self_check.py; the verifier pinned in .clean-docs-trust.json independently checks candidate code, and updating that pin is a release operation.
This table is derived from src/clean_docs/capabilities.py by clean-docs itself:
| binding | source | output | check |
|---|---|---|---|
| claim | Allowlisted JSON command | Assertion at a document anchor | Compare typed expected and observed values |
| region | Static Python, structured data, text, or paths | Table, list, scalar, or fenced text | Re-render and compare |
| symbol | Static path or Python symbol | Reference at a document anchor | Resolve the cited locator |
- Claims consume JSON from an allowlisted command; symbols resolve static paths or Python names.
- Declared commands and plugins run in disposable repository copies with minimal environments, active I/O limits, and timeouts. In an allowlisted
argv,{python}selects the interpreter running clean-docs. Network isolation belongs to the execution environment; the security model defines the remaining OS boundary. - Coverage ignores must name a detected inventory ID and carry a specific reason;
explainreports the evidence and repair for gaps. - Changed checks have published P95 time and peak-memory budgets for small, medium, and monorepo fixtures.
- Destination markers must already exist and cannot nest.
- Evaluation claim boundaries are defined in the evaluation guide.
- clean-docs reports malformed configuration as exit
2, drift as exit1, and extraction failures as exit3. See the recorded drift demonstration and use the evaluation guide for task fixtures. The full product contract and version plan live inCLEAN_DOCS_SPEC.md.