singularity-forge/src/resources/extensions/sf/doctor-format.js
2026-05-08 01:34:07 +02:00

118 lines
3.8 KiB
JavaScript

function matchesScope(unitId, scope) {
if (!scope) return true;
if (unitId === "project" || unitId === "environment") return true;
return (
unitId === scope ||
unitId.startsWith(`${scope}/`) ||
unitId.startsWith(`${scope}`)
);
}
export function summarizeDoctorIssues(issues) {
const errors = issues.filter((issue) => issue.severity === "error").length;
const warnings = issues.filter(
(issue) => issue.severity === "warning",
).length;
const infos = issues.filter((issue) => issue.severity === "info").length;
const fixable = issues.filter((issue) => issue.fixable).length;
const byCodeMap = new Map();
for (const issue of issues) {
byCodeMap.set(issue.code, (byCodeMap.get(issue.code) ?? 0) + 1);
}
const byCode = [...byCodeMap.entries()]
.map(([code, count]) => ({ code, count }))
.sort((a, b) => b.count - a.count || a.code.localeCompare(b.code));
return { total: issues.length, errors, warnings, infos, fixable, byCode };
}
export function filterDoctorIssues(issues, options) {
let filtered = issues;
if (options?.scope)
filtered = filtered.filter((issue) =>
matchesScope(issue.unitId, options.scope),
);
if (!options?.includeWarnings)
filtered = filtered.filter((issue) => issue.severity === "error");
return filtered;
}
export function formatDoctorReport(report, options) {
const scopedIssues = filterDoctorIssues(report.issues, {
scope: options?.scope,
includeWarnings: options?.includeWarnings ?? true,
});
const summary = summarizeDoctorIssues(scopedIssues);
const maxIssues = options?.maxIssues ?? 12;
const lines = [];
lines.push(
options?.title ??
(summary.errors > 0
? "SF doctor found blocking issues."
: "SF doctor report."),
);
lines.push(`Scope: ${options?.scope ?? "all milestones"}`);
lines.push(
`Issues: ${summary.total} total · ${summary.errors} error(s) · ${summary.warnings} warning(s) · ${summary.fixable} fixable`,
);
if (summary.byCode.length > 0) {
lines.push("Top issue types:");
for (const item of summary.byCode.slice(0, 5)) {
lines.push(`- ${item.code}: ${item.count}`);
}
}
if (scopedIssues.length > 0) {
lines.push("Priority issues:");
for (const issue of scopedIssues.slice(0, maxIssues)) {
const prefix =
issue.severity === "error"
? "ERROR"
: issue.severity === "warning"
? "WARN"
: "INFO";
lines.push(
`- [${prefix}] ${issue.unitId}: ${issue.message}${issue.file ? ` (${issue.file})` : ""}`,
);
}
if (scopedIssues.length > maxIssues) {
lines.push(`- ...and ${scopedIssues.length - maxIssues} more in scope`);
}
}
if (report.fixesApplied.length > 0) {
lines.push("Fixes applied:");
for (const fix of report.fixesApplied.slice(0, maxIssues))
lines.push(`- ${fix}`);
if (report.fixesApplied.length > maxIssues)
lines.push(`- ...and ${report.fixesApplied.length - maxIssues} more`);
}
return lines.join("\n");
}
export function formatDoctorIssuesForPrompt(issues) {
if (issues.length === 0) return "- No remaining issues in scope.";
return issues
.map((issue) => {
const prefix =
issue.severity === "error"
? "ERROR"
: issue.severity === "warning"
? "WARN"
: "INFO";
return `- [${prefix}] ${issue.unitId} | ${issue.code} | ${issue.message}${issue.file ? ` | file: ${issue.file}` : ""} | fixable: ${issue.fixable ? "yes" : "no"}`;
})
.join("\n");
}
/**
* Serialize a doctor report to JSON — suitable for CI/tooling integration.
* Usage: /doctor --json
*/
export function formatDoctorReportJson(report) {
return JSON.stringify(
{
ok: report.ok,
basePath: report.basePath,
generatedAt: new Date().toISOString(),
summary: summarizeDoctorIssues(report.issues),
issues: report.issues,
fixesApplied: report.fixesApplied,
...(report.timing ? { timing: report.timing } : {}),
},
null,
2,
);
}