@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.
Files changed (192) hide show
  1. package/dist/src/commands/doctor.d.ts.map +1 -1
  2. package/dist/src/commands/doctor.js +175 -1
  3. package/dist/src/commands/doctor.js.map +1 -1
  4. package/dist/src/commands/eject.d.ts +33 -0
  5. package/dist/src/commands/eject.d.ts.map +1 -0
  6. package/dist/src/commands/eject.js +195 -0
  7. package/dist/src/commands/eject.js.map +1 -0
  8. package/dist/src/commands/hooks.d.ts.map +1 -1
  9. package/dist/src/commands/hooks.js +2 -1
  10. package/dist/src/commands/hooks.js.map +1 -1
  11. package/dist/src/commands/index.d.ts.map +1 -1
  12. package/dist/src/commands/index.js +4 -0
  13. package/dist/src/commands/index.js.map +1 -1
  14. package/dist/src/commands/init.d.ts.map +1 -1
  15. package/dist/src/commands/init.js +3 -0
  16. package/dist/src/commands/init.js.map +1 -1
  17. package/dist/src/commands/metaharness.d.ts +39 -0
  18. package/dist/src/commands/metaharness.d.ts.map +1 -0
  19. package/dist/src/commands/metaharness.js +194 -0
  20. package/dist/src/commands/metaharness.js.map +1 -0
  21. package/dist/src/mcp-client.d.ts.map +1 -1
  22. package/dist/src/mcp-client.js +4 -0
  23. package/dist/src/mcp-client.js.map +1 -1
  24. package/dist/src/mcp-tools/index.d.ts +1 -0
  25. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  26. package/dist/src/mcp-tools/index.js +2 -0
  27. package/dist/src/mcp-tools/index.js.map +1 -1
  28. package/dist/src/mcp-tools/metaharness-tools.d.ts +31 -0
  29. package/dist/src/mcp-tools/metaharness-tools.d.ts.map +1 -0
  30. package/dist/src/mcp-tools/metaharness-tools.js +372 -0
  31. package/dist/src/mcp-tools/metaharness-tools.js.map +1 -0
  32. package/dist/src/plugins/store/discovery.d.ts.map +1 -1
  33. package/dist/src/plugins/store/discovery.js +3 -0
  34. package/dist/src/plugins/store/discovery.js.map +1 -1
  35. package/dist/src/plugins/store/types.d.ts +6 -1
  36. package/dist/src/plugins/store/types.d.ts.map +1 -1
  37. package/dist/src/ruvector/model-router.d.ts.map +1 -1
  38. package/dist/src/ruvector/model-router.js +37 -0
  39. package/dist/src/ruvector/model-router.js.map +1 -1
  40. package/dist/src/ruvector/router-parallel-recorder.d.ts +127 -0
  41. package/dist/src/ruvector/router-parallel-recorder.d.ts.map +1 -0
  42. package/dist/src/ruvector/router-parallel-recorder.js +183 -0
  43. package/dist/src/ruvector/router-parallel-recorder.js.map +1 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +13 -10
  46. package/plugins/ruflo-metaharness/.claude-flow/data/pending-insights.jsonl +1 -0
  47. package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +6 -0
  48. package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +32 -0
  49. package/plugins/ruflo-metaharness/README.md +64 -0
  50. package/plugins/ruflo-metaharness/agents/metaharness-architect.md +58 -0
  51. package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +46 -0
  52. package/plugins/ruflo-metaharness/scripts/_harness.mjs +261 -0
  53. package/plugins/ruflo-metaharness/scripts/_similarity.mjs +161 -0
  54. package/plugins/ruflo-metaharness/scripts/_spike-similarity.mjs +223 -0
  55. package/plugins/ruflo-metaharness/scripts/audit-list.mjs +158 -0
  56. package/plugins/ruflo-metaharness/scripts/audit-trend.mjs +272 -0
  57. package/plugins/ruflo-metaharness/scripts/bench-parse-mcp-scan.mjs +146 -0
  58. package/plugins/ruflo-metaharness/scripts/bench-recordpair-overhead.mjs +186 -0
  59. package/plugins/ruflo-metaharness/scripts/bench-similarity.mjs +177 -0
  60. package/plugins/ruflo-metaharness/scripts/drift-from-history.mjs +363 -0
  61. package/plugins/ruflo-metaharness/scripts/genome.mjs +80 -0
  62. package/plugins/ruflo-metaharness/scripts/mcp-scan.mjs +111 -0
  63. package/plugins/ruflo-metaharness/scripts/mint.mjs +119 -0
  64. package/plugins/ruflo-metaharness/scripts/oia-audit.mjs +228 -0
  65. package/plugins/ruflo-metaharness/scripts/router-parallel-analyze.mjs +250 -0
  66. package/plugins/ruflo-metaharness/scripts/score.mjs +92 -0
  67. package/plugins/ruflo-metaharness/scripts/similarity.mjs +158 -0
  68. package/plugins/ruflo-metaharness/scripts/smoke.sh +2268 -0
  69. package/plugins/ruflo-metaharness/scripts/test-graceful-degradation.mjs +141 -0
  70. package/plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs +437 -0
  71. package/plugins/ruflo-metaharness/scripts/test-parallel-pipeline.mjs +204 -0
  72. package/plugins/ruflo-metaharness/scripts/test-pipeline-roundtrip.mjs +586 -0
  73. package/plugins/ruflo-metaharness/scripts/test-similarity.mjs +334 -0
  74. package/plugins/ruflo-metaharness/scripts/test-with-openrouter.mjs +229 -0
  75. package/plugins/ruflo-metaharness/scripts/threat-model.mjs +59 -0
  76. package/plugins/ruflo-metaharness/skills/harness-drift-from-history/SKILL.md +65 -0
  77. package/plugins/ruflo-metaharness/skills/harness-genome/SKILL.md +54 -0
  78. package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +47 -0
  79. package/plugins/ruflo-metaharness/skills/harness-mint/SKILL.md +72 -0
  80. package/plugins/ruflo-metaharness/skills/harness-oia-audit/SKILL.md +79 -0
  81. package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +66 -0
  82. package/plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md +67 -0
  83. package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +39 -0
  84. package/dist/src/benchmarks/capability-tasks.d.ts +0 -126
  85. package/dist/src/benchmarks/capability-tasks.d.ts.map +0 -1
  86. package/dist/src/benchmarks/capability-tasks.js +0 -147
  87. package/dist/src/benchmarks/capability-tasks.js.map +0 -1
  88. package/dist/src/benchmarks/gaia-agent-contrastive.d.ts +0 -53
  89. package/dist/src/benchmarks/gaia-agent-contrastive.d.ts.map +0 -1
  90. package/dist/src/benchmarks/gaia-agent-contrastive.js +0 -177
  91. package/dist/src/benchmarks/gaia-agent-contrastive.js.map +0 -1
  92. package/dist/src/benchmarks/gaia-agent-gemini.d.ts +0 -51
  93. package/dist/src/benchmarks/gaia-agent-gemini.d.ts.map +0 -1
  94. package/dist/src/benchmarks/gaia-agent-gemini.js +0 -352
  95. package/dist/src/benchmarks/gaia-agent-gemini.js.map +0 -1
  96. package/dist/src/benchmarks/gaia-claude-p.d.ts +0 -105
  97. package/dist/src/benchmarks/gaia-claude-p.d.ts.map +0 -1
  98. package/dist/src/benchmarks/gaia-claude-p.js +0 -269
  99. package/dist/src/benchmarks/gaia-claude-p.js.map +0 -1
  100. package/dist/src/benchmarks/gaia-claude-p.smoke.d.ts +0 -20
  101. package/dist/src/benchmarks/gaia-claude-p.smoke.d.ts.map +0 -1
  102. package/dist/src/benchmarks/gaia-claude-p.smoke.js +0 -168
  103. package/dist/src/benchmarks/gaia-claude-p.smoke.js.map +0 -1
  104. package/dist/src/benchmarks/gaia-codeagent-runner.py +0 -556
  105. package/dist/src/benchmarks/gaia-codeagent.d.ts +0 -129
  106. package/dist/src/benchmarks/gaia-codeagent.d.ts.map +0 -1
  107. package/dist/src/benchmarks/gaia-codeagent.js +0 -590
  108. package/dist/src/benchmarks/gaia-codeagent.js.map +0 -1
  109. package/dist/src/benchmarks/gaia-codeagent.smoke.d.ts +0 -33
  110. package/dist/src/benchmarks/gaia-codeagent.smoke.d.ts.map +0 -1
  111. package/dist/src/benchmarks/gaia-codeagent.smoke.js +0 -241
  112. package/dist/src/benchmarks/gaia-codeagent.smoke.js.map +0 -1
  113. package/dist/src/benchmarks/gaia-dag-pilot.d.ts +0 -22
  114. package/dist/src/benchmarks/gaia-dag-pilot.d.ts.map +0 -1
  115. package/dist/src/benchmarks/gaia-dag-pilot.js +0 -158
  116. package/dist/src/benchmarks/gaia-dag-pilot.js.map +0 -1
  117. package/dist/src/benchmarks/gaia-dag.d.ts +0 -110
  118. package/dist/src/benchmarks/gaia-dag.d.ts.map +0 -1
  119. package/dist/src/benchmarks/gaia-dag.js +0 -658
  120. package/dist/src/benchmarks/gaia-dag.js.map +0 -1
  121. package/dist/src/benchmarks/gaia-dag.smoke.d.ts +0 -21
  122. package/dist/src/benchmarks/gaia-dag.smoke.d.ts.map +0 -1
  123. package/dist/src/benchmarks/gaia-dag.smoke.js +0 -290
  124. package/dist/src/benchmarks/gaia-dag.smoke.js.map +0 -1
  125. package/dist/src/benchmarks/gaia-ensemble-pilot.d.ts +0 -17
  126. package/dist/src/benchmarks/gaia-ensemble-pilot.d.ts.map +0 -1
  127. package/dist/src/benchmarks/gaia-ensemble-pilot.js +0 -106
  128. package/dist/src/benchmarks/gaia-ensemble-pilot.js.map +0 -1
  129. package/dist/src/benchmarks/gaia-ensemble.d.ts +0 -103
  130. package/dist/src/benchmarks/gaia-ensemble.d.ts.map +0 -1
  131. package/dist/src/benchmarks/gaia-ensemble.js +0 -462
  132. package/dist/src/benchmarks/gaia-ensemble.js.map +0 -1
  133. package/dist/src/benchmarks/gaia-gemini-pilot.d.ts +0 -17
  134. package/dist/src/benchmarks/gaia-gemini-pilot.d.ts.map +0 -1
  135. package/dist/src/benchmarks/gaia-gemini-pilot.js +0 -168
  136. package/dist/src/benchmarks/gaia-gemini-pilot.js.map +0 -1
  137. package/dist/src/benchmarks/gaia-mode-router.d.ts +0 -67
  138. package/dist/src/benchmarks/gaia-mode-router.d.ts.map +0 -1
  139. package/dist/src/benchmarks/gaia-mode-router.js +0 -126
  140. package/dist/src/benchmarks/gaia-mode-router.js.map +0 -1
  141. package/dist/src/benchmarks/gaia-mode-router.smoke.d.ts +0 -15
  142. package/dist/src/benchmarks/gaia-mode-router.smoke.d.ts.map +0 -1
  143. package/dist/src/benchmarks/gaia-mode-router.smoke.js +0 -131
  144. package/dist/src/benchmarks/gaia-mode-router.smoke.js.map +0 -1
  145. package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.d.ts +0 -24
  146. package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.d.ts.map +0 -1
  147. package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.js +0 -276
  148. package/dist/src/benchmarks/gaia-tools/grounded_query.smoke.js.map +0 -1
  149. package/dist/src/benchmarks/gaia-tools/image_describe.d.ts +0 -45
  150. package/dist/src/benchmarks/gaia-tools/image_describe.d.ts.map +0 -1
  151. package/dist/src/benchmarks/gaia-tools/image_describe.js +0 -222
  152. package/dist/src/benchmarks/gaia-tools/image_describe.js.map +0 -1
  153. package/dist/src/benchmarks/gaia-tools/image_describe.smoke.d.ts +0 -24
  154. package/dist/src/benchmarks/gaia-tools/image_describe.smoke.d.ts.map +0 -1
  155. package/dist/src/benchmarks/gaia-tools/image_describe.smoke.js +0 -169
  156. package/dist/src/benchmarks/gaia-tools/image_describe.smoke.js.map +0 -1
  157. package/dist/src/benchmarks/gaia-tools/pdf_read.d.ts +0 -27
  158. package/dist/src/benchmarks/gaia-tools/pdf_read.d.ts.map +0 -1
  159. package/dist/src/benchmarks/gaia-tools/pdf_read.js +0 -226
  160. package/dist/src/benchmarks/gaia-tools/pdf_read.js.map +0 -1
  161. package/dist/src/benchmarks/gaia-tools/python_exec.d.ts +0 -31
  162. package/dist/src/benchmarks/gaia-tools/python_exec.d.ts.map +0 -1
  163. package/dist/src/benchmarks/gaia-tools/python_exec.js +0 -94
  164. package/dist/src/benchmarks/gaia-tools/python_exec.js.map +0 -1
  165. package/dist/src/benchmarks/gaia-tools/python_exec.smoke.d.ts +0 -13
  166. package/dist/src/benchmarks/gaia-tools/python_exec.smoke.d.ts.map +0 -1
  167. package/dist/src/benchmarks/gaia-tools/python_exec.smoke.js +0 -98
  168. package/dist/src/benchmarks/gaia-tools/python_exec.smoke.js.map +0 -1
  169. package/dist/src/benchmarks/gaia-tools/visit_webpage.d.ts +0 -52
  170. package/dist/src/benchmarks/gaia-tools/visit_webpage.d.ts.map +0 -1
  171. package/dist/src/benchmarks/gaia-tools/visit_webpage.js +0 -304
  172. package/dist/src/benchmarks/gaia-tools/visit_webpage.js.map +0 -1
  173. package/dist/src/benchmarks/gaia-tools/web_browse.d.ts +0 -61
  174. package/dist/src/benchmarks/gaia-tools/web_browse.d.ts.map +0 -1
  175. package/dist/src/benchmarks/gaia-tools/web_browse.js +0 -217
  176. package/dist/src/benchmarks/gaia-tools/web_browse.js.map +0 -1
  177. package/dist/src/benchmarks/gaia-tools/web_browse.smoke.d.ts +0 -26
  178. package/dist/src/benchmarks/gaia-tools/web_browse.smoke.d.ts.map +0 -1
  179. package/dist/src/benchmarks/gaia-tools/web_browse.smoke.js +0 -152
  180. package/dist/src/benchmarks/gaia-tools/web_browse.smoke.js.map +0 -1
  181. package/dist/src/benchmarks/gaia-tools/web_search.smoke.d.ts +0 -24
  182. package/dist/src/benchmarks/gaia-tools/web_search.smoke.d.ts.map +0 -1
  183. package/dist/src/benchmarks/gaia-tools/web_search.smoke.js +0 -311
  184. package/dist/src/benchmarks/gaia-tools/web_search.smoke.js.map +0 -1
  185. package/dist/src/benchmarks/gaia-visit-webpage.smoke.d.ts +0 -25
  186. package/dist/src/benchmarks/gaia-visit-webpage.smoke.d.ts.map +0 -1
  187. package/dist/src/benchmarks/gaia-visit-webpage.smoke.js +0 -304
  188. package/dist/src/benchmarks/gaia-visit-webpage.smoke.js.map +0 -1
  189. package/dist/src/commands/performance-capability.d.ts +0 -30
  190. package/dist/src/commands/performance-capability.d.ts.map +0 -1
  191. package/dist/src/commands/performance-capability.js +0 -349
  192. 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();