@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.
- 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 +12 -0
- package/dist/src/commands/doctor.js +200 -3
- 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/cli.js +0 -0
- 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/bin.js +0 -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/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
|
@@ -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;
|
|
@@ -253,6 +253,24 @@ grep -q "bin.harness" "$F" 2>/dev/null || miss="$miss no-harness-bin"
|
|
|
253
253
|
grep -q "cwd: opts" "$F" || miss="$miss no-cwd-passthrough"
|
|
254
254
|
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
255
255
|
|
|
256
|
+
step "17r2. no tool-time npx: darwin / redblue / memory use what is installed (#3366)"
|
|
257
|
+
miss=""
|
|
258
|
+
# 17r locked _harness.mjs off npx; _darwin.mjs still ran `npx -y -p
|
|
259
|
+
# @metaharness/darwin@<pin>` per call, _redblue.mjs npm-installed into its
|
|
260
|
+
# cache ignoring the installed copy, and 4 scripts ran
|
|
261
|
+
# `npx @claude-flow/cli@latest memory …`. Static guard + hermetic runtime
|
|
262
|
+
# gate (stub packages in the published layout, npm/npx trap).
|
|
263
|
+
if grep -qE "spawn(Sync)?\('npx'" "$ROOT/scripts/_darwin.mjs" 2>/dev/null; then
|
|
264
|
+
miss="$miss darwin-npx-regressed"
|
|
265
|
+
fi
|
|
266
|
+
for s in audit-list audit-trend oia-audit similarity; do
|
|
267
|
+
grep -q "runRufloCli" "$ROOT/scripts/${s}.mjs" 2>/dev/null || miss="$miss ${s}-not-using-runRufloCli"
|
|
268
|
+
done
|
|
269
|
+
F="$ROOT/scripts/test-no-tool-time-npx.mjs"
|
|
270
|
+
[[ -x "$F" ]] || miss="$miss not-executable"
|
|
271
|
+
node "$F" >/dev/null 2>&1 || miss="$miss test-no-tool-time-npx-fails"
|
|
272
|
+
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
273
|
+
|
|
256
274
|
step "17z80. HIGH security findings survive wrapper + composite boundaries (#2750)"
|
|
257
275
|
miss=""
|
|
258
276
|
SEC_FIXTURE=$(mktemp -d)
|
|
@@ -461,6 +479,19 @@ for pkg in "metaharness" "@metaharness/router" "@metaharness/kernel" "@metaharne
|
|
|
461
479
|
done
|
|
462
480
|
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
463
481
|
|
|
482
|
+
step "17z74a. plugin helper pins accept the CLI-declared ranges — no tool-time download (#3366)"
|
|
483
|
+
miss=""
|
|
484
|
+
# 17z74 compares package.json files with each other; the helpers' own
|
|
485
|
+
# *_PIN_VERSION constants (what findLocalPackageDir accepts) were never
|
|
486
|
+
# compared with them and drifted (~0.3.0 / ~0.8.0 vs declared ^0.4.1 /
|
|
487
|
+
# ~0.10.2), so every metaharness_* call npm-installed an older release next
|
|
488
|
+
# to the one ruflo ships. Hermetic runtime gate: stub packages at the declared
|
|
489
|
+
# versions, published layout, npm/npx trap.
|
|
490
|
+
F="$ROOT/scripts/test-pin-alignment.mjs"
|
|
491
|
+
[[ -x "$F" ]] || miss="$miss not-executable"
|
|
492
|
+
node "$F" >/dev/null 2>&1 || miss="$miss test-pin-alignment-fails"
|
|
493
|
+
[[ -z "$miss" ]] && ok || bad "$miss"
|
|
494
|
+
|
|
464
495
|
step "17z73. metaharness packages tilde-pinned (anti-caret regression, iter 110)"
|
|
465
496
|
miss=""
|
|
466
497
|
# ADR-150 architectural-constraint review-round-1 mandated tilde pinning
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// test-no-tool-time-npx.mjs — regression test: metaharness_* tool calls must
|
|
3
|
+
// not reach for npm / npx when what they need is already installed (#3366).
|
|
4
|
+
//
|
|
5
|
+
// THE BUG
|
|
6
|
+
// Three helpers spawned the npm registry at TOOL-CALL time even on a complete
|
|
7
|
+
// ruflo install:
|
|
8
|
+
// - _darwin.mjs `npx -y -p @metaharness/darwin@<pin> metaharness-darwin`
|
|
9
|
+
// on EVERY evolve / bench / security-bench call, although
|
|
10
|
+
// the CLI ships @metaharness/darwin as an optionalDependency
|
|
11
|
+
// - _redblue.mjs `npm install --prefix ~/.ruflo/redblue-cache-<pin>` on
|
|
12
|
+
// first use, never looking at the installed copy
|
|
13
|
+
// - audit-list / audit-trend / oia-audit / similarity
|
|
14
|
+
// `npx @claude-flow/cli@latest memory …` — a registry
|
|
15
|
+
// round trip per call, possibly a DIFFERENT CLI version
|
|
16
|
+
// than the server that spawned the tool (#3306), and a
|
|
17
|
+
// silent failure offline (oia_audit persisted:false,
|
|
18
|
+
// audit_list "0 records")
|
|
19
|
+
//
|
|
20
|
+
// WHAT THIS TEST PROVES (hermetic — stub packages, no network)
|
|
21
|
+
// A PUBLISHED layout is built in a temp dir:
|
|
22
|
+
// <p>/node_modules/@claude-flow/cli/{package.json,bin/cli.js,dist/src/index.js}
|
|
23
|
+
// <p>/node_modules/@claude-flow/cli/plugins/ruflo-metaharness/scripts/* (copied)
|
|
24
|
+
// <p>/node_modules/metaharness stub, satisfies the _harness pin
|
|
25
|
+
// <p>/node_modules/@metaharness/darwin stub, satisfies the _darwin pin
|
|
26
|
+
// <p>/node_modules/@metaharness/redblue → <p>/store/redblue (pnpm-style
|
|
27
|
+
// SYMLINK; the stub keeps upstream redblue's
|
|
28
|
+
// `import.meta.url === file://${argv[1]}` isMain guard)
|
|
29
|
+
// with a PATH trap whose npm / npx log every call and exit 1 (offline).
|
|
30
|
+
// Phase 1 darwin: bench verify (sync) + security bench (async) run the
|
|
31
|
+
// installed bin — not degraded, zero npm/npx; and, with no
|
|
32
|
+
// installed copy, the existing darwin-cache-<pin> runs instead
|
|
33
|
+
// Phase 2 redblue: the symlinked installed copy runs (isMain holds because
|
|
34
|
+
// the resolved bin is realpath'd) — zero npm/npx
|
|
35
|
+
// Phase 3 memory round trip through the CLI that ships the plugin:
|
|
36
|
+
// oia-audit persists → audit-list lists it → audit-trend and
|
|
37
|
+
// similarity read it back — zero npm/npx
|
|
38
|
+
// Phase 4 contracts kept: CLI_CORE=1 still opts into npx cli-core@alpha; a
|
|
39
|
+
// layout with no usable local CLI — missing, or present but not
|
|
40
|
+
// named @claude-flow/cli — still falls back to
|
|
41
|
+
// `npx @claude-flow/cli@latest`; darwin absent → degraded, exit 0
|
|
42
|
+
// (ADR-150 rule #3)
|
|
43
|
+
// Phase 5 the two new lookups stay inside ruflo's tree: a darwin / redblue
|
|
44
|
+
// in the tool's OWN cwd node_modules is never executed
|
|
45
|
+
// ({fromCwd:false}); a cache dir with package.json but no bin gets
|
|
46
|
+
// one reinstall instead of degrading forever; and neither helper
|
|
47
|
+
// spawns the resolved bin through a shell
|
|
48
|
+
//
|
|
49
|
+
// USAGE
|
|
50
|
+
// node plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs
|
|
51
|
+
//
|
|
52
|
+
// EXIT CODES
|
|
53
|
+
// 0 all assertions passed (or skipped on win32 — the trap is POSIX sh)
|
|
54
|
+
// 1 at least one assertion failed
|
|
55
|
+
|
|
56
|
+
import { spawnSync } from 'node:child_process';
|
|
57
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
58
|
+
import { tmpdir } from 'node:os';
|
|
59
|
+
import { dirname, join } from 'node:path';
|
|
60
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
61
|
+
|
|
62
|
+
const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
63
|
+
|
|
64
|
+
if (process.platform === 'win32') {
|
|
65
|
+
console.log('# test-no-tool-time-npx — SKIPPED on win32 (the npm/npx trap is a POSIX sh script)');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let passed = 0, failed = 0;
|
|
70
|
+
const failures = [];
|
|
71
|
+
function assert(cond, label) {
|
|
72
|
+
console.log(` ${cond ? '✓' : '✗'} ${label}`);
|
|
73
|
+
if (cond) passed++;
|
|
74
|
+
else { failures.push(label); failed++; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Lowest version a tilde pin admits — the stubs sit exactly there. */
|
|
78
|
+
function floorOfPin(fullPin) {
|
|
79
|
+
const m = /@[~^]?(\d+\.\d+\.\d+)$/.exec(fullPin);
|
|
80
|
+
return m ? m[1] : '0.0.0';
|
|
81
|
+
}
|
|
82
|
+
const { METAHARNESS_VERSION_PIN } = await import(pathToFileURL(join(SCRIPTS_DIR, '_harness.mjs')).href);
|
|
83
|
+
const { DARWIN_VERSION_PIN } = await import(pathToFileURL(join(SCRIPTS_DIR, '_darwin.mjs')).href);
|
|
84
|
+
const { REDBLUE_VERSION_PIN } = await import(pathToFileURL(join(SCRIPTS_DIR, '_redblue.mjs')).href);
|
|
85
|
+
|
|
86
|
+
const write = (file, body, mode) => {
|
|
87
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
88
|
+
writeFileSync(file, body);
|
|
89
|
+
if (mode) chmodSync(file, mode);
|
|
90
|
+
};
|
|
91
|
+
const echoStub = (name, version) =>
|
|
92
|
+
`console.log(JSON.stringify({ stub: '${name}', version: '${version}', argv: process.argv.slice(2) }));\n`;
|
|
93
|
+
|
|
94
|
+
// realpath: macOS tmpdir() is /var/… while resolved paths come back as /private/var/…
|
|
95
|
+
const TMP = realpathSync(mkdtempSync(join(tmpdir(), 'ruflo-no-tool-time-npx-')));
|
|
96
|
+
const NM = join(TMP, 'node_modules');
|
|
97
|
+
const CLI = join(NM, '@claude-flow', 'cli');
|
|
98
|
+
const PLUGIN_SCRIPTS = join(CLI, 'plugins', 'ruflo-metaharness', 'scripts');
|
|
99
|
+
const TRAP = join(TMP, 'trap-bin');
|
|
100
|
+
const TRAP_LOG = join(TMP, 'trap.log');
|
|
101
|
+
const CLI_LOG = join(TMP, 'cli-calls.log');
|
|
102
|
+
const PROJECT = join(TMP, 'project');
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
// ── fixture ───────────────────────────────────────────────────────────
|
|
106
|
+
mkdirSync(PLUGIN_SCRIPTS, { recursive: true });
|
|
107
|
+
for (const f of readdirSync(SCRIPTS_DIR)) {
|
|
108
|
+
if (f.endsWith('.mjs')) copyFileSync(join(SCRIPTS_DIR, f), join(PLUGIN_SCRIPTS, f));
|
|
109
|
+
}
|
|
110
|
+
mkdirSync(PROJECT);
|
|
111
|
+
|
|
112
|
+
// The ruflo CLI that ships the plugin: a stub with a tiny file-backed
|
|
113
|
+
// `memory store|list|retrieve`, logging every invocation.
|
|
114
|
+
write(join(CLI, 'package.json'), JSON.stringify({ name: '@claude-flow/cli', version: '0.0.0-test', type: 'module', bin: { cli: 'bin/cli.js' } }));
|
|
115
|
+
write(join(CLI, 'dist', 'src', 'index.js'), 'export {};\n');
|
|
116
|
+
write(join(CLI, 'bin', 'cli.js'), `
|
|
117
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
118
|
+
const argv = process.argv.slice(2);
|
|
119
|
+
appendFileSync(${JSON.stringify(CLI_LOG)}, argv.slice(0, 2).join(' ') + '\\n');
|
|
120
|
+
const opt = (k) => { const i = argv.indexOf(k); return i >= 0 ? argv[i + 1] : undefined; };
|
|
121
|
+
const DB = ${JSON.stringify(join(TMP, 'stub-memory.json'))};
|
|
122
|
+
const db = existsSync(DB) ? JSON.parse(readFileSync(DB, 'utf-8')) : {};
|
|
123
|
+
const ns = opt('--namespace') ?? 'default';
|
|
124
|
+
if (argv[0] !== 'memory') process.exit(2);
|
|
125
|
+
if (argv[1] === 'store') { db[ns] = { ...(db[ns] ?? {}), [opt('--key')]: opt('--value') }; writeFileSync(DB, JSON.stringify(db)); console.log('[OK] Data stored successfully'); }
|
|
126
|
+
else if (argv[1] === 'list') console.log(JSON.stringify(Object.keys(db[ns] ?? {}).map((key) => ({ key, namespace: ns }))));
|
|
127
|
+
else if (argv[1] === 'retrieve') { const v = db[ns]?.[opt('--key')]; if (v === undefined) { console.error('not found'); process.exit(1); } console.log(v); }
|
|
128
|
+
else process.exit(2);
|
|
129
|
+
`);
|
|
130
|
+
|
|
131
|
+
const mhVersion = floorOfPin(METAHARNESS_VERSION_PIN);
|
|
132
|
+
write(join(NM, 'metaharness', 'package.json'), JSON.stringify({ name: 'metaharness', version: mhVersion, type: 'module', bin: { metaharness: 'dist/bin.js', harness: 'dist/harness-bin.js' } }));
|
|
133
|
+
write(join(NM, 'metaharness', 'dist', 'bin.js'), echoStub('metaharness', mhVersion));
|
|
134
|
+
write(join(NM, 'metaharness', 'dist', 'harness-bin.js'), echoStub('harness', mhVersion));
|
|
135
|
+
|
|
136
|
+
const darwinVersion = floorOfPin(DARWIN_VERSION_PIN);
|
|
137
|
+
const pinDigits = DARWIN_VERSION_PIN.replace(/^.*@[~^]?/, ''); // cache-dir suffix
|
|
138
|
+
const DARWIN = join(NM, '@metaharness', 'darwin');
|
|
139
|
+
write(join(DARWIN, 'package.json'), JSON.stringify({ name: '@metaharness/darwin', version: darwinVersion, type: 'module', bin: { 'metaharness-darwin': './dist/cli.js' } }));
|
|
140
|
+
write(join(DARWIN, 'dist', 'cli.js'), echoStub('metaharness-darwin', darwinVersion));
|
|
141
|
+
|
|
142
|
+
// redblue lives in a pnpm-like store and is SYMLINKED into node_modules;
|
|
143
|
+
// its CLI keeps upstream's isMain guard, so it only prints when argv[1] is
|
|
144
|
+
// the real path.
|
|
145
|
+
const redblueVersion = floorOfPin(REDBLUE_VERSION_PIN);
|
|
146
|
+
const RB_STORE = join(TMP, 'store', 'redblue');
|
|
147
|
+
write(join(RB_STORE, 'package.json'), JSON.stringify({ name: '@metaharness/redblue', version: redblueVersion, type: 'module', bin: { redblue: './dist/cli/index.js', 'metaharness-redblue': './dist/cli/index.js' } }));
|
|
148
|
+
write(join(RB_STORE, 'dist', 'cli', 'index.js'),
|
|
149
|
+
'const isMain = import.meta.url === `file://${process.argv[1]}`;\n' +
|
|
150
|
+
`if (isMain) console.log(JSON.stringify({ stub: 'redblue', version: '${redblueVersion}', argv: process.argv.slice(2) }));\n`);
|
|
151
|
+
symlinkSync(RB_STORE, join(NM, '@metaharness', 'redblue'));
|
|
152
|
+
|
|
153
|
+
for (const t of ['npm', 'npx']) {
|
|
154
|
+
write(join(TRAP, t), `#!/bin/sh\nprintf '%s %s\\n' "${t}" "$*" >> "${TRAP_LOG}"\necho "npm ERR! ${t} blocked by test-no-tool-time-npx trap" >&2\nexit 1\n`, 0o755);
|
|
155
|
+
}
|
|
156
|
+
const baseEnv = { ...process.env, PATH: `${TRAP}:${process.env.PATH}`, RUFLO_METAHARNESS_CACHE_BASE: join(TMP, 'empty-cache-base') };
|
|
157
|
+
delete baseEnv.RUFLO_METAHARNESS_SKIP_LOCAL;
|
|
158
|
+
delete baseEnv.CLI_CORE;
|
|
159
|
+
const trapped = () => (existsSync(TRAP_LOG) ? readFileSync(TRAP_LOG, 'utf-8').trim() : '');
|
|
160
|
+
const resetTrap = () => rmSync(TRAP_LOG, { force: true });
|
|
161
|
+
const run = (script, args, env = baseEnv, scriptsDir = PLUGIN_SCRIPTS, cwd = PROJECT) => {
|
|
162
|
+
const r = spawnSync(process.execPath, [join(scriptsDir, script), ...args], { cwd, env, encoding: 'utf-8', timeout: 60_000 });
|
|
163
|
+
let json = null;
|
|
164
|
+
try { json = JSON.parse(r.stdout); } catch { /* callers assert */ }
|
|
165
|
+
return { ...r, json };
|
|
166
|
+
};
|
|
167
|
+
const noNpm = (label) => {
|
|
168
|
+
const t = trapped();
|
|
169
|
+
assert(t === '', `${label}: no npm / npx spawned${t ? ` (got: ${t.split('\n')[0].slice(0, 160)})` : ''}`);
|
|
170
|
+
resetTrap();
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
console.log('# test-no-tool-time-npx\n');
|
|
174
|
+
|
|
175
|
+
// ── Phase 1 — darwin runs the installed bin ──────────────────────────
|
|
176
|
+
console.log(`Phase 1 — @metaharness/darwin ${darwinVersion} (installed) serves bench + security bench`);
|
|
177
|
+
const suite = join(PROJECT, 'suite.json');
|
|
178
|
+
writeFileSync(suite, '{}');
|
|
179
|
+
const bench = run('bench.mjs', ['--op', 'verify', '--suite', suite]);
|
|
180
|
+
assert(bench.status === 0 && bench.json?.success === true, `bench verify exits 0 with success (got ${bench.status}, reason ${bench.json?.reason ?? 'none'})`);
|
|
181
|
+
assert(bench.json?.data?.stub === 'metaharness-darwin' && bench.json?.data?.argv?.[0] === 'bench',
|
|
182
|
+
'bench verify ran the installed metaharness-darwin (sync path)');
|
|
183
|
+
noNpm('bench verify');
|
|
184
|
+
const sec = run('security-bench.mjs', ['--population', '1', '--cycles', '1']);
|
|
185
|
+
assert(sec.json?.degraded !== true, `security bench is not degraded (reason ${sec.json?.reason ?? 'none'})`);
|
|
186
|
+
noNpm('security bench (async path)');
|
|
187
|
+
// …and the OTHER success branch: the one-time versioned cache that gepa
|
|
188
|
+
// already shares, which is what a machine without a qualifying installed
|
|
189
|
+
// copy falls back to. Without this, deleting that branch leaves the suite
|
|
190
|
+
// green.
|
|
191
|
+
const seededCache = join(TMP, 'seeded-cache');
|
|
192
|
+
const cachedDarwin = join(seededCache, `darwin-cache-${pinDigits}`, 'node_modules', '@metaharness', 'darwin');
|
|
193
|
+
write(join(cachedDarwin, 'package.json'), JSON.stringify({ name: '@metaharness/darwin', version: darwinVersion, type: 'module', bin: { 'metaharness-darwin': './dist/cli.js' } }));
|
|
194
|
+
write(join(cachedDarwin, 'dist', 'cli.js'), echoStub('darwin-from-cache', darwinVersion));
|
|
195
|
+
const cachedBench = run('bench.mjs', ['--op', 'verify', '--suite', suite],
|
|
196
|
+
{ ...baseEnv, RUFLO_METAHARNESS_CACHE_BASE: seededCache, RUFLO_METAHARNESS_SKIP_LOCAL: '1' });
|
|
197
|
+
assert(cachedBench.json?.data?.stub === 'darwin-from-cache',
|
|
198
|
+
`no installed copy → the existing darwin-cache-${pinDigits} runs (got stub ${cachedBench.json?.data?.stub ?? 'none'})`);
|
|
199
|
+
noNpm('bench verify via darwin-cache');
|
|
200
|
+
|
|
201
|
+
// ── Phase 2 — redblue runs the installed (symlinked) copy ────────────
|
|
202
|
+
console.log(`\nPhase 2 — @metaharness/redblue ${redblueVersion} (installed, symlinked) serves attack`);
|
|
203
|
+
const rb = run('redblue.mjs', ['attack', 'prompt', '--count', '1']);
|
|
204
|
+
assert(rb.status === 0 && rb.json?.ok === true, `redblue attack exits 0 (got ${rb.status}, reason ${rb.json?.reason ?? 'none'})`);
|
|
205
|
+
assert(/"stub":"redblue"/.test(rb.json?.stdout ?? ''), 'redblue CLI actually dispatched (isMain held: resolved bin is realpath\'d)');
|
|
206
|
+
noNpm('redblue attack');
|
|
207
|
+
|
|
208
|
+
// ── Phase 3 — memory round trip through the CLI that ships the plugin ─
|
|
209
|
+
console.log('\nPhase 3 — oia-audit → audit-list → audit-trend / similarity via the shipping CLI');
|
|
210
|
+
rmSync(CLI_LOG, { force: true });
|
|
211
|
+
const oia = run('oia-audit.mjs', ['--path', PROJECT]);
|
|
212
|
+
assert(oia.json?.persisted?.ok === true, `oia-audit persisted the record (got ${JSON.stringify(oia.json?.persisted ?? null)})`);
|
|
213
|
+
const key = oia.json?.persisted?.key;
|
|
214
|
+
const list = run('audit-list.mjs', ['--format', 'json']);
|
|
215
|
+
assert(list.json?.returned === 1 && list.json?.records?.[0]?.key === key, `audit-list returns the persisted record (got ${list.json?.returned})`);
|
|
216
|
+
const trend = run('audit-trend.mjs', ['--baseline-key', key, '--current-key', key, '--format', 'json']);
|
|
217
|
+
assert(trend.status === 0 && trend.json !== null, `audit-trend reads both records back (exit ${trend.status})`);
|
|
218
|
+
const sim = run('similarity.mjs', ['--a-key', key, '--b-key', key, '--format', 'json']);
|
|
219
|
+
assert(sim.status === 0 && sim.json?.degraded !== true, `similarity reads both records back (exit ${sim.status}, reason ${sim.json?.reason ?? 'none'})`);
|
|
220
|
+
const cliCalls = existsSync(CLI_LOG) ? readFileSync(CLI_LOG, 'utf-8').trim().split('\n') : [];
|
|
221
|
+
assert(cliCalls.includes('memory store') && cliCalls.includes('memory list') && cliCalls.includes('memory retrieve'),
|
|
222
|
+
`the shipping CLI served store + list + retrieve (saw: ${[...new Set(cliCalls)].join(', ') || 'nothing'})`);
|
|
223
|
+
noNpm('memory round trip');
|
|
224
|
+
|
|
225
|
+
// ── Phase 4 — contracts kept ─────────────────────────────────────────
|
|
226
|
+
console.log('\nPhase 4 — opt-in and fallback contracts unchanged');
|
|
227
|
+
run('audit-list.mjs', ['--format', 'json'], { ...baseEnv, CLI_CORE: '1' });
|
|
228
|
+
assert(trapped().startsWith('npx @claude-flow/cli-core@alpha memory list'), 'CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha`');
|
|
229
|
+
resetTrap();
|
|
230
|
+
// No usable local CLI (e.g. a marketplace clone with no dist/ build) → the
|
|
231
|
+
// pre-existing npx fallback, unchanged.
|
|
232
|
+
const bare = join(TMP, 'bare', 'plugins', 'ruflo-metaharness', 'scripts');
|
|
233
|
+
mkdirSync(bare, { recursive: true });
|
|
234
|
+
for (const f of readdirSync(PLUGIN_SCRIPTS)) copyFileSync(join(PLUGIN_SCRIPTS, f), join(bare, f));
|
|
235
|
+
const bareList = run('audit-list.mjs', ['--format', 'json'], baseEnv, bare);
|
|
236
|
+
assert(bareList.status === 0 && trapped().startsWith('npx @claude-flow/cli@latest memory list'),
|
|
237
|
+
'no local CLI → falls back to `npx @claude-flow/cli@latest` (exit 0, as before)');
|
|
238
|
+
resetTrap();
|
|
239
|
+
// A directory two levels up that has bin/cli.js AND dist/src/index.js but
|
|
240
|
+
// is NOT @claude-flow/cli must not be run as the ruflo CLI.
|
|
241
|
+
const impostorScripts = join(TMP, 'impostor', 'plugins', 'ruflo-metaharness', 'scripts');
|
|
242
|
+
mkdirSync(impostorScripts, { recursive: true });
|
|
243
|
+
for (const f of readdirSync(PLUGIN_SCRIPTS)) copyFileSync(join(PLUGIN_SCRIPTS, f), join(impostorScripts, f));
|
|
244
|
+
write(join(TMP, 'impostor', 'package.json'), JSON.stringify({ name: 'some-other-package', version: '1.0.0' }));
|
|
245
|
+
write(join(TMP, 'impostor', 'bin', 'cli.js'), 'console.log("[]");\n');
|
|
246
|
+
write(join(TMP, 'impostor', 'dist', 'src', 'index.js'), 'export {};\n');
|
|
247
|
+
const impostor = run('audit-list.mjs', ['--format', 'json'], baseEnv, impostorScripts);
|
|
248
|
+
assert(impostor.status === 0 && trapped().startsWith('npx @claude-flow/cli@latest memory list'),
|
|
249
|
+
`a bin/cli.js whose package.json is not @claude-flow/cli is rejected (trap: ${trapped().split('\n')[0].slice(0, 60) || 'nothing'})`);
|
|
250
|
+
resetTrap();
|
|
251
|
+
const absent = run('bench.mjs', ['--op', 'verify', '--suite', suite], { ...baseEnv, RUFLO_METAHARNESS_SKIP_LOCAL: '1' });
|
|
252
|
+
assert(absent.status === 0 && absent.json?.degraded === true && absent.json?.reason === 'metaharness-darwin-not-available',
|
|
253
|
+
`darwin absent → {degraded:true, reason:'metaharness-darwin-not-available'}, exit 0 (got ${absent.status}, ${absent.json?.reason})`);
|
|
254
|
+
resetTrap();
|
|
255
|
+
|
|
256
|
+
// ── Phase 5 — the resolution stays inside ruflo's own tree ───────────
|
|
257
|
+
// The darwin / redblue lookups END IN A SPAWN with MCP-supplied argv, so
|
|
258
|
+
// they pass { fromCwd: false } and must ignore a node_modules in the tool's
|
|
259
|
+
// cwd (or any ancestor of it). ISOLATED layout: plugin scripts under their
|
|
260
|
+
// own temp root, so the walk-up from the scripts finds no darwin/redblue at
|
|
261
|
+
// all and only the cwd copy could match.
|
|
262
|
+
console.log('\nPhase 5 — installed-copy lookup is scoped to ruflo, and a half-written cache is repaired');
|
|
263
|
+
const ISO = realpathSync(mkdtempSync(join(tmpdir(), 'ruflo-no-tool-time-npx-iso-')));
|
|
264
|
+
try {
|
|
265
|
+
const isoScripts = join(ISO, 'scripts');
|
|
266
|
+
mkdirSync(isoScripts, { recursive: true });
|
|
267
|
+
for (const f of readdirSync(PLUGIN_SCRIPTS)) copyFileSync(join(PLUGIN_SCRIPTS, f), join(isoScripts, f));
|
|
268
|
+
const isoProject = join(ISO, 'project');
|
|
269
|
+
mkdirSync(isoProject, { recursive: true });
|
|
270
|
+
const cwdDarwin = join(isoProject, 'node_modules', '@metaharness', 'darwin');
|
|
271
|
+
write(join(cwdDarwin, 'package.json'), JSON.stringify({ name: '@metaharness/darwin', version: darwinVersion, type: 'module', bin: { 'metaharness-darwin': './dist/cli.js' } }));
|
|
272
|
+
write(join(cwdDarwin, 'dist', 'cli.js'), echoStub('cwd-darwin', darwinVersion));
|
|
273
|
+
const cwdRedblue = join(isoProject, 'node_modules', '@metaharness', 'redblue');
|
|
274
|
+
write(join(cwdRedblue, 'package.json'), JSON.stringify({ name: '@metaharness/redblue', version: redblueVersion, type: 'module', bin: { redblue: './dist/cli/index.js' } }));
|
|
275
|
+
write(join(cwdRedblue, 'dist', 'cli', 'index.js'), echoStub('cwd-redblue', redblueVersion));
|
|
276
|
+
const isoEnv = { ...baseEnv, RUFLO_METAHARNESS_CACHE_BASE: join(ISO, 'empty-cache-base') };
|
|
277
|
+
|
|
278
|
+
const cwdBench = run('bench.mjs', ['--op', 'verify', '--suite', suite], isoEnv, isoScripts, isoProject);
|
|
279
|
+
assert(cwdBench.json?.data?.stub !== 'cwd-darwin',
|
|
280
|
+
`darwin in the tool's cwd node_modules is NOT executed (got stub ${cwdBench.json?.data?.stub ?? 'none'})`);
|
|
281
|
+
resetTrap();
|
|
282
|
+
const cwdRb = run('redblue.mjs', ['attack', 'prompt', '--count', '1'], isoEnv, isoScripts, isoProject);
|
|
283
|
+
assert(!/"stub":"cwd-redblue"/.test(cwdRb.json?.stdout ?? ''),
|
|
284
|
+
'redblue in the tool\'s cwd node_modules is NOT executed');
|
|
285
|
+
resetTrap();
|
|
286
|
+
|
|
287
|
+
// A cache dir holding package.json but no bin (npm killed by the 180s
|
|
288
|
+
// install timeout, or two first calls racing into the same prefix) must
|
|
289
|
+
// not count as installed forever: one reinstall is attempted.
|
|
290
|
+
const halfCache = join(ISO, 'half-cache');
|
|
291
|
+
write(join(halfCache, `darwin-cache-${pinDigits}`, 'node_modules', '@metaharness', 'darwin', 'package.json'),
|
|
292
|
+
JSON.stringify({ name: '@metaharness/darwin', version: darwinVersion, type: 'module', bin: { 'metaharness-darwin': './dist/cli.js' } }));
|
|
293
|
+
const halfRun = run('bench.mjs', ['--op', 'verify', '--suite', suite], { ...isoEnv, RUFLO_METAHARNESS_CACHE_BASE: halfCache }, isoScripts, isoProject);
|
|
294
|
+
assert(/npm install/.test(trapped()), `a cache with package.json but no bin triggers one reinstall (trap: ${trapped().split('\n')[0].slice(0, 120) || 'nothing'})`);
|
|
295
|
+
assert(halfRun.status === 0 && halfRun.json?.degraded === true,
|
|
296
|
+
`…and still degrades with exit 0 when that reinstall fails (got ${halfRun.status}, degraded ${halfRun.json?.degraded})`);
|
|
297
|
+
resetTrap();
|
|
298
|
+
} finally {
|
|
299
|
+
rmSync(ISO, { recursive: true, force: true });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Static: the resolved bins are spawned directly, never through a shell —
|
|
303
|
+
// their argv carries MCP-supplied values (redblue --config/--out/--in).
|
|
304
|
+
for (const f of ['_darwin.mjs', '_redblue.mjs']) {
|
|
305
|
+
const src = readFileSync(join(SCRIPTS_DIR, f), 'utf-8');
|
|
306
|
+
const code = src.split('\n').filter((l) => !l.trimStart().startsWith('//')).join('\n');
|
|
307
|
+
assert(!/shell:\s*(true|process\.platform)/.test(code), `${f} spawns the resolved bin with shell:false`);
|
|
308
|
+
}
|
|
309
|
+
} finally {
|
|
310
|
+
rmSync(TMP, { recursive: true, force: true });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
314
|
+
if (failed > 0) {
|
|
315
|
+
console.log('\nFailures:');
|
|
316
|
+
for (const f of failures) console.log(` - ${f}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
console.log('\n✓ No npm / npx at tool-call time when darwin, redblue and the ruflo CLI are installed.');
|