Chuyển đến nội dung chính

stupid-comments

Đã xác minh

stupid-comments · v0.3.0 · GPL-3.0-or-later

Enforces your comment policy at write time, inside DeepSeek Harness, Pi, and oh-my-pi. 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.

Cài đặt

dsh plugin add stupid-comments

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Thẻ

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 Claude Code, DeepSeek Harness, Pi, and Pi's oh-my-pi fork (the omp TUI and omp-web), 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

Or, for Pi:

pi install git:github.com/nmindz/stupid-comments

Or, for oh-my-pi, which covers both the omp TUI and omp-web:

omp plugin install github:nmindz/stupid-comments

Finally, add a # Comments Policy section to your agent memory — ~/.claude/CLAUDE.md, ~/.dsh/AGENTS.md, ~/.pi/agent/AGENTS.md, or ~/.omp/agent/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 Pi's agent dir ($PI_CODING_AGENT_DIR, default ~/.pi/agent), then omp's ($PI_CODING_AGENT_DIR, default ~/.omp/agent), then $AGENTS_HOME/AGENTS.md (default ~/.agents/AGENTS.md), then the nearest CLAUDE.md and AGENTS.md at or above the starting directory — the session's working directory for the plugins, the first path given to check. In Pi's agent dir the file read is the one Pi itself loads: the first of AGENTS.override.md, AGENTS.md, and CLAUDE.md that exists. The order is fixed rather than harness-derived on purpose: a machine running more than one 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.

Every harness runs 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; Pi wires them through tool_call and agent_before_settle; oh-my-pi runs the same extension, through tool_call and session_stop. 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.

It targets DeepSeek Harness 0.2.x. The range is declared through optional @deepseek-ai/dsh-* peer dependencies, which is the check dsh itself enforces: a dsh outside that range refuses the install until you grant dsh plugin allow-version. The 0.2 line matters because its session format refuses messages attributed to the retired plugin source kind, which is what releases up to 0.2.1 sent.

Pi:

pi install git:github.com/nmindz/stupid-comments
pi install npm:stupid-comments                  # from the registry, once a release carries Pi support
pi install /path/to/clone                       # from a checkout

The package declares its extension under the pi key of package.json, which is what pi install reads. It needs Pi 0.87 or later, the first release with the agent_before_settle boundary the stop gate runs on, and it has been run end to end against 0.87.1, in the interactive TUI as well as print, JSON, and RPC modes. It imports nothing from Pi, so it carries no Pi dependency of its own.

oh-my-pi (omp and omp-web):

omp plugin install github:nmindz/stupid-comments
omp plugin install stupid-comments              # from the registry, once a release carries Pi support
omp plugin link /path/to/clone                  # from a checkout

oh-my-pi reads the same pi manifest key and loads the same extension, which switches to omp's session_stop seam on its own. omp-web discovers extensions with omp's own loader and keeps user-level plugins for every project, trusted or not, so one install covers the omp TUI and the browser alike. Both have been run end to end against omp 18.1, the line omp-web 0.4 pins.

Restart the session so the hooks register (/reload in Pi and omp). 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
pi update git:github.com/nmindz/stupid-comments
omp plugin upgrade stupid-comments

3. A policy

Add a # Comments Policy section to ~/.claude/CLAUDE.md, ~/.dsh/AGENTS.md, ~/.pi/agent/AGENTS.md, or ~/.omp/agent/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
   In Pi, run it yourself, then tell me to /reload:
     pi install git:github.com/nmindz/stupid-comments
   In oh-my-pi (omp or omp-web), run it yourself, then tell me to /reload:
     omp plugin install github:nmindz/stupid-comments

5. Read my agent memory — ~/.claude/CLAUDE.md, ~/.dsh/AGENTS.md under
   DeepSeek Harness, ~/.pi/agent/AGENTS.md under Pi, or
   ~/.omp/agent/AGENTS.md under oh-my-pi — 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 starting directory — the session's working directory for the plugins, the first path given to check — and 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/**"]
}
Key Type Default Purpose
mode shadow | warn | block shadow Global severity ceiling. shadow reports without blocking
prose path — Read the policy from this file instead of agent memory. ~ expands
maxProseCommentLines integer 5 Longest permitted prose comment block
maxDocCommentLines integer 40 Longest permitted doc comment
maxCommentRatio float 0.35 Share of a file that may be prose comments
minProseCommentsForRatio integer 4 Comment blocks required before the ratio rule applies
bannedPatterns regex list empty Text that may never appear in a comment
redundancy shadow | warn | block warn Comments that restate the line below them
semantic shadow | warn | block shadow LLM taste judgement. See Semantic judging
semanticCommand string list ["claude", "-p"] Command the semantic judge shells out to
exclude glob list empty Paths 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

Rule Default severity Fires when
banned-pattern block A comment matches one of your bannedPatterns
prose-comment-too-long block A prose block exceeds maxProseCommentLines
doc-comment-too-long warn A doc comment exceeds maxDocCommentLines
comment-ratio block Prose comments cover more than maxCommentRatio of the file
redundant-comment warn A comment restates the code directly below it
semantic warn The judge decides a comment has not earned its place
comments-removed warn A 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|pi  # 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 code Meaning
0 No blocking findings
1 A blocking finding, an unreadable config, or a path that does not exist
2 Hook only: block the pending write

Slash commands

Claude Code, Pi, omp DeepSeek Harness Purpose
/stupid-comments:policy /stupid-comments-policy Show 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-off How to disarm for a session

The names differ only because DSH command names cannot carry a colon. The prompts do not: every harness reads 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 every 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. Every plugin honors it, and the DSH, Pi, and omp ones register nothing at all when it is set.

Permanently: change mode in .stupid-comments.jsonc, or remove the plugin with /plugin uninstall stupid-comments@stupid-comments, dsh plugin --profile tui remove stupid-comments, pi remove with the source you installed from, or omp plugin uninstall 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.

Make Cargo equivalent Purpose
make build cargo build --release Compile the release binary
make test cargo test Run the test suite
make dsh-test node plugins/stupid-comments/dsh/test.mjs Drive the DSH adapter against the release binary
make pi-test node plugins/stupid-comments/pi/test.mjs Drive the Pi extension against the release binary
make lint cargo clippy --all-targets Lint every target
make version node scripts/sync-version.mjs X.Y.Z Write one version into all five manifests
make validate claude plugin validate + scripts/validate-{dsh,pi}-manifest.mjs Check every plugin manifest
make check all of the above Everything CI would run
make install cargo install --path crates/stupid-comments --root ~/.local --force Install the binary
make uninstall cargo uninstall --root ~/.local stupid-comments Remove it
make dsh-install dsh plugin --profile tui add $(pwd) Register this checkout with a dsh profile
make dsh-uninstall dsh plugin --profile tui remove stupid-comments Unregister it
make pi-install pi install $(pwd) Register this checkout as a Pi package
make pi-uninstall pi remove $(pwd) Unregister it
make selfcheck ./target/release/stupid-comments check . Enforce this repo's policy on itself
make clean cargo clean Remove 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 none 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 every harness
├── lib/
│   ├── engine.js                # spawn, handshake, exit code -> verdict
│   └── commands.js              # command markdown loader and expander
├── 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
└── pi/
    ├── index.js                 # Pi and omp extension: events, payloads, commands
    └── test.mjs                 # drives the extension against the real binary

package.json at the repo root is both the DSH bundle manifest and the Pi package manifest: dsh.bundle.patch points at that patch file, which is the whole reason dsh plugin add can install this repository directly, and pi.extensions names the extension pi install loads.

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.
  • Policy is resolved from the session's working directory, not from the file being written, so a write outside the workspace answers to the workspace's policy.
  • 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.
  • The stop gate forces at most one continuation per turn. The stop that follows a forced continuation reports stop_hook_active, so a violation the model cannot fix ends the turn instead of looping it; Claude Code sets that flag itself, the DSH adapter tracks it per turn, the Pi extension tracks it per prompt, clearing it on agent_settled, and omp reports it itself.
  • 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.
  • Pi has no built-in subagents, so it has no SubagentStop counterpart. omp skips session_stop for its subagents, so their writes answer to the pre-write gate and to the parent's stop gate.
  • Pi's edit strips a BOM, normalizes CRLF, and falls back to fuzzy matching (trailing whitespace, typographic quotes) before it gives up on an anchor. The engine matches anchors literally, so wherever Pi needed one of those, the pre-write gate stands down and the stop gate catches the result.
  • omp's default hashline edits, and its patch, apply_patch, and sloppy modes, carry no anchors the engine can rebuild a file from, so those edits land unchecked and the stop gate catches what they wrote. Writes are always checked first. Set edit.mode: replace in omp's config.yml to have edits checked before they land too.
  • omp shows its stop-gate continuation as a hidden extension message, so the plugin adds a one-line warning when it sends the model back. omp-web renders that hidden message itself, as a collapsible panel.
  • Pi's one-shot -p and --mode json runs shut down once the typed prompt resolves. A slash command starts the prompt it sends without that prompt being awaited, so /stupid-comments:* never reaches the model there. The interactive TUI and RPC mode run it normally.
  • The DSH, Pi, and omp plugins report 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.