@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
@@ -0,0 +1,361 @@
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 const ANTHROPIC_PROMPT_CACHE_FACTS = {
56
+ /** Where every number in this object comes from, and when it was checked. */
57
+ 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
+ prefixOrder: ['tools', 'system', 'messages'],
63
+ /** Any byte change invalidates the cache from that byte to the end of the prefix. */
64
+ invalidation: 'byte-exact; any change invalidates everything after it',
65
+ /** Minimum cacheable prefix, in tokens — approximately 1024 for most models. */
66
+ minCacheablePrefixTokensApprox: 1024,
67
+ /** Maximum number of cache breakpoints per request. */
68
+ maxCacheBreakpoints: 4,
69
+ /** Default time-to-live of a cache entry, in minutes. */
70
+ defaultTtlMinutes: 5,
71
+ /** Optional extended time-to-live, in minutes (one hour). */
72
+ extendedTtlMinutes: 60,
73
+ /** Writing a 5-minute cache entry costs 1.25x the base input-token price. */
74
+ writeCostMultiplier5m: 1.25,
75
+ /** Writing a 1-hour cache entry costs 2x the base input-token price. */
76
+ writeCostMultiplier1h: 2,
77
+ /** Reading a cached prefix costs approximately 0.1x the base input-token price. */
78
+ readCostMultiplierApprox: 0.1,
79
+ };
80
+ /**
81
+ * The provider's serialization order, rendered as prose from the cited constant
82
+ * above — so the sentence in a warning can never drift from the fact it cites.
83
+ */
84
+ const PREFIX_ORDER_PROSE = ANTHROPIC_PROMPT_CACHE_FACTS.prefixOrder.join(' → ');
85
+ /** The stable rule ids this module can emit. Additive over time, never renamed. */
86
+ export const CACHE_BREAKER_RULES = {
87
+ /** A timestamp-shaped value in the system prompt. */
88
+ systemTimestamp: 'system-timestamp',
89
+ /** A UUID in the system prompt. */
90
+ systemUuid: 'system-uuid',
91
+ /** An object in a tool definition whose keys are not in sorted order. */
92
+ unsortedJsonKeys: 'unsorted-json-keys',
93
+ /** The tool set changed between two successive calls. */
94
+ toolSetVaries: 'tool-set-varies',
95
+ };
96
+ /** Code points around the code point containing `byteOffset` — never a split character. */
97
+ function excerptAroundByte(text, byteOffset, radius = 16) {
98
+ const points = [...text];
99
+ let index = points.length;
100
+ let bytes = 0;
101
+ for (const [i, point] of points.entries()) {
102
+ const width = Buffer.byteLength(point, 'utf8');
103
+ if (byteOffset < bytes + width) {
104
+ index = i;
105
+ break;
106
+ }
107
+ bytes += width;
108
+ }
109
+ const start = Math.max(0, index - radius);
110
+ const end = Math.min(points.length, index + radius);
111
+ const head = start > 0 ? '…' : '';
112
+ const tail = end < points.length ? '…' : '';
113
+ return head + points.slice(start, end).join('') + tail;
114
+ }
115
+ /**
116
+ * Compare two successive prompt prefixes the way the provider's cache does: byte
117
+ * for byte, in UTF-8.
118
+ *
119
+ * Returns `undefined` when `next` starts with every byte of `previous` — identical
120
+ * prefixes, or a pure append, both of which leave a cached prefix intact. Anything
121
+ * else returns the first divergent byte offset and a description of what changed
122
+ * around it. This function reports; it does not repair.
123
+ */
124
+ export function findPrefixDivergence(previous, next) {
125
+ const previousBytes = Buffer.from(previous, 'utf8');
126
+ const nextBytes = Buffer.from(next, 'utf8');
127
+ const limit = Math.min(previousBytes.length, nextBytes.length);
128
+ let byteOffset = 0;
129
+ while (byteOffset < limit && previousBytes[byteOffset] === nextBytes[byteOffset]) {
130
+ byteOffset += 1;
131
+ }
132
+ if (byteOffset === previousBytes.length)
133
+ return undefined;
134
+ const invalidatedBytes = previousBytes.length - byteOffset;
135
+ const previousExcerpt = JSON.stringify(excerptAroundByte(previous, byteOffset));
136
+ const description = byteOffset === nextBytes.length
137
+ ? `next ends at byte ${String(byteOffset)}, where previous continued with ` +
138
+ `${previousExcerpt}; the ${String(invalidatedBytes)} bytes previous carried from ` +
139
+ `there are gone from the prefix`
140
+ : `first divergence at byte ${String(byteOffset)}: previous has ${previousExcerpt} ` +
141
+ `where next has ${JSON.stringify(excerptAroundByte(next, byteOffset))}; a byte-matched ` +
142
+ `cache stops matching here, so the ${String(invalidatedBytes)} bytes after this ` +
143
+ `point are re-sent cold`;
144
+ return { byteOffset, invalidatedBytes, description };
145
+ }
146
+ /** ISO-8601-shaped dates and datetimes — the values that move with the clock. */
147
+ const TIMESTAMP_PATTERN = /\b\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?\b/g;
148
+ /** RFC 4122 textual UUIDs — the values that are fresh per process or per call. */
149
+ const UUID_PATTERN = /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/g;
150
+ function byteOffsetOf(text, index) {
151
+ return Buffer.byteLength(text.slice(0, index), 'utf8');
152
+ }
153
+ function scanSystemPrompt(system) {
154
+ const warnings = [];
155
+ const timestamps = [...system.matchAll(TIMESTAMP_PATTERN)];
156
+ const firstTimestamp = timestamps[0];
157
+ if (firstTimestamp !== undefined) {
158
+ warnings.push({
159
+ rule: CACHE_BREAKER_RULES.systemTimestamp,
160
+ explanation: `system prompt contains ${String(timestamps.length)} timestamp-shaped ` +
161
+ `value${timestamps.length === 1 ? '' : 's'}, first "${firstTimestamp[0]}" at byte ` +
162
+ `${String(byteOffsetOf(system, firstTimestamp.index))}; a value that moves with ` +
163
+ `the clock changes bytes between calls and silently invalidates the cached ` +
164
+ `prefix from that byte`,
165
+ });
166
+ }
167
+ const uuids = [...system.matchAll(UUID_PATTERN)];
168
+ const firstUuid = uuids[0];
169
+ if (firstUuid !== undefined) {
170
+ warnings.push({
171
+ rule: CACHE_BREAKER_RULES.systemUuid,
172
+ explanation: `system prompt contains ${String(uuids.length)} UUID${uuids.length === 1 ? '' : 's'}, ` +
173
+ `first "${firstUuid[0]}" at byte ${String(byteOffsetOf(system, firstUuid.index))}; ` +
174
+ `an id minted per session or per call changes bytes between calls and silently ` +
175
+ `invalidates the cached prefix from that byte`,
176
+ });
177
+ }
178
+ return warnings;
179
+ }
180
+ /**
181
+ * Keys that JavaScript treats as array indices. Their enumeration position is
182
+ * fixed by the language (ascending numeric, ahead of every string key), so they
183
+ * are not "reorderable" in any sense a serializer could act on.
184
+ */
185
+ const INTEGER_LIKE_KEY = /^(?:0|[1-9]\d*)$/;
186
+ /** The first object (depth-first) whose own keys are not in sorted order. Read-only walk. */
187
+ function findUnsortedKeys(value, path) {
188
+ if (Array.isArray(value)) {
189
+ for (const [i, item] of value.entries()) {
190
+ const found = findUnsortedKeys(item, `${path}[${String(i)}]`);
191
+ if (found !== undefined)
192
+ return found;
193
+ }
194
+ return undefined;
195
+ }
196
+ if (typeof value !== 'object' || value === null)
197
+ return undefined;
198
+ const record = value;
199
+ const keys = Object.keys(record);
200
+ // Array-index-like keys are excluded from the order check: JavaScript enumerates
201
+ // them first, in ascending numeric order, regardless of how they were written or
202
+ // inserted — so no serializer walking the object can emit them differently
203
+ // between calls, and their fixed numeric order ("2" before "10") is exactly what
204
+ // a lexicographic check would flag. A warning about an order the caller cannot
205
+ // change would be a warning nobody can act on.
206
+ const reorderable = keys.filter((key) => !INTEGER_LIKE_KEY.test(key));
207
+ for (let i = 1; i < reorderable.length; i += 1) {
208
+ if (reorderable[i - 1] > reorderable[i]) {
209
+ return { path, before: reorderable[i - 1], after: reorderable[i] };
210
+ }
211
+ }
212
+ for (const key of keys) {
213
+ const found = findUnsortedKeys(record[key], `${path}.${key}`);
214
+ if (found !== undefined)
215
+ return found;
216
+ }
217
+ return undefined;
218
+ }
219
+ function scanToolKeyOrder(tools) {
220
+ const warnings = [];
221
+ for (const tool of tools) {
222
+ const found = findUnsortedKeys(tool, `tool "${tool.name}"`);
223
+ if (found !== undefined) {
224
+ warnings.push({
225
+ rule: CACHE_BREAKER_RULES.unsortedJsonKeys,
226
+ explanation: `${found.path} has JSON keys out of sorted order ("${found.before}" before ` +
227
+ `"${found.after}"); a serializer with no canonical key order can emit them ` +
228
+ `differently between calls, changing bytes in the cached prefix with the ` +
229
+ `content unchanged`,
230
+ });
231
+ }
232
+ }
233
+ return warnings;
234
+ }
235
+ const quoteNames = (names) => names.map((name) => `"${name}"`).join(', ');
236
+ /**
237
+ * Stable canonical serialization of one value, for comparing tool definitions
238
+ * between calls the way the cache experiences them: by content. Keys are sorted at
239
+ * every depth, so the comparison is independent of enumeration order — an
240
+ * *ordering* a serializer might vary is `unsorted-json-keys`' concern — while any
241
+ * content change at all (a description edit, a schema tweak, a new field) compares
242
+ * different even when every tool name is unchanged.
243
+ */
244
+ function canonicalize(value) {
245
+ if (Array.isArray(value))
246
+ return `[${value.map((item) => canonicalize(item)).join(',')}]`;
247
+ if (typeof value === 'object' && value !== null) {
248
+ const record = value;
249
+ const body = Object.keys(record)
250
+ .toSorted()
251
+ .map((key) => `${JSON.stringify(key)}:${canonicalize(record[key])}`)
252
+ .join(',');
253
+ return `{${body}}`;
254
+ }
255
+ return JSON.stringify(value) ?? 'undefined';
256
+ }
257
+ /** How many times each tool name appears — duplicates are counted, not collapsed. */
258
+ function countByName(tools) {
259
+ const counts = new Map();
260
+ for (const tool of tools)
261
+ counts.set(tool.name, (counts.get(tool.name) ?? 0) + 1);
262
+ return counts;
263
+ }
264
+ /**
265
+ * Describe a count change for one name truthfully. When the name also exists on
266
+ * the other side, what changed is the number of copies — saying `added "x"` about
267
+ * a duplicate, or claiming "same tools" when a duplicate went away, would
268
+ * misstate what the cache saw.
269
+ */
270
+ function describeCopies(name, delta, otherSideHasIt) {
271
+ if (!otherSideHasIt)
272
+ return delta === 1 ? `"${name}"` : `${String(delta)} copies of "${name}"`;
273
+ return delta === 1 ? `a duplicate of "${name}"` : `${String(delta)} duplicates of "${name}"`;
274
+ }
275
+ function compareToolSets(previous, current) {
276
+ // Content comparison, not name comparison: the cache matches serialized bytes,
277
+ // so a rewritten description under an unchanged name breaks it just as surely as
278
+ // a renamed tool does.
279
+ const previousCanonical = previous.map((tool) => canonicalize(tool));
280
+ const currentCanonical = current.map((tool) => canonicalize(tool));
281
+ const identical = previousCanonical.length === currentCanonical.length &&
282
+ previousCanonical.every((tool, i) => tool === currentCanonical[i]);
283
+ if (identical)
284
+ return undefined;
285
+ const previousCounts = countByName(previous);
286
+ const currentCounts = countByName(current);
287
+ const added = [];
288
+ for (const [name, count] of currentCounts) {
289
+ const delta = count - (previousCounts.get(name) ?? 0);
290
+ if (delta > 0)
291
+ added.push(describeCopies(name, delta, previousCounts.has(name)));
292
+ }
293
+ const removed = [];
294
+ for (const [name, count] of previousCounts) {
295
+ const delta = count - (currentCounts.get(name) ?? 0);
296
+ if (delta > 0)
297
+ removed.push(describeCopies(name, delta, currentCounts.has(name)));
298
+ }
299
+ const changes = [];
300
+ if (added.length > 0)
301
+ changes.push(`added ${added.join(', ')}`);
302
+ if (removed.length > 0)
303
+ changes.push(`removed ${removed.join(', ')}`);
304
+ if (changes.length === 0) {
305
+ // Every name appears the same number of times on both sides, so what differs
306
+ // is order, content, or both.
307
+ const sameNameOrder = previous.length === current.length &&
308
+ previous.every((tool, i) => tool.name === current[i].name);
309
+ const sameContent = previousCanonical.toSorted().join('') === currentCanonical.toSorted().join('');
310
+ if (!sameNameOrder) {
311
+ changes.push(sameContent
312
+ ? 'same tools, different order'
313
+ : 'same tool names in a different order, with definitions changed');
314
+ }
315
+ else {
316
+ const changed = [
317
+ ...new Set(current
318
+ .filter((_tool, i) => currentCanonical[i] !== previousCanonical[i])
319
+ .map((tool) => tool.name)),
320
+ ];
321
+ const plural = changed.length === 1 ? '' : 's';
322
+ changes.push(`definition${plural} of ${quoteNames(changed)} changed with the name${plural} unchanged`);
323
+ }
324
+ }
325
+ return {
326
+ rule: CACHE_BREAKER_RULES.toolSetVaries,
327
+ explanation: `tool set changed between calls (${changes.join('; ')}); tools serialize first in ` +
328
+ `the cached prefix (${PREFIX_ORDER_PROSE}), so this invalidates the entire ` +
329
+ `cache including the system prompt and every message`,
330
+ };
331
+ }
332
+ /**
333
+ * Detect the named silent cache-breakers in a prompt structure.
334
+ *
335
+ * Three rules over `current` and, when `previous` is supplied, one across the pair:
336
+ *
337
+ * - `system-timestamp` — a timestamp-shaped value in the system prompt
338
+ * - `system-uuid` — a UUID in the system prompt
339
+ * - `unsorted-json-keys` — a tool-definition object whose keys are not sorted
340
+ * - `tool-set-varies` — the tools differ from the previous call's, by content:
341
+ * an added, removed or duplicated tool, a reorder, or a definition rewritten
342
+ * under an unchanged name
343
+ *
344
+ * Pure and read-only: inputs are never mutated, and the return value is warnings
345
+ * only — never a corrected prompt. Deciding what to do about a warning is the
346
+ * caller's call, in the caller's code.
347
+ */
348
+ export function detectCacheBreakers(current, previous) {
349
+ const warnings = [];
350
+ if (current.system !== undefined)
351
+ warnings.push(...scanSystemPrompt(current.system));
352
+ if (current.tools !== undefined)
353
+ warnings.push(...scanToolKeyOrder(current.tools));
354
+ if (previous !== undefined) {
355
+ const varies = compareToolSets(previous.tools ?? [], current.tools ?? []);
356
+ if (varies !== undefined)
357
+ warnings.push(varies);
358
+ }
359
+ return warnings;
360
+ }
361
+ //# sourceMappingURL=prefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefix.js","sourceRoot":"","sources":["../../src/cache/prefix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { Strategy } from '../plan/planners.ts';
2
+ import type { DetectedLanguage } from '../types.ts';
3
+ /** The command people type. Independent of the package name. */
4
+ export declare const CLI_NAME = "smelt";
5
+ /** What the CLI was asked to do. Pure data, so the parse is testable on its own. */
6
+ export interface SmeltInvocation {
7
+ readonly mode: 'smelt' | 'reconstruct' | 'help' | 'version' | 'init';
8
+ /** Path to read. `undefined` means stdin. */
9
+ readonly file?: string;
10
+ /**
11
+ * UTF-8 bytes. `undefined` in `'smelt'` mode means the flag was not given; the
12
+ * runner then consults `smelt.config.json` and errors if that has no default either.
13
+ */
14
+ readonly budgetBytes?: number;
15
+ readonly focus: readonly string[];
16
+ readonly language?: DetectedLanguage;
17
+ /** `undefined` means the flag was not given — the config default may apply. */
18
+ readonly strategy?: Strategy;
19
+ readonly json: boolean;
20
+ }
21
+ /**
22
+ * `smelt map <dir>` — the repo-map subcommand, parsed. A separate shape rather than
23
+ * more optional fields on {@link SmeltInvocation}, because the two commands share
24
+ * almost nothing: a map has a directory instead of a file/stdin, an ignore list and
25
+ * a cache directory instead of a language and a strategy.
26
+ */
27
+ export interface MapInvocation {
28
+ readonly mode: 'map';
29
+ /** The repository root to map. Always present — `map` without a directory is a usage error. */
30
+ readonly dir: string;
31
+ /** `undefined` means the flag was not given — the config default may apply. */
32
+ readonly budgetBytes?: number;
33
+ readonly focus: readonly string[];
34
+ /** `--ignore` entries, replacing the built-in default list when non-empty. */
35
+ readonly ignore: readonly string[];
36
+ /** `--cache <dir>`: only when given does the map write to disk. */
37
+ readonly cacheDir?: string;
38
+ readonly json: boolean;
39
+ }
40
+ /**
41
+ * `smelt retrieve <hash>` — the marker's `retrieve("hash")` as a real command, parsed.
42
+ * A {@link MapInvocation}-style sibling: nothing but the hash, because the command's
43
+ * whole contract is "hash in, exact bytes out" — the same contract as the
44
+ * `smelt_retrieve` tool, reachable from a shell.
45
+ */
46
+ export interface RetrieveInvocation {
47
+ readonly mode: 'retrieve';
48
+ /** The hash exactly as the marker printed it. Validated by the store, not here. */
49
+ readonly hash: string;
50
+ }
51
+ /** `smelt stats` — the store's counters, read without touching them. */
52
+ export interface StatsInvocation {
53
+ readonly mode: 'stats';
54
+ readonly json: boolean;
55
+ }
56
+ /** Everything `parseSmeltArgs` can return. Narrow on `mode`. */
57
+ export type CliInvocation = SmeltInvocation | MapInvocation | RetrieveInvocation | StatsInvocation;
58
+ /**
59
+ * Argument parsing on `node:util.parseArgs` — stable since Node 20, which `engines`
60
+ * already requires.
61
+ *
62
+ * The CLI ships as a `bin` on the library rather than as a second package, and this
63
+ * import is the reason that is free: it adds no dependency, so the argument the second
64
+ * package existed to win — keeping the library's dependency tree small — is already won.
65
+ *
66
+ * @throws {CliUsageError} on anything the user got wrong. Never guesses.
67
+ */
68
+ export declare function parseSmeltArgs(argv: readonly string[]): CliInvocation;
69
+ /** The help text. Also the closest thing the CLI has to documentation. */
70
+ export declare function cliUsage(): string;
71
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,gEAAgE;AAChE,eAAO,MAAM,QAAQ,UAAU,CAAC;AAEhC,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IACrE,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,+FAA+F;IAC/F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,gEAAgE;AAChE,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,aAAa,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAEnG;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CA0GrE;AAuJD,0EAA0E;AAC1E,wBAAgB,QAAQ,IAAI,MAAM,CA8FjC"}