[!NOTE] Bugs and requests go to Issues.
Overview
hadome (歯止め) is a ratchet for code quality. It checks the changes in a diff and fails only on what got worse.
Its center is the tests themselves: it checks the rules for how tests are written. It is meant for repositories where AI agents write most of the code.
| What the change contains | Result |
|---|---|
| A new violation | Blocked |
| Anything that shrinks what the tests guard | Blocked |
| A violation that was already there | Allowed (fixing it is up to the developers) |
Languages
One binary. Language adapters find tests, read imports and measure size; the rules are shared.
| Go | TypeScript | Python | Rust | Java | Kotlin | |
|---|---|---|---|---|---|---|
tests/ |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
deps/ |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
size/ |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
comments/ |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
✅ supported
Quick start
1. Install
Pick the one for your language. Each pins the version (distribution). Every way installs the same binary and reads the same .hadome.yml.
From here on, commands are written as hadome …. Use the form for how you installed it, such as npx hadome … or go tool hadome ….
TypeScript / JavaScript
Python
Go
Built from source. With a C compiler it reads all six languages; without one, Go only.
Java / Kotlin (Gradle)
// build.gradle.kts
plugins { id("io.github.rerorets.hadome") version "0.1.0" }
Run it with ./gradlew hadomeCheck.
Java / Kotlin (Maven)
<!-- in pom.xml, under <build><plugins> -->
io.github.rerorets
hadome-maven-plugin
0.1.0
Run it with mvn hadome:check.
Rust
Anything else
You can also install from an archive on the releases page.
2. Tell your AI agents
Writes a block on how to use hadome into the file your agents read. Any other file, such as AGENTS.md, works too. The file is created if it is missing, and later runs update the block in place. Add --lang ja for a Japanese block. The full procedures are in the guide for agents.
To add it by hand, paste this block.
This repository uses hadome to stop regressions in code and tests.
- -----
How it looks
A change that deletes the only test of store.Append, adds a sleep to another test, and adds an import the dependency diagram does not draw:
$ hadome check --base main
✗ deps/not-in-diagram import not in the diagram: store → core (no arrow store --> core)
store/store.go:3
why: the diagram (docs/deps.md) is how people and agents know what may depend on what; an import it does not draw makes it wrong
fix: depend on what the diagram allows, often through an interface the importing unit declares; if the design changed, draw the arrow, which a person reviews
✗ tests/no-sleep test waits by sleeping: time.Sleep(10 * time.Millisecond)
worker/worker_test.go:11
why: a sleep is either too long (a slow suite) or too short (a flaky test); it never waits for the actual event
fix: wait for the event itself: a channel, a callback, or a condition with a deadline that fails loudly
✗ tests/scope-shrunk guarded scope shrank: example.com/app/store.Append is no longer exercised by any active test (was: TestAppendKeepsRows)
store/store_test.go:9
why: the behaviour of example.com/app/store.Append can now change without any test noticing
fix: keep or restore a test that calls example.com/app/store.Append, or remove example.com/app/store.Append itself if it is no longer needed
3 regression(s). 0 existing finding(s) unchanged (not blocking).
Where it runs in your workflow
hadome is one command. Call it where a change is about to move on, and it answers whether the change made anything worse. It compares the current change with main using the built-in rules, and violations already in main are the baseline. There is nothing to fix before you start.
flowchart LR
W["Agent or developer<br/>writes a change"] --> L["<b>hadome check</b><br/>locally / in the agent loop"]
L -- "regression" --> W
L -- "ok" --> P["Push / pull request"]
P --> C["<b>hadome check --base origin/main</b><br/>in CI"]
C -- "regression" --> W
C -- "ok" --> M["Merge"]
| Where | What you run | What you get back |
|---|---|---|
| In an agent's loop | hadome check --format json |
Each regression with its rule, file and line, why it matters, and how to fix it |
| Before you push | hadome check (as a pre-push hook) |
A readable list; exit code 1 on any regression |
| In CI, on every pull request | hadome check --base origin/main |
An exit code and a summary you can post as a comment |
hadome itself never talks to GitHub or any other service. To post the summary, post it from CI.
Configuration
Settings go in .hadome.yml: limits, the dependency diagram, invariants and so on. hadome works without the file.
Agents change the settings and propose invariants, reading the specification with hadome explain <key> (the guide for agents). People decide which proposals to accept and whether to allow a change that loosens the rules. Such a change stops with exit code 3 for a person's review.
Writing an exception
Write it where it applies, with a reason. Without a reason it has no effect.
// gate:allow tests/no-sleep reason: the OS file watcher offers no event to wait on
time.Sleep(50 * time.Millisecond)
Exit codes
| Code | Meaning |
|---|---|
0 |
No regressions |
1 |
One or more regressions |
2 |
Configuration error. The check cannot be trusted, so it does not pass |
3 |
No regressions, but something needs a person's review (a new exception, a configuration that loosens the rules, and so on) |
Your own gates
Rules only your product knows, such as "never UPDATE the log store", are small programs in your own repository, in any language. hadome treats them like the built-in rules: only regressions fail, reasoned exceptions work, and the report has the same shape. See product-specific gates.
What it checks
| Group | Rules |
|---|---|
tests/ |
scope-shrunk (what tests guard shrank) · invariant-unguarded · no-clock · no-sleep · always-true · skip-without-reason · no-mock (mocks in tests of pure code) · purity-lost and target-unknown (review) |
deps/ |
not-in-diagram (imports your dependency diagram does not draw) · diagram-stale (boxes that name nothing) |
size/ |
file-lines · function-lines · params · complexity · interface-methods |
comments/ |
todo-format (TODOs without a when) · missing-doc · broken-reference (comments pointing at missing documents) |
unknown/ |
unread (review): a file hadome could not read |
config/ |
loosened (review): a change that loosens the rules judging it |
Each finding says why it matters and how to fix it. The rules are specified in docs/design.
How it differs from a linter
| A typical linter | hadome | |
|---|---|---|
| Compared with | The rules only; reports every violation | main; reports only new violations and regressions |
| Looks at | How code is written | How code is written, and what the tests actually guard |
| Deleting tests | Not its concern | Tells cleanup (the guarded scope is kept) from regression (it shrank) |
| Languages | A different tool per language | One binary for six languages; the rules are shared and only the adapters differ |
| Exceptions | Disabling comments, often without a reason | Only a marker with a reason, written where it applies |
Regression vs. cleanup
The number of tests cannot tell cleanup from regression: both look like "−3 tests". hadome measures what the tests guard instead.
For each test, hadome works out the functions and methods it calls (its targets). The targets reached by active tests are the guarded scope.
| What you did | Count | Guarded scope | Verdict |
|---|---|---|---|
| Merged three duplicate tests into one | ↓ | Same | Cleanup |
| Deleted code together with its tests | ↓ | Gone with the targets | Cleanup |
| Deleted or weakened an important test | ↓ or same | Narrower | Regression |
To stop guarding a behaviour, change the specification and remove the behaviour itself.
The model: three layers
People manage the rules for which tests can be trusted, not the test cases.
flowchart TB
G["<b>3 · Gate</b><br/>Stops regressions with checks"]
R["<b>2 · Writing rules</b><br/>How a trustworthy test is written"]
I["<b>1 · Invariants</b><br/>What must not break (10 at most)"]
G --- R --- I
- Invariants: a short list of what must not break. Each is tied to the tests that guard it; deleting or disabling them fails
- Writing rules: test observable behaviour only. Never copy expected values from the implementation. Never read the clock. Never wait with sleep. Never skip silently
- Gate: hadome. It enforces the rules with checks
The name
歯止め (hadome) is the Japanese word for a pawl: the part that lets a ratchet wheel turn one way and stops it from slipping back.
Status and roadmap
- Name, scope and model
- Design documents (docs/design: engine, language adapters, configuration, every rule group, gates, suggestions, distribution)
- Engine: compare with the base branch, renames, reasoned exceptions, self-tests
- Configuration: strict validation, JSON Schema,
explain, shared layers, loosening needs review -
tests/rules — how tests are written, the guarded scope, invariants and purity -
hadome agents,suggestandinit, and a guide for agents - Product-specific gates (protocol)
-
deps/,size/andcomments/rules - TypeScript, Python, Rust, Java and Kotlin (with tree-sitter; the pure-Go build reads Go only and reports the rest as unread)
- Published in every ecosystem — the packages and the release workflow are ready (releasing); publishing waits for the registries' accounts
- Mutation-based scope