@getcodesentinel/codesentinel 1.20.0 → 1.21.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.
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Satoshi,Avenir Next,Segoe UI,Helvetica Neue,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 210 33% 98%;--foreground: 222 47% 11%;--card: 0 0% 100%;--card-foreground: 222 47% 11%;--popover: 0 0% 100%;--popover-foreground: 222 47% 11%;--primary: 221 83% 53%;--primary-foreground: 210 40% 98%;--secondary: 210 40% 96%;--secondary-foreground: 222 47% 11%;--muted: 210 40% 96%;--muted-foreground: 215 16% 47%;--accent: 201 94% 94%;--accent-foreground: 222 47% 11%;--destructive: 0 84% 60%;--destructive-foreground: 210 40% 98%;--border: 214 32% 91%;--input: 214 32% 91%;--ring: 221 83% 53%;--radius: 1rem}*{border-color:hsl(var(--border))}body{min-height:100vh;background-color:hsl(var(--background));font-family:Satoshi,Avenir Next,Segoe UI,Helvetica Neue,sans-serif;color:hsl(var(--foreground));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-image:radial-gradient(circle at top left,rgba(59,130,246,.16),transparent 24rem),radial-gradient(circle at top right,rgba(20,184,166,.1),transparent 20rem),linear-gradient(180deg,#ffffffe6,#f8fafc)}#root{min-height:100vh}.absolute{position:absolute}.relative{position:relative}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.min-h-40{min-height:10rem}.min-h-screen{min-height:100vh}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-24{width:6rem}.w-3\.5{width:.875rem}.w-36{width:9rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[220px\]{min-width:220px}.min-w-\[260px\]{min-width:260px}.min-w-\[320px\]{min-width:320px}.min-w-\[880px\]{min-width:880px}.min-w-\[920px\]{min-width:920px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-7xl{max-width:80rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.caption-bottom{caption-side:bottom}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.break-words{overflow-wrap:break-word}.rounded-2xl{border-radius:1rem}.rounded-\[28px\]{border-radius:28px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-500\/20{border-color:#f59e0b33}.border-border{border-color:hsl(var(--border))}.border-border\/50{border-color:hsl(var(--border) / .5)}.border-border\/60{border-color:hsl(var(--border) / .6)}.border-border\/80{border-color:hsl(var(--border) / .8)}.border-emerald-500\/20{border-color:#10b98133}.border-rose-500\/20{border-color:#f43f5e33}.border-l-transparent{border-left-color:transparent}.border-t-transparent{border-top-color:transparent}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-background\/70{background-color:hsl(var(--background) / .7)}.bg-background\/80{background-color:hsl(var(--background) / .8)}.bg-border{background-color:hsl(var(--border))}.bg-border\/70{background-color:hsl(var(--border) / .7)}.bg-card\/60{background-color:hsl(var(--card) / .6)}.bg-card\/70{background-color:hsl(var(--card) / .7)}.bg-card\/80{background-color:hsl(var(--card) / .8)}.bg-card\/90{background-color:hsl(var(--card) / .9)}.bg-emerald-500\/10{background-color:#10b9811a}.bg-rose-500\/10{background-color:#f43f5e1a}.bg-secondary{background-color:hsl(var(--secondary))}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.bg-slate-950{--tw-bg-opacity: 1;background-color:rgb(2 6 23 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white\/70{background-color:#ffffffb3}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-sky-500{--tw-gradient-from: #0ea5e9 var(--tw-gradient-from-position);--tw-gradient-to: rgb(14 165 233 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-cyan-500{--tw-gradient-to: rgb(6 182 212 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #06b6d4 var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-emerald-500{--tw-gradient-to: #10b981 var(--tw-gradient-to-position)}.p-0\.5{padding:.125rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:SFMono-Regular,Menlo,Monaco,Consolas,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[11px\]{font-size:11px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.tracking-\[0\.16em\]{letter-spacing:.16em}.tracking-\[0\.18em\]{letter-spacing:.18em}.tracking-\[0\.28em\]{letter-spacing:.28em}.tracking-normal{letter-spacing:0em}.tracking-tight{letter-spacing:-.025em}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-foreground{color:hsl(var(--foreground))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-rose-700{--tw-text-opacity: 1;color:rgb(190 18 60 / var(--tw-text-opacity, 1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-panel{--tw-shadow: 0 20px 50px -32px rgba(15, 23, 42, .35);--tw-shadow-colored: 0 20px 50px -32px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.hover\:bg-muted\/40:hover{background-color:hsl(var(--muted) / .4)}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:hsl(var(--background))}.data-\[state\=open\]\:bg-muted\/50[data-state=open]{background-color:hsl(var(--muted) / .5)}.data-\[state\=active\]\:text-foreground[data-state=active]{color:hsl(var(--foreground))}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}@media(min-width:640px){.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:p-8{padding:2rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-5xl{font-size:3rem;line-height:1}}@media(min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[1\.05fr_0\.95fr\]{grid-template-columns:1.05fr .95fr}.lg\:grid-cols-\[1\.1fr_0\.9fr\]{grid-template-columns:1.1fr .9fr}.lg\:grid-cols-\[1\.3fr_0\.7fr\]{grid-template-columns:1.3fr .7fr}.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media(min-width:1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state=open]>svg{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-width:0px}.\[\&_tr\]\:border-b tr{border-bottom-width:1px}.\[\&_tr\]\:border-border\/60 tr{border-color:hsl(var(--border) / .6)}
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>CodeSentinel Report</title>
7
+ <script type="module" crossorigin src="./assets/index-BarpZIgq.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-Vsiw9fTu.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
package/dist/index.js CHANGED
@@ -1753,6 +1753,7 @@ var analyzeDependencyCandidateFromRegistry = async (input) => {
1753
1753
  };
1754
1754
 
1755
1755
  // ../reporter/dist/index.js
1756
+ import { basename, posix } from "path";
1756
1757
  var SNAPSHOT_SCHEMA_VERSION = "codesentinel.snapshot.v1";
1757
1758
  var REPORT_SCHEMA_VERSION = "codesentinel.report.v1";
1758
1759
  var RISK_MODEL_VERSION = "deterministic-v1";
@@ -1914,6 +1915,11 @@ var compareSnapshots = (current, baseline) => {
1914
1915
  }
1915
1916
  };
1916
1917
  };
1918
+ var toPosixDirname = (value) => {
1919
+ const normalized = value.replaceAll("\\", "/");
1920
+ const directory = posix.dirname(normalized);
1921
+ return directory === "." ? "root" : directory;
1922
+ };
1917
1923
  var findTraceTarget = (snapshot, targetType, targetId) => snapshot.trace?.targets.find(
1918
1924
  (target) => target.targetType === targetType && target.targetId === targetId
1919
1925
  );
@@ -1959,14 +1965,27 @@ var suggestedActions = (target) => {
1959
1965
  }
1960
1966
  return [...new Set(actions)].slice(0, 3);
1961
1967
  };
1962
- var hotspotItems = (snapshot) => snapshot.analysis.risk.hotspots.slice(0, 10).map((hotspot) => {
1968
+ var hotspotReason = (factors) => {
1969
+ if (factors.length === 0) {
1970
+ return "Limited trace data available for this hotspot.";
1971
+ }
1972
+ return factors.slice(0, 2).map((factor) => `${factor.label} (${factor.contribution})`).join(" + ");
1973
+ };
1974
+ var hotspotItems = (snapshot) => snapshot.analysis.risk.hotspots.slice(0, 10).map((hotspot, index) => {
1963
1975
  const fileScore = snapshot.analysis.risk.fileScores.find((item) => item.file === hotspot.file);
1976
+ const evolutionMetrics = snapshot.analysis.evolution.available ? snapshot.analysis.evolution.files.find((item) => item.filePath === hotspot.file) : void 0;
1964
1977
  const traceTarget = findTraceTarget(snapshot, "file", hotspot.file);
1965
1978
  const factors = toRenderedFactors(traceTarget);
1966
1979
  return {
1980
+ rank: index + 1,
1967
1981
  target: hotspot.file,
1982
+ module: toPosixDirname(hotspot.file),
1968
1983
  score: hotspot.score,
1969
1984
  normalizedScore: fileScore?.normalizedScore ?? round43(hotspot.score / 100),
1985
+ commitCount: evolutionMetrics?.commitCount ?? null,
1986
+ churnTotal: evolutionMetrics?.churnTotal ?? null,
1987
+ riskContributions: hotspot.factors,
1988
+ reason: hotspotReason(factors),
1970
1989
  topFactors: factors,
1971
1990
  suggestedActions: suggestedActions(traceTarget),
1972
1991
  biggestLevers: (traceTarget?.reductionLevers ?? []).slice(0, 3).map((lever) => `${factorLabel(lever.factorId)} (${lever.estimatedImpact})`)
@@ -1987,6 +2006,51 @@ var repositoryConfidence = (snapshot) => {
1987
2006
  );
1988
2007
  return round43(weighted / weight);
1989
2008
  };
2009
+ var normalizeDependencyScope = (scope) => {
2010
+ switch (scope) {
2011
+ case "prod":
2012
+ case "dev":
2013
+ return scope;
2014
+ default:
2015
+ return "unknown";
2016
+ }
2017
+ };
2018
+ var topStructuralFiles = (snapshot, selector) => [...snapshot.analysis.structural.files].sort((a, b) => selector(b) - selector(a) || a.relativePath.localeCompare(b.relativePath)).slice(0, 5).map((file) => ({
2019
+ file: file.relativePath,
2020
+ module: toPosixDirname(file.relativePath),
2021
+ value: selector(file)
2022
+ }));
2023
+ var cycleDetails = (snapshot) => snapshot.analysis.structural.cycles.map((cycle, index) => {
2024
+ const nodes = [...cycle.nodes].sort((a, b) => a.localeCompare(b));
2025
+ return {
2026
+ id: `cycle-${index + 1}`,
2027
+ size: nodes.length,
2028
+ nodes,
2029
+ path: nodes.join(" -> ")
2030
+ };
2031
+ });
2032
+ var riskyDependencies = (snapshot) => {
2033
+ if (!snapshot.analysis.external.available) {
2034
+ return [];
2035
+ }
2036
+ const dependencyByName = new Map(
2037
+ snapshot.analysis.external.dependencies.map((dependency) => [dependency.name, dependency])
2038
+ );
2039
+ return snapshot.analysis.risk.dependencyScores.map((score) => {
2040
+ const dependency = dependencyByName.get(score.dependency);
2041
+ const riskSignals = [.../* @__PURE__ */ new Set([...score.ownRiskSignals, ...score.inheritedRiskSignals])];
2042
+ return {
2043
+ name: score.dependency,
2044
+ score: score.score,
2045
+ normalizedScore: score.normalizedScore,
2046
+ dependencyScope: normalizeDependencyScope(dependency?.dependencyScope),
2047
+ direct: dependency?.direct ?? false,
2048
+ resolvedVersion: dependency?.resolvedVersion ?? null,
2049
+ riskSignals,
2050
+ reason: riskSignals.length === 0 ? "Derived from aggregate dependency risk signals." : riskSignals.join(", ")
2051
+ };
2052
+ }).filter((dependency) => dependency.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, 20);
2053
+ };
1990
2054
  var repositoryDimensionScores = (snapshot) => {
1991
2055
  const target = findTraceTarget(snapshot, "repository", snapshot.analysis.structural.targetPath);
1992
2056
  if (target === void 0) {
@@ -2019,6 +2083,7 @@ var createReport = (snapshot, diff) => {
2019
2083
  schemaVersion: REPORT_SCHEMA_VERSION,
2020
2084
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2021
2085
  repository: {
2086
+ name: basename(snapshot.analysis.structural.targetPath) || snapshot.analysis.structural.targetPath,
2022
2087
  targetPath: snapshot.analysis.structural.targetPath,
2023
2088
  riskScore: snapshot.analysis.risk.riskScore,
2024
2089
  normalizedScore: snapshot.analysis.risk.normalizedScore,
@@ -2034,6 +2099,12 @@ var createReport = (snapshot, diff) => {
2034
2099
  cycles: snapshot.analysis.structural.cycles.map(
2035
2100
  (cycle) => [...cycle.nodes].sort((a, b) => a.localeCompare(b)).join(" -> ")
2036
2101
  ),
2102
+ cycleDetails: cycleDetails(snapshot),
2103
+ fanInOutExtremes: {
2104
+ highestFanIn: topStructuralFiles(snapshot, (file) => file.fanIn),
2105
+ highestFanOut: topStructuralFiles(snapshot, (file) => file.fanOut),
2106
+ deepestFiles: topStructuralFiles(snapshot, (file) => file.depth)
2107
+ },
2037
2108
  fragileClusters: snapshot.analysis.risk.fragileClusters.map((cluster) => ({
2038
2109
  id: cluster.id,
2039
2110
  kind: cluster.kind,
@@ -2057,7 +2128,8 @@ var createReport = (snapshot, diff) => {
2057
2128
  ),
2058
2129
  abandonedDependencies: [...external.abandonedDependencies].sort(
2059
2130
  (a, b) => a.localeCompare(b)
2060
- )
2131
+ ),
2132
+ riskyDependencies: riskyDependencies(snapshot)
2061
2133
  },
2062
2134
  appendix: {
2063
2135
  snapshotSchemaVersion: snapshot.schemaVersion,
@@ -2298,7 +2370,7 @@ var formatReport = (report, format) => {
2298
2370
 
2299
2371
  // ../governance/dist/index.js
2300
2372
  import { mkdirSync, rmSync } from "fs";
2301
- import { basename, join as join5, resolve } from "path";
2373
+ import { basename as basename2, join as join5, resolve } from "path";
2302
2374
  import { execFile } from "child_process";
2303
2375
  import { promisify } from "util";
2304
2376
  var EXIT_CODES = {
@@ -2911,9 +2983,9 @@ var resolveAutoBaselineRef = async (input) => {
2911
2983
 
2912
2984
  // src/index.ts
2913
2985
  import { readFileSync as readFileSync2 } from "fs";
2914
- import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
2915
- import { dirname as dirname2, resolve as resolve5 } from "path";
2916
- import { fileURLToPath } from "url";
2986
+ import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
2987
+ import { dirname as dirname3, resolve as resolve6 } from "path";
2988
+ import { fileURLToPath as fileURLToPath2 } from "url";
2917
2989
 
2918
2990
  // src/application/format-analyze-output.ts
2919
2991
  var toHealthTier2 = (score) => {
@@ -3558,7 +3630,7 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3558
3630
  );
3559
3631
  return "skip";
3560
3632
  }
3561
- return await new Promise((resolve6) => {
3633
+ return await new Promise((resolve7) => {
3562
3634
  emitKeypressEvents(stdin);
3563
3635
  let selectedIndex = 0;
3564
3636
  const previousRawMode = stdin.isRaw;
@@ -3583,7 +3655,7 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3583
3655
  } else {
3584
3656
  stderr.write("\n");
3585
3657
  }
3586
- resolve6(choice);
3658
+ resolve7(choice);
3587
3659
  };
3588
3660
  const onKeypress = (_str, key) => {
3589
3661
  if (key.ctrl === true && key.name === "c") {
@@ -3777,7 +3849,7 @@ var promptSelection = async (currentVersion, actions) => {
3777
3849
  if (!stdin2.isTTY || !stderr2.isTTY || typeof stdin2.setRawMode !== "function") {
3778
3850
  return "exit";
3779
3851
  }
3780
- return await new Promise((resolve6) => {
3852
+ return await new Promise((resolve7) => {
3781
3853
  emitKeypressEvents2(stdin2);
3782
3854
  let selectedIndex = 0;
3783
3855
  const previousRawMode = stdin2.isRaw;
@@ -3792,7 +3864,7 @@ var promptSelection = async (currentVersion, actions) => {
3792
3864
  stdin2.setRawMode(previousRawMode);
3793
3865
  clearTerminal();
3794
3866
  showCursor2();
3795
- resolve6(selection);
3867
+ resolve7(selection);
3796
3868
  };
3797
3869
  const onKeypress = (_str, key) => {
3798
3870
  if (key.ctrl === true && key.name === "c") {
@@ -3885,7 +3957,7 @@ var waitForReturnToMenu = async () => {
3885
3957
  }
3886
3958
  stderr2.write(`
3887
3959
  ${PROMPT_PADDING}Press enter to return to the menu...`);
3888
- await new Promise((resolve6) => {
3960
+ await new Promise((resolve7) => {
3889
3961
  emitKeypressEvents2(stdin2);
3890
3962
  const previousRawMode = stdin2.isRaw;
3891
3963
  const cleanup = () => {
@@ -3894,7 +3966,7 @@ ${PROMPT_PADDING}Press enter to return to the menu...`);
3894
3966
  stdin2.setRawMode(previousRawMode);
3895
3967
  showCursor2();
3896
3968
  stderr2.write("\n");
3897
- resolve6();
3969
+ resolve7();
3898
3970
  };
3899
3971
  const onKeypress = (_str, key) => {
3900
3972
  if (key.ctrl === true && key.name === "c") {
@@ -3912,7 +3984,7 @@ ${PROMPT_PADDING}Press enter to return to the menu...`);
3912
3984
  });
3913
3985
  };
3914
3986
  var runCliCommand = async (scriptPath2, args) => {
3915
- return await new Promise((resolve6, reject) => {
3987
+ return await new Promise((resolve7, reject) => {
3916
3988
  const child = spawn2(process.execPath, [...process.execArgv, scriptPath2, ...args], {
3917
3989
  stdio: ["inherit", "pipe", "pipe"],
3918
3990
  env: {
@@ -3926,7 +3998,7 @@ var runCliCommand = async (scriptPath2, args) => {
3926
3998
  reject(error);
3927
3999
  });
3928
4000
  child.on("close", (code) => {
3929
- resolve6(code ?? 1);
4001
+ resolve7(code ?? 1);
3930
4002
  });
3931
4003
  });
3932
4004
  };
@@ -7640,7 +7712,114 @@ ${ciMarkdown}`;
7640
7712
  };
7641
7713
 
7642
7714
  // src/application/run-report-command.ts
7643
- import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
7715
+ import { join as join8 } from "path";
7716
+ import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
7717
+
7718
+ // src/application/html-report.ts
7719
+ import { mkdir as mkdir3, readFile as readFile5, rm, stat as stat2, writeFile as writeFile5 } from "fs/promises";
7720
+ import { dirname as dirname2, join as join7, resolve as resolve5 } from "path";
7721
+ import { fileURLToPath } from "url";
7722
+ var HTML_REPORT_DIR = ".codesentinel/report";
7723
+ var getBundledHtmlAppPath = () => resolve5(dirname2(fileURLToPath(import.meta.url)), "html-report-app");
7724
+ var serializeReportBootstrap = (report) => `window.__CODESENTINEL_REPORT__ = ${JSON.stringify(report).replaceAll("</", "<\\/")};
7725
+ `;
7726
+ var escapeInlineScript = (script) => script.replaceAll("</script", "<\\/script").replaceAll("<script", "\\x3Cscript").replaceAll("<!--", "\\x3C!--").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
7727
+ var ensureDirectory = async (directoryPath) => {
7728
+ await mkdir3(directoryPath, { recursive: true });
7729
+ };
7730
+ var readReferencedAssets = async (appPath, indexHtml) => {
7731
+ const stylesheetPaths = [
7732
+ ...indexHtml.matchAll(/<link[^>]+rel=["']stylesheet["'][^>]+href=["']([^"']+)["'][^>]*>/g)
7733
+ ].map((match) => match[1]).filter((value) => value !== void 0);
7734
+ const styles = await Promise.all(
7735
+ stylesheetPaths.map(async (href) => readFile5(resolve5(appPath, href), "utf8"))
7736
+ );
7737
+ const scriptPaths = [...indexHtml.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*><\/script>/g)].map((match) => match[1]).filter((value) => value !== void 0);
7738
+ const scripts = await Promise.all(
7739
+ scriptPaths.map(async (src) => readFile5(resolve5(appPath, src), "utf8"))
7740
+ );
7741
+ return { styles, scripts };
7742
+ };
7743
+ var inlineBuiltHtml = async (appPath, report) => {
7744
+ const indexHtml = await readFile5(resolve5(appPath, "index.html"), "utf8");
7745
+ const { styles, scripts } = await readReferencedAssets(appPath, indexHtml);
7746
+ const htmlWithoutExternalAssets = indexHtml.replace(/\s*<link[^>]+rel=["']stylesheet["'][^>]*>\s*/g, "").replace(/\s*<script[^>]+src=["'][^"']+["'][^>]*><\/script>\s*/g, "");
7747
+ const inlineStyles = styles.map((style) => `<style>
7748
+ ${style}
7749
+ </style>`).join("\n");
7750
+ const bootstrapScript = `<script>
7751
+ ${serializeReportBootstrap(report)}</script>`;
7752
+ const inlineScripts = scripts.map((script) => `<script>
7753
+ ${escapeInlineScript(script)}
7754
+ </script>`).join("\n");
7755
+ return htmlWithoutExternalAssets.replace("</head>", () => `${inlineStyles === "" ? "" : `${inlineStyles}
7756
+ `}</head>`).replace(
7757
+ "</body>",
7758
+ () => `${bootstrapScript}
7759
+ ${inlineScripts === "" ? "" : `${inlineScripts}
7760
+ `}</body>`
7761
+ );
7762
+ };
7763
+ var assertHtmlAppAssets = async (assetPath) => {
7764
+ const assetStats = await stat2(assetPath).catch(() => void 0);
7765
+ if (assetStats === void 0 || !assetStats.isDirectory()) {
7766
+ throw new Error(
7767
+ `html_report_assets_missing: expected built app at ${assetPath}. Run the workspace build first.`
7768
+ );
7769
+ }
7770
+ };
7771
+ var resolveHtmlReportOutputPath = (repositoryPath, outputPath) => {
7772
+ const invocationCwd = process.env["INIT_CWD"] ?? process.cwd();
7773
+ return resolve5(invocationCwd, outputPath ?? join7(repositoryPath, HTML_REPORT_DIR));
7774
+ };
7775
+ var writeHtmlReportBundle = async (report, options) => {
7776
+ const bundledAppPath = options.bundledAppPath ?? getBundledHtmlAppPath();
7777
+ await assertHtmlAppAssets(bundledAppPath);
7778
+ const outputPath = resolveHtmlReportOutputPath(options.repositoryPath, options.outputPath);
7779
+ await rm(outputPath, { recursive: true, force: true });
7780
+ await ensureDirectory(outputPath);
7781
+ await writeFile5(
7782
+ resolve5(outputPath, "index.html"),
7783
+ await inlineBuiltHtml(bundledAppPath, report),
7784
+ "utf8"
7785
+ );
7786
+ return outputPath;
7787
+ };
7788
+
7789
+ // src/application/open-path.ts
7790
+ import { spawn as spawn3 } from "child_process";
7791
+ import { platform } from "os";
7792
+ var openCommandForPlatform = (targetPath) => {
7793
+ switch (platform()) {
7794
+ case "darwin":
7795
+ return { command: "open", args: [targetPath] };
7796
+ case "win32":
7797
+ return { command: "cmd", args: ["/c", "start", "", targetPath] };
7798
+ case "linux":
7799
+ return { command: "xdg-open", args: [targetPath] };
7800
+ default:
7801
+ return void 0;
7802
+ }
7803
+ };
7804
+ var openPath = async (targetPath) => {
7805
+ const command = openCommandForPlatform(targetPath);
7806
+ if (command === void 0) {
7807
+ return false;
7808
+ }
7809
+ return new Promise((resolve7) => {
7810
+ const child = spawn3(command.command, command.args, {
7811
+ detached: true,
7812
+ stdio: "ignore"
7813
+ });
7814
+ child.once("error", () => resolve7(false));
7815
+ child.once("spawn", () => {
7816
+ child.unref();
7817
+ resolve7(true);
7818
+ });
7819
+ });
7820
+ };
7821
+
7822
+ // src/application/run-report-command.ts
7644
7823
  var runReportCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
7645
7824
  logger.info("building analysis snapshot");
7646
7825
  const current = await buildAnalysisSnapshot(
@@ -7654,7 +7833,7 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
7654
7833
  logger
7655
7834
  );
7656
7835
  if (options.snapshotPath !== void 0) {
7657
- await writeFile5(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
7836
+ await writeFile6(options.snapshotPath, JSON.stringify(current, null, 2), "utf8");
7658
7837
  logger.info(`snapshot written: ${options.snapshotPath}`);
7659
7838
  }
7660
7839
  let report;
@@ -7662,17 +7841,39 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
7662
7841
  report = createReport(current);
7663
7842
  } else {
7664
7843
  logger.info(`loading baseline snapshot: ${options.comparePath}`);
7665
- const baselineRaw = await readFile5(options.comparePath, "utf8");
7844
+ const baselineRaw = await readFile6(options.comparePath, "utf8");
7666
7845
  const baseline = parseSnapshot(baselineRaw);
7667
7846
  const diff = compareSnapshots(current, baseline);
7668
7847
  report = createReport(current, diff);
7669
7848
  }
7849
+ if (options.format === "html") {
7850
+ const bundlePath = await writeHtmlReportBundle(report, {
7851
+ repositoryPath: current.analysis.structural.targetPath,
7852
+ ...options.outputPath === void 0 ? {} : { outputPath: options.outputPath }
7853
+ });
7854
+ if (options.open === true) {
7855
+ const opened = await openPath(join8(bundlePath, "index.html"));
7856
+ if (!opened) {
7857
+ logger.warn("unable to open html report automatically on this platform");
7858
+ }
7859
+ }
7860
+ logger.info(`html report written: ${bundlePath}`);
7861
+ return {
7862
+ report,
7863
+ rendered: bundlePath,
7864
+ outputPath: bundlePath
7865
+ };
7866
+ }
7670
7867
  const rendered = formatReport(report, options.format);
7671
7868
  if (options.outputPath !== void 0) {
7672
- await writeFile5(options.outputPath, rendered, "utf8");
7869
+ await writeFile6(options.outputPath, rendered, "utf8");
7673
7870
  logger.info(`report written: ${options.outputPath}`);
7674
7871
  }
7675
- return { report, rendered };
7872
+ return {
7873
+ report,
7874
+ rendered,
7875
+ ...options.outputPath === void 0 ? {} : { outputPath: options.outputPath }
7876
+ };
7676
7877
  };
7677
7878
 
7678
7879
  // src/application/run-explain-command.ts
@@ -7742,7 +7943,7 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
7742
7943
 
7743
7944
  // src/index.ts
7744
7945
  var program = new Command();
7745
- var packageJsonPath = resolve5(dirname2(fileURLToPath(import.meta.url)), "../package.json");
7946
+ var packageJsonPath = resolve6(dirname3(fileURLToPath2(import.meta.url)), "../package.json");
7746
7947
  var { version } = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
7747
7948
  var parseRecentWindowDays = (value) => {
7748
7949
  const parsed = Number.parseInt(value, 10);
@@ -7988,8 +8189,8 @@ program.command("report").argument("[path]", "path to the project to analyze").a
7988
8189
  "log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
7989
8190
  ).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
7990
8191
  ).addOption(
7991
- new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("md")
7992
- ).option("--output <path>", "write rendered report to a file path").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
8192
+ new Option("--format <mode>", "output format: text, json, md, html").choices(["text", "json", "md", "html"]).default("md")
8193
+ ).option("--output <path>", "write rendered report to a file path").option("--open", "open the generated HTML report in the default browser").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
7993
8194
  new Option(
7994
8195
  "--recent-window-days <days>",
7995
8196
  "git recency window in days used for evolution volatility metrics"
@@ -8005,14 +8206,20 @@ program.command("report").argument("[path]", "path to the project to analyze").a
8005
8206
  ...options.output === void 0 ? {} : { outputPath: options.output },
8006
8207
  ...options.compare === void 0 ? {} : { comparePath: options.compare },
8007
8208
  ...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
8209
+ ...options.open === void 0 ? {} : { open: options.open },
8008
8210
  includeTrace: options.trace,
8009
8211
  scoringProfile: options.scoringProfile,
8010
8212
  recentWindowDays: options.recentWindowDays
8011
8213
  },
8012
8214
  logger
8013
8215
  );
8014
- if (options.output === void 0) {
8216
+ if (options.output === void 0 && options.format !== "html") {
8015
8217
  process.stdout.write(`${result.rendered}
8218
+ `);
8219
+ return;
8220
+ }
8221
+ if (options.format === "html") {
8222
+ process.stdout.write(`${result.outputPath ?? result.rendered}
8016
8223
  `);
8017
8224
  }
8018
8225
  }
@@ -8031,7 +8238,9 @@ program.command("run").argument("[path]", "path to the project to analyze").addO
8031
8238
  new Option("--format <mode>", "combined output format: text, md, json").choices(["text", "md", "json"]).default("md")
8032
8239
  ).addOption(
8033
8240
  new Option("--detail <level>", "run detail level: compact (default), standard, full").choices(["compact", "standard", "full"]).default("compact")
8034
- ).option("--file <path>", "explain a specific file target").option("--module <name>", "explain a specific module target").option("--top <count>", "number of top hotspots to explain when no target is selected", "5").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
8241
+ ).option("--file <path>", "explain a specific file target").option("--module <name>", "explain a specific module target").option("--top <count>", "number of top hotspots to explain when no target is selected", "5").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").addOption(
8242
+ new Option("--report <format>", "write an additional report bundle during the run").choices(["html"]).default(void 0)
8243
+ ).option("--report-output <path>", "output path for the generated report bundle").option("--open", "open the generated HTML report in the default browser").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
8035
8244
  new Option(
8036
8245
  "--recent-window-days <days>",
8037
8246
  "git recency window in days used for evolution volatility metrics"
@@ -8058,13 +8267,28 @@ program.command("run").argument("[path]", "path to the project to analyze").addO
8058
8267
  ...options.trace === true ? { trace: explain.trace } : {}
8059
8268
  });
8060
8269
  if (options.snapshot !== void 0) {
8061
- await writeFile6(options.snapshot, JSON.stringify(snapshot, null, 2), "utf8");
8270
+ await writeFile7(options.snapshot, JSON.stringify(snapshot, null, 2), "utf8");
8062
8271
  logger.info(`snapshot written: ${options.snapshot}`);
8063
8272
  }
8064
8273
  const report = options.compare === void 0 ? createReport(snapshot) : createReport(
8065
8274
  snapshot,
8066
- compareSnapshots(snapshot, parseSnapshot(await readFile6(options.compare, "utf8")))
8275
+ compareSnapshots(snapshot, parseSnapshot(await readFile7(options.compare, "utf8")))
8067
8276
  );
8277
+ if (options.report === "html") {
8278
+ const htmlOutputPath = await writeHtmlReportBundle(report, {
8279
+ repositoryPath: explain.summary.structural.targetPath,
8280
+ ...options.reportOutput === void 0 ? {} : { outputPath: options.reportOutput }
8281
+ });
8282
+ if (options.open === true) {
8283
+ const opened = await openPath(`${htmlOutputPath}/index.html`);
8284
+ if (!opened) {
8285
+ logger.warn("unable to open html report automatically on this platform");
8286
+ }
8287
+ }
8288
+ logger.info(`html report written: ${htmlOutputPath}`);
8289
+ } else if (options.open === true) {
8290
+ logger.warn("--open has no effect unless --report html is set");
8291
+ }
8068
8292
  if (options.format === "json") {
8069
8293
  const analyzeSummaryOutput = formatAnalyzeOutput(explain.summary, "summary");
8070
8294
  if (options.detail === "compact") {