circle-ir-ai 2.27.0 → 2.28.2

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 (71) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/dist/agents/mastra/workflow.d.ts +15 -0
  3. package/dist/agents/mastra/workflow.d.ts.map +1 -1
  4. package/dist/agents/mastra/workflow.js +33 -12
  5. package/dist/agents/mastra/workflow.js.map +1 -1
  6. package/dist/cache/backend.d.ts +62 -10
  7. package/dist/cache/backend.d.ts.map +1 -1
  8. package/dist/cache/backend.js +20 -4
  9. package/dist/cache/backend.js.map +1 -1
  10. package/dist/cache/backends/index.d.ts +30 -18
  11. package/dist/cache/backends/index.d.ts.map +1 -1
  12. package/dist/cache/backends/index.js +37 -29
  13. package/dist/cache/backends/index.js.map +1 -1
  14. package/dist/cache/backends/layout.d.ts +31 -0
  15. package/dist/cache/backends/layout.d.ts.map +1 -0
  16. package/dist/cache/backends/layout.js +46 -0
  17. package/dist/cache/backends/layout.js.map +1 -0
  18. package/dist/cache/backends/local-fs.d.ts +14 -11
  19. package/dist/cache/backends/local-fs.d.ts.map +1 -1
  20. package/dist/cache/backends/local-fs.js +30 -18
  21. package/dist/cache/backends/local-fs.js.map +1 -1
  22. package/dist/cache/backends/memory.d.ts +11 -6
  23. package/dist/cache/backends/memory.d.ts.map +1 -1
  24. package/dist/cache/backends/memory.js +13 -5
  25. package/dist/cache/backends/memory.js.map +1 -1
  26. package/dist/cache/backends/r2.d.ts +17 -17
  27. package/dist/cache/backends/r2.d.ts.map +1 -1
  28. package/dist/cache/backends/r2.js +14 -16
  29. package/dist/cache/backends/r2.js.map +1 -1
  30. package/dist/cache/classification-cache.d.ts +76 -34
  31. package/dist/cache/classification-cache.d.ts.map +1 -1
  32. package/dist/cache/classification-cache.js +110 -58
  33. package/dist/cache/classification-cache.js.map +1 -1
  34. package/dist/cache/coordinate.d.ts +59 -0
  35. package/dist/cache/coordinate.d.ts.map +1 -0
  36. package/dist/cache/coordinate.js +240 -0
  37. package/dist/cache/coordinate.js.map +1 -0
  38. package/dist/cache/discovery-cache.d.ts +112 -36
  39. package/dist/cache/discovery-cache.d.ts.map +1 -1
  40. package/dist/cache/discovery-cache.js +163 -42
  41. package/dist/cache/discovery-cache.js.map +1 -1
  42. package/dist/cache/tree-cache.d.ts +48 -53
  43. package/dist/cache/tree-cache.d.ts.map +1 -1
  44. package/dist/cache/tree-cache.js +87 -72
  45. package/dist/cache/tree-cache.js.map +1 -1
  46. package/dist/cache/verify-cache.d.ts +26 -21
  47. package/dist/cache/verify-cache.d.ts.map +1 -1
  48. package/dist/cache/verify-cache.js +37 -29
  49. package/dist/cache/verify-cache.js.map +1 -1
  50. package/dist/llm/ax-client.d.ts +3 -16
  51. package/dist/llm/ax-client.d.ts.map +1 -1
  52. package/dist/llm/ax-client.js +49 -2
  53. package/dist/llm/ax-client.js.map +1 -1
  54. package/dist/llm/batch-classifier.d.ts.map +1 -1
  55. package/dist/llm/batch-classifier.js +1 -0
  56. package/dist/llm/batch-classifier.js.map +1 -1
  57. package/dist/llm/config.d.ts +34 -8
  58. package/dist/llm/config.d.ts.map +1 -1
  59. package/dist/llm/config.js +52 -12
  60. package/dist/llm/config.js.map +1 -1
  61. package/dist/llm/discovery.d.ts.map +1 -1
  62. package/dist/llm/discovery.js +50 -4
  63. package/dist/llm/discovery.js.map +1 -1
  64. package/dist/llm/verification.d.ts.map +1 -1
  65. package/dist/llm/verification.js +45 -1
  66. package/dist/llm/verification.js.map +1 -1
  67. package/dist/security-scan/scanner.d.ts +15 -0
  68. package/dist/security-scan/scanner.d.ts.map +1 -1
  69. package/dist/security-scan/scanner.js +34 -0
  70. package/dist/security-scan/scanner.js.map +1 -1
  71. package/package.json +2 -2
@@ -1,62 +1,77 @@
1
1
  /**
2
2
  * Discovery Cache
3
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.
4
+ * Persistent on-disk cache for LLM discovery results per method
5
+ * (cognium-ai#134, P2 of epic #155, key reshape #158 Round 2).
10
6
  *
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:
7
+ * Sits in front of the in-memory `_discoveryDedupCache` from
8
+ * discovery.ts (#136) so identical method-level discovery requests
9
+ * survive across processes, CI runs, and developer iterations.
14
10
  *
15
- * content = className + '\\0' + methodName + '\\0' +
16
- * SHA-256(methodCode) + '\\0' + sortedCweSet
11
+ * **Key composition (Round 2 2.28.0):**
12
+ *
13
+ * content = methodSha + '\0' + sortedCweSet + '\0' + language
14
+ * + '\0' + PROMPT_VER
17
15
  * classifier = 'discovery-method'
18
- * fingerprint = getPhaseModelFingerprint(config) // includes discovery model
16
+ * fingerprint = getDiscoveryModelFingerprint() // discovery model ONLY
17
+ *
18
+ * **D6 fix:** dropped `className` and `methodName` from the key. They
19
+ * were redundant — if two different method *bodies* hash to the same
20
+ * SHA-256, they are byte-identical and the LLM's answer would be too.
21
+ * Including names also broke cache sharing on refactors that renamed
22
+ * a method without changing its body. (Names are still passed to the
23
+ * LLM as context inside the prompt; we just don't key the cache on
24
+ * them.)
25
+ *
26
+ * **D6 also adds `language`:** the same byte-identical snippet means
27
+ * very different things in Java vs Kotlin vs Scala — without language
28
+ * in the key, a Java result could be reused for a Kotlin call. Likely
29
+ * benign in practice but a correctness gap worth closing.
19
30
  *
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).
31
+ * **D2 fix:** uses the discovery-only model fingerprint instead of the
32
+ * joint disc+verify+component fingerprint, so swapping the verify
33
+ * model doesn't invalidate discovery results.
24
34
  *
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)
35
+ * Cache namespace: `discovery` (separate from `tree` and `verify`).
32
36
  *
33
37
  * Env toggles:
34
- * LLM_DISCOVERY_CACHE=0 → disable persistent discovery cache
35
- * (in-memory dedup from #136 still applies)
38
+ * LLM_DISCOVERY_CACHE=0 → disable persistent discovery cache.
36
39
  */
37
40
  import * as crypto from 'crypto';
38
41
  import * as path from 'path';
39
42
  import { ClassificationCache } from './classification-cache.js';
40
- import { getDefaultLLMConfig, getPhaseModelFingerprint } from '../llm/config.js';
41
- const DISCOVERY_CACHE_SUBDIR = path.join('.cognium', 'cache', 'discovery');
43
+ import { getDefaultLLMConfig, getDiscoveryModelFingerprint } from '../llm/config.js';
44
+ const DISCOVERY_CACHE_SUBDIR = path.join('.cognium', 'cache');
45
+ /**
46
+ * Prompt-version sentinel for discovery. Bump when the discovery prompt
47
+ * template materially changes (e.g. new few-shot examples, different
48
+ * instructions for `exploitable`, new output schema).
49
+ */
50
+ const PROMPT_VER = 'disc-v1';
42
51
  /**
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.
52
+ * Separate prompt-version sentinel for the language-specific discoverers
53
+ * (`discoverSourcesForLanguage` / `discoverSinksForLanguage`). These use
54
+ * a different prompt template (`getPrompt(language, 'discoverSources' |
55
+ * 'discoverSinks')`) than the per-CWE `discoverInMethod` flow, so they
56
+ * need an independent version dimension. cognium-ai#161.
47
57
  */
58
+ const LANG_DISCOVERER_PROMPT_VER = 'lang-disc-v1';
48
59
  let _instance = null;
49
60
  let _instanceFingerprint = '';
61
+ let _sourcesLangInstance = null;
62
+ let _sourcesLangFingerprint = '';
63
+ let _sinksLangInstance = null;
64
+ let _sinksLangFingerprint = '';
50
65
  /**
51
66
  * 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).
67
+ * fingerprint. When `LLM_DISCOVERY_CACHE=0` returns a disabled cache.
54
68
  */
55
69
  export function getDiscoveryCache(config = getDefaultLLMConfig()) {
56
70
  const enabled = process.env.LLM_DISCOVERY_CACHE !== '0';
57
- const fingerprint = getPhaseModelFingerprint(config);
71
+ const fingerprint = getDiscoveryModelFingerprint(config);
58
72
  if (!_instance || _instanceFingerprint !== fingerprint || _instance.getModelFingerprint() !== fingerprint) {
59
73
  _instance = new ClassificationCache({
74
+ namespace: 'discovery',
60
75
  cacheDir: path.join(process.cwd(), DISCOVERY_CACHE_SUBDIR),
61
76
  modelFingerprint: fingerprint,
62
77
  enabled,
@@ -69,27 +84,133 @@ export function getDiscoveryCache(config = getDefaultLLMConfig()) {
69
84
  export function resetDiscoveryCache() {
70
85
  _instance = null;
71
86
  _instanceFingerprint = '';
87
+ _sourcesLangInstance = null;
88
+ _sourcesLangFingerprint = '';
89
+ _sinksLangInstance = null;
90
+ _sinksLangFingerprint = '';
91
+ }
92
+ /**
93
+ * Get the persistent cache for `discoverSourcesForLanguage` (cognium-ai#161).
94
+ * Uses the same `'discovery'` namespace and per-phase discovery model
95
+ * fingerprint as the per-CWE cache, but a different classifier so the two
96
+ * never collide. Honors `LLM_DISCOVERY_CACHE=0` to disable.
97
+ */
98
+ export function getDiscoverSourcesLanguageCache(config = getDefaultLLMConfig()) {
99
+ const enabled = process.env.LLM_DISCOVERY_CACHE !== '0';
100
+ const fingerprint = getDiscoveryModelFingerprint(config);
101
+ if (!_sourcesLangInstance
102
+ || _sourcesLangFingerprint !== fingerprint
103
+ || _sourcesLangInstance.getModelFingerprint() !== fingerprint) {
104
+ _sourcesLangInstance = new ClassificationCache({
105
+ namespace: 'discovery',
106
+ cacheDir: path.join(process.cwd(), DISCOVERY_CACHE_SUBDIR),
107
+ modelFingerprint: fingerprint,
108
+ enabled,
109
+ });
110
+ _sourcesLangFingerprint = fingerprint;
111
+ }
112
+ return _sourcesLangInstance;
113
+ }
114
+ /**
115
+ * Get the persistent cache for `discoverSinksForLanguage` (cognium-ai#161).
116
+ * See `getDiscoverSourcesLanguageCache` for the namespace/fingerprint
117
+ * rationale.
118
+ */
119
+ export function getDiscoverSinksLanguageCache(config = getDefaultLLMConfig()) {
120
+ const enabled = process.env.LLM_DISCOVERY_CACHE !== '0';
121
+ const fingerprint = getDiscoveryModelFingerprint(config);
122
+ if (!_sinksLangInstance
123
+ || _sinksLangFingerprint !== fingerprint
124
+ || _sinksLangInstance.getModelFingerprint() !== fingerprint) {
125
+ _sinksLangInstance = new ClassificationCache({
126
+ namespace: 'discovery',
127
+ cacheDir: path.join(process.cwd(), DISCOVERY_CACHE_SUBDIR),
128
+ modelFingerprint: fingerprint,
129
+ enabled,
130
+ });
131
+ _sinksLangFingerprint = fingerprint;
132
+ }
133
+ return _sinksLangInstance;
134
+ }
135
+ /**
136
+ * Compute the `(content, classifier)` tuple for the source-discoverer
137
+ * cache. Components (in order):
138
+ * - language — Java vs Kotlin etc. (same rationale as D6 above)
139
+ * - methodSha — code edit invalidates only that entry
140
+ * - functionName — same body called as different methods is a distinct
141
+ * prompt (the name is templated into the user prompt)
142
+ * - moduleRole — passed verbatim into the prompt
143
+ * - existingSourcesSha — the "what we already found" context changes
144
+ * the prompt, so it must change the key
145
+ * - LANG_DISCOVERER_PROMPT_VER — global invalidator for the language
146
+ * discoverer prompt template
147
+ */
148
+ export function computeDiscoverSourcesLanguageCacheKey(input) {
149
+ const methodSha = crypto.createHash('sha256').update(input.code).digest('hex');
150
+ const existingSha = crypto.createHash('sha256').update(input.existingSources).digest('hex');
151
+ return {
152
+ content: [
153
+ input.language,
154
+ methodSha,
155
+ input.functionName,
156
+ input.moduleRole,
157
+ existingSha,
158
+ LANG_DISCOVERER_PROMPT_VER,
159
+ ].join('\0'),
160
+ classifier: 'discover-sources-lang',
161
+ };
162
+ }
163
+ /**
164
+ * Compute the `(content, classifier)` tuple for the sink-discoverer
165
+ * cache. Mirrors `computeDiscoverSourcesLanguageCacheKey` but uses the
166
+ * sink-prompt context (`methodCalls`, `existingSinks`) instead of the
167
+ * source-prompt context.
168
+ */
169
+ export function computeDiscoverSinksLanguageCacheKey(input) {
170
+ const methodSha = crypto.createHash('sha256').update(input.code).digest('hex');
171
+ const methodCallsSha = crypto.createHash('sha256').update(input.methodCalls).digest('hex');
172
+ const existingSha = crypto.createHash('sha256').update(input.existingSinks).digest('hex');
173
+ return {
174
+ content: [
175
+ input.language,
176
+ methodSha,
177
+ input.functionName,
178
+ methodCallsSha,
179
+ existingSha,
180
+ LANG_DISCOVERER_PROMPT_VER,
181
+ ].join('\0'),
182
+ classifier: 'discover-sinks-lang',
183
+ };
184
+ }
185
+ /** Test-only: expose the language-discoverer prompt-version sentinel. */
186
+ export function getLangDiscovererCachePromptVersion() {
187
+ return LANG_DISCOVERER_PROMPT_VER;
72
188
  }
73
189
  /**
74
190
  * Compute the `(content, classifier)` tuple for the underlying
75
191
  * ClassificationCache. The fingerprint dimension is folded in by the
76
- * cache itself (see classification-cache.ts:computeKey).
192
+ * cache itself.
77
193
  *
78
194
  * Components:
79
- * - classNamedisambiguates 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
195
+ * - methodCode SHA-256 method edit invalidates only that entry
82
196
  * - 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
197
+ * reuse each other's prompts
198
+ * - languageJava vs Kotlin etc. (D6 fix)
199
+ * - PROMPT_VER — global discovery-prompt invalidator
200
+ *
201
+ * Note: `className` / `methodName` are intentionally NOT in the key
202
+ * (D6 fix). They're prompt-context, not identity.
86
203
  */
87
204
  export function computeDiscoveryCacheKey(input) {
88
205
  const methodSha = crypto.createHash('sha256').update(input.methodCode).digest('hex');
89
206
  const sortedCweSet = [...input.targetCWEs].sort().join(',');
90
207
  return {
91
- content: `${input.className}\0${input.methodName}\0${methodSha}\0${sortedCweSet}`,
208
+ content: [methodSha, sortedCweSet, input.language, PROMPT_VER].join('\0'),
92
209
  classifier: 'discovery-method',
93
210
  };
94
211
  }
212
+ /** Test-only: expose the prompt-version sentinel. */
213
+ export function getDiscoveryCachePromptVersion() {
214
+ return PROMPT_VER;
215
+ }
95
216
  //# sourceMappingURL=discovery-cache.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"discovery-cache.js","sourceRoot":"","sources":["../../src/cache/discovery-cache.ts"],"names":[],"mappings":"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"}
@@ -2,56 +2,45 @@
2
2
  * Tree Cache
3
3
  *
4
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.
5
+ * P3 of epic #155, key reshape #158 Round 2).
8
6
  *
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.
7
+ * Pattern-match output is a pure function of file content + engine
8
+ * version + active rule set, so caching it eliminates the parse +
9
+ * AST-walk + SAST-rule firing on warm runs.
16
10
  *
17
11
  * **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
12
  *
23
- * **Key composition:**
24
- * content = SHA-256(sourceCode) + '\0' +
25
- * circleIrVersion + '\0' +
26
- * circleIrAiVersion + '\0' +
27
- * language + '\0' +
28
- * ruleSetFingerprint
13
+ * **Key composition (Round 2 — 2.28.0):**
14
+ *
15
+ * content = sourceSha + '\0' + circleIrVersion + '\0' +
16
+ * circleIrAiVersion + '\0' + language + '\0' +
17
+ * ruleSetFingerprint + '\0' + PROMPT_VER
29
18
  * classifier = 'tree-pattern-match'
30
19
  *
31
- * Any of: file edit, circle-ir bump, circle-ir-ai bump, language
32
- * change, or rule-set change cache miss.
20
+ * The composed (content, classifier) is then fed into ClassificationCache,
21
+ * which folds in `SCHEMA_VER`, the model fingerprint (`''` for tree since
22
+ * the static path doesn't call any LLM), and the coordinate hash-prefix.
33
23
  *
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).
24
+ * Any of: file edit, circle-ir bump, circle-ir-ai bump, language change,
25
+ * rule-set change, prompt-version bump, schema bump cache miss.
26
+ *
27
+ * **D1 fix (Round 2):** `computeRuleSetFingerprint` now accepts an
28
+ * optional `repoRoot` and, when the projectProfile is a Map, normalizes
29
+ * its keys to repo-relative paths before sorting. This eliminates the
30
+ * absolute-path leak that caused cross-runner tree-cache misses in the
31
+ * 2-repo R2 validation: runner-A wrote keys hashing
32
+ * `/tmp/r2-validation/repos/A/...`, runner-B wrote keys hashing
33
+ * `/tmp/r2-validation/repos/B/...`, and the bucket doubled instead of
34
+ * sharing.
38
35
  *
39
36
  * **Env toggles:**
40
37
  * 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
38
  */
48
39
  import { ClassificationCache } from './classification-cache.js';
49
40
  import type { TaintSource, TaintSink, TypeInfo, DFG, SastFinding } from 'circle-ir';
50
41
  /**
51
42
  * 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.
43
+ * `src/agents/mastra/workflow.ts`. All fields are plain JSON-serializable.
55
44
  */
56
45
  export interface PatternMatchResult {
57
46
  patternSources: TaintSource[];
@@ -63,7 +52,7 @@ export interface PatternMatchResult {
63
52
  }
64
53
  /**
65
54
  * 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).
55
+ * disabled cache that no-ops on get/set.
67
56
  */
68
57
  export declare function getTreeCache(): ClassificationCache<PatternMatchResult>;
69
58
  /** Reset the module-scoped singleton — test-only. */
@@ -71,18 +60,6 @@ export declare function resetTreeCache(): void;
71
60
  /**
72
61
  * Compute the `(content, classifier)` tuple for the underlying
73
62
  * 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
63
  */
87
64
  export declare function computeTreeCacheKey(input: {
88
65
  sourceCode: string;
@@ -93,19 +70,37 @@ export declare function computeTreeCacheKey(input: {
93
70
  classifier: string;
94
71
  };
95
72
  /**
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.
73
+ * Compute a stable fingerprint over an arbitrary rule-set descriptor.
74
+ *
75
+ * Order-stable for `disabledPasses` so re-ordering `--disable a,b` vs
76
+ * `--disable b,a` still hits the cache.
77
+ *
78
+ * D1 (Round 2): when `projectProfile` is a `Map<string, ProjectProfile>`
79
+ * keyed by absolute file paths, the keys are relativized to `repoRoot`
80
+ * and sorted. Without this, two runners that check out the same repo to
81
+ * different absolute paths produce different fingerprints and the
82
+ * cross-runner R2 cache never shares.
83
+ *
84
+ * When `repoRoot` is omitted and `projectProfile` is a Map, we fall back
85
+ * to dropping the keys entirely and hashing only the sorted *values*
86
+ * (still order-stable, still cross-runner-safe; loses the per-file
87
+ * granularity but no caller currently needs it for fingerprinting).
101
88
  */
102
89
  export declare function computeRuleSetFingerprint(input: {
103
90
  disabledPasses?: string[];
104
91
  projectProfile?: unknown;
92
+ repoRoot?: string;
105
93
  }): string;
94
+ /**
95
+ * Render a project-profile descriptor as a stable, cross-runner-safe
96
+ * string. Exported for the test suite to assert on the D1 fix.
97
+ */
98
+ export declare function normalizeProjectProfile(profile: unknown, repoRoot?: string): string;
106
99
  /** Test-only: expose the captured engine versions for assertions. */
107
100
  export declare function getTreeCacheVersions(): {
108
101
  circleIrVersion: string;
109
102
  circleIrAiVersion: string;
110
103
  };
104
+ /** Test-only: expose the prompt-version sentinel. */
105
+ export declare function getTreeCachePromptVersion(): string;
111
106
  //# sourceMappingURL=tree-cache.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"tree-cache.d.ts","sourceRoot":"","sources":["../../src/cache/tree-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;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;AAcpF;;;GAGG;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;AAkCD;;;GAGG;AACH,wBAAgB,YAAY,IAAI,mBAAmB,CAAC,kBAAkB,CAAC,CAYtE;AAED,qDAAqD;AACrD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;GAGG;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,CAc1C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAKT;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBnF;AAED,qEAAqE;AACrE,wBAAgB,oBAAoB,IAAI;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAKA;AAED,qDAAqD;AACrD,wBAAgB,yBAAyB,IAAI,MAAM,CAElD"}