Skip to content

Latest commit

 

History

History
572 lines (460 loc) · 24.4 KB

SPEC.md

File metadata and controls

572 lines (460 loc) · 24.4 KB

cleat — Specification

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 …).

Concepts

  • 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 reads AGENTS.md directly, but only where no CLAUDE.md or CLAUDE.local.md sits 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-in agents-md plugin 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.md whose body is the @AGENTS.md ref, optionally followed by Claude-specific guidance (slash commands, hooks, settings.json). An import rather than a symlink, because CLAUDE.md has a legitimate use beyond pointing.
  • Ref — the @AGENTS.md import line inside CLAUDE.md. Its presence is what makes the pointer load AGENTS.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.md content only.
  • Shadowed — an AGENTS.md below the repository root with no CLAUDE.md beside it, under a directory holding CLAUDE.md, .claude/CLAUDE.md, or CLAUDE.local.md. Claude Code reads AGENTS.md directly only where none of those sits at or above the working directory, so it never reads a shadowed file. A CLAUDE.md pointer 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.md is a stub pointing back at CLAUDE.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 @path import loads at launch whatever it costs.
  • Topic rule — a markdown file under .claude/rules/, discovered recursively. Always-on when it carries no paths: 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.md that 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, and narrowing-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). unguided and heading-only duplication are advisories: a repo may have chosen to carry no agent guidance, and a shared ## Commands heading 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, every SKILL.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.md links to AGENTS.md; AGENTS.md and README.md link to docs/; docs/ links only within docs/ 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 under docs/, less any file git ignores or marks linguist-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.md or its peers; for the agent, a hook or linter where one can decide it from a single tool call's inputs, else AGENTS.md or CLAUDE.md where every session needs it, else a nested AGENTS.md and 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.

Requirements

CHECK

The check command

CHECK-01

The system shall provide cleat check [DIR], reporting findings for DIR and defaulting to the current directory.

CHECK-02

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.

CHECK-03

Where --json is passed, cleat check shall emit its findings as JSON on stdout.

CHECK-04

When CLAUDE.md has substantive content and no AGENTS.md sibling exists, the check shall report no-agents.

CHECK-05

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.

CHECK-06

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.

CHECK-07

When AGENTS.md points at CLAUDE.md, the check shall report inverted.

CHECK-08

When the two files overlap in content, the check shall report duplicated.

CHECK-09

When neither file exists, the check shall report unguided.

CHECK-10

When a foreign config is present, the check shall report foreign-config naming that path.

CHECK-11

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.

CHECK-12

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.

CHECK-13

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.

CHECK-14

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.

CHECK-15

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.

CHECK-16

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.

CHECK-17

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.

CHECK-18

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.

CHECK-19

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.

CHECK-20

The check shall report shadowed for each shadowed AGENTS.md below DIR, naming its path relative to DIR.

RULE

Topic rules and the index

RULE-01

The system shall discover topic rules as the *.md files under .claude/rules/, searching recursively.

RULE-02

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.

RULE-03

When a path-scoped rule's filename appears nowhere in AGENTS.md, the check shall report unlisted-rule, naming that rule.

RULE-04

When a retired rule's filename appears in AGENTS.md, the check shall report dead-rule-listed, naming that rule.

RULE-05

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.

RULE-06

The index shall be measured as the AGENTS.md lines that name a topic rule, so no heading convention is imposed on AGENTS.md.

RULE-07

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.

RULE-08

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.

RULE-09

Where AGENTS.md is absent, the system shall report no rules findings, the missing pointer being the prior repair.

RULE-10

The system shall classify topic rules without following directory symlinks, so a symlink cycle cannot stall the check or a hook.

RULE-11

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.

RULE-12

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.

INDEX

The index projector

INDEX-01

The system shall provide cleat index [DIR], printing the rules index AGENTS.md should carry for DIR and defaulting to the current directory.

INDEX-02

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.

INDEX-03

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.

INDEX-04

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.

INDEX-05

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.

INDEX-06

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

Hook dispatch

HOOK-01

cleat hook shall read the hook payload from stdin and dispatch on hook_event_name.

HOOK-02

cleat hook shall always exit 0, whatever it decides.

HOOK-03

cleat hook shall emit its decision as hookSpecificOutput.

HOOK-04

When the tool is Write, the hook shall judge tool_input.content.

HOOK-05

When the tool is Edit, the hook shall judge the on-disk file with old_string replaced by new_string.

HOOK-06

The plugin shall register exactly three hooks: PreToolUse on Write|Edit, PostToolUse on Write|Edit, and PostToolUse on Read.

HOOK-07

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.

HOOK-08

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.

HOOK-09

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.

GATE

The write gate

GATE-01

The gate shall consider a write in scope when the target's basename is CLAUDE.md.

GATE-02

If the target is under $HOME/.claude, then the gate shall allow it untouched, user-scope memory being Claude-specific by nature.

GATE-03

If the target is CLAUDE.local.md, then the gate shall allow it untouched.

GATE-04

When a sibling AGENTS.md has substantive content, the gate shall allow the write silently.

GATE-05

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.

GATE-06

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.

GATE-07

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.

GATE-08

When a write matches an existing re-issue record, the gate shall allow it and clear the record.

GATE-09

The re-issue record shall be scoped to the session that produced the deny, so a stale record cannot pass a later write.

NUDGE

The repair nudge

NUDGE-01

When a write to CLAUDE.md or AGENTS.md completes, the nudge shall run the check against that directory.

NUDGE-02

The nudge shall return the findings and their repairs as additionalContext, so the shape can be corrected in the same turn.

NUDGE-03

The nudge shall treat AGENTS.md as canonical, directing every duplicated repair at CLAUDE.md.

NUDGE-04

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.

NUDGE-05

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.

NUDGE-06

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.

NUDGE-07

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.

PLACEMENT

Where new guidance goes

PLACEMENT-01

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.

PLACEMENT-02

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.

PLACEMENT-03

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.

PLACEMENT-04

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.

PLACEMENT-05

When a write trips both GATE-06 and a placement ask, the gate shall issue a single deny carrying every reason.

PLACEMENT-06

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.

PLACEMENT-07

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/.

PLACEMENT-08

The placement deny reason shall name the placement guide by path.

BOOT

Bootstrap guidance

BOOT-01

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.

BOOT-02

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.

BOOT-03

When a foreign tool's config is read, the system shall offer to consolidate it into AGENTS.md.

BOOT-04

Each bootstrap trigger shall fire at most once per session per directory.

BOOT-05

Each bootstrap trigger shall emit both systemMessage, so the user sees it, and additionalContext, so the model can act on a yes.

BOOT-06

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

Prefilter and cost

PREFILTER-01

A single pure-bash prefilter shall front all three hook registrations.

PREFILTER-02

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.

PREFILTER-03

The prefilter shall decide whether a payload is a candidate without invoking jq or python.

PREFILTER-04

If a payload is not a candidate, then no python process shall be spawned for it.

PACKAGING

Packaging and layout

PACKAGING-01

plugin.yml shall be the source of record; hooks/hooks.json, .claude-plugin/plugin.json, and docs/ shall be generated from source.

PACKAGING-02

The plugin shall declare suite group record and activations [agent].

PACKAGING-03

The plugin shall register no commands and ship no skills, keeping nothing resident in session context.

PACKAGING-04

scripts/cleat shall run on python3 with the standard library only.

PACKAGING-05

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.

PACKAGING-06

cleat's own repository shall hold the shape it enforces — AGENTS.md canonical plus a pointer CLAUDE.md — and shall pass cleat check.

PACKAGING-07

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.

PACKAGING-08

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.

PACKAGING-09

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.

PACKAGING-10

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.

PACKAGING-11

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.

STATE

Session state

STATE-01

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.

STATE-02

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.

STATE-03

The plugin shall prune session state older than seven days, so state written on every session cannot grow without bound.