@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,193 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { config } from 'dotenv';
5
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
6
+ import { loadPipelineConfig } from '../config/pipeline-config';
7
+ import { PipelineServer } from '../server/pipeline-server';
8
+ import { resetKanbanState } from './fixtures/kanban-full.pipeline';
9
+ import kanbanTodoConfig from './fixtures/kanban-todo.config';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const PIPELINE_ROOT = path.resolve(__dirname, '../..');
13
+ const PACKAGES_DIR = path.resolve(__dirname, '../../..');
14
+ const EXAMPLE_DIR = path.resolve(PACKAGES_DIR, '../examples/kanban-todo');
15
+ const CLI_SNAPSHOTS_DIR = path.join(PACKAGES_DIR, 'cli/src/__tests__/e2e/__snapshots__/kanban-todo');
16
+
17
+ config({ path: path.join(PIPELINE_ROOT, '.env') });
18
+
19
+ interface StoredMessage {
20
+ message: {
21
+ type: string;
22
+ data: Record<string, unknown>;
23
+ };
24
+ position: string;
25
+ revision: string;
26
+ messageType: string;
27
+ }
28
+
29
+ interface CliEventSnapshot {
30
+ type: string;
31
+ data: Record<string, unknown>;
32
+ position: string;
33
+ }
34
+
35
+ function loadCliEventStream(): CliEventSnapshot[] | null {
36
+ const snapshotPath = path.join(CLI_SNAPSHOTS_DIR, 'event-stream.snapshot.json');
37
+ if (!existsSync(snapshotPath)) {
38
+ console.log(`Event stream snapshot not found: ${snapshotPath}`);
39
+ return null;
40
+ }
41
+ const content = readFileSync(snapshotPath, 'utf-8');
42
+ return JSON.parse(content) as CliEventSnapshot[];
43
+ }
44
+
45
+ async function waitForPipelineCompletion(
46
+ server: PipelineServer,
47
+ timeoutMs: number,
48
+ pollIntervalMs: number,
49
+ ): Promise<StoredMessage[]> {
50
+ const startTime = Date.now();
51
+ let lastMessageCount = 0;
52
+ let stableCount = 0;
53
+ const stableThreshold = 15;
54
+
55
+ while (Date.now() - startTime < timeoutMs) {
56
+ const response = await fetch(`http://localhost:${server.port}/messages`);
57
+ const messages = (await response.json()) as StoredMessage[];
58
+
59
+ if (messages.length === lastMessageCount) {
60
+ stableCount++;
61
+ if (stableCount >= stableThreshold) {
62
+ console.log(` Pipeline stable after ${stableThreshold} polls`);
63
+ return messages;
64
+ }
65
+ } else {
66
+ if (stableCount > 0) {
67
+ console.log(` New activity after ${stableCount} stable polls`);
68
+ }
69
+ stableCount = 0;
70
+ lastMessageCount = messages.length;
71
+ console.log(` Messages: ${messages.length}`);
72
+ }
73
+
74
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
75
+ }
76
+
77
+ console.log(` Timeout reached after ${timeoutMs}ms`);
78
+ const response = await fetch(`http://localhost:${server.port}/messages`);
79
+ return (await response.json()) as StoredMessage[];
80
+ }
81
+
82
+ describe('Real Execution E2E', () => {
83
+ let server: PipelineServer;
84
+
85
+ beforeAll(async () => {
86
+ const loaded = await loadPipelineConfig(kanbanTodoConfig);
87
+
88
+ console.log('=== Environment Check ===');
89
+ console.log(`ANTHROPIC_API_KEY set: ${process.env.ANTHROPIC_API_KEY !== undefined}`);
90
+ console.log(`Example directory: ${EXAMPLE_DIR}`);
91
+ console.log(`Example exists: ${existsSync(EXAMPLE_DIR)}`);
92
+ console.log(`Handlers loaded: ${loaded.handlers.length}`);
93
+ console.log(`Handler names: ${loaded.handlers.map((h) => h.name).join(', ')}`);
94
+
95
+ server = new PipelineServer({ port: 0 });
96
+ server.registerCommandHandlers(loaded.handlers);
97
+ server.registerPipeline(loaded.pipeline);
98
+ await server.start();
99
+
100
+ console.log(`Server started on port ${server.port}`);
101
+ }, 60000);
102
+
103
+ afterAll(async () => {
104
+ await server.stop();
105
+ });
106
+
107
+ beforeEach(() => {
108
+ resetKanbanState();
109
+ });
110
+
111
+ describe('Full Pipeline Execution', () => {
112
+ it(
113
+ 'should execute ExportSchema and trigger full pipeline chain',
114
+ async () => {
115
+ console.log('\n=== Dispatching ExportSchema ===');
116
+ console.log(`Target directory: ${EXAMPLE_DIR}`);
117
+
118
+ const dispatchResponse = await fetch(`http://localhost:${server.port}/command`, {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: JSON.stringify({
122
+ type: 'ExportSchema',
123
+ data: {
124
+ directory: EXAMPLE_DIR,
125
+ },
126
+ }),
127
+ });
128
+
129
+ const dispatchResult = (await dispatchResponse.json()) as { status: string; commandId: string };
130
+ console.log(`Dispatch response: ${JSON.stringify(dispatchResult)}`);
131
+ expect(dispatchResult.status).toBe('ack');
132
+
133
+ console.log('\n=== Waiting for pipeline completion (5 minute timeout) ===');
134
+ const messages = await waitForPipelineCompletion(server, 5 * 60 * 1000, 2000);
135
+
136
+ console.log(`\n=== Pipeline Results ===`);
137
+ console.log(`Total messages: ${messages.length}`);
138
+
139
+ const events = messages.filter((m) => m.messageType === 'event');
140
+ const commands = messages.filter((m) => m.messageType === 'command');
141
+
142
+ console.log(`Events: ${events.length}`);
143
+ console.log(`Commands: ${commands.length}`);
144
+
145
+ console.log('\n=== Event Types (in order) ===');
146
+ events.forEach((e, i) => {
147
+ console.log(` ${i + 1}. ${e.message.type}`);
148
+ if (e.message.type.includes('Failed')) {
149
+ console.log(` Error: ${JSON.stringify(e.message.data)}`);
150
+ }
151
+ });
152
+
153
+ console.log('\n=== Command Types (in order) ===');
154
+ commands.forEach((c, i) => {
155
+ console.log(` ${i + 1}. ${c.message.type}`);
156
+ });
157
+
158
+ const cliEvents = loadCliEventStream();
159
+ if (cliEvents !== null) {
160
+ console.log('\n=== CLI Event Types (for comparison) ===');
161
+ const cliEventTypes = [...new Set(cliEvents.map((e) => e.type))];
162
+ cliEventTypes.forEach((t) => {
163
+ const count = cliEvents.filter((e) => e.type === t).length;
164
+ console.log(` ${t}: ${count}`);
165
+ });
166
+
167
+ console.log('\n=== Pipeline Event Types (for comparison) ===');
168
+ const pipelineEventTypes = [...new Set(events.map((e) => e.message.type))];
169
+ pipelineEventTypes.forEach((t) => {
170
+ const count = events.filter((e) => e.message.type === t).length;
171
+ console.log(` ${t}: ${count}`);
172
+ });
173
+
174
+ console.log('\n=== Event Type Comparison ===');
175
+ const cliTypeSet = new Set(cliEventTypes);
176
+ const pipelineTypeSet = new Set(pipelineEventTypes);
177
+
178
+ const missingFromPipeline = cliEventTypes.filter((t) => !pipelineTypeSet.has(t));
179
+ const extraInPipeline = pipelineEventTypes.filter((t) => !cliTypeSet.has(t));
180
+
181
+ console.log(`Missing from pipeline: ${missingFromPipeline.join(', ') || 'none'}`);
182
+ console.log(`Extra in pipeline: ${extraInPipeline.join(', ') || 'none'}`);
183
+ }
184
+
185
+ expect(events.length).toBeGreaterThan(0);
186
+
187
+ const hasSchemaExported = events.some((e) => e.message.type === 'SchemaExported');
188
+ expect(hasSchemaExported).toBe(true);
189
+ },
190
+ 10 * 60 * 1000,
191
+ );
192
+ });
193
+ });
@@ -0,0 +1,94 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { loadPipelineConfig } from '../config/pipeline-config';
3
+ import { PipelineServer } from '../server/pipeline-server';
4
+ import { resetKanbanState } from './fixtures/kanban-full.pipeline';
5
+ import kanbanTodoConfig from './fixtures/kanban-todo.config';
6
+
7
+ describe('Real Plugin E2E', () => {
8
+ beforeEach(() => {
9
+ resetKanbanState();
10
+ });
11
+
12
+ describe('Load and Run Pipeline Config', () => {
13
+ it('should load handlers from config and register them', async () => {
14
+ const loaded = await loadPipelineConfig(kanbanTodoConfig);
15
+
16
+ console.log('=== Loaded Handlers ===');
17
+ console.log(`Total handlers loaded: ${loaded.handlers.length}`);
18
+ console.log('Handler names:', loaded.handlers.map((h) => h.name).join(', '));
19
+
20
+ expect(loaded.handlers.length).toBeGreaterThan(0);
21
+ expect(loaded.pipeline.descriptor.name).toBe('kanban-full');
22
+ });
23
+
24
+ it('should start server with real config', async () => {
25
+ const loaded = await loadPipelineConfig(kanbanTodoConfig);
26
+
27
+ const server = new PipelineServer({ port: 0 });
28
+ server.registerCommandHandlers(loaded.handlers);
29
+ server.registerPipeline(loaded.pipeline);
30
+ await server.start();
31
+
32
+ const registeredCommands = server.getRegisteredCommands();
33
+ console.log('=== Registered Commands ===');
34
+ registeredCommands.forEach((cmd) => console.log(` - ${cmd}`));
35
+
36
+ expect(registeredCommands.length).toBe(loaded.handlers.length);
37
+
38
+ await server.stop();
39
+ });
40
+
41
+ it('should serve pipeline graph via API', async () => {
42
+ const loaded = await loadPipelineConfig(kanbanTodoConfig);
43
+
44
+ const server = new PipelineServer({ port: 0 });
45
+ server.registerCommandHandlers(loaded.handlers);
46
+ server.registerPipeline(loaded.pipeline);
47
+ await server.start();
48
+
49
+ const response = await fetch(`http://localhost:${server.port}/pipeline`);
50
+ const text = await response.text();
51
+
52
+ expect(response.status).toBe(200);
53
+ expect(response.headers.get('content-type')).toContain('application/json');
54
+
55
+ const graph = JSON.parse(text) as {
56
+ nodes: Array<{ id: string; name: string }>;
57
+ edges: Array<{ from: string; to: string }>;
58
+ };
59
+
60
+ console.log('=== Pipeline Graph ===');
61
+ console.log(`Nodes: ${graph.nodes.length}`);
62
+ console.log(`Edges: ${graph.edges.length}`);
63
+
64
+ expect(graph.nodes.length).toBeGreaterThan(0);
65
+ expect(graph.edges.length).toBeGreaterThan(0);
66
+
67
+ await server.stop();
68
+ });
69
+
70
+ it('should serve registry via API', async () => {
71
+ const loaded = await loadPipelineConfig(kanbanTodoConfig);
72
+
73
+ const server = new PipelineServer({ port: 0 });
74
+ server.registerCommandHandlers(loaded.handlers);
75
+ server.registerPipeline(loaded.pipeline);
76
+ await server.start();
77
+
78
+ const response = await fetch(`http://localhost:${server.port}/registry`);
79
+ const registry = (await response.json()) as {
80
+ eventHandlers: string[];
81
+ commandHandlers: string[];
82
+ commandsWithMetadata: Array<{ name: string; alias: string; description: string }>;
83
+ };
84
+
85
+ console.log('=== Registry ===');
86
+ console.log('Event handlers:', registry.eventHandlers.join(', '));
87
+ console.log('Command handlers:', registry.commandHandlers.join(', '));
88
+
89
+ expect(registry.commandHandlers.length).toBeGreaterThan(0);
90
+
91
+ await server.stop();
92
+ });
93
+ });
94
+ });
@@ -0,0 +1,162 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
2
+ import { loadPipelineConfig } from '../config/pipeline-config';
3
+ import { PipelineServer } from '../server/pipeline-server';
4
+ import { resetKanbanState } from './fixtures/kanban-full.pipeline';
5
+ import kanbanTodoConfig from './fixtures/kanban-todo.config';
6
+
7
+ interface StoredMessage {
8
+ message: {
9
+ type: string;
10
+ data: Record<string, unknown>;
11
+ requestId?: string;
12
+ correlationId?: string;
13
+ };
14
+ messageType: 'command' | 'event';
15
+ position: string;
16
+ }
17
+
18
+ describe('Server Startup E2E', () => {
19
+ let server: PipelineServer;
20
+
21
+ beforeAll(async () => {
22
+ const loaded = await loadPipelineConfig(kanbanTodoConfig);
23
+ server = new PipelineServer({ port: 0 });
24
+ server.registerCommandHandlers(loaded.handlers);
25
+ server.registerPipeline(loaded.pipeline);
26
+ await server.start();
27
+ }, 30000);
28
+
29
+ afterAll(async () => {
30
+ await server.stop();
31
+ });
32
+
33
+ beforeEach(() => {
34
+ resetKanbanState();
35
+ });
36
+
37
+ describe('StartServer and StartClient handlers', () => {
38
+ it('should have StartServer handler registered', () => {
39
+ const commands = server.getRegisteredCommands();
40
+ expect(commands).toContain('StartServer');
41
+ });
42
+
43
+ it('should have StartClient handler registered', () => {
44
+ const commands = server.getRegisteredCommands();
45
+ expect(commands).toContain('StartClient');
46
+ });
47
+
48
+ it('should expose StartServer in registry API', async () => {
49
+ const response = await fetch(`http://localhost:${server.port}/registry`);
50
+ const registry = (await response.json()) as {
51
+ commandHandlers: string[];
52
+ commandsWithMetadata: Array<{ name: string; alias?: string; description?: string }>;
53
+ };
54
+
55
+ expect(registry.commandHandlers).toContain('StartServer');
56
+
57
+ const startServerMeta = registry.commandsWithMetadata.find((c) => c.name === 'StartServer');
58
+ expect(startServerMeta).toBeDefined();
59
+ expect(startServerMeta?.alias).toBe('start:server');
60
+ expect(startServerMeta?.description).toBe('Start the development server');
61
+ });
62
+
63
+ it('should expose StartClient in registry API', async () => {
64
+ const response = await fetch(`http://localhost:${server.port}/registry`);
65
+ const registry = (await response.json()) as {
66
+ commandHandlers: string[];
67
+ commandsWithMetadata: Array<{ name: string; alias?: string; description?: string }>;
68
+ };
69
+
70
+ expect(registry.commandHandlers).toContain('StartClient');
71
+
72
+ const startClientMeta = registry.commandsWithMetadata.find((c) => c.name === 'StartClient');
73
+ expect(startClientMeta).toBeDefined();
74
+ expect(startClientMeta?.alias).toBe('start:client');
75
+ expect(startClientMeta?.description).toBe('Start the development client');
76
+ });
77
+ });
78
+
79
+ describe('Pipeline routing for StartServer and StartClient', () => {
80
+ it('should have ServerGenerated -> StartServer edge in pipeline graph', async () => {
81
+ const response = await fetch(`http://localhost:${server.port}/pipeline`);
82
+ const graph = (await response.json()) as {
83
+ edges: Array<{ from: string; to: string }>;
84
+ eventToCommand: Record<string, string>;
85
+ };
86
+
87
+ const serverGeneratedEdge = graph.edges.find(
88
+ (e) => e.from === 'evt:ServerGenerated' && e.to === 'cmd:StartServer',
89
+ );
90
+ expect(serverGeneratedEdge).toBeDefined();
91
+ });
92
+
93
+ it('should have ClientGenerated -> StartClient edge in pipeline graph', async () => {
94
+ const response = await fetch(`http://localhost:${server.port}/pipeline`);
95
+ const graph = (await response.json()) as {
96
+ edges: Array<{ from: string; to: string }>;
97
+ eventToCommand: Record<string, string>;
98
+ };
99
+
100
+ const clientGeneratedEdge = graph.edges.find(
101
+ (e) => e.from === 'evt:ClientGenerated' && e.to === 'cmd:StartClient',
102
+ );
103
+ expect(clientGeneratedEdge).toBeDefined();
104
+ });
105
+ });
106
+
107
+ describe('StartServer command dispatch', () => {
108
+ it('should accept StartServer command and return ServerStartFailed for missing directory', async () => {
109
+ const response = await fetch(`http://localhost:${server.port}/command`, {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify({
113
+ type: 'StartServer',
114
+ data: { serverDirectory: '/nonexistent/path' },
115
+ }),
116
+ });
117
+
118
+ const result = (await response.json()) as { status: string; commandId: string };
119
+ expect(result.status).toBe('ack');
120
+
121
+ await new Promise((r) => setTimeout(r, 200));
122
+
123
+ const messagesResponse = await fetch(`http://localhost:${server.port}/messages`);
124
+ const messages = (await messagesResponse.json()) as StoredMessage[];
125
+
126
+ const serverEvent = messages.find(
127
+ (m) => m.message.type === 'ServerStarted' || m.message.type === 'ServerStartFailed',
128
+ );
129
+
130
+ expect(serverEvent).toBeDefined();
131
+ expect(serverEvent?.message.type).toBe('ServerStartFailed');
132
+ });
133
+ });
134
+
135
+ describe('StartClient command dispatch', () => {
136
+ it('should accept StartClient command and return ClientStartFailed for missing directory', async () => {
137
+ const response = await fetch(`http://localhost:${server.port}/command`, {
138
+ method: 'POST',
139
+ headers: { 'Content-Type': 'application/json' },
140
+ body: JSON.stringify({
141
+ type: 'StartClient',
142
+ data: { clientDirectory: '/nonexistent/path' },
143
+ }),
144
+ });
145
+
146
+ const result = (await response.json()) as { status: string; commandId: string };
147
+ expect(result.status).toBe('ack');
148
+
149
+ await new Promise((r) => setTimeout(r, 200));
150
+
151
+ const messagesResponse = await fetch(`http://localhost:${server.port}/messages`);
152
+ const messages = (await messagesResponse.json()) as StoredMessage[];
153
+
154
+ const clientEvent = messages.find(
155
+ (m) => m.message.type === 'ClientStarted' || m.message.type === 'ClientStartFailed',
156
+ );
157
+
158
+ expect(clientEvent).toBeDefined();
159
+ expect(clientEvent?.message.type).toBe('ClientStartFailed');
160
+ });
161
+ });
162
+ });
@@ -0,0 +1,136 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ compareEventSequence,
4
+ containsSubsequence,
5
+ findMissingEvents,
6
+ findUnexpectedEvents,
7
+ formatSnapshotDiff,
8
+ } from './snapshot-compare';
9
+
10
+ describe('snapshot-compare', () => {
11
+ describe('compareEventSequence', () => {
12
+ it('should match identical sequences', () => {
13
+ const result = compareEventSequence(['A', 'B', 'C'], ['A', 'B', 'C']);
14
+ expect(result.matches).toBe(true);
15
+ expect(result.differences).toHaveLength(0);
16
+ });
17
+
18
+ it('should detect mismatches', () => {
19
+ const result = compareEventSequence(['A', 'B', 'C'], ['A', 'X', 'C']);
20
+ expect(result.matches).toBe(false);
21
+ expect(result.differences).toHaveLength(1);
22
+ expect(result.differences[0]).toEqual({
23
+ type: 'mismatch',
24
+ index: 1,
25
+ expected: 'B',
26
+ actual: 'X',
27
+ });
28
+ });
29
+
30
+ it('should detect missing events', () => {
31
+ const result = compareEventSequence(['A', 'B', 'C'], ['A', 'B']);
32
+ expect(result.matches).toBe(false);
33
+ expect(result.differences).toContainEqual({
34
+ type: 'missing',
35
+ index: 2,
36
+ expected: 'C',
37
+ });
38
+ });
39
+
40
+ it('should detect extra events', () => {
41
+ const result = compareEventSequence(['A', 'B'], ['A', 'B', 'C']);
42
+ expect(result.matches).toBe(false);
43
+ expect(result.differences).toContainEqual({
44
+ type: 'extra',
45
+ index: 2,
46
+ actual: 'C',
47
+ });
48
+ });
49
+
50
+ it('should report counts', () => {
51
+ const result = compareEventSequence(['A', 'B', 'C'], ['A', 'B']);
52
+ expect(result.expectedCount).toBe(3);
53
+ expect(result.actualCount).toBe(2);
54
+ });
55
+ });
56
+
57
+ describe('containsSubsequence', () => {
58
+ it('should find exact subsequence', () => {
59
+ expect(containsSubsequence(['A', 'B', 'C', 'D'], ['B', 'C'])).toBe(true);
60
+ });
61
+
62
+ it('should find non-contiguous subsequence', () => {
63
+ expect(containsSubsequence(['A', 'B', 'C', 'D'], ['A', 'C'])).toBe(true);
64
+ });
65
+
66
+ it('should return false for missing subsequence', () => {
67
+ expect(containsSubsequence(['A', 'B', 'C'], ['X', 'Y'])).toBe(false);
68
+ });
69
+
70
+ it('should return true for empty subsequence', () => {
71
+ expect(containsSubsequence(['A', 'B'], [])).toBe(true);
72
+ });
73
+
74
+ it('should return false for empty sequence with non-empty subsequence', () => {
75
+ expect(containsSubsequence([], ['A'])).toBe(false);
76
+ });
77
+
78
+ it('should require order', () => {
79
+ expect(containsSubsequence(['A', 'B', 'C'], ['C', 'A'])).toBe(false);
80
+ });
81
+ });
82
+
83
+ describe('findMissingEvents', () => {
84
+ it('should find missing events', () => {
85
+ const missing = findMissingEvents(['A', 'B', 'C'], ['A', 'B', 'D']);
86
+ expect(missing).toEqual(['D']);
87
+ });
88
+
89
+ it('should return empty for all present', () => {
90
+ const missing = findMissingEvents(['A', 'B', 'C'], ['A', 'B']);
91
+ expect(missing).toEqual([]);
92
+ });
93
+ });
94
+
95
+ describe('findUnexpectedEvents', () => {
96
+ it('should find unexpected events', () => {
97
+ const unexpected = findUnexpectedEvents(['A', 'B', 'X'], ['A', 'B', 'C']);
98
+ expect(unexpected).toEqual(['X']);
99
+ });
100
+
101
+ it('should return empty when all allowed', () => {
102
+ const unexpected = findUnexpectedEvents(['A', 'B'], ['A', 'B', 'C']);
103
+ expect(unexpected).toEqual([]);
104
+ });
105
+ });
106
+
107
+ describe('formatSnapshotDiff', () => {
108
+ it('should format matching result', () => {
109
+ const result = compareEventSequence(['A', 'B'], ['A', 'B']);
110
+ const formatted = formatSnapshotDiff(result);
111
+ expect(formatted).toContain('✓');
112
+ expect(formatted).toContain('matches');
113
+ });
114
+
115
+ it('should format mismatching result', () => {
116
+ const result = compareEventSequence(['A', 'B'], ['A', 'X']);
117
+ const formatted = formatSnapshotDiff(result);
118
+ expect(formatted).toContain('✗');
119
+ expect(formatted).toContain('mismatch');
120
+ expect(formatted).toContain('Expected "B"');
121
+ expect(formatted).toContain('got "X"');
122
+ });
123
+
124
+ it('should format missing events', () => {
125
+ const result = compareEventSequence(['A', 'B', 'C'], ['A', 'B']);
126
+ const formatted = formatSnapshotDiff(result);
127
+ expect(formatted).toContain('Missing "C"');
128
+ });
129
+
130
+ it('should format extra events', () => {
131
+ const result = compareEventSequence(['A', 'B'], ['A', 'B', 'C']);
132
+ const formatted = formatSnapshotDiff(result);
133
+ expect(formatted).toContain('Unexpected "C"');
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,106 @@
1
+ export interface SnapshotDiff {
2
+ type: 'match' | 'mismatch' | 'missing' | 'extra';
3
+ index: number;
4
+ expected?: string;
5
+ actual?: string;
6
+ }
7
+
8
+ export interface SnapshotResult {
9
+ matches: boolean;
10
+ differences: SnapshotDiff[];
11
+ expectedCount: number;
12
+ actualCount: number;
13
+ }
14
+
15
+ export function compareEventSequence(expected: string[], actual: string[]): SnapshotResult {
16
+ const differences: SnapshotDiff[] = [];
17
+ const maxLen = Math.max(expected.length, actual.length);
18
+
19
+ for (let i = 0; i < maxLen; i++) {
20
+ const expectedItem = expected[i];
21
+ const actualItem = actual[i];
22
+
23
+ if (expectedItem === undefined) {
24
+ differences.push({
25
+ type: 'extra',
26
+ index: i,
27
+ actual: actualItem,
28
+ });
29
+ } else if (actualItem === undefined) {
30
+ differences.push({
31
+ type: 'missing',
32
+ index: i,
33
+ expected: expectedItem,
34
+ });
35
+ } else if (expectedItem !== actualItem) {
36
+ differences.push({
37
+ type: 'mismatch',
38
+ index: i,
39
+ expected: expectedItem,
40
+ actual: actualItem,
41
+ });
42
+ }
43
+ }
44
+
45
+ return {
46
+ matches: differences.length === 0,
47
+ differences,
48
+ expectedCount: expected.length,
49
+ actualCount: actual.length,
50
+ };
51
+ }
52
+
53
+ export function containsSubsequence(sequence: string[], subsequence: string[]): boolean {
54
+ if (subsequence.length === 0) return true;
55
+ if (sequence.length === 0) return false;
56
+
57
+ let subIndex = 0;
58
+ for (const item of sequence) {
59
+ if (item === subsequence[subIndex]) {
60
+ subIndex++;
61
+ if (subIndex === subsequence.length) {
62
+ return true;
63
+ }
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+
69
+ export function findMissingEvents(sequence: string[], required: string[]): string[] {
70
+ const present = new Set(sequence);
71
+ return required.filter((r) => !present.has(r));
72
+ }
73
+
74
+ export function findUnexpectedEvents(sequence: string[], allowed: string[]): string[] {
75
+ const allowedSet = new Set(allowed);
76
+ return sequence.filter((s) => !allowedSet.has(s));
77
+ }
78
+
79
+ export function formatSnapshotDiff(result: SnapshotResult): string {
80
+ if (result.matches) {
81
+ return `✓ Event sequence matches (${result.expectedCount} events)`;
82
+ }
83
+
84
+ const lines: string[] = [
85
+ `✗ Event sequence mismatch:`,
86
+ ` Expected: ${result.expectedCount} events`,
87
+ ` Actual: ${result.actualCount} events`,
88
+ ` Differences:`,
89
+ ];
90
+
91
+ for (const diff of result.differences) {
92
+ switch (diff.type) {
93
+ case 'mismatch':
94
+ lines.push(` [${diff.index}] Expected "${diff.expected}", got "${diff.actual}"`);
95
+ break;
96
+ case 'missing':
97
+ lines.push(` [${diff.index}] Missing "${diff.expected}"`);
98
+ break;
99
+ case 'extra':
100
+ lines.push(` [${diff.index}] Unexpected "${diff.actual}"`);
101
+ break;
102
+ }
103
+ }
104
+
105
+ return lines.join('\n');
106
+ }