doc-gardening · git:20260824.ed37043 · 2026-08-24 · sha256 a1889b84d1feef65

doc-gardening git:20260824.ed37043A

Immutable. This exact content never changes and is served at /api/v1/blob/a1889b84d1feef65.

---
name: doc-gardening
description: >
  Maintain the per-package documentation graph (CLAUDE.md/AGENTS.md) and the
  architecture roll-up (docs/architecture/) after code changes. Use when a new
  package was created, types/actor messages/dependencies changed significantly,
  after a large refactor, or when make doc-check fails.
argument-hint: "[package or 'all']"
allowed-tools: Read, Grep, Glob, Bash(make doc-check), Bash(git diff *), Bash(git log *), Bash(find *), Bash(cp *), Bash(ls *), Bash(awk *)
---

# Doc Gardening

Maintain Loom's per-package documentation graph after code changes. This
skill handles both **generating docs for a new package** and **updating
stale docs** for an existing one.

`$ARGUMENTS` is a package name (`core`, `broker`, `sandbox`) or `all` /
empty for a whole-repo sweep. Packages live at `packages/<name>/`; the
Gleam ones have `src/<name>/*.gleam`, and `sandbox` and `tui` are Go
modules.

Accuracy beats coverage. These files are read by humans and by agents
mid-task, so a wrong dependency edge or an invented invariant is worse
than a missing section. Read the actual source; never infer from a file
name.

## Phase 1: Detect what needs attention

**Scoped run** — read the package's `src` modules and its existing
`CLAUDE.md`, and decide new versus stale.

**Full run** (`all` or no argument):

```bash
# Gleam packages with source but no CLAUDE.md
find packages -mindepth 1 -maxdepth 1 -type d | while read d; do
  ls "$d"/src/*/*.gleam >/dev/null 2>&1 || continue
  [ -f "$d/CLAUDE.md" ] || echo "MISSING: $d"
done

# Go modules (sandbox, tui) with source but no CLAUDE.md
find packages -name go.mod -not -path '*/build/*' | while read m; do
  d=$(dirname "$m")
  find "$d" -name '*.go' | grep -q . || continue
  [ -f "$d/CLAUDE.md" ] || echo "MISSING: $d"
done

# Packages touched on this branch
git diff --name-only main...HEAD -- '*.gleam' '*.go' \
  | sed 's|^packages/\([^/]*\)/.*|\1|' | sort -u
```

Classify each package:

- **New** — has `src` modules but no `CLAUDE.md`.
- **Stale** — has `CLAUDE.md`, but `src/` has a newer last commit
  (`make doc-check` prints these as warnings; see Phase 6).
- **Scaffold** — the package directory exists but `src` is empty
  (`events`, `client`, `tui` at various times). Write nothing, or a
  two-line stub explicitly marked as scaffold. Do not describe a package
  from its `gleam.toml` alone.

## Phase 2: Read the package's sources

Gleam has no `go doc`, and `gleam docs build` requires a compile that may
race other agents — **reading source is primary**. For each package
gather:

1. **Module docs** (`////` at the top of each `src/**/*.gleam`). This
   repo's module docs were written to state contracts and invariants; they
   are the richest material available and usually carry the load-bearing
   half of the CLAUDE.md.
2. **Public declarations** — `pub type`, `pub opaque type`, `pub fn`,
   `pub const`, plus the `///` doc comments on them and on individual
   constructors.
3. **Imports** — these are the real dependency edges. `gleam.toml`'s
   `[dependencies]` gives the declared edges; cross-check both against the
   spec's DAG (`docs/loom-implementation-spec.md` §0.1), and report — do
   not silently "fix" — any divergence.
4. **FFI modules** — `src/*/internal/ffi_*.gleam`. Every `@external` in
   the package is confined there (spec §0.2), so the list of FFI modules
   is the list of the package's impurities. Always name them.
5. **Go packages** — the `// Package x ...` doc comment above `package x`,
   and the doc comments above exported types.

A fast sweep that gets module docs, imports, and public surface at once:

```bash
for f in packages/<pkg>/src/<pkg>/**/*.gleam; do
  echo "### $f"
  awk '/^\/\/\/\//{print;next} /^import /{print} /^pub (type|opaque|fn|const)/{print}' "$f"
done
```

Then read whole modules for anything the sweep leaves unclear.

## Phase 3: Trace the traffic

The template's traffic section is where a per-package doc earns its keep.
Loom has three kinds, and each wants **concrete Gleam type names**, not
prose:

**Actor messages.** Find `pub opaque type Message` / `Msg` / `PoolMsg` and
list one line per variant family; a variant with a `reply: Subject(...)`
field is a call, one without is a cast. Then find the *senders*:

```bash
grep -rn 'process\.send\|process\.call\|actor\.send\|actor\.call' packages/*/src
```

Name both ends — `runtime/writer.Commit(tx, reply)` sent by
`runtime/strand_runtime` and `runtime/api`, not "sends commits to the
writer".

**Durable-store traffic.** Almost every package's real interface is what
it commits and what registers it reads. Record:

- the `core/tx.Write` variants it builds (`InsertEntry`, `InsertUsage`,
  `SetRegister`, `DeleteRegister`) and through which path
  (`runtime/writer.commit`, `session` boot seeding, ...);
- the register namespaces it reads or writes, by their wire names —
  `strand.leaf`, `strand.config`, `strand.state`, `strand.last_result`,
  `op.meta`, `op.state`, `op.tool_args`, `op.preparation`,
  `pending.entry`, `fact.name`, `fact.label`, `fact.custom`;
- the CAS expectations it relies on (`SeqExpectation`), since those are
  the concurrency contract.

**Wire boundaries.** Name the frame kinds or event types crossing them:
the effect-plane `broker/framing.Frame` kinds (`hello`, `exec_start`,
`exec_stdin`, `exec_out`, `exec_exit`, `cap_call`, `cap_result`,
`cancel`, `heartbeat`, `error`), the provider's
`provider/stream.StreamEvent` (`Delta`, `Settled`, `Failed`) and its SSE
event names, `provider/http.HttpEvent`.

## Phase 4: Generate or update CLAUDE.md

Follow [template.md](template.md).

- **New package** — write from scratch against the template.
- **Stale package** — read the existing file, diff it against current
  source, and change only what actually moved. Do not rewrite prose that
  is still true; the file's stability is what makes its history readable.

Never name an assistant, an AI, or an authoring tool in generated
content. The file is documentation of the package, nothing else.

## Phase 5: Mirror to AGENTS.md

Every `CLAUDE.md` has a byte-identical `AGENTS.md` beside it:

```bash
cp packages/<pkg>/CLAUDE.md packages/<pkg>/AGENTS.md
```

Copy, never hand-edit the mirror — `make doc-check` compares them byte
for byte.

## Phase 6: Architecture roll-up

Check whether the change belongs in the as-built docs:

- `docs/architecture/{durability,orchestration,effects,simulation}.md`
  each end with a **Where the code lives** table. A new or renamed module
  belongs in the right one.
- A newly recorded interpretation of the spec belongs in
  `docs/spec-gaps.md`, and a frozen-interface change in
  `protocol-change/NNN.md` — never a silent edit to a per-package doc.
- Root `CLAUDE.md` keeps only the one **Per-package docs** pointer; the
  detail lives in the graph.

## Phase 7: Validate

```bash
make doc-check
```

`scripts/doc_check.sh` enforces three things:

1. **Coverage** — every package with `src` modules (Gleam or Go) has a
   `CLAUDE.md`. Failure.
2. **Mirror** — `AGENTS.md` exists and is byte-identical to its
   `CLAUDE.md`. Failure.
3. **Staleness** — the last commit touching `src/` is newer than the last
   commit touching `CLAUDE.md`. **Warning, not failure**: source moves
   faster than prose by design, and a warning is the queue this skill
   works from. Mtimes are meaningless in a fresh git checkout, so the
   comparison uses `git log -1 --format=%ct` on each path.

The script runs no builds and needs no toolchain, so it stays usable
while other work is compiling.

Fix every error before finishing, and report what was created, updated,
and left warning.

## Notes

- Skip `build/` entirely — it holds vendored dependency sources.
- Prefer the module doc's own words for an invariant; they were written
  to state one.
- Invariants are things that break if violated. "Hashline plans are
  digest-bound; re-apply always rejects" is an invariant. "Uses `Result`
  for errors" is not.
- For CI: `make doc-check` is the check-mode gate (coverage + mirror);
  the staleness warnings are the work queue for a `/doc-gardening all`
  pass, which commits its updates under a `docs:` prefix.