What a repository hands the checker: its agent-facing words as one JSON file a repository can produce cheaply. The checker reads this, not the source, so a CLI, an MCP server, a hook or a script take the same path in.
errors: the error and refusal texts an agent can hit, with the stablecodeof a text that has one.tools: the tools or commands, with the description an agent loads.replies: the actions' replies, with the field names they carry.texts: other words that speak to an agent, such as a hook's text or a reply's message.
An id is stable across edits to the wording (a code, a tool name, or file#first-words), because the ratchet records findings by it.
npx shadcn@latest add ourostack/ouro-ui/ax-surfaces#v1.3.68It installs lib/ouro/ax/surfaces.ts, with ax-dev-commands, ax-exit-codes, ax-problem. Its tests are ax-surfaces-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
BaselineResult
interface BaselineResult {
added: SurfaceFinding[];
fixed: string[];
known: number;
ok: boolean;
}checkSurfaces
Runs the checks over a manifest. Throws, with the fix, on a manifest that is not one. A finding names its check, the surface, what is wrong and what to change; the order is the manifest's.
function checkSurfaces(manifest: Surfaces): SurfacesReportExample:
checkSurfaces({ ax: "surfaces/1", tool: "notes", errors: [{ id: "send-empty", text: "The draft is empty. Pass --text.", code: "send_empty" }] }).findings;
// [{ check: "error.leads-with-fix", surface: "send-empty", ... }]countFindings
The findings per group, which is what the baseline file records.
function countFindings(report: SurfacesReport): Record<string, number>ErrorSurface
interface ErrorSurface {
id: string;
text: string;
code?: string;
where?: string;
}extractSurfaces
The error texts in JavaScript and TypeScript source: the first string (or strings joined with +) after a throw, an Error constructor, console.error, a stderr write, die, fail, refuse, or an error: or message: property, outside tests, when it has several words. An id is <path>#<first words>, with ~2 for a repeat, so rewording an error's end keeps its id. A code: on the same line, or on the line before or after, gives the error its code. It reads the source as text, so it can miss a text built elsewhere and read a string that is not an error: its manifest is a start to edit, and the ratchet works from the manifest you keep.
function extractSurfaces(options: { root: string; paths: readonly string[]; tool: string; }): SurfacesExample:
extractSurfaces({ root: ".", paths: ["src"], tool: "notes" }).errors;FIX_VERBS
The first words of a sentence that tell the reader what to do. A fix that opens an error starts with one of these (after an optional "Please" or "Then"), or with a label such as "Fix:" or "To continue,".
const FIX_VERBS: ReadonlySet<string>leadsWithFix
Whether an error's words open with the fix: after a leading tool: or thing: label, the first sentence is a command (starts with an imperative verb such as "Run", "Pass" or "Call") or a label such as "Fix:" or "To continue,". later says the fix does come, in a sentence after the problem.
function leadsWithFix(text: string): { leads: boolean; later: boolean; }Example:
leadsWithFix("Pass --why. The send reaches other people."); // { leads: true, later: false }
leadsWithFix("send: the draft is empty. Fix: pass --text."); // { leads: false, later: true }main
The checker's command line. check reads a manifest and exits with the Ouro AX codes: ok, findings (with a baseline: a finding it does not list, or a listed one that is fixed), usage and error. check --init writes the first baseline and refuses to overwrite one; --update rewrites it with the findings that remain and refuses to add one. extract writes a manifest from source.
function main(args: readonly string[], io?: Io): Promise<number>ratchet
Compares a run to the recorded baseline, which counts findings per check and file (the group error.has-code:src/send.js), so rewording an error or moving it in its file leaves the baseline as it was. A group with more findings than recorded fails, and so does one with fewer: the pull request that fixes a finding lowers its count, so the count only goes down.
function ratchet(report: SurfacesReport, baseline: Readonly<Record<string, number>>): BaselineResultExample:
ratchet(report, { "error.has-code:src/send.js": 2 }); // { added: [], fixed: ["error.has-code:src/send.js: was 2, now 1"], known: 1, ok: false }ReplySurface
interface ReplySurface {
id: string;
action: boolean;
fields: string[];
where?: string;
}SURFACE_CHECKS
The checks, in report order, and the rule they hold a surface to.
const SURFACE_CHECKS: readonly { id: string; rule: string }[]SurfaceFinding
interface SurfaceFinding {
check: string;
surface: string;
where: string | null;
problem: string;
fix: string;
key: string;
group: string;
}Surfaces
What a repository hands the checker: its agent-facing words as one JSON file a repository can produce cheaply. The checker reads this, not the source, so a CLI, an MCP server, a hook or a script take the same path in.
errors: the error and refusal texts an agent can hit, with the stablecodeof a text that has one.tools: the tools or commands, with the description an agent loads.replies: the actions' replies, with the field names they carry.texts: other words that speak to an agent, such as a hook's text or a reply's message.
An id is stable across edits to the wording (a code, a tool name, or file#first-words), because the ratchet records findings by it.
interface Surfaces {
ax: typeof SURFACES_VERSION;
tool: string;
errors?: ErrorSurface[];
tools?: ToolSurface[];
replies?: ReplySurface[];
texts?: TextSurface[];
outcomeFields?: string[];
limits?: Partial<SurfacesLimits>;
}SURFACES_BASELINE_VERSION
The baseline file's version.
const SURFACES_BASELINE_VERSION: "surfaces-baseline/2"SURFACES_DEFAULT_LIMITS
const SURFACES_DEFAULT_LIMITS: SurfacesLimitsSURFACES_VERSION
The manifest's version, written as its ax field.
const SURFACES_VERSION: "surfaces/1"SurfacesLimits
interface SurfacesLimits {
toolDescription: number;
outcomeFields: string[];
}SurfacesReport
interface SurfacesReport {
tool: string;
surfaces: number;
findings: SurfaceFinding[];
}surfacesText
The report for an agent or a person: the verdict on the first line, then the findings as <check> <surface> (<where>): <problem>. Fix: <fix>., then what the ratchet wants.
function surfacesText(report: SurfacesReport, result: BaselineResult | null, options?: { baselinePath?: string | undefined; }): stringTextSurface
interface TextSurface {
id: string;
text: string;
where?: string;
}ToolSurface
interface ToolSurface {
name: string;
description: string;
where?: string;
}
