@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,161 @@
|
|
|
1
|
+
// _similarity.mjs — ADR-152 production similarity module.
|
|
2
|
+
//
|
|
3
|
+
// Graduated from `_spike-similarity.mjs` (iter 35) after both invariants
|
|
4
|
+
// passed on the LEGAL/SUPPORT/DEVOPS fixtures. The spike file STAYS as
|
|
5
|
+
// the regression-suite anchor; this file is what production callers
|
|
6
|
+
// (`similarity.mjs` skill, `metaharness_similarity` MCP tool) import.
|
|
7
|
+
//
|
|
8
|
+
// ADR-150 ARCHITECTURAL CONSTRAINTS PRESERVED
|
|
9
|
+
// Removable ✓ pure-TS — no `@metaharness/*` import path
|
|
10
|
+
// Optional ✓ no new dep on `@metaharness/*`
|
|
11
|
+
// Graceful ✓ malformed input → low-confidence output, never throws
|
|
12
|
+
// CI-gate ✓ standalone unit-importable; no `npx` needed
|
|
13
|
+
//
|
|
14
|
+
// CONTRACT (PUBLIC)
|
|
15
|
+
// projectToVec(input) → 9-dim numerical feature vector
|
|
16
|
+
// cosine(a, b) → [0,1]
|
|
17
|
+
// categoricalAgreement(a, b) → [0,1] over 4 enum fields
|
|
18
|
+
// jaccard(a, b) → [0,1] over agent_topology[]
|
|
19
|
+
// similarity(a, b, opts?) → { overall, components, perDimension? }
|
|
20
|
+
//
|
|
21
|
+
// WEIGHT DEFAULTS (from ADR-152 §Decision)
|
|
22
|
+
// overall = 0.60·cosine + 0.25·categorical + 0.15·jaccard
|
|
23
|
+
//
|
|
24
|
+
// ADR-152 reserves a future per-org weight override; for §3.1 the defaults
|
|
25
|
+
// are global. The opts.weights hook is here so consumers can experiment
|
|
26
|
+
// without forking the module — it is NOT a stable public API.
|
|
27
|
+
|
|
28
|
+
const DEFAULT_WEIGHTS = Object.freeze({ cosine: 0.6, categorical: 0.25, jaccard: 0.15 });
|
|
29
|
+
|
|
30
|
+
const CATEGORICAL_FIELDS = Object.freeze(['repo_type', 'archetype', 'template', 'recommendedMode']);
|
|
31
|
+
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
33
|
+
// 9-dim feature vector. The mapping mirrors ADR-152 §Decision Table 1.
|
|
34
|
+
// Missing fields default to 0 — that's the graceful-degradation path.
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export function projectToVec(input) {
|
|
38
|
+
const s = input?.score ?? {};
|
|
39
|
+
const g = input?.genome ?? {};
|
|
40
|
+
return [
|
|
41
|
+
(s.harnessFit ?? 0) / 100,
|
|
42
|
+
(s.compileConfidence ?? 0) / 100,
|
|
43
|
+
(s.taskCoverage ?? 0) / 100,
|
|
44
|
+
(s.toolSafety ?? 0) / 100,
|
|
45
|
+
(s.memoryUsefulness ?? 0) / 100,
|
|
46
|
+
g.risk_score ?? 0,
|
|
47
|
+
g.test_confidence ?? 0,
|
|
48
|
+
g.publish_readiness ?? 0,
|
|
49
|
+
Math.max(0, Math.min(1, Math.log10((s.estCostPerRunUsd ?? 0) + 0.001) / Math.log10(10))),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function cosine(a, b) {
|
|
54
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return 0;
|
|
55
|
+
let dot = 0, na = 0, nb = 0;
|
|
56
|
+
for (let i = 0; i < a.length; i++) {
|
|
57
|
+
dot += a[i] * b[i];
|
|
58
|
+
na += a[i] * a[i];
|
|
59
|
+
nb += b[i] * b[i];
|
|
60
|
+
}
|
|
61
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
62
|
+
if (denom === 0) return 0;
|
|
63
|
+
return Math.max(0, Math.min(1, dot / denom));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function categoricalAgreement(a, b) {
|
|
67
|
+
let matches = 0;
|
|
68
|
+
for (const f of CATEGORICAL_FIELDS) {
|
|
69
|
+
const av = a?.genome?.[f] ?? a?.score?.[f];
|
|
70
|
+
const bv = b?.genome?.[f] ?? b?.score?.[f];
|
|
71
|
+
if (av && bv && av === bv) matches++;
|
|
72
|
+
}
|
|
73
|
+
return matches / CATEGORICAL_FIELDS.length;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function jaccard(a, b) {
|
|
77
|
+
const A = new Set(a?.genome?.agent_topology ?? []);
|
|
78
|
+
const B = new Set(b?.genome?.agent_topology ?? []);
|
|
79
|
+
if (A.size === 0 && B.size === 0) return 1;
|
|
80
|
+
const intersection = [...A].filter((x) => B.has(x)).length;
|
|
81
|
+
const union = new Set([...A, ...B]).size;
|
|
82
|
+
return union === 0 ? 0 : intersection / union;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
86
|
+
// Composite similarity with optional per-dimension breakdown.
|
|
87
|
+
// Returns the ADR-152 §"return shape": overall + components + per-dim.
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
export function similarity(a, b, opts = {}) {
|
|
91
|
+
const weights = { ...DEFAULT_WEIGHTS, ...(opts.weights ?? {}) };
|
|
92
|
+
const va = projectToVec(a);
|
|
93
|
+
const vb = projectToVec(b);
|
|
94
|
+
const cos = cosine(va, vb);
|
|
95
|
+
const cat = categoricalAgreement(a, b);
|
|
96
|
+
const jac = jaccard(a, b);
|
|
97
|
+
|
|
98
|
+
const overall = weights.cosine * cos + weights.categorical * cat + weights.jaccard * jac;
|
|
99
|
+
|
|
100
|
+
const result = {
|
|
101
|
+
overall: round4(overall),
|
|
102
|
+
components: {
|
|
103
|
+
cosine: round4(cos),
|
|
104
|
+
categorical: round4(cat),
|
|
105
|
+
jaccard: round4(jac),
|
|
106
|
+
},
|
|
107
|
+
weights,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (opts.perDimension) {
|
|
111
|
+
result.perDimension = perDimensionBreakdown(a, b, va, vb, weights);
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function round4(x) {
|
|
117
|
+
return Math.round(x * 10000) / 10000;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function perDimensionBreakdown(a, b, va, vb, weights) {
|
|
121
|
+
// Per-dimension contribution = squared-error-normalized cosine slice + categorical/jaccard direct.
|
|
122
|
+
// We surface raw a/b values + a contribution sign so callers can explain
|
|
123
|
+
// why two harnesses scored as they did (used by Recommendation Engine
|
|
124
|
+
// §3.2 confidence calc + Drift Detection §3.3 alert reason).
|
|
125
|
+
const out = {};
|
|
126
|
+
|
|
127
|
+
const numericKeys = [
|
|
128
|
+
['harnessFit', 'score', 100], ['compileConfidence', 'score', 100],
|
|
129
|
+
['taskCoverage', 'score', 100], ['toolSafety', 'score', 100],
|
|
130
|
+
['memoryUsefulness', 'score', 100],
|
|
131
|
+
['risk_score', 'genome', 1], ['test_confidence', 'genome', 1],
|
|
132
|
+
['publish_readiness', 'genome', 1],
|
|
133
|
+
['estCostPerRunUsd', 'score', 1],
|
|
134
|
+
];
|
|
135
|
+
for (let i = 0; i < numericKeys.length; i++) {
|
|
136
|
+
const [k, src] = numericKeys[i];
|
|
137
|
+
const av = a?.[src]?.[k];
|
|
138
|
+
const bv = b?.[src]?.[k];
|
|
139
|
+
out[`numeric.${k}`] = {
|
|
140
|
+
a: av ?? null, b: bv ?? null,
|
|
141
|
+
contribution: round4(va[i] * vb[i] * weights.cosine / 9),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
for (const f of CATEGORICAL_FIELDS) {
|
|
145
|
+
const av = a?.genome?.[f] ?? a?.score?.[f];
|
|
146
|
+
const bv = b?.genome?.[f] ?? b?.score?.[f];
|
|
147
|
+
out[`categorical.${f}`] = {
|
|
148
|
+
a: av ?? null, b: bv ?? null,
|
|
149
|
+
contribution: av && bv && av === bv ? round4(weights.categorical / 4) : 0,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const A = new Set(a?.genome?.agent_topology ?? []);
|
|
153
|
+
const B = new Set(b?.genome?.agent_topology ?? []);
|
|
154
|
+
const overlap = [...A].filter((x) => B.has(x));
|
|
155
|
+
const union = new Set([...A, ...B]);
|
|
156
|
+
out['set.agent_topology'] = {
|
|
157
|
+
a: [...A], b: [...B],
|
|
158
|
+
contribution: union.size === 0 ? 0 : round4((overlap.length / union.size) * weights.jaccard),
|
|
159
|
+
};
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// _spike-similarity.mjs — ADR-152 §"Smallest demonstrable spike" gate.
|
|
3
|
+
//
|
|
4
|
+
// This file is the implementation gate for ADR-152 (Genome Similarity Search).
|
|
5
|
+
// Per ADR-151's scope-only constraint, ADR-152 cannot graduate to "Accepted"
|
|
6
|
+
// until the two invariants below are proven by running this script.
|
|
7
|
+
//
|
|
8
|
+
// THE TWO INVARIANTS (from ADR-152 §"Smallest demonstrable spike")
|
|
9
|
+
// 1. similarity(X, X) === 1 (self-match exact)
|
|
10
|
+
// 2. similarity(legal, devops) < similarity(legal, support)
|
|
11
|
+
// (vertical-affinity correct)
|
|
12
|
+
//
|
|
13
|
+
// USAGE
|
|
14
|
+
// node scripts/_spike-similarity.mjs (run + assert)
|
|
15
|
+
// node scripts/_spike-similarity.mjs --format json (CI-consumable)
|
|
16
|
+
//
|
|
17
|
+
// EXIT CODES
|
|
18
|
+
// 0 both invariants hold
|
|
19
|
+
// 1 at least one invariant fails (ADR-152 cannot graduate)
|
|
20
|
+
//
|
|
21
|
+
// LIFECYCLE
|
|
22
|
+
// Once ADR-152 is Accepted, the full similarity implementation lands in
|
|
23
|
+
// `_similarity.mjs` (production module). This file (`_spike-similarity.mjs`)
|
|
24
|
+
// remains as the regression-suite anchor for the two invariants.
|
|
25
|
+
|
|
26
|
+
const ARGS = (() => {
|
|
27
|
+
const a = { format: 'table' };
|
|
28
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
29
|
+
if (process.argv[i] === '--format') a.format = process.argv[++i];
|
|
30
|
+
}
|
|
31
|
+
return a;
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
35
|
+
// THE PROPOSED IMPLEMENTATION (ADR-152 §"Decision")
|
|
36
|
+
//
|
|
37
|
+
// Pure-TS. Takes two {genome, score} JSON shapes; emits {overall,
|
|
38
|
+
// components: {cosine, categorical, jaccard}}. The 9 numerical features +
|
|
39
|
+
// 4 categorical fields + 1 set field per ADR-152.
|
|
40
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function projectToVec(input) {
|
|
43
|
+
const s = input.score || {};
|
|
44
|
+
const g = input.genome || {};
|
|
45
|
+
return [
|
|
46
|
+
(s.harnessFit ?? 0) / 100,
|
|
47
|
+
(s.compileConfidence ?? 0) / 100,
|
|
48
|
+
(s.taskCoverage ?? 0) / 100,
|
|
49
|
+
(s.toolSafety ?? 0) / 100,
|
|
50
|
+
(s.memoryUsefulness ?? 0) / 100,
|
|
51
|
+
g.risk_score ?? 0,
|
|
52
|
+
g.test_confidence ?? 0,
|
|
53
|
+
g.publish_readiness ?? 0,
|
|
54
|
+
// estCostPerRunUsd: log10 transform, clamped to [0, 1]
|
|
55
|
+
Math.max(0, Math.min(1, Math.log10((s.estCostPerRunUsd ?? 0) + 0.001) / Math.log10(10))),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function cosine(a, b) {
|
|
60
|
+
let dot = 0, na = 0, nb = 0;
|
|
61
|
+
for (let i = 0; i < a.length; i++) {
|
|
62
|
+
dot += a[i] * b[i];
|
|
63
|
+
na += a[i] * a[i];
|
|
64
|
+
nb += b[i] * b[i];
|
|
65
|
+
}
|
|
66
|
+
const denom = Math.sqrt(na) * Math.sqrt(nb);
|
|
67
|
+
if (denom === 0) return 0;
|
|
68
|
+
// Map cosine from [-1, 1] to [0, 1] for nonnegative vectors it's [0, 1] already
|
|
69
|
+
return Math.max(0, Math.min(1, dot / denom));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function categoricalAgreement(a, b) {
|
|
73
|
+
const fields = ['repo_type', 'archetype', 'template', 'recommendedMode'];
|
|
74
|
+
let matches = 0;
|
|
75
|
+
for (const f of fields) {
|
|
76
|
+
const av = a.genome?.[f] ?? a.score?.[f];
|
|
77
|
+
const bv = b.genome?.[f] ?? b.score?.[f];
|
|
78
|
+
if (av && bv && av === bv) matches++;
|
|
79
|
+
}
|
|
80
|
+
return matches / fields.length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function jaccard(a, b) {
|
|
84
|
+
const A = new Set(a.genome?.agent_topology ?? []);
|
|
85
|
+
const B = new Set(b.genome?.agent_topology ?? []);
|
|
86
|
+
if (A.size === 0 && B.size === 0) return 1;
|
|
87
|
+
const intersection = [...A].filter((x) => B.has(x)).length;
|
|
88
|
+
const union = new Set([...A, ...B]).size;
|
|
89
|
+
return union === 0 ? 0 : intersection / union;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function similarity(a, b) {
|
|
93
|
+
const cos = cosine(projectToVec(a), projectToVec(b));
|
|
94
|
+
const cat = categoricalAgreement(a, b);
|
|
95
|
+
const jac = jaccard(a, b);
|
|
96
|
+
const overall = 0.6 * cos + 0.25 * cat + 0.15 * jac;
|
|
97
|
+
return {
|
|
98
|
+
overall: Math.round(overall * 10000) / 10000,
|
|
99
|
+
components: {
|
|
100
|
+
cosine: Math.round(cos * 10000) / 10000,
|
|
101
|
+
categorical: Math.round(cat * 10000) / 10000,
|
|
102
|
+
jaccard: Math.round(jac * 10000) / 10000,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
108
|
+
// THREE SYNTHETIC FIXTURES designed to exercise the invariants. These
|
|
109
|
+
// mirror the JSON shapes `harness genome` + `harness score` emit (verified
|
|
110
|
+
// against metaharness@0.1.14 via iter-26 test-with-openrouter.mjs).
|
|
111
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
const LEGAL = {
|
|
114
|
+
score: {
|
|
115
|
+
harnessFit: 78, compileConfidence: 92, taskCoverage: 65,
|
|
116
|
+
toolSafety: 88, memoryUsefulness: 70, estCostPerRunUsd: 0.04,
|
|
117
|
+
recommendedMode: 'CLI + MCP', archetype: 'compliance-harness',
|
|
118
|
+
template: 'vertical:legal', scaffoldReady: true,
|
|
119
|
+
},
|
|
120
|
+
genome: {
|
|
121
|
+
repo_type: 'node_mcp_ci',
|
|
122
|
+
agent_topology: ['contract-analyst', 'redline-reviewer', 'risk-rater', 'compliance-officer'],
|
|
123
|
+
risk_score: 0.45,
|
|
124
|
+
mcp_surface: 'remote',
|
|
125
|
+
test_confidence: 0.7,
|
|
126
|
+
publish_readiness: 0.6,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const SUPPORT = {
|
|
131
|
+
score: {
|
|
132
|
+
harnessFit: 75, compileConfidence: 90, taskCoverage: 70,
|
|
133
|
+
toolSafety: 90, memoryUsefulness: 72, estCostPerRunUsd: 0.05,
|
|
134
|
+
recommendedMode: 'CLI + MCP', archetype: 'compliance-harness',
|
|
135
|
+
template: 'vertical:support', scaffoldReady: true,
|
|
136
|
+
},
|
|
137
|
+
genome: {
|
|
138
|
+
repo_type: 'node_mcp_ci',
|
|
139
|
+
agent_topology: ['triager', 'kb-searcher', 'responder', 'risk-rater', 'compliance-officer'],
|
|
140
|
+
risk_score: 0.40,
|
|
141
|
+
mcp_surface: 'remote',
|
|
142
|
+
test_confidence: 0.75,
|
|
143
|
+
publish_readiness: 0.65,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const DEVOPS = {
|
|
148
|
+
score: {
|
|
149
|
+
harnessFit: 88, compileConfidence: 95, taskCoverage: 85,
|
|
150
|
+
toolSafety: 70, memoryUsefulness: 55, estCostPerRunUsd: 0.08,
|
|
151
|
+
recommendedMode: 'CLI', archetype: 'cli-operator-harness',
|
|
152
|
+
template: 'vertical:devops', scaffoldReady: true,
|
|
153
|
+
},
|
|
154
|
+
genome: {
|
|
155
|
+
repo_type: 'python_ops',
|
|
156
|
+
agent_topology: ['incident-commander', 'deployer', 'rollback', 'oncall-pager'],
|
|
157
|
+
risk_score: 0.25,
|
|
158
|
+
mcp_surface: 'local',
|
|
159
|
+
test_confidence: 0.85,
|
|
160
|
+
publish_readiness: 0.9,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
165
|
+
// INVARIANT VERIFICATION (the gate)
|
|
166
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
const self = similarity(LEGAL, LEGAL);
|
|
169
|
+
const legalVsSupport = similarity(LEGAL, SUPPORT);
|
|
170
|
+
const legalVsDevops = similarity(LEGAL, DEVOPS);
|
|
171
|
+
|
|
172
|
+
const inv1Pass = self.overall === 1;
|
|
173
|
+
const inv2Pass = legalVsDevops.overall < legalVsSupport.overall;
|
|
174
|
+
|
|
175
|
+
const payload = {
|
|
176
|
+
adr: 'ADR-152',
|
|
177
|
+
spike: '_spike-similarity.mjs',
|
|
178
|
+
invariants: {
|
|
179
|
+
selfMatch: { pass: inv1Pass, value: self.overall, expected: 1 },
|
|
180
|
+
verticalAffinity: {
|
|
181
|
+
pass: inv2Pass,
|
|
182
|
+
legalVsDevops: legalVsDevops.overall,
|
|
183
|
+
legalVsSupport: legalVsSupport.overall,
|
|
184
|
+
reason: inv2Pass
|
|
185
|
+
? `legal:devops (${legalVsDevops.overall}) < legal:support (${legalVsSupport.overall}) — vertical affinity correct`
|
|
186
|
+
: `INVARIANT VIOLATED: legal:devops (${legalVsDevops.overall}) ≥ legal:support (${legalVsSupport.overall})`,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
detail: {
|
|
190
|
+
'similarity(LEGAL, LEGAL)': self,
|
|
191
|
+
'similarity(LEGAL, SUPPORT)': legalVsSupport,
|
|
192
|
+
'similarity(LEGAL, DEVOPS)': legalVsDevops,
|
|
193
|
+
},
|
|
194
|
+
graduates: inv1Pass && inv2Pass,
|
|
195
|
+
generatedAt: new Date().toISOString(),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (ARGS.format === 'json') {
|
|
199
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
200
|
+
} else {
|
|
201
|
+
console.log(`# ADR-152 §Spike — Genome Similarity Search\n`);
|
|
202
|
+
console.log(`Invariant 1 — selfMatch:`);
|
|
203
|
+
console.log(` similarity(LEGAL, LEGAL).overall = ${self.overall}`);
|
|
204
|
+
console.log(` expected: 1`);
|
|
205
|
+
console.log(` ${inv1Pass ? '✓ PASS' : '✗ FAIL'}`);
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(`Invariant 2 — verticalAffinity (legal closer to support than to devops):`);
|
|
208
|
+
console.log(` similarity(LEGAL, SUPPORT).overall = ${legalVsSupport.overall}`);
|
|
209
|
+
console.log(` similarity(LEGAL, DEVOPS).overall = ${legalVsDevops.overall}`);
|
|
210
|
+
console.log(` ${inv2Pass ? '✓ PASS' : '✗ FAIL'} — ${payload.invariants.verticalAffinity.reason}`);
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(`Per-component breakdown:`);
|
|
213
|
+
console.log(` LEGAL vs SUPPORT — cosine=${legalVsSupport.components.cosine} categorical=${legalVsSupport.components.categorical} jaccard=${legalVsSupport.components.jaccard}`);
|
|
214
|
+
console.log(` LEGAL vs DEVOPS — cosine=${legalVsDevops.components.cosine} categorical=${legalVsDevops.components.categorical} jaccard=${legalVsDevops.components.jaccard}`);
|
|
215
|
+
console.log('');
|
|
216
|
+
if (payload.graduates) {
|
|
217
|
+
console.log(`✓ Both invariants hold. ADR-152 may graduate Proposed → Accepted.`);
|
|
218
|
+
} else {
|
|
219
|
+
console.log(`✗ Invariant violation. ADR-152 stays Proposed.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!payload.graduates) process.exit(1);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// audit-list.mjs — list timestamped oia-audit records from the
|
|
3
|
+
// `metaharness-audit` memory namespace.
|
|
4
|
+
//
|
|
5
|
+
// The iter-7 oia-audit worker writes one record per run (keyed by ISO
|
|
6
|
+
// timestamp). This script enumerates them so operators can see audit
|
|
7
|
+
// cadence, spot gaps, and pick keys to feed into audit-trend.mjs.
|
|
8
|
+
//
|
|
9
|
+
// USAGE
|
|
10
|
+
// node scripts/audit-list.mjs # most recent 20
|
|
11
|
+
// node scripts/audit-list.mjs --limit 50
|
|
12
|
+
// node scripts/audit-list.mjs --format json
|
|
13
|
+
// node scripts/audit-list.mjs --since 30d # last N days
|
|
14
|
+
//
|
|
15
|
+
// EXIT CODES
|
|
16
|
+
// 0 ok
|
|
17
|
+
// 2 config error
|
|
18
|
+
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
|
|
21
|
+
const NS = process.env.AUDIT_LIST_NAMESPACE || 'metaharness-audit';
|
|
22
|
+
const CLI_PKG = process.env.CLI_CORE === '1'
|
|
23
|
+
? '@claude-flow/cli-core@alpha'
|
|
24
|
+
: '@claude-flow/cli@latest';
|
|
25
|
+
|
|
26
|
+
const ARGS = (() => {
|
|
27
|
+
const a = { limit: 20, since: null, format: 'table' };
|
|
28
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
29
|
+
const v = process.argv[i];
|
|
30
|
+
if (v === '--limit') a.limit = parseInt(process.argv[++i], 10);
|
|
31
|
+
else if (v === '--since') a.since = process.argv[++i];
|
|
32
|
+
else if (v === '--format') a.format = process.argv[++i];
|
|
33
|
+
}
|
|
34
|
+
return a;
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
function parseDurationMs(spec) {
|
|
38
|
+
const m = /^(\d+)([hdwm])$/.exec(spec);
|
|
39
|
+
if (!m) return null;
|
|
40
|
+
const n = parseInt(m[1], 10);
|
|
41
|
+
const unit = { h: 3600_000, d: 86400_000, w: 7 * 86400_000, m: 30 * 86400_000 }[m[2]];
|
|
42
|
+
return unit ? n * unit : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function memList() {
|
|
46
|
+
const r = spawnSync('npx', [
|
|
47
|
+
CLI_PKG, 'memory', 'list',
|
|
48
|
+
'--namespace', NS, '--format', 'json',
|
|
49
|
+
], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8', shell: process.platform === 'win32' });
|
|
50
|
+
if (r.status !== 0) return [];
|
|
51
|
+
const m = /\[[\s\S]*\]/.exec(r.stdout || '');
|
|
52
|
+
if (!m) return [];
|
|
53
|
+
try { return JSON.parse(m[0]); } catch { return []; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function memRetrieve(key) {
|
|
57
|
+
const r = spawnSync('npx', [
|
|
58
|
+
CLI_PKG, 'memory', 'retrieve',
|
|
59
|
+
'--namespace', NS, '--key', key,
|
|
60
|
+
], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8', shell: process.platform === 'win32' });
|
|
61
|
+
if (r.status !== 0) return null;
|
|
62
|
+
const m = /\{[\s\S]*\}/.exec(r.stdout || '');
|
|
63
|
+
if (!m) return null;
|
|
64
|
+
try { return JSON.parse(m[0]); } catch { return null; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function main() {
|
|
68
|
+
if (!Number.isInteger(ARGS.limit) || ARGS.limit < 1) {
|
|
69
|
+
console.error(`audit-list: --limit must be a positive integer`);
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
let cutoffMs = null;
|
|
73
|
+
if (ARGS.since) {
|
|
74
|
+
const ms = parseDurationMs(ARGS.since);
|
|
75
|
+
if (!ms) {
|
|
76
|
+
console.error(`audit-list: --since must be N(h|d|w|m); got ${ARGS.since}`);
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
cutoffMs = Date.now() - ms;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const entries = memList()
|
|
83
|
+
.map((e) => e.key)
|
|
84
|
+
.filter((k) => typeof k === 'string' && k.startsWith('audit-'))
|
|
85
|
+
.sort()
|
|
86
|
+
.reverse(); // newest first (ISO keys sort lexicographically by time)
|
|
87
|
+
|
|
88
|
+
// Apply --since by parsing the timestamp out of the key.
|
|
89
|
+
const filtered = entries.filter((k) => {
|
|
90
|
+
if (!cutoffMs) return true;
|
|
91
|
+
// key shape: audit-2026-06-16T15-30-00-000Z
|
|
92
|
+
const m = /^audit-(.+)$/.exec(k);
|
|
93
|
+
if (!m) return true;
|
|
94
|
+
const isoLike = m[1].replace(/-/g, ':').replace(/^(\d{4}):(\d{2}):(\d{2})/, '$1-$2-$3');
|
|
95
|
+
const ts = Date.parse(isoLike);
|
|
96
|
+
return Number.isFinite(ts) && ts >= cutoffMs;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const slice = filtered.slice(0, ARGS.limit);
|
|
100
|
+
|
|
101
|
+
// Pull just enough fields from each record to render a compact line.
|
|
102
|
+
const rows = [];
|
|
103
|
+
for (const key of slice) {
|
|
104
|
+
const rec = memRetrieve(key);
|
|
105
|
+
if (!rec) continue;
|
|
106
|
+
rows.push({
|
|
107
|
+
key,
|
|
108
|
+
startedAt: rec.startedAt || null,
|
|
109
|
+
finishedAt: rec.finishedAt || null,
|
|
110
|
+
worst: rec.composite?.worst || null,
|
|
111
|
+
tmWorst: rec.composite?.threatModelWorst || null,
|
|
112
|
+
mcpWorst: rec.composite?.mcpScanWorst || null,
|
|
113
|
+
degraded: !!(rec.components?.oiaManifest?.degraded
|
|
114
|
+
|| rec.components?.threatModel?.degraded
|
|
115
|
+
|| rec.components?.mcpScan?.degraded),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const payload = {
|
|
120
|
+
namespace: NS,
|
|
121
|
+
filters: { limit: ARGS.limit, since: ARGS.since },
|
|
122
|
+
totalInNamespace: entries.length,
|
|
123
|
+
matchedSinceFilter: filtered.length,
|
|
124
|
+
returned: rows.length,
|
|
125
|
+
records: rows,
|
|
126
|
+
generatedAt: new Date().toISOString(),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (ARGS.format === 'json') {
|
|
130
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(`# audit-list`);
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log(`| Field | Value |`);
|
|
137
|
+
console.log(`|---|---:|`);
|
|
138
|
+
console.log(`| Namespace | \`${NS}\` |`);
|
|
139
|
+
console.log(`| Total records | ${entries.length} |`);
|
|
140
|
+
console.log(`| Matched filter | ${filtered.length} |`);
|
|
141
|
+
console.log(`| Showing | ${rows.length} (newest first) |`);
|
|
142
|
+
console.log('');
|
|
143
|
+
if (rows.length === 0) {
|
|
144
|
+
console.log(`_No audit records found. Run \`harness oia-audit\` to create one._`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.log(`| Key | Started | Composite worst | Threat | MCP | Degraded |`);
|
|
148
|
+
console.log(`|---|---|:---:|:---:|:---:|:---:|`);
|
|
149
|
+
for (const r of rows) {
|
|
150
|
+
const shortKey = r.key.slice(0, 32) + (r.key.length > 32 ? '…' : '');
|
|
151
|
+
const started = r.startedAt ? r.startedAt.slice(0, 19).replace('T', ' ') : '—';
|
|
152
|
+
console.log(`| \`${shortKey}\` | ${started} | ${r.worst || '—'} | ${r.tmWorst || '—'} | ${r.mcpWorst || '—'} | ${r.degraded ? '⚠' : '✓'} |`);
|
|
153
|
+
}
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log(`_Use \`audit-trend --baseline-key <a> --current-key <b>\` to diff two records._`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
main();
|