Skip to main content
N

stupid-comments

nmindz/stupid-comments

Enforces your comment policy at write time, inside DeepSeek Harness. Reads the policy from your AGENTS.md or CLAUDE.md, blocks violating writes, and re-injects the policy verbatim so the model stops drifting from it.

Install

dsh plugin --profile web add github:nmindz/stupid-comments

README

stupid-comments

Runtime enforcement for your code comment policy. A Rust CLI that parses what an LLM is about to write, checks it against your policy, and refuses the write when it violates. It ships as a plugin for both Claude Code and DeepSeek Harness, off the same binary and the same rules.

License: GPL v3 Version Rust


The problem

Every model tier, at every reasoning level, eventually forgets your comment policy and starts writing // Increment the counter above counter++. That is an attention problem, and no amount of restating the rule in CLAUDE.md fixes it — the instruction is simply too far back in the context by the time the code gets written.

So this moves enforcement out of the prompt and into the runtime, re-injecting your policy text verbatim at the exact moment it matters.

The policy is never baked in. It is yours, it is prose, and it lives where you already keep it.

Quick start

git clone https://github.com/nmindz/stupid-comments && cd stupid-comments
make install

Then register the plugin with whichever harness you run. Inside Claude Code:

/plugin marketplace add nmindz/stupid-comments
/plugin install stupid-comments@stupid-comments

Or, for DeepSeek Harness:

dsh plugin --profile tui add github:nmindz/stupid-comments

Finally, add a # Comments Policy section to your agent memory — ~/.claude/CLAUDE.md or ~/.dsh/AGENTS.md — in your own words, and confirm it was picked up:

stupid-comments policy

Without that section and without a config file, the plugin stays completely silent. There is no default policy, because a default policy would be someone else's taste.

Table of contents

How it works

Your policy is read from the # Comments Policy section of your agent memory (any heading level, case-insensitive). That text is quoted verbatim in every rejection, never paraphrased. If no such section and no config file exist, the plugin does nothing at all and says nothing at all.

Memory is searched in a fixed order, and the first file carrying the section wins: $CLAUDE_CONFIG_DIR/CLAUDE.md (default ~/.claude/CLAUDE.md), then $DSH_HOME/AGENTS.md (default ~/.dsh/AGENTS.md), then $AGENTS_HOME/AGENTS.md (default ~/.agents/AGENTS.md), then the nearest CLAUDE.md and AGENTS.md at or above the file being checked. The order is fixed rather than harness-derived on purpose: a machine running both must not get a different policy depending on which agent asked.

Enforcement is layered. The pre-write gate catches Write/Edit/MultiEdit early, reconstructing the post-edit file in memory so rules see whole-file context while reporting only the lines the edit introduced. The stop gate is the real guarantee: it diffs the working tree and analyzes added lines only, which makes it indifferent to how the file was written — heredoc, sed, or a subagent all land in the same net.

Both harnesses run the same engine. Claude Code wires those gates through PreToolUse, Stop, and SubagentStop; DSH wires them through tools/pre-execute, agent/turn-stopping, and subagent/end. Each adapter builds the identical JSON payload and hands it to the same binary, so a rule only ever exists in one place.

Nothing is judged until it is classified. Every comment is sorted into directive, license-header, doc-comment, or prose, and only prose faces the ratio and redundancy rules. Lint pragmas, go:build lines, shebangs, SPDX headers, and JSDoc are structurally exempt rather than merely tolerated — and a pragma placed above a comment block never launders the block beneath it.

Deletion is not compliance. A gate you can satisfy by removing the comment trains the model to write none at all, which inverts a policy that asks for just enough commenting. So findings name the offending span, demand a rewrite, and say outright that removing it does not count. Bulk deletion is legitimate on a legacy codebase, but only under /stupid-comments:fix, where a human is present and there is nothing to game.

Installation

Two pieces, installed separately and on purpose. The plugin never downloads or executes anything on your behalf — a marketplace plugin that silently fetches a remote binary is exactly the supply-chain pattern worth distrusting.

Requires a Rust toolchain. Get one from https://rustup.rs if you have none.

1. The CLI

From a clone (recommended):

make install                        # installs to ~/.local/bin
make install ROOT=$HOME/.cargo      # or wherever your PATH points

make install runs the cargo command below, then reports what command -v actually resolves to and its version.

With cargo directly:

# from a clone
cargo install --path crates/stupid-comments --root ~/.local --force

# or straight from git, without cloning
cargo install --root ~/.local --git https://github.com/nmindz/stupid-comments stupid-comments

Drop --root ~/.local to use cargo's own default of ~/.cargo/bin.

[!IMPORTANT] Pick whichever directory is already on your PATH. The plugin decides whether to enforce by looking the binary up on PATH, so installing somewhere the shell cannot resolve leaves enforcement permanently inert. Confirm with command -v stupid-comments, not by checking that the file exists.

Verify with stupid-comments --version.

2. The plugin

Claude Code:

/plugin marketplace add nmindz/stupid-comments
/plugin install stupid-comments@stupid-comments

DeepSeek Harness:

dsh plugin --profile tui add github:nmindz/stupid-comments
dsh plugin --profile tui add /path/to/clone   # from a checkout

The package declares a dsh.bundle patch, so dsh plugin add installs it and reconciles it into that profile's bundle list on its own. Nothing else needs editing.

Restart the session so the hooks register. If a policy exists but the CLI is missing, the plugin says so once and enforces nothing.

To upgrade later, every piece moves independently:

make install
claude plugin marketplace update stupid-comments
claude plugin update stupid-comments@stupid-comments
dsh plugin --profile tui update stupid-comments

3. A policy

Add a # Comments Policy section to ~/.claude/CLAUDE.md or ~/.dsh/AGENTS.md describing, in your own words, how you want comments written. Confirm it was picked up with stupid-comments policy.

To keep the policy somewhere else, point at it with the prose config key.

Verify

stupid-comments check path/to/your/code

The CLI stands alone, so the same command works as a pre-commit hook or a CI step with --json.

AI agent instructions

Paste this into a Claude Code session and it will do the setup for you:

Set up the stupid-comments comment policy enforcer on this machine.

1. If `command -v stupid-comments` already resolves, it is installed and
   reachable — skip straight to step 4.

2. Pick the install root by checking my PATH FIRST. Never install into a
   directory PATH cannot resolve:
     - if ~/.local/bin is in $PATH  -> cargo install --root ~/.local --git \
         https://github.com/nmindz/stupid-comments stupid-comments
     - else if ~/.cargo/bin is in $PATH -> same command without --root
     - else STOP. Do not install. Tell me which directories cargo can target
       and ask which one I want, or give me the export line to add to my
       shell profile first.
   Check with: case ":$PATH:" in *":$HOME/.local/bin:"*) ...
   If cargo itself is missing, point me at https://rustup.rs and stop there.

3. Verify by running `command -v stupid-comments` and `stupid-comments
   --version`. If `command -v` does not resolve, the binary went somewhere
   PATH cannot see it — say so plainly instead of reporting success.

4. Register the plugin with the harness you are running in.
   In Claude Code, tell me to run these two myself, since you cannot run
   slash commands:
     /plugin marketplace add nmindz/stupid-comments
     /plugin install stupid-comments@stupid-comments
   In DeepSeek Harness, run it yourself and name the profile you targeted:
     dsh plugin --profile <profile> add github:nmindz/stupid-comments

5. Read my agent memory — ~/.claude/CLAUDE.md, or ~/.dsh/AGENTS.md under
   DeepSeek Harness — and look for a heading matching "Comments Policy" at
   any level, case-insensitive. If it is missing, DO NOT invent a policy.
   Show me where the section goes, ask what my rules are, and write exactly
   what I tell you.

6. Run `stupid-comments policy` and show me the resolved source, mode and rules.

7. Explain that mode defaults to `shadow` — findings reported, nothing blocked —
   and that I should stay there until the reports look right before adding a
   .stupid-comments.jsonc with "mode": "block".

8. Do not enable the `semantic` option. Tell me it exists, that it spends a
   `claude -p` call per checked file, and that turning it on is my call.

Configuration

Everything here is optional. Drop a .stupid-comments.jsonc anywhere at or above the file being checked; the nearest one upward wins.

{
  "mode": "block",                    // shadow (default) | warn | block
  "bannedPatterns": ["\\bPRDs?[- ]?\\d*\\b"],
  "maxProseCommentLines": 5,
  "maxDocCommentLines": 40,
  "maxCommentRatio": 0.35,
  "minProseCommentsForRatio": 4,
  "redundancy": "warn",
  "semantic": "shadow",               // shadow (default) | warn | block
  "exclude": ["**/generated/**"]
}
KeyTypeDefaultPurpose
modeshadow | warn | blockshadowGlobal severity ceiling. shadow reports without blocking
prosepathRead the policy from this file instead of agent memory. ~ expands
maxProseCommentLinesinteger5Longest permitted prose comment block
maxDocCommentLinesinteger40Longest permitted doc comment
maxCommentRatiofloat0.35Share of a file that may be prose comments
minProseCommentsForRatiointeger4Comment blocks required before the ratio rule applies
bannedPatternsregex listemptyText that may never appear in a comment
redundancyshadow | warn | blockwarnComments that restate the line below them
semanticshadow | warn | blockshadowLLM taste judgement. See Semantic judging
semanticCommandstring list["claude", "-p"]Command the semantic judge shells out to
excludeglob listemptyPaths to skip entirely

These are calibration, not policy. The defaults are deliberately loose, because a threshold tight enough to be opinionated would be smuggling in someone else's taste.

[!TIP] mode defaults to shadow: findings are reported, nothing is blocked. Stay there until the log convinces you the blocks would have been right, then switch to block.

An unparseable config is an error, not silence — check and policy print the reason and exit non-zero. Only the hook still fails open, since an unreadable config must never block a write.

Rules

RuleDefault severityFires when
banned-patternblockA comment matches one of your bannedPatterns
prose-comment-too-longblockA prose block exceeds maxProseCommentLines
doc-comment-too-longwarnA doc comment exceeds maxDocCommentLines
comment-ratioblockProse comments cover more than maxCommentRatio of the file
redundant-commentwarnA comment restates the code directly below it
semanticwarnThe judge decides a comment has not earned its place
comments-removedwarnA file that had prose comments now has none

Severities are ceilings, not floors: under "mode": "shadow" or "warn" every finding is downgraded, and findings recovered from a file whose grammar failed are always warn-only.

CLI usage

stupid-comments check [PATH]...     # report findings, change nothing
stupid-comments check --json        # machine-readable, for CI
stupid-comments check --adjudicate  # permit deletion as a remedy
stupid-comments policy              # show the resolved policy and its source
stupid-comments hook claude|dsh     # consume a hook payload on stdin

Every run prints a coverage summary to stderr, leaving stdout clean for --json:

Checked 17 files (rust 12, json 2, toml 2, make 1).
Not checked — no grammar for 8 files: .md 5, .gitignore 1, .lock 1, LICENSE 1
Not checked — excluded by config: 10 files

A file with no grammar is not a passing file, so it is never folded into the checked count. The numbers add up on purpose.

Exit codeMeaning
0No blocking findings
1A blocking finding, an unreadable config, or a path that does not exist
2Hook only: block the pending write

Slash commands

Claude CodeDeepSeek HarnessPurpose
/stupid-comments:policy/stupid-comments-policyShow the policy in force and where it came from
/stupid-comments:check [path]/stupid-comments-check [path]Report findings, change nothing
/stupid-comments:fix [path]/stupid-comments-fix [path]Adjudicated sweep of an existing codebase; deletion permitted
/stupid-comments:off/stupid-comments-offHow to disarm for a session

The names differ only because DSH command names cannot carry a colon. The prompts do not: both harnesses read the same markdown files under plugins/stupid-comments/commands/, so the wording has exactly one home.

Semantic judging

Deterministic rules cannot decide whether a comment earns its place. Setting "semantic": "warn" (or "block") sends the prose comments and your policy text to claude -p, using the session authentication you already have — there is no API key to configure and none is wanted. Every failure is silent: no claude on PATH, a timeout, unparseable output, all mean no findings.

The judge is a subprocess, not a harness binding. Point semanticCommand at anything that reads a prompt on stdin and answers with JSON, and it works the same from either plugin.

It is off by default because it spends a model call per checked file. It is also the only rule that catches // Adds a and b sitting above const sum = a + b, which is probably the comment that made you look for this tool.

Escaping it

Set STUPID_COMMENTS=0 in the session environment. That is deliberately the only mid-session hatch — it lives somewhere the model cannot write, so the enforced party cannot disable its own gate. Both plugins honor it, and the DSH one registers no seams at all when it is set.

Permanently: change mode in .stupid-comments.jsonc, or remove the plugin with /plugin uninstall stupid-comments@stupid-comments or dsh plugin --profile tui remove stupid-comments.

Suppression pragmas exist, but they are anchored to git:

// stupid-comments: ignore        -> suppresses findings on the next 3 lines
// stupid-comments: ignore-file   -> suppresses the whole file

[!NOTE] A pragma is honored only if the identical line already exists in HEAD. One introduced in the same change as the violation it silences is ignored entirely, so the model cannot write its own exemption. Outside a git repository no pragma is honored.

Detecting evasion

A gate that counts only violations cannot tell "learned taste" from "stopped writing comments". Prose-comment counts are tracked per file for the session, and a file that had comments and now has none raises a comments-removed warning naming what was lost. Bulk removal is legitimate, but only under /stupid-comments:fix, where a human asked for it.

Languages

JavaScript, TypeScript, TSX/JSX, Rust, Go, Kotlin, JSON/JSONC/JSON5, TOML, YAML, HCL/Terraform, shell (sh/bash/zsh/ksh), and Make, via native tree-sitter grammars.

Not every file carries its language in its extension. Makefile, GNUmakefile, Makefile.* and *.mk are matched by name, as are the usual shell rc files, and an extensionless file is checked for a shell shebang — a scripts/ directory is mostly extensionless, and skipping one silently is indistinguishable from checking it and finding nothing. #!/usr/bin/env bash counts; #!/usr/bin/env python3 does not, and neither does fish.

A # inside a shell string, a heredoc body, or a Make recipe is data, not commentary. Telling those apart is the whole reason this uses grammars rather than a regex over lines starting with #.

Config formats answer to exactly the same rules as code, maxCommentRatio included. They have twice been given something gentler — first an outright exemption from the ratio rule, then a looser threshold of their own — and both times the result was a manifest sitting at a comment load that would be flagged on sight in a .go file. A YAML at 43% comments is a YAML at 43% comments; there is no version of "just enough" that reads differently because the file ends in .yaml.

Templating defeats the YAML grammar. A Helm chart parses to a single error node with no comments in it, which would make every templated manifest in a repository look clean. When the grammar fails on a #-comment format, comments are recovered by a line scan instead — whole-line comments only, block scalars left alone, so the failure direction is a missed comment rather than an invented one.

Failure is otherwise open. Parse error, missing binary, unreadable config — inside the hook all of them mean no findings, never a blocked write.

Development

Requires a Rust toolchain. make help lists every target.

MakeCargo equivalentPurpose
make buildcargo build --releaseCompile the release binary
make testcargo testRun the test suite
make dsh-testnode plugins/stupid-comments/dsh/test.mjsDrive the DSH adapter against the release binary
make lintcargo clippy --all-targetsLint every target
make versionnode scripts/sync-version.mjs X.Y.ZWrite one version into all five manifests
make validateclaude plugin validate + scripts/validate-dsh-manifest.mjsCheck both plugin manifests
make checkall of the aboveEverything CI would run
make installcargo install --path crates/stupid-comments --root ~/.local --forceInstall the binary
make uninstallcargo uninstall --root ~/.local stupid-commentsRemove it
make dsh-installdsh plugin --profile tui add $(pwd)Register this checkout with a dsh profile
make dsh-uninstalldsh plugin --profile tui remove stupid-commentsUnregister it
make selfcheck./target/release/stupid-comments check .Enforce this repo's policy on itself
make cleancargo cleanRemove build artifacts

make selfcheck is the one that matters: the enforcer answers to its own policy, and a change that makes this repo fail its own gate is not ready.

crates/stupid-comments/src/
├── lang.rs        # language detection and grammar bindings
├── comments.rs    # extraction and classification
├── rules.rs       # the deterministic rules
├── semantic.rs    # the opt-in LLM judge
├── policy.rs      # config and policy resolution
├── hook.rs        # hook payloads, shared by every harness
├── suppress.rs    # git-anchored pragmas
├── session.rs     # cross-turn evasion tracking
└── main.rs        # CLI

The harness plugins are adapters over that binary, and neither carries a rule of its own:

plugins/stupid-comments/
├── .claude-plugin/plugin.json   # Claude Code manifest
├── hooks/hooks.json             # Claude Code hook wiring
├── commands/*.md                # slash command prompts, read by both harnesses
└── dsh/
    ├── index.js                 # DSH cordis plugin: seams, payloads, commands
    ├── cordis.patch.yml         # the bundle layer dsh composes
    └── test.mjs                 # drives the adapter against the real binary

package.json at the repo root is the DSH bundle manifest: it points dsh.bundle.patch at that patch file, which is the whole reason dsh plugin add can install this repository directly.

Releases are derived from Conventional Commits by semantic-release, and the npm package is staged rather than published: CI authenticates through OIDC trusted publishing and holds no credential that can ship a version on its own, so a human approves the tarball with a 2FA code. See CONTRIBUTING.md for the commit convention, the release flow, and a walkthrough of adding a language.

Known limits

  • Redundancy detection is warn-only. It is the most false-positive-prone rule here and has not earned blocking authority.
  • Kotlin findings are warn-only while its grammar earns trust, as are findings recovered by line scan from a templated config file.
  • The line-scan fallback reads whole-line comments only, so a trailing # comment on a value line goes unchecked in a templated file.
  • Python has no grammar yet, so .py files are named as unchecked rather than checked.
  • minProseCommentsForRatio counts comment blocks, not lines, so a file carrying fewer than four separate blocks never trips the ratio rule however much of the file they cover. Long blocks are caught by the length rule instead.
  • Semantic judging costs a model call per checked file, so it is off by default.
  • The Stop gate diffs against HEAD, so a tree that was already dirty before the session has those earlier changes considered too.
  • DSH also ships a text-editor tool. Its create and str_replace commands are translated and checked before the write; its insert command carries no anchor to reconstruct from, so it falls to the stop gate.
  • Under DSH, subagent/end is an observation point rather than a decision point. A subagent that ends on a violation is handed the finding as context; only the parent's own stop gate can force the rewrite.
  • The DSH plugin reports a missing binary the first time a write is about to be checked, not at session start, so a session that never writes code stays silent about it.

Contributing

Bug reports and pull requests are welcome. See CONTRIBUTING.md.

License

GPL-3.0-or-later. See LICENSE.

Related plugins