A tool's checks, such as a doctor or a self-check, declared once: progress while they run, the verdict first and the results grouped for a person, the same facts as --json for an agent, the time taken and the exit code. A problem comes with its fix: the exact command, or the thing to do by hand, and never a developer command or a repository path (ax-dev-commands) unless the fix is marked sourceCheckout: true.
npx shadcn@latest add ourostack/ouro-ui/ax-checks#v1.3.10It installs lib/ouro/ax/checks.ts, with ax-dev-commands, ax-exit-codes, ax-outcome, ax-terminal. Its tests are ax-checks-tests. Every item runs on Node 22.18 or later as it is copied: the files import each other as ./<file>.ts. The Ouro AX guide shows how they fit together.
Exports
CheckDef
interface CheckDef<C> {
id: string;
label: string;
needs?: readonly string[];
run(context: C): CheckResult | Promise<CheckResult>;
}CheckFix
A tool's checks, such as a doctor or a self-check, declared once: progress while they run, the verdict first and the results grouped for a person, the same facts as --json for an agent, the time taken and the exit code. A problem comes with its fix: the exact command, or the thing to do by hand, and never a developer command or a repository path (ax-dev-commands) unless the fix is marked sourceCheckout: true.
type CheckFix = NextStep | { byHand: string; sourceCheckout?: true };CheckResult
What a check found. A warning or a failure carries its fix; a check that was not run says why, and kind names what it waits on, read after a count ("waiting on something you name", "by hand"), so the not-run line groups them.
type CheckResult =
| { state: "passed"; detail?: string }
| { state: "warned" | "failed"; detail: string; fix: CheckFix }
| { state: "skipped"; detail: string; kind?: string; fix?: CheckFix };CheckRow
interface CheckRow {
id: string;
label: string;
state: CheckState;
detail: string;
fix: CheckFix | null;
kind: string | null;
durationMs: number;
}Checks
interface Checks<C> {
readonly def: ChecksDef<C>;
run(context: C, options?: ChecksRunOptions): Promise<ChecksReport>;
}CHECKS_VERSION
The version of the report's shape; --json carries it as ax, as an outcome or a wait does.
const CHECKS_VERSION: "checks/1"ChecksDef
interface ChecksDef<C> {
command: string;
noun?: readonly [string, string];
checks: readonly CheckDef<C>[];
}ChecksReport
One run's facts, the same for the person's text and the agent's --json.
interface ChecksReport {
ax: typeof CHECKS_VERSION;
verdict: Verdict;
summary: string;
durationMs: number;
counts: Record<CheckState, number>;
rows: CheckRow[];
next: NextStep[];
exitCode: number;
}ChecksRunOptions
interface ChecksRunOptions {
progress?: ProgressStream;
env?: Readonly<Record<string, string | undefined>>;
off?: boolean;
timers?: Timers;
next?: readonly NextStep[];
}CheckState
errored is a check that threw before finishing, so the tool broke rather than found something.
type CheckState = "passed" | "warned" | "failed" | "errored" | "skipped";checksText
The report for a person: the verdict on the first line, then the results grouped (what failed, what warned, what was not run, and what passed with verbose), each problem with its fix, then the next steps. On success it prints the verdict and what is left to act on, and with verbose every check and its time. Off a terminal the marks are words and there is no colour, so the first line reads FAIL 1 of 12 checks failed: ….
function checksText(report: ChecksReport, style: TerminalStyle, options?: { verbose?: boolean | undefined; command?: string | undefined; }): stringdefineChecks
Declares a tool's checks once: from this one definition come the progress while they run, the verdict and the grouped results for a person, the --json for an agent, the timing and the exit code. Throws at definition time on an empty list, a repeated id, an empty label or a needs that names no earlier check, and at run time when a warning or failure has no fix, or its fix is the command just run, so a tool's own tests catch both. A fix or a next step that asks for a developer command or a repository path throws too, so a doctor never tells a person to run a repository script.
function defineChecks<C>(def: ChecksDef<C>) => Checks<C>Example:
const doctor = defineChecks({
command: "notes doctor",
checks: [
{ id: "store", label: "Notes file", run: () => ({ state: "passed" }) },
{ id: "sync", label: "Last sync", needs: ["store"], run: () => ({ state: "warned", detail: "last synced 3 days ago", fix: { command: "notes sync", why: "Sync now" } }) },
],
});
const report = await doctor.run(context, { progress: process.stderr, env: process.env });isChecksReport
Whether a value is a checks report: what the X-ray looks for in a read's --json.
function isChecksReport(value: unknown): value is ChecksReportprintChecks
Prints a report the way the kit's commands print a result: --json as one JSON document, otherwise the text for the stream it goes to, each with one trailing newline. Returns the exit code.
function printChecks(report: ChecksReport, io: { stdout: { write(s: string): unknown; isTTY?: boolean | undefined; }; env: Readonly<Record<string, string | undefined>>; }, options?: { json?: boolean | undefined; verbose?: boolean | undefined; command?: string | undefined; }): numberVerdict
type Verdict = "passed" | "warned" | "failed" | "errored";