@claude-flow/cli 3.45.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.
- package/.claude/helpers/helpers.manifest.json +2 -2
- package/README.md +2 -0
- package/bin/cli.js +13 -0
- package/bin/mcp-server.js +16 -0
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor.d.ts +3 -0
- package/dist/src/commands/doctor.js +131 -2
- package/dist/src/commands/embeddings.js +50 -7
- package/dist/src/commands/init.js +5 -2
- package/dist/src/commands/mcp.js +27 -4
- package/dist/src/mcp-server.d.ts +50 -6
- package/dist/src/mcp-server.js +267 -33
- package/dist/src/mcp-tools/agent-execute-core.js +7 -0
- package/dist/src/mcp-tools/agentdb-tools.js +20 -8
- package/dist/src/mcp-tools/browser-tools.js +30 -23
- package/dist/src/mcp-tools/memory-tools.js +9 -4
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/mcp-tools/policy-enforcer.d.ts +4 -4
- package/dist/src/mcp-tools/policy-enforcer.js +4 -4
- package/dist/src/mcp-tools/terminal-tools.js +11 -1
- package/dist/src/mcp-tools/workflow-tools.js +1 -1
- package/dist/src/memory/ewc-consolidation.d.ts +16 -1
- package/dist/src/memory/ewc-consolidation.js +63 -13
- package/dist/src/memory/intelligence.d.ts +6 -2
- package/dist/src/memory/intelligence.js +18 -11
- package/dist/src/memory/memory-bridge.js +84 -0
- package/dist/src/ruvector/model-router.js +19 -1
- package/dist/src/services/distill-oracle.js +2 -1
- package/dist/src/services/git-workspace-identity.js +5 -8
- package/dist/src/services/policy-runtime.js +114 -4
- package/dist/src/services/worker-daemon.js +8 -3
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js +2 -6
- package/node_modules/@claude-flow/codex/dist/worktrees/coordinator.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +14 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts +40 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.d.ts.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js +100 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/a2a/planner-descriptor.js.map +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/index.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts +22 -0
- package/node_modules/@claude-flow/security/dist/safe-git.d.ts.map +1 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js +63 -0
- package/node_modules/@claude-flow/security/dist/safe-git.js.map +1 -0
- package/package.json +5 -5
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
- package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +1 -1
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +84 -12
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +10 -4
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +120 -6
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +33 -6
- package/plugins/ruflo-metaharness/scripts/audit-list.mjs +11 -10
- package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +2 -3
- package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +8 -7
- package/plugins/ruflo-metaharness/scripts/similarity.mjs +9 -8
- package/plugins/ruflo-metaharness/scripts/smoke.sh +31 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +319 -0
- package/plugins/ruflo-metaharness/scripts/test-pin-alignment.mjs +182 -0
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +4 -2
- package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +3 -1
- package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +1 -1
|
@@ -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.
|
|
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
|
|
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.
|
|
47
|
-
//
|
|
48
|
-
//
|
|
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.
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
-
//
|
|
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.
|
|
57
|
-
//
|
|
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.
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
84
|
-
|
|
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
|
|
103
|
-
// succeeds (argv[1] matches import.meta.url)
|
|
104
|
-
|
|
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:
|
|
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 {
|
|
19
|
+
import { runRufloCli } from './_invoke.mjs';
|
|
20
20
|
|
|
21
21
|
const NS = process.env.AUDIT_LIST_NAMESPACE || 'metaharness-audit';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 =
|
|
47
|
-
|
|
47
|
+
const r = runRufloCli([
|
|
48
|
+
'memory', 'list',
|
|
48
49
|
'--namespace', NS, '--format', 'json',
|
|
49
|
-
]
|
|
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 =
|
|
58
|
-
|
|
58
|
+
const r = runRufloCli([
|
|
59
|
+
'memory', 'retrieve',
|
|
59
60
|
'--namespace', NS, '--key', key,
|
|
60
|
-
]
|
|
61
|
+
]);
|
|
61
62
|
if (r.status !== 0) return null;
|
|
62
63
|
const m = /\{[\s\S]*\}/.exec(r.stdout || '');
|
|
63
64
|
if (!m) return null;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// 2 config error or input not found
|
|
26
26
|
|
|
27
27
|
import { readFileSync, existsSync } from 'node:fs';
|
|
28
|
-
import {
|
|
28
|
+
import { runRufloCli } from './_invoke.mjs';
|
|
29
29
|
// iter 38 — structural-distance drift via ADR-152 §3.1 production module.
|
|
30
30
|
// Falls back to null if either record predates iter-38 oia-audit (no
|
|
31
31
|
// fingerprint field) — graceful degradation, never throws.
|
|
@@ -36,9 +36,10 @@ import { SEVERITY_RANK, rankSeverity } from './_harness.mjs';
|
|
|
36
36
|
|
|
37
37
|
// iter 63 — SEVERITY_RANK moved to _harness.mjs (imported above)
|
|
38
38
|
const NS = process.env.AUDIT_TREND_NAMESPACE || 'metaharness-audit';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// #3366 — memory calls go through runRufloCli() (_invoke.mjs): the ruflo CLI
|
|
40
|
+
// that ships this plugin, not `npx @claude-flow/cli@latest` (npm-registry
|
|
41
|
+
// resolution per call, possible version skew, fails without registry access).
|
|
42
|
+
// CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha`.
|
|
42
43
|
|
|
43
44
|
const ARGS = (() => {
|
|
44
45
|
const a = {
|
|
@@ -62,10 +63,10 @@ const ARGS = (() => {
|
|
|
62
63
|
})();
|
|
63
64
|
|
|
64
65
|
function memRetrieve(key) {
|
|
65
|
-
const r =
|
|
66
|
-
|
|
66
|
+
const r = runRufloCli([
|
|
67
|
+
'memory', 'retrieve',
|
|
67
68
|
'--namespace', NS, '--key', key,
|
|
68
|
-
]
|
|
69
|
+
]);
|
|
69
70
|
if (r.status !== 0) return null;
|
|
70
71
|
const m = /\{[\s\S]*\}/.exec(r.stdout || '');
|
|
71
72
|
if (!m) return null;
|
|
@@ -42,9 +42,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
42
42
|
|
|
43
43
|
const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
44
44
|
const NS = process.env.METAHARNESS_AUDIT_NAMESPACE || 'metaharness-audit';
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
: '@claude-flow/cli@latest';
|
|
45
|
+
// Memory access is delegated to audit-list / oia-audit / audit-trend (they
|
|
46
|
+
// call the shipping ruflo CLI through _invoke.runRufloCli — #3366).
|
|
48
47
|
|
|
49
48
|
const ARGS = (() => {
|
|
50
49
|
const a = {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// 1 --alert-on-worst threshold exceeded
|
|
25
25
|
// 2 config error or audit failure
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import { runRufloCli } from './_invoke.mjs';
|
|
28
28
|
// iter 63 — SEVERITY_RANK + rankSeverity consolidated to _harness.mjs
|
|
29
29
|
// (was local in iter 62; now shared with audit-trend + mcp-scan).
|
|
30
30
|
import { runHarness, runMetaharness, runHarnessAsync, runMetaharnessAsync, emitDegradedJsonAndExit, parseMcpScanText, SEVERITY_RANK, rankSeverity } from './_harness.mjs';
|
|
@@ -33,9 +33,10 @@ import { runHarness, runMetaharness, runHarnessAsync, runMetaharnessAsync, emitD
|
|
|
33
33
|
// source of truth). The local copy from iter 62 is gone; the imports
|
|
34
34
|
// at the top of this file provide the same names.
|
|
35
35
|
const NS = process.env.OIA_AUDIT_NAMESPACE || 'metaharness-audit';
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// #3366 — memory calls go through runRufloCli() (_invoke.mjs): the ruflo CLI
|
|
37
|
+
// that ships this plugin, not `npx @claude-flow/cli@latest` (npm-registry
|
|
38
|
+
// resolution per call, possible version skew, fails without registry access).
|
|
39
|
+
// CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha`.
|
|
39
40
|
|
|
40
41
|
const ARGS = (() => {
|
|
41
42
|
const a = { path: '.', format: 'json', dryRun: false, alertWorst: null };
|
|
@@ -97,12 +98,12 @@ async function runAllParallel(path) {
|
|
|
97
98
|
|
|
98
99
|
function persist(payload) {
|
|
99
100
|
const key = `audit-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
100
|
-
const r =
|
|
101
|
-
|
|
101
|
+
const r = runRufloCli([
|
|
102
|
+
'memory', 'store',
|
|
102
103
|
'--namespace', NS,
|
|
103
104
|
'--key', key,
|
|
104
105
|
'--value', JSON.stringify(payload),
|
|
105
|
-
]
|
|
106
|
+
]);
|
|
106
107
|
return {
|
|
107
108
|
ok: r.status === 0,
|
|
108
109
|
namespace: NS,
|
|
@@ -18,19 +18,20 @@
|
|
|
18
18
|
//
|
|
19
19
|
// ADR-150 ARCHITECTURAL CONSTRAINTS PRESERVED
|
|
20
20
|
// - Pure-TS function (`_similarity.mjs`), no `@metaharness/*` import
|
|
21
|
-
// - No new dep; uses node:fs +
|
|
21
|
+
// - No new dep; uses node:fs + _invoke.runRufloCli only for memory lookup
|
|
22
22
|
// - Graceful degradation: missing keys / malformed JSON → exit 2 with
|
|
23
23
|
// a structured `{ degraded: true, reason: ... }` payload on stdout
|
|
24
24
|
// - CI-gate ready: smoke step 17y locks this contract
|
|
25
25
|
|
|
26
26
|
import { readFileSync, existsSync } from 'node:fs';
|
|
27
|
-
import {
|
|
27
|
+
import { runRufloCli } from './_invoke.mjs';
|
|
28
28
|
import { similarity } from './_similarity.mjs';
|
|
29
29
|
|
|
30
30
|
const NS = process.env.HARNESS_SIMILARITY_NAMESPACE || 'metaharness-audit';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
// #3366 — memory calls go through runRufloCli() (_invoke.mjs): the ruflo CLI
|
|
32
|
+
// that ships this plugin, not `npx @claude-flow/cli@latest` (npm-registry
|
|
33
|
+
// resolution per call, possible version skew, fails without registry access).
|
|
34
|
+
// CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha`.
|
|
34
35
|
|
|
35
36
|
const ARGS = (() => {
|
|
36
37
|
const a = {
|
|
@@ -66,10 +67,10 @@ function emitDegradedAndExit(reason, code = 2) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
function memRetrieve(key) {
|
|
69
|
-
const r =
|
|
70
|
-
|
|
70
|
+
const r = runRufloCli([
|
|
71
|
+
'memory', 'retrieve',
|
|
71
72
|
'--namespace', NS, '--key', key,
|
|
72
|
-
]
|
|
73
|
+
]);
|
|
73
74
|
if (r.status !== 0) return null;
|
|
74
75
|
const m = /\{[\s\S]*\}/.exec(r.stdout || '');
|
|
75
76
|
if (!m) return null;
|