claude-flow 3.56.2 → 3.56.4

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 (72) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +13 -8
  2. package/.claude/helpers/hook-handler.cjs +19 -14
  3. package/.claude/helpers/learning-service.mjs +5 -2
  4. package/.claude/helpers/session.cjs +56 -10
  5. package/.claude/helpers/statusline.cjs +1338 -1328
  6. package/.claude-plugin/scripts/ruflo-hook.cjs +14 -2
  7. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +1 -0
  8. package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -1
  9. package/node_modules/@claude-flow/mcp/dist/resource-registry.js +10 -4
  10. package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -1
  11. package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -1
  12. package/node_modules/@claude-flow/mcp/dist/sampling.js +13 -6
  13. package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -1
  14. package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -1
  15. package/node_modules/@claude-flow/mcp/dist/task-manager.js +10 -1
  16. package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -1
  17. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +1 -0
  18. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
  19. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +17 -11
  20. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
  21. package/node_modules/@claude-flow/mcp/package.json +1 -1
  22. package/node_modules/@claude-flow/security/dist/index.d.ts +1 -1
  23. package/node_modules/@claude-flow/security/dist/index.d.ts.map +1 -1
  24. package/node_modules/@claude-flow/security/dist/index.js +1 -1
  25. package/node_modules/@claude-flow/security/dist/index.js.map +1 -1
  26. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts +9 -1
  27. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts.map +1 -1
  28. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js +15 -2
  29. package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js.map +1 -1
  30. package/node_modules/@claude-flow/security/package.json +1 -1
  31. package/package.json +4 -4
  32. package/v3/@claude-flow/cli/README.md +62 -158
  33. package/v3/@claude-flow/cli/catalog-manifest.json +5 -5
  34. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +7 -13
  35. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +3 -6
  36. package/v3/@claude-flow/cli/dist/src/commands/init.js +3 -10
  37. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +9 -14
  38. package/v3/@claude-flow/cli/dist/src/commands/plugins.js +81 -8
  39. package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.d.ts +40 -0
  40. package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.js +192 -0
  41. package/v3/@claude-flow/cli/dist/src/commands/security.js +8 -11
  42. package/v3/@claude-flow/cli/dist/src/commands/status.js +8 -10
  43. package/v3/@claude-flow/cli/dist/src/init/executor.d.ts +1 -1
  44. package/v3/@claude-flow/cli/dist/src/init/executor.js +25 -16
  45. package/v3/@claude-flow/cli/dist/src/init/helper-commands.d.ts +18 -0
  46. package/v3/@claude-flow/cli/dist/src/init/helper-commands.js +142 -0
  47. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +128 -29
  48. package/v3/@claude-flow/cli/dist/src/init/settings-generator.js +36 -103
  49. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +6 -2
  50. package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +7 -10
  51. package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +24 -3
  52. package/v3/@claude-flow/cli/dist/src/mcp-tools/testgen-tools.js +8 -12
  53. package/v3/@claude-flow/cli/dist/src/mcp-tools/transfer-tools.js +9 -8
  54. package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +10 -4
  55. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +10 -1
  56. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +2 -19
  57. package/v3/@claude-flow/cli/dist/src/memory/shared-sqlite.d.ts +6 -0
  58. package/v3/@claude-flow/cli/dist/src/memory/shared-sqlite.js +35 -7
  59. package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +3 -1
  60. package/v3/@claude-flow/cli/dist/src/plugins/manager.js +31 -10
  61. package/v3/@claude-flow/cli/dist/src/plugins/own-install.d.ts +15 -0
  62. package/v3/@claude-flow/cli/dist/src/plugins/own-install.js +51 -0
  63. package/v3/@claude-flow/cli/dist/src/plugins/store/discovery.d.ts +26 -2
  64. package/v3/@claude-flow/cli/dist/src/plugins/store/discovery.js +108 -81
  65. package/v3/@claude-flow/cli/dist/src/plugins/store/index.d.ts +1 -1
  66. package/v3/@claude-flow/cli/dist/src/plugins/store/index.js +1 -1
  67. package/v3/@claude-flow/cli/dist/src/plugins/store/types.d.ts +5 -0
  68. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +13 -0
  69. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +29 -0
  70. package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +18 -6
  71. package/v3/@claude-flow/cli/dist/src/transfer/ipfs/client.js +3 -1
  72. package/v3/@claude-flow/cli/package.json +6 -6
@@ -17,11 +17,16 @@ import { fileURLToPath } from 'url';
17
17
 
18
18
  const __filename = fileURLToPath(import.meta.url);
19
19
  const __dirname = dirname(__filename);
20
+ // Executable modules resolve only from the helper's own install root. A
21
+ // home-level helper can serve an untrusted project, so CLAUDE_PROJECT_DIR must
22
+ // never decide which JavaScript gets imported.
23
+ const MODULE_ROOT = join(__dirname, '../..');
20
24
  // Home-level helpers can serve a different project. Keep the memory writer on
21
25
  // the same project root as intelligence.cjs, which reads CLAUDE_PROJECT_DIR.
26
+ // PROJECT_ROOT is for data only.
22
27
  const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
23
28
  ? resolve(process.env.CLAUDE_PROJECT_DIR)
24
- : join(__dirname, '../..');
29
+ : MODULE_ROOT;
25
30
  const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
26
31
  const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
27
32
 
@@ -41,7 +46,7 @@ const dim = (msg) => console.log(` ${DIM}${msg}${RESET}`);
41
46
  // told exactly how to fix it (on both stdout, so it shows in the Claude Code hook
42
47
  // transcript, and stderr, per the issue's requested channel).
43
48
  function warnMemoryUnavailable() {
44
- const line1 = `[AutoMemory] @claude-flow/memory not resolvable from ${PROJECT_ROOT} — self-learning imports are DISABLED.`;
49
+ const line1 = `[AutoMemory] @claude-flow/memory not resolvable from ${MODULE_ROOT} — self-learning imports are DISABLED.`;
45
50
  const line2 = ' Fix: npm i -D @claude-flow/memory (or re-run: npx ruflo@latest init, then npx ruflo@latest doctor --fix)';
46
51
  console.log(`${YELLOW}${line1}${RESET}`);
47
52
  console.log(`${YELLOW}${line2}${RESET}`);
@@ -175,7 +180,7 @@ async function loadMemoryPackage() {
175
180
  // project — so init resolves it from the CLI's own context and records the
176
181
  // absolute path here. This is the only strategy that works on that install.
177
182
  try {
178
- const sidecar = join(PROJECT_ROOT, '.claude-flow', 'memory-package.json');
183
+ const sidecar = join(MODULE_ROOT, '.claude-flow', 'memory-package.json');
179
184
  if (existsSync(sidecar)) {
180
185
  const rec = JSON.parse(readFileSync(sidecar, 'utf-8'));
181
186
  if (rec?.distPath && existsSync(rec.distPath)) {
@@ -185,7 +190,7 @@ async function loadMemoryPackage() {
185
190
  } catch { /* fall through */ }
186
191
 
187
192
  // Strategy 1: Local dev (built dist)
188
- const localDist = join(PROJECT_ROOT, 'v3/@claude-flow/memory/dist/index.js');
193
+ const localDist = join(MODULE_ROOT, 'v3/@claude-flow/memory/dist/index.js');
189
194
  if (existsSync(localDist)) {
190
195
  try {
191
196
  return await import(`file://${localDist}`);
@@ -196,7 +201,7 @@ async function loadMemoryPackage() {
196
201
  // when installed as a transitive dependency via npx ruflo / npx claude-flow)
197
202
  try {
198
203
  const { createRequire } = await import('module');
199
- const require = createRequire(join(PROJECT_ROOT, 'package.json'));
204
+ const require = createRequire(join(MODULE_ROOT, 'package.json'));
200
205
  return require('@claude-flow/memory');
201
206
  } catch { /* fall through */ }
202
207
 
@@ -205,8 +210,8 @@ async function loadMemoryPackage() {
205
210
  return await import('@claude-flow/memory');
206
211
  } catch { /* fall through */ }
207
212
 
208
- // Strategy 4: Walk up from PROJECT_ROOT looking for @claude-flow/memory in any node_modules
209
- let searchDir = PROJECT_ROOT;
213
+ // Strategy 4: Walk up from MODULE_ROOT looking for @claude-flow/memory in any node_modules
214
+ let searchDir = MODULE_ROOT;
210
215
  const { parse } = await import('path');
211
216
  while (searchDir !== parse(searchDir).root) {
212
217
  const candidate = join(searchDir, 'node_modules', '@claude-flow', 'memory', 'dist', 'index.js');
@@ -378,7 +383,7 @@ async function doStatus() {
378
383
  const memPkg = await loadMemoryPackage();
379
384
  const config = readConfig();
380
385
 
381
- const sidecar = join(PROJECT_ROOT, '.claude-flow', 'memory-package.json');
386
+ const sidecar = join(MODULE_ROOT, '.claude-flow', 'memory-package.json');
382
387
  const hasSidecar = existsSync(sidecar);
383
388
 
384
389
  console.log('\n=== Auto Memory Bridge Status ===\n');
@@ -18,6 +18,10 @@ const fs = require('fs');
18
18
  const os = require('os');
19
19
 
20
20
  const helpersDir = __dirname;
21
+ // The helper's own install root: the project for a project-local helper,
22
+ // $HOME for a home-level one. CLI resolution starts here, never at the opened
23
+ // project, so a home-level helper does not run code the project supplies.
24
+ const helperRoot = path.resolve(helpersDir, '..', '..');
21
25
 
22
26
  // Resolve an installed @claude-flow/cli (or ruflo) bin — mirrors
23
27
  // statusline-generator.ts's resolveCliBin() candidate list. Used only to
@@ -38,12 +42,11 @@ const helpersDir = __dirname;
38
42
  function resolveCliBinForHook() {
39
43
  try {
40
44
  const home = os.homedir();
41
- const cwd = process.cwd();
42
45
  const candidates = [
43
46
  path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
44
- path.join(cwd, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
45
- path.join(cwd, 'node_modules', 'ruflo', 'bin', 'cli.js'),
46
- path.join(cwd, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
47
+ path.join(helperRoot, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
48
+ path.join(helperRoot, 'node_modules', 'ruflo', 'bin', 'cli.js'),
49
+ path.join(helperRoot, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
47
50
  // helpersDir is .claude/helpers/ inside the package itself when this
48
51
  // file is running from a real @claude-flow/cli install (not a project
49
52
  // that merely copied the helper) — its bin/ is two levels up.
@@ -77,14 +80,19 @@ function resolveCliBinForHook() {
77
80
  // render path, where local-first exists purely for per-render latency.
78
81
  // `--prefer-offline` avoids a registry round trip for the tarball when
79
82
  // already cached while still resolving the current `@latest` version.
83
+ //
84
+ // `--prefix helperRoot` makes npx resolve @claude-flow/cli from the helper's
85
+ // install root instead of the cwd (the opened project); the spawned CLI still
86
+ // runs with the project as its cwd.
87
+ function cliSpawnArgs(cliBin, args, platform = process.platform) {
88
+ if (cliBin) return [process.execPath, [cliBin, ...args]];
89
+ return [platform === 'win32' ? 'npx.cmd' : 'npx', ['--prefer-offline', '--prefix', helperRoot, '@claude-flow/cli', ...args]];
90
+ }
91
+
80
92
  function spawnDetachedHookRefresh(subcommand) {
81
93
  try {
82
94
  const { spawn } = require('child_process');
83
- const cliBin = resolveCliBinForHook();
84
- const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
85
- const spawnArgs = cliBin
86
- ? [process.execPath, [cliBin, 'hooks', subcommand, '--quiet']]
87
- : [cmd, ['--prefer-offline', '@claude-flow/cli', 'hooks', subcommand, '--quiet']];
95
+ const spawnArgs = cliSpawnArgs(resolveCliBinForHook(), ['hooks', subcommand, '--quiet']);
88
96
  const child = spawn(spawnArgs[0], spawnArgs[1], {
89
97
  detached: true,
90
98
  stdio: 'ignore',
@@ -151,10 +159,7 @@ function firstRunAutoEnableIfEligible() {
151
159
  const cliBin = resolveCliBinForHook();
152
160
  const runDetached = (args) => {
153
161
  try {
154
- const spawnArgs = cliBin
155
- ? [process.execPath, [cliBin, ...args]]
156
- : [process.platform === 'win32' ? 'npx.cmd' : 'npx',
157
- ['--prefer-offline', '@claude-flow/cli', ...args]];
162
+ const spawnArgs = cliSpawnArgs(cliBin, args);
158
163
  const child = spawn(spawnArgs[0], spawnArgs[1], {
159
164
  detached: true, stdio: 'ignore', env: process.env, windowsHide: true,
160
165
  });
@@ -772,4 +777,4 @@ if (require.main === module) {
772
777
  // Which sibling helpers loaded (all CommonJS, shipped as .cjs — #3555).
773
778
  const loadedHelpers = { router: !!router, session: !!session, memory: !!memory, intelligence: !!intelligence };
774
779
 
775
- module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS };
780
+ module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS, resolveCliBinForHook, cliSpawnArgs, helperRoot };
@@ -26,9 +26,12 @@ import Database from 'better-sqlite3';
26
26
 
27
27
  const __filename = fileURLToPath(import.meta.url);
28
28
  const __dirname = dirname(__filename);
29
+ // Executable modules resolve only from the helper's own install root; a
30
+ // home-level helper can serve an untrusted project. PROJECT_ROOT is data only.
31
+ const MODULE_ROOT = join(__dirname, '../..');
29
32
  const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
30
33
  ? resolve(process.env.CLAUDE_PROJECT_DIR)
31
- : join(__dirname, '../..');
34
+ : MODULE_ROOT;
32
35
  const DATA_DIR = join(PROJECT_ROOT, '.claude-flow/learning');
33
36
  const DB_PATH = join(DATA_DIR, 'patterns.db');
34
37
  const METRICS_PATH = join(DATA_DIR, 'learning-metrics.json');
@@ -463,7 +466,7 @@ class EmbeddingService {
463
466
 
464
467
  try {
465
468
  // Dynamically import agentic-flow OptimizedEmbedder
466
- const agenticFlowPath = join(PROJECT_ROOT, 'node_modules/agentic-flow/dist/embeddings/optimized-embedder.js');
469
+ const agenticFlowPath = join(MODULE_ROOT, 'node_modules/agentic-flow/dist/embeddings/optimized-embedder.js');
467
470
 
468
471
  if (existsSync(agenticFlowPath)) {
469
472
  const { getOptimizedEmbedder } = await import(agenticFlowPath);
@@ -6,6 +6,7 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const crypto = require('crypto');
9
10
  const os = require('os');
10
11
 
11
12
  const platform = os.platform();
@@ -30,6 +31,49 @@ function getDataDir() {
30
31
  const SESSION_DIR = getDataDir();
31
32
  const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
32
33
 
34
+ // Archive names are generated by start(); anything else in current.json is
35
+ // untrusted project content and must never pick a path.
36
+ const SESSION_ID = /^session-\d+$/;
37
+
38
+ // current.json lives in the opened project. A symlink there must never be read
39
+ // or written through, so only a regular file counts as an active session.
40
+ function hasSessionFile() {
41
+ try { return fs.lstatSync(SESSION_FILE).isFile(); } catch { return false; }
42
+ }
43
+
44
+ function archiveId(id) {
45
+ return SESSION_ID.test(String(id)) ? String(id) : `session-${Date.now()}`;
46
+ }
47
+
48
+ // The project session directory must resolve inside the project (no
49
+ // symlinked escape). The per-user fallback directory is not project-owned.
50
+ function assertSessionDirContained() {
51
+ if (SESSION_DIR !== path.join(process.cwd(), '.claude-flow', 'sessions')) return;
52
+ const root = fs.realpathSync(process.cwd());
53
+ // Resolve the deepest part of the path that exists (lstat: a symlink counts),
54
+ // so a symlinked .claude-flow is refused before anything is created under it.
55
+ let dir = SESSION_DIR;
56
+ for (;;) {
57
+ try { fs.lstatSync(dir); break; } catch { /* not created yet */ }
58
+ const parent = path.dirname(dir);
59
+ if (parent === dir) break;
60
+ dir = parent;
61
+ }
62
+ const rel = path.relative(root, fs.realpathSync(dir));
63
+ if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
64
+ throw new Error('Session directory resolves outside the project');
65
+ }
66
+ }
67
+
68
+ // Exclusive temp file + rename: never writes through a planted symlink, and
69
+ // rename() replaces a symlinked target instead of following it.
70
+ function atomicWrite(file, data) {
71
+ assertSessionDirContained();
72
+ const tmp = `${file}.tmp.${process.pid}.${crypto.randomBytes(6).toString('hex')}`;
73
+ fs.writeFileSync(tmp, data, { flag: 'wx' });
74
+ fs.renameSync(tmp, file);
75
+ }
76
+
33
77
  function ensureDir(dir) {
34
78
  if (!fs.existsSync(dir)) {
35
79
  fs.mkdirSync(dir, { recursive: true });
@@ -38,6 +82,7 @@ function ensureDir(dir) {
38
82
 
39
83
  const commands = {
40
84
  start: () => {
85
+ assertSessionDirContained();
41
86
  ensureDir(SESSION_DIR);
42
87
  const sessionId = `session-${Date.now()}`;
43
88
  const session = {
@@ -48,25 +93,25 @@ const commands = {
48
93
  context: {},
49
94
  metrics: { edits: 0, commands: 0, tasks: 0, errors: 0 }
50
95
  };
51
- fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
96
+ atomicWrite(SESSION_FILE, JSON.stringify(session, null, 2));
52
97
  console.log(`Session started: ${sessionId}`);
53
98
  return session;
54
99
  },
55
100
 
56
101
  restore: () => {
57
- if (!fs.existsSync(SESSION_FILE)) {
102
+ if (!hasSessionFile()) {
58
103
  console.log('No session to restore');
59
104
  return null;
60
105
  }
61
106
  const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
62
107
  session.restoredAt = new Date().toISOString();
63
- fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
108
+ atomicWrite(SESSION_FILE, JSON.stringify(session, null, 2));
64
109
  console.log(`Session restored: ${session.id}`);
65
110
  return session;
66
111
  },
67
112
 
68
113
  end: () => {
69
- if (!fs.existsSync(SESSION_FILE)) {
114
+ if (!hasSessionFile()) {
70
115
  console.log('No active session');
71
116
  return null;
72
117
  }
@@ -74,17 +119,18 @@ const commands = {
74
119
  session.endedAt = new Date().toISOString();
75
120
  session.duration = Date.now() - new Date(session.startedAt).getTime();
76
121
 
77
- const archivePath = path.join(SESSION_DIR, `${session.id}.json`);
78
- fs.writeFileSync(archivePath, JSON.stringify(session, null, 2));
122
+ // Archive under a validated name; a crafted id cannot pick the path.
123
+ const id = archiveId(session.id);
124
+ atomicWrite(path.join(SESSION_DIR, `${id}.json`), JSON.stringify(session, null, 2));
79
125
  fs.unlinkSync(SESSION_FILE);
80
126
 
81
- console.log(`Session ended: ${session.id}`);
127
+ console.log(`Session ended: ${id}`);
82
128
  console.log(`Duration: ${Math.round(session.duration / 1000 / 60)} minutes`);
83
129
  return session;
84
130
  },
85
131
 
86
132
  status: () => {
87
- if (!fs.existsSync(SESSION_FILE)) {
133
+ if (!hasSessionFile()) {
88
134
  console.log('No active session');
89
135
  return null;
90
136
  }
@@ -98,13 +144,13 @@ const commands = {
98
144
  },
99
145
 
100
146
  metric: (name) => {
101
- if (!fs.existsSync(SESSION_FILE)) {
147
+ if (!hasSessionFile()) {
102
148
  return null;
103
149
  }
104
150
  const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
105
151
  if (session.metrics[name] !== undefined) {
106
152
  session.metrics[name]++;
107
- fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
153
+ atomicWrite(SESSION_FILE, JSON.stringify(session, null, 2));
108
154
  }
109
155
  return session;
110
156
  }