@soleri/core 2.4.0 → 2.6.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 (328) 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/intelligence.d.ts +1 -0
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +164 -148
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +2 -2
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/cognee/client.d.ts +3 -0
  12. package/dist/cognee/client.d.ts.map +1 -1
  13. package/dist/cognee/client.js +17 -0
  14. package/dist/cognee/client.js.map +1 -1
  15. package/dist/cognee/sync-manager.d.ts +94 -0
  16. package/dist/cognee/sync-manager.d.ts.map +1 -0
  17. package/dist/cognee/sync-manager.js +293 -0
  18. package/dist/cognee/sync-manager.js.map +1 -0
  19. package/dist/control/identity-manager.d.ts +3 -1
  20. package/dist/control/identity-manager.d.ts.map +1 -1
  21. package/dist/control/identity-manager.js +49 -51
  22. package/dist/control/identity-manager.js.map +1 -1
  23. package/dist/control/intent-router.d.ts +1 -0
  24. package/dist/control/intent-router.d.ts.map +1 -1
  25. package/dist/control/intent-router.js +32 -32
  26. package/dist/control/intent-router.js.map +1 -1
  27. package/dist/curator/curator.d.ts +9 -1
  28. package/dist/curator/curator.d.ts.map +1 -1
  29. package/dist/curator/curator.js +104 -92
  30. package/dist/curator/curator.js.map +1 -1
  31. package/dist/errors/classify.d.ts +13 -0
  32. package/dist/errors/classify.d.ts.map +1 -0
  33. package/dist/errors/classify.js +97 -0
  34. package/dist/errors/classify.js.map +1 -0
  35. package/dist/errors/index.d.ts +6 -0
  36. package/dist/errors/index.d.ts.map +1 -0
  37. package/dist/errors/index.js +4 -0
  38. package/dist/errors/index.js.map +1 -0
  39. package/dist/errors/retry.d.ts +40 -0
  40. package/dist/errors/retry.d.ts.map +1 -0
  41. package/dist/errors/retry.js +97 -0
  42. package/dist/errors/retry.js.map +1 -0
  43. package/dist/errors/types.d.ts +48 -0
  44. package/dist/errors/types.d.ts.map +1 -0
  45. package/dist/errors/types.js +59 -0
  46. package/dist/errors/types.js.map +1 -0
  47. package/dist/governance/governance.d.ts +1 -0
  48. package/dist/governance/governance.d.ts.map +1 -1
  49. package/dist/governance/governance.js +51 -68
  50. package/dist/governance/governance.js.map +1 -1
  51. package/dist/index.d.ts +26 -5
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +22 -3
  54. package/dist/index.js.map +1 -1
  55. package/dist/intake/content-classifier.d.ts +14 -0
  56. package/dist/intake/content-classifier.d.ts.map +1 -0
  57. package/dist/intake/content-classifier.js +125 -0
  58. package/dist/intake/content-classifier.js.map +1 -0
  59. package/dist/intake/dedup-gate.d.ts +17 -0
  60. package/dist/intake/dedup-gate.d.ts.map +1 -0
  61. package/dist/intake/dedup-gate.js +66 -0
  62. package/dist/intake/dedup-gate.js.map +1 -0
  63. package/dist/intake/intake-pipeline.d.ts +63 -0
  64. package/dist/intake/intake-pipeline.d.ts.map +1 -0
  65. package/dist/intake/intake-pipeline.js +373 -0
  66. package/dist/intake/intake-pipeline.js.map +1 -0
  67. package/dist/intake/types.d.ts +65 -0
  68. package/dist/intake/types.d.ts.map +1 -0
  69. package/dist/intake/types.js +3 -0
  70. package/dist/intake/types.js.map +1 -0
  71. package/dist/intelligence/loader.js +1 -1
  72. package/dist/intelligence/loader.js.map +1 -1
  73. package/dist/intelligence/types.d.ts +3 -1
  74. package/dist/intelligence/types.d.ts.map +1 -1
  75. package/dist/loop/loop-manager.d.ts +58 -7
  76. package/dist/loop/loop-manager.d.ts.map +1 -1
  77. package/dist/loop/loop-manager.js +280 -6
  78. package/dist/loop/loop-manager.js.map +1 -1
  79. package/dist/loop/types.d.ts +69 -1
  80. package/dist/loop/types.d.ts.map +1 -1
  81. package/dist/loop/types.js +4 -1
  82. package/dist/loop/types.js.map +1 -1
  83. package/dist/persistence/index.d.ts +4 -0
  84. package/dist/persistence/index.d.ts.map +1 -0
  85. package/dist/persistence/index.js +3 -0
  86. package/dist/persistence/index.js.map +1 -0
  87. package/dist/persistence/postgres-provider.d.ts +46 -0
  88. package/dist/persistence/postgres-provider.d.ts.map +1 -0
  89. package/dist/persistence/postgres-provider.js +115 -0
  90. package/dist/persistence/postgres-provider.js.map +1 -0
  91. package/dist/persistence/sqlite-provider.d.ts +28 -0
  92. package/dist/persistence/sqlite-provider.d.ts.map +1 -0
  93. package/dist/persistence/sqlite-provider.js +97 -0
  94. package/dist/persistence/sqlite-provider.js.map +1 -0
  95. package/dist/persistence/types.d.ts +58 -0
  96. package/dist/persistence/types.d.ts.map +1 -0
  97. package/dist/persistence/types.js +8 -0
  98. package/dist/persistence/types.js.map +1 -0
  99. package/dist/planning/gap-analysis.d.ts +47 -4
  100. package/dist/planning/gap-analysis.d.ts.map +1 -1
  101. package/dist/planning/gap-analysis.js +190 -13
  102. package/dist/planning/gap-analysis.js.map +1 -1
  103. package/dist/planning/gap-types.d.ts +1 -1
  104. package/dist/planning/gap-types.d.ts.map +1 -1
  105. package/dist/planning/gap-types.js.map +1 -1
  106. package/dist/planning/planner.d.ts +277 -9
  107. package/dist/planning/planner.d.ts.map +1 -1
  108. package/dist/planning/planner.js +611 -46
  109. package/dist/planning/planner.js.map +1 -1
  110. package/dist/playbooks/generic/brainstorming.d.ts +9 -0
  111. package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
  112. package/dist/playbooks/generic/brainstorming.js +105 -0
  113. package/dist/playbooks/generic/brainstorming.js.map +1 -0
  114. package/dist/playbooks/generic/code-review.d.ts +11 -0
  115. package/dist/playbooks/generic/code-review.d.ts.map +1 -0
  116. package/dist/playbooks/generic/code-review.js +176 -0
  117. package/dist/playbooks/generic/code-review.js.map +1 -0
  118. package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
  119. package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
  120. package/dist/playbooks/generic/subagent-execution.js +68 -0
  121. package/dist/playbooks/generic/subagent-execution.js.map +1 -0
  122. package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
  123. package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
  124. package/dist/playbooks/generic/systematic-debugging.js +87 -0
  125. package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
  126. package/dist/playbooks/generic/tdd.d.ts +9 -0
  127. package/dist/playbooks/generic/tdd.d.ts.map +1 -0
  128. package/dist/playbooks/generic/tdd.js +70 -0
  129. package/dist/playbooks/generic/tdd.js.map +1 -0
  130. package/dist/playbooks/generic/verification.d.ts +9 -0
  131. package/dist/playbooks/generic/verification.d.ts.map +1 -0
  132. package/dist/playbooks/generic/verification.js +74 -0
  133. package/dist/playbooks/generic/verification.js.map +1 -0
  134. package/dist/playbooks/index.d.ts +4 -0
  135. package/dist/playbooks/index.d.ts.map +1 -0
  136. package/dist/playbooks/index.js +5 -0
  137. package/dist/playbooks/index.js.map +1 -0
  138. package/dist/playbooks/playbook-registry.d.ts +42 -0
  139. package/dist/playbooks/playbook-registry.d.ts.map +1 -0
  140. package/dist/playbooks/playbook-registry.js +227 -0
  141. package/dist/playbooks/playbook-registry.js.map +1 -0
  142. package/dist/playbooks/playbook-seeder.d.ts +47 -0
  143. package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
  144. package/dist/playbooks/playbook-seeder.js +104 -0
  145. package/dist/playbooks/playbook-seeder.js.map +1 -0
  146. package/dist/playbooks/playbook-types.d.ts +132 -0
  147. package/dist/playbooks/playbook-types.d.ts.map +1 -0
  148. package/dist/playbooks/playbook-types.js +12 -0
  149. package/dist/playbooks/playbook-types.js.map +1 -0
  150. package/dist/project/project-registry.d.ts +4 -4
  151. package/dist/project/project-registry.d.ts.map +1 -1
  152. package/dist/project/project-registry.js +30 -57
  153. package/dist/project/project-registry.js.map +1 -1
  154. package/dist/prompts/index.d.ts +4 -0
  155. package/dist/prompts/index.d.ts.map +1 -0
  156. package/dist/prompts/index.js +3 -0
  157. package/dist/prompts/index.js.map +1 -0
  158. package/dist/prompts/parser.d.ts +17 -0
  159. package/dist/prompts/parser.d.ts.map +1 -0
  160. package/dist/prompts/parser.js +47 -0
  161. package/dist/prompts/parser.js.map +1 -0
  162. package/dist/prompts/template-manager.d.ts +25 -0
  163. package/dist/prompts/template-manager.d.ts.map +1 -0
  164. package/dist/prompts/template-manager.js +71 -0
  165. package/dist/prompts/template-manager.js.map +1 -0
  166. package/dist/prompts/types.d.ts +26 -0
  167. package/dist/prompts/types.d.ts.map +1 -0
  168. package/dist/prompts/types.js +5 -0
  169. package/dist/prompts/types.js.map +1 -0
  170. package/dist/runtime/admin-extra-ops.d.ts +5 -3
  171. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  172. package/dist/runtime/admin-extra-ops.js +348 -11
  173. package/dist/runtime/admin-extra-ops.js.map +1 -1
  174. package/dist/runtime/admin-ops.d.ts.map +1 -1
  175. package/dist/runtime/admin-ops.js +10 -3
  176. package/dist/runtime/admin-ops.js.map +1 -1
  177. package/dist/runtime/capture-ops.d.ts.map +1 -1
  178. package/dist/runtime/capture-ops.js +20 -2
  179. package/dist/runtime/capture-ops.js.map +1 -1
  180. package/dist/runtime/cognee-sync-ops.d.ts +12 -0
  181. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
  182. package/dist/runtime/cognee-sync-ops.js +55 -0
  183. package/dist/runtime/cognee-sync-ops.js.map +1 -0
  184. package/dist/runtime/core-ops.d.ts +8 -6
  185. package/dist/runtime/core-ops.d.ts.map +1 -1
  186. package/dist/runtime/core-ops.js +226 -9
  187. package/dist/runtime/core-ops.js.map +1 -1
  188. package/dist/runtime/curator-extra-ops.d.ts +2 -2
  189. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  190. package/dist/runtime/curator-extra-ops.js +15 -3
  191. package/dist/runtime/curator-extra-ops.js.map +1 -1
  192. package/dist/runtime/domain-ops.js +2 -2
  193. package/dist/runtime/domain-ops.js.map +1 -1
  194. package/dist/runtime/grading-ops.d.ts.map +1 -1
  195. package/dist/runtime/grading-ops.js.map +1 -1
  196. package/dist/runtime/intake-ops.d.ts +14 -0
  197. package/dist/runtime/intake-ops.d.ts.map +1 -0
  198. package/dist/runtime/intake-ops.js +110 -0
  199. package/dist/runtime/intake-ops.js.map +1 -0
  200. package/dist/runtime/loop-ops.d.ts +5 -4
  201. package/dist/runtime/loop-ops.d.ts.map +1 -1
  202. package/dist/runtime/loop-ops.js +84 -12
  203. package/dist/runtime/loop-ops.js.map +1 -1
  204. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -1
  205. package/dist/runtime/memory-cross-project-ops.js.map +1 -1
  206. package/dist/runtime/memory-extra-ops.js +5 -5
  207. package/dist/runtime/memory-extra-ops.js.map +1 -1
  208. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  209. package/dist/runtime/orchestrate-ops.js +8 -2
  210. package/dist/runtime/orchestrate-ops.js.map +1 -1
  211. package/dist/runtime/planning-extra-ops.d.ts +13 -5
  212. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  213. package/dist/runtime/planning-extra-ops.js +381 -18
  214. package/dist/runtime/planning-extra-ops.js.map +1 -1
  215. package/dist/runtime/playbook-ops.d.ts +14 -0
  216. package/dist/runtime/playbook-ops.d.ts.map +1 -0
  217. package/dist/runtime/playbook-ops.js +141 -0
  218. package/dist/runtime/playbook-ops.js.map +1 -0
  219. package/dist/runtime/project-ops.d.ts.map +1 -1
  220. package/dist/runtime/project-ops.js +7 -2
  221. package/dist/runtime/project-ops.js.map +1 -1
  222. package/dist/runtime/runtime.d.ts.map +1 -1
  223. package/dist/runtime/runtime.js +28 -9
  224. package/dist/runtime/runtime.js.map +1 -1
  225. package/dist/runtime/types.d.ts +8 -0
  226. package/dist/runtime/types.d.ts.map +1 -1
  227. package/dist/runtime/vault-extra-ops.d.ts +4 -2
  228. package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
  229. package/dist/runtime/vault-extra-ops.js +383 -4
  230. package/dist/runtime/vault-extra-ops.js.map +1 -1
  231. package/dist/vault/playbook.d.ts +34 -0
  232. package/dist/vault/playbook.d.ts.map +1 -0
  233. package/dist/vault/playbook.js +60 -0
  234. package/dist/vault/playbook.js.map +1 -0
  235. package/dist/vault/vault.d.ts +52 -32
  236. package/dist/vault/vault.d.ts.map +1 -1
  237. package/dist/vault/vault.js +300 -181
  238. package/dist/vault/vault.js.map +1 -1
  239. package/package.json +9 -3
  240. package/src/__tests__/admin-extra-ops.test.ts +62 -15
  241. package/src/__tests__/admin-ops.test.ts +2 -2
  242. package/src/__tests__/brain.test.ts +3 -3
  243. package/src/__tests__/cognee-integration.test.ts +80 -0
  244. package/src/__tests__/cognee-sync-manager.test.ts +103 -0
  245. package/src/__tests__/core-ops.test.ts +36 -4
  246. package/src/__tests__/curator-extra-ops.test.ts +24 -2
  247. package/src/__tests__/errors.test.ts +388 -0
  248. package/src/__tests__/grading-ops.test.ts +28 -7
  249. package/src/__tests__/intake-pipeline.test.ts +162 -0
  250. package/src/__tests__/loop-ops.test.ts +74 -3
  251. package/src/__tests__/memory-cross-project-ops.test.ts +3 -1
  252. package/src/__tests__/orchestrate-ops.test.ts +8 -3
  253. package/src/__tests__/persistence.test.ts +291 -0
  254. package/src/__tests__/planner.test.ts +99 -21
  255. package/src/__tests__/planning-extra-ops.test.ts +168 -10
  256. package/src/__tests__/playbook-registry.test.ts +326 -0
  257. package/src/__tests__/playbook-seeder.test.ts +163 -0
  258. package/src/__tests__/playbook.test.ts +389 -0
  259. package/src/__tests__/postgres-provider.test.ts +58 -0
  260. package/src/__tests__/project-ops.test.ts +18 -4
  261. package/src/__tests__/template-manager.test.ts +222 -0
  262. package/src/__tests__/vault-extra-ops.test.ts +82 -7
  263. package/src/__tests__/vault.test.ts +184 -0
  264. package/src/brain/brain.ts +71 -9
  265. package/src/brain/intelligence.ts +258 -307
  266. package/src/brain/types.ts +2 -2
  267. package/src/cognee/client.ts +18 -0
  268. package/src/cognee/sync-manager.ts +389 -0
  269. package/src/control/identity-manager.ts +77 -75
  270. package/src/control/intent-router.ts +55 -57
  271. package/src/curator/curator.ts +199 -139
  272. package/src/errors/classify.ts +102 -0
  273. package/src/errors/index.ts +5 -0
  274. package/src/errors/retry.ts +132 -0
  275. package/src/errors/types.ts +81 -0
  276. package/src/governance/governance.ts +90 -107
  277. package/src/index.ts +116 -3
  278. package/src/intake/content-classifier.ts +146 -0
  279. package/src/intake/dedup-gate.ts +92 -0
  280. package/src/intake/intake-pipeline.ts +503 -0
  281. package/src/intake/types.ts +69 -0
  282. package/src/intelligence/loader.ts +1 -1
  283. package/src/intelligence/types.ts +3 -1
  284. package/src/loop/loop-manager.ts +325 -7
  285. package/src/loop/types.ts +72 -1
  286. package/src/persistence/index.ts +9 -0
  287. package/src/persistence/postgres-provider.ts +157 -0
  288. package/src/persistence/sqlite-provider.ts +115 -0
  289. package/src/persistence/types.ts +74 -0
  290. package/src/planning/gap-analysis.ts +286 -17
  291. package/src/planning/gap-types.ts +4 -1
  292. package/src/planning/planner.ts +828 -55
  293. package/src/playbooks/generic/brainstorming.ts +110 -0
  294. package/src/playbooks/generic/code-review.ts +181 -0
  295. package/src/playbooks/generic/subagent-execution.ts +74 -0
  296. package/src/playbooks/generic/systematic-debugging.ts +92 -0
  297. package/src/playbooks/generic/tdd.ts +75 -0
  298. package/src/playbooks/generic/verification.ts +79 -0
  299. package/src/playbooks/index.ts +27 -0
  300. package/src/playbooks/playbook-registry.ts +284 -0
  301. package/src/playbooks/playbook-seeder.ts +119 -0
  302. package/src/playbooks/playbook-types.ts +162 -0
  303. package/src/project/project-registry.ts +81 -74
  304. package/src/prompts/index.ts +3 -0
  305. package/src/prompts/parser.ts +59 -0
  306. package/src/prompts/template-manager.ts +77 -0
  307. package/src/prompts/types.ts +28 -0
  308. package/src/runtime/admin-extra-ops.ts +391 -13
  309. package/src/runtime/admin-ops.ts +17 -6
  310. package/src/runtime/capture-ops.ts +25 -6
  311. package/src/runtime/cognee-sync-ops.ts +63 -0
  312. package/src/runtime/core-ops.ts +258 -8
  313. package/src/runtime/curator-extra-ops.ts +17 -3
  314. package/src/runtime/domain-ops.ts +2 -2
  315. package/src/runtime/grading-ops.ts +11 -2
  316. package/src/runtime/intake-ops.ts +126 -0
  317. package/src/runtime/loop-ops.ts +96 -13
  318. package/src/runtime/memory-cross-project-ops.ts +1 -2
  319. package/src/runtime/memory-extra-ops.ts +5 -5
  320. package/src/runtime/orchestrate-ops.ts +8 -2
  321. package/src/runtime/planning-extra-ops.ts +414 -23
  322. package/src/runtime/playbook-ops.ts +169 -0
  323. package/src/runtime/project-ops.ts +9 -3
  324. package/src/runtime/runtime.ts +36 -10
  325. package/src/runtime/types.ts +8 -0
  326. package/src/runtime/vault-extra-ops.ts +425 -4
  327. package/src/vault/playbook.ts +87 -0
  328. package/src/vault/vault.ts +419 -235
@@ -1,5 +1,7 @@
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';
4
+ import type { PersistenceProvider } from '../persistence/types.js';
3
5
  import {
4
6
  tokenize,
5
7
  calculateTfIdf,
@@ -49,9 +51,13 @@ const DEFAULT_TAG_ALIASES: Array<[string, string]> = [
49
51
 
50
52
  export class Curator {
51
53
  private vault: Vault;
54
+ private cognee: CogneeClient | undefined;
55
+ private provider: PersistenceProvider;
52
56
 
53
- constructor(vault: Vault) {
57
+ constructor(vault: Vault, cognee?: CogneeClient) {
54
58
  this.vault = vault;
59
+ this.cognee = cognee;
60
+ this.provider = vault.getProvider();
55
61
  this.initializeTables();
56
62
  this.seedDefaultAliases();
57
63
  }
@@ -59,8 +65,7 @@ export class Curator {
59
65
  // ─── Schema ─────────────────────────────────────────────────────
60
66
 
61
67
  private initializeTables(): void {
62
- const db = this.vault.getDb();
63
- db.exec(`
68
+ this.provider.execSql(`
64
69
  CREATE TABLE IF NOT EXISTS curator_entry_state (
65
70
  entry_id TEXT PRIMARY KEY,
66
71
  status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'stale', 'archived')),
@@ -112,41 +117,39 @@ export class Curator {
112
117
  resolved_at INTEGER,
113
118
  UNIQUE(pattern_id, antipattern_id)
114
119
  );
120
+ CREATE INDEX IF NOT EXISTS idx_curator_state_status ON curator_entry_state(status);
121
+ CREATE INDEX IF NOT EXISTS idx_curator_changelog_entry ON curator_changelog(entry_id);
115
122
  `);
116
123
  }
117
124
 
118
125
  private seedDefaultAliases(): void {
119
- const db = this.vault.getDb();
120
- const insertCanonical = db.prepare(
121
- 'INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)',
122
- );
123
- const insertAlias = db.prepare(
124
- 'INSERT OR IGNORE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)',
125
- );
126
- const tx = db.transaction(() => {
126
+ this.provider.transaction(() => {
127
127
  const canonicals = new Set(DEFAULT_TAG_ALIASES.map(([, c]) => c));
128
128
  for (const tag of canonicals) {
129
- insertCanonical.run(tag);
129
+ this.provider.run('INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)', [tag]);
130
130
  }
131
131
  for (const [alias, canonical] of DEFAULT_TAG_ALIASES) {
132
- insertAlias.run(alias, canonical);
132
+ this.provider.run(
133
+ 'INSERT OR IGNORE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)',
134
+ [alias, canonical],
135
+ );
133
136
  }
134
137
  });
135
- tx();
136
138
  }
137
139
 
138
140
  // ─── Status ─────────────────────────────────────────────────────
139
141
 
140
142
  getStatus(): CuratorStatus {
141
- const db = this.vault.getDb();
142
143
  const tableCount = (table: string): number =>
143
- (db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get() as { count: number }).count;
144
+ (
145
+ this.provider.get<{ count: number }>(`SELECT COUNT(*) as count FROM ${table}`) ?? {
146
+ count: 0,
147
+ }
148
+ ).count;
144
149
 
145
- const lastGroomed = db
146
- .prepare(
147
- 'SELECT MAX(last_groomed_at) as ts FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
148
- )
149
- .get() as { ts: number | null };
150
+ const lastGroomed = this.provider.get<{ ts: number | null }>(
151
+ 'SELECT MAX(last_groomed_at) as ts FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
152
+ ) ?? { ts: null };
150
153
 
151
154
  return {
152
155
  initialized: true,
@@ -164,11 +167,11 @@ export class Curator {
164
167
  // ─── Tag Normalization ──────────────────────────────────────────
165
168
 
166
169
  normalizeTag(tag: string): TagNormalizationResult {
167
- const db = this.vault.getDb();
168
170
  const lower = tag.toLowerCase().trim();
169
- const row = db.prepare('SELECT canonical FROM curator_tag_alias WHERE alias = ?').get(lower) as
170
- | { canonical: string }
171
- | undefined;
171
+ const row = this.provider.get<{ canonical: string }>(
172
+ 'SELECT canonical FROM curator_tag_alias WHERE alias = ?',
173
+ [lower],
174
+ );
172
175
  if (row) {
173
176
  return { original: tag, normalized: row.canonical, wasAliased: true };
174
177
  }
@@ -192,11 +195,10 @@ export class Curator {
192
195
 
193
196
  if (changed) {
194
197
  const dedupedTags = [...new Set(normalizedTags)];
195
- const db = this.vault.getDb();
196
- db.prepare('UPDATE entries SET tags = ?, updated_at = unixepoch() WHERE id = ?').run(
198
+ this.provider.run('UPDATE entries SET tags = ?, updated_at = unixepoch() WHERE id = ?', [
197
199
  JSON.stringify(dedupedTags),
198
200
  entryId,
199
- );
201
+ ]);
200
202
  this.logChange(
201
203
  'normalize_tags',
202
204
  entryId,
@@ -210,26 +212,28 @@ export class Curator {
210
212
  }
211
213
 
212
214
  addTagAlias(alias: string, canonical: string): void {
213
- const db = this.vault.getDb();
214
215
  const lower = alias.toLowerCase().trim();
215
216
  const canonicalLower = canonical.toLowerCase().trim();
216
- db.prepare('INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)').run(canonicalLower);
217
- db.prepare('INSERT OR REPLACE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)').run(
217
+ this.provider.run('INSERT OR IGNORE INTO curator_tag_canonical (tag) VALUES (?)', [
218
+ canonicalLower,
219
+ ]);
220
+ this.provider.run('INSERT OR REPLACE INTO curator_tag_alias (alias, canonical) VALUES (?, ?)', [
218
221
  lower,
219
222
  canonicalLower,
220
- );
223
+ ]);
221
224
  }
222
225
 
223
226
  getCanonicalTags(): CanonicalTag[] {
224
- const db = this.vault.getDb();
225
- const rows = db
226
- .prepare(
227
- `SELECT c.tag, c.description,
228
- (SELECT COUNT(*) FROM curator_tag_alias a WHERE a.canonical = c.tag) as alias_count
229
- FROM curator_tag_canonical c
230
- ORDER BY c.tag`,
231
- )
232
- .all() as Array<{ tag: string; description: string | null; alias_count: number }>;
227
+ const rows = this.provider.all<{
228
+ tag: string;
229
+ description: string | null;
230
+ alias_count: number;
231
+ }>(
232
+ `SELECT c.tag, c.description,
233
+ (SELECT COUNT(*) FROM curator_tag_alias a WHERE a.canonical = c.tag) as alias_count
234
+ FROM curator_tag_canonical c
235
+ ORDER BY c.tag`,
236
+ );
233
237
 
234
238
  return rows.map((row) => ({
235
239
  tag: row.tag,
@@ -240,11 +244,11 @@ export class Curator {
240
244
  }
241
245
 
242
246
  private countTagUsage(tag: string): number {
243
- const db = this.vault.getDb();
244
- const row = db
245
- .prepare('SELECT COUNT(*) as count FROM entries WHERE tags LIKE ?')
246
- .get(`%"${tag}"%`) as { count: number };
247
- return row.count;
247
+ const row = this.provider.get<{ count: number }>(
248
+ 'SELECT COUNT(*) as count FROM entries WHERE tags LIKE ?',
249
+ [`%"${tag}"%`],
250
+ );
251
+ return row?.count ?? 0;
248
252
  }
249
253
 
250
254
  // ─── Duplicate Detection ────────────────────────────────────────
@@ -312,14 +316,8 @@ export class Curator {
312
316
  if (antipatterns.length === 0 || patterns.length === 0) return [];
313
317
 
314
318
  const vocabulary = this.buildVocabulary(entries);
315
- const db = this.vault.getDb();
316
319
  const detected: Contradiction[] = [];
317
320
 
318
- const insertStmt = db.prepare(
319
- `INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity)
320
- VALUES (?, ?, ?)`,
321
- );
322
-
323
321
  for (const ap of antipatterns) {
324
322
  // Stage 1: FTS5 candidate retrieval (fall back to all patterns if FTS returns empty)
325
323
  let candidates: IntelligenceEntry[];
@@ -340,14 +338,16 @@ export class Curator {
340
338
  const similarity = cosineSimilarity(apVec, pVec);
341
339
 
342
340
  if (similarity >= effectiveThreshold) {
343
- const result = insertStmt.run(pattern.id, ap.id, similarity);
341
+ const result = this.provider.run(
342
+ 'INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity) VALUES (?, ?, ?)',
343
+ [pattern.id, ap.id, similarity],
344
+ );
344
345
  if (result.changes > 0) {
345
- const row = db
346
- .prepare(
347
- 'SELECT * FROM curator_contradictions WHERE pattern_id = ? AND antipattern_id = ?',
348
- )
349
- .get(pattern.id, ap.id) as Record<string, unknown>;
350
- detected.push(this.rowToContradiction(row));
346
+ const row = this.provider.get<Record<string, unknown>>(
347
+ 'SELECT * FROM curator_contradictions WHERE pattern_id = ? AND antipattern_id = ?',
348
+ [pattern.id, ap.id],
349
+ );
350
+ if (row) detected.push(this.rowToContradiction(row));
351
351
  }
352
352
  }
353
353
  }
@@ -357,27 +357,100 @@ export class Curator {
357
357
  }
358
358
 
359
359
  getContradictions(status?: ContradictionStatus): Contradiction[] {
360
- const db = this.vault.getDb();
361
360
  const query = status
362
361
  ? 'SELECT * FROM curator_contradictions WHERE status = ? ORDER BY similarity DESC'
363
362
  : 'SELECT * FROM curator_contradictions ORDER BY similarity DESC';
364
- const rows = (status ? db.prepare(query).all(status) : db.prepare(query).all()) as Array<
365
- Record<string, unknown>
366
- >;
363
+ const rows = this.provider.all<Record<string, unknown>>(query, status ? [status] : undefined);
367
364
  return rows.map((r) => this.rowToContradiction(r));
368
365
  }
369
366
 
370
367
  resolveContradiction(id: number, resolution: 'resolved' | 'dismissed'): Contradiction | null {
371
- const db = this.vault.getDb();
372
- db.prepare(
368
+ this.provider.run(
373
369
  'UPDATE curator_contradictions SET status = ?, resolved_at = unixepoch() WHERE id = ?',
374
- ).run(resolution, id);
375
- const row = db.prepare('SELECT * FROM curator_contradictions WHERE id = ?').get(id) as
376
- | Record<string, unknown>
377
- | undefined;
370
+ [resolution, id],
371
+ );
372
+ const row = this.provider.get<Record<string, unknown>>(
373
+ 'SELECT * FROM curator_contradictions WHERE id = ?',
374
+ [id],
375
+ );
378
376
  return row ? this.rowToContradiction(row) : null;
379
377
  }
380
378
 
379
+ async detectContradictionsHybrid(threshold?: number): Promise<{
380
+ contradictions: Contradiction[];
381
+ cogneeAvailable: boolean;
382
+ method: 'hybrid' | 'tfidf-only';
383
+ }> {
384
+ const effectiveThreshold = threshold ?? DEFAULT_CONTRADICTION_THRESHOLD;
385
+ const entries = this.vault.list({ limit: 100000 });
386
+ const antipatterns = entries.filter((e) => e.type === 'anti-pattern');
387
+ const patterns = entries.filter((e) => e.type === 'pattern');
388
+
389
+ if (antipatterns.length === 0 || patterns.length === 0) {
390
+ return { contradictions: [], cogneeAvailable: false, method: 'tfidf-only' };
391
+ }
392
+
393
+ const vocabulary = this.buildVocabulary(entries);
394
+ const detected: Contradiction[] = [];
395
+
396
+ const cogneeAvailable = this.cognee?.isAvailable ?? false;
397
+
398
+ for (const ap of antipatterns) {
399
+ let candidates: IntelligenceEntry[];
400
+ try {
401
+ const searchResults = this.vault.search(ap.title, { type: 'pattern', limit: 20 });
402
+ candidates = searchResults.length > 0 ? searchResults.map((r) => r.entry) : patterns;
403
+ } catch {
404
+ candidates = patterns;
405
+ }
406
+
407
+ const apText = [ap.title, ap.description, ap.context ?? ''].join(' ');
408
+ const apVec = calculateTfIdf(tokenize(apText), vocabulary);
409
+
410
+ for (const pattern of candidates) {
411
+ const pText = [pattern.title, pattern.description, pattern.context ?? ''].join(' ');
412
+ const pVec = calculateTfIdf(tokenize(pText), vocabulary);
413
+ const tfidfScore = cosineSimilarity(apVec, pVec);
414
+
415
+ let finalScore = tfidfScore;
416
+ if (cogneeAvailable && this.cognee) {
417
+ try {
418
+ const cogneeResults = await this.cognee.search(`${ap.title} ${pattern.title}`, {
419
+ limit: 5,
420
+ });
421
+ const cogneeScore =
422
+ cogneeResults.length > 0
423
+ ? cogneeResults.reduce((sum, r) => sum + r.score, 0) / cogneeResults.length
424
+ : 0;
425
+ finalScore = 0.6 * tfidfScore + 0.4 * cogneeScore;
426
+ } catch {
427
+ finalScore = tfidfScore;
428
+ }
429
+ }
430
+
431
+ if (finalScore >= effectiveThreshold) {
432
+ const result = this.provider.run(
433
+ 'INSERT OR IGNORE INTO curator_contradictions (pattern_id, antipattern_id, similarity) VALUES (?, ?, ?)',
434
+ [pattern.id, ap.id, finalScore],
435
+ );
436
+ if (result.changes > 0) {
437
+ const row = this.provider.get<Record<string, unknown>>(
438
+ 'SELECT * FROM curator_contradictions WHERE pattern_id = ? AND antipattern_id = ?',
439
+ [pattern.id, ap.id],
440
+ );
441
+ if (row) detected.push(this.rowToContradiction(row));
442
+ }
443
+ }
444
+ }
445
+ }
446
+
447
+ return {
448
+ contradictions: detected,
449
+ cogneeAvailable,
450
+ method: cogneeAvailable ? 'hybrid' : 'tfidf-only',
451
+ };
452
+ }
453
+
381
454
  // ─── Grooming ───────────────────────────────────────────────────
382
455
 
383
456
  groomEntry(entryId: string): GroomResult | null {
@@ -387,21 +460,22 @@ export class Curator {
387
460
  const tagsNormalized = this.normalizeTags(entryId);
388
461
 
389
462
  // Check staleness based on entry's updated_at timestamp
390
- const db = this.vault.getDb();
391
- const row = db.prepare('SELECT updated_at FROM entries WHERE id = ?').get(entryId) as
392
- | { updated_at: number }
393
- | undefined;
463
+ const row = this.provider.get<{ updated_at: number }>(
464
+ 'SELECT updated_at FROM entries WHERE id = ?',
465
+ [entryId],
466
+ );
394
467
  const now = Math.floor(Date.now() / 1000);
395
468
  const stale = row ? now - row.updated_at > DEFAULT_STALE_DAYS * 86400 : false;
396
469
 
397
470
  const status = stale ? 'stale' : 'active';
398
471
 
399
472
  // Upsert entry state
400
- db.prepare(
473
+ this.provider.run(
401
474
  `INSERT INTO curator_entry_state (entry_id, status, last_groomed_at)
402
475
  VALUES (?, ?, unixepoch())
403
476
  ON CONFLICT(entry_id) DO UPDATE SET status = excluded.status, last_groomed_at = unixepoch()`,
404
- ).run(entryId, status);
477
+ [entryId, status],
478
+ );
405
479
 
406
480
  this.logChange('groom', entryId, null, `status=${status}`, 'Routine grooming');
407
481
 
@@ -450,12 +524,12 @@ export class Curator {
450
524
  const duplicates = this.detectDuplicates(undefined, duplicateThreshold);
451
525
 
452
526
  // Detect stale entries
453
- const db = this.vault.getDb();
454
527
  const now = Math.floor(Date.now() / 1000);
455
528
  const staleThreshold = now - staleDaysThreshold * 86400;
456
- const staleRows = db
457
- .prepare('SELECT id FROM entries WHERE updated_at < ?')
458
- .all(staleThreshold) as Array<{ id: string }>;
529
+ const staleRows = this.provider.all<{ id: string }>(
530
+ 'SELECT id FROM entries WHERE updated_at < ?',
531
+ [staleThreshold],
532
+ );
459
533
  const staleEntries = staleRows.map((r) => r.id);
460
534
 
461
535
  // Detect contradictions
@@ -466,11 +540,12 @@ export class Curator {
466
540
  if (!dryRun) {
467
541
  // Archive stale entries
468
542
  for (const entryId of staleEntries) {
469
- db.prepare(
543
+ this.provider.run(
470
544
  `INSERT INTO curator_entry_state (entry_id, status, last_groomed_at)
471
545
  VALUES (?, 'archived', unixepoch())
472
546
  ON CONFLICT(entry_id) DO UPDATE SET status = 'archived', last_groomed_at = unixepoch()`,
473
- ).run(entryId);
547
+ [entryId],
548
+ );
474
549
  this.logChange(
475
550
  'archive',
476
551
  entryId,
@@ -514,12 +589,10 @@ export class Curator {
514
589
  // ─── Changelog ──────────────────────────────────────────────────
515
590
 
516
591
  getEntryHistory(entryId: string, limit?: number): ChangelogEntry[] {
517
- const db = this.vault.getDb();
518
- const rows = db
519
- .prepare(
520
- 'SELECT * FROM curator_changelog WHERE entry_id = ? ORDER BY created_at DESC, id DESC LIMIT ?',
521
- )
522
- .all(entryId, limit ?? 50) as Array<Record<string, unknown>>;
592
+ const rows = this.provider.all<Record<string, unknown>>(
593
+ 'SELECT * FROM curator_changelog WHERE entry_id = ? ORDER BY created_at DESC, id DESC LIMIT ?',
594
+ [entryId, limit ?? 50],
595
+ );
523
596
  return rows.map((r) => this.rowToChangelog(r));
524
597
  }
525
598
 
@@ -566,13 +639,13 @@ export class Curator {
566
639
  coverageScore = Math.max(0, coverageScore);
567
640
 
568
641
  // Freshness: penalize stale entries
569
- const db = this.vault.getDb();
570
642
  const now = Math.floor(Date.now() / 1000);
571
643
  const staleThreshold = now - DEFAULT_STALE_DAYS * 86400;
572
644
  const staleCount = (
573
- db
574
- .prepare('SELECT COUNT(*) as count FROM entries WHERE updated_at < ?')
575
- .get(staleThreshold) as { count: number }
645
+ this.provider.get<{ count: number }>(
646
+ 'SELECT COUNT(*) as count FROM entries WHERE updated_at < ?',
647
+ [staleThreshold],
648
+ ) ?? { count: 0 }
576
649
  ).count;
577
650
  const staleRatio = staleCount / entries.length;
578
651
  const freshnessScore = 1 - staleRatio;
@@ -616,11 +689,9 @@ export class Curator {
616
689
 
617
690
  // Penalize ungroomed entries
618
691
  const groomedCount = (
619
- db
620
- .prepare(
621
- 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
622
- )
623
- .get() as { count: number }
692
+ this.provider.get<{ count: number }>(
693
+ 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
694
+ ) ?? { count: 0 }
624
695
  ).count;
625
696
  if (groomedCount < entries.length) {
626
697
  const ungroomed = entries.length - groomedCount;
@@ -657,19 +728,15 @@ export class Curator {
657
728
  const entry = this.vault.get(entryId);
658
729
  if (!entry) return { recorded: false, historyId: -1 };
659
730
 
660
- const db = this.vault.getDb();
661
- const result = db
662
- .prepare(
663
- 'INSERT INTO curator_entry_history (entry_id, snapshot, changed_by, change_reason, created_at) VALUES (?, ?, ?, ?, unixepoch())',
664
- )
665
- .run(entryId, JSON.stringify(entry), changedBy ?? 'system', changeReason ?? null);
731
+ const result = this.provider.run(
732
+ 'INSERT INTO curator_entry_history (entry_id, snapshot, changed_by, change_reason, created_at) VALUES (?, ?, ?, ?, unixepoch())',
733
+ [entryId, JSON.stringify(entry), changedBy ?? 'system', changeReason ?? null],
734
+ );
666
735
 
667
736
  return { recorded: true, historyId: Number(result.lastInsertRowid) };
668
737
  }
669
738
 
670
- getVersionHistory(
671
- entryId: string,
672
- ): Array<{
739
+ getVersionHistory(entryId: string): Array<{
673
740
  historyId: number;
674
741
  entryId: string;
675
742
  snapshot: IntelligenceEntry;
@@ -677,12 +744,10 @@ export class Curator {
677
744
  changeReason: string | null;
678
745
  createdAt: number;
679
746
  }> {
680
- const db = this.vault.getDb();
681
- const rows = db
682
- .prepare(
683
- 'SELECT * FROM curator_entry_history WHERE entry_id = ? ORDER BY created_at ASC, id ASC',
684
- )
685
- .all(entryId) as Array<Record<string, unknown>>;
747
+ const rows = this.provider.all<Record<string, unknown>>(
748
+ 'SELECT * FROM curator_entry_history WHERE entry_id = ? ORDER BY created_at ASC, id ASC',
749
+ [entryId],
750
+ );
686
751
 
687
752
  return rows.map((row) => ({
688
753
  historyId: row.id as number,
@@ -704,17 +769,14 @@ export class Curator {
704
769
  freshEntries: number;
705
770
  avgDaysSinceGroom: number;
706
771
  } {
707
- const db = this.vault.getDb();
708
772
  const totalEntries = (
709
- db.prepare('SELECT COUNT(*) as count FROM entries').get() as { count: number }
773
+ this.provider.get<{ count: number }>('SELECT COUNT(*) as count FROM entries') ?? { count: 0 }
710
774
  ).count;
711
775
 
712
776
  const groomedEntries = (
713
- db
714
- .prepare(
715
- 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
716
- )
717
- .get() as { count: number }
777
+ this.provider.get<{ count: number }>(
778
+ 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
779
+ ) ?? { count: 0 }
718
780
  ).count;
719
781
 
720
782
  const ungroomedEntries = totalEntries - groomedEntries;
@@ -724,28 +786,25 @@ export class Curator {
724
786
  const freshThreshold = now - 7 * 86400;
725
787
 
726
788
  const staleEntries = (
727
- db
728
- .prepare(
729
- 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at < ?',
730
- )
731
- .get(staleThreshold) as { count: number }
789
+ this.provider.get<{ count: number }>(
790
+ 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at < ?',
791
+ [staleThreshold],
792
+ ) ?? { count: 0 }
732
793
  ).count;
733
794
 
734
795
  const freshEntries = (
735
- db
736
- .prepare(
737
- 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at >= ?',
738
- )
739
- .get(freshThreshold) as { count: number }
796
+ this.provider.get<{ count: number }>(
797
+ 'SELECT COUNT(*) as count FROM curator_entry_state WHERE last_groomed_at IS NOT NULL AND last_groomed_at >= ?',
798
+ [freshThreshold],
799
+ ) ?? { count: 0 }
740
800
  ).count;
741
801
 
742
802
  let avgDaysSinceGroom = 0;
743
803
  if (groomedEntries > 0) {
744
- const sumRow = db
745
- .prepare(
746
- 'SELECT SUM(? - last_groomed_at) as total FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
747
- )
748
- .get(now) as { total: number | null };
804
+ const sumRow = this.provider.get<{ total: number | null }>(
805
+ 'SELECT SUM(? - last_groomed_at) as total FROM curator_entry_state WHERE last_groomed_at IS NOT NULL',
806
+ [now],
807
+ ) ?? { total: 0 };
749
808
  const totalSeconds = sumRow.total ?? 0;
750
809
  avgDaysSinceGroom = Math.round((totalSeconds / groomedEntries / 86400) * 100) / 100;
751
810
  }
@@ -762,9 +821,10 @@ export class Curator {
762
821
 
763
822
  // ─── Metadata Enrichment ──────────────────────────────────────
764
823
 
765
- enrichMetadata(
766
- entryId: string,
767
- ): { enriched: boolean; changes: Array<{ field: string; before: string; after: string }> } {
824
+ enrichMetadata(entryId: string): {
825
+ enriched: boolean;
826
+ changes: Array<{ field: string; before: string; after: string }>;
827
+ } {
768
828
  const entry = this.vault.get(entryId);
769
829
  if (!entry) return { enriched: false, changes: [] };
770
830
 
@@ -880,10 +940,10 @@ export class Curator {
880
940
  afterValue: string | null,
881
941
  reason: string,
882
942
  ): void {
883
- const db = this.vault.getDb();
884
- db.prepare(
943
+ this.provider.run(
885
944
  'INSERT INTO curator_changelog (action, entry_id, before_value, after_value, reason) VALUES (?, ?, ?, ?, ?)',
886
- ).run(action, entryId, beforeValue, afterValue, reason);
945
+ [action, entryId, beforeValue, afterValue, reason],
946
+ );
887
947
  }
888
948
 
889
949
  private rowToContradiction(row: Record<string, unknown>): Contradiction {
@@ -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';