slopcatcher
v1.0.0
Published
Catch LLM slop patterns in your source files
Maintainers
Readme
slopcatcher
Catch LLM slop patterns before they ship.
Agentic coding is the new normal. The side effect: codebases filling up with defensive fallbacks, swallowed errors, stub log calls, and over-engineered null guards that exist because the model didn't trust the data model it was given. slopcatcher is a static analysis CLI that flags these patterns at the file level so you catch them in review — or in CI before they ever merge.
npx slopcatcher src/usecases/alertDispatch.jsThe problem
This is slop:
// Why does a business rule need a fallback to []?
// If contactGroupIds is absent, that's a DATA bug — fix the schema.
const contactGroupIds = rule.contactGroupIds ?? [];
// Why is this catch returning null?
// Callers think this succeeded. It didn't.
} catch {
return { name: null, provider: null };
}
// Why is a service being constructed inside a constructor?
// This class is now impossible to test without a real DatabaseService.
this.databaseService = new DatabaseService(firestoreRepository);
// Why is there a log that says "would send"?
// Either send it or delete this code path.
logExec(`Email stub: would send to ${contact.email}`);LLMs generate this because they optimise for "the code runs" not "the code is correct". slopcatcher treats these patterns as the errors they are.
Installation
# Global install
npm install -g slopcatcher
# Or use without installing
npx slopcatcher src/usecases/alertDispatch.jsUsage
# Single file
slopcatcher src/usecases/alertDispatch.js
# Multiple files
slopcatcher src/**/*.js
# JSON output (for tooling)
slopcatcher --json src/usecases/alertDispatch.js
# Filter by minimum severity
slopcatcher --min-severity error src/usecases/alertDispatch.jsExit code 0 = clean. Exit code 1 = slop found. Drop it in CI and it becomes a gate.
Rules
| Rule | Severity | What it flags |
|------|----------|---------------|
| constructor-service-init | error | new Service() inside a constructor. Inject your dependencies — don't build them. |
| unnecessary-fallbacks | error | ?? [] / ?? {} / ?? null on business data. If that value can be absent, fix the data model. |
| swallowed-errors | error | catch blocks that return null or empty instead of throwing. Callers deserve to know something failed. |
| stub-code | warn | Log calls containing "would send", "stub", TODO, FIXME. Stubs have no place in production logic. |
| optional-chain-overdose | warn | ?. chains 3+ levels deep. If the shape is guaranteed, drop the guards. If it isn't, fix the contract. |
| magic-timeout | warn | Raw number literals passed to setTimeout / setInterval. Extract to a named constant. |
| jsdoc-slop | info | JSDoc @param descriptions that just restate the parameter name word-for-word. |
Adding rules
Each rule is a module in src/rules/ that follows the Babel visitor pattern:
// src/rules/my-rule.js
module.exports = {
meta: {
id: "my-rule",
description: "What this rule catches",
severity: "warn", // "error" | "warn" | "info"
},
create({ addFinding }) {
return {
CallExpression(path) {
// Inspect the AST node
addFinding({
rule: "my-rule",
message: "Explain what's wrong and how to fix it.",
severity: "warn",
line: path.node.loc.start.line,
column: path.node.loc.start.column,
});
},
};
},
};Register it in src/rules/index.js:
module.exports = [
// ...existing rules
require("./my-rule"),
];Philosophy
If a value can be absent, that's a data-model bug — not something JavaScript needs to paper over.
slopcatcher takes a deliberate stance: defensive fallbacks on business logic are wrong. An EmailService either exists or the code shouldn't be running. A contactGroupIds array either has a schema guarantee or that schema needs to be fixed. Masking those problems with ?? [] doesn't make the code safer — it makes failures silent and harder to trace.
New patterns will be added as the field of LLM slop matures.
