Skip to main content

Ouro AX

ax-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 stable code of 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.68

It 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): SurfacesReport

Example:

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; }): Surfaces

Example:

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>>): BaselineResult

Example:

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 stable code of 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: SurfacesLimits

SURFACES_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; }): string

TextSurface

interface TextSurface {
  id: string;
  text: string;
  where?: string;
}

ToolSurface

interface ToolSurface {
  name: string;
  description: string;
  where?: string;
}