@claude-flow/cli 3.44.0 → 3.46.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 (74) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/README.md +2 -0
  3. package/bin/cli.js +13 -0
  4. package/bin/mcp-server.js +16 -0
  5. package/catalog-manifest.json +2 -2
  6. package/dist/src/commands/doctor.d.ts +12 -0
  7. package/dist/src/commands/doctor.js +200 -3
  8. package/dist/src/commands/embeddings.js +50 -7
  9. package/dist/src/commands/init.js +5 -2
  10. package/dist/src/commands/mcp.js +27 -4
  11. package/dist/src/mcp-server.d.ts +50 -6
  12. package/dist/src/mcp-server.js +267 -33
  13. package/dist/src/mcp-tools/agent-execute-core.js +7 -0
  14. package/dist/src/mcp-tools/agentdb-tools.js +20 -8
  15. package/dist/src/mcp-tools/browser-tools.js +30 -23
  16. package/dist/src/mcp-tools/memory-tools.js +9 -4
  17. package/dist/src/mcp-tools/metaharness-tools.js +1 -1
  18. package/dist/src/mcp-tools/policy-enforcer.d.ts +4 -4
  19. package/dist/src/mcp-tools/policy-enforcer.js +4 -4
  20. package/dist/src/mcp-tools/terminal-tools.js +11 -1
  21. package/dist/src/mcp-tools/workflow-tools.js +1 -1
  22. package/dist/src/memory/ewc-consolidation.d.ts +16 -1
  23. package/dist/src/memory/ewc-consolidation.js +63 -13
  24. package/dist/src/memory/intelligence.d.ts +6 -2
  25. package/dist/src/memory/intelligence.js +18 -11
  26. package/dist/src/memory/memory-bridge.js +84 -0
  27. package/dist/src/ruvector/model-router.js +19 -1
  28. package/dist/src/services/distill-oracle.js +2 -1
  29. package/dist/src/services/git-workspace-identity.js +5 -8
  30. package/dist/src/services/policy-runtime.js +114 -4
  31. package/dist/src/services/worker-daemon.js +8 -3
  32. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  33. package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.d.ts.map +1 -1
  34. package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js +2 -6
  35. package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js.map +1 -1
  36. package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
  37. package/node_modules/@claude-flow/mcp/dist/transport/http.js +14 -1
  38. package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
  39. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts +40 -0
  40. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts.map +1 -0
  41. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js +100 -0
  42. package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js.map +1 -0
  43. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  44. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts +1 -0
  45. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts.map +1 -1
  46. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js +1 -0
  47. package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js.map +1 -1
  48. package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -0
  49. package/node_modules/@claude-flow/security/dist/safe-git.d.ts +22 -0
  50. package/node_modules/@claude-flow/security/dist/safe-git.d.ts.map +1 -0
  51. package/node_modules/@claude-flow/security/dist/safe-git.js +63 -0
  52. package/node_modules/@claude-flow/security/dist/safe-git.js.map +1 -0
  53. package/package.json +5 -5
  54. package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
  55. package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +1 -1
  56. package/plugins/ruflo-metaharness/scripts/_darwin.mjs +84 -12
  57. package/plugins/ruflo-metaharness/scripts/_harness.mjs +10 -4
  58. package/plugins/ruflo-metaharness/scripts/_invoke.mjs +120 -6
  59. package/plugins/ruflo-metaharness/scripts/_redblue.mjs +33 -6
  60. package/plugins/ruflo-metaharness/scripts/audit-list.mjs +11 -10
  61. package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +8 -7
  62. package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +2 -3
  63. package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +8 -7
  64. package/plugins/ruflo-metaharness/scripts/similarity.mjs +9 -8
  65. package/plugins/ruflo-metaharness/scripts/smoke.sh +31 -0
  66. package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +319 -0
  67. package/plugins/ruflo-metaharness/scripts/test-pin-alignment.mjs +182 -0
  68. package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +4 -2
  69. package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
  70. package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +1 -1
  71. package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +1 -1
  72. package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
  73. package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +3 -1
  74. package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-git.d.ts","sourceRoot":"","sources":["../src/safe-git.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CACrC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAI/E;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAQR;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAQR"}
@@ -0,0 +1,63 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ const GIT_CONFIG_OVERRIDE = ['-c', 'core.fsmonitor=false'];
3
+ const DEFAULT_MAX_BUFFER = 4 * 1024 * 1024;
4
+ /**
5
+ * Build the argument vector used for every RuFlo-controlled Git subprocess.
6
+ *
7
+ * `core.fsmonitor` is command-bearing repository-local configuration. A copied
8
+ * repository can therefore select a host command that Git executes while
9
+ * refreshing its index. Command-line `-c` has higher precedence than local
10
+ * repository configuration, so this invariant must precede every repository
11
+ * operation that RuFlo performs itself.
12
+ *
13
+ * This deliberately does not claim that every command-bearing Git setting is
14
+ * neutralized. Keep the wrapper as the single policy point so additional
15
+ * reviewed overrides can be added without auditing every caller again.
16
+ */
17
+ export function safeGitArgv(repoRoot, args) {
18
+ assertGitPath(repoRoot);
19
+ for (const arg of args)
20
+ assertGitArgument(arg);
21
+ return [...GIT_CONFIG_OVERRIDE, '-C', repoRoot, ...args];
22
+ }
23
+ export function safeGitTextSync(repoRoot, args, options = {}) {
24
+ return execFileSync('git', safeGitArgv(repoRoot, args), {
25
+ encoding: 'utf8',
26
+ stdio: ['ignore', 'pipe', options.stderr ?? 'pipe'],
27
+ maxBuffer: boundedMaxBuffer(options.maxBuffer),
28
+ ...(options.timeoutMs === undefined ? {} : { timeout: boundedTimeout(options.timeoutMs) }),
29
+ windowsHide: true,
30
+ }).trim();
31
+ }
32
+ export function safeGitBufferSync(repoRoot, args, options = {}) {
33
+ return execFileSync('git', safeGitArgv(repoRoot, args), {
34
+ encoding: 'buffer',
35
+ stdio: ['ignore', 'pipe', options.stderr ?? 'pipe'],
36
+ maxBuffer: boundedMaxBuffer(options.maxBuffer),
37
+ ...(options.timeoutMs === undefined ? {} : { timeout: boundedTimeout(options.timeoutMs) }),
38
+ windowsHide: true,
39
+ });
40
+ }
41
+ function assertGitPath(value) {
42
+ if (value.length === 0 || value.includes('\0')) {
43
+ throw new Error('Git repository path is invalid');
44
+ }
45
+ }
46
+ function assertGitArgument(value) {
47
+ if (value.includes('\0'))
48
+ throw new Error('Git argument contains a NUL byte');
49
+ }
50
+ function boundedMaxBuffer(value) {
51
+ const resolved = value ?? DEFAULT_MAX_BUFFER;
52
+ if (!Number.isSafeInteger(resolved) || resolved < 1024 || resolved > 256 * 1024 * 1024) {
53
+ throw new Error('Git maxBuffer is outside the supported range');
54
+ }
55
+ return resolved;
56
+ }
57
+ function boundedTimeout(value) {
58
+ if (!Number.isSafeInteger(value) || value < 1 || value > 120_000) {
59
+ throw new Error('Git timeout is outside the supported range');
60
+ }
61
+ return value;
62
+ }
63
+ //# sourceMappingURL=safe-git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-git.js","sourceRoot":"","sources":["../src/safe-git.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.44.0",
3
+ "version": "3.46.0",
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",
@@ -98,10 +98,10 @@
98
98
  "vitest": "^4.1.0"
99
99
  },
100
100
  "dependencies": {
101
- "@claude-flow/cli-core": "3.7.0-alpha.5",
101
+ "@claude-flow/cli-core": "3.7.0-alpha.6",
102
102
  "@claude-flow/codex": "3.0.3",
103
103
  "@claude-flow/mcp": "3.0.0-alpha.10",
104
- "@claude-flow/neural": "3.0.0-alpha.9",
104
+ "@claude-flow/neural": "3.0.0-alpha.10",
105
105
  "@claude-flow/plugin-agent-federation": "1.0.0-alpha.18",
106
106
  "@claude-flow/security": "3.0.0-alpha.14",
107
107
  "@claude-flow/shared": "3.0.0-alpha.7",
@@ -125,11 +125,11 @@
125
125
  "ws": "^8.21.0",
126
126
  "yaml": "^2.8.0",
127
127
  "zod": "^3.22.0",
128
- "@claude-flow/memory": "^3.0.0-alpha.23"
128
+ "@claude-flow/memory": "3.0.0-alpha.26"
129
129
  },
130
130
  "optionalDependencies": {
131
131
  "@agntcy/slim-bindings": "2.0.0-alpha.5",
132
- "@claude-flow/memory": "^3.0.0-alpha.23",
132
+ "@claude-flow/memory": "3.0.0-alpha.26",
133
133
  "@metaharness/darwin": "~0.10.2",
134
134
  "@metaharness/flywheel": "~0.1.10",
135
135
  "@metaharness/radio": "~0.1.0",
@@ -38,7 +38,7 @@ needs its own ADR.
38
38
 
39
39
  ## Tools (subprocess-only, no library imports)
40
40
 
41
- - All skills shell out to the pinned `metaharness` / `harness` binaries (`metaharness@~0.3.0`, local install or one-time versioned cache — never `@latest`) via the `_harness.mjs` shared helper.
41
+ - All skills shell out to the pinned `metaharness` / `harness` binaries (`metaharness@~0.4.1`, local install or one-time versioned cache — never `@latest`) via the `_harness.mjs` shared helper.
42
42
  - 60s hard timeout per subprocess.
43
43
  - Output captured + parsed; `--json` flag forced unless the script opts out.
44
44
  - No `@metaharness/*` import statement appears outside the optional-router path in `v3/@claude-flow/cli/src/ruvector/neural-router.ts`.
@@ -4,7 +4,7 @@ description: MetaHarness integration — score, genome, mint, mcp-scan, threat-m
4
4
  ---
5
5
 
6
6
  MetaHarness integration commands. All shell out to the PINNED
7
- `metaharness`/`harness` binaries (`metaharness@~0.3.0` — resolved from a local
7
+ `metaharness`/`harness` binaries (`metaharness@~0.4.1` — resolved from a local
8
8
  install or a one-time `~/.ruflo/metaharness-cache-<pin>` install, never
9
9
  `@latest`) via the `_harness.mjs` shared helper; no library imports on
10
10
  ruflo's boot path.
@@ -2,16 +2,17 @@
2
2
  //
3
3
  // Mirrors `_harness.mjs` for the umbrella `metaharness` / `harness` binaries,
4
4
  // but targets the separate darwin binary (`metaharness-darwin`) which is
5
- // published as its own npm package (`@metaharness/darwin@~0.8.0`).
5
+ // published as its own npm package (`@metaharness/darwin@~0.10.2`).
6
6
  //
7
7
  // Shared plumbing (degraded classification, --json injection, trailing-JSON
8
8
  // parse, degraded emitter) lives in `_invoke.mjs`. What stays here is the
9
9
  // genuinely-darwin part: the ASYNC STREAMING variant (`runDarwinAsync`) that
10
10
  // long `evolve` runs need for per-generation progress visibility.
11
11
  //
12
- // Three subcommands surfaced (matches darwin 0.8.x — same verbs as 0.3.x,
13
- // with GEPA-engine evolve flags added upstream: --selection modes,
14
- // --crossover, --epistasis, --curriculum, --mutator ruvllm, --sandbox):
12
+ // Three subcommands surfaced (matches darwin 0.8.x-0.10.x — same verbs as
13
+ // 0.3.x, with GEPA-engine evolve flags added upstream: --selection modes,
14
+ // --crossover, --epistasis, --curriculum, --mutator ruvllm, --sandbox;
15
+ // 0.10.x only ADDS `evolve-numeric`, which this helper does not surface):
15
16
  // - `metaharness-darwin evolve <repo> [...]` — harness self-improvement
16
17
  // - `metaharness-darwin bench <create|verify> ...` — bench-suite lifecycle
17
18
  // - `metaharness-darwin security bench [...]` — Darwin Shield (upstream ADR-155)
@@ -31,23 +32,43 @@
31
32
  // matched to their --generations × --children × sandbox cost; the 60s
32
33
  // default is for `bench verify` and `security bench --population 2 --cycles 1`
33
34
  // smoke shapes only.
35
+ //
36
+ // RESOLUTION (#3366 — was `npx -y -p @metaharness/darwin@<pin>` on every call)
37
+ // Same order as _harness.mjs: (a) an installed @metaharness/darwin that
38
+ // satisfies the pin (the @claude-flow/cli optionalDependency — free), then
39
+ // (b) the one-time versioned `~/.ruflo/darwin-cache-<pin>` install that gepa
40
+ // already shares, then `process.execPath <abs bin>` with shell:false. The
41
+ // npx path had npm resolve the range on every evolve / bench /
42
+ // security-bench call (registry or npx cache state), ignored the copy ruflo
43
+ // ships, and could run a different darwin than the gepa import in the same
44
+ // plugin. (a) searches ruflo's own tree only — see findLocalPackageDir's
45
+ // `fromCwd` in _invoke.mjs.
34
46
 
35
47
  import { spawnSync, spawn } from 'node:child_process';
36
48
  import {
37
49
  classifyDegraded,
50
+ ensureCachedInstall,
51
+ findLocalPackageDir,
38
52
  importOptionalLibrary,
39
53
  injectJson,
40
54
  makeDegradedEmitter,
55
+ packageBinRelPath,
41
56
  parseTrailingJson,
57
+ resolvePackageBin,
42
58
  } from './_invoke.mjs';
43
59
 
44
60
  const DEFAULT_TIMEOUT_MS = 60_000;
45
61
 
46
- // Pinned semver range. Bump in lock-step with optionalDependencies in
47
- // @claude-flow/cli/package.json + ruflo/package.json. The `~` allows
48
- // patch upgrades without re-pinning.
62
+ // Pinned semver range. The `~` allows patch upgrades without re-pinning.
63
+ // Must cover the `@metaharness/darwin` optionalDependency range in
64
+ // v3/@claude-flow/cli/package.json (`~0.10.2`). It sat at ~0.8.0 while that
65
+ // range moved to ~0.9.0 (#2958) and ~0.10.2 (#3262), because the pin-drift
66
+ // guard only compared MH_DARWIN_PIN (distill-oracle.ts). This range picks
67
+ // the darwin version the evolve / bench / security-bench tools run and the
68
+ // gepa cache fallback installs; scripts/check-metaharness-pins.mjs now fails
69
+ // when it no longer covers the declared range.
49
70
  const DARWIN_PKG = '@metaharness/darwin';
50
- const DARWIN_PIN_VERSION = '~0.8.0';
71
+ const DARWIN_PIN_VERSION = '~0.10.2';
51
72
  const DARWIN_PIN = `${DARWIN_PKG}@${DARWIN_PIN_VERSION}`;
52
73
 
53
74
  const REASON_PREFIX = 'metaharness-darwin';
@@ -68,22 +89,71 @@ function buildArgv(args, wantJson) {
68
89
  // (This last-block parse is now the family-wide shared implementation.)
69
90
  const maybeParseJson = parseTrailingJson;
70
91
 
92
+ const DARWIN_BIN = 'metaharness-darwin';
93
+
94
+ /**
95
+ * Absolute path of the `metaharness-darwin` bin (see RESOLUTION above).
96
+ * Memoized per process, like _harness.mjs resolveMetaharnessBins().
97
+ */
98
+ let RESOLVED = null;
99
+ function resolveDarwinBin() {
100
+ if (RESOLVED) return RESOLVED;
101
+ // fromCwd:false — this lookup ends in a spawn of the bin with argv that
102
+ // comes from MCP input, so it only searches the ruflo install that owns
103
+ // this plugin, never the tool's cwd or its ancestors (see _invoke.mjs).
104
+ const localDir = findLocalPackageDir(DARWIN_PKG, DARWIN_PIN_VERSION, { fromCwd: false });
105
+ const localBin = localDir ? resolvePackageBin(localDir, DARWIN_BIN) : null;
106
+ if (localBin) return (RESOLVED = { ok: true, bin: localBin, source: 'local' });
107
+ let cached = ensureCachedInstall({ pkg: DARWIN_PKG, pinVersion: DARWIN_PIN_VERSION });
108
+ let cachedBin = cached.ok ? resolvePackageBin(cached.pkgDir, DARWIN_BIN) : null;
109
+ // The default cache probe is `<pkgDir>/package.json`, so a half-written
110
+ // cache — npm killed by the 180s install timeout, or two first calls
111
+ // installing into the same prefix — would count as installed forever and
112
+ // degrade every later call. npx repaired that by re-resolving; re-run the
113
+ // install once, probing the bin file itself, then give up (#3366).
114
+ if (cached.ok && !cachedBin) {
115
+ cached = ensureCachedInstall({
116
+ pkg: DARWIN_PKG,
117
+ pinVersion: DARWIN_PIN_VERSION,
118
+ cliRelPath: packageBinRelPath(cached.pkgDir, DARWIN_BIN) ?? 'dist/cli.js',
119
+ });
120
+ cachedBin = cached.ok ? resolvePackageBin(cached.pkgDir, DARWIN_BIN) : null;
121
+ }
122
+ if (cachedBin) return (RESOLVED = { ok: true, bin: cachedBin, source: 'cache' });
123
+ return (RESOLVED = {
124
+ ok: false,
125
+ stdout: cached.stdout ?? '',
126
+ stderr: cached.stderr ?? cached.error ?? `${DARWIN_PIN} has no ${DARWIN_BIN} bin`,
127
+ });
128
+ }
129
+
130
+ /** Same degraded shape the npx path produced when the package was unreachable. */
131
+ function unavailable(resolved, start) {
132
+ return {
133
+ stdout: resolved.stdout, stderr: resolved.stderr,
134
+ exitCode: 127, json: null, durationMs: Date.now() - start,
135
+ degraded: true, reason: `${REASON_PREFIX}-not-available`,
136
+ };
137
+ }
138
+
71
139
  /**
72
140
  * Sync invocation. Use for short subcommands (`bench verify`, smoke shapes).
73
141
  * Async variant below for long-running `evolve` calls.
74
142
  */
75
143
  export function runDarwin(args, opts = {}) {
76
144
  const start = Date.now();
145
+ const resolved = resolveDarwinBin();
146
+ if (!resolved.ok) return unavailable(resolved, start);
77
147
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
78
148
  const wantJson = opts.json !== false;
79
149
  const argv = buildArgv(args, wantJson);
80
- const r = spawnSync('npx', ['-y', '-p', DARWIN_PIN, 'metaharness-darwin', ...argv], {
150
+ const r = spawnSync(process.execPath, [resolved.bin, ...argv], {
81
151
  stdio: ['ignore', 'pipe', 'pipe'],
82
152
  encoding: 'utf-8',
83
153
  timeout: timeoutMs,
84
154
  cwd: opts.cwd,
85
155
  env: { ...process.env, ...(opts.env || {}) },
86
- shell: process.platform === 'win32',
156
+ shell: false,
87
157
  });
88
158
  const durationMs = Date.now() - start;
89
159
  const stdout = r.stdout || '';
@@ -119,14 +189,16 @@ export function runDarwin(args, opts = {}) {
119
189
  export function runDarwinAsync(args, opts = {}) {
120
190
  return new Promise((resolve) => {
121
191
  const start = Date.now();
192
+ const resolved = resolveDarwinBin();
193
+ if (!resolved.ok) { resolve(unavailable(resolved, start)); return; }
122
194
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
123
195
  const wantJson = opts.json !== false;
124
196
  const argv = buildArgv(args, wantJson);
125
- const p = spawn('npx', ['-y', '-p', DARWIN_PIN, 'metaharness-darwin', ...argv], {
197
+ const p = spawn(process.execPath, [resolved.bin, ...argv], {
126
198
  stdio: ['ignore', 'pipe', 'pipe'],
127
199
  cwd: opts.cwd,
128
200
  env: { ...process.env, ...(opts.env || {}) },
129
- shell: process.platform === 'win32',
201
+ shell: false,
130
202
  });
131
203
  let stdout = '', stderr = '', stderrBuf = '';
132
204
  p.stdout?.on('data', (d) => { stdout += d.toString(); });
@@ -26,7 +26,8 @@
26
26
  // arbitrary code on user machines on the very next skill invocation.
27
27
  // The version is now PINNED to METAHARNESS_PIN_VERSION (tilde range —
28
28
  // patch updates only) and only bumped deliberately, in lock-step with
29
- // optionalDependencies in @claude-flow/cli + ruflo package.json.
29
+ // the range declared in @claude-flow/cli/package.json (enforced by
30
+ // scripts/check-metaharness-pins.mjs).
30
31
  // 2. PERF: @latest forced an npm-registry metadata check on EVERY call.
31
32
  // Resolution is now (a) an already-installed local metaharness
32
33
  // satisfying the pin (walk-up node_modules — free), then (b) a ONE-TIME
@@ -53,10 +54,15 @@ import {
53
54
 
54
55
  const DEFAULT_TIMEOUT_MS = 60_000;
55
56
 
56
- // Pinned semver range. Bump in lock-step with optionalDependencies in
57
- // @claude-flow/cli/package.json + ruflo/package.json. NEVER @latest.
57
+ // Pinned semver range. NEVER @latest. Must cover the `metaharness` range
58
+ // declared in v3/@claude-flow/cli/package.json (optional peer `^0.4.1`):
59
+ // this constant decides which INSTALLED copy findLocalPackageDir() accepts,
60
+ // so a range that misses the declared one rejects the copy ruflo ships and
61
+ // npm-installs another at tool-call time. It sat at ~0.3.0 after the CLI
62
+ // moved to ^0.4.1 (b8ec03f34) because nothing compared the two;
63
+ // scripts/check-metaharness-pins.mjs now fails when they diverge.
58
64
  const METAHARNESS_PKG = 'metaharness';
59
- const METAHARNESS_PIN_VERSION = '~0.3.0';
65
+ const METAHARNESS_PIN_VERSION = '~0.4.1';
60
66
 
61
67
  const REASON_NOT_AVAILABLE = 'metaharness-not-available';
62
68
 
@@ -21,9 +21,15 @@
21
21
  // invalidate stale caches automatically
22
22
  // - findLocalPackageDir() walk-up node_modules resolution so an already
23
23
  // installed optionalDependency is used for free
24
+ // - resolvePackageBin() realpath'd bin-map entry of a resolved package
25
+ // (#3366 — lets _darwin/_redblue run the installed
26
+ // copy instead of npx / a cache install)
24
27
  // - importOptionalLibrary() bare-import → cached-install fallback for
25
28
  // library entries (gepa) — never throws on absence
26
29
  // - makeDegradedEmitter() the ADR-150 rule-#3 exit-0 degraded payload
30
+ // - runRufloCli() `memory store|list|retrieve` through the ruflo CLI
31
+ // that ships this plugin (#3366) — replaces
32
+ // `npx @claude-flow/cli@latest` in 4 scripts
27
33
  //
28
34
  // WHAT DOES NOT LIVE HERE (the per-consumer parts):
29
35
  // - _redblue's node-direct isMain workaround rationale
@@ -36,7 +42,7 @@
36
42
  // - RUFLO_METAHARNESS_SKIP_LOCAL=1 disables local node_modules resolution
37
43
 
38
44
  import { spawnSync } from 'node:child_process';
39
- import { existsSync, mkdirSync, readFileSync } from 'node:fs';
45
+ import { existsSync, mkdirSync, readFileSync, realpathSync } from 'node:fs';
40
46
  import { homedir } from 'node:os';
41
47
  import { dirname, join } from 'node:path';
42
48
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -102,11 +108,22 @@ export function satisfiesTildeRange(version, pinVersion) {
102
108
  * user's ruflo install shipped with). Returns the package dir or null.
103
109
  * When `pinVersion` is given, only a copy satisfying the pin is accepted —
104
110
  * a stale major/minor in an ancestor node_modules is skipped, not used.
111
+ *
112
+ * `{ fromCwd: false }` (#3366) searches ONLY the ruflo install that owns this
113
+ * plugin. The $CWD walk reaches every ancestor of the tool's working
114
+ * directory, i.e. directories the user's project — or anything above it —
115
+ * controls. That is tolerable for the pre-existing `metaharness` lookup,
116
+ * which this PR does not change, but the two lookups added here (_darwin,
117
+ * _redblue) END IN A SPAWN of the package's bin with MCP-supplied argv, so
118
+ * they stay inside ruflo's own tree and fall back to the pinned cache
119
+ * install instead.
105
120
  */
106
- export function findLocalPackageDir(pkg, pinVersion) {
121
+ export function findLocalPackageDir(pkg, pinVersion, { fromCwd = true } = {}) {
107
122
  if (process.env.RUFLO_METAHARNESS_SKIP_LOCAL === '1') return null;
108
123
  const segments = pkg.split('/');
109
- const starts = [dirname(fileURLToPath(import.meta.url)), process.cwd()];
124
+ const starts = fromCwd
125
+ ? [dirname(fileURLToPath(import.meta.url)), process.cwd()]
126
+ : [dirname(fileURLToPath(import.meta.url))];
110
127
  const seen = new Set();
111
128
  for (const start of starts) {
112
129
  let dir = start;
@@ -131,17 +148,51 @@ export function findLocalPackageDir(pkg, pinVersion) {
131
148
  return null;
132
149
  }
133
150
 
151
+ /**
152
+ * Absolute path of `binName` from the package.json `bin` map of an already
153
+ * resolved package dir (findLocalPackageDir / ensureCachedInstall), or null.
154
+ * Read from the bin map rather than hardcoded, same as _harness.mjs.
155
+ *
156
+ * REALPATH'd on purpose (#3366): pnpm and other symlinked layouts hand us a
157
+ * symlinked package dir, and @metaharness/redblue's CLI only dispatches when
158
+ * `import.meta.url === file://${process.argv[1]}` (see _redblue.mjs) — Node
159
+ * reports import.meta.url as the REAL path, so argv[1] has to be one too or
160
+ * the CLI exits 0 having done nothing.
161
+ */
162
+ export function resolvePackageBin(pkgDir, binName) {
163
+ const rel = packageBinRelPath(pkgDir, binName);
164
+ if (!rel) return null;
165
+ try {
166
+ const abs = join(pkgDir, rel);
167
+ return existsSync(abs) ? realpathSync(abs) : null;
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ /** The package.json `bin` entry for `binName`, as declared (relative), or null. */
174
+ export function packageBinRelPath(pkgDir, binName) {
175
+ try {
176
+ const pj = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf-8'));
177
+ const rel = typeof pj.bin === 'string' ? pj.bin : pj.bin?.[binName];
178
+ return rel || null;
179
+ } catch {
180
+ return null;
181
+ }
182
+ }
183
+
134
184
  /**
135
185
  * One-time versioned cache install of a PINNED range. Generalizes
136
186
  * gepa.mjs:98-121 / _redblue.mjs:72-102. The cache dir is
137
187
  * `<base>/<shortname>-cache-<pin-digits>` (e.g. redblue-cache-0.1.4,
138
- * darwin-cache-0.8.0, metaharness-cache-0.3.0) so existing caches created
188
+ * darwin-cache-0.10.2, metaharness-cache-0.4.1) so existing caches created
139
189
  * by the pre-consolidation helpers remain valid, and bumping the pin
140
- * invalidates stale installs automatically.
190
+ * invalidates stale installs automatically (the pre-bump darwin-cache-0.8.0
191
+ * and metaharness-cache-0.3.0 dirs are simply no longer selected).
141
192
  *
142
193
  * @param {object} spec
143
194
  * @param {string} spec.pkg npm package name (may be scoped)
144
- * @param {string} spec.pinVersion tilde range, e.g. '~0.3.0'
195
+ * @param {string} spec.pinVersion tilde range, e.g. '~0.4.1'
145
196
  * @param {string} [spec.cliRelPath] path inside the package that must exist
146
197
  * post-install (also returned as cliPath)
147
198
  * @param {number} [spec.timeoutMs] install timeout (default 180s)
@@ -228,3 +279,66 @@ export function makeDegradedEmitter(pkg, pinVersion) {
228
279
  process.exit(0);
229
280
  };
230
281
  }
282
+
283
+ // ---------------------------------------------------------------------------
284
+ // The ruflo CLI that ships this plugin (#3366)
285
+ // ---------------------------------------------------------------------------
286
+ // audit-list / audit-trend / oia-audit / similarity read and write the
287
+ // `metaharness-audit` memory namespace through the ruflo CLI. They used to
288
+ // spawn `npx @claude-flow/cli@latest memory …`, which on every call:
289
+ // - resolved `latest` through the npm registry (the "metadata check on
290
+ // EVERY call" _harness.mjs removed for its own npx path); without registry
291
+ // access it fails, so oia_audit reports `persisted: false` and audit_list
292
+ // silently reports 0 records;
293
+ // - could run a DIFFERENT @claude-flow/cli than the one that spawned the
294
+ // tool (npx cache vs registry skew — #3306), i.e. another memory backend /
295
+ // schema than the MCP server's own memory_* tools see;
296
+ // - cold-fetched the whole CLI when the npx cache was empty (#3145, #3154).
297
+ // The plugin is published INSIDE @claude-flow/cli
298
+ // (<cli>/plugins/ruflo-metaharness/scripts — see prepare-publish.mjs), and the
299
+ // MCP server / `ruflo metaharness` dispatcher locate it by walking up from
300
+ // their own dist/, so the owning CLI is two directories above this plugin.
301
+ // It is run with process.execPath + bin/cli.js (the mcp-launch.cjs /
302
+ // daemon-autostart.ts pattern). A candidate only counts when dist/src/index.js
303
+ // exists next to bin/cli.js — mcp-launch.cjs resolveLocalCliBin()'s guard
304
+ // against an unbuilt checkout — AND its package.json is named
305
+ // @claude-flow/cli, a check added here so an unrelated directory two levels up
306
+ // can never qualify. Unchanged:
307
+ // - CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha` (ADR-100);
308
+ // - no usable local CLI (e.g. a marketplace clone with no build) still falls
309
+ // back to `npx @claude-flow/cli@latest`.
310
+ const PLUGIN_SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
311
+
312
+ let RUFLO_CLI = null;
313
+ /** `{ command, args, shell, source }` for invoking the ruflo CLI. Memoized. */
314
+ export function resolveRufloCli() {
315
+ if (RUFLO_CLI) return RUFLO_CLI;
316
+ if (process.env.CLI_CORE === '1') {
317
+ return (RUFLO_CLI = { command: 'npx', args: ['@claude-flow/cli-core@alpha'], shell: process.platform === 'win32', source: 'npx-cli-core' });
318
+ }
319
+ const candidates = [
320
+ join(PLUGIN_SCRIPTS_DIR, '..', '..', '..'), // published: <cli>/plugins/ruflo-metaharness/scripts
321
+ join(PLUGIN_SCRIPTS_DIR, '..', '..', '..', 'v3', '@claude-flow', 'cli'), // repo checkout / marketplace clone
322
+ ];
323
+ for (const dir of candidates) {
324
+ try {
325
+ const bin = join(dir, 'bin', 'cli.js');
326
+ const pj = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
327
+ if (pj.name === '@claude-flow/cli' && existsSync(bin) && existsSync(join(dir, 'dist', 'src', 'index.js'))) {
328
+ return (RUFLO_CLI = { command: process.execPath, args: [bin], shell: false, source: 'local' });
329
+ }
330
+ } catch { /* not this layout — try the next */ }
331
+ }
332
+ return (RUFLO_CLI = { command: 'npx', args: ['@claude-flow/cli@latest'], shell: process.platform === 'win32', source: 'npx' });
333
+ }
334
+
335
+ /** spawnSync the ruflo CLI with `args` (e.g. ['memory', 'list', …]); same result shape as before. */
336
+ export function runRufloCli(args, opts = {}) {
337
+ const cli = resolveRufloCli();
338
+ return spawnSync(cli.command, [...cli.args, ...args], {
339
+ stdio: ['ignore', 'pipe', 'pipe'],
340
+ encoding: 'utf-8',
341
+ shell: cli.shell,
342
+ ...opts,
343
+ });
344
+ }
@@ -32,6 +32,16 @@
32
32
  // (file separately — when fixed, we could go back to a bin-shim pattern,
33
33
  // though the node-direct cached path is now the family-wide standard anyway).
34
34
  //
35
+ // INSTALLED COPY FIRST (#3366): an @metaharness/redblue already on disk that
36
+ // satisfies the pin (a ruflo 3.42.4 install carries 0.1.4, via metaharness /
37
+ // ruvector) is used before the cache install, same order as _harness.mjs /
38
+ // _darwin.mjs. Previously the first call always ran `npm install` into the
39
+ // cache even with that copy present.
40
+ // Whichever copy is chosen, its CLI path is realpath'd
41
+ // (_invoke.resolvePackageBin / realpathSync): a pnpm-symlinked package dir,
42
+ // or a cache base under a symlink such as macOS /tmp -> /private/tmp, would
43
+ // otherwise hit the same isMain mismatch described above.
44
+ //
35
45
  // CONTRACT (matches runMetaharness/runDarwin):
36
46
  // - returns `{ stdout, stderr, exitCode, durationMs, degraded, reason? }`
37
47
  // - subprocess hard timeout (default 120s; --mock-judge runs are seconds)
@@ -47,12 +57,15 @@
47
57
  // $OPENROUTER_API_KEY which we never inject.
48
58
 
49
59
  import { spawnSync } from 'node:child_process';
60
+ import { realpathSync } from 'node:fs';
50
61
  import { homedir } from 'node:os';
51
62
  import { join } from 'node:path';
52
63
  import {
53
64
  classifyDegraded,
54
65
  ensureCachedInstall,
66
+ findLocalPackageDir,
55
67
  makeDegradedEmitter,
68
+ resolvePackageBin,
56
69
  } from './_invoke.mjs';
57
70
 
58
71
  const DEFAULT_TIMEOUT_MS = 120_000;
@@ -80,8 +93,15 @@ export function runRedblue(args, opts = {}) {
80
93
  const start = Date.now();
81
94
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
82
95
 
83
- // 1. Ensure redblue is installed in our cache dir (one-time install).
84
- const install = ensureCachedInstall({
96
+ // 0. An installed copy satisfying the pin — free, no npm (#3366).
97
+ // fromCwd:false: the resolved bin is SPAWNED with argv that comes from
98
+ // MCP input, so only ruflo's own tree is searched, never the tool's cwd
99
+ // or its ancestors (see findLocalPackageDir in _invoke.mjs).
100
+ const localDir = findLocalPackageDir(REDBLUE_PKG, REDBLUE_PIN_VERSION, { fromCwd: false });
101
+ const localCli = localDir ? resolvePackageBin(localDir, 'redblue') : null;
102
+
103
+ // 1. Otherwise ensure redblue is installed in our cache dir (one-time install).
104
+ const install = localCli ? { ok: true, cliPath: localCli } : ensureCachedInstall({
85
105
  pkg: REDBLUE_PKG,
86
106
  pinVersion: REDBLUE_PIN_VERSION,
87
107
  cliRelPath: CLI_REL_PATH,
@@ -99,15 +119,22 @@ export function runRedblue(args, opts = {}) {
99
119
  };
100
120
  }
101
121
 
102
- // 2. Invoke `node <real-path-to-cli> <args>` so upstream's isMain check
103
- // succeeds (argv[1] matches import.meta.url).
104
- const r = spawnSync('node', [install.cliPath, ...args], {
122
+ // 2. Invoke `<this node> <real-path-to-cli> <args>` so upstream's isMain
123
+ // check succeeds (argv[1] matches import.meta.url) — realpath'd, see
124
+ // header. process.execPath + shell:false, like _darwin/_harness: the
125
+ // argv carries MCP-supplied values (--config / --out / --in), and with
126
+ // shell:true on win32 Node hands cmd.exe one unquoted string, so a value
127
+ // such as `x.yaml & whoami` would run as a command (and a path with a
128
+ // space — `C:\Program Files\…` — would break). node needs no shell.
129
+ let cliPath = install.cliPath;
130
+ try { cliPath = realpathSync(cliPath); } catch { /* missing — node's MODULE_NOT_FOUND → degraded below */ }
131
+ const r = spawnSync(process.execPath, [cliPath, ...args], {
105
132
  stdio: ['ignore', 'pipe', 'pipe'],
106
133
  encoding: 'utf-8',
107
134
  timeout: timeoutMs,
108
135
  cwd: opts.cwd,
109
136
  env: { ...process.env, ...(opts.env || {}) },
110
- shell: process.platform === 'win32',
137
+ shell: false,
111
138
  });
112
139
  const durationMs = Date.now() - start;
113
140
  const stdout = r.stdout || '';
@@ -16,12 +16,13 @@
16
16
  // 0 ok
17
17
  // 2 config error
18
18
 
19
- import { spawnSync } from 'node:child_process';
19
+ import { runRufloCli } from './_invoke.mjs';
20
20
 
21
21
  const NS = process.env.AUDIT_LIST_NAMESPACE || 'metaharness-audit';
22
- const CLI_PKG = process.env.CLI_CORE === '1'
23
- ? '@claude-flow/cli-core@alpha'
24
- : '@claude-flow/cli@latest';
22
+ // #3366 — memory calls go through runRufloCli() (_invoke.mjs): the ruflo CLI
23
+ // that ships this plugin, not `npx @claude-flow/cli@latest` (npm-registry
24
+ // resolution per call, possible version skew, fails without registry access).
25
+ // CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha`.
25
26
 
26
27
  const ARGS = (() => {
27
28
  const a = { limit: 20, since: null, format: 'table' };
@@ -43,10 +44,10 @@ function parseDurationMs(spec) {
43
44
  }
44
45
 
45
46
  function memList() {
46
- const r = spawnSync('npx', [
47
- CLI_PKG, 'memory', 'list',
47
+ const r = runRufloCli([
48
+ 'memory', 'list',
48
49
  '--namespace', NS, '--format', 'json',
49
- ], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8', shell: process.platform === 'win32' });
50
+ ]);
50
51
  if (r.status !== 0) return [];
51
52
  const m = /\[[\s\S]*\]/.exec(r.stdout || '');
52
53
  if (!m) return [];
@@ -54,10 +55,10 @@ function memList() {
54
55
  }
55
56
 
56
57
  function memRetrieve(key) {
57
- const r = spawnSync('npx', [
58
- CLI_PKG, 'memory', 'retrieve',
58
+ const r = runRufloCli([
59
+ 'memory', 'retrieve',
59
60
  '--namespace', NS, '--key', key,
60
- ], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8', shell: process.platform === 'win32' });
61
+ ]);
61
62
  if (r.status !== 0) return null;
62
63
  const m = /\{[\s\S]*\}/.exec(r.stdout || '');
63
64
  if (!m) return null;