@studio-foundation/engine 0.3.0-beta.1 → 0.3.0-beta.6

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 (246) hide show
  1. package/dist/__tests__/engine.conditions-group.test.d.ts +2 -0
  2. package/dist/__tests__/engine.conditions-group.test.d.ts.map +1 -0
  3. package/dist/__tests__/engine.conditions-group.test.js +222 -0
  4. package/dist/__tests__/engine.conditions-group.test.js.map +1 -0
  5. package/dist/__tests__/engine.conditions.test.d.ts +2 -0
  6. package/dist/__tests__/engine.conditions.test.d.ts.map +1 -0
  7. package/dist/__tests__/engine.conditions.test.js +164 -0
  8. package/dist/__tests__/engine.conditions.test.js.map +1 -0
  9. package/dist/__tests__/engine.resume.test.d.ts +2 -0
  10. package/dist/__tests__/engine.resume.test.d.ts.map +1 -0
  11. package/dist/__tests__/engine.resume.test.js +93 -0
  12. package/dist/__tests__/engine.resume.test.js.map +1 -0
  13. package/dist/__tests__/engine.script-stage.test.d.ts +2 -0
  14. package/dist/__tests__/engine.script-stage.test.d.ts.map +1 -0
  15. package/dist/__tests__/engine.script-stage.test.js +108 -0
  16. package/dist/__tests__/engine.script-stage.test.js.map +1 -0
  17. package/dist/db/client.d.ts +3 -0
  18. package/dist/db/client.d.ts.map +1 -0
  19. package/{src/db/client.ts → dist/db/client.js} +1 -2
  20. package/dist/db/client.js.map +1 -0
  21. package/dist/engine.context-event.test.d.ts +2 -0
  22. package/dist/engine.context-event.test.d.ts.map +1 -0
  23. package/dist/engine.context-event.test.js +111 -0
  24. package/dist/engine.context-event.test.js.map +1 -0
  25. package/dist/engine.d.ts +52 -0
  26. package/dist/engine.d.ts.map +1 -0
  27. package/dist/engine.js +364 -0
  28. package/dist/engine.js.map +1 -0
  29. package/dist/events.d.ts +163 -0
  30. package/dist/events.d.ts.map +1 -0
  31. package/dist/events.js +12 -0
  32. package/dist/events.js.map +1 -0
  33. package/{src/index.ts → dist/index.d.ts} +3 -45
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +24 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/pipeline/agent-loader.d.ts +4 -0
  38. package/dist/pipeline/agent-loader.d.ts.map +1 -0
  39. package/dist/pipeline/agent-loader.js +28 -0
  40. package/dist/pipeline/agent-loader.js.map +1 -0
  41. package/dist/pipeline/agent-loader.test.d.ts +2 -0
  42. package/dist/pipeline/agent-loader.test.d.ts.map +1 -0
  43. package/dist/pipeline/agent-loader.test.js +134 -0
  44. package/dist/pipeline/agent-loader.test.js.map +1 -0
  45. package/dist/pipeline/condition-evaluator.d.ts +6 -0
  46. package/dist/pipeline/condition-evaluator.d.ts.map +1 -0
  47. package/dist/pipeline/condition-evaluator.js +105 -0
  48. package/dist/pipeline/condition-evaluator.js.map +1 -0
  49. package/dist/pipeline/condition-evaluator.test.d.ts +2 -0
  50. package/dist/pipeline/condition-evaluator.test.d.ts.map +1 -0
  51. package/dist/pipeline/condition-evaluator.test.js +103 -0
  52. package/dist/pipeline/condition-evaluator.test.js.map +1 -0
  53. package/dist/pipeline/context-pack-loader.d.ts +3 -0
  54. package/dist/pipeline/context-pack-loader.d.ts.map +1 -0
  55. package/dist/pipeline/context-pack-loader.js +51 -0
  56. package/dist/pipeline/context-pack-loader.js.map +1 -0
  57. package/dist/pipeline/context-propagation.d.ts +28 -0
  58. package/dist/pipeline/context-propagation.d.ts.map +1 -0
  59. package/dist/pipeline/context-propagation.js +161 -0
  60. package/dist/pipeline/context-propagation.js.map +1 -0
  61. package/dist/pipeline/context-propagation.test.d.ts +2 -0
  62. package/dist/pipeline/context-propagation.test.d.ts.map +1 -0
  63. package/dist/pipeline/context-propagation.test.js +194 -0
  64. package/dist/pipeline/context-propagation.test.js.map +1 -0
  65. package/dist/pipeline/contract-loader.d.ts +4 -0
  66. package/dist/pipeline/contract-loader.d.ts.map +1 -0
  67. package/dist/pipeline/contract-loader.js +30 -0
  68. package/dist/pipeline/contract-loader.js.map +1 -0
  69. package/dist/pipeline/group-orchestrator.d.ts +19 -0
  70. package/dist/pipeline/group-orchestrator.d.ts.map +1 -0
  71. package/dist/pipeline/group-orchestrator.js +376 -0
  72. package/dist/pipeline/group-orchestrator.js.map +1 -0
  73. package/dist/pipeline/hook-executor.d.ts +28 -0
  74. package/dist/pipeline/hook-executor.d.ts.map +1 -0
  75. package/dist/pipeline/hook-executor.js +63 -0
  76. package/dist/pipeline/hook-executor.js.map +1 -0
  77. package/dist/pipeline/hook-executor.test.d.ts +2 -0
  78. package/dist/pipeline/hook-executor.test.d.ts.map +1 -0
  79. package/dist/pipeline/hook-executor.test.js +71 -0
  80. package/dist/pipeline/hook-executor.test.js.map +1 -0
  81. package/dist/pipeline/invariants-loader.d.ts +6 -0
  82. package/dist/pipeline/invariants-loader.d.ts.map +1 -0
  83. package/dist/pipeline/invariants-loader.js +15 -0
  84. package/dist/pipeline/invariants-loader.js.map +1 -0
  85. package/dist/pipeline/invariants-loader.test.d.ts +2 -0
  86. package/dist/pipeline/invariants-loader.test.d.ts.map +1 -0
  87. package/dist/pipeline/invariants-loader.test.js +39 -0
  88. package/dist/pipeline/invariants-loader.test.js.map +1 -0
  89. package/dist/pipeline/loader.d.ts +5 -0
  90. package/dist/pipeline/loader.d.ts.map +1 -0
  91. package/dist/pipeline/loader.js +132 -0
  92. package/dist/pipeline/loader.js.map +1 -0
  93. package/dist/pipeline/loader.test.d.ts +2 -0
  94. package/dist/pipeline/loader.test.d.ts.map +1 -0
  95. package/dist/pipeline/loader.test.js +120 -0
  96. package/dist/pipeline/loader.test.js.map +1 -0
  97. package/dist/pipeline/output-validator.d.ts +10 -0
  98. package/dist/pipeline/output-validator.d.ts.map +1 -0
  99. package/dist/pipeline/output-validator.js +20 -0
  100. package/dist/pipeline/output-validator.js.map +1 -0
  101. package/dist/pipeline/output-validator.test.d.ts +2 -0
  102. package/dist/pipeline/output-validator.test.d.ts.map +1 -0
  103. package/dist/pipeline/output-validator.test.js +77 -0
  104. package/dist/pipeline/output-validator.test.js.map +1 -0
  105. package/dist/pipeline/post-validator.d.ts +8 -0
  106. package/dist/pipeline/post-validator.d.ts.map +1 -0
  107. package/dist/pipeline/post-validator.js +103 -0
  108. package/dist/pipeline/post-validator.js.map +1 -0
  109. package/dist/pipeline/skill-loader.d.ts +10 -0
  110. package/dist/pipeline/skill-loader.d.ts.map +1 -0
  111. package/dist/pipeline/skill-loader.js +26 -0
  112. package/dist/pipeline/skill-loader.js.map +1 -0
  113. package/dist/pipeline/skill-loader.test.d.ts +2 -0
  114. package/dist/pipeline/skill-loader.test.d.ts.map +1 -0
  115. package/dist/pipeline/skill-loader.test.js +38 -0
  116. package/dist/pipeline/skill-loader.test.js.map +1 -0
  117. package/dist/pipeline/stage-executor.d.ts +26 -0
  118. package/dist/pipeline/stage-executor.d.ts.map +1 -0
  119. package/dist/pipeline/stage-executor.js +540 -0
  120. package/dist/pipeline/stage-executor.js.map +1 -0
  121. package/dist/pipeline/stage-resolver.d.ts +3 -0
  122. package/dist/pipeline/stage-resolver.d.ts.map +1 -0
  123. package/dist/pipeline/stage-resolver.js +6 -0
  124. package/dist/pipeline/stage-resolver.js.map +1 -0
  125. package/dist/pipeline/startup-executor.d.ts +3 -0
  126. package/dist/pipeline/startup-executor.d.ts.map +1 -0
  127. package/dist/pipeline/startup-executor.js +23 -0
  128. package/dist/pipeline/startup-executor.js.map +1 -0
  129. package/dist/pipeline/startup-executor.test.d.ts +2 -0
  130. package/dist/pipeline/startup-executor.test.d.ts.map +1 -0
  131. package/dist/pipeline/startup-executor.test.js +34 -0
  132. package/dist/pipeline/startup-executor.test.js.map +1 -0
  133. package/dist/pipeline/types.d.ts +28 -0
  134. package/dist/pipeline/types.d.ts.map +1 -0
  135. package/dist/pipeline/types.js +12 -0
  136. package/dist/pipeline/types.js.map +1 -0
  137. package/dist/repo-resolver.d.ts +10 -0
  138. package/dist/repo-resolver.d.ts.map +1 -0
  139. package/dist/repo-resolver.js +39 -0
  140. package/dist/repo-resolver.js.map +1 -0
  141. package/dist/spawners/direct-engine-spawner.d.ts +8 -0
  142. package/dist/spawners/direct-engine-spawner.d.ts.map +1 -0
  143. package/dist/spawners/direct-engine-spawner.js +24 -0
  144. package/dist/spawners/direct-engine-spawner.js.map +1 -0
  145. package/dist/state/run-store.d.ts +74 -0
  146. package/dist/state/run-store.d.ts.map +1 -0
  147. package/dist/state/run-store.js +269 -0
  148. package/dist/state/run-store.js.map +1 -0
  149. package/dist/state/run-store.test.d.ts +2 -0
  150. package/dist/state/run-store.test.d.ts.map +1 -0
  151. package/dist/state/run-store.test.js +130 -0
  152. package/dist/state/run-store.test.js.map +1 -0
  153. package/dist/state/state-machine.d.ts +6 -0
  154. package/dist/state/state-machine.d.ts.map +1 -0
  155. package/dist/state/state-machine.js +27 -0
  156. package/dist/state/state-machine.js.map +1 -0
  157. package/dist/state/status-derivation.d.ts +12 -0
  158. package/dist/state/status-derivation.d.ts.map +1 -0
  159. package/dist/state/status-derivation.js +30 -0
  160. package/dist/state/status-derivation.js.map +1 -0
  161. package/package.json +7 -4
  162. package/ARCHITECTURE.md +0 -57
  163. package/src/__tests__/__fixtures__/script-stage/contracts/book-context.contract.yaml +0 -4
  164. package/src/__tests__/engine.conditions-group.test.ts +0 -244
  165. package/src/__tests__/engine.conditions.test.ts +0 -186
  166. package/src/__tests__/engine.resume.test.ts +0 -108
  167. package/src/__tests__/engine.script-stage.test.ts +0 -125
  168. package/src/engine.context-event.test.ts +0 -175
  169. package/src/engine.ts +0 -491
  170. package/src/events.ts +0 -167
  171. package/src/pipeline/agent-loader.test.ts +0 -151
  172. package/src/pipeline/agent-loader.ts +0 -39
  173. package/src/pipeline/condition-evaluator.test.ts +0 -129
  174. package/src/pipeline/condition-evaluator.ts +0 -121
  175. package/src/pipeline/context-pack-loader.ts +0 -63
  176. package/src/pipeline/context-propagation.test.ts +0 -237
  177. package/src/pipeline/context-propagation.ts +0 -235
  178. package/src/pipeline/contract-loader.ts +0 -41
  179. package/src/pipeline/group-orchestrator.ts +0 -483
  180. package/src/pipeline/hook-executor.test.ts +0 -121
  181. package/src/pipeline/hook-executor.ts +0 -87
  182. package/src/pipeline/invariants-loader.test.ts +0 -51
  183. package/src/pipeline/invariants-loader.ts +0 -14
  184. package/src/pipeline/loader.test.ts +0 -128
  185. package/src/pipeline/loader.ts +0 -149
  186. package/src/pipeline/output-validator.test.ts +0 -100
  187. package/src/pipeline/output-validator.ts +0 -40
  188. package/src/pipeline/post-validator.ts +0 -124
  189. package/src/pipeline/skill-loader.test.ts +0 -44
  190. package/src/pipeline/skill-loader.ts +0 -32
  191. package/src/pipeline/stage-executor.ts +0 -654
  192. package/src/pipeline/stage-resolver.ts +0 -8
  193. package/src/pipeline/startup-executor.test.ts +0 -37
  194. package/src/pipeline/startup-executor.ts +0 -32
  195. package/src/pipeline/types.ts +0 -42
  196. package/src/repo-resolver.ts +0 -61
  197. package/src/spawners/direct-engine-spawner.ts +0 -26
  198. package/src/state/run-store.test.ts +0 -157
  199. package/src/state/run-store.ts +0 -362
  200. package/src/state/state-machine.ts +0 -35
  201. package/src/state/status-derivation.ts +0 -36
  202. package/tests/context-pack-loader.test.ts +0 -113
  203. package/tests/context-propagation.test.ts +0 -267
  204. package/tests/direct-engine-spawner.test.ts +0 -102
  205. package/tests/e2e/feature-v5.test.ts +0 -57
  206. package/tests/events.test.ts +0 -56
  207. package/tests/fixtures/agents/test-agent.agent.yaml +0 -5
  208. package/tests/fixtures/contracts/code-gen.contract.yaml +0 -6
  209. package/tests/fixtures/contracts/qa-gate.contract.yaml +0 -15
  210. package/tests/fixtures/contracts/test-contract.contract.yaml +0 -6
  211. package/tests/fixtures/pipelines/group-test.pipeline.yaml +0 -40
  212. package/tests/fixtures/pipelines/simple.pipeline.yaml +0 -15
  213. package/tests/fixtures/pipelines/two-stage.pipeline.yaml +0 -24
  214. package/tests/fixtures/software/pipelines/feature-builder.pipeline.yaml +0 -75
  215. package/tests/fixtures/test-project/agents/test-agent.agent.yaml +0 -5
  216. package/tests/fixtures/test-project/contracts/basic-result.contract.yaml +0 -6
  217. package/tests/fixtures/test-project/contracts/code-gen.contract.yaml +0 -6
  218. package/tests/fixtures/test-project/contracts/maximum-only.contract.yaml +0 -8
  219. package/tests/fixtures/test-project/contracts/qa-gate.contract.yaml +0 -15
  220. package/tests/fixtures/test-project/contracts/strict-result.contract.yaml +0 -7
  221. package/tests/fixtures/test-project/contracts/test-contract.contract.yaml +0 -6
  222. package/tests/fixtures/test-project/pipelines/group-simple.pipeline.yaml +0 -26
  223. package/tests/fixtures/test-project/pipelines/group-test.pipeline.yaml +0 -40
  224. package/tests/fixtures/test-project/pipelines/hook-output-template.pipeline.yaml +0 -19
  225. package/tests/fixtures/test-project/pipelines/hook-reject-on-failure.pipeline.yaml +0 -19
  226. package/tests/fixtures/test-project/pipelines/maximum-only.pipeline.yaml +0 -15
  227. package/tests/fixtures/test-project/pipelines/parallel-collect-all-test.pipeline.yaml +0 -39
  228. package/tests/fixtures/test-project/pipelines/parallel-context-isolation-test.pipeline.yaml +0 -38
  229. package/tests/fixtures/test-project/pipelines/parallel-fail-test.pipeline.yaml +0 -39
  230. package/tests/fixtures/test-project/pipelines/parallel-test.pipeline.yaml +0 -39
  231. package/tests/fixtures/test-project/pipelines/parallel-then-sequential-test.pipeline.yaml +0 -38
  232. package/tests/fixtures/test-project/pipelines/simple.pipeline.yaml +0 -15
  233. package/tests/fixtures/test-project/pipelines/two-stage.pipeline.yaml +0 -24
  234. package/tests/fixtures/test-project/pipelines/with-startup.pipeline.yaml +0 -19
  235. package/tests/loader.test.ts +0 -385
  236. package/tests/post-validator.test.ts +0 -297
  237. package/tests/repo-resolver.test.ts +0 -102
  238. package/tests/run-store.test.ts +0 -143
  239. package/tests/state-machine.test.ts +0 -110
  240. package/tests/unit/context-propagation.test.ts +0 -45
  241. package/tests/unit/engine.test.ts +0 -770
  242. package/tests/unit/group-loop.test.ts +0 -389
  243. package/tests/unit/group-parallel.test.ts +0 -423
  244. package/tests/unit/state/status-derivation.test.ts +0 -88
  245. package/tsconfig.json +0 -24
  246. package/vitest.config.ts +0 -14
@@ -1,423 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { join } from 'node:path';
3
- import { PipelineEngine } from '../../src/engine.js';
4
- import { InMemoryRunStore } from '../../src/state/run-store.js';
5
- import type { EngineEvents } from '../../src/events.js';
6
- import { mkdirSync, writeFileSync } from 'node:fs';
7
-
8
- const FIXTURES_DIR = join(import.meta.dirname, '..', 'fixtures');
9
- const PROJECT_DIR = join(FIXTURES_DIR, 'test-project');
10
- const PIPELINES_DIR = join(PROJECT_DIR, 'pipelines');
11
- const AGENTS_DIR = join(PROJECT_DIR, 'agents');
12
- const CONTRACTS_DIR = join(PROJECT_DIR, 'contracts');
13
-
14
- mkdirSync(PIPELINES_DIR, { recursive: true });
15
- mkdirSync(AGENTS_DIR, { recursive: true });
16
- mkdirSync(CONTRACTS_DIR, { recursive: true });
17
-
18
- // Reuse agent fixture from group-loop.test.ts (idempotent writeFileSync)
19
- writeFileSync(join(AGENTS_DIR, 'test-agent.agent.yaml'), `
20
- name: test-agent
21
- provider: anthropic
22
- model: claude-sonnet-4-20250514
23
- temperature: 0.3
24
- `);
25
-
26
- // Contract requiring 'result' field
27
- writeFileSync(join(CONTRACTS_DIR, 'basic-result.contract.yaml'), `
28
- name: basic-result
29
- version: 1
30
- schema:
31
- required_fields:
32
- - result
33
- `);
34
-
35
- // Contract with missing required fields to trigger failure
36
- writeFileSync(join(CONTRACTS_DIR, 'strict-result.contract.yaml'), `
37
- name: strict-result
38
- version: 1
39
- schema:
40
- required_fields:
41
- - result
42
- - must_exist
43
- `);
44
-
45
- // Pipeline: 3 stages in parallel, all use basic-result contract
46
- writeFileSync(join(PIPELINES_DIR, 'parallel-test.pipeline.yaml'), `
47
- name: parallel-test
48
- description: Test pipeline with parallel group
49
- version: 1
50
- stages:
51
- - group: parallel-work
52
- mode: parallel
53
- max_iterations: 1
54
- stages:
55
- - name: stage-a
56
- kind: analysis
57
- agent: test-agent
58
- contract: basic-result
59
- ralph:
60
- max_attempts: 1
61
- retry_strategy: none
62
- context:
63
- include:
64
- - input
65
- - name: stage-b
66
- kind: analysis
67
- agent: test-agent
68
- contract: basic-result
69
- ralph:
70
- max_attempts: 1
71
- retry_strategy: none
72
- context:
73
- include:
74
- - input
75
- - name: stage-c
76
- kind: analysis
77
- agent: test-agent
78
- contract: basic-result
79
- ralph:
80
- max_attempts: 1
81
- retry_strategy: none
82
- context:
83
- include:
84
- - input
85
- `);
86
-
87
- // Pipeline: parallel group where stage-b uses strict-result (will fail if wrong output)
88
- writeFileSync(join(PIPELINES_DIR, 'parallel-fail-test.pipeline.yaml'), `
89
- name: parallel-fail-test
90
- description: Test pipeline with a failing stage in parallel group
91
- version: 1
92
- stages:
93
- - group: parallel-work
94
- mode: parallel
95
- on_failure: fail-fast
96
- stages:
97
- - name: stage-a
98
- kind: analysis
99
- agent: test-agent
100
- contract: basic-result
101
- ralph:
102
- max_attempts: 1
103
- retry_strategy: none
104
- context:
105
- include:
106
- - input
107
- - name: stage-b
108
- kind: analysis
109
- agent: test-agent
110
- contract: strict-result
111
- ralph:
112
- max_attempts: 1
113
- retry_strategy: none
114
- context:
115
- include:
116
- - input
117
- - name: stage-c
118
- kind: analysis
119
- agent: test-agent
120
- contract: basic-result
121
- ralph:
122
- max_attempts: 1
123
- retry_strategy: none
124
- context:
125
- include:
126
- - input
127
- `);
128
-
129
- // Pipeline: parallel group with collect-all
130
- writeFileSync(join(PIPELINES_DIR, 'parallel-collect-all-test.pipeline.yaml'), `
131
- name: parallel-collect-all-test
132
- description: Test pipeline with collect-all parallel group
133
- version: 1
134
- stages:
135
- - group: parallel-work
136
- mode: parallel
137
- on_failure: collect-all
138
- stages:
139
- - name: stage-a
140
- kind: analysis
141
- agent: test-agent
142
- contract: basic-result
143
- ralph:
144
- max_attempts: 1
145
- retry_strategy: none
146
- context:
147
- include:
148
- - input
149
- - name: stage-b
150
- kind: analysis
151
- agent: test-agent
152
- contract: strict-result
153
- ralph:
154
- max_attempts: 1
155
- retry_strategy: none
156
- context:
157
- include:
158
- - input
159
- - name: stage-c
160
- kind: analysis
161
- agent: test-agent
162
- contract: basic-result
163
- ralph:
164
- max_attempts: 1
165
- retry_strategy: none
166
- context:
167
- include:
168
- - input
169
- `);
170
-
171
- // Pipeline: parallel group followed by a sequential stage
172
- writeFileSync(join(PIPELINES_DIR, 'parallel-then-sequential-test.pipeline.yaml'), `
173
- name: parallel-then-sequential-test
174
- description: Test pipeline with parallel group followed by sequential stage
175
- version: 1
176
- stages:
177
- - group: parallel-work
178
- mode: parallel
179
- stages:
180
- - name: stage-a
181
- kind: analysis
182
- agent: test-agent
183
- contract: basic-result
184
- ralph:
185
- max_attempts: 1
186
- retry_strategy: none
187
- context:
188
- include:
189
- - input
190
- - name: stage-b
191
- kind: analysis
192
- agent: test-agent
193
- contract: basic-result
194
- ralph:
195
- max_attempts: 1
196
- retry_strategy: none
197
- context:
198
- include:
199
- - input
200
- - name: merge-results
201
- kind: merge
202
- agent: test-agent
203
- contract: basic-result
204
- ralph:
205
- max_attempts: 1
206
- retry_strategy: none
207
- context:
208
- include:
209
- - all_stage_outputs
210
- `);
211
-
212
- function mockProvider(callFn: (...args: any[]) => any) {
213
- return {
214
- name: 'anthropic',
215
- call: vi.fn(callFn),
216
- };
217
- }
218
-
219
- function createMockToolRegistry() {
220
- return {
221
- register: vi.fn(),
222
- get: vi.fn(),
223
- has: vi.fn().mockReturnValue(false),
224
- list: vi.fn().mockReturnValue([]),
225
- toToolDefinitions: vi.fn().mockReturnValue([]),
226
- filter: vi.fn().mockReturnThis(),
227
- getActiveSnippets: vi.fn().mockReturnValue([]),
228
- clone: vi.fn().mockReturnThis(),
229
- };
230
- }
231
-
232
- function createEngine(provider: any, events?: EngineEvents): PipelineEngine {
233
- return new PipelineEngine(
234
- {
235
- configsDir: PROJECT_DIR,
236
- providerRegistry: { get: vi.fn().mockReturnValue(provider), register: vi.fn() } as any,
237
- toolRegistry: createMockToolRegistry() as any,
238
- db: new InMemoryRunStore(),
239
- },
240
- events
241
- );
242
- }
243
-
244
- function successResponse(extra: Record<string, unknown> = {}) {
245
- return {
246
- content: JSON.stringify({ result: 'ok', ...extra }),
247
- tool_calls: [],
248
- finish_reason: 'stop',
249
- usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },
250
- };
251
- }
252
-
253
- describe('Parallel group', () => {
254
- it('runs all stages concurrently and succeeds when all pass', async () => {
255
- const provider = mockProvider(() => successResponse());
256
- const engine = createEngine(provider);
257
-
258
- const result = await engine.run({ pipeline: 'parallel-test', input: 'Test' });
259
-
260
- expect(result.status).toBe('success');
261
- expect(result.stages).toHaveLength(3);
262
- expect(result.stages.map(s => s.stage_name)).toEqual(['stage-a', 'stage-b', 'stage-c']);
263
- expect(provider.call).toHaveBeenCalledTimes(3);
264
- });
265
-
266
- it('stage runs are ordered by definition order (not execution order)', async () => {
267
- const provider = mockProvider(() => successResponse());
268
- const engine = createEngine(provider);
269
-
270
- const result = await engine.run({ pipeline: 'parallel-test', input: 'Test' });
271
-
272
- expect(result.stages[0].stage_name).toBe('stage-a');
273
- expect(result.stages[1].stage_name).toBe('stage-b');
274
- expect(result.stages[2].stage_name).toBe('stage-c');
275
- });
276
-
277
- it('fails group when one stage fails (fail-fast)', async () => {
278
- // stage-b uses strict-result contract which requires 'must_exist' — mock only returns 'result'
279
- const provider = mockProvider(() => successResponse());
280
- const engine = createEngine(provider);
281
-
282
- const result = await engine.run({ pipeline: 'parallel-fail-test', input: 'Test' });
283
-
284
- expect(result.status).toBe('failed');
285
- // stage-b failed, overall group failed
286
- const stageBRun = result.stages.find(s => s.stage_name === 'stage-b');
287
- expect(stageBRun).toBeDefined();
288
- expect(stageBRun?.status).toBe('failed');
289
- });
290
-
291
- it('fails group when one stage fails (collect-all), all stages still run', async () => {
292
- // stage-b uses strict-result — will fail
293
- const provider = mockProvider(() => successResponse());
294
- const engine = createEngine(provider);
295
-
296
- const result = await engine.run({ pipeline: 'parallel-collect-all-test', input: 'Test' });
297
-
298
- expect(result.status).toBe('failed');
299
- // All 3 stages were executed (collect-all doesn't abort)
300
- expect(provider.call).toHaveBeenCalledTimes(3);
301
- // The 3 stage runs are all present
302
- expect(result.stages).toHaveLength(3);
303
- });
304
-
305
- it('merges successful stage outputs into context after group succeeds', async () => {
306
- let callCount = 0;
307
- const provider = mockProvider(() => {
308
- callCount++;
309
- return successResponse({ call_number: callCount });
310
- });
311
- const engine = createEngine(provider);
312
-
313
- // parallel-then-sequential-test: group (stage-a, stage-b) then merge-results stage
314
- // merge-results stage uses all_stage_outputs — it will receive stage-a and stage-b outputs
315
- const result = await engine.run({ pipeline: 'parallel-then-sequential-test', input: 'Test' });
316
-
317
- expect(result.status).toBe('success');
318
- // 3 calls total: stage-a, stage-b (parallel), then merge-results (sequential)
319
- expect(provider.call).toHaveBeenCalledTimes(3);
320
- // Verify merge-results received stage outputs (check via the last provider call's messages)
321
- const lastCallArg = (provider.call.mock.calls[2] as any[])[0];
322
- const lastCallMessages: any[] = lastCallArg?.messages ?? lastCallArg ?? [];
323
- const userMsg = lastCallMessages.find((m: any) => m.role === 'user');
324
- expect(userMsg?.content).toContain('stage-a');
325
- expect(userMsg?.content).toContain('stage-b');
326
- });
327
-
328
- it('parallel stages cannot see each other outputs (pre-group snapshot only)', async () => {
329
- // All parallel stages use context include: [all_stage_outputs]
330
- // They should only see pre-group stage outputs, not sibling outputs
331
- writeFileSync(join(PIPELINES_DIR, 'parallel-context-isolation-test.pipeline.yaml'), `
332
- name: parallel-context-isolation-test
333
- description: context isolation test
334
- version: 1
335
- stages:
336
- - name: pre-stage
337
- kind: analysis
338
- agent: test-agent
339
- contract: basic-result
340
- ralph:
341
- max_attempts: 1
342
- retry_strategy: none
343
- context:
344
- include:
345
- - input
346
- - group: parallel-work
347
- mode: parallel
348
- stages:
349
- - name: stage-a
350
- kind: analysis
351
- agent: test-agent
352
- contract: basic-result
353
- ralph:
354
- max_attempts: 1
355
- retry_strategy: none
356
- context:
357
- include:
358
- - all_stage_outputs
359
- - name: stage-b
360
- kind: analysis
361
- agent: test-agent
362
- contract: basic-result
363
- ralph:
364
- max_attempts: 1
365
- retry_strategy: none
366
- context:
367
- include:
368
- - all_stage_outputs
369
- `);
370
-
371
- const capturedMessages: Record<string, any[]> = {};
372
- let callCount = 0;
373
- const provider = mockProvider((...args: any[]) => {
374
- callCount++;
375
- capturedMessages[`call-${callCount}`] = args[0]?.messages ?? args[0] ?? [];
376
- return successResponse({ call_num: callCount });
377
- });
378
- const engine = createEngine(provider);
379
-
380
- await engine.run({ pipeline: 'parallel-context-isolation-test', input: 'Test' });
381
-
382
- // calls 2 and 3 are the parallel stages (stage-a and stage-b)
383
- // They should see 'pre-stage' output but NOT each other's outputs
384
- const parallelCall1 = capturedMessages['call-2'];
385
- const parallelCall2 = capturedMessages['call-3'];
386
-
387
- const msg1 = parallelCall1?.find((m: any) => m.role === 'user')?.content ?? '';
388
- const msg2 = parallelCall2?.find((m: any) => m.role === 'user')?.content ?? '';
389
-
390
- // Both parallel stages should see pre-stage output
391
- expect(msg1).toContain('pre-stage');
392
- expect(msg2).toContain('pre-stage');
393
-
394
- // stage-a should NOT see stage-b output, and vice versa
395
- expect(msg1).not.toContain('stage-b');
396
- expect(msg2).not.toContain('stage-a');
397
- });
398
-
399
- it('emits group lifecycle events with iteration=1', async () => {
400
- const provider = mockProvider(() => successResponse());
401
- const events: Array<{ type: string; data: any }> = [];
402
-
403
- const engineEvents: EngineEvents = {
404
- onGroupStart: (e) => events.push({ type: 'start', data: e }),
405
- onGroupIteration: (e) => events.push({ type: 'iteration', data: e }),
406
- onGroupFeedback: (e) => events.push({ type: 'feedback', data: e }),
407
- onGroupComplete: (e) => events.push({ type: 'complete', data: e }),
408
- onStageStart: () => {},
409
- onStageComplete: () => {},
410
- onPipelineStart: () => {},
411
- onPipelineComplete: () => {},
412
- };
413
-
414
- const engine = createEngine(provider, engineEvents);
415
- await engine.run({ pipeline: 'parallel-test', input: 'Test' });
416
-
417
- expect(events.find(e => e.type === 'start')).toBeDefined();
418
- expect(events.filter(e => e.type === 'iteration')).toHaveLength(1);
419
- expect(events.find(e => e.type === 'iteration')?.data.iteration).toBe(1);
420
- expect(events.find(e => e.type === 'feedback')).toBeUndefined(); // no feedback in parallel
421
- expect(events.find(e => e.type === 'complete')?.data.status).toBe('success');
422
- });
423
- });
@@ -1,88 +0,0 @@
1
- // THIS IS THE CRITICAL TEST
2
- // Status derivation was the #1 bug in v6
3
- // Write this test FIRST, then implement the function
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { deriveStageStatus } from '../../../src/state/status-derivation.js';
7
- import type { RalphResult } from '@studio-foundation/ralph';
8
- import type { StageStatus } from '@studio-foundation/contracts';
9
-
10
- describe('deriveStageStatus', () => {
11
- it('ralph success → stage success', () => {
12
- const ralphResult: RalphResult<unknown> = {
13
- status: 'success',
14
- result: { output: 'some result' },
15
- attempts: 1
16
- };
17
-
18
- const stageStatus: StageStatus = deriveStageStatus(ralphResult);
19
- expect(stageStatus).toBe('success');
20
- });
21
-
22
- it('ralph exhausted → stage failed', () => {
23
- const ralphResult: RalphResult<unknown> = {
24
- status: 'exhausted',
25
- lastResult: { output: 'failed result' },
26
- failures: ['Validation failed', 'Tool call missing'],
27
- attempts: 3
28
- };
29
-
30
- const stageStatus: StageStatus = deriveStageStatus(ralphResult);
31
- expect(stageStatus).toBe('failed');
32
- });
33
-
34
- it('success after multiple attempts → stage success', () => {
35
- const ralphResult: RalphResult<unknown> = {
36
- status: 'success',
37
- result: { output: 'finally worked' },
38
- attempts: 4
39
- };
40
-
41
- const stageStatus: StageStatus = deriveStageStatus(ralphResult);
42
- expect(stageStatus).toBe('success');
43
- });
44
-
45
- it('success after 1 attempt → stage success', () => {
46
- const ralphResult: RalphResult<unknown> = {
47
- status: 'success',
48
- result: null,
49
- attempts: 1
50
- };
51
-
52
- const stageStatus: StageStatus = deriveStageStatus(ralphResult);
53
- expect(stageStatus).toBe('success');
54
- });
55
-
56
- it('exhausted after max attempts → stage failed', () => {
57
- const ralphResult: RalphResult<unknown> = {
58
- status: 'exhausted',
59
- lastResult: {},
60
- failures: ['Error 1', 'Error 2', 'Error 3'],
61
- attempts: 5
62
- };
63
-
64
- const stageStatus: StageStatus = deriveStageStatus(ralphResult);
65
- expect(stageStatus).toBe('failed');
66
- });
67
-
68
- it('ralph cancelled → stage cancelled', () => {
69
- const ralphResult = {
70
- status: 'cancelled' as const,
71
- lastResult: undefined,
72
- attempts: 2,
73
- };
74
-
75
- const stageStatus = deriveStageStatus(ralphResult as any);
76
- expect(stageStatus).toBe('cancelled');
77
- });
78
-
79
- it('throws error for invalid ralph status', () => {
80
- const invalidResult = {
81
- status: 'invalid_status' as any,
82
- result: {},
83
- attempts: 1
84
- };
85
-
86
- expect(() => deriveStageStatus(invalidResult as any)).toThrow('Unknown ralph status');
87
- });
88
- });
package/tsconfig.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ES2022",
5
- "moduleResolution": "bundler",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "declaration": true,
9
- "declarationMap": true,
10
- "sourceMap": true,
11
- "strict": true,
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "resolveJsonModule": true,
16
- "isolatedModules": true,
17
- "noUnusedLocals": true,
18
- "noUnusedParameters": true,
19
- "noImplicitReturns": true,
20
- "noFallthroughCasesInSwitch": true
21
- },
22
- "include": ["src/**/*"],
23
- "exclude": ["node_modules", "dist", "tests"]
24
- }
package/vitest.config.ts DELETED
@@ -1,14 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: 'node',
7
- exclude: ['dist/**', 'node_modules/**'],
8
- coverage: {
9
- provider: 'v8',
10
- reporter: ['text', 'json', 'html'],
11
- exclude: ['node_modules/', 'dist/', 'tests/']
12
- }
13
- }
14
- });