@soleri/core 8.0.0 → 9.0.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 (239) hide show
  1. package/dist/brain/brain.d.ts +1 -8
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +5 -134
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/knowledge-synthesizer.d.ts.map +1 -1
  6. package/dist/brain/knowledge-synthesizer.js +0 -2
  7. package/dist/brain/knowledge-synthesizer.js.map +1 -1
  8. package/dist/cognee/client.d.ts +5 -0
  9. package/dist/cognee/client.d.ts.map +1 -1
  10. package/dist/cognee/client.js +83 -16
  11. package/dist/cognee/client.js.map +1 -1
  12. package/dist/cognee/sync-manager.d.ts +67 -8
  13. package/dist/cognee/sync-manager.d.ts.map +1 -1
  14. package/dist/cognee/sync-manager.js +129 -32
  15. package/dist/cognee/sync-manager.js.map +1 -1
  16. package/dist/cognee/types.d.ts +16 -0
  17. package/dist/cognee/types.d.ts.map +1 -1
  18. package/dist/context/context-engine.d.ts +2 -5
  19. package/dist/context/context-engine.d.ts.map +1 -1
  20. package/dist/context/context-engine.js +4 -31
  21. package/dist/context/context-engine.js.map +1 -1
  22. package/dist/curator/classifier.d.ts.map +1 -1
  23. package/dist/curator/classifier.js +0 -2
  24. package/dist/curator/classifier.js.map +1 -1
  25. package/dist/curator/curator.d.ts +2 -5
  26. package/dist/curator/curator.d.ts.map +1 -1
  27. package/dist/curator/curator.js +4 -23
  28. package/dist/curator/curator.js.map +1 -1
  29. package/dist/curator/quality-gate.d.ts.map +1 -1
  30. package/dist/curator/quality-gate.js +0 -2
  31. package/dist/curator/quality-gate.js.map +1 -1
  32. package/dist/domain-packs/index.d.ts +0 -3
  33. package/dist/domain-packs/index.d.ts.map +1 -1
  34. package/dist/domain-packs/index.js +0 -3
  35. package/dist/domain-packs/index.js.map +1 -1
  36. package/dist/domain-packs/loader.d.ts.map +1 -1
  37. package/dist/domain-packs/loader.js +20 -4
  38. package/dist/domain-packs/loader.js.map +1 -1
  39. package/dist/domain-packs/pack-runtime.d.ts +5 -5
  40. package/dist/domain-packs/pack-runtime.d.ts.map +1 -1
  41. package/dist/domain-packs/pack-runtime.js +2 -2
  42. package/dist/domain-packs/pack-runtime.js.map +1 -1
  43. package/dist/domain-packs/types.d.ts +8 -2
  44. package/dist/domain-packs/types.d.ts.map +1 -1
  45. package/dist/domain-packs/types.js.map +1 -1
  46. package/dist/engine/bin/soleri-engine.js +18 -7
  47. package/dist/engine/bin/soleri-engine.js.map +1 -1
  48. package/dist/engine/core-ops.d.ts.map +1 -1
  49. package/dist/engine/core-ops.js +11 -6
  50. package/dist/engine/core-ops.js.map +1 -1
  51. package/dist/engine/index.d.ts +2 -0
  52. package/dist/engine/index.d.ts.map +1 -1
  53. package/dist/engine/index.js +1 -0
  54. package/dist/engine/index.js.map +1 -1
  55. package/dist/engine/module-manifest.d.ts +28 -0
  56. package/dist/engine/module-manifest.d.ts.map +1 -0
  57. package/dist/engine/module-manifest.js +85 -0
  58. package/dist/engine/module-manifest.js.map +1 -0
  59. package/dist/engine/register-engine.d.ts +19 -0
  60. package/dist/engine/register-engine.d.ts.map +1 -1
  61. package/dist/engine/register-engine.js +15 -9
  62. package/dist/engine/register-engine.js.map +1 -1
  63. package/dist/index.d.ts +5 -6
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +3 -5
  66. package/dist/index.js.map +1 -1
  67. package/dist/intake/content-classifier.d.ts.map +1 -1
  68. package/dist/intake/content-classifier.js +0 -2
  69. package/dist/intake/content-classifier.js.map +1 -1
  70. package/dist/intelligence/types.d.ts +7 -0
  71. package/dist/intelligence/types.d.ts.map +1 -1
  72. package/dist/llm/llm-client.d.ts.map +1 -1
  73. package/dist/llm/llm-client.js +8 -4
  74. package/dist/llm/llm-client.js.map +1 -1
  75. package/dist/llm/oauth-discovery.d.ts +0 -8
  76. package/dist/llm/oauth-discovery.d.ts.map +1 -1
  77. package/dist/llm/oauth-discovery.js +0 -19
  78. package/dist/llm/oauth-discovery.js.map +1 -1
  79. package/dist/llm/types.d.ts +4 -2
  80. package/dist/llm/types.d.ts.map +1 -1
  81. package/dist/packs/pack-installer.d.ts +2 -1
  82. package/dist/packs/pack-installer.d.ts.map +1 -1
  83. package/dist/packs/pack-installer.js +10 -1
  84. package/dist/packs/pack-installer.js.map +1 -1
  85. package/dist/persistence/index.d.ts +0 -1
  86. package/dist/persistence/index.d.ts.map +1 -1
  87. package/dist/persistence/index.js +0 -1
  88. package/dist/persistence/index.js.map +1 -1
  89. package/dist/persistence/types.d.ts +2 -6
  90. package/dist/persistence/types.d.ts.map +1 -1
  91. package/dist/persona/defaults.d.ts +16 -0
  92. package/dist/persona/defaults.d.ts.map +1 -0
  93. package/dist/persona/defaults.js +78 -0
  94. package/dist/persona/defaults.js.map +1 -0
  95. package/dist/persona/index.d.ts +5 -0
  96. package/dist/persona/index.d.ts.map +1 -0
  97. package/dist/persona/index.js +4 -0
  98. package/dist/persona/index.js.map +1 -0
  99. package/dist/persona/loader.d.ts +11 -0
  100. package/dist/persona/loader.d.ts.map +1 -0
  101. package/dist/persona/loader.js +45 -0
  102. package/dist/persona/loader.js.map +1 -0
  103. package/dist/persona/prompt-generator.d.ts +13 -0
  104. package/dist/persona/prompt-generator.d.ts.map +1 -0
  105. package/dist/persona/prompt-generator.js +89 -0
  106. package/dist/persona/prompt-generator.js.map +1 -0
  107. package/dist/persona/types.d.ts +56 -0
  108. package/dist/persona/types.d.ts.map +1 -0
  109. package/dist/persona/types.js +9 -0
  110. package/dist/persona/types.js.map +1 -0
  111. package/dist/plugins/index.d.ts +4 -0
  112. package/dist/plugins/index.d.ts.map +1 -1
  113. package/dist/plugins/index.js +4 -0
  114. package/dist/plugins/index.js.map +1 -1
  115. package/dist/plugins/plugin-registry.d.ts +4 -0
  116. package/dist/plugins/plugin-registry.d.ts.map +1 -1
  117. package/dist/plugins/plugin-registry.js +4 -0
  118. package/dist/plugins/plugin-registry.js.map +1 -1
  119. package/dist/plugins/types.d.ts +36 -31
  120. package/dist/plugins/types.d.ts.map +1 -1
  121. package/dist/plugins/types.js +6 -3
  122. package/dist/plugins/types.js.map +1 -1
  123. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  124. package/dist/runtime/admin-extra-ops.js +5 -27
  125. package/dist/runtime/admin-extra-ops.js.map +1 -1
  126. package/dist/runtime/admin-ops.d.ts.map +1 -1
  127. package/dist/runtime/admin-ops.js +5 -37
  128. package/dist/runtime/admin-ops.js.map +1 -1
  129. package/dist/runtime/capture-ops.d.ts.map +1 -1
  130. package/dist/runtime/capture-ops.js +32 -16
  131. package/dist/runtime/capture-ops.js.map +1 -1
  132. package/dist/runtime/claude-md-helpers.d.ts +0 -9
  133. package/dist/runtime/claude-md-helpers.d.ts.map +1 -1
  134. package/dist/runtime/claude-md-helpers.js +1 -14
  135. package/dist/runtime/claude-md-helpers.js.map +1 -1
  136. package/dist/runtime/cognee-sync-ops.d.ts +2 -2
  137. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -1
  138. package/dist/runtime/cognee-sync-ops.js +45 -7
  139. package/dist/runtime/cognee-sync-ops.js.map +1 -1
  140. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  141. package/dist/runtime/facades/admin-facade.js +1 -2
  142. package/dist/runtime/facades/admin-facade.js.map +1 -1
  143. package/dist/runtime/facades/index.d.ts +1 -1
  144. package/dist/runtime/facades/index.d.ts.map +1 -1
  145. package/dist/runtime/facades/index.js +1 -10
  146. package/dist/runtime/facades/index.js.map +1 -1
  147. package/dist/runtime/pack-ops.d.ts +3 -0
  148. package/dist/runtime/pack-ops.d.ts.map +1 -1
  149. package/dist/runtime/pack-ops.js +18 -1
  150. package/dist/runtime/pack-ops.js.map +1 -1
  151. package/dist/runtime/plugin-ops.d.ts.map +1 -1
  152. package/dist/runtime/plugin-ops.js +3 -0
  153. package/dist/runtime/plugin-ops.js.map +1 -1
  154. package/dist/runtime/runtime.d.ts.map +1 -1
  155. package/dist/runtime/runtime.js +14 -53
  156. package/dist/runtime/runtime.js.map +1 -1
  157. package/dist/runtime/session-briefing.d.ts.map +1 -1
  158. package/dist/runtime/session-briefing.js +14 -0
  159. package/dist/runtime/session-briefing.js.map +1 -1
  160. package/dist/runtime/types.d.ts +6 -8
  161. package/dist/runtime/types.d.ts.map +1 -1
  162. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  163. package/dist/runtime/vault-linking-ops.js +42 -4
  164. package/dist/runtime/vault-linking-ops.js.map +1 -1
  165. package/dist/runtime/vault-sharing-ops.d.ts.map +1 -1
  166. package/dist/runtime/vault-sharing-ops.js +53 -3
  167. package/dist/runtime/vault-sharing-ops.js.map +1 -1
  168. package/dist/vault/linking.d.ts +37 -0
  169. package/dist/vault/linking.d.ts.map +1 -1
  170. package/dist/vault/linking.js +73 -0
  171. package/dist/vault/linking.js.map +1 -1
  172. package/dist/vault/vault.d.ts +9 -2
  173. package/dist/vault/vault.d.ts.map +1 -1
  174. package/dist/vault/vault.js +21 -12
  175. package/dist/vault/vault.js.map +1 -1
  176. package/package.json +6 -4
  177. package/src/__tests__/curator-pipeline-e2e.test.ts +187 -0
  178. package/src/__tests__/module-manifest-drift.test.ts +59 -0
  179. package/src/brain/brain.ts +4 -157
  180. package/src/brain/knowledge-synthesizer.ts +0 -2
  181. package/src/context/context-engine.ts +3 -31
  182. package/src/curator/classifier.ts +0 -2
  183. package/src/curator/curator.ts +5 -28
  184. package/src/curator/quality-gate.ts +0 -2
  185. package/src/domain-packs/index.ts +0 -6
  186. package/src/domain-packs/loader.ts +25 -5
  187. package/src/domain-packs/pack-runtime.ts +6 -6
  188. package/src/domain-packs/types.ts +8 -2
  189. package/src/engine/bin/soleri-engine.ts +23 -7
  190. package/src/engine/core-ops.ts +11 -6
  191. package/src/engine/index.ts +2 -0
  192. package/src/engine/module-manifest.ts +99 -0
  193. package/src/engine/register-engine.ts +21 -9
  194. package/src/index.ts +20 -17
  195. package/src/intake/content-classifier.ts +0 -2
  196. package/src/intelligence/types.ts +8 -0
  197. package/src/llm/llm-client.ts +12 -6
  198. package/src/llm/oauth-discovery.ts +0 -18
  199. package/src/llm/types.ts +4 -2
  200. package/src/packs/pack-installer.ts +16 -1
  201. package/src/persistence/index.ts +0 -1
  202. package/src/persistence/types.ts +2 -6
  203. package/src/persona/defaults.ts +96 -0
  204. package/src/persona/index.ts +9 -0
  205. package/src/persona/loader.ts +50 -0
  206. package/src/persona/prompt-generator.ts +109 -0
  207. package/src/persona/types.ts +72 -0
  208. package/src/plugins/index.ts +4 -0
  209. package/src/plugins/plugin-registry.ts +6 -1
  210. package/src/plugins/types.ts +10 -5
  211. package/src/runtime/admin-extra-ops.ts +5 -28
  212. package/src/runtime/admin-ops.ts +5 -38
  213. package/src/runtime/capture-ops.ts +33 -14
  214. package/src/runtime/claude-md-helpers.ts +1 -19
  215. package/src/runtime/facades/admin-facade.ts +1 -2
  216. package/src/runtime/facades/index.ts +1 -11
  217. package/src/runtime/pack-ops.ts +26 -1
  218. package/src/runtime/plugin-ops.ts +3 -0
  219. package/src/runtime/runtime.ts +14 -54
  220. package/src/runtime/session-briefing.ts +14 -0
  221. package/src/runtime/types.ts +6 -8
  222. package/src/runtime/vault-linking-ops.ts +43 -4
  223. package/src/runtime/vault-sharing-ops.ts +63 -4
  224. package/src/vault/linking.ts +94 -0
  225. package/src/vault/vault.ts +24 -12
  226. package/src/__tests__/cognee-client-gaps.test.ts +0 -474
  227. package/src/__tests__/cognee-client.test.ts +0 -524
  228. package/src/__tests__/cognee-hybrid-search.test.ts +0 -492
  229. package/src/__tests__/cognee-integration.test.ts +0 -80
  230. package/src/__tests__/cognee-sync-manager-deep.test.ts +0 -654
  231. package/src/__tests__/cognee-sync-manager.test.ts +0 -104
  232. package/src/__tests__/postgres-provider.test.ts +0 -116
  233. package/src/cognee/client.ts +0 -370
  234. package/src/cognee/sync-manager.ts +0 -389
  235. package/src/cognee/types.ts +0 -62
  236. package/src/health/doctor-checks.ts +0 -115
  237. package/src/persistence/postgres-provider.ts +0 -310
  238. package/src/runtime/cognee-sync-ops.ts +0 -63
  239. package/src/runtime/facades/cognee-facade.ts +0 -164
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleri/core",
3
- "version": "8.0.0",
3
+ "version": "9.0.0",
4
4
  "description": "Shared engine for Soleri agents — vault, brain, planner, LLM utilities, and facade infrastructure.",
5
5
  "keywords": [
6
6
  "agent",
@@ -31,6 +31,10 @@
31
31
  ".": {
32
32
  "types": "./dist/index.d.ts",
33
33
  "import": "./dist/index.js"
34
+ },
35
+ "./module-manifest": {
36
+ "types": "./dist/engine/module-manifest.d.ts",
37
+ "import": "./dist/engine/module-manifest.js"
34
38
  }
35
39
  },
36
40
  "publishConfig": {
@@ -50,7 +54,6 @@
50
54
  "devDependencies": {
51
55
  "@types/better-sqlite3": "^7.6.13",
52
56
  "@types/pdf-parse": "^1.1.5",
53
- "@types/pg": "^8.11.0",
54
57
  "@vitest/coverage-v8": "^4.0.18"
55
58
  },
56
59
  "peerDependencies": {
@@ -69,7 +72,6 @@
69
72
  "node": ">=18.0.0"
70
73
  },
71
74
  "optionalDependencies": {
72
- "pdf-parse": "^1.1.4",
73
- "pg": "^8.13.0"
75
+ "pdf-parse": "^1.1.4"
74
76
  }
75
77
  }
@@ -94,6 +94,56 @@ beforeAll(() => {
94
94
  if (!entry) return { skipped: true, reason: 'entry not found' };
95
95
  return classifyEntry(entry, null); // No LLM in tests
96
96
  });
97
+
98
+ // ─── 9 additional handlers for full Salvador parity (#216) ────
99
+ runner.registerHandler('enrich-frontmatter', async (job) => {
100
+ const entry = vault.get(job.entryId ?? '');
101
+ if (!entry) return { skipped: true, reason: 'entry not found' };
102
+ return curator.enrichMetadata(entry.id);
103
+ });
104
+ runner.registerHandler('detect-staleness', async (job) => {
105
+ const entry = vault.get(job.entryId ?? '');
106
+ if (!entry) return { skipped: true, reason: 'entry not found' };
107
+ const entryTimestamp = (entry.validFrom ?? 0) * 1000 || Date.now();
108
+ const ageMs = Date.now() - entryTimestamp;
109
+ const isStale = ageMs > 90 * 86400000;
110
+ return { stale: isStale, ageDays: Math.floor(ageMs / 86400000), entryId: entry.id };
111
+ });
112
+ runner.registerHandler('detect-duplicate', async (job) => {
113
+ const entry = vault.get(job.entryId ?? '');
114
+ if (!entry) return { skipped: true, reason: 'entry not found' };
115
+ return curator.detectDuplicates(entry.id);
116
+ });
117
+ runner.registerHandler('detect-contradiction', async (job) => {
118
+ const entry = vault.get(job.entryId ?? '');
119
+ if (!entry) return { skipped: true, reason: 'entry not found' };
120
+ const contradictions = curator.detectContradictions(0.4);
121
+ const relevant = contradictions.filter(
122
+ (c) => c.patternId === job.entryId || c.antipatternId === job.entryId,
123
+ );
124
+ return { found: relevant.length, contradictions: relevant };
125
+ });
126
+ runner.registerHandler('consolidate-duplicates', async () => {
127
+ return curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
128
+ });
129
+ runner.registerHandler('archive-stale', async () => {
130
+ const result = curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
131
+ return { archived: result.staleEntries.length, result };
132
+ });
133
+ runner.registerHandler('cognee-ingest', async (job) => {
134
+ // No Cognee in tests — graceful degradation
135
+ return { skipped: true, reason: 'cognee not available' };
136
+ });
137
+ runner.registerHandler('cognee-cognify', async () => {
138
+ return { skipped: true, reason: 'cognee not available' };
139
+ });
140
+ runner.registerHandler('verify-searchable', async (job) => {
141
+ const entry = vault.get(job.entryId ?? '');
142
+ if (!entry) return { skipped: true, reason: 'entry not found' };
143
+ const searchResults = vault.search(entry.title, { limit: 1 });
144
+ const found = searchResults.some((r) => r.entry.id === entry.id);
145
+ return { searchable: found, entryId: entry.id };
146
+ });
97
147
  });
98
148
 
99
149
  afterAll(() => {
@@ -266,6 +316,143 @@ describe('Classifier — graceful degradation', () => {
266
316
  });
267
317
  });
268
318
 
319
+ // ─── Salvador Parity Handlers (#216) ─────────────────────────────────
320
+
321
+ describe('Pipeline Runner — Salvador parity handlers (#216)', () => {
322
+ it('enrich-frontmatter enriches entry metadata', async () => {
323
+ const id = queue.enqueue('enrich-frontmatter', { entryId: 'pattern-circuit-breaker' });
324
+ await runner.processOnce();
325
+ const job = queue.get(id);
326
+ expect(job!.status).toBe('completed');
327
+ expect(job!.result).toBeDefined();
328
+ });
329
+
330
+ it('detect-staleness checks entry age', async () => {
331
+ const id = queue.enqueue('detect-staleness', { entryId: 'pattern-circuit-breaker' });
332
+ await runner.processOnce();
333
+ const job = queue.get(id);
334
+ expect(job!.status).toBe('completed');
335
+ const result = job!.result as { stale: boolean; ageDays: number };
336
+ expect(typeof result.stale).toBe('boolean');
337
+ expect(typeof result.ageDays).toBe('number');
338
+ });
339
+
340
+ it('detect-duplicate runs dedup on specific entry', async () => {
341
+ const id = queue.enqueue('detect-duplicate', { entryId: 'pattern-circuit-breaker' });
342
+ await runner.processOnce();
343
+ const job = queue.get(id);
344
+ expect(job!.status).toBe('completed');
345
+ expect(job!.result).toBeDefined();
346
+ });
347
+
348
+ it('detect-contradiction finds pattern/anti-pattern conflicts', async () => {
349
+ const id = queue.enqueue('detect-contradiction', { entryId: 'pattern-circuit-breaker' });
350
+ await runner.processOnce();
351
+ const job = queue.get(id);
352
+ expect(job!.status).toBe('completed');
353
+ const result = job!.result as { found: number };
354
+ expect(typeof result.found).toBe('number');
355
+ });
356
+
357
+ it('consolidate-duplicates runs consolidation', async () => {
358
+ const id = queue.enqueue('consolidate-duplicates', {});
359
+ await runner.processOnce();
360
+ const job = queue.get(id);
361
+ expect(job!.status).toBe('completed');
362
+ expect(job!.result).toBeDefined();
363
+ });
364
+
365
+ it('archive-stale archives old entries', async () => {
366
+ const id = queue.enqueue('archive-stale', {});
367
+ await runner.processOnce();
368
+ const job = queue.get(id);
369
+ expect(job!.status).toBe('completed');
370
+ const result = job!.result as { archived: number };
371
+ expect(typeof result.archived).toBe('number');
372
+ });
373
+
374
+ it('cognee-ingest degrades gracefully without Cognee', async () => {
375
+ const id = queue.enqueue('cognee-ingest', { entryId: 'pattern-circuit-breaker' });
376
+ await runner.processOnce();
377
+ const job = queue.get(id);
378
+ expect(job!.status).toBe('completed');
379
+ expect((job!.result as Record<string, unknown>).skipped).toBe(true);
380
+ });
381
+
382
+ it('cognee-cognify degrades gracefully without Cognee', async () => {
383
+ const id = queue.enqueue('cognee-cognify', {});
384
+ await runner.processOnce();
385
+ const job = queue.get(id);
386
+ expect(job!.status).toBe('completed');
387
+ expect((job!.result as Record<string, unknown>).skipped).toBe(true);
388
+ });
389
+
390
+ it('verify-searchable confirms entry is FTS-indexed', async () => {
391
+ const id = queue.enqueue('verify-searchable', { entryId: 'pattern-circuit-breaker' });
392
+ await runner.processOnce();
393
+ const job = queue.get(id);
394
+ expect(job!.status).toBe('completed');
395
+ const result = job!.result as { searchable: boolean };
396
+ expect(result.searchable).toBe(true);
397
+ });
398
+
399
+ it('all handlers handle missing entries gracefully', async () => {
400
+ const types = [
401
+ 'enrich-frontmatter',
402
+ 'detect-staleness',
403
+ 'detect-duplicate',
404
+ 'detect-contradiction',
405
+ 'verify-searchable',
406
+ ];
407
+ for (const type of types) {
408
+ const id = queue.enqueue(type, { entryId: 'nonexistent' });
409
+ await runner.processOnce();
410
+ const job = queue.get(id);
411
+ expect(job!.status).toBe('completed');
412
+ expect((job!.result as Record<string, unknown>).skipped).toBe(true);
413
+ }
414
+ });
415
+ });
416
+
417
+ describe('Full Salvador DAG — 8-step curator pipeline', () => {
418
+ it('runs the complete quality pipeline in DAG order', async () => {
419
+ const entryId = 'pattern-semantic-tokens';
420
+ const pipelineId = 'full-salvador-dag';
421
+
422
+ const step1 = queue.enqueue('quality-gate', { entryId, pipelineId });
423
+ const step2 = queue.enqueue('enrich-frontmatter', { entryId, pipelineId, dependsOn: [step1] });
424
+ const step3 = queue.enqueue('tag-normalize', { entryId, pipelineId, dependsOn: [step2] });
425
+ const step4 = queue.enqueue('dedup-check', { entryId, pipelineId, dependsOn: [step3] });
426
+ const step5 = queue.enqueue('detect-contradiction', {
427
+ entryId,
428
+ pipelineId,
429
+ dependsOn: [step4],
430
+ });
431
+ const step6 = queue.enqueue('cognee-ingest', { entryId, pipelineId, dependsOn: [step5] });
432
+ const step7 = queue.enqueue('auto-link', { entryId, pipelineId, dependsOn: [step6] });
433
+ const step8 = queue.enqueue('verify-searchable', { entryId, pipelineId, dependsOn: [step7] });
434
+
435
+ // Drain the DAG
436
+ for (let i = 0; i < 15; i++) {
437
+ await runner.processOnce(20);
438
+ }
439
+
440
+ const jobs = queue.getByPipeline(pipelineId);
441
+ const statuses = jobs.map((j) => `${j.type}:${j.status}`);
442
+
443
+ expect(statuses).toEqual([
444
+ 'quality-gate:completed',
445
+ 'enrich-frontmatter:completed',
446
+ 'tag-normalize:completed',
447
+ 'dedup-check:completed',
448
+ 'detect-contradiction:completed',
449
+ 'cognee-ingest:completed',
450
+ 'auto-link:completed',
451
+ 'verify-searchable:completed',
452
+ ]);
453
+ });
454
+ });
455
+
269
456
  // ─── Event Bus Integration ──────────────────────────────────────────
270
457
 
271
458
  describe('Event Bus — curator events', () => {
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Contract Drift Detection — Issue #227
3
+ *
4
+ * Ensures ENGINE_MODULE_MANIFEST (used by forge for template generation)
5
+ * stays in sync with ENGINE_MODULES (used by register-engine at runtime).
6
+ *
7
+ * If this test fails, someone added/removed/renamed a module in one place
8
+ * but not the other. Fix by updating both files to match.
9
+ */
10
+
11
+ import { describe, it, expect } from 'vitest';
12
+ import { ENGINE_MODULE_MANIFEST } from '../engine/module-manifest.js';
13
+ import { ENGINE_MODULES } from '../engine/register-engine.js';
14
+
15
+ describe('Module manifest drift detection', () => {
16
+ const manifestSuffixes = ENGINE_MODULE_MANIFEST.map((m) => m.suffix);
17
+ const runtimeSuffixes = ENGINE_MODULES.map((m) => m.suffix);
18
+
19
+ it('manifest and runtime have the same number of modules', () => {
20
+ expect(manifestSuffixes).toHaveLength(runtimeSuffixes.length);
21
+ });
22
+
23
+ it('manifest and runtime have identical suffixes in the same order', () => {
24
+ expect(manifestSuffixes).toEqual(runtimeSuffixes);
25
+ });
26
+
27
+ it('conditional flags match between manifest and runtime', () => {
28
+ for (const manifest of ENGINE_MODULE_MANIFEST) {
29
+ const runtime = ENGINE_MODULES.find((m) => m.suffix === manifest.suffix);
30
+ expect(runtime, `module "${manifest.suffix}" missing from ENGINE_MODULES`).toBeDefined();
31
+
32
+ const runtimeConditional = runtime!.condition !== undefined;
33
+ const manifestConditional = manifest.conditional === true;
34
+
35
+ expect(runtimeConditional, `conditional flag mismatch for "${manifest.suffix}"`).toBe(
36
+ manifestConditional,
37
+ );
38
+ }
39
+ });
40
+
41
+ it('no runtime module is missing from manifest', () => {
42
+ for (const runtime of ENGINE_MODULES) {
43
+ const manifest = ENGINE_MODULE_MANIFEST.find((m) => m.suffix === runtime.suffix);
44
+ expect(
45
+ manifest,
46
+ `module "${runtime.suffix}" exists in ENGINE_MODULES but missing from ENGINE_MODULE_MANIFEST`,
47
+ ).toBeDefined();
48
+ }
49
+ });
50
+
51
+ it('every manifest entry has at least one keyOp', () => {
52
+ for (const entry of ENGINE_MODULE_MANIFEST) {
53
+ expect(
54
+ entry.keyOps.length,
55
+ `module "${entry.suffix}" has empty keyOps — placeholder tables need at least one op`,
56
+ ).toBeGreaterThan(0);
57
+ }
58
+ });
59
+ });
@@ -10,7 +10,6 @@ import {
10
10
  cosineSimilarity,
11
11
  jaccardSimilarity,
12
12
  } from '../text/similarity.js';
13
- import type { CogneeClient } from '../cognee/client.js';
14
13
  import type {
15
14
  ScoringWeights,
16
15
  ScoreBreakdown,
@@ -44,15 +43,6 @@ const DEFAULT_WEIGHTS: ScoringWeights = {
44
43
  domainMatch: 0.15,
45
44
  };
46
45
 
47
- const COGNEE_WEIGHTS: ScoringWeights = {
48
- semantic: 0.25,
49
- vector: 0.35,
50
- severity: 0.1,
51
- temporalDecay: 0.1,
52
- tagOverlap: 0.1,
53
- domainMatch: 0.1,
54
- };
55
-
56
46
  const WEIGHT_BOUND = 0.15;
57
47
  const FEEDBACK_THRESHOLD = 30;
58
48
  const DUPLICATE_BLOCK_THRESHOLD = 0.8;
@@ -62,14 +52,12 @@ const RECENCY_HALF_LIFE_DAYS = 365;
62
52
  export class Brain {
63
53
  private vault: Vault;
64
54
  private vaultManager: VaultManager | undefined;
65
- private cognee: CogneeClient | undefined;
66
55
  private vocabulary: Map<string, number> = new Map();
67
56
  private weights: ScoringWeights = { ...DEFAULT_WEIGHTS };
68
57
 
69
- constructor(vault: Vault, cognee?: CogneeClient, vaultManager?: VaultManager) {
58
+ constructor(vault: Vault, vaultManager?: VaultManager) {
70
59
  this.vault = vault;
71
60
  this.vaultManager = vaultManager;
72
- this.cognee = cognee;
73
61
  this.rebuildVocabulary();
74
62
  this.recomputeWeights();
75
63
  }
@@ -102,97 +90,6 @@ export class Brain {
102
90
  });
103
91
  }
104
92
 
105
- // Cognee vector search (parallel, with timeout fallback)
106
- let cogneeScoreMap: Map<string, number> = new Map();
107
- const cogneeAvailable = this.cognee?.isAvailable ?? false;
108
- if (cogneeAvailable && this.cognee) {
109
- try {
110
- const cogneeResults = await this.cognee.search(query, { limit: Math.max(limit * 2, 20) });
111
-
112
- // Build title → entryIds reverse index from FTS results for text-based matching.
113
- // Cognee assigns its own UUIDs to chunks and may strip embedded metadata during
114
- // chunking, so we need multiple strategies to cross-reference results.
115
- // Multiple entries can share a title, so map to arrays of IDs.
116
- const titleToIds = new Map<string, string[]>();
117
- for (const r of rawResults) {
118
- const key = r.entry.title.toLowerCase().trim();
119
- const ids = titleToIds.get(key) ?? [];
120
- ids.push(r.entry.id);
121
- titleToIds.set(key, ids);
122
- }
123
-
124
- const vaultIdPattern = /\[vault-id:([^\]]+)\]/;
125
- const unmatchedCogneeResults: Array<{ text: string; score: number }> = [];
126
-
127
- for (const cr of cogneeResults) {
128
- const text = cr.text ?? '';
129
-
130
- // Strategy 1: Extract vault ID from [vault-id:XXX] prefix (if Cognee preserved it)
131
- const vaultIdMatch = text.match(vaultIdPattern);
132
- if (vaultIdMatch) {
133
- const vaultId = vaultIdMatch[1];
134
- cogneeScoreMap.set(vaultId, Math.max(cogneeScoreMap.get(vaultId) ?? 0, cr.score));
135
- continue;
136
- }
137
-
138
- // Strategy 2: Match first line of chunk text against known entry titles.
139
- // serializeEntry() puts the title on the first line after the [vault-id:] prefix,
140
- // and Cognee's chunking typically preserves this as the chunk start.
141
- const firstLine = text.split('\n')[0]?.trim().toLowerCase() ?? '';
142
- const matchedIds = firstLine ? titleToIds.get(firstLine) : undefined;
143
- if (matchedIds) {
144
- for (const id of matchedIds) {
145
- cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, cr.score));
146
- }
147
- continue;
148
- }
149
-
150
- // Strategy 3: Check if any known title appears as a substring in the chunk.
151
- // Handles cases where the title isn't on the first line (mid-document chunks).
152
- const textLower = text.toLowerCase();
153
- let found = false;
154
- for (const [title, ids] of titleToIds) {
155
- if (title.length >= 8 && textLower.includes(title)) {
156
- for (const id of ids) {
157
- cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, cr.score));
158
- }
159
- found = true;
160
- break;
161
- }
162
- }
163
- if (!found && text.length > 0) {
164
- unmatchedCogneeResults.push({ text, score: cr.score });
165
- }
166
- }
167
-
168
- // Strategy 4: For Cognee-only semantic matches (not in FTS results),
169
- // use the first line as a vault FTS query to find the source entry.
170
- // Preserve caller filters (domain/type/severity) to avoid reintroducing
171
- // entries the original query excluded.
172
- for (const unmatched of unmatchedCogneeResults) {
173
- const searchTerm = unmatched.text.split('\n')[0]?.trim();
174
- if (!searchTerm || searchTerm.length < 3) continue;
175
- const vaultHits = this.vault.search(searchTerm, {
176
- domain: options?.domain,
177
- type: options?.type,
178
- severity: options?.severity,
179
- limit: 1,
180
- });
181
- if (vaultHits.length > 0) {
182
- const id = vaultHits[0].entry.id;
183
- cogneeScoreMap.set(id, Math.max(cogneeScoreMap.get(id) ?? 0, unmatched.score));
184
- // Also add to FTS results pool if not already present
185
- if (!rawResults.some((r) => r.entry.id === id)) {
186
- rawResults.push(vaultHits[0]);
187
- }
188
- }
189
- }
190
- } catch {
191
- // Cognee failed — fall back to FTS5 only
192
- cogneeScoreMap = new Map();
193
- }
194
- }
195
-
196
93
  if (rawResults.length === 0) return [];
197
94
 
198
95
  const seedCount = rawResults.length;
@@ -201,22 +98,9 @@ export class Brain {
201
98
  const queryDomain = options?.domain;
202
99
  const now = Math.floor(Date.now() / 1000);
203
100
 
204
- // Use cognee-aware weights only if at least one ranked candidate has a vector score
205
- const hasVectorCandidate = rawResults.some((r) => cogneeScoreMap.has(r.entry.id));
206
- const activeWeights = hasVectorCandidate ? this.getCogneeWeights() : this.weights;
207
-
208
101
  const ranked = rawResults.map((result) => {
209
102
  const entry = result.entry;
210
- const vectorScore = cogneeScoreMap.get(entry.id) ?? 0;
211
- const breakdown = this.scoreEntry(
212
- entry,
213
- queryTokens,
214
- queryTags,
215
- queryDomain,
216
- now,
217
- vectorScore,
218
- activeWeights,
219
- );
103
+ const breakdown = this.scoreEntry(entry, queryTokens, queryTags, queryDomain, now);
220
104
  return { entry, score: breakdown.total, breakdown };
221
105
  });
222
106
 
@@ -335,11 +219,6 @@ export class Brain {
335
219
  this.vault.add(fullEntry);
336
220
  this.updateVocabularyIncremental(fullEntry);
337
221
 
338
- // Fire-and-forget Cognee sync
339
- if (this.cognee?.isAvailable) {
340
- this.cognee.addEntries([fullEntry]).catch(() => {});
341
- }
342
-
343
222
  const result: CaptureResult = {
344
223
  captured: true,
345
224
  id: entry.id,
@@ -465,32 +344,6 @@ export class Brain {
465
344
  });
466
345
  }
467
346
 
468
- async syncToCognee(): Promise<{ synced: number; cognified: boolean }> {
469
- if (!this.cognee?.isAvailable) return { synced: 0, cognified: false };
470
-
471
- const batchSize = 1000;
472
- let offset = 0;
473
- let totalSynced = 0;
474
-
475
- while (true) {
476
- const batch = this.vault.list({ limit: batchSize, offset });
477
- if (batch.length === 0) break;
478
-
479
- const { added } = await this.cognee.addEntries(batch);
480
- totalSynced += added;
481
- offset += batch.length;
482
-
483
- if (batch.length < batchSize) break;
484
- }
485
-
486
- if (totalSynced === 0) return { synced: 0, cognified: false };
487
-
488
- let cognified = false;
489
- const cognifyResult = await this.cognee.cognify();
490
- cognified = cognifyResult.status === 'ok';
491
- return { synced: totalSynced, cognified };
492
- }
493
-
494
347
  rebuildVocabulary(): void {
495
348
  // Collect entries from all connected sources when VaultManager is available
496
349
  let entries: IntelligenceEntry[];
@@ -616,10 +469,8 @@ export class Brain {
616
469
  queryTags: string[],
617
470
  queryDomain: string | undefined,
618
471
  now: number,
619
- vectorScore: number = 0,
620
- activeWeights?: ScoringWeights,
621
472
  ): ScoreBreakdown {
622
- const w = activeWeights ?? this.weights;
473
+ const w = this.weights;
623
474
 
624
475
  let semantic = 0;
625
476
  if (this.vocabulary.size > 0 && queryTokens.length > 0) {
@@ -643,7 +494,7 @@ export class Brain {
643
494
 
644
495
  const domainMatch = queryDomain && entry.domain === queryDomain ? 1.0 : 0;
645
496
 
646
- const vector = vectorScore;
497
+ const vector = 0;
647
498
 
648
499
  const total =
649
500
  w.semantic * semantic +
@@ -747,10 +598,6 @@ export class Brain {
747
598
  tx();
748
599
  }
749
600
 
750
- private getCogneeWeights(): ScoringWeights {
751
- return { ...COGNEE_WEIGHTS };
752
- }
753
-
754
601
  private recomputeWeights(): void {
755
602
  const db = this.vault.getDb();
756
603
  // Exclude 'failed' from weight computation — system errors don't indicate relevance
@@ -114,8 +114,6 @@ export class KnowledgeSynthesizer {
114
114
  let llmOutput: string;
115
115
  try {
116
116
  const result = await this.llm.complete({
117
- provider: 'openai',
118
- model: 'gpt-4o-mini',
119
117
  systemPrompt,
120
118
  userPrompt,
121
119
  temperature: 0.3,
@@ -3,17 +3,15 @@
3
3
  *
4
4
  * Orchestrates three signals to enrich intent classification:
5
5
  * 1. Entity extraction (regex-based, domain-agnostic)
6
- * 2. Knowledge retrieval (vault FTS + Cognee vector + brain recommendations)
6
+ * 2. Knowledge retrieval (vault FTS + brain recommendations)
7
7
  * 3. Confidence scoring (combines entity + knowledge signals)
8
8
  *
9
- * Graceful degradation: if Cognee is unavailable, vault-only.
10
9
  * If vault is empty, keyword confidence from IntentRouter is unchanged.
11
10
  */
12
11
 
13
12
  import type { Vault } from '../vault/vault.js';
14
13
  import type { Brain } from '../brain/brain.js';
15
14
  import type { BrainIntelligence } from '../brain/intelligence.js';
16
- import type { CogneeClient } from '../cognee/client.js';
17
15
  import type {
18
16
  EntityType,
19
17
  ExtractedEntity,
@@ -105,20 +103,17 @@ export class ContextEngine {
105
103
  private vault: Vault;
106
104
  private brain: Brain;
107
105
  private brainIntelligence: BrainIntelligence;
108
- private cognee: CogneeClient | null;
109
106
  private config: Required<ContextEngineConfig>;
110
107
 
111
108
  constructor(
112
109
  vault: Vault,
113
110
  brain: Brain,
114
111
  brainIntelligence: BrainIntelligence,
115
- cognee: CogneeClient | null,
116
112
  config?: ContextEngineConfig,
117
113
  ) {
118
114
  this.vault = vault;
119
115
  this.brain = brain;
120
116
  this.brainIntelligence = brainIntelligence;
121
- this.cognee = cognee;
122
117
  this.config = {
123
118
  vaultSearchLimit: config?.vaultSearchLimit ?? 10,
124
119
  cogneeSearchLimit: config?.cogneeSearchLimit ?? 10,
@@ -167,7 +162,6 @@ export class ContextEngine {
167
162
  async retrieveKnowledge(prompt: string, domain?: string): Promise<KnowledgeRetrievalResult> {
168
163
  const items: KnowledgeItem[] = [];
169
164
  let vaultHits = 0;
170
- let cogneeHits = 0;
171
165
  let brainHits = 0;
172
166
 
173
167
  // 1. Vault FTS search
@@ -208,29 +202,7 @@ export class ContextEngine {
208
202
  // Vault search failed — continue with other sources
209
203
  }
210
204
 
211
- // 2. Cognee vector search (async, graceful degradation)
212
- if (this.cognee) {
213
- try {
214
- const cogneeResults = await this.cognee.search(prompt, {
215
- limit: this.config.cogneeSearchLimit,
216
- });
217
- for (const r of cogneeResults) {
218
- // Avoid duplicates from vault
219
- if (items.some((i) => i.id === r.id)) continue;
220
- items.push({
221
- id: r.id,
222
- title: r.text.slice(0, 100),
223
- score: r.score,
224
- source: 'cognee',
225
- });
226
- cogneeHits++;
227
- }
228
- } catch {
229
- // Cognee unavailable — continue without
230
- }
231
- }
232
-
233
- // 3. Brain recommendations
205
+ // 2. Brain recommendations
234
206
  try {
235
207
  const recommendations = this.brainIntelligence.recommend({
236
208
  domain,
@@ -255,7 +227,7 @@ export class ContextEngine {
255
227
  items.sort((a, b) => b.score - a.score);
256
228
  const filtered = items.filter((i) => i.score >= this.config.minScoreThreshold);
257
229
 
258
- return { items: filtered, vaultHits, cogneeHits, brainHits };
230
+ return { items: filtered, vaultHits, cogneeHits: 0, brainHits };
259
231
  }
260
232
 
261
233
  // ─── Context Analysis ───────────────────────────────────────────
@@ -58,8 +58,6 @@ export async function classifyEntry(
58
58
 
59
59
  try {
60
60
  const result = await llm.complete({
61
- provider: 'openai',
62
- model: 'gpt-4o-mini',
63
61
  systemPrompt: 'You are a knowledge classifier. Respond only with JSON.',
64
62
  userPrompt: prompt,
65
63
  temperature: 0.1,