@claude-flow/cli 3.32.35 → 3.32.37

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 (56) hide show
  1. package/.claude/helpers/helpers.manifest.json +5 -5
  2. package/.claude/helpers/hook-handler.cjs +30 -0
  3. package/.claude/helpers/intelligence.cjs +83 -5
  4. package/.claude/helpers/statusline.cjs +169 -6
  5. package/bin/cli.js +25 -1
  6. package/catalog-manifest.json +2 -2
  7. package/dist/src/commands/agent.js +1 -1
  8. package/dist/src/commands/doctor.js +159 -11
  9. package/dist/src/commands/hooks.js +39 -4
  10. package/dist/src/commands/init.js +52 -8
  11. package/dist/src/commands/mcp.js +1 -1
  12. package/dist/src/commands/memory-distill.js +1 -0
  13. package/dist/src/commands/swarm.js +28 -0
  14. package/dist/src/init/executor.js +45 -10
  15. package/dist/src/init/helpers-generator.js +15 -4
  16. package/dist/src/init/statusline-generator.js +18 -5
  17. package/dist/src/mcp-server.d.ts +25 -0
  18. package/dist/src/mcp-server.js +57 -2
  19. package/dist/src/mcp-tools/embeddings-tools.js +51 -10
  20. package/dist/src/mcp-tools/hooks-tools.js +34 -8
  21. package/dist/src/mcp-tools/metaharness-tools.js +1 -1
  22. package/dist/src/memory/memory-bridge.d.ts +12 -0
  23. package/dist/src/memory/memory-bridge.js +29 -6
  24. package/dist/src/memory/memory-initializer.js +11 -8
  25. package/dist/src/services/memory-distillation.d.ts +1 -0
  26. package/dist/src/services/memory-distillation.js +81 -5
  27. package/dist/src/services/worker-daemon.js +1 -0
  28. package/node_modules/@claude-flow/codex/.agents/skills/github-automation/SKILL.md +32 -0
  29. package/node_modules/@claude-flow/codex/.agents/skills/performance-analysis/SKILL.md +32 -0
  30. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts +4 -0
  31. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts.map +1 -1
  32. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +20 -1
  33. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
  34. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +4 -0
  35. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
  36. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +18 -2
  37. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
  38. package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts +11 -5
  39. package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts.map +1 -1
  40. package/node_modules/@claude-flow/codex/dist/generators/skill-md.js +84 -849
  41. package/node_modules/@claude-flow/codex/dist/generators/skill-md.js.map +1 -1
  42. package/node_modules/@claude-flow/codex/dist/initializer.d.ts +5 -0
  43. package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
  44. package/node_modules/@claude-flow/codex/dist/initializer.js +54 -20
  45. package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
  46. package/package.json +1 -1
  47. package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +4 -2
  48. package/plugins/ruflo-metaharness/scripts/_harness.mjs +5 -1
  49. package/plugins/ruflo-metaharness/scripts/_invoke.mjs +12 -13
  50. package/plugins/ruflo-metaharness/scripts/genome.mjs +29 -4
  51. package/plugins/ruflo-metaharness/scripts/mcp-scan.mjs +7 -8
  52. package/plugins/ruflo-metaharness/scripts/smoke.sh +58 -2
  53. package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +25 -1
  54. package/plugins/ruflo-metaharness/scripts/test-similarity.mjs +38 -0
  55. package/plugins/ruflo-metaharness/scripts/threat-model.mjs +4 -1
  56. package/plugins/ruflo-metaharness/skills/harness-genome/SKILL.md +5 -2
@@ -53,6 +53,16 @@ function getInstalledCliVersionLocal() {
53
53
  return '0.0.0';
54
54
  }
55
55
  }
56
+ function compareVersionsLocal(a, b) {
57
+ const pa = a.split(/[.-]/).map(part => Number.parseInt(part, 10) || 0);
58
+ const pb = b.split(/[.-]/).map(part => Number.parseInt(part, 10) || 0);
59
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
60
+ const delta = (pa[i] ?? 0) - (pb[i] ?? 0);
61
+ if (delta !== 0)
62
+ return delta;
63
+ }
64
+ return 0;
65
+ }
56
66
  /**
57
67
  * Generate optimized statusline script
58
68
  * Output format:
@@ -91,10 +101,12 @@ export function generateStatuslineScript(options) {
91
101
  // getInstalledCliVersionLocal() above — reuse the pattern for the
92
102
  // same reason it exists there.
93
103
  let helperContent = null;
104
+ let helperPackageRoot = '';
94
105
  try {
95
106
  const esmRequire = createRequire(import.meta.url);
96
107
  const pkgJsonPath = esmRequire.resolve('@claude-flow/cli/package.json');
97
- const helperPath = path.join(path.dirname(pkgJsonPath), '.claude', 'helpers', 'statusline.cjs');
108
+ helperPackageRoot = path.dirname(pkgJsonPath);
109
+ const helperPath = path.join(helperPackageRoot, '.claude', 'helpers', 'statusline.cjs');
98
110
  helperContent = fs.readFileSync(helperPath, 'utf-8');
99
111
  }
100
112
  catch {
@@ -105,6 +117,7 @@ export function generateStatuslineScript(options) {
105
117
  if (pkg && pkg.name === '@claude-flow/cli') {
106
118
  const candidate = path.join(dir, '.claude', 'helpers', 'statusline.cjs');
107
119
  if (fs.existsSync(candidate)) {
120
+ helperPackageRoot = dir;
108
121
  helperContent = fs.readFileSync(candidate, 'utf-8');
109
122
  break;
110
123
  }
@@ -128,15 +141,15 @@ export function generateStatuslineScript(options) {
128
141
  // whatever the helper hard-codes) ships. Add a paired test in
129
142
  // statusline-cost-display.test.ts before changing either token.
130
143
  helperContent = helperContent.replace(/maxAgents: \d+,/, `maxAgents: ${maxAgents},`);
144
+ helperContent = helperContent.replace(/const BAKED_INSTALL_ROOT = "[^"]*";/, `const BAKED_INSTALL_ROOT = ${JSON.stringify(helperPackageRoot)};`);
131
145
  // Only overwrite the helper's baked version if OURS resolves higher.
132
146
  // Otherwise the substitution could DOWNGRADE (test environments where
133
147
  // esmRequire.resolve happens to hit an older node_modules install would
134
- // clobber a fresh committed helper). Naive lexicographic compare — works
135
- // for canonical semver strings with same-width digit parts, which is
136
- // reliable at this stage of the version space.
148
+ // clobber a fresh committed helper). Compare numeric components:
149
+ // lexicographic comparison freezes 3.32.24 below 3.32.8 (#2811).
137
150
  const helperVerMatch = helperContent.match(/let ver = "([^"]+)";/);
138
151
  const helperVer = helperVerMatch ? helperVerMatch[1] : '';
139
- if (!helperVer || bakedVersion > helperVer) {
152
+ if (!helperVer || compareVersionsLocal(bakedVersion, helperVer) > 0) {
140
153
  helperContent = helperContent.replace(/let ver = "[^"]+";/, `let ver = ${JSON.stringify(bakedVersion)};`);
141
154
  }
142
155
  return helperContent;
@@ -48,6 +48,31 @@ export interface MCPServerStatus {
48
48
  metrics?: Record<string, number>;
49
49
  };
50
50
  }
51
+ export declare function parseMcpToolSelection(value: string | undefined): string[] | 'all';
52
+ /**
53
+ * Apply the existing `--tools` contract to advertised schemas. A selector can
54
+ * be an exact tool name, a category, or a namespace prefix (`memory` matches
55
+ * `memory_store`). Execution remains registered internally; only the fixed
56
+ * per-request schema catalogue is reduced.
57
+ */
58
+ export declare function filterAdvertisedMcpTools<T extends {
59
+ name: string;
60
+ category?: string;
61
+ }>(tools: T[], selection: string[] | 'all'): T[];
62
+ export interface McpSchemaOverhead {
63
+ toolCount: number;
64
+ bytes: number;
65
+ estimatedTokens: number;
66
+ contextWindowTokens?: number;
67
+ ratio?: number;
68
+ risk: 'normal' | 'high';
69
+ }
70
+ /** Conservative JSON-size estimate for the fixed tools/list catalogue. */
71
+ export declare function assessMcpSchemaOverhead(tools: Array<{
72
+ name: string;
73
+ description?: string;
74
+ inputSchema?: unknown;
75
+ }>, contextWindowTokens?: number): McpSchemaOverhead;
51
76
  /**
52
77
  * MCP Server Manager
53
78
  *
@@ -42,6 +42,54 @@ const DEFAULT_OPTIONS = {
42
42
  daemonize: false,
43
43
  timeout: 30000,
44
44
  };
45
+ export function parseMcpToolSelection(value) {
46
+ if (!value || value.trim().toLowerCase() === 'all')
47
+ return 'all';
48
+ const selectors = value.split(',').map((item) => item.trim()).filter(Boolean);
49
+ return selectors.length > 0 ? selectors : 'all';
50
+ }
51
+ /**
52
+ * Apply the existing `--tools` contract to advertised schemas. A selector can
53
+ * be an exact tool name, a category, or a namespace prefix (`memory` matches
54
+ * `memory_store`). Execution remains registered internally; only the fixed
55
+ * per-request schema catalogue is reduced.
56
+ */
57
+ export function filterAdvertisedMcpTools(tools, selection) {
58
+ if (selection === 'all')
59
+ return tools;
60
+ const selectors = new Set(selection.map((item) => item.toLowerCase()));
61
+ return tools.filter((tool) => {
62
+ const name = tool.name.toLowerCase();
63
+ const category = tool.category?.toLowerCase();
64
+ return selectors.has(name)
65
+ || (category !== undefined && selectors.has(category))
66
+ || Array.from(selectors).some((selector) => name.startsWith(`${selector}_`));
67
+ });
68
+ }
69
+ /** Conservative JSON-size estimate for the fixed tools/list catalogue. */
70
+ export function assessMcpSchemaOverhead(tools, contextWindowTokens) {
71
+ const catalogue = tools.map((tool) => ({
72
+ name: tool.name,
73
+ description: tool.description,
74
+ inputSchema: tool.inputSchema,
75
+ }));
76
+ const bytes = Buffer.byteLength(JSON.stringify(catalogue), 'utf8');
77
+ const estimatedTokens = Math.ceil(bytes / 4);
78
+ const validWindow = Number.isFinite(contextWindowTokens) && Number(contextWindowTokens) > 0
79
+ ? Number(contextWindowTokens)
80
+ : undefined;
81
+ const ratio = validWindow ? estimatedTokens / validWindow : undefined;
82
+ return {
83
+ toolCount: tools.length,
84
+ bytes,
85
+ estimatedTokens,
86
+ ...(validWindow === undefined ? {} : { contextWindowTokens: validWindow }),
87
+ ...(ratio === undefined ? {} : { ratio }),
88
+ risk: (ratio !== undefined ? ratio >= 0.2 : estimatedTokens >= 8_000)
89
+ ? 'high'
90
+ : 'normal',
91
+ };
92
+ }
45
93
  /**
46
94
  * MCP Server Manager
47
95
  *
@@ -55,7 +103,14 @@ export class MCPServerManager extends EventEmitter {
55
103
  healthCheckInterval;
56
104
  constructor(options = {}) {
57
105
  super();
58
- this.options = { ...DEFAULT_OPTIONS, ...options };
106
+ // `options.tools`, populated by the `mcp start --tools` CLI flag, is
107
+ // spread last below and therefore takes precedence over this env fallback.
108
+ const environmentTools = parseMcpToolSelection(process.env.CLAUDE_FLOW_MCP_TOOLS);
109
+ this.options = {
110
+ ...DEFAULT_OPTIONS,
111
+ ...(environmentTools === 'all' ? {} : { tools: environmentTools }),
112
+ ...options,
113
+ };
59
114
  }
60
115
  /**
61
116
  * Start the MCP server
@@ -420,7 +475,7 @@ export class MCPServerManager extends EventEmitter {
420
475
  },
421
476
  };
422
477
  case 'tools/list':
423
- const tools = listMCPTools();
478
+ const tools = filterAdvertisedMcpTools(listMCPTools(), this.options.tools);
424
479
  return {
425
480
  jsonrpc: '2.0',
426
481
  id: message.id,
@@ -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
  },
@@ -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: {
@@ -1074,21 +1092,28 @@ export const hooksMetrics = {
1074
1092
  agentCounts[o.agent] = (agentCounts[o.agent] || 0) + 1;
1075
1093
  }
1076
1094
  const topAgent = Object.entries(agentCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? null;
1077
- const successful = stats.trajectories.successful;
1078
- const total = stats.trajectories.total;
1079
- const failed = Math.max(0, total - successful);
1080
1095
  return {
1081
1096
  _real: true,
1082
1097
  _dataSource: 'intelligence-stats + routing-outcomes',
1083
1098
  period,
1084
1099
  patterns: {
1085
1100
  total: stats.patterns.learned,
1086
- successful,
1087
- 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,
1088
1108
  avgConfidence: stats.routing.avgConfidence || null,
1089
1109
  },
1090
1110
  agents: {
1091
- 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,
1092
1117
  totalRoutes: stats.routing.decisions,
1093
1118
  topAgent,
1094
1119
  },
@@ -1097,7 +1122,7 @@ export const hooksMetrics = {
1097
1122
  successRate,
1098
1123
  avgRiskScore: null,
1099
1124
  },
1100
- _note: total === 0 && totalCommands === 0
1125
+ _note: stats.patterns.learned === 0 && totalCommands === 0
1101
1126
  ? 'No metrics data collected yet. Run hooks_post-task / hooks_intelligence_trajectory-end / hooks_route to populate.'
1102
1127
  : undefined,
1103
1128
  lastUpdated: new Date().toISOString(),
@@ -1311,6 +1336,7 @@ export const hooksPostTask = {
1311
1336
  const bridge = await import('../memory/memory-bridge.js');
1312
1337
  feedbackResult = await bridge.bridgeRecordFeedback({
1313
1338
  taskId,
1339
+ task: params.task || undefined,
1314
1340
  success,
1315
1341
  quality,
1316
1342
  agent,
@@ -3136,7 +3162,7 @@ export const hooksIntelligenceStats = {
3136
3162
  catch {
3137
3163
  memoryStats = {
3138
3164
  trajectories: { total: 0, successful: 0 },
3139
- patterns: { learned: 0, categories: {} },
3165
+ patterns: { learned: 0, successful: 0, failed: 0, unknown: 0, described: 0, categories: {} },
3140
3166
  memory: { indexSize: 0, totalAccessCount: 0, memorySizeBytes: 0 },
3141
3167
  routing: { decisions: 0, avgConfidence: 0 },
3142
3168
  };
@@ -199,7 +199,7 @@ export const metaharnessTools = [
199
199
  },
200
200
  {
201
201
  name: 'metaharness_genome',
202
- description: 'ADR-150 — 7-section categorical readiness report from `metaharness genome <path>` (repo_type / agent_topology / risk_score / mcp_surface / test_confidence / publish_readiness). Use when you need the categorical view (vs numeric score). Pair with metaharness_score for the full readiness picture — score-alone is wrong because two harnesses with the same harnessFit can have very different agent_topology and mcp_surface. ' + MCP_SUCCESS_SEMANTIC,
202
+ description: 'ADR-150 — 7-section categorical readiness report from `metaharness genome <path>` (repo_type / agent_topology / risk_score / mcp_surface / test_confidence / publish_readiness). Upstream needs-work/blocked exit statuses are valid verdicts and return successfully as data.verdict + data.verdictExitCode; only a missing or malformed report is an error. Use when you need the categorical view (vs numeric score). Pair with metaharness_score for the full readiness picture — score-alone is wrong because two harnesses with the same harnessFit can have very different agent_topology and mcp_surface. ' + MCP_SUCCESS_SEMANTIC,
203
203
  category: 'metaharness',
204
204
  inputSchema: {
205
205
  type: 'object',
@@ -275,6 +275,16 @@ export declare function getControllerRegistry(dbPath?: string): Promise<any | nu
275
275
  * operator learns the cause instead of only the symptom.
276
276
  */
277
277
  export declare function getBridgeFailureReason(): string | null;
278
+ /**
279
+ * Install a pre-initialized registry for deterministic bridge tests.
280
+ *
281
+ * The CLI test runner intentionally externalizes the optional
282
+ * `@claude-flow/memory` package so an unbuilt workspace can still exercise
283
+ * fallback paths. That also makes module-level mocking of ControllerRegistry
284
+ * environment-dependent. This narrow seam keeps native SQL regression tests
285
+ * independent of package build order without changing production startup.
286
+ */
287
+ export declare function __setMemoryBridgeRegistryForTests(registry: any | null): void;
278
288
  /**
279
289
  * Shutdown the bridge and release resources.
280
290
  *
@@ -322,6 +332,8 @@ export declare function bridgeSearchPatterns(options: {
322
332
  */
323
333
  export declare function bridgeRecordFeedback(options: {
324
334
  taskId: string;
335
+ /** Human-readable task text used as the semantic learning signal. */
336
+ task?: string;
325
337
  success: boolean;
326
338
  quality: number;
327
339
  agent?: string;
@@ -23,6 +23,10 @@ import { createRequire } from 'node:module';
23
23
  let registryPromise = null;
24
24
  let registryInstance = null;
25
25
  let bridgeAvailable = null;
26
+ // #2652/#2120: rows created before the status column existed receive NULL
27
+ // during migration. They are live rows, not tombstones. Every user-facing
28
+ // read/delete path must agree with list() about their visibility.
29
+ const ACTIVE_MEMORY_ROW_SQL = `(status = 'active' OR status IS NULL)`;
26
30
  /**
27
31
  * Why the bridge is unavailable, when it is.
28
32
  *
@@ -994,7 +998,7 @@ export async function bridgeSearchEntries(options) {
994
998
  const stmt = ctx.db.prepare(`
995
999
  SELECT id, key, namespace, content, embedding, provenance_type
996
1000
  FROM memory_entries
997
- WHERE status = 'active' ${whereExtra}
1001
+ WHERE ${ACTIVE_MEMORY_ROW_SQL} ${whereExtra}
998
1002
  LIMIT 1000
999
1003
  `);
1000
1004
  rows = filterParams.length > 0 ? stmt.all(...filterParams) : stmt.all();
@@ -1114,7 +1118,7 @@ export async function bridgeListEntries(options) {
1114
1118
  // the `status = 'active'` filter matched zero. Treat NULL as
1115
1119
  // "legacy-active" — the safe default for any entry that predates the
1116
1120
  // status column.
1117
- const statusFilter = `(status = 'active' OR status IS NULL)`;
1121
+ const statusFilter = ACTIVE_MEMORY_ROW_SQL;
1118
1122
  // Count
1119
1123
  let total = 0;
1120
1124
  try {
@@ -1206,7 +1210,7 @@ export async function bridgeGetEntry(options) {
1206
1210
  const stmt = ctx.db.prepare(`
1207
1211
  SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, tags
1208
1212
  FROM memory_entries
1209
- WHERE status = 'active' AND key = ? AND namespace = ?
1213
+ WHERE ${ACTIVE_MEMORY_ROW_SQL} AND key = ? AND namespace = ?
1210
1214
  LIMIT 1
1211
1215
  `);
1212
1216
  row = stmt.get(key, namespace);
@@ -1274,7 +1278,7 @@ export async function bridgeDeleteEntry(options) {
1274
1278
  const result = ctx.db.prepare(`
1275
1279
  UPDATE memory_entries
1276
1280
  SET status = 'deleted', updated_at = ?
1277
- WHERE key = ? AND namespace = ? AND status = 'active'
1281
+ WHERE key = ? AND namespace = ? AND ${ACTIVE_MEMORY_ROW_SQL}
1278
1282
  `).run(Date.now(), key, namespace);
1279
1283
  changes = result?.changes ?? 0;
1280
1284
  }
@@ -1306,7 +1310,7 @@ export async function bridgeDeleteEntry(options) {
1306
1310
  }
1307
1311
  let remaining = 0;
1308
1312
  try {
1309
- const row = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE status = 'active'`).get();
1313
+ const row = ctx.db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE ${ACTIVE_MEMORY_ROW_SQL}`).get();
1310
1314
  remaining = row?.cnt ?? 0;
1311
1315
  }
1312
1316
  catch {
@@ -1659,6 +1663,21 @@ export async function getControllerRegistry(dbPath) {
1659
1663
  export function getBridgeFailureReason() {
1660
1664
  return bridgeFailureReason;
1661
1665
  }
1666
+ /**
1667
+ * Install a pre-initialized registry for deterministic bridge tests.
1668
+ *
1669
+ * The CLI test runner intentionally externalizes the optional
1670
+ * `@claude-flow/memory` package so an unbuilt workspace can still exercise
1671
+ * fallback paths. That also makes module-level mocking of ControllerRegistry
1672
+ * environment-dependent. This narrow seam keeps native SQL regression tests
1673
+ * independent of package build order without changing production startup.
1674
+ */
1675
+ export function __setMemoryBridgeRegistryForTests(registry) {
1676
+ registryInstance = registry;
1677
+ registryPromise = registry ? Promise.resolve(registry) : null;
1678
+ bridgeAvailable = registry ? true : null;
1679
+ bridgeFailureReason = null;
1680
+ }
1662
1681
  /**
1663
1682
  * Shutdown the bridge and release resources.
1664
1683
  *
@@ -1840,11 +1859,15 @@ export async function bridgeRecordFeedback(options) {
1840
1859
  try {
1841
1860
  const intelligence = await import('./intelligence.js');
1842
1861
  const verdict = options.success ? 'success' : 'failure';
1862
+ const taskContext = options.task?.trim();
1843
1863
  const recorded = await intelligence.recordTrajectory([{
1844
1864
  type: 'action',
1845
- content: `Task ${options.taskId} completed by ${options.agent || 'unknown'} success=${options.success}, quality=${options.quality.toFixed(3)}`,
1865
+ // Put the task description first so the embedding represents the
1866
+ // work, not just the bookkeeping suffix (#2812).
1867
+ content: `${taskContext ? `${taskContext} — ` : ''}Task ${options.taskId} completed by ${options.agent || 'unknown'} — success=${options.success}, quality=${options.quality.toFixed(3)}`,
1846
1868
  metadata: {
1847
1869
  taskId: options.taskId,
1870
+ task: taskContext,
1848
1871
  agent: options.agent,
1849
1872
  quality: options.quality,
1850
1873
  duration: options.duration,
@@ -153,6 +153,10 @@ export function resolveDbPath(cliFlag) {
153
153
  }
154
154
  return path.join(getMemoryRoot(), 'memory.db');
155
155
  }
156
+ // #2652/#2120: legacy rows with NULL status predate soft-delete semantics and
157
+ // are active. Keep fallback retrieve/delete aligned with list and the native
158
+ // bridge instead of making a row visible to one command but not another.
159
+ const ACTIVE_MEMORY_ROW_SQL = `(status = 'active' OR status IS NULL)`;
156
160
  // ADR-053: Lazy import of AgentDB v3 bridge
157
161
  let _bridge;
158
162
  async function getBridge() {
@@ -1608,8 +1612,7 @@ export async function repairVectorIndexes(dbPath, opts = {}) {
1608
1612
  */
1609
1613
  export async function initializeMemoryDatabase(options) {
1610
1614
  const { backend = 'hybrid', dbPath: customPath, force = false, verbose = false, migrate = true } = options;
1611
- const swarmDir = getMemoryRoot();
1612
- const dbPath = customPath || path.join(swarmDir, 'memory.db');
1615
+ const dbPath = resolveDbPath(customPath);
1613
1616
  const dbDir = path.dirname(dbPath);
1614
1617
  try {
1615
1618
  // Create directory if needed
@@ -2857,7 +2860,7 @@ export async function listEntries(options) {
2857
2860
  // that predate the status column may have NULL after migration.
2858
2861
  // See memory-bridge.ts:bridgeListEntries for full context.
2859
2862
  // Get total count
2860
- const whereClauses = [`(status = 'active' OR status IS NULL)`];
2863
+ const whereClauses = [ACTIVE_MEMORY_ROW_SQL];
2861
2864
  const whereParams = [];
2862
2865
  if (namespace) {
2863
2866
  whereClauses.push('namespace = ?');
@@ -2967,7 +2970,7 @@ export async function getEntry(options) {
2967
2970
  const getStmt = db.prepare(`
2968
2971
  SELECT id, key, namespace, content, embedding, access_count, created_at, updated_at, tags
2969
2972
  FROM memory_entries
2970
- WHERE status = 'active'
2973
+ WHERE ${ACTIVE_MEMORY_ROW_SQL}
2971
2974
  AND key = ?
2972
2975
  AND namespace = ?
2973
2976
  LIMIT 1
@@ -3081,7 +3084,7 @@ export async function deleteEntry(options) {
3081
3084
  // Check if entry exists first
3082
3085
  const checkStmt = db.prepare(`
3083
3086
  SELECT id FROM memory_entries
3084
- WHERE status = 'active'
3087
+ WHERE ${ACTIVE_MEMORY_ROW_SQL}
3085
3088
  AND key = ?
3086
3089
  AND namespace = ?
3087
3090
  LIMIT 1
@@ -3095,7 +3098,7 @@ export async function deleteEntry(options) {
3095
3098
  const checkResult = checkRows.length > 0 ? [{ values: checkRows }] : [];
3096
3099
  if (!checkResult[0]?.values?.[0]) {
3097
3100
  // Get remaining count before closing
3098
- const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE status = 'active'`);
3101
+ const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE ${ACTIVE_MEMORY_ROW_SQL}`);
3099
3102
  const remainingEntries = countResult[0]?.values?.[0]?.[0] || 0;
3100
3103
  db.close();
3101
3104
  return {
@@ -3116,10 +3119,10 @@ export async function deleteEntry(options) {
3116
3119
  updated_at = strftime('%s', 'now') * 1000
3117
3120
  WHERE key = ?
3118
3121
  AND namespace = ?
3119
- AND status = 'active'
3122
+ AND ${ACTIVE_MEMORY_ROW_SQL}
3120
3123
  `, [key, namespace]);
3121
3124
  // Get remaining count
3122
- const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE status = 'active'`);
3125
+ const countResult = db.exec(`SELECT COUNT(*) FROM memory_entries WHERE ${ACTIVE_MEMORY_ROW_SQL}`);
3123
3126
  const remainingEntries = countResult[0]?.values?.[0]?.[0] || 0;
3124
3127
  // Save updated database
3125
3128
  const data = db.export();
@@ -20,6 +20,7 @@ export interface DistillOptions {
20
20
  export interface DistillReport {
21
21
  processed: number;
22
22
  episodes: number;
23
+ episodeEmbeddings: number;
23
24
  patterns: number;
24
25
  patternEmbeddings: number;
25
26
  causalEdges: number;