@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.
- package/dist/src/business-pods/bbs-budget-tracker.d.ts +139 -0
- package/dist/src/business-pods/bbs-budget-tracker.d.ts.map +1 -0
- package/dist/src/business-pods/bbs-budget-tracker.js +358 -0
- package/dist/src/business-pods/bbs-budget-tracker.js.map +1 -0
- package/dist/src/business-pods/domain-affinity-policy.d.ts +47 -0
- package/dist/src/business-pods/domain-affinity-policy.d.ts.map +1 -0
- package/dist/src/business-pods/domain-affinity-policy.js +65 -0
- package/dist/src/business-pods/domain-affinity-policy.js.map +1 -0
- package/dist/src/business-pods/pod-schema.d.ts +96 -0
- package/dist/src/business-pods/pod-schema.d.ts.map +1 -0
- package/dist/src/business-pods/pod-schema.js +225 -0
- package/dist/src/business-pods/pod-schema.js.map +1 -0
- package/dist/src/commands/metaharness.d.ts.map +1 -1
- package/dist/src/commands/metaharness.js +20 -5
- package/dist/src/commands/metaharness.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +21 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-tools/agentbbs-tools.d.ts +28 -0
- package/dist/src/mcp-tools/agentbbs-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/agentbbs-tools.js +394 -0
- package/dist/src/mcp-tools/agentbbs-tools.js.map +1 -0
- package/dist/src/mcp-tools/agenticow-tools.d.ts +36 -0
- package/dist/src/mcp-tools/agenticow-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/agenticow-tools.js +253 -0
- package/dist/src/mcp-tools/agenticow-tools.js.map +1 -0
- package/dist/src/mcp-tools/business-pod-tools.d.ts +20 -0
- package/dist/src/mcp-tools/business-pod-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/business-pod-tools.js +169 -0
- package/dist/src/mcp-tools/business-pod-tools.js.map +1 -0
- package/dist/src/mcp-tools/http-fetch-tools.d.ts +55 -0
- package/dist/src/mcp-tools/http-fetch-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/http-fetch-tools.js +329 -0
- package/dist/src/mcp-tools/http-fetch-tools.js.map +1 -0
- package/dist/src/mcp-tools/index.d.ts +4 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +8 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/mcp-tools/metaharness-tools.d.ts +6 -0
- package/dist/src/mcp-tools/metaharness-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/metaharness-tools.js +83 -4
- package/dist/src/mcp-tools/metaharness-tools.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -1
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +176 -0
- package/plugins/ruflo-metaharness/scripts/redblue.mjs +286 -0
- package/plugins/ruflo-metaharness/scripts/smoke.sh +27 -22
- 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.
|
|
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
|
|
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" == "
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
846
|
-
# have the same
|
|
847
|
-
[[ "$COUNT" == "
|
|
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
|
|
1142
|
-
|
|
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" == "
|
|
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.
|
|
1672
|
-
|
|
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
|
|
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
|
|
2036
|
-
grep -q "tools.length ===
|
|
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 ===
|
|
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
|
|
447
|
+
console.log('\n✓ All 13 MCP tools satisfy the runtime contract.');
|
|
436
448
|
}
|
|
437
449
|
|
|
438
450
|
main().catch((e) => {
|