modestbench 0.1.0 → 0.2.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 (267) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +39 -31
  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/history.cjs +108 -266
  10. package/dist/cli/commands/history.cjs.map +1 -1
  11. package/dist/cli/commands/history.d.cts +75 -12
  12. package/dist/cli/commands/history.d.cts.map +1 -1
  13. package/dist/cli/commands/history.d.ts +75 -12
  14. package/dist/cli/commands/history.d.ts.map +1 -1
  15. package/dist/cli/commands/history.js +105 -268
  16. package/dist/cli/commands/history.js.map +1 -1
  17. package/dist/cli/commands/run.cjs +18 -5
  18. package/dist/cli/commands/run.cjs.map +1 -1
  19. package/dist/cli/commands/run.d.cts +1 -0
  20. package/dist/cli/commands/run.d.cts.map +1 -1
  21. package/dist/cli/commands/run.d.ts +1 -0
  22. package/dist/cli/commands/run.d.ts.map +1 -1
  23. package/dist/cli/commands/run.js +18 -5
  24. package/dist/cli/commands/run.js.map +1 -1
  25. package/dist/cli/index.cjs +307 -91
  26. package/dist/cli/index.cjs.map +1 -1
  27. package/dist/cli/index.d.cts.map +1 -1
  28. package/dist/cli/index.d.ts.map +1 -1
  29. package/dist/cli/index.js +308 -92
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/core/engine.cjs +8 -1
  32. package/dist/core/engine.cjs.map +1 -1
  33. package/dist/core/engine.d.cts +3 -0
  34. package/dist/core/engine.d.cts.map +1 -1
  35. package/dist/core/engine.d.ts +3 -0
  36. package/dist/core/engine.d.ts.map +1 -1
  37. package/dist/core/engine.js +8 -1
  38. package/dist/core/engine.js.map +1 -1
  39. package/dist/core/output-path-resolver.cjs +34 -0
  40. package/dist/core/output-path-resolver.cjs.map +1 -0
  41. package/dist/core/output-path-resolver.d.cts +10 -0
  42. package/dist/core/output-path-resolver.d.cts.map +1 -0
  43. package/dist/core/output-path-resolver.d.ts +10 -0
  44. package/dist/core/output-path-resolver.d.ts.map +1 -0
  45. package/dist/core/output-path-resolver.js +30 -0
  46. package/dist/core/output-path-resolver.js.map +1 -0
  47. package/dist/formatters/history/base.cjs +9 -0
  48. package/dist/formatters/history/base.cjs.map +1 -0
  49. package/dist/formatters/history/base.d.cts +26 -0
  50. package/dist/formatters/history/base.d.cts.map +1 -0
  51. package/dist/formatters/history/base.d.ts +26 -0
  52. package/dist/formatters/history/base.d.ts.map +1 -0
  53. package/dist/formatters/history/base.js +8 -0
  54. package/dist/formatters/history/base.js.map +1 -0
  55. package/dist/formatters/history/compare.cjs +127 -0
  56. package/dist/formatters/history/compare.cjs.map +1 -0
  57. package/dist/formatters/history/compare.d.cts +21 -0
  58. package/dist/formatters/history/compare.d.cts.map +1 -0
  59. package/dist/formatters/history/compare.d.ts +21 -0
  60. package/dist/formatters/history/compare.d.ts.map +1 -0
  61. package/dist/formatters/history/compare.js +123 -0
  62. package/dist/formatters/history/compare.js.map +1 -0
  63. package/dist/formatters/history/list.cjs +74 -0
  64. package/dist/formatters/history/list.cjs.map +1 -0
  65. package/dist/formatters/history/list.d.cts +25 -0
  66. package/dist/formatters/history/list.d.cts.map +1 -0
  67. package/dist/formatters/history/list.d.ts +25 -0
  68. package/dist/formatters/history/list.d.ts.map +1 -0
  69. package/dist/formatters/history/list.js +70 -0
  70. package/dist/formatters/history/list.js.map +1 -0
  71. package/dist/formatters/history/show.cjs +98 -0
  72. package/dist/formatters/history/show.cjs.map +1 -0
  73. package/dist/formatters/history/show.d.cts +21 -0
  74. package/dist/formatters/history/show.d.cts.map +1 -0
  75. package/dist/formatters/history/show.d.ts +21 -0
  76. package/dist/formatters/history/show.d.ts.map +1 -0
  77. package/dist/formatters/history/show.js +94 -0
  78. package/dist/formatters/history/show.js.map +1 -0
  79. package/dist/formatters/history/trends.cjs +194 -0
  80. package/dist/formatters/history/trends.cjs.map +1 -0
  81. package/dist/formatters/history/trends.d.cts +22 -0
  82. package/dist/formatters/history/trends.d.cts.map +1 -0
  83. package/dist/formatters/history/trends.d.ts +22 -0
  84. package/dist/formatters/history/trends.d.ts.map +1 -0
  85. package/dist/formatters/history/trends.js +190 -0
  86. package/dist/formatters/history/trends.js.map +1 -0
  87. package/dist/formatters/history/visualization.cjs +79 -0
  88. package/dist/formatters/history/visualization.cjs.map +1 -0
  89. package/dist/formatters/history/visualization.d.cts +24 -0
  90. package/dist/formatters/history/visualization.d.cts.map +1 -0
  91. package/dist/formatters/history/visualization.d.ts +24 -0
  92. package/dist/formatters/history/visualization.d.ts.map +1 -0
  93. package/dist/formatters/history/visualization.js +74 -0
  94. package/dist/formatters/history/visualization.js.map +1 -0
  95. package/dist/index.cjs +15 -17
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.d.cts +5 -5
  98. package/dist/index.d.cts.map +1 -1
  99. package/dist/index.d.ts +5 -5
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +7 -9
  102. package/dist/index.js.map +1 -1
  103. package/dist/reporters/csv.cjs +2 -2
  104. package/dist/reporters/csv.cjs.map +1 -1
  105. package/dist/reporters/csv.d.cts +1 -1
  106. package/dist/reporters/csv.d.cts.map +1 -1
  107. package/dist/reporters/csv.d.ts +1 -1
  108. package/dist/reporters/csv.d.ts.map +1 -1
  109. package/dist/reporters/csv.js +1 -1
  110. package/dist/reporters/csv.js.map +1 -1
  111. package/dist/reporters/human.cjs +24 -62
  112. package/dist/reporters/human.cjs.map +1 -1
  113. package/dist/reporters/human.d.cts +1 -1
  114. package/dist/reporters/human.d.cts.map +1 -1
  115. package/dist/reporters/human.d.ts +1 -1
  116. package/dist/reporters/human.d.ts.map +1 -1
  117. package/dist/reporters/human.js +2 -40
  118. package/dist/reporters/human.js.map +1 -1
  119. package/dist/reporters/json.cjs +2 -2
  120. package/dist/reporters/json.cjs.map +1 -1
  121. package/dist/reporters/json.d.cts +1 -1
  122. package/dist/reporters/json.d.cts.map +1 -1
  123. package/dist/reporters/json.d.ts +1 -1
  124. package/dist/reporters/json.d.ts.map +1 -1
  125. package/dist/reporters/json.js +1 -1
  126. package/dist/reporters/json.js.map +1 -1
  127. package/dist/reporters/simple.cjs +2 -2
  128. package/dist/reporters/simple.cjs.map +1 -1
  129. package/dist/reporters/simple.d.cts +1 -1
  130. package/dist/reporters/simple.d.cts.map +1 -1
  131. package/dist/reporters/simple.d.ts +1 -1
  132. package/dist/reporters/simple.d.ts.map +1 -1
  133. package/dist/reporters/simple.js +1 -1
  134. package/dist/reporters/simple.js.map +1 -1
  135. package/dist/{config/manager.cjs → services/config-manager.cjs} +2 -2
  136. package/dist/services/config-manager.cjs.map +1 -0
  137. package/dist/{config/manager.d.cts → services/config-manager.d.cts} +1 -1
  138. package/dist/services/config-manager.d.cts.map +1 -0
  139. package/dist/{config/manager.d.ts → services/config-manager.d.ts} +1 -1
  140. package/dist/services/config-manager.d.ts.map +1 -0
  141. package/dist/{config/manager.js → services/config-manager.js} +2 -2
  142. package/dist/services/config-manager.js.map +1 -0
  143. package/dist/{core/loader.cjs → services/file-loader.cjs} +2 -2
  144. package/dist/services/file-loader.cjs.map +1 -0
  145. package/dist/{core/loader.d.cts → services/file-loader.d.cts} +1 -1
  146. package/dist/services/file-loader.d.cts.map +1 -0
  147. package/dist/{core/loader.d.ts → services/file-loader.d.ts} +1 -1
  148. package/dist/services/file-loader.d.ts.map +1 -0
  149. package/dist/{core/loader.js → services/file-loader.js} +2 -2
  150. package/dist/services/file-loader.js.map +1 -0
  151. package/dist/services/history/comparison.cjs +124 -0
  152. package/dist/services/history/comparison.cjs.map +1 -0
  153. package/dist/services/history/comparison.d.cts +18 -0
  154. package/dist/services/history/comparison.d.cts.map +1 -0
  155. package/dist/services/history/comparison.d.ts +18 -0
  156. package/dist/services/history/comparison.d.ts.map +1 -0
  157. package/dist/services/history/comparison.js +120 -0
  158. package/dist/services/history/comparison.js.map +1 -0
  159. package/dist/services/history/models.cjs +9 -0
  160. package/dist/services/history/models.cjs.map +1 -0
  161. package/dist/services/history/models.d.cts +139 -0
  162. package/dist/services/history/models.d.cts.map +1 -0
  163. package/dist/services/history/models.d.ts +139 -0
  164. package/dist/services/history/models.d.ts.map +1 -0
  165. package/dist/services/history/models.js +8 -0
  166. package/dist/services/history/models.js.map +1 -0
  167. package/dist/services/history/query.cjs +97 -0
  168. package/dist/services/history/query.cjs.map +1 -0
  169. package/dist/services/history/query.d.cts +38 -0
  170. package/dist/services/history/query.d.cts.map +1 -0
  171. package/dist/services/history/query.d.ts +38 -0
  172. package/dist/services/history/query.d.ts.map +1 -0
  173. package/dist/services/history/query.js +92 -0
  174. package/dist/services/history/query.js.map +1 -0
  175. package/dist/services/history/trend-analysis.cjs +187 -0
  176. package/dist/services/history/trend-analysis.cjs.map +1 -0
  177. package/dist/services/history/trend-analysis.d.cts +34 -0
  178. package/dist/services/history/trend-analysis.d.cts.map +1 -0
  179. package/dist/services/history/trend-analysis.d.ts +34 -0
  180. package/dist/services/history/trend-analysis.d.ts.map +1 -0
  181. package/dist/services/history/trend-analysis.js +179 -0
  182. package/dist/services/history/trend-analysis.js.map +1 -0
  183. package/dist/{storage/history.cjs → services/history-storage.cjs} +1 -1
  184. package/dist/services/history-storage.cjs.map +1 -0
  185. package/dist/{storage/history.d.cts → services/history-storage.d.cts} +1 -1
  186. package/dist/services/history-storage.d.cts.map +1 -0
  187. package/dist/{storage/history.d.ts → services/history-storage.d.ts} +1 -1
  188. package/dist/services/history-storage.d.ts.map +1 -0
  189. package/dist/{storage/history.js → services/history-storage.js} +1 -1
  190. package/dist/services/history-storage.js.map +1 -0
  191. package/dist/{progress/manager.cjs → services/progress-manager.cjs} +1 -1
  192. package/dist/services/progress-manager.cjs.map +1 -0
  193. package/dist/{progress/manager.d.cts → services/progress-manager.d.cts} +1 -1
  194. package/dist/services/progress-manager.d.cts.map +1 -0
  195. package/dist/{progress/manager.d.ts → services/progress-manager.d.ts} +1 -1
  196. package/dist/services/progress-manager.d.ts.map +1 -0
  197. package/dist/{progress/manager.js → services/progress-manager.js} +1 -1
  198. package/dist/services/progress-manager.js.map +1 -0
  199. package/dist/{reporters/registry.cjs → services/reporter-registry.cjs} +1 -1
  200. package/dist/services/reporter-registry.cjs.map +1 -0
  201. package/dist/{reporters/registry.d.cts → services/reporter-registry.d.cts} +1 -1
  202. package/dist/services/reporter-registry.d.cts.map +1 -0
  203. package/dist/{reporters/registry.d.ts → services/reporter-registry.d.ts} +1 -1
  204. package/dist/services/reporter-registry.d.ts.map +1 -0
  205. package/dist/{reporters/registry.js → services/reporter-registry.js} +1 -1
  206. package/dist/services/reporter-registry.js.map +1 -0
  207. package/dist/types/cli.d.cts +3 -0
  208. package/dist/types/cli.d.cts.map +1 -1
  209. package/dist/types/cli.d.ts +3 -0
  210. package/dist/types/cli.d.ts.map +1 -1
  211. package/dist/utils/ansi.cjs +61 -0
  212. package/dist/utils/ansi.cjs.map +1 -0
  213. package/dist/utils/ansi.d.cts +53 -0
  214. package/dist/utils/ansi.d.cts.map +1 -0
  215. package/dist/utils/ansi.d.ts +53 -0
  216. package/dist/utils/ansi.d.ts.map +1 -0
  217. package/dist/utils/ansi.js +57 -0
  218. package/dist/utils/ansi.js.map +1 -0
  219. package/package.json +5 -4
  220. package/src/bootstrap.ts +5 -5
  221. package/src/cli/commands/history.ts +194 -342
  222. package/src/cli/commands/run.ts +32 -3
  223. package/src/cli/index.ts +361 -106
  224. package/src/core/engine.ts +9 -1
  225. package/src/core/output-path-resolver.ts +38 -0
  226. package/src/formatters/history/base.ts +28 -0
  227. package/src/formatters/history/compare.ts +186 -0
  228. package/src/formatters/history/list.ts +101 -0
  229. package/src/formatters/history/show.ts +155 -0
  230. package/src/formatters/history/trends.ts +281 -0
  231. package/src/formatters/history/visualization.ts +93 -0
  232. package/src/index.ts +7 -11
  233. package/src/reporters/csv.ts +1 -1
  234. package/src/reporters/human.ts +2 -42
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/simple.ts +1 -1
  237. package/src/{config/manager.ts → services/config-manager.ts} +1 -1
  238. package/src/{core/loader.ts → services/file-loader.ts} +1 -1
  239. package/src/services/history/comparison.ts +130 -0
  240. package/src/services/history/models.ts +148 -0
  241. package/src/services/history/query.ts +116 -0
  242. package/src/services/history/trend-analysis.ts +238 -0
  243. package/src/types/cli.ts +3 -0
  244. package/src/utils/ansi.ts +59 -0
  245. package/dist/config/manager.cjs.map +0 -1
  246. package/dist/config/manager.d.cts.map +0 -1
  247. package/dist/config/manager.d.ts.map +0 -1
  248. package/dist/config/manager.js.map +0 -1
  249. package/dist/core/loader.cjs.map +0 -1
  250. package/dist/core/loader.d.cts.map +0 -1
  251. package/dist/core/loader.d.ts.map +0 -1
  252. package/dist/core/loader.js.map +0 -1
  253. package/dist/progress/manager.cjs.map +0 -1
  254. package/dist/progress/manager.d.cts.map +0 -1
  255. package/dist/progress/manager.d.ts.map +0 -1
  256. package/dist/progress/manager.js.map +0 -1
  257. package/dist/reporters/registry.cjs.map +0 -1
  258. package/dist/reporters/registry.d.cts.map +0 -1
  259. package/dist/reporters/registry.d.ts.map +0 -1
  260. package/dist/reporters/registry.js.map +0 -1
  261. package/dist/storage/history.cjs.map +0 -1
  262. package/dist/storage/history.d.cts.map +0 -1
  263. package/dist/storage/history.d.ts.map +0 -1
  264. package/dist/storage/history.js.map +0 -1
  265. /package/src/{storage/history.ts → services/history-storage.ts} +0 -0
  266. /package/src/{progress/manager.ts → services/progress-manager.ts} +0 -0
  267. /package/src/{reporters/registry.ts → services/reporter-registry.ts} +0 -0
@@ -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
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * List Formatter
3
+ *
4
+ * Formats historical run listings in human, JSON, and CSV formats.
5
+ */
6
+
7
+ import type { HistoryListResult } from '../../services/history/models.js';
8
+ import type { HistoryFormatter } from './base.js';
9
+
10
+ import { ansiChars, colorize } from '../../utils/ansi.js';
11
+
12
+ /**
13
+ * Formatter for history list command
14
+ */
15
+ export class HistoryListFormatter
16
+ implements HistoryFormatter<HistoryListResult>
17
+ {
18
+ /**
19
+ * Format as CSV
20
+ */
21
+ formatCsv(data: HistoryListResult): string {
22
+ const lines: string[] = ['id,startTime,duration,files,tasks,passed,failed'];
23
+
24
+ for (const run of data.runs) {
25
+ lines.push(
26
+ `${run.id},${run.startTime.toISOString()},${run.duration},${run.summary.totalFiles},${run.summary.totalTasks},${run.summary.passedTasks},${run.summary.failedTasks}`,
27
+ );
28
+ }
29
+
30
+ return lines.join('\n');
31
+ }
32
+
33
+ /**
34
+ * Format as human-readable list
35
+ */
36
+ formatHuman(data: HistoryListResult): string {
37
+ if (data.runs.length === 0) {
38
+ return colorize('dim', 'No historical data found matching criteria.');
39
+ }
40
+
41
+ const lines: string[] = [
42
+ colorize('brightMagenta', colorize('bold', '\nRecent Benchmark Runs')),
43
+ '',
44
+ ];
45
+
46
+ for (const run of data.runs) {
47
+ const dateStr = run.startTime.toLocaleString();
48
+ const durationStr = `${(run.duration / 1000).toFixed(1)}s`;
49
+
50
+ // Status with colors and symbols
51
+ const hasFailures = run.summary.failedTasks > 0;
52
+ const statusIcon = hasFailures
53
+ ? colorize('brightRed', ansiChars.cross)
54
+ : colorize('brightCyan', ansiChars.checkmark);
55
+
56
+ const passedStr = colorize(
57
+ 'brightCyan',
58
+ `${run.summary.passedTasks} passed`,
59
+ );
60
+ const statusStr = hasFailures
61
+ ? `${passedStr}, ${colorize('brightRed', `${run.summary.failedTasks} failed`)}`
62
+ : passedStr;
63
+
64
+ // Run ID in bright white and bold, date dimmed, duration in magenta
65
+ lines.push(
66
+ ` ${statusIcon} ${colorize('brightWhite', colorize('bold', run.id))} ${colorize('dim', ansiChars.bullet)} ${colorize('gray', dateStr)} ${colorize('dim', ansiChars.bullet)} ${colorize('brightMagenta', durationStr)}`,
67
+ );
68
+
69
+ // Files and tasks info
70
+ lines.push(
71
+ ` ${colorize('dim', `${run.summary.totalFiles} files, ${run.summary.totalTasks} tasks:`)} ${statusStr}`,
72
+ );
73
+ lines.push('');
74
+ }
75
+
76
+ return lines.join('\n');
77
+ }
78
+
79
+ /**
80
+ * Format as JSON array
81
+ */
82
+ formatJson(data: HistoryListResult): string {
83
+ if (data.runs.length === 0) {
84
+ return '[]';
85
+ }
86
+
87
+ return JSON.stringify(
88
+ data.runs.map((run) => ({
89
+ duration: run.duration,
90
+ failed: run.summary.failedTasks,
91
+ files: run.summary.totalFiles,
92
+ id: run.id,
93
+ passed: run.summary.passedTasks,
94
+ startTime: run.startTime,
95
+ tasks: run.summary.totalTasks,
96
+ })),
97
+ null,
98
+ 2,
99
+ );
100
+ }
101
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Show Formatter
3
+ *
4
+ * Formats detailed benchmark run display in human and JSON formats.
5
+ */
6
+
7
+ import { relative } from 'node:path';
8
+
9
+ import type { ShowResult } from '../../services/history/models.js';
10
+ import type { HistoryFormatter } from './base.js';
11
+
12
+ import { ansiChars, colorize } from '../../utils/ansi.js';
13
+
14
+ /**
15
+ * Formatter for history show command
16
+ */
17
+ export class HistoryShowFormatter implements HistoryFormatter<ShowResult> {
18
+ /**
19
+ * Format as human-readable detailed view
20
+ */
21
+ formatHuman(data: ShowResult): string {
22
+ const lines: string[] = [];
23
+
24
+ // Header with run ID
25
+ lines.push(
26
+ colorize(
27
+ 'cyan',
28
+ colorize(
29
+ 'bold',
30
+ `\nBenchmark Run: ${colorize('brightWhite', colorize('bold', data.id))}`,
31
+ ),
32
+ ),
33
+ );
34
+
35
+ // Run details (indented by 2 spaces)
36
+ lines.push(
37
+ ` ${colorize('brightCyan', ansiChars.bullet)} ${colorize('white', data.startTime.toLocaleString())}`,
38
+ );
39
+ lines.push(
40
+ ` ${colorize('brightCyan', ansiChars.bullet)} ${colorize('white', 'Duration:')} ${colorize('magenta', `${(data.duration / 1000).toFixed(1)}s`)}`,
41
+ );
42
+ lines.push(
43
+ ` ${colorize('brightCyan', ansiChars.bullet)} Node.js ${colorize('brightWhite', data.environment.nodeVersion)} on ${colorize('brightWhite', data.environment.platform)} (${colorize('brightWhite', data.environment.arch)})`,
44
+ );
45
+
46
+ // CPU and system info
47
+ lines.push(
48
+ ` ${colorize('brightCyan', ansiChars.bullet)} ${colorize('brightWhite', String(data.environment.cpu.cores))} cores @ ${colorize('brightWhite', `${data.environment.cpu.speed}MHz`)} on ${colorize('brightWhite', data.environment.cpu.model)}`,
49
+ );
50
+
51
+ if (data.git) {
52
+ lines.push(
53
+ ` ${colorize('brightCyan', ansiChars.bullet)} ${colorize('brightBlue', data.git.branch)}@${colorize('dim', data.git.commit.substring(0, 8))}`,
54
+ );
55
+ }
56
+
57
+ // Summary section
58
+ lines.push('');
59
+ lines.push(colorize('cyan', 'Summary'));
60
+ lines.push(
61
+ ` ${colorize('dim', ansiChars.smallSquare)} Files: ${colorize('brightWhite', String(data.summary.totalFiles))}`,
62
+ );
63
+ lines.push(
64
+ ` ${colorize('dim', ansiChars.smallSquare)} Suites: ${colorize('brightWhite', String(data.summary.totalSuites))}`,
65
+ );
66
+ lines.push(
67
+ ` ${colorize('dim', ansiChars.smallSquare)} Tasks: ${colorize('brightWhite', String(data.summary.totalTasks))}`,
68
+ );
69
+
70
+ lines.push(
71
+ ` ${colorize('dim', ansiChars.smallSquare)} Passed: ${colorize('brightCyan', String(data.summary.passedTasks))}`,
72
+ );
73
+
74
+ if (data.summary.failedTasks > 0) {
75
+ lines.push(
76
+ ` ${colorize('dim', ansiChars.smallSquare)} Failed: ${colorize('brightRed', colorize('bold', String(data.summary.failedTasks)))}`,
77
+ );
78
+ }
79
+
80
+ // Detailed results
81
+ lines.push('');
82
+ lines.push(colorize('cyan', 'Results'));
83
+ lines.push('');
84
+
85
+ for (const file of data.files) {
86
+ // Display filepath as relative if within cwd, otherwise absolute
87
+ const displayPath = relative(process.cwd(), file.filePath);
88
+ const finalPath = displayPath.startsWith('..')
89
+ ? file.filePath
90
+ : displayPath;
91
+
92
+ lines.push(
93
+ `${colorize('dim', ansiChars.bullet)} ${colorize('brightMagenta', colorize('bold', finalPath))}`,
94
+ );
95
+
96
+ for (const suite of file.suites) {
97
+ lines.push(
98
+ ` ${colorize('dim', ansiChars.bullet)} ${colorize('brightWhite', suite.name)}`,
99
+ );
100
+
101
+ for (const task of suite.tasks) {
102
+ const statusIcon = task.error
103
+ ? colorize('brightRed', ansiChars.cross)
104
+ : colorize('brightCyan', ansiChars.checkmark);
105
+
106
+ if (task.error) {
107
+ lines.push(
108
+ ` ${statusIcon} ${colorize('white', task.name)} ${colorize('dim', ansiChars.bullet)} ${colorize('brightRed', 'failed')}`,
109
+ );
110
+ } else {
111
+ const mean = formatTime(task.mean);
112
+ const opsStr = task.opsPerSecond.toLocaleString('en-US', {
113
+ maximumFractionDigits: 0,
114
+ });
115
+ const rmeStr = task.marginOfError.toFixed(2);
116
+
117
+ lines.push(
118
+ ` ${statusIcon} ${colorize('white', task.name)}: ${colorize('brightMagenta', mean)} ${colorize('dim', ansiChars.bullet)} ${colorize('brightBlue', `${ansiChars.plusMinus}${rmeStr}`)} ${colorize('dim', ansiChars.bullet)} ${colorize('magenta', opsStr)} ops/sec`,
119
+ );
120
+
121
+ if (task.iterations > 0) {
122
+ lines.push(
123
+ ` ${colorize('dim', `${task.iterations} iterations, cv: ${task.cv.toFixed(1)}%`)}`,
124
+ );
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ lines.push('');
131
+ }
132
+
133
+ return lines.join('\n');
134
+ }
135
+
136
+ /**
137
+ * Format as complete JSON
138
+ */
139
+ formatJson(data: ShowResult): string {
140
+ return JSON.stringify(data, null, 2);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Format nanoseconds as a human-readable time string
146
+ */
147
+ const formatTime = (ns: number): string => {
148
+ if (ns < 1000) {
149
+ return `${ns.toFixed(2)}ns`;
150
+ }
151
+ if (ns < 1000000) {
152
+ return `${(ns / 1000).toFixed(3)}µs`;
153
+ }
154
+ return `${(ns / 1000000).toFixed(3)}ms`;
155
+ };