@smeltjs/core 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.
Files changed (199) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +55 -0
  3. package/THIRD-PARTY.md +140 -0
  4. package/dist/apply.d.ts +145 -0
  5. package/dist/apply.d.ts.map +1 -0
  6. package/dist/apply.js +244 -0
  7. package/dist/apply.js.map +1 -0
  8. package/dist/cache/prefix.d.ts +164 -0
  9. package/dist/cache/prefix.d.ts.map +1 -0
  10. package/dist/cache/prefix.js +361 -0
  11. package/dist/cache/prefix.js.map +1 -0
  12. package/dist/cli/args.d.ts +71 -0
  13. package/dist/cli/args.d.ts.map +1 -0
  14. package/dist/cli/args.js +308 -0
  15. package/dist/cli/args.js.map +1 -0
  16. package/dist/cli/bin.d.ts +3 -0
  17. package/dist/cli/bin.d.ts.map +1 -0
  18. package/dist/cli/bin.js +161 -0
  19. package/dist/cli/bin.js.map +1 -0
  20. package/dist/cli/config.d.ts +73 -0
  21. package/dist/cli/config.d.ts.map +1 -0
  22. package/dist/cli/config.js +144 -0
  23. package/dist/cli/config.js.map +1 -0
  24. package/dist/cli/init.d.ts +56 -0
  25. package/dist/cli/init.d.ts.map +1 -0
  26. package/dist/cli/init.js +449 -0
  27. package/dist/cli/init.js.map +1 -0
  28. package/dist/cli/report.d.ts +44 -0
  29. package/dist/cli/report.d.ts.map +1 -0
  30. package/dist/cli/report.js +113 -0
  31. package/dist/cli/report.js.map +1 -0
  32. package/dist/cli/resolve.d.ts +103 -0
  33. package/dist/cli/resolve.d.ts.map +1 -0
  34. package/dist/cli/resolve.js +106 -0
  35. package/dist/cli/resolve.js.map +1 -0
  36. package/dist/cli/run.d.ts +99 -0
  37. package/dist/cli/run.d.ts.map +1 -0
  38. package/dist/cli/run.js +351 -0
  39. package/dist/cli/run.js.map +1 -0
  40. package/dist/detect.d.ts +16 -0
  41. package/dist/detect.d.ts.map +1 -0
  42. package/dist/detect.js +18 -0
  43. package/dist/detect.js.map +1 -0
  44. package/dist/errors.d.ts +88 -0
  45. package/dist/errors.d.ts.map +1 -0
  46. package/dist/errors.js +107 -0
  47. package/dist/errors.js.map +1 -0
  48. package/dist/hash.d.ts +13 -0
  49. package/dist/hash.d.ts.map +1 -0
  50. package/dist/hash.js +16 -0
  51. package/dist/hash.js.map +1 -0
  52. package/dist/index.d.ts +121 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +93 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/lang/bash.d.ts +3 -0
  57. package/dist/lang/bash.d.ts.map +1 -0
  58. package/dist/lang/bash.js +28 -0
  59. package/dist/lang/bash.js.map +1 -0
  60. package/dist/lang/c.d.ts +3 -0
  61. package/dist/lang/c.d.ts.map +1 -0
  62. package/dist/lang/c.js +39 -0
  63. package/dist/lang/c.js.map +1 -0
  64. package/dist/lang/c_sharp.d.ts +3 -0
  65. package/dist/lang/c_sharp.d.ts.map +1 -0
  66. package/dist/lang/c_sharp.js +37 -0
  67. package/dist/lang/c_sharp.js.map +1 -0
  68. package/dist/lang/cpp.d.ts +3 -0
  69. package/dist/lang/cpp.d.ts.map +1 -0
  70. package/dist/lang/cpp.js +46 -0
  71. package/dist/lang/cpp.js.map +1 -0
  72. package/dist/lang/go.d.ts +3 -0
  73. package/dist/lang/go.d.ts.map +1 -0
  74. package/dist/lang/go.js +31 -0
  75. package/dist/lang/go.js.map +1 -0
  76. package/dist/lang/java.d.ts +3 -0
  77. package/dist/lang/java.d.ts.map +1 -0
  78. package/dist/lang/java.js +34 -0
  79. package/dist/lang/java.js.map +1 -0
  80. package/dist/lang/javascript.d.ts +3 -0
  81. package/dist/lang/javascript.d.ts.map +1 -0
  82. package/dist/lang/javascript.js +28 -0
  83. package/dist/lang/javascript.js.map +1 -0
  84. package/dist/lang/kotlin.d.ts +3 -0
  85. package/dist/lang/kotlin.d.ts.map +1 -0
  86. package/dist/lang/kotlin.js +37 -0
  87. package/dist/lang/kotlin.js.map +1 -0
  88. package/dist/lang/php.d.ts +3 -0
  89. package/dist/lang/php.d.ts.map +1 -0
  90. package/dist/lang/php.js +37 -0
  91. package/dist/lang/php.js.map +1 -0
  92. package/dist/lang/profile.d.ts +130 -0
  93. package/dist/lang/profile.d.ts.map +1 -0
  94. package/dist/lang/profile.js +2 -0
  95. package/dist/lang/profile.js.map +1 -0
  96. package/dist/lang/python.d.ts +3 -0
  97. package/dist/lang/python.d.ts.map +1 -0
  98. package/dist/lang/python.js +33 -0
  99. package/dist/lang/python.js.map +1 -0
  100. package/dist/lang/registry.d.ts +31 -0
  101. package/dist/lang/registry.d.ts.map +1 -0
  102. package/dist/lang/registry.js +89 -0
  103. package/dist/lang/registry.js.map +1 -0
  104. package/dist/lang/ruby.d.ts +3 -0
  105. package/dist/lang/ruby.d.ts.map +1 -0
  106. package/dist/lang/ruby.js +47 -0
  107. package/dist/lang/ruby.js.map +1 -0
  108. package/dist/lang/rust.d.ts +3 -0
  109. package/dist/lang/rust.d.ts.map +1 -0
  110. package/dist/lang/rust.js +45 -0
  111. package/dist/lang/rust.js.map +1 -0
  112. package/dist/lang/swift.d.ts +3 -0
  113. package/dist/lang/swift.d.ts.map +1 -0
  114. package/dist/lang/swift.js +34 -0
  115. package/dist/lang/swift.js.map +1 -0
  116. package/dist/lang/tsx.d.ts +4 -0
  117. package/dist/lang/tsx.d.ts.map +1 -0
  118. package/dist/lang/tsx.js +11 -0
  119. package/dist/lang/tsx.js.map +1 -0
  120. package/dist/lang/typescript.d.ts +18 -0
  121. package/dist/lang/typescript.d.ts.map +1 -0
  122. package/dist/lang/typescript.js +61 -0
  123. package/dist/lang/typescript.js.map +1 -0
  124. package/dist/net/policy.d.ts +49 -0
  125. package/dist/net/policy.d.ts.map +1 -0
  126. package/dist/net/policy.js +120 -0
  127. package/dist/net/policy.js.map +1 -0
  128. package/dist/plan/grammar.d.ts +38 -0
  129. package/dist/plan/grammar.d.ts.map +1 -0
  130. package/dist/plan/grammar.js +85 -0
  131. package/dist/plan/grammar.js.map +1 -0
  132. package/dist/plan/lexical.d.ts +51 -0
  133. package/dist/plan/lexical.d.ts.map +1 -0
  134. package/dist/plan/lexical.js +167 -0
  135. package/dist/plan/lexical.js.map +1 -0
  136. package/dist/plan/planners.d.ts +35 -0
  137. package/dist/plan/planners.d.ts.map +1 -0
  138. package/dist/plan/planners.js +26 -0
  139. package/dist/plan/planners.js.map +1 -0
  140. package/dist/plan/structural.d.ts +68 -0
  141. package/dist/plan/structural.d.ts.map +1 -0
  142. package/dist/plan/structural.js +458 -0
  143. package/dist/plan/structural.js.map +1 -0
  144. package/dist/repomap/cache.d.ts +53 -0
  145. package/dist/repomap/cache.d.ts.map +1 -0
  146. package/dist/repomap/cache.js +139 -0
  147. package/dist/repomap/cache.js.map +1 -0
  148. package/dist/repomap/map.d.ts +161 -0
  149. package/dist/repomap/map.d.ts.map +1 -0
  150. package/dist/repomap/map.js +302 -0
  151. package/dist/repomap/map.js.map +1 -0
  152. package/dist/repomap/rank.d.ts +52 -0
  153. package/dist/repomap/rank.d.ts.map +1 -0
  154. package/dist/repomap/rank.js +155 -0
  155. package/dist/repomap/rank.js.map +1 -0
  156. package/dist/repomap/tags.d.ts +47 -0
  157. package/dist/repomap/tags.d.ts.map +1 -0
  158. package/dist/repomap/tags.js +101 -0
  159. package/dist/repomap/tags.js.map +1 -0
  160. package/dist/retrieve.d.ts +14 -0
  161. package/dist/retrieve.d.ts.map +1 -0
  162. package/dist/retrieve.js +44 -0
  163. package/dist/retrieve.js.map +1 -0
  164. package/dist/stages.d.ts +22 -0
  165. package/dist/stages.d.ts.map +1 -0
  166. package/dist/stages.js +33 -0
  167. package/dist/stages.js.map +1 -0
  168. package/dist/stats.d.ts +40 -0
  169. package/dist/stats.d.ts.map +1 -0
  170. package/dist/stats.js +37 -0
  171. package/dist/stats.js.map +1 -0
  172. package/dist/store-dir.d.ts +98 -0
  173. package/dist/store-dir.d.ts.map +1 -0
  174. package/dist/store-dir.js +379 -0
  175. package/dist/store-dir.js.map +1 -0
  176. package/dist/store.d.ts +36 -0
  177. package/dist/store.d.ts.map +1 -0
  178. package/dist/store.js +56 -0
  179. package/dist/store.js.map +1 -0
  180. package/dist/types.d.ts +298 -0
  181. package/dist/types.d.ts.map +1 -0
  182. package/dist/types.js +2 -0
  183. package/dist/types.js.map +1 -0
  184. package/grammars/tree-sitter-bash.wasm +0 -0
  185. package/grammars/tree-sitter-c.wasm +0 -0
  186. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  187. package/grammars/tree-sitter-cpp.wasm +0 -0
  188. package/grammars/tree-sitter-go.wasm +0 -0
  189. package/grammars/tree-sitter-java.wasm +0 -0
  190. package/grammars/tree-sitter-javascript.wasm +0 -0
  191. package/grammars/tree-sitter-kotlin.wasm +0 -0
  192. package/grammars/tree-sitter-php.wasm +0 -0
  193. package/grammars/tree-sitter-python.wasm +0 -0
  194. package/grammars/tree-sitter-ruby.wasm +0 -0
  195. package/grammars/tree-sitter-rust.wasm +0 -0
  196. package/grammars/tree-sitter-swift.wasm +0 -0
  197. package/grammars/tree-sitter-tsx.wasm +0 -0
  198. package/grammars/tree-sitter-typescript.wasm +0 -0
  199. package/package.json +68 -0
package/dist/apply.js ADDED
@@ -0,0 +1,244 @@
1
+ import { OverlappingElisionError, RangeOutOfBoundsError, UnknownHashError } from './errors.js';
2
+ import { HASH_LENGTH } from './hash.js';
3
+ import { LANGUAGE_PROFILES } from './lang/registry.js';
4
+ /**
5
+ * The version of the marker format itself, carried **in band** in every marker.
6
+ *
7
+ * The marker is the one part of smelt a *model* sees, and it goes into prompts.
8
+ * Changing its shape changes model behaviour downstream and shows up as worse output
9
+ * with no error anywhere — this project's signature failure mode, shipped as a patch
10
+ * release. So the wire surface is frozen from 0.1 and treated as 1.0
11
+ * (`CONTRIBUTING.md` § "Two promises, not one"), and a future format is *additive and
12
+ * identifiable*: `smelt/v2` markers can coexist with `smelt/v1` ones, and a consumer
13
+ * parsing markers can tell which it is holding. A format that changed silently would
14
+ * be a substitution; this makes it a declaration.
15
+ *
16
+ * `test/guards/marker-format.test.ts` pins the rendered marker per version and fails if
17
+ * the format moves without the version moving.
18
+ */
19
+ export const MARKER_FORMAT_VERSION = 'v1';
20
+ /**
21
+ * The default marker.
22
+ *
23
+ * Its shape is the user-facing form of Laws 2 and 3, in one line the model reads:
24
+ * *which format this is* (the version), *what was removed* (the explanation), *how
25
+ * much* (the byte count), and *how to get it back* (the hash). Anything that cannot
26
+ * fill in all of those is not allowed to be an elision.
27
+ *
28
+ * `<<…>>` rather than a Unicode bracket because it survives every tokenizer, terminal,
29
+ * and diff tool without becoming three tokens of nothing.
30
+ */
31
+ export const defaultMarker = ({ explanation, bytes, hash }) => `<<smelt/${MARKER_FORMAT_VERSION}: ${explanation} (${String(bytes)}B) — retrieve("${hash}")>>`;
32
+ /**
33
+ * Line-comment leaders per language: the marker always lands as a comment in the
34
+ * survivor's own syntax, because a bare marker line breaks the syntax of what remains
35
+ * around it — in **every** grammar this was tested against, not just the indented ones.
36
+ *
37
+ * The failure classes, each verified by reparsing a bare-marker survivor:
38
+ *
39
+ * - **python** — significant indentation means a parse error does not stay local.
40
+ * The ERROR node swallows the *neighbouring definitions too* — the survivor stops
41
+ * being Python at all, not just at the marker line.
42
+ * - **ruby** and **bash** — the marker *begins with* `<<`, which both languages read
43
+ * as a heredoc operator. A bare marker line opens a heredoc whose terminator never
44
+ * arrives, and everything after it — every kept declaration — is swallowed into a
45
+ * string literal.
46
+ * - **php** — `<<` is an operator here too: the kept function after a bare marker is
47
+ * re-typed into an anonymous-function operand inside the marker's binary
48
+ * expression. The kept declaration is no longer a declaration in the survivor.
49
+ * - **kotlin**, **swift**, and the rest of the brace-delimited set (typescript, tsx,
50
+ * javascript, rust, go, java, c, cpp, c_sharp) — the folk claim that braces keep a
51
+ * parse error local is **empirically false**: reparsing each language's fixture
52
+ * survivor with its own bundled grammar shows ERROR nodes spanning the kept
53
+ * declarations (a C function's signature absorbed into an ERROR, fifteen cascading
54
+ * ERRORs in swift, and so on). The survivor-reparse guard in
55
+ * `test/guards/structural.test.ts` now asserts the opposite property for every
56
+ * structural language: the survivor reparses with no new issues.
57
+ *
58
+ * Only `'unknown'` keeps the bare marker — lexical text has no syntax to break.
59
+ *
60
+ * This does **not** move the frozen wire surface. The `<<smelt/v1: … >>` core is
61
+ * rendered by {@link defaultMarker}, byte-identical and still versioned in band; the
62
+ * leader is part of the substituted marker text, so `outputRange` covers it and
63
+ * reconstruction stays byte-exact. A comment leader in the survivor's own syntax is
64
+ * the one wrapping that cannot change what a model reads out of the marker.
65
+ *
66
+ * A derived view: each leader is the `markerLeader` fact on the language's
67
+ * {@link LanguageProfile} (`src/lang/`), collected here so marker construction keeps
68
+ * one lookup table.
69
+ */
70
+ export const MARKER_LINE_COMMENT_LEADERS = Object.fromEntries(Object.values(LANGUAGE_PROFILES)
71
+ .filter((profile) => profile.markerLeader !== undefined)
72
+ .map((profile) => [profile.id, profile.markerLeader]));
73
+ /**
74
+ * The marker builder for a language: {@link defaultMarker}, wrapped in the language's
75
+ * line-comment leader when {@link MARKER_LINE_COMMENT_LEADERS} names one — so a Python
76
+ * survivor still parses as Python. Everything else gets `base` unchanged.
77
+ */
78
+ export function markerForLanguage(language, base = defaultMarker) {
79
+ const leader = MARKER_LINE_COMMENT_LEADERS[language];
80
+ if (leader === undefined)
81
+ return base;
82
+ return (info) => `${leader}${base(info)}`;
83
+ }
84
+ /**
85
+ * A stand-in hash of the real length, so a marker can be priced before the cut that
86
+ * would earn it exists. Marker cost depends on the hash's *length*, never its value.
87
+ */
88
+ const PLACEHOLDER_HASH = '0'.repeat(HASH_LENGTH);
89
+ /**
90
+ * The one adapter behind the {@link MarkerPricing} seam.
91
+ *
92
+ * Marker cost is this module's fact: `applyPlan` renders the marker, so only this
93
+ * module can price it without guessing. The pricing is built from the **exact builder
94
+ * `applyPlan` will use** — the same resolution, in the same order: a caller-supplied
95
+ * builder (`SmelterConfig.marker` / `ApplyOptions.marker`) wins wholesale, otherwise
96
+ * the language's leader-wrapped default via {@link markerForLanguage}.
97
+ *
98
+ * The custom-builder leg is load-bearing, not a convenience: a caller who installs a
99
+ * longer `MarkerBuilder` changes what every elision costs, and a planner still pricing
100
+ * the *default* marker would keep planning elisions the real marker makes
101
+ * unprofitable — cuts that grow the output, silently. Pricing with the builder's own
102
+ * rendering closes that hole: `costBytes` measures the marker *that builder* would
103
+ * emit, byte for byte.
104
+ *
105
+ * `createSmelter` (and through it, the CLI) calls this centrally, once per smelt call;
106
+ * a caller driving `planLexical`/`planStructural` directly builds its own and puts it
107
+ * on the {@link PlanInput}.
108
+ */
109
+ export function markerPricing(language = 'unknown', markerBuilder) {
110
+ // The same resolution applyPlan performs: a supplied builder wins wholesale.
111
+ const build = markerBuilder ?? markerForLanguage(language);
112
+ return {
113
+ costBytes: (reason, elidedBytes) => Buffer.byteLength(build({
114
+ hash: PLACEHOLDER_HASH,
115
+ bytes: elidedBytes,
116
+ rule: reason.rule,
117
+ explanation: reason.explanation,
118
+ }), 'utf8'),
119
+ };
120
+ }
121
+ /**
122
+ * Turn a plan into text.
123
+ *
124
+ * This is the only function in smelt that removes anything, and it contains no
125
+ * judgement at all: it validates the plan, stores every removed run, substitutes
126
+ * markers, and records where each marker landed. All the deciding happens in a
127
+ * {@link Planner}, which is why a plan can be reviewed before a byte moves.
128
+ *
129
+ * @throws {RangeOutOfBoundsError} if a range falls outside the input's UTF-8 bytes.
130
+ * @throws {OverlappingElisionError} if two ranges overlap — applying both would
131
+ * corrupt the output, and picking a winner would be a silent guess.
132
+ */
133
+ export function applyPlan(text, plan, store, options = {}) {
134
+ const buildMarker = options.marker ?? markerForLanguage(plan.language);
135
+ const input = Buffer.from(text, 'utf8');
136
+ const ordered = plan.elisions.toSorted((a, b) => a.range.start - b.range.start);
137
+ for (const { range } of ordered)
138
+ assertInBounds(range, input.length);
139
+ for (let i = 1; i < ordered.length; i += 1) {
140
+ const previous = ordered[i - 1];
141
+ const current = ordered[i];
142
+ if (current.range.start < previous.range.end) {
143
+ throw new OverlappingElisionError(`smelt: plan from "${plan.planner}" elides overlapping ranges ` +
144
+ `[${previous.range.start},${previous.range.end}) and ` +
145
+ `[${current.range.start},${current.range.end}). A plan must be a partition.`);
146
+ }
147
+ }
148
+ const pieces = [];
149
+ const applied = [];
150
+ let cursor = 0;
151
+ let outputBytes = 0;
152
+ for (const { range, reason } of ordered) {
153
+ const kept = input.subarray(cursor, range.start);
154
+ pieces.push(kept);
155
+ outputBytes += kept.length;
156
+ const removed = input.subarray(range.start, range.end);
157
+ const removedText = removed.toString('utf8');
158
+ const hash = store.put(removedText);
159
+ const marker = buildMarker({
160
+ hash,
161
+ bytes: removed.length,
162
+ rule: reason.rule,
163
+ explanation: reason.explanation,
164
+ });
165
+ const markerBuffer = Buffer.from(marker, 'utf8');
166
+ pieces.push(markerBuffer);
167
+ applied.push({
168
+ hash,
169
+ range,
170
+ outputRange: { start: outputBytes, end: outputBytes + markerBuffer.length },
171
+ bytes: removed.length,
172
+ reason,
173
+ marker,
174
+ });
175
+ outputBytes += markerBuffer.length;
176
+ cursor = range.end;
177
+ }
178
+ const tail = input.subarray(cursor);
179
+ pieces.push(tail);
180
+ outputBytes += tail.length;
181
+ const output = Buffer.concat(pieces).toString('utf8');
182
+ const measure = options.measure;
183
+ return {
184
+ text: output,
185
+ inputBytes: input.length,
186
+ outputBytes,
187
+ planner: plan.planner,
188
+ language: plan.language,
189
+ elisions: applied,
190
+ ...(measure === undefined
191
+ ? {}
192
+ : {
193
+ measured: {
194
+ measure: measure.id,
195
+ unit: measure.unit,
196
+ input: measure.count(text),
197
+ output: measure.count(output),
198
+ },
199
+ }),
200
+ };
201
+ }
202
+ /**
203
+ * Put it all back. `reconstruct(smelt(x), store) === x`, byte for byte — this is Law 3
204
+ * expressed as an executable equation, and `test/guards/reversibility.test.ts` asserts
205
+ * it on every input the suite knows about.
206
+ *
207
+ * Reads through `peek`, **not** `retrieve`: `retrieveCalls` and the expansion rate
208
+ * exist to count *the model asking for hidden material back* — the honest signal this
209
+ * whole project sells. A caller reassembling the original (to diff it, to verify a
210
+ * round trip, to write it to disk) is not that, and counting it would inflate the one
211
+ * number that must never flatter. The guard in
212
+ * `test/guards/expansion-counter.test.ts` pins this: reconstruction leaves every
213
+ * counter exactly where it was.
214
+ *
215
+ * @throws {UnknownHashError} if the store no longer holds an elision's bytes.
216
+ */
217
+ export function reconstruct(result, store) {
218
+ const output = Buffer.from(result.text, 'utf8');
219
+ const ordered = result.elisions.toSorted((a, b) => a.outputRange.start - b.outputRange.start);
220
+ const pieces = [];
221
+ let cursor = 0;
222
+ for (const elision of ordered) {
223
+ assertInBounds(elision.outputRange, output.length);
224
+ const content = store.peek(elision.hash);
225
+ if (content === undefined)
226
+ throw new UnknownHashError(elision.hash);
227
+ pieces.push(output.subarray(cursor, elision.outputRange.start));
228
+ pieces.push(Buffer.from(content, 'utf8'));
229
+ cursor = elision.outputRange.end;
230
+ }
231
+ pieces.push(output.subarray(cursor));
232
+ return Buffer.concat(pieces).toString('utf8');
233
+ }
234
+ function assertInBounds(range, length) {
235
+ if (!Number.isInteger(range.start) ||
236
+ !Number.isInteger(range.end) ||
237
+ range.start < 0 ||
238
+ range.end > length ||
239
+ range.start >= range.end) {
240
+ throw new RangeOutOfBoundsError(`smelt: range [${range.start},${range.end}) is not a non-empty range inside ` +
241
+ `${String(length)} bytes.`);
242
+ }
243
+ }
244
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Cache-prefix hygiene — Slice 6. **Detect and warn, never rewrite.**
3
+ *
4
+ * **This module is a consumer-facing surface**, exported from the package
5
+ * entrypoint and stable in intent: hand it your prompt structure, get warnings
6
+ * back, decide for yourself. It composes with nothing else in smelt — no planner
7
+ * calls it, no store holds its output — because cache hygiene is a property of the
8
+ * *request* a consumer assembles, which smelt never sees or intercepts. Use it
9
+ * from your own send path:
10
+ *
11
+ * ```ts
12
+ * // Both functions come from the package entrypoint:
13
+ * // detectCacheBreakers, findPrefixDivergence
14
+ *
15
+ * // Before sending: name the silent cache-breakers in what you are about to send.
16
+ * const warnings = detectCacheBreakers(
17
+ * { tools, system }, // this call
18
+ * { tools: previousTools }, // optional: the previous call, for tool-set drift
19
+ * );
20
+ * // → [{ rule: 'system-timestamp', explanation: 'system prompt contains …' }, …]
21
+ *
22
+ * // Between calls: where exactly did the cached prefix stop matching, and what
23
+ * // did the change cost? `undefined` means intact (identical, or a pure append).
24
+ * const divergence = findPrefixDivergence(previousPrefix, nextPrefix);
25
+ * // → { byteOffset, invalidatedBytes, description } | undefined
26
+ * ```
27
+ *
28
+ * Nothing is ever rewritten on your behalf, and no rate is ever claimed — the
29
+ * paragraphs below say why both refusals are load-bearing.
30
+ *
31
+ * Provider prompt caches match the request prefix byte for byte, so a context
32
+ * optimizer that reorders or rewrites a prompt prefix to "help" a cache can cost
33
+ * more than it saves — and worse, a reordering that changes model behaviour is an
34
+ * unexplainable elision wearing a different hat. Headroom's CacheAligner made the
35
+ * same call: it detects prefix volatility and warns; it never edits the prompt.
36
+ * Neither does this module. Every function here is a pure read over its inputs:
37
+ * nothing is mutated, and nothing "fixed" is ever returned. That is not a comment,
38
+ * it is a guarantee — `test/guards/cache-hygiene.test.ts` asserts it on frozen
39
+ * inputs, and `pnpm mutate` proves the guard goes red when a helpful in-place fix
40
+ * appears.
41
+ *
42
+ * Warnings follow the `ElisionReason` two-field discipline from `types.ts`:
43
+ * a stable `rule` id for counters, and an `explanation` a human reads. A warning
44
+ * that cannot be written as a sentence is a rule nobody understands.
45
+ */
46
+ /**
47
+ * Facts about Anthropic's prompt cache, as published in Anthropic's prompt-caching
48
+ * documentation (docs.anthropic.com), verified 2026-09-01.
49
+ *
50
+ * These are *cited provider facts*, not smelt measurements — the only numbers Law 4
51
+ * permits are someone else's, with their source and date named. In particular this
52
+ * module claims nothing about how often anyone's cache hits: the price multipliers
53
+ * below are prices, and a price is not a frequency.
54
+ */
55
+ export declare const ANTHROPIC_PROMPT_CACHE_FACTS: {
56
+ /** Where every number in this object comes from, and when it was checked. */
57
+ readonly source: 'Anthropic prompt-caching documentation (docs.anthropic.com), verified 2026-09-01';
58
+ /**
59
+ * The cached prefix is matched byte for byte over the request in this order.
60
+ * A byte change in `tools` therefore invalidates `system` and `messages` too.
61
+ */
62
+ readonly prefixOrder: readonly ['tools', 'system', 'messages'];
63
+ /** Any byte change invalidates the cache from that byte to the end of the prefix. */
64
+ readonly invalidation: 'byte-exact; any change invalidates everything after it';
65
+ /** Minimum cacheable prefix, in tokens — approximately 1024 for most models. */
66
+ readonly minCacheablePrefixTokensApprox: 1024;
67
+ /** Maximum number of cache breakpoints per request. */
68
+ readonly maxCacheBreakpoints: 4;
69
+ /** Default time-to-live of a cache entry, in minutes. */
70
+ readonly defaultTtlMinutes: 5;
71
+ /** Optional extended time-to-live, in minutes (one hour). */
72
+ readonly extendedTtlMinutes: 60;
73
+ /** Writing a 5-minute cache entry costs 1.25x the base input-token price. */
74
+ readonly writeCostMultiplier5m: 1.25;
75
+ /** Writing a 1-hour cache entry costs 2x the base input-token price. */
76
+ readonly writeCostMultiplier1h: 2;
77
+ /** Reading a cached prefix costs approximately 0.1x the base input-token price. */
78
+ readonly readCostMultiplierApprox: 0.1;
79
+ };
80
+ /**
81
+ * A cache-hygiene warning. Deliberately the same two-field shape as
82
+ * `ElisionReason` in `types.ts`, and for the same reason: a stable id for counters, a
83
+ * sentence for humans, and nothing else — no patch, no replacement text, no
84
+ * "fixed" prompt. Warning is all this module does.
85
+ */
86
+ export interface CacheWarning {
87
+ /** Stable machine id, e.g. `'system-timestamp'`. */
88
+ readonly rule: string;
89
+ /** Present tense, no trailing period, names the evidence. */
90
+ readonly explanation: string;
91
+ }
92
+ /** The stable rule ids this module can emit. Additive over time, never renamed. */
93
+ export declare const CACHE_BREAKER_RULES: {
94
+ /** A timestamp-shaped value in the system prompt. */
95
+ readonly systemTimestamp: 'system-timestamp';
96
+ /** A UUID in the system prompt. */
97
+ readonly systemUuid: 'system-uuid';
98
+ /** An object in a tool definition whose keys are not in sorted order. */
99
+ readonly unsortedJsonKeys: 'unsorted-json-keys';
100
+ /** The tool set changed between two successive calls. */
101
+ readonly toolSetVaries: 'tool-set-varies';
102
+ };
103
+ /**
104
+ * The first byte where two successive prompt prefixes diverge, with a description
105
+ * of what changed around it. Offsets are UTF-8 **byte** offsets, because that is
106
+ * the unit the provider's cache matches in — a code-unit index would be a lie for
107
+ * any prompt containing a multi-byte character.
108
+ */
109
+ export interface PrefixDivergence {
110
+ /** UTF-8 byte offset of the first byte at which the two prefixes differ. */
111
+ readonly byteOffset: number;
112
+ /** Bytes of `previous` from the divergence to its end — the span a cache no longer matches. */
113
+ readonly invalidatedBytes: number;
114
+ /** What changed around the divergence. Excerpts never split a multi-byte character. */
115
+ readonly description: string;
116
+ }
117
+ /**
118
+ * Compare two successive prompt prefixes the way the provider's cache does: byte
119
+ * for byte, in UTF-8.
120
+ *
121
+ * Returns `undefined` when `next` starts with every byte of `previous` — identical
122
+ * prefixes, or a pure append, both of which leave a cached prefix intact. Anything
123
+ * else returns the first divergent byte offset and a description of what changed
124
+ * around it. This function reports; it does not repair.
125
+ */
126
+ export declare function findPrefixDivergence(previous: string, next: string): PrefixDivergence | undefined;
127
+ /**
128
+ * One tool definition as the caller sends it. Only `name` is required here —
129
+ * the rest of the definition is scanned structurally, whatever shape it has.
130
+ */
131
+ export interface PromptTool {
132
+ readonly name: string;
133
+ readonly [key: string]: unknown;
134
+ }
135
+ /**
136
+ * The prompt structure smelt is shown for hygiene checks — the two parts that
137
+ * serialize ahead of the messages in the cached prefix. smelt never sees, holds,
138
+ * or edits the real request; the caller hands in a description and gets warnings
139
+ * back.
140
+ */
141
+ export interface PromptStructure {
142
+ /** Tool definitions, in the order they are sent. */
143
+ readonly tools?: readonly PromptTool[];
144
+ /** The system prompt text. */
145
+ readonly system?: string;
146
+ }
147
+ /**
148
+ * Detect the named silent cache-breakers in a prompt structure.
149
+ *
150
+ * Three rules over `current` and, when `previous` is supplied, one across the pair:
151
+ *
152
+ * - `system-timestamp` — a timestamp-shaped value in the system prompt
153
+ * - `system-uuid` — a UUID in the system prompt
154
+ * - `unsorted-json-keys` — a tool-definition object whose keys are not sorted
155
+ * - `tool-set-varies` — the tools differ from the previous call's, by content:
156
+ * an added, removed or duplicated tool, a reorder, or a definition rewritten
157
+ * under an unchanged name
158
+ *
159
+ * Pure and read-only: inputs are never mutated, and the return value is warnings
160
+ * only — never a corrected prompt. Deciding what to do about a warning is the
161
+ * caller's call, in the caller's code.
162
+ */
163
+ export declare function detectCacheBreakers(current: PromptStructure, previous?: PromptStructure): readonly CacheWarning[];
164
+ //# sourceMappingURL=prefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefix.d.ts","sourceRoot":"","sources":["../../src/cache/prefix.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B;IACvC,6EAA6E;aAC7E,MAAM,EAAE,kFAAkF;IAC1F;;;OAGG;aACH,WAAW,YAAG,OAAO,EAAE,QAAQ,EAAE,UAAU;IAC3C,qFAAqF;aACrF,YAAY,EAAE,wDAAwD;IACtE,gFAAgF;aAChF,8BAA8B,EAAE,IAAI;IACpC,uDAAuD;aACvD,mBAAmB,EAAE,CAAC;IACtB,yDAAyD;aACzD,iBAAiB,EAAE,CAAC;IACpB,6DAA6D;aAC7D,kBAAkB,EAAE,EAAE;IACtB,6EAA6E;aAC7E,qBAAqB,EAAE,IAAI;IAC3B,wEAAwE;aACxE,qBAAqB,EAAE,CAAC;IACxB,mFAAmF;aACnF,wBAAwB,EAAE,GAAG;CACrB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAQD,mFAAmF;AACnF,eAAO,MAAM,mBAAmB;IAC9B,qDAAqD;aACrD,eAAe,EAAE,kBAAkB;IACnC,mCAAmC;aACnC,UAAU,EAAE,aAAa;IACzB,yEAAyE;aACzE,gBAAgB,EAAE,oBAAoB;IACtC,yDAAyD;aACzD,aAAa,EAAE,iBAAiB;CACxB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+FAA+F;IAC/F,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,uFAAuF;IACvF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAsBD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAuBjG;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AA4ND;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EACxB,QAAQ,CAAC,EAAE,eAAe,GACzB,SAAS,YAAY,EAAE,CASzB"}