@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,272 @@
1
+ #!/usr/bin/env node
2
+ // audit-trend.mjs — diff two oia-audit records to detect drift over time.
3
+ //
4
+ // The iter-7 oia-audit composite worker stores timestamped records under
5
+ // the `metaharness-audit` memory namespace. This script reads two such
6
+ // records and surfaces the delta:
7
+ // - composite worst severity change (clean → low, low → medium, etc.)
8
+ // - per-component (oia / threat-model / mcp-scan) status change
9
+ // - new HIGH-severity findings introduced
10
+ // - findings cleared
11
+ //
12
+ // Pairs with iter-7's oia-audit + iter-8's weekly cron — accumulated
13
+ // records enable drift detection without ad-hoc tooling.
14
+ //
15
+ // USAGE
16
+ // node scripts/audit-trend.mjs --baseline audit-<ts1>.json --current audit-<ts2>.json
17
+ // node scripts/audit-trend.mjs --baseline-key audit-2026-06-01... --current-key audit-2026-06-15...
18
+ // # pulls both from memory namespace `metaharness-audit`
19
+ // node scripts/audit-trend.mjs ... --alert-on-worsening
20
+ // node scripts/audit-trend.mjs ... --format json
21
+ //
22
+ // EXIT CODES
23
+ // 0 ok (no worsening, or --alert-on-worsening not set)
24
+ // 1 --alert-on-worsening AND composite severity worsened
25
+ // 2 config error or input not found
26
+
27
+ import { readFileSync, existsSync } from 'node:fs';
28
+ import { spawnSync } from 'node:child_process';
29
+ // iter 38 — structural-distance drift via ADR-152 §3.1 production module.
30
+ // Falls back to null if either record predates iter-38 oia-audit (no
31
+ // fingerprint field) — graceful degradation, never throws.
32
+ import { similarity } from './_similarity.mjs';
33
+ // iter 63 — shared SEVERITY_RANK from _harness.mjs (was a local literal
34
+ // missing info/warn/error/critical, which caused NaN-compare hazards).
35
+ import { SEVERITY_RANK, rankSeverity } from './_harness.mjs';
36
+
37
+ // iter 63 — SEVERITY_RANK moved to _harness.mjs (imported above)
38
+ const NS = process.env.AUDIT_TREND_NAMESPACE || 'metaharness-audit';
39
+ const CLI_PKG = process.env.CLI_CORE === '1'
40
+ ? '@claude-flow/cli-core@alpha'
41
+ : '@claude-flow/cli@latest';
42
+
43
+ const ARGS = (() => {
44
+ const a = {
45
+ baseline: null, current: null,
46
+ baselineKey: null, currentKey: null,
47
+ alertOnWorsening: false, format: 'table',
48
+ // iter 38 — structural-distance gate (ADR-152 §3.1 dep)
49
+ alertOnDistanceBelow: null,
50
+ };
51
+ for (let i = 2; i < process.argv.length; i++) {
52
+ const v = process.argv[i];
53
+ if (v === '--baseline') a.baseline = process.argv[++i];
54
+ else if (v === '--current') a.current = process.argv[++i];
55
+ else if (v === '--baseline-key') a.baselineKey = process.argv[++i];
56
+ else if (v === '--current-key') a.currentKey = process.argv[++i];
57
+ else if (v === '--alert-on-worsening') a.alertOnWorsening = true;
58
+ else if (v === '--alert-on-distance-below') a.alertOnDistanceBelow = Number(process.argv[++i]);
59
+ else if (v === '--format') a.format = process.argv[++i];
60
+ }
61
+ return a;
62
+ })();
63
+
64
+ function memRetrieve(key) {
65
+ const r = spawnSync('npx', [
66
+ CLI_PKG, 'memory', 'retrieve',
67
+ '--namespace', NS, '--key', key,
68
+ ], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8', shell: process.platform === 'win32' });
69
+ if (r.status !== 0) return null;
70
+ const m = /\{[\s\S]*\}/.exec(r.stdout || '');
71
+ if (!m) return null;
72
+ try { return JSON.parse(m[0]); } catch { return null; }
73
+ }
74
+
75
+ function loadRecord(label, filePath, memKey) {
76
+ if (filePath) {
77
+ if (!existsSync(filePath)) {
78
+ console.error(`audit-trend: ${label} file not found: ${filePath}`);
79
+ process.exit(2);
80
+ }
81
+ try { return JSON.parse(readFileSync(filePath, 'utf-8')); }
82
+ catch (e) {
83
+ console.error(`audit-trend: ${label} file invalid JSON: ${e.message}`);
84
+ process.exit(2);
85
+ }
86
+ }
87
+ if (memKey) {
88
+ const rec = memRetrieve(memKey);
89
+ if (!rec) {
90
+ console.error(`audit-trend: ${label} key not found in namespace ${NS}: ${memKey}`);
91
+ process.exit(2);
92
+ }
93
+ return rec;
94
+ }
95
+ console.error(`audit-trend: --${label} or --${label}-key is required`);
96
+ process.exit(2);
97
+ }
98
+
99
+ function main() {
100
+ const baseline = loadRecord('baseline', ARGS.baseline, ARGS.baselineKey);
101
+ const current = loadRecord('current', ARGS.current, ARGS.currentKey);
102
+
103
+ // Sanity: both must look like oia-audit records (composite + components).
104
+ if (!baseline.composite || !current.composite) {
105
+ console.error('audit-trend: snapshots must contain `composite` (from oia-audit.mjs)');
106
+ process.exit(2);
107
+ }
108
+
109
+ const baseWorst = String(baseline.composite.worst || 'clean').toLowerCase();
110
+ const currWorst = String(current.composite.worst || 'clean').toLowerCase();
111
+ // iter 63 — safe rankSeverity() eliminates NaN-compare when either
112
+ // severity is unknown (was a latent bug in audit-trend's drift verdict).
113
+ const deltaRank = rankSeverity(currWorst) - rankSeverity(baseWorst);
114
+ const worsened = deltaRank > 0;
115
+ const improved = deltaRank < 0;
116
+
117
+ // Per-component status change. Each `components.{oiaManifest, threatModel,
118
+ // mcpScan}` has degraded:bool + exitCode + (for threat-model) json.worst.
119
+ const componentDelta = (label, b, c) => {
120
+ if (!b || !c) return { label, delta: 'missing' };
121
+ if (b.degraded !== c.degraded) {
122
+ return { label, delta: c.degraded ? 'became degraded' : 'recovered from degraded' };
123
+ }
124
+ if (b.exitCode !== c.exitCode) {
125
+ return { label, delta: `exit code ${b.exitCode} → ${c.exitCode}` };
126
+ }
127
+ return { label, delta: 'unchanged' };
128
+ };
129
+ const components = {
130
+ oiaManifest: componentDelta('oia-manifest', baseline.components?.oiaManifest, current.components?.oiaManifest),
131
+ threatModel: componentDelta('threat-model', baseline.components?.threatModel, current.components?.threatModel),
132
+ mcpScan: componentDelta('mcp-scan', baseline.components?.mcpScan, current.components?.mcpScan),
133
+ };
134
+
135
+ // mcp-scan findings — what was introduced vs cleared.
136
+ const baseFindings = Array.isArray(baseline.components?.mcpScan?.json?.findings)
137
+ ? baseline.components.mcpScan.json.findings : [];
138
+ const currFindings = Array.isArray(current.components?.mcpScan?.json?.findings)
139
+ ? current.components.mcpScan.json.findings : [];
140
+ const fingerprint = (f) => `${f.severity}:${f.id ?? '-'}:${f.server ?? '-'}:${f.tool ?? '-'}:${(f.message ?? '').slice(0, 80)}`;
141
+ const baseSet = new Set(baseFindings.map(fingerprint));
142
+ const currSet = new Set(currFindings.map(fingerprint));
143
+ const introduced = currFindings.filter((f) => !baseSet.has(fingerprint(f)));
144
+ const cleared = baseFindings.filter((f) => !currSet.has(fingerprint(f)));
145
+
146
+ // iter 38 — structural distance via ADR-152 §3.1 similarity().
147
+ // Both records need a fingerprint (score+genome) — iter-38 oia-audit
148
+ // adds it; older records skip with verdict 'unavailable'.
149
+ let structuralDistance = null;
150
+ if (baseline.fingerprint?.score && baseline.fingerprint?.genome
151
+ && current.fingerprint?.score && current.fingerprint?.genome) {
152
+ const sim = similarity(baseline.fingerprint, current.fingerprint);
153
+ structuralDistance = {
154
+ overall: sim.overall,
155
+ // Distance is the complement of similarity in [0,1]
156
+ distance: Number((1 - sim.overall).toFixed(4)),
157
+ components: sim.components,
158
+ verdict: sim.overall >= 0.95 ? 'near-identical'
159
+ : sim.overall >= 0.80 ? 'minor-drift'
160
+ : sim.overall >= 0.50 ? 'moderate-drift'
161
+ : 'major-drift',
162
+ };
163
+ } else {
164
+ structuralDistance = {
165
+ verdict: 'unavailable',
166
+ reason: 'one or both records predate iter-38 oia-audit fingerprint bundling',
167
+ };
168
+ }
169
+
170
+ // Distance alert is independent of severity worsening — a harness can
171
+ // structurally drift while keeping the same worst-severity verdict.
172
+ const distanceAlertTriggered = ARGS.alertOnDistanceBelow != null
173
+ && structuralDistance.overall != null
174
+ && structuralDistance.overall < ARGS.alertOnDistanceBelow;
175
+
176
+ const payload = {
177
+ baseline: {
178
+ startedAt: baseline.startedAt,
179
+ composite: baseline.composite,
180
+ },
181
+ current: {
182
+ startedAt: current.startedAt,
183
+ composite: current.composite,
184
+ },
185
+ delta: {
186
+ worst: { baseline: baseWorst, current: currWorst, rankDelta: deltaRank,
187
+ verdict: worsened ? 'worsened' : (improved ? 'improved' : 'unchanged') },
188
+ components,
189
+ findings: {
190
+ introducedCount: introduced.length,
191
+ clearedCount: cleared.length,
192
+ introduced: introduced.slice(0, 20), // truncate for output sanity
193
+ cleared: cleared.slice(0, 20),
194
+ },
195
+ // iter 38 — structural distance via ADR-152 §3.1
196
+ structuralDistance,
197
+ },
198
+ alert: (ARGS.alertOnWorsening || ARGS.alertOnDistanceBelow != null) ? {
199
+ triggered: worsened || distanceAlertTriggered,
200
+ reasons: [
201
+ ARGS.alertOnWorsening && worsened
202
+ ? `composite worst ${baseWorst} → ${currWorst}` : null,
203
+ distanceAlertTriggered
204
+ ? `structural similarity ${structuralDistance.overall} < threshold ${ARGS.alertOnDistanceBelow}` : null,
205
+ ].filter(Boolean),
206
+ } : null,
207
+ generatedAt: new Date().toISOString(),
208
+ };
209
+
210
+ if (ARGS.format === 'json') {
211
+ console.log(JSON.stringify(payload, null, 2));
212
+ } else {
213
+ console.log(`# audit-trend`);
214
+ console.log('');
215
+ console.log(`Baseline: ${baseline.startedAt}`);
216
+ console.log(`Current: ${current.startedAt}`);
217
+ console.log('');
218
+ console.log(`| Metric | Baseline | Current | Verdict |`);
219
+ console.log(`|---|---|---|---|`);
220
+ const verdictIcon = worsened ? '⚠ worsened' : (improved ? '✓ improved' : '— unchanged');
221
+ console.log(`| composite worst | ${baseWorst} | ${currWorst} | ${verdictIcon} |`);
222
+ for (const [label, c] of Object.entries(components)) {
223
+ console.log(`| ${label} | — | — | ${c.delta} |`);
224
+ }
225
+ console.log('');
226
+ console.log(`Findings: **+${introduced.length} introduced**, **−${cleared.length} cleared**`);
227
+ if (introduced.length > 0) {
228
+ console.log('');
229
+ console.log('## Introduced findings');
230
+ console.log('');
231
+ console.log('| Severity | Server | Tool | Message |');
232
+ console.log('|---|---|---|---|');
233
+ for (const f of introduced.slice(0, 20)) {
234
+ console.log(`| ${f.severity ?? '-'} | ${f.server ?? '-'} | ${f.tool ?? '-'} | ${(f.message ?? '').slice(0, 80)} |`);
235
+ }
236
+ }
237
+ if (cleared.length > 0) {
238
+ console.log('');
239
+ console.log('## Cleared findings');
240
+ console.log('');
241
+ for (const f of cleared.slice(0, 5)) {
242
+ console.log(`- ${f.severity ?? '-'} ${f.id ?? '-'} (${f.server ?? '-'}/${f.tool ?? '-'})`);
243
+ }
244
+ }
245
+ console.log('');
246
+ // iter 38 — structural distance row
247
+ if (structuralDistance.verdict !== 'unavailable') {
248
+ console.log(`## Structural distance (ADR-152 §3.1)`);
249
+ console.log('');
250
+ console.log(`| Metric | Value |`);
251
+ console.log(`|---|---:|`);
252
+ console.log(`| overall similarity | ${structuralDistance.overall.toFixed(4)} |`);
253
+ console.log(`| distance (1 − sim) | ${structuralDistance.distance.toFixed(4)} |`);
254
+ console.log(`| verdict | **${structuralDistance.verdict}** |`);
255
+ console.log('');
256
+ } else {
257
+ console.log(`Structural distance: _unavailable — ${structuralDistance.reason}_`);
258
+ console.log('');
259
+ }
260
+ if (payload.alert) {
261
+ if (payload.alert.triggered) {
262
+ console.log(`⚠ **ALERT**: ${payload.alert.reasons.join('; ')}`);
263
+ } else {
264
+ console.log(`✓ no alert triggered`);
265
+ }
266
+ }
267
+ }
268
+
269
+ if (payload.alert?.triggered) process.exit(1);
270
+ }
271
+
272
+ main();
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ // bench-parse-mcp-scan — perf characterization for iter-50's
3
+ // parseMcpScanText. Sister to iter-41's bench-similarity.mjs.
4
+ //
5
+ // THE CLAIM
6
+ // parseMcpScanText is a small regex over short text. Sub-microsecond
7
+ // per call expected. Phase-3 consumers (oia-audit + the audit-trend
8
+ // introduced/cleared diff) call it once per audit run, so the per-call
9
+ // cost compounds if the parser is on a slow path.
10
+ //
11
+ // WHAT IT MEASURES
12
+ // Per-call mean + p50 + p99 over 100k iters, across 3 categories:
13
+ // - empty: parseMcpScanText('') — fastest path
14
+ // - typical: ruflo's actual single-INFO output
15
+ // - rich: a synthetic multi-finding payload with continuation lines
16
+ //
17
+ // USAGE
18
+ // node scripts/bench-parse-mcp-scan.mjs # default 100k iters
19
+ // node scripts/bench-parse-mcp-scan.mjs --iters 1000000
20
+ // node scripts/bench-parse-mcp-scan.mjs --format json
21
+ // node scripts/bench-parse-mcp-scan.mjs --max-mean-us 5 # CI gate
22
+ //
23
+ // EXIT CODES
24
+ // 0 ok (or threshold satisfied)
25
+ // 1 --max-mean-us exceeded by any category
26
+
27
+ import { performance } from 'node:perf_hooks';
28
+ import { parseMcpScanText } from './_harness.mjs';
29
+
30
+ const ARGS = (() => {
31
+ const a = { iters: 100_000, format: 'table', maxMeanUs: null };
32
+ for (let i = 2; i < process.argv.length; i++) {
33
+ const v = process.argv[i];
34
+ if (v === '--iters') a.iters = parseInt(process.argv[++i], 10);
35
+ else if (v === '--format') a.format = process.argv[++i];
36
+ else if (v === '--max-mean-us') a.maxMeanUs = parseFloat(process.argv[++i]);
37
+ }
38
+ return a;
39
+ })();
40
+
41
+ const EMPTY = '';
42
+
43
+ const TYPICAL = `harness mcp-scan — /repo
44
+
45
+ [INFO] No MCP security issues found
46
+ Policy is default-deny with safe capability grants and an audit log.
47
+
48
+ Result: INFO (1 finding, 0 high)
49
+ `;
50
+
51
+ const RICH = `harness mcp-scan — /repo
52
+
53
+ [HIGH] First high-severity finding
54
+ Continuation line 1
55
+ Continuation line 2
56
+ [WARN] Second warning finding
57
+ Some additional context
58
+ [HIGH] Third high finding
59
+ [INFO] Fourth informational
60
+ Multi-line message body
61
+ Spanning three lines
62
+ Ending here
63
+ [CRITICAL] Fifth critical issue
64
+ With single continuation
65
+
66
+ Result: HIGH (5 findings, 2 high)
67
+ `;
68
+
69
+ function bench(label, input, iters) {
70
+ // Warm-up
71
+ for (let i = 0; i < 10_000; i++) parseMcpScanText(input);
72
+
73
+ const samples = new Float64Array(iters);
74
+ for (let i = 0; i < iters; i++) {
75
+ const t0 = performance.now();
76
+ parseMcpScanText(input);
77
+ samples[i] = performance.now() - t0;
78
+ }
79
+ let sum = 0;
80
+ for (let i = 0; i < iters; i++) sum += samples[i];
81
+ const mean = sum / iters;
82
+ const sorted = Array.from(samples).sort((x, y) => x - y);
83
+ const p50 = sorted[Math.floor(iters * 0.5)];
84
+ const p99 = sorted[Math.floor(iters * 0.99)];
85
+ return {
86
+ label, iters,
87
+ meanMs: mean, p50Ms: p50, p99Ms: p99,
88
+ meanUs: mean * 1000, p99Us: p99 * 1000,
89
+ };
90
+ }
91
+
92
+ // iter 87 — suppress markdown header in --format json so captured
93
+ // file stays valid JSON.
94
+ if (ARGS.format !== 'json') {
95
+ console.log(`# bench-parse-mcp-scan — iter-50 parser per-call cost\n`);
96
+ console.log(`iters: ${ARGS.iters.toLocaleString()}\n`);
97
+ }
98
+
99
+ const results = [
100
+ bench('empty', EMPTY, ARGS.iters),
101
+ bench('typical (1 INFO)', TYPICAL, ARGS.iters),
102
+ bench('rich (5 findings)', RICH, ARGS.iters),
103
+ ];
104
+
105
+ let gate = { triggered: false, reasons: [] };
106
+ if (ARGS.maxMeanUs != null) {
107
+ for (const r of results) {
108
+ if (r.meanUs > ARGS.maxMeanUs) {
109
+ gate.triggered = true;
110
+ gate.reasons.push(`${r.label}: mean ${r.meanUs.toFixed(3)}μs > ceiling ${ARGS.maxMeanUs}μs`);
111
+ }
112
+ }
113
+ }
114
+
115
+ const payload = {
116
+ iters: ARGS.iters,
117
+ results,
118
+ gate: ARGS.maxMeanUs != null ? {
119
+ thresholdUs: ARGS.maxMeanUs,
120
+ triggered: gate.triggered,
121
+ reasons: gate.reasons,
122
+ } : null,
123
+ generatedAt: new Date().toISOString(),
124
+ contract: 'parseMcpScanText is sub-microsecond for empty/typical; rich (5 findings, 14 lines) stays under 10μs.',
125
+ };
126
+
127
+ if (ARGS.format === 'json') {
128
+ console.log(JSON.stringify(payload, null, 2));
129
+ } else {
130
+ console.log(`| Category | mean | p50 | p99 |`);
131
+ console.log(`|---------------------|-----------:|-----------:|-----------:|`);
132
+ for (const r of results) {
133
+ console.log(`| ${r.label.padEnd(19)} | ${r.meanUs.toFixed(3).padStart(7)}μs | ${(r.p50Ms * 1000).toFixed(3).padStart(7)}μs | ${r.p99Us.toFixed(3).padStart(7)}μs |`);
134
+ }
135
+ console.log('');
136
+ if (payload.gate) {
137
+ if (payload.gate.triggered) {
138
+ console.log(`⚠ ALERT: ceiling ${ARGS.maxMeanUs}μs exceeded by:`);
139
+ for (const reason of payload.gate.reasons) console.log(` - ${reason}`);
140
+ } else {
141
+ console.log(`✓ all categories within --max-mean-us ${ARGS.maxMeanUs}μs ceiling`);
142
+ }
143
+ }
144
+ }
145
+
146
+ if (gate.triggered) process.exit(1);
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ // bench-recordpair-overhead.mjs — micro-benchmark proving iter-12's
3
+ // "zero default-path overhead" claim with measured numbers.
4
+ //
5
+ // THE CLAIM
6
+ // Iter 12 added one env-flag check to model-router.ts route() so
7
+ // recordPair() fires only when CLAUDE_FLOW_ROUTER_PARALLEL_LOG=1.
8
+ // When unset (default), the cost is:
9
+ //
10
+ // if (process.env.CLAUDE_FLOW_ROUTER_PARALLEL_LOG === '1') { ... }
11
+ //
12
+ // This benchmark measures that exact pattern over 1,000,000 iterations
13
+ // and reports the per-call mean + p99. For the claim to hold, the
14
+ // per-call overhead must be sub-microsecond.
15
+ //
16
+ // USAGE
17
+ // node scripts/bench-recordpair-overhead.mjs # 1M iters
18
+ // node scripts/bench-recordpair-overhead.mjs --iters 5000000
19
+ // node scripts/bench-recordpair-overhead.mjs --format json
20
+ //
21
+ // EXIT CODES
22
+ // 0 benchmark complete (always; this is informational, not pass/fail)
23
+
24
+ import { performance } from 'node:perf_hooks';
25
+
26
+ const ARGS = (() => {
27
+ const a = {
28
+ iters: 1_000_000,
29
+ format: 'table',
30
+ // iter 25 — CI regression gate. When --max-overhead-ns N is set,
31
+ // exit 1 if the measured iter-12 default-path overhead exceeds N
32
+ // nanoseconds per call. Default threshold 500ns chosen as ~3.5×
33
+ // headroom over the iter-24 measured baseline of ~147ns on Apple
34
+ // Silicon / Node 22.
35
+ maxOverheadNs: null,
36
+ };
37
+ for (let i = 2; i < process.argv.length; i++) {
38
+ const v = process.argv[i];
39
+ if (v === '--iters') a.iters = parseInt(process.argv[++i], 10);
40
+ else if (v === '--format') a.format = process.argv[++i];
41
+ else if (v === '--max-overhead-ns') a.maxOverheadNs = parseFloat(process.argv[++i]);
42
+ }
43
+ return a;
44
+ })();
45
+
46
+ function bench(label, fn, iters) {
47
+ // Warm-up
48
+ for (let i = 0; i < 10_000; i++) fn();
49
+
50
+ const samples = new Float64Array(iters);
51
+ for (let i = 0; i < iters; i++) {
52
+ const start = performance.now();
53
+ fn();
54
+ samples[i] = performance.now() - start;
55
+ }
56
+ // performance.now() resolution is ~1 microsecond on most platforms.
57
+ // For sub-microsecond ops we can't measure individual iterations
58
+ // accurately; report aggregate timing instead.
59
+
60
+ // Also measure batched timing — sum of N calls in one hot loop —
61
+ // which IS accurate for sub-μs ops.
62
+ const batchStart = performance.now();
63
+ for (let i = 0; i < iters; i++) fn();
64
+ const batchTotal = performance.now() - batchStart;
65
+ const meanNs = (batchTotal * 1e6) / iters; // ms → ns / iters
66
+
67
+ // Sort samples for percentile reporting (per-call resolution-limited)
68
+ const sortedMs = Array.from(samples).sort((a, b) => a - b);
69
+ const p99 = sortedMs[Math.floor(0.99 * sortedMs.length)];
70
+
71
+ return {
72
+ label,
73
+ iters,
74
+ batchTotalMs: Math.round(batchTotal * 1000) / 1000,
75
+ meanNsPerCall: Math.round(meanNs * 100) / 100,
76
+ p99PerCallMs: Math.round(p99 * 10000) / 10000,
77
+ };
78
+ }
79
+
80
+ function main() {
81
+ const iters = ARGS.iters;
82
+ if (!Number.isInteger(iters) || iters < 1000) {
83
+ console.error('bench-recordpair-overhead: --iters must be ≥ 1000');
84
+ process.exit(2);
85
+ }
86
+
87
+ // iter 137 — guard the markdown header behind table-format so
88
+ // --format json emits pure JSON (consumed by CI's JSON.parse).
89
+ // Same fix-class as bench-similarity's iter 87 fix.
90
+ if (ARGS.format !== 'json') {
91
+ console.log(`# bench-recordpair-overhead`);
92
+ console.log('');
93
+ console.log(`Iterations: ${iters.toLocaleString()}`);
94
+ console.log(`Platform: ${process.platform} ${process.arch}, Node ${process.version}`);
95
+ console.log('');
96
+ }
97
+
98
+ // Baseline — no-op
99
+ const baseline = bench('baseline (no-op)', () => {}, iters);
100
+
101
+ // The iter-12 env-flag check, OFF case
102
+ // Match the exact source pattern from model-router.ts
103
+ delete process.env.CLAUDE_FLOW_ROUTER_PARALLEL_LOG; // ensure OFF
104
+ const envCheckOff = bench(
105
+ 'iter-12 env check (FLAG OFF — default path)',
106
+ () => { if (process.env.CLAUDE_FLOW_ROUTER_PARALLEL_LOG === '1') { /* unreached */ } },
107
+ iters,
108
+ );
109
+
110
+ // ON case — but no actual loadParallelRecorder().then() call (that's
111
+ // a one-time microtask amortized over the process lifetime). Measure
112
+ // just the conditional.
113
+ process.env.CLAUDE_FLOW_ROUTER_PARALLEL_LOG = '1';
114
+ const envCheckOn = bench(
115
+ 'iter-12 env check (FLAG ON — branch taken, NO recordPair)',
116
+ () => { if (process.env.CLAUDE_FLOW_ROUTER_PARALLEL_LOG === '1') { /* branch taken */ } },
117
+ iters,
118
+ );
119
+ delete process.env.CLAUDE_FLOW_ROUTER_PARALLEL_LOG;
120
+
121
+ // What does the LAZY-LOADER short-circuit cost when called repeatedly?
122
+ // Once initialized, it's a single nullness check on a module-level var.
123
+ let _cached = null;
124
+ const lazyLoaderInit = () => {
125
+ if (_cached === null) _cached = { mod: { recordPair: () => {} } };
126
+ return _cached;
127
+ };
128
+ // Run once to populate the cache
129
+ lazyLoaderInit();
130
+ const lazyLoaderHot = bench(
131
+ 'lazy loader (post-init nullness check)',
132
+ () => { lazyLoaderInit(); },
133
+ iters,
134
+ );
135
+
136
+ const results = [baseline, envCheckOff, envCheckOn, lazyLoaderHot];
137
+
138
+ if (ARGS.format === 'json') {
139
+ console.log(JSON.stringify({
140
+ platform: { os: process.platform, arch: process.arch, node: process.version },
141
+ iters,
142
+ results,
143
+ generatedAt: new Date().toISOString(),
144
+ }, null, 2));
145
+ return;
146
+ }
147
+
148
+ console.log(`| Variant | Total (ms) | Mean per-call (ns) | p99 sample (ms) |`);
149
+ console.log(`|---|---:|---:|---:|`);
150
+ for (const r of results) {
151
+ console.log(`| ${r.label} | ${r.batchTotalMs} | ${r.meanNsPerCall} | ${r.p99PerCallMs} |`);
152
+ }
153
+
154
+ // Interpret
155
+ const overhead = envCheckOff.meanNsPerCall - baseline.meanNsPerCall;
156
+ console.log('');
157
+ console.log(`**Iter-12 default-path overhead**: ~${Math.round(overhead)}ns per route() call`);
158
+ console.log('');
159
+ if (overhead < 100) {
160
+ console.log(`✓ Overhead < 100ns confirms iter-12's "zero default-path overhead" claim.`);
161
+ console.log(` At 1000 routing decisions per workload, total added cost is ~${Math.round(overhead * 1000 / 1000)}μs — imperceptible.`);
162
+ } else if (overhead < 1000) {
163
+ console.log(`✓ Overhead < 1μs is well within tolerable bounds.`);
164
+ } else {
165
+ console.log(`⚠ Overhead ${Math.round(overhead)}ns is higher than expected; investigate engine optimization.`);
166
+ }
167
+
168
+ // CI regression gate (iter 25).
169
+ if (ARGS.maxOverheadNs !== null) {
170
+ if (!isFinite(ARGS.maxOverheadNs) || ARGS.maxOverheadNs <= 0) {
171
+ console.error('bench-recordpair-overhead: --max-overhead-ns must be a positive number');
172
+ process.exit(2);
173
+ }
174
+ console.log('');
175
+ if (overhead > ARGS.maxOverheadNs) {
176
+ console.log(`⚠ **REGRESSION**: measured ${Math.round(overhead)}ns > threshold ${ARGS.maxOverheadNs}ns`);
177
+ console.log(` The iter-12 dispatch wiring may have grown beyond the env-flag check.`);
178
+ console.log(` Inspect v3/@claude-flow/cli/src/ruvector/model-router.ts route() for new work in the default path.`);
179
+ process.exit(1);
180
+ } else {
181
+ console.log(`✓ Within regression threshold (${Math.round(overhead)}ns ≤ ${ARGS.maxOverheadNs}ns).`);
182
+ }
183
+ }
184
+ }
185
+
186
+ main();