@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.
- package/dist/__tests__/engine.conditions-group.test.d.ts +2 -0
- package/dist/__tests__/engine.conditions-group.test.d.ts.map +1 -0
- package/dist/__tests__/engine.conditions-group.test.js +222 -0
- package/dist/__tests__/engine.conditions-group.test.js.map +1 -0
- package/dist/__tests__/engine.conditions.test.d.ts +2 -0
- package/dist/__tests__/engine.conditions.test.d.ts.map +1 -0
- package/dist/__tests__/engine.conditions.test.js +164 -0
- package/dist/__tests__/engine.conditions.test.js.map +1 -0
- package/dist/__tests__/engine.resume.test.d.ts +2 -0
- package/dist/__tests__/engine.resume.test.d.ts.map +1 -0
- package/dist/__tests__/engine.resume.test.js +93 -0
- package/dist/__tests__/engine.resume.test.js.map +1 -0
- package/dist/__tests__/engine.script-stage.test.d.ts +2 -0
- package/dist/__tests__/engine.script-stage.test.d.ts.map +1 -0
- package/dist/__tests__/engine.script-stage.test.js +108 -0
- package/dist/__tests__/engine.script-stage.test.js.map +1 -0
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.d.ts.map +1 -0
- package/{src/db/client.ts → dist/db/client.js} +1 -2
- package/dist/db/client.js.map +1 -0
- package/dist/engine.context-event.test.d.ts +2 -0
- package/dist/engine.context-event.test.d.ts.map +1 -0
- package/dist/engine.context-event.test.js +111 -0
- package/dist/engine.context-event.test.js.map +1 -0
- package/dist/engine.d.ts +52 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +364 -0
- package/dist/engine.js.map +1 -0
- package/dist/events.d.ts +163 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +12 -0
- package/dist/events.js.map +1 -0
- package/{src/index.ts → dist/index.d.ts} +3 -45
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/agent-loader.d.ts +4 -0
- package/dist/pipeline/agent-loader.d.ts.map +1 -0
- package/dist/pipeline/agent-loader.js +28 -0
- package/dist/pipeline/agent-loader.js.map +1 -0
- package/dist/pipeline/agent-loader.test.d.ts +2 -0
- package/dist/pipeline/agent-loader.test.d.ts.map +1 -0
- package/dist/pipeline/agent-loader.test.js +134 -0
- package/dist/pipeline/agent-loader.test.js.map +1 -0
- package/dist/pipeline/condition-evaluator.d.ts +6 -0
- package/dist/pipeline/condition-evaluator.d.ts.map +1 -0
- package/dist/pipeline/condition-evaluator.js +105 -0
- package/dist/pipeline/condition-evaluator.js.map +1 -0
- package/dist/pipeline/condition-evaluator.test.d.ts +2 -0
- package/dist/pipeline/condition-evaluator.test.d.ts.map +1 -0
- package/dist/pipeline/condition-evaluator.test.js +103 -0
- package/dist/pipeline/condition-evaluator.test.js.map +1 -0
- package/dist/pipeline/context-pack-loader.d.ts +3 -0
- package/dist/pipeline/context-pack-loader.d.ts.map +1 -0
- package/dist/pipeline/context-pack-loader.js +51 -0
- package/dist/pipeline/context-pack-loader.js.map +1 -0
- package/dist/pipeline/context-propagation.d.ts +28 -0
- package/dist/pipeline/context-propagation.d.ts.map +1 -0
- package/dist/pipeline/context-propagation.js +161 -0
- package/dist/pipeline/context-propagation.js.map +1 -0
- package/dist/pipeline/context-propagation.test.d.ts +2 -0
- package/dist/pipeline/context-propagation.test.d.ts.map +1 -0
- package/dist/pipeline/context-propagation.test.js +194 -0
- package/dist/pipeline/context-propagation.test.js.map +1 -0
- package/dist/pipeline/contract-loader.d.ts +4 -0
- package/dist/pipeline/contract-loader.d.ts.map +1 -0
- package/dist/pipeline/contract-loader.js +30 -0
- package/dist/pipeline/contract-loader.js.map +1 -0
- package/dist/pipeline/group-orchestrator.d.ts +19 -0
- package/dist/pipeline/group-orchestrator.d.ts.map +1 -0
- package/dist/pipeline/group-orchestrator.js +376 -0
- package/dist/pipeline/group-orchestrator.js.map +1 -0
- package/dist/pipeline/hook-executor.d.ts +28 -0
- package/dist/pipeline/hook-executor.d.ts.map +1 -0
- package/dist/pipeline/hook-executor.js +63 -0
- package/dist/pipeline/hook-executor.js.map +1 -0
- package/dist/pipeline/hook-executor.test.d.ts +2 -0
- package/dist/pipeline/hook-executor.test.d.ts.map +1 -0
- package/dist/pipeline/hook-executor.test.js +71 -0
- package/dist/pipeline/hook-executor.test.js.map +1 -0
- package/dist/pipeline/invariants-loader.d.ts +6 -0
- package/dist/pipeline/invariants-loader.d.ts.map +1 -0
- package/dist/pipeline/invariants-loader.js +15 -0
- package/dist/pipeline/invariants-loader.js.map +1 -0
- package/dist/pipeline/invariants-loader.test.d.ts +2 -0
- package/dist/pipeline/invariants-loader.test.d.ts.map +1 -0
- package/dist/pipeline/invariants-loader.test.js +39 -0
- package/dist/pipeline/invariants-loader.test.js.map +1 -0
- package/dist/pipeline/loader.d.ts +5 -0
- package/dist/pipeline/loader.d.ts.map +1 -0
- package/dist/pipeline/loader.js +132 -0
- package/dist/pipeline/loader.js.map +1 -0
- package/dist/pipeline/loader.test.d.ts +2 -0
- package/dist/pipeline/loader.test.d.ts.map +1 -0
- package/dist/pipeline/loader.test.js +120 -0
- package/dist/pipeline/loader.test.js.map +1 -0
- package/dist/pipeline/output-validator.d.ts +10 -0
- package/dist/pipeline/output-validator.d.ts.map +1 -0
- package/dist/pipeline/output-validator.js +20 -0
- package/dist/pipeline/output-validator.js.map +1 -0
- package/dist/pipeline/output-validator.test.d.ts +2 -0
- package/dist/pipeline/output-validator.test.d.ts.map +1 -0
- package/dist/pipeline/output-validator.test.js +77 -0
- package/dist/pipeline/output-validator.test.js.map +1 -0
- package/dist/pipeline/post-validator.d.ts +8 -0
- package/dist/pipeline/post-validator.d.ts.map +1 -0
- package/dist/pipeline/post-validator.js +103 -0
- package/dist/pipeline/post-validator.js.map +1 -0
- package/dist/pipeline/skill-loader.d.ts +10 -0
- package/dist/pipeline/skill-loader.d.ts.map +1 -0
- package/dist/pipeline/skill-loader.js +26 -0
- package/dist/pipeline/skill-loader.js.map +1 -0
- package/dist/pipeline/skill-loader.test.d.ts +2 -0
- package/dist/pipeline/skill-loader.test.d.ts.map +1 -0
- package/dist/pipeline/skill-loader.test.js +38 -0
- package/dist/pipeline/skill-loader.test.js.map +1 -0
- package/dist/pipeline/stage-executor.d.ts +26 -0
- package/dist/pipeline/stage-executor.d.ts.map +1 -0
- package/dist/pipeline/stage-executor.js +540 -0
- package/dist/pipeline/stage-executor.js.map +1 -0
- package/dist/pipeline/stage-resolver.d.ts +3 -0
- package/dist/pipeline/stage-resolver.d.ts.map +1 -0
- package/dist/pipeline/stage-resolver.js +6 -0
- package/dist/pipeline/stage-resolver.js.map +1 -0
- package/dist/pipeline/startup-executor.d.ts +3 -0
- package/dist/pipeline/startup-executor.d.ts.map +1 -0
- package/dist/pipeline/startup-executor.js +23 -0
- package/dist/pipeline/startup-executor.js.map +1 -0
- package/dist/pipeline/startup-executor.test.d.ts +2 -0
- package/dist/pipeline/startup-executor.test.d.ts.map +1 -0
- package/dist/pipeline/startup-executor.test.js +34 -0
- package/dist/pipeline/startup-executor.test.js.map +1 -0
- package/dist/pipeline/types.d.ts +28 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +12 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/repo-resolver.d.ts +10 -0
- package/dist/repo-resolver.d.ts.map +1 -0
- package/dist/repo-resolver.js +39 -0
- package/dist/repo-resolver.js.map +1 -0
- package/dist/spawners/direct-engine-spawner.d.ts +8 -0
- package/dist/spawners/direct-engine-spawner.d.ts.map +1 -0
- package/dist/spawners/direct-engine-spawner.js +24 -0
- package/dist/spawners/direct-engine-spawner.js.map +1 -0
- package/dist/state/run-store.d.ts +74 -0
- package/dist/state/run-store.d.ts.map +1 -0
- package/dist/state/run-store.js +269 -0
- package/dist/state/run-store.js.map +1 -0
- package/dist/state/run-store.test.d.ts +2 -0
- package/dist/state/run-store.test.d.ts.map +1 -0
- package/dist/state/run-store.test.js +130 -0
- package/dist/state/run-store.test.js.map +1 -0
- package/dist/state/state-machine.d.ts +6 -0
- package/dist/state/state-machine.d.ts.map +1 -0
- package/dist/state/state-machine.js +27 -0
- package/dist/state/state-machine.js.map +1 -0
- package/dist/state/status-derivation.d.ts +12 -0
- package/dist/state/status-derivation.d.ts.map +1 -0
- package/dist/state/status-derivation.js +30 -0
- package/dist/state/status-derivation.js.map +1 -0
- package/package.json +7 -4
- package/ARCHITECTURE.md +0 -57
- package/src/__tests__/__fixtures__/script-stage/contracts/book-context.contract.yaml +0 -4
- package/src/__tests__/engine.conditions-group.test.ts +0 -244
- package/src/__tests__/engine.conditions.test.ts +0 -186
- package/src/__tests__/engine.resume.test.ts +0 -108
- package/src/__tests__/engine.script-stage.test.ts +0 -125
- package/src/engine.context-event.test.ts +0 -175
- package/src/engine.ts +0 -491
- package/src/events.ts +0 -167
- package/src/pipeline/agent-loader.test.ts +0 -151
- package/src/pipeline/agent-loader.ts +0 -39
- package/src/pipeline/condition-evaluator.test.ts +0 -129
- package/src/pipeline/condition-evaluator.ts +0 -121
- package/src/pipeline/context-pack-loader.ts +0 -63
- package/src/pipeline/context-propagation.test.ts +0 -237
- package/src/pipeline/context-propagation.ts +0 -235
- package/src/pipeline/contract-loader.ts +0 -41
- package/src/pipeline/group-orchestrator.ts +0 -483
- package/src/pipeline/hook-executor.test.ts +0 -121
- package/src/pipeline/hook-executor.ts +0 -87
- package/src/pipeline/invariants-loader.test.ts +0 -51
- package/src/pipeline/invariants-loader.ts +0 -14
- package/src/pipeline/loader.test.ts +0 -128
- package/src/pipeline/loader.ts +0 -149
- package/src/pipeline/output-validator.test.ts +0 -100
- package/src/pipeline/output-validator.ts +0 -40
- package/src/pipeline/post-validator.ts +0 -124
- package/src/pipeline/skill-loader.test.ts +0 -44
- package/src/pipeline/skill-loader.ts +0 -32
- package/src/pipeline/stage-executor.ts +0 -654
- package/src/pipeline/stage-resolver.ts +0 -8
- package/src/pipeline/startup-executor.test.ts +0 -37
- package/src/pipeline/startup-executor.ts +0 -32
- package/src/pipeline/types.ts +0 -42
- package/src/repo-resolver.ts +0 -61
- package/src/spawners/direct-engine-spawner.ts +0 -26
- package/src/state/run-store.test.ts +0 -157
- package/src/state/run-store.ts +0 -362
- package/src/state/state-machine.ts +0 -35
- package/src/state/status-derivation.ts +0 -36
- package/tests/context-pack-loader.test.ts +0 -113
- package/tests/context-propagation.test.ts +0 -267
- package/tests/direct-engine-spawner.test.ts +0 -102
- package/tests/e2e/feature-v5.test.ts +0 -57
- package/tests/events.test.ts +0 -56
- package/tests/fixtures/agents/test-agent.agent.yaml +0 -5
- package/tests/fixtures/contracts/code-gen.contract.yaml +0 -6
- package/tests/fixtures/contracts/qa-gate.contract.yaml +0 -15
- package/tests/fixtures/contracts/test-contract.contract.yaml +0 -6
- package/tests/fixtures/pipelines/group-test.pipeline.yaml +0 -40
- package/tests/fixtures/pipelines/simple.pipeline.yaml +0 -15
- package/tests/fixtures/pipelines/two-stage.pipeline.yaml +0 -24
- package/tests/fixtures/software/pipelines/feature-builder.pipeline.yaml +0 -75
- package/tests/fixtures/test-project/agents/test-agent.agent.yaml +0 -5
- package/tests/fixtures/test-project/contracts/basic-result.contract.yaml +0 -6
- package/tests/fixtures/test-project/contracts/code-gen.contract.yaml +0 -6
- package/tests/fixtures/test-project/contracts/maximum-only.contract.yaml +0 -8
- package/tests/fixtures/test-project/contracts/qa-gate.contract.yaml +0 -15
- package/tests/fixtures/test-project/contracts/strict-result.contract.yaml +0 -7
- package/tests/fixtures/test-project/contracts/test-contract.contract.yaml +0 -6
- package/tests/fixtures/test-project/pipelines/group-simple.pipeline.yaml +0 -26
- package/tests/fixtures/test-project/pipelines/group-test.pipeline.yaml +0 -40
- package/tests/fixtures/test-project/pipelines/hook-output-template.pipeline.yaml +0 -19
- package/tests/fixtures/test-project/pipelines/hook-reject-on-failure.pipeline.yaml +0 -19
- package/tests/fixtures/test-project/pipelines/maximum-only.pipeline.yaml +0 -15
- package/tests/fixtures/test-project/pipelines/parallel-collect-all-test.pipeline.yaml +0 -39
- package/tests/fixtures/test-project/pipelines/parallel-context-isolation-test.pipeline.yaml +0 -38
- package/tests/fixtures/test-project/pipelines/parallel-fail-test.pipeline.yaml +0 -39
- package/tests/fixtures/test-project/pipelines/parallel-test.pipeline.yaml +0 -39
- package/tests/fixtures/test-project/pipelines/parallel-then-sequential-test.pipeline.yaml +0 -38
- package/tests/fixtures/test-project/pipelines/simple.pipeline.yaml +0 -15
- package/tests/fixtures/test-project/pipelines/two-stage.pipeline.yaml +0 -24
- package/tests/fixtures/test-project/pipelines/with-startup.pipeline.yaml +0 -19
- package/tests/loader.test.ts +0 -385
- package/tests/post-validator.test.ts +0 -297
- package/tests/repo-resolver.test.ts +0 -102
- package/tests/run-store.test.ts +0 -143
- package/tests/state-machine.test.ts +0 -110
- package/tests/unit/context-propagation.test.ts +0 -45
- package/tests/unit/engine.test.ts +0 -770
- package/tests/unit/group-loop.test.ts +0 -389
- package/tests/unit/group-parallel.test.ts +0 -423
- package/tests/unit/state/status-derivation.test.ts +0 -88
- package/tsconfig.json +0 -24
- 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.
|
|
3
|
+
"version": "0.3.0-beta.6",
|
|
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.
|
|
27
|
-
"@studio-foundation/
|
|
28
|
-
"@studio-foundation/
|
|
29
|
+
"@studio-foundation/contracts": "0.3.0-beta.6",
|
|
30
|
+
"@studio-foundation/runner": "0.3.0-beta.6",
|
|
31
|
+
"@studio-foundation/ralph": "0.3.0-beta.6"
|
|
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,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
|
-
});
|