@sparkleideas/shared 3.0.0-alpha.7

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 (96) hide show
  1. package/README.md +323 -0
  2. package/__tests__/hooks/bash-safety.test.ts +289 -0
  3. package/__tests__/hooks/file-organization.test.ts +335 -0
  4. package/__tests__/hooks/git-commit.test.ts +336 -0
  5. package/__tests__/hooks/index.ts +23 -0
  6. package/__tests__/hooks/session-hooks.test.ts +357 -0
  7. package/__tests__/hooks/task-hooks.test.ts +193 -0
  8. package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
  9. package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
  10. package/docs/EVENTS_README.md +352 -0
  11. package/package.json +39 -0
  12. package/src/core/config/defaults.ts +207 -0
  13. package/src/core/config/index.ts +15 -0
  14. package/src/core/config/loader.ts +271 -0
  15. package/src/core/config/schema.ts +188 -0
  16. package/src/core/config/validator.ts +209 -0
  17. package/src/core/event-bus.ts +236 -0
  18. package/src/core/index.ts +22 -0
  19. package/src/core/interfaces/agent.interface.ts +251 -0
  20. package/src/core/interfaces/coordinator.interface.ts +363 -0
  21. package/src/core/interfaces/event.interface.ts +267 -0
  22. package/src/core/interfaces/index.ts +19 -0
  23. package/src/core/interfaces/memory.interface.ts +332 -0
  24. package/src/core/interfaces/task.interface.ts +223 -0
  25. package/src/core/orchestrator/event-coordinator.ts +122 -0
  26. package/src/core/orchestrator/health-monitor.ts +214 -0
  27. package/src/core/orchestrator/index.ts +89 -0
  28. package/src/core/orchestrator/lifecycle-manager.ts +263 -0
  29. package/src/core/orchestrator/session-manager.ts +279 -0
  30. package/src/core/orchestrator/task-manager.ts +317 -0
  31. package/src/events/domain-events.ts +584 -0
  32. package/src/events/event-store.test.ts +387 -0
  33. package/src/events/event-store.ts +588 -0
  34. package/src/events/example-usage.ts +293 -0
  35. package/src/events/index.ts +90 -0
  36. package/src/events/projections.ts +561 -0
  37. package/src/events/state-reconstructor.ts +349 -0
  38. package/src/events.ts +367 -0
  39. package/src/hooks/INTEGRATION.md +658 -0
  40. package/src/hooks/README.md +532 -0
  41. package/src/hooks/example-usage.ts +499 -0
  42. package/src/hooks/executor.ts +379 -0
  43. package/src/hooks/hooks.test.ts +421 -0
  44. package/src/hooks/index.ts +131 -0
  45. package/src/hooks/registry.ts +333 -0
  46. package/src/hooks/safety/bash-safety.ts +604 -0
  47. package/src/hooks/safety/file-organization.ts +473 -0
  48. package/src/hooks/safety/git-commit.ts +623 -0
  49. package/src/hooks/safety/index.ts +46 -0
  50. package/src/hooks/session-hooks.ts +559 -0
  51. package/src/hooks/task-hooks.ts +513 -0
  52. package/src/hooks/types.ts +357 -0
  53. package/src/hooks/verify-exports.test.ts +125 -0
  54. package/src/index.ts +195 -0
  55. package/src/mcp/connection-pool.ts +438 -0
  56. package/src/mcp/index.ts +183 -0
  57. package/src/mcp/server.ts +774 -0
  58. package/src/mcp/session-manager.ts +428 -0
  59. package/src/mcp/tool-registry.ts +566 -0
  60. package/src/mcp/transport/http.ts +557 -0
  61. package/src/mcp/transport/index.ts +294 -0
  62. package/src/mcp/transport/stdio.ts +324 -0
  63. package/src/mcp/transport/websocket.ts +484 -0
  64. package/src/mcp/types.ts +565 -0
  65. package/src/plugin-interface.ts +663 -0
  66. package/src/plugin-loader.ts +638 -0
  67. package/src/plugin-registry.ts +604 -0
  68. package/src/plugins/index.ts +34 -0
  69. package/src/plugins/official/hive-mind-plugin.ts +330 -0
  70. package/src/plugins/official/index.ts +24 -0
  71. package/src/plugins/official/maestro-plugin.ts +508 -0
  72. package/src/plugins/types.ts +108 -0
  73. package/src/resilience/bulkhead.ts +277 -0
  74. package/src/resilience/circuit-breaker.ts +326 -0
  75. package/src/resilience/index.ts +26 -0
  76. package/src/resilience/rate-limiter.ts +420 -0
  77. package/src/resilience/retry.ts +224 -0
  78. package/src/security/index.ts +39 -0
  79. package/src/security/input-validation.ts +265 -0
  80. package/src/security/secure-random.ts +159 -0
  81. package/src/services/index.ts +16 -0
  82. package/src/services/v3-progress.service.ts +505 -0
  83. package/src/types/agent.types.ts +144 -0
  84. package/src/types/index.ts +22 -0
  85. package/src/types/mcp.types.ts +300 -0
  86. package/src/types/memory.types.ts +263 -0
  87. package/src/types/swarm.types.ts +255 -0
  88. package/src/types/task.types.ts +205 -0
  89. package/src/types.ts +367 -0
  90. package/src/utils/secure-logger.d.ts +69 -0
  91. package/src/utils/secure-logger.d.ts.map +1 -0
  92. package/src/utils/secure-logger.js +208 -0
  93. package/src/utils/secure-logger.js.map +1 -0
  94. package/src/utils/secure-logger.ts +257 -0
  95. package/tmp.json +0 -0
  96. package/tsconfig.json +9 -0
@@ -0,0 +1,561 @@
1
+ /**
2
+ * Event Projections for Read Models (ADR-007)
3
+ *
4
+ * Build read models from domain events using projections.
5
+ * Projections listen to events and maintain queryable state.
6
+ *
7
+ * Implemented Projections:
8
+ * - AgentStateProjection - Current state of all agents
9
+ * - TaskHistoryProjection - Complete task execution history
10
+ * - MemoryIndexProjection - Memory access patterns and index
11
+ *
12
+ * @module v3/shared/events/projections
13
+ */
14
+
15
+ import { EventEmitter } from 'node:events';
16
+ import { DomainEvent } from './domain-events.js';
17
+ import { EventStore } from './event-store.js';
18
+ import { AgentId, TaskId, AgentStatus, TaskStatus } from '../types.js';
19
+
20
+ // =============================================================================
21
+ // Projection Base Class
22
+ // =============================================================================
23
+
24
+ export abstract class Projection extends EventEmitter {
25
+ protected initialized: boolean = false;
26
+
27
+ constructor(protected eventStore: EventStore) {
28
+ super();
29
+ }
30
+
31
+ /**
32
+ * Initialize the projection by replaying events
33
+ */
34
+ async initialize(): Promise<void> {
35
+ if (this.initialized) return;
36
+
37
+ // Replay all events to build current state
38
+ for await (const event of this.eventStore.replay()) {
39
+ await this.handle(event);
40
+ }
41
+
42
+ this.initialized = true;
43
+ this.emit('initialized');
44
+ }
45
+
46
+ /**
47
+ * Handle a domain event
48
+ */
49
+ abstract handle(event: DomainEvent): Promise<void>;
50
+
51
+ /**
52
+ * Reset the projection state
53
+ */
54
+ abstract reset(): void;
55
+ }
56
+
57
+ // =============================================================================
58
+ // Agent State Projection
59
+ // =============================================================================
60
+
61
+ export interface AgentProjectionState {
62
+ id: AgentId;
63
+ role: string;
64
+ domain: string;
65
+ status: AgentStatus;
66
+ currentTask: TaskId | null;
67
+ completedTasks: TaskId[];
68
+ failedTasks: TaskId[];
69
+ totalTaskDuration: number;
70
+ taskCount: number;
71
+ errorCount: number;
72
+ spawnedAt: number;
73
+ startedAt: number | null;
74
+ stoppedAt: number | null;
75
+ lastActivityAt: number;
76
+ }
77
+
78
+ export class AgentStateProjection extends Projection {
79
+ private agents: Map<AgentId, AgentProjectionState> = new Map();
80
+
81
+ /**
82
+ * Get state for a specific agent
83
+ */
84
+ getAgent(agentId: AgentId): AgentProjectionState | null {
85
+ return this.agents.get(agentId) || null;
86
+ }
87
+
88
+ /**
89
+ * Get all agents
90
+ */
91
+ getAllAgents(): AgentProjectionState[] {
92
+ return Array.from(this.agents.values());
93
+ }
94
+
95
+ /**
96
+ * Get agents by status
97
+ */
98
+ getAgentsByStatus(status: AgentStatus): AgentProjectionState[] {
99
+ return this.getAllAgents().filter((agent) => agent.status === status);
100
+ }
101
+
102
+ /**
103
+ * Get agents by domain
104
+ */
105
+ getAgentsByDomain(domain: string): AgentProjectionState[] {
106
+ return this.getAllAgents().filter((agent) => agent.domain === domain);
107
+ }
108
+
109
+ /**
110
+ * Get active agent count
111
+ */
112
+ getActiveAgentCount(): number {
113
+ return this.getAgentsByStatus('active').length;
114
+ }
115
+
116
+ async handle(event: DomainEvent): Promise<void> {
117
+ switch (event.type) {
118
+ case 'agent:spawned':
119
+ this.handleAgentSpawned(event);
120
+ break;
121
+ case 'agent:started':
122
+ this.handleAgentStarted(event);
123
+ break;
124
+ case 'agent:stopped':
125
+ this.handleAgentStopped(event);
126
+ break;
127
+ case 'agent:failed':
128
+ this.handleAgentFailed(event);
129
+ break;
130
+ case 'agent:status-changed':
131
+ this.handleAgentStatusChanged(event);
132
+ break;
133
+ case 'agent:task-assigned':
134
+ this.handleAgentTaskAssigned(event);
135
+ break;
136
+ case 'agent:task-completed':
137
+ this.handleAgentTaskCompleted(event);
138
+ break;
139
+ }
140
+ }
141
+
142
+ reset(): void {
143
+ this.agents.clear();
144
+ this.emit('reset');
145
+ }
146
+
147
+ private handleAgentSpawned(event: DomainEvent): void {
148
+ const { agentId, role, domain } = event.payload;
149
+
150
+ this.agents.set(agentId as AgentId, {
151
+ id: agentId as AgentId,
152
+ role: role as string,
153
+ domain: domain as string,
154
+ status: 'idle',
155
+ currentTask: null,
156
+ completedTasks: [],
157
+ failedTasks: [],
158
+ totalTaskDuration: 0,
159
+ taskCount: 0,
160
+ errorCount: 0,
161
+ spawnedAt: event.timestamp,
162
+ startedAt: null,
163
+ stoppedAt: null,
164
+ lastActivityAt: event.timestamp,
165
+ });
166
+
167
+ this.emit('agent:spawned', { agentId });
168
+ }
169
+
170
+ private handleAgentStarted(event: DomainEvent): void {
171
+ const { agentId } = event.payload;
172
+ const agent = this.agents.get(agentId as AgentId);
173
+
174
+ if (agent) {
175
+ agent.status = 'active';
176
+ agent.startedAt = event.timestamp;
177
+ agent.lastActivityAt = event.timestamp;
178
+ this.emit('agent:started', { agentId });
179
+ }
180
+ }
181
+
182
+ private handleAgentStopped(event: DomainEvent): void {
183
+ const { agentId } = event.payload;
184
+ const agent = this.agents.get(agentId as AgentId);
185
+
186
+ if (agent) {
187
+ agent.status = 'completed';
188
+ agent.stoppedAt = event.timestamp;
189
+ agent.lastActivityAt = event.timestamp;
190
+ this.emit('agent:stopped', { agentId });
191
+ }
192
+ }
193
+
194
+ private handleAgentFailed(event: DomainEvent): void {
195
+ const { agentId } = event.payload;
196
+ const agent = this.agents.get(agentId as AgentId);
197
+
198
+ if (agent) {
199
+ agent.status = 'error';
200
+ agent.errorCount++;
201
+ agent.lastActivityAt = event.timestamp;
202
+ this.emit('agent:failed', { agentId });
203
+ }
204
+ }
205
+
206
+ private handleAgentStatusChanged(event: DomainEvent): void {
207
+ const { agentId, newStatus } = event.payload;
208
+ const agent = this.agents.get(agentId as AgentId);
209
+
210
+ if (agent) {
211
+ agent.status = newStatus as AgentStatus;
212
+ agent.lastActivityAt = event.timestamp;
213
+ this.emit('agent:status-changed', { agentId, status: newStatus });
214
+ }
215
+ }
216
+
217
+ private handleAgentTaskAssigned(event: DomainEvent): void {
218
+ const { agentId, taskId } = event.payload;
219
+ const agent = this.agents.get(agentId as AgentId);
220
+
221
+ if (agent) {
222
+ agent.currentTask = taskId as TaskId;
223
+ agent.status = 'active';
224
+ agent.lastActivityAt = event.timestamp;
225
+ this.emit('agent:task-assigned', { agentId, taskId });
226
+ }
227
+ }
228
+
229
+ private handleAgentTaskCompleted(event: DomainEvent): void {
230
+ const { agentId, taskId, duration } = event.payload;
231
+ const agent = this.agents.get(agentId as AgentId);
232
+
233
+ if (agent) {
234
+ agent.completedTasks.push(taskId as TaskId);
235
+ agent.currentTask = null;
236
+ agent.taskCount++;
237
+ agent.totalTaskDuration += (duration as number) || 0;
238
+ agent.status = 'idle';
239
+ agent.lastActivityAt = event.timestamp;
240
+ this.emit('agent:task-completed', { agentId, taskId });
241
+ }
242
+ }
243
+ }
244
+
245
+ // =============================================================================
246
+ // Task History Projection
247
+ // =============================================================================
248
+
249
+ export interface TaskProjectionState {
250
+ id: TaskId;
251
+ type: string;
252
+ title: string;
253
+ status: TaskStatus;
254
+ priority: string;
255
+ assignedAgent: AgentId | null;
256
+ dependencies: TaskId[];
257
+ blockedBy: TaskId[];
258
+ createdAt: number;
259
+ startedAt: number | null;
260
+ completedAt: number | null;
261
+ failedAt: number | null;
262
+ duration: number | null;
263
+ result: unknown;
264
+ error: string | null;
265
+ retryCount: number;
266
+ }
267
+
268
+ export class TaskHistoryProjection extends Projection {
269
+ private tasks: Map<TaskId, TaskProjectionState> = new Map();
270
+
271
+ /**
272
+ * Get task by ID
273
+ */
274
+ getTask(taskId: TaskId): TaskProjectionState | null {
275
+ return this.tasks.get(taskId) || null;
276
+ }
277
+
278
+ /**
279
+ * Get all tasks
280
+ */
281
+ getAllTasks(): TaskProjectionState[] {
282
+ return Array.from(this.tasks.values());
283
+ }
284
+
285
+ /**
286
+ * Get tasks by status
287
+ */
288
+ getTasksByStatus(status: TaskStatus): TaskProjectionState[] {
289
+ return this.getAllTasks().filter((task) => task.status === status);
290
+ }
291
+
292
+ /**
293
+ * Get tasks by agent
294
+ */
295
+ getTasksByAgent(agentId: AgentId): TaskProjectionState[] {
296
+ return this.getAllTasks().filter((task) => task.assignedAgent === agentId);
297
+ }
298
+
299
+ /**
300
+ * Get completed task count
301
+ */
302
+ getCompletedTaskCount(): number {
303
+ return this.getTasksByStatus('completed').length;
304
+ }
305
+
306
+ /**
307
+ * Get average task duration
308
+ */
309
+ getAverageTaskDuration(): number {
310
+ const completed = this.getTasksByStatus('completed').filter((t) => t.duration !== null);
311
+
312
+ if (completed.length === 0) return 0;
313
+
314
+ const total = completed.reduce((sum, task) => sum + (task.duration || 0), 0);
315
+ return total / completed.length;
316
+ }
317
+
318
+ async handle(event: DomainEvent): Promise<void> {
319
+ switch (event.type) {
320
+ case 'task:created':
321
+ this.handleTaskCreated(event);
322
+ break;
323
+ case 'task:queued':
324
+ this.handleTaskQueued(event);
325
+ break;
326
+ case 'task:started':
327
+ this.handleTaskStarted(event);
328
+ break;
329
+ case 'task:completed':
330
+ this.handleTaskCompleted(event);
331
+ break;
332
+ case 'task:failed':
333
+ this.handleTaskFailed(event);
334
+ break;
335
+ case 'task:blocked':
336
+ this.handleTaskBlocked(event);
337
+ break;
338
+ }
339
+ }
340
+
341
+ reset(): void {
342
+ this.tasks.clear();
343
+ this.emit('reset');
344
+ }
345
+
346
+ private handleTaskCreated(event: DomainEvent): void {
347
+ const { taskId, taskType, title, priority, dependencies } = event.payload;
348
+
349
+ this.tasks.set(taskId as TaskId, {
350
+ id: taskId as TaskId,
351
+ type: taskType as string,
352
+ title: title as string,
353
+ status: 'pending',
354
+ priority: priority as string,
355
+ assignedAgent: null,
356
+ dependencies: (dependencies as TaskId[]) || [],
357
+ blockedBy: [],
358
+ createdAt: event.timestamp,
359
+ startedAt: null,
360
+ completedAt: null,
361
+ failedAt: null,
362
+ duration: null,
363
+ result: null,
364
+ error: null,
365
+ retryCount: 0,
366
+ });
367
+
368
+ this.emit('task:created', { taskId });
369
+ }
370
+
371
+ private handleTaskQueued(event: DomainEvent): void {
372
+ const { taskId } = event.payload;
373
+ const task = this.tasks.get(taskId as TaskId);
374
+
375
+ if (task) {
376
+ task.status = 'queued';
377
+ this.emit('task:queued', { taskId });
378
+ }
379
+ }
380
+
381
+ private handleTaskStarted(event: DomainEvent): void {
382
+ const { taskId, agentId } = event.payload;
383
+ const task = this.tasks.get(taskId as TaskId);
384
+
385
+ if (task) {
386
+ task.status = 'in-progress';
387
+ task.assignedAgent = agentId as AgentId;
388
+ task.startedAt = event.timestamp;
389
+ this.emit('task:started', { taskId, agentId });
390
+ }
391
+ }
392
+
393
+ private handleTaskCompleted(event: DomainEvent): void {
394
+ const { taskId, result, duration } = event.payload;
395
+ const task = this.tasks.get(taskId as TaskId);
396
+
397
+ if (task) {
398
+ task.status = 'completed';
399
+ task.completedAt = event.timestamp;
400
+ task.duration = (duration as number) || (task.startedAt ? event.timestamp - task.startedAt : null);
401
+ task.result = result;
402
+ this.emit('task:completed', { taskId });
403
+ }
404
+ }
405
+
406
+ private handleTaskFailed(event: DomainEvent): void {
407
+ const { taskId, error, retryCount } = event.payload;
408
+ const task = this.tasks.get(taskId as TaskId);
409
+
410
+ if (task) {
411
+ task.status = 'failed';
412
+ task.failedAt = event.timestamp;
413
+ task.error = error as string;
414
+ task.retryCount = retryCount as number;
415
+ this.emit('task:failed', { taskId });
416
+ }
417
+ }
418
+
419
+ private handleTaskBlocked(event: DomainEvent): void {
420
+ const { taskId, blockedBy } = event.payload;
421
+ const task = this.tasks.get(taskId as TaskId);
422
+
423
+ if (task) {
424
+ task.status = 'blocked';
425
+ task.blockedBy = blockedBy as TaskId[];
426
+ this.emit('task:blocked', { taskId, blockedBy });
427
+ }
428
+ }
429
+ }
430
+
431
+ // =============================================================================
432
+ // Memory Index Projection
433
+ // =============================================================================
434
+
435
+ export interface MemoryProjectionState {
436
+ id: string;
437
+ namespace: string;
438
+ key: string;
439
+ type: string;
440
+ size: number;
441
+ accessCount: number;
442
+ storedAt: number;
443
+ lastAccessedAt: number;
444
+ deletedAt: number | null;
445
+ isDeleted: boolean;
446
+ }
447
+
448
+ export class MemoryIndexProjection extends Projection {
449
+ private memories: Map<string, MemoryProjectionState> = new Map();
450
+
451
+ /**
452
+ * Get memory by ID
453
+ */
454
+ getMemory(memoryId: string): MemoryProjectionState | null {
455
+ return this.memories.get(memoryId) || null;
456
+ }
457
+
458
+ /**
459
+ * Get all active memories (not deleted)
460
+ */
461
+ getActiveMemories(): MemoryProjectionState[] {
462
+ return Array.from(this.memories.values()).filter((m) => !m.isDeleted);
463
+ }
464
+
465
+ /**
466
+ * Get memories by namespace
467
+ */
468
+ getMemoriesByNamespace(namespace: string): MemoryProjectionState[] {
469
+ return this.getActiveMemories().filter((m) => m.namespace === namespace);
470
+ }
471
+
472
+ /**
473
+ * Get most accessed memories
474
+ */
475
+ getMostAccessedMemories(limit: number = 10): MemoryProjectionState[] {
476
+ return this.getActiveMemories()
477
+ .sort((a, b) => b.accessCount - a.accessCount)
478
+ .slice(0, limit);
479
+ }
480
+
481
+ /**
482
+ * Get total memory size by namespace
483
+ */
484
+ getTotalSizeByNamespace(namespace: string): number {
485
+ return this.getMemoriesByNamespace(namespace).reduce((sum, m) => sum + m.size, 0);
486
+ }
487
+
488
+ async handle(event: DomainEvent): Promise<void> {
489
+ switch (event.type) {
490
+ case 'memory:stored':
491
+ this.handleMemoryStored(event);
492
+ break;
493
+ case 'memory:retrieved':
494
+ this.handleMemoryRetrieved(event);
495
+ break;
496
+ case 'memory:deleted':
497
+ this.handleMemoryDeleted(event);
498
+ break;
499
+ case 'memory:expired':
500
+ this.handleMemoryExpired(event);
501
+ break;
502
+ }
503
+ }
504
+
505
+ reset(): void {
506
+ this.memories.clear();
507
+ this.emit('reset');
508
+ }
509
+
510
+ private handleMemoryStored(event: DomainEvent): void {
511
+ const { memoryId, namespace, key, memoryType, size } = event.payload;
512
+
513
+ this.memories.set(memoryId as string, {
514
+ id: memoryId as string,
515
+ namespace: namespace as string,
516
+ key: key as string,
517
+ type: memoryType as string,
518
+ size: (size as number) || 0,
519
+ accessCount: 0,
520
+ storedAt: event.timestamp,
521
+ lastAccessedAt: event.timestamp,
522
+ deletedAt: null,
523
+ isDeleted: false,
524
+ });
525
+
526
+ this.emit('memory:stored', { memoryId });
527
+ }
528
+
529
+ private handleMemoryRetrieved(event: DomainEvent): void {
530
+ const { memoryId, accessCount } = event.payload;
531
+ const memory = this.memories.get(memoryId as string);
532
+
533
+ if (memory && !memory.isDeleted) {
534
+ memory.accessCount = accessCount as number;
535
+ memory.lastAccessedAt = event.timestamp;
536
+ this.emit('memory:retrieved', { memoryId });
537
+ }
538
+ }
539
+
540
+ private handleMemoryDeleted(event: DomainEvent): void {
541
+ const { memoryId } = event.payload;
542
+ const memory = this.memories.get(memoryId as string);
543
+
544
+ if (memory) {
545
+ memory.isDeleted = true;
546
+ memory.deletedAt = event.timestamp;
547
+ this.emit('memory:deleted', { memoryId });
548
+ }
549
+ }
550
+
551
+ private handleMemoryExpired(event: DomainEvent): void {
552
+ const { memoryId } = event.payload;
553
+ const memory = this.memories.get(memoryId as string);
554
+
555
+ if (memory) {
556
+ memory.isDeleted = true;
557
+ memory.deletedAt = event.timestamp;
558
+ this.emit('memory:expired', { memoryId });
559
+ }
560
+ }
561
+ }