@claude-flow/cli 3.12.4 → 3.13.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/mcp-tools/metaharness-tools.d.ts +7 -0
- package/dist/src/mcp-tools/metaharness-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/metaharness-tools.js +144 -0
- package/dist/src/mcp-tools/metaharness-tools.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +200 -0
- package/plugins/ruflo-metaharness/scripts/bench.mjs +95 -0
- package/plugins/ruflo-metaharness/scripts/evolve.mjs +244 -0
- package/plugins/ruflo-metaharness/scripts/security-bench.mjs +174 -0
- package/plugins/ruflo-metaharness/scripts/test-graceful-degradation.mjs +8 -0
- package/plugins/ruflo-metaharness/skills/harness-bench/SKILL.md +64 -0
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +92 -0
- package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +101 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.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",
|
|
@@ -113,9 +113,10 @@
|
|
|
113
113
|
"@claude-flow/memory": "^3.0.0-alpha.20",
|
|
114
114
|
"@claude-flow/plugin-gastown-bridge": "^0.1.3",
|
|
115
115
|
"@claude-flow/security": "^3.0.0-alpha.10",
|
|
116
|
+
"@metaharness/darwin": "~0.3.1",
|
|
116
117
|
"@metaharness/kernel": "~0.1.0",
|
|
117
118
|
"@metaharness/router": "~0.3.2",
|
|
118
|
-
"metaharness": "~0.
|
|
119
|
+
"metaharness": "~0.2.6",
|
|
119
120
|
"@ruvector/attention": "^0.1.32",
|
|
120
121
|
"@ruvector/attention-darwin-arm64": "0.1.32",
|
|
121
122
|
"@ruvector/diskann": "^0.1.0",
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// _darwin.mjs — shared invocation helper for the `@metaharness/darwin` CLI.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors `_harness.mjs` for the umbrella `metaharness` / `harness` binaries,
|
|
4
|
+
// but targets the separate darwin binary (`metaharness-darwin`) which is
|
|
5
|
+
// published as its own npm package (`@metaharness/darwin@~0.3.1`).
|
|
6
|
+
//
|
|
7
|
+
// Three subcommands surfaced (matches darwin 0.3.x):
|
|
8
|
+
// - `metaharness-darwin evolve <repo> [...]` — harness self-improvement
|
|
9
|
+
// - `metaharness-darwin bench <create|verify> ...` — bench-suite lifecycle
|
|
10
|
+
// - `metaharness-darwin security bench [...]` — Darwin Shield (upstream ADR-155)
|
|
11
|
+
//
|
|
12
|
+
// CONTRACT (identical to runMetaharness/runHarness):
|
|
13
|
+
// - returns `{ stdout, stderr, exitCode, json|null, durationMs, degraded, reason? }`
|
|
14
|
+
// - `--json` is appended automatically when `opts.json !== false`
|
|
15
|
+
// - subprocess hard timeout (default 60s, override with opts.timeoutMs)
|
|
16
|
+
// - on MODULE_NOT_FOUND / network failure / "not installed", returns
|
|
17
|
+
// `degraded: true, reason: 'metaharness-darwin-not-available'` — never throws
|
|
18
|
+
// (ADR-150 graceful-degradation rule #3; ADR-153 §"Architecture" constraint 3)
|
|
19
|
+
//
|
|
20
|
+
// IMPORTANT — evolve has long timeouts:
|
|
21
|
+
// `evolve` is the only long-running verb in the metaharness family. A real
|
|
22
|
+
// evolution with --generations 10 --children 5 --concurrency 2 takes
|
|
23
|
+
// minutes-to-hours, NOT seconds. Callers MUST pass an explicit `timeoutMs`
|
|
24
|
+
// matched to their --generations × --children × sandbox cost; the 60s
|
|
25
|
+
// default is for `bench verify` and `security bench --population 2 --cycles 1`
|
|
26
|
+
// smoke shapes only.
|
|
27
|
+
|
|
28
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
29
|
+
|
|
30
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
31
|
+
|
|
32
|
+
// Pinned semver range. Bump in lock-step with optionalDependencies in
|
|
33
|
+
// @claude-flow/cli/package.json + ruflo/package.json. The `~` allows
|
|
34
|
+
// patch upgrades without re-pinning.
|
|
35
|
+
const DARWIN_PIN = '@metaharness/darwin@~0.3.1';
|
|
36
|
+
|
|
37
|
+
const DEGRADED_RX = /could not determine executable|404|not installed|MODULE_NOT_FOUND|ENOTFOUND|getaddrinfo|ECONNREFUSED|ETIMEDOUT/i;
|
|
38
|
+
|
|
39
|
+
function buildArgv(args, wantJson) {
|
|
40
|
+
// `metaharness-darwin` historically does NOT take --json on every
|
|
41
|
+
// subcommand (some emit plain text reports — security bench in
|
|
42
|
+
// particular is markdown). We still append --json for callers that
|
|
43
|
+
// explicitly request it, but never silently inject it on subcommands
|
|
44
|
+
// that don't accept it. The shape that does support --json is the
|
|
45
|
+
// single-verb `evolve` and `bench verify`.
|
|
46
|
+
if (!wantJson || args.includes('--json')) return args;
|
|
47
|
+
return [...args, '--json'];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function classifyDegraded(stderr, exitCode) {
|
|
51
|
+
if (exitCode === null) return { degraded: true, reason: 'metaharness-darwin-timeout' };
|
|
52
|
+
if (DEGRADED_RX.test(stderr)) return { degraded: true, reason: 'metaharness-darwin-not-available' };
|
|
53
|
+
return { degraded: false };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function maybeParseJson(stdout) {
|
|
57
|
+
// darwin's evolve emits a final JSON object at end of stdout when --json
|
|
58
|
+
// is passed; everything before is human-readable progress. Grab the LAST
|
|
59
|
+
// {...} block, not the first — the first may be a per-generation log line.
|
|
60
|
+
const matches = [...stdout.matchAll(/\{[\s\S]*?\}/g)];
|
|
61
|
+
if (matches.length === 0) return null;
|
|
62
|
+
// Try last match first; fall back to greedy single-block match.
|
|
63
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
64
|
+
try { return JSON.parse(matches[i][0]); } catch { /* try previous */ }
|
|
65
|
+
}
|
|
66
|
+
const greedy = /\{[\s\S]*\}/.exec(stdout);
|
|
67
|
+
if (greedy) { try { return JSON.parse(greedy[0]); } catch { return null; } }
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Sync invocation. Use for short subcommands (`bench verify`, smoke shapes).
|
|
73
|
+
* Async variant below for long-running `evolve` calls.
|
|
74
|
+
*/
|
|
75
|
+
export function runDarwin(args, opts = {}) {
|
|
76
|
+
const start = Date.now();
|
|
77
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
78
|
+
const wantJson = opts.json !== false;
|
|
79
|
+
const argv = buildArgv(args, wantJson);
|
|
80
|
+
const r = spawnSync('npx', ['-y', '-p', DARWIN_PIN, 'metaharness-darwin', ...argv], {
|
|
81
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
82
|
+
encoding: 'utf-8',
|
|
83
|
+
timeout: timeoutMs,
|
|
84
|
+
cwd: opts.cwd,
|
|
85
|
+
env: { ...process.env, ...(opts.env || {}) },
|
|
86
|
+
shell: process.platform === 'win32',
|
|
87
|
+
});
|
|
88
|
+
const durationMs = Date.now() - start;
|
|
89
|
+
const stdout = r.stdout || '';
|
|
90
|
+
const stderr = r.stderr || '';
|
|
91
|
+
const classified = classifyDegraded(stderr, r.status);
|
|
92
|
+
if (classified.degraded) {
|
|
93
|
+
return {
|
|
94
|
+
stdout, stderr,
|
|
95
|
+
exitCode: r.status ?? 127,
|
|
96
|
+
json: null,
|
|
97
|
+
durationMs,
|
|
98
|
+
degraded: true,
|
|
99
|
+
reason: classified.reason,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
stdout, stderr,
|
|
104
|
+
exitCode: r.status ?? 0,
|
|
105
|
+
json: wantJson ? maybeParseJson(stdout) : null,
|
|
106
|
+
durationMs,
|
|
107
|
+
degraded: false,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Async invocation with streaming. Required for `evolve` because the run
|
|
113
|
+
* can take 10+ minutes and the caller wants progress visibility.
|
|
114
|
+
*
|
|
115
|
+
* If `opts.onProgress(line)` is provided, called once per stderr line.
|
|
116
|
+
* darwin writes per-generation progress to stderr, structured result to
|
|
117
|
+
* stdout. Cancellable via opts.signal (AbortSignal).
|
|
118
|
+
*/
|
|
119
|
+
export function runDarwinAsync(args, opts = {}) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const start = Date.now();
|
|
122
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
123
|
+
const wantJson = opts.json !== false;
|
|
124
|
+
const argv = buildArgv(args, wantJson);
|
|
125
|
+
const p = spawn('npx', ['-y', '-p', DARWIN_PIN, 'metaharness-darwin', ...argv], {
|
|
126
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
127
|
+
cwd: opts.cwd,
|
|
128
|
+
env: { ...process.env, ...(opts.env || {}) },
|
|
129
|
+
shell: process.platform === 'win32',
|
|
130
|
+
});
|
|
131
|
+
let stdout = '', stderr = '', stderrBuf = '';
|
|
132
|
+
p.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
133
|
+
p.stderr?.on('data', (d) => {
|
|
134
|
+
const s = d.toString();
|
|
135
|
+
stderr += s;
|
|
136
|
+
if (opts.onProgress) {
|
|
137
|
+
stderrBuf += s;
|
|
138
|
+
const lines = stderrBuf.split('\n');
|
|
139
|
+
stderrBuf = lines.pop() ?? '';
|
|
140
|
+
for (const line of lines) opts.onProgress(line);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const timer = setTimeout(() => {
|
|
144
|
+
try { p.kill('SIGTERM'); } catch { /* ignore */ }
|
|
145
|
+
}, timeoutMs);
|
|
146
|
+
if (opts.signal) {
|
|
147
|
+
opts.signal.addEventListener('abort', () => {
|
|
148
|
+
try { p.kill('SIGTERM'); } catch { /* ignore */ }
|
|
149
|
+
}, { once: true });
|
|
150
|
+
}
|
|
151
|
+
p.on('error', (e) => {
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
resolve({
|
|
154
|
+
stdout,
|
|
155
|
+
stderr: stderr + String(e?.message ?? e),
|
|
156
|
+
exitCode: 127, json: null, durationMs: Date.now() - start,
|
|
157
|
+
degraded: true, reason: 'metaharness-darwin-not-available',
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
p.on('close', (code) => {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
const durationMs = Date.now() - start;
|
|
163
|
+
const classified = classifyDegraded(stderr, code);
|
|
164
|
+
if (classified.degraded) {
|
|
165
|
+
resolve({
|
|
166
|
+
stdout, stderr,
|
|
167
|
+
exitCode: code ?? 127, json: null, durationMs,
|
|
168
|
+
degraded: true, reason: classified.reason,
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
resolve({
|
|
173
|
+
stdout, stderr,
|
|
174
|
+
exitCode: code ?? 0,
|
|
175
|
+
json: wantJson ? maybeParseJson(stdout) : null,
|
|
176
|
+
durationMs,
|
|
177
|
+
degraded: false,
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Match the existing `emitDegradedJsonAndExit` shape used by the
|
|
185
|
+
* metaharness-umbrella scripts so MCP tool consumers see one contract.
|
|
186
|
+
*/
|
|
187
|
+
export function emitDarwinDegradedJsonAndExit(reason) {
|
|
188
|
+
const payload = {
|
|
189
|
+
degraded: true,
|
|
190
|
+
reason,
|
|
191
|
+
hint: 'Install with `npm i -D ' + DARWIN_PIN + '` or run `npx -y ' + DARWIN_PIN + ' --help` to verify network access.',
|
|
192
|
+
generatedAt: new Date().toISOString(),
|
|
193
|
+
};
|
|
194
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
195
|
+
// Exit 0 — ADR-150 architectural constraint: ruflo continues to function
|
|
196
|
+
// when MetaHarness is absent. Same posture as the umbrella scripts.
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const DARWIN_VERSION_PIN = DARWIN_PIN;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bench.mjs — wrapper around `metaharness-darwin bench <create|verify>`.
|
|
3
|
+
//
|
|
4
|
+
// Bench suites are JSON files describing a corpus of (input, expected-output)
|
|
5
|
+
// tasks. `evolve --bench <suite.json>` scores each variant against the
|
|
6
|
+
// corpus instead of (or in addition to) the project's test command, which
|
|
7
|
+
// lets you evolve a harness against a fixed evaluation set independent of
|
|
8
|
+
// the repo's natural tests. `bench create` scaffolds a suite from a repo;
|
|
9
|
+
// `bench verify` checks a suite parses and every task is well-formed.
|
|
10
|
+
//
|
|
11
|
+
// USAGE
|
|
12
|
+
// node scripts/bench.mjs --op create --repo . # scaffold suite.json from current dir
|
|
13
|
+
// node scripts/bench.mjs --op create --repo . --out /tmp/foo.json
|
|
14
|
+
// node scripts/bench.mjs --op verify --suite /tmp/foo.json
|
|
15
|
+
//
|
|
16
|
+
// EXIT CODES
|
|
17
|
+
// 0 ok (or degraded — Darwin not available)
|
|
18
|
+
// 1 --op verify and any task malformed
|
|
19
|
+
// 2 config error or upstream invocation failure
|
|
20
|
+
|
|
21
|
+
import { runDarwin, emitDarwinDegradedJsonAndExit } from './_darwin.mjs';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { resolve } from 'node:path';
|
|
24
|
+
|
|
25
|
+
const ARGS = (() => {
|
|
26
|
+
const a = { op: null, repo: '.', suite: null, out: null, format: 'json' };
|
|
27
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
28
|
+
const v = process.argv[i];
|
|
29
|
+
if (v === '--op') a.op = process.argv[++i];
|
|
30
|
+
else if (v === '--repo') a.repo = process.argv[++i];
|
|
31
|
+
else if (v === '--suite') a.suite = process.argv[++i];
|
|
32
|
+
else if (v === '--out') a.out = process.argv[++i];
|
|
33
|
+
else if (v === '--format') a.format = process.argv[++i];
|
|
34
|
+
}
|
|
35
|
+
return a;
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
function safetyChecks() {
|
|
39
|
+
if (!['create', 'verify'].includes(ARGS.op)) {
|
|
40
|
+
console.error('bench: --op must be create|verify');
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
if (ARGS.op === 'create' && !existsSync(resolve(ARGS.repo))) {
|
|
44
|
+
console.error(`bench: --repo path does not exist: ${ARGS.repo}`);
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
if (ARGS.op === 'verify' && !ARGS.suite) {
|
|
48
|
+
console.error('bench: --suite is required for verify');
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
if (ARGS.op === 'verify' && !existsSync(resolve(ARGS.suite))) {
|
|
52
|
+
console.error(`bench: --suite path does not exist: ${ARGS.suite}`);
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function main() {
|
|
58
|
+
safetyChecks();
|
|
59
|
+
|
|
60
|
+
const cliArgs = ARGS.op === 'create'
|
|
61
|
+
? ['bench', 'create', resolve(ARGS.repo), ...(ARGS.out ? ['--out', resolve(ARGS.out)] : [])]
|
|
62
|
+
: ['bench', 'verify', resolve(ARGS.suite)];
|
|
63
|
+
|
|
64
|
+
const r = runDarwin(cliArgs, { timeoutMs: 60_000 });
|
|
65
|
+
|
|
66
|
+
if (r.degraded) {
|
|
67
|
+
emitDarwinDegradedJsonAndExit(r.reason);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (r.exitCode !== 0 && r.exitCode !== 1) {
|
|
72
|
+
const payload = {
|
|
73
|
+
success: false,
|
|
74
|
+
data: { exitCode: r.exitCode, stderrTail: r.stderr.slice(-400) },
|
|
75
|
+
generatedAt: new Date().toISOString(),
|
|
76
|
+
};
|
|
77
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
78
|
+
process.exit(2);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const payload = {
|
|
82
|
+
success: r.exitCode === 0,
|
|
83
|
+
data: {
|
|
84
|
+
op: ARGS.op,
|
|
85
|
+
...(r.json ?? { stdoutTail: r.stdout.slice(-2000) }),
|
|
86
|
+
durationMs: r.durationMs,
|
|
87
|
+
},
|
|
88
|
+
generatedAt: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
92
|
+
process.exit(r.exitCode);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main();
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// evolve.mjs — wrapper around `metaharness-darwin evolve <repo>`.
|
|
3
|
+
//
|
|
4
|
+
// ADR-153: Darwin Mode is the WRITE layer that closes the loop ADR-150's
|
|
5
|
+
// READ layer (score / genome / mcp-scan / threat-model / oia-audit) opens.
|
|
6
|
+
// score+genome tell you where the harness IS; darwin evolve tells you which
|
|
7
|
+
// mutation makes it provably better, without retraining the foundation model.
|
|
8
|
+
//
|
|
9
|
+
// SAFETY (ADR-153 §"Safety model"):
|
|
10
|
+
// - Variant generation + sandbox happen under <repo>/.metaharness/variants/,
|
|
11
|
+
// never in the repo root. Upstream `inspectVariant()` rejects nested dirs,
|
|
12
|
+
// symlinks, secret-shaped strings, shell-out / network / dynamic-eval
|
|
13
|
+
// before any variant runs. Exit code 99 is reserved for "safety-disqualified".
|
|
14
|
+
// - --confirm is REQUIRED. Without it the script prints a plan and exits 0
|
|
15
|
+
// (mirrors mint.mjs convention). This is in addition to upstream's safety
|
|
16
|
+
// layer — defense in depth at the ruflo boundary.
|
|
17
|
+
// - Default --generations 3 --children 3 (small) — anything larger is opt-in.
|
|
18
|
+
// Real evolutions are minutes-to-hours; ruflo's default sandbox config
|
|
19
|
+
// errs toward "show me the mechanism works" over "find a winner today".
|
|
20
|
+
//
|
|
21
|
+
// USAGE
|
|
22
|
+
// node scripts/evolve.mjs --repo . # dry-run plan
|
|
23
|
+
// node scripts/evolve.mjs --repo . --confirm # actually evolve
|
|
24
|
+
// node scripts/evolve.mjs --repo . --confirm --generations 5 --children 3
|
|
25
|
+
// node scripts/evolve.mjs --repo . --confirm --sandbox mock # no real tests
|
|
26
|
+
// node scripts/evolve.mjs --repo . --confirm --selection pareto
|
|
27
|
+
//
|
|
28
|
+
// EXIT CODES
|
|
29
|
+
// 0 evolved OK (or dry-run, or degraded — MetaHarness Darwin not available)
|
|
30
|
+
// 1 --alert-on-no-improvement and champion did not beat parent
|
|
31
|
+
// 2 config error or evolution failure
|
|
32
|
+
// 99 reserved — upstream "safety-disqualified" (propagated)
|
|
33
|
+
|
|
34
|
+
import { runDarwinAsync, emitDarwinDegradedJsonAndExit } from './_darwin.mjs';
|
|
35
|
+
import { existsSync } from 'node:fs';
|
|
36
|
+
import { resolve } from 'node:path';
|
|
37
|
+
|
|
38
|
+
const ARGS = (() => {
|
|
39
|
+
const a = {
|
|
40
|
+
repo: '.',
|
|
41
|
+
generations: 3,
|
|
42
|
+
children: 3,
|
|
43
|
+
concurrency: 2,
|
|
44
|
+
seed: null,
|
|
45
|
+
sandbox: 'real',
|
|
46
|
+
selection: null,
|
|
47
|
+
crossover: false,
|
|
48
|
+
epistasis: false,
|
|
49
|
+
curriculum: false,
|
|
50
|
+
riskBudget: null,
|
|
51
|
+
fdr: null,
|
|
52
|
+
tie: null,
|
|
53
|
+
bench: null,
|
|
54
|
+
mutator: 'deterministic',
|
|
55
|
+
ruvllmUrl: null,
|
|
56
|
+
ruvllmModel: null,
|
|
57
|
+
confirm: false,
|
|
58
|
+
alertOnNoImprovement: false,
|
|
59
|
+
format: 'json',
|
|
60
|
+
timeoutMs: null, // computed below if unset
|
|
61
|
+
};
|
|
62
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
63
|
+
const v = process.argv[i];
|
|
64
|
+
if (v === '--repo') a.repo = process.argv[++i];
|
|
65
|
+
else if (v === '--generations') a.generations = parseInt(process.argv[++i], 10);
|
|
66
|
+
else if (v === '--children') a.children = parseInt(process.argv[++i], 10);
|
|
67
|
+
else if (v === '--concurrency') a.concurrency = parseInt(process.argv[++i], 10);
|
|
68
|
+
else if (v === '--seed') a.seed = parseInt(process.argv[++i], 10);
|
|
69
|
+
else if (v === '--sandbox') a.sandbox = process.argv[++i];
|
|
70
|
+
else if (v === '--selection') a.selection = process.argv[++i];
|
|
71
|
+
else if (v === '--crossover') a.crossover = true;
|
|
72
|
+
else if (v === '--epistasis') a.epistasis = true;
|
|
73
|
+
else if (v === '--curriculum') a.curriculum = true;
|
|
74
|
+
else if (v === '--risk-budget') a.riskBudget = parseInt(process.argv[++i], 10);
|
|
75
|
+
else if (v === '--fdr') a.fdr = parseFloat(process.argv[++i]);
|
|
76
|
+
else if (v === '--tie') a.tie = process.argv[++i];
|
|
77
|
+
else if (v === '--bench') a.bench = process.argv[++i];
|
|
78
|
+
else if (v === '--mutator') a.mutator = process.argv[++i];
|
|
79
|
+
else if (v === '--ruvllm-url') a.ruvllmUrl = process.argv[++i];
|
|
80
|
+
else if (v === '--ruvllm-model') a.ruvllmModel = process.argv[++i];
|
|
81
|
+
else if (v === '--confirm') a.confirm = true;
|
|
82
|
+
else if (v === '--alert-on-no-improvement') a.alertOnNoImprovement = true;
|
|
83
|
+
else if (v === '--format') a.format = process.argv[++i];
|
|
84
|
+
else if (v === '--timeout-ms') a.timeoutMs = parseInt(process.argv[++i], 10);
|
|
85
|
+
}
|
|
86
|
+
return a;
|
|
87
|
+
})();
|
|
88
|
+
|
|
89
|
+
function safetyChecks() {
|
|
90
|
+
const repoPath = resolve(ARGS.repo);
|
|
91
|
+
if (!existsSync(repoPath)) {
|
|
92
|
+
console.error(`evolve: repo path does not exist: ${repoPath}`);
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|
|
95
|
+
if (ARGS.generations < 1 || ARGS.generations > 50) {
|
|
96
|
+
console.error('evolve: --generations must be 1..50 (ruflo cap; upstream supports more)');
|
|
97
|
+
process.exit(2);
|
|
98
|
+
}
|
|
99
|
+
if (ARGS.children < 1 || ARGS.children > 20) {
|
|
100
|
+
console.error('evolve: --children must be 1..20 (ruflo cap)');
|
|
101
|
+
process.exit(2);
|
|
102
|
+
}
|
|
103
|
+
if (ARGS.concurrency < 1 || ARGS.concurrency > 8) {
|
|
104
|
+
console.error('evolve: --concurrency must be 1..8 (ruflo cap)');
|
|
105
|
+
process.exit(2);
|
|
106
|
+
}
|
|
107
|
+
if (!['real', 'mock', 'agent'].includes(ARGS.sandbox)) {
|
|
108
|
+
console.error(`evolve: --sandbox must be real|mock|agent (got: ${ARGS.sandbox})`);
|
|
109
|
+
process.exit(2);
|
|
110
|
+
}
|
|
111
|
+
if (ARGS.selection && !['quality-diversity', 'behavioral-diversity', 'niche-steering', 'clade', 'pareto'].includes(ARGS.selection)) {
|
|
112
|
+
console.error(`evolve: --selection must be one of quality-diversity|behavioral-diversity|niche-steering|clade|pareto`);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
if (!['deterministic', 'ruvllm'].includes(ARGS.mutator)) {
|
|
116
|
+
console.error(`evolve: --mutator must be deterministic|ruvllm`);
|
|
117
|
+
process.exit(2);
|
|
118
|
+
}
|
|
119
|
+
return repoPath;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Compute a sensible timeout from the search shape if caller didn't specify.
|
|
123
|
+
// Rough budget: each variant ~= 30s (sandbox test command + safety inspect),
|
|
124
|
+
// total ~= generations × children × per-variant / concurrency.
|
|
125
|
+
function defaultTimeoutMs() {
|
|
126
|
+
const perVariantMs = ARGS.sandbox === 'mock' ? 2_000 : 60_000;
|
|
127
|
+
const variants = ARGS.generations * ARGS.children;
|
|
128
|
+
const parallelism = Math.min(ARGS.concurrency, variants);
|
|
129
|
+
const wall = Math.ceil(variants / parallelism) * perVariantMs;
|
|
130
|
+
// Add 30s overhead for npm install + initial profile + final report.
|
|
131
|
+
return Math.max(60_000, wall + 30_000);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function main() {
|
|
135
|
+
const repoPath = safetyChecks();
|
|
136
|
+
|
|
137
|
+
const plan = {
|
|
138
|
+
binary: 'metaharness-darwin evolve',
|
|
139
|
+
repo: repoPath,
|
|
140
|
+
generations: ARGS.generations,
|
|
141
|
+
children: ARGS.children,
|
|
142
|
+
concurrency: ARGS.concurrency,
|
|
143
|
+
sandbox: ARGS.sandbox,
|
|
144
|
+
selection: ARGS.selection,
|
|
145
|
+
crossover: ARGS.crossover,
|
|
146
|
+
epistasis: ARGS.epistasis,
|
|
147
|
+
curriculum: ARGS.curriculum,
|
|
148
|
+
mutator: ARGS.mutator,
|
|
149
|
+
estVariants: ARGS.generations * ARGS.children,
|
|
150
|
+
timeoutMs: ARGS.timeoutMs ?? defaultTimeoutMs(),
|
|
151
|
+
output: `${repoPath}/.metaharness/{archive.json, lineage.json, variants/, runs/, reports/winner.json}`,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (!ARGS.confirm) {
|
|
155
|
+
const payload = {
|
|
156
|
+
success: true,
|
|
157
|
+
data: { plan, dryRun: true, message: 'Pass --confirm to run the evolution.' },
|
|
158
|
+
generatedAt: new Date().toISOString(),
|
|
159
|
+
};
|
|
160
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const cliArgs = ['evolve', repoPath,
|
|
165
|
+
'--generations', String(ARGS.generations),
|
|
166
|
+
'--children', String(ARGS.children),
|
|
167
|
+
'--concurrency', String(ARGS.concurrency),
|
|
168
|
+
'--sandbox', ARGS.sandbox,
|
|
169
|
+
'--mutator', ARGS.mutator,
|
|
170
|
+
];
|
|
171
|
+
if (ARGS.seed != null) cliArgs.push('--seed', String(ARGS.seed));
|
|
172
|
+
if (ARGS.selection) cliArgs.push('--selection', ARGS.selection);
|
|
173
|
+
if (ARGS.crossover) cliArgs.push('--crossover');
|
|
174
|
+
if (ARGS.epistasis) cliArgs.push('--epistasis');
|
|
175
|
+
if (ARGS.curriculum) cliArgs.push('--curriculum');
|
|
176
|
+
if (ARGS.riskBudget != null) cliArgs.push('--risk-budget', String(ARGS.riskBudget));
|
|
177
|
+
if (ARGS.fdr != null) cliArgs.push('--fdr', String(ARGS.fdr));
|
|
178
|
+
if (ARGS.tie) cliArgs.push('--tie', ARGS.tie);
|
|
179
|
+
if (ARGS.bench) cliArgs.push('--bench', ARGS.bench);
|
|
180
|
+
if (ARGS.ruvllmUrl) cliArgs.push('--ruvllm-url', ARGS.ruvllmUrl);
|
|
181
|
+
if (ARGS.ruvllmModel) cliArgs.push('--ruvllm-model', ARGS.ruvllmModel);
|
|
182
|
+
|
|
183
|
+
// Forward progress lines to stderr so the user sees per-generation activity
|
|
184
|
+
// (subprocess-of-an-MCP-tool case: this still surfaces in the agent log).
|
|
185
|
+
const r = await runDarwinAsync(cliArgs, {
|
|
186
|
+
timeoutMs: plan.timeoutMs,
|
|
187
|
+
onProgress: (line) => { if (line.trim()) process.stderr.write(`[evolve] ${line}\n`); },
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (r.degraded) {
|
|
191
|
+
emitDarwinDegradedJsonAndExit(r.reason);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Upstream exit code 99 = safety-disqualified — propagate verbatim so
|
|
196
|
+
// CI gates can distinguish "evolution failed" from "evolution surfaced
|
|
197
|
+
// a safety-tripping mutation". This is a designed-in tripwire, not an
|
|
198
|
+
// error the ruflo layer should remap.
|
|
199
|
+
if (r.exitCode === 99) {
|
|
200
|
+
const payload = {
|
|
201
|
+
success: false,
|
|
202
|
+
data: { safetyDisqualified: true, hint: 'A variant tripped the safety inspection layer. See <repo>/.metaharness/runs/ for which surface and pattern.' },
|
|
203
|
+
stderrTail: r.stderr.slice(-400),
|
|
204
|
+
durationMs: r.durationMs,
|
|
205
|
+
generatedAt: new Date().toISOString(),
|
|
206
|
+
};
|
|
207
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
208
|
+
process.exit(99);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (r.exitCode !== 0) {
|
|
212
|
+
const payload = {
|
|
213
|
+
success: false,
|
|
214
|
+
data: { exitCode: r.exitCode, stderrTail: r.stderr.slice(-400) },
|
|
215
|
+
generatedAt: new Date().toISOString(),
|
|
216
|
+
};
|
|
217
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
218
|
+
process.exit(2);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const champion = r.json || {};
|
|
222
|
+
const noImprovement = champion.parentScore != null && champion.championScore != null &&
|
|
223
|
+
champion.championScore <= champion.parentScore;
|
|
224
|
+
|
|
225
|
+
const payload = {
|
|
226
|
+
success: true,
|
|
227
|
+
data: {
|
|
228
|
+
...champion,
|
|
229
|
+
plan,
|
|
230
|
+
durationMs: r.durationMs,
|
|
231
|
+
improved: !noImprovement,
|
|
232
|
+
},
|
|
233
|
+
generatedAt: new Date().toISOString(),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
237
|
+
if (ARGS.alertOnNoImprovement && noImprovement) process.exit(1);
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
main().catch((e) => {
|
|
242
|
+
console.error(`evolve: unexpected failure: ${e?.message ?? e}`);
|
|
243
|
+
process.exit(2);
|
|
244
|
+
});
|