Mentu

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

Both 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

  1. mentu ratchet check scans all files in --source matching the ratchet's fileExtension
  2. Counts regex matches per line for each pattern
  3. Skips files matching any excludePatterns
  4. Compares match count against maxViolations
  5. Reports PASS (count <= max) or FAIL (count > max) with delta
  6. 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:

Step completes in worktree
-> Build command (if defined)
-> Ratchet check (if ratchets.json exists)
-> Plugin before_merge hook
-> Merge to main (if all pass)

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

This 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 -> 1

CIR 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 excludePatterns to skip test files where force-unwraps are acceptable
  • Use in pipelines with --transfer worktree for automatic enforcement
  • Ratchets are per-project -- each workspace can have its own ratchets.json
  • Set maxViolations: 0 for patterns that should never appear (zero-tolerance ratchets)
  • Commit ratchets.json to version control so the team shares the same baselines
© 2026 Mentu.