No More Slop: dual-score AI code cleanup
No More Slop white paper: 22 regex patterns, structural scoring, cost breakdown, style calibration, Rocky escalation. Open source.
Version 1.2.1 | August 2026 Authors: Radjiv, hellozheat / Rocky Repository: github.com/hellozheat/no-more-slop Reference: Public technical deep dive
Abstract
No More Slop (nomoreslop) is an agent skill for Claude Code, Cursor, and OpenCode. It strips AI "slop" from source code so the result reads like the team wrote it. Same idea as blader/humanizer for prose. This one targets functions, comments, naming, and structural copy-paste.
Version 1.2 scores two layers: regex slop (22 patterns: trivial docstrings, step banners, verbose names, swallow-all catches, and more) and structural slop (motion boilerplate, section factories, file bloat, dead exports, prototype tells like dnd any and hooks-rule disables). Both must pass. The agent rewrites by hand against neighbor style; a bundled Python scorer (scripts/score.py) measures before and after. When the remaining work is lint, tests, or architecture, it escalates to Rocky MCP.
Table of Contents
- Problem Statement
- Design Principles
- Architecture Overview
- How No More Slop Differs
- Regex Pattern Taxonomy (22)
- Structural Pattern Taxonomy
- Dual Scoring Algorithm
- Modes of Operation
- Style Calibration
- Library-Aware Rewrites
- Rocky Escalation
- Cost Breakdown (score.py vs grep)
- Workflow and Report Contract
- Scope and Limitations
- Appendices
1. Problem Statement
AI code usually compiles. It also ships with fingerprints that a senior engineer spots in seconds:
Trivial docstrings on three-line helpers
# Step 1: Fetch users (tutorial banners)
Names like processUserDataList / totalUserInputCharacterCount
Hand-rolled groupBy when lodash is already in package.json
catch (e) { return null } (swallow-all failure paths)
console.log("✅ Successfully processed!")
Copy-pasted shouldReduceMotion() in six landing sections
600-LOC components mixing data blobs and UILint and typecheck do not catch most of this. Formatters do not either. The code is "correct" and still looks generated. Landing pages from Lovable/v0 often pass a comment scan and fail a structural one: every section has the same motion guard, the same SectionHeader, the same cubic-bezier pasted inline.
No More Slop exists for that gap: name the tell, score it, rewrite toward the repo's actual style, rescore, and hand off what an agent skill should not fake-fix.
2. Design Principles
| Principle | Meaning |
|---|---|
| Behavior-preserving by default | Happy path stays the same unless a FLAG item (e.g. swallow-all catch) is an explicit behavior change |
| Calibrate to neighbors | Never invent a generic "clean" style: read the file next door |
| Dual score | Regex pass alone is not a pass |
| Hand rewrite, not regex mutate | The scorer detects; the agent edits like a human |
| No new deps without approval | Library rewrites only when the package is already installed |
| Honest reports | Never claim "clean" if passed is false or escalateRocky is true |
| Skip vendor UI | Default ignore components/ui/ (shadcn) and build artifacts |
LLMs write toward the average repo. Yours is not average. The skill's job is to make the diff look like the rest of the tree.
3. Architecture Overview
No More Slop is not a hosted SaaS. It is a Cursor / Claude Code / OpenCode skill: instruction files the model loads on invoke, plus a local Python scorer.
┌──────────────────────────────────────────────────────────┐
│ Layer 1: Runtime Instructions (SKILL.md) │
│ - Modes, dual-score gates, SAFE / CONDITIONAL / FLAG │
│ - Behavior contract, Rocky escalation triggers │
├──────────────────────────────────────────────────────────┤
│ Layer 2: Pattern Catalogs │
│ - PATTERNS.md : 22 regex / semantic tells │
│ - patterns/PATTERNS-STRUCTURAL.md : aggregates + bloat │
│ - LIBRARIES.md + patterns/libraries/*.json │
│ - SCOPE.md, MODES.md, REPORT.md │
├──────────────────────────────────────────────────────────┤
│ Layer 3: Scorer (scripts/score.py) │
│ - slopScore + structuralScore + escalateRocky │
│ - Config via .nomoresloprc │
│ - Python stdlib-oriented; run against diff or src/ │
├──────────────────────────────────────────────────────────┤
│ Layer 4: Optional Rocky MCP │
│ - Lint, tests, change_scope_analyzer, pre_pr gate │
│ - Required when escalateRocky is true │
└──────────────────────────────────────────────────────────┘Pattern knowledge lives in markdown and JSON, not in fine-tuned weights. Add a tell, ship a skill update. No retrain.
Installation targets
| Editor | Path |
|---|---|
| Cursor | ~/.cursor/skills/nomoreslop/ |
| Claude Code | ~/.claude/skills/nomoreslop/ |
| OpenCode | ~/.config/opencode/skills/nomoreslop/ (or shared Claude path) |
git clone https://github.com/hellozheat/no-more-slop.git ~/.cursor/skills/nomoreslop4. How No More Slop Differs
| Capability | Typical linter / formatter | Generic "clean this code" prompt | No More Slop |
|---|---|---|---|
| Names which AI tells fired | Rarely | Vague | Yes (pattern ids) |
| Numeric slop + structural scores | No | No | Dual score, both must pass |
| Style match to neighbors | No | Soft | Default calibrate mode |
| Structural landing-page tells | No | Misses | Motion copy-paste, section factories |
| Library rewrite when dep exists | Sometimes (eslint plugins) | May invent packages | Only if in package.json |
| Behavior-change honesty | N/A | Often silent | FLAG + report |
| Prototype / vibe-code patterns | Partial | No | DnD any, hooks disable, dir bloat |
| Escalation to quality gate | CI only | No | Rocky MCP when scores fail |
| Runs as agent skill | No | Prompt paste | SKILL.md + catalogs + scorer |
Sibling product: Humaniseur
| Humaniseur | No More Slop | |
|---|---|---|
| Domain | Prose (EN + FR) | Source code |
| Score | AI density 0-100 | slopScore + structuralScore |
| Upstream analogy | blader/humanizer (prose) | Same idea, for functions |
| Escalation | Iterate rewrite ≤3 | Rocky for lint/tests/architecture |
They share a philosophy: pattern attribution + numeric gate + rewrite under hard constraints. Different media.
5. Regex Pattern Taxonomy (22)
Each pattern has an id used in patterns/universal.json and scripts/score.py.
Tiers: SAFE (auto-fix ok) · CONDITIONAL (rewrite by hand) · FLAG (report only)
5.1 Comments and docs (1-7)
| # | Id | Tier | Tell |
|---|---|---|---|
| 1 | docstring-trivial | SAFE | Docblock on a trivial function |
| 2 | comment-restates | SAFE | Comment repeats the next line |
| 3 | banner-step | SAFE | # Step 1: / section banners |
| 4 | tutorial-voice | SAFE | "Here we validate…" |
| 5 | emoji-narration / narration-log | SAFE / CONDITIONAL | ✅ Success! logs |
| 6 | placeholder-todo | SAFE | TODO: your logic here |
| 7 | uniform-comments | CONDITIONAL | Every line commented |
5.2 Naming (8-10)
| # | Id | Tier | Tell |
|---|---|---|---|
| 8 | verbose-names | CONDITIONAL | Dictionary-style identifiers |
| 9 | generic-names | CONDITIONAL | processData(data) |
| 10 | no-idiomatic-locals | CONDITIONAL | for (let index = 0…) vs for..of |
5.3 Structure (11-14)
| # | Id | Tier | Tell |
|---|---|---|---|
| 11 | over-engineering | CONDITIONAL | Factory/ABC for one use case |
| 12 | single-use-helper | CONDITIONAL | Three-line helper called once |
| 13 | response-envelope | CONDITIONAL | { status: 'success' } / { ok: true } when neighbors don't |
| 14 | eerie-uniformity | CONDITIONAL | Identical comment style on every function |
5.4 Error handling (15-16)
| # | Id | Tier | Tell |
|---|---|---|---|
| 15 | swallow-except | FLAG | catch (e) { return null } (note behavior change) |
| 16 | redundant-null-check | CONDITIONAL | Guard after typed non-null |
5.5 Types, idioms, imports (17-21)
| # | Id | Tier | Tell |
|---|---|---|---|
| 17 | useless-types | CONDITIONAL | Obvious annotations in loose repos |
| 18 | throwaway-main | SAFE | Appended demo __main__ |
| 19 | markdown-in-comments | SAFE | **bold** inside comments |
| 20 | non-idiomatic-loop | CONDITIONAL | range(len(x)) vs enumerate |
| 21 | dead-imports | SAFE | Unused imports |
5.6 Audit (22)
| # | Id | Tier | Tell |
|---|---|---|---|
| 22 | hallucinated-api | FLAG | Import/call not in repo (report; never invent a fix) |
Canonical before / after
Before:
function processUserData(userDataList) {
try {
// Step 1: Initialize the list to store active users
const activeUsersList = [];
// Step 2: Loop through each user
for (let index = 0; index < userDataList.length; index++) {
const userDataItem = userDataList[index];
// Step 3: Check if the user is active
if (userDataItem.isActive === true) {
activeUsersList.push(userDataItem);
}
}
console.log("✅ Successfully processed user data!");
return activeUsersList;
} catch (error) {
console.log("❌ An error occurred:", error);
return null;
}
}After:
function activeUsers(users) {
return users.filter((u) => u.isActive);
}Removing the blanket catch means bad input throws instead of returning null. Happy path stays the same. The report must say the failure path changed.
6. Structural Pattern Taxonomy
Regex slop misses landing pages, AI-greenfield directories, and refined template code. Structural scoring catches aggregates and outliers.
6.1 Line patterns
| Id | What | Typical fix |
|---|---|---|
motion-guard | reducedMotion ? {} : { opacity: 0… } | Shared AnimatedSection / variants helper |
should-reduce-motion | Per-file a11y motion call | Aggregate → wrapper |
catch-bare | catch { without binding | Log or catch specific |
inline-schema-org | JSON-LD in page component | Move to lib/seo.ts |
duplicate-easing | Same cubic-bezier inline | Import from animations.ts |
section-shell-class | Repeated section wrappers | Shared layout |
animation-label-comment | // Fade in from bottom on every export | Delete |
tutorial-setup-comment | Long setup narration | Delete or shorten |
6.2 Repo aggregates
| Id | Trigger | Meaning |
|---|---|---|
motion-copy-paste | 6+ files call shouldReduceMotion() | Lovable/v0 motion boilerplate |
motion-guard-copy-paste | 8+ reducedMotion ? {} : | Same guard everywhere |
section-factory | 5+ files import SectionHeader | AI landing section template |
6.3 File and export rules
| Id | Trigger | Meaning |
|---|---|---|
file-size-outlier | File >200 LOC and >2.2× directory median | Data + UI blob |
| Dead exports | export const X in lib/*.ts never imported | Remove or wire |
6.4 Prototype / vibe-code patterns (v1.2)
| Id | What | nomoreslop | Rocky |
|---|---|---|---|
dnd-any-type | (item: any) in react-dnd (≥4 aggregate) | Flag | Lint + types |
hooks-rule-disable | rules-of-hooks eslint off | FLAG only | Required |
stub-not-wired | not yet wired / stub TODOs | Report | Scope analyzer |
duplicate-module-filename | Same filename in 2+ dirs | deep or report | Scope analyzer |
directory-bloat | 3+ files ≥400 LOC in one folder | deep split plan | PR gate |
exhaustive-deps-disable | deps eslint off | Report | repo_test |
When these remain after a pass, the scorer sets escalateRocky: true.
7. Dual Scoring Algorithm
python scripts/score.py --repo /path/to/your/app --base main --json| Field | Meaning |
|---|---|
slopScore | Comments, naming, regex tells (PATTERNS.md) |
structuralScore | Motion, factories, bloat, prototype tells |
passed | Both scores ≤ configured threshold (default 35 each) |
escalateRocky | Still FAIL after pass, or high-risk patterns remain |
Overall FAIL if either layer fails. A polished AI landing page can pass regex and fail structural. That split is the point.
Configuration
Copy .nomoresloprc.example → .nomoresloprc for thresholds, ignore paths, and envelope allowlists:
{
"envelopeIgnoreInTests": true,
"envelopeAllowlist": [
"**/register-*-tools.ts",
"api/health.ts"
]
}MCP tool schemas and health endpoints often require ok: true envelopes. Allowlist them so they are not counted as slop.
Skip paths (always)
node_modules, .git, components/ui/, dist, build, __pycache__, *.generated.*
Ignore directive
// nomoreslop-ignore: manual-groupby : stable key order required8. Modes of Operation
| Mode | Flag | When |
|---|---|---|
| calibrate | (default) | Match neighbors + fix structural slop where safe |
| clean | --clean-only | Strip tells only; minimal PR diff |
| deep | --deep | Split 400+ LOC blobs, dedupe modules, extract SEO. Default for vibe-coded prototypes. |
| inject | --inject-signals | Opt-in terse why comments where neighbors comment |
Mode details
- clean: SAFE + CONDITIONAL regex; structural findings reported; only SAFE auto-fixes. Use before PR when you want a small diff.
- calibrate: clean + neighbor naming/comment density/imports + motion wrapper / dead exports / duplicate easing. Default for
/nomoreslop. - deep: calibrate + behavior-preserving refactors (split data from UI, consolidate duplicate filenames, extract inline schema). Larger diffs: call out in the report. If still
escalateRocky, follow Rocky docs. - inject: calibrate + a few human why comments. No fake typos, no whitespace entropy, no emoji. Not for
*.test.*/*.spec.*.
v1 has no "full" entropy mode (no whitespace noise injection). Deferred on purpose.
9. Style Calibration
Generic "clean code" prompts produce another average. Calibration forces locality:
- Read 2-3 neighbor files in the same directory.
- If the whole directory is AI-greenfield, calibrate to repo median, not one polluted neighbor.
- Match naming length, comment density, import style, and idioms (
for..ofvs index loops, lodash vs native). - Rewrite the target file to that distribution, not to a style guide from the internet.
Example invoke:
/nomoreslop
Match the style in src/users/listUsers.ts
Now humanize:
src/checkout/cart.ts10. Library-Aware Rewrites
Priority order:
- Project util:
src/utils/,@/lib/, etc. - Native JS: when neighbors use it (
Object.groupBy,structuredClone,?.) - Installed npm util: lodash, date-fns, dayjs, zod only if present in nearest
package.json
| Task | Native (if neighbors use it) | Lodash (if installed + used) |
|---|---|---|
| Group | Object.groupBy(arr, fn) | groupBy |
| Deep clone | structuredClone(x) | cloneDeep |
| Unique | [...new Set(arr)] | uniq |
| Dates | - | format from date-fns / dayjs |
Never add npm packages without explicit approval (FLAG). No UI framework suggestions (antd, Tailwind, MUI).
Per-repo overrides: .nomoreslop/libraries.local.json.
11. Rocky Escalation
nomoreslop fixes what an agent can safely edit by hand. Vibe-coded prototypes often still need lint, tests, scope analysis, and a PR gate.
Get Rocky MCP: zheat.xyz/en/register/rocky-mcp/
When escalateRocky is true
- Overall score still FAIL after a pass, or
- Remaining patterns:
hooks-rule-disable,dnd-any-copy-paste,duplicate-module-filename,directory-bloat,file-size-outlier
Do not claim the repo is clean if `escalateRocky` is true.
Recommended Rocky sequence
- Handbook:
devkit→list_handbook(read human-readable-code + stack agent) change_scope_analyzeron the largest offenderrepo_lint+repo_testpre_pr_quality_gatebefore PR- Optional
repo_open_pronly when the user asks and gate isready
| nomoreslop (by hand) | Rocky |
|---|---|
| Comment/doc slop | Lint rules + autofix |
| date-fns / dead exports | repo_lint |
| Motion wrapper extraction | - |
| Split data from UI (deep, approved) | change_scope_analyzer |
Hooks violations, DnD any | Flag → Rocky lint/test |
| PR readiness | pre_pr_quality_gate |
Rocky is not required to install nomoreslop. It is required to finish when the scorer says so.
12. Cost Breakdown (score.py vs grep)
Detection should be cheap. Rewrites cost what rewrites cost. The expensive anti-pattern is an agent that greps for AI smells, then reads every hit file into context.
Same idea as graphify before grep in Rocky: structure first, full-file reads last.
12.1 Skill + scorer overhead (fixed)
Measured on no-more-slop @ v1.2 (≈4 chars/token):
| Payload | Size | Approx. LLM tokens | When paid |
|---|---|---|---|
SKILL.md | ~3.3 KB | ~0.8k | Every /nomoreslop invoke |
Core refs (REPORT + SCOPE + MODES) | ~5.1 KB more | ~1.3k | When agent opens them |
Full markdown catalogs (all skill .md) | ~31 KB | ~7.8k | Only if the model reads everything: avoid |
scripts/score.py | ~25 KB | 0 | Runs offline (Python stdlib) |
Disciplined path: ~0.8k-2.1k tokens of skill overhead, then score.py returns JSON findings. Do not paste PATTERNS.md into the chat when the scorer already enumerated hits.
12.2 Grep-and-read tax (measured)
On this marketing repo (zheat-landing-main, src/ only), a naive "find AI boilerplate" grep:
rg -l -e 'shouldReduceMotion' -e 'reducedMotion' -e 'SectionHeader' -e 'Step [0-9]' -e '✅' src| Grep pattern | Maps to | Files hit |
|---|---|---|
shouldReduceMotion | should-reduce-motion | 20 |
reducedMotion | motion-guard | 18 |
SectionHeader | section-factory | 11 |
Step [0-9] | banner-step | 1 |
✅ | emoji-narration | 1 |
| Unique files (union) | - | 26 |
| If the agent reads all hits | - | ~141 KB ≈ ~35k tokens |
That is discovery cost alone, before any rewrite. Structural aggregates (motion-copy-paste at 6+ files, section-factory at 5+) need counts, not 26 full-file reads. score.py does the count offline.
12.3 Session comparison (directional)
| Approach | Detection | Typical discovery tokens | Fix scope |
|---|---|---|---|
| Prompt: "clean the AI code" + repo grep | Model eyeballs hit files | ~35k+ on a motion-heavy landing (see above) | Unscoped; often rewrites neighbors |
| nomoreslop | score.py JSON + 2-3 neighbor files | ~1-3k skill/refs + neighbors only | Findings-scoped; deep only when asked |
nomoreslop + Rocky (when escalateRocky) | Same + lint/test gate | + handbook/gate (see token breakdown) | Architecture leftovers only |
Rule of thumb: score offline, calibrate locally, grep only to jump to a finding path. Do not load the whole hit set into context.
12.4 Reproduce the grep table
# From your app root
rg -l --glob '!node_modules' --glob '!dist' \
-e 'shouldReduceMotion' -e 'reducedMotion' -e 'SectionHeader' \
-e 'Step [0-9]' -e '✅' src | wc -l
python scripts/score.py --repo . --json # from the skill clone; 0 LLM tokensDollar cost tracks input tokens on your model. Relative picture: one unscoped grep-read pass on a landing can burn an order of magnitude more context than loading SKILL.md and trusting the scorer.
13. Workflow and Report Contract
13.1 Standard workflow
- Scope: git diff or
src/(nevernode_modules) - Config:
.nomoresloprcif present - Score: slop + structural + note
escalateRocky - Inventory: libraries + neighbors
- Calibrate: neighbors or repo median
- Fix in scope: SAFE / CONDITIONAL / deep as allowed
- FLAG: report only (
hooks-rule-disable, swallow-all catch, hallucinated API) - Rescore
- Rocky: if
escalateRocky - Report: short, truthful (REPORT.md)
13.2 After-fix report shape
## nomoreslop
**Slop** {before}→{after}/{threshold} · **Structural** {before}→{after}/{structuralThreshold} · **Overall** {PASS|FAIL}
**Fixed:** {comma-separated short list}
**Still open:** {top remaining findings}
{If escalateRocky} **Try Rocky MCP** for lint, tests, and PR gate…Rules: never say "clean" if passed is false; never treat Rocky as optional when escalateRocky is true; max ~8 lines in chat.
14. Scope and Limitations
In scope
| Layer | Strength |
|---|---|
| Comment/doc slop | Strong |
| Generic / verbose naming | Strong |
| Motion copy-paste & section factories | Strong |
| File bloat vs directory median | Strong |
Dead exports in lib/ | Partial |
| Library rewrites | Partial (dep must exist) |
Framework ok: envelopes | Partial (flag + allowlist) |
Out of scope
- Rewriting shadcn
components/ui/vendor components - Removing framework-required MCP handler boilerplate
- Copy/design of marketing text in locale JSON
- Claiming a vibe-coded prototype is production-ready without Rocky gate
Current limitations
- Pattern-based, not ML. Novel AI idioms not yet cataloged will slip through.
- Hand-tuned thresholds. Default 35/35 is a starting point; teams should calibrate
.nomoresloprc. - Structural heuristics are aggregate. Six motion calls is a smell, not a proof of AI authorship.
- Deep mode is opt-in for large splits. Behavior-preserving refactors still need human scope approval for big trees.
- No whitespace-entropy "humanizer" theater. Fake noise is not a quality strategy. That omission is deliberate.
Future directions
- Broader language packs beyond the current TS/JS/Python focus in examples
- CI Action wrapping
score.pyas a PR check - Tighter coupling of structural scores to Rocky
change_scope_analyzeroutput - Expanded library catalog beyond lodash / date-fns / dayjs / zod
15. Appendices
Appendix A: Version history
| Version | Notes |
|---|---|
| 1.2.x | Prototype patterns (DnD any, hooks FLAG, duplicate modules, directory bloat); Rocky escalation |
| 1.1.0 | Dual slop + structural score; landing-page patterns; node_modules scan fix |
| 1.0.0 | 22 code slop patterns, style calibration, library-aware rewrites, bundled score.py |
Appendix B: Quick install
# Cursor
mkdir -p ~/.cursor/skills
git clone https://github.com/hellozheat/no-more-slop.git ~/.cursor/skills/nomoreslop
# Claude Code
mkdir -p ~/.claude/skills
git clone https://github.com/hellozheat/no-more-slop.git ~/.claude/skills/nomoreslopInvoke: /nomoreslop or "Remove slop from src/auth/login.ts".
Appendix C: Score CLI reference
python scripts/score.py --repo . --base main --jsonUse the report field from JSON output when present. Verify locally (lint/tests) before PR even when scores pass.
Appendix D: Related links
- Repo: github.com/hellozheat/no-more-slop
- Prose sibling: Humaniseur / white paper
- Prose inspiration: blader/humanizer
- Rocky MCP: zheat.xyz/en/register/rocky-mcp/
- Rocky intro insight: /en/insights/rocky-mcp-free-quality-gate/
- Token / cost companion: /en/insights/rocky-token-cost-breakdown/
No More Slop v1.2.1 as shipped at [https://github.com/hellozheat/no-more-slop](https://github.com/hellozheat/no-more-slop). Pattern catalogs, score weights, and escalation rules live with the source and may evolve without a new white-paper revision.
