@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.
- package/README.md +323 -0
- package/__tests__/hooks/bash-safety.test.ts +289 -0
- package/__tests__/hooks/file-organization.test.ts +335 -0
- package/__tests__/hooks/git-commit.test.ts +336 -0
- package/__tests__/hooks/index.ts +23 -0
- package/__tests__/hooks/session-hooks.test.ts +357 -0
- package/__tests__/hooks/task-hooks.test.ts +193 -0
- package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
- package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
- package/docs/EVENTS_README.md +352 -0
- package/package.json +39 -0
- package/src/core/config/defaults.ts +207 -0
- package/src/core/config/index.ts +15 -0
- package/src/core/config/loader.ts +271 -0
- package/src/core/config/schema.ts +188 -0
- package/src/core/config/validator.ts +209 -0
- package/src/core/event-bus.ts +236 -0
- package/src/core/index.ts +22 -0
- package/src/core/interfaces/agent.interface.ts +251 -0
- package/src/core/interfaces/coordinator.interface.ts +363 -0
- package/src/core/interfaces/event.interface.ts +267 -0
- package/src/core/interfaces/index.ts +19 -0
- package/src/core/interfaces/memory.interface.ts +332 -0
- package/src/core/interfaces/task.interface.ts +223 -0
- package/src/core/orchestrator/event-coordinator.ts +122 -0
- package/src/core/orchestrator/health-monitor.ts +214 -0
- package/src/core/orchestrator/index.ts +89 -0
- package/src/core/orchestrator/lifecycle-manager.ts +263 -0
- package/src/core/orchestrator/session-manager.ts +279 -0
- package/src/core/orchestrator/task-manager.ts +317 -0
- package/src/events/domain-events.ts +584 -0
- package/src/events/event-store.test.ts +387 -0
- package/src/events/event-store.ts +588 -0
- package/src/events/example-usage.ts +293 -0
- package/src/events/index.ts +90 -0
- package/src/events/projections.ts +561 -0
- package/src/events/state-reconstructor.ts +349 -0
- package/src/events.ts +367 -0
- package/src/hooks/INTEGRATION.md +658 -0
- package/src/hooks/README.md +532 -0
- package/src/hooks/example-usage.ts +499 -0
- package/src/hooks/executor.ts +379 -0
- package/src/hooks/hooks.test.ts +421 -0
- package/src/hooks/index.ts +131 -0
- package/src/hooks/registry.ts +333 -0
- package/src/hooks/safety/bash-safety.ts +604 -0
- package/src/hooks/safety/file-organization.ts +473 -0
- package/src/hooks/safety/git-commit.ts +623 -0
- package/src/hooks/safety/index.ts +46 -0
- package/src/hooks/session-hooks.ts +559 -0
- package/src/hooks/task-hooks.ts +513 -0
- package/src/hooks/types.ts +357 -0
- package/src/hooks/verify-exports.test.ts +125 -0
- package/src/index.ts +195 -0
- package/src/mcp/connection-pool.ts +438 -0
- package/src/mcp/index.ts +183 -0
- package/src/mcp/server.ts +774 -0
- package/src/mcp/session-manager.ts +428 -0
- package/src/mcp/tool-registry.ts +566 -0
- package/src/mcp/transport/http.ts +557 -0
- package/src/mcp/transport/index.ts +294 -0
- package/src/mcp/transport/stdio.ts +324 -0
- package/src/mcp/transport/websocket.ts +484 -0
- package/src/mcp/types.ts +565 -0
- package/src/plugin-interface.ts +663 -0
- package/src/plugin-loader.ts +638 -0
- package/src/plugin-registry.ts +604 -0
- package/src/plugins/index.ts +34 -0
- package/src/plugins/official/hive-mind-plugin.ts +330 -0
- package/src/plugins/official/index.ts +24 -0
- package/src/plugins/official/maestro-plugin.ts +508 -0
- package/src/plugins/types.ts +108 -0
- package/src/resilience/bulkhead.ts +277 -0
- package/src/resilience/circuit-breaker.ts +326 -0
- package/src/resilience/index.ts +26 -0
- package/src/resilience/rate-limiter.ts +420 -0
- package/src/resilience/retry.ts +224 -0
- package/src/security/index.ts +39 -0
- package/src/security/input-validation.ts +265 -0
- package/src/security/secure-random.ts +159 -0
- package/src/services/index.ts +16 -0
- package/src/services/v3-progress.service.ts +505 -0
- package/src/types/agent.types.ts +144 -0
- package/src/types/index.ts +22 -0
- package/src/types/mcp.types.ts +300 -0
- package/src/types/memory.types.ts +263 -0
- package/src/types/swarm.types.ts +255 -0
- package/src/types/task.types.ts +205 -0
- package/src/types.ts +367 -0
- package/src/utils/secure-logger.d.ts +69 -0
- package/src/utils/secure-logger.d.ts.map +1 -0
- package/src/utils/secure-logger.js +208 -0
- package/src/utils/secure-logger.js.map +1 -0
- package/src/utils/secure-logger.ts +257 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
# V3 Hooks System - Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide shows how to integrate the hooks system with V3's event bus, event store, and other core components.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ Application Layer │
|
|
10
|
+
├─────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ │
|
|
12
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
13
|
+
│ │ Tool Manager │───▶│ Hook Executor│───▶│ Event Bus │ │
|
|
14
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
15
|
+
│ │ │ │ │
|
|
16
|
+
│ │ │ ▼ │
|
|
17
|
+
│ │ │ ┌──────────────┐ │
|
|
18
|
+
│ │ │ │ Event Store │ │
|
|
19
|
+
│ │ │ │ (ADR-007) │ │
|
|
20
|
+
│ │ │ └──────────────┘ │
|
|
21
|
+
│ │ │ │
|
|
22
|
+
│ │ ▼ │
|
|
23
|
+
│ │ ┌──────────────┐ │
|
|
24
|
+
│ └───────────▶│Hook Registry │ │
|
|
25
|
+
│ └──────────────┘ │
|
|
26
|
+
│ │
|
|
27
|
+
└─────────────────────────────────────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Integration with Event Bus
|
|
31
|
+
|
|
32
|
+
### Step 1: Initialize Core Components
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createEventBus } from '@claude-flow/shared/core';
|
|
36
|
+
import { createHookRegistry, createHookExecutor } from '@claude-flow/shared/hooks';
|
|
37
|
+
import { EventStore } from '@claude-flow/shared/events';
|
|
38
|
+
|
|
39
|
+
// Create core components
|
|
40
|
+
const eventBus = createEventBus();
|
|
41
|
+
const hookRegistry = createHookRegistry();
|
|
42
|
+
const hookExecutor = createHookExecutor(hookRegistry, eventBus);
|
|
43
|
+
|
|
44
|
+
// Optional: Event store for audit trail (ADR-007)
|
|
45
|
+
const eventStore = new EventStore({
|
|
46
|
+
databasePath: './data/events.db',
|
|
47
|
+
verbose: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await eventStore.initialize();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Step 2: Subscribe to Hook Coordination Events
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Listen for hook execution start
|
|
57
|
+
eventBus.on('hooks:pre-execute', async (event) => {
|
|
58
|
+
console.log(`🎯 Executing ${event.payload.hookCount} hooks for ${event.payload.event}`);
|
|
59
|
+
|
|
60
|
+
// Store event in event store (ADR-007)
|
|
61
|
+
await eventStore.append({
|
|
62
|
+
id: `evt-${Date.now()}`,
|
|
63
|
+
type: 'hooks:pre-execute',
|
|
64
|
+
aggregateId: 'hook-system',
|
|
65
|
+
aggregateType: 'system',
|
|
66
|
+
version: 1,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
source: 'hook-executor',
|
|
69
|
+
payload: event.payload,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Listen for hook execution completion
|
|
74
|
+
eventBus.on('hooks:post-execute', async (event) => {
|
|
75
|
+
const { success, totalExecutionTime, hooksExecuted, hooksFailed } = event.payload;
|
|
76
|
+
|
|
77
|
+
console.log(`✅ Hooks completed: ${hooksExecuted} executed, ${hooksFailed} failed (${totalExecutionTime}ms)`);
|
|
78
|
+
|
|
79
|
+
// Store completion event
|
|
80
|
+
await eventStore.append({
|
|
81
|
+
id: `evt-${Date.now()}`,
|
|
82
|
+
type: 'hooks:post-execute',
|
|
83
|
+
aggregateId: 'hook-system',
|
|
84
|
+
aggregateType: 'system',
|
|
85
|
+
version: 1,
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
source: 'hook-executor',
|
|
88
|
+
payload: event.payload,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Listen for hook errors
|
|
93
|
+
eventBus.on('hooks:error', async (event) => {
|
|
94
|
+
console.error(`❌ Hook error:`, event.payload.error);
|
|
95
|
+
|
|
96
|
+
// Store error event
|
|
97
|
+
await eventStore.append({
|
|
98
|
+
id: `evt-${Date.now()}`,
|
|
99
|
+
type: 'hooks:error',
|
|
100
|
+
aggregateId: 'hook-system',
|
|
101
|
+
aggregateType: 'system',
|
|
102
|
+
version: 1,
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
source: 'hook-executor',
|
|
105
|
+
payload: {
|
|
106
|
+
hookId: event.payload.hookId,
|
|
107
|
+
error: event.payload.error.message,
|
|
108
|
+
stack: event.payload.error.stack,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Integration with Tool Execution
|
|
115
|
+
|
|
116
|
+
### Example: File Read with Hooks
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { HookEvent, HookContext } from '@claude-flow/shared/hooks';
|
|
120
|
+
import { readFile } from 'fs/promises';
|
|
121
|
+
|
|
122
|
+
async function readFileWithHooks(filePath: string): Promise<string> {
|
|
123
|
+
// Create pre-read context
|
|
124
|
+
const preContext: HookContext = {
|
|
125
|
+
event: HookEvent.PreRead,
|
|
126
|
+
timestamp: new Date(),
|
|
127
|
+
file: {
|
|
128
|
+
path: filePath,
|
|
129
|
+
operation: 'read',
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Execute pre-read hooks
|
|
134
|
+
const preResult = await hookExecutor.execute(HookEvent.PreRead, preContext);
|
|
135
|
+
|
|
136
|
+
if (preResult.aborted) {
|
|
137
|
+
throw new Error('File read aborted by hooks');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Perform actual read
|
|
141
|
+
const startTime = Date.now();
|
|
142
|
+
let content: string;
|
|
143
|
+
let success = true;
|
|
144
|
+
let error: Error | undefined;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
content = await readFile(filePath, 'utf-8');
|
|
148
|
+
} catch (err) {
|
|
149
|
+
success = false;
|
|
150
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
151
|
+
throw error;
|
|
152
|
+
} finally {
|
|
153
|
+
// Execute post-read hooks
|
|
154
|
+
const postContext: HookContext = {
|
|
155
|
+
event: HookEvent.PostRead,
|
|
156
|
+
timestamp: new Date(),
|
|
157
|
+
file: {
|
|
158
|
+
path: filePath,
|
|
159
|
+
operation: 'read',
|
|
160
|
+
size: content?.length || 0,
|
|
161
|
+
},
|
|
162
|
+
metadata: {
|
|
163
|
+
...preResult.finalContext?.metadata,
|
|
164
|
+
success,
|
|
165
|
+
executionTime: Date.now() - startTime,
|
|
166
|
+
},
|
|
167
|
+
error: error ? {
|
|
168
|
+
error,
|
|
169
|
+
context: 'file-read',
|
|
170
|
+
severity: 'error',
|
|
171
|
+
recoverable: false,
|
|
172
|
+
} : undefined,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
await hookExecutor.execute(HookEvent.PostRead, postContext);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return content;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Example: Command Execution with Hooks
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { exec } from 'child_process';
|
|
186
|
+
import { promisify } from 'util';
|
|
187
|
+
import { HookEvent, HookContext } from '@claude-flow/shared/hooks';
|
|
188
|
+
|
|
189
|
+
const execAsync = promisify(exec);
|
|
190
|
+
|
|
191
|
+
async function executeCommandWithHooks(
|
|
192
|
+
command: string,
|
|
193
|
+
options: { cwd?: string; timeout?: number } = {}
|
|
194
|
+
): Promise<{ stdout: string; stderr: string }> {
|
|
195
|
+
// Pre-command hooks
|
|
196
|
+
const preContext: HookContext = {
|
|
197
|
+
event: HookEvent.PreCommand,
|
|
198
|
+
timestamp: new Date(),
|
|
199
|
+
command: {
|
|
200
|
+
command,
|
|
201
|
+
cwd: options.cwd,
|
|
202
|
+
timeout: options.timeout,
|
|
203
|
+
isDestructive: /rm|del|format|dd/.test(command),
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const preResult = await hookExecutor.execute(HookEvent.PreCommand, preContext);
|
|
208
|
+
|
|
209
|
+
if (preResult.aborted) {
|
|
210
|
+
throw new Error(`Command execution blocked by security hooks: ${command}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Execute command
|
|
214
|
+
const startTime = Date.now();
|
|
215
|
+
let result: { stdout: string; stderr: string };
|
|
216
|
+
let success = true;
|
|
217
|
+
let error: Error | undefined;
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
result = await execAsync(command, options);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
success = false;
|
|
223
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
224
|
+
throw error;
|
|
225
|
+
} finally {
|
|
226
|
+
// Post-command hooks
|
|
227
|
+
const postContext: HookContext = {
|
|
228
|
+
event: HookEvent.PostCommand,
|
|
229
|
+
timestamp: new Date(),
|
|
230
|
+
command: {
|
|
231
|
+
command,
|
|
232
|
+
cwd: options.cwd,
|
|
233
|
+
timeout: options.timeout,
|
|
234
|
+
},
|
|
235
|
+
metadata: {
|
|
236
|
+
...preResult.finalContext?.metadata,
|
|
237
|
+
success,
|
|
238
|
+
executionTime: Date.now() - startTime,
|
|
239
|
+
},
|
|
240
|
+
error: error ? {
|
|
241
|
+
error,
|
|
242
|
+
context: 'command-execution',
|
|
243
|
+
severity: 'error',
|
|
244
|
+
recoverable: false,
|
|
245
|
+
} : undefined,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
await hookExecutor.execute(HookEvent.PostCommand, postContext);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Integration with Agent Lifecycle
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { HookEvent, HookContext } from '@claude-flow/shared/hooks';
|
|
259
|
+
|
|
260
|
+
class AgentManager {
|
|
261
|
+
async spawnAgent(type: string, config: Record<string, unknown>) {
|
|
262
|
+
const agentId = `agent-${Date.now()}`;
|
|
263
|
+
|
|
264
|
+
// Pre-spawn hooks
|
|
265
|
+
const preContext: HookContext = {
|
|
266
|
+
event: HookEvent.PreAgentSpawn,
|
|
267
|
+
timestamp: new Date(),
|
|
268
|
+
agent: {
|
|
269
|
+
id: agentId,
|
|
270
|
+
type,
|
|
271
|
+
config,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const preResult = await hookExecutor.execute(HookEvent.PreAgentSpawn, preContext);
|
|
276
|
+
|
|
277
|
+
if (preResult.aborted) {
|
|
278
|
+
throw new Error(`Agent spawn aborted: ${type}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Spawn agent
|
|
282
|
+
const agent = await this.createAgent(agentId, type, config);
|
|
283
|
+
|
|
284
|
+
// Post-spawn hooks
|
|
285
|
+
const postContext: HookContext = {
|
|
286
|
+
event: HookEvent.PostAgentSpawn,
|
|
287
|
+
timestamp: new Date(),
|
|
288
|
+
agent: {
|
|
289
|
+
id: agentId,
|
|
290
|
+
type,
|
|
291
|
+
config,
|
|
292
|
+
},
|
|
293
|
+
metadata: {
|
|
294
|
+
...preResult.finalContext?.metadata,
|
|
295
|
+
success: true,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
await hookExecutor.execute(HookEvent.PostAgentSpawn, postContext);
|
|
300
|
+
|
|
301
|
+
return agent;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async terminateAgent(agentId: string) {
|
|
305
|
+
// Pre-terminate hooks
|
|
306
|
+
const preContext: HookContext = {
|
|
307
|
+
event: HookEvent.PreAgentTerminate,
|
|
308
|
+
timestamp: new Date(),
|
|
309
|
+
agent: { id: agentId, type: 'unknown' },
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
await hookExecutor.execute(HookEvent.PreAgentTerminate, preContext);
|
|
313
|
+
|
|
314
|
+
// Terminate agent
|
|
315
|
+
await this.destroyAgent(agentId);
|
|
316
|
+
|
|
317
|
+
// Post-terminate hooks
|
|
318
|
+
const postContext: HookContext = {
|
|
319
|
+
event: HookEvent.PostAgentTerminate,
|
|
320
|
+
timestamp: new Date(),
|
|
321
|
+
agent: { id: agentId, type: 'unknown' },
|
|
322
|
+
metadata: { success: true },
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
await hookExecutor.execute(HookEvent.PostAgentTerminate, postContext);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private async createAgent(id: string, type: string, config: Record<string, unknown>) {
|
|
329
|
+
// Implementation
|
|
330
|
+
return { id, type, config };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private async destroyAgent(id: string) {
|
|
334
|
+
// Implementation
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Integration with Session Management
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { HookEvent, HookContext } from '@claude-flow/shared/hooks';
|
|
343
|
+
|
|
344
|
+
class SessionManager {
|
|
345
|
+
async startSession(userId?: string) {
|
|
346
|
+
const sessionId = `session-${Date.now()}`;
|
|
347
|
+
|
|
348
|
+
const context: HookContext = {
|
|
349
|
+
event: HookEvent.SessionStart,
|
|
350
|
+
timestamp: new Date(),
|
|
351
|
+
session: {
|
|
352
|
+
id: sessionId,
|
|
353
|
+
startTime: new Date(),
|
|
354
|
+
userId,
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const result = await hookExecutor.execute(HookEvent.SessionStart, context);
|
|
359
|
+
|
|
360
|
+
// Emit system event
|
|
361
|
+
eventBus.emit('session:created', {
|
|
362
|
+
sessionId,
|
|
363
|
+
userId,
|
|
364
|
+
timestamp: Date.now(),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return { sessionId, context: result.finalContext };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async endSession(sessionId: string) {
|
|
371
|
+
const context: HookContext = {
|
|
372
|
+
event: HookEvent.SessionEnd,
|
|
373
|
+
timestamp: new Date(),
|
|
374
|
+
session: {
|
|
375
|
+
id: sessionId,
|
|
376
|
+
startTime: new Date(), // Would be loaded from state
|
|
377
|
+
endTime: new Date(),
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
await hookExecutor.execute(HookEvent.SessionEnd, context);
|
|
382
|
+
|
|
383
|
+
// Emit system event
|
|
384
|
+
eventBus.emit('session:terminated', {
|
|
385
|
+
sessionId,
|
|
386
|
+
timestamp: Date.now(),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Plugin System Integration
|
|
393
|
+
|
|
394
|
+
Hooks can be registered via plugins (ADR-004):
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// v3/plugins/security-hooks/index.ts
|
|
398
|
+
import { Plugin } from '@claude-flow/shared';
|
|
399
|
+
import { HookEvent, HookPriority } from '@claude-flow/shared/hooks';
|
|
400
|
+
|
|
401
|
+
export default {
|
|
402
|
+
name: 'security-hooks',
|
|
403
|
+
version: '1.0.0',
|
|
404
|
+
|
|
405
|
+
async initialize({ hookRegistry }) {
|
|
406
|
+
// Register security hooks
|
|
407
|
+
hookRegistry.register(
|
|
408
|
+
HookEvent.PreCommand,
|
|
409
|
+
async (context) => {
|
|
410
|
+
// Security validation
|
|
411
|
+
return { success: true };
|
|
412
|
+
},
|
|
413
|
+
HookPriority.Critical,
|
|
414
|
+
{ name: 'Security: Command Validation' }
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
hookRegistry.register(
|
|
418
|
+
HookEvent.PreWrite,
|
|
419
|
+
async (context) => {
|
|
420
|
+
// File write validation
|
|
421
|
+
return { success: true };
|
|
422
|
+
},
|
|
423
|
+
HookPriority.Critical,
|
|
424
|
+
{ name: 'Security: File Write Validation' }
|
|
425
|
+
);
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
async shutdown({ hookRegistry }) {
|
|
429
|
+
// Cleanup hooks
|
|
430
|
+
hookRegistry.unregisterAll();
|
|
431
|
+
},
|
|
432
|
+
} as Plugin;
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## MCP Integration
|
|
436
|
+
|
|
437
|
+
Expose hooks via MCP tools:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
// v3/@claude-flow/shared/src/mcp/tools/hooks.ts
|
|
441
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
442
|
+
|
|
443
|
+
export const registerHookTool: Tool = {
|
|
444
|
+
name: 'hooks_register',
|
|
445
|
+
description: 'Register a new hook for event interception',
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: 'object',
|
|
448
|
+
properties: {
|
|
449
|
+
event: {
|
|
450
|
+
type: 'string',
|
|
451
|
+
enum: Object.values(HookEvent),
|
|
452
|
+
description: 'Hook event type',
|
|
453
|
+
},
|
|
454
|
+
priority: {
|
|
455
|
+
type: 'string',
|
|
456
|
+
enum: ['critical', 'high', 'normal', 'low', 'lowest'],
|
|
457
|
+
description: 'Hook priority',
|
|
458
|
+
},
|
|
459
|
+
name: {
|
|
460
|
+
type: 'string',
|
|
461
|
+
description: 'Hook name',
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
required: ['event'],
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
export const listHooksTool: Tool = {
|
|
469
|
+
name: 'hooks_list',
|
|
470
|
+
description: 'List all registered hooks',
|
|
471
|
+
inputSchema: {
|
|
472
|
+
type: 'object',
|
|
473
|
+
properties: {
|
|
474
|
+
event: {
|
|
475
|
+
type: 'string',
|
|
476
|
+
enum: Object.values(HookEvent),
|
|
477
|
+
description: 'Filter by event type (optional)',
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
export const getHookStatsTool: Tool = {
|
|
484
|
+
name: 'hooks_stats',
|
|
485
|
+
description: 'Get hook execution statistics',
|
|
486
|
+
inputSchema: {
|
|
487
|
+
type: 'object',
|
|
488
|
+
properties: {},
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Event Sourcing Integration (ADR-007)
|
|
494
|
+
|
|
495
|
+
Store all hook executions for audit trail:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import { EventStore } from '@claude-flow/shared/events';
|
|
499
|
+
|
|
500
|
+
// Create event store
|
|
501
|
+
const eventStore = new EventStore({
|
|
502
|
+
databasePath: './data/hook-events.db',
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
await eventStore.initialize();
|
|
506
|
+
|
|
507
|
+
// Subscribe to all hook events
|
|
508
|
+
eventBus.on('hooks:pre-execute', async (event) => {
|
|
509
|
+
await eventStore.append({
|
|
510
|
+
id: `evt-${Date.now()}`,
|
|
511
|
+
type: 'hooks:pre-execute',
|
|
512
|
+
aggregateId: 'hook-system',
|
|
513
|
+
aggregateType: 'system',
|
|
514
|
+
version: 1,
|
|
515
|
+
timestamp: Date.now(),
|
|
516
|
+
source: 'hook-executor',
|
|
517
|
+
payload: event.payload,
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Query hook history
|
|
522
|
+
const hookEvents = await eventStore.getEventsByType('hooks:pre-execute');
|
|
523
|
+
console.log(`Total hook executions: ${hookEvents.length}`);
|
|
524
|
+
|
|
525
|
+
// Replay hook events
|
|
526
|
+
for await (const event of eventStore.replay()) {
|
|
527
|
+
if (event.type.startsWith('hooks:')) {
|
|
528
|
+
console.log(`Replaying hook event: ${event.type} at ${event.timestamp}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Complete Example: Tool Manager with Hooks
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import {
|
|
537
|
+
createEventBus,
|
|
538
|
+
createHookRegistry,
|
|
539
|
+
createHookExecutor,
|
|
540
|
+
HookEvent,
|
|
541
|
+
HookPriority,
|
|
542
|
+
HookContext,
|
|
543
|
+
} from '@claude-flow/shared';
|
|
544
|
+
|
|
545
|
+
class V3ToolManager {
|
|
546
|
+
private eventBus = createEventBus();
|
|
547
|
+
private hookRegistry = createHookRegistry();
|
|
548
|
+
private hookExecutor = createHookExecutor(this.hookRegistry, this.eventBus);
|
|
549
|
+
|
|
550
|
+
constructor() {
|
|
551
|
+
this.setupHooks();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private setupHooks() {
|
|
555
|
+
// Security hooks
|
|
556
|
+
this.hookRegistry.register(
|
|
557
|
+
HookEvent.PreToolUse,
|
|
558
|
+
async (context) => {
|
|
559
|
+
console.log(`Security check: ${context.tool?.name}`);
|
|
560
|
+
return { success: true };
|
|
561
|
+
},
|
|
562
|
+
HookPriority.Critical
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// Performance tracking
|
|
566
|
+
this.hookRegistry.register(
|
|
567
|
+
HookEvent.PreToolUse,
|
|
568
|
+
async (context) => {
|
|
569
|
+
return {
|
|
570
|
+
success: true,
|
|
571
|
+
data: {
|
|
572
|
+
metadata: {
|
|
573
|
+
...context.metadata,
|
|
574
|
+
startTime: Date.now(),
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
},
|
|
579
|
+
HookPriority.High
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
this.hookRegistry.register(
|
|
583
|
+
HookEvent.PostToolUse,
|
|
584
|
+
async (context) => {
|
|
585
|
+
const startTime = context.metadata?.startTime as number;
|
|
586
|
+
if (startTime) {
|
|
587
|
+
const duration = Date.now() - startTime;
|
|
588
|
+
console.log(`Tool ${context.tool?.name} took ${duration}ms`);
|
|
589
|
+
}
|
|
590
|
+
return { success: true };
|
|
591
|
+
},
|
|
592
|
+
HookPriority.Normal
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async executeTool(name: string, parameters: Record<string, unknown>) {
|
|
597
|
+
const context: HookContext = {
|
|
598
|
+
event: HookEvent.PreToolUse,
|
|
599
|
+
timestamp: new Date(),
|
|
600
|
+
tool: { name, parameters },
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// Execute pre-hooks
|
|
604
|
+
const preResult = await this.hookExecutor.execute(HookEvent.PreToolUse, context);
|
|
605
|
+
|
|
606
|
+
if (preResult.aborted) {
|
|
607
|
+
throw new Error(`Tool execution blocked: ${name}`);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Execute tool (simulated)
|
|
611
|
+
const toolResult = await this.performToolExecution(name, parameters);
|
|
612
|
+
|
|
613
|
+
// Execute post-hooks
|
|
614
|
+
await this.hookExecutor.execute(HookEvent.PostToolUse, {
|
|
615
|
+
...context,
|
|
616
|
+
event: HookEvent.PostToolUse,
|
|
617
|
+
metadata: {
|
|
618
|
+
...preResult.finalContext?.metadata,
|
|
619
|
+
success: true,
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
return toolResult;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private async performToolExecution(name: string, parameters: Record<string, unknown>) {
|
|
627
|
+
// Tool execution implementation
|
|
628
|
+
return { result: 'success' };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
getStats() {
|
|
632
|
+
return this.hookRegistry.getStats();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Usage
|
|
637
|
+
const toolManager = new V3ToolManager();
|
|
638
|
+
await toolManager.executeTool('Read', { path: 'file.ts' });
|
|
639
|
+
const stats = toolManager.getStats();
|
|
640
|
+
console.log('Hook statistics:', stats);
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Best Practices
|
|
644
|
+
|
|
645
|
+
1. **Always use event bus**: Connect hookExecutor to eventBus for coordination
|
|
646
|
+
2. **Store critical events**: Use EventStore for audit trail (ADR-007)
|
|
647
|
+
3. **Handle errors gracefully**: Set `continueOnError` based on severity
|
|
648
|
+
4. **Use appropriate priorities**: Security hooks = Critical, monitoring = Low
|
|
649
|
+
5. **Clean up resources**: Unregister hooks in plugin shutdown
|
|
650
|
+
6. **Monitor performance**: Use hook statistics to identify bottlenecks
|
|
651
|
+
7. **Test hooks thoroughly**: Use the provided test suite as a template
|
|
652
|
+
|
|
653
|
+
## Next Steps
|
|
654
|
+
|
|
655
|
+
- See `hooks.test.ts` for comprehensive examples
|
|
656
|
+
- See `example-usage.ts` for practical use cases
|
|
657
|
+
- See `README.md` for API documentation
|
|
658
|
+
- Implement hooks in your domain modules (security, memory, swarm)
|