# Bl Scrub

> Strip the machine residue out of any draft and score it on five gauges before it goes out - invisible characters, em dashes, stock vocabulary, flat rhythm, hedging. Use whenever text needs to sound human, when the user says "humanize", "de-slop this", "does this read as AI", "will this get flagged", and automatically before any BylineKit draft, comment, reply or message is shown to the user.

- Skill: `ehteshambuildagents/bl-scrub` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add ehteshambuildagents/bl-scrub`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ehteshambuildagents/bl-scrub/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ehteshambuildagents (https://skillmd.com/u/ehteshambuildagents)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ehteshambuildagents/bl-scrub

---


# bl-scrub

Two programs live in this folder and they both actually run. Use them. Do not
judge a draft by reading it - the whole point is that the tells are the things
you stop seeing.

```bash
python3 scrub.py draft.txt --explain        # clean it, itemise every change
python3 scrub.py draft.txt --diff           # see exactly what moved
python3 score.py draft.txt                  # score it on five gauges
python3 score.py before.txt after.txt       # prove the delta
python3 scrub.py draft.txt --check          # exit 1 if it still needs work
```

Both read `profile.json`, which holds every rule. No dependencies, no network,
nothing uploaded. Python 3.9 or newer.

## What scrub.py changes, and what it refuses to

Four rule kinds run in order.

**strip** deletes characters a keyboard does not produce: zero-width spaces and
joiners, word joiners, soft hyphens, byte-order marks, directional marks. These
survive copy-paste, they are invisible in every editor the user owns, and they
are the single most mechanical tell in generated text.

**map** swaps a character for a plain one: em dash to comma, en dash to hyphen,
curly quotes to straight, ellipsis to three dots, non-breaking space to space.

**phrase** swaps stock language for plain language, preserving capitalisation.
URLs, email addresses and code spans are shielded and never touched.

**flag** reports a structure and refuses to touch it. This split is the design:
deleting an invisible character is unambiguous, so it happens automatically.
Reshaping a sentence is a judgement call, so it comes back to you with a note.
Acting on the flags is your job, and it is the part that actually moves the
score.

## The five gauges

| gauge | what it measures |
| --- | --- |
| CADENCE | sentence-length variation, and the longest run of same-length sentences |
| GROUND | weighted density of checkable detail: money, percentages, dates, quotes, names |
| REGISTER | inflated diction against plain words |
| RESIDUE | invisible characters, smart punctuation, spacing anomalies |
| STANCE | hedging, person, contractions - whether the writer commits |

The overall number is the **geometric mean**, not the average. Four gauges at 95
and one at 5 averages to 77, which reads as fine, and scores 53, which reads as
the problem it is. A reader only needs one thing to feel wrong.

Bands: under 40 MACHINE, 40-64 MIXED, 65-84 HUMAN, 85+ STRONG.

## How to run it on a draft

1. Write the draft to a file.
2. `python3 scrub.py draft.txt -o clean.txt --explain`
3. `python3 score.py draft.txt clean.txt` to show the delta.
4. Read the flags. Rewrite what they point at - that is the part a regex cannot
   do and the part that takes the score from MIXED to HUMAN.
5. Re-score. Aim for 65 or better before the user sees it.

If a gauge is stuck, the fix is specific:

- **CADENCE low** - you have three or more sentences of the same length in a
  row. Cut one in half. Let another run long.
- **GROUND low** - there are no checkable facts in the draft. Pull one from
  `ledger.md`, or mark the gap `{{like this}}`. Never invent one.
- **REGISTER low** - too many four-syllable words. Swap the worst three.
- **RESIDUE low** - run `scrub.py` again; you are scoring the unscrubbed file.
- **STANCE low** - the draft hedges. Delete "arguably", "generally", "might",
  and say the thing.

## Editing the rules

`profile.json` is meant to be edited and the repo expects you to. If the user
genuinely says "leverage" in their own voice, take it out of the phrase list. If
they have a tic the list does not cover, add it. Changing the file changes both
programs, because both read it.

Run `python3 tests/test_tools.py` from the repo root after editing. The suite
checks the profile for duplicate rules, self-replacing rules and uncompilable
patterns.

## What this is not

These are local heuristics. They are not GPTZero, Originality, Copyleaks,
Winston or Turnitin, they do not call those services, and they cannot promise
those verdicts. They measure properties that generated prose reliably has,
which is why fixing them tends to move other numbers too. Nobody can honestly
sell "undetectable", and the invisible-character pass is a narrow, checkable
cleanup rather than a claim about defeating any watermarking scheme.

