cachelint 0.1.0
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.
- package/LICENSE +27 -0
- package/LICENSE-PRO.md +67 -0
- package/README.md +298 -0
- package/dist/check-2ONJCYKP.js +23 -0
- package/dist/chunk-2JSY5BQA.js +340 -0
- package/dist/chunk-4GHSUBAY.js +28 -0
- package/dist/chunk-4LB5LF2P.js +51 -0
- package/dist/chunk-BUZCVKMZ.js +246 -0
- package/dist/chunk-DYQPXD3G.js +92 -0
- package/dist/chunk-GXASKZ4Z.js +182 -0
- package/dist/chunk-JZU4PCTS.js +80 -0
- package/dist/chunk-MW7BXKPR.js +593 -0
- package/dist/chunk-O7ZE6CFI.js +32 -0
- package/dist/chunk-SQ4IAMTX.js +65 -0
- package/dist/chunk-UUVSBJ62.js +77 -0
- package/dist/chunk-VWE5TDL5.js +91 -0
- package/dist/chunk-WKFGRNYK.js +494 -0
- package/dist/chunk-WPJYUQMU.js +26 -0
- package/dist/chunk-XKRPGMZT.js +197 -0
- package/dist/chunk-ZSEZQ422.js +286 -0
- package/dist/cli.d.ts +46 -0
- package/dist/cli.js +199 -0
- package/dist/gate-63NKQRXL.js +16 -0
- package/dist/hash-XUTGK6SB.js +15 -0
- package/dist/index.d.ts +1200 -0
- package/dist/index.js +184 -0
- package/dist/license-GFDBTQHG.js +19 -0
- package/dist/lint-PQHIDWOY.js +19 -0
- package/dist/sarif-EQ6VJNGN.js +63 -0
- package/dist/step-summary-NKD4N7AY.js +160 -0
- package/dist/store-KZIQANIT.js +14 -0
- package/package.json +85 -0
- package/schema/cachelint.config.schema.json +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cachelint contributors
|
|
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.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Note: the **Pro** features of cachelint (SARIF output, `--exact` token counting,
|
|
26
|
+
provider-specific bug-pattern packs) are governed by the terms in LICENSE-PRO.md
|
|
27
|
+
and are unlocked by a license key. The MIT terms above apply to the free core.
|
package/LICENSE-PRO.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# cachelint Pro — Commercial License
|
|
2
|
+
|
|
3
|
+
The cachelint **core** — everything reachable without a Pro license key — is MIT
|
|
4
|
+
licensed; see [`LICENSE`](./LICENSE). This document governs the **Pro features**:
|
|
5
|
+
SARIF output (`--sarif`), exact token counting (`--exact`), and provider
|
|
6
|
+
bug-pattern packs (`--pack …`).
|
|
7
|
+
|
|
8
|
+
## Grant
|
|
9
|
+
|
|
10
|
+
Subject to your compliance with these terms, you (the named licensee) are granted
|
|
11
|
+
a non-exclusive, non-transferable, worldwide license to use the cachelint Pro
|
|
12
|
+
features for the duration of your license term:
|
|
13
|
+
|
|
14
|
+
- **Individual license** — one named person; perpetual; covers the version
|
|
15
|
+
released during the purchase term and any patch releases of that line.
|
|
16
|
+
- **Team license** — your organization; renews **annually**; unlimited seats
|
|
17
|
+
within the organization.
|
|
18
|
+
|
|
19
|
+
The buyer's email address is embedded in the license key payload and is printed
|
|
20
|
+
by `cachelint --version` when a license is active. This is the only anti-sharing
|
|
21
|
+
signal in the product; you consent to it at checkout. There is **no telemetry**,
|
|
22
|
+
**no phone-home**, and **no network call** anywhere in license verification — keys
|
|
23
|
+
are verified offline with a bundled Ed25519 public key.
|
|
24
|
+
|
|
25
|
+
## Restrictions
|
|
26
|
+
|
|
27
|
+
You may not: redistribute or resell the license key; remove or obscure the
|
|
28
|
+
buyer-email signal; use the key on behalf of a different person/organization than
|
|
29
|
+
the named licensee; or circumvent the license gate.
|
|
30
|
+
|
|
31
|
+
A leaked or shared key may be added to the revocation denylist that ships in
|
|
32
|
+
subsequent releases; team keys also expire annually, bounding the window. A key
|
|
33
|
+
within 14 days past its expiry still works (with a warning) so a
|
|
34
|
+
renewal lapse never breaks your CI mid-sprint.
|
|
35
|
+
|
|
36
|
+
## Important: SARIF on private repositories
|
|
37
|
+
|
|
38
|
+
`--sarif` produces a SARIF file for GitHub Code Scanning. **Uploading SARIF to a
|
|
39
|
+
private repository requires GitHub Advanced Security.** On public repositories it
|
|
40
|
+
works on the free GitHub plan. cachelint cannot change this — it is GitHub's
|
|
41
|
+
limitation — so if your repo is private and you don't have Advanced Security, the
|
|
42
|
+
free `cachelint lint` (plain stderr + exit codes) and `cachelint check` (the hash
|
|
43
|
+
gate) are the parts of the gate you'll actually use; `--sarif` adds annotations on
|
|
44
|
+
top, not core functionality.
|
|
45
|
+
|
|
46
|
+
## Refunds
|
|
47
|
+
|
|
48
|
+
If cachelint Pro doesn't do what the README says it does, ask for a refund within
|
|
49
|
+
30 days of purchase. (We'd rather fix it — open an issue.)
|
|
50
|
+
|
|
51
|
+
## Warranty / liability
|
|
52
|
+
|
|
53
|
+
The Pro features are provided "as is", without warranty of any kind. To the
|
|
54
|
+
maximum extent permitted by law, the maintainers are not liable for any damages
|
|
55
|
+
arising from their use. Nothing here limits liability that cannot be limited by
|
|
56
|
+
law.
|
|
57
|
+
|
|
58
|
+
## Contact
|
|
59
|
+
|
|
60
|
+
Purchases, license questions, key reissues: see <https://github.com/davioe/cachelint#pro>.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
*This file ships in the npm package and the repo so the terms travel with the
|
|
65
|
+
tool. See [`docs/licensing.md`](./docs/licensing.md) for the authoritative
|
|
66
|
+
description of the token format, offline verification, the 14-day grace period,
|
|
67
|
+
and key-rotation mechanics.*
|
package/README.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# cachelint
|
|
2
|
+
|
|
3
|
+
> A linter + CI gate for LLM prompt-cache regressions.
|
|
4
|
+
|
|
5
|
+
Anthropic and OpenAI prompt caching are **prefix matches**: a single byte change
|
|
6
|
+
anywhere in the cacheable prefix invalidates the cache. Common causes — an
|
|
7
|
+
interpolated `{{ now }}` in a system prompt, an unsorted `JSON.stringify`, a
|
|
8
|
+
leading BOM, mixed line endings — sail through code review and quietly **10–20×
|
|
9
|
+
your token cost** in production. It's documented in Anthropic's own bug tracker:
|
|
10
|
+
|
|
11
|
+
- [`anthropics/claude-code#34629`](https://github.com/anthropics/claude-code/issues/34629) — `cache_read_input_tokens` never grows, ~20× cost increase
|
|
12
|
+
- [`anthropics/claude-code#46829`](https://github.com/anthropics/claude-code/issues/46829) — TTL silently regressed 1h → 5m, 15–53% overpayment
|
|
13
|
+
- [`anthropics/claude-code#41930`](https://github.com/anthropics/claude-code/issues/41930) — widespread quota drain, March 23 2026
|
|
14
|
+
- Anthropic's [April 23 2026 engineering postmortem](https://www.anthropic.com/engineering/april-23-postmortem) — a caching-logic bug was one of the three root causes
|
|
15
|
+
- …and cross-ecosystem reports (`openclaw/openclaw#19534`, `pydantic-ai#3453`, `BerriAI/litellm#5285`, …) all tracing back to dynamic content in a "stable" prompt.
|
|
16
|
+
|
|
17
|
+
`cachelint` does **one thing**: it catches your own committed prompt content
|
|
18
|
+
silently breaking the cacheable prefix — at edit time and in CI — before it
|
|
19
|
+
ships. It is **zero-cost to run** (no servers, no paid APIs, no telemetry).
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i -g cachelint # or: npx cachelint …
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
ESM-only, Node ≥ 22. Ships a `cachelint` binary and a library
|
|
28
|
+
(`import { lint, hash, check } from "cachelint"`).
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
1. **Lint** your prompt files for known silent-invalidator patterns:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cachelint lint prompts/system.md prompts/tools.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. **Hash** them and commit the lockfile (like `package-lock.json` — `hash`
|
|
39
|
+
writes, `check` verifies):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cachelint hash prompts/system.md prompts/tools.json # writes cachelint.lock
|
|
43
|
+
git add cachelint.lock && git commit -m "lock prompt-cache prefixes"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. **Check** in CI — fails the build if a committed prompt's stable-prefix hash
|
|
47
|
+
moved without a fresh `cachelint hash`:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cachelint check
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
That's it. If `check` fails and the change was intentional, re-run
|
|
54
|
+
`cachelint hash …` and commit the updated `cachelint.lock`.
|
|
55
|
+
|
|
56
|
+
### Optional config
|
|
57
|
+
|
|
58
|
+
`cachelint.config.json` (or `.yaml` / `.yml` — data only, never executable):
|
|
59
|
+
|
|
60
|
+
```jsonc
|
|
61
|
+
{
|
|
62
|
+
"$schema": "https://raw.githubusercontent.com/davioe/cachelint/main/schema/cachelint.config.schema.json",
|
|
63
|
+
"prompt_globs": ["prompts/**/*.md", "prompts/**/*.json"],
|
|
64
|
+
"bundles": [
|
|
65
|
+
{ "name": "system", "files": ["prompts/system.md", "prompts/tools.json"] }
|
|
66
|
+
],
|
|
67
|
+
"disable": [], // e.g. ["R003"]
|
|
68
|
+
"rules": { "R001": "error" } // override default severities; "off" disables
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
With `prompt_globs` set you can just run `cachelint lint` / `cachelint hash` /
|
|
73
|
+
`cachelint check` with no arguments.
|
|
74
|
+
|
|
75
|
+
### Suppressing a finding
|
|
76
|
+
|
|
77
|
+
Inline, ESLint-style — `cachelint-disable-next-line` works in any comment style:
|
|
78
|
+
|
|
79
|
+
```md
|
|
80
|
+
<!-- cachelint-disable-next-line R001 -->
|
|
81
|
+
The treaty was signed on 1989-11-09T18:53:00Z.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## GitHub Action
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
# .github/workflows/cachelint.yml
|
|
88
|
+
permissions:
|
|
89
|
+
contents: read
|
|
90
|
+
# security-events: write # only if you set `sarif:` below (Pro)
|
|
91
|
+
jobs:
|
|
92
|
+
cachelint:
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v4
|
|
96
|
+
- uses: davioe/cachelint@<commit-sha> # pin to a SHA, not a tag
|
|
97
|
+
with:
|
|
98
|
+
paths: 'prompts/**/*.md prompts/**/*.json'
|
|
99
|
+
# license: ${{ secrets.CACHELINT_LICENSE_KEY }} # optional, for Pro
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The action masks the license input, installs an **exact** `cachelint` version
|
|
103
|
+
(never a floating tag), runs `lint` then `check`, and (Pro) uploads SARIF for PR
|
|
104
|
+
annotations. Pin the action itself to a **commit SHA**, not a moving tag.
|
|
105
|
+
|
|
106
|
+
### CI job summary
|
|
107
|
+
|
|
108
|
+
On GitHub Actions — and on any CI that exposes a `GITHUB_STEP_SUMMARY` file
|
|
109
|
+
path — `cachelint lint` and `cachelint check` each append a titled
|
|
110
|
+
cache-health section (findings table, bundle stability, cost estimates) to the
|
|
111
|
+
job summary, even on thrown errors. It is best-effort and purely cosmetic:
|
|
112
|
+
it never affects exit codes, respects `--no-cost`, redacts secrets, and is a
|
|
113
|
+
**silent no-op** where the env var is absent. Opt out by setting
|
|
114
|
+
`CACHELINT_NO_STEP_SUMMARY` to **any non-empty value** — e.g.
|
|
115
|
+
`CACHELINT_NO_STEP_SUMMARY=1` (or the action's `no-step-summary: "true"`
|
|
116
|
+
input).
|
|
117
|
+
|
|
118
|
+
### Fork PRs on public repos
|
|
119
|
+
|
|
120
|
+
Fork PRs don't get repo secrets, so a license-gated step would fail on them. If
|
|
121
|
+
you use a Pro feature, gate just that and let the free `lint` + `check` (which
|
|
122
|
+
need no secret) run everywhere:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
- uses: davioe/cachelint@<sha>
|
|
126
|
+
continue-on-error: ${{ github.event.pull_request.head.repo.fork }}
|
|
127
|
+
with:
|
|
128
|
+
paths: 'prompts/**/*'
|
|
129
|
+
license: ${{ secrets.CACHELINT_LICENSE_KEY }}
|
|
130
|
+
sarif: cachelint.sarif
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
(The free tier alone catches the whole silent-invalidator class; Pro adds the
|
|
134
|
+
SARIF annotations on top.)
|
|
135
|
+
|
|
136
|
+
### README badge *(once published)*
|
|
137
|
+
|
|
138
|
+
Once the repo is public, advertise the gate with a static shields.io badge —
|
|
139
|
+
swap in your `OWNER/REPO` and workflow file name:
|
|
140
|
+
|
|
141
|
+
```md
|
|
142
|
+
[](https://github.com/OWNER/REPO/actions/workflows/cachelint.yml)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Or the label-only variant if you don't run the workflow on every push:
|
|
146
|
+
|
|
147
|
+
```md
|
|
148
|
+
[](https://github.com/davioe/cachelint)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Pre-commit hooks
|
|
152
|
+
|
|
153
|
+
The repo ships [`.pre-commit-hooks.yaml`](.pre-commit-hooks.yaml) with two
|
|
154
|
+
hooks for the [pre-commit](https://pre-commit.com) framework — `cachelint`
|
|
155
|
+
(lint) and `cachelint-check` (the lockfile gate). *(Once published — the
|
|
156
|
+
`repo:` URL goes live when the repo does.)*
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
# .pre-commit-config.yaml
|
|
160
|
+
repos:
|
|
161
|
+
- repo: https://github.com/davioe/cachelint
|
|
162
|
+
rev: v0.1.0 # pin to a release tag
|
|
163
|
+
hooks:
|
|
164
|
+
- id: cachelint
|
|
165
|
+
- id: cachelint-check
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Both hooks run config-scoped on the whole project (`pass_filenames: false`,
|
|
169
|
+
`always_run: true`) — deliberate: `check` must see the full bundle set
|
|
170
|
+
(passed filenames would redefine the bundles), and `lint` stays scoped to
|
|
171
|
+
`prompt_globs` so R003 behaves exactly like CI.
|
|
172
|
+
|
|
173
|
+
## What gets caught
|
|
174
|
+
|
|
175
|
+
| Rule | Pattern | Severity | Why it matters | Docs |
|
|
176
|
+
| ----- | ---------------------------------------------------------------------- | -------- | --------------------------------------------------- | ---- |
|
|
177
|
+
| R001 | Interpolated timestamp (`{{ now }}`, `Date.now()`, `datetime.now()`, "Current Date & Time:", ISO-8601 datetimes) | warn | Prefix bytes change every call → 100% cache miss after that point | [R001](docs/rules/R001.md) |
|
|
178
|
+
| R002 | Interpolated UUID / random (`crypto.randomUUID()`, `Math.random()`, `uuidv4()`, `nanoid()`, `{{ requestId }}`, …) | warn | Same — non-determinism in the prefix | [R002](docs/rules/R002.md) |
|
|
179
|
+
| R003 | Unsorted `JSON.stringify` (no key-sort replacer) — only on files in `prompt_globs` / `cachelint.lock` / named literally | warn | Object key order isn't guaranteed stable → cache thrash | [R003](docs/rules/R003.md) |
|
|
180
|
+
| R004 | Leading UTF-8 BOM | error | An invisible char at the head of the prompt; cross-OS checkouts introduce it | [R004](docs/rules/R004.md) |
|
|
181
|
+
| R005 | Mixed CRLF / LF / CR line endings in one file | error | Hashes differently on Windows vs Linux even though the text "looks" identical | [R005](docs/rules/R005.md) |
|
|
182
|
+
|
|
183
|
+
`cachelint hash` / `check` normalize line endings + strip the BOM before
|
|
184
|
+
hashing, so the *gate* isn't fooled — but the lint rules still flag the source
|
|
185
|
+
so the inconsistency gets fixed. Pre-launch the false-positive rate of
|
|
186
|
+
R001–R003 is audited against real OSS repos and published in each rule's page.
|
|
187
|
+
|
|
188
|
+
## Output
|
|
189
|
+
|
|
190
|
+
- Human (default, on stderr): `file:line:col level RID message`, plus a fix
|
|
191
|
+
hint and a docs link per finding.
|
|
192
|
+
- `--json` (on stdout): a stable schema — `{ version, configPath, diagnostics, warnings, exitCode }`,
|
|
193
|
+
plus an optional `cost` block (absent under `--no-cost`).
|
|
194
|
+
- `--sarif <path>` (Pro): SARIF v2.1 for GitHub Code Scanning.
|
|
195
|
+
|
|
196
|
+
### Cost estimates
|
|
197
|
+
|
|
198
|
+
Findings carry a deliberately conservative dollar estimate of what the broken
|
|
199
|
+
prefix costs you (full recipe, assumptions, and error bounds:
|
|
200
|
+
[`docs/cost-model.md`](docs/cost-model.md)). Per finding, `cachelint lint`
|
|
201
|
+
prints:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
prompts/random.md:1:13 warning R002 `crypto.randomUUID()` in prompt content — a fresh UUID every call
|
|
205
|
+
› Session id: crypto.randomUUID()
|
|
206
|
+
fix: Generate the id outside the cached prefix (or after the last breakpoint).
|
|
207
|
+
why: https://github.com/davioe/cachelint/blob/main/docs/rules/R002.md
|
|
208
|
+
→ invalidates ~8 stable prefix tokens from here
|
|
209
|
+
≈ $0.022 wasted per 1,000 calls (est. if caching is enabled, claude-sonnet-4-6 pricing, 2026-06)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
…and a run summary:
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
cachelint: 5 problems (2 errors, 3 warnings)
|
|
216
|
+
cachelint: est. waste ≈ $0.068 per 1,000 calls · ~19 prefix tokens at risk (est. if caching is enabled, claude-sonnet-4-6 pricing, 2026-06)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
A failing `cachelint check` prints the waste/at-risk totals before its
|
|
220
|
+
verdict (and a *green* check prints a value receipt of what the stable
|
|
221
|
+
prefixes protect):
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
cachelint: cachelint.lock is out of date —
|
|
225
|
+
~ bundle "prompts/a.md" stablePrefixHash moved:
|
|
226
|
+
was: 70a6822d8636ba2c31494cda7700b158cf426837df5fe6515ba228d817615f15
|
|
227
|
+
now: ccbaa0ede490b50c246fb4f98e227968653eff0f5a8f666d1fba78ae6cb33d87
|
|
228
|
+
prompts/a.md: 6cdf29393117 → 6a02769c47b4
|
|
229
|
+
|
|
230
|
+
If this change is intentional: run `cachelint hash …` and commit the updated cachelint.lock.
|
|
231
|
+
prompts/a.md:2:13 warning R001 template helper injects the current date/time into the prompt
|
|
232
|
+
why: https://github.com/davioe/cachelint/blob/main/docs/rules/R001.md
|
|
233
|
+
→ invalidates ~3 stable prefix tokens from here
|
|
234
|
+
prompts/b.md:1:1 error R004 file begins with a UTF-8 BOM (U+FEFF) — an invisible character at the head of the prompt
|
|
235
|
+
why: https://github.com/davioe/cachelint/blob/main/docs/rules/R004.md
|
|
236
|
+
~4 prefix tokens at risk (byte-stable — changes on edit/regeneration, not per call)
|
|
237
|
+
cachelint: est. waste ~3 stable prefix tokens per call (under $0.01 per 1,000 calls) · ~4 prefix tokens at risk
|
|
238
|
+
cachelint: FAILED — stable-prefix hash moved; 1 error-severity finding
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Cost output is cosmetic — it never affects exit codes, hashing, or the
|
|
242
|
+
lockfile — and `--no-cost` removes it from every surface (human, `--json`,
|
|
243
|
+
CI summary). See [`docs/cost-model.md`](docs/cost-model.md) for why the
|
|
244
|
+
numbers are defensible: volatility classes, the earliest-divergence model,
|
|
245
|
+
and every conservatism guarantee.
|
|
246
|
+
|
|
247
|
+
### Exit codes (v1.0 stability contract)
|
|
248
|
+
|
|
249
|
+
| code | meaning |
|
|
250
|
+
| ---- | ------- |
|
|
251
|
+
| `0` | ok (or only `warn`-severity findings without `--strict`) |
|
|
252
|
+
| `1` | a `stablePrefixHash` moved (regression), or an `error`-severity rule fired (or a `warn` under `--strict`) |
|
|
253
|
+
| `2` | a Pro feature (`--sarif`, `--exact`, `--pack`) was invoked without a license |
|
|
254
|
+
| `3` | `cachelint.lock` is missing or corrupt |
|
|
255
|
+
| `4` | config or source error |
|
|
256
|
+
|
|
257
|
+
## OSS vs Pro
|
|
258
|
+
|
|
259
|
+
| | Free (MIT) | Pro |
|
|
260
|
+
| --- | --- | --- |
|
|
261
|
+
| `cachelint lint` (all rules, pragmas, `--json`) | ✅ | ✅ |
|
|
262
|
+
| `cachelint hash` / `check` — single **and** multi-file bundles | ✅ | ✅ |
|
|
263
|
+
| The GitHub Action | ✅ | ✅ |
|
|
264
|
+
| `--sarif` PR annotations (GitHub Code Scanning¹) | — | ✅ |
|
|
265
|
+
| `--exact` token counting (Anthropic count-tokens API, your key) | — | ✅ |
|
|
266
|
+
| Provider bug-pattern packs (`--pack anthropic-2026-q1`) | — | ✅ |
|
|
267
|
+
|
|
268
|
+
¹ Uploading SARIF to a **private** repo requires GitHub Advanced Security; on
|
|
269
|
+
public repos it works on the free plan. Disclosed in [`LICENSE-PRO.md`](LICENSE-PRO.md).
|
|
270
|
+
|
|
271
|
+
Pro is a one-time individual / annual team license. Keys are **offline-verifiable**
|
|
272
|
+
(Ed25519 via `node:crypto`) — no servers, no telemetry, works in air-gapped CI.
|
|
273
|
+
`cachelint activate <key>` to enable, `cachelint license status` to inspect,
|
|
274
|
+
`cachelint deactivate` to remove; `CACHELINT_LICENSE_KEY` env for CI. The buyer's
|
|
275
|
+
email is embedded in the key and printed by `cachelint --version` — the only
|
|
276
|
+
anti-share signal, disclosed at checkout. See [`docs/licensing.md`](docs/licensing.md).
|
|
277
|
+
|
|
278
|
+
## Design contract
|
|
279
|
+
|
|
280
|
+
- **Zero operating cost.** No servers. No paid APIs (`--exact` uses *your*
|
|
281
|
+
`ANTHROPIC_API_KEY`). No telemetry, ever.
|
|
282
|
+
- **Determinism.** `cachelint.lock` is byte-identical across Windows / macOS /
|
|
283
|
+
Linux and Node 22/24. Full spec: [`docs/determinism-contract.md`](docs/determinism-contract.md).
|
|
284
|
+
- **Config is data, not code.** `.json` / `.yaml` only — no `.ts`/`.js` config,
|
|
285
|
+
so a shared config can't execute anything.
|
|
286
|
+
- **Rules are pure functions** (no I/O, no network) — enforced by a sandbox
|
|
287
|
+
harness, so future community rule packs are safe by construction.
|
|
288
|
+
- **No secret ever leaks.** API keys and license tokens are redacted in every
|
|
289
|
+
output path, including `--verbose`.
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
Free core: **MIT** ([LICENSE](LICENSE)). Pro features: [LICENSE-PRO.md](LICENSE-PRO.md).
|
|
294
|
+
|
|
295
|
+
## Contributing
|
|
296
|
+
|
|
297
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Implementation plan:
|
|
298
|
+
[`docs/plans/2026-05-11-002-feat-cachelint-mvp-plan.md`](docs/plans/2026-05-11-002-feat-cachelint-mvp-plan.md).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
check,
|
|
3
|
+
renderCheckHuman,
|
|
4
|
+
renderCheckJson
|
|
5
|
+
} from "./chunk-ZSEZQ422.js";
|
|
6
|
+
import "./chunk-UUVSBJ62.js";
|
|
7
|
+
import "./chunk-2JSY5BQA.js";
|
|
8
|
+
import "./chunk-O7ZE6CFI.js";
|
|
9
|
+
import "./chunk-GXASKZ4Z.js";
|
|
10
|
+
import "./chunk-4LB5LF2P.js";
|
|
11
|
+
import "./chunk-WKFGRNYK.js";
|
|
12
|
+
import "./chunk-DYQPXD3G.js";
|
|
13
|
+
import "./chunk-VWE5TDL5.js";
|
|
14
|
+
import "./chunk-BUZCVKMZ.js";
|
|
15
|
+
import "./chunk-MW7BXKPR.js";
|
|
16
|
+
import "./chunk-4GHSUBAY.js";
|
|
17
|
+
import "./chunk-WPJYUQMU.js";
|
|
18
|
+
import "./chunk-XKRPGMZT.js";
|
|
19
|
+
export {
|
|
20
|
+
check,
|
|
21
|
+
renderCheckHuman,
|
|
22
|
+
renderCheckJson
|
|
23
|
+
};
|