@auto-engineer/pipeline 1.113.0 → 1.114.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +3 -3
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +18 -0
- package/package.json +3 -3
- package/ketchup-plan.md +0 -1269
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @auto-engineer/pipeline@1.
|
|
2
|
+
> @auto-engineer/pipeline@1.114.0 build /home/runner/work/auto-engineer/auto-engineer/packages/pipeline
|
|
3
3
|
> tsc && tsx ../../scripts/fix-esm-imports.ts
|
|
4
4
|
|
|
5
5
|
Fixed ESM imports in dist/
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @auto-engineer/pipeline@1.
|
|
2
|
+
> @auto-engineer/pipeline@1.113.0 test /home/runner/work/auto-engineer/auto-engineer/packages/pipeline
|
|
3
3
|
> vitest run --reporter=dot
|
|
4
4
|
|
|
5
5
|
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
[2m Test Files [22m [1m[32m42 passed[39m[22m[90m (42)[39m
|
|
11
11
|
[2m Tests [22m [1m[32m582 passed[39m[22m[90m (582)[39m
|
|
12
|
-
[2m Start at [22m
|
|
13
|
-
[2m Duration [22m
|
|
12
|
+
[2m Start at [22m 05:52:09
|
|
13
|
+
[2m Duration [22m 42.70s[2m (transform 8.47s, setup 0ms, collect 17.61s, tests 35.30s, environment 29ms, prepare 29.72s)[22m
|
|
14
14
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @auto-engineer/pipeline
|
|
2
2
|
|
|
3
|
+
## 1.114.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`9570b75`](https://github.com/BeOnAuto/auto-engineer/commit/9570b75c9312b6b714e8be89960a5ee7de2d8d48) Thanks [@github-actions[bot]](https://github.com/github-actions%5Bbot%5D)! - - **global**: imrpoves ui generation pipeline
|
|
8
|
+
- **component-implementor-react**: rewrite pipeline with parallel gen, fix loops, and evaluation
|
|
9
|
+
- **component-implementor-react**: add test-generation agent
|
|
10
|
+
- **component-implementor-react**: add test-fix agent and loop
|
|
11
|
+
- **component-implementor-react**: add evaluation agent
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [`cae4cf2`](https://github.com/BeOnAuto/auto-engineer/commit/cae4cf2f82d363ea696bd6cf9f10d5d35364659f) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - Removed internal planning files from the repository
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [[`9570b75`](https://github.com/BeOnAuto/auto-engineer/commit/9570b75c9312b6b714e8be89960a5ee7de2d8d48), [`cae4cf2`](https://github.com/BeOnAuto/auto-engineer/commit/cae4cf2f82d363ea696bd6cf9f10d5d35364659f)]:
|
|
18
|
+
- @auto-engineer/file-store@1.114.0
|
|
19
|
+
- @auto-engineer/message-bus@1.114.0
|
|
20
|
+
|
|
3
21
|
## 1.113.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"get-port": "^7.1.0",
|
|
15
15
|
"jose": "^5.9.6",
|
|
16
16
|
"nanoid": "^5.0.0",
|
|
17
|
-
"@auto-engineer/file-store": "1.
|
|
18
|
-
"@auto-engineer/message-bus": "1.
|
|
17
|
+
"@auto-engineer/file-store": "1.114.0",
|
|
18
|
+
"@auto-engineer/message-bus": "1.114.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/cors": "^2.8.17",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
|
-
"version": "1.
|
|
27
|
+
"version": "1.114.0",
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc && tsx ../../scripts/fix-esm-imports.ts",
|
|
30
30
|
"test": "vitest run --reporter=dot",
|
package/ketchup-plan.md
DELETED
|
@@ -1,1269 +0,0 @@
|
|
|
1
|
-
# Pipeline Package - Ketchup Plan
|
|
2
|
-
|
|
3
|
-
## TODO
|
|
4
|
-
|
|
5
|
-
### Fix Settled Node Labels and Event Routing (Bursts 107-112)
|
|
6
|
-
|
|
7
|
-
- [x] Burst 107: processSettledHandler uses descriptor.label for graph node [depends: none]
|
|
8
|
-
- [x] Burst 108: bridge filters commands by sourceEventTypes [depends: none]
|
|
9
|
-
- [x] Burst 109: builder sets sourceEventTypes from emit chain event type [depends: none]
|
|
10
|
-
- [x] Burst 110: Add label and sourceEventTypes to descriptor and builder (merged into 107+109)
|
|
11
|
-
- [x] Burst 111: Add source event filtering to bridge (merged into 108)
|
|
12
|
-
- [x] Burst 112: Thread source event type through pipeline server [depends: 110, 111]
|
|
13
|
-
|
|
14
|
-
### Graph Rendering Fix (Burst 106)
|
|
15
|
-
|
|
16
|
-
- [ ] Burst 106: Show source commands whose events are listened to by the pipeline [depends: none]
|
|
17
|
-
|
|
18
|
-
### Phase 11: 100% Test Coverage (Bursts 93-102)
|
|
19
|
-
|
|
20
|
-
**Goal**: Achieve 100% test coverage by testing uncovered code or removing dead code.
|
|
21
|
-
|
|
22
|
-
**Current Coverage**: 96.53% lines, 97.13% branches, 94.94% functions
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
#### Burst 93: Exclude barrel exports from coverage
|
|
27
|
-
|
|
28
|
-
| Value | Remove false positives from coverage report |
|
|
29
|
-
| Approach | Change `src/index.ts` to `src/**/index.ts` in vitest.config.ts |
|
|
30
|
-
| Size | S |
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
#### Burst 94: Test config/pipeline-config.ts
|
|
35
|
-
|
|
36
|
-
| Value | 0% → 100% coverage for config module |
|
|
37
|
-
| Approach | Test pipelineConfig() identity fn and loadPipelineConfig() with mocked loader |
|
|
38
|
-
| Size | M |
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
it('should return config unchanged', () => {
|
|
42
|
-
const config = { plugins: [], pipeline: mockPipeline };
|
|
43
|
-
expect(pipelineConfig(config)).toBe(config);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should load plugins and adapt handlers', async () => {
|
|
47
|
-
const config = { plugins: ['./test-plugin'], pipeline: mockPipeline };
|
|
48
|
-
const result = await loadPipelineConfig(config, '/workspace');
|
|
49
|
-
expect(result.handlers).toBeDefined();
|
|
50
|
-
expect(result.pipeline).toBe(mockPipeline);
|
|
51
|
-
});
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
#### Burst 95: Test filter-graph.ts addEdgeIfNew (lines 63-69)
|
|
57
|
-
|
|
58
|
-
| Value | Cover duplicate edge scenario |
|
|
59
|
-
| Approach | Test graph with duplicate edges when maintainEdges=true |
|
|
60
|
-
| Size | S |
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
it('should deduplicate edges when reconnecting', () => {
|
|
64
|
-
const graph = {
|
|
65
|
-
nodes: [{ id: 'a', type: 'command', label: 'A' }, { id: 'b', type: 'event', label: 'B' }, { id: 'c', type: 'command', label: 'C' }],
|
|
66
|
-
edges: [{ from: 'a', to: 'b' }, { from: 'a', to: 'b' }] // Duplicate
|
|
67
|
-
};
|
|
68
|
-
const result = filterGraph(graph, { excludeTypes: [], maintainEdges: true });
|
|
69
|
-
// Edges should be deduplicated
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
#### Burst 96: Test await-tracker-projection.ts null document errors (lines 48-49, 60-61)
|
|
76
|
-
|
|
77
|
-
| Value | Cover error paths |
|
|
78
|
-
| Approach | Test evolve() throws when applying events to null document |
|
|
79
|
-
| Size | S |
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
it('should throw when applying AwaitItemCompleted to null', () => {
|
|
83
|
-
expect(() => evolve(null, { type: 'AwaitItemCompleted', data: { correlationId: 'c1', key: 'k', result: {} } }))
|
|
84
|
-
.toThrow('Cannot apply AwaitItemCompleted to null document');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should throw when applying AwaitCompleted to null', () => {
|
|
88
|
-
expect(() => evolve(null, { type: 'AwaitCompleted', data: { correlationId: 'c1' } }))
|
|
89
|
-
.toThrow('Cannot apply AwaitCompleted to null document');
|
|
90
|
-
});
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
#### Burst 97: Remove or test phased-executor.ts getActiveSessionCount (lines 99-101)
|
|
96
|
-
|
|
97
|
-
| Value | Remove dead code or add test |
|
|
98
|
-
| Approach | Check if used; if not, remove; if used, test |
|
|
99
|
-
| Size | S |
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
#### Burst 98: Test pipeline-runtime.ts fallback path (line 46)
|
|
104
|
-
|
|
105
|
-
| Value | Cover when ctx.startPhased is undefined |
|
|
106
|
-
| Approach | Call handleEvent with context missing startPhased |
|
|
107
|
-
| Size | S |
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
it('should fallback to executeForEachPhasedHandler when ctx.startPhased undefined', async () => {
|
|
111
|
-
const ctx = { correlationId: 'c1', emit: vi.fn(), sendCommand: vi.fn() }; // No startPhased
|
|
112
|
-
await runtime.handleEvent(event, ctx);
|
|
113
|
-
expect(ctx.sendCommand).toHaveBeenCalled(); // Fallback dispatched commands
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
#### Burst 99: Remove or test settled-tracker.ts getRegisteredHandlerCount (lines 62-63)
|
|
120
|
-
|
|
121
|
-
| Value | Remove dead code or add test |
|
|
122
|
-
| Approach | Check if used; if not, remove; if used, test |
|
|
123
|
-
| Size | S |
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
#### Burst 100: Test sse-manager.ts clientCount getter (lines 14-15)
|
|
128
|
-
|
|
129
|
-
| Value | Cover getter or remove if dead |
|
|
130
|
-
| Approach | Check usage; test or remove |
|
|
131
|
-
| Size | S |
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
it('should return client count', () => {
|
|
135
|
-
const manager = new SSEManager();
|
|
136
|
-
expect(manager.clientCount).toBe(0);
|
|
137
|
-
manager.addClient('c1', mockResponse);
|
|
138
|
-
expect(manager.clientCount).toBe(1);
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
#### Burst 101: Test pipeline-read-model.ts line 79 branch
|
|
145
|
-
|
|
146
|
-
| Value | Cover endedCount === 0 with no pending items |
|
|
147
|
-
| Approach | Create scenario where items exist but all are running then all complete to idle |
|
|
148
|
-
| Size | S |
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
#### Burst 102: Final verification - run coverage, all at 100%
|
|
153
|
-
|
|
154
|
-
| Value | Confirm 100% coverage achieved |
|
|
155
|
-
| Approach | Run pnpm test:coverage, verify all thresholds pass |
|
|
156
|
-
| Size | S |
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
### Phase 10: SQLite Event Store Persistence (Bursts 88-92)
|
|
161
|
-
|
|
162
|
-
**Goal**: Replace in-memory event store with SQLite for persistence. Events survive restarts; projections rebuilt from event stream on startup.
|
|
163
|
-
|
|
164
|
-
**Current State**:
|
|
165
|
-
```
|
|
166
|
-
In-Memory EventStore → In-Memory Projections → Lost on restart
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
**Target State**:
|
|
170
|
-
```
|
|
171
|
-
SQLite EventStore → Consumer → In-Memory Projections (rebuilt on startup)
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
#### Burst 88: Async createPipelineEventStore with config
|
|
177
|
-
|
|
178
|
-
| Value | Enable async initialization and configurable file path |
|
|
179
|
-
| Approach | Make createPipelineEventStore async, accept optional config |
|
|
180
|
-
| Size | S |
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
it('should create event store with default config', async () => {
|
|
184
|
-
const context = await createPipelineEventStore();
|
|
185
|
-
expect(context.eventStore).toBeDefined();
|
|
186
|
-
expect(context.readModel).toBeDefined();
|
|
187
|
-
await context.close();
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should accept custom fileName config', async () => {
|
|
191
|
-
const context = await createPipelineEventStore({ fileName: ':memory:' });
|
|
192
|
-
expect(context.eventStore).toBeDefined();
|
|
193
|
-
await context.close();
|
|
194
|
-
});
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
#### Burst 89: SQLite event store with schema auto-migration
|
|
200
|
-
|
|
201
|
-
| Value | Events persist to SQLite file |
|
|
202
|
-
| Approach | Replace getInMemoryEventStore with getSQLiteEventStore |
|
|
203
|
-
| Size | M |
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
it('should persist events to SQLite', async () => {
|
|
207
|
-
const context = await createPipelineEventStore({ fileName: ':memory:' });
|
|
208
|
-
await context.eventStore.appendToStream('test-stream', [
|
|
209
|
-
{ type: 'TestEvent', data: { value: 42 } }
|
|
210
|
-
]);
|
|
211
|
-
const stream = await context.eventStore.readStream('test-stream');
|
|
212
|
-
expect(stream.events).toHaveLength(1);
|
|
213
|
-
await context.close();
|
|
214
|
-
});
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
#### Burst 90: Consumer replays events to projections
|
|
220
|
-
|
|
221
|
-
| Value | Projections rebuilt from event stream on startup |
|
|
222
|
-
| Approach | Consumer with projection-updater processor from BEGINNING |
|
|
223
|
-
| Size | M |
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
it('should replay events to projections on startup', async () => {
|
|
227
|
-
const context = await createPipelineEventStore({ fileName: ':memory:' });
|
|
228
|
-
await context.eventStore.appendToStream('pipeline-c1', [
|
|
229
|
-
{ type: 'ItemStatusChanged', data: { correlationId: 'c1', commandType: 'Cmd', itemKey: 'k1', requestId: 'r1', status: 'running', attemptCount: 1 } }
|
|
230
|
-
]);
|
|
231
|
-
// Allow consumer to process
|
|
232
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
233
|
-
const item = await context.readModel.getItemStatus('c1', 'Cmd', 'k1');
|
|
234
|
-
expect(item?.status).toBe('running');
|
|
235
|
-
await context.close();
|
|
236
|
-
});
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
#### Burst 91: close() stops and closes consumer
|
|
242
|
-
|
|
243
|
-
| Value | Clean shutdown without resource leaks |
|
|
244
|
-
| Approach | Call consumer.stop() and consumer.close() in close() |
|
|
245
|
-
| Size | S |
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
it('should stop consumer on close', async () => {
|
|
249
|
-
const context = await createPipelineEventStore({ fileName: ':memory:' });
|
|
250
|
-
await context.close();
|
|
251
|
-
// No error means success - consumer stopped cleanly
|
|
252
|
-
});
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
#### Burst 92: PipelineServer uses async factory
|
|
258
|
-
|
|
259
|
-
| Value | Server initializes SQLite event store |
|
|
260
|
-
| Approach | Static PipelineServer.create() factory, configurable fileName |
|
|
261
|
-
| Size | M |
|
|
262
|
-
| Files | `src/server/pipeline-server.ts` |
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
it('should create server with async event store initialization', async () => {
|
|
266
|
-
const server = await PipelineServer.create({ port: 0 });
|
|
267
|
-
expect(server.port).toBeGreaterThan(0);
|
|
268
|
-
await server.stop();
|
|
269
|
-
});
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
### Phase 9: Remove Dual Caches - Pure Event Sourcing (Bursts 71-87)
|
|
275
|
-
|
|
276
|
-
**Goal**: Remove `nodeStatusCache` and `itemStatusCache` from PipelineServer. All runtime state derived from Emmett projections via `PipelineReadModel`.
|
|
277
|
-
|
|
278
|
-
**Current State** (dual source of truth):
|
|
279
|
-
|
|
280
|
-
```
|
|
281
|
-
Commands → Cache (mutable) + Emmett Events → Projections
|
|
282
|
-
↓ ↓
|
|
283
|
-
Used for lookups Also stores same data
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**Target State** (single source of truth):
|
|
287
|
-
|
|
288
|
-
```
|
|
289
|
-
Commands → Emmett Events → Projections → ReadModel queries
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
---
|
|
293
|
-
|
|
294
|
-
#### Burst 71: Add getNodeStatus query (test)
|
|
295
|
-
|
|
296
|
-
| Value | Query node status from projection |
|
|
297
|
-
| Approach | `getNodeStatus(correlationId, commandName)` returns status or null |
|
|
298
|
-
| Size | S |
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
it("should return null when no node status exists", async () => {
|
|
302
|
-
const result = await readModel.getNodeStatus("c1", "CreateUser");
|
|
303
|
-
expect(result).toBeNull();
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it("should return node status from NodeStatus collection", async () => {
|
|
307
|
-
const collection =
|
|
308
|
-
database.collection<WithId<NodeStatusDocument>>("NodeStatus");
|
|
309
|
-
await collection.insertOne({
|
|
310
|
-
_id: "ns-c1-CreateUser",
|
|
311
|
-
correlationId: "c1",
|
|
312
|
-
commandName: "CreateUser",
|
|
313
|
-
status: "running",
|
|
314
|
-
pendingCount: 1,
|
|
315
|
-
endedCount: 0,
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const result = await readModel.getNodeStatus("c1", "CreateUser");
|
|
319
|
-
expect(result).toEqual({
|
|
320
|
-
correlationId: "c1",
|
|
321
|
-
commandName: "CreateUser",
|
|
322
|
-
status: "running",
|
|
323
|
-
pendingCount: 1,
|
|
324
|
-
endedCount: 0,
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
#### Burst 72: Add getNodeStatus query (implement)
|
|
332
|
-
|
|
333
|
-
| Value | Can query node status from projection |
|
|
334
|
-
| Approach | Query NodeStatus collection with filter |
|
|
335
|
-
| Size | S |
|
|
336
|
-
| Files | `src/store/pipeline-read-model.ts` |
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
#### Burst 73: Add getItemStatus query (test)
|
|
341
|
-
|
|
342
|
-
| Value | Query item status from projection |
|
|
343
|
-
| Approach | `getItemStatus(correlationId, commandType, itemKey)` returns ItemStatusDocument or null |
|
|
344
|
-
| Size | S |
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
it("should return null when no item status exists", async () => {
|
|
348
|
-
const result = await readModel.getItemStatus("c1", "CreateUser", "item1");
|
|
349
|
-
expect(result).toBeNull();
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it("should return item status from ItemStatus collection", async () => {
|
|
353
|
-
const collection =
|
|
354
|
-
database.collection<WithId<ItemStatusDocument>>("ItemStatus");
|
|
355
|
-
await collection.insertOne({
|
|
356
|
-
_id: "is-c1-CreateUser-item1",
|
|
357
|
-
correlationId: "c1",
|
|
358
|
-
commandType: "CreateUser",
|
|
359
|
-
itemKey: "item1",
|
|
360
|
-
currentRequestId: "r1",
|
|
361
|
-
status: "running",
|
|
362
|
-
attemptCount: 1,
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
const result = await readModel.getItemStatus("c1", "CreateUser", "item1");
|
|
366
|
-
expect(result).toMatchObject({
|
|
367
|
-
correlationId: "c1",
|
|
368
|
-
commandType: "CreateUser",
|
|
369
|
-
itemKey: "item1",
|
|
370
|
-
attemptCount: 1,
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
---
|
|
376
|
-
|
|
377
|
-
#### Burst 74: Add getItemStatus query (implement)
|
|
378
|
-
|
|
379
|
-
| Value | Can query item status from projection |
|
|
380
|
-
| Approach | Query ItemStatus collection with filter |
|
|
381
|
-
| Size | S |
|
|
382
|
-
| Files | `src/store/pipeline-read-model.ts` |
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
#### Burst 75: Replace previousStatus lookup (test)
|
|
387
|
-
|
|
388
|
-
| Value | Get previousStatus from projection instead of cache |
|
|
389
|
-
| Approach | Verify `updateNodeStatus` emits correct previousStatus |
|
|
390
|
-
| Size | M |
|
|
391
|
-
|
|
392
|
-
Existing tests should pass with projection-based previousStatus lookup.
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
#### Burst 76: Replace previousStatus lookup (implement)
|
|
397
|
-
|
|
398
|
-
| Value | Remove nodeStatusCache usage in updateNodeStatus |
|
|
399
|
-
| Approach | Query `readModel.getNodeStatus()` for previousStatus |
|
|
400
|
-
| Size | M |
|
|
401
|
-
| Files | `src/server/pipeline-server.ts` lines 468-478 |
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
private async updateNodeStatus(correlationId: string, commandName: string, status: NodeStatus): Promise<void> {
|
|
405
|
-
const existing = await this.eventStoreContext.readModel.getNodeStatus(correlationId, commandName);
|
|
406
|
-
const previousStatus: NodeStatus = existing?.status ?? 'idle';
|
|
407
|
-
await this.emitNodeStatusChanged(correlationId, commandName, status, previousStatus);
|
|
408
|
-
await this.broadcastNodeStatusChanged(correlationId, commandName, status, previousStatus);
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
#### Burst 77: Replace hasCorrelation check (test)
|
|
415
|
-
|
|
416
|
-
| Value | Detect new correlationId via projection |
|
|
417
|
-
| Approach | Test that PipelineRunStarted is emitted only for new correlationIds |
|
|
418
|
-
| Size | M |
|
|
419
|
-
|
|
420
|
-
Existing broadcast tests should pass with projection-based detection.
|
|
421
|
-
|
|
422
|
-
---
|
|
423
|
-
|
|
424
|
-
#### Burst 78: Replace hasCorrelation check (implement)
|
|
425
|
-
|
|
426
|
-
| Value | Remove nodeStatusCache.has() check |
|
|
427
|
-
| Approach | Use `readModel.hasCorrelation()` instead |
|
|
428
|
-
| Size | M |
|
|
429
|
-
| Files | `src/server/pipeline-server.ts` line 874 |
|
|
430
|
-
|
|
431
|
-
```typescript
|
|
432
|
-
const isNewCorrelationId =
|
|
433
|
-
!(await this.eventStoreContext.readModel.hasCorrelation(
|
|
434
|
-
command.correlationId
|
|
435
|
-
));
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
---
|
|
439
|
-
|
|
440
|
-
#### Burst 79: Replace item creation/update logic (test)
|
|
441
|
-
|
|
442
|
-
| Value | Get/create item status from projection |
|
|
443
|
-
| Approach | Test item creation with correct attemptCount from projection |
|
|
444
|
-
| Size | M |
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
it("should increment attemptCount on retry", async () => {
|
|
448
|
-
// First command creates item with attemptCount=1
|
|
449
|
-
// Second command (same itemKey) should have attemptCount=2
|
|
450
|
-
});
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
#### Burst 80: Replace item creation/update logic (implement)
|
|
456
|
-
|
|
457
|
-
| Value | Remove itemStatusCache from getOrCreateItemStatus |
|
|
458
|
-
| Approach | Query projection for existing item, derive attemptCount |
|
|
459
|
-
| Size | M |
|
|
460
|
-
| Files | `src/server/pipeline-server.ts` lines 520-565 |
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
private async getOrCreateItemStatus(
|
|
464
|
-
correlationId: string,
|
|
465
|
-
commandType: string,
|
|
466
|
-
itemKey: string,
|
|
467
|
-
requestId: string,
|
|
468
|
-
): Promise<{ attemptCount: number }> {
|
|
469
|
-
const existing = await this.eventStoreContext.readModel.getItemStatus(correlationId, commandType, itemKey);
|
|
470
|
-
const attemptCount = (existing?.attemptCount ?? 0) + 1;
|
|
471
|
-
|
|
472
|
-
await this.emitItemStatusChanged(
|
|
473
|
-
correlationId,
|
|
474
|
-
commandType,
|
|
475
|
-
itemKey,
|
|
476
|
-
requestId,
|
|
477
|
-
'running',
|
|
478
|
-
attemptCount,
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
return { attemptCount };
|
|
482
|
-
}
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
---
|
|
486
|
-
|
|
487
|
-
#### Burst 81: Replace item update logic (test)
|
|
488
|
-
|
|
489
|
-
| Value | Update item status via events only |
|
|
490
|
-
| Approach | Test item status update emits event with correct data |
|
|
491
|
-
| Size | S |
|
|
492
|
-
|
|
493
|
-
Existing tests should pass with event-only updates.
|
|
494
|
-
|
|
495
|
-
---
|
|
496
|
-
|
|
497
|
-
#### Burst 82: Replace item update logic (implement)
|
|
498
|
-
|
|
499
|
-
| Value | Remove itemStatusCache from updateItemStatus |
|
|
500
|
-
| Approach | Query projection for currentRequestId and attemptCount, emit event |
|
|
501
|
-
| Size | S |
|
|
502
|
-
| Files | `src/server/pipeline-server.ts` lines 567-591 |
|
|
503
|
-
|
|
504
|
-
```typescript
|
|
505
|
-
private async updateItemStatus(
|
|
506
|
-
correlationId: string,
|
|
507
|
-
commandType: string,
|
|
508
|
-
itemKey: string,
|
|
509
|
-
status: 'running' | 'success' | 'error',
|
|
510
|
-
): Promise<void> {
|
|
511
|
-
const existing = await this.eventStoreContext.readModel.getItemStatus(correlationId, commandType, itemKey);
|
|
512
|
-
if (existing !== null) {
|
|
513
|
-
await this.emitItemStatusChanged(
|
|
514
|
-
correlationId,
|
|
515
|
-
commandType,
|
|
516
|
-
itemKey,
|
|
517
|
-
existing.currentRequestId,
|
|
518
|
-
status,
|
|
519
|
-
existing.attemptCount,
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
---
|
|
526
|
-
|
|
527
|
-
#### Burst 83: Remove nodeStatusCache field
|
|
528
|
-
|
|
529
|
-
| Value | Single source of truth |
|
|
530
|
-
| Approach | Delete the field declaration |
|
|
531
|
-
| Size | S |
|
|
532
|
-
| Files | `src/server/pipeline-server.ts` line 465 |
|
|
533
|
-
|
|
534
|
-
---
|
|
535
|
-
|
|
536
|
-
#### Burst 84: Remove itemStatusCache field
|
|
537
|
-
|
|
538
|
-
| Value | Single source of truth |
|
|
539
|
-
| Approach | Delete the field declaration |
|
|
540
|
-
| Size | S |
|
|
541
|
-
| Files | `src/server/pipeline-server.ts` line 466 |
|
|
542
|
-
|
|
543
|
-
---
|
|
544
|
-
|
|
545
|
-
#### Burst 85: Remove ItemStatus interface (if unused)
|
|
546
|
-
|
|
547
|
-
| Value | Clean up dead code |
|
|
548
|
-
| Approach | Delete if no longer referenced |
|
|
549
|
-
| Size | S |
|
|
550
|
-
| Files | `src/server/pipeline-server.ts` lines 44-51 |
|
|
551
|
-
|
|
552
|
-
---
|
|
553
|
-
|
|
554
|
-
#### Burst 86: Remove latestCorrelationId field
|
|
555
|
-
|
|
556
|
-
| Value | Derive from LatestRunProjection |
|
|
557
|
-
| Approach | Query LatestRun projection instead |
|
|
558
|
-
| Size | S |
|
|
559
|
-
| Files | `src/server/pipeline-server.ts` lines 67, 877 |
|
|
560
|
-
|
|
561
|
-
---
|
|
562
|
-
|
|
563
|
-
#### Burst 87: Final verification and cleanup
|
|
564
|
-
|
|
565
|
-
| Value | All tests pass, no dual state |
|
|
566
|
-
| Approach | Run full test suite, verify 100% coverage |
|
|
567
|
-
| Size | M |
|
|
568
|
-
|
|
569
|
-
**Success Criteria:**
|
|
570
|
-
|
|
571
|
-
1. ✅ `nodeStatusCache` field removed
|
|
572
|
-
2. ✅ `itemStatusCache` field removed
|
|
573
|
-
3. ✅ `ItemStatus` interface removed (if unused)
|
|
574
|
-
4. ✅ `latestCorrelationId` derived from projection
|
|
575
|
-
5. ✅ All state queries go through `PipelineReadModel`
|
|
576
|
-
6. ✅ All tests pass with 100% coverage
|
|
577
|
-
7. ✅ No mutable Maps tracking runtime state in PipelineServer
|
|
578
|
-
|
|
579
|
-
---
|
|
580
|
-
|
|
581
|
-
### Phase 3: Phased Execution Pattern (Bursts 27-34)
|
|
582
|
-
|
|
583
|
-
#### Burst 27: ForEachBuilder Interface & TriggerBuilder.forEach()
|
|
584
|
-
|
|
585
|
-
| Value | Entry point to phased execution pattern |
|
|
586
|
-
| Approach | TriggerBuilder.forEach() returns ForEachBuilder |
|
|
587
|
-
| Size | S |
|
|
588
|
-
|
|
589
|
-
```typescript
|
|
590
|
-
it("should return ForEachBuilder from TriggerBuilder.forEach()", () => {
|
|
591
|
-
type ItemsEvent = { data: { items: Array<{ id: string }> } };
|
|
592
|
-
const builder = define("test")
|
|
593
|
-
.on("ItemsReady")
|
|
594
|
-
.forEach((e: ItemsEvent) => e.data.items);
|
|
595
|
-
|
|
596
|
-
expect(builder).toBeDefined();
|
|
597
|
-
expect(typeof builder.groupInto).toBe("function");
|
|
598
|
-
});
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
---
|
|
602
|
-
|
|
603
|
-
#### Burst 28: ForEachBuilder.groupInto() with phases
|
|
604
|
-
|
|
605
|
-
| Value | Define execution phases for items |
|
|
606
|
-
| Approach | groupInto() accepts phase names and classifier |
|
|
607
|
-
| Size | M |
|
|
608
|
-
|
|
609
|
-
```typescript
|
|
610
|
-
it("should configure phases with groupInto()", () => {
|
|
611
|
-
type Item = { id: string; type: "critical" | "normal" };
|
|
612
|
-
const pipeline = define("test")
|
|
613
|
-
.on("ItemsReady")
|
|
614
|
-
.forEach((e: { data: { items: Item[] } }) => e.data.items)
|
|
615
|
-
.groupInto(["critical", "normal"], (item: Item) => item.type)
|
|
616
|
-
.process("ProcessItem", (item: Item) => ({ itemId: item.id }))
|
|
617
|
-
.build();
|
|
618
|
-
|
|
619
|
-
const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
|
|
620
|
-
expect(handler.phases).toEqual(["critical", "normal"]);
|
|
621
|
-
});
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
---
|
|
625
|
-
|
|
626
|
-
#### Burst 29: PhasedBuilder.process() with emit factory
|
|
627
|
-
|
|
628
|
-
| Value | Define command emission for each item |
|
|
629
|
-
| Approach | process() accepts commandType and data factory |
|
|
630
|
-
| Size | S |
|
|
631
|
-
|
|
632
|
-
```typescript
|
|
633
|
-
it("should configure emitFactory with process()", () => {
|
|
634
|
-
type Item = { id: string };
|
|
635
|
-
const pipeline = define("test")
|
|
636
|
-
.on("ItemsReady")
|
|
637
|
-
.forEach((e: { data: { items: Item[] } }) => e.data.items)
|
|
638
|
-
.groupInto(["phase1"], () => "phase1")
|
|
639
|
-
.process("ProcessItem", (item: Item) => ({ itemId: item.id }))
|
|
640
|
-
.build();
|
|
641
|
-
|
|
642
|
-
const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
|
|
643
|
-
expect(typeof handler.emitFactory).toBe("function");
|
|
644
|
-
});
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
---
|
|
648
|
-
|
|
649
|
-
#### Burst 30: PhasedBuilder.stopOnFailure()
|
|
650
|
-
|
|
651
|
-
| Value | Configure failure behavior |
|
|
652
|
-
| Approach | stopOnFailure() sets flag in descriptor |
|
|
653
|
-
| Size | S |
|
|
654
|
-
|
|
655
|
-
```typescript
|
|
656
|
-
it("should set stopOnFailure flag", () => {
|
|
657
|
-
const pipeline = define("test")
|
|
658
|
-
.on("ItemsReady")
|
|
659
|
-
.forEach((e: { data: { items: unknown[] } }) => e.data.items)
|
|
660
|
-
.groupInto(["phase1"], () => "phase1")
|
|
661
|
-
.process("ProcessItem", () => ({}))
|
|
662
|
-
.stopOnFailure()
|
|
663
|
-
.build();
|
|
664
|
-
|
|
665
|
-
const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
|
|
666
|
-
expect(handler.stopOnFailure).toBe(true);
|
|
667
|
-
});
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
---
|
|
671
|
-
|
|
672
|
-
#### Burst 31: PhasedBuilder.onComplete()
|
|
673
|
-
|
|
674
|
-
| Value | Configure completion events |
|
|
675
|
-
| Approach | onComplete() sets success/failure event types |
|
|
676
|
-
| Size | M |
|
|
677
|
-
|
|
678
|
-
```typescript
|
|
679
|
-
it("should configure completion events", () => {
|
|
680
|
-
type Item = { id: string };
|
|
681
|
-
const pipeline = define("test")
|
|
682
|
-
.on("ItemsReady")
|
|
683
|
-
.forEach((e: { data: { items: Item[] } }) => e.data.items)
|
|
684
|
-
.groupInto(["phase1"], () => "phase1")
|
|
685
|
-
.process("ProcessItem", (item: Item) => ({ itemId: item.id }))
|
|
686
|
-
.onComplete({
|
|
687
|
-
success: "AllItemsProcessed",
|
|
688
|
-
failure: "ProcessingFailed",
|
|
689
|
-
itemKey: (e: { data: { itemId: string } }) => e.data.itemId,
|
|
690
|
-
})
|
|
691
|
-
.build();
|
|
692
|
-
|
|
693
|
-
const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
|
|
694
|
-
expect(handler.completion.successEvent).toBe("AllItemsProcessed");
|
|
695
|
-
expect(handler.completion.failureEvent).toBe("ProcessingFailed");
|
|
696
|
-
});
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
---
|
|
700
|
-
|
|
701
|
-
#### Burst 32: Chaining from PhasedBuilder
|
|
702
|
-
|
|
703
|
-
| Value | Continue pipeline definition after phased |
|
|
704
|
-
| Approach | PhasedBuilder returns to chain |
|
|
705
|
-
| Size | S |
|
|
706
|
-
|
|
707
|
-
```typescript
|
|
708
|
-
it("should chain on() from PhasedBuilder", () => {
|
|
709
|
-
const pipeline = define("test")
|
|
710
|
-
.on("ItemsReady")
|
|
711
|
-
.forEach((e: { data: { items: unknown[] } }) => e.data.items)
|
|
712
|
-
.groupInto(["phase1"], () => "phase1")
|
|
713
|
-
.process("ProcessItem", () => ({}))
|
|
714
|
-
.onComplete({ success: "Done", failure: "Failed", itemKey: () => "" })
|
|
715
|
-
.on("Done")
|
|
716
|
-
.emit("Notify", {})
|
|
717
|
-
.build();
|
|
718
|
-
|
|
719
|
-
expect(pipeline.descriptor.handlers).toHaveLength(2);
|
|
720
|
-
});
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
---
|
|
724
|
-
|
|
725
|
-
#### Burst 33: Default stopOnFailure behavior
|
|
726
|
-
|
|
727
|
-
| Value | Sensible defaults |
|
|
728
|
-
| Approach | stopOnFailure defaults to false |
|
|
729
|
-
| Size | S |
|
|
730
|
-
|
|
731
|
-
```typescript
|
|
732
|
-
it("should default stopOnFailure to false", () => {
|
|
733
|
-
const pipeline = define("test")
|
|
734
|
-
.on("ItemsReady")
|
|
735
|
-
.forEach((e: { data: { items: unknown[] } }) => e.data.items)
|
|
736
|
-
.groupInto(["phase1"], () => "phase1")
|
|
737
|
-
.process("ProcessItem", () => ({}))
|
|
738
|
-
.onComplete({ success: "Done", failure: "Failed", itemKey: () => "" })
|
|
739
|
-
.build();
|
|
740
|
-
|
|
741
|
-
const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
|
|
742
|
-
expect(handler.stopOnFailure).toBe(false);
|
|
743
|
-
});
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
---
|
|
747
|
-
|
|
748
|
-
#### Burst 34: Phase 3 Integration Test
|
|
749
|
-
|
|
750
|
-
| Value | Validate complete phased pipeline |
|
|
751
|
-
| Approach | Integration test with realistic scenario |
|
|
752
|
-
| Size | M |
|
|
753
|
-
|
|
754
|
-
```typescript
|
|
755
|
-
it("should create complete phased execution pipeline", () => {
|
|
756
|
-
type Component = { path: string; priority: "high" | "medium" | "low" };
|
|
757
|
-
type ComponentEvent = { data: { components: Component[] } };
|
|
758
|
-
|
|
759
|
-
const pipeline = define("component-processor")
|
|
760
|
-
.version("1.0.0")
|
|
761
|
-
.description("Process components in priority phases")
|
|
762
|
-
.on("ComponentsGenerated")
|
|
763
|
-
.when((e: ComponentEvent) => e.data.components.length > 0)
|
|
764
|
-
.forEach((e: ComponentEvent) => e.data.components)
|
|
765
|
-
.groupInto(["high", "medium", "low"], (c: Component) => c.priority)
|
|
766
|
-
.process("ImplementComponent", (c: Component) => ({
|
|
767
|
-
componentPath: c.path,
|
|
768
|
-
}))
|
|
769
|
-
.stopOnFailure()
|
|
770
|
-
.onComplete({
|
|
771
|
-
success: "AllComponentsImplemented",
|
|
772
|
-
failure: "ComponentImplementationFailed",
|
|
773
|
-
itemKey: (e: { data: { componentPath: string } }) => e.data.componentPath,
|
|
774
|
-
})
|
|
775
|
-
.build();
|
|
776
|
-
|
|
777
|
-
expect(pipeline.descriptor.name).toBe("component-processor");
|
|
778
|
-
const handler = pipeline.descriptor.handlers[0] as ForEachPhasedDescriptor;
|
|
779
|
-
expect(handler.type).toBe("foreach-phased");
|
|
780
|
-
expect(handler.phases).toEqual(["high", "medium", "low"]);
|
|
781
|
-
expect(handler.stopOnFailure).toBe(true);
|
|
782
|
-
});
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
---
|
|
786
|
-
|
|
787
|
-
### Phase 4: Custom Handlers (Bursts 35-37)
|
|
788
|
-
|
|
789
|
-
#### Burst 35: TriggerBuilder.handle() basic
|
|
790
|
-
|
|
791
|
-
| Value | Escape hatch for imperative logic |
|
|
792
|
-
| Approach | handle() accepts async handler function |
|
|
793
|
-
| Size | S |
|
|
794
|
-
|
|
795
|
-
```typescript
|
|
796
|
-
it("should capture custom handler", () => {
|
|
797
|
-
const handler = async (e: { data: unknown }) => {
|
|
798
|
-
console.log(e);
|
|
799
|
-
};
|
|
800
|
-
const pipeline = define("test").on("CustomEvent").handle(handler).build();
|
|
801
|
-
|
|
802
|
-
const desc = pipeline.descriptor.handlers[0] as CustomHandlerDescriptor;
|
|
803
|
-
expect(desc.type).toBe("custom");
|
|
804
|
-
expect(desc.handler).toBe(handler);
|
|
805
|
-
});
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
---
|
|
809
|
-
|
|
810
|
-
#### Burst 36: handle() with declaredEmits
|
|
811
|
-
|
|
812
|
-
| Value | Graph introspection support |
|
|
813
|
-
| Approach | Optional second param for declared emits |
|
|
814
|
-
| Size | S |
|
|
815
|
-
|
|
816
|
-
```typescript
|
|
817
|
-
it("should capture declaredEmits for graph introspection", () => {
|
|
818
|
-
const pipeline = define("test")
|
|
819
|
-
.on("CustomEvent")
|
|
820
|
-
.handle(async () => {}, { emits: ["EventA", "EventB"] })
|
|
821
|
-
.build();
|
|
822
|
-
|
|
823
|
-
const desc = pipeline.descriptor.handlers[0] as CustomHandlerDescriptor;
|
|
824
|
-
expect(desc.declaredEmits).toEqual(["EventA", "EventB"]);
|
|
825
|
-
});
|
|
826
|
-
```
|
|
827
|
-
|
|
828
|
-
---
|
|
829
|
-
|
|
830
|
-
#### Burst 37: Phase 4 Integration & Chaining
|
|
831
|
-
|
|
832
|
-
| Value | Complete custom handler support |
|
|
833
|
-
| Approach | Chaining from handle() |
|
|
834
|
-
| Size | S |
|
|
835
|
-
|
|
836
|
-
```typescript
|
|
837
|
-
it("should chain on() from handle()", () => {
|
|
838
|
-
const pipeline = define("test")
|
|
839
|
-
.on("EventA")
|
|
840
|
-
.handle(async () => {})
|
|
841
|
-
.on("EventB")
|
|
842
|
-
.emit("CommandB", {})
|
|
843
|
-
.build();
|
|
844
|
-
|
|
845
|
-
expect(pipeline.descriptor.handlers).toHaveLength(2);
|
|
846
|
-
});
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
---
|
|
850
|
-
|
|
851
|
-
### Phase 5: Graph Extraction (Bursts 38-42)
|
|
852
|
-
|
|
853
|
-
#### Burst 38: GraphIR type definition
|
|
854
|
-
|
|
855
|
-
| Value | Intermediate representation for visualization |
|
|
856
|
-
| Approach | Define nodes and edges types |
|
|
857
|
-
| Size | S |
|
|
858
|
-
|
|
859
|
-
```typescript
|
|
860
|
-
it("should define GraphIR with nodes and edges", () => {
|
|
861
|
-
const graph: GraphIR = {
|
|
862
|
-
nodes: [
|
|
863
|
-
{ id: "evt:Start", type: "event", label: "Start" },
|
|
864
|
-
{ id: "cmd:Process", type: "command", label: "Process" },
|
|
865
|
-
],
|
|
866
|
-
edges: [{ from: "evt:Start", to: "cmd:Process", label: "triggers" }],
|
|
867
|
-
};
|
|
868
|
-
expect(graph.nodes).toHaveLength(2);
|
|
869
|
-
});
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
---
|
|
873
|
-
|
|
874
|
-
#### Burst 39: Pipeline.toGraph() basic
|
|
875
|
-
|
|
876
|
-
| Value | Extract graph from pipeline |
|
|
877
|
-
| Approach | toGraph() method on Pipeline |
|
|
878
|
-
| Size | M |
|
|
879
|
-
|
|
880
|
-
```typescript
|
|
881
|
-
it("should extract graph from emit handler", () => {
|
|
882
|
-
const pipeline = define("test").on("Start").emit("Process", {}).build();
|
|
883
|
-
|
|
884
|
-
const graph = pipeline.toGraph();
|
|
885
|
-
expect(graph.nodes.some((n) => n.id === "evt:Start")).toBe(true);
|
|
886
|
-
expect(graph.nodes.some((n) => n.id === "cmd:Process")).toBe(true);
|
|
887
|
-
});
|
|
888
|
-
```
|
|
889
|
-
|
|
890
|
-
---
|
|
891
|
-
|
|
892
|
-
#### Burst 40: toGraph() with run-await handlers
|
|
893
|
-
|
|
894
|
-
| Value | Graph extraction for scatter-gather |
|
|
895
|
-
| Approach | Include await relationships |
|
|
896
|
-
| Size | M |
|
|
897
|
-
|
|
898
|
-
---
|
|
899
|
-
|
|
900
|
-
#### Burst 41: toGraph() with foreach-phased handlers
|
|
901
|
-
|
|
902
|
-
| Value | Graph extraction for phased execution |
|
|
903
|
-
| Approach | Include phase groupings |
|
|
904
|
-
| Size | M |
|
|
905
|
-
|
|
906
|
-
---
|
|
907
|
-
|
|
908
|
-
#### Burst 42: toGraph() with custom handlers
|
|
909
|
-
|
|
910
|
-
| Value | Graph extraction using declaredEmits |
|
|
911
|
-
| Approach | Use declaredEmits for edges |
|
|
912
|
-
| Size | S |
|
|
913
|
-
|
|
914
|
-
---
|
|
915
|
-
|
|
916
|
-
### Phase 6: Cloud Abstractions (Bursts 43-48)
|
|
917
|
-
|
|
918
|
-
(Deferred - interfaces only, no runtime implementation yet)
|
|
919
|
-
|
|
920
|
-
---
|
|
921
|
-
|
|
922
|
-
### Phase 7: Pipeline Runtime (Bursts 49-54)
|
|
923
|
-
|
|
924
|
-
(Deferred - requires Phase 6 abstractions)
|
|
925
|
-
|
|
926
|
-
---
|
|
927
|
-
|
|
928
|
-
## DONE
|
|
929
|
-
|
|
930
|
-
### Phase 13: PipelineRunCompleted Event (Bursts 107-112) ✅
|
|
931
|
-
|
|
932
|
-
- [x] Burst 107: Create QuiescenceTracker class with increment/decrement/isQuiescent (f4bc85c4)
|
|
933
|
-
- [x] Burst 108: Add debounce logic to QuiescenceTracker with configurable delay (411129cb)
|
|
934
|
-
- [x] Burst 109-111: Wire QuiescenceTracker into PipelineServer, emit PipelineRunCompleted (0011d83b)
|
|
935
|
-
- [x] Burst 112: Verify quiescence tracking handles retries naturally (bb928573)
|
|
936
|
-
|
|
937
|
-
---
|
|
938
|
-
|
|
939
|
-
### Command Concurrency Control (Bursts CG-1 to CG-8) ✅
|
|
940
|
-
|
|
941
|
-
- [x] Burst CG-1: Gate registration + passthrough (d0a733c8)
|
|
942
|
-
- [x] Burst CG-2: Cancel-in-progress (a6f2f3dc)
|
|
943
|
-
- [x] Burst CG-3: Queue enqueue + drain + FIFO (852f4f2b)
|
|
944
|
-
- [x] Burst CG-4: PipelineContext signal + server wiring (6f1ba55e)
|
|
945
|
-
- [x] Burst CG-5: processCommand integration (49b44544)
|
|
946
|
-
- [x] Burst CG-6: Cancel-in-progress through server (acf1064d)
|
|
947
|
-
- [x] Burst CG-7: Queue + dispatch paths through server (7a8a4bd3)
|
|
948
|
-
- [x] Burst CG-8: Config-level concurrency wiring (b5db28a1)
|
|
949
|
-
|
|
950
|
-
---
|
|
951
|
-
|
|
952
|
-
### V2 Engine Internal Swap ✅
|
|
953
|
-
|
|
954
|
-
- [x] Burst V2-1: Add processKeyed/getState/resetInstance to WorkflowProcessor [depends: none] (d7b0fbc6)
|
|
955
|
-
- [x] Burst V2-2: V2RuntimeBridge — settled path [depends: V2-1] (867c844a)
|
|
956
|
-
- [x] Burst V2-3: V2RuntimeBridge — phased path [depends: V2-1] (985add45)
|
|
957
|
-
- [x] Burst V2-4: Wire bridge into PipelineServer [depends: V2-2, V2-3] (df6e6fcf)
|
|
958
|
-
- [x] Burst V2-5+V2-6: Remove v1 runtime classes and update exports [depends: V2-4] (98b1025c)
|
|
959
|
-
|
|
960
|
-
---
|
|
961
|
-
|
|
962
|
-
### Phase 12: Consistent Non-Blocking Dispatch (Bursts 103-105) ✅
|
|
963
|
-
|
|
964
|
-
- [x] Burst 103: Add `await` to `startPhased` call in pipeline-runtime.ts (e9b391f4)
|
|
965
|
-
- [x] Burst 104: Make `sendCommand` non-blocking in pipeline-server.ts (2309cf5b)
|
|
966
|
-
- [x] Burst 105: Fix phased executor race condition in countPendingInPhase (03f4f951)
|
|
967
|
-
|
|
968
|
-
---
|
|
969
|
-
|
|
970
|
-
### Phase 8: CLI Integration (Bursts 67-70) ✅
|
|
971
|
-
|
|
972
|
-
Burst 67-70 complete. E2E tests validate CLI parity.
|
|
973
|
-
|
|
974
|
-
### Burst 69-70: E2E Tests for CLI Parity
|
|
975
|
-
|
|
976
|
-
Implemented:
|
|
977
|
-
|
|
978
|
-
- 8 E2E tests validating endpoint compatibility
|
|
979
|
-
- Tests for `/registry`, `/pipeline`, `/sessions`, `/messages`, `/stats`, `/command`
|
|
980
|
-
- Tests for command execution and event routing through pipeline
|
|
981
|
-
- Tests for pipeline chain with multiple handlers
|
|
982
|
-
|
|
983
|
-
All 99 tests pass with 100% coverage.
|
|
984
|
-
|
|
985
|
-
---
|
|
986
|
-
|
|
987
|
-
### Burst 67-68: Enhanced /pipeline Response
|
|
988
|
-
|
|
989
|
-
Implemented:
|
|
990
|
-
|
|
991
|
-
- Added `folds: []` to `/registry` response
|
|
992
|
-
- Added `commandToEvents` mapping from command handlers
|
|
993
|
-
- Added `eventToCommand` mapping from pipeline handlers
|
|
994
|
-
- Added `PipelineNode` shape with `id`, `name`, `title`, `status`
|
|
995
|
-
- 4 new tests for response shapes
|
|
996
|
-
|
|
997
|
-
All 91 tests pass with 100% coverage.
|
|
998
|
-
|
|
999
|
-
---
|
|
1000
|
-
|
|
1001
|
-
### Burst 53-54: AwaitTracker
|
|
1002
|
-
|
|
1003
|
-
Implemented:
|
|
1004
|
-
|
|
1005
|
-
- `AwaitTracker` class for scatter-gather completion tracking
|
|
1006
|
-
- `startAwaiting()` to register pending keys
|
|
1007
|
-
- `markComplete()` to mark individual keys as done
|
|
1008
|
-
- `isComplete()` to check if all keys are done
|
|
1009
|
-
- `getResults()` to collect results and clear tracking
|
|
1010
|
-
- 7 tests for await tracking functionality
|
|
1011
|
-
- Exported from package index
|
|
1012
|
-
|
|
1013
|
-
All 87 tests pass with 100% coverage.
|
|
1014
|
-
|
|
1015
|
-
---
|
|
1016
|
-
|
|
1017
|
-
### Burst 55-66: PipelineServer
|
|
1018
|
-
|
|
1019
|
-
Implemented:
|
|
1020
|
-
|
|
1021
|
-
- `PipelineServer` class with HTTP endpoints
|
|
1022
|
-
- `/health`, `/registry`, `/pipeline`, `/messages`, `/sessions`, `/stats` endpoints
|
|
1023
|
-
- `POST /command` with command handler validation and 404 for unknown commands
|
|
1024
|
-
- Event routing through registered pipelines
|
|
1025
|
-
- Custom handler context support (emit, sendCommand)
|
|
1026
|
-
- Command handlers returning multiple events
|
|
1027
|
-
- Integration test for complete workflow
|
|
1028
|
-
- 16 tests for server functionality
|
|
1029
|
-
- Exports added to public API
|
|
1030
|
-
|
|
1031
|
-
All 80 tests pass with 100% coverage.
|
|
1032
|
-
|
|
1033
|
-
---
|
|
1034
|
-
|
|
1035
|
-
### Burst 51-52: Run-await and ForEach-phased Runtime
|
|
1036
|
-
|
|
1037
|
-
Implemented:
|
|
1038
|
-
|
|
1039
|
-
- `handleEvent()` for run-await handlers with command dispatch
|
|
1040
|
-
- Data factory support in static run-await commands
|
|
1041
|
-
- ForEach-phased item processing with phase ordering
|
|
1042
|
-
- Custom handler receives PipelineContext
|
|
1043
|
-
|
|
1044
|
-
All 80 tests pass with 100% coverage.
|
|
1045
|
-
|
|
1046
|
-
---
|
|
1047
|
-
|
|
1048
|
-
### Burst 45-50: PipelineRuntime Core
|
|
1049
|
-
|
|
1050
|
-
Implemented:
|
|
1051
|
-
|
|
1052
|
-
- `PipelineRuntime` class with descriptor and handler index
|
|
1053
|
-
- `getHandlersForEvent()` for O(1) handler lookup by event type
|
|
1054
|
-
- `getMatchingHandlers()` with predicate filtering
|
|
1055
|
-
- `handleEvent()` for emit and custom handlers
|
|
1056
|
-
- Data factory resolution for emit handlers
|
|
1057
|
-
- 7 tests for runtime functionality
|
|
1058
|
-
|
|
1059
|
-
All 59 tests pass with 100% coverage.
|
|
1060
|
-
|
|
1061
|
-
---
|
|
1062
|
-
|
|
1063
|
-
### Burst 43-44: PipelineContext & RuntimeConfig
|
|
1064
|
-
|
|
1065
|
-
Implemented:
|
|
1066
|
-
|
|
1067
|
-
- `PipelineContext` interface with `emit()`, `sendCommand()`, `correlationId`
|
|
1068
|
-
- `RuntimeConfig` interface with optional `defaultTimeout`
|
|
1069
|
-
- 4 tests for context/config types
|
|
1070
|
-
|
|
1071
|
-
---
|
|
1072
|
-
|
|
1073
|
-
### Burst 38-42: Phase 5 Graph Extraction
|
|
1074
|
-
|
|
1075
|
-
Implemented:
|
|
1076
|
-
|
|
1077
|
-
- `GraphIR` type with `nodes` and `edges` arrays
|
|
1078
|
-
- `GraphNode` with `id`, `type` ('event' | 'command'), `label`
|
|
1079
|
-
- `GraphEdge` with `from`, `to`, optional `label`
|
|
1080
|
-
- `Pipeline.toGraph()` method for all handler types
|
|
1081
|
-
- Emit handler graph extraction
|
|
1082
|
-
- Run-await handler graph extraction with success/failure events
|
|
1083
|
-
- Foreach-phased handler graph extraction with completion events
|
|
1084
|
-
- Custom handler graph extraction using `declaredEmits`
|
|
1085
|
-
- Node deduplication
|
|
1086
|
-
- 9 tests for graph functionality
|
|
1087
|
-
|
|
1088
|
-
All 46 tests pass with 100% coverage.
|
|
1089
|
-
|
|
1090
|
-
---
|
|
1091
|
-
|
|
1092
|
-
### Burst 35-37: Phase 4 Custom Handlers
|
|
1093
|
-
|
|
1094
|
-
Implemented:
|
|
1095
|
-
|
|
1096
|
-
- `TriggerBuilder.handle()` with async handler function
|
|
1097
|
-
- `handle()` with `declaredEmits` option for graph introspection
|
|
1098
|
-
- `HandleChain` for chaining `on()` and `build()`
|
|
1099
|
-
- 3 tests for custom handler functionality
|
|
1100
|
-
|
|
1101
|
-
All 37 tests pass with 100% coverage.
|
|
1102
|
-
|
|
1103
|
-
---
|
|
1104
|
-
|
|
1105
|
-
### Burst 27-34: Phase 3 Phased Execution Pattern
|
|
1106
|
-
|
|
1107
|
-
Implemented:
|
|
1108
|
-
|
|
1109
|
-
- `TriggerBuilder.forEach()` returns `ForEachBuilder`
|
|
1110
|
-
- `ForEachBuilder.groupInto()` with phase classifier
|
|
1111
|
-
- `PhasedBuilder.process()` with emit factory
|
|
1112
|
-
- `PhasedChain.stopOnFailure()` optional flag
|
|
1113
|
-
- `PhasedChain.onComplete()` with success/failure events
|
|
1114
|
-
- `PhasedTerminal` for chaining
|
|
1115
|
-
- Integration test with complete phased pipeline
|
|
1116
|
-
|
|
1117
|
-
All 34 tests pass with 100% coverage.
|
|
1118
|
-
|
|
1119
|
-
---
|
|
1120
|
-
|
|
1121
|
-
### Burst 19-26: Phase 2 Scatter-Gather Pattern
|
|
1122
|
-
|
|
1123
|
-
Implemented:
|
|
1124
|
-
|
|
1125
|
-
- `TriggerBuilder.run()` returns `RunBuilder`
|
|
1126
|
-
- `run()` accepts static `CommandDispatch[]` or factory function
|
|
1127
|
-
- `RunBuilder.awaitAll()` with key extractor and optional timeout
|
|
1128
|
-
- `GatherBuilder.onSuccess()` with `SuccessContext`
|
|
1129
|
-
- `GatherBuilder.onFailure()` with `FailureContext`
|
|
1130
|
-
- Chaining: `on()` and `build()` from `GatherBuilder`
|
|
1131
|
-
- Integration test with complete scatter-gather pipeline
|
|
1132
|
-
|
|
1133
|
-
New types added:
|
|
1134
|
-
|
|
1135
|
-
- `SuccessContext<T>` - results, duration, triggerEvent
|
|
1136
|
-
- `FailureContext<T>` - failures, successes, triggerEvent
|
|
1137
|
-
- `GatherEventConfig<T>` - eventType, dataFactory
|
|
1138
|
-
|
|
1139
|
-
All 26 tests pass with 100% coverage.
|
|
1140
|
-
|
|
1141
|
-
---
|
|
1142
|
-
|
|
1143
|
-
### Burst 13, 15, 18: Remaining Phase 1 features
|
|
1144
|
-
|
|
1145
|
-
- emit() with data factory
|
|
1146
|
-
- when() predicate for conditional execution
|
|
1147
|
-
- Integration test with complete pipeline
|
|
1148
|
-
|
|
1149
|
-
All Phase 1 tests pass with 100% coverage.
|
|
1150
|
-
|
|
1151
|
-
---
|
|
1152
|
-
|
|
1153
|
-
### Burst 6-12, 14, 16, 17: Builder API (batched)
|
|
1154
|
-
|
|
1155
|
-
Implemented:
|
|
1156
|
-
|
|
1157
|
-
- define() entry point
|
|
1158
|
-
- version() and description()
|
|
1159
|
-
- build() returns frozen Pipeline
|
|
1160
|
-
- on() returns TriggerBuilder
|
|
1161
|
-
- emit() with static data
|
|
1162
|
-
- EmitChain.build() captures handler
|
|
1163
|
-
- Parallel emit() chain
|
|
1164
|
-
- EmitChain.on() continues chain
|
|
1165
|
-
- key() named extractors
|
|
1166
|
-
|
|
1167
|
-
All tests pass with 100% coverage.
|
|
1168
|
-
|
|
1169
|
-
---
|
|
1170
|
-
|
|
1171
|
-
### Burst 5: PipelineDescriptor Type
|
|
1172
|
-
|
|
1173
|
-
| Value | Pipeline structure definition |
|
|
1174
|
-
| Approach | Interface with metadata + handlers array |
|
|
1175
|
-
| Size | S |
|
|
1176
|
-
|
|
1177
|
-
```typescript
|
|
1178
|
-
it("should create PipelineDescriptor", () => {
|
|
1179
|
-
const descriptor: PipelineDescriptor = {
|
|
1180
|
-
name: "test-pipeline",
|
|
1181
|
-
version: "1.0.0",
|
|
1182
|
-
keys: new Map(),
|
|
1183
|
-
handlers: [],
|
|
1184
|
-
};
|
|
1185
|
-
expect(descriptor.name).toBe("test-pipeline");
|
|
1186
|
-
});
|
|
1187
|
-
```
|
|
1188
|
-
|
|
1189
|
-
---
|
|
1190
|
-
|
|
1191
|
-
### Burst 4: dispatch() Helper
|
|
1192
|
-
|
|
1193
|
-
| Value | Ergonomic CommandDispatch creation |
|
|
1194
|
-
| Approach | Simple factory function |
|
|
1195
|
-
| Size | S |
|
|
1196
|
-
|
|
1197
|
-
```typescript
|
|
1198
|
-
it("should create CommandDispatch via dispatch()", () => {
|
|
1199
|
-
const cmd = dispatch("CheckTests", { targetDirectory: "./src" });
|
|
1200
|
-
expect(cmd).toEqual({
|
|
1201
|
-
commandType: "CheckTests",
|
|
1202
|
-
data: { targetDirectory: "./src" },
|
|
1203
|
-
});
|
|
1204
|
-
});
|
|
1205
|
-
```
|
|
1206
|
-
|
|
1207
|
-
---
|
|
1208
|
-
|
|
1209
|
-
### Burst 3: CommandDispatch Type
|
|
1210
|
-
|
|
1211
|
-
| Value | Dispatch instruction type |
|
|
1212
|
-
| Approach | Simple interface with commandType + data |
|
|
1213
|
-
| Size | S |
|
|
1214
|
-
|
|
1215
|
-
```typescript
|
|
1216
|
-
it("should create CommandDispatch with static data", () => {
|
|
1217
|
-
const cmd: CommandDispatch = {
|
|
1218
|
-
commandType: "CheckTests",
|
|
1219
|
-
data: { targetDirectory: "./src", scope: "slice" },
|
|
1220
|
-
};
|
|
1221
|
-
expect(cmd).toEqual({
|
|
1222
|
-
commandType: "CheckTests",
|
|
1223
|
-
data: { targetDirectory: "./src", scope: "slice" },
|
|
1224
|
-
});
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
it("should create CommandDispatch with data factory", () => {
|
|
1228
|
-
const cmd: CommandDispatch = {
|
|
1229
|
-
commandType: "ImplementSlice",
|
|
1230
|
-
data: (e) => ({ slicePath: e.data.path }),
|
|
1231
|
-
};
|
|
1232
|
-
const event: Event = { type: "SliceGenerated", data: { path: "./slice" } };
|
|
1233
|
-
const resolved = typeof cmd.data === "function" ? cmd.data(event) : cmd.data;
|
|
1234
|
-
expect(resolved).toEqual({ slicePath: "./slice" });
|
|
1235
|
-
});
|
|
1236
|
-
```
|
|
1237
|
-
|
|
1238
|
-
---
|
|
1239
|
-
|
|
1240
|
-
### Burst 2: Core Types
|
|
1241
|
-
|
|
1242
|
-
| Value | Foundation types |
|
|
1243
|
-
| Approach | Re-export message-bus types + add pipeline types |
|
|
1244
|
-
| Size | S |
|
|
1245
|
-
|
|
1246
|
-
```typescript
|
|
1247
|
-
it("should re-export Command and Event from message-bus", () => {
|
|
1248
|
-
const cmd: Command = { type: "Test", data: {} };
|
|
1249
|
-
const evt: Event = { type: "TestDone", data: {} };
|
|
1250
|
-
expect(cmd.type).toBe("Test");
|
|
1251
|
-
expect(evt.type).toBe("TestDone");
|
|
1252
|
-
});
|
|
1253
|
-
```
|
|
1254
|
-
|
|
1255
|
-
---
|
|
1256
|
-
|
|
1257
|
-
### Burst 1: Package Scaffold
|
|
1258
|
-
|
|
1259
|
-
| Value | Foundation for all work |
|
|
1260
|
-
| Approach | Copy patterns from `@auto-engineer/id` |
|
|
1261
|
-
| Size | S |
|
|
1262
|
-
|
|
1263
|
-
**Files:**
|
|
1264
|
-
|
|
1265
|
-
- `package.json` - deps: `@auto-engineer/message-bus: workspace:*`
|
|
1266
|
-
- `tsconfig.json` - extends base, composite: true
|
|
1267
|
-
- `tsconfig.test.json` - includes \*.specs.ts
|
|
1268
|
-
- `vitest.config.ts` - 100% coverage thresholds
|
|
1269
|
-
- `src/index.ts` - empty export
|