cleat is a Claude Code plugin that routes a repository's agent instructions to
the widest reader that can act on them. Guidance lives in AGENTS.md, read by
Claude Code and the ~30 other tools, with CLAUDE.md reduced to a pointer at it.
Scoping metadata may be tool-specific: .claude/rules/ holds topic rules that
only Claude Code loads, and loads on demand, so long as AGENTS.md indexes them
— a tool that cannot load a rule can still find it. Enforcement is hooks only: a
write gate, an in-turn repair nudge, and bootstrap guidance for repos where the
convention is absent.
Requirements use EARS syntax — each is one of:
Ubiquitous (The <system> shall …), State-Driven (While …), Event-Driven
(When …), Optional (Where …), or Unwanted Behaviour (If … then …).
AGENTS.md— the tool-agnostic agent instruction file, read by roughly 30 coding tools. In the target shape it is the canonical location: guidance lives here, and what a rules directory holds instead is indexed from here.CLAUDE.md— Claude Code's own instruction file, and the one it always loads. Since v2.1.277 Claude Code also readsAGENTS.mddirectly, but only where noCLAUDE.mdorCLAUDE.local.mdsits in the working directory or above it, and not in every session: not before v2.1.277 (before v2.1.281 on Bedrock or with telemetry disabled), not with the built-inagents-mdplugin disabled, and not in the first session after an upgrade. The pointer shape is what makes the load unconditional across all of them.- Pointer shape — a
CLAUDE.mdwhose body is the@AGENTS.mdref, optionally followed by Claude-specific guidance (slash commands, hooks,settings.json). An import rather than a symlink, becauseCLAUDE.mdhas a legitimate use beyond pointing. - Ref — the
@AGENTS.mdimport line insideCLAUDE.md. Its presence is what makes the pointer loadAGENTS.md; a link alone does not. - Substantive content — a file body that is more than whitespace and more
than the pointer shape. The gate acts on substantive
CLAUDE.mdcontent only. - Shadowed — an
AGENTS.mdbelow the repository root with noCLAUDE.mdbeside it, under a directory holdingCLAUDE.md,.claude/CLAUDE.md, orCLAUDE.local.md. Claude Code readsAGENTS.mddirectly only where none of those sits at or above the working directory, so it never reads a shadowed file. ACLAUDE.mdpointer beside it is what loads it, when Claude Code reads, writes, or edits a file in that directory. - Inversion — the reverse of the target shape:
AGENTS.mdis a stub pointing back atCLAUDE.md, so every non-Claude tool gets the stub. - Foreign config — a single-tool instruction file (
.cursorrules,.cursor/rules/*,.github/copilot-instructions.md,.windsurfrules,GEMINI.md) holding guidance every other tool ignores..claude/rules/is not one: it is the location cleat recommends for topic rules, because Claude Code loads a path-scoped rule only when it reads, writes, or edits a matching file, and no import mechanism does that — an@pathimport loads at launch whatever it costs. - Topic rule — a markdown file under
.claude/rules/, discovered recursively. Always-on when it carries nopaths:frontmatter, path-scoped when its globs can match a file, retired when every glob it carries can match nothing. - Rules index — the rows of
AGENTS.mdthat name topic rules and the globs they answer to. It is what carries a path-scoped rule to a tool that cannot load one. Every reader pays for it at launch, in every session, so it indexes the path-scoped rules and covers the always-on ones with a single line: a tool that cannot scope must read all of those anyway, and their names buy it nothing. - Index budget — 2048 bytes, the most the rules index may cost every reader at launch. Past it, the index competes with the guidance it points at.
- Finding — one code, the path it concerns, a severity, and the repair that
clears it. The codes are
no-agents,dangling-ref,no-claude,no-ref,inverted,duplicated,unguided,shadowed,foreign-config,unlisted-rule,dead-rule-listed,toc-bloat,bad-glob,restated, andnarrowing-ref. - Severity — a finding is either an error (the shape is broken; drives
the exit code) or an advisory (reported, but a repo can legitimately sit
this way).
unguidedand heading-only duplication are advisories: a repo may have chosen to carry no agent guidance, and a shared## Commandsheading in both files is not itself a defect. - Re-issue record — the receipt a deny leaves behind:
sha256(session_id, path, content)under the plugin's data dir. An identical re-issue of the denied write matches it and is allowed through once. - Prefilter — the pure-bash script fronting every hook registration. It reads the payload on stdin and exits unless the payload looks like a candidate, so the common path never starts a python interpreter.
- Rubric — the file defining the tool-agnostic / Claude-specific split. A file rather than a skill, so it costs nothing until a deny names its path.
- Audience tier — who a file is written for, decided by its path. End
user: anything under
docs/. Tool-specific:CLAUDE.md,CLAUDE.local.md, everySKILL.md, a foreign config, and.claude/outside.claude/rules/. Repo: every other file, read by anyone changing the repository —README.md,CONTRIBUTING.md,AGENTS.md, topic rules, the spec. Tiers widen from tool-specific to repo to end user. - Reference direction — a file may link to a file in its own tier or a wider
one, never a narrower one.
CLAUDE.mdlinks toAGENTS.md;AGENTS.mdandREADME.mdlink todocs/;docs/links only withindocs/and off the repository, so end-user documentation stands alone. - Documentation set — the markdown files cleat compares for copied content:
every
README.md,CONTRIBUTING.md,AGENTS.md,CLAUDE.md,SKILL.md, topic rules, and markdown underdocs/, less any file git ignores or markslinguist-generated. Either is a generated projection of its source, identical by construction. - Placement tree — the questions that decide where new content goes: already
written somewhere, link to it; for end users,
docs/; for anyone changing the repo,README.mdor its peers; for the agent, a hook or linter where one can decide it from a single tool call's inputs, elseAGENTS.mdorCLAUDE.mdwhere every session needs it, else a nestedAGENTS.mdand pointer where it is bound to one directory, else a topic rule, skill, or guide. - Placement guide — the file carrying the placement tree, the audience tiers, and the reference direction. Like the rubric, a file named by path.
The check command
The system shall provide cleat check [DIR], reporting findings for DIR and
defaulting to the current directory.
cleat check shall exit 1 when it reports one or more error findings and 0 when
it reports none, so advisories alone leave the check green.
Where --json is passed, cleat check shall emit its findings as JSON on
stdout.
When CLAUDE.md has substantive content and no AGENTS.md sibling exists, the
check shall report no-agents.
When AGENTS.md exists and no CLAUDE.md exists, the check shall report
no-claude as an advisory. Claude Code reads the AGENTS.md by default, so the
repo is guided; what the missing pointer costs is the sessions where direct
reading is off — an older version, the built-in agents-md plugin disabled,
the first session after an upgrade — plus /memory and /context listing and
InstructionsLoaded hooks, none of which fire for an AGENTS.md read directly.
That is a portability gap, not an unguided repo, and the two are worth telling
apart.
When both files exist and CLAUDE.md carries no @AGENTS.md import line, the
check shall report no-ref, unless it reports inverted, whose repair adds the
ref.
When AGENTS.md points at CLAUDE.md, the check shall report inverted.
When the two files overlap in content, the check shall report duplicated.
When neither file exists, the check shall report unguided.
When a foreign config is present, the check shall report foreign-config naming
that path.
The check shall report duplication in two tiers: identical normalized headings present in both files as advisory, and identical normalized non-heading lines of 40 or more characters as the substantive signal.
unguided shall be reported by check only, since no write hook has a write
to act on.
Retired 2026-09-25: NUDGE-05 gives the write hooks a write in an unguided repo, and the nudge reports whatever the check finds. The ID is not reused.
Each finding shall carry the repair that clears it: create the pointer, add the
ref, flip the inversion, delete the duplicated content from CLAUDE.md, replace
a restated copy with a link or an include, remove a link that points at a narrower tier,
or add the pointer beside a shadowed AGENTS.md.
Each finding shall carry a severity. no-agents, dangling-ref, no-ref,
inverted, shadowed, foreign-config, unlisted-rule, dead-rule-listed,
bad-glob, narrowing-ref, and line-level duplicated shall be errors; no-claude,
unguided, toc-bloat, restated, and heading-only duplicated shall be
advisories.
When CLAUDE.md is only the pointer shape and no AGENTS.md exists, the check
shall report dangling-ref, whose repair is to write AGENTS.md.
If DIR is not a directory, or a file the check needs cannot be read, then
cleat check and cleat index shall exit 2 and name the cause on stderr.
When two files in the documentation set share a run of three or more
consecutive identical normalized non-heading lines, the check shall report
restated naming both paths and the shared lines. A single shared line — a
pitch, a title — is not a run. A run held by more than two files is a template
every page starts from, not a copy, and two files both under docs/ are the
site's own structure; neither is reported. Where the two files sit in different
tiers, the
repair shall keep the copy in the wider tier and link to or include it from the
narrower; where they share a tier, the repair shall name both paths and leave
the choice to the reader.
When a relative markdown link outside a fenced code block resolves to a file in
a narrower audience tier than the file holding the link, the check shall report
narrowing-ref naming both paths, unless the same pair is already reported as
inverted.
When DIR holds a shadowed AGENTS.md, the check shall report shadowed, naming
the nearest file above that shadows it, in place of no-claude.
The check shall report shadowed for each shadowed AGENTS.md below DIR,
naming its path relative to DIR.
Topic rules and the index
The system shall discover topic rules as the *.md files under
.claude/rules/, searching recursively.
The system shall classify each topic rule from its paths: frontmatter:
always-on where the field is absent, retired where every glob it lists can match
nothing, and path-scoped otherwise.
When a path-scoped rule's filename appears nowhere in AGENTS.md, the check
shall report unlisted-rule, naming that rule.
When a retired rule's filename appears in AGENTS.md, the check shall report
dead-rule-listed, naming that rule.
When the AGENTS.md lines naming topic rules exceed the index budget in total,
or name more than half the always-on rules, the check shall report toc-bloat.
Where the projector's own index exceeds the budget, the repair shall be to
consolidate path-scoped rules into fewer glob sets.
The index shall be measured as the AGENTS.md lines that name a topic rule, so
no heading convention is imposed on AGENTS.md.
Where the rules directory resolves under $HOME/.claude, the system shall report
no rules findings, user-scope rules having no AGENTS.md to be indexed from.
Where .claude/rules/ is absent or holds no rule, the system shall report no
rules findings, so the convention costs a repo that has not adopted it nothing.
Where AGENTS.md is absent, the system shall report no rules findings, the
missing pointer being the prior repair.
The system shall classify topic rules without following directory symlinks, so a symlink cycle cannot stall the check or a hook.
If a topic rule's glob is absolute, climbs out of the repo with .., or expands
to more than 64 patterns, then the check shall report bad-glob naming the rule
and the glob, and shall not classify that rule.
Where a .claude/rules/ directory sits below the repository root, the system
shall classify its rules with globs relative to the directory that owns it and
index them from that directory's AGENTS.md, which is how Claude Code anchors a
nested rule's globs.
The index projector
The system shall provide cleat index [DIR], printing the rules index
AGENTS.md should carry for DIR and defaulting to the current directory.
The projector shall render the always-on rules as one line, the path-scoped rules as a row per distinct glob set, and the retired rules not at all.
The projector shall write no file, the placement of the index within
AGENTS.md's prose being the reader's judgment rather than the check's fact.
The index the projector prints shall clear unlisted-rule and
dead-rule-listed for the directory it was rendered from, and shall clear
toc-bloat unless its own rows exceed the index budget.
The projector shall run under the same constraints as the rest of the CLI — python3, standard library only — so a repo adopting the convention needs nothing installed to keep its index current.
When the nudge reports unlisted-rule, dead-rule-listed, or toc-bloat, it
shall carry the rendered index as additionalContext, except where the rendered
index itself exceeds the index budget, composing the index by hand being where
it drifts from the directory.
Hook dispatch
cleat hook shall read the hook payload from stdin and dispatch on
hook_event_name.
cleat hook shall always exit 0, whatever it decides.
cleat hook shall emit its decision as hookSpecificOutput.
When the tool is Write, the hook shall judge tool_input.content.
When the tool is Edit, the hook shall judge the on-disk file with old_string
replaced by new_string.
The plugin shall register exactly three hooks: PreToolUse on Write|Edit,
PostToolUse on Write|Edit, and PostToolUse on Read.
If the payload does not parse, or deciding it fails, then cleat hook shall
report the failure as a systemMessage and make no permission decision.
If the target path is under $HOME/.claude, then the nudge and the bootstrap
shall stay silent, user-scope memory being Claude-specific by nature.
Every deny a hook issues shall be overridable for the rest of the session that received it: an identical re-issue passes, and exempts that path from the gate until the session ends. A later session that brings the same file into scope shall be asked afresh.
The write gate
The gate shall consider a write in scope when the target's basename is
CLAUDE.md.
If the target is under $HOME/.claude, then the gate shall allow it untouched,
user-scope memory being Claude-specific by nature.
If the target is CLAUDE.local.md, then the gate shall allow it untouched.
When a sibling AGENTS.md has substantive content, the gate shall allow the
write silently.
When the written content is empty or already the pointer shape, the gate shall allow the write, the dangling ref being the nudge's concern.
Otherwise the gate shall deny the write with a reason carrying why, the
instruction to re-issue as two writes — tool-agnostic body to AGENTS.md,
CLAUDE.md as the pointer plus any Claude-specific remainder — the short-form
rubric, and the rubric file's path.
When the gate denies a write, it shall record a receipt keyed to the session, the path, and the exact content, under the plugin's data dir.
When a write matches an existing re-issue record, the gate shall allow it and clear the record.
The re-issue record shall be scoped to the session that produced the deny, so a stale record cannot pass a later write.
The repair nudge
When a write to CLAUDE.md or AGENTS.md completes, the nudge shall run the
check against that directory.
The nudge shall return the findings and their repairs as additionalContext, so
the shape can be corrected in the same turn.
The nudge shall treat AGENTS.md as canonical, directing every duplicated
repair at CLAUDE.md.
If the finding set is unchanged from the last nudge in this session, then the nudge shall stay silent, so a finding the model will not clear cannot loop.
When a write under .claude/rules/ completes, the nudge shall run the check
against the directory that owns the rules directory rather than the rule's own
parent, a new rule being unindexed by construction.
When a write to a file in the documentation set other than CLAUDE.md or
AGENTS.md completes, the nudge shall run the check against the repository
root, so restated and narrowing-ref surface in the turn that caused them.
The nudge shall report a given finding at most once while it persists. A finding that clears is forgotten, so its return later in the session is reported afresh.
Where new guidance goes
When a Write or Edit adds substantive content to CLAUDE.md, AGENTS.md,
or a topic rule, the gate shall deny it with the placement tree's agent branch:
whether a hook or linter can decide the constraint from a single tool call's own
inputs, and whether every session needs it.
When the addition targets CLAUDE.md or AGENTS.md, the reason shall also ask
whether the content belongs in its own topic rule, path-scoped where it is bound
to a file type or tree, or in a nested AGENTS.md and pointer where it is bound
to one directory.
When the added content restates a run of lines already in another file of the documentation set, the reason shall name the file holding the existing copy.
The placement ask shall fire at most once per path per session, and an identical
re-issue of the denied write shall pass on the GATE-07 receipt, which
GATE-08 then clears.
When a write trips both GATE-06 and a placement ask, the gate shall issue a
single deny carrying every reason.
If a write only removes content or reshapes the pointer, or its target is under
$HOME/.claude or is CLAUDE.local.md, then the placement ask shall leave it
untouched.
Where extra rules directories are configured — git config --add cleat.rulesDir <repo-relative dir>, repeatable — a write of a markdown file
under one shall get the same placement ask as a write under .claude/rules/.
The placement deny reason shall name the placement guide by path.
Bootstrap guidance
When AGENTS.md is read in a directory whose CLAUDE.md carries no ref, the
system shall surface that the guidance was not loaded at startup and offer to add
the ref.
When a root README.md or CONTRIBUTING.md is read in a directory where
neither instruction file exists, the system shall offer to bootstrap AGENTS.md
plus the pointer.
When a foreign tool's config is read, the system shall offer to consolidate it
into AGENTS.md.
Each bootstrap trigger shall fire at most once per session per directory.
Each bootstrap trigger shall emit both systemMessage, so the user sees it, and
additionalContext, so the model can act on a yes.
When a shadowed AGENTS.md is read, the system shall surface that no Claude Code
session reads it and offer to add the pointer beside it.
Prefilter and cost
A single pure-bash prefilter shall front all three hook registrations.
The prefilter shall read the payload on stdin and exit unless the raw payload
contains one of a fixed set of substrings. The write registrations shall add
the rest of the documentation set to that set; the read registration shall
not, so a read under docs/ never starts python.
The prefilter shall decide whether a payload is a candidate without invoking
jq or python.
If a payload is not a candidate, then no python process shall be spawned for it.
Packaging and layout
plugin.yml shall be the source of record; hooks/hooks.json,
.claude-plugin/plugin.json, and docs/ shall be generated from source.
The plugin shall declare suite group record and activations [agent].
The plugin shall register no commands and ship no skills, keeping nothing resident in session context.
scripts/cleat shall run on python3 with the standard library only.
The rubric shall be a file that the deny reason and the bootstrap offers name by path, so it costs no context until one of them fires.
cleat's own repository shall hold the shape it enforces — AGENTS.md canonical
plus a pointer CLAUDE.md — and shall pass cleat check.
cleat check's verdict for each shape in the founding issue's grading table
shall agree with that table: the two A rows clean, the D row inverted, the C
rows no-claude, and the F rows unguided. A disagreement is a defect in the
check.
The placement guide shall carry the placement tree, where guidance for one
directory goes, the audience tiers, and the reference direction, name the rubric
as the split for the AGENTS.md / CLAUDE.md leaf, and hold only what the
agent acts on. The docs site shall show each guide on a page that includes it
verbatim.
The docs site shall carry a feedback loops page, written for end users, with the four kinds of guidance, the case for pairing a rule with a hook, how to tell whether each kind is working, how cleat applies the model to itself, and how each tool loads a nested instruction file, so the guide an agent reads holds only what it acts on. It shall link to the harness engineering page for the background rather than restate it.
The docs site shall carry a harness engineering page, written for end users, with what a harness is, why blocking feedback can't stand alone, and what each kind of layer costs.
The docs site shall carry a background page, written for end users new to coding
agents, with what an instruction file is and which tools read AGENTS.md and
CLAUDE.md, what loading guidance costs in the context window, the tiers
guidance can load in, and what a hook is. The sidebar shall list it before the
harness engineering page, and the feedback loops page after it.
Session state
Hook state shall live under $CLAUDE_PLUGIN_DATA. If it is unset when a hook
needs state, the hook shall report the failure as a systemMessage.
Hook state shall be partitioned by session, so the gate's re-issue record, the nudge's last reported finding set, and the bootstrap triggers already fired all expire together with the session that made them.
The plugin shall prune session state older than seven days, so state written on every session cannot grow without bound.