@auto-engineer/pipeline 0.0.1

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 (270) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/LICENSE +10 -0
  3. package/claude.md +160 -0
  4. package/dist/src/builder/define.d.ts +90 -0
  5. package/dist/src/builder/define.d.ts.map +1 -0
  6. package/dist/src/builder/define.js +425 -0
  7. package/dist/src/builder/define.js.map +1 -0
  8. package/dist/src/builder/define.specs.d.ts +2 -0
  9. package/dist/src/builder/define.specs.d.ts.map +1 -0
  10. package/dist/src/builder/define.specs.js +435 -0
  11. package/dist/src/builder/define.specs.js.map +1 -0
  12. package/dist/src/config/pipeline-config.d.ts +13 -0
  13. package/dist/src/config/pipeline-config.d.ts.map +1 -0
  14. package/dist/src/config/pipeline-config.js +15 -0
  15. package/dist/src/config/pipeline-config.js.map +1 -0
  16. package/dist/src/core/descriptors.d.ts +84 -0
  17. package/dist/src/core/descriptors.d.ts.map +1 -0
  18. package/dist/src/core/descriptors.js +2 -0
  19. package/dist/src/core/descriptors.js.map +1 -0
  20. package/dist/src/core/descriptors.specs.d.ts +2 -0
  21. package/dist/src/core/descriptors.specs.d.ts.map +1 -0
  22. package/dist/src/core/descriptors.specs.js +24 -0
  23. package/dist/src/core/descriptors.specs.js.map +1 -0
  24. package/dist/src/core/types.d.ts +10 -0
  25. package/dist/src/core/types.d.ts.map +1 -0
  26. package/dist/src/core/types.js +4 -0
  27. package/dist/src/core/types.js.map +1 -0
  28. package/dist/src/core/types.specs.d.ts +2 -0
  29. package/dist/src/core/types.specs.d.ts.map +1 -0
  30. package/dist/src/core/types.specs.js +40 -0
  31. package/dist/src/core/types.specs.js.map +1 -0
  32. package/dist/src/graph/types.d.ts +17 -0
  33. package/dist/src/graph/types.d.ts.map +1 -0
  34. package/dist/src/graph/types.js +2 -0
  35. package/dist/src/graph/types.js.map +1 -0
  36. package/dist/src/graph/types.specs.d.ts +2 -0
  37. package/dist/src/graph/types.specs.d.ts.map +1 -0
  38. package/dist/src/graph/types.specs.js +148 -0
  39. package/dist/src/graph/types.specs.js.map +1 -0
  40. package/dist/src/index.d.ts +20 -0
  41. package/dist/src/index.d.ts.map +1 -0
  42. package/dist/src/index.js +12 -0
  43. package/dist/src/index.js.map +1 -0
  44. package/dist/src/logging/event-logger.d.ts +21 -0
  45. package/dist/src/logging/event-logger.d.ts.map +1 -0
  46. package/dist/src/logging/event-logger.js +31 -0
  47. package/dist/src/logging/event-logger.js.map +1 -0
  48. package/dist/src/logging/event-logger.specs.d.ts +2 -0
  49. package/dist/src/logging/event-logger.specs.d.ts.map +1 -0
  50. package/dist/src/logging/event-logger.specs.js +81 -0
  51. package/dist/src/logging/event-logger.specs.js.map +1 -0
  52. package/dist/src/plugins/handler-adapter.d.ts +5 -0
  53. package/dist/src/plugins/handler-adapter.d.ts.map +1 -0
  54. package/dist/src/plugins/handler-adapter.js +17 -0
  55. package/dist/src/plugins/handler-adapter.js.map +1 -0
  56. package/dist/src/plugins/handler-adapter.specs.d.ts +2 -0
  57. package/dist/src/plugins/handler-adapter.specs.d.ts.map +1 -0
  58. package/dist/src/plugins/handler-adapter.specs.js +129 -0
  59. package/dist/src/plugins/handler-adapter.specs.js.map +1 -0
  60. package/dist/src/plugins/plugin-loader.d.ts +25 -0
  61. package/dist/src/plugins/plugin-loader.d.ts.map +1 -0
  62. package/dist/src/plugins/plugin-loader.js +150 -0
  63. package/dist/src/plugins/plugin-loader.js.map +1 -0
  64. package/dist/src/plugins/plugin-loader.specs.d.ts +2 -0
  65. package/dist/src/plugins/plugin-loader.specs.d.ts.map +1 -0
  66. package/dist/src/plugins/plugin-loader.specs.js +246 -0
  67. package/dist/src/plugins/plugin-loader.specs.js.map +1 -0
  68. package/dist/src/runtime/await-tracker.d.ts +10 -0
  69. package/dist/src/runtime/await-tracker.d.ts.map +1 -0
  70. package/dist/src/runtime/await-tracker.js +42 -0
  71. package/dist/src/runtime/await-tracker.js.map +1 -0
  72. package/dist/src/runtime/await-tracker.specs.d.ts +2 -0
  73. package/dist/src/runtime/await-tracker.specs.d.ts.map +1 -0
  74. package/dist/src/runtime/await-tracker.specs.js +46 -0
  75. package/dist/src/runtime/await-tracker.specs.js.map +1 -0
  76. package/dist/src/runtime/context.d.ts +12 -0
  77. package/dist/src/runtime/context.d.ts.map +1 -0
  78. package/dist/src/runtime/context.js +2 -0
  79. package/dist/src/runtime/context.js.map +1 -0
  80. package/dist/src/runtime/context.specs.d.ts +2 -0
  81. package/dist/src/runtime/context.specs.d.ts.map +1 -0
  82. package/dist/src/runtime/context.specs.js +26 -0
  83. package/dist/src/runtime/context.specs.js.map +1 -0
  84. package/dist/src/runtime/event-command-map.d.ts +15 -0
  85. package/dist/src/runtime/event-command-map.d.ts.map +1 -0
  86. package/dist/src/runtime/event-command-map.js +26 -0
  87. package/dist/src/runtime/event-command-map.js.map +1 -0
  88. package/dist/src/runtime/event-command-map.specs.d.ts +2 -0
  89. package/dist/src/runtime/event-command-map.specs.d.ts.map +1 -0
  90. package/dist/src/runtime/event-command-map.specs.js +108 -0
  91. package/dist/src/runtime/event-command-map.specs.js.map +1 -0
  92. package/dist/src/runtime/phased-executor.d.ts +29 -0
  93. package/dist/src/runtime/phased-executor.d.ts.map +1 -0
  94. package/dist/src/runtime/phased-executor.js +164 -0
  95. package/dist/src/runtime/phased-executor.js.map +1 -0
  96. package/dist/src/runtime/phased-executor.specs.d.ts +2 -0
  97. package/dist/src/runtime/phased-executor.specs.d.ts.map +1 -0
  98. package/dist/src/runtime/phased-executor.specs.js +256 -0
  99. package/dist/src/runtime/phased-executor.specs.js.map +1 -0
  100. package/dist/src/runtime/pipeline-runtime.d.ts +17 -0
  101. package/dist/src/runtime/pipeline-runtime.d.ts.map +1 -0
  102. package/dist/src/runtime/pipeline-runtime.js +87 -0
  103. package/dist/src/runtime/pipeline-runtime.js.map +1 -0
  104. package/dist/src/runtime/pipeline-runtime.specs.d.ts +2 -0
  105. package/dist/src/runtime/pipeline-runtime.specs.d.ts.map +1 -0
  106. package/dist/src/runtime/pipeline-runtime.specs.js +192 -0
  107. package/dist/src/runtime/pipeline-runtime.specs.js.map +1 -0
  108. package/dist/src/runtime/settled-tracker.d.ts +42 -0
  109. package/dist/src/runtime/settled-tracker.d.ts.map +1 -0
  110. package/dist/src/runtime/settled-tracker.js +161 -0
  111. package/dist/src/runtime/settled-tracker.js.map +1 -0
  112. package/dist/src/runtime/settled-tracker.specs.d.ts +2 -0
  113. package/dist/src/runtime/settled-tracker.specs.d.ts.map +1 -0
  114. package/dist/src/runtime/settled-tracker.specs.js +361 -0
  115. package/dist/src/runtime/settled-tracker.specs.js.map +1 -0
  116. package/dist/src/server/full-orchestration.e2e.specs.d.ts +2 -0
  117. package/dist/src/server/full-orchestration.e2e.specs.d.ts.map +1 -0
  118. package/dist/src/server/full-orchestration.e2e.specs.js +561 -0
  119. package/dist/src/server/full-orchestration.e2e.specs.js.map +1 -0
  120. package/dist/src/server/pipeline-server.d.ts +59 -0
  121. package/dist/src/server/pipeline-server.d.ts.map +1 -0
  122. package/dist/src/server/pipeline-server.e2e.specs.d.ts +2 -0
  123. package/dist/src/server/pipeline-server.e2e.specs.d.ts.map +1 -0
  124. package/dist/src/server/pipeline-server.e2e.specs.js +381 -0
  125. package/dist/src/server/pipeline-server.e2e.specs.js.map +1 -0
  126. package/dist/src/server/pipeline-server.js +527 -0
  127. package/dist/src/server/pipeline-server.js.map +1 -0
  128. package/dist/src/server/pipeline-server.specs.d.ts +2 -0
  129. package/dist/src/server/pipeline-server.specs.d.ts.map +1 -0
  130. package/dist/src/server/pipeline-server.specs.js +662 -0
  131. package/dist/src/server/pipeline-server.specs.js.map +1 -0
  132. package/dist/src/server/sse-manager.d.ts +12 -0
  133. package/dist/src/server/sse-manager.d.ts.map +1 -0
  134. package/dist/src/server/sse-manager.js +63 -0
  135. package/dist/src/server/sse-manager.js.map +1 -0
  136. package/dist/src/server/sse-manager.specs.d.ts +2 -0
  137. package/dist/src/server/sse-manager.specs.d.ts.map +1 -0
  138. package/dist/src/server/sse-manager.specs.js +158 -0
  139. package/dist/src/server/sse-manager.specs.js.map +1 -0
  140. package/dist/src/testing/event-capture.d.ts +14 -0
  141. package/dist/src/testing/event-capture.d.ts.map +1 -0
  142. package/dist/src/testing/event-capture.js +55 -0
  143. package/dist/src/testing/event-capture.js.map +1 -0
  144. package/dist/src/testing/event-capture.specs.d.ts +2 -0
  145. package/dist/src/testing/event-capture.specs.d.ts.map +1 -0
  146. package/dist/src/testing/event-capture.specs.js +114 -0
  147. package/dist/src/testing/event-capture.specs.js.map +1 -0
  148. package/dist/src/testing/fixtures/kanban-full.pipeline.d.ts +7 -0
  149. package/dist/src/testing/fixtures/kanban-full.pipeline.d.ts.map +1 -0
  150. package/dist/src/testing/fixtures/kanban-full.pipeline.js +168 -0
  151. package/dist/src/testing/fixtures/kanban-full.pipeline.js.map +1 -0
  152. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts +2 -0
  153. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts.map +1 -0
  154. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js +263 -0
  155. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js.map +1 -0
  156. package/dist/src/testing/fixtures/kanban-todo.config.d.ts +3 -0
  157. package/dist/src/testing/fixtures/kanban-todo.config.d.ts.map +1 -0
  158. package/dist/src/testing/fixtures/kanban-todo.config.js +19 -0
  159. package/dist/src/testing/fixtures/kanban-todo.config.js.map +1 -0
  160. package/dist/src/testing/fixtures/kanban.pipeline.d.ts +5 -0
  161. package/dist/src/testing/fixtures/kanban.pipeline.d.ts.map +1 -0
  162. package/dist/src/testing/fixtures/kanban.pipeline.js +76 -0
  163. package/dist/src/testing/fixtures/kanban.pipeline.js.map +1 -0
  164. package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts +2 -0
  165. package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts.map +1 -0
  166. package/dist/src/testing/fixtures/kanban.pipeline.specs.js +29 -0
  167. package/dist/src/testing/fixtures/kanban.pipeline.specs.js.map +1 -0
  168. package/dist/src/testing/kanban-todo.e2e.specs.d.ts +2 -0
  169. package/dist/src/testing/kanban-todo.e2e.specs.d.ts.map +1 -0
  170. package/dist/src/testing/kanban-todo.e2e.specs.js +160 -0
  171. package/dist/src/testing/kanban-todo.e2e.specs.js.map +1 -0
  172. package/dist/src/testing/mock-handlers.d.ts +21 -0
  173. package/dist/src/testing/mock-handlers.d.ts.map +1 -0
  174. package/dist/src/testing/mock-handlers.js +34 -0
  175. package/dist/src/testing/mock-handlers.js.map +1 -0
  176. package/dist/src/testing/mock-handlers.specs.d.ts +2 -0
  177. package/dist/src/testing/mock-handlers.specs.d.ts.map +1 -0
  178. package/dist/src/testing/mock-handlers.specs.js +193 -0
  179. package/dist/src/testing/mock-handlers.specs.js.map +1 -0
  180. package/dist/src/testing/real-execution.e2e.specs.d.ts +2 -0
  181. package/dist/src/testing/real-execution.e2e.specs.d.ts.map +1 -0
  182. package/dist/src/testing/real-execution.e2e.specs.js +140 -0
  183. package/dist/src/testing/real-execution.e2e.specs.js.map +1 -0
  184. package/dist/src/testing/real-plugin.e2e.specs.d.ts +2 -0
  185. package/dist/src/testing/real-plugin.e2e.specs.d.ts.map +1 -0
  186. package/dist/src/testing/real-plugin.e2e.specs.js +65 -0
  187. package/dist/src/testing/real-plugin.e2e.specs.js.map +1 -0
  188. package/dist/src/testing/server-startup.e2e.specs.d.ts +2 -0
  189. package/dist/src/testing/server-startup.e2e.specs.d.ts.map +1 -0
  190. package/dist/src/testing/server-startup.e2e.specs.js +104 -0
  191. package/dist/src/testing/server-startup.e2e.specs.js.map +1 -0
  192. package/dist/src/testing/snapshot-compare.d.ts +18 -0
  193. package/dist/src/testing/snapshot-compare.d.ts.map +1 -0
  194. package/dist/src/testing/snapshot-compare.js +86 -0
  195. package/dist/src/testing/snapshot-compare.js.map +1 -0
  196. package/dist/src/testing/snapshot-compare.specs.d.ts +2 -0
  197. package/dist/src/testing/snapshot-compare.specs.d.ts.map +1 -0
  198. package/dist/src/testing/snapshot-compare.specs.js +112 -0
  199. package/dist/src/testing/snapshot-compare.specs.js.map +1 -0
  200. package/dist/src/testing/snapshot-sanitize.d.ts +8 -0
  201. package/dist/src/testing/snapshot-sanitize.d.ts.map +1 -0
  202. package/dist/src/testing/snapshot-sanitize.js +10 -0
  203. package/dist/src/testing/snapshot-sanitize.js.map +1 -0
  204. package/dist/src/testing/snapshot-sanitize.specs.d.ts +2 -0
  205. package/dist/src/testing/snapshot-sanitize.specs.d.ts.map +1 -0
  206. package/dist/src/testing/snapshot-sanitize.specs.js +104 -0
  207. package/dist/src/testing/snapshot-sanitize.specs.js.map +1 -0
  208. package/dist/tsconfig.tsbuildinfo +1 -0
  209. package/docs/testing-analysis.md +395 -0
  210. package/package.json +31 -0
  211. package/pipeline-api-new.md +1078 -0
  212. package/pomodoro-plan.md +651 -0
  213. package/scripts/run-kanban-e2e.ts +219 -0
  214. package/scripts/start-server.ts +64 -0
  215. package/snapshots/e2e-run-2025-12-22T15-52-03.json +613 -0
  216. package/snapshots/e2e-run-2025-12-22T16-51-30.json +699 -0
  217. package/src/builder/define.specs.ts +531 -0
  218. package/src/builder/define.ts +700 -0
  219. package/src/config/pipeline-config.ts +32 -0
  220. package/src/core/descriptors.specs.ts +28 -0
  221. package/src/core/descriptors.ts +99 -0
  222. package/src/core/types.specs.ts +44 -0
  223. package/src/core/types.ts +16 -0
  224. package/src/graph/types.specs.ts +176 -0
  225. package/src/graph/types.ts +19 -0
  226. package/src/index.ts +54 -0
  227. package/src/logging/event-logger.specs.ts +100 -0
  228. package/src/logging/event-logger.ts +50 -0
  229. package/src/plugins/handler-adapter.specs.ts +164 -0
  230. package/src/plugins/handler-adapter.ts +21 -0
  231. package/src/plugins/plugin-loader.specs.ts +295 -0
  232. package/src/plugins/plugin-loader.ts +202 -0
  233. package/src/runtime/await-tracker.specs.ts +52 -0
  234. package/src/runtime/await-tracker.ts +50 -0
  235. package/src/runtime/context.specs.ts +28 -0
  236. package/src/runtime/context.ts +13 -0
  237. package/src/runtime/event-command-map.specs.ts +136 -0
  238. package/src/runtime/event-command-map.ts +38 -0
  239. package/src/runtime/phased-executor.specs.ts +358 -0
  240. package/src/runtime/phased-executor.ts +224 -0
  241. package/src/runtime/pipeline-runtime.specs.ts +214 -0
  242. package/src/runtime/pipeline-runtime.ts +119 -0
  243. package/src/runtime/settled-tracker.specs.ts +448 -0
  244. package/src/runtime/settled-tracker.ts +237 -0
  245. package/src/server/full-orchestration.e2e.specs.ts +672 -0
  246. package/src/server/pipeline-server.e2e.specs.ts +505 -0
  247. package/src/server/pipeline-server.specs.ts +761 -0
  248. package/src/server/pipeline-server.ts +656 -0
  249. package/src/server/sse-manager.specs.ts +208 -0
  250. package/src/server/sse-manager.ts +79 -0
  251. package/src/testing/event-capture.specs.ts +143 -0
  252. package/src/testing/event-capture.ts +65 -0
  253. package/src/testing/fixtures/kanban-full.pipeline.specs.ts +337 -0
  254. package/src/testing/fixtures/kanban-full.pipeline.ts +225 -0
  255. package/src/testing/fixtures/kanban-todo.config.ts +19 -0
  256. package/src/testing/fixtures/kanban.pipeline.specs.ts +33 -0
  257. package/src/testing/fixtures/kanban.pipeline.ts +124 -0
  258. package/src/testing/kanban-todo.e2e.specs.ts +209 -0
  259. package/src/testing/mock-handlers.specs.ts +229 -0
  260. package/src/testing/mock-handlers.ts +58 -0
  261. package/src/testing/real-execution.e2e.specs.ts +193 -0
  262. package/src/testing/real-plugin.e2e.specs.ts +94 -0
  263. package/src/testing/server-startup.e2e.specs.ts +162 -0
  264. package/src/testing/snapshot-compare.specs.ts +136 -0
  265. package/src/testing/snapshot-compare.ts +106 -0
  266. package/src/testing/snapshot-sanitize.specs.ts +131 -0
  267. package/src/testing/snapshot-sanitize.ts +17 -0
  268. package/tsconfig.json +11 -0
  269. package/tsconfig.test.json +9 -0
  270. package/vitest.config.ts +29 -0
@@ -0,0 +1,531 @@
1
+ import { vi } from 'vitest';
2
+ import type {
3
+ CustomHandlerDescriptor,
4
+ EmitHandlerDescriptor,
5
+ ForEachPhasedDescriptor,
6
+ RunAwaitHandlerDescriptor,
7
+ SettledHandlerDescriptor,
8
+ } from '../core/descriptors';
9
+ import { define } from './define';
10
+
11
+ describe('define()', () => {
12
+ it('should create PipelineBuilder via define()', () => {
13
+ const builder = define('my-pipeline');
14
+ expect(builder).toBeDefined();
15
+ expect(typeof builder.version).toBe('function');
16
+ expect(typeof builder.on).toBe('function');
17
+ expect(typeof builder.build).toBe('function');
18
+ });
19
+
20
+ it('should chain version() and description()', () => {
21
+ const pipeline = define('test').version('1.0.0').description('Test pipeline').build();
22
+ expect(pipeline.descriptor.name).toBe('test');
23
+ expect(pipeline.descriptor.version).toBe('1.0.0');
24
+ expect(pipeline.descriptor.description).toBe('Test pipeline');
25
+ });
26
+
27
+ it('should return frozen Pipeline from build()', () => {
28
+ const pipeline = define('test').build();
29
+ expect(pipeline.descriptor).toBeDefined();
30
+ expect(Object.isFrozen(pipeline.descriptor)).toBe(true);
31
+ });
32
+
33
+ it('should define named key extractors', () => {
34
+ const extractor = (e: { data: { slicePath?: string } }) => e.data.slicePath ?? '';
35
+ const pipeline = define('test').key('bySlice', extractor).build();
36
+ expect(pipeline.descriptor.keys.get('bySlice')).toBe(extractor);
37
+ });
38
+ });
39
+
40
+ describe('on() and emit()', () => {
41
+ it('should capture emit handler in descriptor', () => {
42
+ const pipeline = define('test').on('SchemaExported').emit('GenerateServer', { modelPath: './schema.json' }).build();
43
+ expect(pipeline.descriptor.handlers).toHaveLength(1);
44
+ const handler = pipeline.descriptor.handlers[0] as EmitHandlerDescriptor;
45
+ expect(handler.type).toBe('emit');
46
+ expect(handler.eventType).toBe('SchemaExported');
47
+ expect(handler.commands).toEqual([{ commandType: 'GenerateServer', data: { modelPath: './schema.json' } }]);
48
+ });
49
+
50
+ it('should accept data factory in emit()', () => {
51
+ type SliceEvent = { data: { path: string } };
52
+ const factory = (e: SliceEvent) => ({ slicePath: e.data.path });
53
+ const pipeline = define('test').on('SliceGenerated').emit('ImplementSlice', factory).build();
54
+ const handler = pipeline.descriptor.handlers[0] as EmitHandlerDescriptor;
55
+ expect(typeof handler.commands[0].data).toBe('function');
56
+ });
57
+
58
+ it('should chain emit() for parallel commands', () => {
59
+ const pipeline = define('test')
60
+ .on('ServerGenerated')
61
+ .emit('GenerateIA', { modelPath: './schema.json' })
62
+ .emit('StartServer', { serverDirectory: './server' })
63
+ .build();
64
+ const handler = pipeline.descriptor.handlers[0] as EmitHandlerDescriptor;
65
+ expect(handler.commands).toHaveLength(2);
66
+ expect(handler.commands[0].commandType).toBe('GenerateIA');
67
+ expect(handler.commands[1].commandType).toBe('StartServer');
68
+ });
69
+
70
+ it('should chain on() from EmitChain', () => {
71
+ const pipeline = define('test').on('EventA').emit('CmdA', { d: 'a' }).on('EventB').emit('CmdB', { d: 'b' }).build();
72
+ expect(pipeline.descriptor.handlers).toHaveLength(2);
73
+ const handlerA = pipeline.descriptor.handlers[0] as EmitHandlerDescriptor;
74
+ const handlerB = pipeline.descriptor.handlers[1] as EmitHandlerDescriptor;
75
+ expect(handlerA.eventType).toBe('EventA');
76
+ expect(handlerB.eventType).toBe('EventB');
77
+ });
78
+ });
79
+
80
+ describe('when() predicate', () => {
81
+ it('should apply predicate with when()', () => {
82
+ type ClientEvent = { data: { components?: string[] } };
83
+ const predicate = (e: ClientEvent) => (e.data.components?.length ?? 0) > 0;
84
+ const pipeline = define('test')
85
+ .on('ClientGenerated')
86
+ .when(predicate)
87
+ .emit('ImplementComponent', { path: './c' })
88
+ .build();
89
+ const handler = pipeline.descriptor.handlers[0];
90
+ expect(handler.type).toBe('emit');
91
+ if (handler.type === 'emit') {
92
+ expect(handler.predicate).toBe(predicate);
93
+ }
94
+ });
95
+ });
96
+
97
+ describe('settled()', () => {
98
+ it('should create a SettledHandlerDescriptor', () => {
99
+ const handler = vi.fn();
100
+ const pipeline = define('test')
101
+ .settled(['CheckTests', 'CheckTypes', 'CheckLint'])
102
+ .dispatch({ dispatches: [] }, handler)
103
+ .build();
104
+
105
+ const descriptor = pipeline.descriptor.handlers[0] as SettledHandlerDescriptor;
106
+ expect(descriptor.type).toBe('settled');
107
+ expect(descriptor.commandTypes).toEqual(['CheckTests', 'CheckTypes', 'CheckLint']);
108
+ expect(descriptor.handler).toBe(handler);
109
+ });
110
+
111
+ it('should chain multiple settled handlers', () => {
112
+ const handler1 = vi.fn();
113
+ const handler2 = vi.fn();
114
+
115
+ const pipeline = define('test')
116
+ .settled(['A', 'B'])
117
+ .dispatch({ dispatches: [] }, handler1)
118
+ .settled(['C', 'D'])
119
+ .dispatch({ dispatches: [] }, handler2)
120
+ .build();
121
+
122
+ expect(pipeline.descriptor.handlers).toHaveLength(2);
123
+ expect((pipeline.descriptor.handlers[0] as SettledHandlerDescriptor).commandTypes).toEqual(['A', 'B']);
124
+ expect((pipeline.descriptor.handlers[1] as SettledHandlerDescriptor).commandTypes).toEqual(['C', 'D']);
125
+ });
126
+
127
+ it('should chain settled with on()', () => {
128
+ const settledHandler = vi.fn();
129
+
130
+ const pipeline = define('test')
131
+ .settled(['CheckTests', 'CheckTypes'])
132
+ .dispatch({ dispatches: [] }, settledHandler)
133
+ .on('ServerGenerated')
134
+ .emit('GenerateIA', {})
135
+ .build();
136
+
137
+ expect(pipeline.descriptor.handlers).toHaveLength(2);
138
+ expect(pipeline.descriptor.handlers[0].type).toBe('settled');
139
+ expect(pipeline.descriptor.handlers[1].type).toBe('emit');
140
+ });
141
+
142
+ it('should include settled handler in graph', () => {
143
+ const pipeline = define('test')
144
+ .settled(['CheckTests', 'CheckTypes'])
145
+ .dispatch({ dispatches: [] }, () => {})
146
+ .build();
147
+
148
+ const graph = pipeline.toGraph();
149
+ const settledNode = graph.nodes.find((n) => n.id.startsWith('settled:'));
150
+ expect(settledNode).toBeDefined();
151
+ expect(settledNode?.label).toBe('settled(CheckTests, CheckTypes)');
152
+ });
153
+
154
+ it('should accept options-first dispatch with dispatches array', () => {
155
+ const pipeline = define('test')
156
+ .settled(['CheckA'])
157
+ .dispatch({ dispatches: ['RetryCommand'] }, () => {})
158
+ .build();
159
+
160
+ const descriptor = pipeline.descriptor.handlers[0] as SettledHandlerDescriptor;
161
+ expect(descriptor.dispatches).toEqual(['RetryCommand']);
162
+ });
163
+ });
164
+
165
+ describe('Integration', () => {
166
+ it('should create complete simple pipeline', () => {
167
+ type SliceEvent = { data: { slicePath: string } };
168
+ const pipeline = define('kanban')
169
+ .version('1.0.0')
170
+ .description('Kanban app generation')
171
+ .key('bySlice', (e: SliceEvent) => e.data.slicePath ?? '')
172
+ .on('SchemaExported')
173
+ .emit('GenerateServer', { modelPath: './schema.json', destination: '.' })
174
+ .on('SliceGenerated')
175
+ .emit('ImplementSlice', (e: SliceEvent) => ({
176
+ slicePath: e.data.slicePath,
177
+ context: { attemptNumber: 0, previousOutputs: 'errors' },
178
+ aiOptions: { maxTokens: 2000 },
179
+ }))
180
+ .on('ServerGenerated')
181
+ .emit('GenerateIA', { modelPath: './schema.json', outputDir: './.context' })
182
+ .emit('StartServer', { serverDirectory: './server' })
183
+ .build();
184
+
185
+ expect(pipeline.descriptor.name).toBe('kanban');
186
+ expect(pipeline.descriptor.handlers).toHaveLength(3);
187
+ });
188
+ });
189
+
190
+ describe('run() and awaitAll() - Scatter-Gather', () => {
191
+ it('should return RunBuilder from TriggerBuilder.run()', () => {
192
+ const builder = define('test')
193
+ .on('BatchReady')
194
+ .run([{ commandType: 'ProcessItem', data: { id: 1 } }]);
195
+
196
+ expect(builder).toBeDefined();
197
+ expect(typeof builder.awaitAll).toBe('function');
198
+ });
199
+
200
+ it('should accept static commands array in run()', () => {
201
+ const pipeline = define('test')
202
+ .on('StartBatch')
203
+ .run([
204
+ { commandType: 'TaskA', data: { id: 1 } },
205
+ { commandType: 'TaskB', data: { id: 2 } },
206
+ ])
207
+ .awaitAll('byTaskId', (e: { data: { taskId: string } }) => e.data.taskId)
208
+ .build();
209
+
210
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
211
+ expect(handler.commands).toHaveLength(2);
212
+ });
213
+
214
+ it('should accept command factory in run()', () => {
215
+ type BatchEvent = { data: { items: Array<{ id: string }> } };
216
+ const factory = (e: BatchEvent) =>
217
+ e.data.items.map((item) => ({ commandType: 'ProcessItem', data: { itemId: item.id } }));
218
+
219
+ const pipeline = define('test')
220
+ .on('BatchReady')
221
+ .run(factory)
222
+ .awaitAll('byItem', (e: { data: { itemId: string } }) => e.data.itemId)
223
+ .build();
224
+
225
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
226
+ expect(typeof handler.commands).toBe('function');
227
+ });
228
+
229
+ it('should configure awaitAll with key extractor', () => {
230
+ const keyExtractor = (e: { data: { taskId: string } }) => e.data.taskId;
231
+
232
+ const pipeline = define('test')
233
+ .on('StartBatch')
234
+ .run([{ commandType: 'ProcessItem', data: { items: [] } }])
235
+ .awaitAll('byTask', keyExtractor)
236
+ .build();
237
+
238
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
239
+ expect(handler.awaitConfig.key).toBe(keyExtractor);
240
+ expect(handler.awaitConfig.keyName).toBe('byTask');
241
+ });
242
+
243
+ it('should support timeout in awaitAll', () => {
244
+ const pipeline = define('test')
245
+ .on('StartBatch')
246
+ .run([{ commandType: 'ProcessItem', data: { items: [] } }])
247
+ .awaitAll('byTask', (e: { data: { taskId: string } }) => e.data.taskId, { timeout: 30000 })
248
+ .build();
249
+
250
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
251
+ expect(handler.awaitConfig.timeout).toBe(30000);
252
+ });
253
+
254
+ it('should configure onSuccess handler', () => {
255
+ const pipeline = define('test')
256
+ .on('StartBatch')
257
+ .run([{ commandType: 'ProcessItem', data: { items: [] } }])
258
+ .awaitAll('byTask', (e: { data: { taskId: string } }) => e.data.taskId)
259
+ .onSuccess('BatchCompleted', (ctx) => ({
260
+ results: ctx.results,
261
+ duration: ctx.duration,
262
+ }))
263
+ .build();
264
+
265
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
266
+ expect(handler.onSuccess).toBeDefined();
267
+ expect(handler.onSuccess?.eventType).toBe('BatchCompleted');
268
+ });
269
+
270
+ it('should configure onFailure handler', () => {
271
+ const pipeline = define('test')
272
+ .on('StartBatch')
273
+ .run([{ commandType: 'ProcessItem', data: { items: [] } }])
274
+ .awaitAll('byTask', (e: { data: { taskId: string } }) => e.data.taskId)
275
+ .onFailure('BatchFailed', (ctx) => ({
276
+ failedCount: ctx.failures.length,
277
+ errors: ctx.failures.map((f) => f.error),
278
+ }))
279
+ .build();
280
+
281
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
282
+ expect(handler.onFailure).toBeDefined();
283
+ expect(handler.onFailure?.eventType).toBe('BatchFailed');
284
+ });
285
+
286
+ it('should chain on() from GatherBuilder', () => {
287
+ const pipeline = define('test')
288
+ .on('StartBatch')
289
+ .run([{ commandType: 'ProcessItem', data: { items: [] } }])
290
+ .awaitAll('byTask', (e: { data: { taskId: string } }) => e.data.taskId)
291
+ .onSuccess('BatchDone', () => ({}))
292
+ .on('BatchDone')
293
+ .emit('NotifyUser', { message: 'Complete' })
294
+ .build();
295
+
296
+ expect(pipeline.descriptor.handlers).toHaveLength(2);
297
+ });
298
+
299
+ it('should chain onSuccess and onFailure from GatherChain', () => {
300
+ const pipeline = define('test')
301
+ .on('StartBatch')
302
+ .run([{ commandType: 'ProcessItem', data: {} }])
303
+ .awaitAll('byTask', (e: { data: { taskId: string } }) => e.data.taskId)
304
+ .onFailure('BatchFailed', (ctx) => ({ errors: ctx.failures }))
305
+ .onSuccess('BatchDone', (ctx) => ({ count: ctx.results.length }))
306
+ .build();
307
+
308
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
309
+ expect(handler.onSuccess?.eventType).toBe('BatchDone');
310
+ expect(handler.onFailure?.eventType).toBe('BatchFailed');
311
+ });
312
+
313
+ it('should chain build() from GatherBuilder', () => {
314
+ const pipeline = define('test')
315
+ .on('StartBatch')
316
+ .run([{ commandType: 'ProcessItem', data: { items: [] } }])
317
+ .awaitAll('byTask', (e: { data: { taskId: string } }) => e.data.taskId)
318
+ .build();
319
+
320
+ expect(pipeline.descriptor.handlers).toHaveLength(1);
321
+ });
322
+
323
+ it('should create complete scatter-gather pipeline', () => {
324
+ type BatchEvent = { data: { items: Array<{ id: string; priority: number }> } };
325
+ type ResultEvent = { data: { itemId: string; result: unknown } };
326
+
327
+ const pipeline = define('batch-processor')
328
+ .version('1.0.0')
329
+ .description('Process items in parallel with gather')
330
+ .key('byItem', (e: ResultEvent) => e.data.itemId)
331
+ .on('BatchReceived')
332
+ .when((e: BatchEvent) => e.data.items.length > 0)
333
+ .run((e: BatchEvent) =>
334
+ e.data.items.map((item) => ({
335
+ commandType: 'ProcessItem',
336
+ data: { itemId: item.id, priority: item.priority },
337
+ })),
338
+ )
339
+ .awaitAll('byItem', (e: ResultEvent) => e.data.itemId, { timeout: 60000 })
340
+ .onSuccess('BatchCompleted', (ctx) => ({
341
+ processedCount: ctx.results.length,
342
+ totalDuration: ctx.duration,
343
+ }))
344
+ .onFailure('BatchFailed', (ctx) => ({
345
+ failedItems: ctx.failures.map((f) => f.key),
346
+ successCount: ctx.successes.length,
347
+ }))
348
+ .build();
349
+
350
+ expect(pipeline.descriptor.name).toBe('batch-processor');
351
+ expect(pipeline.descriptor.handlers).toHaveLength(1);
352
+ const handler = pipeline.descriptor.handlers[0] as RunAwaitHandlerDescriptor;
353
+ expect(handler.type).toBe('run-await');
354
+ expect(handler.awaitConfig.timeout).toBe(60000);
355
+ expect(handler.onSuccess?.eventType).toBe('BatchCompleted');
356
+ expect(handler.onFailure?.eventType).toBe('BatchFailed');
357
+ });
358
+ });
359
+
360
+ describe('forEach() and groupInto() - Phased Execution', () => {
361
+ it('should return ForEachBuilder from TriggerBuilder.forEach()', () => {
362
+ type ItemsEvent = { data: { items: Array<{ id: string }> } };
363
+ const builder = define('test')
364
+ .on('ItemsReady')
365
+ .forEach((e: ItemsEvent) => e.data.items);
366
+
367
+ expect(builder).toBeDefined();
368
+ expect(typeof builder.groupInto).toBe('function');
369
+ });
370
+
371
+ it('should configure phases with groupInto()', () => {
372
+ type Item = { id: string; type: 'critical' | 'normal' };
373
+ const pipeline = define('test')
374
+ .on('ItemsReady')
375
+ .forEach((e: { data: { items: Item[] } }) => e.data.items)
376
+ .groupInto(['critical', 'normal'], (item: Item) => item.type)
377
+ .process('ProcessItem', (item: Item) => ({ itemId: item.id }))
378
+ .onComplete({ success: 'Done', failure: 'Failed', itemKey: () => '' })
379
+ .build();
380
+
381
+ const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
382
+ expect(handler.phases).toEqual(['critical', 'normal']);
383
+ });
384
+
385
+ it('should configure emitFactory with process()', () => {
386
+ type Item = { id: string };
387
+ const pipeline = define('test')
388
+ .on('ItemsReady')
389
+ .forEach((e: { data: { items: Item[] } }) => e.data.items)
390
+ .groupInto(['phase1'], () => 'phase1')
391
+ .process('ProcessItem', (item: Item) => ({ itemId: item.id }))
392
+ .onComplete({ success: 'Done', failure: 'Failed', itemKey: () => '' })
393
+ .build();
394
+
395
+ const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
396
+ expect(typeof handler.emitFactory).toBe('function');
397
+ });
398
+
399
+ it('should set stopOnFailure flag', () => {
400
+ const pipeline = define('test')
401
+ .on('ItemsReady')
402
+ .forEach((e: { data: { items: unknown[] } }) => e.data.items)
403
+ .groupInto(['phase1'], () => 'phase1')
404
+ .process('ProcessItem', () => ({}))
405
+ .stopOnFailure()
406
+ .onComplete({ success: 'Done', failure: 'Failed', itemKey: () => '' })
407
+ .build();
408
+
409
+ const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
410
+ expect(handler.stopOnFailure).toBe(true);
411
+ });
412
+
413
+ it('should configure completion events', () => {
414
+ type Item = { id: string };
415
+ type ResultEvent = { data: { itemId: string } };
416
+ const pipeline = define('test')
417
+ .on('ItemsReady')
418
+ .forEach((e: { data: { items: Item[] } }) => e.data.items)
419
+ .groupInto(['phase1'], () => 'phase1')
420
+ .process('ProcessItem', (item: Item) => ({ itemId: item.id }))
421
+ .onComplete({
422
+ success: 'AllItemsProcessed',
423
+ failure: 'ProcessingFailed',
424
+ itemKey: (e) => (e as unknown as ResultEvent).data.itemId,
425
+ })
426
+ .build();
427
+
428
+ const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
429
+ expect(handler.completion.successEvent).toBe('AllItemsProcessed');
430
+ expect(handler.completion.failureEvent).toBe('ProcessingFailed');
431
+ });
432
+
433
+ it('should chain on() from PhasedTerminal', () => {
434
+ const pipeline = define('test')
435
+ .on('ItemsReady')
436
+ .forEach((e: { data: { items: unknown[] } }) => e.data.items)
437
+ .groupInto(['phase1'], () => 'phase1')
438
+ .process('ProcessItem', () => ({}))
439
+ .onComplete({ success: 'Done', failure: 'Failed', itemKey: () => '' })
440
+ .on('Done')
441
+ .emit('Notify', {})
442
+ .build();
443
+
444
+ expect(pipeline.descriptor.handlers).toHaveLength(2);
445
+ });
446
+
447
+ it('should default stopOnFailure to false', () => {
448
+ const pipeline = define('test')
449
+ .on('ItemsReady')
450
+ .forEach((e: { data: { items: unknown[] } }) => e.data.items)
451
+ .groupInto(['phase1'], () => 'phase1')
452
+ .process('ProcessItem', () => ({}))
453
+ .onComplete({ success: 'Done', failure: 'Failed', itemKey: () => '' })
454
+ .build();
455
+
456
+ const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
457
+ expect(handler.stopOnFailure).toBe(false);
458
+ });
459
+
460
+ it('should create complete phased execution pipeline', () => {
461
+ type Component = { path: string; priority: 'high' | 'medium' | 'low' };
462
+ type ComponentEvent = { data: { components: Component[] } };
463
+ type ResultEvent = { data: { componentPath: string } };
464
+
465
+ const pipeline = define('component-processor')
466
+ .version('1.0.0')
467
+ .description('Process components in priority phases')
468
+ .on('ComponentsGenerated')
469
+ .when((e: ComponentEvent) => e.data.components.length > 0)
470
+ .forEach((e: ComponentEvent) => e.data.components)
471
+ .groupInto(['high', 'medium', 'low'], (c: Component) => c.priority)
472
+ .process('ImplementComponent', (c: Component) => ({ componentPath: c.path }))
473
+ .stopOnFailure()
474
+ .onComplete({
475
+ success: 'AllComponentsImplemented',
476
+ failure: 'ComponentImplementationFailed',
477
+ itemKey: (e) => (e as unknown as ResultEvent).data.componentPath,
478
+ })
479
+ .build();
480
+
481
+ expect(pipeline.descriptor.name).toBe('component-processor');
482
+ const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
483
+ expect(handler.type).toBe('foreach-phased');
484
+ expect(handler.phases).toEqual(['high', 'medium', 'low']);
485
+ expect(handler.stopOnFailure).toBe(true);
486
+ });
487
+
488
+ it('should throw when build() is called without onComplete()', () => {
489
+ const chain = define('test')
490
+ .on('ItemsReady')
491
+ .forEach((e: { data: { items: unknown[] } }) => e.data.items)
492
+ .groupInto(['phase1'], () => 'phase1')
493
+ .process('ProcessItem', () => ({}));
494
+
495
+ expect(() => chain.build()).toThrow('onComplete() must be called before build()');
496
+ });
497
+ });
498
+
499
+ describe('handle() - Custom Handlers', () => {
500
+ it('should capture custom handler', () => {
501
+ const customHandler = async (e: { data: unknown }) => {
502
+ console.log(e);
503
+ };
504
+ const pipeline = define('test').on('CustomEvent').handle(customHandler).build();
505
+
506
+ const desc = pipeline.descriptor.handlers[0] as CustomHandlerDescriptor;
507
+ expect(desc.type).toBe('custom');
508
+ expect(desc.handler).toBe(customHandler);
509
+ });
510
+
511
+ it('should capture declaredEmits for graph introspection', () => {
512
+ const pipeline = define('test')
513
+ .on('CustomEvent')
514
+ .handle(async () => {}, { emits: ['EventA', 'EventB'] })
515
+ .build();
516
+
517
+ const desc = pipeline.descriptor.handlers[0] as CustomHandlerDescriptor;
518
+ expect(desc.declaredEmits).toEqual(['EventA', 'EventB']);
519
+ });
520
+
521
+ it('should chain on() from handle()', () => {
522
+ const pipeline = define('test')
523
+ .on('EventA')
524
+ .handle(async () => {})
525
+ .on('EventB')
526
+ .emit('CommandB', {})
527
+ .build();
528
+
529
+ expect(pipeline.descriptor.handlers).toHaveLength(2);
530
+ });
531
+ });