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.
Files changed (48) hide show
  1. package/CHANGELOG.md +351 -0
  2. package/dist/agents/mastra/workflow.d.ts.map +1 -1
  3. package/dist/agents/mastra/workflow.js +41 -1
  4. package/dist/agents/mastra/workflow.js.map +1 -1
  5. package/dist/cache/classification-cache.d.ts +27 -2
  6. package/dist/cache/classification-cache.d.ts.map +1 -1
  7. package/dist/cache/classification-cache.js +37 -5
  8. package/dist/cache/classification-cache.js.map +1 -1
  9. package/dist/cache/discovery-cache.d.ts +71 -0
  10. package/dist/cache/discovery-cache.d.ts.map +1 -0
  11. package/dist/cache/discovery-cache.js +95 -0
  12. package/dist/cache/discovery-cache.js.map +1 -0
  13. package/dist/cache/tree-cache.d.ts +111 -0
  14. package/dist/cache/tree-cache.d.ts.map +1 -0
  15. package/dist/cache/tree-cache.js +171 -0
  16. package/dist/cache/tree-cache.js.map +1 -0
  17. package/dist/cache/verify-cache.d.ts +60 -0
  18. package/dist/cache/verify-cache.d.ts.map +1 -0
  19. package/dist/cache/verify-cache.js +103 -0
  20. package/dist/cache/verify-cache.js.map +1 -0
  21. package/dist/cluster/cluster.d.ts +174 -0
  22. package/dist/cluster/cluster.d.ts.map +1 -0
  23. package/dist/cluster/cluster.js +392 -0
  24. package/dist/cluster/cluster.js.map +1 -0
  25. package/dist/cluster/index.d.ts +11 -0
  26. package/dist/cluster/index.d.ts.map +1 -0
  27. package/dist/cluster/index.js +10 -0
  28. package/dist/cluster/index.js.map +1 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +4 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/llm/batch-classifier.d.ts.map +1 -1
  34. package/dist/llm/batch-classifier.js +13 -4
  35. package/dist/llm/batch-classifier.js.map +1 -1
  36. package/dist/llm/call-logger.d.ts +36 -0
  37. package/dist/llm/call-logger.d.ts.map +1 -1
  38. package/dist/llm/call-logger.js +36 -0
  39. package/dist/llm/call-logger.js.map +1 -1
  40. package/dist/llm/discovery.d.ts +1 -1
  41. package/dist/llm/discovery.d.ts.map +1 -1
  42. package/dist/llm/discovery.js +39 -0
  43. package/dist/llm/discovery.js.map +1 -1
  44. package/dist/llm/verification.d.ts +1 -1
  45. package/dist/llm/verification.d.ts.map +1 -1
  46. package/dist/llm/verification.js +39 -0
  47. package/dist/llm/verification.js.map +1 -1
  48. 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 (content hash + classifier name).
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
- return crypto.createHash('sha256').update(content + '\0' + classifier).digest('hex');
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
- if (!classificationCache) {
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":"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"}
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAGhE,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AACjE,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,8BAA8B;AAiBjF;;;;GAIG;AACH,IAAI,SAAS,GAAmD,IAAI,CAAC;AAErE;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,IAAY,EAAE,OAAe;IACpD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACvC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,SAAS,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,gBAAgB,GAAG,eAAe,CAAC,wBAAwB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpF,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE;IAC/B,sEAAsE;IACtE,sDAAsD;IACtD,IAAI,CAAC;QACH,wEAAwE;QACxE,MAAM,UAAU,GAAG;YACjB,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,uBAAuB,CAAC;YACxE,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,oBAAoB,CAAC;SACtE,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;gBACnD,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;oBAC/C,OAAO,GAAG,CAAC,OAAiB,CAAC;gBAC/B,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC,EAAE,CAAC;AAEL;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG,CAAC;IACnD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,IAAI,mBAAmB,CAAqB;YACtD,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,iBAAiB,CAAC;YACrD,MAAM,EAAE,iBAAiB;YACzB,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,cAAc;IAC5B,SAAS,GAAG,IAAI,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAInC;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,OAAO,GAAG,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC;IAC/C,OAAO;QACL,OAAO,EAAE,GAAG,SAAS,KAAK,gBAAgB,KAAK,kBAAkB,KAAK,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE;QAClG,UAAU,EAAE,oBAAoB;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAGzC;IACC,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,qEAAqE;IACrE,2EAA2E;IAC3E,sEAAsE;IACtE,yDAAyD;IACzD,MAAM,OAAO,GAAG,KAAK,CAAC,cAAc,KAAK,SAAS;QAChD,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACnC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnF,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,oBAAoB;IAIlC,OAAO;QACL,eAAe,EAAE,gBAAgB;QACjC,iBAAiB,EAAE,kBAAkB;KACtC,CAAC;AACJ,CAAC"}
@@ -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"}