@claude-flow/cli 3.57.1 → 3.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.claude/helpers/.helpers-version +1 -1
  2. package/.claude/helpers/helpers.manifest.json +3 -3
  3. package/.claude/helpers/intelligence.cjs +3 -5
  4. package/README.md +1 -0
  5. package/catalog-manifest.json +2 -2
  6. package/dist/src/commands/agent.js +4 -1
  7. package/dist/src/commands/hooks.js +20 -1
  8. package/dist/src/commands/mcp.js +66 -6
  9. package/dist/src/commands/memory.js +78 -3
  10. package/dist/src/commands/workflow.js +55 -15
  11. package/dist/src/init/executor.js +9 -5
  12. package/dist/src/init/helper-companions.d.ts +1 -1
  13. package/dist/src/init/helper-companions.js +4 -3
  14. package/dist/src/mcp-server.d.ts +5 -0
  15. package/dist/src/mcp-server.js +39 -0
  16. package/dist/src/mcp-tools/agent-tools.js +28 -0
  17. package/dist/src/mcp-tools/claims-tools.js +25 -15
  18. package/dist/src/mcp-tools/hooks-tools.js +9 -3
  19. package/dist/src/mcp-tools/memory-tools.js +58 -9
  20. package/dist/src/mcp-tools/workflow-tools.js +2 -1
  21. package/dist/src/memory/graph-edge-writer.d.ts +16 -4
  22. package/dist/src/memory/graph-edge-writer.js +19 -7
  23. package/dist/src/memory/intelligence.js +4 -0
  24. package/dist/src/memory/memory-initializer.js +55 -9
  25. package/dist/src/memory/memory-peek.d.ts +28 -0
  26. package/dist/src/memory/memory-peek.js +222 -0
  27. package/dist/src/memory/model-cache-dir.d.ts +11 -0
  28. package/dist/src/memory/model-cache-dir.js +17 -0
  29. package/dist/src/ruvector/task-embedder.js +38 -4
  30. package/dist/src/services/memory-backup.d.ts +6 -0
  31. package/dist/src/services/memory-backup.js +109 -81
  32. package/dist/src/services/policy-ledger-anchor.d.ts +2 -0
  33. package/dist/src/services/policy-ledger-anchor.js +4 -4
  34. package/dist/src/services/policy-runtime.js +54 -3
  35. package/dist/src/services/worker-daemon.js +4 -0
  36. package/node_modules/@claude-flow/codex/README.md +87 -9
  37. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts +3 -0
  38. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts.map +1 -0
  39. package/node_modules/@claude-flow/codex/dist/activity/cli.js +115 -0
  40. package/node_modules/@claude-flow/codex/dist/activity/cli.js.map +1 -0
  41. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts +14 -0
  42. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts.map +1 -0
  43. package/node_modules/@claude-flow/codex/dist/activity/files.js +142 -0
  44. package/node_modules/@claude-flow/codex/dist/activity/files.js.map +1 -0
  45. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts +21 -0
  46. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts.map +1 -0
  47. package/node_modules/@claude-flow/codex/dist/activity/parser.js +179 -0
  48. package/node_modules/@claude-flow/codex/dist/activity/parser.js.map +1 -0
  49. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts +20 -0
  50. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts.map +1 -0
  51. package/node_modules/@claude-flow/codex/dist/activity/reader.js +106 -0
  52. package/node_modules/@claude-flow/codex/dist/activity/reader.js.map +1 -0
  53. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts +4 -0
  54. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts.map +1 -0
  55. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js +27 -0
  56. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js.map +1 -0
  57. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts +31 -0
  58. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts.map +1 -0
  59. package/node_modules/@claude-flow/codex/dist/activity/types.js +5 -0
  60. package/node_modules/@claude-flow/codex/dist/activity/types.js.map +1 -0
  61. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts +21 -0
  62. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts.map +1 -0
  63. package/node_modules/@claude-flow/codex/dist/activity/viewer.js +168 -0
  64. package/node_modules/@claude-flow/codex/dist/activity/viewer.js.map +1 -0
  65. package/node_modules/@claude-flow/codex/dist/cli.js +3 -0
  66. package/node_modules/@claude-flow/codex/dist/cli.js.map +1 -1
  67. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +3 -3
  68. package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
  69. package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -1
  70. package/node_modules/@claude-flow/mcp/dist/server.js +5 -3
  71. package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -1
  72. package/node_modules/@claude-flow/mcp/dist/types.d.ts +11 -0
  73. package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -1
  74. package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -1
  75. package/package.json +5 -4
  76. package/plugins/ruflo-console/dist/tui.mjs +159 -159
@@ -216,6 +216,7 @@ export const agentTools = [
216
216
  properties: {
217
217
  agentType: { type: 'string', description: 'Type of agent to spawn' },
218
218
  agentId: { type: 'string', description: 'Optional custom agent ID' },
219
+ name: { type: 'string', description: 'Optional display name for the agent (shown by agent_list, agent_status and ruflo-console)' },
219
220
  // #2085 — accept swarmId so spawned agents register in the
220
221
  // swarm.agents array that swarm_status reports. Omit to register
221
222
  // with the most-recently-created swarm.
@@ -241,6 +242,18 @@ export const agentTools = [
241
242
  }
242
243
  const agentId = input.agentId || `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
243
244
  const agentType = input.agentType;
245
+ // #4300 — older callers (including `agent spawn` before this fix) send
246
+ // the name only as metadata.name.
247
+ const metadata = input.metadata;
248
+ const rawName = input.name ?? metadata?.name;
249
+ let name;
250
+ if (rawName !== undefined && rawName !== '') {
251
+ const vName = validateText(rawName, 'name', 200);
252
+ if (!vName.valid) {
253
+ return { success: false, error: `Input validation failed: ${vName.error}` };
254
+ }
255
+ name = vName.sanitized;
256
+ }
244
257
  const config = input.config || {};
245
258
  // Add explicit model to config if provided
246
259
  if (input.model) {
@@ -262,6 +275,7 @@ export const agentTools = [
262
275
  const agent = {
263
276
  agentId,
264
277
  agentType,
278
+ ...(name ? { name } : {}),
265
279
  status: 'idle',
266
280
  health: 1.0,
267
281
  taskCount: 0,
@@ -347,6 +361,7 @@ export const agentTools = [
347
361
  success: true,
348
362
  agentId,
349
363
  agentType: agent.agentType,
364
+ ...(agent.name ? { name: agent.name } : {}),
350
365
  model: agent.model,
351
366
  modelRoutedBy: routingResult.routedBy,
352
367
  ...(routingResult.modelId ? { modelId: routingResult.modelId } : {}),
@@ -501,6 +516,7 @@ export const agentTools = [
501
516
  return {
502
517
  agentId: agent.agentId,
503
518
  agentType: agent.agentType,
519
+ ...(agent.name ? { name: agent.name } : {}),
504
520
  status: agent.status,
505
521
  health: agent.health,
506
522
  taskCount: agent.taskCount,
@@ -525,6 +541,7 @@ export const agentTools = [
525
541
  properties: {
526
542
  status: { type: 'string', description: 'Filter by status' },
527
543
  domain: { type: 'string', description: 'Filter by domain' },
544
+ agentType: { type: 'string', description: 'Filter by agent type' },
528
545
  includeTerminated: { type: 'boolean', description: 'Include terminated agents' },
529
546
  },
530
547
  },
@@ -539,6 +556,11 @@ export const agentTools = [
539
556
  if (!v.valid)
540
557
  return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
541
558
  }
559
+ if (input.agentType) {
560
+ const v = validateIdentifier(input.agentType, 'agentType');
561
+ if (!v.valid)
562
+ return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
563
+ }
542
564
  let agents = Object.values(loadAllAgents()); // #1916: includes hive-mind-spawned workers
543
565
  // Filter by status
544
566
  if (input.status) {
@@ -551,10 +573,15 @@ export const agentTools = [
551
573
  if (input.domain) {
552
574
  agents = agents.filter(a => a.domain === input.domain);
553
575
  }
576
+ // Filter by agent type
577
+ if (input.agentType) {
578
+ agents = agents.filter(a => a.agentType === input.agentType);
579
+ }
554
580
  return {
555
581
  agents: agents.map(a => ({
556
582
  agentId: a.agentId,
557
583
  agentType: a.agentType,
584
+ ...(a.name ? { name: a.name } : {}),
558
585
  status: a.status,
559
586
  health: a.health,
560
587
  taskCount: a.taskCount,
@@ -565,6 +592,7 @@ export const agentTools = [
565
592
  filters: {
566
593
  status: input.status,
567
594
  domain: input.domain,
595
+ agentType: input.agentType,
568
596
  includeTerminated: input.includeTerminated,
569
597
  },
570
598
  };
@@ -13,6 +13,7 @@ import { existsSync, writeFileSync, mkdirSync } from 'fs';
13
13
  import { join, resolve } from 'path';
14
14
  const CLAIMS_DIR = '.claude-flow/claims';
15
15
  const CLAIMS_FILE = 'claims.json';
16
+ const DEFAULT_MAX_CLAIMS = 5;
16
17
  function getClaimsPath() {
17
18
  return resolve(join(CLAIMS_DIR, CLAIMS_FILE));
18
19
  }
@@ -47,7 +48,7 @@ function parseClaimant(str) {
47
48
  export const claimsTools = [
48
49
  {
49
50
  name: 'claims_claim',
50
- description: 'Claim an issue for work (human or agent) Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
51
+ description: 'Claim an issue for work (human or agent). Use when multiple agents might pick up the same issue — claiming makes ownership visible via claims_list before work starts.',
51
52
  category: 'claims',
52
53
  inputSchema: {
53
54
  type: 'object',
@@ -64,6 +65,10 @@ export const claimsTools = [
64
65
  type: 'string',
65
66
  description: 'Optional context about the work approach',
66
67
  },
68
+ ttlMs: {
69
+ type: 'number',
70
+ description: 'Optional time-to-live in milliseconds; when set, the claim records an expiresAt timestamp. Informational only today — no handler currently enforces it (e.g. claims_steal still requires claims_mark-stealable rather than checking expiry).',
71
+ },
67
72
  },
68
73
  required: ['issueId', 'claimant'],
69
74
  },
@@ -71,6 +76,7 @@ export const claimsTools = [
71
76
  const issueId = input.issueId;
72
77
  const claimantStr = input.claimant;
73
78
  const context = input.context;
79
+ const ttlMs = input.ttlMs;
74
80
  {
75
81
  const v = validateIdentifier(issueId, 'issueId');
76
82
  if (!v.valid)
@@ -86,6 +92,9 @@ export const claimsTools = [
86
92
  if (!v.valid)
87
93
  return { success: false, error: v.error };
88
94
  }
95
+ if (ttlMs !== undefined && (typeof ttlMs !== 'number' || !Number.isFinite(ttlMs) || ttlMs <= 0)) {
96
+ return { success: false, error: 'ttlMs must be a positive number' };
97
+ }
89
98
  const claimant = parseClaimant(claimantStr);
90
99
  if (!claimant) {
91
100
  return { success: false, error: 'Invalid claimant format. Use "human:userId:name" or "agent:agentId:agentType"' };
@@ -109,6 +118,7 @@ export const claimsTools = [
109
118
  statusChangedAt: now,
110
119
  progress: 0,
111
120
  context,
121
+ expiresAt: ttlMs !== undefined ? new Date(Date.now() + ttlMs).toISOString() : undefined,
112
122
  };
113
123
  store.claims[issueId] = claim;
114
124
  saveClaims(store);
@@ -121,7 +131,7 @@ export const claimsTools = [
121
131
  },
122
132
  {
123
133
  name: 'claims_release',
124
- description: 'Release a claim on an issue Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
134
+ description: 'Release a claim on an issue. Use when the claimant stops working on it, freeing it for another claims_claim instead of leaving it claimed indefinitely.',
125
135
  category: 'claims',
126
136
  inputSchema: {
127
137
  type: 'object',
@@ -186,7 +196,7 @@ export const claimsTools = [
186
196
  },
187
197
  {
188
198
  name: 'claims_handoff',
189
- description: 'Request handoff of an issue to another claimant Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
199
+ description: 'Request handoff of an issue to another claimant. Use when transferring in-progress work to a different agent, instead of releasing and having them claim it fresh.',
190
200
  category: 'claims',
191
201
  inputSchema: {
192
202
  type: 'object',
@@ -270,7 +280,7 @@ export const claimsTools = [
270
280
  },
271
281
  {
272
282
  name: 'claims_accept-handoff',
273
- description: 'Accept a pending handoff Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
283
+ description: 'Accept a pending handoff. Use when the receiving claimant confirms a claims_handoff request, completing the ownership transfer.',
274
284
  category: 'claims',
275
285
  inputSchema: {
276
286
  type: 'object',
@@ -333,7 +343,7 @@ export const claimsTools = [
333
343
  },
334
344
  {
335
345
  name: 'claims_status',
336
- description: 'Update claim status Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
346
+ description: 'Update claim status. Use when progress changes (active, blocked, review-requested, etc.) so claims_list and claims_load reflect current state.',
337
347
  category: 'claims',
338
348
  inputSchema: {
339
349
  type: 'object',
@@ -398,7 +408,7 @@ export const claimsTools = [
398
408
  },
399
409
  {
400
410
  name: 'claims_list',
401
- description: 'List all claims or filter by criteria Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
411
+ description: 'List all claims or filter by criteria. Use when checking what is already claimed before starting new work, instead of guessing from issue comments.',
402
412
  category: 'claims',
403
413
  inputSchema: {
404
414
  type: 'object',
@@ -453,7 +463,7 @@ export const claimsTools = [
453
463
  },
454
464
  {
455
465
  name: 'claims_mark-stealable',
456
- description: 'Mark an issue as stealable by other agents Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
466
+ description: 'Mark an issue as stealable by other agents. Use when a claimant is overloaded or stalled and wants another agent to pick it up via claims_steal.',
457
467
  category: 'claims',
458
468
  inputSchema: {
459
469
  type: 'object',
@@ -521,7 +531,7 @@ export const claimsTools = [
521
531
  },
522
532
  {
523
533
  name: 'claims_steal',
524
- description: 'Steal a stealable issue Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
534
+ description: 'Steal a stealable issue. Use when picking up work already marked stealable via claims_mark-stealable, rather than claiming an issue someone else still owns.',
525
535
  category: 'claims',
526
536
  inputSchema: {
527
537
  type: 'object',
@@ -592,7 +602,7 @@ export const claimsTools = [
592
602
  },
593
603
  {
594
604
  name: 'claims_stealable',
595
- description: 'List all stealable issues Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
605
+ description: 'List all stealable issues. Use when looking for available work that has been freed up via claims_mark-stealable.',
596
606
  category: 'claims',
597
607
  inputSchema: {
598
608
  type: 'object',
@@ -628,7 +638,7 @@ export const claimsTools = [
628
638
  },
629
639
  {
630
640
  name: 'claims_load',
631
- description: 'Get agent load information Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
641
+ description: 'Get agent load information. Use when deciding which agent should take new work by comparing claim counts against claims_rebalance targets.',
632
642
  category: 'claims',
633
643
  inputSchema: {
634
644
  type: 'object',
@@ -689,8 +699,8 @@ export const claimsTools = [
689
699
  agentId: l.agentId,
690
700
  agentType: l.agentType,
691
701
  claimCount: l.claims.length,
692
- maxClaims: 5, // Default max
693
- utilization: l.claims.length / 5,
702
+ maxClaims: DEFAULT_MAX_CLAIMS,
703
+ utilization: l.claims.length / DEFAULT_MAX_CLAIMS,
694
704
  blockedCount: l.blockedCount,
695
705
  claims: l.claims.map(c => ({
696
706
  issueId: c.issueId,
@@ -711,7 +721,7 @@ export const claimsTools = [
711
721
  },
712
722
  {
713
723
  name: 'claims_board',
714
- description: 'Get a visual board view of all claims Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
724
+ description: 'Get a visual board view of all claims. Use when scanning overall claim status across issues and agents instead of composing the view from claims_list yourself.',
715
725
  category: 'claims',
716
726
  inputSchema: {
717
727
  type: 'object',
@@ -760,7 +770,7 @@ export const claimsTools = [
760
770
  },
761
771
  {
762
772
  name: 'claims_rebalance',
763
- description: 'Suggest or apply load rebalancing across agents Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
773
+ description: 'Suggest or apply load rebalancing across agents. Use when some agents are overloaded and others idle, instead of manually reassigning claims one at a time.',
764
774
  category: 'claims',
765
775
  inputSchema: {
766
776
  type: 'object',
@@ -794,7 +804,7 @@ export const claimsTools = [
794
804
  agentLoads.get(key).claims.push(claim);
795
805
  }
796
806
  const loads = Array.from(agentLoads.values());
797
- const maxClaims = 5;
807
+ const maxClaims = DEFAULT_MAX_CLAIMS;
798
808
  const avgLoad = loads.length > 0
799
809
  ? loads.reduce((sum, l) => sum + l.claims.length, 0) / loads.length
800
810
  : 0;
@@ -4175,12 +4175,12 @@ export const hooksIntelligenceLearn = {
4175
4175
  // Intelligence attention hook
4176
4176
  export const hooksIntelligenceAttention = {
4177
4177
  name: 'hooks_intelligence_attention',
4178
- description: 'Compute attention-weighted similarity using MoE/Flash/Hyperbolic Use when native Bash hooks (via Claude Code\'s settings.json) are wrong because you need Ruflo-side state — pattern persistence, neural training signals, model-routing learning, cost tracking, audit chain. For one-off shell commands, plain Bash hooks are fine.',
4178
+ description: 'Compute attention-weighted similarity using MoE/Flash. Use when native Bash hooks (via Claude Code\'s settings.json) are wrong because you need Ruflo-side state — pattern persistence, neural training signals, model-routing learning, cost tracking, audit chain. For one-off shell commands, plain Bash hooks are fine.',
4179
4179
  inputSchema: {
4180
4180
  type: 'object',
4181
4181
  properties: {
4182
4182
  query: { type: 'string', description: 'Query for attention computation' },
4183
- mode: { type: 'string', description: 'Attention mode (flash, moe, hyperbolic)' },
4183
+ mode: { type: 'string', description: 'Attention mode (flash, moe)' },
4184
4184
  topK: { type: 'number', description: 'Top-k results' },
4185
4185
  },
4186
4186
  required: ['query'],
@@ -4195,6 +4195,12 @@ export const hooksIntelligenceAttention = {
4195
4195
  if (!v.valid)
4196
4196
  return { success: false, error: v.error };
4197
4197
  }
4198
+ // #3548: only flash and moe have an implementation. Any other mode
4199
+ // (including the previously advertised 'hyperbolic') used to return an
4200
+ // empty stub whose note blamed a missing package; say so instead.
4201
+ if (mode !== 'flash' && mode !== 'moe') {
4202
+ return { success: false, error: `Attention mode "${mode}" is not implemented; supported modes: flash, moe` };
4203
+ }
4198
4204
  let implementation = 'placeholder';
4199
4205
  let embeddingSource = 'none';
4200
4206
  const results = [];
@@ -4370,7 +4376,7 @@ export const hooksIntelligenceAttention = {
4370
4376
  implementation,
4371
4377
  _embeddingSource: embeddingSource,
4372
4378
  _stub: implementation === 'none',
4373
- _note: implementation === 'none' ? 'No attention backend available. Install @ruvector/attention for real computation.' : undefined,
4379
+ _note: implementation === 'none' ? 'No attention backend available: flash/moe attention from @claude-flow/neural could not be loaded or failed.' : undefined,
4374
4380
  ...(embeddingSource === 'hash-fallback' && implementation !== 'none'
4375
4381
  ? { _embeddingNote: 'Query embeddings are hash-based (not semantic). Install @claude-flow/embeddings for real ONNX embeddings.' }
4376
4382
  : {}),
@@ -227,15 +227,31 @@ function loadLegacyStore() {
227
227
  /**
228
228
  * Mark migration as complete
229
229
  */
230
- function markMigrationComplete() {
230
+ function markMigrationComplete(counts) {
231
231
  const legacyDir = resolve(LEGACY_MEMORY_DIR);
232
232
  if (!existsSync(legacyDir))
233
233
  mkdirSync(legacyDir, { recursive: true });
234
234
  writeFileSync(resolve(join(LEGACY_MEMORY_DIR, MIGRATION_MARKER)), JSON.stringify({
235
235
  migratedAt: new Date().toISOString(),
236
236
  version: '3.0.0',
237
+ ...(counts ?? {}),
237
238
  }), 'utf-8');
238
239
  }
240
+ /**
241
+ * Counts recorded by the migration that wrote the marker, or null when no
242
+ * marker (or an older marker without counts) is present.
243
+ */
244
+ function readMigrationCounts() {
245
+ try {
246
+ const marker = JSON.parse(readFileSync(resolve(join(LEGACY_MEMORY_DIR, MIGRATION_MARKER)), 'utf-8'));
247
+ return typeof marker?.migrated === 'number' && typeof marker?.total === 'number'
248
+ ? { migrated: marker.migrated, total: marker.total }
249
+ : null;
250
+ }
251
+ catch {
252
+ return null;
253
+ }
254
+ }
239
255
  /**
240
256
  * Lazy-load memory initializer functions to avoid circular deps
241
257
  */
@@ -391,7 +407,7 @@ async function ensureInitialized(dbPath) {
391
407
  }
392
408
  }
393
409
  console.error(`[MCP Memory] Migrated ${migrated}/${Object.keys(legacyStore.entries).length} entries`);
394
- markMigrationComplete();
410
+ markMigrationComplete({ migrated, total: Object.keys(legacyStore.entries).length });
395
411
  }
396
412
  }
397
413
  }
@@ -636,7 +652,11 @@ export const memoryTools = [
636
652
  });
637
653
  return {
638
654
  results: r.results.map(e => ({
639
- id: e.id,
655
+ // #2271: searchEntries() truncates ids to 12 chars ("entry_" +
656
+ // the leading digits of Date.now()), so entries written within
657
+ // ~2.8h share one id and RRF fused them into a single result.
658
+ // SmartRetrieval dedupes on id: give it the (namespace, key) tuple.
659
+ id: JSON.stringify([e.namespace, e.key]),
640
660
  key: e.key,
641
661
  content: e.content,
642
662
  score: e.score,
@@ -966,11 +986,17 @@ export const memoryTools = [
966
986
  },
967
987
  handler: async (input) => {
968
988
  const force = input.force;
969
- // Remove migration marker if forcing
989
+ // Remove the migration marker if forcing. hasLegacyStore() checks the marker
990
+ // beside the legacy store, so that is the one force must clear (#4194);
991
+ // the old .swarm marker path is cleared too for compatibility.
970
992
  if (force) {
971
- const markerPath = getMigrationMarkerPath();
972
- if (existsSync(markerPath)) {
973
- unlinkSync(markerPath);
993
+ for (const markerPath of [
994
+ resolve(join(LEGACY_MEMORY_DIR, MIGRATION_MARKER)),
995
+ getMigrationMarkerPath(),
996
+ ]) {
997
+ if (existsSync(markerPath)) {
998
+ unlinkSync(markerPath);
999
+ }
974
1000
  }
975
1001
  }
976
1002
  // Check for legacy data
@@ -982,12 +1008,35 @@ export const memoryTools = [
982
1008
  migrated: 0,
983
1009
  };
984
1010
  }
1011
+ // A marker beside the store means a migration already ran; ensureInitialized
1012
+ // would move nothing, so say so instead of reporting the store's size.
1013
+ if (!hasLegacyStore()) {
1014
+ return {
1015
+ success: true,
1016
+ message: 'Legacy store already migrated (marker present); nothing was moved. Pass force: true to re-run.',
1017
+ migrated: 0,
1018
+ total: Object.keys(legacyStore.entries).length,
1019
+ };
1020
+ }
985
1021
  // Run migration via ensureInitialized
986
1022
  await ensureInitialized();
1023
+ // ensureInitialized swallows errors and records what it moved in the marker.
1024
+ const counts = readMigrationCounts();
1025
+ if (!counts) {
1026
+ return {
1027
+ success: false,
1028
+ message: 'Migration did not complete; no entries were recorded as migrated. See the MCP server log.',
1029
+ migrated: 0,
1030
+ total: Object.keys(legacyStore.entries).length,
1031
+ };
1032
+ }
987
1033
  return {
988
1034
  success: true,
989
- message: 'Migration completed',
990
- migrated: Object.keys(legacyStore.entries).length,
1035
+ message: counts.migrated === counts.total
1036
+ ? 'Migration completed'
1037
+ : `Migration completed with failures: ${counts.migrated} of ${counts.total} entries migrated`,
1038
+ migrated: counts.migrated,
1039
+ total: counts.total,
991
1040
  backend: await describeBackend(),
992
1041
  };
993
1042
  },
@@ -577,7 +577,8 @@ export const workflowTools = [
577
577
  const store = loadWorkflowStore();
578
578
  let workflows = Object.values(store.workflows);
579
579
  // Apply filters
580
- if (input.status) {
580
+ // 'all' is the CLI's explicit "no filter" value (`workflow list` sends it by default).
581
+ if (input.status && input.status !== 'all') {
581
582
  workflows = workflows.filter(w => w.status === input.status);
582
583
  }
583
584
  // Sort by creation date (newest first)
@@ -30,10 +30,12 @@
30
30
  * @module v3/cli/memory/graph-edge-writer
31
31
  */
32
32
  /**
33
- * True iff the memory.db bridge at `customDbPath` (or the default location)
34
- * is currently RFE1-encrypted-at-rest. Callers that receive `null` from
35
- * getBridgeDb()/insertGraphEdge() can use this to avoid recommending the
36
- * destructive `memory init` command for this specific, non-corrupt case.
33
+ * True iff the memory.db bridge at `customDbPath` — or, when omitted, the
34
+ * path `resolveDbPath()` resolves from the `CLAUDE_FLOW_DB_PATH` env binding
35
+ * / memory root default (#4291) — is currently RFE1-encrypted-at-rest.
36
+ * Callers that receive `null` from getBridgeDb()/insertGraphEdge() can use
37
+ * this to avoid recommending the destructive `memory init` command for this
38
+ * specific, non-corrupt case.
37
39
  */
38
40
  export declare function isBridgeDbEncryptedAtRest(customDbPath?: string): boolean;
39
41
  /** Test-only: clear the once-per-path warning dedup set. */
@@ -60,6 +62,16 @@ export declare function releaseBridgeDb(dbPath?: string): boolean;
60
62
  * #2431 fix: better-sqlite3 + WAL mode replaces sql.js + whole-file
61
63
  * writeFileSync. Eliminates the dual-write race that corrupted memory.db
62
64
  * when called alongside the memory bridge's better-sqlite3 writer.
65
+ *
66
+ * #4291 fix: the file is resolved via `resolveDbPath()` — the project's one
67
+ * documented path-priority rule (explicit `customDbPath` argument wins, then
68
+ * the `CLAUDE_FLOW_DB_PATH` env binding, then the plain `memory.db` default
69
+ * under the memory root) — instead of a local `customDbPath ?? <default>`
70
+ * expression that silently ignored `CLAUDE_FLOW_DB_PATH`. A caller that
71
+ * explicitly selected a store via that env var no longer gets answers read
72
+ * from a different, unselected database file. When the resolved file is
73
+ * missing (and `createIfMissing` is false) or encrypted at rest, this
74
+ * refuses (returns null) rather than falling back to any other store.
63
75
  */
64
76
  export declare function getBridgeDb(customDbPath?: string, opts?: {
65
77
  createIfMissing?: boolean;
@@ -33,7 +33,7 @@ import * as path from 'path';
33
33
  import * as fs from 'fs';
34
34
  import * as crypto from 'crypto';
35
35
  import { execFileSync } from 'child_process';
36
- import { getMemoryRoot } from './memory-initializer.js';
36
+ import { resolveDbPath } from './memory-initializer.js';
37
37
  import { encodeEmbedding } from './embedding-quantization.js';
38
38
  import { loadBetterSqlite3 } from './shared-sqlite.js';
39
39
  import { isEncryptedBlob } from '../encryption/vault.js';
@@ -125,13 +125,15 @@ function warnRfe1EncryptedOnce(dbPath) {
125
125
  'See #2889 (https://github.com/ruvnet/ruflo/issues/2889) for details and recovery options.');
126
126
  }
127
127
  /**
128
- * True iff the memory.db bridge at `customDbPath` (or the default location)
129
- * is currently RFE1-encrypted-at-rest. Callers that receive `null` from
130
- * getBridgeDb()/insertGraphEdge() can use this to avoid recommending the
131
- * destructive `memory init` command for this specific, non-corrupt case.
128
+ * True iff the memory.db bridge at `customDbPath` — or, when omitted, the
129
+ * path `resolveDbPath()` resolves from the `CLAUDE_FLOW_DB_PATH` env binding
130
+ * / memory root default (#4291) — is currently RFE1-encrypted-at-rest.
131
+ * Callers that receive `null` from getBridgeDb()/insertGraphEdge() can use
132
+ * this to avoid recommending the destructive `memory init` command for this
133
+ * specific, non-corrupt case.
132
134
  */
133
135
  export function isBridgeDbEncryptedAtRest(customDbPath) {
134
- const dbPath = customDbPath ?? path.join(getMemoryRoot(), 'memory.db');
136
+ const dbPath = resolveDbPath(customDbPath);
135
137
  return fs.existsSync(dbPath) && isRfe1EncryptedAtRest(dbPath);
136
138
  }
137
139
  /** Test-only: clear the once-per-path warning dedup set. */
@@ -222,9 +224,19 @@ export function releaseBridgeDb(dbPath) {
222
224
  * #2431 fix: better-sqlite3 + WAL mode replaces sql.js + whole-file
223
225
  * writeFileSync. Eliminates the dual-write race that corrupted memory.db
224
226
  * when called alongside the memory bridge's better-sqlite3 writer.
227
+ *
228
+ * #4291 fix: the file is resolved via `resolveDbPath()` — the project's one
229
+ * documented path-priority rule (explicit `customDbPath` argument wins, then
230
+ * the `CLAUDE_FLOW_DB_PATH` env binding, then the plain `memory.db` default
231
+ * under the memory root) — instead of a local `customDbPath ?? <default>`
232
+ * expression that silently ignored `CLAUDE_FLOW_DB_PATH`. A caller that
233
+ * explicitly selected a store via that env var no longer gets answers read
234
+ * from a different, unselected database file. When the resolved file is
235
+ * missing (and `createIfMissing` is false) or encrypted at rest, this
236
+ * refuses (returns null) rather than falling back to any other store.
225
237
  */
226
238
  export async function getBridgeDb(customDbPath, opts) {
227
- const dbPath = customDbPath ?? path.join(getMemoryRoot(), 'memory.db');
239
+ const dbPath = resolveDbPath(customDbPath);
228
240
  const createIfMissing = opts?.createIfMissing === true;
229
241
  if (_db && _dbPath === dbPath) {
230
242
  armIdleRelease();
@@ -1077,6 +1077,10 @@ export async function recordTrajectory(steps, verdict) {
1077
1077
  }
1078
1078
  bumpStat('trajectoriesRecorded');
1079
1079
  globalStats.lastAdaptation = Date.now();
1080
+ // #4283: the bank's own save is debounced, and the one-shot bins exit as
1081
+ // soon as the command resolves, so write the patterns now. Otherwise
1082
+ // stats.json counts patterns that never reach patterns.json.
1083
+ reasoningBank?.flushToDisk();
1080
1084
  savePersistedStats();
1081
1085
  return true;
1082
1086
  }