
Slop Detector
- 151 installs
- 325 repo stars
- Updated August 2, 2026
- athola/claude-night-market
Audit AI-generated or bloated code for removable slop while preserving high-value why-comments and safety notes the codebase still needs.
About
Slop-detector (anti-goals module) is a Night Market safety skill for solo developers cleaning up LLM-heavy codebases without accidentally stripping the comments that justify non-obvious behavior. It complements aggressive de-slopping rules by defining Class 1保留 patterns: why-comments, rate-limiter rationales, and unsafe-block safety notes that look dense but are not marketing fluff. The workflow bias is conservative—flag for human decision rather than auto-delete when pattern matchers misfire. Use it during Ship when you are reviewing diffs, shrinking comment noise, or running agentic refactors on Rust or polyglot repos. It depends only on Read in metadata and fits checker-style agent sessions where quality modules stack. Not a substitute for security auditing or functional tests; it specifically prevents false-positive removals that would hide operational constraints.
- Anti-goals module sets a higher bar for deletion than for flagging—when in doubt, leave code and surface a finding
- Preserves why-comments that name constraints, upstream contracts, or counter-intuitive choices
- Always keeps SAFETY comments on unsafe blocks even when they pattern-match generic comment slop
- Classifies comment slop vs information the code cannot carry (Rust examples in SKILL.md)
- Estimated ~500 tokens as a safety-rail slice within the broader slop-detector family
Slop Detector by the numbers
- 151 all-time installs (skills.sh)
- Ranked #375 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/athola/claude-night-market --skill slop-detectorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 151 |
|---|---|
| repo stars | ★ 325 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 2, 2026 |
| Repository | athola/claude-night-market ↗ |
What it does
Audit AI-generated or bloated code for removable slop while preserving high-value why-comments and safety notes the codebase still needs.
Files
AI Slop Detection
Slop is a density problem, not a word problem.
A single "delve" is fine. Five "delves" near a "tapestry" and an "embark" is generated text. This skill scores density per 100 words, marker clustering, and whether the overall register fits the document type. It does not ban words. It flags concentrations.
Execution Workflow
Identify target files and classify them as technical docs, narrative prose, or code comments. Classification feeds context-aware scoring: tier-1 markers in marketing copy score lower than the same markers in API reference.
Language Detection
- Auto-detect language from text content using function word frequency
- Override with explicit
--langparameter (en, de, fr, es) - Load language-specific patterns from
data/languages/{lang}.yaml - Fall back to English if detection confidence is low
- See
modules/language-handling.mdfor cultural calibration and concrete pattern sets
Spelling Normalization (British to American)
Convert British spellings to American by default, in any scanned document, unless the document opts out via .slop-config.yaml (spelling: british) or a per-word allowlist. This is a consistency pass, separate from slop scoring. Use the tested scribe.spelling functions (find_british_spellings, to_american); both preserve case and skip code, inline code, and URLs.
Load: @modules/spelling-normalization.md
Vocabulary and Phrase Detection
Load: @modules/vocabulary-patterns.md
Markers fall into three confidence tiers. Tier 1 words ("delve", "multifaceted", "leverage") appear far more often in AI text than human text. Tier 2 covers context-dependent transitions ("moreover", "subsequently"). Tier 3 covers vapid phrases ("In today's fast-paced world", "cannot be overstated").
| Word | Context | Human Alternative |
|---|---|---|
| delve | "delve into" | explore, examine, look at |
| tapestry | "rich tapestry" | mix, combination, variety |
| realm | "in the realm of" | in, within, regarding |
| embark | "embark on a journey" | start, begin |
| beacon | "a beacon of" | example, model |
| spearheaded | formal attribution | led, started |
| multifaceted | describing complexity | complex, varied |
| comprehensive | describing scope | thorough, complete |
| pivotal | importance marker | key, important |
| nuanced | sophistication signal | subtle, detailed |
| meticulous/meticulously | care marker | careful, detailed |
| intricate | complexity marker | detailed, complex |
| showcasing | display verb | showing, displaying |
| leveraging | business jargon | using |
| streamline | optimization verb | simplify, improve |
Tier 2: Medium-Confidence Markers (Score: 2 each)
Common but context-dependent:
| Category | Words |
|---|---|
| Transition overuse | moreover, furthermore, indeed, notably, subsequently |
| Intensity clustering | significantly, substantially, fundamentally, profoundly |
| Hedging stacks | potentially, typically, often, might, perhaps |
| Action inflation | revolutionize, transform, unlock, unleash, elevate |
| Empty emphasis | crucial, vital, essential, paramount |
Tier 3: Phrase Patterns (Score: 2-4 each)
| Phrase | Score | Issue |
|---|---|---|
| "In today's fast-paced world" | 4 | Vapid opener |
| "It's worth noting that" | 3 | Filler |
| "At its core" | 2 | Positional crutch |
| "Cannot be overstated" | 3 | Empty emphasis |
| "A testament to" | 3 | Attribution cliche |
| "Navigate the complexities" | 4 | Business speak |
| "Unlock the potential" | 4 | Marketing speak |
| "Treasure trove of" | 3 | Overused metaphor |
| "Game changer" | 3 | Buzzword |
| "Look no further" | 4 | Sales pitch |
| "Nestled in the heart of" | 4 | Travel writing cliche |
| "Embark on a journey" | 4 | Melodrama |
| "Ever-evolving landscape" | 4 | Tech cliche |
| "Hustle and bustle" | 3 | Filler |
Step 3: Structural Pattern Detection
Load: @modules/structural-patterns.md
Em Dash Overuse
The single most-cited 2026 AI tell across Wikipedia, the Field Guide, and the Algorithmic Bridge. Detection runs in two modes:
Audit mode (forensic, applied to unknown prose):
- 0-1 per 1000 words: Normal human range
- 2-4: Elevated, review usage
- 5+: Strong AI signal
Prevention mode (applied to docs the agent just generated):
- Target zero. Every em-dash is a finding.
- Replace with commas (asides), parentheses (tangents), colons
(definitions), or periods (separate thoughts). See modules/structural-patterns.md § Em Dash Analysis for the full replacement table.
# Count em dashes in file
grep -o '—' file.md | wc -lTricolon Detection
AI loves groups of three with alliteration:
- "fast, efficient, and reliable"
- "clear, concise, and compelling"
- "robust, reliable, and resilient"
Pattern: adjective, adjective, and adjective with similar sounds.
List-to-Prose Ratio
Count bullet points vs paragraph sentences:
- >60% bullets: AI tendency
- Emoji-led bullets: Strong AI signal in technical docs
Sentence Length Uniformity
Measure standard deviation of sentence lengths:
- Low variance (SD < 5 words): AI monotony
- High variance (SD > 10 words): Human variation
Paragraph Symmetry
AI produces "blocky" text with uniform paragraph lengths. Check whether paragraphs cluster around the same word count.
Step 4: Identity & Voice Leak Sweep (P0)
Load: @modules/identity-and-voice-leaks.md
Some patterns are not slop: they are direct evidence that AI generated text leaked into a published artifact. A single match in this class fails review independently of any other score.
Scan for:
1. Identity leaks ("As a large language model", "as of my training cutoff", "I cannot provide") — severity: critical, no exceptions. 2. Conversational voice leaks ("Hope this helps!", "Great question!", "Sure!") outside transcript blocks. 3. Self-narration of structure ("In this section, we will cover...", "Let's dive into...", "By the end of this guide..."). 4. Hedging seesaw ("While X has its merits, it's not without its challenges"). 5. Contrastive constructions, opening a clause or trailing it: both contrastive negation ("not just X, but Y", "It's not X, it's Y", and the trailing "It's X, not Y" / "Y, not X") and affirmative antithesis ("Less X, more Y", "Where others X, we Y"). Avoid in all but the most necessary cases; keep only when the contrast carries information that survives removal. The trailing copula form ("It's a tool, not a toy") is the easiest to miss because the opener reads as a plain definition.
See the module for the full pattern catalogue and false- positive guidance.
Step 4.5: Sycophantic Pattern Detection
Especially relevant for conversational or instructional content (complements Class 2 of the identity-and-voice-leaks module):
| Phrase | Issue |
|---|---|
| "I'd be happy to" | Servile opener |
| "Great question!" | Empty validation |
| "Absolutely!" | Over-agreement |
| "That's a wonderful point" | Flattery |
| "I'm glad you asked" | Filler |
| "You're absolutely right" | Sycophancy |
These phrases add no information and signal generated content.
Step 4.6: Tier 5 / 2026 Patterns (Prevention-Strict)
The 2026 cross-source consensus (Wikipedia Signs of AI writing, Algorithmic Bridge 10 Signs, Ignorance.ai Field Guide, Stop-Slop Claude skill, George Kao, ContentBeta, OliviaCal) identifies a handful of shapes that dominate post-GPT-5 / post-Claude-4.5 prose. Each is detailed in @modules/vocabulary-patterns.md (lexical form) and @modules/structural-patterns.md (structural form).
| Pattern | Form | Why it matters |
|---|---|---|
| Em-dash overuse | — used as rhetorical pause | Most-cited single tell of 2026 |
| Plus-sign for "and" | "hooks and skills" in prose | Strong: humans have "and" |
| Spatial copula | "lives in", "sits at", "stands as", "boasts" | Inanimate subject with animate verb |
| Negative parallelism (contrastive negation) | "Not X but Y", "No X. No Y. Just Z.", "No X, no Y, no Z", "It's not X, it's Y", "It's X, not Y", "Y, not X" | Rhetorical scaffold with no argument |
| Contrastive parallelism (affirmative antithesis) | "Less X, more Y", "Where others X, we Y", "Humans propose; machines dispose" | Manufactured punch; same scaffold without the "not" |
| Throat-clearing openers | "Here's the thing,", "Look,", "Let that sink in." | Discourse markers signaling nothing |
| Three-fragment burst | "Focused. Aligned. Measurable." | Rhythm without information |
| Significance cluster | "stands as a testament to", "marks a turning point" | Asserts importance without showing it |
| Smart quotes in technical prose | "text" / "text" instead of "text" | Word-processor paste signature |
| Semicolon splice | "The system is fast; it scales" | Prose semicolon joining two independent clauses. Rephrase into two sentences unless absolutely necessary |
| Loop/cascade vocab | "unpack", "surface" (verb), "a quiet shift" | 2026 systems-theory affectation |
| Performative honesty | "to be honest", "Honestly,", "An Honest Review" | Manufactured authenticity |
| Sophistication marker | "survey the prior art", "state of the art", "body of work" | Rigor signaling in non-academic prose |
| Participial tail | ", highlighting", ", underscoring", ", paving the way for" | Fake-analysis tack-on |
| Emphasis crutch | "Full stop.", "Make no mistake", "Read that again." | Manufactured-importance terminator |
Prevention rule: when the slop-detector runs on docs the agent itself just generated (auto-invoked by /doc-generate, /doc-polish, /update-readme, /update-docs, etc.), every match in this table is a hard failure. Fix before write. See modules/remediation-strategies.md § Tier 5 / 2026 for the substitution tables.
Step 5: Calculate Slop Density Score
slop_score = (tier1_count * 3 + tier2_count * 2 + phrase_count * avg_phrase_score) / word_count * 100| Score | Rating | Action |
|---|---|---|
| 0-1.0 | Clean | No action needed |
| 1.0-2.5 | Light | Spot remediation |
| 2.5-5.0 | Moderate | Section rewrite recommended |
| 5.0+ | Heavy | Full document review |
Step 6: Document Economy Check
Load: @modules/document-economy.md
Sentence cleanliness is necessary, not sufficient. A document can score 0 on slop density and still waste reader time by being too long, lacking a thesis, or repeating everything except the one message that matters.
Score the document on three checks (0-2 each):
1. Thesis-first: does the lead state the single takeaway? 2. Sentence weight: does every sentence carry, instance, bound, or repeat the thesis? 3. Repetition rule: is the thesis echoed (good) while ambient repetition is cut (good)?
Combine sentence-level slop score with document-economy score. Both must pass. See modules/document-economy.md for the full rubric, the reader-time budget table, and a worked example.
Step 7: Hallucination & Stub Sweep
Load: @modules/hallucination-detection.md and @modules/stub-and-deferral.md.
Hallucination is not slop: it is wrongness with confident phrasing. Always P0.
Scan for:
1. Phantom code references: every backticked identifier, function name, or file path in prose must exist in the codebase. 2. Phantom dependencies: every recommended pip install / cargo install / npm install must resolve on the relevant registry (slopsquatting defense). 3. Dead URLs: every cited URL should return 200. 4. Made-up config keys: every config key in docs must be read by the code. 5. Bare TODO/FIXME: requires either a tracked-issue link or deletion. 6. Hedging language ("for now", "should work", "placeholder", "dummy"): each one is deferred work. 7. Stub constructs (todo!(), unimplemented!(), NotImplementedError): defects in any path reachable from a public API.
See modules for detection commands and severity matrix.
Step 8: Evidence-Backed Claims (READMEs and public docs)
Load: @modules/evidence-backed-claims.md
Every quality claim must point to evidence in the same repository. No evidence, delete the claim.
For each claim of "production-ready", "fast", "memory- safe", "scalable", etc., verify the corresponding evidence (CI workflow, benchmark directory, audit markers, etc.) actually exists. The module contains the full claim → required-evidence table and language- specific detection commands.
This step is highest-leverage for crate/library/project READMEs, where feature-list buzzword soup is the most common AI-generated failure mode.
Step 9: Apply Anti-Goals (safety check)
Load: @modules/anti-goals.md
Aggressive de-slopping has its own failure modes.
Before applying any fix surfaced by the prior steps, verify it does not violate the anti-goals:
1. Do not strip safety comments (// SAFETY:, // INVARIANT:, etc.) on unsafe, locked, or contract-bearing code. 2. Do not collapse public error variants without an explicit major-version-bump decision. 3. Do not "simplify" typed errors to boxed/dynamic errors. 4. Do not inline a function that has a domain-specific name even if it is short. 5. Do not touch generated code, vendored code, or historical changelog entries. 6. Do not auto-apply confidence: low findings — surface them for human decision.
When in doubt: leave the match flagged, do not delete.
The full multi-pass cleanup workflow
For systematic project-wide cleanup, run the multi-pass workflow in order. See @modules/cleanup-workflow.md for the full ten-pass methodology and the rationale for the ordering. Summary:
| Pass | Focus |
|---|---|
| 0 | Pre-slop sweep: secrets, agent configs |
| 1 | Surface lint floor (formatter and linter) |
| 2 | Hallucination & stubs (modules: hallucination, stub-and-deferral) |
| 3 | Identity & voice leaks |
| 4 | Comment slop (translation, marketing, banner, deferral) |
| 5 | Prose slop (vocabulary, structural, document-economy, and evidence-backed-claims) |
| 6 | Code idiom (delegate to language-specific plugins) |
| 7 | Architecture (judgment-heavy; see anti-goals) |
| 8 | Tests (tautology, mocks, snapshots) |
| 9 | README & public docs |
| 10 | Establish guardrails (CI, lints, constitution) |
Cardinal rules: one pass per commit; deletion beats rewriting; do not silently apply low-confidence fixes; stop when a pass finds nothing.
Empirical baseline (cite when justifying severity)
Load: @modules/empirical-baseline.md for the 2025-Q1 2026 research baseline that justifies the severity weighting. Headline numbers:
- AI PRs ship 1.7x more total issues, 1.75x more
logic/correctness issues, 2.74x more XSS, ~8x more excessive I/O than human-only PRs (CodeRabbit, Dec 2025).
- 92-96% of detected AI-code issues are maintainability
("code smell"), not correctness (Sonar, Q4 2025).
- Model-specific patterns: GPT fabricates; Claude omits.
Calibrate the audit accordingly.
When a finding's severity is challenged in review, cite from this module rather than asserting from authority.
Step 10: Generate Report
For per-finding output that reviewers can accept or reject independently, use the canonical structured format defined in @modules/structured-finding-output.md. Each finding carries file, line, category, severity, confidence, evidence, rationale, fix, and (for high-confidence) diff. Auto-apply policy is set by confidence; never auto-apply confidence: low.
Summary report format (human-readable):
## Slop Detection Report: [filename]
**Overall Score**: X.X / 10 (Rating)
**Word Count**: N words
**Markers Found**: N total
### CRITICAL (P0, must resolve before merge)
- Line 8: "As a large language model". IDENTITY LEAK
- Line 47: References `Client.connect_with_timeout(...)` —
HALLUCINATION (method does not exist; closest match is
`Client.connect`)
- Line 102: "production-ready" claim with no CI workflow
. UNVERIFIED CLAIM
### High-Confidence Markers (vocabulary)
- Line 23: "delve into" -> consider: "explore"
- Line 45: "rich tapestry" -> consider: "variety"
### Structural Issues
- Em dash density: 8/1000 words (HIGH)
- Bullet ratio: 72% (ELEVATED)
- Sentence length SD: 3.2 words (LOW VARIANCE)
### Phrase Patterns
- Line 12: "In today's fast-paced world" (vapid opener)
- Line 89: "cannot be overstated" (empty emphasis)
- Line 134: "Let's dive into" (self-narration of structure)
### Tier 5 / 2026 Patterns
- Line 19: "The skill lives in `plugins/scribe/`" → "is in"
(spatial copula, inanimate subject)
- Line 27: "hooks + skills" → "hooks and skills" (plus-sign
conjunction in prose)
- Line 34: "It's not a tool, it's a transformation" →
rewrite positively (negative parallelism / contrastive
negation)
- Line 38: "Less config, more code" → state plainly
(contrastive parallelism; keep only if load-bearing)
- Line 56: "Here's the thing," → delete (throat-clearing
opener)
- Line 78: "Focused. Aligned. Measurable." → "Focused,
aligned, and measurable." (three-fragment burst)
- Line 91: 3 smart quotes outside code blocks (Word-processor
paste signature)
### Stub & Deferral
- Line 56: bare `// TODO: handle expired tokens` (no
tracked issue link)
- Line 71: "for now, we recommend" (deferral language)
### Document Economy Score: X / 6
- Thesis-first: 1/2 (thesis present but buried in para 3)
- Sentence weight: 1/2 (~65% of sentences earn weight)
- Repetition: 2/2 (thesis echoed; ambient repetition cut)
### Recommendations
1. **CRITICAL**: delete line 8 identity leak before merge
2. **CRITICAL**: replace `Client.connect_with_timeout`
with `Client.connect(opts)` and update example
3. **CRITICAL**: either add CI + version >= 1.0 to back
"production-ready", or delete the claim
4. Replace [specific word] with [alternative]
5. Convert bullet list at line 34-56 to prose
6. Hoist the thesis (line 47) into the lead paragraph
7. Link bare TODOs to tracked issues or delete code path
### Confidence-low findings (require human decision)
- Line 89: bullet count of 8 may be appropriate for this
enumeration; do not auto-flatten
- Line 156: `Manager` suffix may be domain-meaningful;
verify before renamingPer anti-goals.md: surface confidence: low findings in a separate section. Do not silently apply them.
Module Reference
- See
modules/fiction-patterns.mdfor narrative-specific slop markers - See
modules/remediation-strategies.mdfor fix recommendations
Integration with Remediation
After detection, invoke Skill(scribe:doc-generator) with the --remediate flag to apply fixes, or manually edit using the report as a guide.
Exit Criteria
- All target files scanned
- Density scores calculated
- Report generated with specific, line-anchored fixes
- High-severity items flagged for immediate attention
Anti-Goals: What NOT to Clean Up
Aggressive de-slopping has its own failure modes.
This module is the safety rail. Every other module in the slop-detector tells you what to flag and remove; this one tells you what to leave alone even when it pattern- matches. The bar for deletion is higher than the bar for flagging.
When in doubt: leave it alone, surface it as a finding, and let a human decide.
Class 1: Comments that earn their bytes
These look like slop on density alone but carry meaning the code does not:
Why-comments (always keep)
A comment that explains why a non-obvious decision was made is the highest-value comment class. The code is the "what"; comments earn their place by carrying the "why".
// We sleep 200ms specifically because the upstream
// rate-limiter buckets at 5/s; faster retries return
// 429 and waste a slot:
thread::sleep(Duration::from_millis(200));This pattern-matches as a "magic constant with comment" which §3.2 flags as marketing slop, but it is the opposite of slop: the comment names the constraint that makes the constant correct.
Rule: a comment that names a constraint, references an upstream contract, or explains a counter-intuitive choice is information the code cannot carry. Keep it.
Safety comments on unsafe blocks (always keep)
// SAFETY: the caller has already validated that `idx`
// is within `slice.len()`; see the bounds check in
// `Buffer::insert` two frames up:
unsafe { *slice.as_ptr().add(idx) }These are required by clippy::undocumented_unsafe_blocks and are part of the contract the code makes with reviewers. Stripping them removes the only proof the unsafe block is correct.
Structured-meaning comment prefixes (always keep)
Many codebases adopt structured prefixes for specific comment classes. Examples:
// SAFETY: ...
// INVARIANT: ...
// LOCK ORDER: ...
// BLOCKING: ...
// PERFORMANCE: ...
// SECURITY: ...
// THREAD: ...These are project-specific contracts. They are not slop even if they look formulaic: the formula is the contract. Audit before stripping; do not strip on pattern match alone.
Regression-pinning tests (always keep)
A test that looks trivial (assert!(parse("").is_err())) may be pinning a regression. Deleting it because it "looks slop" is exactly how the regression returns.
Rule: tests with bug-tracker references in their name or comment (test_regression_1234, // repro for #1234) must not be removed without an explicit decision that the regression class is no longer relevant.
Class 2: Code that should not be flattened
thiserror-style error variants (do not collapse)
#[derive(Error)]
pub enum Error {
#[error("connection refused")]
ConnectionRefused,
#[error("timeout after {0}s")]
Timeout(u64),
#[error("invalid response: {0}")]
InvalidResponse(String),
// ... 9 more variants, several rare ...
}The "12 variants, of which 3 are ever constructed internally" pattern from §6 looks like inflation, but public error enums are part of the API. Removing variants:
- Breaks downstream pattern-match exhaustiveness checks.
- Removes information that helps users handle specific
failures.
- Cannot be reversed without a major-version bump.
Rule: never collapse public error variants without an explicit major-version-bump decision.
Small named helpers (do not inline)
fn is_ascii_alphabetic_or_underscore(c: char) -> bool {
c.is_ascii_alphabetic() || c == '_'
}This is two lines and looks like inflation, but the name makes the calling code readable:
if is_ascii_alphabetic_or_underscore(c) { ... }vs. the inlined version:
if c.is_ascii_alphabetic() || c == '_' { ... }The inline reads as "checking ascii alpha or underscore"; the named version reads as "checking the leading-char rule". The function carries domain meaning.
Rule: a one-line helper with a domain-specific name is not slop. Inline only when the name adds no clarity over the inline expression.
Result<T, MyError> (do not "simplify" to Box<dyn Error>)
The "simplify the error type" instinct is backward direction. Typed errors at API boundaries are correct; boxed dynamic errors are tutorial code.
Rule: never replace a typed error with Box<dyn Error> or anyhow::Error in a public library API as part of a slop sweep. That is an API-design decision, not a cleanup.
Class 3: Files that must not be touched
Generated code
build.rs output
prost/tonic generated modules
bindgen output
serde_derive/serde_json schemas
GraphQL codegen
OpenAPI/Swagger codegen
protoc outputGenerated code follows the conventions of its generator. It often looks bloated by human-written-code standards because the generator is conservative. Editing it is pointless: the next regeneration overwrites the changes.
Rule: detect generated code by header comment ("DO NOT EDIT", "AUTOMATICALLY GENERATED", or generator-specific markers) or by directory convention (target/, generated/, gen/, __generated__/). Exclude from all slop scans.
Vendored / third-party code
Code copied from another project (with attribution) follows the upstream's conventions. Reformatting it to match local style breaks the ability to diff against upstream for security updates.
Rule: directories named vendor/, third_party/, thirdparty/, or external/ are excluded from style sweeps.
Historical changelog entries
## [1.2.0] - 2024-03-15
- Added `parse_json` function with `comprehensive` error
reporting. <-- "comprehensive" is slop in new prose,
but this is a historical artifact.Past releases are immutable. Editing changelog entries rewrites history readers may have relied on (vendor SBOMs, audit trails, blog-post backreferences).
Rule: anything before the ## [Unreleased] header in a CHANGELOG file is read-only.
Migration scripts and historical fixtures
A test fixture that contains slop because the original input was sloppy is correct as-is. The test exists to prove the parser handles real-world slop, and "fixing" the fixture removes the very thing under test.
Rule: directories named fixtures/, golden/, testdata/, examples/ (when used as test inputs) are excluded from prose sweeps.
Class 4: Patterns that look generated but are not
Section headings that follow a template
## Installation
## Usage
## Configuration
## API Reference
## Contributing
## LicenseThese look formulaic because every README has them. They are not slop: they are the convention. Removing them because they are predictable would make the README harder to navigate, not easier.
Rule: structural conventions (canonical README sections, standard rustdoc sections like # Examples / # Errors / # Panics, conventional commit prefixes) are not slop. Flag only when content inside the section violates a rule.
Em-dash density in narrative writing
§2.3 flags em-dash density >3 per 500 words as a signal. This is a heuristic, not a rule. A novelist or essayist who uses em dashes deliberately for rhythm is not generating AI text.
Rule: em-dash density flags require human review before edits. In narrative or literary genres, leave the em dashes alone unless other signals also fire.
Class 5: When a finding is "low confidence"
The slop-detector should never auto-apply low-confidence fixes. From the structured-finding format in §10:
The agent should not silently apply low-confidence
fixes; surface them as findings with confidence: lowand let a human decide.
Categories that default to confidence: low:
- Premature abstraction (§4.9): impossible to prove an
abstraction is wrong without knowing future use.
- Generic name slop (§4.10): "Manager" is wrong in some
domains and exactly right in others.
- Bullet-list-bloat: the right number of bullets depends
on whether the content is actually enumerable.
- Em-dash density in narrative.
- Anything in
examples/or under a `// AI-generated:
do not delete` marker.
Override mechanism
For unavoidable false positives, projects should support inline ignore markers:
<!-- slop-detector:ignore-next-line vocabulary -->
The comprehensive integration tests cover ...
<!-- slop-detector:ignore-block start -->
[block of intentionally-formulaic content]
<!-- slop-detector:ignore-block end -->// slop-detector:allow(needless_clone)
let owned = borrowed.clone();These are escape hatches, not silencers. Each ignore marker should explain why in a trailing comment:
<!-- slop-detector:ignore-next-line vocabulary
reason: "comprehensive" is the documented test-suite
name; renaming it breaks external references -->Without the rationale, the ignore is itself a defect.
CI Integration
Use the --ci flag to produce machine-readable output and exit with a non-zero code when slop density exceeds a threshold. Intended for use in GitHub Actions and pre-commit hooks.
Flags
| Flag | Default | Description |
|---|---|---|
--ci | off | Emit JSON output instead of the markdown report |
--threshold <float> | 3.0 | Score above which the run fails (exit code 1) |
JSON Output Schema
When --ci is set, write a single JSON object to stdout:
{
"files": [
{
"path": "docs/guide.md",
"score": 2.4,
"rating": "Light",
"markers": 7
}
],
"summary": {
"total_files": 1,
"avg_score": 2.4,
"max_score": 2.4,
"pass": true
}
}Field Definitions
| Field | Type | Description |
|---|---|---|
files[].path | str | Path to the scanned file (relative to repo root) |
files[].score | float | Slop density score (0–10+) |
files[].rating | str | One of: Clean, Light, Moderate, Heavy |
files[].markers | int | Total marker count in the file |
summary.total_files | int | Number of files scanned |
summary.avg_score | float | Mean score across all files |
summary.max_score | float | Highest score across all files |
summary.pass | bool | True when max_score <= threshold |
Exit Codes
| Code | Meaning |
|---|---|
| 0 | All files pass (max_score <= threshold) |
| 1 | One or more files exceed the threshold |
| 2 | Execution error (file not found, parse failure, etc.) |
Instructions for Claude
When --ci appears in the invocation:
1. Run the full detection workflow as normal. 2. Collect per-file results: path, score, rating, marker count. 3. Compute summary fields: total_files, avg_score (round to 2 decimal places), max_score. 4. Set pass to true when max_score <= threshold, false otherwise. 5. Write the JSON object to stdout. Do not write the markdown report. 6. Report exit code 1 if pass is false, 0 if true, 2 on any error.
Do not mix prose with the JSON output. The JSON must be the only content on stdout so it can be parsed by downstream tools.
GitHub Actions Example
- name: Slop check
run: |
result=$(claude -p "Skill(scribe:slop-detector) --ci --threshold 3.0 docs/")
echo "$result" | jq .
pass=$(echo "$result" | jq -r '.summary.pass')
if [ "$pass" != "true" ]; then
echo "Slop threshold exceeded" >&2
exit 1
fiPre-commit Hook Example
# .pre-commit-config.yaml
- repo: local
hooks:
- id: slop-check
name: Slop density check
language: system
entry: bash -c 'claude -p "Skill(scribe:slop-detector) --ci --threshold 3.0" "$@"'
types: [markdown]
pass_filenames: trueCleanup Workflow
Run passes in order. Each pass is independent. Commit between passes. Prefer deletion over rewriting.
This module gives the multi-pass cleanup methodology. The order matters: each pass assumes the prior passes have landed. Mixing concerns within a pass produces diffs that no reviewer can audit.
The cardinal rules
1. One pass per commit. A commit titled "cleanup" that touches comments, prose, error handling, and tests is not reviewable. Split. 2. Deletion beats rewriting. When in doubt, remove the material. AI slop is additive; the cheapest correct fix is almost always to take material away. 3. Cleanup decisions on a compromised baseline are themselves compromised. Run Pass 0 first. 4. Do not silently apply low-confidence fixes. Surface them as findings, let a human decide (see anti-goals.md). 5. Stop when a pass finds nothing. Do not invent work to fill the pass.
Pass 0: Pre-slop sweep (always first)
Before any cleanup, audit for things that should not be in the repo at all:
- Committed agent-config files (
CLAUDE.md,.cursorrules,
AGENTS.md, .codex/config.toml, .aider.conf.yml, etc.) with secrets or broad capability grants.
- Committed credentials (run
gitleaks/trufflehog). - Untrusted MCP server entries.
- Hooks that auto-execute on session start.
Commit any redactions or revocations before any other cleanup, since later passes assume an uncompromised baseline.
# Pre-slop sweep checklist
gitleaks detect --no-banner
ls -la | grep -E '^.*(CLAUDE|cursor|codex|aider|kiro)'
find . -name '.mcp' -o -name 'mcp.json' -type fPass 1: Surface lint sweep
Run the cheap automated detectors. Fix or delete what they flag. This is the floor, not the ceiling.
# Linter floor
[language-specific formatter] --check
[language-specific linter] --strict
# Dependency hygiene
[unused-dep detector]
[vulnerability scanner]Commit. If your linter supports an "no escape hatches" rule (e.g. allow_attributes = "deny" in Rust clippy), enable it. it prevents the most common AI-agent dodge: silencing a lint with #[allow(...)] instead of fixing the underlying code.
Pass 2: Hallucination sweep
Run Skill(scribe:slop-detector) module hallucination-detection.md:
- Every quoted identifier in prose: does it exist?
- Every backticked file path: does it exist?
- Every cited URL: does it 200?
- Every recommended package install: does it resolve on
the relevant registry?
- Every config key in docs: does the code read it?
Then run module stub-and-deferral.md:
- Every TODO/FIXME/XXX/HACK: is there a tracked issue
link, or is the surrounding code path defunct?
- Every
// for now,// placeholder,// dummy: same
question.
- Every
todo!()/unimplemented!()/
NotImplementedError: is this reachable from a public API?
Resolve, link, or delete. Commit per category.
Pass 3: Identity & voice leaks
Run module identity-and-voice-leaks.md:
- P0. identity leaks: any "as a large language model",
"as of my training cutoff", etc.; delete on sight.
- Conversational voice leaks: "Hope this helps!",
"Great question!", "Sure!" outside transcript blocks; delete the phrase, keep substance.
- Self-narration of structure: "In this section, we
will cover..."; strip framing, start at content.
This pass is small but high-priority. Identity leaks in particular fail review independent of any other score.
Pass 4: Comment slop
Walk every code comment and doc comment. For each, ask: does this convey information not present in the code, names, or signatures? If no, delete.
For doc comments specifically (docstrings, ///, //!, JSDoc, etc.), enforce the docstring/implementation ratio:
| Ratio (doc lines / impl lines) | Action |
|---|---|
| >= 2.0 | CRITICAL: almost certainly slop; trim or rewrite |
| >= 1.0 | warning; investigate |
| ~ 0.5 | acceptable for public API |
| < 0.5 | balanced or code-heavy; usually fine |
Trivial helpers should often have no doc comment at all — the function name and signature is the spec. See anti-goals.md Class 1 for what to keep.
Commit.
Pass 5: Prose slop in markdown & docstrings
Walk every *.md and every multi-line doc-comment block. Apply:
vocabulary-patterns.md. tier-1 banned words and
phrases.
structural-patterns.md. em dashes, bullet ratio,
paragraph blockiness.
document-economy.md. thesis-first, sentence weight,
repetition rule, reader-time budget.
evidence-backed-claims.md. every quality claim
points to repo evidence.
Strike banned vocabulary, verify quality claims, remove emoji from headers, flatten over-deep heading trees. Commit per category.
Pass 6: Code idiom sweep
Apply the language-specific anti-pattern modules. For Rust, see pensive:rust-review (this scribe skill delegates code idiom checks to that plugin). For Python, see parseltongue:python-pro. For shell, see pensive:shell-review.
Calibrate by model: per the 2025-26 cross-evaluation research, GPT-family-generated code has more concurrency mistakes; Claude-family-generated code has more omissions. Weight your audit accordingly.
Commit per category, not per file.
Pass 7: Architecture slop
This is the highest-judgment pass and the most prone to over-correction. See anti-goals.md Class 2 for what not to flatten.
Look for:
- Traits with one implementor, not used as
dyn, not
used for mocking, not exported.
- "Manager" / "Handler" / "Service" structs that own one
method.
- Builder patterns for structs with two fields.
- Layered structures where each layer just delegates one
method to the next.
- An error enum with 12 variants, three of which are ever
constructed (but see anti-goals: do not collapse public variants).
Prefer to leave a borderline abstraction in place rather than delete one that turns out to be load-bearing. Commit.
Pass 8: Test slop
Apply tests/ audit:
- Tautological tests (
assert!(s.is_some())after
Foo::new() -> Foo).
- Tests that re-implement the function under test in the
assertion.
- Mock-everything tests that prove only that orchestration
calls the orchestrator.
- Snapshot tests on data with no semantic meaning.
#[ignore]tests with no comment.- One giant
test_everything()asserting 30 unrelated
things.
Where pure functions are under-covered, prefer property- based tests (hypothesis/proptest/quickcheck) and golden-file tests for serializers. Both resist the "test mirrors implementation" failure mode.
Run mutation testing if available: it is the cheapest way to expose tests that pattern-match correctly but catch nothing.
Commit.
Pass 9: README and public docs
Apply evidence-backed-claims.md strictly. The README should open with:
1. One sentence: what it is. 2. Minimal working example (5-15 lines, runnable). 3. Install instruction.
Then features, configuration, contributing, etc. Move deep API documentation to docs.rs / readthedocs / wiki. Strip emoji from headers. Verify badges resolve and are green.
Commit.
Pass 10: Establish guardrails
The cleanup is incomplete without preventing the slop from coming back. Add:
- A
CONSTITUTION.md(or equivalent project rules file)
with immutable rules the AI and contributors must respect (see evidence-backed-claims.md for the pattern).
- Strict linter configuration in the build config
(e.g. [lints.clippy] block in Cargo.toml).
- A CI step running the slop-detector on changed prose
files.
- Pre-commit hooks running the cheap detectors locally.
Commit. This is what prevents the slop you just removed from coming back next sprint.
Order rationale
Why this order specifically:
1. Pass 0 (pre-slop sweep) before everything because cleanup decisions on compromised baselines are themselves compromised. 2. Pass 1 (surface lint) before anything semantic because the linter is the cheapest signal and clears the trivial finds. 3. Pass 2 (hallucination & stubs) before prose work because polishing text that is wrong about the world is wasted polish. 4. Pass 3 (identity leaks) early because it is small, high-severity, and pattern-matchable. 5. Passes 4-5 (comments and prose) before code idiom because comment removal often makes code idiom issues visible. 6. Pass 6 (code idiom) before architecture because localized fixes inform whether structural patterns are real. 7. Pass 7 (architecture) before tests because architecture churn changes which tests matter. 8. Pass 8 (tests) before README because final test coverage informs what claims the README can make. 9. Pass 9 (README) last among content passes because it is downstream of everything else. 10. Pass 10 (guardrails) closes the loop.
Stopping rule
Stop when a pass finds nothing. Do not invent work to fill the pass. The slop sweep is a removal operation; "nothing to remove" is success, not failure.
If consecutive passes find nothing, the cleanup is done. Commit, push, and let it land.
Config File Support
Load a .slop-config.yaml file to adjust detection behavior for the current project.
Discovery
Walk up the directory tree from the target file toward the repo root. Stop at the first .slop-config.yaml found. If none exists, use built-in defaults.
target file: /project/docs/guide.md
check: /project/docs/.slop-config.yaml
check: /project/.slop-config.yaml <- found, use this
check: /.slop-config.yaml (would stop here at repo root)To find the repo root, check for a .git directory while walking up.
YAML Schema
# .slop-config.yaml
# Extra words treated as tier-1 markers (score: 3 each)
custom_words:
tier1:
- synergize
- ideate
tier2:
- impactful
- learnings
# Words to skip during detection (exact match, case-insensitive)
allowlist:
- robust # used correctly in our engineering specs
- leverage # used correctly in our physics docs
- labour # proper noun: the Labour Party, keep British
# British -> American spelling normalization.
# american (default): convert British spellings to American
# british: keep British spelling, report nothing
# off: skip the spelling pass entirely
spelling: american
# Score thresholds (warn < error required)
thresholds:
warn: 2.0 # flag for review
error: 5.0 # fail CI check
# Glob patterns for files to skip entirely
exclude_patterns:
- "vendor/**"
- "**/*.generated.md"
- "CHANGELOG.md"
# Inherit from a base config, then apply overrides above
extends: "../../.slop-config.yaml"Field Reference
| Field | Type | Default | Description |
|---|---|---|---|
custom_words.tier1 | list[str] | [] | Additional tier-1 words (score 3 each) |
custom_words.tier2 | list[str] | [] | Additional tier-2 words (score 2 each) |
allowlist | list[str] | [] | Words to ignore during detection (also suppresses spelling conversion for these words) |
spelling | str | american | Spelling normalization mode: american, british, or off |
thresholds.warn | float | 2.0 | Score at which to warn |
thresholds.error | float | 5.0 | Score at which to fail |
exclude_patterns | list[str] | [] | Glob patterns for files to skip |
extends | str | none | Path to a base config to inherit from |
Loading Procedure
1. Walk directories from target file up to repo root, collecting any .slop-config.yaml files found. 2. If extends is set in a config, load that base config first. 3. Merge: base config values are the defaults; the child config overrides them. 4. For list fields (custom_words.tier1, allowlist, etc.), merge lists rather than replace. 5. Validate that thresholds.warn < thresholds.error. If not, warn and use built-in defaults.
Merging Custom Words with Built-in Patterns
After loading the config:
- Append
custom_words.tier1to the built-in TIER1 word list before scanning. - Append
custom_words.tier2to the built-in TIER2 word list before scanning. - After each match, check if the matched word appears in the
allowlist. If so, discard the match.
The allowlist check is case-insensitive and applied per-match, not per-word-list.
Exclude Pattern Matching
Before scanning a file, check its path against each pattern in exclude_patterns using fnmatch. If any pattern matches, skip the file and report it as excluded.
import fnmatch
def is_excluded(file_path: str, patterns: list) -> bool:
for pattern in patterns:
if fnmatch.fnmatch(file_path, pattern):
return True
return FalseReporting
When a config file is active, include it in the report header:
Config: /project/.slop-config.yaml
Allowlist: robust, leverage (2 words)
Custom tier-1: synergize, ideate (2 words)
Thresholds: warn=2.0, error=5.0Document Economy
A document costs the sum of its readers' time. Earn that cost or cut.
This module adds document-level checks to the slop detector. The other modules score sentences and words; this one scores whether the document earns its existence at all.
When to apply
Run this check on any document that will be read more than once or by more than one person. Skip it for ephemeral 1:1 messages where a brain dump is fine.
The principle is invariant: writing time should scale with total reader time. A 1:1 note absorbs no one else's hours, so optimize for your throughput. A skill file loaded 50× per day absorbs hours of reader-time per week, so optimize for theirs.
The three checks
Check 1: Thesis-first
The first paragraph (or, for SKILL files, the activation cue plus the first paragraph after the H1) must state the single message you want the reader to walk away with.
A thesis is not a topic.
| Topic (weak) | Thesis (strong) |
|---|---|
| "This skill detects slop." | "Slop is a density problem, not a word problem." |
| "How to write tutorials." | "A tutorial moves a reader from cannot to can. Everything else is decoration." |
| "Code review checklist." | "Review for the bug you would ship, not the style you would prefer." |
Failure modes:
- "This document covers X, Y, and Z." That is a table of
contents. It tells the reader what is in the document, not what to take from it.
- Burying the takeaway after 200 lines of context.
- Three competing theses fighting for the lead. Pick one.
Fix: rewrite the lead until you can highlight one sentence and say "if the reader only reads this, the document succeeded."
Check 2: Sentence weight
Every sentence must do one of:
1. State the thesis. 2. Instance the thesis (a concrete example of it). 3. Bound the thesis (when it does not apply). 4. Repeat the thesis (allowed, see Check 3).
Sentences that do none of those four are bloat. Cut them.
Common bloat patterns:
- "It's also worth noting that..." — if it is worth
noting, note it. Drop the throat-clear.
- "As mentioned above..." — if you must remind the
reader, your structure is wrong.
- Restating the heading in the body. The heading
already said it.
- Transitional connective tissue ("Now that we have
covered X, let us turn to Y"). Just turn to Y.
- "In summary" sections that re-list bullets the reader
just read.
Check 3: The repetition rule
Repeat the thesis. Cut everything else that repeats.
The thesis is the message you want internalized. People skim. They remember what they see three times. Echo the thesis in the intro, in the middle, and at the close. Vary the surface; hold the meaning.
Everything else that repeats is bloat:
- Restated headers.
- Multiple examples making the same sub-point. One is
proof. Two is emphasis. Three is filler.
- "TL;DR" boxes that duplicate the conclusion.
- Section summaries that just re-list the section.
The reader-time budget
Estimate before you write. Then check after.
| Audience | Reads | Time per read | Total budget |
|---|---|---|---|
| 1 person, 1:1 | 1 | 2 min | 2 min |
| 5-person team | 1 | 5 min | 25 min |
| 50-person org doc | 1 | 5 min | ~4 hours |
| 50-person skill, loaded daily | ~250/yr | 30 sec | ~10 hours/year |
| Public skill, 1000 users | varies | 30 sec | days/year |
The author's writing time should match the budget. If the budget is 10 hours and you spent 30 minutes, you owe more polish, more cuts, or both. If the budget is 5 minutes and you spent a week, you over-built; ship and move on.
This is asymmetric on purpose. Cheap to write, expensive to read is the failure mode worth catching.
Scoring rubric
For each check, score 0-2:
| Score | Thesis-first | Sentence weight | Repetition |
|---|---|---|---|
| 0 | No identifiable thesis | <50% sentences earn weight | No thesis repetition; ambient repetition |
| 1 | Thesis present but buried or diluted | 50-80% earn weight | Some thesis repetition; some ambient |
| 2 | Thesis stated in lead, single and clear | >80% earn weight | Thesis repeated 3+ times; ambient cut |
Document economy score: sum / 6.
| Score | Action |
|---|---|
| 5-6 | Ship |
| 3-4 | Revise: identify the cuts |
| 0-2 | Restart from the thesis |
A document can have a clean sentence-level slop score (0-1.0) and still score 0/6 here. Sentence cleanliness is necessary, not sufficient.
Worked example
Before (score: 1/6):
# Logging Configuration Guide
>
This document covers the various aspects of configuring
logging in our system. Logging is an important part of
any production application. There are many ways to
configure logging and this guide will walk you through
them. We will look at log levels, log destinations, log
formatting, and log rotation. By the end of this guide
you will understand how to configure logging.
>
## Log Levels
>
Log levels are used to indicate the severity of a log
message. There are several log levels you can use. The
log levels are DEBUG, INFO, WARN, ERROR, and FATAL.
[...]
Problems:
- No thesis, only a topic ("covers various aspects").
- "Logging is important" carries no information.
- The "we will look at" sentence is a TOC.
- "By the end of this guide" is filler.
- The Log Levels section restates the heading.
After (score: 5/6):
# Logging Configuration
>
Log what you would page someone for. Drop the rest.
>
Most logging configuration time is spent suppressing
noise from libraries you do not own. The defaults below
bias toward silence; raise the volume only for the code
you would actually wake up to debug.
>
## Log levels
>
Use INFO for events you would mention in a postmortem.
Use WARN for events that should not happen but did not
break anything. Use ERROR for events that broke something
a user could see. DEBUG and FATAL are mostly traps:
DEBUG ships verbose noise to production, FATAL implies
the process should die but rarely does.
[...]
The thesis ("log what you would page someone for") shows up in the lead, frames the level explanations, and would recur in destinations and rotation sections.
Integration
The full slop-detector pipeline now runs:
1. Sentence-level scoring (vocabulary, structure, sycophancy) 2. Document-economy scoring (this module) 3. Combined report
A document passes only when both layers pass. Sentence slop is necessary; document economy is sufficient.
Empirical Baseline (2025-Q1 2026)
Treat AI-generated artifacts as unreviewed contractor work, not as junior-developer work. The cross-study record is unambiguous about which defect classes occur at which rates; calibrate the cleanup priorities accordingly.
This module is reference material. Cite from it when a finding's severity needs justification. Re-validate the numbers every six months: the empirical landscape moves fast.
Headline ratios (CodeRabbit, December 2025)
Analysis of 470 GitHub PRs (320 AI-co-authored, 150 human-only), normalized to issues per 100 PRs with Poisson rate ratios.
| Defect class | AI vs. human multiplier |
|---|---|
| Total issues | ~1.7x |
| Critical issues | ~1.4x |
| Logic / correctness | 1.75x |
| Algorithm and business logic errors | >2x |
| Error handling gaps | ~2x |
| Code readability | >3x |
| Naming inconsistency | ~2x |
| Improper password handling | ~2x |
| Insecure object references | ~2x |
| Cross-site scripting (XSS) | 2.74x |
| Insecure deserialization | ~1.8x |
| Excessive I/O operations | ~8x |
Cleanup priority implication: weight logic/correctness, error-handling gaps, readability/naming, and excessive I/O checks more heavily than the average linter would. These are the categories where AI-amplified rates are highest.
Quality and maintainability data
From GitClear's analysis of 211M changed lines, 2020-2024:
- Code duplication: 5+-line duplicated blocks grew
~8x. In 2024, copy-pasted lines exceeded refactored (moved) lines for the first time on record.
- Code churn: code reverted or rewritten within two
weeks rose from a 3.1-3.3% baseline (2021) to 5.7-7.9% (2024-2025).
- Refactoring rate: cleanup-of-existing-code as a
share of changed lines collapsed from ~25% (2021) to <10% (2024). AI accelerates "add new" while suppressing "improve existing."
From METR's July 2025 randomized controlled trial on 16 experienced OSS contributors:
- Developers expected a 24% speedup.
- Developers reported feeling 20% faster.
- Developers were measurably 19% slower.
Cleanup-phase implication: when an AI agent (or a human and AI) reports that a module has been cleaned up, do not trust the felt-productivity report. Verify with external metrics: lint counts, defect counts, test pass rates, mutation kill rates.
Maintainability dominates correctness
From Sonar's December 2025 leaderboard analysis across GPT-5.2 High, GPT-5.1 High, Gemini 3 Pro, Opus 4.5 Thinking, and Claude Sonnet 4.5:
- 92-96% of detected issues across all models are "code
smells" (maintainability), not correctness.
Implication: the cleanup payoff is heaviest in readability, structure, and dead-code removal: not in correctness fixes. Optimize the slop-detector for those categories.
Model-specific failure patterns
From Sonar's evaluation work, also Q4 2025:
| Model | Distinctive failure mode |
|---|---|
| GPT-5.2 High | ~470 concurrency issues per MLOC (2x next-closest, 6x Gemini 3 Pro). Expect Send/Sync mistakes, MutexGuard-across-await, broken channel patterns. |
| Claude Sonnet 4.5 | ~195 resource-management leaks per MLOC (~4x GPT-5.1). 198 blocker-severity vulns per MLOC (vs 44 for Opus 4.5 Thinking). Expect file/socket lifetime mistakes, missed Drop ordering, path-traversal-class flaws. |
| Gemini 3 Pro | ~200 control-flow mistakes per MLOC, ~4x Opus 4.5 Thinking. Expect incorrect match arms, off-by-one loops, missed early returns. |
| Opus 4.5 Thinking | Best on security (44 blocker vulns/MLOC) but tends toward verbose, abstraction-heavy code. |
General correlation Sonar identified: as models reason harder ("Thinking" / "High"), outputs grow more verbose and more cyclomatically complex. The cleanup burden scales with reasoning depth, not just code volume.
Hallucination patterns by model family
From cross-evaluation work (Anthropic and DEV.to community benchmarks, Q1 2026):
| Family | Tendency |
|---|---|
| GPT-5.x | Fabricates: invents function names, library methods, config keys, API endpoints that look plausible but do not exist. Verify every use/import, every dep, every method, every config flag. |
| Claude 4.x | Omits: silently skips edge cases, drops a match arm, leaves None-paths unhandled. Errors of omission are easier to find in review than confident fabrications, but more likely to slip through tests that mirror the implementation. |
| Both | Produce plausible doc comments that paraphrase the function name without adding information. |
Implication for the slop-detector audit:
- For Claude-generated code: weight toward incomplete
match arms, missing error paths, skipped edge cases.
- For GPT-generated code: weight toward fabricated
identifiers, made-up clippy/lint names, hallucinated crate/package names, and concurrency mistakes.
If you cannot tell which model generated a region, run the full audit. It is never wrong, just sometimes redundant.
Security baseline
From Veracode's 2025 GenAI Code Security Report, re-tested March 2026:
- 45% of AI-generated code samples on
security-sensitive tasks fail OWASP Top 10 tests.
- 86% failed XSS-defense tasks.
- 88% failed log-injection defense.
- The pass rate has not improved across multiple testing
cycles.
From Apiiro's Fortune-50 enterprise study (Dec 2024 - Jun 2025):
- AI-assisted developers commit code at 3-4x their
non-AI peer rate.
- Their monthly security findings rose ~10x.
- A 153% increase in design-level security flaws
specifically (auth bypasses, IDOR, missing trust-boundary validation, broken session management) flaws line-level patches cannot fix.
From Trend Micro's TrendAI report (March 2026):
- AI-related CVEs reached 4.42% of all CVEs in 2025
(up 34.6% YoY).
- 2,130 AI CVEs disclosed in 2025 alone.
- 26.2% of scored AI CVEs are high-severity.
- Includes the slopsquatting attack class: adversaries
registering hallucinated package names that AI tools recommend.
Implication: the slop-detector should treat unverified package recommendations as critical findings (see hallucination-detection.md Class 2).
What this baseline does not mean
These are important so the data does not produce its own bad cleanup decisions:
1. It does not mean AI code is always worse than human code. GitClear's January 2026 follow-up using direct API integration found a substantial productivity multiplier for "Power Users" of AI tools. The defect/duplication problem is real and the productivity gain is real; both can be true. 2. It does not mean ban AI tooling. It means: spend the saved time on review, not on accepting more PRs. 3. It does not validate prose-level "AI tells" as proof of authorship. Em-dash density, vocabulary clustering, and similar surface signals are triage hints, not evidence. Do not gate human work on them. Do not accuse contributors based on them. 4. It does not justify aggressive over-cleanup. See anti-goals.md. The slop-detector is a tool for reviewers, not a hammer for autonomous agents.
Currency note
Model behavior changes faster than these notes can. The specific multipliers above will be wrong in 12 months. The pattern. AI artifacts have predictable defect profiles that differ by family and by reasoning depth — will persist.
Re-validate the model-specific numbers every six months against:
- Sonar's live LLM leaderboard
- The latest CodeRabbit / Apiiro / Veracode quarterly
reports
- Your own internal defect data, if you track it
The goal of this module is not to memorize numbers. It is to give the slop-detector and its users a defensible posture: informed skepticism, calibrated to data, not performative caution.
Sources for citation
When a slop-detector finding needs justification, cite from:
- *CodeRabbit, State of AI vs Human Code Generation***
(Dec 17, 2025): ratios.
- *GitClear, AI Copilot Code Quality: 2025 Data*** (2025):
duplication, churn, refactoring rate.
- METR, arXiv:2507.09089 (July 2025): productivity
perception vs. reality.
- Sonar LLM leaderboard (live, Q4 2025+): model-specific
failure modes.
- *Apiiro, 4× Velocity, 10× Vulnerabilities*** (June 2025):
security findings rate.
- *Veracode, 2025 GenAI Code Security Report*** (Aug 2025,
March 2026 update): OWASP fail rates.
- *Trend Micro, TrendAI 2025*** (March 2026): CVE share,
slopsquatting.
- AI Code in the Wild, arXiv:2512.18567 (Dec 2025):
repository-scale empirical study.
- Antislop, arXiv:2510.15061 (Oct 2025, ICLR 2026): most
rigorous current paper on prose slop.
Evidence-Backed Claims
Every quality claim must point to evidence in the same repository. No evidence, delete the claim.
This module operationalizes the §2.4 README rule from the AI slop playbook. It is the highest-leverage prose check for crate/library/project READMEs, because feature-list buzzword soup is the most common AI-generated README failure mode.
The rule
For each quality claim, the repository must contain the evidence that backs it. If the evidence does not exist, the claim is marketing slop and must be deleted.
Required-evidence table
| Claim | Required evidence |
|---|---|
| "Production-ready" | CI workflow, release process doc, version >= 1.0, named adopters |
| "Fast" / "Blazing fast" / "High-performance" | benches/ directory with reproducible benchmark and numbers |
| "Memory-safe" / "Safe" | #![forbid(unsafe_code)], audited unsafe blocks, or fuzz harness |
| "Zero-cost" | benchmark vs. equivalent unabstracted code |
| "Type-safe" | named the type system property, or strict mode enabled |
| "Fault-tolerant" | tests covering the failure modes named |
| "Resilient" | retry logic and tests of failure paths |
| "Scalable" | load tests, capacity numbers, or deployment story |
| "Battle-tested" | named adopters, version history, issue-resolution track |
| "Robust" | replace with concrete error-handling guarantees and test coverage |
| "Idiomatic" | replace with "passes [linter] — -D warnings" |
| "Secure" | threat model, audit reference, or cargo audit/equivalent in CI |
| "Easy to use" | three-line "minimal example" that actually runs |
| "Well-tested" | coverage % from a real run, or test count |
| "Well-documented" | docs.rs / readthedocs link with non-trivial content |
| "Cross-platform" | named platforms with CI matrix |
| "Lightweight" | binary size, dep count, or LOC number |
| "No dependencies" | empty [dependencies] or named exceptions |
Detection
For each claim word/phrase in the README and other public-facing docs, check whether the corresponding evidence exists.
Pattern 1: simple grep and file existence
# Does the README claim "production-ready"?
grep -i 'production[- ]ready' README.md
# Then verify the evidence:
[ -d ".github/workflows" ] && echo "CI exists" || echo "MISSING: CI"
[ -f "RELEASE.md" ] || [ -f "RELEASING.md" ] && echo "release doc exists" || echo "MISSING: release process"
grep -E '^version = "[1-9]' Cargo.toml *.toml 2>/dev/null || echo "MISSING: version >= 1.0"Pattern 2: claim → benchmark cross-reference
# Does the README claim "fast"?
grep -iE '\b(fast|blazing|high[- ]performance)\b' README.md
# Then verify benchmarks exist with results:
[ -d "benches/" ] || [ -d "benchmarks/" ] || echo "MISSING: benches dir"
ls benches/ 2>/dev/null | grep -E '\.(rs|py|js|ts)$' || echo "MISSING: benchmark sources"
# And ideally that BENCHMARKS.md exists with numbers:
[ -f "BENCHMARKS.md" ] && echo "results documented" || echo "WARN: no published results"Pattern 3: safety claims vs. unsafe usage
# Does the README claim "safe" or "memory-safe"?
grep -iE 'memory[- ]safe|"safe"' README.md
# Then verify the project enforces it:
grep -r '#!\[forbid(unsafe_code)\]' src/ && echo "unsafe forbidden"
# Or that any unsafe blocks have SAFETY comments:
unsafe_count=$(rg -c 'unsafe\s*\{' src/ | awk -F: '{s+=$2} END {print s}')
safety_count=$(rg -c '// SAFETY:' src/ | awk -F: '{s+=$2} END {print s}')
[ "$unsafe_count" -gt 0 ] && [ "$safety_count" -lt "$unsafe_count" ] && \
echo "FAIL: $unsafe_count unsafe blocks, only $safety_count SAFETY comments"README-specific anti-patterns
Beyond claim verification, the playbook §2.4 lists structural anti-patterns common in AI-generated READMEs:
Pattern A: Features-list-as-first-section
A good README opens with: 1. One sentence: what it is. 2. Minimal working example (5-15 lines, runnable). 3. Install instruction.
Then features, configuration, contributing, etc.
Detection: if the first section after the title is a bullet list of features, flag for restructuring.
Pattern B: TOC in a short README
A 200-line README does not need a table of contents. GitHub auto-generates one. Detection:
lines=$(wc -l < README.md)
has_toc=$(grep -c '## Table of Contents\|## Contents' README.md)
[ "$lines" -lt 300 ] && [ "$has_toc" -gt 0 ] && \
echo "FAIL: README is short enough that TOC is noise"Pattern C: Emoji-prefixed feature bullets
Lines like:
- 🚀 Fast
- 🔒 Safe
- 🎯 EasyStrip the emoji; if the bullet still says something, keep the bullet. If the bullet only had value because of the emoji, delete it.
Detection:
grep -E '^- (\\xF0\\x9F|🚀|🔒|🎯|✨|⚡)' README.mdPattern D: "Why X?" section that doesn't compare
A "Why our crate?" section that recites generic benefits ("safe, fast, easy") without naming specific alternatives or making falsifiable claims is slop. Either:
- Rewrite to compare against named alternatives with
specifics.
- Delete the section.
Pattern E: Status badges that don't match reality
A green CI badge for a workflow that no longer exists is worse than no badge.
Detection:
# Extract badge URLs
grep -oE 'https://img.shields.io/[^)]+|https://github.com/[^/]+/[^/]+/actions/[^)]+' README.md
# Verify each linked workflow exists and is green
# (manual check, or use gh-actions API)Pattern F: -rs / -rust suffix in crate name
Per Rust API Guidelines: redundant. Detection in Cargo.toml:
grep -E '^name = ".*-(rs|rust)"' Cargo.toml && echo "REDUNDANT suffix"(Same applies to -py/-python, -js/-javascript, -go/-golang in their respective ecosystems, flag the redundancy.)
Output format
[FINDING N]
file: README.md
line: 12
category: evidence-backed-claims/unverified-claim
severity: medium
confidence: high
evidence: > A blazing-fast, production-ready, memory-safe
> library for parsing JSON:
rationale: Three claims in one sentence, none backed:
- "blazing-fast": no benches/ directory
- "production-ready": no CI, version 0.1.2
- "memory-safe": no #![forbid(unsafe_code)]
and 14 unsafe blocks in src/, only 3 with
SAFETY comments
fix: Either:
1. Add the evidence (benches, CI, audit unsafe)
and keep the claims.
2. Replace with: "A library for parsing JSON.
Pre-1.0; benchmarks in progress."
Default to option 2 unless option 1 is on
the actual roadmap.Anti-goal
This module is not a vibe check. It does not flag claims that are imprecise: only claims that are unevidenced. "Fast" is fine if benches/ exists. "Safe" is fine if unsafe blocks are documented. The bar is evidence, not modesty.
When in doubt: flag for human review. The cost of a false positive (a real claim deleted) is higher than the cost of a false negative (a false claim left in).
Fiction-Specific AI Pattern Detection
Creative writing has distinct AI tells beyond technical documentation markers.
Physical/Emotional Cliche Beats
AI defaults to formulaic body language and emotional descriptions.
Breath Cliches (Score: 3 each)
"breath he didn't know he was holding"
"let out a breath"
"released a breath"
"exhaled a breath he'd been holding"
"breath caught in [his/her] throat"Body Protest Metaphors (Score: 2 each)
"[body part] protested"
"his shoulder protests"
"muscles screamed in protest"
"[body part] screamed"Emotion Washing (Score: 3 each)
"relief washed over"
"[emotion] washed over"
"a sense of [emotion] washed"
"dread pooled in [his/her] stomach"
"heart clenched"Vague Depth Markers (Score: 3 each)
"something in [his/her] expression"
"something in [his/her] eyes"
"something shifted"
"something precious"
"something soft in"
"something [adjective] in [his/her] voice"Decision/Reaction Avoidance (Score: 2 each)
"doesn't know what to do with that"
"didn't know what to do with"
"couldn't process"
"brain short-circuited"Narrative Structure Cliches
Simile Abuse (Score: 2-4 each)
"like it's the most natural thing in the world" (4)
"like a vow" (3)
"like a promise" (3)
"like coming home" (3)
"like a blade wrapped in silk" (4)
"stone in still water" (4)Rhetorical Emphasis (Score: 3 each)
"He x—really x—" pattern
"He looked at her—really looked"
"He listened—really listened"
"Not x but y" / "Not x, just y"
"didn't [verb] but [verb]"Sentence Fragment Overuse
AI uses stylistic fragments excessively for "punch":
"Tosses it somewhere behind him."
"Gone."
"Just like that."
"Nothing more."One or two per scene is stylistic; five or more signals generation.
Word-Level Fiction Tells
Overused Descriptors
cataloguing, measured, clocked, flickering,
perhaps, maybe, just, that, something,
kind of, sort ofAction Inflation
screaming (metaphorical: "hip screaming")
protesting (body parts)
dancing (non-dance contexts: "fingers dancing")
singing (objects: "the blade sang")Adverb Clustering
AI repeats the same adverbs within short spans:
- "softly" appearing 3+ times in a scene
- "quietly" used with multiple actions
- "slowly" describing everything
Check adverb variety: count unique adverbs vs total adverb uses.
Dialogue Patterns
Sycophantic Character Speech
"That's a great idea"
"You're absolutely right"
"I never thought of it that way"Overly Clean Attribution
AI avoids "said" excessively:
he murmured, she whispered, he breathed,
she exhaled, he muttered, she remarkedNatural dialogue uses "said" frequently without variation.
Chapter/Scene Structure
Upbeat Endings
AI ends scenes with false resolution:
- Characters reaching understanding
- Hopeful forward-looking statements
- Emotional catharsis without buildup
Character Name Patterns
AI defaults to certain names with suspicious frequency:
- Sarah Chen (extremely common in AI fiction)
- Emma, Liam, Maya, Marcus
- Asian names with Western first names
Detection Regex
FICTION_PATTERNS = [
r"breath \w+ didn't know",
r"let out a breath",
r"\w+ protested?(?:\s|$)",
r"(?:relief|fear|dread|panic) washed over",
r"something (?:in|about) (?:his|her)",
r"like (?:it's|it was) the most natural",
r"—really \w+—",
r"(?:didn't|doesn't) know what to do with",
]Scoring for Fiction
def fiction_slop_score(text):
patterns_found = []
for pattern, score in FICTION_PATTERNS:
matches = re.findall(pattern, text, re.IGNORECASE)
patterns_found.extend([(m, score) for m in matches])
# Weight by scene length (per 500 words)
word_count = len(text.split())
raw_score = sum(score for _, score in patterns_found)
normalized = (raw_score / word_count) * 500
return {
'score': min(10, normalized),
'patterns': patterns_found,
'density': len(patterns_found) / (word_count / 500)
}Remediation Notes
Fiction slop requires rewriting, not just word replacement. The underlying issue is reliance on familiar emotional beats rather than character-specific reactions.
Recommend: 1. What would THIS character actually do/feel? 2. What sensory details are specific to this scene? 3. What's the subtext beneath the surface emotion?
Hallucination Detection
Hallucination is not slop: it is wrongness with confident phrasing. Always P0.
This module covers the class of AI defects where the generated text refers to something that does not exist: a function never defined, a library never published, a config key never read, a cited URL that 404s. Slop detectors that only score word density miss these entirely, because each individual word is fine.
The 2025-26 cross-evaluation work (see empirical-baseline.md) is unambiguous: GPT-5.x family fabricates more (invented identifiers, made-up library APIs); Claude 4.x family omits more (skipped match arms, missed error paths). Both produce plausible-but-fake doc references. Calibrate the audit weighting to the model that produced the artifact, but always run the full check.
Class 1: Phantom code references
Comments and docs that reference code that does not exist in the repository.
Patterns to scan
"see also `module_name::function`"
"as defined in `path/to/file.rs`"
"this calls into `helper_fn()`"
"the `FooConfig` struct"
"flag this with `--enable-bar`"Detection rule
For each backtick-quoted identifier or path in prose, verify it actually exists:
# Identifiers
rg -c "\bfunction_name\b" --type-add 'src:*.{py,rs,ts,js,go}' --type src
# File paths
[ -f "path/to/file.rs" ] && echo "exists" || echo "MISSING"
# Module paths (language-dependent)
rg "^(pub )?(mod|fn|struct|enum) function_name" --type srcFindings format:
confidence: highif the identifier appears in prose
but rg finds zero matches in source.
confidence: mediumif the identifier exists but in a
surprising location (suggests rename without doc update).
Common phantom-reference patterns
- "deprecated in favor of
new_api": verifynew_api
exists and is not itself deprecated.
- "see
tests/test_foo.py": verify file exists. - "the
--strictflag": verify the CLI parses that flag. - "the
MAX_RETRIESconstant": verify the constant is
defined and exported.
Class 2: Phantom external dependencies
Library imports and config keys that AI invented because they sounded plausible.
Patterns to scan
For Rust/Python/JS, every import or use statement should resolve to a real published crate/package.
# Python: every import in source
rg "^(?:from|import)\s+\w+" --type py | sort -u
# Rust: every use statement
rg "^use\s+[a-z][a-z0-9_]+::" --type rust | sort -u
# Cross-reference with declared dependencies
diff <(extract-imports) <(extract-deps)"Slopsquatting": the security flank
Trend Micro's 2024-25 research documented adversaries registering hallucinated package names that AI agents suggested. A doc that recommends installing a non-existent package is a vector for supply-chain attack the next time that name does get registered.
Detection:
- Cross-reference every
pip install,cargo install,
npm install, gem install recommendation in docs against the relevant registry.
- Flag any package that does not currently resolve.
- Especially flag packages with names suspiciously close
to popular ones (e.g. requesst for requests).
Made-up identifiers (model-specific weighting)
Per 2025-26 research:
- GPT-family-generated text: weight toward verifying
every method name, attribute name, lint name, config key actually exists.
- Claude-family-generated text: weight toward verifying
every match arm and error variant is reachable, every edge case is handled.
A simple cross-reference grep catches most of these.
Class 3: Dead URLs and broken citations
AI confidently cites docs URLs that 404 or arXiv papers that do not exist.
Detection
# Extract all URLs from docs
rg -o 'https?://[^\s\)]+' docs/ *.md
# Verify each (rate-limited, batch)
while read url; do
status=$(curl -sI -o /dev/null -w '%{http_code}' "$url" --max-time 5)
[ "$status" != "200" ] && echo "DEAD: $status $url"
done < urls.txtSpecial cases
- arXiv citations: verify the arXiv ID exists.
https://arxiv.org/abs/{ID} should return 200.
- GitHub references:
github.com/user/reposhould
resolve. github.com/user/repo/blob/main/path should also resolve at the named branch.
- Internal docs: relative links should point to files
that exist at that path in the repo.
Class 4: Phantom test/file references
Comments that reference test files, fixtures, or modules that do not exist.
# SLOP: references file that doesn't exist
# See tests/integration/test_full_flow.py for end-to-end coverageIf tests/integration/test_full_flow.py does not exist, the comment is hallucinated. Either: 1. The test was removed and the comment was not updated. 2. The test was never written and the comment is invention.
Either way, the comment is a defect: it tells future maintainers that coverage exists where it does not.
Detection
# Pull every file path from comments
rg -o '(?:tests?/|src/|docs?/)[\w/.-]+\.\w+' --no-filename .
# Verify each path exists
while read path; do
[ ! -e "$path" ] && echo "MISSING: $path"
done < referenced-paths.txtClass 5: Made-up configuration
Config keys, environment variables, or feature flags that appear in docs but are never read by the code.
Detection
For every config key mentioned in docs, verify the code actually reads it:
# Pull config keys from docs (tune the regex per config format)
rg -o '`[A-Z][A-Z0-9_]+`' --no-filename docs/ *.md | sort -u > docs-keys.txt
# Pull keys actually read in code
rg -o 'env::var\("([A-Z_]+)"' src/ | sort -u > code-keys.txt
rg -o 'os.environ\["([A-Z_]+)"\]' src/ | sort -u >> code-keys.txt
# Diff
comm -23 docs-keys.txt code-keys.txt
# Lines in docs but not in code = phantom config keysOutput format
Hallucination findings should follow the structured-finding format with severity: critical (since the documentation is actively wrong, not just bloated):
[FINDING N]
file: docs/api.md
line: 47
category: hallucination/phantom-identifier
severity: critical
confidence: high
evidence: > Use `Client.connect_with_timeout(...)` to ...
rationale: `Client.connect_with_timeout` does not exist
anywhere in the codebase. `Client.connect`
accepts a timeout via the `Options` struct:
fix: Replace with `Client.connect(opts)` and update
the surrounding example accordingly.Integration
Hallucination detection runs before the cleanup workflow (see cleanup-workflow.md Pass 2). Cleaning up text that references phantoms means polishing prose that is wrong about the world: fix the wrongness first, then polish.
A document with any critical-severity hallucination finding must not pass review until the finding is resolved or explicitly waived with rationale.
Identity & Voice Leaks
Some patterns are not "slop": they are direct evidence that AI generated text leaked into a published artifact. These are P0 fails: detect, alert, and require remediation before merge.
This module covers three distinct classes that the vocabulary and structural detectors miss because they are about register and self-reference, not word density:
1. Identity leaks: the model talking about itself. 2. Conversational artifacts: chat-register openers and closers that escaped into a document. 3. Self-narration of structure: the model describing what it is about to write rather than writing it.
All three are absolute, not probabilistic. A single identity leak in a published doc is enough to fail review.
Class 1: Identity leaks (P0, always fail)
These phrases reveal that the text was generated by an LLM that did not realize it was being asked to write as the project. Found in any published artifact, they must be deleted on sight.
Direct identity claims
As a large language model
As an AI assistant
As an AI language model
I am an AI
I am Claude
I am ChatGPT
I'm an AICapability/limitation disclaimers
I cannot provide
I do not have access to
I do not have the ability to
I am not able to
my knowledge is limited to
my training data does not includeTemporal disclaimers
as of my last update
as of my training cutoff
as of my knowledge cutoff
as of [date], I do not know
based on information available to meSelf-reference in technical text
in my response
in this response
the following response
let me [verb] (as opener; "let me explain", "let me clarify")These last patterns are softer signals. "let me" is normal in some genres, but in technical documentation, README files, or commit messages, they are voice leaks worth flagging.
Detection rule
Any match in the identity-leaks list = severity: critical, confidence: high, action: remove before merge.
There is no tuning here. Identity leaks are categorical.
Class 2: Conversational voice leaks
Chat-register pleasantries that escaped from a turn-of- conversation into a published document. The signal is the exclamation point and the social warmth: both are appropriate in chat, neither is appropriate in a README.
Servile openers (Score: 4 each, strong signal)
"Sure!"
"Sure thing!"
"Certainly!"
"Absolutely!"
"Of course!"
"I'd be happy to"
"I'd love to help"
"I'm happy to"
"Great question!"
"Great point!"
"Excellent question!"
"That's a wonderful question"Servile closers (Score: 4 each, strong signal)
"Hope this helps!"
"Hope that helps!"
"Let me know if you have any questions"
"Feel free to ask if anything is unclear"
"Happy coding!"
"Happy to help with anything else"
"Best of luck!"
"Good luck!"Validation phrases (Score: 3 each, sycophancy signal)
"You're absolutely right"
"That's a great point"
"You raise a valid concern"
"That's a really good question"
"I see what you mean"
"That makes total sense"Detection rule
These belong in conversation, not in artifacts. Found in README, docs, code comments, commit messages, or PR descriptions: delete the phrase, keep the substance.
Special case: an assistant: block in a saved transcript is fine: it is supposed to sound like chat. Flag only when these phrases appear outside explicit transcript blocks.
Class 3: Self-narration of structure
The model telling the reader what it is about to write, or what the reader is about to read. The structure- narration steals reader attention from the actual content.
"We will" / "Let's" openers (Score: 3 each)
"In this article, we will explore"
"In this guide, we will cover"
"In this section, we will discuss"
"In this post, we will look at"
"This article explores"
"This guide covers"
"Let's dive into"
"Let's break this down"
"Let's take a closer look"
"Let's explore"
"Let's examine"
"We'll cover"
"We'll explore"
"We'll discuss"
"By the end of this guide / article / section""First, second, third" scaffolding (Score: 2)
"First, we will [verb]"
"Next, we will [verb]"
"Finally, we will [verb]"
"To begin with"
"Moving on"
"Wrapping up"Empty conclusions (Score: 3 each)
"In conclusion,"
"In summary,"
"To summarize,"
"All in all,"
"It is clear that"
"Overall," (as paragraph opener)
"Ultimately," (as conclusion opener)Detection rule
Strip the framing; start the sentence at the substantive content. "In this section, we will discuss authentication" becomes "Authentication uses..." or simply the first substantive sentence of the actual discussion.
If a doc cannot survive removing all of these phrases, the doc is mostly scaffolding and needs to be rewritten or deleted, not patched.
Class 4: Hedging seesaw and parallel "not just"
Two more patterns that the Wikipedia Signs of AI Writing research identifies as primary AI markers.
Hedging seesaw (Score: 3)
The "while X has its merits, it also has its challenges" construction. AI defaults to balanced two-sided framing even when one side is clearly stronger.
"While [X] has its merits, [Y] is not without its challenges"
"While [X] is powerful, it can also be [negative]"
"On one hand [X], on the other hand [Y]"
"Despite [X], it is important to consider [Y]"
"That said, [X]" (as reflexive softener)The fix is to take a position. If the analysis genuinely warrants a hedge, name which trade-off and why: the seesaw form just performs balance without earning it.
Parallel "not just" / "not only" (Score: 3)
Wikipedia's Signs of AI Writing lists this as a primary marker. The construction creates artificial parallelism that human writers rarely use as a paragraph opener.
"Not only [X], but also [Y]"
"Not just [X], but [Y]"
"It's not only [X]: it's also [Y]"Especially as a paragraph opener. As a single sentence inside a longer argument it can be fine; as a structural reflex it is generated.
The "not just / not only" family is the contrastive negation half of a broader device. Its affirmative sibling has no "not" anchor and is covered next.
Affirmative antithesis (Score: 3)
The same opposition scaffold with the negation removed: two parallel clauses set against each other to manufacture punch.
"Less [X], more [Y]" ("Less config, more code")
"Where others [X], we [Y]"
"[Old]: X. [New]: Y."
subject-swap clauses ("Humans propose; machines dispose")Treat it the same as contrastive negation: avoid in all but the most necessary cases, keeping it only when both sides are concrete and the contrast survives removal. The comparative "Less X, more Y" form is regex-detectable; subject-swap and chiasmus are judgment-level (confidence: low), so surface them for human decision rather than auto-rewriting. The full detection regex, scoring, and remediation table live in structural-patterns.md § Contrastive Parallelism. Leave Before:/After: labels on code examples alone.
Detection rule
Both patterns are pattern-matchable but contextual. Flag on match; require human review before deletion since each has legitimate uses. The diagnostic question: is this the shortest way the author could have said the thing? If no, cut.
Combined scoring
Identity leaks (Class 1) are always severity: critical regardless of count. Classes 2-4 contribute to the sentence-level slop score per the standard formula.
def has_identity_leak(text: str) -> list[str]:
"""Return list of identity-leak matches found in text.
Any non-empty result = critical-severity finding."""
leaks = []
for pattern in IDENTITY_LEAK_PATTERNS:
if re.search(pattern, text, re.IGNORECASE):
leaks.append(pattern)
return leaksA document with even one identity leak fails review until the leak is removed, regardless of its other scores.
False positives
A few legitimate uses of these patterns:
- A blog post about AI writing that quotes an LLM
saying "As a large language model": fine in a quote block or fenced code block.
- A test fixture that intentionally contains a sycophantic
opener to test the slop detector: should live under tests/ and be excluded by path.
- A glossary that defines what an "identity leak" is and
shows examples: mark with <!-- slop-detector:ignore --> or equivalent project-specific marker.
When in doubt: leave the match flagged, surface it to a human reviewer, do not auto-delete.
Language Handling: Detection and i18n Patterns
Two related concerns are bundled in one module:
1. Language detection and calibration — which languages are supported, how to detect them, and how to calibrate scores. 2. Concrete pattern sets for non-English slop (German, French, Spanish, with extension hooks for Portuguese and Italian).
Merged from language-support.md and i18n-patterns.md (P-14).
Part 1: Supported Languages
| Code | Language | Tier Coverage | Calibration |
|---|---|---|---|
| en | English | Full (Tier 1-4, phrases, fiction, sycophantic) | 1.0 (baseline) |
| de | German | Core (Tier 1-2, key phrases) | 0.85 |
| fr | French | Core (Tier 1-2, key phrases) | 0.80 |
| es | Spanish | Core (Tier 1-2, key phrases) | 0.85 |
| pt | Portuguese | Core (Tier 1-2, key phrases) | 0.85 |
| it | Italian | Core (Tier 1-2, key phrases) | 0.80 |
Part 2: Language Selection
Step 1 — Check config
Look for a languages key in .slop-config.yaml:
languages:
- en
- deIf languages is set, scan only those pattern sets. If absent, fall back to heuristic detection.
Step 2 — Heuristic (no config)
Sample the first 200 words. Count function-word hits:
| Language | Function words |
|---|---|
| German | der, die, das, und, ist, nicht, mit, von |
| French | le, la, les, et, est, pas, avec, une, dans |
| Spanish | el, la, los, las, es, con, una, por, que |
Use the highest-scoring language. If the top score is below 5, treat the document as English only and skip i18n pattern matching.
Detection is conservative: defaults to English unless another language has significantly more markers in the text.
Override
Specify language explicitly when auto-detection is unreliable:
- Mixed-language documents
- Short texts (< 100 words)
- Code-heavy documents
Part 3: Pattern File Layout
Patterns are stored in data/languages/{code}.yaml with consistent structure:
language: xx
name: Language Name
tier1:
power_words: [...]
sophistication_signals: [...]
metaphor_abuse: [...]
tier2:
transition_overuse: [...]
hedging: [...]
business_jargon: [...]
phrases:
vapid_openers:
score: 4
patterns: [...]
filler:
score: 2
patterns: [...]
tier5:
contrastive_parallelism:
score: 2
confidence: low
ignore_case: false
patterns: [...] # regex strings
negative_parallelism:
score: 3
confidence: high
ignore_case: true
patterns: [...]The optional tier5 section holds the 2026 structural tells (spatial copula, negative parallelism, contrastive parallelism, three-fragment burst, smart quotes, and similar) as regex rather than word lists. Each category carries a score, a confidence level (high, or low for judgment-level patterns that must never auto-apply), and an ignore_case flag. pattern_loader exposes the section through get_tier5_patterns(). A language without a tier5 section returns an empty list, so detection degrades to vocabulary and phrase tiers until the pack is translated. English (en.yaml) is the reference pack; the other languages carry no tier5 patterns yet.
Part 4: Cultural Calibration
Slop perception varies by culture:
- German: Formal register is more accepted; fewer words flagged as pretentious
- French: Literary flourishes are culturally valued; calibrate sensitivity
- Spanish: Formal transitions are standard in academic writing
- Portuguese: Academic norms closely follow Spanish; formal phrasing less penalised
- Italian: Literary style is culturally valued, similar to French calibration
Structural metrics (em dashes, bullet ratios) are language-agnostic.
Each language has a calibration factor (see LANGUAGE_CALIBRATION in pattern_loader.py). Multiply raw slop scores by this factor before reporting. English is the baseline (1.0). Languages with a factor below 1.0 are less penalised for formal or literary register.
Part 5: Concrete Pattern Sets (de/fr/es)
The same density scoring applies: tier-1 words score 3 each, tier-3 phrases score 4 each, normalised per 100 words.
German (de)
DE_TIER1_PATTERNS = [
r'\bumfassend\w*\b', # umfassend, umfassende, umfassender ...
r'\bnutzen\b',
r'\bvielf[äa]ltig\w*\b',
r'\btiefgreifend\w*\b',
r'\bbahnbrechend\w*\b',
r'\bganzheitlich\w*\b',
r'\bmaßgeblich\w*\b',
r'\bwegweisend\w*\b',
]
DE_PHRASE_PATTERNS = [
r'in der heutigen schnelllebigen welt', # vapid opener
r'es sei darauf hingewiesen', # filler
]French (fr)
tirer parti de is a multi-word phrase treated as a single tier-1 unit.
FR_TIER1_PATTERNS = [
r'\btirer parti de\b',
r'\bexhaustif(?:ve)?\b|\bexhaustive\b', # exhaustif / exhaustive
r'\bpolyvalent\w*\b',
r'\bincontournable[s]?\b',
r'\bnovateur\b|\bnovatrice\b',
r'\bprimordial\w*\b',
]
FR_PHRASE_PATTERNS = [
r"dans le monde d'aujourd'hui", # vapid opener
r'il convient de noter que', # filler
]Spanish (es)
ES_TIER1_PATTERNS = [
r'\baprovechar?\b', # aprovechar
r'\bintegral[e-z]?\b', # integral
r'\bpolifac[eé]tico[s]?\b', # polifacético
r'\binnovador[a-z]?\b', # innovador
r'\bfundamental[e-z]?\b', # fundamental
r'\bimprescindible[s]?\b', # imprescindible
]
ES_PHRASE_PATTERNS = [
r'en el mundo acelerado de hoy', # vapid opener
r'cabe destacar que', # filler
]Part 6: Scoring and Reporting
Add i18n matches to the overall document score:
slop_score += (tier1_count * 3 + phrase_count * 4) / word_count * 100Report language-specific hits in a subsection:
### Non-English Markers (de)
- Line 4: "bahnbrechend" -> consider: "neu" or be specific
- Line 12: "In der heutigen schnelllebigen Welt" (vapid opener)If two or more languages score above 5 in the heuristic, apply all matching pattern sets and label each match with its language code.
AI Slop Remediation Strategies
Guidance for fixing detected slop patterns while preserving meaning.
Core Principles
1. Preserve meaning: Never change what's said, only how it's said 2. Match context: Technical docs need different fixes than narratives 3. Maintain voice: If the document has established tone, preserve it 4. Incremental changes: Edit section by section, not wholesale rewrites 5. Ask before gutting: Major restructuring requires user approval
Vocabulary Remediation
Direct Substitutions
| AI Word | Context | Replacements |
|---|---|---|
| delve | exploration | explore, examine, look at, dig into |
| leverage | usage | use, apply, employ |
| utilize | usage | use |
| embark | starting | begin, start, launch |
| comprehensive | scope | thorough, complete, full |
| robust | quality | solid, strong, reliable |
| seamless | integration | smooth, easy, simple |
| pivotal | importance | key, important, critical |
| multifaceted | complexity | complex, varied, diverse |
| nuanced | detail | subtle, detailed, fine-grained |
| streamline | improvement | simplify, improve, speed up |
| optimize | improvement | improve, tune, adjust |
| facilitate | enablement | enable, help, allow |
| utilize | use | use |
Phrase Substitutions
| AI Phrase | Replacement Options |
|---|---|
| "In today's fast-paced world" | [Delete entirely] or start with the actual point |
| "It's worth noting that" | [Delete] - just state the thing |
| "At its core" | "Fundamentally" or [delete] |
| "Cannot be overstated" | "is important" or "matters because [reason]" |
| "Navigate the complexities" | "handle", "work through", "deal with" |
| "Unlock the potential" | "enable", "allow", "make possible" |
| "A testament to" | "shows", "demonstrates", "proves" |
| "Treasure trove" | "collection", "set", "many" |
Structural Remediation
Reducing Em Dashes
Replace with:
- Commas for brief asides
- Parentheses for tangential info
- Periods for complete thoughts
- Colons for introductions
Before: "The system—which was designed for scale—handles millions of requests." After: "The system handles millions of requests. It was designed for scale."
Converting Bullets to Prose
Before:
Key benefits:
- Fast processing
- Easy integration
- Low maintenanceAfter: "The system processes quickly, integrates without friction, and requires little upkeep."
Varying Sentence Length
If all sentences are 15-20 words, intersperse:
- Short punchy statements (5-8 words)
- Longer explanatory sentences (25-30 words)
- Questions or fragments for emphasis
Adding Contractions
Replace formal constructions:
- "do not" -> "don't"
- "cannot" -> "can't"
- "it is" -> "it's"
- "we will" -> "we'll"
Exception: Legal, academic, or formal documents may require formality.
Tone Remediation
Removing Sycophancy
| Remove | Replace With |
|---|---|
| "Great question!" | [Delete, just answer] |
| "I'd be happy to" | [Delete, just do it] |
| "Absolutely!" | [Delete or use sparingly] |
| "That's a wonderful point" | [Delete] |
Adding Authorial Voice
Insert:
- First-person perspective where appropriate
- Specific examples from real experience
- Acknowledgment of limitations or unknowns
- Trade-off discussions with reasoning
Before: "This approach optimizes performance." After: "We chose this approach because it cut latency by 40% in our tests, though it uses more memory."
Grounding Abstract Claims
Before: "This provides comprehensive coverage." After: "This covers all 47 API endpoints documented in v2.3."
Section-by-Section Workflow
For documents over 200 lines:
1. Scan entire document for slop density 2. Prioritize sections by severity 3. Present each section to user with proposed changes 4. Wait for approval before proceeding 5. Track changes in a summary
## Section 3: API Overview (Lines 45-89)
**Slop Score**: 4.2 (Moderate)
**Proposed Changes**:
1. Line 47: "delve into" -> "examine"
2. Line 52: Remove "In today's fast-paced world"
3. Lines 60-75: Convert bullet list to two paragraphs
Proceed with these changes? [Y/n/edit]Docstring Remediation
Special rules for code comments:
1. Imperative mood: "Validate" not "Validates" 2. No surrounding code changes: Only modify the comment text 3. Preserve parameter documentation: Keep Args/Returns format 4. Brief is better: Remove filler, keep essential info
Before:
def process(data):
"""
This function processes the data in a comprehensive manner,
leveraging advanced algorithms to optimize the output.
"""After:
def process(data):
"""Process input data and return optimized result."""When NOT to Remediate
- Quoted material: Don't change quotes from other sources
- Historical documents: Preserve original language
- Intentional style: Some "AI-like" formality may be intentional
- User preference: If user wants formal tone, respect it
Tier 5 / 2026 Remediations
These complement the older substitutions above. Apply during the Pass-5 prose sweep and during prevention-mode generation.
Copula-Avoidance Verbs (Spatial / Animated)
| Slop | Replacement |
|---|---|
| "lives in" / "lives at" | "is in" / "is at" |
| "sits at" / "sits between" / "sits within" | "is at" / "is between" / "is in" |
| "stands as" | "is" or delete |
| "rests on" | "depends on" / "uses" |
| "rooted in" | "based on" / "comes from" |
| "anchored in" | "based on" / delete |
| "nestled in" | "in" |
| "serves as" | "is" |
| "marks" (a turning point/shift) | "starts" / "begins" / delete |
| "represents" (a shift/transformation) | "is" or delete |
| "embodies" | "is" or "shows" |
| "boasts" | "has" |
| "features" (as main verb) | "has" / "includes" |
| "encompasses" | "covers" / "includes" |
Heuristic: if the subject cannot literally do the verb, replace with "is", "has", or "uses".
Plus-Sign Conjunction
| Slop | Replacement |
|---|---|
| "hooks and skills" | "hooks and skills" |
| "Python and Rust workflow" | "Python and Rust workflow" |
| "API and cache" | "API and cache" |
| "fast and cheap" | "fast and cheap" |
Exception: keep in code blocks, version strings ("3.11+"), stack labels in diagrams, math.
Em-Dash Replacement (Prevention Mode)
| Original | Replacement | When |
|---|---|---|
| "X — Y — Z" | "X, Y, Z" | Brief aside |
| "X — a Y — Z" | "X (a Y) Z" | Tangential definition |
| "X — and Y" | "X. And Y." | Dramatic pause |
| "X — Y." | "X: Y." | Definition / list-lead |
| "X — Y." | "X. Y." | Two complete thoughts |
Semicolon Splice (Prevention Mode)
A prose semicolon joining two independent clauses almost always reads more naturally as two sentences or one coordinating conjunction. Rephrase in this order. Keep the semicolon only when removing it creates ambiguity.
| Original | Replacement | When |
|---|---|---|
| "X; it does Y." | "X. It does Y." | Default: two complete thoughts |
| "X; it does Y." | "X, and it does Y." | Clauses are tightly linked |
| "X; therefore Y." | "X, so Y." | Causal link |
| "X; however Y." | "X, but Y." or "X. However, Y." | Contrast |
| "a, b; c, d; e, f" | (keep the semicolons) | List items carry internal commas |
Confidence is low: surface every prose semicolon for a human to judge against "absolutely necessary" rather than auto-rewriting.
Negative Parallelism (Contrastive Negation)
| Slop | Replacement |
|---|---|
| "It's not X, it's Y" | "It is Y" (state Y positively; avoid the "Y, not X" tail, which is itself flagged) |
| "Y, not X" (trailing) | "Y instead of X" (when the contrast carries information) or "Y" (drop the tail) |
| "Not just X, but Y" | "X and Y" or "X. Also Y." |
| "Not only X, but also Y" | "X and Y" |
| "No X. No Y. Just Z." | "Zero X. Zero Y. Only Z." (rare) or "Z, with no X or Y" |
| "No X, no Y, no Z" | "Z, with no X or Y" |
| "Not because X. Because Y." | "Because Y" (drop the "not X" half) |
| "X. That's it. That's the Y." | "X is the Y." |
| "And that's okay." | Delete |
The replacements remove the rhetorical scaffold and state the claim directly.
Contrastive Parallelism (Affirmative Antithesis)
The affirmative sibling: parallel clauses in opposition with no "not" anchor. Keep it only when the contrast is load-bearing and used once; otherwise state the point plainly.
| Slop | Replacement |
|---|---|
| "Less config, more code" | "Setup is one file; the rest is code" |
| "Where others X, we Y" | "We do Y" (drop the unnamed comparison) |
| "Humans propose; machines dispose" | "A human picks; the agent applies" (when both sides are concrete) |
| "Old way: X. New way: Y." | "Y replaces X" |
Subject-swap clauses and chiasmus are judgment-level (confidence: low); surface for human decision rather than auto-rewriting. Leave Before:/After: labels on code examples alone.
Throat-Clearing Openers
| Slop opener | Replacement |
|---|---|
| "Here's the thing," | Delete; start at substance |
| "Look," (sentence opener) | Delete |
| "So," (non-contrastive) | Delete |
| "The thing is," | Delete |
| "Let that sink in." | Delete |
| "The uncomfortable truth is" | Delete; state the truth directly |
| "This matters because" | Delete the framing; state why directly |
| "Let me explain." | Delete; just explain |
| "Bear with me." | Delete |
Significance Cluster
| Slop | Replacement |
|---|---|
| "stands as a testament to" | "shows" / delete |
| "marks a turning point" | "is when X changed" |
| "represents a shift" | "is a shift" or describe the shift |
| "indelible mark" | name the specific effect |
| "deeply rooted" | "old" / "longstanding" |
| "setting the stage for" | "before" / "leading to" |
| "shaping the future of" | name the specific influence |
| "underscores the importance" | "is important because" |
| "plays a pivotal role" | "is central" or describe the role |
Three-Fragment Burst
When you see "X. Y. Z." with three short fragments, ask: does the rhythm carry information, or is it ornament? If ornament, replace with a complete sentence.
| Slop | Replacement |
|---|---|
| "Focused. Aligned. Measurable." | "Focused, aligned, measurable." or full sentence |
| "Fast. Reliable. Cheap." | "Fast, reliable, and cheap." |
| "Built. Tested. Shipped." | "Built, tested, and shipped." |
Loop/Signal/Cascade Vocabulary
| Slop | Replacement |
|---|---|
| "unpack this" | "explain" / "go through" |
| "surface the issue" | "raise" / "report" |
| "a quiet shift" | name the shift |
| "the signal here is" | "the point is" |
| "a sharp framing" | describe the framing |
| "feedback loop" (non-control) | describe the actual mechanism |
Scan Reporting: Progress and Metrics
Two responsibilities are bundled here because both control what the slop scanner emits during and after a run:
1. Progress indicators while files are being scanned. 2. Historical metrics persisted to .slop-history/ for trend detection.
Merged from progress-indicators.md and metrics.md (P-14) so a caller loading "how do I report results?" gets one module.
Part 1: Progress Indicators
When to Show Progress
Skip progress output entirely for single files or pairs of files. Show progress when scanning 3 or more files. This avoids noise for the common single-file case.
| File count | Default behavior |
|---|---|
| 1-2 | No progress output |
| 3+ | Show file count progress |
Output Modes
Three modes control what gets printed during a scan.
Default Mode
Show a counter line for each file as it is processed:
[1/49] Scanning plugins/scribe/README.md...
[2/49] Scanning plugins/scribe/SKILL.md...
...
[49/49] Scanning plugins/abstract/README.md...
Scanned 49 files in 3.2sFormat for each progress line:
[{current}/{total}] Scanning {filepath}...Format for the completion summary:
Scanned {total} files in {elapsed:.1f}sQuiet Mode (--quiet)
Suppress all progress output. Print only the final report. Use this in CI pipelines and scripts where progress lines would pollute logs.
No per-file lines. No summary line. Only the report.
Verbose Mode (--verbose)
Print the per-file slop score immediately after each file is processed:
[1/49] Scanning plugins/scribe/README.md... score=2.1 (Light)
[2/49] Scanning plugins/scribe/SKILL.md... score=0.4 (Clean)
...
Scanned 49 files in 3.2sScore label mapping:
| Score range | Label |
|---|---|
| 0 - 1.0 | Clean |
| 1.0 - 2.5 | Light |
| 2.5 - 5.0 | Moderate |
| 5.0+ | Heavy |
Implementation Notes
- Write progress lines to stdout so they can be redirected or captured.
- Compute elapsed time from scan start to the final file completion.
- Round elapsed time to one decimal place.
- Use 1-based indexing in the counter (
[1/49], not[0/49]). - If a file cannot be read, print a warning on that line and continue:
[7/49] Scanning path/to/file.md... WARNING: could not read fileDo not abort the scan on a single unreadable file.
Part 2: Historical Metrics
Store scan results over time to detect regressions and measure cleanup progress.
Storage Location
Write scan records to .slop-history/ at the repo root. Add this directory to .gitignore:
.slop-history/File Naming
One JSON file per scan:
.slop-history/scan-YYYY-MM-DD-HHMMSS.jsonExample: .slop-history/scan-2026-03-01-143022.json
Generate the timestamp with:
from datetime import datetime
datetime.now().strftime("%Y-%m-%d-%H%M%S")JSON Schema
{
"timestamp": "2026-03-01T14:30:22",
"files_scanned": 12,
"scores": [
{"path": "docs/guide.md", "score": 1.4, "word_count": 320}
],
"summary": {
"avg_score": 1.4,
"max_score": 3.1,
"total_markers": 18
}
}All fields are required. Use datetime.isoformat() for the timestamp string.
Saving Results (--track)
When the --track flag is present, write a record after every scan:
1. Build the scores list from the per-file results. 2. Compute summary.avg_score as the mean of all scores (0.0 if no files). 3. Compute summary.max_score as the maximum score (0.0 if no files). 4. Count summary.total_markers as the sum of all raw marker hits across files. 5. Write the JSON file to .slop-history/ using the timestamp name. 6. Report the path written: Saved: .slop-history/scan-YYYY-MM-DD-HHMMSS.json
Loading History (--history)
When the --history flag is present, read all files in .slop-history/ sorted by filename (chronological order) and print a trend table:
Date Files Avg Score Delta
2026-02-15-090000 10 1.20 —
2026-02-22-143000 12 1.45 +0.25
2026-03-01-143022 12 1.90 +0.45Column widths: date 20, files 6, avg score 10, delta 8. Use — for the delta on the first row. Prefix positive deltas with +.
Regression Warning
After printing the trend table, check the two most recent records. If avg_score increased by more than 0.5:
WARNING: avg score increased by 0.62 since last scan (1.28 -> 1.90)This warning also fires when --track saves a new result and the previous record exists. Compare the new avg against the most recent existing record before writing.
Spelling Normalization: British to American
The slop workflow normalizes British spellings to American by default. This is a consistency concern, not a slop-density one, so it runs as its own pass rather than feeding the tier scores.
This is orthography within English. It is not language detection (language-handling.md handles English vs German vs French). A British document is still English; only its spelling shifts.
Default behavior
Convert British spellings to American in any scanned document, unless the document opts out (see Opt-out) or a word is on the allowlist. This matches the project rule .claude/rules/slop-scan-for-docs.md: in prevention mode the target is zero British spellings in docs the agent just generated.
The one rule that matters
Use an explicit word list. Never apply a suffix transform.
A blanket "-ise becomes -ize" rule corrupts words that are -ise in both dialects (surprise, exercise, advertise, comprise) and nouns that are -ysis in both (analysis stays analysis). British English also accepts -ize (Oxford spelling), so the suffix alone is not a reliable signal. The curated map in data/spelling/british_american.yaml lists explicit words and their common inflections instead.
Programmatic path (preferred)
The scribe.spelling module implements the pass and is unit-tested:
find_british_spellings(text, allowlist=None)reports each
occurrence with line, column, the matched word, and the American replacement. Use for flag-only review.
to_american(text, allowlist=None)rewrites the text, preserving
case (Colour to Color, COLOUR to COLOR) and leaving fenced code, inline code, and URLs untouched.
Both skip the per-document allowlist (case-insensitive). Conversion is idempotent: no American value is also a British key.
Manual detection (no Python available)
Grep for the highest-frequency families, then confirm each hit is prose (not code, a URL, or a proper noun) before editing:
rg -n -i '\b(colou?r|behaviou?r|favou?rite|organis|recognis|optimis|\
analyse|centre|metre|licence|defence|catalogue|travell|grey|artefact)\w*' \
--glob '*.md'Treat matches inside code fences, inline code, and links as false positives.
What to leave alone (anti-goals)
- Code blocks, inline code, file paths, identifiers, and URLs. A CSS
color, a variable behaviour_flag, or a link path is not prose.
- Proper nouns and cited titles: "Labour Party", "World Health
Organisation", "Centre for Disease Control" (when quoting a name). Add these to the allowlist.
- Direct quotations of someone else's British text.
- Words that are identical in both dialects (do not "fix" surprise,
exercise, analysis, focus, or status).
Opt-out
A document or project may keep British spelling. Honor, in order:
1. .slop-config.yaml spelling: british (or spelling: off) for a project or subtree. See config-file.md. 2. The per-word allowlist in .slop-config.yaml for individual intentional terms. 3. An explicit user instruction in the session ("keep British spelling here").
When opted out, report British spellings as informational at most; do not rewrite.
Reporting
Group spelling hits separately from slop markers so the two concerns stay legible:
### Spelling (British -> American)
- Line 12: "colour" -> "color"
- Line 40: "organisation" -> "organization"
- Skipped (allowlist): "Labour" x2Structured Finding Output
One finding per atomic change. Each finding carries its own evidence, severity, and confidence so reviewers can accept or reject independently.
This module defines the canonical output format for any slop-detector finding. The shape is intentionally verbose: every field exists because reviewers have asked for it in past audits. Do not abbreviate; do not omit.
The format
Every finding is a block of these fields, in this order:
[FINDING N]
file: path/relative/to/repo/root
line: single-line or range (e.g. 45 or 45-52)
category: section/subsection (e.g. identity-and-voice-leaks/llm-self-reference)
severity: critical | high | medium | low
confidence: high | medium | low
evidence: > <quoted excerpt from the file>
rationale: <why this is a finding; one short paragraph>
fix: <one specific proposed change; OR a list of options>
diff: <unified diff if confidence is high; absent if low>Findings are numbered in scan order, not severity order. A reviewer reads top-to-bottom; the numbers are anchors, not priorities. Sort by severity in the summary line, not in the finding sequence.
Worked example
[FINDING 12]
file: plugins/foo/skills/bar/SKILL.md
line: 47-52
category: identity-and-voice-leaks/llm-self-reference
severity: critical
confidence: high
evidence: > As a large language model, I cannot directly
> validate the configuration without access to
> your environment, but here is what I would
> suggest...
rationale: Identity leak in a published skill file. The
phrase "As a large language model" is direct
evidence the text was generated by an LLM that
did not realise it was being asked to write as
the project. Per the repository constitution
rule #4, this is an automatic revert.
fix: Replace the entire paragraph with the
substantive guidance, omitting the disclaimer:
"Validate the configuration with: `<command>`.
If the command exits non-zero, ..."
diff: --- a/plugins/foo/skills/bar/SKILL.md
+++ b/plugins/foo/skills/bar/SKILL.md
@@ -45,8 +45,4 @@
## Validation
-As a large language model, I cannot directly
-validate the configuration without access to
-your environment, but here is what I would
-suggest...
+Validate the configuration with: `<command>`.
+If the command exits non-zero, ...Field-by-field rules
file
Always relative to the repo root. Never absolute paths. Never ~/-paths. The reviewer should be able to copy the path into code <path> or vim <path> from any directory.
line
A single line number or a range. For a range, use start-end inclusive. If the finding spans multiple non-contiguous ranges, file separate findings.
category
Two-level dotted path: module-name/subsection. Use the slop-detector module file as the first level so reviewers can immediately load the relevant guidance:
| Module | Example subsection |
|---|---|
| identity-and-voice-leaks | llm-self-reference, conversational-artifact, structure-narration, hedging-seesaw |
| hallucination-detection | phantom-identifier, dead-url, made-up-config, slopsquatting |
| stub-and-deferral | bare-todo, hedging-language, stub-construct, magic-constant |
| evidence-backed-claims | unverified-claim, missing-bench, unsafe-without-safety |
| document-economy | missing-thesis, sentence-weight-low, ambient-repetition |
| vocabulary-patterns | tier1-density, tier2-cluster, phrase-pattern |
| structural-patterns | em-dash-overuse, bullet-bloat, paragraph-symmetry |
severity
Four levels with concrete rules:
- critical: identity leaks, hallucinations of code that
does not exist, security claims with contradicting code (e.g. "memory-safe" with undocumented unsafe). Must be resolved before merge.
- high: bare TODOs in production code paths, unverified
README claims, broken external links, document-economy score below 3/6.
- medium: tier-1 vocabulary density above threshold,
document-economy score 3-4/6, structural pattern violations, stub language without "for now"-class hedge.
- low: tier-2 vocabulary, em-dash density between 3-5
per 1000 words, paragraph blockiness.
confidence
Three levels with concrete rules:
- high: pattern-matched against a deterministic detector
(regex, file-existence check, identifier-resolution check). The fix is mechanical.
- medium: pattern matches but context might justify the
match (e.g. "comprehensive" inside a quoted glossary definition). Reviewer should verify the context, not the pattern.
- low: judgment-heavy categories (premature
abstraction, generic-name slop, em-dash density in narrative writing). Surface for human decision; never auto-apply.
evidence
The literal text as it appears in the file. Use > as a quote prefix. Wrap to ~60 characters. Preserve the original casing and punctuation; do not "tidy". The fidelity is the point: the reviewer is auditing what was actually there.
If the evidence is multi-line, indent continuation lines to align under the first character after the >.
rationale
One short paragraph (2-4 sentences). Names:
1. What the issue is in plain language. 2. Why it is a finding (cite the rule, the constitution, or the empirical baseline). 3. (Optional) Blast radius: who reads this, what breaks.
Do not pad. Do not editorialise. The rationale is to help the reviewer decide; flowery language slows that down.
fix
A single concrete proposed change. If multiple options exist, list them numbered, with the recommended option first and the trade-offs in one line each:
fix: Either:
1. Add benches/ with criterion runs and keep
the "fast" claim. (best if performance is
actually a goal)
2. Replace "fast" with the empirical claim
you can support, e.g. "<10ms cold start".
3. Delete the claim. (default if 1 and 2 are
not on the roadmap)For high-confidence findings, prefer to write the recommended option as the only fix line and put alternatives in a notes: field.
diff
Unified diff format. Required for confidence: high. Absent (or replaced with (see fix)) for confidence: low since the fix is judgment-bound.
Diffs should be minimal: only the lines that change, plus 2-3 lines of context. Do not include unrelated whitespace changes or reformatting.
Summary line
Every report ends with a summary line that the CI / agent runner can parse:
[SUMMARY] N findings: A critical, B high, C medium, D low | scanned M files in T secondsThe pipe character separates aggregate counts from runtime metadata.
Auto-apply policy
Per the anti-goals module:
confidence: high+severity: critical | high: an
automated agent may apply the diff if the user has authorised auto-apply for those severities.
confidence: medium: auto-apply requires explicit
per-finding authorisation.
confidence: low: never auto-apply; surface for human
decision.
The default for any unattended run is "report only, do not modify". Apply happens only with explicit --auto-apply flag or equivalent.
Machine-parseable variant
For CI integration, emit JSON Lines (one finding per line):
{"id": 12, "file": "plugins/foo/skills/bar/SKILL.md", "line": "47-52", "category": "identity-and-voice-leaks/llm-self-reference", "severity": "critical", "confidence": "high", "evidence": "As a large language model, ...", "rationale": "Identity leak in published skill file ...", "fix": "Replace paragraph with substantive guidance ...", "diff": "..."}The text format above is the human-readable canonical form; JSONL is generated from it for tooling. Do not let the JSONL format drift from the text format; if a field is added, add it to both.
Stub & Deferral Detection
Every stub marker is a defect awaiting a production incident. Either resolve, file a tracked issue, or delete the surrounding code.
This module covers the surface markers AI uses to signal "I did not actually finish this." The markers themselves are simple to detect (pattern-matched comments and constructs), but they accumulate fast in AI-generated codebases because the agent commits its incomplete drafts.
The rule
Every match in this module must resolve to one of:
1. Resolved: the work is done, the marker is deleted. 2. Tracked: a linked issue/ticket replaces the bare marker (// TODO(#1234): ...). 3. Deleted: the surrounding code path is removed because the work is no longer needed.
Bare stub markers in production code are debt. The existence of the marker is the bug.
Patterns
Class 1: Standard stub comments
// TODO
// TODO:
// FIXME
// FIXME:
// XXX
// HACK
// NOTE: temporary
// TBD
// To be done
// To be implemented
// Implement later
// Coming soonDetection:
rg -n '^\s*(//|#|--)\s*(TODO|FIXME|XXX|HACK|TBD)\b' --type-add 'src:*.{py,rs,ts,js,go,rb}' --type srcClass 2: Hedging comments (deferral language)
These are softer than TODO but signal the same thing: "this is not final."
// for now,
// for the moment,
// this should work
// this should work in most cases
// works for now
// good enough for now
// placeholder
// dummy data
// dummy value
// stub
// mock implementation
// fake data
// quick and dirty
// hack
// kludge
// workaround
// work around (without "for X" specifier)
// not sure if this is right
// might need to revisit
// revisit laterThe diagnostic phrase is "for now": every "for now" needs to either become "permanent" or be deleted.
Class 3: Stub constructs in code
Language-specific constructs that the runtime treats as explicit "not implemented":
# Python
raise NotImplementedError
raise NotImplementedError("...")
pass # in a body that should do something
def foo(): ... # body is just `...`// Rust
todo!()
todo!("...")
unimplemented!()
unimplemented!("...")
panic!("not implemented")
panic!("TODO")// TypeScript / JavaScript
throw new Error("not implemented");
throw new Error("TODO");
return null as any; // placeholder castThese in any code path reachable from a public API are defects with high blast radius. Detect by:
rg -n 'todo!\(|unimplemented!\(|NotImplementedError|throw new Error\("(?:TODO|not implemented)' .Class 4: Magic constants flagged as arbitrary
let limit = 100; // arbitrary
let timeout = 5000; // tune later
let max_retries = 3; // why 3?
const BUFFER: usize = 4096; // seems reasonableComments that admit the constant was guessed are stubs. Either:
- Determine the right value (benchmark, requirement, spec).
- Make the constant configurable.
- Delete the comment if the value really is arbitrary
(and accept that it can be revisited any time).
Hedging in prose
The same "for now" / "should work" pattern shows up in markdown documentation, commit messages, and PR descriptions. Flag these with the same severity:
"this should work in most cases"
"for now, we recommend"
"this is a temporary workaround"
"this is a placeholder until"
"this approach may not scale"
"may need to revisit"
"good enough for now"
"works in our environment"Each one tells the reader the documented behavior is provisional. Either:
- Make it not provisional (commit to the design).
- Document the actual constraint ("works for inputs
under 1MB; for larger inputs use the streaming API").
- Delete the section.
Output format
[FINDING N]
file: src/auth.rs
line: 142
category: stub-and-deferral/bare-todo
severity: medium
confidence: high
evidence: > // TODO: handle expired tokens
rationale: Bare TODO with no tracked issue, in a code
path that handles authentication. Current
behavior on expired tokens: silent success:
fix: Either:
1. Implement the expired-token branch and
delete the TODO.
2. Open issue, change to `// TODO(#NNNN):
handle expired tokens`.
3. Add an explicit early return + 401 if
the design is "fail closed".Configuration: allowed bare TODOs
Some teams allow bare TODOs in non-production paths (experiments, examples, local scripts). Configure path exclusions in the slop-detector config:
stub-and-deferral:
allow_bare_todo:
- "examples/**"
- "scratch/**"
- "tests/fixtures/**"
require_tracked_issue:
- "src/**"
- "lib/**"The default is strict: every TODO in src/ requires a tracked issue link.
Special cases
unreachable!()is not a stub if the surrounding
code makes the unreachability provable. Treat it as asserting an invariant, not deferring work.
- A
// TODO:with a name attached (// TODO(alice):)
is conventionally a deferred assignment, not a stub. Whether you accept this depends on team convention.
- Generated code (build.rs output, prost/tonic, codegen
templates) should be excluded from this scan: it legitimately contains placeholder patterns.
Integration
Runs as part of the cleanup workflow Pass 2 (hallucination & stubs sweep). The "tracked-issue" check should run in CI, not just locally: a bare TODO that compiles is a bare TODO that ships.
Related skills
FAQ
Is Slop Detector safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.