claude-flow 3.6.13 → 3.6.15

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.
@@ -0,0 +1 @@
1
+ {"sessionId":"36428a63-dfb2-42a4-a159-cf8be916193e","pid":86429,"procStart":"Sun May 3 19:17:47 2026","acquiredAt":1777840775529}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.6.13",
3
+ "version": "3.6.15",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -70,7 +70,18 @@
70
70
  },
71
71
  "overrides": {
72
72
  "hono": ">=4.11.4",
73
- "@ruvector/rvf-wasm": "0.1.5"
73
+ "@ruvector/rvf-wasm": "0.1.5",
74
+ "@hono/node-server": ">=1.19.10",
75
+ "flatted": ">=3.4.0",
76
+ "tar": ">=7.5.0",
77
+ "picomatch": ">=4.0.3",
78
+ "path-to-regexp": ">=8.2.1",
79
+ "undici": ">=7.18.0",
80
+ "minimatch": ">=10.0.0",
81
+ "@isaacs/brace-expansion": ">=5.0.1",
82
+ "cacache": ">=20.0.0",
83
+ "make-fetch-happen": ">=15.0.0",
84
+ "express-rate-limit": ">=8.4.1"
74
85
  },
75
86
  "devDependencies": {
76
87
  "@openai/codex": "^0.98.0",
@@ -121,13 +121,43 @@ export const agentdbPatternStore = {
121
121
  const pattern = validateString(params.pattern, 'pattern');
122
122
  if (!pattern)
123
123
  return { success: false, error: 'pattern is required (non-empty string, max 100KB)' };
124
+ const type = validateString(params.type, 'type', 200) ?? 'general';
125
+ const confidence = validateScore(params.confidence, 0.8);
124
126
  const bridge = await getBridge();
125
- const result = await bridge.bridgeStorePattern({
126
- pattern,
127
- type: validateString(params.type, 'type', 200) ?? 'general',
128
- confidence: validateScore(params.confidence, 0.8),
129
- });
130
- return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
127
+ const result = await bridge.bridgeStorePattern({ pattern, type, confidence });
128
+ if (result)
129
+ return result;
130
+ // ADR-093 F4: when the ReasoningBank controller registry returns
131
+ // null (the cause of audit-reported "AgentDB bridge not available"
132
+ // even though `agentdb_health.reasoningBank.enabled === true`), fall
133
+ // back to a direct memory_store write so the caller's pattern still
134
+ // persists. Surface the controller as `memory-store-fallback` so the
135
+ // path is observable instead of silently lost.
136
+ try {
137
+ const { storeEntry } = await import('../memory/memory-initializer.js');
138
+ const patternId = `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
139
+ const value = JSON.stringify({ pattern, type, confidence, _fallback: 'reasoningBank-unavailable' });
140
+ await storeEntry({
141
+ key: patternId,
142
+ value,
143
+ namespace: 'pattern',
144
+ tags: [type, 'reasoning-pattern', 'fallback'],
145
+ });
146
+ return {
147
+ success: true,
148
+ patternId,
149
+ controller: 'memory-store-fallback',
150
+ note: 'ReasoningBank controller registry unavailable. Pattern persisted via memory_store. Run `agentdb_health` to inspect controller registration.',
151
+ };
152
+ }
153
+ catch (fallbackErr) {
154
+ return {
155
+ success: false,
156
+ error: 'Pattern store failed: both ReasoningBank bridge and memory_store fallback unavailable',
157
+ fallbackError: sanitizeError(fallbackErr),
158
+ recommendation: 'Run agentdb_health to inspect controller registration and check that .swarm/memory.db is writable.',
159
+ };
160
+ }
131
161
  }
132
162
  catch (error) {
133
163
  return { success: false, error: sanitizeError(error) };
@@ -224,30 +224,35 @@ export const configTools = [
224
224
  const scope = input.scope || 'default';
225
225
  const prefix = input.prefix;
226
226
  const includeDefaults = input.includeDefaults !== false;
227
- // Merge stored values with defaults
228
- let configs = {};
227
+ const merged = new Map();
229
228
  if (includeDefaults) {
230
- configs = { ...DEFAULT_CONFIG };
229
+ for (const [key, value] of Object.entries(DEFAULT_CONFIG)) {
230
+ merged.set(key, { value, source: 'default' });
231
+ }
231
232
  }
232
- // Add stored values
233
- Object.assign(configs, store.values);
234
- // Add scope-specific values
235
- if (scope !== 'default' && store.scopes[scope]) {
236
- Object.assign(configs, store.scopes[scope]);
233
+ for (const [key, value] of Object.entries(store.values)) {
234
+ merged.set(key, { value, source: 'stored' });
235
+ }
236
+ // Always include keys from every scope so they're discoverable; the
237
+ // scope filter only narrows which set is used as the *winner*.
238
+ for (const [scopeName, scopeValues] of Object.entries(store.scopes)) {
239
+ for (const [key, value] of Object.entries(scopeValues)) {
240
+ if (scope === scopeName || scope === 'default') {
241
+ merged.set(key, { value, source: `scope:${scopeName}` });
242
+ }
243
+ else if (!merged.has(key)) {
244
+ // Surface scoped keys that aren't shadowed when listing default scope
245
+ merged.set(key, { value, source: `scope:${scopeName}` });
246
+ }
247
+ }
237
248
  }
238
- // Filter by prefix
239
- let entries = Object.entries(configs);
249
+ let entries = Array.from(merged.entries());
240
250
  if (prefix) {
241
251
  entries = entries.filter(([key]) => key.startsWith(prefix));
242
252
  }
243
- // Sort by key
244
253
  entries.sort(([a], [b]) => a.localeCompare(b));
245
254
  return {
246
- configs: entries.map(([key, value]) => ({
247
- key,
248
- value,
249
- source: store.values[key] !== undefined ? 'stored' : 'default',
250
- })),
255
+ configs: entries.map(([key, { value, source }]) => ({ key, value, source })),
251
256
  total: entries.length,
252
257
  scope,
253
258
  updatedAt: store.updatedAt,
@@ -634,15 +634,40 @@ export const coordinationTools = [
634
634
  const agents = input.agents || Object.keys(store.nodes);
635
635
  const strategy = input.strategy || 'parallel';
636
636
  const orchestrationId = `orch-${Date.now()}`;
637
+ // ADR-093 F7: this tool only schedules an orchestration record — it
638
+ // does not actually execute. Previously it returned a hardcoded
639
+ // `estimatedCompletion: "50ms"` which was misleading. Now we return
640
+ // an honest stub-status with a note pointing callers at agent_spawn
641
+ // / Task tool / hive-mind tools for real orchestration. Persist the
642
+ // record so callers can list/inspect what was scheduled.
643
+ const orchestration = {
644
+ id: orchestrationId,
645
+ task,
646
+ strategy,
647
+ agents,
648
+ status: 'scheduled',
649
+ scheduledAt: new Date().toISOString(),
650
+ topology: store.topology.type,
651
+ };
652
+ const orchStore = store;
653
+ if (!Array.isArray(orchStore.orchestrations))
654
+ orchStore.orchestrations = [];
655
+ orchStore.orchestrations.push(orchestration);
656
+ if (orchStore.orchestrations.length > 100) {
657
+ orchStore.orchestrations = orchStore.orchestrations.slice(-100);
658
+ }
659
+ saveCoordStore(orchStore);
637
660
  return {
638
661
  success: true,
639
662
  orchestrationId,
640
663
  task,
641
664
  strategy,
642
665
  agents,
643
- status: 'initiated',
666
+ status: 'scheduled',
644
667
  topology: store.topology.type,
645
- estimatedCompletion: `${agents.length * (strategy === 'sequential' ? 100 : 50)}ms`,
668
+ // Honest stub: no executor wired up yet. Don't lie about completion time.
669
+ executor: 'none',
670
+ _note: 'coordination_orchestrate currently records the orchestration request but does not execute it. For real multi-agent execution use agent_spawn + the Task tool, or hive-mind_spawn for queen-led coordination.',
646
671
  };
647
672
  },
648
673
  },
@@ -777,6 +777,24 @@ export const embeddingsTools = [
777
777
  message: 'Embeddings not initialized. Run embeddings/init first.',
778
778
  };
779
779
  }
780
+ // ADR-093 F5: distinguish "@ruvector/core installed" from "wired into
781
+ // the embedding pipeline". Previously this collapsed both into a
782
+ // single `ruvector: boolean` field, which gave callers no way to
783
+ // tell whether re-running embeddings_init would help (#1698 partial
784
+ // regression on the MCP boundary).
785
+ let ruvectorAvailable = false;
786
+ let ruvectorVersion;
787
+ try {
788
+ const mod = await import('@ruvector/core');
789
+ ruvectorAvailable = !!mod;
790
+ try {
791
+ // Best-effort: many packages expose a `version` constant
792
+ ruvectorVersion = mod.version;
793
+ }
794
+ catch { /* ignore */ }
795
+ }
796
+ catch { /* not installed */ }
797
+ const ruvectorEnabled = config.neural.ruvector?.enabled ?? false;
780
798
  return {
781
799
  success: true,
782
800
  initialized: true,
@@ -787,7 +805,16 @@ export const embeddingsTools = [
787
805
  hyperbolic: config.hyperbolic,
788
806
  neural: {
789
807
  enabled: config.neural.enabled,
790
- ruvector: config.neural.ruvector?.enabled ?? false,
808
+ // Backwards-compatible: keep the boolean view (truthy when wired).
809
+ ruvector: ruvectorEnabled,
810
+ // New shape — additive, non-breaking. Callers that need to
811
+ // distinguish "package is installed" from "feature wired in"
812
+ // read these instead of guessing from a single bool.
813
+ ruvectorStatus: {
814
+ available: ruvectorAvailable,
815
+ enabled: ruvectorEnabled,
816
+ version: ruvectorVersion,
817
+ },
791
818
  },
792
819
  },
793
820
  paths: {
@@ -218,6 +218,14 @@ export const hiveMindTools = [
218
218
  type: 'object',
219
219
  properties: {
220
220
  topology: { type: 'string', enum: ['mesh', 'hierarchical', 'ring', 'star'], description: 'Network topology' },
221
+ // ADR-093 F3: schema now exposes the consensus strategy so callers
222
+ // can actually request raft / byzantine / quorum / etc. Default
223
+ // matches the documented anti-drift posture (raft).
224
+ consensus: {
225
+ type: 'string',
226
+ enum: ['raft', 'byzantine', 'gossip', 'crdt', 'quorum'],
227
+ description: 'Consensus strategy. Default: raft (anti-drift). Use byzantine for f<n/3 fault tolerance.',
228
+ },
221
229
  queenId: { type: 'string', description: 'Initial queen agent ID' },
222
230
  },
223
231
  },
@@ -230,8 +238,10 @@ export const hiveMindTools = [
230
238
  const state = loadHiveState();
231
239
  const hiveId = `hive-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
232
240
  const queenId = input.queenId || `queen-${Date.now()}`;
241
+ const requestedConsensus = input.consensus || 'raft';
233
242
  state.initialized = true;
234
243
  state.topology = input.topology || 'mesh';
244
+ state.consensusStrategy = requestedConsensus;
235
245
  state.createdAt = new Date().toISOString();
236
246
  state.queen = {
237
247
  agentId: queenId,
@@ -243,12 +253,12 @@ export const hiveMindTools = [
243
253
  success: true,
244
254
  hiveId,
245
255
  topology: state.topology,
246
- consensus: input.consensus || 'byzantine',
256
+ consensus: state.consensusStrategy,
247
257
  queenId,
248
258
  status: 'initialized',
249
259
  config: {
250
260
  topology: state.topology,
251
- consensus: input.consensus || 'byzantine',
261
+ consensus: state.consensusStrategy,
252
262
  maxAgents: input.maxAgents || 15,
253
263
  persist: input.persist !== false,
254
264
  memoryBackend: input.memoryBackend || 'hybrid',
@@ -298,7 +308,8 @@ export const hiveMindTools = [
298
308
  hiveId: `hive-${state.createdAt ? new Date(state.createdAt).getTime() : Date.now()}`,
299
309
  status: state.initialized ? 'active' : 'offline',
300
310
  topology: state.topology,
301
- consensus: 'byzantine', // Default consensus type
311
+ // ADR-093 F3: surface the persisted strategy instead of a hardcoded "byzantine".
312
+ consensus: state.consensusStrategy ?? 'byzantine',
302
313
  queen: state.queen ? {
303
314
  id: state.queen.agentId,
304
315
  agentId: state.queen.agentId,
@@ -945,44 +945,55 @@ export const hooksMetrics = {
945
945
  },
946
946
  handler: async (params) => {
947
947
  const period = params.period || '24h';
948
- // Try to read real counts from memory store
949
- const store = loadMemoryStore();
950
- const entries = Object.values(store.entries);
951
- // Count patterns by looking at stored pattern entries
952
- const patternEntries = entries.filter(e => e.key.includes('pattern'));
953
- const routingEntries = entries.filter(e => e.key.includes('route') || e.key.includes('routing'));
954
- const taskEntries = entries.filter(e => e.key.includes('task'));
955
- if (entries.length === 0) {
956
- return {
957
- _real: true,
958
- _note: 'No metrics data collected yet. Data populates from hooks_post-task, hooks_post-edit, hooks_post-command, and hooks_route calls.',
959
- period,
960
- patterns: { total: 0, successful: 0, failed: 0, avgConfidence: null },
961
- agents: { routingAccuracy: null, totalRoutes: 0, topAgent: null },
962
- commands: { totalExecuted: 0, successRate: null, avgRiskScore: null },
963
- lastUpdated: new Date().toISOString(),
964
- };
965
- }
948
+ // ADR-093 F1: read from the same trajectory/pattern store that
949
+ // hooks_post-task and hooks_intelligence_stats write to. Previously
950
+ // this handler key-substring-filtered the memory store for "pattern",
951
+ // "route", "task" none of which match the trajectory keys that
952
+ // post-task actually writes so counters stayed at 0 forever (#1686).
953
+ const stats = getIntelligenceStatsFromMemory();
954
+ // Routing outcomes are persisted to a separate file (loadRoutingOutcomes)
955
+ // by post-task; surface them so the dashboard sees command counters too.
956
+ let routingOutcomes = [];
957
+ try {
958
+ routingOutcomes = loadRoutingOutcomes();
959
+ }
960
+ catch { /* non-fatal */ }
961
+ const totalCommands = routingOutcomes.length;
962
+ const successfulCommands = routingOutcomes.filter(o => o.success).length;
963
+ const successRate = totalCommands > 0 ? successfulCommands / totalCommands : null;
964
+ // Compute top agent from routing outcomes
965
+ const agentCounts = {};
966
+ for (const o of routingOutcomes) {
967
+ if (o.agent)
968
+ agentCounts[o.agent] = (agentCounts[o.agent] || 0) + 1;
969
+ }
970
+ const topAgent = Object.entries(agentCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? null;
971
+ const successful = stats.trajectories.successful;
972
+ const total = stats.trajectories.total;
973
+ const failed = Math.max(0, total - successful);
966
974
  return {
975
+ _real: true,
976
+ _dataSource: 'intelligence-stats + routing-outcomes',
967
977
  period,
968
978
  patterns: {
969
- total: patternEntries.length,
970
- successful: null,
971
- failed: null,
972
- avgConfidence: null,
979
+ total: stats.patterns.learned,
980
+ successful,
981
+ failed,
982
+ avgConfidence: stats.routing.avgConfidence || null,
973
983
  },
974
984
  agents: {
975
- routingAccuracy: null,
976
- totalRoutes: routingEntries.length,
977
- topAgent: null,
985
+ routingAccuracy: stats.routing.avgConfidence || null,
986
+ totalRoutes: stats.routing.decisions,
987
+ topAgent,
978
988
  },
979
989
  commands: {
980
- totalExecuted: taskEntries.length,
981
- successRate: null,
990
+ totalExecuted: totalCommands,
991
+ successRate,
982
992
  avgRiskScore: null,
983
993
  },
984
- dataSource: 'memory-store',
985
- entriesFound: entries.length,
994
+ _note: total === 0 && totalCommands === 0
995
+ ? 'No metrics data collected yet. Run hooks_post-task / hooks_intelligence_trajectory-end / hooks_route to populate.'
996
+ : undefined,
986
997
  lastUpdated: new Date().toISOString(),
987
998
  };
988
999
  },
@@ -2977,40 +2988,90 @@ export const hooksIntelligenceAttention = {
2977
2988
  }
2978
2989
  }
2979
2990
  else if (mode === 'flash') {
2980
- // Try Flash Attention
2991
+ // Try Flash Attention. ADR-093 F10: previously this attended over
2992
+ // synthetic cosine-derived keys/values with constant-vector values,
2993
+ // which produced uniform 0.333 weights and labels like "Flash
2994
+ // attention target #1/2/3". Now we attend over actual stored
2995
+ // patterns when available — real semantic content yields non-uniform
2996
+ // weights and human-readable labels.
2981
2997
  const flash = await getFlashAttention();
2982
2998
  if (flash) {
2983
2999
  try {
2984
3000
  const embResult = await getQueryEmbedding(query, 384);
2985
3001
  embeddingSource = embResult.source;
2986
3002
  const q = embResult.embedding;
3003
+ // Pull real stored patterns to attend over. If none exist yet,
3004
+ // fall back to the synthetic harness but mark it honestly.
3005
+ const realPatterns = [];
3006
+ try {
3007
+ const { searchEntries: searchFn } = await import('../memory/memory-initializer.js');
3008
+ const hits = await searchFn({ query, limit: topK });
3009
+ if (Array.isArray(hits)) {
3010
+ for (const h of hits.slice(0, topK)) {
3011
+ const content = h.content ?? h.value ?? '';
3012
+ const id = String(h.id ?? h.key ?? `pattern-${realPatterns.length}`);
3013
+ realPatterns.push({ id, content: String(content) });
3014
+ }
3015
+ }
3016
+ }
3017
+ catch { /* memory not initialized — fall through to synthetic */ }
3018
+ const useReal = realPatterns.length > 0;
2987
3019
  const keys = [];
2988
3020
  const values = [];
2989
- // Generate some keys/values
2990
- for (let k = 0; k < topK; k++) {
2991
- const key = new Float32Array(384);
2992
- const value = new Float32Array(384);
2993
- for (let i = 0; i < 384; i++) {
2994
- key[i] = Math.cos((k + 1) * (i + 1) * 0.01);
2995
- value[i] = k + 1;
3021
+ const labels = [];
3022
+ if (useReal) {
3023
+ // Build keys from real pattern embeddings (re-embed if no vector cached)
3024
+ for (let k = 0; k < realPatterns.length; k++) {
3025
+ const p = realPatterns[k];
3026
+ let keyEmbedding;
3027
+ if (p.embedding && p.embedding.length === 384) {
3028
+ keyEmbedding = new Float32Array(p.embedding);
3029
+ }
3030
+ else {
3031
+ const enc = await getQueryEmbedding(p.content.slice(0, 1024), 384);
3032
+ keyEmbedding = enc.embedding;
3033
+ }
3034
+ const value = new Float32Array(384);
3035
+ // Value carries pattern identity strength — magnitude = recency proxy (k position)
3036
+ const strength = 1 / (k + 1);
3037
+ for (let i = 0; i < 384; i++)
3038
+ value[i] = keyEmbedding[i] * strength;
3039
+ keys.push(keyEmbedding);
3040
+ values.push(value);
3041
+ const label = p.content.length > 0
3042
+ ? `${p.id}: ${p.content.slice(0, 60)}${p.content.length > 60 ? '…' : ''}`
3043
+ : p.id;
3044
+ labels.push(label);
3045
+ }
3046
+ }
3047
+ else {
3048
+ // No real patterns — surface a synthetic harness honestly.
3049
+ for (let k = 0; k < topK; k++) {
3050
+ const key = new Float32Array(384);
3051
+ const value = new Float32Array(384);
3052
+ for (let i = 0; i < 384; i++) {
3053
+ key[i] = Math.cos((k + 1) * (i + 1) * 0.01);
3054
+ value[i] = k + 1;
3055
+ }
3056
+ keys.push(key);
3057
+ values.push(value);
3058
+ labels.push(`(synthetic harness) pattern #${k + 1}`);
2996
3059
  }
2997
- keys.push(key);
2998
- values.push(value);
2999
3060
  }
3000
3061
  const attentionResult = flash.attention([q], keys, values);
3001
3062
  // Compute softmax weights from output magnitudes
3002
3063
  const outputMags = attentionResult.output[0]
3003
- ? Array.from(attentionResult.output[0]).slice(0, topK).map(v => Math.abs(v))
3004
- : new Array(topK).fill(1);
3064
+ ? Array.from(attentionResult.output[0]).slice(0, keys.length).map(v => Math.abs(v))
3065
+ : new Array(keys.length).fill(1);
3005
3066
  const sumMags = outputMags.reduce((a, b) => a + b, 0) || 1;
3006
- for (let i = 0; i < topK; i++) {
3067
+ for (let i = 0; i < keys.length; i++) {
3007
3068
  results.push({
3008
3069
  index: i,
3009
3070
  weight: outputMags[i] / sumMags,
3010
- pattern: `Flash attention target #${i + 1}`,
3071
+ pattern: labels[i],
3011
3072
  });
3012
3073
  }
3013
- implementation = 'real-flash-attention';
3074
+ implementation = useReal ? 'real-flash-attention+memory' : 'real-flash-attention+synthetic-harness';
3014
3075
  }
3015
3076
  catch {
3016
3077
  // Fall back to placeholder
@@ -3325,21 +3386,61 @@ export const hooksWorkerDispatch = {
3325
3386
  }
3326
3387
  const workerId = `worker_${trigger}_${++workerIdCounter}_${Date.now().toString(36)}`;
3327
3388
  const config = WORKER_CONFIGS[trigger];
3389
+ // ADR-093 F2: stop returning status:"completed" for a worker that
3390
+ // never ran (#1700 item 1). Detect daemon presence via PID file and
3391
+ // surface honest verdicts (`no-daemon` / `queued` / `synthetic`).
3392
+ const cwd = getProjectCwd();
3393
+ const pidFile = join(cwd, '.claude-flow', 'daemon.pid');
3394
+ let daemonPid = null;
3395
+ let daemonAlive = false;
3396
+ if (existsSync(pidFile)) {
3397
+ try {
3398
+ const raw = readFileSync(pidFile, 'utf-8').trim();
3399
+ const pid = parseInt(raw, 10);
3400
+ if (Number.isFinite(pid) && pid > 0) {
3401
+ daemonPid = pid;
3402
+ try {
3403
+ process.kill(pid, 0);
3404
+ daemonAlive = true;
3405
+ }
3406
+ catch {
3407
+ daemonAlive = false;
3408
+ }
3409
+ }
3410
+ }
3411
+ catch { /* unreadable PID file */ }
3412
+ }
3328
3413
  const worker = {
3329
3414
  id: workerId,
3330
3415
  trigger,
3331
3416
  context,
3332
- status: 'running',
3417
+ status: daemonAlive ? 'pending' : 'pending',
3333
3418
  progress: 0,
3334
3419
  phase: 'initializing',
3335
3420
  startedAt: new Date(),
3336
3421
  };
3337
3422
  activeWorkers.set(workerId, worker);
3338
- if (!background) {
3423
+ // Determine honest status
3424
+ let reportedStatus;
3425
+ let note;
3426
+ if (!daemonAlive) {
3427
+ reportedStatus = 'no-daemon';
3428
+ note = 'No worker daemon detected. Run `claude-flow daemon start` to enable real worker execution. The dispatch was recorded in-process but no actual work will run.';
3429
+ }
3430
+ else if (background) {
3431
+ // Daemon is alive — record the queued worker. The daemon polls activeWorkers
3432
+ // via its own state file, so this constitutes a real queue entry.
3433
+ reportedStatus = 'queued';
3434
+ note = `Worker queued for daemon (pid ${daemonPid}). Poll hooks_worker-status to track progression — do not assume completion until status === "completed".`;
3435
+ }
3436
+ else {
3437
+ // Synchronous mode without a runner — be honest about it
3438
+ reportedStatus = 'synthetic-completed';
3339
3439
  worker.progress = 100;
3340
3440
  worker.phase = 'completed';
3341
3441
  worker.status = 'completed';
3342
3442
  worker.completedAt = new Date();
3443
+ note = 'Synchronous mode: worker record marked completed but no real work executed (no in-process runner). Use background:true with the daemon for real execution.';
3343
3444
  }
3344
3445
  return {
3345
3446
  success: true,
@@ -3352,9 +3453,11 @@ export const hooksWorkerDispatch = {
3352
3453
  estimatedDuration: config.estimatedDuration,
3353
3454
  capabilities: config.capabilities,
3354
3455
  },
3355
- status: background ? 'scheduled' : 'completed',
3456
+ status: reportedStatus,
3457
+ daemonAlive,
3458
+ daemonPid: daemonAlive ? daemonPid : null,
3356
3459
  background,
3357
- note: background ? 'Worker scheduled. Use hooks_worker-status to check progress. Start the daemon (daemon start) for real background execution.' : undefined,
3460
+ note,
3358
3461
  timestamp: new Date().toISOString(),
3359
3462
  };
3360
3463
  },
@@ -222,10 +222,38 @@ export const neuralTools = [
222
222
  continue;
223
223
  const embedding = await generateEmbedding(text, 384);
224
224
  const patternId = `${modelId}-train-${i}`;
225
+ // ADR-093 F11: extract a meaningful label instead of dumping raw
226
+ // training JSON as the pattern name. Audit reported neural_predict
227
+ // returned `label: <raw training data JSON>` because the previous
228
+ // fallback was `text.slice(0, 100)` where text was `JSON.stringify(entry)`.
229
+ let label;
230
+ if (typeof entry === 'string') {
231
+ label = entry.slice(0, 80);
232
+ }
233
+ else if (entry && typeof entry === 'object') {
234
+ const e = entry;
235
+ // Prefer common semantic fields over a JSON dump
236
+ const labelField = e.label ?? e.category ?? e.class ?? e.tag ?? e.intent ?? e.name ?? e.title;
237
+ if (typeof labelField === 'string' && labelField.length > 0) {
238
+ label = labelField.slice(0, 80);
239
+ }
240
+ else {
241
+ const summaryField = e.text ?? e.input ?? e.task ?? e.description ?? e.content;
242
+ if (typeof summaryField === 'string' && summaryField.length > 0) {
243
+ label = `${summaryField.slice(0, 60)}${summaryField.length > 60 ? '…' : ''}`;
244
+ }
245
+ else {
246
+ // Last resort: reduce to a stable short hash-like id
247
+ label = `${modelType}:entry-${i}`;
248
+ }
249
+ }
250
+ }
251
+ else {
252
+ label = `${modelType}:entry-${i}`;
253
+ }
225
254
  store.patterns[patternId] = {
226
255
  id: patternId,
227
- name: typeof entry === 'object' && entry !== null && 'label' in entry
228
- ? String(entry.label) : text.slice(0, 100),
256
+ name: label,
229
257
  type: modelType,
230
258
  embedding,
231
259
  metadata: { modelId, epoch: epochs, index: i, raw: entry },
@@ -294,36 +322,67 @@ export const neuralTools = [
294
322
  const startTime = performance.now();
295
323
  const embedding = await generateEmbedding(inputText, 384);
296
324
  const latency = Math.round(performance.now() - startTime);
297
- // Search stored patterns via real cosine similarity
325
+ // ADR-093 F11: real classifier head over stored patterns. Previously
326
+ // confidence was the raw cosine similarity (often clamped to 0 when
327
+ // stored embeddings were stale or zero-vectored). Now we run k-NN
328
+ // with cosine distance and apply a temperature-controlled softmax
329
+ // over the top-K so confidence is a proper distribution that sums
330
+ // to 1, and we surface enough metadata to trust the result.
298
331
  const storedPatterns = Object.values(store.patterns);
299
332
  let predictions;
300
333
  if (storedPatterns.length > 0) {
301
- // Real nearest-neighbor prediction using stored pattern embeddings
302
- predictions = storedPatterns
303
- .map(p => ({
304
- label: p.name || p.type || p.id,
305
- confidence: Math.max(0, cosineSimilarity(embedding, p.embedding)),
306
- patternId: p.id,
307
- }))
308
- .sort((a, b) => b.confidence - a.confidence)
334
+ // Step 1: k-NN with cosine
335
+ const scored = storedPatterns
336
+ .map(p => {
337
+ const sim = cosineSimilarity(embedding, p.embedding);
338
+ return {
339
+ patternId: p.id,
340
+ label: p.name || p.type || p.id,
341
+ cosineSimilarity: sim,
342
+ };
343
+ })
344
+ .sort((a, b) => b.cosineSimilarity - a.cosineSimilarity)
309
345
  .slice(0, topK);
346
+ // Step 2: temperature-softmax over the top-K so confidence sums to 1.
347
+ // Temperature 0.1 sharpens differences between similar candidates.
348
+ const tau = 0.1;
349
+ const exps = scored.map(s => Math.exp(s.cosineSimilarity / tau));
350
+ const z = exps.reduce((a, b) => a + b, 0) || 1;
351
+ predictions = scored.map((s, i) => ({
352
+ label: s.label,
353
+ patternId: s.patternId,
354
+ cosineSimilarity: Number(s.cosineSimilarity.toFixed(4)),
355
+ confidence: Number((exps[i] / z).toFixed(4)),
356
+ }));
310
357
  }
311
358
  else {
312
- // No patterns stored — no predictions possible
359
+ // No patterns stored — no predictions possible. Be honest about it
360
+ // instead of returning empty silently.
313
361
  predictions = [];
314
362
  }
363
+ const topConfidence = predictions[0]?.confidence ?? 0;
364
+ const topSimilarity = predictions[0]?.cosineSimilarity ?? 0;
315
365
  return {
316
366
  success: true,
317
367
  _realEmbedding: !!realEmbeddings,
318
368
  _embeddingSource: embeddingServiceName,
319
369
  embeddingProvider: embeddingServiceName,
320
370
  _hasStoredPatterns: storedPatterns.length > 0,
371
+ _classifierHead: storedPatterns.length > 0 ? 'knn-cosine+softmax(tau=0.1)' : 'none',
321
372
  modelId: model?.id || 'default',
322
373
  input: inputText,
323
374
  predictions,
375
+ // Surface cosineSimilarity separately so callers know whether the
376
+ // softmax confidence reflects true match strength.
377
+ topPrediction: predictions[0]?.label ?? null,
378
+ topConfidence,
379
+ topSimilarity,
324
380
  embedding: embedding.slice(0, 8), // Preview of embedding
325
381
  embeddingDims: embedding.length,
326
382
  latency,
383
+ ...(storedPatterns.length === 0 ? {
384
+ _note: 'No patterns stored. Train with neural_train(modelType, trainingData) before predicting.',
385
+ } : {}),
327
386
  };
328
387
  },
329
388
  },
@@ -74,7 +74,27 @@ export const performanceTools = [
74
74
  const freeMem = os.freemem();
75
75
  // Calculate real CPU usage percentage from load average
76
76
  const cpuPercent = (loadAvg[0] / cpus.length) * 100;
77
- // Generate current metrics with REAL values
77
+ // ADR-093 F8: replace hardcoded latency fixtures (50/40/100/200) with
78
+ // an actual self-measured latency probe. Throughput now reflects real
79
+ // metric collection cadence (calls/min over the stored history) rather
80
+ // than an arbitrary +1/+10 increment per call.
81
+ const probeStart = process.hrtime.bigint();
82
+ // Tiny CPU+memory work that mirrors a typical MCP tool call
83
+ let probeAcc = 0;
84
+ for (let i = 0; i < 1000; i++)
85
+ probeAcc += Math.sqrt(i);
86
+ const probeNs = Number(process.hrtime.bigint() - probeStart);
87
+ const selfLatencyMs = probeNs / 1e6;
88
+ const recent = store.metrics.slice(-10);
89
+ const recentLatencies = recent.map(m => m.latency.avg).filter(n => Number.isFinite(n));
90
+ recentLatencies.push(selfLatencyMs);
91
+ const sorted = [...recentLatencies].sort((a, b) => a - b);
92
+ const pct = (p) => sorted.length === 0 ? selfLatencyMs : sorted[Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length))];
93
+ const avg = recentLatencies.reduce((s, n) => s + n, 0) / Math.max(1, recentLatencies.length);
94
+ // Throughput from real cadence: count metric samples in the last 60s.
95
+ const cutoff = Date.now() - 60_000;
96
+ const samplesInLastMinute = store.metrics.filter(m => new Date(m.timestamp).getTime() >= cutoff).length + 1;
97
+ const opsPerSecond = samplesInLastMinute / 60;
78
98
  const currentMetrics = {
79
99
  timestamp: new Date().toISOString(),
80
100
  cpu: { usage: Math.min(cpuPercent, 100), cores: cpus.length },
@@ -84,17 +104,20 @@ export const performanceTools = [
84
104
  heap: Math.round(memUsage.heapUsed / 1024 / 1024),
85
105
  },
86
106
  latency: {
87
- avg: store.metrics.length > 0 ? store.metrics.slice(-10).reduce((s, m) => s + m.latency.avg, 0) / Math.min(store.metrics.length, 10) : 50,
88
- p50: store.metrics.length > 0 ? store.metrics.slice(-10).reduce((s, m) => s + m.latency.p50, 0) / Math.min(store.metrics.length, 10) : 40,
89
- p95: store.metrics.length > 0 ? store.metrics.slice(-10).reduce((s, m) => s + m.latency.p95, 0) / Math.min(store.metrics.length, 10) : 100,
90
- p99: store.metrics.length > 0 ? store.metrics.slice(-10).reduce((s, m) => s + m.latency.p99, 0) / Math.min(store.metrics.length, 10) : 200,
107
+ avg: Number(avg.toFixed(3)),
108
+ p50: Number(pct(50).toFixed(3)),
109
+ p95: Number(pct(95).toFixed(3)),
110
+ p99: Number(pct(99).toFixed(3)),
91
111
  },
92
112
  throughput: {
93
- requests: store.metrics.length > 0 ? store.metrics[store.metrics.length - 1].throughput.requests + 1 : 1,
94
- operations: store.metrics.length > 0 ? store.metrics[store.metrics.length - 1].throughput.operations + 10 : 10,
113
+ requests: store.metrics.length + 1,
114
+ operations: Number(opsPerSecond.toFixed(2)),
95
115
  },
96
116
  errors: { count: 0, rate: 0 },
97
117
  };
118
+ // probeAcc kept reachable to prevent V8 dead-code elimination of the loop
119
+ if (probeAcc < 0)
120
+ currentMetrics.errors.count = -1;
98
121
  store.metrics.push(currentMetrics);
99
122
  // Keep last 100 metrics
100
123
  if (store.metrics.length > 100) {
@@ -260,29 +260,43 @@ export const sessionTools = [
260
260
  },
261
261
  },
262
262
  handler: async (input) => {
263
- let sessions = listSessions();
263
+ const raw = listSessions();
264
+ let sessions = raw.map((s) => ({
265
+ ...s,
266
+ sessionId: s.sessionId || s.id || 'unknown',
267
+ savedAt: s.savedAt || s.startedAt || '',
268
+ }));
264
269
  // Sort
265
270
  const sortBy = input.sortBy || 'date';
266
271
  if (sortBy === 'date') {
267
- sessions.sort((a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime());
272
+ sessions.sort((a, b) => new Date(String(b.savedAt || '')).getTime() - new Date(String(a.savedAt || '')).getTime());
268
273
  }
269
274
  else if (sortBy === 'name') {
270
- sessions.sort((a, b) => a.name.localeCompare(b.name));
275
+ sessions.sort((a, b) => String(a.name || a.sessionId || '').localeCompare(String(b.name || b.sessionId || '')));
271
276
  }
272
277
  else if (sortBy === 'size') {
273
- sessions.sort((a, b) => b.stats.totalSize - a.stats.totalSize);
278
+ sessions.sort((a, b) => (b.stats?.totalSize ?? 0) - (a.stats?.totalSize ?? 0));
274
279
  }
275
280
  // Apply limit
276
281
  const limit = input.limit || 10;
277
282
  sessions = sessions.slice(0, limit);
278
283
  return {
279
- sessions: sessions.map(s => ({
280
- sessionId: s.sessionId,
281
- name: s.name,
282
- description: s.description,
283
- savedAt: s.savedAt,
284
- stats: s.stats,
285
- })),
284
+ sessions: sessions.map(s => {
285
+ // Project to a stable shape; pull through either source's metadata.
286
+ const projection = {
287
+ sessionId: s.sessionId,
288
+ name: s.name ?? s.sessionId,
289
+ description: s.description,
290
+ savedAt: s.savedAt,
291
+ stats: s.stats ?? null,
292
+ };
293
+ // Preserve auto-session shape fields when present
294
+ if (s.platform)
295
+ projection.platform = s.platform;
296
+ if (s.metrics)
297
+ projection.metrics = s.metrics;
298
+ return projection;
299
+ }),
286
300
  total: sessions.length,
287
301
  limit,
288
302
  };
@@ -131,6 +131,56 @@ async function getRegistry(dbPath) {
131
131
  }
132
132
  }
133
133
  catch { /* AgentDB not available */ }
134
+ // ADR-093 F9: post-init injection of low-risk disabled controllers.
135
+ // Try multiple constructor names because the agentdb API renamed
136
+ // SemanticRouter across alpha versions (alpha.10 had SemanticRouter,
137
+ // alpha.11+ removed it in favor of @ruvector/router separately;
138
+ // future versions may reintroduce). If the cwd's agentdb has a
139
+ // viable router-shaped controller, wire it; otherwise leave it
140
+ // unbound and let bridgeSemanticRoute return its actionable error.
141
+ try {
142
+ const agentdb = await import('agentdb');
143
+ const candidates = ['SemanticRouter', 'IntentRouter', 'TaskRouter'];
144
+ let routerInstance = null;
145
+ for (const name of candidates) {
146
+ const Ctor = agentdb[name];
147
+ if (typeof Ctor === 'function') {
148
+ try {
149
+ // Try with dimension config first (newer router classes), then no-args
150
+ const inst = (() => {
151
+ try {
152
+ return new Ctor({ dimension: 384 });
153
+ }
154
+ catch {
155
+ return new Ctor();
156
+ }
157
+ })();
158
+ if (inst && typeof inst.route === 'function') {
159
+ routerInstance = inst;
160
+ break;
161
+ }
162
+ }
163
+ catch { /* try next candidate */ }
164
+ }
165
+ }
166
+ if (routerInstance && !reg.get('semanticRouter')) {
167
+ if (typeof reg.set === 'function')
168
+ reg.set('semanticRouter', routerInstance);
169
+ else
170
+ reg._controllers = { ...(reg._controllers || {}), semanticRouter: routerInstance };
171
+ }
172
+ }
173
+ catch { /* SemanticRouter optional — bridgeSemanticRoute will surface "not available" */ }
174
+ // Other disabled controllers remain disabled and tracked in
175
+ // ADR-093 F9 for future enablement:
176
+ // - mutationGuard (write protection — needs config)
177
+ // - attestationLog (needs sqlite db handle the registry does
178
+ // not currently expose)
179
+ // - gnnService (graph neural net — heavy deps, needs WASM/CUDA)
180
+ // - guardedVectorBackend (secured vector backend variant —
181
+ // needs key material)
182
+ // - rvfOptimizer (RVF format optimizer — needs RVF storage)
183
+ // - graphAdapter (graph DB adapter — needs graph DB)
134
184
  }
135
185
  catch {
136
186
  // Intelligence module not available — learning stays unwired
@@ -1545,13 +1595,22 @@ export async function bridgeSemanticRoute(params) {
1545
1595
  return null;
1546
1596
  try {
1547
1597
  const router = registry.get('semanticRouter');
1548
- if (!router)
1549
- return { route: null, error: 'SemanticRouter not available' };
1598
+ if (!router) {
1599
+ // ADR-093 F9: surface an actionable error pointing callers at the
1600
+ // alternative routing surfaces that DO work, instead of just
1601
+ // saying "not available".
1602
+ return {
1603
+ route: null,
1604
+ error: 'SemanticRouter not available in current agentdb build',
1605
+ recommendation: 'Use bridgeRouteTask (registers as `agentdb_route` MCP tool) for keyword+pattern routing, or hooks_model-route for ADR-026 model selection.',
1606
+ controller: 'none',
1607
+ };
1608
+ }
1550
1609
  const result = await router.route(params.input);
1551
1610
  return { route: result, controller: 'semanticRouter' };
1552
1611
  }
1553
1612
  catch (e) {
1554
- return { route: null, error: e.message };
1613
+ return { route: null, error: e.message, controller: 'error' };
1555
1614
  }
1556
1615
  }
1557
1616
  // ===== RaBitQ data export =====
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.6.13",
3
+ "version": "3.6.15",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",