From 2a6a744cdea848edd232abff5bfeaf5099b38a33 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:02:51 +0000 Subject: [PATCH 1/8] feat: add comment-checker-setup skill with doctor script --- .claude/skills/comment-checker-setup/SKILL.md | 139 +++++++++++++++ .../comment-checker-setup/evals/evals.json | 56 ++++++ .../references/setup-resolution.md | 23 +++ .../comment-checker-setup/scripts/deno.jsonc | 11 ++ .../comment-checker-setup/scripts/deno.lock | 28 +++ .../comment-checker-setup/scripts/doctor.ts | 166 ++++++++++++++++++ 6 files changed, 423 insertions(+) create mode 100644 .claude/skills/comment-checker-setup/SKILL.md create mode 100644 .claude/skills/comment-checker-setup/evals/evals.json create mode 100644 .claude/skills/comment-checker-setup/references/setup-resolution.md create mode 100644 .claude/skills/comment-checker-setup/scripts/deno.jsonc create mode 100644 .claude/skills/comment-checker-setup/scripts/deno.lock create mode 100755 .claude/skills/comment-checker-setup/scripts/doctor.ts diff --git a/.claude/skills/comment-checker-setup/SKILL.md b/.claude/skills/comment-checker-setup/SKILL.md new file mode 100644 index 0000000..3557be0 --- /dev/null +++ b/.claude/skills/comment-checker-setup/SKILL.md @@ -0,0 +1,139 @@ +--- +name: comment-checker-setup +description: Set up or repair a comment-checker PostToolUse hook. Use when comment-checker does not resolve or 'comment-checker did not run' appears on edits, or a flake/direnv/npm install path must be verified. Triggers on: 'comment-checker setup', 'hook not running', 'doctor the comment checker'. Do not use for comment-writing advice or unrelated hook debugging. +--- + +# comment-checker-setup + +Install and verify the comment-checker `PostToolUse` hook so every edit is checked. A hook that cannot resolve its binary checks nothing: it must either find `comment-checker` on PATH or reach it through the direnv bridge, and the whole chain must be proven with the bundled doctor, never by eyeballing a shell. + +## When to Activate + +```yaml +- id: A1 + title: Activate on setup or repair intent + do: activate when the task is installing, wiring, or diagnosing the comment-checker hook, or when 'comment-checker did not run' appears on edits + dont: activate for comment-style feedback on code you are writing, or for generic hook debugging unrelated to comment-checker + check: the request names the hook, the binary, or the 'did not run' symptom +- id: A2 + title: Boundary - do not activate for comment writing advice + do: for advice about which comments to write or remove, note that comment-checker itself (called as the hook) is the authority and stop + dont: apply this skill's setup workflow to comment-content questions + check: the ask is about wiring, not about a specific comment's merits +``` + +## Workflow: provision and verify + +```yaml +- id: W1 + title: Run the doctor first + do: run `./scripts/doctor.ts [project-dir]` from a clean environment (no ambient dev-shell PATH), and let its output drive the fix + dont: skip the doctor and hand-edit PATH or directories on suspicion; a resolution failure is traced, not guessed + check: the doctor exits 0, or each broken check carries a fix hint you applied +- id: W2 + title: Resolve binary on PATH first + do: ensure `comment-checker` resolves on PATH (npm global install, or a dev shell that provides it); `command -v comment-checker` from a clean shell must print a path + dont: rely on a dev shell you are not provably inside; hook subprocesses do not inherit your interactive shell's direnv state + check: `env -i PATH=/usr/bin:/bin sh -c 'command -v comment-checker'` finds it, or the direnv bridge covers the gap +- id: W3 + title: Wire the direnv bridge when the repo is flake-based + do: when the project has a flake.nix that provides the binary, add `.envrc` containing `use flake` and run `direnv allow`; the hook falls back to `direnv exec` when PATH misses + dont: stop at `direnv allow` -- a blocked .envrc loads nothing, so verify with `direnv exec . command -v comment-checker` + check: the doctor's direnv bridge check reports [ok] +- id: W4 + title: Prove the exit-code contract + do: feed a restating-comment payload and a clean payload to the binary and assert exit 2 and exit 0 respectively (the doctor does this) + dont: accept 'the binary runs' as 'the hook works' -- presence is not the contract + check: the doctor's exit-code contract check reports [ok] +- id: W5 + title: Name the real provider in the final report + do: state which provider the project uses (npm global, direnv+flake, or nix develop) and that the doctor verified it end-to-end + dont: leave the resolution mechanism implicit or report 'verified' without the doctor run + check: the report names the provider and cites the doctor exit code +``` + +```bash +# exact commands for W2-W4 (run from the project root) +env -i PATH=/usr/bin:/bin sh -c 'command -v comment-checker' # W2 path probe +printf 'use flake\n' > .envrc && direnv allow # W3 wiring +direnv exec . command -v comment-checker # W3 verify +``` + +## Common failures + +```yaml +- id: F1 + title: Ambiguous PATH shadowing + do: when the doctor's identity check fails, treat 'a different program named comment-checker' on PATH as the cause and remove/reorder it + dont: assume the shadowing binary is the real checker just because it answers + check: the doctor's binary identity check reports the expected `claude-code-comment-checker ` line +- id: F2 + title: Blocked .envrc + do: when the direnv bridge fails with 'is blocked', run `direnv allow` and re-run the doctor + dont: edit .envrc contents to make the error go away + check: `direnv exec . command -v comment-checker` resolves +- id: F3 + title: Hook file missing + do: when the doctor reports no hook wiring, install the plugin or add the PostToolUse entry to `.claude/settings.json` + dont: ship a binary with no hook attached and call the setup done + check: the doctor's hook wiring check reports [ok] +``` + +## Verification + +```yaml +- id: V1 + title: Doctor is the gate + do: run `./scripts/doctor.ts .` and require exit 0 before claiming the hook works + dont: claim 'the comment checker is set up' from a PATH or directory listing alone + check: the doctor prints 'all checks passed' and exits 0 +- id: V2 + title: Doctor scripts stay green + do: after any edit to `scripts/doctor.ts`, run `deno check doctor.ts && deno lint doctor.ts` (in `scripts/`) + dont: ship a doctor that does not typecheck or lint clean + check: both `deno check` and `deno lint` exit 0 in the scripts directory +``` + +## Scripts + +| Script | Purpose | When to run | +|--------|---------|-------------| +| `scripts/doctor.ts` | Probes resolution, identity, contract, hook wiring, direnv bridge, flake dev shell; exits 0 all-pass, 1 broken | First, and after every fix | + +## References (load on demand) + +| Reference | When to load (intent) | Hash | +|-----------|--------------|------| +| `references/setup-resolution.md` | Resolve which provider path applies, or when PATH/direnv/nix ordering matters | `5bed20` | + +## Integration + +```yaml +- id: I1 + title: Coordinate with the agent-harness design + do: when the harness that runs the hook needs a path or env change, design it together with this skill's wiring (one change, not two) + dont: treat the hook wiring as isolated from how the harness spawns subprocesses + check: the resolution falls out of the harness's own env, not a workaround +``` + +## Critical Rules at Document End + +```yaml +- id: END1 + title: A hook that does not resolve checks nothing + do: prove resolution and the exit-code contract with the doctor from a clean environment before trusting the hook + dont: trust a shell you happened to be in, or a direnv state you did not verify + harm: an unverified hook silently checks zero edits, and the failure is invisible until bad comments ship + check: `./scripts/doctor.ts .` exits 0 from a clean env +- id: END2 + title: Never edit the body to chase a failing eval + do: when a check fails, attribute the cause (resolution, identity, wiring) from the doctor's output and fix that, not the skill text + dont: weaken the skill's rules because a fixture fails + harm: editing the skill on an unattributed failure ships the drift + check: every body edit traces to a diagnosed cause, not to a failing run +``` + +## Do not use for + +- Writing or judging code comments in your own work — invoke the checker as the hook does. +- Debugging hook subprocess env unrelated to comment-checker (PATH drop, plugin host) — that is the agent-harness-design skill's surface; cross-reference by capability, never by name. \ No newline at end of file diff --git a/.claude/skills/comment-checker-setup/evals/evals.json b/.claude/skills/comment-checker-setup/evals/evals.json new file mode 100644 index 0000000..7301145 --- /dev/null +++ b/.claude/skills/comment-checker-setup/evals/evals.json @@ -0,0 +1,56 @@ +[ + { + "id": "resolve-path", + "prompt": "comment-checker does not resolve when the hook runs, though it works in my shell. What is the first step the fix workflow prescribes?", + "needles": [ + "W1", + "doctor" + ] + }, + { + "id": "blocked-envrc", + "prompt": "The hook reports 'comment-checker did not run' and direnv says the .envrc is blocked. After allowing it, what does the skill's common-failure check run to verify?", + "needles": [ + "F2", + "direnv exec . command -v comment-checker" + ] + }, + { + "id": "identity-shadow", + "prompt": "A binary named comment-checker responds on PATH, but examining it reveals it is not the checker this skill assumes. What exact output contract does the skill's identity check require?", + "needles": [ + "claude-code-comment-checker", + "semver" + ] + }, + { + "id": "doctor-run", + "prompt": "You wired the hook but want proof it actually checks edits, the way the skill's verification section demands. What artifact and what exit does the gate require?", + "needles": [ + "doctor.ts", + "exit 2", + "V1" + ] + }, + { + "id": "flake-direnv-wiring", + "prompt": "A flake.nix provides the checker. The skill's W3 workflow names two exact steps that complete the direnv wiring. What are they?", + "needles": [ + "W3", + "direnv allow", + "comment-checker on PATH" + ] + }, + { + "id": "negative-trigger-comment-advice", + "prompt": "Is this comment restating the code? // increments counter next to counter += 1", + "should_not_trigger": true, + "needles": [] + }, + { + "id": "negative-trigger-other-hook", + "prompt": "My lint hook subprocess drops PATH but is unrelated to comment-checker. What do I do?", + "should_not_trigger": true, + "needles": [] + } +] \ No newline at end of file diff --git a/.claude/skills/comment-checker-setup/references/setup-resolution.md b/.claude/skills/comment-checker-setup/references/setup-resolution.md new file mode 100644 index 0000000..6b2b5b7 --- /dev/null +++ b/.claude/skills/comment-checker-setup/references/setup-resolution.md @@ -0,0 +1,23 @@ +# Setup resolution: which provider path applies + +Decide how the project provisions `comment-checker`, then verify with the doctor. + +## The provider paths + +| Provider | When it applies | Resolves when | Common failure | +|----------|-----------------|---------------|----------------| +| npm global | Any project; no flake needed | package manager bin dir is on PATH | global bin dir outside PATH (`pnpm bin -g` / `npm bin -g`) | +| direnv + flake | Project has `flake.nix` providing the checker | `.envrc` = `use flake` and `direnv allow` ran | `.envrc` blocked; `direnv allow` never run | +| `nix develop` | Ad-hoc shell entry | the dev shell is active | someone trusts an ambient PATH that is not the shell's | + +The hook resolves PATH first, then `direnv exec "$CLAUDE_PROJECT_DIR"`. When PATH misses and no `.envrc` exists, nothing checks the edit — the hook exits 1 with the "did not run" error. + +## Path-resolution traps + +1. **Ambient direnv state contaminates probes.** A shell that already loaded a dev shell makes `command -v` succeed even when the hook's clean subprocess would miss. Probe with `env -i PATH=/usr/bin:/bin sh -c 'command -v comment-checker'`. +2. **A shadowing binary passes `command -v` but not identity.** The real binary prints `claude-code-comment-checker ` to `--version`. Any other output means a different program owns the name on PATH. +3. **A blocked `.envrc` loads nothing.** `direnv allow` is per-clone state; the doctor's direnv bridge check distinguishes "not installed" from "installed but blocked". + +## The one invariant + +A hook's resolution must be proven from a clean environment, the same one the hook subprocess runs in — never from the interactive shell you happen to be in. \ No newline at end of file diff --git a/.claude/skills/comment-checker-setup/scripts/deno.jsonc b/.claude/skills/comment-checker-setup/scripts/deno.jsonc new file mode 100644 index 0000000..b89519a --- /dev/null +++ b/.claude/skills/comment-checker-setup/scripts/deno.jsonc @@ -0,0 +1,11 @@ +{ + "lock": "./deno.lock", + "tasks": { + "check": "deno check doctor.ts", + "lint": "deno lint doctor.ts" + }, + "imports": { + "@std/fs": "jsr:@std/fs@1.0.19", + "@std/path": "jsr:@std/path@1.1.6" + } +} \ No newline at end of file diff --git a/.claude/skills/comment-checker-setup/scripts/deno.lock b/.claude/skills/comment-checker-setup/scripts/deno.lock new file mode 100644 index 0000000..4e9aee3 --- /dev/null +++ b/.claude/skills/comment-checker-setup/scripts/deno.lock @@ -0,0 +1,28 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/fs@1.0.19": "1.0.19", + "jsr:@std/internal@^1.0.14": "1.0.14", + "jsr:@std/path@1.1.6": "1.1.6" + }, + "jsr": { + "@std/fs@1.0.19": { + "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06" + }, + "@std/internal@1.0.14": { + "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7" + }, + "@std/path@1.1.6": { + "integrity": "c68485c2a4dfbb5ae3cc74fae4e8c4e5d874cf8a8ed12927917235c758b46cbe", + "dependencies": [ + "jsr:@std/internal" + ] + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/fs@1.0.19", + "jsr:@std/path@1.1.6" + ] + } +} diff --git a/.claude/skills/comment-checker-setup/scripts/doctor.ts b/.claude/skills/comment-checker-setup/scripts/doctor.ts new file mode 100755 index 0000000..4bad778 --- /dev/null +++ b/.claude/skills/comment-checker-setup/scripts/doctor.ts @@ -0,0 +1,166 @@ +#!/usr/bin/env -S deno run --allow-read=.envrc,flake.nix,.claude,hooks --allow-run=comment-checker,direnv,deno,nix,git,pnpm,npm,cargo,sh --allow-env=PATH,HOME,CLAUDE_PROJECT_DIR,XDG_CACHE_HOME + +import { exists } from '@std/fs/exists' +import { join, resolve } from '@std/path' + +const projectDir = resolve(Deno.args[0] ?? '.') +let failed = 0 + +function report(ok: boolean, name: string, detail: string, hint: string): void { + const mark = ok ? '[ok]' : '[broken]' + console.log(`${mark} ${name}: ${detail}`) + if (!ok) { + console.log(` fix: ${hint}`) + failed += 1 + } +} + +async function safeExists(p: string): Promise { + try { + return await exists(p) + } catch { + return false + } +} + +async function run( + cmd: string, + args: string[], + input?: string, +): Promise<{ code: number; stdout: string; stderr: string } | undefined> { + try { + const p = new Deno.Command(cmd, { + args, + stdin: input === undefined ? 'null' : 'piped', + stdout: 'piped', + stderr: 'piped', + }) + const child = p.spawn() + if (input !== undefined) { + const w = child.stdin.getWriter() + await w.write(new TextEncoder().encode(input)) + await w.close() + } + const { code, stdout, stderr } = await child.output() + return { code, stdout: new TextDecoder().decode(stdout), stderr: new TextDecoder().decode(stderr) } + } catch (e) { + if (e instanceof Deno.errors.NotFound) return undefined + throw e + } +} + +async function resolveOnPath(name: string): Promise { + const r = await run('sh', ['-lc', `command -v ${name}`]) + const line = r !== undefined && r.code === 0 ? r.stdout.trim() : '' + return line.length > 0 && line !== name ? line : undefined +} + +const envrcPath = join(projectDir, '.envrc') +const flakePath = join(projectDir, 'flake.nix') +const hooksPath = join(projectDir, '.claude', 'hooks', 'hooks.json') +const claudeSettingsPath = join(projectDir, '.claude', 'settings.json') + +const culpritPayload = JSON.stringify({ + tool_name: 'Write', + tool_input: { file_path: 'demo.ts', content: '// increment counter\nlet counter = 0;\ncounter += 1;\n' }, +}) +const cleanPayload = JSON.stringify({ + tool_name: 'Write', + tool_input: { file_path: 'demo.ts', content: '// SPDX-License-Identifier: Apache-2.0\nexport const x = 1;\n' }, +}) + +const versionExpected = /^claude-code-comment-checker\s+\d+\.\d+\.\d+\s*$/m + +const resolved = await resolveOnPath('comment-checker') +const onPath = resolved !== undefined +report( + onPath, + 'comment-checker on PATH', + onPath ? resolved! : 'no comment-checker executable found on PATH', + 'Install it (npm global) or enter a dev shell that provides it; see references/setup-resolution.md in this skill', +) + +let identityOk = false +let versionLine = '' +if (onPath) { + const v = await run('comment-checker', ['--version']) + if (v !== undefined) { + versionLine = v.stdout.trim().split('\n')[0] ?? '' + identityOk = versionExpected.test(versionLine) + } +} +report( + identityOk, + 'binary identity', + identityOk ? versionLine : `unexpected version output: ${versionLine || '(empty)'}`, + 'A different program named comment-checker is shadowing the real one on PATH; remove or reorder it, then re-run', +) + +let blocks = false +let spares = false +const contractChecked = identityOk || onPath +if (identityOk || onPath) { + const block = await run('comment-checker', [], culpritPayload) + const spare = await run('comment-checker', [], cleanPayload) + blocks = block !== undefined && block.code === 2 && /unnecessary/i.test(block.stderr) + spares = spare !== undefined && spare.code === 0 +} +const contractOk = blocks && spares +report( + contractOk, + 'exit-code contract', + contractChecked + ? contractOk + ? 'blocks restating comments (exit 2) and spares clean input (exit 0)' + : `block exits ${blocks ? 'right' : 'wrong'}, spare ${spares ? 'right' : 'wrong'}` + : 'no binary to exercise', + 'The resolved binary is not behaving like comment-checker; reinstall it or fix PATH ordering', +) + +const hookPresent = + (await safeExists(join(projectDir, 'hooks', 'hooks.json'))) || + (await safeExists(hooksPath)) || + (await safeExists(claudeSettingsPath)) +if (hookPresent) { + const denoV = await run('deno', ['--version']) + const denoOk = denoV !== undefined && denoV.code === 0 + report(true, 'hook wiring present', 'PostToolUse hook file found (hooks/hooks.json, .claude/hooks, or .claude/settings.json)', '') + report(denoOk, 'deno on PATH', denoOk ? 'deno resolves' : 'deno not found', 'Install Deno; the hook bridge runs via `deno run`') +} else { + report(false, 'hook wiring present', 'no PostToolUse hook file found in this project', 'Install the plugin or add the hook entry to .claude/settings.json; see the plugin README') +} + +const hasEnvrc = await safeExists(envrcPath) +if (hasEnvrc) { + const dv = await run('direnv', ['exec', projectDir, 'sh', '-c', 'command -v comment-checker']) + const direnvOk = dv !== undefined && dv.code === 0 && dv.stdout.trim().length > 0 + report( + direnvOk, + 'direnv bridge', + direnvOk ? `direnv exec resolves: ${dv!.stdout.trim()}` : `direnv exec failed (exit ${dv?.code ?? 'n/a'}): ${(dv?.stderr ?? '').trim().split('\n')[0] ?? 'direnv not installed'}`, + 'Run `direnv allow` in the project (a blocked .envrc loads nothing), then re-run', + ) +} else { + report(false, 'direnv bridge', 'no .envrc found', 'Add `.envrc` containing `use flake` when the project is flake-based, or install the checker globally so it resolves without direnv') +} + +const hasFlake = await safeExists(flakePath) +if (hasFlake) { + const nv = await run('nix', ['develop', '--command', 'sh', '-lc', 'command -v comment-checker']) + const nixOk = nv !== undefined && nv.code === 0 && nv.stdout.trim().length > 0 + report( + nixOk, + 'flake dev shell', + nixOk ? `nix develop resolves: ${nv!.stdout.trim()}` : `nix develop failed (exit ${nv?.code ?? 'n/a'}): ${(nv?.stderr ?? '').trim().split('\n').find((l) => l.includes('error')) ?? 'nix not installed or flake build failed'}`, + 'Build or enter the dev shell once (`nix develop`), or rely on direnv; see references/setup-resolution.md', + ) +} else { + report(true, 'flake dev shell', 'no flake.nix (npm global install is the path)', '') +} + +if (failed === 0) { + console.log('comment-checker doctor: all checks passed') +} else { + console.log(`comment-checker doctor: ${failed} check(s) broken`) +} +Deno.exit(failed === 0 ? 0 : 1) \ No newline at end of file From 97412b739dfe680c244b81a9162e257a17b5cdb1 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:05:30 +0000 Subject: [PATCH 2/8] docs: point the npm README at the setup skill --- npm/packages/comment-checker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/packages/comment-checker/README.md b/npm/packages/comment-checker/README.md index 6206492..93f5984 100644 --- a/npm/packages/comment-checker/README.md +++ b/npm/packages/comment-checker/README.md @@ -35,6 +35,6 @@ When comments are flagged it writes the report, with per-comment reasons, to std ## Docs -- Full documentation: comments flagged, comments spared, languages, plus `--prompt` and `--strip` — [the project README](https://github.com/systemfsoftware/comment-checker/blob/master/README.md) +- Setup and repair: the `comment-checker-setup` skill (`.claude/skills/comment-checker-setup/`) — run its `scripts/doctor.ts` to verify resolution, identity, the exit-code contract, hook wiring, and the direnv bridge end-to-end - 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 From 0f62ea0e9bbed1d7ce3521da432f2ff62e38f1dc Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:22:21 +0000 Subject: [PATCH 3/8] fix(doctor): anchor read grants, PATH-first direnv, content-aware hook check --- .../comment-checker-setup/evals/evals.json | 2 +- .../comment-checker-setup/scripts/deno.jsonc | 1 + .../comment-checker-setup/scripts/deno.lock | 25 +++- .../comment-checker-setup/scripts/doctor.ts | 112 +++++++++++++----- 4 files changed, 104 insertions(+), 36 deletions(-) diff --git a/.claude/skills/comment-checker-setup/evals/evals.json b/.claude/skills/comment-checker-setup/evals/evals.json index 7301145..4663672 100644 --- a/.claude/skills/comment-checker-setup/evals/evals.json +++ b/.claude/skills/comment-checker-setup/evals/evals.json @@ -38,7 +38,7 @@ "needles": [ "W3", "direnv allow", - "comment-checker on PATH" + "direnv exec . command -v comment-checker" ] }, { diff --git a/.claude/skills/comment-checker-setup/scripts/deno.jsonc b/.claude/skills/comment-checker-setup/scripts/deno.jsonc index b89519a..bfad83b 100644 --- a/.claude/skills/comment-checker-setup/scripts/deno.jsonc +++ b/.claude/skills/comment-checker-setup/scripts/deno.jsonc @@ -5,6 +5,7 @@ "lint": "deno lint doctor.ts" }, "imports": { + "@std/cli": "jsr:@std/cli@^1.0.32", "@std/fs": "jsr:@std/fs@1.0.19", "@std/path": "jsr:@std/path@1.1.6" } diff --git a/.claude/skills/comment-checker-setup/scripts/deno.lock b/.claude/skills/comment-checker-setup/scripts/deno.lock index 4e9aee3..c35cf99 100644 --- a/.claude/skills/comment-checker-setup/scripts/deno.lock +++ b/.claude/skills/comment-checker-setup/scripts/deno.lock @@ -1,13 +1,31 @@ { "version": "5", "specifiers": { + "jsr:@std/cli@^1.0.32": "1.0.32", + "jsr:@std/fmt@^1.0.10": "1.0.10", "jsr:@std/fs@1.0.19": "1.0.19", "jsr:@std/internal@^1.0.14": "1.0.14", - "jsr:@std/path@1.1.6": "1.1.6" + "jsr:@std/internal@^1.0.9": "1.0.14", + "jsr:@std/path@1.1.6": "1.1.6", + "jsr:@std/path@^1.1.1": "1.1.6" }, "jsr": { + "@std/cli@1.0.32": { + "integrity": "188b3a100d6202d64e3f5bd3d799c7fa4f6d77f92cc65eb7f641c1fa0aa92a66", + "dependencies": [ + "jsr:@std/fmt", + "jsr:@std/internal@^1.0.14" + ] + }, + "@std/fmt@1.0.10": { + "integrity": "90dfba288802ac6de82fb31d0917eb9e4450b9925b954d5e51fc29ac07419db5" + }, "@std/fs@1.0.19": { - "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06" + "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", + "dependencies": [ + "jsr:@std/internal@^1.0.9", + "jsr:@std/path@^1.1.1" + ] }, "@std/internal@1.0.14": { "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7" @@ -15,12 +33,13 @@ "@std/path@1.1.6": { "integrity": "c68485c2a4dfbb5ae3cc74fae4e8c4e5d874cf8a8ed12927917235c758b46cbe", "dependencies": [ - "jsr:@std/internal" + "jsr:@std/internal@^1.0.14" ] } }, "workspace": { "dependencies": [ + "jsr:@std/cli@^1.0.32", "jsr:@std/fs@1.0.19", "jsr:@std/path@1.1.6" ] diff --git a/.claude/skills/comment-checker-setup/scripts/doctor.ts b/.claude/skills/comment-checker-setup/scripts/doctor.ts index 4bad778..645d06e 100755 --- a/.claude/skills/comment-checker-setup/scripts/doctor.ts +++ b/.claude/skills/comment-checker-setup/scripts/doctor.ts @@ -1,15 +1,17 @@ -#!/usr/bin/env -S deno run --allow-read=.envrc,flake.nix,.claude,hooks --allow-run=comment-checker,direnv,deno,nix,git,pnpm,npm,cargo,sh --allow-env=PATH,HOME,CLAUDE_PROJECT_DIR,XDG_CACHE_HOME +#!/usr/bin/env -S deno run --allow-read --allow-run=comment-checker,direnv,deno,nix,git,pnpm,npm,cargo,sh --allow-env=PATH,HOME,CLAUDE_PROJECT_DIR,XDG_CACHE_HOME import { exists } from '@std/fs/exists' import { join, resolve } from '@std/path' +import { parseArgs } from '@std/cli/parse-args' -const projectDir = resolve(Deno.args[0] ?? '.') +const args = parseArgs(Deno.args) +const projectDir = resolve(String(args._[0] ?? '.')) let failed = 0 -function report(ok: boolean, name: string, detail: string, hint: string): void { +function report(ok: boolean, name: string, detail: string, hint: string, counted = true): void { const mark = ok ? '[ok]' : '[broken]' console.log(`${mark} ${name}: ${detail}`) - if (!ok) { + if (!ok && counted) { console.log(` fix: ${hint}`) failed += 1 } @@ -23,6 +25,19 @@ async function safeExists(p: string): Promise { } } +async function readFileSafe(p: string): Promise { + try { + return await Deno.readTextFile(p) + } catch { + return undefined + } +} + +async function direnvNeeded(): Promise { + const r = await run('sh', ['-c', 'command -v comment-checker']) + return r === undefined || r.code !== 0 || r.stdout.trim().length === 0 +} + async function run( cmd: string, args: string[], @@ -50,14 +65,32 @@ async function run( } async function resolveOnPath(name: string): Promise { - const r = await run('sh', ['-lc', `command -v ${name}`]) + const r = await run('sh', ['-c', `command -v ${name}`]) const line = r !== undefined && r.code === 0 ? r.stdout.trim() : '' return line.length > 0 && line !== name ? line : undefined } +function hooksWireCommentChecker(text: string): boolean { + try { + const cfg = JSON.parse(text) + const post = cfg?.hooks?.PostToolUse + if (!Array.isArray(post)) return false + return post.some((grp: { hooks?: Array<{ command?: unknown }> }) => + Array.isArray(grp?.hooks) && + grp.hooks.some((h: { command?: unknown }) => + typeof h?.command === 'string' && + /comment-checker/.test(h.command) + ) + ) + } catch { + return false + } +} + const envrcPath = join(projectDir, '.envrc') const flakePath = join(projectDir, 'flake.nix') -const hooksPath = join(projectDir, '.claude', 'hooks', 'hooks.json') +const pluginHooksPath = join(projectDir, 'hooks', 'hooks.json') +const claudeHooksPath = join(projectDir, '.claude', 'hooks', 'hooks.json') const claudeSettingsPath = join(projectDir, '.claude', 'settings.json') const culpritPayload = JSON.stringify({ @@ -98,8 +131,8 @@ report( let blocks = false let spares = false -const contractChecked = identityOk || onPath -if (identityOk || onPath) { +const contractChecked = onPath +if (onPath) { const block = await run('comment-checker', [], culpritPayload) const spare = await run('comment-checker', [], cleanPayload) blocks = block !== undefined && block.code === 2 && /unnecessary/i.test(block.stderr) @@ -107,46 +140,61 @@ if (identityOk || onPath) { } const contractOk = blocks && spares report( - contractOk, + contractChecked ? contractOk : true, 'exit-code contract', contractChecked ? contractOk ? 'blocks restating comments (exit 2) and spares clean input (exit 0)' : `block exits ${blocks ? 'right' : 'wrong'}, spare ${spares ? 'right' : 'wrong'}` - : 'no binary to exercise', + : 'no binary to exercise - skipped', 'The resolved binary is not behaving like comment-checker; reinstall it or fix PATH ordering', ) -const hookPresent = - (await safeExists(join(projectDir, 'hooks', 'hooks.json'))) || - (await safeExists(hooksPath)) || - (await safeExists(claudeSettingsPath)) -if (hookPresent) { - const denoV = await run('deno', ['--version']) - const denoOk = denoV !== undefined && denoV.code === 0 - report(true, 'hook wiring present', 'PostToolUse hook file found (hooks/hooks.json, .claude/hooks, or .claude/settings.json)', '') - report(denoOk, 'deno on PATH', denoOk ? 'deno resolves' : 'deno not found', 'Install Deno; the hook bridge runs via `deno run`') -} else { - report(false, 'hook wiring present', 'no PostToolUse hook file found in this project', 'Install the plugin or add the hook entry to .claude/settings.json; see the plugin README') +for (const [label, path] of [ + ['plugin hooks/hooks.json', pluginHooksPath], + ['.claude/hooks/hooks.json', claudeHooksPath], + ['.claude/settings.json', claudeSettingsPath], +] as const) { + const text = await readFileSafe(path) + if (text !== undefined && hooksWireCommentChecker(text)) { + report(true, 'hook wiring present', `PostToolUse hook wired in ${label}`, '') + break + } + if (text !== undefined) { + report(false, 'hook wiring present', `${label} exists but has no comment-checker PostToolUse entry`, 'Add the comment-checker hook entry to .claude/settings.json; see the plugin README') + break + } +} +const hookFound = + await safeExists(pluginHooksPath) || + await safeExists(claudeHooksPath) || + await safeExists(claudeSettingsPath) +if (!hookFound) { + report(false, 'hook wiring present', 'no hook file found (hooks/hooks.json, .claude/hooks, or .claude/settings.json)', 'Install the plugin or add the hook entry to .claude/settings.json; see the plugin README') } const hasEnvrc = await safeExists(envrcPath) -if (hasEnvrc) { - const dv = await run('direnv', ['exec', projectDir, 'sh', '-c', 'command -v comment-checker']) - const direnvOk = dv !== undefined && dv.code === 0 && dv.stdout.trim().length > 0 - report( - direnvOk, - 'direnv bridge', - direnvOk ? `direnv exec resolves: ${dv!.stdout.trim()}` : `direnv exec failed (exit ${dv?.code ?? 'n/a'}): ${(dv?.stderr ?? '').trim().split('\n')[0] ?? 'direnv not installed'}`, - 'Run `direnv allow` in the project (a blocked .envrc loads nothing), then re-run', - ) +const needsDirenv = await direnvNeeded() +if (needsDirenv) { + if (hasEnvrc) { + const dv = await run('direnv', ['exec', projectDir, 'sh', '-c', 'command -v comment-checker']) + const direnvOk = dv !== undefined && dv.code === 0 && dv.stdout.trim().length > 0 + report( + direnvOk, + 'direnv bridge', + direnvOk ? `direnv exec resolves: ${dv!.stdout.trim()}` : `direnv exec failed (exit ${dv?.code ?? 'n/a'}): ${(dv?.stderr ?? '').trim().split('\n')[0] ?? 'direnv not installed'}`, + 'Run `direnv allow` in the project (a blocked .envrc loads nothing), then re-run', + ) + } else { + report(false, 'direnv bridge', 'no .envrc found and comment-checker is not on PATH', 'Add `.envrc` containing `use flake` when the project is flake-based, or install the checker globally so it resolves without direnv') + } } else { - report(false, 'direnv bridge', 'no .envrc found', 'Add `.envrc` containing `use flake` when the project is flake-based, or install the checker globally so it resolves without direnv') + report(true, 'direnv bridge', 'not needed (comment-checker resolves on PATH)', '') } const hasFlake = await safeExists(flakePath) if (hasFlake) { - const nv = await run('nix', ['develop', '--command', 'sh', '-lc', 'command -v comment-checker']) + const nv = await run('nix', ['develop', '--command', 'sh', '-c', 'command -v comment-checker']) const nixOk = nv !== undefined && nv.code === 0 && nv.stdout.trim().length > 0 report( nixOk, From a7ac854d5ca3f86c04665d2615676c5ec29febe5 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:23:27 +0000 Subject: [PATCH 4/8] docs: record why a doctor must be proven against fixtures on both sides --- ...ostic-hook-needs-fixtures-on-both-sides.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/solutions/integration-issues/diagnostic-hook-needs-fixtures-on-both-sides.md diff --git a/docs/solutions/integration-issues/diagnostic-hook-needs-fixtures-on-both-sides.md b/docs/solutions/integration-issues/diagnostic-hook-needs-fixtures-on-both-sides.md new file mode 100644 index 0000000..775d180 --- /dev/null +++ b/docs/solutions/integration-issues/diagnostic-hook-needs-fixtures-on-both-sides.md @@ -0,0 +1,70 @@ +--- +title: "Diagnostic hooks need fixtures on both sides - a doctor must be run from a foreign cwd and against a working provider before it ships" +date: 2026-08-30 +category: integration-issues +module: comment-checker +problem_type: integration_issue +component: dev-tooling +symptoms: + - "a comment-checker doctor reported 4 check(s) broken on a fully working npm-global setup" + - "running the doctor from an unrelated cwd fabricated false negatives for hooks and direnv" + - "the hook-wiring check passed on a settings.json with no comment-checker entry" +root_cause: "diagnostic probes assumed the ambient cwd and the only provider path, so they never saw the architectures that would break them" +resolution_type: code_fix +severity: medium +tags: [doctor, diagnostics, comment-checker, fixtures, provisioning] +--- + +# Diagnostic hooks need fixtures on both sides + +## Problem + +A setup-doctor for the comment-checker hook returned `4 check(s) broken` on a +working npm-global install, and returned `no hook file found` on a project +that had one. The doctor's verdicts disagreed with reality in both directions: +false-broken on healthy setups, and a false-pass when a `settings.json` +existed but had no comment-checker entry. + +## Root cause + +Three probe defects, each a different assumption: + +1. **Path anchoring.** The doctor read `.envrc`, `.claude`, and `hooks` from + its own cwd, not from the project directory it was asked to check. Run from + an unrelated cwd, every probe missed its target and reported broken. +2. **Provider myopia.** The doctor demanded a `.envrc` even when + `comment-checker` already resolved on PATH. The hook's real resolution is + PATH first, direnv only as fallback; a setup with the binary on PATH needs + no `.envrc`, and demanding one is a false-broken. +3. **Presence-blind wiring.** The hook-wiring check accepted any existing + `settings.json` as wired, without parsing whether a `PostToolUse` entry + actually referenced `comment-checker`. A file with no entry passed. + +## Solution + +- Anchor every probe on the resolved project directory, and grant read access + to that directory absolutely (not to relative names that resolve against the + doctor's own cwd). +- Model the hook's actual resolution matrix: PATH first, then direnv. When the + binary resolves on PATH, report the direnv bridge as "not needed". +- Parse candidate hook files and require a `PostToolUse` entry whose command + references `comment-checker` before reporting the wiring as present. +- Prove each verdict with fixtures on both sides of the boundary: a working + provider that must exit 0, and a broken provider that must exit 1. + +## Why This Works + +A doctor is a decision procedure over reality. It is only trustworthy when its +positive and negative verdicts are both exercised against real shapes: a +healthy setup that must pass, and each broken shape that must fail. A doctor +tested only against the author's own working setup cannot see the assumptions +that break elsewhere. + +## Prevention + +- Any new diagnostic probe ships with a fixture on both sides of its verdict: + one that must pass and one that must fail, run from a foreign cwd. +- Never grant read access by a relative name that resolves against the + doctor's own cwd; resolve the target directory first. +- A presence check must parse, not merely exist: a hook file with no + comment-checker entry is not "wired". \ No newline at end of file From a0aff087ee93ca6c5965fb96b504302dd70f4516 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:41:25 +0000 Subject: [PATCH 5/8] docs(npm): make registry README standalone with absolute links only --- npm/packages/comment-checker/README.md | 110 +++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/npm/packages/comment-checker/README.md b/npm/packages/comment-checker/README.md index 93f5984..32482fb 100644 --- a/npm/packages/comment-checker/README.md +++ b/npm/packages/comment-checker/README.md @@ -1,18 +1,25 @@ # @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. Without `--strip` it never edits your files. It never sends code anywhere. +A Claude Code `PostToolUse` hook that flags unnecessary code comments and states the exact reason each one fails. A tree-sitter classifier over 37 languages, gated to F1 ≥ 0.85 on a 60-case corpus, it spares public API docs, directives, and non-obvious intent instead of flagging every comment. Without `--strip` it never edits your files, and it never sends code anywhere. It exits deterministically so the hook can gate automation. ## Install +One command, for any npm-compatible manager (npm, pnpm, yarn, bun): + ```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. +Prebuilt native binaries for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64) are selected through `os`/`cpu` constraints, so `--ignore-scripts` environments work. + +| Method | Command | +|---|---| +| npm/pnpm/yarn/bun | `pnpm install -g @systemfsoftware/claude-code-comment-checker` | +| Cargo (from source) | `cargo install --git https://github.com/systemfsoftware/comment-checker --package claude-code-comment-checker` | ## Wire it into Claude Code -Add the hook to `~/.claude/settings.json` (user) or `.claude/settings.json` (project): +Add the hook to user (`~/.claude/settings.json`) or project (`.claude/settings.json`) configuration: ```json { @@ -29,12 +36,99 @@ Add the hook to `~/.claude/settings.json` (user) or `.claude/settings.json` (pro } ``` -On `Edit` and `MultiEdit` only newly added comments are checked; restatement detection is disabled on edit fragments. -On a clean write the hook writes `[check-comments] Skipping: No unnecessary comments found` to stdout and exits 0. -When comments are flagged it writes the report, with per-comment reasons, to stderr and exits 2 — stderr because that is the stream a host forwards to the model on exit 2, and the status code is the contract for automation. +The hook's resolution chain is PATH first, then `direnv exec`: + +- When `comment-checker` is on PATH, the hook runs it directly. Install globally and the package manager's bin directory must be on PATH (`pnpm bin -g` or `npm bin -g`). +- When the binary is missing on PATH, the hook falls back to `direnv exec "$CLAUDE_PROJECT_DIR"`. Projects with a `flake.nix` that provides the checker (wrapped in bubblewrap) need a `.envrc` containing `use flake` and a one-time `direnv allow`. +- When neither arm resolves, the hook reports that it did not run — nothing was checked. Run the [setup and repair skill](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/SKILL.md) to fix it. + +On `Edit` and `MultiEdit`, only the comments *added* by the edit are checked; pre-existing comments are left alone, and restatement detection is disabled on edit fragments to avoid false positives. + +## See it in action + +Pipe a `Write` payload to the binary. The report goes to stderr, so `2>&1` keeps it when you redirect: + +```bash +$ echo '{"tool_name":"Write","tool_input":{"file_path":"demo.ts","content":"// increment counter\nlet counter = 0;\ncounter += 1;\n"}}' | comment-checker 2>&1 +An automated reviewer flagged 1 comment(s) in demo.ts as unnecessary. + +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 1 — // increment counter — restates what the code already says (shares counter) + +Action: delete the flagged comments. If the code is unclear without +one, make the code self-explanatory instead — better names, extraction, +a clearer type — and do not re-add the comment. +``` + +The exit status is the contract: this write exited `2`. A clean write prints a skip note and exits `0`: + +```bash +$ echo '{"tool_name":"Write","tool_input":{"file_path":"demo.ts","content":"// SPDX-License-Identifier: Apache-2.0\nexport const x = 1;\n"}}' | comment-checker 2>&1 +[check-comments] Skipping: No unnecessary comments found +``` + +## What it flags and what it spares + +| Comment kind | Cited reason | Example | +|---|---|---| +| Restates code | `restates what the code already says` | `// 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` | `// console.log("debug")` | +| Untracked TODO | `a TODO with no tracked reference` | `// TODO: fix this later` | + +Spared without warning: license and generated headers, directives (`# noqa: E501`, `// @ts-ignore`), BDD steps (`# given`, `// then`), structured API docs (`@param`, `Returns:`), non-obvious intent, `Why:` notes and `// ref:` links, and shebang lines. + +## Exit codes + +| Code | Meaning | +|---|---| +| `0` | Pass — no unnecessary comments found (empty or unparseable payload also passes) | +| `2` | Block — one or more unnecessary comments; the report is on stderr, the stream a host forwards to the model | + +## Configuration + +Replace the default warning text with `--prompt` and put the report where it goes: + +```bash +comment-checker --prompt "Review feedback:\n\n{{comments}}\n\nRevise the code." +``` + +Delete whole-line flagged comments from the file named in the payload with `--strip`. Trailing and inline comments stay in the file and are still reported: + +```bash +comment-checker --strip +``` + +```json +{ + "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "comment-checker --strip" } ] } ] } +} +``` + +## Troubleshooting + +**Q: `command not found: comment-checker` after installing.** +A: The package manager's global bin directory is not on PATH. Check with `pnpm bin -g` or `npm bin -g`, then add that directory to PATH. + +**Q: The hook reports "comment-checker did not run".** +A: The hook could not resolve the binary on PATH and the `direnv exec` fallback also missed. For a flake-based project, run `direnv allow` once so the `.envrc` loads; otherwise install globally and fix PATH. The [setup and repair skill](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/SKILL.md) ships a doctor (`scripts/doctor.ts`) that probes resolution, identity, the exit-code contract, hook wiring, and the direnv bridge, and prints a fix hint per broken check. + +**Q: Does it modify my files?** +A: Not unless you pass `--strip`. The default reads a hook payload over stdin and prints a report. + +**Q: Does it send my code anywhere?** +A: No network requests at all. The binary is fully offline. + +**Q: It is flagging comments in unrelated files.** +A: It reads the hook payload's file path and skips unsupported formats. If a `matcher` scope is too wide, restrict it in settings — most setups want `Write|Edit|MultiEdit` only. -## Docs +## Resources -- Setup and repair: the `comment-checker-setup` skill (`.claude/skills/comment-checker-setup/`) — run its `scripts/doctor.ts` to verify resolution, identity, the exit-code contract, hook wiring, and the direnv bridge end-to-end +- Full documentation — comments flagged, comments spared, the 37 languages, and version history: [the project README](https://github.com/systemfsoftware/comment-checker/blob/master/README.md) +- Setup and repair: [the comment-checker-setup skill](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/SKILL.md), including its [doctor script](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/scripts/doctor.ts) - 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 From 257580806f9615ffa51c4246a726573372bc0ff7 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:47:26 +0000 Subject: [PATCH 6/8] docs(npm): rewrite package README for standalone registry isolation --- npm/packages/comment-checker/README.md | 143 ++++++++++++++----------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/npm/packages/comment-checker/README.md b/npm/packages/comment-checker/README.md index 32482fb..92be7ec 100644 --- a/npm/packages/comment-checker/README.md +++ b/npm/packages/comment-checker/README.md @@ -1,25 +1,25 @@ # @systemfsoftware/claude-code-comment-checker -A Claude Code `PostToolUse` hook that flags unnecessary code comments and states the exact reason each one fails. A tree-sitter classifier over 37 languages, gated to F1 ≥ 0.85 on a 60-case corpus, it spares public API docs, directives, and non-obvious intent instead of flagging every comment. Without `--strip` it never edits your files, and it never sends code anywhere. It exits deterministically so the hook can gate automation. +[![npm version](https://img.shields.io/npm/v/@systemfsoftware/claude-code-comment-checker.svg)](https://www.npmjs.com/package/@systemfsoftware/claude-code-comment-checker) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/systemfsoftware/comment-checker/blob/master/LICENSE) -## Install +`@systemfsoftware/claude-code-comment-checker` is the npm distribution launcher for `comment-checker`, a standalone `PostToolUse` hook for Claude Code that classifies code comments as justified or unnecessary across 37 programming languages. -One command, for any npm-compatible manager (npm, pnpm, yarn, bun): +It downloads or executes native platform binaries for Linux, macOS, and Windows via optional platform dependencies. Without `--strip`, it never modifies files on disk and performs all parsing and classification offline. + +## Quick Start + +### 1. Install globally ```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, so `--ignore-scripts` environments work. - -| Method | Command | -|---|---| -| npm/pnpm/yarn/bun | `pnpm install -g @systemfsoftware/claude-code-comment-checker` | -| Cargo (from source) | `cargo install --git https://github.com/systemfsoftware/comment-checker --package claude-code-comment-checker` | +*Compatible with `npm`, `yarn`, and `bun`.* -## Wire it into Claude Code +### 2. Configure Claude Code -Add the hook to user (`~/.claude/settings.json`) or project (`.claude/settings.json`) configuration: +Add the command hook to your project's `.claude/settings.json` or your global `~/.claude/settings.json`: ```json { @@ -28,7 +28,10 @@ Add the hook to user (`~/.claude/settings.json`) or project (`.claude/settings.j { "matcher": "Write|Edit|MultiEdit", "hooks": [ - { "type": "command", "command": "comment-checker" } + { + "type": "command", + "command": "comment-checker" + } ] } ] @@ -36,21 +39,21 @@ Add the hook to user (`~/.claude/settings.json`) or project (`.claude/settings.j } ``` -The hook's resolution chain is PATH first, then `direnv exec`: +## How It Works -- When `comment-checker` is on PATH, the hook runs it directly. Install globally and the package manager's bin directory must be on PATH (`pnpm bin -g` or `npm bin -g`). -- When the binary is missing on PATH, the hook falls back to `direnv exec "$CLAUDE_PROJECT_DIR"`. Projects with a `flake.nix` that provides the checker (wrapped in bubblewrap) need a `.envrc` containing `use flake` and a one-time `direnv allow`. -- When neither arm resolves, the hook reports that it did not run — nothing was checked. Run the [setup and repair skill](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/SKILL.md) to fix it. +When Claude Code executes a tool call matching `Write`, `Edit`, or `MultiEdit`, the tool input payload is piped to `comment-checker` over `stdin`. -On `Edit` and `MultiEdit`, only the comments *added* by the edit are checked; pre-existing comments are left alone, and restatement detection is disabled on edit fragments to avoid false positives. +- **On clean code (Pass)**: The process outputs `[check-comments] Skipping: No unnecessary comments found` on `stdout` and exits with status code `0`. +- **On flagged comments (Block)**: The process formats an explanation detailing why each comment was flagged, outputs the diagnostics to `stderr`, and exits with status code `2`. Claude Code passes `stderr` back to the model to prompt remediation. +- **On partial edits**: On `Edit` and `MultiEdit`, only freshly introduced comments are evaluated. Pre-existing comments in surrounding lines are preserved. -## See it in action +## Example Output -Pipe a `Write` payload to the binary. The report goes to stderr, so `2>&1` keeps it when you redirect: +Piping a tool payload with unnecessary comments: ```bash -$ echo '{"tool_name":"Write","tool_input":{"file_path":"demo.ts","content":"// increment counter\nlet counter = 0;\ncounter += 1;\n"}}' | comment-checker 2>&1 -An automated reviewer flagged 1 comment(s) in demo.ts as unnecessary. +$ echo '{"tool_name":"Write","tool_input":{"file_path":"src/math.ts","content":"// increment counter\ncounter += 1;\n"}}' | comment-checker 2>&1 +An automated reviewer flagged 1 comment(s) in src/math.ts as unnecessary. 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 @@ -63,72 +66,82 @@ one, make the code self-explanatory instead — better names, extraction, a clearer type — and do not re-add the comment. ``` -The exit status is the contract: this write exited `2`. A clean write prints a skip note and exits `0`: +## Supported Classifications -```bash -$ echo '{"tool_name":"Write","tool_input":{"file_path":"demo.ts","content":"// SPDX-License-Identifier: Apache-2.0\nexport const x = 1;\n"}}' | comment-checker 2>&1 -[check-comments] Skipping: No unnecessary comments found -``` +| Classification | Rule Description | Example | +|---|---|---| +| **Restatement** | Restates syntax or operations visible in adjacent code | `// increment counter` above `counter += 1;` | +| **Control Flow** | Narrates standard control flow structures | `// loop through items` above `for item in items:` | +| **Changelog Memo** | Explains prior code states that belong in git history | `// Changed from old_api to new_api` | +| **Dead Code** | Commented-out code blocks or debugging statements | `// console.log("debug", value);` | +| **Untracked TODO** | Action items with no ticket or reference issue | `// TODO: fix this later` | -## What it flags and what it spares +### Allowed Comments -| Comment kind | Cited reason | Example | -|---|---|---| -| Restates code | `restates what the code already says` | `// 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` | `// console.log("debug")` | -| Untracked TODO | `a TODO with no tracked reference` | `// TODO: fix this later` | +The classifier preserves: +- License headers and SPDX tags (`// SPDX-License-Identifier: Apache-2.0`) +- Linter and compiler directives (`// eslint-disable-next-line`, `# noqa: E501`, `// @ts-ignore`) +- Structured API documentation (`@param`, `@returns`, `Args:`, `Returns:`, `# Safety`) +- Non-obvious intent and architectural rationale (`// Workaround for upstream race in connection pool`) +- BDD test annotations (`// Given`, `// When`, `// Then`) -Spared without warning: license and generated headers, directives (`# noqa: E501`, `// @ts-ignore`), BDD steps (`# given`, `// then`), structured API docs (`@param`, `Returns:`), non-obvious intent, `Why:` notes and `// ref:` links, and shebang lines. +## Exit Code Contract -## Exit codes +| Exit Code | Result | Destination Stream | Description | +|---|---|---|---| +| `0` | Pass | `stdout` | Clean code or unparseable payload (fails open) | +| `2` | Block | `stderr` | Unnecessary comments detected; diagnostics sent to model | -| Code | Meaning | -|---|---| -| `0` | Pass — no unnecessary comments found (empty or unparseable payload also passes) | -| `2` | Block — one or more unnecessary comments; the report is on stderr, the stream a host forwards to the model | +## Options -## Configuration +### Custom Prompt Text (`--prompt`) -Replace the default warning text with `--prompt` and put the report where it goes: +Customize the instruction wrapper surrounding the diagnostics: ```bash -comment-checker --prompt "Review feedback:\n\n{{comments}}\n\nRevise the code." +comment-checker --prompt "Formatting Guidelines Violation:\n\n{{comments}}\n\nPlease clean up the comments." ``` -Delete whole-line flagged comments from the file named in the payload with `--strip`. Trailing and inline comments stay in the file and are still reported: +### Auto-Strip Mode (`--strip`) -```bash -comment-checker --strip -``` +Pass `--strip` to delete flagged whole-line comments directly from the target file on disk when invoked: ```json { - "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "comment-checker --strip" } ] } ] } + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "comment-checker --strip" + } + ] + } + ] + } } ``` ## Troubleshooting -**Q: `command not found: comment-checker` after installing.** -A: The package manager's global bin directory is not on PATH. Check with `pnpm bin -g` or `npm bin -g`, then add that directory to PATH. - -**Q: The hook reports "comment-checker did not run".** -A: The hook could not resolve the binary on PATH and the `direnv exec` fallback also missed. For a flake-based project, run `direnv allow` once so the `.envrc` loads; otherwise install globally and fix PATH. The [setup and repair skill](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/SKILL.md) ships a doctor (`scripts/doctor.ts`) that probes resolution, identity, the exit-code contract, hook wiring, and the direnv bridge, and prints a fix hint per broken check. - -**Q: Does it modify my files?** -A: Not unless you pass `--strip`. The default reads a hook payload over stdin and prints a report. +### `command not found: comment-checker` +Ensure your global npm/pnpm/yarn binary directory is included in your system `$PATH`: +- pnpm: `pnpm bin -g` +- npm: `npm bin -g` +- yarn: `yarn global bin` -**Q: Does it send my code anywhere?** -A: No network requests at all. The binary is fully offline. +### Verification via Doctor Tool +For repository setup diagnosis (PATH resolution, direnv fallbacks, binary identity verification), review the [comment-checker-setup skill](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/SKILL.md) and execute its diagnostic script: -**Q: It is flagging comments in unrelated files.** -A: It reads the hook payload's file path and skips unsupported formats. If a `matcher` scope is too wide, restrict it in settings — most setups want `Write|Edit|MultiEdit` only. +```bash +deno run -A https://raw.githubusercontent.com/systemfsoftware/comment-checker/master/.claude/skills/comment-checker-setup/scripts/doctor.ts +``` -## Resources +## Links -- Full documentation — comments flagged, comments spared, the 37 languages, and version history: [the project README](https://github.com/systemfsoftware/comment-checker/blob/master/README.md) -- Setup and repair: [the comment-checker-setup skill](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/SKILL.md), including its [doctor script](https://github.com/systemfsoftware/comment-checker/blob/master/.claude/skills/comment-checker-setup/scripts/doctor.ts) -- 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 +- **Repository**: [github.com/systemfsoftware/comment-checker](https://github.com/systemfsoftware/comment-checker) +- **Rust Core & Native Builds**: [GitHub Releases](https://github.com/systemfsoftware/comment-checker/releases) +- **Issues & Support**: [GitHub Issues](https://github.com/systemfsoftware/comment-checker/issues) +- **License**: [Apache-2.0](https://github.com/systemfsoftware/comment-checker/blob/master/LICENSE) From 7cbb9ead2241f13f5806a7c05fd5d2ace688bcf6 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:52:07 +0000 Subject: [PATCH 7/8] docs: strip down monorepo root README into clean navigation index --- README.md | 195 ++++++------------------------------------------------ 1 file changed, 20 insertions(+), 175 deletions(-) diff --git a/README.md b/README.md index 83b4ba2..b3c6567 100644 --- a/README.md +++ b/README.md @@ -3,202 +3,47 @@ [![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) -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. - -Without `--strip` it never edits your files. It never sends code anywhere, and it exits deterministically so the hook can gate automation. +`comment-checker` is a fast, offline `PostToolUse` hook for Claude Code and coding agents that classifies code comments across 37 languages using tree-sitter. It flags unnecessary restatements, flow narrations, and dead code with exact reasons while sparing justified API docs, directives, and rationale. ```bash pnpm add -g @systemfsoftware/claude-code-comment-checker ``` -Pipe a `Write` payload to the binary and it reports what it would block. The report goes to stderr, so `2>&1` keeps it when you redirect: - ```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 2>&1 -An automated reviewer flagged 3 comment(s) in src/load_config.py as unnecessary. +$ echo '{"tool_name":"Write","tool_input":{"file_path":"src/load_config.py","content":"def load_config(path):\n # Parse the config file\n return json.load(open(path))\n"}}' | comment-checker 2>&1 +An automated reviewer flagged 1 comment(s) in src/load_config.py as unnecessary. -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 - 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 + line 2 — # Parse the config file — restates what the code already says Action: delete the flagged comments. If the code is unclear without one, make the code self-explanatory instead — better names, extraction, a clearer type — and do not re-add the comment. ``` -Exit status is the contract: `0` on pass, `2` when comments are flagged. The report is written to stderr, because that is the stream the host forwards to the model on exit 2 while stdout is discarded — see the [Claude Code hooks reference](https://code.claude.com/docs/en/hooks#exit-code-2). - -## Install +Exit status is the gate contract: `0` on clean writes, `2` when comments are flagged (diagnostics output to `stderr` for agent recovery). -One command, for any npm-compatible manager (npm, pnpm, yarn, bun): - -```bash -pnpm add -g @systemfsoftware/claude-code-comment-checker -``` +## Monorepo Layout -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. +This repository contains the native Rust classifier core, the npm multi-platform distribution launcher, integration suites, and setup tooling. -| Method | Command | Notes | +| Workspace / Component | Path | Description | |---|---|---| -| 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` | - -## Wire it into Claude Code - -Add the hook to user (`~/.claude/settings.json`) or project (`.claude/settings.json`) configuration: - -```json -{ - "hooks": { - "PostToolUse": [ - { - "matcher": "Write|Edit|MultiEdit", - "hooks": [ - { "type": "command", "command": "comment-checker" } - ] - } - ] - } -} -``` - -Or install this repo as a Claude Code plugin. The hook runs `comment-checker --strip`, then `direnv exec` if that binary is missing. A `flake.nix` in the project makes the error tell you to `direnv allow` or `nix develop` (the flake wraps the checker in bwrap). Deno must be on PATH. - -```bash -claude --plugin-dir . -``` - -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. - -### 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 "exit=$?" -[check-comments] Skipping: No unnecessary comments found -exit=0 -``` - -## Why not a flag-everything linter - -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. - -| | Flag-everything linters | comment-checker | -|---|---|---| -| 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 - -Five kinds of unnecessary comment, each with the reason the hook cites: - -| 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` | - -## What it spares - -Justified comments pass without warnings: - -- **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` - -## Supported languages - -Tree-sitter parsers are compiled into the binary — 37 languages and formats: - -| 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. - -## Configuration - -### Custom prompt - -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\nRevise the code." -``` - -```json -{ - "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "comment-checker --prompt \"Violations detected:\n\n{{comments}}\"" } ] } ] } -} -``` - -### Strip flagged comments - -Pass `--strip` to delete whole-line flagged comments from the file named in the payload. Trailing and inline comments (those sharing a line with code) stay in the file and are reported. If the file is missing, `--strip` is report-only — same as the default. - -```bash -comment-checker --strip -``` - -```json -{ - "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "comment-checker --strip" } ] } ] } -} -``` - -### Exit codes - -Deterministic, and the reason sessions and scripts can gate on the hook: - -| Code | Meaning | -|---|---| -| 0 | Pass — no unnecessary comments found; empty input or unparseable payload also passes. Skip note on stdout | -| 2 | Block — one or more unnecessary comments; report on stderr, the stream the host forwards to the model | - -## FAQ - -**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: Does it modify my files?** -A: Not unless you pass `--strip`. The default reads a hook payload over stdin and prints a report. `--strip` deletes whole-line flagged comments from the named file; trailing and inline comments are left in place. - -**Q: Does it send my code anywhere?** -A: No network requests at all. The binary is fully offline. - -**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. +| **npm Launcher** | [`npm/packages/comment-checker`](npm/packages/comment-checker/README.md) | Node/npm distribution launcher package (`@systemfsoftware/claude-code-comment-checker`) | +| **Rust Core** | [`crates/comment-checker`](crates/comment-checker) | Native binary: tree-sitter parsers, rule classification, and report generation | +| **Setup Skill & Doctor** | [`.claude/skills/comment-checker-setup`](.claude/skills/comment-checker-setup/SKILL.md) | Diagnostic doctor script and hook resolution skill for agent harnesses | +| **Test & Eval Corpus** | [`tests/`](tests), [`eval/corpus.json`](eval/corpus.json) | 60-case multi-language classification test suite gated to F1 ≥ 0.85 | +| **CI & Release Workflows** | [`.github/workflows/`](.github/workflows) | Multi-platform compilation matrix and OIDC npm publication pipeline | -## Repository layout +## Packages & Usage -| 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) | +- **Installation & Hook Setup**: See the [npm Package README](npm/packages/comment-checker/README.md) for Claude Code hook wiring (`.claude/settings.json`), options (`--prompt`, `--strip`), and troubleshooting. +- **Hook Diagnostics**: Run `./.claude/skills/comment-checker-setup/scripts/doctor.ts` to probe PATH resolution, binary identity, exit contracts, and direnv bridges. -## Contributing +## Development & Gates -Development setup, verification gates, and the mutation-testing standard live in [AGENTS.md](AGENTS.md). +- **Contributing & Workflows**: See [AGENTS.md](AGENTS.md) for Rust toolchain setup, Cargo test gates, and 100% classifier mutation testing rules. +- **Architecture & Domain Models**: See [CONCEPTS.md](CONCEPTS.md) for classifier verdict definitions and context semantics. ## License -Apache-2.0. See [LICENSE](LICENSE). \ No newline at end of file +Apache-2.0. See [LICENSE](LICENSE). From 430f61b3380e12d41459daf14e2b246fffef9534 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Sun, 30 Aug 2026 19:53:23 +0000 Subject: [PATCH 8/8] docs: clean monorepo root README to routing map --- README.md | 52 +++++++++++++++------------------------------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index b3c6567..ce9f6f1 100644 --- a/README.md +++ b/README.md @@ -3,47 +3,25 @@ [![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) -`comment-checker` is a fast, offline `PostToolUse` hook for Claude Code and coding agents that classifies code comments across 37 languages using tree-sitter. It flags unnecessary restatements, flow narrations, and dead code with exact reasons while sparing justified API docs, directives, and rationale. +> A tree-sitter-based `PostToolUse` hook for Claude Code and coding agents that flags unnecessary comments across 37 programming languages. -```bash -pnpm add -g @systemfsoftware/claude-code-comment-checker -``` +## Workspaces & Packages -```bash -$ echo '{"tool_name":"Write","tool_input":{"file_path":"src/load_config.py","content":"def load_config(path):\n # Parse the config file\n return json.load(open(path))\n"}}' | comment-checker 2>&1 -An automated reviewer flagged 1 comment(s) in src/load_config.py as unnecessary. +| Workspace / Package | Description | +|---|---| +| [`npm/packages/comment-checker`](npm/packages/comment-checker/README.md) | Node/npm distribution launcher package (`@systemfsoftware/claude-code-comment-checker`) | +| [`crates/comment-checker`](crates/comment-checker) | Rust core classifier engine, parser rules, and native CLI executable | +| [`.claude/skills/comment-checker-setup`](.claude/skills/comment-checker-setup/SKILL.md) | Harness setup skill and automated diagnostic doctor script | +| [`tests/`](tests) / [`eval/corpus.json`](eval/corpus.json) | 60-case multi-language classification test suite (F1 ≥ 0.85) | +| [`.github/workflows/`](.github/workflows) | Multi-platform build matrix, binary packaging, and npm release pipeline | - line 2 — # Parse the config file — restates what the code already says +## Documentation & Contributing -Action: delete the flagged comments. If the code is unclear without -one, make the code self-explanatory instead — better names, extraction, -a clearer type — and do not re-add the comment. -``` - -Exit status is the gate contract: `0` on clean writes, `2` when comments are flagged (diagnostics output to `stderr` for agent recovery). - -## Monorepo Layout - -This repository contains the native Rust classifier core, the npm multi-platform distribution launcher, integration suites, and setup tooling. - -| Workspace / Component | Path | Description | -|---|---|---| -| **npm Launcher** | [`npm/packages/comment-checker`](npm/packages/comment-checker/README.md) | Node/npm distribution launcher package (`@systemfsoftware/claude-code-comment-checker`) | -| **Rust Core** | [`crates/comment-checker`](crates/comment-checker) | Native binary: tree-sitter parsers, rule classification, and report generation | -| **Setup Skill & Doctor** | [`.claude/skills/comment-checker-setup`](.claude/skills/comment-checker-setup/SKILL.md) | Diagnostic doctor script and hook resolution skill for agent harnesses | -| **Test & Eval Corpus** | [`tests/`](tests), [`eval/corpus.json`](eval/corpus.json) | 60-case multi-language classification test suite gated to F1 ≥ 0.85 | -| **CI & Release Workflows** | [`.github/workflows/`](.github/workflows) | Multi-platform compilation matrix and OIDC npm publication pipeline | - -## Packages & Usage - -- **Installation & Hook Setup**: See the [npm Package README](npm/packages/comment-checker/README.md) for Claude Code hook wiring (`.claude/settings.json`), options (`--prompt`, `--strip`), and troubleshooting. -- **Hook Diagnostics**: Run `./.claude/skills/comment-checker-setup/scripts/doctor.ts` to probe PATH resolution, binary identity, exit contracts, and direnv bridges. - -## Development & Gates - -- **Contributing & Workflows**: See [AGENTS.md](AGENTS.md) for Rust toolchain setup, Cargo test gates, and 100% classifier mutation testing rules. -- **Architecture & Domain Models**: See [CONCEPTS.md](CONCEPTS.md) for classifier verdict definitions and context semantics. +- **Usage & Hook Setup**: See the [npm launcher README](npm/packages/comment-checker/README.md) for installation, settings configuration, and CLI flags. +- **Diagnostic Tooling**: See the [comment-checker-setup skill](.claude/skills/comment-checker-setup/SKILL.md) for hook verification and troubleshooting. +- **Development & Verification**: See [AGENTS.md](AGENTS.md) for Cargo build gates, code standards, and mutation test instructions. +- **Architecture & Vocabulary**: See [CONCEPTS.md](CONCEPTS.md) for domain terms and classifier verdict mechanics. ## License -Apache-2.0. See [LICENSE](LICENSE). +[Apache-2.0](LICENSE)