@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,37 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { executeStartupCommands } from './startup-executor.js';
3
-
4
- describe('executeStartupCommands', () => {
5
- it('returns stdout keyed by inject_as', async () => {
6
- const result = await executeStartupCommands([
7
- { command: 'echo hello', inject_as: 'greeting' },
8
- { command: 'echo world', inject_as: 'place' },
9
- ]);
10
- expect(result.greeting).toBe('hello');
11
- expect(result.place).toBe('world');
12
- });
13
-
14
- it('skips and warns when a command fails', async () => {
15
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
16
- const result = await executeStartupCommands([
17
- { command: 'exit 1', inject_as: 'bad' },
18
- { command: 'echo ok', inject_as: 'good' },
19
- ]);
20
- expect(result.bad).toBeUndefined();
21
- expect(result.good).toBe('ok');
22
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('[on_pipeline_start]'));
23
- warnSpy.mockRestore();
24
- });
25
-
26
- it('returns empty object when no commands given', async () => {
27
- const result = await executeStartupCommands([]);
28
- expect(result).toEqual({});
29
- });
30
-
31
- it('trims trailing whitespace from stdout', async () => {
32
- const result = await executeStartupCommands([
33
- { command: 'printf "trimmed"', inject_as: 'val' },
34
- ]);
35
- expect(result.val).toBe('trimmed');
36
- });
37
- });
@@ -1,32 +0,0 @@
1
- // Execute on_pipeline_start commands and collect their stdout
2
-
3
- import { exec } from 'node:child_process';
4
- import { promisify } from 'node:util';
5
- import type { StartupCommand } from '@studio-foundation/contracts';
6
-
7
- const execAsync = promisify(exec);
8
- const COMMAND_TIMEOUT_MS = 10_000;
9
-
10
- export async function executeStartupCommands(
11
- commands: StartupCommand[],
12
- cwd?: string
13
- ): Promise<Record<string, string>> {
14
- const result: Record<string, string> = {};
15
-
16
- for (const cmd of commands) {
17
- try {
18
- const { stdout } = await execAsync(cmd.command, {
19
- cwd,
20
- timeout: COMMAND_TIMEOUT_MS,
21
- maxBuffer: 1024 * 1024 * 10,
22
- });
23
- result[cmd.inject_as] = stdout.trim();
24
- } catch (err) {
25
- console.warn(
26
- `[on_pipeline_start] command failed: "${cmd.command}" — ${(err as Error).message}`
27
- );
28
- }
29
- }
30
-
31
- return result;
32
- }
@@ -1,42 +0,0 @@
1
- // engine/src/pipeline/types.ts
2
- // Shared local types for pipeline execution — used by engine, StageExecutor, GroupOrchestrator
3
-
4
- import { join } from 'node:path';
5
- import type { StageRun, StageStatus, ToolCall } from '@studio-foundation/contracts';
6
- import type { PostValidationResult } from './post-validator.js';
7
- import type { PipelineContext } from './context-propagation.js';
8
-
9
- export interface ProjectPaths {
10
- projectDir: string;
11
- pipelinesDir: string;
12
- agentsDir: string;
13
- contractsDir: string;
14
- }
15
-
16
- export function resolveProjectPaths(configsDir: string): ProjectPaths {
17
- return {
18
- projectDir: configsDir,
19
- pipelinesDir: join(configsDir, 'pipelines'),
20
- agentsDir: join(configsDir, 'agents'),
21
- contractsDir: join(configsDir, 'contracts'),
22
- };
23
- }
24
-
25
- export interface StageResult {
26
- stageRun: StageRun;
27
- status: StageStatus;
28
- postValidation?: PostValidationResult;
29
- lastAgentOutput?: unknown;
30
- toolCalls?: ToolCall[];
31
- tokensDelta?: number; // tokens consumed by this stage
32
- toolCallsDelta?: number; // tool calls made by this stage
33
- }
34
-
35
- export interface GroupResult {
36
- status: StageStatus;
37
- stageRuns: StageRun[];
38
- stagesExecuted: number;
39
- context: PipelineContext;
40
- totalTokensDelta: number;
41
- totalToolCallsDelta: number;
42
- }
@@ -1,61 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import { mkdir } from 'node:fs/promises';
3
- import { resolve, join } from 'node:path';
4
- import { homedir } from 'node:os';
5
-
6
- export interface RepoResolveOptions {
7
- repoPathOverride?: string;
8
- repoUrl?: string;
9
- rawProjectsDir?: string;
10
- pipelineName: string;
11
- branch?: string;
12
- }
13
-
14
- export async function cloneRepo(
15
- repoUrl: string,
16
- projectsDir: string,
17
- pipelineName: string,
18
- branch?: string
19
- ): Promise<string> {
20
- await mkdir(projectsDir, { recursive: true });
21
-
22
- const timestamp = new Date().toISOString()
23
- .replace(/:/g, 'h')
24
- .replace(/\..+$/, '')
25
- .replace('T', 'T');
26
- const dirName = `${pipelineName}-${timestamp}`;
27
- const clonePath = join(projectsDir, dirName);
28
-
29
- const branchArg = branch ? `--branch ${branch}` : '';
30
- const cmd = `git clone --depth 1 ${branchArg} ${repoUrl} ${clonePath}`;
31
-
32
- try {
33
- execSync(cmd, { stdio: 'pipe' });
34
- } catch (error) {
35
- const msg = error instanceof Error ? error.message : String(error);
36
- throw new Error(`Failed to clone ${repoUrl}: ${msg}`);
37
- }
38
-
39
- return clonePath;
40
- }
41
-
42
- export async function resolveRepoPath(options: RepoResolveOptions): Promise<string> {
43
- const { repoPathOverride, repoUrl, rawProjectsDir, pipelineName, branch } = options;
44
-
45
- if (repoPathOverride) {
46
- return resolve(repoPathOverride);
47
- }
48
-
49
- if (repoUrl) {
50
- const rawDir = rawProjectsDir || process.env['STUDIO_PROJECTS_DIR'];
51
- const projectsDir = rawDir?.replace(/^~/, homedir());
52
- if (!projectsDir) {
53
- throw new Error(
54
- 'STUDIO_PROJECTS_DIR is not set. Set it in config.yaml paths.projects_dir or as an environment variable.'
55
- );
56
- }
57
- return cloneRepo(repoUrl, projectsDir, pipelineName, branch);
58
- }
59
-
60
- return '.';
61
- }
@@ -1,26 +0,0 @@
1
- import type { RunSpawner, SpawnConfig, SpawnResult, PipelineRun } from '@studio-foundation/contracts';
2
- import { PipelineEngine, type EngineConfig } from '../engine.js';
3
-
4
- export class DirectEngineSpawner implements RunSpawner {
5
- constructor(private engineConfig: EngineConfig) {}
6
-
7
- async spawnAndWait(config: SpawnConfig): Promise<SpawnResult> {
8
- const child = new PipelineEngine(this.engineConfig);
9
- const result: PipelineRun = await child.run({
10
- pipeline: config.pipeline,
11
- input: config.input,
12
- parentRunId: config.parentRunId,
13
- depth: config.depth,
14
- });
15
-
16
- if (result.status === 'failed' || result.status === 'rejected' || result.status === 'cancelled') {
17
- throw new Error(`Child run ${result.id} ${result.status}`);
18
- }
19
-
20
- // Extract output from the last successful stage
21
- const lastStage = [...result.stages].reverse().find(s => s.status === 'success');
22
- const output = (lastStage as { output?: unknown } | undefined)?.output ?? null;
23
-
24
- return { run_id: result.id, status: result.status, output };
25
- }
26
- }
@@ -1,157 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { SQLiteRunStore, PgRunStore } from './run-store.js';
3
- import type { PipelineRun } from '@studio-foundation/contracts';
4
-
5
- function makeRun(overrides?: Partial<PipelineRun>): PipelineRun {
6
- return {
7
- id: 'run-1',
8
- pipeline_name: 'test-pipeline',
9
- status: 'running',
10
- started_at: '2026-01-01T00:00:00.000Z',
11
- stages: [],
12
- ...overrides,
13
- };
14
- }
15
-
16
- describe('SQLiteRunStore', () => {
17
- describe('saveLogPath / getLogPath', () => {
18
- it('returns null when no log path has been set', () => {
19
- const store = new SQLiteRunStore(':memory:');
20
- store.savePipelineRun(makeRun());
21
- expect(store.getLogPath('run-1')).toBeNull();
22
- });
23
-
24
- it('returns the log path after saveLogPath', () => {
25
- const store = new SQLiteRunStore(':memory:');
26
- store.savePipelineRun(makeRun());
27
- store.saveLogPath('run-1', '/tmp/run-1.jsonl');
28
- expect(store.getLogPath('run-1')).toBe('/tmp/run-1.jsonl');
29
- });
30
-
31
- it('preserves log_path when savePipelineRun is called again after saveLogPath', () => {
32
- // Regression for STU-150: savePipelineRun used INSERT OR REPLACE which clobbers log_path
33
- const store = new SQLiteRunStore(':memory:');
34
- store.savePipelineRun(makeRun());
35
- store.saveLogPath('run-1', '/tmp/run-1.jsonl');
36
-
37
- // Simulate engine completing the run (this triggered the bug)
38
- store.savePipelineRun(makeRun({ status: 'success', completed_at: '2026-01-01T00:01:00.000Z' }));
39
-
40
- expect(store.getLogPath('run-1')).toBe('/tmp/run-1.jsonl');
41
- });
42
-
43
- it('returns null for unknown run id', () => {
44
- const store = new SQLiteRunStore(':memory:');
45
- expect(store.getLogPath('does-not-exist')).toBeNull();
46
- });
47
- });
48
-
49
- describe('parent_run_id', () => {
50
- it('persists and retrieves parent_run_id', () => {
51
- const run: PipelineRun = {
52
- id: 'child-123',
53
- pipeline_name: 'child-pipe',
54
- status: 'success',
55
- started_at: new Date().toISOString(),
56
- stages: [],
57
- parent_run_id: 'parent-456',
58
- };
59
- const store = new SQLiteRunStore(':memory:');
60
- store.savePipelineRun(run);
61
- const retrieved = store.getPipelineRun('child-123');
62
- expect(retrieved?.parent_run_id).toBe('parent-456');
63
- });
64
- });
65
- });
66
-
67
- const TEST_PG_URL = process.env.TEST_DATABASE_URL;
68
-
69
- describe.skipIf(!TEST_PG_URL)('PgRunStore', () => {
70
- let store: PgRunStore;
71
-
72
- beforeEach(async () => {
73
- store = new PgRunStore(TEST_PG_URL!);
74
- await store.dangerouslyTruncateForTests();
75
- });
76
-
77
- afterEach(async () => {
78
- await store.close();
79
- });
80
-
81
- it('saves and retrieves a pipeline run', async () => {
82
- const run = makePgRun('pg-run-1');
83
- await store.savePipelineRun(run);
84
- const found = await store.getPipelineRun('pg-run-1');
85
- expect(found).toMatchObject({ id: 'pg-run-1', status: 'success' });
86
- });
87
-
88
- it('returns null for unknown id', async () => {
89
- const found = await store.getPipelineRun('doesnt-exist');
90
- expect(found).toBeNull();
91
- });
92
-
93
- it('updates an existing run (upsert)', async () => {
94
- const run = makePgRun('pg-run-2');
95
- await store.savePipelineRun(run);
96
- await store.savePipelineRun({ ...run, status: 'failed' });
97
- const found = await store.getPipelineRun('pg-run-2');
98
- expect(found?.status).toBe('failed');
99
- });
100
-
101
- it('lists runs with status filter', async () => {
102
- await store.savePipelineRun(makePgRun('pg-1', 'success'));
103
- await store.savePipelineRun(makePgRun('pg-2', 'failed'));
104
- const successes = await store.listPipelineRuns({ status: 'success' });
105
- expect(successes).toHaveLength(1);
106
- expect(successes[0].id).toBe('pg-1');
107
- });
108
-
109
- it('lists runs with limit', async () => {
110
- for (let i = 0; i < 5; i++) {
111
- await store.savePipelineRun(makePgRun(`pg-limit-${i}`));
112
- }
113
- const runs = await store.listPipelineRuns({ limit: 3 });
114
- expect(runs).toHaveLength(3);
115
- });
116
-
117
- it('getLatestRun returns most recent', async () => {
118
- await store.savePipelineRun(makePgRun('pg-old', 'success', '2024-01-01T00:00:00Z'));
119
- await store.savePipelineRun(makePgRun('pg-new', 'success', '2024-06-01T00:00:00Z'));
120
- const latest = await store.getLatestRun();
121
- expect(latest?.id).toBe('pg-new');
122
- });
123
-
124
- it('getLatestRun filters by pipeline name', async () => {
125
- await store.savePipelineRun(makePgRun('pg-a', 'success', undefined, 'pipeline-a'));
126
- await store.savePipelineRun(makePgRun('pg-b', 'success', undefined, 'pipeline-b'));
127
- const latest = await store.getLatestRun('pipeline-a');
128
- expect(latest?.id).toBe('pg-a');
129
- });
130
-
131
- it('saves and retrieves log path', async () => {
132
- await store.savePipelineRun(makePgRun('pg-log-1'));
133
- await store.saveLogPath('pg-log-1', '/tmp/test.jsonl');
134
- const path = await store.getLogPath('pg-log-1');
135
- expect(path).toBe('/tmp/test.jsonl');
136
- });
137
-
138
- it('returns null log path for unknown run', async () => {
139
- const path = await store.getLogPath('no-such-run');
140
- expect(path).toBeNull();
141
- });
142
- });
143
-
144
- function makePgRun(
145
- id: string,
146
- status: string = 'success',
147
- startedAt: string = new Date().toISOString(),
148
- pipelineName: string = 'test-pipeline'
149
- ) {
150
- return {
151
- id,
152
- pipeline_name: pipelineName,
153
- status,
154
- started_at: startedAt,
155
- stages: [],
156
- } as PipelineRun;
157
- }