claude-flow 3.32.34 → 3.32.36

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.
Files changed (69) hide show
  1. package/.claude/helpers/hook-handler.cjs +134 -3
  2. package/.claude/helpers/intelligence.cjs +47 -4
  3. package/node_modules/@claude-flow/codex/.agents/skills/github-automation/SKILL.md +32 -0
  4. package/node_modules/@claude-flow/codex/.agents/skills/performance-analysis/SKILL.md +32 -0
  5. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts +4 -0
  6. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts.map +1 -1
  7. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +20 -1
  8. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
  9. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +4 -0
  10. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
  11. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +18 -2
  12. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
  13. package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts +11 -5
  14. package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts.map +1 -1
  15. package/node_modules/@claude-flow/codex/dist/generators/skill-md.js +84 -849
  16. package/node_modules/@claude-flow/codex/dist/generators/skill-md.js.map +1 -1
  17. package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
  18. package/node_modules/@claude-flow/codex/dist/initializer.js +9 -13
  19. package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
  20. package/package.json +1 -1
  21. package/v3/@claude-flow/cli/bin/cli.js +25 -1
  22. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  23. package/v3/@claude-flow/cli/dist/src/commands/agent.js +50 -2
  24. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +159 -11
  25. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +63 -4
  26. package/v3/@claude-flow/cli/dist/src/commands/mcp.js +1 -1
  27. package/v3/@claude-flow/cli/dist/src/commands/memory-distill.js +1 -0
  28. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +4 -0
  29. package/v3/@claude-flow/cli/dist/src/commands/swarm.js +89 -5
  30. package/v3/@claude-flow/cli/dist/src/config-adapter.js +1 -0
  31. package/v3/@claude-flow/cli/dist/src/index.js +3 -2
  32. package/v3/@claude-flow/cli/dist/src/init/executor.js +16 -10
  33. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +15 -4
  34. package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +18 -5
  35. package/v3/@claude-flow/cli/dist/src/mcp-server.d.ts +25 -0
  36. package/v3/@claude-flow/cli/dist/src/mcp-server.js +57 -2
  37. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +10 -0
  38. package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +51 -10
  39. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +120 -60
  40. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +14 -2
  41. package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +7 -0
  42. package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.d.ts +16 -0
  43. package/v3/@claude-flow/cli/dist/src/mcp-tools/swarm-tools.js +172 -7
  44. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +2 -0
  45. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +5 -1
  46. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
  47. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +66 -19
  48. package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.js +7 -4
  49. package/v3/@claude-flow/cli/dist/src/services/flywheel-receipt.d.ts +3 -0
  50. package/v3/@claude-flow/cli/dist/src/services/flywheel-receipt.js +1 -0
  51. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.d.ts +6 -0
  52. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.js +19 -19
  53. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.d.ts +2 -0
  54. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.js +1 -0
  55. package/v3/@claude-flow/cli/dist/src/services/harness-project-anchor.d.ts +23 -0
  56. package/v3/@claude-flow/cli/dist/src/services/harness-project-anchor.js +129 -0
  57. package/v3/@claude-flow/cli/dist/src/services/learned-routing.d.ts +34 -0
  58. package/v3/@claude-flow/cli/dist/src/services/learned-routing.js +85 -0
  59. package/v3/@claude-flow/cli/dist/src/services/memory-distillation.d.ts +1 -0
  60. package/v3/@claude-flow/cli/dist/src/services/memory-distillation.js +81 -5
  61. package/v3/@claude-flow/cli/dist/src/services/pheromone-adaptive.d.ts +71 -0
  62. package/v3/@claude-flow/cli/dist/src/services/pheromone-adaptive.js +214 -0
  63. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +5 -1
  64. package/v3/@claude-flow/cli/package.json +1 -1
  65. package/v3/@claude-flow/shared/dist/core/config/loader.js +1 -1
  66. package/v3/@claude-flow/shared/dist/core/config/schema.d.ts +12 -12
  67. package/v3/@claude-flow/shared/dist/core/config/schema.js +1 -1
  68. package/v3/@claude-flow/shared/dist/core/interfaces/coordinator.interface.d.ts +1 -1
  69. package/v3/@claude-flow/shared/dist/types/swarm.types.js +18 -0
@@ -50,13 +50,16 @@ async function getRealEmbeddingFunction() {
50
50
  }
51
51
  return realEmbeddingFn;
52
52
  }
53
- // Generate real ONNX embedding (falls back to deterministic hash if ONNX unavailable)
54
53
  async function generateRealEmbedding(text, dimension) {
55
54
  const realFn = await getRealEmbeddingFunction();
56
55
  if (realFn) {
57
56
  try {
58
57
  const result = await realFn(text);
59
- return result.embedding;
58
+ return {
59
+ embedding: result.embedding,
60
+ backend: result.backend ?? 'onnx',
61
+ model: result.model,
62
+ };
60
63
  }
61
64
  catch {
62
65
  // Fall through to fallback
@@ -75,7 +78,11 @@ async function generateRealEmbedding(text, dimension) {
75
78
  }
76
79
  // L2 normalize
77
80
  const norm = Math.sqrt(embedding.reduce((sum, x) => sum + x * x, 0));
78
- return embedding.map(x => x / norm);
81
+ return {
82
+ embedding: embedding.map(x => x / norm),
83
+ backend: 'mock',
84
+ model: 'hash-fallback',
85
+ };
79
86
  }
80
87
  // Convert Euclidean embedding to Poincaré ball
81
88
  function toPoincare(euclidean, curvature) {
@@ -238,7 +245,8 @@ export const embeddingsTools = [
238
245
  }
239
246
  const useHyperbolic = input.hyperbolic === true && config.hyperbolic.enabled;
240
247
  // Generate real ONNX embedding
241
- const embedding = await generateRealEmbedding(text, config.dimension);
248
+ const generated = await generateRealEmbedding(text, config.dimension);
249
+ const embedding = generated.embedding;
242
250
  let result;
243
251
  let geometry;
244
252
  if (useHyperbolic) {
@@ -254,6 +262,8 @@ export const embeddingsTools = [
254
262
  embedding: result,
255
263
  metadata: {
256
264
  model: config.model,
265
+ embeddingBackend: generated.backend,
266
+ semanticGrounded: generated.backend === 'onnx',
257
267
  dimension: config.dimension,
258
268
  geometry,
259
269
  curvature: useHyperbolic ? config.hyperbolic.curvature : null,
@@ -309,10 +319,13 @@ export const embeddingsTools = [
309
319
  return { success: false, error: v.error };
310
320
  }
311
321
  // Generate real ONNX embeddings for both texts
312
- const [emb1, emb2] = await Promise.all([
322
+ const [generated1, generated2] = await Promise.all([
313
323
  generateRealEmbedding(text1, config.dimension),
314
324
  generateRealEmbedding(text2, config.dimension)
315
325
  ]);
326
+ const emb1 = generated1.embedding;
327
+ const emb2 = generated2.embedding;
328
+ const embeddingBackend = generated1.backend === 'onnx' && generated2.backend === 'onnx' ? 'onnx' : 'mock';
316
329
  let similarity;
317
330
  let distance;
318
331
  switch (metric) {
@@ -341,14 +354,21 @@ export const embeddingsTools = [
341
354
  similarity,
342
355
  distance,
343
356
  metric,
357
+ embeddingBackend,
358
+ semanticGrounded: embeddingBackend === 'onnx',
344
359
  texts: {
345
360
  text1: { length: text1.length, preview: text1.slice(0, 50) },
346
361
  text2: { length: text2.length, preview: text2.slice(0, 50) },
347
362
  },
348
- interpretation: similarity > 0.8 ? 'very similar' :
349
- similarity > 0.6 ? 'similar' :
350
- similarity > 0.4 ? 'somewhat similar' :
351
- similarity > 0.2 ? 'different' : 'very different',
363
+ interpretation: embeddingBackend === 'onnx'
364
+ ? similarity > 0.8 ? 'very similar'
365
+ : similarity > 0.6 ? 'similar'
366
+ : similarity > 0.4 ? 'somewhat similar'
367
+ : similarity > 0.2 ? 'different' : 'very different'
368
+ : null,
369
+ warning: embeddingBackend === 'mock'
370
+ ? 'Hash fallback scores are deterministic but not semantically meaningful.'
371
+ : undefined,
352
372
  };
353
373
  },
354
374
  },
@@ -426,6 +446,8 @@ export const embeddingsTools = [
426
446
  })),
427
447
  metadata: {
428
448
  model: config.model,
449
+ embeddingBackend: queryEmbedding.backend,
450
+ semanticGrounded: queryEmbedding.backend === 'onnx',
429
451
  topK,
430
452
  threshold,
431
453
  namespace: namespace || 'all',
@@ -433,6 +455,9 @@ export const embeddingsTools = [
433
455
  indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
434
456
  resultCount: searchResult.results.length
435
457
  },
458
+ warning: queryEmbedding.backend === 'mock'
459
+ ? 'Results use a hash fallback and are not semantically ranked.'
460
+ : undefined,
436
461
  };
437
462
  }
438
463
  catch {
@@ -444,12 +469,17 @@ export const embeddingsTools = [
444
469
  results: [],
445
470
  metadata: {
446
471
  model: config.model,
472
+ embeddingBackend: queryEmbedding.backend,
473
+ semanticGrounded: queryEmbedding.backend === 'onnx',
447
474
  topK,
448
475
  threshold,
449
476
  namespace: namespace || 'all',
450
477
  searchTime: `${searchTime}ms`,
451
478
  indexType: config.hyperbolic.enabled ? 'HNSW (hyperbolic)' : 'HNSW (euclidean)',
452
479
  },
480
+ warning: queryEmbedding.backend === 'mock'
481
+ ? 'Hash fallback is active; semantic search is unavailable.'
482
+ : undefined,
453
483
  message: 'No embeddings indexed yet. Use memory store to add documents.',
454
484
  };
455
485
  }
@@ -795,6 +825,8 @@ export const embeddingsTools = [
795
825
  }
796
826
  catch { /* not installed */ }
797
827
  const ruvectorEnabled = config.neural.ruvector?.enabled ?? false;
828
+ const backendProbe = await generateRealEmbedding('ruflo embedding backend probe', config.dimension);
829
+ const semanticGrounded = backendProbe.backend === 'onnx';
798
830
  return {
799
831
  success: true,
800
832
  initialized: true,
@@ -822,11 +854,20 @@ export const embeddingsTools = [
822
854
  models: config.modelPath,
823
855
  },
824
856
  initializedAt: config.initialized,
857
+ embeddingBackend: backendProbe.backend,
858
+ semanticGrounded,
859
+ warning: semanticGrounded
860
+ ? undefined
861
+ : 'Hash fallback is active. Similarity scores are deterministic but not semantically meaningful.',
825
862
  capabilities: {
826
863
  onnxModels: ['Xenova/all-MiniLM-L6-v2', 'Xenova/all-mpnet-base-v2'],
827
864
  geometries: ['euclidean', 'poincare'],
828
865
  normalizations: ['L2', 'L1', 'minmax', 'zscore'],
829
- features: ['semantic search', 'hyperbolic projection', 'neural substrate'],
866
+ features: [
867
+ ...(semanticGrounded ? ['semantic search'] : []),
868
+ 'hyperbolic projection',
869
+ 'neural substrate',
870
+ ],
830
871
  },
831
872
  };
832
873
  },
@@ -8,6 +8,7 @@ import { dirname, join, resolve } from 'path';
8
8
  import { getProjectCwd } from './types.js';
9
9
  import { validateIdentifier, validateText, validatePath } from './validate-input.js';
10
10
  import { checkCommandLoop, recordCommandOutcome } from './tool-loop-guardrail.js';
11
+ import { buildLearnedRoutingPatterns, } from '../services/learned-routing.js';
11
12
  // Real vector search functions - lazy loaded to avoid circular imports
12
13
  let searchEntriesFn = null;
13
14
  /**
@@ -185,6 +186,13 @@ function saveRoutingOutcomes(outcomes) {
185
186
  // Cap at 500 entries to bound file size
186
187
  const capped = outcomes.slice(-500);
187
188
  writeFileSync(ROUTING_OUTCOMES_PATH, JSON.stringify({ outcomes: capped }, null, 2));
189
+ // A long-lived MCP process must observe the new label on the next route.
190
+ // The prior singleton cache made the learned store inert until restart.
191
+ semanticRouter = null;
192
+ nativeVectorDb = null;
193
+ semanticRouterInitialized = false;
194
+ routerBackend = 'none';
195
+ TASK_PATTERN_EMBEDDINGS.clear();
188
196
  }
189
197
  catch { /* non-critical */ }
190
198
  }
@@ -194,24 +202,7 @@ function saveRoutingOutcomes(outcomes) {
194
202
  * merged into both the native HNSW and pure-JS semantic routers.
195
203
  */
196
204
  function loadLearnedPatterns() {
197
- const outcomes = loadRoutingOutcomes();
198
- const byAgent = {};
199
- for (const o of outcomes) {
200
- if (!o.success || !o.agent || !o.keywords?.length)
201
- continue;
202
- if (!byAgent[o.agent])
203
- byAgent[o.agent] = new Set();
204
- for (const kw of o.keywords)
205
- byAgent[o.agent].add(kw);
206
- }
207
- const patterns = {};
208
- for (const [agent, kwSet] of Object.entries(byAgent)) {
209
- patterns[`learned-${agent}`] = {
210
- keywords: [...kwSet].slice(0, 50),
211
- agents: [agent],
212
- };
213
- }
214
- return patterns;
205
+ return buildLearnedRoutingPatterns(loadRoutingOutcomes());
215
206
  }
216
207
  /**
217
208
  * Merge static TASK_PATTERNS with runtime-learned patterns.
@@ -290,46 +281,55 @@ async function getSemanticRouter() {
290
281
  // STEP 1: Try native VectorDb from @ruvector/router (HNSW-backed)
291
282
  // Note: Native VectorDb uses a persistent database file which can have lock issues
292
283
  // in concurrent environments. We try it first but fall back gracefully to pure JS.
293
- try {
294
- // Use createRequire for ESM compatibility with native modules
295
- const { createRequire } = await import('module');
296
- const require = createRequire(import.meta.url);
297
- const router = require('@ruvector/router');
298
- if (router.VectorDb && router.DistanceMetric) {
299
- // Try to create VectorDb - may fail with lock error in concurrent envs
300
- const db = new router.VectorDb({
301
- dimensions: 384,
302
- distanceMetric: router.DistanceMetric.Cosine,
303
- hnswM: 16,
304
- hnswEfConstruction: 200,
305
- hnswEfSearch: 100,
306
- });
307
- // Initialize with static + runtime-learned task patterns
308
- for (const [patternName, { keywords }] of Object.entries(getMergedTaskPatterns())) {
309
- for (const keyword of keywords) {
310
- const embedding = generateSimpleEmbedding(keyword);
311
- db.insert(`${patternName}:${keyword}`, embedding);
312
- TASK_PATTERN_EMBEDDINGS.set(`${patternName}:${keyword}`, embedding);
284
+ // Deterministic/test and lock-constrained environments may force the
285
+ // pure-JS router; production continues to prefer the native backend.
286
+ if (process.env.CLAUDE_FLOW_DISABLE_NATIVE_ROUTER !== '1')
287
+ try {
288
+ // Use createRequire for ESM compatibility with native modules
289
+ const { createRequire } = await import('module');
290
+ const require = createRequire(import.meta.url);
291
+ const router = require('@ruvector/router');
292
+ if (router.VectorDb && router.DistanceMetric) {
293
+ // Try to create VectorDb - may fail with lock error in concurrent envs
294
+ const db = new router.VectorDb({
295
+ dimensions: 384,
296
+ distanceMetric: router.DistanceMetric.Cosine,
297
+ hnswM: 16,
298
+ hnswEfConstruction: 200,
299
+ hnswEfSearch: 100,
300
+ });
301
+ // Initialize with static + runtime-learned task patterns
302
+ for (const [patternName, { keywords }] of Object.entries(getMergedTaskPatterns())) {
303
+ for (const keyword of keywords) {
304
+ const embedding = generateSimpleEmbedding(keyword);
305
+ db.insert(`${patternName}:${keyword}`, embedding);
306
+ TASK_PATTERN_EMBEDDINGS.set(`${patternName}:${keyword}`, embedding);
307
+ }
313
308
  }
309
+ nativeVectorDb = db;
310
+ routerBackend = 'native';
311
+ console.log('[hooks] Semantic router initialized: native VectorDb (HNSW, 16k+ routes/s)');
312
+ return { router: null, backend: routerBackend, native: nativeVectorDb };
314
313
  }
315
- nativeVectorDb = db;
316
- routerBackend = 'native';
317
- console.log('[hooks] Semantic router initialized: native VectorDb (HNSW, 16k+ routes/s)');
318
- return { router: null, backend: routerBackend, native: nativeVectorDb };
319
314
  }
320
- }
321
- catch (err) {
322
- // Native not available or database locked - fall back to pure JS
323
- // Common errors: "Database already open. Cannot acquire lock." or "MODULE_NOT_FOUND"
324
- // This is expected in concurrent environments or when binary isn't installed
325
- }
315
+ catch (err) {
316
+ // Native not available or database locked - fall back to pure JS
317
+ // Common errors: "Database already open. Cannot acquire lock." or "MODULE_NOT_FOUND"
318
+ // This is expected in concurrent environments or when binary isn't installed
319
+ }
326
320
  // STEP 2: Fall back to pure JS SemanticRouter
327
321
  try {
328
322
  const { SemanticRouter } = await import('../ruvector/semantic-router.js');
329
323
  semanticRouter = new SemanticRouter({ dimension: 384 });
330
- for (const [patternName, { keywords, agents }] of Object.entries(getMergedTaskPatterns())) {
324
+ for (const [patternName, { keywords, agents, source, support, reliability }] of Object.entries(getMergedTaskPatterns())) {
331
325
  const embeddings = keywords.map(kw => generateSimpleEmbedding(kw));
332
- semanticRouter.addIntentWithEmbeddings(patternName, embeddings, { agents, keywords });
326
+ semanticRouter.addIntentWithEmbeddings(patternName, embeddings, {
327
+ agents,
328
+ keywords,
329
+ source: source ?? 'static',
330
+ support,
331
+ reliability,
332
+ });
333
333
  // Cache embeddings for keywords
334
334
  keywords.forEach((kw, i) => {
335
335
  TASK_PATTERN_EMBEDDINGS.set(kw, embeddings[i]);
@@ -439,6 +439,20 @@ function getIntelligenceStatsFromMemory() {
439
439
  const category = e.metadata?.category || 'general';
440
440
  categories[category] = (categories[category] || 0) + 1;
441
441
  });
442
+ const successfulPatterns = patternEntries.filter(e => e.metadata?.success === true).length;
443
+ const failedPatterns = patternEntries.filter(e => e.metadata?.success === false).length;
444
+ const describedPatterns = patternEntries.filter(e => {
445
+ if (typeof e.metadata?.task === 'string' && e.metadata.task.trim())
446
+ return true;
447
+ try {
448
+ const value = typeof e.value === 'string' ? JSON.parse(e.value) : e.value;
449
+ return typeof value?.task === 'string' &&
450
+ Boolean(String(value.task).trim());
451
+ }
452
+ catch {
453
+ return false;
454
+ }
455
+ }).length;
442
456
  // Count routing decisions
443
457
  const routingEntries = entries.filter(e => e.key.includes('routing') ||
444
458
  e.metadata?.type === 'routing-decision');
@@ -472,6 +486,10 @@ function getIntelligenceStatsFromMemory() {
472
486
  },
473
487
  patterns: {
474
488
  learned: patternEntries.length,
489
+ successful: successfulPatterns,
490
+ failed: failedPatterns,
491
+ unknown: Math.max(0, patternEntries.length - successfulPatterns - failedPatterns),
492
+ described: describedPatterns,
475
493
  categories,
476
494
  },
477
495
  memory: {
@@ -946,6 +964,9 @@ export const hooksRoute = {
946
964
  score: 1 - r.score, // Native uses distance (lower is better), convert to similarity
947
965
  metadata: {
948
966
  agents: pattern?.agents || (patternName.startsWith('learned-') ? [patternName.slice(8)] : ['coder']),
967
+ source: pattern?.source ?? (patternName.startsWith('learned-') ? 'learned' : 'static'),
968
+ support: pattern?.support,
969
+ reliability: pattern?.reliability,
949
970
  },
950
971
  };
951
972
  });
@@ -966,8 +987,18 @@ export const hooksRoute = {
966
987
  let agents;
967
988
  let confidence;
968
989
  let matchedPattern = '';
969
- if (semanticResult.length > 0 && semanticResult[0].score > 0.4) {
970
- const topMatch = semanticResult[0];
990
+ const eligibleSemantic = semanticResult.find((match) => {
991
+ if (match.score <= 0.4)
992
+ return false;
993
+ const learned = match.intent.startsWith('learned-') || match.metadata.source === 'learned';
994
+ if (!learned)
995
+ return true;
996
+ return match.score >= 0.65
997
+ && Number(match.metadata.support ?? 0) >= 2
998
+ && Number(match.metadata.reliability ?? 0) >= 0.75;
999
+ });
1000
+ if (eligibleSemantic) {
1001
+ const topMatch = eligibleSemantic;
971
1002
  agents = topMatch.metadata.agents || ['coder', 'researcher'];
972
1003
  confidence = topMatch.score;
973
1004
  matchedPattern = topMatch.intent;
@@ -1061,21 +1092,28 @@ export const hooksMetrics = {
1061
1092
  agentCounts[o.agent] = (agentCounts[o.agent] || 0) + 1;
1062
1093
  }
1063
1094
  const topAgent = Object.entries(agentCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? null;
1064
- const successful = stats.trajectories.successful;
1065
- const total = stats.trajectories.total;
1066
- const failed = Math.max(0, total - successful);
1067
1095
  return {
1068
1096
  _real: true,
1069
1097
  _dataSource: 'intelligence-stats + routing-outcomes',
1070
1098
  period,
1071
1099
  patterns: {
1072
1100
  total: stats.patterns.learned,
1073
- successful,
1074
- failed,
1101
+ successful: stats.patterns.successful,
1102
+ failed: stats.patterns.failed,
1103
+ unknown: stats.patterns.unknown,
1104
+ described: stats.patterns.described,
1105
+ descriptionCoverage: stats.patterns.learned > 0
1106
+ ? stats.patterns.described / stats.patterns.learned
1107
+ : null,
1075
1108
  avgConfidence: stats.routing.avgConfidence || null,
1076
1109
  },
1077
1110
  agents: {
1078
- routingAccuracy: stats.routing.avgConfidence || null,
1111
+ // Confidence is a model self-score, not observed routing accuracy
1112
+ // (#2809). Keep the old key as an explicit null for compatibility
1113
+ // while exposing the truthful additive field.
1114
+ routingAccuracy: null,
1115
+ averageConfidence: stats.routing.avgConfidence || null,
1116
+ outcomeSuccessRate: successRate,
1079
1117
  totalRoutes: stats.routing.decisions,
1080
1118
  topAgent,
1081
1119
  },
@@ -1084,7 +1122,7 @@ export const hooksMetrics = {
1084
1122
  successRate,
1085
1123
  avgRiskScore: null,
1086
1124
  },
1087
- _note: total === 0 && totalCommands === 0
1125
+ _note: stats.patterns.learned === 0 && totalCommands === 0
1088
1126
  ? 'No metrics data collected yet. Run hooks_post-task / hooks_intelligence_trajectory-end / hooks_route to populate.'
1089
1127
  : undefined,
1090
1128
  lastUpdated: new Date().toISOString(),
@@ -1249,6 +1287,10 @@ export const hooksPostTask = {
1249
1287
  agent: { type: 'string', description: 'Agent that completed the task' },
1250
1288
  quality: { type: 'number', description: 'Quality score (0-1)' },
1251
1289
  task: { type: 'string', description: 'Task description text (used for learning keyword extraction)' },
1290
+ duration: { type: 'number', description: 'Observed task duration in milliseconds (used by pheromone-adaptive topology)' },
1291
+ latencyBudgetMs: { type: 'number', description: 'Latency budget used to normalize duration (default 60000ms)' },
1292
+ consensusAlignment: { type: 'number', description: 'Agreement with accepted swarm consensus in [0,1] (default quality)' },
1293
+ agentRole: { type: 'string', description: 'Role for role-local pheromone normalization (default agent)' },
1252
1294
  storeDecisions: { type: 'boolean', description: 'Also store routing decision in memory DB' },
1253
1295
  // ADR-147 P2: nested-subagent spawn-tree capture
1254
1296
  parentAgentId: { type: 'string', description: 'ID of the parent agent (from Claude Code\'s parent_agent_id OTel span tag / x-claude-code-parent-agent-id header). Omit for top-level work.' },
@@ -1294,6 +1336,7 @@ export const hooksPostTask = {
1294
1336
  const bridge = await import('../memory/memory-bridge.js');
1295
1337
  feedbackResult = await bridge.bridgeRecordFeedback({
1296
1338
  taskId,
1339
+ task: params.task || undefined,
1297
1340
  success,
1298
1341
  quality,
1299
1342
  agent,
@@ -1405,6 +1448,22 @@ export const hooksPostTask = {
1405
1448
  }
1406
1449
  catch { /* non-critical */ }
1407
1450
  }
1451
+ let pheromone;
1452
+ if (agent) {
1453
+ try {
1454
+ const { recordSwarmPheromoneSignal } = await import('./swarm-tools.js');
1455
+ const observedDuration = Math.max(0, Number(params.duration ?? (Date.now() - startTime)));
1456
+ const latencyBudgetMs = Math.max(1, Number(params.latencyBudgetMs ?? 60_000));
1457
+ pheromone = recordSwarmPheromoneSignal({
1458
+ agentId: agent,
1459
+ role: String(params.agentRole ?? agent),
1460
+ taskSuccess: success ? 1 : 0,
1461
+ normalizedLatency: Math.max(0, Math.min(1, observedDuration / latencyBudgetMs)),
1462
+ consensusAlignment: Math.max(0, Math.min(1, Number(params.consensusAlignment ?? quality))),
1463
+ });
1464
+ }
1465
+ catch { /* no active APSC swarm or optional path unavailable */ }
1466
+ }
1408
1467
  const duration = Date.now() - startTime;
1409
1468
  // Persist to auto-memory-store for statusline visibility
1410
1469
  try {
@@ -1444,6 +1503,7 @@ export const hooksPostTask = {
1444
1503
  outcomePersisted,
1445
1504
  },
1446
1505
  quality,
1506
+ pheromone,
1447
1507
  feedback: feedbackResult ? {
1448
1508
  recorded: feedbackResult.success,
1449
1509
  controller: feedbackResult.controller,
@@ -3102,7 +3162,7 @@ export const hooksIntelligenceStats = {
3102
3162
  catch {
3103
3163
  memoryStats = {
3104
3164
  trajectories: { total: 0, successful: 0 },
3105
- patterns: { learned: 0, categories: {} },
3165
+ patterns: { learned: 0, successful: 0, failed: 0, unknown: 0, described: 0, categories: {} },
3106
3166
  memory: { indexSize: 0, totalAccessCount: 0, memorySizeBytes: 0 },
3107
3167
  routing: { decisions: 0, avgConfidence: 0 },
3108
3168
  };
@@ -1175,6 +1175,7 @@ export const memoryTools = [
1175
1175
  handler: async (input) => {
1176
1176
  await ensureInitialized();
1177
1177
  const { listEntries, deleteEntry } = await getMemoryFunctions();
1178
+ const startedAt = Date.now();
1178
1179
  const dryRun = input.dryRun !== false; // default true
1179
1180
  const namespace = input.namespace ? String(input.namespace) : undefined;
1180
1181
  if (namespace) {
@@ -1193,6 +1194,7 @@ export const memoryTools = [
1193
1194
  });
1194
1195
  let freedBytes = 0;
1195
1196
  let deleted = 0;
1197
+ let vectorsRemoved = 0;
1196
1198
  if (!dryRun) {
1197
1199
  for (const e of expired) {
1198
1200
  try {
@@ -1206,11 +1208,21 @@ export const memoryTools = [
1206
1208
  else {
1207
1209
  freedBytes = expired.reduce((s, e) => s + (e.size || 0), 0);
1208
1210
  }
1211
+ if (!dryRun) {
1212
+ const { reconcileHNSWIndex } = await import('../memory/memory-initializer.js');
1213
+ vectorsRemoved = await reconcileHNSWIndex();
1214
+ }
1215
+ const formatted = freedBytes < 1024
1216
+ ? `${freedBytes} B`
1217
+ : freedBytes < 1024 * 1024
1218
+ ? `${(freedBytes / 1024).toFixed(1)} KiB`
1219
+ : `${(freedBytes / (1024 * 1024)).toFixed(1)} MiB`;
1209
1220
  return {
1210
1221
  dryRun,
1211
1222
  candidates: { expired: expired.length, stale: 0, lowQuality: 0, total: expired.length },
1212
- deleted: { entries: dryRun ? 0 : deleted, vectors: 0, patterns: 0 },
1213
- freed: { bytes: freedBytes },
1223
+ deleted: { entries: dryRun ? 0 : deleted, vectors: vectorsRemoved, patterns: 0 },
1224
+ freed: { bytes: freedBytes, formatted },
1225
+ duration: Date.now() - startedAt,
1214
1226
  note: dryRun ? 'dry run — re-run with dryRun:false to delete' : undefined,
1215
1227
  };
1216
1228
  },
@@ -700,6 +700,9 @@ export const metaharnessTools = [
700
700
  confirm: { type: 'boolean', description: 'Required for promote; never inferred', default: false },
701
701
  maxConcurrency: { type: 'number', description: 'ADR-324 hard local candidate-evaluation concurrency cap (1-8)', default: 2 },
702
702
  timeoutMs: { type: 'number', description: 'Abort concurrent evaluation after this wall-clock limit', default: 120000 },
703
+ anchorPath: { type: 'string', description: 'Project-contained human-labelled anchor JSON; requires anchorHash' },
704
+ anchorHash: { type: 'string', description: 'Pinned sha256 of canonical anchor tasks; requires anchorPath' },
705
+ anchorManifestPath: { type: 'string', description: 'Project-contained anchor manifest path (default .claude/eval/flywheel-anchor.manifest.json)' },
703
706
  approvalIds: { type: 'array', items: { type: 'string' }, description: 'Scoped ADR-324 approval IDs for privileged promotion' },
704
707
  },
705
708
  required: ['operation'],
@@ -721,6 +724,7 @@ export const metaharnessTools = [
721
724
  if (operation === 'receipts') {
722
725
  const data = listFlywheelReceipts(projectRoot).map(({ receipt, state }) => ({
723
726
  receiptId: receipt.payload.receiptId,
727
+ anchorRef: receipt.payload.anchorRef,
724
728
  candidateId: receipt.payload.candidateId,
725
729
  baselineRef: receipt.payload.baselineRef,
726
730
  decision: receipt.payload.decision,
@@ -748,6 +752,9 @@ export const metaharnessTools = [
748
752
  receiptPublicKeyPem: publicKeyPath ? readFileSync(publicKeyPath, 'utf8') : undefined,
749
753
  maxConcurrency: Number(input.maxConcurrency ?? 2),
750
754
  evaluationTimeoutMs: Number(input.timeoutMs ?? 120_000),
755
+ anchorPath: input.anchorPath ? String(input.anchorPath) : undefined,
756
+ anchorHash: input.anchorHash ? String(input.anchorHash) : undefined,
757
+ anchorManifestPath: input.anchorManifestPath ? String(input.anchorManifestPath) : undefined,
751
758
  });
752
759
  return { success: data.ran, data, degraded: false, exitCode: data.ran ? 0 : 1 };
753
760
  }
@@ -5,6 +5,7 @@
5
5
  * Replaces previous stub implementations with real state tracking.
6
6
  */
7
7
  import { type MCPTool } from './types.js';
8
+ import { type ApscDecision, type ApscSignal } from '../services/pheromone-adaptive.js';
8
9
  interface SwarmState {
9
10
  swarmId: string;
10
11
  topology: string;
@@ -32,6 +33,21 @@ interface SwarmStore {
32
33
  }
33
34
  export declare function loadSwarmStore(): SwarmStore;
34
35
  export declare function saveSwarmStore(store: SwarmStore): void;
36
+ /** Shared by hooks_post-task so outcome labels immediately drive APSC. */
37
+ export declare function recordSwarmPheromoneSignal(signal: ApscSignal): {
38
+ active: false;
39
+ reason: string;
40
+ } | {
41
+ active: true;
42
+ swarmId: string;
43
+ decision: ApscDecision;
44
+ };
45
+ /** Scheduling gate used by agent_execute. Dry-run topologies remain eligible. */
46
+ export declare function pheromoneAgentEligibility(agentId: string): {
47
+ eligible: boolean;
48
+ active: boolean;
49
+ reason?: string;
50
+ };
35
51
  export declare const swarmTools: MCPTool[];
36
52
  export {};
37
53
  //# sourceMappingURL=swarm-tools.d.ts.map