@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,351 @@
|
|
|
1
|
+
# History & Snapshots
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/core` — time-travel: snapshot, undo/redo, replay, export/import, changeset grouping.
|
|
4
|
+
|
|
5
|
+
Directive records fact changes as snapshots, enabling undo/redo, replay, export/import, and changeset grouping.
|
|
6
|
+
|
|
7
|
+
## Decision Tree: "Should I enable history?"
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
What's the use case?
|
|
11
|
+
├── Debugging during development → Yes, enable with maxSnapshots cap
|
|
12
|
+
├── Production app → No, disable for performance
|
|
13
|
+
├── Bug reproduction → Enable, use export() to share
|
|
14
|
+
├── Testing → Usually no – use assertFact/assertDerivation instead
|
|
15
|
+
└── Demo / presentation → Yes, great for showing state changes
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Enabling History
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { createSystem } from "@directive-run/core";
|
|
22
|
+
|
|
23
|
+
const system = createSystem({
|
|
24
|
+
module: myModule,
|
|
25
|
+
history: {
|
|
26
|
+
maxSnapshots: 100, // Cap memory usage (default: 100)
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
History is disabled by default. When disabled, `system.history` is `null`. When enabled, snapshots are recorded automatically after every fact mutation.
|
|
32
|
+
|
|
33
|
+
## Filtering Snapshot Events
|
|
34
|
+
|
|
35
|
+
By default every event that changes facts creates a snapshot. Use `history.snapshotEvents` on your module to limit which events create snapshots — keeps undo/redo clean by excluding UI-only events like selection or timer ticks.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
const game = createModule("game", {
|
|
39
|
+
schema: gameSchema,
|
|
40
|
+
|
|
41
|
+
// Only these events appear in undo/redo history.
|
|
42
|
+
// Omit to snapshot ALL events (the default).
|
|
43
|
+
history: {
|
|
44
|
+
snapshotEvents: [
|
|
45
|
+
"inputNumber",
|
|
46
|
+
"requestHint",
|
|
47
|
+
"newGame",
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
events: {
|
|
52
|
+
tick: (facts) => { /* no snapshot */ },
|
|
53
|
+
selectCell: (facts, { index }) => { /* no snapshot */ },
|
|
54
|
+
inputNumber: (facts, { value }) => { /* creates snapshot */ },
|
|
55
|
+
requestHint: (facts) => { /* creates snapshot */ },
|
|
56
|
+
newGame: (facts, { difficulty }) => { /* creates snapshot */ },
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Filtering by Module
|
|
62
|
+
|
|
63
|
+
In a multi-module system, control which modules create snapshots at the system level:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const system = createSystem({
|
|
67
|
+
modules: { ui: uiModule, game: gameModule },
|
|
68
|
+
history: {
|
|
69
|
+
maxSnapshots: 100,
|
|
70
|
+
snapshotModules: ["game"], // Only game events create snapshots
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Rules:**
|
|
76
|
+
- `snapshotEvents` omitted → all events snapshot (per module)
|
|
77
|
+
- `snapshotModules` omitted → all modules snapshot (per system)
|
|
78
|
+
- Both provided → they intersect (module must be in `snapshotModules` AND event in `snapshotEvents`)
|
|
79
|
+
- Direct fact mutations (`system.facts.x = 5`) always snapshot regardless of filtering
|
|
80
|
+
- `snapshotEvents` entries are type-checked against schema events
|
|
81
|
+
|
|
82
|
+
## Core API: `system.history` (`HistoryAPI`)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const history = system.history; // HistoryAPI | null
|
|
86
|
+
|
|
87
|
+
if (history) {
|
|
88
|
+
// Read-only state
|
|
89
|
+
history.snapshots; // Snapshot[] — all recorded snapshots
|
|
90
|
+
history.currentIndex; // number — position in the snapshot array
|
|
91
|
+
history.isPaused; // boolean — whether recording is paused
|
|
92
|
+
|
|
93
|
+
// Navigation
|
|
94
|
+
history.goBack(); // Step backward one snapshot (changeset-aware)
|
|
95
|
+
history.goBack(3); // Step backward 3 snapshots
|
|
96
|
+
history.goForward(); // Step forward one snapshot (changeset-aware)
|
|
97
|
+
history.goForward(3); // Step forward 3 snapshots
|
|
98
|
+
history.goTo(snapshotId); // Jump to a specific snapshot by its ID
|
|
99
|
+
history.replay(); // Jump to the first snapshot
|
|
100
|
+
|
|
101
|
+
// Export / Import (JSON strings)
|
|
102
|
+
history.export(); // Serialize entire history to JSON string
|
|
103
|
+
history.import(json); // Restore history from JSON string
|
|
104
|
+
|
|
105
|
+
// Changesets — group multiple snapshots into one undo/redo unit
|
|
106
|
+
history.beginChangeset("Move piece");
|
|
107
|
+
// ... mutations happen ...
|
|
108
|
+
history.endChangeset();
|
|
109
|
+
|
|
110
|
+
// Recording control
|
|
111
|
+
history.pause(); // Temporarily stop recording snapshots
|
|
112
|
+
history.resume(); // Resume recording
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Snapshot Structure
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface Snapshot {
|
|
120
|
+
id: number; // Auto-incrementing identifier
|
|
121
|
+
timestamp: number; // When captured (Date.now())
|
|
122
|
+
facts: Record<string, unknown>; // Complete copy of all fact values
|
|
123
|
+
trigger: string; // What caused this snapshot (e.g., "fact:count")
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Framework Hook: `useHistory` (`HistoryState`)
|
|
128
|
+
|
|
129
|
+
Each framework adapter provides a reactive `useHistory` hook that re-renders on snapshot changes. Returns `null` when history is disabled, otherwise a `HistoryState` object that wraps the core API with convenience properties.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
interface SnapshotMeta {
|
|
133
|
+
id: number; // Snapshot identifier
|
|
134
|
+
timestamp: number; // When captured
|
|
135
|
+
trigger: string; // What caused this snapshot
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface HistoryState {
|
|
139
|
+
// Convenience booleans (not on core API)
|
|
140
|
+
canGoBack: boolean; // True when currentIndex > 0
|
|
141
|
+
canGoForward: boolean; // True when currentIndex < totalSnapshots - 1
|
|
142
|
+
currentIndex: number;
|
|
143
|
+
totalSnapshots: number;
|
|
144
|
+
|
|
145
|
+
// Snapshot access (metadata only — keeps re-renders cheap)
|
|
146
|
+
snapshots: SnapshotMeta[];
|
|
147
|
+
getSnapshotFacts: (id: number) => Record<string, unknown> | null;
|
|
148
|
+
|
|
149
|
+
// Navigation
|
|
150
|
+
goTo: (snapshotId: number) => void;
|
|
151
|
+
goBack: (steps: number) => void;
|
|
152
|
+
goForward: (steps: number) => void;
|
|
153
|
+
replay: () => void;
|
|
154
|
+
|
|
155
|
+
// Session persistence
|
|
156
|
+
exportSession: () => string; // Wraps history.export()
|
|
157
|
+
importSession: (json: string) => void; // Wraps history.import()
|
|
158
|
+
|
|
159
|
+
// Changesets
|
|
160
|
+
beginChangeset: (label: string) => void;
|
|
161
|
+
endChangeset: () => void;
|
|
162
|
+
|
|
163
|
+
// Recording control
|
|
164
|
+
isPaused: boolean;
|
|
165
|
+
pause: () => void;
|
|
166
|
+
resume: () => void;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### React Example
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { useHistory } from "@directive-run/react";
|
|
174
|
+
|
|
175
|
+
function HistoryToolbar() {
|
|
176
|
+
const history = useHistory(system);
|
|
177
|
+
if (!history) return null;
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div>
|
|
181
|
+
<button onClick={() => history.goBack()} disabled={!history.canGoBack}>Undo</button>
|
|
182
|
+
<button onClick={() => history.goForward()} disabled={!history.canGoForward}>Redo</button>
|
|
183
|
+
<span>{history.currentIndex + 1} / {history.totalSnapshots}</span>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Undo/Redo Pattern
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const system = createSystem({
|
|
193
|
+
module: editorModule,
|
|
194
|
+
history: { maxSnapshots: 200 },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
system.start();
|
|
198
|
+
|
|
199
|
+
// User makes changes
|
|
200
|
+
system.facts.text = "Hello";
|
|
201
|
+
system.facts.text = "Hello, world";
|
|
202
|
+
system.facts.text = "Hello, world!";
|
|
203
|
+
|
|
204
|
+
const history = system.history!;
|
|
205
|
+
|
|
206
|
+
// Undo (one step back)
|
|
207
|
+
history.goBack();
|
|
208
|
+
console.log(system.facts.text); // "Hello, world"
|
|
209
|
+
|
|
210
|
+
history.goBack();
|
|
211
|
+
console.log(system.facts.text); // "Hello"
|
|
212
|
+
|
|
213
|
+
// Redo (one step forward)
|
|
214
|
+
history.goForward();
|
|
215
|
+
console.log(system.facts.text); // "Hello, world"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## How Snapshots Work
|
|
219
|
+
|
|
220
|
+
Snapshots are taken **once per reconciliation cycle**, not per individual fact change. All synchronous fact mutations within the same event handler batch into a single snapshot. One `goBack()` reverts all changes from that event.
|
|
221
|
+
|
|
222
|
+
## Changesets: Grouping Multiple Events
|
|
223
|
+
|
|
224
|
+
Changesets group snapshots from **multiple separate events** into one undo/redo unit. Useful when a single user action triggers a sequence of events.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const history = system.history!;
|
|
228
|
+
|
|
229
|
+
// Two separate events = two snapshots
|
|
230
|
+
system.events.pickUp({ index: 0 }); // → snapshot 1
|
|
231
|
+
system.events.place({ from: 0, to: 1 }); // → snapshot 2
|
|
232
|
+
// goBack() only reverts the place — need two goBack() calls
|
|
233
|
+
|
|
234
|
+
// With changeset — grouped into one undo unit
|
|
235
|
+
history.beginChangeset("Move piece");
|
|
236
|
+
system.events.pickUp({ index: 0 }); // → snapshot 1 ─┐
|
|
237
|
+
system.events.place({ from: 0, to: 1 }); // → snapshot 2 ─┘ grouped
|
|
238
|
+
history.endChangeset();
|
|
239
|
+
// One goBack() reverts both events
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Also works for direct fact mutations across separate synchronous blocks:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
history.beginChangeset("Update user");
|
|
246
|
+
system.facts.firstName = "Alice";
|
|
247
|
+
system.facts.lastName = "Smith";
|
|
248
|
+
// These batch into one snapshot synchronously, but changeset
|
|
249
|
+
// still works if an async gap separates them
|
|
250
|
+
history.endChangeset();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Always close your changesets. If you forget `endChangeset()`, all subsequent mutations get grouped into the same changeset.
|
|
254
|
+
|
|
255
|
+
## Exporting and Importing History
|
|
256
|
+
|
|
257
|
+
Serialize the full snapshot history for bug reports or debugging.
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const history = system.history!;
|
|
261
|
+
|
|
262
|
+
// Export — returns a JSON string
|
|
263
|
+
const exported = history.export();
|
|
264
|
+
localStorage.setItem("debug-session", exported);
|
|
265
|
+
|
|
266
|
+
// Import — restore from a JSON string
|
|
267
|
+
const saved = localStorage.getItem("debug-session");
|
|
268
|
+
if (saved) {
|
|
269
|
+
history.import(saved);
|
|
270
|
+
|
|
271
|
+
// Step through the user's exact state sequence
|
|
272
|
+
history.goTo(0); // First snapshot
|
|
273
|
+
history.goTo(5); // When the bug occurred
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Performance: maxSnapshots
|
|
278
|
+
|
|
279
|
+
Every fact mutation creates a snapshot. Cap the number to control memory:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// Low memory — keeps last 20 snapshots, discards oldest
|
|
283
|
+
history: { maxSnapshots: 20 },
|
|
284
|
+
|
|
285
|
+
// Development — generous cap for deep debugging
|
|
286
|
+
history: { maxSnapshots: 500 },
|
|
287
|
+
|
|
288
|
+
// Default if not specified
|
|
289
|
+
history: true, // maxSnapshots defaults to 100
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
When the cap is reached, the oldest snapshot is discarded (ring buffer / FIFO).
|
|
293
|
+
|
|
294
|
+
## Common Mistakes
|
|
295
|
+
|
|
296
|
+
### Enabling history in production
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// WRONG — snapshots consume memory on every mutation
|
|
300
|
+
const system = createSystem({
|
|
301
|
+
module: myModule,
|
|
302
|
+
history: true,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// CORRECT — gate on environment
|
|
306
|
+
const system = createSystem({
|
|
307
|
+
module: myModule,
|
|
308
|
+
history: process.env.NODE_ENV === "development"
|
|
309
|
+
? { maxSnapshots: 100 }
|
|
310
|
+
: false,
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Forgetting to end a changeset
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// WRONG — changeset never closed, all subsequent mutations are grouped
|
|
318
|
+
history.beginChangeset("update");
|
|
319
|
+
system.facts.name = "Alice";
|
|
320
|
+
// ... forgot endChangeset()
|
|
321
|
+
system.facts.unrelated = true; // Still part of the changeset!
|
|
322
|
+
|
|
323
|
+
// CORRECT — always close changesets
|
|
324
|
+
history.beginChangeset("update");
|
|
325
|
+
system.facts.name = "Alice";
|
|
326
|
+
history.endChangeset();
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Accessing history when disabled
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// WRONG — history not enabled, system.history is null
|
|
333
|
+
const system = createSystem({ module: myModule });
|
|
334
|
+
system.history.goBack(); // TypeError!
|
|
335
|
+
|
|
336
|
+
// CORRECT — check before using
|
|
337
|
+
if (system.history) {
|
|
338
|
+
system.history.goBack();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Or enable it
|
|
342
|
+
const system = createSystem({
|
|
343
|
+
module: myModule,
|
|
344
|
+
history: true,
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## See also
|
|
349
|
+
|
|
350
|
+
- [`system-api.md`](./system-api.md) — `history` config in `createSystem` + the full `system.history.*` API
|
|
351
|
+
- [`testing.md`](./testing.md) — replay-driven testing patterns that pair with recorded snapshots
|