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

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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=engine.conditions-group.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.conditions-group.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/engine.conditions-group.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,222 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { resolve } from 'node:path';
3
+ import { PipelineEngine } from '../engine.js';
4
+ vi.mock('@studio-foundation/runner', async (importOriginal) => {
5
+ const actual = await importOriginal();
6
+ return {
7
+ ...actual,
8
+ runScript: vi.fn(),
9
+ };
10
+ });
11
+ import { runScript } from '@studio-foundation/runner';
12
+ const FIXTURES_DIR = resolve(__dirname, '__fixtures__/script-stage');
13
+ function makeEngine() {
14
+ return new PipelineEngine({
15
+ configsDir: FIXTURES_DIR,
16
+ providerRegistry: {},
17
+ });
18
+ }
19
+ describe('engine — conditions inside sequential groups', () => {
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ });
23
+ it('skips a conditional stage inside a sequential group', async () => {
24
+ vi.mocked(runScript).mockResolvedValue({
25
+ output: { done: true },
26
+ tool_calls: [],
27
+ tool_calls_count: 0,
28
+ duration_ms: 10,
29
+ });
30
+ const pipeline = {
31
+ name: 'test-group-conditions',
32
+ description: 'test',
33
+ version: 1,
34
+ stages: [
35
+ {
36
+ group: 'processing',
37
+ max_iterations: 1,
38
+ stages: [
39
+ {
40
+ name: 'always-runs',
41
+ executor: 'script',
42
+ script: 'scripts/run.py',
43
+ runtime: 'shell',
44
+ },
45
+ {
46
+ name: 'conditional-in-group',
47
+ executor: 'script',
48
+ script: 'scripts/optional.py',
49
+ runtime: 'shell',
50
+ condition: 'input.optional >= 1',
51
+ },
52
+ ],
53
+ },
54
+ ],
55
+ };
56
+ const engine = makeEngine();
57
+ const result = await engine.run({
58
+ pipelineDef: pipeline,
59
+ input: { optional: 0 },
60
+ });
61
+ expect(result.status).toBe('success');
62
+ expect(result.stages[0]?.status).toBe('success');
63
+ expect(result.stages[1]?.status).toBe('skipped');
64
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1);
65
+ });
66
+ it('marks group as skipped when all stages in sequential group are skipped', async () => {
67
+ vi.mocked(runScript).mockResolvedValue({
68
+ output: { done: true },
69
+ tool_calls: [],
70
+ tool_calls_count: 0,
71
+ duration_ms: 10,
72
+ });
73
+ // A stage after the group should still run
74
+ const pipeline = {
75
+ name: 'test-all-skipped-group',
76
+ description: 'test',
77
+ version: 1,
78
+ stages: [
79
+ {
80
+ group: 'optional-processing',
81
+ max_iterations: 1,
82
+ stages: [
83
+ {
84
+ name: 'optional-a',
85
+ executor: 'script',
86
+ script: 'scripts/a.py',
87
+ runtime: 'shell',
88
+ condition: 'input.run_optional >= 1',
89
+ },
90
+ {
91
+ name: 'optional-b',
92
+ executor: 'script',
93
+ script: 'scripts/b.py',
94
+ runtime: 'shell',
95
+ condition: 'input.run_optional >= 1',
96
+ },
97
+ ],
98
+ },
99
+ {
100
+ name: 'post-group-stage',
101
+ executor: 'script',
102
+ script: 'scripts/final.py',
103
+ runtime: 'shell',
104
+ },
105
+ ],
106
+ };
107
+ const engine = makeEngine();
108
+ const result = await engine.run({
109
+ pipelineDef: pipeline,
110
+ input: { run_optional: 0 },
111
+ });
112
+ expect(result.status).toBe('success');
113
+ // Both group stages skipped
114
+ expect(result.stages[0]?.status).toBe('skipped');
115
+ expect(result.stages[1]?.status).toBe('skipped');
116
+ // Post-group stage ran
117
+ expect(result.stages[2]?.status).toBe('success');
118
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only post-group-stage
119
+ });
120
+ });
121
+ describe('engine — conditions inside parallel groups', () => {
122
+ beforeEach(() => {
123
+ vi.clearAllMocks();
124
+ });
125
+ it('marks parallel group as skipped when all stages are skipped', async () => {
126
+ vi.mocked(runScript).mockResolvedValue({
127
+ output: { done: true },
128
+ tool_calls: [],
129
+ tool_calls_count: 0,
130
+ duration_ms: 10,
131
+ });
132
+ const pipeline = {
133
+ name: 'test-parallel-all-skipped',
134
+ description: 'test',
135
+ version: 1,
136
+ stages: [
137
+ {
138
+ group: 'optional-parallel',
139
+ max_iterations: 1,
140
+ mode: 'parallel',
141
+ stages: [
142
+ {
143
+ name: 'parallel-a',
144
+ executor: 'script',
145
+ script: 'scripts/a.py',
146
+ runtime: 'shell',
147
+ condition: 'input.enabled >= 1',
148
+ },
149
+ {
150
+ name: 'parallel-b',
151
+ executor: 'script',
152
+ script: 'scripts/b.py',
153
+ runtime: 'shell',
154
+ condition: 'input.enabled >= 1',
155
+ },
156
+ ],
157
+ },
158
+ {
159
+ name: 'post-group',
160
+ executor: 'script',
161
+ script: 'scripts/final.py',
162
+ runtime: 'shell',
163
+ },
164
+ ],
165
+ };
166
+ const engine = makeEngine();
167
+ const result = await engine.run({
168
+ pipelineDef: pipeline,
169
+ input: { enabled: 0 },
170
+ });
171
+ expect(result.status).toBe('success');
172
+ expect(result.stages[0]?.status).toBe('skipped');
173
+ expect(result.stages[1]?.status).toBe('skipped');
174
+ expect(result.stages[2]?.status).toBe('success');
175
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only post-group
176
+ });
177
+ it('runs parallel group normally when at least one stage is not skipped', async () => {
178
+ vi.mocked(runScript).mockResolvedValue({
179
+ output: { done: true },
180
+ tool_calls: [],
181
+ tool_calls_count: 0,
182
+ duration_ms: 10,
183
+ });
184
+ const pipeline = {
185
+ name: 'test-parallel-partial-skip',
186
+ description: 'test',
187
+ version: 1,
188
+ stages: [
189
+ {
190
+ group: 'mixed-parallel',
191
+ max_iterations: 1,
192
+ mode: 'parallel',
193
+ stages: [
194
+ {
195
+ name: 'always-runs',
196
+ executor: 'script',
197
+ script: 'scripts/a.py',
198
+ runtime: 'shell',
199
+ },
200
+ {
201
+ name: 'conditional',
202
+ executor: 'script',
203
+ script: 'scripts/b.py',
204
+ runtime: 'shell',
205
+ condition: 'input.enabled >= 1',
206
+ },
207
+ ],
208
+ },
209
+ ],
210
+ };
211
+ const engine = makeEngine();
212
+ const result = await engine.run({
213
+ pipelineDef: pipeline,
214
+ input: { enabled: 0 },
215
+ });
216
+ expect(result.status).toBe('success');
217
+ expect(result.stages[0]?.status).toBe('success');
218
+ expect(result.stages[1]?.status).toBe('skipped');
219
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only always-runs
220
+ });
221
+ });
222
+ //# sourceMappingURL=engine.conditions-group.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.conditions-group.test.js","sourceRoot":"","sources":["../../src/__tests__/engine.conditions-group.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=engine.conditions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.conditions.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/engine.conditions.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,164 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { resolve } from 'node:path';
3
+ import { PipelineEngine } from '../engine.js';
4
+ // Mock the runner so we can inspect which stages actually ran
5
+ vi.mock('@studio-foundation/runner', async (importOriginal) => {
6
+ const actual = await importOriginal();
7
+ return {
8
+ ...actual,
9
+ runScript: vi.fn(),
10
+ };
11
+ });
12
+ import { runScript } from '@studio-foundation/runner';
13
+ const FIXTURES_DIR = resolve(__dirname, '__fixtures__/script-stage');
14
+ function makeEngine() {
15
+ return new PipelineEngine({
16
+ configsDir: FIXTURES_DIR,
17
+ providerRegistry: {},
18
+ });
19
+ }
20
+ function mockScriptSuccess(output = { result: 'ok' }) {
21
+ vi.mocked(runScript).mockResolvedValue({
22
+ output,
23
+ tool_calls: [],
24
+ tool_calls_count: 0,
25
+ duration_ms: 10,
26
+ });
27
+ }
28
+ describe('engine — stage conditions', () => {
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ });
32
+ it('skips a stage when input condition is false', async () => {
33
+ mockScriptSuccess();
34
+ const pipeline = {
35
+ name: 'test-conditions',
36
+ description: 'test',
37
+ version: 1,
38
+ stages: [
39
+ {
40
+ name: 'always-runs',
41
+ executor: 'script',
42
+ script: 'scripts/parse.py',
43
+ runtime: 'shell',
44
+ },
45
+ {
46
+ name: 'conditional-stage',
47
+ executor: 'script',
48
+ script: 'scripts/parse.py',
49
+ runtime: 'shell',
50
+ condition: 'input.meals_count >= 6',
51
+ },
52
+ ],
53
+ };
54
+ const engine = makeEngine();
55
+ const result = await engine.run({
56
+ pipelineDef: pipeline,
57
+ input: { meals_count: 3 }, // condition is false — stage should skip
58
+ });
59
+ expect(result.status).toBe('success');
60
+ expect(result.stages).toHaveLength(2);
61
+ expect(result.stages[0]?.status).toBe('success');
62
+ expect(result.stages[1]?.status).toBe('skipped');
63
+ // Script was only called once (for always-runs, not conditional-stage)
64
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1);
65
+ });
66
+ it('runs a stage when input condition is true', async () => {
67
+ mockScriptSuccess();
68
+ const pipeline = {
69
+ name: 'test-conditions-true',
70
+ description: 'test',
71
+ version: 1,
72
+ stages: [
73
+ {
74
+ name: 'conditional-stage',
75
+ executor: 'script',
76
+ script: 'scripts/parse.py',
77
+ runtime: 'shell',
78
+ condition: 'input.meals_count >= 6',
79
+ },
80
+ ],
81
+ };
82
+ const engine = makeEngine();
83
+ const result = await engine.run({
84
+ pipelineDef: pipeline,
85
+ input: { meals_count: 7 }, // condition is true — stage should run
86
+ });
87
+ expect(result.status).toBe('success');
88
+ expect(result.stages[0]?.status).toBe('success');
89
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1);
90
+ });
91
+ it('skips a stage based on previous stage output', async () => {
92
+ // First call returns extraction result, second call would be entity-resolution
93
+ vi.mocked(runScript).mockResolvedValueOnce({
94
+ output: { counts: { OTHER: 0, PERSON: 2 } },
95
+ tool_calls: [],
96
+ tool_calls_count: 0,
97
+ duration_ms: 10,
98
+ });
99
+ const pipeline = {
100
+ name: 'test-conditions-stage-output',
101
+ description: 'test',
102
+ version: 1,
103
+ stages: [
104
+ {
105
+ name: 'entity-extraction',
106
+ executor: 'script',
107
+ script: 'scripts/extract.py',
108
+ runtime: 'shell',
109
+ context: { include: ['input'] },
110
+ },
111
+ {
112
+ name: 'entity-resolution-OTHER',
113
+ executor: 'script',
114
+ script: 'scripts/resolve.py',
115
+ runtime: 'shell',
116
+ condition: 'stages.entity-extraction.output.counts.OTHER > 0',
117
+ context: { include: ['all_stage_outputs'] },
118
+ },
119
+ ],
120
+ };
121
+ const engine = makeEngine();
122
+ const result = await engine.run({
123
+ pipelineDef: pipeline,
124
+ input: 'extract entities',
125
+ });
126
+ expect(result.status).toBe('success');
127
+ expect(result.stages[0]?.status).toBe('success');
128
+ expect(result.stages[1]?.status).toBe('skipped'); // counts.OTHER is 0
129
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only extraction ran
130
+ });
131
+ it('pipeline continues after skipped stage', async () => {
132
+ mockScriptSuccess({ final: 'result' });
133
+ const pipeline = {
134
+ name: 'test-skip-continues',
135
+ description: 'test',
136
+ version: 1,
137
+ stages: [
138
+ {
139
+ name: 'skipped-stage',
140
+ executor: 'script',
141
+ script: 'scripts/parse.py',
142
+ runtime: 'shell',
143
+ condition: 'input.run_optional >= 1',
144
+ },
145
+ {
146
+ name: 'final-stage',
147
+ executor: 'script',
148
+ script: 'scripts/finalize.py',
149
+ runtime: 'shell',
150
+ },
151
+ ],
152
+ };
153
+ const engine = makeEngine();
154
+ const result = await engine.run({
155
+ pipelineDef: pipeline,
156
+ input: { run_optional: 0 },
157
+ });
158
+ expect(result.status).toBe('success');
159
+ expect(result.stages[0]?.status).toBe('skipped');
160
+ expect(result.stages[1]?.status).toBe('success');
161
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only final-stage
162
+ });
163
+ });
164
+ //# sourceMappingURL=engine.conditions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.conditions.test.js","sourceRoot":"","sources":["../../src/__tests__/engine.conditions.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,8DAA8D;AAC9D,EAAE,CAAC,IAAI,CAAC,2BAA2B,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IAC5D,MAAM,MAAM,GAAG,MAAM,cAAc,EAA8C,CAAC;IAClF,OAAO;QACL,GAAG,MAAM;QACT,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;KACnB,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEtD,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE,2BAA2B,CAAC,CAAC;AAErE,SAAS,UAAU;IACjB,OAAO,IAAI,cAAc,CAAC;QACxB,UAAU,EAAE,YAAY;QACxB,gBAAgB,EAAE,EAAS;KAC5B,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,SAAkC,EAAE,MAAM,EAAE,IAAI,EAAE;IAC3E,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC;QACrC,MAAM;QACN,UAAU,EAAE,EAAE;QACd,gBAAgB,EAAE,CAAC;QACnB,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,iBAAiB,EAAE,CAAC;QAEpB,MAAM,QAAQ,GAAuB;YACnC,IAAI,EAAE,iBAAiB;YACvB,WAAW,EAAE,MAAM;YACnB,OAAO,EAAE,CAAC;YACV,MAAM,EAAE;gBACN;oBACE,IAAI,EAAE,aAAa;oBACnB,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,kBAAkB;oBAC1B,OAAO,EAAE,OAAO;iBACjB;gBACD;oBACE,IAAI,EAAE,mBAAmB;oBACzB,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,kBAAkB;oBAC1B,OAAO,EAAE,OAAO;oBAChB,SAAS,EAAE,wBAAwB;iBACpC;aACF;SACF,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC;YAC9B,WAAW,EAAE,QAAQ;YACrB,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,EAAG,yCAAyC;SACtE,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,uEAAuE;QACvE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,iBAAiB,EAAE,CAAC;QAEpB,MAAM,QAAQ,GAAuB;YACnC,IAAI,EAAE,sBAAsB;YAC5B,WAAW,EAAE,MAAM;YACnB,OAAO,EAAE,CAAC;YACV,MAAM,EAAE;gBACN;oBACE,IAAI,EAAE,mBAAmB;oBACzB,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,kBAAkB;oBAC1B,OAAO,EAAE,OAAO;oBAChB,SAAS,EAAE,wBAAwB;iBACpC;aACF;SACF,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC;YAC9B,WAAW,EAAE,QAAQ;YACrB,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,EAAG,uCAAuC;SACpE,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,+EAA+E;QAC/E,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC;YACzC,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE;YAC3C,UAAU,EAAE,EAAE;YACd,gBAAgB,EAAE,CAAC;YACnB,WAAW,EAAE,EAAE;SAChB,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAuB;YACnC,IAAI,EAAE,8BAA8B;YACpC,WAAW,EAAE,MAAM;YACnB,OAAO,EAAE,CAAC;YACV,MAAM,EAAE;gBACN;oBACE,IAAI,EAAE,mBAAmB;oBACzB,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,oBAAoB;oBAC5B,OAAO,EAAE,OAAO;oBAChB,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE;iBAChC;gBACD;oBACE,IAAI,EAAE,yBAAyB;oBAC/B,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,oBAAoB;oBAC5B,OAAO,EAAE,OAAO;oBAChB,SAAS,EAAE,kDAAkD;oBAC7D,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,mBAAmB,CAAC,EAAE;iBAC5C;aACF;SACF,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC;YAC9B,WAAW,EAAE,QAAQ;YACrB,KAAK,EAAE,kBAAkB;SAC1B,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAE,oBAAoB;QACvE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAE,sBAAsB;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,iBAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAEvC,MAAM,QAAQ,GAAuB;YACnC,IAAI,EAAE,qBAAqB;YAC3B,WAAW,EAAE,MAAM;YACnB,OAAO,EAAE,CAAC;YACV,MAAM,EAAE;gBACN;oBACE,IAAI,EAAE,eAAe;oBACrB,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,kBAAkB;oBAC1B,OAAO,EAAE,OAAO;oBAChB,SAAS,EAAE,yBAAyB;iBACrC;gBACD;oBACE,IAAI,EAAE,aAAa;oBACnB,QAAQ,EAAE,QAAQ;oBAClB,MAAM,EAAE,qBAAqB;oBAC7B,OAAO,EAAE,OAAO;iBACjB;aACF;SACF,CAAC;QAEF,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC;YAC9B,WAAW,EAAE,QAAQ;YACrB,KAAK,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE;SAC3B,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAE,mBAAmB;IAC7E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=engine.resume.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.resume.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/engine.resume.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { PipelineEngine } from '../engine.js';
3
+ vi.mock('@studio-foundation/runner', async (importOriginal) => {
4
+ const actual = await importOriginal();
5
+ return {
6
+ ...actual,
7
+ runScript: vi.fn(),
8
+ };
9
+ });
10
+ import { runScript } from '@studio-foundation/runner';
11
+ function makeEngine() {
12
+ return new PipelineEngine({
13
+ configsDir: '/tmp',
14
+ providerRegistry: {},
15
+ });
16
+ }
17
+ function mockScriptSuccess(output = { result: 'ok' }) {
18
+ vi.mocked(runScript).mockResolvedValue({
19
+ output,
20
+ tool_calls: [],
21
+ tool_calls_count: 0,
22
+ duration_ms: 10,
23
+ });
24
+ }
25
+ describe('engine — resume from stage', () => {
26
+ beforeEach(() => vi.clearAllMocks());
27
+ const pipeline = {
28
+ name: 'test-resume',
29
+ description: 'test',
30
+ version: 1,
31
+ stages: [
32
+ { name: 'stage-a', executor: 'script', script: 'x.py', runtime: 'shell' },
33
+ { name: 'stage-b', executor: 'script', script: 'x.py', runtime: 'shell' },
34
+ { name: 'stage-c', executor: 'script', script: 'x.py', runtime: 'shell' },
35
+ ],
36
+ };
37
+ it('skips stages before resumeFromStage and marks them skipped', async () => {
38
+ mockScriptSuccess({ result: 'c-result' });
39
+ const engine = makeEngine();
40
+ const result = await engine.run({
41
+ pipelineDef: pipeline,
42
+ input: { x: 1 },
43
+ resumeFromStage: 'stage-c',
44
+ priorStageOutputs: new Map([
45
+ ['stage-a', { result: 'a-cached' }],
46
+ ['stage-b', { result: 'b-cached' }],
47
+ ]),
48
+ originalRunId: 'abc12345',
49
+ });
50
+ expect(result.status).toBe('success');
51
+ expect(result.stages).toHaveLength(3);
52
+ expect(result.stages[0]?.stage_name).toBe('stage-a');
53
+ expect(result.stages[0]?.status).toBe('skipped');
54
+ expect(result.stages[0]?.skipped_reason).toContain('abc12345');
55
+ expect(result.stages[1]?.stage_name).toBe('stage-b');
56
+ expect(result.stages[1]?.status).toBe('skipped');
57
+ expect(result.stages[2]?.stage_name).toBe('stage-c');
58
+ expect(result.stages[2]?.status).toBe('success');
59
+ // Only stage-c was actually executed
60
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1);
61
+ });
62
+ it('pre-populates context so resumed stage can access prior outputs', async () => {
63
+ mockScriptSuccess({ result: 'ok' });
64
+ const engine = makeEngine();
65
+ const result = await engine.run({
66
+ pipelineDef: {
67
+ ...pipeline,
68
+ stages: [
69
+ { name: 'stage-a', executor: 'script', script: 'x.py', runtime: 'shell' },
70
+ { name: 'stage-b', executor: 'script', script: 'x.py', runtime: 'shell' },
71
+ ],
72
+ },
73
+ input: { x: 1 },
74
+ resumeFromStage: 'stage-b',
75
+ priorStageOutputs: new Map([['stage-a', { result: 'a-cached' }]]),
76
+ });
77
+ // stage-b ran successfully (prior context was pre-populated, no crash)
78
+ expect(result.status).toBe('success');
79
+ expect(result.stages).toHaveLength(2);
80
+ expect(result.stages[0]?.status).toBe('skipped');
81
+ expect(result.stages[1]?.status).toBe('success');
82
+ });
83
+ it('throws if resumeFromStage is not found in pipeline', async () => {
84
+ const engine = makeEngine();
85
+ await expect(engine.run({
86
+ pipelineDef: pipeline,
87
+ input: {},
88
+ resumeFromStage: 'nonexistent-stage',
89
+ priorStageOutputs: new Map(),
90
+ })).rejects.toThrow(/not found/i);
91
+ });
92
+ });
93
+ //# sourceMappingURL=engine.resume.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.resume.test.js","sourceRoot":"","sources":["../../src/__tests__/engine.resume.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=engine.script-stage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.script-stage.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/engine.script-stage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { resolve } from 'node:path';
3
+ import { PipelineEngine } from '../engine.js';
4
+ // Mock runScript from runner to avoid real subprocess spawning
5
+ vi.mock('@studio-foundation/runner', async (importOriginal) => {
6
+ const actual = await importOriginal();
7
+ return {
8
+ ...actual,
9
+ runScript: vi.fn(),
10
+ };
11
+ });
12
+ import { runScript } from '@studio-foundation/runner';
13
+ const FIXTURES_DIR = resolve(__dirname, '__fixtures__/script-stage');
14
+ const SCRIPT_PIPELINE = {
15
+ name: 'test-script-pipeline',
16
+ description: 'Test pipeline with script stage',
17
+ version: 1,
18
+ stages: [
19
+ {
20
+ name: 'epub-ingestion',
21
+ executor: 'script',
22
+ script: 'scripts/parse.py',
23
+ runtime: 'python',
24
+ contract: 'book-context',
25
+ },
26
+ ],
27
+ };
28
+ function makeEngine() {
29
+ return new PipelineEngine({
30
+ configsDir: FIXTURES_DIR,
31
+ providerRegistry: {},
32
+ });
33
+ }
34
+ describe('engine — script stage execution', () => {
35
+ beforeEach(() => {
36
+ vi.clearAllMocks();
37
+ });
38
+ it('executes a script stage and returns success when output matches contract', async () => {
39
+ vi.mocked(runScript).mockResolvedValue({
40
+ output: { title: 'My Book', chapters: 3 },
41
+ tool_calls: [],
42
+ tool_calls_count: 0,
43
+ duration_ms: 50,
44
+ });
45
+ const engine = makeEngine();
46
+ const result = await engine.run({
47
+ pipelineDef: SCRIPT_PIPELINE,
48
+ userInput: 'parse book.epub',
49
+ });
50
+ expect(result.status).toBe('success');
51
+ expect(result.stages[0]?.status).toBe('success');
52
+ expect(result.stages[0]?.output).toEqual({ title: 'My Book', chapters: 3 });
53
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1);
54
+ expect(vi.mocked(runScript)).toHaveBeenCalledWith(expect.objectContaining({
55
+ scriptPath: 'scripts/parse.py',
56
+ runtime: 'python',
57
+ }));
58
+ });
59
+ it('retries on script error (runScript returns error field)', async () => {
60
+ const scriptError = {
61
+ output: null,
62
+ tool_calls: [],
63
+ tool_calls_count: 0,
64
+ duration_ms: 10,
65
+ error: 'Script exited with code 1: parse error',
66
+ };
67
+ vi.mocked(runScript)
68
+ .mockResolvedValueOnce(scriptError)
69
+ .mockResolvedValueOnce(scriptError)
70
+ .mockResolvedValue({
71
+ output: { title: 'My Book', chapters: 3 },
72
+ tool_calls: [],
73
+ tool_calls_count: 0,
74
+ duration_ms: 50,
75
+ });
76
+ const engine = makeEngine();
77
+ const result = await engine.run({
78
+ pipelineDef: {
79
+ ...SCRIPT_PIPELINE,
80
+ stages: [{ ...SCRIPT_PIPELINE.stages[0], ralph: { max_attempts: 3, retry_strategy: 'none' } }],
81
+ },
82
+ userInput: 'parse book.epub',
83
+ });
84
+ expect(result.status).toBe('success');
85
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(3);
86
+ });
87
+ it('fails stage after exhausting max_attempts', async () => {
88
+ vi.mocked(runScript).mockResolvedValue({
89
+ output: null,
90
+ tool_calls: [],
91
+ tool_calls_count: 0,
92
+ duration_ms: 10,
93
+ error: 'Script exited with code 1: always failing',
94
+ });
95
+ const engine = makeEngine();
96
+ const result = await engine.run({
97
+ pipelineDef: {
98
+ ...SCRIPT_PIPELINE,
99
+ stages: [{ ...SCRIPT_PIPELINE.stages[0], ralph: { max_attempts: 2, retry_strategy: 'none' } }],
100
+ },
101
+ userInput: 'parse book.epub',
102
+ });
103
+ expect(result.status).toBe('failed');
104
+ expect(result.stages[0]?.status).toBe('failed');
105
+ expect(vi.mocked(runScript)).toHaveBeenCalledTimes(2);
106
+ });
107
+ });
108
+ //# sourceMappingURL=engine.script-stage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.script-stage.test.js","sourceRoot":"","sources":["../../src/__tests__/engine.script-stage.test.ts"],"names":[],"mappings":"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"}