circle-ir-ai 2.21.0 → 2.26.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/CHANGELOG.md +351 -0
- package/dist/agents/mastra/workflow.d.ts.map +1 -1
- package/dist/agents/mastra/workflow.js +41 -1
- package/dist/agents/mastra/workflow.js.map +1 -1
- package/dist/cache/classification-cache.d.ts +27 -2
- package/dist/cache/classification-cache.d.ts.map +1 -1
- package/dist/cache/classification-cache.js +37 -5
- package/dist/cache/classification-cache.js.map +1 -1
- package/dist/cache/discovery-cache.d.ts +71 -0
- package/dist/cache/discovery-cache.d.ts.map +1 -0
- package/dist/cache/discovery-cache.js +95 -0
- package/dist/cache/discovery-cache.js.map +1 -0
- package/dist/cache/tree-cache.d.ts +111 -0
- package/dist/cache/tree-cache.d.ts.map +1 -0
- package/dist/cache/tree-cache.js +171 -0
- package/dist/cache/tree-cache.js.map +1 -0
- package/dist/cache/verify-cache.d.ts +60 -0
- package/dist/cache/verify-cache.d.ts.map +1 -0
- package/dist/cache/verify-cache.js +103 -0
- package/dist/cache/verify-cache.js.map +1 -0
- package/dist/cluster/cluster.d.ts +174 -0
- package/dist/cluster/cluster.d.ts.map +1 -0
- package/dist/cluster/cluster.js +392 -0
- package/dist/cluster/cluster.js.map +1 -0
- package/dist/cluster/index.d.ts +11 -0
- package/dist/cluster/index.d.ts.map +1 -0
- package/dist/cluster/index.js +10 -0
- package/dist/cluster/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/batch-classifier.d.ts.map +1 -1
- package/dist/llm/batch-classifier.js +13 -4
- package/dist/llm/batch-classifier.js.map +1 -1
- package/dist/llm/call-logger.d.ts +36 -0
- package/dist/llm/call-logger.d.ts.map +1 -1
- package/dist/llm/call-logger.js +36 -0
- package/dist/llm/call-logger.js.map +1 -1
- package/dist/llm/discovery.d.ts +1 -1
- package/dist/llm/discovery.d.ts.map +1 -1
- package/dist/llm/discovery.js +39 -0
- package/dist/llm/discovery.js.map +1 -1
- package/dist/llm/verification.d.ts +1 -1
- package/dist/llm/verification.d.ts.map +1 -1
- package/dist/llm/verification.js +39 -0
- package/dist/llm/verification.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Classification Cache
|
|
3
3
|
*
|
|
4
|
-
* Caches LLM classification results by composite key
|
|
4
|
+
* Caches LLM classification results by composite key
|
|
5
|
+
* (content hash + classifier name + per-phase model fingerprint).
|
|
5
6
|
* Optimized for batch classifier workloads with shorter TTL than FileCache.
|
|
6
7
|
*
|
|
7
8
|
* Key differences from FileCache:
|
|
8
|
-
* - Composite key: SHA-256(content + '\0' + classifierName)
|
|
9
|
+
* - Composite key: SHA-256(content + '\0' + classifierName + '\0' + modelFingerprint)
|
|
9
10
|
* - Shorter default TTL (24h vs 7d)
|
|
10
11
|
* - Cache directory: .cognium/cache/
|
|
11
12
|
* - Hit/miss tracking for observability
|
|
13
|
+
*
|
|
14
|
+
* Model dimension (cognium-ai#151 P0 spike):
|
|
15
|
+
* When `modelFingerprint` is supplied (typically via
|
|
16
|
+
* `getPhaseModelFingerprint()` from `../llm/config.ts`) it becomes part
|
|
17
|
+
* of the cache key. This prevents a `gemma3:12b` discovery result from
|
|
18
|
+
* being silently reused when the operator switches to `gpt-4o-mini` for
|
|
19
|
+
* discovery. The fingerprint is the same identity the persistent cache
|
|
20
|
+
* (#158) uses, so call-site cache and S3 cache stay in lockstep.
|
|
21
|
+
*
|
|
22
|
+
* Backward compat: when omitted the key falls through to the prior
|
|
23
|
+
* 2-component shape, so existing callers (and on-disk entries from
|
|
24
|
+
* ≤2.21.0) keep working — the first run after wire-through is a cache
|
|
25
|
+
* miss only for the call sites that opt into the fingerprint.
|
|
12
26
|
*/
|
|
13
27
|
import * as fs from 'fs';
|
|
14
28
|
import * as path from 'path';
|
|
@@ -18,6 +32,7 @@ export class ClassificationCache {
|
|
|
18
32
|
cacheDir;
|
|
19
33
|
maxAge;
|
|
20
34
|
enabled;
|
|
35
|
+
modelFingerprint;
|
|
21
36
|
memoryCache = new Map();
|
|
22
37
|
hits = 0;
|
|
23
38
|
misses = 0;
|
|
@@ -25,12 +40,24 @@ export class ClassificationCache {
|
|
|
25
40
|
this.cacheDir = options.cacheDir || path.join(process.cwd(), '.cognium', 'cache');
|
|
26
41
|
this.maxAge = options.maxAge ?? DEFAULT_MAX_AGE;
|
|
27
42
|
this.enabled = options.enabled ?? true;
|
|
43
|
+
this.modelFingerprint = options.modelFingerprint ?? '';
|
|
28
44
|
if (this.enabled) {
|
|
29
45
|
this.ensureCacheDir();
|
|
30
46
|
}
|
|
31
47
|
}
|
|
48
|
+
/** Per-phase model identity used in cache key (cognium-ai#151). */
|
|
49
|
+
getModelFingerprint() {
|
|
50
|
+
return this.modelFingerprint;
|
|
51
|
+
}
|
|
32
52
|
computeKey(content, classifier) {
|
|
33
|
-
|
|
53
|
+
// 3-component key when fingerprint present (cognium-ai#151). When
|
|
54
|
+
// empty falls through to the prior 2-component shape via the trailing
|
|
55
|
+
// `'\0' + ''` collapse — bit-identical to ≤2.21.0 only when no
|
|
56
|
+
// fingerprint is supplied at all, which is the back-compat path.
|
|
57
|
+
const parts = this.modelFingerprint
|
|
58
|
+
? content + '\0' + classifier + '\0' + this.modelFingerprint
|
|
59
|
+
: content + '\0' + classifier;
|
|
60
|
+
return crypto.createHash('sha256').update(parts).digest('hex');
|
|
34
61
|
}
|
|
35
62
|
getCachePath(key) {
|
|
36
63
|
const subdir = key.substring(0, 2);
|
|
@@ -147,10 +174,15 @@ export class ClassificationCache {
|
|
|
147
174
|
};
|
|
148
175
|
}
|
|
149
176
|
}
|
|
150
|
-
// Singleton
|
|
177
|
+
// Singleton — keyed on cacheDir + modelFingerprint so two scans with
|
|
178
|
+
// different per-phase models in the same process get distinct caches
|
|
179
|
+
// (cognium-ai#151). Without the fingerprint dimension, switching
|
|
180
|
+
// `LLM_DISCOVERY_MODEL` between scans would silently reuse the first
|
|
181
|
+
// scan's classifier results.
|
|
151
182
|
let classificationCache = null;
|
|
152
183
|
export function getClassificationCache(options) {
|
|
153
|
-
|
|
184
|
+
const wantFingerprint = options?.modelFingerprint ?? '';
|
|
185
|
+
if (!classificationCache || classificationCache.getModelFingerprint() !== wantFingerprint) {
|
|
154
186
|
classificationCache = new ClassificationCache(options);
|
|
155
187
|
}
|
|
156
188
|
return classificationCache;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classification-cache.js","sourceRoot":"","sources":["../../src/cache/classification-cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"classification-cache.js","sourceRoot":"","sources":["../../src/cache/classification-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery Cache
|
|
3
|
+
*
|
|
4
|
+
* Persistent on-disk cache for LLM discovery results per (className,
|
|
5
|
+
* methodName, methodCode, CWE-set) tuple (cognium-ai#134, P2 of epic
|
|
6
|
+
* #155). Sits in front of the in-memory `_discoveryDedupCache` from
|
|
7
|
+
* discovery.ts (#136) so that identical method-level discovery requests
|
|
8
|
+
* survive across processes, CI runs, and developer iterations — not just
|
|
9
|
+
* within one process.
|
|
10
|
+
*
|
|
11
|
+
* Built on top of `ClassificationCache<DiscoveryResult>` (which already
|
|
12
|
+
* carries the cognium-ai#151 model-fingerprint key dimension), with
|
|
13
|
+
* discovery-specific key construction:
|
|
14
|
+
*
|
|
15
|
+
* content = className + '\\0' + methodName + '\\0' +
|
|
16
|
+
* SHA-256(methodCode) + '\\0' + sortedCweSet
|
|
17
|
+
* classifier = 'discovery-method'
|
|
18
|
+
* fingerprint = getPhaseModelFingerprint(config) // includes discovery model
|
|
19
|
+
*
|
|
20
|
+
* Cache directory: `.cognium/cache/discovery/` (separate from the
|
|
21
|
+
* verify-phase `.cognium/cache/verify/` so each phase can be cleared
|
|
22
|
+
* independently — `rm -rf .cognium/cache/discovery/` only nukes
|
|
23
|
+
* discovery entries).
|
|
24
|
+
*
|
|
25
|
+
* Invalidation matrix (#134 acceptance criteria):
|
|
26
|
+
* - Edit one method → only that method's entry invalidates
|
|
27
|
+
* (methodCode SHA-256 is part of the key)
|
|
28
|
+
* - Add a new CWE → existing entries still hit for old CWE-sets;
|
|
29
|
+
* new CWE-set requests miss
|
|
30
|
+
* - Change disc model → entire cache invalidates
|
|
31
|
+
* (fingerprint is part of the singleton key)
|
|
32
|
+
*
|
|
33
|
+
* Env toggles:
|
|
34
|
+
* LLM_DISCOVERY_CACHE=0 → disable persistent discovery cache
|
|
35
|
+
* (in-memory dedup from #136 still applies)
|
|
36
|
+
*/
|
|
37
|
+
import { ClassificationCache } from './classification-cache.js';
|
|
38
|
+
import { type LLMConfig } from '../llm/config.js';
|
|
39
|
+
import type { DiscoveryResult } from '../llm/discovery.js';
|
|
40
|
+
/**
|
|
41
|
+
* Get the discovery-phase persistent cache, keyed by per-phase model
|
|
42
|
+
* fingerprint. When `LLM_DISCOVERY_CACHE=0` returns a disabled cache that
|
|
43
|
+
* no-ops on get/set (callers don't need to branch).
|
|
44
|
+
*/
|
|
45
|
+
export declare function getDiscoveryCache(config?: LLMConfig): ClassificationCache<DiscoveryResult>;
|
|
46
|
+
/** Reset the module-scoped singleton — test-only. */
|
|
47
|
+
export declare function resetDiscoveryCache(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Compute the `(content, classifier)` tuple for the underlying
|
|
50
|
+
* ClassificationCache. The fingerprint dimension is folded in by the
|
|
51
|
+
* cache itself (see classification-cache.ts:computeKey).
|
|
52
|
+
*
|
|
53
|
+
* Components:
|
|
54
|
+
* - className — disambiguates same-named methods across classes
|
|
55
|
+
* - methodName — per-method granularity (matches the per-method LLM call)
|
|
56
|
+
* - methodCode SHA-256 — method edit invalidates only that method's entry
|
|
57
|
+
* - sortedCweSet — different CWE-set requests on the same method don't
|
|
58
|
+
* reuse each other's prompts (different system prompt, different answer)
|
|
59
|
+
* - classifier 'discovery-method' — namespace for future
|
|
60
|
+
* 'discovery-file' / 'discovery-class' tiers without collision
|
|
61
|
+
*/
|
|
62
|
+
export declare function computeDiscoveryCacheKey(input: {
|
|
63
|
+
className: string;
|
|
64
|
+
methodName: string;
|
|
65
|
+
methodCode: string;
|
|
66
|
+
targetCWEs: ReadonlyArray<string>;
|
|
67
|
+
}): {
|
|
68
|
+
content: string;
|
|
69
|
+
classifier: string;
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=discovery-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-cache.d.ts","sourceRoot":"","sources":["../../src/cache/discovery-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAiD,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAa3D;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,SAAiC,GACxC,mBAAmB,CAAC,eAAe,CAAC,CActC;AAED,qDAAqD;AACrD,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACnC,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAO1C"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery Cache
|
|
3
|
+
*
|
|
4
|
+
* Persistent on-disk cache for LLM discovery results per (className,
|
|
5
|
+
* methodName, methodCode, CWE-set) tuple (cognium-ai#134, P2 of epic
|
|
6
|
+
* #155). Sits in front of the in-memory `_discoveryDedupCache` from
|
|
7
|
+
* discovery.ts (#136) so that identical method-level discovery requests
|
|
8
|
+
* survive across processes, CI runs, and developer iterations — not just
|
|
9
|
+
* within one process.
|
|
10
|
+
*
|
|
11
|
+
* Built on top of `ClassificationCache<DiscoveryResult>` (which already
|
|
12
|
+
* carries the cognium-ai#151 model-fingerprint key dimension), with
|
|
13
|
+
* discovery-specific key construction:
|
|
14
|
+
*
|
|
15
|
+
* content = className + '\\0' + methodName + '\\0' +
|
|
16
|
+
* SHA-256(methodCode) + '\\0' + sortedCweSet
|
|
17
|
+
* classifier = 'discovery-method'
|
|
18
|
+
* fingerprint = getPhaseModelFingerprint(config) // includes discovery model
|
|
19
|
+
*
|
|
20
|
+
* Cache directory: `.cognium/cache/discovery/` (separate from the
|
|
21
|
+
* verify-phase `.cognium/cache/verify/` so each phase can be cleared
|
|
22
|
+
* independently — `rm -rf .cognium/cache/discovery/` only nukes
|
|
23
|
+
* discovery entries).
|
|
24
|
+
*
|
|
25
|
+
* Invalidation matrix (#134 acceptance criteria):
|
|
26
|
+
* - Edit one method → only that method's entry invalidates
|
|
27
|
+
* (methodCode SHA-256 is part of the key)
|
|
28
|
+
* - Add a new CWE → existing entries still hit for old CWE-sets;
|
|
29
|
+
* new CWE-set requests miss
|
|
30
|
+
* - Change disc model → entire cache invalidates
|
|
31
|
+
* (fingerprint is part of the singleton key)
|
|
32
|
+
*
|
|
33
|
+
* Env toggles:
|
|
34
|
+
* LLM_DISCOVERY_CACHE=0 → disable persistent discovery cache
|
|
35
|
+
* (in-memory dedup from #136 still applies)
|
|
36
|
+
*/
|
|
37
|
+
import * as crypto from 'crypto';
|
|
38
|
+
import * as path from 'path';
|
|
39
|
+
import { ClassificationCache } from './classification-cache.js';
|
|
40
|
+
import { getDefaultLLMConfig, getPhaseModelFingerprint } from '../llm/config.js';
|
|
41
|
+
const DISCOVERY_CACHE_SUBDIR = path.join('.cognium', 'cache', 'discovery');
|
|
42
|
+
/**
|
|
43
|
+
* Module-scoped singleton — distinct from the discovery-phase singleton
|
|
44
|
+
* in `classification-cache.ts` and the verify-phase singleton in
|
|
45
|
+
* `verify-cache.ts` so each phase can carry its own model fingerprint
|
|
46
|
+
* (e.g. cheap discovery + strong verifier) without thrashing.
|
|
47
|
+
*/
|
|
48
|
+
let _instance = null;
|
|
49
|
+
let _instanceFingerprint = '';
|
|
50
|
+
/**
|
|
51
|
+
* Get the discovery-phase persistent cache, keyed by per-phase model
|
|
52
|
+
* fingerprint. When `LLM_DISCOVERY_CACHE=0` returns a disabled cache that
|
|
53
|
+
* no-ops on get/set (callers don't need to branch).
|
|
54
|
+
*/
|
|
55
|
+
export function getDiscoveryCache(config = getDefaultLLMConfig()) {
|
|
56
|
+
const enabled = process.env.LLM_DISCOVERY_CACHE !== '0';
|
|
57
|
+
const fingerprint = getPhaseModelFingerprint(config);
|
|
58
|
+
if (!_instance || _instanceFingerprint !== fingerprint || _instance.getModelFingerprint() !== fingerprint) {
|
|
59
|
+
_instance = new ClassificationCache({
|
|
60
|
+
cacheDir: path.join(process.cwd(), DISCOVERY_CACHE_SUBDIR),
|
|
61
|
+
modelFingerprint: fingerprint,
|
|
62
|
+
enabled,
|
|
63
|
+
});
|
|
64
|
+
_instanceFingerprint = fingerprint;
|
|
65
|
+
}
|
|
66
|
+
return _instance;
|
|
67
|
+
}
|
|
68
|
+
/** Reset the module-scoped singleton — test-only. */
|
|
69
|
+
export function resetDiscoveryCache() {
|
|
70
|
+
_instance = null;
|
|
71
|
+
_instanceFingerprint = '';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Compute the `(content, classifier)` tuple for the underlying
|
|
75
|
+
* ClassificationCache. The fingerprint dimension is folded in by the
|
|
76
|
+
* cache itself (see classification-cache.ts:computeKey).
|
|
77
|
+
*
|
|
78
|
+
* Components:
|
|
79
|
+
* - className — disambiguates same-named methods across classes
|
|
80
|
+
* - methodName — per-method granularity (matches the per-method LLM call)
|
|
81
|
+
* - methodCode SHA-256 — method edit invalidates only that method's entry
|
|
82
|
+
* - sortedCweSet — different CWE-set requests on the same method don't
|
|
83
|
+
* reuse each other's prompts (different system prompt, different answer)
|
|
84
|
+
* - classifier 'discovery-method' — namespace for future
|
|
85
|
+
* 'discovery-file' / 'discovery-class' tiers without collision
|
|
86
|
+
*/
|
|
87
|
+
export function computeDiscoveryCacheKey(input) {
|
|
88
|
+
const methodSha = crypto.createHash('sha256').update(input.methodCode).digest('hex');
|
|
89
|
+
const sortedCweSet = [...input.targetCWEs].sort().join(',');
|
|
90
|
+
return {
|
|
91
|
+
content: `${input.className}\0${input.methodName}\0${methodSha}\0${sortedCweSet}`,
|
|
92
|
+
classifier: 'discovery-method',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=discovery-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-cache.js","sourceRoot":"","sources":["../../src/cache/discovery-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAkB,MAAM,kBAAkB,CAAC;AAGjG,MAAM,sBAAsB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;AAE3E;;;;;GAKG;AACH,IAAI,SAAS,GAAgD,IAAI,CAAC;AAClE,IAAI,oBAAoB,GAAG,EAAE,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAoB,mBAAmB,EAAE;IAEzC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,GAAG,CAAC;IACxD,MAAM,WAAW,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAErD,IAAI,CAAC,SAAS,IAAI,oBAAoB,KAAK,WAAW,IAAI,SAAS,CAAC,mBAAmB,EAAE,KAAK,WAAW,EAAE,CAAC;QAC1G,SAAS,GAAG,IAAI,mBAAmB,CAAkB;YACnD,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,sBAAsB,CAAC;YAC1D,gBAAgB,EAAE,WAAW;YAC7B,OAAO;SACR,CAAC,CAAC;QACH,oBAAoB,GAAG,WAAW,CAAC;IACrC,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,mBAAmB;IACjC,SAAS,GAAG,IAAI,CAAC;IACjB,oBAAoB,GAAG,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAKxC;IACC,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrF,MAAM,YAAY,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5D,OAAO;QACL,OAAO,EAAE,GAAG,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,UAAU,KAAK,SAAS,KAAK,YAAY,EAAE;QACjF,UAAU,EAAE,kBAAkB;KAC/B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree Cache
|
|
3
|
+
*
|
|
4
|
+
* Persistent on-disk cache for `runPatternMatch` output (cognium-ai#153,
|
|
5
|
+
* P3 of epic #155). Pattern-match output is a pure function of file
|
|
6
|
+
* content + engine version, so caching it eliminates the parse +
|
|
7
|
+
* AST-walk + SAST-rule firing on warm runs.
|
|
8
|
+
*
|
|
9
|
+
* Empirically (v2.20.6 gson re-verify, 2026-06-29) the scan stage ate
|
|
10
|
+
* 179s of 358s end-to-end — ~half the wall-clock is re-discovering
|
|
11
|
+
* structure circle-ir already knows. Caching the pattern-match seam
|
|
12
|
+
* is the smallest empirical win in the cache stack (no LLM-token
|
|
13
|
+
* reduction, only CPU + wall-clock), but it stacks cleanly on the
|
|
14
|
+
* #145 static pre-filter — a cached tree means the pre-filter has
|
|
15
|
+
* more to chew on, which reduces enrichment calls downstream.
|
|
16
|
+
*
|
|
17
|
+
* **Scope:** caches `PatternMatchResult` only, NOT full `CircleIR`.
|
|
18
|
+
* The full graph (cross-file CFG, project-level type hierarchy) is
|
|
19
|
+
* too entangled with project context to cache safely without a full
|
|
20
|
+
* `CircleIR.serialize()` upstream — that's a separate D3 design
|
|
21
|
+
* question, file separately if pursued.
|
|
22
|
+
*
|
|
23
|
+
* **Key composition:**
|
|
24
|
+
* content = SHA-256(sourceCode) + '\0' +
|
|
25
|
+
* circleIrVersion + '\0' +
|
|
26
|
+
* circleIrAiVersion + '\0' +
|
|
27
|
+
* language + '\0' +
|
|
28
|
+
* ruleSetFingerprint
|
|
29
|
+
* classifier = 'tree-pattern-match'
|
|
30
|
+
*
|
|
31
|
+
* Any of: file edit, circle-ir bump, circle-ir-ai bump, language
|
|
32
|
+
* change, or rule-set change → cache miss.
|
|
33
|
+
*
|
|
34
|
+
* **Storage:** `ClassificationCache<PatternMatchResult>` under
|
|
35
|
+
* `.cognium/cache/tree/`, 7-day TTL (longer than discovery cache
|
|
36
|
+
* because the input is structural and stable — pattern-match output
|
|
37
|
+
* doesn't drift between scans the way an LLM call would).
|
|
38
|
+
*
|
|
39
|
+
* **Env toggles:**
|
|
40
|
+
* LLM_TREE_CACHE=0 → disable persistent tree cache.
|
|
41
|
+
*
|
|
42
|
+
* **Versioning note:** unlike discovery-cache / verify-cache, this
|
|
43
|
+
* cache is NOT keyed on the LLM model fingerprint (pattern-match is
|
|
44
|
+
* static — circle-ir doesn't call any LLM). It IS keyed on the
|
|
45
|
+
* circle-ir and circle-ir-ai package versions because either bump
|
|
46
|
+
* can change the workflow-level transforms applied to the result.
|
|
47
|
+
*/
|
|
48
|
+
import { ClassificationCache } from './classification-cache.js';
|
|
49
|
+
import type { TaintSource, TaintSink, TypeInfo, DFG, SastFinding } from 'circle-ir';
|
|
50
|
+
/**
|
|
51
|
+
* Shape persisted to disk. Mirrors the return of `runPatternMatch` in
|
|
52
|
+
* `src/agents/mastra/workflow.ts`. All fields are plain JSON-serializable
|
|
53
|
+
* (no class instances, no cycles) so the underlying ClassificationCache
|
|
54
|
+
* round-trip is safe.
|
|
55
|
+
*/
|
|
56
|
+
export interface PatternMatchResult {
|
|
57
|
+
patternSources: TaintSource[];
|
|
58
|
+
patternSinks: TaintSink[];
|
|
59
|
+
types: TypeInfo[];
|
|
60
|
+
imports: string[];
|
|
61
|
+
sastFindings: SastFinding[];
|
|
62
|
+
dfg: DFG;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the tree-cache singleton. When `LLM_TREE_CACHE=0` returns a
|
|
66
|
+
* disabled cache that no-ops on get/set (callers don't need to branch).
|
|
67
|
+
*/
|
|
68
|
+
export declare function getTreeCache(): ClassificationCache<PatternMatchResult>;
|
|
69
|
+
/** Reset the module-scoped singleton — test-only. */
|
|
70
|
+
export declare function resetTreeCache(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Compute the `(content, classifier)` tuple for the underlying
|
|
73
|
+
* ClassificationCache.
|
|
74
|
+
*
|
|
75
|
+
* Components:
|
|
76
|
+
* - source code SHA-256 — file edit invalidates only that file's entry
|
|
77
|
+
* - circleIrVersion — bumping circle-ir invalidates all entries
|
|
78
|
+
* - circleIrAiVersion — bumping circle-ir-ai (which may change
|
|
79
|
+
* workflow-level transforms applied to pattern-match output)
|
|
80
|
+
* invalidates all entries
|
|
81
|
+
* - language — same source, different language tag must miss (the
|
|
82
|
+
* analyzer's behavior depends on the language argument)
|
|
83
|
+
* - ruleSetFingerprint — caller-supplied digest of the active rule
|
|
84
|
+
* set (disabledPasses + projectProfile). Empty string is fine for
|
|
85
|
+
* the default rule set.
|
|
86
|
+
*/
|
|
87
|
+
export declare function computeTreeCacheKey(input: {
|
|
88
|
+
sourceCode: string;
|
|
89
|
+
language: string;
|
|
90
|
+
ruleSetFingerprint?: string;
|
|
91
|
+
}): {
|
|
92
|
+
content: string;
|
|
93
|
+
classifier: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Compute a stable fingerprint over an arbitrary rule-set descriptor
|
|
97
|
+
* (disabledPasses array + projectProfile object). Result is suitable to
|
|
98
|
+
* pass as `ruleSetFingerprint` to `computeTreeCacheKey`. Order-stable
|
|
99
|
+
* for arrays so re-ordering `--disable a,b` vs `--disable b,a` still
|
|
100
|
+
* hits the cache.
|
|
101
|
+
*/
|
|
102
|
+
export declare function computeRuleSetFingerprint(input: {
|
|
103
|
+
disabledPasses?: string[];
|
|
104
|
+
projectProfile?: unknown;
|
|
105
|
+
}): string;
|
|
106
|
+
/** Test-only: expose the captured engine versions for assertions. */
|
|
107
|
+
export declare function getTreeCacheVersions(): {
|
|
108
|
+
circleIrVersion: string;
|
|
109
|
+
circleIrAiVersion: string;
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=tree-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-cache.d.ts","sourceRoot":"","sources":["../../src/cache/tree-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAMH,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAKpF;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,WAAW,EAAE,CAAC;IAC9B,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,GAAG,EAAE,GAAG,CAAC;CACV;AAiDD;;;GAGG;AACH,wBAAgB,YAAY,IAAI,mBAAmB,CAAC,kBAAkB,CAAC,CAUtE;AAED,qDAAqD;AACrD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAO1C;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,MAAM,CAWT;AAED,qEAAqE;AACrE,wBAAgB,oBAAoB,IAAI;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAKA"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree Cache
|
|
3
|
+
*
|
|
4
|
+
* Persistent on-disk cache for `runPatternMatch` output (cognium-ai#153,
|
|
5
|
+
* P3 of epic #155). Pattern-match output is a pure function of file
|
|
6
|
+
* content + engine version, so caching it eliminates the parse +
|
|
7
|
+
* AST-walk + SAST-rule firing on warm runs.
|
|
8
|
+
*
|
|
9
|
+
* Empirically (v2.20.6 gson re-verify, 2026-06-29) the scan stage ate
|
|
10
|
+
* 179s of 358s end-to-end — ~half the wall-clock is re-discovering
|
|
11
|
+
* structure circle-ir already knows. Caching the pattern-match seam
|
|
12
|
+
* is the smallest empirical win in the cache stack (no LLM-token
|
|
13
|
+
* reduction, only CPU + wall-clock), but it stacks cleanly on the
|
|
14
|
+
* #145 static pre-filter — a cached tree means the pre-filter has
|
|
15
|
+
* more to chew on, which reduces enrichment calls downstream.
|
|
16
|
+
*
|
|
17
|
+
* **Scope:** caches `PatternMatchResult` only, NOT full `CircleIR`.
|
|
18
|
+
* The full graph (cross-file CFG, project-level type hierarchy) is
|
|
19
|
+
* too entangled with project context to cache safely without a full
|
|
20
|
+
* `CircleIR.serialize()` upstream — that's a separate D3 design
|
|
21
|
+
* question, file separately if pursued.
|
|
22
|
+
*
|
|
23
|
+
* **Key composition:**
|
|
24
|
+
* content = SHA-256(sourceCode) + '\0' +
|
|
25
|
+
* circleIrVersion + '\0' +
|
|
26
|
+
* circleIrAiVersion + '\0' +
|
|
27
|
+
* language + '\0' +
|
|
28
|
+
* ruleSetFingerprint
|
|
29
|
+
* classifier = 'tree-pattern-match'
|
|
30
|
+
*
|
|
31
|
+
* Any of: file edit, circle-ir bump, circle-ir-ai bump, language
|
|
32
|
+
* change, or rule-set change → cache miss.
|
|
33
|
+
*
|
|
34
|
+
* **Storage:** `ClassificationCache<PatternMatchResult>` under
|
|
35
|
+
* `.cognium/cache/tree/`, 7-day TTL (longer than discovery cache
|
|
36
|
+
* because the input is structural and stable — pattern-match output
|
|
37
|
+
* doesn't drift between scans the way an LLM call would).
|
|
38
|
+
*
|
|
39
|
+
* **Env toggles:**
|
|
40
|
+
* LLM_TREE_CACHE=0 → disable persistent tree cache.
|
|
41
|
+
*
|
|
42
|
+
* **Versioning note:** unlike discovery-cache / verify-cache, this
|
|
43
|
+
* cache is NOT keyed on the LLM model fingerprint (pattern-match is
|
|
44
|
+
* static — circle-ir doesn't call any LLM). It IS keyed on the
|
|
45
|
+
* circle-ir and circle-ir-ai package versions because either bump
|
|
46
|
+
* can change the workflow-level transforms applied to the result.
|
|
47
|
+
*/
|
|
48
|
+
import * as crypto from 'crypto';
|
|
49
|
+
import * as fs from 'fs';
|
|
50
|
+
import * as path from 'path';
|
|
51
|
+
import { createRequire } from 'module';
|
|
52
|
+
import { ClassificationCache } from './classification-cache.js';
|
|
53
|
+
const TREE_CACHE_SUBDIR = path.join('.cognium', 'cache', 'tree');
|
|
54
|
+
const TREE_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days — structural, stable
|
|
55
|
+
/**
|
|
56
|
+
* Module-scoped singleton — distinct from the verify/discovery
|
|
57
|
+
* singletons so the tree cache (which doesn't depend on the LLM model)
|
|
58
|
+
* doesn't get invalidated when the operator swaps discovery model.
|
|
59
|
+
*/
|
|
60
|
+
let _instance = null;
|
|
61
|
+
/**
|
|
62
|
+
* Read package versions once at module load (best-effort). Wrapped in a
|
|
63
|
+
* try/catch so unit tests that don't ship a package.json next to the
|
|
64
|
+
* source can still construct the cache; in that case the version
|
|
65
|
+
* component degrades to 'unknown' and the cache still works (just isn't
|
|
66
|
+
* cross-version-safe).
|
|
67
|
+
*/
|
|
68
|
+
function safeReadVersion(spec, baseUrl) {
|
|
69
|
+
try {
|
|
70
|
+
const require = createRequire(baseUrl);
|
|
71
|
+
return require(spec).version || 'unknown';
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return 'unknown';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const _circleIrVersion = safeReadVersion('circle-ir/package.json', import.meta.url);
|
|
78
|
+
const _circleIrAiVersion = (() => {
|
|
79
|
+
// The package's own version is harder to resolve via require, so read
|
|
80
|
+
// it directly from disk by walking up from this file.
|
|
81
|
+
try {
|
|
82
|
+
// Compiled output sits at dist/cache/tree-cache.js → ../../package.json
|
|
83
|
+
const candidates = [
|
|
84
|
+
path.resolve(new URL(import.meta.url).pathname, '../../../package.json'),
|
|
85
|
+
path.resolve(new URL(import.meta.url).pathname, '../../package.json'),
|
|
86
|
+
];
|
|
87
|
+
for (const p of candidates) {
|
|
88
|
+
if (fs.existsSync(p)) {
|
|
89
|
+
const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
90
|
+
if (pkg.name === 'circle-ir-ai' && pkg.version) {
|
|
91
|
+
return pkg.version;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* fallthrough */
|
|
98
|
+
}
|
|
99
|
+
return 'unknown';
|
|
100
|
+
})();
|
|
101
|
+
/**
|
|
102
|
+
* Get the tree-cache singleton. When `LLM_TREE_CACHE=0` returns a
|
|
103
|
+
* disabled cache that no-ops on get/set (callers don't need to branch).
|
|
104
|
+
*/
|
|
105
|
+
export function getTreeCache() {
|
|
106
|
+
const enabled = process.env.LLM_TREE_CACHE !== '0';
|
|
107
|
+
if (!_instance) {
|
|
108
|
+
_instance = new ClassificationCache({
|
|
109
|
+
cacheDir: path.join(process.cwd(), TREE_CACHE_SUBDIR),
|
|
110
|
+
maxAge: TREE_CACHE_TTL_MS,
|
|
111
|
+
enabled,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return _instance;
|
|
115
|
+
}
|
|
116
|
+
/** Reset the module-scoped singleton — test-only. */
|
|
117
|
+
export function resetTreeCache() {
|
|
118
|
+
_instance = null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Compute the `(content, classifier)` tuple for the underlying
|
|
122
|
+
* ClassificationCache.
|
|
123
|
+
*
|
|
124
|
+
* Components:
|
|
125
|
+
* - source code SHA-256 — file edit invalidates only that file's entry
|
|
126
|
+
* - circleIrVersion — bumping circle-ir invalidates all entries
|
|
127
|
+
* - circleIrAiVersion — bumping circle-ir-ai (which may change
|
|
128
|
+
* workflow-level transforms applied to pattern-match output)
|
|
129
|
+
* invalidates all entries
|
|
130
|
+
* - language — same source, different language tag must miss (the
|
|
131
|
+
* analyzer's behavior depends on the language argument)
|
|
132
|
+
* - ruleSetFingerprint — caller-supplied digest of the active rule
|
|
133
|
+
* set (disabledPasses + projectProfile). Empty string is fine for
|
|
134
|
+
* the default rule set.
|
|
135
|
+
*/
|
|
136
|
+
export function computeTreeCacheKey(input) {
|
|
137
|
+
const sourceSha = crypto.createHash('sha256').update(input.sourceCode).digest('hex');
|
|
138
|
+
const ruleSet = input.ruleSetFingerprint ?? '';
|
|
139
|
+
return {
|
|
140
|
+
content: `${sourceSha}\0${_circleIrVersion}\0${_circleIrAiVersion}\0${input.language}\0${ruleSet}`,
|
|
141
|
+
classifier: 'tree-pattern-match',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Compute a stable fingerprint over an arbitrary rule-set descriptor
|
|
146
|
+
* (disabledPasses array + projectProfile object). Result is suitable to
|
|
147
|
+
* pass as `ruleSetFingerprint` to `computeTreeCacheKey`. Order-stable
|
|
148
|
+
* for arrays so re-ordering `--disable a,b` vs `--disable b,a` still
|
|
149
|
+
* hits the cache.
|
|
150
|
+
*/
|
|
151
|
+
export function computeRuleSetFingerprint(input) {
|
|
152
|
+
const passes = [...(input.disabledPasses ?? [])].sort().join(',');
|
|
153
|
+
// projectProfile JSON-stringifies; key order in objects matters only
|
|
154
|
+
// if the producer is non-deterministic — `@cognium/project-profile-detect`
|
|
155
|
+
// emits stable keys, so this is fine. If a future profile producer is
|
|
156
|
+
// less stable we can switch to a sorted-keys serializer.
|
|
157
|
+
const profile = input.projectProfile === undefined
|
|
158
|
+
? ''
|
|
159
|
+
: JSON.stringify(input.projectProfile);
|
|
160
|
+
if (!passes && !profile)
|
|
161
|
+
return '';
|
|
162
|
+
return crypto.createHash('sha256').update(`${passes}\0${profile}`).digest('hex');
|
|
163
|
+
}
|
|
164
|
+
/** Test-only: expose the captured engine versions for assertions. */
|
|
165
|
+
export function getTreeCacheVersions() {
|
|
166
|
+
return {
|
|
167
|
+
circleIrVersion: _circleIrVersion,
|
|
168
|
+
circleIrAiVersion: _circleIrAiVersion,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=tree-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-cache.js","sourceRoot":"","sources":["../../src/cache/tree-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Cache
|
|
3
|
+
*
|
|
4
|
+
* Persistent on-disk cache for LLM verification batch results
|
|
5
|
+
* (cognium-ai#152, P1 of epic #155). Sits in front of the in-memory
|
|
6
|
+
* `_verifierDedupCache` from verification.ts (#138 step 3) so that
|
|
7
|
+
* identical `(file SHA, pair-set)` requests survive across processes,
|
|
8
|
+
* CI runs, and developer iterations — not just within one process.
|
|
9
|
+
*
|
|
10
|
+
* Built on top of `ClassificationCache<BatchVerificationResult>` (which
|
|
11
|
+
* already carries the cognium-ai#151 model-fingerprint key dimension),
|
|
12
|
+
* with verify-specific key construction:
|
|
13
|
+
*
|
|
14
|
+
* content = SHA-256(code) + '\\0' + pairsFingerprint
|
|
15
|
+
* classifier = 'verify-batch'
|
|
16
|
+
* fingerprint = getPhaseModelFingerprint(config) // includes verify model
|
|
17
|
+
*
|
|
18
|
+
* Cache directory: `.cognium/cache/verify/` (separate from the
|
|
19
|
+
* discovery-phase `.cognium/cache/` so a `rm -rf .cognium/cache/verify/`
|
|
20
|
+
* only nukes verifier entries).
|
|
21
|
+
*
|
|
22
|
+
* Invalidation matrix (acceptance criteria #152):
|
|
23
|
+
* - Edit one file in a project → only that file's entries invalidate
|
|
24
|
+
* (file SHA-256 is part of the cache content key)
|
|
25
|
+
* - Add a new sink to the engine → existing entries still hit if the
|
|
26
|
+
* new sink doesn't change the pair-set for that file
|
|
27
|
+
* - Change verification model → entire cache invalidates
|
|
28
|
+
* (model fingerprint is part of the singleton key)
|
|
29
|
+
*
|
|
30
|
+
* Env toggles:
|
|
31
|
+
* LLM_VERIFY_CACHE=0 → disable persistent verify cache (in-memory
|
|
32
|
+
* dedup from #138 still applies)
|
|
33
|
+
*/
|
|
34
|
+
import { ClassificationCache } from './classification-cache.js';
|
|
35
|
+
import { type LLMConfig } from '../llm/config.js';
|
|
36
|
+
import type { BatchVerificationInput, BatchVerificationResult } from '../llm/verification.js';
|
|
37
|
+
/**
|
|
38
|
+
* Get the verify-phase persistent cache, keyed by per-phase model
|
|
39
|
+
* fingerprint. When `LLM_VERIFY_CACHE=0` returns a disabled cache that
|
|
40
|
+
* no-ops on get/set (callers don't need to branch).
|
|
41
|
+
*/
|
|
42
|
+
export declare function getVerifyCache(config?: LLMConfig): ClassificationCache<BatchVerificationResult>;
|
|
43
|
+
/** Reset the module-scoped singleton — test-only. */
|
|
44
|
+
export declare function resetVerifyCache(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Compute the `(content, classifier)` tuple for the underlying
|
|
47
|
+
* ClassificationCache. The fingerprint dimension is folded in by the
|
|
48
|
+
* cache itself (see classification-cache.ts:computeKey).
|
|
49
|
+
*
|
|
50
|
+
* Components:
|
|
51
|
+
* - file SHA-256 — file edit invalidates only that file's entry
|
|
52
|
+
* - pairs fingerprint — sort-invariant over the source/sink set
|
|
53
|
+
* - classifier 'verify-batch' — namespace for future
|
|
54
|
+
* 'verify-pair' / 'verify-flow' tiers without collision
|
|
55
|
+
*/
|
|
56
|
+
export declare function computeVerifyCacheKey(input: BatchVerificationInput): {
|
|
57
|
+
content: string;
|
|
58
|
+
classifier: string;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=verify-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-cache.d.ts","sourceRoot":"","sources":["../../src/cache/verify-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAiD,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAa9F;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,GAAE,SAAiC,GACxC,mBAAmB,CAAC,uBAAuB,CAAC,CAc9C;AAED,qDAAqD;AACrD,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,sBAAsB,GAC5B;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAOzC"}
|