@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,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,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scaffolding-directive-modules
|
|
3
|
+
description: "Generate Directive module scaffolds with schema, init, derivations, constraints, resolvers, and matching test files. Use when asked to scaffold, generate, or create a new module from scratch, or when the user describes a feature and wants the boilerplate created."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Scaffolding Directive Modules
|
|
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 to "scaffold a module" or "generate a module"
|
|
16
|
+
- User describes a feature and wants boilerplate created
|
|
17
|
+
- User asks for a "starter" or "template" for a Directive module
|
|
18
|
+
- User wants to create a module with tests in one step
|
|
19
|
+
|
|
20
|
+
### Exclusions – Use a Different Skill
|
|
21
|
+
- User wants to understand Directive concepts → `getting-started-with-directive`
|
|
22
|
+
- User has existing code and wants patterns guidance → `writing-directive-modules`
|
|
23
|
+
- User wants to review existing code → `reviewing-directive-code`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Module Scaffold Template
|
|
28
|
+
|
|
29
|
+
## Step 1: Identify the Domain
|
|
30
|
+
|
|
31
|
+
Ask the user (or infer from context):
|
|
32
|
+
1. What is the module managing? (e.g., "user authentication", "shopping cart")
|
|
33
|
+
2. What state does it track? (e.g., "login status, user profile, tokens")
|
|
34
|
+
3. What actions should happen automatically? (e.g., "redirect when not logged in", "fetch profile on login")
|
|
35
|
+
|
|
36
|
+
## Step 2: Generate Module
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { createModule, t } from "@directive-run/core";
|
|
40
|
+
|
|
41
|
+
// Module: {module-name}
|
|
42
|
+
// Purpose: {one-line description}
|
|
43
|
+
|
|
44
|
+
export const {camelCaseName} = createModule("{kebab-case-name}", {
|
|
45
|
+
schema: {
|
|
46
|
+
// State – all facts must have type builders
|
|
47
|
+
// {factName}: t.{type}(),
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
init: (facts) => {
|
|
51
|
+
// Set ALL schema keys to sensible defaults
|
|
52
|
+
// No async work, no side effects
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
derive: {
|
|
56
|
+
// Computed values – pure functions, auto-tracked
|
|
57
|
+
// {derivedName}: (facts) => {computation},
|
|
58
|
+
// Composition: {name}: (facts, derived) => derived.other && facts.x,
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
constraints: {
|
|
62
|
+
// Business rules: when X is true, require Y
|
|
63
|
+
// {constraintName}: {
|
|
64
|
+
// when: (facts) => {condition},
|
|
65
|
+
// require: { type: "REQUIREMENT_TYPE", ...payload },
|
|
66
|
+
// },
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
resolvers: {
|
|
70
|
+
// Async handlers for requirements
|
|
71
|
+
// {resolverName}: {
|
|
72
|
+
// requirement: "REQUIREMENT_TYPE",
|
|
73
|
+
// resolve: async (req, context) => {
|
|
74
|
+
// // Fulfill the requirement
|
|
75
|
+
// },
|
|
76
|
+
// },
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Step 3: Generate Test File
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { describe, it, expect } from "vitest";
|
|
85
|
+
import { createTestSystem } from "@directive-run/core/testing";
|
|
86
|
+
import { {camelCaseName} } from "./{kebab-case-name}";
|
|
87
|
+
|
|
88
|
+
describe("{kebab-case-name}", () => {
|
|
89
|
+
function createTest() {
|
|
90
|
+
return createTestSystem({ module: {camelCaseName} });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("initialization", () => {
|
|
94
|
+
it("sets default values", () => {
|
|
95
|
+
const system = createTest();
|
|
96
|
+
// expect(system.facts.{fact}).toBe({default});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("derivations", () => {
|
|
101
|
+
it("computes {derivedName}", () => {
|
|
102
|
+
const system = createTest();
|
|
103
|
+
// system.facts.{fact} = {value};
|
|
104
|
+
// expect(system.derive.{derivedName}).toBe({expected});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("constraints + resolvers", () => {
|
|
109
|
+
it("triggers {requirement} when {condition}", async () => {
|
|
110
|
+
const system = createTest();
|
|
111
|
+
// system.facts.{fact} = {trigger value};
|
|
112
|
+
// await system.settle();
|
|
113
|
+
// expect(system.facts.{result}).toBe({expected});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Scaffold Decision Tree
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
What kind of module?
|
|
123
|
+
│
|
|
124
|
+
├── Simple state container (no async)
|
|
125
|
+
│ → schema + init + derive
|
|
126
|
+
│ → Skip constraints/resolvers
|
|
127
|
+
│ → Example: counter, feature-flags
|
|
128
|
+
│
|
|
129
|
+
├── Data-fetching module
|
|
130
|
+
│ → schema includes loading/error/data states
|
|
131
|
+
│ → Constraint triggers fetch when needed
|
|
132
|
+
│ → Resolver fetches with retry policy
|
|
133
|
+
│ → Example: dashboard-loader, auth-flow
|
|
134
|
+
│
|
|
135
|
+
├── Form/validation module
|
|
136
|
+
│ → schema for field values + validation state
|
|
137
|
+
│ → Constraints check validity rules
|
|
138
|
+
│ → Resolver submits form data
|
|
139
|
+
│ → Example: contact-form, form-wizard
|
|
140
|
+
│
|
|
141
|
+
├── Multi-module coordination
|
|
142
|
+
│ → Multiple modules + crossModuleDeps
|
|
143
|
+
│ → createSystem({ modules: { a, b } })
|
|
144
|
+
│ → Example: multi-module, permissions
|
|
145
|
+
│
|
|
146
|
+
└── AI orchestrator module
|
|
147
|
+
│ → Use createAgentOrchestrator or createMultiAgentOrchestrator
|
|
148
|
+
│ → See building-ai-orchestrators skill
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Common Schema Patterns
|
|
152
|
+
|
|
153
|
+
### Loading State
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
schema: {
|
|
157
|
+
status: t.string<"idle" | "loading" | "success" | "error">(),
|
|
158
|
+
data: t.object<DataType | null>(),
|
|
159
|
+
error: t.string<string | null>(),
|
|
160
|
+
},
|
|
161
|
+
init: (facts) => {
|
|
162
|
+
facts.status = "idle";
|
|
163
|
+
facts.data = null;
|
|
164
|
+
facts.error = null;
|
|
165
|
+
},
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Resource State (Generic)
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
schema: {
|
|
172
|
+
resource: t.object<ResourceState<T>>(),
|
|
173
|
+
},
|
|
174
|
+
// Where ResourceState<T> = { status, data, error, lastFetched }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Authentication
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
schema: {
|
|
181
|
+
isAuthenticated: t.boolean(),
|
|
182
|
+
user: t.object<User | null>(),
|
|
183
|
+
token: t.string<string | null>(),
|
|
184
|
+
loginError: t.string<string | null>(),
|
|
185
|
+
},
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Pagination
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
schema: {
|
|
192
|
+
items: t.array<Item>(),
|
|
193
|
+
page: t.number(),
|
|
194
|
+
pageSize: t.number(),
|
|
195
|
+
total: t.number(),
|
|
196
|
+
hasMore: t.boolean(),
|
|
197
|
+
},
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Naming Rules (Enforced)
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
Module variable: camelCase const userAuth = createModule(...)
|
|
204
|
+
Module string: kebab-case createModule("user-auth", ...)
|
|
205
|
+
Fact keys: camelCase isLoggedIn, userName
|
|
206
|
+
Derivation keys: camelCase canEdit, isAdmin
|
|
207
|
+
Constraint keys: camelCase enforceLogin, requireProfile
|
|
208
|
+
Resolver keys: camelCase fetchProfile, submitLogin
|
|
209
|
+
Requirement types: UPPER_SNAKE "FETCH_PROFILE", "SUBMIT_LOGIN"
|
|
210
|
+
Test describe: kebab-case describe("user-auth", ...)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Reference Files
|
|
214
|
+
|
|
215
|
+
Supporting knowledge files loaded with this skill:
|
|
216
|
+
- `core-patterns.md` – Patterns and API reference
|
|
217
|
+
- `schema-types.md` – Type builders and schema patterns
|
|
218
|
+
- `naming.md` – Naming conventions
|