@webgrip/comment-ban (0.1.0)
Installation
@webgrip:registry=npm install @webgrip/comment-ban@0.1.0"@webgrip/comment-ban": "0.1.0"About this package
@webgrip/comment-ban
Finds, ledgers and removes source comments, and proves per language that only comments came out.
A comment is a failure of the code to say what it means. This tool enforces that rule without throwing knowledge away: nothing is deleted until a human has read it and decided where it belongs.
npx @webgrip/comment-ban --report --md # what is in here, and what class is it
npx @webgrip/comment-ban --check # CI gate on the delta
npx @webgrip/comment-ban --write # strip, once the ledger is decided
npx @webgrip/comment-ban --audit-directives
The four modes
--report
A block-level ledger of every non-allowlisted comment: file, line range, the text, and a guessed
class (knowledge, noise, commented-out-code). A thirteen-line rationale is one item, not
thirteen.
The guess is display only. It never decides anything; deletion is gated on the ledger below.
| flag | effect |
|---|---|
--json |
machine-readable items |
--md |
a Markdown ledger, one section per block |
--no-ledger |
print only; do not write .comment-ban-ledger.json |
--check
Exit 1 on any non-allowlisted comment. Also fails when a registered generator marker is missing or unpaired.
The default scope is the delta: files changed since the merge-base with the default branch,
plus untracked files. --since <ref> picks another base; --all scans the whole tree.
--write
Strips, with the per-language proof enforced, and refuses to touch a file whose proof fails.
It refuses to run at all while the repo's ledger holds an undecided entry, and says which ones.
--audit-directives
Reports three problems around version pins:
| problem | meaning |
|---|---|
unannotated |
a pinned anchor with no # renovate: annotation on the line directly above it |
detached |
a # renovate: annotation that is not on the line immediately preceding its key |
orphan |
a # renovate: annotation with no anchor key below it, so nothing reads it |
The anchors are ARG, image:, tag:, version:, repository:, talosVersion:,
kubernetesVersion: and FROM …:<version>. A repo can add its own; see anchors below.
The proof
Stripping is only allowed when the language can prove the result is the same program.
| language | proof |
|---|---|
TypeScript / JavaScript / .astro frontmatter |
both ASTs printed with removeComments: true must be byte-identical, and the strip must not add a parse diagnostic |
| YAML | parseAllDocuments round-tripped to JSON must be equal |
| every language | re-tokenising the stripped file must leave no banned comment behind |
Go, Rust, PHP and Lua currently have the tokenizer and the allowlist but no semantic proof of their own; they rely on the universal re-tokenise check.
The allowlist is a datafile
Machine-read directives stay, because the toolchain acts on them as syntax. The shipped classes
live in allowlist.json; a repo extends them with a .comment-ban.json at its
root, merged over the defaults.
{
"anchors": ["^\\s*-?\\s*chart\\s*:"],
"exclude": ["third_party/**"],
"markers": [
{
"files": ["ops/docker/web/*.conf"],
"begin": "# BEGIN generated from public/_headers",
"end": "# END generated from public/_headers"
}
],
"classes": {
"terraform": { "body": ["^tflint-ignore:"], "extensions": [".tf"] }
}
}
A class is a named set of patterns:
| field | meaning |
|---|---|
raw |
regexes matched against the comment's raw text, delimiters included |
body |
regexes matched against the comment with its delimiters stripped |
extensions |
restrict the class to these file extensions |
firstLine |
the comment must start on line 1 |
rule |
a structural rule that no regex can express (below) |
A class matches when every field it declares matches. Structural rules:
| rule | holds when |
|---|---|
precedes-anchor |
the line immediately below the comment is a pin anchor (this is what makes # renovate: valid) |
godoc |
the comment is a // run whose first following non-comment line declares an exported Go identifier |
markers |
the comment starts with a begin or end string registered in markers |
vendored |
git check-attr reports linguist-vendored or linguist-generated for the path |
The shipped classes: shebang, dockerfile-frontend, yaml-schema, renovate, ts-directives,
markup, shell, python, go, go-buildtag, godoc, lua-ls, rustdoc, phpdoc, markers,
vendored.
The ledger
--report writes .comment-ban-ledger.json in the target repo. One entry per block, keyed by a
content hash so it survives line-number drift:
{
"version": 1,
"entries": {
"9f2c1a0b4e7d3556": {
"decision": "undecided",
"guess": "knowledge",
"text": "# python3, not yq: yq is not a declared dependency of this script",
"occurrences": [{ "file": "scripts/gate.sh", "startLine": 4, "endLine": 6 }]
}
}
}
A human sets decision to one of:
| decision | meaning | effect on --write |
|---|---|---|
undecided |
nobody has read it yet | blocks every write in the repo |
docs |
the knowledge moved into docs/ |
stripped |
adr |
the knowledge became an ADR | stripped |
drop |
it was noise | stripped |
keep |
it stays where it is | left alone, and --check still reports it |
Decisions survive re-reports: an entry that is no longer present in the tree is kept as long as it carries a decision, so moving a file does not reset the work.
Scope and exclusions
Files come from git ls-files. Anything without a recognised language is skipped, as is anything
matching exclude (lockfiles, node_modules/, dist/, minified bundles and CHANGELOG.md by
default).
Supported languages
TypeScript, JavaScript (.ts .tsx .mts .cts .js .jsx .mjs .cjs), Astro, CSS, HTML/SVG, Markdown,
YAML, TOML, Python, Go, Rust, PHP, Lua, shell (.sh .bash .zsh .bats), Dockerfile, justfile,
Makefile, .conf, .ini, _headers, _redirects, CODEOWNERS and the ignore files.
Dependencies
Dependencies
| ID | Version |
|---|---|
| typescript | ^5.9.3 |
| yaml | ^2.9.0 |