@directive-run/claude-plugin 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE +26 -0
  3. package/README.md +113 -0
  4. package/dist/index.cjs +2 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +66 -0
  7. package/dist/index.d.ts +66 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +64 -0
  11. package/skills/README.md +9 -0
  12. package/skills/building-ai-agents/SKILL.md +414 -0
  13. package/skills/building-ai-agents/ai-adapters.md +257 -0
  14. package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
  15. package/skills/building-ai-agents/ai-communication.md +304 -0
  16. package/skills/building-ai-agents/api-skeleton.md +5 -0
  17. package/skills/building-ai-agents/examples.md +849 -0
  18. package/skills/building-ai-orchestrators/SKILL.md +401 -0
  19. package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
  20. package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
  21. package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
  22. package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
  23. package/skills/building-ai-orchestrators/examples.md +1609 -0
  24. package/skills/building-directive-systems/SKILL.md +499 -0
  25. package/skills/building-directive-systems/api-skeleton.md +5 -0
  26. package/skills/building-directive-systems/examples.md +4149 -0
  27. package/skills/building-directive-systems/multi-module.md +327 -0
  28. package/skills/building-directive-systems/plugins.md +361 -0
  29. package/skills/building-directive-systems/react-adapter.md +376 -0
  30. package/skills/building-directive-systems/system-api.md +463 -0
  31. package/skills/getting-started-with-directive/SKILL.md +198 -0
  32. package/skills/getting-started-with-directive/api-skeleton.md +5 -0
  33. package/skills/getting-started-with-directive/core-patterns.md +240 -0
  34. package/skills/getting-started-with-directive/examples.md +87 -0
  35. package/skills/getting-started-with-directive/sitemap.md +202 -0
  36. package/skills/hardening-ai-systems/SKILL.md +377 -0
  37. package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
  38. package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
  39. package/skills/hardening-ai-systems/ai-security.md +400 -0
  40. package/skills/hardening-ai-systems/api-skeleton.md +5 -0
  41. package/skills/hardening-ai-systems/examples.md +628 -0
  42. package/skills/migrating-to-directive/SKILL.md +249 -0
  43. package/skills/migrating-to-directive/anti-patterns.md +382 -0
  44. package/skills/migrating-to-directive/api-skeleton.md +5 -0
  45. package/skills/migrating-to-directive/core-patterns.md +240 -0
  46. package/skills/migrating-to-directive/examples.md +511 -0
  47. package/skills/migrating-to-directive/schema-types.md +273 -0
  48. package/skills/reviewing-directive-code/SKILL.md +232 -0
  49. package/skills/reviewing-directive-code/anti-patterns.md +382 -0
  50. package/skills/reviewing-directive-code/api-skeleton.md +5 -0
  51. package/skills/reviewing-directive-code/core-patterns.md +240 -0
  52. package/skills/reviewing-directive-code/examples.md +463 -0
  53. package/skills/reviewing-directive-code/naming.md +321 -0
  54. package/skills/scaffolding-directive-modules/SKILL.md +218 -0
  55. package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
  56. package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
  57. package/skills/scaffolding-directive-modules/examples.md +1386 -0
  58. package/skills/scaffolding-directive-modules/naming.md +321 -0
  59. package/skills/scaffolding-directive-modules/schema-types.md +273 -0
  60. package/skills/testing-ai-systems/SKILL.md +398 -0
  61. package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
  62. package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
  63. package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
  64. package/skills/testing-ai-systems/api-skeleton.md +5 -0
  65. package/skills/testing-ai-systems/examples.md +1261 -0
  66. package/skills/testing-directive-code/SKILL.md +479 -0
  67. package/skills/testing-directive-code/api-skeleton.md +5 -0
  68. package/skills/testing-directive-code/examples.md +156 -0
  69. package/skills/testing-directive-code/history.md +351 -0
  70. package/skills/testing-directive-code/testing.md +412 -0
  71. package/skills/writing-directive-constraints/SKILL.md +486 -0
  72. package/skills/writing-directive-constraints/api-skeleton.md +5 -0
  73. package/skills/writing-directive-constraints/constraints.md +274 -0
  74. package/skills/writing-directive-constraints/error-boundaries.md +330 -0
  75. package/skills/writing-directive-constraints/examples.md +1361 -0
  76. package/skills/writing-directive-constraints/resolvers.md +367 -0
  77. package/skills/writing-directive-modules/SKILL.md +342 -0
  78. package/skills/writing-directive-modules/anti-patterns.md +382 -0
  79. package/skills/writing-directive-modules/api-skeleton.md +5 -0
  80. package/skills/writing-directive-modules/core-patterns.md +240 -0
  81. package/skills/writing-directive-modules/examples.md +1639 -0
  82. package/skills/writing-directive-modules/naming.md +321 -0
  83. package/skills/writing-directive-modules/schema-types.md +273 -0
@@ -0,0 +1,321 @@
1
+ # Naming Conventions
2
+
3
+ > Covers all `@directive-run/*` packages — terminology, parameter names, return-style rules.
4
+
5
+ Directive uses precise vocabulary by design: each concept has one canonical name across every package, every language adapter, and every doc. The names are non-negotiable for code Directive ships, AND every term has a cross-paradigm alias so a developer coming from Redux / Zustand / XState / Jotai / React Query can find the right Directive concept on the first search.
6
+
7
+ This file is both the rules and the bridge. Search for the term you already know; you'll land on the Directive equivalent.
8
+
9
+ ## Coming from another library? Start here
10
+
11
+ | If you call it… | (in this library) | Directive calls it… | Why the name |
12
+ |---|---|---|---|
13
+ | **state**, **store**, **atom**, **signal**, **observable** | Redux, Zustand, Jotai, Preact Signals, MobX | **facts** | "Facts about the world" — the system observes them. Same value-bag role as state, with a proxy that auto-tracks reads. |
14
+ | **selector**, **computed**, **getter**, **memo**, **derived store** | Redux, Vue/MobX, Zustand getters, Memoize, Svelte | **derivations** / `derive` | Auto-tracked computed values. No dep arrays. Directive's name aligns with "derived from facts." |
15
+ | **action**, **dispatch**, **reducer**, **command**, **intent** | Redux, Redux Toolkit, CQRS | **events** + **resolvers** | Events are the user-facing trigger; resolvers fulfill them. Reducers are split into the two halves Directive cares about. |
16
+ | **thunk**, **saga**, **effect**, **middleware**, **listener** | Redux Toolkit, redux-saga, redux-observable, NgRx | **resolvers** | Async logic that mutates state in response to a requirement. Same role as a thunk; declared once, run by the runtime. |
17
+ | **rule**, **condition**, **guard**, **trigger**, **policy** | XState guards, Cerbos, OPA, business-rule engines | **constraints** | Declarative "when X is true, Y must happen." Constraints emit requirements; resolvers fulfill them. |
18
+ | **request**, **query**, **side-effect spec** | React Query / TanStack Query queries, Apollo queries | **requirements** | The object a constraint emits. The runtime dedupes them, dispatches to resolvers, tracks status. |
19
+ | **subscription**, **listener**, **reaction**, **watcher** | Zustand subscribe, MobX reactions, Redux subscriptions | **effects** | Side-effects that run when watched facts change. Cleaner than `subscribe()` callbacks because effects auto-track. |
20
+ | **slice**, **feature**, **domain**, **bounded context** | Redux Toolkit slices, NgRx feature modules | **modules** | Encapsulates facts + derivations + constraints + resolvers + events for one bounded slice of the system. |
21
+ | **store**, **container**, **context**, **app state** | Redux Store, Pinia, React Context, NgRx Store | **system** | The runtime that wires modules together, evaluates constraints, dispatches resolvers, and exposes observation. |
22
+ | **state machine**, **statechart**, **xstate machine** | XState | (closest: **module** + **constraints**) | Directive isn't a state machine — but if your XState `states` model business rules (not UI flow), constraints + facts express the same logic declaratively with no transition functions. |
23
+ | **cache**, **query cache**, **stale-while-revalidate** | TanStack Query, SWR, Apollo cache | (closest: `@directive-run/query` + **constraints**) | Use `@directive-run/query` for the cache; constraints decide when refetches fire. Directive doesn't replace TanStack Query — it composes with it. |
24
+ | **agent**, **chain**, **graph**, **workflow** | LangChain, LangGraph, OpenAI Assistants | **agent** + **orchestrator** | `@directive-run/ai` keeps the names — agents are agents, orchestrators are orchestrators — but they run under the same constraint engine as the rest of Directive. |
25
+ | **guardrail**, **filter**, **safety check**, **moderation** | OpenAI moderation API, NVIDIA NeMo Guardrails | **guardrails** | Same name, same role; declarative input/output/tool-call checks. |
26
+
27
+ If your term isn't in the table, search the canonical name in this doc's [Terminology quick reference](#terminology-quick-reference) — both directions are listed.
28
+
29
+ ## Canonical terms (non-negotiable for code Directive ships)
30
+
31
+ Above is the bridge from your existing vocabulary. Below are the canonical names — the only ones that should appear in `@directive-run/*` source, tests, docs, and generated AI rules.
32
+
33
+ ### `facts` — the value bag
34
+
35
+ Not state, not store, not atoms. **Facts.** Accessed at `system.facts.x`. Mutated through events and resolvers. Read through derivations, hooks, and `system.facts.$store`.
36
+
37
+ ### `derivations` — auto-tracked computed values
38
+
39
+ Declared via `derive: { name: (facts) => … }`. Read at `system.derive.name`. The hook is `useDerived` (not `useComputed`, not `useSelector` — Directive's `useSelector` is a different, broader primitive that takes a selector function).
40
+
41
+ ### `constraints` — when-then declarations
42
+
43
+ Declared via `constraints: { name: { when: …, require: … } }`. The `when` returns a boolean; the `require` returns a requirement.
44
+
45
+ ### `resolvers` — requirement fulfillment
46
+
47
+ Declared via `resolvers: { name: { requirement: …, resolve: async (req, context) => … } }`. Mutate `context.facts`; never return data from `resolve`.
48
+
49
+ ### `requirements` — what constraints emit
50
+
51
+ Tagged union `{ type: "FETCH_USER", … }`. Constraints emit them; the runtime dedupes by `type` + `key()`; resolvers fulfill them.
52
+
53
+ ### `effects` — side effects from watched changes
54
+
55
+ Declared via `effects: { name: { run: (facts, prev) => … } }`. Run on relevant fact changes. The auto-tracking dependency model means no `deps` array unless you specifically need a partial dep set.
56
+
57
+ ### `events` — typed mutation entry points
58
+
59
+ Declared via `events: { name: (facts, payload) => … }`. Called as `system.events.name(payload)`. The typed events accessor is the canonical way to dispatch — `system.dispatch({type, ...payload})` works but the typed accessor carries autocomplete.
60
+
61
+ ### `module` — bounded slice
62
+
63
+ Created via `createModule(name, { schema, init, derive, effects, events, constraints, resolvers })`. Composes into systems.
64
+
65
+ ### `system` — the runtime
66
+
67
+ Created via `createSystem({ module })` or `createSystem({ modules: { x, y } })`. Hosts the reconciliation loop.
68
+
69
+ ## Parameter naming
70
+
71
+ ### `req` = requirement (NOT request)
72
+
73
+ The `req` parameter in resolvers and constraint `key()` functions is short for **requirement** — the object emitted by a constraint's `require` property.
74
+
75
+ ```typescript
76
+ // CORRECT — req is a requirement
77
+ resolvers: {
78
+ fetchUser: {
79
+ requirement: "FETCH_USER",
80
+ key: (req) => `fetch-${req.userId}`,
81
+ resolve: async (req, context) => {
82
+ // req.type === "FETCH_USER"
83
+ const user = await fetchUser(req.userId);
84
+ context.facts.user = user;
85
+ },
86
+ },
87
+ },
88
+
89
+ // WRONG — never "request" or "r"
90
+ resolve: async (request, context) => { /* ... */ },
91
+ resolve: async (r, context) => { /* ... */ },
92
+ ```
93
+
94
+ ### `context` is never abbreviated
95
+
96
+ ```typescript
97
+ // CORRECT
98
+ resolve: async (req, context) => {
99
+ context.facts.status = "loaded";
100
+ context.signal; // AbortSignal
101
+ context.snapshot(); // facts snapshot
102
+ },
103
+
104
+ // WRONG — never abbreviate to ctx
105
+ resolve: async (req, ctx) => { /* ... */ },
106
+ ```
107
+
108
+ ## Return style
109
+
110
+ ### Braces for `if` blocks with `return` (NOT for arrow expressions)
111
+
112
+ The brace rule applies to control-flow blocks — not to arrow-expression bodies.
113
+
114
+ **Arrow expressions** (single-line derivations, predicates, computed requirements): the concise form is preferred. No braces, no explicit `return`.
115
+
116
+ ```typescript
117
+ // CORRECT — single-line arrow expressions stay concise
118
+ derive: {
119
+ isReady: (facts) => facts.phase === "ready",
120
+ greeting: (facts) => `Hi, ${facts.name}!`,
121
+ },
122
+
123
+ constraints: {
124
+ check: {
125
+ when: (facts) => facts.count > 10,
126
+ require: { type: "PROCESS" },
127
+ },
128
+ },
129
+ ```
130
+
131
+ **Control-flow statements** (`if`, `for`, `while`): braces required, even for single-line bodies. Single-line `if (x) return y` shapes are never used.
132
+
133
+ ```typescript
134
+ // WRONG — single-line if return
135
+ if (facts.user) return "ready";
136
+
137
+ // CORRECT — always wrap the body in braces
138
+ if (facts.user) {
139
+ return "ready";
140
+ }
141
+ ```
142
+
143
+ ### Blank line before `return`
144
+
145
+ Add a blank line before `return` when there is code above it. Skip the blank line when `return` is the first statement in a block.
146
+
147
+ ```typescript
148
+ // CORRECT — blank line before return when code precedes it
149
+ function getStatus(facts) {
150
+ const phase = facts.phase;
151
+ const hasUser = facts.user !== null;
152
+
153
+ return phase === "ready" && hasUser;
154
+ }
155
+
156
+ // CORRECT — no blank line when return is first statement
157
+ function isReady(facts) {
158
+ return facts.phase === "ready";
159
+ }
160
+
161
+ // CORRECT — blank line after brace-style return block
162
+ function process(facts) {
163
+ if (!facts.ready) {
164
+ return null;
165
+ }
166
+
167
+ const result = computeResult(facts);
168
+
169
+ return result;
170
+ }
171
+ ```
172
+
173
+ ## Multi-line code formatting
174
+
175
+ Never put properties or statements on a single line inside braces. Always expand to one item per line with proper indentation. This applies everywhere: schema definitions, init functions, events, effects, requirement types, and any other object or block.
176
+
177
+ ```typescript
178
+ // WRONG — properties crammed on one line
179
+ schema: {
180
+ facts: { phase: t.string(), count: t.number() },
181
+ requirements: { FETCH_USER: { id: t.string() }, RESET: {} },
182
+ },
183
+
184
+ // CORRECT — one property per line, always expanded
185
+ schema: {
186
+ facts: {
187
+ phase: t.string(),
188
+ count: t.number(),
189
+ },
190
+ requirements: {
191
+ FETCH_USER: {
192
+ id: t.string(),
193
+ },
194
+ RESET: {},
195
+ },
196
+ },
197
+
198
+ // WRONG — statements crammed on one line
199
+ init: (facts) => { facts.phase = "idle"; facts.count = 0; },
200
+
201
+ // CORRECT — one statement per line
202
+ init: (facts) => {
203
+ facts.phase = "idle";
204
+ facts.count = 0;
205
+ },
206
+ ```
207
+
208
+ Single-expression arrows (no braces) are fine on one line. Empty objects `{}` are fine inline.
209
+
210
+ ```typescript
211
+ // OK — single expression, no braces
212
+ derive: {
213
+ isReady: (facts) => facts.phase === "ready",
214
+ },
215
+
216
+ // OK — empty object
217
+ RESET: {},
218
+ ```
219
+
220
+ ## Multi-module naming
221
+
222
+ ### `facts.self.*` for own module
223
+
224
+ In multi-module systems, constraints, effects, and derivations with `crossModuleDeps` receive namespaced facts. Own module facts are always at `facts.self.*`.
225
+
226
+ ```typescript
227
+ // CORRECT
228
+ constraints: {
229
+ loadWhenAuth: {
230
+ when: (facts) => facts.auth.isAuthenticated && !facts.self.loaded,
231
+ require: { type: "LOAD_DATA" },
232
+ },
233
+ },
234
+
235
+ // WRONG — bare facts.* in multi-module context
236
+ constraints: {
237
+ loadWhenAuth: {
238
+ when: (facts) => facts.isAuthenticated && !facts.loaded,
239
+ require: { type: "LOAD_DATA" },
240
+ },
241
+ },
242
+ ```
243
+
244
+ ### System-level access uses dot notation
245
+
246
+ ```typescript
247
+ // CORRECT — dot notation through namespace proxy
248
+ system.facts.auth.token;
249
+ system.facts.cart.items;
250
+ system.derive.auth.isLoggedIn;
251
+ system.events.auth.login({ token: "..." });
252
+
253
+ // WRONG — bracket notation with internal separator
254
+ system.facts["auth::token"];
255
+ system.facts["auth_token"];
256
+ ```
257
+
258
+ ## Type casting rules
259
+
260
+ ### Never cast when reading
261
+
262
+ The schema provides all types. Do not add `as` casts when reading facts or derivations from the system.
263
+
264
+ ```typescript
265
+ // CORRECT — schema provides the type
266
+ const profile = system.facts.profile;
267
+ const isReady = system.derive.isReady;
268
+
269
+ // WRONG — unnecessary cast
270
+ const profile = system.facts.profile as UserProfile;
271
+ const isReady = system.derive.isReady as boolean;
272
+ ```
273
+
274
+ ### Cast only in schema definition
275
+
276
+ Type assertions are only valid in schema definition using the `{} as {}` pattern, or via the `t.*` builders (preferred):
277
+
278
+ ```typescript
279
+ // CORRECT — cast in schema definition
280
+ schema: {
281
+ facts: {} as { profile: UserProfile; settings: AppSettings },
282
+ derivations: {} as { displayName: string },
283
+ },
284
+
285
+ // PREFERRED — t.* builders
286
+ schema: {
287
+ facts: {
288
+ profile: t.object<UserProfile>(),
289
+ settings: t.object<AppSettings>(),
290
+ },
291
+ derivations: {
292
+ displayName: t.string(),
293
+ },
294
+ },
295
+ ```
296
+
297
+ ## Terminology quick reference
298
+
299
+ Two-way lookup. Search the term you know.
300
+
301
+ | Directive term | Cross-paradigm aliases (use these to search) |
302
+ |---|---|
303
+ | **facts** | state, store, atoms, signals, observables, model |
304
+ | **derivations** / `derive` | computed, selectors, getters, memos, derived stores, $: blocks (Svelte) |
305
+ | **constraints** | rules, conditions, triggers, guards, policies, invariants |
306
+ | **resolvers** | handlers, actions, reducers, sagas, thunks, effects (Redux), middleware, listeners |
307
+ | **requirements** | requests, commands, intents, queries (TanStack), tasks |
308
+ | **effects** | watchers, subscriptions, reactions, autoruns, listeners |
309
+ | **events** | actions, intents, commands, methods (Pinia), signals (NgRx) |
310
+ | **module** | slice, feature, domain, bounded context, NgRx feature module |
311
+ | **system** | store, container, context, app state, app shell |
312
+ | **`req`** (parameter) | request, r, requirement (spelled out) |
313
+ | **`context`** (parameter) | ctx, c, resolverContext |
314
+
315
+ The forbidden direction is also non-negotiable: in code Directive ships, the columns reverse. Do NOT use `state`, `store`, `selectors`, `computed`, `actions`, `reducers`, `subscriptions`, `slices`, `request`, or `ctx` in `@directive-run/*` source. The alias map is for retrieval — the canonical names are for code.
316
+
317
+ ## See also
318
+
319
+ - [`core-patterns.md`](./core-patterns.md) — the actual code shapes every naming rule here applies to
320
+ - [`multi-module.md`](./multi-module.md) — `facts.self.*` convention and dot-notation rules
321
+ - [`anti-patterns.md`](./anti-patterns.md) — naming-shaped mistakes (`request` for `req`, `ctx` for `context`, hallucinated TS schema types)
@@ -0,0 +1,273 @@
1
+ # Schema Types
2
+
3
+ > Covers `@directive-run/core` — schema builders (`t.string` / `t.number` / `t.array` / etc.) and inference helpers.
4
+
5
+ The `t.*()` builders define fact types in module schemas. They provide runtime validation in dev mode and full TypeScript inference.
6
+
7
+ ## Decision Tree: "Which type builder do I use?"
8
+
9
+ ```
10
+ What kind of value?
11
+ ├── String → t.string() or t.string<"a" | "b">() for literal unions
12
+ ├── Number → t.number() with optional .min() / .max()
13
+ ├── Boolean → t.boolean()
14
+ ├── Array → t.array<ItemType>() with optional .of() / .nonEmpty()
15
+ ├── Object/Record → t.object<Shape>()
16
+ ├── Fixed set of string values → t.enum("a", "b", "c")
17
+ ├── Exact value → t.literal(42) or t.literal("active")
18
+ ├── Nullable → t.nullable(t.string())
19
+ ├── Optional → t.optional(t.number())
20
+ ├── Union → t.union(t.string(), t.number())
21
+ ├── Map/Set → t.object<Map<K,V>>() or t.object<Set<T>>()
22
+ ├── Date → t.object<Date>() or t.number() for timestamps
23
+ ├── Unknown/any → t.object<unknown>()
24
+ └── Any type + metadata → t.string().meta({ label: "Email", tags: ["pii"] })
25
+ ```
26
+
27
+ All builders support `.meta({ label, description, category, color, tags })` for devtools/AI annotations. Chains with other methods: `t.string().meta({...}).nullable()`.
28
+
29
+ ## Primitive Types
30
+
31
+ ```typescript
32
+ import { createModule, t } from "@directive-run/core";
33
+
34
+ const myModule = createModule("example", {
35
+ schema: {
36
+ facts: {
37
+ // Basic string
38
+ name: t.string(),
39
+
40
+ // String literal union – full type safety
41
+ phase: t.string<"idle" | "loading" | "done">(),
42
+
43
+ // Number with validation
44
+ count: t.number(),
45
+ age: t.number().min(0).max(150),
46
+
47
+ // Boolean
48
+ isActive: t.boolean(),
49
+ },
50
+ },
51
+ init: (facts) => {
52
+ facts.name = "";
53
+ facts.phase = "idle";
54
+ facts.count = 0;
55
+ facts.age = 25;
56
+ facts.isActive = false;
57
+ },
58
+ });
59
+ ```
60
+
61
+ ## Complex Types
62
+
63
+ ```typescript
64
+ schema: {
65
+ facts: {
66
+ // Object with shape
67
+ user: t.object<{ id: string; name: string; email: string }>(),
68
+
69
+ // Nullable object
70
+ profile: t.object<{ bio: string; avatar: string } | null>(),
71
+
72
+ // Array
73
+ tags: t.array<string>(),
74
+ items: t.array<{ id: string; label: string }>().nonEmpty(),
75
+
76
+ // Record (key-value map)
77
+ scores: t.object<Record<string, number>>(),
78
+
79
+ // Nested complex type
80
+ config: t.object<{
81
+ theme: "light" | "dark";
82
+ notifications: { email: boolean; push: boolean };
83
+ }>(),
84
+ },
85
+ },
86
+ ```
87
+
88
+ ## Enum and Literal
89
+
90
+ ```typescript
91
+ schema: {
92
+ facts: {
93
+ // Enum – string literal union from values
94
+ status: t.enum("pending", "active", "archived"),
95
+ // TypeScript type: "pending" | "active" | "archived"
96
+
97
+ // Literal – exact match
98
+ version: t.literal(2),
99
+ mode: t.literal("strict"),
100
+ enabled: t.literal(true),
101
+ },
102
+ },
103
+ ```
104
+
105
+ ## Nullable and Optional
106
+
107
+ ```typescript
108
+ schema: {
109
+ facts: {
110
+ // Nullable – T | null
111
+ selectedItem: t.nullable(t.string()),
112
+
113
+ // Optional – T | undefined
114
+ nickname: t.optional(t.string()),
115
+
116
+ // Union – combine types
117
+ result: t.union(t.string(), t.number()),
118
+
119
+ // Nullable via object generic (also valid)
120
+ user: t.object<{ id: string } | null>(),
121
+ },
122
+ },
123
+ ```
124
+
125
+ ## Chainable Methods (Available on All Types)
126
+
127
+ ```typescript
128
+ schema: {
129
+ facts: {
130
+ // Default value – used if init doesn't set it
131
+ theme: t.string<"light" | "dark">().default("light"),
132
+
133
+ // Custom validation – runs in dev mode
134
+ email: t.string().validate((val) => val.includes("@")),
135
+
136
+ // Transform on set – runs when fact is mutated
137
+ slug: t.string().transform((val) => val.toLowerCase().replace(/\s+/g, "-")),
138
+
139
+ // Branded type – nominal typing
140
+ userId: t.string().brand<"UserId">(),
141
+
142
+ // Description – for docs and devtools
143
+ retryCount: t.number().describe("Number of failed attempts"),
144
+
145
+ // Refinement – predicate with error message
146
+ port: t.number().refine(
147
+ (n) => n >= 1 && n <= 65535,
148
+ "Port must be between 1 and 65535",
149
+ ),
150
+
151
+ // Chaining – combine multiple modifiers
152
+ score: t.number()
153
+ .min(0)
154
+ .max(100)
155
+ .default(0)
156
+ .describe("Player score"),
157
+ },
158
+ },
159
+ ```
160
+
161
+ ## Array-Specific Methods
162
+
163
+ ```typescript
164
+ schema: {
165
+ facts: {
166
+ // Basic array
167
+ items: t.array<string>(),
168
+
169
+ // Non-empty validation
170
+ requiredItems: t.array<string>().nonEmpty(),
171
+
172
+ // Length constraints
173
+ topFive: t.array<string>().maxLength(5),
174
+ atLeastThree: t.array<number>().minLength(3),
175
+
176
+ // Combined
177
+ tags: t.array<string>().nonEmpty().maxLength(10),
178
+ },
179
+ },
180
+ ```
181
+
182
+ ## Object-Specific Methods
183
+
184
+ ```typescript
185
+ schema: {
186
+ facts: {
187
+ // Non-null assertion
188
+ config: t.object<{ url: string }>().nonNull(),
189
+
190
+ // Required keys validation
191
+ settings: t.object<Record<string, unknown>>().hasKeys("apiUrl", "timeout"),
192
+ },
193
+ },
194
+ ```
195
+
196
+ ## Types That DO NOT Exist
197
+
198
+ These are common AI hallucinations. Do not use them.
199
+
200
+ ```typescript
201
+ // WRONG – t.map() does not exist
202
+ items: t.map<string, number>(),
203
+ // CORRECT
204
+ items: t.object<Map<string, number>>(),
205
+
206
+ // WRONG – t.set() does not exist
207
+ tags: t.set<string>(),
208
+ // CORRECT
209
+ tags: t.object<Set<string>>(),
210
+
211
+ // WRONG – t.date() does not exist
212
+ createdAt: t.date(),
213
+ // CORRECT
214
+ createdAt: t.object<Date>(),
215
+ // OR use timestamps
216
+ createdAt: t.number(), // Unix ms
217
+
218
+ // WRONG – t.tuple() does not exist
219
+ coords: t.tuple<[number, number]>(),
220
+ // CORRECT
221
+ coords: t.array<[number, number]>(),
222
+
223
+ // WRONG – t.record() does not exist
224
+ scores: t.record<string, number>(),
225
+ // CORRECT
226
+ scores: t.object<Record<string, number>>(),
227
+
228
+ // WRONG – t.promise() does not exist (facts are synchronous)
229
+ result: t.promise<string>(),
230
+
231
+ // WRONG – t.any() does not exist
232
+ data: t.any(),
233
+ // CORRECT
234
+ data: t.object<unknown>(),
235
+
236
+ // WRONG – t.void() does not exist (not a fact type)
237
+ // WRONG – t.function() does not exist (functions are not serializable)
238
+ ```
239
+
240
+ ## Type Assertion Alternative
241
+
242
+ For simple modules, you can skip `t.*()` and use TypeScript type assertions:
243
+
244
+ ```typescript
245
+ const myModule = createModule("simple", {
246
+ schema: {
247
+ facts: {} as {
248
+ count: number;
249
+ name: string;
250
+ items: string[];
251
+ },
252
+ derivations: {} as {
253
+ doubled: number;
254
+ },
255
+ },
256
+ init: (facts) => {
257
+ facts.count = 0;
258
+ facts.name = "";
259
+ facts.items = [];
260
+ },
261
+ derive: {
262
+ doubled: (facts) => facts.count * 2,
263
+ },
264
+ });
265
+ ```
266
+
267
+ This gives full TypeScript inference but skips runtime validation. Use `t.*()` when you want dev-mode validation, transforms, or self-documenting schemas.
268
+
269
+ ## See also
270
+
271
+ - [`core-patterns.md`](./core-patterns.md) — where the schemas these builders define get attached (modules)
272
+ - [`system-api.md`](./system-api.md) — schema flows through `createSystem` and shows up on `system.facts.*`
273
+ - [`anti-patterns.md`](./anti-patterns.md) — hallucinated schema types (`t.map`, `t.set`, `t.date`, `t.promise`, `t.tuple`, `t.record`, `t.any`) that don't exist