Doc graph
A per-package documentation graph is a CLAUDE.md beside every package
that has source, denser and more current than the root file about that
package, mirrored byte-for-byte to AGENTS.md so every agent reads the
same thing. Two mechanisms keep it true: a gate script that costs nothing
to run and says exactly which file is missing, mismatched or stale, and a
gardening skill that reads source and rewrites only what moved. Loom
runs this at make doc-check; this skill installs the same arrangement
elsewhere, adapted to the repo rather than copied.
The gate is scripts/doc_check.sh in this skill; it is POSIX sh and
awk, runs no build, and is configured by a block of variables at its
top. init rewrites that block. references/checks.md explains each
check and why its tier is what it is; read it before changing a tier.
init: install the graph in a repo
Everything below is one pass. Do the detection, ask the one question, write the files, run the gate, report.
1. Detect the layout
Find what a "package" is here and what makes it real:
ls; ls packages internal pkg cmd crates apps libs 2>/dev/null
find . -name go.mod -o -name Cargo.toml -o -name gleam.toml -o -name package.json -o -name pyproject.toml | grep -v node_modules | head -30
Map the result to the config block:
| Repo shape | PACKAGE_DIRS |
SOURCE_EXTS |
|---|---|---|
Go module with internal/ and cmd/ |
internal/* cmd/* (add pkg/* if present) |
go |
| Go multi-module | the directories holding a go.mod |
go |
| Cargo workspace | crates/* |
rs |
| Gleam / Erlang umbrella | packages/* |
gleam erl |
| JS / TS monorepo | packages/* apps/* |
ts tsx js |
| Python monorepo | packages/* src/* |
py |
A Go module whose packages are one level deep under internal/ is the
common case and the one Loom's packages/* glob does not cover; the
port supports several globs, space-separated. If a directory tree is
deeper than one level, prefer listing the parent globs over a recursive
walk: a package the gate checks is a package that gets a doc, and docs
per leaf directory in a deep tree is noise.
CITE_EXTS is the wider set: every file kind a doc under docs/ may
cite by line. Include md, sh, sql, toml, yml alongside the
source extensions. PRUNE_DIRS defaults to the usual build and vendor
directories; add any repo-specific one.
2. Ask what traffic means here
The template's Traffic section is where a per-package doc earns its keep,
and its vocabulary is repo-specific. Loom's three kinds are actor
messages, durable-store commits and wire frames. A Go service might have
gRPC methods, database tables and channels; a Rust crate might have
traits implemented, Send boundaries and FFI. Ask one
AskUserQuestion, multi-select, seeded from what the detection found
(grep -rl 'grpc\|chan \|sqlx\|tokio::' | head), with an "Other" for the
rest. Two or three kinds is right; more than four means the section will
be padded.
Also confirm the doc and mirror names. CLAUDE.md plus AGENTS.md is
the default; a repo that reads only one can set MIRROR_NAME empty and
skip the mirror check.
3. Write the files
scripts/doc_check.sh— copy from this skill'sscripts/and rewrite the configuration block's defaults with the detected values. SetGARDEN_HINTto the command that runs the installed gardening skill.chmod +x.make doc-check— appendassets/Makefile-target.mkto the Makefile, or the equivalentjust/npm script/cargo xtaskentry if the repo uses one; match whatmake helpor the README says the house command runner is.- CI — add
assets/ci-job.yml's step to the workflow that runs onmain. Loom keeps it in the nightly cold gate rather than the per-push check, because staleness is a queue, not a failure; follow that unless the user says otherwise. .claude/skills/doc-gardening/— renderassets/doc-gardening/ SKILL.mdandtemplate.mdinto the repo, replacing every{{PLACEHOLDER}}. The placeholders and what fills them are listed inassets/doc-gardening/PLACEHOLDERS.md. The Traffic kinds from step 2 become the template's Traffic bullets and the SKILL's Phase 3; a kind with no grep pattern is a kind the skill cannot trace, so give each one a command.- Root
CLAUDE.md— add the Per-package docs section fromassets/claude-md-section.md. Keep it to that pointer; the detail lives in the graph.
4. Run the gate and report
make doc-check; echo "exit=$?"
On a fresh install every real package errors as missing its doc. That is
the correct output: the list is the queue. Do not write the docs in the
same pass unless the repo is small (under five packages); for anything
larger, report the queue and let the user run garden all as its own
body of work, since each doc wants the source read and that is the
expensive part. Commit the install on its own under build: or ci:,
and the first docs under docs:.
check
Run the installed gate and read it:
make doc-check; echo "exit=$?"
Errors are the fix-now list. Warnings split into staleness (the
gardening queue) and citation drift (a doc names a line the tree has
moved; fix the citation or the sentence). DOC_CHECK_CITE_LIMIT=0 lists
every warning; DOC_CHECK_CITE_WINDOW=N widens the symbol search.
garden <package|all>
Invoke the repo's installed skill (/doc-gardening <package>), which
carries the repo's own vocabulary. If the repo has no installed skill but
does have the gate, run init step 3.4 first. The phases are the same
everywhere and are in references/gardening.md: detect what needs
attention, read the source (never infer from a filename), trace the
traffic with concrete type names, generate or update against the
template changing only what moved, mirror with cp, roll up into the
architecture docs, validate with the gate.