claude-flow 3.47.1 → 3.49.0
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/.proven-config-version +1 -0
- package/.claude/helpers/auto-memory-hook.mjs +6 -2
- package/.claude/helpers/hook-handler.cjs +7 -4
- package/.claude/helpers/learning-service.mjs +4 -2
- package/.claude/helpers/memory.cjs +1 -1
- package/.claude/helpers/metrics-db.mjs +4 -2
- package/.claude/helpers/router.cjs +1 -1
- package/.claude/helpers/session.cjs +1 -1
- package/.claude/proven-config.json +42 -0
- package/.claude-plugin/marketplace.json +16 -1
- package/README.md +1 -53
- package/README.zh-CN.md +1 -53
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +6 -3
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +2 -3
- package/package.json +7 -7
- package/v3/@claude-flow/cli/README.md +1 -53
- package/v3/@claude-flow/cli/bin/cli.js +4 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
- package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +69 -8
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +22 -9
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +119 -34
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
- package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
- package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
- package/v3/@claude-flow/cli/dist/src/commands/session.js +132 -25
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
- package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
- package/v3/@claude-flow/cli/dist/src/index.js +10 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +14 -12
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -7
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +226 -51
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +131 -40
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +270 -58
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +47 -34
- package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
- package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
- package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +27 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
- package/v3/@claude-flow/cli/package.json +9 -9
- package/v3/@claude-flow/guidance/package.json +5 -6
- package/v3/@claude-flow/shared/package.json +6 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { feedbackPatternsSchema, validateFeedbackPatterns } from '../memory/feedback-patterns.js';
|
|
1
2
|
/**
|
|
2
3
|
* Hooks MCP Tools
|
|
3
4
|
* Provides intelligent hooks functionality via MCP protocol
|
|
@@ -410,8 +411,75 @@ async function getLoRAAdapter() {
|
|
|
410
411
|
}
|
|
411
412
|
return loraAdapter;
|
|
412
413
|
}
|
|
413
|
-
//
|
|
414
|
+
// Cache pending trajectories; the existing memory store also persists checkpoints.
|
|
414
415
|
const activeTrajectories = new Map();
|
|
416
|
+
const dirtyTrajectories = new Set();
|
|
417
|
+
const trajectoryOperations = new Map();
|
|
418
|
+
/** Serialize read/modify/checkpoint operations for a trajectory in this process. */
|
|
419
|
+
async function withTrajectoryOperation(id, operation) {
|
|
420
|
+
const previous = trajectoryOperations.get(id) ?? Promise.resolve();
|
|
421
|
+
let release;
|
|
422
|
+
const gate = new Promise(resolve => { release = resolve; });
|
|
423
|
+
const current = previous.then(() => gate);
|
|
424
|
+
trajectoryOperations.set(id, current);
|
|
425
|
+
await previous;
|
|
426
|
+
try {
|
|
427
|
+
return await operation();
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
release();
|
|
431
|
+
if (trajectoryOperations.get(id) === current)
|
|
432
|
+
trajectoryOperations.delete(id);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async function persistPendingTrajectory(trajectory) {
|
|
436
|
+
dirtyTrajectories.add(trajectory.id);
|
|
437
|
+
try {
|
|
438
|
+
const store = await getRealStoreFunction();
|
|
439
|
+
const result = await store?.({
|
|
440
|
+
key: `trajectory-pending-${trajectory.id}`, value: JSON.stringify(trajectory),
|
|
441
|
+
namespace: 'trajectories', tags: [trajectory.agent, 'pending', 'sona-trajectory'], upsert: true,
|
|
442
|
+
});
|
|
443
|
+
if (result?.success === true)
|
|
444
|
+
dirtyTrajectories.delete(trajectory.id);
|
|
445
|
+
return result?.success === true;
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function loadPendingTrajectory(id) {
|
|
452
|
+
try {
|
|
453
|
+
const { getEntry } = await import('../memory/memory-initializer.js');
|
|
454
|
+
// A completed record is authoritative even if pending-record cleanup failed
|
|
455
|
+
// or another process still has an old cache entry.
|
|
456
|
+
const completed = await getEntry({ key: `trajectory-${id}`, namespace: 'trajectories' });
|
|
457
|
+
if (completed.success && completed.found) {
|
|
458
|
+
activeTrajectories.delete(id);
|
|
459
|
+
dirtyTrajectories.delete(id);
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
// Failed checkpoints must not erase already-acknowledged live steps.
|
|
463
|
+
// Clean caches still reload so another process's later checkpoint is seen.
|
|
464
|
+
if (dirtyTrajectories.has(id) && activeTrajectories.has(id))
|
|
465
|
+
return activeTrajectories.get(id);
|
|
466
|
+
const pending = await getEntry({ key: `trajectory-pending-${id}`, namespace: 'trajectories' });
|
|
467
|
+
if (pending.success && pending.found) {
|
|
468
|
+
const value = JSON.parse(pending.entry?.content ?? 'null');
|
|
469
|
+
if (!value || value.id !== id || typeof value.task !== 'string' || typeof value.agent !== 'string'
|
|
470
|
+
|| typeof value.startedAt !== 'string' || !Number.isFinite(Date.parse(value.startedAt))
|
|
471
|
+
|| !Array.isArray(value.steps) || !value.steps.every((step) => step
|
|
472
|
+
&& typeof step.action === 'string' && typeof step.result === 'string'
|
|
473
|
+
&& typeof step.quality === 'number' && Number.isFinite(step.quality)
|
|
474
|
+
&& typeof step.timestamp === 'string'))
|
|
475
|
+
return undefined;
|
|
476
|
+
activeTrajectories.set(id, value);
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch { /* store unavailable: retain same-process best-effort tracking */ }
|
|
481
|
+
return activeTrajectories.get(id);
|
|
482
|
+
}
|
|
415
483
|
const MEMORY_DIR = '.claude-flow/memory';
|
|
416
484
|
const MEMORY_FILE = 'store.json';
|
|
417
485
|
function getMemoryPath() {
|
|
@@ -654,12 +722,41 @@ const KEYWORD_MATCHERS = Object.entries(KEYWORD_PATTERNS).map(([keyword, result]
|
|
|
654
722
|
const body = /[\s/]/.test(keyword) ? escaped : `${escaped}(?:s|es|ing|ed)?`;
|
|
655
723
|
return { regex: new RegExp(`\\b${body}\\b`, 'i'), result };
|
|
656
724
|
});
|
|
725
|
+
/**
|
|
726
|
+
* Confidence for a task nothing matched (#3567). It must rank below every real
|
|
727
|
+
* signal: keyword hits are >= 0.8, learned outcomes >= 0.7, and a semantic
|
|
728
|
+
* match is only eligible above 0.4. Matches the helper router's 0.3 fall-through.
|
|
729
|
+
*/
|
|
730
|
+
export const NO_MATCH_CONFIDENCE = 0.3;
|
|
731
|
+
/** True when the task has at least one letter or digit in any script. */
|
|
732
|
+
export function hasRoutableText(task) {
|
|
733
|
+
return /[\p{L}\p{N}]/u.test(task);
|
|
734
|
+
}
|
|
735
|
+
// Keyword matchers are English regexes; letters outside Latin script can never hit them.
|
|
736
|
+
const NON_LATIN_LETTER = /(?![A-Za-zÀ-ɏ])\p{L}/u;
|
|
737
|
+
function noMatchNote(task) {
|
|
738
|
+
if (!hasRoutableText(task))
|
|
739
|
+
return 'Task has no words to match.';
|
|
740
|
+
if (NON_LATIN_LETTER.test(task)) {
|
|
741
|
+
return 'Keyword matchers are English-only, so non-English text cannot match them.';
|
|
742
|
+
}
|
|
743
|
+
return 'No keyword or learned pattern matched.';
|
|
744
|
+
}
|
|
657
745
|
/** Exported for tests. */
|
|
658
746
|
export function suggestAgentsForTask(task) {
|
|
747
|
+
const noMatch = () => ({
|
|
748
|
+
agents: ['coder', 'researcher', 'tester'],
|
|
749
|
+
confidence: NO_MATCH_CONFIDENCE,
|
|
750
|
+
matched: false,
|
|
751
|
+
reason: 'no-match-default',
|
|
752
|
+
note: noMatchNote(task),
|
|
753
|
+
});
|
|
754
|
+
if (!hasRoutableText(task))
|
|
755
|
+
return noMatch();
|
|
659
756
|
// Check static keyword patterns first
|
|
660
757
|
for (const { regex, result } of KEYWORD_MATCHERS) {
|
|
661
758
|
if (regex.test(task)) {
|
|
662
|
-
return result;
|
|
759
|
+
return { ...result, matched: true };
|
|
663
760
|
}
|
|
664
761
|
}
|
|
665
762
|
// Check runtime-learned patterns from successful task outcomes
|
|
@@ -679,11 +776,10 @@ export function suggestAgentsForTask(task) {
|
|
|
679
776
|
}
|
|
680
777
|
// Require at least 2 keyword overlap to prevent false positives
|
|
681
778
|
if (bestAgent && bestOverlap >= 2) {
|
|
682
|
-
return { agents: [bestAgent], confidence: Math.min(0.6 + bestOverlap * 0.05, 0.85) };
|
|
779
|
+
return { agents: [bestAgent], confidence: Math.min(0.6 + bestOverlap * 0.05, 0.85), matched: true };
|
|
683
780
|
}
|
|
684
781
|
}
|
|
685
|
-
|
|
686
|
-
return { agents: ['coder', 'researcher', 'tester'], confidence: 0.7 };
|
|
782
|
+
return noMatch();
|
|
687
783
|
}
|
|
688
784
|
function assessCommandRisk(command) {
|
|
689
785
|
const warnings = [];
|
|
@@ -897,13 +993,19 @@ export const hooksPostCommand = {
|
|
|
897
993
|
properties: {
|
|
898
994
|
command: { type: 'string', description: 'Executed command' },
|
|
899
995
|
exitCode: { type: 'number', description: 'Command exit code' },
|
|
996
|
+
success: { type: 'boolean', description: 'Explicit execution outcome; false records a failure even without a nonzero exit code' },
|
|
997
|
+
ttl: { type: 'integer', minimum: 1, maximum: 2147483647, description: 'Command history lifetime in seconds (default: 30 days)' },
|
|
900
998
|
},
|
|
901
999
|
required: ['command'],
|
|
902
1000
|
},
|
|
903
1001
|
handler: async (params) => {
|
|
904
1002
|
const command = params.command;
|
|
905
1003
|
const exitCode = params.exitCode || 0;
|
|
906
|
-
const success = exitCode === 0;
|
|
1004
|
+
const success = exitCode === 0 && params.success !== false;
|
|
1005
|
+
const ttl = params.ttl ?? 30 * 24 * 60 * 60;
|
|
1006
|
+
if (typeof ttl !== 'number' || !Number.isSafeInteger(ttl) || ttl < 1 || ttl > 2147483647) {
|
|
1007
|
+
return { success: false, recorded: false, error: 'ttl must be a positive integer number of seconds (at most 2147483647)' };
|
|
1008
|
+
}
|
|
907
1009
|
{
|
|
908
1010
|
const v = validateText(command, 'command');
|
|
909
1011
|
if (!v.valid)
|
|
@@ -916,21 +1018,35 @@ export const hooksPostCommand = {
|
|
|
916
1018
|
let _storedIn = 'none';
|
|
917
1019
|
try {
|
|
918
1020
|
const bridge = await import('../memory/memory-bridge.js');
|
|
919
|
-
await bridge.bridgeStoreEntry({
|
|
1021
|
+
const stored = await bridge.bridgeStoreEntry({
|
|
920
1022
|
key: `cmd-${Date.now()}`,
|
|
921
1023
|
value: JSON.stringify({ command, exitCode, success }),
|
|
922
1024
|
namespace: 'commands',
|
|
923
1025
|
tags: [success ? 'success' : 'error'],
|
|
1026
|
+
ttl,
|
|
1027
|
+
generateEmbeddingFlag: false,
|
|
924
1028
|
});
|
|
1029
|
+
if (!stored?.success)
|
|
1030
|
+
throw new Error(stored?.error || 'Native bridge did not store the command');
|
|
925
1031
|
_storedIn = 'agentdb';
|
|
926
1032
|
}
|
|
927
1033
|
catch {
|
|
928
1034
|
// AgentDB not available — store in JSON
|
|
929
1035
|
try {
|
|
930
1036
|
const store = loadMemoryStore();
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
1037
|
+
const now = Date.now();
|
|
1038
|
+
for (const [entryKey, entry] of Object.entries(store.entries)) {
|
|
1039
|
+
if (entry.namespace === 'commands' && entry.expiresAt && Date.parse(entry.expiresAt) <= now) {
|
|
1040
|
+
delete store.entries[entryKey];
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const key = `cmd-${now}`;
|
|
1044
|
+
store.entries[key] = {
|
|
1045
|
+
key, value: JSON.stringify({ command, exitCode, success }), namespace: 'commands',
|
|
1046
|
+
storedAt: new Date(now).toISOString(), lastAccessed: new Date(now).toISOString(), accessCount: 0,
|
|
1047
|
+
expiresAt: new Date(now + ttl * 1000).toISOString(),
|
|
1048
|
+
};
|
|
1049
|
+
const memDir = dirname(getMemoryPath());
|
|
934
1050
|
if (!existsSync(memDir))
|
|
935
1051
|
mkdirSync(memDir, { recursive: true });
|
|
936
1052
|
writeFileSync(getMemoryPath(), JSON.stringify(store, null, 2), 'utf-8');
|
|
@@ -1117,6 +1233,7 @@ async function routeTaskLocal(task, context, useSemanticRouter, embedderOverride
|
|
|
1117
1233
|
let agents;
|
|
1118
1234
|
let confidence;
|
|
1119
1235
|
let matchedPattern = '';
|
|
1236
|
+
let noMatchDetail = null;
|
|
1120
1237
|
// Both static and learned patterns are gated on the same similarity
|
|
1121
1238
|
// score. Learned patterns additionally require support/reliability as a
|
|
1122
1239
|
// quality guard, but do NOT need a higher score bar — a learned pattern
|
|
@@ -1124,7 +1241,9 @@ async function routeTaskLocal(task, context, useSemanticRouter, embedderOverride
|
|
|
1124
1241
|
// (#2864: a 25pp higher threshold made a top-scoring learned-researcher
|
|
1125
1242
|
// match at 0.57 lose to a static match at 0.52, discarding the learned
|
|
1126
1243
|
// store's output on the majority of routes).
|
|
1127
|
-
|
|
1244
|
+
// A task with no letters or digits has nothing for the embedder to mean;
|
|
1245
|
+
// any similarity it scores is noise, so it never becomes a semantic match.
|
|
1246
|
+
const eligibleSemantic = !hasRoutableText(task) ? undefined : semanticResult.find((match) => {
|
|
1128
1247
|
if (match.score <= 0.4)
|
|
1129
1248
|
return false;
|
|
1130
1249
|
const learned = match.intent.startsWith('learned-') || match.metadata.source === 'learned';
|
|
@@ -1144,9 +1263,16 @@ async function routeTaskLocal(task, context, useSemanticRouter, embedderOverride
|
|
|
1144
1263
|
const suggestion = suggestAgentsForTask(task);
|
|
1145
1264
|
agents = suggestion.agents;
|
|
1146
1265
|
confidence = suggestion.confidence;
|
|
1147
|
-
matchedPattern = 'keyword-fallback';
|
|
1148
1266
|
routingMethod = 'keyword';
|
|
1149
|
-
|
|
1267
|
+
if (suggestion.matched) {
|
|
1268
|
+
matchedPattern = 'keyword-fallback';
|
|
1269
|
+
backendInfo = 'keyword matching';
|
|
1270
|
+
}
|
|
1271
|
+
else {
|
|
1272
|
+
matchedPattern = 'no-match-default';
|
|
1273
|
+
backendInfo = 'keyword matching (no match)';
|
|
1274
|
+
noMatchDetail = suggestion.note ?? 'No pattern matched.';
|
|
1275
|
+
}
|
|
1150
1276
|
}
|
|
1151
1277
|
// Determine complexity
|
|
1152
1278
|
const taskLower = task.toLowerCase();
|
|
@@ -1164,6 +1290,9 @@ async function routeTaskLocal(task, context, useSemanticRouter, embedderOverride
|
|
|
1164
1290
|
throughput: routingLatencyMs > 0 ? `${Math.round(1000 / routingLatencyMs)} routes/s` : 'N/A',
|
|
1165
1291
|
},
|
|
1166
1292
|
matchedPattern,
|
|
1293
|
+
// #3567: callers branch on `matched`; a no-match result is a default, not a decision.
|
|
1294
|
+
matched: noMatchDetail === null,
|
|
1295
|
+
...(noMatchDetail !== null ? { reason: 'no-match-default', note: noMatchDetail } : {}),
|
|
1167
1296
|
semanticMatches: semanticResult.slice(0, 3).map(r => ({
|
|
1168
1297
|
pattern: r.intent,
|
|
1169
1298
|
score: Math.round(r.score * 100) / 100,
|
|
@@ -1173,15 +1302,18 @@ async function routeTaskLocal(task, context, useSemanticRouter, embedderOverride
|
|
|
1173
1302
|
confidence: Math.round(confidence * 100) / 100,
|
|
1174
1303
|
reason: routingMethod.startsWith('semantic')
|
|
1175
1304
|
? `Semantic similarity to "${matchedPattern}" pattern (${Math.round(confidence * 100)}%)`
|
|
1176
|
-
:
|
|
1305
|
+
: noMatchDetail !== null
|
|
1306
|
+
? `Nothing matched; default suggestion only. ${noMatchDetail}`
|
|
1307
|
+
: `Task contains keywords matching ${agents[0]} specialization`,
|
|
1177
1308
|
},
|
|
1178
1309
|
alternativeAgents: agents.slice(1).map((agent, i) => ({
|
|
1179
1310
|
type: agent,
|
|
1180
|
-
confidence: Math.round((confidence - (0.1 * (i + 1))) * 100) / 100,
|
|
1181
|
-
reason: `Alternative agent for ${agent} capabilities`,
|
|
1311
|
+
confidence: Math.max(0, Math.round((confidence - (0.1 * (i + 1))) * 100) / 100),
|
|
1312
|
+
reason: noMatchDetail !== null ? 'Default suggestion (nothing matched)' : `Alternative agent for ${agent} capabilities`,
|
|
1182
1313
|
})),
|
|
1183
1314
|
estimatedMetrics: {
|
|
1184
|
-
|
|
1315
|
+
// No pattern means no basis for a success estimate.
|
|
1316
|
+
successProbability: noMatchDetail !== null ? null : Math.round(confidence * 100) / 100,
|
|
1185
1317
|
estimatedDuration: complexity === 'high' ? '2-4 hours' : complexity === 'medium' ? '30-60 min' : '10-30 min',
|
|
1186
1318
|
complexity,
|
|
1187
1319
|
},
|
|
@@ -1418,12 +1550,15 @@ export const hooksPreTask = {
|
|
|
1418
1550
|
return {
|
|
1419
1551
|
taskId,
|
|
1420
1552
|
description,
|
|
1553
|
+
agentsMatched: suggestion.matched,
|
|
1421
1554
|
suggestedAgents: suggestion.agents.map((agent, i) => ({
|
|
1422
1555
|
type: agent,
|
|
1423
1556
|
confidence: suggestion.confidence - (0.05 * i),
|
|
1424
|
-
reason:
|
|
1425
|
-
?
|
|
1426
|
-
:
|
|
1557
|
+
reason: !suggestion.matched
|
|
1558
|
+
? 'Default suggestion (nothing matched)'
|
|
1559
|
+
: i === 0
|
|
1560
|
+
? `Primary agent for ${agent} tasks based on learned patterns`
|
|
1561
|
+
: `Alternative agent with ${agent} capabilities`,
|
|
1427
1562
|
})),
|
|
1428
1563
|
complexity,
|
|
1429
1564
|
estimatedDuration: complexity === 'high' ? '2-4 hours' : complexity === 'medium' ? '30-60 min' : '10-30 min',
|
|
@@ -1444,9 +1579,10 @@ export const hooksPostTask = {
|
|
|
1444
1579
|
type: 'object',
|
|
1445
1580
|
properties: {
|
|
1446
1581
|
taskId: { type: 'string', description: 'Task identifier' },
|
|
1582
|
+
patterns: feedbackPatternsSchema,
|
|
1447
1583
|
success: { type: 'boolean', description: 'Whether task was successful' },
|
|
1448
1584
|
agent: { type: 'string', description: 'Agent that completed the task' },
|
|
1449
|
-
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
1585
|
+
quality: { type: 'number', minimum: 0, maximum: 1, description: 'Quality score (0-1)' },
|
|
1450
1586
|
task: { type: 'string', description: 'Task description text (used for learning keyword extraction)' },
|
|
1451
1587
|
duration: { type: 'number', description: 'Observed task duration in milliseconds (used by pheromone-adaptive topology)' },
|
|
1452
1588
|
latencyBudgetMs: { type: 'number', description: 'Latency budget used to normalize duration (default 60000ms)' },
|
|
@@ -1463,8 +1599,19 @@ export const hooksPostTask = {
|
|
|
1463
1599
|
const taskId = params.taskId;
|
|
1464
1600
|
const success = params.success !== false;
|
|
1465
1601
|
const agent = params.agent;
|
|
1466
|
-
|
|
1602
|
+
if (params.quality !== undefined && (typeof params.quality !== 'number'
|
|
1603
|
+
|| !Number.isFinite(params.quality) || params.quality < 0 || params.quality > 1)) {
|
|
1604
|
+
return { success: false, error: 'quality must be a finite number between 0 and 1' };
|
|
1605
|
+
}
|
|
1606
|
+
const quality = params.quality === undefined ? (success ? 0.85 : 0.3) : params.quality;
|
|
1467
1607
|
const startTime = Date.now();
|
|
1608
|
+
let patterns;
|
|
1609
|
+
try {
|
|
1610
|
+
patterns = validateFeedbackPatterns(params.patterns);
|
|
1611
|
+
}
|
|
1612
|
+
catch (error) {
|
|
1613
|
+
return { success: false, error: error.message };
|
|
1614
|
+
}
|
|
1468
1615
|
{
|
|
1469
1616
|
const v = validateIdentifier(taskId, 'taskId');
|
|
1470
1617
|
if (!v.valid)
|
|
@@ -1502,7 +1649,7 @@ export const hooksPostTask = {
|
|
|
1502
1649
|
quality,
|
|
1503
1650
|
agent,
|
|
1504
1651
|
duration: params.duration || undefined,
|
|
1505
|
-
patterns
|
|
1652
|
+
patterns,
|
|
1506
1653
|
// ADR-147 P2: forward spawn-tree lineage so it lands in feedback + memory
|
|
1507
1654
|
parentAgentId,
|
|
1508
1655
|
depth,
|
|
@@ -1756,8 +1903,34 @@ export const hooksExplain = {
|
|
|
1756
1903
|
catch {
|
|
1757
1904
|
// File unreadable; leave as null
|
|
1758
1905
|
}
|
|
1906
|
+
// #3567: when nothing matched, say so instead of explaining a match that did not happen.
|
|
1907
|
+
if (!suggestion.matched) {
|
|
1908
|
+
return {
|
|
1909
|
+
task,
|
|
1910
|
+
matched: false,
|
|
1911
|
+
reason: 'no-match-default',
|
|
1912
|
+
explanation: `No keyword or learned pattern matched this task. ${suggestion.note ?? ''} ` +
|
|
1913
|
+
`"${suggestion.agents[0]}" is a default suggestion, not a routing decision.`.trim(),
|
|
1914
|
+
factors: [
|
|
1915
|
+
{ factor: 'Keyword Match', weight: 0.4, value: null, impact: 'No keyword matched' },
|
|
1916
|
+
{ factor: 'Historical Success', weight: 0.3, value: historicalSuccess, impact: historicalNote },
|
|
1917
|
+
{ factor: 'Agent Availability', weight: 0.2, value: null, impact: 'Agent availability tracking not implemented' },
|
|
1918
|
+
{ factor: 'Task Complexity', weight: 0.1, value: task.length > 100 ? 0.8 : 0.3, impact: 'Complexity assessment' },
|
|
1919
|
+
],
|
|
1920
|
+
patterns: matchedPatterns,
|
|
1921
|
+
decision: {
|
|
1922
|
+
agent: suggestion.agents[0],
|
|
1923
|
+
confidence: suggestion.confidence,
|
|
1924
|
+
reasoning: [
|
|
1925
|
+
'No pattern matched; the agent is a default suggestion',
|
|
1926
|
+
`Confidence ${(suggestion.confidence * 100).toFixed(0)}% is below every real match`,
|
|
1927
|
+
],
|
|
1928
|
+
},
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1759
1931
|
return {
|
|
1760
1932
|
task,
|
|
1933
|
+
matched: true,
|
|
1761
1934
|
explanation: `The routing decision was made based on keyword analysis of the task description. ` +
|
|
1762
1935
|
`The task contains keywords that match the "${suggestion.agents[0]}" specialization with ${(suggestion.confidence * 100).toFixed(0)}% confidence.`,
|
|
1763
1936
|
factors: [
|
|
@@ -2754,9 +2927,8 @@ export const hooksIntelligenceReset = {
|
|
|
2754
2927
|
// Directory read failed
|
|
2755
2928
|
}
|
|
2756
2929
|
}
|
|
2757
|
-
//
|
|
2758
|
-
cleared.trajectories =
|
|
2759
|
-
activeTrajectories.clear();
|
|
2930
|
+
// Reset learned state without abandoning tasks still recording outcomes.
|
|
2931
|
+
cleared.trajectories = 0;
|
|
2760
2932
|
return {
|
|
2761
2933
|
reset: true,
|
|
2762
2934
|
cleared,
|
|
@@ -2815,27 +2987,15 @@ export const hooksTrajectoryStart = {
|
|
|
2815
2987
|
getTrajectoryTree().openTrajectory({ sessionId, trajectoryId, task, agent });
|
|
2816
2988
|
}
|
|
2817
2989
|
catch { /* prototype path — never blocks trajectory recording */ }
|
|
2818
|
-
//
|
|
2819
|
-
const
|
|
2820
|
-
if (storeFn) {
|
|
2821
|
-
try {
|
|
2822
|
-
await storeFn({
|
|
2823
|
-
key: `trajectory-pending-${trajectoryId}`,
|
|
2824
|
-
value: JSON.stringify(trajectory),
|
|
2825
|
-
namespace: 'trajectories',
|
|
2826
|
-
tags: [agent, 'pending', 'sona-trajectory'],
|
|
2827
|
-
});
|
|
2828
|
-
}
|
|
2829
|
-
catch {
|
|
2830
|
-
// Best-effort persistence — trajectory still lives in-memory
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2990
|
+
// Checkpoint before handing the id to another process.
|
|
2991
|
+
const persisted = await persistPendingTrajectory(trajectory);
|
|
2833
2992
|
return {
|
|
2834
2993
|
trajectoryId,
|
|
2835
2994
|
task,
|
|
2836
2995
|
agent,
|
|
2837
2996
|
started: startedAt,
|
|
2838
2997
|
status: 'recording',
|
|
2998
|
+
persisted,
|
|
2839
2999
|
implementation: 'real-trajectory-tracking',
|
|
2840
3000
|
activeCount: activeTrajectories.size,
|
|
2841
3001
|
};
|
|
@@ -2850,17 +3010,21 @@ export const hooksTrajectoryStep = {
|
|
|
2850
3010
|
trajectoryId: { type: 'string', description: 'Trajectory ID' },
|
|
2851
3011
|
action: { type: 'string', description: 'Action taken' },
|
|
2852
3012
|
result: { type: 'string', description: 'Action result' },
|
|
2853
|
-
quality: { type: 'number', description: 'Quality score (0-1)' },
|
|
3013
|
+
quality: { type: 'number', minimum: 0, maximum: 1, description: 'Quality score (0-1)' },
|
|
2854
3014
|
},
|
|
2855
3015
|
required: ['trajectoryId', 'action'],
|
|
2856
3016
|
},
|
|
2857
|
-
handler: async (params) => {
|
|
3017
|
+
handler: async (params) => withTrajectoryOperation(params.trajectoryId, async () => {
|
|
2858
3018
|
const trajectoryId = params.trajectoryId;
|
|
2859
3019
|
// #14: scrub extended-thinking blocks so reasoning tokens don't contaminate
|
|
2860
3020
|
// the learning signal (DISTILL embeds this text).
|
|
2861
3021
|
const action = scrubReasoningBlocks(params.action);
|
|
2862
3022
|
const result = scrubReasoningBlocks(params.result || 'success');
|
|
2863
|
-
|
|
3023
|
+
if (params.quality !== undefined && (typeof params.quality !== 'number'
|
|
3024
|
+
|| !Number.isFinite(params.quality) || params.quality < 0 || params.quality > 1)) {
|
|
3025
|
+
return { success: false, error: 'quality must be a finite number between 0 and 1' };
|
|
3026
|
+
}
|
|
3027
|
+
const quality = params.quality === undefined ? 0.85 : params.quality;
|
|
2864
3028
|
const timestamp = new Date().toISOString();
|
|
2865
3029
|
const stepId = `step-${Date.now()}`;
|
|
2866
3030
|
{
|
|
@@ -2874,7 +3038,7 @@ export const hooksTrajectoryStep = {
|
|
|
2874
3038
|
return { success: false, error: v.error };
|
|
2875
3039
|
}
|
|
2876
3040
|
// Add step to real trajectory if it exists
|
|
2877
|
-
const trajectory =
|
|
3041
|
+
const trajectory = await loadPendingTrajectory(trajectoryId);
|
|
2878
3042
|
if (trajectory) {
|
|
2879
3043
|
trajectory.steps.push({
|
|
2880
3044
|
action,
|
|
@@ -2883,6 +3047,7 @@ export const hooksTrajectoryStep = {
|
|
|
2883
3047
|
timestamp,
|
|
2884
3048
|
});
|
|
2885
3049
|
}
|
|
3050
|
+
const persisted = trajectory ? await persistPendingTrajectory(trajectory) : false;
|
|
2886
3051
|
// MAGE-style execution-state-tree mirror (prototype, non-fatal)
|
|
2887
3052
|
try {
|
|
2888
3053
|
const { getTrajectoryTree } = await import('../ruvector/trajectory-tree.js');
|
|
@@ -2914,11 +3079,12 @@ export const hooksTrajectoryStep = {
|
|
|
2914
3079
|
result,
|
|
2915
3080
|
quality,
|
|
2916
3081
|
recorded: !!trajectory,
|
|
3082
|
+
persisted,
|
|
2917
3083
|
timestamp,
|
|
2918
3084
|
totalSteps: trajectory?.steps.length || 0,
|
|
2919
3085
|
implementation: trajectory ? 'real-step-recording' : 'trajectory-not-found',
|
|
2920
3086
|
};
|
|
2921
|
-
},
|
|
3087
|
+
}),
|
|
2922
3088
|
};
|
|
2923
3089
|
export const hooksTrajectoryEnd = {
|
|
2924
3090
|
name: 'hooks_intelligence_trajectory-end',
|
|
@@ -2932,7 +3098,7 @@ export const hooksTrajectoryEnd = {
|
|
|
2932
3098
|
},
|
|
2933
3099
|
required: ['trajectoryId'],
|
|
2934
3100
|
},
|
|
2935
|
-
handler: async (params) => {
|
|
3101
|
+
handler: async (params) => withTrajectoryOperation(params.trajectoryId, async () => {
|
|
2936
3102
|
const trajectoryId = params.trajectoryId;
|
|
2937
3103
|
{
|
|
2938
3104
|
const v = validateIdentifier(trajectoryId, 'trajectoryId');
|
|
@@ -2944,7 +3110,7 @@ export const hooksTrajectoryEnd = {
|
|
|
2944
3110
|
const endedAt = new Date().toISOString();
|
|
2945
3111
|
const startTime = Date.now();
|
|
2946
3112
|
// Get and finalize real trajectory
|
|
2947
|
-
const trajectory =
|
|
3113
|
+
const trajectory = await loadPendingTrajectory(trajectoryId);
|
|
2948
3114
|
let persistResult = { success: false };
|
|
2949
3115
|
if (trajectory) {
|
|
2950
3116
|
trajectory.success = success;
|
|
@@ -2970,8 +3136,17 @@ export const hooksTrajectoryEnd = {
|
|
|
2970
3136
|
persistResult = { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
2971
3137
|
}
|
|
2972
3138
|
}
|
|
2973
|
-
//
|
|
2974
|
-
|
|
3139
|
+
// Failed writes remain retryable. A completed row prevents stale pending
|
|
3140
|
+
// data from being replayed even if cleanup itself fails.
|
|
3141
|
+
if (persistResult.success) {
|
|
3142
|
+
activeTrajectories.delete(trajectoryId);
|
|
3143
|
+
dirtyTrajectories.delete(trajectoryId);
|
|
3144
|
+
try {
|
|
3145
|
+
const { deleteEntry } = await import('../memory/memory-initializer.js');
|
|
3146
|
+
await deleteEntry({ key: `trajectory-pending-${trajectoryId}`, namespace: 'trajectories' });
|
|
3147
|
+
}
|
|
3148
|
+
catch { /* completed record is the replay guard */ }
|
|
3149
|
+
}
|
|
2975
3150
|
}
|
|
2976
3151
|
// MAGE-style execution-state-tree mirror (prototype, non-fatal)
|
|
2977
3152
|
try {
|
|
@@ -3187,7 +3362,7 @@ export const hooksTrajectoryEnd = {
|
|
|
3187
3362
|
? `SONA learned pattern "${sonaResult.patternKey}" with ${(sonaResult.confidence * 100).toFixed(1)}% confidence`
|
|
3188
3363
|
: (persistResult.success ? 'Trajectory persisted for future learning' : (persistResult.error || 'Trajectory not found')),
|
|
3189
3364
|
};
|
|
3190
|
-
},
|
|
3365
|
+
}),
|
|
3191
3366
|
};
|
|
3192
3367
|
// Pattern store/search hooks - REAL implementation using storeEntry
|
|
3193
3368
|
export const hooksPatternStore = {
|
|
@@ -10,5 +10,12 @@
|
|
|
10
10
|
* @module v3/cli/mcp-tools/memory-tools
|
|
11
11
|
*/
|
|
12
12
|
import type { MCPTool } from './types.js';
|
|
13
|
+
export declare const DANGEROUS_KEY_PATTERN: RegExp;
|
|
14
|
+
/**
|
|
15
|
+
* #3570: the one key rule for every memory write path (MCP store, CLI store,
|
|
16
|
+
* import). Plain `/` stays legal (`probe/x`); traversal and shell metacharacters
|
|
17
|
+
* do not. Returns the error message, or null when the key is acceptable.
|
|
18
|
+
*/
|
|
19
|
+
export declare function memoryKeyError(key: string): string | null;
|
|
13
20
|
export declare const memoryTools: MCPTool[];
|
|
14
21
|
//# sourceMappingURL=memory-tools.d.ts.map
|