Deterministic code-quality detectors for JavaScript / TypeScript. Six pure-function detectors that find the structural "code slop" AI assistants and fast-moving teams leave behind — no network, no LLM, fully re-runnable.
- CD01 — abstraction bloat: an interface with exactly one implementation and no polymorphic use.
- CD02 — guard spam: a function body packed with defensive early returns.
- CD03 — unused export: an exported symbol never referenced anywhere in the corpus.
- CD04 — re-export plumbing: a barrel file that only re-exports, with no local logic.
- CD05 — reinvention: a helper that re-implements a stdlib capability (the left-pad class).
- CD06 — dead code: unreachable statements after a terminal
return/throw.
Findings are candidates, not deletion verdicts. Text-based analysis cannot resolve JS module reachability (star-barrels, namespace imports, dynamic imports). Always confirm a candidate with a real module graph (
knip,tsc --noUnusedLocals, a bundler's tree-shaking report, or human review) before removing code.
npm install @gregshevchenko/code-qualityOr run without installing:
npx @gregshevchenko/code-quality src/# Human-readable report
code-quality src/
# JSON
code-quality src/ --json
# Only show higher-confidence candidates
code-quality src/ --min-confidence 0.7Output (one line per finding):
src/utils/helpers.ts:42 CD03 [0.50] Exported symbol "formatDate" has no references in the scanned corpus (candidate).
import { analyzeSource } from "@gregshevchenko/code-quality";
import { readFileSync } from "node:fs";
const text = readFileSync("src/foo.ts", "utf8");
const findings = await analyzeSource(text, {
allSources: [text, /* ...other files for cross-file reference counting */],
publicApiSources: [/* index/barrel files — their re-exports are public API */],
thresholds: { guard_spam_min: 6 }, // tune any detector
});
for (const f of findings) {
console.log(`${f.id} line ${f.line} [${f.confidence}] ${f.message}`);
}| Export | Signature | Purpose |
|---|---|---|
analyzeSource |
(text, options?) => Promise<Finding[]> |
Run all detectors over one file |
detectAbstractionBloat |
(text, allSources) => Finding[] |
CD01 |
detectGuardSpam |
(text, min) => Finding[] |
CD02 |
detectUnusedExports |
(text, allSources, publicApiSources?) => Promise<Finding[]> |
CD03 |
detectReExportPlumbing |
(text) => Finding[] |
CD04 |
detectReinvention |
(text) => Finding[] |
CD05 |
detectDeadCode |
(text) => Finding[] |
CD06 |
A Finding is { id, line, confidence, message, suggested_action }.
Every threshold is overridable — the shipped defaults are conservative and favor recall over precision. Tune them for your own codebase:
await analyzeSource(text, { thresholds: { guard_spam_min: 7 } });- Not a type checker. It does not build your project or resolve
tsconfigpaths. - Not a deletion robot. It produces candidates for a human or a graph-backed tool to confirm.
- Not tuned to your codebase out of the box. Measure precision/recall on a labeled sample of your own code, then set thresholds.
MIT