@claude-flow/shared 3.0.0-alpha.7 → 3.0.0-alpha.8
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/.claude-flow/daemon-state.json +135 -0
- package/.claude-flow/data/pending-insights.jsonl +2 -0
- package/.claude-flow/data/ranked-context.json +5 -0
- package/.claude-flow/logs/daemon.log +45 -0
- package/.claude-flow/logs/headless/audit_1777379186972_h5un5x_prompt.log +3210 -0
- package/.claude-flow/logs/headless/audit_1777379186972_h5un5x_result.log +117 -0
- package/.claude-flow/logs/headless/audit_1777379816437_w0eaul_prompt.log +3210 -0
- package/.claude-flow/logs/headless/audit_1777379816437_w0eaul_result.log +53 -0
- package/.claude-flow/logs/headless/audit_1777380440097_621y8m_prompt.log +3210 -0
- package/.claude-flow/logs/headless/audit_1777380440097_621y8m_result.log +75 -0
- package/.claude-flow/logs/headless/optimize_1777379306973_an4lmy_prompt.log +3504 -0
- package/.claude-flow/logs/headless/optimize_1777379306973_an4lmy_result.log +166 -0
- package/.claude-flow/logs/headless/optimize_1777380274732_apxz3s_prompt.log +3504 -0
- package/.claude-flow/logs/headless/optimize_1777380274732_apxz3s_result.log +219 -0
- package/.claude-flow/logs/headless/testgaps_1777379546969_dvf2a1_prompt.log +3189 -0
- package/.claude-flow/logs/headless/testgaps_1777379546969_dvf2a1_result.log +155 -0
- package/.claude-flow/metrics/codebase-map.json +11 -0
- package/.claude-flow/metrics/consolidation.json +6 -0
- package/.claude-flow/sessions/current.json +13 -0
- package/.swarm/hnsw.index +0 -0
- package/.swarm/hnsw.metadata.json +1 -0
- package/.swarm/memory.db +0 -0
- package/.swarm/memory.db-shm +0 -0
- package/.swarm/memory.db-wal +0 -0
- package/.swarm/schema.sql +305 -0
- package/dist/core/config/schema.d.ts +96 -96
- package/dist/events/event-store.d.ts.map +1 -1
- package/dist/events/event-store.js +20 -9
- package/dist/events/event-store.js.map +1 -1
- package/dist/hooks/executor.d.ts.map +1 -1
- package/dist/hooks/executor.js +7 -4
- package/dist/hooks/executor.js.map +1 -1
- package/dist/hooks/verify-exports.test.js +6 -6
- package/dist/hooks/verify-exports.test.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +3 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/types.d.ts +4 -6
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js.map +1 -1
- package/package.json +3 -2
- package/ruvector.db +0 -0
- package/src/events/event-store.ts +18 -9
- package/src/hooks/executor.ts +7 -5
- package/src/hooks/verify-exports.test.ts +6 -6
- package/src/mcp/server.ts +3 -6
- package/src/mcp/types.ts +4 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/.agentic-flow/intelligence.json +0 -16
- package/__tests__/coverage/base.css +0 -224
- package/__tests__/coverage/block-navigation.js +0 -87
- package/__tests__/coverage/coverage-final.json +0 -50
- package/__tests__/coverage/favicon.png +0 -0
- package/__tests__/coverage/index.html +0 -326
- package/__tests__/coverage/lcov-report/base.css +0 -224
- package/__tests__/coverage/lcov-report/block-navigation.js +0 -87
- package/__tests__/coverage/lcov-report/favicon.png +0 -0
- package/__tests__/coverage/lcov-report/index.html +0 -326
- package/__tests__/coverage/lcov-report/prettify.css +0 -1
- package/__tests__/coverage/lcov-report/prettify.js +0 -2
- package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/lcov-report/sorter.js +0 -210
- package/__tests__/coverage/lcov-report/src/core/config/defaults.ts.html +0 -706
- package/__tests__/coverage/lcov-report/src/core/config/index.html +0 -161
- package/__tests__/coverage/lcov-report/src/core/config/loader.ts.html +0 -898
- package/__tests__/coverage/lcov-report/src/core/config/schema.ts.html +0 -649
- package/__tests__/coverage/lcov-report/src/core/config/validator.ts.html +0 -712
- package/__tests__/coverage/lcov-report/src/core/event-bus.ts.html +0 -793
- package/__tests__/coverage/lcov-report/src/core/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/core/interfaces/event.interface.ts.html +0 -886
- package/__tests__/coverage/lcov-report/src/core/interfaces/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/core/orchestrator/event-coordinator.ts.html +0 -451
- package/__tests__/coverage/lcov-report/src/core/orchestrator/health-monitor.ts.html +0 -727
- package/__tests__/coverage/lcov-report/src/core/orchestrator/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/core/orchestrator/lifecycle-manager.ts.html +0 -874
- package/__tests__/coverage/lcov-report/src/core/orchestrator/session-manager.ts.html +0 -922
- package/__tests__/coverage/lcov-report/src/core/orchestrator/task-manager.ts.html +0 -1036
- package/__tests__/coverage/lcov-report/src/events/domain-events.ts.html +0 -1837
- package/__tests__/coverage/lcov-report/src/events/event-store.ts.html +0 -1849
- package/__tests__/coverage/lcov-report/src/events/example-usage.ts.html +0 -964
- package/__tests__/coverage/lcov-report/src/events/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/events/projections.ts.html +0 -1768
- package/__tests__/coverage/lcov-report/src/events/state-reconstructor.ts.html +0 -1132
- package/__tests__/coverage/lcov-report/src/events.ts.html +0 -1186
- package/__tests__/coverage/lcov-report/src/hooks/example-usage.ts.html +0 -1582
- package/__tests__/coverage/lcov-report/src/hooks/executor.ts.html +0 -1222
- package/__tests__/coverage/lcov-report/src/hooks/index.html +0 -191
- package/__tests__/coverage/lcov-report/src/hooks/registry.ts.html +0 -1084
- package/__tests__/coverage/lcov-report/src/hooks/safety/bash-safety.ts.html +0 -1897
- package/__tests__/coverage/lcov-report/src/hooks/safety/file-organization.ts.html +0 -1504
- package/__tests__/coverage/lcov-report/src/hooks/safety/git-commit.ts.html +0 -1954
- package/__tests__/coverage/lcov-report/src/hooks/safety/index.html +0 -146
- package/__tests__/coverage/lcov-report/src/hooks/session-hooks.ts.html +0 -1762
- package/__tests__/coverage/lcov-report/src/hooks/task-hooks.ts.html +0 -1624
- package/__tests__/coverage/lcov-report/src/hooks/types.ts.html +0 -1156
- package/__tests__/coverage/lcov-report/src/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/mcp/connection-pool.ts.html +0 -1399
- package/__tests__/coverage/lcov-report/src/mcp/index.html +0 -176
- package/__tests__/coverage/lcov-report/src/mcp/server.ts.html +0 -2407
- package/__tests__/coverage/lcov-report/src/mcp/session-manager.ts.html +0 -1369
- package/__tests__/coverage/lcov-report/src/mcp/tool-registry.ts.html +0 -1783
- package/__tests__/coverage/lcov-report/src/mcp/transport/http.ts.html +0 -1756
- package/__tests__/coverage/lcov-report/src/mcp/transport/index.html +0 -146
- package/__tests__/coverage/lcov-report/src/mcp/transport/stdio.ts.html +0 -1057
- package/__tests__/coverage/lcov-report/src/mcp/transport/websocket.ts.html +0 -1537
- package/__tests__/coverage/lcov-report/src/mcp/types.ts.html +0 -1780
- package/__tests__/coverage/lcov-report/src/plugin-interface.ts.html +0 -2074
- package/__tests__/coverage/lcov-report/src/plugin-loader.ts.html +0 -1999
- package/__tests__/coverage/lcov-report/src/plugin-registry.ts.html +0 -1897
- package/__tests__/coverage/lcov-report/src/plugins/official/hive-mind-plugin.ts.html +0 -1075
- package/__tests__/coverage/lcov-report/src/plugins/official/index.html +0 -131
- package/__tests__/coverage/lcov-report/src/plugins/official/maestro-plugin.ts.html +0 -1609
- package/__tests__/coverage/lcov-report/src/resilience/bulkhead.ts.html +0 -916
- package/__tests__/coverage/lcov-report/src/resilience/circuit-breaker.ts.html +0 -1063
- package/__tests__/coverage/lcov-report/src/resilience/index.html +0 -161
- package/__tests__/coverage/lcov-report/src/resilience/rate-limiter.ts.html +0 -1345
- package/__tests__/coverage/lcov-report/src/resilience/retry.ts.html +0 -757
- package/__tests__/coverage/lcov-report/src/security/index.html +0 -131
- package/__tests__/coverage/lcov-report/src/security/input-validation.ts.html +0 -880
- package/__tests__/coverage/lcov-report/src/security/secure-random.ts.html +0 -562
- package/__tests__/coverage/lcov-report/src/types/index.html +0 -131
- package/__tests__/coverage/lcov-report/src/types/swarm.types.ts.html +0 -850
- package/__tests__/coverage/lcov-report/src/types/task.types.ts.html +0 -700
- package/__tests__/coverage/lcov-report/src/types.ts.html +0 -1186
- package/__tests__/coverage/lcov-report/src/utils/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/utils/secure-logger.ts.html +0 -856
- package/__tests__/coverage/lcov.info +0 -19877
- package/__tests__/coverage/prettify.css +0 -1
- package/__tests__/coverage/prettify.js +0 -2
- package/__tests__/coverage/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/sorter.js +0 -210
- package/__tests__/coverage/src/core/config/defaults.ts.html +0 -706
- package/__tests__/coverage/src/core/config/index.html +0 -161
- package/__tests__/coverage/src/core/config/loader.ts.html +0 -898
- package/__tests__/coverage/src/core/config/schema.ts.html +0 -649
- package/__tests__/coverage/src/core/config/validator.ts.html +0 -712
- package/__tests__/coverage/src/core/event-bus.ts.html +0 -793
- package/__tests__/coverage/src/core/index.html +0 -116
- package/__tests__/coverage/src/core/interfaces/event.interface.ts.html +0 -886
- package/__tests__/coverage/src/core/interfaces/index.html +0 -116
- package/__tests__/coverage/src/core/orchestrator/event-coordinator.ts.html +0 -451
- package/__tests__/coverage/src/core/orchestrator/health-monitor.ts.html +0 -727
- package/__tests__/coverage/src/core/orchestrator/index.html +0 -176
- package/__tests__/coverage/src/core/orchestrator/lifecycle-manager.ts.html +0 -874
- package/__tests__/coverage/src/core/orchestrator/session-manager.ts.html +0 -922
- package/__tests__/coverage/src/core/orchestrator/task-manager.ts.html +0 -1036
- package/__tests__/coverage/src/events/domain-events.ts.html +0 -1837
- package/__tests__/coverage/src/events/event-store.ts.html +0 -1849
- package/__tests__/coverage/src/events/example-usage.ts.html +0 -964
- package/__tests__/coverage/src/events/index.html +0 -176
- package/__tests__/coverage/src/events/projections.ts.html +0 -1768
- package/__tests__/coverage/src/events/state-reconstructor.ts.html +0 -1132
- package/__tests__/coverage/src/events.ts.html +0 -1186
- package/__tests__/coverage/src/hooks/example-usage.ts.html +0 -1582
- package/__tests__/coverage/src/hooks/executor.ts.html +0 -1222
- package/__tests__/coverage/src/hooks/index.html +0 -191
- package/__tests__/coverage/src/hooks/registry.ts.html +0 -1084
- package/__tests__/coverage/src/hooks/safety/bash-safety.ts.html +0 -1897
- package/__tests__/coverage/src/hooks/safety/file-organization.ts.html +0 -1504
- package/__tests__/coverage/src/hooks/safety/git-commit.ts.html +0 -1954
- package/__tests__/coverage/src/hooks/safety/index.html +0 -146
- package/__tests__/coverage/src/hooks/session-hooks.ts.html +0 -1762
- package/__tests__/coverage/src/hooks/task-hooks.ts.html +0 -1624
- package/__tests__/coverage/src/hooks/types.ts.html +0 -1156
- package/__tests__/coverage/src/index.html +0 -176
- package/__tests__/coverage/src/mcp/connection-pool.ts.html +0 -1399
- package/__tests__/coverage/src/mcp/index.html +0 -176
- package/__tests__/coverage/src/mcp/server.ts.html +0 -2407
- package/__tests__/coverage/src/mcp/session-manager.ts.html +0 -1369
- package/__tests__/coverage/src/mcp/tool-registry.ts.html +0 -1783
- package/__tests__/coverage/src/mcp/transport/http.ts.html +0 -1756
- package/__tests__/coverage/src/mcp/transport/index.html +0 -146
- package/__tests__/coverage/src/mcp/transport/stdio.ts.html +0 -1057
- package/__tests__/coverage/src/mcp/transport/websocket.ts.html +0 -1537
- package/__tests__/coverage/src/mcp/types.ts.html +0 -1780
- package/__tests__/coverage/src/plugin-interface.ts.html +0 -2074
- package/__tests__/coverage/src/plugin-loader.ts.html +0 -1999
- package/__tests__/coverage/src/plugin-registry.ts.html +0 -1897
- package/__tests__/coverage/src/plugins/official/hive-mind-plugin.ts.html +0 -1075
- package/__tests__/coverage/src/plugins/official/index.html +0 -131
- package/__tests__/coverage/src/plugins/official/maestro-plugin.ts.html +0 -1609
- package/__tests__/coverage/src/resilience/bulkhead.ts.html +0 -916
- package/__tests__/coverage/src/resilience/circuit-breaker.ts.html +0 -1063
- package/__tests__/coverage/src/resilience/index.html +0 -161
- package/__tests__/coverage/src/resilience/rate-limiter.ts.html +0 -1345
- package/__tests__/coverage/src/resilience/retry.ts.html +0 -757
- package/__tests__/coverage/src/security/index.html +0 -131
- package/__tests__/coverage/src/security/input-validation.ts.html +0 -880
- package/__tests__/coverage/src/security/secure-random.ts.html +0 -562
- package/__tests__/coverage/src/types/index.html +0 -131
- package/__tests__/coverage/src/types/swarm.types.ts.html +0 -850
- package/__tests__/coverage/src/types/task.types.ts.html +0 -700
- package/__tests__/coverage/src/types.ts.html +0 -1186
- package/__tests__/coverage/src/utils/index.html +0 -116
- package/__tests__/coverage/src/utils/secure-logger.ts.html +0 -856
|
@@ -0,0 +1,3189 @@
|
|
|
1
|
+
[2026-04-28T12:32:26.973Z] PROMPT
|
|
2
|
+
============================================================
|
|
3
|
+
Analyze test coverage and identify gaps:
|
|
4
|
+
- Find untested functions and classes
|
|
5
|
+
- Identify edge cases not covered
|
|
6
|
+
- Suggest new test scenarios
|
|
7
|
+
- Check for missing error handling tests
|
|
8
|
+
- Identify integration test gaps
|
|
9
|
+
|
|
10
|
+
For each gap, provide a test skeleton.
|
|
11
|
+
|
|
12
|
+
## Codebase Context
|
|
13
|
+
|
|
14
|
+
--- __tests__/hooks/bash-safety.test.ts (truncated) ---
|
|
15
|
+
/**
|
|
16
|
+
* V3 Bash Safety Hook Tests
|
|
17
|
+
*
|
|
18
|
+
* Tests for command safety analysis and dangerous command detection.
|
|
19
|
+
*
|
|
20
|
+
* @module v3/shared/hooks/__tests__/bash-safety.test
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
24
|
+
import {
|
|
25
|
+
createHookRegistry,
|
|
26
|
+
createBashSafetyHook,
|
|
27
|
+
BashSafetyHook,
|
|
28
|
+
HookRegistry,
|
|
29
|
+
} from '../../src/hooks/index.js';
|
|
30
|
+
|
|
31
|
+
describe('BashSafetyHook', () => {
|
|
32
|
+
let registry: HookRegistry;
|
|
33
|
+
let bashSafety: BashSafetyHook;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
registry = createHookRegistry();
|
|
37
|
+
bashSafety = createBashSafetyHook(registry);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('dangerous command detection', () => {
|
|
41
|
+
it('should block rm -rf / command', async () => {
|
|
42
|
+
const result = await bashSafety.analyze('rm -rf /');
|
|
43
|
+
|
|
44
|
+
expect(result.blocked).toBe(true);
|
|
45
|
+
expect(result.riskLevel).toBe('critical');
|
|
46
|
+
expect(result.risks.length).toBeGreaterThan(0);
|
|
47
|
+
expect(result.risks[0].type).toBe('destructive');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should block rm -rf /* command', async () => {
|
|
51
|
+
const result = await bashSafety.analyze('rm -rf /*');
|
|
52
|
+
|
|
53
|
+
expect(result.blocked).toBe(true);
|
|
54
|
+
expect(result.riskLevel).toBe('critical');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should block dd to disk device', async () => {
|
|
58
|
+
const result = await bashSafety.analyze('dd if=/dev/zero of=/dev/sda');
|
|
59
|
+
|
|
60
|
+
expect(result.blocked).toBe(true);
|
|
61
|
+
expect(result.riskLevel).toBe('critical');
|
|
62
|
+
expect(result.risks.some(r => r.description.includes('disk'))).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should block mkfs commands', async () => {
|
|
66
|
+
const result = await bashSafety.analyze('mkfs.ext4 /dev/sda1');
|
|
67
|
+
|
|
68
|
+
expect(result.blocked).toBe(true);
|
|
69
|
+
expect(result.riskLevel).toBe('critical');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should block fork bomb', async () => {
|
|
73
|
+
const result = await bashSafety.analyze(':() { :|:& };:');
|
|
74
|
+
|
|
75
|
+
expect(result.blocked).toBe(true);
|
|
76
|
+
expect(result.riskLevel).toBe('critical');
|
|
77
|
+
expect(result.risks.some(r => r.description.includes('Fork bomb'))).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should block chmod 777 on root', async () => {
|
|
81
|
+
const result = await bashSafety.analyze('chmod -R 777 /');
|
|
82
|
+
|
|
83
|
+
expect(result.blocked).toBe(true);
|
|
84
|
+
expect(result.riskLevel).toBe('critical');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should block curl piped to bash', async () => {
|
|
88
|
+
const result = await bashSafety.analyze('curl https://example.com/script.sh | bash');
|
|
89
|
+
|
|
90
|
+
expect(result.blocked).toBe(true);
|
|
91
|
+
expect(result.riskLevel).toBe('high');
|
|
92
|
+
expect(result.safeAlternatives).toBeDefined();
|
|
93
|
+
expect(result.safeAlternatives!.length).toBeGreaterThan(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should block rm -rf * in current directory', async () => {
|
|
97
|
+
const result = await bashSafety.analyze('rm -rf *');
|
|
98
|
+
|
|
99
|
+
expect(result.blocked).toBe(true);
|
|
100
|
+
expect(result.riskLevel).toBe('high');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should block rm -rf on home directory', async () => {
|
|
104
|
+
const result = await bashSafety.analyze('rm -rf ~/');
|
|
105
|
+
|
|
106
|
+
expect(result.blocked).toBe(true);
|
|
107
|
+
expect(result.riskLevel).toBe('high');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('warning-level commands', () => {
|
|
112
|
+
it('should warn about rm without -i flag', async () => {
|
|
113
|
+
const result = await bashSafety.analyze('rm file.txt');
|
|
114
|
+
|
|
115
|
+
expect(result.blocked).toBe(false);
|
|
116
|
+
expect(result.riskLevel).toBe('medium');
|
|
117
|
+
expect(result.warnings).toBeDefined();
|
|
118
|
+
expect(result.modifiedCommand).toBe('rm -i file.txt');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should warn about sudo rm', async () => {
|
|
122
|
+
const result = await bashSafety.analyze('sudo rm important.txt');
|
|
123
|
+
|
|
124
|
+
expect(result.blocked).toBe(false);
|
|
125
|
+
expect(result.risks.some(r => r.type === 'privilege')).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should warn about git push --force', async () => {
|
|
129
|
+
const result = await bashSafety.analyze('git push origin main --force');
|
|
130
|
+
|
|
131
|
+
expect(result.blocked).toBe(false);
|
|
132
|
+
expect(result.riskLevel).toBe('medium');
|
|
133
|
+
expect(result.risks.some(r => r.description.includes('Force push'))).toBe(true);
|
|
134
|
+
expect(result.safeAlternatives).toBeDefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should warn about git reset --hard', async () => {
|
|
138
|
+
const result = await bashSafety.analyze('git reset --hard HEAD~1');
|
|
139
|
+
|
|
140
|
+
expect(result.blocked).toBe(false);
|
|
141
|
+
expect(result.riskLevel).toBe('medium');
|
|
142
|
+
expect(result.risks.some(r => r.description.includes('Hard reset'))).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should warn about DROP DATABASE', async () => {
|
|
146
|
+
const result = await bashSafety.analyze('mysql -e "DROP DATABASE production"');
|
|
147
|
+
|
|
148
|
+
expect(result.blocked).toBe(false);
|
|
149
|
+
expect(result.riskLevel).toBe('high');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should warn about kill -9', async () => {
|
|
153
|
+
const result = await bashSafety.analyze('kill -9 12345');
|
|
154
|
+
|
|
155
|
+
expect(result.blocked).toBe(false);
|
|
156
|
+
expect(result.riskLevel).toBe('low');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('secret detection', () => {
|
|
161
|
+
it('should detect password in command', async () => {
|
|
162
|
+
const result = await bashSafety.analyze('mysql -p password=secret123');
|
|
163
|
+
|
|
164
|
+
expect(result.risks.some(r => r
|
|
165
|
+
|
|
166
|
+
--- __tests__/hooks/file-organization.test.ts (truncated) ---
|
|
167
|
+
/**
|
|
168
|
+
* V3 File Organization Hook Tests
|
|
169
|
+
*
|
|
170
|
+
* Tests for file organization enforcement and formatter recommendations.
|
|
171
|
+
*
|
|
172
|
+
* @module v3/shared/hooks/__tests__/file-organization.test
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
176
|
+
import {
|
|
177
|
+
createHookRegistry,
|
|
178
|
+
createFileOrganizationHook,
|
|
179
|
+
FileOrganizationHook,
|
|
180
|
+
HookRegistry,
|
|
181
|
+
} from '../../src/hooks/index.js';
|
|
182
|
+
|
|
183
|
+
describe('FileOrganizationHook', () => {
|
|
184
|
+
let registry: HookRegistry;
|
|
185
|
+
let fileOrg: FileOrganizationHook;
|
|
186
|
+
|
|
187
|
+
beforeEach(() => {
|
|
188
|
+
registry = createHookRegistry();
|
|
189
|
+
fileOrg = createFileOrganizationHook(registry);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('root folder blocking', () => {
|
|
193
|
+
it('should block TypeScript source files in root', async () => {
|
|
194
|
+
const result = await fileOrg.analyze('utils.ts');
|
|
195
|
+
|
|
196
|
+
expect(result.blocked).toBe(true);
|
|
197
|
+
expect(result.blockReason).toBeDefined();
|
|
198
|
+
expect(result.suggestedDirectory).toBe('src/');
|
|
199
|
+
expect(result.suggestedPath).toBe('src/utils.ts');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should block JavaScript source files in root', async () => {
|
|
203
|
+
const result = await fileOrg.analyze('index.js');
|
|
204
|
+
|
|
205
|
+
expect(result.blocked).toBe(true);
|
|
206
|
+
expect(result.suggestedDirectory).toBe('src/');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should block TypeScript test files in root', async () => {
|
|
210
|
+
const result = await fileOrg.analyze('utils.test.ts');
|
|
211
|
+
|
|
212
|
+
expect(result.blocked).toBe(true);
|
|
213
|
+
expect(result.suggestedDirectory).toBe('tests/');
|
|
214
|
+
expect(result.suggestedPath).toBe('tests/utils.test.ts');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should block spec files in root', async () => {
|
|
218
|
+
const result = await fileOrg.analyze('api.spec.ts');
|
|
219
|
+
|
|
220
|
+
expect(result.blocked).toBe(true);
|
|
221
|
+
expect(result.suggestedDirectory).toBe('tests/');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should block Python files in root', async () => {
|
|
225
|
+
const result = await fileOrg.analyze('main.py');
|
|
226
|
+
|
|
227
|
+
expect(result.blocked).toBe(true);
|
|
228
|
+
expect(result.suggestedDirectory).toBe('src/');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should block Go files in root', async () => {
|
|
232
|
+
const result = await fileOrg.analyze('main.go');
|
|
233
|
+
|
|
234
|
+
expect(result.blocked).toBe(true);
|
|
235
|
+
expect(result.suggestedDirectory).toBe('cmd/');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should block shell scripts in root', async () => {
|
|
239
|
+
const result = await fileOrg.analyze('deploy.sh');
|
|
240
|
+
|
|
241
|
+
expect(result.blocked).toBe(true);
|
|
242
|
+
expect(result.suggestedDirectory).toBe('scripts/');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should block CSS files in root', async () => {
|
|
246
|
+
const result = await fileOrg.analyze('styles.css');
|
|
247
|
+
|
|
248
|
+
expect(result.blocked).toBe(true);
|
|
249
|
+
expect(result.suggestedDirectory).toBe('styles/');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('allowed root files', () => {
|
|
254
|
+
it('should allow JSON config files in root', async () => {
|
|
255
|
+
const result = await fileOrg.analyze('package.json');
|
|
256
|
+
|
|
257
|
+
expect(result.blocked).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should allow YAML config files in root', async () => {
|
|
261
|
+
const result = await fileOrg.analyze('config.yaml');
|
|
262
|
+
|
|
263
|
+
expect(result.blocked).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should allow Markdown files in root', async () => {
|
|
267
|
+
const result = await fileOrg.analyze('README.md');
|
|
268
|
+
|
|
269
|
+
expect(result.blocked).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should allow environment files in root', async () => {
|
|
273
|
+
const result = await fileOrg.analyze('.env');
|
|
274
|
+
|
|
275
|
+
expect(result.blocked).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should allow .env.local files in root', async () => {
|
|
279
|
+
const result = await fileOrg.analyze('.env.local');
|
|
280
|
+
|
|
281
|
+
expect(result.blocked).toBe(false);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('files in correct directories', () => {
|
|
286
|
+
it('should allow TypeScript files in src/', async () => {
|
|
287
|
+
const result = await fileOrg.analyze('src/utils.ts');
|
|
288
|
+
|
|
289
|
+
expect(result.blocked).toBe(false);
|
|
290
|
+
expect(result.issues?.some(i => i.type === 'wrong-directory')).toBeFalsy();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should allow test files in tests/', async () => {
|
|
294
|
+
const result = await fileOrg.analyze('tests/utils.test.ts');
|
|
295
|
+
|
|
296
|
+
expect(result.blocked).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should allow test files in __tests__/', async () => {
|
|
300
|
+
const result = await fileOrg.analyze('__tests__/utils.test.ts');
|
|
301
|
+
|
|
302
|
+
expect(result.blocked).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should allow scripts in scripts/', async () => {
|
|
306
|
+
const result = await fileOrg.analyze('scripts/deploy.sh');
|
|
307
|
+
|
|
308
|
+
expect(result.blocked).toBe(false);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should allow Go files in cmd/', async () => {
|
|
312
|
+
const result = await fileOrg.analyze('cmd/main.go');
|
|
313
|
+
|
|
314
|
+
expect(result.blocked).toBe(false);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('files in wrong directories', () => {
|
|
319
|
+
it('should warn about test files in src/', async () => {
|
|
320
|
+
const result = await fileOrg.analyze('src/utils.test.ts');
|
|
321
|
+
|
|
322
|
+
expect(result.blocked).toBe(false);
|
|
323
|
+
expect(result.issues?.some(i => i.type === 'wrong-directory')).toBe(true);
|
|
324
|
+
expect(result.warnings?.length).toBeGreaterThan(0);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should warn about source files in tests/', async () => {
|
|
328
|
+
const result = await fileO
|
|
329
|
+
|
|
330
|
+
--- __tests__/hooks/git-commit.test.ts (truncated) ---
|
|
331
|
+
/**
|
|
332
|
+
* V3 Git Commit Hook Tests
|
|
333
|
+
*
|
|
334
|
+
* Tests for git commit message formatting and validation.
|
|
335
|
+
*
|
|
336
|
+
* @module v3/shared/hooks/__tests__/git-commit.test
|
|
337
|
+
*/
|
|
338
|
+
|
|
339
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
340
|
+
import {
|
|
341
|
+
createHookRegistry,
|
|
342
|
+
createGitCommitHook,
|
|
343
|
+
GitCommitHook,
|
|
344
|
+
HookRegistry,
|
|
345
|
+
} from '../../src/hooks/index.js';
|
|
346
|
+
|
|
347
|
+
describe('GitCommitHook', () => {
|
|
348
|
+
let registry: HookRegistry;
|
|
349
|
+
let gitCommit: GitCommitHook;
|
|
350
|
+
|
|
351
|
+
beforeEach(() => {
|
|
352
|
+
registry = createHookRegistry();
|
|
353
|
+
gitCommit = createGitCommitHook(registry);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('commit type detection', () => {
|
|
357
|
+
it('should detect feat type from message', async () => {
|
|
358
|
+
const result = await gitCommit.process('Add user authentication');
|
|
359
|
+
|
|
360
|
+
expect(result.commitType).toBe('feat');
|
|
361
|
+
expect(result.modifiedMessage).toMatch(/^feat:/);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should detect fix type from message', async () => {
|
|
365
|
+
const result = await gitCommit.process('Fix login validation bug');
|
|
366
|
+
|
|
367
|
+
expect(result.commitType).toBe('fix');
|
|
368
|
+
expect(result.modifiedMessage).toMatch(/^fix:/);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should detect docs type from message', async () => {
|
|
372
|
+
const result = await gitCommit.process('Update README documentation');
|
|
373
|
+
|
|
374
|
+
expect(result.commitType).toBe('docs');
|
|
375
|
+
expect(result.modifiedMessage).toMatch(/^docs:/);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should detect refactor type from message', async () => {
|
|
379
|
+
const result = await gitCommit.process('Refactor authentication module');
|
|
380
|
+
|
|
381
|
+
expect(result.commitType).toBe('refactor');
|
|
382
|
+
expect(result.modifiedMessage).toMatch(/^refactor:/);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should detect test type from message', async () => {
|
|
386
|
+
const result = await gitCommit.process('Add unit tests for user service');
|
|
387
|
+
|
|
388
|
+
expect(result.commitType).toBe('test');
|
|
389
|
+
expect(result.modifiedMessage).toMatch(/^test:/);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should detect perf type from message', async () => {
|
|
393
|
+
const result = await gitCommit.process('Optimize database queries');
|
|
394
|
+
|
|
395
|
+
expect(result.commitType).toBe('perf');
|
|
396
|
+
expect(result.modifiedMessage).toMatch(/^perf:/);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should detect build type from message', async () => {
|
|
400
|
+
const result = await gitCommit.process('Update webpack configuration');
|
|
401
|
+
|
|
402
|
+
expect(result.commitType).toBe('build');
|
|
403
|
+
expect(result.modifiedMessage).toMatch(/^build:/);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should detect ci type from message', async () => {
|
|
407
|
+
const result = await gitCommit.process('Update GitHub Actions workflow');
|
|
408
|
+
|
|
409
|
+
expect(result.commitType).toBe('ci');
|
|
410
|
+
expect(result.modifiedMessage).toMatch(/^ci:/);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should detect chore type from message', async () => {
|
|
414
|
+
const result = await gitCommit.process('Update dependencies');
|
|
415
|
+
|
|
416
|
+
expect(result.commitType).toBe('chore');
|
|
417
|
+
expect(result.modifiedMessage).toMatch(/^chore:/);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('should detect revert type from message', async () => {
|
|
421
|
+
const result = await gitCommit.process('Revert previous commit');
|
|
422
|
+
|
|
423
|
+
expect(result.commitType).toBe('revert');
|
|
424
|
+
expect(result.modifiedMessage).toMatch(/^revert:/);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe('preserving existing prefixes', () => {
|
|
429
|
+
it('should not add duplicate prefix if already present', async () => {
|
|
430
|
+
const result = await gitCommit.process('feat: add user authentication');
|
|
431
|
+
|
|
432
|
+
expect(result.commitType).toBe('feat');
|
|
433
|
+
// Should not have double prefix
|
|
434
|
+
expect(result.modifiedMessage).not.toMatch(/^feat:.*feat:/);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should detect type from existing prefix', async () => {
|
|
438
|
+
const result = await gitCommit.process('fix(auth): resolve login issue');
|
|
439
|
+
|
|
440
|
+
expect(result.commitType).toBe('fix');
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should handle scoped commits', async () => {
|
|
444
|
+
const result = await gitCommit.process('feat(api): add new endpoint');
|
|
445
|
+
|
|
446
|
+
expect(result.commitType).toBe('feat');
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('ticket extraction', () => {
|
|
451
|
+
it('should extract JIRA ticket from branch name', async () => {
|
|
452
|
+
const result = await gitCommit.process('Add feature', 'feature/ABC-123-new-feature');
|
|
453
|
+
|
|
454
|
+
expect(result.ticketReference).toBe('ABC-123');
|
|
455
|
+
expect(result.modifiedMessage).toContain('Refs: ABC-123');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should extract GitHub issue from branch name', async () => {
|
|
459
|
+
const result = await gitCommit.process('Fix bug', 'fix/#456-login-bug');
|
|
460
|
+
|
|
461
|
+
expect(result.ticketReference).toBe('#456');
|
|
462
|
+
expect(result.modifiedMessage).toContain('Refs: #456');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should not duplicate ticket if already in message', async () => {
|
|
466
|
+
const result = await gitCommit.process('Fix ABC-123 bug', 'feature/ABC-123-test');
|
|
467
|
+
|
|
468
|
+
// Should only appear once
|
|
469
|
+
const matches = result.modifiedMessage.match(/ABC-123/g);
|
|
470
|
+
expect(matches).toBeDefined();
|
|
471
|
+
expect(matches!.length).toBeLessThanOrEqual(2);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe('co-author addition', () => {
|
|
476
|
+
it('should add co-author by default', async () => {
|
|
477
|
+
const result = await gitCommit.process('Add feature');
|
|
478
|
+
|
|
479
|
+
expect(res
|
|
480
|
+
|
|
481
|
+
--- __tests__/hooks/index.ts ---
|
|
482
|
+
/**
|
|
483
|
+
* V3 Hooks Tests Index
|
|
484
|
+
*
|
|
485
|
+
* Exports all hook tests for the V3 hooks system.
|
|
486
|
+
*
|
|
487
|
+
* @module v3/shared/hooks/__tests__
|
|
488
|
+
*/
|
|
489
|
+
|
|
490
|
+
// Test files are automatically discovered by vitest
|
|
491
|
+
// This file serves as documentation of available tests
|
|
492
|
+
|
|
493
|
+
export const testFiles = [
|
|
494
|
+
'./task-hooks.test.ts',
|
|
495
|
+
'./session-hooks.test.ts',
|
|
496
|
+
'./bash-safety.test.ts',
|
|
497
|
+
'./file-organization.test.ts',
|
|
498
|
+
'./git-commit.test.ts',
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
export const testCategories = {
|
|
502
|
+
'Core Hooks': ['task-hooks.test.ts', 'session-hooks.test.ts'],
|
|
503
|
+
'Safety Hooks': ['bash-safety.test.ts', 'file-organization.test.ts', 'git-commit.test.ts'],
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
--- __tests__/hooks/session-hooks.test.ts (truncated) ---
|
|
508
|
+
/**
|
|
509
|
+
* V3 Session Hooks Tests
|
|
510
|
+
*
|
|
511
|
+
* Tests for session-end and session-restore hook functionality.
|
|
512
|
+
*
|
|
513
|
+
* @module v3/shared/hooks/__tests__/session-hooks.test
|
|
514
|
+
*/
|
|
515
|
+
|
|
516
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
517
|
+
import {
|
|
518
|
+
createHookRegistry,
|
|
519
|
+
createSessionHooksManager,
|
|
520
|
+
SessionHooksManager,
|
|
521
|
+
HookRegistry,
|
|
522
|
+
HookEvent,
|
|
523
|
+
InMemorySessionStorage,
|
|
524
|
+
} from '../../src/hooks/index.js';
|
|
525
|
+
|
|
526
|
+
describe('SessionHooksManager', () => {
|
|
527
|
+
let registry: HookRegistry;
|
|
528
|
+
let sessionManager: SessionHooksManager;
|
|
529
|
+
let storage: InMemorySessionStorage;
|
|
530
|
+
|
|
531
|
+
beforeEach(() => {
|
|
532
|
+
registry = createHookRegistry();
|
|
533
|
+
storage = new InMemorySessionStorage();
|
|
534
|
+
sessionManager = createSessionHooksManager(registry, storage);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('session lifecycle hooks', () => {
|
|
538
|
+
it('should register session hooks on creation', () => {
|
|
539
|
+
const startHooks = registry.getHandlers(HookEvent.SessionStart);
|
|
540
|
+
const endHooks = registry.getHandlers(HookEvent.SessionEnd);
|
|
541
|
+
const resumeHooks = registry.getHandlers(HookEvent.SessionResume);
|
|
542
|
+
|
|
543
|
+
expect(startHooks.some(h => h.name === 'session-hooks:start')).toBe(true);
|
|
544
|
+
expect(endHooks.some(h => h.name === 'session-hooks:end')).toBe(true);
|
|
545
|
+
expect(resumeHooks.some(h => h.name === 'session-hooks:resume')).toBe(true);
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
describe('session-end hook', () => {
|
|
550
|
+
it('should end session and return summary', async () => {
|
|
551
|
+
// Simulate session start by triggering tracking
|
|
552
|
+
const context = {
|
|
553
|
+
event: HookEvent.SessionStart,
|
|
554
|
+
timestamp: new Date(),
|
|
555
|
+
session: { id: 'test-session', startTime: new Date() },
|
|
556
|
+
};
|
|
557
|
+
await sessionManager['handleSessionStart'](context);
|
|
558
|
+
|
|
559
|
+
// Wait a moment to ensure duration > 0
|
|
560
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
561
|
+
|
|
562
|
+
const result = await sessionManager.executeSessionEnd();
|
|
563
|
+
|
|
564
|
+
expect(result.success).toBe(true);
|
|
565
|
+
expect(result.duration).toBeGreaterThanOrEqual(0);
|
|
566
|
+
expect(result.summary).toBeDefined();
|
|
567
|
+
expect(result.summary!.tasksExecuted).toBe(0);
|
|
568
|
+
expect(result.summary!.commandsExecuted).toBe(0);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should persist session state', async () => {
|
|
572
|
+
// Start session
|
|
573
|
+
const startContext = {
|
|
574
|
+
event: HookEvent.SessionStart,
|
|
575
|
+
timestamp: new Date(),
|
|
576
|
+
session: { id: 'persist-session', startTime: new Date() },
|
|
577
|
+
};
|
|
578
|
+
await sessionManager['handleSessionStart'](startContext);
|
|
579
|
+
|
|
580
|
+
const result = await sessionManager.executeSessionEnd();
|
|
581
|
+
|
|
582
|
+
expect(result.persistedState).toBeDefined();
|
|
583
|
+
expect(result.statePath).toBeDefined();
|
|
584
|
+
|
|
585
|
+
// Verify state was saved
|
|
586
|
+
const sessions = await storage.list();
|
|
587
|
+
expect(sessions.length).toBeGreaterThan(0);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it('should handle ending session without active session', async () => {
|
|
591
|
+
const result = await sessionManager.executeSessionEnd();
|
|
592
|
+
expect(result.success).toBe(true);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('should reset activity tracking after session end', async () => {
|
|
596
|
+
// Start session
|
|
597
|
+
const context = {
|
|
598
|
+
event: HookEvent.SessionStart,
|
|
599
|
+
timestamp: new Date(),
|
|
600
|
+
session: { id: 'reset-session', startTime: new Date() },
|
|
601
|
+
};
|
|
602
|
+
await sessionManager['handleSessionStart'](context);
|
|
603
|
+
|
|
604
|
+
await sessionManager.executeSessionEnd();
|
|
605
|
+
|
|
606
|
+
expect(sessionManager.getCurrentSessionId()).toBeNull();
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
describe('session-restore hook', () => {
|
|
611
|
+
it('should restore a previous session', async () => {
|
|
612
|
+
// Create and end a session first
|
|
613
|
+
const startContext = {
|
|
614
|
+
event: HookEvent.SessionStart,
|
|
615
|
+
timestamp: new Date(),
|
|
616
|
+
session: { id: 'restore-test', startTime: new Date() },
|
|
617
|
+
};
|
|
618
|
+
await sessionManager['handleSessionStart'](startContext);
|
|
619
|
+
await sessionManager.executeSessionEnd();
|
|
620
|
+
|
|
621
|
+
// Restore the session
|
|
622
|
+
const result = await sessionManager.executeSessionRestore('restore-test');
|
|
623
|
+
|
|
624
|
+
expect(result.success).toBe(true);
|
|
625
|
+
expect(result.restoredState).toBeDefined();
|
|
626
|
+
expect(result.restoredState!.sessionId).toBe('restore-test');
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it('should restore latest session when no ID specified', async () => {
|
|
630
|
+
// Create and end multiple sessions
|
|
631
|
+
for (const id of ['session-1', 'session-2', 'session-3']) {
|
|
632
|
+
const context = {
|
|
633
|
+
event: HookEvent.SessionStart,
|
|
634
|
+
timestamp: new Date(),
|
|
635
|
+
session: { id, startTime: new Date() },
|
|
636
|
+
};
|
|
637
|
+
await sessionManager['handleSessionStart'](context);
|
|
638
|
+
await sessionManager.executeSessionEnd();
|
|
639
|
+
await new Promise(resolve => setTimeout(resolve, 5));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const result = await sessionManager.executeSessionRestore();
|
|
643
|
+
|
|
644
|
+
expect(result.success).toBe(true);
|
|
645
|
+
expect(result.restoredState).toBeDefined();
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it('should fail gracefully when session not found', async () => {
|
|
649
|
+
const result = await sessionManager.executeSessionRestore('non-existent');
|
|
650
|
+
|
|
651
|
+
expect(result.success).toBe(false);
|
|
652
|
+
expect(result.error).toBeDefin
|
|
653
|
+
|
|
654
|
+
--- __tests__/hooks/task-hooks.test.ts (truncated) ---
|
|
655
|
+
/**
|
|
656
|
+
* V3 Task Hooks Tests
|
|
657
|
+
*
|
|
658
|
+
* Tests for pre-task and post-task hook functionality.
|
|
659
|
+
*
|
|
660
|
+
* @module v3/shared/hooks/__tests__/task-hooks.test
|
|
661
|
+
*/
|
|
662
|
+
|
|
663
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
664
|
+
import {
|
|
665
|
+
createHookRegistry,
|
|
666
|
+
createTaskHooksManager,
|
|
667
|
+
TaskHooksManager,
|
|
668
|
+
HookRegistry,
|
|
669
|
+
HookEvent,
|
|
670
|
+
} from '../../src/hooks/index.js';
|
|
671
|
+
|
|
672
|
+
describe('TaskHooksManager', () => {
|
|
673
|
+
let registry: HookRegistry;
|
|
674
|
+
let taskManager: TaskHooksManager;
|
|
675
|
+
|
|
676
|
+
beforeEach(() => {
|
|
677
|
+
registry = createHookRegistry();
|
|
678
|
+
taskManager = createTaskHooksManager(registry);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
describe('pre-task hook', () => {
|
|
682
|
+
it('should register pre-task hook on creation', () => {
|
|
683
|
+
const hooks = registry.getHandlers(HookEvent.PreTaskExecute);
|
|
684
|
+
expect(hooks.length).toBeGreaterThan(0);
|
|
685
|
+
expect(hooks.some(h => h.name === 'task-hooks:pre-task')).toBe(true);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('should analyze task and suggest agents for coding task', async () => {
|
|
689
|
+
const result = await taskManager.executePreTask(
|
|
690
|
+
'task-123',
|
|
691
|
+
'Implement user authentication feature'
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
expect(result.success).toBe(true);
|
|
695
|
+
expect(result.suggestedAgents).toBeDefined();
|
|
696
|
+
expect(result.suggestedAgents!.length).toBeGreaterThan(0);
|
|
697
|
+
expect(result.suggestedAgents![0].type).toBe('coder');
|
|
698
|
+
expect(result.suggestedAgents![0].confidence).toBeGreaterThan(0);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('should suggest security-architect for security tasks', async () => {
|
|
702
|
+
const result = await taskManager.executePreTask(
|
|
703
|
+
'task-456',
|
|
704
|
+
'Fix security vulnerability in authentication'
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
expect(result.success).toBe(true);
|
|
708
|
+
expect(result.suggestedAgents).toBeDefined();
|
|
709
|
+
const securityAgent = result.suggestedAgents!.find(a => a.type === 'security-architect');
|
|
710
|
+
expect(securityAgent).toBeDefined();
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it('should suggest tester for test-related tasks', async () => {
|
|
714
|
+
const result = await taskManager.executePreTask(
|
|
715
|
+
'task-789',
|
|
716
|
+
'Write unit tests for user service'
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
expect(result.success).toBe(true);
|
|
720
|
+
expect(result.suggestedAgents).toBeDefined();
|
|
721
|
+
const testerAgent = result.suggestedAgents!.find(a => a.type === 'tester');
|
|
722
|
+
expect(testerAgent).toBeDefined();
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it('should estimate complexity based on task description', async () => {
|
|
726
|
+
// Simple task
|
|
727
|
+
const simpleResult = await taskManager.executePreTask(
|
|
728
|
+
'task-simple',
|
|
729
|
+
'Fix typo in readme'
|
|
730
|
+
);
|
|
731
|
+
expect(simpleResult.complexity).toBe('low');
|
|
732
|
+
|
|
733
|
+
// Complex task
|
|
734
|
+
const complexResult = await taskManager.executePreTask(
|
|
735
|
+
'task-complex',
|
|
736
|
+
'Refactor and redesign the entire authentication system with multiple OAuth providers'
|
|
737
|
+
);
|
|
738
|
+
expect(complexResult.complexity).toBe('high');
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it('should detect risks in task description', async () => {
|
|
742
|
+
const result = await taskManager.executePreTask(
|
|
743
|
+
'task-risky',
|
|
744
|
+
'Delete old data from production database'
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
expect(result.risks).toBeDefined();
|
|
748
|
+
expect(result.risks!.length).toBeGreaterThan(0);
|
|
749
|
+
expect(result.risks!.some(r => r.includes('production'))).toBe(true);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
it('should track active tasks', async () => {
|
|
753
|
+
await taskManager.executePreTask('task-1', 'Task 1');
|
|
754
|
+
await taskManager.executePreTask('task-2', 'Task 2');
|
|
755
|
+
|
|
756
|
+
const activeTasks = taskManager.getActiveTasks();
|
|
757
|
+
expect(activeTasks.size).toBe(2);
|
|
758
|
+
expect(activeTasks.has('task-1')).toBe(true);
|
|
759
|
+
expect(activeTasks.has('task-2')).toBe(true);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('should provide recommendations for high complexity tasks', async () => {
|
|
763
|
+
const result = await taskManager.executePreTask(
|
|
764
|
+
'task-high-complexity',
|
|
765
|
+
'Implement complex distributed system with multiple services'
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
expect(result.recommendations).toBeDefined();
|
|
769
|
+
expect(result.recommendations!.length).toBeGreaterThan(0);
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
describe('post-task hook', () => {
|
|
774
|
+
it('should register post-task hook on creation', () => {
|
|
775
|
+
const hooks = registry.getHandlers(HookEvent.PostTaskExecute);
|
|
776
|
+
expect(hooks.length).toBeGreaterThan(0);
|
|
777
|
+
expect(hooks.some(h => h.name === 'task-hooks:post-task')).toBe(true);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it('should record successful task outcome', async () => {
|
|
781
|
+
// First start the task
|
|
782
|
+
await taskManager.executePreTask('task-success', 'Test task');
|
|
783
|
+
|
|
784
|
+
// Then complete it
|
|
785
|
+
const result = await taskManager.executePostTask('task-success', true);
|
|
786
|
+
|
|
787
|
+
expect(result.success).toBe(true);
|
|
788
|
+
expect(result.outcome).toBeDefined();
|
|
789
|
+
expect(result.outcome!.success).toBe(true);
|
|
790
|
+
expect(result.outcome!.duration).toBeGreaterThanOrEqual(0);
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it('should record failed task outcome', async () => {
|
|
794
|
+
await taskManager.executePreTask('task-failed', 'Test task');
|
|
795
|
+
|
|
796
|
+
const result = await taskManager.executePostTask('task-failed', false, {
|
|
797
|
+
error: 'Test failed due to timeout',
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
ex
|
|
801
|
+
|
|
802
|
+
--- src/core/config/defaults.ts ---
|
|
803
|
+
/**
|
|
804
|
+
* V3 Default Configuration Values
|
|
805
|
+
*/
|
|
806
|
+
|
|
807
|
+
import type {
|
|
808
|
+
AgentConfig,
|
|
809
|
+
TaskConfig,
|
|
810
|
+
SwarmConfig,
|
|
811
|
+
MemoryConfig,
|
|
812
|
+
MCPServerConfig,
|
|
813
|
+
OrchestratorConfig,
|
|
814
|
+
SystemConfig,
|
|
815
|
+
} from './schema.js';
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Default agent configuration
|
|
819
|
+
*/
|
|
820
|
+
export const defaultAgentConfig: Partial<AgentConfig> = {
|
|
821
|
+
capabilities: [],
|
|
822
|
+
maxConcurrentTasks: 5,
|
|
823
|
+
priority: 50,
|
|
824
|
+
retryPolicy: {
|
|
825
|
+
maxRetries: 3,
|
|
826
|
+
backoffMs: 1000,
|
|
827
|
+
backoffMultiplier: 2,
|
|
828
|
+
},
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Default task configuration
|
|
833
|
+
*/
|
|
834
|
+
export const defaultTaskConfig: Partial<TaskConfig> = {
|
|
835
|
+
priority: 50,
|
|
836
|
+
metadata: {
|
|
837
|
+
maxRetries: 3,
|
|
838
|
+
},
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Default swarm configuration (core version)
|
|
843
|
+
*/
|
|
844
|
+
export const defaultSwarmConfigCore: SwarmConfig = {
|
|
845
|
+
topology: 'hierarchical-mesh',
|
|
846
|
+
maxAgents: 20,
|
|
847
|
+
autoScale: {
|
|
848
|
+
enabled: false,
|
|
849
|
+
minAgents: 1,
|
|
850
|
+
maxAgents: 20,
|
|
851
|
+
scaleUpThreshold: 0.8,
|
|
852
|
+
scaleDownThreshold: 0.3,
|
|
853
|
+
},
|
|
854
|
+
coordination: {
|
|
855
|
+
consensusRequired: false,
|
|
856
|
+
timeoutMs: 10000,
|
|
857
|
+
retryPolicy: {
|
|
858
|
+
maxRetries: 3,
|
|
859
|
+
backoffMs: 500,
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
communication: {
|
|
863
|
+
protocol: 'events',
|
|
864
|
+
batchSize: 10,
|
|
865
|
+
flushIntervalMs: 100,
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Default memory configuration (hybrid backend - ADR-009)
|
|
871
|
+
*/
|
|
872
|
+
export const defaultMemoryConfig: MemoryConfig = {
|
|
873
|
+
type: 'hybrid',
|
|
874
|
+
path: './data/memory',
|
|
875
|
+
sqlite: {
|
|
876
|
+
inMemory: false,
|
|
877
|
+
wal: true,
|
|
878
|
+
},
|
|
879
|
+
agentdb: {
|
|
880
|
+
dimensions: 1536,
|
|
881
|
+
indexType: 'hnsw',
|
|
882
|
+
efConstruction: 200,
|
|
883
|
+
m: 16,
|
|
884
|
+
quantization: 'none',
|
|
885
|
+
},
|
|
886
|
+
hybrid: {
|
|
887
|
+
vectorThreshold: 100,
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Default MCP server configuration
|
|
893
|
+
*/
|
|
894
|
+
export const defaultMCPServerConfig: MCPServerConfig = {
|
|
895
|
+
name: 'claude-flow',
|
|
896
|
+
version: '3.0.0',
|
|
897
|
+
transport: {
|
|
898
|
+
type: 'stdio',
|
|
899
|
+
},
|
|
900
|
+
capabilities: {
|
|
901
|
+
tools: true,
|
|
902
|
+
resources: true,
|
|
903
|
+
prompts: true,
|
|
904
|
+
logging: true,
|
|
905
|
+
},
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Default orchestrator configuration
|
|
910
|
+
*/
|
|
911
|
+
export const defaultOrchestratorConfig: OrchestratorConfig = {
|
|
912
|
+
session: {
|
|
913
|
+
persistSessions: true,
|
|
914
|
+
dataDir: './data',
|
|
915
|
+
sessionRetentionMs: 3600000, // 1 hour
|
|
916
|
+
},
|
|
917
|
+
health: {
|
|
918
|
+
checkInterval: 30000, // 30 seconds
|
|
919
|
+
historyLimit: 100,
|
|
920
|
+
degradedThreshold: 1,
|
|
921
|
+
unhealthyThreshold: 2,
|
|
922
|
+
},
|
|
923
|
+
lifecycle: {
|
|
924
|
+
maxConcurrentAgents: 20,
|
|
925
|
+
spawnTimeout: 30000, // 30 seconds
|
|
926
|
+
terminateTimeout: 10000, // 10 seconds
|
|
927
|
+
maxSpawnRetries: 3,
|
|
928
|
+
},
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Default full system configuration
|
|
933
|
+
*/
|
|
934
|
+
export const defaultSystemConfig: SystemConfig = {
|
|
935
|
+
orchestrator: defaultOrchestratorConfig,
|
|
936
|
+
memory: defaultMemoryConfig,
|
|
937
|
+
mcp: defaultMCPServerConfig,
|
|
938
|
+
swarm: defaultSwarmConfigCore,
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Agent type presets
|
|
943
|
+
*/
|
|
944
|
+
export const agentTypePresets: Record<string, Partial<AgentConfig>> = {
|
|
945
|
+
coder: {
|
|
946
|
+
type: 'coder',
|
|
947
|
+
capabilities: ['code', 'debug', 'refactor', 'test'],
|
|
948
|
+
maxConcurrentTasks: 3,
|
|
949
|
+
priority: 70,
|
|
950
|
+
},
|
|
951
|
+
reviewer: {
|
|
952
|
+
type: 'reviewer',
|
|
953
|
+
capabilities: ['review', 'analyze', 'suggest'],
|
|
954
|
+
maxConcurrentTasks: 5,
|
|
955
|
+
priority: 60,
|
|
956
|
+
},
|
|
957
|
+
tester: {
|
|
958
|
+
type: 'tester',
|
|
959
|
+
capabilities: ['test', 'validate', 'benchmark'],
|
|
960
|
+
maxConcurrentTasks: 4,
|
|
961
|
+
priority: 65,
|
|
962
|
+
},
|
|
963
|
+
researcher: {
|
|
964
|
+
type: 'researcher',
|
|
965
|
+
capabilities: ['research', 'analyze', 'summarize'],
|
|
966
|
+
maxConcurrentTasks: 3,
|
|
967
|
+
priority: 50,
|
|
968
|
+
},
|
|
969
|
+
planner: {
|
|
970
|
+
type: 'planner',
|
|
971
|
+
capabilities: ['plan', 'organize', 'decompose'],
|
|
972
|
+
maxConcurrentTasks: 2,
|
|
973
|
+
priority: 80,
|
|
974
|
+
},
|
|
975
|
+
architect: {
|
|
976
|
+
type: 'architect',
|
|
977
|
+
capabilities: ['design', 'architecture', 'patterns'],
|
|
978
|
+
maxConcurrentTasks: 2,
|
|
979
|
+
priority: 85,
|
|
980
|
+
},
|
|
981
|
+
coordinator: {
|
|
982
|
+
type: 'coordinator',
|
|
983
|
+
capabilities: ['coordinate', 'delegate', 'monitor'],
|
|
984
|
+
maxConcurrentTasks: 10,
|
|
985
|
+
priority: 90,
|
|
986
|
+
},
|
|
987
|
+
security: {
|
|
988
|
+
type: 'security',
|
|
989
|
+
capabilities: ['audit', 'scan', 'validate', 'secure'],
|
|
990
|
+
maxConcurrentTasks: 3,
|
|
991
|
+
priority: 95,
|
|
992
|
+
},
|
|
993
|
+
performance: {
|
|
994
|
+
type: 'performance',
|
|
995
|
+
capabilities: ['benchmark', 'optimize', 'profile'],
|
|
996
|
+
maxConcurrentTasks: 2,
|
|
997
|
+
priority: 70,
|
|
998
|
+
},
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Get merged configuration with defaults
|
|
1003
|
+
*/
|
|
1004
|
+
export function mergeWithDefaults<T extends Record<string, unknown>>(
|
|
1005
|
+
config: Partial<T>,
|
|
1006
|
+
defaults: T,
|
|
1007
|
+
): T {
|
|
1008
|
+
return { ...defaults, ...config } as T;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
--- src/core/config/index.ts ---
|
|
1013
|
+
/**
|
|
1014
|
+
* V3 Configuration - Public API
|
|
1015
|
+
*/
|
|
1016
|
+
|
|
1017
|
+
// Schemas
|
|
1018
|
+
export * from './schema.js';
|
|
1019
|
+
|
|
1020
|
+
// Validation
|
|
1021
|
+
export * from './validator.js';
|
|
1022
|
+
|
|
1023
|
+
// Defaults
|
|
1024
|
+
export * from './defaults.js';
|
|
1025
|
+
|
|
1026
|
+
// Loader
|
|
1027
|
+
export * from './loader.js';
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
--- src/core/config/loader.ts (truncated) ---
|
|
1031
|
+
/**
|
|
1032
|
+
* V3 Configuration Loader
|
|
1033
|
+
* Load configuration from various sources
|
|
1034
|
+
*/
|
|
1035
|
+
|
|
1036
|
+
import { readFile } from 'fs/promises';
|
|
1037
|
+
import { join, resolve } from 'path';
|
|
1038
|
+
import { existsSync } from 'fs';
|
|
1039
|
+
import type { SystemConfig } from './schema.js';
|
|
1040
|
+
import { validateSystemConfig, type ValidationResult } from './validator.js';
|
|
1041
|
+
import { defaultSystemConfig, mergeWithDefaults } from './defaults.js';
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Configuration source type
|
|
1045
|
+
*/
|
|
1046
|
+
export type ConfigSource = 'file' | 'env' | 'default' | 'merged';
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Loaded configuration with metadata
|
|
1050
|
+
*/
|
|
1051
|
+
export interface LoadedConfig {
|
|
1052
|
+
config: SystemConfig;
|
|
1053
|
+
source: ConfigSource;
|
|
1054
|
+
path?: string;
|
|
1055
|
+
warnings?: string[];
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Configuration file names to search for
|
|
1060
|
+
*/
|
|
1061
|
+
const CONFIG_FILE_NAMES = [
|
|
1062
|
+
'claude-flow.config.json',
|
|
1063
|
+
'claude-flow.config.js',
|
|
1064
|
+
'claude-flow.json',
|
|
1065
|
+
'.claude-flow.json',
|
|
1066
|
+
];
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Find configuration file in directory
|
|
1070
|
+
*/
|
|
1071
|
+
async function findConfigFile(directory: string): Promise<string | null> {
|
|
1072
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
1073
|
+
const path = join(directory, name);
|
|
1074
|
+
if (existsSync(path)) {
|
|
1075
|
+
return path;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Load configuration from JSON file
|
|
1083
|
+
*/
|
|
1084
|
+
async function loadJsonConfig(path: string): Promise<unknown> {
|
|
1085
|
+
const content = await readFile(path, 'utf8');
|
|
1086
|
+
return JSON.parse(content);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Load configuration from environment variables
|
|
1091
|
+
*/
|
|
1092
|
+
function loadEnvConfig(): Partial<SystemConfig> {
|
|
1093
|
+
const config: Partial<SystemConfig> = {};
|
|
1094
|
+
|
|
1095
|
+
// Orchestrator settings
|
|
1096
|
+
if (process.env.CLAUDE_FLOW_MAX_AGENTS) {
|
|
1097
|
+
config.orchestrator = {
|
|
1098
|
+
...defaultSystemConfig.orchestrator,
|
|
1099
|
+
lifecycle: {
|
|
1100
|
+
...defaultSystemConfig.orchestrator.lifecycle,
|
|
1101
|
+
maxConcurrentAgents: parseInt(process.env.CLAUDE_FLOW_MAX_AGENTS, 10),
|
|
1102
|
+
},
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Data directory
|
|
1107
|
+
if (process.env.CLAUDE_FLOW_DATA_DIR) {
|
|
1108
|
+
config.orchestrator = {
|
|
1109
|
+
...config.orchestrator,
|
|
1110
|
+
...defaultSystemConfig.orchestrator,
|
|
1111
|
+
session: {
|
|
1112
|
+
...defaultSystemConfig.orchestrator.session,
|
|
1113
|
+
dataDir: process.env.CLAUDE_FLOW_DATA_DIR,
|
|
1114
|
+
},
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Memory type
|
|
1119
|
+
if (process.env.CLAUDE_FLOW_MEMORY_TYPE) {
|
|
1120
|
+
const memoryType = process.env.CLAUDE_FLOW_MEMORY_TYPE as NonNullable<SystemConfig['memory']>['type'];
|
|
1121
|
+
if (['sqlite', 'agentdb', 'hybrid', 'redis', 'memory'].includes(memoryType)) {
|
|
1122
|
+
config.memory = {
|
|
1123
|
+
...(defaultSystemConfig.memory ?? { type: 'hybrid' }),
|
|
1124
|
+
type: memoryType,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// MCP transport
|
|
1130
|
+
const defaultMcp = defaultSystemConfig.mcp ?? { name: 'claude-flow', version: '3.0.0', transport: { type: 'stdio' as const } };
|
|
1131
|
+
if (process.env.CLAUDE_FLOW_MCP_TRANSPORT) {
|
|
1132
|
+
const transport = process.env.CLAUDE_FLOW_MCP_TRANSPORT as 'stdio' | 'http' | 'websocket';
|
|
1133
|
+
if (['stdio', 'http', 'websocket'].includes(transport)) {
|
|
1134
|
+
config.mcp = {
|
|
1135
|
+
...defaultMcp,
|
|
1136
|
+
transport: {
|
|
1137
|
+
...defaultMcp.transport,
|
|
1138
|
+
type: transport,
|
|
1139
|
+
},
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
if (process.env.CLAUDE_FLOW_MCP_PORT) {
|
|
1145
|
+
config.mcp = {
|
|
1146
|
+
...config.mcp,
|
|
1147
|
+
...defaultMcp,
|
|
1148
|
+
transport: {
|
|
1149
|
+
...config.mcp?.transport,
|
|
1150
|
+
...defaultMcp.transport,
|
|
1151
|
+
port: parseInt(process.env.CLAUDE_FLOW_MCP_PORT, 10),
|
|
1152
|
+
},
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Swarm topology
|
|
1157
|
+
const defaultSwarm = defaultSystemConfig.swarm ?? { topology: 'hierarchical-mesh' as const, maxAgents: 20 };
|
|
1158
|
+
if (process.env.CLAUDE_FLOW_SWARM_TOPOLOGY) {
|
|
1159
|
+
const topology = process.env.CLAUDE_FLOW_SWARM_TOPOLOGY as NonNullable<SystemConfig['swarm']>['topology'];
|
|
1160
|
+
if (['hierarchical', 'mesh', 'ring', 'star', 'adaptive', 'hierarchical-mesh'].includes(topology)) {
|
|
1161
|
+
config.swarm = {
|
|
1162
|
+
...defaultSwarm,
|
|
1163
|
+
topology,
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
return config;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Configuration loader class
|
|
1173
|
+
*/
|
|
1174
|
+
export class ConfigLoader {
|
|
1175
|
+
private searchPaths: string[] = [];
|
|
1176
|
+
|
|
1177
|
+
constructor(additionalPaths?: string[]) {
|
|
1178
|
+
// Default search paths
|
|
1179
|
+
this.searchPaths = [
|
|
1180
|
+
process.cwd(),
|
|
1181
|
+
resolve(process.cwd(), '..'),
|
|
1182
|
+
resolve(process.env.HOME ?? '', '.claude-flow'),
|
|
1183
|
+
];
|
|
1184
|
+
|
|
1185
|
+
if (additionalPaths) {
|
|
1186
|
+
this.searchPaths.push(...additionalPaths);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Load configuration from all sources
|
|
1192
|
+
*/
|
|
1193
|
+
async load(): Promise<LoadedConfig> {
|
|
1194
|
+
const warnings: string[] = [];
|
|
1195
|
+
|
|
1196
|
+
// Start with defaults
|
|
1197
|
+
let config: SystemConfig = { ...defaultSystemConfig };
|
|
1198
|
+
let source: ConfigSource = 'default';
|
|
1199
|
+
let path: string | undefined;
|
|
1200
|
+
|
|
1201
|
+
// Try to load from file
|
|
1202
|
+
for (const searchPath of this.searchPaths) {
|
|
1203
|
+
const configPath = await findConfigFile(searchPath);
|
|
1204
|
+
if (configPath) {
|
|
1205
|
+
try {
|
|
1206
|
+
const fileConfig = await loadJsonConfig(configPath);
|
|
1207
|
+
const validation = validateSystemConfig(fileConfig);
|
|
1208
|
+
|
|
1209
|
+
if (validation.success) {
|
|
1210
|
+
config = mergeWithDefaults(validation.data!, defaultSystemConfig) as SystemConfig;
|
|
1211
|
+
source = 'file';
|
|
1212
|
+
path = configPath;
|
|
1213
|
+
break;
|
|
1214
|
+
} else {
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
--- src/core/config/schema.ts (truncated) ---
|
|
1218
|
+
/**
|
|
1219
|
+
* V3 Configuration Schemas
|
|
1220
|
+
* Zod schemas for all configuration types
|
|
1221
|
+
*/
|
|
1222
|
+
|
|
1223
|
+
import { z } from 'zod';
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Agent configuration schema
|
|
1227
|
+
*/
|
|
1228
|
+
export const AgentConfigSchema = z.object({
|
|
1229
|
+
id: z.string().min(1),
|
|
1230
|
+
name: z.string().min(1),
|
|
1231
|
+
type: z.string().min(1),
|
|
1232
|
+
capabilities: z.array(z.string()).default([]),
|
|
1233
|
+
maxConcurrentTasks: z.number().int().min(1).default(5),
|
|
1234
|
+
priority: z.number().int().min(0).max(100).default(50),
|
|
1235
|
+
timeout: z.number().int().positive().optional(),
|
|
1236
|
+
retryPolicy: z.object({
|
|
1237
|
+
maxRetries: z.number().int().min(0).default(3),
|
|
1238
|
+
backoffMs: z.number().int().positive().default(1000),
|
|
1239
|
+
backoffMultiplier: z.number().positive().default(2),
|
|
1240
|
+
}).optional(),
|
|
1241
|
+
resources: z.object({
|
|
1242
|
+
maxMemoryMb: z.number().int().positive().optional(),
|
|
1243
|
+
maxCpuPercent: z.number().min(0).max(100).optional(),
|
|
1244
|
+
}).optional(),
|
|
1245
|
+
metadata: z.record(z.unknown()).optional(),
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* Task configuration schema
|
|
1250
|
+
*/
|
|
1251
|
+
export const TaskConfigSchema = z.object({
|
|
1252
|
+
type: z.string().min(1),
|
|
1253
|
+
description: z.string().min(1),
|
|
1254
|
+
priority: z.number().int().min(0).max(100).default(50),
|
|
1255
|
+
timeout: z.number().int().positive().optional(),
|
|
1256
|
+
assignedAgent: z.string().optional(),
|
|
1257
|
+
input: z.record(z.unknown()).optional(),
|
|
1258
|
+
metadata: z.object({
|
|
1259
|
+
requiredCapabilities: z.array(z.string()).optional(),
|
|
1260
|
+
retryCount: z.number().int().min(0).optional(),
|
|
1261
|
+
maxRetries: z.number().int().min(0).optional(),
|
|
1262
|
+
critical: z.boolean().optional(),
|
|
1263
|
+
parentTaskId: z.string().optional(),
|
|
1264
|
+
childTaskIds: z.array(z.string()).optional(),
|
|
1265
|
+
tags: z.array(z.string()).optional(),
|
|
1266
|
+
}).optional(),
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Swarm configuration schema
|
|
1271
|
+
*/
|
|
1272
|
+
export const SwarmConfigSchema = z.object({
|
|
1273
|
+
topology: z.enum(['hierarchical', 'mesh', 'ring', 'star', 'adaptive', 'hierarchical-mesh']),
|
|
1274
|
+
maxAgents: z.number().int().positive().default(20),
|
|
1275
|
+
autoScale: z.object({
|
|
1276
|
+
enabled: z.boolean().default(false),
|
|
1277
|
+
minAgents: z.number().int().min(0).default(1),
|
|
1278
|
+
maxAgents: z.number().int().positive().default(20),
|
|
1279
|
+
scaleUpThreshold: z.number().min(0).max(1).default(0.8),
|
|
1280
|
+
scaleDownThreshold: z.number().min(0).max(1).default(0.3),
|
|
1281
|
+
}).optional(),
|
|
1282
|
+
coordination: z.object({
|
|
1283
|
+
consensusRequired: z.boolean().default(false),
|
|
1284
|
+
timeoutMs: z.number().int().positive().default(10000),
|
|
1285
|
+
retryPolicy: z.object({
|
|
1286
|
+
maxRetries: z.number().int().min(0).default(3),
|
|
1287
|
+
backoffMs: z.number().int().positive().default(500),
|
|
1288
|
+
}),
|
|
1289
|
+
}).optional(),
|
|
1290
|
+
communication: z.object({
|
|
1291
|
+
protocol: z.enum(['events', 'messages', 'shared-memory']).default('events'),
|
|
1292
|
+
batchSize: z.number().int().positive().default(10),
|
|
1293
|
+
flushIntervalMs: z.number().int().positive().default(100),
|
|
1294
|
+
}).optional(),
|
|
1295
|
+
metadata: z.record(z.unknown()).optional(),
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* Memory configuration schema
|
|
1300
|
+
*/
|
|
1301
|
+
export const MemoryConfigSchema = z.object({
|
|
1302
|
+
type: z.enum(['sqlite', 'agentdb', 'hybrid', 'redis', 'memory']).default('hybrid'),
|
|
1303
|
+
path: z.string().optional(),
|
|
1304
|
+
maxSize: z.number().int().positive().optional(),
|
|
1305
|
+
ttlMs: z.number().int().positive().optional(),
|
|
1306
|
+
sqlite: z.object({
|
|
1307
|
+
filename: z.string().optional(),
|
|
1308
|
+
inMemory: z.boolean().default(false),
|
|
1309
|
+
wal: z.boolean().default(true),
|
|
1310
|
+
}).optional(),
|
|
1311
|
+
agentdb: z.object({
|
|
1312
|
+
dimensions: z.number().int().positive().default(1536),
|
|
1313
|
+
indexType: z.enum(['hnsw', 'flat', 'ivf']).default('hnsw'),
|
|
1314
|
+
efConstruction: z.number().int().positive().default(200),
|
|
1315
|
+
m: z.number().int().positive().default(16),
|
|
1316
|
+
quantization: z.enum(['none', 'scalar', 'product']).default('none'),
|
|
1317
|
+
}).optional(),
|
|
1318
|
+
redis: z.object({
|
|
1319
|
+
host: z.string().default('localhost'),
|
|
1320
|
+
port: z.number().int().positive().default(6379),
|
|
1321
|
+
password: z.string().optional(),
|
|
1322
|
+
db: z.number().int().min(0).default(0),
|
|
1323
|
+
keyPrefix: z.string().default('claude-flow:'),
|
|
1324
|
+
}).optional(),
|
|
1325
|
+
hybrid: z.object({
|
|
1326
|
+
vectorThreshold: z.number().int().positive().default(100),
|
|
1327
|
+
}).optional(),
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* MCP server configuration schema
|
|
1332
|
+
*/
|
|
1333
|
+
export const MCPServerConfigSchema = z.object({
|
|
1334
|
+
name: z.string().min(1).default('claude-flow'),
|
|
1335
|
+
version: z.string().min(1).default('3.0.0'),
|
|
1336
|
+
transport: z.object({
|
|
1337
|
+
type: z.enum(['stdio', 'http', 'websocket']).default('stdio'),
|
|
1338
|
+
port: z.number().int().positive().optional(),
|
|
1339
|
+
host: z.string().optional(),
|
|
1340
|
+
path: z.string().optional(),
|
|
1341
|
+
}),
|
|
1342
|
+
capabilities: z.object({
|
|
1343
|
+
tools: z.boolean().default(true),
|
|
1344
|
+
resources: z.boolean().default(true),
|
|
1345
|
+
prompts: z.boolean().default(true),
|
|
1346
|
+
logging: z.boolean().default(true),
|
|
1347
|
+
experimental: z.record(z.boolean()).optional(),
|
|
1348
|
+
}).optional(),
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Orchestrator configuration schema
|
|
1353
|
+
*/
|
|
1354
|
+
export const OrchestratorConfigSchema = z.object({
|
|
1355
|
+
session: z.object({
|
|
1356
|
+
persistSessions: z.boolean().default(true),
|
|
1357
|
+
dataDir: z.string().default('./data'),
|
|
1358
|
+
sessionRetentionMs: z.number().int().positive().default(3600000),
|
|
1359
|
+
}),
|
|
1360
|
+
health: z.object({
|
|
1361
|
+
checkInterval: z.number().int().positive().default(30000),
|
|
1362
|
+
historyLimit: z.number().int().positive()
|
|
1363
|
+
|
|
1364
|
+
--- src/core/config/validator.ts ---
|
|
1365
|
+
/**
|
|
1366
|
+
* V3 Configuration Validator
|
|
1367
|
+
* Validation logic using Zod schemas
|
|
1368
|
+
*/
|
|
1369
|
+
|
|
1370
|
+
import { z, type ZodError } from 'zod';
|
|
1371
|
+
import {
|
|
1372
|
+
AgentConfigSchema,
|
|
1373
|
+
TaskConfigSchema,
|
|
1374
|
+
SwarmConfigSchema,
|
|
1375
|
+
MemoryConfigSchema,
|
|
1376
|
+
MCPServerConfigSchema,
|
|
1377
|
+
OrchestratorConfigSchema,
|
|
1378
|
+
SystemConfigSchema,
|
|
1379
|
+
type AgentConfig,
|
|
1380
|
+
type TaskConfig,
|
|
1381
|
+
type SwarmConfig,
|
|
1382
|
+
type MemoryConfig,
|
|
1383
|
+
type MCPServerConfig,
|
|
1384
|
+
type OrchestratorConfig,
|
|
1385
|
+
type SystemConfig,
|
|
1386
|
+
} from './schema.js';
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Validation result
|
|
1390
|
+
*/
|
|
1391
|
+
export interface ValidationResult<T> {
|
|
1392
|
+
success: boolean;
|
|
1393
|
+
data?: T;
|
|
1394
|
+
errors?: ValidationError[];
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Validation error
|
|
1399
|
+
*/
|
|
1400
|
+
export interface ValidationError {
|
|
1401
|
+
path: string;
|
|
1402
|
+
message: string;
|
|
1403
|
+
code: string;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Convert Zod error to validation errors
|
|
1408
|
+
*/
|
|
1409
|
+
function zodErrorToValidationErrors(error: ZodError): ValidationError[] {
|
|
1410
|
+
return error.errors.map((e) => ({
|
|
1411
|
+
path: e.path.join('.'),
|
|
1412
|
+
message: e.message,
|
|
1413
|
+
code: e.code,
|
|
1414
|
+
}));
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* Generic validation function
|
|
1419
|
+
* Uses parse + try/catch to get output types with defaults applied
|
|
1420
|
+
*/
|
|
1421
|
+
function validate<TInput, TOutput>(
|
|
1422
|
+
schema: z.ZodType<TOutput, z.ZodTypeDef, TInput>,
|
|
1423
|
+
data: unknown
|
|
1424
|
+
): ValidationResult<TOutput> {
|
|
1425
|
+
try {
|
|
1426
|
+
const parsed = schema.parse(data);
|
|
1427
|
+
return {
|
|
1428
|
+
success: true,
|
|
1429
|
+
data: parsed,
|
|
1430
|
+
};
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
if (error instanceof z.ZodError) {
|
|
1433
|
+
return {
|
|
1434
|
+
success: false,
|
|
1435
|
+
errors: zodErrorToValidationErrors(error),
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
throw error;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* Validate agent configuration
|
|
1444
|
+
*/
|
|
1445
|
+
export function validateAgentConfig(data: unknown): ValidationResult<AgentConfig> {
|
|
1446
|
+
return validate(AgentConfigSchema, data);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* Validate task configuration
|
|
1451
|
+
*/
|
|
1452
|
+
export function validateTaskConfig(data: unknown): ValidationResult<TaskConfig> {
|
|
1453
|
+
return validate(TaskConfigSchema, data);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* Validate swarm configuration
|
|
1458
|
+
*/
|
|
1459
|
+
export function validateSwarmConfig(data: unknown): ValidationResult<SwarmConfig> {
|
|
1460
|
+
return validate(SwarmConfigSchema, data);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* Validate memory configuration
|
|
1465
|
+
*/
|
|
1466
|
+
export function validateMemoryConfig(data: unknown): ValidationResult<MemoryConfig> {
|
|
1467
|
+
return validate(MemoryConfigSchema, data);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Validate MCP server configuration
|
|
1472
|
+
*/
|
|
1473
|
+
export function validateMCPServerConfig(data: unknown): ValidationResult<MCPServerConfig> {
|
|
1474
|
+
return validate(MCPServerConfigSchema, data);
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Validate orchestrator configuration
|
|
1479
|
+
*/
|
|
1480
|
+
export function validateOrchestratorConfig(data: unknown): ValidationResult<OrchestratorConfig> {
|
|
1481
|
+
return validate(OrchestratorConfigSchema, data);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* Validate full system configuration
|
|
1486
|
+
*/
|
|
1487
|
+
export function validateSystemConfig(data: unknown): ValidationResult<SystemConfig> {
|
|
1488
|
+
return validate(SystemConfigSchema, data);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* Configuration validator class
|
|
1493
|
+
*/
|
|
1494
|
+
export class ConfigValidator {
|
|
1495
|
+
/**
|
|
1496
|
+
* Validate and throw on error
|
|
1497
|
+
*/
|
|
1498
|
+
static validateOrThrow<TInput, TOutput>(
|
|
1499
|
+
schema: z.ZodType<TOutput, z.ZodTypeDef, TInput>,
|
|
1500
|
+
data: unknown,
|
|
1501
|
+
configName: string
|
|
1502
|
+
): TOutput {
|
|
1503
|
+
const result = validate(schema, data);
|
|
1504
|
+
|
|
1505
|
+
if (!result.success) {
|
|
1506
|
+
const errorMessages = result.errors
|
|
1507
|
+
?.map((e) => ` - ${e.path}: ${e.message}`)
|
|
1508
|
+
.join('\n');
|
|
1509
|
+
throw new Error(`Invalid ${configName} configuration:\n${errorMessages}`);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
return result.data!;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* Validate agent config or throw
|
|
1517
|
+
*/
|
|
1518
|
+
static validateAgentOrThrow(data: unknown): AgentConfig {
|
|
1519
|
+
return this.validateOrThrow(AgentConfigSchema, data, 'agent');
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
/**
|
|
1523
|
+
* Validate task config or throw
|
|
1524
|
+
*/
|
|
1525
|
+
static validateTaskOrThrow(data: unknown): TaskConfig {
|
|
1526
|
+
return this.validateOrThrow(TaskConfigSchema, data, 'task');
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Validate swarm config or throw
|
|
1531
|
+
*/
|
|
1532
|
+
static validateSwarmOrThrow(data: unknown): SwarmConfig {
|
|
1533
|
+
return this.validateOrThrow(SwarmConfigSchema, data, 'swarm');
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Validate memory config or throw
|
|
1538
|
+
*/
|
|
1539
|
+
static validateMemoryOrThrow(data: unknown): MemoryConfig {
|
|
1540
|
+
return this.validateOrThrow(MemoryConfigSchema, data, 'memory');
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* Validate MCP server config or throw
|
|
1545
|
+
*/
|
|
1546
|
+
static validateMCPServerOrThrow(data: unknown): MCPServerConfig {
|
|
1547
|
+
return this.validateOrThrow(MCPServerConfigSchema, data, 'MCP server');
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* Validate orchestrator config or throw
|
|
1552
|
+
*/
|
|
1553
|
+
static validateOrchestratorOrThrow(data: unknown): OrchestratorConfig {
|
|
1554
|
+
return this.validateOrThrow(OrchestratorConfigSchema, data, 'orchestrator');
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* Validate system config or throw
|
|
1559
|
+
*/
|
|
1560
|
+
static validateSystemOrThrow(data: unknown): SystemConfig {
|
|
1561
|
+
return this.validateOrThrow(SystemConfigSchema, data, 'system');
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Check if data matches schema
|
|
1566
|
+
*/
|
|
1567
|
+
static isValid<TInput, TOutput>(
|
|
1568
|
+
schema: z.ZodType<TOutput, z.ZodTypeDef, TInput>,
|
|
1569
|
+
data: unknown
|
|
1570
|
+
): boolean {
|
|
1571
|
+
return validate(schema, data).success;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
--- src/core/event-bus.ts (truncated) ---
|
|
1577
|
+
/**
|
|
1578
|
+
* V3 Event Bus
|
|
1579
|
+
* Core event pub/sub implementation
|
|
1580
|
+
*/
|
|
1581
|
+
|
|
1582
|
+
import type {
|
|
1583
|
+
IEvent,
|
|
1584
|
+
IEventBus,
|
|
1585
|
+
IEventCreate,
|
|
1586
|
+
IEventHandler,
|
|
1587
|
+
IEventSubscription,
|
|
1588
|
+
IEventFilter,
|
|
1589
|
+
} from './interfaces/event.interface.js';
|
|
1590
|
+
import { randomBytes } from 'crypto';
|
|
1591
|
+
|
|
1592
|
+
// Secure event ID generation
|
|
1593
|
+
function generateSecureEventId(): string {
|
|
1594
|
+
const timestamp = Date.now().toString(36);
|
|
1595
|
+
const random = randomBytes(12).toString('hex');
|
|
1596
|
+
return `evt_${timestamp}_${random}`;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/**
|
|
1600
|
+
* Event subscription implementation
|
|
1601
|
+
*/
|
|
1602
|
+
class EventSubscription implements IEventSubscription {
|
|
1603
|
+
private active = true;
|
|
1604
|
+
private paused = false;
|
|
1605
|
+
|
|
1606
|
+
constructor(
|
|
1607
|
+
readonly id: string,
|
|
1608
|
+
readonly filter: IEventFilter,
|
|
1609
|
+
private removeCallback: () => void,
|
|
1610
|
+
) {}
|
|
1611
|
+
|
|
1612
|
+
unsubscribe(): void {
|
|
1613
|
+
this.active = false;
|
|
1614
|
+
this.removeCallback();
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
pause(): void {
|
|
1618
|
+
this.paused = true;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
resume(): void {
|
|
1622
|
+
this.paused = false;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
isActive(): boolean {
|
|
1626
|
+
return this.active && !this.paused;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* Event bus implementation
|
|
1632
|
+
*/
|
|
1633
|
+
export class EventBus implements IEventBus {
|
|
1634
|
+
private handlers = new Map<string, Set<IEventHandler>>();
|
|
1635
|
+
private subscriptions = new Map<string, { filter: IEventFilter; handler: IEventHandler; subscription: EventSubscription }>();
|
|
1636
|
+
private subscriptionId = 0;
|
|
1637
|
+
|
|
1638
|
+
emit<T = unknown>(type: string, payload: T, options?: Partial<IEventCreate<T>>): void {
|
|
1639
|
+
const event = this.createEvent(type, payload, options);
|
|
1640
|
+
this.dispatchEvent(event);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
async emitAsync<T = unknown>(type: string, payload: T, options?: Partial<IEventCreate<T>>): Promise<void> {
|
|
1644
|
+
const event = this.createEvent(type, payload, options);
|
|
1645
|
+
await this.dispatchEventAsync(event);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
on<T = unknown>(type: string, handler: IEventHandler<T>): IEventSubscription {
|
|
1649
|
+
return this.subscribe({ types: [type] }, handler);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
subscribe<T = unknown>(filter: IEventFilter, handler: IEventHandler<T>): IEventSubscription {
|
|
1653
|
+
const id = `sub_${++this.subscriptionId}`;
|
|
1654
|
+
|
|
1655
|
+
// Register for all matching types
|
|
1656
|
+
const types = filter.types ?? ['*'];
|
|
1657
|
+
for (const type of types) {
|
|
1658
|
+
let handlers = this.handlers.get(type);
|
|
1659
|
+
if (!handlers) {
|
|
1660
|
+
handlers = new Set();
|
|
1661
|
+
this.handlers.set(type, handlers);
|
|
1662
|
+
}
|
|
1663
|
+
handlers.add(handler as IEventHandler);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
const subscription = new EventSubscription(id, filter, () => {
|
|
1667
|
+
this.removeSubscription(id);
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
this.subscriptions.set(id, { filter, handler: handler as IEventHandler, subscription });
|
|
1671
|
+
|
|
1672
|
+
return subscription;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
once<T = unknown>(type: string, handler: IEventHandler<T>): IEventSubscription {
|
|
1676
|
+
const wrappedHandler: IEventHandler<T> = async (event) => {
|
|
1677
|
+
subscription.unsubscribe();
|
|
1678
|
+
await handler(event);
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
const subscription = this.on(type, wrappedHandler);
|
|
1682
|
+
return subscription;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
off(type: string, handler: IEventHandler): void {
|
|
1686
|
+
const handlers = this.handlers.get(type);
|
|
1687
|
+
if (handlers) {
|
|
1688
|
+
handlers.delete(handler);
|
|
1689
|
+
if (handlers.size === 0) {
|
|
1690
|
+
this.handlers.delete(type);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
removeAllListeners(type?: string): void {
|
|
1696
|
+
if (type) {
|
|
1697
|
+
this.handlers.delete(type);
|
|
1698
|
+
} else {
|
|
1699
|
+
this.handlers.clear();
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
listenerCount(type: string): number {
|
|
1704
|
+
return this.handlers.get(type)?.size ?? 0;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
eventNames(): string[] {
|
|
1708
|
+
return Array.from(this.handlers.keys());
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
private createEvent<T>(type: string, payload: T, options?: Partial<IEventCreate<T>>): IEvent<T> {
|
|
1712
|
+
return {
|
|
1713
|
+
id: generateSecureEventId(),
|
|
1714
|
+
type,
|
|
1715
|
+
timestamp: new Date(),
|
|
1716
|
+
source: options?.source ?? 'event-bus',
|
|
1717
|
+
payload,
|
|
1718
|
+
priority: options?.priority,
|
|
1719
|
+
correlationId: options?.correlationId,
|
|
1720
|
+
causationId: options?.causationId,
|
|
1721
|
+
metadata: options?.metadata,
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
private dispatchEvent<T>(event: IEvent<T>): void {
|
|
1726
|
+
// Get handlers for specific type
|
|
1727
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
1728
|
+
|
|
1729
|
+
// Get wildcard handlers
|
|
1730
|
+
const wildcardHandlers = this.handlers.get('*');
|
|
1731
|
+
|
|
1732
|
+
const allHandlers = new Set<IEventHandler>();
|
|
1733
|
+
|
|
1734
|
+
if (typeHandlers) {
|
|
1735
|
+
for (const handler of typeHandlers) {
|
|
1736
|
+
allHandlers.add(handler);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
if (wildcardHandlers) {
|
|
1741
|
+
for (const handler of wildcardHandlers) {
|
|
1742
|
+
allHandlers.add(handler);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
for (const handler of allHandlers) {
|
|
1747
|
+
try {
|
|
1748
|
+
const result = handler(event);
|
|
1749
|
+
if (result instanceof Promise) {
|
|
1750
|
+
result.catch((error) => {
|
|
1751
|
+
console.error(`Error in async event handler for ${event.type}:`, error);
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
console.error(`Error in event handler for ${event.type}:`, error);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
private async dispatchEventAsync<T>(event: IEvent<T>): Promise<void> {
|
|
1761
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
1762
|
+
const wildcardHandlers = this.handlers.get('*');
|
|
1763
|
+
|
|
1764
|
+
const allHandlers = new Set<IEventHandler>();
|
|
1765
|
+
|
|
1766
|
+
if (typeHandlers) {
|
|
1767
|
+
for (const handler
|
|
1768
|
+
|
|
1769
|
+
--- src/core/index.ts ---
|
|
1770
|
+
/**
|
|
1771
|
+
* V3 Core Module - Public API
|
|
1772
|
+
* Domain-Driven Design with Clean Architecture
|
|
1773
|
+
*
|
|
1774
|
+
* This module provides the core architecture for claude-flow v3:
|
|
1775
|
+
* - Decomposed orchestrator (task, session, health, lifecycle management)
|
|
1776
|
+
* - Event-driven architecture with event bus and coordinator
|
|
1777
|
+
* - Type-safe configuration with Zod validation
|
|
1778
|
+
* - Clean interfaces following DDD principles
|
|
1779
|
+
*/
|
|
1780
|
+
|
|
1781
|
+
// Interfaces (Domain contracts)
|
|
1782
|
+
export * from './interfaces/index.js';
|
|
1783
|
+
|
|
1784
|
+
// Event system
|
|
1785
|
+
export { EventBus, createEventBus } from './event-bus.js';
|
|
1786
|
+
|
|
1787
|
+
// Orchestrator components (decomposed)
|
|
1788
|
+
export * from './orchestrator/index.js';
|
|
1789
|
+
|
|
1790
|
+
// Configuration
|
|
1791
|
+
export * from './config/index.js';
|
|
1792
|
+
|
|
1793
|
+
|
|
1794
|
+
--- src/core/interfaces/agent.interface.ts ---
|
|
1795
|
+
/**
|
|
1796
|
+
* V3 Agent Interfaces
|
|
1797
|
+
* Domain-Driven Design - Agent Lifecycle Bounded Context
|
|
1798
|
+
*/
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* Agent status in the system
|
|
1802
|
+
*/
|
|
1803
|
+
export type AgentStatus = 'spawning' | 'active' | 'idle' | 'busy' | 'error' | 'terminated';
|
|
1804
|
+
|
|
1805
|
+
/**
|
|
1806
|
+
* Agent type classification
|
|
1807
|
+
*/
|
|
1808
|
+
export type AgentType =
|
|
1809
|
+
| 'coder'
|
|
1810
|
+
| 'reviewer'
|
|
1811
|
+
| 'tester'
|
|
1812
|
+
| 'researcher'
|
|
1813
|
+
| 'planner'
|
|
1814
|
+
| 'architect'
|
|
1815
|
+
| 'coordinator'
|
|
1816
|
+
| 'security'
|
|
1817
|
+
| 'performance'
|
|
1818
|
+
| 'custom';
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Agent capability declaration
|
|
1822
|
+
*/
|
|
1823
|
+
export interface IAgentCapability {
|
|
1824
|
+
name: string;
|
|
1825
|
+
level: 'basic' | 'intermediate' | 'advanced' | 'expert';
|
|
1826
|
+
description?: string;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* Agent configuration for spawning
|
|
1831
|
+
*/
|
|
1832
|
+
export interface IAgentConfig {
|
|
1833
|
+
readonly id: string;
|
|
1834
|
+
readonly name: string;
|
|
1835
|
+
readonly type: AgentType | string;
|
|
1836
|
+
|
|
1837
|
+
capabilities: string[];
|
|
1838
|
+
maxConcurrentTasks: number;
|
|
1839
|
+
priority: number;
|
|
1840
|
+
|
|
1841
|
+
timeout?: number;
|
|
1842
|
+
retryPolicy?: {
|
|
1843
|
+
maxRetries: number;
|
|
1844
|
+
backoffMs: number;
|
|
1845
|
+
backoffMultiplier: number;
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
resources?: {
|
|
1849
|
+
maxMemoryMb?: number;
|
|
1850
|
+
maxCpuPercent?: number;
|
|
1851
|
+
};
|
|
1852
|
+
|
|
1853
|
+
metadata?: Record<string, unknown>;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
/**
|
|
1857
|
+
* Core agent entity
|
|
1858
|
+
*/
|
|
1859
|
+
export interface IAgent {
|
|
1860
|
+
readonly id: string;
|
|
1861
|
+
readonly name: string;
|
|
1862
|
+
readonly type: AgentType | string;
|
|
1863
|
+
readonly config: IAgentConfig;
|
|
1864
|
+
readonly createdAt: Date;
|
|
1865
|
+
|
|
1866
|
+
status: AgentStatus;
|
|
1867
|
+
currentTaskCount: number;
|
|
1868
|
+
lastActivity: Date;
|
|
1869
|
+
|
|
1870
|
+
sessionId?: string;
|
|
1871
|
+
terminalId?: string;
|
|
1872
|
+
memoryBankId?: string;
|
|
1873
|
+
|
|
1874
|
+
metrics?: {
|
|
1875
|
+
tasksCompleted: number;
|
|
1876
|
+
tasksFailed: number;
|
|
1877
|
+
avgTaskDuration: number;
|
|
1878
|
+
errorCount: number;
|
|
1879
|
+
uptime: number;
|
|
1880
|
+
};
|
|
1881
|
+
|
|
1882
|
+
health?: {
|
|
1883
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
1884
|
+
lastCheck: Date;
|
|
1885
|
+
issues?: string[];
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Agent session for tracking active work
|
|
1891
|
+
*/
|
|
1892
|
+
export interface IAgentSession {
|
|
1893
|
+
readonly id: string;
|
|
1894
|
+
readonly agentId: string;
|
|
1895
|
+
readonly startTime: Date;
|
|
1896
|
+
|
|
1897
|
+
status: 'active' | 'idle' | 'terminated';
|
|
1898
|
+
terminalId: string;
|
|
1899
|
+
memoryBankId: string;
|
|
1900
|
+
|
|
1901
|
+
lastActivity: Date;
|
|
1902
|
+
endTime?: Date;
|
|
1903
|
+
|
|
1904
|
+
metadata?: Record<string, unknown>;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
/**
|
|
1908
|
+
* Agent pool for managing multiple agents
|
|
1909
|
+
*/
|
|
1910
|
+
export interface IAgentPool {
|
|
1911
|
+
/**
|
|
1912
|
+
* Add an agent to the pool
|
|
1913
|
+
*/
|
|
1914
|
+
add(agent: IAgent): void;
|
|
1915
|
+
|
|
1916
|
+
/**
|
|
1917
|
+
* Remove an agent from the pool
|
|
1918
|
+
*/
|
|
1919
|
+
remove(agentId: string): boolean;
|
|
1920
|
+
|
|
1921
|
+
/**
|
|
1922
|
+
* Get an agent by ID
|
|
1923
|
+
*/
|
|
1924
|
+
get(agentId: string): IAgent | undefined;
|
|
1925
|
+
|
|
1926
|
+
/**
|
|
1927
|
+
* Get all agents in the pool
|
|
1928
|
+
*/
|
|
1929
|
+
getAll(): IAgent[];
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* Get agents by status
|
|
1933
|
+
*/
|
|
1934
|
+
getByStatus(status: AgentStatus): IAgent[];
|
|
1935
|
+
|
|
1936
|
+
/**
|
|
1937
|
+
* Get agents by type
|
|
1938
|
+
*/
|
|
1939
|
+
getByType(type: AgentType | string): IAgent[];
|
|
1940
|
+
|
|
1941
|
+
/**
|
|
1942
|
+
* Get available agents (can accept more tasks)
|
|
1943
|
+
*/
|
|
1944
|
+
getAvailable(): IAgent[];
|
|
1945
|
+
|
|
1946
|
+
/**
|
|
1947
|
+
* Get pool size
|
|
1948
|
+
*/
|
|
1949
|
+
size(): number;
|
|
1950
|
+
|
|
1951
|
+
/**
|
|
1952
|
+
* Check if pool has capacity
|
|
1953
|
+
*/
|
|
1954
|
+
hasCapacity(maxSize: number): boolean;
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* Clear all agents
|
|
1958
|
+
*/
|
|
1959
|
+
clear(): void;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* Agent lifecycle manager interface
|
|
1964
|
+
*/
|
|
1965
|
+
export interface IAgentLifecycleManager {
|
|
1966
|
+
/**
|
|
1967
|
+
* Spawn a new agent
|
|
1968
|
+
*/
|
|
1969
|
+
spawn(config: IAgentConfig): Promise<IAgent>;
|
|
1970
|
+
|
|
1971
|
+
/**
|
|
1972
|
+
* Spawn multiple agents in parallel
|
|
1973
|
+
*/
|
|
1974
|
+
spawnBatch(configs: IAgentConfig[]): Promise<Map<string, IAgent>>;
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* Terminate an agent
|
|
1978
|
+
*/
|
|
1979
|
+
terminate(agentId: string, reason?: string): Promise<void>;
|
|
1980
|
+
|
|
1981
|
+
/**
|
|
1982
|
+
* Terminate all agents
|
|
1983
|
+
*/
|
|
1984
|
+
terminateAll(reason?: string): Promise<void>;
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* Restart an agent
|
|
1988
|
+
*/
|
|
1989
|
+
restart(agentId: string): Promise<IAgent>;
|
|
1990
|
+
|
|
1991
|
+
/**
|
|
1992
|
+
* Update agent configuration
|
|
1993
|
+
*/
|
|
1994
|
+
updateConfig(agentId: string, config: Partial<IAgentConfig>): Promise<void>;
|
|
1995
|
+
|
|
1996
|
+
/**
|
|
1997
|
+
* Get agent by ID
|
|
1998
|
+
*/
|
|
1999
|
+
getAgent(agentId: string): IAgent | undefined;
|
|
2000
|
+
|
|
2001
|
+
/**
|
|
2002
|
+
* Get all agents
|
|
2003
|
+
*/
|
|
2004
|
+
getAllAgents(): IAgent[];
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* Get active agents count
|
|
2008
|
+
*/
|
|
2009
|
+
getActiveCount(): number;
|
|
2010
|
+
|
|
2011
|
+
/**
|
|
2012
|
+
* Check agent health
|
|
2013
|
+
*/
|
|
2014
|
+
checkHealth(agentId: string): Promise<IAgent['health']>;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
/**
|
|
2018
|
+
* Agent registry for type definitions
|
|
2019
|
+
*/
|
|
2020
|
+
export interface IAgentRegistry {
|
|
2021
|
+
/**
|
|
2022
|
+
* Register an agent type with default config
|
|
2023
|
+
*/
|
|
2024
|
+
register(type: string, defaultConfig: Partial<IAgentConfig>): void;
|
|
2025
|
+
|
|
2026
|
+
/**
|
|
2027
|
+
* Unregister an agent type
|
|
2028
|
+
*/
|
|
2029
|
+
unregister(type: string): boolean;
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* Get default config for a type
|
|
2033
|
+
*/
|
|
2034
|
+
getDefaultConfig(type: string): Partial<IAgentConfig> | undefined;
|
|
2035
|
+
|
|
2036
|
+
/**
|
|
2037
|
+
* Get all registered types
|
|
2038
|
+
*/
|
|
2039
|
+
getRegisteredTypes(): string[];
|
|
2040
|
+
|
|
2041
|
+
/**
|
|
2042
|
+
* Check if a type is registered
|
|
2043
|
+
*/
|
|
2044
|
+
isRegistered(type: string): boolean;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
--- src/core/interfaces/coordinator.interface.ts (truncated) ---
|
|
2049
|
+
/**
|
|
2050
|
+
* V3 Coordinator Interfaces
|
|
2051
|
+
* Domain-Driven Design - Coordination Bounded Context
|
|
2052
|
+
* Aligned with ADR-003 (Single Coordination Engine)
|
|
2053
|
+
*/
|
|
2054
|
+
|
|
2055
|
+
import type { ITask, ITaskResult } from './task.interface.js';
|
|
2056
|
+
import type { IAgent, IAgentConfig } from './agent.interface.js';
|
|
2057
|
+
|
|
2058
|
+
/**
|
|
2059
|
+
* Swarm topology types
|
|
2060
|
+
*/
|
|
2061
|
+
export type SwarmTopology =
|
|
2062
|
+
| 'hierarchical'
|
|
2063
|
+
| 'mesh'
|
|
2064
|
+
| 'ring'
|
|
2065
|
+
| 'star'
|
|
2066
|
+
| 'adaptive'
|
|
2067
|
+
| 'hierarchical-mesh';
|
|
2068
|
+
|
|
2069
|
+
/**
|
|
2070
|
+
* Coordination status
|
|
2071
|
+
*/
|
|
2072
|
+
export type CoordinationStatus =
|
|
2073
|
+
| 'initializing'
|
|
2074
|
+
| 'ready'
|
|
2075
|
+
| 'coordinating'
|
|
2076
|
+
| 'degraded'
|
|
2077
|
+
| 'error'
|
|
2078
|
+
| 'shutdown';
|
|
2079
|
+
|
|
2080
|
+
/**
|
|
2081
|
+
* Swarm configuration
|
|
2082
|
+
*/
|
|
2083
|
+
export interface ISwarmConfig {
|
|
2084
|
+
topology: SwarmTopology;
|
|
2085
|
+
maxAgents: number;
|
|
2086
|
+
|
|
2087
|
+
autoScale?: {
|
|
2088
|
+
enabled: boolean;
|
|
2089
|
+
minAgents: number;
|
|
2090
|
+
maxAgents: number;
|
|
2091
|
+
scaleUpThreshold: number;
|
|
2092
|
+
scaleDownThreshold: number;
|
|
2093
|
+
};
|
|
2094
|
+
|
|
2095
|
+
coordination?: {
|
|
2096
|
+
consensusRequired: boolean;
|
|
2097
|
+
timeoutMs: number;
|
|
2098
|
+
retryPolicy: {
|
|
2099
|
+
maxRetries: number;
|
|
2100
|
+
backoffMs: number;
|
|
2101
|
+
};
|
|
2102
|
+
};
|
|
2103
|
+
|
|
2104
|
+
communication?: {
|
|
2105
|
+
protocol: 'events' | 'messages' | 'shared-memory';
|
|
2106
|
+
batchSize: number;
|
|
2107
|
+
flushIntervalMs: number;
|
|
2108
|
+
};
|
|
2109
|
+
|
|
2110
|
+
metadata?: Record<string, unknown>;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
/**
|
|
2114
|
+
* Swarm state
|
|
2115
|
+
*/
|
|
2116
|
+
export interface ISwarmState {
|
|
2117
|
+
readonly id: string;
|
|
2118
|
+
readonly topology: SwarmTopology;
|
|
2119
|
+
readonly createdAt: Date;
|
|
2120
|
+
|
|
2121
|
+
status: CoordinationStatus;
|
|
2122
|
+
agentCount: number;
|
|
2123
|
+
taskCount: number;
|
|
2124
|
+
|
|
2125
|
+
metrics?: {
|
|
2126
|
+
throughput: number;
|
|
2127
|
+
latencyMs: number;
|
|
2128
|
+
successRate: number;
|
|
2129
|
+
resourceUtilization: number;
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* Coordinator interface - unified coordination engine
|
|
2135
|
+
*/
|
|
2136
|
+
export interface ICoordinator {
|
|
2137
|
+
/**
|
|
2138
|
+
* Initialize the coordinator
|
|
2139
|
+
*/
|
|
2140
|
+
initialize(): Promise<void>;
|
|
2141
|
+
|
|
2142
|
+
/**
|
|
2143
|
+
* Shutdown the coordinator
|
|
2144
|
+
*/
|
|
2145
|
+
shutdown(): Promise<void>;
|
|
2146
|
+
|
|
2147
|
+
/**
|
|
2148
|
+
* Initialize a swarm with configuration
|
|
2149
|
+
*/
|
|
2150
|
+
initializeSwarm(config: ISwarmConfig): Promise<ISwarmState>;
|
|
2151
|
+
|
|
2152
|
+
/**
|
|
2153
|
+
* Get swarm state
|
|
2154
|
+
*/
|
|
2155
|
+
getSwarmState(): ISwarmState | undefined;
|
|
2156
|
+
|
|
2157
|
+
/**
|
|
2158
|
+
* Assign a task to an agent
|
|
2159
|
+
*/
|
|
2160
|
+
assignTask(task: ITask, agentId: string): Promise<void>;
|
|
2161
|
+
|
|
2162
|
+
/**
|
|
2163
|
+
* Get tasks assigned to an agent
|
|
2164
|
+
*/
|
|
2165
|
+
getAgentTasks(agentId: string): Promise<ITask[]>;
|
|
2166
|
+
|
|
2167
|
+
/**
|
|
2168
|
+
* Get task count for an agent
|
|
2169
|
+
*/
|
|
2170
|
+
getAgentTaskCount(agentId: string): Promise<number>;
|
|
2171
|
+
|
|
2172
|
+
/**
|
|
2173
|
+
* Cancel a task
|
|
2174
|
+
*/
|
|
2175
|
+
cancelTask(taskId: string): Promise<void>;
|
|
2176
|
+
|
|
2177
|
+
/**
|
|
2178
|
+
* Report task completion
|
|
2179
|
+
*/
|
|
2180
|
+
reportTaskComplete(taskId: string, result: ITaskResult): Promise<void>;
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* Get coordination health status
|
|
2184
|
+
*/
|
|
2185
|
+
getHealthStatus(): Promise<{ healthy: boolean; error?: string; metrics?: Record<string, number> }>;
|
|
2186
|
+
|
|
2187
|
+
/**
|
|
2188
|
+
* Perform maintenance tasks
|
|
2189
|
+
*/
|
|
2190
|
+
performMaintenance(): Promise<void>;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
/**
|
|
2194
|
+
* Coordination manager interface - higher-level orchestration
|
|
2195
|
+
*/
|
|
2196
|
+
export interface ICoordinationManager extends ICoordinator {
|
|
2197
|
+
/**
|
|
2198
|
+
* Register an agent with the coordinator
|
|
2199
|
+
*/
|
|
2200
|
+
registerAgent(agent: IAgent): Promise<void>;
|
|
2201
|
+
|
|
2202
|
+
/**
|
|
2203
|
+
* Unregister an agent
|
|
2204
|
+
*/
|
|
2205
|
+
unregisterAgent(agentId: string): Promise<void>;
|
|
2206
|
+
|
|
2207
|
+
/**
|
|
2208
|
+
* Get all registered agents
|
|
2209
|
+
*/
|
|
2210
|
+
getRegisteredAgents(): IAgent[];
|
|
2211
|
+
|
|
2212
|
+
/**
|
|
2213
|
+
* Request agent consensus on a decision
|
|
2214
|
+
*/
|
|
2215
|
+
requestConsensus(topic: string, options: unknown[], timeout?: number): Promise<unknown>;
|
|
2216
|
+
|
|
2217
|
+
/**
|
|
2218
|
+
* Broadcast message to all agents
|
|
2219
|
+
*/
|
|
2220
|
+
broadcast(message: unknown): Promise<void>;
|
|
2221
|
+
|
|
2222
|
+
/**
|
|
2223
|
+
* Send message to specific agent
|
|
2224
|
+
*/
|
|
2225
|
+
sendToAgent(agentId: string, message: unknown): Promise<void>;
|
|
2226
|
+
|
|
2227
|
+
/**
|
|
2228
|
+
* Acquire a distributed lock
|
|
2229
|
+
*/
|
|
2230
|
+
acquireLock(resourceId: string, agentId: string, timeout?: number): Promise<boolean>;
|
|
2231
|
+
|
|
2232
|
+
/**
|
|
2233
|
+
* Release a distributed lock
|
|
2234
|
+
*/
|
|
2235
|
+
releaseLock(resourceId: string, agentId: string): Promise<void>;
|
|
2236
|
+
|
|
2237
|
+
/**
|
|
2238
|
+
* Check for deadlocks
|
|
2239
|
+
*/
|
|
2240
|
+
detectDeadlocks(): Promise<{ detected: boolean; agents?: string[]; resources?: string[] }>;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
/**
|
|
2244
|
+
* Health status for components
|
|
2245
|
+
*/
|
|
2246
|
+
export interface IHealthStatus {
|
|
2247
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
2248
|
+
components: Record<string, IComponentHealth>;
|
|
2249
|
+
timestamp: Date;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/**
|
|
2253
|
+
* Component health details
|
|
2254
|
+
*/
|
|
2255
|
+
export interface IComponentHealth {
|
|
2256
|
+
name: string;
|
|
2257
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
2258
|
+
lastCheck: Date;
|
|
2259
|
+
error?: string;
|
|
2260
|
+
metrics?: Record<string, number>;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
/**
|
|
2264
|
+
* Health monitor interface
|
|
2265
|
+
*/
|
|
2266
|
+
export interface IHealthMonitor {
|
|
2267
|
+
/**
|
|
2268
|
+
* Start health monitoring
|
|
2269
|
+
*/
|
|
2270
|
+
start(): void;
|
|
2271
|
+
|
|
2272
|
+
/**
|
|
2273
|
+
* Stop health monitoring
|
|
2274
|
+
*/
|
|
2275
|
+
stop(): void;
|
|
2276
|
+
|
|
2277
|
+
/**
|
|
2278
|
+
* Get current health status
|
|
2279
|
+
*/
|
|
2280
|
+
getStatus(): Promise<IHealthStatus>;
|
|
2281
|
+
|
|
2282
|
+
/**
|
|
2283
|
+
* Register a health check
|
|
2284
|
+
*/
|
|
2285
|
+
registerCheck(
|
|
2286
|
+
name: string,
|
|
2287
|
+
check: () => Promise<{ healthy: boolean; error?: string; metrics?: Record<string, number> }>
|
|
2288
|
+
): void;
|
|
2289
|
+
|
|
2290
|
+
/**
|
|
2291
|
+
* Unregister a health check
|
|
2292
|
+
*/
|
|
2293
|
+
unregisterCheck(name: string): void;
|
|
2294
|
+
|
|
2295
|
+
/**
|
|
2296
|
+
* Get health history
|
|
2297
|
+
*/
|
|
2298
|
+
getHistory(limit?: number): IHealthStatus[];
|
|
2299
|
+
|
|
2300
|
+
/**
|
|
2301
|
+
* Subscribe to health changes
|
|
2302
|
+
*/
|
|
2303
|
+
onHealthChange(callback: (status: IHealthStatus) => void): () => void;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
/**
|
|
2307
|
+
* Metrics co
|
|
2308
|
+
|
|
2309
|
+
--- src/core/interfaces/event.interface.ts (truncated) ---
|
|
2310
|
+
/**
|
|
2311
|
+
* V3 Event Interfaces
|
|
2312
|
+
* Domain-Driven Design - Event Sourcing Pattern (ADR-007)
|
|
2313
|
+
*/
|
|
2314
|
+
|
|
2315
|
+
/**
|
|
2316
|
+
* Event priority levels
|
|
2317
|
+
*/
|
|
2318
|
+
export type EventPriority = 'critical' | 'high' | 'normal' | 'low';
|
|
2319
|
+
|
|
2320
|
+
/**
|
|
2321
|
+
* Core event structure
|
|
2322
|
+
*/
|
|
2323
|
+
export interface IEvent<T = unknown> {
|
|
2324
|
+
readonly id: string;
|
|
2325
|
+
readonly type: string;
|
|
2326
|
+
readonly timestamp: Date;
|
|
2327
|
+
readonly source: string;
|
|
2328
|
+
|
|
2329
|
+
payload: T;
|
|
2330
|
+
priority?: EventPriority;
|
|
2331
|
+
correlationId?: string;
|
|
2332
|
+
causationId?: string;
|
|
2333
|
+
|
|
2334
|
+
metadata?: {
|
|
2335
|
+
version?: number;
|
|
2336
|
+
userId?: string;
|
|
2337
|
+
sessionId?: string;
|
|
2338
|
+
[key: string]: unknown;
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/**
|
|
2343
|
+
* Event creation parameters
|
|
2344
|
+
*/
|
|
2345
|
+
export interface IEventCreate<T = unknown> {
|
|
2346
|
+
type: string;
|
|
2347
|
+
payload: T;
|
|
2348
|
+
source?: string;
|
|
2349
|
+
priority?: EventPriority;
|
|
2350
|
+
correlationId?: string;
|
|
2351
|
+
causationId?: string;
|
|
2352
|
+
metadata?: IEvent['metadata'];
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
/**
|
|
2356
|
+
* Event handler function type
|
|
2357
|
+
*/
|
|
2358
|
+
export type IEventHandler<T = unknown> = (event: IEvent<T>) => void | Promise<void>;
|
|
2359
|
+
|
|
2360
|
+
/**
|
|
2361
|
+
* Event filter for subscriptions
|
|
2362
|
+
*/
|
|
2363
|
+
export interface IEventFilter {
|
|
2364
|
+
types?: string[];
|
|
2365
|
+
sources?: string[];
|
|
2366
|
+
priority?: EventPriority[];
|
|
2367
|
+
correlationId?: string;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
/**
|
|
2371
|
+
* Event subscription handle
|
|
2372
|
+
*/
|
|
2373
|
+
export interface IEventSubscription {
|
|
2374
|
+
readonly id: string;
|
|
2375
|
+
readonly filter: IEventFilter;
|
|
2376
|
+
|
|
2377
|
+
/**
|
|
2378
|
+
* Unsubscribe from events
|
|
2379
|
+
*/
|
|
2380
|
+
unsubscribe(): void;
|
|
2381
|
+
|
|
2382
|
+
/**
|
|
2383
|
+
* Pause subscription
|
|
2384
|
+
*/
|
|
2385
|
+
pause(): void;
|
|
2386
|
+
|
|
2387
|
+
/**
|
|
2388
|
+
* Resume subscription
|
|
2389
|
+
*/
|
|
2390
|
+
resume(): void;
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* Check if subscription is active
|
|
2394
|
+
*/
|
|
2395
|
+
isActive(): boolean;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
/**
|
|
2399
|
+
* Event bus interface for pub/sub communication
|
|
2400
|
+
*/
|
|
2401
|
+
export interface IEventBus {
|
|
2402
|
+
/**
|
|
2403
|
+
* Emit an event to all subscribers
|
|
2404
|
+
*/
|
|
2405
|
+
emit<T = unknown>(type: string, payload: T, options?: Partial<IEventCreate<T>>): void;
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* Emit an event and wait for all handlers
|
|
2409
|
+
*/
|
|
2410
|
+
emitAsync<T = unknown>(type: string, payload: T, options?: Partial<IEventCreate<T>>): Promise<void>;
|
|
2411
|
+
|
|
2412
|
+
/**
|
|
2413
|
+
* Subscribe to events matching a type pattern
|
|
2414
|
+
*/
|
|
2415
|
+
on<T = unknown>(type: string, handler: IEventHandler<T>): IEventSubscription;
|
|
2416
|
+
|
|
2417
|
+
/**
|
|
2418
|
+
* Subscribe to events with filter
|
|
2419
|
+
*/
|
|
2420
|
+
subscribe<T = unknown>(filter: IEventFilter, handler: IEventHandler<T>): IEventSubscription;
|
|
2421
|
+
|
|
2422
|
+
/**
|
|
2423
|
+
* Subscribe to a single event occurrence
|
|
2424
|
+
*/
|
|
2425
|
+
once<T = unknown>(type: string, handler: IEventHandler<T>): IEventSubscription;
|
|
2426
|
+
|
|
2427
|
+
/**
|
|
2428
|
+
* Remove a specific handler
|
|
2429
|
+
*/
|
|
2430
|
+
off(type: string, handler: IEventHandler): void;
|
|
2431
|
+
|
|
2432
|
+
/**
|
|
2433
|
+
* Remove all handlers for a type
|
|
2434
|
+
*/
|
|
2435
|
+
removeAllListeners(type?: string): void;
|
|
2436
|
+
|
|
2437
|
+
/**
|
|
2438
|
+
* Get count of listeners for a type
|
|
2439
|
+
*/
|
|
2440
|
+
listenerCount(type: string): number;
|
|
2441
|
+
|
|
2442
|
+
/**
|
|
2443
|
+
* Get all event types with active listeners
|
|
2444
|
+
*/
|
|
2445
|
+
eventNames(): string[];
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
/**
|
|
2449
|
+
* System event types enumeration
|
|
2450
|
+
*/
|
|
2451
|
+
export const SystemEventTypes = {
|
|
2452
|
+
// System lifecycle
|
|
2453
|
+
SYSTEM_READY: 'system:ready',
|
|
2454
|
+
SYSTEM_SHUTDOWN: 'system:shutdown',
|
|
2455
|
+
SYSTEM_ERROR: 'system:error',
|
|
2456
|
+
SYSTEM_HEALTHCHECK: 'system:healthcheck',
|
|
2457
|
+
|
|
2458
|
+
// Agent lifecycle
|
|
2459
|
+
AGENT_SPAWNED: 'agent:spawned',
|
|
2460
|
+
AGENT_TERMINATED: 'agent:terminated',
|
|
2461
|
+
AGENT_ERROR: 'agent:error',
|
|
2462
|
+
AGENT_IDLE: 'agent:idle',
|
|
2463
|
+
AGENT_BUSY: 'agent:busy',
|
|
2464
|
+
AGENT_HEALTH_CHANGED: 'agent:health:changed',
|
|
2465
|
+
|
|
2466
|
+
// Task lifecycle
|
|
2467
|
+
TASK_CREATED: 'task:created',
|
|
2468
|
+
TASK_ASSIGNED: 'task:assigned',
|
|
2469
|
+
TASK_STARTED: 'task:started',
|
|
2470
|
+
TASK_COMPLETED: 'task:completed',
|
|
2471
|
+
TASK_FAILED: 'task:failed',
|
|
2472
|
+
TASK_CANCELLED: 'task:cancelled',
|
|
2473
|
+
TASK_TIMEOUT: 'task:timeout',
|
|
2474
|
+
TASK_RETRY: 'task:retry',
|
|
2475
|
+
|
|
2476
|
+
// Session lifecycle
|
|
2477
|
+
SESSION_CREATED: 'session:created',
|
|
2478
|
+
SESSION_RESTORED: 'session:restored',
|
|
2479
|
+
SESSION_TERMINATED: 'session:terminated',
|
|
2480
|
+
SESSION_PERSISTED: 'session:persisted',
|
|
2481
|
+
|
|
2482
|
+
// Memory events
|
|
2483
|
+
MEMORY_STORED: 'memory:stored',
|
|
2484
|
+
MEMORY_RETRIEVED: 'memory:retrieved',
|
|
2485
|
+
MEMORY_CLEARED: 'memory:cleared',
|
|
2486
|
+
|
|
2487
|
+
// Coordination events
|
|
2488
|
+
COORDINATION_STARTED: 'coordination:started',
|
|
2489
|
+
COORDINATION_COMPLETED: 'coordination:completed',
|
|
2490
|
+
DEADLOCK_DETECTED: 'coordination:deadlock',
|
|
2491
|
+
|
|
2492
|
+
// Metrics events
|
|
2493
|
+
METRICS_COLLECTED: 'metrics:collected',
|
|
2494
|
+
} as const;
|
|
2495
|
+
|
|
2496
|
+
export type SystemEventType = typeof SystemEventTypes[keyof typeof SystemEventTypes];
|
|
2497
|
+
|
|
2498
|
+
/**
|
|
2499
|
+
* Event store interface for event sourcing
|
|
2500
|
+
*/
|
|
2501
|
+
export interface IEventStore {
|
|
2502
|
+
/**
|
|
2503
|
+
* Append an event to the store
|
|
2504
|
+
*/
|
|
2505
|
+
append(event: IEvent): Promise<void>;
|
|
2506
|
+
|
|
2507
|
+
/**
|
|
2508
|
+
* Get events by aggregate ID
|
|
2509
|
+
*/
|
|
2510
|
+
getByAggregateId(aggregateId: string, fromVersion?: number): Promise<IEvent[]>;
|
|
2511
|
+
|
|
2512
|
+
/**
|
|
2513
|
+
* Get events by type
|
|
2514
|
+
*/
|
|
2515
|
+
getByType(type: string, options?: { limit?: number; offset?: number }): Promise<IEvent[]>;
|
|
2516
|
+
|
|
2517
|
+
/**
|
|
2518
|
+
* Get events in time range
|
|
2519
|
+
*/
|
|
2520
|
+
getByTimeRange(start: Date, end: Date): Promise<IEvent[]>;
|
|
2521
|
+
|
|
2522
|
+
/**
|
|
2523
|
+
* Get events by correlation ID
|
|
2524
|
+
*/
|
|
2525
|
+
getByCorrelationId(correlationId: string): Promise<IEvent[]>;
|
|
2526
|
+
|
|
2527
|
+
/**
|
|
2528
|
+
* Get all events (paginated)
|
|
2529
|
+
*/
|
|
2530
|
+
getAll(options?: { limit?: number; offset?: number }): Promise<IEvent[]>;
|
|
2531
|
+
|
|
2532
|
+
/**
|
|
2533
|
+
* Get event count
|
|
2534
|
+
*/
|
|
2535
|
+
count(filter?: IEventFilter): Promise<number>;
|
|
2536
|
+
|
|
2537
|
+
/**
|
|
2538
|
+
* Clear old events
|
|
2539
|
+
*/
|
|
2540
|
+
prune(olderThan: Date): P
|
|
2541
|
+
|
|
2542
|
+
--- src/core/interfaces/index.ts ---
|
|
2543
|
+
/**
|
|
2544
|
+
* V3 Core Interfaces - Public API
|
|
2545
|
+
* Domain-Driven Design with Clean Architecture
|
|
2546
|
+
*/
|
|
2547
|
+
|
|
2548
|
+
// Task interfaces
|
|
2549
|
+
export * from './task.interface.js';
|
|
2550
|
+
|
|
2551
|
+
// Agent interfaces
|
|
2552
|
+
export * from './agent.interface.js';
|
|
2553
|
+
|
|
2554
|
+
// Event interfaces
|
|
2555
|
+
export * from './event.interface.js';
|
|
2556
|
+
|
|
2557
|
+
// Memory interfaces
|
|
2558
|
+
export * from './memory.interface.js';
|
|
2559
|
+
|
|
2560
|
+
// Coordinator interfaces
|
|
2561
|
+
export * from './coordinator.interface.js';
|
|
2562
|
+
|
|
2563
|
+
|
|
2564
|
+
--- src/core/interfaces/memory.interface.ts (truncated) ---
|
|
2565
|
+
/**
|
|
2566
|
+
* V3 Memory Interfaces
|
|
2567
|
+
* Domain-Driven Design - Memory Management Bounded Context
|
|
2568
|
+
* Aligned with ADR-006 (Unified Memory Service) and ADR-009 (Hybrid Memory Backend)
|
|
2569
|
+
*/
|
|
2570
|
+
|
|
2571
|
+
/**
|
|
2572
|
+
* Memory entry types
|
|
2573
|
+
*/
|
|
2574
|
+
export type MemoryType = 'session' | 'persistent' | 'vector' | 'cache' | 'pattern';
|
|
2575
|
+
|
|
2576
|
+
/**
|
|
2577
|
+
* Memory entry structure
|
|
2578
|
+
*/
|
|
2579
|
+
export interface IMemoryEntry {
|
|
2580
|
+
readonly id: string;
|
|
2581
|
+
readonly key: string;
|
|
2582
|
+
readonly type: MemoryType;
|
|
2583
|
+
readonly createdAt: Date;
|
|
2584
|
+
|
|
2585
|
+
value: unknown;
|
|
2586
|
+
updatedAt: Date;
|
|
2587
|
+
expiresAt?: Date;
|
|
2588
|
+
|
|
2589
|
+
metadata?: {
|
|
2590
|
+
source?: string;
|
|
2591
|
+
agentId?: string;
|
|
2592
|
+
sessionId?: string;
|
|
2593
|
+
version?: number;
|
|
2594
|
+
tags?: string[];
|
|
2595
|
+
embedding?: number[];
|
|
2596
|
+
[key: string]: unknown;
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
/**
|
|
2601
|
+
* Memory entry creation parameters
|
|
2602
|
+
*/
|
|
2603
|
+
export interface IMemoryEntryCreate {
|
|
2604
|
+
key: string;
|
|
2605
|
+
value: unknown;
|
|
2606
|
+
type?: MemoryType;
|
|
2607
|
+
expiresAt?: Date;
|
|
2608
|
+
ttlMs?: number;
|
|
2609
|
+
metadata?: IMemoryEntry['metadata'];
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
/**
|
|
2613
|
+
* Vector search parameters
|
|
2614
|
+
*/
|
|
2615
|
+
export interface IVectorSearchParams {
|
|
2616
|
+
embedding: number[];
|
|
2617
|
+
k?: number;
|
|
2618
|
+
threshold?: number;
|
|
2619
|
+
filter?: {
|
|
2620
|
+
type?: MemoryType;
|
|
2621
|
+
tags?: string[];
|
|
2622
|
+
agentId?: string;
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
/**
|
|
2627
|
+
* Vector search result
|
|
2628
|
+
*/
|
|
2629
|
+
export interface IVectorSearchResult {
|
|
2630
|
+
entry: IMemoryEntry;
|
|
2631
|
+
score: number;
|
|
2632
|
+
distance: number;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
/**
|
|
2636
|
+
* Memory backend interface for storage operations
|
|
2637
|
+
*/
|
|
2638
|
+
export interface IMemoryBackend {
|
|
2639
|
+
/**
|
|
2640
|
+
* Initialize the backend
|
|
2641
|
+
*/
|
|
2642
|
+
initialize(): Promise<void>;
|
|
2643
|
+
|
|
2644
|
+
/**
|
|
2645
|
+
* Shutdown the backend
|
|
2646
|
+
*/
|
|
2647
|
+
shutdown(): Promise<void>;
|
|
2648
|
+
|
|
2649
|
+
/**
|
|
2650
|
+
* Store a memory entry
|
|
2651
|
+
*/
|
|
2652
|
+
store(entry: IMemoryEntryCreate): Promise<IMemoryEntry>;
|
|
2653
|
+
|
|
2654
|
+
/**
|
|
2655
|
+
* Retrieve a memory entry by key
|
|
2656
|
+
*/
|
|
2657
|
+
retrieve(key: string): Promise<IMemoryEntry | undefined>;
|
|
2658
|
+
|
|
2659
|
+
/**
|
|
2660
|
+
* Retrieve by ID
|
|
2661
|
+
*/
|
|
2662
|
+
retrieveById(id: string): Promise<IMemoryEntry | undefined>;
|
|
2663
|
+
|
|
2664
|
+
/**
|
|
2665
|
+
* Update a memory entry
|
|
2666
|
+
*/
|
|
2667
|
+
update(key: string, value: unknown, metadata?: Partial<IMemoryEntry['metadata']>): Promise<IMemoryEntry | undefined>;
|
|
2668
|
+
|
|
2669
|
+
/**
|
|
2670
|
+
* Delete a memory entry
|
|
2671
|
+
*/
|
|
2672
|
+
delete(key: string): Promise<boolean>;
|
|
2673
|
+
|
|
2674
|
+
/**
|
|
2675
|
+
* Check if a key exists
|
|
2676
|
+
*/
|
|
2677
|
+
exists(key: string): Promise<boolean>;
|
|
2678
|
+
|
|
2679
|
+
/**
|
|
2680
|
+
* List all keys matching a pattern
|
|
2681
|
+
*/
|
|
2682
|
+
keys(pattern?: string): Promise<string[]>;
|
|
2683
|
+
|
|
2684
|
+
/**
|
|
2685
|
+
* Get all entries matching filter
|
|
2686
|
+
*/
|
|
2687
|
+
list(filter?: { type?: MemoryType; tags?: string[] }): Promise<IMemoryEntry[]>;
|
|
2688
|
+
|
|
2689
|
+
/**
|
|
2690
|
+
* Clear all entries
|
|
2691
|
+
*/
|
|
2692
|
+
clear(): Promise<void>;
|
|
2693
|
+
|
|
2694
|
+
/**
|
|
2695
|
+
* Get entry count
|
|
2696
|
+
*/
|
|
2697
|
+
count(): Promise<number>;
|
|
2698
|
+
|
|
2699
|
+
/**
|
|
2700
|
+
* Prune expired entries
|
|
2701
|
+
*/
|
|
2702
|
+
prune(): Promise<number>;
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* Get health status
|
|
2706
|
+
*/
|
|
2707
|
+
getHealthStatus(): Promise<{ healthy: boolean; error?: string; metrics?: Record<string, number> }>;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
/**
|
|
2711
|
+
* Vector memory backend for similarity search
|
|
2712
|
+
*/
|
|
2713
|
+
export interface IVectorMemoryBackend extends IMemoryBackend {
|
|
2714
|
+
/**
|
|
2715
|
+
* Store with embedding
|
|
2716
|
+
*/
|
|
2717
|
+
storeVector(entry: IMemoryEntryCreate & { embedding: number[] }): Promise<IMemoryEntry>;
|
|
2718
|
+
|
|
2719
|
+
/**
|
|
2720
|
+
* Search by vector similarity
|
|
2721
|
+
*/
|
|
2722
|
+
search(params: IVectorSearchParams): Promise<IVectorSearchResult[]>;
|
|
2723
|
+
|
|
2724
|
+
/**
|
|
2725
|
+
* Update embedding for an entry
|
|
2726
|
+
*/
|
|
2727
|
+
updateEmbedding(key: string, embedding: number[]): Promise<boolean>;
|
|
2728
|
+
|
|
2729
|
+
/**
|
|
2730
|
+
* Build or rebuild index
|
|
2731
|
+
*/
|
|
2732
|
+
buildIndex(): Promise<void>;
|
|
2733
|
+
|
|
2734
|
+
/**
|
|
2735
|
+
* Get index statistics
|
|
2736
|
+
*/
|
|
2737
|
+
getIndexStats(): Promise<{
|
|
2738
|
+
vectorCount: number;
|
|
2739
|
+
dimensions: number;
|
|
2740
|
+
indexType: string;
|
|
2741
|
+
memoryUsageMb: number;
|
|
2742
|
+
}>;
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
/**
|
|
2746
|
+
* Memory bank for agent-specific storage
|
|
2747
|
+
*/
|
|
2748
|
+
export interface IMemoryBank {
|
|
2749
|
+
readonly id: string;
|
|
2750
|
+
readonly agentId: string;
|
|
2751
|
+
readonly createdAt: Date;
|
|
2752
|
+
|
|
2753
|
+
/**
|
|
2754
|
+
* Store in bank
|
|
2755
|
+
*/
|
|
2756
|
+
store(key: string, value: unknown, options?: Partial<IMemoryEntryCreate>): Promise<IMemoryEntry>;
|
|
2757
|
+
|
|
2758
|
+
/**
|
|
2759
|
+
* Retrieve from bank
|
|
2760
|
+
*/
|
|
2761
|
+
retrieve(key: string): Promise<IMemoryEntry | undefined>;
|
|
2762
|
+
|
|
2763
|
+
/**
|
|
2764
|
+
* Delete from bank
|
|
2765
|
+
*/
|
|
2766
|
+
delete(key: string): Promise<boolean>;
|
|
2767
|
+
|
|
2768
|
+
/**
|
|
2769
|
+
* List all entries in bank
|
|
2770
|
+
*/
|
|
2771
|
+
list(): Promise<IMemoryEntry[]>;
|
|
2772
|
+
|
|
2773
|
+
/**
|
|
2774
|
+
* Clear all entries in bank
|
|
2775
|
+
*/
|
|
2776
|
+
clear(): Promise<void>;
|
|
2777
|
+
|
|
2778
|
+
/**
|
|
2779
|
+
* Get bank size
|
|
2780
|
+
*/
|
|
2781
|
+
size(): Promise<number>;
|
|
2782
|
+
|
|
2783
|
+
/**
|
|
2784
|
+
* Close the bank
|
|
2785
|
+
*/
|
|
2786
|
+
close(): Promise<void>;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
/**
|
|
2790
|
+
* Memory manager interface
|
|
2791
|
+
*/
|
|
2792
|
+
export interface IMemoryManager {
|
|
2793
|
+
/**
|
|
2794
|
+
* Initialize the manager
|
|
2795
|
+
*/
|
|
2796
|
+
initialize(): Promise<void>;
|
|
2797
|
+
|
|
2798
|
+
/**
|
|
2799
|
+
* Shutdown the manager
|
|
2800
|
+
*/
|
|
2801
|
+
shutdown(): Promise<void>;
|
|
2802
|
+
|
|
2803
|
+
/**
|
|
2804
|
+
* Create a memory bank for an agent
|
|
2805
|
+
*/
|
|
2806
|
+
createBank(agentId: string): Promise<string>;
|
|
2807
|
+
|
|
2808
|
+
/**
|
|
2809
|
+
* Get a memory bank
|
|
2810
|
+
*/
|
|
2811
|
+
getBank(bankId: string): IMemoryBank | undefined;
|
|
2812
|
+
|
|
2813
|
+
/**
|
|
2814
|
+
* Close a memory bank
|
|
2815
|
+
*/
|
|
2816
|
+
closeBank(bankId: string): Promise<void>;
|
|
2817
|
+
|
|
2818
|
+
/**
|
|
2819
|
+
* Store in global memory
|
|
2820
|
+
*/
|
|
2821
|
+
store(entry: IMemoryEntryCreate): Promise<IMemoryEntry>;
|
|
2822
|
+
|
|
2823
|
+
/**
|
|
2824
|
+
* Retrieve from global memory
|
|
2825
|
+
*/
|
|
2826
|
+
retrieve(key: string): Promise<IMemoryEntry | undefined>;
|
|
2827
|
+
|
|
2828
|
+
/**
|
|
2829
|
+
* Search vectors (if vector backend available)
|
|
2830
|
+
*/
|
|
2831
|
+
searchVectors?(params: IVectorSearchParams): Promise<IVectorSearchResult[]>;
|
|
2832
|
+
|
|
2833
|
+
/**
|
|
2834
|
+
|
|
2835
|
+
|
|
2836
|
+
--- src/core/interfaces/task.interface.ts ---
|
|
2837
|
+
/**
|
|
2838
|
+
* V3 Task Interfaces
|
|
2839
|
+
* Domain-Driven Design - Task Bounded Context
|
|
2840
|
+
*/
|
|
2841
|
+
|
|
2842
|
+
/**
|
|
2843
|
+
* Task priority levels
|
|
2844
|
+
*/
|
|
2845
|
+
export type TaskPriority = 'critical' | 'high' | 'medium' | 'low';
|
|
2846
|
+
|
|
2847
|
+
/**
|
|
2848
|
+
* Task status throughout its lifecycle
|
|
2849
|
+
*/
|
|
2850
|
+
export type TaskStatus =
|
|
2851
|
+
| 'pending'
|
|
2852
|
+
| 'queued'
|
|
2853
|
+
| 'assigned'
|
|
2854
|
+
| 'running'
|
|
2855
|
+
| 'completed'
|
|
2856
|
+
| 'failed'
|
|
2857
|
+
| 'cancelled'
|
|
2858
|
+
| 'timeout';
|
|
2859
|
+
|
|
2860
|
+
/**
|
|
2861
|
+
* Core task entity
|
|
2862
|
+
*/
|
|
2863
|
+
export interface ITask {
|
|
2864
|
+
readonly id: string;
|
|
2865
|
+
readonly type: string;
|
|
2866
|
+
readonly description: string;
|
|
2867
|
+
readonly priority: number;
|
|
2868
|
+
readonly createdAt: Date;
|
|
2869
|
+
|
|
2870
|
+
status: TaskStatus;
|
|
2871
|
+
assignedAgent?: string;
|
|
2872
|
+
startedAt?: Date;
|
|
2873
|
+
completedAt?: Date;
|
|
2874
|
+
timeout?: number;
|
|
2875
|
+
|
|
2876
|
+
input?: Record<string, unknown>;
|
|
2877
|
+
output?: Record<string, unknown>;
|
|
2878
|
+
error?: Error;
|
|
2879
|
+
|
|
2880
|
+
metadata?: {
|
|
2881
|
+
requiredCapabilities?: string[];
|
|
2882
|
+
retryCount?: number;
|
|
2883
|
+
maxRetries?: number;
|
|
2884
|
+
critical?: boolean;
|
|
2885
|
+
parentTaskId?: string;
|
|
2886
|
+
childTaskIds?: string[];
|
|
2887
|
+
tags?: string[];
|
|
2888
|
+
[key: string]: unknown;
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
/**
|
|
2893
|
+
* Task creation parameters
|
|
2894
|
+
*/
|
|
2895
|
+
export interface ITaskCreate {
|
|
2896
|
+
type: string;
|
|
2897
|
+
description: string;
|
|
2898
|
+
priority?: number;
|
|
2899
|
+
timeout?: number;
|
|
2900
|
+
assignedAgent?: string;
|
|
2901
|
+
input?: Record<string, unknown>;
|
|
2902
|
+
metadata?: ITask['metadata'];
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
/**
|
|
2906
|
+
* Task result after completion
|
|
2907
|
+
*/
|
|
2908
|
+
export interface ITaskResult {
|
|
2909
|
+
taskId: string;
|
|
2910
|
+
success: boolean;
|
|
2911
|
+
output?: Record<string, unknown>;
|
|
2912
|
+
error?: Error;
|
|
2913
|
+
duration: number;
|
|
2914
|
+
agentId?: string;
|
|
2915
|
+
metrics?: {
|
|
2916
|
+
tokensUsed?: number;
|
|
2917
|
+
memoryPeakMb?: number;
|
|
2918
|
+
retryCount?: number;
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
/**
|
|
2923
|
+
* Task queue interface for managing task ordering and processing
|
|
2924
|
+
*/
|
|
2925
|
+
export interface ITaskQueue {
|
|
2926
|
+
/**
|
|
2927
|
+
* Add a task to the queue
|
|
2928
|
+
*/
|
|
2929
|
+
enqueue(task: ITask): Promise<void>;
|
|
2930
|
+
|
|
2931
|
+
/**
|
|
2932
|
+
* Remove and return the highest priority task
|
|
2933
|
+
*/
|
|
2934
|
+
dequeue(): Promise<ITask | undefined>;
|
|
2935
|
+
|
|
2936
|
+
/**
|
|
2937
|
+
* Peek at the next task without removing it
|
|
2938
|
+
*/
|
|
2939
|
+
peek(): Promise<ITask | undefined>;
|
|
2940
|
+
|
|
2941
|
+
/**
|
|
2942
|
+
* Get the current queue size
|
|
2943
|
+
*/
|
|
2944
|
+
size(): number;
|
|
2945
|
+
|
|
2946
|
+
/**
|
|
2947
|
+
* Check if the queue is empty
|
|
2948
|
+
*/
|
|
2949
|
+
isEmpty(): boolean;
|
|
2950
|
+
|
|
2951
|
+
/**
|
|
2952
|
+
* Clear all tasks from the queue
|
|
2953
|
+
*/
|
|
2954
|
+
clear(): Promise<void>;
|
|
2955
|
+
|
|
2956
|
+
/**
|
|
2957
|
+
* Get all queued tasks (for inspection)
|
|
2958
|
+
*/
|
|
2959
|
+
getAll(): Promise<ITask[]>;
|
|
2960
|
+
|
|
2961
|
+
/**
|
|
2962
|
+
* Remove a specific task by ID
|
|
2963
|
+
*/
|
|
2964
|
+
remove(taskId: string): Promise<boolean>;
|
|
2965
|
+
|
|
2966
|
+
/**
|
|
2967
|
+
* Update task priority
|
|
2968
|
+
*/
|
|
2969
|
+
updatePriority(taskId: string, priority: number): Promise<boolean>;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
/**
|
|
2973
|
+
* Task manager interface for lifecycle management
|
|
2974
|
+
*/
|
|
2975
|
+
export interface ITaskManager {
|
|
2976
|
+
/**
|
|
2977
|
+
* Create a new task
|
|
2978
|
+
*/
|
|
2979
|
+
createTask(params: ITaskCreate): Promise<ITask>;
|
|
2980
|
+
|
|
2981
|
+
/**
|
|
2982
|
+
* Get a task by ID
|
|
2983
|
+
*/
|
|
2984
|
+
getTask(taskId: string): ITask | undefined;
|
|
2985
|
+
|
|
2986
|
+
/**
|
|
2987
|
+
* Get all tasks matching optional filter
|
|
2988
|
+
*/
|
|
2989
|
+
getTasks(filter?: Partial<Pick<ITask, 'status' | 'type' | 'assignedAgent'>>): ITask[];
|
|
2990
|
+
|
|
2991
|
+
/**
|
|
2992
|
+
* Assign a task to an agent
|
|
2993
|
+
*/
|
|
2994
|
+
assignTask(taskId: string, agentId: string): Promise<void>;
|
|
2995
|
+
|
|
2996
|
+
/**
|
|
2997
|
+
* Start task execution
|
|
2998
|
+
*/
|
|
2999
|
+
startTask(taskId: string): Promise<void>;
|
|
3000
|
+
|
|
3001
|
+
/**
|
|
3002
|
+
* Complete a task with result
|
|
3003
|
+
*/
|
|
3004
|
+
completeTask(taskId: string, result: ITaskResult): Promise<void>;
|
|
3005
|
+
|
|
3006
|
+
/**
|
|
3007
|
+
* Fail a task with error
|
|
3008
|
+
*/
|
|
3009
|
+
failTask(taskId: string, error: Error): Promise<void>;
|
|
3010
|
+
|
|
3011
|
+
/**
|
|
3012
|
+
* Cancel a task
|
|
3013
|
+
*/
|
|
3014
|
+
cancelTask(taskId: string, reason?: string): Promise<void>;
|
|
3015
|
+
|
|
3016
|
+
/**
|
|
3017
|
+
* Retry a failed task
|
|
3018
|
+
*/
|
|
3019
|
+
retryTask(taskId: string): Promise<void>;
|
|
3020
|
+
|
|
3021
|
+
/**
|
|
3022
|
+
* Get task metrics
|
|
3023
|
+
*/
|
|
3024
|
+
getMetrics(): TaskManagerMetrics;
|
|
3025
|
+
|
|
3026
|
+
/**
|
|
3027
|
+
* Clean up old completed/failed tasks
|
|
3028
|
+
*/
|
|
3029
|
+
cleanup(olderThan: Date): Promise<number>;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
/**
|
|
3033
|
+
* Task manager metrics
|
|
3034
|
+
*/
|
|
3035
|
+
export interface TaskManagerMetrics {
|
|
3036
|
+
totalTasks: number;
|
|
3037
|
+
pendingTasks: number;
|
|
3038
|
+
runningTasks: number;
|
|
3039
|
+
completedTasks: number;
|
|
3040
|
+
failedTasks: number;
|
|
3041
|
+
cancelledTasks: number;
|
|
3042
|
+
avgDuration: number;
|
|
3043
|
+
avgWaitTime: number;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
/**
|
|
3047
|
+
* Task assignment strategy interface
|
|
3048
|
+
*/
|
|
3049
|
+
export interface ITaskAssignmentStrategy {
|
|
3050
|
+
/**
|
|
3051
|
+
* Select the best agent for a task
|
|
3052
|
+
*/
|
|
3053
|
+
selectAgent(task: ITask, availableAgents: string[]): Promise<string | undefined>;
|
|
3054
|
+
|
|
3055
|
+
/**
|
|
3056
|
+
* Score an agent for a task (higher is better)
|
|
3057
|
+
*/
|
|
3058
|
+
scoreAgent(task: ITask, agentId: string): Promise<number>;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
|
|
3062
|
+
--- src/core/orchestrator/event-coordinator.ts ---
|
|
3063
|
+
/**
|
|
3064
|
+
* V3 Event Coordinator
|
|
3065
|
+
* Decomposed from orchestrator.ts - Event routing
|
|
3066
|
+
* ~100 lines (target achieved)
|
|
3067
|
+
*/
|
|
3068
|
+
|
|
3069
|
+
import type {
|
|
3070
|
+
IEvent,
|
|
3071
|
+
IEventBus,
|
|
3072
|
+
IEventHandler,
|
|
3073
|
+
IEventCoordinator,
|
|
3074
|
+
} from '../interfaces/event.interface.js';
|
|
3075
|
+
import { SystemEventTypes } from '../interfaces/event.interface.js';
|
|
3076
|
+
|
|
3077
|
+
/**
|
|
3078
|
+
* Event coordinator implementation
|
|
3079
|
+
*/
|
|
3080
|
+
export class EventCoordinator implements IEventCoordinator {
|
|
3081
|
+
private handlers = new Map<string, Set<IEventHandler>>();
|
|
3082
|
+
private initialized = false;
|
|
3083
|
+
|
|
3084
|
+
constructor(private eventBus: IEventBus) {}
|
|
3085
|
+
|
|
3086
|
+
async initialize(): Promise<void> {
|
|
3087
|
+
if (this.initialized) {
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
// Register default system event handlers
|
|
3092
|
+
this.registerSystemHandlers();
|
|
3093
|
+
|
|
3094
|
+
this.initialized = true;
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
async shutdown(): Promise<void> {
|
|
3098
|
+
// Clear all handlers
|
|
3099
|
+
this.handlers.clear();
|
|
3100
|
+
this.initialized = false;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
async route(event: IEvent): Promise<void> {
|
|
3104
|
+
const handlers = this.handlers.get(event.type);
|
|
3105
|
+
if (!handlers || handlers.size === 0) {
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
const handlerPromises = Array.from(handlers).map(async handler => {
|
|
3110
|
+
try {
|
|
3111
|
+
await handler(event);
|
|
3112
|
+
} catch (error) {
|
|
3113
|
+
// Log error but don't throw
|
|
3114
|
+
console.error(`Error in event handler for ${event.type}:`, error);
|
|
3115
|
+
}
|
|
3116
|
+
});
|
|
3117
|
+
|
|
3118
|
+
await Promise.allSettled(handlerPromises);
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
registerHandler(type: string, handler: IEventHandler): void {
|
|
3122
|
+
let handlers = this.handlers.get(type);
|
|
3123
|
+
if (!handlers) {
|
|
3124
|
+
handlers = new Set();
|
|
3125
|
+
this.handlers.set(type, handlers);
|
|
3126
|
+
}
|
|
3127
|
+
handlers.add(handler);
|
|
3128
|
+
|
|
3129
|
+
// Also register with event bus
|
|
3130
|
+
this.eventBus.on(type, handler);
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
unregisterHandler(type: string, handler: IEventHandler): void {
|
|
3134
|
+
const handlers = this.handlers.get(type);
|
|
3135
|
+
if (handlers) {
|
|
3136
|
+
handlers.delete(handler);
|
|
3137
|
+
if (handlers.size === 0) {
|
|
3138
|
+
this.handlers.delete(type);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
// Also unregister from event bus
|
|
3143
|
+
this.eventBus.off(type, handler);
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
getEventBus(): IEventBus {
|
|
3147
|
+
return this.eventBus;
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
private registerSystemHandlers(): void {
|
|
3151
|
+
// Error handling
|
|
3152
|
+
this.eventBus.on(SystemEventTypes.SYSTEM_ERROR, (event: IEvent) => {
|
|
3153
|
+
const { error, component } = event.payload as { error: Error; component: string };
|
|
3154
|
+
console.error(`System error in ${component}:`, error);
|
|
3155
|
+
});
|
|
3156
|
+
|
|
3157
|
+
// Deadlock detection
|
|
3158
|
+
this.eventBus.on(SystemEventTypes.DEADLOCK_DETECTED, (event: IEvent) => {
|
|
3159
|
+
const { agents, resources } = event.payload as { agents: string[]; resources: string[] };
|
|
3160
|
+
console.warn('Deadlock detected:', { agents, resources });
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
/**
|
|
3165
|
+
* Get registered handler count for a type
|
|
3166
|
+
*/
|
|
3167
|
+
getHandlerCount(type: string): number {
|
|
3168
|
+
return this.handlers.get(type)?.size ?? 0;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
/**
|
|
3172
|
+
* Get all registered event types
|
|
3173
|
+
*/
|
|
3174
|
+
getRegisteredTypes(): string[] {
|
|
3175
|
+
return Array.from(this.handlers.keys());
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
/**
|
|
3179
|
+
* Check if coordinator is initialized
|
|
3180
|
+
*/
|
|
3181
|
+
isInitialized(): boolean {
|
|
3182
|
+
return this.initialized;
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
|
|
3187
|
+
## Instructions
|
|
3188
|
+
|
|
3189
|
+
Analyze the above codebase context and provide your response following the format specified in the task.
|