agentleFS
musterd-iomusterd-io/musterd/.agents/skills/docs-that-catch-drift/SKILL.md

docs-that-catch-drift

Skill1 starsChanged 9 days agoMITView on GitHub

Docs go stale for four specific reasons, and each has a structural fix — one doc one job, one fact one home, and "checker, not generator" — enforce the structure where it is mechanical and refuse to produce the prose where it is judgement. Ships a drift-checker for documented file trees with deliberately no --fix. Use when adopting docs conventions, when a doc has accumulated strikethroughs, or before writing a tool that generates documentation.

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    2. Hand-narrating status that is derivable: If it can be read off the decision records, the tags, or the test count, writing it down by …

    line 25
  2. Distinctive:

    When a document contains both mechanical structure and load-bearing judgement — which is most useful documents — you have three options, …

    line 51
  3. Distinctive:

    A generated section anchored to something outside itself: If you do generate — a roadmap from a typed data module, say — then check the …

    line 93
  4. Distinctive:

    Writer and checker read the same list: If a tool writes a scoped set of files and another verifies that scope, they import the same …

    line 105

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

docs-that-catch-drift

Note the name. Docs can drift — a checker only narrows where. Anything promising documentation that cannot go stale is selling you something, and the honest version is more useful anyway.

Four things make docs rot, and each has a structural fix rather than a discipline fix.

The four anti-patterns

1. One doc doing several jobs at once. Status, findings, roadmap and index in one file. Its parts have different lifecycles, so it can never be rewritten — only appended to. That is why such documents accumulate strikethroughs instead of being replaced.

One doc, one job, one lifecycle. Then a doc whose job is finished can be rewritten wholesale without anyone losing anything.

2. Hand-narrating status that is derivable. If it can be read off the decision records, the tags, or the test count, writing it down by hand creates a second copy that will disagree with the first.

3. Re-narrating a decision somewhere a record already holds it. Link, don't restate.

4. Two specs hand-synced. A "live" one and a "draft" one, kept in step by somebody remembering. There is one spec; unreleased work is an appendix inside it.

Underneath all four is a single rule:

One fact has one home. Everything else points at it.

Duplication is what drifts. A pointer cannot disagree with its target.

And the commit-level version, which is the one that actually holds the line: docs and code never disagree at the end of a commit. A behaviour change updates its doc in the same commit — not the next one. A commit that changes behaviour and leaves its doc stale has shipped a lie with a true diff attached.

Checker, not generator

This is the part worth taking away even if you adopt nothing else.

When a document contains both mechanical structure and load-bearing judgement — which is most useful documents — you have three options, and only one is good:

What happens
Check nothingit drifts, silently, and you find out from a reader
Generate itthe judgement half gets written by a machine that has none
Check the structure, refuse to write the prosea new file fails until a human says what it is

The middle option is the tempting one and the damaging one. A generated description reads as documentation and says nothing, which is strictly worse than an empty section: an empty section invites a reader, and a filled one stops them looking.

So: enforce the set, refuse to author the sentence.

./tree-check.py <doc-or-dir>... [--root DIR] [--ignore SUFFIX]...

A doc carries a block like this, and the checker verifies the file set against the filesystem:

## File tree `src/`

```
index.ts     — the entry point; wires the server and exits non-zero on a bad config
store.ts     — every read and write of the database goes through here
sub/util.ts  — string helpers shared by both of the above
```

It refuses a file that exists and is undocumented, an entry whose file is gone, a tree pointing at a directory that does not exist, and an entry with no description — because the description is the whole point and nothing will write it for you.

There is deliberately no --fix.

Four more instruments, and the rule each one taught

A generated section anchored to something outside itself. If you do generate — a roadmap from a typed data module, say — then check the generated output against a signal the generator cannot see, like git history. A generator checked only against its own input is a tautology with a build step.

Duplicate names, never descriptions. Where two surfaces must both mention a command, let the second duplicate the name only, and check that every name it mentions still resolves. One-directional on purpose: a rename then breaks the build, and a semantic change under a stable name does not — which is a real gap, so cover the distinctions with a test each rather than pretending the name check caught it.

Writer and checker read the same list. If a tool writes a scoped set of files and another verifies that scope, they import the same exported list. Two copies of a scope definition is the same doc-drift problem with a compile step.

Measure a denylist's recall; do not assume it. A hand-kept pattern list is a floor, not an inventory. Label a corpus, measure what share the list actually catches, and print that number on every run — so a green result says no match in a known shape, never no problem. Ours reads well under half, and saying so is what stops the gate being trusted for more than it does.

Mentions are not uses

The trap that sinks a text gate, and it has a sharp edge.

A document that defines a rule must write the rule down in order to forbid it. A style gate whose own rulebook enumerates the banned sentences will fail its own rulebook — and the only ways out are to suppress the canonical list or to paraphrase the bans until they stop being quotable. Both destroy the thing being protected.

So strip mentions before matching: fenced code, comments, and — the case that matters — backticked and double-quoted spans.

A gate that fails when you document it teaches you to document it less clearly. That is a gate actively making your docs worse.

Abstain rather than pass

tree-check.py counts code fences before it reads anything. An odd number means it cannot tell code from prose in that file, so it abstains at exit 2 rather than reporting a pass.

This is not hypothetical. A sibling gate once met a document with a single stray fence on line 1; its fence-pair stripping swallowed everything to the next fence, including the paragraph the gate existed to police. Every mutation planted in that region reported green.

A gate that silently skips the most important surface it has is worse than no gate, because it is trusted.

Two more abstentions in the same spirit: a file that cannot be read, and a run that found no blocks at all, which reports "nothing was checked — that is not a pass" rather than a cheerful zero.

The falsifier

Nine cases exercised on 2026-09-21 against a real directory tree:

CaseExpected
doc and tree agreeexit 0
a file exists and is undocumentedexit 1, naming it
a documented file no longer existsexit 1, "remove the line, or restore the file"
an entry with no descriptionexit 1, "nothing will write it for you"
the tree names a directory that is not thereexit 1, "the stalest kind"
odd number of code fencesexit 2, abstains — not a pass, not a finding
a doc with no tree block at allexit 2, "nothing was checked — that is not a pass"
a *.test.ts file, undocumentedexit 0 — ignored by default
the same file with --ignore overriddenexit 1 — the ignore list is yours
a tree heading inside a fenced exampleexit 0 — a mention, not a use
a target that does not existexit 2

The bug this shipped with, which is this page's own subject

The first version refused its own SKILL.md. The ## File tree heading in the example above sits inside a fenced block — it is documentation of the format, not an instance of it — and the checker read it as a real tree, then complained that src/ was not a directory.

That is the mentions-are-not-uses trap, committed inside the file that warns about it, by the person writing the warning. It is also the failure mode the section above names exactly: a gate that fails when you document it. Had it shipped, the first thing any adopter would have learned is to stop putting examples in their docs.

The fix masks fenced regions before scanning for headings, and fences nest by length — a ```` block may contain ``` blocks — so a fence closes only on one at least as long as the one that opened it.

The cheap way to find this: run the checker against its own documentation. Any text gate whose own docs must quote the thing it matches has this bug until proven otherwise, and its own page is the shortest path to proving it.

One cosmetic defect from the same run: a declared path with a trailing slash produced src//new.ts in the message. Harmless, fixed, and worth the line — a message a reader has to squint at is the beginning of a gate people stop reading.


From the musterd team — Your agents, on a team with you. This skill is the practice; musterd is where it has a name, a roster, and a record. This one travels whole: it is files, a convention, and one script. musterd.io.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.