diff --git a/README.md b/README.md index ba318ad..977bebd 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # comment-checker -[![CI](https://github.com/systemfsoftware/claude-code-comment-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/systemfsoftware/claude-code-comment-checker/actions/workflows/ci.yml) -[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE) +[![CI](https://github.com/systemfsoftware/comment-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/systemfsoftware/comment-checker/actions/workflows/ci.yml) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) -> A Claude Code `PostToolUse` hook that blocks unnecessary code comments with checkable, cited reasons — while sparing earned API documentation, directives, and non-obvious intent. +Comment-checker is a `PostToolUse` hook for Claude Code that flags unnecessary code comments and states the exact reason each one fails. It is an alternative to flag-everything comment linters that train agents to ignore warnings: a tree-sitter classifier over 37 languages, gated to F1 ≥ 0.85 on a 60-case corpus, that spares public API docs, directives, and non-obvious intent. + +It never edits your files, never sends code anywhere, and exits deterministically so the hook can gate automation. ```bash -pnpm install -g @systemfsoftware/claude-code-comment-checker +pnpm add -g @systemfsoftware/claude-code-comment-checker ``` -``` +Pipe a `Write` payload to the binary and it reports what it would block: + +```bash $ echo '{"tool_name":"Write","tool_input":{"file_path":"src/load_config.py","content":"import json\n\ndef load_config(path):\n # Parse the config file\n data = json.load(open(path))\n # TODO: fix this later\n # print(data)\n return data\n"}}' | comment-checker An automated reviewer flagged 3 comment(s) in src/load_config.py as unnecessary. @@ -17,7 +21,7 @@ Each is stated with the specific reason it should be removed. Do not dismiss these as "justified" — the reason is given so the claim can be checked, not argued away. - line 4 — # Parse the config file — restates what the code already says (shares config, file, parse) + line 4 — # Parse the config file — restates what the code already says line 6 — # TODO: fix this later — a TODO with no tracked reference — file a ticket or delete it line 7 — # print(data) — dead code left in a comment @@ -26,54 +30,27 @@ one, make the code self-explanatory instead — better names, extraction, a clearer type — and do not re-add the comment. ``` ---- - -## Why - -Most comment linters rely on blunt allowlists: they flag every comment that lacks a specific annotation, or blindly permit any text placed inside a docstring block. This creates high false-positive noise that trains agents and engineers to dismiss warnings entirely. - -`comment-checker` uses tree-sitter AST extraction across 37 programming languages and evaluates comments against prioritized classification rules. When a comment is flagged, the hook provides concrete citations — such as token overlap percentages or verb-to-operator mappings — allowing the agent to verify why a comment failed and fix the underlying code rather than arguing with the tool. - -| Capability | Flag-everything linters | comment-checker | -|---|---|---| -| **Classification model** | Blunt allowlist or regex scan | Prioritized rule tables with syntactic AST context | -| **Public API docstrings** | Flagged or blindly permitted | Spared when containing structured contract tags (`@param`, `Args:`, `Returns:`) | -| **Flag feedback** | Generic warning message | Specific, checkable reason citing token overlap and operator evidence | -| **Incremental edits** | Re-evaluates entire source file | Evaluates only newly added comments; skips fragment restatements | -| **Evaluation standard** | Ad-hoc heuristics | F1 ≥ 0.85 on 60-case multi-language benchmark (`eval/corpus.json`) | - ---- - -## Quick Start +Exit status is the contract: `0` on pass, `2` when comments are flagged. -### 1. Install the binary +## Install -**Recommended (npm / pnpm / yarn / bun):** +One command, for any npm-compatible manager (npm, pnpm, yarn, bun): ```bash -pnpm install -g @systemfsoftware/claude-code-comment-checker +pnpm add -g @systemfsoftware/claude-code-comment-checker ``` -The package distributes prebuilt native binaries for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64) via `optionalDependencies`. Package managers install only the single binary target required for your operating system. No postinstall lifecycle scripts run during installation, ensuring full compatibility with `--ignore-scripts`. +The package publishes one launcher plus per-platform native binaries for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64). The platform package is selected through `os`/`cpu` constraints, and no install hook runs, so `--ignore-scripts` environments work. -
-Other install methods (Cargo, direct binary download) - -**Install via Cargo (requires Rust 1.85+):** - -```bash -cargo install --git https://github.com/systemfsoftware/comment-checker --package claude-code-comment-checker -``` - -**Direct download:** - -Prebuilt tarballs (`comment-checker-.tar.gz`) for all supported platforms are attached to every [GitHub Release](https://github.com/systemfsoftware/comment-checker/releases). - -
+| Method | Command | Notes | +|---|---|---| +| npm/pnpm/yarn/bun | `pnpm install -g @systemfsoftware/claude-code-comment-checker` | Primary path; prebuilt binaries | +| Cargo (from source) | `cargo install --git https://github.com/systemfsoftware/comment-checker --package claude-code-comment-checker` | Rust 1.85+; compiles from source | +| Direct download | Tarball per platform from the [Releases page](https://github.com/systemfsoftware/comment-checker/releases) | `comment-checker-.tar.gz` | -### 2. Configure Claude Code hook +## Wire it into Claude Code -Add `comment-checker` as a `PostToolUse` hook in your user configuration (`~/.claude/settings.json`) or project configuration (`.claude/settings.json`): +Add the hook to user (`~/.claude/settings.json`) or project (`.claude/settings.json`) configuration: ```json { @@ -90,130 +67,118 @@ Add `comment-checker` as a `PostToolUse` hook in your user configuration (`~/.cl } ``` -### 3. Verify execution +On `Edit` and `MultiEdit`, only the comments *added* by the edit are checked — pre-existing comments are left alone. Edits also arrive as fragments, so restatement detection is disabled on them to avoid false positives. -When an agent writes code containing justified comments or clean documentation, the tool exits cleanly with status code `0`: +### Verify the wiring + +Sanity-check with a write that should pass: ```bash -$ echo '{"tool_name":"Write","tool_input":{"file_path":"src/client.py","content":"# SPDX-License-Identifier: Apache-2.0\ndef load(path):\n return open(path).read()\n"}}' | comment-checker +$ echo '{"tool_name":"Write","tool_input":{"file_path":"src/client.py","content":"# SPDX-License-Identifier: Apache-2.0\ndef load(path):\n return open(path).read()\n"}}' | comment-checker; echo "exit=$?" [check-comments] Skipping: No unnecessary comments found +exit=0 ``` ---- - -## What It Flags +## Why not a flag-everything linter -`comment-checker` identifies unnecessary comments across five distinct categories: +Most comment linters use allowlists: flag any comment lacking an annotation, or exempt everything inside a docstring. Both produce noise, and agents learn to dismiss the hook. -| Category | Reason cited | Example | +| | Flag-everything linters | comment-checker | |---|---|---| -| **Restates the code** | `restates what the code already says ()` | `// adds one to one` adjacent to `x += 1` | -| **Narrates control flow** | `narrates the construct the code already shows` | `// loop over each item` adjacent to `for item in items:` | -| **Change-log memo** | `describes what changed, not why — git history already records this` | `// Changed from old_value to new_value` | -| **Dead code** | `dead code left in a comment` | `// fmt.Println("debug")` | -| **Untracked TODO** | `a TODO with no tracked reference — file a ticket or delete it` | `// TODO: fix this later` | +| Classification | Regex/allowlist | Prioritized rule tables over tree-sitter AST context | +| API docstrings | Flagged or fully exempt | Spared when they carry contract structure (`@param`, `Args:`, `Returns:`) | +| Flag feedback | Generic warning | The specific reason, with token-overlap and verb-to-operator evidence where available | +| Incremental edits | Rechecks whole file | Only the new comments; fragment restatements skipped | +| Evaluation standard | Ad-hoc | F1 ≥ 0.85 enforced on a 60-case, 37-language corpus in CI (`crates/comment-checker/tests/f1.rs` + `eval/corpus.json`) | ---- +## What it flags -## What It Spares +Five kinds of unnecessary comment, each with the reason the hook cites: -Comments that provide non-redundant intent or satisfy interface documentation standards are classified as justified and pass without warnings: +| Comment kind | Cited reason | Example | +|---|---|---| +| Restates code | `restates what the code already says` (token overlap cited) | `// adds one to one` next to `x += 1` | +| Narrates flow | `narrates the the code already shows` | `// loop over each item` next to `for item in items:` | +| Change-log memo | `describes what changed, not why` — git already records it | `// Changed from old_value to new_value` | +| Dead code | `dead code left in a comment` | `// fmt.Println("debug")` | +| Untracked TODO | `a TODO with no tracked reference` | `// TODO: fix this later` | -- **License & generated headers** — SPDX identifiers, copyright lines, and generated-file notices (`// SPDX-License-Identifier: Apache-2.0`, `/* Copyright (c) 2026 ... */`) -- **Compiler & linter directives** — `# noqa: E501`, `// @ts-ignore`, `// eslint-disable-next-line`, `# shellcheck disable=SC2086`, `// clippy::too_many_arguments`, `/* istanbul ignore next */` -- **BDD test steps** — `# given`, `# when`, `// then` -- **Structured API docstrings & contract tags** — Docstrings containing `@param`, `@returns`, `Args:`, `Returns:`, `# panics`, or `# safety`, as well as leading contract tags on declarations -- **Non-obvious intent & rationale** — Comments explaining *why* something is done (`// workaround: SDK panics on empty input`, `# because SQLite locks during write`, `// to avoid TOCTOU race`, `Why: 1-based index`) -- **Attribution & references** — `// @author Jane Doe`, `// ref: https://...`, `// adapted from ...` -- **Executable shebangs** — `#!/usr/bin/env python3`, `#!/bin/bash` +## What it spares ---- +Justified comments pass without warnings: -## Supported Languages +- **License and generated headers** — SPDX identifiers, copyrights, generated-file notices +- **Directives** — `# noqa: E501`, `// @ts-ignore`, `// eslint-disable-next-line`, `# shellcheck disable=SC2086`, `// clippy::too_many_arguments` +- **BDD steps** — `# given`, `# when`, `// then` +- **Structured API docs** — docstrings with `@param`, `@returns`, `Args:`, `Returns:`, `# panics`, `# safety` +- **Non-obvious intent** — `// workaround: SDK panics on empty input`, `# avoid TOCTOU race` +- **Rationale** — `Why:` notes, attribution (`// @author`), and references (`// ref: https://…`) +- **Shebang lines** — `#!/usr/bin/env python3` -Tree-sitter AST parsers are compiled directly into the binary across 37 programming languages and formats: +## Supported languages -| Category | Languages | -|---|---| -| **Systems & Native** | Rust (`.rs`), C (`.c`, `.h`), C++ (`.cpp`, `.cc`, `.cxx`, `.hpp`), Zig (`.zig`) | -| **Web & Applications** | TypeScript (`.ts`, `.tsx`), JavaScript (`.js`, `.jsx`, `.mjs`, `.cjs`), Python (`.py`, `.pyi`), Go (`.go`), Java (`.java`), C# (`.cs`), Kotlin (`.kt`), Scala (`.scala`), Swift (`.swift`), Dart (`.dart`), PHP (`.php`), Ruby (`.rb`), Elixir (`.ex`, `.exs`), Svelte (`.svelte`), Elm (`.elm`), Lua (`.lua`), Groovy (`.groovy`, `.gradle`), OCaml (`.ml`, `.mli`), Haskell (`.hs`), R (`.r`, `.rmd`) | -| **Shell & Config** | Bash / Shell (`.sh`, `.bash`, `.zsh`), SQL (`.sql`), JSON (`.json`), YAML (`.yaml`, `.yml`), TOML (`.toml`), HTML (`.html`), CSS (`.css`), Dockerfile (`Dockerfile`), HCL / Terraform (`.tf`, `.hcl`), CUE (`.cue`), Protocol Buffers (`.proto`), Markdown (`.md`) | +Tree-sitter parsers are compiled into the binary — 37 languages and formats: -Files written in unsupported extensions or non-code formats are skipped automatically, allowing standard tool execution to proceed without interruptions. +| Family | Languages | +|---|---| +| Systems | Rust, C, C++, Zig | +| Web & apps | TypeScript (`.ts`, `.tsx`), JavaScript, Python, Go, Java, C#, Kotlin, Scala, Swift, PHP, Ruby, Elixir, Svelte, Elm, Lua, Groovy, OCaml, Haskell, R, Dart | +| Shell & config | Bash/Zsh, SQL, JSON, YAML, TOML, HTML, CSS, Dockerfile, HCL/Terraform, CUE, Protocol Buffers, Markdown | ---- +Unsupported files are skipped, so the hook never blocks unrelated work. -## Usage & Configuration +## Configuration -### Custom Prompt Formatting +### Custom prompt -The `--prompt` command-line option allows teams to override the default notification text delivered to Claude Code. The `{{comments}}` placeholder is replaced with the structured list of flagged comments and citations: +The default warning text is a single message; replace it with `--prompt` and put the report where it goes: ```bash -comment-checker --prompt "Review feedback:\n\n{{comments}}\n\nPlease revise the code." +comment-checker --prompt "Review feedback:\n\n{{comments}}\n\nRevise the code." ``` -You can configure this flag directly in your `.claude/settings.json` file: - ```json { - "hooks": { - "PostToolUse": [ - { - "matcher": "Write|Edit|MultiEdit", - "hooks": [ - { - "type": "command", - "command": "comment-checker --prompt \"Violations detected:\n\n{{comments}}\"" - } - ] - } - ] - } + "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "comment-checker --prompt \"Violations detected:\n\n{{comments}}\"" } ] } ] } } ``` -### Exit Codes +### Exit codes -`comment-checker` returns deterministic status codes suitable for shell scripts and automated editor integrations: +Deterministic, and the reason sessions and scripts can gate on the hook: -| Exit Code | Status | Description | -|---|---|---| -| `0` | **Pass** | No unnecessary comments found, clean payload, or unparseable input (hook never blocks on invalid input) | -| `2` | **Block** | One or more unnecessary comments detected; diagnostic report emitted to stdout | - ---- +| Code | Meaning | +|---|---| +| 0 | Pass — no unnecessary comments found; empty input or unparseable payload also passes | +| 2 | Block — one or more unnecessary comments; report on stdout | ## FAQ -**Q: `command not found: comment-checker` after installation.** -A: Verify that your global package bin directory is included in your shell `PATH` environment variable. For global npm or pnpm installations, you can check active bin paths with `npm bin -g` or `pnpm root -g`. +**Q: `command not found: comment-checker` after installing.** +A: Make sure the package manager's global bin directory is on `PATH`. Check with `pnpm bin -g` (or `npm bin -g`); npm global bins can otherwise land outside the shell path on some setups. -**Q: Why are comments in `Edit` or `MultiEdit` tool calls treated differently than `Write`?** -A: When Claude Code uses `Edit` or `MultiEdit`, only the newly added comments in the diff are evaluated; pre-existing comments in the file are ignored. Furthermore, since code fragments may lack surrounding AST context, restatement detection is disabled on fragments to prevent false positives. +**Q: Does it modify my files?** +A: No. It reads a hook payload over stdin and prints a report; nothing is written to disk. -**Q: Does `comment-checker` modify my source files?** -A: No. `comment-checker` is purely diagnostic. It emits a report to stdout and exits with code 2 to inform the agent of the required correction. +**Q: Does it send my code anywhere?** +A: No network requests at all. The binary is fully offline. -**Q: Does `comment-checker` transmit code over the network?** -A: No. The binary runs entirely locally, processes JSON over stdin, and makes no network requests. +**Q: It started flagging comments in unrelated files.** +A: It reads the hook payload's file path and skips unsupported formats, but if a `matcher` scope is too wide, restrict it in settings — most setups want `Write|Edit|MultiEdit` only. ---- +## Repository layout -## Maintenance & Releases - -Releases are triggered by semantic tags pushed to `main` (such as `v0.1.0`), which automatically executes [`.github/workflows/release.yml`](.github/workflows/release.yml). The pipeline compiles release binaries across all matrix targets, validates cryptographic checksums, and publishes each platform package followed by the root launcher with npm OIDC provenance. - -Detailed release specifications, matrix bindings, and trusted publisher instructions are documented in [docs/plans/2026-08-17-001-feat-npm-distribution-release-plan.md](docs/plans/2026-08-17-001-feat-npm-distribution-release-plan.md). - ---- +| Path | Contains | +|---|---| +| `crates/comment-checker` | The Rust binary: tree-sitter detection, classification rules, report | +| `npm/packages/comment-checker` | The published npm launcher; its README is the [registry product page](npm/packages/comment-checker/README.md) | +| `tests/` + `eval/corpus.json` | Integration tests and the F1 corpus | +| `.github/workflows/` | CI and the release pipeline (publish with npm OIDC provenance) | ## Contributing -Development setup, test execution, and mutation testing guidelines are maintained in [AGENTS.md](AGENTS.md). - ---- +Development setup, verification gates, and the mutation-testing standard live in [AGENTS.md](AGENTS.md). ## License -Distributed under the [Apache-2.0 License](LICENSE). © [System F Software](https://github.com/systemfsoftware) +Apache-2.0. See [LICENSE](LICENSE). \ No newline at end of file diff --git a/npm/packages/comment-checker/README.md b/npm/packages/comment-checker/README.md new file mode 100644 index 0000000..dca77a7 --- /dev/null +++ b/npm/packages/comment-checker/README.md @@ -0,0 +1,40 @@ +# @systemfsoftware/claude-code-comment-checker + +A Claude Code `PostToolUse` hook that flags unnecessary code comments and states the exact reason each one fails — an alternative to flag-everything linters, gated to F1 ≥ 0.85 on a 60-case, 37-language corpus. It never edits your files and never sends code anywhere. + +## Install + +```bash +pnpm add -g @systemfsoftware/claude-code-comment-checker +``` + +Prebuilt native binaries for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64) are selected through `os`/`cpu` constraints. + +## Wire it into Claude Code + +Add the hook to `~/.claude/settings.json` (user) or `.claude/settings.json` (project): + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { "type": "command", "command": "comment-checker" } + ] + } + ] + } +} +``` + +On `Edit` and `MultiEdit` only newly added comments are checked; restatement detection is disabled on edit fragments. +On a clean write the hook prints `[check-comments] Skipping: No unnecessary comments found` and exits 0. +When comments are flagged it prints the report with per-comment reasons and exits 2 — the status code is the contract for automation. + +## Docs + +- Full documentation: comments flagged, comments spared, languages, and `--prompt` configuration — [the project README](https://github.com/systemfsoftware/comment-checker/blob/master/README.md) +- License: [Apache-2.0](https://github.com/systemfsoftware/comment-checker/blob/master/LICENSE) +- Development and contributing: [AGENTS.md](https://github.com/systemfsoftware/comment-checker/blob/master/AGENTS.md) \ No newline at end of file