stop-ai-slop
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Uyari
- fs module — File system access in .github/workflows/publish.yml
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Zero-dependency linter and gate that blocks AI-generated comment slop: multi-line narrative comments, changelog markers, step-numbered filler. Write-time plugin for OpenCode, Claude Code hooks, pre-commit, GitHub Action, GitLab CI, MCP server. RU+EN+DE+FR+ES detection.
stop-ai-slop
English | Русский
stop-ai-slop is a zero-dependency comment linter and gate that blocks AI-generated comment slop before it lands in your codebase. One scanner (
skill/scripts/scan.mjs, theRULEStable) is the single source of truth for a one-line, why-only comment policy. It is enforced at write-time (OpenCode plugin, Claude Code PreToolUse hook), at commit-time (pre-commit hook installed with--install), and in CI (GitHub Action, GitLab CI template) — and it also runs as an MCP server. Node >= 18, zero npm dependencies, MIT licensed, works on Windows, Linux and macOS.
AI coding agents over-comment: multi-line narrative blocks, // was X, now Y changelog notes, Step 1 / Step 2 filler, banner dividers, markdown inside comments, TODO without a ticket, comments that restate the code line beneath them, rotting file.py:123 pointers, even invisible zero-width and BiDi characters. stop-ai-slop catches all of it deterministically — no LLM, no scoring threshold, no network — and blocks the offending edit or commit.
Install in two commands
npm i -D stop-ai-slop
npx stop-ai-slop --install # writes the pre-commit hook + npm scripts into the current repo
--install is the only command that modifies your repo: it appends a marked block to .git/hooks/pre-commit (idempotent, never clobbers an existing hook) and adds the stop-ai-slop / stop-ai-slop:all npm scripts. The hook embeds the absolute path to the scanner as of install time — after moving or re-cloning the scanner, run --install again.
Homebrew
brew tap WhiteBite/stop-ai-slop https://github.com/WhiteBite/stop-ai-slop
brew install WhiteBite/stop-ai-slop/stop-ai-slop
The formula wraps the npm package (this repository doubles as its own tap — the formula lives in Formula/stop-ai-slop.rb) and pulls in Homebrew's node, which satisfies the Node >= 18 requirement.
Who is it for
Teams and solo developers whose code is written partly or mostly by AI coding agents (OpenCode, Claude Code, Cursor, Codex, Copilot, Gemini CLI) and who want a comment policy enforced mechanically — at the write, at the commit, in CI — instead of relying on review discipline.
Why stop-ai-slop
- Zero dependencies. The runtime is a single
.mjsscanner (the self-test lives in a separate module,selftest.mjs, so the shipped gate stays lean); the OpenCode plugin is a single.tsfile. No biome, ruff, oxlint, Python or ripgrep to install. - Blocks at the moment of the edit, not after. The OpenCode plugin rejects
write/edit/multiedit; the Claude Code PreToolUse hook deniesWrite/Editbefore they happen. Most alternatives only scan after the fact, or ask the model to run grep itself. - Multi-language natural-language detection. Changelog markers, numbered steps and "This function…" openers are matched in RU + EN + DE + FR + ES. Deterministic competitors are English-only; the LLM-based ones read any language but need an API key.
- One source of truth. Every rule lives in a single
RULEStable;--explain <rule-id>prints each rule's rationale (Why / Instead of / Write / Ignore it when). - Machine-readable output.
text(default), Reviewdogrdjson, and SARIF 2.1.0 for GitHub code scanning. - Legacy-friendly. A baseline grandfathers existing findings so the gate only fires on new slop.
- Wide enforcement surface. OpenCode, Claude Code, Cursor, Codex, GitHub Actions, GitLab CI, MCP, VS Code, IntelliJ IDEA.
What you get, and what installs automatically
One scanner (skill/scripts/scan.mjs, the RULES table) is exposed through eleven surfaces. They are independent: enable the ones you need, none conflict, and all enforce the same rules.
| Surface | What it does | How it gets installed |
|---|---|---|
CLI (stop-ai-slop bin) |
scans files, staged changes or a diff; --explain, --audit, baseline |
automatic with npm i -D stop-ai-slop |
| pre-commit hook + npm scripts | blocks every commit that adds slop | automatic: npx stop-ai-slop --install |
| OpenCode write-time plugin | rejects write/edit/multiedit at the moment of the write; shape-gates unknown tool names with write-shaped payloads |
manual: a one-line stub file (below) |
| Claude Code plugin | skill + PreToolUse hook (blocks Write/Edit) + PostToolUse hook (delivers findings for the written file back to the model via stderr, exit 2) |
automatic: /plugin marketplace add + /plugin install |
Agent skill (skill/SKILL.md) |
teaches the policy and the commands to any agent | automatic with the Claude Code plugin; a manual junction/symlink for OpenCode |
MCP server (--mcp) |
pull-mode slop_scan / slop_explain / slop_baseline for any MCP client |
manual: one config entry per client |
| GitHub Action / GitLab CI template | blocks PR/MR pipelines on the diff | manual: a workflow / CI snippet |
| VS Code task / IDEA File Watcher | on-demand or on-save scan inside the IDE | manual: a snippet / template import |
--install-hooks |
writes agent hook configs (.codex/hooks.json, .github/hooks/stop-ai-slop.json, .devin/hooks.v1.json) + prints settings snippets for Gemini CLI / Qwen Code; shape-gates UNKNOWN tool names |
manual: npx stop-ai-slop --install-hooks |
--install-rules |
generates agent instruction files from the RULES table (.cursor/rules/stop-ai-slop.mdc, .windsurfrules, CONVENTIONS.md, .clinerules, .devin/rules/stop-ai-slop.md, marked block in .github/copilot-instructions.md) |
manual: npx stop-ai-slop --install-rules |
Config JSON Schema (schema/stop-ai-slop.schema.json) |
draft-07 schema for .stop-ai-slop.yaml with IDE autocomplete and parity enforcement |
shipped in npm tarball; modeline at top of config file |
Nothing happens silently
npm i -D stop-ai-slop has no postinstall script: it only places files under node_modules. No hook, editor config or agent setting is touched until you run --install or add one of the snippets below yourself.
Which surfaces to pick
- Git-only teams — the two commands above; done.
- OpenCode users — add the plugin stub so slop is rejected at write time: create
~/.config/opencode/plugins/comment-gate.tswith one line,export { default } from "<path to node_modules>/stop-ai-slop/plugin/comment-gate.ts"(OpenCode 1.18.29+ and 2.x; on older 1.x useexport { CommentGate } from ...instead). - Claude Code users —
/plugin marketplace add WhiteBite/stop-ai-slopthen/plugin install stop-ai-slop; the skill and both hooks arrive automatically. - Any MCP client (Cursor, Codex, others) — add the stdio entry
npx stop-ai-slop --mcp(see "MCP server"). - CI — add the GitHub Action or the GitLab include (see "IDE and CI").
- Codex CLI / VS Code Copilot / Devin users —
npx stop-ai-slop --install-hookswrites.codex/hooks.json,.github/hooks/stop-ai-slop.json,.devin/hooks.v1.json; shape-gating keeps it safe for unknown tool names. - Cursor / Windsurf / Cline / Aider users —
npx stop-ai-slop --install-rulesgenerates instruction files from the RULES table; see "Agent hook integrations". - Gemini CLI / Qwen Code —
--install-hooksprints ready-to-paste settings snippets into.gemini/settings.jsonand.qwen/settings.json.
What it enforces, and why
Policy: a comment is at most one line and only states a non-obvious external constraint, an invariant, or a workaround. The retelling of a diff belongs in the commit message; the "why" of a test belongs in the test name. The rule table and the detector live in skill/scripts/scan.mjs (the RULES const) — change rules there; everything else only applies them.
Error rules block (exit 1; the write-time gate throws). Warning rules teach: they are printed but do not block (unless --strict).
Enforcement points
- OpenCode write-time plugin —
plugin/comment-gate.tsinterceptswrite/edit/multieditand rejects an edit that has error findings at the moment of the write; unknown or capitalized tool names whose payload is write-shaped (file_path+content,file_path+old_string+new_string,file_path+edits[], or patch text with*** Begin Patch) are evaluated by shape too, and a mutating-shaped call that cannot be evaluated is recorded in the audit log with verdictunevaluated. It applies the.stop-ai-slop.yamlof the edited file's repository root (resolved from the file, notprocess.cwd(), so a file in repo B is judged by repo B's policy even when OpenCode runs from repo A). Mounted into~/.config/opencode/plugins/via a re-export stub. The default export{ id: "stop-ai-slop", server: CommentGate, setup }serves both APIs: OpenCode 1.18.29+ callsserver()(v1 hooktool.execute.before), OpenCode 2.x callssetup()(registersctx.tool.hook("execute.before"), the only V2 hook that may reject a call); the legacy named exportCommentGatekeeps old stubs on older 1.x working. OpenCode displays local plugins by their stub file name — name the stubstop-ai-slop.tsinstead ofcomment-gate.tsif you want that label. - Pre-commit hook via
--install— one command weavesnode .../scan.mjs --stagedinto.git/hooks/pre-commit(idempotent: appends a marked block without clobbering an existing hook) and adds thestop-ai-slop/stop-ai-slop:allnpm scripts to package.json. The hook and npm scripts embed the absolute path to the scanner as of install time — after moving or re-cloning the scanner, run--installagain. - Agent skill —
skill/SKILL.md(name:stop-ai-slop): the policy, the rule table, the run modes. Mounts into OpenCode and Claude Code. - Baseline for legacy — 1)
--install, 2)--baseline-write(records current findings), 3) commit the baseline, 4) from then on the gate sees only new findings. Baseline v2 stores each finding as a pair of lines —relpath:lineplusfp:<hash>, the fingerprint being a SHA-256 hash (first 16 hex chars) of the rule id and the trimmed comment text — and matches by fingerprint, not by position: edits above a baselined line no longer resurrect legacy, while changed comment text surfaces as new slop. The same text pasted again is masked only up to the number of baselined occurrences, so a fresh copy of legacy slop still counts as new slop. Old v1 baselines (relpath:linelines only) keep masking by position until the next--baseline-write.--baseline-pruneremoves entries that have no live finding (in v2 the pair dies together); a repeated--baseline-writewould also amnesty new slop — do not do that.
Quick start
git clone https://github.com/WhiteBite/stop-ai-slop && cd stop-ai-slop
node skill/scripts/scan.mjs --self-test # detector sabotage test
node skill/scripts/scan.mjs scan . # full scan: git-tracked + untracked non-ignored files, generated/binary/artifacts skipped
node skill/scripts/scan.mjs --staged # only lines added in git diff --cached
node skill/scripts/scan.mjs --diff <ref> # added lines of tracked files relative to <ref>; untracked files are invisible
node skill/scripts/scan.mjs --fix --dry-run # preview the mechanical fixes as a unified diff, change nothing
node skill/scripts/scan.mjs --fix # apply the mechanical fixes, then rescan and report what is left
node skill/scripts/scan.mjs --strict # warnings also block the gate (exit 1)
node skill/scripts/scan.mjs --install # npm scripts + pre-commit hook in the current repo
node skill/scripts/scan.mjs --install --strict # same, but the hook runs --strict
node skill/scripts/scan.mjs --install-hooks # write agent hook configs (Codex CLI, VS Code Copilot local hooks, Devin CLI) + print settings snippets for Gemini CLI / Qwen Code
node skill/scripts/scan.mjs --install-rules # generate agent instruction files from the RULES table (.cursor/rules/.windsurfrules/CONVENTIONS.md/.clinerules/.devin/rules/, marked block in .github/copilot-instructions.md)
node skill/scripts/scan.mjs --baseline-write # record current findings into the baseline
node skill/scripts/scan.mjs --baseline-prune # remove baseline entries with no live finding
node skill/scripts/scan.mjs --doctor # environment diagnostics: node, git, pre-commit hook path, config, baseline
node skill/scripts/scan.mjs --help # help for all flags
Suppression directives: // stop-ai-slop-ignore-next-line [rule-id] (next line), // stop-ai-slop-ignore-line [rule-id] (current line), // stop-ai-slop-ignore-file (whole file); after -- give a reason. Directives are honored only in real comment segments — the same text inside a string literal does not suppress anything.
Exit 1 — there are error findings outside the baseline; otherwise 0. Exit 2 — a usage or git error (bad flag, nonexistent ref).
Output formats
By default — human-readable text with a final slop-gate: … line. The --format <text|json|sarif> flag (modes scan, --staged, --diff) switches stdout to a machine-readable format: only JSON is printed, the summary line is omitted. Exit codes do not depend on the format — still 0/1/2.
--format json — Reviewdog RDFormat: a single JSON object on one line (pipe-friendly), diagnostics sorted by path and line, an empty result is diagnostics: []:
npx stop-ai-slop --diff origin/main --format json | reviewdog -f=rdjson -reporter=github-pr-review
--format sarif — SARIF 2.1.0 with two-space indent; the rules array lists every rule regardless of findings, results only the real findings. Upload to GitHub code scanning:
- run: npx stop-ai-slop --diff origin/${{ github.base_ref }} --format sarif > results.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Output language
Messages are Russian by default; --lang en switches findings, --explain, --help, error messages, --fix, bench and audit output to English. Without the flag, the language is auto-detected: STOP_AI_SLOP_LANG > LC_ALL/LANG (anything not starting with ru selects English; unset — Russian). Rule ids and the instead: labels are English in both languages. The --install/--install-hooks/--install-rules output and the generated hook text remain Russian in this version.
Autofix (--fix)
--fix applies the deterministic, mechanical fixes across the whole scan scope in one pass — no agent, no LLM, no per-finding rewrite. Preview first, then apply:
node skill/scripts/scan.mjs --fix --dry-run # print a unified diff of every planned change, touch nothing
node skill/scripts/scan.mjs --fix # write the changes, then rescan and report what is left
--dry-run never writes: it prints a per-file unified diff (context 2) and a summary line запланировано N правок в M файлах; не чинится автоматически: K, exit 0. Apply mode writes the files, then rescans and prints the surviving findings through the normal text pipeline (exit 1 if an error finding remains outside the baseline, so it chains into CI). --strict also treats surviving warnings as a failure. The fixer is idempotent: a second --fix is a no-op.
Fixed mechanically (deletion or in-place shrink — "delete" is always an acceptable outcome per the policy):
| Rule | Fix |
|---|---|
multi-line-comment, vend/file-summary-header |
delete the whole comment run |
vend/section-divider |
delete the divider line (or strip it from an inline comment) |
changelog-marker, vend/cross-file-ref, vend/obvious-comment |
delete a full-line comment; strip the trailing comment off a code line |
vend/step-numbered |
strip the numbered-step prefix, keep the rest of the text (full-line and trailing inline comments) |
vend/zero-width-chars, vend/bidi-controls |
remove the real invisible/BiDi characters |
Not auto-fixed — they need a human or an agent to write a replacement, so --fix leaves them and reports them: long-comment (compress the meaning), vend/this-function-opener (rephrase as an invariant), vend/generic-todo (add a ticket), vend/markdown-in-comment (semantic), vend/cjk-noise (rewrite the identifier).
Safety rails: a block-comment opener is never deleted mid-block (only whole runs go); a real invisible character is stripped but its backslash-escape spelling in source (a BOM test literal) is left intact — that is the subject of the code, not slop; a legit emoji ZWJ sequence and a leading BOM are preserved; a stop-ai-slop-ignore-next-line directive is dropped together with the code it guarded only when that target line is itself deleted, so no directive is left dangling; suppression directives and the findings they cover are never touched. Generated, binary and git-ignored files are skipped exactly as in the scan.
For bulk semantic cleanup (the not-auto-fixed rules), the intended flow is: --fix first to clear the mechanical majority, then an agent pass over the residual report — or --baseline-write to grandfather what the team decides to keep.
Configuration
An optional .stop-ai-slop.yaml in the repository root (same place as the baseline: the git root, or the scan directory outside a repo). Read by the scan, --staged, --diff and --pre-tool modes and by the OpenCode write-time plugin, which resolves it from the edited file's git root (falling back to the file's directory outside a repo). The parser is a zero-dep YAML subset: key: value scalars, lists via - , a rules: section with two-space indent, an overrides: list of {paths, rules} entries, # comments and blank lines skipped, values may be quoted. Unknown keys are ignored; an invalid severity or an unknown rule id in rules/overrides is exit 2 with the file name and line number.
| Key | Meaning |
|---|---|
maxCommentLength |
comment-line length threshold for long-comment (default 120) |
excludePaths |
list of relative path prefixes: a path is excluded if it equals an entry or starts with entry/; works in the full scan and in diff modes |
generatedPaths |
list of relative path prefixes treated as generated (same prefix semantics as excludePaths); see Generated code |
scanGenerated |
true disables the generated-file exemption — generated code is linted like any other file |
rules |
severity override per rule id: error, warning or off (rule disabled) |
overrides |
list of per-path severity overrides, each {paths: [patterns], rules: {rule-id: severity}}; a pattern without * is a path prefix (same semantics as excludePaths), * matches any characters except /, ** matches any characters including /; for a matching file the last matching entry wins, and an override beats the global rules remap |
ticketPattern |
regex source enabling vend/ticket-ref (e.g. '\bKRY-\d+\b'); without the key the rule is inert — a bare ticket reference in a comment is flagged, while TODO lines, tracker links and CVE-/GHSA- advisories are exempt |
maxCommentLength: 100
excludePaths:
- generated
- docs/api.md
rules:
multi-line-comment: off
vend/step-numbered: error
overrides:
- paths:
- "*.yaml"
- ".github/**/*.yml"
rules:
multi-line-comment: warning
Severity remap is applied after detection and before baseline filtering and exit-code computation; per-path overrides are applied on top of the global rules remap — for a matching file the last matching entry wins, and off removes the finding. The baseline matches on the finding fingerprint (rule id + trimmed text; on rel:line for old v1 baselines) regardless of severity, so changing a severity in the config neither resurrects nor masks baselined findings.
Config JSON Schema
schema/stop-ai-slop.schema.json (draft-07, shipped in the npm tarball). Modeline at top of .stop-ai-slop.yaml:
# yaml-language-server: $schema=https://raw.githubusercontent.com/WhiteBite/stop-ai-slop/v2.0.0/schema/stop-ai-slop.schema.json
(or node_modules path ./node_modules/stop-ai-slop/schema/stop-ai-slop.schema.json). Parity with the parser is enforced by a self-test gate (schema-parity-config / schema-parity-rules).
MCP server
The --mcp flag runs stop-ai-slop as an MCP server over stdio (JSON-RPC 2.0). This is pull-mode: any MCP client calls the scanner itself before editing a file.
npx stop-ai-slop --mcp
The server supports protocols 2024-11-05, 2025-11-25 and 2026-07-28 — the version is negotiated on initialize. JSON-RPC batch requests (an array in) are answered with an array of responses. stdout carries only protocol messages; logs go to stderr. slop_scan resolves .stop-ai-slop.yaml and the baseline from the git root of the scanned path, not from the server's cwd.
Three tools:
| Tool | Arguments | Description |
|---|---|---|
slop_scan |
{ path?: string } |
Full scan of a directory or file; returns text findings |
slop_explain |
{ ruleId: string } |
Returns a rule's rationale (Why / Instead of / Write / Ignore it when) |
slop_baseline |
{} |
Prints the baseline entries (relpath:line and fp:<hash> lines) |
Client setup:
Claude Code (.mcp.json):
{
"mcpServers": {
"stop-ai-slop": {
"command": "npx",
"args": ["stop-ai-slop", "--mcp"]
}
}
}
OpenCode / Cursor (stdio entry in the configuration):
{
"mcpServers": {
"stop-ai-slop": {
"command": "npx",
"args": ["stop-ai-slop", "--mcp"]
}
}
}
Blocking before the write (Claude Code)
The --pre-tool flag reads a PreToolUse JSON payload from stdin ({ tool_name, tool_input }), scans the proposed delta content (the Write content, or the Edit difference — new_string minus old_string), and on error findings prints them to stderr and exits with code 2 — Claude Code cancels the tool call and shows the reason to the model. A clean payload exits 0 with no output.
The plugin already carries the hook (hooks/hooks.json, matcher Write|Edit), so a marketplace install gets it automatically. For manual setup, add to .claude/settings.json:
{
"hooks": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node \"<path to repo>/skill/scripts/scan.mjs\" --pre-tool"
}
]
}
]
}
The alternative decision form via hookSpecificOutput.permissionDecision (deny) exists, but this hook uses exit 2 + stderr to surface multi-line findings.
The plugin also ships a SessionStart hook (no matcher — startup, resume and clear) running scan.mjs --policy, which states the one-line comment policy and the per-rule summary up front in the session context, so most edits never trip the gate.
For agent-driven repair, the scan, --staged, --diff and --stdin-path modes accept --fix-suggestions: after the normal output the scanner prints one extra line, fix-suggestions: <single-line JSON array>, listing the exact edits --fix would apply — one {"file","line","rule","kind","from","to"} entry per mechanically fixable finding (kind is delete-line, replace-line or delete-run; findings that need a human rewrite, like long-comment, get no entry). With --format json or --format sarif the line goes to stderr, so stdout stays machine-parseable.
fix-suggestions: [{"file":"src/a.ts","line":3,"rule":"changelog-marker","kind":"delete-line","from":"// this fixes the cache miss","to":null}]
Agent hook integrations
--pre-tool generalization
Beyond Claude Code's Write/Edit/MultiEdit, --pre-tool now understands:
- Gemini CLI / Qwen Code — tools
write_file{file_path,content} andreplace{file_path,old_string,new_string}. - OpenAI Codex CLI —
apply_patch:tool_input.commandcarries a V4A patch (*** Begin Patch/*** Add File:/*** Update File:/*** Move to:/*** Delete File:/*** End Patch,+lines= added content); multi-file patches are scanned per file. - UNKNOWN tool names — gated by SHAPE: payload with
file_path+content, orfile_path+old_string+new_string, orfile_path+edits[], or patch text containing*** Begin Patch. This covers VS Code Copilot local hooks (Preview feature) and Devin CLI whose tool names are not a stable API. Read-only tool names (containingread/view/grep/search/glob/list/ls/bash/shell/exec/run/fetch/web/think/todo/plan) never gate.
--install-hooks
Writes agent hook configs invoking node <abs>/scan.mjs --pre-tool:
.codex/hooks.json— Codex CLI, Claude-style envelope; matcherWrite|Edit|MultiEdit|write_file|replace|apply_patch; merges with existing entries, never clobbers foreign hooks; broken JSON skipped with warning..github/hooks/stop-ai-slop.json— VS Code Copilot local hooks (Preview feature); own file rewritten each run; no matcher in that format — shape-gating keeps it safe; timeout 30s..devin/hooks.v1.json— Devin CLI, no matcher → all tools → shape-gating. Idempotent; refreshes the embedded scanner path in place.
Prints ready-to-paste snippets for Gemini CLI (.gemini/settings.json, hooks.BeforeTool, matcher write_file|replace, timeout ms) and Qwen Code (.qwen/settings.json, hooks.PreToolUse) — settings.json is user-global so it is never auto-edited.
--install-rules
Generates agent instruction files from the RULES table (single source of truth):
.cursor/rules/stop-ai-slop.mdc— Cursor; frontmatterdescription/globs/alwaysApply..windsurfrules— Windsurf.CONVENTIONS.md— Aider; pair with--read CONVENTIONS.md..clinerules— Cline..devin/rules/stop-ai-slop.md— Devin.- Marked block inside
.github/copilot-instructions.md— VS Code Copilot; foreign content preserved, block replaced in place.
Files owned by us are overwritten; shared-name files WITHOUT our first-line marker are never clobbered (skipped with a warning). Generated files pass our own scanner.
Tool coverage summary
| Agent | Integration |
|---|---|
| Claude Code | PreToolUse exit 2 (see "Blocking before the write") |
| Codex CLI | .codex/hooks.json (matcher `write_file |
| VS Code Copilot | Local hooks (Preview), .github/hooks/stop-ai-slop.json |
| Devin CLI | .devin/hooks.v1.json (also auto-reads .claude/ hooks) |
| Gemini CLI / Qwen Code | Printed settings snippets (.gemini/settings.json / .qwen/settings.json) |
| OpenCode | Write-time plugin (see "Enforcement points") |
| Cursor / Windsurf / Cline / Roo / Aider / Goose / OpenHands / Codex / Copilot | AGENTS.md or their rules files → --install-rules + our AGENTS.md-compatible skill cover them |
| MCP clients (Cursor, Zed, JetBrains AI, Cody, …) | Existing --mcp |
IDE and CI
VS Code
The extension in editors/vscode/ is the primary path: copy or link that directory into ~/.vscode/extensions/whitebite.stop-ai-slop-0.1.0/, reload the window, and set stopAiSlop.command to your scanner invocation (default npx stop-ai-slop). Findings land in the Problems panel on save and via the stop-ai-slop: Scan the current file / Scan the workspace commands; see editors/vscode/README.md.
The zero-install alternative is a tasks.json task with a problem matcher to highlight findings in the Problems panel:
{
"version": "2.0.0",
"tasks": [
{
"label": "stop-ai-slop scan",
"type": "shell",
"command": "npx stop-ai-slop scan",
"problemMatcher": {
"owner": "external",
"source": "stop-ai-slop",
"severity": "error",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": {
"regexp": "^(.+):(\\d+)\\s+(\\S+)\\s+\\[(error|warning)\\]\\s+(.+)$",
"file": 1,
"line": 2,
"code": 3,
"severity": 4,
"message": 5
}
},
"presentation": {
"reveal": "always",
"panel": "new"
}
}
]
}
One-line pattern: nested instead: lines simply do not match. Run from the terminal or bind to a hotkey.
GitLab CI
The templates/stop-ai-slop.gitlab-ci.yml template blocks MR pipelines on the diff (exit code); no code-quality report is shipped. Wire it in:
include:
- project: 'WhiteBite/stop-ai-slop'
file: '/templates/stop-ai-slop.gitlab-ci.yml'
ref: <tag>
Bitbucket Pipelines
pipelines:
pull-requests:
"**":
- step:
image: node:20
script:
- npx stop-ai-slop --diff origin/$BITBUCKET_PR_DESTINATION_BRANCH
CI (GitHub Actions)
on: pull_request:
jobs:
slop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: WhiteBite/stop-ai-slop@main
with:
base: ${{ github.base_ref }}
The action fetches the base ref itself, so a standard shallow checkout is enough; strict: "true" enables warnings-as-errors; format: "json" or format: "sarif" switches the action output to a machine-readable format (see "Output formats"). The action fails when no base ref is resolvable (a push event without the base input) instead of scanning an empty diff — use pull_request or pass base explicitly. The annotations input (default "true") adds --annotations to the text-format scan: every finding is also emitted as a GitHub Actions workflow command (::error file=<rel>,line=<n>::… / ::warning …) and shows up inline on the PR diff; with format: "json" or "sarif" the flag is a no-op.
Publishing to npm
Bootstrap, once: the first publish of a new package needs interactive confirmation — npm publish in a terminal: npm either asks for an OTP (if 2FA is on) or offers browser-approve ("Authenticate your account at …"), which is enough without an OTP and without 2FA; the third path is a granular token with bypass-2FA in ~/.npmrc. Right after it: npmjs.com → package Settings → Trusted publishing → add WhiteBite/stop-ai-slop and the publish.yml workflow; if that form requires enabling 2FA, that is the only place it is mandatory for fully automatic tags.
After that, deploy is tag-driven: bump the version in package.json + add a CHANGELOG entry, commit, git tag vX.Y.Z && git push origin main --tags. The .github/workflows/publish.yml workflow (trigger push: tags: v*) runs the self-test, skips the publish if that version is already in the registry (tag arrival order does not matter), and publishes via OIDC with provenance. Node 24 in the workflow is required: OIDC publishing needs npm CLI >= 11.5.1. After a release, update Formula/stop-ai-slop.rb: point url at the new version's npm tarball and recompute sha256 — the tarball hash changes with every version.
The same workflow also creates the GitHub Release for the tag (notes taken from the matching CHANGELOG.md section, skipped when the release already exists) and mirrors the package to GitHub Packages as @whitebite/stop-ai-slop (the GitHub npm registry only accepts scoped names; the mirror is skipped when that version is already there). Consumers of the mirror configure the registry per scope:
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
@whitebite:registry=https://npm.pkg.github.com
Mounting on another machine
git clone https://github.com/WhiteBite/stop-ai-slop <path>
New-Item -ItemType Junction -Path "$env:USERPROFILE\.config\opencode\skills\stop-ai-slop" -Target "<path>\skill"
New-Item -ItemType Junction -Path "$env:USERPROFILE\.claude\skills\stop-ai-slop" -Target "<path>\skill"
OpenCode write-time plugin: a file %USERPROFILE%\.config\opencode\plugins\comment-gate.ts with the single lineexport { default } from "<path>/plugin/comment-gate.ts". The default-export stub requires OpenCode 1.18.29 or newer (including 2.x); on older 1.x releases use the legacy one-liner export { CommentGate } from "<path>/plugin/comment-gate.ts".
The cmd.exe equivalent is mklink /J; on Linux/macOS use ln -s. In any git repo without agents, scan.mjs --install works.
Other integrations
pre-commit framework
repos:
- repo: https://github.com/WhiteBite/stop-ai-slop
rev: v2.0.0
hooks:
- id: stop-ai-slop
The hook runs --staged on every commit.
Claude Code / Cursor / Codex
The repository is a ready Claude Code plugin marketplace:
/plugin marketplace add WhiteBite/stop-ai-slop
/plugin install stop-ai-slop
The plugin carries the skill and a PostToolUse hook (Write|Edit → scan.mjs --stdin-path) that scans the just-written file and, when the gate fails, prints the findings to stderr and exits 2 — Claude Code delivers them back to the model. For Codex CLI, run npx stop-ai-slop --install-hooks to write .codex/hooks.json (see "Agent hook integrations") instead of copying the Claude hook by hand. Cursor has no public write-time hook surface yet — use npx stop-ai-slop --install-rules to drop the policy into .cursor/rules/.
IntelliJ IDEA
File Watchers (Ultimate)
The idea/filewatchers/stop-ai-slop.xml template imports via Settings → Tools → File Watchers → + → Import. After importing, replace <path-to-scan.mjs> with the absolute path to skill/scripts/scan.mjs on your machine. The template targets the common types (Kotlin, Java, TypeScript, JavaScript, Python, YAML); other scanner profiles are covered by External Tools or the CLI scan. Runs on every file change; scan exclusions are the standard artifact directories (venv, node_modules, .git, build, target, .next, out, Pods, site-packages, .dart_tool, .gradle).
Findings appear in the Run window with clickable paths, because the scanner output format is file:line.
Actions on Save (all IDEA 2024+ editions)
There is no built-in support for external commands. Two working options:
- External Tools (Settings → Tools → External Tools → +): program =
node, arguments =<path>/scan.mjs scan $FilePath$, working directory =$FileDir$. Trigger manually or via the Save Actions plugin. - File Watcher (see above) — the only on-save option without third-party plugins; available only in Ultimate.
Debugging
The OpenCode plugin writes every gate decision to a JSONL log (~/.config/opencode/logs/comment-gate.jsonl, path overridden by the STOP_AI_SLOP_LOG variable): loaded, blocked and passed events with the tool, file and rules. Inspect:
node skill/scripts/scan.mjs --audit # counters + last 20 entries
node skill/scripts/scan.mjs --audit 50 # last 50
node skill/scripts/scan.mjs --doctor diagnoses the environment in one pass — node version, git on PATH, the pre-commit hook and its embedded scanner path (the moved-scanner case above), the npm scripts, .stop-ai-slop.yaml validity, the baseline and the OpenCode plugin stub — and exits 1 naming the broken piece; ! lines are informational and never fail.
The plugin is loaded by the OpenCode process at session start: after editing plugin/comment-gate.ts, restart OpenCode, otherwise the old version runs (the audit log shows this immediately by the absence of new entries).
Language profiles
Comment syntax comes from a language profile, not a single shared list: # is a comment in .py/.sh/.yaml but a preprocessor directive in .c and an attribute in .rs. 160 extensions and 26 file names are supported: Dockerfile, Containerfile, Makefile, GNUmakefile, Justfile, Rakefile, Vagrantfile, Gemfile, CMakeLists.txt, Jenkinsfile, BUILD, BUILD.bazel, WORKSPACE, WORKSPACE.bazel, meson.build, SConstruct, SConscript, Pipfile, Procfile, .env, .gitignore, .dockerignore, .npmignore, .gitattributes, .gitmodules, .editorconfig. Matching: exact file name → extension → name prefix, so suffixed variants (Dockerfile.dev, Makefile.am) are detected by prefix, while build.gradle stays c-family — a bare BUILD does not capture it.
| Profile | Line comment | Block / doc | Examples |
|---|---|---|---|
| c-family | // |
/* */, /** */, {/* */} |
ts, js, kt, java, rs, cs, c, cpp, swift, dart, scala, mts, cts, sol, v, sv, qml, styl, res |
| go | // |
/* */, /** */, {/* */} |
go (doc-comment convention: a // run above a declaration whose first word names it is exempt from multi-line/header) |
| css | //, /* |
/* */ |
css, scss, less |
| py | # |
""" / ''' |
py, pyi, vy |
| hash | # |
— | rb, sh, yaml, toml, ex, raku, awk, go.mod, go.sum, Dockerfile, Makefile, .gitignore |
| gherkin | # |
— | feature (Gherkin: multi-line and summary-header exempt; changelog/step/markdown still apply) |
| hashblock | #, /* |
/* */ |
nix, hcl, tf, tfvars |
| powershell | # |
<# #> |
ps1, psm1 |
| julia | # |
#= =# |
jl |
| nim | # |
#[ ]# |
nim |
| sql | --, # |
/* */ |
sql, plsql, pks, pkb |
| dash | -- |
— | vhd, vhdl, adb, ads |
| lua / haskell | -- |
--[[ ]] / {- -} |
lua, hs, elm, purs, idr, agda, dhall |
| lisp | ; |
— | clj, el, scm, rkt |
| percent | % |
— | tex, bib, erl |
| fortran / vb / batch / vim | ! / ', REM / ::, REM / " |
— | f90, vb, bat, vim |
| rst | .. |
— | rst |
| markup | <!-- |
<!-- --> |
html, xml, svg, md, xsl |
| mdx | <!-- |
<!-- -->, {/* */} |
mdx |
| vue | //, /*, <!-- |
/* */, {/* */}, <!-- --> |
vue, svelte, astro |
| ocaml | (* |
(* *) |
ml, mli |
| php | //, # |
/* */, /** */ |
php |
| pascal | //, (* |
(* *), /// doc |
pas, pp, fs (F#) |
| coffee | # |
### ### |
coffee, litcoffee |
| adoc | // |
//// //// |
adoc, asciidoc |
| handlebars | {{!, <!-- |
{{!-- --}}, <!-- --> |
hbs |
| gotmpl | {{/* |
{{/* */}} |
tpl, gotmpl, gohtml, tmpl |
| ini / properties | ;, # / #, ! |
— | ini, properties, .editorconfig |
.m is not scanned: the extension is ambiguous (Objective-C vs MATLAB). .pp is also ambiguous (Puppet vs Pascal) — marked as pascal. Not scanned: COBOL, assembly (.asm/.s), .ahk, .ipynb, server-side engine templates (.erb, .ejs, .jsp, .cshtml, .razor, .twig, .blade.php, .pug, .haml). A new language is added with one row in the scan.mjs profile table.
Rules
| Rule | Severity | What it catches |
|---|---|---|
multi-line-comment |
error | a comment spans 2+ consecutive lines (doc-blocks and /// doc-comment runs exempt) |
changelog-marker |
error | the comment retells the diff (a pair of changelog markers or a strong marker, ru/en/de/fr/es) |
long-comment |
error | a comment line longer than 120 characters |
vend/step-numbered |
warning | a numbered step in a comment (Step N / Шаг N / N., any language marker) |
vend/section-divider |
warning | a divider line made of -=#* characters |
vend/markdown-in-comment |
warning | markdown syntax inside a comment (**, -, |) |
vend/this-function-opener |
warning | a comment starts with "This function/…", «Эта функция/…», «Diese Funktion…», «Cette fonction…» or «Esta función…» |
vend/file-summary-header |
warning | a 2+ line summary-header comment at the top of a file |
vend/generic-todo |
warning | a TODO/FIXME/XXX without a ticket link (any case) |
vend/ticket-ref |
warning | a bare ticket reference in a comment — the change context belongs in the commit message or PR, not the code |
vend/cross-file-ref |
warning | a pointer to another file/line in a comment (handler.py:147) |
vend/obvious-comment |
warning | a single-line comment restates the code line beneath it |
vend/ai-plan-narration |
warning | a comment narrates the agent's own work process (plan/spec/task references, instruction acknowledgements) |
vend/ai-vocab-density |
warning | 3+ distinct AI-vocabulary words (delve, pivotal, tapestry...) in the file's comments |
vend/research-citation |
warning | a research citation in a comment — code is not a bibliography |
vend/self-suppression |
warning | a suppression directive without a rule list arrived together with the code it suppresses |
vend/cjk-noise |
warning | CJK characters glued to Latin letters or digits in the code part of a line (a generation artifact) |
vend/zero-width-chars |
error | an invisible zero-width character (U+200B, U+200C, U+200D, U+2060, U+FEFF) |
vend/bidi-controls |
error | BiDi controls (U+202A-U+202E, U+2066-U+2069) on any line, and directional marks (U+200E, U+200F) in code |
Error rules do not apply to doc-blocks (JSDoc /** … */, Python docstrings, and /// doc-comment lines — dartdoc, rustdoc, C# XML doc): contract documentation for classes and functions may be any length. Inside doc-blocks, changelog markers (error) and signature restatement "This function…" (warning) are still caught. A long-comment line whose text carries a why-marker (because, since, otherwise, workaround, to avoid, by design, trade-off, e.g. — plus RU/DE/FR/ES equivalents) is not flagged: a long single-line why-comment is legitimate, a multi-line narrative is not. The same why-marker exempts a 2-line comment run (a wrapped why that would fit one line); 3+ lines are still flagged. Gherkin .feature specs exempt multi-line-comment and vend/file-summary-header entirely — scenario narration and header blocks there are a normal form — while the other slop rules (changelog-marker, step-numbered, markdown-in-comment) keep firing.
Text rules (step-numbered, markdown-in-comment, this-function-opener, cross-file-ref) are matched on the text after the comment marker is stripped, so they work in every profile — # Шаг 3 in yaml and -- Step 3 in sql are caught identically. step-numbered, this-function-opener and changelog-marker understand RU + EN + DE + FR + ES ("Шаг N", "Schritt N", "Étape N", "Diese Funktion", "au lieu de", "ya no", etc.); other natural languages are not covered. Structural rules (multi-line, divider, header, todo) do not depend on the wording language. step-numbered and markdown-in-comment do not fire inside doc-blocks. vend/obvious-comment is additionally gated by a per-file density filter: a file's obvious-comment findings are dropped unless they make up at least 2% of its non-empty, non-comment lines — hand-written files carry a few terse comments, while generated files narrate every few lines.
Full rationale per rule (Why / Instead of / Write / Ignore it when, from the same RULES table):
node skill/scripts/scan.mjs --explain <rule-id>
Rule ids and service labels are EN; messages and rationales are RU. The vend/ prefix = rules vendored from external pattern catalogs.
The detector sees inline comments after code (const x = 1 // was), block comments without a marker on middle lines, doc-blocks of any length (contract JSDoc/docstrings////), UTF-16 files with a BOM; zero-width characters (U+200B–U+200F, U+FEFF) are stripped when matching markers; vend/zero-width-chars flags U+200B, U+200C, U+2060, U+FEFF and a non-emoji U+200D on raw lines, and vend/bidi-controls flags the BiDi overrides and isolates (U+202A–U+202E, U+2066–U+2069) on raw lines including inside comments, while the directional marks U+200E/U+200F are flagged only in the code part of a line — they are legitimate RTL typography in comments and prose. Escape forms in the source are flagged too; a ZWJ inside emoji sequences and a BOM at position 0 are not flagged. CJK adjacency to Latin letters or digits is checked only in the code part of a line: Chinese comments and i18n strings without Latin adjacency are legitimate. In prose formats (.md/.mdx/.html/.xml/.rst/.adoc) CJK adjacency is not checked at all: mixed JP/CN prose with Latin brand names is normal there. Not scanned: languages without a profile (see the table above; .m is ambiguous), binary and office formats; --staged and --diff do not see untracked files. Warnings do not block the gate unless --strict is given. Non-ASCII file names are supported in diff modes.
What the full scan walks. Inside a git repository, scan enumerates files via git ls-files --cached --others --exclude-standard, so everything git-ignored is invisible: __pycache__, .venv, build outputs, vendored trees, downloaded toolchains, packaged runtimes, minified bundles, compiled .dart.js. Outside a repo it falls back to a directory walk. On top of that, hard-coded artifact directories are always skipped (venv, build, .next, target, out, .gradle, Pods, __pycache__, .idea, .codegraph, site-packages, .dart_tool, coverage, .git, node_modules, dist).
Generated files are exempt from the slop rules in every mode (full scan, diff, write-time gate) — detection layers, user signals and knobs are in Generated code. A file whose first 8 KB contain a NUL byte is treated as binary and skipped (UTF-16 with a BOM is decoded first, so it is not mistaken for binary). YAML block scalars (key: |, - >) are string content, not comments: their lines are never flagged.
License headers are exempt from the multi-line rule, and a shebang line never merges with the comment line that follows it. In .go files, a run of 2+ // comment lines directly above a func/type/const/var declaration (methods included) whose first word names the declared identifier is a Go doc comment — exempt from multi-line-comment and vend/file-summary-header like a doc-block; the other rules still apply. Inline comments are detected by the profile markers (//, #, --, %, ;, !) except py/fs floor division (//). A directory argument that lies outside the current repository is walked directly (the git listing only covers the repo itself).
Generated code
Generated files are exempt from the slop rules in every mode (full scan, diff, write-time gate). Three detection layers, any one is enough:
- Unique file-name suffixes nobody writes by hand — build_runner (
*.g.dart,*.freezed.dart,*.gr.dart,*.chopper.dart,*.pb*.dart), protoc (*_pb2.py,*_pb2.pyi,*_pb.go,*_grpc.pb.go,*.pb.cc/h/hpp/cpp,*.pb.mojom.*), Kuberneteszz_generated.*, stringer*_string.go, sqlc (*.sql.go,*.querier.go), .NET (*.Designer.cs,*.g.i.cs,*AssemblyAttributes.cs,GlobalUsings.g.cs), bundles (*.min.js,*.min.css,*.bundle.js). Ambiguous names (*.gen.go,*.generated.ts,*_Factory.java,*.g.cs,mock_*.go,*.d.ts) are deliberately not name-exempt — too many false positives; they are exempt only through a header below. - Tool-named headers in the first 10 lines:
@generated,Code generated by … DO NOT EDIT,generated code - do not modify by hand,<auto-generated,automatically generated by rust-bindgen,@generated by prost-build,@javax.annotation.Generated(,generated by openapi-generator,code generated by sqlc. - Ad-hoc generators: both
generat…/codegenanddo not edit/modifywithin the first 10 lines. A bareDO NOT EDITwithout a generator word does not exempt — hand-written policy files stay linted.
Two user signals sit on top of the layers: a .gitattributes line with linguist-generated (the GitHub linguist convention) and the generatedPaths list in .stop-ai-slop.yaml (same prefix semantics as excludePaths).
Semantics: on a generated file the slop rules are exempt, but the security rules vend/zero-width-chars, vend/bidi-controls and vend/cjk-noise still fire — poisoned codegen is a supply-chain signal, not a style issue. The write-time gate follows the same rule: generated files are exempt from the slop rules, but the security rules still fire on them. Set scanGenerated: true in .stop-ai-slop.yaml to lint generated code like any other file.
Bench (FP regression cohort)
--bench pins a cohort of 8 mature open-source repositories (expressjs/express, pallets/flask, gin-gonic/gin, tokio-rs/tokio, rack/rack, redis/redis, PowerShell/PowerShell, vuejs/vue — each pinned in the BENCH_COHORT table in skill/scripts/scan.mjs to a commit dated before 2025-01-01), scans every one with the raw rule pipeline (no config, no baseline), and counts findings per rule id. Comparing the counts against the committed bench-history.json catches false-positive regressions: a rule whose count grew on a pinned tree means a rule change introduced new false positives. Counts only — no scoring.
node skill/scripts/scan.mjs --bench # per-repo per-rule counts table + delta vs bench-history.json
node skill/scripts/scan.mjs --bench-write # overwrite bench-history.json with the current counts (exit 0)
Semantics: any increase of a rule's count on any repo is a regression — --bench prints the delta entries and exits 1; decreases and equal counts are fine (exit 0). A missing bench-history.json is treated as empty history (every finding counts as an increase), with a hint to run --bench-write. The history file is committed to this repository — it is the pinned FP baseline.
The first run needs network and git: each repo is fetched once (git init + git fetch --depth 1 <sha>) into ~/.cache/stop-ai-slop/bench/<owner>--<name> (override with the STOP_AI_SLOP_BENCH_CACHE environment variable). Later runs reuse the cache; a cached checkout at the wrong SHA is re-fetched. A fetch failure exits 2.
Refreshing the cohort: edit BENCH_COHORT (repo + pinned SHA), re-run --bench-write, and review the resulting bench-history.json delta in the PR — every count increase must be explainable as a true positive, otherwise the rule change is a false-positive regression.
DetBench (recall regression corpus)
--detbench runs the detector over detbench-corpus.json — a committed corpus of real agent comment slop mined from the write-time gate's audit log (the findings of every blocked event). The bench cohort pins false positives on human code; detbench pins recall on real agent output: a rule change that stops firing on a corpus entry fails the mode with exit 1. Each entry stores the source file name (for profile resolution), the reconstructed line run, and the rule that must fire.
node skill/scripts/scan.mjs --detbench
Corpus semantics: a multi-line-comment finding is recorded as its line repeated three times — runs of 3+ lines always flag, so the reconstruction is immune to the two-line wrapped-why exemption; single-line rules are recorded as-is. Only error rules appear: the write-time gate blocks error findings only, so warnings never reach the audit log. long-comment findings cannot be mined — the audit log truncates the recorded text at 120 characters, and a truncated line can never exceed the length limit again.
Regeneration: node skill/scripts/gen-detbench.mjs --write re-mines the local audit log (STOP_AI_SLOP_LOG overrides the path), keeps only entries that fire today, and reports the dropped ones; --check (CI-safe — it never reads the log) fails when a committed entry stops firing, the recall-regression tripwire, or when shape or sort order drifts; --normalize re-sorts the file after manual edits. The corpus path resolves from the git root (STOP_AI_SLOP_DETBENCH overrides). Refresh discipline mirrors --bench-write: regenerate, review the diff — corpus texts derive from real blocked edits, so rewrite identifying content before committing — and commit.
The current corpus was mined 2026-10-15 from one machine's audit log: 180 blocked write-time edits → 181 unique entries after deduplication, all multi-line-comment.
Playground
A zero-backend page that runs the real detector (skill/scripts/src/detect.mjs) in the browser: Vite bundles the actual modules, nothing is vendored. The built page is committed at playground/dist/index.html and works offline, including opened straight from disk (file://).
cd playground && npm install && npm run build
Then open playground/dist/index.html. The playground is a devDependency-isolated demo: it is not part of the shipped npm package (the files list does not include playground/). GitHub Pages deployment is a future step.
Comparison with alternatives
Facts from the competitors' READMEs, GitHub metadata and npm download counts, checked 2026-09-29. Traction = GitHub stars and npm downloads per month on the check date.
Dedicated slop linters
| Tool | What it scans | Languages / natural languages | Blocks at write | Gate model | Custom rules | Runtime | Traction |
|---|---|---|---|---|---|---|---|
| stop-ai-slop | comments in code, 16 rules | 160 extensions, 26 file names, 34 profiles / RU, EN, DE, FR, ES; messages RU or EN (--lang) |
yes — OpenCode plugin, Claude Code, Codex, Gemini, Qwen, Devin hooks | policy: rule → exit 1; errors vs warnings | RULES table in one file; severity remap; --explain |
Node >= 18, zero deps, one .mjs scanner | 449 dl/mo |
| windbag | change-narration comments, 6 rules (HISTORY_NARRATION, HEDGE_LANGUAGE, TICKET_ID, CROSS_FILE_REF, VERBOSE_COMMENT, OBVIOUS_COMMENT) | Python, JS/TS, Terraform, Rust, Go, Java, SQL (dbt/SQLMesh), YAML/HTML/MD / EN | Claude Code PostToolUse hook; pre-commit; no OpenCode | error rules fail the check, warn rules report | none documented | Rust binary shipped as a PyPI wheel | 25★ |
| aislop | code slop: 50+ rules — narrative comments, swallowed exceptions, as any, dead code, hallucinated imports |
10 targets (TS, JS, Expo/RN, Python, Go, Rust, Ruby, PHP, C#, C/C++) / EN | hooks for 10 agents (Claude, Cursor, Gemini, Pi, Codex, Windsurf, Cline, Kilocode, Antigravity, Copilot); no OpenCode | score 0–100, failBelow; CI mode; SARIF; MCP server | per-rule severity; new rules only in their repo | npm + optional engines (biome, ruff, oxlint); PyPI; Homebrew | 655★, 47k dl/mo |
| slop-scan | AI-associated code patterns; hotspots; repo comparison | JS/TS / EN | no | score + normalized metrics (per KLOC, per function) | config and plugins | npm | 319★, 35k dl/mo |
| anti-slop | low-evidence code patterns, an Oxlint ruleset meant to be vendored | TS/JS / EN | no | rule → error (Oxlint) | vendored by design — edit your copy | Oxlint plugin; no npm package | 5.0k★ |
| AI-SLOP-Detector | "fake-done" code: 27 checks — empty stubs, unresolvable imports, dead pipelines, buzzword-padded docs | Python first; JS/TS and Go extras / EN | no | 0–100 risk score per file; soft/hard/quarantine CI gates; MCP | domain presets; local calibration | Python (PyPI), offline, deterministic; VS Code extension | 96★ |
| gptlint | best-practice violations judged by an LLM; rules authored in markdown | JS/TS (MVP) / any language the LLM reads | no | LLM verdict; eslint-style CLI and config | custom rules are first-class (markdown) | npm + LLM API keys or a local model; caching | 297★, 138 dl/mo |
| dotnet-slopwatch | LLM reward hacking: disabled tests, suppressed warnings, swallowed exceptions, masking delays | .NET / EN | Claude Code hook; CI | rule → block | — | .NET tool (NuGet) | 110★ |
| grain | AI code patterns as an agentic repair queue (JSON violations, multi-session worklog) | Python / EN | no | rule → error; fixable flag per violation | — | Python | 34★, idle since 2026-04 |
| sloppylint | over-engineering, hallucinations, dead code | Python / EN | no | findings report | — | Python | 90★, idle since 2025-12 |
| ai-slop-linter | prose: commit messages, PR descriptions, docs; 21 tells | any text / EN tells; their own benchmark: the em dash hits correct Russian prose 24 times per 1000 words | no — commit-msg hook and commitlint rule gate the message at commit time | weighted score per 1000 words; SARIF | ignore/only per file | npm, zero runtime deps | 1.6k dl/mo |
| vibecheck-slop-stopper | 78 grep rules across all slop categories | 9 stacks / EN | no — GitHub Action, CLI; the Claude Code skill asks the LLM to run the patterns via its Grep tool | severity levels | rules.toml | Python + ripgrep (the skill itself: no deps) | 0★, idle since 2026-04 |
Smaller and newer tools
Checked the same date, one line each: dannote/sloplint (AST-based, multilingual; idle since 2026-02), bibekmhj/sloplint (the first JVM AI-slop linter), rbaumier/comply, thrash-d/slop-linter (Vale rules + a Claude Code hook for prose and code comments), almcc/slop-linter (a Jev LLM classifier), Aaryan-9/ai-slop-remover (comment noise among its detectors), agiwhitelist/auteur (1035★ — a website-directing skill whose ship-gate embeds an anti-slop linter; design domain), mattpocock/slopwatch (51★, undocumented), LanNguyenSi/agent-dx (a monorepo toolkit whose slop-detector lints PRs).
Adjacent approaches
SaaS review bots: CodeRabbit ships a named "Slop Detection" — PR-level AI-spam triage: early-access, non-blocking, exempts your own members, from $24/dev/mo. Greptile, Graphite Diamond and Qodo ship no slop feature — a comment policy can only ride their custom LLM rules. Codacy and SonarQube wrap community linters: commented-out code (S125) and TODO tags (S1135), no narrative detection. Classic linters (ESLint no-warning-comments and friends, Biome, Checkstyle, ktlint, detekt, godot/revive) enforce comment style — position, case, punctuation, TODO vocabulary — and none detect changelog narration or step numbering; the closest are eslint-plugin-write-good-comments (prose quality inside comments) and Vale (tree-sitter comment extraction in 28 languages with style packages — no structural slop rules). Community agent skills (dashed/claude-marketplace comment-slop, kubosho/anti-slop-comment, manutej/craft) encode the same taxonomy as advisory LLM instructions — no deterministic gate.
Where stop-ai-slop is worse
- Only comments. Code-level slop — swallowed exceptions,
as any, dead code, hallucinated imports, reward-hacked tests — is out of scope: aislop (50+ rules, 10 language targets), dmmulroy/anti-slop, AI-SLOP-Detector, grain and dotnet-slopwatch cover it. - No prose scanning. Commit messages, PR descriptions and docs are ai-slop-linter's territory (its commitlint rule and commit-msg hook gate those at commit time).
- Line-based extraction, not grammars. windbag reads comments through real grammars: a
#inside a quoted YAML scalar stays data,.sqlis parsed as Jinja templates (dbt/SQLMesh), fenced Markdown blocks are skipped. Our inline detection tracks template literals with${}interpolation in the JS/TS family profiles, but stays line-local: a line that starts inside an open multi-line template literal cannot be tracked without file state — a documented limitation. - Comment length is an absolute limit. Our
long-commentis 120 characters; windbag's VERBOSE_COMMENT measures the comment against the code it documents. (After the 2026-09-29 comparison we adopted two of windbag's rules asvend/cross-file-refandvend/obvious-comment; the relative-length rule remains theirs.) - No semantic matching. gptlint (LLM) and almcc/slop-linter (Jev model) judge meaning and catch paraphrased slop; our markers are dictionaries in RU+EN+DE+FR+ES — no ZH/JA phrase detection.
- No IDE extension. AI-SLOP-Detector ships a VS Code extension with inline findings and a status-bar score; our IDE story is a tasks.json problem matcher plus an IDEA File Watcher template.
- Adoption and distribution. aislop: 655 stars, 47k downloads/month, npm + PyPI + Homebrew, score badges,
aislop agentrepair sessions driving Codex/Claude/OpenCode worktrees. slop-scan: 35k downloads/month. stop-ai-slop: 449 downloads/month, npm + a GitHub Packages mirror + a Homebrew formula (this repository as its own tap).
What only stop-ai-slop has
- the OpenCode write-time gate — no competitor blocks a write inside OpenCode: aislop's hook list covers ten agents without OpenCode, windbag blocks only in Claude Code;
- a zero-dependency single-file scanner — aislop drives external linter engines, windbag ships a Rust binary, vibecheck needs Python + ripgrep;
- Russian, German, French and Spanish natural-language markers;
- invisible-character rules with escape-form awareness: zero-width and BiDi controls, CJK glued to Latin in code — a poisoned-codegen supply-chain signal;
- a fingerprinted baseline (rule id + comment text) that survives line shifts and still flags re-pasted legacy slop.
stop-ai-slop is deliberately narrower: comment policy only. Swallowed exceptions, as any, dead code — the territory of aislop, dmmulroy/anti-slop, grain and dotnet-slopwatch; EN prose and commit messages — ai-slop-linter. It complements them exactly where they do not reach: the moment of the edit in OpenCode, and the Russian changelog markers.
License
MIT — see LICENSE.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi