@claude-flow/hooks 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +440 -0
- package/bin/hooks-daemon.js +199 -0
- package/bin/statusline.js +77 -0
- package/dist/bridge/official-hooks-bridge.d.ts +99 -0
- package/dist/bridge/official-hooks-bridge.d.ts.map +1 -0
- package/dist/bridge/official-hooks-bridge.js +280 -0
- package/dist/bridge/official-hooks-bridge.js.map +1 -0
- package/dist/cli/guidance-cli.d.ts +17 -0
- package/dist/cli/guidance-cli.d.ts.map +1 -0
- package/dist/cli/guidance-cli.js +486 -0
- package/dist/cli/guidance-cli.js.map +1 -0
- package/dist/daemons/index.d.ts +204 -0
- package/dist/daemons/index.d.ts.map +1 -0
- package/dist/daemons/index.js +443 -0
- package/dist/daemons/index.js.map +1 -0
- package/dist/executor/index.d.ts +80 -0
- package/dist/executor/index.d.ts.map +1 -0
- package/dist/executor/index.js +273 -0
- package/dist/executor/index.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/index.d.ts +11 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +11 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/llm-hooks.d.ts +93 -0
- package/dist/llm/llm-hooks.d.ts.map +1 -0
- package/dist/llm/llm-hooks.js +382 -0
- package/dist/llm/llm-hooks.js.map +1 -0
- package/dist/mcp/index.d.ts +61 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +501 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/reasoningbank/guidance-provider.d.ts +78 -0
- package/dist/reasoningbank/guidance-provider.d.ts.map +1 -0
- package/dist/reasoningbank/guidance-provider.js +350 -0
- package/dist/reasoningbank/guidance-provider.js.map +1 -0
- package/dist/reasoningbank/index.d.ts +212 -0
- package/dist/reasoningbank/index.d.ts.map +1 -0
- package/dist/reasoningbank/index.js +785 -0
- package/dist/reasoningbank/index.js.map +1 -0
- package/dist/registry/index.d.ts +85 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +212 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/statusline/index.d.ts +128 -0
- package/dist/statusline/index.d.ts.map +1 -0
- package/dist/statusline/index.js +493 -0
- package/dist/statusline/index.js.map +1 -0
- package/dist/swarm/index.d.ts +271 -0
- package/dist/swarm/index.d.ts.map +1 -0
- package/dist/swarm/index.js +638 -0
- package/dist/swarm/index.js.map +1 -0
- package/dist/types.d.ts +525 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +56 -0
- package/dist/types.js.map +1 -0
- package/dist/workers/index.d.ts +232 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +1521 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/mcp-tools.d.ts +37 -0
- package/dist/workers/mcp-tools.d.ts.map +1 -0
- package/dist/workers/mcp-tools.js +414 -0
- package/dist/workers/mcp-tools.js.map +1 -0
- package/dist/workers/session-hook.d.ts +42 -0
- package/dist/workers/session-hook.d.ts.map +1 -0
- package/dist/workers/session-hook.js +172 -0
- package/dist/workers/session-hook.js.map +1 -0
- package/package.json +101 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 ReasoningBank - Pattern Learning with AgentDB
|
|
3
|
+
*
|
|
4
|
+
* Connects hooks to persistent vector storage using AgentDB adapter.
|
|
5
|
+
* No JSON - all patterns stored as vectors in memory.db
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Real HNSW indexing (M=16, efConstruction=200) for 150x+ faster search
|
|
9
|
+
* - ONNX embeddings via @claude-flow/embeddings (MiniLM-L6 384-dim)
|
|
10
|
+
* - AgentDB backend for persistence
|
|
11
|
+
* - Pattern promotion from short-term to long-term memory
|
|
12
|
+
*
|
|
13
|
+
* @module @claude-flow/hooks/reasoningbank
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from 'node:events';
|
|
16
|
+
// Dynamic imports for optional dependencies
|
|
17
|
+
let AgentDBAdapter = null;
|
|
18
|
+
let HNSWIndex = null;
|
|
19
|
+
let EmbeddingServiceImpl = null;
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
dimensions: 384, // MiniLM-L6
|
|
22
|
+
hnswM: 16,
|
|
23
|
+
hnswEfConstruction: 200,
|
|
24
|
+
hnswEfSearch: 100,
|
|
25
|
+
maxShortTerm: 1000,
|
|
26
|
+
maxLongTerm: 5000,
|
|
27
|
+
promotionThreshold: 3,
|
|
28
|
+
qualityThreshold: 0.6,
|
|
29
|
+
dedupThreshold: 0.95,
|
|
30
|
+
dbPath: '.claude-flow/memory.db',
|
|
31
|
+
useMockEmbeddings: false,
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Agent mapping for routing
|
|
35
|
+
*/
|
|
36
|
+
const AGENT_PATTERNS = {
|
|
37
|
+
'security-architect': /security|auth|cve|vuln|encrypt|password|token/i,
|
|
38
|
+
'test-architect': /test|spec|mock|coverage|tdd|assert/i,
|
|
39
|
+
'performance-engineer': /perf|optim|fast|memory|cache|speed|slow/i,
|
|
40
|
+
'core-architect': /architect|design|ddd|domain|refactor|struct/i,
|
|
41
|
+
'swarm-specialist': /swarm|agent|coordinate|orchestrat|parallel/i,
|
|
42
|
+
'memory-specialist': /memory|agentdb|hnsw|vector|embedding/i,
|
|
43
|
+
'coder': /fix|bug|implement|create|add|build|error|code/i,
|
|
44
|
+
'reviewer': /review|quality|lint|check|audit/i,
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Domain-specific guidance templates
|
|
48
|
+
*/
|
|
49
|
+
const DOMAIN_GUIDANCE = {
|
|
50
|
+
security: [
|
|
51
|
+
'Validate all inputs at system boundaries',
|
|
52
|
+
'Use parameterized queries (no string concatenation)',
|
|
53
|
+
'Store secrets in environment variables only',
|
|
54
|
+
'Apply principle of least privilege',
|
|
55
|
+
'Check OWASP Top 10 patterns',
|
|
56
|
+
],
|
|
57
|
+
testing: [
|
|
58
|
+
'Write test first, then implementation (TDD)',
|
|
59
|
+
'Mock external dependencies',
|
|
60
|
+
'Test behavior, not implementation',
|
|
61
|
+
'One assertion per test concept',
|
|
62
|
+
'Use descriptive test names',
|
|
63
|
+
],
|
|
64
|
+
performance: [
|
|
65
|
+
'Use HNSW for vector search (not brute-force)',
|
|
66
|
+
'Batch database operations',
|
|
67
|
+
'Implement caching at appropriate layers',
|
|
68
|
+
'Profile before optimizing',
|
|
69
|
+
'Target: <1ms searches, <100ms operations',
|
|
70
|
+
],
|
|
71
|
+
architecture: [
|
|
72
|
+
'Respect bounded context boundaries',
|
|
73
|
+
'Use domain events for cross-module communication',
|
|
74
|
+
'Keep domain logic in domain layer',
|
|
75
|
+
'Infrastructure adapters for external services',
|
|
76
|
+
'Follow ADR decisions (ADR-001 through ADR-010)',
|
|
77
|
+
],
|
|
78
|
+
debugging: [
|
|
79
|
+
'Reproduce the issue first',
|
|
80
|
+
'Check recent changes in git log',
|
|
81
|
+
'Add logging before fixing',
|
|
82
|
+
'Write regression test',
|
|
83
|
+
"Verify fix doesn't break other tests",
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* ReasoningBank - Vector-based pattern storage and retrieval
|
|
88
|
+
*
|
|
89
|
+
* Uses AgentDB adapter for HNSW-indexed pattern storage.
|
|
90
|
+
* Provides guidance generation from learned patterns.
|
|
91
|
+
*/
|
|
92
|
+
export class ReasoningBank extends EventEmitter {
|
|
93
|
+
config;
|
|
94
|
+
agentDB = null;
|
|
95
|
+
hnswIndex = null;
|
|
96
|
+
embeddingService;
|
|
97
|
+
initialized = false;
|
|
98
|
+
useRealBackend = false;
|
|
99
|
+
// In-memory caches for fast access
|
|
100
|
+
shortTermPatterns = new Map();
|
|
101
|
+
longTermPatterns = new Map();
|
|
102
|
+
// Metrics
|
|
103
|
+
metrics = {
|
|
104
|
+
patternsStored: 0,
|
|
105
|
+
patternsRetrieved: 0,
|
|
106
|
+
searchCount: 0,
|
|
107
|
+
totalSearchTime: 0,
|
|
108
|
+
promotions: 0,
|
|
109
|
+
hnswSearchTime: 0,
|
|
110
|
+
bruteForceSearchTime: 0,
|
|
111
|
+
};
|
|
112
|
+
constructor(config = {}) {
|
|
113
|
+
super();
|
|
114
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
115
|
+
this.embeddingService = new FallbackEmbeddingService(this.config.dimensions);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Initialize ReasoningBank with AgentDB backend and real HNSW
|
|
119
|
+
*/
|
|
120
|
+
async initialize() {
|
|
121
|
+
if (this.initialized)
|
|
122
|
+
return;
|
|
123
|
+
try {
|
|
124
|
+
// Try to load real implementations
|
|
125
|
+
await this.loadDependencies();
|
|
126
|
+
if (AgentDBAdapter && HNSWIndex) {
|
|
127
|
+
// Initialize real HNSW index
|
|
128
|
+
this.hnswIndex = new HNSWIndex({
|
|
129
|
+
dimensions: this.config.dimensions,
|
|
130
|
+
M: this.config.hnswM,
|
|
131
|
+
efConstruction: this.config.hnswEfConstruction,
|
|
132
|
+
maxElements: this.config.maxShortTerm + this.config.maxLongTerm,
|
|
133
|
+
metric: 'cosine',
|
|
134
|
+
});
|
|
135
|
+
// Initialize AgentDB adapter
|
|
136
|
+
this.agentDB = new AgentDBAdapter({
|
|
137
|
+
dimensions: this.config.dimensions,
|
|
138
|
+
hnswM: this.config.hnswM,
|
|
139
|
+
hnswEfConstruction: this.config.hnswEfConstruction,
|
|
140
|
+
maxEntries: this.config.maxShortTerm + this.config.maxLongTerm,
|
|
141
|
+
persistenceEnabled: true,
|
|
142
|
+
persistencePath: this.config.dbPath,
|
|
143
|
+
embeddingGenerator: (text) => this.embeddingService.embed(text),
|
|
144
|
+
});
|
|
145
|
+
await this.agentDB.initialize();
|
|
146
|
+
this.useRealBackend = true;
|
|
147
|
+
// Try to use real embedding service
|
|
148
|
+
if (EmbeddingServiceImpl && !this.config.useMockEmbeddings) {
|
|
149
|
+
try {
|
|
150
|
+
this.embeddingService = new RealEmbeddingService(this.config.dimensions);
|
|
151
|
+
await this.embeddingService.initialize();
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.warn('[ReasoningBank] Real embeddings unavailable, using hash-based fallback');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
await this.loadPatterns();
|
|
158
|
+
console.log(`[ReasoningBank] Initialized with AgentDB + HNSW (M=${this.config.hnswM}, efConstruction=${this.config.hnswEfConstruction})`);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
throw new Error('Dependencies not available');
|
|
162
|
+
}
|
|
163
|
+
this.initialized = true;
|
|
164
|
+
this.emit('initialized', {
|
|
165
|
+
shortTermCount: this.shortTermPatterns.size,
|
|
166
|
+
longTermCount: this.longTermPatterns.size,
|
|
167
|
+
useRealBackend: this.useRealBackend,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
// Fallback to in-memory only mode
|
|
172
|
+
console.warn('[ReasoningBank] AgentDB not available, using in-memory mode');
|
|
173
|
+
this.useRealBackend = false;
|
|
174
|
+
this.initialized = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Load optional dependencies
|
|
179
|
+
*/
|
|
180
|
+
async loadDependencies() {
|
|
181
|
+
// Try to load optional peer dependencies at runtime
|
|
182
|
+
const dynamicImport = async (moduleName) => {
|
|
183
|
+
try {
|
|
184
|
+
return await import(/* webpackIgnore: true */ moduleName);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const memoryModule = await dynamicImport('@claude-flow/memory');
|
|
191
|
+
if (memoryModule) {
|
|
192
|
+
AgentDBAdapter = memoryModule.AgentDBAdapter;
|
|
193
|
+
HNSWIndex = memoryModule.HNSWIndex;
|
|
194
|
+
}
|
|
195
|
+
const embeddingsModule = await dynamicImport('@claude-flow/embeddings');
|
|
196
|
+
if (embeddingsModule) {
|
|
197
|
+
EmbeddingServiceImpl = embeddingsModule.createEmbeddingService;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Store a new pattern from hook execution
|
|
202
|
+
*/
|
|
203
|
+
async storePattern(strategy, domain, metadata = {}) {
|
|
204
|
+
await this.ensureInitialized();
|
|
205
|
+
const embedding = await this.embeddingService.embed(strategy);
|
|
206
|
+
// Check for duplicates using vector similarity
|
|
207
|
+
const similar = await this.searchPatterns(embedding, 1);
|
|
208
|
+
if (similar.length > 0 && similar[0].similarity > this.config.dedupThreshold) {
|
|
209
|
+
// Update existing pattern
|
|
210
|
+
const existing = similar[0].pattern;
|
|
211
|
+
existing.usageCount++;
|
|
212
|
+
existing.updatedAt = Date.now();
|
|
213
|
+
existing.quality = this.calculateQuality(existing);
|
|
214
|
+
await this.updateInStorage(existing);
|
|
215
|
+
this.checkPromotion(existing);
|
|
216
|
+
return { id: existing.id, action: 'updated' };
|
|
217
|
+
}
|
|
218
|
+
// Create new pattern
|
|
219
|
+
const pattern = {
|
|
220
|
+
id: `pat_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
221
|
+
strategy,
|
|
222
|
+
domain,
|
|
223
|
+
embedding,
|
|
224
|
+
quality: 0.5,
|
|
225
|
+
usageCount: 1,
|
|
226
|
+
successCount: 0,
|
|
227
|
+
createdAt: Date.now(),
|
|
228
|
+
updatedAt: Date.now(),
|
|
229
|
+
metadata,
|
|
230
|
+
};
|
|
231
|
+
this.shortTermPatterns.set(pattern.id, pattern);
|
|
232
|
+
// Add to HNSW index if available
|
|
233
|
+
if (this.hnswIndex) {
|
|
234
|
+
await this.hnswIndex.addPoint(pattern.id, embedding);
|
|
235
|
+
}
|
|
236
|
+
await this.storeInAgentDB(pattern, 'short_term');
|
|
237
|
+
this.metrics.patternsStored++;
|
|
238
|
+
this.emit('pattern:stored', { id: pattern.id, domain });
|
|
239
|
+
return { id: pattern.id, action: 'created' };
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Search for similar patterns using HNSW (if available) or brute-force
|
|
243
|
+
*/
|
|
244
|
+
async searchPatterns(query, k = 5) {
|
|
245
|
+
await this.ensureInitialized();
|
|
246
|
+
const startTime = performance.now();
|
|
247
|
+
const embedding = typeof query === 'string'
|
|
248
|
+
? await this.embeddingService.embed(query)
|
|
249
|
+
: query;
|
|
250
|
+
let results = [];
|
|
251
|
+
// Try HNSW search first (150x+ faster)
|
|
252
|
+
if (this.hnswIndex && this.useRealBackend) {
|
|
253
|
+
const hnswStart = performance.now();
|
|
254
|
+
try {
|
|
255
|
+
const hnswResults = await this.hnswIndex.search(embedding, k, this.config.hnswEfSearch);
|
|
256
|
+
this.metrics.hnswSearchTime += performance.now() - hnswStart;
|
|
257
|
+
for (const { id, distance } of hnswResults) {
|
|
258
|
+
const pattern = this.shortTermPatterns.get(id) || this.longTermPatterns.get(id);
|
|
259
|
+
if (pattern) {
|
|
260
|
+
// Convert distance to similarity (cosine distance -> similarity)
|
|
261
|
+
const similarity = 1 - distance;
|
|
262
|
+
results.push({ pattern, similarity });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
console.warn('[ReasoningBank] HNSW search failed, falling back to brute-force');
|
|
268
|
+
results = this.bruteForceSearch(embedding, k);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// Brute-force search
|
|
273
|
+
results = this.bruteForceSearch(embedding, k);
|
|
274
|
+
}
|
|
275
|
+
const searchTime = performance.now() - startTime;
|
|
276
|
+
this.metrics.searchCount++;
|
|
277
|
+
this.metrics.totalSearchTime += searchTime;
|
|
278
|
+
this.metrics.patternsRetrieved += results.length;
|
|
279
|
+
return results;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Brute-force search (fallback)
|
|
283
|
+
*/
|
|
284
|
+
bruteForceSearch(embedding, k) {
|
|
285
|
+
const startTime = performance.now();
|
|
286
|
+
const results = [];
|
|
287
|
+
// Search long-term first (higher quality)
|
|
288
|
+
for (const pattern of this.longTermPatterns.values()) {
|
|
289
|
+
const similarity = this.cosineSimilarity(embedding, pattern.embedding);
|
|
290
|
+
results.push({ pattern, similarity });
|
|
291
|
+
}
|
|
292
|
+
// Search short-term
|
|
293
|
+
for (const pattern of this.shortTermPatterns.values()) {
|
|
294
|
+
const similarity = this.cosineSimilarity(embedding, pattern.embedding);
|
|
295
|
+
results.push({ pattern, similarity });
|
|
296
|
+
}
|
|
297
|
+
// Sort by similarity and take top k
|
|
298
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
299
|
+
this.metrics.bruteForceSearchTime += performance.now() - startTime;
|
|
300
|
+
return results.slice(0, k);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Generate guidance for a given context
|
|
304
|
+
*/
|
|
305
|
+
async generateGuidance(context) {
|
|
306
|
+
await this.ensureInitialized();
|
|
307
|
+
const startTime = performance.now();
|
|
308
|
+
const query = this.buildQueryFromContext(context);
|
|
309
|
+
const patterns = await this.searchPatterns(query, 5);
|
|
310
|
+
// Detect domains from context
|
|
311
|
+
const domains = this.detectDomains(query);
|
|
312
|
+
// Build recommendations from domain templates
|
|
313
|
+
const recommendations = [];
|
|
314
|
+
for (const domain of domains) {
|
|
315
|
+
if (DOMAIN_GUIDANCE[domain]) {
|
|
316
|
+
recommendations.push(...DOMAIN_GUIDANCE[domain]);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Generate context string
|
|
320
|
+
const contextParts = [];
|
|
321
|
+
if (domains.length > 0) {
|
|
322
|
+
contextParts.push(`**Detected Domains**: ${domains.join(', ')}`);
|
|
323
|
+
}
|
|
324
|
+
if (patterns.length > 0) {
|
|
325
|
+
contextParts.push('**Relevant Patterns**:');
|
|
326
|
+
for (const { pattern, similarity } of patterns.slice(0, 3)) {
|
|
327
|
+
contextParts.push(`- ${pattern.strategy} (${(similarity * 100).toFixed(0)}% match)`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Agent suggestion
|
|
331
|
+
const agentSuggestion = this.suggestAgent(query);
|
|
332
|
+
return {
|
|
333
|
+
patterns,
|
|
334
|
+
context: contextParts.join('\n'),
|
|
335
|
+
recommendations: recommendations.slice(0, 5),
|
|
336
|
+
agentSuggestion,
|
|
337
|
+
searchTimeMs: performance.now() - startTime,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Route task to optimal agent based on learned patterns
|
|
342
|
+
*/
|
|
343
|
+
async routeTask(task) {
|
|
344
|
+
await this.ensureInitialized();
|
|
345
|
+
const suggestion = this.suggestAgent(task);
|
|
346
|
+
// Get historical performance from patterns
|
|
347
|
+
const taskPatterns = await this.searchPatterns(task, 10);
|
|
348
|
+
const agentPerformance = new Map();
|
|
349
|
+
for (const { pattern } of taskPatterns) {
|
|
350
|
+
const agent = pattern.metadata.agent || 'coder';
|
|
351
|
+
const perf = agentPerformance.get(agent) || { success: 0, total: 0, quality: 0 };
|
|
352
|
+
perf.total++;
|
|
353
|
+
perf.success += pattern.successCount / Math.max(pattern.usageCount, 1);
|
|
354
|
+
perf.quality += pattern.quality;
|
|
355
|
+
agentPerformance.set(agent, perf);
|
|
356
|
+
}
|
|
357
|
+
// Calculate historical performance for suggested agent
|
|
358
|
+
const historicalPerf = agentPerformance.get(suggestion.agent);
|
|
359
|
+
const historicalPerformance = historicalPerf
|
|
360
|
+
? {
|
|
361
|
+
successRate: historicalPerf.success / historicalPerf.total,
|
|
362
|
+
avgQuality: historicalPerf.quality / historicalPerf.total,
|
|
363
|
+
taskCount: historicalPerf.total,
|
|
364
|
+
}
|
|
365
|
+
: undefined;
|
|
366
|
+
// Build alternatives
|
|
367
|
+
const alternatives = Object.entries(AGENT_PATTERNS)
|
|
368
|
+
.filter(([agent]) => agent !== suggestion.agent)
|
|
369
|
+
.map(([agent, pattern]) => ({
|
|
370
|
+
agent,
|
|
371
|
+
confidence: pattern.test(task) ? 85 : 60,
|
|
372
|
+
}))
|
|
373
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
374
|
+
.slice(0, 3);
|
|
375
|
+
return {
|
|
376
|
+
agent: suggestion.agent,
|
|
377
|
+
confidence: suggestion.confidence,
|
|
378
|
+
alternatives,
|
|
379
|
+
reasoning: suggestion.reasoning,
|
|
380
|
+
historicalPerformance,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Record pattern usage outcome
|
|
385
|
+
*/
|
|
386
|
+
async recordOutcome(patternId, success) {
|
|
387
|
+
const pattern = this.shortTermPatterns.get(patternId) ||
|
|
388
|
+
this.longTermPatterns.get(patternId);
|
|
389
|
+
if (!pattern)
|
|
390
|
+
return;
|
|
391
|
+
pattern.usageCount++;
|
|
392
|
+
if (success)
|
|
393
|
+
pattern.successCount++;
|
|
394
|
+
pattern.quality = this.calculateQuality(pattern);
|
|
395
|
+
pattern.updatedAt = Date.now();
|
|
396
|
+
await this.updateInStorage(pattern);
|
|
397
|
+
this.checkPromotion(pattern);
|
|
398
|
+
this.emit('outcome:recorded', { patternId, success });
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Consolidate patterns (dedup, prune, promote)
|
|
402
|
+
* Called by HooksLearningDaemon
|
|
403
|
+
*/
|
|
404
|
+
async consolidate() {
|
|
405
|
+
await this.ensureInitialized();
|
|
406
|
+
let duplicatesRemoved = 0;
|
|
407
|
+
let patternsPruned = 0;
|
|
408
|
+
let patternsPromoted = 0;
|
|
409
|
+
// Check promotions
|
|
410
|
+
for (const pattern of this.shortTermPatterns.values()) {
|
|
411
|
+
if (this.shouldPromote(pattern)) {
|
|
412
|
+
await this.promotePattern(pattern);
|
|
413
|
+
patternsPromoted++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Prune old low-quality short-term patterns
|
|
417
|
+
const now = Date.now();
|
|
418
|
+
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
|
|
419
|
+
for (const [id, pattern] of this.shortTermPatterns) {
|
|
420
|
+
if (now - pattern.createdAt > maxAge && pattern.usageCount < 2) {
|
|
421
|
+
this.shortTermPatterns.delete(id);
|
|
422
|
+
await this.deleteFromStorage(id);
|
|
423
|
+
patternsPruned++;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Deduplicate similar patterns
|
|
427
|
+
const patterns = Array.from(this.shortTermPatterns.values());
|
|
428
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
429
|
+
for (let j = i + 1; j < patterns.length; j++) {
|
|
430
|
+
const similarity = this.cosineSimilarity(patterns[i].embedding, patterns[j].embedding);
|
|
431
|
+
if (similarity > this.config.dedupThreshold) {
|
|
432
|
+
// Keep the one with higher quality
|
|
433
|
+
const toRemove = patterns[i].quality >= patterns[j].quality ? patterns[j] : patterns[i];
|
|
434
|
+
this.shortTermPatterns.delete(toRemove.id);
|
|
435
|
+
await this.deleteFromStorage(toRemove.id);
|
|
436
|
+
duplicatesRemoved++;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
this.emit('consolidated', { duplicatesRemoved, patternsPruned, patternsPromoted });
|
|
441
|
+
return { duplicatesRemoved, patternsPruned, patternsPromoted };
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get statistics
|
|
445
|
+
*/
|
|
446
|
+
getStats() {
|
|
447
|
+
const avgHnsw = this.metrics.searchCount > 0 ? this.metrics.hnswSearchTime / this.metrics.searchCount : 0;
|
|
448
|
+
const avgBrute = this.metrics.searchCount > 0 ? this.metrics.bruteForceSearchTime / this.metrics.searchCount : 1;
|
|
449
|
+
return {
|
|
450
|
+
shortTermCount: this.shortTermPatterns.size,
|
|
451
|
+
longTermCount: this.longTermPatterns.size,
|
|
452
|
+
metrics: { ...this.metrics },
|
|
453
|
+
avgSearchTime: this.metrics.searchCount > 0
|
|
454
|
+
? this.metrics.totalSearchTime / this.metrics.searchCount
|
|
455
|
+
: 0,
|
|
456
|
+
useRealBackend: this.useRealBackend,
|
|
457
|
+
hnswSpeedup: avgBrute > 0 && avgHnsw > 0 ? avgBrute / avgHnsw : 1,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Export patterns for backup/transfer
|
|
462
|
+
*/
|
|
463
|
+
async exportPatterns() {
|
|
464
|
+
return {
|
|
465
|
+
shortTerm: Array.from(this.shortTermPatterns.values()),
|
|
466
|
+
longTerm: Array.from(this.longTermPatterns.values()),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Import patterns from backup
|
|
471
|
+
*/
|
|
472
|
+
async importPatterns(data) {
|
|
473
|
+
await this.ensureInitialized();
|
|
474
|
+
let imported = 0;
|
|
475
|
+
for (const pattern of data.shortTerm) {
|
|
476
|
+
if (!this.shortTermPatterns.has(pattern.id)) {
|
|
477
|
+
this.shortTermPatterns.set(pattern.id, pattern);
|
|
478
|
+
if (this.hnswIndex) {
|
|
479
|
+
await this.hnswIndex.addPoint(pattern.id, pattern.embedding);
|
|
480
|
+
}
|
|
481
|
+
await this.storeInAgentDB(pattern, 'short_term');
|
|
482
|
+
imported++;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
for (const pattern of data.longTerm) {
|
|
486
|
+
if (!this.longTermPatterns.has(pattern.id)) {
|
|
487
|
+
this.longTermPatterns.set(pattern.id, pattern);
|
|
488
|
+
if (this.hnswIndex) {
|
|
489
|
+
await this.hnswIndex.addPoint(pattern.id, pattern.embedding);
|
|
490
|
+
}
|
|
491
|
+
await this.storeInAgentDB(pattern, 'long_term');
|
|
492
|
+
imported++;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return { imported };
|
|
496
|
+
}
|
|
497
|
+
// ===== Private Methods =====
|
|
498
|
+
async ensureInitialized() {
|
|
499
|
+
if (!this.initialized) {
|
|
500
|
+
await this.initialize();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async loadPatterns() {
|
|
504
|
+
if (!this.agentDB)
|
|
505
|
+
return;
|
|
506
|
+
try {
|
|
507
|
+
// Load from AgentDB namespaces
|
|
508
|
+
const shortTermEntries = await this.agentDB.query({
|
|
509
|
+
namespace: 'patterns:short_term',
|
|
510
|
+
limit: this.config.maxShortTerm,
|
|
511
|
+
});
|
|
512
|
+
for (const entry of shortTermEntries) {
|
|
513
|
+
const pattern = this.entryToPattern(entry);
|
|
514
|
+
this.shortTermPatterns.set(pattern.id, pattern);
|
|
515
|
+
if (this.hnswIndex) {
|
|
516
|
+
await this.hnswIndex.addPoint(pattern.id, pattern.embedding);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
const longTermEntries = await this.agentDB.query({
|
|
520
|
+
namespace: 'patterns:long_term',
|
|
521
|
+
limit: this.config.maxLongTerm,
|
|
522
|
+
});
|
|
523
|
+
for (const entry of longTermEntries) {
|
|
524
|
+
const pattern = this.entryToPattern(entry);
|
|
525
|
+
this.longTermPatterns.set(pattern.id, pattern);
|
|
526
|
+
if (this.hnswIndex) {
|
|
527
|
+
await this.hnswIndex.addPoint(pattern.id, pattern.embedding);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
console.warn('[ReasoningBank] Failed to load patterns:', error);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async storeInAgentDB(pattern, type) {
|
|
536
|
+
if (!this.agentDB)
|
|
537
|
+
return;
|
|
538
|
+
try {
|
|
539
|
+
await this.agentDB.store({
|
|
540
|
+
key: pattern.id,
|
|
541
|
+
namespace: `patterns:${type}`,
|
|
542
|
+
content: pattern.strategy,
|
|
543
|
+
embedding: pattern.embedding,
|
|
544
|
+
tags: [pattern.domain, type],
|
|
545
|
+
metadata: {
|
|
546
|
+
quality: pattern.quality,
|
|
547
|
+
usageCount: pattern.usageCount,
|
|
548
|
+
successCount: pattern.successCount,
|
|
549
|
+
createdAt: pattern.createdAt,
|
|
550
|
+
updatedAt: pattern.updatedAt,
|
|
551
|
+
...pattern.metadata,
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
console.warn('[ReasoningBank] Failed to store pattern:', error);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async updateInStorage(pattern) {
|
|
560
|
+
if (!this.agentDB)
|
|
561
|
+
return;
|
|
562
|
+
try {
|
|
563
|
+
await this.agentDB.update(pattern.id, {
|
|
564
|
+
metadata: {
|
|
565
|
+
quality: pattern.quality,
|
|
566
|
+
usageCount: pattern.usageCount,
|
|
567
|
+
successCount: pattern.successCount,
|
|
568
|
+
updatedAt: pattern.updatedAt,
|
|
569
|
+
},
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
console.warn('[ReasoningBank] Failed to update pattern:', error);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
async deleteFromStorage(id) {
|
|
577
|
+
if (!this.agentDB)
|
|
578
|
+
return;
|
|
579
|
+
try {
|
|
580
|
+
await this.agentDB.delete(id);
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
console.warn('[ReasoningBank] Failed to delete pattern:', error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
entryToPattern(entry) {
|
|
587
|
+
return {
|
|
588
|
+
id: entry.id || entry.key,
|
|
589
|
+
strategy: entry.content,
|
|
590
|
+
domain: entry.tags?.[0] || 'general',
|
|
591
|
+
embedding: entry.embedding instanceof Float32Array
|
|
592
|
+
? entry.embedding
|
|
593
|
+
: new Float32Array(entry.embedding || []),
|
|
594
|
+
quality: entry.metadata?.quality || 0.5,
|
|
595
|
+
usageCount: entry.metadata?.usageCount || 1,
|
|
596
|
+
successCount: entry.metadata?.successCount || 0,
|
|
597
|
+
createdAt: entry.metadata?.createdAt || entry.createdAt || Date.now(),
|
|
598
|
+
updatedAt: entry.metadata?.updatedAt || entry.updatedAt || Date.now(),
|
|
599
|
+
metadata: entry.metadata || {},
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
buildQueryFromContext(context) {
|
|
603
|
+
const parts = [];
|
|
604
|
+
if (context.file?.path) {
|
|
605
|
+
parts.push(`file: ${context.file.path}`);
|
|
606
|
+
}
|
|
607
|
+
if (context.command?.raw) {
|
|
608
|
+
parts.push(`command: ${context.command.raw}`);
|
|
609
|
+
}
|
|
610
|
+
if (context.task?.description) {
|
|
611
|
+
parts.push(context.task.description);
|
|
612
|
+
}
|
|
613
|
+
if (context.routing?.task) {
|
|
614
|
+
parts.push(context.routing.task);
|
|
615
|
+
}
|
|
616
|
+
return parts.join(' ');
|
|
617
|
+
}
|
|
618
|
+
detectDomains(text) {
|
|
619
|
+
const domains = [];
|
|
620
|
+
const lowerText = text.toLowerCase();
|
|
621
|
+
if (/security|auth|password|token|secret|cve|vuln/i.test(lowerText)) {
|
|
622
|
+
domains.push('security');
|
|
623
|
+
}
|
|
624
|
+
if (/test|spec|mock|coverage|tdd|assert/i.test(lowerText)) {
|
|
625
|
+
domains.push('testing');
|
|
626
|
+
}
|
|
627
|
+
if (/perf|optim|fast|slow|memory|cache|speed/i.test(lowerText)) {
|
|
628
|
+
domains.push('performance');
|
|
629
|
+
}
|
|
630
|
+
if (/architect|design|ddd|domain|refactor|struct/i.test(lowerText)) {
|
|
631
|
+
domains.push('architecture');
|
|
632
|
+
}
|
|
633
|
+
if (/fix|bug|error|issue|broken|fail|debug/i.test(lowerText)) {
|
|
634
|
+
domains.push('debugging');
|
|
635
|
+
}
|
|
636
|
+
return domains;
|
|
637
|
+
}
|
|
638
|
+
suggestAgent(task) {
|
|
639
|
+
let bestAgent = 'coder';
|
|
640
|
+
let bestConfidence = 70;
|
|
641
|
+
let reasoning = 'Default agent for general tasks';
|
|
642
|
+
for (const [agent, pattern] of Object.entries(AGENT_PATTERNS)) {
|
|
643
|
+
if (pattern.test(task)) {
|
|
644
|
+
const matches = task.match(pattern);
|
|
645
|
+
const confidence = 85 + (matches ? Math.min(matches.length * 5, 13) : 0);
|
|
646
|
+
if (confidence > bestConfidence) {
|
|
647
|
+
bestAgent = agent;
|
|
648
|
+
bestConfidence = confidence;
|
|
649
|
+
reasoning = `Task matches ${agent} patterns`;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return { agent: bestAgent, confidence: bestConfidence, reasoning };
|
|
654
|
+
}
|
|
655
|
+
calculateQuality(pattern) {
|
|
656
|
+
if (pattern.usageCount === 0)
|
|
657
|
+
return 0.5;
|
|
658
|
+
const successRate = pattern.successCount / pattern.usageCount;
|
|
659
|
+
return 0.3 + successRate * 0.7; // Range: 0.3 to 1.0
|
|
660
|
+
}
|
|
661
|
+
shouldPromote(pattern) {
|
|
662
|
+
return (pattern.usageCount >= this.config.promotionThreshold &&
|
|
663
|
+
pattern.quality >= this.config.qualityThreshold);
|
|
664
|
+
}
|
|
665
|
+
checkPromotion(pattern) {
|
|
666
|
+
if (this.shortTermPatterns.has(pattern.id) && this.shouldPromote(pattern)) {
|
|
667
|
+
this.promotePattern(pattern);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async promotePattern(pattern) {
|
|
671
|
+
// Move from short-term to long-term
|
|
672
|
+
this.shortTermPatterns.delete(pattern.id);
|
|
673
|
+
this.longTermPatterns.set(pattern.id, pattern);
|
|
674
|
+
// Update storage
|
|
675
|
+
await this.deleteFromStorage(pattern.id);
|
|
676
|
+
await this.storeInAgentDB(pattern, 'long_term');
|
|
677
|
+
this.metrics.promotions++;
|
|
678
|
+
this.emit('pattern:promoted', { id: pattern.id });
|
|
679
|
+
}
|
|
680
|
+
cosineSimilarity(a, b) {
|
|
681
|
+
if (a.length !== b.length)
|
|
682
|
+
return 0;
|
|
683
|
+
let dot = 0, normA = 0, normB = 0;
|
|
684
|
+
for (let i = 0; i < a.length; i++) {
|
|
685
|
+
dot += a[i] * b[i];
|
|
686
|
+
normA += a[i] * a[i];
|
|
687
|
+
normB += b[i] * b[i];
|
|
688
|
+
}
|
|
689
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
690
|
+
return denom > 0 ? dot / denom : 0;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Real embedding service using @claude-flow/embeddings
|
|
695
|
+
*/
|
|
696
|
+
class RealEmbeddingService {
|
|
697
|
+
service = null;
|
|
698
|
+
dimensions;
|
|
699
|
+
cache = new Map();
|
|
700
|
+
constructor(dimensions = 384) {
|
|
701
|
+
this.dimensions = dimensions;
|
|
702
|
+
}
|
|
703
|
+
async initialize() {
|
|
704
|
+
if (EmbeddingServiceImpl) {
|
|
705
|
+
this.service = await EmbeddingServiceImpl({
|
|
706
|
+
provider: 'transformers',
|
|
707
|
+
model: 'Xenova/all-MiniLM-L6-v2',
|
|
708
|
+
dimensions: this.dimensions,
|
|
709
|
+
cacheSize: 1000,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
async embed(text) {
|
|
714
|
+
const cacheKey = text.slice(0, 200);
|
|
715
|
+
if (this.cache.has(cacheKey)) {
|
|
716
|
+
return this.cache.get(cacheKey);
|
|
717
|
+
}
|
|
718
|
+
if (this.service) {
|
|
719
|
+
const result = await this.service.embed(text);
|
|
720
|
+
const embedding = result.embedding;
|
|
721
|
+
this.cache.set(cacheKey, embedding);
|
|
722
|
+
return embedding;
|
|
723
|
+
}
|
|
724
|
+
throw new Error('Embedding service not initialized');
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Fallback embedding service (hash-based)
|
|
729
|
+
*/
|
|
730
|
+
class FallbackEmbeddingService {
|
|
731
|
+
dimensions;
|
|
732
|
+
cache = new Map();
|
|
733
|
+
constructor(dimensions = 384) {
|
|
734
|
+
this.dimensions = dimensions;
|
|
735
|
+
}
|
|
736
|
+
async embed(text) {
|
|
737
|
+
const cacheKey = text.slice(0, 200);
|
|
738
|
+
if (this.cache.has(cacheKey)) {
|
|
739
|
+
return this.cache.get(cacheKey);
|
|
740
|
+
}
|
|
741
|
+
// Try agentic-flow ONNX embeddings first
|
|
742
|
+
try {
|
|
743
|
+
const { execFileSync } = await import('child_process');
|
|
744
|
+
// Use execFileSync with shell: false to prevent command injection
|
|
745
|
+
// Pass text as argument array to avoid shell interpolation
|
|
746
|
+
const safeText = text.slice(0, 500).replace(/[\x00-\x1f]/g, ''); // Remove control chars
|
|
747
|
+
const result = execFileSync('npx', ['agentic-flow@alpha', 'embeddings', 'generate', safeText, '--format', 'json'], { encoding: 'utf-8', timeout: 10000, shell: false, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
748
|
+
const parsed = JSON.parse(result);
|
|
749
|
+
const embedding = new Float32Array(parsed.embedding || parsed);
|
|
750
|
+
this.cache.set(cacheKey, embedding);
|
|
751
|
+
return embedding;
|
|
752
|
+
}
|
|
753
|
+
catch {
|
|
754
|
+
// Fallback to hash-based embedding
|
|
755
|
+
return this.hashEmbed(text);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
hashEmbed(text) {
|
|
759
|
+
const embedding = new Float32Array(this.dimensions);
|
|
760
|
+
const normalized = text.toLowerCase().trim();
|
|
761
|
+
for (let i = 0; i < this.dimensions; i++) {
|
|
762
|
+
let hash = 0;
|
|
763
|
+
for (let j = 0; j < normalized.length; j++) {
|
|
764
|
+
hash = ((hash << 5) - hash + normalized.charCodeAt(j) * (i + 1)) | 0;
|
|
765
|
+
}
|
|
766
|
+
embedding[i] = (Math.sin(hash) + 1) / 2;
|
|
767
|
+
}
|
|
768
|
+
// Normalize
|
|
769
|
+
let norm = 0;
|
|
770
|
+
for (let i = 0; i < this.dimensions; i++) {
|
|
771
|
+
norm += embedding[i] * embedding[i];
|
|
772
|
+
}
|
|
773
|
+
norm = Math.sqrt(norm);
|
|
774
|
+
if (norm > 0) {
|
|
775
|
+
for (let i = 0; i < this.dimensions; i++) {
|
|
776
|
+
embedding[i] /= norm;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
this.cache.set(text.slice(0, 200), embedding);
|
|
780
|
+
return embedding;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
// Export singleton instance
|
|
784
|
+
export const reasoningBank = new ReasoningBank();
|
|
785
|
+
//# sourceMappingURL=index.js.map
|