scripts/render-report.mjs
scripts/render-report.mjsBrowse 6 files
997 tokens
3,982 bytes
Token encoding: o200k_base
Snapshot 5b913e7
← Back to SKILL.md
1#!/usr/bin/env node2import { writeFileSync } from "node:fs";3import { pathToFileURL } from "node:url";4import { parseArgs, readJson } from "./lib.mjs";5 6const LABELS = { high: "High", medium: "Medium", low: "Low" };7 8function escapeMarkdownText(value) {9 return String(value).replace(/([\\`*_[\]()!|<>])/g, "\\$1");10}11 12function issueLabel(issue) {13 if (!issue) return "No originating issue";14 return issue.identifier ? `${issue.identifier} (${issue.status})` : `${issue.issueId} (${issue.status})`;15}16 17function reasonText(entry) {18 if (entry.reasons.length === 0) return "All mechanical readiness gates passed.";19 return entry.reasons.map((entryReason) => entryReason.message).join("; ");20}21 22function scopeText(readiness) {23 const authors = readiness.authors?.length24 ? `PRs authored by ${readiness.authors.map((author) => `\`${author}\``).join(", ")} (this Paperclip instance)`25 : "PRs by any author (community included)";26 const window = readiness.windowDays ? ` referenced by issues active in the last ${readiness.windowDays} day(s)` : "";27 return `${authors}${window}`;28}29 30export function renderReport(readiness) {31 const lines = [32 "# PR Gardening Report",33 "",34 `Repository: \`${readiness.repository}\` `,35 `Scope: ${scopeText(readiness)} `,36 `Generated: ${readiness.generatedAt} `,37 `Head-SHA verification: every verdict below was computed from the recorded current head SHA.`,38 "",39 `Summary: **${readiness.summary.ready} ready**, **${readiness.summary.needsGardening} need gardening**, **${readiness.summary.reportOnly} report-only drafts**.`,40 "",41 ];42 43 if (readiness.truncatedIssues?.length) {44 lines.push(45 `Discovery caveat: ${readiness.truncatedIssues.length} issue(s) hit the per-issue extract match cap, so PR mentions beyond the cap were not scanned: ${readiness.truncatedIssues.map((issue) => issue.identifier ?? issue.issueId).join(", ")}.`,46 "",47 );48 }49 50 for (const confidence of ["high", "medium", "low"]) {51 const entries = readiness.pullRequests.filter((entry) => entry.confidence === confidence && entry.state === "open");52 lines.push(`## ${LABELS[confidence]} Confidence`, "");53 if (entries.length === 0) {54 lines.push("_None._", "");55 continue;56 }57 for (const entry of entries) {58 const draft = entry.isDraft ? " — draft (report only)" : "";59 lines.push(60 `### [#${entry.number}](${entry.url}) — ${escapeMarkdownText(entry.title)}${draft}`,61 "",62 `- Purpose: ${escapeMarkdownText(entry.purpose ?? "No description provided.")}`,63 `- Author: ${entry.author ? `\`${entry.author}\`` : "unknown"}`,64 `- Verdict: \`${entry.verdict}\``,65 `- Head: \`${entry.headSha}\``,66 `- Originating issue: ${issueLabel(entry.originatingIssue)}`,67 `- Checks: ${entry.checks.checks.length - entry.checks.pending.length - entry.checks.failing.length} green, ${entry.checks.pending.length} pending, ${entry.checks.failing.length} failing`,68 `- Greptile: ${entry.greptile.clean ? "clean" : entry.greptile.present ? "not clean/current" : "missing"}`,69 `- Base distance: ${entry.behindBy} commit(s) behind \`${entry.baseRefName}\``,70 `- Reasons: ${reasonText(entry)}`,71 "",72 );73 }74 }75 76 lines.push(77 "## Guardrail",78 "",79 "This report is advisory. The gardening workflow never merges, approves, or closes pull requests and never instructs anyone to merge them.",80 "",81 );82 return `${lines.join("\n")}\n`;83}84 85function main() {86 const options = parseArgs(process.argv.slice(2), { input: "readiness.json", output: "gardening-report.md" });87 const report = renderReport(readJson(options.input));88 if (options.output === "-") process.stdout.write(report);89 else writeFileSync(options.output, report);90}91 92if (import.meta.url === pathToFileURL(process.argv[1]).href) {93 try {94 main();95 } catch (error) {96 console.error(error.message);97 process.exitCode = 1;98 }99}100