| 32 | |
| 33 | /** Markdown report — paste into a PR / issue / team chat. PURE. */ |
| 34 | export function buildMaintainReportMarkdown(d: MaintainReportData): string { |
| 35 | const s = d.stats; |
| 36 | const lines = [ |
| 37 | `## 🔧 Self-Improvement Report${d.project ? ` — ${d.project}` : ''}`, |
| 38 | '', |
| 39 | `*Generated ${d.generatedAt} by [QodeX](https://github.com/QodeXcli/QodeX) \`maintain\` — every number below comes from verified-run receipts, not model claims.*`, |
| 40 | '', |
| 41 | '| | |', |
| 42 | '|---|---|', |
| 43 | `| **Cleanup PRs shipped** | ${s.opened} |`, |
| 44 | `| **Safely blocked** (guardrail declined) | ${s.blocked} |`, |
| 45 | `| **Files cleaned** | ${s.filesCleaned} |`, |
| 46 | `| **Est. minutes saved** | ~${s.estMinutesSaved} |`, |
| 47 | `| **Success rate** | ${Math.round(s.successRate * 100)}% of ${s.totalRuns} runs |`, |
| 48 | '', |
| 49 | `**This week:** ${d.weekly.opened} PR(s) · ${d.weekly.filesCleaned} files · ${d.weekly.openedDelta >= 0 ? '▲' : '▼'}${Math.abs(d.weekly.openedDelta)} vs last week`, |
| 50 | `**8-week trend:** \`${sparkline(d.trend)}\` (opened/week)`, |
| 51 | `**Forecast:** ${dirWord(d.forecast.direction)} · avg ~${d.forecast.weeklyAvg}/wk · next week ≈ ${d.forecast.nextWeek}`, |
| 52 | `**Projected:** ~${d.projection.cleanupsPerMonth} cleanups/mo · ~${d.projection.minutesPerMonth} min/mo`, |
| 53 | '', |
| 54 | `**By scope:** ${s.byScope.map(x => `\`${x.scope}\` ${x.opened}/${x.runs}`).join(' · ') || '—'}`, |
| 55 | ]; |
| 56 | if (d.next) lines.push('', `**Suggested next:** \`${d.next.scope}\` — ${d.next.why}`); |
| 57 | return lines.join('\n'); |
| 58 | } |
| 59 | |
| 60 | /** PDF report blocks (feed to pdf-lite buildPdf). PURE. */ |
| 61 | export function buildMaintainReportPdfBlocks(d: MaintainReportData): PdfBlock[] { |