@claude-flow/cli 3.5.23 → 3.5.25
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/agents/templates/base-template-generator.md +22 -1
- package/dist/src/commands/daemon.d.ts.map +1 -1
- package/dist/src/commands/daemon.js +54 -7
- package/dist/src/commands/daemon.js.map +1 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/init/executor.js +17 -17
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/helpers-generator.js +10 -10
- package/dist/src/init/helpers-generator.js.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +2 -2
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/config-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/config-tools.js +10 -1
- package/dist/src/mcp-tools/config-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +150 -7
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +2 -0
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.d.ts +2 -1
- package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.js +216 -30
- package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
- package/dist/src/services/index.d.ts +1 -1
- package/dist/src/services/index.d.ts.map +1 -1
- package/dist/src/services/ruvector-training.d.ts.map +1 -1
- package/dist/src/services/ruvector-training.js +11 -4
- package/dist/src/services/ruvector-training.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts +24 -3
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +123 -12
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/src/transfer/storage/gcs.d.ts.map +1 -1
- package/dist/src/transfer/storage/gcs.js +22 -6
- package/dist/src/transfer/storage/gcs.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Provides intelligent hooks functionality via MCP protocol
|
|
4
4
|
*/
|
|
5
5
|
import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync } from 'fs';
|
|
6
|
-
import { join, resolve } from 'path';
|
|
6
|
+
import { dirname, join, resolve } from 'path';
|
|
7
7
|
// Real vector search functions - lazy loaded to avoid circular imports
|
|
8
8
|
let searchEntriesFn = null;
|
|
9
9
|
async function getRealSearchFunction() {
|
|
@@ -121,7 +121,88 @@ function generateSimpleEmbedding(text, dimension = 384) {
|
|
|
121
121
|
}
|
|
122
122
|
return embedding;
|
|
123
123
|
}
|
|
124
|
-
//
|
|
124
|
+
// ── Runtime routing outcome persistence ──────────────────────────────
|
|
125
|
+
// Closes the learning loop: post-task records outcomes → route loads them.
|
|
126
|
+
const ROUTING_OUTCOMES_PATH = join(resolve('.'), '.claude-flow/routing-outcomes.json');
|
|
127
|
+
const ROUTING_STOPWORDS = new Set([
|
|
128
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had',
|
|
129
|
+
'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'shall', 'can',
|
|
130
|
+
'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'during',
|
|
131
|
+
'before', 'after', 'above', 'below', 'between', 'under', 'again', 'further', 'then', 'once',
|
|
132
|
+
'it', 'its', 'this', 'that', 'these', 'those', 'i', 'me', 'my', 'we', 'our', 'you', 'your',
|
|
133
|
+
'he', 'she', 'they', 'them', 'and', 'but', 'or', 'nor', 'not', 'no', 'so', 'if', 'when', 'than',
|
|
134
|
+
'very', 'just', 'also', 'only', 'both', 'each', 'all', 'any', 'few', 'more', 'most', 'other',
|
|
135
|
+
'some', 'such', 'same', 'new', 'now', 'here', 'there', 'where', 'how', 'what', 'which', 'who',
|
|
136
|
+
]);
|
|
137
|
+
function extractKeywords(text) {
|
|
138
|
+
if (!text)
|
|
139
|
+
return [];
|
|
140
|
+
return text.toLowerCase()
|
|
141
|
+
.replace(/[^a-z0-9\s-]/g, ' ')
|
|
142
|
+
.split(/\s+/)
|
|
143
|
+
.filter(w => w.length > 2 && !ROUTING_STOPWORDS.has(w));
|
|
144
|
+
}
|
|
145
|
+
function loadRoutingOutcomes() {
|
|
146
|
+
try {
|
|
147
|
+
if (existsSync(ROUTING_OUTCOMES_PATH)) {
|
|
148
|
+
const data = JSON.parse(readFileSync(ROUTING_OUTCOMES_PATH, 'utf-8'));
|
|
149
|
+
return data.outcomes || [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch { /* corrupt file, start fresh */ }
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
function saveRoutingOutcomes(outcomes) {
|
|
156
|
+
try {
|
|
157
|
+
const dir = dirname(ROUTING_OUTCOMES_PATH);
|
|
158
|
+
if (!existsSync(dir))
|
|
159
|
+
mkdirSync(dir, { recursive: true });
|
|
160
|
+
// Cap at 500 entries to bound file size
|
|
161
|
+
const capped = outcomes.slice(-500);
|
|
162
|
+
writeFileSync(ROUTING_OUTCOMES_PATH, JSON.stringify({ outcomes: capped }, null, 2));
|
|
163
|
+
}
|
|
164
|
+
catch { /* non-critical */ }
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build learned routing patterns from successful task outcomes.
|
|
168
|
+
* Returns patterns in the same shape as TASK_PATTERNS so they can be
|
|
169
|
+
* merged into both the native HNSW and pure-JS semantic routers.
|
|
170
|
+
*/
|
|
171
|
+
function loadLearnedPatterns() {
|
|
172
|
+
const outcomes = loadRoutingOutcomes();
|
|
173
|
+
const byAgent = {};
|
|
174
|
+
for (const o of outcomes) {
|
|
175
|
+
if (!o.success || !o.agent || !o.keywords?.length)
|
|
176
|
+
continue;
|
|
177
|
+
if (!byAgent[o.agent])
|
|
178
|
+
byAgent[o.agent] = new Set();
|
|
179
|
+
for (const kw of o.keywords)
|
|
180
|
+
byAgent[o.agent].add(kw);
|
|
181
|
+
}
|
|
182
|
+
const patterns = {};
|
|
183
|
+
for (const [agent, kwSet] of Object.entries(byAgent)) {
|
|
184
|
+
patterns[`learned-${agent}`] = {
|
|
185
|
+
keywords: [...kwSet].slice(0, 50),
|
|
186
|
+
agents: [agent],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return patterns;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Merge static TASK_PATTERNS with runtime-learned patterns.
|
|
193
|
+
* Static patterns take precedence (learned patterns won't overwrite them).
|
|
194
|
+
*/
|
|
195
|
+
function getMergedTaskPatterns() {
|
|
196
|
+
const merged = { ...TASK_PATTERNS };
|
|
197
|
+
const learned = loadLearnedPatterns();
|
|
198
|
+
for (const [key, pattern] of Object.entries(learned)) {
|
|
199
|
+
if (!merged[key]) {
|
|
200
|
+
merged[key] = pattern;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return merged;
|
|
204
|
+
}
|
|
205
|
+
// ── Static task patterns (used by both native and pure-JS routers) ───
|
|
125
206
|
const TASK_PATTERNS = {
|
|
126
207
|
'security-task': {
|
|
127
208
|
keywords: ['authentication', 'security', 'auth', 'password', 'encryption', 'vulnerability', 'cve', 'audit'],
|
|
@@ -198,8 +279,8 @@ async function getSemanticRouter() {
|
|
|
198
279
|
hnswEfConstruction: 200,
|
|
199
280
|
hnswEfSearch: 100,
|
|
200
281
|
});
|
|
201
|
-
// Initialize with task patterns
|
|
202
|
-
for (const [patternName, { keywords }] of Object.entries(
|
|
282
|
+
// Initialize with static + runtime-learned task patterns
|
|
283
|
+
for (const [patternName, { keywords }] of Object.entries(getMergedTaskPatterns())) {
|
|
203
284
|
for (const keyword of keywords) {
|
|
204
285
|
const embedding = generateSimpleEmbedding(keyword);
|
|
205
286
|
db.insert(`${patternName}:${keyword}`, embedding);
|
|
@@ -220,7 +301,7 @@ async function getSemanticRouter() {
|
|
|
220
301
|
try {
|
|
221
302
|
const { SemanticRouter } = await import('../ruvector/semantic-router.js');
|
|
222
303
|
semanticRouter = new SemanticRouter({ dimension: 384 });
|
|
223
|
-
for (const [patternName, { keywords, agents }] of Object.entries(
|
|
304
|
+
for (const [patternName, { keywords, agents }] of Object.entries(getMergedTaskPatterns())) {
|
|
224
305
|
const embeddings = keywords.map(kw => generateSimpleEmbedding(kw));
|
|
225
306
|
semanticRouter.addIntentWithEmbeddings(patternName, embeddings, { agents, keywords });
|
|
226
307
|
// Cache embeddings for keywords
|
|
@@ -414,11 +495,32 @@ function suggestAgentsForFile(filePath) {
|
|
|
414
495
|
}
|
|
415
496
|
function suggestAgentsForTask(task) {
|
|
416
497
|
const taskLower = task.toLowerCase();
|
|
498
|
+
// Check static keyword patterns first
|
|
417
499
|
for (const [pattern, result] of Object.entries(KEYWORD_PATTERNS)) {
|
|
418
500
|
if (taskLower.includes(pattern)) {
|
|
419
501
|
return result;
|
|
420
502
|
}
|
|
421
503
|
}
|
|
504
|
+
// Check runtime-learned patterns from successful task outcomes
|
|
505
|
+
const taskKeywords = extractKeywords(task);
|
|
506
|
+
if (taskKeywords.length > 0) {
|
|
507
|
+
const outcomes = loadRoutingOutcomes();
|
|
508
|
+
let bestAgent = '';
|
|
509
|
+
let bestOverlap = 0;
|
|
510
|
+
for (const outcome of outcomes) {
|
|
511
|
+
if (!outcome.success || !outcome.agent || !outcome.keywords?.length)
|
|
512
|
+
continue;
|
|
513
|
+
const overlap = taskKeywords.filter(kw => outcome.keywords.includes(kw)).length;
|
|
514
|
+
if (overlap > bestOverlap) {
|
|
515
|
+
bestOverlap = overlap;
|
|
516
|
+
bestAgent = outcome.agent;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Require at least 2 keyword overlap to prevent false positives
|
|
520
|
+
if (bestAgent && bestOverlap >= 2) {
|
|
521
|
+
return { agents: [bestAgent], confidence: Math.min(0.6 + bestOverlap * 0.05, 0.85) };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
422
524
|
// Default fallback
|
|
423
525
|
return { agents: ['coder', 'researcher', 'tester'], confidence: 0.7 };
|
|
424
526
|
}
|
|
@@ -674,13 +776,16 @@ export const hooksRoute = {
|
|
|
674
776
|
routingMethod = 'semantic-native';
|
|
675
777
|
backendInfo = 'native VectorDb (HNSW)';
|
|
676
778
|
// Convert results to semantic format
|
|
779
|
+
const mergedPatterns = getMergedTaskPatterns();
|
|
677
780
|
semanticResult = results.map((r) => {
|
|
678
781
|
const [patternName] = r.id.split(':');
|
|
679
|
-
const pattern =
|
|
782
|
+
const pattern = mergedPatterns[patternName];
|
|
680
783
|
return {
|
|
681
784
|
intent: patternName,
|
|
682
785
|
score: 1 - r.score, // Native uses distance (lower is better), convert to similarity
|
|
683
|
-
metadata: {
|
|
786
|
+
metadata: {
|
|
787
|
+
agents: pattern?.agents || (patternName.startsWith('learned-') ? [patternName.slice(8)] : ['coder']),
|
|
788
|
+
},
|
|
684
789
|
};
|
|
685
790
|
});
|
|
686
791
|
}
|
|
@@ -941,6 +1046,8 @@ export const hooksPostTask = {
|
|
|
941
1046
|
success: { type: 'boolean', description: 'Whether task was successful' },
|
|
942
1047
|
agent: { type: 'string', description: 'Agent that completed the task' },
|
|
943
1048
|
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
1049
|
+
task: { type: 'string', description: 'Task description text (used for learning keyword extraction)' },
|
|
1050
|
+
storeDecisions: { type: 'boolean', description: 'Also store routing decision in memory DB' },
|
|
944
1051
|
},
|
|
945
1052
|
required: ['taskId'],
|
|
946
1053
|
},
|
|
@@ -979,6 +1086,41 @@ export const hooksPostTask = {
|
|
|
979
1086
|
catch {
|
|
980
1087
|
// Non-fatal
|
|
981
1088
|
}
|
|
1089
|
+
// Persist routing outcome for runtime learning (file-based, always reliable)
|
|
1090
|
+
const taskText = params.task || '';
|
|
1091
|
+
const outcomeKeywords = extractKeywords(taskText);
|
|
1092
|
+
let outcomePersisted = false;
|
|
1093
|
+
if (taskText && agent && agent.length <= 100 && /^[a-zA-Z0-9_-]+$/.test(agent)) {
|
|
1094
|
+
try {
|
|
1095
|
+
const outcomes = loadRoutingOutcomes();
|
|
1096
|
+
outcomes.push({
|
|
1097
|
+
task: taskText,
|
|
1098
|
+
agent,
|
|
1099
|
+
success,
|
|
1100
|
+
quality,
|
|
1101
|
+
keywords: outcomeKeywords,
|
|
1102
|
+
timestamp: new Date().toISOString(),
|
|
1103
|
+
});
|
|
1104
|
+
saveRoutingOutcomes(outcomes);
|
|
1105
|
+
outcomePersisted = true;
|
|
1106
|
+
}
|
|
1107
|
+
catch { /* non-critical */ }
|
|
1108
|
+
}
|
|
1109
|
+
// Optionally store in memory DB for cross-session vector retrieval
|
|
1110
|
+
if (params.storeDecisions && taskText && agent) {
|
|
1111
|
+
try {
|
|
1112
|
+
const storeFn = await getRealStoreFunction();
|
|
1113
|
+
if (storeFn) {
|
|
1114
|
+
await storeFn({
|
|
1115
|
+
key: `routing-decision:${taskId}`,
|
|
1116
|
+
namespace: 'patterns',
|
|
1117
|
+
value: JSON.stringify({ task: taskText, agent, success, quality, keywords: outcomeKeywords }),
|
|
1118
|
+
tags: ['routing-decision'],
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
catch { /* non-critical */ }
|
|
1123
|
+
}
|
|
982
1124
|
const duration = Date.now() - startTime;
|
|
983
1125
|
return {
|
|
984
1126
|
taskId,
|
|
@@ -989,6 +1131,7 @@ export const hooksPostTask = {
|
|
|
989
1131
|
newPatterns: success ? 1 : 0,
|
|
990
1132
|
trajectoryId: `traj-${Date.now()}`,
|
|
991
1133
|
controller: feedbackResult?.controller || 'none',
|
|
1134
|
+
outcomePersisted,
|
|
992
1135
|
},
|
|
993
1136
|
quality,
|
|
994
1137
|
feedback: feedbackResult ? {
|