@claude-flow/cli 3.11.0 → 3.12.1
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/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +175 -1
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/eject.d.ts +33 -0
- package/dist/src/commands/eject.d.ts.map +1 -0
- package/dist/src/commands/eject.js +195 -0
- package/dist/src/commands/eject.js.map +1 -0
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +2 -1
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +4 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +3 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/metaharness.d.ts +39 -0
- package/dist/src/commands/metaharness.d.ts.map +1 -0
- package/dist/src/commands/metaharness.js +194 -0
- package/dist/src/commands/metaharness.js.map +1 -0
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +4 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-tools/index.d.ts +1 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +2 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/mcp-tools/metaharness-tools.d.ts +31 -0
- package/dist/src/mcp-tools/metaharness-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/metaharness-tools.js +372 -0
- package/dist/src/mcp-tools/metaharness-tools.js.map +1 -0
- package/dist/src/plugins/store/discovery.d.ts.map +1 -1
- package/dist/src/plugins/store/discovery.js +3 -0
- package/dist/src/plugins/store/discovery.js.map +1 -1
- package/dist/src/plugins/store/types.d.ts +6 -1
- package/dist/src/plugins/store/types.d.ts.map +1 -1
- package/dist/src/ruvector/model-router.d.ts.map +1 -1
- package/dist/src/ruvector/model-router.js +37 -0
- package/dist/src/ruvector/model-router.js.map +1 -1
- package/dist/src/ruvector/router-parallel-recorder.d.ts +127 -0
- package/dist/src/ruvector/router-parallel-recorder.d.ts.map +1 -0
- package/dist/src/ruvector/router-parallel-recorder.js +183 -0
- package/dist/src/ruvector/router-parallel-recorder.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -10
- package/plugins/ruflo-metaharness/.claude-flow/data/pending-insights.jsonl +1 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +6 -0
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +32 -0
- package/plugins/ruflo-metaharness/README.md +64 -0
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +58 -0
- package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +46 -0
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +261 -0
- package/plugins/ruflo-metaharness/scripts/_similarity.mjs +161 -0
- package/plugins/ruflo-metaharness/scripts/_spike-similarity.mjs +223 -0
- package/plugins/ruflo-metaharness/scripts/audit-list.mjs +158 -0
- package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +272 -0
- package/plugins/ruflo-metaharness/scripts/bench-parse-mcp-scan.mjs +146 -0
- package/plugins/ruflo-metaharness/scripts/bench-recordpair-overhead.mjs +186 -0
- package/plugins/ruflo-metaharness/scripts/bench-similarity.mjs +177 -0
- package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +363 -0
- package/plugins/ruflo-metaharness/scripts/genome.mjs +80 -0
- package/plugins/ruflo-metaharness/scripts/mcp-scan.mjs +111 -0
- package/plugins/ruflo-metaharness/scripts/mint.mjs +119 -0
- package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +228 -0
- package/plugins/ruflo-metaharness/scripts/router-parallel-analyze.mjs +250 -0
- package/plugins/ruflo-metaharness/scripts/score.mjs +92 -0
- package/plugins/ruflo-metaharness/scripts/similarity.mjs +158 -0
- package/plugins/ruflo-metaharness/scripts/smoke.sh +2268 -0
- package/plugins/ruflo-metaharness/scripts/test-graceful-degradation.mjs +141 -0
- package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +437 -0
- package/plugins/ruflo-metaharness/scripts/test-parallel-pipeline.mjs +204 -0
- package/plugins/ruflo-metaharness/scripts/test-pipeline-roundtrip.mjs +586 -0
- package/plugins/ruflo-metaharness/scripts/test-similarity.mjs +334 -0
- package/plugins/ruflo-metaharness/scripts/test-with-openrouter.mjs +229 -0
- package/plugins/ruflo-metaharness/scripts/threat-model.mjs +59 -0
- package/plugins/ruflo-metaharness/skills/harness-drift-from-history/SKILL.md +65 -0
- package/plugins/ruflo-metaharness/skills/harness-genome/SKILL.md +54 -0
- package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +47 -0
- package/plugins/ruflo-metaharness/skills/harness-mint/SKILL.md +72 -0
- package/plugins/ruflo-metaharness/skills/harness-oia-audit/SKILL.md +79 -0
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +66 -0
- package/plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md +67 -0
- package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +39 -0
- package/dist/src/benchmarks/capability-tasks.d.ts +0 -126
- package/dist/src/benchmarks/capability-tasks.d.ts.map +0 -1
- package/dist/src/benchmarks/capability-tasks.js +0 -147
- package/dist/src/benchmarks/capability-tasks.js.map +0 -1
- package/dist/src/benchmarks/gaia-agent-contrastive.d.ts +0 -53
- package/dist/src/benchmarks/gaia-agent-contrastive.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-agent-contrastive.js +0 -177
- package/dist/src/benchmarks/gaia-agent-contrastive.js.map +0 -1
- package/dist/src/benchmarks/gaia-agent-gemini.d.ts +0 -51
- package/dist/src/benchmarks/gaia-agent-gemini.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-agent-gemini.js +0 -352
- package/dist/src/benchmarks/gaia-agent-gemini.js.map +0 -1
- package/dist/src/benchmarks/gaia-claude-p.d.ts +0 -105
- package/dist/src/benchmarks/gaia-claude-p.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-claude-p.js +0 -269
- package/dist/src/benchmarks/gaia-claude-p.js.map +0 -1
- package/dist/src/benchmarks/gaia-claude-p.smoke.d.ts +0 -20
- package/dist/src/benchmarks/gaia-claude-p.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-claude-p.smoke.js +0 -168
- package/dist/src/benchmarks/gaia-claude-p.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-codeagent-runner.py +0 -556
- package/dist/src/benchmarks/gaia-codeagent.d.ts +0 -129
- package/dist/src/benchmarks/gaia-codeagent.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-codeagent.js +0 -590
- package/dist/src/benchmarks/gaia-codeagent.js.map +0 -1
- package/dist/src/benchmarks/gaia-codeagent.smoke.d.ts +0 -33
- package/dist/src/benchmarks/gaia-codeagent.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-codeagent.smoke.js +0 -241
- package/dist/src/benchmarks/gaia-codeagent.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-dag-pilot.d.ts +0 -22
- package/dist/src/benchmarks/gaia-dag-pilot.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-dag-pilot.js +0 -158
- package/dist/src/benchmarks/gaia-dag-pilot.js.map +0 -1
- package/dist/src/benchmarks/gaia-dag.d.ts +0 -110
- package/dist/src/benchmarks/gaia-dag.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-dag.js +0 -658
- package/dist/src/benchmarks/gaia-dag.js.map +0 -1
- package/dist/src/benchmarks/gaia-dag.smoke.d.ts +0 -21
- package/dist/src/benchmarks/gaia-dag.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-dag.smoke.js +0 -290
- package/dist/src/benchmarks/gaia-dag.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-ensemble-pilot.d.ts +0 -17
- package/dist/src/benchmarks/gaia-ensemble-pilot.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-ensemble-pilot.js +0 -106
- package/dist/src/benchmarks/gaia-ensemble-pilot.js.map +0 -1
- package/dist/src/benchmarks/gaia-ensemble.d.ts +0 -103
- package/dist/src/benchmarks/gaia-ensemble.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-ensemble.js +0 -462
- package/dist/src/benchmarks/gaia-ensemble.js.map +0 -1
- package/dist/src/benchmarks/gaia-gemini-pilot.d.ts +0 -17
- package/dist/src/benchmarks/gaia-gemini-pilot.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-gemini-pilot.js +0 -168
- package/dist/src/benchmarks/gaia-gemini-pilot.js.map +0 -1
- package/dist/src/benchmarks/gaia-mode-router.d.ts +0 -67
- package/dist/src/benchmarks/gaia-mode-router.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-mode-router.js +0 -126
- package/dist/src/benchmarks/gaia-mode-router.js.map +0 -1
- package/dist/src/benchmarks/gaia-mode-router.smoke.d.ts +0 -15
- package/dist/src/benchmarks/gaia-mode-router.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-mode-router.smoke.js +0 -131
- package/dist/src/benchmarks/gaia-mode-router.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.d.ts +0 -24
- package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.js +0 -276
- package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/image_describe.d.ts +0 -45
- package/dist/src/benchmarks/gaia-tools/image_describe.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/image_describe.js +0 -222
- package/dist/src/benchmarks/gaia-tools/image_describe.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/image_describe.smoke.d.ts +0 -24
- package/dist/src/benchmarks/gaia-tools/image_describe.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/image_describe.smoke.js +0 -169
- package/dist/src/benchmarks/gaia-tools/image_describe.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/pdf_read.d.ts +0 -27
- package/dist/src/benchmarks/gaia-tools/pdf_read.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/pdf_read.js +0 -226
- package/dist/src/benchmarks/gaia-tools/pdf_read.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/python_exec.d.ts +0 -31
- package/dist/src/benchmarks/gaia-tools/python_exec.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/python_exec.js +0 -94
- package/dist/src/benchmarks/gaia-tools/python_exec.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/python_exec.smoke.d.ts +0 -13
- package/dist/src/benchmarks/gaia-tools/python_exec.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/python_exec.smoke.js +0 -98
- package/dist/src/benchmarks/gaia-tools/python_exec.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/visit_webpage.d.ts +0 -52
- package/dist/src/benchmarks/gaia-tools/visit_webpage.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/visit_webpage.js +0 -304
- package/dist/src/benchmarks/gaia-tools/visit_webpage.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/web_browse.d.ts +0 -61
- package/dist/src/benchmarks/gaia-tools/web_browse.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/web_browse.js +0 -217
- package/dist/src/benchmarks/gaia-tools/web_browse.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/web_browse.smoke.d.ts +0 -26
- package/dist/src/benchmarks/gaia-tools/web_browse.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/web_browse.smoke.js +0 -152
- package/dist/src/benchmarks/gaia-tools/web_browse.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-tools/web_search.smoke.d.ts +0 -24
- package/dist/src/benchmarks/gaia-tools/web_search.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-tools/web_search.smoke.js +0 -311
- package/dist/src/benchmarks/gaia-tools/web_search.smoke.js.map +0 -1
- package/dist/src/benchmarks/gaia-visit-webpage.smoke.d.ts +0 -25
- package/dist/src/benchmarks/gaia-visit-webpage.smoke.d.ts.map +0 -1
- package/dist/src/benchmarks/gaia-visit-webpage.smoke.js +0 -304
- package/dist/src/benchmarks/gaia-visit-webpage.smoke.js.map +0 -1
- package/dist/src/commands/performance-capability.d.ts +0 -30
- package/dist/src/commands/performance-capability.d.ts.map +0 -1
- package/dist/src/commands/performance-capability.js +0 -349
- package/dist/src/commands/performance-capability.js.map +0 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bench-similarity.mjs — micro-benchmark for ADR-152 §3.1's production
|
|
3
|
+
// similarity() function. Establishes the per-call cost budget that
|
|
4
|
+
// future Phase-3 consumers (§3.2 Recommender N×M ranking, §3.3 Drift
|
|
5
|
+
// fleet-wide scan, §3.4 Capability graph traversal) inherit.
|
|
6
|
+
//
|
|
7
|
+
// THE CLAIM (iter 41 baseline)
|
|
8
|
+
// similarity(a, b) is a pure 9-dim cosine + 4-field categorical +
|
|
9
|
+
// set-jaccard composite — sub-microsecond per call on Apple Silicon /
|
|
10
|
+
// Node 22. Phase-3 consumers can therefore safely call it 10k+ times
|
|
11
|
+
// per request without sweating budget.
|
|
12
|
+
//
|
|
13
|
+
// WHAT IT MEASURES
|
|
14
|
+
// - Per-call mean + p50 + p99 over 1M iterations
|
|
15
|
+
// - Three input categories (cheap / typical / rich) to surface any
|
|
16
|
+
// payload-size sensitivity in projectToVec
|
|
17
|
+
//
|
|
18
|
+
// USAGE
|
|
19
|
+
// node scripts/bench-similarity.mjs # default 1M iters
|
|
20
|
+
// node scripts/bench-similarity.mjs --iters 5000000
|
|
21
|
+
// node scripts/bench-similarity.mjs --format json
|
|
22
|
+
// node scripts/bench-similarity.mjs --max-mean-us 10 # CI gate (exit 1 if mean > 10μs)
|
|
23
|
+
//
|
|
24
|
+
// EXIT CODES
|
|
25
|
+
// 0 ok (or --max-mean-us not set / threshold satisfied)
|
|
26
|
+
// 1 --max-mean-us threshold exceeded (regression)
|
|
27
|
+
|
|
28
|
+
import { performance } from 'node:perf_hooks';
|
|
29
|
+
import { similarity } from './_similarity.mjs';
|
|
30
|
+
|
|
31
|
+
const ARGS = (() => {
|
|
32
|
+
const a = {
|
|
33
|
+
iters: 1_000_000,
|
|
34
|
+
format: 'table',
|
|
35
|
+
// CI regression gate. When --max-mean-us N is set, exit 1 if any
|
|
36
|
+
// measured category's mean per-call cost exceeds N microseconds.
|
|
37
|
+
// Default ceiling 10μs chosen as ~5× headroom over Apple-Silicon
|
|
38
|
+
// baseline; works on slower CI runners.
|
|
39
|
+
maxMeanUs: null,
|
|
40
|
+
};
|
|
41
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
42
|
+
const v = process.argv[i];
|
|
43
|
+
if (v === '--iters') a.iters = parseInt(process.argv[++i], 10);
|
|
44
|
+
else if (v === '--format') a.format = process.argv[++i];
|
|
45
|
+
else if (v === '--max-mean-us') a.maxMeanUs = parseFloat(process.argv[++i]);
|
|
46
|
+
}
|
|
47
|
+
return a;
|
|
48
|
+
})();
|
|
49
|
+
|
|
50
|
+
// ───────────────────────────────────────────────────────────────────
|
|
51
|
+
// Three fixture categories
|
|
52
|
+
// ───────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
const CHEAP = {
|
|
55
|
+
// Bare-bones — everything defaulted from missing fields
|
|
56
|
+
score: { harnessFit: 50 },
|
|
57
|
+
genome: { agent_topology: ['a'] },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const TYPICAL = {
|
|
61
|
+
// The shape the iter-38 oia-audit fingerprint produces
|
|
62
|
+
score: {
|
|
63
|
+
harnessFit: 78, compileConfidence: 92, taskCoverage: 65,
|
|
64
|
+
toolSafety: 88, memoryUsefulness: 70, estCostPerRunUsd: 0.04,
|
|
65
|
+
recommendedMode: 'CLI + MCP', archetype: 'compliance-harness',
|
|
66
|
+
template: 'vertical:legal',
|
|
67
|
+
},
|
|
68
|
+
genome: {
|
|
69
|
+
repo_type: 'node_mcp_ci',
|
|
70
|
+
agent_topology: ['contract-analyst', 'redline-reviewer', 'risk-rater', 'compliance-officer'],
|
|
71
|
+
risk_score: 0.45, test_confidence: 0.7, publish_readiness: 0.6,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const RICH = {
|
|
76
|
+
// Larger agent_topology (Jaccard's variable-cost path)
|
|
77
|
+
score: TYPICAL.score,
|
|
78
|
+
genome: {
|
|
79
|
+
...TYPICAL.genome,
|
|
80
|
+
agent_topology: Array.from({ length: 32 }, (_, i) => `agent-${i}`),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ───────────────────────────────────────────────────────────────────
|
|
85
|
+
// Benchmark harness
|
|
86
|
+
// ───────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function bench(label, a, b, iters) {
|
|
89
|
+
// Warm-up
|
|
90
|
+
for (let i = 0; i < 10_000; i++) similarity(a, b);
|
|
91
|
+
|
|
92
|
+
const samples = new Float64Array(iters);
|
|
93
|
+
for (let i = 0; i < iters; i++) {
|
|
94
|
+
const t0 = performance.now();
|
|
95
|
+
similarity(a, b);
|
|
96
|
+
samples[i] = performance.now() - t0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Stats
|
|
100
|
+
let sum = 0;
|
|
101
|
+
for (let i = 0; i < iters; i++) sum += samples[i];
|
|
102
|
+
const mean = sum / iters;
|
|
103
|
+
|
|
104
|
+
const sorted = Array.from(samples).sort((x, y) => x - y);
|
|
105
|
+
const p50 = sorted[Math.floor(iters * 0.5)];
|
|
106
|
+
const p99 = sorted[Math.floor(iters * 0.99)];
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
label,
|
|
110
|
+
iters,
|
|
111
|
+
meanMs: mean,
|
|
112
|
+
p50Ms: p50,
|
|
113
|
+
p99Ms: p99,
|
|
114
|
+
meanUs: mean * 1000,
|
|
115
|
+
p99Us: p99 * 1000,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ───────────────────────────────────────────────────────────────────
|
|
120
|
+
// iter 87 — suppress markdown header when --format json so the file
|
|
121
|
+
// captured via `> /tmp/bench-similarity.json` is valid JSON. Iter 82's
|
|
122
|
+
// CI step JSON.parse'd the captured file but silently failed because
|
|
123
|
+
// the `# bench-similarity` header contaminated the input.
|
|
124
|
+
if (ARGS.format !== 'json') {
|
|
125
|
+
console.log(`# bench-similarity — ADR-152 §3.1 per-call cost\n`);
|
|
126
|
+
console.log(`iters: ${ARGS.iters.toLocaleString()}\n`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const results = [
|
|
130
|
+
bench('cheap', CHEAP, CHEAP, ARGS.iters),
|
|
131
|
+
bench('typical', TYPICAL, TYPICAL, ARGS.iters),
|
|
132
|
+
bench('rich (32 agents)', RICH, RICH, ARGS.iters),
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
let gate = { triggered: false, reasons: [] };
|
|
136
|
+
if (ARGS.maxMeanUs != null) {
|
|
137
|
+
for (const r of results) {
|
|
138
|
+
if (r.meanUs > ARGS.maxMeanUs) {
|
|
139
|
+
gate.triggered = true;
|
|
140
|
+
gate.reasons.push(`${r.label}: mean ${r.meanUs.toFixed(3)}μs > threshold ${ARGS.maxMeanUs}μs`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const payload = {
|
|
146
|
+
iters: ARGS.iters,
|
|
147
|
+
results,
|
|
148
|
+
gate: ARGS.maxMeanUs != null ? {
|
|
149
|
+
thresholdUs: ARGS.maxMeanUs,
|
|
150
|
+
triggered: gate.triggered,
|
|
151
|
+
reasons: gate.reasons,
|
|
152
|
+
} : null,
|
|
153
|
+
generatedAt: new Date().toISOString(),
|
|
154
|
+
// The performance contract — captured for /docs/benchmarks consumers
|
|
155
|
+
contract: 'similarity() is sub-microsecond on Apple Silicon / Node 22+; Phase-3 consumers may freely call O(N²) on N=1000 harnesses (~1s budget).',
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (ARGS.format === 'json') {
|
|
159
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`| Category | mean | p50 | p99 |`);
|
|
162
|
+
console.log(`|------------------|-----------:|-----------:|-----------:|`);
|
|
163
|
+
for (const r of results) {
|
|
164
|
+
console.log(`| ${r.label.padEnd(16)} | ${r.meanUs.toFixed(3).padStart(7)}μs | ${(r.p50Ms * 1000).toFixed(3).padStart(7)}μs | ${r.p99Us.toFixed(3).padStart(7)}μs |`);
|
|
165
|
+
}
|
|
166
|
+
console.log('');
|
|
167
|
+
if (payload.gate) {
|
|
168
|
+
if (payload.gate.triggered) {
|
|
169
|
+
console.log(`⚠ ALERT: mean per-call exceeded ${ARGS.maxMeanUs}μs ceiling:`);
|
|
170
|
+
for (const reason of payload.gate.reasons) console.log(` - ${reason}`);
|
|
171
|
+
} else {
|
|
172
|
+
console.log(`✓ all categories within --max-mean-us ${ARGS.maxMeanUs}μs ceiling`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (gate.triggered) process.exit(1);
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// drift-from-history.mjs — iter 53 — one-command drift detection.
|
|
3
|
+
//
|
|
4
|
+
// Composes the existing pipeline into the workflow users actually want:
|
|
5
|
+
// "did my repo drift since the last audit?" Implementation:
|
|
6
|
+
//
|
|
7
|
+
// 1. List audit records from `metaharness-audit` memory namespace
|
|
8
|
+
// 2. Pick the most recent one (or filter --since Nd to skip recent ones
|
|
9
|
+
// and use an older baseline)
|
|
10
|
+
// 3. Run a fresh oia-audit against the current repo state
|
|
11
|
+
// 4. Diff via audit-trend → structural distance + severity rollup
|
|
12
|
+
// 5. Alert if structural similarity falls below --threshold
|
|
13
|
+
//
|
|
14
|
+
// Until this iter, doing this required the user to:
|
|
15
|
+
// $ npx ruflo metaharness audit-list --format json
|
|
16
|
+
// $ # ... pick a key by hand from the listing
|
|
17
|
+
// $ npx ruflo metaharness oia-audit --format json > /tmp/curr.json
|
|
18
|
+
// $ npx ruflo metaharness audit-trend --baseline-key X --current-file /tmp/curr.json
|
|
19
|
+
// Now it's:
|
|
20
|
+
// $ npx ruflo metaharness drift-from-history --threshold 0.95
|
|
21
|
+
//
|
|
22
|
+
// USAGE
|
|
23
|
+
// node scripts/drift-from-history.mjs # default: last record vs now
|
|
24
|
+
// node scripts/drift-from-history.mjs --baseline-since 7d # use an audit ≥ 7d old
|
|
25
|
+
// node scripts/drift-from-history.mjs --threshold 0.95 # alert under 0.95 similarity
|
|
26
|
+
// node scripts/drift-from-history.mjs --format json
|
|
27
|
+
// node scripts/drift-from-history.mjs --dry-run # don't persist current
|
|
28
|
+
//
|
|
29
|
+
// EXIT CODES
|
|
30
|
+
// 0 ok (no drift below --threshold)
|
|
31
|
+
// 1 drift below --threshold (alert fired)
|
|
32
|
+
// 2 config / input error (no history available, etc.)
|
|
33
|
+
// 3 upstream metaharness absent — degraded payload returned
|
|
34
|
+
|
|
35
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
36
|
+
// iter 78 — share the iter-63 SEVERITY_RANK for the new finding-alert gate
|
|
37
|
+
import { rankSeverity } from './_harness.mjs';
|
|
38
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
39
|
+
import { tmpdir } from 'node:os';
|
|
40
|
+
import { join, dirname } from 'node:path';
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
|
|
43
|
+
const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
const NS = process.env.METAHARNESS_AUDIT_NAMESPACE || 'metaharness-audit';
|
|
45
|
+
const CLI_PKG = process.env.CLI_CORE === '1'
|
|
46
|
+
? '@claude-flow/cli-core@alpha'
|
|
47
|
+
: '@claude-flow/cli@latest';
|
|
48
|
+
|
|
49
|
+
const ARGS = (() => {
|
|
50
|
+
const a = {
|
|
51
|
+
path: '.', baselineSince: null, baselineKey: null, baselineFile: null,
|
|
52
|
+
threshold: 0.95, dryRun: false, format: 'table',
|
|
53
|
+
// iter 78 — opt-in finding-severity alert gate. Default null (off)
|
|
54
|
+
// preserves iter-53 behavior; set to a severity name to ALSO alert
|
|
55
|
+
// when any introduced finding meets or exceeds that severity.
|
|
56
|
+
alertOnNewSeverity: null,
|
|
57
|
+
};
|
|
58
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
59
|
+
const v = process.argv[i];
|
|
60
|
+
if (v === '--path') a.path = process.argv[++i];
|
|
61
|
+
else if (v === '--baseline-since') a.baselineSince = process.argv[++i];
|
|
62
|
+
// iter 66 — explicit baseline-key skips the ONNX-heavy audit-list
|
|
63
|
+
// call entirely. Cron jobs that know the key avoid the ~25s warmup.
|
|
64
|
+
else if (v === '--baseline-key') a.baselineKey = process.argv[++i];
|
|
65
|
+
// iter 67 — baseline-file goes further: no memory call at all.
|
|
66
|
+
// Useful for CI workflows that store last-run audit as an artifact
|
|
67
|
+
// and download it for diff against fresh audit.
|
|
68
|
+
else if (v === '--baseline-file') a.baselineFile = process.argv[++i];
|
|
69
|
+
else if (v === '--threshold') a.threshold = Number(process.argv[++i]);
|
|
70
|
+
else if (v === '--dry-run') a.dryRun = true;
|
|
71
|
+
else if (v === '--format') a.format = process.argv[++i];
|
|
72
|
+
// iter 78 — orthogonal alert: any new finding ≥ this severity triggers
|
|
73
|
+
else if (v === '--alert-on-new-severity') a.alertOnNewSeverity = String(process.argv[++i]).toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
return a;
|
|
76
|
+
})();
|
|
77
|
+
|
|
78
|
+
function emitAndExit(payload, code) {
|
|
79
|
+
if (ARGS.format === 'json') {
|
|
80
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
81
|
+
} else {
|
|
82
|
+
console.log(`# drift-from-history\n`);
|
|
83
|
+
if (payload.degraded) {
|
|
84
|
+
console.log(`⊘ ${payload.reason}`);
|
|
85
|
+
} else if (payload.error) {
|
|
86
|
+
console.log(`✗ ${payload.error}`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log(`Baseline: ${payload.baseline?.key ?? payload.baseline?.startedAt}`);
|
|
89
|
+
console.log(`Current: ${payload.current?.startedAt ?? '(fresh)'}`);
|
|
90
|
+
// iter 96 — surface the timing.path label so users see which
|
|
91
|
+
// fast-path ran (or didn't) at a glance.
|
|
92
|
+
if (payload.timing?.path) {
|
|
93
|
+
const wallMs = payload.timing.parallelWallMs;
|
|
94
|
+
console.log(`Path: ${payload.timing.path} (wall ${wallMs}ms)`);
|
|
95
|
+
}
|
|
96
|
+
console.log('');
|
|
97
|
+
const sd = payload.drift?.structuralDistance;
|
|
98
|
+
if (sd && sd.verdict !== 'unavailable') {
|
|
99
|
+
console.log(`Structural similarity: ${sd.overall} (${sd.verdict})`);
|
|
100
|
+
console.log(`Distance: ${sd.distance}`);
|
|
101
|
+
}
|
|
102
|
+
console.log('');
|
|
103
|
+
if (payload.alert?.triggered) {
|
|
104
|
+
console.log(`⚠ ALERT: ${payload.alert.reason}`);
|
|
105
|
+
} else if (payload.alert) {
|
|
106
|
+
console.log(`✓ ${payload.alert.reason}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
process.exit(code);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function runScriptJson(script, args) {
|
|
114
|
+
const r = spawnSync('node', [join(SCRIPTS_DIR, script), ...args, '--format', 'json'], {
|
|
115
|
+
encoding: 'utf-8',
|
|
116
|
+
});
|
|
117
|
+
const m = /\{[\s\S]*\}/.exec(r.stdout || '');
|
|
118
|
+
const json = m ? JSON.parse(m[0]) : null;
|
|
119
|
+
// audit-list emits {records:[...]} — check that shape too
|
|
120
|
+
const arrM = /\[[\s\S]*\]/.exec(r.stdout || '');
|
|
121
|
+
return { json, exitCode: r.status ?? -1, stdout: r.stdout || '', stderr: r.stderr || '', arrMatch: arrM };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* iter 58 — async variant. Drift-from-history's audit-list (memory query)
|
|
126
|
+
* and oia-audit (fresh subprocess chain) are mutually independent and
|
|
127
|
+
* can race. This shaves ~2-5s off every drift check by overlapping the
|
|
128
|
+
* subprocess wait time.
|
|
129
|
+
*/
|
|
130
|
+
function runScriptJsonAsync(script, args) {
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
const p = spawn('node', [join(SCRIPTS_DIR, script), ...args, '--format', 'json'], {
|
|
133
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
134
|
+
});
|
|
135
|
+
let stdout = '', stderr = '';
|
|
136
|
+
p.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
137
|
+
p.stderr?.on('data', (d) => { stderr += d.toString(); });
|
|
138
|
+
p.on('close', (code) => {
|
|
139
|
+
const m = /\{[\s\S]*\}/.exec(stdout);
|
|
140
|
+
let json = null;
|
|
141
|
+
if (m) { try { json = JSON.parse(m[0]); } catch { /* leave null */ } }
|
|
142
|
+
resolve({ json, exitCode: code ?? -1, stdout, stderr });
|
|
143
|
+
});
|
|
144
|
+
p.on('error', () => resolve({ json: null, exitCode: 127, stdout, stderr: 'spawn-failed' }));
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function main() {
|
|
149
|
+
// iter 58 — parallelize audit-list + oia-audit. They're mutually
|
|
150
|
+
// independent (audit-list queries memory, oia-audit runs fresh
|
|
151
|
+
// metaharness subprocesses) so racing them shaves ~2-5s off every
|
|
152
|
+
// drift check. The audit-trend step still serializes after because
|
|
153
|
+
// it consumes both prior outputs.
|
|
154
|
+
const listArgs = ['--limit', '50'];
|
|
155
|
+
if (ARGS.baselineSince) listArgs.push('--since', ARGS.baselineSince);
|
|
156
|
+
const auditArgs = ['--path', ARGS.path];
|
|
157
|
+
if (ARGS.dryRun) auditArgs.push('--dry-run');
|
|
158
|
+
|
|
159
|
+
// iter 65 — measure parallel batch wall-clock so a future iter that
|
|
160
|
+
// accidentally serializes (await audit-list; await oia-audit) doesn't
|
|
161
|
+
// silently regress. timing.parallelSpeedup is surfaced in payload.
|
|
162
|
+
// iter 66 — when --baseline-key is provided, skip audit-list entirely
|
|
163
|
+
// (it's ~25s of ONNX warmup for what would be one record lookup).
|
|
164
|
+
// Run oia-audit alone in that case.
|
|
165
|
+
const parallelStart = Date.now();
|
|
166
|
+
const listStart = Date.now();
|
|
167
|
+
const auditStart = Date.now();
|
|
168
|
+
let listResult;
|
|
169
|
+
let auditResult;
|
|
170
|
+
let skippedAuditList = false;
|
|
171
|
+
let usedBaselineFile = false;
|
|
172
|
+
if (ARGS.baselineFile) {
|
|
173
|
+
// iter 67 fast-fast path — baseline already on disk. Skip
|
|
174
|
+
// audit-list AND skip memory roundtrip when running audit-trend.
|
|
175
|
+
skippedAuditList = true;
|
|
176
|
+
usedBaselineFile = true;
|
|
177
|
+
listResult = {
|
|
178
|
+
json: { records: [{ key: `file:${ARGS.baselineFile}`, startedAt: null }] },
|
|
179
|
+
exitCode: 0, stdout: '', stderr: '', durationMs: 0,
|
|
180
|
+
};
|
|
181
|
+
auditResult = await runScriptJsonAsync('oia-audit.mjs', auditArgs);
|
|
182
|
+
auditResult.durationMs = Date.now() - auditStart;
|
|
183
|
+
} else if (ARGS.baselineKey) {
|
|
184
|
+
skippedAuditList = true;
|
|
185
|
+
// Synthesize a list result containing the user-provided key.
|
|
186
|
+
// No memory call needed — drift-from-history's downstream code
|
|
187
|
+
// only reads the `key` field from the picked record.
|
|
188
|
+
listResult = {
|
|
189
|
+
json: { records: [{ key: ARGS.baselineKey, startedAt: null }] },
|
|
190
|
+
exitCode: 0,
|
|
191
|
+
stdout: '',
|
|
192
|
+
stderr: '',
|
|
193
|
+
durationMs: 0,
|
|
194
|
+
};
|
|
195
|
+
auditResult = await runScriptJsonAsync('oia-audit.mjs', auditArgs);
|
|
196
|
+
auditResult.durationMs = Date.now() - auditStart;
|
|
197
|
+
} else {
|
|
198
|
+
[listResult, auditResult] = await Promise.all([
|
|
199
|
+
runScriptJsonAsync('audit-list.mjs', listArgs)
|
|
200
|
+
.then((r) => ({ ...r, durationMs: Date.now() - listStart })),
|
|
201
|
+
runScriptJsonAsync('oia-audit.mjs', auditArgs)
|
|
202
|
+
.then((r) => ({ ...r, durationMs: Date.now() - auditStart })),
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
205
|
+
const parallelWallMs = Date.now() - parallelStart;
|
|
206
|
+
const parallelSumMs = (listResult.durationMs || 0) + (auditResult.durationMs || 0);
|
|
207
|
+
|
|
208
|
+
if (listResult.exitCode !== 0) {
|
|
209
|
+
emitAndExit({
|
|
210
|
+
error: `audit-list failed (exit ${listResult.exitCode})`,
|
|
211
|
+
stderrTail: listResult.stderr.slice(-200),
|
|
212
|
+
}, 2);
|
|
213
|
+
}
|
|
214
|
+
const records = listResult.json?.records ?? listResult.json?.entries ?? [];
|
|
215
|
+
if (records.length === 0) {
|
|
216
|
+
// iter 57 + iter 58 — disambiguate "no history yet" (exit 2) from
|
|
217
|
+
// "metaharness absent" (exit 3). Use the audit we already ran
|
|
218
|
+
// (iter 58 fused the probe into the parallel batch) — no extra
|
|
219
|
+
// subprocess needed.
|
|
220
|
+
if (auditResult.json?.degraded === true) {
|
|
221
|
+
emitAndExit({
|
|
222
|
+
degraded: true,
|
|
223
|
+
reason: auditResult.json.reason || 'metaharness-not-available',
|
|
224
|
+
hint: 'Install metaharness to enable drift detection.',
|
|
225
|
+
}, 3);
|
|
226
|
+
}
|
|
227
|
+
emitAndExit({
|
|
228
|
+
error: 'no audit records found in namespace ' + NS,
|
|
229
|
+
hint: 'Run `ruflo metaharness oia-audit` at least once to seed history.',
|
|
230
|
+
}, 2);
|
|
231
|
+
}
|
|
232
|
+
// Pick the most recent record (records are typically newest-first; if not,
|
|
233
|
+
// sort by startedAt). Each record's key is in the entry.
|
|
234
|
+
const sorted = [...records].sort((a, b) =>
|
|
235
|
+
String(b.startedAt ?? b.key ?? '').localeCompare(String(a.startedAt ?? a.key ?? '')));
|
|
236
|
+
const baseline = sorted[0];
|
|
237
|
+
if (!baseline?.key) {
|
|
238
|
+
emitAndExit({
|
|
239
|
+
error: 'audit record has no `key` field — cannot reference',
|
|
240
|
+
sample: baseline,
|
|
241
|
+
}, 2);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Step 2: run fresh oia-audit (write to temp file so audit-trend can read it)
|
|
245
|
+
const tmp = mkdtempSync(join(tmpdir(), 'drift-from-history-'));
|
|
246
|
+
const currPath = join(tmp, 'current.json');
|
|
247
|
+
try {
|
|
248
|
+
// iter 58 — reuse auditResult from the parallel batch above instead
|
|
249
|
+
// of re-running oia-audit. Saves the ~600ms-3s the second run took.
|
|
250
|
+
if (!auditResult.json || auditResult.exitCode !== 0) {
|
|
251
|
+
emitAndExit({
|
|
252
|
+
error: `oia-audit failed (exit ${auditResult.exitCode})`,
|
|
253
|
+
stderrTail: auditResult.stderr.slice(-200),
|
|
254
|
+
}, 2);
|
|
255
|
+
}
|
|
256
|
+
if (auditResult.json.degraded === true) {
|
|
257
|
+
emitAndExit({
|
|
258
|
+
degraded: true,
|
|
259
|
+
reason: auditResult.json.reason || 'metaharness-not-available',
|
|
260
|
+
}, 3);
|
|
261
|
+
}
|
|
262
|
+
writeFileSync(currPath, JSON.stringify(auditResult.json));
|
|
263
|
+
|
|
264
|
+
// Step 3: audit-trend
|
|
265
|
+
// iter 67 — when --baseline-file was given, pass it through as
|
|
266
|
+
// audit-trend's --baseline file input instead of --baseline-key.
|
|
267
|
+
// Avoids the memory roundtrip on the trend leg too.
|
|
268
|
+
const trendArgs = usedBaselineFile
|
|
269
|
+
? [
|
|
270
|
+
'--baseline', ARGS.baselineFile,
|
|
271
|
+
'--current', currPath,
|
|
272
|
+
'--alert-on-distance-below', String(ARGS.threshold),
|
|
273
|
+
]
|
|
274
|
+
: [
|
|
275
|
+
'--baseline-key', baseline.key,
|
|
276
|
+
'--current', currPath,
|
|
277
|
+
'--alert-on-distance-below', String(ARGS.threshold),
|
|
278
|
+
];
|
|
279
|
+
const trendResult = runScriptJson('audit-trend.mjs', trendArgs);
|
|
280
|
+
if (!trendResult.json) {
|
|
281
|
+
emitAndExit({
|
|
282
|
+
error: `audit-trend produced no JSON (exit ${trendResult.exitCode})`,
|
|
283
|
+
stderrTail: trendResult.stderr.slice(-200),
|
|
284
|
+
}, 2);
|
|
285
|
+
}
|
|
286
|
+
const trend = trendResult.json;
|
|
287
|
+
let alertTriggered = trendResult.exitCode === 1;
|
|
288
|
+
const alertReasons = trend.alert?.reasons ? [...trend.alert.reasons] : [];
|
|
289
|
+
|
|
290
|
+
// iter 78 — orthogonal finding-severity gate: any introduced
|
|
291
|
+
// finding ≥ --alert-on-new-severity triggers regardless of
|
|
292
|
+
// structural-distance threshold. Catches the "security regression
|
|
293
|
+
// that didn't move the genome much" case.
|
|
294
|
+
let elevatedFindings = [];
|
|
295
|
+
if (ARGS.alertOnNewSeverity) {
|
|
296
|
+
const threshold = rankSeverity(ARGS.alertOnNewSeverity);
|
|
297
|
+
const introduced = trend?.delta?.findings?.introduced ?? [];
|
|
298
|
+
elevatedFindings = introduced.filter((f) => rankSeverity(f.severity) >= threshold);
|
|
299
|
+
if (elevatedFindings.length > 0) {
|
|
300
|
+
alertTriggered = true;
|
|
301
|
+
alertReasons.push(
|
|
302
|
+
`${elevatedFindings.length} new finding(s) at or above ${ARGS.alertOnNewSeverity} severity`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const payload = {
|
|
308
|
+
adr: 'ADR-150 + ADR-152 §3.1',
|
|
309
|
+
command: 'drift-from-history',
|
|
310
|
+
// iter 65 — parallel batch metrics. parallelSpeedup>1 means
|
|
311
|
+
// audit-list + oia-audit raced; ~1.0 means serial regression.
|
|
312
|
+
timing: {
|
|
313
|
+
parallelWallMs,
|
|
314
|
+
parallelSumMs,
|
|
315
|
+
parallelSpeedup: parallelSumMs > 0
|
|
316
|
+
? Math.round((parallelSumMs / Math.max(parallelWallMs, 1)) * 100) / 100
|
|
317
|
+
: 0,
|
|
318
|
+
// iter 66 — when true, audit-list was skipped via --baseline-key.
|
|
319
|
+
// Fastpath drops wall-clock from ~26s to ~1s (avoids ONNX warmup).
|
|
320
|
+
skippedAuditList,
|
|
321
|
+
// iter 67 — when true, baseline came from a file instead of memory.
|
|
322
|
+
// Skips audit-list AND the audit-trend memory roundtrip.
|
|
323
|
+
usedBaselineFile,
|
|
324
|
+
// iter 95 — derived path label for cleaner consumer code:
|
|
325
|
+
// 'slow' — default (no fast-path arg); audit-list ran
|
|
326
|
+
// 'key' — --baseline-key skipped audit-list, baseline came from memory
|
|
327
|
+
// 'file' — --baseline-file skipped audit-list AND memory roundtrip
|
|
328
|
+
path: usedBaselineFile ? 'file' : (skippedAuditList ? 'key' : 'slow'),
|
|
329
|
+
},
|
|
330
|
+
baseline: {
|
|
331
|
+
key: baseline.key,
|
|
332
|
+
startedAt: baseline.startedAt ?? null,
|
|
333
|
+
},
|
|
334
|
+
current: {
|
|
335
|
+
startedAt: auditResult.json.startedAt,
|
|
336
|
+
composite: auditResult.json.composite,
|
|
337
|
+
},
|
|
338
|
+
drift: trend.delta,
|
|
339
|
+
alert: {
|
|
340
|
+
threshold: ARGS.threshold,
|
|
341
|
+
// iter 78 — orthogonal gates surfaced: similarity threshold AND
|
|
342
|
+
// new-finding-severity. Either can flip triggered=true.
|
|
343
|
+
newSeverityThreshold: ARGS.alertOnNewSeverity,
|
|
344
|
+
triggered: alertTriggered,
|
|
345
|
+
reasons: alertReasons,
|
|
346
|
+
// Keep .reason for backward-compat with consumers that read it as a string
|
|
347
|
+
reason: alertReasons.length > 0
|
|
348
|
+
? alertReasons.join('; ')
|
|
349
|
+
: (alertTriggered ? `similarity < ${ARGS.threshold}` : `similarity ≥ ${ARGS.threshold} — OK`),
|
|
350
|
+
elevatedFindings,
|
|
351
|
+
},
|
|
352
|
+
generatedAt: new Date().toISOString(),
|
|
353
|
+
};
|
|
354
|
+
emitAndExit(payload, alertTriggered ? 1 : 0);
|
|
355
|
+
} finally {
|
|
356
|
+
try { rmSync(tmp, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main().catch((e) => {
|
|
361
|
+
console.error('drift-from-history crashed:', e.message || e);
|
|
362
|
+
process.exit(2);
|
|
363
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// genome.mjs — wrapper around `metaharness genome <path>`.
|
|
3
|
+
//
|
|
4
|
+
// Returns the 7-section readiness report: repo_type / agent_topology /
|
|
5
|
+
// risk_score / mcp_surface / test_confidence / publish_readiness +
|
|
6
|
+
// verdict (ready | needs-work | blocked). Reads-only.
|
|
7
|
+
//
|
|
8
|
+
// USAGE
|
|
9
|
+
// node scripts/genome.mjs
|
|
10
|
+
// node scripts/genome.mjs --path <dir> --alert-on-risk-above 0.5 --format json
|
|
11
|
+
//
|
|
12
|
+
// EXIT CODES
|
|
13
|
+
// 0 OK
|
|
14
|
+
// 1 --alert-on-risk-above threshold breached
|
|
15
|
+
// 2 config error or genome failure
|
|
16
|
+
|
|
17
|
+
import { runMetaharness, emitDegradedJsonAndExit } from './_harness.mjs';
|
|
18
|
+
|
|
19
|
+
const ARGS = (() => {
|
|
20
|
+
const a = { path: '.', format: 'json', alertRiskAbove: null };
|
|
21
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
22
|
+
const v = process.argv[i];
|
|
23
|
+
if (v === '--path') a.path = process.argv[++i];
|
|
24
|
+
else if (v === '--alert-on-risk-above') a.alertRiskAbove = parseFloat(process.argv[++i]);
|
|
25
|
+
else if (v === '--format') a.format = process.argv[++i];
|
|
26
|
+
}
|
|
27
|
+
return a;
|
|
28
|
+
})();
|
|
29
|
+
|
|
30
|
+
function main() {
|
|
31
|
+
const r = runMetaharness(['genome', ARGS.path]);
|
|
32
|
+
if (r.degraded) { emitDegradedJsonAndExit(r.reason); return; }
|
|
33
|
+
if (r.exitCode !== 0 || !r.json) {
|
|
34
|
+
console.error(`genome: metaharness exited ${r.exitCode}`);
|
|
35
|
+
if (r.stderr) console.error(r.stderr.slice(0, 400));
|
|
36
|
+
process.exit(2);
|
|
37
|
+
}
|
|
38
|
+
// iter 112 — generatedAt for consistency with other --format json outputs
|
|
39
|
+
const payload = { ...r.json, path: ARGS.path, durationMs: r.durationMs,
|
|
40
|
+
generatedAt: new Date().toISOString() };
|
|
41
|
+
|
|
42
|
+
if (ARGS.alertRiskAbove !== null) {
|
|
43
|
+
if (!isFinite(ARGS.alertRiskAbove)) {
|
|
44
|
+
console.error(`genome: --alert-on-risk-above must be a finite number`);
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
payload.alert = {
|
|
48
|
+
threshold: ARGS.alertRiskAbove,
|
|
49
|
+
triggered: typeof payload.risk_score === 'number' && payload.risk_score > ARGS.alertRiskAbove,
|
|
50
|
+
reason: typeof payload.risk_score === 'number' && payload.risk_score > ARGS.alertRiskAbove
|
|
51
|
+
? `risk_score ${payload.risk_score} > ${ARGS.alertRiskAbove}`
|
|
52
|
+
: `risk_score ${payload.risk_score ?? 'unknown'} ≤ ${ARGS.alertRiskAbove} — OK`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (ARGS.format === 'json') {
|
|
57
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
58
|
+
} else {
|
|
59
|
+
console.log(`# harness-genome — ${ARGS.path}`);
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(`| Section | Value |`);
|
|
62
|
+
console.log(`|---|---|`);
|
|
63
|
+
console.log(`| repo_type | ${payload.repo_type ?? '—'} |`);
|
|
64
|
+
console.log(`| agent_topology | ${(payload.agent_topology || []).join(', ') || '—'} |`);
|
|
65
|
+
console.log(`| risk_score | ${payload.risk_score ?? '—'} |`);
|
|
66
|
+
console.log(`| mcp_surface | ${payload.mcp_surface ?? '—'} |`);
|
|
67
|
+
console.log(`| test_confidence | ${payload.test_confidence ?? '—'} |`);
|
|
68
|
+
console.log(`| publish_readiness | ${payload.publish_readiness ?? '—'} |`);
|
|
69
|
+
console.log(`| **duration** | ${payload.durationMs}ms |`);
|
|
70
|
+
console.log('');
|
|
71
|
+
if (payload.alert) {
|
|
72
|
+
console.log(payload.alert.triggered ? `⚠ **ALERT**: ${payload.alert.reason}` : `✓ ${payload.alert.reason}`);
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (payload.alert?.triggered) process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main();
|