@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
@@ -0,0 +1,30 @@
1
+ // Load output contracts from YAML files
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import * as yaml from 'js-yaml';
5
+ export async function loadContract(name, contractsDir) {
6
+ const path = join(contractsDir, `${name}.contract.yaml`);
7
+ let content;
8
+ try {
9
+ content = await readFile(path, 'utf-8');
10
+ }
11
+ catch (err) {
12
+ throw new Error(`Failed to load contract '${name}' at ${path}: ${err.message}`);
13
+ }
14
+ return parseContractYaml(content, path);
15
+ }
16
+ export function parseContractYaml(yamlContent, sourcePath) {
17
+ const parsed = yaml.load(yamlContent);
18
+ const context = sourcePath ? ` (from ${sourcePath})` : '';
19
+ if (!parsed || typeof parsed !== 'object') {
20
+ throw new Error(`Invalid contract YAML: expected an object${context}`);
21
+ }
22
+ if (!parsed.name || typeof parsed.name !== 'string') {
23
+ throw new Error(`Contract missing required field 'name'${context}`);
24
+ }
25
+ if (parsed.version === undefined) {
26
+ throw new Error(`Contract missing required field 'version'${context}`);
27
+ }
28
+ return parsed;
29
+ }
30
+ //# sourceMappingURL=contract-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-loader.js","sourceRoot":"","sources":["../../src/pipeline/contract-loader.ts"],"names":[],"mappings":"AAAA,wCAAwC;AAExC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,IAAI,MAAM,SAAS,CAAC;AAGhC,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,YAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,IAAI,gBAAgB,CAAC,CAAC;IAEzD,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,QAAQ,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,OAAO,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,WAAmB,EAAE,UAAmB;IACxE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAA4B,CAAC;IACjE,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAE1D,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,4CAA4C,OAAO,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,yCAAyC,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4CAA4C,OAAO,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,MAAmC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { StageGroup } from '@studio-foundation/contracts';
2
+ import { type PipelineContext } from './context-propagation.js';
3
+ import type { EngineEvents, PipelineEventEmitter } from '../events.js';
4
+ import type { ToolRegistry, AnonymizationMiddleware } from '@studio-foundation/runner';
5
+ import type { StageExecutor } from './stage-executor.js';
6
+ import type { GroupResult, ProjectPaths } from './types.js';
7
+ export interface GroupOrchestratorConfig {
8
+ events?: EngineEvents;
9
+ emitter: PipelineEventEmitter;
10
+ stageExecutor: StageExecutor;
11
+ }
12
+ export declare class GroupOrchestrator {
13
+ private config;
14
+ constructor(config: GroupOrchestratorConfig);
15
+ run(group: StageGroup, context: PipelineContext, stageOffset: number, totalStages: number, userInput: string | Record<string, unknown>, paths: ProjectPaths, toolRegistry: ToolRegistry | undefined, runMiddleware?: AnonymizationMiddleware | null, runId?: string, signal?: AbortSignal, skipSet?: Set<string>, originalRunId?: string): Promise<GroupResult>;
16
+ private runParallel;
17
+ private runSequential;
18
+ }
19
+ //# sourceMappingURL=group-orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-orchestrator.d.ts","sourceRoot":"","sources":["../../src/pipeline/group-orchestrator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAyB,MAAM,8BAA8B,CAAC;AACtF,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAe,MAAM,YAAY,CAAC;AAEzE,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,qBAAa,iBAAiB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,uBAAuB;IAE7C,GAAG,CACP,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,eAAe,EACxB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3C,KAAK,EAAE,YAAY,EACnB,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,aAAa,CAAC,EAAE,uBAAuB,GAAG,IAAI,EAC9C,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,WAAW,CAAC;YAOT,WAAW;YAoJX,aAAa;CAiS5B"}
@@ -0,0 +1,376 @@
1
+ // GroupOrchestrator — extracted from PipelineEngine.runGroup/runGroupParallel/runGroupSequential()
2
+ // Handles group-level orchestration: parallel and sequential iteration with feedback loops.
3
+ import { randomUUID } from 'node:crypto';
4
+ import { addStageOutput, addStageToolResults, setGroupFeedback, } from './context-propagation.js';
5
+ export class GroupOrchestrator {
6
+ config;
7
+ constructor(config) {
8
+ this.config = config;
9
+ }
10
+ async run(group, context, stageOffset, totalStages, userInput, paths, toolRegistry, runMiddleware, runId, signal, skipSet, originalRunId) {
11
+ if (group.mode === 'parallel') {
12
+ return this.runParallel(group, context, stageOffset, totalStages, userInput, paths, toolRegistry, runMiddleware, runId, signal);
13
+ }
14
+ return this.runSequential(group, context, stageOffset, totalStages, userInput, paths, toolRegistry, runMiddleware, runId, signal, skipSet, originalRunId);
15
+ }
16
+ async runParallel(group, context, stageOffset, totalStages, userInput, paths, toolRegistry, runMiddleware, runId, signal) {
17
+ let totalTokensDelta = 0;
18
+ let totalToolCallsDelta = 0;
19
+ this.config.events?.onGroupStart?.({
20
+ group_name: group.group,
21
+ max_iterations: group.max_iterations,
22
+ parallel: true,
23
+ });
24
+ this.config.emitter.emit({
25
+ type: 'group_start',
26
+ groupName: group.group,
27
+ maxIterations: group.max_iterations,
28
+ });
29
+ // Parallel groups run exactly one iteration
30
+ this.config.events?.onGroupIteration?.({
31
+ group_name: group.group,
32
+ iteration: 1,
33
+ max_iterations: group.max_iterations,
34
+ });
35
+ this.config.emitter.emit({
36
+ type: 'group_iteration',
37
+ groupName: group.group,
38
+ iteration: 1,
39
+ maxIterations: group.max_iterations,
40
+ });
41
+ if (signal?.aborted) {
42
+ this.config.events?.onGroupComplete?.({ group_name: group.group, iterations: 1, status: 'cancelled' });
43
+ this.config.emitter.emit({ type: 'group_complete', groupName: group.group, iterations: 1, status: 'cancelled' });
44
+ return { status: 'cancelled', stageRuns: [], stagesExecuted: group.stages.length, context, totalTokensDelta, totalToolCallsDelta };
45
+ }
46
+ // All parallel stages read the same pre-group context snapshot.
47
+ // previousStageName = the last stage before this group.
48
+ let previousStageName;
49
+ for (const [name] of context.stageOutputs) {
50
+ previousStageName = name;
51
+ }
52
+ // fail-fast: create a shared AbortController to cancel siblings on first failure
53
+ const groupAbort = (group.on_failure ?? 'fail-fast') === 'fail-fast'
54
+ ? new AbortController()
55
+ : null;
56
+ if (groupAbort && signal) {
57
+ signal.addEventListener('abort', () => groupAbort.abort(), { once: true });
58
+ }
59
+ const stageSignal = groupAbort?.signal ?? signal;
60
+ // Launch all stages concurrently
61
+ const settled = await Promise.allSettled(group.stages.map(async (stage, i) => {
62
+ const result = await this.config.stageExecutor.execute(stage, context, previousStageName, userInput, stageOffset + i, totalStages, paths, toolRegistry, runMiddleware, runId, stageSignal);
63
+ // fail-fast: abort remaining stages on first non-success
64
+ if (groupAbort && result.status !== 'success') {
65
+ groupAbort.abort();
66
+ }
67
+ return { stageName: stage.name, result };
68
+ }));
69
+ // Build result map keyed by stage name
70
+ const resultMap = new Map();
71
+ for (const s of settled) {
72
+ if (s.status === 'fulfilled') {
73
+ resultMap.set(s.value.stageName, s.value.result);
74
+ }
75
+ }
76
+ // Collect stage runs in definition order (deterministic output ordering)
77
+ const allStageRuns = [];
78
+ for (const stage of group.stages) {
79
+ const result = resultMap.get(stage.name);
80
+ if (result)
81
+ allStageRuns.push(result.stageRun);
82
+ }
83
+ // After building resultMap, accumulate token/tool call totals
84
+ for (const [, result] of resultMap) {
85
+ totalTokensDelta += result.tokensDelta ?? 0;
86
+ totalToolCallsDelta += result.toolCallsDelta ?? 0;
87
+ }
88
+ // Derive group status: cancelled > failed > success
89
+ // rejected treated as failed in parallel mode (no feedback loop)
90
+ let groupStatus = 'success';
91
+ for (const stage of group.stages) {
92
+ const result = resultMap.get(stage.name);
93
+ if (!result) {
94
+ groupStatus = 'failed';
95
+ continue;
96
+ }
97
+ if (result.status === 'cancelled' && groupStatus === 'success')
98
+ groupStatus = 'cancelled';
99
+ if (result.status === 'failed' || result.status === 'rejected')
100
+ groupStatus = 'failed';
101
+ }
102
+ // If every stage was skipped, the group is skipped (not success)
103
+ const allSkipped = group.stages.every((s) => resultMap.get(s.name)?.status === 'skipped');
104
+ if (allSkipped)
105
+ groupStatus = 'skipped';
106
+ // Merge successful outputs into context in definition order (regardless of group status)
107
+ // This preserves observability for collect-all partial failures
108
+ for (const stage of group.stages) {
109
+ const result = resultMap.get(stage.name);
110
+ if (!result || result.status !== 'success')
111
+ continue;
112
+ if (result.lastAgentOutput !== undefined) {
113
+ addStageOutput(context, stage.name, result.lastAgentOutput);
114
+ }
115
+ if (result.toolCalls?.length) {
116
+ addStageToolResults(context, stage.name, result.toolCalls);
117
+ }
118
+ }
119
+ this.config.events?.onGroupComplete?.({ group_name: group.group, iterations: 1, status: groupStatus });
120
+ this.config.emitter.emit({ type: 'group_complete', groupName: group.group, iterations: 1, status: groupStatus });
121
+ return {
122
+ status: groupStatus,
123
+ stageRuns: allStageRuns,
124
+ stagesExecuted: group.stages.length,
125
+ context,
126
+ totalTokensDelta,
127
+ totalToolCallsDelta,
128
+ };
129
+ }
130
+ async runSequential(group, context, stageOffset, totalStages, userInput, paths, toolRegistry, runMiddleware, runId, signal, skipSet, originalRunId) {
131
+ const allStageRuns = [];
132
+ let iteration = 0;
133
+ let totalTokensDelta = 0;
134
+ let totalToolCallsDelta = 0;
135
+ this.config.events?.onGroupStart?.({
136
+ group_name: group.group,
137
+ max_iterations: group.max_iterations,
138
+ });
139
+ this.config.emitter.emit({
140
+ type: 'group_start',
141
+ groupName: group.group,
142
+ maxIterations: group.max_iterations,
143
+ });
144
+ while (iteration < group.max_iterations) {
145
+ // Check cancellation before group iteration
146
+ if (signal?.aborted) {
147
+ this.config.events?.onGroupComplete?.({
148
+ group_name: group.group,
149
+ iterations: iteration,
150
+ status: 'cancelled',
151
+ });
152
+ this.config.emitter.emit({
153
+ type: 'group_complete',
154
+ groupName: group.group,
155
+ iterations: iteration,
156
+ status: 'cancelled',
157
+ });
158
+ return {
159
+ status: 'cancelled',
160
+ stageRuns: allStageRuns,
161
+ stagesExecuted: group.stages.length,
162
+ context,
163
+ totalTokensDelta,
164
+ totalToolCallsDelta,
165
+ };
166
+ }
167
+ iteration++;
168
+ this.config.events?.onGroupIteration?.({
169
+ group_name: group.group,
170
+ iteration,
171
+ max_iterations: group.max_iterations,
172
+ });
173
+ this.config.emitter.emit({
174
+ type: 'group_iteration',
175
+ groupName: group.group,
176
+ iteration,
177
+ maxIterations: group.max_iterations,
178
+ });
179
+ let groupSucceeded = true;
180
+ let anyStageExecuted = false;
181
+ let previousStageName;
182
+ // Find the last stage name before this group in the pipeline context
183
+ for (const [name] of context.stageOutputs) {
184
+ previousStageName = name;
185
+ }
186
+ for (let i = 0; i < group.stages.length; i++) {
187
+ if (signal?.aborted)
188
+ break;
189
+ const stage = group.stages[i];
190
+ const stageNumber = stageOffset + i;
191
+ // On iteration 1 only, skip stages before resumeFromStage
192
+ if (iteration === 1 && skipSet?.has(stage.name)) {
193
+ const skippedReason = originalRunId
194
+ ? `resumed from run ${originalRunId}`
195
+ : 'resumed from prior run';
196
+ const skippedRun = {
197
+ id: randomUUID(),
198
+ stage_name: stage.name,
199
+ status: 'skipped',
200
+ started_at: new Date().toISOString(),
201
+ completed_at: new Date().toISOString(),
202
+ tasks: [],
203
+ skipped_reason: skippedReason,
204
+ };
205
+ this.config.events?.onStageComplete?.({
206
+ stage_name: stage.name,
207
+ stage_index: stageNumber,
208
+ total_stages: totalStages,
209
+ status: 'skipped',
210
+ attempts: 0,
211
+ duration_ms: 0,
212
+ skipped_reason: skippedReason,
213
+ });
214
+ allStageRuns.push(skippedRun);
215
+ previousStageName = stage.name;
216
+ continue;
217
+ }
218
+ const result = await this.config.stageExecutor.execute(stage, context, previousStageName, userInput, stageNumber, totalStages, paths, toolRegistry, runMiddleware, runId, signal);
219
+ allStageRuns.push(result.stageRun);
220
+ totalTokensDelta += result.tokensDelta ?? 0;
221
+ totalToolCallsDelta += result.toolCallsDelta ?? 0;
222
+ if (result.status !== 'skipped')
223
+ anyStageExecuted = true;
224
+ // Cancelled → stop group
225
+ if (result.status === 'cancelled') {
226
+ this.config.events?.onGroupComplete?.({
227
+ group_name: group.group,
228
+ iterations: iteration,
229
+ status: 'cancelled',
230
+ });
231
+ this.config.emitter.emit({
232
+ type: 'group_complete',
233
+ groupName: group.group,
234
+ iterations: iteration,
235
+ status: 'cancelled',
236
+ });
237
+ return {
238
+ status: 'cancelled',
239
+ stageRuns: allStageRuns,
240
+ stagesExecuted: group.stages.length,
241
+ context,
242
+ totalTokensDelta,
243
+ totalToolCallsDelta,
244
+ };
245
+ }
246
+ // Technical failure → stop everything
247
+ if (result.status === 'failed') {
248
+ this.config.events?.onGroupComplete?.({
249
+ group_name: group.group,
250
+ iterations: iteration,
251
+ status: 'failed',
252
+ });
253
+ this.config.emitter.emit({
254
+ type: 'group_complete',
255
+ groupName: group.group,
256
+ iterations: iteration,
257
+ status: 'failed',
258
+ });
259
+ return {
260
+ status: 'failed',
261
+ stageRuns: allStageRuns,
262
+ stagesExecuted: group.stages.length,
263
+ context,
264
+ totalTokensDelta,
265
+ totalToolCallsDelta,
266
+ };
267
+ }
268
+ // Propagate output to context (will be cleared if we loop)
269
+ if (result.lastAgentOutput !== undefined) {
270
+ addStageOutput(context, stage.name, result.lastAgentOutput);
271
+ }
272
+ if (result.toolCalls && result.toolCalls.length > 0) {
273
+ addStageToolResults(context, stage.name, result.toolCalls);
274
+ }
275
+ previousStageName = stage.name;
276
+ // Last stage rejected → feedback loop
277
+ const isLastStage = i === group.stages.length - 1;
278
+ if (result.status === 'rejected' && isLastStage) {
279
+ groupSucceeded = false;
280
+ if (iteration < group.max_iterations) {
281
+ // Clear group stage outputs and tool results for next iteration
282
+ for (const gs of group.stages) {
283
+ context.stageOutputs.delete(gs.name);
284
+ context.stageToolResults.delete(gs.name);
285
+ }
286
+ // Set feedback for next iteration
287
+ setGroupFeedback(context, {
288
+ iteration,
289
+ max_iterations: group.max_iterations,
290
+ rejection_reason: result.postValidation?.rejection_reason || 'Rejected',
291
+ rejection_details: result.postValidation?.rejection_details,
292
+ });
293
+ this.config.events?.onGroupFeedback?.({
294
+ group_name: group.group,
295
+ iteration,
296
+ rejection_reason: result.postValidation?.rejection_reason || 'Rejected',
297
+ rejection_details: result.postValidation?.rejection_details || [],
298
+ });
299
+ this.config.emitter.emit({
300
+ type: 'group_feedback',
301
+ groupName: group.group,
302
+ iteration,
303
+ rejectionReason: result.postValidation?.rejection_reason || 'Rejected',
304
+ });
305
+ }
306
+ break; // Exit inner stage loop, retry group
307
+ }
308
+ // Non-gate stage rejected → stop
309
+ if (result.status === 'rejected') {
310
+ this.config.events?.onGroupComplete?.({
311
+ group_name: group.group,
312
+ iterations: iteration,
313
+ status: 'rejected',
314
+ });
315
+ this.config.emitter.emit({
316
+ type: 'group_complete',
317
+ groupName: group.group,
318
+ iterations: iteration,
319
+ status: 'rejected',
320
+ });
321
+ return {
322
+ status: 'rejected',
323
+ stageRuns: allStageRuns,
324
+ stagesExecuted: group.stages.length,
325
+ context,
326
+ totalTokensDelta,
327
+ totalToolCallsDelta,
328
+ };
329
+ }
330
+ }
331
+ if (groupSucceeded) {
332
+ const groupStatus = anyStageExecuted ? 'success' : 'skipped';
333
+ this.config.events?.onGroupComplete?.({
334
+ group_name: group.group,
335
+ iterations: iteration,
336
+ status: groupStatus,
337
+ });
338
+ this.config.emitter.emit({
339
+ type: 'group_complete',
340
+ groupName: group.group,
341
+ iterations: iteration,
342
+ status: groupStatus,
343
+ });
344
+ return {
345
+ status: groupStatus,
346
+ stageRuns: allStageRuns,
347
+ stagesExecuted: group.stages.length,
348
+ context,
349
+ totalTokensDelta,
350
+ totalToolCallsDelta,
351
+ };
352
+ }
353
+ }
354
+ // Max iterations exhausted
355
+ this.config.events?.onGroupComplete?.({
356
+ group_name: group.group,
357
+ iterations: iteration,
358
+ status: 'rejected',
359
+ });
360
+ this.config.emitter.emit({
361
+ type: 'group_complete',
362
+ groupName: group.group,
363
+ iterations: iteration,
364
+ status: 'rejected',
365
+ });
366
+ return {
367
+ status: 'rejected',
368
+ stageRuns: allStageRuns,
369
+ stagesExecuted: group.stages.length,
370
+ context,
371
+ totalTokensDelta,
372
+ totalToolCallsDelta,
373
+ };
374
+ }
375
+ }
376
+ //# sourceMappingURL=group-orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-orchestrator.js","sourceRoot":"","sources":["../../src/pipeline/group-orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { StageHookDef, ToolHookDef } from '@studio-foundation/contracts';
2
+ export interface HookResult {
3
+ success: boolean;
4
+ stdout: string;
5
+ stderr: string;
6
+ }
7
+ /**
8
+ * Renders {{tool.argName}} and {{output.field}} placeholders.
9
+ * Arrays in outputContext are space-joined for CLI argument passing.
10
+ * Unknown keys → empty string.
11
+ *
12
+ * Note: values are substituted verbatim into the command string.
13
+ * Hook commands are authored by pipeline owners (trusted), not end users.
14
+ * Do not use with untrusted input sources.
15
+ */
16
+ export declare function renderHookCommand(command: string, toolArgs: Record<string, unknown>, outputContext?: Record<string, unknown>): string;
17
+ /**
18
+ * Run a stage-level hook command (on_stage_start, on_stage_complete).
19
+ * outputContext provides {{output.<field>}} substitution values.
20
+ * on_stage_start hooks omit outputContext (no output available before the stage runs).
21
+ */
22
+ export declare function runStageHook(hook: StageHookDef, cwd: string, outputContext?: Record<string, unknown>): Promise<HookResult>;
23
+ /**
24
+ * Run a tool-level hook command (pre_tool_use, post_tool_use).
25
+ * The command may reference tool arguments via {{tool.argName}}.
26
+ */
27
+ export declare function runToolHook(hook: ToolHookDef, toolArgs: Record<string, unknown>, cwd: string): Promise<HookResult>;
28
+ //# sourceMappingURL=hook-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-executor.d.ts","sourceRoot":"","sources":["../../src/pipeline/hook-executor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAK9E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAC1C,MAAM,CAYR;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,MAAM,EACX,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAC1C,OAAO,CAAC,UAAU,CAAC,CAGrB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,CAAC,CAGrB"}
@@ -0,0 +1,63 @@
1
+ // Hook executor — runs shell commands at lifecycle points within a stage
2
+ // Mirrors startup-executor.ts but with on_failure semantics and tool arg templates
3
+ import { exec } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ const execAsync = promisify(exec);
6
+ const HOOK_TIMEOUT_MS = 30_000;
7
+ /**
8
+ * Renders {{tool.argName}} and {{output.field}} placeholders.
9
+ * Arrays in outputContext are space-joined for CLI argument passing.
10
+ * Unknown keys → empty string.
11
+ *
12
+ * Note: values are substituted verbatim into the command string.
13
+ * Hook commands are authored by pipeline owners (trusted), not end users.
14
+ * Do not use with untrusted input sources.
15
+ */
16
+ export function renderHookCommand(command, toolArgs, outputContext = {}) {
17
+ return command
18
+ .replace(/\{\{tool\.(\w+)\}\}/g, (_, key) => (toolArgs[key] !== undefined ? String(toolArgs[key]) : ''))
19
+ .replace(/\{\{output\.(\w+)\}\}/g, (_, key) => {
20
+ const val = outputContext[key];
21
+ if (val === undefined)
22
+ return '';
23
+ if (Array.isArray(val))
24
+ return val.join(' ');
25
+ return String(val);
26
+ });
27
+ }
28
+ /**
29
+ * Run a stage-level hook command (on_stage_start, on_stage_complete).
30
+ * outputContext provides {{output.<field>}} substitution values.
31
+ * on_stage_start hooks omit outputContext (no output available before the stage runs).
32
+ */
33
+ export async function runStageHook(hook, cwd, outputContext = {}) {
34
+ const command = renderHookCommand(hook.command, {}, outputContext);
35
+ return execHook(command, cwd);
36
+ }
37
+ /**
38
+ * Run a tool-level hook command (pre_tool_use, post_tool_use).
39
+ * The command may reference tool arguments via {{tool.argName}}.
40
+ */
41
+ export async function runToolHook(hook, toolArgs, cwd) {
42
+ const command = renderHookCommand(hook.command, toolArgs);
43
+ return execHook(command, cwd);
44
+ }
45
+ async function execHook(command, cwd) {
46
+ try {
47
+ const { stdout, stderr } = await execAsync(command, {
48
+ cwd,
49
+ timeout: HOOK_TIMEOUT_MS,
50
+ maxBuffer: 1024 * 1024 * 10,
51
+ });
52
+ return { success: true, stdout: stdout.trim(), stderr: stderr.trim() };
53
+ }
54
+ catch (err) {
55
+ const e = err;
56
+ return {
57
+ success: false,
58
+ stdout: e.stdout?.trim() ?? '',
59
+ stderr: e.stderr?.trim() ?? String(err),
60
+ };
61
+ }
62
+ }
63
+ //# sourceMappingURL=hook-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-executor.js","sourceRoot":"","sources":["../../src/pipeline/hook-executor.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,mFAAmF;AAEnF,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAClC,MAAM,eAAe,GAAG,MAAM,CAAC;AAQ/B;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,QAAiC,EACjC,gBAAyC,EAAE;IAE3C,OAAO,OAAO;SACX,OAAO,CACN,sBAAsB,EACtB,CAAC,CAAC,EAAE,GAAW,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAC/E;SACA,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAAC,EAAE,GAAW,EAAE,EAAE;QACpD,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAkB,EAClB,GAAW,EACX,gBAAyC,EAAE;IAE3C,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,aAAa,CAAC,CAAC;IACnE,OAAO,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAiB,EACjB,QAAiC,EACjC,GAAW;IAEX,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC1D,OAAO,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAChC,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,OAAe,EAAE,GAAW;IAClD,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE;YAClD,GAAG;YACH,OAAO,EAAE,eAAe;YACxB,SAAS,EAAE,IAAI,GAAG,IAAI,GAAG,EAAE;SAC5B,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACzE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAA2C,CAAC;QACtD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE;YAC9B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC;SACxC,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hook-executor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-executor.test.d.ts","sourceRoot":"","sources":["../../src/pipeline/hook-executor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { renderHookCommand, runStageHook, runToolHook } from './hook-executor.js';
3
+ describe('renderHookCommand', () => {
4
+ it('substitutes {{tool.argName}} with tool argument value', () => {
5
+ const result = renderHookCommand('npx prettier --write {{tool.path}}', { path: '/tmp/foo.ts' });
6
+ expect(result).toBe('npx prettier --write /tmp/foo.ts');
7
+ });
8
+ it('returns empty string for missing tool argument', () => {
9
+ const result = renderHookCommand('do something {{tool.missing}}', {});
10
+ expect(result).toBe('do something ');
11
+ });
12
+ it('substitutes multiple occurrences of the same placeholder', () => {
13
+ const result = renderHookCommand('cp {{tool.src}} {{tool.dst}}', { src: 'a.ts', dst: 'b.ts' });
14
+ expect(result).toBe('cp a.ts b.ts');
15
+ });
16
+ it('leaves non-tool placeholders unchanged', () => {
17
+ const result = renderHookCommand('echo {{other}}', { other: 'x' });
18
+ // {{other}} is not a {{tool.*}} pattern — left as-is
19
+ expect(result).toBe('echo {{other}}');
20
+ });
21
+ it('substitutes {{output.field}} with value from outputContext', () => {
22
+ const result = renderHookCommand('npx eslint {{output.files_changed}}', {}, { files_changed: 'src/foo.ts' });
23
+ expect(result).toBe('npx eslint src/foo.ts');
24
+ });
25
+ it('space-joins array values from outputContext', () => {
26
+ const result = renderHookCommand('npx eslint {{output.files_changed}}', {}, { files_changed: ['src/foo.ts', 'src/bar.ts'] });
27
+ expect(result).toBe('npx eslint src/foo.ts src/bar.ts');
28
+ });
29
+ it('returns empty string for missing output field', () => {
30
+ const result = renderHookCommand('npx eslint {{output.missing}}', {}, {});
31
+ expect(result).toBe('npx eslint ');
32
+ });
33
+ it('handles mixed {{tool.*}} and {{output.*}} in same command', () => {
34
+ const result = renderHookCommand('run {{tool.script}} on {{output.files_changed}}', { script: 'check.sh' }, { files_changed: 'src/foo.ts' });
35
+ expect(result).toBe('run check.sh on src/foo.ts');
36
+ });
37
+ it('leaves {{tool.*}} unchanged when outputContext not provided', () => {
38
+ const result = renderHookCommand('echo {{tool.path}}', { path: 'x.ts' });
39
+ expect(result).toBe('echo x.ts');
40
+ });
41
+ });
42
+ describe('runStageHook', () => {
43
+ it('returns success with stdout when command exits 0', async () => {
44
+ const result = await runStageHook({ command: 'echo hello', on_failure: 'warn' }, '/tmp');
45
+ expect(result.success).toBe(true);
46
+ expect(result.stdout).toBe('hello');
47
+ expect(result.stderr).toBe('');
48
+ });
49
+ it('returns failure with stderr when command exits non-zero', async () => {
50
+ const result = await runStageHook({ command: 'sh -c "echo boom >&2; exit 1"', on_failure: 'warn' }, '/tmp');
51
+ expect(result.success).toBe(false);
52
+ expect(result.stderr).toContain('boom');
53
+ });
54
+ it('resolves {{output.files_changed}} from outputContext in command', async () => {
55
+ const result = await runStageHook({ command: 'echo {{output.files_changed}}', on_failure: 'warn' }, '/tmp', { files_changed: ['src/foo.ts', 'src/bar.ts'] });
56
+ expect(result.success).toBe(true);
57
+ expect(result.stdout).toBe('src/foo.ts src/bar.ts');
58
+ });
59
+ });
60
+ describe('runToolHook', () => {
61
+ it('renders template and executes command', async () => {
62
+ const result = await runToolHook({ matcher: 'repo_manager-write_file', command: 'echo {{tool.path}}', on_failure: 'warn' }, { path: '/tmp/test.ts' }, '/tmp');
63
+ expect(result.success).toBe(true);
64
+ expect(result.stdout).toBe('/tmp/test.ts');
65
+ });
66
+ it('returns failure when rendered command exits non-zero', async () => {
67
+ const result = await runToolHook({ matcher: 'any-tool', command: 'exit 1', on_failure: 'warn' }, {}, '/tmp');
68
+ expect(result.success).toBe(false);
69
+ });
70
+ });
71
+ //# sourceMappingURL=hook-executor.test.js.map