comment-slop 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comment_slop-0.1.0/.gitignore +6 -0
- comment_slop-0.1.0/CHANGELOG.md +33 -0
- comment_slop-0.1.0/LICENSE +21 -0
- comment_slop-0.1.0/PKG-INFO +393 -0
- comment_slop-0.1.0/README.md +344 -0
- comment_slop-0.1.0/docs/calibration.md +335 -0
- comment_slop-0.1.0/docs/evaluation.md +164 -0
- comment_slop-0.1.0/docs/internals.md +254 -0
- comment_slop-0.1.0/docs/llm-sweep.md +156 -0
- comment_slop-0.1.0/eval/cases-py-docstring.json +1182 -0
- comment_slop-0.1.0/eval/cases-rust-pybamm-doc.json +985 -0
- comment_slop-0.1.0/eval/cases-rust-pybamm.json +800 -0
- comment_slop-0.1.0/eval/cases-rust.json +770 -0
- comment_slop-0.1.0/eval/cases-shell.json +754 -0
- comment_slop-0.1.0/eval/cases-yaml.json +790 -0
- comment_slop-0.1.0/eval/cases.json +2884 -0
- comment_slop-0.1.0/eval/corpus-py-docstring.txt +748 -0
- comment_slop-0.1.0/eval/corpus-rust-pybamm-doc.txt +551 -0
- comment_slop-0.1.0/eval/corpus-rust-pybamm.txt +366 -0
- comment_slop-0.1.0/eval/corpus-rust.txt +356 -0
- comment_slop-0.1.0/eval/corpus-shell.txt +340 -0
- comment_slop-0.1.0/eval/corpus-yaml.txt +376 -0
- comment_slop-0.1.0/eval/corpus.txt +1459 -0
- comment_slop-0.1.0/eval/dogfood.md +133 -0
- comment_slop-0.1.0/eval/generate_corpus.py +273 -0
- comment_slop-0.1.0/eval/score.py +283 -0
- comment_slop-0.1.0/pyproject.toml +55 -0
- comment_slop-0.1.0/run_hook.sh +60 -0
- comment_slop-0.1.0/src/comment_slop.py +2237 -0
- comment_slop-0.1.0/src/recalibrate.py +246 -0
- comment_slop-0.1.0/tests/conftest.py +16 -0
- comment_slop-0.1.0/tests/test_comment_slop.py +1424 -0
- comment_slop-0.1.0/tests/test_corpus_schema.py +183 -0
- comment_slop-0.1.0/tests/test_extractors.py +1151 -0
- comment_slop-0.1.0/tests/test_firing_rate.py +126 -0
- comment_slop-0.1.0/tests/test_oracle_differential.py +428 -0
- comment_slop-0.1.0/tests/test_pipeline.py +420 -0
- comment_slop-0.1.0/tests/test_recall.py +241 -0
- comment_slop-0.1.0/tests/test_review_fixes.py +570 -0
- comment_slop-0.1.0/tests/test_robustness.py +139 -0
- comment_slop-0.1.0/tests/test_score.py +95 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog — comment-slop
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented here. The format
|
|
4
|
+
is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this package follows [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
<!-- New release sections are prepended below by the release-packages skill. -->
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-08-26
|
|
10
|
+
|
|
11
|
+
First public release. The detector has been running as a write-time hook and
|
|
12
|
+
commit gate in the Ionworks monorepo since 2026-07; this packages it so other
|
|
13
|
+
projects can install it rather than vendor it.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `comment-slop` console script and `python -m comment_slop`, over the same
|
|
17
|
+
engine the hook runs: `--all`, `--since REF`, `--include-unreviewed`,
|
|
18
|
+
`--hook`, and `--llm`.
|
|
19
|
+
- Comment extractors for Python, JavaScript/JSX/TSX, TypeScript, YAML, TOML,
|
|
20
|
+
Rust, shell and Dockerfiles, each checked against a tree-sitter grammar by
|
|
21
|
+
the differential suite in the `oracle` extra.
|
|
22
|
+
- Detection tiers spanning restated code, edit narration, history, benchmark
|
|
23
|
+
snapshots, decorative banners, process leaks and comment density, calibrated
|
|
24
|
+
against reference corpora rather than guessed. 95% precision / 85% recall
|
|
25
|
+
over 121 human labels.
|
|
26
|
+
- `run_hook.sh`, the Claude Code `PostToolUse` wrapper, which re-derives its
|
|
27
|
+
exit code from the detector's output so a broken interpreter cannot report
|
|
28
|
+
itself as findings or block the edit loop.
|
|
29
|
+
|
|
30
|
+
### Notes
|
|
31
|
+
- No runtime dependencies, and none are planned: the detector runs on every
|
|
32
|
+
edit and falls back to whatever `python3` is on `PATH`, so an import is a way
|
|
33
|
+
for a broken environment to break editing.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ionworks Technologies Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: comment-slop
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Report agent-authored comment slop on the lines you just changed.
|
|
5
|
+
Project-URL: Homepage, https://ionworks.com
|
|
6
|
+
Project-URL: Repository, https://github.com/ionworks/comment-slop
|
|
7
|
+
Author-email: Ionworks Technologies Inc <info@ionworks.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Ionworks Technologies Inc
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: agent,claude-code,code-review,comments,hook,linter
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
36
|
+
Requires-Python: >=3.11
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest-cov>=7.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
40
|
+
Provides-Extra: oracle
|
|
41
|
+
Requires-Dist: tree-sitter; extra == 'oracle'
|
|
42
|
+
Requires-Dist: tree-sitter-bash; extra == 'oracle'
|
|
43
|
+
Requires-Dist: tree-sitter-dockerfile; extra == 'oracle'
|
|
44
|
+
Requires-Dist: tree-sitter-rust; extra == 'oracle'
|
|
45
|
+
Requires-Dist: tree-sitter-toml; extra == 'oracle'
|
|
46
|
+
Requires-Dist: tree-sitter-typescript; extra == 'oracle'
|
|
47
|
+
Requires-Dist: tree-sitter-yaml; extra == 'oracle'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# comment_slop.py
|
|
51
|
+
|
|
52
|
+
A write-time hook that reports comment slop on the lines you just changed.
|
|
53
|
+
|
|
54
|
+
Detection only — it never edits a source file, and by default it never calls a
|
|
55
|
+
model. It runs as a `PostToolUse` hook in Claude Code, so the report goes back to
|
|
56
|
+
the agent that wrote the file, which is the last point where anyone still knows
|
|
57
|
+
whether a comment was load-bearing.
|
|
58
|
+
|
|
59
|
+
comment-slop: backend/src/pipeline.py
|
|
60
|
+
L42 [restates-code] Increment the counter
|
|
61
|
+
L67 [verbose] This function takes the raw payload and, after
|
|
62
|
+
L91 [historical] Previously this returned a dict
|
|
63
|
+
Repo convention: comment the non-obvious why, not the what.
|
|
64
|
+
Delete or rewrite these, or explain why each is load-bearing.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
uv add comment-slop # or: pip install comment-slop
|
|
69
|
+
|
|
70
|
+
giving you a `comment-slop` command and `python -m comment_slop`. It has **no
|
|
71
|
+
dependencies** and none are planned — see [Usage](#usage).
|
|
72
|
+
|
|
73
|
+
Installing is optional. The engine is one stdlib-only module, so vendoring
|
|
74
|
+
`src/comment_slop.py` and running it with any Python 3.11+ works identically;
|
|
75
|
+
that is how it runs in the repo it was written for.
|
|
76
|
+
|
|
77
|
+
### As a Claude Code hook
|
|
78
|
+
|
|
79
|
+
Register `run_hook.sh` as a `PostToolUse` hook on `Edit|Write`:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"hooks": {
|
|
84
|
+
"PostToolUse": [
|
|
85
|
+
{
|
|
86
|
+
"matcher": "Edit|Write",
|
|
87
|
+
"hooks": [
|
|
88
|
+
{
|
|
89
|
+
"type": "command",
|
|
90
|
+
"command": "sh \"$CLAUDE_PROJECT_DIR\"/packages/comment-slop/run_hook.sh"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
In this monorepo both layers are already wired: the write-time hook in the
|
|
100
|
+
tracked `.claude/settings.json` and the commit gate in
|
|
101
|
+
`.pre-commit-config.yaml` — see [Two layers](#two-layers). The wrapper runs on
|
|
102
|
+
the repo's Python, reading the version from `.python-version` rather than
|
|
103
|
+
restating it, since `uv` would otherwise resolve it from a cwd the hook does not
|
|
104
|
+
control. `uv` only makes startup faster; the wrapper falls back to whatever
|
|
105
|
+
`python3` is on `PATH`, including when the pinned version is unavailable, so a
|
|
106
|
+
broken toolchain cannot stop edits either.
|
|
107
|
+
|
|
108
|
+
Exit **2** means findings, **0** means clean. Any internal failure also exits 0 —
|
|
109
|
+
the hook must never break the edit loop.
|
|
110
|
+
|
|
111
|
+
That last rule is why `settings.json` runs `run_hook.sh` rather than the detector
|
|
112
|
+
directly. Exit 2 is the hook protocol's "block, and show this text to the agent",
|
|
113
|
+
and *every* interpreter also exits 2 for its own failures: `uv` on an unwritable
|
|
114
|
+
cache, a broken `UV_PYTHON` or an unreadable config; `python3` on a missing
|
|
115
|
+
script; `argparse` on a bad flag. Invoked directly, a broken toolchain reported
|
|
116
|
+
its own error text as comment findings and blocked the edit loop. The wrapper
|
|
117
|
+
re-derives the exit code from the output instead — the findings path always
|
|
118
|
+
opens with `comment-slop:` — and retries under `python3` when `uv` itself
|
|
119
|
+
failed, so a broken `uv` degrades to a working fallback rather than to silence.
|
|
120
|
+
|
|
121
|
+
With no git information it scans the whole file; inside a repo it scans only
|
|
122
|
+
lines changed since `HEAD`, so it reports on what you just wrote rather than on
|
|
123
|
+
the file's history.
|
|
124
|
+
|
|
125
|
+
## Two layers
|
|
126
|
+
|
|
127
|
+
The write-time hook covers `Edit` and `Write`, which name their file in
|
|
128
|
+
`tool_input.file_path`. It cannot cover `Bash`, and that gap is not small: an
|
|
129
|
+
agent told to edit with `sed -i`, a heredoc redirect, or a throwaway script
|
|
130
|
+
writes whatever it likes and the detector never sees the file.
|
|
131
|
+
|
|
132
|
+
The gap was first closed inside this hook, by asking git what the shell command
|
|
133
|
+
had changed and bounding it with an mtime cursor per session. That worked, and it
|
|
134
|
+
was the wrong layer. Recovering the file list cost ~150 lines of state, taxed
|
|
135
|
+
every `Bash` call in the session with three `git` calls, and still leaked: a
|
|
136
|
+
`mv`, a `cp -p` or a `rsync -t` preserves the old mtime, and a bulk change past
|
|
137
|
+
the per-call cap was dropped and never revisited.
|
|
138
|
+
|
|
139
|
+
So the real gate is **prek** (`comment-slop` in `.pre-commit-config.yaml`, via
|
|
140
|
+
`scripts/check-comment-slop.sh`). pre-commit hands it the staged paths, which is
|
|
141
|
+
the question the cursor was guessing at. Everything reaching a commit is covered
|
|
142
|
+
— shell writes, another agent, another harness, a human in an editor — and
|
|
143
|
+
nothing has to infer anything.
|
|
144
|
+
|
|
145
|
+
| | write-time hook | prek gate |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| covers | `Edit`, `Write` | anything that reaches a commit |
|
|
148
|
+
| file list | named in the payload | staged paths, from pre-commit |
|
|
149
|
+
| when | as the file is written | at `git commit`, and in CI |
|
|
150
|
+
| cost | ~85ms per edit | one run per commit, changed files only |
|
|
151
|
+
|
|
152
|
+
Neither is redundant. The hook reports while the agent still knows whether a
|
|
153
|
+
comment was load-bearing, which is the only moment that judgement is cheap. The
|
|
154
|
+
gate is what actually holds, because it cannot be routed around by choosing a
|
|
155
|
+
different tool.
|
|
156
|
+
|
|
157
|
+
### `--since` is not optional in CI
|
|
158
|
+
|
|
159
|
+
CI runs prek over a clean checkout. Every tracked file matches `HEAD` there, so
|
|
160
|
+
the detector — which by design reports only changed lines — finds nothing, and
|
|
161
|
+
the job goes green having scanned nothing at all. That is worse than having no
|
|
162
|
+
CI check, because the green tick reads as coverage.
|
|
163
|
+
|
|
164
|
+
`scripts/check-comment-slop.sh` therefore switches to `--since
|
|
165
|
+
origin/$GITHUB_BASE_REF` whenever `GITHUB_BASE_REF` is set, and **exits 1 if that
|
|
166
|
+
ref cannot be resolved** rather than scanning zero files. `ci-lint.yml` checks out
|
|
167
|
+
with `fetch-depth: 0` so the ref exists; that setting is load-bearing.
|
|
168
|
+
|
|
169
|
+
### Narrowing
|
|
170
|
+
|
|
171
|
+
Both modes drop paths git reports as unchanged before scanning them, grouped by
|
|
172
|
+
repository so the query costs two `git` calls rather than the two per path
|
|
173
|
+
`added_lines` would spend reaching the same answer. It matters because the hook
|
|
174
|
+
declares no `files:` — the detector's supported-extension set is the only filter,
|
|
175
|
+
so `prek run --all-files` hands it all 2907 supported files in this repo. Narrowed,
|
|
176
|
+
that run is 0.5s instead of ~56s.
|
|
177
|
+
|
|
178
|
+
Untracked files survive it: relative to the ref they are additions, and they get
|
|
179
|
+
the whole-file scan they always did. `--all` bypasses it entirely, since narrowing
|
|
180
|
+
the documented sweep of committed code would make it report nothing.
|
|
181
|
+
|
|
182
|
+
## Usage
|
|
183
|
+
|
|
184
|
+
Run it on a file, a directory, or a glob:
|
|
185
|
+
|
|
186
|
+
comment-slop backend/src/foo.py
|
|
187
|
+
comment-slop --all 'frontend/src/**/*.ts'
|
|
188
|
+
comment-slop --all backend/src
|
|
189
|
+
|
|
190
|
+
Or without installing, which is what the hook and the recipes below do:
|
|
191
|
+
|
|
192
|
+
uv run --no-project python packages/comment-slop/src/comment_slop.py backend/src/foo.py
|
|
193
|
+
|
|
194
|
+
Quote a glob: the tool expands `**` itself, because whether a shell recurses on
|
|
195
|
+
a bare `**` depends on the shell. Directories skip `node_modules`, `target`,
|
|
196
|
+
`dist` and the rest of the build output.
|
|
197
|
+
|
|
198
|
+
| flag | effect |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `--hook` | read the file path from hook JSON on stdin; bounds the model sweep by wall clock |
|
|
201
|
+
| `--since REF` | compare against `REF` instead of `HEAD`. The CI gate — see [`--since` is not optional in CI](#--since-is-not-optional-in-ci) |
|
|
202
|
+
| `--all` | report every line, not only those changed since `HEAD`. **Required to sweep committed code** — without it a clean tracked file reports nothing, which is what made `--include-unreviewed` look broken |
|
|
203
|
+
| `--include-unreviewed` | also report every short unmatched comment. High recall, low precision. For a deliberate sweep, never the hook |
|
|
204
|
+
| `--llm` | add the model narration sweep. Exploratory; see [docs/llm-sweep.md](docs/llm-sweep.md) |
|
|
205
|
+
|
|
206
|
+
A sweep of already-committed code is therefore
|
|
207
|
+
`--all --include-unreviewed <paths>`; the hook itself passes neither.
|
|
208
|
+
|
|
209
|
+
## What it reports
|
|
210
|
+
|
|
211
|
+
| tier | catches |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `restates-code` | comment says what the next line already says |
|
|
214
|
+
| `edit-narration` | narrates a completed change (`Fixed: skip None entries`). Past tense and the `verb:` changelog prefix only — an imperative opening is how a docstring summary describes the code |
|
|
215
|
+
| `historical` | what the code used to do — needs a pronoun subject (`this used to …`), since `the pattern used to build X` is a contract, not history |
|
|
216
|
+
| `unbuilt` | code that never reached `main` — first-person edit narration (`we removed the retry wrapper`) and a road not taken (`tried a shared cache but it did not help`). Both belong in the PR: the branch they refer to is unmerged or gone, so `git log` cannot resolve them either. Narrow by measurement — `no longer` and `is gone` are almost always present-state facts, so they are not in it |
|
|
217
|
+
| `process-leak` | task numbers, plan references, review chatter |
|
|
218
|
+
| `benchmark` | a measured performance snapshot (`~144x fewer buffers`, `98.2% smaller`, `measured 812ms`). The unit is the discriminator, not the digit: units, limits and non-latency percentiles stay silent, and so does a cost that carries an argument (`costs 1.3 ms/file, because …`) |
|
|
219
|
+
| `banner`, `section-label`, `step-marker` | decorative dividers and `Step 1:` labels |
|
|
220
|
+
| `bare-identifier` | a lone name that repeats the identifier below it |
|
|
221
|
+
| `narrative`, `meta`, `emoji` | chatty asides, comments about comments |
|
|
222
|
+
| `conditional-narration` | short `If condition, perform action` restatements |
|
|
223
|
+
| `code-reference` | pointers to other code that will rot |
|
|
224
|
+
| `verbose` | plain: over 30 words / at least 3 prose lines; API-doc prose: over 90 / at least 10 |
|
|
225
|
+
| `density` | a changed region that is more comment than reference practice. The ceiling is per language (`DENSITY_CEILINGS`): 30% by default, 40% for shell, whose scope is the whole script rather than a function |
|
|
226
|
+
| `narration (model)` | `--llm` only |
|
|
227
|
+
|
|
228
|
+
A **trailing** comment is exempt from the tiers whose signal is terseness —
|
|
229
|
+
`bare-identifier`, `restates-code`, `section-label`, `narrative`,
|
|
230
|
+
`conditional-narration`, `historical`, `unbuilt`, `process-leak` — because a trailing
|
|
231
|
+
comment annotates the value beside it, which is what those tiers read as a label
|
|
232
|
+
for the line. `timeout = 5 # seconds` is the shape, and it alone fired 133
|
|
233
|
+
times across this repo. What inline-ness does not excuse still fires: edit
|
|
234
|
+
narration, banners, `Step 3:` markers, `meta`, emoji, rotting `file.ts:120`
|
|
235
|
+
pointers, benchmark snapshots, and length. The `--llm` sweep skips inline
|
|
236
|
+
comments entirely, and this is the deterministic half of the same argument.
|
|
237
|
+
|
|
238
|
+
Density counts a line carrying both code and a comment as a **code** line, for
|
|
239
|
+
the same reason — see *density* in [docs/internals.md](docs/internals.md).
|
|
240
|
+
|
|
241
|
+
## Languages
|
|
242
|
+
|
|
243
|
+
| language | extensions | plain | doc |
|
|
244
|
+
|---|---|---|---|
|
|
245
|
+
| Python | `.py` `.pyi` | `#`, `#:` | docstrings |
|
|
246
|
+
| JS/JSX/TSX | `.js` `.jsx` `.mjs` `.cjs` `.tsx` | `//`, `/* */` | `/** */` |
|
|
247
|
+
| TS (no JSX) | `.ts` `.mts` `.cts` | `//`, `/* */` | `/** */` |
|
|
248
|
+
| YAML | `.yml` `.yaml` | `#` | — |
|
|
249
|
+
| TOML | `.toml` | `#` | — |
|
|
250
|
+
| Rust | `.rs` | `//`, `/* */` | `///` `//!` `/** */` `/*! */` |
|
|
251
|
+
| Shell | `.sh` `.bash` `.zsh` `.ksh` | `#` | — |
|
|
252
|
+
| Dockerfile | `Dockerfile`, `Dockerfile.*`, `*.dockerfile`, `Containerfile*` | `#` | — |
|
|
253
|
+
|
|
254
|
+
Shell is the only `#` language with a trailing comment form, so it is not the
|
|
255
|
+
Dockerfile scanner with more extensions: `#` opens a comment at the start of a
|
|
256
|
+
word and nowhere else (`${x#pre}`, `$((16#ff))` and `it#not` are words), quoting
|
|
257
|
+
carries across lines, and a heredoc body is skipped while a `<<<` herestring is
|
|
258
|
+
not. Extensions only — a `#!/usr/bin/env bash` script named `deploy` reaches no
|
|
259
|
+
extractor, because the dispatch is given a path and not the source.
|
|
260
|
+
|
|
261
|
+
`.ts` gets its own row because TypeScript forbids JSX there so that `<Foo>x` can
|
|
262
|
+
stay a type assertion, and `ts_comments` turns the JSX scanner off to match.
|
|
263
|
+
Reading a cast as an element would consume the rest of the file as element text.
|
|
264
|
+
Babel allows JSX in `.js`, so only the three `.ts` spellings opt out.
|
|
265
|
+
|
|
266
|
+
Doc comments are judged on the prose the author **chose** — the narrative before
|
|
267
|
+
the first `@tag`, rustdoc section or numpydoc header, plus every free-text
|
|
268
|
+
section (`Notes`, `Warnings`, `@remarks`) — and sit on neither side of the
|
|
269
|
+
density ratio. A prescribed section does not spend the budget: a parameter list
|
|
270
|
+
is as long as the signature and an example as long as the code it shows. Details
|
|
271
|
+
in [docs/internals.md](docs/internals.md).
|
|
272
|
+
|
|
273
|
+
Our numpydoc convention in `.claude/rules/python-docstrings.md` is compatible:
|
|
274
|
+
the `Parameters` / `Returns` / `Raises` sections are prescribed and unbudgeted,
|
|
275
|
+
and the imperative summary it mandates is not read as edit narration.
|
|
276
|
+
|
|
277
|
+
## House style
|
|
278
|
+
|
|
279
|
+
The `banner` tier reports the `# ----` section dividers used across
|
|
280
|
+
`backend/src/jobs/` and parts of the frontend. Those fires are accurate — they
|
|
281
|
+
really are decorative dividers — and are deliberately left in place rather than
|
|
282
|
+
suppressed. The tier only fires when you touch a divider line, so treat it as a
|
|
283
|
+
nudge away from the style, not a bug.
|
|
284
|
+
|
|
285
|
+
## Tuning
|
|
286
|
+
|
|
287
|
+
When the hook reports something deliberate, prefer widening `NEVER` over
|
|
288
|
+
narrowing `PATTERNS`. `NEVER` is the documented escape hatch and already covers
|
|
289
|
+
lint directives (`noqa`, `eslint-`, `shellcheck`, …), `TODO`/`SAFETY`/`NOTE`,
|
|
290
|
+
licences, URLs and issue references.
|
|
291
|
+
|
|
292
|
+
The intent markers match **uppercase only**, because each is also an ordinary
|
|
293
|
+
English word: `BUG: broken` is exempt and `a real lexer bug sat here` is not.
|
|
294
|
+
|
|
295
|
+
Widening `NEVER` is safe for the comment's neighbours: an exempt line no longer
|
|
296
|
+
merges, so a new `NEVER` alternative silences the comment that matches it and
|
|
297
|
+
nothing glued to it. It used to silence the whole grouped run.
|
|
298
|
+
|
|
299
|
+
`NEVER` is case-insensitive except the issue key (`ABC-123`), which must be
|
|
300
|
+
shouted. Case-folding it made `us-east-1`, `theta-1`, `libssh2-1` and
|
|
301
|
+
`Wasserstein-1` read as issue keys, and the veto hid every comment around them.
|
|
302
|
+
A new alternative that can match a lowercase `word-digits` token needs the same
|
|
303
|
+
`(?-i:...)` treatment.
|
|
304
|
+
|
|
305
|
+
Thresholds live at the top of `comment_slop.py` and are calibrated rather than
|
|
306
|
+
guessed — see [docs/calibration.md](docs/calibration.md) before changing one.
|
|
307
|
+
Log every wrong fire in `eval/dogfood.md`; that log is what justifies a tier
|
|
308
|
+
change, and it records the reasoning behind the ones already made.
|
|
309
|
+
|
|
310
|
+
## Tests
|
|
311
|
+
|
|
312
|
+
just test-hooks # golden tests, ~15s
|
|
313
|
+
just test-hooks-oracle # the tree-sitter differential, ~4s (first run installs grammars)
|
|
314
|
+
just test-hooks 'packages/comment-slop/tests/test_pipeline.py -v'
|
|
315
|
+
|
|
316
|
+
773 passing, 1 skipped. The one skip is the `--llm` recall check, which needs a
|
|
317
|
+
local model reachable. `test-hooks-oracle` skips nothing.
|
|
318
|
+
|
|
319
|
+
**`test-hooks` does not collect the differential**, deliberately: it installs no
|
|
320
|
+
tree-sitter, so the file would `importorskip` its way to green and a skip nobody
|
|
321
|
+
can act on reads as coverage. `test-hooks-oracle` is its one home, where
|
|
322
|
+
`COMMENT_SLOP_ORACLE_STRICT=1` turns a missing grammar or corpus into a
|
|
323
|
+
**failure**. CI runs both (`.github/workflows/ci-hooks.yml`), gated on
|
|
324
|
+
`packages/comment-slop/**` and `.claude/settings.json`. Why the exclusion lives in
|
|
325
|
+
`tests/conftest.py` rather than in the recipe, and why the differential reads
|
|
326
|
+
every matching file rather than a sample, are in
|
|
327
|
+
[docs/internals.md](docs/internals.md).
|
|
328
|
+
|
|
329
|
+
Its corpora default to this checkout, so TSX, TS, YAML and shell need no setup;
|
|
330
|
+
TOML and Dockerfiles are a handful per repo, so theirs widens to every local
|
|
331
|
+
checkout under `~/Documents/git-repos` when there is one. `COMMENT_SLOP_ORACLE_APP` and
|
|
332
|
+
`_REPOS` override those. Rust lives outside the repo — point `COMMENT_SLOP_ORACLE_RUST` at a
|
|
333
|
+
Rust checkout to include it, or rely on `test_rust_traps`, which carries its
|
|
334
|
+
source inline. **Run the differential before touching any extractor** — see
|
|
335
|
+
[docs/internals.md](docs/internals.md).
|
|
336
|
+
|
|
337
|
+
`ruff-check` and `ruff-format` cover `packages/` at the repo's own settings, so
|
|
338
|
+
they reach this package like any other. The one exemption is in `ruff.toml`: the
|
|
339
|
+
tests and `eval/` scripts insert `src/` on `sys.path` before importing
|
|
340
|
+
`comment_slop`, which E402 reads as a misplaced import and which is how they
|
|
341
|
+
import the engine without depending on it being installed.
|
|
342
|
+
|
|
343
|
+
Keep a constant's rationale in own-line comments **above** it. `ruff-format`
|
|
344
|
+
de-indents a hanging trailing comment to column 0, where `VERBOSE_WORDS`'
|
|
345
|
+
measurements ended up reading as `VERBOSE_LINES`' rationale.
|
|
346
|
+
|
|
347
|
+
Score against the labelled corpus with:
|
|
348
|
+
|
|
349
|
+
just score-hooks
|
|
350
|
+
|
|
351
|
+
Every corpus carries `body_lines` — the immutable comment text captured at
|
|
352
|
+
sampling time — and `tests/test_corpus_schema.py` asserts that plus the rest of
|
|
353
|
+
the schema the scorer reads. Regenerate a corpus with `generate_corpus.py`, never
|
|
354
|
+
by hand.
|
|
355
|
+
|
|
356
|
+
`eval/` is **not** part of the published package. Its corpora quote the source
|
|
357
|
+
they were sampled from verbatim, and that source is a private monorepo, so they
|
|
358
|
+
stay there rather than shipping to PyPI; the figures they produce are quoted
|
|
359
|
+
above. Everything below applies to a monorepo checkout.
|
|
360
|
+
|
|
361
|
+
`eval/score.py` reads `COMMENT_SLOP_EVAL_REPO`, which already defaults to this
|
|
362
|
+
monorepo — the base corpus was sampled from it. The committed corpora record a
|
|
363
|
+
`source_repo` name rather than the path they came from, so scoring a genre
|
|
364
|
+
sampled elsewhere (both Rust genres come from `diffsol`) means pointing that
|
|
365
|
+
variable at the right checkout first. Only `--live` reads the source tree at
|
|
366
|
+
all; the default snapshot scoring works from captured comment lines.
|
|
367
|
+
|
|
368
|
+
## Docs
|
|
369
|
+
|
|
370
|
+
| file | contents |
|
|
371
|
+
|---|---|
|
|
372
|
+
| [docs/internals.md](docs/internals.md) | pipeline, comment kinds, span semantics, how to verify a lexer change |
|
|
373
|
+
| [docs/calibration.md](docs/calibration.md) | every threshold and where it came from; firing rates |
|
|
374
|
+
| [docs/evaluation.md](docs/evaluation.md) | `eval/`, the labelled corpus, sampling channels, dogfood window |
|
|
375
|
+
| [docs/llm-sweep.md](docs/llm-sweep.md) | the `--llm` sweep: model and prompt measurements, why it is off by default |
|
|
376
|
+
|
|
377
|
+
The original design spec and implementation plan sit in `docs/superpowers/`,
|
|
378
|
+
which this repo's `.gitignore` excludes along with every other Claude-generated
|
|
379
|
+
plan. They are present in a local clone but are not tracked here; the upstream
|
|
380
|
+
repo below keeps them.
|
|
381
|
+
|
|
382
|
+
Current deterministic accuracy: **95% precision, 85% recall** over 121 human
|
|
383
|
+
labels, scored from immutable captured comment lines. Precision is the figure
|
|
384
|
+
that matters — a false positive spends the author's trust, a miss costs nothing.
|
|
385
|
+
|
|
386
|
+
## Provenance
|
|
387
|
+
|
|
388
|
+
Originally developed at https://github.com/BradyPlanden/claude-comment-hook and
|
|
389
|
+
vendored into the Ionworks monorepo at `20d7caf`. Development continues there
|
|
390
|
+
and the two have diverged; this package is built from that copy, which is the
|
|
391
|
+
source of truth.
|
|
392
|
+
|
|
393
|
+
Released under the MIT licence — see [LICENSE](LICENSE).
|