@soleri/core 2.4.0 → 2.5.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 (300) hide show
  1. package/dist/brain/brain.d.ts +7 -0
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +56 -9
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/types.d.ts +2 -2
  6. package/dist/brain/types.d.ts.map +1 -1
  7. package/dist/cognee/client.d.ts +3 -0
  8. package/dist/cognee/client.d.ts.map +1 -1
  9. package/dist/cognee/client.js +17 -0
  10. package/dist/cognee/client.js.map +1 -1
  11. package/dist/cognee/sync-manager.d.ts +94 -0
  12. package/dist/cognee/sync-manager.d.ts.map +1 -0
  13. package/dist/cognee/sync-manager.js +293 -0
  14. package/dist/cognee/sync-manager.js.map +1 -0
  15. package/dist/curator/curator.d.ts +8 -1
  16. package/dist/curator/curator.d.ts.map +1 -1
  17. package/dist/curator/curator.js +64 -1
  18. package/dist/curator/curator.js.map +1 -1
  19. package/dist/errors/classify.d.ts +13 -0
  20. package/dist/errors/classify.d.ts.map +1 -0
  21. package/dist/errors/classify.js +97 -0
  22. package/dist/errors/classify.js.map +1 -0
  23. package/dist/errors/index.d.ts +6 -0
  24. package/dist/errors/index.d.ts.map +1 -0
  25. package/dist/errors/index.js +4 -0
  26. package/dist/errors/index.js.map +1 -0
  27. package/dist/errors/retry.d.ts +40 -0
  28. package/dist/errors/retry.d.ts.map +1 -0
  29. package/dist/errors/retry.js +97 -0
  30. package/dist/errors/retry.js.map +1 -0
  31. package/dist/errors/types.d.ts +48 -0
  32. package/dist/errors/types.d.ts.map +1 -0
  33. package/dist/errors/types.js +59 -0
  34. package/dist/errors/types.js.map +1 -0
  35. package/dist/index.d.ts +25 -5
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +21 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/intake/content-classifier.d.ts +14 -0
  40. package/dist/intake/content-classifier.d.ts.map +1 -0
  41. package/dist/intake/content-classifier.js +125 -0
  42. package/dist/intake/content-classifier.js.map +1 -0
  43. package/dist/intake/dedup-gate.d.ts +17 -0
  44. package/dist/intake/dedup-gate.d.ts.map +1 -0
  45. package/dist/intake/dedup-gate.js +66 -0
  46. package/dist/intake/dedup-gate.js.map +1 -0
  47. package/dist/intake/intake-pipeline.d.ts +63 -0
  48. package/dist/intake/intake-pipeline.d.ts.map +1 -0
  49. package/dist/intake/intake-pipeline.js +373 -0
  50. package/dist/intake/intake-pipeline.js.map +1 -0
  51. package/dist/intake/types.d.ts +65 -0
  52. package/dist/intake/types.d.ts.map +1 -0
  53. package/dist/intake/types.js +3 -0
  54. package/dist/intake/types.js.map +1 -0
  55. package/dist/intelligence/loader.js +1 -1
  56. package/dist/intelligence/loader.js.map +1 -1
  57. package/dist/intelligence/types.d.ts +3 -1
  58. package/dist/intelligence/types.d.ts.map +1 -1
  59. package/dist/loop/loop-manager.d.ts +58 -7
  60. package/dist/loop/loop-manager.d.ts.map +1 -1
  61. package/dist/loop/loop-manager.js +280 -6
  62. package/dist/loop/loop-manager.js.map +1 -1
  63. package/dist/loop/types.d.ts +69 -1
  64. package/dist/loop/types.d.ts.map +1 -1
  65. package/dist/loop/types.js +4 -1
  66. package/dist/loop/types.js.map +1 -1
  67. package/dist/persistence/index.d.ts +3 -0
  68. package/dist/persistence/index.d.ts.map +1 -0
  69. package/dist/persistence/index.js +2 -0
  70. package/dist/persistence/index.js.map +1 -0
  71. package/dist/persistence/sqlite-provider.d.ts +25 -0
  72. package/dist/persistence/sqlite-provider.d.ts.map +1 -0
  73. package/dist/persistence/sqlite-provider.js +59 -0
  74. package/dist/persistence/sqlite-provider.js.map +1 -0
  75. package/dist/persistence/types.d.ts +36 -0
  76. package/dist/persistence/types.d.ts.map +1 -0
  77. package/dist/persistence/types.js +8 -0
  78. package/dist/persistence/types.js.map +1 -0
  79. package/dist/planning/gap-analysis.d.ts +47 -4
  80. package/dist/planning/gap-analysis.d.ts.map +1 -1
  81. package/dist/planning/gap-analysis.js +190 -13
  82. package/dist/planning/gap-analysis.js.map +1 -1
  83. package/dist/planning/gap-types.d.ts +1 -1
  84. package/dist/planning/gap-types.d.ts.map +1 -1
  85. package/dist/planning/gap-types.js.map +1 -1
  86. package/dist/planning/planner.d.ts +277 -9
  87. package/dist/planning/planner.d.ts.map +1 -1
  88. package/dist/planning/planner.js +611 -46
  89. package/dist/planning/planner.js.map +1 -1
  90. package/dist/playbooks/generic/brainstorming.d.ts +9 -0
  91. package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
  92. package/dist/playbooks/generic/brainstorming.js +105 -0
  93. package/dist/playbooks/generic/brainstorming.js.map +1 -0
  94. package/dist/playbooks/generic/code-review.d.ts +11 -0
  95. package/dist/playbooks/generic/code-review.d.ts.map +1 -0
  96. package/dist/playbooks/generic/code-review.js +176 -0
  97. package/dist/playbooks/generic/code-review.js.map +1 -0
  98. package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
  99. package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
  100. package/dist/playbooks/generic/subagent-execution.js +68 -0
  101. package/dist/playbooks/generic/subagent-execution.js.map +1 -0
  102. package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
  103. package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
  104. package/dist/playbooks/generic/systematic-debugging.js +87 -0
  105. package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
  106. package/dist/playbooks/generic/tdd.d.ts +9 -0
  107. package/dist/playbooks/generic/tdd.d.ts.map +1 -0
  108. package/dist/playbooks/generic/tdd.js +70 -0
  109. package/dist/playbooks/generic/tdd.js.map +1 -0
  110. package/dist/playbooks/generic/verification.d.ts +9 -0
  111. package/dist/playbooks/generic/verification.d.ts.map +1 -0
  112. package/dist/playbooks/generic/verification.js +74 -0
  113. package/dist/playbooks/generic/verification.js.map +1 -0
  114. package/dist/playbooks/index.d.ts +4 -0
  115. package/dist/playbooks/index.d.ts.map +1 -0
  116. package/dist/playbooks/index.js +5 -0
  117. package/dist/playbooks/index.js.map +1 -0
  118. package/dist/playbooks/playbook-registry.d.ts +42 -0
  119. package/dist/playbooks/playbook-registry.d.ts.map +1 -0
  120. package/dist/playbooks/playbook-registry.js +227 -0
  121. package/dist/playbooks/playbook-registry.js.map +1 -0
  122. package/dist/playbooks/playbook-seeder.d.ts +47 -0
  123. package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
  124. package/dist/playbooks/playbook-seeder.js +104 -0
  125. package/dist/playbooks/playbook-seeder.js.map +1 -0
  126. package/dist/playbooks/playbook-types.d.ts +132 -0
  127. package/dist/playbooks/playbook-types.d.ts.map +1 -0
  128. package/dist/playbooks/playbook-types.js +12 -0
  129. package/dist/playbooks/playbook-types.js.map +1 -0
  130. package/dist/project/project-registry.d.ts.map +1 -1
  131. package/dist/project/project-registry.js +9 -11
  132. package/dist/project/project-registry.js.map +1 -1
  133. package/dist/prompts/index.d.ts +4 -0
  134. package/dist/prompts/index.d.ts.map +1 -0
  135. package/dist/prompts/index.js +3 -0
  136. package/dist/prompts/index.js.map +1 -0
  137. package/dist/prompts/parser.d.ts +17 -0
  138. package/dist/prompts/parser.d.ts.map +1 -0
  139. package/dist/prompts/parser.js +47 -0
  140. package/dist/prompts/parser.js.map +1 -0
  141. package/dist/prompts/template-manager.d.ts +25 -0
  142. package/dist/prompts/template-manager.d.ts.map +1 -0
  143. package/dist/prompts/template-manager.js +71 -0
  144. package/dist/prompts/template-manager.js.map +1 -0
  145. package/dist/prompts/types.d.ts +26 -0
  146. package/dist/prompts/types.d.ts.map +1 -0
  147. package/dist/prompts/types.js +5 -0
  148. package/dist/prompts/types.js.map +1 -0
  149. package/dist/runtime/admin-extra-ops.d.ts +5 -3
  150. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  151. package/dist/runtime/admin-extra-ops.js +322 -11
  152. package/dist/runtime/admin-extra-ops.js.map +1 -1
  153. package/dist/runtime/admin-ops.d.ts.map +1 -1
  154. package/dist/runtime/admin-ops.js +10 -3
  155. package/dist/runtime/admin-ops.js.map +1 -1
  156. package/dist/runtime/capture-ops.d.ts.map +1 -1
  157. package/dist/runtime/capture-ops.js +20 -2
  158. package/dist/runtime/capture-ops.js.map +1 -1
  159. package/dist/runtime/cognee-sync-ops.d.ts +12 -0
  160. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
  161. package/dist/runtime/cognee-sync-ops.js +55 -0
  162. package/dist/runtime/cognee-sync-ops.js.map +1 -0
  163. package/dist/runtime/core-ops.d.ts +8 -6
  164. package/dist/runtime/core-ops.d.ts.map +1 -1
  165. package/dist/runtime/core-ops.js +226 -9
  166. package/dist/runtime/core-ops.js.map +1 -1
  167. package/dist/runtime/curator-extra-ops.d.ts +2 -2
  168. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  169. package/dist/runtime/curator-extra-ops.js +15 -3
  170. package/dist/runtime/curator-extra-ops.js.map +1 -1
  171. package/dist/runtime/domain-ops.js +2 -2
  172. package/dist/runtime/domain-ops.js.map +1 -1
  173. package/dist/runtime/grading-ops.d.ts.map +1 -1
  174. package/dist/runtime/grading-ops.js.map +1 -1
  175. package/dist/runtime/intake-ops.d.ts +14 -0
  176. package/dist/runtime/intake-ops.d.ts.map +1 -0
  177. package/dist/runtime/intake-ops.js +110 -0
  178. package/dist/runtime/intake-ops.js.map +1 -0
  179. package/dist/runtime/loop-ops.d.ts +5 -4
  180. package/dist/runtime/loop-ops.d.ts.map +1 -1
  181. package/dist/runtime/loop-ops.js +84 -12
  182. package/dist/runtime/loop-ops.js.map +1 -1
  183. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -1
  184. package/dist/runtime/memory-cross-project-ops.js.map +1 -1
  185. package/dist/runtime/memory-extra-ops.js +5 -5
  186. package/dist/runtime/memory-extra-ops.js.map +1 -1
  187. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  188. package/dist/runtime/orchestrate-ops.js +8 -2
  189. package/dist/runtime/orchestrate-ops.js.map +1 -1
  190. package/dist/runtime/planning-extra-ops.d.ts +13 -5
  191. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  192. package/dist/runtime/planning-extra-ops.js +381 -18
  193. package/dist/runtime/planning-extra-ops.js.map +1 -1
  194. package/dist/runtime/playbook-ops.d.ts +14 -0
  195. package/dist/runtime/playbook-ops.d.ts.map +1 -0
  196. package/dist/runtime/playbook-ops.js +141 -0
  197. package/dist/runtime/playbook-ops.js.map +1 -0
  198. package/dist/runtime/project-ops.d.ts.map +1 -1
  199. package/dist/runtime/project-ops.js +7 -2
  200. package/dist/runtime/project-ops.js.map +1 -1
  201. package/dist/runtime/runtime.d.ts.map +1 -1
  202. package/dist/runtime/runtime.js +27 -8
  203. package/dist/runtime/runtime.js.map +1 -1
  204. package/dist/runtime/types.d.ts +8 -0
  205. package/dist/runtime/types.d.ts.map +1 -1
  206. package/dist/runtime/vault-extra-ops.d.ts +3 -2
  207. package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
  208. package/dist/runtime/vault-extra-ops.js +345 -4
  209. package/dist/runtime/vault-extra-ops.js.map +1 -1
  210. package/dist/vault/playbook.d.ts +34 -0
  211. package/dist/vault/playbook.d.ts.map +1 -0
  212. package/dist/vault/playbook.js +60 -0
  213. package/dist/vault/playbook.js.map +1 -0
  214. package/dist/vault/vault.d.ts +31 -32
  215. package/dist/vault/vault.d.ts.map +1 -1
  216. package/dist/vault/vault.js +201 -181
  217. package/dist/vault/vault.js.map +1 -1
  218. package/package.json +7 -3
  219. package/src/__tests__/admin-extra-ops.test.ts +62 -15
  220. package/src/__tests__/admin-ops.test.ts +2 -2
  221. package/src/__tests__/brain.test.ts +3 -3
  222. package/src/__tests__/cognee-integration.test.ts +80 -0
  223. package/src/__tests__/cognee-sync-manager.test.ts +103 -0
  224. package/src/__tests__/core-ops.test.ts +30 -4
  225. package/src/__tests__/curator-extra-ops.test.ts +24 -2
  226. package/src/__tests__/errors.test.ts +388 -0
  227. package/src/__tests__/grading-ops.test.ts +28 -7
  228. package/src/__tests__/intake-pipeline.test.ts +162 -0
  229. package/src/__tests__/loop-ops.test.ts +74 -3
  230. package/src/__tests__/memory-cross-project-ops.test.ts +3 -1
  231. package/src/__tests__/orchestrate-ops.test.ts +8 -3
  232. package/src/__tests__/persistence.test.ts +225 -0
  233. package/src/__tests__/planner.test.ts +99 -21
  234. package/src/__tests__/planning-extra-ops.test.ts +168 -10
  235. package/src/__tests__/playbook-registry.test.ts +326 -0
  236. package/src/__tests__/playbook-seeder.test.ts +163 -0
  237. package/src/__tests__/playbook.test.ts +389 -0
  238. package/src/__tests__/project-ops.test.ts +18 -4
  239. package/src/__tests__/template-manager.test.ts +222 -0
  240. package/src/__tests__/vault-extra-ops.test.ts +82 -7
  241. package/src/brain/brain.ts +71 -9
  242. package/src/brain/types.ts +2 -2
  243. package/src/cognee/client.ts +18 -0
  244. package/src/cognee/sync-manager.ts +389 -0
  245. package/src/curator/curator.ts +88 -7
  246. package/src/errors/classify.ts +102 -0
  247. package/src/errors/index.ts +5 -0
  248. package/src/errors/retry.ts +132 -0
  249. package/src/errors/types.ts +81 -0
  250. package/src/index.ts +114 -3
  251. package/src/intake/content-classifier.ts +146 -0
  252. package/src/intake/dedup-gate.ts +92 -0
  253. package/src/intake/intake-pipeline.ts +503 -0
  254. package/src/intake/types.ts +69 -0
  255. package/src/intelligence/loader.ts +1 -1
  256. package/src/intelligence/types.ts +3 -1
  257. package/src/loop/loop-manager.ts +325 -7
  258. package/src/loop/types.ts +72 -1
  259. package/src/persistence/index.ts +7 -0
  260. package/src/persistence/sqlite-provider.ts +62 -0
  261. package/src/persistence/types.ts +44 -0
  262. package/src/planning/gap-analysis.ts +286 -17
  263. package/src/planning/gap-types.ts +4 -1
  264. package/src/planning/planner.ts +828 -55
  265. package/src/playbooks/generic/brainstorming.ts +110 -0
  266. package/src/playbooks/generic/code-review.ts +181 -0
  267. package/src/playbooks/generic/subagent-execution.ts +74 -0
  268. package/src/playbooks/generic/systematic-debugging.ts +92 -0
  269. package/src/playbooks/generic/tdd.ts +75 -0
  270. package/src/playbooks/generic/verification.ts +79 -0
  271. package/src/playbooks/index.ts +27 -0
  272. package/src/playbooks/playbook-registry.ts +284 -0
  273. package/src/playbooks/playbook-seeder.ts +119 -0
  274. package/src/playbooks/playbook-types.ts +162 -0
  275. package/src/project/project-registry.ts +29 -17
  276. package/src/prompts/index.ts +3 -0
  277. package/src/prompts/parser.ts +59 -0
  278. package/src/prompts/template-manager.ts +77 -0
  279. package/src/prompts/types.ts +28 -0
  280. package/src/runtime/admin-extra-ops.ts +358 -13
  281. package/src/runtime/admin-ops.ts +17 -6
  282. package/src/runtime/capture-ops.ts +25 -6
  283. package/src/runtime/cognee-sync-ops.ts +63 -0
  284. package/src/runtime/core-ops.ts +258 -8
  285. package/src/runtime/curator-extra-ops.ts +17 -3
  286. package/src/runtime/domain-ops.ts +2 -2
  287. package/src/runtime/grading-ops.ts +11 -2
  288. package/src/runtime/intake-ops.ts +126 -0
  289. package/src/runtime/loop-ops.ts +96 -13
  290. package/src/runtime/memory-cross-project-ops.ts +1 -2
  291. package/src/runtime/memory-extra-ops.ts +5 -5
  292. package/src/runtime/orchestrate-ops.ts +8 -2
  293. package/src/runtime/planning-extra-ops.ts +414 -23
  294. package/src/runtime/playbook-ops.ts +169 -0
  295. package/src/runtime/project-ops.ts +9 -3
  296. package/src/runtime/runtime.ts +35 -9
  297. package/src/runtime/types.ts +8 -0
  298. package/src/runtime/vault-extra-ops.ts +385 -4
  299. package/src/vault/playbook.ts +87 -0
  300. package/src/vault/vault.ts +301 -235
@@ -0,0 +1,389 @@
1
+ /**
2
+ * CogneeSyncManager — queued, resilient sync between the Vault SQLite DB and Cognee.
3
+ *
4
+ * Maintains a persistent queue (`cognee_sync_queue`) so that ingestions, updates,
5
+ * and deletions survive process restarts. Drain is idempotent and retry-safe.
6
+ * Health-flip detection auto-drains when Cognee comes back online.
7
+ *
8
+ * Ported from Salvador MCP's battle-tested cognee-sync module.
9
+ */
10
+
11
+ import { createHash } from 'node:crypto';
12
+ import type { PersistenceProvider } from '../persistence/types.js';
13
+ import type { CogneeClient } from './client.js';
14
+ import type { IntelligenceEntry } from '../intelligence/types.js';
15
+
16
+ // ─── Types ──────────────────────────────────────────────────────────
17
+
18
+ export type SyncOp = 'ingest' | 'update' | 'delete';
19
+ export type SyncStatus = 'pending' | 'processing' | 'completed' | 'failed';
20
+
21
+ export interface SyncQueueItem {
22
+ id: number;
23
+ op: SyncOp;
24
+ entryId: string;
25
+ dataset: string;
26
+ contentHash: string | null;
27
+ status: SyncStatus;
28
+ attempts: number;
29
+ error: string | null;
30
+ createdAt: number;
31
+ processedAt: number | null;
32
+ }
33
+
34
+ export interface SyncManagerStats {
35
+ pending: number;
36
+ processing: number;
37
+ completed: number;
38
+ failed: number;
39
+ queueSize: number;
40
+ lastDrainAt: number | null;
41
+ }
42
+
43
+ // ─── Constants ──────────────────────────────────────────────────────
44
+
45
+ const MAX_BATCH = 10;
46
+ const MAX_RETRIES = 3;
47
+
48
+ // ─── CogneeSyncManager ─────────────────────────────────────────────
49
+
50
+ export class CogneeSyncManager {
51
+ private db: PersistenceProvider;
52
+ private cognee: CogneeClient;
53
+ private dataset: string;
54
+ private lastDrainAt: number | null = null;
55
+ private drainTimer: ReturnType<typeof setInterval> | null = null;
56
+ private wasAvailable: boolean = false;
57
+
58
+ constructor(db: PersistenceProvider, cognee: CogneeClient, dataset: string) {
59
+ this.db = db;
60
+ this.cognee = cognee;
61
+ this.dataset = dataset;
62
+ this.initSchema();
63
+ this.wasAvailable = cognee.isAvailable;
64
+ }
65
+
66
+ // ─── Schema ────────────────────────────────────────────────────
67
+
68
+ private initSchema(): void {
69
+ this.db.execSql(`
70
+ CREATE TABLE IF NOT EXISTS cognee_sync_queue (
71
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
72
+ op TEXT NOT NULL,
73
+ entry_id TEXT NOT NULL,
74
+ dataset TEXT NOT NULL,
75
+ content_hash TEXT,
76
+ status TEXT NOT NULL DEFAULT 'pending',
77
+ attempts INTEGER NOT NULL DEFAULT 0,
78
+ error TEXT,
79
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
80
+ processed_at INTEGER
81
+ )
82
+ `);
83
+
84
+ // Add cognee_ingested_hash column to entries table for reconciliation.
85
+ // ALTER TABLE ... ADD COLUMN is a no-op error when the column already exists.
86
+ try {
87
+ this.db.run('ALTER TABLE entries ADD COLUMN cognee_ingested_hash TEXT');
88
+ } catch {
89
+ // Column already exists — expected on subsequent runs.
90
+ }
91
+ }
92
+
93
+ // ─── Content hashing ──────────────────────────────────────────
94
+
95
+ /**
96
+ * SHA-256 of the serialized entry fields, truncated to 16 hex characters.
97
+ * Deterministic for identical content — used to detect drift.
98
+ */
99
+ static contentHash(entry: IntelligenceEntry): string {
100
+ const payload = JSON.stringify({
101
+ id: entry.id,
102
+ type: entry.type,
103
+ domain: entry.domain,
104
+ title: entry.title,
105
+ severity: entry.severity,
106
+ description: entry.description,
107
+ context: entry.context ?? null,
108
+ example: entry.example ?? null,
109
+ counterExample: entry.counterExample ?? null,
110
+ why: entry.why ?? null,
111
+ tags: entry.tags,
112
+ appliesTo: entry.appliesTo ?? [],
113
+ });
114
+ return createHash('sha256').update(payload).digest('hex').slice(0, 16);
115
+ }
116
+
117
+ // ─── Enqueue ──────────────────────────────────────────────────
118
+
119
+ /**
120
+ * Add an operation to the sync queue.
121
+ *
122
+ * @param op The operation type (ingest | update | delete).
123
+ * @param entryId The vault entry ID.
124
+ * @param entry Optional entry for hash computation. If omitted, hash is null.
125
+ */
126
+ enqueue(op: SyncOp, entryId: string, entry?: IntelligenceEntry): void {
127
+ const contentHash = entry ? CogneeSyncManager.contentHash(entry) : null;
128
+ this.db.run(
129
+ `INSERT INTO cognee_sync_queue (op, entry_id, dataset, content_hash)
130
+ VALUES (@op, @entryId, @dataset, @contentHash)`,
131
+ {
132
+ op,
133
+ entryId,
134
+ dataset: this.dataset,
135
+ contentHash,
136
+ },
137
+ );
138
+ }
139
+
140
+ // ─── Drain ────────────────────────────────────────────────────
141
+
142
+ /**
143
+ * Process up to MAX_BATCH pending items from the queue.
144
+ * Returns the number of items successfully processed.
145
+ *
146
+ * If Cognee is not available, returns 0 without touching the queue.
147
+ */
148
+ async drain(): Promise<number> {
149
+ if (!this.cognee.isAvailable) return 0;
150
+
151
+ // Claim a batch: move pending → processing
152
+ const items = this.db.all<Record<string, unknown>>(
153
+ `SELECT * FROM cognee_sync_queue
154
+ WHERE status = 'pending' AND dataset = @dataset
155
+ ORDER BY created_at ASC
156
+ LIMIT @limit`,
157
+ { dataset: this.dataset, limit: MAX_BATCH },
158
+ );
159
+
160
+ if (items.length === 0) return 0;
161
+
162
+ let processed = 0;
163
+
164
+ for (const raw of items) {
165
+ const item = rowToQueueItem(raw);
166
+
167
+ // Mark as processing
168
+ this.db.run(
169
+ `UPDATE cognee_sync_queue SET status = 'processing', attempts = attempts + 1 WHERE id = @id`,
170
+ { id: item.id },
171
+ );
172
+
173
+ try {
174
+ if (item.op === 'ingest' || item.op === 'update') {
175
+ const entry = this.readEntry(item.entryId);
176
+ if (!entry) {
177
+ // Entry was deleted from vault before we could sync — mark completed
178
+ this.markCompleted(item.id);
179
+ processed++;
180
+ continue;
181
+ }
182
+
183
+ const result = await this.cognee.addEntries([entry]);
184
+ if (result.added === 0) {
185
+ throw new Error('Cognee addEntries returned 0 added');
186
+ }
187
+
188
+ // Update the ingested hash on the entries table
189
+ const hash = CogneeSyncManager.contentHash(entry);
190
+ this.db.run(`UPDATE entries SET cognee_ingested_hash = @hash WHERE id = @id`, {
191
+ hash,
192
+ id: item.entryId,
193
+ });
194
+
195
+ this.markCompleted(item.id);
196
+ processed++;
197
+ } else if (item.op === 'delete') {
198
+ // deleteEntries may not exist yet — graceful degradation
199
+ const client = this.cognee as unknown as Record<string, unknown>;
200
+ if (typeof client.deleteEntries === 'function') {
201
+ await (client.deleteEntries as (ids: string[]) => Promise<unknown>)([item.entryId]);
202
+ }
203
+ // Clear the ingested hash (entry may already be gone from entries table)
204
+ this.db.run(`UPDATE entries SET cognee_ingested_hash = NULL WHERE id = @id`, {
205
+ id: item.entryId,
206
+ });
207
+ this.markCompleted(item.id);
208
+ processed++;
209
+ }
210
+ } catch (err) {
211
+ const errorMsg = err instanceof Error ? err.message : String(err);
212
+ const attempts = item.attempts + 1; // Already incremented above
213
+ if (attempts >= MAX_RETRIES) {
214
+ this.db.run(
215
+ `UPDATE cognee_sync_queue SET status = 'failed', error = @error, processed_at = unixepoch() WHERE id = @id`,
216
+ { id: item.id, error: errorMsg },
217
+ );
218
+ } else {
219
+ // Back to pending for retry
220
+ this.db.run(
221
+ `UPDATE cognee_sync_queue SET status = 'pending', error = @error WHERE id = @id`,
222
+ { id: item.id, error: errorMsg },
223
+ );
224
+ }
225
+ }
226
+ }
227
+
228
+ this.lastDrainAt = Math.floor(Date.now() / 1000);
229
+ return processed;
230
+ }
231
+
232
+ // ─── Reconciliation ───────────────────────────────────────────
233
+
234
+ /**
235
+ * Find entries whose cognee_ingested_hash is NULL or doesn't match the
236
+ * current content hash. Enqueue dirty entries for re-ingestion.
237
+ *
238
+ * Returns the number of entries enqueued.
239
+ */
240
+ reconcile(): number {
241
+ // Get all entries that either have never been ingested or whose content changed
242
+ const rows = this.db.all<Record<string, unknown>>(
243
+ `SELECT * FROM entries WHERE cognee_ingested_hash IS NULL
244
+ UNION ALL
245
+ SELECT * FROM entries WHERE cognee_ingested_hash IS NOT NULL`,
246
+ );
247
+
248
+ let enqueued = 0;
249
+
250
+ for (const raw of rows) {
251
+ const entry = this.rowToEntry(raw);
252
+ const currentHash = CogneeSyncManager.contentHash(entry);
253
+ const ingestedHash = raw.cognee_ingested_hash as string | null;
254
+
255
+ if (ingestedHash === currentHash) continue;
256
+
257
+ // Determine op: null hash means never ingested, mismatched means update
258
+ const op: SyncOp = ingestedHash === null ? 'ingest' : 'update';
259
+
260
+ // Avoid duplicate pending items for the same entry
261
+ const existing = this.db.get<{ id: number }>(
262
+ `SELECT id FROM cognee_sync_queue
263
+ WHERE entry_id = @entryId AND dataset = @dataset AND status = 'pending'`,
264
+ { entryId: entry.id, dataset: this.dataset },
265
+ );
266
+
267
+ if (!existing) {
268
+ this.enqueue(op, entry.id, entry);
269
+ enqueued++;
270
+ }
271
+ }
272
+
273
+ return enqueued;
274
+ }
275
+
276
+ // ─── Stats ────────────────────────────────────────────────────
277
+
278
+ getStats(): SyncManagerStats {
279
+ const countByStatus = (status: SyncStatus): number => {
280
+ const row = this.db.get<{ count: number }>(
281
+ `SELECT COUNT(*) as count FROM cognee_sync_queue WHERE status = @status AND dataset = @dataset`,
282
+ { status, dataset: this.dataset },
283
+ );
284
+ return row?.count ?? 0;
285
+ };
286
+
287
+ const pending = countByStatus('pending');
288
+ const processing = countByStatus('processing');
289
+ const completed = countByStatus('completed');
290
+ const failed = countByStatus('failed');
291
+
292
+ return {
293
+ pending,
294
+ processing,
295
+ completed,
296
+ failed,
297
+ queueSize: pending + processing,
298
+ lastDrainAt: this.lastDrainAt,
299
+ };
300
+ }
301
+
302
+ // ─── Health-flip detection ────────────────────────────────────
303
+
304
+ /**
305
+ * Detects an unavailable-to-available transition on the Cognee client.
306
+ * When Cognee comes back online, automatically triggers a drain.
307
+ *
308
+ * Call this periodically (e.g. after each health check).
309
+ */
310
+ async checkHealthFlip(): Promise<void> {
311
+ const nowAvailable = this.cognee.isAvailable;
312
+ if (nowAvailable && !this.wasAvailable) {
313
+ // Cognee just came back online — drain the queue
314
+ await this.drain();
315
+ }
316
+ this.wasAvailable = nowAvailable;
317
+ }
318
+
319
+ // ─── Cleanup ──────────────────────────────────────────────────
320
+
321
+ /**
322
+ * Clear any periodic drain timer.
323
+ */
324
+ close(): void {
325
+ if (this.drainTimer) {
326
+ clearInterval(this.drainTimer);
327
+ this.drainTimer = null;
328
+ }
329
+ }
330
+
331
+ // ─── Private helpers ──────────────────────────────────────────
332
+
333
+ private markCompleted(id: number): void {
334
+ this.db.run(
335
+ `UPDATE cognee_sync_queue SET status = 'completed', processed_at = unixepoch() WHERE id = @id`,
336
+ { id },
337
+ );
338
+ }
339
+
340
+ /**
341
+ * Read an entry from the entries table by ID.
342
+ * Returns null if the entry doesn't exist.
343
+ */
344
+ private readEntry(id: string): IntelligenceEntry | null {
345
+ const row = this.db.get<Record<string, unknown>>('SELECT * FROM entries WHERE id = @id', {
346
+ id,
347
+ });
348
+ return row ? this.rowToEntry(row) : null;
349
+ }
350
+
351
+ /**
352
+ * Convert a raw DB row into an IntelligenceEntry.
353
+ */
354
+ private rowToEntry(row: Record<string, unknown>): IntelligenceEntry {
355
+ return {
356
+ id: row.id as string,
357
+ type: row.type as IntelligenceEntry['type'],
358
+ domain: row.domain as string,
359
+ title: row.title as string,
360
+ severity: row.severity as IntelligenceEntry['severity'],
361
+ description: row.description as string,
362
+ context: (row.context as string) ?? undefined,
363
+ example: (row.example as string) ?? undefined,
364
+ counterExample: (row.counter_example as string) ?? undefined,
365
+ why: (row.why as string) ?? undefined,
366
+ tags: JSON.parse((row.tags as string) || '[]'),
367
+ appliesTo: JSON.parse((row.applies_to as string) || '[]'),
368
+ validFrom: (row.valid_from as number) ?? undefined,
369
+ validUntil: (row.valid_until as number) ?? undefined,
370
+ };
371
+ }
372
+ }
373
+
374
+ // ─── Module-level helpers ─────────────────────────────────────────
375
+
376
+ function rowToQueueItem(row: Record<string, unknown>): SyncQueueItem {
377
+ return {
378
+ id: row.id as number,
379
+ op: row.op as SyncOp,
380
+ entryId: row.entry_id as string,
381
+ dataset: row.dataset as string,
382
+ contentHash: (row.content_hash as string) ?? null,
383
+ status: row.status as SyncStatus,
384
+ attempts: row.attempts as number,
385
+ error: (row.error as string) ?? null,
386
+ createdAt: row.created_at as number,
387
+ processedAt: (row.processed_at as number) ?? null,
388
+ };
389
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Vault } from '../vault/vault.js';
2
2
  import type { IntelligenceEntry } from '../intelligence/types.js';
3
+ import type { CogneeClient } from '../cognee/client.js';
3
4
  import {
4
5
  tokenize,
5
6
  calculateTfIdf,
@@ -49,9 +50,11 @@ const DEFAULT_TAG_ALIASES: Array<[string, string]> = [
49
50
 
50
51
  export class Curator {
51
52
  private vault: Vault;
53
+ private cognee: CogneeClient | undefined;
52
54
 
53
- constructor(vault: Vault) {
55
+ constructor(vault: Vault, cognee?: CogneeClient) {
54
56
  this.vault = vault;
57
+ this.cognee = cognee;
55
58
  this.initializeTables();
56
59
  this.seedDefaultAliases();
57
60
  }
@@ -378,6 +381,85 @@ export class Curator {
378
381
  return row ? this.rowToContradiction(row) : null;
379
382
  }
380
383
 
384
+ async detectContradictionsHybrid(threshold?: number): Promise<{
385
+ contradictions: Contradiction[];
386
+ cogneeAvailable: boolean;
387
+ method: 'hybrid' | 'tfidf-only';
388
+ }> {
389
+ const effectiveThreshold = threshold ?? DEFAULT_CONTRADICTION_THRESHOLD;
390
+ const entries = this.vault.list({ limit: 100000 });
391
+ const antipatterns = entries.filter((e) => e.type === 'anti-pattern');
392
+ const patterns = entries.filter((e) => e.type === 'pattern');
393
+
394
+ if (antipatterns.length === 0 || patterns.length === 0) {
395
+ return { contradictions: [], cogneeAvailable: false, method: 'tfidf-only' };
396
+ }
397
+
398
+ const vocabulary = this.buildVocabulary(entries);
399
+ const db = this.vault.getDb();
400
+ const detected: Contradiction[] = [];
401
+
402
+ const insertStmt = db.prepare(
403
+ `INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity)
404
+ VALUES (?, ?, ?)`,
405
+ );
406
+
407
+ const cogneeAvailable = this.cognee?.isAvailable ?? false;
408
+
409
+ for (const ap of antipatterns) {
410
+ let candidates: IntelligenceEntry[];
411
+ try {
412
+ const searchResults = this.vault.search(ap.title, { type: 'pattern', limit: 20 });
413
+ candidates = searchResults.length > 0 ? searchResults.map((r) => r.entry) : patterns;
414
+ } catch {
415
+ candidates = patterns;
416
+ }
417
+
418
+ const apText = [ap.title, ap.description, ap.context ?? ''].join(' ');
419
+ const apVec = calculateTfIdf(tokenize(apText), vocabulary);
420
+
421
+ for (const pattern of candidates) {
422
+ const pText = [pattern.title, pattern.description, pattern.context ?? ''].join(' ');
423
+ const pVec = calculateTfIdf(tokenize(pText), vocabulary);
424
+ const tfidfScore = cosineSimilarity(apVec, pVec);
425
+
426
+ let finalScore = tfidfScore;
427
+ if (cogneeAvailable && this.cognee) {
428
+ try {
429
+ const cogneeResults = await this.cognee.search(`${ap.title} ${pattern.title}`, {
430
+ limit: 5,
431
+ });
432
+ const cogneeScore =
433
+ cogneeResults.length > 0
434
+ ? cogneeResults.reduce((sum, r) => sum + r.score, 0) / cogneeResults.length
435
+ : 0;
436
+ finalScore = 0.6 * tfidfScore + 0.4 * cogneeScore;
437
+ } catch {
438
+ finalScore = tfidfScore;
439
+ }
440
+ }
441
+
442
+ if (finalScore >= effectiveThreshold) {
443
+ const result = insertStmt.run(pattern.id, ap.id, finalScore);
444
+ if (result.changes > 0) {
445
+ const row = db
446
+ .prepare(
447
+ 'SELECT * FROM curator_contradictions WHERE pattern_id = ? AND antipattern_id = ?',
448
+ )
449
+ .get(pattern.id, ap.id) as Record<string, unknown>;
450
+ detected.push(this.rowToContradiction(row));
451
+ }
452
+ }
453
+ }
454
+ }
455
+
456
+ return {
457
+ contradictions: detected,
458
+ cogneeAvailable,
459
+ method: cogneeAvailable ? 'hybrid' : 'tfidf-only',
460
+ };
461
+ }
462
+
381
463
  // ─── Grooming ───────────────────────────────────────────────────
382
464
 
383
465
  groomEntry(entryId: string): GroomResult | null {
@@ -667,9 +749,7 @@ export class Curator {
667
749
  return { recorded: true, historyId: Number(result.lastInsertRowid) };
668
750
  }
669
751
 
670
- getVersionHistory(
671
- entryId: string,
672
- ): Array<{
752
+ getVersionHistory(entryId: string): Array<{
673
753
  historyId: number;
674
754
  entryId: string;
675
755
  snapshot: IntelligenceEntry;
@@ -762,9 +842,10 @@ export class Curator {
762
842
 
763
843
  // ─── Metadata Enrichment ──────────────────────────────────────
764
844
 
765
- enrichMetadata(
766
- entryId: string,
767
- ): { enriched: boolean; changes: Array<{ field: string; before: string; after: string }> } {
845
+ enrichMetadata(entryId: string): {
846
+ enriched: boolean;
847
+ changes: Array<{ field: string; before: string; after: string }>;
848
+ } {
768
849
  const entry = this.vault.get(entryId);
769
850
  if (!entry) return { enriched: false, changes: [] };
770
851
 
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Error classifier — converts any thrown value into a typed SoleriError.
3
+ *
4
+ * Maps HTTP status codes, network error codes, and message patterns
5
+ * to the appropriate SoleriErrorCode.
6
+ */
7
+
8
+ import { SoleriError, SoleriErrorCode } from './types.js';
9
+
10
+ interface ErrorLike {
11
+ message?: string;
12
+ status?: number;
13
+ statusCode?: number;
14
+ code?: string;
15
+ cause?: unknown;
16
+ }
17
+
18
+ function toErrorLike(error: unknown): ErrorLike {
19
+ if (error instanceof SoleriError) return error;
20
+ if (error instanceof Error) return error as unknown as ErrorLike;
21
+ if (typeof error === 'object' && error !== null) return error as ErrorLike;
22
+ return { message: String(error) };
23
+ }
24
+
25
+ function getHttpStatus(e: ErrorLike): number | undefined {
26
+ return e.status ?? e.statusCode;
27
+ }
28
+
29
+ function classifyByHttpStatus(status: number): SoleriErrorCode | undefined {
30
+ if (status === 401 || status === 403) return SoleriErrorCode.AUTH;
31
+ if (status === 404) return SoleriErrorCode.RESOURCE_NOT_FOUND;
32
+ if (status === 408) return SoleriErrorCode.TIMEOUT;
33
+ if (status === 429) return SoleriErrorCode.RATE_LIMIT;
34
+ if (status === 422) return SoleriErrorCode.VALIDATION;
35
+ if (status >= 500 && status < 600) return SoleriErrorCode.INTERNAL;
36
+ return undefined;
37
+ }
38
+
39
+ const NETWORK_CODES = new Set(['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH']);
40
+ const TIMEOUT_CODES = new Set(['ETIMEDOUT', 'ESOCKETTIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT']);
41
+
42
+ function classifyByErrorCode(code: string | undefined): SoleriErrorCode | undefined {
43
+ if (!code) return undefined;
44
+ if (NETWORK_CODES.has(code)) return SoleriErrorCode.NETWORK;
45
+ if (TIMEOUT_CODES.has(code)) return SoleriErrorCode.TIMEOUT;
46
+ return undefined;
47
+ }
48
+
49
+ const MESSAGE_PATTERNS: Array<[RegExp, SoleriErrorCode]> = [
50
+ [/overloaded|capacity|model.*busy/i, SoleriErrorCode.LLM_OVERLOAD],
51
+ [/timeout|timed?\s*out/i, SoleriErrorCode.TIMEOUT],
52
+ [/vault|database|sqlite/i, SoleriErrorCode.VAULT_UNREACHABLE],
53
+ [/invalid|validation|schema/i, SoleriErrorCode.VALIDATION],
54
+ [/config(uration)?|missing.*key|env/i, SoleriErrorCode.CONFIG_ERROR],
55
+ [/auth(entication|orization)?|forbidden|denied|unauthorized/i, SoleriErrorCode.AUTH],
56
+ [/not\s*found|404|no\s+such/i, SoleriErrorCode.RESOURCE_NOT_FOUND],
57
+ [/rate\s*limit|too\s+many\s+requests|throttl/i, SoleriErrorCode.RATE_LIMIT],
58
+ [/network|connect|socket|dns/i, SoleriErrorCode.NETWORK],
59
+ ];
60
+
61
+ function classifyByMessage(message: string | undefined): SoleriErrorCode | undefined {
62
+ if (!message) return undefined;
63
+ for (const [pattern, code] of MESSAGE_PATTERNS) {
64
+ if (pattern.test(message)) return code;
65
+ }
66
+ return undefined;
67
+ }
68
+
69
+ /**
70
+ * Classify any thrown value into a SoleriError.
71
+ * If the value is already a SoleriError, returns it as-is.
72
+ */
73
+ export function classifyError(error: unknown): SoleriError {
74
+ if (error instanceof SoleriError) return error;
75
+
76
+ const e = toErrorLike(error);
77
+ const originalError = error instanceof Error ? error : undefined;
78
+ const message = e.message ?? 'Unknown error';
79
+
80
+ // 1. HTTP status code
81
+ const httpStatus = getHttpStatus(e);
82
+ if (httpStatus !== undefined) {
83
+ const code = classifyByHttpStatus(httpStatus);
84
+ if (code)
85
+ return new SoleriError(message, code, { cause: originalError, context: { httpStatus } });
86
+ }
87
+
88
+ // 2. Node.js error code
89
+ const errCode = classifyByErrorCode(e.code);
90
+ if (errCode)
91
+ return new SoleriError(message, errCode, {
92
+ cause: originalError,
93
+ context: { errorCode: e.code },
94
+ });
95
+
96
+ // 3. Message pattern matching
97
+ const msgCode = classifyByMessage(message);
98
+ if (msgCode) return new SoleriError(message, msgCode, { cause: originalError });
99
+
100
+ // 4. Default: permanent INTERNAL
101
+ return new SoleriError(message, SoleriErrorCode.INTERNAL, { cause: originalError });
102
+ }
@@ -0,0 +1,5 @@
1
+ export { SoleriErrorCode, SoleriError, ok, err, isOk, isErr } from './types.js';
2
+ export type { ErrorClassification, Result, SoleriErrorOptions } from './types.js';
3
+ export { classifyError } from './classify.js';
4
+ export { retryWithPreset, shouldRetry, getRetryDelay, RETRY_PRESETS } from './retry.js';
5
+ export type { RetryPreset, RetryConfig, RetryOptions } from './retry.js';