# scanaislop — full text
This file contains the full prose of every published blog post on scanaislop.com, concatenated for LLM ingestion. Docs pages are linked, not inlined (fetch them from the URLs if needed).
## Index
- [https://scanaislop.com/docs](https://scanaislop.com/docs) — Docs overview
- [https://scanaislop.com/docs/writing-comments](https://scanaislop.com/docs/writing-comments) — Writing comments guide
- [https://scanaislop.com/.well-known/api-catalog](https://scanaislop.com/.well-known/api-catalog) — Linkset JSON for site, API, docs, and agent metadata
- [https://scanaislop.com/.well-known/agent-skills/index.json](https://scanaislop.com/.well-known/agent-skills/index.json) — Agent Skills discovery index
- [https://scanaislop.com/.well-known/security.txt](https://scanaislop.com/.well-known/security.txt) — Responsible-disclosure contact routes
- [https://scanaislop.com/standard](https://scanaislop.com/standard) — Clean Agent Code Standard
- [https://scanaislop.com/research](https://scanaislop.com/research) — Research protocol and public scan programme
- [https://scanaislop.com/benchmark](https://scanaislop.com/benchmark) — AI coding agent benchmark
- [https://scanaislop.com/blog/ai-slop-detection-complete-guide/](https://scanaislop.com/blog/ai-slop-detection-complete-guide/) — AI Slop: How to Detect and Prevent Low-Quality AI Code
- [https://scanaislop.com/blog/what-i-fixed-and-what-i-kept/](https://scanaislop.com/blog/what-i-fixed-and-what-i-kept/) — What I fixed after that score, and what I kept
- [https://scanaislop.com/blog/the-feedback-that-made-my-launch/](https://scanaislop.com/blog/the-feedback-that-made-my-launch/) — The feedback that made my launch
- [https://scanaislop.com/blog/aislop-v0-9-4-python-scbench-rules/](https://scanaislop.com/blog/aislop-v0-9-4-python-scbench-rules/) — aislop v0.9.4. SlopCodeBench called it verbosity. We turned it into rules.
- [https://scanaislop.com/blog/aislop-v0-9-3-rule-precision/](https://scanaislop.com/blog/aislop-v0-9-3-rule-precision/) — aislop v0.9.3. We measured the noise. Then we cut it by 38%.
- [https://scanaislop.com/blog/your-agents-need-a-guardrail/](https://scanaislop.com/blog/your-agents-need-a-guardrail/) — A prompt is a suggestion. A CI gate is a guardrail.
- [https://scanaislop.com/blog/ai-slop-loop-breaks-production/](https://scanaislop.com/blog/ai-slop-loop-breaks-production/) — The AI Slop Loop: When AI-Generated Code Creates Self-Reinforcing Quality Problems
- [https://scanaislop.com/blog/ai-slop-statistics-2026/](https://scanaislop.com/blog/ai-slop-statistics-2026/) — AI Slop Statistics 2026: The Data Behind the Backlash
- [https://scanaislop.com/blog/aislop-vs-coderabbit-deterministic-vs-llm/](https://scanaislop.com/blog/aislop-vs-coderabbit-deterministic-vs-llm/) — aislop and CodeRabbit: Deterministic Gates and AI-Powered Review
- [https://scanaislop.com/blog/best-ai-code-quality-tools-2026/](https://scanaislop.com/blog/best-ai-code-quality-tools-2026/) — Best AI Code Quality Tools 2026: From Linters to Quality Gates
- [https://scanaislop.com/blog/quality-gate-ai-agent-in-2-minutes/](https://scanaislop.com/blog/quality-gate-ai-agent-in-2-minutes/) — How to Add a Quality Gate to Your AI Agent in 2 Minutes
- [https://scanaislop.com/blog/the-swallowed-exception-that-broke-production/](https://scanaislop.com/blog/the-swallowed-exception-that-broke-production/) — The Swallowed Exception That Broke Production: AI Slop Pattern Deep Dive
- [https://scanaislop.com/blog/why-sonarqube-misses-ai-slop/](https://scanaislop.com/blog/why-sonarqube-misses-ai-slop/) — Where SonarQube Stops, and AI-Slop Rules Start
- [https://scanaislop.com/blog/high-quality-ai-coding-standards/](https://scanaislop.com/blog/high-quality-ai-coding-standards/) — High-quality AI coding standards
- [https://scanaislop.com/blog/vibe-coding-done-right/](https://scanaislop.com/blog/vibe-coding-done-right/) — Vibe coding, done right
- [https://scanaislop.com/blog/agents-md-is-not-enough/](https://scanaislop.com/blog/agents-md-is-not-enough/) — AGENTS.md is a sign on the wall. Agents don't read signs.
- [https://scanaislop.com/blog/we-shouldnt-be-cleaning-up-after-our-agents/](https://scanaislop.com/blog/we-shouldnt-be-cleaning-up-after-our-agents/) — Stop cleaning up after your coding agent
- [https://scanaislop.com/blog/nobody-reads-code-anymore/](https://scanaislop.com/blog/nobody-reads-code-anymore/) — Nobody reads code anymore. Write for the linter.
- [https://scanaislop.com/blog/aislop-v0-7-0-config-inheritance/](https://scanaislop.com/blog/aislop-v0-7-0-config-inheritance/) — aislop v0.7.0. Config inheritance, public score badge, security floor
- [https://scanaislop.com/blog/function-size-limits-for-ai-code/](https://scanaislop.com/blog/function-size-limits-for-ai-code/) — If your function needs 80 lines, your agent gave up. If your file needs 400, it never started.
- [https://scanaislop.com/blog/stop-letting-your-agent-write-comments/](https://scanaislop.com/blog/stop-letting-your-agent-write-comments/) — You shouldn't be writing comments. And if you do, it should have a reason.
- [https://scanaislop.com/blog/your-agent-is-leaving-dead-code-behind/](https://scanaislop.com/blog/your-agent-is-leaving-dead-code-behind/) — Agents add. They don't subtract. That's the bug.
- [https://scanaislop.com/blog/top-ai-coding-mistakes/](https://scanaislop.com/blog/top-ai-coding-mistakes/) — The top 10 AI slop patterns we see often
- [https://scanaislop.com/blog/25-projects-aislop-findings/](https://scanaislop.com/blog/25-projects-aislop-findings/) — What 25 real projects taught us about aislop 0.5
- [https://scanaislop.com/blog/aislop-v0-6-0-agent-hooks/](https://scanaislop.com/blog/aislop-v0-6-0-agent-hooks/) — aislop v0.6.0. Agent hooks for Claude, Cursor, Gemini, and six more
- [https://scanaislop.com/blog/aislop-v0-5-0-ast-fix-engine/](https://scanaislop.com/blog/aislop-v0-5-0-ast-fix-engine/) — aislop v0.5.0. New CLI, own AST fix engine, stable output, better experience
- [https://scanaislop.com/blog/aislop-v0-4-0-agent-handoff/](https://scanaislop.com/blog/aislop-v0-4-0-agent-handoff/) — aislop v0.4.0. Agent Handoff, Smarter Fix, Better Scoring
- [https://scanaislop.com/blog/aislop-in-ci-cd/](https://scanaislop.com/blog/aislop-in-ci-cd/) — How to Add a Quality Gate to Your AI-Assisted Pipeline in 5 Minutes
- [https://scanaislop.com/blog/what-is-ai-slop/](https://scanaislop.com/blog/what-is-ai-slop/) — What Is AI Slop? The Patterns AI Agents Leave Behind
- [https://scanaislop.com/blog/why-we-built-aislop/](https://scanaislop.com/blog/why-we-built-aislop/) — Why We Built scanaislop: The Problem with Five Linters and No Standard
---
# AI Slop: How to Detect and Prevent Low-Quality AI Code
**URL:** https://scanaislop.com/blog/ai-slop-detection-complete-guide/
**Date:** 2026-05-30
**Tag:** Guide
AI slop compiles, passes tests, and still weakens your codebase. Here is what it is, the named patterns that give it away, how to detect them, and the workflow that keeps them out of review.
**TL;DR.**
- AI slop is code that compiles, passes tests, and still degrades your codebase because it is structurally shallow, repeated at machine scale.
- It is harder to catch than normal tech debt because it looks finished, spreads uniformly, and passes the checks teams trust.
- Most of it falls into a small set of named patterns: swallowed errors, comments that lie, type-system escape hatches, hallucinated imports, dead code, generic names, and complexity inflation.
- Deterministic rules catch the patterns you can define. LLM review catches logic that needs intent. Use deterministic rules for enforceable gates, and use LLM review where judgment is useful.
- Prevention is a loop, not a cleanup: scan at the hook, gate in CI, track the trend. Run `npx aislop scan` to see where you stand.
## What AI slop actually is
In the content world, "AI slop" means mass-produced, low-effort text generated to capture search traffic. In code, the definition is sharper: **AI slop is AI-generated code that compiles, passes tests, and still carries shallow structure that can fail later.**
The crucial part is that it is often not broken in the obvious way. A single trivial comment is harmless. A single empty catch block is a small risk. The problem is volume. Agent-written changes can repeat the same small shortcuts across many files. At that scale, style noise becomes comprehension drag, debugging tax, and production risk.
You know it when you read it. A pull request where everything technically works, but following the logic feels like wading through filler. That feeling is the signal. The rest of this guide turns that feeling into specific, named, detectable patterns.
## The numbers behind the problem
Merriam-Webster named *slop* a defining word of 2025. Search interest in "AI slop" went from niche slang to a broader engineering concern inside a year. The term crossed over because the experience is becoming familiar: teams shipping with Claude Code, Cursor, Codex, or other agents are generating more code, faster, than their review process was designed to absorb.
The volume is the story. When a human wrote every line, review scaled with output because both were bounded by the same person typing. Agents break that coupling. One engineer can now open a hundred-file change in an afternoon. The code arrives faster than anyone can read it, and it arrives polished enough that skimming feels safe. It is not.
Across 25+ real projects we scanned, the same handful of patterns showed up again and again, independent of language, framework, or which agent wrote the code. The receipts are in our [25-project findings post](/blog/25-projects-aislop-findings). The patterns are remarkably consistent, which is exactly why they are detectable.
## Why AI slop is harder to catch than normal tech debt
Traditional tech debt has a tell. A human took a shortcut, knew it was a shortcut, and usually left a scar: a TODO, a hack comment, a function everyone on the team quietly avoids. You can find it because someone chose it.
AI slop has none of those tells, for three reasons.
**It looks finished.** Agents write fluent, idiomatic-looking code with confident naming and tidy formatting. The surface signals your brain uses to flag "this was rushed" are exactly the signals an agent gets right. The shallowness is underneath.
**It is uniform.** Human debt is local and idiosyncratic. AI slop is systemic. The same swallowed-exception habit, the same generic `data` and `result` names, the same redundant try/catch appear everywhere, which makes any single instance look normal because it matches everything around it.
**It passes your checks.** Tests are green because the code does the happy path. The type checker is satisfied because an `as any` told it to be. The linter is quiet because trivial comments and shallow structure are not lint errors. Many quality gates were designed for human-paced output, and agent output changes the load profile.
This is why manual review should not be the only answer. You cannot ask reviewers to read a hundred files with the same care they gave to ten, every day. The catch should be mechanical for the patterns that can be mechanical, so reviewers spend their attention on the things only a human can judge.
## The patterns, and the rules that catch them
This is where vague talk about "code smell" has to become specific. Below are the families of AI slop we see most often, each tied to the deterministic rules that flag it. Named patterns are the difference between "this feels off" and "line 42, swallowed-exception, fix it." You can browse the full set on the [rules reference](/docs/rules).
**Comments that lie.** `// Initialize the database connection` above `initDB()`. It says nothing the code does not, and worse, it drifts. The function gets renamed six months later, the comment does not, and now it actively misleads. Caught by `trivial-comment`, `narrative-comment`, and `meta-comment`. We wrote a whole piece on why this is the most under-rated slop signal: [stop letting your agent write comments](/blog/stop-letting-your-agent-write-comments).
**Swallowed errors.** An empty catch block, or a catch whose only body is `console.log(err)`. The failure is consumed and the caller never learns it happened. This can become a production incident when the system keeps running while quietly doing the wrong thing. Caught by `swallowed-exception`, `silent-recovery`, `redundant-try-catch`, and in Python by `python-bare-except` and `python-broad-except`. We told the story of one such bug in [the swallowed exception that broke production](/blog/the-swallowed-exception-that-broke-production).
**Type-system escape hatches.** `as any`, `as unknown as T`, a `@ts-ignore` dropped in to make an error disappear. The code satisfied the compiler by removing part of the guarantee the type system was there to provide. Caught by `unsafe-type-assertion`, `double-type-assertion`, `ts-directive`, and `redundant-type-coercion`.
**Hallucinated imports.** An import of a package that does not exist. Sometimes it is a harmless typo the build catches. Sometimes it is a name an attacker has already registered, which makes it a supply-chain vector, not a bug. This is the one slop pattern we treat as an error rather than a warning. Caught by `hallucinated-import`.
**Dead code and duplication.** Unreachable branches, variables assigned but never read, the same utility re-implemented in four files with four slightly different shapes and no canonical version. Agents generate locally and forget globally, so duplication is the default. Caught by `unreachable-code`, `duplicate-import`, `unused-import`, and the `knip/*` family for unused files, exports, and dependencies. More on this in [your agent is leaving dead code behind](/blog/your-agent-is-leaving-dead-code-behind).
**Generic names and stubs.** `data`, `data2`, `result`, `temp`, empty functions that pretend to do work, thin wrappers that forward a call and add nothing, and `TODO` stubs left where real logic should be. Each makes the next reader slower. Caught by `generic-naming`, `empty-function`, `thin-wrapper`, `todo-stub`, and `rust-todo-stub`.
**Debug leftovers and hardcoded values.** A stray `console.log` or `print` shipped to production, an ID or URL pasted inline instead of read from config, a secret hardcoded in source. These are useful signals that generated code needs another pass. Caught by `console-leftover`, `python-print-debug`, `hardcoded-id`, `hardcoded-url`, and the error-level `security/hardcoded-secret`.
**Complexity inflation.** Functions that grow to hundreds of lines, files that become dumping grounds, nesting four levels deep, parameter lists no caller can hold in their head. Agents do not feel the pain of a long function, so they keep adding to it. Caught by `function-too-long`, `file-too-large`, `deep-nesting`, and `too-many-params`. We cover where to set the thresholds in [function size limits for AI code](/blog/function-size-limits-for-ai-code).
**Security shortcuts.** String interpolation in a SQL query, a shell command built from user input, `eval` on untrusted data, `innerHTML` with a value the model never sanitized, a dependency with a known CVE. The agent generates the most direct path to the result, which is rarely the safest one. Caught at error level by `security/sql-injection`, `security/shell-injection`, `security/eval`, `security/innerhtml`, and `security/vulnerable-dependency`.
**Language-specific tells.** Slop is not only a JavaScript problem. Python mutable default arguments, `range(len(x))` loops, and `isinstance` ladders. Rust `.unwrap()` outside tests. Go library code that panics instead of returning an error. Each language has its own dialect of shortcut, and each has rules to match: `python-mutable-default`, `python-range-len-loop`, `python-isinstance-ladder`, `rust-non-test-unwrap`, and `go-library-panic`.
## What a scan actually looks like
Abstract patterns are easier to act on once you have seen them land. A scan does not return prose, it returns a score and a list of findings, each pinned to a file, a line, and a named rule. That precision is the whole point: it is the difference between a reviewer writing "this feels noisy" and a gate saying exactly what to change.
```
{`$ npx aislop scan
src/auth/session.ts
14:3 warning swallowed-exception empty catch swallows the failure
22:9 warning unsafe-type-assertion as-any discards the checked type
41:1 warning trivial-comment comment restates the next line
src/db/pool.ts
8:12 error hallucinated-import 'pg-promise-x' is not a known package
score 78/100 · 1 error 3 warnings · 4 files`}
```
Every line is concrete: rule, file, line, and severity. Errors can block the gate, warnings can count against the score, and an agent reading this output knows what to fix before the next commit. That consistency is what lets you put it in front of a merge.
## Three ways to detect it
There are three approaches to detection, and the right answer is not to pick one but to put each where it belongs.
**LLM-based detection.** Ask a model to review the diff for slop. It can catch context-dependent issues a fixed rule cannot, like a function that is technically fine but solves the wrong problem. The weakness is consistency: the same code can receive different feedback on a later run, because the verdict is sampled from a distribution. Treat it as advisory review unless your team accepts that variability.
**Deterministic rule-based detection.** Scan the code against a fixed set of patterns. The verdict is identical every run, it needs no network, and it is fast enough to sit in a pre-commit hook. The limit is honesty: a rule only catches what you can define precisely, so deterministic scanning will not find a subtle logic bug that depends on business intent.
**Hybrid, which is what we recommend.** Deterministic rules run the quality gate, because a gate has to be consistent to be fair. LLM review runs alongside as advisory, catching intent-level issues and leaving suggestions. We go deeper on this split in [deterministic versus LLM review](/blog/aislop-vs-coderabbit-deterministic-vs-llm).
This is also why a general-purpose static analyzer may not be enough on its own. Tools built for broad static analysis carry thousands of rules for bugs, security, and maintainability, but they are not always tuned for the shallow patterns that show up in agent-assisted changes. We unpack that gap in [where SonarQube stops, and AI-slop rules start](/blog/why-sonarqube-misses-ai-slop).
## The prevention workflow that holds
Detection is half the job. The other half is putting it where slop is created instead of where it is discovered. A practical workflow has three layers, and none of them depends on manual cleanup after the fact.
**Layer 1: the agent hook.** Run the scanner as the agent writes, not after. Findings surface in-editor, and the agent can fix them while the context is still loaded. This is usually the lowest-friction place to catch a repeatable issue. Setup is in the [hooks guide](/docs/hooks).
**Layer 2: the CI gate.** Every pull request is scanned, and a score below your configured threshold can block the merge. The gate is not there to be perfect, it is there to be consistent, so the same standard applies to every PR whether a human or an agent wrote it. Setup is covered in the [CI guide](/docs/ci).
**Layer 3: the trend.** Track the score over time, per repo and per team. A single score is a snapshot; a trend shows whether the workflow is improving. The goal is to make the cleaner path the easier path.
The order matters. Catching slop at the hook is worth more than catching it in CI, and catching it in CI is worth more than catching it in review, because the cost of a fix rises at every step the problem survives. The goal is to move the catch as early as it will go.
## The compounding cost of doing nothing
Slop does not stay the same size. Every merge that ships a little shallow code makes the next change harder to reason about, which makes the next agent prompt produce shallower output because it is matching the surrounding code, which lowers the bar again. The loop accelerates. We wrote about how it reaches production in [when the AI slop loop breaks production](/blog/ai-slop-loop-breaks-production).
The cost lands in three places. Engineers spend more time reading and less time building, because comprehension is the bottleneck and slop attacks comprehension directly. Incidents get harder to trace when swallowed errors and missing observability hide what happened. Review quality also drops when people spend their attention on repeatable cleanup instead of design and behavior.
The fix is not heroics. It is a gate that holds the line for defined patterns, so the loop is easier to control before review.
## Frequently asked questions
### What is AI slop?
AI slop is AI-generated code that compiles, passes tests, and looks professional but is structurally shallow. It is not broken. It is slightly worse than what a careful human would write, repeated at machine scale, so the cost shows up as comprehension drag, silent failures, and rework rather than red CI.
### How is AI slop different from normal tech debt?
Normal tech debt is usually a known shortcut a human chose under time pressure. AI slop is unchosen and uniform. It arrives looking finished, it spreads across every file an agent touches, and it passes the checks teams rely on, so it stays invisible until it reaches production or until reviewers burn out trying to catch it by hand.
### Can you detect AI slop automatically?
Yes, for the patterns you can define precisely. Swallowed exceptions, trivial comments, unsafe type assertions, hallucinated imports, dead code, generic naming, and oversized functions are all detectable with deterministic rules that produce the same verdict every run. Logic-level problems that depend on intent still need a reviewer or an LLM pass.
### Is deterministic or LLM-based detection better?
Use both, for different jobs. Deterministic rules belong in the quality gate because they are consistent enough to enforce repeatable standards. LLM review is useful for logic-level suggestions that require understanding intent, but it is probabilistic, so avoid making it the only merge blocker.
### How do I stop my AI agent from writing slop?
Put the rules where the agent works. Run a scanner as a pre-commit or agent hook so issues surface in-editor and can be fixed before they are committed, then enforce a score threshold in CI when the team is ready. The value is a shorter, clearer feedback loop.
### Does an AI slop scanner replace my linter or SonarQube?
No, it complements them. Linters catch wrong code and style. General static-analysis tools may not focus on AI-specific patterns like trivial comments and swallowed exceptions. An AI slop scanner targets that third category: correct-but-shallow code repeated at scale.
## The bottom line
AI slop is not a content problem that wandered into your repo. It is a code-quality problem with a specific failure profile, and it benefits from tooling built for that profile. Linters catch wrong code. LLM reviewers catch logic bugs. AI slop detection catches a third thing: code that is correct but shallow, repeated across agent-assisted changes.
A deterministic gate helps because it is consistent, because it can run before review, and because it gives the team a repeatable baseline. Run `npx aislop scan` on your repo to see where you stand.
---
# What I fixed after that score, and what I kept
**URL:** https://scanaislop.com/blog/what-i-fixed-and-what-i-kept/
**Date:** 2026-05-30
**Tag:** Engineering
A clean library scored 1 out of 100, and the score was my bug, not the code. Here is the week I spent fixing it: rule by rule, measured on real projects, including the scoring change I built, liked for an hour, and then reverted because it was lying.
[Part one](/blog/the-feedback-that-made-my-launch)
ended with a maintainer's clean library scoring 1 out of 100, and me realizing the score was my
bug, not his code. This is the week I spent fixing it, and the one decision I am most glad I did
not get wrong.
## Precision, not suppression
When you find a noisy rule, the lazy fix is to turn it off. I did not want to do that, because a
rule that is off catches nothing. Every change I made instead tightens the rule so it still fires
on the real pattern but stops flagging the convention around it.
I kept his library open as a benchmark and re-scanned it on every single change. No guessing, no
"this feels better." Just one question each time: did the noise drop while the real findings
stayed?
## The rule-by-rule pass
A few of the changes, so you can see the shape of it:
- wrappersnow only flag a true passthrough, a function forwarding its own arguments unchanged. A call that transforms its arguments, like `bool(message.animation)`, is real work, not a wrapper.
- commentsa long, well-written explanatory comment is no longer slop just because it is long. The rule now needs an actual narration signal, like "This function does X" or "First it, then it, finally it", before it fires.
- catchesa catch that logs the error is observable, intentional recovery. People write that on purpose. It is only flagged now when the error is dropped on the floor.
- importsimport-shaped text inside docstrings and example blocks is no longer read as a real import, and a handful of standard-library modules I was simply missing are now recognized.
On his library the findings fell from 426 to 92. I checked it both ways. The ones I removed were
false positives. The ones I kept were real.
## The fix I built, liked, and threw away
Even at 92 findings the library still scored low, because the rest were real: large modules, and a
lot of descriptive comments. So I built a scoring change that judged a codebase by its findings
per file instead of its total. A big clean repo stops getting punished for being big, and his
library jumped into the 80s.
It felt great for about an hour. Then I saw what it actually was. A way to make the number look
generous without removing a single real finding. The library genuinely had 92 things my rules
flag, and the new math did not fix them. It hid them behind a friendlier denominator. That is the
exact move I had refused a week earlier, wearing a different hat. So I reverted it.
## What I kept strict
I did make one small, honest change. Style rules, like comment density and file size, now count for
half in the score, so the number is driven by genuine slop rather than house style. But I kept the
tool opinionated. A great library can still have debt by my standards, and I would rather say that
plainly than tune the math until the score looks kind.
The whole point of a quality gate is that it tells you the truth. The day it starts being generous
to be liked is the day it stops being worth running.
## Then I checked it everywhere
One library is not proof. So I ran the new build across a basket of well-known projects in
different languages, and it found more of my own false positives, like imports parsed out of
docstrings and stdlib modules I had missed. I fixed each one with a regression test so it cannot
come back. The tool got more accurate, not quieter. The real findings, the unused variables, the
leftover debug prints, the swallowed errors, the vulnerable dependencies, all still fire.
All of this shipped in **v0.10.0**. It started with one
person being generous enough to tell me my tool was wrong, and it ended with a tool that is
sharper and still honest.
If you run it and think I drew a line in the wrong place, I want to hear that too. Try it with
`npx aislop scan`, then tell me where I got it wrong
on [GitHub Discussions](https://github.com/scanaislop/aislop/discussions)
or at [x.com/heavykenny](https://x.com/heavykenny).
I read every one.
---
# The feedback that made my launch
**URL:** https://scanaislop.com/blog/the-feedback-that-made-my-launch/
**Date:** 2026-05-29
**Tag:** Opinion
I launched aislop on Hacker News and went from 21 stars to over 200. Then the maintainer of a library with tens of thousands of stars ran it on his own code, scored 1 out of 100, and told me plainly that I had it wrong. That message is the reason I keep building this.
I built **aislop** because I kept seeing the same patterns
in AI-written code. Narrative comments that restate the line below them. Wrappers that wrap
nothing. Catch blocks that swallow the error and move on. I wanted a tool that catches those, so
a human reviewer never has to.
For a while it sat at 21 stars. Then I posted it on Hacker News, and over a couple of days it
went past 200. I am genuinely happy about that. But the stars are not the part I want to write
about. The part I want to write about is one message from one stranger, and why it made the whole
launch worth it.
## First, the part I got wrong
Before the good story, the honest one. Under launch traffic the dashboard started throwing errors.
People connected a repo, ran a scan, and watched the results page fail to load. The cause was
plain once I looked: every request was loading a full scan payload into memory and parsing it.
Fine at ten requests a minute, not fine under a spike. I fixed it that afternoon and added
headroom. The scans themselves never failed. The page that fetched them did.
Rough timing for a first impression, and I am sorry to anyone who hit it. The thing that breaks is
never the thing you stress-tested.
## The message that mattered
Someone who maintains a Python library with tens of thousands of stars ran aislop on his own
projects. One of them, written largely before the current wave of AI tools even existed, scored
in the low teens. He pointed it at another of his libraries and it scored **1
out of 100**.
His note was short and honest. He expected a tool with "slop" in the name to detect
AI-generated code. Instead it had graded his careful, mostly-human code as if it were the problem.
He did not have to say any of that. He could have closed the tab and moved on. Instead he wrote
back, pointed at exactly what felt wrong, and offered to get on a call. We talked.
That is the thing. A maintainer with that much on his plate took the time to tell me, kindly and
directly, that my tool was wrong. People do not owe you that. When they give it anyway, you listen.
## Why I did not get defensive
The reflex is to explain. "Well, the score measures maintainability, not whether the code is
AI-written." All true, and completely useless. When someone who writes excellent code tells you
your tool called it slop, the score is the message, and the message landed wrong.
So instead of explaining, I did the thing he suggested. I scanned his library myself, one finding
at a time, to see exactly why clean code scored a 1.
## What the 1 was actually made of
The library produced 426 findings. I broke them down by rule, and the result was uncomfortable.
Three rules accounted for most of them, and they were firing on normal library code. A one-line
method like this:
```
def filter(self, message): return bool(message.animation)
```
is not a wrapper to delete. It is exactly how that library exposes its public API. A comment that
explains a non-obvious ordering constraint is not narration. It is the comment you most want to
keep. The 1 was not a verdict on his code. It was a verdict on my rules.
## This is why I build it
A real maintainer, real code, honest feedback, and a clear bug to go fix. That loop is the reason
I started this. The launch numbers are fun, but a person taking my tool seriously enough to tell
me where it falls down is worth more than any of them.
So here is my ask. Run it on your own code: `npx aislop scan`.
If it gets something wrong, that is exactly what I want to hear. Tell me on
[GitHub Discussions](https://github.com/scanaislop/aislop/discussions)
or find me at [x.com/heavykenny](https://x.com/heavykenny).
In [part two](/blog/what-i-fixed-and-what-i-kept) I
go through what I actually changed after that score, and the one fix I built, loved for an hour,
and then threw away.
---
# aislop v0.9.4. SlopCodeBench called it verbosity. We turned it into rules.
**URL:** https://scanaislop.com/blog/aislop-v0-9-4-python-scbench-rules/
**Date:** 2026-05-28
**Tag:** Release
Four new Python rules drawn from the verbosity signal in SlopCodeBench (SCBench, arXiv 2603.24755). Plus a CLI star prompt and GitHub Discussions for the community.
**aislop** has always been an empirical project. Most rules come from a real codebase where an agent shipped something nobody human would have written. v0.9.3 was the receipts release — 70 open-source repos, 38% noise reduction, no rule disabled.
v0.9.4 is different. This week we read a paper.
## SlopCodeBench
**SlopCodeBench** (SCBench for short, [arXiv 2603.24755](https://arxiv.org/abs/2603.24755), March 2026) measures something most coding-agent benchmarks ignore: what happens when an agent has to extend its own prior code repeatedly under changing specifications. The benchmark has 20 problems and 93 checkpoints. The test suite is hidden. Only observable behavior at a CLI or API boundary is specified. Internal structure is left to the agent.
Two trajectory-level quality signals are tracked: **verbosity** (the fraction of redundant or duplicated code) and **structural erosion** (the share of complexity mass concentrated in high-complexity functions). The findings are sobering: agents pass individual test cases at high rates, but strict solve rates that include regression collapse to 0.5% by the final checkpoint.
Translation: agents can solve a problem at checkpoint one, but by checkpoint nine they've buried it in plumbing nobody asked for. That plumbing has shapes. We can match those shapes deterministically. So we did.
## Four new Python rules
Each rule has paired tests covering the pattern and the legitimate alternative (so the negative case stays protected against regression). 35 new tests landed in the Python suite; the full test count is now 842, all passing.
### ai-slop/python-range-len-loop info
Flags `for i in range(len(items))`. The Pythonic alternative is `enumerate(items)` or direct iteration. SCBench surfaces this as a recurring agent shortcut: the model writes a C-style index loop where the language has a one-token primitive.
# flagged
for i in range(len(users)):
out.append(users[i].name)
# clean
for user in users:
out.append(user.name)
### ai-slop/python-chained-dict-get warning
Flags `.get(..., {''}).get(...)` chains. The empty-dict fallback hides missing-data cases and turns brittle as schemas evolve. Help text points to boundary normalization or a typed object.
# flagged
name = payload.get("user", {''}).get("profile", {''}).get("name", "")
# clean — normalize at the boundary
user = User.parse(payload)
name = user.profile.name
### ai-slop/python-repetitive-dispatch warning
Flags four or more `if x == "..." / elif x == "..."` branches on the same selector. The clean version is a dispatch table or handler map.
# flagged
if event == "created":
handle_create()
elif event == "updated":
handle_update()
elif event == "deleted":
handle_delete()
elif event == "archived":
handle_archive()
# clean — dispatch table
HANDLERS = {'
"created": handle_create,
"updated": handle_update,
"deleted": handle_delete,
"archived": handle_archive,
'}
HANDLERS[event]()
### ai-slop/python-isinstance-ladder warning
Flags four or more chained `isinstance(...)` branches on the same value. Recommends a handler map or normalized representation. Same shape as the dispatch ladder; same fix.
## Why we cared about this paper
Most coding-agent benchmarks score one-shot completion: can the agent solve the problem from a fresh prompt? That measures the wrong thing. Real engineering is a sequence: you write code, then you change it, then you change it again, and the question is whether the codebase stays maintainable along the way.
SCBench is the first benchmark we've seen that takes trajectory seriously. It puts numbers on the thing we keep saying: *agents make you 10x faster and 10x messier at the same time, and the cost shows up downstream*.
Verbosity, in particular, is exactly the slop class aislop was built for. The four rules in this release map cleanly to the patterns the paper's Python track surfaces. If you've been wondering whether the rules in this CLI are arbitrary or empirical: now they're both.
## Also in this release
- **A one-line star prompt at the end of scan output.** Running `npx aislop scan` now ends with `★ Found this useful? Star us at github.com/scanaislop/aislop`. Muted styling, single line. Suppressed in JSON output, in `aislop ci`, and for any hook caller that passes `printBrand: false`.
- **GitHub Discussions is open.** Two structured templates ready to use: `[FP]` for false-positive reports (rule name, snippet, reasoning, version) and `[Rule]` for rule requests (pattern, what should pass, suggested name, language). Most rules in this repo came from community comments; the Discussions surface makes that easier.
- **README leads with the verb.** The headline now reads *"Catch the slop AI coding agents leave in your code"* instead of the previous category-language version. The lead names the agents (Claude Code, Cursor, Codex, OpenCode) and the patterns they leave behind.
## What's next
SCBench tracks two signals. We shipped rules for the first one (verbosity). The second one (structural erosion — complexity mass concentrated in high-complexity functions) is on the next release. The shape is clear: function-too-large, file-too-large, deep-nesting, and a complexity-density metric to catch the case where one function does ten things while its siblings do nothing.
Run the new release:
npx aislop@latest scan
If you find a false positive or want a rule we haven't shipped yet, the [Discussions](https://github.com/scanaislop/aislop/discussions) are the right place. Most of what's in this release was suggested by someone there or on Reddit.
[Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# aislop v0.9.3. We measured the noise. Then we cut it by 38%.
**URL:** https://scanaislop.com/blog/aislop-v0-9-3-rule-precision/
**Date:** 2026-05-22
**Tag:** Release
Patch release focused on rule precision. Tightens detection across the ai-slop, security, lint, and source-file engines so language conventions are no longer flagged as slop. No new rules — existing ones now discriminate better.
For most of **aislop**'s history we tuned rules against our own codebases and a handful of trending projects. That's fine for shipping early, but it leaves blind spots: every language has documentation conventions, intentional code patterns, and ecosystem-level injections that look exactly like the AI slop our rules were built to catch. When the rule can't tell them apart, you get noise. Enough noise and developers learn to scroll past your output.
v0.9.3 is the receipts release. We ran the numbers. We fixed what was wrong. We kept the regression tests.
## The experiment
We scanned **70 popular open-source repositories** — 10 each across TypeScript/JavaScript, Python, Go, Rust, Ruby, PHP, and Java. Names you know: `pytest`, `pydantic`, `mypy`, `vue/core`, `tailwindcss`, `guava`, `rubocop`, `faker`, `phpunit`, `regex`, `ripgrep`, `composer`, `laravel/framework`, and 57 more.
Baseline cohort: **32,840 findings**.
For every rule that fired heavily, we sampled the actual code being flagged and asked one question: *is this AI slop, or is it the language's documentation convention? An interface implementation? A bundler-injected global? A test-helper pattern?*
## What we found
A handful of false-positive classes kept showing up:
- `security/eval` matched `$obj->eval(...)` in PHP — Predis Lua-script calls, not PHP's `eval()` language construct. 28 of 29 PHP hits were this pattern.
- `ai-slop/thin-wrapper` matched `public function valid(): bool {' return isset(...); '}`. The JS function-shape regex didn't require an `export`, so PHP interface implementations (`Countable::count`, `Iterator::valid`) and Java methods got swept in. 325 hits across the cohort.
- `ai-slop/hallucinated-import` flagged `` 835 times in pytest alone. Pytest's own internal `` package wasn't declared in `pyproject.toml`, so the rule treated it as a missing dependency.
- `ai-slop/narrative-comment` flagged every Javadoc, PHPDoc, and YARD block above a declaration. 1,230 hits in Guava alone — all on standard Javadoc.
- `eslint/no-undef` flagged `__DEV__` / `__BROWSER__` / `__VERSION__` in Vue 777 times. These are conventional `define` injections every modern bundler does.
- `ai-slop/rust-non-test-unwrap` flagged `.unwrap()` inside `/*! ... */` doc comments in the `regex` crate. Those are ````rust` example blocks — teaching shorthand, not production code.
These aren't quirks. They're the language conventions developers expect their linter to understand.
## Precision, not suppression
The lazy fix when you find a noisy rule is to turn it off. We didn't.
Every change in v0.9.3 *tightens* detection. The slop-signal paths still fire — `// Matches X`, `// This function does N things`, `// First it ... then it ... finally it` are all still caught, because those *are* the patterns the rule was designed for. What changed is that the rule now distinguishes those AI-style restatements from the genuine documentation around them.
Pattern
Before
After
Javadoc above a class, no slop signal
flagged
exempt
Javadoc starting with `Represents X` / `Wraps Y`
flagged
still flagged
`catch (Exception ignored)`
flagged
exempt
`catch (Exception e) {''}` (empty)
flagged
still flagged
`from X import Y as Y` (PEP 484 re-export)
flagged
exempt
`from X import Y` where `Y` is never used
flagged
still flagged
`$obj->eval(...)` (Redis Lua)
flagged
exempt
Top-level `eval($code)`
flagged
still flagged
## How we got here
This wasn't one diff. We iterated **eight rounds** — each round sampled the new top-of-list false-positive class, traced it back to the specific regex or heuristic that was misfiring, and tightened the rule. After every round we re-scanned all 70 repos to confirm the change moved the needle without breaking the cases the rule was supposed to catch.
Every fix landed with regression tests. The suite grew from 800 → 835 cases, all green.
## The numbers
Same 70 repos, same commits, before and after the full v0.9.3 change set:
Rule
Before
After
Δ
ai-slop/narrative-comment13,8176,250−55%
ai-slop/hallucinated-import2,161900−58%
eslint/no-undef926314−66%
ai-slop/thin-wrapper569244−57%
ai-slop/trivial-comment4,0353,190−21%
ai-slop/swallowed-exception694497−28%
security/eval (Ruby)8726−70%
security/eval (PHP)2912−59%
Total cohort findings32,84020,366−38%
Several popular projects moved from unranked (score 0) to honest scores. A few highlights:
faker-ruby/faker
0 → 36
sinatra/sinatra
3 → 31
rspec/rspec-mocks
3 → 30
phpunit/phpunit
9 → 25
doctrine/dbal
14 → 24
Seldaek/monolog
32 → 44
jekyll/jekyll
1 → 18
rubocop/rubocop
0 → 2
And four more popular projects moved from score 0 to a positive score for the first time: `vuejs/core`, `pytest`, `tailwindcss`, and `rust-lang/regex`.
## What ships
- **Comment rules** recognize Javadoc, PHPDoc, JSDoc, YARD, RDoc, and Go struct-field docs as documentation. The catch-all "long narrative" rule no longer fires above declarations; explicit slop signals (`^This function/method/class`, `^It does/handles/...`, `^First/Then/Finally it`) replace the length-only heuristic.
- **Hallucinated-import** discovers local Python packages via `` scanning and honors PEP 484 re-exports.
- **Security/eval** has a proper lookbehind for method-call forms.
- **Thin-wrapper** patterns are language-gated to JS/TS and Python.
- **Swallowed-exception** allows `tolerated`/`ignored`/`expected` catch parameters.
- **Rust non-test-unwrap** skips `` doc-comment example blocks; `` (singular) recognized as test file.
- **Oxlint** registers conventional bundler globals (`` and friends) for every project, plus discovers ambient `.d.ts` declarations and SST globals automatically.
- **Source-file walker** filters ``, ``, honors Biome's `files.includes`, and ignores ``.
- **Dangerous innerHTML** respects `aislop-ignore` / `biome-ignore` / `eslint-disable` directives and JSON-LD structured data.
## What's next
A 38% drop is meaningful, but it isn't the finish line. The remaining findings include real slop, real complexity issues, and a long tail of cases where the rule is right but the call is debatable. We'll keep iterating — and we'll keep posting the receipts when we do.
The whole point of a quality gate is that it tells you the truth. v0.9.3 makes aislop a little more honest.
## Install
$ npx aislop scan
Full changelog on [GitHub](https://github.com/scanaislop/aislop/releases/tag/v0.9.3). [Star the repo](https://github.com/scanaislop/aislop) to get the next release in your feed.
---
# A prompt is a suggestion. A CI gate is a guardrail.
**URL:** https://scanaislop.com/blog/your-agents-need-a-guardrail/
**Date:** 2026-05-18
**Tag:** Opinion
A prompt can guide an agent, but an enforced check is what keeps standards visible in the workflow.
Is `AGENTS.md` a guardrail? It helps, but by itself it is guidance. A stronger guardrail is an enforced check the workflow cannot quietly skip.
There is a whole category of thing the industry keeps calling a "guardrail" that is not one. A markdown file at the repo root with rules in it. A team retro where everyone agrees on a style. A pinned Slack message. These are **norms**. Useful, sometimes followed, not automatically enforced. A **guardrail** is different: the check runs, the result is visible, and a configured threshold can block the commit or PR. The distinction matters because norms depend on memory; checks depend on execution.
Does your coding agent have a coding standard? `AGENTS.md` is not enough. You have to hold your agent accountable. Your coding agent should have guardrails.
## What's not a guardrail
All of these are fine. None of them are guardrails.
Pattern
Why it is not a guardrail
AGENTS.md / CLAUDE.md / .cursorrules
The agent may or may not open the file. If it does, it may or may not follow it. There is no consequence for ignoring it.
System prompts with rules
"Do not use `any`. Do not leave dead code." These are preferences. The agent honors them maybe 80% of the time, which is worse than 0% because you stop checking.
LLM-as-reviewer
A second agent reads the first agent's PR. Both agree it looks good. Both miss the unused import because neither is grepping. They are vibing.
The Slack pin
"Hey please stop writing narrative JSDoc" gets pinned, gets ignored, gets unpinned by the next admin cleanup.
## What is
A guardrail executes. It has a binary output. It runs without anyone remembering to run it.
- + **A CI check with a threshold.** `scanaislop/aislop@v0.10.1` in GitHub Actions, with `ci.failBelow: 70` in `.aislop/config.yml`. Score drops under 70, the PR is red. No merge button.
- + **A pre-commit hook.** `aislop fix --staged` runs before every commit. Slop does not enter the repo.
- + **A pre-push hook.** Block the push if there are unresolved errors, so risky changes are surfaced before they leave the machine.
- + **An MCP server.** The agent can call `aislop scan` before returning its response. Score below threshold, the agent gets concrete findings to address.
## A small guardrail you can ship today
One small workflow. One Action step. A practical check between your repo and code that no one has reviewed closely yet.
That is the file. Commit it. Open a PR. The check runs. The score comes from `.aislop/config.yml`. If it drops below threshold, the PR can be blocked until somebody fixes it. Start the threshold near where your repo scores today and ratchet it up over time. The point is not to instantly hit 90. The point is to make quality visible and keep the bar from drifting down unnoticed.
## Where rules live
**aislop** ships 40+ rules across six engines: format, lint, code-quality, ai-slop, security, architecture. The rules cover the things AGENTS.md asks for and cannot enforce. `ai-slop/narrative-comment`. `ai-slop/swallowed-exception`. `ai-slop/unsafe-type-assertion`. `ai-slop/console-leftover`. `complexity/function-too-long`. `complexity/file-too-large`. `security/vulnerable-dependency`. `security/sql-injection`.
Each is a deterministic check with a rule ID, a file location, and a fix path. The CI gate aggregates the violations into a score. The score is what the threshold compares against. The threshold is what fails the PR. The rule is not just told. It is enforced.
## The MCP future (0.6)
On the 0.6 roadmap, **aislop** becomes an MCP server. One sentence pitch: the agent can run `aislop scan` before its response reaches the user, then use the findings to clean up repeatable issues before a PR exists.
Think of it as shortening the feedback loop. Write code. Call `aislop scan`. Look at issues. Address them. Call `aislop scan` again. When the score crosses the configured threshold, the human sees code that has already passed the mechanical checks.
CI gates protect the repo at merge time. An agent-time scan protects the reviewer earlier in the loop, while the code is still cheap to change.
## Why norms decay
A team running on norms alone gets to a predictable place. First month, great. New rules, everyone excited, code looks cleaner. Month three, someone ships a PR violating one rule and nobody blocks it because "it is a small thing." Month six, the rule is effectively gone. Month nine, a retro proposes adding the rule back. The cycle continues until somebody realizes the rule was never really there. Only the agreement that it should be was.
Guardrails do not negotiate. They do not have an off day. They do not make exceptions for the new hire's first PR. They do the one thing they exist to do, every time, until the team turns them off deliberately. That is the property worth building for. Humans are good at judgment calls. Machines are good at refusing to make judgment calls. Use each for what it is good at.
## Put the wall up
Written guardrails are useful context. Executable ones are how teams make the rule visible every time. One workflow and a threshold in config gives the next PR a clear standard to meet.
## Ship the gate
{`- uses: scanaislop/aislop@v0.10.1
with:
version: latest`}
Drop it in your workflow. The next PR has a wall. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# The AI Slop Loop: When AI-Generated Code Creates Self-Reinforcing Quality Problems
**URL:** https://scanaislop.com/blog/ai-slop-loop-breaks-production/
**Date:** 2026-05-16
**Tag:** Essay
AI writes shallow code. Shallow code produces incidents. Incidents produce more AI-generated fixes. The fixes introduce new shallow patterns. The loop accelerates.
There is a pattern emerging across teams that have been shipping AI-generated code for 6-12 months. The data is still anecdotal — no longitudinal study has been published yet — but the signal is consistent enough across engineering teams to describe it.
Teams report an initial 2-3x velocity gain from AI coding, followed by a sharp deceleration. More code to maintain. Weaker understanding of the system. Longer debug cycles. More incidents from shallow patterns. The fix for each incident is often another AI-generated PR — which introduces its own shallow patterns. The loop accelerates.
We call this **the AI slop loop**.
## How the loop works
**Phase 1: Velocity.** Team adopts AI coding. PRs get bigger. Cycle time drops. Excitement is high.
**Phase 2: Comprehension debt accumulates.** Code volume grows faster than understanding. AI writes code the team has not fully reviewed. "I don't even review anymore, I just deploy." The system becomes a black box.
**Phase 3: The first incident.** A swallowed exception. A missing timeout. A connection leak. Something breaks in production. The team fires an AI agent to fix it.
**Phase 4: The fix compounds the problem.** The AI fix patch is another AI-generated PR. It fixes the immediate symptom. It introduces two new shallow patterns. The comprehension debt grows.
**Phase 5: Velocity collapses.** The team is now spending more time debugging AI-generated incident fixes than they saved by using AI to write code. The productivity gain has inverted.
## The driver: comprehension debt
Comprehension debt is the gap between how much code exists and how much the team understands. Traditional technical debt accumulates when teams take shortcuts. AI code accelerates this because the code was never understood in the first place — it was generated, reviewed at speed, and merged. The debt starts accruing on day one, before the first incident.
The patterns that drive comprehension debt are the same ones aislop detects: trivial comments that drift into misinformation, generic naming that makes debugging impossible, unused variables that confuse readers, dead code paths that no one understands. Each one is small. Collectively, they make the codebase inscrutable.
## The AI slop loop in the LLM context
Lily Ray's research on the "AI slop loop" in search engines documents a parallel phenomenon. When one AI system hallucinates a detail, AI-powered content pipelines scrape and republish it. More AI scrapers pick up those copies. RAG systems then cite the fabrication as fact because it now has multiple "sources." The same structural dynamic applies to code: AI-generated fixes to AI-generated bugs train on AI-generated context, producing increasingly shallow output.
## Breaking the loop
The loop has a single point of intervention: **the quality gate.** If every AI-generated PR is scanned against deterministic rules before it merges, the loop cannot form. The gate blocks the shallow pattern before it becomes the context for the next fix. Each generation of AI output starts from a clean baseline instead of compounding the previous generation's debt.
This is what aislop was built for. An aislop quality gate does not just catch individual issues — it prevents the compounding loop. When a fix PR triggers `pro/error-handling/empty-catch`, the team catches it before it merges. The next AI fix does not inherit the pattern. The loop breaks.
## Signs you are in the loop
1. Your PR size has grown 3x but your team size has not changed.
2. You have stopped reading PR diffs and started scanning summaries.
3. Incidents are increasingly traced to code nobody remembers writing.
4. Your debug time has increased even though your output has increased.
5. You have at least one swallowed exception in production right now. (You probably do not know about it.)
## The bottom line
The AI slop loop is not inevitable. It is the result of adopting AI coding without adding an AI-quality gate. Teams that ship the most successful AI-generated code share one practice: they enforce a quality standard that applies to every PR, regardless of author. The standard is mechanical, deterministic, and blocks below a threshold. It does not negotiate with the agent. It enforces.
Run `npx aislop scan` on your repo. See how many loops have already started.
---
# AI Slop Statistics 2026: The Data Behind the Backlash
**URL:** https://scanaislop.com/blog/ai-slop-statistics-2026/
**Date:** 2026-05-16
**Tag:** Reference
74% of new web pages contain AI content. 86.5% of Google top-ranking pages are AI-generated. $117M flows to AI slop channels on YouTube. The numbers behind the trend.
December 2025, Merriam-Webster named *slop* its Word of the Year. Not because the dictionary wanted to make a statement — because the data made the choice unavoidable. By late 2025, AI-generated content was no longer a fringe phenomenon. It was the dominant mode of production on the internet.
Here is the data that defines the AI slop era, compiled from every major study published in the last 18 months. These are the numbers that matter for engineering leaders deciding how to handle AI-generated code.
## Web content: the scale of the problem
**74% of new web pages** published in 2025 contained at least some AI-generated material, according to an Ahrefs study of 600,000 top-ranking Google pages. Only 2.5% were 100% AI-generated with no human contribution — the rest were hybrid, making detection harder.
**86.5% of Google's top-ranking pages** contain AI-generated content. The correlation between AI content percentage and Google ranking position? 0.011 — effectively zero. Google's algorithm does not penalize AI content. Quality signals, not provenance, determine rank.
**3,006 AI content farm sites** identified by NewsGuard and Pangram Labs as of March 2026, growing at 300 to 500 new sites per month. Of these, 358 were linked to Storm-1516, a pro-Russian influence operation mimicking local US and European newspapers.
**$117 million per year** flows to AI slop channels on YouTube alone, per Kapwing's analysis of the 15,000 most popular channels. They found 278 channels containing only AI slop with 63 billion combined views.
## AI-generated code: the data your team should know
**51% of GitHub commits** now contain AI-generated code. The majority of code entering your repository was written by a model, not a person.
**43% of AI-generated code changes** need debugging in production, per VentureBeat analysis. These are not compile errors — they are silent failures that pass tests and break under real conditions.
**AI code produces 1.7x more issues per PR** than human code, per CodeRabbit's 2025 benchmark. The gap widens with PR size — large AI-generated PRs have disproportionately more defects.
**60% of AI code faults** are "silent failures" that compile, pass tests, and produce wrong results. These are the patterns traditional CI does not catch.
**62% of AI-generated code** contains security weaknesses, per Endor Labs analysis. AI models trained on public repos learn the vulnerabilities along with the patterns.
**10x surge** in AI-code-related security findings reported by enterprise security teams. The same forces driving adoption velocity are driving the demand for enforcement tooling.
## The social signal
**475,000+ social media mentions** of "AI slop" in a single 30-day period across X, Instagram, TikTok, and Threads. The largest single-day spike: December 11, 2025, when McDonald's Netherlands pulled an AI-generated Christmas ad — 37,000 posts in one day.
**Search volume for "AI slop":** 74,000 monthly searches, growing +9,100% year over year. It went from zero to a top-trending keyword in 12 months.
**"AI slop" search interest** was discovered as an exploding topic in February 2026 and has not plateaued. The backlash is not a temporary cycle — it is a structural shift in how people talk about AI output.
## What the data means for engineering teams
Three takeaways for anyone shipping AI-generated code to production:
**1. Traditional CI is not enough.** Your tests pass. Your linter is green. Your PR is approved. None of these detect the patterns AI code produces — swallowed exceptions, unsafe assertions, missing timeouts, hallucinated imports. You need tooling purpose-built for AI failure modes.
**2. The volume is too high for manual review.** AI agents commit hundreds of lines across dozens of files per session. No human can review that volume with attention to detail. The only scalable solution is automated enforcement — a quality gate that blocks before review, not a reviewer that comments after.
**3. The market is consolidating around enforcement.** The community demand in 2026 is clear: deterministic, CI-native, diff-aware scanners that block below a threshold. Tools that provide suggestions instead of gates are losing relevance.
## Sources
Ahrefs (600K page study, July 2025) · NewsGuard & Pangram Labs (content farm tracking, March 2026) · Kapwing (YouTube analysis, December 2025) · Endor Labs (security analysis, 2025) · CodeRabbit (PR benchmark, 2025) · VentureBeat (production failure rates, 2025) · Exploding Topics (keyword trending, 2026) · Visibrain (social media monitoring, December 2025) · scanaislop internal research
---
# aislop and CodeRabbit: Deterministic Gates and AI-Powered Review
**URL:** https://scanaislop.com/blog/aislop-vs-coderabbit-deterministic-vs-llm/
**Date:** 2026-05-16
**Tag:** Guide
Two tools, two philosophies. CodeRabbit helps with LLM-powered PR review. aislop enforces defined AI-code hygiene rules. Here is when each fits.
If you ask five people what "AI code quality tool" means, you will get five different answers. Some mean a probabilistic reviewer that comments on PR diffs. Some mean a deterministic scanner that blocks merges below a threshold. Some mean a linter with AI-specific rules. The confusion is understandable — the category is new and every vendor is still finding their positioning.
This comparison focuses on two tools that represent opposite ends of that spectrum: **CodeRabbit** (probabilistic, LLM-powered, suggestion-based) and **aislop** (deterministic, rule-based, enforcement-focused). They are not direct competitors in most use cases. But understanding the difference between them is how you decide what your team actually needs.
## How they work
### CodeRabbit: probabilistic PR review
CodeRabbit sends every PR diff through an LLM and asks it to find problems. The model returns observations — a swallowed exception here, a potential null reference there. CodeRabbit formats these as inline PR comments and a walkthrough summary. The result can be useful, especially for explanation and review assistance. The tradeoff is consistency: the same diff can produce different feedback on different runs because LLMs are probability distributions, not deterministic engines.
### aislop: deterministic quality gate
aislop scans files against 40+ deterministic rules purpose-built for AI-generated code patterns. The same code produces the same score. No LLM inference, no probability, no model variation. The output is a score from 0-100 with an optional pass/fail threshold. Below the configured threshold, CI can block the PR. Every rule has a clear, machine-readable contract: if the pattern matches, the diagnostic fires.
## What each catches that the other misses
**CodeRabbit can help catch** things aislop will not: logic bugs, off-by-one errors, incorrect algorithm choices, missing edge cases. These require understanding intent — something deterministic rules cannot do.
**aislop catches** things a conversational reviewer may not flag consistently: trivial comments, swallowed exceptions, generic naming, unsafe type assertions, dead code, console leftovers, hallucinated imports, missing timeouts. These are defined patterns, not judgment calls.
## Key differences at a glance
Approach
CodeRabbit: LLM-powered probabilistic analysis. aislop: deterministic rule-based scanning.
Consistency
CodeRabbit: Variable. Same diff can produce different results. aislop: Identical. Same code, same score, every time.
Enforcement
CodeRabbit: Suggests in review. aislop: Can enforce a configurable threshold in CI.
AI-specific patterns
CodeRabbit: Review comments from an LLM. aislop: 40+ deterministic rules targeting defined AI-code hygiene patterns.
False positive rate
CodeRabbit: Depends on model output and review context. aislop: Depends on rule precision; deterministic matches make false positives easier to reproduce and fix.
Speed
CodeRabbit: Seconds to minutes per PR (LLM inference). aislop: Sub-second per file, seconds per project.
Network dependency
CodeRabbit: Requires API access to LLM. aislop CLI: Runs locally with no model call at scan time.
CI integration
CodeRabbit: GitHub/GitLab/Bitbucket app (comments on PRs). aislop: Native CLI exit codes for any CI. JSON output. GitHub Action, pre-commit hook.
Agent handoff
CodeRabbit: PR-review workflow. aislop: Auto-fix safe issues and hand unresolved findings to coding agents as structured prompts.
## When to use each
**Use CodeRabbit when** you want a second pair of eyes on every PR — logic concerns, suggested improvements, and readable summaries for human reviewers. It augments your review process.
**Use aislop when** you want a repeatable gate for defined AI-code hygiene patterns before merge. It reduces manual pattern-checking that humans should not have to repeat at scale.
**Use both when** you want open-ended review and deterministic rules in the same workflow. CodeRabbit comments. aislop can enforce.
## The bottom line
CodeRabbit and aislop solve different problems. CodeRabbit helps answer "does this change look right?" aislop answers "did this code violate a named rule?" One is probabilistic, the other is deterministic. One suggests, the other can enforce.
The strongest setup is often layered: LLM review for intent and explanation, deterministic checks for standards the team wants applied the same way every run.
---
# Best AI Code Quality Tools 2026: From Linters to Quality Gates
**URL:** https://scanaislop.com/blog/best-ai-code-quality-tools-2026/
**Date:** 2026-05-16
**Tag:** Guide
A practical guide to SonarQube, CodeRabbit, Greptile, Qodo, GitHub Copilot Review, and deterministic quality gates: what each category does well, where it stops, and how to choose.
AI-assisted code has made code review faster and more uneven at the same time. More code reaches PR faster. More of it looks polished. Some of it is correct. Some of it only looks correct.
The right tooling stack depends on what you are trying to catch. A linter catches style and obvious bugs. SAST catches known security patterns. An AI reviewer can explain a diff and suggest tests. A deterministic gate can enforce repeatable standards in CI. Those are different jobs.
This guide compares the main categories and common tools teams evaluate in 2026. It is not a winner-takes-all list. Most mature teams will combine two or three of these rather than betting on one.
## What the market looks like in 2026
The landscape is easier to understand if you group tools by job:
**Traditional static analysis** (SonarQube, Codacy, CodeClimate) checks syntax, style, maintainability, and known security patterns. These tools are mature, deterministic, and good at enforcing baseline engineering hygiene.
**AI code review** (CodeRabbit, Greptile, GitHub Copilot Review) reads PRs and leaves comments. These tools are useful for summaries, context, and logic-level suggestions, especially when reviewers need a second pass through a diff.
**Test-focused AI tools** (Qodo and similar products) focus on missing test paths, regression coverage, and generated test cases. They are strongest when the main risk is behavior that changed without coverage.
**Deterministic quality gates** (custom CI rules, policy-as-code, eslint rule packs) enforce repeatable standards before merge. They are less conversational than an AI reviewer, but more consistent when the team needs the same answer on every run.
## Tool-by-tool breakdown
### SonarQube
**Best for:** Teams that already run SonarQube and need deterministic security scanning.
SonarQube is still the default enterprise answer for broad static analysis. It is good at maintainability, code smells, duplicated code, and common security issues. It is also predictable: the same rule violation gets the same result in CI.
The limit is scope. SonarQube is not trying to be an AI-code reviewer. It will not understand whether a generated implementation matches a product requirement, and it may not flag the softer patterns that appear in agent-written code: over-broad fallbacks, generic naming, stale scaffolding, or comments that restate the line below.
### CodeRabbit
**Best for:** Teams that want LLM-powered PR summaries with inline suggestions.
CodeRabbit is strong when teams want a readable walkthrough of a PR. It can summarize changes, call out suspicious logic, and leave inline comments that help a human reviewer move faster.
The tradeoff is that it is a reviewer, not a deterministic gate. Some comments will be useful. Some will be debatable. A team still needs a human to decide which suggestions matter and which ones are noise.
### Greptile
**Best for:** Large monorepos where cross-file context matters.
Greptile is a better fit when a diff only makes sense with repository context. It indexes the codebase and reviews changes against more than the visible patch, which can help in large monorepos or systems with shared abstractions.
The tradeoff is operational: repository indexing, review latency, and the usual LLM-review judgment calls. It can be very useful, but it is not a replacement for tests, static analysis, or merge policy.
### Qodo (formerly Codium)
**Best for:** Teams that want test generation coupled with code review.
Qodo is strongest when the main question is not "is this style acceptable?" but "what test paths did this change miss?" That makes it useful for teams trying to turn review comments into better coverage.
Generated tests still need review. They can encode the current behavior instead of the intended behavior, and they can add maintenance cost if the team accepts them blindly.
## Where each category stops
AI reviewers can explain a diff, but they do not own your product requirements. Static analysis can enforce known rules, but it cannot prove the feature is useful. Test generators can suggest coverage, but generated tests can still assert the wrong behavior. Quality gates can block repeatable patterns, but they only know the patterns you define.
That is why the best setup is usually layered. Use deterministic tools where consistency matters. Use AI review where context and explanation help. Use humans for product judgment, architecture, and tradeoffs that cannot be reduced to a rule.
## How to choose
**You already have SonarQube?** Keep it for baseline static analysis and security. Add other tools only for gaps SonarQube is not meant to cover.
**You want PR summaries and inline suggestions?** Start with CodeRabbit, GitHub Copilot Review, or a similar AI reviewer. Measure comment quality before making it required.
**You have a sprawling monorepo?** Look at tools with repository context, such as Greptile, especially for areas where a diff cannot be understood file by file.
**You want repeatable merge rules?** Use deterministic gates: custom lint rules, policy-as-code, security thresholds, or a focused AI-code hygiene gate when that is the gap.
**You need better test coverage?** A test-focused assistant such as Qodo can help propose missing cases, but keep humans responsible for whether the tests express the intended behavior.
## The bottom line
No single tool covers every angle. The useful question is not "which tool wins?" It is "which failure mode are we trying to reduce?"
Use linters and SAST for baseline hygiene, AI reviewers for explanation, test tools for coverage pressure, and deterministic gates for rules you want enforced the same way every time. The stack should make review calmer, not louder.
## What we are doing differently
At scanaislop, we are not trying to replace SonarQube, CodeRabbit, Qodo, or human review. We are building for one narrower gap: AI-assisted code that looks finished but carries repeatable quality debt.
That changes the product shape. The open-source aislop CLI runs locally, uses named deterministic rules, returns the same score for the same code, and fits into CI as a merge bar. The hosted product adds team standards, PR enforcement, and dashboards on top.
The difference is focus. Reviewers explain. Linters enforce general hygiene. Tests protect behavior. scanaislop makes AI-code hygiene measurable enough to hold every agent to the same standard.
---
# How to Add a Quality Gate to Your AI Agent in 2 Minutes
**URL:** https://scanaislop.com/blog/quality-gate-ai-agent-in-2-minutes/
**Date:** 2026-05-16
**Tag:** Tutorial
One local scan. One GitHub Actions job. Every PR from every agent held to the same bar. Here is the fastest way to stop AI slop from shipping.
Your AI agent writes code at machine speed. Your PR review process was designed for human pace. Something has to give — and in most teams, it is the review. PRs merge without being read. Incidents pile up. The loop accelerates.
A quality gate fixes this. It does not replace review — it replaces the manual pattern-checking that humans should not be doing at scale. The gate scans every diff, scores it 0-100, and blocks merges below your threshold. Every PR from every agent. Every time.
Here is how to set it up in 2 minutes.
## Step 1: Scan your repo
```
npx aislop scan
```
This runs all 40+ deterministic rules against every file in your project. The output is a score from 0-100 with a breakdown by category — formatting, linting, code quality, AI slop, security. No installation required. No config file needed. No API calls.
## Step 2: Set your threshold
Create an `.aislop/config.yml` file in your project root:
```
{`ci:
failBelow: 70`}
```
That is it. Any PR that scores below 70 will now fail in CI. Set it to 85 for stricter enforcement, 50 for a gentler start. The threshold applies to every PR from every agent — Claude Code, Cursor, Codex, GitHub Copilot, Windsurf, Gemini CLI, all of them.
## Step 3: Add it to CI
If you use GitHub Actions, add this job to your workflow:
```
{`jobs:
quality-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: scanaislop/aislop@v0.10.1
with:
version: latest`}
```
For GitLab CI, Jenkins, CircleCI, or any other platform, the pattern is the same: run `npx --yes aislop@latest ci`, check the exit code, block if it is non-zero. aislop exits with code 1 when the score is below threshold.
## Step 4: Wire it to your agent
Install aislop as a hook in your agent's runtime. The hook scans every edit as your agent writes — before the code hits your repo:
```
npx aislop hook install --claude
```
Replace `--claude` with your agent: `--cursor`, `--codex`, `--gemini`, `--windsurf`. The hook catches issues on the turn the agent wrote them — instant feedback, instant fix, no PR required.
## Step 5 (optional): Set team standards
Use `.aislop/config.yml` to define thresholds and quality rules that apply across your team:
```
ci:
failBelow: 70
quality:
maxFunctionLoc: 60
maxParams: 4
engines:
ai-slop: true
security: true
```
## What changes
Before the gate: Agent writes code → PR is created → Reviewer skims → Merge → Incident → Debug → Repeat.
After the gate: Agent writes code → aislop scans → Score below threshold → PR blocked → Agent fixes → Merge.
The gate moves the quality check from after review to before review. That is the difference between catching issues and cleaning up incidents.
## The bottom line
Two minutes. One local scan. One CI gate. Your entire team gets a quality gate that works across every language, every agent, every PR. No config file required to start. The gate enforces from day one. You can tune the threshold later.
Run `npx aislop scan` right now. Your score takes 10 seconds. Your gate takes two more minutes. Start before the next PR merges.
---
# The Swallowed Exception That Broke Production: AI Slop Pattern Deep Dive
**URL:** https://scanaislop.com/blog/the-swallowed-exception-that-broke-production/
**Date:** 2026-05-16
**Tag:** Pattern Guide
An empty catch block. A 401 error. Three weeks of blank dashboards. This is the most common bug in AI-generated code — and it has a documented fix.
This is the first in a 14-part series breaking down every AI slop pattern we have documented in production code. Each post covers what the pattern looks like, why AI generates it, what breaks in production, and how aislop catches it deterministically.
## The incident
A production dashboard went blank. Not crashed — blank. Data tables rendered as empty states. Charts showed no series. For three weeks, the team assumed the upstream data pipeline had an issue. They traced through the ETL, the database, the API layer — all clean.
The root cause was a single try-catch block an AI agent had written three months earlier:
```
{`try {
const user = await api.getUser(id);
displayDashboard(user.data);
} catch (e) {
// Empty — error silently swallowed
}`}
```
The API call returned a 401. The catch block did nothing. The function returned `undefined`. Three components downstream crashed silently. The dashboard rendered blank. Nobody noticed because no error was thrown — the catch consumed everything.
This is Pattern 1 in our taxonomy: **The Optimistic Catch Block**. It appears in approximately 80% of AI-generated PRs touching an API or database — found in 41 of 50 PRs in one audit.
## What it looks like in code
```
{`// What AI writes:
try {
await saveToDatabase(data);
} catch (e) {
console.error("Something went wrong");
// No rethrow. No retry. No meaningful recovery.
}
// Or worse:
try {
await saveToDatabase(data);
} catch (e) {
// Empty — nothing at all
}`}
```
## Why AI generates this
AI models are trained on code that compiles — not code that survives. The model sees try-catch as a syntactic pattern to complete, not a recovery mechanism to reason about. It has no concept of which errors are recoverable, which should propagate, or what context a future debugger needs.
The training distribution heavily weights "catch → log → continue" patterns from tutorial code. Stack Overflow snippets. Blog examples. Production-hardened error handling — the kind that comes from debugging 2 AM incidents — is dramatically underrepresented in training data.
## What breaks in production
Silent failures cascade. A 401 is swallowed. A timeout is swallowed. A database connection failure is swallowed. Each one produces undefined behavior in the caller, which produces undefined behavior in the caller's caller, and so on up the stack. The developer spends hours tracing the root cause because no error was ever logged.
The documented production incidents linked to this pattern include connection pool exhaustion (litellm PR #21213), Redis pool failures (#21717), and an event loop leak (langchain PR #35352) — all caused by swallowed exceptions in error handling code AI agents generated.
## The fix
```
{`// GOOD — log with context AND rethrow
catch (error) {
logger.error("Failed to save data", {
error: error.message,
requestId: req.headers['x-request-id'],
orderId: data?.orderId,
});
throw error;
}
// BETTER — recover meaningfully
catch (error) {
if (error.code === 'CARD_DECLINED') {
return ;
}
throw error; // Everything else propagates
}`}
```
## How aislop catches this
aislop has a deterministic rule for swallowed exceptions. It scans every try-catch block in every file and checks three things:
1. Is the catch block empty? → Fires `pro/error-handling/empty-catch`
2. Does it only contain a comment or console.log? → Fires `pro/error-handling/log-only-catch`
3. Is there a missing finally for resource cleanup? → Fires `pro/connection-leaks/database-pool`
Deterministic means deterministic. A try-catch with no rethrow always produces a diagnostic. Every time. No LLM guesswork. No "the model was having a bad day." Same code, same result, same block.
## The key rule
The community has converged on one principle: **log or throw, not both**. The layer that handles the exception logs it. The layer that cannot handle it throws it. Never do both in the same catch block. Every catch block should either recover meaningfully or let the error propagate. Silence is not a strategy.
Check your repos. Search for empty catch blocks. Count them. If you find more than zero, you have AI slop in production — and it is only a matter of time before one of them swallows the error you needed to see.
---
# Where SonarQube Stops, and AI-Slop Rules Start
**URL:** https://scanaislop.com/blog/why-sonarqube-misses-ai-slop/
**Date:** 2026-05-16
**Tag:** Essay
SonarQube is a mature static analysis suite. AI-written code adds a different set of repeatable patterns. Here is where a focused AI-code hygiene layer can help.
SonarQube is not wrong. It is solving a broader problem.
The platform is designed for broad static analysis. Its rule set catches security vulnerabilities, code smells, bugs, and style violations that matter regardless of author. It is widely used for a reason: deep language coverage, OWASP Top 10, CWE, SAST, all in one platform. If you run SonarQube, you should keep running it.
But AI-assisted code adds a different kind of review problem. Some failures are still normal bugs. Others are plausible-looking shortcuts repeated across many files: shallow comments, generic naming, swallowed errors, and type escapes that make code pass without making it clearer.
## The patterns a focused AI-code layer can add
In AI-generated PRs, these are the kinds of patterns we look for alongside traditional static analysis:
**Trivial comments.** `// Set the user name to value` above `user.name = value`. It is not a code smell in the traditional sense: it is not wrong, it is just useless. But when repeated across generated files, trivial comments become maintainability noise.
**Swallowed exceptions.** Empty catch blocks or catch blocks with only `console.log(err)`. Some static-analysis setups catch part of this. The AI-code pattern is broader: catches that log and continue without rethrowing, or catches that silence error types that should propagate.
**Generic naming.** `data`, `data2`, `result`, `temp`, `helper_func`. Generic naming is not a bug, but it is maintainability debt when it compounds across generated changes.
**Unsafe type assertions.** `as any`, `as unknown as T`, unchecked casts. SonarQube catches some type-related issues in statically typed languages but does not have rules targeting the specific AI pattern of using escape hatches to make code compile rather than fixing the type.
**Console leftovers.** `console.log` statements in production code. Some linters catch this. In AI-generated PRs, debug leftovers are a useful signal that the change needs another pass.
**Hallucinated imports.** Imports that reference packages that do not exist. AI models can invent package names, and import validation is usually handled by the build, package manager, or a dedicated dependency check rather than a general code-quality dashboard.
## Why this gap exists
SonarQube's rule set was built incrementally around known security, maintainability, and bug patterns. AI-assisted code can trigger those same rules, but it also creates softer signals: code that looks plausible, passes baseline checks, and still adds review drag.
This is less about rule count and more about focus. General static analysis catches broad defects. AI-code hygiene catches repeatable shallow patterns that are worth naming and enforcing separately.
## What the stack should look like
A practical stack can run two layers:
**Layer 1: SonarQube (or equivalent)** for security vulnerabilities, style violations, code smells, and architectural issues. This catches what it has always caught. Keep it.
**Layer 2: aislop** for AI-specific patterns — trivial comments, swallowed exceptions, generic naming, unsafe assertions, console leftovers, hallucinated imports, dead code, missing timeouts. This focuses on repeatable generated-code hygiene.
The two layers are complementary. SonarQube catches broad static-analysis concerns. aislop catches defined AI-code hygiene patterns. Some teams will want both because agent-written code can produce both failure modes in the same function.
## The bottom line
SonarQube is not obsolete. It remains strong at the problems it was designed to catch. The question is whether your team also wants a focused layer for AI-code hygiene.
Keep SonarQube where it works. Add `npx aislop scan` when you want named, deterministic checks for generated-code patterns alongside your existing quality gate.
---
# High-quality AI coding standards
**URL:** https://scanaislop.com/blog/high-quality-ai-coding-standards/
**Date:** 2026-05-15
**Tag:** Reference
Does your coding agent have a coding standard? AGENTS.md is not enough. Here are ten rules we enforce mechanically, on every commit, on every project.
Ask any team shipping with Claude Code, Cursor, Opencode, or Codex what their coding standard for the agent is. You will get a shrug. Maybe an `AGENTS.md`. Maybe a style guide from 2019. Nothing mechanical. Nothing the agent is actually held to.
These ten rules came out of watching AI-generated code fail in production. Our code. Customer code. The backlog. We tried treating agent output like a junior PR. It does not work. Juniors learn. Models do not. The standard has to be mechanical or it is not a standard.
Each rule below maps to an **aislop** rule. Your agent hits it on every commit. No review labor. No vibes. The rule is not just declared, it is enforced.
## The ten standards
01
### No trivial comments
The name carries the information. If the line reads `initDB()`, a comment saying "initialize the database" is noise. Worse, it rots the first time someone renames the function. Now the comment is a lie the next agent will read as truth.
**Enforce.** `ai-slop/trivial-comment`. Autofix on.
02
### No narrative preambles
Multi-paragraph JSDoc and section banners belong in a PR description, not in source. Agents write them because the training data rewards visible effort. Readers do not read them. Your agent does not need them.
**Enforce.** `ai-slop/narrative-comment`. Autofix on.
03
### Explicit error handling
No silent catches. No `return []` on failure. The error is handled with a named strategy (retry, fallback, user message), or it propagates. Pick one. Agents love the silent catch because it makes the happy path pass. It also makes the production incident a mystery.
**Enforce.** `ai-slop/swallowed-exception`. Severity: error.
04
### No `console.log` in source
Debug output belongs in a logger. Every `console.log` in production is either forgotten debug or a quiet admission that no real logger exists. Both are a problem. Your agent is leaving them behind. Your pipeline should catch them.
**Enforce.** `ai-slop/console-leftover`.
05
### No `as any`
Types are the contract. `as any` and `as unknown as X` delete the contract right at the point you needed it. Every escape hatch is a production surprise waiting. Agents reach for them when they do not want to do the actual typing work. Do not let them.
**Enforce.** `ai-slop/unsafe-type-assertion`.
06
### Size limits
Files under 400 lines. Functions under 80 lines. Ten percent tolerance. If your function is more than 80 lines, your agent is doing something wrong. If your file is more than 400 lines, same story. Agents have no internal pressure to keep anything small. They will append until something warns them. That something is you.
**Enforce.** `file-too-large`, `function-too-long`. Configurable in `.aislop/config.yml`.
07
### Dependency hygiene
Dead code, unused dependencies, unused exports. Your agents are leaving them behind. Every symbol in `package.json` is a supply chain entry. Every unused export is future dead code nobody wants to touch. Cut them.
**Enforce.** knip via **aislop**. Aggressive fix removes unused exports and unimported files.
08
### AST-aware fixes only
Never regex-mutate source code. Destructure patterns, generics, JSX, none of them are regular. The three-line regex you wrote this morning will corrupt a file next month. We learned this in 0.5. We have scars.
**Enforce.** Code review convention. **aislop** itself uses the TypeScript compiler API for every transformation.
09
### Parse-check before write
Every autofix has to prove its output still parses. A mutation that produces a syntax error is silently discarded. Revert on failure. Always. This single rule would have caught three of the four GAP bugs external fixers shipped in 0.5.
**Enforce.** Engineering discipline. **aislop** wraps all mutations in a parse gate.
10
### Invocation-aware copy
Help text has to work no matter how the user installed the tool. If your CLI says `aislop fix --claude` but the user ran `npx`, the copy-paste fails. Use `npx ` everywhere, or detect the invocation at runtime.
**Enforce.** **aislop** 0.5 detects `npx` vs global vs local install and adapts every hint in its own output.
## The scoring model
**aislop** emits a single score from 0 to 100 per scan. Teams need a quality gate, not just a linter. A linter tells you what is wrong. A gate tells you whether to merge. Warnings contribute proportionally to the deduction. Errors contribute more. The engine count is factored in so small projects are not punished for having fewer surfaces.
**Rough guide:**
- **90 and up** — clean.
- **80 and up** — a well-maintained codebase.
- **70 and up** — no critical issues.
Start your threshold low. Ratchet up as the slop comes out.
## How to enforce
GitHub Actions, one Action step:
{`- uses: scanaislop/aislop@v0.10.1
with:
version: latest`}
Config lives in `.aislop/config.yml`:
ci:
failBelow: 70
quality:
maxFileLoc: 400
maxFunctionLoc: 80
Commit this file. It becomes your team's engineering standard. Every human, every agent, every PR is held to the same bar. That is what a coding standard for your agent actually looks like.
## Closing
These are not opinions for the sake of opinions. Every rule here came out of watching AI-generated code fail in production. If you want the receipts for any one of them, the [25-project validation post](/blog/25-projects-aislop-findings) has them.
## See your score
$ npx --yes aislop@latest ci
Run it in CI. Hold your agent to the same ten rules every PR. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# Vibe coding, done right
**URL:** https://scanaislop.com/blog/vibe-coding-done-right/
**Date:** 2026-05-13
**Tag:** Opinion
Letting the agent drive is a cheat code. It is also how you ship five unrelated files full of slop at 2am. Here is how we vibe-code without shipping the slop.
Vibe coding is letting the agent drive, reviewing changes as they stream in, going with the flow. Anyone shipping with Claude Code, Cursor, Opencode, or Codex is doing it. It is genuinely faster. It is also how you end up with a dozen files of useless JSDoc, three different wrappers around the same fetch, and a `catch {' return [] '}` on something that should have thrown.
The question is not whether to vibe-code. The question is how to do it without shipping slop. Here is the loop we landed on while building **aislop**.
## The false dichotomy
Engineering has a reflex that treats "careful" and "fast" as opposites. Slow down to ship quality. Speed up to ship fast. The people who say this are usually doing neither. Careful because they are scared. Fast because they stopped reading.
With the right tooling these are not opposites. You can vibe at full speed as long as the guardrails are automatic. If a rule exists, a human should not be enforcing it. The tool should. Your coding agent should have guardrails.
## The rules we use
### 1. Scan before, scan after
Run `aislop scan` before you start a session and before every commit. The score is a running number. If it goes up, the work is net positive. If it goes down, the agent added more slop than it cleaned up. That is the whole signal you need.
During 0.5 development, one commit took **aislop** itself from **78 to 71**. We looked at the diff. Thirty lines of narrative JSDoc the agent had left behind. Ran `aislop fix`. Ended at **83**. Three commands. No manual comment-hunting.
### 2. Let the agent write. Let the tool remove.
Do not hand-edit the agent's output. If there is slop, the rule should catch it. If the rule does not catch it, the rule is the bug. Add one. Do not argue with the agent about comment style. You will lose, and you will waste an hour.
This is the feedback loop that makes vibe-coding sustainable. You upgrade the linter, not the prompt.
### 3. Own your destructive operations
External tools have opinions that do not match yours. `oxlint --fix` will happily turn `const {' foo: bar '} = x` into a syntax error. `knip --fix` will delete exports that a framework picks up through conventions it does not know about.
In **aislop** 0.5 we moved every destructive operation in-house. We use oxlint and knip for detection. The actual mutation runs through our own AST engine because we know our edge cases. Yours will be different. The principle is the same. Do not delegate code mutation to a tool you cannot read the source of.
### 4. Validate against real code, not fixtures
Fixture tests prove the tool passes. Real projects prove the tool does not corrupt. We ran **aislop** against 25 projects from the backlog before tagging 0.5. Found four bugs. Every one was a destructure pattern no fixture had covered. The fixtures were written by the same mind that wrote the code. Of course they missed the weird cases.
If you are building anything that touches user source code, keep a directory of weird real projects. Run against all of them on every release. It is the only thing that catches the edge cases that matter.
### 5. Parse-check before write
Never write a file until you have confirmed it still parses. Revert on failure. Always. **aislop**'s fix pipeline runs the TypeScript parser on every output before committing to disk. A broken mutation is silently discarded.
This single rule would have prevented every one of the four GAP bugs external fixers caused in 0.5. They do not do it. So we do it around them.
### 6. Short commits, big stories
Small atomic commits. The story of what you built lives in the PR description, not in block comments above functions. The PR description gets read once. The block comment gets scrolled past a thousand times.
If you find yourself writing a paragraph comment to explain why code exists, ask whether it belongs in the git history instead. `git blame` will find it. Nobody reads stale comments.
## What vibe coding is NOT
- − Not skipping tests. Tests are the contract. No vibe gets you out of that.
- − Not ignoring warnings. If the tool says "this looks like slop," it is slop. Fix it now, not later.
- − Not "I'll clean it up later." You will not. You will open another branch, the agent will write more slop, and in three weeks the cleanup commit will be bigger than the feature.
## The closing
Build the tool that catches your own slop. Then let the agent rip.
That is the whole loop. The agent produces code ten times faster than you. The tool catches the slop ten times faster than you. The throughput multiplier is the composition. Neither side alone is the story.
## Try it on your repo
$ npx aislop scan
$ npx aislop fix
Run the scan before your next session. Run the fix after. Watch the score move. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# AGENTS.md is a sign on the wall. Agents don't read signs.
**URL:** https://scanaislop.com/blog/agents-md-is-not-enough/
**Date:** 2026-05-11
**Tag:** Opinion
AGENTS.md is not enough. You have to hold your agent accountable. AGENTS.md is the WHAT. aislop rules are the HOW. aislop ci is the GATE. aislop fix --agent is the LOOP. Four layers, none optional.
Is `AGENTS.md` enough to keep your coding agent honest? Some teams swear by it. Some stack a `CLAUDE.md` and a `.cursorrules` on top. Some write nothing at all and hope the model remembers house style from the last session. After running **aislop** against 25 real projects, here is what we landed on. The markdown file is a nice idea. It is not a guardrail. It is a sign on the wall, and agents do not read signs.
`AGENTS.md`, `CLAUDE.md`, `.cursorrules`, the `system` field of whatever model you are calling. A team writes down its conventions, the agents that pass through the repo read the file, everyone agrees on house style, the PRs get cleaner. That is the dream. Then you look at last week's diff and the agent ignored three of the four rules. Not because the file is broken. Because `AGENTS.md` is a norm, not a guardrail. The agent is told not to. It still does. There is no consequence.
## The failure mode
Almost every AI-assisted codebase we scanned in the 25-project run had something that looked like `AGENTS.md`. Markdown files at the root. Pinned style notes in the prompt. `.cursorrules` with explicit "do not" lists. None of those repos were free of the things their own files told the agent not to do. `as any` casts. 200-line functions. Swallowed exceptions. Console leftovers. Narrative JSDoc above one-line functions.
The pattern was the same every time. The agent had read the file at some point, complied for a few turns, drifted, and the team had no mechanism to catch the drift. `aislop scan` caught it on first run. The rules in the markdown file matched the rules the linter ran. The difference was that one ran every PR and the other ran when somebody remembered.
## The four-layer model
A standard sticks when four layers are present. None of them is optional. None of them is sufficient alone.
### 1. WHAT. The written standard.
`AGENTS.md`. Describes the *what* and the *why*. Humans read it during onboarding. Agents read it when a tool puts it in their context. It is documentation. It does not enforce.
### 2. HOW. The machine-enforceable rules.
The *what*, automated. In **aislop**'s case that is 40+ rules across six engines. Format, lint, code-quality, ai-slop, security, architecture. Rule IDs like `ai-slop/narrative-comment`, `ai-slop/swallowed-exception`, `ai-slop/unsafe-type-assertion`, `complexity/function-too-long`, `security/vulnerable-dependency`. If a rule can be automated, it must be. The rest stays in the markdown file.
### 3. GATE. The quality threshold.
`aislop ci`. The enforcement layer. A PR that drops the score below threshold cannot merge. No human has to remember to look. The gate looks. One small workflow.
### 4. LOOP. The agent handoff.
`aislop fix --claude`, `--codex`, `--cursor`, and 11 more. When the gate flags issues the deterministic fix cannot safely resolve, the agent gets them. File paths, rule IDs, the violating code, the architectural reason behind the rule. The same agents that produced the slop, with structured input, cleaning it up.
All four together. `AGENTS.md` without rules is documentation nobody enforces. Rules without a gate is a linter nobody runs. A gate without a loop is a wall with no door, and the door is how you fix what the wall blocked.
## Told not to vs. cannot
The four-layer model is doing one thing. Closing the gap between "the agent is told not to do X" and "the agent cannot do X without it being caught and reverted."
**Told not to** lives in `AGENTS.md`. The agent reads the rule, internalizes it for a few turns, drifts. There is no consequence to drift because nothing checks. PR ships with the rule violated. Team notices weeks later in a retro. Adds the rule back to `AGENTS.md`. Cycle continues.
**Cannot** lives in CI. The agent writes the violation. The pipeline runs `aislop ci`. The score drops. The PR is red. The merge button is disabled. The agent, or the human, has to fix it before anything ships. The rule is not just told. It is enforced.
## Case study: this site
This marketing site uses all four layers. Worth walking through.
Layer
Tool
What it does
WHAT
`AGENTS.md`
At the repo root. Describes the voice, the routing convention (drafts get an underscore prefix so Astro does not route them), the size targets for components, the don'ts.
HOW
`aislop`
Runs against the source. The size rules catch oversized page files. The narrative-comment rule catches JSDoc the agent felt obliged to write above page components. The unused-import and dead-pattern rules catch the leftovers from agent-driven refactors.
GATE
`aislop ci`
CI runs on every PR. Score has to hold or move forward. The threshold is set just below the current score so any regression fails the build.
LOOP
`aislop fix --claude`
Handles the residuals. The size violations the deterministic fix will not auto-split, the typed-shorthand cases that need a human-shaped refactor, the orphan files knip can find but will not safely delete.
Every layer earns its place. The markdown file alone would not have stopped the agent shipping a 600-line page. The rules alone would not fail the PR. The gate alone would not fix what it blocked. The loop alone has nothing to fix because nothing flagged. All four, in sequence, is the only configuration that ships clean.
## What 0.5 proved
During the 0.5 rehaul itself, every layer caught something. `AGENTS.md` guided the agent on commit-message style and the "own the destructive fix" principle. The 40+ rules ran self-scan against the engine source on every commit. The CI gate held the line at 100/100. Any regression would have failed the release. The handoff loop fed Claude the residuals from the narrative-comment sweep. The 70 comments mentioned in the other essays.
Pull any layer and the release would have shipped messier. The markdown file alone could not keep the agent from writing JSDoc paragraphs. The rules alone could not have made the rehaul fail. We would have shipped 70 comments and noticed two weeks later. The gate alone would not have given the agent any way to repair what it blocked. The loop alone had no input until the gate produced the failure list.
## The four-layer rule
If your team's standards live only in `AGENTS.md`, you have a wish list. If they live in rules without a gate, you have a linter no one runs. If they live in a gate without a loop, you have a wall with no door. Write the file. Encode the rules. Commit the gate. Wire the handoff. `AGENTS.md` is not enough. You have to hold your agent accountable. The agent now operates inside something that will catch what it gets wrong, and the human at the end of the loop reviews what passed, not what slipped.
## Wire the gate
{`- uses: scanaislop/aislop@v0.10.1
with:
version: latest`}
One workflow and the agent stops being told not to. It cannot. [Star the repo](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# Stop cleaning up after your coding agent
**URL:** https://scanaislop.com/blog/we-shouldnt-be-cleaning-up-after-our-agents/
**Date:** 2026-05-08
**Tag:** Opinion
Your agent writes code. You clean up. That is backwards.
How much of your day goes to cleaning up after your agent? Ask anyone, or any team, shipping
(or vibecoding, like we say now) with Claude Code, Cursor, Opencode, Codex, or whatever agent
they're on. Some say the cleanup is the job now. Some say they stopped reviewing entirely and
just deploy. Some honestly do not know the difference between good code and shitty AI slop, so
they ship whatever runs. You'll hear things like, "I shipped this feature in ten minutes. Then
I spent the rest of the day cleaning up the mess the agent left behind." Or, "I don't even
review anymore, I just deploy. I pushed 10K lines yesterday."
Here is what we landed on after 25 real projects. In a good engineering team, this stuff
matters. Dead code. Good naming. Function size. File size. Duplicate helpers that should have
been one reusable function. `console.log` statements
sitting in production (for real). `as any` casts
everywhere. Missing error handling. Half renamed variables. JSDoc paragraphs nobody asked for.
The list goes on, and your agents are leaving all of it behind. You'll also hear the other
version, "Oh sh**, it works now, but I can tell it's not scalable." And they're right. The
thing runs, the code underneath is a mess. We have normalized this. We call it the cost of
working with AI. It is not. It is a tooling failure.
## The agent is doing exactly what you asked
You said "add this feature." The agent added the feature. It imported what it needed. It did
not remove the old import because you never told it to. The agent is a function. Prompt in,
code out. It does what you ask.
The mistake is expecting that same function to be a reviewer, a janitor, a linter, and a type
checker. All at once. In natural language. With no feedback loop. You'll hear engineers say,
"My agent produced garbage again." It didn't. It did exactly what you asked. You just didn't
ask for clean code. You asked for the feature.
Your coding agent should have guardrails. Does your coding agent have a coding standard?
`AGENTS.md` is not enough. You have to hold your
agent accountable.
## The loop
Three commands. Same on every project.
$ npx aislop scan
score: 15/100 - 287 issues across 41 files
$ npx aislop fix
removed 96 issues - score: 32
$ npx aislop fix --claude
handing 191 issues to claude...
resolved 142 - score: 47
`aislop scan` scores the codebase.
`aislop fix` cleans up what the AST engine can touch
safely. Unused imports, dead branches, narrative comments, format.
`aislop fix --claude` hands the rest back to your
coding agent with the issue list attached. You come back, review the diff, ship or don't.
## What 25 projects looked like
Median across those projects:
before any cleanup: 15/100
after aislop fix: 32/100
after aislop fix --claude: 47/100
Fifteen. That is what a codebase looks like when nobody is reading it. When the agent writes,
the human skims, and nobody runs the linter. We showed one team their score. First reaction:
"No way, our tests pass." Tests passed. The code was still a mess. Tests don't measure slop.
The biggest jumps came from the projects that had been on agent autopilot the longest.
`chrome-extension` went from **9 to 88**. Go look at the [AI Slop CLI on GitHub](https://github.com/scanaislop/aislop). It does exactly what you think it does. Star it, try it on your own repo. Takes three minutes.
## The cost is being the cleanup
The cost isn't the dirty code. The cost is you, playing janitor for a generative model, every
single day. The pipeline should catch what the agent misses. You shouldn't be a sanitation
worker for a tool. That's the tooling's job.
"But I don't have time to set up another tool." You already lost the time. You lost it the
moment you started cleaning up after the agent instead of letting something else do it. We
shouldn't be cleaning up after our coding agents. They should do it right the first time. And
when they don't, the pipeline should catch it before you ever see the diff. Build the loop.
Ship the diff.
## Run it on your repo
$ npx aislop scan
$ npx aislop fix
$ npx aislop fix --claude
Three commands. Stop being the janitor.
[Star the repo](https://github.com/scanaislop/aislop)
if you want the next release in your feed.
---
# Nobody reads code anymore. Write for the linter.
**URL:** https://scanaislop.com/blog/nobody-reads-code-anymore/
**Date:** 2026-05-05
**Tag:** Opinion
Median score across 25 real projects was 15 out of 100. That is what code looks like when nobody is reading it. The linter is the reader now.
When was the last time anyone on your team actually read the codebase? Not skimmed the diff, not
glanced at the PR title. Sat with the file. Some teams still do it. Some say they don't have
time. Some never really did. Here is what we landed on after running
`aislop scan` on 25 real projects. Internal tools.
Client repos. Open source apps. Median score before any cleanup, 15 out of 100. After one
`aislop fix` pass, 32. After
`aislop fix --claude`, 47.
The headline is 15. That's what every one of these codebases looked like when we showed up. All
of them had passing tests. All of them were shipping. None of them had been read in any real
way.
## How review used to work
Five years ago, reading the codebase was a weekly thing. A senior handed a junior a subsystem
and said "go read this." A bug report meant cloning the repo and following the call graph for
an hour. Code review meant opening the file, jumping to definitions, scrolling through what
lived next door.
That's gone. The agent spits out a thousand lines in a minute. The human skims two hundred
before the next standup. The PR description is the artifact most reviewers actually read. The
diff gets a glance.
That 981 line component buried in folder seven of one project? It got reviewed and approved.
Somebody clicked merge. Nobody sat with it.
## The damage nobody noticed
Four of the 25 projects had real TypeScript damage when we arrived. Compile errors sitting in
committed code for weeks.
Nobody noticed because nobody ran `tsc` locally. CI
either didn't have a type check, or had it muted. The agent wrote. The agent committed. CI ran
whatever still passed. Ship.
If a human were reading the code, those errors would have been caught. The IDE underlines them
in red. The deployment would have screamed. They sat there because the loop had degraded. The
TypeScript compiler is the first reader. Nobody was listening to it.
## The new reader
The new reader is deterministic. It does not get tired. It does not skim. Every line, every
time.
In our stack the reader is `aislop scan` on top of
TypeScript, biome, oxlint, and knip. Forty plus rules across six engines. None of them have an
off day.
The reader produces a number. Zero to a hundred. That number is the new pull request artifact.
The PR description says what changed. The score says whether the code itself got better, worse,
or stayed flat.
Comments tell the same story. A comment above
`const MAGIC = 0.87` explaining the value used to be
written for the next human in the file. There is no next human in the file. The comment is now
aimed at the toolchain, which only reads directives.
`@deprecated`.
`biome-ignore`.
`ts-expect-error`. Everything else gets removed by
`ai-slop/narrative-comment` because nothing was
reading it anyway.
## What changes for code itself
Code has to become self describing. The linter cannot read intent. The human will not. Names
carry the weight comments used to.
If your function is more than 80 lines, your agent is doing something wrong. That's what
`complexity/function-too-long` catches. If your file
is more than 400 lines, same story.
`complexity/file-too-large` is the linter saying
"this would have been split if anyone were reading it."
Type narrowing replaces narrative. A
`type Status = 'pending' | 'success' | 'error'`
tells the next reader more than a JSDoc paragraph, and it tells them in a language the reader
actually parses.
## Optimize for the reader you actually have
The reader is a linter. The linter has rules. Make the code pass them.
Name things so the agent summarizing the file gets it right. Keep files small enough that the
next agent reasoning about them has a chance. Skip the narrative comments. Nothing reads them.
Lean into the score. It is the artifact of a working pipeline. One number that says whether the
next person in the file, or the next agent, is starting from clean ground or from rubble.
## See your score
$ npx aislop scan
Run it. You will see the number the linter has been trying to tell you about.
[Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop)
if you want the next release in your feed.
---
# aislop v0.7.0. Config inheritance, public score badge, security floor
**URL:** https://scanaislop.com/blog/aislop-v0-7-0-config-inheritance/
**Date:** 2026-05-02
**Tag:** Release
extends: lets project configs inherit a parent and override only what they need. The public score badge puts your live aislop score in any README. A postcss security floor closes the only vulnerability aislop's own scan flagged on itself.
v0.7.0 is a focused minor release. No new scan engines, no new CLI commands. Three additions that remove friction for teams running aislop across more than one repo.
## 1. Config inheritance with `extends:`
Before v0.7.0, every repo needed its own complete `.aislop/config.yml`. For a team running aislop across ten repos, that meant ten copies of the same thresholds and weights — and ten places to update when the standard changed.
`extends:` fixes this. A project config can point to a parent and declare only the keys that differ:
# packages/payments/.aislop/config.yml
extends: ../../.aislop/base.yml
ci:
failBelow: 80 # override one key, inherit the rest
The merge rules are deliberate: nested objects deep-merge key-by-key so you only need to state what changes, and arrays replace wholesale so an explicit rule list in a child isn't accidentally extended by the parent. Multiple parents are supported via an array — later entries win. Cycles and inheritance chains longer than five levels are rejected at load time with a clear error.
The practical shape for most teams is one `base.yml` in the repo root or a shared config package, with per-project files that override `ci.failBelow` or tweak weights for specific contexts. The full spec is in [docs/configuration.md](https://github.com/scanaislop/aislop/blob/v0.7.0/docs/configuration.md#extending-a-shared-config).
## 2. Public score badge
Any project can put a live aislop score in its README. Here's ours:
The SVG is shields-compatible and edge-cached on Cloudflare. The colour bands match the standard thresholds: green at 85 and above, amber between 70 and 84, red below 70, grey if the repo hasn't been scanned publicly yet.
It's free for any project. The badge reflects the last scan aislop recorded for that owner/repo combination — no separate setup step beyond running a scan. The full URL spec and edge-cache details are at [badges.scanaislop.com](https://badges.scanaislop.com).
## 3. Security: postcss floor
aislop's own scan flagged a `security/vulnerable-dependency` finding on this repo — a transitive `postcss` below 8.5.10. No direct dependency used it, so a `pnpm.overrides` entry is the right fix over pulling in a runtime dep we don't need. The lock now resolves to 8.5.13.
This is also a demonstration of the tool catching its own issues. aislop scans itself on every release. When it finds something, we fix it before shipping.
## Install
$ npx aislop@latest scan
Full changelog on [GitHub](https://github.com/scanaislop/aislop/releases/tag/v0.7.0). [Star the repo](https://github.com/scanaislop/aislop) to get the next release in your feed.
---
# If your function needs 80 lines, your agent gave up. If your file needs 400, it never started.
**URL:** https://scanaislop.com/blog/function-size-limits-for-ai-code/
**Date:** 2026-04-30
**Tag:** Engineering
If your function is more than 80 lines, your agent is doing something wrong. If your file is more than 400, same story. We found a 981-line component in the wild.
The largest single file we flagged across 25 real projects was `OnboardingSvgIllustration.tsx`. 981 lines. One React component. All in the default export. An agent had been asked to inline an SVG illustration. It chose to render it as JSX, expanded every path element, named nothing, and stopped. Nobody had reviewed it because nobody was going to scroll a 981-line file at PR time.
**aislop** size limits, after 25 projects:
- 80 lines per function
- 400 lines per file
- 800 lines for JSX and TSX
- 10% soft tolerance
Is there a golden rule for function size or file size? Not really. Ask ten engineers and you will get ten answers. Some teams say 50 lines. Some say 100. Some do not check at all and trust the agent. That used to be fine because the human writing the code felt the pain of a file getting too big. The agent does not feel that pain. The numbers above are not sacred. They exist because somewhere in your repo there is a file nobody admits exists, and the numbers are what catch it.
The 981-line component is the extreme. The class of problem is everywhere. `MediumWidget` in `SYMSavingsWidget.tsx`. 171 lines for a single React component rendering three unrelated sub-views and doing its own data fetching. `generate_seed_data`. 176 lines in one Python function producing fixtures for six different models. `useSessionMigration` at 94 lines. `useStoreReview` at 99 lines. Both React hooks that should have been three hooks each. `services/auth/authService.ts` at 552 lines. `utils/sharedStyles.ts` at 492 lines. Every one of them written by an agent. Every one of them shipped.
## The two limits, the two rules
**aislop** ships two complexity rules for size, both with a 10% soft tolerance so a single line over does not tank the score.
Rule
Applies to
Hard limit
Warn at
`complexity/function-too-long`
functions
80 lines
88
`complexity/file-too-large`
files
400 lines
440
`complexity/file-too-large`
.jsx / .tsx
800 lines
880
Function and file are different limits because they fail in different ways. A long function fails on logic. You cannot hold the local state in your head, the branches multiply, the bug rate climbs. A long file fails on orientation. You cannot find anything, the imports lie about the file's responsibilities, and the next change goes in the wrong place because the right place was buried at line 820. The 10% tolerance is deliberate. 81 lines over an 80-line cap should not fail the score. An agent split that hits 88 and one that hits 91 are functionally the same outcome. 89 over 80 is where it stops being a rounding case and starts being a refusal to split.
## Why 80 and 400
Both numbers are working-memory arguments dressed as limits. 80 lines is roughly what fits on a 16-inch laptop screen at a comfortable font size. A function you can hold on one screen is a function you can reason about without scrolling, without losing the local variable in line 12 by the time you are reading line 55. Above 80 the scrolling starts. Above 120 the scrolling becomes the work.
400 lines is roughly what a human can orient inside without a table of contents. You open the file, skim the imports, skim the exports, skim the top-level declarations, and you have a mental map. Above 400 you start needing search. Above 800 you start needing search to find what you were searching for.
JSX and TSX get double. 800 lines, soft cap 880. Component files carry a lot of declarative markup that does not load the same cognitive register as logic. A 600-line component that is 400 lines of tags and 200 lines of handlers reads more like a 200-line file. We let the limit reflect that and trust the function rule to catch the actual logic blowups.
## Why agents don't feel the pain
A human writing a function starts to suffer around 80 lines. Not because anyone told them to suffer. Working memory is finite and exceeding it hurts in real time. You scroll up to remember what `parsed` meant. Scroll back. Lose your place. Try to hold the shape of three nested branches at once. Fail. The pain is the prompt to extract.
An agent does not have that pressure. The whole function fits in the context window. Line 10 and line 400 are equally accessible at generation time. The agent reasons about both at once, with no scrolling cost, no working-memory burden, no felt urgency to split. The motivation to extract is gone because the pain that motivates extraction is gone.
This would be fine if the next reader were also an agent with infinite context. But one of three things is always true. A human will eventually modify the function. A human will eventually debug the function. A human will eventually review the diff. All three still have working memory. The function is still 981 lines.
## What **aislop** does about it
Detection is straightforward. The size engine counts AST nodes per function and lines per file, applies the threshold, applies the 10% tolerance, and emits two rule IDs. `complexity/function-too-long` and `complexity/file-too-large`. They show up in the scan with a file path, a line range, and a delta over the cap.
Auto-fix does not touch them. A 200-line function can be split four different ways and three of them change the semantics. Same with files. Splitting a 600-line file can move imports, change cyclic dependencies, and surface circular references that were previously masked. `aislop fix` only does rewrites we can guarantee are safe, and splitting is not on that list.
What **aislop** ships instead is the agent handoff. `aislop fix --claude` (or `--codex`, `--cursor`, `--gemini`, and the rest) hands the size violations to a coding agent with the rule, the file location, and enough context to do the refactor. The agent splits. The deterministic engine re-checks. The human reviews the diff. The deterministic tool measures, the agent transforms, the human approves. That is the only division of labor that scales.
## A class of smell agents can't smell
Every code smell that depends on feeling tired is invisible to an agent. File too big. Function too long. Name too generic. Repetition too obvious. The agent never gets tired. The tool has to measure what the agent cannot feel, and the rules have to be specific enough that the measurement is unambiguous.
80 for a function. 400 for a file. 800 for JSX. 10% tolerance. Two rule IDs. One `OnboardingSvgIllustration.tsx` that nobody scrolled and the linter caught on the first scan. If your function is more than 80 lines, your agent is doing something wrong. If your file is more than 400, same story. The pain that kept humans honest is gone. The number does the keeping now.
## Find the 981-line file in your own repo
$ npx aislop scan
Run it. You will see the file nobody admits exists. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# You shouldn't be writing comments. And if you do, it should have a reason.
**URL:** https://scanaislop.com/blog/stop-letting-your-agent-write-comments/
**Date:** 2026-04-28
**Tag:** Essay
Our pair-programming AI added ~70 narrative comments to our own source during the 0.5 rehaul. Our own tool caught every one. If the tool builder needs the rule, you probably do too.
Should your agent be writing comments at all? Honestly, it depends on who you ask. Some teams treat every function as a documentation surface and want a JSDoc block above it. Some teams delete everything that is not a directive. Most teams just do not look. Here is what we landed on after our own pair agent got caught leaking 70 narrative comments into the aislop source during the 0.5 rehaul.
Two commits before the tag, we ran `aislop scan` against our own source. The source the agent had been writing into all day. The score moved in the wrong direction. The diff was clean by every other measure. Tests passed. Self-scan should have been 100. It was not.
We pulled the report. The damage was almost entirely comments. Decorative separators. Phase headers. Cross-reference paragraphs naming functions in adjacent files. Multi-paragraph JSDoc blocks narrating what the body already said in TypeScript. None of it intentional. All of it the agent being "helpful" in the way pre-2020 style guides told it to be helpful.
One `aislop fix` pass took the `//` count from 319 to 256. Sixty-three lines deleted. JSDoc blocks went 58 to 39. Net 82 comments removed in a single command, parse-checked on every file before write. The tool builder got caught by the tool. We tagged on the next commit.
## The five patterns we kept seeing
Every one of these was lifted from real diffs the agent wrote into the **aislop** source. None invented.
### Decorative separators
A banner inside a file is the file confessing it is too big. The cure is splitting the file, not decorating it.
### Phase headers
If something is a phase, it is a function. The header is pretending the phases are already separated when they are not.
### Narrative JSDoc
const findEagerCalls = (ast: Program): CallSite[] => ...
The function name says it. The signature says it. The body says it. The block is restatement. It is the first thing that goes stale on rename.
### Cross-references by name
printHeader(state)
Rots the moment anyone renames `buildFixRender`. Nobody updates it. The next agent reads the lie and bakes it into its next answer.
### Section-header-with-text
Decoration plus a doc string. Twice the rot. The fix is the same. Split the file. Name the function.
## The rule
`ai-slop/narrative-comment` shipped in 0.5. It flags decorative separators, phase headers, multi-paragraph JSDoc preambles, cross-reference commentary, and section-header-with-text. The rule wrote itself during the rehaul. The agent kept leaking these patterns into commits we had not yet tagged.
Companion rule `ai-slop/trivial-comment` handles the smaller stuff. `// increment counter` above `i++`. `// return result` above a return statement. Same pattern at a smaller scale. Same deletion logic.
Removal goes through the in-house AST engine, not a regex. Each delete proves the file still parses before write. That is the only reason we run it unattended.
## What we kept
The rule is not "delete every comment." A working scan keeps everything that earns its place.
Category
What it looks like
License / SPDX
License headers and SPDX tags at the top of the file.
Toolchain JSDoc tags
`@deprecated`, `@see`, `@example`, `@param` with real type narrowing, `@internal`.
Suppression directives
With a reason attached: `biome-ignore`, `eslint-disable-next-line`, `ts-expect-error`.
Non-obvious constraints
Things the type system cannot express. "This array must stay sorted; downstream binary search depends on it."
Library-quirk workarounds
Paired with a link. "Safari 16 returns undefined, see bug ID."
Everything else is restatement. Restatement rots. Delete it. Rename the function. Put it in the commit message.
## Why agent comments rot faster than human ones
Human-written comments aged badly because the human wrote them once, moved on, and never came back to maintain them. Agent-written comments are worse on two axes. The agent has no stake in whether the comment stays accurate. It will not be back in six months to fix the lie. And the volume is an order of magnitude larger. If an agent produces ten times the code a human does and three percent of it gets commented, you now have thirty times the stale narration.
Then there is the training-data feedback loop. The next agent reads the lying comment, treats it as ground truth about the function it sits above, and shapes its next answer around it. Every stale comment becomes a future bug shaped by a ghost.
## The rule that catches you too
Honest version. We wrote `ai-slop/narrative-comment` for ourselves first. We were the team shipping a tool to police AI-written slop, and our own pair was producing the cleanest case of it we had ever seen. The rule pinged us for 70 comments in our own engine source. We accepted the patches. Score back to 100. Tagged 0.5. If the tool builder needs the rule, you probably do too.
If your agent has to explain what it just did in a comment, you need a better agent or a better guardrail. Probably both. Explanation belongs in the PR description and the commit message. Things humans read once, in context, with the diff in front of them. Not above a function that will outlive five refactors.
## Run it on your repo
$ npx aislop fix
Point it at your source. See how many narrative comments the agent left behind. [Star the repo](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# Agents add. They don't subtract. That's the bug.
**URL:** https://scanaislop.com/blog/your-agent-is-leaving-dead-code-behind/
**Date:** 2026-04-25
**Tag:** Engineering
Agents add. They don't subtract. The chrome-extension repo went 9 to 68 to 88 almost entirely on dead code removal. Orphan imports. Unused exports. Half refactored functions the agent forgot to delete.
Who is supposed to delete the dead code the agent leaves behind? Ask around, nobody has a clean answer. Some teams trust the reviewer to catch it. Some trust the agent to refactor cleanly. Some never look at the diff past the feature bullet. Here is what we landed on after 25 projects. Dead code removal was the single biggest contributor to score jumps we saw, and nobody was owning the deletion.
`chrome-extension` went 9 to 68 on one pass, 88 on the second. A 79 point swing. `dailyapp-backend` went 27 to 87 to 94. `buildwithkenny` went 47 to 58 to 100. None of those moves were clever transformations. They were the linter doing the deletion the agent should have done at refactor time. Dead code, unused functions, useless variables. Your agents are leaving them behind.
The pattern is the same in every case. The agent refactored a function. Rewrote the body, renamed it, updated the call sites it could see. Shipped. Three commits later there is a top of file `import foo from './old-helper'` that nothing uses. A module exporting four functions, two of which are never imported. A `try` wrapping a call that no longer throws. A `legacyMode` flag that once gated something. None of that showed up in the diff. The agent refactored by addition, not by replacement, and the orphans stayed exactly where they were.
## The four classes of dead
Four rules cover it. Each one catches a different failure mode.
### `knip/exports`
Symbols exported from a module but never imported anywhere in the project. Usually the agent refactored the consumer and left the producer sitting there.
### `knip/files`
Whole files nothing references. Typically a v1 of a helper that got rewritten as `helper-v2.ts` and never garbage collected. Or a route file the agent thought it was creating fresh when it was actually duplicating an existing one.
### `ai-slop/unused-import`
`import x from '...'` with no usage in the file. The single most common finding per file across the 25 project run.
### `ai-slop/dead-patterns`
Unreachable code after `return`. Constant true conditions like `if (true)`. `try` blocks whose body provably cannot throw. Empty functions with a TODO that has been there for a year.
## Why we don't trust upstream --fix
Both `oxlint --fix` and `knip --fix` will happily remove dead code. Neither is safe to run unattended. We have four cases from the 0.5 validation run that prove it.
### Dangling colon (`zingo-web`)
Before:
const {' languages, isLoading: languagesLoading '} = useLanguages()
After `oxlint --fix` stripped the unused alias:
const {' languages, isLoading: '} = useLanguages()
Dangling colon. The file no longer parses.
### Rest-element alias (`snappymenu`)
Before:
({' ...props '}) =>
After:
({' ...props: _props '}) =>
Rest elements do not take aliases. The autofixer did not know that.
### Typed shorthand rename (`buildwithkenny`)
{`type Props = \nconst Foo: FC = () => `}
After:
const Foo: FC = ({' _durationInFrames '}) => {''}
Renaming a typed shorthand binding breaks the contract. The fixer treated it like a free identifier.
### TDZ from exhaustive-deps autofix (`joiner-landing-page`)
{`useEffect(() => , [searchParams]);\nconst loadScript = (s: string) => ;`}
After autofix added `loadScript` to the deps:
useEffect(() => {' if (script) loadScript(script); '}, [searchParams, loadScript]);
The dep got added before the variable was declared in scope. Build broke.
Four projects. Four different ways an upstream `--fix` shipped a TypeScript parse error into committed code. Detection in those tools is excellent. Mutation in those tools is not. The two have to be separated.
## Detection stays external. Fix is in-house.
aislop uses oxlint and knip for what they do well. Finding things. Neither is allowed to write to user files. The actual removal runs through an AST engine built on the TypeScript compiler API. Every mutation follows the same contract:
- 1. Classify the binding shape: shorthand property, aliased property, rest element, array binding, positional parameter, catch parameter.
- 2. Apply the rewrite for that shape. Never a regex.
- 3. Parse the resulting file. If it does not parse, revert.
- 4. Write.
Own the destructive fix. That is the principle. Detection is fine to outsource because a false positive in detection is just a missed fix. Mutation is not fine to outsource because a false positive in mutation is a broken file in your commit history. The asymmetry is the whole reason this engine exists.
Parse check before write is the implementation. Every auto fix proves its output parses before it persists. Slower than a regex by a factor of ten. Also the only reason `aislop fix -f` can run unattended in CI without anybody losing sleep over it.
## What the dead code is costing you
Easy to say "it is just a few unused imports." The cost is not size. The cost is signal. Every orphan is a future bug report waiting. Someone imports the dead function thinking it is live, and it silently misbehaves. Someone greps for a name and gets three hits, only one of which actually runs. The next agent passes through and the dead code becomes part of its training context for the next answer. Now you have fresh code shaped by a ghost.
Dead code also inflates the context window of every agent working in the file. A 400 line file that should be 240 lines is 40% more tokens to reason about. 40% more chances to trip on a stale reference. 40% more friction before every small change. The tax compounds with every commit, and the agent never feels the friction it created.
## Pick the tool
Either your tooling removes dead code, or your codebase has dead code. There is no third option. Agents do not clean up after themselves. Reviewers skim the additions and not the leftovers. Every week of shipping adds more orphans than the week before. `aislop scan` finds them. `aislop fix` removes them through the AST. `aislop fix --claude` hands the rest to an agent. The chrome-extension's +79 swing was not magic. It was the gap between "an agent has been writing here" and "a tool has been reading here."
## Try it on your repo
$ npx aislop fix
Run it. See what the agent left behind. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# The top 10 AI slop patterns we see often
**URL:** https://scanaislop.com/blog/top-ai-coding-mistakes/
**Date:** 2026-04-23
**Tag:** Pattern Guide
Ten common patterns in agent-written code, ranked by the risk they add. Each one maps to an aislop rule so you can catch it before review.
Which AI slop patterns actually matter? Ask ten teams, you get ten answers. Some say narrative JSDoc is the worst. Some say swallowed exceptions. Here is the list we landed on after running **aislop** against 25 real repos. Ten patterns, ranked by how often they appear and how much risk they add. Each one has a rule ID. Each one is deterministic. A few may already be in your repo.
01
## Narrative preamble JSDoc
ai-slop/narrative-comment
The multi paragraph JSDoc that restates, in prose, what the function body already says. It is one of the clearest signals of generated or over-explained code, especially when repeated across a codebase.
export const hasSideEffect = (): boolean => false;
**What to do instead.** Delete it. If you actually need a directive like `@deprecated` or `@see`, keep the tag and nothing else.
02
## Trivial "this function does X" comments
ai-slop/trivial-comment
A comment that says the same thing as the name of the line below it. Pure autocomplete residue. Nobody reads it. Nothing reads it.
initDB()
**What to do instead.** Delete the comment. If the line is doing something subtle, rename it so the subtlety is in the name.
03
## Decorative section headers
ai-slop/narrative-comment
ASCII banners separating sections of a file. Structure is what folders and filenames are for. If your file needs a banner, your file is too big.
**What to do instead.** Split the file. If a file needs banners to stay readable, it probably needs clearer module boundaries. A banner will not save it.
04
## Dead code after `return`
ai-slop/unreachable-code
"Safety" fallbacks emitted after an early return. They can never execute, but they still add noise and make future readers wonder which branch is real.
if (err) return null;return result;return undefined;
**What to do instead.** Delete the fallback. A line that cannot run is a line that cannot be tested. Dead code, unused functions, and useless variables should not survive review.
05
## Swallowed exceptions
ai-slop/swallowed-exception
A `catch` block that returns an empty value or does nothing. The agent is making the code compile. It is not handling an error.
try {' return await fetchUsers() '}catch {' return [] '}
**What to do instead.** Let it throw, or catch the specific error you know about and handle it explicitly. The silent `return []` is how you ship an empty dashboard with no idea why.
06
## `console.log` as debugging residue
ai-slop/console-leftover
Agent-assisted debugging can leave `console.log` calls behind. If nobody checks, they can reach production.
console.log('user:', user)console.log('before save')
**What to do instead.** Delete them. Or move to a real logger. Production code does not get narrated to stdout.
07
## `as any` / `as unknown as X`
ai-slop/unsafe-type-assertion
A common escape hatch. It removes the exact safety TypeScript exists to give you. Every `as any` is a place where the code needs a real type, a guard, or a narrower API.
const el = document.querySelector('.foo') as unknown as HTMLInputElement
**What to do instead.** Narrow the type. If it needs a guard, write the guard. If the library types are wrong, declare the missing type in a `.d.ts` once. Do not paper over it at every call site.
08
## Cross-reference commentary
ai-slop/narrative-comment
A comment that explains how two functions coordinate. Tightly coupled to whatever the code looks like today. Rots the moment anything near it changes.
printHeader(state)
**What to do instead.** Trust the reader. If the coordination is fragile enough that it needs a comment, encode it in a type or a helper.
09
## Empty exception handlers with a TODO
ai-slop/swallowed-exception · ai-slop/todo-stub
A `catch` block with nothing but a TODO in it. It makes the missing behavior visible, but it still leaves the error path unresolved.
catch (err) {' /* TODO: handle */ '}
**What to do instead.** Delete the `try` and let it throw, or handle the error. A TODO in a catch block is not error handling.
10
## Generic naming
ai-slop/generic-naming
`processData`. `handleRequest`. `utils.ts`. Agents reach for the generic name when a specific one would tell the reader what is going on. Naming is the cheapest documentation you have. On a good engineering team, this stuff matters.
function processData(data: any): any {' ... '}
**What to do instead.** Ask what data, and what processing. The answer is your function name. If you cannot answer, the function is doing too much.
## See your score
All ten are detected by **aislop**. Run `aislop rules` for the full catalogue. Run the scan below to get a starting point for cleanup.
$ npx aislop scan
[Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# What 25 real projects taught us about aislop 0.5
**URL:** https://scanaislop.com/blog/25-projects-aislop-findings/
**Date:** 2026-04-21
**Tag:** Engineering
Before tagging aislop 0.5 we ran it against 25 real projects from our own backlog. Here is what broke, what got fixed, and why we moved destructive fixes in-house.
When is a release actually ready to tag? Some teams ship once the fixtures pass. Some wait for
a beta user to flinch. Some cut the release, then hot-fix all week. For
**aislop** 0.5 we wanted to know before we shipped, so we
pointed it at 25 real projects from our own backlog and watched what broke. Here is what we
found, what we fixed, and what changed about how we build the tool.
## The validation run
Fixture tests prove the tool passes. Real projects prove the tool does not corrupt. So we ran
`aislop scan` and
`aislop fix -f` against 25 production codebases in
the Skivelane backlog. A mix of React and Next apps, Expo projects, a Chrome extension,
Remotion video templates, and a handful of Node backends.
Outcome
Count
Notes
PASS
15
Scan and fix ran clean. No source corruption.
PASS_WITH_WARNINGS
3
Ran clean. Flagged real pre-existing issues.
GAP
4
Destructive corruption from external fixers.
SKIPPED
2
Install failed before we could scan.
PARTIAL
2
Hung on `expo install --fix`.
Median score improvements across the PASS set:
before: 15
after fix: 32 (+17)
after fix -f: 47 (+32)
A few standouts:
- +
`chrome-extension`: 9 → 68 → 88 (+79)
- +
`dailyapp-backend`: 27 → 87 → 94 (+67)
- +
`buildwithkenny`: 47 → 58 → 100 (+53)
The four GAP projects are the interesting ones. Every single gap came from a destructive auto
fix that broke TypeScript in a way no fixture test had caught.
## The four bugs
### 1. Dangling colon on aliased destructure removal
Project: `zingo-web`. The user's code:
const {' languages, isLoading: languagesLoading '} = useLanguages()
After `oxlint --fix` stripped the unused alias:
const {' languages, isLoading: '} = useLanguages()
// TS1003: Identifier expected.
**What we did.** We stopped asking `oxlint --fix` to remove unused destructure identifiers. **aislop** owns that rewrite now. When the alias is unused, we remove the whole `key: alias` pair through the TypeScript compiler API. Not a regex.
### 2. Invalid rest-element rename
Project: `snappymenu`. **aislop**'s own regex-based rename helper hit rest parameters:
({' ...props '}) =>
The naive `foo: _foo` rename produced:
({' ...props: _props '}) =>
// TS2566: A rest element cannot have a property name.
**What we did.** Threw the regex rename out. The new implementation walks the AST and classifies each binding. Positional parameter. Shorthand property. Aliased property. Rest element. Catch parameter. Array binding. Rest elements rename in place: `...foo` becomes `..._foo`. Each shape has its own rewrite. No regex touches source code any more.
### 3. Typed shorthand rename breaks property binding
Projects: `buildwithkenny`, `joiner-landing-page`.
type Props = {' durationInFrames: number '}
const Foo: FC = ({' durationInFrames '}) => {''}
Renaming the shorthand property renamed both the property key and the local binding:
const Foo: FC = ({' _durationInFrames '}) => {''}
// TS2339: Property '_durationInFrames' does not exist on type 'Props'.
**What we did.** Convert shorthand into aliased form instead of renaming. `{' foo '}` becomes `{' foo: _foo '}`. The property key `foo` still matches the type. The local binding gets the underscore prefix. Unused-var rule is satisfied.
### 4. `react-hooks/exhaustive-deps` autofix causes TDZ
Project: `joiner-landing-page`.
useEffect(() => {' if (script) loadScript(script); '}, [searchParams]);
const loadScript = (s: string) => {' ... '};
oxlint appended `loadScript` to the dep array without checking hoisting order. The result failed to compile. TS2448. "Block-scoped variable 'loadScript' used before its declaration."
**What we did.** Disabled the autofix for `react-hooks/exhaustive-deps` in **aislop**'s oxlint config. The warning still fires. Developers still get told about the missing dependency. We just don't apply a fix that assumes a human reviewer is about to reorder declarations.
All four gaps closed. The common thread. External auto fixers assume a human will review the diff and catch what they got wrong. Run them unattended, which is exactly what `aislop fix` does, and you ship broken TypeScript.
## The moment the tool beat the tool maker
Somewhere in the middle of all this, we added a new rule:
`ai-slop/narrative-comment`. It catches decorative
separators, phase headers, multi line preambles, cross reference commentary, and JSDoc
paragraphs that summarize what a function does.
Then we ran **aislop** against its own source. Two fix passes removed 82 comments. They were all
ours. Put there by an AI agent working with one of us during the rehaul. The agent writes
prose. The tool removes prose. The commit is clean. We have a feedback loop that works even
when we're not paying attention.
The honest version. We wrote a linter for AI slop. Then we used AI to build features. Then we
caught ourselves producing the exact slop the linter was designed to flag. If the tool builder
needs the rule, you probably do too.
## What we learned about AI-generated code
**AI agents produce distinct comment patterns.** Decorative section banners. Phase numbered preambles. JSDoc that narrates implementation. Comments that cross reference other functions by name. Humans rarely write these. They take too long and add no information. Agents write them because training data rewards visible effort.
**External fixers are designed for humans, not pipelines.** `oxlint --fix` and `knip --fix` both assume a reviewer will look at the diff. Wire them into an unattended pipeline, which is where **aislop** lives, and their "fixes" corrupt the exact destructure patterns React and TypeScript code is full of. Three of our four GAPs came from this assumption.
**Regex based source mutation is a bug factory.** **aislop** had a regex rename helper from 0.4. It worked on 90% of cases. The 10% it broke were real user code. Moving to AST based rewrites was the direct response. Every transformation now runs through the TypeScript compiler, classifies the binding shape, and parse verifies the output before writing. Revert on any failure. Always.
The broader move with 0.5. We stopped delegating destructive operations to external tools. Detection we'll happily take from anywhere. But when something mutates user code, we want to own the rewrite, know the edge cases, and guard the file with a parse check.
## Where **aislop** goes next
0.5 is tagged. Next up. A formal release to npm. Hydrogen style recipes for common stacks like
Next, Expo, and Remotion. And the scanaislop GitHub app so teams can enforce a score threshold
on every PR without touching a workflow file.
## Try it on your repo
$ npx aislop scan
$ npx aislop fix -f
Run it on your own code. If it breaks anything, open an issue. We'll pin your project to the
next validation matrix.
[Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop)
if you want releases to show up in your feed.
---
# aislop v0.6.0. Agent hooks for Claude, Cursor, Gemini, and six more
**URL:** https://scanaislop.com/blog/aislop-v0-6-0-agent-hooks/
**Date:** 2026-04-20
**Tag:** Release
Findings flow back to the agent on the turn it wrote the code. Runtime adapters for Claude Code, Cursor, Gemini. Rules-only installers for six more. Structured feedback contract, quality-gate mode, sentinel-guarded writes.
For five versions of **aislop** the feedback loop was the same shape. Agent writes code. You (or CI) run `aislop scan` later. Aislop reports 40 findings. You tell the agent. The agent fixes them. Rinse.
That loop is slow and it leaves the agent unaware of what it's producing until the code has already landed somewhere. By the time the feedback arrives, the context is gone. v0.6.0 is the release where we collapsed the loop. One command wires aislop into the agent's native lifecycle. The agent sees its own slop on the turn it wrote the code, not at review time.
## 1. One command, nine agents
`aislop hook install` plugs aislop into your agent. Pick the agent, or install everything at once:
$ npx aislop hook install --claude
$ npx aislop hook install --cursor
$ npx aislop hook install --gemini
# or every supported agent in one shot
$ npx aislop hook install
Three agents get **runtime adapters** — Claude Code (`PostToolUse`), Cursor (`afterFileEdit`), Gemini CLI (`AfterTool`). Aislop runs as a hook on every edit and pipes structured findings back.
Six more — Codex, Windsurf, Cline (plus Roo), Kilo Code, Antigravity, Copilot — don't have a native hook lifecycle. They get a **rules-only installer**: aislop writes an `AISLOP.md` the agent reads every turn. Same standards, different mechanism.
## 2. The feedback the agent sees
The agent doesn't get "you made a mess, please fix it." It gets JSON it can act on. Every hook fire emits an `aislop.hook.v1` envelope:
{'
"schemaVersion": "aislop.hook.v1",
"cliVersion": "0.6.0",
"score": 94,
"counts": {' "errors": 0, "warnings": 3, "fixable": 1 '},
"findings": [
{' "filePath": "src/routes/auth.ts", "line": 42,
"rule": "ai-slop/narrative-comment",
"severity": "warning", "fixable": true '}
],
"nextSteps": ["Run aislop fix to auto-resolve 1 issue"],
"baseline": 95, "regressed": true
'}
File path, line number, rule ID, severity, fixable flag, suggested next steps. No prose-to-action translation step. The agent parses it, acts on it, moves on. Top-20 findings per hook fire so the agent's context doesn't flood on a big edit.
## 3. Quality-gate mode
One step further. With `--quality-gate`, aislop captures your project's current score as a baseline at install time. Claude's `Stop` hook then blocks the session if the score would drop below that baseline.
$ npx aislop hook install --claude --quality-gate
The agent cannot end a turn that regresses the codebase. It sees the block reason, the diff that caused it, and gets one more pass to clean up before the session stops. Instead of the reviewer being the floor, aislop becomes the floor. Code that leaves the agent's session is always at or above the bar you set.
## 4. Safety rails
Writing config into nine different agent-specific locations needed a safety model. Four pieces:
Guard
What it prevents
SHA-256 sentinel
Every write stamps a hash fence. Uninstall only removes blocks that match. Hand-edited sections are never touched.
Atomic writes
JSON goes to a tempfile and renames in. Markdown is fenced between `` markers. No half-written config.
Recursion guard
`.aislop/hook.lock` with a 30-second stale window. Aislop doesn't scan itself through its own hook.
git diff fallback
Kicks in when the hook payload doesn't carry a file path. Cursor does this on some tool calls.
## Everything else
### Added
Change
Details
`aislop hook` command
`install`, `uninstall`, `status`, `baseline` — full lifecycle management for hooks across all nine supported agents.
Structured feedback contract
`aislop.hook.v1` — versioned JSON envelope with score, counts, top-20 findings, next steps, regression flag.
Swagger / OpenAPI safe
Narrative-comment rule was flagging `@swagger`, `@openapi`, `@api*` blocks as slop and auto-fix was deleting them. Real repos lost 1,340 lines of API docs in a single run. All API-doc JSDoc tags now in `MEANINGFUL_JSDOC_TAGS`. Covered by tests.
### Fixed
Change
Details
`npx aislop scan --json` clean stdout
`postinstall-tools.mjs` was logging to stdout. In CI (no npx cache) install progress prefixed the JSON envelope and any parser defaulted to `score: 0`. Install progress now routes to stderr.
`pnpm audit --fix` subcommand
Layered with develop's 410-endpoint fallback. Pnpm now writes surgical `pnpm.overrides` when the audit endpoint is live, falls back to npm otherwise.
Breaking changes
None at the CLI contract. The `hook` subcommand is additive.
Full changelog on [GitHub](https://github.com/scanaislop/aislop/releases/tag/v0.6.0).
## Install
$ npx aislop@latest hook install --claude
Or pick every supported agent:
$ npx aislop@latest hook install
Full guide at [/docs/hooks](/docs/hooks). [Star the repo on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# aislop v0.5.0. New CLI, own AST fix engine, stable output, better experience
**URL:** https://scanaislop.com/blog/aislop-v0-5-0-ast-fix-engine/
**Date:** 2026-04-18
**Tag:** Release
The release where we stopped blindly trusting external fixers with destructive cleanup, wrote our own AST engine, and made aislop fix produce zero phantom diffs on clean repos.
For two versions I was blindly trusting external tools to do the destructive cleanup in **aislop**. `oxlint --fix` removed unused imports. `knip --fix` stripped unused exports. It worked 90% of the time. The other 10% shipped broken TypeScript into committed code in ways I wouldn't see until CI broke three commits later.
v0.5.0 is the release where we stopped. Three threads landed together. A new CLI that shows every engine updating live. Our own AST engine that knows exactly what it is deleting (and reverts if anything goes wrong). And a fix pipeline that's finally stable: run it twice on a clean repo and the second pass is a no-op.
## 1. A new CLI you can actually watch
Every command was rewritten. Scan now shows all six engines updating concurrently in a live grid with aligned columns. Fix renders as a live rail: each step appears with a spinner while it runs, then resolves to a checkmark, warning, or cross when it completes. The footer always summarizes what happened, and the next-step hints carry an accent-green arrow.
$ npx aislop scan
aislop 0.5.0 · the quality gate for agentic coding
scan · my-app · typescript · 142 files
◆ Formatting done 0 issues 0.4s
◆ Linting done 0 issues 0.4s
! Code Quality warn 2 issues 0.8s
! AI Slop warn 4 issues 0.5s
◆ Security done 0 issues 1.3s
87 / 100 Healthy
· 0 errors · 6 warnings · 4 fixable
The output is invocation-aware. If you ran the tool via `npx`, every hint in the footer says `npx aislop ...`. If you installed globally, it drops the prefix. Copy-paste works regardless of how the binary got on your machine. Small thing, big difference on a shared team.
## 2. Our own AST fix engine
The biggest investment in v0.5.0. Here is why.
During the 25-project validation run for 0.5, four real projects broke. Not from our code. From the external `--fix` tools we were delegating to. A dangling colon left behind after stripping an unused alias. An invalid rest-element rename. A typed shorthand rewrite that broke the property binding. A TDZ error from the exhaustive-deps autofix. Every one of them shipped broken TypeScript. None were caught by fixture tests.
The pattern was always the same. Those tools were designed for humans who review the diff before committing. **aislop** runs unattended. It's a fundamental mismatch. So we stopped delegating and wrote our own engine.
Every mutation now goes through the TypeScript compiler API. Each change classifies the binding shape first: shorthand property, aliased property, rest element, array binding, positional parameter, catch parameter. Each shape has its own rewrite path. Before a file is written, the output is parsed. If the parse fails, the write is reverted. It's slower than a regex by a factor of ten. It's also the only reason we can promise `aislop fix` won't corrupt your code.
For now, the more aggressive destructive fixes (dependency audit, unused file removal, Expo alignment) sit behind the `-f` flag. Run `aislop fix -f` to opt in. Once enough teams have run it successfully in production, it becomes the default. For the current release, `aislop fix` on its own does the safe things through the new engine. `-f` adds the rest.
## 3. Fix-then-scan is a stable fixed point
Running `aislop fix` a second time on a clean repo used to produce phantom diffs. Biome reformatting one whitespace. Knip stripping a keyword that wasn't quite unused. Little changes that looked like regressions in git but weren't. In v0.5.0 they're gone.
The fix pipeline scans first, then only runs fixers for engines that actually found issues. If formatting reports zero issues, Biome does not run. If linting is clean, the lint fixer is skipped. A clean repo stays clean after `aislop fix`. Always. That's a property we can now promise in CI.
## Everything else
### Added
Change
Details
Top-level help output
Every subcommand help screen now renders with the brand header and a consistent accent-green hint line.
JSON schema metadata
Output gains `schemaVersion` and `cliVersion` at the top so consumers can version their parsing.
`aislop ci --human`
Re-enables the full visual design when you want human-readable CI output.
`aislop init` wizard
Now a proper interactive wizard built on [@clack/prompts](https://www.clack.cc/). Four prompts, preserves existing schema, emits the right next-step hint.
Project-aware doctor
One row per engine with its backing tool. Shows a cross with inline remediation only for tools you actually need.
### Changed
Change
Details
SQL-injection detection
Template literals only flag when preceded by a database-like receiver (`db.`, `knex.`, `prisma.`, etc.). `log.raw` and similar no longer false-positive.
`typescript` dependency
Moved from `devDependencies` to `dependencies`. Required at runtime by the new AST engine.
Breaking changes
None at the CLI contract level. All flags, exit codes, and JSON field names stayed stable.
Full changelog on [GitHub](https://github.com/scanaislop/aislop/releases/tag/v0.5.0).
## Upgrade
$ npx aislop@latest scan
Or if you have it installed already:
$ npm update aislop
$ pnpm update aislop
[Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# aislop v0.4.0. Agent Handoff, Smarter Fix, Better Scoring
**URL:** https://scanaislop.com/blog/aislop-v0-4-0-agent-handoff/
**Date:** 2026-04-15
**Tag:** Release
aislop v0.4.0 ships agent handoff for 14 coding agents, a fix pipeline that leaves clean files alone, and scoring that treats formatting as warnings not errors.
What is new in v0.4.0? Three things worth opening the changelog for. `aislop fix` can now hand remaining issues to any of 14 coding agents with one flag. The fix pipeline stopped running fixers when nothing needed fixing, so clean code stays clean. And formatting issues are warnings now, not errors, which means a stray tab no longer drops your score from 100 to 94.
Here is the full picture, with the upgrade command at the bottom.
## Hand off to any agent in one flag
When `aislop fix` resolves what it can automatically, you are usually left with issues that need human (or agent) judgment. Complex refactors. Architectural decisions. Security fixes that need context. v0.4.0 generates a structured prompt with every remaining issue, the affected files, and suggested fixes, then opens your preferred coding agent.
$ aislop fix --claude # opens Claude Code with full context
$ aislop fix --codex # opens Codex CLI
$ aislop fix --cursor # copies prompt to Cursor
$ aislop fix --gemini # opens Gemini CLI
$ aislop fix --prompt # print prompt for any agent
14 agents supported out of the box: Claude Code, Codex CLI, Cursor, Windsurf, Gemini CLI, Amp, VS Code Copilot, Aider, Goose, OpenCode, Warp, Kimi Code, Antigravity, and Deep Agents. If your agent is not in the list, `--prompt` prints the context so you can paste it anywhere. The prompt carries the scan score, every diagnostic with file path and line number, and a prioritized fix list. The agent has everything it needs without asking follow up questions.
## Fix that doesn't touch clean code
In previous versions, `aislop fix` ran every fixer unconditionally. Even if your code was already clean, Biome would reformat files and produce noisy diffs like "40 insertions, 40 deletions" for what was just re-indentation. v0.4.0 scans first, then only runs fixers for engines that actually found issues. If formatting reports zero issues, the formatter does not run. If linting is clean, the lint fixer is skipped.
- + **No phantom diffs.** Clean code stays clean after fix.
- + **Faster runs.** Skips unnecessary tool invocations.
- + **Trustworthy output.** "0 resolved" means 0 files changed. Always.
The output is cleaner too. When everything passes, the result box shows your score and stops. No more "Next steps: 1. All clear!" If it is clear, we just say nothing.
## Formatting issues are warnings, not errors
A tab being two spaces instead of four should not block your PR. But that is exactly what happened. Biome reports formatting issues as `error` severity and **aislop** was passing that through. Your score dropped from 100 to 94 because of indentation.
v0.4.0 downgrades all formatting issues to `warning`. This matches every other format engine in **aislop** (gofmt, ruff, rubocop, cargo fmt) which already used warning severity. The principle: auto fixable style issues are warnings. Real bugs are errors. Your scores will be more accurate. A project with perfect logic but messy tabs gets 100, not 94.
## Everything else
- + Diagnostic deduplication. No more duplicate issue reports from overlapping engines.
- + Unused file removal in `aislop fix`.
- + Directory existence validation in scan.
- Respect `biome.json` lineWidth config when scanning and fixing.
- Fallback to npm audit when the pnpm audit endpoint is retired.
- Graceful config loading errors.
- CI upgraded to Node 22/24, pnpm/action-setup v5.
## Upgrade
$ npx aislop@latest scan
Or if you have it installed already:
$ npm update aislop
$ pnpm update aislop
Full changelog on [GitHub](https://github.com/scanaislop/aislop/releases/tag/v0.4.0).
## Try it on your repo
$ npx aislop scan
Run it. You will see the number. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# How to Add a Quality Gate to Your AI-Assisted Pipeline in 5 Minutes
**URL:** https://scanaislop.com/blog/aislop-in-ci-cd/
**Date:** 2026-04-14
**Tag:** Tutorial
One GitHub Actions workflow. One score threshold. Every PR from every agent held to the same bar.
What is the least amount of CI setup that will actually stop your agent from shipping slop? Ask around and you will hear everything from a full custom pipeline to nothing at all. Your agent is writing code. You are reviewing (or not). Quality drifts. The easiest way to stop the drift is to check it on every push. Here is how to set that up in five minutes with one workflow and one threshold.
## GitHub Actions
Drop this into `.github/workflows/aislop.yml`:
{`name: aislop
on: [push, pull_request]
jobs:
quality-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: scanaislop/aislop@v0.10.1
with:
version: latest`}
That is it. The `ci` command exits 1 if the score drops below the threshold in `.aislop/config.yml`. Your PR goes red. Your agent gets held accountable.
## GitLab CI
{`code-quality:
image: node:22
script:
- npx --yes aislop@latest ci`}
## Picking a threshold
Start low. If your codebase scores 55 today, do not set the threshold at 80. You will just disable the check on day one. Set it at 50. Fix the worst stuff. Bump to 60. Ratchet up over time. The threshold is a floor, not an aspiration.
Rough guide. 70 and up means no critical issues. 80 and up is a well-maintained codebase. 90 and up is clean.
## Only scan changed files
On large repos, scanning everything on every PR is slow. Use `--changes` to only check files modified in the current diff:
$ npx aislop scan --changes
## Auto-fix before commit
Some teams run `aislop fix` as a pre-commit hook so formatting and dead imports never make it into a PR. Reviewers see clean diffs. They can focus on logic instead of the trail of `console.log` the agent forgot.
npx aislop fix --staged
## Run it
$ npx --yes aislop@latest ci
Drop it in your workflow. Watch a PR go red the first time the agent regresses the score. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# What Is AI Slop? The Patterns AI Agents Leave Behind
**URL:** https://scanaislop.com/blog/what-is-ai-slop/
**Date:** 2026-04-08
**Tag:** Guide
Trivial comments. Swallowed exceptions. Unsafe casts. None of them fail your CI. All of them reach production. Here is what to look for.
What counts as AI slop? Some people say it is anything an agent wrote. Some say only the stuff that actually breaks. Some do not care as long as it compiles. Here is what we landed on after 25 real projects. You know it when you see it. A PR where everything technically works but reading it feels like wading through filler. That is AI slop. It is not broken code. It is code that is slightly worse than what a human would write, repeated across hundreds of lines, compounding with every merge.
Ask anyone, or any team, shipping (or vibecoding, like we say now) with Claude Code, Cursor, Opencode, Codex, or whatever agent they are on. The patterns are the same everywhere.
## The patterns
**Comments that say nothing.** `// Initialize the database connection` sitting above `initDB()`. These are not helpful. They are visual noise. Worse, they drift. Six months from now the function gets renamed but the comment does not, and now it is actively lying to you.
**Silent failures.** Empty catch blocks, or catch blocks with just `console.log(err)`. The agent generates these because it is trying to make the code compile, not because it thought about error handling. You end up with apps that fail silently in production and nobody knows why. "Oh sh**, it works now. But I can tell it's not scalable." Right.
**Naming that gives up.** `data`, `data2`, `result`, `temp`, `helper_func`. These are placeholders that never got replaced. When you are debugging at 2am and every variable is called `data`, the naming is not a style issue. It is a productivity issue.
**Type system escape hatches.** `as any` is the agent's way of saying "I don't know the type." `as unknown as HTMLElement` is the agent's way of saying "I really don't know the type." Both throw out the safety TypeScript is supposed to give you.
And that is before you get to the rest. Dead code. Unused functions. Useless variables. Duplicate helpers that should have been one reusable function. `console.log` statements sitting in production (for real). Half renamed variables. JSDoc paragraphs nobody asked for. Your agents are leaving all of it behind.
## Why it is hard to catch in review
Each instance is small. A trivial comment here. A generic name there. Nobody rejects a PR over one comment. But across a 60-file PR, these add up. And since AI-generated PRs tend to be large, the reviewer is already overwhelmed. "I don't even review anymore, I just deploy. I pushed 10K lines yesterday." That is the reality for most teams now.
The other problem is that it looks professional. The indentation is perfect, the imports are sorted, the function signatures are reasonable. It *looks* like good code at a glance. You have to read carefully to notice the catch block is empty. Nobody is reading carefully. Nobody is reading at all.
## What to do
Do not stop using AI agents. They are genuinely useful. But treat their output the way you would treat a junior developer's code. Review it properly. Rename the variables. Fill in the error handling. Delete the useless comments. Or run `npx aislop scan` and let it flag the obvious stuff so you can focus on the logic. Your coding agent should have guardrails. `AGENTS.md` is not enough. You have to hold your agent accountable.
## Try it on your repo
$ npx aislop scan
Run it. You will see the patterns in your own code. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---
# Why We Built scanaislop: The Problem with Five Linters and No Standard
**URL:** https://scanaislop.com/blog/why-we-built-aislop/
**Date:** 2026-04-01
**Tag:** Announcement
Every team shipping with AI agents has the same problem. Code that compiles, tests that pass, and a diff that is still a mess. We built aislop because nothing else catches it.
Why build another linter when every team already has five? Fair question. Some teams chain ESLint, Prettier, Biome, oxlint, and ruff together and call it a standard. Some only run whatever the IDE runs and ship. Some have stopped noticing. Here is what we landed on, and the PR that made it obvious nothing we had was catching the problem.
I was reviewing a PR last year. Sixty files. TypeScript and Python. The code worked, tests passed, CI was green. Something was off. Half the catch blocks were empty. There were comments like `// get the user` sitting above `getUser()`. Three different variables called `data`, `data2`, and `result`. The author had clearly accepted most of the agent's suggestions without touching them. I left about forty comments. It took an hour. The next PR was the same.
## The tooling gap
We already had ESLint, Prettier, and ruff in CI. None of them flagged any of this. That is not a knock on those tools. They do what they are designed to do. ESLint catches syntax and style. Prettier formats. ruff lints Python fast. But none of them ask whether a comment is actually useful, or whether a catch block is doing anything. Those are judgment calls, and they are exactly the calls AI generators get wrong the most.
## Five tools, five configs
The other problem was simpler. Our monorepo had TypeScript, Python, Go, and some Rust. Each language had its own linter, its own formatter, its own CI step. Onboarding a new dev meant explaining four different tool configs before they could push code. I wanted one command that could scan everything and hand me a number. Not a dashboard. Not a platform. Just a CLI that runs locally and in CI.
## What **aislop** does
`npx aislop scan` walks your project, detects the languages, runs the right checks in parallel, and outputs a score from 0 to 100. No config to start. No API keys. Runs offline except for optional dependency audits. `npx aislop fix` auto-resolves what it can. Formatting. Unused imports. Dead code. The rest it reports so you can clean it up yourself or hand it to an agent with `aislop fix --claude`.
The scoring is deterministic. Same code, same score, every time. No LLM in the loop. That matters. The score is the artifact. If an LLM were in the middle of it, the number would drift and the gate would be useless.
## Where it's going
Architecture rules are next. Import bans, layering enforcement, that kind of thing. After that, better Expo and React Native support and deeper Python checks. It is MIT licensed and on [GitHub](https://github.com/scanaislop/aislop). If you have ideas, open an issue. If it breaks on your repo, open an issue. We will pin it to the next validation matrix.
## Try it on your repo
$ npx aislop scan
Run it. You will get a number. [Star the AI Slop CLI on GitHub](https://github.com/scanaislop/aislop) if you want the next release in your feed.
---