Ratchets
Ratchets
Ratchets are one-way quality gates. You define regex patterns and maximum violation counts. The count can go down (you fixed something) but never up (you added a new violation). Ratchets are checked during worktree canary merge-back, so they enforce quality without blocking development.
Quick start
# Check current violations against baselines
mentu ratchet check --source ./mentu-engine/Sources --config ./ratchets.json
# Update baselines to current counts (can only decrease)
mentu ratchet baseline --source ./mentu-engine/Sources --config ./ratchets.jsonBoth commands default to --config ratchets.json and --source mentu-engine/Sources when run from the project root.
Configuration
ratchets.json at project root:
[
{
"name": "force_try",
"pattern": "try!",
"fileExtension": ".swift",
"maxViolations": 2,
"description": "Force try (try!) crashes on failure -- use do/catch or try? instead",
"excludePatterns": ["Tests/"]
},
{
"name": "force_cast",
"pattern": "\\bas!\\s",
"fileExtension": ".swift",
"maxViolations": 1,
"description": "Force cast (as!) crashes on type mismatch -- use as? with guard instead",
"excludePatterns": ["Tests/"]
},
{
"name": "fatal_error",
"pattern": "fatalError\\(",
"fileExtension": ".swift",
"maxViolations": 0,
"description": "fatalError() crashes unconditionally -- return errors or use precondition",
"excludePatterns": ["Tests/"]
},
{
"name": "todo_fixme",
"pattern": "// TODO|// FIXME",
"fileExtension": ".swift",
"maxViolations": 3,
"description": "TODO/FIXME markers indicate incomplete work -- resolve or track externally"
}
]| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable ratchet name |
pattern |
string | Yes | Regex pattern to count matches |
fileExtension |
string | Yes | Only scan files with this extension (e.g. .swift, .ts) |
maxViolations |
number | Yes | Maximum allowed occurrences (baseline) |
description |
string | Yes | Why this pattern matters |
excludePatterns |
string[] | No | Regex patterns matched against relative file paths to skip |
How it works
mentu ratchet checkscans all files in--sourcematching the ratchet'sfileExtension- Counts regex matches per line for each pattern
- Skips files matching any
excludePatterns - Compares match count against
maxViolations - Reports PASS (count <= max) or FAIL (count > max) with delta
- Emits a CIR signal with results
Terminal output:
Ratchet Current Max Status
────────────────────────────────────────────────────────
force_try 1 2 PASS (-1)
force_cast 1 1 PASS (0)
fatal_error 0 0 PASS (0)
todo_fixme 2 3 PASS (-1)A negative delta means you have room to tighten. Zero means you are at the baseline. A positive delta means regression -- the check fails and exits non-zero.
Canary integration
When running with --transfer worktree (or mirror/rsync), mentu checks ratchets during the canary phase before merging back to main:
If ratchets regress (new violations exceed the baseline), the merge is blocked. Your changes stay in the worktree for fixing. The ratchet check during canary is non-fatal to the overall canary process -- it blocks the merge but does not crash the runner.
The canary phase loads ratchets.json from the worktree root and scans mentu-engine/Sources within the worktree, so it checks the code as modified by the step, not the main branch version.
Updating baselines
When you fix violations and want to lower the bar:
mentu ratchet baseline --source ./mentu-engine/Sources --config ./ratchets.jsonThis updates the maxViolations values in ratchets.json to match current counts -- but only downward. If your current count is lower than the max, the max decreases. If it is higher (somehow), the max stays. This is the monotonic guarantee.
The command shows before and after states:
Before:
Ratchet Current Max Status
────────────────────────────────────────────────────────
todo_fixme 1 3 PASS (-2)
After:
Ratchet Current Max Status
────────────────────────────────────────────────────────
todo_fixme 1 1 PASS (0)
Tightened: todo_fixme: 3 -> 1CIR signals
Every ratchet check emits a CIR signal with kind ratchet and domain quality:
{
"ratchets": [
{ "name": "force_try", "current": 1, "max": 2, "passed": true },
{ "name": "fatal_error", "current": 0, "max": 0, "passed": true }
],
"all_passed": true
}This feeds into CIR's quality tracking. Over time, you can observe the monotonic improvement curve -- violation counts that only go down.
In scripts
const result = mentu.ratchet.check({
source: './mentu-engine/Sources',
config: './ratchets.json',
});
if (!result.passed) {
for (const v of result.violations) {
console.log(`${v.name}: ${v.count} found, limit is ${v.limit}`);
}
mentu.notify.send('Ratchet failure', `${result.violations.length} ratchets regressed`);
}Update baselines programmatically:
mentu.ratchet.baseline({
source: './mentu-engine/Sources',
config: './ratchets.json',
});Best practices
- Start with current counts as baselines, then lower them as you fix issues
- Add ratchets for patterns you want to eliminate:
try!,as!,fatalError(,// TODO - Use
excludePatternsto skip test files where force-unwraps are acceptable - Use in pipelines with
--transfer worktreefor automatic enforcement - Ratchets are per-project -- each workspace can have its own
ratchets.json - Set
maxViolations: 0for patterns that should never appear (zero-tolerance ratchets) - Commit
ratchets.jsonto version control so the team shares the same baselines