@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,130 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { SQLiteRunStore, PgRunStore } from './run-store.js';
3
+ function makeRun(overrides) {
4
+ return {
5
+ id: 'run-1',
6
+ pipeline_name: 'test-pipeline',
7
+ status: 'running',
8
+ started_at: '2026-01-01T00:00:00.000Z',
9
+ stages: [],
10
+ ...overrides,
11
+ };
12
+ }
13
+ describe('SQLiteRunStore', () => {
14
+ describe('saveLogPath / getLogPath', () => {
15
+ it('returns null when no log path has been set', () => {
16
+ const store = new SQLiteRunStore(':memory:');
17
+ store.savePipelineRun(makeRun());
18
+ expect(store.getLogPath('run-1')).toBeNull();
19
+ });
20
+ it('returns the log path after saveLogPath', () => {
21
+ const store = new SQLiteRunStore(':memory:');
22
+ store.savePipelineRun(makeRun());
23
+ store.saveLogPath('run-1', '/tmp/run-1.jsonl');
24
+ expect(store.getLogPath('run-1')).toBe('/tmp/run-1.jsonl');
25
+ });
26
+ it('preserves log_path when savePipelineRun is called again after saveLogPath', () => {
27
+ // Regression for STU-150: savePipelineRun used INSERT OR REPLACE which clobbers log_path
28
+ const store = new SQLiteRunStore(':memory:');
29
+ store.savePipelineRun(makeRun());
30
+ store.saveLogPath('run-1', '/tmp/run-1.jsonl');
31
+ // Simulate engine completing the run (this triggered the bug)
32
+ store.savePipelineRun(makeRun({ status: 'success', completed_at: '2026-01-01T00:01:00.000Z' }));
33
+ expect(store.getLogPath('run-1')).toBe('/tmp/run-1.jsonl');
34
+ });
35
+ it('returns null for unknown run id', () => {
36
+ const store = new SQLiteRunStore(':memory:');
37
+ expect(store.getLogPath('does-not-exist')).toBeNull();
38
+ });
39
+ });
40
+ describe('parent_run_id', () => {
41
+ it('persists and retrieves parent_run_id', () => {
42
+ const run = {
43
+ id: 'child-123',
44
+ pipeline_name: 'child-pipe',
45
+ status: 'success',
46
+ started_at: new Date().toISOString(),
47
+ stages: [],
48
+ parent_run_id: 'parent-456',
49
+ };
50
+ const store = new SQLiteRunStore(':memory:');
51
+ store.savePipelineRun(run);
52
+ const retrieved = store.getPipelineRun('child-123');
53
+ expect(retrieved?.parent_run_id).toBe('parent-456');
54
+ });
55
+ });
56
+ });
57
+ const TEST_PG_URL = process.env.TEST_DATABASE_URL;
58
+ describe.skipIf(!TEST_PG_URL)('PgRunStore', () => {
59
+ let store;
60
+ beforeEach(async () => {
61
+ store = new PgRunStore(TEST_PG_URL);
62
+ await store.dangerouslyTruncateForTests();
63
+ });
64
+ afterEach(async () => {
65
+ await store.close();
66
+ });
67
+ it('saves and retrieves a pipeline run', async () => {
68
+ const run = makePgRun('pg-run-1');
69
+ await store.savePipelineRun(run);
70
+ const found = await store.getPipelineRun('pg-run-1');
71
+ expect(found).toMatchObject({ id: 'pg-run-1', status: 'success' });
72
+ });
73
+ it('returns null for unknown id', async () => {
74
+ const found = await store.getPipelineRun('doesnt-exist');
75
+ expect(found).toBeNull();
76
+ });
77
+ it('updates an existing run (upsert)', async () => {
78
+ const run = makePgRun('pg-run-2');
79
+ await store.savePipelineRun(run);
80
+ await store.savePipelineRun({ ...run, status: 'failed' });
81
+ const found = await store.getPipelineRun('pg-run-2');
82
+ expect(found?.status).toBe('failed');
83
+ });
84
+ it('lists runs with status filter', async () => {
85
+ await store.savePipelineRun(makePgRun('pg-1', 'success'));
86
+ await store.savePipelineRun(makePgRun('pg-2', 'failed'));
87
+ const successes = await store.listPipelineRuns({ status: 'success' });
88
+ expect(successes).toHaveLength(1);
89
+ expect(successes[0].id).toBe('pg-1');
90
+ });
91
+ it('lists runs with limit', async () => {
92
+ for (let i = 0; i < 5; i++) {
93
+ await store.savePipelineRun(makePgRun(`pg-limit-${i}`));
94
+ }
95
+ const runs = await store.listPipelineRuns({ limit: 3 });
96
+ expect(runs).toHaveLength(3);
97
+ });
98
+ it('getLatestRun returns most recent', async () => {
99
+ await store.savePipelineRun(makePgRun('pg-old', 'success', '2024-01-01T00:00:00Z'));
100
+ await store.savePipelineRun(makePgRun('pg-new', 'success', '2024-06-01T00:00:00Z'));
101
+ const latest = await store.getLatestRun();
102
+ expect(latest?.id).toBe('pg-new');
103
+ });
104
+ it('getLatestRun filters by pipeline name', async () => {
105
+ await store.savePipelineRun(makePgRun('pg-a', 'success', undefined, 'pipeline-a'));
106
+ await store.savePipelineRun(makePgRun('pg-b', 'success', undefined, 'pipeline-b'));
107
+ const latest = await store.getLatestRun('pipeline-a');
108
+ expect(latest?.id).toBe('pg-a');
109
+ });
110
+ it('saves and retrieves log path', async () => {
111
+ await store.savePipelineRun(makePgRun('pg-log-1'));
112
+ await store.saveLogPath('pg-log-1', '/tmp/test.jsonl');
113
+ const path = await store.getLogPath('pg-log-1');
114
+ expect(path).toBe('/tmp/test.jsonl');
115
+ });
116
+ it('returns null log path for unknown run', async () => {
117
+ const path = await store.getLogPath('no-such-run');
118
+ expect(path).toBeNull();
119
+ });
120
+ });
121
+ function makePgRun(id, status = 'success', startedAt = new Date().toISOString(), pipelineName = 'test-pipeline') {
122
+ return {
123
+ id,
124
+ pipeline_name: pipelineName,
125
+ status,
126
+ started_at: startedAt,
127
+ stages: [],
128
+ };
129
+ }
130
+ //# sourceMappingURL=run-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-store.test.js","sourceRoot":"","sources":["../../src/state/run-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export type StageLifecycleState = 'pending' | 'running' | 'success' | 'failed' | 'skipped' | 'rejected' | 'cancelled';
2
+ type StageEvent = 'start' | 'succeed' | 'fail' | 'skip' | 'reject' | 'cancel';
3
+ export declare function isValidTransition(from: StageLifecycleState, to: StageLifecycleState): boolean;
4
+ export declare function transition(current: StageLifecycleState, event: StageEvent): StageLifecycleState;
5
+ export {};
6
+ //# sourceMappingURL=state-machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../../src/state/state-machine.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAEtH,KAAK,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAW9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,EAAE,EAAE,EAAE,mBAAmB,GAAG,OAAO,CAO7F;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,GAAG,mBAAmB,CAS/F"}
@@ -0,0 +1,27 @@
1
+ // Stage lifecycle state machine
2
+ // Keeps it simple: linear transitions, no DAG
3
+ const VALID_TRANSITIONS = {
4
+ 'pending:start': 'running',
5
+ 'running:succeed': 'success',
6
+ 'running:fail': 'failed',
7
+ 'pending:skip': 'skipped',
8
+ 'running:reject': 'rejected',
9
+ 'running:cancel': 'cancelled',
10
+ };
11
+ export function isValidTransition(from, to) {
12
+ for (const [key, target] of Object.entries(VALID_TRANSITIONS)) {
13
+ if (key.startsWith(`${from}:`) && target === to) {
14
+ return true;
15
+ }
16
+ }
17
+ return false;
18
+ }
19
+ export function transition(current, event) {
20
+ const key = `${current}:${event}`;
21
+ const next = VALID_TRANSITIONS[key];
22
+ if (!next) {
23
+ throw new Error(`Invalid state transition: ${current} + ${event}`);
24
+ }
25
+ return next;
26
+ }
27
+ //# sourceMappingURL=state-machine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.js","sourceRoot":"","sources":["../../src/state/state-machine.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,8CAA8C;AAM9C,MAAM,iBAAiB,GAAwC;IAC7D,eAAe,EAAE,SAAS;IAC1B,iBAAiB,EAAE,SAAS;IAC5B,cAAc,EAAE,QAAQ;IACxB,cAAc,EAAE,SAAS;IACzB,gBAAgB,EAAE,UAAU;IAC5B,gBAAgB,EAAE,WAAW;CAC9B,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,IAAyB,EAAE,EAAuB;IAClF,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAC9D,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAA4B,EAAE,KAAiB;IACxE,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,KAAK,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAEpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { RalphResult } from '@studio-foundation/ralph';
2
+ import type { StageStatus } from '@studio-foundation/contracts';
3
+ /**
4
+ * Derives stage status from ralph result.
5
+ *
6
+ * Rules (simple and exhaustive):
7
+ * - ralph 'success' → stage 'success'
8
+ * - ralph 'exhausted' → stage 'failed'
9
+ * - anything else → error (should never happen)
10
+ */
11
+ export declare function deriveStageStatus(ralphResult: RalphResult<unknown>): StageStatus;
12
+ //# sourceMappingURL=status-derivation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-derivation.d.ts","sourceRoot":"","sources":["../../src/state/status-derivation.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEhE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,WAAW,CAehF"}
@@ -0,0 +1,30 @@
1
+ // Derive stage status from ralph result
2
+ // THIS IS THE CRITICAL FUNCTION
3
+ //
4
+ // In v7, each stage = 1 ralph call = 1 task.
5
+ // This function maps ralph's result directly to stage status.
6
+ //
7
+ // This was the #1 bug in v6: stage status didn't match task status.
8
+ // v7 fix: SIMPLE, DETERMINISTIC, NO MAGIC.
9
+ /**
10
+ * Derives stage status from ralph result.
11
+ *
12
+ * Rules (simple and exhaustive):
13
+ * - ralph 'success' → stage 'success'
14
+ * - ralph 'exhausted' → stage 'failed'
15
+ * - anything else → error (should never happen)
16
+ */
17
+ export function deriveStageStatus(ralphResult) {
18
+ if (ralphResult.status === 'success') {
19
+ return 'success';
20
+ }
21
+ if (ralphResult.status === 'exhausted') {
22
+ return 'failed';
23
+ }
24
+ if (ralphResult.status === 'cancelled') {
25
+ return 'cancelled';
26
+ }
27
+ // Should never reach here if RalphResult types are exhaustive
28
+ throw new Error(`Unknown ralph status: ${JSON.stringify(ralphResult)}`);
29
+ }
30
+ //# sourceMappingURL=status-derivation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-derivation.js","sourceRoot":"","sources":["../../src/state/status-derivation.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,gCAAgC;AAChC,EAAE;AACF,6CAA6C;AAC7C,8DAA8D;AAC9D,EAAE;AACF,oEAAoE;AACpE,2CAA2C;AAK3C;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAiC;IACjE,IAAI,WAAW,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACvC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACvC,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,8DAA8D;IAC9D,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AAC1E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studio-foundation/engine",
3
- "version": "0.3.0-beta.1",
3
+ "version": "0.3.0-beta.5",
4
4
  "description": "Pipeline orchestration engine for Studio v7",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,6 +11,9 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
+ "files": [
15
+ "dist"
16
+ ],
14
17
  "keywords": [
15
18
  "studio",
16
19
  "engine",
@@ -23,9 +26,9 @@
23
26
  "better-sqlite3": "^9.0.0",
24
27
  "js-yaml": "^4.1.0",
25
28
  "pg": "^8.19.0",
26
- "@studio-foundation/contracts": "0.3.0-beta.1",
27
- "@studio-foundation/ralph": "0.3.0-beta.1",
28
- "@studio-foundation/runner": "0.3.0-beta.1"
29
+ "@studio-foundation/contracts": "0.3.0-beta.5",
30
+ "@studio-foundation/ralph": "0.3.0-beta.5",
31
+ "@studio-foundation/runner": "0.3.0-beta.5"
29
32
  },
30
33
  "devDependencies": {
31
34
  "@types/better-sqlite3": "^7.6.8",
package/ARCHITECTURE.md DELETED
@@ -1,57 +0,0 @@
1
- # @studio-foundation/engine
2
-
3
- Orchestrateur de pipelines. Le cerveau de Studio.
4
-
5
- ## Concept
6
-
7
- Charge une pipeline YAML → exécute les stages en séquence (ou en groups avec feedback loops) → pour chaque stage : hooks + `ralph(runAgent, validator)` → persiste les runs → émet des events.
8
-
9
- ## Règles
10
-
11
- - **Domain-agnostic.** Pas de référence à "code", "file", "git", "QA" dans le source. `StageKind = string`.
12
- - `deriveStageStatus()` dans `state/status-derivation.ts` est LA fonction critique — mapping déterministe ralph result → stage status
13
- - La DB est configurable : `SQLiteRunStore` | `PgRunStore` | `InMemoryRunStore` (union `AnyRunStore`)
14
- - Le engine ne construit pas de prompts — c'est runner
15
- - Le engine ne sait pas ce qu'est `repo_manager-write_file` — c'est runner
16
- - Dépend de `@studio-foundation/contracts`, `@studio-foundation/ralph`, `@studio-foundation/runner`, `@studio-foundation/anonymizer`
17
-
18
- ## Fichiers clés
19
-
20
- - `engine.ts` — `PipelineEngine` (classe principale), `EngineConfig`, `RunInput`
21
- - `events.ts` — `EngineEvents`, `PipelineEventEmitter`
22
- - `state/status-derivation.ts` — `deriveStageStatus()` ← CRITIQUE
23
- - `state/run-store.ts` — `AnyRunStore`, `InMemoryRunStore`
24
- - `pipeline/loader.ts` — charge YAML → `PipelineDefinition`
25
- - `pipeline/agent-loader.ts` — charge agent YAML + injecte skills
26
- - `pipeline/contract-loader.ts` — charge contract YAML
27
- - `pipeline/context-propagation.ts` — `createInitialContext`, `addStageOutput`, `getContextForStage`
28
- - `pipeline/context-pack-loader.ts` — charge les context packs
29
- - `pipeline/hook-executor.ts` — `runStageHook()`, `runToolHook()` (4 points de hook)
30
- - `pipeline/startup-executor.ts` — `executeStartupCommands()` pour `on_pipeline_start`
31
- - `pipeline/post-validator.ts` — `postValidate()` (rejection detection)
32
- - `pipeline/skill-loader.ts` — charge les `.skill.md` files
33
- - `db/client.ts` — SQLite client (pour WebhookStore, IntegrationStore côté api)
34
- - `spawners/direct-engine-spawner.ts` — `DirectEngineSpawner` implémente `RunSpawner`
35
-
36
- ## Groups (feedback loops)
37
-
38
- Un group contient plusieurs stages qui itèrent ensemble. Si le dernier stage rejette (via `post_validation.rejection_detection`), le group redémarre depuis le début avec le feedback accumulé. `max_iterations` cap.
39
-
40
- ## Hooks lifecycle
41
-
42
- 4 points déterministes : `on_stage_start`, `on_stage_complete`, `pre_tool_use`, `post_tool_use`.
43
- `on_failure: warn | reject | fail`. Les hooks `pre_tool_use` avec reject bloquent le tool call.
44
-
45
- ## État machine
46
-
47
- ```
48
- pending → running → success
49
- → failed (ralph exhausted, ou hook on_failure: fail)
50
- → rejected (post_validation, ou hook on_failure: reject)
51
- → skipped
52
- → cancelled (AbortSignal)
53
- ```
54
-
55
- ## Dépendances
56
-
57
- `@studio-foundation/contracts`, `@studio-foundation/ralph`, `@studio-foundation/runner`, `@studio-foundation/anonymizer`
@@ -1,4 +0,0 @@
1
- name: book-context
2
- version: 1
3
- schema:
4
- required_fields: []
@@ -1,244 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { resolve } from 'node:path';
3
- import { PipelineEngine } from '../engine.js';
4
- import type { PipelineDefinition } from '@studio-foundation/contracts';
5
-
6
- vi.mock('@studio-foundation/runner', async (importOriginal) => {
7
- const actual = await importOriginal<typeof import('@studio-foundation/runner')>();
8
- return {
9
- ...actual,
10
- runScript: vi.fn(),
11
- };
12
- });
13
-
14
- import { runScript } from '@studio-foundation/runner';
15
-
16
- const FIXTURES_DIR = resolve(__dirname, '__fixtures__/script-stage');
17
-
18
- function makeEngine() {
19
- return new PipelineEngine({
20
- configsDir: FIXTURES_DIR,
21
- providerRegistry: {} as any,
22
- });
23
- }
24
-
25
- describe('engine — conditions inside sequential groups', () => {
26
- beforeEach(() => {
27
- vi.clearAllMocks();
28
- });
29
-
30
- it('skips a conditional stage inside a sequential group', async () => {
31
- vi.mocked(runScript).mockResolvedValue({
32
- output: { done: true },
33
- tool_calls: [],
34
- tool_calls_count: 0,
35
- duration_ms: 10,
36
- });
37
-
38
- const pipeline: PipelineDefinition = {
39
- name: 'test-group-conditions',
40
- description: 'test',
41
- version: 1,
42
- stages: [
43
- {
44
- group: 'processing',
45
- max_iterations: 1,
46
- stages: [
47
- {
48
- name: 'always-runs',
49
- executor: 'script',
50
- script: 'scripts/run.py',
51
- runtime: 'shell',
52
- },
53
- {
54
- name: 'conditional-in-group',
55
- executor: 'script',
56
- script: 'scripts/optional.py',
57
- runtime: 'shell',
58
- condition: 'input.optional >= 1',
59
- },
60
- ],
61
- },
62
- ],
63
- };
64
-
65
- const engine = makeEngine();
66
- const result = await engine.run({
67
- pipelineDef: pipeline,
68
- input: { optional: 0 },
69
- });
70
-
71
- expect(result.status).toBe('success');
72
- expect(result.stages[0]?.status).toBe('success');
73
- expect(result.stages[1]?.status).toBe('skipped');
74
- expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1);
75
- });
76
-
77
- it('marks group as skipped when all stages in sequential group are skipped', async () => {
78
- vi.mocked(runScript).mockResolvedValue({
79
- output: { done: true },
80
- tool_calls: [],
81
- tool_calls_count: 0,
82
- duration_ms: 10,
83
- });
84
-
85
- // A stage after the group should still run
86
- const pipeline: PipelineDefinition = {
87
- name: 'test-all-skipped-group',
88
- description: 'test',
89
- version: 1,
90
- stages: [
91
- {
92
- group: 'optional-processing',
93
- max_iterations: 1,
94
- stages: [
95
- {
96
- name: 'optional-a',
97
- executor: 'script',
98
- script: 'scripts/a.py',
99
- runtime: 'shell',
100
- condition: 'input.run_optional >= 1',
101
- },
102
- {
103
- name: 'optional-b',
104
- executor: 'script',
105
- script: 'scripts/b.py',
106
- runtime: 'shell',
107
- condition: 'input.run_optional >= 1',
108
- },
109
- ],
110
- },
111
- {
112
- name: 'post-group-stage',
113
- executor: 'script',
114
- script: 'scripts/final.py',
115
- runtime: 'shell',
116
- },
117
- ],
118
- };
119
-
120
- const engine = makeEngine();
121
- const result = await engine.run({
122
- pipelineDef: pipeline,
123
- input: { run_optional: 0 },
124
- });
125
-
126
- expect(result.status).toBe('success');
127
- // Both group stages skipped
128
- expect(result.stages[0]?.status).toBe('skipped');
129
- expect(result.stages[1]?.status).toBe('skipped');
130
- // Post-group stage ran
131
- expect(result.stages[2]?.status).toBe('success');
132
- expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only post-group-stage
133
- });
134
- });
135
-
136
- describe('engine — conditions inside parallel groups', () => {
137
- beforeEach(() => {
138
- vi.clearAllMocks();
139
- });
140
-
141
- it('marks parallel group as skipped when all stages are skipped', async () => {
142
- vi.mocked(runScript).mockResolvedValue({
143
- output: { done: true },
144
- tool_calls: [],
145
- tool_calls_count: 0,
146
- duration_ms: 10,
147
- });
148
-
149
- const pipeline: PipelineDefinition = {
150
- name: 'test-parallel-all-skipped',
151
- description: 'test',
152
- version: 1,
153
- stages: [
154
- {
155
- group: 'optional-parallel',
156
- max_iterations: 1,
157
- mode: 'parallel',
158
- stages: [
159
- {
160
- name: 'parallel-a',
161
- executor: 'script',
162
- script: 'scripts/a.py',
163
- runtime: 'shell',
164
- condition: 'input.enabled >= 1',
165
- },
166
- {
167
- name: 'parallel-b',
168
- executor: 'script',
169
- script: 'scripts/b.py',
170
- runtime: 'shell',
171
- condition: 'input.enabled >= 1',
172
- },
173
- ],
174
- },
175
- {
176
- name: 'post-group',
177
- executor: 'script',
178
- script: 'scripts/final.py',
179
- runtime: 'shell',
180
- },
181
- ],
182
- };
183
-
184
- const engine = makeEngine();
185
- const result = await engine.run({
186
- pipelineDef: pipeline,
187
- input: { enabled: 0 },
188
- });
189
-
190
- expect(result.status).toBe('success');
191
- expect(result.stages[0]?.status).toBe('skipped');
192
- expect(result.stages[1]?.status).toBe('skipped');
193
- expect(result.stages[2]?.status).toBe('success');
194
- expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only post-group
195
- });
196
-
197
- it('runs parallel group normally when at least one stage is not skipped', async () => {
198
- vi.mocked(runScript).mockResolvedValue({
199
- output: { done: true },
200
- tool_calls: [],
201
- tool_calls_count: 0,
202
- duration_ms: 10,
203
- });
204
-
205
- const pipeline: PipelineDefinition = {
206
- name: 'test-parallel-partial-skip',
207
- description: 'test',
208
- version: 1,
209
- stages: [
210
- {
211
- group: 'mixed-parallel',
212
- max_iterations: 1,
213
- mode: 'parallel',
214
- stages: [
215
- {
216
- name: 'always-runs',
217
- executor: 'script',
218
- script: 'scripts/a.py',
219
- runtime: 'shell',
220
- },
221
- {
222
- name: 'conditional',
223
- executor: 'script',
224
- script: 'scripts/b.py',
225
- runtime: 'shell',
226
- condition: 'input.enabled >= 1',
227
- },
228
- ],
229
- },
230
- ],
231
- };
232
-
233
- const engine = makeEngine();
234
- const result = await engine.run({
235
- pipelineDef: pipeline,
236
- input: { enabled: 0 },
237
- });
238
-
239
- expect(result.status).toBe('success');
240
- expect(result.stages[0]?.status).toBe('success');
241
- expect(result.stages[1]?.status).toBe('skipped');
242
- expect(vi.mocked(runScript)).toHaveBeenCalledTimes(1); // only always-runs
243
- });
244
- });