**An integrity hook for Claude Code that watches the agent's own tool calls and blocks the ones that fake a check.** When Claude says it did something — ran the tests, cited a paper, committed the fix, verified the certificate — makoto holds that word against
> /plugin marketplace add Clear-Sights/Makoto> /plugin install makoto@makoto
What's inside
An integrity hook for Claude Code that watches the agent's own tool calls and blocks the ones that fake a check. When Claude says it did something — ran the tests, cited a paper, committed the fix, verified the certificate — makoto holds that word against its record. If the deed isn't there, or the verification was quietly disabled, makoto blocks the tool call (or the end-of-turn) and hands the agent a one-line correction to retry against.
That publication claim is deliberately bounded: Shipped plugin — installable and versioned. The dispatcher is replay-tested against authored sessions; its effect on a live session's outcome is unmeasured.
Integrity, as this tool uses the word, is exactly that agreement: a claim the agent made this
turn is matched by the record of the deed it names. Nothing wider — not correctness, not code
quality, not whether the deed was a good idea. So gate.relative_path_citation says a bare
path is "a communication-quality signal, not an integrity violation": it contradicts no claim
against the record, it is only harder to follow. makoto.vocab's _INTEG_VOCAB (vocab.py) is
the lexical half of the same idea — the word-set naming integrity concepts in a subject's
code — and is not a second definition of this one.
Checks declare their inputs in registry.Check.eats. Runtime outcomes are folded by
verdict.apply; receipt fields come from state.ledger.emit_receipt.
makoto fires on mechanical hook events — every PreToolUse, PostToolUse, and Stop — and
blocks on pre-check findings and blocking end-of-turn gate findings. The live inventory is:
content: 11, event: 2may_block=True)registry.blocking_eligible)Two different things are called a gate in that list, and the counts are not comparable. The
gate. in a pre-check id is a naming prefix and nothing more; an end-of-turn gate is a
check registered at the Stop edge with may_block=True. No pre-check carries the prefix today —
gate.contract_order, the one that did, was cut 2026-09-18 along with its Stop sibling. Every
count above is scoped by edge, so no check is counted twice within a line.
Verifier weakening — a check silently neutered
content.verifier_predicate_weakened loose-comparator verifier (startswith/endswith/re.match where == is meant)content.verifier_exit_masking exit-code masking (|| true, ; true, set +e on a test/build/lint)content.verifier_body_hollowed hollowed verifier body (return True / pass in a constitution check)content.env_gated_audit audit/verification code gated behind an env var · content.integrity_suppression_flag integrity-named suppression flag (*_skip = true)Fabricated evidence — a claim with no backing artifact
content.phantom_citation phantom citation (Author-Year not in makoto/docs/CITATIONS.md)content.unsourced_webfetch WebFetch of a URL never seen in any prior tool result this sessioncontent.fabricated_commit_sha fabricated commit SHA/tag presented as proof of a commitcontent.illusory_authorship_trailer an illusory Claude/Anthropic authorship or generation attribution (trailer, session link, routing address, or "Generated with/by Claude" footer) — a plain "Claude Code" product-name mention is not matchedcontent.illusory_interruption_claim a fabricated "interrupted by user" claim with no genuine harness-set interruption anywhere in this session's recorded historySelf-defense
content.self_mute_guard makoto self-mute (disabling or un-wiring makoto via settings.json)Scope & contract discipline — illusory progress and out-of-contract action (SPEC-5, ported by shape from Assay)
event.thrash_revert a whole-file Write that reverts a file to an earlier byte-identical content after an intervening different Write (A→B→A, no net progress)End-of-turn gates — fire on the agent's closing claims, checked against the recorded ledger. docs/CATALOG.md points to the registered checks and their implementations.
The certification column uses the following labels, each naming its own denominator:
gate.canon_fingerprints (gold-oracle
certification). Zero-FP on those sets is the claim; the live-session false-positive rate
accumulates from field use and is not covered by it.| Check id | One-line trigger | Fire | Certification |
|---|---|---|---|
gate.completion | "done / created X" but the artifact isn't on disk | blocking | established |
gate.green_claim | "suite green" against a recorded test failure | blocking | established |
gate.dropped | an identifying forward promise left undischarged at turn-end | blocking | established |
gate.fabricated_action | "I ran X" in a turn with no tool call at all | blocking | established |
gate.named_test | "test_foo passes" against a recorded FAILED of that named test | blocking | established |
gate.stale_pass | "all tests pass" against pytest's own live lastfailed record | blocking | established |
gate.claimed_running | "it's running/up" contradicted by this session's own Bash record | blocking | established |
gate.claimed_shipped | "merged/pushed/live" with no successful remote-mutating call on record | blocking | established |
gate.claimed_consent_absent | cites the operator's approval, instruction or word in a session whose transcript carries no genuine operator turn at all | blocking | new |
gate.unexamined_wall | states that a fact cannot be determined when no action at all has been taken since the operator's last turn | blocking | new |
gate.liveness | a statement with no live effect inside a closed function | blocking | established |
gate.hollow_test | a test gutted so it can never fail (no assert, tautology, swallowed failure, uncollectable) | blocking | established |
gate.canon | last call ended in an unresolved direct error, or a byte-identical stuck retry loop | blocking | replayed |
gate.canon_fingerprints | ported canon fingerprints in the robust core established by gold-oracle certification | blocking | established |
gate.self_wired | makoto's own hook wiring partially stripped from settings.json | advisory | advisory |
gate.canon_fingerprints_advisory | the advisory remainder (soft/claim atoms or gold-disqualified) | advisory | advisory |
gate.relative_path_citation | a chat response citing a non-absolute (unclickable) path | advisory | advisory |
gate.plan_item_drift | open plan/task-labeled commitments sourced from chat prose | advisory | advisory |
gate.unprobed_fanout | work dispatched to a subagent with no read, glob or grep before it | advisory | advisory |
gate.unasked_plan | a plan presented with no question asked, so an ambiguity was guessed | advisory | advisory |
gate.unread_structure | a traversal of structured data that printed null with no structure read before it | advisory | advisory |
gate.unwitnessed_verifier | a verifier reporting clean that has never been seen reporting a failure | advisory | advisory |
gate.unknown_ref_switch | HEAD moved to a ref nothing in the session had printed | advisory | advisory |
gate.unobserved_destruction | content destroyed with no verifier report before it | advisory | advisory |
gate.relaunched_unchanged | a second worker launch with no verifier report anywhere before it | advisory | advisory |
gate.undischarged_waiver | a checker-silencing directive introduced with no checkable end named beside it | advisory | advisory |
gate.unnamed_failure | a counted failure whose recorded failing identity the turn never names | advisory | advisory |
gate.report_before_run | a run's success written into prose with no verifier run before it | advisory | advisory |
gate.unclaimed_unit | a top-level unit added that no turn names, nothing reaches, and no decorator registered | advisory | advisory |
gate.pasted_fix | one repair's text edited into a second file with no verifier run between the two landings | advisory | advisory |
Inspect the pre-tool catalog with makoto pattern list; see one pattern in full with makoto pattern show content.phantom_citation.
Follow the finding's retry hint; exemption scope belongs to the check. See Makoto conventions for the marker syntax.
if os.environ.get("ENABLE_AUDIT_TRAIL"): # makoto-allow: app feature, gates user-facing audit logging
write_audit_trail()
/plugin marketplace add Clear-Sights/Makoto
/plugin install makoto@makoto
Enabling the plugin wires the events declared in hooks.json.
Its shim executes python -m makoto.dispatch from the plugin root.
State dir + makoto.record.db are created lazily on the first hook invocation.
An illusory AI-authorship commit trailer can reach a commit through either path. Pre-Check content.illusory_authorship_trailer blocks
the agent-authored one — the trailer typed into a git commit message or into file content, the
surface no setting can reach. The other door is Claude Code's own automatic append, which a
setting governs. To close it at the source, set in ~/.claude/settings.json:
{ "includeCoAuthoredBy": false }
This is defense in depth, not a replacement: the setting closes the auto-append door, content.illusory_authorship_trailer closes
the agent-authored one. makoto's install does not write this for you — it leaves settings.json
untouched beyond hook wiring (above); set it yourself if you want the earlier layer.
If you previously ran the old python -m makoto install (0.3.0 or earlier), your
~/.claude/settings.json has makoto-managed hook entries. Running the plugin alongside would cause
double-dispatch. How to tell if you're affected: grep makoto ~/.claude/settings.json — any hit
means the old entries are present. Migrate cleanly:
python -m makoto uninstall # removes old settings.json entries
/plugin install https://github.com/Clear-Sights/Makoto # installs the plugin
Reports are welcome and are credited by name; pull requests from outside this repository are not
FAQ
makoto is a Claude Code plugin with hand-picked skills for security work, indexed on Flowy. Install it with the command on its page. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
Is this plugin yours?
Claim it with GitHubSubmit a pluginPromote it