modestbench 0.2.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 (332) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +131 -34
  3. package/dist/cli/commands/analyze.cjs +60 -0
  4. package/dist/cli/commands/analyze.cjs.map +1 -0
  5. package/dist/cli/commands/analyze.d.cts +35 -0
  6. package/dist/cli/commands/analyze.d.cts.map +1 -0
  7. package/dist/cli/commands/analyze.d.ts +35 -0
  8. package/dist/cli/commands/analyze.d.ts.map +1 -0
  9. package/dist/cli/commands/analyze.js +56 -0
  10. package/dist/cli/commands/analyze.js.map +1 -0
  11. package/dist/cli/commands/baseline.cjs +404 -0
  12. package/dist/cli/commands/baseline.cjs.map +1 -0
  13. package/dist/cli/commands/baseline.d.cts +72 -0
  14. package/dist/cli/commands/baseline.d.cts.map +1 -0
  15. package/dist/cli/commands/baseline.d.ts +72 -0
  16. package/dist/cli/commands/baseline.d.ts.map +1 -0
  17. package/dist/cli/commands/baseline.js +396 -0
  18. package/dist/cli/commands/baseline.js.map +1 -0
  19. package/dist/cli/commands/history.d.cts +1 -1
  20. package/dist/cli/commands/history.d.cts.map +1 -1
  21. package/dist/cli/commands/history.d.ts +1 -1
  22. package/dist/cli/commands/history.d.ts.map +1 -1
  23. package/dist/cli/commands/init.cjs +88 -155
  24. package/dist/cli/commands/init.cjs.map +1 -1
  25. package/dist/cli/commands/init.d.cts +4 -4
  26. package/dist/cli/commands/init.d.cts.map +1 -1
  27. package/dist/cli/commands/init.d.ts +4 -4
  28. package/dist/cli/commands/init.d.ts.map +1 -1
  29. package/dist/cli/commands/init.js +88 -155
  30. package/dist/cli/commands/init.js.map +1 -1
  31. package/dist/cli/commands/run.cjs +132 -114
  32. package/dist/cli/commands/run.cjs.map +1 -1
  33. package/dist/cli/commands/run.d.cts +16 -3
  34. package/dist/cli/commands/run.d.cts.map +1 -1
  35. package/dist/cli/commands/run.d.ts +16 -3
  36. package/dist/cli/commands/run.d.ts.map +1 -1
  37. package/dist/cli/commands/run.js +131 -80
  38. package/dist/cli/commands/run.js.map +1 -1
  39. package/dist/cli/index.cjs +583 -394
  40. package/dist/cli/index.cjs.map +1 -1
  41. package/dist/cli/index.d.cts +4 -16
  42. package/dist/cli/index.d.cts.map +1 -1
  43. package/dist/cli/index.d.ts +4 -16
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +575 -386
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/config/budget-schema.cjs +172 -0
  48. package/dist/config/budget-schema.cjs.map +1 -0
  49. package/dist/config/budget-schema.d.cts +59 -0
  50. package/dist/config/budget-schema.d.cts.map +1 -0
  51. package/dist/config/budget-schema.d.ts +59 -0
  52. package/dist/config/budget-schema.d.ts.map +1 -0
  53. package/dist/config/budget-schema.js +166 -0
  54. package/dist/config/budget-schema.js.map +1 -0
  55. package/dist/config/schema.cjs +182 -2
  56. package/dist/config/schema.cjs.map +1 -1
  57. package/dist/config/schema.d.cts +122 -3
  58. package/dist/config/schema.d.cts.map +1 -1
  59. package/dist/config/schema.d.ts +122 -3
  60. package/dist/config/schema.d.ts.map +1 -1
  61. package/dist/config/schema.js +180 -1
  62. package/dist/config/schema.js.map +1 -1
  63. package/dist/constants.cjs +45 -2
  64. package/dist/constants.cjs.map +1 -1
  65. package/dist/constants.d.cts +41 -0
  66. package/dist/constants.d.cts.map +1 -1
  67. package/dist/constants.d.ts +41 -0
  68. package/dist/constants.d.ts.map +1 -1
  69. package/dist/constants.js +44 -1
  70. package/dist/constants.js.map +1 -1
  71. package/dist/core/engine.cjs +103 -21
  72. package/dist/core/engine.cjs.map +1 -1
  73. package/dist/core/engine.d.cts +7 -7
  74. package/dist/core/engine.d.cts.map +1 -1
  75. package/dist/core/engine.d.ts +7 -7
  76. package/dist/core/engine.d.ts.map +1 -1
  77. package/dist/core/engine.js +104 -22
  78. package/dist/core/engine.js.map +1 -1
  79. package/dist/core/output-path-resolver.cjs +8 -1
  80. package/dist/core/output-path-resolver.cjs.map +1 -1
  81. package/dist/core/output-path-resolver.d.cts.map +1 -1
  82. package/dist/core/output-path-resolver.d.ts.map +1 -1
  83. package/dist/core/output-path-resolver.js +9 -2
  84. package/dist/core/output-path-resolver.js.map +1 -1
  85. package/dist/errors/base.cjs +12 -3
  86. package/dist/errors/base.cjs.map +1 -1
  87. package/dist/errors/base.d.cts +7 -0
  88. package/dist/errors/base.d.cts.map +1 -1
  89. package/dist/errors/base.d.ts +7 -0
  90. package/dist/errors/base.d.ts.map +1 -1
  91. package/dist/errors/base.js +10 -2
  92. package/dist/errors/base.js.map +1 -1
  93. package/dist/errors/budget.cjs +37 -0
  94. package/dist/errors/budget.cjs.map +1 -0
  95. package/dist/errors/budget.d.cts +31 -0
  96. package/dist/errors/budget.d.cts.map +1 -0
  97. package/dist/errors/budget.d.ts +31 -0
  98. package/dist/errors/budget.d.ts.map +1 -0
  99. package/dist/errors/budget.js +33 -0
  100. package/dist/errors/budget.js.map +1 -0
  101. package/dist/errors/index.cjs +4 -1
  102. package/dist/errors/index.cjs.map +1 -1
  103. package/dist/errors/index.d.cts +1 -0
  104. package/dist/errors/index.d.cts.map +1 -1
  105. package/dist/errors/index.d.ts +1 -0
  106. package/dist/errors/index.d.ts.map +1 -1
  107. package/dist/errors/index.js +2 -0
  108. package/dist/errors/index.js.map +1 -1
  109. package/dist/index.cjs +13 -1
  110. package/dist/index.cjs.map +1 -1
  111. package/dist/index.d.cts +5 -0
  112. package/dist/index.d.cts.map +1 -1
  113. package/dist/index.d.ts +5 -0
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +7 -0
  116. package/dist/index.js.map +1 -1
  117. package/dist/reporters/csv.cjs +37 -17
  118. package/dist/reporters/csv.cjs.map +1 -1
  119. package/dist/reporters/csv.d.cts +3 -6
  120. package/dist/reporters/csv.d.cts.map +1 -1
  121. package/dist/reporters/csv.d.ts +3 -6
  122. package/dist/reporters/csv.d.ts.map +1 -1
  123. package/dist/reporters/csv.js +37 -17
  124. package/dist/reporters/csv.js.map +1 -1
  125. package/dist/reporters/human.cjs +66 -40
  126. package/dist/reporters/human.cjs.map +1 -1
  127. package/dist/reporters/human.d.cts +14 -13
  128. package/dist/reporters/human.d.cts.map +1 -1
  129. package/dist/reporters/human.d.ts +14 -13
  130. package/dist/reporters/human.d.ts.map +1 -1
  131. package/dist/reporters/human.js +66 -40
  132. package/dist/reporters/human.js.map +1 -1
  133. package/dist/reporters/json.cjs +23 -48
  134. package/dist/reporters/json.cjs.map +1 -1
  135. package/dist/reporters/json.d.cts +2 -28
  136. package/dist/reporters/json.d.cts.map +1 -1
  137. package/dist/reporters/json.d.ts +2 -28
  138. package/dist/reporters/json.d.ts.map +1 -1
  139. package/dist/reporters/json.js +25 -50
  140. package/dist/reporters/json.js.map +1 -1
  141. package/dist/reporters/profile-human.cjs +149 -0
  142. package/dist/reporters/profile-human.cjs.map +1 -0
  143. package/dist/reporters/profile-human.d.cts +44 -0
  144. package/dist/reporters/profile-human.d.cts.map +1 -0
  145. package/dist/reporters/profile-human.d.ts +44 -0
  146. package/dist/reporters/profile-human.d.ts.map +1 -0
  147. package/dist/reporters/profile-human.js +142 -0
  148. package/dist/reporters/profile-human.js.map +1 -0
  149. package/dist/reporters/simple.cjs +64 -44
  150. package/dist/reporters/simple.cjs.map +1 -1
  151. package/dist/reporters/simple.d.cts +14 -14
  152. package/dist/reporters/simple.d.cts.map +1 -1
  153. package/dist/reporters/simple.d.ts +14 -14
  154. package/dist/reporters/simple.d.ts.map +1 -1
  155. package/dist/reporters/simple.js +64 -44
  156. package/dist/reporters/simple.js.map +1 -1
  157. package/dist/schema/modestbench-config.schema.json +153 -0
  158. package/dist/services/baseline-storage.cjs +151 -0
  159. package/dist/services/baseline-storage.cjs.map +1 -0
  160. package/dist/services/baseline-storage.d.cts +55 -0
  161. package/dist/services/baseline-storage.d.cts.map +1 -0
  162. package/dist/services/baseline-storage.d.ts +55 -0
  163. package/dist/services/baseline-storage.d.ts.map +1 -0
  164. package/dist/services/baseline-storage.js +147 -0
  165. package/dist/services/baseline-storage.js.map +1 -0
  166. package/dist/services/budget-evaluator.cjs +146 -0
  167. package/dist/services/budget-evaluator.cjs.map +1 -0
  168. package/dist/services/budget-evaluator.d.cts +29 -0
  169. package/dist/services/budget-evaluator.d.cts.map +1 -0
  170. package/dist/services/budget-evaluator.d.ts +29 -0
  171. package/dist/services/budget-evaluator.d.ts.map +1 -0
  172. package/dist/services/budget-evaluator.js +142 -0
  173. package/dist/services/budget-evaluator.js.map +1 -0
  174. package/dist/services/config-manager.cjs +23 -9
  175. package/dist/services/config-manager.cjs.map +1 -1
  176. package/dist/services/config-manager.d.cts +6 -1
  177. package/dist/services/config-manager.d.cts.map +1 -1
  178. package/dist/services/config-manager.d.ts +6 -1
  179. package/dist/services/config-manager.d.ts.map +1 -1
  180. package/dist/services/config-manager.js +23 -9
  181. package/dist/services/config-manager.js.map +1 -1
  182. package/dist/services/file-loader.cjs +3 -6
  183. package/dist/services/file-loader.cjs.map +1 -1
  184. package/dist/services/file-loader.d.cts.map +1 -1
  185. package/dist/services/file-loader.d.ts.map +1 -1
  186. package/dist/services/file-loader.js +3 -6
  187. package/dist/services/file-loader.js.map +1 -1
  188. package/dist/services/profiler/profile-filter.cjs +113 -0
  189. package/dist/services/profiler/profile-filter.cjs.map +1 -0
  190. package/dist/services/profiler/profile-filter.d.cts +20 -0
  191. package/dist/services/profiler/profile-filter.d.cts.map +1 -0
  192. package/dist/services/profiler/profile-filter.d.ts +20 -0
  193. package/dist/services/profiler/profile-filter.d.ts.map +1 -0
  194. package/dist/services/profiler/profile-filter.js +109 -0
  195. package/dist/services/profiler/profile-filter.js.map +1 -0
  196. package/dist/services/profiler/profile-parser.cjs +139 -0
  197. package/dist/services/profiler/profile-parser.cjs.map +1 -0
  198. package/dist/services/profiler/profile-parser.d.cts +18 -0
  199. package/dist/services/profiler/profile-parser.d.cts.map +1 -0
  200. package/dist/services/profiler/profile-parser.d.ts +18 -0
  201. package/dist/services/profiler/profile-parser.d.ts.map +1 -0
  202. package/dist/services/profiler/profile-parser.js +132 -0
  203. package/dist/services/profiler/profile-parser.js.map +1 -0
  204. package/dist/services/profiler/profile-runner.cjs +90 -0
  205. package/dist/services/profiler/profile-runner.cjs.map +1 -0
  206. package/dist/services/profiler/profile-runner.d.cts +29 -0
  207. package/dist/services/profiler/profile-runner.d.cts.map +1 -0
  208. package/dist/services/profiler/profile-runner.d.ts +29 -0
  209. package/dist/services/profiler/profile-runner.d.ts.map +1 -0
  210. package/dist/services/profiler/profile-runner.js +86 -0
  211. package/dist/services/profiler/profile-runner.js.map +1 -0
  212. package/dist/services/reporter-registry.cjs +18 -24
  213. package/dist/services/reporter-registry.cjs.map +1 -1
  214. package/dist/services/reporter-registry.d.cts +18 -40
  215. package/dist/services/reporter-registry.d.cts.map +1 -1
  216. package/dist/services/reporter-registry.d.ts +18 -40
  217. package/dist/services/reporter-registry.d.ts.map +1 -1
  218. package/dist/services/reporter-registry.js +18 -24
  219. package/dist/services/reporter-registry.js.map +1 -1
  220. package/dist/types/budgets.cjs +8 -0
  221. package/dist/types/budgets.cjs.map +1 -0
  222. package/dist/types/budgets.d.cts +149 -0
  223. package/dist/types/budgets.d.cts.map +1 -0
  224. package/dist/types/budgets.d.ts +149 -0
  225. package/dist/types/budgets.d.ts.map +1 -0
  226. package/dist/types/budgets.js +7 -0
  227. package/dist/types/budgets.js.map +1 -0
  228. package/dist/types/cli.cjs +2 -11
  229. package/dist/types/cli.cjs.map +1 -1
  230. package/dist/types/cli.d.cts +3 -227
  231. package/dist/types/cli.d.cts.map +1 -1
  232. package/dist/types/cli.d.ts +3 -227
  233. package/dist/types/cli.d.ts.map +1 -1
  234. package/dist/types/cli.js +2 -11
  235. package/dist/types/cli.js.map +1 -1
  236. package/dist/types/core.cjs +6 -1
  237. package/dist/types/core.cjs.map +1 -1
  238. package/dist/types/core.d.cts +13 -2
  239. package/dist/types/core.d.cts.map +1 -1
  240. package/dist/types/core.d.ts +13 -2
  241. package/dist/types/core.d.ts.map +1 -1
  242. package/dist/types/core.js +2 -1
  243. package/dist/types/core.js.map +1 -1
  244. package/dist/types/index.cjs +5 -0
  245. package/dist/types/index.cjs.map +1 -1
  246. package/dist/types/index.d.cts +2 -0
  247. package/dist/types/index.d.cts.map +1 -1
  248. package/dist/types/index.d.ts +2 -0
  249. package/dist/types/index.d.ts.map +1 -1
  250. package/dist/types/index.js +2 -0
  251. package/dist/types/index.js.map +1 -1
  252. package/dist/types/interfaces.d.cts +15 -8
  253. package/dist/types/interfaces.d.cts.map +1 -1
  254. package/dist/types/interfaces.d.ts +15 -8
  255. package/dist/types/interfaces.d.ts.map +1 -1
  256. package/dist/types/profiler.cjs +11 -0
  257. package/dist/types/profiler.cjs.map +1 -0
  258. package/dist/types/profiler.d.cts +100 -0
  259. package/dist/types/profiler.d.cts.map +1 -0
  260. package/dist/types/profiler.d.ts +100 -0
  261. package/dist/types/profiler.d.ts.map +1 -0
  262. package/dist/types/profiler.js +10 -0
  263. package/dist/types/profiler.js.map +1 -0
  264. package/dist/types/utility.cjs.map +1 -1
  265. package/dist/types/utility.d.cts +0 -8
  266. package/dist/types/utility.d.cts.map +1 -1
  267. package/dist/types/utility.d.ts +0 -8
  268. package/dist/types/utility.d.ts.map +1 -1
  269. package/dist/types/utility.js.map +1 -1
  270. package/dist/utils/identifiers.cjs +32 -0
  271. package/dist/utils/identifiers.cjs.map +1 -0
  272. package/dist/utils/identifiers.d.cts +32 -0
  273. package/dist/utils/identifiers.d.cts.map +1 -0
  274. package/dist/utils/identifiers.d.ts +32 -0
  275. package/dist/utils/identifiers.d.ts.map +1 -0
  276. package/dist/utils/identifiers.js +27 -0
  277. package/dist/utils/identifiers.js.map +1 -0
  278. package/dist/utils/package.cjs +40 -0
  279. package/dist/utils/package.cjs.map +1 -0
  280. package/dist/utils/package.d.cts +15 -0
  281. package/dist/utils/package.d.cts.map +1 -0
  282. package/dist/utils/package.d.ts +15 -0
  283. package/dist/utils/package.d.ts.map +1 -0
  284. package/dist/utils/package.js +33 -0
  285. package/dist/utils/package.js.map +1 -0
  286. package/dist/utils/type-guards.cjs +48 -0
  287. package/dist/utils/type-guards.cjs.map +1 -0
  288. package/dist/utils/type-guards.d.cts +22 -0
  289. package/dist/utils/type-guards.d.cts.map +1 -0
  290. package/dist/utils/type-guards.d.ts +22 -0
  291. package/dist/utils/type-guards.d.ts.map +1 -0
  292. package/dist/utils/type-guards.js +43 -0
  293. package/dist/utils/type-guards.js.map +1 -0
  294. package/package.json +10 -10
  295. package/src/cli/commands/analyze.ts +101 -0
  296. package/src/cli/commands/baseline.ts +577 -0
  297. package/src/cli/commands/history.ts +1 -1
  298. package/src/cli/commands/init.ts +105 -183
  299. package/src/cli/commands/run.ts +167 -98
  300. package/src/cli/index.ts +425 -183
  301. package/src/config/budget-schema.ts +189 -0
  302. package/src/config/schema.ts +260 -1
  303. package/src/constants.ts +53 -1
  304. package/src/core/engine.ts +151 -20
  305. package/src/core/output-path-resolver.ts +10 -2
  306. package/src/errors/base.ts +11 -2
  307. package/src/errors/budget.ts +38 -0
  308. package/src/errors/index.ts +3 -0
  309. package/src/index.ts +9 -0
  310. package/src/reporters/csv.ts +54 -25
  311. package/src/reporters/human.ts +88 -47
  312. package/src/reporters/json.ts +26 -71
  313. package/src/reporters/profile-human.ts +204 -0
  314. package/src/reporters/simple.ts +84 -53
  315. package/src/services/baseline-storage.ts +199 -0
  316. package/src/services/budget-evaluator.ts +182 -0
  317. package/src/services/config-manager.ts +23 -8
  318. package/src/services/file-loader.ts +3 -6
  319. package/src/services/profiler/profile-filter.ts +143 -0
  320. package/src/services/profiler/profile-parser.ts +194 -0
  321. package/src/services/profiler/profile-runner.ts +121 -0
  322. package/src/services/reporter-registry.ts +46 -81
  323. package/src/types/budgets.ts +180 -0
  324. package/src/types/cli.ts +5 -238
  325. package/src/types/core.ts +50 -10
  326. package/src/types/index.ts +5 -0
  327. package/src/types/interfaces.ts +16 -6
  328. package/src/types/profiler.ts +132 -0
  329. package/src/types/utility.ts +0 -10
  330. package/src/utils/identifiers.ts +58 -0
  331. package/src/utils/package.ts +35 -0
  332. package/src/utils/type-guards.ts +51 -0
@@ -0,0 +1,577 @@
1
+ /**
2
+ * ModestBench Baseline Command
3
+ *
4
+ * Manage performance baselines for regression testing and budget comparison.
5
+ */
6
+
7
+ import { relative } from 'node:path';
8
+
9
+ import type { BenchmarkRun } from '../../types/index.js';
10
+ import type { CliContext } from '../index.js';
11
+
12
+ import { BaselineStorageService } from '../../services/baseline-storage.js';
13
+ import { createTaskId } from '../../types/index.js';
14
+
15
+ /**
16
+ * Options for baseline analyze command
17
+ */
18
+ interface BaselineAnalyzeOptions extends BaselineBaseOptions {
19
+ confidence?: number | undefined;
20
+ runs?: number | undefined;
21
+ }
22
+
23
+ /**
24
+ * Base options shared by all baseline subcommands
25
+ */
26
+ interface BaselineBaseOptions {
27
+ cwd?: string;
28
+ quiet?: boolean | undefined;
29
+ verbose?: boolean | undefined;
30
+ }
31
+
32
+ /**
33
+ * Options for baseline delete command
34
+ */
35
+ interface BaselineDeleteOptions extends BaselineBaseOptions {
36
+ name: string;
37
+ }
38
+
39
+ /**
40
+ * Options for baseline list command
41
+ */
42
+ interface BaselineListOptions extends BaselineBaseOptions {
43
+ format?: 'human' | 'json' | undefined;
44
+ }
45
+
46
+ /**
47
+ * Options for baseline set command
48
+ */
49
+ interface BaselineSetOptions extends BaselineBaseOptions {
50
+ branch?: string | undefined;
51
+ commit?: string | undefined;
52
+ default?: boolean | undefined;
53
+ name: string;
54
+ runId?: string | undefined;
55
+ }
56
+
57
+ /**
58
+ * Options for baseline show command
59
+ */
60
+ interface BaselineShowOptions extends BaselineBaseOptions {
61
+ format?: 'human' | 'json' | undefined;
62
+ name: string;
63
+ }
64
+
65
+ /**
66
+ * Format duration in human-readable format
67
+ */
68
+ const formatDuration = (nanoseconds: number): string => {
69
+ if (nanoseconds < 1000) {
70
+ return `${nanoseconds.toFixed(2)}ns`;
71
+ }
72
+ if (nanoseconds < 1000000) {
73
+ return `${(nanoseconds / 1000).toFixed(2)}μs`;
74
+ }
75
+ if (nanoseconds < 1000000000) {
76
+ return `${(nanoseconds / 1000000).toFixed(2)}ms`;
77
+ }
78
+ return `${(nanoseconds / 1000000000).toFixed(2)}s`;
79
+ };
80
+
81
+ /**
82
+ * Format operations per second
83
+ */
84
+ const formatOpsPerSec = (ops: number): string => {
85
+ if (ops < 1000) {
86
+ return `${ops.toFixed(2)} ops/sec`;
87
+ }
88
+ if (ops < 1000000) {
89
+ return `${(ops / 1000).toFixed(2)}K ops/sec`;
90
+ }
91
+ if (ops < 1000000000) {
92
+ return `${(ops / 1000000).toFixed(2)}M ops/sec`;
93
+ }
94
+ return `${(ops / 1000000000).toFixed(2)}B ops/sec`;
95
+ };
96
+
97
+ /**
98
+ * Format date in readable format
99
+ */
100
+ const formatDate = (date: Date): string => {
101
+ return date.toISOString().replace('T', ' ').substring(0, 19);
102
+ };
103
+
104
+ /**
105
+ * Calculate mean of an array of numbers
106
+ */
107
+ const calculateMean = (values: number[]): number => {
108
+ if (values.length === 0) {
109
+ return 0;
110
+ }
111
+ return values.reduce((sum, val) => sum + val, 0) / values.length;
112
+ };
113
+
114
+ /**
115
+ * Calculate standard deviation
116
+ */
117
+ const calculateStdDev = (values: number[]): number => {
118
+ if (values.length === 0) {
119
+ return 0;
120
+ }
121
+ const mean = calculateMean(values);
122
+ const squaredDiffs = values.map((val) => Math.pow(val - mean, 2));
123
+ const variance = calculateMean(squaredDiffs);
124
+ return Math.sqrt(variance);
125
+ };
126
+
127
+ /**
128
+ * Get z-score for confidence level
129
+ */
130
+ const getZScore = (confidence: number): number => {
131
+ // Common confidence levels
132
+ if (confidence >= 0.99) {
133
+ return 2.576;
134
+ } // 99%
135
+ if (confidence >= 0.98) {
136
+ return 2.326;
137
+ } // 98%
138
+ if (confidence >= 0.95) {
139
+ return 1.96;
140
+ } // 95%
141
+ if (confidence >= 0.9) {
142
+ return 1.645;
143
+ } // 90%
144
+ if (confidence >= 0.85) {
145
+ return 1.44;
146
+ } // 85%
147
+ if (confidence >= 0.8) {
148
+ return 1.282;
149
+ } // 80%
150
+ return 1.96; // Default to 95%
151
+ };
152
+
153
+ /**
154
+ * Handle the set subcommand
155
+ */
156
+ export const handleSetCommand = async (
157
+ context: CliContext,
158
+ options: BaselineSetOptions,
159
+ ): Promise<number> => {
160
+ try {
161
+ const storage = new BaselineStorageService(options.cwd);
162
+
163
+ // Get the benchmark run
164
+ let run: BenchmarkRun | null = null;
165
+
166
+ if (options.runId) {
167
+ // Load specific run by ID
168
+ run = await context.historyStorage.loadRun(options.runId);
169
+ if (!run) {
170
+ console.error(`Error: Run with ID "${options.runId}" not found`);
171
+ return 1;
172
+ }
173
+ } else {
174
+ // Get most recent run
175
+ const runs = await context.historyStorage.queryRuns({ limit: 1 });
176
+ if (runs.length === 0) {
177
+ console.error(
178
+ 'Error: No benchmark runs found. Run benchmarks first with "modestbench run"',
179
+ );
180
+ return 1;
181
+ }
182
+ run = runs[0]!;
183
+ }
184
+
185
+ // Save baseline
186
+ await storage.saveBaseline(options.name, run, {
187
+ branch: options.branch,
188
+ commit: options.commit,
189
+ });
190
+
191
+ // Set as default if requested
192
+ if (options.default) {
193
+ await storage.setDefault(options.name);
194
+ }
195
+
196
+ if (!options.quiet) {
197
+ console.log(`✓ Baseline "${options.name}" saved successfully`);
198
+ console.log(` Run ID: ${run.id}`);
199
+ if (options.commit) {
200
+ console.log(` Commit: ${options.commit}`);
201
+ }
202
+ if (options.branch) {
203
+ console.log(` Branch: ${options.branch}`);
204
+ }
205
+ if (options.default) {
206
+ console.log(` Set as default baseline`);
207
+ }
208
+ }
209
+
210
+ return 0;
211
+ } catch (error) {
212
+ console.error(
213
+ 'Failed to save baseline:',
214
+ error instanceof Error ? error.message : String(error),
215
+ );
216
+ return 5;
217
+ }
218
+ };
219
+
220
+ /**
221
+ * Handle the list subcommand
222
+ */
223
+ export const handleListCommand = async (
224
+ context: CliContext,
225
+ options: BaselineListOptions,
226
+ ): Promise<number> => {
227
+ try {
228
+ const storage = new BaselineStorageService(options.cwd);
229
+ const baselines = await storage.listBaselines();
230
+ const defaultBaseline = await storage.getDefault();
231
+
232
+ if (baselines.length === 0) {
233
+ if (!options.quiet) {
234
+ console.log('No baselines found');
235
+ }
236
+ return 0;
237
+ }
238
+
239
+ if (options.format === 'json') {
240
+ console.log(
241
+ JSON.stringify(
242
+ {
243
+ baselines: baselines.map((b) => ({
244
+ ...b,
245
+ date: b.date.toISOString(),
246
+ isDefault: b.name === defaultBaseline,
247
+ })),
248
+ default: defaultBaseline,
249
+ },
250
+ null,
251
+ 2,
252
+ ),
253
+ );
254
+ } else {
255
+ // Human-readable format
256
+ if (!options.quiet) {
257
+ console.log(`\nBaselines (${baselines.length}):\n`);
258
+ }
259
+
260
+ for (const baseline of baselines) {
261
+ const isDefault = baseline.name === defaultBaseline;
262
+ const defaultMarker = isDefault ? ' (default)' : '';
263
+ console.log(` ${baseline.name}${defaultMarker}`);
264
+ console.log(` Date: ${formatDate(baseline.date)}`);
265
+ console.log(` Run ID: ${baseline.runId}`);
266
+ if (baseline.commit) {
267
+ console.log(` Commit: ${baseline.commit.substring(0, 8)}`);
268
+ }
269
+ if (baseline.branch) {
270
+ console.log(` Branch: ${baseline.branch}`);
271
+ }
272
+ console.log(` Tasks: ${Object.keys(baseline.summary).length}`);
273
+ console.log();
274
+ }
275
+ }
276
+
277
+ return 0;
278
+ } catch (error) {
279
+ console.error(
280
+ 'Failed to list baselines:',
281
+ error instanceof Error ? error.message : String(error),
282
+ );
283
+ return 5;
284
+ }
285
+ };
286
+
287
+ /**
288
+ * Handle the show subcommand
289
+ */
290
+ export const handleShowCommand = async (
291
+ context: CliContext,
292
+ options: BaselineShowOptions,
293
+ ): Promise<number> => {
294
+ try {
295
+ const storage = new BaselineStorageService(options.cwd);
296
+ const baseline = await storage.getBaseline(options.name);
297
+
298
+ if (!baseline) {
299
+ console.error(`Error: Baseline "${options.name}" not found`);
300
+ return 1;
301
+ }
302
+
303
+ const defaultBaseline = await storage.getDefault();
304
+ const isDefault = baseline.name === defaultBaseline;
305
+
306
+ if (options.format === 'json') {
307
+ console.log(
308
+ JSON.stringify(
309
+ {
310
+ ...baseline,
311
+ date: baseline.date.toISOString(),
312
+ isDefault,
313
+ },
314
+ null,
315
+ 2,
316
+ ),
317
+ );
318
+ } else {
319
+ // Human-readable format
320
+ console.log(
321
+ `\nBaseline: ${baseline.name}${isDefault ? ' (default)' : ''}\n`,
322
+ );
323
+ console.log(` Date: ${formatDate(baseline.date)}`);
324
+ console.log(` Run ID: ${baseline.runId}`);
325
+ if (baseline.commit) {
326
+ console.log(` Commit: ${baseline.commit}`);
327
+ }
328
+ if (baseline.branch) {
329
+ console.log(` Branch: ${baseline.branch}`);
330
+ }
331
+ console.log();
332
+
333
+ // Show task summary
334
+ const tasks = Object.entries(baseline.summary);
335
+ if (tasks.length > 0) {
336
+ console.log(` Tasks (${tasks.length}):\n`);
337
+ for (const [taskId, data] of tasks) {
338
+ console.log(` ${taskId}`);
339
+ console.log(` Mean: ${formatDuration(data.mean)}`);
340
+ console.log(` Ops/sec: ${formatOpsPerSec(data.opsPerSecond)}`);
341
+ if (data.p99) {
342
+ console.log(` P99: ${formatDuration(data.p99)}`);
343
+ }
344
+ console.log();
345
+ }
346
+ }
347
+ }
348
+
349
+ return 0;
350
+ } catch (error) {
351
+ console.error(
352
+ 'Failed to show baseline:',
353
+ error instanceof Error ? error.message : String(error),
354
+ );
355
+ return 5;
356
+ }
357
+ };
358
+
359
+ /**
360
+ * Handle the delete subcommand
361
+ */
362
+ export const handleDeleteCommand = async (
363
+ context: CliContext,
364
+ options: BaselineDeleteOptions,
365
+ ): Promise<number> => {
366
+ try {
367
+ const storage = new BaselineStorageService(options.cwd);
368
+
369
+ // Check if baseline exists
370
+ const baseline = await storage.getBaseline(options.name);
371
+ if (!baseline) {
372
+ console.error(`Error: Baseline "${options.name}" not found`);
373
+ return 1;
374
+ }
375
+
376
+ // Delete it
377
+ await storage.deleteBaseline(options.name);
378
+
379
+ if (!options.quiet) {
380
+ console.log(`✓ Baseline "${options.name}" deleted successfully`);
381
+ }
382
+
383
+ return 0;
384
+ } catch (error) {
385
+ console.error(
386
+ 'Failed to delete baseline:',
387
+ error instanceof Error ? error.message : String(error),
388
+ );
389
+ return 5;
390
+ }
391
+ };
392
+
393
+ /**
394
+ * Handle the analyze subcommand
395
+ */
396
+ export const handleAnalyzeCommand = async (
397
+ context: CliContext,
398
+ options: BaselineAnalyzeOptions,
399
+ ): Promise<number> => {
400
+ try {
401
+ const runLimit = options.runs || 10;
402
+ const confidence = options.confidence || 0.95;
403
+
404
+ // Validate confidence
405
+ if (confidence < 0.5 || confidence > 0.999) {
406
+ console.error('Error: Confidence must be between 0.5 and 0.999');
407
+ return 1;
408
+ }
409
+
410
+ // Query recent runs
411
+ const runs = await context.historyStorage.queryRuns({ limit: runLimit });
412
+
413
+ if (runs.length < 2) {
414
+ console.error(
415
+ `Error: Insufficient history. Found ${runs.length} run(s), need at least 2 to analyze trends.`,
416
+ );
417
+ return 1;
418
+ }
419
+
420
+ // Collect metrics per task
421
+ const taskMetrics = new Map<
422
+ string,
423
+ { means: number[]; opsPerSec: number[]; p99s: number[] }
424
+ >();
425
+
426
+ for (const run of runs) {
427
+ for (const file of run.files) {
428
+ for (const suite of file.suites) {
429
+ for (const task of suite.tasks) {
430
+ // Normalize file path to be relative to cwd for consistency with budgets
431
+ const relativePath = relative(
432
+ options.cwd || process.cwd(),
433
+ file.filePath,
434
+ );
435
+ const taskId = createTaskId(relativePath, suite.name, task.name);
436
+
437
+ if (!taskMetrics.has(taskId)) {
438
+ taskMetrics.set(taskId, { means: [], opsPerSec: [], p99s: [] });
439
+ }
440
+
441
+ const metrics = taskMetrics.get(taskId)!;
442
+ metrics.means.push(task.mean);
443
+ metrics.opsPerSec.push(task.opsPerSecond);
444
+ if (task.p99) {
445
+ metrics.p99s.push(task.p99);
446
+ }
447
+ }
448
+ }
449
+ }
450
+ }
451
+
452
+ // Calculate suggested budgets (flat format first)
453
+ const zScore = getZScore(confidence);
454
+ const flatBudgets: Record<string, { absolute: Record<string, unknown> }> =
455
+ {};
456
+
457
+ for (const [taskId, metrics] of taskMetrics.entries()) {
458
+ const avgMean = calculateMean(metrics.means);
459
+ const stdDevMean = calculateStdDev(metrics.means);
460
+ const suggestedMaxTime = Math.ceil(avgMean + zScore * stdDevMean);
461
+
462
+ const avgOps = calculateMean(metrics.opsPerSec);
463
+ const stdDevOps = calculateStdDev(metrics.opsPerSec);
464
+ const suggestedMinOps = Math.floor(
465
+ Math.max(0, avgOps - zScore * stdDevOps),
466
+ );
467
+
468
+ flatBudgets[taskId] = {
469
+ absolute: {
470
+ maxTime: suggestedMaxTime,
471
+ ...(suggestedMinOps > 0 && { minOpsPerSec: suggestedMinOps }),
472
+ },
473
+ };
474
+
475
+ // Add p99 if available
476
+ if (metrics.p99s.length > 0) {
477
+ const avgP99 = calculateMean(metrics.p99s);
478
+ const stdDevP99 = calculateStdDev(metrics.p99s);
479
+ const suggestedMaxP99 = Math.ceil(avgP99 + zScore * stdDevP99);
480
+ flatBudgets[taskId].absolute.maxP99 = suggestedMaxP99;
481
+ }
482
+ }
483
+
484
+ // Convert flat budgets to nested format for user config
485
+ const nestedBudgets: Record<
486
+ string,
487
+ Record<string, Record<string, unknown>>
488
+ > = {};
489
+
490
+ for (const [taskId, budget] of Object.entries(flatBudgets)) {
491
+ // Parse taskId format: "file/suite/task"
492
+ const lastSlash = taskId.lastIndexOf('/');
493
+ const secondLastSlash = taskId.lastIndexOf('/', lastSlash - 1);
494
+
495
+ const file = taskId.substring(0, secondLastSlash);
496
+ const suite = taskId.substring(secondLastSlash + 1, lastSlash);
497
+ const task = taskId.substring(lastSlash + 1);
498
+
499
+ if (!nestedBudgets[file]) {
500
+ nestedBudgets[file] = {};
501
+ }
502
+ if (!nestedBudgets[file][suite]) {
503
+ nestedBudgets[file][suite] = {};
504
+ }
505
+ nestedBudgets[file][suite][task] = budget;
506
+ }
507
+
508
+ // Output results
509
+ if (!options.quiet) {
510
+ console.log(
511
+ `\nAnalyzed ${runs.length} run(s) with ${confidence * 100}% confidence\n`,
512
+ );
513
+ console.log('Suggested budget configuration:\n');
514
+ }
515
+
516
+ const config = {
517
+ budgetMode: 'fail',
518
+ budgets: nestedBudgets,
519
+ };
520
+
521
+ console.log(JSON.stringify(config, null, 2));
522
+
523
+ if (!options.quiet) {
524
+ const confidencePercent = confidence * 100;
525
+ console.log('\n' + '='.repeat(70));
526
+ console.log('How these budget values were calculated:');
527
+ console.log('='.repeat(70));
528
+ console.log();
529
+ console.log(
530
+ `Using ${confidencePercent}% confidence level with ${runs.length} historical runs:`,
531
+ );
532
+ console.log();
533
+ console.log(
534
+ ` • maxTime = mean + (${confidencePercent}% z-score × std deviation)`,
535
+ );
536
+ console.log(
537
+ ` • minOpsPerSec = mean - (${confidencePercent}% z-score × std deviation)`,
538
+ );
539
+ console.log(
540
+ ` • maxP99 = mean + (${confidencePercent}% z-score × std deviation)`,
541
+ );
542
+ console.log();
543
+ console.log(
544
+ `This means each budget is statistically expected to pass ${confidencePercent}% of`,
545
+ );
546
+ console.log(
547
+ 'the time based on your historical benchmark data. The higher the',
548
+ );
549
+ console.log(
550
+ 'confidence level, the more lenient the budgets (less likely to fail).',
551
+ );
552
+ console.log();
553
+ console.log('To adjust strictness:');
554
+ console.log(
555
+ ' • Lower confidence (e.g., 0.90) = stricter budgets, catch smaller regressions',
556
+ );
557
+ console.log(
558
+ ' • Higher confidence (e.g., 0.99) = looser budgets, reduce false positives',
559
+ );
560
+ console.log();
561
+ console.log(
562
+ 'Copy the above configuration into your modestbench.config.json file.',
563
+ );
564
+ console.log(
565
+ 'You may adjust the values based on your performance requirements.',
566
+ );
567
+ }
568
+
569
+ return 0;
570
+ } catch (error) {
571
+ console.error(
572
+ 'Failed to analyze history:',
573
+ error instanceof Error ? error.message : String(error),
574
+ );
575
+ return 5;
576
+ }
577
+ };
@@ -27,7 +27,7 @@ import { TrendAnalysisService } from '../../services/history/trend-analysis.js';
27
27
  * Base options shared by all history subcommands
28
28
  */
29
29
  interface BaseHistoryOptions {
30
- cwd: string;
30
+ cwd?: string;
31
31
  quiet?: boolean | undefined;
32
32
  verbose?: boolean | undefined;
33
33
  }