@soleri/core 2.12.0 → 7.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 (251) 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/brain/intelligence.d.ts.map +1 -1
  10. package/dist/brain/intelligence.js +16 -2
  11. package/dist/brain/intelligence.js.map +1 -1
  12. package/dist/capabilities/chain-mapping.d.ts +21 -0
  13. package/dist/capabilities/chain-mapping.d.ts.map +1 -0
  14. package/dist/capabilities/chain-mapping.js +86 -0
  15. package/dist/capabilities/chain-mapping.js.map +1 -0
  16. package/dist/capabilities/index.d.ts +10 -0
  17. package/dist/capabilities/index.d.ts.map +1 -0
  18. package/dist/capabilities/index.js +8 -0
  19. package/dist/capabilities/index.js.map +1 -0
  20. package/dist/capabilities/registry.d.ts +95 -0
  21. package/dist/capabilities/registry.d.ts.map +1 -0
  22. package/dist/capabilities/registry.js +227 -0
  23. package/dist/capabilities/registry.js.map +1 -0
  24. package/dist/capabilities/types.d.ts +106 -0
  25. package/dist/capabilities/types.d.ts.map +1 -0
  26. package/dist/capabilities/types.js +12 -0
  27. package/dist/capabilities/types.js.map +1 -0
  28. package/dist/control/intent-router.d.ts.map +1 -1
  29. package/dist/control/intent-router.js +58 -2
  30. package/dist/control/intent-router.js.map +1 -1
  31. package/dist/domain-packs/index.d.ts +8 -0
  32. package/dist/domain-packs/index.d.ts.map +1 -0
  33. package/dist/domain-packs/index.js +8 -0
  34. package/dist/domain-packs/index.js.map +1 -0
  35. package/dist/domain-packs/inject-rules.d.ts +24 -0
  36. package/dist/domain-packs/inject-rules.d.ts.map +1 -0
  37. package/dist/domain-packs/inject-rules.js +65 -0
  38. package/dist/domain-packs/inject-rules.js.map +1 -0
  39. package/dist/domain-packs/knowledge-installer.d.ts +27 -0
  40. package/dist/domain-packs/knowledge-installer.d.ts.map +1 -0
  41. package/dist/domain-packs/knowledge-installer.js +89 -0
  42. package/dist/domain-packs/knowledge-installer.js.map +1 -0
  43. package/dist/domain-packs/loader.d.ts +28 -0
  44. package/dist/domain-packs/loader.d.ts.map +1 -0
  45. package/dist/domain-packs/loader.js +105 -0
  46. package/dist/domain-packs/loader.js.map +1 -0
  47. package/dist/domain-packs/pack-runtime.d.ts +80 -0
  48. package/dist/domain-packs/pack-runtime.d.ts.map +1 -0
  49. package/dist/domain-packs/pack-runtime.js +36 -0
  50. package/dist/domain-packs/pack-runtime.js.map +1 -0
  51. package/dist/domain-packs/skills-installer.d.ts +21 -0
  52. package/dist/domain-packs/skills-installer.d.ts.map +1 -0
  53. package/dist/domain-packs/skills-installer.js +38 -0
  54. package/dist/domain-packs/skills-installer.js.map +1 -0
  55. package/dist/domain-packs/token-resolver.d.ts +37 -0
  56. package/dist/domain-packs/token-resolver.d.ts.map +1 -0
  57. package/dist/domain-packs/token-resolver.js +109 -0
  58. package/dist/domain-packs/token-resolver.js.map +1 -0
  59. package/dist/domain-packs/types.d.ts +91 -0
  60. package/dist/domain-packs/types.d.ts.map +1 -0
  61. package/dist/domain-packs/types.js +122 -0
  62. package/dist/domain-packs/types.js.map +1 -0
  63. package/dist/engine/bin/soleri-engine.d.ts +12 -0
  64. package/dist/engine/bin/soleri-engine.d.ts.map +1 -0
  65. package/dist/engine/bin/soleri-engine.js +183 -0
  66. package/dist/engine/bin/soleri-engine.js.map +1 -0
  67. package/dist/engine/core-ops.d.ts +27 -0
  68. package/dist/engine/core-ops.d.ts.map +1 -0
  69. package/dist/engine/core-ops.js +159 -0
  70. package/dist/engine/core-ops.js.map +1 -0
  71. package/dist/engine/index.d.ts +19 -0
  72. package/dist/engine/index.d.ts.map +1 -0
  73. package/dist/engine/index.js +17 -0
  74. package/dist/engine/index.js.map +1 -0
  75. package/dist/engine/register-engine.d.ts +54 -0
  76. package/dist/engine/register-engine.d.ts.map +1 -0
  77. package/dist/engine/register-engine.js +270 -0
  78. package/dist/engine/register-engine.js.map +1 -0
  79. package/dist/engine/test-helpers.d.ts +30 -0
  80. package/dist/engine/test-helpers.d.ts.map +1 -0
  81. package/dist/engine/test-helpers.js +59 -0
  82. package/dist/engine/test-helpers.js.map +1 -0
  83. package/dist/flows/context-router.d.ts +39 -0
  84. package/dist/flows/context-router.d.ts.map +1 -0
  85. package/dist/flows/context-router.js +206 -0
  86. package/dist/flows/context-router.js.map +1 -0
  87. package/dist/flows/dispatch-registry.d.ts +24 -0
  88. package/dist/flows/dispatch-registry.d.ts.map +1 -0
  89. package/dist/flows/dispatch-registry.js +70 -0
  90. package/dist/flows/dispatch-registry.js.map +1 -0
  91. package/dist/flows/epilogue.d.ts +24 -0
  92. package/dist/flows/epilogue.d.ts.map +1 -0
  93. package/dist/flows/epilogue.js +52 -0
  94. package/dist/flows/epilogue.js.map +1 -0
  95. package/dist/flows/executor.d.ts +25 -0
  96. package/dist/flows/executor.d.ts.map +1 -0
  97. package/dist/flows/executor.js +153 -0
  98. package/dist/flows/executor.js.map +1 -0
  99. package/dist/flows/gate-evaluator.d.ts +26 -0
  100. package/dist/flows/gate-evaluator.d.ts.map +1 -0
  101. package/dist/flows/gate-evaluator.js +162 -0
  102. package/dist/flows/gate-evaluator.js.map +1 -0
  103. package/dist/flows/index.d.ts +14 -0
  104. package/dist/flows/index.d.ts.map +1 -0
  105. package/dist/flows/index.js +20 -0
  106. package/dist/flows/index.js.map +1 -0
  107. package/dist/flows/loader.d.ts +17 -0
  108. package/dist/flows/loader.d.ts.map +1 -0
  109. package/dist/flows/loader.js +61 -0
  110. package/dist/flows/loader.js.map +1 -0
  111. package/dist/flows/plan-builder.d.ts +40 -0
  112. package/dist/flows/plan-builder.d.ts.map +1 -0
  113. package/dist/flows/plan-builder.js +213 -0
  114. package/dist/flows/plan-builder.js.map +1 -0
  115. package/dist/flows/probes.d.ts +11 -0
  116. package/dist/flows/probes.d.ts.map +1 -0
  117. package/dist/flows/probes.js +62 -0
  118. package/dist/flows/probes.js.map +1 -0
  119. package/dist/flows/types.d.ts +950 -0
  120. package/dist/flows/types.d.ts.map +1 -0
  121. package/dist/flows/types.js +105 -0
  122. package/dist/flows/types.js.map +1 -0
  123. package/dist/index.d.ts +11 -1
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +10 -1
  126. package/dist/index.js.map +1 -1
  127. package/dist/intelligence/loader.d.ts +19 -0
  128. package/dist/intelligence/loader.d.ts.map +1 -1
  129. package/dist/intelligence/loader.js +35 -0
  130. package/dist/intelligence/loader.js.map +1 -1
  131. package/dist/intelligence/types.d.ts +1 -0
  132. package/dist/intelligence/types.d.ts.map +1 -1
  133. package/dist/packs/types.d.ts +58 -19
  134. package/dist/packs/types.d.ts.map +1 -1
  135. package/dist/packs/types.js +14 -0
  136. package/dist/packs/types.js.map +1 -1
  137. package/dist/playbooks/generic/onboarding.d.ts +9 -0
  138. package/dist/playbooks/generic/onboarding.d.ts.map +1 -0
  139. package/dist/playbooks/generic/onboarding.js +74 -0
  140. package/dist/playbooks/generic/onboarding.js.map +1 -0
  141. package/dist/playbooks/playbook-registry.d.ts.map +1 -1
  142. package/dist/playbooks/playbook-registry.js +2 -0
  143. package/dist/playbooks/playbook-registry.js.map +1 -1
  144. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  145. package/dist/runtime/admin-extra-ops.js +15 -9
  146. package/dist/runtime/admin-extra-ops.js.map +1 -1
  147. package/dist/runtime/admin-ops.js +4 -4
  148. package/dist/runtime/admin-ops.js.map +1 -1
  149. package/dist/runtime/capture-ops.d.ts.map +1 -1
  150. package/dist/runtime/capture-ops.js +33 -1
  151. package/dist/runtime/capture-ops.js.map +1 -1
  152. package/dist/runtime/domain-ops.d.ts +21 -5
  153. package/dist/runtime/domain-ops.d.ts.map +1 -1
  154. package/dist/runtime/domain-ops.js +64 -6
  155. package/dist/runtime/domain-ops.js.map +1 -1
  156. package/dist/runtime/facades/cognee-facade.d.ts.map +1 -1
  157. package/dist/runtime/facades/cognee-facade.js +3 -1
  158. package/dist/runtime/facades/cognee-facade.js.map +1 -1
  159. package/dist/runtime/facades/index.d.ts.map +1 -1
  160. package/dist/runtime/facades/index.js +10 -6
  161. package/dist/runtime/facades/index.js.map +1 -1
  162. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  163. package/dist/runtime/facades/vault-facade.js +2 -0
  164. package/dist/runtime/facades/vault-facade.js.map +1 -1
  165. package/dist/runtime/orchestrate-ops.d.ts +8 -7
  166. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  167. package/dist/runtime/orchestrate-ops.js +217 -61
  168. package/dist/runtime/orchestrate-ops.js.map +1 -1
  169. package/dist/runtime/runtime.d.ts.map +1 -1
  170. package/dist/runtime/runtime.js +23 -17
  171. package/dist/runtime/runtime.js.map +1 -1
  172. package/dist/runtime/types.d.ts +6 -2
  173. package/dist/runtime/types.d.ts.map +1 -1
  174. package/dist/runtime/vault-linking-ops.d.ts +13 -0
  175. package/dist/runtime/vault-linking-ops.d.ts.map +1 -0
  176. package/dist/runtime/vault-linking-ops.js +367 -0
  177. package/dist/runtime/vault-linking-ops.js.map +1 -0
  178. package/dist/vault/linking.d.ts +46 -0
  179. package/dist/vault/linking.d.ts.map +1 -0
  180. package/dist/vault/linking.js +275 -0
  181. package/dist/vault/linking.js.map +1 -0
  182. package/dist/vault/vault-types.d.ts +37 -0
  183. package/dist/vault/vault-types.d.ts.map +1 -1
  184. package/dist/vault/vault.d.ts +12 -0
  185. package/dist/vault/vault.d.ts.map +1 -1
  186. package/dist/vault/vault.js +85 -6
  187. package/dist/vault/vault.js.map +1 -1
  188. package/package.json +4 -1
  189. package/src/__tests__/admin-extra-ops.test.ts +1 -1
  190. package/src/__tests__/admin-ops.test.ts +2 -1
  191. package/src/__tests__/cognee-client-gaps.test.ts +470 -0
  192. package/src/__tests__/cognee-hybrid-search.test.ts +478 -0
  193. package/src/__tests__/cognee-sync-manager-deep.test.ts +630 -0
  194. package/src/__tests__/cognee-sync-manager.test.ts +1 -0
  195. package/src/__tests__/core-ops.test.ts +9 -61
  196. package/src/__tests__/domain-packs.test.ts +421 -0
  197. package/src/__tests__/flows.test.ts +604 -0
  198. package/src/__tests__/playbook-registry.test.ts +2 -2
  199. package/src/__tests__/playbook-seeder.test.ts +8 -8
  200. package/src/__tests__/playbook.test.ts +5 -5
  201. package/src/__tests__/token-resolver.test.ts +79 -0
  202. package/src/brain/intelligence.ts +21 -2
  203. package/src/capabilities/chain-mapping.ts +93 -0
  204. package/src/capabilities/index.ts +21 -0
  205. package/src/capabilities/registry.ts +290 -0
  206. package/src/capabilities/types.ts +143 -0
  207. package/src/control/intent-router.ts +46 -2
  208. package/src/domain-packs/index.ts +27 -0
  209. package/src/domain-packs/inject-rules.ts +74 -0
  210. package/src/domain-packs/knowledge-installer.ts +116 -0
  211. package/src/domain-packs/loader.ts +124 -0
  212. package/src/domain-packs/pack-runtime.ts +99 -0
  213. package/src/domain-packs/skills-installer.ts +56 -0
  214. package/src/domain-packs/token-resolver.ts +126 -0
  215. package/src/domain-packs/types.ts +229 -0
  216. package/src/engine/__tests__/register-engine.test.ts +104 -0
  217. package/src/engine/bin/soleri-engine.ts +217 -0
  218. package/src/engine/core-ops.ts +178 -0
  219. package/src/engine/index.ts +19 -0
  220. package/src/engine/register-engine.ts +385 -0
  221. package/src/engine/test-helpers.ts +83 -0
  222. package/src/flows/context-router.ts +257 -0
  223. package/src/flows/dispatch-registry.ts +80 -0
  224. package/src/flows/epilogue.ts +65 -0
  225. package/src/flows/executor.ts +182 -0
  226. package/src/flows/gate-evaluator.ts +171 -0
  227. package/src/flows/index.ts +52 -0
  228. package/src/flows/loader.ts +63 -0
  229. package/src/flows/plan-builder.ts +250 -0
  230. package/src/flows/probes.ts +70 -0
  231. package/src/flows/types.ts +217 -0
  232. package/src/index.ts +68 -1
  233. package/src/intelligence/loader.ts +38 -0
  234. package/src/intelligence/types.ts +1 -0
  235. package/src/packs/types.ts +19 -0
  236. package/src/playbooks/generic/onboarding.ts +79 -0
  237. package/src/playbooks/playbook-registry.ts +2 -0
  238. package/src/runtime/admin-extra-ops.ts +14 -8
  239. package/src/runtime/admin-ops.ts +4 -4
  240. package/src/runtime/capture-ops.ts +40 -1
  241. package/src/runtime/domain-ops.ts +71 -5
  242. package/src/runtime/facades/cognee-facade.ts +3 -1
  243. package/src/runtime/facades/index.ts +12 -6
  244. package/src/runtime/facades/vault-facade.ts +2 -0
  245. package/src/runtime/orchestrate-ops.ts +261 -65
  246. package/src/runtime/runtime.ts +27 -18
  247. package/src/runtime/types.ts +6 -2
  248. package/src/runtime/vault-linking-ops.ts +454 -0
  249. package/src/vault/linking.ts +333 -0
  250. package/src/vault/vault-types.ts +46 -0
  251. package/src/vault/vault.ts +94 -7
@@ -0,0 +1,604 @@
1
+ /**
2
+ * Flow engine tests — loader, probes, plan builder, gate evaluator, executor.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { join, dirname } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { createAgentRuntime } from '../runtime/runtime.js';
9
+ import type { AgentRuntime } from '../runtime/types.js';
10
+ import { loadFlowById, loadAllFlows } from '../flows/loader.js';
11
+ import { runProbes } from '../flows/probes.js';
12
+ import {
13
+ INTENT_TO_FLOW,
14
+ chainToToolName,
15
+ chainToRequires,
16
+ pruneSteps,
17
+ } from '../flows/plan-builder.js';
18
+ import { evaluateCondition, extractScore, resolvePath } from '../flows/gate-evaluator.js';
19
+ import { FlowExecutor } from '../flows/executor.js';
20
+
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+ const FLOWS_DIR = join(__dirname, '..', '..', 'data', 'flows');
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Flow Loader
26
+ // ---------------------------------------------------------------------------
27
+
28
+ describe('Flow Loader', () => {
29
+ it('should load all 8 flow files', () => {
30
+ const flows = loadAllFlows(FLOWS_DIR);
31
+ expect(flows.length).toBe(8);
32
+ });
33
+
34
+ it('should load BUILD-flow by ID', () => {
35
+ const flow = loadFlowById('BUILD-flow', FLOWS_DIR);
36
+ expect(flow).not.toBeNull();
37
+ expect(flow!.id).toBe('BUILD-flow');
38
+ expect(flow!.steps.length).toBeGreaterThan(0);
39
+ });
40
+
41
+ it('should load FIX-flow by ID', () => {
42
+ const flow = loadFlowById('FIX-flow', FLOWS_DIR);
43
+ expect(flow).not.toBeNull();
44
+ expect(flow!.triggers.modes).toContain('FIX');
45
+ });
46
+
47
+ it('should return null for unknown flow ID', () => {
48
+ const flow = loadFlowById('NONEXISTENT-flow', FLOWS_DIR);
49
+ expect(flow).toBeNull();
50
+ });
51
+
52
+ it('each flow should have valid structure', () => {
53
+ const flows = loadAllFlows(FLOWS_DIR);
54
+ for (const flow of flows) {
55
+ expect(flow.id).toBeTruthy();
56
+ expect(flow.triggers.modes.length).toBeGreaterThan(0);
57
+ expect(flow.steps.length).toBeGreaterThan(0);
58
+ }
59
+ });
60
+ });
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Intent Mapping
64
+ // ---------------------------------------------------------------------------
65
+
66
+ describe('Intent to Flow mapping', () => {
67
+ it('should map BUILD to BUILD-flow', () => {
68
+ expect(INTENT_TO_FLOW.BUILD).toBe('BUILD-flow');
69
+ });
70
+
71
+ it('should map FIX to FIX-flow', () => {
72
+ expect(INTENT_TO_FLOW.FIX).toBe('FIX-flow');
73
+ });
74
+
75
+ it('should map DELIVER to DELIVER-flow', () => {
76
+ expect(INTENT_TO_FLOW.DELIVER).toBe('DELIVER-flow');
77
+ });
78
+
79
+ it('should have all 8 flows mapped', () => {
80
+ const flowIds = new Set(Object.values(INTENT_TO_FLOW));
81
+ expect(flowIds.size).toBeGreaterThanOrEqual(8);
82
+ });
83
+ });
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Chain to Tool Name
87
+ // ---------------------------------------------------------------------------
88
+
89
+ describe('chainToToolName', () => {
90
+ it('should prefix with agentId and convert hyphens', () => {
91
+ expect(chainToToolName('vault-search', 'myagent')).toBe('myagent_vault_search');
92
+ });
93
+
94
+ it('should handle single-word chains', () => {
95
+ expect(chainToToolName('validate', 'test')).toBe('test_validate');
96
+ });
97
+ });
98
+
99
+ describe('chainToRequires', () => {
100
+ it('should detect vault requirement', () => {
101
+ expect(chainToRequires('vault-search')).toBe('vault');
102
+ });
103
+
104
+ it('should detect brain requirement', () => {
105
+ expect(chainToRequires('brain-recommend')).toBe('brain');
106
+ });
107
+
108
+ it('should detect designSystem requirement', () => {
109
+ expect(chainToRequires('component-search')).toBe('designSystem');
110
+ });
111
+
112
+ it('should return undefined for recommendation chains', () => {
113
+ expect(chainToRequires('recommend-style')).toBeUndefined();
114
+ expect(chainToRequires('get-stack-guidelines')).toBeUndefined();
115
+ });
116
+ });
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Step Pruning
120
+ // ---------------------------------------------------------------------------
121
+
122
+ describe('pruneSteps', () => {
123
+ it('should keep steps with no requirements', () => {
124
+ const steps = [
125
+ {
126
+ id: 's1',
127
+ name: 'Step 1',
128
+ tools: ['t1'],
129
+ parallel: false,
130
+ requires: [],
131
+ status: 'pending' as const,
132
+ },
133
+ ];
134
+ const probes = {
135
+ vault: false,
136
+ brain: false,
137
+ designSystem: false,
138
+ sessionStore: false,
139
+ projectRules: false,
140
+ active: true,
141
+ };
142
+ const { kept, skipped } = pruneSteps(steps, probes);
143
+ expect(kept.length).toBe(1);
144
+ expect(skipped.length).toBe(0);
145
+ });
146
+
147
+ it('should skip steps with unmet requirements', () => {
148
+ const steps = [
149
+ {
150
+ id: 's1',
151
+ name: 'Vault Step',
152
+ tools: ['t1'],
153
+ parallel: false,
154
+ requires: ['vault' as const],
155
+ status: 'pending' as const,
156
+ },
157
+ {
158
+ id: 's2',
159
+ name: 'No Req',
160
+ tools: ['t2'],
161
+ parallel: false,
162
+ requires: [],
163
+ status: 'pending' as const,
164
+ },
165
+ ];
166
+ const probes = {
167
+ vault: false,
168
+ brain: false,
169
+ designSystem: false,
170
+ sessionStore: false,
171
+ projectRules: false,
172
+ active: true,
173
+ };
174
+ const { kept, skipped } = pruneSteps(steps, probes);
175
+ expect(kept.length).toBe(1);
176
+ expect(kept[0].id).toBe('s2');
177
+ expect(skipped.length).toBe(1);
178
+ expect(skipped[0].reason).toContain('vault');
179
+ });
180
+
181
+ it('should keep steps when requirements are met', () => {
182
+ const steps = [
183
+ {
184
+ id: 's1',
185
+ name: 'Vault Step',
186
+ tools: ['t1'],
187
+ parallel: false,
188
+ requires: ['vault' as const],
189
+ status: 'pending' as const,
190
+ },
191
+ ];
192
+ const probes = {
193
+ vault: true,
194
+ brain: false,
195
+ designSystem: false,
196
+ sessionStore: false,
197
+ projectRules: false,
198
+ active: true,
199
+ };
200
+ const { kept } = pruneSteps(steps, probes);
201
+ expect(kept.length).toBe(1);
202
+ });
203
+ });
204
+
205
+ // ---------------------------------------------------------------------------
206
+ // Gate Evaluator
207
+ // ---------------------------------------------------------------------------
208
+
209
+ describe('evaluateCondition', () => {
210
+ it('should evaluate equality', () => {
211
+ expect(evaluateCondition('count == 0', { count: 0 })).toBe(true);
212
+ expect(evaluateCondition('count == 0', { count: 5 })).toBe(false);
213
+ });
214
+
215
+ it('should evaluate inequality', () => {
216
+ expect(evaluateCondition('count != 0', { count: 5 })).toBe(true);
217
+ });
218
+
219
+ it('should evaluate greater than', () => {
220
+ expect(evaluateCondition('score >= 80', { score: 90 })).toBe(true);
221
+ expect(evaluateCondition('score >= 80', { score: 70 })).toBe(false);
222
+ });
223
+
224
+ it('should evaluate boolean', () => {
225
+ expect(evaluateCondition('pass == true', { pass: true })).toBe(true);
226
+ });
227
+
228
+ it('should evaluate truthy path', () => {
229
+ expect(evaluateCondition('result', { result: 'something' })).toBe(true);
230
+ expect(evaluateCondition('result', { result: '' })).toBe(false);
231
+ });
232
+ });
233
+
234
+ describe('extractScore', () => {
235
+ it('should extract score from data.score', () => {
236
+ expect(extractScore({ score: 85 })).toBe(85);
237
+ });
238
+
239
+ it('should extract from nested data', () => {
240
+ expect(extractScore({ validationScore: 92 })).toBe(92);
241
+ });
242
+
243
+ it('should return 0 if no score found', () => {
244
+ expect(extractScore({ unrelated: 'data' })).toBe(0);
245
+ });
246
+ });
247
+
248
+ describe('resolvePath', () => {
249
+ it('should resolve simple path', () => {
250
+ expect(resolvePath({ count: 5 }, 'count')).toBe(5);
251
+ });
252
+
253
+ it('should resolve dotted path', () => {
254
+ expect(resolvePath({ a: { b: 42 } }, 'a.b')).toBe(42);
255
+ });
256
+
257
+ it('should return undefined for missing path', () => {
258
+ expect(resolvePath({ a: 1 }, 'b')).toBeUndefined();
259
+ });
260
+ });
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // Executor
264
+ // ---------------------------------------------------------------------------
265
+
266
+ describe('FlowExecutor', () => {
267
+ it('should execute a simple plan', async () => {
268
+ const dispatch = async (tool: string, _params: Record<string, unknown>) => ({
269
+ tool,
270
+ status: 'ok',
271
+ data: { result: true },
272
+ });
273
+
274
+ const executor = new FlowExecutor(dispatch);
275
+ const plan = {
276
+ planId: 'test-plan',
277
+ intent: 'BUILD',
278
+ flowId: 'BUILD-flow',
279
+ steps: [
280
+ {
281
+ id: 's1',
282
+ name: 'Step 1',
283
+ tools: ['tool1'],
284
+ parallel: false,
285
+ requires: [],
286
+ status: 'pending' as const,
287
+ },
288
+ {
289
+ id: 's2',
290
+ name: 'Step 2',
291
+ tools: ['tool2'],
292
+ parallel: false,
293
+ requires: [],
294
+ status: 'pending' as const,
295
+ },
296
+ ],
297
+ skipped: [],
298
+ epilogue: [],
299
+ warnings: [],
300
+ summary: 'Test plan',
301
+ estimatedTools: 2,
302
+ context: {
303
+ intent: 'BUILD',
304
+ probes: {
305
+ vault: true,
306
+ brain: false,
307
+ designSystem: false,
308
+ sessionStore: true,
309
+ projectRules: false,
310
+ active: true,
311
+ },
312
+ entities: { components: [], actions: [] },
313
+ projectPath: '/test',
314
+ },
315
+ };
316
+
317
+ const result = await executor.execute(plan);
318
+ expect(result.status).toBe('completed');
319
+ expect(result.stepsCompleted).toBe(2);
320
+ expect(result.toolsCalled).toContain('tool1');
321
+ expect(result.toolsCalled).toContain('tool2');
322
+ });
323
+
324
+ it('should handle STOP gate', async () => {
325
+ const dispatch = async (tool: string) => ({
326
+ tool,
327
+ status: 'ok',
328
+ data: { pass: false },
329
+ });
330
+
331
+ const executor = new FlowExecutor(dispatch);
332
+ const plan = {
333
+ planId: 'test-stop',
334
+ intent: 'DELIVER',
335
+ flowId: 'DELIVER-flow',
336
+ steps: [
337
+ {
338
+ id: 's1',
339
+ name: 'Gate Step',
340
+ tools: ['check'],
341
+ parallel: false,
342
+ requires: [],
343
+ gate: {
344
+ type: 'GATE',
345
+ condition: 'pass == true',
346
+ onFail: { action: 'STOP', message: 'Failed' },
347
+ },
348
+ status: 'pending' as const,
349
+ },
350
+ {
351
+ id: 's2',
352
+ name: 'After Gate',
353
+ tools: ['next'],
354
+ parallel: false,
355
+ requires: [],
356
+ status: 'pending' as const,
357
+ },
358
+ ],
359
+ skipped: [],
360
+ epilogue: [],
361
+ warnings: [],
362
+ summary: 'Test stop',
363
+ estimatedTools: 2,
364
+ context: {
365
+ intent: 'DELIVER',
366
+ probes: {
367
+ vault: true,
368
+ brain: false,
369
+ designSystem: false,
370
+ sessionStore: true,
371
+ projectRules: false,
372
+ active: true,
373
+ },
374
+ entities: { components: [], actions: [] },
375
+ projectPath: '/test',
376
+ },
377
+ };
378
+
379
+ const result = await executor.execute(plan);
380
+ expect(result.stepsCompleted).toBeLessThan(2);
381
+ });
382
+
383
+ it('should execute parallel steps', async () => {
384
+ const callOrder: string[] = [];
385
+ const dispatch = async (tool: string) => {
386
+ callOrder.push(tool);
387
+ return { tool, status: 'ok', data: {} };
388
+ };
389
+
390
+ const executor = new FlowExecutor(dispatch);
391
+ const plan = {
392
+ planId: 'test-parallel',
393
+ intent: 'BUILD',
394
+ flowId: 'BUILD-flow',
395
+ steps: [
396
+ {
397
+ id: 's1',
398
+ name: 'Parallel',
399
+ tools: ['a', 'b', 'c'],
400
+ parallel: true,
401
+ requires: [],
402
+ status: 'pending' as const,
403
+ },
404
+ ],
405
+ skipped: [],
406
+ epilogue: [],
407
+ warnings: [],
408
+ summary: 'Test parallel',
409
+ estimatedTools: 3,
410
+ context: {
411
+ intent: 'BUILD',
412
+ probes: {
413
+ vault: true,
414
+ brain: false,
415
+ designSystem: false,
416
+ sessionStore: true,
417
+ projectRules: false,
418
+ active: true,
419
+ },
420
+ entities: { components: [], actions: [] },
421
+ projectPath: '/test',
422
+ },
423
+ };
424
+
425
+ const result = await executor.execute(plan);
426
+ expect(result.toolsCalled.length).toBe(3);
427
+ expect(result.toolsCalled).toContain('a');
428
+ expect(result.toolsCalled).toContain('b');
429
+ expect(result.toolsCalled).toContain('c');
430
+ });
431
+ });
432
+
433
+ // ---------------------------------------------------------------------------
434
+ // Context Router
435
+ // ---------------------------------------------------------------------------
436
+
437
+ import { detectContext, applyContextOverrides } from '../flows/context-router.js';
438
+ import { flowStepsToPlanSteps } from '../flows/plan-builder.js';
439
+
440
+ describe('detectContext', () => {
441
+ const emptyEntities = { components: [], actions: [] };
442
+
443
+ it('should find "small-component" context for button prompts', () => {
444
+ const contexts = detectContext('Build a submit button', emptyEntities);
445
+ expect(contexts).toContain('small-component');
446
+ });
447
+
448
+ it('should find "large-component" context for dashboard prompts', () => {
449
+ const contexts = detectContext('Create a dashboard layout', emptyEntities);
450
+ expect(contexts).toContain('large-component');
451
+ });
452
+
453
+ it('should find "form-component" context for input prompts', () => {
454
+ const contexts = detectContext('Build a select dropdown input', emptyEntities);
455
+ expect(contexts).toContain('form-component');
456
+ });
457
+
458
+ it('should find "container-component" context for modal prompts', () => {
459
+ const contexts = detectContext('Build a confirmation dialog', emptyEntities);
460
+ expect(contexts).toContain('container-component');
461
+ });
462
+
463
+ it('should find "design-fix" context for styling fix prompts', () => {
464
+ const contexts = detectContext('Fix the color tokens in the header', emptyEntities);
465
+ expect(contexts).toContain('design-fix');
466
+ });
467
+
468
+ it('should find "a11y-fix" context for accessibility fix prompts', () => {
469
+ const contexts = detectContext('Fix accessibility issues with ARIA labels', emptyEntities);
470
+ expect(contexts).toContain('a11y-fix');
471
+ });
472
+
473
+ it('should find "pr-review" context for pull request prompts', () => {
474
+ const contexts = detectContext('Review this PR diff', emptyEntities);
475
+ expect(contexts).toContain('pr-review');
476
+ });
477
+
478
+ it('should find "architecture-review" context for architecture prompts', () => {
479
+ const contexts = detectContext('Review the import structure', emptyEntities);
480
+ expect(contexts).toContain('architecture-review');
481
+ });
482
+
483
+ it('should return empty array for generic prompts', () => {
484
+ const contexts = detectContext('Do something useful', emptyEntities);
485
+ expect(contexts).toHaveLength(0);
486
+ });
487
+
488
+ it('should detect multiple contexts when prompt matches several', () => {
489
+ const contexts = detectContext(
490
+ 'Build a form with input fields and a submit button',
491
+ emptyEntities,
492
+ );
493
+ expect(contexts).toContain('small-component');
494
+ expect(contexts).toContain('form-component');
495
+ });
496
+
497
+ it('should also match entity content', () => {
498
+ const contexts = detectContext('Build this', { components: ['Button'], actions: [] });
499
+ expect(contexts).toContain('small-component');
500
+ });
501
+ });
502
+
503
+ describe('applyContextOverrides', () => {
504
+ const agentId = 'test';
505
+
506
+ function loadBuildSteps(): PlanStep[] {
507
+ const flow = loadFlowById('BUILD-flow', FLOWS_DIR);
508
+ return flowStepsToPlanSteps(flow!, agentId);
509
+ }
510
+
511
+ function loadFixSteps(): PlanStep[] {
512
+ const flow = loadFlowById('FIX-flow', FLOWS_DIR);
513
+ return flowStepsToPlanSteps(flow!, agentId);
514
+ }
515
+
516
+ it('should skip get-architecture for small-component context', () => {
517
+ const steps = loadBuildSteps();
518
+ const result = applyContextOverrides(steps, ['small-component'], 'BUILD-flow', agentId);
519
+ const ids = result.map((s) => s.id);
520
+ expect(ids).not.toContain('get-architecture');
521
+ });
522
+
523
+ it('should inject button-semantics-check before validate for small-component', () => {
524
+ const steps = loadBuildSteps();
525
+ const result = applyContextOverrides(steps, ['small-component'], 'BUILD-flow', agentId);
526
+ const ids = result.map((s) => s.id);
527
+ expect(ids).toContain('ctx-before-validate');
528
+ const injected = result.find((s) => s.id === 'ctx-before-validate');
529
+ expect(injected!.tools).toContain('test_button_semantics_check');
530
+ });
531
+
532
+ it('should inject responsive-patterns before validate for large-component', () => {
533
+ const steps = loadBuildSteps();
534
+ const result = applyContextOverrides(steps, ['large-component'], 'BUILD-flow', agentId);
535
+ const ids = result.map((s) => s.id);
536
+ const beforeIdx = ids.indexOf('ctx-before-validate');
537
+ const validateIdx = ids.indexOf('validate');
538
+ expect(beforeIdx).toBeGreaterThan(-1);
539
+ expect(beforeIdx).toBeLessThan(validateIdx);
540
+ const injected = result.find((s) => s.id === 'ctx-before-validate');
541
+ expect(injected!.tools).toContain('test_responsive_patterns');
542
+ });
543
+
544
+ it('should inject performance-check after validate for large-component', () => {
545
+ const steps = loadBuildSteps();
546
+ const result = applyContextOverrides(steps, ['large-component'], 'BUILD-flow', agentId);
547
+ const ids = result.map((s) => s.id);
548
+ const afterIdx = ids.indexOf('ctx-after-validate');
549
+ const validateIdx = ids.indexOf('validate');
550
+ expect(afterIdx).toBeGreaterThan(validateIdx);
551
+ const injected = result.find((s) => s.id === 'ctx-after-validate');
552
+ expect(injected!.tools).toContain('test_performance_check');
553
+ });
554
+
555
+ it('should inject contrast-check and token-validation for design-fix context', () => {
556
+ const steps = loadFixSteps();
557
+ const result = applyContextOverrides(steps, ['design-fix'], 'FIX-flow', agentId);
558
+ const injected = result.find((s) => s.id === 'ctx-before-validate');
559
+ expect(injected).toBeDefined();
560
+ expect(injected!.tools).toContain('test_contrast_check');
561
+ expect(injected!.tools).toContain('test_token_validation');
562
+ });
563
+
564
+ it('should return steps unchanged for unknown flow', () => {
565
+ const steps = loadBuildSteps();
566
+ const result = applyContextOverrides(steps, ['small-component'], 'UNKNOWN-flow', agentId);
567
+ expect(result).toEqual(steps);
568
+ });
569
+
570
+ it('should return steps unchanged for empty contexts', () => {
571
+ const steps = loadBuildSteps();
572
+ const result = applyContextOverrides(steps, [], 'BUILD-flow', agentId);
573
+ expect(result).toEqual(steps);
574
+ });
575
+ });
576
+
577
+ // ---------------------------------------------------------------------------
578
+ // Context Probes
579
+ // ---------------------------------------------------------------------------
580
+
581
+ describe('Context Probes', () => {
582
+ let runtime: AgentRuntime;
583
+
584
+ beforeEach(() => {
585
+ runtime = createAgentRuntime({ agentId: 'test-probes', vaultPath: ':memory:' });
586
+ });
587
+
588
+ afterEach(() => {
589
+ runtime.close();
590
+ });
591
+
592
+ it('should return probe results', async () => {
593
+ const probes = await runProbes(runtime, '/tmp/nonexistent');
594
+ expect(typeof probes.vault).toBe('boolean');
595
+ expect(typeof probes.brain).toBe('boolean');
596
+ expect(typeof probes.active).toBe('boolean');
597
+ expect(probes.active).toBe(true);
598
+ });
599
+
600
+ it('should detect vault as available', async () => {
601
+ const probes = await runProbes(runtime, '/tmp/nonexistent');
602
+ expect(probes.vault).toBe(true); // :memory: vault is connected
603
+ });
604
+ });
@@ -308,9 +308,9 @@ describe('mergePlaybooks', () => {
308
308
  });
309
309
 
310
310
  describe('getAllBuiltinPlaybooks', () => {
311
- it('should return 6 built-in playbooks', () => {
311
+ it('should return all built-in playbooks', () => {
312
312
  const all = getAllBuiltinPlaybooks();
313
- expect(all).toHaveLength(6);
313
+ expect(all.length).toBeGreaterThanOrEqual(6);
314
314
  });
315
315
 
316
316
  it('should all be generic tier', () => {
@@ -101,32 +101,32 @@ describe('seedDefaultPlaybooks', () => {
101
101
  vault.close();
102
102
  });
103
103
 
104
- it('should seed 6 built-in playbooks into empty vault', () => {
104
+ it('should seed built-in playbooks into empty vault', () => {
105
105
  const result = seedDefaultPlaybooks(vault);
106
- expect(result.seeded).toBe(6);
106
+ expect(result.seeded).toBeGreaterThanOrEqual(6);
107
107
  expect(result.skipped).toBe(0);
108
108
  expect(result.errors).toBe(0);
109
- expect(result.details).toHaveLength(6);
109
+ expect(result.details.length).toBe(result.seeded);
110
110
  expect(result.details.every((d) => d.action === 'seeded')).toBe(true);
111
111
 
112
112
  // Verify they're in the vault
113
113
  const entries = vault.list({ type: 'playbook' });
114
- expect(entries).toHaveLength(6);
114
+ expect(entries.length).toBe(result.seeded);
115
115
  });
116
116
 
117
117
  it('should be idempotent — second call skips all', () => {
118
- seedDefaultPlaybooks(vault);
118
+ const first = seedDefaultPlaybooks(vault);
119
119
  const result = seedDefaultPlaybooks(vault);
120
120
 
121
121
  expect(result.seeded).toBe(0);
122
- expect(result.skipped).toBe(6);
122
+ expect(result.skipped).toBe(first.seeded);
123
123
  expect(result.errors).toBe(0);
124
124
  expect(result.details.every((d) => d.action === 'skipped')).toBe(true);
125
125
  });
126
126
 
127
127
  it('should not overwrite user modifications', () => {
128
128
  // Seed first
129
- seedDefaultPlaybooks(vault);
129
+ const first = seedDefaultPlaybooks(vault);
130
130
 
131
131
  // Simulate user modifying a playbook by removing and re-adding with different content
132
132
  const builtins = getAllBuiltinPlaybooks();
@@ -143,7 +143,7 @@ describe('seedDefaultPlaybooks', () => {
143
143
 
144
144
  // Re-seed should skip this one
145
145
  const result = seedDefaultPlaybooks(vault);
146
- expect(result.skipped).toBe(6); // all exist, including user-modified one
146
+ expect(result.skipped).toBe(first.seeded); // all exist, including user-modified one
147
147
 
148
148
  // Verify user's version is preserved
149
149
  const entry = vault.get(builtins[0].id);