circle-ir-ai 2.22.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 (41) hide show
  1. package/CHANGELOG.md +307 -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/discovery-cache.d.ts +71 -0
  6. package/dist/cache/discovery-cache.d.ts.map +1 -0
  7. package/dist/cache/discovery-cache.js +95 -0
  8. package/dist/cache/discovery-cache.js.map +1 -0
  9. package/dist/cache/tree-cache.d.ts +111 -0
  10. package/dist/cache/tree-cache.d.ts.map +1 -0
  11. package/dist/cache/tree-cache.js +171 -0
  12. package/dist/cache/tree-cache.js.map +1 -0
  13. package/dist/cache/verify-cache.d.ts +60 -0
  14. package/dist/cache/verify-cache.d.ts.map +1 -0
  15. package/dist/cache/verify-cache.js +103 -0
  16. package/dist/cache/verify-cache.js.map +1 -0
  17. package/dist/cluster/cluster.d.ts +174 -0
  18. package/dist/cluster/cluster.d.ts.map +1 -0
  19. package/dist/cluster/cluster.js +392 -0
  20. package/dist/cluster/cluster.js.map +1 -0
  21. package/dist/cluster/index.d.ts +11 -0
  22. package/dist/cluster/index.d.ts.map +1 -0
  23. package/dist/cluster/index.js +10 -0
  24. package/dist/cluster/index.js.map +1 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/llm/call-logger.d.ts +36 -0
  30. package/dist/llm/call-logger.d.ts.map +1 -1
  31. package/dist/llm/call-logger.js +36 -0
  32. package/dist/llm/call-logger.js.map +1 -1
  33. package/dist/llm/discovery.d.ts +1 -1
  34. package/dist/llm/discovery.d.ts.map +1 -1
  35. package/dist/llm/discovery.js +39 -0
  36. package/dist/llm/discovery.js.map +1 -1
  37. package/dist/llm/verification.d.ts +1 -1
  38. package/dist/llm/verification.d.ts.map +1 -1
  39. package/dist/llm/verification.js +39 -0
  40. package/dist/llm/verification.js.map +1 -1
  41. package/package.json +1 -1
@@ -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"}
@@ -0,0 +1,103 @@
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 * as crypto from 'crypto';
35
+ import * as path from 'path';
36
+ import { ClassificationCache } from './classification-cache.js';
37
+ import { getDefaultLLMConfig, getPhaseModelFingerprint } from '../llm/config.js';
38
+ const VERIFY_CACHE_SUBDIR = path.join('.cognium', 'cache', 'verify');
39
+ /**
40
+ * Module-scoped singleton — distinct from the discovery-phase singleton
41
+ * in `classification-cache.ts` so the two phases can carry different
42
+ * model fingerprints (e.g. cheap discovery + strong verifier) without
43
+ * thrashing each other's cache instance.
44
+ */
45
+ let _instance = null;
46
+ let _instanceFingerprint = '';
47
+ /**
48
+ * Get the verify-phase persistent cache, keyed by per-phase model
49
+ * fingerprint. When `LLM_VERIFY_CACHE=0` returns a disabled cache that
50
+ * no-ops on get/set (callers don't need to branch).
51
+ */
52
+ export function getVerifyCache(config = getDefaultLLMConfig()) {
53
+ const enabled = process.env.LLM_VERIFY_CACHE !== '0';
54
+ const fingerprint = getPhaseModelFingerprint(config);
55
+ if (!_instance || _instanceFingerprint !== fingerprint || _instance.getModelFingerprint() !== fingerprint) {
56
+ _instance = new ClassificationCache({
57
+ cacheDir: path.join(process.cwd(), VERIFY_CACHE_SUBDIR),
58
+ modelFingerprint: fingerprint,
59
+ enabled,
60
+ });
61
+ _instanceFingerprint = fingerprint;
62
+ }
63
+ return _instance;
64
+ }
65
+ /** Reset the module-scoped singleton — test-only. */
66
+ export function resetVerifyCache() {
67
+ _instance = null;
68
+ _instanceFingerprint = '';
69
+ }
70
+ /**
71
+ * Stable sort over the source/sink pair-set; never mutates the caller's
72
+ * arrays. Mirrors `_serializeVerifierPairs` in verification.ts so
73
+ * persistent + in-memory dedup agree on what counts as "the same call".
74
+ */
75
+ function serializeVerifyPairs(input) {
76
+ const sources = [...input.sources]
77
+ .map((s) => `${s.line}|${s.type}|${s.variable ?? ''}`)
78
+ .sort();
79
+ const sinks = [...input.sinks]
80
+ .map((s) => `${s.line}|${s.type}|${s.cwe}|${s.method ?? ''}`)
81
+ .sort();
82
+ return JSON.stringify({ sources, sinks });
83
+ }
84
+ /**
85
+ * Compute the `(content, classifier)` tuple for the underlying
86
+ * ClassificationCache. The fingerprint dimension is folded in by the
87
+ * cache itself (see classification-cache.ts:computeKey).
88
+ *
89
+ * Components:
90
+ * - file SHA-256 — file edit invalidates only that file's entry
91
+ * - pairs fingerprint — sort-invariant over the source/sink set
92
+ * - classifier 'verify-batch' — namespace for future
93
+ * 'verify-pair' / 'verify-flow' tiers without collision
94
+ */
95
+ export function computeVerifyCacheKey(input) {
96
+ const fileSha = crypto.createHash('sha256').update(input.code).digest('hex');
97
+ const pairsFp = crypto.createHash('sha256').update(serializeVerifyPairs(input)).digest('hex');
98
+ return {
99
+ content: `${fileSha}\0${pairsFp}`,
100
+ classifier: 'verify-batch',
101
+ };
102
+ }
103
+ //# sourceMappingURL=verify-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-cache.js","sourceRoot":"","sources":["../../src/cache/verify-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;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,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;AAErE;;;;;GAKG;AACH,IAAI,SAAS,GAAwD,IAAI,CAAC;AAC1E,IAAI,oBAAoB,GAAG,EAAE,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,SAAoB,mBAAmB,EAAE;IAEzC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,GAAG,CAAC;IACrD,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,CAA0B;YAC3D,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,mBAAmB,CAAC;YACvD,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,gBAAgB;IAC9B,SAAS,GAAG,IAAI,CAAC;IACjB,oBAAoB,GAAG,EAAE,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,KAA6B;IACzD,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;SAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;SACrD,IAAI,EAAE,CAAC;IACV,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;SAC5D,IAAI,EAAE,CAAC;IACV,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAA6B;IAE7B,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9F,OAAO;QACL,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,EAAE;QACjC,UAAU,EAAE,cAAc;KAC3B,CAAC;AACJ,CAAC"}