OComment
OComment is a fast, byte-preserving comment checker and remover. It reads source bytes without requiring them to be UTF-8, reports every comment it finds, and removes the ones a policy allows it to remove — through a rollback-backed transaction, so a run either applies every edit or none of them.
The production tool is the Rust ocomment binary and the public ocomment-core library.
ocomment-ref is an independent OCaml implementation, and the two are compared on the scanner, the classification, the diagnostics, the edits, the transformed bytes, and the source maps.
Nothing is shared between them; matching normalized output is the cross-check.
$ ocomment check src
src/main.rs:2:5: removable line comment: // TODO: drop this
Found 1 removable comment in 1 file (1 file scanned). Run `ocomment fix` to remove it.
What it is for
A comment remover is usually wanted for one of three reasons, and OComment is built for all three:
- Shipping less than you wrote. Stripping comments out of a build artifact, a container image, or a vendored copy, without changing what the code does.
- Holding a line in review. A team that has agreed which comments are worth keeping can encode that agreement in
.ocomment.tomland let the pre-commit hook or CI enforce it. This repository does exactly that to itself. - Reading what a file really says.
ocomment scangives the kind, the disposition, and the byte span of every comment, for a tool to consume.
What it will not do
It will not remove a comment that is not commentary.
A shebang, an encoding preamble, a build tag, a lint control, an optimiser hint, and a MySQL versioned comment all change what some other program does with the file, and the default policy keeps every one of them.
Why was this comment kept? is the page about that, and --explain answers it for any single comment.
It will not corrupt a file it does not understand. The engine never requires the complete source to be UTF-8, so BOMs, CRLF line endings, trailing newlines, and non-UTF-8 bytes outside the edited spans come back exactly as they went in.
Where to go next
- Getting started is the five-minute version.
- Installation lists every channel the tool ships through.
- Commands is the complete CLI reference, generated from the binary.
- Configuration is
.ocomment.tomlin full. - Policies and layouts shows what each setting does to one sample file.
- Languages and dialects is what OComment can read.
- Editors and LSP, CI and hooks, and Docker cover the integrations.
- Library and Plugins are for building on it.
OComment is available under either the MIT license or the Apache License 2.0, at your option.
Getting started
Install it
cargo install ocomment --locked
That is one of several channels — a prebuilt archive, Homebrew, Scoop, WinGet, a container image, a GitHub Action, and a pre-commit hook are all documented under Installation. Everything below works the same way whichever one you used.
Look before you leap
ocomment with no command is ocomment check, and check with no path is the current directory, so the shortest useful run is the tool’s own name:
$ ocomment check src
src/main.rs:2:5: removable line comment: // TODO: drop this
Found 1 removable comment in 1 file (1 file scanned). Run `ocomment fix` to remove it.
Nothing has changed on disk.
check only reports, and it reports the same set of comments that fix would remove.
See the change as a patch
$ ocomment diff src
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
fn main() {
- // TODO: drop this
+
println!("hello");
}
Found 1 removable comment in 1 file (1 file scanned). Run `ocomment fix` to apply the patch.
The patch goes to standard output and the summary line goes to standard error,
so ocomment diff src > fix.patch writes a file git apply will take.
The blank line the removal leaves behind is the lines layout at work; compact and columns leave something else, and Policies and layouts shows all three side by side.
ocomment fix --dry-run src prints the same patch and applies nothing, which is the form to reach for inside a script.
Make the change
$ ocomment fix src
fixed src/main.rs: removed 1 comment
Removed 1 comment in 1 file (1 file scanned).
Every edit of a run is prepared first and committed as one transaction, so an interrupted fix leaves the tree as it found it rather than half-rewritten.
ocomment fix -i asks about each comment instead, with three lines of context either side: y removes it, n keeps it, a and d answer for the rest of the file, q stops asking and applies what was accepted, and x abandons the run without writing anything.
Exit codes
| Code | Meaning |
|---|---|
0 | Nothing removable was found, and every requested change was applied. |
1 | Removable comments were reported, a diff was printed, --tidy left a removal for you, or a staged fix rewrote the index. |
2 | An invalid source, configuration, plugin, or I/O failure. |
That is why ocomment check works as a CI gate on its own, and why 1 from diff is not an error: it means the patch is not empty.
Decide what your project keeps
The default conservative policy removes ordinary comments and keeps the ones something else depends on: documentation, licence notices, tool and language directives, shebangs and encoding lines.
Write the decision down instead of passing flags every time:
ocomment init config
init config writes a .ocomment.toml holding every default spelled out, so the file starts as a complete description of what the tool already does and you change the lines you disagree with.
A project that has made a few decisions ends up looking like this:
version = 1
[policy]
mode = "conservative"
layout = "lines"
keep_kind = ["doc-line", "doc-block"]
keep_regex = ['^//\s*NOTE\b']
[[overrides]]
paths = ["generated/**"]
policy = "all"
That file keeps licence headers, keeps documentation comments, keeps any comment opening with NOTE, and takes everything out of generated/.
Configuration documents every key, and ocomment config explain prints the resolved result with the source of each value.
Ask why a comment survived
ocomment check --explain
--explain puts the rule that decided each comment, and the setting behind that rule, on the line underneath it — for the comments it kept as much as the ones it would remove.
Why was this comment kept? walks through a real answer.
Put it in the loop
ocomment init lefthook --tidy
lefthook install
The generated hook runs ocomment fix --tidy --staged, which judges the bytes the commit will actually carry rather than the working tree — the distinction that matters for a partially staged file.
--tidy writes the half a machine can settle and leaves every removal reported and unapplied, so nothing is deleted on your behalf; the run exits 1 when it rewrote the index, which stops the commit long enough for you to look at what changed.
Write ocomment init lefthook for a hook that only reports, or --fix for one that applies the removals too.
CI and hooks covers the pre-commit manifest, the composite GitHub Action, and SARIF upload to code scanning; Editors and LSP covers seeing the same diagnostics as you type.
Installation
Every channel below installs the same binary for the same release.
Pick one;
they do not need each other.
Examples pin 0.1.0 — use the version you want,
and prefer a full pin over a moving tag wherever a workflow or a tap will resolve it later.
From crates.io
cargo install ocomment --locked
--locked builds against the dependency versions the release was tested with.
This is the only channel that compiles on your machine, so it needs a Rust toolchain of 1.88 or newer and takes a few minutes.
Prebuilt binary with cargo-binstall
cargo binstall ocomment
The crate carries the archive URL, the archive format, and the path of the binary inside it as package.metadata.binstall, so cargo-binstall downloads the release archive for your target instead of compiling anything.
Release archives
Every release publishes one archive per target:
| Platform | Asset |
|---|---|
| Linux x86-64 (glibc) | ocomment-x86_64-unknown-linux-gnu.tar.gz |
| Linux ARM64 (glibc) | ocomment-aarch64-unknown-linux-gnu.tar.gz |
| Linux x86-64 (musl, static) | ocomment-x86_64-unknown-linux-musl.tar.gz |
| Linux ARM64 (musl, static) | ocomment-aarch64-unknown-linux-musl.tar.gz |
| macOS Intel | ocomment-x86_64-apple-darwin.tar.gz |
| macOS Apple Silicon | ocomment-aarch64-apple-darwin.tar.gz |
| Windows x64 | ocomment-x86_64-pc-windows-msvc.zip |
Each archive unpacks into an ocomment-<target>/ directory holding the binary,
both licences, the README, the ocomment.1 manual page, and completion scripts for Bash, Zsh, fish, PowerShell, and Elvish.
gh release download v0.1.0 --repo P4suta/OComment \
--pattern 'ocomment-x86_64-unknown-linux-gnu.tar.gz*'
tar -xzf ocomment-x86_64-unknown-linux-gnu.tar.gz
install -m 0755 ocomment-x86_64-unknown-linux-gnu/ocomment ~/.local/bin/ocomment
Do not skip Verifying downloads: every archive ships a SHA-256, a Sigstore signature bundle, and a GitHub build-provenance attestation, and checking them is three commands.
Homebrew, Scoop, and WinGet
Every release generates and signs a Homebrew formula (ocomment.rb), a Scoop manifest (ocomment-scoop.json), and a WinGet manifest (ocomment.winget.yaml) from the SHA-256 values of the archives that release actually built.
They are attached to the release as assets and can be installed directly:
brew install --formula ./ocomment.rb
scoop install .\ocomment-scoop.json
winget install --manifest .\ocomment.winget.yaml
There is no published tap, bucket, or WinGet listing yet.
Once those exist, the same generated files are what gets submitted to them, and the commands become the ordinary brew install ocomment, scoop install ocomment, and winget install OComment.OComment.
Container image
docker run --rm -v "$PWD:/src" ghcr.io/p4suta/ocomment:0.1.0 check
The image is scratch plus one statically linked musl binary, built from the exact archives of the same release rather than from a second compilation.
Docker covers writing files back, exit codes, and running as your own user.
GitHub Actions
- uses: P4suta/OComment@v0.1.0
with:
paths: src tests
The composite action downloads the release archive for the runner, verifies its SHA-256 and its build-provenance attestation, and annotates the pull request. CI and hooks documents every input and output, including SARIF upload to code scanning.
pre-commit
repos:
- repo: https://github.com/P4suta/OComment
rev: v0.1.0
hooks:
- id: ocomment-check
The hooks are language: system, so install the CLI through one of the channels above first.
CI and hooks explains why, and what args: ["--staged"] changes for a partially staged file.
Editor extension
The VS Code extension is currently source-only: it has not been published to the Visual Studio Marketplace, Open VSX, or GitHub Releases.
The source under editors/vscode can be built and installed locally, but it has its own version and release lifecycle.
It is a client only and launches the separately installed ocomment binary.
Every other LSP client can launch ocomment lsp directly —
see Editors and LSP.
From source
git clone https://github.com/P4suta/OComment
cd OComment
cargo build --manifest-path rust/Cargo.toml --release --locked -p ocomment
The binary lands at rust/target/release/ocomment.
Building the OCaml reference implementation and running the differential suite additionally needs OCaml 5.5,
opam, Dune, and Python 3; see CONTRIBUTING.md.
Check what you got
ocomment --version
ocomment doctor
doctor reports the resolved configuration, the Git integration, the plugin lock, and the external tools it can find, which is the fastest way to see that an install is complete and that a hook or an editor will be able to launch it.
Commands
Each block below is the exact --help text of the binary this page was
generated from, so a flag documented here is a flag the tool has, and a flag
that is missing here does not exist.
ocomment with no command is ocomment check, and a command with no path is
the current directory. Findings, patches, listings, and every machine format go
to standard output; the run summary and every note go to standard error, so
ocomment diff src > fix.patch keeps the patch clean.
check exits 0 when nothing removable was found, 1 when removable comments
were reported or a diff was printed, and 2 for an invalid source,
configuration, plugin, or I/O failure.
The Policy and Output option groups are global. Every command that can act
on them accepts them, which is why the same two groups appear under most of the
blocks below. ocomment man renders the same material as a manual page.
Contents
ocommentocomment checkocomment fixocomment diffocomment scanocomment stripocomment lspocomment initocomment configocomment languagesocomment profilesocomment pluginocomment plugin addocomment plugin removeocomment plugin listocomment plugin updateocomment plugin verifyocomment plugin newocomment completionsocomment coverageocomment tagsocomment ratchetocomment hookocomment selftestocomment doctorocomment man
ocomment
$ ocomment --help
OComment scans source bytes without requiring UTF-8 and reports or removes comment tokens. The default policy removes ordinary comments and keeps the ones something else depends on: documentation, licence notices, tool and language directives, shebangs and encoding lines. Rewrites are prepared and committed as one rollback-backed transaction.
Usage: ocomment [OPTIONS] [PATH]...
ocomment <COMMAND>
Commands:
check Report removable comments (default command)
fix Remove comments in place through an atomic, rollback-backed transaction
diff Print a unified diff of the changes fix would make
scan List every comment with its kind, disposition and byte span
strip Read source on stdin and write the stripped result to stdout
lsp Run the LSP 3.18 server over stdio
init Write a starter .ocomment.toml or Lefthook configuration
config Show, locate, explain, or export the resolved configuration
languages List built-in languages, extensions, and dialects
profiles List the declarative profiles that read files no built-in language does
plugin Manage sandboxed WASM scanner plugins
completions Generate shell completions
coverage Report which files a walk scanned and which it passed over, and why
tags Count the tags this tree's comments open with, against the ones it allows
ratchet Check the tree against its ledger, or record the tree in one
hook Answer an agent editing hook in the host's own protocol
selftest Re-run the shared corpus against this binary and report any disagreement
doctor Diagnose the environment (config, git, plugins, tools)
man Render the roff manual page to stdout
help Print this message or the help of the given subcommand(s)
Arguments:
[PATH]...
Files or directories to check; `-` reads standard input (default: current directory)
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
-V, --version
Print version
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
EXIT STATUS
0 Nothing removable was found and every requested change was applied.
1 Removable comments were reported, a diff was printed, `--tidy` left a
removal for you, or a staged fix rewrote the index.
2 Invalid source, configuration, plugin, or I/O failure.
FILES
.ocomment.toml Project configuration, merged over the user file.
.ocommentignore Extra ignore patterns honoured by repository walks.
.ocomment.lock Pinned digests of the installed WASM scanner plugins.
$XDG_CONFIG_HOME/ocomment/config.toml
User configuration, merged over the built-in defaults.
EXAMPLES
ocomment
Check the current directory and report removable comments.
ocomment fix --policy all --layout compact src
Remove every comment under src and close the gaps it leaves.
ocomment fix --tidy --staged
Reflow what the style rules decide and leave every removal to you.
ocomment strip --language rust < before.rs > after.rs
Strip one file from standard input to standard output.
SEE ALSO
The complete schemas and guides are available in the OComment repository.
ocomment check
$ ocomment check --help
Report removable comments (default command)
Usage: ocomment check [OPTIONS] [PATH]...
Arguments:
[PATH]...
Files or directories to process; `-` reads standard input (default: current directory)
Options:
--staged
Read and update Git index blobs rather than treating the working tree as the source
--index-only
With `--staged`, do not attempt a uniquely mappable working-tree update
--base <REV>
Check only the working-tree files that differ from this revision's merge base with HEAD
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment fix
$ ocomment fix --help
Remove comments in place through an atomic, rollback-backed transaction
Usage: ocomment fix [OPTIONS] [PATH]...
Arguments:
[PATH]...
Files or directories to rewrite (default: current directory)
Options:
--staged
Read and update Git index blobs rather than treating the working tree as the source
--index-only
With `--staged`, do not attempt a uniquely mappable working-tree update
--base <REV>
Check only the working-tree files that differ from this revision's merge base with HEAD
--dry-run
Print the patch `fix` would apply and write nothing
--tidy
Apply what the style rules rewrote and leave every removal to you.
The removals are still reported and the run still exits 1 for them; what changes is that none of them reaches the file. This is the half a machine can finish on its own, which is what makes it the half a commit hook may run unattended.
-i, --interactive
Ask about each comment in turn and remove only the accepted ones.
The index has no working-tree line to show a hunk from, `--dry-run` writes nothing whatever the answers were, and `-q` asks for a run with no commentary at all. None of the three can also be a conversation.
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment diff
$ ocomment diff --help
Print a unified diff of the changes fix would make
Usage: ocomment diff [OPTIONS] [PATH]...
Arguments:
[PATH]...
Files or directories to process; `-` reads standard input (default: current directory)
Options:
--staged
Read and update Git index blobs rather than treating the working tree as the source
--index-only
With `--staged`, do not attempt a uniquely mappable working-tree update
--base <REV>
Check only the working-tree files that differ from this revision's merge base with HEAD
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment scan
$ ocomment scan --help
List every comment with its kind, disposition and byte span
Usage: ocomment scan [OPTIONS] [PATH]...
Arguments:
[PATH]...
Files or directories to process; `-` reads standard input (default: current directory)
Options:
--staged
Read and update Git index blobs rather than treating the working tree as the source
--index-only
With `--staged`, do not attempt a uniquely mappable working-tree update
--base <REV>
Check only the working-tree files that differ from this revision's merge base with HEAD
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment strip
$ ocomment strip --help
Read source on stdin and write the stripped result to stdout
Usage: ocomment strip [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment lsp
$ ocomment lsp --help
Run the LSP 3.18 server over stdio
Usage: ocomment lsp [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment init
$ ocomment init --help
Write a starter .ocomment.toml or Lefthook configuration
Usage: ocomment init [OPTIONS] [KIND]
Arguments:
[KIND]
Which starter file to write
[default: config]
[possible values: config, lefthook]
Options:
--tidy
For the Lefthook hook, run `fix --tidy` instead of `check`.
The hook writes what the style rules settle and leaves every removal reported and unapplied, which is the shape a gate on every commit wants.
--fix
For the Lefthook hook, run `fix` instead of `check`.
The removals too, including the comments above them that were worth keeping. `--tidy` is the one that writes nothing a reader would have wanted back.
--force
Replace the file if it already exists
--stdout
Print the template to standard output and write no file
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment config
$ ocomment config --help
Show, locate, explain, or export the resolved configuration
Usage: ocomment config [OPTIONS] [ACTION]
Arguments:
[ACTION]
Which view of the resolved configuration to print
[default: show]
[possible values: show, locate, explain, schema]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment languages
$ ocomment languages --help
List built-in languages, extensions, and dialects
Usage: ocomment languages [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment profiles
$ ocomment profiles --help
List the declarative profiles that read files no built-in language does
Usage: ocomment profiles [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment plugin
$ ocomment plugin --help
Manage sandboxed WASM scanner plugins
Usage: ocomment plugin [OPTIONS] <COMMAND>
Commands:
add Install a plugin and pin its digest in .ocomment.lock
remove Uninstall a plugin and drop its lock entry
list List the installed plugins and their pinned digests
update Re-fetch plugins and refresh their pinned digests
verify Check installed plugins against their pinned digests
new Scaffold a new plugin crate from the scanner WIT world
help Print this message or the help of the given subcommand(s)
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment plugin add
$ ocomment plugin add --help
Install a plugin and pin its digest in .ocomment.lock
Usage: ocomment plugin add [OPTIONS] <SOURCE>
Arguments:
<SOURCE>
Path or URL of the WASM component to install
Options:
--name <NAME>
Name to register the plugin under (default: the file stem)
--sha256 <HEX>
Expected SHA-256 digest of the component, verified before install
--identity <IDENTITY>
Publisher identity recorded alongside the pinned digest
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment plugin remove
$ ocomment plugin remove --help
Uninstall a plugin and drop its lock entry
Usage: ocomment plugin remove [OPTIONS] <NAME>
Arguments:
<NAME>
Name of the plugin to remove
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment plugin list
$ ocomment plugin list --help
List the installed plugins and their pinned digests
Usage: ocomment plugin list [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment plugin update
$ ocomment plugin update --help
Re-fetch plugins and refresh their pinned digests
Usage: ocomment plugin update [OPTIONS] [NAME]
Arguments:
[NAME]
Name of the plugin to update (default: all of them)
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment plugin verify
$ ocomment plugin verify --help
Check installed plugins against their pinned digests
Usage: ocomment plugin verify [OPTIONS] [NAME]
Arguments:
[NAME]
Name of the plugin to verify (default: all of them)
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment plugin new
$ ocomment plugin new --help
Scaffold a new plugin crate from the scanner WIT world
Usage: ocomment plugin new [OPTIONS] <PATH>
Arguments:
<PATH>
Directory to create the plugin crate in
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment completions
$ ocomment completions --help
Generate shell completions
Usage: ocomment completions [OPTIONS] <SHELL>
Arguments:
<SHELL>
Shell whose completion script is written to stdout
[possible values: bash, elvish, fish, powershell, zsh]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment coverage
$ ocomment coverage --help
Report which files a walk scanned and which it passed over, and why
Usage: ocomment coverage [OPTIONS] [PATH]...
Arguments:
[PATH]...
Files or directories to process; `-` reads standard input (default: current directory)
Options:
--staged
Read and update Git index blobs rather than treating the working tree as the source
--index-only
With `--staged`, do not attempt a uniquely mappable working-tree update
--base <REV>
Check only the working-tree files that differ from this revision's merge base with HEAD
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment tags
$ ocomment tags --help
Count the tags this tree's comments open with, against the ones it allows
Usage: ocomment tags [OPTIONS] [PATH]...
Arguments:
[PATH]...
Files or directories to process; `-` reads standard input (default: current directory)
Options:
--staged
Read and update Git index blobs rather than treating the working tree as the source
--index-only
With `--staged`, do not attempt a uniquely mappable working-tree update
--base <REV>
Check only the working-tree files that differ from this revision's merge base with HEAD
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment ratchet
$ ocomment ratchet --help
Check the tree against its ledger, or record the tree in one
Usage: ocomment ratchet [OPTIONS] [PATH]...
Arguments:
[PATH]...
Files or directories to process; `-` reads standard input (default: current directory)
Options:
--update
Rewrite the ledger to match the tree, rather than checking against it
--staged
Read and update Git index blobs rather than treating the working tree as the source
--index-only
With `--staged`, do not attempt a uniquely mappable working-tree update
--base <REV>
Check only the working-tree files that differ from this revision's merge base with HEAD
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment hook
$ ocomment hook --help
Answer an agent editing hook in the host's own protocol
Usage: ocomment hook [OPTIONS] <SURFACE>
Arguments:
<SURFACE>
Which host's hook protocol is spoken on standard input and output
Possible values:
- claude-code: Claude Code's `PreToolUse` and `PostToolUse` hooks
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment selftest
$ ocomment selftest --help
Re-run the shared corpus against this binary and report any disagreement
Usage: ocomment selftest [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment doctor
$ ocomment doctor --help
Diagnose the environment (config, git, plugins, tools)
Usage: ocomment doctor [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
ocomment man
$ ocomment man --help
Render the roff manual page to stdout
Usage: ocomment man [OPTIONS]
Options:
--config <FILE>
Read this configuration file instead of discovering `.ocomment.toml`
-h, --help
Print help (see a summary with '-h')
Policy:
--policy <POLICY>
Which classes of comment the run is allowed to remove
Possible values:
- none: Remove nothing. Every comment is kept, which is the mode for a repository that wants the style rules and not the removals
- conservative: Remove ordinary comments; keep documentation, licence notices, directives, shebangs and encoding lines (was `legal`)
- standard: Like conservative, and remove documentation, licence and copyright comments too (was `safe`)
- all: Remove every comment except shebangs, encoding lines and the directives the language itself reads
--layout <LAYOUT>
How the bytes left behind by a removed comment are laid out
Possible values:
- lines: Keep the line structure and separate tokens that would otherwise join
- columns: Pad each removed comment so the following columns do not shift
- compact: Drop lines that held only a removed comment, the whitespace it left behind, and any blank line the removal would otherwise have added to a run
--language <LANGUAGE>
Force this language instead of detecting it from path and contents
Possible values:
- rust: Rust source files
- ocaml: OCaml implementation and interface files
- c: C source and header files
- cpp: C++ source and header files
- go: Go source files
- java: Java source files, including Unicode escape translation
- javascript: JavaScript modules and scripts, including JSX
- typescript: TypeScript modules and scripts, including TSX
- python: Python source and stub files
- shell: POSIX sh, Bash, and zsh scripts
- html: HTML documents, including nested script and style elements
- css: CSS stylesheets
- jsonc: JSON with comments, including JSON5
- sql: SQL for every supported database dialect
- kotlin: Kotlin source and script files
- toml: TOML documents, including the lock files written in it
- lua: Lua chunks and LuaRocks rockspecs
- yaml: YAML documents, including the tool configurations written in it
- php: PHP scripts and templates; the inline HTML around the tags is content
- ruby: Ruby scripts, gem manifests, and the project files named after their tool
- zig: Zig source files and Zig Object Notation data
- r: R scripts and the `.Rprofile` an R session sources at start-up
- dart: Dart source files, whose block comments nest
- swift: Swift source files, whose block comments nest and whose `#/../#` is a regex
- csharp: C# source and script files, whose `#` lines are preprocessor directives
- scala: Scala source and script files, whose block comments nest and whose XML literals are opaque
- vue: Vue single-file components, whose templates are HTML with `{{ ... }}` code
- svelte: Svelte components, whose templates are HTML with `{ ... }` code
- markdown: Markdown documents, whose fenced code blocks are scanned as their named languages
- perl: Perl scripts and modules, whose quote words and regexes hide a `#`
--dialect <DIALECT>
Force this dialect of the selected language
Possible values:
- standard: The default lexical rules of the language
- jsx: JavaScript with JSX elements
- tsx: TypeScript with JSX elements
- objective-c: Objective-C extensions to C
- objective-cpp: Objective-C++ extensions to C++
- gnu-c: GNU extensions to C
- gnu-cpp: GNU extensions to C++
- cuda: CUDA extensions to C++
- posix-sh: The POSIX shell command language
- bash53: Bash 5.3
- zsh: The Z shell
- postgresql: PostgreSQL, with dollar-quoted bodies
- mysql: MySQL, including its executable versioned comments
- sqlite: SQLite
- t-sql: Microsoft Transact-SQL
- oracle: Oracle SQL and PL/SQL
- scss: SCSS
- sass: The indentation-based Sass syntax
--keep-kind <KIND>
Comma-separated comment kinds to protect on top of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--remove-kind <KIND>
Comma-separated comment kinds to remove regardless of the policy
Possible values:
- line: An ordinary comment running to the end of the line
- block: An ordinary delimited comment
- doc-line: A documentation comment running to the end of the line
- doc-block: A delimited documentation comment
- directive: A tool or language directive such as a pragma or lint control
- license: A licence or copyright notice
- html-comment: A DOM-observable HTML comment
- shebang: The interpreter line starting an executable script
- encoding: A source encoding declaration
- optimizer-hint: A compiler or database optimizer hint
- version-comment: A MySQL versioned comment that the server executes
- load-bearing: A directive the language or its build reads as part of the program, such as `//go:build`
--include-generated
Scan files another tool writes: lock files, recorded seeds, generated output
--deny-skipped[=<REASON>]
Fail when a file was passed over for one of these reasons, rather than noting it. With no reason given, the two that are holes rather than decisions: unknown-language and unreadable
Possible values:
- unknown-language: Nothing here reads this kind of file: no built-in language claimed it, and no profile or plugin was routed to it
- unreadable: The file could not be read at all
- too-large: Past `[files] max_size`
- binary: A NUL byte in the first bytes read
- language-disabled: Turned off by `[languages.<name>] enabled = false`
--force-invalid
Edit a file that failed to scan, outside the bytes the failure covers. What the scanner calls a comment inside them is a guess: the code under an unterminated block opener is reported as part of it and is not a comment
--force-protected
Remove protected comments: shebangs, encoding lines, and the directives the language or its build reads
Output:
--format <FORMAT>
Output encoding
Possible values:
- human: Every finding on one line, in the `path:line:column:` stream a pipeline greps. Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding
- review: The findings grouped by the decision each one asks for, with the edit beside it. The default everywhere, terminal or pipe
- json
- jsonl
- sarif
- github
- agent: The report as an instruction, for a reader that is going to act on it
[default: review]
--color <WHEN>
When to colour terminal output
[default: auto]
[possible values: auto, always, never]
--hyperlinks <WHEN>
When to emit terminal hyperlinks for reported paths
[default: auto]
[possible values: auto, always, never]
--no-preview
Omit the comment text from human `check` and `scan` lines and from the JSON formats
--annotation-level <LEVEL>
The level `--format github` annotates a removable comment at (default: the run's exit status)
Possible values:
- error: Annotate as an error, which fails a job that checks annotations
- warning: Annotate as a warning
- notice: Annotate as a notice, which GitHub folds away beside an error
--explain
List every comment `check` and `scan` met and name the rule and setting behind each one
--source-map
Include the byte-for-byte map from the output back to the source in the JSON formats
--trace <WHEN>
Record how the run reached its verdicts, on standard error
Possible values:
- off: Record nothing, and collect nothing to record
- human: One line per step, for a person reading a terminal
- json: One JSON object per line, against `spec/trace.schema.json`
[default: off]
--progress <WHEN>
When to draw the live scanning counter on standard error
[default: auto]
[possible values: auto, always, never]
-j, --jobs <N>
How many threads the run uses to walk, read and scan; 0 chooses one per core
--summary <FILE>
Also write the end-of-run counts to this file, as one JSON object
-q, --quiet
Drop the run summary and notes; the command's product (findings, patch, listing) is still written
-v, --verbose
Trace what is scanned and summarize every comment kind and skipped file
Reading a report
Three readers ask three different questions of the same run, and there is a format for each.
| who | what it answers | |
|---|---|---|
review | a person, deciding | what to do about these, and how many of each |
human | a pipeline, counting | where each one is, one line at a time |
agent | a program, acting | the edit, and the command to run next |
review is the default, in a pipe as much as on a terminal.
That is deliberate and it is not what most tools do: a person on a screen and an agent reading the same run through a pipe are in one conversation about one report, and a format that changes shape between them leaves each arguing from something the other cannot see.
Colour still follows the terminal, because colour is the one thing here that carries no meaning of its own.
review
$ ocomment check
NO 5 comments in 1 file · 1 file scanned · policy conservative
DECIDE make it a documentation comment 2 comments
src/budget.rs:3-4
- // The retry budget is per connection, not per request, because the
- // server counts attempts against the socket it sees.
+ /// The retry budget is per connection, not per request, because the
+ /// server counts attempts against the socket it sees.
pub struct Budget {
or keep them [policy.allow]
tags = ["NOTE"]
DECIDE do what it promises, or delete it 1 comment
src/budget.rs:9
- // TODO: make this configurable
or keep them [policy.allow]
tags = ["TODO"]
ALLOWED 1 comment this run did not report; `--explain` names the rule that kept each
Findings are grouped by the decision they ask for rather than by the rule that produced them, because five comments under one rule are not five questions. They are one question asked five times, and the answer to each is decided by the code the comment sits on — which is why the code is there.
Adjacent comment lines are one finding. A comment beside code never is: forty trailing notes on forty assignments are forty decisions, one per statement.
or keep them is the other half of every decision. A gate that can only say “delete it” is one somebody turns off the first time it is wrong about a single comment.
What it offers is a setting rather than a flag — a flag makes one run pass and a setting is a decision the repository keeps.
It will not offer you the shortest path to a green run; a gate that names the flag which silences it, at the moment it fires, is arguing against its own finding.
ALLOWED is what the run did not report. A tool that is silent when it is green leaves nobody able to check that the green is right.
--explain turns the count into the list, with the rule that kept each:
$ ocomment check --explain
ALLOWED 1 comment this run did not report
src/budget.rs:13 /// A fresh budget.
kept: policy conservative protects documentation comments, and this is a `doc-line`
--explain also puts the engine’s own verdict under each finding.
The decision above it says what to do, read from where the comment sits; the line --explain adds says why it is being asked, which is the rule and the setting behind it.
When there are thousands
Above twenty findings a group shows its shape instead of its contents: where its comments are, most first, and two of them as an example.
DECIDE make it a documentation comment 4679 comments
rust/ocomment-core/src/scanner.rs 1973
rust/ocomment/src/output.rs 499
… and 52 more files
editors/vscode/esbuild.mjs:6
- /** @type {import("esbuild").BuildOptions} */
const options = {
ocomment check rust/ocomment-core/src/scanner.rs the 2259 in one file, in full
A count with no location cannot set an order. “1,973 of these are in one file” is the difference between a project-wide problem and an afternoon, and the last line is the one thing a count never gives you: somewhere to start. It is withheld when the busiest file holds under a twentieth of the total, because that is not a place to start — it is a place that happens to be first.
human
One line per finding, in the path:line:column: stream a pipeline greps.
$ ocomment check --format human
src/budget.rs:3:1: removable line comment: // The retry budget is per connection, not per
src/budget.rs:9:1: removable line comment: // TODO: make this configurable
Kept because a pipeline written against it should not have to be rewritten, and because one line per finding is the right shape for counting even when it is the wrong shape for deciding. The end-of-run summary on standard error is the same whichever format wrote the report.
agent
The same report for a reader that is going to act on it, carrying its own schema — a machine format whose reader has to go and learn it first spends a round trip doing that.
$ ocomment check --format agent
# ocomment: 5 comments to answer for in 1 of 1 file scanned, policy conservative.
# Every line starts with a marker. DECIDE opens one question, asked of each
# FINDING under it. A FINDING names a path and the first and last line of one
# comment, which may span several, and the column when the comment does not
# open its line. `-` is what is there now, `+` what would replace it, `=` the
# code the comment is about. KEEP names a file and `|` the setting that would
# stop the question being asked. BROKEN is a file that did not parse. The
# argv lines are commands, ready to run.
DECIDE make it a documentation comment | 2 comments
FINDING src/budget.rs:3-4
- // The retry budget is per connection, not per request, because the
+ /// The retry budget is per connection, not per request, because the
= pub struct Budget {
KEEP .ocomment.toml
| [policy.allow]
| tags = ["NOTE"]
RECHECK ["ocomment","check"]
REMOVE-ALL ["ocomment","fix"] removes 5 comments, including any above that were worth keeping
Every payload line carries a marker, so text that happens to contain a colon or a keyword cannot be mistaken for structure. Commands are argv arrays rather than prose, because a copied array cannot be mistyped.
A clean run writes nothing at all, which is what makes this usable as the body of a hook decision. See Agents.
json, jsonl, sarif, github
--format json carries the whole report — every comment, its span, its line and column, its text and its verdict — against spec/result.schema.json.
Each file also says what read it.
language is which built-in grammar applied,
and it is unknown for a file no built-in language claims; read_by is the reader that answered, which for such a file is a declarative profile or a plugin that read it from end to end.
One field without the other said unknown about a file the run had just read in full:
{ "path": ".gitignore", "language": "unknown",
"read_by": { "kind": "profile", "name": "hash-line" } }
It also carries decisions: the same grouping the other two formats show, with the lines as they are, what would replace them, and the settings to add.
{
"decision": "explains-the-item-below",
"instruction": "make it a documentation comment",
"comments": 2,
"findings": [
{
"path": "src/budget.rs",
"span": { "start": 26, "end": 147 },
"line": 3,
"column": 1,
"end_line": 4,
"old": ["// The retry budget is per connection, not per request, because the"],
"new": ["/// The retry budget is per connection, not per request, because the"],
"subject": "pub struct Budget {"
}
],
"keep_instead": { "file": ".ocomment.toml", "add": "[policy.allow]\ntags = [\"NOTE\"]" }
}
span is what identifies a finding; the path and the line do not.
Two removable comments share a line whenever one of them sits beside code —
let x = 1; /* directive */ /* prose */ is two findings, asked two different questions — and named by line alone they arrive identical.
The text formats put the column after the line in that case for the same reason, and leave it off for a comment that only happens to be indented, which is the only one on its line.
A paragraph a style rule would write differently is a decision like any other, and it is in the same list.
The one difference is that the answer is already computed: new carries the bytes, and keep_instead names the setting that would stop the rule asking.
{
"decision": "wrap",
"instruction": "run `ocomment fix --tidy` and it is written for you",
"comments": 1,
"findings": [
{
"path": "src/budget.rs",
"span": { "start": 26, "end": 92 },
"line": 3,
"column": 5,
"end_line": 3,
"old": [" /// One sentence. Another one."],
"new": [" /// One sentence.", " /// Another one."]
}
],
"keep_instead": { "file": ".ocomment.toml", "add": "[style]\nwrap = \"preserve\"" }
}
The report itself carries them too, beside the comments rather than among them, because the bytes a reflow moves belong to no single comment:
{ "runs": [ { "span": { "start": 26, "end": 92 },
"line": 3, "column": 5, "end_line": 3, "end_column": 36,
"origin": "comments", "rule": "wrap",
"replacement": "/// One sentence.\n /// Another one." } ] }
origin says what the paragraph was: comments for a run of adjacent comments, document for the prose of a Markdown page.
runs is absent where the run asked for no rule about how a paragraph is broken, which is every run that set none.
--format jsonl is the same content one object per line.
--format sarif and --format github are for the tools that read them; see CI and hooks.
Both carry rewrites as well as removals — a SARIF result for a rewrite carries the replacement as its fixes[], so an editor or a review bot can apply it — and both name a rewrite as a rewrite: a format that called it a removal would be telling a reader their documentation is about to be deleted.
After a fix
fix reports what it removed and then what it left.
$ ocomment fix
OK 5 comments removed from 1 file · 1 file scanned
KEPT 1 comment, still in the files
src/budget.rs:13 /// A fresh budget.
A run that says only what it removed is a run whose judgement nobody can audit: you are told five went and have no way to check that the sixth was right to stay.
diff writes a patch under either person-facing format, because a patch is a product rather than a report about one and there is no grouped view of one.
Configuration
OComment reads .ocomment.toml version 1. Values are merged in this order:
built-in defaults, the XDG user file, the nearest project file, matching path overrides, and command-line flags.
Unknown keys and incompatible dialects are errors.
ocomment config locate, show, explain, and schema expose the resolved state.
The user file is $XDG_CONFIG_HOME/ocomment/config.toml, or the platform’s standard config directory when XDG_CONFIG_HOME is unset.
version = 1
[files]
max_size = 33554432
hidden = false
follow_symlinks = false
ignore = true
include = []
exclude = ["vendor/**"]
[policy]
mode = "conservative" # NOTE: conservative, standard, all
layout = "lines" # NOTE: lines, columns, compact
keep_kind = ["directive"]
remove_kind = []
keep_regex = ["(?i)generated"]
remove_regex = []
force_invalid = false
force_protected = false
[git]
staged = false
index_only = false
[lsp]
on_save = false
diagnostics = true
code_lens = true
[languages.sql]
dialect = "postgresql"
[[overrides]]
paths = ["fixtures/**"]
policy = "all"
layout = "compact"
Normal repository walks honor .gitignore, .ignore, and .ocommentignore,
skip hidden files, binary files, symlinks, and files larger than 32 MiB.
An explicit file or directory bypasses the hidden and size limits.
Binary and symlink safety checks still apply.
Setting files.follow_symlinks = true permits read-only check, scan,
diff, and fix --dry-run operations to follow links.
A real fix, including an interactive one, refuses the whole transaction with exit code 2 if any selected path is a symbolic link; neither the link nor its target is changed.
A command that names no path walks the current directory under those normal limits; naming a path explicitly (ocomment ., ocomment src) is a request rather than a default, so it bypasses the hidden-file and size limits.
files.include, files.exclude, and every [[overrides]].paths glob is relative to the project root — the directory holding .ocomment.toml, or the repository above it — however deep in the tree the command is run from.
Passing --config FILE replaces normal XDG and project discovery: only the built-in defaults and that file are loaded.
Its parent directory becomes the root for globs and the plugin lock, while path arguments written on the command line remain relative to the directory in which OComment was invoked.
Layouts
layout decides what a removal leaves behind.
It moves bytes, never decisions:
no comment is kept or removed because of it.
| Layout | What a removal leaves in place of the comment |
|---|---|
lines | The default. The line terminators the comment spanned, so every following line keeps its number, and a single space where the comment was all that kept two tokens apart. |
columns | As lines, plus spaces of the comment’s own display width, so every following column keeps its number as well. A tab counts to the next multiple of eight. |
compact | As lines, except that a line which held nothing but a removed comment goes away with it, terminator included, and the whitespace a removal would leave at the end of a line is trimmed. |
compact never touches a line that code survives on.
Such a line keeps its terminator and its CRLF or LF style, and a comment running across several lines with code before or after it closes up to a single line rather than joining two statements.
A surviving line keeps the ending it had in the source — the same LF or CRLF, from inside the comment if that is where it was — or no ending at all if the file stopped there without one.
Being alone on a line is judged from the original bytes, so a line holding two comments and nothing else keeps its terminator: neither of them was alone on it.
YAML has one exception, and it is the only one in any language: a block scalar decides where its body ends from the lines below it, so a whole-line comment under a body is what terminates it and anything a removal writes on that line is read back as part of the value.
There every layout takes the whole line —
lines gives up that line’s number and columns its columns rather than give up the value.
Languages states the rule in full.
Policies and layouts shows all three on one sample.
What a comment has to be, beyond its kind
A policy decides by kind, and a kind is a coarse thing to decide by.
A one-line // NOTE: explaining a decision and a forty-line essay above a function are both line, and a project that wants the first and not the second cannot say so with a policy.
[policy.allow] is the other axes.
[policy.allow]
tags = ["NOTE", "SAFETY", "INVARIANT"]
max_lines = 1
trailing = false
-
tagskeeps a comment the policy would have removed, when its text opens with one of these. Matched against the comment’s text — delimiters removed, and the*a block comment’s continuation lines carry removed with them — so one rule holds in every language. This is whatkeep_regexcannot do: a pattern is matched against the whole raw token, so^//\s*NOTEprotects a Rust comment and silently fails to protect the identical rule written in Lua, where the token opens--.A tag is a word rather than a prefix:
NOTEallows a note and does not allowNOTEBOOK. What may follow it is punctuation or space —NOTE:,TODO(alice),FIXME -— or nothing at all. -
max_linesremoves a comment, or a run of comments on consecutive lines, that occupies more lines than this. A run is measured rather than a single token because four consecutive//lines are four comments to a scanner and one paragraph to a reader, and the reader is right.A blank line ends a run: that is how a writer says the next remark is a separate remark. A limit that counted across one would be measuring the gap as well as the prose.
-
trailing = falseremoves a comment sitting after code on the same line. It closes the obvious way around a rule about comments above code, which is to put the comment beside it instead.
What these rules reach, and what they do not
They cut across the policy rather than under it: a comment failing one is removed whatever the policy said about its kind, a tag included. A tagged comment still has to be short enough and still may not sit beside code.
Two things are out of their reach, and both for the same reason — the rules are about commentary, and neither of these is commentary.
A comment somebody named outright. keep_kind names a kind and keep_regex names the bytes; both are a project saying keep exactly this,
and a shape rule is a project saying keep things like this. The specific wins.
This repository pins every GitHub Action to a SHA and writes the version beside it as a comment that Dependabot rewrites — a keep_regex names it, and it has to sit beside the line it annotates.
A comment that is not prose. Documentation comments and licence notices are as long as their content requires.
A directive is addressed to a tool, and a tool reads it where it sits: x = 1 # noqa silences a warning about that line and silences nothing a line above it.
The protections no policy reaches — a shebang, an encoding line, a directive the language or its build reads — are out for the reason they are always out.
How a comment that survives is written
The rules above decide what stays.
[style] decides how what stays reads, and it is a table of its own for that reason: a comment that fails one of the rules above is removed, and a comment that fails one of these is rewritten.
One table whose entries have two different consequences is a table nobody can add to safely.
[style]
wrap = "sentence"
space_after_marker = true
trailing_whitespace = false
Every rule here is off unless you turn it on. A formatter that starts reformatting a repository because it was installed is a formatter somebody uninstalls.
-
wrapdecides where the line breaks in a paragraph of comment prose go."preserve"is the default and leaves every break where it is."unwrap"undoes a break that only exists to keep a line short."sentence"undoes those and puts one back after every sentence, so a diff reviews one sentence at a time and a line break means something.The unit is the run, not the comment. Four consecutive
///lines are four comments to a scanner and one paragraph to a reader, and joining two of them moves the newline and the indentation between them — bytes that belong to neither comment. That is why a rewritten run is reported as one finding rather than as several, and why it is the one rule whose verdict is not any comment’s.A break after a clause is left where its writer put it. A comma, a colon or a dash ends a clause, the rule allows a break after one, and a fixer that removed breaks its own checker accepts would not be a fixer whose output is its checker’s fixed point.
A great deal is passed through byte for byte, and deliberately: a fenced code block, an indented example, a table, a block quote, a heading — which in a Rust doc comment is a rustdoc section — a list item’s own indentation, a documentation tag such as
@param, and a link reference definition. A formatter that reflowed any of those has not tidied a comment; it has broken the page the comment was.A sentence ends at
。,!or?wherever they occur, and at.,!or?only where white space follows and the word in front is not one that always carries one. That is what tells a sentence from a host name, a version number, an abbreviation and an initial in a name. Two lines of Japanese are joined without a space put between them. -
space_after_marker = truerewrites//textas// text. It says nothing about a comment that already has a space, and nothing about a marker with no text after it: a bare//is a blank line in a paragraph rather than a comment missing its space. It is deliberately timid about what counts as text — it acts only when the first character is neither white space nor ASCII punctuation — so a ruler like////////or#####or//------comes back unchanged. -
trailing_whitespace = falsestrips white space from the end of every line a comment covers, the last one included. A line comment’s span ends where its text ends, so the spaces// notetrails are inside it. What a removal leaves behind is the layout’s business and is not touched here.
An [[overrides]] entry may carry its own [style], which replaces the global one whole rather than merging into it, for the reason [policy.allow] does.
What the style rules reach
Almost the mirror of the rules above, and the one place they disagree is the point.
| Kind | [policy.allow] | [style] |
|---|---|---|
line, block, html-comment | yes | yes |
doc-line, doc-block | no | yes |
license | no | no |
directive, shebang, encoding, load-bearing, optimizer-hint, version-comment | no | no |
A documentation comment is exempt from the length rule because it is documentation — it is as long as its content requires. That same fact is why it is the first thing the style rules should reach: it is the prose in a repository that most readers actually read, and it is the prose nobody has a tool for.
A licence notice is out, and out more firmly than anything else. It is a legal text quoted verbatim, and verbatim is the whole of its value; a formatter that tidied one would be changing a document the project does not own. The directives and the preamble are out for the reason they are always out: a tool reads them, a tool is not a reader, and rewriting bytes something parses is how a tidy-up changes what a build does.
A comment whose bytes are not valid UTF-8 is never rewritten. The engine does not decode a whole source, and a boundary guessed at inside bytes it could not read is how a formatter corrupts a file it was asked to tidy.
Removing nothing
mode = "none" is the policy for a repository that wants the style rules and not the removals.
[policy]
mode = "none"
[style]
space_after_marker = true
trailing_whitespace = false
It sits at the weak end of the scale the other three already form, and it answers before the kind table rather than inside it, so every kind is kept for the same reason.
Saying this used to mean listing every comment kind under keep_kind, which is a setting that has to be revisited each time a kind is added: it said “these twelve kinds” when what it meant was “all of them”.
It is the policy default and not the first word.
remove_kind and remove_regex still name comments outright, and a comment they name still goes.
Taking stock of the convention
$ ocomment tags
INVARIANT 101
NOTE 1482
PERF 7
! XXX 4
Allowed and never written: SAFETY.
Written and not allowed: XXX (4). These are comments this run removes today;
add one to `[policy.allow] tags` to keep it, or to `[policy.allow.expiry]` to
keep it for a while.
A convention drifts in two directions and the report looks both ways.
A tag nobody writes any more is a line of configuration that protects nothing and reads like a rule; a tag people write that nobody configured is a comment the run removes today, which is usually the first anybody hears of it.
The ! is the second kind.
It reports rather than gates, as ocomment coverage does: what to do about an unconfigured tag is a decision about that tag, and a run that failed would be making it for you.
--format json gives the same four lists as fields.
The question it asks is not quite the one tags matches.
The rule asks does this comment carry the tag NOTE?, and // NOTEBOOK entry does not; the inventory asks what word does this comment open with?, and the answer there is NOTEBOOK — which is what you want to see, because that comment is one the run removes and the listing is where you would find out.
Tags that are promises
A TODO is not the same kind of thing as a SAFETY.
One records why the code is the way it is and is true for as long as the code is; the other says somebody will do something, and saying so is not doing it.
A rule that treats them alike has to pick a bad answer.
Forbid the TODO, and nobody obeys it — the note is lost along with the nagging.
Permit it, and the repository ends up carrying one from four years ago that everybody has learned to read past.
[policy.allow.expiry]
TODO = "30d"
FIXME = "14d"
A tag here is allowed exactly as one in tags is, until the line carrying it reaches that age — and is a finding after that, every run, until somebody either does it or deletes it:
$ ocomment check --explain src/pool.rs
src/pool.rs:44:1: removable line comment: // TODO: retry on timeout
removed: `TODO` is a promise with 30d to keep it, and this line is 61d old ([policy.allow] in .ocomment.toml); do it, or delete the comment
...
1 comment past its deadline: 1 TODO. Do it or delete it.
The age is the age of the commit that introduced the line, read from git blame.
Writing one therefore costs nothing: a TODO you typed a minute ago belongs to no commit and has not started counting, and "0d" means the deadline starts at the next commit.
Ages are written "14d", "2w", or as a bare number of days; an hour is not a meaningful deadline for a line of source and a month is not a fixed number of days, so neither is accepted.
Three consequences worth knowing:
ocomment fixdeletes an overdue promise, because the rule is the same rule andfixapplies the rules. That is the half of “do it or delete it” a machine can do.ocomment check --stagednever reports one. A staged run judges the lines the commit adds, and a line the commit adds is new. The pre-commit gate is about what you are writing; the deadline is about what the repository has been carrying.- No repository, no clock. Outside a Git repository, or on a file Git does not track, the age cannot be read and the comment is left alone. A deadline nobody can measure has not passed.
Getting to a rule you cannot turn on today
A repository with eleven thousand comments and a rule it wants to reach has two bad options: turn the rule on and fail every commit, or leave it off and never arrive. A ledger is the third.
[ratchet]
ledger = ".ocomment-ledger"
ocomment ratchet --update records what each file holds today.
ocomment ratchet checks the tree against that record and fails when a file holds more — and fails when it holds fewer, asking for the ledger to be updated.
That second direction is what makes it different from a baseline file. A baseline forgives what it recorded and says nothing once the work is done; a ledger that only noticed growth would eventually describe a repository that no longer exists. Checked both ways, the number in the file is always the number in the tree, and the distance left to go is readable at a glance:
# 1674 comment(s) in 78 file(s) left to remove.
3 .dockerignore
54 src/legacy/session.c
It is deliberately not a suppression mechanism. The entries carry no reasons, no expiry dates and no per-comment granularity — a ledger is a measurement, and the moment it starts explaining itself it has become a second configuration file arguing with the first.
This repository does not run one, and that is deliberate.
It reached its own rules by fixing every comment that broke them rather than by recording how many did: ocomment over this tree exits 0, and the CI job that runs it is a gate rather than a report.
A ledger is for a repository that cannot get there today; a tool’s own repository does not get to be that repository.
Files another tool writes
A lock file, a recorded seed list, a code generator’s output: something else wrote the comments in these and will write them again.
Removing one is editing a tool’s file, and it is the class most likely to be auto-fixed without being read, because nobody opens a generated file before committing it.
Coverage of a file you should not touch is worse than skipping it — a skip is visible in the summary, and a removal in a generated file is a diff somebody waves through —
so --deny-skipped does not refuse these, and --include-generated scans them anyway for the run that means it.
spec/generated.toml is the catalogue.
It lists whole file names, because a lock file’s name is a convention of the tool that writes it; suffixes, matched without the dot and case-insensitively; and the headers a generated file announces itself with, searched case-insensitively in the first header_lines lines only.
That bound is what makes the header search usable at all.
A file that lists these markers would otherwise claim itself, and two in this repository do —
spec/directives.toml names C#’s <auto-generated, and the catalogue names all of them.
A generated file declares itself in its first few lines, because that is where the reader it is warning will look, so the bound costs nothing real and rules out every catalogue, changelog and piece of documentation that merely mentions one.
A lock file, a recorded seed list, a code generator’s output: the comments in these belong to the tool that wrote them and come back on its next run. Removing one is editing a tool’s file, and it is the class most likely to be auto-fixed without being read, because nobody opens a generated file before committing it.
OComment passes them over, under a skip reason of their own that --deny-skipped does not refuse — being passed over is what should happen to them.
A file is recognised by name (Cargo.lock, package-lock.json,
*.proptest-regressions, and the rest of spec/generated.toml) or by the header a format uses to say so: @generated, DO NOT EDIT, Code generated by and their neighbours, in the first five lines only.
The line bound matters.
Without it a file that lists those markers claims itself — this repository’s own catalogue of protected directives names C#’s <auto-generated, and did exactly that.
A generated file declares itself at the top, where the reader it is warning will look.
[files]
include_generated = true # NOTE: scan them anyway
--include-generated does the same for one run.
Declarative language profiles
Profiles cover delimiter-based syntaxes. Empty definitions are rejected while loading configuration, and so are two delimiters spelled the same way — nothing could choose between them.
One comment token being the start of another is not ambiguous and is not refused.
It is how a language spells a documentation comment: Gleam writes //, /// and ////, Haskell writes -- and -- |, WIT writes // and ///.
The scan takes the longest token that matches, so the order the delimiters are written in carries no meaning and an author cannot get it wrong.
[profiles.lisp]
extensions = ["lisp", "cl"]
[[profiles.lisp.line_comments]]
start = ";"
kind = "line"
[[profiles.lisp.block_comments]]
start = "#|"
end = "|#"
nested = true
kind = "block"
[[profiles.lisp.strings]]
start = "\""
end = "\""
escape = "\\"
[[profiles.lisp.protected_patterns]]
contains = "ocomment: keep"
reason = "local directive"
[[profiles.lisp.protected_patterns]]
contains = "lisp-build:"
reason = "read by the build"
tier = "load-bearing"
A protected pattern says how strongly it asks.
tier = "tool" is the default and the weaker one: the comment is recorded as a directive, every policy but all keeps it, and all is entitled to take it.
tier = "load-bearing" records it as load-bearing, which no policy removes and only --force-protected does.
The distinction is the one the built-in languages already draw, and a profile needs it for the same reason. A profile describes a syntax OComment has no scanner for, so its author is the only one who knows whether a marker is read by their toolchain or by their linter — and removing the first changes what the build produces while removing the second changes what a tool reports. Declaring the stronger tier is deliberate: a pattern that says nothing gets the weaker one, which is what every profile written before this field meant.
Complex lexical grammars should use a WASM scanner plugin instead.
A plugin returns the comment kind itself, so it can return load-bearing directly.
The profiles OComment ships with
spec/profiles.toml carries a few, and ocomment profiles lists them — with the ones this project declared or replaced marked as its own.
They have a listing of their own rather than a place in ocomment languages because they are not built-in languages and are not meant to become one: a built-in language is a hand-written scanner, and a format earns that when its lexical form has something a delimiter list cannot say — a string that hides a comment token, a nesting rule, an embedded language.
The formats here have none of that, so a profile says everything there is to say, and says it in data rather than in a match arm that would then have to be written twice, once in Rust and once in OCaml.
They exist because of what ocomment coverage reported without them.
A gate that says “no removable comments in 143 files” while 25 files were never opened is a gate over 85% of a repository, and the files it was missing here were .gitignore, CODEOWNERS, dune and OComment’s own .wit interface — every one of which holds comments.
A profile added to a build changes what a gate covers. Files the previous version passed over in silence are read by the next one, and the comments in them are reported against the same policy as everything else — the reader does not change the verdict, and a # line of prose is prose wherever it sits.
That is deliberate: a rule that softened for files a profile happened to read would mean the same bytes getting different answers from different readers.
What must not be silent is the change in what is read, so ocomment coverage names the reader for every file it scanned, and the count that moves out of no built-in language for this file and into read by the \hash-line` profile` is the size of the change.
A project that wants the prose in those files kept says so where every other such decision is said:
[[overrides]]
paths = [".gitignore", ".gitattributes", ".github/CODEOWNERS"]
keep_kind = ["line"]
hash-lineis the pattern-list family:CODEOWNERS, theignorefiles,.editorconfig, and OComment’s own ledger. A#opens a comment here only as the first byte of its line. Anywhere else it belongs to the pattern —file#namenames a file with one in it,\#literalis how a pattern that starts with one is written, and a#after the pattern is still the pattern. Reading any of those as a comment wrote a shorter pattern back, so a defaultfixquietly stopped ignoring what the line named.hash-anywhereis the other half:.gitmodulesand.opam, whose syntaxes do let a comment open after a value. It is separate rather than sharing the rule above because the two rules disagree about the same byte, and a profile that has to be right about both is right about neither.duneis a Lisp:;opens a line comment,"..."is a string with backslash escapes so a;inside one is text, and#|...|#nests.go-moduleisgo.modandgo.work://to end of line, no block comment, no string form a//could hide inside. Two markers in them are kept, at the strength each has earned.// indirectis addressed togo mod tidy, which puts it back, so it is a directive — every policy butallkeeps it.// Deprecated:is put back by nothing: before the module declaration it is whatgo getwarns with and what a proxy serves to everyone downstream, and inside aretractblock it is the reasongo list -m -retractedprints, so no policy reaches it. A profile matches a substring, so each also claims a comment that merely opens with the same words — which is the tier’s other job, because a line of prose caught by// indirectis kept by a gate rather than put beyond every policy there is.go.sumandgo.work.sumare not here: they are lock files, andspec/generated.tomlis where a file another tool writes belongs.witis the Component Model’s interface language, which OComment’s own plugin contract is written in.///documents the item below it and//is a remark, and the two are listed side by side. They could not be, once: a delimiter that was the start of another was refused as ambiguous, so WIT’s documentation comments were reported as ordinary line comments and a default policy was entitled to remove them.gleamhas three line comment forms and nothing else://is a remark,///documents the item below it, and////documents the module. All three share a prefix, which is exactly what the longest-token rule exists for.haskellis the one that needed new vocabulary. Its comment opener is a run of dashes, and whether it opens a comment depends on what follows the run:-- xis a comment,-->and---->are operators, and---xis a comment again.forbidden_afterstates that clause (Haskell 2010 §2.2). Haddock marks only the first line of a documentation comment and continues it with the plain opener, so the profile setsdoc_continuation; without it the conservative policy would keep the first line of a published page and remove the rest.{-|and{-both close with-}and both nest, and the nesting count is kept against the closing token rather than the opener — a remark nested inside documentation still has to be got past. Literate Haskell is deliberately absent: a.lhsfile is a different format, where code is what is marked up rather than prose.
A [profiles.<name>] entry in your configuration wins over the shipped profile of the same name, so a project that disagrees with one can replace it rather than work around it.
Policies and layouts
Two settings decide what a run does, and they are independent of each other. A
policy decides which comments may be removed at all. A layout decides
what is left behind where a removed comment used to be. Both are available as
--policy and --layout on the command line, as mode and layout under
[policy] in .ocomment.toml, and as policy and layout on an
[[overrides]] entry that matches a path.
Every before-and-after pair on this page was produced by running the sample
below through ocomment strip, so the outputs are the real bytes, trailing
spaces included.
The sample
// SPDX-License-Identifier: MIT OR Apache-2.0
// rustfmt::skip
/// Adds two numbers.
pub fn add(a: u32, b: u32) -> u32 {
let total = a + /* NOTE: widen */ b; // TODO: check for overflow
/* NOTE: Everything from here down is one block comment
that runs across three lines, so each layout has
something to show. */
total
}
It carries a licence header, a tool directive, a documentation comment, an inline block comment between two tokens, a trailing comment, and a block comment that spans several lines.
What each policy removes
Each of these is ocomment strip --language rust --policy <mode> reading
the sample on standard input.
none
// SPDX-License-Identifier: MIT OR Apache-2.0
// rustfmt::skip
/// Adds two numbers.
pub fn add(a: u32, b: u32) -> u32 {
let total = a + /* NOTE: widen */ b; // TODO: check for overflow
/* NOTE: Everything from here down is one block comment
that runs across three lines, so each layout has
something to show. */
total
}
conservative
// SPDX-License-Identifier: MIT OR Apache-2.0
// rustfmt::skip
/// Adds two numbers.
pub fn add(a: u32, b: u32) -> u32 {
let total = a + b;
total
}
standard
// rustfmt::skip
pub fn add(a: u32, b: u32) -> u32 {
let total = a + b;
total
}
all
pub fn add(a: u32, b: u32) -> u32 {
let total = a + b;
total
}
none is the mode for a repository that wants the style rules and not the
removals: it returns the sample unchanged.
conservative and standard differ over the licence header alone, and all
is the only one that takes the // rustfmt::skip directive out.
all still refuses to touch a shebang or an encoding preamble until
--force-protected is given as well; see
Why was this comment kept?.
What each layout leaves behind
Each of these is ocomment strip --language rust --layout <layout>
reading the same sample, under the default conservative policy.
lines
// SPDX-License-Identifier: MIT OR Apache-2.0
// rustfmt::skip
/// Adds two numbers.
pub fn add(a: u32, b: u32) -> u32 {
let total = a + b;
total
}
columns
// SPDX-License-Identifier: MIT OR Apache-2.0
// rustfmt::skip
/// Adds two numbers.
pub fn add(a: u32, b: u32) -> u32 {
let total = a + b;
total
}
compact
// SPDX-License-Identifier: MIT OR Apache-2.0
// rustfmt::skip
/// Adds two numbers.
pub fn add(a: u32, b: u32) -> u32 {
let total = a + b;
total
}
lines and columns keep the line count of the file: a comment that
spanned three lines is replaced by something that still spans three
lines, so a line number in a stack trace or a git blame still points
at the same statement. columns additionally keeps every following
column in place by padding with spaces, which is what a table of
aligned initialisers or a column-sensitive language wants, at the cost
of trailing whitespace that a formatter may then remove.
compact is the layout that gives line numbers up. A line that held
nothing but a removed comment goes away with it, terminator included,
and the whitespace a removal would leave at the end of a line is
trimmed. Code keeps its own lines: a comment sharing a line with code
leaves that line, its terminator and its CRLF or LF style as they
were. A surviving line keeps the ending it had in the source - the
same LF or CRLF, from inside the comment if that is where it was - or
no ending at all if the file stopped there without one.
Which one a formatter accepts
compact, and only compact. This is measured rather than argued:
rust/ocomment-core/tests/layout_format.rs strips a source that
gofmt and rustfmt already call normal, in every position a
comment can sit, and asks each formatter about the result. lines
and columns never conform and are not meant to - the empty line
and the padding are the promise - so a pipeline that runs
gofmt -l or cargo fmt --check beside OComment wants compact.
The test pins that table in both directions, so a layout that stopped conforming fails and so does one that started: the second is a layout that has quietly changed what it promises.
Where they are set
version = 1
[policy]
mode = "conservative"
layout = "lines"
[[overrides]]
paths = ["generated/**"]
policy = "all"
layout = "compact"
The flag wins over the file, and an [[overrides]] entry whose globs
match the path wins over [policy]. ocomment config explain prints
the resolved values and where each came from, and ocomment --explain
names the rule and the setting behind any one comment.
Why was this comment kept?
A run that reports fewer comments than you expected has not lost them: it
decided to keep them, and it can say which rule decided. --explain lists every
comment a human check or scan met, the kept ones included, and puts the rule
and the setting behind that rule on the line under each one.
--explain annotates a report of comments, so it belongs to the two commands
that write one. Asking for it with --format json, or any other machine format,
or with a command that writes no report of comments, is a usage error rather
than a flag that quietly does nothing.
Ask the binary
This project has a generated directory, a JavaScript file with a lint
directive, and a house rule that a NOTE: comment is deliberate:
version = 1
[policy]
mode = "conservative"
keep_regex = ['^//\s*NOTE\b']
[[overrides]]
paths = ["gen/**"]
policy = "all"
keep_regex = ["(?i)generated"]
.ocomment.toml
gen/api.rs
src/app.js
src/lib.rs
--explain names the rule and the setting behind every comment it meets —
0 comments in this project:
$ ocomment check --explain
NO 1 comment in 1 file · 2 of 3 files scanned · policy conservative
DECIDE do what it promises, or delete it 1 comment
src/app.js:2
- console.log(1); // TODO: drop
removed: policy `conservative` removes ordinary comments ([policy] in .ocomment.toml)
or keep them [policy.allow]
tags = ["TODO"]
ALLOWED 2 comments this run did not report
src/app.js:1 // eslint-disable-next-line no-console
kept: tool or language directive `eslint`; use --remove-kind directive or --policy all to remove it
src/lib.rs:1 // NOTE: the retry budget is what the server documents.
kept: matched keep_regex #0 `^//\s*NOTE\b` ([policy] in .ocomment.toml)
──────────────────────────────────────────────────────────────────────
ocomment fix removes all 1, including anything above you meant to keep
Found 1 removable comment in 1 file (2 files scanned). Run `ocomment fix` to remove it. 1 file skipped (generated file: 1; use -v to list).
Read the second line of each pair as the answer. A keep_regex match kept the
first comment even though [[overrides]] puts the whole gen/** tree under the
all policy; a marker the scanner recognises kept the lint directive with no
configuration at all; and the setting in brackets is named where it was written,
so [policy] in .ocomment.toml and [[overrides]] #0 point at the two places
that decided this run. A comment no setting decided is left with the flag that
would overrule the built-in rule instead.
What each policy removes
spec/directives.toml is the shared table both implementations are
checked against, and this is that table. --policy and [policy] mode
choose a column, and the settings in the next sections move a single
comment out of the column its kind lands in.
| Comment kind | none | conservative | standard | all |
|---|---|---|---|---|
line | kept | removed | removed | removed |
block | kept | removed | removed | removed |
doc-line | kept | removed | removed | removed |
doc-block | kept | removed | removed | removed |
license | kept | kept | removed | removed |
directive | kept | kept | kept | removed |
load-bearing | kept | kept | kept | kept unless --force-protected |
html-comment | kept | kept | kept | removed |
shebang | kept | kept | kept | kept unless --force-protected |
encoding | kept | kept | kept | kept unless --force-protected |
optimizer-hint | kept | kept | kept | kept unless --force-protected |
version-comment | kept | kept | kept | kept unless --force-protected |
Markers that survive a removal
Some comments are not commentary at all: a build tag, a lint control, or an optimiser hint changes what a compiler, a linter, or a database does with the file. Each row below is scanned by the binary that built this page, so the table cannot claim a protection that is not there.
The Kept because column says which of two protections a marker has,
and the difference is what --policy all does to it. A marker kept as
a tool or language directive is addressed to something that reports
on the code – a linter, a formatter, a coverage tool – so losing it
makes that tool noisier and leaves the program alone, and all is
free to take it. A marker kept as required by the language or its
build is read by the language itself, by its compiler or by its
package manager, and losing it changes what compiles or what the code
does: //go:build linux decides whether the file is compiled at all,
and // swift-tools-version: decides whether a Package.swift is a
manifest. No policy is offered that choice, and --force-protected
is the only way to give one up.
| Marker | Language | Written as | Kind | Kept because |
|---|---|---|---|---|
go: | go | //go:build linux | load-bearing | required by the language or its build |
+build | go | // +build linux | load-bearing | required by the language or its build |
triple-slash-reference | typescript | /// <reference path="types.d.ts" /> | load-bearing | required by the language or its build |
syntax= | shell | # syntax=docker/dockerfile:1 | load-bearing | required by the language or its build |
frozen_string_literal: | ruby | # frozen_string_literal: true | load-bearing | required by the language or its build |
warn_indent: | ruby | # warn_indent: true | load-bearing | required by the language or its build |
shareable_constant_value: | ruby | # shareable_constant_value: literal | load-bearing | required by the language or its build |
@dart | dart | // @dart = 2.12 | load-bearing | required by the language or its build |
swift-tools-version: | swift | // swift-tools-version:5.9 | load-bearing | required by the language or its build |
//> using | scala | //> using scala "3.3.0" | load-bearing | required by the language or its build |
optimizer-hint | oracle | /*+ index(t) */ | optimizer-hint | required by the language or its build |
version-comment | mysql | /*!40101 SET NAMES utf8 */ | version-comment | required by the language or its build |
webpack | javascript | /* webpackChunkName: "x" */ | load-bearing | required by the language or its build |
vite-ignore | javascript | /* @vite-ignore */ | load-bearing | required by the language or its build |
#__PURE__ | javascript | /*#__PURE__*/ | load-bearing | required by the language or its build |
@__PURE__ | javascript | /*@__PURE__*/ | load-bearing | required by the language or its build |
#__NO_SIDE_EFFECTS__ | javascript | /*#__NO_SIDE_EFFECTS__*/ | load-bearing | required by the language or its build |
shebang | shell | #!/bin/sh | shebang | required source preamble |
encoding | python | # -*- coding: utf-8 -*- | encoding | required source preamble |
sourceMappingURL | javascript | //# sourceMappingURL=bundle.js.map | directive | tool or language directive |
sourceURL | javascript | //# sourceURL=bundle.js | directive | tool or language directive |
lint-and-formatter | javascript | // eslint-disable-next-line no-eval | directive | tool or language directive |
type-checker | python | # type: ignore | directive | tool or language directive |
hadolint | shell | # hadolint ignore=DL3018 | directive | tool or language directive |
:schema | toml | #:schema https://example.test/pyproject.json | directive | tool or language directive |
taplo: | toml | # taplo: array_auto_expand = false | directive | tool or language directive |
---@diagnostic | lua | ---@diagnostic disable-next-line: undefined-global | directive | tool or language directive |
luacheck: | lua | -- luacheck: ignore 212 | directive | tool or language directive |
selene: | lua | -- selene: allow(unused_variable) | directive | tool or language directive |
stylua: | lua | -- stylua: ignore | directive | tool or language directive |
luacov: | lua | -- luacov: disable | directive | tool or language directive |
yaml-language-server: | yaml | # yaml-language-server: $schema=https://example.test/schema.json | directive | tool or language directive |
yamllint | yaml | # yamllint disable-line rule:line-length | directive | tool or language directive |
renovate: | yaml | # renovate: datasource=docker depName=alpine | directive | tool or language directive |
checkov:skip | yaml | # checkov:skip=CKV_AWS_20:public by design | directive | tool or language directive |
trivy:ignore | yaml | # trivy:ignore:AVD-AWS-0089 | directive | tool or language directive |
nosec | yaml | # nosec | directive | tool or language directive |
kics-scan | yaml | # kics-scan ignore-line | directive | tool or language directive |
@schema | yaml | # @schema type: string | directive | tool or language directive |
phpcs: | php | // phpcs:ignore Squiz.Commenting.FunctionComment | directive | tool or language directive |
@phpstan-ignore | php | // @phpstan-ignore-next-line | directive | tool or language directive |
@psalm-suppress | php | /** @psalm-suppress InvalidReturnType */ | directive | tool or language directive |
@codeCoverageIgnore | php | // @codeCoverageIgnoreStart | directive | tool or language directive |
rubocop: | ruby | # rubocop:disable Style/Documentation | directive | tool or language directive |
standard: | ruby | # standard:disable Style/StringLiterals | directive | tool or language directive |
typed: | ruby | # typed: strict | directive | tool or language directive |
zig fmt: | zig | // zig fmt: off | directive | tool or language directive |
styler: | r | # styler: off | directive | tool or language directive |
nocov | r | # nocov start | directive | tool or language directive |
dart format | dart | // dart format off | directive | tool or language directive |
ignore: | dart | // ignore: unused_local_variable | directive | tool or language directive |
ignore_for_file: | dart | // ignore_for_file: unused_import | directive | tool or language directive |
swiftlint: | swift | // swiftlint:disable force_cast | directive | tool or language directive |
swiftformat: | swift | // swiftformat:disable redundantSelf | directive | tool or language directive |
swift-format-ignore | swift | // swift-format-ignore | directive | tool or language directive |
<auto-generated | csharp | // <auto-generated/> | directive | tool or language directive |
ReSharper | csharp | // ReSharper disable once UnusedMember.Local | directive | tool or language directive |
csharpier-ignore | csharp | // csharpier-ignore | directive | tool or language directive |
formatter: | java | // @formatter:off | directive | tool or language directive |
nosonar | java | // NOSONAR | directive | tool or language directive |
pylint: | python | # pylint: disable=invalid-name | directive | tool or language directive |
pragma: | python | # pragma: no cover | directive | tool or language directive |
$non-nls | java | //$NON-NLS-1$ | directive | tool or language directive |
checkstyle: | java | // CHECKSTYLE:OFF | directive | tool or language directive |
no critic | perl | ## no critic | directive | tool or language directive |
use critic | perl | ## use critic | directive | tool or language directive |
cppcheck-suppress | c | // cppcheck-suppress nullPointer | directive | tool or language directive |
lint:ignore | go | //lint:ignore SA1000 the pattern is checked | directive | tool or language directive |
format: | scala | // format: off | directive | tool or language directive |
--remove-kind directive or --policy all removes a directive anyway.
A shebang and an encoding preamble need --force-protected on top of
--policy all, because removing one changes how the file is executed
or decoded rather than how it reads.
The one keep no setting reaches
Every rule above is about what a comment says. One is about where it sits, and it is the only keep no flag overrules:
k: |
a
# ends the block
# yamllint disable
z: 1
A YAML block scalar decides where its body ends from the lines below
it, so # ends the block is not commentary: it is what terminates the
body, and the directive under it is indented deep enough to be content
of that body. Remove the terminating line – and a removal there takes
the whole line, which is the least it can take – and the directive
rejoins the scalar, so k changes from a to two lines. No removal
preserves the value, so the comment stays, and --explain writes the
reason under it:
k.yaml:3:1: kept line comment: # ends the block
kept: it separates a `yaml` block scalar from the kept comment below it; the comment under it has to go first
--policy all is not a way out: it removes the directive as well, and
with nothing left standing under the body both comments go. What holds
the first one in place is whatever comment survives under it, so that
is the line to take first. A surviving comment shallower than the
body’s own content ends the scalar on its own and keeps nothing above
it – see Languages for the depth this is measured at.
Keeping more
version = 1
[policy]
keep_kind = ["doc-line", "doc-block"]
keep_regex = ['^(//|\(\*|/\*|#)\s*(NOTE|SAFETY|INVARIANT|PERF)\b']
[[overrides]]
paths = ["vendor/**"]
policy = "conservative"
keep_kind names whole kinds from the first column of the table above,
so keep_kind = ["doc-line", "doc-block"] takes documentation out of
reach of every policy. keep_regex matches the comment token itself,
delimiters included, which is what lets ^// anchor a rule to the start
of the comment; this repository uses exactly that to require a tag on
every explanatory comment it keeps. Both are lists, and --explain
reports the index it matched, so keep_regex #0 is the first entry.
That the pattern is matched against the whole token is the detail
worth reading twice, because a pattern written against the text
inside the comment silently protects nothing: ^\s*rustfmt::
anchors in front of a // that is always there and can never
match. A setting that protects nothing is the one failure that
looks like success, so a run that walks a directory names every
keep_kind, remove_kind, keep_regex and remove_regex that
met no comment:
$ ocomment check
keep_regex #0 `^\s*rustfmt::` matched none of the 2 comments this run scanned; it is set in [policy] in .ocomment.toml
A pattern is matched against the whole comment token, so `^` is the comment's own first byte — the `//`, `#` or `/*` — and not the text after it.
The report goes to standard error beside the summary, so a
--format json consumer keeps a clean pipe, and -q drops it with
every other note. A run over named files stays quiet: a walk is the
caller saying everything under here, so a pattern that met
nothing in it is a pattern doing no work, while a run over one file
is a question about that file and a pattern with nothing to say
about it has not thereby failed.
The same walk names an [[overrides]] block whose globs matched no
file. A pattern that protects nothing is one failure that looks
like success; a path glob that matches nothing is the other, and it
is the one that fails in the direction of removing more. An
override is how a project exempts files from a rule it keeps
everywhere else, so a glob a character off the name of a file
sitting right there leaves that rule in force over exactly the
files somebody decided it should not cover:
$ ocomment check
[[overrides]] #0 (`.gitignor`) matched none of the 3 files this run reached, so everything it sets was left unapplied
Keeping less
--remove-kind is the mirror of keep_kind and removes a kind the
policy would have kept. --policy all removes every kind at once.
--force-protected is needed on top of it for a shebang or an encoding
preamble. --force-invalid edits the part of a file that scanned
when the rest of it did not, and is the only one of these that is
about a broken file rather than about a policy. It stops at the
failure: past that point the scanner is guessing where tokens end,
and the bytes it calls a comment may be code. The verdicts on those
comments still stand in the report – they are removable, and they
are still in the file, which is what a file that does not lex earns.
When the answer is still surprising
ocomment config explain prints the resolved configuration and where
each value came from, naming every kind and pattern it resolved with
the index the reports above count from, ocomment doctor reports the
environment around it, and ocomment scan --format json gives the
span, the line, the column and the text of every comment for a tool
to read.
--explain and --trace answer different questions
--explain is about one comment: the rule that decided it and the
setting behind that rule, printed under the finding it belongs to. It
is part of the report, so it goes to standard output and only the two
commands that write a report of comments accept it.
--trace is about the run: which layer of configuration applied, what
evidence chose each file’s language, which files were never scanned and
why, what was decided for every comment, and which edits were planned
from those decisions. It is a diagnostic rather than a product, so it
goes to standard error and every command accepts it.
That separation is what lets the two be combined with anything else:
--trace json beside --format json leaves the document on standard
output byte-for-byte identical to the one the same run writes without
it. Standard error also carries the run summary, so a reader that needs
every line to parse should add --quiet:
$ ocomment check --quiet --trace json 2>trace.jsonl >/dev/null
$ head -2 trace.jsonl
{"event":"config-resolved","root":"/repo","sources":["built-in defaults"]}
{"event":"file-detected","path":"src/main.rs","language":"rust","dialect":"standard","how":"extension","bytes":18}
The stream is described by spec/trace.schema.json. What it does not
record is the scanner’s recursion into an embedded language — a
<script> body read as JavaScript, a Markdown fence read as the
language its info string names. Those comments are reported at their
byte span in the outer file, as they are everywhere else.
Languages and dialects
spec/languages.toml is the canonical table. The table below is generated from
that file, while the published pre-commit hooks pass every text file to the CLI
detector so reserved names and extensionless shebang scripts are covered too.
The binary embeds that same file, so ocomment languages prints these rows in
columns and ocomment languages --format json prints them as JSON.
A dialect changes the lexical rules rather than the file type: --dialect mysql is still SQL, and only that dialect treats /*!40101 ... */ as
something the server executes rather than as a comment.
A language is chosen from the file extension, and --language overrides that
for a run — which is what ocomment strip needs, because standard input has no
name. --dialect picks the dialect for the same run, and [languages.<name>] dialect = "..." in .ocomment.toml picks one for everybody working in the
repository. An incompatible pair is an error rather than a silent fallback:
$ ocomment strip --language rust --dialect mysql
ocomment: unsupported dialect `mysql` for rust; supported: standard
OComment has 30 built-in languages covering 79 file extensions and 18 named dialects.
| Language | Extensions | Dialects |
|---|---|---|
rust | .rs | standard |
ocaml | .ml, .mli, .mlt | standard |
c | .c, .h, .m (objective-c) | standard, objective-c, gnu-c |
cpp | .cc, .cpp, .cxx, .hh, .hpp, .hxx, .mm (objective-cpp), .cu (cuda), .cuh (cuda) | standard, objective-cpp, gnu-cpp, cuda |
go | .go | standard |
java | .java | standard |
javascript | .js, .mjs, .cjs, .jsx (jsx) | standard, jsx |
typescript | .ts, .mts, .cts, .tsx (tsx) | standard, tsx |
python | .py, .pyw, .pyi | standard |
shell | .sh (posix-sh), .bash (bash53), .zsh (zsh) | standard, posix-sh, bash53, zsh |
html | .html, .htm, .xhtml, .shtml | standard |
css | .css, .scss (scss), .sass (sass) | standard, scss, sass |
jsonc | .jsonc, .json5, .json | standard |
sql | .sql | standard, postgresql, mysql, sqlite, t-sql, oracle |
kotlin | .kt, .kts | standard |
toml | .toml | standard |
lua | .lua, .rockspec | standard |
yaml | .yml, .yaml | standard |
php | .php, .phtml, .phpt | standard |
ruby | .rb, .rbw, .rake, .gemspec, .ru, .podspec, .jbuilder, .thor, .rbi | standard |
zig | .zig, .zon | standard |
r | .r | standard |
dart | .dart | standard |
swift | .swift | standard |
csharp | .cs, .csx | standard |
scala | .scala, .sc | standard |
vue | .vue | standard |
svelte | .svelte | standard |
markdown | .md, .markdown, .rmd | standard |
perl | .pl, .pm, .t | standard |
Detected without an extension
A file whose extension decides nothing is looked up by its whole name, and a
file with no name at all — a script on standard input — is read from its #!
line. A name is matched without regard to case. A direct shebang uses only the
executable basename; for /usr/bin/env, options, assignments, --, and
-S/--split-string are consumed to find the executable it actually launches.
Parent directories, option values, and program arguments are never searched for
an interpreter-looking name.
| Language | File names | Shebangs |
|---|---|---|
javascript | — | node, deno |
python | — | python |
shell | Dockerfile, Containerfile, Makefile, GNUmakefile, .profile, .bashrc, .zshrc | sh, bash, zsh |
toml | Cargo.lock, Pipfile, poetry.lock, uv.lock, pdm.lock | — |
lua | — | lua, luajit |
yaml | .clang-format, .clang-tidy, .yamllint | — |
php | — | php |
ruby | Gemfile, Rakefile, Guardfile, Capfile, Vagrantfile, Brewfile, Podfile, Fastfile, Appfile, Berksfile, Thorfile, Dangerfile, .irbrc, .pryrc | truffleruby, jruby, ruby |
r | .Rprofile | rscript, r |
dart | — | dart |
swift | — | swift |
csharp | — | dotnet-script |
scala | — | scala-cli, scala |
perl | — | perl |
Anything else
HTML is scanned recursively: the contents of a <script> element are
scanned as JavaScript and the contents of a <style> element as CSS,
each with the comment forms of that language rather than of HTML.
PHP is scanned in the code half of the file only. What sits between
<?php (or <?=) and ?> is scanned for PHP comments; the inline
HTML around those tags is content, so an HTML <!-- ... --> comment
in a PHP file is not reported and is never removed. Which mode a byte
sits in is decided by everything above it, so only a line break in
inline HTML is a point an editor may rescan a PHP file from: a file
that is all PHP is rescanned from the top.
Ruby is scanned with a lexer that keeps four states, because four
of Ruby’s tokens are spelled with a byte that is also an operator
and only where the token stands decides which: / is a regular
expression or a division, % a literal or a modulo, ? a
one-character string or a ternary, and << a here document or an
append. The fourth state is the one alias and undef leave Ruby
in, where %s opens a symbol literal however it is spaced while
%w, %q and / after the same keyword stay operators.
Ruby’s own parser answers those from a lexer state that a
symbol table feeds – it knows whether a is a local variable or a
method – and a scanner has no symbol table, so a bare word is
always read as a method that may take a command argument. a /b/ c
is therefore a pattern here where Ruby, knowing a to be a
variable, reads two divisions. The reading that differs is the one
that keeps more bytes inside a literal, so a comment is never
invented out of a division; it is a comment left unfound, not a
byte removed.
Three smaller readings go the same way. A / inside a character
class stays inside the pattern, where Ruby’s own lexer ends the
literal at it, so /[/]/ is one regular expression. $#, which
Ruby refuses outright as a global variable name, is read as a $
and then the comment that # opens everywhere else. And a here
document terminator may be spelled with digits, because Ruby
builds an unquoted one out of name bytes and a digit is one of
those from the first byte on: puts <<2 opens a here document
that runs to a line reading 2, and every line between the two
is body rather than code. Where the same << follows an operand
– a[0] <<2, p 1 <<2 – it is the shift operator it looks
like, and the rest of that line is code.
Zig is the one built-in language with no block comment. /* is the
division operator followed by multiplication, so a /* ... */
written in a Zig file is code: it is not reported and it is never
removed. /// and //! are documentation comments and a fourth
slash takes the marker back, so //// is an ordinary one. A
multiline string literal is read one line at a time: a \\
wherever a token may begin runs to the end of that line as string
content, and the line under it starts in code again. zig fmt: off
and zig fmt: on are kept, and they are matched as the whole
phrase the formatter compares rather than as a prefix of it.
R has one comment token and # opens it, so what the scanner has
to know is the four literals that carry a # as content. Two are
quoted strings and both may run over a line break; the third is a
backquoted name, which is lexed the same way; and the fourth is
the %...% operator, whose name is every byte up to the next %
on the same line – x %a # b% y is one operator, and a % with
no second % before the line break is an error rather than a
comment opener. A raw string is r or R, a quote, any run of
dashes and one of (, [ or {, and it closes only on the
matching bracket with the same run of dashes and the same quote.
The r opens one only where it begins a token, so the quote in
xr"(a)" opens an ordinary string instead. #' is roxygen2’s
documentation marker and is a doc-line; R’s own parser calls
every one of these a comment and draws no distinction.
Dart is the one built-in C-family language whose block comment
nests, so /* /* */ */ is one comment and commenting out a region
that already holds one works. Its documentation markers are ///
and /**, decided at the single byte behind the opener: a fourth
slash leaves //// documentation, where Lua’s ---- and Zig’s
//// take the marker back, and //! and /*! document nothing.
A string is written six ways – either quote, single-line or
triple-quoted, raw or not – and only the r of a raw one takes
away the \ escape and ${ ... } interpolation. That
interpolation is code, so a comment written inside one is a
comment, and a // there ends at the line break while the string
around it carries on below. # opens a symbol literal and is a
comment only as the #! script tag at the very first byte of a
file. // @dart = 2.12 is kept because the language version it
names changes what the rest of the file means, and
// dart format off and // dart format on are matched as the
whole phrase dart_style compares rather than as a prefix.
Swift’s block comment nests as Dart’s does, /// and /**
document – //// still does and the empty /**/ does not, since
its second * is the first byte of its own terminator – and //!
and /*! document nothing. A string is written four ways: single
line or """, each of them raw or not, where raw is a run of #
in front of the quote. Those hashes do not take the escape and the
interpolation away, they rename them: with one hash the escape
is \# and \#( opens the interpolation, the closing delimiter
needs the same run behind the quote, and a bare \( is content.
The interpolation is code, so a comment written inside one is a
comment.
The regular expression literal is the one thing in Swift that can
carry a // with no quote in front of it, and only JavaScript’s
scanner faces the construct at all besides this one. #/ ... /#
is the extended form, which may hold an unescaped / and, when
its opener ends the line, may span lines. The bare / ... / ends at
the first unescaped /, never crosses a line, and may not begin
with a space or a tab (The Swift Programming Language, Lexical
Structure) – and its last two bytes may still spell //, because
/a\// is a literal whose content is a\/. One rule cannot be
had from the bytes alone: whether a / in an ambiguous position
is a literal at all is settled in Swift by the parser, and this
is a lexer. It reads a / as a literal exactly where a prefix
operator may stand and where the content closes on the same line;
every case that decides differently from the compiler is a file
swiftc rejects, where the compiler is lexing a literal it has
already diagnosed and this reads the comment inside it instead –
so a fix on a file that does not compile can take the two bytes
of a comment opener a repaired file would have kept. The corpus
case swift-bare-regex-limitation records one.
' is no delimiter of the language, but the compiler
lexes '...' anyway so that it can offer a fix-it, and this
follows it for the same reason.
C# is the one built-in language whose lines are lexed two ways.
A line whose first non-blank byte is # is a pre-processing
directive, and ECMA-334 6.5.1 ends one with
PP_Whitespace? SINGLE_LINE_COMMENT? New_Line: a // is the only
comment it can carry, a /* on it opens nothing, and a " opens
a string that takes no \ escape and ends at the line – which is
what keeps the // inside #line 1 "a//b.cs" out of reach. Four
directives take the rest of their line as a message instead:
#error and #warning carry the text a diagnostic quotes, and
#region and #endregion the label an editor folds under, so
#region // x carries a comment and #region x // y does not. A
conditional section is scanned as ordinary code rather than
skipped, for the reason #if 0 is in C and C++: which symbols a
build defines is not in the file, and code is what an #if DEBUG
body is in every build that defines the symbol. The price is a
section written to be skipped rather than compiled – prose, or
another language – whose bytes are not C#: an apostrophe in one
opens a character literal its line does not close, so the file is
called invalid and no edit is offered for it, where Roslyn reports
the whole section as one blob of disabled text and finds nothing
in it. Refusing to edit is the safe half of being wrong, and two
files of the 70,630 measured are affected – both of them a block
of prose under an #if false, and the only two this scanner calls
invalid that Roslyn does not. The corpus case
csharp-conditional-section-limitation records one.
A string is written eight ways – plain, verbatim, raw, and each of
those interpolated – and the three rules differ in what closes
them. A plain one takes the \ escape, which carries the
character behind it in whatever it is, a line terminator included;
a verbatim @"..." spells its quote "", takes no escape, and
carries line breaks; and a raw one is opaque until a run of at
least as many quotes as its opener carried comes back, carrying
line breaks only when its opener ends a line. Interpolation is a
switch on top of all three: a run of n $ in front of the quote
makes a run of n braces the thing that opens a hole, so { is
content in a $$""" literal and {{ is code. A hole is code, so
a comment written in one is a comment and may carry a line break
the text around it could not – but the format clause behind the
first : of a hole is text again, which is why the // in
$"{x:D4 // n}" is not a comment and why $"{global::X}" needs
the parentheses the compiler asks for.
C# also counts five line terminators where every other C-family
scanner here counts two: ECMA-334 6.3.1 adds U+0085, U+2028 and
U+2029 to the carriage return and line feed, and Roslyn ends a
// comment at all five. A scanner that read on past one would
swallow the code behind it on the same physical line, and a
removal would take that code with it.
Scala’s block comment nests as Dart’s does, and its documentation
comment is the one the Scala 3 compiler’s comment reader answers to:
a comment is documentation exactly when its text starts with /**
(Comment.isDocComment), so /**/ and /***/ are documentation
comments and /// – which scaladoc does not read – is an ordinary
line comment.
A Scala string is interpolated exactly when an identifier stands
directly before its quote: the compiler’s lexer turns that
identifier into INTERPOLATIONID, so s"...", raw"..." and a
custom interpolator such as xml"..." interpolate, while a
keyword – its own token – and a number leave the quote to a plain
string whose $ is content. Inside an interpolated string $$ and
$" write a literal $ and ", ${ ... } opens an expression
that is code – a comment written there is a comment – and $
followed by an identifier starts another one. A triple-quoted
string closes on the first three quotes of a run and makes any
further quotes of the run part of its value, so """a"""" is
the string a"; a backquoted identifier may hold // without it
being a comment.
The XML literal is the one Scala construct whose text is not code:
the compiler’s lexer emits an XMLSTART token and the parser
re-reads the literal with an XML scanner, so this scanner reads it
the way the parser does. Element text, CDATA and processing
instructions are opaque, { ... } in text or an attribute is code,
<!-- ... --> is an XML comment, and the literal ends at the close
tag matching its root or at a self-closing />. A literal begins
exactly where the lexer says one does: a < preceded by space,
tab, line feed, {, ( or > and followed by an XML name start,
! or ?.
Vue and Svelte components are HTML with code in the template: a
<!-- ... --> is an HTML comment, and {{ ... }} in Vue or
{ ... } in Svelte opens an expression whose comments are
comments — in Vue, a v-pre element makes its whole content raw
text instead. The <script> and <style> bodies are scanned as
their own languages, the lang attribute choosing which: ts and
tsx select TypeScript, jsx JavaScript with JSX, and scss and
the indented sass the SCSS dialect. A lang this scanner has no
rules for — coffee, less, pug — makes the whole block opaque.
SCSS and the indented Sass syntax are CSS plus two rules: //
opens a silent comment, and #{ ... } opens an interpolation
whose expression is code. An unquoted url( ... ) is read the way
dart-sass reads it — its bytes are URL text until the ) that
ends them, a protocol-relative url(//cdn/x.png) included — with
#{ ... } inside it code.
Markdown is scanned per CommonMark: an HTML comment is a comment,
a fenced code block is scanned as the language its info string
names — ```rust, {r} and c++ all reach their scanners — and an
inline code span or an indented code block is opaque, so a // or
a /* inside one is code text, not a comment.
Perl is scanned conservatively: a # runs to the end of its
line, a POD block is opaque, and every quote word — the single
and double quotes and backticks, q, qq, qw, qx, m,
s, tr and y with delimiters of their own, and the
here-documents — hides a # written inside it. A / directly
after a closing parenthesis, bracket or brace is reported as
lexically ambiguous: perl reads f() /a#b/ as a regular
expression and (2) / 2 as a division, and only the parse
context tells which, so the file is called invalid and nothing
is edited.
YAML is scanned lexically, and valid is a lexical answer: the
shapes a YAML parser rejects are not all shapes a lexer can see.
A comment line inside a multi-line plain scalar makes the file a
parse error while it is there, and taking it away leaves a scalar
that parses and folds the two halves into one value; that comment
is reported and removed like any other.
The block scalar is the other way round, and it is the one place
in any language where the hole a removal leaves carries meaning.
A block scalar decides where its body ends from the lines below it
(YAML 1.2.2, 8.1.1), so a whole-line comment under a body is what
terminates it – and whatever a removal writes on that line is read
back as part of the value. A line of spaces as wide as the comment,
which columns writes, is indented at least as deep as the body
whenever the comment was wide enough. An empty line, which lines
writes, is content under |+ and >+, which keep every empty line
trailing a body (8.1.1.2).
So the rule is one rule, and it holds whatever the block scalar
chomps: a whole-line comment sitting in the run of blank and
comment lines under a block scalar body is removed by taking its
whole line, terminator and all, under every layout. Those lines
are the one place lines does not keep a line’s number and
columns does not keep a column’s – both give that up rather than
give up the value. Under |+ and >+ the removal also takes the
blank lines the comment was sheltering, which become content the
moment it is gone; the blank lines above the first comment were
content already and are left exactly where they were.
There is one comment that rule cannot reach, and it is kept instead.
The line a body ends at is a comment shallower than the body’s own
content; take it away and the lines under it are read against the body
again. When one of those is a comment the run keeps, and it is
indented to the content depth, the body swallows it and the value
grows a line. No removal preserves the value there, so the comment
that ends the body is kept, with the reason structural in a YAML block scalar trail – the one keep --policy all does not overrule.
The depth this is measured at is the body’s content indentation: the explicit indentation indicator when the header spells one out, and otherwise the indentation of the body’s first non-empty line (YAML 1.2.2, 8.1.1.1). A surviving comment shallower than that is outside the scalar before and after the removal, so nothing above it is kept.
tools/yaml_roundtrip.py is what holds this to its word: it strips
thousands of generated YAML documents under every layout and every
policy and asserts that PyYAML reads the same value out of each one
before and after.
A delimiter-based syntax that is not in the table above can be described
declaratively as a profile in .ocomment.toml, which needs no code; see
Configuration. A syntax whose comments cannot be
described by delimiters alone needs a scanner plugin instead; see
Plugins.
Editor and LSP setup
ocomment lsp is an LSP 3.18 stdio server.
It negotiates UTF-8, UTF-16, or UTF-32 positions, accepts incremental document changes, supports pull diagnostics with a push fallback, and exposes comment, selection, document, and workspace fixes.
Diagnostics are hints by default.
Save-time edits are disabled unless [lsp].on_save = true.
OComment fixes are code actions, not formatting operations.
Enable source.fixAll.ocomment where an editor supports fix-all actions.
Workspace diagnostics and fixes report LSP work-done progress when the client provides a token and advertise it as cancellable; $/cancelRequest aborts the pending request with the standard cancellation response.
In a multi-root session, every workspace folder has its own configuration and plugin host.
A document uses the most deeply nested workspace folder that contains it.
An open document outside all folders gets a standalone context discovered from its parent directory and is omitted from workspace-wide fixes;
only a folder-less session treats every open document as workspace scope.
Save-time editing remains an advertised capability so live configuration can turn it on, but the handler is a no-op whenever [lsp].on_save is false.
Neovim
vim.lsp.config.ocomment = {
cmd = { "ocomment", "lsp" },
filetypes = {
"rust", "ocaml", "c", "cpp", "go", "java", "javascript",
"typescript", "python", "sh", "html", "css", "jsonc", "sql", "kotlin",
"toml", "lua", "yaml", "php", "ruby", "zig", "r", "dart", "swift", "cs",
"scala", "vue", "svelte", "markdown", "pl",
},
root_markers = { ".ocomment.toml", ".git" },
}
vim.lsp.enable("ocomment")
Helix
[language-server.ocomment]
command = "ocomment"
args = ["lsp"]
[[language]]
name = "rust"
language-servers = ["rust-analyzer", "ocomment"]
Add ocomment to the language-servers list for each desired language.
Zed
{
"lsp": {
"ocomment": { "binary": { "path": "ocomment", "arguments": ["lsp"] } }
}
}
Associate the server with the desired languages in the project or extension configuration used by your Zed version.
Emacs (Eglot)
(add-to-list 'eglot-server-programs
'((rust-mode c-mode c++-mode python-mode js-mode typescript-mode)
. ("ocomment" "lsp")))
VS Code
The extension is currently source-only and is not distributed through the Visual Studio Marketplace, Open VSX, or GitHub Releases. To build a local VSIX from a checkout:
cd editors/vscode
npm ci
npm run package -- --out ocomment.vsix
code --install-extension ocomment.vsix
This local build is a client only: it launches the ocomment binary, which has to be installed separately and on PATH, or named by ocomment.path.
The extension’s version and any future publication are independent of CLI tags.
It attaches to thirty-five language identifiers — rust, ocaml, c,
cpp, objective-c, objective-cpp, cuda-cpp, go, java, javascript,
javascriptreact, typescript, typescriptreact, python, shellscript,
html, css, jsonc, sql, kotlin, toml, lua, yaml, php,
ruby, zig, r, dart, swift, csharp, scala, vue, svelte,
markdown, and perl — and contributes OComment: Remove comments in file, ... in workspace, OComment: Restart server, and OComment: Show output, plus a status bar count of the removable comments in the open files.
{
"ocomment.path": "",
"ocomment.extraArgs": [],
"editor.codeActionsOnSave": { "source.fixAll.ocomment": "explicit" }
}
[lsp].on_save = true in .ocomment.toml does the same thing for everyone working in the repository, rather than for one editor.
The source and its development notes are under editors/vscode.
The extension is disabled in untrusted workspaces, because ocomment.path names an executable it launches.
Run ocomment doctor in the same environment when the editor cannot start the process.
Any other LSP client can launch ocomment lsp directly; configure the document selectors for the languages listed by ocomment languages.
The server speaks stdio and defines no transport flag, so a client that appends --stdio has to be told not to.
Agents
OComment has three readers, and one output shape for each.
A person reads --format human: colour, hyperlinks, a summary at the end.
A program reads --format json, --format jsonl, --format sarif or --format github: a schema, a fixed shape, no prose.
An agent about to edit the file reads --format agent, which is neither — it is an instruction.
This page is about the third.
docs/library.md is the library, docs/ci.md is the pipeline, and AGENTS.md at the repository root is for an agent working on OComment itself rather than with it.
--format agent
$ ocomment check --format agent src
ocomment: 3 comments to go in 2 files.
src/session.rs:12:5 shorten to 1 line: // The retry budget is per connection
src/session.rs:13:5 shorten to 1 line: // rather than per request, because a
src/pool.rs:44:23 move above the code: // NOTE: closed by the caller
rule: policy `conservative` removes line and block comments. Allowed: a comment tagged NOTE, SAFETY or INVARIANT, at most 1 line of adjacent comments, never beside code.
next: edit them, or run `ocomment fix src/session.rs src/pool.rs`.
Three parts, in the order they are needed.
What has to change, one line per comment, path:line:column first and the verb second.
The verb is the rule that decided the comment, not a description of the verdict: a comment that is only too long says shorten to 1 line, and one that only sits in the wrong place says move above the code.
Telling a reader to delete a comment that had to move is wrong advice however correct the verdict was.
The rule, once.
A report that lists three comments and never says what would have been acceptable teaches nothing: the reader fixes those three and writes the fourth the same way.
This line is written only when every file in the report was judged by the same rules — a [[overrides]] table covering part of the tree means there is no single sentence to write, and none is written rather than one that is true of only some of the findings.
The way through, split by who has to answer.
TIDY-ALL runs ocomment fix --tidy, which writes every rewrite in the report and takes no comment away; it is safe to run without reading the findings first, because nothing it does is a judgement.
REMOVE-ALL runs ocomment fix, which also applies every removal above — including the ones that were worth keeping, which is why its line says how many.
Both are named only when the bytes are on the disk; a proposal a hook is judging gets a plain instruction instead.
One verb is deliberately not a single action:
src/pool.rs:44:1 do it or drop it (61d old, 30d allowed): // TODO: retry on timeout
That is a tag with a deadline — see [policy.allow.expiry].
Deleting the line satisfies the rule and loses the promise; doing the work satisfies both.
Only the reader knows which, so the report does not pick.
A clean run writes nothing at all, on either stream, and exits 0. Silence is the pass. That is what makes this format usable as the body of a hook decision: there is no “nothing to do” line to parse before finding out whether there is anything to do.
Editing hooks
ocomment hook <SURFACE> reads an agent host’s hook payload on standard input and answers in that host’s protocol.
It decides nothing of its own: it works out which bytes are about to become which file, hands that pair to the same machinery ocomment check runs, and writes the answer in the shape the host reads.
Your .ocomment.toml is the whole of the policy, exactly as it is for the command line and for CI.
Claude Code
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [{ "type": "command", "command": "ocomment hook claude-code" }]
}
]
}
}
In .claude/settings.json for a project, or ~/.claude/settings.json for every project.
PreToolUse is the one worth having: the hook is asked before the edit lands, so a comment that would not survive the project’s policy never enters the file.
Writecarries the whole file, and is judged as written.EditandMultiEditcarry replacements. The hook applies them to the file as it stands and judges the result, so the line numbers it reports are the ones the agent will find when it looks. Nothing is written: the file on disk is untouched either way.- A replacement that does not match the file is an edit the host will refuse on its own, and the hook says nothing about it rather than judging bytes that will never exist.
A clean edit gets no answer at all, and the edit proceeds under whatever permission rules its user set.
The hook never answers allow: waving an edit past those rules is not what it was asked about.
An unclean edit is denied, with the --format agent report as the reason.
The agent sees it, rewrites, and tries again.
PostToolUse works too, and is the one to use for a file the agent did not write through Write or Edit — a generator, a formatter, a shell command:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [{ "type": "command", "command": "ocomment hook claude-code" }]
}
]
}
}
The edit has already happened by then, so there is nothing left to refuse and the report is a correction: the hook exits 2 with the report on standard error, which is how that host puts text in front of the model.
Either hook is silent about every event that is not an edit. Reading a file with comments in it is not writing one.
Another host
One file, rust/ocomment/src/hook.rs, holds every line of this that is specific to a host — the same arrangement as editors/ and action.yml, which speak an editor’s and a CI system’s protocols without either reaching into the scanner.
Supporting another agent host is one more Surface and one more arm.
Nothing in ocomment-core knows that any of them exist.
Exit status
The same three everywhere, and the hook surfaces are the documented exception:
| Status | Meaning |
|---|---|
| 0 | Nothing removable, and every requested change applied. |
| 1 | Removable comments were reported, or a diff was printed. |
| 2 | Invalid source, configuration, plugin, or I/O failure. |
ocomment hook claude-code answers in its host’s vocabulary instead: 0 when it has nothing to say or when it is carrying a decision in its JSON output, 2 when a PostToolUse correction has to reach the model.
Reading a report as data
--format agent is prose with a fixed shape, meant to be read rather than parsed.
When you want fields, ask for fields:
$ ocomment scan --format json src/session.rs
spec/result.schema.json is the schema, published in the repository and embedded in the binary — ocomment config schema writes the configuration one.
--format jsonl is the same content one object per line, for a stream you do not want to buffer.
A report can tell you it was guessing
valid is false when the source failed to lex, and a report like that still carries a verdict for every comment it found — because those are what the scanner concluded, not because you should act on all of them.
The ones it could not establish are marked, and only those:
for comment in report["comments"]:
if comment.get("established", True):
act_on(comment)
A comment without the field is one the scan established, and its verdict is worth what any verdict in a clean report is worth.
A scanner that cannot find the end of a token does not know where the next one starts, so an unterminated block opener is reported as a comment running to the end of the file — and the code under it is not a comment.
Acting on that verdict deletes code.
fix --force-invalid skips exactly these comments for the same reason, so if you are shelling out rather than reading the report you already have this for free.
The mark is narrow on purpose. A C# string that never closes ends at the newline, and the comment on the line below it is delimited by a scanner that knows exactly where it is — that comment is not marked, and a forced run still removes it.
Finding out why
--explain names, under each reported comment, the rule that decided it and the table that rule came from:
$ ocomment check --explain src/session.rs
src/session.rs:12:5: removable line comment: // The retry budget is per connection
removed: it belongs to a run of 2 adjacent comment lines, and at most 1 is allowed ([policy.allow] in .ocomment.toml); cut the run to 1 line
--trace json records the whole path to that verdict — the language detection,
the scanner entered, each comment classified, each edit planned — as JSONL on standard error, against spec/trace.schema.json.
Standard output is unchanged,
so a trace can be collected from a run whose output is being consumed.
ocomment doctor reports what the environment resolved to: which configuration files were found, which Git repository, which plugins.
ocomment selftest runs the corpus embedded in the binary against that binary,
which is how an agent can tell a broken install from a disagreement about policy.
Hooks and CI
OComment ships two integrations: a pre-commit hook manifest at .pre-commit-hooks.yaml, and a composite GitHub Action at action.yml.
Both drive the same CLI and the same exit codes: 0 clean, 1 something is outstanding — a removable comment, a printed diff, a removal a --tidy run left alone, or an index a staged fix rewrote — and 2 an invalid source, configuration, plugin, or I/O failure.
pre-commit
Install the CLI first
The hooks declare language: system, so ocomment must already be on PATH when pre-commit runs them.
pre-commit’s language: rust runs cargo install --path . at the checkout root, and this repository’s manifest lives in rust/, so it cannot build these hooks.
Install the CLI once per machine and CI image:
cargo install ocomment --locked
A future release may publish a wheel so language: python can install the binary itself.
Until then, a missing ocomment fails the hook with a “command not found” error rather than silently passing.
Recommended configuration
repos:
- repo: https://github.com/P4suta/OComment
rev: v0.1.0
hooks:
- id: ocomment-check
ocomment-check reports removable comments in the staged source files and exits 1, which blocks the commit and leaves the fix to you.
That is the safe default: nothing is rewritten behind your back.
To let the hook write what a machine can settle, add ocomment-tidy in front of it:
repos:
- repo: https://github.com/P4suta/OComment
rev: v0.1.0
hooks:
- id: ocomment-tidy
- id: ocomment-check
ocomment-tidy runs ocomment fix --tidy, which applies the style axis — a paragraph reflowed to one sentence per line, a missing space after a marker — and takes no comment away.
Every removal it found is still reported by the ocomment-check behind it, so the gate is no weaker for the rewrite.
It is the pairing to reach for when OComment runs on every commit: the half nobody has to think about is written, and the half only its author can answer is left to them.
ocomment-fix is the blunt one.
It applies the removals too, including the comments above that were worth keeping, so run it when that is what you mean:
repos:
- repo: https://github.com/P4suta/OComment
rev: v0.1.0
hooks:
- id: ocomment-fix
- id: ocomment-check
Both hooks accept the full CLI surface through args, for example args: ["--policy", "standard"] or args: ["--config", "ci/.ocomment.toml"].
Judging the commit rather than the disk
pre-commit passes the staged file names to the hook and stashes unstaged changes before running it, so by default OComment reads the working tree that pre-commit has already reduced to the staged content.
Add --staged to read the Git index blobs directly — the exact bytes the commit will contain:
- id: ocomment-check
args: ["--staged"]
For a partially staged file the difference is visible: the working tree shows every comment, the index shows only the ones being committed.
$ ocomment check a.rs
a.rs:2:16: removable line comment: // staged comment
a.rs:3:16: removable line comment: // unstaged comment
Found 2 removable comments in 1 file (1 file scanned). Run `ocomment fix` to remove them.
$ ocomment check --staged a.rs
a.rs:2:16: removable line comment: // staged comment
Found 1 removable comment in 1 file (1 file scanned). Run `ocomment fix` to remove it.
Two caveats come with --staged, and both are worth knowing before you enable it.
pre-commit cannot see that fix --staged changed anything, so the exit code is what stops the commit. pre-commit decides that “files were modified by this hook” by comparing the unstaged diff before and after the hook.
After its stash the working tree already equals the index, and ocomment fix --staged moves both sides by the same edits, so the unstaged diff is empty both before and after:
$ git status --short
M a.rs # staged, working tree clean
That detection never fires.
A staged fix therefore exits 1 whenever it rewrote the index: the bytes the commit will carry have stopped being the bytes their author staged, and with pre-commit’s own check blind to it the exit code is the only place that can say so.
The commit stops, git diff --cached shows what changed, and committing again records it.
Outside pre-commit, where a file really is partially staged, fix --staged refuses rather than guessing:
$ ocomment fix --staged a.rs
ocomment: unstaged changes in a.rs make the staged fix ambiguous; no files were
modified (use --index-only): edit context does not have one unique working-tree
mapping
--staged sees nothing outside the pre-commit stage. Under pre-commit run --all-files, or in a pre-push or manual stage, there is no staged change set, so the run scans zero files and exits 0:
$ ocomment check --staged
No removable comments in 0 files.
That is a hook which always passes, not a hook which found nothing.
Use a separate entry without --staged for those stages, or gate the --staged entry with stages: [pre-commit].
Keeping the hook manifest honest
Both published hooks use types: [text] and intentionally have no files: regex.
Pre-commit selects the text files and OComment’s own detector decides which ones it understands.
That keeps reserved names such as Dockerfile and extensionless shebang scripts on the same path as an ordinary CLI run.
tools/check_hooks.py rejects any manifest-level files: filter, an unknown manifest key, or a hook missing id, name, entry, or language.
CI runs it next to tools/check_embedded_specs.py.
python3 tools/check_hooks.py
GitHub Action
action.yml at the repository root is a composite action.
It resolves a release, downloads the archive for the runner, verifies its SHA-256 and its build provenance, runs ocomment check or ocomment diff, and turns the exit code into a verdict.
Annotate a pull request
format: github is the default and writes annotations that GitHub renders on the changed lines.
The level of each one is the level its run’s exit status justifies: check and diff answer a finding with exit 1, so what they report is an ::error,
while scan and fix end at 0 whatever they find and report a ::notice.
That way a job which fails on the 1 does not describe the comments it failed over as though nothing had gone wrong — and GitHub folds a notice away where it surfaces an error, so the annotation was easy to miss entirely.
A job that posts annotations without gating on them, or gates without wanting the red, says so with --annotation-level <error|warning|notice> and is believed.
A diagnostic — a file that would not scan at all — stays an ::error whatever that flag says, because it is not a finding the run is offering an opinion about.
name: Comments
on: [pull_request]
permissions:
contents: read
jobs:
ocomment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: P4suta/OComment@v0.1.0
with:
paths: src tests
Upload SARIF to code scanning
jobs:
ocomment:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # NOTE: Upload the SARIF file to code scanning.
steps:
- uses: actions/checkout@v7
- uses: P4suta/OComment@v0.1.0
with:
format: sarif
upload-sarif: "true"
fail-on-findings: "false" # NOTE: Let the code-scanning alerts carry the result.
upload-sarif: "true" requires format: sarif; any other format is a usage error rather than a silent skip.
The SARIF is uploaded under the ocomment category, so it does not collide with other tools’ results.
Inputs
| Input | Default | Meaning |
|---|---|---|
version | "" | Release tag to download. Empty uses the tag the action was referenced by when that looks like a version, and otherwise the latest release. |
command | check | check or diff. |
paths | "" | Files or directories, split on whitespace. Empty processes the working directory. |
policy | "" | Value for --policy. Empty leaves the configured policy alone. |
format | github | Value for --format. |
args | "" | Extra arguments, split on whitespace. |
fail-on-findings | "true" | Fail the step on exit 1. Exit 2 always fails. |
upload-sarif | "false" | Upload the SARIF file to code scanning. |
sarif-file | ocomment.sarif | Where SARIF output is written. |
verify-attestation | "true" | Run gh attestation verify; the action fails closed when gh is unavailable. Set false only as an explicit opt-out. |
binary-path | "" | Use an already-built binary and download nothing. |
working-directory | . | Directory the command runs in. |
token | ${{ github.token }} | Used to resolve the latest release and verify attestations. |
paths and args are split on whitespace with globbing disabled; quoting inside them is not interpreted, so a path containing a space needs a separate run or a --config file.
Outputs
| Output | Meaning |
|---|---|
exit-code | 0 clean, 1 removable comments, 2 failure. |
version | Release tag downloaded, or the version the supplied binary reported. |
sarif-file | Absolute path of the SARIF file, empty when format is not sarif. |
fail-on-findings: "false" keeps the step green on exit 1 so a later step can branch on exit-code:
- id: comments
uses: P4suta/OComment@v0.1.0
with:
fail-on-findings: "false"
- if: steps.comments.outputs.exit-code == '1'
run: echo "Removable comments are present but not blocking."
What the action verifies
Every downloaded archive is checked against the release SHA256SUMS before it is unpacked, and the run stops with exit 2 on a mismatch or on an archive that the checksum file does not list.
With verify-attestation: "true" — the default — the archive is also checked against its GitHub build-provenance attestation with gh attestation verify --repo P4suta/OComment.
A runner without the gh CLI fails closed; verify-attestation: "false" is the only explicit opt-out.
The action also validates SARIF structure before invoking the upload action, and reports a CLI exit 2 before any upload failure can obscure it.
Inputs and binary version output containing line breaks are rejected before they can become workflow outputs.
Runner platforms map to the published targets as follows. Linux uses the statically linked musl archives, so no glibc version is required.
| Runner | Target | Archive |
|---|---|---|
| Linux x64 | x86_64-unknown-linux-musl | .tar.gz |
| Linux arm64 | aarch64-unknown-linux-musl | .tar.gz |
| macOS x64 | x86_64-apple-darwin | .tar.gz |
| macOS arm64 | aarch64-apple-darwin | .tar.gz |
| Windows x64 | x86_64-pc-windows-msvc | .zip |
Any other combination fails with exit 2 and points at binary-path.
Runners without a published archive
binary-path skips resolution and download entirely and uses a binary you already have.
A missing path is retried with an .exe suffix, so one value works across the runner matrix.
This is how the repository’s own action-smoke job tests the action against a freshly built CLI:
- run: cargo build --manifest-path rust/Cargo.toml --locked -p ocomment
- uses: ./
with:
binary-path: rust/target/debug/ocomment
paths: action-fixture
Pinning
Version tags are immutable under the repository’s release-tag ruleset, so P4suta/OComment@v0.1.0 is a stable reference and there is no moving v0 tag to follow.
Pin to a full version, or to a commit SHA with a version comment if your policy requires it.
Keeping the protected directives honest
spec/directives.toml publishes the markers that take a comment out of reach of a remove policy — # syntax=, //go:build, # hadolint ignore=, and the rest — so a consumer can read the contract without reading the scanner.
tools/check_directives.py is what keeps the two the same thing.
It feeds every name to the built binary as the comment a project would really write, and the answer has to be a keep with the reason that says why; a name in the spec with no sample fails, and so does a sample the spec does not list.
Each sample carries two comments the scanner has to remove: an ordinary one,
which catches a run that protected the whole file, and a near-miss derived from the name — hadolint against hadolintish note — which catches a marker matched so loosely that prose merely opening with those letters is protected too.
cargo build --manifest-path rust/Cargo.toml --locked -p ocomment
python3 tools/check_directives.py
python3 tools/check_directives.py --binary rust/target/release/ocomment
The rust CI job runs it next to tools/check_hooks.py and tools/check_embedded_specs.py, and cargo xtask release-check runs it again against the release binary before a tag is pushed.
Putting this on a repository that already exists
A repository with eleven thousand comments cannot turn the rule it wants on today. The order below tightens one axis at a time, and each step leaves a gate that passes.
1. Find out what is there. ocomment coverage says which files were read and which were passed over; a gate over 85% of a tree is not the gate you think it is, so close that first with [files] and, where a format has no built-in scanner, a [profiles.<name>] entry.
ocomment tags says which tags your comments already open with — that list, not a list you invent, is the one to start [policy.allow] tags from.
2. Protect what your own tools read. [policy] protected names the markers your build, your linter or your test runner reads.
This is the step that has to come before any removal, because it is the only one whose omission changes what the code does. ocomment scan --policy all --explain over a directory you know well is a quick way to find what you have been relying on.
3. Gate the new work, not the old. ocomment check --base main in a pull request checks only what the branch changed.
The tree stays as it is and nothing new is added to it, which is most of the value and costs no cleanup at all.
4. Record the distance, and close it. [ratchet] ledger counts what each file holds today and fails when a file holds more — and when it holds fewer,
asking to be updated, so the number in the file is always the number in the tree.
See the configuration guide.
5. Tighten one axis. max_lines, then trailing = false, then deadlines on the tags that are promises.
Each is a separate number the ledger can carry to zero.
ocomment check --format agent is the report to hand somebody — or something — that is going to do the editing.
6. Drop the ledger. When it reaches zero, delete it and make the bare run the gate. A ledger that has nothing left to say is a file that describes a repository that no longer exists.
What the gate never looked at
$ ocomment coverage
1 of 3 files scanned (33.3%)
2: hidden file or directory ([files] hidden = false)
1 .yml
1 .toml
The percentage is of the tree and not of the walk.
A file the walk reached and passed over is a skip and has always been reported; a file the walk’s own limits kept out was met by nothing, so nothing reported it — and hidden = false is the default, which means every .github/workflows/*.yml a project has.
A run that read one of three files used to say 100.0%, which was a true sentence about the walk and a false assurance about the repository.
Three settings can keep a file out, and each is named with the line a reader would change: [files] hidden, [files] include/exclude, and [files] max_size.
A file a .gitignore excludes is deliberately not counted — that is build output, and a percentage taken over a hundred thousand object files would mean nothing.
When more than one kind of reader answered, the scanned share is split by which one did:
$ ocomment coverage .
184 of 221 files scanned (83.2%)
181: read by a built-in language
...
3: read by the `hash-line` profile
2 .gitignore
1 CODEOWNERS
This is what makes an upgrade legible.
A release that adds a profile reads files the previous one passed over, and those files move out of a skip reason and into a named reader — so the gate covers more than it did, and the size of the change is a number rather than a wall of findings nobody asked for.
The verdict on a comment does not depend on which reader found it: a line of prose is prose in a .gitignore as much as in a .py, and a rule that softened for one would mean the same bytes getting different answers from different readers.
A project that wants the prose in those files kept says so the way it says everything else, with a path override.
A cached gate is not a gate
A test that runs ocomment is a test whose answer depends on a program the test runner did not build.
Most runners cache on what a test read, and a process a test started is not a file it read — so the gate keeps returning its last answer after the tool underneath it has changed, or broken.
The shape is worth stating on its own, because the tool is rarely the thing anyone suspects:
A gate that only execs an external program can go on passing after that program starts giving wrong answers. Nothing in the gate’s inputs changed, so nothing invalidates its result — and a cached pass is printed in the same words as a real one.
The fix is to make the tool part of what the gate reads. Reading the binary is enough, because that is the thing that changed — Go invalidates a cached result on the files a test read, so opening the binary puts it in the key:
binary, err := exec.LookPath("ocomment")
if err != nil {
t.Fatalf("find the comment gate's tool: %v", err)
}
if _, err := os.ReadFile(binary); err != nil {
t.Fatalf("read the comment gate's own tool: %v", err)
}
Read the binary rather than recording ocomment --version.
Two builds can answer ocomment 0.1.0 and disagree about the same file — one from a release,
one from a working tree — and a version string cannot tell them apart.
ocomment doctor says which one answered:
$ ocomment doctor
ocomment 0.1.0
binary: /usr/local/bin/ocomment (sha256:19010bf16aa8983d95a7f6d83b8aae9854369961ecd8dc1edff12c8a40a7208b)
``` This is
not hypothetical: it is how the licence bug that `[policy] mode` fixed was reported as a failing gate in one shell and a passing one in another, on the same machine, on the same day, with `mise exec` and a bare `PATH` resolving to different `0.1.0`s.
The same hole is not Go's.
Any runner that caches on declared inputs has it: a Cargo build script needs `cargo:rerun-if-changed` for a tool it shells out to,
and a CI cache keyed on a lockfile is keyed on a lockfile rather than on the toolchain the job installed.
## Gating a branch on what it changed
```console
$ ocomment check --base main
Only the working-tree files that differ from git merge-base HEAD main.
The merge base and not the branch tip: on a branch several commits behind its trunk, a plain diff against the trunk reports every file the trunk changed as well, and a gate that reported those would be asking this branch to answer for somebody else’s work.
A deleted file is dropped rather than reported — there is nothing left to read, and failing on one would refuse the change that cleaned it up.
A path named beside it narrows it further: --base main src is the files under src that the branch changed.
--base applies the ordinary walk limits, so a generated file the branch touched is still passed over; a path typed on the command line without --base is you saying this one and lifts them.
A gate that examined nothing says so
--base main: no changed files to check, so nothing was examined.
--staged: nothing is staged, so nothing was examined. A runner that stages
nothing of its own -- `pre-commit run --all-files`, say -- needs a run without
--staged.
Both runs are correct and both exit 0, which reads exactly like a clean branch.
That is how --staged under pre-commit run --all-files becomes a gate that is green forever.
The run stays right; the silence goes.
Numbers a later step can read
--summary <FILE> writes the end-of-run counts as one JSON object, whatever --format the run wrote its product in:
$ ocomment check --format sarif --summary counts.json > ocomment.sarif
$ jq .removable_comments counts.json
14
spec/summary.schema.json is the schema.
The counts are the ones the run already made, so there is no second scan to pay for and no parsing of the product to get at them — and comments_removed is non-zero only for a fix that reached the disk.
The GitHub Action uses it for its own outputs.
findings-count,
files-with-findings, files-scanned, removed-count and summary-file are available to later steps, and the job summary carries a table of the same numbers unless step-summary: false:
- uses: P4suta/OComment@v0
id: comments
- if: steps.comments.outputs.findings-count != '0'
run: echo "still ${{ steps.comments.outputs.findings-count }} to go"
A run that failed before it finished reports those outputs as empty rather than as zero: “none found” and “never looked” are different answers, and a gate downstream must not read the second as the first.
Threads
--jobs <N> sets how many threads the run uses to walk, read and scan; 0 chooses one per core, which is the default.
The walk, the reads and the scans all take it from the same place, so one flag is the whole knob.
It was previously settable only through RAYON_NUM_THREADS, which is an implementation detail leaking as a user interface.
Output order does not depend on it. The candidates a walk finds are sorted before any of them is opened, so two runs over the same tree write the same bytes however many threads they used.
The published pre-commit hooks
.pre-commit-hooks.yaml is what pre-commit reads when this repository is used as a repo: entry.
Both hooks deliberately receive every text file pre-commit selects: OComment’s detector, not a second extension list, decides which files are supported, and that is what lets reserved names and extensionless shebang scripts reach the same detector an ordinary CLI run uses.
tools/check_hooks.py rejects a manifest-level filter that would undo it.
language: system requires ocomment to already be on PATH.
pre-commit’s language: rust runs cargo install --path . at the checkout root, and this repository’s manifest lives in rust/, so it cannot build these hooks.
The YAML round trip
The one invariant no byte-level fixture can state: a YAML block scalar reads the lines below it, so the hole a removal leaves on a comment’s line can be read back as part of a value.
tools/yaml_roundtrip.py strips thousands of generated documents under every layout and every policy and asks a real YAML parser whether they still mean the same thing.
The corpus and both enumerated sweeps run in full in CI: they are where the hazard lives, and they are the same documents on every run.
Only the pseudo-random set is cut there, because its cost is linear and its value is not — python3 tools/yaml_roundtrip.py runs the whole 2400 on demand, and --seed moves it.
Every pass is one fsync per rewritten file, so the tool overlaps them rather than waiting on them in turn.
The container image
Every release publishes a multi-architecture image to the GitHub Container Registry:
docker run --rm -v "$PWD:/src" ghcr.io/p4suta/ocomment:0.1.0 check
linux/amd64 and linux/arm64 are built, and both carry the exact binary the matching ocomment-<arch>-unknown-linux-musl.tar.gz release archive contains — the release workflow pushes the artifacts it already built and smoke tested rather than compiling the tag a second time.
What is in the image
A scratch base, one statically linked musl binary at /ocomment, and LICENSE-MIT and LICENSE-APACHE under /licenses.
There is no shell, no package manager, and no libc, so nothing else can be run in the container and docker exec … sh has nothing to exec.
The entrypoint is the binary itself,
which is why arguments are written as if ocomment were on the command line:
docker run --rm -v "$PWD:/src" ghcr.io/p4suta/ocomment:0.1.0 --version
docker run --rm -v "$PWD:/src" ghcr.io/p4suta/ocomment:0.1.0 diff src >fix.patch
docker run --rm -v "$PWD:/src" ghcr.io/p4suta/ocomment:0.1.0 check --format sarif
The working directory is /src and the default command is check, so a bare run checks whatever was mounted there.
Exit codes, --format, and the .ocomment.toml discovery rules are the ones the CLI documents: a config file inside the mounted tree is found exactly as it would be on the host.
Writing files back
The container runs as uid 65532, which owns nothing on the host, so fix needs to be told who to write as:
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/src" \
ghcr.io/p4suta/ocomment:0.1.0 fix src
fix writes each file through a temporary file beside it, so the process needs write permission on the containing directory as well as the file.
For a read-only command, mounting read-only makes that explicit and costs nothing:
docker run --rm -v "$PWD:/src:ro" ghcr.io/p4suta/ocomment:0.1.0 check
fix --interactive needs a terminal on both standard input and standard output, so add -it when you want it.
What the image cannot do
--staged reads and rewrites Git index blobs by running git, and there is no git in the image, so it fails with --staged needs a Git repository.
Mounting the host’s .git directory does not help — the binary still has no git to run.
Use the host CLI for staged workflows: cargo install ocomment --locked, a release archive, or the pre-commit hook.
Fetching a plugin from an https:, gh:, or oci: source needs curl, gh,
or oras, and --identity verification needs cosign; none of them are in the image either.
A plugin already vendored into the mounted tree loads normally, because that is the binary’s own WASM host doing the work.
ocomment doctor lists every one of these:
$ docker run --rm -v "$PWD:/src:ro" ghcr.io/p4suta/ocomment:0.1.0 doctor
...
git: not found (needed for --staged)
curl: not found (needed for https:// plugin sources)
gh: not found (needed for gh: plugin sources)
oras: not found (needed for oci: plugin sources)
cosign: not found (needed for --identity verification)
Tags
0.1.0 pins one release.
0.1 follows the patch releases of that minor series, and latest follows the newest release.
Pin the full version in CI,
or pin the digest when the image must never move at all:
docker run --rm -v "$PWD:/src" ghcr.io/p4suta/ocomment@sha256:… check
Verifying the image
The image is signed keylessly with Sigstore and carries a build-provenance attestation, both bound to the release workflow of this repository:
cosign verify ghcr.io/p4suta/ocomment:0.1.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github\.com/P4suta/OComment/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$'
The regexp is the point of the check: it accepts only a signature made by .github/workflows/release.yml in P4suta/OComment running on a v* tag.
A looser identity — anything matching .*, say — would accept a signature from any workflow in any repository and prove nothing.
The provenance attestation is verified with either the GitHub CLI or cosign:
gh attestation verify oci://ghcr.io/p4suta/ocomment:0.1.0 --repo P4suta/OComment
The image is also published with an SPDX SBOM and SLSA provenance attached by buildx, which docker buildx imagetools inspect ghcr.io/p4suta/ocomment:0.1.0 lists.
Building it yourself
docker build . at the repository root compiles the CLI from source in a rust:1.88-alpine stage instead of downloading a release.
That is the path CI exercises on every pull request, so it stays working between releases:
docker build -t ocomment:dev .
docker run --rm -v "$PWD:/src:ro" ocomment:dev check
A source build compiles for the platform being built, so building a foreign architecture this way runs the compiler under emulation and is slow. The release workflow avoids that entirely by replacing the builder stage with a buildx named context holding the already-built binaries:
docker buildx build --build-context builder=release/binaries \
--platform linux/amd64,linux/arm64 .
where release/binaries holds out/amd64/ocomment and out/arm64/ocomment.
Library
ocomment-core is the engine the CLI, the LSP server, and the plugin host are all built on, published as an ordinary crate.
It does no I/O: it takes bytes and returns what it found and what it would write.
cargo add ocomment-core
The complete API reference lives on docs.rs and is generated from the source, so it is the authority on every type and every field:
ocomment-core— the scanner, the policy, the transform, and the source map.ocomment-plugin-sdk— for writing a WebAssembly scanner plugin; see Plugins.ocomment— the CLI crate, if you need to depend on the binary’s own version metadata.
Every example on this page is a doctest in the crate, so CI compiles and runs it on every pull request. The longer ones are checked-in examples you can run:
cargo run -p ocomment-core --example strip
cargo run -p ocomment-core --example external_spans
cargo run -p ocomment-core --example incremental
cargo run -p ocomment-core --example profile
The three calls
scan reports.
transform reports and also gives you the bytes.
apply_edits is the last step of transform, exposed on its own for a caller that wants to filter or postpone the edits.
use ocomment_core::{CommentKind, Language, ScanOptions, scan};
let report = scan(b"let x = 1; // note\n", Language::Rust, ScanOptions::default());
assert_eq!(report.comments.len(), 1);
assert_eq!(report.comments[0].kind, CommentKind::Line);
assert!(report.comments[0].action().removes());
use ocomment_core::{Language, TransformOptions, transform};
// NOTE: A BOM, a CRLF ending, and a comment to take out.
let source = "\u{feff}fn main() {} // trailing\r\n".as_bytes();
let result = transform(source, Language::Rust, TransformOptions::default());
assert_eq!(result.output, "\u{feff}fn main() {} \r\n".as_bytes());
use ocomment_core::{Language, TransformOptions, apply_edits, transform};
let source = b"let x = 1; // note\nlet y = 2; // and\n";
let result = transform(source, Language::Rust, TransformOptions::default());
// NOTE: Sorted and non-overlapping, so one pass applies them.
assert!(
result
.edits
.windows(2)
.all(|pair| pair[0].span.end <= pair[1].span.start)
);
assert_eq!(apply_edits(source, &result.edits), result.output);
A TransformResult carries the output, the edits that produced it, the report those edits came from, and a source_map from the original byte offsets to the new ones — which is what lets an editor keep a cursor, a diagnostic, or a breakpoint pointing at the right place after a removal.
What the engine guarantees
- Byte spans are half-open:
span.start..span.end. - Edits are sorted and non-overlapping, so applying them in order is enough.
- The source is never required to be valid UTF-8. A BOM, CRLF line endings, a missing trailing newline, and non-UTF-8 bytes outside the edited spans all come back unchanged.
- The output of a scan is deterministic for the same bytes, language, and options — that is what the OCaml reference implementation is compared against.
The other axis
Policy decides whether a comment stays.
StyleRules decides how it reads once it has, and the two are deliberately separate tables: a comment that fails a condition of survival is removed, and a comment that fails a style rule is rewritten.
A verdict is therefore three-valued.
Action is Keep, Rewrite or Remove, and the two questions worth asking about one are removes() and changes_bytes() — not == Action::Keep, which is a category written as one variant’s name and answers wrongly the day the category gains a member.
Disposition::Rewrite carries the bytes it would write, so the rule and the replacement cannot disagree.
Where the answer is about a paragraph rather than a comment it is not on any comment at all.
ScanReport::runs holds them, in source order:
use ocomment_core::{Language, ProseOrigin, ScanOptions, StyleRule, StyleRules, Wrap, scan};
let options = ScanOptions {
policy: ocomment_core::Policy::None,
style: StyleRules { wrap: Wrap::Sentence, ..StyleRules::default() },
..ScanOptions::default()
};
let report = scan(b"fn a() {}\n// One sentence. Another one.\n", Language::Rust, options);
let run = &report.runs[0];
assert_eq!(run.origin, ProseOrigin::Comments);
assert_eq!(run.rule, StyleRule::Wrap);
assert_eq!(run.replacement, b"// One sentence.\n// Another one.");
A run is there and not on the comments because the bytes it replaces are not any one comment’s: joining two comment lines moves the newline and the indentation between them, and those belong to neither.
ProseOrigin::Document is the same kind of answer about a Markdown page’s own prose, which is not a comment either.
A caller that reads only comments finds every removal and no reflow.
What survives
Every comment is classified as a CommentKind first — from its delimiters, then from its own text and position — and the Policy then decides that kind:
| Kind | conservative | standard | all |
|---|---|---|---|
line, block | remove | remove | remove |
doc-line, doc-block | keep | remove | remove |
license | remove | keep | remove |
directive, html-comment | keep | keep | remove |
shebang, encoding | keep | keep | keep unless forced |
load-bearing, optimizer-hint, version-comment | keep | keep | keep unless forced |
The policy is the last word rather than the first: keep_kinds, keep_regex,
remove_kinds and remove_regex on ScanOptions are all tested before it, in that order.
Policies is the same table from the binary’s own mouth, and Why a comment was kept lists what makes a comment a directive.
explain_disposition answers why for one comment, naming the rule that applied rather than summarising it:
use ocomment_core::{
Action, CommentKind, DispositionExplanation, Language, Policy, ScanOptions,
explain_disposition,
};
let mut options = ScanOptions::default();
let why = explain_disposition(CommentKind::Line, b"// note", Language::Rust, &options);
assert_eq!(why.action(), Action::Remove);
assert!(matches!(
why,
DispositionExplanation::RemovedByDefault {
policy: Policy::Standard,
kind: CommentKind::Line,
}
));
options.keep_regex.push(r"^//\s*NOTE\b".into());
let kept = explain_disposition(CommentKind::Line, b"// NOTE: why", Language::Rust, &options);
assert_eq!(kept.action(), Action::Keep);
assert!(matches!(kept, DispositionExplanation::KeptByRegex { index: 0, .. }));
assert_eq!(kept.to_string(), r"kept: matched keep_regex #0 `^//\s*NOTE\b`");
explain_disposition_with takes pattern sets compiled once, which is what you want when explaining a whole file rather than one comment.
One rule is missing from that account, and no reading of a comment’s bytes could supply it: a YAML block scalar decides where its body ends from the lines below it, so the comment a body ends at is kept for where it sits rather than for what it says.
explain_comment takes the Comment a scan produced instead of a kind and some bytes, which is what lets it see that rule — it is the entry point that agrees with what the scan recorded, and for every other comment it answers exactly as explain_disposition does:
use ocomment_core::{
Action, DispositionExplanation, Language, ScanOptions, explain_comment, scan,
};
let source = b"k: |\n a\n# ends the block\n # yamllint disable\nz: 1\n";
let options = ScanOptions::default();
let report = scan(source, Language::Yaml, options.clone());
// NOTE: The scan kept the first comment: removing its line would hand the directive under it back to the block scalar above.
let comment = &report.comments[0];
let why = explain_comment(
comment,
&source[comment.span.start..comment.span.end],
Language::Yaml,
&options,
);
assert_eq!(why.action(), Action::Keep);
assert!(matches!(
why,
DispositionExplanation::KeptStructural {
language: Language::Yaml
}
));
explain_comment_with is to explain_comment what explain_disposition_with is to explain_disposition: the same answer, against pattern sets the caller compiled once.
Choosing the language
detect_language resolves a path and its contents to a Language, and Language can also be named directly when you already know it — which is what you want for a buffer that has no path.
Languages and dialects lists everything built in, and a profile describes a delimiter-based syntax that is not.
use std::path::Path;
use ocomment_core::{Dialect, Language, detect_language};
let found = detect_language(Some(Path::new("src/app.tsx")), b"").unwrap();
assert_eq!(found.language, Language::TypeScript);
assert_eq!(found.dialect, Dialect::Tsx);
assert_eq!(found.reason, "extension");
// NOTE: No name, so the shebang decides.
let piped = detect_language(None, b"#!/usr/bin/env python3\n").unwrap();
assert_eq!(piped.language, Language::Python);
A scanner of your own
transform_spans takes comment spans an external scanner already found and puts them through the same policy, layout, edit validation, and source map as a built-in scan, after checking that the spans are non-empty, sorted,
non-overlapping, and inside the source.
That is the hand-off point a WebAssembly plugin uses, and it is the one to use for a scanner you would rather keep in your own process.
use ocomment_core::{
ByteSpan, CommentKind, ExternalSpanError, Language, TransformOptions, transform_spans,
};
let source = b"a/* ordinary */b/* directive */";
let result = transform_spans(
source,
Language::Unknown,
&[
(ByteSpan::new(1, 15), CommentKind::Block),
(ByteSpan::new(16, source.len()), CommentKind::Directive),
],
TransformOptions::default(),
)
.unwrap();
// NOTE: The same policy the built-in scanners get: the directive is kept.
assert_eq!(result.output, b"a b/* directive */");
let bad = transform_spans(
source,
Language::Unknown,
&[(ByteSpan::new(2, source.len() + 1), CommentKind::Block)],
TransformOptions::default(),
);
assert!(matches!(bad, Err(ExternalSpanError::OutOfBounds { .. })));
A DeclarativeProfile is the smaller answer: literal comment and string delimiters, read in a single byte-oriented pass.
It needs no code, and what it cannot express it refuses rather than guesses — validate_profile rejects a delimiter that is a prefix of another, a nested block whose tokens overlap, and a delimiter containing a line terminator, because none of those has a single reading.
requires_boundary and requires_line_start are how a profile says where its token is allowed to open.
The first keeps a token that also occurs inside an identifier from swallowing the rest of the line; the second is for the formats whose # is a comment as the first byte of a line and part of the data anywhere else, which is every pattern list — a .gitignore entry may contain one, and \#literal is how an entry that starts with one is written.
Without the second, a removal in such a file writes a shorter pattern back and the file quietly stops matching what the line named.
use ocomment_core::{
CommentKind, DeclarativeProfile, LineDelimiter, StringDelimiter, TransformOptions,
transform_profile,
};
let profile = DeclarativeProfile {
name: "lisp".into(),
extensions: vec!["lisp".into()],
line_comments: vec![LineDelimiter {
start: ";;".into(),
kind: CommentKind::Line,
..Default::default()
}],
strings: vec![StringDelimiter {
start: "\"".into(),
end: "\"".into(),
escape: Some("\\".into()),
..Default::default()
}],
..Default::default()
};
let source = b"(print \";; not a comment\") ;; a comment\n";
let result = transform_profile(source, &profile, TransformOptions::default()).unwrap();
assert_eq!(result.output, b"(print \";; not a comment\") \n");
The same profile can be written in .ocomment.toml instead, which is what most callers want; see Configuration.
Editing a live buffer
IncrementalDocument applies DocumentChanges and rescans only what moved,
under a PositionEncoding of UTF-8, UTF-16, or UTF-32.
That is the path the LSP server takes, and it is the one to use for anything that rescans on every keystroke rather than on every save.
apply_changes is transactional.
A batch that fails validation — a stale version, an inverted span, a span reaching past the end — leaves the source, the report, the checkpoints, and the version exactly as they were, so a misbehaving client cannot corrupt the document:
use ocomment_core::{
ByteSpan, DocumentChange, IncrementalDocument, IncrementalError, Language, ScanOptions,
};
let mut document = IncrementalDocument::new(
b"let x = 1; // note\n".to_vec(),
Language::Rust,
ScanOptions::default(),
1,
);
// NOTE: A span past the end of the document is refused, and nothing moves.
let outside = ByteSpan::new(0, document.source().len() + 1);
assert_eq!(
document.apply_changes(
&[DocumentChange {
span: outside,
replacement: Vec::new(),
}],
2,
),
Err(IncrementalError::InvalidSpan),
);
assert_eq!(document.version(), 1);
assert_eq!(document.report().comments.len(), 1);
last_rescan_span reports how much of the document the last edit actually cost,
so the saving is measurable rather than assumed.
Versioning
The workspace follows semantic versioning, and every crate in it is released at the same version by the same tag. The minimum supported Rust version is 1.88, and a dedicated CI job builds the workspace against exactly that toolchain on every pull request, so a dependency that quietly raises it fails the build rather than a user’s install.
Both library crates are documented item by item: missing_docs is denied in CI, the doctests on this page run there, and cargo doc runs with -D warnings, so a public item added without documentation, an example that stops compiling, or a broken intra-doc link fails the build.
The ocomment binary crate is documented in the same run for its links alone — nothing publishes its rustdoc, but its modules describe each other, and a link naming an item somebody has since renamed is a wrong sentence wherever it is written.
Scanner plugins
OComment scanner plugins are WebAssembly components implementing spec/ocomment-scanner.wit.
A plugin receives source bytes and scan options and returns only comment spans and kinds.
It cannot edit files.
The host rechecks API version, bounds, ordering, overlap, policy, and every generated edit.
The host exposes no WASI, filesystem, network, clock, random, or imported host functions. Each invocation receives an input-proportional fuel budget and explicit memory and instance limits.
ocomment plugin new my-scanner
cd my-scanner
cargo build --release --target wasm32-unknown-unknown
wasm-tools component new target/wasm32-unknown-unknown/release/my_scanner.wasm \
-o my-scanner.component.wasm
Add local artifacts directly.
Remote artifacts require a verified digest and Sigstore identity and are fetched only by explicit add or update commands.
Normal scans and LSP sessions are offline.
ocomment plugin add ./my-scanner.component.wasm --name my-scanner
ocomment plugin add 'https://example.test/my-scanner.wasm' \
--name my-scanner --sha256 <64-hex-digest> --identity release@example.test
ocomment plugin add 'gh:owner/repository@v1.2.3#my-scanner.wasm' \
--name my-scanner --sha256 <64-hex-digest> --identity release@example.test
ocomment plugin add 'oci:ghcr.io/owner/my-scanner:v1#my-scanner.wasm' \
--name my-scanner --sha256 <64-hex-digest> --identity release@example.test
ocomment plugin verify
.ocomment.lock pins the source, version, SHA-256, signature identity, API, and capabilities.
plugin update accepts a new digest only after verifying the artifact against the identity already pinned in that lock.
Artifacts live below .ocomment/plugins/.
Route a locked and enabled plugin by extension:
[plugins]
enabled = ["my-scanner"]
routes = { xyz = "my-scanner" }
memory_mib = 64
instances = 4
fuel_per_byte = 128
Verifying downloads
Every release asset is published with three independent pieces of evidence: a SHA-256 digest, a keyless Sigstore signature, and a GitHub build-provenance attestation. They answer different questions, so it is worth knowing which one you are relying on.
| Evidence | Answers |
|---|---|
SHA256SUMS and the per-archive .sha256 | Did the bytes arrive intact? |
*.sigstore.json (cosign) | Was this file signed by this repository’s release workflow? |
Build provenance (gh attestation) | Which workflow run, from which commit, produced it? |
A digest alone proves nothing about origin: whoever could replace the archive could replace the digest beside it.
The signature and the attestation are what tie the file to P4suta/OComment and to the tag it claims to come from.
Examples pin 0.1.0 — use the version you want, and change it in every line of a command: the tag inside a signing identity is part of what the check proves,
not a detail of the example.
Download
gh release download v0.1.0 --repo P4suta/OComment \
--pattern 'ocomment-x86_64-unknown-linux-gnu.tar.gz*' \
--pattern 'SHA256SUMS*'
The trailing * in the first pattern brings the archive’s .sha256 and its .sigstore.json bundle along with the archive itself.
Check the digest
sha256sum --ignore-missing --check SHA256SUMS
SHA256SUMS covers every archive, per-archive checksum, the SPDX SBOM, and the generated Homebrew, Scoop, and WinGet definitions of that release, so --ignore-missing is what lets it pass when you downloaded one of them.
The combined checksum file itself is signed and attested.
On macOS the command is shasum -a 256, and in PowerShell it is Get-FileHash.
Check the provenance attestation
gh attestation verify ocomment-x86_64-unknown-linux-gnu.tar.gz \
--repo P4suta/OComment
This is the shortest honest check, because gh resolves the trust root itself.
It succeeds only for an archive built by a workflow run in this repository, and it prints the workflow and the commit that produced it.
The composite GitHub Action runs this same verification on the runner before it uses the binary.
Check the signature
cosign verify-blob \
--bundle ocomment-x86_64-unknown-linux-gnu.tar.gz.sigstore.json \
--certificate-identity \
'https://github.com/P4suta/OComment/.github/workflows/release.yml@refs/tags/v0.1.0' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ocomment-x86_64-unknown-linux-gnu.tar.gz
The identity is the signing workflow, not a person: releases are signed keylessly by .github/workflows/release.yml running on the tag, and there is no private key anywhere to be stolen.
Pin the exact tag as above when you know which version you are installing.
A script that accepts any released version wants the pattern instead:
--certificate-identity-regexp \
'^https://github\.com/P4suta/OComment/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$'
Do not relax that expression to match any ref.
@refs/tags/v... is the part that says the signature came from a released tag rather than from a branch or a pull request, and the release-tag ruleset is what makes those tags immutable.
The same command verifies any other signed asset of the release by name: the SPDX SBOM ocomment.spdx.json, the generated ocomment.rb,
ocomment-scoop.json, and ocomment.winget.yaml definitions, the SHA256SUMS file itself, and each per-archive checksum.
Verify the container image
cosign verify ghcr.io/p4suta/ocomment:0.1.0 \
--certificate-identity-regexp \
'^https://github\.com/P4suta/OComment/\.github/workflows/release\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify oci://ghcr.io/p4suta/ocomment:0.1.0 --repo P4suta/OComment
The image carries the same musl binary the matching release archive contains, because the release workflow feeds the already-built archives to the image build rather than compiling the tag a second time. Verifying the archive and verifying the image therefore vouch for the same bytes.
If a check fails
Do not install the file. A digest mismatch is usually a truncated download and is worth retrying once. A signature or attestation failure is not: report it through the process in SECURITY.md rather than opening a public issue.
FAQ
Does the default policy remove documentation comments?
No. A doc comment is not commentary about the code, it is the API documentation, and it ships: removing one empties a page on docs.rs, an entry on pkg.go.dev, a section of a javadoc site. That is a public loss of the same kind as removing a licence notice, and the default declines both.
standard takes them, which is a policy someone names on purpose.
If you want them gone for one build artifact and kept in the source, that is what [[overrides]] is for.
If your project wants them kept — this one does — say so once:
[policy]
keep_kind = ["doc-line", "doc-block"]
Why was this comment kept? has the full table of what each policy does to each kind.
Does a bare ocomment check the whole repository or just here?
Just here. A command that names no path checks the current directory, so running it from a subdirectory checks that subdirectory. From the root of a repository that is the whole repository:
cd "$(git rev-parse --show-toplevel)" && ocomment
Glob settings do not move with you.
files.include, files.exclude, and every [[overrides]].paths pattern is relative to the project root — the directory holding .ocomment.toml, or the repository above it — however deep you run from.
Why did naming a path change what got scanned?
Because naming it is a request rather than a default.
A walk with no path skips hidden files and files over 32 MiB; an explicit ocomment . or ocomment src bypasses those two limits, on the grounds that you asked for that path by name.
The binary and symlink safety checks still apply either way.
Does it handle a partially staged file?
Yes, with --staged.
ocomment check --staged reads the Git index blobs — the exact bytes the commit will carry — instead of the working tree, so a file whose comment is staged but whose other edits are not is judged on the staged half alone.
ocomment fix --staged rewrites the index blobs and maps those edits back to the working tree only where the mapping is unique; --index-only is the escape hatch when it is not.
The generated Lefthook hook deliberately does not use Lefthook’s stage_fixed,
because that setting stages the whole working-tree file and destroys the partial staging it was meant to protect.
What happens to a file that is not valid UTF-8?
It is scanned anyway. The engine works on bytes, so a file with a Latin-1 name in a string literal, or an encoding it has never heard of outside the regions it edits, is read, reported on, and rewritten with those bytes untouched. Only the spans it actually removes are changed.
A file that fails to lex — an unterminated block comment, say — is reported as invalid and left alone, unless --force-invalid tells the run to edit the part that scanned.
That part ends where the lex failed.
A scanner that cannot find the end of a token does not know where the next one starts, so an unterminated /* is reported as a comment running to the end of the file — and the code under it is not a comment.
--force-invalid removes the comments the scanner closed before the failure and leaves everything from there on alone.
The verdicts on the rest still stand in the report: they are removable, and they are still in the file,
which is what a file that does not lex earns.
The one exception is an error that cost the lexer nothing.
A malformed Java \uXXXX escape is found in a pass over the whole source before a token is read,
so it makes the file invalid without putting a single comment in doubt, and a forced run over it edits everything.
Why is this comment still here after fix?
Ask it:
ocomment check --explain
--explain puts the rule that decided each comment, and the setting behind that rule, on the line under it.
Nine times in ten the answer is one of: the policy does not remove that kind, a keep_regex or keep_kind in .ocomment.toml protects it, an [[overrides]] entry matched the path, or the comment is a directive that some other tool reads.
See Why was this comment kept?.
What do the exit codes mean?
0 clean, 1 findings, 2 failure.
Specifically: 0 when nothing removable was found and every requested change was applied, and 2 for an invalid source, configuration, plugin, or I/O failure.
1 covers the four ways a run ends with something outstanding: removable comments were reported, a diff was printed, fix --tidy left a removal for whoever is reading, or a staged fix rewrote the index.
The last is why a hook can trust it — the bytes the commit will carry are no longer the bytes their author staged, and the exit code is where that is said.
1 from diff or fix --dry-run means the patch is not empty, which is why a CI gate can be ocomment check with nothing around it, and why a script that tests $? -ne 0 will misread a non-empty diff as an error.
Are CRLF line endings, BOMs, and the final newline preserved?
Yes, all three.
A removal replaces the comment’s own bytes and nothing else, so a CRLF file stays CRLF, a UTF-8 BOM stays where it was, and a file with no trailing newline does not grow one.
Every layout also preserves the line count of the file: a comment that spanned three lines is replaced by something that still spans three lines, so line numbers in stack traces and git blame keep pointing at the same statements.
Policies and layouts shows what each one leaves behind.
How fast is it, and how would I know?
Fast enough that it is not the slow part of a hook.
The release gate refuses to publish a build that misses any of: a 20 ms median cold --version, 500 MiB/s for the simple scanners, 200 MiB/s for JavaScript and Shell, a 25 MiB stripped binary, and no more than a 5% regression against the checked-in baseline on a fixed machine.
Where typos is installed, it also requires a no-op repository scan to be no slower than 1.5 times typos on the same tree.
CLI workloads add a 17.3 ms self-scan budget, a 90 MiB peak-RSS budget for a 64 MiB quiet check, a one-second budget for 40,000 Human findings, and 100 MiB peak-RSS budgets for 100,000 JSON and SARIF findings. A regex-configured set of 2,000 small files is measured alongside them.
Those are gates rather than marketing numbers: measure on your own tree with ocomment -v, which reports what was scanned and skipped.
How much do I have to trust a scanner plugin?
Less than you would have to trust a normal plugin, by construction. A plugin is a WebAssembly component that receives source bytes and returns comment spans and kinds; it cannot edit a file, and the host rechecks the API version, the bounds, the ordering, the overlap, the policy, and every edit generated from what it returned. There is no WASI, no filesystem, no network, no clock, and no randomness, and each invocation gets a fuel budget and explicit memory and instance limits.
Remote artifacts need a pinned SHA-256 and a Sigstore identity, recorded in .ocomment.lock, and are fetched only by an explicit plugin add or plugin update.
Ordinary scans and LSP sessions never go to the network.
See Plugins.
Does it work on Windows?
Yes.
Every release publishes an x86_64-pc-windows-msvc archive with PowerShell completions in it, plus the Scoop and WinGet manifests generated from that archive, and CI smoke tests the binary on a Windows runner alongside Linux and macOS.
CRLF line endings are preserved rather than normalised, which matters more on Windows than anywhere else.
The container image is Linux-only, as container images are.
Comparison
Several well-known tools remove comments from source code, and most of them were built for a different job than OComment was. This page is about scope, not quality: each of these does its own job well, and the useful question is which job you have.
The rows below describe each project’s documented purpose and scope, taken from its own documentation, and were not benchmarked or feature-tested here. Tools change; check the current documentation of anything on this page before relying on a row. Only the OComment column describes software this book can check: the rest is read off other projects’ own documentation.
| OComment | strip-comments | decomment | cloc --strip-comments | gcc -E -fpreprocessed | |
|---|---|---|---|---|---|
| What it is | A comment checker and remover | A Node.js library and CLI for stripping comments | A Node.js library for stripping comments | A line counter, with a comment-stripping side output | A C preprocessor |
| Built to | Report, gate, and remove comments under a policy | Strip comments from JavaScript-style source | Strip comments while preserving string literals | Count lines of code | Preprocess C-family translation units |
| Language coverage | 30 languages and 18 dialects, plus declarative profiles and WebAssembly plugins | JavaScript and other C-style syntaxes | JavaScript, JSON, CSS, HTML | Very broad, from its own per-language comment table | C, C++, Objective-C, and their preprocessed inputs |
| Keeps tool directives by default | Yes — shebangs, encoding preambles, //go:build, lint controls, optimiser hints, MySQL versioned comments | Documents an option for keeping /*! “protected” comments | Documents an option for keeping /*! “protected” comments | Not a stated goal | Not a stated goal |
| Configurable per path | Yes, [[overrides]] globs in .ocomment.toml | Through the calling program | Through the calling program | No | No |
| Check-only mode with a CI exit code | Yes, ocomment check | No | No | No | No |
| Machine-readable report | JSON, JSONL, SARIF, GitHub annotations | No | No | Counts, in several formats | No |
| Rewrites files in place | Yes, as one rollback-backed transaction | Through the calling program | Through the calling program | Writes a stripped copy of each file | Writes to standard output |
| Non-UTF-8 input | Scanned and preserved byte for byte | Not stated | Not stated | Not stated | Set by -finput-charset |
| Keeps line numbers | Yes under lines and columns, but for the one YAML line a block scalar would read back; compact gives them up by design | Not stated | Not stated | Not stated | Rewrites line structure, and emits line markers |
| Editor integration | LSP 3.18 server, VS Code extension | No | No | No | Not applicable |
| Independent cross-check | An OCaml reference implementation compared on every fixture | — | — | — | — |
| Installs as | A single static binary, or a crate | An npm package | An npm package | A Perl script or package | Part of a C toolchain |
When something else is the right tool
- You want a count, not a rewrite.
clocanswers “how much of this is comment?” directly, across more languages than any comment remover needs to support, and writing the stripped copies is a side output of that. - You are already inside a Node build step, transforming strings in memory rather than files on disk.
A library you can call is less friction than a binary you have to install, and
strip-commentsanddecommentare libraries first. - You are preprocessing C anyway. If the compiler is already running over the translation unit,
gcc -Ehas removed the comments as part of the job. Note that it is doing much more than that — macro expansion, includes, line markers — so its output is not the same file minus comments.
What OComment adds
- A policy, not a switch. A comment that another program reads is not commentary, and the default keeps every one it recognises. See Why was this comment kept?.
- An answer to “why”.
--explainnames the rule and the setting behind every decision, which is what makes a house rule reviewable rather than mysterious. - A gate.
ocomment checkexits1on findings and speaks SARIF, so the same tool that removes comments can hold a line in CI and in a pre-commit hook. This repository uses it on itself. - Bytes in, bytes out. BOMs, CRLF, missing trailing newlines, and non-UTF-8 bytes outside the edited spans survive a rewrite, and every removal is committed as one transaction.
- A second implementation. The OCaml reference implementation shares no code with the Rust one, and the two are compared on the scanner, the classification, the diagnostics, the edits, the transformed bytes, and the source maps.
Releasing OComment
Tags named vMAJOR.MINOR.PATCH run the release workflow.
It builds and smoke tests Linux x86_64/aarch64 GNU and musl, macOS Intel/Apple Silicon, and Windows x64 archives.
Archives contain the binary, licenses, README, man page, and shell completions.
It creates a draft release containing every archive and checksum,
an SPDX JSON SBOM, package-manager definitions, keyless Sigstore signatures,
and GitHub build-provenance attestations.
The workflow publishes the CLI to GitHub Releases and crates.io and publishes its container to GHCR; it does not build or publish a VS Code extension.
Release preparation is automated by .github/workflows/release-pr.yml.
On a push to main, release-plz compares the three product crates with crates.io and opens or refreshes one draft Release PR.
It updates their shared workspace version, internal dependency requirements, Cargo.lock, and the root CLI changelog.
The workflow then synchronizes the stable version pins in user docs,
regenerates the man page and completions, and explicitly dispatches CI, docs,
and CodeQL for the bot-created branch.
Pull-request runs caused by the default Actions token can require approval; workflow_dispatch is an explicit GITHUB_TOKEN recursion exception, so the dispatch makes the required checks independent of a separate PAT and of that approval queue.
Release-plz deliberately does not publish a crate, create a tag, or create a GitHub Release here.
Those operations remain owned by the signed-tag workflow and its final Environment approval.
release-plz.toml and tools/check_ci_contracts.py both enforce that separation.
Before tagging:
- Review the draft Release PR, including its proposed SemVer change and changelog.
Mark it ready and merge it only after its dispatched checks pass.
editors/vscode/package.jsonand its changelog are deliberately independent and are not CLI release inputs. - On the merged
main, runcargo xtask release-checkand confirm the cross-target smoke jobs and three expanded-crate artifact checks are green. - Confirm
HEADis a clean, signed commit equal toorigin/main, versionMAJOR.MINOR.PATCHis still unused by all three crates, and neither its tag nor its GitHub Release exists. - Confirm the crates.io Trusted Publisher entries, GHCR visibility plan, and required
releaseEnvironment reviewer from the checklist below are ready.
Publishing is workflow-owned.
After the draft exists, crates.io and GHCR run as independent retryable jobs using the tag and already-built artifacts.
tools/publish-crates.sh reads all three package names and versions from Cargo metadata, skips an exact version already visible in the registry, and resumes in dependency order.
Only after both destinations succeed does the reviewer-protected release Environment allow finalize to make the GitHub release public.
Do not publish crates by hand between those jobs; that defeats the resumable state the workflow verifies.
Published crate boundary
The product has three intentionally public crates: ocomment,
ocomment-core, and ocomment-plugin-sdk.
Release-plz manages exactly these three as one version group, and only the CLI owns the release changelog.
The CLI used to publish three implementation-support forks:
ocomment-wasm-runtime-layer, ocomment-wasmi-runtime-layer, and ocomment-wasm-component-layer.
Their narrowly patched implementations now live as private modules inside ocomment, so releases publish only the three product crates in dependency order.
The support-fork versions already used by ocomment 0.1.0 remain immutable registry history and must not be yanked: doing so would break resolution for that release.
Do not publish new versions of those support crates.
One-time publishing setup
- In the crates.io settings for each of
ocomment,ocomment-core, andocomment-plugin-sdk, add a GitHub Trusted Publisher with ownerP4suta, repositoryOComment, workflow filenamerelease.yml, and environmentcrates-io. The release job uses GitHub OIDC to obtain a short-lived token; it does not read a registry secret. After verifying the first trusted publication, revoke the old crates.io API token and delete the now-unusedCARGO_REGISTRY_TOKENEnvironment secret. See the crates.io Trusted Publishing documentation. - If this repository restricts Actions to an allowlist, permit the official
rust-lang/crates-io-auth-actionat the SHA pinned inrelease.yml. - Configure the
releaseEnvironment withP4sutaas a required reviewer. The approval is intentionally the last gate: leavefinalizewaiting until the registries and draft assets have been verified. - The first GHCR package is private.
As soon as
publish-containercreates it, change the package visibility to public before approvingrelease.
Release sequence
- Fetch
origin/mainand tags, verify a clean tree and signedHEAD, and run the metadata and release gates one final time on the exact commit. Check thatvMAJOR.MINOR.PATCHand its GitHub Release do not exist and that all three target crate versions are unused. - Create and verify a signed annotated
vMAJOR.MINOR.PATCHtag on that commit, then push only that tag ref. Do not move or recreate a release tag. - Monitor the Release workflow. Before either registry result is accepted, it must have built and smoke-tested seven target archives, generated the SBOM, checksums, signatures and attestations, published three crates in dependency order, and pushed the amd64/arm64 image.
- Make the new GHCR package public.
Inspect its multi-platform manifest,
verify its cosign signature and GitHub attestation, then run
--versionand a sample scan from the image. - After crates.io propagation, install into a clean temporary prefix with
cargo install ocomment --version MAJOR.MINOR.PATCH --locked. Exercise detection,diff,fix --dry-run, andfix, and confirm the installed binary reportsocomment MAJOR.MINOR.PATCH. - While
finalizewaits for approval, download the authenticated draft assets. VerifySHA256SUMS, each Sigstore bundle, GitHub provenance, and the unpacked binaries as described in Verifying downloads. - Approve the
releaseEnvironment only after the preceding checks pass. Confirm thatfinalizepublishes the existing draft as the latest GitHub Release without replacing its assets. - From an external-user path, repeat a GitHub Release download,
cargo install, GHCR pull, and a workflow usingP4suta/OComment@vMAJOR.MINOR.PATCH.
The benchmark workflow is manual-only.
Select the branch or tag to benchmark in the workflow’s Run workflow ref picker, then enter that ref’s full 40-character commit SHA.
A hosted runner requires the input SHA, the immutable workflow-dispatch SHA, and the checked-out commit to agree before the reviewer-protected benchmark environment lets an ephemeral runner execute it.
The runner uses labels self-hosted, linux, x64, ocomment-benchmark, and ephemeral.
tools/release_gate.py enforces a 20 ms median cold --version, 500 MiB/s for simple scanners, 200 MiB/s for JavaScript and Shell,
a 25 MiB stripped binary, and a maximum 5% regression from the checked-in machine baseline.
If typos is installed it also checks that a no-op repository scan is no slower than 1.5 times typos.
Every release also contains signed ocomment.rb, ocomment-scoop.json, and ocomment.winget.yaml definitions generated from the final archive SHA-256 values.
They can be installed directly or submitted unchanged to a future Homebrew tap, Scoop bucket, and WinGet repository.
Creating those upstream listings is outside the CLI release.
The CLI crate contains explicit cargo-binstall URL, archive-format, and in-archive binary metadata for the same target-qualified archives.
The container image
publish-container pushes ghcr.io/p4suta/ocomment for linux/amd64 and linux/arm64 after the draft release exists.
It does not rebuild the tag: it downloads the two *-unknown-linux-musl archives the matrix already produced and feeds them to the Dockerfile through the buildx named context builder, so the image and the archives contain the same bytes.
The image is tagged MAJOR.MINOR.PATCH, MAJOR.MINOR, and latest, signed with cosign, and given a build-provenance attestation pushed to the registry.
A GHCR package is private when it is first created, and the visibility setting belongs to the package rather than the repository, so nothing in this repository can set it.
During the first release, open the package page and set its visibility to public before approving the release Environment. Until that is done every unauthenticated docker pull fails even though the workflow succeeded.
Later releases inherit the setting.
Renaming the image, dropping an architecture, or moving the builder context layout — out/amd64/ocomment and out/arm64/ocomment — breaks pinned pulls and the Dockerfile respectively, so treat both as part of the released contract, exactly like the archive layout.
The VS Code extension
The extension remains under editors/vscode, and the ordinary vscode CI job still lints, compiles, unit-tests, drives a real VS Code instance, and packages a test VSIX.
It is source-only and is not an asset or publication target of the CLI release: the release workflow does not checksum, sign, attest, or attach a VSIX and has no Visual Studio Marketplace or Open VSX jobs.
The extension’s manifest version and changelog are independent of the Rust workspace version and CLI tag. A future extension release needs its own review, credentials, workflow, verification contract, and publication documentation; none should be inferred from a successful CLI release.
The documentation site
.github/workflows/docs.yml builds this book with mdBook and publishes it to GitHub Pages at https://p4suta.github.io/OComment/. It is not tied to a release: the docs job runs on every pull request as a required check, and the deploy-pages job runs on every push to main that touches docs/, spec/,
or the generator, so the site follows the default branch rather than the tags.
Four pages are generated by tools/gen_docs.py from the built binary and from spec/, and python3 tools/gen_docs.py --check runs in the rust job of CI as well as in the docs build, so a change to the CLI’s --help, to the language table, or to the directive table fails the build until the pages are regenerated with python3 tools/gen_docs.py.
One thing is manual and is done once, not per release:
Open Settings → Pages and set the source to “GitHub Actions”. Until that is done, deploy-pages fails on main with a Pages API error even though the build succeeded, and the site stays unpublished.
The default source is a branch,
and nothing in this repository can change it.
Every link into the site from the README, from rust/ocomment/Cargo.toml, and from this book assumes it has been set.
The SARIF helpUri of every rule still points at the README anchor in this repository rather than at the site; moving it is a code change, and a separate one.
The published GitHub Action
action.yml at the repository root is released with the source, so P4suta/OComment@vMAJOR.MINOR.PATCH resolves as soon as the tag exists; no extra publishing step is needed for it to work in a workflow.
Listing it on the GitHub Marketplace is separate and manual and would require its own review. GitHub Action Marketplace listing is outside the CLI release; do not couple it to approval of the CLI’s GitHub Release.
Recommend full-version pins such as P4suta/OComment@vMAJOR.MINOR.PATCH in every example.
The release-tag ruleset forbids deleting or force-moving v*, so a published tag never changes underneath a workflow, and there is deliberately no moving v0 or v0.1 tag to maintain.
A release that changes the action’s inputs,
outputs, or verdict rules is therefore a version bump like any other, and docs/ci.md documents the surface those pins are buying.
The action downloads ocomment-<target>.tar.gz (or the Windows .zip), the combined SHA256SUMS, and the build-provenance attestation.
Verification fails closed when gh is unavailable unless the caller explicitly opts out with verify-attestation: false.
Renaming a release asset, dropping a musl target, or changing the ocomment-<target>/ leading directory breaks every pinned workflow, so treat the archive layout as part of the released contract.