AI agents: see llms.md for complete installation and usage instructions intended for LLMs.
- Overview
- Installation Methods
- LLM Agent Plugins
- Basic Usage
- Command reference
- Highlights
- Safety guarantees
- Supported languages
- Zed editor integration
rm-comments is a cli tool that quickly and safely removes comments from source code files.
Files are parsed using the tree-sitter crate rather than matched with regular expressions, so removal is done using the language's actual syntax.
Comment-like sequences inside string literals, regular expressions, and docstrings are never affected, and everything that is not a comment is preserved byte for byte.
The tool also ships with a rich set of arguments/options that provide granular control over exactly what comments are removed or kept from source files.
brew trust bryceremick/tap
brew install bryceremick/tap/rm-commentscargo install rm-commentscargo binstall rm-commentsAll platforms (incl. Windows), on the releases page.
git clone https://github.com/bryceremick/rm-comments
cd rm-comments && cargo build --releaseThese plugins do not include the binary, only a skill instructing your agent on how and when to utilize this tool.
Community contributions are welcome if you don't see your preferred agent here
/plugin marketplace add bryceremick/rm-comments
/plugin install rm-comments@rm-commentsIndividual files:
rm-comments src/main.rsDirectories:
rm-comments src/Before:
/// Loads the configuration from the given path.
fn load(path: &Path) -> Config {
// TODO: validate the path exists before reading
// First, read the contents of the file into a string.
let s = read(path); // read the file
/* Now that we have the string, we can parse it.
The parse function takes the string and a key. */
let cfg = parse(&s, "key // not a comment");
// Finally, return the parsed configuration.
cfg
}After:
/// Loads the configuration from the given path.
fn load(path: &Path) -> Config {
// TODO: validate the path exists before reading
let s = read(path);
let cfg = parse(&s, "key // not a comment");
cfg
}rm-comments [OPTIONS] <FILE|DIR> # strip in place (atomic write)
rm-comments [OPTIONS] --stdin --lang <NAME> # read stdin, write stdout
rm-comments install-zed-task # add the Zed editor task
| Flag | Effect |
|---|---|
--stdout |
Print the result instead of writing the file (file/stdin only) |
--check, --dry-run |
Report only; exit 1 if changes would be made, 0 if clean |
--stdin |
Read source from stdin, write to stdout; requires --lang |
--lang <NAME> |
Language name or extension for --stdin (e.g. rust, py) |
--strip-doc-comments |
Also remove doc comments (///, //!, /*!, /** */); kept by default |
--strip-directives |
Also remove directive comments (eslint-disable, # noqa, …); kept by default |
--strip-markers |
Also remove task markers (TODO, FIXME, HACK, XXX, BUG); kept by default |
--keep <REGEX> |
Preserve comments matching REGEX (repeatable) |
--lines <A-B> |
Restrict removal to a 1-based line range (repeatable; N = single line) |
--list |
Print every comment as JSON; modifies nothing |
--apply <IDS> |
Remove exactly these comma-separated --list ids; ignores keep policies (file only) |
Exit codes: 0 success / nothing to do; 1 (--check only) changes would be made;
2 error (unsupported extension, parse failure, bad flags). On any error the file is
left untouched. For a directory, 2 means at least one file errored — the rest were
still processed.
# Strip every comment from one file, in place
rm-comments src/main.rs
# Clean a whole project (respects .gitignore, skips hidden dirs)
rm-comments src/
# Preview without writing — CI/pre-commit friendly (exit 1 if anything would change)
rm-comments --check src/
# Remove EVERYTHING, including docs, directives, and markers
rm-comments --strip-doc-comments --strip-directives --strip-markers src/lib.rs
# Only touch the region you just edited
rm-comments --lines 40-80 src/handler.rs
# Pipe through a formatter without touching disk
rm-comments --stdin --lang rust < in.rs > out.rs
# Surgical removal: enumerate, pick ids, remove exactly those
rm-comments --list src/main.rs # -> JSON with an id per comment
rm-comments --apply 2,5,7 src/main.rs # remove ids 2, 5, 7 only- Safety — files that fail to parse are never modified; writes are atomic; line endings, shebang lines, and all non-comment content are preserved exactly. Running the tool twice produces the same result as running it once.
- Selective removal — a bare run removes only plain narration comments. Doc
comments, directives (
eslint-disable,# noqa,//go:generate, …), and task markers (TODO,FIXME,HACK,XXX,BUG) are all kept unless you opt into removing them with--strip-doc-comments/--strip-directives/--strip-markers. User-defined--keeppatterns and--linesscoping add further control. - LLM integration — this repo also ships a plugin whose skill
(
SKILL.md) applies a defined policy: comments that explain rationale or constraints are kept, comments that narrate what the code already expresses are removed. - Automation support — JSON enumeration of every comment, removal by id, line-range scoping, and a dry-run mode with conventional exit codes for CI and pre-commit use.
- Files that don't parse cleanly, or have an unknown extension, are never modified.
- Writes are atomic (temp file + rename) — a crash can't leave a truncated file.
- Line endings (LF/CRLF), trailing-newline presence, and a
#!shebang on line 1 are preserved. Idempotent: running twice = running once. - Everything that isn't a comment survives byte-for-byte, apart from deliberate whitespace
cleanup: full-line comments are removed including their newline; trailing comments are
removed along with the gap before them; blank-line runs around removals collapse to at
most one blank line. The whole policy lives in one function (
rebuild()insrc/lib.rs). - Python docstrings are string expression statements in the grammar — they are correctly
left in place. Only
#comments are removed in Python files. By design, not a bug. - Doc comments (Rust
//////!, JSDoc/Javadoc/** */, Doxygen/*!), directives, and task markers are kept by default — a bare run can't silently delete a///doc or a// TODO. Pass--strip-doc-comments/--strip-directives/--strip-markersto remove a category.
For the authoritative list, see the LANGUAGES registry in
src/languages.rs — every supported language, its file extensions,
and grammar live there. Language is detected from the file extension (case-insensitive).
-
cargo add tree-sitter-<lang> -
Add one entry to
LANGUAGESinsrc/languages.rs:Lang { name: "kotlin", extensions: &["kt", "kts"], language: || tree_sitter_kotlin::LANGUAGE.into(), comment_kinds: &["comment"], // check the grammar's node-types.json },
-
Add
tests/fixtures/<name>/input.<ext>andexpected.<ext>— the golden test picks them up automatically (and fails if the fixture pair is missing).
Comment node-kind names differ per grammar — verify against the grammar's
node-types.json (most use "comment"; Rust and Java use "line_comment" +
"block_comment").
rm-comments ships as a command in Zed via a task:
rm-comments install-zed-taskThis adds a rm-comments task (pointing at the binary's own absolute path) to
~/.config/zed/tasks.json — creating the file, or splicing into an existing one while
preserving your comments and trailing commas (a backup is written first; if the file looks
too unusual to edit safely, it prints the snippet for you to paste instead). Idempotent.
Then in Zed: cmd-shift-p → task: spawn → rm-comments. The task saves the focused
buffer first ("save": "current"), strips the file on disk, and Zed reloads it. For a
one-press keybinding, add zed/keymap.json to ~/.config/zed/keymap.json
(cmd-alt-/ by default). Manual task setup: zed/tasks.json.