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