@claude-flow/cli 3.12.3 → 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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// security-bench.mjs — wrapper around `metaharness-darwin security bench`.
|
|
3
|
+
//
|
|
4
|
+
// "Darwin Shield" — upstream's own ADR-155 — evolves a champion harness
|
|
5
|
+
// on a 10-vuln/9-decoy corpus and measures it against four baselines
|
|
6
|
+
// (B0 static-only, B1 LLM single-pass, B2 fixed agent, B3 Darwin champion).
|
|
7
|
+
// The CHAMPION reaches TPR=1, FPR=0; the eight acceptance gates verify
|
|
8
|
+
// reproducibility, statistical significance, and unsafe-output=0.
|
|
9
|
+
//
|
|
10
|
+
// CONNECTION TO RUFLO ADR-155
|
|
11
|
+
// ===========================
|
|
12
|
+
// ruflo's ADR-155 (#2417) proposes a nightly self-learning security harness
|
|
13
|
+
// with three learning loops (per-dimension confidence weighting, severity
|
|
14
|
+
// calibration, auto-fix bid). The upstream Darwin Shield is the closest
|
|
15
|
+
// reference implementation — same shape, different scope (evolves a
|
|
16
|
+
// security-detection harness vs evaluates findings; both grade by realized
|
|
17
|
+
// TPR/FPR vs ground-truth corpus). Running `security bench` periodically
|
|
18
|
+
// gives us the empirical baseline that ruflo's Phase 2 loop A needs before
|
|
19
|
+
// training: if Darwin Shield converges on a known-good corpus, the loop A
|
|
20
|
+
// gradient signal is sound; if it doesn't, the corpus / sandbox is the
|
|
21
|
+
// gap, not the learning algorithm.
|
|
22
|
+
//
|
|
23
|
+
// USAGE
|
|
24
|
+
// node scripts/security-bench.mjs # default population=2 cycles=1
|
|
25
|
+
// node scripts/security-bench.mjs --population 4 --cycles 3 # deeper run
|
|
26
|
+
// node scripts/security-bench.mjs --population 4 --cycles 3 --alert-on-fail
|
|
27
|
+
//
|
|
28
|
+
// EXIT CODES
|
|
29
|
+
// 0 bench passed all gates (or degraded — Darwin not available)
|
|
30
|
+
// 1 --alert-on-fail and any acceptance gate failed
|
|
31
|
+
// 2 config error or bench infrastructure failure
|
|
32
|
+
|
|
33
|
+
import { runDarwinAsync, emitDarwinDegradedJsonAndExit } from './_darwin.mjs';
|
|
34
|
+
|
|
35
|
+
const ARGS = (() => {
|
|
36
|
+
const a = {
|
|
37
|
+
population: 2,
|
|
38
|
+
cycles: 1,
|
|
39
|
+
seed: null,
|
|
40
|
+
alertOnFail: false,
|
|
41
|
+
format: 'json',
|
|
42
|
+
timeoutMs: null,
|
|
43
|
+
};
|
|
44
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
45
|
+
const v = process.argv[i];
|
|
46
|
+
if (v === '--population') a.population = parseInt(process.argv[++i], 10);
|
|
47
|
+
else if (v === '--cycles') a.cycles = parseInt(process.argv[++i], 10);
|
|
48
|
+
else if (v === '--seed') a.seed = parseInt(process.argv[++i], 10);
|
|
49
|
+
else if (v === '--alert-on-fail') a.alertOnFail = true;
|
|
50
|
+
else if (v === '--format') a.format = process.argv[++i];
|
|
51
|
+
else if (v === '--timeout-ms') a.timeoutMs = parseInt(process.argv[++i], 10);
|
|
52
|
+
}
|
|
53
|
+
return a;
|
|
54
|
+
})();
|
|
55
|
+
|
|
56
|
+
function safetyChecks() {
|
|
57
|
+
if (ARGS.population < 1 || ARGS.population > 20) {
|
|
58
|
+
console.error('security-bench: --population must be 1..20 (ruflo cap)');
|
|
59
|
+
process.exit(2);
|
|
60
|
+
}
|
|
61
|
+
if (ARGS.cycles < 1 || ARGS.cycles > 100) {
|
|
62
|
+
console.error('security-bench: --cycles must be 1..100 (ruflo cap)');
|
|
63
|
+
process.exit(2);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function defaultTimeoutMs() {
|
|
68
|
+
// Bench corpus has 10 vulns + 9 decoys = 19 evaluations per cycle.
|
|
69
|
+
// Each evaluation runs the candidate detector + scores patches.
|
|
70
|
+
// Rough budget: ~3s per evaluation × population × cycles + 30s overhead.
|
|
71
|
+
return Math.max(60_000, 3_000 * 19 * ARGS.population * ARGS.cycles + 30_000);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Parse the markdown report that `security bench` emits. The header
|
|
75
|
+
// "Overall: ✅ PASS" or "❌ FAIL" is the rolled-up gate. We also extract
|
|
76
|
+
// each acceptance gate's pass/fail line.
|
|
77
|
+
function parseSecurityBenchMarkdown(stdout) {
|
|
78
|
+
const overallMatch = /\*\*Overall:\s*([✅❌])\s*(PASS|FAIL)\*\*/i.exec(stdout);
|
|
79
|
+
const overall = overallMatch ? { ok: overallMatch[2].toUpperCase() === 'PASS', icon: overallMatch[1] } : null;
|
|
80
|
+
|
|
81
|
+
const gates = [];
|
|
82
|
+
// Match lines like: "- ✅ **TPR improvement ≥ 25% vs fixed harness** — +150%"
|
|
83
|
+
const gateRx = /^-\s+([✅❌])\s+\*\*(.+?)\*\*\s*[—-]\s*(.+)$/gm;
|
|
84
|
+
let m;
|
|
85
|
+
while ((m = gateRx.exec(stdout)) !== null) {
|
|
86
|
+
gates.push({
|
|
87
|
+
ok: m[1] === '✅',
|
|
88
|
+
criterion: m[2].trim(),
|
|
89
|
+
measured: m[3].trim(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Extract the baselines-vs-champion table — useful for diff over time.
|
|
94
|
+
const baselines = [];
|
|
95
|
+
const tableRx = /^\|\s*B[0-3]\s+([^|]+?)\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|\s*(\d+)\s*\|\s*(.+?)\s*\|/gm;
|
|
96
|
+
while ((m = tableRx.exec(stdout)) !== null) {
|
|
97
|
+
baselines.push({
|
|
98
|
+
harness: m[1].trim(),
|
|
99
|
+
fitness: parseFloat(m[2]),
|
|
100
|
+
tpr: parseFloat(m[3]),
|
|
101
|
+
fpr: parseFloat(m[4]),
|
|
102
|
+
patchPass: parseFloat(m[5]),
|
|
103
|
+
repro: parseFloat(m[6]),
|
|
104
|
+
unsafe: parseInt(m[7], 10),
|
|
105
|
+
cost: m[8].trim(),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { overall, gates, baselines };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main() {
|
|
113
|
+
safetyChecks();
|
|
114
|
+
|
|
115
|
+
const cliArgs = ['security', 'bench',
|
|
116
|
+
'--population', String(ARGS.population),
|
|
117
|
+
'--cycles', String(ARGS.cycles),
|
|
118
|
+
];
|
|
119
|
+
if (ARGS.seed != null) cliArgs.push('--seed', String(ARGS.seed));
|
|
120
|
+
|
|
121
|
+
const r = await runDarwinAsync(cliArgs, {
|
|
122
|
+
timeoutMs: ARGS.timeoutMs ?? defaultTimeoutMs(),
|
|
123
|
+
json: false, // bench output is markdown, not JSON
|
|
124
|
+
onProgress: (line) => { if (line.trim()) process.stderr.write(`[security-bench] ${line}\n`); },
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (r.degraded) {
|
|
128
|
+
emitDarwinDegradedJsonAndExit(r.reason);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (r.exitCode !== 0 && r.exitCode !== 1) {
|
|
133
|
+
// Exit 1 may be the bench's own "gates failed" signal; treat as data.
|
|
134
|
+
// Anything else is infrastructure failure.
|
|
135
|
+
const payload = {
|
|
136
|
+
success: false,
|
|
137
|
+
data: { exitCode: r.exitCode, stderrTail: r.stderr.slice(-400) },
|
|
138
|
+
generatedAt: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
141
|
+
process.exit(2);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const parsed = parseSecurityBenchMarkdown(r.stdout);
|
|
145
|
+
const gatesPassed = parsed.gates.filter((g) => g.ok).length;
|
|
146
|
+
const gatesFailed = parsed.gates.length - gatesPassed;
|
|
147
|
+
|
|
148
|
+
const payload = {
|
|
149
|
+
success: true,
|
|
150
|
+
data: {
|
|
151
|
+
overall: parsed.overall,
|
|
152
|
+
gates: {
|
|
153
|
+
total: parsed.gates.length,
|
|
154
|
+
passed: gatesPassed,
|
|
155
|
+
failed: gatesFailed,
|
|
156
|
+
details: parsed.gates,
|
|
157
|
+
},
|
|
158
|
+
baselines: parsed.baselines,
|
|
159
|
+
rawMarkdown: r.stdout,
|
|
160
|
+
shape: { population: ARGS.population, cycles: ARGS.cycles, seed: ARGS.seed },
|
|
161
|
+
durationMs: r.durationMs,
|
|
162
|
+
},
|
|
163
|
+
generatedAt: new Date().toISOString(),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
167
|
+
if (ARGS.alertOnFail && parsed.overall && !parsed.overall.ok) process.exit(1);
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
main().catch((e) => {
|
|
172
|
+
console.error(`security-bench: unexpected failure: ${e?.message ?? e}`);
|
|
173
|
+
process.exit(2);
|
|
174
|
+
});
|
|
@@ -111,6 +111,14 @@ function main() {
|
|
|
111
111
|
{ name: 'mcp-scan', args: ['--format', 'json'] },
|
|
112
112
|
{ name: 'threat-model', args: ['--format', 'json'] },
|
|
113
113
|
{ name: 'oia-audit', args: ['--dry-run', '--format', 'json'] },
|
|
114
|
+
// ADR-153 — darwin scripts (separate optional dep @metaharness/darwin).
|
|
115
|
+
// evolve.mjs without --confirm short-circuits to a dry-run plan BEFORE
|
|
116
|
+
// any subprocess call, so it's not exercising the degraded path. Pass
|
|
117
|
+
// --confirm with --sandbox mock + minimal shape so we hit the subprocess
|
|
118
|
+
// path quickly and verify the {degraded: true} emit on registry-unreachable.
|
|
119
|
+
{ name: 'evolve', args: ['--repo', '.', '--confirm', '--sandbox', 'mock', '--generations', '1', '--children', '1', '--concurrency', '1', '--timeout-ms', '60000'] },
|
|
120
|
+
{ name: 'security-bench', args: ['--population', '1', '--cycles', '1', '--timeout-ms', '60000'] },
|
|
121
|
+
{ name: 'bench', args: ['--op', 'verify', '--suite', '/dev/null'] },
|
|
114
122
|
];
|
|
115
123
|
|
|
116
124
|
for (const s of skills) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-bench
|
|
3
|
+
description: Manage `@metaharness/darwin` bench suites — `bench create <repo>` scaffolds a JSON suite from a repo's test corpus; `bench verify <suite.json>` checks suite well-formedness. Bench suites are the fixed evaluation corpora that `harness-evolve --bench <suite.json>` scores variants against, decoupling evolution from the repo's natural tests. Degrades gracefully when @metaharness/darwin is absent.
|
|
4
|
+
argument-hint: "--op create --repo <path> [--out <path>] | --op verify --suite <path>"
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Surfaces `metaharness-darwin bench <create|verify>` — the supporting verb
|
|
9
|
+
for `harness-evolve --bench`. Use when you want evolution scored against a
|
|
10
|
+
fixed corpus (independent of `npm test`) so champion fitness is comparable
|
|
11
|
+
across commits or across forks of the same harness.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
- Setting up a new evolution pipeline for a repo whose `npm test` is
|
|
16
|
+
flaky, slow, or undersized — scaffold a deterministic bench suite once,
|
|
17
|
+
then evolve against it repeatedly.
|
|
18
|
+
- CI: `bench verify` the checked-in suite on every PR that touches it
|
|
19
|
+
(cheap; ~5s).
|
|
20
|
+
- Forking a harness to a new domain: copy and edit the suite to retarget
|
|
21
|
+
the evaluation without losing comparability to the parent.
|
|
22
|
+
|
|
23
|
+
## Algorithm
|
|
24
|
+
|
|
25
|
+
Implementation: [`scripts/bench.mjs`](../../scripts/bench.mjs).
|
|
26
|
+
|
|
27
|
+
### `--op create`
|
|
28
|
+
1. Resolve `--repo` path; reject if missing.
|
|
29
|
+
2. Shell to `metaharness-darwin bench create <repo> [--out <suite.json>]`.
|
|
30
|
+
3. Default output path: `<repo>/.metaharness/bench/suite.json` (chosen by upstream).
|
|
31
|
+
4. Suite shape (per upstream): array of `{ input, expectedOutput, weight }` tasks
|
|
32
|
+
derived from existing test cases.
|
|
33
|
+
|
|
34
|
+
### `--op verify`
|
|
35
|
+
1. Resolve `--suite` path; reject if missing.
|
|
36
|
+
2. Shell to `metaharness-darwin bench verify <suite.json>`.
|
|
37
|
+
3. Exit 1 if any task malformed (upstream's signal).
|
|
38
|
+
|
|
39
|
+
## Output shape
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"success": true,
|
|
44
|
+
"data": {
|
|
45
|
+
"op": "verify",
|
|
46
|
+
"taskCount": 42,
|
|
47
|
+
"wellFormed": true,
|
|
48
|
+
"durationMs": 870
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Exit codes
|
|
54
|
+
|
|
55
|
+
| Code | Meaning |
|
|
56
|
+
|---|---|
|
|
57
|
+
| 0 | OK (or degraded — Darwin absent) |
|
|
58
|
+
| 1 | `--op verify` and suite malformed |
|
|
59
|
+
| 2 | Config error or upstream invocation failure |
|
|
60
|
+
|
|
61
|
+
## Graceful degradation
|
|
62
|
+
|
|
63
|
+
When `@metaharness/darwin` is absent, emits the standard `{degraded: true,
|
|
64
|
+
reason: 'metaharness-darwin-not-available'}` payload and exits 0.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-evolve
|
|
3
|
+
description: Run `@metaharness/darwin evolve <repo>` to mutate a harness's seven policy surfaces (planner/contextBuilder/reviewer/retryPolicy/toolPolicy/memoryPolicy/scorePolicy), sandbox-score each variant, and promote only measured wins. The model is frozen; the harness evolves. Closes the loop ADR-150 opens (score+genome describe; evolve changes). Degrades gracefully when @metaharness/darwin is absent (ADR-150 + ADR-153 architectural constraints).
|
|
4
|
+
argument-hint: "--repo <path> [--generations 3] [--children 3] [--concurrency 2] [--sandbox real|mock|agent] [--selection pareto|quality-diversity|...] [--mutator deterministic|ruvllm] [--confirm]"
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Surfaces the upstream `metaharness-darwin evolve` CLI as a ruflo skill. The
|
|
9
|
+
**write** layer that pairs with ADR-150's read layer (score / genome /
|
|
10
|
+
mcp-scan / threat-model / oia-audit). Use when you have a harness whose
|
|
11
|
+
readiness scores are flat and you want to discover *which* surface mutation
|
|
12
|
+
moves them — without retraining the foundation model.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- A `harness-score` result is below target and you don't know which policy
|
|
17
|
+
surface is responsible.
|
|
18
|
+
- You're seeding a harness for a new vertical and want to find a good
|
|
19
|
+
starting configuration empirically rather than hand-tuning.
|
|
20
|
+
- You're comparing your hand-tuned harness against an evolved baseline
|
|
21
|
+
(treat darwin's champion as the strawman).
|
|
22
|
+
|
|
23
|
+
## When NOT to use
|
|
24
|
+
|
|
25
|
+
- For continuous background optimization. Darwin Mode is human-initiated.
|
|
26
|
+
Wire it into CI for one-shot exploration, not for autonomous self-modification.
|
|
27
|
+
- For ruflo itself in CI. ADR-153 §5 explicitly rejects auto-evolving ruflo
|
|
28
|
+
— the CI gate verifies graceful degradation, not convergence.
|
|
29
|
+
|
|
30
|
+
## Algorithm
|
|
31
|
+
|
|
32
|
+
Implementation: [`scripts/evolve.mjs`](../../scripts/evolve.mjs).
|
|
33
|
+
|
|
34
|
+
1. Validate args (`--repo` exists, caps on `--generations` ≤ 50, `--children`
|
|
35
|
+
≤ 20, `--concurrency` ≤ 8, sandbox/selection/mutator are known values).
|
|
36
|
+
2. Without `--confirm`: print plan + exit 0 (mirrors `harness-mint` safety
|
|
37
|
+
convention; defense in depth over the upstream `safety.ts` checks).
|
|
38
|
+
3. With `--confirm`: shell to `npx -y @metaharness/darwin@~0.3.1 metaharness-darwin evolve <repo> ...`
|
|
39
|
+
via the shared `_darwin.mjs` async helper. Per-generation progress is
|
|
40
|
+
forwarded to stderr; final champion JSON is captured from stdout.
|
|
41
|
+
4. Compute timeout from `generations × children × per-variant` (per-variant
|
|
42
|
+
≈ 60s real, ≈ 2s mock). Caller may override with `--timeout-ms`.
|
|
43
|
+
5. Honor upstream exit code 99 — propagate as "safety-disqualified", do not
|
|
44
|
+
remap. This is a designed-in tripwire (a variant tripped `inspectVariant`
|
|
45
|
+
for secrets / shell-out / network / dynamic-eval). See ADR-153 §"Safety model".
|
|
46
|
+
6. Optional `--alert-on-no-improvement`: exit 1 when champion ≤ parent.
|
|
47
|
+
|
|
48
|
+
## The seven mutation surfaces
|
|
49
|
+
|
|
50
|
+
| Surface | What it owns |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `planner` | task decomposition / step ordering |
|
|
53
|
+
| `contextBuilder` | what gets fed into the prompt |
|
|
54
|
+
| `reviewer` | self-critique / output verification |
|
|
55
|
+
| `retryPolicy` | when + how to retry on failure |
|
|
56
|
+
| `toolPolicy` | which tools the agent may use, under which conditions |
|
|
57
|
+
| `memoryPolicy` | what to persist, recall, forget |
|
|
58
|
+
| `scorePolicy` | how the agent grades its own output |
|
|
59
|
+
|
|
60
|
+
One mutation per variant. Multi-surface mutations are not allowed (causal
|
|
61
|
+
attribution stays clean).
|
|
62
|
+
|
|
63
|
+
## Output
|
|
64
|
+
|
|
65
|
+
Reports land under `<repo>/.metaharness/`:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
.metaharness/
|
|
69
|
+
archive.json # full lineage tree (sampling next gen draws from this)
|
|
70
|
+
lineage.json # parent→child edges only
|
|
71
|
+
variants/<id>/ # per-variant code (kept for audit)
|
|
72
|
+
runs/<id>/ # per-variant sandbox test output
|
|
73
|
+
reports/winner.json # final champion + score delta vs parent
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Skill stdout = JSON `{success, data: {champion, plan, durationMs, improved}}`.
|
|
77
|
+
|
|
78
|
+
## Exit codes
|
|
79
|
+
|
|
80
|
+
| Code | Meaning |
|
|
81
|
+
|---|---|
|
|
82
|
+
| 0 | Evolved OK, or dry-run, or degraded (Darwin absent) |
|
|
83
|
+
| 1 | `--alert-on-no-improvement` and champion did not beat parent |
|
|
84
|
+
| 2 | Config error or evolution infrastructure failure |
|
|
85
|
+
| 99 | Upstream "safety-disqualified" (PROPAGATED, not remapped) |
|
|
86
|
+
|
|
87
|
+
## Graceful degradation (ADR-150 constraint 3 + ADR-153)
|
|
88
|
+
|
|
89
|
+
When `@metaharness/darwin` is not installed, the script emits
|
|
90
|
+
`{degraded: true, reason: 'metaharness-darwin-not-available', hint: ...}`
|
|
91
|
+
and exits 0. ruflo continues to function. CI's
|
|
92
|
+
`no-metaharness-smoke.yml`-style job asserts this path.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness-security-bench
|
|
3
|
+
description: Run `@metaharness/darwin security bench` (upstream "Darwin Shield" / ADR-155) — evolves a champion security-detection harness against a 10-vuln / 9-decoy corpus and grades it on TPR/FPR/patch-pass/repro/unsafe vs four baselines (B0 static, B1 LLM-single-pass, B2 fixed-agent, B3 Darwin-champion). Closest reference implementation for ruflo's own ADR-155 nightly self-learning security harness (PR #2417). Degrades gracefully when @metaharness/darwin is absent.
|
|
4
|
+
argument-hint: "[--population 2] [--cycles 1] [--seed N] [--alert-on-fail]"
|
|
5
|
+
allowed-tools: Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Surfaces the upstream `metaharness-darwin security bench` command. **This is
|
|
9
|
+
the upstream's own ADR-155 — Darwin Shield — and is the closest reference
|
|
10
|
+
implementation for ruflo's nightly self-learning security harness ([#2417](https://github.com/ruvnet/ruflo/pull/2417)).**
|
|
11
|
+
|
|
12
|
+
## Why this matters for ruflo's ADR-155
|
|
13
|
+
|
|
14
|
+
ruflo's ADR-155 proposes three learning loops (per-dimension confidence,
|
|
15
|
+
severity calibration, auto-fix bid). Loop A trains on accumulated
|
|
16
|
+
`(finding, dimension, human_outcome)` tuples — but the gradient signal is
|
|
17
|
+
only sound if the underlying detection mechanism converges on a known-good
|
|
18
|
+
corpus. Darwin Shield evolves exactly that mechanism on a 10-vuln/9-decoy
|
|
19
|
+
ground-truth set. Running this nightly gives us:
|
|
20
|
+
|
|
21
|
+
- **Empirical floor:** if Darwin Shield's champion can't reach
|
|
22
|
+
TPR=1/FPR=0 on the bench corpus, our Loop A's reward signal is noise.
|
|
23
|
+
- **Drift detection:** week-over-week champion fitness deltas surface
|
|
24
|
+
when the security landscape (or our mutator policy) shifts.
|
|
25
|
+
- **Baseline diversity:** the 4 baselines (B0–B3) give us 4 anchor
|
|
26
|
+
points to weight per-dimension confidence against.
|
|
27
|
+
|
|
28
|
+
## Algorithm
|
|
29
|
+
|
|
30
|
+
Implementation: [`scripts/security-bench.mjs`](../../scripts/security-bench.mjs).
|
|
31
|
+
|
|
32
|
+
1. Shell to `npx -y @metaharness/darwin@~0.3.1 metaharness-darwin security bench --population N --cycles N [--seed S]`.
|
|
33
|
+
2. Default timeout = `3s × 19 evaluations × population × cycles + 30s overhead`.
|
|
34
|
+
At default `--population 2 --cycles 1` ≈ 144s; at `--population 4 --cycles 3` ≈ 12 min.
|
|
35
|
+
3. Parse the markdown report — overall PASS/FAIL plus per-gate
|
|
36
|
+
pass/fail rows (gate examples: "TPR improvement ≥ 25% vs fixed",
|
|
37
|
+
"FPR reduction ≥ 40%", "Patch-test pass rate ≥ 80%", "Reproduction
|
|
38
|
+
success ≥ 90%", "Unsafe outputs = 0", "Cost increase ≤ 2× fixed",
|
|
39
|
+
"Beyond SOTA: champion statistically beats previous champion",
|
|
40
|
+
"Compounding: false-positive repeat-rate drop ≥ 35%").
|
|
41
|
+
4. Parse the baselines-vs-champion table (4 rows: fitness/TPR/FPR/patchPass/
|
|
42
|
+
repro/unsafe/cost per harness).
|
|
43
|
+
5. Emit structured JSON. With `--alert-on-fail`, exit 1 when overall = FAIL.
|
|
44
|
+
|
|
45
|
+
## Output shape
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"success": true,
|
|
50
|
+
"data": {
|
|
51
|
+
"overall": { "ok": true, "icon": "✅" },
|
|
52
|
+
"gates": {
|
|
53
|
+
"total": 11,
|
|
54
|
+
"passed": 11,
|
|
55
|
+
"failed": 0,
|
|
56
|
+
"details": [{ "ok": true, "criterion": "TPR improvement ≥ 25% vs fixed harness", "measured": "+150% (B2 0.4 → B3 1)" }, ...]
|
|
57
|
+
},
|
|
58
|
+
"baselines": [
|
|
59
|
+
{ "harness": "static-only", "fitness": 0.5665, "tpr": 0.3, "fpr": 1, "unsafe": 0, ... },
|
|
60
|
+
{ "harness": "LLM single-pass", "fitness": 0.1365, ... },
|
|
61
|
+
{ "harness": "fixed agent", "fitness": 0.598, ... },
|
|
62
|
+
{ "harness": "Darwin champion", "fitness": 0.93275, "tpr": 1, "fpr": 0, ... }
|
|
63
|
+
],
|
|
64
|
+
"rawMarkdown": "...",
|
|
65
|
+
"shape": { "population": 2, "cycles": 1, "seed": null },
|
|
66
|
+
"durationMs": 142000
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Wiring into ADR-155 nightly harness
|
|
72
|
+
|
|
73
|
+
The ADR-155 nightly workflow (per #2418 task `W1.5`) will spawn this as
|
|
74
|
+
one of the active-pentest dimension's calls — its results become a
|
|
75
|
+
trajectory record:
|
|
76
|
+
|
|
77
|
+
```jsonc
|
|
78
|
+
{
|
|
79
|
+
"dimension": "mcp-pentest",
|
|
80
|
+
"subdimension": "darwin-shield-bench",
|
|
81
|
+
"champion_fitness": 0.93275,
|
|
82
|
+
"champion_tpr": 1, "champion_fpr": 0,
|
|
83
|
+
"gates_passed": 11, "gates_failed": 0,
|
|
84
|
+
"shape": { "population": 4, "cycles": 3 }
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Loop A learns: if `darwin-shield-bench` consistently passes on the seeded
|
|
89
|
+
corpus, weight findings caught only by `mcp-pentest` higher.
|
|
90
|
+
|
|
91
|
+
## Exit codes
|
|
92
|
+
|
|
93
|
+
| Code | Meaning |
|
|
94
|
+
|---|---|
|
|
95
|
+
| 0 | Bench ran (overall PASS or FAIL — distinguish via JSON `overall.ok`), or degraded |
|
|
96
|
+
| 1 | `--alert-on-fail` and `overall.ok === false` |
|
|
97
|
+
| 2 | Config error or upstream infrastructure failure |
|
|
98
|
+
|
|
99
|
+
## Graceful degradation
|
|
100
|
+
|
|
101
|
+
When `@metaharness/darwin` is absent, emits `{degraded: true, reason: 'metaharness-darwin-not-available'}` and exits 0.
|