@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.
- package/LICENSE +202 -0
- package/README.md +55 -0
- package/THIRD-PARTY.md +140 -0
- package/dist/apply.d.ts +145 -0
- package/dist/apply.d.ts.map +1 -0
- package/dist/apply.js +244 -0
- package/dist/apply.js.map +1 -0
- package/dist/cache/prefix.d.ts +164 -0
- package/dist/cache/prefix.d.ts.map +1 -0
- package/dist/cache/prefix.js +361 -0
- package/dist/cache/prefix.js.map +1 -0
- package/dist/cli/args.d.ts +71 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +308 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +161 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/config.d.ts +73 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +144 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/init.d.ts +56 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +449 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/report.d.ts +44 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +113 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/resolve.d.ts +103 -0
- package/dist/cli/resolve.d.ts.map +1 -0
- package/dist/cli/resolve.js +106 -0
- package/dist/cli/resolve.js.map +1 -0
- package/dist/cli/run.d.ts +99 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +351 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/detect.d.ts +16 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +18 -0
- package/dist/detect.js.map +1 -0
- package/dist/errors.d.ts +88 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +107 -0
- package/dist/errors.js.map +1 -0
- package/dist/hash.d.ts +13 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +16 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/lang/bash.d.ts +3 -0
- package/dist/lang/bash.d.ts.map +1 -0
- package/dist/lang/bash.js +28 -0
- package/dist/lang/bash.js.map +1 -0
- package/dist/lang/c.d.ts +3 -0
- package/dist/lang/c.d.ts.map +1 -0
- package/dist/lang/c.js +39 -0
- package/dist/lang/c.js.map +1 -0
- package/dist/lang/c_sharp.d.ts +3 -0
- package/dist/lang/c_sharp.d.ts.map +1 -0
- package/dist/lang/c_sharp.js +37 -0
- package/dist/lang/c_sharp.js.map +1 -0
- package/dist/lang/cpp.d.ts +3 -0
- package/dist/lang/cpp.d.ts.map +1 -0
- package/dist/lang/cpp.js +46 -0
- package/dist/lang/cpp.js.map +1 -0
- package/dist/lang/go.d.ts +3 -0
- package/dist/lang/go.d.ts.map +1 -0
- package/dist/lang/go.js +31 -0
- package/dist/lang/go.js.map +1 -0
- package/dist/lang/java.d.ts +3 -0
- package/dist/lang/java.d.ts.map +1 -0
- package/dist/lang/java.js +34 -0
- package/dist/lang/java.js.map +1 -0
- package/dist/lang/javascript.d.ts +3 -0
- package/dist/lang/javascript.d.ts.map +1 -0
- package/dist/lang/javascript.js +28 -0
- package/dist/lang/javascript.js.map +1 -0
- package/dist/lang/kotlin.d.ts +3 -0
- package/dist/lang/kotlin.d.ts.map +1 -0
- package/dist/lang/kotlin.js +37 -0
- package/dist/lang/kotlin.js.map +1 -0
- package/dist/lang/php.d.ts +3 -0
- package/dist/lang/php.d.ts.map +1 -0
- package/dist/lang/php.js +37 -0
- package/dist/lang/php.js.map +1 -0
- package/dist/lang/profile.d.ts +130 -0
- package/dist/lang/profile.d.ts.map +1 -0
- package/dist/lang/profile.js +2 -0
- package/dist/lang/profile.js.map +1 -0
- package/dist/lang/python.d.ts +3 -0
- package/dist/lang/python.d.ts.map +1 -0
- package/dist/lang/python.js +33 -0
- package/dist/lang/python.js.map +1 -0
- package/dist/lang/registry.d.ts +31 -0
- package/dist/lang/registry.d.ts.map +1 -0
- package/dist/lang/registry.js +89 -0
- package/dist/lang/registry.js.map +1 -0
- package/dist/lang/ruby.d.ts +3 -0
- package/dist/lang/ruby.d.ts.map +1 -0
- package/dist/lang/ruby.js +47 -0
- package/dist/lang/ruby.js.map +1 -0
- package/dist/lang/rust.d.ts +3 -0
- package/dist/lang/rust.d.ts.map +1 -0
- package/dist/lang/rust.js +45 -0
- package/dist/lang/rust.js.map +1 -0
- package/dist/lang/swift.d.ts +3 -0
- package/dist/lang/swift.d.ts.map +1 -0
- package/dist/lang/swift.js +34 -0
- package/dist/lang/swift.js.map +1 -0
- package/dist/lang/tsx.d.ts +4 -0
- package/dist/lang/tsx.d.ts.map +1 -0
- package/dist/lang/tsx.js +11 -0
- package/dist/lang/tsx.js.map +1 -0
- package/dist/lang/typescript.d.ts +18 -0
- package/dist/lang/typescript.d.ts.map +1 -0
- package/dist/lang/typescript.js +61 -0
- package/dist/lang/typescript.js.map +1 -0
- package/dist/net/policy.d.ts +49 -0
- package/dist/net/policy.d.ts.map +1 -0
- package/dist/net/policy.js +120 -0
- package/dist/net/policy.js.map +1 -0
- package/dist/plan/grammar.d.ts +38 -0
- package/dist/plan/grammar.d.ts.map +1 -0
- package/dist/plan/grammar.js +85 -0
- package/dist/plan/grammar.js.map +1 -0
- package/dist/plan/lexical.d.ts +51 -0
- package/dist/plan/lexical.d.ts.map +1 -0
- package/dist/plan/lexical.js +167 -0
- package/dist/plan/lexical.js.map +1 -0
- package/dist/plan/planners.d.ts +35 -0
- package/dist/plan/planners.d.ts.map +1 -0
- package/dist/plan/planners.js +26 -0
- package/dist/plan/planners.js.map +1 -0
- package/dist/plan/structural.d.ts +68 -0
- package/dist/plan/structural.d.ts.map +1 -0
- package/dist/plan/structural.js +458 -0
- package/dist/plan/structural.js.map +1 -0
- package/dist/repomap/cache.d.ts +53 -0
- package/dist/repomap/cache.d.ts.map +1 -0
- package/dist/repomap/cache.js +139 -0
- package/dist/repomap/cache.js.map +1 -0
- package/dist/repomap/map.d.ts +161 -0
- package/dist/repomap/map.d.ts.map +1 -0
- package/dist/repomap/map.js +302 -0
- package/dist/repomap/map.js.map +1 -0
- package/dist/repomap/rank.d.ts +52 -0
- package/dist/repomap/rank.d.ts.map +1 -0
- package/dist/repomap/rank.js +155 -0
- package/dist/repomap/rank.js.map +1 -0
- package/dist/repomap/tags.d.ts +47 -0
- package/dist/repomap/tags.d.ts.map +1 -0
- package/dist/repomap/tags.js +101 -0
- package/dist/repomap/tags.js.map +1 -0
- package/dist/retrieve.d.ts +14 -0
- package/dist/retrieve.d.ts.map +1 -0
- package/dist/retrieve.js +44 -0
- package/dist/retrieve.js.map +1 -0
- package/dist/stages.d.ts +22 -0
- package/dist/stages.d.ts.map +1 -0
- package/dist/stages.js +33 -0
- package/dist/stages.js.map +1 -0
- package/dist/stats.d.ts +40 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +37 -0
- package/dist/stats.js.map +1 -0
- package/dist/store-dir.d.ts +98 -0
- package/dist/store-dir.d.ts.map +1 -0
- package/dist/store-dir.js +379 -0
- package/dist/store-dir.js.map +1 -0
- package/dist/store.d.ts +36 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +56 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +298 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- 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"}
|