@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
@@ -269,15 +269,17 @@ describe('createLoopOps', () => {
269
269
  return op;
270
270
  }
271
271
 
272
- it('exports 7 loop ops', () => {
273
- expect(ops).toHaveLength(7);
272
+ it('exports 9 loop ops', () => {
273
+ expect(ops).toHaveLength(9);
274
274
  const names = ops.map((o) => o.name).sort();
275
275
  expect(names).toEqual([
276
+ 'loop_anomaly_check',
276
277
  'loop_cancel',
277
278
  'loop_complete',
278
279
  'loop_history',
279
280
  'loop_is_active',
280
281
  'loop_iterate',
282
+ 'loop_iterate_gate',
281
283
  'loop_start',
282
284
  'loop_status',
283
285
  ]);
@@ -384,8 +386,77 @@ describe('createLoopOps', () => {
384
386
  expect(result.active).toBe(false);
385
387
  });
386
388
 
389
+ it('loop_iterate_gate accepts durationMs parameter', async () => {
390
+ await findOp('loop_start').handler({ mode: 'token-migration', prompt: 'Test with timing' });
391
+
392
+ const result = (await findOp('loop_iterate_gate').handler({
393
+ lastOutput: 'Some LLM output that does not complete',
394
+ durationMs: 15000,
395
+ })) as Record<string, unknown>;
396
+
397
+ expect(result.decision).toBeDefined();
398
+ // Should not have anomaly warning for normal duration
399
+ expect(result.anomalyWarning).toBeUndefined();
400
+ });
401
+
402
+ it('loop_iterate_gate reports anomaly for fast + low-score iteration', async () => {
403
+ await findOp('loop_start').handler({ mode: 'token-migration', prompt: 'Test anomaly' });
404
+
405
+ // First iterate to set a low score baseline, then gate with fast duration
406
+ await findOp('loop_iterate').handler({ passed: false, validationScore: 20 });
407
+
408
+ const result = (await findOp('loop_iterate_gate').handler({
409
+ lastOutput: 'Quick response with low quality',
410
+ durationMs: 500, // Very fast — below 5000ms threshold for token-migration
411
+ })) as Record<string, unknown>;
412
+
413
+ // The gate still works, anomaly warning may or may not appear depending on score
414
+ expect(result.decision).toBeDefined();
415
+ });
416
+
417
+ it('loop_anomaly_check returns inactive when no loop', async () => {
418
+ const result = (await findOp('loop_anomaly_check').handler({})) as Record<string, unknown>;
419
+ expect(result.active).toBe(false);
420
+ expect(result.anomalies).toEqual([]);
421
+ expect(result.summary).toBe('No active loop');
422
+ });
423
+
424
+ it('loop_anomaly_check detects consecutive failures', async () => {
425
+ await findOp('loop_start').handler({ mode: 'custom', prompt: 'Failing loop' });
426
+ await findOp('loop_iterate').handler({ passed: false, validationScore: 20 });
427
+ await findOp('loop_iterate').handler({ passed: false, validationScore: 25 });
428
+ await findOp('loop_iterate').handler({ passed: false, validationScore: 30 });
429
+
430
+ const result = (await findOp('loop_anomaly_check').handler({})) as {
431
+ active: boolean;
432
+ hasAnomalies: boolean;
433
+ anomalies: string[];
434
+ totalIterations: number;
435
+ };
436
+
437
+ expect(result.active).toBe(true);
438
+ expect(result.totalIterations).toBe(3);
439
+ expect(result.hasAnomalies).toBe(true);
440
+ expect(result.anomalies.some((a) => a.includes('consecutive failing'))).toBe(true);
441
+ });
442
+
443
+ it('loop_anomaly_check returns no anomalies for passing loop', async () => {
444
+ await findOp('loop_start').handler({ mode: 'custom', prompt: 'Good loop' });
445
+ await findOp('loop_iterate').handler({ passed: true, validationScore: 90 });
446
+
447
+ const result = (await findOp('loop_anomaly_check').handler({})) as {
448
+ active: boolean;
449
+ hasAnomalies: boolean;
450
+ anomalies: string[];
451
+ };
452
+
453
+ expect(result.active).toBe(true);
454
+ expect(result.hasAnomalies).toBe(false);
455
+ expect(result.anomalies).toEqual([]);
456
+ });
457
+
387
458
  it('assigns correct auth levels', () => {
388
- const readOps = ['loop_status', 'loop_history', 'loop_is_active'];
459
+ const readOps = ['loop_status', 'loop_history', 'loop_is_active', 'loop_anomaly_check'];
389
460
  const writeOps = ['loop_start', 'loop_iterate', 'loop_cancel', 'loop_complete'];
390
461
 
391
462
  for (const name of readOps) {
@@ -125,7 +125,9 @@ describe('Memory Cross-Project Ops', () => {
125
125
 
126
126
  const project = runtime.projectRegistry.getByPath('/test/project-b');
127
127
  expect(project!.metadata.custom).toBe('value');
128
- expect((project!.metadata.memoryConfig as Record<string, unknown>).crossProjectEnabled).toBe(false);
128
+ expect((project!.metadata.memoryConfig as Record<string, unknown>).crossProjectEnabled).toBe(
129
+ false,
130
+ );
129
131
  });
130
132
  });
131
133
 
@@ -64,7 +64,10 @@ describe('createOrchestrateOps', () => {
64
64
  objective: 'Build a new button component',
65
65
  scope: 'src/components/Button',
66
66
  domain: 'component',
67
- })) as { plan: { id: string; objective: string; decisions: string[] }; recommendations: unknown[] };
67
+ })) as {
68
+ plan: { id: string; objective: string; decisions: string[] };
69
+ recommendations: unknown[];
70
+ };
68
71
 
69
72
  expect(result.plan).toBeDefined();
70
73
  expect(result.plan.objective).toBe('Build a new button component');
@@ -130,7 +133,7 @@ describe('createOrchestrateOps', () => {
130
133
  });
131
134
 
132
135
  const op = findOp('orchestrate_execute');
133
- await expect(op.handler({ planId: plan.id })).rejects.toThrow(/must be 'approved'/);
136
+ await expect(op.handler({ planId: plan.id })).rejects.toThrow(/Invalid transition/);
134
137
  });
135
138
  });
136
139
 
@@ -138,13 +141,14 @@ describe('createOrchestrateOps', () => {
138
141
 
139
142
  describe('orchestrate_complete', () => {
140
143
  it('should complete plan, end session, and extract knowledge', async () => {
141
- // Full lifecycle: create -> approve -> execute -> complete
144
+ // Full lifecycle: create -> approve -> execute -> reconciling -> complete
142
145
  const plan = runtime.planner.create({
143
146
  objective: 'Full lifecycle test',
144
147
  scope: 'test',
145
148
  });
146
149
  runtime.planner.approve(plan.id);
147
150
  runtime.planner.startExecution(plan.id);
151
+ runtime.planner.startReconciliation(plan.id);
148
152
 
149
153
  // Start a brain session
150
154
  const session = runtime.brainIntelligence.lifecycle({
@@ -176,6 +180,7 @@ describe('createOrchestrateOps', () => {
176
180
  const plan = runtime.planner.create({ objective: 'Abandoned test', scope: 'test' });
177
181
  runtime.planner.approve(plan.id);
178
182
  runtime.planner.startExecution(plan.id);
183
+ runtime.planner.startReconciliation(plan.id);
179
184
 
180
185
  const session = runtime.brainIntelligence.lifecycle({
181
186
  action: 'start',
@@ -0,0 +1,291 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SQLitePersistenceProvider } from '../persistence/sqlite-provider.js';
3
+ import { Vault } from '../vault/vault.js';
4
+
5
+ // ─── SQLitePersistenceProvider ────────────────────────────────────────
6
+
7
+ describe('SQLitePersistenceProvider', () => {
8
+ it('execSql creates tables', () => {
9
+ const p = new SQLitePersistenceProvider(':memory:');
10
+ p.execSql('CREATE TABLE test (id TEXT PRIMARY KEY, val TEXT)');
11
+ p.run('INSERT INTO test (id, val) VALUES (?, ?)', ['a', 'hello']);
12
+ const row = p.get<{ id: string; val: string }>('SELECT * FROM test WHERE id = ?', ['a']);
13
+ expect(row).toEqual({ id: 'a', val: 'hello' });
14
+ p.close();
15
+ });
16
+
17
+ it('run with named params', () => {
18
+ const p = new SQLitePersistenceProvider(':memory:');
19
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY, name TEXT)');
20
+ const result = p.run('INSERT INTO t (id, name) VALUES (@id, @name)', {
21
+ id: '1',
22
+ name: 'test',
23
+ });
24
+ expect(result.changes).toBe(1);
25
+ p.close();
26
+ });
27
+
28
+ it('run with positional params', () => {
29
+ const p = new SQLitePersistenceProvider(':memory:');
30
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY)');
31
+ const result = p.run('INSERT INTO t (id) VALUES (?)', ['x']);
32
+ expect(result.changes).toBe(1);
33
+ p.close();
34
+ });
35
+
36
+ it('run with no params', () => {
37
+ const p = new SQLitePersistenceProvider(':memory:');
38
+ p.execSql('CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)');
39
+ const result = p.run('INSERT INTO t DEFAULT VALUES');
40
+ expect(result.changes).toBe(1);
41
+ expect(result.lastInsertRowid).toBe(1);
42
+ p.close();
43
+ });
44
+
45
+ it('get returns undefined for no match', () => {
46
+ const p = new SQLitePersistenceProvider(':memory:');
47
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY)');
48
+ const row = p.get('SELECT * FROM t WHERE id = ?', ['nonexistent']);
49
+ expect(row).toBeUndefined();
50
+ p.close();
51
+ });
52
+
53
+ it('all returns empty for no matches', () => {
54
+ const p = new SQLitePersistenceProvider(':memory:');
55
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY)');
56
+ const rows = p.all('SELECT * FROM t');
57
+ expect(rows).toEqual([]);
58
+ p.close();
59
+ });
60
+
61
+ it('all returns multiple rows', () => {
62
+ const p = new SQLitePersistenceProvider(':memory:');
63
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY, val INTEGER)');
64
+ p.run('INSERT INTO t VALUES (?, ?)', ['a', 1]);
65
+ p.run('INSERT INTO t VALUES (?, ?)', ['b', 2]);
66
+ p.run('INSERT INTO t VALUES (?, ?)', ['c', 3]);
67
+ const rows = p.all<{ id: string; val: number }>('SELECT * FROM t ORDER BY id');
68
+ expect(rows).toHaveLength(3);
69
+ expect(rows[0].id).toBe('a');
70
+ expect(rows[2].val).toBe(3);
71
+ p.close();
72
+ });
73
+
74
+ it('transaction commits on success', () => {
75
+ const p = new SQLitePersistenceProvider(':memory:');
76
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY)');
77
+ p.transaction(() => {
78
+ p.run('INSERT INTO t VALUES (?)', ['a']);
79
+ p.run('INSERT INTO t VALUES (?)', ['b']);
80
+ });
81
+ const rows = p.all('SELECT * FROM t');
82
+ expect(rows).toHaveLength(2);
83
+ p.close();
84
+ });
85
+
86
+ it('transaction rolls back on error', () => {
87
+ const p = new SQLitePersistenceProvider(':memory:');
88
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY)');
89
+ expect(() =>
90
+ p.transaction(() => {
91
+ p.run('INSERT INTO t VALUES (?)', ['a']);
92
+ throw new Error('rollback');
93
+ }),
94
+ ).toThrow('rollback');
95
+ const rows = p.all('SELECT * FROM t');
96
+ expect(rows).toHaveLength(0);
97
+ p.close();
98
+ });
99
+
100
+ it('transaction returns value', () => {
101
+ const p = new SQLitePersistenceProvider(':memory:');
102
+ p.execSql('CREATE TABLE t (id TEXT PRIMARY KEY)');
103
+ const result = p.transaction(() => {
104
+ p.run('INSERT INTO t VALUES (?)', ['a']);
105
+ return 42;
106
+ });
107
+ expect(result).toBe(42);
108
+ p.close();
109
+ });
110
+
111
+ it('getDatabase returns raw db', () => {
112
+ const p = new SQLitePersistenceProvider(':memory:');
113
+ const db = p.getDatabase();
114
+ expect(db).toBeDefined();
115
+ // Verify it's a real better-sqlite3 database
116
+ expect(typeof db.prepare).toBe('function');
117
+ p.close();
118
+ });
119
+
120
+ it('close prevents further operations', () => {
121
+ const p = new SQLitePersistenceProvider(':memory:');
122
+ p.close();
123
+ expect(() => p.run('SELECT 1')).toThrow();
124
+ });
125
+
126
+ it('backend returns sqlite', () => {
127
+ const p = new SQLitePersistenceProvider(':memory:');
128
+ expect(p.backend).toBe('sqlite');
129
+ p.close();
130
+ });
131
+
132
+ it('ftsSearch returns matches', () => {
133
+ const p = new SQLitePersistenceProvider(':memory:');
134
+ p.execSql(`
135
+ CREATE TABLE docs (rowid INTEGER PRIMARY KEY, title TEXT, body TEXT);
136
+ CREATE VIRTUAL TABLE docs_fts USING fts5(title, body, content=docs, content_rowid=rowid);
137
+ INSERT INTO docs (rowid, title, body) VALUES (1, 'Hello World', 'This is a test document');
138
+ INSERT INTO docs (rowid, title, body) VALUES (2, 'Goodbye', 'Another test here');
139
+ INSERT INTO docs_fts (rowid, title, body) VALUES (1, 'Hello World', 'This is a test document');
140
+ INSERT INTO docs_fts (rowid, title, body) VALUES (2, 'Goodbye', 'Another test here');
141
+ `);
142
+ const results = p.ftsSearch<{ title: string; body: string }>('docs', 'hello');
143
+ expect(results).toHaveLength(1);
144
+ expect(results[0].title).toBe('Hello World');
145
+ p.close();
146
+ });
147
+
148
+ it('ftsSearch respects limit/offset', () => {
149
+ const p = new SQLitePersistenceProvider(':memory:');
150
+ p.execSql(`
151
+ CREATE TABLE items (rowid INTEGER PRIMARY KEY, name TEXT);
152
+ CREATE VIRTUAL TABLE items_fts USING fts5(name, content=items, content_rowid=rowid);
153
+ INSERT INTO items (rowid, name) VALUES (1, 'test alpha');
154
+ INSERT INTO items (rowid, name) VALUES (2, 'test beta');
155
+ INSERT INTO items (rowid, name) VALUES (3, 'test gamma');
156
+ INSERT INTO items_fts (rowid, name) VALUES (1, 'test alpha');
157
+ INSERT INTO items_fts (rowid, name) VALUES (2, 'test beta');
158
+ INSERT INTO items_fts (rowid, name) VALUES (3, 'test gamma');
159
+ `);
160
+ const limited = p.ftsSearch<{ name: string }>('items', 'test', { limit: 2 });
161
+ expect(limited).toHaveLength(2);
162
+ const offset = p.ftsSearch<{ name: string }>('items', 'test', { limit: 1, offset: 2 });
163
+ expect(offset).toHaveLength(1);
164
+ p.close();
165
+ });
166
+
167
+ it('ftsSearch applies filters', () => {
168
+ const p = new SQLitePersistenceProvider(':memory:');
169
+ p.execSql(`
170
+ CREATE TABLE notes (rowid INTEGER PRIMARY KEY, title TEXT, category TEXT);
171
+ CREATE VIRTUAL TABLE notes_fts USING fts5(title, content=notes, content_rowid=rowid);
172
+ INSERT INTO notes (rowid, title, category) VALUES (1, 'design pattern', 'arch');
173
+ INSERT INTO notes (rowid, title, category) VALUES (2, 'design review', 'process');
174
+ INSERT INTO notes_fts (rowid, title) VALUES (1, 'design pattern');
175
+ INSERT INTO notes_fts (rowid, title) VALUES (2, 'design review');
176
+ `);
177
+ const filtered = p.ftsSearch<{ title: string; category: string }>('notes', 'design', {
178
+ filters: { category: 'arch' },
179
+ });
180
+ expect(filtered).toHaveLength(1);
181
+ expect(filtered[0].category).toBe('arch');
182
+ p.close();
183
+ });
184
+
185
+ it('ftsRebuild no-ops gracefully', () => {
186
+ const p = new SQLitePersistenceProvider(':memory:');
187
+ // No FTS table exists — should not throw
188
+ expect(() => p.ftsRebuild('nonexistent')).not.toThrow();
189
+ p.close();
190
+ });
191
+ });
192
+
193
+ // ─── Vault with PersistenceProvider ───────────────────────────────────
194
+
195
+ describe('Vault with PersistenceProvider', () => {
196
+ it('accepts string path (backward compat)', () => {
197
+ const vault = new Vault(':memory:');
198
+ expect(vault.stats().totalEntries).toBe(0);
199
+ vault.close();
200
+ });
201
+
202
+ it('accepts SQLitePersistenceProvider', () => {
203
+ const provider = new SQLitePersistenceProvider(':memory:');
204
+ provider.run('PRAGMA journal_mode = WAL');
205
+ provider.run('PRAGMA foreign_keys = ON');
206
+ const vault = new Vault(provider);
207
+ expect(vault.stats().totalEntries).toBe(0);
208
+ vault.close();
209
+ });
210
+
211
+ it('createWithSQLite factory works', () => {
212
+ const vault = Vault.createWithSQLite(':memory:');
213
+ vault.add({
214
+ id: 'test-1',
215
+ type: 'pattern',
216
+ domain: 'core',
217
+ title: 'Test Pattern',
218
+ severity: 'suggestion',
219
+ description: 'A test',
220
+ tags: ['test'],
221
+ });
222
+ expect(vault.stats().totalEntries).toBe(1);
223
+ vault.close();
224
+ });
225
+
226
+ it('getProvider returns the provider', () => {
227
+ const provider = new SQLitePersistenceProvider(':memory:');
228
+ provider.run('PRAGMA journal_mode = WAL');
229
+ provider.run('PRAGMA foreign_keys = ON');
230
+ const vault = new Vault(provider);
231
+ expect(vault.getProvider()).toBe(provider);
232
+ vault.close();
233
+ });
234
+
235
+ it('getDb works with SQLite provider', () => {
236
+ const vault = new Vault(':memory:');
237
+ const db = vault.getDb();
238
+ expect(typeof db.prepare).toBe('function');
239
+ vault.close();
240
+ });
241
+
242
+ it('all vault operations work through provider', () => {
243
+ const vault = new Vault(':memory:');
244
+
245
+ // seed + get
246
+ vault.seed([
247
+ {
248
+ id: 'e1',
249
+ type: 'pattern',
250
+ domain: 'test',
251
+ title: 'Pattern One',
252
+ severity: 'suggestion',
253
+ description: 'First pattern',
254
+ tags: ['a', 'b'],
255
+ },
256
+ {
257
+ id: 'e2',
258
+ type: 'anti-pattern',
259
+ domain: 'test',
260
+ title: 'Anti Pattern',
261
+ severity: 'warning',
262
+ description: 'Avoid this',
263
+ tags: ['c'],
264
+ },
265
+ ]);
266
+ expect(vault.get('e1')?.title).toBe('Pattern One');
267
+ expect(vault.stats().totalEntries).toBe(2);
268
+
269
+ // search
270
+ const results = vault.search('pattern');
271
+ expect(results.length).toBeGreaterThan(0);
272
+
273
+ // list
274
+ const all = vault.list({ domain: 'test' });
275
+ expect(all).toHaveLength(2);
276
+
277
+ // update
278
+ const updated = vault.update('e1', { title: 'Updated' });
279
+ expect(updated?.title).toBe('Updated');
280
+
281
+ // remove
282
+ expect(vault.remove('e2')).toBe(true);
283
+ expect(vault.stats().totalEntries).toBe(1);
284
+
285
+ // bulk remove
286
+ expect(vault.bulkRemove(['e1'])).toBe(1);
287
+ expect(vault.stats().totalEntries).toBe(0);
288
+
289
+ vault.close();
290
+ });
291
+ });
@@ -95,7 +95,7 @@ describe('Planner', () => {
95
95
  it('should throw when approving non-draft plan', () => {
96
96
  const plan = planner.create({ objective: 'Already approved', scope: 'test' });
97
97
  planner.approve(plan.id);
98
- expect(() => planner.approve(plan.id)).toThrow('must be');
98
+ expect(() => planner.approve(plan.id)).toThrow('Invalid transition');
99
99
  });
100
100
 
101
101
  it('should throw for unknown plan', () => {
@@ -113,7 +113,7 @@ describe('Planner', () => {
113
113
 
114
114
  it('should throw when executing non-approved plan', () => {
115
115
  const plan = planner.create({ objective: 'Not approved', scope: 'test' });
116
- expect(() => planner.startExecution(plan.id)).toThrow('must be');
116
+ expect(() => planner.startExecution(plan.id)).toThrow('Invalid transition');
117
117
  });
118
118
  });
119
119
 
@@ -178,17 +178,25 @@ describe('Planner', () => {
178
178
  });
179
179
 
180
180
  describe('complete', () => {
181
- it('should transition executing to completed', () => {
181
+ it('should transition reconciling to completed', () => {
182
182
  const plan = planner.create({ objective: 'Complete me', scope: 'test' });
183
183
  planner.approve(plan.id);
184
184
  planner.startExecution(plan.id);
185
+ planner.startReconciliation(plan.id);
185
186
  const completed = planner.complete(plan.id);
186
187
  expect(completed.status).toBe('completed');
187
188
  });
188
189
 
189
- it('should throw when completing non-executing plan', () => {
190
+ it('should throw when completing from executing (must go through reconciling)', () => {
191
+ const plan = planner.create({ objective: 'Not reconciling', scope: 'test' });
192
+ planner.approve(plan.id);
193
+ planner.startExecution(plan.id);
194
+ expect(() => planner.complete(plan.id)).toThrow('Invalid transition');
195
+ });
196
+
197
+ it('should throw when completing from draft', () => {
190
198
  const plan = planner.create({ objective: 'Not executing', scope: 'test' });
191
- expect(() => planner.complete(plan.id)).toThrow('must be');
199
+ expect(() => planner.complete(plan.id)).toThrow('Invalid transition');
192
200
  });
193
201
  });
194
202
 
@@ -210,7 +218,7 @@ describe('Planner', () => {
210
218
  });
211
219
 
212
220
  describe('getActive', () => {
213
- it('should return draft, approved, and executing plans', () => {
221
+ it('should return brainstorming, draft, approved, executing, validating, and reconciling plans', () => {
214
222
  planner.create({ objective: 'Draft', scope: 'a' });
215
223
  const p2 = planner.create({ objective: 'Approved', scope: 'b' });
216
224
  const p3 = planner.create({ objective: 'Executing', scope: 'c' });
@@ -220,6 +228,7 @@ describe('Planner', () => {
220
228
  planner.startExecution(p3.id);
221
229
  planner.approve(p4.id);
222
230
  planner.startExecution(p4.id);
231
+ planner.startReconciliation(p4.id);
223
232
  planner.complete(p4.id);
224
233
  const active = planner.getActive();
225
234
  expect(active).toHaveLength(3);
@@ -237,10 +246,22 @@ describe('Planner', () => {
237
246
  'Set TTL to 5 minutes since average data freshness requirement is 10 minutes',
238
247
  ],
239
248
  tasks: [
240
- { title: 'Set up Redis client', description: 'Install and configure Redis connection pool' },
241
- { title: 'Add cache middleware', description: 'Express middleware for transparent caching' },
242
- { title: 'Add invalidation logic', description: 'Purge cache on write operations to ensure consistency' },
243
- { title: 'Write integration tests', description: 'Test cache hit/miss scenarios with Redis' },
249
+ {
250
+ title: 'Set up Redis client',
251
+ description: 'Install and configure Redis connection pool',
252
+ },
253
+ {
254
+ title: 'Add cache middleware',
255
+ description: 'Express middleware for transparent caching',
256
+ },
257
+ {
258
+ title: 'Add invalidation logic',
259
+ description: 'Purge cache on write operations to ensure consistency',
260
+ },
261
+ {
262
+ title: 'Write integration tests',
263
+ description: 'Test cache hit/miss scenarios with Redis',
264
+ },
244
265
  { title: 'Add monitoring', description: 'Track and verify cache hit rate metrics' },
245
266
  ],
246
267
  });
@@ -325,13 +346,17 @@ describe('Planner', () => {
325
346
  const plan = planner.create({
326
347
  objective: 'Build a comprehensive authentication system for the application',
327
348
  scope: 'Backend authentication module',
328
- decisions: [
329
- 'Use JWT tokens because they are stateless and work well with microservices',
330
- ],
349
+ decisions: ['Use JWT tokens because they are stateless and work well with microservices'],
331
350
  tasks: [
332
351
  { title: 'Create auth middleware', description: 'JWT validation middleware for Express' },
333
- { title: 'Add login endpoint', description: 'POST /auth/login with credential validation' },
334
- { title: 'Add refresh tokens', description: 'Implement token refresh flow with rotation' },
352
+ {
353
+ title: 'Add login endpoint',
354
+ description: 'POST /auth/login with credential validation',
355
+ },
356
+ {
357
+ title: 'Add refresh tokens',
358
+ description: 'Implement token refresh flow with rotation',
359
+ },
335
360
  { title: 'Write auth tests', description: 'Integration tests for all auth endpoints' },
336
361
  ],
337
362
  });
@@ -354,9 +379,7 @@ describe('Planner', () => {
354
379
  const plan = planner.create({
355
380
  objective: 'Maybe perhaps build something simple and easy, possibly soon, etc',
356
381
  scope: 'Various things, probably several modules, somehow',
357
- decisions: [
358
- 'Use some appropriate approach because it seems good due to various reasons',
359
- ],
382
+ decisions: ['Use some appropriate approach because it seems good due to various reasons'],
360
383
  tasks: [
361
384
  { title: 'Do some stuff', description: 'Maybe implement various things somehow' },
362
385
  { title: 'Maybe test', description: 'Perhaps write some tests probably' },
@@ -374,7 +397,10 @@ describe('Planner', () => {
374
397
  });
375
398
 
376
399
  it('should store check in plan history', () => {
377
- const plan = planner.create({ objective: 'History test plan objective', scope: 'test scope' });
400
+ const plan = planner.create({
401
+ objective: 'History test plan objective',
402
+ scope: 'test scope',
403
+ });
378
404
  planner.grade(plan.id);
379
405
  planner.grade(plan.id);
380
406
  const history = planner.getCheckHistory(plan.id);
@@ -383,7 +409,10 @@ describe('Planner', () => {
383
409
  });
384
410
 
385
411
  it('should persist latestCheck', () => {
386
- const plan = planner.create({ objective: 'Persist test plan objective', scope: 'test scope' });
412
+ const plan = planner.create({
413
+ objective: 'Persist test plan objective',
414
+ scope: 'test scope',
415
+ });
387
416
  const check = planner.grade(plan.id);
388
417
  const latest = planner.getLatestCheck(plan.id);
389
418
  expect(latest).not.toBeNull();
@@ -557,7 +586,7 @@ describe('Planner', () => {
557
586
  });
558
587
 
559
588
  describe('full lifecycle', () => {
560
- it('should support draft → approved → executing → completed with tasks', () => {
589
+ it('should support draft → approved → executing → reconciling → completed with tasks', () => {
561
590
  const plan = planner.create({
562
591
  objective: 'Full lifecycle test',
563
592
  scope: 'integration',
@@ -582,11 +611,60 @@ describe('Planner', () => {
582
611
  planner.updateTask(plan.id, 'task-2', 'completed');
583
612
  planner.updateTask(plan.id, 'task-3', 'skipped');
584
613
 
614
+ planner.startReconciliation(plan.id);
615
+ expect(planner.get(plan.id)!.status).toBe('reconciling');
616
+
585
617
  const final = planner.complete(plan.id);
586
618
  expect(final.status).toBe('completed');
587
619
  expect(final.tasks[0].status).toBe('completed');
588
620
  expect(final.tasks[1].status).toBe('completed');
589
621
  expect(final.tasks[2].status).toBe('skipped');
590
622
  });
623
+
624
+ it('should support brainstorming → draft → approved → executing lifecycle', () => {
625
+ const plan = planner.create({
626
+ objective: 'Brainstorming lifecycle test',
627
+ scope: 'integration',
628
+ initialStatus: 'brainstorming',
629
+ });
630
+ expect(plan.status).toBe('brainstorming');
631
+
632
+ planner.promoteToDraft(plan.id);
633
+ expect(planner.get(plan.id)!.status).toBe('draft');
634
+
635
+ planner.approve(plan.id);
636
+ expect(planner.get(plan.id)!.status).toBe('approved');
637
+ });
638
+
639
+ it('should support validating state', () => {
640
+ const plan = planner.create({
641
+ objective: 'Validation lifecycle test',
642
+ scope: 'integration',
643
+ tasks: [{ title: 'Task 1', description: 'Test task' }],
644
+ });
645
+ planner.approve(plan.id);
646
+ planner.startExecution(plan.id);
647
+ planner.startValidation(plan.id);
648
+ expect(planner.get(plan.id)!.status).toBe('validating');
649
+
650
+ // Can update tasks during validation
651
+ planner.updateTask(plan.id, 'task-1', 'completed');
652
+
653
+ // Can go back to executing from validating
654
+ planner.startExecution(plan.id);
655
+ expect(planner.get(plan.id)!.status).toBe('executing');
656
+ });
657
+
658
+ it('should support archiving completed plans', () => {
659
+ const plan = planner.create({ objective: 'Archive test', scope: 'test' });
660
+ planner.approve(plan.id);
661
+ planner.startExecution(plan.id);
662
+ planner.startReconciliation(plan.id);
663
+ planner.complete(plan.id);
664
+
665
+ const archived = planner.archive();
666
+ expect(archived).toHaveLength(1);
667
+ expect(archived[0].status).toBe('archived');
668
+ });
591
669
  });
592
670
  });