claude-flow 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 (45) hide show
  1. package/.claude/helpers/hook-handler.cjs +134 -3
  2. package/.claude/helpers/intelligence.cjs +83 -5
  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 +5 -0
  18. package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
  19. package/node_modules/@claude-flow/codex/dist/initializer.js +54 -20
  20. package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
  21. package/package.json +1 -1
  22. package/v3/@claude-flow/cli/bin/cli.js +25 -1
  23. package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
  24. package/v3/@claude-flow/cli/dist/src/commands/agent.js +1 -1
  25. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +159 -11
  26. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +39 -4
  27. package/v3/@claude-flow/cli/dist/src/commands/init.js +52 -8
  28. package/v3/@claude-flow/cli/dist/src/commands/mcp.js +1 -1
  29. package/v3/@claude-flow/cli/dist/src/commands/memory-distill.js +1 -0
  30. package/v3/@claude-flow/cli/dist/src/commands/swarm.js +28 -0
  31. package/v3/@claude-flow/cli/dist/src/init/executor.js +45 -10
  32. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +15 -4
  33. package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +18 -5
  34. package/v3/@claude-flow/cli/dist/src/mcp-server.d.ts +25 -0
  35. package/v3/@claude-flow/cli/dist/src/mcp-server.js +57 -2
  36. package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +51 -10
  37. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +34 -8
  38. package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +1 -1
  39. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +12 -0
  40. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +29 -6
  41. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +11 -8
  42. package/v3/@claude-flow/cli/dist/src/services/memory-distillation.d.ts +1 -0
  43. package/v3/@claude-flow/cli/dist/src/services/memory-distillation.js +81 -5
  44. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +1 -0
  45. package/v3/@claude-flow/cli/package.json +1 -1
@@ -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;
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Turns raw `memory_entries` (what ruflo has been RECORDING for thousands of
5
5
  * commits) into the structured intelligence substrate it has never populated:
6
- * `episodes` → `reasoning_patterns` (+ `pattern_embeddings`) → `causal_edges`.
6
+ * `episodes` (+ `episode_embeddings`) → `reasoning_patterns`
7
+ * (+ `pattern_embeddings`) → `causal_edges`.
7
8
  * This is the DISTILL/CONSOLIDATE half of the RETRIEVE→JUDGE→DISTILL→CONSOLIDATE
8
9
  * pipeline; RETRIEVE (embeddings) was already populated, the rest was empty
9
10
  * because the daemon's `consolidate` worker was a stub (see ADR-174 root cause).
@@ -28,7 +29,8 @@ import * as path from 'path';
28
29
  import { distillTrajectoryContent, serialiseDistilled } from '../memory/structured-distill.js';
29
30
  function emptyReport(dryRun, extra = {}) {
30
31
  return {
31
- processed: 0, episodes: 0, patterns: 0, patternEmbeddings: 0, causalEdges: 0,
32
+ processed: 0, episodes: 0, episodeEmbeddings: 0,
33
+ patterns: 0, patternEmbeddings: 0, causalEdges: 0,
32
34
  promoted: 0, byProvenance: {}, namespaces: [], dryRun, spendUsd: 0, ...extra,
33
35
  };
34
36
  }
@@ -74,6 +76,20 @@ function judgeFeedback(content) {
74
76
  catch { /* not JSON — treat as neutral */ }
75
77
  return { success: true, reward: 0.5 };
76
78
  }
79
+ /** Preserve an execution-observed lesson for Reflexion without presenting
80
+ * proxy/structural summaries as genuine self-critique. */
81
+ function feedbackCritique(content) {
82
+ try {
83
+ const value = JSON.parse(content);
84
+ for (const key of ['critique', 'error', 'message', 'summary', 'stderr']) {
85
+ if (typeof value[key] === 'string' && value[key].trim()) {
86
+ return value[key].trim().slice(0, 2000);
87
+ }
88
+ }
89
+ }
90
+ catch { /* retain the original execution feedback below */ }
91
+ return `Observed execution feedback: ${content}`.slice(0, 2000);
92
+ }
77
93
  /**
78
94
  * Distill accumulated memory into the structured intelligence tables.
79
95
  * Incremental, $0, transactional, provenance-tagged.
@@ -115,6 +131,17 @@ export async function runDistillation(options) {
115
131
  db.close();
116
132
  return { ...report, corrupt: true, skipped: 'memory DB reports corruption — run recoverMemoryDatabase first' };
117
133
  }
134
+ // #2677 — older AgentDB stores may have episodes but no embedding table.
135
+ // Create the stable AgentDB v1 table only after the corruption gate, and
136
+ // never create it during a dry-run.
137
+ if (!dryRun && !tableExists('episode_embeddings')) {
138
+ db.exec(`CREATE TABLE episode_embeddings (
139
+ episode_id INTEGER PRIMARY KEY,
140
+ embedding BLOB NOT NULL,
141
+ embedding_model TEXT DEFAULT 'all-MiniLM-L6-v2',
142
+ FOREIGN KEY(episode_id) REFERENCES episodes(id) ON DELETE CASCADE
143
+ )`);
144
+ }
118
145
  // M0: incremental cursor table (per namespace, by monotonic rowid).
119
146
  db.exec(`CREATE TABLE IF NOT EXISTS distill_state (
120
147
  namespace TEXT PRIMARY KEY,
@@ -128,10 +155,54 @@ export async function runDistillation(options) {
128
155
  const getCursor = db.prepare('SELECT last_rowid FROM distill_state WHERE namespace = ?');
129
156
  const setCursor = db.prepare('INSERT INTO distill_state (namespace, last_rowid, last_run_at) VALUES (?, ?, ?) ' +
130
157
  'ON CONFLICT(namespace) DO UPDATE SET last_rowid = excluded.last_rowid, last_run_at = excluded.last_run_at');
131
- const insEpisode = db.prepare('INSERT INTO episodes (session_id, task, input, output, reward, success, tags, metadata) VALUES (?,?,?,?,?,?,?,?)');
158
+ const insEpisode = db.prepare('INSERT INTO episodes (session_id, task, input, output, critique, reward, success, tags, metadata) VALUES (?,?,?,?,?,?,?,?,?)');
159
+ const insEpisodeEmbedding = dryRun ? null : db.prepare('INSERT OR REPLACE INTO episode_embeddings (episode_id, embedding) VALUES (?, ?)');
132
160
  const insPattern = db.prepare('INSERT INTO reasoning_patterns (task_type, approach, success_rate, uses, avg_reward, tags, metadata) VALUES (?,?,?,?,?,?,?)');
133
161
  const insPatEmb = db.prepare('INSERT OR REPLACE INTO pattern_embeddings (pattern_id, embedding) VALUES (?, ?)');
134
162
  const insEdge = db.prepare('INSERT INTO causal_edges (from_memory_id, from_memory_type, to_memory_id, to_memory_type, similarity, confidence, mechanism, metadata) VALUES (?,?,?,?,?,?,?,?)');
163
+ // Repair episodes produced by older distillation releases. Their metadata
164
+ // records sourceIds; if that provenance is absent, exact input matching is
165
+ // a conservative fallback. Never invent a vector or call a model.
166
+ if (!dryRun) {
167
+ const missingEpisodes = db.prepare(`
168
+ SELECT e.id, e.input, e.metadata
169
+ FROM episodes e
170
+ LEFT JOIN episode_embeddings ee ON ee.episode_id = e.id
171
+ WHERE ee.episode_id IS NULL AND e.session_id LIKE 'distill:%'
172
+ `).all();
173
+ const embeddingById = db.prepare('SELECT embedding FROM memory_entries WHERE id = ? AND embedding IS NOT NULL');
174
+ const embeddingByContent = db.prepare('SELECT embedding FROM memory_entries WHERE content = ? AND embedding IS NOT NULL LIMIT 1');
175
+ const missingCritiques = db.prepare(`
176
+ SELECT id, input FROM episodes
177
+ WHERE session_id LIKE 'distill:feedback%'
178
+ AND length(trim(coalesce(critique,''))) = 0
179
+ `).all();
180
+ const updateCritique = db.prepare('UPDATE episodes SET critique = ? WHERE id = ?');
181
+ const backfill = db.transaction(() => {
182
+ for (const episode of missingEpisodes) {
183
+ let sourceId;
184
+ try {
185
+ const metadata = JSON.parse(episode.metadata ?? '{}');
186
+ if (Array.isArray(metadata.sourceIds) && typeof metadata.sourceIds[0] === 'string') {
187
+ sourceId = metadata.sourceIds[0];
188
+ }
189
+ }
190
+ catch { /* use exact-content fallback */ }
191
+ let raw = sourceId ? embeddingById.get(sourceId)?.embedding : undefined;
192
+ if (!raw)
193
+ raw = embeddingByContent.get(episode.input ?? '')?.embedding;
194
+ const vector = parseEmbedding(raw);
195
+ if (!vector)
196
+ continue;
197
+ insEpisodeEmbedding.run(episode.id, Buffer.from(Float32Array.from(vector).buffer));
198
+ report.episodeEmbeddings++;
199
+ }
200
+ for (const episode of missingCritiques) {
201
+ updateCritique.run(feedbackCritique(episode.input ?? ''), episode.id);
202
+ }
203
+ });
204
+ backfill();
205
+ }
135
206
  let remaining = typeof maxEntries === 'number' ? maxEntries : Infinity;
136
207
  for (const ns of nsList) {
137
208
  if (remaining <= 0)
@@ -200,8 +271,14 @@ export async function runDistillation(options) {
200
271
  sourceIds: cl.members.map(m => m.id).slice(0, 25),
201
272
  paths: distilled.paths.slice(0, 10), namespace: ns, distilledBy: 'structural',
202
273
  };
203
- const epInfo = insEpisode.run(`distill:${ns}`, approach, cl.rep.content.slice(0, 2000), '', avgReward, promoted && cl.successCount > 0 ? 1 : 0, JSON.stringify(distilled.labels), JSON.stringify(provMeta));
274
+ const epInfo = insEpisode.run(`distill:${ns}`, approach, cl.rep.content.slice(0, 2000), '', cl.provenance === 'oracle:test-exec' ? feedbackCritique(cl.rep.content) : null, avgReward, promoted && cl.successCount > 0 ? 1 : 0, JSON.stringify(distilled.labels), JSON.stringify(provMeta));
204
275
  report.episodes++;
276
+ const epId = Number(epInfo.lastInsertRowid);
277
+ // ReflexionMemory.retrieveRelevant() INNER JOINs this table.
278
+ // Reuse the source embedding so every distilled episode is
279
+ // immediately retrievable without an embedding-model call.
280
+ insEpisodeEmbedding.run(epId, Buffer.from(Float32Array.from(embVec).buffer));
281
+ report.episodeEmbeddings++;
205
282
  const patInfo = insPattern.run(taskType, approach, successRate, uses, avgReward, JSON.stringify(distilled.labels), JSON.stringify(provMeta));
206
283
  const patternId = Number(patInfo.lastInsertRowid);
207
284
  report.patterns++;
@@ -217,7 +294,6 @@ export async function runDistillation(options) {
217
294
  // rowids). Explicitly typed co-occurrence / proxy-tier / non-
218
295
  // promoted: may rank retrieval, must NOT justify autonomous action
219
296
  // (ADR-174 edge contract).
220
- const epId = Number(epInfo.lastInsertRowid);
221
297
  if (prevEpId !== null) {
222
298
  insEdge.run(prevEpId, 'episode', epId, 'episode', 0, 0.3, 'co-occurrence', JSON.stringify({
223
299
  edge_type: 'cooccurrence',
@@ -1545,6 +1545,7 @@ export class WorkerDaemon extends EventEmitter {
1545
1545
  // instead of becoming their own distinct pattern.
1546
1546
  duplicatesRemoved: Math.max(0, report.processed - report.patterns),
1547
1547
  episodes: report.episodes,
1548
+ episodeEmbeddings: report.episodeEmbeddings,
1548
1549
  patternEmbeddings: report.patternEmbeddings,
1549
1550
  causalEdges: report.causalEdges,
1550
1551
  promoted: report.promoted,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.32.35",
3
+ "version": "3.32.37",
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",