@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,337 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import type { EmitHandlerDescriptor, ForEachPhasedDescriptor } from '../../core/descriptors';
3
+ import {
4
+ createKanbanFullPipeline,
5
+ resetKanbanState,
6
+ setProjectRoot,
7
+ setSliceRetryCount,
8
+ testResolvePath,
9
+ testShouldRetry,
10
+ } from './kanban-full.pipeline';
11
+
12
+ describe('kanban-full.pipeline', () => {
13
+ beforeEach(() => {
14
+ resetKanbanState();
15
+ });
16
+
17
+ describe('resolvePath', () => {
18
+ it('should return relativePath when projectRoot is empty', () => {
19
+ expect(testResolvePath('./foo/bar')).toBe('./foo/bar');
20
+ });
21
+
22
+ it('should return absolute path unchanged', () => {
23
+ setProjectRoot('/my/project');
24
+ expect(testResolvePath('/absolute/path')).toBe('/absolute/path');
25
+ });
26
+
27
+ it('should resolve ./ relative paths', () => {
28
+ setProjectRoot('/my/project');
29
+ expect(testResolvePath('./foo/bar')).toBe('/my/project/foo/bar');
30
+ });
31
+
32
+ it('should resolve bare relative paths', () => {
33
+ setProjectRoot('/my/project');
34
+ expect(testResolvePath('foo/bar')).toBe('/my/project/foo/bar');
35
+ });
36
+ });
37
+
38
+ describe('shouldRetry', () => {
39
+ it('should return true when retry count is below max', () => {
40
+ expect(testShouldRetry('./slice-path')).toBe(true);
41
+ });
42
+
43
+ it('should return false when retry count reaches max', () => {
44
+ setSliceRetryCount('./slice-path', 4);
45
+ expect(testShouldRetry('./slice-path')).toBe(false);
46
+ });
47
+ });
48
+
49
+ describe('pipeline structure', () => {
50
+ it('should have name kanban-full', () => {
51
+ const pipeline = createKanbanFullPipeline();
52
+ expect(pipeline.descriptor.name).toBe('kanban-full');
53
+ });
54
+
55
+ it('should have emit handlers for schema export', () => {
56
+ const pipeline = createKanbanFullPipeline();
57
+ const emitHandlers = pipeline.descriptor.handlers.filter((h): h is EmitHandlerDescriptor => h.type === 'emit');
58
+ const eventTypes = emitHandlers.map((h) => h.eventType);
59
+ expect(eventTypes).toContain('SchemaExported');
60
+ });
61
+
62
+ it('should have settled handler for slice checks', () => {
63
+ const pipeline = createKanbanFullPipeline();
64
+ const settledHandlers = pipeline.descriptor.handlers.filter((h) => h.type === 'settled');
65
+ expect(settledHandlers.length).toBeGreaterThan(0);
66
+ const commandTypes = settledHandlers[0].commandTypes;
67
+ expect(commandTypes).toContain('CheckTests');
68
+ expect(commandTypes).toContain('CheckTypes');
69
+ expect(commandTypes).toContain('CheckLint');
70
+ });
71
+
72
+ it('should have foreach-phased handler for client components', () => {
73
+ const pipeline = createKanbanFullPipeline();
74
+ const foreachHandlers = pipeline.descriptor.handlers.filter((h) => h.type === 'foreach-phased');
75
+ expect(foreachHandlers.length).toBeGreaterThan(0);
76
+ });
77
+ });
78
+
79
+ describe('command data shapes', () => {
80
+ function findEmitCommand(
81
+ pipeline: ReturnType<typeof createKanbanFullPipeline>,
82
+ eventType: string,
83
+ commandType: string,
84
+ ) {
85
+ const emitHandlers = pipeline.descriptor.handlers.filter(
86
+ (h): h is EmitHandlerDescriptor => h.type === 'emit' && h.eventType === eventType,
87
+ );
88
+ for (const handler of emitHandlers) {
89
+ const cmd = handler.commands.find((c) => c.commandType === commandType);
90
+ if (cmd) return cmd;
91
+ }
92
+ return undefined;
93
+ }
94
+
95
+ it('should emit GenerateServer with modelPath and destination', () => {
96
+ const pipeline = createKanbanFullPipeline();
97
+ const cmd = findEmitCommand(pipeline, 'SchemaExported', 'GenerateServer');
98
+ expect(cmd).toBeDefined();
99
+ const data =
100
+ typeof cmd?.data === 'function'
101
+ ? cmd.data({
102
+ type: 'SchemaExported',
103
+ data: { outputPath: './.context/schema.json', directory: '.' },
104
+ })
105
+ : cmd?.data;
106
+ expect(data).toEqual({
107
+ modelPath: './.context/schema.json',
108
+ destination: '.',
109
+ });
110
+ });
111
+
112
+ it('should emit ImplementSlice with slicePath, context, and aiOptions', () => {
113
+ const pipeline = createKanbanFullPipeline();
114
+ const cmd = findEmitCommand(pipeline, 'SliceGenerated', 'ImplementSlice');
115
+ expect(cmd).toBeDefined();
116
+ const data =
117
+ typeof cmd?.data === 'function'
118
+ ? cmd.data({ type: 'SliceGenerated', data: { slicePath: './adds-todo' } })
119
+ : cmd?.data;
120
+ expect(data).toEqual({
121
+ slicePath: './adds-todo',
122
+ context: { previousOutputs: 'errors', attemptNumber: 0 },
123
+ aiOptions: { maxTokens: 2000 },
124
+ });
125
+ });
126
+
127
+ it('should emit CheckTests with targetDirectory and scope', () => {
128
+ const pipeline = createKanbanFullPipeline();
129
+ const cmd = findEmitCommand(pipeline, 'SliceImplemented', 'CheckTests');
130
+ expect(cmd).toBeDefined();
131
+ const data =
132
+ typeof cmd?.data === 'function'
133
+ ? cmd.data({ type: 'SliceImplemented', data: { slicePath: './adds-todo' } })
134
+ : cmd?.data;
135
+ expect(data).toEqual({
136
+ targetDirectory: './adds-todo',
137
+ scope: 'slice',
138
+ });
139
+ });
140
+
141
+ it('should emit CheckTypes with targetDirectory and scope', () => {
142
+ const pipeline = createKanbanFullPipeline();
143
+ const cmd = findEmitCommand(pipeline, 'SliceImplemented', 'CheckTypes');
144
+ expect(cmd).toBeDefined();
145
+ const data =
146
+ typeof cmd?.data === 'function'
147
+ ? cmd.data({ type: 'SliceImplemented', data: { slicePath: './adds-todo' } })
148
+ : cmd?.data;
149
+ expect(data).toEqual({
150
+ targetDirectory: './adds-todo',
151
+ scope: 'slice',
152
+ });
153
+ });
154
+
155
+ it('should emit CheckLint with targetDirectory, scope, and fix', () => {
156
+ const pipeline = createKanbanFullPipeline();
157
+ const cmd = findEmitCommand(pipeline, 'SliceImplemented', 'CheckLint');
158
+ expect(cmd).toBeDefined();
159
+ const data =
160
+ typeof cmd?.data === 'function'
161
+ ? cmd.data({ type: 'SliceImplemented', data: { slicePath: './adds-todo' } })
162
+ : cmd?.data;
163
+ expect(data).toEqual({
164
+ targetDirectory: './adds-todo',
165
+ scope: 'slice',
166
+ fix: true,
167
+ });
168
+ });
169
+
170
+ it('should emit GenerateIA with modelPath and outputDir', () => {
171
+ const pipeline = createKanbanFullPipeline();
172
+ const cmd = findEmitCommand(pipeline, 'ServerGenerated', 'GenerateIA');
173
+ expect(cmd).toBeDefined();
174
+ const data = typeof cmd?.data === 'function' ? cmd.data({ type: 'ServerGenerated', data: {} }) : cmd?.data;
175
+ expect(data).toEqual({
176
+ modelPath: './.context/schema.json',
177
+ outputDir: './.context',
178
+ });
179
+ });
180
+
181
+ it('should emit StartServer with serverDirectory', () => {
182
+ const pipeline = createKanbanFullPipeline();
183
+ const cmd = findEmitCommand(pipeline, 'ServerGenerated', 'StartServer');
184
+ expect(cmd).toBeDefined();
185
+ const data = typeof cmd?.data === 'function' ? cmd.data({ type: 'ServerGenerated', data: {} }) : cmd?.data;
186
+ expect(data).toEqual({
187
+ serverDirectory: './server',
188
+ });
189
+ });
190
+
191
+ it('should emit GenerateClient with all paths', () => {
192
+ const pipeline = createKanbanFullPipeline();
193
+ const cmd = findEmitCommand(pipeline, 'IAGenerated', 'GenerateClient');
194
+ expect(cmd).toBeDefined();
195
+ const data = typeof cmd?.data === 'function' ? cmd.data({ type: 'IAGenerated', data: {} }) : cmd?.data;
196
+ expect(data).toEqual({
197
+ targetDir: './client',
198
+ iaSchemaPath: './.context/auto-ia-scheme.json',
199
+ gqlSchemaPath: './.context/schema.graphql',
200
+ figmaVariablesPath: './.context/figma-file.json',
201
+ });
202
+ });
203
+ });
204
+
205
+ describe('ClientGenerated with StartClient', () => {
206
+ it('should emit StartClient when ClientGenerated has valid components', () => {
207
+ const pipeline = createKanbanFullPipeline();
208
+ const emitHandlers = pipeline.descriptor.handlers.filter(
209
+ (h): h is EmitHandlerDescriptor =>
210
+ h.type === 'emit' && h.eventType === 'ClientGenerated' && h.predicate !== undefined,
211
+ );
212
+ const startClientHandler = emitHandlers.find((h) => h.commands.some((c) => c.commandType === 'StartClient'));
213
+ expect(startClientHandler).toBeDefined();
214
+ const startClientCmd = startClientHandler?.commands.find((c) => c.commandType === 'StartClient');
215
+ expect(startClientCmd).toBeDefined();
216
+ const data =
217
+ typeof startClientCmd?.data === 'function'
218
+ ? startClientCmd.data({ type: 'ClientGenerated', data: { components: [], targetDir: './client' } })
219
+ : startClientCmd?.data;
220
+ expect(data).toEqual({ clientDirectory: './client' });
221
+ });
222
+
223
+ it('should have predicate for StartClient that checks valid components', () => {
224
+ const pipeline = createKanbanFullPipeline();
225
+ const emitHandlers = pipeline.descriptor.handlers.filter(
226
+ (h): h is EmitHandlerDescriptor =>
227
+ h.type === 'emit' && h.eventType === 'ClientGenerated' && h.predicate !== undefined,
228
+ );
229
+ const startClientHandler = emitHandlers.find((h) => h.commands.some((c) => c.commandType === 'StartClient'));
230
+ const predicate = startClientHandler?.predicate;
231
+ expect(
232
+ predicate?.({ type: 'ClientGenerated', data: { components: [{ type: 'molecule', filePath: 'x' }] } }),
233
+ ).toBe(true);
234
+ expect(predicate?.({ type: 'ClientGenerated', data: { components: [] } })).toBe(false);
235
+ });
236
+ });
237
+
238
+ describe('ClientGenerated edge cases', () => {
239
+ it('should have predicate on foreach-phased handler to check for valid components', () => {
240
+ const pipeline = createKanbanFullPipeline();
241
+ const foreachHandler = pipeline.descriptor.handlers.find(
242
+ (h): h is ForEachPhasedDescriptor => h.type === 'foreach-phased' && h.eventType === 'ClientGenerated',
243
+ );
244
+ expect(foreachHandler).toBeDefined();
245
+ expect(foreachHandler?.predicate).toBeDefined();
246
+ });
247
+
248
+ it('should predicate return true when components array has items', () => {
249
+ const pipeline = createKanbanFullPipeline();
250
+ const foreachHandler = pipeline.descriptor.handlers.find(
251
+ (h): h is ForEachPhasedDescriptor => h.type === 'foreach-phased' && h.eventType === 'ClientGenerated',
252
+ );
253
+ const predicate = foreachHandler?.predicate;
254
+ const eventWithComponents = {
255
+ type: 'ClientGenerated',
256
+ data: {
257
+ components: [{ type: 'molecule', filePath: 'src/Foo.tsx' }],
258
+ targetDir: './client',
259
+ },
260
+ };
261
+ expect(predicate?.(eventWithComponents)).toBe(true);
262
+ });
263
+
264
+ it('should predicate return false when data is null', () => {
265
+ const pipeline = createKanbanFullPipeline();
266
+ const foreachHandler = pipeline.descriptor.handlers.find(
267
+ (h): h is ForEachPhasedDescriptor => h.type === 'foreach-phased' && h.eventType === 'ClientGenerated',
268
+ );
269
+ const predicate = foreachHandler?.predicate;
270
+ const eventWithNullData = { type: 'ClientGenerated', data: null as unknown as Record<string, unknown> };
271
+ expect(predicate?.(eventWithNullData)).toBe(false);
272
+ });
273
+
274
+ it('should predicate return false when components is not an array', () => {
275
+ const pipeline = createKanbanFullPipeline();
276
+ const foreachHandler = pipeline.descriptor.handlers.find(
277
+ (h): h is ForEachPhasedDescriptor => h.type === 'foreach-phased' && h.eventType === 'ClientGenerated',
278
+ );
279
+ const predicate = foreachHandler?.predicate;
280
+ const eventWithInvalidComponents = { type: 'ClientGenerated', data: { components: 'not-array' } };
281
+ expect(predicate?.(eventWithInvalidComponents)).toBe(false);
282
+ });
283
+
284
+ it('should predicate return false when components array is empty', () => {
285
+ const pipeline = createKanbanFullPipeline();
286
+ const foreachHandler = pipeline.descriptor.handlers.find(
287
+ (h): h is ForEachPhasedDescriptor => h.type === 'foreach-phased' && h.eventType === 'ClientGenerated',
288
+ );
289
+ const predicate = foreachHandler?.predicate;
290
+ const eventWithEmptyComponents = { type: 'ClientGenerated', data: { components: [] } };
291
+ expect(predicate?.(eventWithEmptyComponents)).toBe(false);
292
+ });
293
+
294
+ it('should have fallback emit handler for invalid ClientGenerated data', () => {
295
+ const pipeline = createKanbanFullPipeline();
296
+ const fallbackHandler = pipeline.descriptor.handlers.find(
297
+ (h): h is EmitHandlerDescriptor =>
298
+ h.type === 'emit' &&
299
+ h.eventType === 'ClientGenerated' &&
300
+ h.predicate !== undefined &&
301
+ h.commands.some((c) => c.commandType === 'ImplementComponent'),
302
+ );
303
+ expect(fallbackHandler).toBeDefined();
304
+ const fallbackPredicate = fallbackHandler?.predicate;
305
+ expect(fallbackPredicate?.({ type: 'ClientGenerated', data: null as unknown as Record<string, unknown> })).toBe(
306
+ true,
307
+ );
308
+ expect(fallbackPredicate?.({ type: 'ClientGenerated', data: { components: [] } })).toBe(true);
309
+ });
310
+
311
+ it('should fallback handler emit ImplementComponent with example data', () => {
312
+ const pipeline = createKanbanFullPipeline();
313
+ const fallbackHandler = pipeline.descriptor.handlers.find(
314
+ (h): h is EmitHandlerDescriptor =>
315
+ h.type === 'emit' &&
316
+ h.eventType === 'ClientGenerated' &&
317
+ h.predicate !== undefined &&
318
+ h.commands.some((c) => c.commandType === 'ImplementComponent'),
319
+ );
320
+ const implementCmd = fallbackHandler?.commands.find((c) => c.commandType === 'ImplementComponent');
321
+ expect(implementCmd).toBeDefined();
322
+ const data =
323
+ typeof implementCmd?.data === 'function'
324
+ ? implementCmd.data({ type: 'ClientGenerated', data: null as unknown as Record<string, unknown> })
325
+ : implementCmd?.data;
326
+ expect(data).toEqual({
327
+ projectDir: './client',
328
+ iaSchemeDir: './.context',
329
+ designSystemPath: './.context/design-system.md',
330
+ componentType: 'molecule',
331
+ filePath: 'client/src/components/molecules/Example.tsx',
332
+ componentName: 'Example.tsx',
333
+ aiOptions: { maxTokens: 3000 },
334
+ });
335
+ });
336
+ });
337
+ });
@@ -0,0 +1,225 @@
1
+ import type { Event } from '@auto-engineer/message-bus';
2
+ import { define } from '../../builder/define';
3
+
4
+ interface Component {
5
+ type: 'molecule' | 'organism' | 'page';
6
+ filePath: string;
7
+ }
8
+
9
+ interface SchemaExportedData {
10
+ directory: string;
11
+ outputPath: string;
12
+ }
13
+
14
+ interface SliceGeneratedData {
15
+ slicePath: string;
16
+ }
17
+
18
+ interface SliceImplementedData {
19
+ slicePath: string;
20
+ }
21
+
22
+ interface ClientGeneratedData {
23
+ components: Component[];
24
+ targetDir: string;
25
+ }
26
+
27
+ interface CheckEventData {
28
+ targetDirectory?: string;
29
+ errors?: string;
30
+ }
31
+
32
+ const MAX_RETRIES = 4;
33
+ const sliceRetryState = new Map<string, number>();
34
+ let projectRoot = '';
35
+
36
+ function hasAnyFailures(events: Event[]): boolean {
37
+ return events.some((e) => e.type.includes('Failed'));
38
+ }
39
+
40
+ function collectErrors(events: Event[]): string {
41
+ return events
42
+ .filter((e) => e.type.includes('Failed'))
43
+ .map((e) => (e.data as CheckEventData).errors ?? '')
44
+ .filter((s) => s.length > 0)
45
+ .join('\n');
46
+ }
47
+
48
+ function extractSlicePath(events: Record<string, Event[]>): string {
49
+ const firstEvent = events.CheckTests?.[0] ?? events.CheckTypes?.[0] ?? events.CheckLint?.[0];
50
+ const data = firstEvent?.data as CheckEventData | undefined;
51
+ return data?.targetDirectory ?? '';
52
+ }
53
+
54
+ function gatherAllCheckEvents(events: Record<string, Event[]>): Event[] {
55
+ return [...(events.CheckTests ?? []), ...(events.CheckTypes ?? []), ...(events.CheckLint ?? [])];
56
+ }
57
+
58
+ function shouldRetry(slicePath: string): boolean {
59
+ const attempts = sliceRetryState.get(slicePath) ?? 0;
60
+ return attempts < MAX_RETRIES;
61
+ }
62
+
63
+ function incrementRetryCount(slicePath: string): number {
64
+ const attempts = sliceRetryState.get(slicePath) ?? 0;
65
+ sliceRetryState.set(slicePath, attempts + 1);
66
+ return attempts + 1;
67
+ }
68
+
69
+ function hasValidComponents(e: { data: ClientGeneratedData | null }): boolean {
70
+ return e.data !== null && Array.isArray(e.data.components) && e.data.components.length > 0;
71
+ }
72
+
73
+ function hasInvalidComponents(e: { data: ClientGeneratedData | null }): boolean {
74
+ return !hasValidComponents(e);
75
+ }
76
+
77
+ function resolvePath(relativePath: string): string {
78
+ if (projectRoot === '') {
79
+ return relativePath;
80
+ }
81
+ if (relativePath.startsWith('/')) {
82
+ return relativePath;
83
+ }
84
+ if (relativePath.startsWith('./')) {
85
+ return `${projectRoot}/${relativePath.slice(2)}`;
86
+ }
87
+ return `${projectRoot}/${relativePath}`;
88
+ }
89
+
90
+ export function createKanbanFullPipeline() {
91
+ return define('kanban-full')
92
+ .on('SchemaExported')
93
+ .emit('GenerateServer', (e: { data: SchemaExportedData }) => {
94
+ projectRoot = e.data.directory;
95
+ return {
96
+ modelPath: e.data.outputPath,
97
+ destination: e.data.directory,
98
+ };
99
+ })
100
+
101
+ .on('SliceGenerated')
102
+ .emit('ImplementSlice', (e: { data: SliceGeneratedData }) => ({
103
+ slicePath: resolvePath(e.data.slicePath),
104
+ context: { previousOutputs: 'errors', attemptNumber: 0 },
105
+ aiOptions: { maxTokens: 2000 },
106
+ }))
107
+
108
+ .on('SliceImplemented')
109
+ .emit('CheckTests', (e: { data: SliceImplementedData }) => ({
110
+ targetDirectory: e.data.slicePath,
111
+ scope: 'slice',
112
+ }))
113
+ .emit('CheckTypes', (e: { data: SliceImplementedData }) => ({
114
+ targetDirectory: e.data.slicePath,
115
+ scope: 'slice',
116
+ }))
117
+ .emit('CheckLint', (e: { data: SliceImplementedData }) => ({
118
+ targetDirectory: e.data.slicePath,
119
+ scope: 'slice',
120
+ fix: true,
121
+ }))
122
+
123
+ .settled(['CheckTests', 'CheckTypes', 'CheckLint'])
124
+ .dispatch({ dispatches: ['ImplementSlice'] }, (events, send) => {
125
+ const allEvents = gatherAllCheckEvents(events);
126
+
127
+ if (!hasAnyFailures(allEvents)) {
128
+ const slicePath = extractSlicePath(events);
129
+ sliceRetryState.delete(slicePath);
130
+ return;
131
+ }
132
+
133
+ const slicePath = extractSlicePath(events);
134
+
135
+ if (!shouldRetry(slicePath)) {
136
+ sliceRetryState.delete(slicePath);
137
+ return;
138
+ }
139
+
140
+ const retryAttempt = incrementRetryCount(slicePath);
141
+ send('ImplementSlice', {
142
+ slicePath,
143
+ context: { previousOutputs: collectErrors(allEvents), attemptNumber: retryAttempt },
144
+ aiOptions: { maxTokens: 2000 },
145
+ });
146
+ return { persist: true };
147
+ })
148
+
149
+ .on('ServerGenerated')
150
+ .emit('GenerateIA', () => ({
151
+ modelPath: resolvePath('./.context/schema.json'),
152
+ outputDir: resolvePath('./.context'),
153
+ }))
154
+ .emit('StartServer', () => ({
155
+ serverDirectory: resolvePath('./server'),
156
+ }))
157
+
158
+ .on('IAGenerated')
159
+ .emit('GenerateClient', () => ({
160
+ targetDir: resolvePath('./client'),
161
+ iaSchemaPath: resolvePath('./.context/auto-ia-scheme.json'),
162
+ gqlSchemaPath: resolvePath('./.context/schema.graphql'),
163
+ figmaVariablesPath: resolvePath('./.context/figma-file.json'),
164
+ }))
165
+
166
+ .on('ClientGenerated')
167
+ .when(hasValidComponents)
168
+ .emit('StartClient', () => ({
169
+ clientDirectory: resolvePath('./client'),
170
+ }))
171
+
172
+ .on('ClientGenerated')
173
+ .when(hasInvalidComponents)
174
+ .emit('ImplementComponent', () => ({
175
+ projectDir: resolvePath('./client'),
176
+ iaSchemeDir: resolvePath('./.context'),
177
+ designSystemPath: resolvePath('./.context/design-system.md'),
178
+ componentType: 'molecule',
179
+ filePath: resolvePath('client/src/components/molecules/Example.tsx'),
180
+ componentName: 'Example.tsx',
181
+ aiOptions: { maxTokens: 3000 },
182
+ }))
183
+
184
+ .on('ClientGenerated')
185
+ .when(hasValidComponents)
186
+ .forEach((e: { data: ClientGeneratedData }) => e.data.components)
187
+ .groupInto(['molecule', 'organism', 'page'], (c) => c.type)
188
+ .process('ImplementComponent', (c: Component) => ({
189
+ projectDir: resolvePath('./client'),
190
+ iaSchemeDir: resolvePath('./.context'),
191
+ designSystemPath: resolvePath('./.context/design-system.md'),
192
+ componentType: c.type ?? 'molecule',
193
+ filePath: resolvePath(c.filePath ?? ''),
194
+ componentName: (c.filePath ?? '').split('/').pop()?.replace('.tsx', '') ?? '',
195
+ aiOptions: { maxTokens: 3000 },
196
+ }))
197
+ .onComplete({
198
+ success: 'AllComponentsImplemented',
199
+ failure: 'ComponentsFailed',
200
+ itemKey: (e) => (e.data as { filePath?: string }).filePath ?? '',
201
+ })
202
+
203
+ .build();
204
+ }
205
+
206
+ export function resetKanbanState(): void {
207
+ sliceRetryState.clear();
208
+ projectRoot = '';
209
+ }
210
+
211
+ export function setProjectRoot(root: string): void {
212
+ projectRoot = root;
213
+ }
214
+
215
+ export function testResolvePath(relativePath: string): string {
216
+ return resolvePath(relativePath);
217
+ }
218
+
219
+ export function setSliceRetryCount(slicePath: string, count: number): void {
220
+ sliceRetryState.set(slicePath, count);
221
+ }
222
+
223
+ export function testShouldRetry(slicePath: string): boolean {
224
+ return shouldRetry(slicePath);
225
+ }
@@ -0,0 +1,19 @@
1
+ import { pipelineConfig } from '../../config/pipeline-config';
2
+ import { createKanbanFullPipeline } from './kanban-full.pipeline';
3
+
4
+ export default pipelineConfig({
5
+ plugins: [
6
+ '@auto-engineer/server-checks',
7
+ '@auto-engineer/design-system-importer',
8
+ '@auto-engineer/server-generator-apollo-emmett',
9
+ '@auto-engineer/narrative',
10
+ '@auto-engineer/frontend-checks',
11
+ '@auto-engineer/frontend-implementer',
12
+ '@auto-engineer/component-implementer',
13
+ '@auto-engineer/information-architect',
14
+ '@auto-engineer/frontend-generator-react-graphql',
15
+ '@auto-engineer/server-implementer',
16
+ '@auto-engineer/dev-server',
17
+ ],
18
+ pipeline: createKanbanFullPipeline(),
19
+ });
@@ -0,0 +1,33 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { createKanbanPipeline, resetRetryState, setRetryCount, testShouldRetry } from './kanban.pipeline';
3
+
4
+ describe('kanban.pipeline', () => {
5
+ beforeEach(() => {
6
+ resetRetryState();
7
+ });
8
+
9
+ describe('shouldRetry', () => {
10
+ it('should return true when retry count is below max', () => {
11
+ expect(testShouldRetry('./slice-path')).toBe(true);
12
+ });
13
+
14
+ it('should return false when retry count reaches max', () => {
15
+ setRetryCount('./slice-path', 3);
16
+ expect(testShouldRetry('./slice-path')).toBe(false);
17
+ });
18
+ });
19
+
20
+ describe('pipeline structure', () => {
21
+ it('should have name kanban', () => {
22
+ const pipeline = createKanbanPipeline();
23
+ expect(pipeline.descriptor.name).toBe('kanban');
24
+ });
25
+
26
+ it('should have handlers for slice workflow', () => {
27
+ const pipeline = createKanbanPipeline();
28
+ const eventTypes = pipeline.descriptor.handlers.filter((h) => h.type === 'emit').map((h) => h.eventType);
29
+ expect(eventTypes).toContain('SchemaExported');
30
+ expect(eventTypes).toContain('SliceGenerated');
31
+ });
32
+ });
33
+ });