@claude-flow/cli 3.14.4 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/src/business-pods/bbs-budget-tracker.d.ts +139 -0
  2. package/dist/src/business-pods/bbs-budget-tracker.d.ts.map +1 -0
  3. package/dist/src/business-pods/bbs-budget-tracker.js +358 -0
  4. package/dist/src/business-pods/bbs-budget-tracker.js.map +1 -0
  5. package/dist/src/business-pods/domain-affinity-policy.d.ts +47 -0
  6. package/dist/src/business-pods/domain-affinity-policy.d.ts.map +1 -0
  7. package/dist/src/business-pods/domain-affinity-policy.js +65 -0
  8. package/dist/src/business-pods/domain-affinity-policy.js.map +1 -0
  9. package/dist/src/business-pods/pod-schema.d.ts +96 -0
  10. package/dist/src/business-pods/pod-schema.d.ts.map +1 -0
  11. package/dist/src/business-pods/pod-schema.js +225 -0
  12. package/dist/src/business-pods/pod-schema.js.map +1 -0
  13. package/dist/src/commands/metaharness.d.ts.map +1 -1
  14. package/dist/src/commands/metaharness.js +20 -5
  15. package/dist/src/commands/metaharness.js.map +1 -1
  16. package/dist/src/mcp-client.d.ts.map +1 -1
  17. package/dist/src/mcp-client.js +21 -0
  18. package/dist/src/mcp-client.js.map +1 -1
  19. package/dist/src/mcp-tools/agentbbs-tools.d.ts +28 -0
  20. package/dist/src/mcp-tools/agentbbs-tools.d.ts.map +1 -0
  21. package/dist/src/mcp-tools/agentbbs-tools.js +394 -0
  22. package/dist/src/mcp-tools/agentbbs-tools.js.map +1 -0
  23. package/dist/src/mcp-tools/agenticow-tools.d.ts +36 -0
  24. package/dist/src/mcp-tools/agenticow-tools.d.ts.map +1 -0
  25. package/dist/src/mcp-tools/agenticow-tools.js +253 -0
  26. package/dist/src/mcp-tools/agenticow-tools.js.map +1 -0
  27. package/dist/src/mcp-tools/business-pod-tools.d.ts +20 -0
  28. package/dist/src/mcp-tools/business-pod-tools.d.ts.map +1 -0
  29. package/dist/src/mcp-tools/business-pod-tools.js +169 -0
  30. package/dist/src/mcp-tools/business-pod-tools.js.map +1 -0
  31. package/dist/src/mcp-tools/http-fetch-tools.d.ts +55 -0
  32. package/dist/src/mcp-tools/http-fetch-tools.d.ts.map +1 -0
  33. package/dist/src/mcp-tools/http-fetch-tools.js +329 -0
  34. package/dist/src/mcp-tools/http-fetch-tools.js.map +1 -0
  35. package/dist/src/mcp-tools/index.d.ts +4 -0
  36. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  37. package/dist/src/mcp-tools/index.js +8 -0
  38. package/dist/src/mcp-tools/index.js.map +1 -1
  39. package/dist/src/mcp-tools/metaharness-tools.d.ts +6 -0
  40. package/dist/src/mcp-tools/metaharness-tools.d.ts.map +1 -1
  41. package/dist/src/mcp-tools/metaharness-tools.js +83 -4
  42. package/dist/src/mcp-tools/metaharness-tools.js.map +1 -1
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +4 -1
  45. package/plugins/ruflo-metaharness/scripts/_redblue.mjs +176 -0
  46. package/plugins/ruflo-metaharness/scripts/redblue.mjs +286 -0
  47. package/plugins/ruflo-metaharness/scripts/smoke.sh +27 -22
  48. package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +15 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.14.4",
3
+ "version": "3.16.0",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",
@@ -106,6 +106,8 @@
106
106
  "yaml": "^2.8.0"
107
107
  },
108
108
  "optionalDependencies": {
109
+ "agentbbs": "~0.1.0",
110
+ "agenticow": "~0.2.3",
109
111
  "@claude-flow/aidefence": "^3.0.2",
110
112
  "@claude-flow/codex": "^3.0.0-alpha.8",
111
113
  "@claude-flow/embeddings": "^3.0.0-alpha.18",
@@ -115,6 +117,7 @@
115
117
  "@claude-flow/security": "^3.0.0-alpha.10",
116
118
  "@metaharness/darwin": "~0.3.1",
117
119
  "@metaharness/kernel": "~0.1.0",
120
+ "@metaharness/redblue": "~0.1.1",
118
121
  "@metaharness/router": "~0.3.2",
119
122
  "metaharness": "~0.2.6",
120
123
  "@ruvector/attention": "^0.1.32",
@@ -0,0 +1,176 @@
1
+ // _redblue.mjs — invocation helper for `@metaharness/redblue`.
2
+ //
3
+ // Sibling of `_darwin.mjs` / `_harness.mjs`. Targets the `redblue` binary
4
+ // from the standalone `@metaharness/redblue@~0.1.1` package.
5
+ //
6
+ // Subcommands surfaced (matches redblue 0.1.x):
7
+ // - `redblue init [--out redblue.yaml]`
8
+ // - `redblue run [--config redblue.yaml] [--tests N] [--patch] [--mock-judge] [--out report.json]`
9
+ // - `redblue attack <prompt|tools|data|all> [--count N]`
10
+ // - `redblue patch [--config redblue.yaml] [--mock-judge]`
11
+ // - `redblue report --in report.json`
12
+ //
13
+ // UPSTREAM BUG WORKAROUND
14
+ // -----------------------
15
+ // @metaharness/redblue@0.1.1's CLI bootstrap is:
16
+ // const isMain = import.meta.url === `file://${process.argv[1]}`;
17
+ // if (isMain) { dispatch(...) }
18
+ // When npx links `redblue` → `dist/cli/index.js`, process.argv[1] is the
19
+ // symlink path but import.meta.url is the resolved real path, so the
20
+ // check fails silently — the binary exits 0 with no output, no file,
21
+ // no error. Darwin's CLI doesn't suffer this (calls main() unconditionally).
22
+ //
23
+ // Workaround: install the package once into a ruflo-owned cache dir
24
+ // (~/.ruflo/redblue-cache) and invoke `node <abs_path>/dist/cli/index.js`
25
+ // directly. argv[1] then equals the real path and isMain becomes true.
26
+ //
27
+ // Track upstream fix at: github.com/ruvnet/agent-harness-generator/issues
28
+ // (file separately — when fixed, we can drop the cached-install path and
29
+ // go back to the `npx -y -p ...` pattern used by _darwin.mjs).
30
+ //
31
+ // CONTRACT (matches runMetaharness/runDarwin):
32
+ // - returns `{ stdout, stderr, exitCode, durationMs, degraded, reason? }`
33
+ // - subprocess hard timeout (default 120s; --mock-judge runs are seconds)
34
+ // - on install failure / MODULE_NOT_FOUND / network failure, returns
35
+ // `degraded: true, reason: 'metaharness-redblue-not-available'`
36
+ // - never throws — ADR-150 graceful-degradation rule #3
37
+ //
38
+ // SAFETY NOTE
39
+ // redblue itself enforces hard safety boundaries in `src/config/safety.ts`
40
+ // (no real creds, no live targets, no shell, no eval, no arbitrary network).
41
+ // This wrapper does NOT relax those — it only forwards argv with shell:false.
42
+ // `--mock-judge` is the $0 CI path; the real model judge gates on
43
+ // $OPENROUTER_API_KEY which we never inject.
44
+
45
+ import { spawnSync } from 'node:child_process';
46
+ import { existsSync, mkdirSync } from 'node:fs';
47
+ import { homedir } from 'node:os';
48
+ import { join } from 'node:path';
49
+
50
+ const DEFAULT_TIMEOUT_MS = 120_000;
51
+ const INSTALL_TIMEOUT_MS = 180_000; // npm install can be slow on cold cache
52
+
53
+ // Pinned semver range. Bump in lock-step with optionalDependencies in
54
+ // @claude-flow/cli/package.json + ruflo/package.json.
55
+ const REDBLUE_PIN = '@metaharness/redblue@~0.1.1';
56
+ const REDBLUE_PKG = '@metaharness/redblue';
57
+ const REDBLUE_PIN_VERSION = '~0.1.1';
58
+
59
+ const CACHE_DIR = join(homedir(), '.ruflo', 'redblue-cache');
60
+ const RESOLVED_CLI = join(
61
+ CACHE_DIR, 'node_modules', '@metaharness', 'redblue', 'dist', 'cli', 'index.js',
62
+ );
63
+
64
+ const DEGRADED_RX = /could not determine executable|404|not installed|MODULE_NOT_FOUND|ENOTFOUND|getaddrinfo|ECONNREFUSED|ETIMEDOUT|npm ERR/i;
65
+
66
+ function ensureInstalled() {
67
+ if (existsSync(RESOLVED_CLI)) return { ok: true };
68
+ try {
69
+ mkdirSync(CACHE_DIR, { recursive: true });
70
+ } catch (e) {
71
+ return { ok: false, reason: 'cache-dir-create-failed', error: String(e) };
72
+ }
73
+ // Use `npm install --prefix` so the package lands in a known location;
74
+ // this avoids npx's symlinked shim entirely (the upstream bin-bootstrap bug).
75
+ // shell:false; argv only.
76
+ const r = spawnSync('npm', [
77
+ 'install',
78
+ '--no-audit', '--no-fund', '--no-package-lock',
79
+ '--prefix', CACHE_DIR,
80
+ `${REDBLUE_PKG}@${REDBLUE_PIN_VERSION}`,
81
+ ], {
82
+ stdio: ['ignore', 'pipe', 'pipe'],
83
+ encoding: 'utf-8',
84
+ timeout: INSTALL_TIMEOUT_MS,
85
+ shell: process.platform === 'win32',
86
+ });
87
+ if (r.status !== 0 || !existsSync(RESOLVED_CLI)) {
88
+ return {
89
+ ok: false,
90
+ reason: 'install-failed',
91
+ stderr: (r.stderr || '').slice(0, 600),
92
+ stdout: (r.stdout || '').slice(0, 600),
93
+ };
94
+ }
95
+ return { ok: true };
96
+ }
97
+
98
+ function classifyDegraded(stderr, exitCode) {
99
+ if (exitCode === null) return { degraded: true, reason: 'metaharness-redblue-timeout' };
100
+ if (DEGRADED_RX.test(stderr)) return { degraded: true, reason: 'metaharness-redblue-not-available' };
101
+ return { degraded: false };
102
+ }
103
+
104
+ /**
105
+ * Sync invocation. `redblue` runs are bounded by `max_cost_usd` and
106
+ * `max_runtime_minutes` from the config, so sync is fine for the CLI
107
+ * wrapper. The default --mock-judge fixture path completes in seconds.
108
+ */
109
+ export function runRedblue(args, opts = {}) {
110
+ const start = Date.now();
111
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
112
+
113
+ // 1. Ensure redblue is installed in our cache dir (one-time install).
114
+ const install = ensureInstalled();
115
+ if (!install.ok) {
116
+ return {
117
+ stdout: install.stdout ?? '',
118
+ stderr: install.stderr ?? install.error ?? '',
119
+ exitCode: 127,
120
+ durationMs: Date.now() - start,
121
+ degraded: true,
122
+ reason: install.reason === 'install-failed'
123
+ ? 'metaharness-redblue-not-available'
124
+ : `metaharness-redblue-${install.reason}`,
125
+ };
126
+ }
127
+
128
+ // 2. Invoke `node <real-path-to-cli> <args>` so upstream's isMain check
129
+ // succeeds (argv[1] matches import.meta.url).
130
+ const r = spawnSync('node', [RESOLVED_CLI, ...args], {
131
+ stdio: ['ignore', 'pipe', 'pipe'],
132
+ encoding: 'utf-8',
133
+ timeout: timeoutMs,
134
+ cwd: opts.cwd,
135
+ env: { ...process.env, ...(opts.env || {}) },
136
+ shell: process.platform === 'win32',
137
+ });
138
+ const durationMs = Date.now() - start;
139
+ const stdout = r.stdout || '';
140
+ const stderr = r.stderr || '';
141
+ const classified = classifyDegraded(stderr, r.status);
142
+ if (classified.degraded) {
143
+ return {
144
+ stdout, stderr,
145
+ exitCode: r.status ?? 127,
146
+ durationMs,
147
+ degraded: true,
148
+ reason: classified.reason,
149
+ };
150
+ }
151
+ return {
152
+ stdout, stderr,
153
+ exitCode: r.status ?? 0,
154
+ durationMs,
155
+ degraded: false,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Match the existing `emit*DegradedJsonAndExit` shape used by sibling
161
+ * scripts so MCP tool consumers see one contract.
162
+ */
163
+ export function emitRedblueDegradedJsonAndExit(reason) {
164
+ const payload = {
165
+ degraded: true,
166
+ reason,
167
+ hint: 'Install with `npm i -D ' + REDBLUE_PIN + '` or run `npx -y ' + REDBLUE_PIN + ' --help` to verify network access.',
168
+ generatedAt: new Date().toISOString(),
169
+ };
170
+ console.log(JSON.stringify(payload, null, 2));
171
+ // Exit 0 — ADR-150 architectural constraint #3.
172
+ process.exit(0);
173
+ }
174
+
175
+ export const REDBLUE_VERSION_PIN = REDBLUE_PIN;
176
+ export const REDBLUE_CACHE_DIR = CACHE_DIR;
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+ // redblue.mjs — wrapper around `@metaharness/redblue` for LLM red/blue testing.
3
+ //
4
+ // ADR-150 sibling-package integration: `@metaharness/redblue` runs an
5
+ // adversarial red-team → judge → blue-team patch loop against an LLM
6
+ // target you own. Upstream README: packages/redblue#readme on
7
+ // github.com/ruvnet/agent-harness-generator.
8
+ //
9
+ // SAFETY (enforced by upstream `src/config/safety.ts`, NOT this wrapper):
10
+ // - no real credentials (`allow_real_credentials:true` = load-time error)
11
+ // - no live external targets (validateTarget() rejects non-loopback)
12
+ // - no shell, no arbitrary network, no eval
13
+ // - sensitive outputs redacted before storage
14
+ // - `max_cost_usd` / `max_runtime_minutes` / `max_tests` cap every run
15
+ //
16
+ // USAGE
17
+ // node scripts/redblue.mjs init # write redblue.yaml
18
+ // node scripts/redblue.mjs run --mock-judge --tests 10 # $0 marker-fixture path
19
+ // node scripts/redblue.mjs run --tests 50 --patch # real judge (needs OPENROUTER_API_KEY)
20
+ // node scripts/redblue.mjs attack prompt --count 5 # preview attacks
21
+ // node scripts/redblue.mjs patch --mock-judge # baseline → patch → retest delta
22
+ // node scripts/redblue.mjs report --in report.json # render summary
23
+ //
24
+ // EXIT CODES
25
+ // 0 ran OK (or degraded — redblue not available)
26
+ // 1 --alert-on-fail and overall verdict was FAIL (after patch retest)
27
+ // 2 config / argv error
28
+ //
29
+ // REPORT FORMAT
30
+ // `run` / `patch` write a JSON report to --out (default: ./redblue-report.json).
31
+ // We re-emit it on stdout when --format json so MCP tools can parse it
32
+ // without round-tripping through disk. Markdown rendering is delegated
33
+ // to `redblue report` upstream.
34
+
35
+ import { runRedblue, emitRedblueDegradedJsonAndExit } from './_redblue.mjs';
36
+ import { existsSync, readFileSync, mkdtempSync, unlinkSync } from 'node:fs';
37
+ import { join, resolve } from 'node:path';
38
+ import { tmpdir } from 'node:os';
39
+
40
+ const VALID_SUBCOMMANDS = new Set(['init', 'run', 'patch', 'attack', 'report']);
41
+ const VALID_ATTACK_FAMILIES = new Set(['prompt', 'tools', 'data', 'all']);
42
+
43
+ const ARGS = (() => {
44
+ const a = {
45
+ sub: null,
46
+ // shared
47
+ config: null,
48
+ out: null,
49
+ inPath: null,
50
+ format: 'json',
51
+ // run/patch
52
+ tests: null,
53
+ patch: false,
54
+ mockJudge: false,
55
+ alertOnFail: false,
56
+ // attack
57
+ family: null,
58
+ count: null,
59
+ // budget overrides (forwarded only when set)
60
+ maxCostUsd: null,
61
+ maxRuntimeMinutes: null,
62
+ timeoutMs: null,
63
+ };
64
+ const argv = process.argv.slice(2);
65
+ if (argv.length === 0) return a;
66
+ a.sub = argv[0];
67
+ for (let i = 1; i < argv.length; i++) {
68
+ const v = argv[i];
69
+ if (v === '--config') a.config = argv[++i];
70
+ else if (v === '--out') a.out = argv[++i];
71
+ else if (v === '--in') a.inPath = argv[++i];
72
+ else if (v === '--format') a.format = argv[++i];
73
+ else if (v === '--tests') a.tests = parseInt(argv[++i], 10);
74
+ else if (v === '--patch') a.patch = true;
75
+ else if (v === '--mock-judge') a.mockJudge = true;
76
+ else if (v === '--alert-on-fail') a.alertOnFail = true;
77
+ else if (v === '--count') a.count = parseInt(argv[++i], 10);
78
+ else if (v === '--max-cost-usd') a.maxCostUsd = parseFloat(argv[++i]);
79
+ else if (v === '--max-runtime-minutes') a.maxRuntimeMinutes = parseFloat(argv[++i]);
80
+ else if (v === '--timeout-ms') a.timeoutMs = parseInt(argv[++i], 10);
81
+ else if (v === '--help' || v === '-h') a.sub = 'help';
82
+ else if (!v.startsWith('-') && a.sub === 'attack' && !a.family) a.family = v;
83
+ // any other unknown flag — pass through to upstream redblue
84
+ else if (v.startsWith('--')) {
85
+ // forward unknown flags verbatim so we don't bottleneck new redblue features
86
+ a._passthrough = a._passthrough || [];
87
+ a._passthrough.push(v);
88
+ // peek for next-token value (only consume if it's not another flag)
89
+ const next = argv[i + 1];
90
+ if (next !== undefined && !next.startsWith('-')) {
91
+ a._passthrough.push(next);
92
+ i++;
93
+ }
94
+ }
95
+ }
96
+ return a;
97
+ })();
98
+
99
+ function printHelp() {
100
+ console.log('npx ruflo metaharness redblue <subcommand> [options]');
101
+ console.log('');
102
+ console.log('Subcommands:');
103
+ console.log(' init Write a sample redblue.yaml config');
104
+ console.log(' run Run red-team → judge → (optional --patch → retest) → report');
105
+ console.log(' patch Baseline → blue-team patch → retest delta');
106
+ console.log(' attack <prompt|tools|data|all> Preview generated attack test cases (no target call)');
107
+ console.log(' report Render an existing report.json as markdown');
108
+ console.log('');
109
+ console.log('Common options:');
110
+ console.log(' --config <path> Config YAML (default: redblue.yaml in cwd)');
111
+ console.log(' --out <path> Output path for run/patch (default: ./redblue-report.json)');
112
+ console.log(' --in <path> Input report path (for `report` subcommand)');
113
+ console.log(' --tests N How many test cases (run/patch)');
114
+ console.log(' --patch After baseline run, apply blue-team patches and retest');
115
+ console.log(' --mock-judge $0 TEST-ONLY marker fixture — for CI / offline');
116
+ console.log(' --alert-on-fail Exit 1 when overall post-patch verdict is FAIL');
117
+ console.log(' --format json|markdown Output format (default: json)');
118
+ console.log(' --max-cost-usd N Override config max_cost_usd');
119
+ console.log(' --max-runtime-minutes N Override config max_runtime_minutes');
120
+ console.log(' --timeout-ms N Subprocess hard timeout (default: 120000)');
121
+ console.log('');
122
+ console.log('Examples:');
123
+ console.log(' node scripts/redblue.mjs init');
124
+ console.log(' node scripts/redblue.mjs run --mock-judge --tests 10');
125
+ console.log(' node scripts/redblue.mjs run --tests 50 --patch # needs OPENROUTER_API_KEY');
126
+ console.log(' node scripts/redblue.mjs attack prompt --count 3');
127
+ console.log('');
128
+ console.log('SAFETY: upstream redblue enforces no-creds, no-live-targets, no-shell,');
129
+ console.log('no-eval at load time (src/config/safety.ts). This wrapper does not relax');
130
+ console.log('those — it only forwards argv with shell:false.');
131
+ }
132
+
133
+ function buildUpstreamArgs() {
134
+ switch (ARGS.sub) {
135
+ case 'init': {
136
+ const out = ARGS.out ?? null;
137
+ return ['init', ...(out ? ['--out', out] : [])];
138
+ }
139
+ case 'run': {
140
+ const a = ['run'];
141
+ if (ARGS.config) a.push('--config', ARGS.config);
142
+ if (ARGS.tests !== null && Number.isFinite(ARGS.tests)) a.push('--tests', String(ARGS.tests));
143
+ if (ARGS.patch) a.push('--patch');
144
+ if (ARGS.mockJudge) a.push('--mock-judge');
145
+ if (ARGS.out) a.push('--out', ARGS.out);
146
+ return a;
147
+ }
148
+ case 'patch': {
149
+ const a = ['patch'];
150
+ if (ARGS.config) a.push('--config', ARGS.config);
151
+ if (ARGS.mockJudge) a.push('--mock-judge');
152
+ if (ARGS.out) a.push('--out', ARGS.out);
153
+ return a;
154
+ }
155
+ case 'attack': {
156
+ if (!ARGS.family || !VALID_ATTACK_FAMILIES.has(ARGS.family)) {
157
+ console.error(`redblue: --attack family must be one of ${[...VALID_ATTACK_FAMILIES].join('|')} (got: ${ARGS.family ?? 'none'})`);
158
+ process.exit(2);
159
+ }
160
+ const a = ['attack', ARGS.family];
161
+ if (ARGS.count !== null && Number.isFinite(ARGS.count)) a.push('--count', String(ARGS.count));
162
+ return a;
163
+ }
164
+ case 'report': {
165
+ if (!ARGS.inPath) {
166
+ console.error('redblue: report requires --in <path>');
167
+ process.exit(2);
168
+ }
169
+ return ['report', '--in', ARGS.inPath];
170
+ }
171
+ default:
172
+ console.error(`redblue: unknown subcommand "${ARGS.sub}". Valid: ${[...VALID_SUBCOMMANDS].join(', ')}`);
173
+ process.exit(2);
174
+ }
175
+ }
176
+
177
+ function main() {
178
+ if (!ARGS.sub || ARGS.sub === 'help' || ARGS.sub === '--help' || ARGS.sub === '-h') {
179
+ printHelp();
180
+ process.exit(0);
181
+ }
182
+ if (!VALID_SUBCOMMANDS.has(ARGS.sub)) {
183
+ console.error(`redblue: unknown subcommand "${ARGS.sub}". Valid: ${[...VALID_SUBCOMMANDS].join(', ')}`);
184
+ process.exit(2);
185
+ }
186
+
187
+ // For `run` and `patch` we want the report JSON in our hands even when
188
+ // the caller didn't pass --out (so MCP tools can read it back from stdout).
189
+ let synthOut = null;
190
+ if ((ARGS.sub === 'run' || ARGS.sub === 'patch') && !ARGS.out) {
191
+ const dir = mkdtempSync(join(tmpdir(), 'ruflo-redblue-'));
192
+ synthOut = join(dir, 'report.json');
193
+ ARGS.out = synthOut;
194
+ }
195
+
196
+ const upstreamArgs = buildUpstreamArgs();
197
+ if (ARGS._passthrough?.length) upstreamArgs.push(...ARGS._passthrough);
198
+
199
+ const r = runRedblue(upstreamArgs, { timeoutMs: ARGS.timeoutMs ?? undefined });
200
+
201
+ if (r.degraded) {
202
+ if (synthOut && existsSync(synthOut)) {
203
+ try { unlinkSync(synthOut); } catch { /* ignore */ }
204
+ }
205
+ emitRedblueDegradedJsonAndExit(r.reason);
206
+ return;
207
+ }
208
+
209
+ // Pass through stderr (progress) when format=markdown so users see it live
210
+ if (ARGS.format === 'markdown' && r.stderr) {
211
+ process.stderr.write(r.stderr);
212
+ }
213
+
214
+ // For run/patch: read the report we asked redblue to write.
215
+ let payload = null;
216
+ if (ARGS.sub === 'run' || ARGS.sub === 'patch') {
217
+ const reportPath = resolve(ARGS.out);
218
+ if (existsSync(reportPath)) {
219
+ try {
220
+ payload = JSON.parse(readFileSync(reportPath, 'utf-8'));
221
+ } catch (e) {
222
+ console.error(`redblue: failed to parse report at ${reportPath}: ${e instanceof Error ? e.message : String(e)}`);
223
+ if (synthOut) { try { unlinkSync(synthOut); } catch { /* ignore */ } }
224
+ process.exit(2);
225
+ }
226
+ }
227
+ }
228
+
229
+ // Decorate every payload with our own metadata + the safety boundary
230
+ // reminder so consumers always see "this was capability-contained".
231
+ const enriched = payload && typeof payload === 'object' ? {
232
+ ...payload,
233
+ _ruflo: {
234
+ subcommand: ARGS.sub,
235
+ mockJudge: ARGS.mockJudge,
236
+ durationMs: r.durationMs,
237
+ safetyBoundary: 'enforced-by-upstream-redblue (no creds, no live targets, no shell, no eval)',
238
+ },
239
+ } : payload;
240
+
241
+ // Determine overall verdict for --alert-on-fail (post-patch run only).
242
+ // redblue reports include a `verdict` or `gate` field; we treat absence
243
+ // as "no data → no alert" rather than erroring.
244
+ let alertTriggered = false;
245
+ if (ARGS.alertOnFail && enriched && typeof enriched === 'object') {
246
+ const v = (enriched.verdict || enriched.gate || enriched.status || '').toString().toUpperCase();
247
+ if (v.includes('FAIL')) alertTriggered = true;
248
+ }
249
+
250
+ if (ARGS.format === 'json') {
251
+ if (enriched !== null) {
252
+ console.log(JSON.stringify(enriched, null, 2));
253
+ } else {
254
+ // init / attack / report subcommands — forward upstream stdout verbatim
255
+ // (it's already markdown/yaml). Wrap in a tiny JSON envelope.
256
+ console.log(JSON.stringify({
257
+ subcommand: ARGS.sub,
258
+ ok: r.exitCode === 0,
259
+ durationMs: r.durationMs,
260
+ stdout: r.stdout,
261
+ _ruflo: { safetyBoundary: 'enforced-by-upstream-redblue' },
262
+ }, null, 2));
263
+ }
264
+ } else {
265
+ // markdown / passthrough
266
+ if (r.stdout) process.stdout.write(r.stdout);
267
+ if (enriched && typeof enriched === 'object') {
268
+ console.log('');
269
+ console.log('---');
270
+ console.log('## redblue report (parsed)');
271
+ console.log('```json');
272
+ console.log(JSON.stringify(enriched, null, 2));
273
+ console.log('```');
274
+ }
275
+ }
276
+
277
+ // Clean up synthetic temp file
278
+ if (synthOut && existsSync(synthOut)) {
279
+ try { unlinkSync(synthOut); } catch { /* ignore */ }
280
+ }
281
+
282
+ if (alertTriggered) process.exit(1);
283
+ if (r.exitCode !== 0) process.exit(r.exitCode);
284
+ }
285
+
286
+ main();
@@ -151,9 +151,9 @@ F="$ROOT/../../v3/@claude-flow/cli/src/commands/metaharness.ts"
151
151
  miss=""
152
152
  [[ -f "$F" ]] || miss="$miss command-file-missing"
153
153
  grep -q "name: 'metaharness'" "$F" 2>/dev/null || miss="$miss no-name-field"
154
- # All 8 subcommands must each be present in the dispatch table.
154
+ # All 9 subcommands must each be present in the dispatch table.
155
155
  # Match either quoted ('mcp-scan': ...) or unquoted shorthand (score: ...) keys.
156
- for sub in score genome mcp-scan threat-model oia-audit audit-list audit-trend mint; do
156
+ for sub in score genome mcp-scan threat-model oia-audit audit-list audit-trend mint redblue; do
157
157
  grep -qE "(^|[[:space:]])'?${sub}'?:" "$F" 2>/dev/null || miss="$miss missing-$sub"
158
158
  done
159
159
  # Registered in the loader
@@ -342,7 +342,7 @@ miss=""
342
342
  ROOTPJ="$ROOT/../../package.json"
343
343
  RUFLOPJ="$ROOT/../../ruflo/package.json"
344
344
  CLIPJ="$ROOT/../../v3/@claude-flow/cli/package.json"
345
- for pkg in "metaharness" "@metaharness/router" "@metaharness/kernel"; do
345
+ for pkg in "metaharness" "@metaharness/router" "@metaharness/kernel" "@metaharness/redblue"; do
346
346
  ROOT_PIN=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$ROOTPJ')).optionalDependencies?.['$pkg'] || '')" 2>/dev/null)
347
347
  RUFLO_PIN=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$RUFLOPJ')).optionalDependencies?.['$pkg'] || '')" 2>/dev/null)
348
348
  CLI_PIN=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$CLIPJ')).optionalDependencies?.['$pkg'] || '')" 2>/dev/null)
@@ -722,7 +722,7 @@ for k in $KEYS; do
722
722
  grep -qE "npx ruflo metaharness ${k}([ \\\\]|$)" "$CMD" 2>/dev/null \
723
723
  || miss="$miss subcommand-${k}-not-in-claude-md"
724
724
  done
725
- [[ "$COUNT" == "10" ]] || miss="$miss subcommand-count-stale:$COUNT-expected-10"
725
+ [[ "$COUNT" == "11" ]] || miss="$miss subcommand-count-stale:$COUNT-expected-11"
726
726
  [[ -z "$miss" ]] && ok || bad "$miss"
727
727
 
728
728
  step "17z56. every MCP tool documented in CLAUDE.md (iter 93)"
@@ -742,10 +742,11 @@ for t in $TOOLS; do
742
742
  grep -q "mcp__claude-flow__${t}" "$CMD" 2>/dev/null \
743
743
  || miss="$miss ${t}-not-in-claude-md"
744
744
  done
745
- # Lock count: 12 MCP tools (mint deliberately excluded — see iter 73).
745
+ # Lock count: 13 MCP tools (mint deliberately excluded — see iter 73).
746
746
  # Bumped from 9 → 12 after ADR-153 added metaharness_bench,
747
- # metaharness_evolve, and metaharness_security_bench.
748
- [[ "$COUNT" == "12" ]] || miss="$miss mcp-tool-count-stale:$COUNT-expected-12"
747
+ # metaharness_evolve, and metaharness_security_bench. Bumped from 12 → 13
748
+ # after @metaharness/redblue integration added metaharness_redblue.
749
+ [[ "$COUNT" == "13" ]] || miss="$miss mcp-tool-count-stale:$COUNT-expected-13"
749
750
  [[ -z "$miss" ]] && ok || bad "$miss"
750
751
 
751
752
  step "17z55. MCP enum + SEVERITY_RANK vocabulary aligned (iter 92)"
@@ -820,9 +821,10 @@ for f in $REFS; do
820
821
  COUNT=$((COUNT + 1))
821
822
  [[ -f "$SCRIPTS_DIR/$f" ]] || miss="$miss mcp-script-${f}-missing"
822
823
  done
823
- # Should be 12 unique scripts (one per MCP tool; mint deliberately excluded).
824
+ # Should be 13 unique scripts (one per MCP tool; mint deliberately excluded).
824
825
  # Bumped from 9 → 12 after ADR-153 added bench/evolve/security_bench tools.
825
- [[ "$COUNT" == "12" ]] || miss="$miss mcp-script-count-stale:$COUNT-expected-12"
826
+ # Bumped from 12 13 after @metaharness/redblue integration added redblue.mjs.
827
+ [[ "$COUNT" == "13" ]] || miss="$miss mcp-script-count-stale:$COUNT-expected-13"
826
828
  [[ -z "$miss" ]] && ok || bad "$miss"
827
829
 
828
830
  step "17z52. SUBCOMMANDS map entries point at existing script files (iter 89)"
@@ -842,9 +844,9 @@ for f in $MAPPINGS; do
842
844
  COUNT=$((COUNT + 1))
843
845
  [[ -f "$SCRIPTS_DIR/$f" ]] || miss="$miss script-${f}-missing"
844
846
  done
845
- # Iter 73's description string lists 10 subcommands. SUBCOMMANDS map should
846
- # have the same 10 entries. Lock the count at exactly 10.
847
- [[ "$COUNT" == "10" ]] || miss="$miss mapping-count-stale:$COUNT-expected-10"
847
+ # Iter 73's description string + redblue addition lists 11 subcommands.
848
+ # SUBCOMMANDS map should have the same 11 entries. Lock the count at exactly 11.
849
+ [[ "$COUNT" == "11" ]] || miss="$miss mapping-count-stale:$COUNT-expected-11"
848
850
  [[ -z "$miss" ]] && ok || bad "$miss"
849
851
 
850
852
  step "17z51. all metaharness scripts produce parseable JSON in --format json mode (iter 88)"
@@ -1138,8 +1140,9 @@ step "17z36. CLI subcommand list current + mint anti-MCP guard (iter 73)"
1138
1140
  miss=""
1139
1141
  DISP="$ROOT/../../v3/@claude-flow/cli/src/commands/metaharness.ts"
1140
1142
  WRAPPER="$ROOT/../../v3/@claude-flow/cli/src/mcp-tools/metaharness-tools.ts"
1141
- # iter-73 description string lists all 10 dispatchable subcommands
1142
- for sub in score genome mcp-scan threat-model oia-audit audit-list audit-trend similarity drift-from-history mint; do
1143
+ # All 11 dispatchable subcommands present (iter-73 had 10; @metaharness/redblue
1144
+ # integration added redblue.mjs).
1145
+ for sub in score genome mcp-scan threat-model oia-audit audit-list audit-trend similarity drift-from-history mint redblue; do
1143
1146
  grep -q "${sub}" "$DISP" 2>/dev/null || miss="$miss subcommand-${sub}-not-listed"
1144
1147
  done
1145
1148
  # ANTI-MINT GUARD: mint is intentionally CLI-only per ADR-150 §Sandboxing
@@ -1628,8 +1631,9 @@ WRAPPER="$ROOT/../../v3/@claude-flow/cli/src/mcp-tools/metaharness-tools.ts"
1628
1631
  # Success-semantic constant declared + appended to N descriptions = N+1 occurrences.
1629
1632
  # Iter 46 set this at 9 (8 tools); iter 54 added the 9th → 10. ADR-153
1630
1633
  # added 3 more tools (bench/evolve/security_bench) → 1 + 12 = 13.
1634
+ # @metaharness/redblue integration added 1 more → 1 + 13 = 14.
1631
1635
  COUNT=$(grep -c "MCP_SUCCESS_SEMANTIC" "$WRAPPER" 2>/dev/null; true)
1632
- [[ "$COUNT" == "13" ]] || miss="$miss footnote-count:$COUNT-expected-13"
1636
+ [[ "$COUNT" == "14" ]] || miss="$miss footnote-count:$COUNT-expected-14"
1633
1637
  # audit_trend now exposes baselineFile / currentFile
1634
1638
  grep -q "baselineFile" "$WRAPPER" 2>/dev/null || miss="$miss no-baseline-file"
1635
1639
  grep -q "currentFile" "$WRAPPER" 2>/dev/null || miss="$miss no-current-file"
@@ -1668,8 +1672,9 @@ COUNT_OLD=$(grep -c "success: !r.degraded" "$WRAPPER" 2>/dev/null; true)
1668
1672
  [[ "$COUNT_OLD" == "0" ]] || miss="$miss old-pattern-still-present:$COUNT_OLD"
1669
1673
  COUNT_NEW=$(grep -c "success: r.success" "$WRAPPER" 2>/dev/null; true)
1670
1674
  # Iter 54 added a 9th tool → 9. ADR-153 added 3 more (bench, evolve,
1671
- # security_bench) → 12. Future iters that add tools should bump this.
1672
- [[ "$COUNT_NEW" == "12" ]] || miss="$miss new-pattern-count:$COUNT_NEW-expected-12"
1675
+ # security_bench) → 12. @metaharness/redblue integration added 1 more 13.
1676
+ # Future iters that add tools should bump this.
1677
+ [[ "$COUNT_NEW" == "13" ]] || miss="$miss new-pattern-count:$COUNT_NEW-expected-13"
1673
1678
  # Runtime anchors: iter 44 success assertions present
1674
1679
  T="$ROOT/scripts/test-mcp-tools.mjs"
1675
1680
  grep -q "iter 44 fix" "$T" 2>/dev/null || miss="$miss no-iter44-anchors"
@@ -2027,13 +2032,13 @@ grep -q "result has 'success'" "$F" || miss="$miss no-success-assertion"
2027
2032
  grep -q "result has 'data'" "$F" || miss="$miss no-data-assertion"
2028
2033
  grep -q "result has 'degraded'" "$F" || miss="$miss no-degraded-assertion"
2029
2034
  grep -q "result has 'exitCode'" "$F" || miss="$miss no-exitcode-assertion"
2030
- # All 12 tool names enumerated (similarity iter 36, drift_from_history iter 54,
2031
- # ADR-153 added bench/evolve/security_bench)
2032
- for tool in metaharness_score metaharness_genome metaharness_mcp_scan metaharness_threat_model metaharness_oia_audit metaharness_audit_list metaharness_audit_trend metaharness_similarity metaharness_drift_from_history metaharness_bench metaharness_evolve metaharness_security_bench; do
2035
+ # All 13 tool names enumerated (similarity iter 36, drift_from_history iter 54,
2036
+ # ADR-153 added bench/evolve/security_bench, @metaharness/redblue added redblue)
2037
+ for tool in metaharness_score metaharness_genome metaharness_mcp_scan metaharness_threat_model metaharness_oia_audit metaharness_audit_list metaharness_audit_trend metaharness_similarity metaharness_drift_from_history metaharness_bench metaharness_evolve metaharness_security_bench metaharness_redblue; do
2033
2038
  grep -q "${tool}" "$F" || miss="$miss missing-${tool}"
2034
2039
  done
2035
- # Count assertion must match iter-54 expansion (8 → 9), then ADR-153 (9 → 12)
2036
- grep -q "tools.length === 12" "$F" || miss="$miss tool-count-assertion-stale"
2040
+ # Count assertion must match iter-54 (8 → 9), then ADR-153 (9 → 12), then redblue (12 → 13)
2041
+ grep -q "tools.length === 13" "$F" || miss="$miss tool-count-assertion-stale"
2037
2042
  # Graceful skip when dist absent (so the script is smoke-runnable pre-build)
2038
2043
  grep -q "SKIPPED" "$F" || miss="$miss no-skip-doc"
2039
2044
  [[ -z "$miss" ]] && ok || bad "$miss"
@@ -74,7 +74,7 @@ async function main() {
74
74
  // ──────────────────────────────────────────────────────────────────
75
75
  console.log('Phase 1 — module shape');
76
76
  assert(Array.isArray(tools), 'metaharnessTools is an array');
77
- assert(tools.length === 12, `12 tools registered (got ${tools.length})`);
77
+ assert(tools.length === 13, `13 tools registered (got ${tools.length})`);
78
78
 
79
79
  const expectedNames = new Set([
80
80
  'metaharness_score',
@@ -92,6 +92,8 @@ async function main() {
92
92
  'metaharness_bench',
93
93
  'metaharness_evolve',
94
94
  'metaharness_security_bench',
95
+ // @metaharness/redblue@~0.1.1 — adversarial red/blue LLM testing
96
+ 'metaharness_redblue',
95
97
  ]);
96
98
  const actualNames = new Set(tools.map((t) => t.name));
97
99
  for (const name of expectedNames) {
@@ -145,6 +147,12 @@ async function main() {
145
147
  // test doesn't pollute namespaces.
146
148
  input = { dryRun: true };
147
149
  }
150
+ if (tool.name === 'metaharness_redblue') {
151
+ // `attack` preview is the fastest path that exercises the upstream
152
+ // binary without needing OPENROUTER_API_KEY or running any model
153
+ // calls. Count=1 keeps cold-cache npx fetch the dominant cost.
154
+ input = { subcommand: 'attack', family: 'prompt', count: 1 };
155
+ }
148
156
 
149
157
  // iter 124 → 130 — timeouts have crept up as CI cold-cache npx
150
158
  // warmup costs got measured. Final budgets:
@@ -157,7 +165,11 @@ async function main() {
157
165
  // ONNX). 180s gives 30x headroom over the local cost.
158
166
  const isChainTool = tool.name === 'metaharness_drift_from_history'
159
167
  || tool.name === 'metaharness_oia_audit'
160
- || tool.name === 'metaharness_audit_list';
168
+ || tool.name === 'metaharness_audit_list'
169
+ // redblue: `attack prompt --count 1` is preview-only (no model
170
+ // calls) but the cold-cache `npx -y @metaharness/redblue@~0.1.1`
171
+ // fetch can take 30-60s. 180s gives 3x headroom.
172
+ || tool.name === 'metaharness_redblue';
161
173
  const timeoutMs = isChainTool ? 180_000 : 60_000;
162
174
  const handlerPromise = tool.handler(input);
163
175
  const timeoutPromise = new Promise((_, reject) =>
@@ -432,7 +444,7 @@ async function main() {
432
444
  for (const f of failures) console.log(` - ${f}`);
433
445
  process.exit(1);
434
446
  }
435
- console.log('\n✓ All 9 MCP tools satisfy the runtime contract.');
447
+ console.log('\n✓ All 13 MCP tools satisfy the runtime contract.');
436
448
  }
437
449
 
438
450
  main().catch((e) => {