Your documentation is lying to you, and nothing in your build is checking.
Tests prove the code does what the tests say. Nothing proves the README does. So the paths drift, the flags get renamed, the spec describes a design that shipped differently, and the first thing a new person learns about the repo is that its documentation cannot be trusted.
docgarden is ten linters that read your documentation against your repository — and a scheduled agent that fixes what they find and opens one pull request a week.
It runs entirely on your machine. The linters make no network call and use no model, which is what lets them block a build.
One Python file tree with no dependencies — Python 3.8 or newer and nothing else.
As a Claude Code plugin (gives Claude the skill, so you can just ask):
/plugin marketplace add seyonv/docgarden
/plugin install docgarden
Then type /docgarden in any repository. It reads the repo, proposes a
config, runs the first pass, hands you the report, and installs the jobs you
pick.
As a skill only:
git clone https://github.com/seyonv/docgarden
cp -r docgarden/plugins/docgarden/skills/docgarden ~/.claude/skills/As a plain command line tool, no Claude Code required:
git clone https://github.com/seyonv/docgarden
docgarden/bin/docgarden demobin/docgarden works from wherever the clone lives. To type docgarden from
anywhere, symlink it onto your PATH:
ln -s "$PWD/docgarden/bin/docgarden" /usr/local/bin/docgardendocgarden demoA small billing service with defects planted on purpose — a link to a spec that was deleted, a README table pointing at two files that were renamed, a runbook running an npm script nobody defined, a status page claiming a different test count from the README. Every picture on this page is made from it, so nothing of anyone's real work is here.
Two families. Hygiene asks whether the knowledge base is well-formed. Reality asks whether it is still true.
| Rule | What it catches |
|---|---|
links |
Relative links to files that are gone — and #anchors naming a heading that no longer exists, which is the half nothing else checks. A dead link fails loudly; a dead anchor silently lands the reader at the top of the page and lets them conclude the document does not say what it promised. |
orphans |
Documents no path leads to from a declared root. Not wrong on its own — but the strongest predictor of staleness there is, because nobody re-reads what they cannot reach. |
index |
Both directions: a doc missing from its collection's index, and an index row pointing at a doc that no longer exists. |
crosslinks |
Declared edges. "Every plan links the spec it implements." These are the joints of a knowledge base and the first thing to decay. |
structure |
Filename conventions and required sections, per collection. |
frontmatter |
Required keys, where a collection asks for them. Off by default. |
| Rule | What it catches |
|---|---|
coderefs |
Every path written in backticks resolves to a file that is really there — and, when it can, tells you where it went. "There is a session.py at src/auth/session.py — moved?", or "Nothing named invoices.py is anywhere in the repo — deleted?" |
commands |
The copy-pasteable blocks are still commands: the script exists, the --flags appear in that script's source, the npm run target and make target are really defined. The most damaging kind of stale doc, because a fence looks executable so people execute it. |
agreement |
A fact written in two places, written differently. Test counts, versions, ports. Finds them across hard-wrapped prose, where 1271 ends one line and tests starts the next. |
staleness |
Ranked by how far the watched code has moved since the doc was last touched. Never a verdict — every finding is a suspect, suspects never fail a build, and they exist to tell the gardener where to read. |
This is the part most doc linters get wrong. A repository with a hundred documents has a hundred findings on day one, CI goes red, and the tool is uninstalled by the afternoon.
docgarden check --update-baseline.docgarden/baseline.json records today's backlog by fingerprint — not by line
number, so an unrelated edit above a finding does not resurrect it. CI then
fails only on findings that are not in it. Your build is green today, new
breakage is caught tomorrow, and the backlog is what the gardener works
through.
It is a debt ledger, not a suppression list. It should get smaller.
Point docgarden at a repo where the docs were never cross-linked and orphans
will fire on 128 of 130 files. That is not 128 problems. It is one: the
convention is not one this repo follows, or the config is pointing at the wrong
thing.
So above a threshold, a rule collapses into a single finding that says so:
muted:
orphansmatched 128 of the 130 documents it looks at (98%)A rule that matches almost everything is describing your config, not your docs. Either fix the setting it depends on, turn it off with
[rules.orphans] enabled = false, or raise its tolerance with[rules.orphans] saturation = 0.9to see all 128 findings.
Measured against the documents each rule actually looks at, not against the whole repo — 27 findings across all 27 plans is saturated even though it is 20% of the tree.
docgarden init reads the repo and writes a starting .docgarden.toml. The
interesting part is collections — a family of docs that share rules, which
is where docgarden stops being generic:
[docgarden]
roots = ["README.md", "docs/STATUS.md"] # a doc reachable from none of these is an orphan
docs = ["*.md", "docs/**/*.md"]
source = ["src/**/*.ts", "scripts/**/*.py"]
[[collections]]
name = "spec"
match = "docs/specs/*.md"
filename = "^\\d{4}-\\d{2}-\\d{2}-.+\\.md$"
require_sections = ["Decision"]
index = "docs/specs/index.md" # every spec must be listed there
watches = ["src/**/*.ts"] # this spec goes suspect when that code moves
[[crosslinks]]
name = "plan cites its spec"
from = "docs/plans/*.md"
to = "docs/specs/*.md"
[[agreements]]
name = "the test count"
pattern = '(\d{3,5}) tests'
files = ["README.md", "docs/STATUS.md"]
first_only = true # read each file's headline, not its changelogSeverities are per-rule: error fails the build, warn reports, suspect is
gardener-only fuel.
docgarden contains no agent. It contains the two things an agent is bad at
supplying itself — a precise, bounded worklist and a safe way to land the
result — and points whatever Claude you have at the gap.
docgarden garden --max 8prints a brief: the defects with their evidence and leads, the documents to re-read, and the rules of engagement. Fix the documentation, never the code — the repo is the authority. Open both files before editing; a finding is a hypothesis. Say what you left alone, and why.
Then:
docgarden garden --open-prThree guardrails, in order of how much damage they prevent:
- Only files matching the doc globs are ever staged. A pass that wandered into source gets its source edits left in the working tree and the run reported as refused.
- One branch, reused. A gardener that opens a new pull request every week is a gardener somebody mutes in three weeks. It updates its open PR instead.
- It never edits its own config or baseline. Landing refuses those outright.
Two schedules ship alongside it, and they are genuinely different trades:
docgarden-gardener.yml |
gardener.cron |
|
|---|---|---|
| Runs on | GitHub Actions, weekly | your machine, weekly |
| Needs | an ANTHROPIC_API_KEY secret |
the Claude Code you are already logged into |
| Costs | per run | nothing |
| Works when your laptop is shut | yes | no |
Or run neither, and use /docgarden in a conversation when you feel like it —
which is free, and the only version where you watch it work.
plugins/docgarden/skills/docgarden/templates/docgarden.yml is one scan with three outputs: annotations inline on
the diff, a summary on the run page, and the full report as a downloadable
artifact.
- name: Check the knowledge base
run: |
docgarden check \
--format github \
--summary "$GITHUB_STEP_SUMMARY" \
--html docgarden-report.htmlThere is a pre-commit hook in the same directory too, running only the three rules that need
nothing but the file itself, so it costs a second rather than a scan.
The report is one self-contained HTML file. No fonts, no scripts, no images
fetched from anywhere — the findings are embedded as JSON and the page renders
itself from them, which is what lets the table sort on any column and filter by
rule without a round trip. It opens from a file:// URL, survives being
emailed, and looks the same in a CI artifact as on your laptop.
It follows your system theme.
docgarden init # read the repo, write a config
docgarden init --print # just show me what you inferred
docgarden check # run the linters
docgarden check --html report.html --open # …and open the report
docgarden check --rule links --rule commands
docgarden check --strict # warnings fail too
docgarden check --update-baseline # record today's backlog
docgarden garden --max 8 # a worklist for an agent
docgarden garden --open-pr # commit docs, open or update the PR
docgarden explain # the ten rules
docgarden explain coderefs # one rule, and why it exists
docgarden demo # the bundled example repoExit codes: 0 clean, 1 new findings at error, 2 the tool itself failed.
A crashed linter must never look like a clean repo.
- It finds shapes, not meaning. No deterministic rule can tell that a
paragraph describes behaviour the code no longer has.
stalenessis the closest thing and it only ranks where to look. Reading the prose is the gardener's job, and the gardener is a language model, with everything that implies. coderefsandcommandstrade recall for precision on purpose. Bare filenames and extensionless paths are skipped by default. Turning them on produced several thousand false positives on the first real repository this ran against — a git branch calledovernight/todaylooks exactly like a directory.[rules.coderefs] bare_names = trueif you want them.stalenessneeds real git history. A shallow clone has none, and the rule finds nothing rather than pretending. Usefetch-depth: 0.
python3 -m pytest tests/ -q92 of them. Each rule test builds a throwaway repository with exactly one planted defect and asserts the rule finds that one and nothing else — on disk, with a real walk, because three of the first four bugs in this tool were in the walk and the glob and none would have been visible to a test that handed the rules a pre-made file list.
docgarden also runs against its own documentation, in its own CI.
MIT.

