/** * `doctor` — read-only deployment health, one finding per past failure mode * (#287, legibility 1/5). * * The probe logic lives in ../doctor.ts with every I/O seam injectable; this * module only parses the doctor's own flags, renders the findings the runner * produced (the same findings `--json` emits), and derives the exit code: * nonzero iff the report contains a failing finding. */ import type { CommandContext } from "./context.ts"; import { runDoctor, type DoctorReport, type FindingStatus } from "../doctor.ts"; import { dim, fail, heading, ok, warn } from "../ui/style.ts"; const DOCTOR_USAGE = `omp-conductor doctor — check the deployment faults that have already cost debugging sessions, mechanically and read-only. usage: omp-conductor doctor [--project NAME] [--json] [--probe-telegram] Checks, each one the mechanical version of a past production incident: gh auth token alive and scopes cover every configured repo (a token that expired under a live daemon failed every tracker call silently) labels every configured routing/state label exists with exact case — label matching is case-sensitive and a mismatch fails silently (README Limitations) systemd unit the installed units vs the staged render (User=, environment, working directory, exec, restart/success status) and the ownership/modes of the unit and runtime dirs (the worker die-at-turn-0 class) config config.json validates, and its pre-write backup exists and is fresh store PRAGMA integrity_check spend telemetry every completed run recording $0.00 means the USD cap cannot fire — $0.00 is not proof of no spend timezone reporting timezones are known IANA zones omp settings the effective omp settings overlay each project's workers load, and whether it can be materialised under the session root telegram bot health, and one self-identified probe message flags: --json print the stable report shape {project, checkedAt, status, findings:[{id,status,summary,fix?}]} — the CI contract --probe-telegram the ONE opt-in side effect: send one self-identified test message through the report transport --project NAME narrow to one project. With no name, the host-wide facts are checked once and every configured project's per-project facts (labels, timezone, telegram) are checked, each named. Exit code is 0 only when nothing failed; warnings do not fail the run.`; const LABEL_BY_STATUS: Record = { pass: "PASS", warn: "WARN", fail: "FAIL" }; /** Renders the same findings `--json` emits, so the two cannot disagree. */ export function renderDoctorReport(report: DoctorReport): string { const width = Math.max(...report.findings.map((f) => f.id.length).concat(0), 8); const lines = [ `omp-conductor doctor — ${report.project}`, `status: ${report.status.toUpperCase()} (${report.findings.filter((f) => f.status === "fail").length} failed · ${report.findings.filter((f) => f.status === "warn").length} warnings) · checked ${report.checkedAt}`, "", ]; for (const f of report.findings) { const pad = " ".repeat(Math.max(0, width - f.id.length)); lines.push(` ${LABEL_BY_STATUS[f.status].padEnd(5)} ${f.id}${pad} ${f.summary}`); if (f.fix !== undefined) lines.push(` ${" ".repeat(width)} fix: ${f.fix}`); } return `${lines.join("\n")}\n`; } function styleDoctorReport(report: DoctorReport, text: string): string { return text .split("\n") .map((line, index) => { if (index === 0) return heading(line); if (index === 1) return report.status === "fail" ? fail(line) : report.status === "warn" ? warn(line) : ok(line); if (line.startsWith(" FAIL ")) return fail(line); if (line.startsWith(" WARN ")) return warn(line); if (line.startsWith(" PASS ")) return ok(line); if (line.includes(" fix: ")) return dim(line); return line; }) .join("\n"); } export async function doctorCommand(ctx: CommandContext): Promise { // Help first: parsing stops before any probe, config read, or side effect. if (ctx.argv[1] === "--help" || ctx.argv[1] === "-h") { process.stdout.write(`${DOCTOR_USAGE}\n`); return; } // Every remaining token after the verb is accounted for: the two flag // presence markers and the `--project NAME` pair cli.ts already parsed. const DOCTOR_FLAGS: Record = { "--json": true, "--probe-telegram": true, "--project": true, }; for (let i = 1; i < ctx.argv.length; i++) { const token = ctx.argv[i]; if (token === undefined) continue; if (DOCTOR_FLAGS[token] !== true) { process.stderr.write(`omp-conductor: doctor: unexpected argument "${token}"\n`); process.exit(2); } if (token === "--project") i += 1; } const report = await runDoctor(ctx.projectFlag, { probeTelegram: ctx.argv.includes("--probe-telegram"), }); if (ctx.argv.includes("--json")) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`); else process.stdout.write(styleDoctorReport(report, renderDoctorReport(report))); // The one contract CI reads: nonzero iff something failed. Warnings pass. if (report.status === "fail") process.exitCode = 1; }