comment-slop 0.1.0__py3-none-any.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.
@@ -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).
@@ -0,0 +1,6 @@
1
+ comment_slop.py,sha256=-CLeIo5jlqH_j0pbnQhrtm0Y4Ryg-1lGb_EdKFRR1vo,81169
2
+ comment_slop-0.1.0.dist-info/METADATA,sha256=5H0YLHK5pCDehP5Wob2pSl0qRKIprOvS8-qnU7jAZvI,20912
3
+ comment_slop-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
4
+ comment_slop-0.1.0.dist-info/entry_points.txt,sha256=yzLox6043L6yuUgm7bqDbaA6Lw6dsQ_NZ7w8WsW8oIY,51
5
+ comment_slop-0.1.0.dist-info/licenses/LICENSE,sha256=MaRldz8wcFVJcX3KzjWwjj8QaFeFd3FwB0mB8hQMLQo,1082
6
+ comment_slop-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ comment-slop = comment_slop:main
@@ -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.