Skip to content

Repository files navigation

Pixel art: a sprung mousetrap under a serving dome, the cheese already smashed on the floor, and a furious mouse swearing at it

cheese

cheese-lint status stars MIT no dependencies Python 3.9+

Your agent games your tests instead of fixing the bug. It finds the cheapest change that turns the check green, ships that, and tells you it's done. The tests pass. The bug is back in a month.

Four skills for Claude Code and any harness that reads SKILL.md files, plus cheese-lint — one file, no dependencies.

In a hurry? Read FLOORS.md and close the tab. Eight failure patterns, each with the tell that lets you spot it before you know what you're looking at. Costs nothing to adopt, and it's the part that stays useful even if you never run the rest.


Why your gates make this worse

Most engineering discipline verifies that a thing works. Almost nothing verifies it is the right thing, built in the right place. So when a gate says "tests pass", the cheapest way to satisfy it is the narrowest fix that clears the criteria — and a genuine structural fix is strictly riskier against that gate, because it presents more surface to fail on.

So the gate is not neutral. Anti-reward-hacking machinery actively selects against root-level fixes, because the root fix is the expensive way to go green and the patch is the cheap one. Every gate you add sharpens that gradient.

And you cannot gate your way out. "Is this the best design?" is unfalsifiable, so it gets cheesed harder than the tests did. The only repair is to move the check before the fix is chosen — to grade the reasoning that selected the fix rather than the artifact that survived. That is what this is.

The loop

SYMPTOM -> FLOOR -> DESIGN -> TREND -> LATERAL -> ATTACK -> CONVERGE
              ^        ^                   |         |
              +--------+-------------------+---------+
                  (findings re-enter the stage they attack)
stage what it does the failure it prevents
FLOOR trace necessary conditions down to the lowest actionable cause fixing a symptom that explains one thing and recurs
DESIGN rebuild bottom-up from sourced invariants, smallest first patching around the cause instead of at it
TREND name one countable quantity across candidates, extrapolate to its limit stopping at the third-best answer because it was next
LATERAL search project state for the same floor elsewhere fixing one instance of a class
ATTACK a different agent tries to break it the author certifying their own blind spot
CONVERGE iterate until a full attack pass is empty shipping on the first pass that felt fine

The stage that pays for the method is TREND. Candidates usually form a lineage — V1 the obvious fix, V2 the smallest thing that survives V1's downstream failure, V3 the smallest invariant-complete answer — and that lineage has a direction. Name the quantity that moves (policy locations: 4 -> 4 -> 1), evaluate its limit, and the limit is frequently a larger and better answer than any candidate on the list. It is reached by following the vector, not by designing it. Twenty iterations of hill-climbing on V1 never arrive there.


Install

git clone https://github.com/seattled23/cheese
cd cheese
./install.sh            # copies skills into ~/.claude/skills, cheese-lint into ~/.local/bin

Or do it by hand — there is nothing clever in the installer:

cp -r skills/* ~/.claude/skills/
cp cheese-lint ~/.local/bin/ && chmod +x ~/.local/bin/cheese-lint

Then, in a session:

use the cheese skill on this — the same deploy failure has come back three times

Use

Copy CHEESE-LOG.template.md into cheese-log.md at your task root, fill it in as the run proceeds, and before calling the run done:

cheese-lint cheese-log.md
  [ ok ] C1   symptom recorded           SYMPTOM @ line 11
  [ ok ] C2   floor stated               FLOOR @ line 55
  [FAIL] C8   trend is countable         no numeric sequence (want e.g. `12 -> 6 -> 1`)
  [FAIL] C12  attacker is not author     attacker == author ('agent-alpha')
  ...
RECORD INCOMPLETE — 2/15 checks failed

What cheese-lint is, and what it is not

It grades the record, not the reasoning.

The verdict is three-state, and RECORD THIN exits non-zero: structurally complete, but the form signals (does it cite anything that exists? are its invariants ever measured against? does its trend name a countable thing?) say it is cheaply empty.

Those signals were once advertised as separating "someone who did the work" from "someone who filled in a form". Three independent attackers falsified that — with blocks of x, then with fabricated citations, then with citations pointing at real files having nothing to do with any claim in the record. Each round raised a threshold and the next attacker cleared it. That is this method's own three-passes-same-class stop rule firing, and the class died by correcting the claim rather than adding a fourth threshold: any text-checkable property can be satisfied by text. Substance is not in the document, so no checker reading the document can find it.

What the form signals actually do, and all they do: catch records that are cheaply empty. That is a real floor and worth a non-zero exit. It is not a substance test.

A run can pass all fifteen checks and still rest on a wrong floor, a manufactured trend, or a design nobody should ship. Nothing in a text checker can see that. Green means this run left behind a complete, checkable record — it does not mean this run was right, and it must never be quoted as though it did.

Three of the method's anti-hollow conditions are properties of the process rather than the artifact, so they are invisible here by construction:

  • R1 — was a component skill re-run through some other workflow instead of being invoked once and preserved?

  • R2 — did implementation begin before the execution handoff?

  • R3 — were diagnosis and design collapsed into one step? Missing entirely from the first release: it mapped to no check and no residue, so it had quietly become a condition graded by nobody. An attacker found it by counting the original conditions and getting seven where the release claimed six.

These are printed on every run and assigned to the independent attacker, who is the only party positioned to judge them. The linter grades what is visible in the artifact, the attacker grades what is not, and the author grades neither.

The boundary is softer than "invisible", though. All three attackers established R2 by running stat and git log by hand — evidence from outside the record. So --occurrence gathers that timeline for them:

cheese-lint cheese-log.md --occurrence     # reads git history, not the log's claims about itself

It prints when the record first appeared, when EXECUTION: first appeared, and every commit touching anything else, each marked before or after the record.

It returns no verdict, and that is a correction rather than a limitation. An earlier version voted, and an attacker measured it wrong in both directions: GREEN on the maximal violation — every line of implementation committed the day before the record existed — and RED on a compliant run. Deciding R2 means knowing which commits are "the work" and which are "the record", and nothing in git says which is which. So R2 stays where the design always put it: with the attacker, who now gets a measured timeline instead of running stat by hand.

A file with no recognisable cheese blocks exits DEAD, never clean. "Found nothing wrong" and "evaluated nothing" are different results and are reported differently — a checker that cannot tell you which one it hit is the failure it was written to prevent.


Why the checker exists at all

The method's working record had no machine-readable schema. Every stage was prose under whatever heading its author chose — ### Floor test and FLOOR-TEST: are the same work, but only one of them is addressable. So conformance could be graded only by the run's own author, and drift followed silently, because drift needs a reader to be noticed and there was none.

Across three real runs the record drifted three different ways: stale stage numbers pointing into a renumbered document, two competing formats, and one keyword (PASS) meaning "the author iterated" in one run and "the adversary attacked" in another. Nothing noticed, because nothing was reading. Writing the contract down harder was already the thing that had failed — so: literal keys, and a grader that is not the model. The schema does the load-bearing work here; the checker is downstream of it.

What this does not claim, because an independent attacker made me correct it. The first version of this README blamed "the author graded themselves." That was a post-hoc fit, and the attacker disproved it from the corpus: one of those three runs refused to self-certify, in writing — "Self-certifying the completeness of an audit whose finding is 'author == grader' would be the finding, committed again" — and dispatched four independent adversarial passes to a different named agent. The runs were not dishonest. The record was unreadable. That is a shallower cause and strictly upstream, and the fix survives the correction unchanged.

On those three runs, honestly. They fail the current gate 8, 11 and 8 of 15 checks — mostly on labels, not absent work. Two did the three-clause floor test correctly under a heading no checker could find. Three runs is also a small sample from one team. Take the numbers for what they are.

Those numbers were wrong in an earlier draft, and the way they went wrong is the method's own drift pattern recurring inside the release: a parser change made a real block invisible, and the quoted figures were never re-measured against it. An attacker caught it. The parser now accepts ENDPOINT (revised):, which is exactly what an honest re-entry looks like in a method built around OVERTURN.

On stage numbers. Stages here are named, never numbered. Numbering looked harmless and produced the single most common drift in this method's history: a run referring to "stage 7" against a document whose stages stopped at 5, because a stage had been inserted in between and every downstream reference went stale in silence. Names survive insertion.


The skills

cheese the controller — ordering, trend, lateral, convergence, stop rule
find-causal-floor read-only diagnosis, symptom to verified floor plus sourced invariants
design-from-invariants read-only design, floor to smallest invariant-complete architecture
cheese-attack the independent adversarial pass — never run it on your own work

Two agent definitions ship alongside them, because the ATTACK stage is only real if something else runs it: cheese-attacker (read-only adversary) and cheese-analyst (read-only floor + design). Prefer two attackers with different lenses — break the instrument and break the floor. The marginal cost is one dispatch, and where they disagree is signal one attacker cannot give you.

Something has to oblige the gate to run — a checker nobody is required to invoke is the prose gate wearing an executable's clothes. .github/workflows/cheese-lint.yml is that party: not the author, not the model. Copy it into your own repo.

FLOORS.md is the part that compounds: eight recurring floors extrapolated to their transferable shape, each with the tell that lets you recognise it before you know what you are in, its repair shape, the way repairing it goes wrong, and the real instances it was extracted from. It turns the LATERAL stage from an exercise in memory into a lookup, and it gets stronger every run.

Each is usable alone. find-causal-floor on its own is a good incident-diagnosis skill; cheese-attack on its own is a good pre-ship review. The full loop is for when a locally correct answer would cost you a second pass.

skills/cheese/worked-example.md has two complete runs as narrative — one on code, one on a rule. examples/ has this repo's own run in the canonical format — the one to copy for shape. Read its verdict before you copy its status: it deliberately reports RECORD INCOMPLETE, because the run it records is not converged and saying otherwise would be the exact failure this repo exists to make visible. It also keeps its own overturned floor, a retracted attacker finding, and two R2 violations the attackers caught the author committing mid-run. A worked example showing only the parts that went well teaches the wrong thing.

When not to run it

A mechanical one-file, one-caller change. A cheese run costs real attention, and spending it on a rename is how a method gets a reputation for ceremony. The trigger list is in the skill; if none of it fires, just do the work.

Portability

Nothing here depends on a particular harness, language, repo layout, or CI. The skills name a governed executor for the implementation handoff — a contract runner, a task system, or a human with the change boundary attached. Wire it to whatever you have.

cheese-lint is Python 3.9+, standard library only, no install step.

./cheese-lint --init                  # write a blank cheese-log.md skeleton
./cheese-lint --selfcheck             # built-in assertions
./cheese-lint --pointers skills/      # every referenced skill and link resolves

--init exists because the drift happened when complying with the format was work. Make compliance the path of least resistance.

The second one exists because a method that documents a component it does not ship is how a contract silently loses a stage — the reader follows the pointer into nothing.


License

MIT. See LICENSE.

David Everett + Sōren Vale

About

Cheese finds the correct reward function before you optimize, not after. You spend hours optimizing, then find you missed something below the reasoning floor. Common failure mode, uncommonly fixed. Four skills for Claude Code plus cheese-lint — one file, no dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages