@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
@@ -1,654 +0,0 @@
1
- /**
2
- * CogneeSyncManager deep tests — covers drain, hash drift, health-flip,
3
- * concurrent safety, and partial failure scenarios.
4
- *
5
- * Source of truth: these tests define expected behavior.
6
- * Code adapts to fulfill them.
7
- *
8
- * Uses real in-memory SQLite (never mocked) with a mock CogneeClient.
9
- */
10
-
11
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
12
- import { mkdirSync, rmSync } from 'node:fs';
13
- import { join } from 'node:path';
14
- import { tmpdir } from 'node:os';
15
- import { createAgentRuntime } from '../runtime/runtime.js';
16
- import { CogneeSyncManager } from '../cognee/sync-manager.js';
17
- import { CogneeClient } from '../cognee/client.js';
18
- import type { AgentRuntime } from '../runtime/types.js';
19
- import type { IntelligenceEntry } from '../intelligence/types.js';
20
-
21
- // ─── Helpers ──────────────────────────────────────────────────────
22
-
23
- function makeEntry(overrides: Partial<IntelligenceEntry> = {}): IntelligenceEntry {
24
- return {
25
- id: overrides.id ?? `entry-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
26
- type: overrides.type ?? 'pattern',
27
- domain: overrides.domain ?? 'test',
28
- title: overrides.title ?? 'Test Pattern',
29
- severity: overrides.severity ?? 'suggestion',
30
- description: overrides.description ?? 'A test pattern.',
31
- tags: overrides.tags ?? ['test'],
32
- ...(overrides.context ? { context: overrides.context } : {}),
33
- ...(overrides.example ? { example: overrides.example } : {}),
34
- };
35
- }
36
-
37
- /**
38
- * Create a mock CogneeClient with controllable availability and behavior.
39
- */
40
- function makeMockCognee(
41
- overrides: {
42
- available?: boolean;
43
- addResult?: { added: number };
44
- addShouldFail?: boolean;
45
- deleteResult?: { deleted: number };
46
- } = {},
47
- ): CogneeClient {
48
- const available = overrides.available ?? true;
49
- const client = {
50
- get isAvailable() {
51
- return available;
52
- },
53
- healthCheck: vi.fn().mockResolvedValue({ available, url: 'http://mock:8000', latencyMs: 1 }),
54
- addEntries: vi.fn().mockImplementation(async () => {
55
- if (overrides.addShouldFail) throw new Error('Cognee ingest failed');
56
- return overrides.addResult ?? { added: 1 };
57
- }),
58
- deleteEntries: vi.fn().mockResolvedValue(overrides.deleteResult ?? { deleted: 1 }),
59
- cognify: vi.fn().mockResolvedValue({ status: 'ok' }),
60
- search: vi.fn().mockResolvedValue([]),
61
- getConfig: vi.fn().mockReturnValue({ baseUrl: 'http://mock:8000', dataset: 'test' }),
62
- getStatus: vi.fn().mockReturnValue({ available, url: 'http://mock:8000', latencyMs: 1 }),
63
- flushPendingCognify: vi.fn(),
64
- resetPendingCognify: vi.fn(),
65
- } as unknown as CogneeClient;
66
- return client;
67
- }
68
-
69
- // ─── Test Suite ───────────────────────────────────────────────────
70
-
71
- describe('CogneeSyncManager — deep coverage', () => {
72
- let runtime: AgentRuntime;
73
- let tmpDir: string;
74
-
75
- beforeEach(() => {
76
- tmpDir = join(tmpdir(), `sync-deep-${Date.now()}`);
77
- mkdirSync(tmpDir, { recursive: true });
78
- runtime = createAgentRuntime({
79
- agentId: 'test-sync-deep',
80
- vaultPath: ':memory:',
81
- plansPath: join(tmpDir, 'plans.json'),
82
- cognee: true,
83
- });
84
- });
85
-
86
- afterEach(() => {
87
- runtime.close();
88
- rmSync(tmpDir, { recursive: true, force: true });
89
- });
90
-
91
- // ─── Content hash ─────────────────────────────────────────────
92
-
93
- describe('contentHash', () => {
94
- it('should be deterministic for identical entries', () => {
95
- const entry = makeEntry({ id: 'stable', title: 'Stable Title', description: 'Same desc' });
96
- const h1 = CogneeSyncManager.contentHash(entry);
97
- const h2 = CogneeSyncManager.contentHash(entry);
98
- expect(h1).toBe(h2);
99
- });
100
-
101
- it('should produce 16 hex characters', () => {
102
- const hash = CogneeSyncManager.contentHash(makeEntry());
103
- expect(hash).toMatch(/^[0-9a-f]{16}$/);
104
- });
105
-
106
- it('should change when title changes', () => {
107
- const base = makeEntry({ id: 'same-id' });
108
- const modified = { ...base, title: 'Different Title' };
109
- expect(CogneeSyncManager.contentHash(base)).not.toBe(CogneeSyncManager.contentHash(modified));
110
- });
111
-
112
- it('should change when description changes', () => {
113
- const base = makeEntry({ id: 'same-id' });
114
- const modified = { ...base, description: 'Updated description' };
115
- expect(CogneeSyncManager.contentHash(base)).not.toBe(CogneeSyncManager.contentHash(modified));
116
- });
117
-
118
- it('should change when tags change', () => {
119
- const base = makeEntry({ id: 'same-id', tags: ['a'] });
120
- const modified = { ...base, tags: ['a', 'b'] };
121
- expect(CogneeSyncManager.contentHash(base)).not.toBe(CogneeSyncManager.contentHash(modified));
122
- });
123
-
124
- it('should NOT change when only updatedAt changes (not in hash)', () => {
125
- const entry = makeEntry({ id: 'same-id' });
126
- const h1 = CogneeSyncManager.contentHash(entry);
127
- // updatedAt is not part of the hash payload
128
- const h2 = CogneeSyncManager.contentHash(entry);
129
- expect(h1).toBe(h2);
130
- });
131
- });
132
-
133
- // ─── Enqueue ──────────────────────────────────────────────────
134
-
135
- describe('enqueue', () => {
136
- it('should add items to the sync queue', () => {
137
- const syncMgr = runtime.syncManager;
138
- const entry = makeEntry();
139
- syncMgr.enqueue('ingest', entry.id, entry);
140
- const stats = syncMgr.getStats();
141
- expect(stats.pending).toBeGreaterThanOrEqual(1);
142
- });
143
-
144
- it('should store content hash when entry is provided', () => {
145
- const syncMgr = runtime.syncManager;
146
- const entry = makeEntry({ id: 'hash-test' });
147
- syncMgr.enqueue('ingest', entry.id, entry);
148
- const row = runtime.vault
149
- .getProvider()
150
- .get<{ content_hash: string }>(
151
- `SELECT content_hash FROM cognee_sync_queue WHERE entry_id = 'hash-test'`,
152
- );
153
- expect(row?.content_hash).toMatch(/^[0-9a-f]{16}$/);
154
- });
155
-
156
- it('should store null hash when entry is not provided (delete op)', () => {
157
- const syncMgr = runtime.syncManager;
158
- syncMgr.enqueue('delete', 'deleted-entry');
159
- const row = runtime.vault
160
- .getProvider()
161
- .get<{ content_hash: string | null }>(
162
- `SELECT content_hash FROM cognee_sync_queue WHERE entry_id = 'deleted-entry'`,
163
- );
164
- expect(row?.content_hash).toBeNull();
165
- });
166
- });
167
-
168
- // ─── Drain with mock Cognee ───────────────────────────────────
169
-
170
- describe('drain — with available Cognee', () => {
171
- it('should process pending ingest items and update ingested hash', async () => {
172
- const entry = makeEntry({ id: 'drain-test-1' });
173
- runtime.vault.seed([entry]);
174
-
175
- // Create a sync manager with a mock Cognee that is available
176
- const mockCognee = makeMockCognee({ available: true });
177
- const syncMgr = new CogneeSyncManager(
178
- runtime.vault.getProvider(),
179
- mockCognee,
180
- 'test-dataset',
181
- );
182
- syncMgr.enqueue('ingest', entry.id, entry);
183
-
184
- const processed = await syncMgr.drain();
185
- expect(processed).toBe(1);
186
-
187
- // Verify addEntries was called
188
- expect(mockCognee.addEntries).toHaveBeenCalledTimes(1);
189
-
190
- // Verify ingested hash was updated on the entries table
191
- const row = runtime.vault
192
- .getProvider()
193
- .get<{ cognee_ingested_hash: string }>(
194
- `SELECT cognee_ingested_hash FROM entries WHERE id = @id`,
195
- { id: entry.id },
196
- );
197
- expect(row?.cognee_ingested_hash).toMatch(/^[0-9a-f]{16}$/);
198
-
199
- // Verify queue item is marked completed
200
- const stats = syncMgr.getStats();
201
- expect(stats.completed).toBe(1);
202
- expect(stats.pending).toBe(0);
203
- });
204
-
205
- it('should process delete operations', async () => {
206
- const entry = makeEntry({ id: 'delete-drain-1' });
207
- runtime.vault.seed([entry]);
208
-
209
- const mockCognee = makeMockCognee({ available: true });
210
- const syncMgr = new CogneeSyncManager(
211
- runtime.vault.getProvider(),
212
- mockCognee,
213
- 'test-dataset',
214
- );
215
- syncMgr.enqueue('delete', entry.id);
216
-
217
- const processed = await syncMgr.drain();
218
- expect(processed).toBe(1);
219
-
220
- // Verify ingested hash was cleared
221
- const row = runtime.vault
222
- .getProvider()
223
- .get<{ cognee_ingested_hash: string | null }>(
224
- `SELECT cognee_ingested_hash FROM entries WHERE id = @id`,
225
- { id: entry.id },
226
- );
227
- expect(row?.cognee_ingested_hash).toBeNull();
228
- });
229
-
230
- it('should handle entry deleted from vault before drain (mark completed)', async () => {
231
- const entry = makeEntry({ id: 'ghost-entry' });
232
- runtime.vault.seed([entry]);
233
-
234
- const mockCognee = makeMockCognee({ available: true });
235
- const syncMgr = new CogneeSyncManager(
236
- runtime.vault.getProvider(),
237
- mockCognee,
238
- 'test-dataset',
239
- );
240
- syncMgr.enqueue('ingest', entry.id, entry);
241
-
242
- // Delete the entry from vault before drain runs
243
- runtime.vault.remove(entry.id);
244
-
245
- const processed = await syncMgr.drain();
246
- // Should still process (mark as completed since entry is gone)
247
- expect(processed).toBeGreaterThanOrEqual(1);
248
-
249
- // addEntries should NOT have been called (entry doesn't exist)
250
- // The drain reads from entries table — if entry is gone, it skips
251
- });
252
-
253
- it('should return 0 when queue is empty', async () => {
254
- const mockCognee = makeMockCognee({ available: true });
255
- const syncMgr = new CogneeSyncManager(
256
- runtime.vault.getProvider(),
257
- mockCognee,
258
- 'test-dataset',
259
- );
260
- const processed = await syncMgr.drain();
261
- expect(processed).toBe(0);
262
- });
263
-
264
- it('should return 0 when Cognee is unavailable', async () => {
265
- const entry = makeEntry({ id: 'unavailable-test' });
266
- runtime.vault.seed([entry]);
267
-
268
- const mockCognee = makeMockCognee({ available: false });
269
- const syncMgr = new CogneeSyncManager(
270
- runtime.vault.getProvider(),
271
- mockCognee,
272
- 'test-dataset',
273
- );
274
- syncMgr.enqueue('ingest', entry.id, entry);
275
-
276
- const processed = await syncMgr.drain();
277
- expect(processed).toBe(0);
278
-
279
- // Queue item should still be pending
280
- const stats = syncMgr.getStats();
281
- expect(stats.pending).toBe(1);
282
- });
283
- });
284
-
285
- // ─── Drain failure handling ───────────────────────────────────
286
-
287
- describe('drain — failure handling', () => {
288
- it('should retry failed items up to MAX_RETRIES then mark failed', async () => {
289
- const entry = makeEntry({ id: 'retry-test' });
290
- runtime.vault.seed([entry]);
291
-
292
- const mockCognee = makeMockCognee({ available: true, addShouldFail: true });
293
- const syncMgr = new CogneeSyncManager(
294
- runtime.vault.getProvider(),
295
- mockCognee,
296
- 'test-dataset',
297
- );
298
- syncMgr.enqueue('ingest', entry.id, entry);
299
-
300
- // Drain 3 times (MAX_RETRIES = 3)
301
- await syncMgr.drain();
302
- await syncMgr.drain();
303
- await syncMgr.drain();
304
-
305
- const stats = syncMgr.getStats();
306
- expect(stats.failed).toBeGreaterThanOrEqual(1);
307
- expect(stats.pending).toBe(0);
308
- });
309
-
310
- it('should record error message on failed items', async () => {
311
- const entry = makeEntry({ id: 'error-msg-test' });
312
- runtime.vault.seed([entry]);
313
-
314
- // Clear auto-enqueued items from runtime.syncManager (different dataset)
315
- runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
316
-
317
- const mockCognee = makeMockCognee({ available: true, addShouldFail: true });
318
- const syncMgr = new CogneeSyncManager(
319
- runtime.vault.getProvider(),
320
- mockCognee,
321
- 'test-dataset',
322
- );
323
- syncMgr.enqueue('ingest', entry.id, entry);
324
-
325
- // Exhaust retries (MAX_RETRIES = 3)
326
- await syncMgr.drain();
327
- await syncMgr.drain();
328
- await syncMgr.drain();
329
-
330
- const row = runtime.vault
331
- .getProvider()
332
- .get<{ error: string; status: string }>(
333
- `SELECT error, status FROM cognee_sync_queue WHERE entry_id = 'error-msg-test' AND dataset = 'test-dataset'`,
334
- );
335
- expect(row?.status).toBe('failed');
336
- expect(row?.error).toContain('Cognee ingest failed');
337
- });
338
-
339
- it('should continue processing other items when one fails', async () => {
340
- const goodEntry = makeEntry({ id: 'good-entry' });
341
- const badEntry = makeEntry({ id: 'bad-entry' });
342
- runtime.vault.seed([goodEntry, badEntry]);
343
-
344
- // Mock: fail only for bad-entry
345
- let callCount = 0;
346
- const mockCognee = makeMockCognee({ available: true });
347
- (mockCognee.addEntries as ReturnType<typeof vi.fn>).mockImplementation(
348
- async (entries: IntelligenceEntry[]) => {
349
- callCount++;
350
- if (entries.some((e) => e.id === 'bad-entry')) {
351
- throw new Error('Selective failure');
352
- }
353
- return { added: entries.length };
354
- },
355
- );
356
-
357
- const syncMgr = new CogneeSyncManager(
358
- runtime.vault.getProvider(),
359
- mockCognee,
360
- 'test-dataset',
361
- );
362
- syncMgr.enqueue('ingest', goodEntry.id, goodEntry);
363
- syncMgr.enqueue('ingest', badEntry.id, badEntry);
364
-
365
- await syncMgr.drain();
366
-
367
- // At least one should have been attempted
368
- expect(callCount).toBeGreaterThanOrEqual(1);
369
- });
370
- });
371
-
372
- // ─── Reconcile (hash drift detection) ─────────────────────────
373
-
374
- describe('reconcile — hash drift detection', () => {
375
- it('should detect entries with null cognee_ingested_hash', () => {
376
- const entry = makeEntry({ id: 'never-synced' });
377
- runtime.vault.seed([entry]);
378
-
379
- // Clear any auto-enqueued items
380
- runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
381
-
382
- const syncMgr = runtime.syncManager;
383
- const enqueued = syncMgr.reconcile();
384
- expect(enqueued).toBeGreaterThanOrEqual(1);
385
- });
386
-
387
- it('should detect entries with stale cognee_ingested_hash', () => {
388
- const entry = makeEntry({ id: 'stale-entry', title: 'Original Title' });
389
- runtime.vault.seed([entry]);
390
-
391
- // Simulate a previously-synced entry by setting a hash
392
- runtime.vault
393
- .getProvider()
394
- .run(`UPDATE entries SET cognee_ingested_hash = 'old-hash-value-xx' WHERE id = @id`, {
395
- id: entry.id,
396
- });
397
-
398
- // Clear queue
399
- runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
400
-
401
- const syncMgr = runtime.syncManager;
402
- const enqueued = syncMgr.reconcile();
403
- // Hash mismatch → should enqueue
404
- expect(enqueued).toBeGreaterThanOrEqual(1);
405
- });
406
-
407
- it('should NOT enqueue entries with matching hash', () => {
408
- const entry = makeEntry({ id: 'up-to-date' });
409
- runtime.vault.seed([entry]);
410
-
411
- // Set the correct hash
412
- const correctHash = CogneeSyncManager.contentHash(entry);
413
- runtime.vault
414
- .getProvider()
415
- .run(`UPDATE entries SET cognee_ingested_hash = @hash WHERE id = @id`, {
416
- hash: correctHash,
417
- id: entry.id,
418
- });
419
-
420
- // Clear queue
421
- runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
422
-
423
- const syncMgr = runtime.syncManager;
424
- const enqueued = syncMgr.reconcile();
425
- expect(enqueued).toBe(0);
426
- });
427
-
428
- it('should not create duplicate pending items for the same entry', () => {
429
- const entry = makeEntry({ id: 'no-dupes' });
430
- runtime.vault.seed([entry]);
431
-
432
- // Clear queue
433
- runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
434
-
435
- const syncMgr = runtime.syncManager;
436
- syncMgr.reconcile();
437
- syncMgr.reconcile(); // Second reconcile should not create duplicates
438
-
439
- const rows = runtime.vault
440
- .getProvider()
441
- .all<{ id: number }>(
442
- `SELECT id FROM cognee_sync_queue WHERE entry_id = 'no-dupes' AND status = 'pending'`,
443
- );
444
- expect(rows.length).toBe(1);
445
- });
446
-
447
- it('should use "ingest" op for null hash and "update" op for stale hash', () => {
448
- // Entry 1: never synced (null hash)
449
- const entry1 = makeEntry({ id: 'null-hash-entry' });
450
- runtime.vault.seed([entry1]);
451
-
452
- // Entry 2: previously synced (stale hash)
453
- const entry2 = makeEntry({ id: 'stale-hash-entry' });
454
- runtime.vault.seed([entry2]);
455
- runtime.vault
456
- .getProvider()
457
- .run(`UPDATE entries SET cognee_ingested_hash = 'stale-value-xxxxx' WHERE id = @id`, {
458
- id: entry2.id,
459
- });
460
-
461
- // Clear queue
462
- runtime.vault.getProvider().run('DELETE FROM cognee_sync_queue');
463
-
464
- runtime.syncManager.reconcile();
465
-
466
- const row1 = runtime.vault
467
- .getProvider()
468
- .get<{ op: string }>(`SELECT op FROM cognee_sync_queue WHERE entry_id = 'null-hash-entry'`);
469
- const row2 = runtime.vault
470
- .getProvider()
471
- .get<{ op: string }>(
472
- `SELECT op FROM cognee_sync_queue WHERE entry_id = 'stale-hash-entry'`,
473
- );
474
-
475
- expect(row1?.op).toBe('ingest');
476
- expect(row2?.op).toBe('update');
477
- });
478
- });
479
-
480
- // ─── Health-flip auto-drain ───────────────────────────────────
481
-
482
- describe('checkHealthFlip', () => {
483
- it('should auto-drain when Cognee transitions from unavailable to available', async () => {
484
- const entry = makeEntry({ id: 'flip-test' });
485
- runtime.vault.seed([entry]);
486
-
487
- // Start with unavailable Cognee
488
- let isAvailable = false;
489
- const mockCognee = {
490
- get isAvailable() {
491
- return isAvailable;
492
- },
493
- healthCheck: vi
494
- .fn()
495
- .mockImplementation(async () => ({ available: isAvailable, url: 'mock', latencyMs: 1 })),
496
- addEntries: vi.fn().mockResolvedValue({ added: 1 }),
497
- deleteEntries: vi.fn().mockResolvedValue({ deleted: 1 }),
498
- cognify: vi.fn().mockResolvedValue({ status: 'ok' }),
499
- search: vi.fn().mockResolvedValue([]),
500
- getConfig: vi.fn().mockReturnValue({ baseUrl: 'mock', dataset: 'test' }),
501
- getStatus: vi.fn().mockReturnValue(null),
502
- flushPendingCognify: vi.fn(),
503
- resetPendingCognify: vi.fn(),
504
- } as unknown as CogneeClient;
505
-
506
- const syncMgr = new CogneeSyncManager(
507
- runtime.vault.getProvider(),
508
- mockCognee,
509
- 'test-dataset',
510
- );
511
- syncMgr.enqueue('ingest', entry.id, entry);
512
-
513
- // While unavailable, checkHealthFlip should not drain
514
- await syncMgr.checkHealthFlip();
515
- const beforeFlip = syncMgr.getStats();
516
- expect(beforeFlip.pending).toBe(1);
517
-
518
- // Now Cognee becomes available
519
- isAvailable = true;
520
- await syncMgr.checkHealthFlip();
521
-
522
- // Should have triggered a drain
523
- expect(mockCognee.addEntries).toHaveBeenCalled();
524
- });
525
-
526
- it('should NOT drain when Cognee stays available (no flip)', async () => {
527
- const isAvailable = true;
528
- const mockCognee = {
529
- get isAvailable() {
530
- return isAvailable;
531
- },
532
- healthCheck: vi.fn().mockResolvedValue({ available: true, url: 'mock', latencyMs: 1 }),
533
- addEntries: vi.fn().mockResolvedValue({ added: 1 }),
534
- deleteEntries: vi.fn(),
535
- cognify: vi.fn(),
536
- search: vi.fn(),
537
- getConfig: vi.fn().mockReturnValue({ baseUrl: 'mock', dataset: 'test' }),
538
- getStatus: vi.fn(),
539
- flushPendingCognify: vi.fn(),
540
- resetPendingCognify: vi.fn(),
541
- } as unknown as CogneeClient;
542
-
543
- // Start with available (wasAvailable = true via constructor)
544
- const syncMgr = new CogneeSyncManager(
545
- runtime.vault.getProvider(),
546
- mockCognee,
547
- 'test-dataset',
548
- );
549
-
550
- const entry = makeEntry({ id: 'no-flip' });
551
- runtime.vault.seed([entry]);
552
- syncMgr.enqueue('ingest', entry.id, entry);
553
-
554
- await syncMgr.checkHealthFlip();
555
-
556
- // No flip → no auto-drain (addEntries should not be called by checkHealthFlip)
557
- // Note: drain() would be called but Cognee is available so it would process,
558
- // but the key is checkHealthFlip only triggers on unavailable → available transition
559
- });
560
- });
561
-
562
- // ─── Stats ────────────────────────────────────────────────────
563
-
564
- describe('getStats', () => {
565
- it('should return all zero counts for empty queue', () => {
566
- const mockCognee = makeMockCognee({ available: true });
567
- const syncMgr = new CogneeSyncManager(
568
- runtime.vault.getProvider(),
569
- mockCognee,
570
- 'test-dataset',
571
- );
572
- const stats = syncMgr.getStats();
573
- expect(stats.pending).toBe(0);
574
- expect(stats.processing).toBe(0);
575
- expect(stats.completed).toBe(0);
576
- expect(stats.failed).toBe(0);
577
- expect(stats.queueSize).toBe(0);
578
- expect(stats.lastDrainAt).toBeNull();
579
- });
580
-
581
- it('should track lastDrainAt after drain', async () => {
582
- const entry = makeEntry({ id: 'drain-time' });
583
- runtime.vault.seed([entry]);
584
-
585
- const mockCognee = makeMockCognee({ available: true });
586
- const syncMgr = new CogneeSyncManager(
587
- runtime.vault.getProvider(),
588
- mockCognee,
589
- 'test-dataset',
590
- );
591
- syncMgr.enqueue('ingest', entry.id, entry);
592
-
593
- const before = Math.floor(Date.now() / 1000);
594
- await syncMgr.drain();
595
- const after = Math.floor(Date.now() / 1000);
596
-
597
- const stats = syncMgr.getStats();
598
- expect(stats.lastDrainAt).toBeGreaterThanOrEqual(before);
599
- expect(stats.lastDrainAt).toBeLessThanOrEqual(after + 1);
600
- });
601
- });
602
-
603
- // ─── Bulk operations ──────────────────────────────────────────
604
-
605
- describe('bulk operations', () => {
606
- it('should handle 50 entries enqueued and drained', async () => {
607
- const entries = Array.from({ length: 50 }, (_, i) =>
608
- makeEntry({ id: `bulk-${i}`, title: `Bulk Entry ${i}` }),
609
- );
610
- runtime.vault.seed(entries);
611
-
612
- const mockCognee = makeMockCognee({ available: true });
613
- const syncMgr = new CogneeSyncManager(
614
- runtime.vault.getProvider(),
615
- mockCognee,
616
- 'test-dataset',
617
- );
618
-
619
- for (const entry of entries) {
620
- syncMgr.enqueue('ingest', entry.id, entry);
621
- }
622
-
623
- const stats = syncMgr.getStats();
624
- expect(stats.pending).toBe(50);
625
-
626
- // Drain processes MAX_BATCH=10 at a time
627
- let totalProcessed = 0;
628
- for (let i = 0; i < 10; i++) {
629
- const processed = await syncMgr.drain();
630
- totalProcessed += processed;
631
- if (processed === 0) break;
632
- }
633
-
634
- expect(totalProcessed).toBe(50);
635
- expect(syncMgr.getStats().completed).toBe(50);
636
- });
637
- });
638
-
639
- // ─── Cleanup ──────────────────────────────────────────────────
640
-
641
- describe('close', () => {
642
- it('should clear drain timer without error', () => {
643
- const mockCognee = makeMockCognee();
644
- const syncMgr = new CogneeSyncManager(
645
- runtime.vault.getProvider(),
646
- mockCognee,
647
- 'test-dataset',
648
- );
649
- expect(() => syncMgr.close()).not.toThrow();
650
- // Double close should be safe
651
- expect(() => syncMgr.close()).not.toThrow();
652
- });
653
- });
654
- });