STAGE 3 / Projects and tooling · LESSON 19
Why this exists
A program consuming CLI output needs a stable data schema and an exit status it can trust. Agent-readable software should not require brittle scraping of colored terminal output.
Stable tool contract
Exit categories are 0 success, 1 unmet gate or generic failure, 2 command usage, 3 lexer/parser failure, 4 runtime failure, 5 I/O failure, and 6 internal/tooling failure. Treat a nonzero exit status as failure even if the output contains a success message.
Successful --json commands put machine JSON on stdout. Failures generally use stderr. Two documented exceptions are run --json-runtime-diagnostics and lsp-rename --json: their runtime failure envelopes are on stdout with a nonzero exit. A consumer must know the specific command contract instead of assuming all JSON commands behave identically.
kujo check --json returns a check object including status and statement/bytecode counts. It does not execute script effects. kujo doctor helps diagnose the environment, not your business logic. Use their actual help and contracts rather than inventing a universal --json flag for every command.
Application contract
The example uses a schema identifier, a boolean result, and a count. The same data can feed a human renderer and a machine renderer. Keep narration off stdout when it is reserved for JSON. Do not serialize arbitrary runtime handles; select the fields that form the external interface.
Professional pattern
Version payload-affecting changes. Add fields compatibly where the consumer contract allows it; removing or changing a field type can break automation. Test both successful and failed results, including exit status. Stable ordering helps reproducibility, but clients should parse JSON rather than compare dictionary display text.
Evidence drill
Run check --json on the success example, then run the breaking example with --json-runtime-diagnostics. Inspect stdout, stderr, and the status separately. The course keeps captured runtime diagnostics and additional command receipts in its verification ledger.
Working example
let result := {"schema": "course.report/v1", "ok": true, "count": 2}
assert_equal(result["ok"], true)
print(to_json(result))
Run it
From the course repository root, use the pinned Kujo 1.9.0 runtime.
kujo check examples/19.kujo
kujo run --untrusted examples/19.kujo
Captured output
{"count":2,"ok":true,"schema":"course.report/v1"}
Break it and diagnose it
Run this with --json-runtime-diagnostics as an additional drill. JSON on stdout must not override the nonzero failure exit.
throw("report validation failed")
kujo run --untrusted examples/19-break.kujo
Exit status: 4. Captured diagnostic:
[KUJOVM001] [vm] Runtime Error: report validation failed
--> 0:0
Exercise
Add human and machine renderers to your report. Keep the same underlying data. Test that machine stdout parses as one JSON value and that failed work cannot be mistaken for a passing gate.
Checkpoint
- I preserve authoritative exit status.
- I know the documented JSON failure exceptions.
- I version application result shapes.
Bounded stdin in 1.5.0
read_stdin(max_bytes) reads exact UTF-8 through EOF, preserving whitespace. The limit must be 1–8,388,608 bytes. Overflow, malformed UTF-8, and I/O failures are errors; the API does not truncate silently. It needs no ambient filesystem capability because stdin is an explicit process input stream.
The caller must close the stream and enforce a process deadline. A byte ceiling cannot terminate a producer that stalls before EOF. examples/supplemental/v1-5-stdin.kujo uses a four-byte ceiling. The release verifier sends whitespace-preserving input, five bytes, and invalid UTF-8, then checks the actual success or diagnostic in both runtimes.
Professional pattern: read a bounded request, parse it, validate its schema, and emit a separate structured result. Exercise: add schema validation to the stdin example and test an empty stream, malformed JSON, and an oversized request.
Verified 1.5.0 example
// The caller must close stdin and enforce a process deadline.
print(to_json(read_stdin(4)))
Download this example. The repository release verifier supplies its fixtures and records success and failure diagnostics.
Verified 2026-10-22 · Official Kujo 1.9.0 release · Source contract · Download example