circle-ir-ai 2.26.0 → 2.28.1

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 +205 -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 +104 -0
  7. package/dist/cache/backend.d.ts.map +1 -0
  8. package/dist/cache/backend.js +35 -0
  9. package/dist/cache/backend.js.map +1 -0
  10. package/dist/cache/backends/index.d.ts +44 -0
  11. package/dist/cache/backends/index.d.ts.map +1 -0
  12. package/dist/cache/backends/index.js +84 -0
  13. package/dist/cache/backends/index.js.map +1 -0
  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 +34 -0
  19. package/dist/cache/backends/local-fs.d.ts.map +1 -0
  20. package/dist/cache/backends/local-fs.js +113 -0
  21. package/dist/cache/backends/local-fs.js.map +1 -0
  22. package/dist/cache/backends/memory.d.ts +24 -0
  23. package/dist/cache/backends/memory.d.ts.map +1 -0
  24. package/dist/cache/backends/memory.js +38 -0
  25. package/dist/cache/backends/memory.js.map +1 -0
  26. package/dist/cache/backends/r2.d.ts +51 -0
  27. package/dist/cache/backends/r2.d.ts.map +1 -0
  28. package/dist/cache/backends/r2.js +114 -0
  29. package/dist/cache/backends/r2.js.map +1 -0
  30. package/dist/cache/classification-cache.d.ts +90 -34
  31. package/dist/cache/classification-cache.d.ts.map +1 -1
  32. package/dist/cache/classification-cache.js +131 -106
  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 +40 -36
  39. package/dist/cache/discovery-cache.d.ts.map +1 -1
  40. package/dist/cache/discovery-cache.js +50 -42
  41. package/dist/cache/discovery-cache.js.map +1 -1
  42. package/dist/cache/index.d.ts +4 -0
  43. package/dist/cache/index.d.ts.map +1 -1
  44. package/dist/cache/index.js +2 -0
  45. package/dist/cache/index.js.map +1 -1
  46. package/dist/cache/tree-cache.d.ts +48 -53
  47. package/dist/cache/tree-cache.d.ts.map +1 -1
  48. package/dist/cache/tree-cache.js +87 -72
  49. package/dist/cache/tree-cache.js.map +1 -1
  50. package/dist/cache/verify-cache.d.ts +26 -21
  51. package/dist/cache/verify-cache.d.ts.map +1 -1
  52. package/dist/cache/verify-cache.js +37 -29
  53. package/dist/cache/verify-cache.js.map +1 -1
  54. package/dist/llm/batch-classifier.d.ts.map +1 -1
  55. package/dist/llm/batch-classifier.js +3 -2
  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 +52 -6
  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 +51 -9
  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 +3 -2
@@ -2,69 +2,125 @@
2
2
  * Classification Cache
3
3
  *
4
4
  * Caches LLM classification results by composite key
5
- * (content hash + classifier name + per-phase model fingerprint).
6
- * Optimized for batch classifier workloads with shorter TTL than FileCache.
5
+ * (schemaVer + content hash + classifier name + per-phase model fingerprint
6
+ * + optional coordinate hash-prefix).
7
7
  *
8
- * Key differences from FileCache:
9
- * - Composite key: SHA-256(content + '\0' + classifierName + '\0' + modelFingerprint)
10
- * - Shorter default TTL (24h vs 7d)
11
- * - Cache directory: .cognium/cache/
12
- * - Hit/miss tracking for observability
8
+ * Storage is delegated to a `CacheBackend` (cognium-ai#158). Default
9
+ * = `LocalFSBackend`. Selecting `LLM_CACHE_BACKEND=r2` swaps to Cloudflare
10
+ * R2 transparently so two runners on different machines share the same
11
+ * cache.
13
12
  *
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.
13
+ * # Round 2 (2.28.0) key shape
14
+ *
15
+ * sha256(
16
+ * 'cic:v2' // SCHEMA_VER bump on breaking key changes
17
+ * + '\0' + namespace // 'tree' | 'verify' | 'discovery'
18
+ * + '\0' + classifier
19
+ * + '\0' + content
20
+ * + '\0' + modelFingerprint // per-phase, see ../llm/config.ts
21
+ * + '\0' + coordinateHashPrefix // empty for non-tree, or _unowned
22
+ * )
23
+ *
24
+ * Why `namespace` is in the hash:
25
+ * So a tree-cache entry can never collide with a verify-cache entry
26
+ * even if a future caller happens to choose the same classifier name.
27
+ *
28
+ * Why `coordinate` is in the hash (when present):
29
+ * So `commons-lang3@3.12.0` and `commons-lang3@3.13.0` produce
30
+ * different SHAs for the same byte-identical file (we WANT them
31
+ * distinct because the surrounding rules / version-conditioned
32
+ * profile could differ). The coordinate is also reflected in the
33
+ * backend path layout — see `./backends/layout.ts` — so the cache
34
+ * is browseable in R2 dashboard by package.
21
35
  *
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.
36
+ * Model dimension (cognium-ai#151 P0 spike):
37
+ * `modelFingerprint` is the per-phase identity from
38
+ * `getDiscoveryModelFingerprint()` / `getVerifyModelFingerprint()` /
39
+ * `getComponentModelFingerprint()` in `../llm/config.ts`. Each cache
40
+ * instance uses the fingerprint scoped to its phase so swapping the
41
+ * discovery model doesn't invalidate verify results and vice versa
42
+ * (#158 D2).
43
+ */
44
+ import type { CacheBackend, CacheNamespace } from './backend.js';
45
+ import type { PackageCoordinate } from './coordinate.js';
46
+ /**
47
+ * Wire-format version. Bump on every breaking change to the key composition
48
+ * OR the on-disk envelope shape. Operators rolling back to an older
49
+ * circle-ir-ai will see misses against newer SCHEMA_VER entries and
50
+ * re-populate cleanly.
26
51
  */
52
+ export declare const SCHEMA_VER = "cic:v2";
27
53
  export interface ClassificationCacheOptions {
28
- /** Cache directory (default: .cognium/cache/ in project root) */
54
+ /**
55
+ * Logical cache namespace. Determines backend path layout AND whether
56
+ * the cache participates in the coordinate-aware tree layout.
57
+ *
58
+ * Defaults to `'classifier'` for generic LLM classification callers
59
+ * (batch-classifier, trust passes) that don't care about the namespace
60
+ * dimension. The three SAST caches (`tree-cache.ts`, `verify-cache.ts`,
61
+ * `discovery-cache.ts`) always pass their own namespace explicitly.
62
+ */
63
+ namespace?: CacheNamespace;
64
+ /** Cache directory (default: .cognium/cache/ in project root). For
65
+ * non-fs backends this is interpreted as a sub-prefix only. */
29
66
  cacheDir?: string;
30
67
  /** Maximum age of entries in ms (default: 24h) */
31
68
  maxAge?: number;
32
69
  /** Enable/disable cache (default: true) */
33
70
  enabled?: boolean;
34
71
  /**
35
- * Per-phase model identity for cache namespacing (cognium-ai#151).
36
- * Typically the output of `getPhaseModelFingerprint(config)` from
37
- * `../llm/config.ts`. Two scans with different discovery / verification
38
- * models will produce different cache keys and not poison each other.
39
- * Defaults to empty string (legacy 2-component key shape).
72
+ * Per-phase model identity for cache namespacing (cognium-ai#151 / #158 D2).
73
+ * Tree-cache passes the discovery+verify joint fingerprint; verify-cache
74
+ * passes the verify-only fingerprint; discovery-cache passes the
75
+ * discovery-only fingerprint. See `../llm/config.ts`.
40
76
  */
41
77
  modelFingerprint?: string;
78
+ /**
79
+ * Override the backend instance (tests). When omitted we build one
80
+ * via `makeBackend(cacheDir, namespace)` based on `LLM_CACHE_BACKEND` env.
81
+ */
82
+ backend?: CacheBackend;
83
+ }
84
+ /**
85
+ * Per-get/set call extras. `coordinate` is honored only for the
86
+ * `tree` namespace; passing it elsewhere is a no-op so callers don't
87
+ * have to branch.
88
+ */
89
+ export interface CacheCallOptions {
90
+ coordinate?: PackageCoordinate;
42
91
  }
43
92
  export declare class ClassificationCache<T = unknown> {
93
+ private namespace;
44
94
  private cacheDir;
45
95
  private maxAge;
46
96
  private enabled;
47
97
  private modelFingerprint;
98
+ private backend;
48
99
  private memoryCache;
49
100
  private hits;
50
101
  private misses;
51
102
  constructor(options?: ClassificationCacheOptions);
52
103
  /** Per-phase model identity used in cache key (cognium-ai#151). */
53
104
  getModelFingerprint(): string;
54
- private computeKey;
55
- private getCachePath;
56
- private ensureCacheDir;
105
+ /** Backend kind identifier (`local-fs` | `memory` | `r2`). Observability. */
106
+ getBackendKind(): string;
107
+ /** Logical namespace (`tree` | `verify` | `discovery`). Observability. */
108
+ getNamespace(): CacheNamespace;
109
+ private computeDigest;
110
+ private toCacheKey;
57
111
  private isValid;
58
- get(content: string, classifier: string): T | null;
59
- set(content: string, classifier: string, result: T): void;
60
- has(content: string, classifier: string): boolean;
61
- clear(): void;
62
- stats(): {
112
+ get(content: string, classifier: string, opts?: CacheCallOptions): Promise<T | null>;
113
+ set(content: string, classifier: string, result: T, opts?: CacheCallOptions): Promise<void>;
114
+ has(content: string, classifier: string, opts?: CacheCallOptions): Promise<boolean>;
115
+ clear(): Promise<void>;
116
+ stats(): Promise<{
117
+ namespace: CacheNamespace;
63
118
  memoryEntries: number;
64
119
  diskEntries: number;
65
120
  hitRate: number;
66
- };
121
+ backend: string;
122
+ }>;
67
123
  }
68
- export declare function getClassificationCache(options?: ClassificationCacheOptions): ClassificationCache;
124
+ export declare function getClassificationCache<T = unknown>(options?: ClassificationCacheOptions): ClassificationCache<T>;
69
125
  export declare function resetClassificationCache(): void;
70
126
  //# sourceMappingURL=classification-cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"classification-cache.d.ts","sourceRoot":"","sources":["../../src/cache/classification-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAUD,qBAAa,mBAAmB,CAAC,CAAC,GAAG,OAAO;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAK;gBAEP,OAAO,GAAE,0BAA+B;IAWpD,mEAAmE;IACnE,mBAAmB,IAAI,MAAM;IAI7B,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,OAAO;IAIf,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAmClD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI;IAwBzD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAIjD,KAAK,IAAI,IAAI;IAeb,KAAK,IAAI;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CA4BzE;AASD,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,mBAAmB,CAMhG;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
1
+ {"version":3,"file":"classification-cache.d.ts","sourceRoot":"","sources":["../../src/cache/classification-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAY,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGzD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,WAAW,CAAC;AAEnC,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;oEACgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAWD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,qBAAa,mBAAmB,CAAC,CAAC,GAAG,OAAO;IAC1C,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAK;gBAEP,OAAO,GAAE,0BAA+B;IASpD,mEAAmE;IACnE,mBAAmB,IAAI,MAAM;IAI7B,6EAA6E;IAC7E,cAAc,IAAI,MAAM;IAIxB,0EAA0E;IAC1E,YAAY,IAAI,cAAc;IAI9B,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,OAAO;IAKT,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAqCpF,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB3F,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAInF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,KAAK,IAAI,OAAO,CAAC;QACrB,SAAS,EAAE,cAAc,CAAC;QAC1B,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CAcH;AASD,wBAAgB,sBAAsB,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,GAAE,0BAA+B,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAUpH;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
@@ -2,192 +2,217 @@
2
2
  * Classification Cache
3
3
  *
4
4
  * Caches LLM classification results by composite key
5
- * (content hash + classifier name + per-phase model fingerprint).
6
- * Optimized for batch classifier workloads with shorter TTL than FileCache.
5
+ * (schemaVer + content hash + classifier name + per-phase model fingerprint
6
+ * + optional coordinate hash-prefix).
7
7
  *
8
- * Key differences from FileCache:
9
- * - Composite key: SHA-256(content + '\0' + classifierName + '\0' + modelFingerprint)
10
- * - Shorter default TTL (24h vs 7d)
11
- * - Cache directory: .cognium/cache/
12
- * - Hit/miss tracking for observability
8
+ * Storage is delegated to a `CacheBackend` (cognium-ai#158). Default
9
+ * = `LocalFSBackend`. Selecting `LLM_CACHE_BACKEND=r2` swaps to Cloudflare
10
+ * R2 transparently so two runners on different machines share the same
11
+ * cache.
13
12
  *
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.
13
+ * # Round 2 (2.28.0) key shape
14
+ *
15
+ * sha256(
16
+ * 'cic:v2' // SCHEMA_VER bump on breaking key changes
17
+ * + '\0' + namespace // 'tree' | 'verify' | 'discovery'
18
+ * + '\0' + classifier
19
+ * + '\0' + content
20
+ * + '\0' + modelFingerprint // per-phase, see ../llm/config.ts
21
+ * + '\0' + coordinateHashPrefix // empty for non-tree, or _unowned
22
+ * )
23
+ *
24
+ * Why `namespace` is in the hash:
25
+ * So a tree-cache entry can never collide with a verify-cache entry
26
+ * even if a future caller happens to choose the same classifier name.
21
27
  *
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.
28
+ * Why `coordinate` is in the hash (when present):
29
+ * So `commons-lang3@3.12.0` and `commons-lang3@3.13.0` produce
30
+ * different SHAs for the same byte-identical file (we WANT them
31
+ * distinct because the surrounding rules / version-conditioned
32
+ * profile could differ). The coordinate is also reflected in the
33
+ * backend path layout — see `./backends/layout.ts` — so the cache
34
+ * is browseable in R2 dashboard by package.
35
+ *
36
+ * Model dimension (cognium-ai#151 P0 spike):
37
+ * `modelFingerprint` is the per-phase identity from
38
+ * `getDiscoveryModelFingerprint()` / `getVerifyModelFingerprint()` /
39
+ * `getComponentModelFingerprint()` in `../llm/config.ts`. Each cache
40
+ * instance uses the fingerprint scoped to its phase so swapping the
41
+ * discovery model doesn't invalidate verify results and vice versa
42
+ * (#158 D2).
26
43
  */
27
- import * as fs from 'fs';
28
44
  import * as path from 'path';
29
45
  import * as crypto from 'crypto';
46
+ import { makeBackend } from './backends/index.js';
47
+ import { coordinateSegments } from './coordinate.js';
48
+ /**
49
+ * Wire-format version. Bump on every breaking change to the key composition
50
+ * OR the on-disk envelope shape. Operators rolling back to an older
51
+ * circle-ir-ai will see misses against newer SCHEMA_VER entries and
52
+ * re-populate cleanly.
53
+ */
54
+ export const SCHEMA_VER = 'cic:v2';
30
55
  const DEFAULT_MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours
31
56
  export class ClassificationCache {
57
+ namespace;
32
58
  cacheDir;
33
59
  maxAge;
34
60
  enabled;
35
61
  modelFingerprint;
62
+ backend;
36
63
  memoryCache = new Map();
37
64
  hits = 0;
38
65
  misses = 0;
39
66
  constructor(options = {}) {
67
+ this.namespace = options.namespace ?? 'classifier';
40
68
  this.cacheDir = options.cacheDir || path.join(process.cwd(), '.cognium', 'cache');
41
69
  this.maxAge = options.maxAge ?? DEFAULT_MAX_AGE;
42
70
  this.enabled = options.enabled ?? true;
43
71
  this.modelFingerprint = options.modelFingerprint ?? '';
44
- if (this.enabled) {
45
- this.ensureCacheDir();
46
- }
72
+ this.backend = options.backend ?? makeBackend(this.cacheDir, this.namespace);
47
73
  }
48
74
  /** Per-phase model identity used in cache key (cognium-ai#151). */
49
75
  getModelFingerprint() {
50
76
  return this.modelFingerprint;
51
77
  }
52
- computeKey(content, classifier) {
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');
78
+ /** Backend kind identifier (`local-fs` | `memory` | `r2`). Observability. */
79
+ getBackendKind() {
80
+ return this.backend.kind;
61
81
  }
62
- getCachePath(key) {
63
- const subdir = key.substring(0, 2);
64
- return path.join(this.cacheDir, subdir, `${key}.json`);
82
+ /** Logical namespace (`tree` | `verify` | `discovery`). Observability. */
83
+ getNamespace() {
84
+ return this.namespace;
65
85
  }
66
- ensureCacheDir() {
67
- if (!fs.existsSync(this.cacheDir)) {
68
- fs.mkdirSync(this.cacheDir, { recursive: true });
86
+ computeDigest(content, classifier, coordinate) {
87
+ // Coordinate hash-prefix only applies to the `tree` namespace.
88
+ // Including it in the digest (not just the path) is what guarantees
89
+ // a fileSHA collision across packages still produces distinct keys.
90
+ const coordPart = (this.namespace === 'tree' && coordinate)
91
+ ? coordinateSegments(coordinate).hashPrefix
92
+ : '';
93
+ const parts = SCHEMA_VER
94
+ + '\0' + this.namespace
95
+ + '\0' + classifier
96
+ + '\0' + content
97
+ + '\0' + this.modelFingerprint
98
+ + '\0' + coordPart;
99
+ return crypto.createHash('sha256').update(parts).digest('hex');
100
+ }
101
+ toCacheKey(digest, coordinate) {
102
+ const key = { namespace: this.namespace, digest };
103
+ if (this.namespace === 'tree' && coordinate) {
104
+ key.coordinate = coordinate;
69
105
  }
106
+ return key;
70
107
  }
71
108
  isValid(entry) {
109
+ if (entry.schemaVer !== SCHEMA_VER)
110
+ return false;
72
111
  return (Date.now() - entry.timestamp) <= this.maxAge;
73
112
  }
74
- get(content, classifier) {
113
+ async get(content, classifier, opts) {
75
114
  if (!this.enabled)
76
115
  return null;
77
- const key = this.computeKey(content, classifier);
78
- // Memory cache
79
- const memEntry = this.memoryCache.get(key);
116
+ const digest = this.computeDigest(content, classifier, opts?.coordinate);
117
+ const cacheKey = this.toCacheKey(digest, opts?.coordinate);
118
+ // Memory cache (process-local hot path)
119
+ const memEntry = this.memoryCache.get(digest);
80
120
  if (memEntry && this.isValid(memEntry)) {
81
121
  this.hits++;
82
122
  return memEntry.result;
83
123
  }
84
- // Disk cache
85
- const cachePath = this.getCachePath(key);
124
+ // Backend (disk / R2 / etc.)
86
125
  try {
87
- if (fs.existsSync(cachePath)) {
88
- const data = fs.readFileSync(cachePath, 'utf-8');
89
- const entry = JSON.parse(data);
126
+ const raw = await this.backend.get(cacheKey);
127
+ if (raw !== null) {
128
+ const entry = JSON.parse(raw);
90
129
  if (this.isValid(entry)) {
91
- this.memoryCache.set(key, entry);
130
+ this.memoryCache.set(digest, entry);
92
131
  this.hits++;
93
132
  return entry.result;
94
133
  }
95
- else {
96
- fs.unlinkSync(cachePath);
134
+ // Stale OR schemaVer-mismatch — drop. We don't await the deletion
135
+ // to keep the read path fast.
136
+ if (this.backend.delete) {
137
+ void this.backend.delete(cacheKey);
97
138
  }
98
139
  }
99
140
  }
100
141
  catch {
101
- // Ignore read errors
142
+ // Decode / I/O error: treat as miss.
102
143
  }
103
144
  this.misses++;
104
145
  return null;
105
146
  }
106
- set(content, classifier, result) {
147
+ async set(content, classifier, result, opts) {
107
148
  if (!this.enabled)
108
149
  return;
109
- const key = this.computeKey(content, classifier);
110
- const entry = { key, timestamp: Date.now(), result };
111
- this.memoryCache.set(key, entry);
112
- // #31: atomic write (tmp + rename) so concurrent processes don't
113
- // corrupt each other's cache files. Same pattern as file-cache.ts.
114
- const cachePath = this.getCachePath(key);
115
- const cacheSubdir = path.dirname(cachePath);
116
- const tmpPath = `${cachePath}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 8)}`;
150
+ const digest = this.computeDigest(content, classifier, opts?.coordinate);
151
+ const cacheKey = this.toCacheKey(digest, opts?.coordinate);
152
+ const entry = {
153
+ schemaVer: SCHEMA_VER,
154
+ key: digest,
155
+ timestamp: Date.now(),
156
+ result,
157
+ };
158
+ this.memoryCache.set(digest, entry);
117
159
  try {
118
- if (!fs.existsSync(cacheSubdir)) {
119
- fs.mkdirSync(cacheSubdir, { recursive: true });
120
- }
121
- fs.writeFileSync(tmpPath, JSON.stringify(entry));
122
- fs.renameSync(tmpPath, cachePath);
160
+ await this.backend.set(cacheKey, JSON.stringify(entry));
123
161
  }
124
162
  catch {
125
- try {
126
- fs.unlinkSync(tmpPath);
127
- }
128
- catch { /* ignore */ }
163
+ // Cache write is best-effort; never fail the caller.
129
164
  }
130
165
  }
131
- has(content, classifier) {
132
- return this.get(content, classifier) !== null;
166
+ async has(content, classifier, opts) {
167
+ return (await this.get(content, classifier, opts)) !== null;
133
168
  }
134
- clear() {
169
+ async clear() {
135
170
  this.memoryCache.clear();
136
171
  this.hits = 0;
137
172
  this.misses = 0;
138
- if (fs.existsSync(this.cacheDir)) {
173
+ if (this.backend.clear) {
139
174
  try {
140
- fs.rmSync(this.cacheDir, { recursive: true, force: true });
141
- this.ensureCacheDir();
142
- }
143
- catch {
144
- // Ignore errors
175
+ await this.backend.clear();
145
176
  }
177
+ catch { /* ignore */ }
146
178
  }
147
179
  }
148
- stats() {
180
+ async stats() {
149
181
  let diskEntries = 0;
150
- if (fs.existsSync(this.cacheDir)) {
182
+ if (this.backend.count) {
151
183
  try {
152
- const subdirs = fs.readdirSync(this.cacheDir);
153
- for (const subdir of subdirs) {
154
- const subdirPath = path.join(this.cacheDir, subdir);
155
- try {
156
- if (fs.statSync(subdirPath).isDirectory()) {
157
- diskEntries += fs.readdirSync(subdirPath).length;
158
- }
159
- }
160
- catch {
161
- // Skip unreadable subdirs
162
- }
163
- }
164
- }
165
- catch {
166
- // Ignore
184
+ diskEntries = await this.backend.count();
167
185
  }
186
+ catch { /* ignore */ }
168
187
  }
169
188
  const total = this.hits + this.misses;
170
189
  return {
190
+ namespace: this.namespace,
171
191
  memoryEntries: this.memoryCache.size,
172
192
  diskEntries,
173
193
  hitRate: total > 0 ? this.hits / total : 0,
194
+ backend: this.backend.kind,
174
195
  };
175
196
  }
176
197
  }
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.
182
- let classificationCache = null;
183
- export function getClassificationCache(options) {
184
- const wantFingerprint = options?.modelFingerprint ?? '';
185
- if (!classificationCache || classificationCache.getModelFingerprint() !== wantFingerprint) {
186
- classificationCache = new ClassificationCache(options);
198
+ /**
199
+ * Singleton table keyed on (namespace, modelFingerprint) so two scans
200
+ * with different per-phase models in the same process get distinct
201
+ * caches (cognium-ai#151).
202
+ */
203
+ const singletons = new Map();
204
+ export function getClassificationCache(options = {}) {
205
+ const fp = options.modelFingerprint ?? '';
206
+ const namespace = options.namespace ?? 'classifier';
207
+ const slot = `${namespace}\0${fp}`;
208
+ let existing = singletons.get(slot);
209
+ if (!existing) {
210
+ existing = new ClassificationCache(options);
211
+ singletons.set(slot, existing);
187
212
  }
188
- return classificationCache;
213
+ return existing;
189
214
  }
190
215
  export function resetClassificationCache() {
191
- classificationCache = null;
216
+ singletons.clear();
192
217
  }
193
218
  //# sourceMappingURL=classification-cache.js.map
@@ -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,59 @@
1
+ /**
2
+ * Package coordinate detection for cache path layout.
3
+ *
4
+ * The tree-cache backend (R2 / local-fs) uses the coordinate to build a
5
+ * package-aware key layout:
6
+ *
7
+ * tree/<ecosystem>/<name>@<version>/<4hex>/<sha>.json (published coords)
8
+ * tree/_local/<name>@<version>/<4hex>/<sha>.json (repo-root manifest)
9
+ * tree/_unowned/<4hex>/<sha>.json (no coordinate)
10
+ *
11
+ * Coordinate is resolved ONCE per scan from the repo root. We deliberately
12
+ * keep the extractor minimal (only manifest files we can parse with zero
13
+ * deps) so it can be folded into hot paths without measurable overhead.
14
+ *
15
+ * What's intentionally *not* in scope here:
16
+ * - per-file coordinate from `node_modules/<pkg>/...` paths (deferred until
17
+ * we actually scan transitive deps)
18
+ * - lockfile-derived coordinates
19
+ * - Maven `<artifactId>` resolution from a multi-module reactor
20
+ *
21
+ * For the first-party github sweep use case, every file in a repo shares
22
+ * the same coordinate (or falls through to `_unowned`), which is exactly
23
+ * what we want for cross-runner cache sharing.
24
+ */
25
+ export type Ecosystem = 'npm' | 'maven' | 'pypi' | 'cargo' | 'go' | '_local' | '_unowned';
26
+ export interface PackageCoordinate {
27
+ ecosystem: Ecosystem;
28
+ /** Package name in its ecosystem's canonical form (lower-case, normalized). */
29
+ name: string;
30
+ /** Resolved version string, or `'HEAD'` for `_local`. */
31
+ version: string;
32
+ }
33
+ /**
34
+ * The sentinel coordinate emitted when the repo has no detectable manifest.
35
+ *
36
+ * Tree-cache content still gets cached, just under `_unowned/<4hex>/<sha>`.
37
+ * Re-using the singleton means the path layout is constant for un-coord'd
38
+ * scans and avoids per-file object allocation.
39
+ */
40
+ export declare const UNOWNED: PackageCoordinate;
41
+ /**
42
+ * Render the coordinate as a stable path prefix and a stable hash prefix.
43
+ *
44
+ * `pathSegment` is what the backend uses to build the R2 / local-fs key.
45
+ * `hashPrefix` is what cache-key composers fold into the content hash so
46
+ * the SHA itself encodes the coordinate (no collision across packages).
47
+ */
48
+ export declare function coordinateSegments(c: PackageCoordinate): {
49
+ pathSegment: string;
50
+ hashPrefix: string;
51
+ };
52
+ /**
53
+ * Detect a package coordinate from the repo root manifest files.
54
+ *
55
+ * Synchronous because it runs once per scan at scanner entry and the
56
+ * disk reads are tiny (`package.json` / `pom.xml`).
57
+ */
58
+ export declare function detectCoordinate(repoRoot: string): PackageCoordinate;
59
+ //# sourceMappingURL=coordinate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinate.d.ts","sourceRoot":"","sources":["../../src/cache/coordinate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE1F,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,iBAIpB,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,iBAAiB,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CASpG;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAwFpE"}