@shrkcrft/compress 0.1.0-alpha.16

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 (162) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/dist/cache/align-volatile-tokens.d.ts +13 -0
  4. package/dist/cache/align-volatile-tokens.d.ts.map +1 -0
  5. package/dist/cache/align-volatile-tokens.js +51 -0
  6. package/dist/cache/alignment-map.d.ts +23 -0
  7. package/dist/cache/alignment-map.d.ts.map +1 -0
  8. package/dist/cache/alignment-map.js +1 -0
  9. package/dist/cache/alignment-result.d.ts +11 -0
  10. package/dist/cache/alignment-result.d.ts.map +1 -0
  11. package/dist/cache/alignment-result.js +1 -0
  12. package/dist/cache/detect-volatile-tokens.d.ts +10 -0
  13. package/dist/cache/detect-volatile-tokens.d.ts.map +1 -0
  14. package/dist/cache/detect-volatile-tokens.js +41 -0
  15. package/dist/cache/placeholder.d.ts +28 -0
  16. package/dist/cache/placeholder.d.ts.map +1 -0
  17. package/dist/cache/placeholder.js +0 -0
  18. package/dist/cache/restore-volatile-tokens.d.ts +10 -0
  19. package/dist/cache/restore-volatile-tokens.d.ts.map +1 -0
  20. package/dist/cache/restore-volatile-tokens.js +21 -0
  21. package/dist/cache/volatile-classify.d.ts +11 -0
  22. package/dist/cache/volatile-classify.d.ts.map +1 -0
  23. package/dist/cache/volatile-classify.js +35 -0
  24. package/dist/cache/volatile-kind.d.ts +13 -0
  25. package/dist/cache/volatile-kind.d.ts.map +1 -0
  26. package/dist/cache/volatile-kind.js +13 -0
  27. package/dist/cache/volatile-token.d.ts +14 -0
  28. package/dist/cache/volatile-token.d.ts.map +1 -0
  29. package/dist/cache/volatile-token.js +1 -0
  30. package/dist/ccr/ccr-entry.d.ts +13 -0
  31. package/dist/ccr/ccr-entry.d.ts.map +1 -0
  32. package/dist/ccr/ccr-entry.js +1 -0
  33. package/dist/ccr/ccr-key.d.ts +9 -0
  34. package/dist/ccr/ccr-key.d.ts.map +1 -0
  35. package/dist/ccr/ccr-key.js +19 -0
  36. package/dist/ccr/ccr-marker.d.ts +23 -0
  37. package/dist/ccr/ccr-marker.d.ts.map +1 -0
  38. package/dist/ccr/ccr-marker.js +30 -0
  39. package/dist/ccr/ccr-store.d.ts +18 -0
  40. package/dist/ccr/ccr-store.d.ts.map +1 -0
  41. package/dist/ccr/ccr-store.js +1 -0
  42. package/dist/ccr/file-ccr-store.d.ts +19 -0
  43. package/dist/ccr/file-ccr-store.d.ts.map +1 -0
  44. package/dist/ccr/file-ccr-store.js +53 -0
  45. package/dist/ccr/in-memory-ccr-store.d.ts +21 -0
  46. package/dist/ccr/in-memory-ccr-store.d.ts.map +1 -0
  47. package/dist/ccr/in-memory-ccr-store.js +45 -0
  48. package/dist/ccr/ttl-file-ccr-store.d.ts +43 -0
  49. package/dist/ccr/ttl-file-ccr-store.d.ts.map +1 -0
  50. package/dist/ccr/ttl-file-ccr-store.js +117 -0
  51. package/dist/code/compress-code.d.ts +4 -0
  52. package/dist/code/compress-code.d.ts.map +1 -0
  53. package/dist/code/compress-code.js +294 -0
  54. package/dist/compress-content.d.ts +11 -0
  55. package/dist/compress-content.d.ts.map +1 -0
  56. package/dist/compress-content.js +79 -0
  57. package/dist/content/content-type.d.ts +28 -0
  58. package/dist/content/content-type.d.ts.map +1 -0
  59. package/dist/content/content-type.js +28 -0
  60. package/dist/content/detect-content-type.d.ts +9 -0
  61. package/dist/content/detect-content-type.d.ts.map +1 -0
  62. package/dist/content/detect-content-type.js +184 -0
  63. package/dist/content/segment.d.ts +21 -0
  64. package/dist/content/segment.d.ts.map +1 -0
  65. package/dist/content/segment.js +117 -0
  66. package/dist/index.d.ts +61 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +49 -0
  69. package/dist/json/compress-json.d.ts +18 -0
  70. package/dist/json/compress-json.d.ts.map +1 -0
  71. package/dist/json/compress-json.js +139 -0
  72. package/dist/json/render-compact-json.d.ts +10 -0
  73. package/dist/json/render-compact-json.d.ts.map +1 -0
  74. package/dist/json/render-compact-json.js +18 -0
  75. package/dist/relevance/bm25.d.ts +26 -0
  76. package/dist/relevance/bm25.d.ts.map +1 -0
  77. package/dist/relevance/bm25.js +115 -0
  78. package/dist/result/compress-options.d.ts +26 -0
  79. package/dist/result/compress-options.d.ts.map +1 -0
  80. package/dist/result/compress-options.js +1 -0
  81. package/dist/result/compression-result.d.ts +26 -0
  82. package/dist/result/compression-result.d.ts.map +1 -0
  83. package/dist/result/compression-result.js +1 -0
  84. package/dist/result/compression-strategy.d.ts +30 -0
  85. package/dist/result/compression-strategy.d.ts.map +1 -0
  86. package/dist/result/compression-strategy.js +30 -0
  87. package/dist/table/adaptive-size.d.ts +46 -0
  88. package/dist/table/adaptive-size.d.ts.map +1 -0
  89. package/dist/table/adaptive-size.js +170 -0
  90. package/dist/table/apply-value-dictionaries.d.ts +30 -0
  91. package/dist/table/apply-value-dictionaries.d.ts.map +1 -0
  92. package/dist/table/apply-value-dictionaries.js +99 -0
  93. package/dist/table/column-presence.d.ts +20 -0
  94. package/dist/table/column-presence.d.ts.map +1 -0
  95. package/dist/table/column-presence.js +52 -0
  96. package/dist/table/columnar-json.d.ts +24 -0
  97. package/dist/table/columnar-json.d.ts.map +1 -0
  98. package/dist/table/columnar-json.js +83 -0
  99. package/dist/table/columnar-table.d.ts +24 -0
  100. package/dist/table/columnar-table.d.ts.map +1 -0
  101. package/dist/table/columnar-table.js +1 -0
  102. package/dist/table/compact-object-array.d.ts +12 -0
  103. package/dist/table/compact-object-array.d.ts.map +1 -0
  104. package/dist/table/compact-object-array.js +88 -0
  105. package/dist/table/field-spec.d.ts +13 -0
  106. package/dist/table/field-spec.d.ts.map +1 -0
  107. package/dist/table/field-spec.js +1 -0
  108. package/dist/table/object-map.d.ts +28 -0
  109. package/dist/table/object-map.d.ts.map +1 -0
  110. package/dist/table/object-map.js +119 -0
  111. package/dist/table/render-table.d.ts +11 -0
  112. package/dist/table/render-table.d.ts.map +1 -0
  113. package/dist/table/render-table.js +39 -0
  114. package/dist/table/sample-object-array.d.ts +11 -0
  115. package/dist/table/sample-object-array.d.ts.map +1 -0
  116. package/dist/table/sample-object-array.js +171 -0
  117. package/dist/table/sample-options.d.ts +29 -0
  118. package/dist/table/sample-options.d.ts.map +1 -0
  119. package/dist/table/sample-options.js +1 -0
  120. package/dist/table/sampled-table.d.ts +33 -0
  121. package/dist/table/sampled-table.d.ts.map +1 -0
  122. package/dist/table/sampled-table.js +8 -0
  123. package/dist/table/table-compaction.d.ts +19 -0
  124. package/dist/table/table-compaction.d.ts.map +1 -0
  125. package/dist/table/table-compaction.js +1 -0
  126. package/dist/table/table-formats.d.ts +23 -0
  127. package/dist/table/table-formats.d.ts.map +1 -0
  128. package/dist/table/table-formats.js +233 -0
  129. package/dist/text/compress-diff.d.ts +20 -0
  130. package/dist/text/compress-diff.d.ts.map +1 -0
  131. package/dist/text/compress-diff.js +344 -0
  132. package/dist/text/compress-lines.d.ts +12 -0
  133. package/dist/text/compress-lines.d.ts.map +1 -0
  134. package/dist/text/compress-lines.js +44 -0
  135. package/dist/text/compress-log.d.ts +12 -0
  136. package/dist/text/compress-log.d.ts.map +1 -0
  137. package/dist/text/compress-log.js +202 -0
  138. package/dist/text/compress-markdown.d.ts +15 -0
  139. package/dist/text/compress-markdown.d.ts.map +1 -0
  140. package/dist/text/compress-markdown.js +96 -0
  141. package/dist/text/compress-search.d.ts +11 -0
  142. package/dist/text/compress-search.d.ts.map +1 -0
  143. package/dist/text/compress-search.js +78 -0
  144. package/dist/text/finalize.d.ts +21 -0
  145. package/dist/text/finalize.d.ts.map +1 -0
  146. package/dist/text/finalize.js +54 -0
  147. package/dist/text/line-utils.d.ts +20 -0
  148. package/dist/text/line-utils.d.ts.map +1 -0
  149. package/dist/text/line-utils.js +65 -0
  150. package/dist/text/lockfile-names.d.ts +3 -0
  151. package/dist/text/lockfile-names.d.ts.map +1 -0
  152. package/dist/text/lockfile-names.js +33 -0
  153. package/dist/text/log-template.d.ts +31 -0
  154. package/dist/text/log-template.d.ts.map +1 -0
  155. package/dist/text/log-template.js +239 -0
  156. package/dist/tokens/estimate-tokens.d.ts +17 -0
  157. package/dist/tokens/estimate-tokens.d.ts.map +1 -0
  158. package/dist/tokens/estimate-tokens.js +53 -0
  159. package/dist/tokens/token-savings.d.ts +20 -0
  160. package/dist/tokens/token-savings.d.ts.map +1 -0
  161. package/dist/tokens/token-savings.js +1 -0
  162. package/package.json +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SharkCraft contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @shrkcrft/compress
2
+
3
+ SharkCraft's deterministic context-compression engine.
4
+
5
+ Built to honour SharkCraft's hard rule — **no model inside the engine**. Every
6
+ transform is a pure function of its input: content routing, lossless
7
+ columnar/table compaction of homogeneous object arrays, log / search / diff /
8
+ line reduction, and reversible Compress-Cache-Retrieve (CCR).
9
+
10
+ Part of [SharkCraft](https://github.com/shrkcrft/sharkcraft) — a deterministic,
11
+ local-first toolkit that gives AI coding agents durable project context. See
12
+ [`docs/compression.md`](https://github.com/shrkcrft/sharkcraft/blob/main/docs/compression.md)
13
+ for the full guide, and the main repo for the `shrk` CLI and MCP server.
14
+
15
+ ```ts
16
+ import { compressContent, InMemoryCcrStore } from '@shrkcrft/compress';
17
+
18
+ const store = new InMemoryCcrStore();
19
+ const result = compressContent(blob, { store, query: 'auth' });
20
+ // result.compressed · result.strategy · result.savings · result.ccrKey
21
+ ```
@@ -0,0 +1,13 @@
1
+ import type { IAlignmentMap } from './alignment-map.js';
2
+ import type { IAlignmentResult } from './alignment-result.js';
3
+ /**
4
+ * Replace volatile tokens (UUID/JWT/ISO-8601/hex/epoch) with stable, kind-tagged
5
+ * placeholders so a provider KV-cache prefix stays steady across turns.
6
+ * Reversible via `restoreVolatileTokens`. Deterministic and pure: the caller's
7
+ * `prior` map is never mutated (cloned), ordinals are assigned strictly in scan
8
+ * order, and the same `(text, prior)` yields byte-identical output. Shares
9
+ * `clean`/`classify` with `detectVolatileTokens`, so the two never disagree on
10
+ * what is volatile.
11
+ */
12
+ export declare function alignVolatileTokens(text: string, prior?: IAlignmentMap): IAlignmentResult;
13
+ //# sourceMappingURL=align-volatile-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align-volatile-tokens.d.ts","sourceRoot":"","sources":["../../src/cache/align-volatile-tokens.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,gBAAgB,CAsCzF"}
@@ -0,0 +1,51 @@
1
+ import { clean, classify, MIN_VOLATILE_LEN } from "./volatile-classify.js";
2
+ import { formatPlaceholder, escapePlaceholders } from "./placeholder.js";
3
+ /**
4
+ * Replace volatile tokens (UUID/JWT/ISO-8601/hex/epoch) with stable, kind-tagged
5
+ * placeholders so a provider KV-cache prefix stays steady across turns.
6
+ * Reversible via `restoreVolatileTokens`. Deterministic and pure: the caller's
7
+ * `prior` map is never mutated (cloned), ordinals are assigned strictly in scan
8
+ * order, and the same `(text, prior)` yields byte-identical output. Shares
9
+ * `clean`/`classify` with `detectVolatileTokens`, so the two never disagree on
10
+ * what is volatile.
11
+ */
12
+ export function alignVolatileTokens(text, prior) {
13
+ const bindings = prior ? prior.bindings.map((b) => ({ ...b })) : [];
14
+ const byOriginal = new Map();
15
+ const nextOrdinal = new Map();
16
+ for (const b of bindings) {
17
+ byOriginal.set(b.original, b);
18
+ nextOrdinal.set(b.kind, Math.max(nextOrdinal.get(b.kind) ?? 0, b.ordinal));
19
+ }
20
+ let replaced = 0;
21
+ // Escape any pre-existing `«vk:…»`-shaped literal so a generated placeholder
22
+ // can never collide with content that was already there (restore stays exact).
23
+ const safe = escapePlaceholders(text);
24
+ // Split keeping whitespace runs so they survive verbatim.
25
+ const aligned = safe
26
+ .split(/(\s+)/)
27
+ .map((segment) => {
28
+ if (segment.length === 0 || /^\s+$/.test(segment))
29
+ return segment;
30
+ const cleaned = clean(segment);
31
+ if (cleaned.length < MIN_VOLATILE_LEN)
32
+ return segment;
33
+ const kind = classify(cleaned);
34
+ if (!kind)
35
+ return segment;
36
+ let binding = byOriginal.get(cleaned);
37
+ if (!binding) {
38
+ const ordinal = (nextOrdinal.get(kind) ?? 0) + 1;
39
+ nextOrdinal.set(kind, ordinal);
40
+ binding = { placeholder: formatPlaceholder(kind, ordinal), original: cleaned, kind, ordinal };
41
+ bindings.push(binding);
42
+ byOriginal.set(cleaned, binding);
43
+ }
44
+ replaced += 1;
45
+ // Replace the cleaned span inside the raw segment so wrappers like
46
+ // `"<uuid>",` keep their surrounding quotes/punctuation.
47
+ return segment.replace(cleaned, binding.placeholder);
48
+ })
49
+ .join('');
50
+ return { aligned, map: { version: 1, bindings }, replaced };
51
+ }
@@ -0,0 +1,23 @@
1
+ import type { EVolatileKind } from './volatile-kind.js';
2
+ /** One original-value ↔ placeholder binding. */
3
+ export interface IAlignmentBinding {
4
+ /** `«vk:uuid:0001»`. */
5
+ placeholder: string;
6
+ /** The cleaned original token (no surrounding quotes/punctuation). */
7
+ original: string;
8
+ kind: EVolatileKind;
9
+ /** Per-kind ordinal (first-appearance order). */
10
+ ordinal: number;
11
+ }
12
+ /**
13
+ * The reversible alignment map. Append-only, first-appearance order, plain JSON
14
+ * so it serialises for the CLI and travels in MCP tool I/O. Carry it across
15
+ * turns so a value keeps its placeholder — that carry-forward is what stabilises
16
+ * the cache prefix. Per-kind next ordinal is `1 + max(ordinal of that kind)`,
17
+ * a pure function of `bindings` (no hidden counter).
18
+ */
19
+ export interface IAlignmentMap {
20
+ version: 1;
21
+ bindings: IAlignmentBinding[];
22
+ }
23
+ //# sourceMappingURL=alignment-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment-map.d.ts","sourceRoot":"","sources":["../../src/cache/alignment-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IAChC,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,aAAa,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { IAlignmentMap } from './alignment-map.js';
2
+ /** The outcome of an alignment pass. */
3
+ export interface IAlignmentResult {
4
+ /** Text with volatile tokens replaced by stable placeholders. */
5
+ aligned: string;
6
+ /** The (new) alignment map — pass it back next turn to keep ordinals stable. */
7
+ map: IAlignmentMap;
8
+ /** Number of token occurrences replaced. */
9
+ replaced: number;
10
+ }
11
+ //# sourceMappingURL=alignment-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment-result.d.ts","sourceRoot":"","sources":["../../src/cache/alignment-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,GAAG,EAAE,aAAa,CAAC;IACnB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { IVolatileToken } from './volatile-token.js';
2
+ /**
3
+ * Scan text for volatile tokens that would bust a provider KV-cache prefix.
4
+ * Deterministic, detection-only (never mutates). Returns one aggregated entry
5
+ * per kind found, in a stable kind order, with an occurrence count and a
6
+ * truncated sample. Shares its `clean`/`classify` with `alignVolatileTokens`
7
+ * so the two never disagree.
8
+ */
9
+ export declare function detectVolatileTokens(text: string): IVolatileToken[];
10
+ //# sourceMappingURL=detect-volatile-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-volatile-tokens.d.ts","sourceRoot":"","sources":["../../src/cache/detect-volatile-tokens.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CA0BnE"}
@@ -0,0 +1,41 @@
1
+ import { EVolatileKind } from "./volatile-kind.js";
2
+ import { clean, classify, MIN_VOLATILE_LEN } from "./volatile-classify.js";
3
+ /**
4
+ * Scan text for volatile tokens that would bust a provider KV-cache prefix.
5
+ * Deterministic, detection-only (never mutates). Returns one aggregated entry
6
+ * per kind found, in a stable kind order, with an occurrence count and a
7
+ * truncated sample. Shares its `clean`/`classify` with `alignVolatileTokens`
8
+ * so the two never disagree.
9
+ */
10
+ export function detectVolatileTokens(text) {
11
+ const counts = new Map();
12
+ const samples = new Map();
13
+ for (const raw of text.split(/\s+/)) {
14
+ if (raw.length === 0)
15
+ continue;
16
+ const token = clean(raw);
17
+ if (token.length < MIN_VOLATILE_LEN)
18
+ continue;
19
+ const kind = classify(token);
20
+ if (!kind)
21
+ continue;
22
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
23
+ if (!samples.has(kind))
24
+ samples.set(kind, token.slice(0, 24));
25
+ }
26
+ // Stable kind order so output is deterministic regardless of scan order.
27
+ const order = [
28
+ EVolatileKind.Uuid,
29
+ EVolatileKind.Jwt,
30
+ EVolatileKind.Iso8601,
31
+ EVolatileKind.HexHash,
32
+ EVolatileKind.EpochTimestamp,
33
+ ];
34
+ const out = [];
35
+ for (const kind of order) {
36
+ const count = counts.get(kind);
37
+ if (count)
38
+ out.push({ kind, count, sample: samples.get(kind) ?? '' });
39
+ }
40
+ return out;
41
+ }
@@ -0,0 +1,28 @@
1
+ import type { EVolatileKind } from './volatile-kind.js';
2
+ /**
3
+ * Matches a volatile-token placeholder: `«vk:KIND:NNNN»`. The `«…»` guillemets
4
+ * essentially never occur in code/logs/JSON, so the placeholder is unambiguous
5
+ * to scan back out. Global so a restore pass finds every one.
6
+ */
7
+ export declare const PLACEHOLDER_RE: RegExp;
8
+ /**
9
+ * Render a stable, self-describing placeholder. The 4-wide zero-padded ordinal
10
+ * keeps the placeholder a constant length (for the first 9999 of a kind), which
11
+ * is what keeps surrounding token offsets — and thus a provider KV-cache prefix
12
+ * — stable across turns.
13
+ */
14
+ export declare function formatPlaceholder(kind: EVolatileKind, ordinal: number): string;
15
+ /**
16
+ * The raw placeholder prefix and an escaped form that a NUL breaks so it can
17
+ * never match `PLACEHOLDER_RE`. Used to escape any pre-existing `«vk:…»`-shaped
18
+ * literal in the source BEFORE alignment, so a generated placeholder can never
19
+ * collide with content that was already there (the round-trip stays lossless
20
+ * even for input that literally contains a placeholder).
21
+ */
22
+ export declare const PLACEHOLDER_PREFIX = "\u00ABvk:";
23
+ export declare const ESCAPED_PREFIX = "\u00ABvk\0:";
24
+ /** Escape pre-existing placeholder-shaped prefixes in the source. */
25
+ export declare function escapePlaceholders(text: string): string;
26
+ /** Inverse of {@link escapePlaceholders}; applied after restore. */
27
+ export declare function unescapePlaceholders(text: string): string;
28
+ //# sourceMappingURL=placeholder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholder.d.ts","sourceRoot":"","sources":["../../src/cache/placeholder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,cAAc,QAAgC,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,cAAS,CAAC;AACzC,eAAO,MAAM,cAAc,gBAAU,CAAC;AAEtC,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,oEAAoE;AACpE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIzD"}
Binary file
@@ -0,0 +1,10 @@
1
+ import type { IAlignmentMap } from './alignment-map.js';
2
+ /**
3
+ * Reverse `alignVolatileTokens`: replace every `«vk:…»` placeholder with its
4
+ * original value from the map. Unknown placeholders pass through untouched
5
+ * (total, never throws). Property:
6
+ * `restoreVolatileTokens(alignVolatileTokens(t).aligned, map) === t` for any `t`
7
+ * whose volatile tokens were cleanly delimited (lossless-via-restore).
8
+ */
9
+ export declare function restoreVolatileTokens(text: string, map: IAlignmentMap): string;
10
+ //# sourceMappingURL=restore-volatile-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore-volatile-tokens.d.ts","sourceRoot":"","sources":["../../src/cache/restore-volatile-tokens.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE3E;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAiB9E"}
@@ -0,0 +1,21 @@
1
+ import { PLACEHOLDER_RE, unescapePlaceholders } from "./placeholder.js";
2
+ /**
3
+ * Reverse `alignVolatileTokens`: replace every `«vk:…»` placeholder with its
4
+ * original value from the map. Unknown placeholders pass through untouched
5
+ * (total, never throws). Property:
6
+ * `restoreVolatileTokens(alignVolatileTokens(t).aligned, map) === t` for any `t`
7
+ * whose volatile tokens were cleanly delimited (lossless-via-restore).
8
+ */
9
+ export function restoreVolatileTokens(text, map) {
10
+ // Defensive: a corrupt/hand-edited map may carry a non-object (or
11
+ // placeholder-less) element in `bindings[]` that still passes a shallow
12
+ // `Array.isArray` validation. Skip those so the documented "never throws"
13
+ // guarantee holds rather than dereferencing `b.placeholder` on `null`.
14
+ const byPlaceholder = new Map((Array.isArray(map.bindings) ? map.bindings : [])
15
+ .filter((b) => b != null && typeof b === 'object' && typeof b.placeholder === 'string')
16
+ .map((b) => [b.placeholder, b.original]));
17
+ // Revert generated placeholders, then unescape any literal `«vk:…»` that
18
+ // align escaped — so the round trip is exact even for placeholder-shaped input.
19
+ const reverted = text.replace(PLACEHOLDER_RE, (match) => byPlaceholder.get(match) ?? match);
20
+ return unescapePlaceholders(reverted);
21
+ }
@@ -0,0 +1,11 @@
1
+ import { EVolatileKind } from './volatile-kind.js';
2
+ /** Minimum cleaned-token length before classification is attempted. */
3
+ export declare const MIN_VOLATILE_LEN = 8;
4
+ /**
5
+ * Strip surrounding quotes / brackets / punctuation a token tends to carry.
6
+ * Shared by both detection and active alignment so they never drift.
7
+ */
8
+ export declare function clean(token: string): string;
9
+ /** Classify a CLEANED token into a volatile kind, or null. */
10
+ export declare function classify(token: string): EVolatileKind | null;
11
+ //# sourceMappingURL=volatile-classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volatile-classify.d.ts","sourceRoot":"","sources":["../../src/cache/volatile-classify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAenD,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAO5D"}
@@ -0,0 +1,35 @@
1
+ import { EVolatileKind } from "./volatile-kind.js";
2
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+ // A JWT's first segment is base64url of a `{"alg":...}` header, which always
4
+ // starts with `eyJ` — require it so ordinary dotted identifiers
5
+ // (`config.settings.default`, `lodash.debounce.cancel`) aren't flagged.
6
+ const JWT = /^eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{4,}$/;
7
+ const ISO8601 = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
8
+ const HEX_HASH = /^(?:[0-9a-f]{32}|[0-9a-f]{40}|[0-9a-f]{64})$/i;
9
+ // Plausible unix epochs only (leading `1`: ~2001–2033 in seconds, ~2001–2286 in
10
+ // millis) so phone numbers / arbitrary IDs (`5551234567`, `9999999999`) don't
11
+ // register as timestamps.
12
+ const EPOCH = /^1\d{9}$|^1\d{12}$/;
13
+ /** Minimum cleaned-token length before classification is attempted. */
14
+ export const MIN_VOLATILE_LEN = 8;
15
+ /**
16
+ * Strip surrounding quotes / brackets / punctuation a token tends to carry.
17
+ * Shared by both detection and active alignment so they never drift.
18
+ */
19
+ export function clean(token) {
20
+ return token.replace(/^[("'[<{]+/, '').replace(/[)"'\]>}.,;:]+$/, '');
21
+ }
22
+ /** Classify a CLEANED token into a volatile kind, or null. */
23
+ export function classify(token) {
24
+ if (UUID.test(token))
25
+ return EVolatileKind.Uuid;
26
+ if (token.includes('.') && JWT.test(token))
27
+ return EVolatileKind.Jwt;
28
+ if (ISO8601.test(token))
29
+ return EVolatileKind.Iso8601;
30
+ if (HEX_HASH.test(token))
31
+ return EVolatileKind.HexHash;
32
+ if (EPOCH.test(token))
33
+ return EVolatileKind.EpochTimestamp;
34
+ return null;
35
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Classes of volatile token that destabilise a provider's KV-cache prefix.
3
+ * When these change between otherwise-identical prompts the cache misses, so
4
+ * surfacing them lets a caller hoist/pin them for cheaper cache hits.
5
+ */
6
+ export declare enum EVolatileKind {
7
+ Uuid = "uuid",
8
+ Jwt = "jwt",
9
+ Iso8601 = "iso8601",
10
+ HexHash = "hex-hash",
11
+ EpochTimestamp = "epoch-timestamp"
12
+ }
13
+ //# sourceMappingURL=volatile-kind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volatile-kind.d.ts","sourceRoot":"","sources":["../../src/cache/volatile-kind.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,oBAAY,aAAa;IACvB,IAAI,SAAS;IACb,GAAG,QAAQ;IACX,OAAO,YAAY;IACnB,OAAO,aAAa;IACpB,cAAc,oBAAoB;CACnC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Classes of volatile token that destabilise a provider's KV-cache prefix.
3
+ * When these change between otherwise-identical prompts the cache misses, so
4
+ * surfacing them lets a caller hoist/pin them for cheaper cache hits.
5
+ */
6
+ export var EVolatileKind;
7
+ (function (EVolatileKind) {
8
+ EVolatileKind["Uuid"] = "uuid";
9
+ EVolatileKind["Jwt"] = "jwt";
10
+ EVolatileKind["Iso8601"] = "iso8601";
11
+ EVolatileKind["HexHash"] = "hex-hash";
12
+ EVolatileKind["EpochTimestamp"] = "epoch-timestamp";
13
+ })(EVolatileKind || (EVolatileKind = {}));
@@ -0,0 +1,14 @@
1
+ import type { EVolatileKind } from './volatile-kind.js';
2
+ /**
3
+ * An aggregated volatile-token finding: how many times a kind of cache-busting
4
+ * token appears, plus one representative sample (truncated). Detection only —
5
+ * the prompt is never mutated.
6
+ */
7
+ export interface IVolatileToken {
8
+ kind: EVolatileKind;
9
+ /** Number of occurrences in the scanned text. */
10
+ count: number;
11
+ /** A representative occurrence (first seen), truncated to 24 chars. */
12
+ sample: string;
13
+ }
14
+ //# sourceMappingURL=volatile-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volatile-token.d.ts","sourceRoot":"","sources":["../../src/cache/volatile-token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * A cached original in the Compress-Cache-Retrieve store. Keyed by a
3
+ * deterministic content hash so the same bytes always map to the same entry.
4
+ */
5
+ export interface ICcrEntry {
6
+ /** Deterministic content key (see {@link ccrKey}). */
7
+ key: string;
8
+ /** The original, uncompressed text. */
9
+ content: string;
10
+ /** Byte length of {@link content}. */
11
+ bytes: number;
12
+ }
13
+ //# sourceMappingURL=ccr-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ccr-entry.d.ts","sourceRoot":"","sources":["../../src/ccr/ccr-entry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;CACf"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Deterministic content key for the CCR store. A 64-bit FNV-1a hash rendered
3
+ * as 16 lowercase hex chars — dependency-free, stable across processes and
4
+ * platforms, and collision-resistant enough for a local content cache. The
5
+ * same bytes always produce the same key, which is what makes compression
6
+ * reproducible (same workspace ⇒ same markers).
7
+ */
8
+ export declare function ccrKey(content: string): string;
9
+ //# sourceMappingURL=ccr-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ccr-key.d.ts","sourceRoot":"","sources":["../../src/ccr/ccr-key.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ9C"}
@@ -0,0 +1,19 @@
1
+ const FNV_OFFSET = 0xcbf29ce484222325n;
2
+ const FNV_PRIME = 0x100000001b3n;
3
+ const MASK_64 = 0xffffffffffffffffn;
4
+ /**
5
+ * Deterministic content key for the CCR store. A 64-bit FNV-1a hash rendered
6
+ * as 16 lowercase hex chars — dependency-free, stable across processes and
7
+ * platforms, and collision-resistant enough for a local content cache. The
8
+ * same bytes always produce the same key, which is what makes compression
9
+ * reproducible (same workspace ⇒ same markers).
10
+ */
11
+ export function ccrKey(content) {
12
+ let hash = FNV_OFFSET;
13
+ for (let i = 0; i < content.length; i += 1) {
14
+ const code = content.charCodeAt(i);
15
+ hash = (hash ^ BigInt(code & 0xff)) * FNV_PRIME & MASK_64;
16
+ hash = (hash ^ BigInt((code >> 8) & 0xff)) * FNV_PRIME & MASK_64;
17
+ }
18
+ return hash.toString(16).padStart(16, '0');
19
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Matches a CCR retrieval marker: `<<ccr:KEY>>` or `<<ccr:KEY note text>>`.
3
+ * The key is the hex content hash; the optional note is a short human hint
4
+ * (e.g. `42 rows offloaded`). Global + multiline so a scan finds every marker
5
+ * in a blob.
6
+ */
7
+ export declare const CCR_MARKER_RE: RegExp;
8
+ /** A parsed CCR marker found inside a compressed blob. */
9
+ export interface ICcrMarkerRef {
10
+ /** Content key to retrieve. */
11
+ key: string;
12
+ /** Optional human hint that accompanied the marker. */
13
+ note?: string;
14
+ }
15
+ /**
16
+ * Render a retrieval marker. Callers embed this in compressed output wherever
17
+ * detail was dropped; an agent that wants the original calls
18
+ * `retrieve_original` / `shrk expand` with {@link key}.
19
+ */
20
+ export declare function formatCcrMarker(key: string, note?: string): string;
21
+ /** Extract every CCR marker from a blob, in order of appearance. */
22
+ export declare function parseCcrMarkers(text: string): ICcrMarkerRef[];
23
+ //# sourceMappingURL=ccr-marker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ccr-marker.d.ts","sourceRoot":"","sources":["../../src/ccr/ccr-marker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QAA4C,CAAC;AAEvE,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAS7D"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Matches a CCR retrieval marker: `<<ccr:KEY>>` or `<<ccr:KEY note text>>`.
3
+ * The key is the hex content hash; the optional note is a short human hint
4
+ * (e.g. `42 rows offloaded`). Global + multiline so a scan finds every marker
5
+ * in a blob.
6
+ */
7
+ export const CCR_MARKER_RE = /<<ccr:([0-9a-f]{8,})(?:\s+([^>]*?))?>>/g;
8
+ /**
9
+ * Render a retrieval marker. Callers embed this in compressed output wherever
10
+ * detail was dropped; an agent that wants the original calls
11
+ * `retrieve_original` / `shrk expand` with {@link key}.
12
+ */
13
+ export function formatCcrMarker(key, note) {
14
+ // Strip `>` from the note so an embedded `>>` can't prematurely close the
15
+ // marker (format → parse must round-trip for any note the formatter accepts).
16
+ const trimmed = note?.trim().replace(/>/g, '');
17
+ return trimmed ? `<<ccr:${key} ${trimmed}>>` : `<<ccr:${key}>>`;
18
+ }
19
+ /** Extract every CCR marker from a blob, in order of appearance. */
20
+ export function parseCcrMarkers(text) {
21
+ const out = [];
22
+ for (const match of text.matchAll(CCR_MARKER_RE)) {
23
+ const key = match[1];
24
+ if (!key)
25
+ continue;
26
+ const note = match[2]?.trim();
27
+ out.push(note ? { key, note } : { key });
28
+ }
29
+ return out;
30
+ }
@@ -0,0 +1,18 @@
1
+ import type { ICcrEntry } from './ccr-entry.js';
2
+ /**
3
+ * Storage contract for cached originals. Implementations may be in-memory
4
+ * (MCP server lifetime — never touches disk, honouring "MCP never writes")
5
+ * or filesystem-backed (CLI, under `.sharkcraft/ccr/`). Reads are total;
6
+ * writes are idempotent on the content key.
7
+ */
8
+ export interface ICcrStore {
9
+ /** Cache `content`, returning its deterministic key. Idempotent. */
10
+ put(content: string): string;
11
+ /** Fetch a cached original by key, or `undefined` if absent/evicted. */
12
+ get(key: string): ICcrEntry | undefined;
13
+ /** Whether `key` is currently cached. */
14
+ has(key: string): boolean;
15
+ /** Number of cached entries. */
16
+ size(): number;
17
+ }
18
+ //# sourceMappingURL=ccr-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ccr-store.d.ts","sourceRoot":"","sources":["../../src/ccr/ccr-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,oEAAoE;IACpE,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,wEAAwE;IACxE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACxC,yCAAyC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,gCAAgC;IAChC,IAAI,IAAI,MAAM,CAAC;CAChB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { ICcrStore } from './ccr-store.js';
2
+ import type { ICcrEntry } from './ccr-entry.js';
3
+ /**
4
+ * Filesystem-backed CCR store, used by the CLI (the write path). Each cached
5
+ * original is one content-addressed file under the store directory (typically
6
+ * `.sharkcraft/ccr/`), so `shrk compress` in one process and `shrk expand` in
7
+ * a later process share the cache. The MCP server never uses this backend —
8
+ * it stays in memory to honour the read-only contract.
9
+ */
10
+ export declare class FileCcrStore implements ICcrStore {
11
+ private readonly dir;
12
+ constructor(dir: string);
13
+ private pathFor;
14
+ put(content: string): string;
15
+ get(key: string): ICcrEntry | undefined;
16
+ has(key: string): boolean;
17
+ size(): number;
18
+ }
19
+ //# sourceMappingURL=file-ccr-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-ccr-store.d.ts","sourceRoot":"","sources":["../../src/ccr/file-ccr-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQhD;;;;;;GAMG;AACH,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,EAAE,MAAM;IAKvB,OAAO,CAAC,OAAO;IAIf,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAU5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAQvC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAKzB,IAAI,IAAI,MAAM;CAIf"}
@@ -0,0 +1,53 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { Buffer } from 'node:buffer';
4
+ import { ccrKey } from "./ccr-key.js";
5
+ /** CCR keys are content hashes — hex only. Anything else (e.g. a path-traversal
6
+ * attempt like `../../etc/passwd`) is rejected so a lookup can never escape the
7
+ * store directory or read arbitrary files. */
8
+ const VALID_KEY = /^[0-9a-f]{1,64}$/;
9
+ /**
10
+ * Filesystem-backed CCR store, used by the CLI (the write path). Each cached
11
+ * original is one content-addressed file under the store directory (typically
12
+ * `.sharkcraft/ccr/`), so `shrk compress` in one process and `shrk expand` in
13
+ * a later process share the cache. The MCP server never uses this backend —
14
+ * it stays in memory to honour the read-only contract.
15
+ */
16
+ export class FileCcrStore {
17
+ dir;
18
+ constructor(dir) {
19
+ // Field init only — directory creation is deferred to the first write.
20
+ this.dir = dir;
21
+ }
22
+ pathFor(key) {
23
+ return nodePath.join(this.dir, `${key}.txt`);
24
+ }
25
+ put(content) {
26
+ const key = ccrKey(content);
27
+ const file = this.pathFor(key);
28
+ if (!existsSync(file)) {
29
+ mkdirSync(this.dir, { recursive: true });
30
+ writeFileSync(file, content, 'utf8');
31
+ }
32
+ return key;
33
+ }
34
+ get(key) {
35
+ if (!VALID_KEY.test(key))
36
+ return undefined;
37
+ const file = this.pathFor(key);
38
+ if (!existsSync(file))
39
+ return undefined;
40
+ const content = readFileSync(file, 'utf8');
41
+ return { key, content, bytes: Buffer.byteLength(content, 'utf8') };
42
+ }
43
+ has(key) {
44
+ if (!VALID_KEY.test(key))
45
+ return false;
46
+ return existsSync(this.pathFor(key));
47
+ }
48
+ size() {
49
+ if (!existsSync(this.dir))
50
+ return 0;
51
+ return readdirSync(this.dir).filter((f) => f.endsWith('.txt')).length;
52
+ }
53
+ }