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
@@ -0,0 +1,182 @@
1
+ import type {
2
+ BaselineSummaryData,
3
+ Budget,
4
+ BudgetResult,
5
+ BudgetSummary,
6
+ BudgetViolation,
7
+ TaskId,
8
+ TaskResult,
9
+ } from '../types/core.js';
10
+
11
+ /**
12
+ * Service for evaluating performance budgets
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ export class BudgetEvaluator {
17
+ /**
18
+ * Format number with thousands separators
19
+ */
20
+ private static formatNumber(this: void, value: number): string {
21
+ return value.toLocaleString('en-US', {
22
+ maximumFractionDigits: 0,
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Format decimal as percentage
28
+ */
29
+ private static formatPercentage(this: void, value: number): string {
30
+ return `${(value * 100).toFixed(1)}%`;
31
+ }
32
+
33
+ /**
34
+ * Format time in nanoseconds to human-readable string
35
+ */
36
+ private static formatTime(this: void, nanoseconds: number): string {
37
+ if (nanoseconds < 1_000) {
38
+ return `${nanoseconds.toFixed(0)}ns`;
39
+ } else if (nanoseconds < 1_000_000) {
40
+ return `${(nanoseconds / 1_000).toFixed(2)}μs`;
41
+ } else if (nanoseconds < 1_000_000_000) {
42
+ return `${(nanoseconds / 1_000_000).toFixed(2)}ms`;
43
+ } else {
44
+ return `${(nanoseconds / 1_000_000_000).toFixed(2)}s`;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Evaluate budgets for an entire benchmark run
50
+ */
51
+ evaluateRun(
52
+ budgets: Record<string, Budget>,
53
+ taskResults: Map<TaskId, TaskResult>,
54
+ baselineData?: Map<TaskId, BaselineSummaryData>,
55
+ ): BudgetSummary {
56
+ const results: BudgetResult[] = [];
57
+
58
+ for (const [taskId, budget] of Object.entries(budgets)) {
59
+ const taskResult = taskResults.get(taskId as TaskId);
60
+
61
+ // Skip if no result for this task
62
+ if (!taskResult) {
63
+ continue;
64
+ }
65
+
66
+ // Skip relative budgets if no baseline data
67
+ if (budget.relative && !baselineData) {
68
+ continue;
69
+ }
70
+
71
+ const budgetResult = this.evaluateTask(
72
+ taskId as TaskId,
73
+ budget,
74
+ taskResult,
75
+ baselineData?.get(taskId as TaskId),
76
+ );
77
+
78
+ results.push(budgetResult);
79
+ }
80
+
81
+ const passed = results.filter((r) => r.passed).length;
82
+ const failed = results.filter((r) => !r.passed).length;
83
+
84
+ return {
85
+ failed,
86
+ passed,
87
+ results,
88
+ total: results.length,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Evaluate budgets for a single task
94
+ */
95
+ private evaluateTask(
96
+ taskId: TaskId,
97
+ budget: Budget,
98
+ actual: TaskResult,
99
+ baseline?: BaselineSummaryData,
100
+ ): BudgetResult {
101
+ const violations: BudgetViolation[] = [];
102
+
103
+ // Evaluate absolute budgets
104
+ if (budget.absolute) {
105
+ if (budget.absolute.maxTime !== undefined) {
106
+ if (actual.mean > budget.absolute.maxTime) {
107
+ violations.push({
108
+ actual: actual.mean,
109
+ delta:
110
+ (actual.mean - budget.absolute.maxTime) / budget.absolute.maxTime,
111
+ message: `Mean execution time ${BudgetEvaluator.formatTime(actual.mean)} exceeded budget of ${BudgetEvaluator.formatTime(budget.absolute.maxTime)} by ${BudgetEvaluator.formatPercentage((actual.mean - budget.absolute.maxTime) / budget.absolute.maxTime)}`,
112
+ threshold: budget.absolute.maxTime,
113
+ type: 'maxTime',
114
+ });
115
+ }
116
+ }
117
+
118
+ if (budget.absolute.minOpsPerSec !== undefined) {
119
+ if (actual.opsPerSecond < budget.absolute.minOpsPerSec) {
120
+ violations.push({
121
+ actual: actual.opsPerSecond,
122
+ delta:
123
+ (budget.absolute.minOpsPerSec - actual.opsPerSecond) /
124
+ budget.absolute.minOpsPerSec,
125
+ message: `Operations per second ${BudgetEvaluator.formatNumber(actual.opsPerSecond)} is below minimum of ${BudgetEvaluator.formatNumber(budget.absolute.minOpsPerSec)} by ${BudgetEvaluator.formatPercentage((budget.absolute.minOpsPerSec - actual.opsPerSecond) / budget.absolute.minOpsPerSec)}`,
126
+ threshold: budget.absolute.minOpsPerSec,
127
+ type: 'minOpsPerSec',
128
+ });
129
+ }
130
+ }
131
+
132
+ if (budget.absolute.maxP99 !== undefined && actual.p99 !== undefined) {
133
+ if (actual.p99 > budget.absolute.maxP99) {
134
+ violations.push({
135
+ actual: actual.p99,
136
+ delta:
137
+ (actual.p99 - budget.absolute.maxP99) / budget.absolute.maxP99,
138
+ message: `P99 latency ${BudgetEvaluator.formatTime(actual.p99)} exceeded budget of ${BudgetEvaluator.formatTime(budget.absolute.maxP99)} by ${BudgetEvaluator.formatPercentage((actual.p99 - budget.absolute.maxP99) / budget.absolute.maxP99)}`,
139
+ threshold: budget.absolute.maxP99,
140
+ type: 'maxP99',
141
+ });
142
+ }
143
+ }
144
+ }
145
+
146
+ // Evaluate relative budgets
147
+ if (budget.relative && baseline) {
148
+ if (budget.relative.maxRegression !== undefined) {
149
+ const regression = (actual.mean - baseline.mean) / baseline.mean;
150
+
151
+ if (regression > budget.relative.maxRegression) {
152
+ violations.push({
153
+ actual: regression,
154
+ delta: regression - budget.relative.maxRegression,
155
+ message: `Performance regressed by ${BudgetEvaluator.formatPercentage(regression)} exceeding maximum allowed regression of ${BudgetEvaluator.formatPercentage(budget.relative.maxRegression)}`,
156
+ threshold: budget.relative.maxRegression,
157
+ type: 'maxRegression',
158
+ });
159
+ }
160
+ }
161
+ }
162
+
163
+ return {
164
+ actual: {
165
+ mean: actual.mean,
166
+ opsPerSecond: actual.opsPerSecond,
167
+ p99: actual.p99,
168
+ },
169
+ baseline: baseline
170
+ ? {
171
+ mean: baseline.mean,
172
+ opsPerSecond: baseline.opsPerSecond,
173
+ p99: baseline.p99,
174
+ }
175
+ : undefined,
176
+ budget,
177
+ passed: violations.length === 0,
178
+ taskId,
179
+ violations,
180
+ };
181
+ }
182
+ }
@@ -18,9 +18,9 @@ import type {
18
18
  ValidationWarning,
19
19
  } from '../types/index.js';
20
20
 
21
+ import { safeParseConfig } from '../config/schema.js';
21
22
  import { ErrorCodes } from '../constants.js';
22
23
  import { ConfigLoadError, ConfigValidationError } from '../errors/index.js';
23
- import { safeParseConfig } from './schema.js';
24
24
 
25
25
  /**
26
26
  * Get the default reporter based on TTY status and environment
@@ -56,7 +56,7 @@ const DEFAULT_CONFIG: ModestBenchConfig = {
56
56
  limitBy: 'iterations', // Default to limiting by iteration count
57
57
  metadata: {},
58
58
  outputDir: './benchmark-results',
59
- pattern: '**/*.bench.{js,ts,mjs,cjs,mts,cts}',
59
+ pattern: 'bench/**/*.bench.{js,ts,mjs,cjs,mts,cts}', // Search bench/ directory recursively
60
60
  quiet: false,
61
61
  reporterConfig: {},
62
62
  reporters: [getDefaultReporter()],
@@ -121,10 +121,16 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
121
121
 
122
122
  /**
123
123
  * Load configuration from various sources with precedence
124
+ *
125
+ * @param configPath - Optional path to configuration file
126
+ * @param cliArgs - Optional CLI arguments to merge
127
+ * @param commandDefaults - Command-specific defaults (fallback to
128
+ * DEFAULT_CONFIG)
124
129
  */
125
130
  async load(
126
131
  configPath?: string,
127
132
  cliArgs?: Record<string, unknown>,
133
+ commandDefaults?: Partial<ModestBenchConfig>,
128
134
  ): Promise<ModestBenchConfig> {
129
135
  try {
130
136
  // Create a fresh explorer for each load to avoid module caching issues
@@ -164,9 +170,13 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
164
170
 
165
171
  const fileConfig = (result?.config || {}) as Partial<ModestBenchConfig>;
166
172
 
167
- // 2. Merge: defaults <- file <- CLI args
173
+ // 2. Merge: command defaults <- file <- CLI args
174
+ // Use command-specific defaults if provided, otherwise use DEFAULT_CONFIG
175
+ const baseDefaults = commandDefaults
176
+ ? this.merge(DEFAULT_CONFIG, commandDefaults)
177
+ : DEFAULT_CONFIG;
168
178
  const normalizedCliArgs = cliArgs ? this.normalizeCliArgs(cliArgs) : {};
169
- const merged = this.merge(DEFAULT_CONFIG, fileConfig, normalizedCliArgs);
179
+ const merged = this.merge(baseDefaults, fileConfig, normalizedCliArgs);
170
180
 
171
181
  // 2.5. Apply smart defaults for limitBy if not explicitly provided
172
182
  const finalConfig = ModestBenchConfigurationManager.applySmartDefaults(
@@ -175,15 +185,20 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
175
185
  fileConfig,
176
186
  );
177
187
 
178
- // 3. Validate final configuration
179
- const validation = this.validate(finalConfig);
180
- if (!validation.valid) {
188
+ // 3. Validate final configuration and get transformed config
189
+ // The validation also transforms budgets from nested to flat format
190
+ const validation = safeParseConfig(finalConfig);
191
+ if (!validation.success) {
192
+ const errors = validation.error.issues.map((issue) => {
193
+ const path = issue.path.join('.');
194
+ return `${path ? `${path}: ` : ''}${issue.message}`;
195
+ });
181
196
  throw new ConfigValidationError(
182
- `Configuration validation failed: ${validation.errors.map((e) => e.message).join(', ')}`,
197
+ `Configuration validation failed: ${errors.join(', ')}`,
183
198
  );
184
199
  }
185
200
 
186
- return finalConfig;
201
+ return validation.data;
187
202
  } catch (error) {
188
203
  // Re-throw our custom errors
189
204
  if (
@@ -22,12 +22,12 @@ import {
22
22
  BENCHMARK_FILE_EXTENSIONS,
23
23
  BENCHMARK_FILE_PATTERN,
24
24
  } from '../constants.js';
25
+ import { benchmarkFileSchema } from '../core/benchmark-schema.js';
25
26
  import {
26
27
  FileDiscoveryError,
27
28
  FileLoadError,
28
29
  StructureValidationError,
29
30
  } from '../errors/index.js';
30
- import { benchmarkFileSchema } from './benchmark-schema.js';
31
31
 
32
32
  /**
33
33
  * File change notification for watch functionality
@@ -64,8 +64,7 @@ export class BenchmarkFileLoader implements FileLoader {
64
64
  // Handle empty patterns - use sensible defaults
65
65
  if (patterns.length === 0) {
66
66
  patterns = [
67
- `*${BENCHMARK_FILE_PATTERN}`, // top-level current directory
68
- `bench/*${BENCHMARK_FILE_PATTERN}`, // top-level bench/ directory
67
+ `bench/**/*${BENCHMARK_FILE_PATTERN}`, // bench/ directory (recursive)
69
68
  ];
70
69
  }
71
70
 
@@ -152,10 +151,8 @@ export class BenchmarkFileLoader implements FileLoader {
152
151
  default?: unknown;
153
152
  };
154
153
  } else {
155
- // Use native dynamic import for JavaScript files with cache busting
156
- // Add timestamp to prevent module caching issues across multiple loads
157
- const timestamp = Date.now();
158
- module = (await import(`${filePath}?t=${timestamp}`)) as {
154
+ // Use native dynamic import for JavaScript files
155
+ module = (await import(filePath)) as {
159
156
  [key: string]: unknown;
160
157
  default?: unknown;
161
158
  };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Benchmark Comparison Service
3
+ *
4
+ * Handles comparison logic between two benchmark runs, calculating performance
5
+ * differences and categorizing tasks.
6
+ */
7
+
8
+ import type { BenchmarkRun } from '../../types/index.js';
9
+ import type { CompareResult, TaskComparison } from './models.js';
10
+
11
+ /**
12
+ * Service for comparing benchmark runs
13
+ */
14
+ export class ComparisonService {
15
+ /**
16
+ * Compare two benchmark runs and produce detailed comparison result
17
+ */
18
+ compareRuns(run1: BenchmarkRun, run2: BenchmarkRun): CompareResult {
19
+ // Build task maps for comparison
20
+ const tasksMap1 = new Map<string, TaskComparison>();
21
+ const tasksMap2 = new Map<string, TaskComparison>();
22
+
23
+ // Extract tasks from run1
24
+ for (const file of run1.files) {
25
+ for (const suite of file.suites) {
26
+ for (const task of suite.tasks) {
27
+ if (!task.error) {
28
+ const key = `${file.filePath}::${suite.name}::${task.name}`;
29
+ tasksMap1.set(key, {
30
+ file: file.filePath,
31
+ inBoth: false,
32
+ percentChange: 0,
33
+ run1: {
34
+ cv: task.cv,
35
+ iterations: task.iterations,
36
+ max: task.max,
37
+ mean: task.mean,
38
+ min: task.min,
39
+ },
40
+ suite: suite.name,
41
+ task: task.name,
42
+ });
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ // Extract tasks from run2 and merge
49
+ for (const file of run2.files) {
50
+ for (const suite of file.suites) {
51
+ for (const task of suite.tasks) {
52
+ if (!task.error) {
53
+ const key = `${file.filePath}::${suite.name}::${task.name}`;
54
+ const existing = tasksMap1.get(key);
55
+
56
+ if (existing && existing.run1) {
57
+ // Task exists in both runs - calculate comparison
58
+ const percentChange =
59
+ ((task.mean - existing.run1.mean) / existing.run1.mean) * 100;
60
+
61
+ tasksMap1.set(key, {
62
+ ...existing,
63
+ inBoth: true,
64
+ percentChange,
65
+ run2: {
66
+ cv: task.cv,
67
+ iterations: task.iterations,
68
+ max: task.max,
69
+ mean: task.mean,
70
+ min: task.min,
71
+ },
72
+ });
73
+ } else {
74
+ // Task only in run2
75
+ tasksMap2.set(key, {
76
+ file: file.filePath,
77
+ inBoth: false,
78
+ percentChange: 0,
79
+ run2: {
80
+ cv: task.cv,
81
+ iterations: task.iterations,
82
+ max: task.max,
83
+ mean: task.mean,
84
+ min: task.min,
85
+ },
86
+ suite: suite.name,
87
+ task: task.name,
88
+ });
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ // Separate tasks into categories
96
+ const tasksInBoth: TaskComparison[] = [];
97
+ const tasksOnlyIn1: TaskComparison[] = [];
98
+ const tasksOnlyIn2: TaskComparison[] = [];
99
+
100
+ for (const task of tasksMap1.values()) {
101
+ if (task.inBoth) {
102
+ tasksInBoth.push(task);
103
+ } else {
104
+ tasksOnlyIn1.push(task);
105
+ }
106
+ }
107
+
108
+ for (const task of tasksMap2.values()) {
109
+ tasksOnlyIn2.push(task);
110
+ }
111
+
112
+ return {
113
+ run1: {
114
+ endTime: run1.endTime,
115
+ id: run1.id,
116
+ startTime: run1.startTime,
117
+ summary: run1.summary,
118
+ },
119
+ run2: {
120
+ endTime: run2.endTime,
121
+ id: run2.id,
122
+ startTime: run2.startTime,
123
+ summary: run2.summary,
124
+ },
125
+ tasksInBoth,
126
+ tasksOnlyInRun1: tasksOnlyIn1,
127
+ tasksOnlyInRun2: tasksOnlyIn2,
128
+ };
129
+ }
130
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Data Models for History Services
3
+ *
4
+ * Type definitions for history command data structures, shared between
5
+ * services, formatters, and CLI handlers.
6
+ */
7
+
8
+ import type { BenchmarkRun } from '../../types/index.js';
9
+
10
+ /**
11
+ * Result from comparing two benchmark runs
12
+ */
13
+ export interface CompareResult {
14
+ run1: {
15
+ endTime: Date;
16
+ id: string;
17
+ startTime: Date;
18
+ summary: {
19
+ failedTasks: number;
20
+ passedTasks: number;
21
+ totalFiles: number;
22
+ totalTasks: number;
23
+ };
24
+ };
25
+ run2: {
26
+ endTime: Date;
27
+ id: string;
28
+ startTime: Date;
29
+ summary: {
30
+ failedTasks: number;
31
+ passedTasks: number;
32
+ totalFiles: number;
33
+ totalTasks: number;
34
+ };
35
+ };
36
+ tasksInBoth: TaskComparison[];
37
+ tasksOnlyInRun1: TaskComparison[];
38
+ tasksOnlyInRun2: TaskComparison[];
39
+ }
40
+
41
+ /**
42
+ * Distribution bucket for histogram visualization
43
+ */
44
+ export interface DistributionBucket {
45
+ count: number;
46
+ label: string;
47
+ max: number;
48
+ min: number;
49
+ }
50
+
51
+ /**
52
+ * Result from listing historical runs
53
+ */
54
+ export interface HistoryListResult {
55
+ runs: Array<{
56
+ duration: number;
57
+ id: string;
58
+ startTime: Date;
59
+ summary: {
60
+ failedTasks: number;
61
+ passedTasks: number;
62
+ totalFiles: number;
63
+ totalTasks: number;
64
+ };
65
+ }>;
66
+ totalCount: number;
67
+ }
68
+
69
+ /**
70
+ * Show command result (just wraps BenchmarkRun for consistency)
71
+ */
72
+ export type ShowResult = BenchmarkRun;
73
+
74
+ /**
75
+ * Task comparison result between two runs
76
+ */
77
+ export interface TaskComparison {
78
+ file: string;
79
+ inBoth: boolean;
80
+ percentChange: number;
81
+ run1?: {
82
+ cv: number;
83
+ iterations: number;
84
+ max: number;
85
+ mean: number;
86
+ min: number;
87
+ };
88
+ run2?: {
89
+ cv: number;
90
+ iterations: number;
91
+ max: number;
92
+ mean: number;
93
+ min: number;
94
+ };
95
+ suite: string;
96
+ task: string;
97
+ }
98
+
99
+ /**
100
+ * Complete trend analysis for a single task
101
+ */
102
+ export interface TrendData {
103
+ confidence: number;
104
+ dataPoints: TrendDataPoint[];
105
+ percentChange: number;
106
+ runs: number;
107
+ statistics: TrendStatistics;
108
+ task: string;
109
+ trend: 'degrading' | 'improving' | 'stable';
110
+ }
111
+
112
+ /**
113
+ * Single data point in a trend series
114
+ */
115
+ export interface TrendDataPoint {
116
+ date: Date;
117
+ mean: number;
118
+ }
119
+
120
+ /**
121
+ * Result from analyzing performance trends
122
+ */
123
+ export interface TrendsResult {
124
+ lowConfidenceRegressions: TrendData[];
125
+ regressions: TrendData[];
126
+ runs: number;
127
+ summary: {
128
+ degradingTasks: number;
129
+ improvingTasks: number;
130
+ stableTasks: number;
131
+ totalTasks: number;
132
+ };
133
+ timespan: {
134
+ end: Date;
135
+ start: Date;
136
+ };
137
+ trends: TrendData[];
138
+ }
139
+
140
+ /**
141
+ * Statistical metrics for trend analysis
142
+ */
143
+ export interface TrendStatistics {
144
+ mean: number;
145
+ median: number;
146
+ stdDeviation: number;
147
+ variance: number;
148
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * History Query Service
3
+ *
4
+ * Handles querying benchmark run history with date parsing and filtering.
5
+ */
6
+
7
+ import type {
8
+ BenchmarkRun,
9
+ HistoryQuery,
10
+ HistoryStorage,
11
+ } from '../../types/index.js';
12
+
13
+ /**
14
+ * Service for querying historical benchmark runs
15
+ */
16
+ export class HistoryQueryService {
17
+ constructor(private readonly storage: HistoryStorage) {}
18
+
19
+ /**
20
+ * Query runs with automatic date string parsing
21
+ */
22
+ async queryWithDateParsing(options: {
23
+ limit?: number;
24
+ pattern?: string;
25
+ since?: string;
26
+ tags?: string[];
27
+ until?: string;
28
+ }): Promise<BenchmarkRun[]> {
29
+ // Build query object all at once
30
+ const query: Partial<HistoryQuery> = {
31
+ ...(options.since && { since: parseDate(options.since) }),
32
+ ...(options.until && { until: parseDate(options.until) }),
33
+ ...(options.pattern && { pattern: options.pattern }),
34
+ ...(options.tags && options.tags.length > 0 && { tags: options.tags }),
35
+ ...(options.limit && { limit: options.limit }),
36
+ };
37
+
38
+ return await this.storage.queryRuns(query);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Parse date string (ISO 8601 or relative)
44
+ *
45
+ * Supports:
46
+ *
47
+ * - ISO 8601: "2025-10-24T12:00:00Z", "2025-10-24"
48
+ * - Relative: "1 day ago", "3 weeks ago", "2 hours ago"
49
+ * - Shorthand: "1d", "2w", "3m", "6h"
50
+ *
51
+ * @param dateStr - Date string to parse
52
+ * @returns Parsed Date object
53
+ * @throws Error if date format is invalid
54
+ */
55
+ export const parseDate = (dateStr: string): Date => {
56
+ if (!dateStr || dateStr.trim() === '') {
57
+ throw new Error(`Invalid date format: "${dateStr}"`);
58
+ }
59
+
60
+ // Try parsing as ISO 8601 first
61
+ const isoDate = new Date(dateStr);
62
+ if (!isNaN(isoDate.getTime())) {
63
+ return isoDate;
64
+ }
65
+
66
+ // Parse relative dates like "1 week ago", "3 days ago"
67
+ const relativePattern = /^(\d+)\s+(hour|day|week|month)s?\s+ago$/i;
68
+ const relativeMatch = dateStr.trim().match(relativePattern);
69
+
70
+ if (relativeMatch && relativeMatch[1] && relativeMatch[2]) {
71
+ const amount = parseInt(relativeMatch[1], 10);
72
+ const unit = relativeMatch[2].toLowerCase();
73
+
74
+ if (amount <= 0) {
75
+ throw new Error(`Invalid date format: "${dateStr}"`);
76
+ }
77
+
78
+ const now = new Date();
79
+ const msPerUnit: Record<string, number> = {
80
+ day: 24 * 60 * 60 * 1000,
81
+ hour: 60 * 60 * 1000,
82
+ month: 30 * 24 * 60 * 60 * 1000, // Approximate
83
+ week: 7 * 24 * 60 * 60 * 1000,
84
+ };
85
+
86
+ const offset = amount * (msPerUnit[unit] || 0);
87
+ return new Date(now.getTime() - offset);
88
+ }
89
+
90
+ // Parse shorthand formats like "1d", "2w", "3m", "6h"
91
+ // cspell:ignore hdwm
92
+ const shorthandPattern = /^(\d+)([hdwm])$/i;
93
+ const shorthandMatch = dateStr.trim().match(shorthandPattern);
94
+
95
+ if (shorthandMatch && shorthandMatch[1] && shorthandMatch[2]) {
96
+ const amount = parseInt(shorthandMatch[1], 10);
97
+ const unit = shorthandMatch[2].toLowerCase();
98
+
99
+ if (amount <= 0) {
100
+ throw new Error(`Invalid date format: "${dateStr}"`);
101
+ }
102
+
103
+ const now = new Date();
104
+ const msPerUnit: Record<string, number> = {
105
+ d: 24 * 60 * 60 * 1000,
106
+ h: 60 * 60 * 1000,
107
+ m: 30 * 24 * 60 * 60 * 1000, // Approximate month
108
+ w: 7 * 24 * 60 * 60 * 1000,
109
+ };
110
+
111
+ const offset = amount * (msPerUnit[unit] || 0);
112
+ return new Date(now.getTime() - offset);
113
+ }
114
+
115
+ throw new Error(`Invalid date format: "${dateStr}"`);
116
+ };