executable-stories-formatters 0.12.0 → 0.13.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.
@@ -625,4 +625,4 @@ interface PlaywrightAdapterOptions {
625
625
  */
626
626
  declare function adaptPlaywrightRun(testResults: Array<[PlaywrightTestCase, PlaywrightTestResult]>, options?: PlaywrightAdapterOptions): RawRun;
627
627
 
628
- export { type Attachment as A, STORY_META_KEY as B, type CIInfo$1 as C, type DocEntry as D, type StepKeyword as E, type StepMode as F, type StoryFileReport as G, type StoryMeta as H, type TestCaseAttempt as I, type JestAdapterOptions as J, type TestCaseEvidence as K, type VitestSerializedError as L, type VitestState as M, type NormalizedTicket as N, type OtelSpan as O, type PlaywrightAdapterOptions as P, type VitestTestCase as Q, type RawStatus as R, type StoryStep as S, type TestRunResult as T, type VitestTestModule as U, type VitestAdapterOptions as V, type VitestTestResult as W, toCIInfo as X, toRawCIInfo as Y, type TestCaseResult as a, type TestStatus as b, type DocPhase as c, type StepResult as d, type CIProvider as e, type RawAttachment as f, type RawRun as g, type RawCIInfo as h, adaptJestRun as i, adaptPlaywrightRun as j, adaptVitestRun as k, type CIInfo as l, type CoverageSummary as m, type JestAggregatedResult as n, type JestFileResult as o, type JestTestResult as p, type OtelAttributeValue as q, type PlaywrightAnnotation as r, type PlaywrightAttachment as s, type PlaywrightError as t, type PlaywrightLocation as u, type PlaywrightStatus as v, type PlaywrightTestCase as w, type PlaywrightTestResult as x, type RawStepEvent as y, type RawTestCase as z };
628
+ export { type Attachment as A, type RawTestCase as B, type CIInfo$1 as C, type DocEntry as D, STORY_META_KEY as E, type StepMode as F, type StoryFileReport as G, type StoryMeta as H, type TestCaseAttempt as I, type JestAdapterOptions as J, type TestCaseEvidence as K, type VitestSerializedError as L, type VitestState as M, type NormalizedTicket as N, type OtelSpan as O, type PlaywrightAdapterOptions as P, type VitestTestCase as Q, type RawStatus as R, type StoryStep as S, type TestRunResult as T, type VitestTestModule as U, type VitestAdapterOptions as V, type VitestTestResult as W, toCIInfo as X, toRawCIInfo as Y, type TestCaseResult as a, type TestStatus as b, type DocPhase as c, type StepResult as d, type CIProvider as e, type RawAttachment as f, type RawRun as g, type RawCIInfo as h, type StepKeyword as i, adaptJestRun as j, adaptPlaywrightRun as k, adaptVitestRun as l, type CIInfo as m, type CoverageSummary as n, type JestAggregatedResult as o, type JestFileResult as p, type JestTestResult as q, type OtelAttributeValue as r, type PlaywrightAnnotation as s, type PlaywrightAttachment as t, type PlaywrightError as u, type PlaywrightLocation as v, type PlaywrightStatus as w, type PlaywrightTestCase as x, type PlaywrightTestResult as y, type RawStepEvent as z };
@@ -625,4 +625,4 @@ interface PlaywrightAdapterOptions {
625
625
  */
626
626
  declare function adaptPlaywrightRun(testResults: Array<[PlaywrightTestCase, PlaywrightTestResult]>, options?: PlaywrightAdapterOptions): RawRun;
627
627
 
628
- export { type Attachment as A, STORY_META_KEY as B, type CIInfo$1 as C, type DocEntry as D, type StepKeyword as E, type StepMode as F, type StoryFileReport as G, type StoryMeta as H, type TestCaseAttempt as I, type JestAdapterOptions as J, type TestCaseEvidence as K, type VitestSerializedError as L, type VitestState as M, type NormalizedTicket as N, type OtelSpan as O, type PlaywrightAdapterOptions as P, type VitestTestCase as Q, type RawStatus as R, type StoryStep as S, type TestRunResult as T, type VitestTestModule as U, type VitestAdapterOptions as V, type VitestTestResult as W, toCIInfo as X, toRawCIInfo as Y, type TestCaseResult as a, type TestStatus as b, type DocPhase as c, type StepResult as d, type CIProvider as e, type RawAttachment as f, type RawRun as g, type RawCIInfo as h, adaptJestRun as i, adaptPlaywrightRun as j, adaptVitestRun as k, type CIInfo as l, type CoverageSummary as m, type JestAggregatedResult as n, type JestFileResult as o, type JestTestResult as p, type OtelAttributeValue as q, type PlaywrightAnnotation as r, type PlaywrightAttachment as s, type PlaywrightError as t, type PlaywrightLocation as u, type PlaywrightStatus as v, type PlaywrightTestCase as w, type PlaywrightTestResult as x, type RawStepEvent as y, type RawTestCase as z };
628
+ export { type Attachment as A, type RawTestCase as B, type CIInfo$1 as C, type DocEntry as D, STORY_META_KEY as E, type StepMode as F, type StoryFileReport as G, type StoryMeta as H, type TestCaseAttempt as I, type JestAdapterOptions as J, type TestCaseEvidence as K, type VitestSerializedError as L, type VitestState as M, type NormalizedTicket as N, type OtelSpan as O, type PlaywrightAdapterOptions as P, type VitestTestCase as Q, type RawStatus as R, type StoryStep as S, type TestRunResult as T, type VitestTestModule as U, type VitestAdapterOptions as V, type VitestTestResult as W, toCIInfo as X, toRawCIInfo as Y, type TestCaseResult as a, type TestStatus as b, type DocPhase as c, type StepResult as d, type CIProvider as e, type RawAttachment as f, type RawRun as g, type RawCIInfo as h, type StepKeyword as i, adaptJestRun as j, adaptPlaywrightRun as k, adaptVitestRun as l, type CIInfo as m, type CoverageSummary as n, type JestAggregatedResult as o, type JestFileResult as p, type JestTestResult as q, type OtelAttributeValue as r, type PlaywrightAnnotation as s, type PlaywrightAttachment as t, type PlaywrightError as u, type PlaywrightLocation as v, type PlaywrightStatus as w, type PlaywrightTestCase as x, type PlaywrightTestResult as y, type RawStepEvent as z };
package/dist/index.cjs CHANGED
@@ -55,12 +55,16 @@ __export(src_exports, {
55
55
  STORY_REPORT_SCHEMA_VERSION: () => STORY_REPORT_SCHEMA_VERSION,
56
56
  ScenarioIndexJsonFormatter: () => ScenarioIndexJsonFormatter,
57
57
  StoryReportJsonFormatter: () => StoryReportJsonFormatter,
58
+ TraceabilityMatrixFormatter: () => TraceabilityMatrixFormatter,
58
59
  adaptJestRun: () => adaptJestRun,
59
60
  adaptPlaywrightRun: () => adaptPlaywrightRun,
60
61
  adaptVitestRun: () => adaptVitestRun,
61
62
  assertValidRun: () => assertValidRun,
63
+ buildCheck: () => buildCheck,
64
+ buildGoal: () => buildGoal,
62
65
  buildHtmlDocEntry: () => buildHtmlDocEntry,
63
66
  buildReview: () => buildReview,
67
+ buildTriage: () => buildTriage,
64
68
  bundleAssets: () => bundleAssets,
65
69
  calculateFlakiness: () => calculateFlakiness,
66
70
  calculateStability: () => calculateStability,
@@ -110,6 +114,9 @@ __export(src_exports, {
110
114
  readPackageVersion: () => readPackageVersion,
111
115
  recordDeployment: () => recordDeployment,
112
116
  regenerateArtifacts: () => regenerateArtifacts,
117
+ renderCheck: () => renderCheck,
118
+ renderGoal: () => renderGoal,
119
+ renderTriage: () => renderTriage,
113
120
  resolveAttachment: () => resolveAttachment,
114
121
  resolveAttachments: () => resolveAttachments,
115
122
  resolveTheme: () => resolveTheme,
@@ -131,6 +138,7 @@ __export(src_exports, {
131
138
  toReleaseManifest: () => toReleaseManifest,
132
139
  toScenarioIndex: () => toScenarioIndex,
133
140
  toStoryReport: () => toStoryReport,
141
+ toTraceabilityMatrix: () => toTraceabilityMatrix,
134
142
  tryGetActiveOtelContext: () => tryGetActiveOtelContext,
135
143
  updateHistory: () => updateHistory,
136
144
  validateCanonicalRun: () => validateCanonicalRun
@@ -663,14 +671,14 @@ var CucumberJsonFormatter = class {
663
671
  duration: 0
664
672
  };
665
673
  }
666
- const statusMap = {
674
+ const statusMap2 = {
667
675
  passed: "passed",
668
676
  failed: "failed",
669
677
  skipped: "skipped",
670
678
  pending: "pending"
671
679
  };
672
680
  const stepResult = {
673
- status: statusMap[result.status] ?? "undefined",
681
+ status: statusMap2[result.status] ?? "undefined",
674
682
  // Duration in nanoseconds (Cucumber uses nanoseconds)
675
683
  duration: result.durationMs * 1e6
676
684
  };
@@ -15854,6 +15862,132 @@ function escapePipe(value) {
15854
15862
  return value.replace(/\|/g, "\\|");
15855
15863
  }
15856
15864
 
15865
+ // src/formatters/traceability-matrix.ts
15866
+ var TraceabilityMatrixFormatter = class {
15867
+ format(run) {
15868
+ const matrix = toTraceabilityMatrix(run);
15869
+ const lines = [];
15870
+ lines.push("# Traceability Matrix");
15871
+ lines.push("");
15872
+ lines.push(`Generated: ${matrix.generatedAt}`);
15873
+ lines.push(`Run: ${matrix.run.startedAt} to ${matrix.run.finishedAt}`);
15874
+ if (matrix.run.branch) lines.push(`Branch: ${matrix.run.branch}`);
15875
+ if (matrix.run.gitSha) lines.push(`Commit: ${matrix.run.gitSha}`);
15876
+ lines.push("");
15877
+ lines.push("| Requirements | Verified | Failing | Scenarios | Untraced |");
15878
+ lines.push("| ---: | ---: | ---: | ---: | ---: |");
15879
+ lines.push(
15880
+ `| ${matrix.summary.requirements} | ${matrix.summary.requirementsVerified} | ${matrix.summary.requirementsFailing} | ${matrix.summary.scenarios} | ${matrix.summary.untracedScenarios} |`
15881
+ );
15882
+ lines.push("");
15883
+ for (const req of matrix.requirements) {
15884
+ const heading2 = req.url ? `[${req.ticket}](${req.url})` : req.ticket;
15885
+ lines.push(`## ${heading2}`);
15886
+ lines.push("");
15887
+ lines.push(`Status: ${renderRequirementStatus(req.status)}`);
15888
+ if (req.covers.length > 0) {
15889
+ lines.push(`Covers: ${req.covers.map((path12) => `\`${path12}\``).join(", ")}`);
15890
+ }
15891
+ lines.push("");
15892
+ lines.push("| Status | Scenario | Source | Covers |");
15893
+ lines.push("| --- | --- | --- | --- |");
15894
+ for (const scenario of req.scenarios) {
15895
+ const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
15896
+ const covers = scenario.covers.length > 0 ? scenario.covers.map((path12) => `\`${path12}\``).join(", ") : "";
15897
+ lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` | ${covers} |`);
15898
+ }
15899
+ lines.push("");
15900
+ }
15901
+ if (matrix.untraced.length > 0) {
15902
+ lines.push("## Untraced scenarios");
15903
+ lines.push("");
15904
+ lines.push("Behavior with no requirement link. Add a `ticket` to each so it appears against a requirement.");
15905
+ lines.push("");
15906
+ lines.push("| Status | Scenario | Source |");
15907
+ lines.push("| --- | --- | --- |");
15908
+ for (const scenario of matrix.untraced) {
15909
+ const source = `${scenario.sourceFile}:${scenario.sourceLine}`;
15910
+ lines.push(`| ${scenario.status} | ${escapePipe2(scenario.title)} | \`${source}\` |`);
15911
+ }
15912
+ lines.push("");
15913
+ }
15914
+ return lines.join("\n").trimEnd();
15915
+ }
15916
+ };
15917
+ function toTraceabilityMatrix(run) {
15918
+ const sorted = [...run.testCases].sort((a, b) => a.id.localeCompare(b.id));
15919
+ const byTicket = /* @__PURE__ */ new Map();
15920
+ const untraced = [];
15921
+ for (const tc of sorted) {
15922
+ const tickets = tc.story.tickets ?? [];
15923
+ if (tickets.length === 0) {
15924
+ untraced.push({
15925
+ id: tc.id,
15926
+ title: tc.story.scenario,
15927
+ status: tc.status,
15928
+ sourceFile: tc.sourceFile,
15929
+ sourceLine: tc.sourceLine
15930
+ });
15931
+ continue;
15932
+ }
15933
+ for (const ticket of tickets) {
15934
+ const entry = byTicket.get(ticket.id) ?? { url: ticket.url, cases: [] };
15935
+ if (!entry.url && ticket.url) entry.url = ticket.url;
15936
+ entry.cases.push(tc);
15937
+ byTicket.set(ticket.id, entry);
15938
+ }
15939
+ }
15940
+ const requirements = [...byTicket.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([ticket, entry]) => {
15941
+ const scenarios = entry.cases.map((tc) => ({
15942
+ id: tc.id,
15943
+ title: tc.story.scenario,
15944
+ status: tc.status,
15945
+ sourceFile: tc.sourceFile,
15946
+ sourceLine: tc.sourceLine,
15947
+ covers: tc.story.covers ?? []
15948
+ }));
15949
+ const covers = [...new Set(scenarios.flatMap((s) => s.covers))].sort();
15950
+ return { ticket, url: entry.url, status: requirementStatus(entry.cases), scenarios, covers };
15951
+ });
15952
+ return {
15953
+ schemaVersion: "1.0",
15954
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
15955
+ run: {
15956
+ startedAt: new Date(run.startedAtMs).toISOString(),
15957
+ finishedAt: new Date(run.finishedAtMs).toISOString(),
15958
+ gitSha: run.gitSha,
15959
+ branch: run.ci?.branch
15960
+ },
15961
+ summary: {
15962
+ requirements: requirements.length,
15963
+ requirementsVerified: requirements.filter((r) => r.status === "verified").length,
15964
+ requirementsFailing: requirements.filter((r) => r.status === "failing").length,
15965
+ scenarios: run.testCases.length,
15966
+ untracedScenarios: untraced.length
15967
+ },
15968
+ requirements,
15969
+ untraced
15970
+ };
15971
+ }
15972
+ function requirementStatus(cases) {
15973
+ if (cases.some((tc) => tc.status === "failed")) return "failing";
15974
+ if (cases.some((tc) => tc.status === "passed")) return "verified";
15975
+ return "incomplete";
15976
+ }
15977
+ function renderRequirementStatus(status) {
15978
+ switch (status) {
15979
+ case "verified":
15980
+ return "verified (all scenarios passed)";
15981
+ case "failing":
15982
+ return "failing (a scenario failed)";
15983
+ default:
15984
+ return "incomplete (no scenario passed yet)";
15985
+ }
15986
+ }
15987
+ function escapePipe2(value) {
15988
+ return value.replace(/\|/g, "\\|");
15989
+ }
15990
+
15857
15991
  // src/formatters/cucumber-messages/synthesize-feature.ts
15858
15992
  function extractFeatureName(testCases, uri) {
15859
15993
  for (const tc of testCases) {
@@ -20602,6 +20736,280 @@ function collectDocKinds(testCase) {
20602
20736
  return [...kinds].sort();
20603
20737
  }
20604
20738
 
20739
+ // src/scenario-failure.ts
20740
+ function failingScenarioMessage(tc) {
20741
+ const failingStep = tc.stepResults.find((s) => s.status === "failed" && s.errorMessage);
20742
+ return failingStep?.errorMessage ?? tc.errorMessage;
20743
+ }
20744
+
20745
+ // src/check.ts
20746
+ var ICON_PASS = "\u2713";
20747
+ var ICON_FAIL = "\u2717";
20748
+ var ICON_SKIP = "\u2298";
20749
+ var ICON_PENDING = "\u23F3";
20750
+ var ICON_WARN = "\u26A0";
20751
+ function buildCheck(args, _deps = {}) {
20752
+ const { testCases, baseline } = args;
20753
+ const summary = {
20754
+ total: testCases.length,
20755
+ passed: testCases.filter((tc) => tc.status === "passed").length,
20756
+ failed: testCases.filter((tc) => tc.status === "failed").length,
20757
+ skipped: testCases.filter((tc) => tc.status === "skipped").length,
20758
+ pending: testCases.filter((tc) => tc.status === "pending").length
20759
+ };
20760
+ let regressed = 0;
20761
+ let fixed = 0;
20762
+ if (baseline) {
20763
+ for (const tc of testCases) {
20764
+ const before = baseline.get(tc.id);
20765
+ if (before === "passed" && tc.status === "failed") regressed += 1;
20766
+ if (before === "failed" && tc.status === "passed") fixed += 1;
20767
+ }
20768
+ }
20769
+ const failures = testCases.filter((tc) => tc.status === "failed").map((tc) => toFailure(tc, baseline)).sort((a, b) => {
20770
+ if (a.regressed !== b.regressed) return a.regressed ? -1 : 1;
20771
+ return a.location.localeCompare(b.location);
20772
+ });
20773
+ return {
20774
+ summary,
20775
+ failures,
20776
+ regressed,
20777
+ fixed,
20778
+ comparedToBaseline: baseline !== void 0
20779
+ };
20780
+ }
20781
+ function toFailure(tc, baseline) {
20782
+ const failedIndexes = new Set(
20783
+ tc.stepResults.filter((s) => s.status === "failed").map((s) => s.index)
20784
+ );
20785
+ const steps = tc.story.steps.map((step, index) => ({
20786
+ keyword: step.keyword,
20787
+ text: step.text,
20788
+ failed: failedIndexes.has(index)
20789
+ }));
20790
+ return {
20791
+ id: tc.id,
20792
+ scenario: tc.story.scenario,
20793
+ location: `${tc.sourceFile}:${tc.sourceLine}`,
20794
+ steps,
20795
+ errorMessage: failingScenarioMessage(tc),
20796
+ covers: tc.story.covers ?? [],
20797
+ tickets: (tc.story.tickets ?? []).map((t) => t.id),
20798
+ regressed: baseline?.get(tc.id) === "passed"
20799
+ };
20800
+ }
20801
+ function renderCheck(report, format) {
20802
+ return format === "json" ? JSON.stringify(report, null, 2) : renderCheckText(report);
20803
+ }
20804
+ function renderCheckText(report) {
20805
+ const { summary, failures } = report;
20806
+ const headlineParts = [`${ICON_PASS} ${summary.passed} passed`];
20807
+ if (summary.failed > 0) headlineParts.push(`${ICON_FAIL} ${summary.failed} failed`);
20808
+ if (summary.skipped > 0) headlineParts.push(`${ICON_SKIP} ${summary.skipped} skipped`);
20809
+ if (summary.pending > 0) headlineParts.push(`${ICON_PENDING} ${summary.pending} pending`);
20810
+ const headline = `${headlineParts.join(" ")} (${summary.total} scenarios)`;
20811
+ if (failures.length === 0) {
20812
+ const lines2 = [headline];
20813
+ if (report.comparedToBaseline && report.fixed > 0) {
20814
+ lines2.push(`${ICON_PASS} ${report.fixed} fixed since baseline.`);
20815
+ }
20816
+ lines2.push("All scenarios green.");
20817
+ return lines2.join("\n");
20818
+ }
20819
+ const lines = [headline, ""];
20820
+ for (const f of failures) {
20821
+ lines.push(`${ICON_FAIL} ${f.scenario}${f.regressed ? " (regressed)" : ""}`);
20822
+ lines.push(` ${f.location}`);
20823
+ for (const step of f.steps) {
20824
+ const marker = step.failed ? ` ${ICON_FAIL} ` : " ";
20825
+ lines.push(`${marker}${step.keyword} ${step.text}`);
20826
+ }
20827
+ if (f.errorMessage) {
20828
+ const firstLine = f.errorMessage.split("\n")[0];
20829
+ lines.push(` \u2192 ${firstLine}`);
20830
+ }
20831
+ if (f.covers.length > 0) {
20832
+ lines.push(` covers: ${f.covers.join(", ")}`);
20833
+ }
20834
+ if (f.tickets.length > 0) {
20835
+ lines.push(` ticket: ${f.tickets.join(", ")}`);
20836
+ }
20837
+ lines.push("");
20838
+ }
20839
+ if (report.comparedToBaseline) {
20840
+ if (report.regressed > 0) {
20841
+ lines.push(`${ICON_WARN} ${report.regressed} regressed since baseline (was passing).`);
20842
+ }
20843
+ if (report.fixed > 0) {
20844
+ lines.push(`${ICON_PASS} ${report.fixed} fixed since baseline.`);
20845
+ }
20846
+ if (report.regressed === 0 && report.fixed === 0) {
20847
+ lines.push("No status changes vs. baseline.");
20848
+ }
20849
+ }
20850
+ return lines.join("\n").trimEnd();
20851
+ }
20852
+
20853
+ // src/goal.ts
20854
+ var ACTIVE = ["passed", "failed"];
20855
+ function buildGoal(args, _deps = {}) {
20856
+ const { run, baseline } = args;
20857
+ const cases = run.testCases;
20858
+ const selectors = [
20859
+ ...args.requireTags.map((tag) => ({ label: `tag:${tag}`, match: (tc) => tc.tags.includes(tag) })),
20860
+ ...args.requireTickets.map((id) => ({ label: `ticket:${id}`, match: (tc) => (tc.story.tickets ?? []).some((t) => t.id === id) })),
20861
+ ...args.requireScenarios.map((sel) => ({ label: `scenario:${sel}`, match: (tc) => tc.id === sel || tc.story.scenario === sel }))
20862
+ ];
20863
+ const requirements = selectors.length === 0 ? [evaluate("all scenarios", cases)] : selectors.map((s) => evaluate(s.label, cases.filter(s.match)));
20864
+ const regressions = [];
20865
+ if (baseline && args.enforceNoRegressions) {
20866
+ const before = statusMap(baseline);
20867
+ for (const tc of cases) {
20868
+ if (before.get(tc.id) === "passed" && tc.status === "failed") {
20869
+ regressions.push({ id: tc.id, title: tc.story.scenario });
20870
+ }
20871
+ }
20872
+ }
20873
+ const violations = [];
20874
+ if (baseline && args.enforceRatchet) {
20875
+ const current = new Map(cases.map((tc) => [tc.id, tc]));
20876
+ for (const base of baseline.testCases) {
20877
+ const now = current.get(base.id);
20878
+ if (!now) {
20879
+ violations.push({ id: base.id, title: base.story.scenario, kind: "removed", detail: "scenario no longer present" });
20880
+ continue;
20881
+ }
20882
+ if (ACTIVE.includes(base.status) && (now.status === "skipped" || now.status === "pending")) {
20883
+ violations.push({ id: base.id, title: base.story.scenario, kind: "disabled", detail: `${base.status} -> ${now.status}` });
20884
+ }
20885
+ const baseSteps = base.story.steps.length;
20886
+ const nowSteps = now.story.steps.length;
20887
+ if (nowSteps < baseSteps) {
20888
+ violations.push({ id: base.id, title: base.story.scenario, kind: "weakened", detail: `${baseSteps} steps -> ${nowSteps} steps` });
20889
+ }
20890
+ }
20891
+ }
20892
+ const met = requirements.every((r) => r.met) && regressions.length === 0 && violations.length === 0;
20893
+ return {
20894
+ met,
20895
+ requirements,
20896
+ regressions,
20897
+ regressionsEnforced: Boolean(baseline && args.enforceNoRegressions),
20898
+ ratchet: { enforced: Boolean(baseline && args.enforceRatchet), violations }
20899
+ };
20900
+ }
20901
+ function evaluate(selector, matched) {
20902
+ const passed = matched.filter((tc) => tc.status === "passed").length;
20903
+ const failing = matched.filter((tc) => tc.status !== "passed").map((tc) => tc.story.scenario);
20904
+ return {
20905
+ selector,
20906
+ matched: matched.length,
20907
+ passed,
20908
+ failing,
20909
+ met: matched.length > 0 && failing.length === 0
20910
+ };
20911
+ }
20912
+ function statusMap(run) {
20913
+ return new Map(run.testCases.map((tc) => [tc.id, tc.status]));
20914
+ }
20915
+ function renderGoal(report, format) {
20916
+ if (format === "json") return JSON.stringify(report, null, 2);
20917
+ const lines = [`GOAL: ${report.met ? "met" : "not met"}`];
20918
+ for (const req of report.requirements) {
20919
+ if (req.matched === 0) {
20920
+ lines.push(` ${req.selector}: no matching scenario (no proof)`);
20921
+ continue;
20922
+ }
20923
+ const tail = req.failing.length > 0 ? ` (${req.failing.length} failing)` : "";
20924
+ lines.push(` ${req.selector}: ${req.passed}/${req.matched} scenarios pass${tail}`);
20925
+ }
20926
+ if (report.regressionsEnforced) {
20927
+ if (report.regressions.length === 0) {
20928
+ lines.push(" regressions: 0");
20929
+ } else {
20930
+ lines.push(` regressions: ${report.regressions.length} (${report.regressions.map((r) => r.title).join(", ")})`);
20931
+ }
20932
+ }
20933
+ if (report.ratchet.enforced) {
20934
+ if (report.ratchet.violations.length === 0) {
20935
+ lines.push(" ratchet: clean (0 scenarios removed/weakened)");
20936
+ } else {
20937
+ lines.push(` ratchet: ${report.ratchet.violations.length} removed/weakened`);
20938
+ for (const v of report.ratchet.violations) {
20939
+ lines.push(` ${v.kind}: ${v.title} (${v.detail})`);
20940
+ }
20941
+ }
20942
+ }
20943
+ return lines.join("\n");
20944
+ }
20945
+
20946
+ // src/triage.ts
20947
+ function buildTriage(args, _deps = {}) {
20948
+ const { testCases, baseline } = args;
20949
+ const failing = testCases.filter((tc) => tc.status === "failed");
20950
+ const ranked = failing.map((tc) => {
20951
+ const regressed = baseline?.get(tc.id) === "passed";
20952
+ return {
20953
+ tc,
20954
+ regressed,
20955
+ covers: tc.story.covers ?? []
20956
+ };
20957
+ }).sort((a, b) => {
20958
+ if (a.regressed !== b.regressed) return a.regressed ? -1 : 1;
20959
+ const la = `${a.tc.sourceFile}:${a.tc.sourceLine}`;
20960
+ const lb = `${b.tc.sourceFile}:${b.tc.sourceLine}`;
20961
+ return la.localeCompare(lb);
20962
+ });
20963
+ const items = ranked.map((entry, index) => ({
20964
+ rank: index + 1,
20965
+ id: entry.tc.id,
20966
+ scenario: entry.tc.story.scenario,
20967
+ status: entry.tc.status,
20968
+ location: `${entry.tc.sourceFile}:${entry.tc.sourceLine}`,
20969
+ covers: entry.covers,
20970
+ tickets: (entry.tc.story.tickets ?? []).map((t) => t.id),
20971
+ errorMessage: failingScenarioMessage(entry.tc),
20972
+ regressed: entry.regressed,
20973
+ reason: entry.regressed ? "regression" : "failing"
20974
+ }));
20975
+ return {
20976
+ total: testCases.length,
20977
+ failing: failing.length,
20978
+ regressions: items.filter((i) => i.regressed).length,
20979
+ needsCovers: items.filter((i) => i.covers.length === 0).length,
20980
+ items
20981
+ };
20982
+ }
20983
+ function renderTriage(report, format) {
20984
+ if (format === "json") return JSON.stringify(report, null, 2);
20985
+ if (report.items.length === 0) {
20986
+ return "Nothing to triage. No failing scenarios.";
20987
+ }
20988
+ const header = report.regressions > 0 ? `${report.items.length} items to triage (${report.regressions} regression${report.regressions === 1 ? "" : "s"})` : `${report.items.length} items to triage`;
20989
+ const lines = [header, ""];
20990
+ for (const item of report.items) {
20991
+ const tag = item.regressed ? "[regression] " : "";
20992
+ lines.push(`${item.rank}. ${tag}${item.scenario}`);
20993
+ lines.push(` ${item.location}`);
20994
+ if (item.errorMessage) {
20995
+ lines.push(` \u2192 ${item.errorMessage.split("\n")[0]}`);
20996
+ }
20997
+ if (item.covers.length > 0) {
20998
+ lines.push(` fix: ${item.covers.join(", ")}`);
20999
+ } else {
21000
+ lines.push(" fix: (no covers declared \u2014 add `covers` to route this to code)");
21001
+ }
21002
+ if (item.tickets.length > 0) {
21003
+ lines.push(` ticket: ${item.tickets.join(", ")}`);
21004
+ }
21005
+ lines.push("");
21006
+ }
21007
+ if (report.needsCovers > 0) {
21008
+ lines.push(`${report.needsCovers} failing scenario(s) have no covers and can't be routed to code automatically.`);
21009
+ }
21010
+ return lines.join("\n").trimEnd();
21011
+ }
21012
+
20605
21013
  // src/review/conventions.ts
20606
21014
  var CHANGE_TAG_PREFIX = "change:";
20607
21015
  var AUDIENCE_TAG_PREFIX = "audience:";
@@ -21403,6 +21811,7 @@ var FORMAT_EXTENSIONS = {
21403
21811
  "behavior-manifest-json": ".behavior-manifest.json",
21404
21812
  markdown: ".md",
21405
21813
  "release-manifest": ".release-manifest.md",
21814
+ "traceability-matrix": ".traceability-matrix.md",
21406
21815
  html: ".html",
21407
21816
  "cucumber-html": ".cucumber.html",
21408
21817
  junit: ".junit.xml",
@@ -21829,6 +22238,10 @@ var ReportGenerator = class {
21829
22238
  const formatter = new ReleaseManifestFormatter();
21830
22239
  return formatter.format(run);
21831
22240
  }
22241
+ case "traceability-matrix": {
22242
+ const formatter = new TraceabilityMatrixFormatter();
22243
+ return formatter.format(run);
22244
+ }
21832
22245
  case "story-report-json": {
21833
22246
  const formatter = new StoryReportJsonFormatter({
21834
22247
  pretty: this.options.storyReportJson.pretty
@@ -21909,12 +22322,16 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
21909
22322
  STORY_REPORT_SCHEMA_VERSION,
21910
22323
  ScenarioIndexJsonFormatter,
21911
22324
  StoryReportJsonFormatter,
22325
+ TraceabilityMatrixFormatter,
21912
22326
  adaptJestRun,
21913
22327
  adaptPlaywrightRun,
21914
22328
  adaptVitestRun,
21915
22329
  assertValidRun,
22330
+ buildCheck,
22331
+ buildGoal,
21916
22332
  buildHtmlDocEntry,
21917
22333
  buildReview,
22334
+ buildTriage,
21918
22335
  bundleAssets,
21919
22336
  calculateFlakiness,
21920
22337
  calculateStability,
@@ -21964,6 +22381,9 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
21964
22381
  readPackageVersion,
21965
22382
  recordDeployment,
21966
22383
  regenerateArtifacts,
22384
+ renderCheck,
22385
+ renderGoal,
22386
+ renderTriage,
21967
22387
  resolveAttachment,
21968
22388
  resolveAttachments,
21969
22389
  resolveTheme,
@@ -21985,6 +22405,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
21985
22405
  toReleaseManifest,
21986
22406
  toScenarioIndex,
21987
22407
  toStoryReport,
22408
+ toTraceabilityMatrix,
21988
22409
  tryGetActiveOtelContext,
21989
22410
  updateHistory,
21990
22411
  validateCanonicalRun