@soleri/core 9.14.4 → 9.16.7

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 (355) hide show
  1. package/data/flows/deliver.flow.yaml +11 -0
  2. package/data/flows/design.flow.yaml +4 -14
  3. package/data/flows/enhance.flow.yaml +10 -0
  4. package/data/flows/explore.flow.yaml +16 -0
  5. package/data/flows/fix.flow.yaml +1 -1
  6. package/data/flows/review.flow.yaml +13 -4
  7. package/dist/brain/brain.d.ts +9 -0
  8. package/dist/brain/brain.d.ts.map +1 -1
  9. package/dist/brain/brain.js +11 -1
  10. package/dist/brain/brain.js.map +1 -1
  11. package/dist/brain/intelligence.d.ts.map +1 -1
  12. package/dist/brain/intelligence.js +24 -0
  13. package/dist/brain/intelligence.js.map +1 -1
  14. package/dist/brain/types.d.ts +1 -0
  15. package/dist/brain/types.d.ts.map +1 -1
  16. package/dist/capabilities/chain-mapping.d.ts.map +1 -1
  17. package/dist/capabilities/chain-mapping.js +5 -4
  18. package/dist/capabilities/chain-mapping.js.map +1 -1
  19. package/dist/capabilities/registry.d.ts +6 -0
  20. package/dist/capabilities/registry.d.ts.map +1 -1
  21. package/dist/capabilities/registry.js +3 -2
  22. package/dist/capabilities/registry.js.map +1 -1
  23. package/dist/chat/chat-session.d.ts +6 -0
  24. package/dist/chat/chat-session.d.ts.map +1 -1
  25. package/dist/chat/chat-session.js +68 -17
  26. package/dist/chat/chat-session.js.map +1 -1
  27. package/dist/context/context-engine.js +1 -1
  28. package/dist/context/context-engine.js.map +1 -1
  29. package/dist/curator/curator.d.ts +6 -0
  30. package/dist/curator/curator.d.ts.map +1 -1
  31. package/dist/curator/curator.js +138 -0
  32. package/dist/curator/curator.js.map +1 -1
  33. package/dist/curator/types.d.ts +10 -0
  34. package/dist/curator/types.d.ts.map +1 -1
  35. package/dist/engine/bin/soleri-engine.js +0 -0
  36. package/dist/engine/core-ops.d.ts.map +1 -1
  37. package/dist/engine/core-ops.js +38 -1
  38. package/dist/engine/core-ops.js.map +1 -1
  39. package/dist/flows/epilogue.d.ts +5 -1
  40. package/dist/flows/epilogue.d.ts.map +1 -1
  41. package/dist/flows/epilogue.js +11 -3
  42. package/dist/flows/epilogue.js.map +1 -1
  43. package/dist/flows/executor.d.ts.map +1 -1
  44. package/dist/flows/executor.js +13 -5
  45. package/dist/flows/executor.js.map +1 -1
  46. package/dist/flows/index.d.ts +1 -2
  47. package/dist/flows/index.d.ts.map +1 -1
  48. package/dist/flows/index.js +1 -0
  49. package/dist/flows/index.js.map +1 -1
  50. package/dist/flows/plan-builder.d.ts +17 -1
  51. package/dist/flows/plan-builder.d.ts.map +1 -1
  52. package/dist/flows/plan-builder.js +67 -6
  53. package/dist/flows/plan-builder.js.map +1 -1
  54. package/dist/flows/probes.d.ts +1 -1
  55. package/dist/flows/probes.d.ts.map +1 -1
  56. package/dist/flows/probes.js +15 -3
  57. package/dist/flows/probes.js.map +1 -1
  58. package/dist/flows/types.d.ts +47 -20
  59. package/dist/flows/types.d.ts.map +1 -1
  60. package/dist/flows/types.js +6 -1
  61. package/dist/flows/types.js.map +1 -1
  62. package/dist/index.d.ts +10 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +9 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/intake/content-classifier.d.ts +10 -4
  67. package/dist/intake/content-classifier.d.ts.map +1 -1
  68. package/dist/intake/content-classifier.js +19 -5
  69. package/dist/intake/content-classifier.js.map +1 -1
  70. package/dist/intake/text-ingester.d.ts +18 -0
  71. package/dist/intake/text-ingester.d.ts.map +1 -1
  72. package/dist/intake/text-ingester.js +37 -13
  73. package/dist/intake/text-ingester.js.map +1 -1
  74. package/dist/packs/pack-installer.d.ts.map +1 -1
  75. package/dist/packs/pack-installer.js +28 -2
  76. package/dist/packs/pack-installer.js.map +1 -1
  77. package/dist/planning/planner-types.d.ts +2 -0
  78. package/dist/planning/planner-types.d.ts.map +1 -1
  79. package/dist/planning/planner.d.ts +4 -0
  80. package/dist/planning/planner.d.ts.map +1 -1
  81. package/dist/planning/planner.js +50 -4
  82. package/dist/planning/planner.js.map +1 -1
  83. package/dist/playbooks/playbook-executor.d.ts +10 -1
  84. package/dist/playbooks/playbook-executor.d.ts.map +1 -1
  85. package/dist/playbooks/playbook-executor.js +8 -2
  86. package/dist/playbooks/playbook-executor.js.map +1 -1
  87. package/dist/playbooks/playbook-types.d.ts +8 -0
  88. package/dist/playbooks/playbook-types.d.ts.map +1 -1
  89. package/dist/plugins/types.d.ts +2 -2
  90. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  91. package/dist/runtime/admin-extra-ops.js +30 -0
  92. package/dist/runtime/admin-extra-ops.js.map +1 -1
  93. package/dist/runtime/admin-ops.d.ts.map +1 -1
  94. package/dist/runtime/admin-ops.js +60 -21
  95. package/dist/runtime/admin-ops.js.map +1 -1
  96. package/dist/runtime/admin-setup-ops.d.ts +11 -0
  97. package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
  98. package/dist/runtime/admin-setup-ops.js +146 -37
  99. package/dist/runtime/admin-setup-ops.js.map +1 -1
  100. package/dist/runtime/capture-ops.d.ts.map +1 -1
  101. package/dist/runtime/capture-ops.js +38 -12
  102. package/dist/runtime/capture-ops.js.map +1 -1
  103. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  104. package/dist/runtime/facades/brain-facade.js +16 -4
  105. package/dist/runtime/facades/brain-facade.js.map +1 -1
  106. package/dist/runtime/facades/context-facade.d.ts.map +1 -1
  107. package/dist/runtime/facades/context-facade.js +9 -3
  108. package/dist/runtime/facades/context-facade.js.map +1 -1
  109. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  110. package/dist/runtime/facades/memory-facade.js +20 -7
  111. package/dist/runtime/facades/memory-facade.js.map +1 -1
  112. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
  113. package/dist/runtime/facades/orchestrate-facade.js +40 -1
  114. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  115. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  116. package/dist/runtime/facades/plan-facade.js +113 -4
  117. package/dist/runtime/facades/plan-facade.js.map +1 -1
  118. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  119. package/dist/runtime/facades/vault-facade.js +24 -3
  120. package/dist/runtime/facades/vault-facade.js.map +1 -1
  121. package/dist/runtime/orchestrate-ops.d.ts +21 -0
  122. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  123. package/dist/runtime/orchestrate-ops.js +132 -38
  124. package/dist/runtime/orchestrate-ops.js.map +1 -1
  125. package/dist/runtime/runtime.d.ts.map +1 -1
  126. package/dist/runtime/runtime.js +16 -0
  127. package/dist/runtime/runtime.js.map +1 -1
  128. package/dist/runtime/schema-helpers.d.ts.map +1 -1
  129. package/dist/runtime/schema-helpers.js +4 -0
  130. package/dist/runtime/schema-helpers.js.map +1 -1
  131. package/dist/runtime/types.d.ts +19 -0
  132. package/dist/runtime/types.d.ts.map +1 -1
  133. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  134. package/dist/runtime/vault-linking-ops.js +16 -3
  135. package/dist/runtime/vault-linking-ops.js.map +1 -1
  136. package/dist/scheduler/cron-validator.d.ts +15 -0
  137. package/dist/scheduler/cron-validator.d.ts.map +1 -0
  138. package/dist/scheduler/cron-validator.js +93 -0
  139. package/dist/scheduler/cron-validator.js.map +1 -0
  140. package/dist/scheduler/platform-linux.d.ts +14 -0
  141. package/dist/scheduler/platform-linux.d.ts.map +1 -0
  142. package/dist/scheduler/platform-linux.js +107 -0
  143. package/dist/scheduler/platform-linux.js.map +1 -0
  144. package/dist/scheduler/platform-macos.d.ts +15 -0
  145. package/dist/scheduler/platform-macos.d.ts.map +1 -0
  146. package/dist/scheduler/platform-macos.js +131 -0
  147. package/dist/scheduler/platform-macos.js.map +1 -0
  148. package/dist/scheduler/scheduler-ops.d.ts +14 -0
  149. package/dist/scheduler/scheduler-ops.d.ts.map +1 -0
  150. package/dist/scheduler/scheduler-ops.js +77 -0
  151. package/dist/scheduler/scheduler-ops.js.map +1 -0
  152. package/dist/scheduler/scheduler.d.ts +55 -0
  153. package/dist/scheduler/scheduler.d.ts.map +1 -0
  154. package/dist/scheduler/scheduler.js +144 -0
  155. package/dist/scheduler/scheduler.js.map +1 -0
  156. package/dist/scheduler/types.d.ts +48 -0
  157. package/dist/scheduler/types.d.ts.map +1 -0
  158. package/dist/scheduler/types.js +6 -0
  159. package/dist/scheduler/types.js.map +1 -0
  160. package/dist/skills/sync-skills.d.ts +11 -0
  161. package/dist/skills/sync-skills.d.ts.map +1 -1
  162. package/dist/skills/sync-skills.js +132 -38
  163. package/dist/skills/sync-skills.js.map +1 -1
  164. package/dist/skills/validate-skills.d.ts +32 -0
  165. package/dist/skills/validate-skills.d.ts.map +1 -0
  166. package/dist/skills/validate-skills.js +396 -0
  167. package/dist/skills/validate-skills.js.map +1 -0
  168. package/dist/utils/worktree-reaper.d.ts +38 -0
  169. package/dist/utils/worktree-reaper.d.ts.map +1 -0
  170. package/dist/utils/worktree-reaper.js +85 -0
  171. package/dist/utils/worktree-reaper.js.map +1 -0
  172. package/dist/vault/default-canonical-tags.d.ts +15 -0
  173. package/dist/vault/default-canonical-tags.d.ts.map +1 -0
  174. package/dist/vault/default-canonical-tags.js +65 -0
  175. package/dist/vault/default-canonical-tags.js.map +1 -0
  176. package/dist/vault/scope-detector.d.ts.map +1 -1
  177. package/dist/vault/scope-detector.js +37 -4
  178. package/dist/vault/scope-detector.js.map +1 -1
  179. package/dist/vault/tag-normalizer.d.ts +42 -0
  180. package/dist/vault/tag-normalizer.d.ts.map +1 -0
  181. package/dist/vault/tag-normalizer.js +157 -0
  182. package/dist/vault/tag-normalizer.js.map +1 -0
  183. package/dist/vault/vault-entries.d.ts.map +1 -1
  184. package/dist/vault/vault-entries.js +3 -1
  185. package/dist/vault/vault-entries.js.map +1 -1
  186. package/package.json +5 -1
  187. package/src/__tests__/embeddings.test.ts +3 -3
  188. package/src/agency/agency-manager.test.ts +4 -4
  189. package/src/agency/default-rules.test.ts +0 -13
  190. package/src/brain/brain-intelligence.test.ts +0 -5
  191. package/src/brain/brain.ts +25 -1
  192. package/src/brain/intelligence.ts +25 -0
  193. package/src/brain/second-brain-features.test.ts +2 -14
  194. package/src/brain/types.ts +1 -0
  195. package/src/capabilities/chain-mapping.test.ts +1 -6
  196. package/src/capabilities/chain-mapping.ts +6 -4
  197. package/src/capabilities/registry.test.ts +1 -1
  198. package/src/capabilities/registry.ts +9 -2
  199. package/src/chat/agent-loop.test.ts +1 -1
  200. package/src/chat/chat-enhanced.test.ts +0 -8
  201. package/src/chat/chat-session.ts +75 -17
  202. package/src/chat/chat-transport.test.ts +31 -1
  203. package/src/claudemd/compose.test.ts +0 -5
  204. package/src/context/context-engine.test.ts +0 -1
  205. package/src/context/context-engine.ts +1 -1
  206. package/src/control/intent-router.test.ts +2 -2
  207. package/src/curator/curator.ts +180 -0
  208. package/src/curator/tag-manager.test.ts +0 -4
  209. package/src/curator/types.ts +10 -0
  210. package/src/domain-packs/types.test.ts +0 -5
  211. package/src/dream/dream.test.ts +0 -7
  212. package/src/enforcement/registry.test.ts +2 -2
  213. package/src/engine/core-ops.test.ts +4 -22
  214. package/src/engine/core-ops.ts +36 -1
  215. package/src/engine/module-manifest.test.ts +1 -31
  216. package/src/engine/register-engine.test.ts +3 -33
  217. package/src/errors/retry.test.ts +3 -1
  218. package/src/flows/chain-runner.test.ts +0 -6
  219. package/src/flows/context-router.test.ts +3 -3
  220. package/src/flows/epilogue.test.ts +40 -2
  221. package/src/flows/epilogue.ts +11 -2
  222. package/src/flows/executor.test.ts +48 -2
  223. package/src/flows/executor.ts +15 -5
  224. package/src/flows/index.ts +1 -3
  225. package/src/flows/plan-builder.test.ts +201 -0
  226. package/src/flows/plan-builder.ts +81 -5
  227. package/src/flows/probes.ts +17 -3
  228. package/src/flows/types.ts +31 -2
  229. package/src/health/health-registry.test.ts +3 -1
  230. package/src/index.ts +24 -0
  231. package/src/intake/content-classifier.ts +22 -4
  232. package/src/intake/dedup-gate.test.ts +2 -6
  233. package/src/intake/text-ingester.test.ts +3 -4
  234. package/src/intake/text-ingester.ts +61 -12
  235. package/src/llm/llm-client.test.ts +1 -1
  236. package/src/llm/utils.test.ts +1 -1
  237. package/src/migrations/migration-runner.test.ts +0 -1
  238. package/src/operator/operator-context-store.test.ts +0 -13
  239. package/src/operator/operator-profile.test.ts +2 -20
  240. package/src/packs/pack-installer.ts +28 -2
  241. package/src/packs/pack-system.test.ts +2 -2
  242. package/src/persona/defaults.test.ts +19 -19
  243. package/src/planning/gap-passes.test.ts +0 -46
  244. package/src/planning/gap-patterns.test.ts +0 -42
  245. package/src/planning/goal-ancestry.test.ts +3 -1
  246. package/src/planning/plan-lifecycle.test.ts +15 -7
  247. package/src/planning/planner-types.ts +2 -0
  248. package/src/planning/planner.test.ts +86 -90
  249. package/src/planning/planner.ts +56 -4
  250. package/src/planning/reconciliation-engine.test.ts +3 -10
  251. package/src/planning/task-complexity-assessor.test.ts +0 -5
  252. package/src/planning/task-verifier.test.ts +3 -1
  253. package/src/playbooks/generic/generic-playbooks.test.ts +0 -28
  254. package/src/playbooks/index.test.ts +0 -55
  255. package/src/playbooks/playbook-executor.test.ts +76 -0
  256. package/src/playbooks/playbook-executor.ts +24 -3
  257. package/src/playbooks/playbook-types.ts +8 -0
  258. package/src/plugins/plugin-registry.test.ts +6 -2
  259. package/src/project/project-registry.test.ts +2 -0
  260. package/src/queue/async-infrastructure.test.ts +6 -4
  261. package/src/queue/job-queue.test.ts +13 -7
  262. package/src/runtime/admin-extra-ops.test.ts +35 -30
  263. package/src/runtime/admin-extra-ops.ts +30 -0
  264. package/src/runtime/admin-ops.test.ts +0 -4
  265. package/src/runtime/admin-ops.ts +63 -21
  266. package/src/runtime/admin-setup-ops.test.ts +229 -13
  267. package/src/runtime/admin-setup-ops.ts +145 -36
  268. package/src/runtime/archive-ops.test.ts +0 -28
  269. package/src/runtime/branching-ops.test.ts +0 -17
  270. package/src/runtime/capture-ops.test.ts +41 -16
  271. package/src/runtime/capture-ops.ts +78 -46
  272. package/src/runtime/chain-ops.test.ts +0 -21
  273. package/src/runtime/facades/admin-facade.test.ts +0 -34
  274. package/src/runtime/facades/agency-facade.test.ts +0 -39
  275. package/src/runtime/facades/archive-facade.test.ts +0 -43
  276. package/src/runtime/facades/brain-facade.test.ts +8 -99
  277. package/src/runtime/facades/brain-facade.ts +29 -12
  278. package/src/runtime/facades/branching-facade.test.ts +30 -17
  279. package/src/runtime/facades/chat-facade.test.ts +0 -91
  280. package/src/runtime/facades/chat-service-ops.test.ts +0 -24
  281. package/src/runtime/facades/chat-session-ops.test.ts +0 -12
  282. package/src/runtime/facades/chat-transport-ops.test.ts +0 -23
  283. package/src/runtime/facades/context-facade.test.ts +0 -17
  284. package/src/runtime/facades/context-facade.ts +11 -4
  285. package/src/runtime/facades/control-facade.test.ts +0 -30
  286. package/src/runtime/facades/curator-facade.test.ts +0 -33
  287. package/src/runtime/facades/intake-facade.test.ts +0 -33
  288. package/src/runtime/facades/links-facade.test.ts +0 -37
  289. package/src/runtime/facades/loop-facade.test.ts +0 -26
  290. package/src/runtime/facades/memory-facade.test.ts +0 -18
  291. package/src/runtime/facades/memory-facade.ts +27 -11
  292. package/src/runtime/facades/operator-facade.test.ts +0 -31
  293. package/src/runtime/facades/orchestrate-facade.test.ts +0 -21
  294. package/src/runtime/facades/orchestrate-facade.ts +39 -1
  295. package/src/runtime/facades/plan-facade.test.ts +7 -32
  296. package/src/runtime/facades/plan-facade.ts +137 -4
  297. package/src/runtime/facades/review-facade.test.ts +1 -49
  298. package/src/runtime/facades/sync-facade.test.ts +24 -41
  299. package/src/runtime/facades/tier-facade.test.ts +30 -22
  300. package/src/runtime/facades/vault-facade.test.ts +0 -41
  301. package/src/runtime/facades/vault-facade.ts +26 -3
  302. package/src/runtime/grading-ops.test.ts +0 -27
  303. package/src/runtime/intake-ops.test.ts +0 -19
  304. package/src/runtime/loop-ops.test.ts +0 -48
  305. package/src/runtime/memory-cross-project-ops.test.ts +0 -14
  306. package/src/runtime/memory-extra-ops.test.ts +4 -8
  307. package/src/runtime/orchestrate-ops.test.ts +238 -19
  308. package/src/runtime/orchestrate-ops.ts +166 -41
  309. package/src/runtime/pack-ops.test.ts +0 -26
  310. package/src/runtime/planning-extra-ops.test.ts +2 -14
  311. package/src/runtime/playbook-ops-execution.test.ts +9 -20
  312. package/src/runtime/playbook-ops.test.ts +4 -67
  313. package/src/runtime/review-ops.test.ts +0 -15
  314. package/src/runtime/runtime.ts +18 -0
  315. package/src/runtime/schema-helpers.ts +4 -0
  316. package/src/runtime/sync-ops.test.ts +0 -18
  317. package/src/runtime/tier-ops.test.ts +0 -21
  318. package/src/runtime/types.ts +19 -0
  319. package/src/runtime/vault-extra-ops.test.ts +0 -12
  320. package/src/runtime/vault-linking-ops.test.ts +0 -4
  321. package/src/runtime/vault-linking-ops.ts +26 -8
  322. package/src/runtime/vault-sharing-ops.test.ts +0 -9
  323. package/src/scheduler/cron-validator.ts +101 -0
  324. package/src/scheduler/platform-linux.ts +122 -0
  325. package/src/scheduler/platform-macos.ts +150 -0
  326. package/src/scheduler/scheduler-ops.ts +77 -0
  327. package/src/scheduler/scheduler.test.ts +247 -0
  328. package/src/scheduler/scheduler.ts +174 -0
  329. package/src/scheduler/types.ts +52 -0
  330. package/src/skills/__tests__/sync-skills.test.ts +6 -17
  331. package/src/skills/global-claude-md.test.ts +113 -0
  332. package/src/skills/sync-skills.ts +143 -35
  333. package/src/skills/validate-skills.test.ts +206 -0
  334. package/src/skills/validate-skills.ts +470 -0
  335. package/src/telemetry/telemetry.test.ts +1 -0
  336. package/src/transport/http-server.test.ts +3 -0
  337. package/src/transport/session-manager.test.ts +3 -1
  338. package/src/transport/token-auth.test.ts +6 -9
  339. package/src/transport/ws-server.test.ts +10 -2
  340. package/src/utils/worktree-reaper.ts +113 -0
  341. package/src/vault/__tests__/vault-characterization.test.ts +0 -108
  342. package/src/vault/default-canonical-tags.ts +64 -0
  343. package/src/vault/linking.test.ts +0 -2
  344. package/src/vault/playbook.test.ts +4 -1
  345. package/src/vault/scope-detector.test.ts +3 -1
  346. package/src/vault/scope-detector.ts +42 -4
  347. package/src/vault/tag-normalizer.test.ts +214 -0
  348. package/src/vault/tag-normalizer.ts +188 -0
  349. package/src/vault/vault-connect.test.ts +1 -1
  350. package/src/vault/vault-entries.ts +3 -1
  351. package/src/vault/vault.test.ts +23 -8
  352. package/dist/embeddings/index.d.ts +0 -5
  353. package/dist/embeddings/index.d.ts.map +0 -1
  354. package/dist/embeddings/index.js +0 -3
  355. package/dist/embeddings/index.js.map +0 -1
@@ -22,18 +22,27 @@ export async function runEpilogue(
22
22
  probes: ProbeResults,
23
23
  projectPath: string,
24
24
  summary: string,
25
+ planContext?: { intent?: string; objective?: string; domain?: string },
25
26
  ): Promise<{ captured: boolean; sessionId?: string }> {
26
27
  let captured = false;
27
28
  let sessionId: string | undefined;
28
29
 
29
30
  // Capture knowledge to vault
30
31
  if (probes.vault) {
32
+ const intent = planContext?.intent?.toUpperCase() ?? 'FLOW';
33
+ const objective = planContext?.objective ?? summary;
34
+ const title = `${intent} execution — ${objective}`.slice(0, 120);
35
+ const tags = [
36
+ 'auto-captured',
37
+ intent.toLowerCase(),
38
+ ...(planContext?.domain ? [planContext.domain] : []),
39
+ ];
31
40
  try {
32
41
  await dispatch('capture_knowledge', {
33
- title: 'Flow execution summary',
42
+ title,
34
43
  content: summary,
35
44
  type: 'workflow',
36
- tags: ['flow-engine', 'auto-captured'],
45
+ tags,
37
46
  projectPath,
38
47
  });
39
48
  captured = true;
@@ -230,9 +230,9 @@ describe('FlowExecutor', () => {
230
230
  expect(result.planId).toBe('test-plan');
231
231
  expect(result.totalSteps).toBe(2);
232
232
  expect(result.stepsCompleted).toBe(2);
233
- expect(result.durationMs).toBeGreaterThanOrEqual(0);
233
+ expect(typeof result.durationMs).toBe('number');
234
234
  expect(result.stepResults).toHaveLength(2);
235
- expect(result.stepResults[0].durationMs).toBeGreaterThanOrEqual(0);
235
+ expect(typeof result.stepResults[0].durationMs).toBe('number');
236
236
  });
237
237
 
238
238
  it('returns failed status when a step has a STOP gate that fails', async () => {
@@ -260,4 +260,50 @@ describe('FlowExecutor', () => {
260
260
  expect(result.stepResults[0].gateResult?.message).toBe('Blocked');
261
261
  });
262
262
  });
263
+
264
+ describe('step context (output → input flow)', () => {
265
+ it('passes prior step outputs as context to subsequent steps', async () => {
266
+ const received: Array<Record<string, unknown>> = [];
267
+
268
+ const dispatch = vi.fn(async (tool: string, params: Record<string, unknown>) => {
269
+ received.push({ tool, context: params.context });
270
+ return {
271
+ tool,
272
+ status: 'ok',
273
+ data: { 'vault-patterns': ['pattern-A', 'pattern-B'] },
274
+ };
275
+ });
276
+
277
+ const executor = new FlowExecutor(dispatch);
278
+ const plan = makePlan([
279
+ step('search-vault', ['vault.search'], { output: ['vault-patterns'] }),
280
+ step('brainstorm', ['brain.recommend']),
281
+ ]);
282
+
283
+ await executor.execute(plan);
284
+
285
+ // Step 1 receives empty context
286
+ expect(received[0].context).toEqual({});
287
+
288
+ // Step 2 receives vault-patterns from step 1
289
+ expect(received[1].context).toEqual({ 'vault-patterns': ['pattern-A', 'pattern-B'] });
290
+ });
291
+
292
+ it('steps with no output declaration do not pollute context', async () => {
293
+ const received: Array<Record<string, unknown>> = [];
294
+
295
+ const dispatch = vi.fn(async (tool: string, params: Record<string, unknown>) => {
296
+ received.push({ tool, context: params.context });
297
+ return { tool, status: 'ok', data: { 'some-key': 'value' } };
298
+ });
299
+
300
+ const executor = new FlowExecutor(dispatch);
301
+ const plan = makePlan([step('s1', ['tool-a']), step('s2', ['tool-b'])]);
302
+
303
+ await executor.execute(plan);
304
+
305
+ // No output declared on s1 — context stays empty for s2
306
+ expect(received[1].context).toEqual({});
307
+ });
308
+ });
263
309
  });
@@ -104,6 +104,9 @@ export class FlowExecutor {
104
104
  let branchIterations = 0;
105
105
  let currentIndex = 0;
106
106
 
107
+ // Accumulated outputs from completed steps — passed as context to subsequent dispatches
108
+ const stepContext: Record<string, unknown> = {};
109
+
107
110
  // Set up persistence if configured
108
111
  let runDir: string | undefined;
109
112
  let manifest: PlanRunManifest | undefined;
@@ -118,12 +121,13 @@ export class FlowExecutor {
118
121
  step.status = 'running';
119
122
 
120
123
  const toolResults: StepResult['toolResults'] = {};
124
+ const dispatchParams = { stepId: step.id, planId: plan.planId, context: { ...stepContext } };
121
125
 
122
126
  try {
123
127
  if (step.parallel && step.tools.length > 1) {
124
128
  // Execute tools in parallel
125
129
  const results = await Promise.allSettled(
126
- step.tools.map((tool) => this.dispatch(tool, { stepId: step.id, planId: plan.planId })),
130
+ step.tools.map((tool) => this.dispatch(tool, dispatchParams)),
127
131
  );
128
132
  for (let i = 0; i < step.tools.length; i++) {
129
133
  const toolName = step.tools[i];
@@ -144,10 +148,7 @@ export class FlowExecutor {
144
148
  // Execute tools sequentially
145
149
  for (const toolName of step.tools) {
146
150
  try {
147
- toolResults[toolName] = await this.dispatch(toolName, {
148
- stepId: step.id,
149
- planId: plan.planId,
150
- });
151
+ toolResults[toolName] = await this.dispatch(toolName, dispatchParams);
151
152
  } catch (_err) {
152
153
  toolResults[toolName] = {
153
154
  tool: toolName,
@@ -180,6 +181,15 @@ export class FlowExecutor {
180
181
  }
181
182
  }
182
183
 
184
+ // Accumulate declared step outputs into stepContext for subsequent steps
185
+ if (step.output) {
186
+ for (const outputKey of step.output) {
187
+ if (outputKey in flatData) {
188
+ stepContext[outputKey] = flatData[outputKey];
189
+ }
190
+ }
191
+ }
192
+
183
193
  const verdict = evaluateGate(step.gate, flatData);
184
194
 
185
195
  const stepResult: StepResult = {
@@ -6,8 +6,6 @@
6
6
  export type {
7
7
  Flow,
8
8
  FlowStep,
9
- Gate,
10
- GateAction,
11
9
  ProbeName,
12
10
  ProbeResults,
13
11
  PlanStep,
@@ -38,7 +36,7 @@ export {
38
36
 
39
37
  // Context router
40
38
  export { detectContext, applyContextOverrides, getFlowOverrides } from './context-router.js';
41
- export type { ContextOverride } from './context-router.js';
39
+ // ContextOverride is intentionally unexported internal use only
42
40
 
43
41
  // Gate evaluator
44
42
  export { evaluateGate, evaluateCondition, extractScore, resolvePath } from './gate-evaluator.js';
@@ -0,0 +1,201 @@
1
+ /**
2
+ * plan-builder — colocated contract tests.
3
+ *
4
+ * Contract:
5
+ * - buildPlan() returns blocked:true with zero steps when a blocking capability's probe fails
6
+ * - buildPlan() skips (not blocks) steps whose optional probes are unavailable
7
+ * - buildPlan() builds a normal plan when all blocking capabilities are available
8
+ * - capabilityToProbe() maps known capability ID prefixes to probe names
9
+ * - capabilityToProbe() returns undefined for unmapped capabilities (no spurious blocking)
10
+ * - buildPlan() attaches vault constraints as recommendations (not gate steps)
11
+ * - buildPlan() marks mandatory entries and anti-patterns as mandatory:true in recommendations
12
+ * - buildPlan() includes recommendations in blocked plans
13
+ * - buildPlan() does not inject vault-gate-* steps
14
+ */
15
+
16
+ import { describe, it, expect, vi } from 'vitest';
17
+ import { buildPlan, capabilityToProbe, type VaultConstraint } from './plan-builder.js';
18
+ import type { AgentRuntime } from '../runtime/types.js';
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Helpers
22
+ // ---------------------------------------------------------------------------
23
+
24
+ function makeRuntime(vaultAvailable: boolean, brainAvailable = false): AgentRuntime {
25
+ return {
26
+ vault: {
27
+ stats: vi.fn(() =>
28
+ vaultAvailable
29
+ ? { totalEntries: 10 }
30
+ : (() => {
31
+ throw new Error('vault down');
32
+ })(),
33
+ ),
34
+ },
35
+ brain: {
36
+ getVocabularySize: vi.fn(() => (brainAvailable ? 5 : 0)),
37
+ },
38
+ projectRegistry: {
39
+ list: vi.fn(() => []),
40
+ },
41
+ } as unknown as AgentRuntime;
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // capabilityToProbe unit tests
46
+ // ---------------------------------------------------------------------------
47
+
48
+ describe('capabilityToProbe', () => {
49
+ it('maps vault.* capabilities to the vault probe', () => {
50
+ expect(capabilityToProbe('vault.search')).toBe('vault');
51
+ expect(capabilityToProbe('vault.load')).toBe('vault');
52
+ });
53
+
54
+ it('maps brain.* capabilities to the brain probe', () => {
55
+ expect(capabilityToProbe('brain.recommend')).toBe('brain');
56
+ });
57
+
58
+ it('returns undefined for capabilities with no probe mapping — unknown cap does not block', () => {
59
+ // An unmapped capability must never trigger a blocking halt.
60
+ // If this returned a valid probe name, unrelated capabilities would silently block flows.
61
+ expect(capabilityToProbe('auth.validate')).toBeUndefined();
62
+ expect(capabilityToProbe('unknown.op')).toBeUndefined();
63
+ expect(capabilityToProbe('')).toBeUndefined();
64
+ });
65
+ });
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // buildPlan blocking behaviour
69
+ // ---------------------------------------------------------------------------
70
+
71
+ describe('buildPlan — blocking capability enforcement', () => {
72
+ it('returns blocked:true with zero steps when vault is down and vault.search is blocking', async () => {
73
+ // vault.search is in the blocking list of all 8 flows.
74
+ // When vault probe fails, the plan must halt — not silently skip steps.
75
+ const runtime = makeRuntime(false);
76
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
77
+
78
+ expect(plan.blocked).toBe(true);
79
+ expect(plan.steps).toHaveLength(0);
80
+ expect(plan.warnings[0]).toMatch(/Blocked/);
81
+ expect(plan.warnings[0]).toMatch(/vault\.search/);
82
+ });
83
+
84
+ it('builds a normal plan when vault is available', async () => {
85
+ // Blocking check must pass through when the probe is healthy.
86
+ // If blocking fired regardless of probe state, no plan would ever build.
87
+ const runtime = makeRuntime(true);
88
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
89
+
90
+ expect(plan.blocked).toBeUndefined();
91
+ expect(plan.steps.length).toBeGreaterThanOrEqual(1);
92
+ });
93
+
94
+ it('skips (not blocks) steps whose required probe is missing but not in blocking list', async () => {
95
+ // brain is not in the blocking list — its absence should skip brain-dependent
96
+ // steps with a warning, not halt the entire plan.
97
+ const runtime = makeRuntime(true, false); // vault up, brain down
98
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
99
+
100
+ expect(plan.blocked).toBeUndefined();
101
+ // Plan continues; brain-dependent steps are skipped or warnings added
102
+ const hasBrainWarning =
103
+ plan.warnings.some((w) => /brain/i.test(w)) ||
104
+ plan.skipped.some((s) => /brain/i.test(s.reason));
105
+ // Either skipped or warned — what matters is the plan is not blocked
106
+ expect(plan.steps.length).toBeGreaterThanOrEqual(0);
107
+ expect(plan.blocked).toBeUndefined();
108
+ // suppress unused-var lint
109
+ void hasBrainWarning;
110
+ });
111
+ });
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // buildPlan vault recommendations
115
+ // ---------------------------------------------------------------------------
116
+
117
+ describe('buildPlan — vault recommendations', () => {
118
+ it('attaches mandatory constraint as recommendation with mandatory:true', async () => {
119
+ // Critical vault entries must be surfaced as mandatory recommendations so the
120
+ // executor can enforce them. They must NOT become gate steps (evaluateCondition
121
+ // cannot parse free-text narrative — gates would always fire STOP).
122
+ const runtime = makeRuntime(true);
123
+ const constraint: VaultConstraint = {
124
+ entryId: 'crit-1',
125
+ title: 'No skipping tests',
126
+ context: 'Tests must not be skipped under time pressure.',
127
+ mandatory: true,
128
+ entryType: 'pattern',
129
+ };
130
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
131
+
132
+ const rec = plan.recommendations?.find((r) => r.entryId === 'crit-1');
133
+ expect(rec).toBeDefined();
134
+ expect(rec?.title).toBe('No skipping tests');
135
+ expect(rec?.context).toBe('Tests must not be skipped under time pressure.');
136
+ expect(rec?.mandatory).toBe(true);
137
+ expect(rec?.strength).toBe(100);
138
+ expect(rec?.source).toBe('vault');
139
+ // No gate step injected
140
+ expect(plan.steps.filter((s) => s.id.startsWith('vault-gate-'))).toHaveLength(0);
141
+ });
142
+
143
+ it('marks anti-pattern entry as mandatory:true even when mandatory flag is false', async () => {
144
+ // anti-pattern entries are always treated as mandatory regardless of severity flag.
145
+ const runtime = makeRuntime(true);
146
+ const constraint: VaultConstraint = {
147
+ entryId: 'ap-1',
148
+ title: 'Avoid God Objects',
149
+ context: 'Classes must not exceed 500 lines.',
150
+ mandatory: false,
151
+ entryType: 'anti-pattern',
152
+ };
153
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
154
+
155
+ const rec = plan.recommendations?.find((r) => r.entryId === 'ap-1');
156
+ expect(rec).toBeDefined();
157
+ expect(rec?.mandatory).toBe(true);
158
+ expect(plan.steps.filter((s) => s.id.startsWith('vault-gate-'))).toHaveLength(0);
159
+ });
160
+
161
+ it('does not attach recommendations when no constraints are passed', async () => {
162
+ // Backward compatibility: callers that omit vaultConstraints get an unchanged plan.
163
+ const runtime = makeRuntime(true);
164
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime);
165
+ expect(plan.recommendations).toBeUndefined();
166
+ expect(plan.steps.filter((s) => s.id.startsWith('vault-gate-'))).toHaveLength(0);
167
+ });
168
+
169
+ it('attaches non-mandatory pattern as recommendation with mandatory:false', async () => {
170
+ // Warning and suggestion vault entries are surfaced as non-mandatory recommendations.
171
+ const runtime = makeRuntime(true);
172
+ const constraint: VaultConstraint = {
173
+ entryId: 'sug-1',
174
+ title: 'Consider using named exports',
175
+ mandatory: false,
176
+ entryType: 'pattern',
177
+ };
178
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
179
+ const rec = plan.recommendations?.find((r) => r.entryId === 'sug-1');
180
+ expect(rec).toBeDefined();
181
+ expect(rec?.mandatory).toBe(false);
182
+ expect(rec?.strength).toBe(80);
183
+ });
184
+
185
+ it('includes recommendations in blocked plans', async () => {
186
+ // Blocked plans must still carry vault constraints so callers can surface them.
187
+ const runtime = makeRuntime(false); // vault down → blocked
188
+ const constraint: VaultConstraint = {
189
+ entryId: 'crit-2',
190
+ title: 'No direct DB writes outside repositories',
191
+ mandatory: true,
192
+ entryType: 'anti-pattern',
193
+ };
194
+ const plan = await buildPlan('BUILD', 'myagent', '/tmp/proj', runtime, undefined, [constraint]);
195
+
196
+ expect(plan.blocked).toBe(true);
197
+ const rec = plan.recommendations?.find((r) => r.entryId === 'crit-2');
198
+ expect(rec).toBeDefined();
199
+ expect(rec?.mandatory).toBe(true);
200
+ });
201
+ });
@@ -11,6 +11,7 @@ import type {
11
11
  OrchestrationPlan,
12
12
  ProbeResults,
13
13
  ProbeName,
14
+ VaultRecommendation,
14
15
  } from './types.js';
15
16
  import { loadFlowById } from './loader.js';
16
17
  import { runProbes } from './probes.js';
@@ -56,6 +57,7 @@ export function chainToRequires(chain: string): ProbeName | undefined {
56
57
  if (lower.startsWith('component') || lower.startsWith('token') || lower.startsWith('design'))
57
58
  return 'designSystem';
58
59
  if (lower.startsWith('session')) return 'sessionStore';
60
+ if (lower.startsWith('test')) return 'test';
59
61
  // recommend-* and get-stack-* have no hard requirements
60
62
  if (lower.startsWith('recommend') || lower.startsWith('get-stack')) return undefined;
61
63
  return undefined;
@@ -119,6 +121,7 @@ export function flowStepsToPlanSteps(
119
121
  tools,
120
122
  parallel: step.parallel ?? false,
121
123
  requires,
124
+ output: step.output,
122
125
  status: 'pending',
123
126
  };
124
127
 
@@ -155,6 +158,19 @@ export function flowStepsToPlanSteps(
155
158
  });
156
159
  }
157
160
 
161
+ /**
162
+ * Map a capability ID (e.g. "vault.search") to the probe name that covers it.
163
+ * Returns undefined for capability IDs that have no corresponding probe.
164
+ */
165
+ export function capabilityToProbe(capId: string): ProbeName | undefined {
166
+ if (capId.startsWith('vault.') || capId === 'vault') return 'vault';
167
+ if (capId.startsWith('brain.') || capId === 'brain') return 'brain';
168
+ if (capId.startsWith('design.') || capId.startsWith('component.') || capId.startsWith('token.'))
169
+ return 'designSystem';
170
+ if (capId.startsWith('session.')) return 'sessionStore';
171
+ return undefined;
172
+ }
173
+
158
174
  /**
159
175
  * Remove steps whose required capabilities are not available.
160
176
  */
@@ -181,6 +197,18 @@ export function pruneSteps(
181
197
  return { kept, skipped };
182
198
  }
183
199
 
200
+ /**
201
+ * A vault entry that should influence plan structure.
202
+ * critical severity OR anti-pattern type entries are surfaced as mandatory recommendations.
203
+ */
204
+ export interface VaultConstraint {
205
+ entryId: string;
206
+ title: string;
207
+ context?: string;
208
+ mandatory: boolean;
209
+ entryType?: 'pattern' | 'anti-pattern' | 'rule' | 'playbook';
210
+ }
211
+
184
212
  /**
185
213
  * Build a full orchestration plan from intent, agent config, and runtime.
186
214
  */
@@ -190,6 +218,7 @@ export async function buildPlan(
190
218
  projectPath: string,
191
219
  runtime: AgentRuntime,
192
220
  prompt?: string,
221
+ vaultConstraints: VaultConstraint[] = [],
193
222
  ): Promise<OrchestrationPlan> {
194
223
  const normalizedIntent = intent.toUpperCase();
195
224
  const flowId = INTENT_TO_FLOW[normalizedIntent] ?? 'BUILD-flow';
@@ -197,17 +226,63 @@ export async function buildPlan(
197
226
 
198
227
  const probes = await runProbes(runtime, projectPath);
199
228
 
229
+ // Map vault constraints to recommendations — surfaced to executor as knowledge context.
230
+ // Anti-pattern entries are always mandatory regardless of the mandatory flag.
231
+ const recommendations: VaultRecommendation[] = vaultConstraints.map((c) => ({
232
+ entryId: c.entryId,
233
+ title: c.title,
234
+ ...(c.context ? { context: c.context } : {}),
235
+ mandatory: c.mandatory || c.entryType === 'anti-pattern',
236
+ entryType: c.entryType,
237
+ source: 'vault' as const,
238
+ strength: c.mandatory ? 100 : 80,
239
+ }));
240
+
241
+ // Detect context entities from prompt before any early returns — blocked plans
242
+ // should still carry entity context so callers can surface useful information.
243
+ const entities = { components: [] as string[], actions: [] as string[] };
244
+ const contexts = prompt ? detectContext(prompt, entities) : [];
245
+
200
246
  let steps: PlanStep[] = [];
201
247
  let skipped: SkippedStep[] = [];
202
248
  const warnings: string[] = [];
203
249
 
204
250
  if (flow) {
251
+ // Check blocking capabilities before pruning optional steps.
252
+ // If any blocking capability maps to an unavailable probe, the plan cannot run.
253
+ const blockingCaps = flow['on-missing-capability']?.blocking ?? [];
254
+ const missingBlockers = blockingCaps.filter((capId) => {
255
+ const probe = capabilityToProbe(capId);
256
+ return probe !== undefined && !probes[probe];
257
+ });
258
+
259
+ if (missingBlockers.length > 0) {
260
+ return {
261
+ planId: randomUUID(),
262
+ intent: normalizedIntent,
263
+ flowId,
264
+ steps: [],
265
+ skipped: [],
266
+ epilogue: [],
267
+ warnings: [
268
+ `Blocked: required capabilities unavailable — ${missingBlockers.join(', ')}. Resolve these before running this flow.`,
269
+ ],
270
+ summary: prompt ?? `${normalizedIntent} plan blocked`,
271
+ estimatedTools: 0,
272
+ blocked: true,
273
+ ...(recommendations.length > 0 ? { recommendations } : {}),
274
+ context: {
275
+ intent: normalizedIntent,
276
+ probes,
277
+ entities,
278
+ projectPath,
279
+ },
280
+ };
281
+ }
282
+
205
283
  let allSteps = flowStepsToPlanSteps(flow, agentId);
206
284
 
207
- // Context-sensitive chain routing: detect what's being built/fixed/reviewed
208
- // and apply chain overrides (inject, skip, substitute) before pruning.
209
- const entities = { components: [] as string[], actions: [] as string[] };
210
- const contexts = prompt ? detectContext(prompt, entities) : [];
285
+ // Apply context-sensitive chain overrides (inject, skip, substitute) before pruning.
211
286
  if (contexts.length > 0) {
212
287
  allSteps = applyContextOverrides(allSteps, contexts, flowId, agentId);
213
288
  }
@@ -240,10 +315,11 @@ export async function buildPlan(
240
315
  warnings,
241
316
  summary: prompt ?? `${normalizedIntent} plan with ${steps.length} step(s)`,
242
317
  estimatedTools: steps.reduce((acc, s) => acc + s.tools.length, 0),
318
+ ...(recommendations.length > 0 ? { recommendations } : {}),
243
319
  context: {
244
320
  intent: normalizedIntent,
245
321
  probes,
246
- entities: { components: [], actions: [] },
322
+ entities,
247
323
  projectPath,
248
324
  },
249
325
  };
@@ -9,19 +9,20 @@ import type { AgentRuntime } from '../runtime/types.js';
9
9
  import type { ProbeResults } from './types.js';
10
10
 
11
11
  /**
12
- * Run all 6 capability probes in parallel and return results.
12
+ * Run all capability probes in parallel and return results.
13
13
  */
14
14
  export async function runProbes(runtime: AgentRuntime, projectPath: string): Promise<ProbeResults> {
15
- const [vault, brain, designSystem, sessionStore, projectRules, active] = await Promise.all([
15
+ const [vault, brain, designSystem, sessionStore, projectRules, active, test] = await Promise.all([
16
16
  probeVault(runtime),
17
17
  probeBrain(runtime),
18
18
  probeDesignSystem(runtime),
19
19
  probeSessionStore(),
20
20
  probeProjectRules(projectPath),
21
21
  probeActive(),
22
+ probeTestRunner(projectPath),
22
23
  ]);
23
24
 
24
- return { vault, brain, designSystem, sessionStore, projectRules, active };
25
+ return { vault, brain, designSystem, sessionStore, projectRules, active, test };
25
26
  }
26
27
 
27
28
  async function probeVault(runtime: AgentRuntime): Promise<boolean> {
@@ -68,3 +69,16 @@ async function probeActive(): Promise<boolean> {
68
69
  // Always true when the engine is running
69
70
  return true;
70
71
  }
72
+
73
+ async function probeTestRunner(projectPath: string): Promise<boolean> {
74
+ try {
75
+ return (
76
+ existsSync(join(projectPath, 'vitest.config.ts')) ||
77
+ existsSync(join(projectPath, 'vitest.config.js')) ||
78
+ existsSync(join(projectPath, 'jest.config.ts')) ||
79
+ existsSync(join(projectPath, 'jest.config.js'))
80
+ );
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
@@ -75,6 +75,11 @@ export const flowSchema = z.object({
75
75
  'min-confidence': z.enum(['HIGH', 'MEDIUM', 'LOW']).optional(),
76
76
  }),
77
77
  steps: z.array(flowStepSchema),
78
+ /**
79
+ * Scoring weights declared per step — parsed but not yet computed by the executor.
80
+ * Weighted-sum formula is not implemented; gate thresholds in steps are the active enforcement.
81
+ * @see https://github.com/adrozdenko/soleri/issues/632
82
+ */
78
83
  scoring: z
79
84
  .object({
80
85
  weights: z.record(z.number()),
@@ -100,7 +105,7 @@ export const flowSchema = z.object({
100
105
  })
101
106
  .optional(),
102
107
  /** Strategy when a step's capability requirement is not satisfied */
103
- onMissingCapability: z
108
+ 'on-missing-capability': z
104
109
  .object({
105
110
  default: z.enum(['skip-with-warning', 'fail', 'ask-user']).default('skip-with-warning'),
106
111
  blocking: z.array(z.string()).optional().default([]),
@@ -130,7 +135,8 @@ export type ProbeName =
130
135
  | 'designSystem'
131
136
  | 'sessionStore'
132
137
  | 'projectRules'
133
- | 'active';
138
+ | 'active'
139
+ | 'test';
134
140
 
135
141
  export interface ProbeResults {
136
142
  vault: boolean;
@@ -139,6 +145,7 @@ export interface ProbeResults {
139
145
  sessionStore: boolean;
140
146
  projectRules: boolean;
141
147
  active: boolean;
148
+ test: boolean;
142
149
  }
143
150
 
144
151
  // ---------------------------------------------------------------------------
@@ -158,6 +165,8 @@ export interface PlanStep {
158
165
  min?: number;
159
166
  onFail?: { action: string; goto?: string; message?: string };
160
167
  };
168
+ /** Output keys this step produces — merged into stepContext for subsequent steps. */
169
+ output?: string[];
161
170
  status:
162
171
  | 'pending'
163
172
  | 'running'
@@ -175,6 +184,22 @@ export interface SkippedStep {
175
184
  reason: string;
176
185
  }
177
186
 
187
+ /**
188
+ * A vault knowledge entry surfaced as a planning constraint.
189
+ * Replaces gate-step injection — constraints are carried as metadata
190
+ * so the executor can apply judgment rather than mechanical evaluation.
191
+ */
192
+ export interface VaultRecommendation {
193
+ entryId: string;
194
+ title: string;
195
+ context?: string;
196
+ example?: string;
197
+ mandatory: boolean;
198
+ entryType?: 'pattern' | 'anti-pattern' | 'rule' | 'playbook';
199
+ source: 'vault';
200
+ strength: number;
201
+ }
202
+
178
203
  export interface ToolDeviation {
179
204
  stepId: string;
180
205
  expectedTools: string[];
@@ -198,6 +223,10 @@ export interface OrchestrationPlan {
198
223
  workflowPrompt?: string;
199
224
  /** Name of the matched workflow */
200
225
  workflowName?: string;
226
+ /** True when a blocking capability is unavailable — plan cannot run */
227
+ blocked?: boolean;
228
+ /** Vault knowledge constraints relevant to this plan — executor reads these as context */
229
+ recommendations?: VaultRecommendation[];
201
230
  }
202
231
 
203
232
  export interface OrchestrationContext {
@@ -21,6 +21,7 @@ describe('HealthRegistry', () => {
21
21
  });
22
22
 
23
23
  it('tracks transitions: healthy -> degraded -> healthy', () => {
24
+ const before = Date.now();
24
25
  const reg = new HealthRegistry();
25
26
  reg.register('svc');
26
27
  reg.update('svc', 'degraded', 'timeout');
@@ -31,7 +32,8 @@ describe('HealthRegistry', () => {
31
32
  reg.update('svc', 'healthy');
32
33
  expect(reg.get('svc')!.failureCount).toBe(0);
33
34
  expect(reg.get('svc')!.lastError).toBeNull();
34
- expect(reg.get('svc')!.lastHealthyAt).toBeGreaterThan(0);
35
+ expect(reg.get('svc')!.lastHealthyAt).toBeGreaterThanOrEqual(before);
36
+ expect(reg.get('svc')!.lastHealthyAt).toBeLessThanOrEqual(Date.now());
35
37
  });
36
38
 
37
39
  it('auto-registers on update if not previously registered', () => {