@prism-d1/cli 1.0.99 → 1.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 (162) hide show
  1. package/dist/assets/github-workflows/prism-agent-eval.yml +2 -1
  2. package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +11 -1
  3. package/dist/assets/gitlab-workflows/prism-eval-gate.yml +8 -0
  4. package/dist/assets/infra/lib/constructs/otel-collector-construct.ts +312 -0
  5. package/dist/assets/infra/lib/dashboard-stack.ts +74 -45
  6. package/dist/assets/infra/lib/lambda/otel-receiver.ts +324 -12
  7. package/dist/assets/infra/lib/lambda/pre-token-generation.ts +79 -0
  8. package/dist/assets/infra/lib/lambda/pricing/litellm-snapshot.json +3769 -0
  9. package/dist/assets/infra/lib/lambda/pricing/price-source.ts +56 -0
  10. package/dist/assets/infra/lib/lambda/pricing/price-table.ts +240 -0
  11. package/dist/assets/infra/lib/lambda/pricing/prices-key.ts +6 -0
  12. package/dist/assets/infra/lib/lambda/pricing/reprice.ts +108 -0
  13. package/dist/assets/infra/lib/lambda/pricing-refresh.ts +352 -0
  14. package/dist/assets/infra/lib/lambda/velocity-widget.ts +397 -1
  15. package/dist/assets/infra/lib/metrics-pipeline-stack.ts +13 -0
  16. package/dist/assets/infra/package.json +5 -2
  17. package/dist/assets/metric-hooks/prepare-commit-msg +3 -1
  18. package/dist/src/codewatch/config.d.ts +17 -0
  19. package/dist/src/codewatch/config.d.ts.map +1 -0
  20. package/dist/src/codewatch/config.js +40 -0
  21. package/dist/src/codewatch/config.js.map +1 -0
  22. package/dist/src/codewatch/gitleaks.d.ts +49 -0
  23. package/dist/src/codewatch/gitleaks.d.ts.map +1 -0
  24. package/dist/src/codewatch/gitleaks.js +160 -0
  25. package/dist/src/codewatch/gitleaks.js.map +1 -0
  26. package/dist/src/codewatch/globalHooks.d.ts +52 -0
  27. package/dist/src/codewatch/globalHooks.d.ts.map +1 -0
  28. package/dist/src/codewatch/globalHooks.js +235 -0
  29. package/dist/src/codewatch/globalHooks.js.map +1 -0
  30. package/dist/src/codewatch/hooks.d.ts +38 -0
  31. package/dist/src/codewatch/hooks.d.ts.map +1 -0
  32. package/dist/src/codewatch/hooks.js +176 -0
  33. package/dist/src/codewatch/hooks.js.map +1 -0
  34. package/dist/src/codewatch/prepush.d.ts +37 -0
  35. package/dist/src/codewatch/prepush.d.ts.map +1 -0
  36. package/dist/src/codewatch/prepush.js +231 -0
  37. package/dist/src/codewatch/prepush.js.map +1 -0
  38. package/dist/src/codewatch/results.d.ts +90 -0
  39. package/dist/src/codewatch/results.d.ts.map +1 -0
  40. package/dist/src/codewatch/results.js +121 -0
  41. package/dist/src/codewatch/results.js.map +1 -0
  42. package/dist/src/codewatch/reviewers/claude.d.ts +9 -0
  43. package/dist/src/codewatch/reviewers/claude.d.ts.map +1 -0
  44. package/dist/src/codewatch/reviewers/claude.js +48 -0
  45. package/dist/src/codewatch/reviewers/claude.js.map +1 -0
  46. package/dist/src/codewatch/reviewers/index.d.ts +39 -0
  47. package/dist/src/codewatch/reviewers/index.d.ts.map +1 -0
  48. package/dist/src/codewatch/reviewers/index.js +97 -0
  49. package/dist/src/codewatch/reviewers/index.js.map +1 -0
  50. package/dist/src/codewatch/reviewers/kiro.d.ts +14 -0
  51. package/dist/src/codewatch/reviewers/kiro.d.ts.map +1 -0
  52. package/dist/src/codewatch/reviewers/kiro.js +32 -0
  53. package/dist/src/codewatch/reviewers/kiro.js.map +1 -0
  54. package/dist/src/codewatch/reviewers/types.d.ts +56 -0
  55. package/dist/src/codewatch/reviewers/types.d.ts.map +1 -0
  56. package/dist/src/codewatch/reviewers/types.js +91 -0
  57. package/dist/src/codewatch/reviewers/types.js.map +1 -0
  58. package/dist/src/codewatch/rules.d.ts +12 -0
  59. package/dist/src/codewatch/rules.d.ts.map +1 -0
  60. package/dist/src/codewatch/rules.js +27 -0
  61. package/dist/src/codewatch/rules.js.map +1 -0
  62. package/dist/src/commands/_top/dashboard.d.ts +17 -0
  63. package/dist/src/commands/_top/dashboard.d.ts.map +1 -0
  64. package/dist/src/commands/_top/dashboard.js +76 -0
  65. package/dist/src/commands/_top/dashboard.js.map +1 -0
  66. package/dist/src/commands/bedrock-protection/scan-account.d.ts.map +1 -1
  67. package/dist/src/commands/bedrock-protection/scan-account.js +2 -1
  68. package/dist/src/commands/bedrock-protection/scan-account.js.map +1 -1
  69. package/dist/src/commands/bedrock-protection/scan-org.d.ts.map +1 -1
  70. package/dist/src/commands/bedrock-protection/scan-org.js +16 -4
  71. package/dist/src/commands/bedrock-protection/scan-org.js.map +1 -1
  72. package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
  73. package/dist/src/commands/bootstrapper/install-eval-harness.js +15 -48
  74. package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
  75. package/dist/src/commands/bootstrapper/install-git-hooks.d.ts +13 -0
  76. package/dist/src/commands/bootstrapper/install-git-hooks.d.ts.map +1 -1
  77. package/dist/src/commands/bootstrapper/install-git-hooks.js +28 -147
  78. package/dist/src/commands/bootstrapper/install-git-hooks.js.map +1 -1
  79. package/dist/src/commands/bootstrapper/setup-otel-sync.d.ts.map +1 -1
  80. package/dist/src/commands/bootstrapper/setup-otel-sync.js +5 -39
  81. package/dist/src/commands/bootstrapper/setup-otel-sync.js.map +1 -1
  82. package/dist/src/commands/codewatch/pre-commit.d.ts +7 -0
  83. package/dist/src/commands/codewatch/pre-commit.d.ts.map +1 -0
  84. package/dist/src/commands/codewatch/pre-commit.js +30 -0
  85. package/dist/src/commands/codewatch/pre-commit.js.map +1 -0
  86. package/dist/src/commands/codewatch/pre-push.d.ts +16 -0
  87. package/dist/src/commands/codewatch/pre-push.d.ts.map +1 -0
  88. package/dist/src/commands/codewatch/pre-push.js +32 -0
  89. package/dist/src/commands/codewatch/pre-push.js.map +1 -0
  90. package/dist/src/commands/codewatch/report.d.ts +22 -0
  91. package/dist/src/commands/codewatch/report.d.ts.map +1 -0
  92. package/dist/src/commands/codewatch/report.js +64 -0
  93. package/dist/src/commands/codewatch/report.js.map +1 -0
  94. package/dist/src/commands/enable/codewatch.d.ts +15 -0
  95. package/dist/src/commands/enable/codewatch.d.ts.map +1 -0
  96. package/dist/src/commands/enable/codewatch.js +186 -0
  97. package/dist/src/commands/enable/codewatch.js.map +1 -0
  98. package/dist/src/commands/git/claude-session-context.d.ts.map +1 -1
  99. package/dist/src/commands/git/claude-session-context.js +2 -1
  100. package/dist/src/commands/git/claude-session-context.js.map +1 -1
  101. package/dist/src/commands/git/commit-trailers.js +1 -1
  102. package/dist/src/commands/git/commit-trailers.js.map +1 -1
  103. package/dist/src/commands/workshop/verify-setup.d.ts.map +1 -1
  104. package/dist/src/commands/workshop/verify-setup.js +5 -3
  105. package/dist/src/commands/workshop/verify-setup.js.map +1 -1
  106. package/dist/src/dashboard/codewatch.d.ts +106 -0
  107. package/dist/src/dashboard/codewatch.d.ts.map +1 -0
  108. package/dist/src/dashboard/codewatch.js +119 -0
  109. package/dist/src/dashboard/codewatch.js.map +1 -0
  110. package/dist/src/dashboard/commits.d.ts +96 -0
  111. package/dist/src/dashboard/commits.d.ts.map +1 -0
  112. package/dist/src/dashboard/commits.js +207 -0
  113. package/dist/src/dashboard/commits.js.map +1 -0
  114. package/dist/src/dashboard/page.d.ts +10 -0
  115. package/dist/src/dashboard/page.d.ts.map +1 -0
  116. package/dist/src/dashboard/page.js +315 -0
  117. package/dist/src/dashboard/page.js.map +1 -0
  118. package/dist/src/dashboard/payload.d.ts +111 -0
  119. package/dist/src/dashboard/payload.d.ts.map +1 -0
  120. package/dist/src/dashboard/payload.js +93 -0
  121. package/dist/src/dashboard/payload.js.map +1 -0
  122. package/dist/src/dashboard/report.d.ts +99 -0
  123. package/dist/src/dashboard/report.d.ts.map +1 -0
  124. package/dist/src/dashboard/report.js +300 -0
  125. package/dist/src/dashboard/report.js.map +1 -0
  126. package/dist/src/dashboard/repos.d.ts +58 -0
  127. package/dist/src/dashboard/repos.d.ts.map +1 -0
  128. package/dist/src/dashboard/repos.js +111 -0
  129. package/dist/src/dashboard/repos.js.map +1 -0
  130. package/dist/src/dashboard/server.d.ts +52 -0
  131. package/dist/src/dashboard/server.d.ts.map +1 -0
  132. package/dist/src/dashboard/server.js +163 -0
  133. package/dist/src/dashboard/server.js.map +1 -0
  134. package/dist/src/dashboard/setup.d.ts +52 -0
  135. package/dist/src/dashboard/setup.d.ts.map +1 -0
  136. package/dist/src/dashboard/setup.js +129 -0
  137. package/dist/src/dashboard/setup.js.map +1 -0
  138. package/dist/src/dashboard/usage.d.ts +43 -0
  139. package/dist/src/dashboard/usage.d.ts.map +1 -0
  140. package/dist/src/dashboard/usage.js +88 -0
  141. package/dist/src/dashboard/usage.js.map +1 -0
  142. package/dist/src/index.d.ts +1 -0
  143. package/dist/src/index.d.ts.map +1 -1
  144. package/dist/src/index.js +40 -1
  145. package/dist/src/index.js.map +1 -1
  146. package/dist/src/utils/audit-bedrock.d.ts +1 -0
  147. package/dist/src/utils/audit-bedrock.d.ts.map +1 -1
  148. package/dist/src/utils/audit-bedrock.js +133 -0
  149. package/dist/src/utils/audit-bedrock.js.map +1 -1
  150. package/dist/src/utils/audit-org.d.ts +1 -0
  151. package/dist/src/utils/audit-org.d.ts.map +1 -1
  152. package/dist/src/utils/audit-org.js +155 -0
  153. package/dist/src/utils/audit-org.js.map +1 -1
  154. package/dist/src/utils/otel-sync-schedule.d.ts +22 -0
  155. package/dist/src/utils/otel-sync-schedule.d.ts.map +1 -0
  156. package/dist/src/utils/otel-sync-schedule.js +68 -0
  157. package/dist/src/utils/otel-sync-schedule.js.map +1 -0
  158. package/dist/src/utils/tool-check.d.ts +14 -0
  159. package/dist/src/utils/tool-check.d.ts.map +1 -0
  160. package/dist/src/utils/tool-check.js +42 -0
  161. package/dist/src/utils/tool-check.js.map +1 -0
  162. package/package.json +3 -2
@@ -0,0 +1,315 @@
1
+ /**
2
+ * The dashboard's single-page app: one self-contained HTML document with
3
+ * inline CSS and JS (nonce-gated by the CSP), no CDN and no network fetch
4
+ * other than its own GET /api/data, so it works offline.
5
+ *
6
+ * Every value from the payload is placed with textContent or a DOM attribute;
7
+ * innerHTML is never used for data.
8
+ */
9
+ const STYLE = `
10
+ :root { color-scheme: light dark; --bg:#f7f7f8; --card:#fff; --text:#1d1d1f; --muted:#6b6b73; --line:#e3e3e8;
11
+ --ok:#1a7f37; --warn:#9a6700; --bad:#cf222e; --high:#cf222e; --medium:#bf8700; --low:#57606a; --accent:#0969da; }
12
+ @media (prefers-color-scheme: dark) { :root { --bg:#0d1117; --card:#161b22; --text:#e6edf3; --muted:#8b949e; --line:#30363d;
13
+ --ok:#3fb950; --warn:#d29922; --bad:#f85149; --high:#f85149; --medium:#d29922; --low:#8b949e; --accent:#58a6ff; } }
14
+ * { box-sizing: border-box; }
15
+ body { margin:0; font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background:var(--bg); color:var(--text); }
16
+ header { display:flex; align-items:center; gap:12px; padding:14px 20px; border-bottom:1px solid var(--line); background:var(--card); }
17
+ header h1 { font-size:17px; margin:0; } header .meta { color:var(--muted); font-size:12px; margin-left:auto; }
18
+ button { font:inherit; padding:4px 10px; border:1px solid var(--line); border-radius:6px; background:var(--bg); color:var(--text); cursor:pointer; }
19
+ main { padding:16px 20px; display:grid; gap:16px; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); }
20
+ section.card { background:var(--card); border:1px solid var(--line); border-radius:10px; padding:14px 16px; min-width:0; }
21
+ section.wide { grid-column: 1 / -1; }
22
+ h2 { font-size:15px; margin:0 0 10px; } h3 { font-size:13px; margin:14px 0 6px; color:var(--muted); text-transform:uppercase; letter-spacing:.04em; }
23
+ .empty { color:var(--muted); padding:8px 0; } code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:12px; background:var(--bg); padding:1px 4px; border-radius:4px; }
24
+ .tablewrap { overflow-x:auto; }
25
+ table { width:100%; border-collapse:collapse; font-size:13px; } th, td { text-align:left; padding:5px 6px; border-bottom:1px solid var(--line); vertical-align:top; overflow-wrap:break-word; }
26
+ th { white-space:nowrap; } td.msg { min-width:280px; } td.count { font-variant-numeric:tabular-nums; font-weight:600; } td code { white-space:normal; overflow-wrap:anywhere; }
27
+ th { color:var(--muted); font-weight:600; } td.num { text-align:right; font-variant-numeric: tabular-nums; }
28
+ .pill { white-space:nowrap; display:inline-block; padding:0 7px; border-radius:10px; font-size:12px; font-weight:600; border:1px solid currentColor; }
29
+ .s-ok { color:var(--ok); } .s-warn, .s-findings, .s-skipped { color:var(--warn); } .s-missing, .s-error { color:var(--bad); }
30
+ .sev-high { color:var(--high); } .sev-medium { color:var(--medium); } .sev-low { color:var(--low); }
31
+ .stats { display:flex; gap:18px; flex-wrap:wrap; margin:6px 0; } .stat b { display:block; font-size:20px; } .stat span { color:var(--muted); font-size:12px; }
32
+ .fix { color:var(--muted); font-size:12px; overflow-wrap:anywhere; }
33
+ .tag { margin-left:6px; font-size:11px; padding:0 6px; border-radius:8px; background:var(--bg); border:1px solid var(--line); color:var(--muted); white-space:nowrap; }
34
+ details.missing-group > summary { cursor:pointer; padding:8px 0; color:var(--muted); font-weight:600; }
35
+ details.repo { border-top:1px solid var(--line); padding:8px 0; } details.repo > summary { cursor:pointer; padding:4px 0; } details.repo .detail { padding-left:14px; }
36
+ .callout { border:1px solid var(--accent); border-left-width:4px; border-radius:6px; padding:10px 12px; margin:6px 0 10px; background:color-mix(in srgb, var(--accent) 8%, var(--card)); }
37
+ .callout.warn { border-color:var(--warn); background:color-mix(in srgb, var(--warn) 10%, var(--card)); } .callout code { white-space:normal; overflow-wrap:anywhere; }
38
+ .crow { display:grid; grid-template-columns: 6.5em minmax(10em,2.2fr) minmax(5em,0.8fr) 10.5em 7.5em minmax(11em,1.6fr); gap:8px; padding:6px 6px; border-bottom:1px solid var(--line); align-items:start; font-size:13px; overflow-wrap:anywhere; }
39
+ .chead { color:var(--muted); font-weight:600; } summary.crow { cursor:pointer; list-style:none; } summary.crow::-webkit-details-marker { display:none; } details.cdetails[open] > summary.crow { background:var(--bg); } details.cdetails > .tablewrap { padding:4px 0 8px 18px; }
40
+ @media (max-width: 760px) { .crow { grid-template-columns: 1fr 1fr; } .chead { display:none; } }
41
+ a.rlink { color:var(--accent); } a.rlink:hover { text-decoration:underline; }
42
+ .banner { border:1px solid var(--warn); border-left-width:4px; border-radius:6px; padding:10px 12px; margin:4px 0 12px; background:color-mix(in srgb, var(--warn) 10%, var(--card)); }
43
+ .banner strong { color:var(--text); display:block; margin-bottom:4px; } .banner .fix code { white-space:normal; overflow-wrap:anywhere; } .note { color:var(--muted); font-size:12px; margin-top:10px; }
44
+ svg.trend rect.high { fill:var(--high); } svg.trend rect.medium { fill:var(--medium); } svg.trend rect.low { fill:var(--low); } svg.trend rect.zero { fill:var(--ok); }
45
+ svg.trend line { stroke:var(--line); }
46
+ .repo-title { font-size:12px; color:var(--muted); margin-bottom:6px; word-break:break-all; }
47
+ `;
48
+ const SCRIPT = `
49
+ 'use strict';
50
+ const SVG = 'http://www.w3.org/2000/svg';
51
+ function el(tag, attrs, ...kids) {
52
+ const n = document.createElement(tag);
53
+ for (const [k, v] of Object.entries(attrs || {})) { if (v !== null && v !== undefined) n.setAttribute(k, String(v)); }
54
+ for (const c of kids.flat()) { if (c === null || c === undefined || c === false) continue; n.append(c instanceof Node ? c : document.createTextNode(String(c))); }
55
+ return n;
56
+ }
57
+ const txt = (v) => (v === null || v === undefined ? '' : String(v));
58
+ const pill = (status, label) => el('span', { class: 'pill s-' + txt(status).replace(/[^a-z-]/g, '') }, label || status);
59
+ const sev = (s) => el('span', { class: 'sev-' + txt(s).replace(/[^a-z]/g, '') }, s);
60
+ const when = (iso) => { const d = new Date(iso); return isNaN(d) ? txt(iso) : d.toLocaleString(); };
61
+ const short = (sha) => (sha ? txt(sha).slice(0, 8) : '');
62
+ function money(n, cur) { try { return new Intl.NumberFormat(undefined, { style: 'currency', currency: cur || 'USD' }).format(n || 0); } catch { return '$' + (n || 0).toFixed(2); } }
63
+ function table(head, rows) {
64
+ return el('div', { class: 'tablewrap' }, el('table', {}, el('thead', {}, el('tr', {}, head.map((h) => el('th', {}, h)))),
65
+ el('tbody', {}, rows.map((r) => el('tr', {}, r.map((c) => (c instanceof Node && c.nodeName === 'TD') ? c : el('td', {}, c)))))));
66
+ }
67
+ const num = (v) => el('td', { class: 'num' }, txt(v));
68
+
69
+ function trendSvg(trend) {
70
+ const w = Math.max(trend.length * 14, 140), h = 48, svg = document.createElementNS(SVG, 'svg');
71
+ svg.setAttribute('class', 'trend'); svg.setAttribute('width', String(w)); svg.setAttribute('height', String(h + 2));
72
+ svg.setAttribute('role', 'img'); svg.setAttribute('aria-label', 'findings per run, oldest first');
73
+ const max = Math.max(1, ...trend.map((r) => r.high + r.medium + r.low));
74
+ const base = document.createElementNS(SVG, 'line');
75
+ base.setAttribute('x1', '0'); base.setAttribute('x2', String(w)); base.setAttribute('y1', String(h)); base.setAttribute('y2', String(h)); svg.append(base);
76
+ trend.forEach((r, i) => {
77
+ let y = h; const parts = [['high', r.high], ['medium', r.medium], ['low', r.low]];
78
+ const total = r.high + r.medium + r.low;
79
+ if (total === 0) parts.push(['zero', 0]);
80
+ for (const [cls, n] of parts) {
81
+ const bh = cls === 'zero' ? 3 : Math.round((n / max) * (h - 4)); if (!bh) continue;
82
+ y -= bh; const rect = document.createElementNS(SVG, 'rect');
83
+ rect.setAttribute('x', String(i * 14 + 2)); rect.setAttribute('y', String(y)); rect.setAttribute('width', '10'); rect.setAttribute('height', String(bh)); rect.setAttribute('class', cls);
84
+ const t = document.createElementNS(SVG, 'title'); t.textContent = when(r.started_at) + ' ' + r.kind + ' ' + r.status + ' (' + r.tool + '): ' + r.high + ' high, ' + r.medium + ' medium, ' + r.low + ' low';
85
+ rect.append(t); svg.append(rect);
86
+ }
87
+ });
88
+ return svg;
89
+ }
90
+
91
+ function scanBanner(ss) {
92
+ if (!ss || !ss.banner) return null;
93
+ return el('div', { class: 'banner', role: 'alert' },
94
+ el('strong', {}, ss.banner),
95
+ el('div', { class: 'fix' }, 'Install: ', el('code', {}, ss.installHint)));
96
+ }
97
+
98
+ function repoName(r) {
99
+ return el('span', { title: r.path }, r.name, r.current ? el('span', { class: 'tag' }, 'this repo') : null);
100
+ }
101
+
102
+ const SCAN_LABEL = { ok: ['ok', 'ok'], 'not-scanned': ['warn', 'not scanned'], 'no-verify': ['warn', 'no-verify'], flagged: ['missing', 'secrets found'], none: ['skipped', '-'] };
103
+
104
+ /** Link to a scan report. Ids come from the server's own listing; encodeURIComponent keeps them literal. */
105
+ const reportLink = (id, label) => el('a', { href: '/report/' + encodeURIComponent(id), class: 'rlink' }, label);
106
+
107
+ function reviewCell(rv) {
108
+ if (rv.state === 'not-reviewed') return el('span', { class: 'fix' }, 'not reviewed');
109
+ const link = rv.reportId ? [' ', reportLink(rv.reportId, 'report')] : [];
110
+ if (rv.state === 'failed') return el('span', { title: txt(rv.reason) }, pill('error', 'review failed'), link);
111
+ const who = el('span', { class: 'fix' }, ' ' + txt(rv.tool || '') + (rv.model ? ' / ' + rv.model : '') + (rv.reviewCount > 1 ? ' · reviewed ' + rv.reviewCount + 'x' : ''));
112
+ if (rv.state === 'clean') return el('span', {}, pill('ok', 'reviewed, clean'), who, link);
113
+ const counts = el('span', {},
114
+ rv.high ? el('b', { class: 'sev-high' }, rv.high + ' high ') : null,
115
+ rv.medium ? el('b', { class: 'sev-medium' }, rv.medium + ' medium ') : null,
116
+ rv.low ? el('b', { class: 'sev-low' }, rv.low + ' low') : null);
117
+ return el('span', {}, counts, who, link);
118
+ }
119
+
120
+ /** The newest reviews, each linking to its report. */
121
+ function scansList(scans) {
122
+ if (!scans || !scans.length) return el('p', { class: 'empty' }, 'No pre-push reviews yet.');
123
+ return table(['When', 'Branch', 'Reviewer', 'Result', 'Findings', ''], scans.map((s) => [
124
+ when(s.started_at), txt(s.branch || '-'), txt(s.tool) + (s.model ? ' / ' + s.model : ''),
125
+ pill(s.status === 'ok' ? 'ok' : s.status === 'findings' ? 'findings' : s.status === 'error' ? 'error' : 'skipped', s.status === 'ok' ? 'clean' : s.status),
126
+ el('span', {}, s.high ? el('b', { class: 'sev-high' }, s.high + ' high ') : null, s.medium ? el('b', { class: 'sev-medium' }, s.medium + ' medium ') : null, s.low ? el('b', { class: 'sev-low' }, s.low + ' low') : null, (s.high + s.medium + s.low) ? null : '-'),
127
+ reportLink(s.id, 'Report'),
128
+ ]));
129
+ }
130
+
131
+ /** Commits as rows; a row with findings expands to list them. */
132
+ function commitsList(c) {
133
+ const wrap = el('div', { class: 'commits' });
134
+ if (!c || !c.rows.length) { wrap.append(el('p', { class: 'empty' }, 'No commits recorded yet.')); return wrap; }
135
+ wrap.append(el('div', { class: 'crow chead' }, el('span', {}, 'Commit'), el('span', {}, 'Subject'), el('span', {}, 'Branch'), el('span', {}, 'Date'), el('span', {}, 'Secret scan'), el('span', {}, 'Review')));
136
+ for (const r of c.rows) {
137
+ const scan = SCAN_LABEL[r.secretScan.state] || ['skipped', r.secretScan.state];
138
+ const cells = [
139
+ el('code', {}, r.short),
140
+ el('span', { class: r.inHistory ? '' : 'fix' }, r.inHistory ? txt(r.subject) : '(no longer in history)'),
141
+ el('span', {}, txt(r.branch || '-')),
142
+ el('span', {}, r.authorDate ? when(r.authorDate) : '-'),
143
+ r.secretScan.state === 'none' ? el('span', { class: 'fix' }, '-') : el('span', { title: txt(r.secretScan.reason) }, pill(scan[0], scan[1])),
144
+ reviewCell(r.review),
145
+ ];
146
+ if (!r.findings.length) { wrap.append(el('div', { class: 'crow' }, cells)); continue; }
147
+ const d = el('details', { class: 'cdetails' }, el('summary', { class: 'crow' }, cells));
148
+ d.append(table(['Severity', 'Category', 'Location', 'Rule', 'Message'], r.findings.map((f) => [
149
+ sev(f.severity), f.category, el('code', {}, f.file + (f.line ? ':' + f.line : '')), f.rule,
150
+ el('td', { class: 'msg' }, f.message, f.inPushOf ? el('div', { class: 'fix' }, 'in push of ' + txt(f.inPushOf).slice(0, 8) + ' (blame could not place this line)') : null)])));
151
+ wrap.append(d);
152
+ }
153
+ if (c.truncated) wrap.append(el('p', { class: 'note' }, 'showing ' + c.rows.length + ' of ' + c.total + ' commits'));
154
+ return wrap;
155
+ }
156
+
157
+ /** One repo's detail, shown inside its <details>. */
158
+ function codewatchDetail(repo) {
159
+ const cw = repo.codewatch, s = el('div', { class: 'detail' });
160
+ if (!cw.hasResults) {
161
+ const fix = repo.setup && repo.setup.fix ? repo.setup.fix : 'prism-cli enable codewatch';
162
+ s.append(el('p', { class: 'empty' }, 'No codewatch results yet. Run ', el('code', {}, fix), ', then commit and push.'));
163
+ return s;
164
+ }
165
+ s.append(el('div', { class: 'stats' },
166
+ el('div', { class: 'stat' }, el('b', {}, cw.byCategory.security), el('span', {}, 'security')),
167
+ el('div', { class: 'stat' }, el('b', {}, cw.byCategory.quality), el('span', {}, 'quality')),
168
+ el('div', { class: 'stat' }, el('b', {}, cw.gitleaksBlocks.length), el('span', {}, 'gitleaks blocks')),
169
+ el('div', { class: 'stat' }, el('b', {}, cw.skipped.length), el('span', {}, 'pushed without a scan'))));
170
+
171
+ s.append(el('h3', {}, 'Commits'));
172
+ s.append(commitsList(cw.commits));
173
+
174
+ s.append(el('h3', {}, 'Scans (last ' + cw.scans.length + ')'));
175
+ s.append(scansList(cw.scans));
176
+
177
+ s.append(el('h3', {}, 'Trend (last ' + cw.trend.length + ' runs)'));
178
+ s.append(trendSvg(cw.trend));
179
+
180
+ s.append(el('h3', {}, 'Gitleaks blocks'));
181
+ if (!cw.gitleaksBlocks.length) s.append(el('p', { class: 'empty' }, 'No commits blocked.'));
182
+ else s.append(table(['When', 'Branch', 'Result', 'Secrets'], cw.gitleaksBlocks.slice(0, 20).map((r) => [when(r.started_at), txt(r.branch), pill(r.status), r.status === 'findings' ? r.high : txt(r.reason)])));
183
+
184
+ s.append(el('h3', {}, 'Commits pushed without a pre-commit scan'));
185
+ if (!cw.skipped.length) s.append(el('p', { class: 'empty' }, 'None.'));
186
+ else {
187
+ s.append(table(['Commit', 'Branch', 'Recorded'], cw.skipped.slice(0, 20).map((r) => [el('code', {}, short(r.commit)), txt(r.branch), when(r.started_at)])));
188
+ s.append(el('p', { class: 'note' }, 'Usually git commit --no-verify; rebases and cherry-picks also show up here.'));
189
+ }
190
+ return s;
191
+ }
192
+
193
+ function codewatchPanel(data) {
194
+ const s = el('section', { class: 'card wide' }, el('h2', {}, 'Codewatch'));
195
+ const banner = scanBanner(data.machine.secretScanning);
196
+ if (banner) s.append(banner);
197
+ const rows = data.overview.codewatch;
198
+ if (!rows.length) {
199
+ const sg = (data.suggestions || []).find((x) => x.id === 'codewatch-global');
200
+ if (sg) s.append(el('p', { class: 'empty' }, "Codewatch isn't enabled in any repo yet. Cover every repo at once: ", el('code', {}, sg.command), ', or run ', el('code', {}, 'prism-cli enable codewatch'), ' inside one repo.'));
201
+ else s.append(el('p', { class: 'empty' }, "Codewatch isn't enabled in any repo yet. Run ", el('code', {}, 'prism-cli enable codewatch'), ' inside a repo.'));
202
+ return s;
203
+ }
204
+ const count = (n, cls) => el('td', { class: 'count' + (n ? ' ' + cls : '') }, n);
205
+ s.append(table(['Repo', 'High', 'Medium', 'Low', 'Last run', 'Reviewer', 'Unscanned', 'Gitleaks'], rows.map((r) => [
206
+ repoName(r), count(r.high, 'sev-high'), count(r.medium, 'sev-medium'), count(r.low, 'sev-low'),
207
+ r.lastRunAt ? (r.lastReviewId ? reportLink(r.lastReviewId, when(r.lastRunAt)) : when(r.lastRunAt)) : 'never', txt(r.reviewer || '-'), count(r.unscannedCommits, 'sev-medium'),
208
+ r.gitleaksWarning ? pill('warn', 'not scanned') : '-',
209
+ ])));
210
+ const byPath = Object.fromEntries(data.repos.map((r) => [r.path, r]));
211
+ s.append(el('h3', {}, 'Per repo'));
212
+ for (const row of rows) {
213
+ const repo = byPath[row.path]; if (!repo) continue;
214
+ const total = row.high + row.medium + row.low;
215
+ const d = el('details', repo.current ? { class: 'repo', open: '' } : { class: 'repo' },
216
+ el('summary', {}, el('b', {}, repo.name), ' ', el('span', { class: 'fix' }, repo.codewatch.hasResults ? total + ' finding' + (total === 1 ? '' : 's') + ' in the latest reviews' : 'no results yet'),
217
+ repo.current ? el('span', { class: 'tag' }, 'this repo') : null),
218
+ el('div', { class: 'repo-title' }, repo.path));
219
+ d.append(codewatchDetail(repo));
220
+ s.append(d);
221
+ }
222
+ return s;
223
+ }
224
+
225
+ function usagePanel(u) {
226
+ const s = el('section', { class: 'card' }, el('h2', {}, 'Usage (codeburn)'));
227
+ if (u.status !== 'ok') {
228
+ s.append(el('p', { class: 'empty' }, pill(u.status === 'no-data' ? 'warn' : 'missing', u.status), ' ', txt(u.message)));
229
+ } else {
230
+ s.append(el('div', { class: 'stats' },
231
+ el('div', { class: 'stat' }, el('b', {}, money(u.today.cost, u.currency)), el('span', {}, 'today, ' + u.today.calls + ' calls')),
232
+ el('div', { class: 'stat' }, el('b', {}, money(u.month.cost, u.currency)), el('span', {}, 'this month, ' + u.month.calls + ' calls, ' + u.month.sessions + ' sessions'))));
233
+ s.append(el('h3', {}, 'Top models this month'));
234
+ s.append(table(['Model', 'Cost', 'Calls'], u.topModels.map((m) => [m.name, num(money(m.cost, u.currency)), num(m.calls)])));
235
+ s.append(el('h3', {}, 'Top projects this month'));
236
+ s.append(table(['Project', 'Cost', 'Sessions'], u.topProjects.map((p) => [el('span', { title: p.path }, p.name), num(money(p.cost, u.currency)), num(p.sessions)])));
237
+ }
238
+ s.append(el('p', { class: 'note' }, 'Full charts: run ', el('code', {}, txt(u.fullView)), ' in a terminal.'));
239
+ return s;
240
+ }
241
+
242
+ /** Info (or warn) callout for the global install. Text only: the page never runs anything. */
243
+ function suggestionCallout(sg) {
244
+ if (sg.style === 'warn') {
245
+ return el('div', { class: 'callout warn', role: 'note' },
246
+ el('strong', {}, sg.missing + " repos don't have codewatch."),
247
+ el('div', {}, 'Your global core.hooksPath is set to ', el('code', {}, txt(sg.currentHooksPath)), ', so the plain global install would refuse. Use ',
248
+ el('code', {}, sg.command), ': it saves that value and still runs its hooks after codewatch.'));
249
+ }
250
+ return el('div', { class: 'callout info', role: 'note' },
251
+ el('strong', {}, sg.missing + " repos don't have codewatch. Cover all of them at once: "), el('code', {}, sg.command),
252
+ el('div', { class: 'fix' }, 'Applies to every repo on this machine, including new clones. Repos can opt out with ', el('code', {}, 'git config prism.codewatch false'), '.'));
253
+ }
254
+
255
+ function setupPanel(data) {
256
+ const s = el('section', { class: 'card' }, el('h2', {}, 'Setup checklist'));
257
+ const rows = (items) => items.map((i) => [pill(i.status), i.label, el('div', {}, i.detail, i.fix ? el('div', { class: 'fix' }, 'Fix: ' + i.fix) : null)]);
258
+ s.append(el('h3', {}, 'This machine'), table(['', 'Check', 'Detail'], rows(data.machine.setup)));
259
+ const repos = data.overview.setup, c = data.overview.counts;
260
+ s.append(el('h3', {}, 'Codewatch per repo (' + c.codewatch + ' of ' + c.repos + ' installed)'));
261
+ const label = { ok: 'ok', warn: 'warn', missing: 'missing', 'opted-out': 'opted out' };
262
+ const pillClass = { ok: 'ok', warn: 'warn', missing: 'missing', 'opted-out': 'skipped' };
263
+ const rowOf = (r) => [
264
+ repoName(r),
265
+ el('div', {}, pill(pillClass[r.status] || 'missing', (label[r.status] || r.status) + (r.status === 'ok' && r.global ? ' (global)' : '')), ' ',
266
+ r.status === 'ok' ? 'reviewer ' + txt(r.reviewer || 'none') : txt(r.detail),
267
+ r.fix ? el('div', { class: 'fix' }, 'Fix: ', el('code', {}, r.fix)) : null),
268
+ ];
269
+ const sg = (data.suggestions || []).find((x) => x.id === 'codewatch-global');
270
+ if (sg) s.append(suggestionCallout(sg));
271
+ // Always shown: the current repo (installed or not) and every installed repo.
272
+ const shown = repos.filter((r) => !r.collapsed);
273
+ const hidden = repos.filter((r) => r.collapsed);
274
+ if (shown.length) s.append(table(['Repo', 'Codewatch'], shown.map(rowOf)));
275
+ else if (!hidden.length) s.append(el('p', { class: 'empty' }, 'No git repositories found under ' + txt(data.scope.root_display) + '.'));
276
+ if (hidden.length) {
277
+ s.append(el('details', { class: 'missing-group' },
278
+ el('summary', {}, hidden.length + ' repo' + (hidden.length === 1 ? '' : 's') + ' without codewatch'),
279
+ table(['Repo', 'Codewatch'], hidden.map(rowOf))));
280
+ }
281
+ return s;
282
+ }
283
+
284
+ function securityPanel(sec) {
285
+ return el('section', { class: 'card' }, el('h2', {}, 'Security findings'), el('p', { class: 'empty' }, sec.status === 'coming-soon' ? ['Coming with ', el('code', {}, 'prism-cli security findings'), '.'] : txt(sec.message)));
286
+ }
287
+
288
+ async function load() {
289
+ const main = document.getElementById('main'), meta = document.getElementById('meta');
290
+ meta.textContent = 'loading...';
291
+ let data;
292
+ try {
293
+ const res = await fetch('/api/data', { credentials: 'same-origin', cache: 'no-store' });
294
+ if (!res.ok) throw new Error('HTTP ' + res.status);
295
+ data = await res.json();
296
+ } catch (e) { meta.textContent = 'failed to load: ' + e.message; return; }
297
+ main.replaceChildren();
298
+ if (data.scope.error) main.append(el('section', { class: 'card wide' }, pill('error'), ' ', data.scope.error));
299
+ main.append(codewatchPanel(data), usagePanel(data.usage), setupPanel(data), securityPanel(data.security));
300
+ const c = data.overview.counts;
301
+ meta.textContent = c.repos + ' repos under ' + txt(data.scope.root_display) + ' · ' + c.codewatch + ' with codewatch' + (data.scope.truncated ? ' (scan capped)' : '') + ' · updated ' + when(data.generated_at);
302
+ }
303
+ document.getElementById('refresh').addEventListener('click', load);
304
+ load();
305
+ `;
306
+ export function renderPage(nonce) {
307
+ // The nonce is base64 generated server-side; nothing user-derived is interpolated here.
308
+ return `<!DOCTYPE html>
309
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
310
+ <title>PRISM dashboard</title><style nonce="${nonce}">${STYLE}</style></head>
311
+ <body><header><h1>PRISM dashboard</h1><button id="refresh" type="button">Refresh</button><span class="meta" id="meta"></span></header>
312
+ <main id="main"></main>
313
+ <script nonce="${nonce}">${SCRIPT}</script></body></html>`;
314
+ }
315
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCb,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiQd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,wFAAwF;IACxF,OAAO;;8CAEqC,KAAK,KAAK,KAAK;;;iBAG5C,KAAK,KAAK,MAAM,yBAAyB,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The one data document behind both `prism-cli dashboard --json` and the
3
+ * web UI's /api/data, so the two can never disagree. The overview rows are
4
+ * sorted here, not in the page, so `--json` carries the same order.
5
+ */
6
+ import { type CodewatchPanel, type SecretScanning } from './codewatch.js';
7
+ import { type DiscoverOptions } from './repos.js';
8
+ import { type CheckItem, type CodewatchSetup, type MachineDeps } from './setup.js';
9
+ import { type GlobalStatus } from '../codewatch/globalHooks.js';
10
+ export declare function tildeHome(p: string, home: string): string;
11
+ import { type CodeburnExport, type UsagePanel } from './usage.js';
12
+ export declare const PAYLOAD_SCHEMA = "prism.dashboard.v2";
13
+ export interface RepoPayload {
14
+ name: string;
15
+ path: string;
16
+ current: boolean;
17
+ hasPrism: boolean;
18
+ codewatch: CodewatchPanel;
19
+ setup: CodewatchSetup;
20
+ }
21
+ /** One row of the setup table. */
22
+ export interface SetupRow {
23
+ name: string;
24
+ path: string;
25
+ current: boolean;
26
+ status: CodewatchSetup['status'];
27
+ reviewer: string | null;
28
+ detail: string;
29
+ fix: string;
30
+ /** Global hooks cover this repo. */
31
+ global: boolean;
32
+ /** Shown inside the collapsed "repos without codewatch" group: missing or opted out, and not the current repo. */
33
+ collapsed: boolean;
34
+ }
35
+ /** One row of the codewatch summary table. */
36
+ export interface CodewatchRow {
37
+ name: string;
38
+ path: string;
39
+ current: boolean;
40
+ high: number;
41
+ medium: number;
42
+ low: number;
43
+ lastRunAt: string | null;
44
+ /** Report id of the newest review, linked from the Last run cell. */
45
+ lastReviewId: string | null;
46
+ /** Reviewer of the last review, else the one that would run now. */
47
+ reviewer: string | null;
48
+ unscannedCommits: number;
49
+ /** Commits here were made while gitleaks was missing. */
50
+ gitleaksWarning: boolean;
51
+ }
52
+ /** A next step the page suggests. Read-only: the page shows the command, it never runs it. */
53
+ export interface Suggestion {
54
+ id: 'codewatch-global';
55
+ /** info: a suggestion. warn: a foreign global core.hooksPath is in the way. */
56
+ style: 'info' | 'warn';
57
+ command: string;
58
+ reason: string;
59
+ /** Repos without codewatch (status missing; opted-out repos are not counted). */
60
+ missing: number;
61
+ /** The foreign global core.hooksPath, for the warn variant. */
62
+ currentHooksPath: string | null;
63
+ }
64
+ /** Minimum missing repos before the global install is suggested; one repo is served by its own fix line. */
65
+ export declare const SUGGEST_GLOBAL_MIN_MISSING = 2;
66
+ export declare function globalSuggestion(g: GlobalStatus, missing: number): Suggestion | null;
67
+ export interface DashboardPayload {
68
+ /** repos: every repo found. codewatch: repos with both codewatch hooks installed. */
69
+ schema: typeof PAYLOAD_SCHEMA;
70
+ generated_at: string;
71
+ scope: {
72
+ root: string; /** root with the home directory shown as ~ */
73
+ root_display: string;
74
+ current_repo: string | null;
75
+ scan_ms: number;
76
+ truncated: boolean;
77
+ error: string | null;
78
+ };
79
+ repos: RepoPayload[];
80
+ overview: {
81
+ counts: {
82
+ repos: number;
83
+ codewatch: number;
84
+ };
85
+ setup: SetupRow[];
86
+ codewatch: CodewatchRow[];
87
+ };
88
+ machine: {
89
+ setup: CheckItem[];
90
+ secretScanning: SecretScanning;
91
+ globalHooks: GlobalStatus;
92
+ };
93
+ suggestions: Suggestion[];
94
+ usage: UsagePanel;
95
+ security: {
96
+ status: 'coming-soon';
97
+ message: string;
98
+ };
99
+ }
100
+ export interface PayloadOptions extends DiscoverOptions {
101
+ trendRuns?: number;
102
+ exporter?: CodeburnExport;
103
+ machineDeps?: MachineDeps;
104
+ pathEnv?: string;
105
+ }
106
+ /** The current repo first, then installed, bypassed (warn), missing and opted-out repos, each by name. */
107
+ export declare function sortSetupRows(rows: SetupRow[]): SetupRow[];
108
+ /** Most high findings first, then medium, then low, then by name. */
109
+ export declare function sortCodewatchRows(rows: CodewatchRow[]): CodewatchRow[];
110
+ export declare function buildPayload(opts: PayloadOptions): Promise<DashboardPayload>;
111
+ //# sourceMappingURL=payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../../src/dashboard/payload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The one data document behind both `prism-cli dashboard --json` and the
3
+ * web UI's /api/data, so the two can never disagree. The overview rows are
4
+ * sorted here, not in the page, so `--json` carries the same order.
5
+ */
6
+ import { collectCodewatch, combineSecretScanning } from './codewatch.js';
7
+ import { discoverRepos } from './repos.js';
8
+ import { codewatchSetup, GLOBAL_FORCE_CMD, GLOBAL_INSTALL_CMD, machineChecks } from './setup.js';
9
+ import { globalStatus } from '../codewatch/globalHooks.js';
10
+ import { findOnPath } from '../utils/tool-check.js';
11
+ import { homedir } from 'node:os';
12
+ import { sep } from 'node:path';
13
+ export function tildeHome(p, home) {
14
+ if (p === home)
15
+ return '~';
16
+ return p.startsWith(home + sep) ? '~' + p.slice(home.length) : p;
17
+ }
18
+ import { collectUsage } from './usage.js';
19
+ export const PAYLOAD_SCHEMA = 'prism.dashboard.v2';
20
+ /** Minimum missing repos before the global install is suggested; one repo is served by its own fix line. */
21
+ export const SUGGEST_GLOBAL_MIN_MISSING = 2;
22
+ export function globalSuggestion(g, missing) {
23
+ if (g.state === 'installed' || missing < SUGGEST_GLOBAL_MIN_MISSING)
24
+ return null;
25
+ if (g.state === 'elsewhere') {
26
+ return {
27
+ id: 'codewatch-global', style: 'warn', command: GLOBAL_FORCE_CMD, missing, currentHooksPath: g.hooksPath,
28
+ reason: `${missing} repos don't have codewatch. Your global core.hooksPath is set to ${g.hooksPath}, so --global alone would refuse; --force saves that value and still runs its hooks after codewatch.`,
29
+ };
30
+ }
31
+ return {
32
+ id: 'codewatch-global', style: 'info', command: GLOBAL_INSTALL_CMD, missing, currentHooksPath: null,
33
+ reason: `${missing} repos don't have codewatch. Cover all of them at once.`,
34
+ };
35
+ }
36
+ const byName = (a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path);
37
+ const STATUS_ORDER = { ok: 0, warn: 1, missing: 2, 'opted-out': 3 };
38
+ /** The current repo first, then installed, bypassed (warn), missing and opted-out repos, each by name. */
39
+ export function sortSetupRows(rows) {
40
+ return [...rows].sort((a, b) => Number(b.current) - Number(a.current) || STATUS_ORDER[a.status] - STATUS_ORDER[b.status] || byName(a, b));
41
+ }
42
+ /** Most high findings first, then medium, then low, then by name. */
43
+ export function sortCodewatchRows(rows) {
44
+ return [...rows].sort((a, b) => b.high - a.high || b.medium - a.medium || b.low - a.low || byName(a, b));
45
+ }
46
+ export async function buildPayload(opts) {
47
+ const d = discoverRepos(opts);
48
+ const usage = await collectUsage({ exporter: opts.exporter, pathEnv: opts.pathEnv });
49
+ const gitleaksAvailable = findOnPath('gitleaks', opts.pathEnv) !== null;
50
+ const g = globalStatus(opts.home || homedir());
51
+ const repos = d.repos.map((r) => ({
52
+ name: r.name,
53
+ path: r.path,
54
+ current: r.current,
55
+ hasPrism: r.hasPrism,
56
+ codewatch: collectCodewatch(r.path, opts.trendRuns, gitleaksAvailable),
57
+ setup: codewatchSetup(r.path, { pathEnv: opts.pathEnv, global: g }),
58
+ }));
59
+ const setupRows = sortSetupRows(repos.map((r) => ({
60
+ name: r.name, path: r.path, current: r.current, status: r.setup.status, reviewer: r.setup.reviewer, detail: r.setup.detail, fix: r.setup.fix,
61
+ global: r.setup.global,
62
+ // Bypassed repos (warn) need attention, so they stay visible.
63
+ collapsed: !r.current && (r.setup.status === 'missing' || r.setup.status === 'opted-out'),
64
+ })));
65
+ // The summary lists only repos where codewatch is installed or has left
66
+ // results; the rest have nothing to summarize and appear in the setup list.
67
+ const active = repos.filter((r) => r.setup.status === 'ok' || r.codewatch.hasResults);
68
+ const cwRows = sortCodewatchRows(active.map((r) => ({
69
+ name: r.name, path: r.path, current: r.current,
70
+ high: r.codewatch.bySeverity.high, medium: r.codewatch.bySeverity.medium, low: r.codewatch.bySeverity.low,
71
+ lastRunAt: r.codewatch.lastRunAt,
72
+ lastReviewId: r.codewatch.lastReviewId,
73
+ reviewer: r.codewatch.lastReviewer ?? r.setup.reviewer,
74
+ unscannedCommits: r.codewatch.secretScanning.unscannedCommits,
75
+ gitleaksWarning: r.codewatch.secretScanning.unscannedCommits > 0,
76
+ })));
77
+ return {
78
+ schema: PAYLOAD_SCHEMA,
79
+ generated_at: new Date().toISOString(),
80
+ scope: { root: d.scannedRoot, root_display: tildeHome(d.scannedRoot, opts.home || homedir()), current_repo: d.currentRepo, scan_ms: d.scanMs, truncated: d.truncated, error: d.error },
81
+ repos,
82
+ overview: { counts: { repos: repos.length, codewatch: setupRows.filter((r) => r.status === 'ok').length }, setup: setupRows, codewatch: cwRows },
83
+ machine: {
84
+ setup: machineChecks({ pathEnv: opts.pathEnv, global: g, ...opts.machineDeps }),
85
+ globalHooks: g,
86
+ secretScanning: combineSecretScanning(repos.map((r) => r.codewatch.secretScanning), gitleaksAvailable),
87
+ },
88
+ suggestions: [globalSuggestion(g, setupRows.filter((r) => r.status === 'missing').length)].filter((x) => x !== null),
89
+ usage,
90
+ security: { status: 'coming-soon', message: 'Coming with prism-cli security findings.' },
91
+ };
92
+ }
93
+ //# sourceMappingURL=payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.js","sourceRoot":"","sources":["../../../src/dashboard/payload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { type PrismResult, type ResultEntry, type Severity } from '../codewatch/results.js';
2
+ import { Blamer, type CommitFinding, type LogEntry, type SecretScanState } from './commits.js';
3
+ /** The listing entry whose id equals `id`, or null. Unknown, malformed and traversal ids all return null. */
4
+ export declare function findResultById(repoRoot: string, id: string, entries?: ResultEntry[]): ResultEntry | null;
5
+ /** Newest review entries, newest first. */
6
+ export declare function reviewEntries(entries: ResultEntry[]): ResultEntry[];
7
+ export interface ReportCommit {
8
+ sha: string;
9
+ short: string;
10
+ subject: string | null;
11
+ authorDate: string | null;
12
+ secretScan: SecretScanState;
13
+ secretScanReason: string | null;
14
+ high: number;
15
+ medium: number;
16
+ low: number;
17
+ }
18
+ export interface ReportFinding extends CommitFinding {
19
+ commit: string | null;
20
+ commitSubject: string | null;
21
+ }
22
+ export interface ReportBlock {
23
+ at: string;
24
+ branch: string | null;
25
+ /** rule id and location only; never the secret. */
26
+ items: {
27
+ rule: string;
28
+ file: string;
29
+ line: number | null;
30
+ }[];
31
+ }
32
+ export interface ScanReport {
33
+ schema: 'prism.codewatch.report.v1';
34
+ id: string;
35
+ repo: {
36
+ name: string;
37
+ path: string;
38
+ };
39
+ branch: string | null;
40
+ upstream: string | null;
41
+ range: {
42
+ base: string | null;
43
+ tip: string;
44
+ commits: number;
45
+ };
46
+ reviewer: {
47
+ tool: string;
48
+ version: string | null;
49
+ model: string | null;
50
+ };
51
+ startedAt: string;
52
+ finishedAt: string;
53
+ durationSeconds: number | null;
54
+ status: PrismResult['status'];
55
+ /** Display state: findings, clean, error or skipped. */
56
+ state: 'findings' | 'clean' | 'error' | 'skipped';
57
+ reason: string | null;
58
+ score: number | null;
59
+ truncated: boolean;
60
+ summary: string | null;
61
+ totals: {
62
+ severity: Record<Severity, number>;
63
+ category: {
64
+ security: number;
65
+ quality: number;
66
+ };
67
+ findings: number;
68
+ };
69
+ files: {
70
+ file: string;
71
+ findings: ReportFinding[];
72
+ }[];
73
+ secretScans: ReportCommit[];
74
+ gitleaksBlocks: ReportBlock[];
75
+ commits: ReportCommit[];
76
+ }
77
+ /** Resolves `--commit <sha>`: the newest review whose range includes it (prefix match allowed). */
78
+ export declare function reviewForCommit(entries: ResultEntry[], sha: string, log: Map<string, LogEntry>): ResultEntry | null;
79
+ export declare function buildReport(repoRoot: string, entry: ResultEntry, all: ResultEntry[], deps?: {
80
+ log?: Map<string, LogEntry>;
81
+ blamer?: Blamer;
82
+ }): ScanReport;
83
+ export declare function renderReportJson(r: ScanReport): string;
84
+ export declare function renderReportMarkdown(r: ScanReport): string;
85
+ export declare function escapeHtml(s: unknown): string;
86
+ export interface HtmlOptions {
87
+ /** CSP nonce for the inline style, when served by the dashboard. */
88
+ nonce?: string;
89
+ /** Show the back and Download Markdown links (served page only). */
90
+ links?: {
91
+ back: string;
92
+ markdown: string;
93
+ };
94
+ }
95
+ /** Self-contained HTML: inline CSS, no script, every value escaped. */
96
+ export declare function renderReportHtml(r: ScanReport, opts?: HtmlOptions): string;
97
+ /** Finds `id` in any of `repos` and builds its report; null when unknown or not a review. */
98
+ export declare function reportFromRepos(repos: string[], id: string): ScanReport | null;
99
+ //# sourceMappingURL=report.d.ts.map