modestbench 0.1.0 → 0.3.0

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 (484) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +162 -57
  3. package/dist/bootstrap.cjs +10 -10
  4. package/dist/bootstrap.cjs.map +1 -1
  5. package/dist/bootstrap.d.cts.map +1 -1
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +5 -5
  8. package/dist/bootstrap.js.map +1 -1
  9. package/dist/cli/commands/analyze.cjs +60 -0
  10. package/dist/cli/commands/analyze.cjs.map +1 -0
  11. package/dist/cli/commands/analyze.d.cts +35 -0
  12. package/dist/cli/commands/analyze.d.cts.map +1 -0
  13. package/dist/cli/commands/analyze.d.ts +35 -0
  14. package/dist/cli/commands/analyze.d.ts.map +1 -0
  15. package/dist/cli/commands/analyze.js +56 -0
  16. package/dist/cli/commands/analyze.js.map +1 -0
  17. package/dist/cli/commands/baseline.cjs +404 -0
  18. package/dist/cli/commands/baseline.cjs.map +1 -0
  19. package/dist/cli/commands/baseline.d.cts +72 -0
  20. package/dist/cli/commands/baseline.d.cts.map +1 -0
  21. package/dist/cli/commands/baseline.d.ts +72 -0
  22. package/dist/cli/commands/baseline.d.ts.map +1 -0
  23. package/dist/cli/commands/baseline.js +396 -0
  24. package/dist/cli/commands/baseline.js.map +1 -0
  25. package/dist/cli/commands/history.cjs +108 -266
  26. package/dist/cli/commands/history.cjs.map +1 -1
  27. package/dist/cli/commands/history.d.cts +75 -12
  28. package/dist/cli/commands/history.d.cts.map +1 -1
  29. package/dist/cli/commands/history.d.ts +75 -12
  30. package/dist/cli/commands/history.d.ts.map +1 -1
  31. package/dist/cli/commands/history.js +105 -268
  32. package/dist/cli/commands/history.js.map +1 -1
  33. package/dist/cli/commands/init.cjs +88 -155
  34. package/dist/cli/commands/init.cjs.map +1 -1
  35. package/dist/cli/commands/init.d.cts +4 -4
  36. package/dist/cli/commands/init.d.cts.map +1 -1
  37. package/dist/cli/commands/init.d.ts +4 -4
  38. package/dist/cli/commands/init.d.ts.map +1 -1
  39. package/dist/cli/commands/init.js +88 -155
  40. package/dist/cli/commands/init.js.map +1 -1
  41. package/dist/cli/commands/run.cjs +143 -112
  42. package/dist/cli/commands/run.cjs.map +1 -1
  43. package/dist/cli/commands/run.d.cts +17 -3
  44. package/dist/cli/commands/run.d.cts.map +1 -1
  45. package/dist/cli/commands/run.d.ts +17 -3
  46. package/dist/cli/commands/run.d.ts.map +1 -1
  47. package/dist/cli/commands/run.js +142 -78
  48. package/dist/cli/commands/run.js.map +1 -1
  49. package/dist/cli/index.cjs +671 -266
  50. package/dist/cli/index.cjs.map +1 -1
  51. package/dist/cli/index.d.cts +4 -16
  52. package/dist/cli/index.d.cts.map +1 -1
  53. package/dist/cli/index.d.ts +4 -16
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +664 -259
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/config/budget-schema.cjs +172 -0
  58. package/dist/config/budget-schema.cjs.map +1 -0
  59. package/dist/config/budget-schema.d.cts +59 -0
  60. package/dist/config/budget-schema.d.cts.map +1 -0
  61. package/dist/config/budget-schema.d.ts +59 -0
  62. package/dist/config/budget-schema.d.ts.map +1 -0
  63. package/dist/config/budget-schema.js +166 -0
  64. package/dist/config/budget-schema.js.map +1 -0
  65. package/dist/config/schema.cjs +182 -2
  66. package/dist/config/schema.cjs.map +1 -1
  67. package/dist/config/schema.d.cts +122 -3
  68. package/dist/config/schema.d.cts.map +1 -1
  69. package/dist/config/schema.d.ts +122 -3
  70. package/dist/config/schema.d.ts.map +1 -1
  71. package/dist/config/schema.js +180 -1
  72. package/dist/config/schema.js.map +1 -1
  73. package/dist/constants.cjs +45 -2
  74. package/dist/constants.cjs.map +1 -1
  75. package/dist/constants.d.cts +41 -0
  76. package/dist/constants.d.cts.map +1 -1
  77. package/dist/constants.d.ts +41 -0
  78. package/dist/constants.d.ts.map +1 -1
  79. package/dist/constants.js +44 -1
  80. package/dist/constants.js.map +1 -1
  81. package/dist/core/engine.cjs +104 -15
  82. package/dist/core/engine.cjs.map +1 -1
  83. package/dist/core/engine.d.cts +7 -4
  84. package/dist/core/engine.d.cts.map +1 -1
  85. package/dist/core/engine.d.ts +7 -4
  86. package/dist/core/engine.d.ts.map +1 -1
  87. package/dist/core/engine.js +105 -16
  88. package/dist/core/engine.js.map +1 -1
  89. package/dist/core/output-path-resolver.cjs +41 -0
  90. package/dist/core/output-path-resolver.cjs.map +1 -0
  91. package/dist/core/output-path-resolver.d.cts +10 -0
  92. package/dist/core/output-path-resolver.d.cts.map +1 -0
  93. package/dist/core/output-path-resolver.d.ts +10 -0
  94. package/dist/core/output-path-resolver.d.ts.map +1 -0
  95. package/dist/core/output-path-resolver.js +37 -0
  96. package/dist/core/output-path-resolver.js.map +1 -0
  97. package/dist/errors/base.cjs +12 -3
  98. package/dist/errors/base.cjs.map +1 -1
  99. package/dist/errors/base.d.cts +7 -0
  100. package/dist/errors/base.d.cts.map +1 -1
  101. package/dist/errors/base.d.ts +7 -0
  102. package/dist/errors/base.d.ts.map +1 -1
  103. package/dist/errors/base.js +10 -2
  104. package/dist/errors/base.js.map +1 -1
  105. package/dist/errors/budget.cjs +37 -0
  106. package/dist/errors/budget.cjs.map +1 -0
  107. package/dist/errors/budget.d.cts +31 -0
  108. package/dist/errors/budget.d.cts.map +1 -0
  109. package/dist/errors/budget.d.ts +31 -0
  110. package/dist/errors/budget.d.ts.map +1 -0
  111. package/dist/errors/budget.js +33 -0
  112. package/dist/errors/budget.js.map +1 -0
  113. package/dist/errors/index.cjs +4 -1
  114. package/dist/errors/index.cjs.map +1 -1
  115. package/dist/errors/index.d.cts +1 -0
  116. package/dist/errors/index.d.cts.map +1 -1
  117. package/dist/errors/index.d.ts +1 -0
  118. package/dist/errors/index.d.ts.map +1 -1
  119. package/dist/errors/index.js +2 -0
  120. package/dist/errors/index.js.map +1 -1
  121. package/dist/formatters/history/base.cjs +9 -0
  122. package/dist/formatters/history/base.cjs.map +1 -0
  123. package/dist/formatters/history/base.d.cts +26 -0
  124. package/dist/formatters/history/base.d.cts.map +1 -0
  125. package/dist/formatters/history/base.d.ts +26 -0
  126. package/dist/formatters/history/base.d.ts.map +1 -0
  127. package/dist/formatters/history/base.js +8 -0
  128. package/dist/formatters/history/base.js.map +1 -0
  129. package/dist/formatters/history/compare.cjs +127 -0
  130. package/dist/formatters/history/compare.cjs.map +1 -0
  131. package/dist/formatters/history/compare.d.cts +21 -0
  132. package/dist/formatters/history/compare.d.cts.map +1 -0
  133. package/dist/formatters/history/compare.d.ts +21 -0
  134. package/dist/formatters/history/compare.d.ts.map +1 -0
  135. package/dist/formatters/history/compare.js +123 -0
  136. package/dist/formatters/history/compare.js.map +1 -0
  137. package/dist/formatters/history/list.cjs +74 -0
  138. package/dist/formatters/history/list.cjs.map +1 -0
  139. package/dist/formatters/history/list.d.cts +25 -0
  140. package/dist/formatters/history/list.d.cts.map +1 -0
  141. package/dist/formatters/history/list.d.ts +25 -0
  142. package/dist/formatters/history/list.d.ts.map +1 -0
  143. package/dist/formatters/history/list.js +70 -0
  144. package/dist/formatters/history/list.js.map +1 -0
  145. package/dist/formatters/history/show.cjs +98 -0
  146. package/dist/formatters/history/show.cjs.map +1 -0
  147. package/dist/formatters/history/show.d.cts +21 -0
  148. package/dist/formatters/history/show.d.cts.map +1 -0
  149. package/dist/formatters/history/show.d.ts +21 -0
  150. package/dist/formatters/history/show.d.ts.map +1 -0
  151. package/dist/formatters/history/show.js +94 -0
  152. package/dist/formatters/history/show.js.map +1 -0
  153. package/dist/formatters/history/trends.cjs +194 -0
  154. package/dist/formatters/history/trends.cjs.map +1 -0
  155. package/dist/formatters/history/trends.d.cts +22 -0
  156. package/dist/formatters/history/trends.d.cts.map +1 -0
  157. package/dist/formatters/history/trends.d.ts +22 -0
  158. package/dist/formatters/history/trends.d.ts.map +1 -0
  159. package/dist/formatters/history/trends.js +190 -0
  160. package/dist/formatters/history/trends.js.map +1 -0
  161. package/dist/formatters/history/visualization.cjs +79 -0
  162. package/dist/formatters/history/visualization.cjs.map +1 -0
  163. package/dist/formatters/history/visualization.d.cts +24 -0
  164. package/dist/formatters/history/visualization.d.cts.map +1 -0
  165. package/dist/formatters/history/visualization.d.ts +24 -0
  166. package/dist/formatters/history/visualization.d.ts.map +1 -0
  167. package/dist/formatters/history/visualization.js +74 -0
  168. package/dist/formatters/history/visualization.js.map +1 -0
  169. package/dist/index.cjs +27 -17
  170. package/dist/index.cjs.map +1 -1
  171. package/dist/index.d.cts +10 -5
  172. package/dist/index.d.cts.map +1 -1
  173. package/dist/index.d.ts +10 -5
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +14 -9
  176. package/dist/index.js.map +1 -1
  177. package/dist/reporters/csv.cjs +39 -19
  178. package/dist/reporters/csv.cjs.map +1 -1
  179. package/dist/reporters/csv.d.cts +4 -7
  180. package/dist/reporters/csv.d.cts.map +1 -1
  181. package/dist/reporters/csv.d.ts +4 -7
  182. package/dist/reporters/csv.d.ts.map +1 -1
  183. package/dist/reporters/csv.js +38 -18
  184. package/dist/reporters/csv.js.map +1 -1
  185. package/dist/reporters/human.cjs +87 -99
  186. package/dist/reporters/human.cjs.map +1 -1
  187. package/dist/reporters/human.d.cts +15 -14
  188. package/dist/reporters/human.d.cts.map +1 -1
  189. package/dist/reporters/human.d.ts +15 -14
  190. package/dist/reporters/human.d.ts.map +1 -1
  191. package/dist/reporters/human.js +68 -80
  192. package/dist/reporters/human.js.map +1 -1
  193. package/dist/reporters/json.cjs +25 -50
  194. package/dist/reporters/json.cjs.map +1 -1
  195. package/dist/reporters/json.d.cts +3 -29
  196. package/dist/reporters/json.d.cts.map +1 -1
  197. package/dist/reporters/json.d.ts +3 -29
  198. package/dist/reporters/json.d.ts.map +1 -1
  199. package/dist/reporters/json.js +26 -51
  200. package/dist/reporters/json.js.map +1 -1
  201. package/dist/reporters/profile-human.cjs +149 -0
  202. package/dist/reporters/profile-human.cjs.map +1 -0
  203. package/dist/reporters/profile-human.d.cts +44 -0
  204. package/dist/reporters/profile-human.d.cts.map +1 -0
  205. package/dist/reporters/profile-human.d.ts +44 -0
  206. package/dist/reporters/profile-human.d.ts.map +1 -0
  207. package/dist/reporters/profile-human.js +142 -0
  208. package/dist/reporters/profile-human.js.map +1 -0
  209. package/dist/reporters/simple.cjs +66 -46
  210. package/dist/reporters/simple.cjs.map +1 -1
  211. package/dist/reporters/simple.d.cts +15 -15
  212. package/dist/reporters/simple.d.cts.map +1 -1
  213. package/dist/reporters/simple.d.ts +15 -15
  214. package/dist/reporters/simple.d.ts.map +1 -1
  215. package/dist/reporters/simple.js +65 -45
  216. package/dist/reporters/simple.js.map +1 -1
  217. package/dist/schema/modestbench-config.schema.json +153 -0
  218. package/dist/services/baseline-storage.cjs +151 -0
  219. package/dist/services/baseline-storage.cjs.map +1 -0
  220. package/dist/services/baseline-storage.d.cts +55 -0
  221. package/dist/services/baseline-storage.d.cts.map +1 -0
  222. package/dist/services/baseline-storage.d.ts +55 -0
  223. package/dist/services/baseline-storage.d.ts.map +1 -0
  224. package/dist/services/baseline-storage.js +147 -0
  225. package/dist/services/baseline-storage.js.map +1 -0
  226. package/dist/services/budget-evaluator.cjs +146 -0
  227. package/dist/services/budget-evaluator.cjs.map +1 -0
  228. package/dist/services/budget-evaluator.d.cts +29 -0
  229. package/dist/services/budget-evaluator.d.cts.map +1 -0
  230. package/dist/services/budget-evaluator.d.ts +29 -0
  231. package/dist/services/budget-evaluator.d.ts.map +1 -0
  232. package/dist/services/budget-evaluator.js +142 -0
  233. package/dist/services/budget-evaluator.js.map +1 -0
  234. package/dist/{config/manager.cjs → services/config-manager.cjs} +25 -11
  235. package/dist/services/config-manager.cjs.map +1 -0
  236. package/dist/{config/manager.d.cts → services/config-manager.d.cts} +7 -2
  237. package/dist/services/config-manager.d.cts.map +1 -0
  238. package/dist/{config/manager.d.ts → services/config-manager.d.ts} +7 -2
  239. package/dist/services/config-manager.d.ts.map +1 -0
  240. package/dist/{config/manager.js → services/config-manager.js} +25 -11
  241. package/dist/services/config-manager.js.map +1 -0
  242. package/dist/{core/loader.cjs → services/file-loader.cjs} +5 -8
  243. package/dist/services/file-loader.cjs.map +1 -0
  244. package/dist/{core/loader.d.cts → services/file-loader.d.cts} +1 -1
  245. package/dist/services/file-loader.d.cts.map +1 -0
  246. package/dist/{core/loader.d.ts → services/file-loader.d.ts} +1 -1
  247. package/dist/services/file-loader.d.ts.map +1 -0
  248. package/dist/{core/loader.js → services/file-loader.js} +5 -8
  249. package/dist/services/file-loader.js.map +1 -0
  250. package/dist/services/history/comparison.cjs +124 -0
  251. package/dist/services/history/comparison.cjs.map +1 -0
  252. package/dist/services/history/comparison.d.cts +18 -0
  253. package/dist/services/history/comparison.d.cts.map +1 -0
  254. package/dist/services/history/comparison.d.ts +18 -0
  255. package/dist/services/history/comparison.d.ts.map +1 -0
  256. package/dist/services/history/comparison.js +120 -0
  257. package/dist/services/history/comparison.js.map +1 -0
  258. package/dist/services/history/models.cjs +9 -0
  259. package/dist/services/history/models.cjs.map +1 -0
  260. package/dist/services/history/models.d.cts +139 -0
  261. package/dist/services/history/models.d.cts.map +1 -0
  262. package/dist/services/history/models.d.ts +139 -0
  263. package/dist/services/history/models.d.ts.map +1 -0
  264. package/dist/services/history/models.js +8 -0
  265. package/dist/services/history/models.js.map +1 -0
  266. package/dist/services/history/query.cjs +97 -0
  267. package/dist/services/history/query.cjs.map +1 -0
  268. package/dist/services/history/query.d.cts +38 -0
  269. package/dist/services/history/query.d.cts.map +1 -0
  270. package/dist/services/history/query.d.ts +38 -0
  271. package/dist/services/history/query.d.ts.map +1 -0
  272. package/dist/services/history/query.js +92 -0
  273. package/dist/services/history/query.js.map +1 -0
  274. package/dist/services/history/trend-analysis.cjs +187 -0
  275. package/dist/services/history/trend-analysis.cjs.map +1 -0
  276. package/dist/services/history/trend-analysis.d.cts +34 -0
  277. package/dist/services/history/trend-analysis.d.cts.map +1 -0
  278. package/dist/services/history/trend-analysis.d.ts +34 -0
  279. package/dist/services/history/trend-analysis.d.ts.map +1 -0
  280. package/dist/services/history/trend-analysis.js +179 -0
  281. package/dist/services/history/trend-analysis.js.map +1 -0
  282. package/dist/{storage/history.cjs → services/history-storage.cjs} +1 -1
  283. package/dist/services/history-storage.cjs.map +1 -0
  284. package/dist/{storage/history.d.cts → services/history-storage.d.cts} +1 -1
  285. package/dist/services/history-storage.d.cts.map +1 -0
  286. package/dist/{storage/history.d.ts → services/history-storage.d.ts} +1 -1
  287. package/dist/services/history-storage.d.ts.map +1 -0
  288. package/dist/{storage/history.js → services/history-storage.js} +1 -1
  289. package/dist/services/history-storage.js.map +1 -0
  290. package/dist/services/profiler/profile-filter.cjs +113 -0
  291. package/dist/services/profiler/profile-filter.cjs.map +1 -0
  292. package/dist/services/profiler/profile-filter.d.cts +20 -0
  293. package/dist/services/profiler/profile-filter.d.cts.map +1 -0
  294. package/dist/services/profiler/profile-filter.d.ts +20 -0
  295. package/dist/services/profiler/profile-filter.d.ts.map +1 -0
  296. package/dist/services/profiler/profile-filter.js +109 -0
  297. package/dist/services/profiler/profile-filter.js.map +1 -0
  298. package/dist/services/profiler/profile-parser.cjs +139 -0
  299. package/dist/services/profiler/profile-parser.cjs.map +1 -0
  300. package/dist/services/profiler/profile-parser.d.cts +18 -0
  301. package/dist/services/profiler/profile-parser.d.cts.map +1 -0
  302. package/dist/services/profiler/profile-parser.d.ts +18 -0
  303. package/dist/services/profiler/profile-parser.d.ts.map +1 -0
  304. package/dist/services/profiler/profile-parser.js +132 -0
  305. package/dist/services/profiler/profile-parser.js.map +1 -0
  306. package/dist/services/profiler/profile-runner.cjs +90 -0
  307. package/dist/services/profiler/profile-runner.cjs.map +1 -0
  308. package/dist/services/profiler/profile-runner.d.cts +29 -0
  309. package/dist/services/profiler/profile-runner.d.cts.map +1 -0
  310. package/dist/services/profiler/profile-runner.d.ts +29 -0
  311. package/dist/services/profiler/profile-runner.d.ts.map +1 -0
  312. package/dist/services/profiler/profile-runner.js +86 -0
  313. package/dist/services/profiler/profile-runner.js.map +1 -0
  314. package/dist/{progress/manager.cjs → services/progress-manager.cjs} +1 -1
  315. package/dist/services/progress-manager.cjs.map +1 -0
  316. package/dist/{progress/manager.d.cts → services/progress-manager.d.cts} +1 -1
  317. package/dist/services/progress-manager.d.cts.map +1 -0
  318. package/dist/{progress/manager.d.ts → services/progress-manager.d.ts} +1 -1
  319. package/dist/services/progress-manager.d.ts.map +1 -0
  320. package/dist/{progress/manager.js → services/progress-manager.js} +1 -1
  321. package/dist/services/progress-manager.js.map +1 -0
  322. package/dist/{reporters/registry.cjs → services/reporter-registry.cjs} +19 -25
  323. package/dist/services/reporter-registry.cjs.map +1 -0
  324. package/dist/{reporters/registry.d.cts → services/reporter-registry.d.cts} +19 -41
  325. package/dist/services/reporter-registry.d.cts.map +1 -0
  326. package/dist/{reporters/registry.d.ts → services/reporter-registry.d.ts} +19 -41
  327. package/dist/services/reporter-registry.d.ts.map +1 -0
  328. package/dist/{reporters/registry.js → services/reporter-registry.js} +19 -25
  329. package/dist/services/reporter-registry.js.map +1 -0
  330. package/dist/types/budgets.cjs +8 -0
  331. package/dist/types/budgets.cjs.map +1 -0
  332. package/dist/types/budgets.d.cts +149 -0
  333. package/dist/types/budgets.d.cts.map +1 -0
  334. package/dist/types/budgets.d.ts +149 -0
  335. package/dist/types/budgets.d.ts.map +1 -0
  336. package/dist/types/budgets.js +7 -0
  337. package/dist/types/budgets.js.map +1 -0
  338. package/dist/types/cli.cjs +2 -11
  339. package/dist/types/cli.cjs.map +1 -1
  340. package/dist/types/cli.d.cts +3 -224
  341. package/dist/types/cli.d.cts.map +1 -1
  342. package/dist/types/cli.d.ts +3 -224
  343. package/dist/types/cli.d.ts.map +1 -1
  344. package/dist/types/cli.js +2 -11
  345. package/dist/types/cli.js.map +1 -1
  346. package/dist/types/core.cjs +6 -1
  347. package/dist/types/core.cjs.map +1 -1
  348. package/dist/types/core.d.cts +13 -2
  349. package/dist/types/core.d.cts.map +1 -1
  350. package/dist/types/core.d.ts +13 -2
  351. package/dist/types/core.d.ts.map +1 -1
  352. package/dist/types/core.js +2 -1
  353. package/dist/types/core.js.map +1 -1
  354. package/dist/types/index.cjs +5 -0
  355. package/dist/types/index.cjs.map +1 -1
  356. package/dist/types/index.d.cts +2 -0
  357. package/dist/types/index.d.cts.map +1 -1
  358. package/dist/types/index.d.ts +2 -0
  359. package/dist/types/index.d.ts.map +1 -1
  360. package/dist/types/index.js +2 -0
  361. package/dist/types/index.js.map +1 -1
  362. package/dist/types/interfaces.d.cts +15 -8
  363. package/dist/types/interfaces.d.cts.map +1 -1
  364. package/dist/types/interfaces.d.ts +15 -8
  365. package/dist/types/interfaces.d.ts.map +1 -1
  366. package/dist/types/profiler.cjs +11 -0
  367. package/dist/types/profiler.cjs.map +1 -0
  368. package/dist/types/profiler.d.cts +100 -0
  369. package/dist/types/profiler.d.cts.map +1 -0
  370. package/dist/types/profiler.d.ts +100 -0
  371. package/dist/types/profiler.d.ts.map +1 -0
  372. package/dist/types/profiler.js +10 -0
  373. package/dist/types/profiler.js.map +1 -0
  374. package/dist/types/utility.cjs.map +1 -1
  375. package/dist/types/utility.d.cts +0 -8
  376. package/dist/types/utility.d.cts.map +1 -1
  377. package/dist/types/utility.d.ts +0 -8
  378. package/dist/types/utility.d.ts.map +1 -1
  379. package/dist/types/utility.js.map +1 -1
  380. package/dist/utils/ansi.cjs +61 -0
  381. package/dist/utils/ansi.cjs.map +1 -0
  382. package/dist/utils/ansi.d.cts +53 -0
  383. package/dist/utils/ansi.d.cts.map +1 -0
  384. package/dist/utils/ansi.d.ts +53 -0
  385. package/dist/utils/ansi.d.ts.map +1 -0
  386. package/dist/utils/ansi.js +57 -0
  387. package/dist/utils/ansi.js.map +1 -0
  388. package/dist/utils/identifiers.cjs +32 -0
  389. package/dist/utils/identifiers.cjs.map +1 -0
  390. package/dist/utils/identifiers.d.cts +32 -0
  391. package/dist/utils/identifiers.d.cts.map +1 -0
  392. package/dist/utils/identifiers.d.ts +32 -0
  393. package/dist/utils/identifiers.d.ts.map +1 -0
  394. package/dist/utils/identifiers.js +27 -0
  395. package/dist/utils/identifiers.js.map +1 -0
  396. package/dist/utils/package.cjs +40 -0
  397. package/dist/utils/package.cjs.map +1 -0
  398. package/dist/utils/package.d.cts +15 -0
  399. package/dist/utils/package.d.cts.map +1 -0
  400. package/dist/utils/package.d.ts +15 -0
  401. package/dist/utils/package.d.ts.map +1 -0
  402. package/dist/utils/package.js +33 -0
  403. package/dist/utils/package.js.map +1 -0
  404. package/dist/utils/type-guards.cjs +48 -0
  405. package/dist/utils/type-guards.cjs.map +1 -0
  406. package/dist/utils/type-guards.d.cts +22 -0
  407. package/dist/utils/type-guards.d.cts.map +1 -0
  408. package/dist/utils/type-guards.d.ts +22 -0
  409. package/dist/utils/type-guards.d.ts.map +1 -0
  410. package/dist/utils/type-guards.js +43 -0
  411. package/dist/utils/type-guards.js.map +1 -0
  412. package/package.json +14 -13
  413. package/src/bootstrap.ts +5 -5
  414. package/src/cli/commands/analyze.ts +101 -0
  415. package/src/cli/commands/baseline.ts +577 -0
  416. package/src/cli/commands/history.ts +194 -342
  417. package/src/cli/commands/init.ts +105 -183
  418. package/src/cli/commands/run.ts +186 -88
  419. package/src/cli/index.ts +731 -234
  420. package/src/config/budget-schema.ts +189 -0
  421. package/src/config/schema.ts +260 -1
  422. package/src/constants.ts +53 -1
  423. package/src/core/engine.ts +153 -14
  424. package/src/core/output-path-resolver.ts +46 -0
  425. package/src/errors/base.ts +11 -2
  426. package/src/errors/budget.ts +38 -0
  427. package/src/errors/index.ts +3 -0
  428. package/src/formatters/history/base.ts +28 -0
  429. package/src/formatters/history/compare.ts +186 -0
  430. package/src/formatters/history/list.ts +101 -0
  431. package/src/formatters/history/show.ts +155 -0
  432. package/src/formatters/history/trends.ts +281 -0
  433. package/src/formatters/history/visualization.ts +93 -0
  434. package/src/index.ts +17 -12
  435. package/src/reporters/csv.ts +55 -26
  436. package/src/reporters/human.ts +90 -89
  437. package/src/reporters/json.ts +27 -72
  438. package/src/reporters/profile-human.ts +204 -0
  439. package/src/reporters/simple.ts +85 -54
  440. package/src/services/baseline-storage.ts +199 -0
  441. package/src/services/budget-evaluator.ts +182 -0
  442. package/src/{config/manager.ts → services/config-manager.ts} +24 -9
  443. package/src/{core/loader.ts → services/file-loader.ts} +4 -7
  444. package/src/services/history/comparison.ts +130 -0
  445. package/src/services/history/models.ts +148 -0
  446. package/src/services/history/query.ts +116 -0
  447. package/src/services/history/trend-analysis.ts +238 -0
  448. package/src/services/profiler/profile-filter.ts +143 -0
  449. package/src/services/profiler/profile-parser.ts +194 -0
  450. package/src/services/profiler/profile-runner.ts +121 -0
  451. package/src/{reporters/registry.ts → services/reporter-registry.ts} +46 -81
  452. package/src/types/budgets.ts +180 -0
  453. package/src/types/cli.ts +5 -235
  454. package/src/types/core.ts +50 -10
  455. package/src/types/index.ts +5 -0
  456. package/src/types/interfaces.ts +16 -6
  457. package/src/types/profiler.ts +132 -0
  458. package/src/types/utility.ts +0 -10
  459. package/src/utils/ansi.ts +59 -0
  460. package/src/utils/identifiers.ts +58 -0
  461. package/src/utils/package.ts +35 -0
  462. package/src/utils/type-guards.ts +51 -0
  463. package/dist/config/manager.cjs.map +0 -1
  464. package/dist/config/manager.d.cts.map +0 -1
  465. package/dist/config/manager.d.ts.map +0 -1
  466. package/dist/config/manager.js.map +0 -1
  467. package/dist/core/loader.cjs.map +0 -1
  468. package/dist/core/loader.d.cts.map +0 -1
  469. package/dist/core/loader.d.ts.map +0 -1
  470. package/dist/core/loader.js.map +0 -1
  471. package/dist/progress/manager.cjs.map +0 -1
  472. package/dist/progress/manager.d.cts.map +0 -1
  473. package/dist/progress/manager.d.ts.map +0 -1
  474. package/dist/progress/manager.js.map +0 -1
  475. package/dist/reporters/registry.cjs.map +0 -1
  476. package/dist/reporters/registry.d.cts.map +0 -1
  477. package/dist/reporters/registry.d.ts.map +0 -1
  478. package/dist/reporters/registry.js.map +0 -1
  479. package/dist/storage/history.cjs.map +0 -1
  480. package/dist/storage/history.d.cts.map +0 -1
  481. package/dist/storage/history.d.ts.map +0 -1
  482. package/dist/storage/history.js.map +0 -1
  483. /package/src/{storage/history.ts → services/history-storage.ts} +0 -0
  484. /package/src/{progress/manager.ts → services/progress-manager.ts} +0 -0
@@ -6,12 +6,18 @@
6
6
  * architecture.
7
7
  */
8
8
 
9
+ import { randomBytes } from 'node:crypto';
10
+ import { relative as pathRelative } from 'node:path';
11
+
9
12
  import type {
13
+ BaselineSummaryData,
10
14
  BenchmarkDefinition,
11
15
  BenchmarkEngine,
12
16
  BenchmarkRun,
13
17
  BenchmarkSuite,
14
18
  BenchmarkTask,
19
+ Budget,
20
+ BudgetSummary,
15
21
  CiInfo,
16
22
  ConfigurationManager,
17
23
  EnvironmentInfo,
@@ -24,7 +30,9 @@ import type {
24
30
  Reporter,
25
31
  ReporterRegistry,
26
32
  RunConfiguration,
33
+ RunId,
27
34
  SuiteResult,
35
+ TaskId,
28
36
  TaskResult,
29
37
  ValidationError,
30
38
  ValidationResult,
@@ -33,11 +41,15 @@ import type {
33
41
 
34
42
  import {
35
43
  BenchmarkExecutionError,
44
+ BudgetExceededError,
36
45
  FileDiscoveryError,
37
46
  SchemaValidationError,
38
47
  SetupError,
39
48
  StructureValidationError,
40
49
  } from '../errors/index.js';
50
+ import { BaselineStorageService } from '../services/baseline-storage.js';
51
+ import { BudgetEvaluator } from '../services/budget-evaluator.js';
52
+ import { createRunId, createTaskId } from '../types/index.js';
41
53
 
42
54
  /**
43
55
  * Dependencies required by the BenchmarkEngine
@@ -76,6 +88,20 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
76
88
  this.progressManager = dependencies.progressManager;
77
89
  }
78
90
 
91
+ /**
92
+ * Generate a unique run ID
93
+ *
94
+ * Uses crypto.randomBytes for cryptographically random 7-character IDs.
95
+ * Format: 7 lowercase alphanumeric characters (e.g., "k3m9x2p")
96
+ */
97
+ private static generateRunId(this: void): RunId {
98
+ // Generate random bytes, convert to hex, then to base36, take first 7 chars
99
+ const hex = randomBytes(4).toString('hex');
100
+ const num = parseInt(hex, 16);
101
+ const id = num.toString(36).padStart(7, '0').substring(0, 7);
102
+ return createRunId(id);
103
+ }
104
+
79
105
  /**
80
106
  * Discover benchmark files matching the pattern(s)
81
107
  */
@@ -134,7 +160,7 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
134
160
  }
135
161
 
136
162
  // 4. Initialize progress tracking
137
- const runId = this.generateRunId();
163
+ const runId = ModestBenchEngine.generateRunId();
138
164
 
139
165
  // Pre-calculate total tasks for progress tracking
140
166
  let totalTasks = 0;
@@ -210,9 +236,9 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
210
236
 
211
237
  // Register progress callbacks with reporters that support them
212
238
  for (const reporter of reporters) {
213
- if (typeof reporter.onProgress === 'function') {
239
+ if (reporter.onProgress) {
214
240
  this.progressManager.onProgress((state) => {
215
- void reporter.onProgress(state);
241
+ void reporter.onProgress?.(state);
216
242
  });
217
243
  }
218
244
  }
@@ -225,12 +251,17 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
225
251
 
226
252
  for (const filePath of files) {
227
253
  try {
228
- // Call reporter onFileStart
229
- await this.callReporters(reporters, 'onFileStart', filePath);
254
+ // Normalize file path to be relative to cwd
255
+ const cwd = config.cwd || process.cwd();
256
+ const relativePath = pathRelative(cwd, filePath);
257
+
258
+ // Call reporter onFileStart with relative path
259
+ await this.callReporters(reporters, 'onFileStart', relativePath);
230
260
 
231
261
  const fileResult = await this.executeBenchmarkFile(
232
262
  filePath,
233
263
  mergedConfig,
264
+ cwd,
234
265
  reporters,
235
266
  signal,
236
267
  );
@@ -244,6 +275,16 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
244
275
  currentFile: filePath,
245
276
  filesCompleted: fileResults.length,
246
277
  });
278
+
279
+ // Check for bail: stop execution if any task failed
280
+ if (mergedConfig.bail) {
281
+ const hasFailedTask = fileResult.suites.some((suite) =>
282
+ suite.tasks.some((task) => task.error),
283
+ );
284
+ if (hasFailedTask) {
285
+ break;
286
+ }
287
+ }
247
288
  } catch (error) {
248
289
  const fileError =
249
290
  error instanceof Error ? error : new Error(String(error));
@@ -265,6 +306,11 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
265
306
 
266
307
  // Call reporter onFileEnd for error case
267
308
  await this.callReporters(reporters, 'onFileEnd', errorResult);
309
+
310
+ // Check bail flag for file-level errors
311
+ if (mergedConfig.bail) {
312
+ break;
313
+ }
268
314
  }
269
315
  }
270
316
 
@@ -300,10 +346,103 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
300
346
  }
301
347
 
302
348
  const overallMean = totalOperations > 0 ? totalTime / totalOperations : 0;
349
+ // Evaluate budgets if configured
350
+ let budgetSummary: BudgetSummary | undefined;
351
+
352
+ if (config.budgets && Object.keys(config.budgets).length > 0) {
353
+ const evaluator = new BudgetEvaluator();
354
+ const baselineStorage = new BaselineStorageService(process.cwd());
355
+
356
+ // Collect task results
357
+ const taskResults = new Map<TaskId, TaskResult>();
358
+
359
+ for (const file of fileResults) {
360
+ for (const suite of file.suites) {
361
+ for (const task of suite.tasks) {
362
+ if (!task.error) {
363
+ // file.filePath is already relative to cwd
364
+ const taskId = createTaskId(
365
+ file.filePath,
366
+ suite.name,
367
+ task.name,
368
+ );
369
+ taskResults.set(taskId, task);
370
+ }
371
+ }
372
+ }
373
+ }
374
+
375
+ // Load baseline data if needed for relative budgets
376
+ let baselineData: Map<TaskId, BaselineSummaryData> | undefined;
377
+
378
+ // Check if any budgets use relative thresholds
379
+ const hasRelativeBudgets = Object.values(config.budgets).some(
380
+ (budget) => (budget as Budget).relative,
381
+ );
382
+
383
+ if (hasRelativeBudgets) {
384
+ const baselineName =
385
+ config.baseline || (await baselineStorage.getDefault());
386
+
387
+ if (baselineName) {
388
+ const baseline = await baselineStorage.getBaseline(baselineName);
389
+
390
+ if (baseline) {
391
+ // Cast keys to TaskId since they come from validated baseline storage
392
+ baselineData = new Map(
393
+ Object.entries(baseline.summary) as [
394
+ TaskId,
395
+ BaselineSummaryData,
396
+ ][],
397
+ );
398
+ } else {
399
+ console.warn(
400
+ `Warning: Baseline "${baselineName}" not found. Relative budgets will be skipped.`,
401
+ );
402
+ }
403
+ } else {
404
+ console.warn(
405
+ 'Warning: Relative budgets configured but no baseline specified. Relative budgets will be skipped.',
406
+ );
407
+ }
408
+ }
409
+
410
+ // Evaluate budgets
411
+ budgetSummary = evaluator.evaluateRun(
412
+ config.budgets as Record<string, Budget>,
413
+ taskResults,
414
+ baselineData,
415
+ );
416
+
417
+ // Notify reporters of budget results
418
+ for (const reporter of reporters) {
419
+ if (reporter.onBudgetResult) {
420
+ await reporter.onBudgetResult(budgetSummary);
421
+ }
422
+ }
423
+
424
+ // Handle budget failures based on budgetMode
425
+ if (budgetSummary.failed > 0) {
426
+ const mode = config.budgetMode || 'fail';
427
+
428
+ if (mode === 'fail') {
429
+ throw new BudgetExceededError(
430
+ `${budgetSummary.failed} of ${budgetSummary.total} budget(s) exceeded`,
431
+ budgetSummary,
432
+ );
433
+ } else if (mode === 'warn') {
434
+ console.warn(
435
+ `Warning: ${budgetSummary.failed} of ${budgetSummary.total} budget(s) exceeded`,
436
+ );
437
+ }
438
+ // mode === 'report': just include in output, don't fail
439
+ }
440
+ }
303
441
 
304
442
  const endTime = new Date();
305
443
  const finalRun: BenchmarkRun = {
306
444
  ...initialRun,
445
+ budgetSummary,
307
446
  duration: endTime.getTime() - startTime.getTime(),
308
447
  endTime,
309
448
  files: fileResults,
@@ -487,6 +626,7 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
487
626
  private async executeBenchmarkFile(
488
627
  filePath: string,
489
628
  config: ModestBenchConfig,
629
+ cwd: string,
490
630
  reporters: Reporter[] = [],
491
631
  signal?: AbortSignal,
492
632
  ): Promise<FileResult> {
@@ -541,11 +681,14 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
541
681
 
542
682
  const endTime = new Date();
543
683
 
684
+ // Normalize file path to be relative to cwd
685
+ const relativePath = pathRelative(cwd, filePath);
686
+
544
687
  return {
545
688
  config: benchmarkDef.config,
546
689
  duration: endTime.getTime() - startTime.getTime(),
547
690
  endTime,
548
- filePath,
691
+ filePath: relativePath,
549
692
  startTime,
550
693
  suites: suiteResults,
551
694
  };
@@ -554,11 +697,14 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
554
697
  const executionError =
555
698
  error instanceof Error ? error : new Error(String(error));
556
699
 
700
+ // Normalize file path to be relative to cwd
701
+ const relativePath = pathRelative(cwd, filePath);
702
+
557
703
  return {
558
704
  duration: endTime.getTime() - startTime.getTime(),
559
705
  endTime,
560
706
  error: executionError,
561
- filePath,
707
+ filePath: relativePath,
562
708
  startTime,
563
709
  suites: [],
564
710
  };
@@ -695,13 +841,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
695
841
  }
696
842
  }
697
843
 
698
- /**
699
- * Generate a unique run ID
700
- */
701
- private generateRunId(): string {
702
- return `run-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
703
- }
704
-
705
844
  /**
706
845
  * Get CI/CD information if available
707
846
  */
@@ -0,0 +1,46 @@
1
+ import { extname, isAbsolute, join, resolve } from 'node:path';
2
+
3
+ /**
4
+ * Resolves the final output path for a reporter
5
+ *
6
+ * @param outputDir - Optional output directory from --output flag
7
+ * @param outputFile - Optional output filename from --output-file flag
8
+ * @param defaultFilename - Default filename to use if none specified
9
+ * @returns Resolved output path, or undefined if no output to file requested
10
+ */
11
+ export const resolveOutputPath = (
12
+ outputDir?: string,
13
+ outputFile?: string,
14
+ defaultFilename?: string,
15
+ ): string | undefined => {
16
+ // If outputFile is provided
17
+ if (outputFile) {
18
+ // If outputFile is absolute, use as-is
19
+ if (isAbsolute(outputFile)) {
20
+ return outputFile;
21
+ }
22
+
23
+ // If outputDir specified, join them
24
+ if (outputDir) {
25
+ return join(outputDir, outputFile);
26
+ }
27
+
28
+ // Otherwise, resolve relative to cwd
29
+ return resolve(process.cwd(), outputFile);
30
+ }
31
+
32
+ // If outputDir looks like a file (has extension), treat it as a file path
33
+ // This handles cases like: --output results.csv
34
+ if (outputDir && extname(outputDir)) {
35
+ return isAbsolute(outputDir)
36
+ ? outputDir
37
+ : resolve(process.cwd(), outputDir);
38
+ }
39
+
40
+ // Fall back to default behavior (outputDir is a directory)
41
+ if (outputDir && defaultFilename) {
42
+ return join(outputDir, defaultFilename);
43
+ }
44
+
45
+ return undefined;
46
+ };
@@ -143,10 +143,19 @@ export const isModestBenchError = (
143
143
  error: unknown,
144
144
  ): error is ModestBenchError => {
145
145
  return (
146
- typeof error === 'object' &&
147
- error !== null &&
146
+ isError(error) &&
148
147
  'code' in error &&
149
148
  typeof (error as { code: unknown }).code === 'string' &&
150
149
  (error as { code: string }).code.startsWith('ERR_MB_')
151
150
  );
152
151
  };
152
+
153
+ /**
154
+ * Type guard to check if an error is a standard Error
155
+ *
156
+ * @param error - The error to check
157
+ * @returns `true` if the error is an `Error`
158
+ */
159
+ export const isError = (error: unknown): error is Error => {
160
+ return error instanceof Error;
161
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Budget-related errors
3
+ *
4
+ * Errors that occur during budget evaluation and enforcement.
5
+ */
6
+
7
+ import type { BudgetSummary } from '../types/core.js';
8
+
9
+ import { ModestBenchError } from './base.js';
10
+
11
+ /**
12
+ * Error thrown when performance budgets are exceeded
13
+ *
14
+ * Thrown when budget evaluation fails and budgetMode is set to 'fail'. Contains
15
+ * the full budget summary for detailed reporting.
16
+ */
17
+ export class BudgetExceededError extends ModestBenchError {
18
+ /**
19
+ * Budget summary containing details of all violations
20
+ */
21
+ public readonly budgetSummary: BudgetSummary;
22
+
23
+ /**
24
+ * Error code for budget exceeded errors
25
+ */
26
+ readonly code = 'ERR_MB_BUDGET_EXCEEDED';
27
+
28
+ /**
29
+ * Create a new budget exceeded error
30
+ *
31
+ * @param message - Human-readable error message
32
+ * @param budgetSummary - Budget evaluation results
33
+ */
34
+ constructor(message: string, budgetSummary: BudgetSummary) {
35
+ super(message);
36
+ this.budgetSummary = budgetSummary;
37
+ }
38
+ }
@@ -13,6 +13,9 @@ export {
13
13
  ModestBenchError,
14
14
  } from './base.js';
15
15
 
16
+ // Budget errors
17
+ export { BudgetExceededError } from './budget.js';
18
+
16
19
  // CLI errors
17
20
  export {
18
21
  InvalidArgumentError,
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Base Formatter Interface
3
+ *
4
+ * Defines the contract for history command formatters. Each formatter
5
+ * transforms processed data into human-readable or machine-readable output.
6
+ */
7
+
8
+ /**
9
+ * Formatter interface for history command output
10
+ *
11
+ * @template TData - The data type this formatter accepts
12
+ */
13
+ export interface HistoryFormatter<TData> {
14
+ /**
15
+ * Format data as CSV (optional, not all commands support CSV)
16
+ */
17
+ formatCsv?(data: TData): string;
18
+
19
+ /**
20
+ * Format data for human-readable terminal output
21
+ */
22
+ formatHuman(data: TData): string;
23
+
24
+ /**
25
+ * Format data as JSON
26
+ */
27
+ formatJson(data: TData): string;
28
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Compare Formatter
3
+ *
4
+ * Formats benchmark run comparison results in human and JSON formats.
5
+ */
6
+
7
+ import type { CompareResult } from '../../services/history/models.js';
8
+ import type { HistoryFormatter } from './base.js';
9
+
10
+ import { colorize } from '../../utils/ansi.js';
11
+ import { ansiChars } from '../../utils/ansi.js';
12
+
13
+ /**
14
+ * Formatter for history compare command
15
+ */
16
+ export class HistoryCompareFormatter
17
+ implements HistoryFormatter<CompareResult>
18
+ {
19
+ /**
20
+ * Format as human-readable comparison
21
+ */
22
+ formatHuman(data: CompareResult): string {
23
+ const lines: string[] = [];
24
+
25
+ lines.push(colorize('brightMagenta', colorize('bold', 'Comparing runs:')));
26
+ lines.push(
27
+ ` ${colorize('brightCyan', ansiChars.bullet)} ${colorize('brightWhite', colorize('bold', 'Run 1'))} ${colorize('dim', data.run1.id)} (${colorize('white', data.run1.startTime.toLocaleString())})`,
28
+ );
29
+ lines.push(
30
+ ` ${colorize('brightCyan', ansiChars.bullet)} ${colorize('brightWhite', colorize('bold', 'Run 2'))} ${colorize('dim', data.run2.id)} (${colorize('white', data.run2.startTime.toLocaleString())})`,
31
+ );
32
+ lines.push('');
33
+
34
+ lines.push(colorize('cyan', 'Summary comparison:'));
35
+ lines.push('');
36
+ lines.push(
37
+ ` ${colorize('dim', ansiChars.smallSquare)} Files: ${colorize('brightWhite', String(data.run1.summary.totalFiles))} vs ${colorize('brightWhite', String(data.run2.summary.totalFiles))}`,
38
+ );
39
+ lines.push(
40
+ ` ${colorize('dim', ansiChars.smallSquare)} Tasks: ${colorize('brightWhite', String(data.run1.summary.totalTasks))} vs ${colorize('brightWhite', String(data.run2.summary.totalTasks))}`,
41
+ );
42
+ lines.push(
43
+ ` ${colorize('dim', ansiChars.smallSquare)} Passed: ${colorize('brightCyan', String(data.run1.summary.passedTasks))} vs ${colorize('brightCyan', String(data.run2.summary.passedTasks))}`,
44
+ );
45
+ lines.push(
46
+ ` ${colorize('dim', ansiChars.smallSquare)} Failed: ${colorize('brightRed', colorize('bold', String(data.run1.summary.failedTasks)))} vs ${colorize('brightRed', colorize('bold', String(data.run2.summary.failedTasks)))}`,
47
+ );
48
+ lines.push('');
49
+
50
+ // Detailed task comparison
51
+ if (data.tasksInBoth.length > 0) {
52
+ lines.push(colorize('cyan', 'Task-by-task comparison:'));
53
+ lines.push('');
54
+
55
+ for (const comparison of data.tasksInBoth) {
56
+ const mean1 = comparison.run1!.mean / 1000000; // Convert to ms
57
+ const mean2 = comparison.run2!.mean / 1000000;
58
+ const changeSign = comparison.percentChange >= 0 ? '+' : '';
59
+ const changeStr = `${changeSign}${comparison.percentChange.toFixed(1)}%`;
60
+
61
+ lines.push(
62
+ ` ${colorize('brightWhite', `${comparison.suite} › ${comparison.task}`)}`,
63
+ );
64
+
65
+ // Mean - highlight higher (slower/worse) number
66
+ const meanHigher = mean2 > mean1;
67
+ const mean1Str = meanHigher
68
+ ? colorize('magenta', `${mean1.toFixed(3)}ms`)
69
+ : colorize('brightMagenta', `${mean1.toFixed(3)}ms`);
70
+ const mean2Str = meanHigher
71
+ ? colorize('brightMagenta', `${mean2.toFixed(3)}ms`)
72
+ : colorize('magenta', `${mean2.toFixed(3)}ms`);
73
+ lines.push(
74
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('white', 'Mean:')} ${mean1Str} ${colorize('dim', '→')} ${mean2Str} ${colorize('dim', `(${colorize('white', changeStr)}`)}`,
75
+ );
76
+
77
+ // Min - highlight higher number
78
+ const min1 = comparison.run1!.min / 1000000;
79
+ const min2 = comparison.run2!.min / 1000000;
80
+ const minHigher = min2 > min1;
81
+ const min1Str = minHigher
82
+ ? colorize('magenta', `${min1.toFixed(3)}ms`)
83
+ : colorize('brightMagenta', `${min1.toFixed(3)}ms`);
84
+ const min2Str = minHigher
85
+ ? colorize('brightMagenta', `${min2.toFixed(3)}ms`)
86
+ : colorize('magenta', `${min2.toFixed(3)}ms`);
87
+ lines.push(
88
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('white', 'Min:')} ${min1Str} ${colorize('dim', '→')} ${min2Str}`,
89
+ );
90
+
91
+ // Max - highlight higher number
92
+ const max1 = comparison.run1!.max / 1000000;
93
+ const max2 = comparison.run2!.max / 1000000;
94
+ const maxHigher = max2 > max1;
95
+ const max1Str = maxHigher
96
+ ? colorize('magenta', `${max1.toFixed(3)}ms`)
97
+ : colorize('brightMagenta', `${max1.toFixed(3)}ms`);
98
+ const max2Str = maxHigher
99
+ ? colorize('brightMagenta', `${max2.toFixed(3)}ms`)
100
+ : colorize('magenta', `${max2.toFixed(3)}ms`);
101
+ lines.push(
102
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('white', 'Max:')} ${max1Str} ${colorize('dim', '→')} ${max2Str}`,
103
+ );
104
+
105
+ // Iterations - highlight higher number
106
+ const iter1 = comparison.run1!.iterations;
107
+ const iter2 = comparison.run2!.iterations;
108
+ const iterHigher = iter2 > iter1;
109
+ const iter1Str = iterHigher
110
+ ? colorize('brightWhite', String(iter1))
111
+ : colorize('bold', colorize('brightWhite', String(iter1)));
112
+ const iter2Str = iterHigher
113
+ ? colorize('bold', colorize('brightWhite', String(iter2)))
114
+ : colorize('brightWhite', String(iter2));
115
+ lines.push(
116
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('white', 'Iterations:')} ${iter1Str} ${colorize('dim', 'vs')} ${iter2Str}`,
117
+ );
118
+
119
+ // CV (Coefficient of Variation) - shows measurement consistency
120
+ const cv1 = comparison.run1!.cv * 100;
121
+ const cv2 = comparison.run2!.cv * 100;
122
+ const cvHigher = cv2 > cv1;
123
+ const cv1Str = cvHigher
124
+ ? colorize('magenta', `${cv1.toFixed(2)}%`)
125
+ : colorize('brightMagenta', `${cv1.toFixed(2)}%`);
126
+ const cv2Str = cvHigher
127
+ ? colorize('brightMagenta', `${cv2.toFixed(2)}%`)
128
+ : colorize('magenta', `${cv2.toFixed(2)}%`);
129
+ lines.push(
130
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('white', 'CV:')} ${cv1Str} ${colorize('dim', '→')} ${cv2Str}`,
131
+ );
132
+
133
+ lines.push('');
134
+ }
135
+ }
136
+
137
+ if (data.tasksOnlyInRun1.length > 0) {
138
+ lines.push(
139
+ colorize(
140
+ 'cyan',
141
+ `Tasks only in run 1 (${data.tasksOnlyInRun1.length}):`,
142
+ ),
143
+ );
144
+ for (const task of data.tasksOnlyInRun1) {
145
+ lines.push(
146
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('brightWhite', `${task.suite} › ${task.task}`)}`,
147
+ );
148
+ }
149
+ lines.push('');
150
+ }
151
+
152
+ if (data.tasksOnlyInRun2.length > 0) {
153
+ lines.push(
154
+ colorize(
155
+ 'cyan',
156
+ `Tasks only in run 2 (${data.tasksOnlyInRun2.length}):`,
157
+ ),
158
+ );
159
+ for (const task of data.tasksOnlyInRun2) {
160
+ lines.push(
161
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('brightWhite', `${task.suite} › ${task.task}`)}`,
162
+ );
163
+ }
164
+ lines.push('');
165
+ }
166
+
167
+ return lines.join('\n');
168
+ }
169
+
170
+ /**
171
+ * Format as JSON
172
+ */
173
+ formatJson(data: CompareResult): string {
174
+ return JSON.stringify(
175
+ {
176
+ run1: data.run1,
177
+ run2: data.run2,
178
+ taskComparisons: data.tasksInBoth,
179
+ tasksOnlyInRun1: data.tasksOnlyInRun1,
180
+ tasksOnlyInRun2: data.tasksOnlyInRun2,
181
+ },
182
+ null,
183
+ 2,
184
+ );
185
+ }
186
+ }