@auto-engineer/pipeline 1.28.0 → 1.29.0

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 (53) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +5 -5
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +45 -0
  5. package/dist/src/builder/define.d.ts +8 -1
  6. package/dist/src/builder/define.d.ts.map +1 -1
  7. package/dist/src/builder/define.js +35 -0
  8. package/dist/src/builder/define.js.map +1 -1
  9. package/dist/src/core/descriptors.d.ts +7 -2
  10. package/dist/src/core/descriptors.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +2 -2
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js.map +1 -1
  14. package/dist/src/runtime/pipeline-runtime.js +1 -1
  15. package/dist/src/runtime/pipeline-runtime.js.map +1 -1
  16. package/dist/src/runtime/settled-tracker.d.ts +1 -1
  17. package/dist/src/runtime/settled-tracker.d.ts.map +1 -1
  18. package/dist/src/runtime/settled-tracker.js.map +1 -1
  19. package/dist/src/server/pipeline-server.d.ts.map +1 -1
  20. package/dist/src/server/pipeline-server.js +8 -6
  21. package/dist/src/server/pipeline-server.js.map +1 -1
  22. package/dist/src/testing/fixtures/kanban-full.pipeline.d.ts.map +1 -1
  23. package/dist/src/testing/fixtures/kanban-full.pipeline.js +0 -8
  24. package/dist/src/testing/fixtures/kanban-full.pipeline.js.map +1 -1
  25. package/dist/src/testing/fixtures/kanban-todo.config.d.ts.map +1 -1
  26. package/dist/src/testing/fixtures/kanban-todo.config.js +0 -1
  27. package/dist/src/testing/fixtures/kanban-todo.config.js.map +1 -1
  28. package/dist/src/testing/fixtures/kanban.pipeline.d.ts.map +1 -1
  29. package/dist/src/testing/fixtures/kanban.pipeline.js +0 -2
  30. package/dist/src/testing/fixtures/kanban.pipeline.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/ketchup-plan.md +4 -0
  33. package/package.json +3 -3
  34. package/scripts/run-kanban-e2e.ts +3 -3
  35. package/scripts/start-server.ts +1 -1
  36. package/src/builder/define.specs.ts +82 -5
  37. package/src/builder/define.ts +52 -1
  38. package/src/core/descriptors.ts +8 -2
  39. package/src/index.ts +2 -0
  40. package/src/plugins/plugin-loader.specs.ts +0 -7
  41. package/src/runtime/pipeline-runtime.ts +1 -1
  42. package/src/runtime/settled-tracker.ts +2 -2
  43. package/src/server/full-orchestration.e2e.specs.ts +4 -16
  44. package/src/server/pipeline-server.e2e.specs.ts +27 -33
  45. package/src/server/pipeline-server.specs.ts +41 -6
  46. package/src/server/pipeline-server.ts +10 -6
  47. package/src/testing/fixtures/kanban-full.pipeline.specs.ts +0 -24
  48. package/src/testing/fixtures/kanban-full.pipeline.ts +0 -14
  49. package/src/testing/fixtures/kanban-todo.config.ts +0 -1
  50. package/src/testing/fixtures/kanban.pipeline.specs.ts +0 -1
  51. package/src/testing/fixtures/kanban.pipeline.ts +0 -7
  52. package/src/testing/kanban-todo.e2e.specs.ts +1 -1
  53. package/src/testing/real-execution.e2e.specs.ts +6 -6
@@ -1,5 +1,6 @@
1
1
  import type { Event } from '@auto-engineer/message-bus';
2
2
  import type {
3
+ AcceptsDescriptor,
3
4
  CustomHandlerDescriptor,
4
5
  EmitHandlerDescriptor,
5
6
  EventPredicate,
@@ -23,10 +24,15 @@ export interface Pipeline {
23
24
  toGraph(): GraphIR;
24
25
  }
25
26
 
27
+ export interface DeclareBuilder {
28
+ accepts(targets: string[]): PipelineBuilder;
29
+ }
30
+
26
31
  export interface PipelineBuilder {
27
32
  version(v: string): PipelineBuilder;
28
33
  description(d: string): PipelineBuilder;
29
34
  key<E>(name: string, extractor: (event: E) => string): PipelineBuilder;
35
+ declare(commandType: string): DeclareBuilder;
30
36
  on(eventType: string): TriggerBuilder;
31
37
  settled(commandTypes: readonly string[]): SettledBuilder;
32
38
  build(): Pipeline;
@@ -42,11 +48,12 @@ export interface SettledBuilder {
42
48
  handler: (
43
49
  events: Record<string, Event[]>,
44
50
  send: (commandType: D[number], data: unknown) => void,
45
- ) => void | { persist: boolean },
51
+ ) => undefined | { persist: boolean },
46
52
  ): SettledChain;
47
53
  }
48
54
 
49
55
  export interface SettledChain {
56
+ declare(commandType: string): DeclareBuilder;
50
57
  on(eventType: string): TriggerBuilder;
51
58
  settled(commandTypes: readonly string[]): SettledBuilder;
52
59
  build(): Pipeline;
@@ -124,12 +131,14 @@ export interface GatherChain {
124
131
 
125
132
  export interface EmitChain {
126
133
  emit(commandType: string, data: unknown): EmitChain;
134
+ declare(commandType: string): DeclareBuilder;
127
135
  on(eventType: string): TriggerBuilder;
128
136
  settled(commandTypes: readonly string[]): SettledBuilder;
129
137
  build(): Pipeline;
130
138
  }
131
139
 
132
140
  export interface HandleChain {
141
+ declare(commandType: string): DeclareBuilder;
133
142
  on(eventType: string): TriggerBuilder;
134
143
  build(): Pipeline;
135
144
  }
@@ -167,6 +176,10 @@ class PipelineBuilderImpl implements PipelineBuilder {
167
176
  return this;
168
177
  }
169
178
 
179
+ declare(commandType: string): DeclareBuilder {
180
+ return new DeclareBuilderImpl(this, commandType);
181
+ }
182
+
170
183
  on(eventType: string): TriggerBuilder {
171
184
  return new TriggerBuilderImpl(this, eventType);
172
185
  }
@@ -195,6 +208,18 @@ class PipelineBuilderImpl implements PipelineBuilder {
195
208
  }
196
209
  }
197
210
 
211
+ class DeclareBuilderImpl implements DeclareBuilder {
212
+ constructor(
213
+ private readonly parent: PipelineBuilderImpl,
214
+ private readonly commandType: string,
215
+ ) {}
216
+
217
+ accepts(targets: string[]): PipelineBuilder {
218
+ this.parent.addHandler({ type: 'accepts', commandType: this.commandType, accepts: targets });
219
+ return this.parent;
220
+ }
221
+ }
222
+
198
223
  type GraphBuilderContext = {
199
224
  nodeMap: Map<string, GraphNode>;
200
225
  edges: GraphEdge[];
@@ -254,6 +279,14 @@ function processCustomHandler(ctx: GraphBuilderContext, handler: CustomHandlerDe
254
279
  }
255
280
  }
256
281
 
282
+ function processAcceptsHandler(ctx: GraphBuilderContext, handler: AcceptsDescriptor): void {
283
+ addNode(ctx, `cmd:${handler.commandType}`, 'command', handler.commandType);
284
+ for (const target of handler.accepts) {
285
+ addNode(ctx, `cmd:${target}`, 'command', target);
286
+ ctx.edges.push({ from: `cmd:${handler.commandType}`, to: `cmd:${target}` });
287
+ }
288
+ }
289
+
257
290
  function processSettledHandler(ctx: GraphBuilderContext, handler: SettledHandlerDescriptor): void {
258
291
  const settledNodeId = `settled:${handler.commandTypes.join(',')}`;
259
292
  addNode(ctx, settledNodeId, 'settled', 'Settled');
@@ -294,6 +327,9 @@ function extractGraph(descriptor: PipelineDescriptor): GraphIR {
294
327
  case 'settled':
295
328
  processSettledHandler(ctx, handler);
296
329
  break;
330
+ case 'accepts':
331
+ processAcceptsHandler(ctx, handler);
332
+ break;
297
333
  }
298
334
  }
299
335
 
@@ -356,6 +392,11 @@ class EmitChainImpl implements EmitChain {
356
392
  return new EmitChainImpl(this.parent, this.eventType, [...this.commands, { commandType, data }], this.predicate);
357
393
  }
358
394
 
395
+ declare(commandType: string): DeclareBuilder {
396
+ this.finalizeHandler();
397
+ return new DeclareBuilderImpl(this.parent, commandType);
398
+ }
399
+
359
400
  on(eventType: string): TriggerBuilder {
360
401
  this.finalizeHandler();
361
402
  return new TriggerBuilderImpl(this.parent, eventType);
@@ -393,6 +434,11 @@ class HandleChainImpl implements HandleChain {
393
434
  private readonly declaredEmits?: string[],
394
435
  ) {}
395
436
 
437
+ declare(commandType: string): DeclareBuilder {
438
+ this.finalizeHandler();
439
+ return new DeclareBuilderImpl(this.parent, commandType);
440
+ }
441
+
396
442
  on(eventType: string): TriggerBuilder {
397
443
  this.finalizeHandler();
398
444
  return new TriggerBuilderImpl(this.parent, eventType);
@@ -682,6 +728,11 @@ class SettledChainImpl implements SettledChain {
682
728
  private readonly dispatches?: readonly string[],
683
729
  ) {}
684
730
 
731
+ declare(commandType: string): DeclareBuilder {
732
+ this.finalizeHandler();
733
+ return new DeclareBuilderImpl(this.parent, commandType);
734
+ }
735
+
685
736
  on(eventType: string): TriggerBuilder {
686
737
  this.finalizeHandler();
687
738
  return new TriggerBuilderImpl(this.parent, eventType);
@@ -78,7 +78,7 @@ type SettledSendFunction = (commandType: string, data: unknown) => void;
78
78
  export type SettledHandler = (
79
79
  events: Record<string, Event[]>,
80
80
  send: SettledSendFunction,
81
- ) => void | { persist: boolean };
81
+ ) => undefined | { persist: boolean };
82
82
 
83
83
  export interface SettledHandlerDescriptor {
84
84
  type: 'settled';
@@ -87,13 +87,19 @@ export interface SettledHandlerDescriptor {
87
87
  dispatches?: readonly string[];
88
88
  }
89
89
 
90
+ export interface AcceptsDescriptor {
91
+ type: 'accepts';
92
+ commandType: string;
93
+ accepts: string[];
94
+ }
95
+
90
96
  export type EventHandlerDescriptor =
91
97
  | EmitHandlerDescriptor
92
98
  | RunAwaitHandlerDescriptor
93
99
  | ForEachPhasedDescriptor
94
100
  | CustomHandlerDescriptor;
95
101
 
96
- export type HandlerDescriptor = EventHandlerDescriptor | SettledHandlerDescriptor;
102
+ export type HandlerDescriptor = EventHandlerDescriptor | SettledHandlerDescriptor | AcceptsDescriptor;
97
103
 
98
104
  export interface PipelineDescriptor {
99
105
  name: string;
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type { EventHandler, EventSubscription, MessageBus } from '@auto-engineer/message-bus';
2
2
  export type {
3
3
  CompletionConfig,
4
+ DeclareBuilder,
4
5
  EmitChain,
5
6
  ForEachBuilder,
6
7
  GatherBuilder,
@@ -17,6 +18,7 @@ export type {
17
18
  } from './builder/define';
18
19
  export { define } from './builder/define';
19
20
  export type {
21
+ AcceptsDescriptor,
20
22
  CustomHandlerDescriptor,
21
23
  EmitHandlerDescriptor,
22
24
  EventPredicate,
@@ -277,12 +277,5 @@ describe('PluginLoader', () => {
277
277
  expect(handlerNames).toContain('GenerateServer');
278
278
  expect(handlers.length).toBeGreaterThan(3);
279
279
  });
280
-
281
- it('should load ExportSchema from narrative package node entry', async () => {
282
- const loader = new PluginLoader();
283
- const handlers = await loader.loadPlugin('@auto-engineer/narrative');
284
- const handlerNames = handlers.map((h) => h.name);
285
- expect(handlerNames).toContain('ExportSchema');
286
- });
287
280
  });
288
281
  });
@@ -109,7 +109,7 @@ export class PipelineRuntime {
109
109
  private buildHandlerIndex(): Map<string, EventHandlerDescriptor[]> {
110
110
  const index = new Map<string, EventHandlerDescriptor[]>();
111
111
  for (const handler of this.descriptor.handlers) {
112
- if (handler.type === 'settled') {
112
+ if (handler.type === 'settled' || handler.type === 'accepts') {
113
113
  continue;
114
114
  }
115
115
  const existing = index.get(handler.eventType) ?? [];
@@ -4,7 +4,7 @@ import type { PipelineReadModel } from '../store/pipeline-read-model';
4
4
 
5
5
  type SendFunction = (commandType: string, data: unknown) => void;
6
6
 
7
- type SettledHandler = (events: Record<string, Event[]>, send: SendFunction) => void | { persist: boolean };
7
+ type SettledHandler = (events: Record<string, Event[]>, send: SendFunction) => undefined | { persist: boolean };
8
8
 
9
9
  interface SettledHandlerRegistration {
10
10
  commandTypes: readonly string[];
@@ -223,7 +223,7 @@ export class SettledTracker {
223
223
  return events;
224
224
  }
225
225
 
226
- private shouldPersist(result: void | { persist: boolean }): boolean {
226
+ private shouldPersist(result: undefined | { persist: boolean }): boolean {
227
227
  return (
228
228
  result !== null &&
229
229
  result !== undefined &&
@@ -51,8 +51,8 @@ describe('Full Orchestration E2E', () => {
51
51
  expect(graph.edges.length).toBeGreaterThan(0);
52
52
 
53
53
  const nodeIds = graph.nodes.map((n) => n.id);
54
- expect(nodeIds).toContain('evt:SchemaExported');
55
- expect(nodeIds).toContain('cmd:GenerateServer');
54
+ expect(nodeIds).toContain('evt:ServerGenerated');
55
+ expect(nodeIds).toContain('cmd:GenerateIA');
56
56
  expect(nodeIds).toContain('evt:SliceImplemented');
57
57
  expect(nodeIds).toContain('evt:AllComponentsImplemented');
58
58
  });
@@ -260,11 +260,6 @@ describe('Full Orchestration E2E', () => {
260
260
  describe('complete kanban workflow', () => {
261
261
  it('should execute full kanban workflow with mock handlers', async () => {
262
262
  const handlers = createMockHandlers([
263
- {
264
- name: 'ExportSchema',
265
- events: ['SchemaExported'],
266
- fn: () => ({ type: 'SchemaExported', data: { outputPath: './schema.json' } }),
267
- },
268
263
  {
269
264
  name: 'GenerateServer',
270
265
  events: ['ServerGenerated', 'SliceGenerated'],
@@ -340,7 +335,7 @@ describe('Full Orchestration E2E', () => {
340
335
  await fetchJson(`http://localhost:${server.port}/command`, {
341
336
  method: 'POST',
342
337
  headers: { 'Content-Type': 'application/json' },
343
- body: JSON.stringify({ type: 'ExportSchema', data: {} }),
338
+ body: JSON.stringify({ type: 'GenerateServer', data: {} }),
344
339
  });
345
340
 
346
341
  await delay(800);
@@ -349,7 +344,6 @@ describe('Full Orchestration E2E', () => {
349
344
  const eventTypes = messages.filter((m) => m.messageType === 'event').map((m) => m.message.type);
350
345
 
351
346
  const expectedSubsequence = [
352
- 'SchemaExported',
353
347
  'SliceGenerated',
354
348
  'SliceImplemented',
355
349
  'TestsCheckPassed',
@@ -363,7 +357,6 @@ describe('Full Orchestration E2E', () => {
363
357
  expect(eventTypes).toContain('AllComponentsImplemented');
364
358
 
365
359
  const missingEvents = findMissingEvents(eventTypes, [
366
- 'SchemaExported',
367
360
  'SliceGenerated',
368
361
  'SliceImplemented',
369
362
  'ServerGenerated',
@@ -381,11 +374,6 @@ describe('Full Orchestration E2E', () => {
381
374
  let typeCheckCallCount = 0;
382
375
 
383
376
  const handlers = createMockHandlers([
384
- {
385
- name: 'ExportSchema',
386
- events: ['SchemaExported'],
387
- fn: () => ({ type: 'SchemaExported', data: { outputPath: './schema.json' } }),
388
- },
389
377
  {
390
378
  name: 'GenerateServer',
391
379
  events: ['ServerGenerated', 'SliceGenerated'],
@@ -461,7 +449,7 @@ describe('Full Orchestration E2E', () => {
461
449
  await fetchJson(`http://localhost:${server.port}/command`, {
462
450
  method: 'POST',
463
451
  headers: { 'Content-Type': 'application/json' },
464
- body: JSON.stringify({ type: 'ExportSchema', data: {} }),
452
+ body: JSON.stringify({ type: 'GenerateServer', data: {} }),
465
453
  });
466
454
 
467
455
  await delay(1000);
@@ -55,17 +55,14 @@ describe('PipelineServer E2E', () => {
55
55
  describe('baseline endpoints (CLI E2E parity)', () => {
56
56
  it('should return registry with expected shape', async () => {
57
57
  const handler: CommandHandlerWithMetadata = {
58
- name: 'ExportSchema',
59
- alias: 'export:schema',
60
- description: 'Export flow schemas to context directory',
61
- events: ['SchemaExported'],
62
- handle: async () => ({ type: 'SchemaExported', data: {} }),
58
+ name: 'TestCommand',
59
+ alias: 'test:run',
60
+ description: 'Run test command',
61
+ events: ['TestDone'],
62
+ handle: async () => ({ type: 'TestDone', data: {} }),
63
63
  };
64
64
 
65
- const pipeline = define('kanban')
66
- .on('SchemaExported')
67
- .emit('GenerateServer', { modelPath: './.context/schema.json' })
68
- .build();
65
+ const pipeline = define('kanban').on('TestDone').emit('GenerateServer', {}).build();
69
66
 
70
67
  const server = new PipelineServer({ port: 0 });
71
68
  server.registerCommandHandlers([handler]);
@@ -74,26 +71,26 @@ describe('PipelineServer E2E', () => {
74
71
 
75
72
  const registry = await fetchJson<RegistryResponse>(`http://localhost:${server.port}/registry`);
76
73
 
77
- expect(registry.eventHandlers).toContain('SchemaExported');
78
- expect(registry.commandHandlers).toContain('ExportSchema');
74
+ expect(registry.eventHandlers).toContain('TestDone');
75
+ expect(registry.commandHandlers).toContain('TestCommand');
79
76
  expect(registry.folds).toEqual([]);
80
77
  expect(registry.commandsWithMetadata).toHaveLength(1);
81
- expect(registry.commandsWithMetadata[0].alias).toBe('export:schema');
82
- expect(registry.commandsWithMetadata[0].description).toBe('Export flow schemas to context directory');
78
+ expect(registry.commandsWithMetadata[0].alias).toBe('test:run');
79
+ expect(registry.commandsWithMetadata[0].description).toBe('Run test command');
83
80
 
84
81
  await server.stop();
85
82
  });
86
83
 
87
84
  it('should return pipeline with expected shape', async () => {
88
85
  const handler: CommandHandlerWithMetadata = {
89
- name: 'ExportSchema',
90
- alias: 'export:schema',
91
- description: 'Export schemas',
92
- events: ['SchemaExported'],
93
- handle: async () => ({ type: 'SchemaExported', data: {} }),
86
+ name: 'TestCommand',
87
+ alias: 'test:run',
88
+ description: 'Run test command',
89
+ events: ['TestDone'],
90
+ handle: async () => ({ type: 'TestDone', data: {} }),
94
91
  };
95
92
 
96
- const pipeline = define('kanban').on('SchemaExported').emit('GenerateServer', {}).build();
93
+ const pipeline = define('kanban').on('TestDone').emit('GenerateServer', {}).build();
97
94
 
98
95
  const server = new PipelineServer({ port: 0 });
99
96
  server.registerCommandHandlers([handler]);
@@ -102,9 +99,9 @@ describe('PipelineServer E2E', () => {
102
99
 
103
100
  const pipelineRes = await fetchJson<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
104
101
 
105
- expect(pipelineRes.nodes.some((n) => n.id === 'evt:SchemaExported')).toBe(true);
102
+ expect(pipelineRes.nodes.some((n) => n.id === 'evt:TestDone')).toBe(true);
106
103
  expect(pipelineRes.nodes.some((n) => n.id === 'cmd:GenerateServer')).toBe(true);
107
- expect(pipelineRes.pipelineNodes.some((n) => n.id === 'ExportSchema')).toBe(true);
104
+ expect(pipelineRes.pipelineNodes.some((n) => n.id === 'TestCommand')).toBe(true);
108
105
 
109
106
  await server.stop();
110
107
  });
@@ -131,8 +128,8 @@ describe('PipelineServer E2E', () => {
131
128
 
132
129
  it('should accept command and return ack', async () => {
133
130
  const handler: CommandHandlerWithMetadata = {
134
- name: 'ExportSchema',
135
- handle: async () => ({ type: 'SchemaExported', data: {} }),
131
+ name: 'TestCommand',
132
+ handle: async () => ({ type: 'TestDone', data: {} }),
136
133
  };
137
134
 
138
135
  const server = new PipelineServer({ port: 0 });
@@ -142,7 +139,7 @@ describe('PipelineServer E2E', () => {
142
139
  const ack = await fetchJson<CommandAck>(`http://localhost:${server.port}/command`, {
143
140
  method: 'POST',
144
141
  headers: { 'Content-Type': 'application/json' },
145
- body: JSON.stringify({ type: 'ExportSchema', data: {} }),
142
+ body: JSON.stringify({ type: 'TestCommand', data: {} }),
146
143
  });
147
144
 
148
145
  expect(ack.status).toBe('ack');
@@ -155,9 +152,9 @@ describe('PipelineServer E2E', () => {
155
152
  describe('command execution and event routing', () => {
156
153
  it('should execute command and route resulting event through pipeline', async () => {
157
154
  const exportHandler: CommandHandlerWithMetadata = {
158
- name: 'ExportSchema',
159
- events: ['SchemaExported'],
160
- handle: async () => ({ type: 'SchemaExported', data: { path: './schema.json' } }),
155
+ name: 'TestCommand',
156
+ events: ['TestDone'],
157
+ handle: async () => ({ type: 'TestDone', data: { path: './schema.json' } }),
161
158
  };
162
159
 
163
160
  const generateHandler: CommandHandlerWithMetadata = {
@@ -166,10 +163,7 @@ describe('PipelineServer E2E', () => {
166
163
  handle: async () => ({ type: 'ServerGenerated', data: { slices: 3 } }),
167
164
  };
168
165
 
169
- const pipeline = define('kanban')
170
- .on('SchemaExported')
171
- .emit('GenerateServer', (e: { data: { path: string } }) => ({ modelPath: e.data.path }))
172
- .build();
166
+ const pipeline = define('kanban').on('TestDone').emit('GenerateServer', {}).build();
173
167
 
174
168
  const server = new PipelineServer({ port: 0 });
175
169
  server.registerCommandHandlers([exportHandler, generateHandler]);
@@ -179,7 +173,7 @@ describe('PipelineServer E2E', () => {
179
173
  await fetchJson(`http://localhost:${server.port}/command`, {
180
174
  method: 'POST',
181
175
  headers: { 'Content-Type': 'application/json' },
182
- body: JSON.stringify({ type: 'ExportSchema', data: {} }),
176
+ body: JSON.stringify({ type: 'TestCommand', data: {} }),
183
177
  });
184
178
 
185
179
  await new Promise((r) => setTimeout(r, 200));
@@ -187,7 +181,7 @@ describe('PipelineServer E2E', () => {
187
181
  const messages = await fetchJson<StoredMessage[]>(`http://localhost:${server.port}/messages`);
188
182
  const eventTypes = messages.filter((m) => m.messageType === 'event').map((m) => m.message.type);
189
183
 
190
- expect(eventTypes).toContain('SchemaExported');
184
+ expect(eventTypes).toContain('TestDone');
191
185
  expect(eventTypes).toContain('ServerGenerated');
192
186
 
193
187
  await server.stop();
@@ -1117,11 +1117,9 @@ describe('PipelineServer', () => {
1117
1117
  handle: async () => ({ type: 'SliceImplemented', data: {} }),
1118
1118
  };
1119
1119
  const pipeline = define('test')
1120
- .on('SchemaExported')
1121
- .emit('GenerateServer', {})
1122
1120
  .on('ServerGenerated')
1123
1121
  .emit('GenerateIA', {})
1124
- .on('SliceGenerated')
1122
+ .on('IAGenerated')
1125
1123
  .emit('ImplementSlice', {})
1126
1124
  .build();
1127
1125
  const server = new PipelineServer({ port: 0 });
@@ -1130,9 +1128,8 @@ describe('PipelineServer', () => {
1130
1128
  await server.start();
1131
1129
  const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
1132
1130
  const mermaid = await res.text();
1133
- expect(mermaid).toContain('evt_SchemaExported --> GenerateServer');
1134
- expect(mermaid).toContain('GenerateServer --> evt_ServerGenerated');
1135
- expect(mermaid).toContain('GenerateServer --> evt_SliceGenerated');
1131
+ expect(mermaid).toContain('evt_ServerGenerated --> GenerateIA');
1132
+ expect(mermaid).toContain('evt_IAGenerated --> ImplementSlice');
1136
1133
  await server.stop();
1137
1134
  });
1138
1135
 
@@ -1202,6 +1199,44 @@ describe('PipelineServer', () => {
1202
1199
  await server.stop();
1203
1200
  });
1204
1201
 
1202
+ it('should show source commands whose events are listened to by the pipeline', async () => {
1203
+ const sourceHandler = {
1204
+ name: 'SourceCmd',
1205
+ events: ['SourceEvent'],
1206
+ handle: async () => ({ type: 'SourceEvent', data: {} }),
1207
+ };
1208
+ const nextHandler = {
1209
+ name: 'NextCmd',
1210
+ events: ['NextDone'],
1211
+ handle: async () => ({ type: 'NextDone', data: {} }),
1212
+ };
1213
+ const pipeline = define('test').on('SourceEvent').emit('NextCmd', {}).build();
1214
+ const server = new PipelineServer({ port: 0 });
1215
+ server.registerCommandHandlers([sourceHandler, nextHandler]);
1216
+ server.registerPipeline(pipeline);
1217
+ await server.start();
1218
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
1219
+ const mermaid = await res.text();
1220
+ expect(mermaid).toEqual(
1221
+ [
1222
+ 'flowchart LR',
1223
+ ' evt_SourceEvent([SourceEvent])',
1224
+ ' NextCmd[NextCmd]',
1225
+ ' SourceCmd[SourceCmd]',
1226
+ ' evt_SourceEvent --> NextCmd',
1227
+ ' SourceCmd --> evt_SourceEvent',
1228
+ '',
1229
+ ' classDef event fill:#fff3e0,stroke:#e65100',
1230
+ ' classDef eventFailed fill:#fff3e0,stroke:#e65100,color:#d32f2f',
1231
+ ' classDef command fill:#e3f2fd,stroke:#1565c0',
1232
+ ' classDef settled fill:#f3e5f5,stroke:#7b1fa2',
1233
+ ' class evt_SourceEvent event',
1234
+ ' class NextCmd,SourceCmd command',
1235
+ ].join('\n'),
1236
+ );
1237
+ await server.stop();
1238
+ });
1239
+
1205
1240
  it('should show edges from commands to settled node', async () => {
1206
1241
  const checkAHandler = {
1207
1242
  name: 'CheckA',
@@ -205,7 +205,7 @@ export class PipelineServer {
205
205
  const eventHandlers: string[] = [];
206
206
  for (const pipeline of this.pipelines.values()) {
207
207
  for (const handler of pipeline.descriptor.handlers) {
208
- if (handler.type === 'settled') {
208
+ if (handler.type === 'settled' || handler.type === 'accepts') {
209
209
  continue;
210
210
  }
211
211
  if (!eventHandlers.includes(handler.eventType)) {
@@ -715,13 +715,17 @@ export class PipelineServer {
715
715
  const newEdges = [...graph.edges];
716
716
 
717
717
  for (const [commandName, events] of Object.entries(commandToEvents)) {
718
- if (!commandNodes.has(commandName)) {
718
+ const relevantEvents = events.filter((e) => pipelineEvents.has(e));
719
+ if (relevantEvents.length === 0) {
719
720
  continue;
720
721
  }
721
- for (const eventName of events) {
722
- if (!pipelineEvents.has(eventName)) {
723
- continue;
724
- }
722
+
723
+ if (!commandNodes.has(commandName)) {
724
+ newNodes.push({ id: `cmd:${commandName}`, type: 'command', label: commandName });
725
+ commandNodes.add(commandName);
726
+ }
727
+
728
+ for (const eventName of relevantEvents) {
725
729
  const eventId = `evt:${eventName}`;
726
730
  if (!existingEventIds.has(eventId)) {
727
731
  newNodes.push({ id: eventId, type: 'event', label: eventName });
@@ -52,13 +52,6 @@ describe('kanban-full.pipeline', () => {
52
52
  expect(pipeline.descriptor.name).toBe('kanban-full');
53
53
  });
54
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
55
  it('should have settled handler for slice checks', () => {
63
56
  const pipeline = createKanbanFullPipeline();
64
57
  const settledHandlers = pipeline.descriptor.handlers.filter((h) => h.type === 'settled');
@@ -92,23 +85,6 @@ describe('kanban-full.pipeline', () => {
92
85
  return undefined;
93
86
  }
94
87
 
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
88
  it('should emit ImplementSlice with slicePath, context, and aiOptions', () => {
113
89
  const pipeline = createKanbanFullPipeline();
114
90
  const cmd = findEmitCommand(pipeline, 'SliceGenerated', 'ImplementSlice');
@@ -6,11 +6,6 @@ interface Component {
6
6
  filePath: string;
7
7
  }
8
8
 
9
- interface SchemaExportedData {
10
- directory: string;
11
- outputPath: string;
12
- }
13
-
14
9
  interface SliceGeneratedData {
15
10
  slicePath: string;
16
11
  }
@@ -89,15 +84,6 @@ function resolvePath(relativePath: string): string {
89
84
 
90
85
  export function createKanbanFullPipeline() {
91
86
  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
87
  .on('SliceGenerated')
102
88
  .emit('ImplementSlice', (e: { data: SliceGeneratedData }) => ({
103
89
  slicePath: resolvePath(e.data.slicePath),
@@ -6,7 +6,6 @@ export default pipelineConfig({
6
6
  '@auto-engineer/server-checks',
7
7
  '@auto-engineer/server-generator-apollo-emmett',
8
8
  '@auto-engineer/narrative',
9
- '@auto-engineer/information-architect',
10
9
  '@auto-engineer/generate-react-client',
11
10
  '@auto-engineer/react-component-implementer',
12
11
  '@auto-engineer/server-implementer',
@@ -26,7 +26,6 @@ describe('kanban.pipeline', () => {
26
26
  it('should have handlers for slice workflow', () => {
27
27
  const pipeline = createKanbanPipeline();
28
28
  const eventTypes = pipeline.descriptor.handlers.filter((h) => h.type === 'emit').map((h) => h.eventType);
29
- expect(eventTypes).toContain('SchemaExported');
30
29
  expect(eventTypes).toContain('SliceGenerated');
31
30
  });
32
31
  });
@@ -7,10 +7,6 @@ interface Component {
7
7
  filePath: string;
8
8
  }
9
9
 
10
- interface SchemaExportedData {
11
- outputPath: string;
12
- }
13
-
14
10
  interface SliceGeneratedData {
15
11
  slicePath: string;
16
12
  }
@@ -68,9 +64,6 @@ function incrementRetryCount(slicePath: string): number {
68
64
 
69
65
  export function createKanbanPipeline() {
70
66
  return define('kanban')
71
- .on('SchemaExported')
72
- .emit('GenerateServer', (e: { data: SchemaExportedData }) => ({ modelPath: e.data.outputPath }))
73
-
74
67
  .on('SliceGenerated')
75
68
  .emit('ImplementSlice', (e: { data: SliceGeneratedData }) => ({ slicePath: e.data.slicePath }))
76
69
 
@@ -169,7 +169,7 @@ describe('Kanban-Todo Pipeline E2E Comparison', () => {
169
169
  describe('Workflow Sequence Validation', () => {
170
170
  it('should have correct causal dependencies in pipeline definition', () => {
171
171
  const expectedDependencies: [string, string][] = [
172
- ['SchemaExported', 'GenerateServer'],
172
+ ['ServerGenerated', 'GenerateIA'],
173
173
  ['SliceGenerated', 'ImplementSlice'],
174
174
  ['SliceImplemented', 'CheckTests'],
175
175
  ['SliceImplemented', 'CheckTypes'],