Independent AI intelligence Two editions daily · ET
Fervor AI

Analysis · October 28, 2026 · repo

unclebob/ub-modeClaude Codeai-skillsclaude-codeagent-harness

ub-mode Turns Uncle Bob's Rules Into Gates for Your AI Coding Agent

A prompt can only ask your agent to test first. Three small tools, Crapper, Dryer and Mutator, measure whether it did, if you wire them in.

Coding agents have learned to write tests. That sounds like good news, and mostly it is. But a passing test only proves the test ran. It doesn't prove the test checked anything.

Anyone who has reviewed agent-written code has seen the version that slips through: a test that calls the function, asserts something vague, and goes green no matter what the function returns. The suite looks healthy. Nothing is protected.

One line in a new agent skill names this problem better than most testing books: "If the test would still pass when every function returned nothing, throw it out." The skill is ub-mode, and it was the #3 repo on Trendshift (a site that ranks GitHub repos by how fast they are gaining attention) on October 28. The interesting part isn't the rules. It's that the same author wrote three small command-line tools that can check those rules for you, and that is what turns this from advice into a gate.

What ub-mode is

ub-mode is a single file, a SKILL.md of about 740 words. A skill (in Claude Code and similar agents) is a Markdown file of instructions the agent loads when you call it by name, here with /ub-mode.

It comes from the unclebob GitHub account, and the companion tools' license files name Robert C. Martin, the author of Clean Code, as the copyright holder (crapper LICENSE). The repo had four commits, all on October 25, and roughly 3,000 stars on October 28 (GitHub and the shields badge disagreed, between 2.9k and 3.1k). The README says it is "loosely based on portions of" the rules of the developer @poteto.

The skill walks the agent through three phases for every change:

  • First make it work. Write the failing test first. Ship the smallest edit that solves the problem. Get the data shape right before the logic. Take complexity out before adding anything.
  • Then make it right. Fix the root cause, not the symptom. Test behavior the way a user would see it. Then run three quality tools and fix what they find.
  • Practice. Keep a log of every action and why. Commit in small steps, each one saying what changed and how it was proved.

If you have read any of Martin's books, none of this will surprise you. That's fine. The rules aren't the new part.

The catch: rules in a prompt are suggestions

Here is what stands out when you read the file closely. The "make it right" section says "Use the crapper tool" and "Use the mutator tool." It never says how to install them, where they live, or what command to run.

So if you only copy SKILL.md, the agent has instructions to use tools that may not exist on your machine. A model in that position will often do the reasonable-sounding thing: say it reviewed complexity, or skip the step without mentioning it. You end up with the vocabulary of discipline and none of the checking.

Claude Code's own documentation draws this line clearly. It says an output style (instructions that shape every response) "doesn't guarantee that something always happens or never happens," and points to hooks "for something that has to happen every time." Skills are the same kind of thing as output styles here: instructions the model follows when it follows them.

The tools are the other half. They're small Python programs that measure your code and return numbers. Numbers don't negotiate.

The three gates, in plain words

All three tools support Clojure, Java, Go, TypeScript, Rust and Python, and all three carry the very permissive Zero-Clause BSD license.

Crapper: complexity that nothing tests

Crapper computes a CRAP score for every function. Think of it as "how risky is this function to change?" It combines two things: how many paths run through the function (cyclomatic complexity, roughly the count of ifs, loops and branches) and how much of it your tests actually run (coverage).

The formula in the README is CC² × (1 − coverage)³ + CC. You don't need to memorize it. What it does: a complicated function with full test coverage scores low, and the same function with no tests explodes. The README calls 1 to 5 low risk, 5 to 30 worth a look, and 30 and up "complex and under-tested." ub-mode sets a stricter bar: every function under 10.

It needs a coverage report from your normal tools (coverage.py for Python, LCOV from Vitest or c8 for TypeScript, go test -coverprofile for Go, and so on). A function the coverage report doesn't mention scores 0%.

Dryer: copy-paste that changed its variable names

Dryer finds structural duplication. It strips out names, literals and local variables, fingerprints the shape of each block of code, and scores pairs from 0 to 1 by how many fingerprints they share. Two functions that do the same thing with different variable names score high.

One detail worth knowing: ub-mode says to remove duplication ranked 0.8 or above, but Dryer's default threshold is 0.82. If you want the skill's bar, pass --threshold 0.8 explicitly.

Mutator: proof that your tests check something

Mutator does mutation testing, and this is the tool that makes ub-mode worth installing.

The idea is simple. Mutator makes a small deliberate bug in your code, such as flipping a + to a -, a > to a >=, or true to false. Each changed copy is called a mutant. Then it runs your tests. If the tests fail, the mutant is "killed": your tests noticed the bug. If the tests still pass, the mutant "survived": you could ship that bug and your suite would stay green.

Now reread the skill's best line. "If the test would still pass when every function returned nothing, throw it out." A surviving mutant is that exact situation, found automatically, pointing at the line. Mutation testing is the rule turned into a measurement.

The score is killed divided by killed plus survived. Mutants on lines your tests never run are reported as uncovered and skipped, which is why the skill says "cover the uncovered" before "kill the survivors."

Put this into practice

Start with one project you know, and one file you are about to change.

1. Install the skill. The README says to copy SKILL.md into your agent's skills folder. These commands do that for Claude Code:

mkdir -p ~/.claude/skills/ub-mode
curl -fsSL -o ~/.claude/skills/ub-mode/SKILL.md \
  https://raw.githubusercontent.com/unclebob/ub-mode/master/SKILL.md

Then type /ub-mode in Claude Code before asking for a change. The same file works in Cursor (~/.cursor/skills/) and Grok (~/.grok/skills/), per the README.

2. Clone the tools side by side. Mutator's README says crapper has to be checked out next to it, at ../crapper:

mkdir -p ~/tools && cd ~/tools
git clone https://github.com/unclebob/crapper.git
git clone https://github.com/unclebob/dryer.git
git clone https://github.com/unclebob/mutator.git

Each tool's first run creates its own Python virtual environment and installs itself, so you need python3 with venv and pip available. To reproduce exactly what this article reviewed, check out the commits listed in Sources.

3. Get a baseline from your project root. The READMEs show each tool run as ./crapper from a project root; with the tools cloned elsewhere, call them by path:

~/tools/crapper/crapper --changed
~/tools/dryer/dryer --changed --threshold 0.8
~/tools/mutator/mutator src/path/to/the_file_you_are_changing.py

--changed limits the run to files git sees as added or edited, which keeps the first run small. Start Mutator on a single file: its README warns the first run on a tree executes every covered mutant. Later runs are faster because they only re-run survivors and functions whose text changed.

What to expect: Crapper prints a table, worst function first. Dryer prints DUPLICATE score=... lines with both locations. Mutator lists surviving mutants. On most real codebases, the first Mutator report is humbling.

4. Make the results block, not just print. This is the step that matters. The exit codes behave differently, so check the READMEs before scripting:

  • crapper --threshold 10 exits with code 2 when the worst score is higher (crapper README).
  • mutator exits 3 when any mutant survives and 2 when your baseline tests fail (mutator README).
  • dryer exits 0 after a report even when it finds duplicates (dryer README), so you have to check its output yourself.

An exit code is the number a program hands back when it finishes: 0 means fine, anything else means something to look at. A small gate script ties the three together (it needs jq, a command-line tool for reading JSON):

#!/bin/bash
# .claude/hooks/ub-gates.sh
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then exit 0; fi
T=~/tools
$T/crapper/crapper --changed --threshold 10 >&2 || { echo "CRAP over 10" >&2; exit 2; }
if $T/dryer/dryer --changed --threshold 0.8 | grep -q DUPLICATE; then
  echo "Duplication at or above 0.8" >&2; exit 2
fi
$T/mutator/mutator --changed >&2 || { echo "Mutator: survivors or failing tests" >&2; exit 2; }
exit 0

Make it executable with chmod +x .claude/hooks/ub-gates.sh, then register it as a Stop hook in .claude/settings.json, so it runs when Claude tries to finish:

{
  "hooks": {
    "Stop": [
      { "hooks": [ { "type": "command", "command": ".claude/hooks/ub-gates.sh" } ] }
    ]
  }
}

In Claude Code, a hook that exits 2 blocks the action and passes its stderr back as feedback, so Claude sees which gate failed and keeps working. The stop_hook_active check at the top comes from the same guide: Claude Code overrides a Stop hook after eight blocks in a row, and the check stops your hook from looping. The trade-off: on the retry right after a block, the check lets Claude stop without re-running the gates, so treat the hook as a strong nudge and keep CI as the hard gate. The gate commands themselves run in CI unchanged (drop the stop_hook_active lines there), which is where the final gate belongs.

Honest limitations

  • ub-mode has no license file. As of October 28 the repo has a README and the SKILL.md, and no LICENSE. Using it yourself is low-risk in practice, but copying it into a project you distribute leaves your rights unclear. The three tools are 0BSD, so they are not the problem.
  • Mutation testing is slow on a big codebase. Each mutant runs your test suite, with a per-mutant timeout of ten times your baseline test time. Mutator runs one file's mutants in parallel, one worker per core by default, but a slow suite times dozens of mutants is still a long wait. Stay with --changed and single files.
  • The tools are young. Crapper showed nine commits on GitHub when this was written, the latest on October 21. Expect rough edges, especially in TypeScript, Rust and Python, whose mutation rules are adapted from the Java set.
  • Metrics can be gamed. An agent told to get CRAP under 10 can split one honest function into four awkward ones. Mutation testing is much harder to fake, which is another reason it's the gate to keep if you only keep one.
  • The operator lists are short. Mutator flips arithmetic, comparisons, booleans and a few others. Rust gets a narrower set than the rest. A killed mutant means your tests catch those bug types, not every bug.
  • You need working coverage. If your coverage tool isn't set up, Crapper scores the language at 0% and every function looks terrible.

Where this leaves you

ub-mode is a good set of rules written by someone who has been arguing for test-first code for decades. On its own it's another prompt your agent can agree with and then ignore.

The tools change that. Install the skill, clone the three repos, run them on one file today, and wire Mutator in first. Then the next time an agent tells you the tests pass, you'll know whether they check anything.

Sources: unclebob/ub-mode at commit 57a8bdd (Oct 25, 2026): SKILL.md and README; unclebob/crapper at 6fd92c4, unclebob/dryer at 66ff6d2 and unclebob/mutator at a5a0cb8, READMEs and LICENSE files; Claude Code docs, Hooks guide and Output styles, read October 28, 2026; Trendshift daily board, read about 07:35 ET October 28.