AI Workflow
A few things the compiler can't generate on its own: a clear field label, a friendly error message, a believable sample name. Those have to be written, by a person or these days an AI agent.
So RunTypes splits the work cleanly. The compiler writes the code; the agent fills in the blanks. From your type, the compiler generates a real, committed source file. Every field is already in place and correctly typed, with the spots that need a human touch left blank and marked. The agent just fills those blanks. It can't get the shape wrong, because the compiler built it.
How it works
Three steps, and the compiler does two of them:
1The compiler scaffolds
From your type, enrich writes a real source file: one entry per field, correctly typed, with each blank marked @todo. The structure is correct by construction.
2The agent fills the blanks
The fields are already laid out, so it writes the labels, messages and sample values into the blanks, clearing each @todo as it goes.
3The compiler checks & keeps in sync
It validates every value against the type, and as the type changes it updates the scaffold: new fields get new blanks, removed ones are flagged, and your edits are preserved.
The compiler never calls an AI model during a build, so builds stay fast and predictable. Filling the blanks is a separate, opt-in step the agent runs on its own. The result is always a reviewable, committed diff you approve like any other change.
Enrich Skills
There are ready-made agent skills that teach an AI agent the whole enrichment workflow. The main skill covers the command loop plus the JSDoc tags it relies on (@rtType, @rtIds, @rtOrphan, @rtOrphanChild and @todo), so the agent can drive enrich and its --update, --prune and --no-emit modes correctly. Two companion skills cover authoring in depth, one for friendly labels and error messages, one for mock data.
Install them into your project's skills folder with a single command:
npx ts-runtypes-skills --claude
npx ts-runtypes-skills --agent
--claude writes the skills into .claude/skills/; --agent writes them into .agent/skills/. Use --dir <path> to install into a custom folder instead.
Once installed, the agent picks the right skill up automatically whenever it works on an enrichment task. No extra prompting needed.
What the compiler generates
enrich produces an ordinary TypeScript file, committed next to your code and safe to hand-edit. Every field is already there and fully typed. The blanks are marked, so the agent (or you) knows exactly what's left to fill:
// generated by the compiler, the @todo marks what's left to fill in
// @todo: add realistic sample data
export const mockUser: MockData<User> = {
name: { pool: [] }, // ← believable names go here
age: { pool: [] }, // ← a realistic range
email: { pool: [] }, // ← real-looking addresses
};
The agent fills the empty pools; the type annotation keeps it honest. When your type gains a field, the compiler adds a new blank for it. When a field changes type, it flags the old value as stale. When a field is removed, it tidies up. You never regenerate by hand, and your filled-in values are never clobbered.
On disk, the generated files mirror your source tree under <genDir>/enriched (the conventional home under your genDir, by default src/__runtypes/enriched), one folder per family: friendly maps under friendly/, mock data under mock/, and translations under i18n/<locale>/. So the maps for models/user.ts land in:
src/__runtypes/enriched/friendly/models/user.ts
src/__runtypes/enriched/mock/models/user.ts
Commands
A small CLI drives the loop, for an agent or for your CI. The same checks also run during your normal build. The CLI verbs are the authoring path, and an opt-in bundler-plugin option can run the same scaffold and sync for you during dev, so a running dev server keeps the mirrors current without a manual command (see Configuration).
| Command | What it does |
|---|---|
enrich <file> <Type> | Scaffold the source file with the blanks the agent fills. |
enrich … --update | Re-sync the file after a type change, keeping your filled-in values. |
enrich … --prune | Clean up entries left behind by deleted types or fields. |
enrich [files] --no-emit | Health check, writing nothing. Fails on wrong or stale content (a field that no longer exists, a leftover carcass), and treats an unfilled blank (a @todo line, an empty label, an empty pool) as expected work in progress. Good for a quick pre-commit check. |
enrich [files] --require-complete | The stricter gate. Everything --no-emit catches, plus it fails when anything is still unfinished (an unfilled @todo, a blank value, a missing or out of date translation). This is the gate a production build enforces. Good for CI and release checks. |
enrich --i18n <locale> | Scaffold the translation files for a locale, or all for every configured one. Takes --update and --prune like plain enrich. |
enrich --i18n <locale> --no-emit | Report a locale's translation status (or all), writing nothing. Add --require-complete (or set i18n.strict) to fail CI on anything untranslated or out of date. |
Both check lanes report the dirty-state details themselves (an unfilled @todo, a blank value, a stale @rtOrphan carcass) and take a --json flag for scripts. The difference is only what makes them fail: --no-emit answers "is what I wrote correct?" and passes over the blanks you have not filled yet, while --require-complete answers "is it correct and finished?" and fails on those blanks. A committed blank ships blank to the app, so --require-complete treats an empty label the same as an unresolved @todo. The same findings power the lint rules in the Linting guide, so a team can see them live in the editor and block unfinished enrichment files at commit time.
Every command also takes a --tsconfig <path> flag. Without it, the CLI finds your tsconfig the same way tsc does, searching upward from the directory you run it in, so types resolve exactly as they do in your build. A config that is named or found but does not load stops the command with an error instead of silently falling back.
These map cleanly onto MCP tools, so any agent (Claude Code, Cursor, your own) can drive the whole thing.
FriendlyText<T> / MockData<T> types (checked against your type by your own TypeScript compiler), the createFriendlyText and createFriendlyTextI18n renderers, the createMockDataFn({ data }) integration, and the CLI commands above. The always-on build diagnostics described here are in active development.Next, the two kinds of data themselves: human-readable labels & errors and real-world mock data, then translations for the friendly text.