@soleri/core 2.12.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/data/flows/build.flow.yaml +128 -0
  2. package/data/flows/deliver.flow.yaml +110 -0
  3. package/data/flows/design.flow.yaml +108 -0
  4. package/data/flows/enhance.flow.yaml +90 -0
  5. package/data/flows/explore.flow.yaml +84 -0
  6. package/data/flows/fix.flow.yaml +90 -0
  7. package/data/flows/plan.flow.yaml +87 -0
  8. package/data/flows/review.flow.yaml +90 -0
  9. package/dist/agency/agency-manager.d.ts +27 -1
  10. package/dist/agency/agency-manager.d.ts.map +1 -1
  11. package/dist/agency/agency-manager.js +180 -9
  12. package/dist/agency/agency-manager.js.map +1 -1
  13. package/dist/agency/default-rules.d.ts +7 -0
  14. package/dist/agency/default-rules.d.ts.map +1 -0
  15. package/dist/agency/default-rules.js +79 -0
  16. package/dist/agency/default-rules.js.map +1 -0
  17. package/dist/agency/types.d.ts +48 -0
  18. package/dist/agency/types.d.ts.map +1 -1
  19. package/dist/brain/brain.d.ts +17 -2
  20. package/dist/brain/brain.d.ts.map +1 -1
  21. package/dist/brain/brain.js +118 -8
  22. package/dist/brain/brain.js.map +1 -1
  23. package/dist/brain/intelligence.d.ts.map +1 -1
  24. package/dist/brain/intelligence.js +16 -2
  25. package/dist/brain/intelligence.js.map +1 -1
  26. package/dist/brain/knowledge-synthesizer.d.ts +37 -0
  27. package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
  28. package/dist/brain/knowledge-synthesizer.js +161 -0
  29. package/dist/brain/knowledge-synthesizer.js.map +1 -0
  30. package/dist/brain/learning-radar.d.ts +96 -0
  31. package/dist/brain/learning-radar.d.ts.map +1 -0
  32. package/dist/brain/learning-radar.js +202 -0
  33. package/dist/brain/learning-radar.js.map +1 -0
  34. package/dist/brain/types.d.ts +15 -0
  35. package/dist/brain/types.d.ts.map +1 -1
  36. package/dist/capabilities/chain-mapping.d.ts +21 -0
  37. package/dist/capabilities/chain-mapping.d.ts.map +1 -0
  38. package/dist/capabilities/chain-mapping.js +86 -0
  39. package/dist/capabilities/chain-mapping.js.map +1 -0
  40. package/dist/capabilities/index.d.ts +10 -0
  41. package/dist/capabilities/index.d.ts.map +1 -0
  42. package/dist/capabilities/index.js +8 -0
  43. package/dist/capabilities/index.js.map +1 -0
  44. package/dist/capabilities/registry.d.ts +95 -0
  45. package/dist/capabilities/registry.d.ts.map +1 -0
  46. package/dist/capabilities/registry.js +227 -0
  47. package/dist/capabilities/registry.js.map +1 -0
  48. package/dist/capabilities/types.d.ts +106 -0
  49. package/dist/capabilities/types.d.ts.map +1 -0
  50. package/dist/capabilities/types.js +12 -0
  51. package/dist/capabilities/types.js.map +1 -0
  52. package/dist/context/context-engine.d.ts.map +1 -1
  53. package/dist/context/context-engine.js +82 -17
  54. package/dist/context/context-engine.js.map +1 -1
  55. package/dist/context/types.d.ts +5 -0
  56. package/dist/context/types.d.ts.map +1 -1
  57. package/dist/control/intent-router.d.ts +12 -1
  58. package/dist/control/intent-router.d.ts.map +1 -1
  59. package/dist/control/intent-router.js +126 -2
  60. package/dist/control/intent-router.js.map +1 -1
  61. package/dist/control/types.d.ts +17 -0
  62. package/dist/control/types.d.ts.map +1 -1
  63. package/dist/curator/classifier.d.ts +18 -0
  64. package/dist/curator/classifier.d.ts.map +1 -0
  65. package/dist/curator/classifier.js +61 -0
  66. package/dist/curator/classifier.js.map +1 -0
  67. package/dist/curator/quality-gate.d.ts +29 -0
  68. package/dist/curator/quality-gate.d.ts.map +1 -0
  69. package/dist/curator/quality-gate.js +88 -0
  70. package/dist/curator/quality-gate.js.map +1 -0
  71. package/dist/domain-packs/index.d.ts +8 -0
  72. package/dist/domain-packs/index.d.ts.map +1 -0
  73. package/dist/domain-packs/index.js +8 -0
  74. package/dist/domain-packs/index.js.map +1 -0
  75. package/dist/domain-packs/inject-rules.d.ts +24 -0
  76. package/dist/domain-packs/inject-rules.d.ts.map +1 -0
  77. package/dist/domain-packs/inject-rules.js +65 -0
  78. package/dist/domain-packs/inject-rules.js.map +1 -0
  79. package/dist/domain-packs/knowledge-installer.d.ts +27 -0
  80. package/dist/domain-packs/knowledge-installer.d.ts.map +1 -0
  81. package/dist/domain-packs/knowledge-installer.js +89 -0
  82. package/dist/domain-packs/knowledge-installer.js.map +1 -0
  83. package/dist/domain-packs/loader.d.ts +28 -0
  84. package/dist/domain-packs/loader.d.ts.map +1 -0
  85. package/dist/domain-packs/loader.js +105 -0
  86. package/dist/domain-packs/loader.js.map +1 -0
  87. package/dist/domain-packs/pack-runtime.d.ts +80 -0
  88. package/dist/domain-packs/pack-runtime.d.ts.map +1 -0
  89. package/dist/domain-packs/pack-runtime.js +36 -0
  90. package/dist/domain-packs/pack-runtime.js.map +1 -0
  91. package/dist/domain-packs/skills-installer.d.ts +21 -0
  92. package/dist/domain-packs/skills-installer.d.ts.map +1 -0
  93. package/dist/domain-packs/skills-installer.js +38 -0
  94. package/dist/domain-packs/skills-installer.js.map +1 -0
  95. package/dist/domain-packs/token-resolver.d.ts +37 -0
  96. package/dist/domain-packs/token-resolver.d.ts.map +1 -0
  97. package/dist/domain-packs/token-resolver.js +109 -0
  98. package/dist/domain-packs/token-resolver.js.map +1 -0
  99. package/dist/domain-packs/types.d.ts +91 -0
  100. package/dist/domain-packs/types.d.ts.map +1 -0
  101. package/dist/domain-packs/types.js +122 -0
  102. package/dist/domain-packs/types.js.map +1 -0
  103. package/dist/engine/bin/soleri-engine.d.ts +12 -0
  104. package/dist/engine/bin/soleri-engine.d.ts.map +1 -0
  105. package/dist/engine/bin/soleri-engine.js +184 -0
  106. package/dist/engine/bin/soleri-engine.js.map +1 -0
  107. package/dist/engine/core-ops.d.ts +27 -0
  108. package/dist/engine/core-ops.d.ts.map +1 -0
  109. package/dist/engine/core-ops.js +159 -0
  110. package/dist/engine/core-ops.js.map +1 -0
  111. package/dist/engine/index.d.ts +19 -0
  112. package/dist/engine/index.d.ts.map +1 -0
  113. package/dist/engine/index.js +17 -0
  114. package/dist/engine/index.js.map +1 -0
  115. package/dist/engine/register-engine.d.ts +54 -0
  116. package/dist/engine/register-engine.d.ts.map +1 -0
  117. package/dist/engine/register-engine.js +270 -0
  118. package/dist/engine/register-engine.js.map +1 -0
  119. package/dist/engine/test-helpers.d.ts +30 -0
  120. package/dist/engine/test-helpers.d.ts.map +1 -0
  121. package/dist/engine/test-helpers.js +59 -0
  122. package/dist/engine/test-helpers.js.map +1 -0
  123. package/dist/events/event-bus.d.ts +30 -0
  124. package/dist/events/event-bus.d.ts.map +1 -0
  125. package/dist/events/event-bus.js +51 -0
  126. package/dist/events/event-bus.js.map +1 -0
  127. package/dist/flows/chain-runner.d.ts +46 -0
  128. package/dist/flows/chain-runner.d.ts.map +1 -0
  129. package/dist/flows/chain-runner.js +271 -0
  130. package/dist/flows/chain-runner.js.map +1 -0
  131. package/dist/flows/chain-types.d.ts +103 -0
  132. package/dist/flows/chain-types.d.ts.map +1 -0
  133. package/dist/flows/chain-types.js +23 -0
  134. package/dist/flows/chain-types.js.map +1 -0
  135. package/dist/flows/context-router.d.ts +39 -0
  136. package/dist/flows/context-router.d.ts.map +1 -0
  137. package/dist/flows/context-router.js +206 -0
  138. package/dist/flows/context-router.js.map +1 -0
  139. package/dist/flows/dispatch-registry.d.ts +24 -0
  140. package/dist/flows/dispatch-registry.d.ts.map +1 -0
  141. package/dist/flows/dispatch-registry.js +70 -0
  142. package/dist/flows/dispatch-registry.js.map +1 -0
  143. package/dist/flows/epilogue.d.ts +24 -0
  144. package/dist/flows/epilogue.d.ts.map +1 -0
  145. package/dist/flows/epilogue.js +52 -0
  146. package/dist/flows/epilogue.js.map +1 -0
  147. package/dist/flows/executor.d.ts +25 -0
  148. package/dist/flows/executor.d.ts.map +1 -0
  149. package/dist/flows/executor.js +153 -0
  150. package/dist/flows/executor.js.map +1 -0
  151. package/dist/flows/gate-evaluator.d.ts +26 -0
  152. package/dist/flows/gate-evaluator.d.ts.map +1 -0
  153. package/dist/flows/gate-evaluator.js +162 -0
  154. package/dist/flows/gate-evaluator.js.map +1 -0
  155. package/dist/flows/index.d.ts +14 -0
  156. package/dist/flows/index.d.ts.map +1 -0
  157. package/dist/flows/index.js +20 -0
  158. package/dist/flows/index.js.map +1 -0
  159. package/dist/flows/loader.d.ts +17 -0
  160. package/dist/flows/loader.d.ts.map +1 -0
  161. package/dist/flows/loader.js +61 -0
  162. package/dist/flows/loader.js.map +1 -0
  163. package/dist/flows/plan-builder.d.ts +40 -0
  164. package/dist/flows/plan-builder.d.ts.map +1 -0
  165. package/dist/flows/plan-builder.js +213 -0
  166. package/dist/flows/plan-builder.js.map +1 -0
  167. package/dist/flows/probes.d.ts +11 -0
  168. package/dist/flows/probes.d.ts.map +1 -0
  169. package/dist/flows/probes.js +62 -0
  170. package/dist/flows/probes.js.map +1 -0
  171. package/dist/flows/types.d.ts +950 -0
  172. package/dist/flows/types.d.ts.map +1 -0
  173. package/dist/flows/types.js +105 -0
  174. package/dist/flows/types.js.map +1 -0
  175. package/dist/health/doctor-checks.d.ts +15 -0
  176. package/dist/health/doctor-checks.d.ts.map +1 -0
  177. package/dist/health/doctor-checks.js +98 -0
  178. package/dist/health/doctor-checks.js.map +1 -0
  179. package/dist/index.d.ts +11 -1
  180. package/dist/index.d.ts.map +1 -1
  181. package/dist/index.js +10 -1
  182. package/dist/index.js.map +1 -1
  183. package/dist/intake/text-ingester.d.ts +52 -0
  184. package/dist/intake/text-ingester.d.ts.map +1 -0
  185. package/dist/intake/text-ingester.js +181 -0
  186. package/dist/intake/text-ingester.js.map +1 -0
  187. package/dist/intelligence/loader.d.ts +19 -0
  188. package/dist/intelligence/loader.d.ts.map +1 -1
  189. package/dist/intelligence/loader.js +35 -0
  190. package/dist/intelligence/loader.js.map +1 -1
  191. package/dist/intelligence/types.d.ts +1 -0
  192. package/dist/intelligence/types.d.ts.map +1 -1
  193. package/dist/llm/llm-client.d.ts.map +1 -1
  194. package/dist/llm/llm-client.js +37 -1
  195. package/dist/llm/llm-client.js.map +1 -1
  196. package/dist/llm/oauth-discovery.d.ts +26 -0
  197. package/dist/llm/oauth-discovery.d.ts.map +1 -0
  198. package/dist/llm/oauth-discovery.js +149 -0
  199. package/dist/llm/oauth-discovery.js.map +1 -0
  200. package/dist/packs/types.d.ts +58 -19
  201. package/dist/packs/types.d.ts.map +1 -1
  202. package/dist/packs/types.js +14 -0
  203. package/dist/packs/types.js.map +1 -1
  204. package/dist/planning/evidence-collector.d.ts +41 -0
  205. package/dist/planning/evidence-collector.d.ts.map +1 -0
  206. package/dist/planning/evidence-collector.js +194 -0
  207. package/dist/planning/evidence-collector.js.map +1 -0
  208. package/dist/planning/planner.d.ts +4 -0
  209. package/dist/planning/planner.d.ts.map +1 -1
  210. package/dist/planning/planner.js +11 -0
  211. package/dist/planning/planner.js.map +1 -1
  212. package/dist/playbooks/generic/onboarding.d.ts +9 -0
  213. package/dist/playbooks/generic/onboarding.d.ts.map +1 -0
  214. package/dist/playbooks/generic/onboarding.js +74 -0
  215. package/dist/playbooks/generic/onboarding.js.map +1 -0
  216. package/dist/playbooks/playbook-registry.d.ts.map +1 -1
  217. package/dist/playbooks/playbook-registry.js +2 -0
  218. package/dist/playbooks/playbook-registry.js.map +1 -1
  219. package/dist/queue/job-queue.d.ts +92 -0
  220. package/dist/queue/job-queue.d.ts.map +1 -0
  221. package/dist/queue/job-queue.js +180 -0
  222. package/dist/queue/job-queue.js.map +1 -0
  223. package/dist/queue/pipeline-runner.d.ts +62 -0
  224. package/dist/queue/pipeline-runner.d.ts.map +1 -0
  225. package/dist/queue/pipeline-runner.js +126 -0
  226. package/dist/queue/pipeline-runner.js.map +1 -0
  227. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  228. package/dist/runtime/admin-extra-ops.js +15 -9
  229. package/dist/runtime/admin-extra-ops.js.map +1 -1
  230. package/dist/runtime/admin-ops.js +4 -4
  231. package/dist/runtime/admin-ops.js.map +1 -1
  232. package/dist/runtime/admin-setup-ops.d.ts +20 -0
  233. package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
  234. package/dist/runtime/admin-setup-ops.js +583 -0
  235. package/dist/runtime/admin-setup-ops.js.map +1 -0
  236. package/dist/runtime/capture-ops.d.ts.map +1 -1
  237. package/dist/runtime/capture-ops.js +33 -1
  238. package/dist/runtime/capture-ops.js.map +1 -1
  239. package/dist/runtime/chain-ops.d.ts +9 -0
  240. package/dist/runtime/chain-ops.d.ts.map +1 -0
  241. package/dist/runtime/chain-ops.js +107 -0
  242. package/dist/runtime/chain-ops.js.map +1 -0
  243. package/dist/runtime/claude-md-helpers.d.ts +65 -0
  244. package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
  245. package/dist/runtime/claude-md-helpers.js +173 -0
  246. package/dist/runtime/claude-md-helpers.js.map +1 -0
  247. package/dist/runtime/curator-extra-ops.d.ts +3 -2
  248. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  249. package/dist/runtime/curator-extra-ops.js +81 -3
  250. package/dist/runtime/curator-extra-ops.js.map +1 -1
  251. package/dist/runtime/domain-ops.d.ts +21 -5
  252. package/dist/runtime/domain-ops.d.ts.map +1 -1
  253. package/dist/runtime/domain-ops.js +64 -6
  254. package/dist/runtime/domain-ops.js.map +1 -1
  255. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  256. package/dist/runtime/facades/admin-facade.js +4 -0
  257. package/dist/runtime/facades/admin-facade.js.map +1 -1
  258. package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
  259. package/dist/runtime/facades/agency-facade.js +64 -0
  260. package/dist/runtime/facades/agency-facade.js.map +1 -1
  261. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  262. package/dist/runtime/facades/brain-facade.js +122 -1
  263. package/dist/runtime/facades/brain-facade.js.map +1 -1
  264. package/dist/runtime/facades/cognee-facade.d.ts.map +1 -1
  265. package/dist/runtime/facades/cognee-facade.js +3 -1
  266. package/dist/runtime/facades/cognee-facade.js.map +1 -1
  267. package/dist/runtime/facades/control-facade.d.ts.map +1 -1
  268. package/dist/runtime/facades/control-facade.js +42 -0
  269. package/dist/runtime/facades/control-facade.js.map +1 -1
  270. package/dist/runtime/facades/index.d.ts.map +1 -1
  271. package/dist/runtime/facades/index.js +10 -6
  272. package/dist/runtime/facades/index.js.map +1 -1
  273. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  274. package/dist/runtime/facades/memory-facade.js +20 -2
  275. package/dist/runtime/facades/memory-facade.js.map +1 -1
  276. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  277. package/dist/runtime/facades/plan-facade.js +2 -0
  278. package/dist/runtime/facades/plan-facade.js.map +1 -1
  279. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  280. package/dist/runtime/facades/vault-facade.js +27 -5
  281. package/dist/runtime/facades/vault-facade.js.map +1 -1
  282. package/dist/runtime/intake-ops.d.ts +7 -5
  283. package/dist/runtime/intake-ops.d.ts.map +1 -1
  284. package/dist/runtime/intake-ops.js +98 -5
  285. package/dist/runtime/intake-ops.js.map +1 -1
  286. package/dist/runtime/memory-extra-ops.d.ts +6 -3
  287. package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
  288. package/dist/runtime/memory-extra-ops.js +292 -4
  289. package/dist/runtime/memory-extra-ops.js.map +1 -1
  290. package/dist/runtime/orchestrate-ops.d.ts +8 -7
  291. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  292. package/dist/runtime/orchestrate-ops.js +217 -61
  293. package/dist/runtime/orchestrate-ops.js.map +1 -1
  294. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  295. package/dist/runtime/planning-extra-ops.js +85 -0
  296. package/dist/runtime/planning-extra-ops.js.map +1 -1
  297. package/dist/runtime/playbook-ops.js +1 -1
  298. package/dist/runtime/playbook-ops.js.map +1 -1
  299. package/dist/runtime/runtime.d.ts.map +1 -1
  300. package/dist/runtime/runtime.js +165 -18
  301. package/dist/runtime/runtime.js.map +1 -1
  302. package/dist/runtime/session-briefing.d.ts +23 -0
  303. package/dist/runtime/session-briefing.d.ts.map +1 -0
  304. package/dist/runtime/session-briefing.js +140 -0
  305. package/dist/runtime/session-briefing.js.map +1 -0
  306. package/dist/runtime/types.d.ts +29 -2
  307. package/dist/runtime/types.d.ts.map +1 -1
  308. package/dist/runtime/vault-linking-ops.d.ts +13 -0
  309. package/dist/runtime/vault-linking-ops.d.ts.map +1 -0
  310. package/dist/runtime/vault-linking-ops.js +365 -0
  311. package/dist/runtime/vault-linking-ops.js.map +1 -0
  312. package/dist/vault/linking.d.ts +46 -0
  313. package/dist/vault/linking.d.ts.map +1 -0
  314. package/dist/vault/linking.js +275 -0
  315. package/dist/vault/linking.js.map +1 -0
  316. package/dist/vault/vault-types.d.ts +37 -0
  317. package/dist/vault/vault-types.d.ts.map +1 -1
  318. package/dist/vault/vault.d.ts +37 -0
  319. package/dist/vault/vault.d.ts.map +1 -1
  320. package/dist/vault/vault.js +152 -9
  321. package/dist/vault/vault.js.map +1 -1
  322. package/package.json +4 -1
  323. package/src/__tests__/admin-extra-ops.test.ts +1 -1
  324. package/src/__tests__/admin-ops.test.ts +2 -1
  325. package/src/__tests__/admin-setup-ops.test.ts +355 -0
  326. package/src/__tests__/async-infrastructure.test.ts +307 -0
  327. package/src/__tests__/cognee-client-gaps.test.ts +474 -0
  328. package/src/__tests__/cognee-hybrid-search.test.ts +492 -0
  329. package/src/__tests__/cognee-sync-manager-deep.test.ts +654 -0
  330. package/src/__tests__/cognee-sync-manager.test.ts +1 -0
  331. package/src/__tests__/core-ops.test.ts +9 -61
  332. package/src/__tests__/curator-extra-ops.test.ts +6 -2
  333. package/src/__tests__/curator-pipeline-e2e.test.ts +358 -0
  334. package/src/__tests__/domain-packs.test.ts +421 -0
  335. package/src/__tests__/flows.test.ts +604 -0
  336. package/src/__tests__/memory-extra-ops.test.ts +2 -2
  337. package/src/__tests__/planning-extra-ops.test.ts +2 -2
  338. package/src/__tests__/playbook-registry.test.ts +2 -2
  339. package/src/__tests__/playbook-seeder.test.ts +8 -8
  340. package/src/__tests__/playbook.test.ts +5 -5
  341. package/src/__tests__/second-brain-features.test.ts +583 -0
  342. package/src/__tests__/token-resolver.test.ts +79 -0
  343. package/src/agency/agency-manager.ts +217 -9
  344. package/src/agency/default-rules.ts +83 -0
  345. package/src/agency/types.ts +61 -0
  346. package/src/brain/brain.ts +110 -8
  347. package/src/brain/intelligence.ts +21 -2
  348. package/src/brain/knowledge-synthesizer.ts +218 -0
  349. package/src/brain/learning-radar.ts +340 -0
  350. package/src/brain/types.ts +16 -0
  351. package/src/capabilities/chain-mapping.ts +93 -0
  352. package/src/capabilities/index.ts +21 -0
  353. package/src/capabilities/registry.ts +290 -0
  354. package/src/capabilities/types.ts +143 -0
  355. package/src/context/context-engine.ts +114 -15
  356. package/src/context/types.ts +5 -0
  357. package/src/control/intent-router.ts +153 -2
  358. package/src/control/types.ts +10 -0
  359. package/src/curator/classifier.ts +88 -0
  360. package/src/curator/quality-gate.ts +129 -0
  361. package/src/domain-packs/index.ts +27 -0
  362. package/src/domain-packs/inject-rules.ts +74 -0
  363. package/src/domain-packs/knowledge-installer.ts +116 -0
  364. package/src/domain-packs/loader.ts +124 -0
  365. package/src/domain-packs/pack-runtime.ts +99 -0
  366. package/src/domain-packs/skills-installer.ts +56 -0
  367. package/src/domain-packs/token-resolver.ts +126 -0
  368. package/src/domain-packs/types.ts +229 -0
  369. package/src/engine/__tests__/register-engine.test.ts +104 -0
  370. package/src/engine/bin/soleri-engine.ts +218 -0
  371. package/src/engine/core-ops.ts +178 -0
  372. package/src/engine/index.ts +19 -0
  373. package/src/engine/register-engine.ts +385 -0
  374. package/src/engine/test-helpers.ts +83 -0
  375. package/src/events/event-bus.ts +58 -0
  376. package/src/flows/chain-runner.ts +369 -0
  377. package/src/flows/chain-types.ts +57 -0
  378. package/src/flows/context-router.ts +257 -0
  379. package/src/flows/dispatch-registry.ts +80 -0
  380. package/src/flows/epilogue.ts +65 -0
  381. package/src/flows/executor.ts +182 -0
  382. package/src/flows/gate-evaluator.ts +171 -0
  383. package/src/flows/index.ts +52 -0
  384. package/src/flows/loader.ts +63 -0
  385. package/src/flows/plan-builder.ts +250 -0
  386. package/src/flows/probes.ts +70 -0
  387. package/src/flows/types.ts +217 -0
  388. package/src/health/doctor-checks.ts +115 -0
  389. package/src/index.ts +68 -1
  390. package/src/intake/text-ingester.ts +234 -0
  391. package/src/intelligence/loader.ts +38 -0
  392. package/src/intelligence/types.ts +1 -0
  393. package/src/llm/llm-client.ts +38 -1
  394. package/src/llm/oauth-discovery.ts +169 -0
  395. package/src/packs/types.ts +19 -0
  396. package/src/planning/evidence-collector.ts +247 -0
  397. package/src/planning/planner.ts +11 -0
  398. package/src/playbooks/generic/onboarding.ts +79 -0
  399. package/src/playbooks/playbook-registry.ts +2 -0
  400. package/src/queue/job-queue.ts +281 -0
  401. package/src/queue/pipeline-runner.ts +149 -0
  402. package/src/runtime/admin-extra-ops.ts +14 -8
  403. package/src/runtime/admin-ops.ts +4 -4
  404. package/src/runtime/admin-setup-ops.ts +664 -0
  405. package/src/runtime/capture-ops.ts +40 -1
  406. package/src/runtime/chain-ops.ts +121 -0
  407. package/src/runtime/claude-md-helpers.ts +236 -0
  408. package/src/runtime/curator-extra-ops.ts +86 -3
  409. package/src/runtime/domain-ops.ts +71 -5
  410. package/src/runtime/facades/admin-facade.ts +4 -0
  411. package/src/runtime/facades/agency-facade.ts +68 -0
  412. package/src/runtime/facades/brain-facade.ts +142 -1
  413. package/src/runtime/facades/cognee-facade.ts +3 -1
  414. package/src/runtime/facades/control-facade.ts +45 -0
  415. package/src/runtime/facades/index.ts +12 -6
  416. package/src/runtime/facades/memory-facade.ts +20 -2
  417. package/src/runtime/facades/plan-facade.ts +2 -0
  418. package/src/runtime/facades/vault-facade.ts +30 -5
  419. package/src/runtime/intake-ops.ts +107 -5
  420. package/src/runtime/memory-extra-ops.ts +312 -4
  421. package/src/runtime/orchestrate-ops.ts +261 -65
  422. package/src/runtime/planning-extra-ops.ts +94 -0
  423. package/src/runtime/playbook-ops.ts +1 -1
  424. package/src/runtime/runtime.ts +164 -19
  425. package/src/runtime/session-briefing.ts +161 -0
  426. package/src/runtime/types.ts +29 -2
  427. package/src/runtime/vault-linking-ops.ts +452 -0
  428. package/src/vault/linking.ts +333 -0
  429. package/src/vault/vault-types.ts +46 -0
  430. package/src/vault/vault.ts +173 -11
@@ -189,7 +189,7 @@ describe('playbook_create op', () => {
189
189
  vaultPath: ':memory:',
190
190
  plansPath: join(plannerDir, 'plans.json'),
191
191
  });
192
- ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
192
+ ops = createSemanticFacades(runtime, 'test').flatMap((f) => f.ops);
193
193
  });
194
194
 
195
195
  afterEach(() => {
@@ -300,7 +300,7 @@ describe('playbook_match op', () => {
300
300
  vaultPath: ':memory:',
301
301
  plansPath: join(plannerDir, 'plans.json'),
302
302
  });
303
- ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
303
+ ops = createSemanticFacades(runtime, 'test').flatMap((f) => f.ops);
304
304
  });
305
305
 
306
306
  afterEach(() => {
@@ -356,7 +356,7 @@ describe('playbook_seed op', () => {
356
356
  vaultPath: ':memory:',
357
357
  plansPath: join(plannerDir, 'plans.json'),
358
358
  });
359
- ops = createSemanticFacades(runtime, 'test').flatMap(f => f.ops);
359
+ ops = createSemanticFacades(runtime, 'test').flatMap((f) => f.ops);
360
360
  });
361
361
 
362
362
  afterEach(() => {
@@ -371,7 +371,7 @@ describe('playbook_seed op', () => {
371
371
  errors: number;
372
372
  };
373
373
 
374
- expect(result.seeded).toBe(6);
374
+ expect(result.seeded).toBe(7);
375
375
  expect(result.skipped).toBe(0);
376
376
  expect(result.errors).toBe(0);
377
377
  });
@@ -384,6 +384,6 @@ describe('playbook_seed op', () => {
384
384
  };
385
385
 
386
386
  expect(result.seeded).toBe(0);
387
- expect(result.skipped).toBe(6);
387
+ expect(result.skipped).toBe(7);
388
388
  });
389
389
  });
@@ -0,0 +1,583 @@
1
+ /**
2
+ * E2E tests for Second Brain features (#39 milestone).
3
+ *
4
+ * Tests all 8 features end-to-end using a real in-memory vault + brain:
5
+ * 1. Two-pass vault retrieval (scan → load)
6
+ * 2. Session briefing
7
+ * 3. Evidence-based reconciliation
8
+ * 4. Routing feedback loop
9
+ * 5. Ambient learning radar
10
+ * 6. External knowledge ingestion (text)
11
+ * 7. Content synthesis
12
+ * 8. Composable skill chains
13
+ */
14
+
15
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
16
+ import { join } from 'node:path';
17
+ import { mkdtempSync, rmSync } from 'node:fs';
18
+ import { tmpdir } from 'node:os';
19
+ import { Vault } from '../vault/vault.js';
20
+ import { Brain } from '../brain/brain.js';
21
+ import { BrainIntelligence } from '../brain/intelligence.js';
22
+ import { Planner } from '../planning/planner.js';
23
+ import { Curator } from '../curator/curator.js';
24
+ import { IntentRouter } from '../control/intent-router.js';
25
+ import { LearningRadar } from '../brain/learning-radar.js';
26
+ import { KnowledgeSynthesizer } from '../brain/knowledge-synthesizer.js';
27
+ import { ChainRunner } from '../flows/chain-runner.js';
28
+ import { TextIngester } from '../intake/text-ingester.js';
29
+ import { collectGitEvidence } from '../planning/evidence-collector.js';
30
+ import type { IntelligenceEntry } from '../intelligence/types.js';
31
+
32
+ // ─── Shared Setup ────────────────────────────────────────────────────
33
+
34
+ let vault: Vault;
35
+ let brain: Brain;
36
+ let brainIntelligence: BrainIntelligence;
37
+ let planner: Planner;
38
+ let curator: Curator;
39
+ let intentRouter: IntentRouter;
40
+ let learningRadar: LearningRadar;
41
+ let synthesizer: KnowledgeSynthesizer;
42
+ let chainRunner: ChainRunner;
43
+ let textIngester: TextIngester;
44
+ let tempDir: string;
45
+
46
+ const SEED: IntelligenceEntry[] = [
47
+ {
48
+ id: 'pattern-retry',
49
+ type: 'pattern',
50
+ domain: 'architecture',
51
+ title: 'Retry with Exponential Backoff',
52
+ severity: 'critical',
53
+ description: 'Always use exponential backoff when retrying failed network requests.',
54
+ tags: ['networking', 'retry', 'resilience'],
55
+ context: 'HTTP clients, API gateways.',
56
+ why: 'Prevents thundering herd on service recovery.',
57
+ },
58
+ {
59
+ id: 'anti-pattern-polling',
60
+ type: 'anti-pattern',
61
+ domain: 'architecture',
62
+ title: 'Polling Without Timeout',
63
+ severity: 'critical',
64
+ description: 'Never poll an external service without a maximum timeout.',
65
+ tags: ['networking', 'polling', 'timeout'],
66
+ },
67
+ {
68
+ id: 'pattern-semantic-tokens',
69
+ type: 'pattern',
70
+ domain: 'design',
71
+ title: 'Semantic Token Priority',
72
+ severity: 'warning',
73
+ description: 'Use semantic tokens over primitive tokens for maintainability.',
74
+ tags: ['tokens', 'design-system', 'css'],
75
+ },
76
+ {
77
+ id: 'pattern-fts5',
78
+ type: 'pattern',
79
+ domain: 'architecture',
80
+ title: 'FTS5 Full-Text Search',
81
+ severity: 'suggestion',
82
+ description: 'Use SQLite FTS5 with porter tokenizer for text search in the vault.',
83
+ tags: ['search', 'sqlite', 'fts5'],
84
+ },
85
+ {
86
+ id: 'pattern-tdd',
87
+ type: 'pattern',
88
+ domain: 'testing',
89
+ title: 'Test-Driven Development',
90
+ severity: 'warning',
91
+ description: 'Write tests before implementation. RED → GREEN → REFACTOR cycle.',
92
+ tags: ['testing', 'tdd', 'quality'],
93
+ why: 'Catches design issues early and produces better APIs.',
94
+ },
95
+ ];
96
+
97
+ beforeAll(() => {
98
+ tempDir = mkdtempSync(join(tmpdir(), 'soleri-brain-test-'));
99
+ vault = new Vault(':memory:');
100
+ vault.seed(SEED);
101
+ brain = new Brain(vault);
102
+ brainIntelligence = new BrainIntelligence(vault, brain);
103
+ planner = new Planner(join(tempDir, 'plans.json'));
104
+ curator = new Curator(vault);
105
+ intentRouter = new IntentRouter(vault);
106
+ learningRadar = new LearningRadar(vault, brain);
107
+ synthesizer = new KnowledgeSynthesizer(brain, null); // No LLM — tests raw fallback
108
+ chainRunner = new ChainRunner(vault.getProvider());
109
+ textIngester = new TextIngester(vault, null); // No LLM — tests graceful degradation
110
+ });
111
+
112
+ afterAll(() => {
113
+ vault.close();
114
+ try {
115
+ rmSync(tempDir, { recursive: true });
116
+ } catch {
117
+ /* cleanup best-effort */
118
+ }
119
+ });
120
+
121
+ // ─── 1. Two-Pass Vault Retrieval ─────────────────────────────────────
122
+
123
+ describe('Two-pass vault retrieval (#205)', () => {
124
+ it('scanSearch returns lightweight results without full entry body', async () => {
125
+ const results = await brain.scanSearch('retry network');
126
+ expect(results.length).toBeGreaterThan(0);
127
+
128
+ const first = results[0];
129
+ expect(first.id).toBeDefined();
130
+ expect(first.title).toBeDefined();
131
+ expect(first.score).toBeGreaterThan(0);
132
+ expect(first.snippet).toBeDefined();
133
+ expect(first.tokenEstimate).toBeGreaterThan(0);
134
+ // Scan results should NOT have full entry fields
135
+ expect((first as Record<string, unknown>).description).toBeUndefined();
136
+ expect((first as Record<string, unknown>).context).toBeUndefined();
137
+ });
138
+
139
+ it('loadEntries returns full entries by ID', () => {
140
+ const entries = brain.loadEntries(['pattern-retry', 'pattern-fts5']);
141
+ expect(entries.length).toBe(2);
142
+ expect(entries[0].description).toBeDefined();
143
+ expect(entries[0].tags).toBeDefined();
144
+ });
145
+
146
+ it('loadEntries skips unknown IDs gracefully', () => {
147
+ const entries = brain.loadEntries(['pattern-retry', 'nonexistent-id']);
148
+ expect(entries.length).toBe(1);
149
+ expect(entries[0].id).toBe('pattern-retry');
150
+ });
151
+
152
+ it('scanSearch snippet is truncated to 120 chars', async () => {
153
+ const results = await brain.scanSearch('retry');
154
+ for (const r of results) {
155
+ expect(r.snippet.length).toBeLessThanOrEqual(123); // 120 + '...'
156
+ }
157
+ });
158
+ });
159
+
160
+ // ─── 2. Session Briefing ─────────────────────────────────────────────
161
+
162
+ describe('Session briefing (#202)', () => {
163
+ // Session briefing is an op — tested via the module imports directly
164
+ it('brainIntelligence.listSessions returns sessions', () => {
165
+ const sessions = brainIntelligence.listSessions({ limit: 5 });
166
+ // May be empty in a fresh vault, but shouldn't throw
167
+ expect(Array.isArray(sessions)).toBe(true);
168
+ });
169
+
170
+ it('planner.list returns plans array', () => {
171
+ const plans = planner.list();
172
+ expect(Array.isArray(plans)).toBe(true);
173
+ });
174
+
175
+ it('vault.getRecent returns recent entries', () => {
176
+ const recent = vault.getRecent(5);
177
+ expect(recent.length).toBeGreaterThan(0);
178
+ expect(recent[0].title).toBeDefined();
179
+ });
180
+
181
+ it('curator.healthAudit returns a score', () => {
182
+ const audit = curator.healthAudit();
183
+ expect(audit.score).toBeGreaterThanOrEqual(0);
184
+ expect(audit.score).toBeLessThanOrEqual(100);
185
+ });
186
+ });
187
+
188
+ // ─── 3. Evidence-Based Reconciliation ────────────────────────────────
189
+
190
+ describe('Evidence-based reconciliation (#206)', () => {
191
+ it('collectGitEvidence handles non-git directory gracefully', () => {
192
+ const plan = planner.create({
193
+ objective: 'Test plan',
194
+ scope: 'test',
195
+ decisions: [],
196
+ tasks: [{ title: 'Add retry logic', description: 'Implement retry' }],
197
+ });
198
+ planner.approve(plan.id);
199
+ planner.startExecution(plan.id);
200
+
201
+ // /tmp is not a git repo — should return empty evidence, not crash
202
+ const evidence = collectGitEvidence(plan, '/tmp');
203
+ expect(evidence.planId).toBe(plan.id);
204
+ expect(evidence.accuracy).toBeDefined();
205
+ expect(Array.isArray(evidence.taskEvidence)).toBe(true);
206
+ expect(Array.isArray(evidence.unplannedChanges)).toBe(true);
207
+ });
208
+
209
+ it('reports MISSING verdict when no git changes match tasks', () => {
210
+ const plan = planner.create({
211
+ objective: 'Another test',
212
+ scope: 'test',
213
+ decisions: [],
214
+ tasks: [
215
+ { title: 'Create FooWidget component', description: 'New widget' },
216
+ { title: 'Add unit tests for FooWidget', description: 'Tests' },
217
+ ],
218
+ });
219
+ planner.approve(plan.id);
220
+ planner.startExecution(plan.id);
221
+
222
+ const evidence = collectGitEvidence(plan, '/tmp');
223
+ // No git changes in /tmp → all tasks should be MISSING
224
+ for (const te of evidence.taskEvidence) {
225
+ expect(['MISSING', 'SKIPPED']).toContain(te.verdict);
226
+ }
227
+ });
228
+ });
229
+
230
+ // ─── 4. Routing Feedback Loop ────────────────────────────────────────
231
+
232
+ describe('Routing feedback loop (#209)', () => {
233
+ it('records routing feedback', () => {
234
+ const result = intentRouter.recordRoutingFeedback({
235
+ initialIntent: 'build',
236
+ actualIntent: 'fix',
237
+ confidence: 0.72,
238
+ correction: true,
239
+ });
240
+ expect(result.recorded).toBe(true);
241
+ expect(result.id).toBeGreaterThan(0);
242
+ });
243
+
244
+ it('records correct routing feedback', () => {
245
+ intentRouter.recordRoutingFeedback({
246
+ initialIntent: 'build',
247
+ actualIntent: 'build',
248
+ confidence: 0.85,
249
+ correction: false,
250
+ });
251
+ intentRouter.recordRoutingFeedback({
252
+ initialIntent: 'fix',
253
+ actualIntent: 'fix',
254
+ confidence: 0.9,
255
+ correction: false,
256
+ });
257
+ });
258
+
259
+ it('getRoutingAccuracy returns accuracy report', () => {
260
+ const report = intentRouter.getRoutingAccuracy(30);
261
+ expect(report.total).toBeGreaterThanOrEqual(3);
262
+ expect(report.accuracy).toBeGreaterThanOrEqual(0);
263
+ expect(report.accuracy).toBeLessThanOrEqual(100);
264
+ expect(report.corrections).toBeGreaterThanOrEqual(1);
265
+ expect(Array.isArray(report.commonMisroutes)).toBe(true);
266
+ expect(typeof report.confidenceCalibration).toBe('object');
267
+ });
268
+
269
+ it('tracks common misroutes', () => {
270
+ const report = intentRouter.getRoutingAccuracy(30);
271
+ const misroute = report.commonMisroutes.find((m) => m.from === 'build' && m.to === 'fix');
272
+ expect(misroute).toBeDefined();
273
+ expect(misroute!.count).toBeGreaterThanOrEqual(1);
274
+ });
275
+ });
276
+
277
+ // ─── 5. Ambient Learning Radar ───────────────────────────────────────
278
+
279
+ describe('Ambient learning radar (#208)', () => {
280
+ it('analyze with high confidence auto-captures', () => {
281
+ const result = learningRadar.analyze({
282
+ type: 'explicit_capture',
283
+ title: 'Always validate inputs',
284
+ description: 'User explicitly asked to remember this validation pattern.',
285
+ confidence: 0.95,
286
+ });
287
+ expect(result).not.toBeNull();
288
+ expect(result!.status).toBe('captured');
289
+ });
290
+
291
+ it('analyze with medium confidence queues as pending', () => {
292
+ const result = learningRadar.analyze({
293
+ type: 'correction',
294
+ title: 'Use execFileSync not execSync',
295
+ description: 'User corrected shell execution to avoid injection.',
296
+ });
297
+ expect(result).not.toBeNull();
298
+ // Default correction confidence is 0.75 → pending
299
+ expect(result!.status).toBe('pending');
300
+ });
301
+
302
+ it('analyze with low confidence logs only', () => {
303
+ const result = learningRadar.analyze({
304
+ type: 'pattern_success',
305
+ title: 'FTS5 search worked',
306
+ description: 'Vault search returned good results.',
307
+ confidence: 0.3,
308
+ });
309
+ expect(result).not.toBeNull();
310
+ expect(result!.status).toBe('logged');
311
+ });
312
+
313
+ it('getCandidates returns pending candidates', () => {
314
+ const candidates = learningRadar.getCandidates();
315
+ expect(candidates.length).toBeGreaterThanOrEqual(1);
316
+ expect(candidates[0].status).toBe('pending');
317
+ });
318
+
319
+ it('approve captures a pending candidate', () => {
320
+ const candidates = learningRadar.getCandidates();
321
+ const pending = candidates[0];
322
+ const result = learningRadar.approve(pending.id);
323
+ expect(result.captured).toBe(true);
324
+ expect(result.entryId).toBeDefined();
325
+ });
326
+
327
+ it('dismiss marks candidate as dismissed', () => {
328
+ // Create another pending
329
+ learningRadar.analyze({
330
+ type: 'workaround',
331
+ title: 'Workaround for stale cache',
332
+ description: 'Clear cache before rebuilding.',
333
+ });
334
+ const candidates = learningRadar.getCandidates();
335
+ const pending = candidates.find((c) => c.title.includes('stale cache'));
336
+ expect(pending).toBeDefined();
337
+ const result = learningRadar.dismiss(pending!.id);
338
+ expect(result.dismissed).toBe(true);
339
+ });
340
+
341
+ it('getStats returns radar statistics', () => {
342
+ const stats = learningRadar.getStats();
343
+ expect(stats.totalAnalyzed).toBeGreaterThanOrEqual(3);
344
+ expect(stats.autoCaptured).toBeGreaterThanOrEqual(1);
345
+ expect(stats.dismissed).toBeGreaterThanOrEqual(1);
346
+ });
347
+
348
+ it('flush captures pending candidates above threshold', () => {
349
+ // Add a high-confidence pending
350
+ learningRadar.analyze({
351
+ type: 'search_miss',
352
+ title: 'No vault entry for caching patterns',
353
+ description: 'Repeated search for caching returned 0 results.',
354
+ confidence: 0.85,
355
+ });
356
+ const result = learningRadar.flush(0.8);
357
+ expect(result.captured).toBeGreaterThanOrEqual(0);
358
+ expect(Array.isArray(result.capturedIds)).toBe(true);
359
+ });
360
+ });
361
+
362
+ // ─── 6. External Knowledge Ingestion ─────────────────────────────────
363
+
364
+ describe('External knowledge ingestion (#203)', () => {
365
+ it('ingestText returns graceful empty result without LLM', async () => {
366
+ const result = await textIngester.ingestText(
367
+ 'This is a test article about software architecture patterns.',
368
+ { type: 'article', title: 'Test Article' },
369
+ { domain: 'architecture' },
370
+ );
371
+ // No LLM → 0 ingested (graceful degradation)
372
+ expect(result.ingested).toBe(0);
373
+ expect(result.source.title).toBe('Test Article');
374
+ expect(result.source.type).toBe('article');
375
+ });
376
+
377
+ it('ingestUrl returns graceful empty result without LLM', async () => {
378
+ const result = await textIngester.ingestUrl('https://example.com');
379
+ expect(result.ingested).toBe(0);
380
+ expect(result.source.type).toBe('article');
381
+ });
382
+
383
+ it('ingestBatch processes multiple items', async () => {
384
+ const results = await textIngester.ingestBatch([
385
+ { text: 'Item 1', source: { type: 'notes', title: 'Note 1' } },
386
+ { text: 'Item 2', source: { type: 'transcript', title: 'Talk 2' } },
387
+ ]);
388
+ expect(results.length).toBe(2);
389
+ expect(results[0].source.title).toBe('Note 1');
390
+ expect(results[1].source.title).toBe('Talk 2');
391
+ });
392
+ });
393
+
394
+ // ─── 7. Content Synthesis ────────────────────────────────────────────
395
+
396
+ describe('Content synthesis (#207)', () => {
397
+ it('synthesize returns raw entries when no LLM available', async () => {
398
+ const result = await synthesizer.synthesize('retry networking', {
399
+ format: 'brief',
400
+ maxEntries: 5,
401
+ });
402
+ expect(result.query).toBe('retry networking');
403
+ expect(result.format).toBe('brief');
404
+ expect(result.entriesConsulted).toBeGreaterThan(0);
405
+ expect(result.content).toContain('Retry');
406
+ expect(result.sources.length).toBeGreaterThan(0);
407
+ expect(result.coverage).toBeGreaterThanOrEqual(0);
408
+ });
409
+
410
+ it('synthesize returns empty message for no results', async () => {
411
+ const result = await synthesizer.synthesize('quantum computing blockchain', {
412
+ format: 'outline',
413
+ });
414
+ expect(result.entriesConsulted).toBe(0);
415
+ expect(result.content).toContain('No vault entries found');
416
+ expect(result.gaps.length).toBeGreaterThanOrEqual(1);
417
+ });
418
+
419
+ it('synthesize includes source attribution', async () => {
420
+ const result = await synthesizer.synthesize('architecture patterns', {
421
+ format: 'talking-points',
422
+ maxEntries: 3,
423
+ });
424
+ for (const source of result.sources) {
425
+ expect(source.id).toBeDefined();
426
+ expect(source.title).toBeDefined();
427
+ expect(source.score).toBeGreaterThanOrEqual(0);
428
+ }
429
+ });
430
+
431
+ it('coverage score is between 0 and 100', async () => {
432
+ const result = await synthesizer.synthesize('testing tdd', {
433
+ format: 'post-draft',
434
+ });
435
+ expect(result.coverage).toBeGreaterThanOrEqual(0);
436
+ expect(result.coverage).toBeLessThanOrEqual(100);
437
+ });
438
+ });
439
+
440
+ // ─── 8. Composable Skill Chains ──────────────────────────────────────
441
+
442
+ describe('Composable skill chains (#204)', () => {
443
+ const mockDispatch = async (op: string, params: Record<string, unknown>): Promise<unknown> => {
444
+ // Simulate op results
445
+ if (op === 'search') return { results: [{ id: 'test', title: 'Test' }] };
446
+ if (op === 'capture_knowledge') return { captured: true, id: 'cap-1' };
447
+ return { ok: true, op, params };
448
+ };
449
+
450
+ it('executes a simple two-step chain', async () => {
451
+ const chain = {
452
+ id: 'test-chain',
453
+ name: 'Test Chain',
454
+ steps: [
455
+ { id: 'step1', op: 'search', params: { query: 'retry' }, output: 'searchResult' },
456
+ { id: 'step2', op: 'capture_knowledge', params: { title: 'From chain' } },
457
+ ],
458
+ };
459
+
460
+ const instance = await chainRunner.execute(chain, {}, mockDispatch);
461
+ expect(instance.status).toBe('completed');
462
+ expect(instance.stepsCompleted).toBe(2);
463
+ expect(instance.totalSteps).toBe(2);
464
+ expect(instance.stepOutputs.length).toBe(2);
465
+ });
466
+
467
+ it('resolves $variable references between steps', async () => {
468
+ let capturedParams: Record<string, unknown> = {};
469
+ const trackingDispatch = async (op: string, params: Record<string, unknown>) => {
470
+ if (op === 'step2-op') capturedParams = params;
471
+ return { value: 'hello from step1' };
472
+ };
473
+
474
+ const chain = {
475
+ id: 'var-chain',
476
+ steps: [
477
+ { id: 'step1', op: 'step1-op', output: 'step1Result' },
478
+ { id: 'step2', op: 'step2-op', params: { data: '$step1.value' } },
479
+ ],
480
+ };
481
+
482
+ await chainRunner.execute(chain, {}, trackingDispatch);
483
+ expect(capturedParams.data).toBe('hello from step1');
484
+ });
485
+
486
+ it('resolves $input references', async () => {
487
+ let capturedParams: Record<string, unknown> = {};
488
+ const trackingDispatch = async (op: string, params: Record<string, unknown>) => {
489
+ capturedParams = params;
490
+ return { ok: true };
491
+ };
492
+
493
+ const chain = {
494
+ id: 'input-chain',
495
+ steps: [{ id: 'step1', op: 'my-op', params: { url: '$input.targetUrl' } }],
496
+ };
497
+
498
+ await chainRunner.execute(chain, { targetUrl: 'https://example.com' }, trackingDispatch);
499
+ expect(capturedParams.url).toBe('https://example.com');
500
+ });
501
+
502
+ it('pauses on user-approval gate', async () => {
503
+ const chain = {
504
+ id: 'gate-chain',
505
+ steps: [
506
+ { id: 'step1', op: 'search', gate: 'user-approval' as const },
507
+ { id: 'step2', op: 'capture_knowledge' },
508
+ ],
509
+ };
510
+
511
+ const instance = await chainRunner.execute(chain, {}, mockDispatch);
512
+ expect(instance.status).toBe('paused');
513
+ expect(instance.pausedAtGate).toBe('step1');
514
+ expect(instance.stepsCompleted).toBe(1);
515
+ });
516
+
517
+ it('resumes a paused chain after approval', async () => {
518
+ const chain = {
519
+ id: 'resume-chain',
520
+ steps: [
521
+ { id: 'step1', op: 'search', gate: 'user-approval' as const },
522
+ { id: 'step2', op: 'capture_knowledge' },
523
+ ],
524
+ };
525
+
526
+ const paused = await chainRunner.execute(chain, {}, mockDispatch);
527
+ expect(paused.status).toBe('paused');
528
+
529
+ const resumed = await chainRunner.approve(paused.id, chain, mockDispatch);
530
+ expect(resumed.status).toBe('completed');
531
+ expect(resumed.stepsCompleted).toBe(2);
532
+ });
533
+
534
+ it('fails on auto-test gate when step returns error', async () => {
535
+ const failDispatch = async () => ({ error: 'build failed' });
536
+
537
+ const chain = {
538
+ id: 'fail-chain',
539
+ steps: [{ id: 'step1', op: 'build', gate: 'auto-test' as const }],
540
+ };
541
+
542
+ const instance = await chainRunner.execute(chain, {}, failDispatch);
543
+ expect(instance.status).toBe('failed');
544
+ });
545
+
546
+ it('getInstance returns persisted chain state', async () => {
547
+ const chain = {
548
+ id: 'persist-chain',
549
+ steps: [{ id: 'step1', op: 'search' }],
550
+ };
551
+
552
+ const instance = await chainRunner.execute(chain, { key: 'value' }, mockDispatch);
553
+ const loaded = chainRunner.getInstance(instance.id);
554
+ expect(loaded).not.toBeNull();
555
+ expect(loaded!.chainId).toBe('persist-chain');
556
+ expect(loaded!.status).toBe('completed');
557
+ expect(loaded!.input.key).toBe('value');
558
+ });
559
+
560
+ it('list returns all chain instances', () => {
561
+ const instances = chainRunner.list();
562
+ expect(instances.length).toBeGreaterThanOrEqual(1);
563
+ });
564
+
565
+ it('handles step failure gracefully', async () => {
566
+ const throwDispatch = async () => {
567
+ throw new Error('connection refused');
568
+ };
569
+
570
+ const chain = {
571
+ id: 'error-chain',
572
+ steps: [
573
+ { id: 'step1', op: 'failing-op' },
574
+ { id: 'step2', op: 'should-not-run' },
575
+ ],
576
+ };
577
+
578
+ const instance = await chainRunner.execute(chain, {}, throwDispatch);
579
+ expect(instance.status).toBe('failed');
580
+ expect(instance.stepsCompleted).toBe(0);
581
+ expect(instance.stepOutputs[0].status).toBe('failed');
582
+ });
583
+ });