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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/dist/__tests__/engine.conditions-group.test.d.ts +2 -0
  2. package/dist/__tests__/engine.conditions-group.test.d.ts.map +1 -0
  3. package/dist/__tests__/engine.conditions-group.test.js +222 -0
  4. package/dist/__tests__/engine.conditions-group.test.js.map +1 -0
  5. package/dist/__tests__/engine.conditions.test.d.ts +2 -0
  6. package/dist/__tests__/engine.conditions.test.d.ts.map +1 -0
  7. package/dist/__tests__/engine.conditions.test.js +164 -0
  8. package/dist/__tests__/engine.conditions.test.js.map +1 -0
  9. package/dist/__tests__/engine.resume.test.d.ts +2 -0
  10. package/dist/__tests__/engine.resume.test.d.ts.map +1 -0
  11. package/dist/__tests__/engine.resume.test.js +93 -0
  12. package/dist/__tests__/engine.resume.test.js.map +1 -0
  13. package/dist/__tests__/engine.script-stage.test.d.ts +2 -0
  14. package/dist/__tests__/engine.script-stage.test.d.ts.map +1 -0
  15. package/dist/__tests__/engine.script-stage.test.js +108 -0
  16. package/dist/__tests__/engine.script-stage.test.js.map +1 -0
  17. package/dist/db/client.d.ts +3 -0
  18. package/dist/db/client.d.ts.map +1 -0
  19. package/{src/db/client.ts → dist/db/client.js} +1 -2
  20. package/dist/db/client.js.map +1 -0
  21. package/dist/engine.context-event.test.d.ts +2 -0
  22. package/dist/engine.context-event.test.d.ts.map +1 -0
  23. package/dist/engine.context-event.test.js +111 -0
  24. package/dist/engine.context-event.test.js.map +1 -0
  25. package/dist/engine.d.ts +52 -0
  26. package/dist/engine.d.ts.map +1 -0
  27. package/dist/engine.js +364 -0
  28. package/dist/engine.js.map +1 -0
  29. package/dist/events.d.ts +163 -0
  30. package/dist/events.d.ts.map +1 -0
  31. package/dist/events.js +12 -0
  32. package/dist/events.js.map +1 -0
  33. package/{src/index.ts → dist/index.d.ts} +3 -45
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +24 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/pipeline/agent-loader.d.ts +4 -0
  38. package/dist/pipeline/agent-loader.d.ts.map +1 -0
  39. package/dist/pipeline/agent-loader.js +28 -0
  40. package/dist/pipeline/agent-loader.js.map +1 -0
  41. package/dist/pipeline/agent-loader.test.d.ts +2 -0
  42. package/dist/pipeline/agent-loader.test.d.ts.map +1 -0
  43. package/dist/pipeline/agent-loader.test.js +134 -0
  44. package/dist/pipeline/agent-loader.test.js.map +1 -0
  45. package/dist/pipeline/condition-evaluator.d.ts +6 -0
  46. package/dist/pipeline/condition-evaluator.d.ts.map +1 -0
  47. package/dist/pipeline/condition-evaluator.js +105 -0
  48. package/dist/pipeline/condition-evaluator.js.map +1 -0
  49. package/dist/pipeline/condition-evaluator.test.d.ts +2 -0
  50. package/dist/pipeline/condition-evaluator.test.d.ts.map +1 -0
  51. package/dist/pipeline/condition-evaluator.test.js +103 -0
  52. package/dist/pipeline/condition-evaluator.test.js.map +1 -0
  53. package/dist/pipeline/context-pack-loader.d.ts +3 -0
  54. package/dist/pipeline/context-pack-loader.d.ts.map +1 -0
  55. package/dist/pipeline/context-pack-loader.js +51 -0
  56. package/dist/pipeline/context-pack-loader.js.map +1 -0
  57. package/dist/pipeline/context-propagation.d.ts +28 -0
  58. package/dist/pipeline/context-propagation.d.ts.map +1 -0
  59. package/dist/pipeline/context-propagation.js +161 -0
  60. package/dist/pipeline/context-propagation.js.map +1 -0
  61. package/dist/pipeline/context-propagation.test.d.ts +2 -0
  62. package/dist/pipeline/context-propagation.test.d.ts.map +1 -0
  63. package/dist/pipeline/context-propagation.test.js +194 -0
  64. package/dist/pipeline/context-propagation.test.js.map +1 -0
  65. package/dist/pipeline/contract-loader.d.ts +4 -0
  66. package/dist/pipeline/contract-loader.d.ts.map +1 -0
  67. package/dist/pipeline/contract-loader.js +30 -0
  68. package/dist/pipeline/contract-loader.js.map +1 -0
  69. package/dist/pipeline/group-orchestrator.d.ts +19 -0
  70. package/dist/pipeline/group-orchestrator.d.ts.map +1 -0
  71. package/dist/pipeline/group-orchestrator.js +376 -0
  72. package/dist/pipeline/group-orchestrator.js.map +1 -0
  73. package/dist/pipeline/hook-executor.d.ts +28 -0
  74. package/dist/pipeline/hook-executor.d.ts.map +1 -0
  75. package/dist/pipeline/hook-executor.js +63 -0
  76. package/dist/pipeline/hook-executor.js.map +1 -0
  77. package/dist/pipeline/hook-executor.test.d.ts +2 -0
  78. package/dist/pipeline/hook-executor.test.d.ts.map +1 -0
  79. package/dist/pipeline/hook-executor.test.js +71 -0
  80. package/dist/pipeline/hook-executor.test.js.map +1 -0
  81. package/dist/pipeline/invariants-loader.d.ts +6 -0
  82. package/dist/pipeline/invariants-loader.d.ts.map +1 -0
  83. package/dist/pipeline/invariants-loader.js +15 -0
  84. package/dist/pipeline/invariants-loader.js.map +1 -0
  85. package/dist/pipeline/invariants-loader.test.d.ts +2 -0
  86. package/dist/pipeline/invariants-loader.test.d.ts.map +1 -0
  87. package/dist/pipeline/invariants-loader.test.js +39 -0
  88. package/dist/pipeline/invariants-loader.test.js.map +1 -0
  89. package/dist/pipeline/loader.d.ts +5 -0
  90. package/dist/pipeline/loader.d.ts.map +1 -0
  91. package/dist/pipeline/loader.js +132 -0
  92. package/dist/pipeline/loader.js.map +1 -0
  93. package/dist/pipeline/loader.test.d.ts +2 -0
  94. package/dist/pipeline/loader.test.d.ts.map +1 -0
  95. package/dist/pipeline/loader.test.js +120 -0
  96. package/dist/pipeline/loader.test.js.map +1 -0
  97. package/dist/pipeline/output-validator.d.ts +10 -0
  98. package/dist/pipeline/output-validator.d.ts.map +1 -0
  99. package/dist/pipeline/output-validator.js +20 -0
  100. package/dist/pipeline/output-validator.js.map +1 -0
  101. package/dist/pipeline/output-validator.test.d.ts +2 -0
  102. package/dist/pipeline/output-validator.test.d.ts.map +1 -0
  103. package/dist/pipeline/output-validator.test.js +77 -0
  104. package/dist/pipeline/output-validator.test.js.map +1 -0
  105. package/dist/pipeline/post-validator.d.ts +8 -0
  106. package/dist/pipeline/post-validator.d.ts.map +1 -0
  107. package/dist/pipeline/post-validator.js +103 -0
  108. package/dist/pipeline/post-validator.js.map +1 -0
  109. package/dist/pipeline/skill-loader.d.ts +10 -0
  110. package/dist/pipeline/skill-loader.d.ts.map +1 -0
  111. package/dist/pipeline/skill-loader.js +26 -0
  112. package/dist/pipeline/skill-loader.js.map +1 -0
  113. package/dist/pipeline/skill-loader.test.d.ts +2 -0
  114. package/dist/pipeline/skill-loader.test.d.ts.map +1 -0
  115. package/dist/pipeline/skill-loader.test.js +38 -0
  116. package/dist/pipeline/skill-loader.test.js.map +1 -0
  117. package/dist/pipeline/stage-executor.d.ts +26 -0
  118. package/dist/pipeline/stage-executor.d.ts.map +1 -0
  119. package/dist/pipeline/stage-executor.js +540 -0
  120. package/dist/pipeline/stage-executor.js.map +1 -0
  121. package/dist/pipeline/stage-resolver.d.ts +3 -0
  122. package/dist/pipeline/stage-resolver.d.ts.map +1 -0
  123. package/dist/pipeline/stage-resolver.js +6 -0
  124. package/dist/pipeline/stage-resolver.js.map +1 -0
  125. package/dist/pipeline/startup-executor.d.ts +3 -0
  126. package/dist/pipeline/startup-executor.d.ts.map +1 -0
  127. package/dist/pipeline/startup-executor.js +23 -0
  128. package/dist/pipeline/startup-executor.js.map +1 -0
  129. package/dist/pipeline/startup-executor.test.d.ts +2 -0
  130. package/dist/pipeline/startup-executor.test.d.ts.map +1 -0
  131. package/dist/pipeline/startup-executor.test.js +34 -0
  132. package/dist/pipeline/startup-executor.test.js.map +1 -0
  133. package/dist/pipeline/types.d.ts +28 -0
  134. package/dist/pipeline/types.d.ts.map +1 -0
  135. package/dist/pipeline/types.js +12 -0
  136. package/dist/pipeline/types.js.map +1 -0
  137. package/dist/repo-resolver.d.ts +10 -0
  138. package/dist/repo-resolver.d.ts.map +1 -0
  139. package/dist/repo-resolver.js +39 -0
  140. package/dist/repo-resolver.js.map +1 -0
  141. package/dist/spawners/direct-engine-spawner.d.ts +8 -0
  142. package/dist/spawners/direct-engine-spawner.d.ts.map +1 -0
  143. package/dist/spawners/direct-engine-spawner.js +24 -0
  144. package/dist/spawners/direct-engine-spawner.js.map +1 -0
  145. package/dist/state/run-store.d.ts +74 -0
  146. package/dist/state/run-store.d.ts.map +1 -0
  147. package/dist/state/run-store.js +269 -0
  148. package/dist/state/run-store.js.map +1 -0
  149. package/dist/state/run-store.test.d.ts +2 -0
  150. package/dist/state/run-store.test.d.ts.map +1 -0
  151. package/dist/state/run-store.test.js +130 -0
  152. package/dist/state/run-store.test.js.map +1 -0
  153. package/dist/state/state-machine.d.ts +6 -0
  154. package/dist/state/state-machine.d.ts.map +1 -0
  155. package/dist/state/state-machine.js +27 -0
  156. package/dist/state/state-machine.js.map +1 -0
  157. package/dist/state/status-derivation.d.ts +12 -0
  158. package/dist/state/status-derivation.d.ts.map +1 -0
  159. package/dist/state/status-derivation.js +30 -0
  160. package/dist/state/status-derivation.js.map +1 -0
  161. package/package.json +7 -4
  162. package/ARCHITECTURE.md +0 -57
  163. package/src/__tests__/__fixtures__/script-stage/contracts/book-context.contract.yaml +0 -4
  164. package/src/__tests__/engine.conditions-group.test.ts +0 -244
  165. package/src/__tests__/engine.conditions.test.ts +0 -186
  166. package/src/__tests__/engine.resume.test.ts +0 -108
  167. package/src/__tests__/engine.script-stage.test.ts +0 -125
  168. package/src/engine.context-event.test.ts +0 -175
  169. package/src/engine.ts +0 -491
  170. package/src/events.ts +0 -167
  171. package/src/pipeline/agent-loader.test.ts +0 -151
  172. package/src/pipeline/agent-loader.ts +0 -39
  173. package/src/pipeline/condition-evaluator.test.ts +0 -129
  174. package/src/pipeline/condition-evaluator.ts +0 -121
  175. package/src/pipeline/context-pack-loader.ts +0 -63
  176. package/src/pipeline/context-propagation.test.ts +0 -237
  177. package/src/pipeline/context-propagation.ts +0 -235
  178. package/src/pipeline/contract-loader.ts +0 -41
  179. package/src/pipeline/group-orchestrator.ts +0 -483
  180. package/src/pipeline/hook-executor.test.ts +0 -121
  181. package/src/pipeline/hook-executor.ts +0 -87
  182. package/src/pipeline/invariants-loader.test.ts +0 -51
  183. package/src/pipeline/invariants-loader.ts +0 -14
  184. package/src/pipeline/loader.test.ts +0 -128
  185. package/src/pipeline/loader.ts +0 -149
  186. package/src/pipeline/output-validator.test.ts +0 -100
  187. package/src/pipeline/output-validator.ts +0 -40
  188. package/src/pipeline/post-validator.ts +0 -124
  189. package/src/pipeline/skill-loader.test.ts +0 -44
  190. package/src/pipeline/skill-loader.ts +0 -32
  191. package/src/pipeline/stage-executor.ts +0 -654
  192. package/src/pipeline/stage-resolver.ts +0 -8
  193. package/src/pipeline/startup-executor.test.ts +0 -37
  194. package/src/pipeline/startup-executor.ts +0 -32
  195. package/src/pipeline/types.ts +0 -42
  196. package/src/repo-resolver.ts +0 -61
  197. package/src/spawners/direct-engine-spawner.ts +0 -26
  198. package/src/state/run-store.test.ts +0 -157
  199. package/src/state/run-store.ts +0 -362
  200. package/src/state/state-machine.ts +0 -35
  201. package/src/state/status-derivation.ts +0 -36
  202. package/tests/context-pack-loader.test.ts +0 -113
  203. package/tests/context-propagation.test.ts +0 -267
  204. package/tests/direct-engine-spawner.test.ts +0 -102
  205. package/tests/e2e/feature-v5.test.ts +0 -57
  206. package/tests/events.test.ts +0 -56
  207. package/tests/fixtures/agents/test-agent.agent.yaml +0 -5
  208. package/tests/fixtures/contracts/code-gen.contract.yaml +0 -6
  209. package/tests/fixtures/contracts/qa-gate.contract.yaml +0 -15
  210. package/tests/fixtures/contracts/test-contract.contract.yaml +0 -6
  211. package/tests/fixtures/pipelines/group-test.pipeline.yaml +0 -40
  212. package/tests/fixtures/pipelines/simple.pipeline.yaml +0 -15
  213. package/tests/fixtures/pipelines/two-stage.pipeline.yaml +0 -24
  214. package/tests/fixtures/software/pipelines/feature-builder.pipeline.yaml +0 -75
  215. package/tests/fixtures/test-project/agents/test-agent.agent.yaml +0 -5
  216. package/tests/fixtures/test-project/contracts/basic-result.contract.yaml +0 -6
  217. package/tests/fixtures/test-project/contracts/code-gen.contract.yaml +0 -6
  218. package/tests/fixtures/test-project/contracts/maximum-only.contract.yaml +0 -8
  219. package/tests/fixtures/test-project/contracts/qa-gate.contract.yaml +0 -15
  220. package/tests/fixtures/test-project/contracts/strict-result.contract.yaml +0 -7
  221. package/tests/fixtures/test-project/contracts/test-contract.contract.yaml +0 -6
  222. package/tests/fixtures/test-project/pipelines/group-simple.pipeline.yaml +0 -26
  223. package/tests/fixtures/test-project/pipelines/group-test.pipeline.yaml +0 -40
  224. package/tests/fixtures/test-project/pipelines/hook-output-template.pipeline.yaml +0 -19
  225. package/tests/fixtures/test-project/pipelines/hook-reject-on-failure.pipeline.yaml +0 -19
  226. package/tests/fixtures/test-project/pipelines/maximum-only.pipeline.yaml +0 -15
  227. package/tests/fixtures/test-project/pipelines/parallel-collect-all-test.pipeline.yaml +0 -39
  228. package/tests/fixtures/test-project/pipelines/parallel-context-isolation-test.pipeline.yaml +0 -38
  229. package/tests/fixtures/test-project/pipelines/parallel-fail-test.pipeline.yaml +0 -39
  230. package/tests/fixtures/test-project/pipelines/parallel-test.pipeline.yaml +0 -39
  231. package/tests/fixtures/test-project/pipelines/parallel-then-sequential-test.pipeline.yaml +0 -38
  232. package/tests/fixtures/test-project/pipelines/simple.pipeline.yaml +0 -15
  233. package/tests/fixtures/test-project/pipelines/two-stage.pipeline.yaml +0 -24
  234. package/tests/fixtures/test-project/pipelines/with-startup.pipeline.yaml +0 -19
  235. package/tests/loader.test.ts +0 -385
  236. package/tests/post-validator.test.ts +0 -297
  237. package/tests/repo-resolver.test.ts +0 -102
  238. package/tests/run-store.test.ts +0 -143
  239. package/tests/state-machine.test.ts +0 -110
  240. package/tests/unit/context-propagation.test.ts +0 -45
  241. package/tests/unit/engine.test.ts +0 -770
  242. package/tests/unit/group-loop.test.ts +0 -389
  243. package/tests/unit/group-parallel.test.ts +0 -423
  244. package/tests/unit/state/status-derivation.test.ts +0 -88
  245. package/tsconfig.json +0 -24
  246. package/vitest.config.ts +0 -14
@@ -1,237 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- createInitialContext,
4
- getContextForStage,
5
- addStageOutput,
6
- buildContextKeys,
7
- buildContextContent,
8
- } from './context-propagation.js';
9
- import type { StageDefinition, ToolCall } from '@studio-foundation/contracts';
10
- import type { AgentContext } from '@studio-foundation/runner';
11
-
12
- const makeStage = (include: string[]): StageDefinition => ({
13
- name: 'test-stage',
14
- kind: 'analysis',
15
- agent: 'analyst',
16
- context: { include },
17
- });
18
-
19
- describe('getContextForStage — pipeline_start_context', () => {
20
- it('injects startup_context when stage includes pipeline_start_context', () => {
21
- const ctx = createInitialContext('my input');
22
- ctx.startupContext = { git_status: 'M src/foo.ts', recent_commits: 'abc123 feat: stuff' };
23
-
24
- const agentCtx = getContextForStage(ctx, makeStage(['pipeline_start_context']));
25
- expect(agentCtx.startup_context).toEqual({
26
- git_status: 'M src/foo.ts',
27
- recent_commits: 'abc123 feat: stuff',
28
- });
29
- });
30
-
31
- it('does not inject startup_context when not in include list', () => {
32
- const ctx = createInitialContext('my input');
33
- ctx.startupContext = { git_status: 'M src/foo.ts' };
34
-
35
- const agentCtx = getContextForStage(ctx, makeStage(['input']));
36
- expect(agentCtx.startup_context).toBeUndefined();
37
- });
38
-
39
- it('does not inject startup_context when startupContext is empty', () => {
40
- const ctx = createInitialContext('my input');
41
- ctx.startupContext = {};
42
-
43
- const agentCtx = getContextForStage(ctx, makeStage(['pipeline_start_context']));
44
- expect(agentCtx.startup_context).toBeUndefined();
45
- });
46
- });
47
-
48
- describe('addStageOutput — size tracking', () => {
49
- it('tracks serialized size when output is added', () => {
50
- const ctx = createInitialContext('input');
51
- const output = { summary: 'hello', items: [1, 2, 3] };
52
- addStageOutput(ctx, 'my-stage', output);
53
-
54
- const expectedSize = JSON.stringify(output).length;
55
- expect(ctx.stageOutputSizes.get('my-stage')).toBe(expectedSize);
56
- });
57
-
58
- it('tracks size for each stage independently', () => {
59
- const ctx = createInitialContext('input');
60
- const out1 = { a: 'short' };
61
- const out2 = { b: 'a much longer value here', c: [1, 2, 3, 4, 5] };
62
- addStageOutput(ctx, 'stage-1', out1);
63
- addStageOutput(ctx, 'stage-2', out2);
64
-
65
- expect(ctx.stageOutputSizes.get('stage-1')).toBe(JSON.stringify(out1).length);
66
- expect(ctx.stageOutputSizes.get('stage-2')).toBe(JSON.stringify(out2).length);
67
- });
68
-
69
- it('createInitialContext initializes stageOutputSizes as empty map', () => {
70
- const ctx = createInitialContext('input');
71
- expect(ctx.stageOutputSizes).toBeInstanceOf(Map);
72
- expect(ctx.stageOutputSizes.size).toBe(0);
73
- });
74
- });
75
-
76
- describe('buildContextKeys', () => {
77
- it('returns empty object for empty AgentContext', () => {
78
- const ctx: AgentContext = {};
79
- expect(buildContextKeys(ctx, new Map())).toEqual({});
80
- });
81
-
82
- it('includes input key when additional_context is set', () => {
83
- const ctx: AgentContext = { additional_context: 'hello world' };
84
- const keys = buildContextKeys(ctx, new Map());
85
- expect(keys.input).toBe('hello world'.length);
86
- });
87
-
88
- it('includes previous_stage_output with total size from size map', () => {
89
- const ctx: AgentContext = {
90
- previous_outputs: { 'brief-analysis': { summary: 'ok' } },
91
- };
92
- const sizes = new Map([['brief-analysis', 42]]);
93
- const keys = buildContextKeys(ctx, sizes);
94
- expect(keys.previous_stage_output).toBe(42);
95
- });
96
-
97
- it('sums sizes for multiple previous outputs', () => {
98
- const ctx: AgentContext = {
99
- previous_outputs: {
100
- 'stage-a': { x: 1 },
101
- 'stage-b': { y: 2 },
102
- },
103
- };
104
- const sizes = new Map([['stage-a', 10], ['stage-b', 20]]);
105
- const keys = buildContextKeys(ctx, sizes);
106
- expect(keys.previous_stage_output).toBe(30);
107
- });
108
-
109
- it('includes group_feedback key', () => {
110
- const feedback = { iteration: 1, max_iterations: 3, rejection_reason: 'nope' };
111
- const ctx: AgentContext = { group_feedback: feedback };
112
- const keys = buildContextKeys(ctx, new Map());
113
- expect(keys.group_feedback).toBe(JSON.stringify(feedback).length);
114
- });
115
-
116
- it('expands startup_context keys individually', () => {
117
- const ctx: AgentContext = {
118
- startup_context: {
119
- git_status: 'M src/foo.ts',
120
- recent_commits: 'abc def',
121
- },
122
- };
123
- const keys = buildContextKeys(ctx, new Map());
124
- expect(keys.git_status).toBe('M src/foo.ts'.length);
125
- expect(keys.recent_commits).toBe('abc def'.length);
126
- expect(keys.input).toBeUndefined();
127
- });
128
-
129
- it('includes context packs by name with total section chars', () => {
130
- const ctx: AgentContext = {
131
- context_packs: [
132
- {
133
- name: 'api-docs',
134
- sections: [
135
- { title: 'intro', content: 'hello' },
136
- { title: 'details', content: 'world!' },
137
- ],
138
- },
139
- ],
140
- };
141
- const keys = buildContextKeys(ctx, new Map());
142
- expect(keys['api-docs']).toBe('hello'.length + 'world!'.length);
143
- });
144
-
145
- it('omits absent keys', () => {
146
- const ctx: AgentContext = { additional_context: 'x' };
147
- const keys = buildContextKeys(ctx, new Map());
148
- expect(Object.keys(keys)).toEqual(['input']);
149
- });
150
- });
151
-
152
- const WRITE_TOOL_CALL: ToolCall = {
153
- id: '1',
154
- name: 'repo_manager-write_file',
155
- arguments: { path: 'src/foo.ts', content: 'const x = 1;' },
156
- result: { written: true },
157
- };
158
-
159
- describe('buildContextKeys — previous_tool_results', () => {
160
- it('includes previous_stage_tool_results key with total serialized byte size', () => {
161
- const ctx: AgentContext = { previous_tool_results: { 'code-generation': [WRITE_TOOL_CALL] } };
162
- const keys = buildContextKeys(ctx, new Map());
163
- expect(keys['previous_stage_tool_results']).toBe(JSON.stringify([WRITE_TOOL_CALL]).length);
164
- });
165
-
166
- it('sums sizes across multiple stages', () => {
167
- const tc2: ToolCall = { id: '2', name: 'repo_manager-read_file', arguments: { path: 'src/bar.ts' }, result: { path: 'src/bar.ts', content: 'hi' } };
168
- const ctx: AgentContext = {
169
- previous_tool_results: {
170
- 'stage-a': [WRITE_TOOL_CALL],
171
- 'stage-b': [tc2],
172
- },
173
- };
174
- const keys = buildContextKeys(ctx, new Map());
175
- expect(keys['previous_stage_tool_results']).toBe(
176
- JSON.stringify([WRITE_TOOL_CALL]).length + JSON.stringify([tc2]).length
177
- );
178
- });
179
-
180
- it('omits previous_stage_tool_results when previous_tool_results is absent', () => {
181
- const keys = buildContextKeys({}, new Map());
182
- expect(keys['previous_stage_tool_results']).toBeUndefined();
183
- });
184
-
185
- it('omits previous_stage_tool_results when previous_tool_results is empty', () => {
186
- const keys = buildContextKeys({ previous_tool_results: {} }, new Map());
187
- expect(keys['previous_stage_tool_results']).toBeUndefined();
188
- });
189
- });
190
-
191
- describe('buildContextContent — previous_tool_results', () => {
192
- it('maps previous_stage_tool_results to the previous_tool_results object', () => {
193
- const ctx: AgentContext = { previous_tool_results: { 'code-generation': [WRITE_TOOL_CALL] } };
194
- expect(buildContextContent(ctx)['previous_stage_tool_results']).toEqual({ 'code-generation': [WRITE_TOOL_CALL] });
195
- });
196
-
197
- it('omits previous_stage_tool_results when previous_tool_results is absent', () => {
198
- expect(buildContextContent({})['previous_stage_tool_results']).toBeUndefined();
199
- });
200
-
201
- it('omits previous_stage_tool_results when previous_tool_results is empty', () => {
202
- expect(buildContextContent({ previous_tool_results: {} })['previous_stage_tool_results']).toBeUndefined();
203
- });
204
- });
205
-
206
- describe('buildContextContent', () => {
207
- it('returns empty object for empty AgentContext', () => {
208
- expect(buildContextContent({})).toEqual({});
209
- });
210
-
211
- it('maps input key to additional_context value', () => {
212
- const ctx: AgentContext = { additional_context: 'my input' };
213
- expect(buildContextContent(ctx).input).toBe('my input');
214
- });
215
-
216
- it('maps previous_stage_output to previous_outputs object', () => {
217
- const ctx: AgentContext = { previous_outputs: { 'stage-a': { x: 1 } } };
218
- expect(buildContextContent(ctx).previous_stage_output).toEqual({ 'stage-a': { x: 1 } });
219
- });
220
-
221
- it('maps group_feedback key', () => {
222
- const fb = { iteration: 1, max_iterations: 3, rejection_reason: 'fail' };
223
- const ctx: AgentContext = { group_feedback: fb };
224
- expect(buildContextContent(ctx).group_feedback).toEqual(fb);
225
- });
226
-
227
- it('expands startup_context keys individually', () => {
228
- const ctx: AgentContext = { startup_context: { git_status: 'clean' } };
229
- expect(buildContextContent(ctx).git_status).toBe('clean');
230
- });
231
-
232
- it('maps each context pack by name to its pack object', () => {
233
- const pack = { name: 'api-docs', sections: [{ title: 'h', content: 'c' }] };
234
- const ctx: AgentContext = { context_packs: [pack] };
235
- expect(buildContextContent(ctx)['api-docs']).toEqual(pack);
236
- });
237
- });
@@ -1,235 +0,0 @@
1
- // Propagate context between stages
2
- // Each stage gets the accumulated context from all previous stages
3
-
4
- import * as yaml from 'js-yaml';
5
- import type { StageDefinition, ToolCall } from '@studio-foundation/contracts';
6
- import type { AgentContext } from '@studio-foundation/runner';
7
-
8
- export type PipelineInput = string | Record<string, unknown>;
9
-
10
- export interface GroupFeedback {
11
- iteration: number;
12
- max_iterations: number;
13
- rejection_reason: string;
14
- rejection_details?: string[];
15
- }
16
-
17
- export interface PipelineContext {
18
- input: PipelineInput;
19
- stageOutputs: Map<string, unknown>;
20
- stageOutputSizes: Map<string, number>;
21
- stageToolResults: Map<string, ToolCall[]>;
22
- repoPath?: string;
23
- groupFeedback?: GroupFeedback;
24
- startupContext?: Record<string, string>;
25
- invariantsContent?: string;
26
- }
27
-
28
- export function createInitialContext(input: PipelineInput, repoPath?: string): PipelineContext {
29
- return {
30
- input,
31
- stageOutputs: new Map(),
32
- stageOutputSizes: new Map(),
33
- stageToolResults: new Map(),
34
- repoPath,
35
- };
36
- }
37
-
38
- export function addStageOutput(
39
- context: PipelineContext,
40
- stageName: string,
41
- output: unknown
42
- ): PipelineContext {
43
- context.stageOutputs.set(stageName, output);
44
- context.stageOutputSizes.set(stageName, JSON.stringify(output).length);
45
- return context;
46
- }
47
-
48
- export function addStageToolResults(
49
- context: PipelineContext,
50
- stageName: string,
51
- toolCalls: ToolCall[]
52
- ): PipelineContext {
53
- context.stageToolResults.set(stageName, toolCalls);
54
- return context;
55
- }
56
-
57
- export function setGroupFeedback(
58
- context: PipelineContext,
59
- feedback: GroupFeedback
60
- ): void {
61
- context.groupFeedback = feedback;
62
- }
63
-
64
- export function clearGroupFeedback(context: PipelineContext): void {
65
- context.groupFeedback = undefined;
66
- }
67
-
68
- export function buildContextKeys(
69
- agentContext: AgentContext,
70
- previousOutputSizes: Map<string, number>,
71
- ): Record<string, number> {
72
- const keys: Record<string, number> = {};
73
-
74
- if (agentContext.additional_context !== undefined) {
75
- keys['input'] = agentContext.additional_context.length;
76
- }
77
-
78
- if (agentContext.previous_outputs && Object.keys(agentContext.previous_outputs).length > 0) {
79
- let total = 0;
80
- for (const stageName of Object.keys(agentContext.previous_outputs)) {
81
- total += previousOutputSizes.get(stageName) ?? 0;
82
- }
83
- keys['previous_stage_output'] = total;
84
- }
85
-
86
- if (agentContext.group_feedback !== undefined) {
87
- keys['group_feedback'] = JSON.stringify(agentContext.group_feedback).length;
88
- }
89
-
90
- if (agentContext.startup_context) {
91
- for (const [key, value] of Object.entries(agentContext.startup_context)) {
92
- keys[key] = value.length;
93
- }
94
- }
95
-
96
- if (agentContext.context_packs?.length) {
97
- for (const pack of agentContext.context_packs) {
98
- keys[pack.name] = pack.sections.reduce((sum, s) => sum + s.content.length, 0);
99
- }
100
- }
101
-
102
- if (agentContext.previous_tool_results && Object.keys(agentContext.previous_tool_results).length > 0) {
103
- let total = 0;
104
- for (const toolCalls of Object.values(agentContext.previous_tool_results)) {
105
- total += JSON.stringify(toolCalls).length;
106
- }
107
- keys['previous_stage_tool_results'] = total;
108
- }
109
-
110
- return keys;
111
- }
112
-
113
- export function buildContextContent(
114
- agentContext: AgentContext,
115
- ): Record<string, unknown> {
116
- const content: Record<string, unknown> = {};
117
-
118
- if (agentContext.additional_context !== undefined) {
119
- content['input'] = agentContext.additional_context;
120
- }
121
-
122
- if (agentContext.previous_outputs && Object.keys(agentContext.previous_outputs).length > 0) {
123
- content['previous_stage_output'] = agentContext.previous_outputs;
124
- }
125
-
126
- if (agentContext.group_feedback !== undefined) {
127
- content['group_feedback'] = agentContext.group_feedback;
128
- }
129
-
130
- if (agentContext.startup_context) {
131
- for (const [key, value] of Object.entries(agentContext.startup_context)) {
132
- content[key] = value;
133
- }
134
- }
135
-
136
- if (agentContext.context_packs?.length) {
137
- for (const pack of agentContext.context_packs) {
138
- content[pack.name] = pack;
139
- }
140
- }
141
-
142
- if (agentContext.previous_tool_results && Object.keys(agentContext.previous_tool_results).length > 0) {
143
- content['previous_stage_tool_results'] = agentContext.previous_tool_results;
144
- }
145
-
146
- return content;
147
- }
148
-
149
- export function getContextForStage(
150
- context: PipelineContext,
151
- stage: StageDefinition,
152
- previousStageName?: string
153
- ): AgentContext {
154
- const agentContext: AgentContext = {};
155
- const includes = stage.context?.include ?? ['input'];
156
-
157
- for (const include of includes) {
158
- switch (include) {
159
- case 'input':
160
- agentContext.additional_context = typeof context.input === 'string'
161
- ? context.input
162
- : yaml.dump(context.input, { lineWidth: 120 });
163
- break;
164
-
165
- case 'previous_stage_output':
166
- if (previousStageName) {
167
- const output = context.stageOutputs.get(previousStageName);
168
- if (output !== undefined) {
169
- agentContext.previous_outputs = {
170
- ...agentContext.previous_outputs,
171
- [previousStageName]: output,
172
- };
173
- }
174
- }
175
- break;
176
-
177
- case 'all_stage_outputs':
178
- agentContext.previous_outputs = {
179
- ...agentContext.previous_outputs,
180
- ...Object.fromEntries(context.stageOutputs),
181
- };
182
- break;
183
-
184
- case 'stage_name':
185
- agentContext.stage_name = stage.name;
186
- break;
187
-
188
- case 'group_feedback':
189
- if (context.groupFeedback) {
190
- agentContext.group_feedback = {
191
- iteration: context.groupFeedback.iteration,
192
- max_iterations: context.groupFeedback.max_iterations,
193
- rejection_reason: context.groupFeedback.rejection_reason,
194
- rejection_details: context.groupFeedback.rejection_details,
195
- };
196
- }
197
- break;
198
-
199
- case 'previous_stage_tool_results':
200
- if (previousStageName) {
201
- const toolResults = context.stageToolResults.get(previousStageName);
202
- if (toolResults) {
203
- agentContext.previous_tool_results = {
204
- ...agentContext.previous_tool_results,
205
- [previousStageName]: toolResults,
206
- };
207
- }
208
- }
209
- break;
210
-
211
- case 'all_stage_tool_results':
212
- if (context.stageToolResults.size > 0) {
213
- agentContext.previous_tool_results = {
214
- ...agentContext.previous_tool_results,
215
- ...Object.fromEntries(context.stageToolResults),
216
- };
217
- }
218
- break;
219
-
220
- case 'repo_files':
221
- case 'repo_structure':
222
- // Mark that repo files are needed — the engine populates this
223
- agentContext.repo_files = agentContext.repo_files ?? [];
224
- break;
225
-
226
- case 'pipeline_start_context':
227
- if (context.startupContext && Object.keys(context.startupContext).length > 0) {
228
- agentContext.startup_context = context.startupContext;
229
- }
230
- break;
231
- }
232
- }
233
-
234
- return agentContext;
235
- }
@@ -1,41 +0,0 @@
1
- // Load output contracts from YAML files
2
-
3
- import { readFile } from 'node:fs/promises';
4
- import { join } from 'node:path';
5
- import * as yaml from 'js-yaml';
6
- import type { OutputContract } from '@studio-foundation/contracts';
7
-
8
- export async function loadContract(
9
- name: string,
10
- contractsDir: string
11
- ): Promise<OutputContract> {
12
- const path = join(contractsDir, `${name}.contract.yaml`);
13
-
14
- let content: string;
15
- try {
16
- content = await readFile(path, 'utf-8');
17
- } catch (err) {
18
- throw new Error(`Failed to load contract '${name}' at ${path}: ${(err as Error).message}`);
19
- }
20
-
21
- return parseContractYaml(content, path);
22
- }
23
-
24
- export function parseContractYaml(yamlContent: string, sourcePath?: string): OutputContract {
25
- const parsed = yaml.load(yamlContent) as Record<string, unknown>;
26
- const context = sourcePath ? ` (from ${sourcePath})` : '';
27
-
28
- if (!parsed || typeof parsed !== 'object') {
29
- throw new Error(`Invalid contract YAML: expected an object${context}`);
30
- }
31
-
32
- if (!parsed.name || typeof parsed.name !== 'string') {
33
- throw new Error(`Contract missing required field 'name'${context}`);
34
- }
35
-
36
- if (parsed.version === undefined) {
37
- throw new Error(`Contract missing required field 'version'${context}`);
38
- }
39
-
40
- return parsed as unknown as OutputContract;
41
- }