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/dist/index.js ADDED
@@ -0,0 +1,184 @@
1
+ import {
2
+ check,
3
+ renderCheckHuman,
4
+ renderCheckJson
5
+ } from "./chunk-ZSEZQ422.js";
6
+ import {
7
+ activate,
8
+ deactivate,
9
+ licenseStatus
10
+ } from "./chunk-JZU4PCTS.js";
11
+ import "./chunk-UUVSBJ62.js";
12
+ import {
13
+ KNOWN_PACK_IDS,
14
+ estimateCosts,
15
+ heuristicTokens,
16
+ lint,
17
+ renderLintHuman,
18
+ renderLintJson,
19
+ usdPer1kCalls
20
+ } from "./chunk-2JSY5BQA.js";
21
+ import {
22
+ PRICING_AS_OF,
23
+ PRICING_TABLE_VERSION,
24
+ REFERENCE_MODEL,
25
+ getModelPricing,
26
+ referencePricing,
27
+ wastePerTokenUsd
28
+ } from "./chunk-O7ZE6CFI.js";
29
+ import {
30
+ GRACE_PERIOD_DAYS,
31
+ activeLicenseInfo,
32
+ isPro,
33
+ requirePro,
34
+ verifyLicenseToken
35
+ } from "./chunk-GXASKZ4Z.js";
36
+ import "./chunk-4LB5LF2P.js";
37
+ import {
38
+ allRules,
39
+ getRule,
40
+ ruleDocsUrl
41
+ } from "./chunk-WKFGRNYK.js";
42
+ import {
43
+ diffLockfiles,
44
+ isRegression
45
+ } from "./chunk-DYQPXD3G.js";
46
+ import {
47
+ COST_LINE_OVERLAPPED,
48
+ RECEIPT_EXACT_FALLBACK_LINE,
49
+ buildReceiptLines,
50
+ costLabel,
51
+ costLineAtRisk,
52
+ costLineInvalidates,
53
+ costLineWasted,
54
+ costSummary,
55
+ formatTokens,
56
+ formatUsdPer1k,
57
+ receiptAllBelowMinimumLine,
58
+ receiptProtectedLine,
59
+ receiptWarnQualifier,
60
+ receiptWontEngageLine
61
+ } from "./chunk-VWE5TDL5.js";
62
+ import {
63
+ hash,
64
+ renderHashHuman,
65
+ renderHashJson
66
+ } from "./chunk-SQ4IAMTX.js";
67
+ import {
68
+ DEFAULT_LOCKFILE_NAME,
69
+ LOCKFILE_FORMAT_VERSION,
70
+ canonicalJsonStringify,
71
+ parseLockfile,
72
+ serializeLockfile
73
+ } from "./chunk-BUZCVKMZ.js";
74
+ import {
75
+ CONFIG_FILENAMES,
76
+ configJsonSchema,
77
+ configSchema,
78
+ defineConfig,
79
+ loadConfig,
80
+ parseConfig,
81
+ resolveConfigArg
82
+ } from "./chunk-MW7BXKPR.js";
83
+ import {
84
+ DEFAULT_RULE_SEVERITY,
85
+ DEFAULT_RULE_VOLATILITY,
86
+ RULE_IDS
87
+ } from "./chunk-4GHSUBAY.js";
88
+ import {
89
+ CACHELINT_VERSION,
90
+ COST_MODEL_VERSION,
91
+ NORMALIZATION_POLICY_VERSION,
92
+ RULE_TABLE_VERSION
93
+ } from "./chunk-WPJYUQMU.js";
94
+ import {
95
+ AdapterError,
96
+ COST_UNAVAILABLE_CODE,
97
+ CachelintError,
98
+ ConfigError,
99
+ ExactCountUnavailableError,
100
+ LicenseError,
101
+ LockCorruptError,
102
+ LockError,
103
+ LockMissingError,
104
+ RuleError,
105
+ SourceError
106
+ } from "./chunk-XKRPGMZT.js";
107
+ export {
108
+ AdapterError,
109
+ CACHELINT_VERSION,
110
+ CONFIG_FILENAMES,
111
+ COST_LINE_OVERLAPPED,
112
+ COST_MODEL_VERSION,
113
+ COST_UNAVAILABLE_CODE,
114
+ CachelintError,
115
+ ConfigError,
116
+ DEFAULT_LOCKFILE_NAME,
117
+ DEFAULT_RULE_SEVERITY,
118
+ DEFAULT_RULE_VOLATILITY,
119
+ ExactCountUnavailableError,
120
+ GRACE_PERIOD_DAYS,
121
+ KNOWN_PACK_IDS,
122
+ LOCKFILE_FORMAT_VERSION,
123
+ LicenseError,
124
+ LockCorruptError,
125
+ LockError,
126
+ LockMissingError,
127
+ NORMALIZATION_POLICY_VERSION,
128
+ PRICING_AS_OF,
129
+ PRICING_TABLE_VERSION,
130
+ RECEIPT_EXACT_FALLBACK_LINE,
131
+ REFERENCE_MODEL,
132
+ RULE_IDS,
133
+ RULE_TABLE_VERSION,
134
+ RuleError,
135
+ SourceError,
136
+ activate,
137
+ activeLicenseInfo,
138
+ allRules,
139
+ buildReceiptLines,
140
+ canonicalJsonStringify,
141
+ check,
142
+ configJsonSchema,
143
+ configSchema,
144
+ costLabel,
145
+ costLineAtRisk,
146
+ costLineInvalidates,
147
+ costLineWasted,
148
+ costSummary,
149
+ deactivate,
150
+ defineConfig,
151
+ diffLockfiles,
152
+ estimateCosts,
153
+ formatTokens,
154
+ formatUsdPer1k,
155
+ getModelPricing,
156
+ getRule,
157
+ hash,
158
+ heuristicTokens,
159
+ isPro,
160
+ isRegression,
161
+ licenseStatus,
162
+ lint,
163
+ loadConfig,
164
+ parseConfig,
165
+ parseLockfile,
166
+ receiptAllBelowMinimumLine,
167
+ receiptProtectedLine,
168
+ receiptWarnQualifier,
169
+ receiptWontEngageLine,
170
+ referencePricing,
171
+ renderCheckHuman,
172
+ renderCheckJson,
173
+ renderHashHuman,
174
+ renderHashJson,
175
+ renderLintHuman,
176
+ renderLintJson,
177
+ requirePro,
178
+ resolveConfigArg,
179
+ ruleDocsUrl,
180
+ serializeLockfile,
181
+ usdPer1kCalls,
182
+ verifyLicenseToken,
183
+ wastePerTokenUsd
184
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ activate,
3
+ deactivate,
4
+ licenseStatus,
5
+ renderActivateHuman,
6
+ renderDeactivateHuman,
7
+ renderLicenseStatusHuman
8
+ } from "./chunk-JZU4PCTS.js";
9
+ import "./chunk-GXASKZ4Z.js";
10
+ import "./chunk-4LB5LF2P.js";
11
+ import "./chunk-XKRPGMZT.js";
12
+ export {
13
+ activate,
14
+ deactivate,
15
+ licenseStatus,
16
+ renderActivateHuman,
17
+ renderDeactivateHuman,
18
+ renderLicenseStatusHuman
19
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ lint,
3
+ renderLintHuman,
4
+ renderLintJson
5
+ } from "./chunk-2JSY5BQA.js";
6
+ import "./chunk-O7ZE6CFI.js";
7
+ import "./chunk-GXASKZ4Z.js";
8
+ import "./chunk-4LB5LF2P.js";
9
+ import "./chunk-WKFGRNYK.js";
10
+ import "./chunk-VWE5TDL5.js";
11
+ import "./chunk-MW7BXKPR.js";
12
+ import "./chunk-4GHSUBAY.js";
13
+ import "./chunk-WPJYUQMU.js";
14
+ import "./chunk-XKRPGMZT.js";
15
+ export {
16
+ lint,
17
+ renderLintHuman,
18
+ renderLintJson
19
+ };
@@ -0,0 +1,63 @@
1
+ import {
2
+ allRules,
3
+ ruleDocsUrl
4
+ } from "./chunk-WKFGRNYK.js";
5
+ import "./chunk-4GHSUBAY.js";
6
+ import {
7
+ CACHELINT_VERSION
8
+ } from "./chunk-WPJYUQMU.js";
9
+ import "./chunk-XKRPGMZT.js";
10
+
11
+ // src/adapters/sarif.ts
12
+ var SARIF_SCHEMA = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
13
+ var INFORMATION_URI = "https://github.com/davioe/cachelint";
14
+ function toSarif(diagnostics) {
15
+ const rules = allRules().map((r) => ({
16
+ id: r.id,
17
+ name: r.id,
18
+ shortDescription: { text: r.title },
19
+ helpUri: ruleDocsUrl(r.id),
20
+ defaultConfiguration: { level: r.defaultSeverity === "error" ? "error" : "warning" }
21
+ }));
22
+ const results = diagnostics.map((d) => ({
23
+ ruleId: d.rule,
24
+ level: d.severity === "error" ? "error" : "warning",
25
+ message: { text: d.fixHint ? `${d.message} \u2014 ${d.fixHint}` : d.message },
26
+ locations: [
27
+ {
28
+ physicalLocation: {
29
+ artifactLocation: { uri: d.file },
30
+ region: {
31
+ startLine: d.line,
32
+ startColumn: d.column,
33
+ ...d.snippet ? { snippet: { text: d.snippet } } : {}
34
+ }
35
+ }
36
+ }
37
+ ]
38
+ }));
39
+ return {
40
+ $schema: SARIF_SCHEMA,
41
+ version: "2.1.0",
42
+ runs: [
43
+ {
44
+ tool: {
45
+ driver: {
46
+ name: "cachelint",
47
+ informationUri: INFORMATION_URI,
48
+ version: CACHELINT_VERSION,
49
+ rules
50
+ }
51
+ },
52
+ results
53
+ }
54
+ ]
55
+ };
56
+ }
57
+ function sarifString(diagnostics) {
58
+ return JSON.stringify(toSarif(diagnostics), null, 2) + "\n";
59
+ }
60
+ export {
61
+ sarifString,
62
+ toSarif
63
+ };
@@ -0,0 +1,160 @@
1
+ import {
2
+ renderDiffHuman
3
+ } from "./chunk-DYQPXD3G.js";
4
+ import {
5
+ buildReceiptLines,
6
+ costLabel,
7
+ costLineAtRisk,
8
+ costSummary
9
+ } from "./chunk-VWE5TDL5.js";
10
+ import {
11
+ redactSecrets
12
+ } from "./chunk-XKRPGMZT.js";
13
+
14
+ // src/ci/step-summary.ts
15
+ import { appendFileSync } from "fs";
16
+ function stepSummaryPath(env = process.env) {
17
+ const optOut = env["CACHELINT_NO_STEP_SUMMARY"];
18
+ if (optOut !== void 0 && optOut.trim() !== "") return null;
19
+ const path = env["GITHUB_STEP_SUMMARY"];
20
+ if (path === void 0 || path.trim() === "") return null;
21
+ return path;
22
+ }
23
+ function mdEscapeHtml(s) {
24
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
25
+ }
26
+ function mdCell(s) {
27
+ return mdEscapeHtml(s).replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
28
+ }
29
+ function fencedBlock(text) {
30
+ let longestRun = 0;
31
+ for (const m of text.matchAll(/`+/g)) {
32
+ if (m[0].length > longestRun) longestRun = m[0].length;
33
+ }
34
+ const fence = "`".repeat(Math.max(3, longestRun + 1));
35
+ return [`${fence}text`, text, fence];
36
+ }
37
+ function findingsTableLines(diagnostics) {
38
+ if (diagnostics.length === 0) return ["No rule findings."];
39
+ const lines = ["| location | severity | rule | message |", "| --- | --- | --- | --- |"];
40
+ for (const d of diagnostics) {
41
+ const severity = d.severity === "error" ? "error" : "warning";
42
+ lines.push(`| \`${mdCell(`${d.file}:${d.line}:${d.column}`)}\` | ${severity} | ${mdCell(d.rule)} | ${mdCell(d.message)} |`);
43
+ }
44
+ return lines;
45
+ }
46
+ function problemTally(diagnostics) {
47
+ let errors = 0;
48
+ let warns = 0;
49
+ for (const d of diagnostics) {
50
+ if (d.severity === "error") errors++;
51
+ else warns++;
52
+ }
53
+ const parts = [];
54
+ if (errors > 0) parts.push(`${errors} error${errors === 1 ? "" : "s"}`);
55
+ if (warns > 0) parts.push(`${warns} warning${warns === 1 ? "" : "s"}`);
56
+ const total = diagnostics.length;
57
+ return `${total} problem${total === 1 ? "" : "s"} (${parts.join(", ")})`;
58
+ }
59
+ function costTotalsLines(cost) {
60
+ if (cost === void 0) return [];
61
+ const label = costLabel({ model: cost.pricing.referenceModel }, cost.pricing.asOf);
62
+ const summary = costSummary(cost.totals, label);
63
+ if (summary !== null) return [`**Cost:** ${summary}`];
64
+ if (cost.totals.atRiskTokens > 0) return [`**Cost:** ${costLineAtRisk(cost.totals.atRiskTokens)}`];
65
+ return [];
66
+ }
67
+ function renderLintSection(result) {
68
+ const lines = ["## cachelint lint", ""];
69
+ lines.push(...findingsTableLines(result.diagnostics));
70
+ lines.push("");
71
+ lines.push(
72
+ result.diagnostics.length === 0 ? "**Result:** no problems found (exit code 0)" : `**Result:** ${problemTally(result.diagnostics)} \u2014 exit code ${result.exitCode}`
73
+ );
74
+ const cost = costTotalsLines(result.cost);
75
+ if (cost.length > 0) {
76
+ lines.push("");
77
+ lines.push(...cost);
78
+ }
79
+ return lines.join("\n");
80
+ }
81
+ function receiptLines(cost, label) {
82
+ return buildReceiptLines(cost, label).map((l) => `- ${mdEscapeHtml(l)}`);
83
+ }
84
+ function renderCheckSection(r) {
85
+ const lines = ["## cachelint check", ""];
86
+ if (r.regression) {
87
+ lines.push("**Bundle stability:** REGRESSION \u2014 `cachelint.lock` is out of date:", "", ...fencedBlock(renderDiffHuman(r.diff)));
88
+ } else {
89
+ lines.push("**Bundle stability:** lock OK \u2014 `cachelint.lock` matches the current sources.");
90
+ }
91
+ lines.push("");
92
+ lines.push(...findingsTableLines(r.diagnostics));
93
+ lines.push("");
94
+ lines.push(`**Result:** ${r.exitCode === 0 ? "PASSED (exit code 0)" : "FAILED (exit code 1)"}`);
95
+ if (r.cost !== void 0) {
96
+ const label = costLabel({ model: r.cost.pricing.referenceModel }, r.cost.pricing.asOf);
97
+ const cost = r.exitCode === 0 ? receiptLines(r.cost, label) : costTotalsLines(r.cost);
98
+ if (cost.length > 0) {
99
+ lines.push("");
100
+ lines.push(...cost);
101
+ }
102
+ }
103
+ return lines.join("\n");
104
+ }
105
+ function renderFailureSection(command, exitCode, err) {
106
+ const message = err instanceof Error ? err.message : String(err);
107
+ const headline = message.split("\n")[0] ?? "";
108
+ return [
109
+ `## cachelint ${command} \u2014 error`,
110
+ "",
111
+ `\`cachelint ${command}\` did not complete (exit code ${exitCode}): ${headline === "" ? "unknown error" : mdEscapeHtml(headline)}`
112
+ ].join("\n");
113
+ }
114
+ function appendStepSummary(markdown, env = process.env) {
115
+ const path = stepSummaryPath(env);
116
+ if (path === null) return;
117
+ try {
118
+ appendFileSync(path, redactSecrets(markdown) + "\n\n", "utf8");
119
+ } catch (e) {
120
+ noteFailure(e);
121
+ }
122
+ }
123
+ function noteFailure(e) {
124
+ const msg = e instanceof Error ? e.message : String(e);
125
+ try {
126
+ process.stderr.write(redactSecrets(`cachelint: could not append the CI step summary (${msg}) \u2014 continuing
127
+ `));
128
+ } catch {
129
+ }
130
+ }
131
+ function writeSection(render, env) {
132
+ if (stepSummaryPath(env) === null) return;
133
+ let markdown;
134
+ try {
135
+ markdown = render();
136
+ } catch (e) {
137
+ noteFailure(e);
138
+ return;
139
+ }
140
+ appendStepSummary(markdown, env);
141
+ }
142
+ function writeLintStepSummary(result, env = process.env) {
143
+ writeSection(() => renderLintSection(result), env);
144
+ }
145
+ function writeCheckStepSummary(result, env = process.env) {
146
+ writeSection(() => renderCheckSection(result), env);
147
+ }
148
+ function writeFailureStepSummary(context, exitCode, err, env = process.env) {
149
+ writeSection(() => renderFailureSection(context.command, exitCode, err), env);
150
+ }
151
+ export {
152
+ appendStepSummary,
153
+ renderCheckSection,
154
+ renderFailureSection,
155
+ renderLintSection,
156
+ stepSummaryPath,
157
+ writeCheckStepSummary,
158
+ writeFailureStepSummary,
159
+ writeLintStepSummary
160
+ };
@@ -0,0 +1,14 @@
1
+ import {
2
+ clearStoredLicense,
3
+ hasEnvLicense,
4
+ licenseFilePath,
5
+ readStoredLicense,
6
+ writeStoredLicense
7
+ } from "./chunk-4LB5LF2P.js";
8
+ export {
9
+ clearStoredLicense,
10
+ hasEnvLicense,
11
+ licenseFilePath,
12
+ readStoredLicense,
13
+ writeStoredLicense
14
+ };
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "cachelint",
3
+ "version": "0.1.0",
4
+ "description": "A linter + CI gate for LLM prompt-cache regressions. Catches the silent-invalidator class (interpolated timestamps, unsorted JSON, BOM, mixed line endings) and fails PRs when a committed prompt's stable-prefix hash moves.",
5
+ "license": "MIT",
6
+ "author": "cachelint contributors",
7
+ "homepage": "https://github.com/davioe/cachelint",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/davioe/cachelint.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/davioe/cachelint/issues"
14
+ },
15
+ "keywords": [
16
+ "llm",
17
+ "prompt",
18
+ "prompt-cache",
19
+ "prompt-caching",
20
+ "anthropic",
21
+ "claude",
22
+ "openai",
23
+ "linter",
24
+ "ci",
25
+ "cache-control",
26
+ "cachelint",
27
+ "developer-tools",
28
+ "lint"
29
+ ],
30
+ "type": "module",
31
+ "bin": {
32
+ "cachelint": "./dist/cli.js"
33
+ },
34
+ "main": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "default": "./dist/index.js"
40
+ },
41
+ "./package.json": "./package.json"
42
+ },
43
+ "engines": {
44
+ "node": ">=22.0.0"
45
+ },
46
+ "files": [
47
+ "dist/",
48
+ "schema/",
49
+ "README.md",
50
+ "LICENSE",
51
+ "LICENSE-PRO.md"
52
+ ],
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "dev": "tsup --watch",
56
+ "typecheck": "tsc --noEmit",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "lint": "tsc --noEmit",
60
+ "clean": "node -e \"for (const d of ['dist','coverage']) require('node:fs').rmSync(d,{recursive:true,force:true})\"",
61
+ "pack:check": "node scripts/check-pack-allowlist.mjs",
62
+ "prepublishOnly": "npm run build && npm run pack:check"
63
+ },
64
+ "dependencies": {
65
+ "acorn": "^8.16.0",
66
+ "acorn-walk": "^8.3.5",
67
+ "commander": "^14.0.0",
68
+ "yaml": "^2.9.0",
69
+ "zod": "^4.4.3"
70
+ },
71
+ "peerDependencies": {
72
+ "@anthropic-ai/sdk": "^0.30.0"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "@anthropic-ai/sdk": {
76
+ "optional": true
77
+ }
78
+ },
79
+ "devDependencies": {
80
+ "@types/node": "^22.10.0",
81
+ "tsup": "^8.5.0",
82
+ "typescript": "^5.6.0",
83
+ "vitest": "^2.1.0"
84
+ }
85
+ }
@@ -0,0 +1,114 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "$schema": {
6
+ "type": "string"
7
+ },
8
+ "prompt_globs": {
9
+ "type": "array",
10
+ "items": {
11
+ "type": "string",
12
+ "minLength": 1
13
+ }
14
+ },
15
+ "exclude": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "string",
19
+ "minLength": 1
20
+ }
21
+ },
22
+ "disable": {
23
+ "type": "array",
24
+ "items": {
25
+ "type": "string",
26
+ "enum": [
27
+ "R001",
28
+ "R002",
29
+ "R003",
30
+ "R004",
31
+ "R005"
32
+ ]
33
+ }
34
+ },
35
+ "rules": {
36
+ "type": "object",
37
+ "propertyNames": {
38
+ "type": "string",
39
+ "enum": [
40
+ "R001",
41
+ "R002",
42
+ "R003",
43
+ "R004",
44
+ "R005"
45
+ ]
46
+ },
47
+ "additionalProperties": {
48
+ "type": "string",
49
+ "enum": [
50
+ "off",
51
+ "warn",
52
+ "error"
53
+ ]
54
+ }
55
+ },
56
+ "bundles": {
57
+ "type": "array",
58
+ "items": {
59
+ "type": "object",
60
+ "properties": {
61
+ "name": {
62
+ "type": "string",
63
+ "minLength": 1
64
+ },
65
+ "files": {
66
+ "minItems": 1,
67
+ "type": "array",
68
+ "items": {
69
+ "type": "string",
70
+ "minLength": 1
71
+ }
72
+ }
73
+ },
74
+ "required": [
75
+ "name",
76
+ "files"
77
+ ],
78
+ "additionalProperties": false,
79
+ "description": "A named, ordered group of files hashed together as one cache prefix."
80
+ }
81
+ },
82
+ "max_file_bytes": {
83
+ "type": "integer",
84
+ "exclusiveMinimum": 0,
85
+ "maximum": 9007199254740991
86
+ },
87
+ "max_total_bytes": {
88
+ "type": "integer",
89
+ "exclusiveMinimum": 0,
90
+ "maximum": 9007199254740991
91
+ },
92
+ "pro": {
93
+ "type": "object",
94
+ "properties": {
95
+ "exact_limit": {
96
+ "type": "integer",
97
+ "exclusiveMinimum": 0,
98
+ "maximum": 9007199254740991
99
+ },
100
+ "packs": {
101
+ "type": "array",
102
+ "items": {
103
+ "type": "string",
104
+ "minLength": 1
105
+ }
106
+ }
107
+ },
108
+ "additionalProperties": false,
109
+ "description": "Pro-tier settings (only consulted when a Pro feature is invoked with a valid license)."
110
+ }
111
+ },
112
+ "additionalProperties": false,
113
+ "description": "cachelint configuration (cachelint.config.json / .yaml / .yml)."
114
+ }