executable-stories-formatters 0.7.14 → 0.8.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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  import "fs";
3
- import * as path8 from "path";
3
+ import * as path9 from "path";
4
4
  import * as fsPromises from "fs/promises";
5
5
 
6
6
  // src/converters/acl/status.ts
@@ -678,9 +678,299 @@ ${doc.markdown}`,
678
678
  }
679
679
  };
680
680
 
681
+ // src/converters/story-report.ts
682
+ import { posix as path2 } from "path";
683
+
684
+ // src/types/story-report.ts
685
+ var STORY_REPORT_SCHEMA_VERSION = "1.0";
686
+ var STORY_REPORT_SCHEMA_MAJOR = 1;
687
+
688
+ // src/converters/story-report.ts
689
+ function reportSlug(text2) {
690
+ return text2.toLowerCase().replace(/[/\\.]+/g, "-").replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
691
+ }
692
+ function toRelativeSourceFile(sourceFile, projectRoot) {
693
+ if (!sourceFile) return sourceFile;
694
+ const normalized = sourceFile.split(path2.sep).join("/");
695
+ const root = projectRoot.split(path2.sep).join("/").replace(/\/$/, "");
696
+ if (root && normalized.startsWith(root + "/")) return normalized.slice(root.length + 1);
697
+ return normalized;
698
+ }
699
+ function fileBasenameTitle(sourceFile) {
700
+ const base = sourceFile.split("/").pop() ?? sourceFile;
701
+ return base.replace(/\.(story\.)?(test|spec)\.[tj]sx?$/, "").replace(/\.[tj]sx?$/, "");
702
+ }
703
+ function emptySummary() {
704
+ return { total: 0, passed: 0, failed: 0, skipped: 0, pending: 0, durationMs: 0 };
705
+ }
706
+ function addToSummary(summary, status, durationMs) {
707
+ summary.total += 1;
708
+ summary[status] += 1;
709
+ summary.durationMs += durationMs;
710
+ }
711
+ function isKeyword(value) {
712
+ return value === "Given" || value === "When" || value === "Then" || value === "And" || value === "But";
713
+ }
714
+ function copyDocEntries(entries) {
715
+ if (!entries || entries.length === 0) return [];
716
+ return entries.map(copyDocEntry);
717
+ }
718
+ function copyDocEntry(entry) {
719
+ const children = entry.children ? { children: copyDocEntries(entry.children) } : {};
720
+ switch (entry.kind) {
721
+ case "note":
722
+ return { kind: "note", text: entry.text, phase: entry.phase, ...children };
723
+ case "tag":
724
+ return { kind: "tag", names: [...entry.names], phase: entry.phase, ...children };
725
+ case "kv":
726
+ return { kind: "kv", label: entry.label, value: entry.value, phase: entry.phase, ...children };
727
+ case "code":
728
+ return {
729
+ kind: "code",
730
+ label: entry.label,
731
+ content: entry.content,
732
+ ...entry.lang ? { lang: entry.lang } : {},
733
+ phase: entry.phase,
734
+ ...children
735
+ };
736
+ case "table":
737
+ return {
738
+ kind: "table",
739
+ label: entry.label,
740
+ columns: [...entry.columns],
741
+ rows: entry.rows.map((r) => [...r]),
742
+ phase: entry.phase,
743
+ ...children
744
+ };
745
+ case "link":
746
+ return { kind: "link", label: entry.label, url: entry.url, phase: entry.phase, ...children };
747
+ case "section":
748
+ return { kind: "section", title: entry.title, markdown: entry.markdown, phase: entry.phase, ...children };
749
+ case "mermaid":
750
+ return {
751
+ kind: "mermaid",
752
+ code: entry.code,
753
+ ...entry.title ? { title: entry.title } : {},
754
+ phase: entry.phase,
755
+ ...children
756
+ };
757
+ case "screenshot":
758
+ return {
759
+ kind: "screenshot",
760
+ path: entry.path,
761
+ ...entry.alt ? { alt: entry.alt } : {},
762
+ phase: entry.phase,
763
+ ...children
764
+ };
765
+ case "custom":
766
+ return {
767
+ kind: "custom",
768
+ type: entry.type,
769
+ data: entry.data,
770
+ phase: entry.phase,
771
+ ...children
772
+ };
773
+ }
774
+ }
775
+ function buildStep(args) {
776
+ const step = {
777
+ id: `${args.scenarioId}--step-${args.index}`,
778
+ index: args.index,
779
+ keyword: args.keyword,
780
+ text: args.text,
781
+ status: args.status,
782
+ durationMs: args.durationMs,
783
+ docEntries: args.docEntries
784
+ };
785
+ if (args.errorMessage !== void 0) step.errorMessage = args.errorMessage;
786
+ if (args.mode !== void 0) step.mode = args.mode;
787
+ return step;
788
+ }
789
+ function buildSteps(scenarioId, tc) {
790
+ const declared = tc.story.steps ?? [];
791
+ const results = tc.stepResults;
792
+ const max = Math.max(declared.length, results.length);
793
+ const steps = [];
794
+ for (let i = 0; i < max; i++) {
795
+ const decl = declared[i];
796
+ const res = results[i];
797
+ const keywordSource = decl?.keyword;
798
+ const keyword = keywordSource && isKeyword(keywordSource) ? keywordSource : "Given";
799
+ const text2 = decl?.text ?? "";
800
+ const status = res?.status ?? "pending";
801
+ const durationMs = res?.durationMs ?? decl?.durationMs ?? 0;
802
+ const docEntries = copyDocEntries(decl?.docs);
803
+ steps.push(buildStep({
804
+ scenarioId,
805
+ index: i,
806
+ keyword,
807
+ text: text2,
808
+ status,
809
+ durationMs,
810
+ ...res?.errorMessage !== void 0 ? { errorMessage: res.errorMessage } : {},
811
+ ...decl?.mode !== void 0 ? { mode: decl.mode } : {},
812
+ docEntries
813
+ }));
814
+ }
815
+ return steps;
816
+ }
817
+ function buildAttachments(tc) {
818
+ return tc.attachments.map((a) => ({
819
+ name: a.name,
820
+ mediaType: a.mediaType,
821
+ body: a.body,
822
+ contentEncoding: a.contentEncoding
823
+ }));
824
+ }
825
+ function buildScenario(tc, featureId) {
826
+ const titleRaw = tc.story.scenario?.trim() || "(untitled scenario)";
827
+ const id = `${featureId}--${reportSlug(titleRaw) || `case-${tc.id}`}`;
828
+ const steps = buildSteps(id, tc);
829
+ const scenario = {
830
+ id,
831
+ title: titleRaw,
832
+ status: tc.status,
833
+ durationMs: tc.durationMs,
834
+ tags: [...tc.tags],
835
+ retry: tc.retry,
836
+ retries: tc.retries,
837
+ docEntries: copyDocEntries(tc.story.docs),
838
+ steps,
839
+ attachments: buildAttachments(tc)
840
+ };
841
+ if (tc.sourceLine && tc.sourceLine > 0) scenario.sourceLine = tc.sourceLine;
842
+ if (tc.errorMessage !== void 0) scenario.errorMessage = tc.errorMessage;
843
+ if (tc.errorStack !== void 0) scenario.errorStack = tc.errorStack;
844
+ const tickets = tc.story.tickets;
845
+ if (tickets && tickets.length > 0) {
846
+ scenario.tickets = tickets.map((t) => t.url ? { id: t.id, url: t.url } : { id: t.id });
847
+ }
848
+ return scenario;
849
+ }
850
+ function deriveFeatureTitle(group, relSourceFile) {
851
+ for (const tc of group) {
852
+ const head = tc.titlePath?.[0];
853
+ if (head && head.trim()) return head.trim();
854
+ }
855
+ return fileBasenameTitle(relSourceFile);
856
+ }
857
+ function compareScenarios(a, b) {
858
+ const aLine = a.sourceLine ?? Number.POSITIVE_INFINITY;
859
+ const bLine = b.sourceLine ?? Number.POSITIVE_INFINITY;
860
+ if (aLine !== bLine) return aLine - bLine;
861
+ return a.title.localeCompare(b.title);
862
+ }
863
+ function buildFeature(relSourceFile, group) {
864
+ const id = `feature-${reportSlug(relSourceFile.replace(/\.[^.]+$/, "")) || "untitled"}`;
865
+ const title = deriveFeatureTitle(group, relSourceFile);
866
+ const summary = emptySummary();
867
+ const scenarios = [];
868
+ for (const tc of group) {
869
+ const scenario = buildScenario(tc, id);
870
+ scenarios.push(scenario);
871
+ addToSummary(summary, scenario.status, scenario.durationMs);
872
+ }
873
+ scenarios.sort(compareScenarios);
874
+ return { id, title, sourceFile: relSourceFile, summary, scenarios };
875
+ }
876
+ function ensureUniqueFeatureIds(features) {
877
+ const seen = /* @__PURE__ */ new Map();
878
+ for (const f of features) {
879
+ const count = seen.get(f.id) ?? 0;
880
+ if (count > 0) f.id = `${f.id}-${count + 1}`;
881
+ seen.set(f.id, count + 1);
882
+ }
883
+ }
884
+ function ensureUniqueScenarioIds(feature) {
885
+ const seen = /* @__PURE__ */ new Map();
886
+ for (const s of feature.scenarios) {
887
+ const count = seen.get(s.id) ?? 0;
888
+ if (count > 0) {
889
+ const newId = `${s.id}-${count + 1}`;
890
+ for (const step of s.steps) {
891
+ step.id = step.id.replace(s.id, newId);
892
+ }
893
+ s.id = newId;
894
+ }
895
+ seen.set(s.id, count + 1);
896
+ }
897
+ }
898
+ function toStoryReport(run) {
899
+ const groups = /* @__PURE__ */ new Map();
900
+ for (const tc of run.testCases) {
901
+ const rel = toRelativeSourceFile(tc.sourceFile, run.projectRoot);
902
+ const existing = groups.get(rel);
903
+ if (existing) existing.push(tc);
904
+ else groups.set(rel, [tc]);
905
+ }
906
+ const features = [];
907
+ for (const [rel, group] of groups) {
908
+ features.push(buildFeature(rel, group));
909
+ }
910
+ features.sort((a, b) => a.title.localeCompare(b.title));
911
+ ensureUniqueFeatureIds(features);
912
+ for (const f of features) ensureUniqueScenarioIds(f);
913
+ const summary = emptySummary();
914
+ for (const f of features) {
915
+ summary.total += f.summary.total;
916
+ summary.passed += f.summary.passed;
917
+ summary.failed += f.summary.failed;
918
+ summary.skipped += f.summary.skipped;
919
+ summary.pending += f.summary.pending;
920
+ summary.durationMs += f.summary.durationMs;
921
+ }
922
+ const report = {
923
+ schemaVersion: STORY_REPORT_SCHEMA_VERSION,
924
+ runId: run.runId,
925
+ startedAtMs: run.startedAtMs,
926
+ finishedAtMs: run.finishedAtMs,
927
+ durationMs: run.durationMs,
928
+ projectRoot: run.projectRoot,
929
+ summary,
930
+ features
931
+ };
932
+ if (run.packageVersion) report.packageVersion = run.packageVersion;
933
+ if (run.gitSha) report.gitSha = run.gitSha;
934
+ if (run.ci) {
935
+ const ci = { name: run.ci.name };
936
+ if (run.ci.url) ci.url = run.ci.url;
937
+ if (run.ci.buildNumber) ci.buildNumber = run.ci.buildNumber;
938
+ if (run.ci.branch) ci.branch = run.ci.branch;
939
+ if (run.ci.commitSha) ci.commitSha = run.ci.commitSha;
940
+ if (run.ci.prNumber) ci.prNumber = run.ci.prNumber;
941
+ report.ci = ci;
942
+ }
943
+ if (run.coverage) {
944
+ const cov = {};
945
+ if (run.coverage.linesPct !== void 0) cov.linesPct = run.coverage.linesPct;
946
+ if (run.coverage.branchesPct !== void 0) cov.branchesPct = run.coverage.branchesPct;
947
+ if (run.coverage.functionsPct !== void 0) cov.functionsPct = run.coverage.functionsPct;
948
+ if (run.coverage.statementsPct !== void 0) cov.statementsPct = run.coverage.statementsPct;
949
+ report.coverage = cov;
950
+ }
951
+ return report;
952
+ }
953
+
954
+ // src/formatters/story-report-json.ts
955
+ var StoryReportJsonFormatter = class {
956
+ options;
957
+ constructor(options = {}) {
958
+ this.options = {
959
+ pretty: options.pretty ?? true
960
+ };
961
+ }
962
+ toReport(run) {
963
+ return toStoryReport(run);
964
+ }
965
+ format(run) {
966
+ const report = toStoryReport(run);
967
+ return this.options.pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
968
+ }
969
+ };
970
+
681
971
  // src/formatters/html/renderers/index.ts
682
972
  import * as fs2 from "fs";
683
- import * as path2 from "path";
973
+ import * as path3 from "path";
684
974
 
685
975
  // src/formatters/html/template.ts
686
976
  var JS_THEME = `
@@ -1031,11 +1321,53 @@ function initKeyboardShortcuts() {
1031
1321
  });
1032
1322
  }
1033
1323
 
1034
- // Collapse/expand functionality
1324
+ // Collapse/expand functionality (persisted in localStorage)
1325
+ var COLLAPSE_KEY = 'es-collapsed-ids';
1326
+
1327
+ function loadCollapseState() {
1328
+ try {
1329
+ var raw = localStorage.getItem(COLLAPSE_KEY);
1330
+ return raw ? new Set(JSON.parse(raw)) : new Set();
1331
+ } catch (e) {
1332
+ return new Set();
1333
+ }
1334
+ }
1335
+
1336
+ function saveCollapseState(set) {
1337
+ try {
1338
+ localStorage.setItem(COLLAPSE_KEY, JSON.stringify(Array.from(set)));
1339
+ } catch (e) { /* quota or disabled */ }
1340
+ }
1341
+
1342
+ function persistCollapse(container) {
1343
+ if (!container || !container.id) return;
1344
+ var state = loadCollapseState();
1345
+ if (container.classList.contains('collapsed')) {
1346
+ state.add(container.id);
1347
+ } else {
1348
+ state.delete(container.id);
1349
+ }
1350
+ saveCollapseState(state);
1351
+ }
1352
+
1035
1353
  function toggleCollapse(header, container) {
1036
1354
  container?.classList.toggle('collapsed');
1037
1355
  const isCollapsed = container?.classList.contains('collapsed');
1038
1356
  header.setAttribute('aria-expanded', !isCollapsed);
1357
+ persistCollapse(container);
1358
+ }
1359
+
1360
+ function restoreCollapseState() {
1361
+ var state = loadCollapseState();
1362
+ if (state.size === 0) return;
1363
+ state.forEach(function(id) {
1364
+ var el = document.getElementById(id);
1365
+ if (!el) return;
1366
+ if (!el.classList.contains('feature') && !el.classList.contains('scenario')) return;
1367
+ el.classList.add('collapsed');
1368
+ var header = el.querySelector('.feature-header, .scenario-header');
1369
+ if (header) header.setAttribute('aria-expanded', 'false');
1370
+ });
1039
1371
  }
1040
1372
 
1041
1373
  function initCollapse() {
@@ -1082,14 +1414,20 @@ function expandAll() {
1082
1414
  const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
1083
1415
  header?.setAttribute('aria-expanded', 'true');
1084
1416
  });
1417
+ saveCollapseState(new Set());
1085
1418
  }
1086
1419
 
1087
1420
  function collapseAll() {
1421
+ var ids = new Set();
1088
1422
  document.querySelectorAll('.feature, .scenario, .trace-view').forEach(el => {
1089
1423
  el.classList.add('collapsed');
1090
1424
  const header = el.querySelector('.feature-header, .scenario-header, .trace-view-header');
1091
1425
  header?.setAttribute('aria-expanded', 'false');
1426
+ if (el.id && (el.classList.contains('feature') || el.classList.contains('scenario'))) {
1427
+ ids.add(el.id);
1428
+ }
1092
1429
  });
1430
+ saveCollapseState(ids);
1093
1431
  }
1094
1432
 
1095
1433
  // Detail level toggle
@@ -1216,6 +1554,68 @@ function copyScenarioAsMarkdown(scenarioId) {
1216
1554
  });
1217
1555
  }
1218
1556
 
1557
+ // Copy scenario as Claude-ready prompt (failure investigation context)
1558
+ function copyScenarioAsPrompt(scenarioId) {
1559
+ var scenario = document.getElementById(scenarioId);
1560
+ if (!scenario) return;
1561
+
1562
+ var feature = scenario.closest('.feature');
1563
+ var featureTitle = feature ? (feature.querySelector('.feature-title') || {}).textContent || '' : '';
1564
+ var title = (scenario.querySelector('.scenario-name') || {}).textContent || '';
1565
+ var statusEl = scenario.querySelector('.status-icon');
1566
+ var status = statusEl && statusEl.classList.contains('status-passed') ? 'passed' :
1567
+ statusEl && statusEl.classList.contains('status-failed') ? 'failed' :
1568
+ statusEl && statusEl.classList.contains('status-skipped') ? 'skipped' : 'pending';
1569
+ var sourceLink = scenario.querySelector('.source-link');
1570
+ var source = sourceLink ? sourceLink.textContent || '' : '';
1571
+ var tags = Array.from(scenario.querySelectorAll('.scenario-meta .tag')).map(function(t) { return t.textContent.trim(); });
1572
+ var steps = scenario.querySelectorAll('.step, .step.continuation');
1573
+
1574
+ var lines = [];
1575
+ lines.push('I need help investigating a failing executable-story scenario.');
1576
+ lines.push('');
1577
+ if (featureTitle.trim()) lines.push('Feature: ' + featureTitle.trim());
1578
+ lines.push('Scenario: ' + title.trim());
1579
+ lines.push('Status: ' + status);
1580
+ if (source.trim()) lines.push('Source: ' + source.trim());
1581
+ if (tags.length > 0) lines.push('Tags: ' + tags.join(', '));
1582
+ lines.push('');
1583
+ lines.push('Steps:');
1584
+ steps.forEach(function(step) {
1585
+ var keyword = step.getAttribute('data-keyword') || '';
1586
+ var text = step.getAttribute('data-text') || '';
1587
+ var stepStatusEl = step.querySelector('.step-status');
1588
+ var marker = ' ';
1589
+ if (stepStatusEl) {
1590
+ if (stepStatusEl.classList.contains('status-failed')) marker = 'x ';
1591
+ else if (stepStatusEl.classList.contains('status-passed')) marker = '+ ';
1592
+ else if (stepStatusEl.classList.contains('status-skipped')) marker = '- ';
1593
+ }
1594
+ lines.push(marker + keyword + ' ' + text);
1595
+ });
1596
+
1597
+ var errorBox = scenario.querySelector('.error-message');
1598
+ if (errorBox) {
1599
+ lines.push('');
1600
+ lines.push('Error:');
1601
+ lines.push((errorBox.textContent || '').trim());
1602
+ }
1603
+ var stackBox = scenario.querySelector('.error-stack');
1604
+ if (stackBox) {
1605
+ lines.push('');
1606
+ lines.push('Stack:');
1607
+ lines.push((stackBox.textContent || '').trim());
1608
+ }
1609
+
1610
+ lines.push('');
1611
+ lines.push('Please read the source file, identify the root cause, and propose a fix.');
1612
+
1613
+ var text = lines.join('\\n');
1614
+ navigator.clipboard.writeText(text).then(function() {
1615
+ showCopyToast(scenario);
1616
+ });
1617
+ }
1618
+
1219
1619
  // Hash scroll on load
1220
1620
  function initHashScroll() {
1221
1621
  if (!location.hash) return;
@@ -1385,6 +1785,7 @@ function generateScript(options) {
1385
1785
  initCalls.push("initStatusFilter();");
1386
1786
  initCalls.push("initKeyboardShortcuts();");
1387
1787
  initCalls.push("initCollapse();");
1788
+ initCalls.push("restoreCollapseState();");
1388
1789
  initCalls.push("initDetailLevel();");
1389
1790
  initCalls.push("applyAllFilters();");
1390
1791
  initCalls.push("initHashScroll();");
@@ -1489,6 +1890,106 @@ function escapeHtml(str) {
1489
1890
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1490
1891
  }
1491
1892
 
1893
+ // src/theme/tokens.ts
1894
+ var ES_THEME_TOKENS_CSS = `
1895
+ :root,
1896
+ [data-theme="light"] {
1897
+ --es-color-bg: #ffffff;
1898
+ --es-color-fg: #111827;
1899
+ --es-color-muted: #6b7280;
1900
+ --es-color-border: #e5e7eb;
1901
+ --es-color-surface: #f9fafb;
1902
+ --es-color-link: #2563eb;
1903
+ --es-color-passed: #16a34a;
1904
+ --es-color-failed: #dc2626;
1905
+ --es-color-skipped: #9ca3af;
1906
+ --es-color-pending: #d97706;
1907
+ --es-color-passed-bg: #f0fdf4;
1908
+ --es-color-failed-bg: #fef2f2;
1909
+ --es-color-skipped-bg: #f3f4f6;
1910
+ --es-color-pending-bg: #fffbeb;
1911
+ --es-font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
1912
+ --es-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
1913
+ --es-size-base: 1rem;
1914
+ --es-size-sm: 0.875rem;
1915
+ --es-size-xs: 0.75rem;
1916
+ --es-size-h1: 1.875rem;
1917
+ --es-size-h2: 1.5rem;
1918
+ --es-size-h3: 1.25rem;
1919
+ --es-space-1: 0.25rem;
1920
+ --es-space-2: 0.5rem;
1921
+ --es-space-3: 0.75rem;
1922
+ --es-space-4: 1rem;
1923
+ --es-space-6: 1.5rem;
1924
+ --es-space-8: 2rem;
1925
+ --es-radius: 0.5rem;
1926
+ --es-line: 1.6;
1927
+ --es-measure: 72ch;
1928
+ }
1929
+
1930
+ @media (prefers-color-scheme: dark) {
1931
+ :root {
1932
+ --es-color-bg: #0b0f17;
1933
+ --es-color-fg: #e5e7eb;
1934
+ --es-color-muted: #9ca3af;
1935
+ --es-color-border: #1f2937;
1936
+ --es-color-surface: #111827;
1937
+ --es-color-link: #60a5fa;
1938
+ --es-color-passed: #4ade80;
1939
+ --es-color-failed: #f87171;
1940
+ --es-color-skipped: #6b7280;
1941
+ --es-color-pending: #fbbf24;
1942
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
1943
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
1944
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
1945
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
1946
+ }
1947
+ }
1948
+
1949
+ [data-theme="dark"] {
1950
+ --es-color-bg: #0b0f17;
1951
+ --es-color-fg: #e5e7eb;
1952
+ --es-color-muted: #9ca3af;
1953
+ --es-color-border: #1f2937;
1954
+ --es-color-surface: #111827;
1955
+ --es-color-link: #60a5fa;
1956
+ --es-color-passed: #4ade80;
1957
+ --es-color-failed: #f87171;
1958
+ --es-color-skipped: #6b7280;
1959
+ --es-color-pending: #fbbf24;
1960
+ --es-color-passed-bg: rgba(74, 222, 128, 0.08);
1961
+ --es-color-failed-bg: rgba(248, 113, 113, 0.08);
1962
+ --es-color-skipped-bg: rgba(107, 114, 128, 0.08);
1963
+ --es-color-pending-bg: rgba(251, 191, 36, 0.08);
1964
+ }
1965
+ `.trim();
1966
+ var ES_THEME_TOKEN_VALUES = {
1967
+ light: {
1968
+ "--es-color-bg": "#ffffff",
1969
+ "--es-color-fg": "#111827",
1970
+ "--es-color-muted": "#6b7280",
1971
+ "--es-color-border": "#e5e7eb",
1972
+ "--es-color-surface": "#f9fafb",
1973
+ "--es-color-link": "#2563eb",
1974
+ "--es-color-passed": "#16a34a",
1975
+ "--es-color-failed": "#dc2626",
1976
+ "--es-color-skipped": "#9ca3af",
1977
+ "--es-color-pending": "#d97706"
1978
+ },
1979
+ dark: {
1980
+ "--es-color-bg": "#0b0f17",
1981
+ "--es-color-fg": "#e5e7eb",
1982
+ "--es-color-muted": "#9ca3af",
1983
+ "--es-color-border": "#1f2937",
1984
+ "--es-color-surface": "#111827",
1985
+ "--es-color-link": "#60a5fa",
1986
+ "--es-color-passed": "#4ade80",
1987
+ "--es-color-failed": "#f87171",
1988
+ "--es-color-skipped": "#6b7280",
1989
+ "--es-color-pending": "#fbbf24"
1990
+ }
1991
+ };
1992
+
1492
1993
  // src/formatters/html/styles.ts
1493
1994
  var CSS_STYLES = `
1494
1995
  /* ============================================================================
@@ -1496,6 +1997,14 @@ var CSS_STYLES = `
1496
1997
  ============================================================================ */
1497
1998
  @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
1498
1999
 
2000
+ /* ============================================================================
2001
+ executable-stories canonical tokens (--es-*).
2002
+ Shared with executable-stories-react. Override on :root or any ancestor of
2003
+ the report to re-color both the standalone HTML and the React component.
2004
+ ============================================================================ */
2005
+ ${ES_THEME_TOKENS_CSS}
2006
+
2007
+
1499
2008
  /* ============================================================================
1500
2009
  CSS Custom Properties - Light Mode (Default)
1501
2010
  Cucumber-branded shadcn/ui base theme
@@ -3384,6 +3893,33 @@ body {
3384
3893
  color: var(--primary);
3385
3894
  }
3386
3895
 
3896
+ .copy-prompt-btn {
3897
+ display: inline-flex;
3898
+ align-items: center;
3899
+ justify-content: center;
3900
+ width: 1.5rem;
3901
+ height: 1.5rem;
3902
+ border: none;
3903
+ background: none;
3904
+ color: var(--muted-foreground);
3905
+ cursor: pointer;
3906
+ opacity: 0.6;
3907
+ transition: opacity 0.15s ease, transform 0.15s ease;
3908
+ font-size: 0.95rem;
3909
+ padding: 0;
3910
+ flex-shrink: 0;
3911
+ }
3912
+
3913
+ .scenario-header:hover .copy-prompt-btn,
3914
+ .copy-prompt-btn:focus-visible {
3915
+ opacity: 1;
3916
+ }
3917
+
3918
+ .copy-prompt-btn:hover {
3919
+ color: var(--primary);
3920
+ transform: scale(1.15);
3921
+ }
3922
+
3387
3923
  /* ============================================================================
3388
3924
  Keyboard Navigation
3389
3925
  ============================================================================ */
@@ -3621,6 +4157,82 @@ a.toc-title:hover {
3621
4157
  outline-offset: 2px;
3622
4158
  }
3623
4159
 
4160
+ /* ============================================================================
4161
+ Mobile responsive refinements
4162
+ ============================================================================ */
4163
+ @media (max-width: 640px) {
4164
+ .container {
4165
+ padding: 0.875rem;
4166
+ }
4167
+
4168
+ .header {
4169
+ flex-direction: column;
4170
+ align-items: stretch;
4171
+ gap: 0.75rem;
4172
+ }
4173
+
4174
+ .header-actions {
4175
+ flex-wrap: wrap;
4176
+ gap: 0.5rem;
4177
+ }
4178
+
4179
+ .search-input {
4180
+ width: 100%;
4181
+ flex: 1 1 100%;
4182
+ min-width: 0;
4183
+ }
4184
+
4185
+ .header h1 {
4186
+ font-size: 1.25rem;
4187
+ }
4188
+
4189
+ .scenario-header,
4190
+ .feature-header {
4191
+ flex-wrap: wrap;
4192
+ gap: 0.5rem;
4193
+ }
4194
+
4195
+ .scenario-meta {
4196
+ flex-wrap: wrap;
4197
+ }
4198
+
4199
+ .scenario-actions {
4200
+ flex-wrap: wrap;
4201
+ }
4202
+
4203
+ /* Always-visible action buttons on touch (no hover) */
4204
+ .copy-scenario-btn,
4205
+ .copy-prompt-btn,
4206
+ .permalink-anchor {
4207
+ opacity: 1 !important;
4208
+ }
4209
+
4210
+ .summary-card {
4211
+ padding: 0.75rem 0.875rem;
4212
+ }
4213
+
4214
+ .summary-card .value {
4215
+ font-size: 1.5rem;
4216
+ }
4217
+
4218
+ .tag-bar {
4219
+ overflow-x: auto;
4220
+ -webkit-overflow-scrolling: touch;
4221
+ }
4222
+
4223
+ .shortcuts-overlay {
4224
+ padding: 1rem;
4225
+ }
4226
+ }
4227
+
4228
+ @media (hover: none) and (pointer: coarse) {
4229
+ .copy-scenario-btn,
4230
+ .copy-prompt-btn,
4231
+ .permalink-anchor {
4232
+ opacity: 1;
4233
+ }
4234
+ }
4235
+
3624
4236
  `;
3625
4237
 
3626
4238
  // src/formatters/html/themes/default.ts
@@ -13347,6 +13959,7 @@ function renderScenario(args, deps) {
13347
13959
  </div>
13348
13960
  <div class="scenario-actions">
13349
13961
  <button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
13962
+ ${tc.status === "failed" ? `<button class="copy-prompt-btn" onclick="copyScenarioAsPrompt('scenario-${tc.id}')" aria-label="Copy as Claude prompt" title="Copy as AI prompt">\u2728</button>` : ""}
13350
13963
  <button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
13351
13964
  <span class="scenario-duration">${duration}</span>
13352
13965
  </div>
@@ -13746,7 +14359,7 @@ var SCREENSHOT_MIME_BY_EXT = {
13746
14359
  };
13747
14360
  function readScreenshotAsDataUri(filePath) {
13748
14361
  try {
13749
- const ext = path2.extname(filePath).slice(1).toLowerCase();
14362
+ const ext = path3.extname(filePath).slice(1).toLowerCase();
13750
14363
  const mime = SCREENSHOT_MIME_BY_EXT[ext];
13751
14364
  if (!mime) return void 0;
13752
14365
  if (!fs2.existsSync(filePath)) return void 0;
@@ -15223,8 +15836,8 @@ function extractDocAttachments(step) {
15223
15836
  }
15224
15837
  return attachments;
15225
15838
  }
15226
- function guessMediaType(path9) {
15227
- const lower = path9.toLowerCase();
15839
+ function guessMediaType(path10) {
15840
+ const lower = path10.toLowerCase();
15228
15841
  if (lower.endsWith(".png")) return "image/png";
15229
15842
  if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
15230
15843
  if (lower.endsWith(".gif")) return "image/gif";
@@ -16269,7 +16882,7 @@ function selectTestCases(args, deps) {
16269
16882
 
16270
16883
  // src/bundler/bundle-assets.ts
16271
16884
  import * as fs4 from "fs";
16272
- import * as path4 from "path";
16885
+ import * as path5 from "path";
16273
16886
 
16274
16887
  // src/bundler/scan-html-assets.ts
16275
16888
  function scanHtmlAssets(html) {
@@ -16300,7 +16913,7 @@ function isLocalAssetRef(ref) {
16300
16913
 
16301
16914
  // src/bundler/copy-asset.ts
16302
16915
  import * as fs3 from "fs";
16303
- import * as path3 from "path";
16916
+ import * as path4 from "path";
16304
16917
  import * as crypto from "crypto";
16305
16918
  function copyAsset(sourcePath, assetsDir) {
16306
16919
  if (!fs3.existsSync(assetsDir)) {
@@ -16308,10 +16921,10 @@ function copyAsset(sourcePath, assetsDir) {
16308
16921
  }
16309
16922
  const content = fs3.readFileSync(sourcePath);
16310
16923
  const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
16311
- const ext = path3.extname(sourcePath);
16312
- const baseName = sanitize(path3.basename(sourcePath, ext));
16924
+ const ext = path4.extname(sourcePath);
16925
+ const baseName = sanitize(path4.basename(sourcePath, ext));
16313
16926
  const destName = `${baseName}-${hash}${ext}`;
16314
- const destPath = path3.join(assetsDir, destName);
16927
+ const destPath = path4.join(assetsDir, destName);
16315
16928
  if (!fs3.existsSync(destPath)) {
16316
16929
  fs3.copyFileSync(sourcePath, destPath);
16317
16930
  }
@@ -16323,14 +16936,14 @@ function sanitize(name) {
16323
16936
 
16324
16937
  // src/bundler/bundle-assets.ts
16325
16938
  function bundleAssets(htmlPath, options = {}) {
16326
- const htmlDir = path4.dirname(htmlPath);
16327
- const assetsDir = path4.join(htmlDir, "assets");
16939
+ const htmlDir = path5.dirname(htmlPath);
16940
+ const assetsDir = path5.join(htmlDir, "assets");
16328
16941
  let html = fs4.readFileSync(htmlPath, "utf8");
16329
16942
  const refs = scanHtmlAssets(html);
16330
16943
  let copiedCount = 0;
16331
16944
  const missing = [];
16332
16945
  for (const ref of refs) {
16333
- const absolutePath = path4.resolve(htmlDir, ref);
16946
+ const absolutePath = path5.resolve(htmlDir, ref);
16334
16947
  if (!fs4.existsSync(absolutePath)) {
16335
16948
  missing.push(ref);
16336
16949
  continue;
@@ -16828,14 +17441,14 @@ function groupBy7(items, keyFn) {
16828
17441
 
16829
17442
  // src/formatters/astro-assets.ts
16830
17443
  import * as fs5 from "fs";
16831
- import * as path5 from "path";
17444
+ import * as path6 from "path";
16832
17445
  var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
16833
17446
  function isLocalPath(src) {
16834
17447
  const trimmed = src.trim();
16835
17448
  if (SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix))) {
16836
17449
  return false;
16837
17450
  }
16838
- return !path5.posix.isAbsolute(trimmed) && !path5.win32.isAbsolute(trimmed);
17451
+ return !path6.posix.isAbsolute(trimmed) && !path6.win32.isAbsolute(trimmed);
16839
17452
  }
16840
17453
  function stripCodeContent(markdown) {
16841
17454
  let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
@@ -16929,7 +17542,7 @@ function copyMarkdownAssets(options) {
16929
17542
  const pathMap = /* @__PURE__ */ new Map();
16930
17543
  const missing = [];
16931
17544
  for (const ref of refs) {
16932
- const absPath = path5.resolve(markdownDir, ref);
17545
+ const absPath = path6.resolve(markdownDir, ref);
16933
17546
  if (!fs5.existsSync(absPath)) {
16934
17547
  if (!allowMissing) {
16935
17548
  throw new Error(`Asset not found: ${absPath}`);
@@ -17954,24 +18567,24 @@ function pickleStepArgumentToDocs(ps) {
17954
18567
 
17955
18568
  // src/utils/git-info.ts
17956
18569
  import * as fs6 from "fs";
17957
- import * as path6 from "path";
18570
+ import * as path7 from "path";
17958
18571
  function readGitSha(cwd = process.cwd()) {
17959
18572
  const envSha = process.env.GITHUB_SHA || process.env.GIT_COMMIT || process.env.CI_COMMIT_SHA;
17960
18573
  if (envSha) return envSha;
17961
18574
  const gitDir = findGitDir(cwd);
17962
18575
  if (!gitDir) return void 0;
17963
18576
  try {
17964
- const headPath = path6.join(gitDir, "HEAD");
18577
+ const headPath = path7.join(gitDir, "HEAD");
17965
18578
  const head = fs6.readFileSync(headPath, "utf8").trim();
17966
18579
  if (!head.startsWith("ref:")) {
17967
18580
  return head;
17968
18581
  }
17969
18582
  const refPath = head.replace("ref:", "").trim();
17970
- const refFile = path6.join(gitDir, refPath);
18583
+ const refFile = path7.join(gitDir, refPath);
17971
18584
  if (fs6.existsSync(refFile)) {
17972
18585
  return fs6.readFileSync(refFile, "utf8").trim();
17973
18586
  }
17974
- const packedRefs = path6.join(gitDir, "packed-refs");
18587
+ const packedRefs = path7.join(gitDir, "packed-refs");
17975
18588
  if (fs6.existsSync(packedRefs)) {
17976
18589
  const content = fs6.readFileSync(packedRefs, "utf8");
17977
18590
  for (const line of content.split("\n")) {
@@ -17988,19 +18601,19 @@ function readGitSha(cwd = process.cwd()) {
17988
18601
  function findGitDir(start) {
17989
18602
  let current = start;
17990
18603
  while (true) {
17991
- const candidate = path6.join(current, ".git");
18604
+ const candidate = path7.join(current, ".git");
17992
18605
  if (fs6.existsSync(candidate)) {
17993
18606
  const stat = fs6.statSync(candidate);
17994
18607
  if (stat.isFile()) {
17995
18608
  const content = fs6.readFileSync(candidate, "utf8").trim();
17996
18609
  const match = content.match(/^gitdir: (.+)$/);
17997
18610
  if (match) {
17998
- return path6.resolve(current, match[1]);
18611
+ return path7.resolve(current, match[1]);
17999
18612
  }
18000
18613
  }
18001
18614
  return candidate;
18002
18615
  }
18003
- const parent = path6.dirname(current);
18616
+ const parent = path7.dirname(current);
18004
18617
  if (parent === current) return void 0;
18005
18618
  current = parent;
18006
18619
  }
@@ -18011,7 +18624,7 @@ function readBranchName(cwd = process.cwd()) {
18011
18624
  const gitDir = findGitDir(cwd);
18012
18625
  if (!gitDir) return void 0;
18013
18626
  try {
18014
- const headPath = path6.join(gitDir, "HEAD");
18627
+ const headPath = path7.join(gitDir, "HEAD");
18015
18628
  const head = fs6.readFileSync(headPath, "utf8").trim();
18016
18629
  if (head.startsWith("ref:")) {
18017
18630
  const refPath = head.replace("ref:", "").trim();
@@ -18050,7 +18663,7 @@ function nanosecondsToMs(ns) {
18050
18663
 
18051
18664
  // src/utils/metadata.ts
18052
18665
  import * as fs7 from "fs";
18053
- import * as path7 from "path";
18666
+ import * as path8 from "path";
18054
18667
  var versionCache = /* @__PURE__ */ new Map();
18055
18668
  function readPackageVersion(root) {
18056
18669
  if (versionCache.has(root)) {
@@ -18061,9 +18674,9 @@ function readPackageVersion(root) {
18061
18674
  return version;
18062
18675
  }
18063
18676
  function findPackageVersion(startDir) {
18064
- let current = path7.resolve(startDir);
18677
+ let current = path8.resolve(startDir);
18065
18678
  while (true) {
18066
- const pkgPath = path7.join(current, "package.json");
18679
+ const pkgPath = path8.join(current, "package.json");
18067
18680
  try {
18068
18681
  if (fs7.existsSync(pkgPath)) {
18069
18682
  const raw = fs7.readFileSync(pkgPath, "utf8");
@@ -18072,7 +18685,7 @@ function findPackageVersion(startDir) {
18072
18685
  }
18073
18686
  } catch {
18074
18687
  }
18075
- const parent = path7.dirname(current);
18688
+ const parent = path8.dirname(current);
18076
18689
  if (parent === current) {
18077
18690
  return void 0;
18078
18691
  }
@@ -19030,7 +19643,8 @@ var FORMAT_EXTENSIONS = {
19030
19643
  junit: ".junit.xml",
19031
19644
  "cucumber-json": ".cucumber.json",
19032
19645
  "cucumber-messages": ".ndjson",
19033
- confluence: ".adf.json"
19646
+ confluence: ".adf.json",
19647
+ "story-report-json": ".story-report.json"
19034
19648
  };
19035
19649
  var TEST_EXTENSIONS = [
19036
19650
  ".test.ts",
@@ -19057,11 +19671,11 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
19057
19671
  const ext = FORMAT_EXTENSIONS[format];
19058
19672
  const effectiveName = outputName + (outputNameSuffix ?? "");
19059
19673
  if (mode === "aggregated") {
19060
- return toPosix(path8.join(baseOutputDir, `${effectiveName}${ext}`));
19674
+ return toPosix(path9.join(baseOutputDir, `${effectiveName}${ext}`));
19061
19675
  }
19062
19676
  const normalizedSource = toPosix(sourceFile);
19063
- const dirOfSource = path8.posix.dirname(normalizedSource);
19064
- let baseName = path8.posix.basename(normalizedSource);
19677
+ const dirOfSource = path9.posix.dirname(normalizedSource);
19678
+ let baseName = path9.posix.basename(normalizedSource);
19065
19679
  for (const testExt of TEST_EXTENSIONS) {
19066
19680
  if (baseName.endsWith(testExt)) {
19067
19681
  baseName = baseName.slice(0, -testExt.length);
@@ -19070,9 +19684,9 @@ function computeOutputPath(sourceFile, format, mode, colocatedStyle, baseOutputD
19070
19684
  }
19071
19685
  const fileName = `${baseName}.${effectiveName}${ext}`;
19072
19686
  if (colocatedStyle === "adjacent") {
19073
- return toPosix(path8.posix.join(dirOfSource, fileName));
19687
+ return toPosix(path9.posix.join(dirOfSource, fileName));
19074
19688
  }
19075
- return toPosix(path8.posix.join(baseOutputDir, dirOfSource, fileName));
19689
+ return toPosix(path9.posix.join(baseOutputDir, dirOfSource, fileName));
19076
19690
  }
19077
19691
  function groupTestCasesByOutput(testCases, format, options, logger, outputNameSuffix) {
19078
19692
  const groups = /* @__PURE__ */ new Map();
@@ -19141,7 +19755,7 @@ var ReportGenerator = class {
19141
19755
  exclude: options.exclude ?? [],
19142
19756
  includeTags: options.includeTags ?? [],
19143
19757
  excludeTags: options.excludeTags ?? [],
19144
- formats: options.formats ?? ["cucumber-json"],
19758
+ formats: options.formats ?? ["html"],
19145
19759
  outputDir: options.outputDir ?? "reports",
19146
19760
  outputName: options.outputName ?? "index",
19147
19761
  outputNameTimestamp: options.outputNameTimestamp ?? false,
@@ -19155,6 +19769,9 @@ var ReportGenerator = class {
19155
19769
  cucumberJson: {
19156
19770
  pretty: options.cucumberJson?.pretty ?? false
19157
19771
  },
19772
+ storyReportJson: {
19773
+ pretty: options.storyReportJson?.pretty ?? true
19774
+ },
19158
19775
  cucumberMessages: {
19159
19776
  uriStrategy: options.cucumberMessages?.uriStrategy ?? "sourceFile",
19160
19777
  includeSynthetics: options.cucumberMessages?.includeSynthetics ?? true,
@@ -19270,8 +19887,8 @@ var ReportGenerator = class {
19270
19887
  if (astroPaths) {
19271
19888
  for (const mdPath of astroPaths) {
19272
19889
  const content = await fsPromises.readFile(mdPath, "utf8");
19273
- const mdDir = path8.dirname(mdPath);
19274
- const assetsDir = path8.resolve(this.options.astro.assetsDir);
19890
+ const mdDir = path9.dirname(mdPath);
19891
+ const assetsDir = path9.resolve(this.options.astro.assetsDir);
19275
19892
  const result = copyMarkdownAssets({
19276
19893
  markdown: content,
19277
19894
  markdownDir: mdDir,
@@ -19302,9 +19919,9 @@ var ReportGenerator = class {
19302
19919
  if (groups.size === 0 && this.options.output.mode === "aggregated") {
19303
19920
  const ext = FORMAT_EXTENSIONS[format];
19304
19921
  const effectiveName = this.options.outputName + (outputNameSuffix ?? "");
19305
- const outputPath = toPosix(path8.join(this.options.outputDir, `${effectiveName}${ext}`));
19922
+ const outputPath = toPosix(path9.join(this.options.outputDir, `${effectiveName}${ext}`));
19306
19923
  const content = await this.formatContent(run, format);
19307
- const dir = path8.dirname(outputPath);
19924
+ const dir = path9.dirname(outputPath);
19308
19925
  await fsPromises.mkdir(dir, { recursive: true });
19309
19926
  await this.deps.writeFile(outputPath, content);
19310
19927
  return [outputPath];
@@ -19316,7 +19933,7 @@ var ReportGenerator = class {
19316
19933
  testCases
19317
19934
  };
19318
19935
  const content = await this.formatContent(groupRun, format);
19319
- const dir = path8.dirname(outputPath);
19936
+ const dir = path9.dirname(outputPath);
19320
19937
  await fsPromises.mkdir(dir, { recursive: true });
19321
19938
  await this.deps.writeFile(outputPath, content);
19322
19939
  writtenPaths.push(outputPath);
@@ -19423,6 +20040,12 @@ var ReportGenerator = class {
19423
20040
  });
19424
20041
  return formatter.format(run);
19425
20042
  }
20043
+ case "story-report-json": {
20044
+ const formatter = new StoryReportJsonFormatter({
20045
+ pretty: this.options.storyReportJson.pretty
20046
+ });
20047
+ return formatter.format(run);
20048
+ }
19426
20049
  default:
19427
20050
  throw new Error(`Unknown format: ${format}`);
19428
20051
  }
@@ -19439,7 +20062,7 @@ async function generateRunComparison(args) {
19439
20062
  await fsPromises.mkdir(outputDir, { recursive: true });
19440
20063
  for (const format of args.formats) {
19441
20064
  const ext = format === "html" ? ".html" : ".md";
19442
- const outputPath = toPosix(path8.join(outputDir, `${outputName}${ext}`));
20065
+ const outputPath = toPosix(path9.join(outputDir, `${outputName}${ext}`));
19443
20066
  const content = format === "html" ? new RunDiffHtmlFormatter({ title: args.title }).format(diff) : new RunDiffMarkdownFormatter({ title: args.title }).format(diff);
19444
20067
  await fsPromises.writeFile(outputPath, content, "utf8");
19445
20068
  files.push(outputPath);
@@ -19464,6 +20087,8 @@ export {
19464
20087
  CucumberHtmlFormatter,
19465
20088
  CucumberJsonFormatter,
19466
20089
  CucumberMessagesFormatter,
20090
+ ES_THEME_TOKENS_CSS,
20091
+ ES_THEME_TOKEN_VALUES,
19467
20092
  HtmlFormatter,
19468
20093
  JUnitFormatter,
19469
20094
  MIN_FLAKINESS_SAMPLES,
@@ -19474,6 +20099,9 @@ export {
19474
20099
  RunDiffHtmlFormatter,
19475
20100
  RunDiffMarkdownFormatter,
19476
20101
  STORY_META_KEY,
20102
+ STORY_REPORT_SCHEMA_MAJOR,
20103
+ STORY_REPORT_SCHEMA_VERSION,
20104
+ StoryReportJsonFormatter,
19477
20105
  adaptJestRun,
19478
20106
  adaptPlaywrightRun,
19479
20107
  adaptVitestRun,
@@ -19530,6 +20158,7 @@ export {
19530
20158
  stripAnsi,
19531
20159
  toCIInfo,
19532
20160
  toRawCIInfo,
20161
+ toStoryReport,
19533
20162
  tryGetActiveOtelContext,
19534
20163
  updateHistory,
19535
20164
  validateCanonicalRun