@claude-flow/cli 3.32.39 → 3.32.41

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.
@@ -140,7 +140,7 @@ hooks:
140
140
  # ═══════════════════════════════════════════════════════════════
141
141
  echo "🔒 Running comprehensive security validation..."
142
142
 
143
- npx claude-flow@v3alpha security scan --depth full --output-format json > /tmp/security-scan.json 2>/dev/null
143
+ npx claude-flow@v3alpha security scan --depth deep --output-format json > /tmp/security-scan.json 2>/dev/null
144
144
  VULNERABILITIES=$(jq -r '.vulnerabilities | length' /tmp/security-scan.json 2>/dev/null || echo "0")
145
145
  CRITICAL_COUNT=$(jq -r '.vulnerabilities | map(select(.severity == "critical")) | length' /tmp/security-scan.json 2>/dev/null || echo "0")
146
146
  HIGH_COUNT=$(jq -r '.vulnerabilities | map(select(.severity == "high")) | length' /tmp/security-scan.json 2>/dev/null || echo "0")
@@ -58,7 +58,7 @@ hooks:
58
58
  echo "✅ Security architecture analysis complete"
59
59
 
60
60
  # 1. Run comprehensive security validation
61
- npx claude-flow@v3alpha security scan --depth full --output-format json > /tmp/security-scan.json 2>/dev/null
61
+ npx claude-flow@v3alpha security scan --depth deep --output-format json > /tmp/security-scan.json 2>/dev/null
62
62
  VULNERABILITIES=$(jq -r '.vulnerabilities | length' /tmp/security-scan.json 2>/dev/null || echo "0")
63
63
  CRITICAL_COUNT=$(jq -r '.vulnerabilities | map(select(.severity == "critical")) | length' /tmp/security-scan.json 2>/dev/null || echo "0")
64
64
 
@@ -834,7 +834,7 @@ mcp__claude-flow__memory_usage({
834
834
 
835
835
  ```bash
836
836
  # Full security scan
837
- npx claude-flow@v3alpha security scan --depth full
837
+ npx claude-flow@v3alpha security scan --depth deep
838
838
 
839
839
  # CVE-specific checks
840
840
  npx claude-flow@v3alpha security cve --check CVE-2024-001
@@ -1 +1 @@
1
- 3.32.39
1
+ 3.32.40
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.32.39",
3
+ "version": "3.32.41",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "68be7e9a9eba7bf9c4e8a230db7bf61a243b965639f8504842799d6c6ca28762",
6
6
  "hook-handler.cjs": "dae295fb9ae2626b89899c19a20cc911541af82b52d2eeb9b214d618b96e9a86",
@@ -8,6 +8,6 @@
8
8
  "statusline.cjs": "0457fe53f8cd2c56458ff178392536a5868efd1a573665fa43bc01d2d95ca677"
9
9
  }
10
10
  },
11
- "signature": "v71S5vogcB+hBHI1nrB4pYHFC5kkRKJb/Qj+QdGdYqsxaKlK+2+9qo6ovpQaADjjseTJzrUAtHcJ7CbdEQt6CA==",
11
+ "signature": "E9neB/wxzCHAXPsX8ICLwt7X02M0joPEtF9kqHMH6qBp6y0C5OblOxerCq/AOE+8UrtsA/utlCpnmdQL6iHqDw==",
12
12
  "algorithm": "ed25519"
13
13
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "adoptedAt": 1785375525809,
2
+ "adoptedAt": 1785428938153,
3
3
  "championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
4
4
  "manifest": {
5
5
  "schema": "ruflo.proven-config/v1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 4,
4
- "generatedAt": "2026-07-30T02:05:05.762Z",
5
- "gitSha": "fabcc926",
4
+ "generatedAt": "2026-07-30T16:29:15.655Z",
5
+ "gitSha": "48d532b4",
6
6
  "catalog": {
7
7
  "agents": 164,
8
8
  "tools": 397,
Binary file
@@ -1387,6 +1387,16 @@ const transferFromProjectCommand = {
1387
1387
  minConfidence,
1388
1388
  mergeStrategy: 'keep-highest-confidence',
1389
1389
  });
1390
+ // #2859 — the handler reports success:false (no destination write
1391
+ // happened) when the source has no matching patterns at all. Surface
1392
+ // that honestly instead of claiming a transfer occurred.
1393
+ if (!result.success || typeof result.transferred === 'number') {
1394
+ spinner.fail(result.message ?? 'No patterns transferred');
1395
+ if (ctx.flags.format === 'json') {
1396
+ output.printJson(result);
1397
+ }
1398
+ return { success: false, exitCode: 1, data: result };
1399
+ }
1390
1400
  spinner.succeed(`Transferred ${result.transferred.total} patterns`);
1391
1401
  if (ctx.flags.format === 'json') {
1392
1402
  output.printJson(result);
@@ -1401,9 +1411,9 @@ const transferFromProjectCommand = {
1401
1411
  ],
1402
1412
  data: [
1403
1413
  { category: 'Total Transferred', count: output.success(String(result.transferred.total)) },
1404
- { category: 'Skipped (Low Confidence)', count: result.skipped.lowConfidence },
1405
- { category: 'Skipped (Duplicates)', count: result.skipped.duplicates },
1406
- { category: 'Skipped (Conflicts)', count: result.skipped.conflicts }
1414
+ { category: 'Skipped (Low Confidence)', count: result.skipped?.lowConfidence ?? 0 },
1415
+ { category: 'Skipped (Duplicates)', count: result.skipped?.duplicates ?? 0 },
1416
+ { category: 'Skipped (Conflicts)', count: result.skipped?.conflicts ?? 0 }
1407
1417
  ]
1408
1418
  });
1409
1419
  if (Object.keys(result.transferred.byType).length > 0) {
@@ -1417,11 +1427,19 @@ const transferFromProjectCommand = {
1417
1427
  data: Object.entries(result.transferred.byType).map(([type, count]) => ({ type, count }))
1418
1428
  });
1419
1429
  }
1420
- output.writeln();
1421
- output.printList([
1422
- `Avg Confidence: ${(result.stats.avgConfidence * 100).toFixed(1)}%`,
1423
- `Avg Age: ${result.stats.avgAge}`
1424
- ]);
1430
+ // #2865-style honesty: omit stats that have no real measured value
1431
+ // rather than showing a fabricated number.
1432
+ const statLines = [];
1433
+ if (result.stats?.avgConfidence != null) {
1434
+ statLines.push(`Avg Confidence: ${(result.stats.avgConfidence * 100).toFixed(1)}%`);
1435
+ }
1436
+ if (result.stats?.avgAgeDays != null) {
1437
+ statLines.push(`Avg Age: ${result.stats.avgAgeDays.toFixed(1)} days`);
1438
+ }
1439
+ if (statLines.length > 0) {
1440
+ output.writeln();
1441
+ output.printList(statLines);
1442
+ }
1425
1443
  return { success: true, data: result };
1426
1444
  }
1427
1445
  catch (error) {
@@ -2137,9 +2155,17 @@ const intelligenceCommand = {
2137
2155
  moe: {
2138
2156
  enabled: enableMoe,
2139
2157
  status: String(mcpMoe?.status ?? (hasLocalData ? 'active' : 'idle')),
2140
- expertsActive: Number(mcpMoe?.expertsActive ?? (hasLocalData ? 8 : 0)),
2141
- routingAccuracy: Number(mcpMoe?.routingAccuracy ?? (hasLocalData ? 0.82 : 0)),
2142
- loadBalance: Number(mcpMoe?.loadBalance ?? (hasLocalData ? 0.9 : 0)),
2158
+ // #2865 these three previously fell back to hardcoded
2159
+ // hasLocalData ? <constant> : 0 whenever the MCP tool didn't
2160
+ // report a real value, so "Routing Accuracy: 82.0%" displayed
2161
+ // on every project with local neural data regardless of actual
2162
+ // routing quality (measured 49% on a real store). None of these
2163
+ // three has a cheap, honest local substitute the way
2164
+ // `hooks metrics`' routingAccuracy falls back to averageConfidence,
2165
+ // so they are null (unmeasured) rather than a fabricated number.
2166
+ expertsActive: mcpMoe?.expertsActive == null ? null : Number(mcpMoe.expertsActive),
2167
+ routingAccuracy: mcpMoe?.routingAccuracy == null ? null : Number(mcpMoe.routingAccuracy),
2168
+ loadBalance: mcpMoe?.loadBalance == null ? null : Number(mcpMoe.loadBalance),
2143
2169
  },
2144
2170
  hnsw: {
2145
2171
  enabled: enableHnsw,
@@ -2235,9 +2261,11 @@ const intelligenceCommand = {
2235
2261
  ],
2236
2262
  data: [
2237
2263
  { metric: 'Status', value: formatIntelligenceStatus(moe.status) },
2238
- { metric: 'Active Experts', value: moe.expertsActive ?? 0 },
2239
- { metric: 'Routing Accuracy', value: `${((moe.routingAccuracy ?? 0) * 100).toFixed(1)}%` },
2240
- { metric: 'Load Balance', value: `${((moe.loadBalance ?? 0) * 100).toFixed(1)}%` }
2264
+ // #2865 omit rather than show a fabricated 0/0.0% when the
2265
+ // MCP tool hasn't reported a real value for these.
2266
+ ...(moe.expertsActive == null ? [] : [{ metric: 'Active Experts', value: moe.expertsActive }]),
2267
+ ...(moe.routingAccuracy == null ? [] : [{ metric: 'Routing Accuracy', value: `${(moe.routingAccuracy * 100).toFixed(1)}%` }]),
2268
+ ...(moe.loadBalance == null ? [] : [{ metric: 'Load Balance', value: `${(moe.loadBalance * 100).toFixed(1)}%` }]),
2241
2269
  ]
2242
2270
  });
2243
2271
  }
@@ -6,15 +6,51 @@
6
6
  */
7
7
  import { output } from '../output.js';
8
8
  import { execSync } from 'node:child_process';
9
+ import { existsSync, statSync } from 'node:fs';
10
+ import { resolve as resolvePath } from 'node:path';
9
11
  import { createBuiltinAIDefence } from '../security/builtin-aidefence.js';
12
+ // Accepted values for `security scan`'s enum flags — the single source of truth
13
+ // for both validation and the traversal-depth maps below.
14
+ const SCAN_DEPTHS = ['quick', 'standard', 'deep'];
15
+ const SCAN_TYPES = ['code', 'deps', 'all'];
16
+ // Real type predicates, not `as` casts. Array.includes() does not narrow a
17
+ // string to a literal union on its own, so without these the depth-map lookups
18
+ // below need an unchecked assertion — and an unchecked assertion would let a
19
+ // future refactor index the maps with an invalid key, yielding `undefined`.
20
+ // That matters: `undefined <= 0` is false and `undefined - 1` is NaN, so the
21
+ // recursion guard in scanDir would never fire. An unsafe cast here would
22
+ // silently disable the depth limiter — the exact class of failure this command
23
+ // is being fixed for.
24
+ const isScanDepth = (v) => SCAN_DEPTHS.includes(v);
25
+ const isScanType = (v) => SCAN_TYPES.includes(v);
26
+ // `full` was never a supported depth, but the CLI itself printed it — the
27
+ // statusline insight, the announcement, the release-notes blurb, the CLAUDE.md
28
+ // that `init` generates, and two shipped agent definitions all tell people to
29
+ // run `security scan --depth full`. Pre-fix it silently fell through to the
30
+ // SHALLOWEST traversal, which is the bug. Hard-rejecting it would break every
31
+ // caller we ourselves told to use it, so it is normalised to the depth the word
32
+ // promised, with a warning. Remove once those emitters have aged out.
33
+ const DEPRECATED_SCAN_DEPTHS = { full: 'deep' };
34
+ // `container` has been advertised in --type's help since this command was
35
+ // written, but no phase below implements it. Accepting it would reproduce the
36
+ // exact bug this validation exists to fix: a documented flag value that scans
37
+ // nothing and still prints a clean bill of health. Rejected explicitly, with
38
+ // its own message, until a container phase exists.
39
+ const UNIMPLEMENTED_SCAN_TYPES = ['container'];
40
+ // Directory-recursion limits per depth. Exhaustive records rather than chained
41
+ // ternaries, so adding a depth is a compile error here instead of a silently
42
+ // shallower scan. CODE_SCAN_DEPTH.quick is unreachable — phase 3 is gated on
43
+ // depth !== 'quick' — but is present so the record stays total.
44
+ const SECRET_SCAN_DEPTH = { quick: 3, standard: 5, deep: 10 };
45
+ const CODE_SCAN_DEPTH = { quick: 0, standard: 5, deep: 10 };
10
46
  // Scan subcommand
11
47
  const scanCommand = {
12
48
  name: 'scan',
13
- description: 'Run security scan on target (code, dependencies, containers)',
49
+ description: 'Run security scan on target (code, dependencies)',
14
50
  options: [
15
51
  { name: 'target', short: 't', type: 'string', description: 'Target path or URL to scan', default: '.' },
16
52
  { name: 'depth', short: 'd', type: 'string', description: 'Scan depth: quick, standard, deep', default: 'standard' },
17
- { name: 'type', type: 'string', description: 'Scan type: code, deps, container, all', default: 'all' },
53
+ { name: 'type', type: 'string', description: 'Scan type: code, deps, all', default: 'all' },
18
54
  { name: 'output', short: 'o', type: 'string', description: 'Output format: text, json, sarif', default: 'text' },
19
55
  { name: 'fix', short: 'f', type: 'boolean', description: 'Auto-fix vulnerabilities where possible' },
20
56
  ],
@@ -24,9 +60,52 @@ const scanCommand = {
24
60
  ],
25
61
  action: async (ctx) => {
26
62
  const target = ctx.flags.target || '.';
27
- const depth = ctx.flags.depth || 'standard';
63
+ const requestedDepth = ctx.flags.depth || 'standard';
28
64
  const scanType = ctx.flags.type || 'all';
29
65
  const fix = ctx.flags.fix;
66
+ // A security scanner must never silently degrade on an unrecognised enum
67
+ // value. An unknown --depth used to fall through to the shallowest
68
+ // traversal (so `--depth full` scanned *less* than the default), and an
69
+ // unknown --type skipped every phase while still reporting "No security
70
+ // issues found!" with exit 0 — a typo was indistinguishable from a clean
71
+ // result, and it silently disabled the critical/high exit-code gate.
72
+ // Fail closed instead, before anything is printed or scanned.
73
+ const aliasedDepth = DEPRECATED_SCAN_DEPTHS[requestedDepth];
74
+ if (aliasedDepth) {
75
+ output.printWarning(`--depth '${requestedDepth}' is deprecated and has been treated as '${aliasedDepth}'. ` +
76
+ `Use one of: ${SCAN_DEPTHS.join(', ')}.`);
77
+ }
78
+ const candidateDepth = aliasedDepth ?? requestedDepth;
79
+ if (!isScanDepth(candidateDepth)) {
80
+ output.printError(`Invalid --depth '${requestedDepth}'. Expected one of: ${SCAN_DEPTHS.join(', ')}.`);
81
+ return { success: false, exitCode: 1 };
82
+ }
83
+ const depth = candidateDepth;
84
+ // Case-insensitive so `--type Container` gets the accurate "not implemented"
85
+ // message rather than being reported as a misspelling.
86
+ if (UNIMPLEMENTED_SCAN_TYPES.includes(scanType.toLowerCase())) {
87
+ output.printError(`--type '${scanType}' is not implemented yet. Expected one of: ${SCAN_TYPES.join(', ')}.`);
88
+ return { success: false, exitCode: 1 };
89
+ }
90
+ if (!isScanType(scanType)) {
91
+ output.printError(`Invalid --type '${scanType}'. Expected one of: ${SCAN_TYPES.join(', ')}.`);
92
+ return { success: false, exitCode: 1 };
93
+ }
94
+ // --target names WHAT gets scanned, and was never validated. A path that
95
+ // does not exist (or is a file, not a directory) made every phase read
96
+ // nothing, and the swallowed dir-read catches turned that into zero
97
+ // findings, the clean banner, exit 0 — and a PERSISTED clean report that
98
+ // getSecurityStatus then reports as CLEAN. Same fail-open class as the enum
99
+ // flags, and the likelier typo of the two, so it fails closed too.
100
+ const resolvedTarget = resolvePath(target);
101
+ if (!existsSync(resolvedTarget)) {
102
+ output.printError(`Target does not exist: ${resolvedTarget}`);
103
+ return { success: false, exitCode: 1 };
104
+ }
105
+ if (!statSync(resolvedTarget).isDirectory()) {
106
+ output.printError(`Target is not a directory: ${resolvedTarget}`);
107
+ return { success: false, exitCode: 1 };
108
+ }
30
109
  output.writeln();
31
110
  output.writeln(output.bold('Security Scan'));
32
111
  output.writeln(output.dim('─'.repeat(50)));
@@ -98,7 +177,9 @@ const scanCommand = {
98
177
  { pattern: /password\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Hardcoded Password' },
99
178
  ];
100
179
  const scanDir = (dir, depthLimit) => {
101
- if (depthLimit <= 0)
180
+ // Positive-test rather than `<= 0`: undefined and NaN both fail this,
181
+ // so a bad budget stops the recursion instead of disabling the limiter.
182
+ if (!(depthLimit > 0))
102
183
  return;
103
184
  try {
104
185
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -134,7 +215,7 @@ const scanCommand = {
134
215
  }
135
216
  catch { /* dir read error */ }
136
217
  };
137
- const scanDepth = depth === 'deep' ? 10 : depth === 'standard' ? 5 : 3;
218
+ const scanDepth = SECRET_SCAN_DEPTH[depth];
138
219
  scanDir(path.resolve(target), scanDepth);
139
220
  }
140
221
  // Phase 3: Check for common security issues in code
@@ -148,7 +229,9 @@ const scanCommand = {
148
229
  { pattern: /\$\{.*\}.*sql|sql.*\$\{/gi, type: 'SQL Injection', severity: 'high', desc: 'Possible SQL injection' },
149
230
  ];
150
231
  const scanCodeDir = (dir, depthLimit) => {
151
- if (depthLimit <= 0)
232
+ // Positive-test rather than `<= 0`: undefined and NaN both fail this,
233
+ // so a bad budget stops the recursion instead of disabling the limiter.
234
+ if (!(depthLimit > 0))
152
235
  return;
153
236
  try {
154
237
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -187,7 +270,7 @@ const scanCommand = {
187
270
  }
188
271
  catch { /* dir read error */ }
189
272
  };
190
- const scanDepth = depth === 'deep' ? 10 : 5;
273
+ const scanDepth = CODE_SCAN_DEPTH[depth];
191
274
  scanCodeDir(path.resolve(target), scanDepth);
192
275
  }
193
276
  spinner.succeed('Scan complete');
@@ -1222,7 +1305,7 @@ export const securityCommand = {
1222
1305
  output.writeln();
1223
1306
  output.writeln('Subcommands:');
1224
1307
  output.printList([
1225
- 'scan - Run security scans on code, deps, containers',
1308
+ 'scan - Run security scans on code, deps',
1226
1309
  'cve - Check and manage CVE vulnerabilities',
1227
1310
  'threats - Threat modeling (STRIDE, DREAD, PASTA)',
1228
1311
  'audit - Security audit logging and compliance',
@@ -51,7 +51,7 @@ function securityInsight(ctx) {
51
51
  };
52
52
  }
53
53
  if (s.status === 'PENDING') {
54
- return { id: 'insight-scan-pending', text: '🛡 Security scan pending — Run ruflo security scan --depth full', priority: 70 };
54
+ return { id: 'insight-scan-pending', text: '🛡 Security scan pending — Run ruflo security scan --depth deep', priority: 70 };
55
55
  }
56
56
  return null;
57
57
  }
@@ -158,7 +158,7 @@ export const MESSAGES = [
158
158
  schemaVersion: 1,
159
159
  id: 'local.edu.security-scan',
160
160
  class: 'educational',
161
- text: '🔒 ruflo security scan --depth full — audits dependencies and config',
161
+ text: '🔒 ruflo security scan --depth deep — audits dependencies and config',
162
162
  url: 'https://cognitum.one/docs/security',
163
163
  },
164
164
  {
@@ -247,7 +247,7 @@ function securitySection() {
247
247
  - Always use parameterized queries (prevent injection)
248
248
 
249
249
  \`\`\`bash
250
- npx @claude-flow/cli@latest security scan --depth full
250
+ npx @claude-flow/cli@latest security scan --depth deep
251
251
  npx @claude-flow/cli@latest security audit --report
252
252
  \`\`\`
253
253
 
@@ -987,14 +987,20 @@ export const hooksRoute = {
987
987
  let agents;
988
988
  let confidence;
989
989
  let matchedPattern = '';
990
+ // Both static and learned patterns are gated on the same similarity
991
+ // score. Learned patterns additionally require support/reliability as a
992
+ // quality guard, but do NOT need a higher score bar — a learned pattern
993
+ // that outscores every static candidate must not lose to one anyway
994
+ // (#2864: a 25pp higher threshold made a top-scoring learned-researcher
995
+ // match at 0.57 lose to a static match at 0.52, discarding the learned
996
+ // store's output on the majority of routes).
990
997
  const eligibleSemantic = semanticResult.find((match) => {
991
998
  if (match.score <= 0.4)
992
999
  return false;
993
1000
  const learned = match.intent.startsWith('learned-') || match.metadata.source === 'learned';
994
1001
  if (!learned)
995
1002
  return true;
996
- return match.score >= 0.65
997
- && Number(match.metadata.support ?? 0) >= 2
1003
+ return Number(match.metadata.support ?? 0) >= 2
998
1004
  && Number(match.metadata.reliability ?? 0) >= 0.75;
999
1005
  });
1000
1006
  if (eligibleSemantic) {
@@ -1866,16 +1872,23 @@ export const hooksTransfer = {
1866
1872
  catch {
1867
1873
  // Fall back to empty store
1868
1874
  }
1869
- const sourceEntries = Object.values(sourceStore.entries);
1870
- // Count patterns by type from source
1871
- const byType = {
1872
- 'file-patterns': sourceEntries.filter(e => e.key.includes('file') || e.metadata?.type === 'file-pattern').length,
1873
- 'task-routing': sourceEntries.filter(e => e.key.includes('routing') || e.metadata?.type === 'routing').length,
1874
- 'command-risk': sourceEntries.filter(e => e.key.includes('command') || e.metadata?.type === 'command-risk').length,
1875
- 'agent-success': sourceEntries.filter(e => e.key.includes('agent') || e.metadata?.type === 'agent-success').length,
1875
+ const classifyType = (key, metadata) => {
1876
+ if (key.includes('file') || metadata?.type === 'file-pattern')
1877
+ return 'file-patterns';
1878
+ if (key.includes('routing') || metadata?.type === 'routing')
1879
+ return 'task-routing';
1880
+ if (key.includes('command') || metadata?.type === 'command-risk')
1881
+ return 'command-risk';
1882
+ if (key.includes('agent') || metadata?.type === 'agent-success')
1883
+ return 'agent-success';
1884
+ return null;
1876
1885
  };
1886
+ const candidates = Object.entries(sourceStore.entries)
1887
+ .map(([key, entry]) => ({ key, entry, type: classifyType(key, entry.metadata) }))
1888
+ .filter((c) => c.type !== null)
1889
+ .filter(c => !filter || c.type.includes(filter));
1877
1890
  // If source has no patterns, report honestly instead of substituting demo data
1878
- if (Object.values(byType).every(v => v === 0)) {
1891
+ if (candidates.length === 0) {
1879
1892
  return {
1880
1893
  success: false,
1881
1894
  message: 'No patterns found in source project',
@@ -1883,28 +1896,76 @@ export const hooksTransfer = {
1883
1896
  transferred: 0,
1884
1897
  };
1885
1898
  }
1886
- if (filter) {
1887
- Object.keys(byType).forEach(key => {
1888
- if (!key.includes(filter))
1889
- delete byType[key];
1890
- });
1899
+ // #2859 — this used to count source patterns, then invent skip counts
1900
+ // as fixed percentages of that count and a fixed avgConfidence/avgAge,
1901
+ // without ever reading or writing the destination store. An operator
1902
+ // could believe state moved between projects when nothing changed.
1903
+ // Perform a real merge: skip entries below the confidence threshold,
1904
+ // skip exact duplicates, skip real conflicts (destination already has
1905
+ // a different value for that key — never silently overwritten), and
1906
+ // actually write whatever remains into this project's own memory store.
1907
+ const destStore = loadMemoryStore();
1908
+ const byType = {};
1909
+ let lowConfidence = 0;
1910
+ let duplicates = 0;
1911
+ let conflicts = 0;
1912
+ let transferredCount = 0;
1913
+ let confidenceSum = 0;
1914
+ let confidenceCount = 0;
1915
+ let ageSumMs = 0;
1916
+ let ageCount = 0;
1917
+ const now = Date.now();
1918
+ for (const { key, entry, type } of candidates) {
1919
+ const confidenceRaw = entry.metadata?.confidence ?? entry.metadata?.reliability;
1920
+ const confidence = typeof confidenceRaw === 'number' ? confidenceRaw : undefined;
1921
+ if (confidence !== undefined && confidence < minConfidence) {
1922
+ lowConfidence++;
1923
+ continue;
1924
+ }
1925
+ const existing = destStore.entries[key];
1926
+ if (existing) {
1927
+ const same = JSON.stringify(existing.value) === JSON.stringify(entry.value);
1928
+ if (same) {
1929
+ duplicates++;
1930
+ continue;
1931
+ }
1932
+ conflicts++;
1933
+ continue;
1934
+ }
1935
+ destStore.entries[key] = entry;
1936
+ transferredCount++;
1937
+ byType[type] = (byType[type] ?? 0) + 1;
1938
+ if (confidence !== undefined) {
1939
+ confidenceSum += confidence;
1940
+ confidenceCount++;
1941
+ }
1942
+ const storedAtMs = Date.parse(entry.storedAt ?? '');
1943
+ if (!Number.isNaN(storedAtMs)) {
1944
+ ageSumMs += now - storedAtMs;
1945
+ ageCount++;
1946
+ }
1947
+ }
1948
+ if (transferredCount > 0) {
1949
+ const memDir = resolve(MEMORY_DIR);
1950
+ if (!existsSync(memDir))
1951
+ mkdirSync(memDir, { recursive: true });
1952
+ writeFileSync(getMemoryPath(), JSON.stringify(destStore, null, 2), 'utf-8');
1891
1953
  }
1892
- const total = Object.values(byType).reduce((a, b) => a + b, 0);
1893
1954
  return {
1894
1955
  success: true,
1895
1956
  sourcePath,
1896
1957
  transferred: {
1897
- total,
1958
+ total: transferredCount,
1898
1959
  byType,
1899
1960
  },
1900
1961
  skipped: {
1901
- lowConfidence: Math.floor(total * 0.15),
1902
- duplicates: Math.floor(total * 0.08),
1903
- conflicts: Math.floor(total * 0.03),
1962
+ lowConfidence,
1963
+ duplicates,
1964
+ conflicts,
1904
1965
  },
1905
1966
  stats: {
1906
- avgConfidence: 0.82 + (minConfidence > 0.8 ? 0.1 : 0),
1907
- avgAge: '3 days',
1967
+ avgConfidence: confidenceCount > 0 ? Number((confidenceSum / confidenceCount).toFixed(3)) : null,
1968
+ avgAgeDays: ageCount > 0 ? Number((ageSumMs / ageCount / 86_400_000).toFixed(1)) : null,
1908
1969
  },
1909
1970
  dataSource: 'source-project',
1910
1971
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.32.39",
3
+ "version": "3.32.41",
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",