@claude-flow/cli 3.54.1 → 3.56.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/.claude/settings.json +9 -9
- package/bin/cli.js +1 -1
- package/bin/mcp-server.js +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/auth/client.js +40 -24
- package/dist/src/commands/agent.js +12 -0
- package/dist/src/commands/analyze.js +18 -14
- package/dist/src/commands/autopilot.js +2 -2
- package/dist/src/commands/daemon.js +15 -3
- package/dist/src/commands/doctor.js +65 -58
- package/dist/src/commands/hive-mind.js +7 -0
- package/dist/src/commands/mcp.js +35 -1
- package/dist/src/commands/memory-backup.d.ts +1 -1
- package/dist/src/commands/memory-backup.js +23 -9
- package/dist/src/commands/memory.d.ts +0 -4
- package/dist/src/commands/memory.js +10 -2
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/commands/policy.js +10 -2
- package/dist/src/commands/task.js +12 -0
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-server.d.ts +33 -0
- package/dist/src/mcp-server.js +96 -4
- package/dist/src/mcp-tools/agent-execute-core.js +244 -188
- package/dist/src/mcp-tools/agent-tools.js +15 -16
- package/dist/src/mcp-tools/agentdb-tools.js +22 -3
- package/dist/src/mcp-tools/claims-tools.js +3 -11
- package/dist/src/mcp-tools/hive-mind-tools.d.ts +2 -4
- package/dist/src/mcp-tools/hive-mind-tools.js +214 -40
- package/dist/src/mcp-tools/hooks-tools.js +47 -2
- package/dist/src/mcp-tools/memory-tools.js +29 -10
- package/dist/src/mcp-tools/record-store.d.ts +3 -0
- package/dist/src/mcp-tools/record-store.js +21 -0
- package/dist/src/mcp-tools/session-tools.js +47 -13
- package/dist/src/mcp-tools/task-tools.js +5 -11
- package/dist/src/mcp-tools/workflow-tools.js +78 -29
- package/dist/src/memory/append-conditions.d.ts +28 -0
- package/dist/src/memory/append-conditions.js +56 -0
- package/dist/src/memory/graph-edge-writer.d.ts +9 -0
- package/dist/src/memory/graph-edge-writer.js +90 -0
- package/dist/src/memory/memory-bridge.d.ts +4 -0
- package/dist/src/memory/memory-bridge.js +50 -9
- package/dist/src/memory/memory-initializer.d.ts +4 -0
- package/dist/src/memory/memory-initializer.js +95 -61
- package/dist/src/memory/memory-root.d.ts +2 -0
- package/dist/src/memory/memory-root.js +28 -0
- package/dist/src/memory/sona-optimizer.d.ts +2 -0
- package/dist/src/memory/sona-optimizer.js +4 -0
- package/dist/src/missions/schemas.d.ts +125 -125
- package/dist/src/mods/command-registry/schema.d.ts +199 -199
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.js +71 -13
- package/dist/src/plugins/store/discovery.d.ts +6 -0
- package/dist/src/plugins/store/discovery.js +7 -3
- package/dist/src/production/retry.js +1 -1
- package/dist/src/ruvector/neural-router.js +7 -2
- package/dist/src/services/bounded-worker-pool.js +3 -0
- package/dist/src/services/claim-service.js +39 -18
- package/dist/src/services/claims-store.d.ts +3 -0
- package/dist/src/services/claims-store.js +40 -0
- package/dist/src/services/config-file-manager.d.ts +3 -0
- package/dist/src/services/config-file-manager.js +24 -7
- package/dist/src/services/distill-tuning.js +17 -10
- package/dist/src/services/memory-backup.d.ts +7 -0
- package/dist/src/services/memory-backup.js +29 -2
- package/dist/src/services/memory-distillation.js +2 -2
- package/dist/src/services/policy-ledger-anchor.d.ts +42 -0
- package/dist/src/services/policy-ledger-anchor.js +163 -0
- package/dist/src/services/policy-runtime.d.ts +11 -5
- package/dist/src/services/policy-runtime.js +70 -15
- package/dist/src/services/worker-daemon.d.ts +5 -2
- package/dist/src/services/worker-daemon.js +26 -6
- package/dist/src/services/worker-queue.js +11 -2
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js +5 -2
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +7 -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 +60 -47
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts +3 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js +4 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts +2 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js +48 -16
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts +2 -2
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.js +15 -4
- package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +11 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +19 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +4 -2
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +7 -7
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +80 -7
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
- package/plugins/ruflo-metaharness/scripts/test-cli-resolution-3558.mjs +141 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +5 -0
- package/.claude/agents/tmp.json +0 -0
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
// library entries (gepa) — never throws on absence
|
|
29
29
|
// - makeDegradedEmitter() the ADR-150 rule-#3 exit-0 degraded payload
|
|
30
30
|
// - runRufloCli() `memory store|list|retrieve` through the ruflo CLI
|
|
31
|
-
// that ships this plugin (#3366)
|
|
31
|
+
// that ships this plugin (#3366), else an installed
|
|
32
|
+
// `ruflo` / `claude-flow` on PATH (#3558) — replaces
|
|
32
33
|
// `npx @claude-flow/cli@latest` in 4 scripts
|
|
33
34
|
//
|
|
34
35
|
// WHAT DOES NOT LIVE HERE (the per-consumer parts):
|
|
@@ -40,11 +41,16 @@
|
|
|
40
41
|
// stays meaningful on machines with a warm cache / local install):
|
|
41
42
|
// - RUFLO_METAHARNESS_CACHE_BASE overrides ~/.ruflo as the cache root
|
|
42
43
|
// - RUFLO_METAHARNESS_SKIP_LOCAL=1 disables local node_modules resolution
|
|
44
|
+
// - RUFLO_PLUGIN_SKIP_LOCAL_CLI=1 resolveRufloCli() skips the shipping CLI /
|
|
45
|
+
// repo checkout (#3558; same name in
|
|
46
|
+
// ruflo-cost-tracker and ruflo-adr)
|
|
47
|
+
// - RUFLO_PLUGIN_SKIP_PATH_CLI=1 resolveRufloCli() skips `ruflo` /
|
|
48
|
+
// `claude-flow` on PATH (#3558)
|
|
43
49
|
|
|
44
50
|
import { spawnSync } from 'node:child_process';
|
|
45
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync } from 'node:fs';
|
|
51
|
+
import { accessSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
46
52
|
import { homedir } from 'node:os';
|
|
47
|
-
import { dirname, join } from 'node:path';
|
|
53
|
+
import { basename, delimiter, dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
48
54
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
49
55
|
|
|
50
56
|
const INSTALL_TIMEOUT_MS = 180_000; // npm install can be slow on cold cache
|
|
@@ -303,12 +309,77 @@ export function makeDegradedEmitter(pkg, pinVersion) {
|
|
|
303
309
|
// exists next to bin/cli.js — mcp-launch.cjs resolveLocalCliBin()'s guard
|
|
304
310
|
// against an unbuilt checkout — AND its package.json is named
|
|
305
311
|
// @claude-flow/cli, a check added here so an unrelated directory two levels up
|
|
306
|
-
// can never qualify.
|
|
312
|
+
// can never qualify.
|
|
313
|
+
// #3558: a Claude Code marketplace install copies the plugin to
|
|
314
|
+
// ~/.claude/plugins/cache/<marketplace>/ruflo-metaharness/<version>/, where
|
|
315
|
+
// neither candidate exists, so every memory call still went to npx @latest on
|
|
316
|
+
// a machine with ruflo installed. A `ruflo`, then `claude-flow`, found on
|
|
317
|
+
// PATH (ruflo-core ruflo-hook.cjs's order) is now tried before npx.
|
|
318
|
+
// Unchanged:
|
|
307
319
|
// - CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha` (ADR-100);
|
|
308
|
-
// - no usable local CLI
|
|
309
|
-
//
|
|
320
|
+
// - no usable local CLI and no ruflo on PATH still falls back to
|
|
321
|
+
// `npx @claude-flow/cli@latest`.
|
|
310
322
|
const PLUGIN_SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
311
323
|
|
|
324
|
+
/** Case-insensitive env lookup — Windows env keys are not case-stable (`Path`). */
|
|
325
|
+
function envValue(env, name) {
|
|
326
|
+
const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
327
|
+
return key ? env[key] : undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* npm's Windows shim (`<prefix>/ruflo.cmd`, or `node_modules/.bin/ruflo.cmd`)
|
|
332
|
+
* cannot be spawned without a shell (CVE-2024-27980), so map it to the entry
|
|
333
|
+
* it wraps: the package's own `bin` field, which must resolve inside the
|
|
334
|
+
* package — ruflo-core's resolveNpmShim(). null when it is not such a shim.
|
|
335
|
+
*/
|
|
336
|
+
function npmShimEntry(shim, name) {
|
|
337
|
+
try {
|
|
338
|
+
const shimDir = dirname(shim);
|
|
339
|
+
const pkgDir = basename(shimDir).toLowerCase() === '.bin'
|
|
340
|
+
? join(shimDir, '..', name)
|
|
341
|
+
: join(shimDir, 'node_modules', name);
|
|
342
|
+
const pj = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf-8'));
|
|
343
|
+
const declared = typeof pj.bin === 'string' ? pj.bin : pj.bin?.[name];
|
|
344
|
+
if (typeof declared !== 'string') return null;
|
|
345
|
+
const entry = realpathSync(join(pkgDir, declared));
|
|
346
|
+
const rel = relative(realpathSync(pkgDir), entry);
|
|
347
|
+
if (rel === '..' || rel.startsWith('..' + sep) || isAbsolute(rel) || !statSync(entry).isFile()) return null;
|
|
348
|
+
return entry;
|
|
349
|
+
} catch { return null; }
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* `ruflo`, then `claude-flow`, on PATH → `{ command, args, shell, source }`,
|
|
354
|
+
* else null. fs-only like ruflo-core's resolveCommandPath(): no `which` /
|
|
355
|
+
* `where` shell per call. Relative PATH entries (including the empty one) are
|
|
356
|
+
* skipped so a `ruflo` file inside the project being worked on is never run.
|
|
357
|
+
* On win32 only an npm shim that maps to its JS entry counts; anything else
|
|
358
|
+
* is skipped rather than run through cmd.exe. `env` / `platform` are
|
|
359
|
+
* parameters so the win32 branch is testable on POSIX.
|
|
360
|
+
*/
|
|
361
|
+
export function findRufloOnPath(env = process.env, platform = process.platform) {
|
|
362
|
+
const dirs = (envValue(env, 'PATH') || '')
|
|
363
|
+
.split(platform === 'win32' ? ';' : delimiter)
|
|
364
|
+
.filter((d) => isAbsolute(d));
|
|
365
|
+
for (const name of ['ruflo', 'claude-flow']) {
|
|
366
|
+
for (const dir of dirs) {
|
|
367
|
+
if (platform === 'win32') {
|
|
368
|
+
const shim = join(dir, `${name}.cmd`);
|
|
369
|
+
const entry = existsSync(shim) ? npmShimEntry(shim, name) : null;
|
|
370
|
+
if (entry) return { command: process.execPath, args: [entry], shell: false, source: 'path' };
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const file = join(dir, name);
|
|
374
|
+
try {
|
|
375
|
+
accessSync(file, constants.X_OK);
|
|
376
|
+
if (statSync(file).isFile()) return { command: file, args: [], shell: false, source: 'path' };
|
|
377
|
+
} catch { /* keep searching */ }
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
312
383
|
let RUFLO_CLI = null;
|
|
313
384
|
/** `{ command, args, shell, source }` for invoking the ruflo CLI. Memoized. */
|
|
314
385
|
export function resolveRufloCli() {
|
|
@@ -316,7 +387,7 @@ export function resolveRufloCli() {
|
|
|
316
387
|
if (process.env.CLI_CORE === '1') {
|
|
317
388
|
return (RUFLO_CLI = { command: 'npx', args: ['@claude-flow/cli-core@alpha'], shell: process.platform === 'win32', source: 'npx-cli-core' });
|
|
318
389
|
}
|
|
319
|
-
const candidates = [
|
|
390
|
+
const candidates = process.env.RUFLO_PLUGIN_SKIP_LOCAL_CLI === '1' ? [] : [
|
|
320
391
|
join(PLUGIN_SCRIPTS_DIR, '..', '..', '..'), // published: <cli>/plugins/ruflo-metaharness/scripts
|
|
321
392
|
join(PLUGIN_SCRIPTS_DIR, '..', '..', '..', 'v3', '@claude-flow', 'cli'), // repo checkout / marketplace clone
|
|
322
393
|
];
|
|
@@ -329,6 +400,8 @@ export function resolveRufloCli() {
|
|
|
329
400
|
}
|
|
330
401
|
} catch { /* not this layout — try the next */ }
|
|
331
402
|
}
|
|
403
|
+
const onPath = process.env.RUFLO_PLUGIN_SKIP_PATH_CLI === '1' ? null : findRufloOnPath();
|
|
404
|
+
if (onPath) return (RUFLO_CLI = onPath);
|
|
332
405
|
return (RUFLO_CLI = { command: 'npx', args: ['@claude-flow/cli@latest'], shell: process.platform === 'win32', source: 'npx' });
|
|
333
406
|
}
|
|
334
407
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Structural smoke test for ruflo-metaharness v0.2.
|
|
2
|
+
# Structural smoke test for ruflo-metaharness v0.2.4 (ADR-150 Phase 1).
|
|
3
3
|
set -u
|
|
4
4
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
5
|
PASS=0
|
|
@@ -32,10 +32,10 @@ else
|
|
|
32
32
|
bad "extraction-regex-rot: EXPECTED_TOOLS=$EXPECTED_TOOLS EXPECTED_SUBS=$EXPECTED_SUBS"
|
|
33
33
|
fi
|
|
34
34
|
|
|
35
|
-
step "1. plugin.json declares 0.2.
|
|
35
|
+
step "1. plugin.json declares 0.2.4 with adr-150 keywords"
|
|
36
36
|
v=$(grep -E '"version"' "$ROOT/.claude-plugin/plugin.json" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
|
37
|
-
if [[ "$v" != "0.2.
|
|
38
|
-
bad "expected 0.2.
|
|
37
|
+
if [[ "$v" != "0.2.4" ]]; then
|
|
38
|
+
bad "expected 0.2.4, got '$v'"
|
|
39
39
|
else
|
|
40
40
|
miss=""
|
|
41
41
|
for k in ruflo metaharness harness scorecard genome mcp-scan threat-model router adr-150 adr-148 adr-149 optional-dependency graceful-degradation subprocess phase-1-mvp; do
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// test-cli-resolution-3558.mjs — regression test for #3558: in a Claude Code
|
|
3
|
+
// marketplace install, metaharness memory calls must use the ruflo CLI that
|
|
4
|
+
// is already installed on PATH, not `npx @claude-flow/cli@latest`.
|
|
5
|
+
//
|
|
6
|
+
// #3366 taught resolveRufloCli() (_invoke.mjs) two local layouts: the
|
|
7
|
+
// @claude-flow/cli that ships the plugin, and a repo checkout. A marketplace
|
|
8
|
+
// install is neither — the plugin is copied to
|
|
9
|
+
// ~/.claude/plugins/cache/<marketplace>/ruflo-metaharness/<version>/ — so it
|
|
10
|
+
// still fell through to npx @latest on a machine with ruflo installed.
|
|
11
|
+
//
|
|
12
|
+
// Hermetic: the plugin is copied into a temp plugin-cache layout; PATH holds
|
|
13
|
+
// only temp dirs with fake `ruflo` / `claude-flow` / `npx` scripts that log
|
|
14
|
+
// their argv. No network, no real CLI. The end-to-end cases use POSIX sh
|
|
15
|
+
// fakes and are skipped on win32; the win32 shim-mapping case runs everywhere.
|
|
16
|
+
//
|
|
17
|
+
// USAGE
|
|
18
|
+
// node --test plugins/ruflo-metaharness/scripts/test-cli-resolution-3558.mjs
|
|
19
|
+
import { test, before, after } from 'node:test';
|
|
20
|
+
import assert from 'node:assert/strict';
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
24
|
+
import { delimiter, dirname, join } from 'node:path';
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
|
+
|
|
27
|
+
const PLUGIN_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
28
|
+
const posixOnly = { skip: process.platform === 'win32' ? 'fake CLIs are POSIX sh scripts' : false };
|
|
29
|
+
|
|
30
|
+
const write = (file, body, mode) => {
|
|
31
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
32
|
+
writeFileSync(file, body);
|
|
33
|
+
if (mode) chmodSync(file, mode);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let TMP, CACHE, LOG, WITH_RUFLO, WITH_CLAUDE_FLOW, NPX_ONLY, PROJECT, baseEnv;
|
|
37
|
+
before(() => {
|
|
38
|
+
// realpath: macOS tmpdir() is /var/… while resolved paths come back as /private/var/…
|
|
39
|
+
TMP = realpathSync(mkdtempSync(join(tmpdir(), 'metaharness-cli-resolution-')));
|
|
40
|
+
const home = join(TMP, 'home');
|
|
41
|
+
CACHE = join(home, '.claude', 'plugins', 'cache', 'ruflo', 'ruflo-metaharness', '0.0.0-test');
|
|
42
|
+
cpSync(PLUGIN_ROOT, CACHE, { recursive: true });
|
|
43
|
+
LOG = join(TMP, 'calls.log');
|
|
44
|
+
const fake = (dir, name) => write(join(dir, name),
|
|
45
|
+
`#!/bin/sh\nprintf '%s %s\\n' "${name}" "$*" >> "${LOG}"\ncase "$*" in *"memory list"*) echo '[]' ;; *) echo '[OK] done' ;; esac\n`, 0o755);
|
|
46
|
+
WITH_RUFLO = join(TMP, 'bin-ruflo');
|
|
47
|
+
fake(WITH_RUFLO, 'ruflo');
|
|
48
|
+
fake(WITH_RUFLO, 'claude-flow');
|
|
49
|
+
WITH_CLAUDE_FLOW = join(TMP, 'bin-claude-flow');
|
|
50
|
+
fake(WITH_CLAUDE_FLOW, 'claude-flow');
|
|
51
|
+
NPX_ONLY = join(TMP, 'bin-npx');
|
|
52
|
+
fake(NPX_ONLY, 'npx');
|
|
53
|
+
PROJECT = join(TMP, 'project');
|
|
54
|
+
mkdirSync(PROJECT);
|
|
55
|
+
baseEnv = { ...process.env, HOME: home, USERPROFILE: home, RUFLO_METAHARNESS_CACHE_BASE: join(TMP, 'empty-cache-base') };
|
|
56
|
+
for (const k of ['CLI_CORE', 'RUFLO_PLUGIN_SKIP_LOCAL_CLI', 'RUFLO_PLUGIN_SKIP_PATH_CLI', 'NODE_OPTIONS']) delete baseEnv[k];
|
|
57
|
+
});
|
|
58
|
+
after(() => rmSync(TMP, { recursive: true, force: true }));
|
|
59
|
+
|
|
60
|
+
const calls = () => (existsSync(LOG) ? readFileSync(LOG, 'utf-8').trim().split('\n').filter(Boolean) : []);
|
|
61
|
+
/** resolveRufloCli() as seen from `scriptsDir`, in a fresh process. */
|
|
62
|
+
function resolved(env, scriptsDir = join(CACHE, 'scripts')) {
|
|
63
|
+
const src = `import { resolveRufloCli } from ${JSON.stringify(pathToFileURL(join(scriptsDir, '_invoke.mjs')).href)};\nconsole.log(JSON.stringify(resolveRufloCli()));`;
|
|
64
|
+
const r = spawnSync(process.execPath, ['--input-type=module', '-e', src], { cwd: PROJECT, encoding: 'utf-8', env: { ...baseEnv, ...env } });
|
|
65
|
+
assert.equal(r.status, 0, r.stderr);
|
|
66
|
+
return JSON.parse(r.stdout);
|
|
67
|
+
}
|
|
68
|
+
function auditList(env, scriptsDir = join(CACHE, 'scripts')) {
|
|
69
|
+
rmSync(LOG, { force: true });
|
|
70
|
+
const r = spawnSync(process.execPath, [join(scriptsDir, 'audit-list.mjs'), '--format', 'json'], { cwd: PROJECT, encoding: 'utf-8', timeout: 30_000, env: { ...baseEnv, ...env } });
|
|
71
|
+
assert.equal(r.status, 0, r.stderr);
|
|
72
|
+
return calls();
|
|
73
|
+
}
|
|
74
|
+
const pathOf = (...dirs) => dirs.join(delimiter);
|
|
75
|
+
|
|
76
|
+
test('win32: npm\'s ruflo.cmd shim maps to `node <package bin>`, never cmd.exe', async () => {
|
|
77
|
+
const { findRufloOnPath } = await import(pathToFileURL(join(PLUGIN_ROOT, 'scripts', '_invoke.mjs')).href);
|
|
78
|
+
const prefix = join(TMP, 'win-prefix');
|
|
79
|
+
write(join(prefix, 'ruflo.cmd'), '@ECHO off\r\n');
|
|
80
|
+
write(join(prefix, 'node_modules', 'ruflo', 'package.json'), JSON.stringify({ name: 'ruflo', bin: { ruflo: 'bin/ruflo.js' } }));
|
|
81
|
+
write(join(prefix, 'node_modules', 'ruflo', 'bin', 'ruflo.js'), '');
|
|
82
|
+
assert.deepEqual(findRufloOnPath({ Path: prefix }, 'win32'),
|
|
83
|
+
{ command: process.execPath, args: [realpathSync(join(prefix, 'node_modules', 'ruflo', 'bin', 'ruflo.js'))], shell: false, source: 'path' });
|
|
84
|
+
const bare = join(TMP, 'win-bare');
|
|
85
|
+
write(join(bare, 'ruflo.cmd'), '@ECHO off\r\n');
|
|
86
|
+
assert.equal(findRufloOnPath({ PATH: bare }, 'win32'), null, 'a ruflo.cmd that is not an npm package shim is skipped, not run through cmd.exe');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('plugin-cache install: the installed `ruflo` on PATH serves memory calls, not npx', posixOnly, () => {
|
|
90
|
+
const env = { PATH: pathOf(WITH_RUFLO, NPX_ONLY) };
|
|
91
|
+
assert.deepEqual(resolved(env), { command: join(WITH_RUFLO, 'ruflo'), args: [], shell: false, source: 'path' });
|
|
92
|
+
const c = auditList(env);
|
|
93
|
+
assert.ok(c.some((l) => l.startsWith('ruflo memory list --namespace metaharness-audit')), c.join(' | ') || 'no CLI called');
|
|
94
|
+
assert.deepEqual(c.filter((l) => l.startsWith('npx ')), []);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('no `ruflo` but `claude-flow` on PATH → claude-flow', posixOnly, () => {
|
|
98
|
+
const c = auditList({ PATH: pathOf(WITH_CLAUDE_FLOW, NPX_ONLY) });
|
|
99
|
+
assert.ok(c.some((l) => l.startsWith('claude-flow memory list')), c.join(' | '));
|
|
100
|
+
assert.deepEqual(c.filter((l) => l.startsWith('npx ')), []);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('no installed CLI → still `npx @claude-flow/cli@latest` (unchanged fallback)', posixOnly, () => {
|
|
104
|
+
const env = { PATH: pathOf(NPX_ONLY) };
|
|
105
|
+
assert.equal(resolved(env).source, 'npx');
|
|
106
|
+
const c = auditList(env);
|
|
107
|
+
assert.ok(c.some((l) => l.startsWith('npx @claude-flow/cli@latest memory list')), c.join(' | '));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('CLI_CORE=1 still opts into `npx @claude-flow/cli-core@alpha` (ADR-100)', posixOnly, () => {
|
|
111
|
+
const c = auditList({ PATH: pathOf(WITH_RUFLO, NPX_ONLY), CLI_CORE: '1' });
|
|
112
|
+
assert.ok(c.some((l) => l.startsWith('npx @claude-flow/cli-core@alpha memory list')), c.join(' | '));
|
|
113
|
+
assert.deepEqual(c.filter((l) => l.startsWith('ruflo ')), []);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('RUFLO_PLUGIN_SKIP_PATH_CLI=1 skips the PATH lookup (test seam)', posixOnly, () => {
|
|
117
|
+
assert.equal(resolved({ PATH: pathOf(WITH_RUFLO, NPX_ONLY), RUFLO_PLUGIN_SKIP_PATH_CLI: '1' }).source, 'npx');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('a `ruflo` in the working directory is never run via a relative PATH entry', posixOnly, () => {
|
|
121
|
+
write(join(PROJECT, 'ruflo'), '#!/bin/sh\nexit 0\n', 0o755);
|
|
122
|
+
try {
|
|
123
|
+
assert.equal(resolved({ PATH: ['', '.', NPX_ONLY].join(delimiter) }).source, 'npx');
|
|
124
|
+
} finally {
|
|
125
|
+
rmSync(join(PROJECT, 'ruflo'));
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('published layout: the @claude-flow/cli that ships the plugin still wins over PATH (#3366)', posixOnly, () => {
|
|
130
|
+
const cli = join(TMP, 'published', 'node_modules', '@claude-flow', 'cli');
|
|
131
|
+
const scripts = join(cli, 'plugins', 'ruflo-metaharness', 'scripts');
|
|
132
|
+
cpSync(join(PLUGIN_ROOT, 'scripts'), scripts, { recursive: true });
|
|
133
|
+
write(join(cli, 'package.json'), JSON.stringify({ name: '@claude-flow/cli', version: '0.0.0-test', type: 'module' }));
|
|
134
|
+
write(join(cli, 'dist', 'src', 'index.js'), 'export {};\n');
|
|
135
|
+
write(join(cli, 'bin', 'cli.js'), `import { appendFileSync } from 'node:fs';\nappendFileSync(${JSON.stringify(LOG)}, 'shipping-cli ' + process.argv.slice(2).join(' ') + '\\n');\nconsole.log('[]');\n`);
|
|
136
|
+
const env = { PATH: pathOf(WITH_RUFLO, NPX_ONLY) };
|
|
137
|
+
assert.equal(resolved(env, scripts).source, 'local');
|
|
138
|
+
const c = auditList(env, scripts);
|
|
139
|
+
assert.ok(c.some((l) => l.startsWith('shipping-cli memory list')), c.join(' | '));
|
|
140
|
+
assert.deepEqual(c.filter((l) => !l.startsWith('shipping-cli ')), []);
|
|
141
|
+
});
|
|
@@ -156,6 +156,11 @@ else process.exit(2);
|
|
|
156
156
|
const baseEnv = { ...process.env, PATH: `${TRAP}:${process.env.PATH}`, RUFLO_METAHARNESS_CACHE_BASE: join(TMP, 'empty-cache-base') };
|
|
157
157
|
delete baseEnv.RUFLO_METAHARNESS_SKIP_LOCAL;
|
|
158
158
|
delete baseEnv.CLI_CORE;
|
|
159
|
+
delete baseEnv.RUFLO_PLUGIN_SKIP_LOCAL_CLI;
|
|
160
|
+
// #3558: Phase 4 asserts the npx fallback; an installed `ruflo` on the
|
|
161
|
+
// developer's PATH would now be preferred over it. PATH resolution has its
|
|
162
|
+
// own test (test-cli-resolution-3558.mjs).
|
|
163
|
+
baseEnv.RUFLO_PLUGIN_SKIP_PATH_CLI = '1';
|
|
159
164
|
const trapped = () => (existsSync(TRAP_LOG) ? readFileSync(TRAP_LOG, 'utf-8').trim() : '');
|
|
160
165
|
const resetTrap = () => rmSync(TRAP_LOG, { force: true });
|
|
161
166
|
const run = (script, args, env = baseEnv, scriptsDir = PLUGIN_SCRIPTS, cwd = PROJECT) => {
|
package/.claude/agents/tmp.json
DELETED
|
File without changes
|