@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.
- package/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# System API
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/core` — `createSystem`, the system instance surface, hydration, inspection, distributable snapshots.
|
|
4
|
+
|
|
5
|
+
The system is created with `createSystem()` and is the runtime that orchestrates modules, constraints, resolvers, and plugins.
|
|
6
|
+
|
|
7
|
+
## Decision Tree: "How do I interact with the system?"
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
What do you want to do?
|
|
11
|
+
├── Read/write state → system.facts.fieldName
|
|
12
|
+
├── Read computed values → system.derive.derivationName
|
|
13
|
+
├── Dispatch user actions → system.events.eventName(payload)
|
|
14
|
+
├── React to changes → system.subscribe() or system.watch()
|
|
15
|
+
├── Wait for a condition → system.when()
|
|
16
|
+
├── Wait for all async to finish → system.settle()
|
|
17
|
+
├── Debug/inspect current state → system.inspect()
|
|
18
|
+
├── Control lifecycle → system.start() / system.stop() / system.destroy()
|
|
19
|
+
└── Multi-module access → system.facts.moduleName.fieldName
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Creating a System
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { createSystem } from "@directive-run/core";
|
|
26
|
+
import { loggingPlugin, devtoolsPlugin } from "@directive-run/core/plugins";
|
|
27
|
+
|
|
28
|
+
// Single module – direct access to facts/derive/events
|
|
29
|
+
const system = createSystem({
|
|
30
|
+
module: myModule,
|
|
31
|
+
plugins: [loggingPlugin(), devtoolsPlugin()],
|
|
32
|
+
history: { maxSnapshots: 100 },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Multi-module – namespaced access
|
|
36
|
+
const system = createSystem({
|
|
37
|
+
modules: {
|
|
38
|
+
auth: authModule,
|
|
39
|
+
cart: cartModule,
|
|
40
|
+
ui: uiModule,
|
|
41
|
+
},
|
|
42
|
+
plugins: [devtoolsPlugin()],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Facts: Reading and Writing State
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Single module
|
|
50
|
+
system.facts.count = 5;
|
|
51
|
+
const val = system.facts.count;
|
|
52
|
+
|
|
53
|
+
// Multi-module – access through module namespace
|
|
54
|
+
system.facts.auth.token = "abc123";
|
|
55
|
+
system.facts.cart.items = [];
|
|
56
|
+
const token = system.facts.auth.token;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Facts are proxy-based. Mutations are tracked automatically and trigger derivation recomputation, constraint evaluation, and effect execution.
|
|
60
|
+
|
|
61
|
+
## Derivations: Reading Computed Values
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// Single module
|
|
65
|
+
const loading = system.derive.isLoading;
|
|
66
|
+
const display = system.derive.displayName;
|
|
67
|
+
|
|
68
|
+
// Multi-module
|
|
69
|
+
const isAdmin = system.derive.auth.isAdmin;
|
|
70
|
+
const total = system.derive.cart.totalPrice;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Derivations are read-only. They recompute lazily when their tracked facts change.
|
|
74
|
+
|
|
75
|
+
## Events: Dispatching Actions
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Single module
|
|
79
|
+
system.events.increment();
|
|
80
|
+
system.events.setUser({ user: { id: "1", name: "Alice" } });
|
|
81
|
+
|
|
82
|
+
// Multi-module
|
|
83
|
+
system.events.auth.login({ email: "alice@example.com" });
|
|
84
|
+
system.events.cart.addItem({ productId: "p1", quantity: 2 });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Events are synchronous. They mutate facts in the event handler, which triggers the reactive pipeline.
|
|
88
|
+
|
|
89
|
+
## Subscribing to Changes
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// Subscribe to specific keys (facts or derivations)
|
|
93
|
+
const unsub = system.subscribe(["count", "isLoading"], () => {
|
|
94
|
+
console.log(system.facts.count, system.derive.isLoading);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Watch a single value with old/new
|
|
98
|
+
system.watch("count", (newVal, oldVal) => {
|
|
99
|
+
console.log(`Count: ${oldVal} -> ${newVal}`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Unsubscribe
|
|
103
|
+
unsub();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Waiting for Conditions
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Wait until a condition is true
|
|
110
|
+
await system.when((facts) => facts.phase === "done");
|
|
111
|
+
|
|
112
|
+
// With timeout – throws if condition not met in time
|
|
113
|
+
await system.when((facts) => facts.phase === "done", { timeout: 5000 });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Settling: Waiting for Async Completion
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
system.start();
|
|
120
|
+
|
|
121
|
+
// Wait for all resolvers and async constraints to complete
|
|
122
|
+
await system.settle();
|
|
123
|
+
|
|
124
|
+
// With timeout
|
|
125
|
+
await system.settle(5000); // Throws if not settled in 5s
|
|
126
|
+
|
|
127
|
+
// Check settlement state synchronously
|
|
128
|
+
if (system.isSettled) {
|
|
129
|
+
console.log("All resolvers complete");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Subscribe to settlement changes
|
|
133
|
+
const unsub = system.onSettledChange(() => {
|
|
134
|
+
console.log("Settlement:", system.isSettled);
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Reading by Key
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Read fact or derivation by string key
|
|
142
|
+
const count = system.read("count");
|
|
143
|
+
const isLoading = system.read("isLoading");
|
|
144
|
+
|
|
145
|
+
// Multi-module – use dot notation
|
|
146
|
+
const token = system.read("auth.token");
|
|
147
|
+
const total = system.read("cart.totalPrice");
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Inspecting System State
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const inspection = system.inspect();
|
|
154
|
+
|
|
155
|
+
// Unmet requirements
|
|
156
|
+
inspection.unmet;
|
|
157
|
+
// [{ id: "req-1", requirement: { type: "FETCH_USER" }, fromConstraint: "needsUser" }]
|
|
158
|
+
|
|
159
|
+
// Inflight resolvers
|
|
160
|
+
inspection.inflight;
|
|
161
|
+
// [{ id: "req-2", resolverId: "fetchData", startedAt: 1709000000 }]
|
|
162
|
+
|
|
163
|
+
// Facts with meta
|
|
164
|
+
inspection.facts;
|
|
165
|
+
// [{ key: "userId", meta: { label: "User ID" } }, { key: "count" }]
|
|
166
|
+
|
|
167
|
+
// Constraints with state + meta
|
|
168
|
+
inspection.constraints;
|
|
169
|
+
// [{ id: "needsUser", active: true, disabled: false, priority: 0, hitCount: 3, meta: { label: "..." } }]
|
|
170
|
+
|
|
171
|
+
// Resolver definitions + meta
|
|
172
|
+
inspection.resolverDefs;
|
|
173
|
+
// [{ id: "fetchUser", requirement: "FETCH_USER", meta: { label: "..." } }]
|
|
174
|
+
|
|
175
|
+
// Resolver statuses (inflight only)
|
|
176
|
+
inspection.resolvers;
|
|
177
|
+
// { "req-1": { state: "running" } }
|
|
178
|
+
|
|
179
|
+
// Effects, derivations, modules — all with optional meta
|
|
180
|
+
inspection.effects; // [{ id: "log", meta: { ... } }]
|
|
181
|
+
inspection.derivations; // [{ id: "doubled", meta: { ... } }]
|
|
182
|
+
inspection.modules; // [{ id: "auth", meta: { ... } }]
|
|
183
|
+
inspection.events; // [{ name: "increment", meta: { ... } }]
|
|
184
|
+
|
|
185
|
+
// Unmet requirements (no matching resolver)
|
|
186
|
+
inspection.unmet;
|
|
187
|
+
|
|
188
|
+
// Explain why a requirement exists (uses meta.label + meta.description)
|
|
189
|
+
const explanation = system.explain("req-123");
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Definition Meta
|
|
193
|
+
|
|
194
|
+
Attach optional metadata to any definition for debugging, devtools, and AI context:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// On constraints, resolvers, effects — meta field
|
|
198
|
+
constraints: {
|
|
199
|
+
needsAuth: {
|
|
200
|
+
when: (f) => !f.user,
|
|
201
|
+
require: { type: "LOGIN" },
|
|
202
|
+
meta: { label: "Requires Auth", category: "auth", tags: ["critical"] },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
// On derivations — { compute, meta } object form
|
|
207
|
+
derive: {
|
|
208
|
+
displayName: {
|
|
209
|
+
compute: (f) => `${f.first} ${f.last}`,
|
|
210
|
+
meta: { label: "Display Name" },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// On facts — chainable .meta()
|
|
215
|
+
schema: { facts: { email: t.string().meta({ label: "Email", tags: ["pii"] }) } },
|
|
216
|
+
|
|
217
|
+
// On modules
|
|
218
|
+
meta: { label: "Auth Module", category: "auth" },
|
|
219
|
+
|
|
220
|
+
// O(1) accessor
|
|
221
|
+
system.meta.constraint("needsAuth")?.label; // "Requires Auth"
|
|
222
|
+
system.meta.fact("email")?.tags; // ["pii"]
|
|
223
|
+
system.meta.module("auth")?.label; // "Auth Module"
|
|
224
|
+
|
|
225
|
+
// Bulk queries
|
|
226
|
+
system.meta.byCategory("auth"); // MetaMatch[] — all auth definitions
|
|
227
|
+
system.meta.byTag("pii"); // MetaMatch[] — all PII-tagged fields
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Meta is frozen at registration (Object.create(null) + Object.freeze). Zero hot-path cost. See [Definition Meta docs](https://directive.run/docs/advanced/meta).
|
|
231
|
+
|
|
232
|
+
## Observation Protocol
|
|
233
|
+
|
|
234
|
+
Typed event stream for all lifecycle events — enables browser extensions, third-party tools, and test assertions:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import type { ObservationEvent } from "@directive-run/core";
|
|
238
|
+
|
|
239
|
+
const unsub = system.observe((event: ObservationEvent) => {
|
|
240
|
+
if (event.type === "constraint.evaluate") console.log(event.id, event.active);
|
|
241
|
+
if (event.type === "resolver.complete") console.log(event.resolver, event.duration);
|
|
242
|
+
if (event.type === "fact.change") console.log(event.key, event.prev, "→", event.next);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 18 event types: fact.change, constraint.evaluate/error, requirement.created/met/canceled,
|
|
246
|
+
// resolver.start/complete/error, effect.run/error, derivation.compute,
|
|
247
|
+
// reconcile.start/end, system.init/start/stop/destroy
|
|
248
|
+
|
|
249
|
+
unsub(); // Stop observing
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Zero overhead when no observers. Implemented as an internal plugin.
|
|
253
|
+
|
|
254
|
+
## Lifecycle
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// Start – begins constraint evaluation and reconciliation
|
|
258
|
+
system.start();
|
|
259
|
+
|
|
260
|
+
// Stop – pauses evaluation, cancels inflight resolvers
|
|
261
|
+
system.stop();
|
|
262
|
+
|
|
263
|
+
// Destroy – cleans up all resources, subscriptions, plugins
|
|
264
|
+
system.destroy();
|
|
265
|
+
|
|
266
|
+
// Lifecycle order:
|
|
267
|
+
// createSystem() → system.start() → ... → system.stop() → system.destroy()
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`system.start()` is auto-called in most cases. Call it explicitly when you need to set up subscriptions before the first evaluation cycle.
|
|
271
|
+
|
|
272
|
+
## Constraint Control at Runtime
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Disable a constraint – it won't be evaluated
|
|
276
|
+
system.constraints.disable("fetchWhenReady");
|
|
277
|
+
|
|
278
|
+
// Check if disabled
|
|
279
|
+
system.constraints.isDisabled("fetchWhenReady"); // true
|
|
280
|
+
|
|
281
|
+
// Re-enable – triggers re-evaluation on next cycle
|
|
282
|
+
system.constraints.enable("fetchWhenReady");
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## SSR and Hydration
|
|
286
|
+
|
|
287
|
+
Four mechanisms for populating a system with external state:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// 1. initialFacts – simplest, at construction time
|
|
291
|
+
const system = createSystem({
|
|
292
|
+
module: myModule,
|
|
293
|
+
initialFacts: { userId: "user-1", name: "Alice" },
|
|
294
|
+
});
|
|
295
|
+
system.start();
|
|
296
|
+
|
|
297
|
+
// 2. system.hydrate() – async, before start()
|
|
298
|
+
const system = createSystem({ module: myModule });
|
|
299
|
+
await system.hydrate(async () => {
|
|
300
|
+
const res = await fetch('/api/state');
|
|
301
|
+
|
|
302
|
+
return res.json();
|
|
303
|
+
});
|
|
304
|
+
system.start();
|
|
305
|
+
|
|
306
|
+
// 3. system.restore() – sync, applies facts from a snapshot
|
|
307
|
+
const system = createSystem({ module: myModule });
|
|
308
|
+
system.restore(serverSnapshot);
|
|
309
|
+
system.start();
|
|
310
|
+
|
|
311
|
+
// 4. DirectiveHydrator + useHydratedSystem (React only)
|
|
312
|
+
// Server: getDistributableSnapshot() → serialize
|
|
313
|
+
// Client: <DirectiveHydrator snapshot={s}><App /></DirectiveHydrator>
|
|
314
|
+
// useHydratedSystem(module) inside App
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### SSR Lifecycle
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// Server: create → start → settle → snapshot → destroy
|
|
321
|
+
const system = createSystem({
|
|
322
|
+
module: pageModule,
|
|
323
|
+
initialFacts: { userId: req.user.id },
|
|
324
|
+
});
|
|
325
|
+
system.start();
|
|
326
|
+
await system.settle(5000); // Throws on timeout
|
|
327
|
+
const snapshot = system.getSnapshot();
|
|
328
|
+
system.stop();
|
|
329
|
+
system.destroy();
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Snapshot Types
|
|
333
|
+
|
|
334
|
+
- `SystemSnapshot` – facts only, used with `getSnapshot()` / `restore()`
|
|
335
|
+
- `DistributableSnapshot` – facts + derivations + metadata + TTL, used with `getDistributableSnapshot()` and `DirectiveHydrator`
|
|
336
|
+
|
|
337
|
+
### Avoiding Singletons
|
|
338
|
+
|
|
339
|
+
Never use module-level systems in SSR – they share state across concurrent requests. Always create a fresh system per request and destroy it when done.
|
|
340
|
+
|
|
341
|
+
## Runtime Dynamics
|
|
342
|
+
|
|
343
|
+
All four subsystems (constraints, resolvers, derivations, effects) share a uniform dynamic definition interface:
|
|
344
|
+
|
|
345
|
+
### Enable / Disable (Constraints & Effects only)
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
system.constraints.disable("id");
|
|
349
|
+
system.constraints.enable("id");
|
|
350
|
+
system.constraints.isDisabled("id");
|
|
351
|
+
|
|
352
|
+
system.effects.disable("id");
|
|
353
|
+
system.effects.enable("id");
|
|
354
|
+
system.effects.isEnabled("id");
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Register / Assign / Unregister (All 4 subsystems)
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
// Register a new definition at runtime
|
|
361
|
+
system.constraints.register("id", { when: ..., require: ... });
|
|
362
|
+
system.resolvers.register("id", { requirement: "TYPE", resolve: ... });
|
|
363
|
+
system.derive.register("id", (facts) => facts.count * 3);
|
|
364
|
+
system.effects.register("id", { run: (facts) => { ... } });
|
|
365
|
+
|
|
366
|
+
// Override an existing definition
|
|
367
|
+
system.constraints.assign("id", { when: ..., require: ... });
|
|
368
|
+
|
|
369
|
+
// Remove a dynamically registered definition (static = no-op + dev warning)
|
|
370
|
+
system.constraints.unregister("id");
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Semantics: `register` throws if ID exists. `assign` throws if ID doesn't exist. `unregister` on static ID = dev warning, no-op. All three are deferred if called during reconciliation.
|
|
374
|
+
|
|
375
|
+
### Introspection
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
system.constraints.isDynamic("id"); // true if registered at runtime
|
|
379
|
+
system.constraints.listDynamic(); // all dynamic constraint IDs
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### getOriginal / restoreOriginal
|
|
383
|
+
|
|
384
|
+
When `assign()` overrides a static definition, the original is saved:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
system.getOriginal("constraint", "id"); // returns original definition
|
|
388
|
+
system.restoreOriginal("constraint", "id"); // restores it, returns true/false
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Dynamic Module Registration
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
system.registerModule("chat", chatModule); // adds module to running system
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
See docs: https://directive.run/docs/advanced/runtime
|
|
398
|
+
|
|
399
|
+
## Common Mistakes
|
|
400
|
+
|
|
401
|
+
### Reading facts before settling
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// WRONG – resolver hasn't completed, facts are stale
|
|
405
|
+
system.start();
|
|
406
|
+
console.log(system.facts.user); // null
|
|
407
|
+
|
|
408
|
+
// CORRECT – wait for async resolution
|
|
409
|
+
system.start();
|
|
410
|
+
await system.settle();
|
|
411
|
+
console.log(system.facts.user); // { id: "1", name: "Alice" }
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Casting facts/derivations unnecessarily
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// WRONG – the schema already provides types
|
|
418
|
+
const profile = system.facts.profile as ResourceState<Profile>;
|
|
419
|
+
|
|
420
|
+
// CORRECT – types are inferred from the schema
|
|
421
|
+
const profile = system.facts.profile;
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Using single-line returns without braces
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
// WRONG
|
|
428
|
+
system.watch("phase", (val) => { if (val === "done") return; });
|
|
429
|
+
|
|
430
|
+
// CORRECT
|
|
431
|
+
system.watch("phase", (val) => {
|
|
432
|
+
if (val === "done") {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Forgetting to destroy
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// WRONG – resources leak
|
|
442
|
+
function setupSystem() {
|
|
443
|
+
const system = createSystem({ module: myModule });
|
|
444
|
+
system.start();
|
|
445
|
+
|
|
446
|
+
return system;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// CORRECT – clean up when done
|
|
450
|
+
const system = createSystem({ module: myModule });
|
|
451
|
+
system.start();
|
|
452
|
+
// ... when finished:
|
|
453
|
+
system.stop();
|
|
454
|
+
system.destroy();
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## See also
|
|
458
|
+
|
|
459
|
+
- [`core-patterns.md`](./core-patterns.md) — the modules that go into `createSystem({ module: ... })`
|
|
460
|
+
- [`multi-module.md`](./multi-module.md) — `createSystem({ modules: { ... } })` for namespaced systems
|
|
461
|
+
- [`history.md`](./history.md) — the `history:` config and the `system.history.*` time-travel surface
|
|
462
|
+
- [`plugins.md`](./plugins.md) — the `plugins:` config and the lifecycle hooks plugins observe
|
|
463
|
+
- [`react-adapter.md`](./react-adapter.md) — consuming a system from React components
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: getting-started-with-directive
|
|
3
|
+
description: "Understand Directive fundamentals: what modules, facts, derivations, constraints, resolvers, and systems are, and how they fit together. Use when someone is new to Directive, asks 'what is Directive', wants to understand the mental model, or needs help choosing between Directive concepts."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Getting Started with Directive
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
This skill applies when the project uses `@directive-run/core`. If not found in `package.json`, suggest installing it: `npm install @directive-run/core`.
|
|
11
|
+
|
|
12
|
+
## When Claude Should Use This Skill
|
|
13
|
+
|
|
14
|
+
### Auto-Invoke Triggers
|
|
15
|
+
- User asks "what is Directive" or "how does Directive work"
|
|
16
|
+
- User is new to Directive and needs orientation
|
|
17
|
+
- User asks about the relationship between facts, constraints, and resolvers
|
|
18
|
+
- User asks "should I use a constraint or an effect" or similar conceptual questions
|
|
19
|
+
- User wants to understand the Directive mental model before writing code
|
|
20
|
+
|
|
21
|
+
### Exclusions – Use a Different Skill
|
|
22
|
+
- User already knows Directive and wants to write a specific module → `writing-directive-modules`
|
|
23
|
+
- User asks about specific constraint/resolver patterns → `writing-directive-constraints`
|
|
24
|
+
- User asks about multi-module systems or React → `building-directive-systems`
|
|
25
|
+
- User asks about testing → `testing-directive-code`
|
|
26
|
+
- User asks about AI agents → `building-ai-orchestrators`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Directive Mental Model
|
|
31
|
+
|
|
32
|
+
Directive is a **constraint-driven runtime** for TypeScript. Instead of imperative state management, you:
|
|
33
|
+
|
|
34
|
+
1. **Declare facts** – observable state values
|
|
35
|
+
2. **Derive** computed values – auto-tracked, no manual deps
|
|
36
|
+
3. **Set constraints** – conditions that must be true (when X, require Y)
|
|
37
|
+
4. **Write resolvers** – how to fulfill requirements (async handlers)
|
|
38
|
+
5. **Compose into systems** – wire modules together with plugins
|
|
39
|
+
|
|
40
|
+
The runtime watches your facts, evaluates constraints, and dispatches requirements to resolvers automatically.
|
|
41
|
+
|
|
42
|
+
## Core Concepts Flow
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Facts (state) → Derivations (computed)
|
|
46
|
+
↓
|
|
47
|
+
Constraints (when/require) → Requirements → Resolvers (async handlers)
|
|
48
|
+
↓
|
|
49
|
+
Effects (side effects, logging, sync)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Key Terminology
|
|
53
|
+
|
|
54
|
+
| Term | What it is | Analogy |
|
|
55
|
+
|------|-----------|---------|
|
|
56
|
+
| **Fact** | A piece of observable state | Redux store field |
|
|
57
|
+
| **Derivation** | Auto-tracked computed value | Recoil selector |
|
|
58
|
+
| **Constraint** | "When X is true, require Y" declaration | Business rule |
|
|
59
|
+
| **Requirement** | A typed request emitted by a constraint | Redux action |
|
|
60
|
+
| **Resolver** | Async handler that fulfills requirements | Redux thunk |
|
|
61
|
+
| **Effect** | Fire-and-forget side effect on fact changes | useEffect |
|
|
62
|
+
| **Module** | Bundle of schema + init + derive + constraints + resolvers | Feature slice |
|
|
63
|
+
| **System** | Runtime that wires modules together | Redux store |
|
|
64
|
+
|
|
65
|
+
## Minimal Example
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { createModule, createSystem, t } from "@directive-run/core";
|
|
69
|
+
|
|
70
|
+
const counter = createModule("counter", {
|
|
71
|
+
schema: {
|
|
72
|
+
facts: {
|
|
73
|
+
count: t.number(),
|
|
74
|
+
limit: t.number(),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
init: (facts) => {
|
|
79
|
+
facts.count = 0;
|
|
80
|
+
facts.limit = 10;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
derive: {
|
|
84
|
+
isAtLimit: (facts) => facts.count >= facts.limit,
|
|
85
|
+
remaining: (facts) => facts.limit - facts.count,
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
constraints: {
|
|
89
|
+
enforceLimit: {
|
|
90
|
+
when: (facts) => facts.count > facts.limit,
|
|
91
|
+
require: { type: "RESET_COUNT" },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
resolvers: {
|
|
96
|
+
resetCount: {
|
|
97
|
+
requirement: "RESET_COUNT",
|
|
98
|
+
resolve: async (req, context) => {
|
|
99
|
+
context.facts.count = context.facts.limit;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const system = createSystem({ module: counter });
|
|
106
|
+
|
|
107
|
+
// Read state
|
|
108
|
+
console.log(system.facts.count); // 0
|
|
109
|
+
console.log(system.derive.isAtLimit); // false
|
|
110
|
+
|
|
111
|
+
// Mutate – constraints auto-evaluate
|
|
112
|
+
system.facts.count = 15;
|
|
113
|
+
// → constraint fires → resolver resets count to limit
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## When to Use What
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Need to store state? → schema + init (facts)
|
|
120
|
+
Need computed values from state? → derive (derivations)
|
|
121
|
+
Need "if X then do Y" business logic? → constraint + resolver
|
|
122
|
+
Need side effects (logging, sync)? → effect
|
|
123
|
+
Need to compose multiple features? → multi-module system
|
|
124
|
+
Need lifecycle hooks? → plugins
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Decision Tree: Constraint vs Effect vs Direct Mutation
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Is it a business rule (if X then Y)?
|
|
131
|
+
├── YES → Constraint + Resolver
|
|
132
|
+
│ Examples: auth redirect, data fetch, validation enforcement
|
|
133
|
+
│
|
|
134
|
+
├── NO → Does it modify facts?
|
|
135
|
+
│ ├── YES → Direct mutation (system.facts.x = y)
|
|
136
|
+
│ │ Examples: user input, button click handlers
|
|
137
|
+
│ │
|
|
138
|
+
│ └── NO → Effect
|
|
139
|
+
│ Examples: logging, analytics, DOM sync, external API sync
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Common Patterns
|
|
143
|
+
|
|
144
|
+
### Schema Type Builders
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
schema: {
|
|
148
|
+
facts: {
|
|
149
|
+
name: t.string(), // string
|
|
150
|
+
age: t.number(), // number
|
|
151
|
+
active: t.boolean(), // boolean
|
|
152
|
+
role: t.string<"admin" | "user">(), // union type
|
|
153
|
+
profile: t.object<{ name: string }>(), // typed object
|
|
154
|
+
tags: t.array<string>(), // typed array
|
|
155
|
+
data: t.object<Profile | null>(), // nullable
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Naming Convention
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Module name: "kebab-case" → createModule("user-auth", ...)
|
|
164
|
+
Fact keys: camelCase → schema: { isLoggedIn: t.boolean() }
|
|
165
|
+
Derivation: camelCase → derive: { canEdit: ... }
|
|
166
|
+
Constraint: camelCase → constraints: { enforceAuth: ... }
|
|
167
|
+
Resolver: camelCase → resolvers: { fetchProfile: ... }
|
|
168
|
+
Requirement: UPPER_SNAKE_CASE → require: { type: "FETCH_PROFILE" }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Skill Map
|
|
172
|
+
|
|
173
|
+
All available Directive skills, grouped by use case:
|
|
174
|
+
|
|
175
|
+
**Learning:**
|
|
176
|
+
- `getting-started-with-directive` – This skill. Fundamentals and mental model
|
|
177
|
+
- `migrating-to-directive` – Convert from Redux, Zustand, XState, MobX
|
|
178
|
+
|
|
179
|
+
**Building (Core):**
|
|
180
|
+
- `writing-directive-modules` – Schema, type builders, derivations, naming
|
|
181
|
+
- `writing-directive-constraints` – Constraints, resolvers, error boundaries, retry
|
|
182
|
+
- `building-directive-systems` – Multi-module, plugins, React integration
|
|
183
|
+
- `scaffolding-directive-modules` – Generate module + test boilerplate
|
|
184
|
+
|
|
185
|
+
**Building (AI):**
|
|
186
|
+
- `building-ai-orchestrators` – Single and multi-agent orchestrators
|
|
187
|
+
- `building-ai-agents` – Streaming, provider adapters, cross-agent communication
|
|
188
|
+
- `hardening-ai-systems` – Guardrails, budgets, PII detection, security
|
|
189
|
+
|
|
190
|
+
**Quality:**
|
|
191
|
+
- `testing-directive-code` – createTestSystem, mockResolver, time-travel
|
|
192
|
+
- `testing-ai-systems` – Mock runners, evaluations, observability
|
|
193
|
+
- `reviewing-directive-code` – Anti-pattern scanner, naming audit
|
|
194
|
+
|
|
195
|
+
## Reference Files
|
|
196
|
+
|
|
197
|
+
Supporting knowledge files loaded with this skill:
|
|
198
|
+
- `core-patterns.md` – Core patterns and API reference
|