@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.
- 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,389 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { PipelineEngine, type EngineConfig } 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
|
-
// Ensure fixture dirs exist (shared with engine.test.ts)
|
|
15
|
-
mkdirSync(PIPELINES_DIR, { recursive: true });
|
|
16
|
-
mkdirSync(AGENTS_DIR, { recursive: true });
|
|
17
|
-
mkdirSync(CONTRACTS_DIR, { recursive: true });
|
|
18
|
-
|
|
19
|
-
// Agent fixture (reuse existing)
|
|
20
|
-
writeFileSync(join(AGENTS_DIR, 'test-agent.agent.yaml'), `
|
|
21
|
-
name: test-agent
|
|
22
|
-
provider: anthropic
|
|
23
|
-
model: claude-sonnet-4-20250514
|
|
24
|
-
temperature: 0.3
|
|
25
|
-
`);
|
|
26
|
-
|
|
27
|
-
// Contract with approval gate for QA
|
|
28
|
-
writeFileSync(join(CONTRACTS_DIR, 'qa-gate.contract.yaml'), `
|
|
29
|
-
name: qa-gate
|
|
30
|
-
version: 1
|
|
31
|
-
schema:
|
|
32
|
-
required_fields:
|
|
33
|
-
- status
|
|
34
|
-
- issues
|
|
35
|
-
post_validation:
|
|
36
|
-
rejection_detection:
|
|
37
|
-
field: status
|
|
38
|
-
approved_values:
|
|
39
|
-
- approved
|
|
40
|
-
- pass
|
|
41
|
-
details_field: issues
|
|
42
|
-
summary_field: summary
|
|
43
|
-
`);
|
|
44
|
-
|
|
45
|
-
// Contract without approval (for code-gen stage)
|
|
46
|
-
writeFileSync(join(CONTRACTS_DIR, 'code-gen.contract.yaml'), `
|
|
47
|
-
name: code-gen
|
|
48
|
-
version: 1
|
|
49
|
-
schema:
|
|
50
|
-
required_fields:
|
|
51
|
-
- files_changed
|
|
52
|
-
`);
|
|
53
|
-
|
|
54
|
-
// Pipeline with a group
|
|
55
|
-
writeFileSync(join(PIPELINES_DIR, 'group-test.pipeline.yaml'), `
|
|
56
|
-
name: group-test
|
|
57
|
-
description: Test pipeline with a feedback loop group
|
|
58
|
-
version: 2
|
|
59
|
-
stages:
|
|
60
|
-
- name: analysis
|
|
61
|
-
kind: analysis
|
|
62
|
-
agent: test-agent
|
|
63
|
-
ralph:
|
|
64
|
-
max_attempts: 1
|
|
65
|
-
retry_strategy: none
|
|
66
|
-
context:
|
|
67
|
-
include:
|
|
68
|
-
- input
|
|
69
|
-
- group: impl-review
|
|
70
|
-
max_iterations: 3
|
|
71
|
-
stages:
|
|
72
|
-
- name: code-gen
|
|
73
|
-
kind: code_generation
|
|
74
|
-
agent: test-agent
|
|
75
|
-
contract: code-gen
|
|
76
|
-
ralph:
|
|
77
|
-
max_attempts: 1
|
|
78
|
-
retry_strategy: none
|
|
79
|
-
context:
|
|
80
|
-
include:
|
|
81
|
-
- input
|
|
82
|
-
- all_stage_outputs
|
|
83
|
-
- group_feedback
|
|
84
|
-
- name: qa-review
|
|
85
|
-
kind: qa
|
|
86
|
-
agent: test-agent
|
|
87
|
-
contract: qa-gate
|
|
88
|
-
ralph:
|
|
89
|
-
max_attempts: 1
|
|
90
|
-
retry_strategy: none
|
|
91
|
-
context:
|
|
92
|
-
include:
|
|
93
|
-
- input
|
|
94
|
-
- all_stage_outputs
|
|
95
|
-
`);
|
|
96
|
-
|
|
97
|
-
function createMockProvider(callFn: (...args: any[]) => any) {
|
|
98
|
-
return {
|
|
99
|
-
name: 'anthropic',
|
|
100
|
-
call: vi.fn(callFn),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function createMockToolRegistry() {
|
|
105
|
-
return {
|
|
106
|
-
register: vi.fn(),
|
|
107
|
-
get: vi.fn(),
|
|
108
|
-
has: vi.fn().mockReturnValue(false),
|
|
109
|
-
list: vi.fn().mockReturnValue([]),
|
|
110
|
-
toToolDefinitions: vi.fn().mockReturnValue([]),
|
|
111
|
-
filter: vi.fn().mockReturnThis(),
|
|
112
|
-
getActiveSnippets: vi.fn().mockReturnValue([]),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function createEngine(provider: any, events?: EngineEvents): PipelineEngine {
|
|
117
|
-
return new PipelineEngine(
|
|
118
|
-
{
|
|
119
|
-
configsDir: PROJECT_DIR,
|
|
120
|
-
providerRegistry: { get: vi.fn().mockReturnValue(provider), register: vi.fn() } as any,
|
|
121
|
-
toolRegistry: createMockToolRegistry() as any,
|
|
122
|
-
db: new InMemoryRunStore(),
|
|
123
|
-
},
|
|
124
|
-
events
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Response helpers
|
|
129
|
-
function analysisResponse() {
|
|
130
|
-
return {
|
|
131
|
-
content: JSON.stringify({
|
|
132
|
-
summary: 'Analysis done',
|
|
133
|
-
requirements: ['r1'],
|
|
134
|
-
acceptance_criteria: ['ac1'],
|
|
135
|
-
}),
|
|
136
|
-
tool_calls: [],
|
|
137
|
-
finish_reason: 'stop',
|
|
138
|
-
usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function codeGenResponse() {
|
|
143
|
-
return {
|
|
144
|
-
content: JSON.stringify({
|
|
145
|
-
files_changed: ['src/app.ts'],
|
|
146
|
-
}),
|
|
147
|
-
tool_calls: [],
|
|
148
|
-
finish_reason: 'stop',
|
|
149
|
-
usage: { prompt_tokens: 200, completion_tokens: 100, total_tokens: 300 },
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function qaApproveResponse() {
|
|
154
|
-
return {
|
|
155
|
-
content: JSON.stringify({
|
|
156
|
-
status: 'approved',
|
|
157
|
-
issues: [],
|
|
158
|
-
}),
|
|
159
|
-
tool_calls: [],
|
|
160
|
-
finish_reason: 'stop',
|
|
161
|
-
usage: { prompt_tokens: 150, completion_tokens: 50, total_tokens: 200 },
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function qaRejectResponse(reason: string, issues: string[]) {
|
|
166
|
-
return {
|
|
167
|
-
content: JSON.stringify({
|
|
168
|
-
status: 'needs_changes',
|
|
169
|
-
summary: reason,
|
|
170
|
-
issues,
|
|
171
|
-
}),
|
|
172
|
-
tool_calls: [],
|
|
173
|
-
finish_reason: 'stop',
|
|
174
|
-
usage: { prompt_tokens: 150, completion_tokens: 50, total_tokens: 200 },
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
describe('Group feedback loop', () => {
|
|
179
|
-
it('succeeds on first iteration when QA approves', async () => {
|
|
180
|
-
let callCount = 0;
|
|
181
|
-
const provider = createMockProvider(() => {
|
|
182
|
-
callCount++;
|
|
183
|
-
if (callCount === 1) return analysisResponse();
|
|
184
|
-
if (callCount === 2) return codeGenResponse();
|
|
185
|
-
return qaApproveResponse();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const engine = createEngine(provider);
|
|
189
|
-
const result = await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
190
|
-
|
|
191
|
-
expect(result.status).toBe('success');
|
|
192
|
-
// 3 stage runs: analysis + code-gen + qa-review
|
|
193
|
-
expect(result.stages).toHaveLength(3);
|
|
194
|
-
expect(result.stages[0].stage_name).toBe('analysis');
|
|
195
|
-
expect(result.stages[1].stage_name).toBe('code-gen');
|
|
196
|
-
expect(result.stages[2].stage_name).toBe('qa-review');
|
|
197
|
-
expect(provider.call).toHaveBeenCalledTimes(3);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('succeeds on second iteration after QA rejection', async () => {
|
|
201
|
-
let callCount = 0;
|
|
202
|
-
const provider = createMockProvider(() => {
|
|
203
|
-
callCount++;
|
|
204
|
-
if (callCount === 1) return analysisResponse(); // analysis
|
|
205
|
-
if (callCount === 2) return codeGenResponse(); // code-gen iter 1
|
|
206
|
-
if (callCount === 3) return qaRejectResponse( // qa rejects iter 1
|
|
207
|
-
'Missing error handling',
|
|
208
|
-
['No try-catch around API call', 'Missing null check']
|
|
209
|
-
);
|
|
210
|
-
if (callCount === 4) return codeGenResponse(); // code-gen iter 2
|
|
211
|
-
return qaApproveResponse(); // qa approves iter 2
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const engine = createEngine(provider);
|
|
215
|
-
const result = await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
216
|
-
|
|
217
|
-
expect(result.status).toBe('success');
|
|
218
|
-
// 5 stage runs: analysis + (code-gen + qa) × 2
|
|
219
|
-
expect(result.stages).toHaveLength(5);
|
|
220
|
-
expect(provider.call).toHaveBeenCalledTimes(5);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('rejects after max_iterations exhausted', async () => {
|
|
224
|
-
let callCount = 0;
|
|
225
|
-
const provider = createMockProvider(() => {
|
|
226
|
-
callCount++;
|
|
227
|
-
if (callCount === 1) return analysisResponse();
|
|
228
|
-
// Alternate code-gen / qa-reject forever
|
|
229
|
-
if (callCount % 2 === 0) return codeGenResponse();
|
|
230
|
-
return qaRejectResponse('Still broken', ['Issue persists']);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const engine = createEngine(provider);
|
|
234
|
-
const result = await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
235
|
-
|
|
236
|
-
expect(result.status).toBe('rejected');
|
|
237
|
-
// 1 analysis + 3 × (code-gen + qa) = 7
|
|
238
|
-
expect(result.stages).toHaveLength(7);
|
|
239
|
-
expect(provider.call).toHaveBeenCalledTimes(7);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('fails immediately on technical failure in group', async () => {
|
|
243
|
-
let callCount = 0;
|
|
244
|
-
const provider = createMockProvider(() => {
|
|
245
|
-
callCount++;
|
|
246
|
-
if (callCount === 1) return analysisResponse();
|
|
247
|
-
// code-gen returns invalid output (missing required field)
|
|
248
|
-
return {
|
|
249
|
-
content: JSON.stringify({ no_files_changed: true }),
|
|
250
|
-
tool_calls: [],
|
|
251
|
-
finish_reason: 'stop',
|
|
252
|
-
usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },
|
|
253
|
-
};
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
const engine = createEngine(provider);
|
|
257
|
-
const result = await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
258
|
-
|
|
259
|
-
expect(result.status).toBe('failed');
|
|
260
|
-
// Only 2 stage runs: analysis + code-gen (failed, no qa)
|
|
261
|
-
expect(result.stages).toHaveLength(2);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('preserves pre-group outputs across iterations', async () => {
|
|
265
|
-
// Track what context the code-gen agent receives on iteration 2
|
|
266
|
-
let callCount = 0;
|
|
267
|
-
const callArgs: any[] = [];
|
|
268
|
-
const provider = createMockProvider((...args: any[]) => {
|
|
269
|
-
callCount++;
|
|
270
|
-
callArgs.push(args);
|
|
271
|
-
if (callCount === 1) return analysisResponse();
|
|
272
|
-
if (callCount === 2) return codeGenResponse();
|
|
273
|
-
if (callCount === 3) return qaRejectResponse('Bug', ['Fix it']);
|
|
274
|
-
if (callCount === 4) return codeGenResponse(); // iter 2 code-gen
|
|
275
|
-
return qaApproveResponse();
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const engine = createEngine(provider);
|
|
279
|
-
await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
280
|
-
|
|
281
|
-
// The 4th call is code-gen iteration 2 — it should have analysis output in context
|
|
282
|
-
// (Verified via provider.call args which include messages containing the context)
|
|
283
|
-
expect(provider.call).toHaveBeenCalledTimes(5);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('passes object issues from QA rejection into group_feedback for the coder', async () => {
|
|
287
|
-
// Regression test for STU-129: group_feedback.rejection_details was empty when
|
|
288
|
-
// QA returned issues as objects with field names other than "description".
|
|
289
|
-
let callCount = 0;
|
|
290
|
-
let coderIter2Messages: any[] = [];
|
|
291
|
-
const provider = createMockProvider((...args: any[]) => {
|
|
292
|
-
callCount++;
|
|
293
|
-
if (callCount === 1) return analysisResponse();
|
|
294
|
-
if (callCount === 2) return codeGenResponse(); // iter 1
|
|
295
|
-
if (callCount === 3) {
|
|
296
|
-
// QA rejects with structured issues (objects with "issue" field, not "description")
|
|
297
|
-
return {
|
|
298
|
-
content: JSON.stringify({
|
|
299
|
-
status: 'needs_changes',
|
|
300
|
-
summary: 'Several problems found',
|
|
301
|
-
issues: [
|
|
302
|
-
{ issue: 'Missing error handling', severity: 'high' },
|
|
303
|
-
{ issue: 'No input validation', file: 'src/api.ts' },
|
|
304
|
-
],
|
|
305
|
-
}),
|
|
306
|
-
tool_calls: [],
|
|
307
|
-
finish_reason: 'stop',
|
|
308
|
-
usage: { prompt_tokens: 150, completion_tokens: 50, total_tokens: 200 },
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
if (callCount === 4) {
|
|
312
|
-
// Capture the messages sent to the coder on iteration 2
|
|
313
|
-
coderIter2Messages = args[0]?.messages ?? [];
|
|
314
|
-
return codeGenResponse();
|
|
315
|
-
}
|
|
316
|
-
return qaApproveResponse();
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const engine = createEngine(provider);
|
|
320
|
-
const result = await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
321
|
-
|
|
322
|
-
expect(result.status).toBe('success');
|
|
323
|
-
expect(provider.call).toHaveBeenCalledTimes(5);
|
|
324
|
-
|
|
325
|
-
// The coder on iteration 2 must have received group_feedback with the specific issues
|
|
326
|
-
const userMessage = coderIter2Messages.find((m: any) => m.role === 'user');
|
|
327
|
-
expect(userMessage).toBeDefined();
|
|
328
|
-
expect(userMessage.content).toContain('REVISION REQUIRED');
|
|
329
|
-
expect(userMessage.content).toContain('Missing error handling');
|
|
330
|
-
expect(userMessage.content).toContain('No input validation');
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('fails pipeline (not group retry) when executor throws during group iteration', async () => {
|
|
334
|
-
let callCount = 0;
|
|
335
|
-
const provider = createMockProvider(() => {
|
|
336
|
-
callCount++;
|
|
337
|
-
if (callCount === 1) return analysisResponse(); // pre-group analysis
|
|
338
|
-
if (callCount === 2) return codeGenResponse(); // iter 1: code-gen OK
|
|
339
|
-
if (callCount === 3) return qaRejectResponse( // iter 1: QA rejects
|
|
340
|
-
'Missing error handling',
|
|
341
|
-
['No try-catch around API call']
|
|
342
|
-
);
|
|
343
|
-
// iter 2: code-gen throws a technical error (network timeout, etc.)
|
|
344
|
-
throw new Error('Network timeout');
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const engine = createEngine(provider);
|
|
348
|
-
const result = await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
349
|
-
|
|
350
|
-
// Technical throw → stage failed → pipeline failed, no infinite group retry
|
|
351
|
-
expect(result.status).toBe('failed');
|
|
352
|
-
// analysis + code-gen(iter1) + qa-reject(iter1) + code-gen(iter2 throws) = 4
|
|
353
|
-
expect(result.stages).toHaveLength(4);
|
|
354
|
-
expect(provider.call).toHaveBeenCalledTimes(4);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it('emits group lifecycle events', async () => {
|
|
358
|
-
let callCount = 0;
|
|
359
|
-
const provider = createMockProvider(() => {
|
|
360
|
-
callCount++;
|
|
361
|
-
if (callCount === 1) return analysisResponse();
|
|
362
|
-
if (callCount === 2) return codeGenResponse();
|
|
363
|
-
if (callCount === 3) return qaRejectResponse('Bug', ['Fix it']);
|
|
364
|
-
if (callCount === 4) return codeGenResponse();
|
|
365
|
-
return qaApproveResponse();
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const events: string[] = [];
|
|
369
|
-
const engineEvents: EngineEvents = {
|
|
370
|
-
onGroupStart: () => events.push('group_start'),
|
|
371
|
-
onGroupIteration: (e) => events.push(`group_iter_${e.iteration}`),
|
|
372
|
-
onGroupFeedback: () => events.push('group_feedback'),
|
|
373
|
-
onGroupComplete: (e) => events.push(`group_complete_${e.status}`),
|
|
374
|
-
onStageStart: () => {},
|
|
375
|
-
onStageComplete: () => {},
|
|
376
|
-
onPipelineStart: () => {},
|
|
377
|
-
onPipelineComplete: () => {},
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
const engine = createEngine(provider, engineEvents);
|
|
381
|
-
await engine.run({ pipeline: 'group-test', input: 'Build feature' });
|
|
382
|
-
|
|
383
|
-
expect(events).toContain('group_start');
|
|
384
|
-
expect(events).toContain('group_iter_1');
|
|
385
|
-
expect(events).toContain('group_feedback');
|
|
386
|
-
expect(events).toContain('group_iter_2');
|
|
387
|
-
expect(events).toContain('group_complete_success');
|
|
388
|
-
});
|
|
389
|
-
});
|