From ed80dc2d15b3b2bf02a4d52050341ee2854a85b1 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 27 Apr 2026 12:57:10 -0700 Subject: [PATCH] feat(cli): show artifacts and top findings --- src/cli.js | 4 ++-- src/report.js | 41 +++++++++++++++++++++++++++++++++++--- test/report.test.js | 48 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/cli.js b/src/cli.js index 5db0e70..68a455e 100755 --- a/src/cli.js +++ b/src/cli.js @@ -83,7 +83,7 @@ async function runCheck(commandArgs) { if (json) { console.log(JSON.stringify(report, null, 2)); } else { - console.log(renderTextSummary(report)); + console.log(renderTextSummary(report, { artifacts: paths })); } if (report.status !== "pass") { @@ -128,7 +128,7 @@ async function runReport(command, commandArgs) { if (json) { console.log(JSON.stringify(report, null, 2)); } else { - console.log(renderTextSummary(report)); + console.log(renderTextSummary(report, { artifacts: paths })); } if (check && report.status !== "pass") { diff --git a/src/report.js b/src/report.js index e4cd4af..afa4785 100644 --- a/src/report.js +++ b/src/report.js @@ -260,13 +260,48 @@ export async function writeCompatibilityReport(report, options = {}) { ); } -export function renderTextSummary(report) { - return [ +export function renderTextSummary(report, options = {}) { + const lines = [ `Status: ${report.status.toUpperCase()}`, `Fixtures: ${report.summary.fixtureCount}`, `Breakages: ${report.summary.breakageCount}`, + ...(typeof report.summary.issueCount === "number" ? [`Issues: ${report.summary.issueCount}`] : []), `Logs: ${report.summary.logCount}`, - ].join("\n"); + ]; + const artifacts = Object.entries(options.artifacts ?? {}).filter(([, filePath]) => Boolean(filePath)); + if (artifacts.length > 0) { + lines.push("", "Reports:", ...artifacts.map(([name, filePath]) => `- ${artifactLabel(name)}: ${filePath}`)); + } + const findings = topTextFindings(report, options.topFindings ?? 3); + if (findings.length > 0) { + lines.push("", "Top findings:", ...findings.map((finding) => `- ${finding}`)); + } + return lines.join("\n"); +} + +function topTextFindings(report, limit) { + if (report.status === "pass" || limit <= 0) { + return []; + } + return [ + ...(report.breakages ?? []).map((finding) => formatTextFinding(finding, "breakage")), + ...(report.issues ?? []) + .filter((issue) => issue.status === "blocking" || issue.severity === "P0" || issue.severity === "P1") + .map((issue) => formatTextFinding(issue, issue.severity ?? "issue")), + ...(report.warnings ?? []).map((finding) => formatTextFinding(finding, "warning")), + ].slice(0, limit); +} + +function formatTextFinding(finding, fallbackLevel) { + const level = finding.level ?? finding.severity ?? fallbackLevel; + const code = finding.code ? ` ${finding.code}` : ""; + const message = finding.message ?? finding.title ?? "see report"; + const evidence = Array.isArray(finding.evidence) && finding.evidence.length > 0 ? ` (${finding.evidence[0]})` : ""; + return `${String(level).toUpperCase()} ${finding.fixture ?? "unknown"}${code}: ${message}${evidence}`; +} + +function artifactLabel(name) { + return String(name).replace(/Path$/u, ""); } export function renderMarkdownReport(report) { diff --git a/test/report.test.js b/test/report.test.js index 6f1fa1e..4ce907d 100644 --- a/test/report.test.js +++ b/test/report.test.js @@ -18,6 +18,7 @@ import { renderJunitXml, renderMarkdownReport, renderMarkdownTable, + renderTextSummary, writeArtifacts, writeCiOutputArtifacts, writeReport, @@ -32,6 +33,53 @@ test("markdown report includes summary and inventory", async () => { assert.match(markdown, /\| sample-plugin \| high \| native-tool \| before_tool_call \| definePluginEntry, registerTool \| tools \|/); }); +test("text summary includes artifact paths and top blocking findings", () => { + const summary = renderTextSummary( + { + status: "fail", + summary: { + fixtureCount: 1, + breakageCount: 1, + issueCount: 1, + logCount: 0, + }, + breakages: [ + { + fixture: "weather", + code: "missing-expected-seam", + level: "breakage", + message: "weather: missing expected registration registerTool", + evidence: ["src/index.js:12"], + }, + ], + warnings: [], + issues: [ + { + fixture: "weather", + code: "sdk-export-missing", + severity: "P0", + status: "blocking", + title: "SDK export is missing", + evidence: ["src/index.js:1"], + }, + ], + }, + { + artifacts: { + jsonPath: "reports/plugin-inspector-report.json", + markdownPath: "reports/plugin-inspector-report.md", + }, + }, + ); + + assert.match(summary, /Status: FAIL/); + assert.match(summary, /Issues: 1/); + assert.match(summary, /Reports:/); + assert.match(summary, /json: reports\/plugin-inspector-report\.json/); + assert.match(summary, /Top findings:/); + assert.match(summary, /BREAKAGE weather missing-expected-seam/); +}); + test("compatibility report renderer supports issue metadata and evidence links", () => { const report = { generatedAt: "test",