windbag 0.1.0__py3-none-win_amd64.whl
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.
|
Binary file
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: windbag
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
License-File: LICENSE
|
|
5
|
+
Summary: Catches comments that narrate a change instead of documenting a real constraint
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
9
|
+
Project-URL: Repository, https://github.com/scale-venture-partners/windbag
|
|
10
|
+
|
|
11
|
+
# windbag
|
|
12
|
+
|
|
13
|
+
A pre-commit linter that catches comments narrating a change — a ticket number, what the code used to do, hedging about whether it works — instead of documenting why the current code is the way it is. Checks Python, JavaScript/TypeScript, Terraform/HCL, Rust, and SQL (including dbt and SQLMesh templates), plus the markup formats that carry comments: YAML, HTML, and Markdown.
|
|
14
|
+
|
|
15
|
+
## What it detects
|
|
16
|
+
|
|
17
|
+
| Rule | Severity | Flags |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `TICKET_ID` | error | A ticket ID in a comment (`SCA-533`). Exempts `TODO(SCA-600)`-style tracked tasks and security-advisory IDs (`CVE-`, `GHSA-`, ...). |
|
|
20
|
+
| `HISTORY_NARRATION` | error | "was missing", "used to be", "no longer", "silently swallows", and similar. |
|
|
21
|
+
| `HEDGE_LANGUAGE` | error | "should work", "hopefully", "not sure why", "i believe", and similar. |
|
|
22
|
+
| `CROSS_FILE_REF` | warn | A pointer to another file/line (`handler.py:147`). Documentation URLs are exempt. |
|
|
23
|
+
| `VERBOSE_COMMENT` | warn | A comment that's long relative to what it documents. Markup files are exempt. |
|
|
24
|
+
| `OBVIOUS_COMMENT` | warn | A comment that just restates the line below it (`// increment the counter` above `counter += 1`). |
|
|
25
|
+
|
|
26
|
+
`error` rules fail the check; `warn` rules are reported but don't block.
|
|
27
|
+
|
|
28
|
+
## Markup files
|
|
29
|
+
|
|
30
|
+
YAML comments (`#`) come from the YAML grammar, so a `#` inside a quoted
|
|
31
|
+
scalar stays data rather than becoming a comment. HTML and Markdown are
|
|
32
|
+
checked through `<!-- ... -->`; in Markdown, anything inside a fenced code
|
|
33
|
+
block is sample markup, not a comment, and is skipped.
|
|
34
|
+
|
|
35
|
+
The content rules — `TICKET_ID`, `HISTORY_NARRATION`, `HEDGE_LANGUAGE`,
|
|
36
|
+
`CROSS_FILE_REF` — carry the weight here. `VERBOSE_COMMENT` does not apply:
|
|
37
|
+
a few lines of explanation above a one-line config key is the idiomatic
|
|
38
|
+
shape in a config file, not a comment outgrowing its code.
|
|
39
|
+
|
|
40
|
+
## SQL files
|
|
41
|
+
|
|
42
|
+
`.sql` files are read as Jinja-templated SQL, which is what dbt and SQLMesh
|
|
43
|
+
models are. `--`, `/* */`, and Jinja `{# ... #}` comments are all checked. A
|
|
44
|
+
marker inside a `'string'`, a `"quoted identifier"`, a `$$ ... $$` body, or a
|
|
45
|
+
`{{ ... }}` / `{% ... %}` tag is data the template emits, not a comment. The
|
|
46
|
+
scanner is dialect-agnostic, so Snowflake, Postgres, BigQuery, and the rest
|
|
47
|
+
all work; MySQL-style `#` line comments are the one form it does not read.
|
|
48
|
+
|
|
49
|
+
A comment is measured against the statement below it: the non-blank lines
|
|
50
|
+
that follow, through the first one ending in `;`.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
Needs a Rust toolchain. If you don't have one:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
58
|
+
. "$HOME/.cargo/env" # and add this line to ~/.zshrc
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then, from a checkout:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
cargo install --path .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That puts `windbag` in `~/.cargo/bin`, which must be on your `PATH`.
|
|
68
|
+
|
|
69
|
+
Without wanting `windbag` on `PATH` permanently: this repo also builds as a
|
|
70
|
+
Python package via [maturin](https://www.maturin.rs)'s `bindings = "bin"`
|
|
71
|
+
mode, which just wraps the compiled binary in a wheel — there's no Python
|
|
72
|
+
code here, and `import windbag` doesn't work. From a checkout with a Rust
|
|
73
|
+
toolchain and [uv](https://docs.astral.sh/uv/):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uvx maturin build --release
|
|
77
|
+
uvx --from target/wheels/windbag-*.whl windbag check --all
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
No package is published yet, so this only works from a local build for now
|
|
81
|
+
— `uvx windbag` (pulling from an index) isn't available.
|
|
82
|
+
|
|
83
|
+
## Use
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
windbag init # write windbag.toml with generic defaults
|
|
87
|
+
windbag check --staged # check what's about to be committed
|
|
88
|
+
windbag check --all # check every git-tracked file in the repo
|
|
89
|
+
windbag check --json --staged # machine-readable output
|
|
90
|
+
windbag check --new-only f.py # only comments on lines the working tree adds over HEAD
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Pre-commit:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
- repo: local
|
|
97
|
+
hooks:
|
|
98
|
+
- id: windbag
|
|
99
|
+
name: windbag
|
|
100
|
+
entry: windbag check --staged
|
|
101
|
+
language: system
|
|
102
|
+
pass_filenames: false
|
|
103
|
+
types_or: [python, javascript, jsx, ts, tsx, terraform, rust, yaml, markdown, html, sql]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
(`windbag` needs to already be on `PATH` — `language: system` doesn't install it for you.)
|
|
107
|
+
|
|
108
|
+
## Claude Code plugin
|
|
109
|
+
|
|
110
|
+
Pre-commit catches slop after it's written. The plugin catches it as it's
|
|
111
|
+
written: a `PostToolUse` hook runs `windbag` on every file Claude edits and
|
|
112
|
+
exits non-zero on a violation, so the findings go straight back to Claude as a
|
|
113
|
+
blocking error and it rewrites the comment before moving on. A `SessionStart`
|
|
114
|
+
hook states the rules up front so most edits never trip the linter at all.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
/plugin marketplace add scale-venture-partners/windbag
|
|
118
|
+
/plugin install windbag@windbag
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The binary has to be on `PATH` too — the plugin ships the hooks, not the linter,
|
|
122
|
+
and they exit quietly when they can't find it. That means a Rust toolchain
|
|
123
|
+
(see [Install](#install)) plus:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
cargo install --git https://github.com/scale-venture-partners/windbag
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
TODO: publish prebuilt macOS binaries from a tagged release so installing this
|
|
130
|
+
doesn't require a Rust toolchain. Fine while it's a couple of people; not fine
|
|
131
|
+
as a team-wide ask.
|
|
132
|
+
|
|
133
|
+
To turn it on for everyone working in a given repo, commit this to that repo's
|
|
134
|
+
`.claude/settings.json`. Anyone who opens the repo is prompted to trust the
|
|
135
|
+
marketplace, and the hooks apply from their next session:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"extraKnownMarketplaces": {
|
|
140
|
+
"windbag": {
|
|
141
|
+
"source": { "source": "github", "repo": "scale-venture-partners/windbag" }
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"enabledPlugins": { "windbag@windbag": true }
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Working on the plugin itself? `/plugin marketplace add /path/to/windbag` points
|
|
149
|
+
at a local checkout instead. Either way the install copies [`plugin/`](plugin/)
|
|
150
|
+
into `~/.claude/plugins/cache/` — keeping it out of the repo root is what keeps
|
|
151
|
+
`target/` out of the copy. That copy is a snapshot: after editing a hook,
|
|
152
|
+
reinstall to pick up the change.
|
|
153
|
+
|
|
154
|
+
The hook needs `windbag` on `PATH` (or `WINDBAG_BIN` set) and `jq` installed;
|
|
155
|
+
without either it exits quietly rather than breaking the session.
|
|
156
|
+
|
|
157
|
+
| Env var | Effect |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `WINDBAG_HOOK=off` | Disable both hooks without uninstalling. |
|
|
160
|
+
| `WINDBAG_HOOK_LEVEL=error` | Block only on `error` rules; ignore warnings. |
|
|
161
|
+
| `WINDBAG_BIN` | Explicit path to the binary. |
|
|
162
|
+
|
|
163
|
+
Only comments on lines the working tree adds over `HEAD` are reported, so
|
|
164
|
+
editing a file doesn't re-litigate comments that were already there. Claude is
|
|
165
|
+
told not to silence a rule with `windbag: ignore` on its own — a false positive
|
|
166
|
+
should surface to you, not get suppressed.
|
|
167
|
+
|
|
168
|
+
`/windbag` sweeps the whole repo and fixes what it finds.
|
|
169
|
+
|
|
170
|
+
Suppress a false positive inline:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Was missing until v2 (LEGACY-1) — kept for the changelog. windbag: ignore[TICKET_ID]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Config lives in `windbag.toml`; see [`examples/scalevp.toml`](examples/scalevp.toml) for narrowing `TICKET_ID` to a real tracker prefix instead of the generic default.
|
|
177
|
+
|
|
178
|
+
## Examples
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
main.tf:218 error TICKET_ID comment references a ticket ID (SCA-533) — that
|
|
182
|
+
context belongs in the commit message or PR
|
|
183
|
+
description, not the code
|
|
184
|
+
main.tf:218 error HISTORY_NARRATION comment narrates the change ("was missing")
|
|
185
|
+
instead of the current state — describe the
|
|
186
|
+
constraint, not the history
|
|
187
|
+
main.tf:218 warn VERBOSE_COMMENT comment block is long relative to what it
|
|
188
|
+
documents (7 comment lines, 7.0x the 1 attached
|
|
189
|
+
code line(s))
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# Was missing entirely (SCA-533): this used to silently no-op. <- TICKET_ID, HISTORY_NARRATION
|
|
194
|
+
value = fetch_value()
|
|
195
|
+
|
|
196
|
+
# This should work but I'm not sure why it fails sometimes. <- HEDGE_LANGUAGE
|
|
197
|
+
retry(fetch_value)
|
|
198
|
+
|
|
199
|
+
# increment the counter <- OBVIOUS_COMMENT
|
|
200
|
+
counter += 1
|
|
201
|
+
|
|
202
|
+
# TODO(SCA-600): revisit after Q3 pricing model ships <- clean, exempt
|
|
203
|
+
schedule_followup()
|
|
204
|
+
|
|
205
|
+
# Sorted DESC because the caller assumes the first row is newest. <- clean, real WHY
|
|
206
|
+
return sorted(items, reverse=True)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
# Was missing (SCA-533): the deploy no longer fails. <- TICKET_ID, HISTORY_NARRATION
|
|
211
|
+
steps:
|
|
212
|
+
- checkout
|
|
213
|
+
|
|
214
|
+
# Pinned because the orb syntax below requires 2.1. <- clean, real WHY
|
|
215
|
+
version: 2.1
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
In Markdown, a comment shown as sample markup inside a fence is content:
|
|
219
|
+
|
|
220
|
+
````markdown
|
|
221
|
+
<!-- Was missing (SCA-533): renders wrong without it. --> <- TICKET_ID, HISTORY_NARRATION
|
|
222
|
+
|
|
223
|
+
```html
|
|
224
|
+
<!-- Was missing (SCA-901): this one is an example. --> <- clean, inside a fence
|
|
225
|
+
```
|
|
226
|
+
````
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT
|
|
231
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
windbag-0.1.0.data/scripts/windbag.exe,sha256=Td17jIe8HMuY1aMTtBZ1BZOvxdH7ECr7cMmTR9ULFM0,6731776
|
|
2
|
+
windbag-0.1.0.dist-info/METADATA,sha256=9_gZjzMSVK1F_pTQANQqjUVriW9yBvTReDNVnoFDJYU,9156
|
|
3
|
+
windbag-0.1.0.dist-info/WHEEL,sha256=8Aej0W0a6Cz6apA3IzJrTnxLRVLAt-w0Oh8SA3Con_c,94
|
|
4
|
+
windbag-0.1.0.dist-info/licenses/LICENSE,sha256=kKxeedoKI5yoj3tSp8rBpxCQlAPTDD8XWjZY4la8XFk,1085
|
|
5
|
+
windbag-0.1.0.dist-info/sboms/windbag.cyclonedx.json,sha256=VoeY8q52CqJ6qqOrOd7SVpiYX67LpB1_EoJq2rUFies,62696
|
|
6
|
+
windbag-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ScaleVP
|
|
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.
|