patchdrill 0.1.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 (169) hide show
  1. package/.patchdrill.yml +33 -0
  2. package/CHANGELOG.md +150 -0
  3. package/CONTRIBUTING.md +59 -0
  4. package/LICENSE +21 -0
  5. package/README.md +601 -0
  6. package/SECURITY.md +28 -0
  7. package/action.yml +338 -0
  8. package/dist/baseline.d.ts +9 -0
  9. package/dist/baseline.js +38 -0
  10. package/dist/baseline.js.map +1 -0
  11. package/dist/cli.d.ts +19 -0
  12. package/dist/cli.js +662 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/codeowners.d.ts +14 -0
  15. package/dist/codeowners.js +104 -0
  16. package/dist/codeowners.js.map +1 -0
  17. package/dist/command-plan.d.ts +3 -0
  18. package/dist/command-plan.js +26 -0
  19. package/dist/command-plan.js.map +1 -0
  20. package/dist/demo.d.ts +5 -0
  21. package/dist/demo.js +525 -0
  22. package/dist/demo.js.map +1 -0
  23. package/dist/dependency.d.ts +4 -0
  24. package/dist/dependency.js +1424 -0
  25. package/dist/dependency.js.map +1 -0
  26. package/dist/doctor.d.ts +26 -0
  27. package/dist/doctor.js +183 -0
  28. package/dist/doctor.js.map +1 -0
  29. package/dist/evidence.d.ts +64 -0
  30. package/dist/evidence.js +352 -0
  31. package/dist/evidence.js.map +1 -0
  32. package/dist/git.d.ts +16 -0
  33. package/dist/git.js +349 -0
  34. package/dist/git.js.map +1 -0
  35. package/dist/i18n-catalog.d.ts +8 -0
  36. package/dist/i18n-catalog.js +446 -0
  37. package/dist/i18n-catalog.js.map +1 -0
  38. package/dist/i18n.d.ts +20 -0
  39. package/dist/i18n.js +67 -0
  40. package/dist/i18n.js.map +1 -0
  41. package/dist/init.d.ts +13 -0
  42. package/dist/init.js +312 -0
  43. package/dist/init.js.map +1 -0
  44. package/dist/markdown-links.d.ts +18 -0
  45. package/dist/markdown-links.js +180 -0
  46. package/dist/markdown-links.js.map +1 -0
  47. package/dist/package-scripts.d.ts +3 -0
  48. package/dist/package-scripts.js +55 -0
  49. package/dist/package-scripts.js.map +1 -0
  50. package/dist/planner.d.ts +8 -0
  51. package/dist/planner.js +2351 -0
  52. package/dist/planner.js.map +1 -0
  53. package/dist/policy.d.ts +12 -0
  54. package/dist/policy.js +255 -0
  55. package/dist/policy.js.map +1 -0
  56. package/dist/project.d.ts +2 -0
  57. package/dist/project.js +1085 -0
  58. package/dist/project.js.map +1 -0
  59. package/dist/release-readiness.d.ts +25 -0
  60. package/dist/release-readiness.js +426 -0
  61. package/dist/release-readiness.js.map +1 -0
  62. package/dist/report-annotations.d.ts +3 -0
  63. package/dist/report-annotations.js +28 -0
  64. package/dist/report-annotations.js.map +1 -0
  65. package/dist/report-contract.d.ts +2 -0
  66. package/dist/report-contract.js +82 -0
  67. package/dist/report-contract.js.map +1 -0
  68. package/dist/report-html.d.ts +7 -0
  69. package/dist/report-html.js +706 -0
  70. package/dist/report-html.js.map +1 -0
  71. package/dist/report-sarif.d.ts +2 -0
  72. package/dist/report-sarif.js +90 -0
  73. package/dist/report-sarif.js.map +1 -0
  74. package/dist/report.d.ts +14 -0
  75. package/dist/report.js +310 -0
  76. package/dist/report.js.map +1 -0
  77. package/dist/risk.d.ts +19 -0
  78. package/dist/risk.js +1226 -0
  79. package/dist/risk.js.map +1 -0
  80. package/dist/runner.d.ts +8 -0
  81. package/dist/runner.js +113 -0
  82. package/dist/runner.js.map +1 -0
  83. package/dist/scan.d.ts +2 -0
  84. package/dist/scan.js +195 -0
  85. package/dist/scan.js.map +1 -0
  86. package/dist/schema.d.ts +12 -0
  87. package/dist/schema.js +30 -0
  88. package/dist/schema.js.map +1 -0
  89. package/dist/stack-coverage.d.ts +8 -0
  90. package/dist/stack-coverage.js +94 -0
  91. package/dist/stack-coverage.js.map +1 -0
  92. package/dist/types.d.ts +206 -0
  93. package/dist/types.js +2 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/verification.d.ts +11 -0
  96. package/dist/verification.js +108 -0
  97. package/dist/verification.js.map +1 -0
  98. package/docs/ANNOTATIONS.md +34 -0
  99. package/docs/ARCHITECTURE.md +79 -0
  100. package/docs/BASELINES.md +32 -0
  101. package/docs/CASE_STUDIES.md +106 -0
  102. package/docs/CODEOWNERS.md +23 -0
  103. package/docs/DASHBOARD.md +87 -0
  104. package/docs/EVIDENCE.md +55 -0
  105. package/docs/LAUNCH_PLAYBOOK.md +103 -0
  106. package/docs/MONOREPOS.md +74 -0
  107. package/docs/POLICY.md +98 -0
  108. package/docs/PROOF_PACKS.md +57 -0
  109. package/docs/PR_COMMENTS.md +56 -0
  110. package/docs/RELEASE.md +35 -0
  111. package/docs/ROADMAP.md +152 -0
  112. package/docs/RULE_CATALOG.md +90 -0
  113. package/docs/SARIF.md +74 -0
  114. package/docs/SCHEMAS.md +49 -0
  115. package/docs/SECURITY_POSTURE.md +32 -0
  116. package/docs/STACK_COVERAGE.md +20 -0
  117. package/docs/assets/patchdrill-demo.svg +21 -0
  118. package/docs/media/patchdrill-dashboard.png +0 -0
  119. package/docs/media/patchdrill-demo.gif +0 -0
  120. package/examples/case-studies/README.md +20 -0
  121. package/examples/demo/README.md +21 -0
  122. package/examples/demo/patchdrill-demo-summary.md +35 -0
  123. package/examples/demo/patchdrill-demo.html +623 -0
  124. package/examples/demo/patchdrill-demo.json +355 -0
  125. package/examples/demo/patchdrill-demo.md +120 -0
  126. package/examples/demo/patchdrill-demo.sarif +195 -0
  127. package/examples/report.md +128 -0
  128. package/examples/risky-agent-pr/README.md +15 -0
  129. package/examples/risky-agent-pr/patchdrill-demo-summary.md +41 -0
  130. package/examples/risky-agent-pr/patchdrill-demo.html +681 -0
  131. package/examples/risky-agent-pr/patchdrill-demo.json +483 -0
  132. package/examples/risky-agent-pr/patchdrill-demo.md +140 -0
  133. package/examples/risky-agent-pr/patchdrill-demo.sarif +398 -0
  134. package/fixtures/stacks/README.md +4 -0
  135. package/fixtures/stacks/android-gradle/fixture.json +33 -0
  136. package/fixtures/stacks/aspnet-core-service/fixture.json +36 -0
  137. package/fixtures/stacks/bazel-workspace/fixture.json +30 -0
  138. package/fixtures/stacks/buck2-workspace/fixture.json +30 -0
  139. package/fixtures/stacks/cargo-workspace/fixture.json +48 -0
  140. package/fixtures/stacks/django-app/fixture.json +25 -0
  141. package/fixtures/stacks/docker-compose/fixture.json +17 -0
  142. package/fixtures/stacks/dockerfile-service/fixture.json +17 -0
  143. package/fixtures/stacks/dotnet-service/fixture.json +36 -0
  144. package/fixtures/stacks/dotnet-solution-filter/fixture.json +62 -0
  145. package/fixtures/stacks/fastapi-app/fixture.json +29 -0
  146. package/fixtures/stacks/go-workspace/fixture.json +48 -0
  147. package/fixtures/stacks/java-gradle/fixture.json +29 -0
  148. package/fixtures/stacks/java-maven/fixture.json +32 -0
  149. package/fixtures/stacks/kubernetes-helm/fixture.json +25 -0
  150. package/fixtures/stacks/kubernetes-kustomize/fixture.json +21 -0
  151. package/fixtures/stacks/nested-go-workspace/fixture.json +51 -0
  152. package/fixtures/stacks/nextjs-app/fixture.json +34 -0
  153. package/fixtures/stacks/node-turbo-workspace/fixture.json +39 -0
  154. package/fixtures/stacks/pants-python/fixture.json +33 -0
  155. package/fixtures/stacks/php-composer/fixture.json +31 -0
  156. package/fixtures/stacks/python-service/fixture.json +21 -0
  157. package/fixtures/stacks/rails-app/fixture.json +25 -0
  158. package/fixtures/stacks/spring-boot-gradle/fixture.json +29 -0
  159. package/fixtures/stacks/spring-boot-maven/fixture.json +43 -0
  160. package/fixtures/stacks/swift-package/fixture.json +21 -0
  161. package/fixtures/stacks/terraform-module/fixture.json +17 -0
  162. package/fixtures/stacks/uv-python-service/fixture.json +47 -0
  163. package/fixtures/stacks/xcode-app/fixture.json +72 -0
  164. package/package.json +80 -0
  165. package/schemas/patchdrill-doctor.schema.json +171 -0
  166. package/schemas/patchdrill-evidence.schema.json +239 -0
  167. package/schemas/patchdrill-policy.schema.json +170 -0
  168. package/schemas/patchdrill-release-check.schema.json +78 -0
  169. package/schemas/patchdrill-report.schema.json +647 -0
@@ -0,0 +1,706 @@
1
+ import { t } from "./i18n.js";
2
+ import { formatVerificationStatus, verificationExecutions, verificationSummary } from "./verification.js";
3
+ export function renderHtml(report, options = {}) {
4
+ const locale = options.locale ?? "en";
5
+ const tr = (text) => t(locale, text);
6
+ const summary = report.summary;
7
+ const statusLabel = tr(summary.status.toUpperCase());
8
+ const statusTone = htmlStatusTone(summary.status);
9
+ const requiredCommands = report.commandPlan.filter((command) => command.required);
10
+ const optionalCommands = report.commandPlan.filter((command) => !command.required);
11
+ const verification = verificationSummary(report);
12
+ const runTrend = htmlRunTrend(options.history, locale);
13
+ const commandResultsHtml = htmlCommandResults(report, locale);
14
+ const context = [
15
+ report.base ? `Base: ${report.base}` : undefined,
16
+ report.head ? `Head: ${report.head}` : undefined,
17
+ `Generated: ${report.generatedAt}`,
18
+ `Schema: ${report.schemaVersion}`
19
+ ].filter((item) => item !== undefined);
20
+ return `<!doctype html>
21
+ <html lang="en">
22
+ <head>
23
+ <meta charset="utf-8">
24
+ <meta name="viewport" content="width=device-width, initial-scale=1">
25
+ <link rel="icon" href="data:,">
26
+ <title>PatchDrill Dashboard</title>
27
+ <style>
28
+ :root {
29
+ color-scheme: light;
30
+ --bg: #f6f7f9;
31
+ --panel: #ffffff;
32
+ --text: #15181e;
33
+ --muted: #5c6470;
34
+ --border: #d9dee7;
35
+ --code-bg: #f0f3f7;
36
+ --pass: #0b6b43;
37
+ --pass-bg: #e5f5ed;
38
+ --warn: #9a5b00;
39
+ --warn-bg: #fff0d6;
40
+ --fail: #a12828;
41
+ --fail-bg: #fde7e7;
42
+ --info: #285da1;
43
+ --info-bg: #e7f0fb;
44
+ --shadow: 0 1px 2px rgb(16 24 40 / 8%);
45
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
46
+ }
47
+
48
+ * {
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ body {
53
+ margin: 0;
54
+ background: var(--bg);
55
+ color: var(--text);
56
+ line-height: 1.5;
57
+ }
58
+
59
+ main {
60
+ width: min(1180px, calc(100% - 32px));
61
+ margin: 0 auto;
62
+ padding: 32px 0 48px;
63
+ }
64
+
65
+ header {
66
+ display: grid;
67
+ gap: 18px;
68
+ margin-bottom: 22px;
69
+ }
70
+
71
+ h1,
72
+ h2,
73
+ h3,
74
+ p {
75
+ margin: 0;
76
+ }
77
+
78
+ h1 {
79
+ font-size: 32px;
80
+ line-height: 1.15;
81
+ letter-spacing: 0;
82
+ }
83
+
84
+ h2 {
85
+ font-size: 19px;
86
+ line-height: 1.25;
87
+ letter-spacing: 0;
88
+ }
89
+
90
+ h3 {
91
+ font-size: 15px;
92
+ line-height: 1.3;
93
+ letter-spacing: 0;
94
+ }
95
+
96
+ .eyebrow {
97
+ color: var(--muted);
98
+ font-size: 13px;
99
+ font-weight: 700;
100
+ letter-spacing: .08em;
101
+ text-transform: uppercase;
102
+ }
103
+
104
+ .header-row,
105
+ .section-heading,
106
+ .finding-head,
107
+ summary {
108
+ align-items: center;
109
+ display: flex;
110
+ gap: 12px;
111
+ justify-content: space-between;
112
+ }
113
+
114
+ .context {
115
+ color: var(--muted);
116
+ display: flex;
117
+ flex-wrap: wrap;
118
+ font-size: 13px;
119
+ gap: 8px 16px;
120
+ }
121
+
122
+ .grid {
123
+ display: grid;
124
+ gap: 12px;
125
+ }
126
+
127
+ .metrics {
128
+ grid-template-columns: repeat(5, minmax(0, 1fr));
129
+ }
130
+
131
+ .two-column {
132
+ grid-template-columns: repeat(2, minmax(0, 1fr));
133
+ }
134
+
135
+ .metric,
136
+ .finding,
137
+ .table-wrap,
138
+ details {
139
+ background: var(--panel);
140
+ border: 1px solid var(--border);
141
+ border-radius: 8px;
142
+ box-shadow: var(--shadow);
143
+ }
144
+
145
+ section {
146
+ display: grid;
147
+ gap: 14px;
148
+ margin-top: 24px;
149
+ padding: 0;
150
+ }
151
+
152
+ .metric {
153
+ min-width: 0;
154
+ padding: 14px;
155
+ }
156
+
157
+ .metric-label {
158
+ color: var(--muted);
159
+ font-size: 12px;
160
+ font-weight: 700;
161
+ text-transform: uppercase;
162
+ }
163
+
164
+ .metric-value {
165
+ font-size: 24px;
166
+ font-weight: 760;
167
+ line-height: 1.2;
168
+ margin-top: 6px;
169
+ overflow-wrap: anywhere;
170
+ }
171
+
172
+ .metric-detail {
173
+ color: var(--muted);
174
+ font-size: 12px;
175
+ margin-top: 4px;
176
+ overflow-wrap: anywhere;
177
+ }
178
+
179
+ .bar {
180
+ background: #e7ebf1;
181
+ border-radius: 999px;
182
+ height: 8px;
183
+ margin-top: 10px;
184
+ overflow: hidden;
185
+ }
186
+
187
+ .bar span {
188
+ display: block;
189
+ height: 100%;
190
+ }
191
+
192
+ .bar .pass {
193
+ background: var(--pass);
194
+ }
195
+
196
+ .bar .warn {
197
+ background: var(--warn);
198
+ }
199
+
200
+ .bar .fail {
201
+ background: var(--fail);
202
+ }
203
+
204
+ .trend-table td,
205
+ .trend-table th {
206
+ white-space: nowrap;
207
+ }
208
+
209
+ .trend-risk {
210
+ align-items: center;
211
+ display: grid;
212
+ gap: 8px;
213
+ grid-template-columns: 54px minmax(120px, 1fr);
214
+ }
215
+
216
+ .pill {
217
+ border-radius: 999px;
218
+ display: inline-flex;
219
+ font-size: 12px;
220
+ font-weight: 760;
221
+ gap: 6px;
222
+ line-height: 1;
223
+ padding: 7px 9px;
224
+ text-transform: uppercase;
225
+ white-space: nowrap;
226
+ }
227
+
228
+ .pass {
229
+ background: var(--pass-bg);
230
+ color: var(--pass);
231
+ }
232
+
233
+ .warn {
234
+ background: var(--warn-bg);
235
+ color: var(--warn);
236
+ }
237
+
238
+ .fail,
239
+ .critical,
240
+ .high {
241
+ background: var(--fail-bg);
242
+ color: var(--fail);
243
+ }
244
+
245
+ .medium {
246
+ background: var(--warn-bg);
247
+ color: var(--warn);
248
+ }
249
+
250
+ .low,
251
+ .info {
252
+ background: var(--info-bg);
253
+ color: var(--info);
254
+ }
255
+
256
+ .muted,
257
+ .empty {
258
+ color: var(--muted);
259
+ }
260
+
261
+ .table-wrap {
262
+ overflow-x: auto;
263
+ border-radius: 8px;
264
+ box-shadow: var(--shadow);
265
+ }
266
+
267
+ table {
268
+ border-collapse: collapse;
269
+ font-size: 13px;
270
+ min-width: 720px;
271
+ width: 100%;
272
+ }
273
+
274
+ th,
275
+ td {
276
+ border-bottom: 1px solid var(--border);
277
+ padding: 10px;
278
+ text-align: left;
279
+ vertical-align: top;
280
+ }
281
+
282
+ th {
283
+ color: var(--muted);
284
+ font-size: 12px;
285
+ text-transform: uppercase;
286
+ }
287
+
288
+ tr:last-child td {
289
+ border-bottom: 0;
290
+ }
291
+
292
+ code,
293
+ pre {
294
+ background: var(--code-bg);
295
+ border-radius: 6px;
296
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
297
+ font-size: 12px;
298
+ }
299
+
300
+ code {
301
+ padding: 2px 5px;
302
+ }
303
+
304
+ pre {
305
+ margin: 10px 0 0;
306
+ max-height: 360px;
307
+ overflow: auto;
308
+ padding: 12px;
309
+ white-space: pre-wrap;
310
+ word-break: break-word;
311
+ }
312
+
313
+ .finding-list {
314
+ display: grid;
315
+ gap: 10px;
316
+ }
317
+
318
+ .finding {
319
+ display: grid;
320
+ gap: 8px;
321
+ padding: 14px;
322
+ }
323
+
324
+ .finding-title {
325
+ font-weight: 760;
326
+ overflow-wrap: anywhere;
327
+ }
328
+
329
+ .detail-list {
330
+ display: grid;
331
+ gap: 8px;
332
+ grid-template-columns: repeat(2, minmax(0, 1fr));
333
+ }
334
+
335
+ .detail-item {
336
+ border: 1px solid var(--border);
337
+ border-radius: 8px;
338
+ padding: 12px;
339
+ }
340
+
341
+ .detail-label {
342
+ color: var(--muted);
343
+ font-size: 12px;
344
+ font-weight: 700;
345
+ text-transform: uppercase;
346
+ }
347
+
348
+ .detail-value {
349
+ margin-top: 4px;
350
+ overflow-wrap: anywhere;
351
+ }
352
+
353
+ details {
354
+ padding: 0;
355
+ }
356
+
357
+ summary {
358
+ cursor: pointer;
359
+ font-weight: 700;
360
+ list-style: none;
361
+ padding: 14px;
362
+ }
363
+
364
+ summary::-webkit-details-marker {
365
+ display: none;
366
+ }
367
+
368
+ .command-body {
369
+ border-top: 1px solid var(--border);
370
+ padding: 0 14px 14px;
371
+ }
372
+
373
+ @media (max-width: 900px) {
374
+ .metrics,
375
+ .two-column,
376
+ .detail-list {
377
+ grid-template-columns: 1fr;
378
+ }
379
+
380
+ .header-row,
381
+ .section-heading,
382
+ .finding-head,
383
+ summary {
384
+ align-items: flex-start;
385
+ flex-direction: column;
386
+ }
387
+
388
+ main {
389
+ width: min(100% - 20px, 1180px);
390
+ padding-top: 20px;
391
+ }
392
+ }
393
+ </style>
394
+ </head>
395
+ <body>
396
+ <main>
397
+ <header>
398
+ <div class="header-row">
399
+ <div>
400
+ <p class="eyebrow">PatchDrill</p>
401
+ <h1>${escapeHtml(tr("Verification Dashboard"))}</h1>
402
+ </div>
403
+ <span class="pill ${statusTone}">${escapeHtml(statusLabel)}</span>
404
+ </div>
405
+ <div class="context">${context.map((item) => `<span>${escapeHtml(item)}</span>`).join("")}</div>
406
+ </header>
407
+
408
+ <div class="grid metrics">
409
+ ${htmlMetric(tr("Risk score"), `${summary.riskScore}/100`, tr("Higher means more review proof is needed."), htmlScoreBar(summary.riskScore, statusTone))}
410
+ ${htmlMetric(tr("Confidence"), `${summary.confidenceScore}/100`, tr("Higher means stronger verification evidence."), htmlScoreBar(summary.confidenceScore, "pass"))}
411
+ ${htmlMetric(tr("Changed files"), summary.changedFileCount, `+${summary.additions} / -${summary.deletions}`)}
412
+ ${htmlMetric(tr("Required checks"), summary.requiredCommandCount, `${verification.passed} ${tr("passed")}, ${verification.failed} ${tr("failed")}, ${verification.missingRequired} ${tr("missing")}`)}
413
+ ${htmlMetric(tr("Added lines"), report.addedLines, tr("Diff lines scanned for risky content."))}
414
+ </div>
415
+
416
+ ${runTrend}
417
+
418
+ <section>
419
+ <div class="section-heading">
420
+ <h2>${escapeHtml(tr("Findings"))}</h2>
421
+ <span class="pill ${statusTone}">${escapeHtml(report.findings.length)} ${escapeHtml(tr("total"))}</span>
422
+ </div>
423
+ ${htmlFindings(report, locale)}
424
+ </section>
425
+
426
+ <section>
427
+ <div class="section-heading">
428
+ <h2>${escapeHtml(tr("Verification Plan"))}</h2>
429
+ <span class="pill ${requiredCommands.length > 0 ? "info" : "pass"}">${escapeHtml(requiredCommands.length)} ${escapeHtml(tr("required"))}, ${escapeHtml(optionalCommands.length)} ${escapeHtml(tr("optional"))}</span>
430
+ </div>
431
+ ${htmlVerificationPlan(report, locale)}
432
+ </section>
433
+
434
+ ${commandResultsHtml}
435
+
436
+ <section>
437
+ <h2>${escapeHtml(tr("Changed Files"))}</h2>
438
+ ${htmlChangedFiles(report, locale)}
439
+ </section>
440
+
441
+ <div class="grid two-column">
442
+ <section>
443
+ <h2>${escapeHtml(tr("Project Signals"))}</h2>
444
+ ${htmlProjectSignals(report, locale)}
445
+ </section>
446
+ <section>
447
+ <h2>${escapeHtml(tr("Review Context"))}</h2>
448
+ ${htmlReviewContext(report, locale)}
449
+ </section>
450
+ </div>
451
+
452
+ <section>
453
+ <h2>${escapeHtml(tr("Dependency Changes"))}</h2>
454
+ ${htmlDependencyChanges(report, locale)}
455
+ </section>
456
+
457
+ <section>
458
+ <h2>${escapeHtml(tr("Package Script Changes"))}</h2>
459
+ ${htmlPackageScriptChanges(report, locale)}
460
+ </section>
461
+
462
+ <section>
463
+ <h2>${escapeHtml(tr("Reviewer Notes"))}</h2>
464
+ <p class="muted">${escapeHtml(tr("Treat this dashboard as triage evidence, not a replacement for review. High-impact areas still need human sign-off even when automated commands pass."))}</p>
465
+ </section>
466
+ </main>
467
+ </body>
468
+ </html>
469
+ `;
470
+ }
471
+ function htmlRunTrend(history, locale) {
472
+ const tr = (text) => t(locale, text);
473
+ if (!history || history.length <= 1)
474
+ return "";
475
+ const previous = history[history.length - 2];
476
+ const latest = history[history.length - 1];
477
+ const riskDelta = previous && latest ? latest.summary.riskScore - previous.summary.riskScore : 0;
478
+ const failedDelta = previous && latest ? latest.summary.failedCommandCount - previous.summary.failedCommandCount : 0;
479
+ const deltaTone = riskDelta > 0 || failedDelta > 0 ? "warn" : riskDelta < 0 || failedDelta < 0 ? "pass" : "info";
480
+ const table = htmlTable([tr("Run"), tr("Status"), tr("Risk"), tr("Confidence"), tr("Changed"), tr("Required"), tr("Failed"), tr("Generated"), tr("Base"), tr("Head")], history.map((run, index) => [
481
+ escapeHtml(index === history.length - 1 ? `${index + 1} ${tr("latest")}` : `${index + 1}`),
482
+ `<span class="pill ${htmlStatusTone(run.summary.status)}">${escapeHtml(tr(run.summary.status.toUpperCase()))}</span>`,
483
+ `<div class="trend-risk"><span>${escapeHtml(`${run.summary.riskScore}/100`)}</span>${htmlScoreBar(run.summary.riskScore, htmlStatusTone(run.summary.status))}</div>`,
484
+ escapeHtml(`${run.summary.confidenceScore}/100`),
485
+ escapeHtml(`${run.summary.changedFileCount} (+${run.summary.additions}/-${run.summary.deletions})`),
486
+ escapeHtml(run.summary.requiredCommandCount),
487
+ escapeHtml(run.summary.failedCommandCount),
488
+ escapeHtml(run.generatedAt),
489
+ escapeHtml(run.base ?? ""),
490
+ escapeHtml(run.head ?? "")
491
+ ]), tr("No historical runs provided.")).replace('class="table-wrap"', 'class="table-wrap trend-table"');
492
+ return `<section>
493
+ <div class="section-heading">
494
+ <h2>${escapeHtml(tr("Run Trend"))}</h2>
495
+ <span class="pill ${deltaTone}">${escapeHtml(tr("risk"))} ${formatDelta(riskDelta)}, ${escapeHtml(tr("failed checks"))} ${formatDelta(failedDelta)}</span>
496
+ </div>
497
+ ${table}
498
+ </section>`;
499
+ }
500
+ function formatDelta(value) {
501
+ return value > 0 ? `+${value}` : `${value}`;
502
+ }
503
+ function htmlMetric(label, value, detail, extra = "") {
504
+ const extraLine = extra ? `\n ${extra}` : "";
505
+ return `<div class="metric">
506
+ <div class="metric-label">${escapeHtml(label)}</div>
507
+ <div class="metric-value">${escapeHtml(value)}</div>
508
+ <div class="metric-detail">${escapeHtml(detail)}</div>${extraLine}
509
+ </div>`;
510
+ }
511
+ function htmlScoreBar(score, tone) {
512
+ return `<div class="bar" aria-hidden="true"><span class="${escapeHtml(tone)}" style="width: ${clampScore(score)}%;"></span></div>`;
513
+ }
514
+ function htmlFindings(report, locale) {
515
+ const tr = (text) => t(locale, text);
516
+ if (report.findings.length === 0) {
517
+ return `<p class="empty">${escapeHtml(tr("No risk findings."))}</p>`;
518
+ }
519
+ return `<div class="finding-list">
520
+ ${report.findings
521
+ .map((finding) => {
522
+ const location = finding.file ? `${finding.file}${finding.line ? `:${finding.line}` : ""}` : tr("Global");
523
+ const tags = finding.tags && finding.tags.length > 0 ? `${tr("Tags")}: ${finding.tags.join(", ")}` : undefined;
524
+ const remediation = finding.remediation ? `\n <p class="muted">${escapeHtml(tr("Remediation"))}: ${escapeHtml(tr(finding.remediation))}</p>` : "";
525
+ const rule = finding.ruleId ? `\n <p class="muted">${escapeHtml(tr("Rule"))}: <code>${escapeHtml(finding.ruleId)}</code></p>` : "";
526
+ const tagLine = tags ? `\n <p class="muted">${escapeHtml(tags)}</p>` : "";
527
+ return `<article class="finding">
528
+ <div class="finding-head">
529
+ <div>
530
+ <div class="finding-title">${escapeHtml(tr(finding.title))}</div>
531
+ <div class="metric-detail">${escapeHtml(location)}</div>
532
+ </div>
533
+ <span class="pill ${escapeHtml(finding.severity)}">${escapeHtml(tr(finding.severity))}</span>
534
+ </div>
535
+ <p>${escapeHtml(tr(finding.detail))}</p>${remediation}${rule}${tagLine}
536
+ </article>`;
537
+ })
538
+ .join("")}
539
+ </div>`;
540
+ }
541
+ function htmlVerificationPlan(report, locale) {
542
+ const tr = (text) => t(locale, text);
543
+ return htmlTable([tr("Required"), tr("Package"), tr("Command"), tr("Result"), tr("Reason")], verificationExecutions(report).map((execution) => [
544
+ `<span class="pill ${execution.required ? "warn" : "info"}">${execution.required ? tr("yes") : tr("no")}</span>`,
545
+ escapeHtml(execution.packageName ?? execution.packagePath ?? ""),
546
+ `<code>${escapeHtml(execution.command)}</code>`,
547
+ htmlVerificationStatus(execution, locale),
548
+ escapeHtml(tr(execution.reason))
549
+ ]), tr("No verification commands were inferred. This is common for docs-only patches or repos without recognized manifests."));
550
+ }
551
+ function htmlCommandResults(report, locale) {
552
+ if (report.commandResults.length === 0)
553
+ return "";
554
+ const tr = (text) => t(locale, text);
555
+ return `<section>
556
+ <div class="section-heading">
557
+ <h2>${escapeHtml(tr("Command Results"))}</h2>
558
+ <span class="pill ${report.summary.failedCommandCount > 0 ? "fail" : "pass"}">${escapeHtml(report.summary.failedCommandCount)} ${escapeHtml(tr("failed"))}</span>
559
+ </div>
560
+ <div class="grid">
561
+ ${report.commandResults
562
+ .map((result) => {
563
+ const tone = result.exitCode === 0 ? "pass" : "fail";
564
+ const stdout = result.stdout.trim() ? `\n <h3>stdout</h3><pre>${escapeHtml(result.stdout.trim())}</pre>` : "";
565
+ const stderr = result.stderr.trim() ? `\n <h3>stderr</h3><pre>${escapeHtml(result.stderr.trim())}</pre>` : "";
566
+ return `<details>
567
+ <summary>
568
+ <span><code>${escapeHtml(result.command)}</code></span>
569
+ <span class="pill ${tone}">${escapeHtml(tr("exit"))} ${escapeHtml(result.exitCode)}</span>
570
+ </summary>
571
+ <div class="command-body">
572
+ <p class="muted">${escapeHtml(tr("Duration"))}: ${escapeHtml(result.durationMs)}ms${result.timedOut ? ` | ${escapeHtml(tr("Timed out: yes"))}` : ""}</p>${stdout}${stderr}
573
+ </div>
574
+ </details>`;
575
+ })
576
+ .join("")}
577
+ </div>
578
+ </section>`;
579
+ }
580
+ function htmlChangedFiles(report, locale) {
581
+ const tr = (text) => t(locale, text);
582
+ return htmlTable([tr("File"), tr("Status"), tr("+/-"), tr("Owners")], report.changedFiles.map((file) => {
583
+ const path = file.previousPath ? `${escapeHtml(file.previousPath)} <span class="muted">-&gt;</span> ${escapeHtml(file.path)}` : escapeHtml(file.path);
584
+ const owners = file.owners && file.owners.length > 0 ? file.owners.join(", ") : "";
585
+ return [
586
+ path,
587
+ escapeHtml(tr(file.status)),
588
+ escapeHtml(`+${file.additions} / -${file.deletions}${file.binary ? ` (${tr("binary")})` : ""}`),
589
+ escapeHtml(owners)
590
+ ];
591
+ }), tr("No changed files detected."));
592
+ }
593
+ function htmlVerificationStatus(execution, locale) {
594
+ return `<span class="pill ${htmlVerificationTone(execution.status)}">${escapeHtml(t(locale, formatVerificationStatus(execution)))}</span>`;
595
+ }
596
+ function htmlVerificationTone(status) {
597
+ if (status === "passed")
598
+ return "pass";
599
+ if (status === "failed" || status === "timed-out")
600
+ return "fail";
601
+ if (status === "not-run")
602
+ return "warn";
603
+ return "info";
604
+ }
605
+ function htmlProjectSignals(report, locale) {
606
+ const tr = (text) => t(locale, text);
607
+ return htmlTable([tr("Ecosystem"), tr("Framework"), tr("Entrypoint"), tr("Manifest"), tr("Package manager"), tr("Task runner")], report.projectSignals.map((signal) => [
608
+ escapeHtml(signal.ecosystem),
609
+ escapeHtml(signal.framework ?? ""),
610
+ escapeHtml(signal.entrypoint ?? ""),
611
+ escapeHtml(signal.manifestPath),
612
+ escapeHtml(signal.packageManager ?? ""),
613
+ escapeHtml(signal.taskRunner ?? "")
614
+ ]), tr("No project manifests were recognized."));
615
+ }
616
+ function htmlReviewContext(report, locale) {
617
+ const tr = (text) => t(locale, text);
618
+ const details = [];
619
+ if (report.policy) {
620
+ details.push([tr("Policy"), `${report.policy.path} (${report.policy.ruleCount} ${tr("rules")})`]);
621
+ details.push([tr("Policy commands"), `${report.policy.requiredCommandCount} ${tr("required")}, ${report.policy.optionalCommandCount} ${tr("optional")}`]);
622
+ if (report.policy.failOn)
623
+ details.push([tr("Fail-on"), report.policy.failOn]);
624
+ if (report.policy.maxRisk !== undefined)
625
+ details.push([tr("Max risk"), `${report.policy.maxRisk}`]);
626
+ }
627
+ if (report.codeOwners) {
628
+ details.push([tr("Code owners"), `${report.codeOwners.path} (${report.codeOwners.ruleCount} ${tr("rules")})`]);
629
+ }
630
+ if (report.baseline) {
631
+ details.push([tr("Baseline"), report.baseline.path]);
632
+ details.push([tr("Risk delta"), formatDelta(report.baseline.riskDelta)]);
633
+ details.push([tr("Findings delta"), `${report.baseline.newFindingCount} ${tr("new")}, ${report.baseline.resolvedFindingCount} ${tr("resolved")}, ${report.baseline.unchangedFindingCount} ${tr("unchanged")}`]);
634
+ }
635
+ if (report.affectedPackages.length > 0) {
636
+ details.push([tr("Affected packages"), report.affectedPackages.map((workspacePackage) => workspacePackage.name).join(", ")]);
637
+ }
638
+ if (details.length === 0) {
639
+ return `<p class="empty">${escapeHtml(tr("No policy, owner, baseline, or workspace package context was detected."))}</p>`;
640
+ }
641
+ return `<div class="detail-list">
642
+ ${details
643
+ .map(([label, value]) => `<div class="detail-item">
644
+ <div class="detail-label">${escapeHtml(label)}</div>
645
+ <div class="detail-value">${escapeHtml(value)}</div>
646
+ </div>`)
647
+ .join("")}
648
+ </div>`;
649
+ }
650
+ function htmlDependencyChanges(report, locale) {
651
+ const tr = (text) => t(locale, text);
652
+ return htmlTable([tr("File"), tr("Type"), tr("Package"), tr("Path"), tr("Change"), tr("Before"), tr("After")], report.dependencyChanges.map((change) => [
653
+ escapeHtml(change.file),
654
+ escapeHtml(change.dependencyType),
655
+ escapeHtml(change.packageName),
656
+ escapeHtml(change.packagePath ?? ""),
657
+ escapeHtml(change.changeType),
658
+ escapeHtml(change.before ?? ""),
659
+ escapeHtml(change.after ?? "")
660
+ ]), tr("No dependency changes detected."));
661
+ }
662
+ function htmlPackageScriptChanges(report, locale) {
663
+ const tr = (text) => t(locale, text);
664
+ return htmlTable([tr("File"), tr("Script"), tr("Change"), tr("Before"), tr("After")], report.packageScriptChanges.map((change) => [
665
+ escapeHtml(change.file),
666
+ `<code>${escapeHtml(change.scriptName)}</code>`,
667
+ escapeHtml(change.changeType),
668
+ `<code>${escapeHtml(change.before ?? "")}</code>`,
669
+ `<code>${escapeHtml(change.after ?? "")}</code>`
670
+ ]), tr("No package script changes detected."));
671
+ }
672
+ function htmlTable(headers, rows, emptyMessage) {
673
+ if (rows.length === 0)
674
+ return `<p class="empty">${escapeHtml(emptyMessage)}</p>`;
675
+ return `<div class="table-wrap">
676
+ <table>
677
+ <thead>
678
+ <tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("")}</tr>
679
+ </thead>
680
+ <tbody>
681
+ ${rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("")}
682
+ </tbody>
683
+ </table>
684
+ </div>`;
685
+ }
686
+ function htmlStatusTone(status) {
687
+ if (status === "pass")
688
+ return "pass";
689
+ if (status === "warn")
690
+ return "warn";
691
+ return "fail";
692
+ }
693
+ function clampScore(score) {
694
+ if (!Number.isFinite(score))
695
+ return 0;
696
+ return Math.min(100, Math.max(0, Math.round(score)));
697
+ }
698
+ function escapeHtml(value) {
699
+ return String(value)
700
+ .replaceAll("&", "&amp;")
701
+ .replaceAll("<", "&lt;")
702
+ .replaceAll(">", "&gt;")
703
+ .replaceAll('"', "&quot;")
704
+ .replaceAll("'", "&#39;");
705
+ }
706
+ //# sourceMappingURL=report-html.js.map