mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-09-14 14:01:28 +08:00
a4c2755c8d
ADR-150 Phase 1 MVP shipped. Adds plugins/ruflo-metaharness/ with the five most-leverage skills from the integration matrix: harness-score — 5-dim readiness scorecard harness-genome — 7-section categorical report harness-mcp-scan — static MCP security findings harness-threat-model — enterprise-grade threat model harness-mint — scaffold custom harness (DRY-RUN by default) ARCHITECTURE - Every skill shells out to `npx metaharness ...` / `npx -p metaharness harness ...` via the `_harness.mjs` shared helper. NO library imports on ruflo's boot path. 60s hard timeout per subprocess. - `_harness.mjs` is the single bridge — every skill imports from it. Centralizes spawnSync, timeout handling, JSON parse, and graceful degradation (`emitDegradedJsonAndExit`). - Graceful degradation per ADR-150 architectural constraint rule #3: when `npx` can't fetch metaharness (offline, no network, unreachable registry), each script emits `{degraded:true, reason:'metaharness- not-available', hint:...}` and exits 0. Ruflo continues to function. PHASE-0 BASELINE CAPTURED (ruflo's own scorecard, 2026-06-16): harnessFit 82/100 compileConfidence 100 taskCoverage 79 toolSafety 100 memoryUsefulness 40 ← weakest dimension, track over time estCostPerRunUsd $0.048 recommendedMode CLI + MCP archetype typescript-sdk-harness template vertical:coding scaffoldReady true risk_score 0.27 ← low publish_readiness 0.9 SAFETY (harness-mint) - DRY-RUN by default; --confirm required to actually write - Refuses to write to project root or any path inside the calling repo - Defaults --target to /tmp/ruflo-mint-<ts>-<name>/ - Refuses existing target dirs (no overwrites) DEPENDENCIES (ADR-150 constraint rule #2: optional only, never required) - `metaharness@~0.1.11` added to @claude-flow/cli optionalDependencies - `@metaharness/router` re-pinned from `^0.3.2` to `~0.3.2` (tilde, not caret) per ADR-150 review-round-1 — the upstream had 5 releases in 2.7h on 2026-06-15, the caret was too permissive SMOKE - 14 structural invariants in plugins/ruflo-metaharness/scripts/smoke.sh - Pin: graceful degradation must be present in _harness.mjs (rule #3) - Pin: every script must use the shared _harness.mjs (not duplicate spawnSync — single audit point) - Pin: mint.mjs MUST contain the "refusing to write to project root" string (safety regression guard) - Pin: no SKILL.md grants wildcard tools (security) FLEET - 32 → 33 plugins, 402 → 422 structural steps passing - All three audits (exit-bypass, frontmatter, manifest) green - Five new MCP-tool exposures live via the plugin Co-Authored-By: RuFlo <ruv@ruv.net>
93 lines
3.6 KiB
JavaScript
Executable File
93 lines
3.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
// score.mjs — `cost-style` wrapper around `metaharness score <path>`.
|
|
//
|
|
// Returns the 5-dimension scorecard (harnessFit / compileConfidence /
|
|
// taskCoverage / toolSafety / memoryUsefulness) + estCostPerRunUsd +
|
|
// scaffoldReady boolean. Reads-only; no side effects.
|
|
//
|
|
// USAGE
|
|
// node scripts/score.mjs # current dir
|
|
// node scripts/score.mjs --path <dir> # specific dir
|
|
// node scripts/score.mjs --alert-on-fit-below 70 # exit 1 if harnessFit < 70
|
|
// node scripts/score.mjs --format json
|
|
//
|
|
// EXIT CODES
|
|
// 0 scored OK (or degraded — MetaHarness not available)
|
|
// 1 --alert-on-fit-below threshold breached
|
|
// 2 config error or scoring failure
|
|
|
|
import { runMetaharness, emitDegradedJsonAndExit } from './_harness.mjs';
|
|
|
|
const ARGS = (() => {
|
|
const a = { path: '.', format: 'json', alertFitBelow: null };
|
|
for (let i = 2; i < process.argv.length; i++) {
|
|
const v = process.argv[i];
|
|
if (v === '--path') a.path = process.argv[++i];
|
|
else if (v === '--alert-on-fit-below') a.alertFitBelow = parseFloat(process.argv[++i]);
|
|
else if (v === '--format') a.format = process.argv[++i];
|
|
}
|
|
return a;
|
|
})();
|
|
|
|
function main() {
|
|
const r = runMetaharness(['score', ARGS.path]);
|
|
if (r.degraded) {
|
|
emitDegradedJsonAndExit(r.reason);
|
|
return;
|
|
}
|
|
if (r.exitCode !== 0 || !r.json) {
|
|
console.error(`score: metaharness exited ${r.exitCode}`);
|
|
if (r.stderr) console.error(r.stderr.slice(0, 400));
|
|
process.exit(2);
|
|
}
|
|
const payload = { ...r.json, durationMs: r.durationMs };
|
|
|
|
if (ARGS.alertFitBelow !== null) {
|
|
if (!isFinite(ARGS.alertFitBelow)) {
|
|
console.error(`score: --alert-on-fit-below must be a finite number`);
|
|
process.exit(2);
|
|
}
|
|
payload.alert = {
|
|
threshold: ARGS.alertFitBelow,
|
|
triggered: typeof payload.harnessFit === 'number' && payload.harnessFit < ARGS.alertFitBelow,
|
|
reason: typeof payload.harnessFit === 'number' && payload.harnessFit < ARGS.alertFitBelow
|
|
? `harnessFit ${payload.harnessFit} < ${ARGS.alertFitBelow}`
|
|
: `harnessFit ${payload.harnessFit ?? 'unknown'} ≥ ${ARGS.alertFitBelow} — OK`,
|
|
};
|
|
}
|
|
|
|
if (ARGS.format === 'json') {
|
|
console.log(JSON.stringify(payload, null, 2));
|
|
} else {
|
|
console.log(`# harness-score — ${payload.repo || ARGS.path}`);
|
|
console.log('');
|
|
console.log(`| Dimension | Value |`);
|
|
console.log(`|---|---:|`);
|
|
console.log(`| harnessFit | ${payload.harnessFit ?? '—'} |`);
|
|
console.log(`| compileConfidence | ${payload.compileConfidence ?? '—'} |`);
|
|
console.log(`| taskCoverage | ${payload.taskCoverage ?? '—'} |`);
|
|
console.log(`| toolSafety | ${payload.toolSafety ?? '—'} |`);
|
|
console.log(`| memoryUsefulness | ${payload.memoryUsefulness ?? '—'} |`);
|
|
console.log(`| estCostPerRunUsd | $${payload.estCostPerRunUsd ?? '—'} |`);
|
|
console.log(`| recommendedMode | ${payload.recommendedMode ?? '—'} |`);
|
|
console.log(`| archetype | ${payload.archetype ?? '—'} |`);
|
|
console.log(`| template | ${payload.template ?? '—'} |`);
|
|
console.log(`| scaffoldReady | ${payload.scaffoldReady ?? '—'} |`);
|
|
console.log(`| hardConstraints | ${payload.hardConstraints ?? '—'} |`);
|
|
console.log(`| **duration** | ${payload.durationMs}ms |`);
|
|
console.log('');
|
|
if (payload.alert) {
|
|
if (payload.alert.triggered) {
|
|
console.log(`⚠ **ALERT**: ${payload.alert.reason}`);
|
|
} else {
|
|
console.log(`✓ ${payload.alert.reason}`);
|
|
}
|
|
console.log('');
|
|
}
|
|
}
|
|
|
|
if (payload.alert?.triggered) process.exit(1);
|
|
}
|
|
|
|
main();
|