@monster-orange/den 0.0.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 (255) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -1
  4. package/bin/den +68 -0
  5. package/docs/distributions.md +499 -0
  6. package/make/demo.mk +82 -0
  7. package/make/den.mk +80 -0
  8. package/make/journeys.mk +57 -0
  9. package/make/local_dev.mk +154 -0
  10. package/make/security.mk +59 -0
  11. package/make/test.mk +405 -0
  12. package/package.json +101 -4
  13. package/scripts/auth-configure.ts +415 -0
  14. package/scripts/heal-config.ts +92 -0
  15. package/scripts/jsonl-playwright-reporter.js +70 -0
  16. package/scripts/manifest-read.cjs +91 -0
  17. package/scripts/setup-1password.ts +570 -0
  18. package/scripts/setup-secrets.ts +149 -0
  19. package/scripts/view-results.js +156 -0
  20. package/service/install.sh +112 -0
  21. package/src/pillars/api/fixtures/index.ts +128 -0
  22. package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
  23. package/src/pillars/api/global-setup.ts +103 -0
  24. package/src/pillars/api/playwright.config.ts +105 -0
  25. package/src/pillars/api/schemas/example-api.json +110 -0
  26. package/src/pillars/api/tests/health/health.spec.ts +19 -0
  27. package/src/pillars/api/tests/health/manifest.yml +6 -0
  28. package/src/pillars/browser/fixtures/index.ts +232 -0
  29. package/src/pillars/browser/fixtures/package.json +10 -0
  30. package/src/pillars/browser/global-setup.ts +140 -0
  31. package/src/pillars/browser/global-teardown.ts +48 -0
  32. package/src/pillars/browser/playwright.config.ts +212 -0
  33. package/src/pillars/browser/setup.ts +219 -0
  34. package/src/pillars/journeys/app-config.ts +237 -0
  35. package/src/pillars/journeys/discover.ts +160 -0
  36. package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
  37. package/src/pillars/journeys/observability/events.ts +288 -0
  38. package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
  39. package/src/pillars/journeys/report/friction.ts +304 -0
  40. package/src/pillars/journeys/report/junit.ts +105 -0
  41. package/src/pillars/journeys/report/render.ts +273 -0
  42. package/src/pillars/journeys/report/results.ts +323 -0
  43. package/src/pillars/journeys/run.ts +673 -0
  44. package/src/pillars/journeys/runner.ts +1025 -0
  45. package/src/pillars/journeys/sandbox.ts +403 -0
  46. package/src/pillars/journeys/spec/loader.ts +152 -0
  47. package/src/pillars/journeys/spec/schema.ts +208 -0
  48. package/src/pillars/journeys/teardown.ts +98 -0
  49. package/src/pillars/journeys/tools/browser-session.ts +219 -0
  50. package/src/pillars/journeys/tools/browser-tool.ts +914 -0
  51. package/src/pillars/journeys/tools/http-tool.ts +564 -0
  52. package/src/pillars/journeys/tools/ledger.ts +278 -0
  53. package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
  54. package/src/pillars/journeys/verify/assertions.ts +502 -0
  55. package/src/pillars/journeys/verify/verdict.ts +71 -0
  56. package/src/pillars/perf/build-report.ts +36 -0
  57. package/src/pillars/perf/k6.config.js +112 -0
  58. package/src/pillars/perf/run.ts +345 -0
  59. package/src/pillars/perf/scenarios/load-profile.js +97 -0
  60. package/src/pillars/perf/staging.ts +73 -0
  61. package/src/pillars/security/discover.ts +124 -0
  62. package/src/pillars/security/findings.ts +151 -0
  63. package/src/pillars/security/plan.ts +355 -0
  64. package/src/pillars/security/run.ts +757 -0
  65. package/src/pillars/smoke/admin-probe.ts +68 -0
  66. package/src/pillars/smoke/base-url.ts +56 -0
  67. package/src/pillars/smoke/cleanup.ts +8 -0
  68. package/src/pillars/smoke/discover.ts +237 -0
  69. package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
  70. package/src/pillars/smoke/fixture-manager.ts +217 -0
  71. package/src/pillars/smoke/fixtures.ts +468 -0
  72. package/src/pillars/smoke/global-setup-all.ts +262 -0
  73. package/src/pillars/smoke/global-setup-global.ts +262 -0
  74. package/src/pillars/smoke/global-setup.ts +273 -0
  75. package/src/pillars/smoke/global-teardown-all.ts +8 -0
  76. package/src/pillars/smoke/global-teardown-global.ts +8 -0
  77. package/src/pillars/smoke/global-teardown.ts +139 -0
  78. package/src/pillars/smoke/playwright-all.config.ts +147 -0
  79. package/src/pillars/smoke/playwright-global.config.ts +141 -0
  80. package/src/pillars/smoke/playwright.config.ts +162 -0
  81. package/src/pillars/smoke/setup-env.ts +132 -0
  82. package/src/pillars/smoke/setup-global.ts +126 -0
  83. package/src/pillars/smoke/setup.ts +115 -0
  84. package/src/pillars/smoke/smoke-repo.ts +16 -0
  85. package/src/pillars/smoke/staging.ts +49 -0
  86. package/src/pillars/suites/run.ts +147 -0
  87. package/src/scripts/configure-aws-profiles.ts +114 -0
  88. package/src/scripts/register-repos.ts +307 -0
  89. package/src/scripts/setup-default-workspace.ts +203 -0
  90. package/src/shared/agent-actor.ts +77 -0
  91. package/src/shared/applications.ts +138 -0
  92. package/src/shared/auth/api-token.ts +416 -0
  93. package/src/shared/auth/auth0-helper.ts +284 -0
  94. package/src/shared/auth/headless-login.ts +265 -0
  95. package/src/shared/auth/jwt-helper.ts +186 -0
  96. package/src/shared/auth/sudo-vault.ts +69 -0
  97. package/src/shared/auth/test-accounts.ts +98 -0
  98. package/src/shared/auth/token-injection.ts +184 -0
  99. package/src/shared/auth/token-providers/auth0.ts +80 -0
  100. package/src/shared/auth/token-providers/index.ts +182 -0
  101. package/src/shared/auth/token-refresh.ts +273 -0
  102. package/src/shared/banner.ts +15 -0
  103. package/src/shared/brand.ts +79 -0
  104. package/src/shared/cli-tabs.ts +131 -0
  105. package/src/shared/constants.ts +128 -0
  106. package/src/shared/den-config.ts +688 -0
  107. package/src/shared/dev-metrics/emit.ts +354 -0
  108. package/src/shared/edge/config.ts +229 -0
  109. package/src/shared/edge/render.ts +221 -0
  110. package/src/shared/edge/runtime.ts +268 -0
  111. package/src/shared/edge/status.ts +74 -0
  112. package/src/shared/edge/types.ts +62 -0
  113. package/src/shared/engine.ts +67 -0
  114. package/src/shared/environment/aws-profiles.ts +49 -0
  115. package/src/shared/environment/env-selector.ts +161 -0
  116. package/src/shared/environment/playwright-env.ts +82 -0
  117. package/src/shared/environment-manager/catalog.ts +77 -0
  118. package/src/shared/environment-manager/clone-guard.ts +163 -0
  119. package/src/shared/environment-manager/dag.ts +286 -0
  120. package/src/shared/environment-manager/default-workspace.ts +182 -0
  121. package/src/shared/environment-manager/derivation/alias.ts +90 -0
  122. package/src/shared/environment-manager/derivation/cache.ts +97 -0
  123. package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
  124. package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
  125. package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
  126. package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
  127. package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
  128. package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
  129. package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
  130. package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
  131. package/src/shared/environment-manager/derivation/scan.ts +160 -0
  132. package/src/shared/environment-manager/derivation/text.ts +40 -0
  133. package/src/shared/environment-manager/derivation/types.ts +69 -0
  134. package/src/shared/environment-manager/health.ts +635 -0
  135. package/src/shared/environment-manager/index.ts +6 -0
  136. package/src/shared/environment-manager/load-applications.ts +108 -0
  137. package/src/shared/environment-manager/log-tail.ts +213 -0
  138. package/src/shared/environment-manager/make-targets.ts +78 -0
  139. package/src/shared/environment-manager/opctl.ts +335 -0
  140. package/src/shared/environment-manager/readiness.ts +93 -0
  141. package/src/shared/environment-manager/register-local.ts +72 -0
  142. package/src/shared/environment-manager/secrets-check.ts +260 -0
  143. package/src/shared/environment-manager/service-container.ts +47 -0
  144. package/src/shared/environment-manager/service-env.ts +110 -0
  145. package/src/shared/environment-manager/service-runner.ts +1603 -0
  146. package/src/shared/environment-manager/sync-targets.ts +66 -0
  147. package/src/shared/environment-manager/up-plan.ts +326 -0
  148. package/src/shared/environment-manager/workspace-manager.ts +905 -0
  149. package/src/shared/environment-manager/workspaces.ts +860 -0
  150. package/src/shared/manifest/deps.ts +27 -0
  151. package/src/shared/manifest/merge-override.ts +75 -0
  152. package/src/shared/manifest/migrate-manifest.ts +101 -0
  153. package/src/shared/manifest/placement.ts +321 -0
  154. package/src/shared/manifest/read-manifest.ts +278 -0
  155. package/src/shared/manifest/schema.ts +313 -0
  156. package/src/shared/manifest/service-links.ts +93 -0
  157. package/src/shared/manifest/validate-layers.ts +229 -0
  158. package/src/shared/manifest/validate-manifest.ts +200 -0
  159. package/src/shared/manifest/workspace-manifests.ts +92 -0
  160. package/src/shared/open-url.ts +122 -0
  161. package/src/shared/org.ts +44 -0
  162. package/src/shared/paths.ts +190 -0
  163. package/src/shared/perf/optional-fields.ts +105 -0
  164. package/src/shared/perf/report-normalizer.ts +404 -0
  165. package/src/shared/perf/report-paths.ts +19 -0
  166. package/src/shared/perf/report-store.ts +110 -0
  167. package/src/shared/perf/report-types.ts +70 -0
  168. package/src/shared/perf/test-paths.ts +25 -0
  169. package/src/shared/process/log-rotation.ts +147 -0
  170. package/src/shared/process/spawn-streaming.ts +229 -0
  171. package/src/shared/product.ts +92 -0
  172. package/src/shared/profile.ts +141 -0
  173. package/src/shared/redact.ts +107 -0
  174. package/src/shared/repo-kind.ts +32 -0
  175. package/src/shared/reporting/jsonl-reporter.ts +131 -0
  176. package/src/shared/reporting/slack-reporter.ts +340 -0
  177. package/src/shared/results/result-reader.ts +266 -0
  178. package/src/shared/results/result-writer.ts +168 -0
  179. package/src/shared/secrets/doppler.ts +126 -0
  180. package/src/shared/secrets/env.ts +48 -0
  181. package/src/shared/secrets/index.ts +197 -0
  182. package/src/shared/secrets/keys.ts +33 -0
  183. package/src/shared/secrets/onepassword.ts +75 -0
  184. package/src/shared/secrets/types.ts +52 -0
  185. package/src/shared/service-url.ts +119 -0
  186. package/src/shared/smoke/branch-resolver.ts +141 -0
  187. package/src/shared/smoke/git-remote.ts +70 -0
  188. package/src/shared/smoke/repo-cloner.ts +151 -0
  189. package/src/shared/stacks/config.ts +137 -0
  190. package/src/shared/stacks/manifest-commands.ts +118 -0
  191. package/src/shared/stacks/render.ts +253 -0
  192. package/src/shared/stacks/runtime.ts +334 -0
  193. package/src/shared/stacks/status.ts +99 -0
  194. package/src/shared/stacks/types.ts +53 -0
  195. package/src/shared/terminal/tabs.ts +212 -0
  196. package/src/shared/test-discovery/module-scanner.ts +569 -0
  197. package/src/shared/types/index.ts +701 -0
  198. package/src/shared/ui-marker.ts +19 -0
  199. package/src/ui/cli/applications-command.ts +148 -0
  200. package/src/ui/cli/config-command.ts +68 -0
  201. package/src/ui/cli/create-main.ts +9 -0
  202. package/src/ui/cli/create.ts +360 -0
  203. package/src/ui/cli/edge-command.ts +81 -0
  204. package/src/ui/cli/flag-parser.ts +246 -0
  205. package/src/ui/cli/graph-command.ts +570 -0
  206. package/src/ui/cli/health-display.ts +63 -0
  207. package/src/ui/cli/index.ts +3742 -0
  208. package/src/ui/cli/init.ts +855 -0
  209. package/src/ui/cli/invocation-target.ts +175 -0
  210. package/src/ui/cli/logs-command.ts +104 -0
  211. package/src/ui/cli/mcp-main.ts +13 -0
  212. package/src/ui/cli/mcp-stdout-guard.ts +10 -0
  213. package/src/ui/cli/module-selection.ts +31 -0
  214. package/src/ui/cli/prompts.ts +767 -0
  215. package/src/ui/cli/results-viewer.ts +436 -0
  216. package/src/ui/cli/secrets-preflight.ts +91 -0
  217. package/src/ui/cli/stack-command.ts +117 -0
  218. package/src/ui/cli/stalled-setup-recovery.ts +129 -0
  219. package/src/ui/cli/test-runner.ts +1319 -0
  220. package/src/ui/cli/web-command.ts +1073 -0
  221. package/src/ui/mcp/api.ts +284 -0
  222. package/src/ui/mcp/guards.ts +141 -0
  223. package/src/ui/mcp/server.ts +147 -0
  224. package/src/ui/mcp/tools.ts +565 -0
  225. package/src/ui/server/audit.ts +71 -0
  226. package/src/ui/server/changelog-parser.ts +105 -0
  227. package/src/ui/server/daemon-reaper.ts +129 -0
  228. package/src/ui/server/editor-manager.ts +301 -0
  229. package/src/ui/server/github-service.ts +223 -0
  230. package/src/ui/server/graph-prefs.ts +89 -0
  231. package/src/ui/server/health-row.ts +15 -0
  232. package/src/ui/server/http-helpers.ts +98 -0
  233. package/src/ui/server/index.ts +3350 -0
  234. package/src/ui/server/log-history.ts +453 -0
  235. package/src/ui/server/perf-report.ts +42 -0
  236. package/src/ui/server/public/environments.html +496 -0
  237. package/src/ui/server/public/favicon.ico +0 -0
  238. package/src/ui/server/public/graph-model.js +416 -0
  239. package/src/ui/server/public/graph.css +976 -0
  240. package/src/ui/server/public/graph.js +986 -0
  241. package/src/ui/server/public/header.js +1373 -0
  242. package/src/ui/server/public/index.html +2595 -0
  243. package/src/ui/server/public/local-dev.html +5344 -0
  244. package/src/ui/server/public/logs.html +1496 -0
  245. package/src/ui/server/public/panel.js +787 -0
  246. package/src/ui/server/public/perf-report.js +359 -0
  247. package/src/ui/server/public/release.html +2111 -0
  248. package/src/ui/server/public/services.html +1103 -0
  249. package/src/ui/server/public/workspace-picker.js +69 -0
  250. package/src/ui/server/readonly-routes.ts +200 -0
  251. package/src/ui/server/release-deploy-session.ts +246 -0
  252. package/src/ui/server/release-service.ts +1125 -0
  253. package/src/ui/server/sudo-store.ts +111 -0
  254. package/src/ui/server/ui-assets.ts +73 -0
  255. package/src/ui/server/workspace-routes.ts +2185 -0
@@ -0,0 +1,359 @@
1
+ function escapeHtml(value) {
2
+ return String(value)
3
+ .replaceAll("&", "&")
4
+ .replaceAll("<", "&lt;")
5
+ .replaceAll(">", "&gt;")
6
+ .replaceAll('"', "&quot;")
7
+ .replaceAll("'", "&#39;");
8
+ }
9
+
10
+ const METRIC_VALUE_ORDER = [
11
+ "avg",
12
+ "med",
13
+ "p(50)",
14
+ "p(90)",
15
+ "p(95)",
16
+ "p(99)",
17
+ "min",
18
+ "max",
19
+ "rate",
20
+ "count",
21
+ "value",
22
+ "passes",
23
+ "fails",
24
+ ];
25
+
26
+ function formatNumber(value) {
27
+ if (typeof value !== "number" || !Number.isFinite(value)) {
28
+ return String(value);
29
+ }
30
+
31
+ if (Number.isInteger(value)) {
32
+ return String(value);
33
+ }
34
+
35
+ const abs = Math.abs(value);
36
+ if (abs >= 100) {
37
+ return value.toFixed(2).replace(/\.?0+$/, "");
38
+ }
39
+
40
+ if (abs >= 1) {
41
+ return value.toFixed(2).replace(/\.?0+$/, "");
42
+ }
43
+
44
+ return value.toFixed(4).replace(/\.?0+$/, "");
45
+ }
46
+
47
+ function isRateLikeLabel(label) {
48
+ return /rate|failure/i.test(label ?? "");
49
+ }
50
+
51
+ function isRateLikeKey(key) {
52
+ return key === "rate" || /rate|error/i.test(key ?? "");
53
+ }
54
+
55
+ function formatRate(value) {
56
+ if (typeof value !== "number") {
57
+ return String(value);
58
+ }
59
+
60
+ return `${formatNumber(value * 100)}%`;
61
+ }
62
+
63
+ function formatDisplayValue(value, options = {}) {
64
+ if (value == null) {
65
+ return "—";
66
+ }
67
+
68
+ if (typeof value !== "number") {
69
+ return String(value);
70
+ }
71
+
72
+ if (options.asPercent) {
73
+ return formatRate(value);
74
+ }
75
+
76
+ const formatted = formatNumber(value);
77
+ return options.unit ? `${formatted} ${options.unit}` : formatted;
78
+ }
79
+
80
+ function renderMetricList(metrics) {
81
+ if (!metrics?.length) {
82
+ return '<div class="perf-empty">No metrics captured.</div>';
83
+ }
84
+
85
+ return metrics
86
+ .map(
87
+ (metric) => `
88
+ <div class="perf-stat">
89
+ <span class="perf-stat-label">${escapeHtml(metric.label)}</span>
90
+ <strong>${escapeHtml(
91
+ formatDisplayValue(metric.value, {
92
+ unit: metric.unit,
93
+ asPercent: isRateLikeLabel(metric.label),
94
+ }),
95
+ )}</strong>
96
+ </div>`,
97
+ )
98
+ .join("");
99
+ }
100
+
101
+ function renderMetricValueMap(values, metricType, metricContains) {
102
+ const entries = Object.entries(values ?? {}).sort(([left], [right]) => {
103
+ const leftIndex = METRIC_VALUE_ORDER.indexOf(left);
104
+ const rightIndex = METRIC_VALUE_ORDER.indexOf(right);
105
+
106
+ if (leftIndex === -1 && rightIndex === -1) {
107
+ return left.localeCompare(right);
108
+ }
109
+ if (leftIndex === -1) return 1;
110
+ if (rightIndex === -1) return -1;
111
+ return leftIndex - rightIndex;
112
+ });
113
+ if (!entries.length) {
114
+ return '<span class="perf-muted">No metric values captured</span>';
115
+ }
116
+
117
+ const unit = metricContains === "time" ? "ms" : undefined;
118
+
119
+ return entries
120
+ .map(([key, value]) => {
121
+ const usePercent = key === "rate" && metricType === "rate";
122
+ const suffix = key === "rate" && !usePercent ? "/s" : undefined;
123
+ return `<div class="perf-value-chip">${escapeHtml(key)}: ${escapeHtml(
124
+ formatDisplayValue(value, {
125
+ asPercent: usePercent,
126
+ unit: suffix ?? unit,
127
+ }),
128
+ )}</div>`;
129
+ })
130
+ .join("");
131
+ }
132
+
133
+ function renderTable(headers, rows, emptyMessage = "No data captured.") {
134
+ if (!rows.length) {
135
+ return `<div class="perf-empty">${escapeHtml(emptyMessage)}</div>`;
136
+ }
137
+
138
+ return `
139
+ <table class="perf-table">
140
+ <thead>
141
+ <tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("")}</tr>
142
+ </thead>
143
+ <tbody>
144
+ ${rows
145
+ .map(
146
+ (cells) => `
147
+ <tr>${cells
148
+ .map((cell) => {
149
+ if (cell == null) return "<td>—</td>";
150
+ if (typeof cell === "object" && cell && "html" in cell) {
151
+ return `<td>${cell.html}</td>`;
152
+ }
153
+ return `<td>${escapeHtml(
154
+ typeof cell === "number" ? formatNumber(cell) : cell,
155
+ )}</td>`;
156
+ })
157
+ .join("")}</tr>`,
158
+ )
159
+ .join("")}
160
+ </tbody>
161
+ </table>`;
162
+ }
163
+
164
+ export function buildReportPaneModel({ pillar, selectedView, perfReportUrl }) {
165
+ if (pillar === "journeys") {
166
+ // Journeys produce per-run markdown reports + snapshot galleries under
167
+ // reports/journeys/<run-id>/ (no Playwright HTML report); the pane
168
+ // shows the raw run output which links to those artifacts.
169
+ return {
170
+ title: "Journeys Output",
171
+ activeView: "raw",
172
+ openUrl: null,
173
+ tabsVisible: false,
174
+ emptyMessage:
175
+ "Run journeys to see run output; per-run reports land under reports/journeys/",
176
+ };
177
+ }
178
+
179
+ if (pillar === "security") {
180
+ // ZAP writes report.json/report.html per scan under
181
+ // reports/security/<env>/<run>/ (no Playwright HTML report).
182
+ return {
183
+ title: "Security Scan Output",
184
+ activeView: "raw",
185
+ openUrl: null,
186
+ tabsVisible: false,
187
+ emptyMessage:
188
+ "Run security scans to see run output; ZAP reports land under reports/security/",
189
+ };
190
+ }
191
+
192
+ if (pillar === "suites") {
193
+ return {
194
+ title: "Suite Output",
195
+ activeView: "raw",
196
+ openUrl: null,
197
+ tabsVisible: false,
198
+ emptyMessage: "Run the repo suites to see their output",
199
+ };
200
+ }
201
+
202
+ if (pillar !== "performance") {
203
+ return {
204
+ title: "Playwright Report",
205
+ activeView: "playwright",
206
+ openUrl: "/report/",
207
+ tabsVisible: false,
208
+ emptyMessage: "Run tests to see the Playwright report",
209
+ };
210
+ }
211
+
212
+ return {
213
+ title: "Performance Report",
214
+ activeView: selectedView === "raw" ? "raw" : "rendered",
215
+ openUrl: perfReportUrl ?? null,
216
+ tabsVisible: true,
217
+ emptyMessage: "Run performance tests to see the rendered report",
218
+ };
219
+ }
220
+
221
+ export function getNextReportViewOnPillarChange({
222
+ currentPillar,
223
+ nextPillar,
224
+ currentReportView,
225
+ }) {
226
+ if (currentPillar === nextPillar) {
227
+ return currentReportView;
228
+ }
229
+
230
+ if (nextPillar === "performance") {
231
+ return currentReportView === "playwright" ? "rendered" : currentReportView;
232
+ }
233
+
234
+ return currentReportView;
235
+ }
236
+
237
+ export function renderPerfReportHtml(report) {
238
+ const moduleSections = (report.modules ?? [])
239
+ .map((moduleReport) => {
240
+ const thresholds = renderTable(
241
+ ["Metric", "Expression", "Actual", "Status"],
242
+ (moduleReport.thresholds ?? []).map((threshold) => [
243
+ threshold.metric,
244
+ threshold.expression,
245
+ formatDisplayValue(threshold.actualValue, {
246
+ asPercent: /^rate/.test(threshold.expression),
247
+ unit:
248
+ threshold.unit && !/^rate/.test(threshold.expression)
249
+ ? threshold.unit
250
+ : undefined,
251
+ }),
252
+ threshold.passed ? "passed" : "failed",
253
+ ]),
254
+ "No thresholds configured.",
255
+ );
256
+
257
+ const checks = renderTable(
258
+ ["Check", "Passes", "Fails", "Pass rate"],
259
+ (moduleReport.checks ?? []).map((check) => [
260
+ check.name,
261
+ check.passes,
262
+ check.fails,
263
+ check.passRate == null
264
+ ? null
265
+ : `${(check.passRate * 100).toFixed(1)}%`,
266
+ ]),
267
+ "No checks captured.",
268
+ );
269
+
270
+ const scenarios = renderTable(
271
+ ["Scenario", "Executor", "Details"],
272
+ (moduleReport.scenarios ?? []).map((scenario) => [
273
+ scenario.name,
274
+ scenario.executor ?? "—",
275
+ JSON.stringify(scenario.options ?? {}),
276
+ ]),
277
+ "No scenarios captured.",
278
+ );
279
+
280
+ const customMetrics = renderTable(
281
+ ["Metric", "Values", "Thresholds"],
282
+ (moduleReport.customMetrics ?? []).map((metric) => [
283
+ metric.key,
284
+ {
285
+ html: renderMetricValueMap(
286
+ metric.values,
287
+ metric.type,
288
+ metric.contains,
289
+ ),
290
+ },
291
+ metric.thresholds?.length
292
+ ? metric.thresholds
293
+ .map(
294
+ (threshold) =>
295
+ `${threshold.expression} (${threshold.passed ? "pass" : "fail"})`,
296
+ )
297
+ .join(", ")
298
+ : "—",
299
+ ]),
300
+ "No custom metrics captured.",
301
+ );
302
+
303
+ return `
304
+ <details class="perf-module"${moduleReport.status === "failed" ? " open" : ""}>
305
+ <summary class="perf-module-header">
306
+ <span class="perf-module-title-wrap">
307
+ <span class="perf-module-toggle" aria-hidden="true">▸</span>
308
+ <span class="perf-module-title">${escapeHtml(moduleReport.module)}</span>
309
+ </span>
310
+ <span class="perf-badge ${moduleReport.status === "passed" ? "perf-ok" : "perf-fail"}">${escapeHtml(moduleReport.status)}</span>
311
+ </summary>
312
+ <div class="perf-section">
313
+ <h4>Summary</h4>
314
+ <div class="perf-stat-grid">${renderMetricList(moduleReport.coreMetrics)}</div>
315
+ </div>
316
+ <div class="perf-section">
317
+ <h4>Latency</h4>
318
+ <div class="perf-stat-grid">${renderMetricList(moduleReport.latencyMetrics)}</div>
319
+ </div>
320
+ <div class="perf-section">
321
+ <h4>Throughput &amp; Errors</h4>
322
+ <div class="perf-stat-grid">${renderMetricList(moduleReport.throughputMetrics)}</div>
323
+ </div>
324
+ <div class="perf-section">
325
+ <h4>Thresholds</h4>
326
+ ${thresholds}
327
+ </div>
328
+ <div class="perf-section">
329
+ <h4>Scenarios</h4>
330
+ ${scenarios}
331
+ </div>
332
+ <div class="perf-section">
333
+ <h4>Checks</h4>
334
+ ${checks}
335
+ </div>
336
+ <div class="perf-section">
337
+ <h4>Custom Metrics</h4>
338
+ ${customMetrics}
339
+ </div>
340
+ </details>`;
341
+ })
342
+ .join("");
343
+
344
+ return `
345
+ <article class="perf-report">
346
+ <header class="perf-run-header">
347
+ <div>
348
+ <h2>Summary</h2>
349
+ <p>${escapeHtml(report.environment)} · ${escapeHtml(report.status)}</p>
350
+ </div>
351
+ <div class="perf-run-meta">
352
+ <span>Modules: ${escapeHtml(report.modules?.length ?? 0)}</span>
353
+ <span>Started: ${escapeHtml(report.startedAt ?? "—")}</span>
354
+ <span>Finished: ${escapeHtml(report.finishedAt ?? "—")}</span>
355
+ </div>
356
+ </header>
357
+ ${moduleSections}
358
+ </article>`;
359
+ }