@thehoneyjar/sigil-anchor 4.3.1

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/dist/index.js ADDED
@@ -0,0 +1,2449 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readFile, mkdir, writeFile, readdir, unlink, rm } from 'node:fs/promises';
3
+ import { existsSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { EventEmitter } from 'eventemitter3';
6
+ import { z } from 'zod';
7
+
8
+ // src/lifecycle/fork-manager.ts
9
+ var RpcError = class extends Error {
10
+ constructor(code, message, data) {
11
+ super(message);
12
+ this.code = code;
13
+ this.data = data;
14
+ this.name = "RpcError";
15
+ }
16
+ };
17
+ var RpcTimeoutError = class extends Error {
18
+ constructor(method, timeoutMs) {
19
+ super(`RPC call '${method}' timed out after ${timeoutMs}ms`);
20
+ this.method = method;
21
+ this.timeoutMs = timeoutMs;
22
+ this.name = "RpcTimeoutError";
23
+ }
24
+ };
25
+ var DEFAULT_TIMEOUT_MS = 3e4;
26
+ var requestId = 0;
27
+ async function rpcCall(url, method, params = [], timeoutMs = DEFAULT_TIMEOUT_MS) {
28
+ const controller = new AbortController();
29
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
30
+ const request = {
31
+ jsonrpc: "2.0",
32
+ id: ++requestId,
33
+ method,
34
+ params
35
+ };
36
+ try {
37
+ const response = await fetch(url, {
38
+ method: "POST",
39
+ headers: {
40
+ "Content-Type": "application/json"
41
+ },
42
+ body: JSON.stringify(request),
43
+ signal: controller.signal
44
+ });
45
+ if (!response.ok) {
46
+ throw new RpcError(-32e3, `HTTP error: ${response.status} ${response.statusText}`);
47
+ }
48
+ const data = await response.json();
49
+ if (data.error) {
50
+ throw new RpcError(data.error.code, data.error.message, data.error.data);
51
+ }
52
+ if (data.result === void 0) {
53
+ throw new RpcError(-32e3, "RPC response missing result");
54
+ }
55
+ return data.result;
56
+ } catch (error) {
57
+ if (error instanceof Error && error.name === "AbortError") {
58
+ throw new RpcTimeoutError(method, timeoutMs);
59
+ }
60
+ throw error;
61
+ } finally {
62
+ clearTimeout(timeoutId);
63
+ }
64
+ }
65
+ async function isRpcReady(url, timeoutMs = 5e3) {
66
+ try {
67
+ await rpcCall(url, "eth_chainId", [], timeoutMs);
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+ async function waitForRpc(url, maxAttempts = 30, intervalMs = 1e3) {
74
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
75
+ if (await isRpcReady(url)) {
76
+ return;
77
+ }
78
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
79
+ }
80
+ throw new Error(`RPC at ${url} not ready after ${maxAttempts} attempts`);
81
+ }
82
+ var hexStringSchema = z.string().regex(/^0x[0-9a-fA-F]*$/);
83
+ z.union([hexStringSchema, z.literal("latest"), z.literal("pending"), z.literal("earliest")]);
84
+ var ForkSchema = z.object({
85
+ id: z.string(),
86
+ network: z.object({
87
+ name: z.string(),
88
+ chainId: z.number(),
89
+ rpcUrl: z.string()
90
+ }),
91
+ blockNumber: z.number(),
92
+ rpcUrl: z.string(),
93
+ port: z.number(),
94
+ pid: z.number(),
95
+ createdAt: z.string().transform((s) => new Date(s)),
96
+ sessionId: z.string().optional()
97
+ });
98
+ var ForkRegistrySchema = z.object({
99
+ forks: z.array(ForkSchema),
100
+ lastUpdated: z.string().transform((s) => new Date(s))
101
+ });
102
+ var ZONE_HIERARCHY = ["critical", "elevated", "standard", "local"];
103
+ var ExitCode = {
104
+ PASS: 0,
105
+ DRIFT: 1,
106
+ DECEPTIVE: 2,
107
+ VIOLATION: 3,
108
+ REVERT: 4,
109
+ CORRUPT: 5,
110
+ SCHEMA: 6
111
+ };
112
+ z.object({
113
+ impersonatedAddress: z.string(),
114
+ realAddress: z.string().optional(),
115
+ component: z.string(),
116
+ observedValue: z.string().optional(),
117
+ onChainValue: z.string().optional(),
118
+ indexedValue: z.string().optional(),
119
+ dataSource: z.enum(["on-chain", "indexed", "mixed", "unknown"]).optional()
120
+ });
121
+ z.object({
122
+ type: z.enum(["data_source_mismatch", "stale_indexed_data", "lens_financial_check", "impersonation_leak"]),
123
+ severity: z.enum(["error", "warning", "info"]),
124
+ message: z.string(),
125
+ component: z.string(),
126
+ zone: z.enum(["critical", "elevated", "standard", "local"]).optional(),
127
+ expected: z.string().optional(),
128
+ actual: z.string().optional(),
129
+ suggestion: z.string().optional()
130
+ });
131
+ ({
132
+ ...ExitCode});
133
+
134
+ // src/lifecycle/fork-manager.ts
135
+ var DEFAULT_PORT_START = 8545;
136
+ var DEFAULT_PORT_END = 8600;
137
+ var DEFAULT_REGISTRY_PATH = "grimoires/anchor/forks.json";
138
+ var ForkManager = class extends EventEmitter {
139
+ forks = /* @__PURE__ */ new Map();
140
+ processes = /* @__PURE__ */ new Map();
141
+ usedPorts = /* @__PURE__ */ new Set();
142
+ registryPath;
143
+ constructor(options) {
144
+ super();
145
+ this.registryPath = options?.registryPath ?? DEFAULT_REGISTRY_PATH;
146
+ }
147
+ /**
148
+ * Initialize the ForkManager by loading persisted registry
149
+ */
150
+ async init() {
151
+ await this.loadRegistry();
152
+ }
153
+ /**
154
+ * Spawn a new Anvil fork
155
+ *
156
+ * @param config - Fork configuration
157
+ * @returns Promise resolving to the created Fork
158
+ */
159
+ async fork(config) {
160
+ const port = config.port ?? this.findAvailablePort();
161
+ const forkId = this.generateForkId();
162
+ const args = [
163
+ "--fork-url",
164
+ config.network.rpcUrl,
165
+ "--port",
166
+ port.toString(),
167
+ "--chain-id",
168
+ config.network.chainId.toString()
169
+ ];
170
+ if (config.blockNumber !== void 0) {
171
+ args.push("--fork-block-number", config.blockNumber.toString());
172
+ }
173
+ const process2 = spawn("anvil", args, {
174
+ stdio: ["ignore", "pipe", "pipe"],
175
+ detached: false
176
+ });
177
+ const pid = process2.pid;
178
+ if (!pid) {
179
+ throw new Error("Failed to spawn Anvil process");
180
+ }
181
+ const rpcUrl = `http://127.0.0.1:${port}`;
182
+ try {
183
+ await waitForRpc(rpcUrl, 30, 500);
184
+ } catch {
185
+ process2.kill();
186
+ throw new Error(`Anvil fork failed to become ready at ${rpcUrl}`);
187
+ }
188
+ const blockNumberHex = await rpcCall(rpcUrl, "eth_blockNumber");
189
+ const blockNumber = config.blockNumber ?? parseInt(blockNumberHex, 16);
190
+ const fork = {
191
+ id: forkId,
192
+ network: config.network,
193
+ blockNumber,
194
+ rpcUrl,
195
+ port,
196
+ pid,
197
+ createdAt: /* @__PURE__ */ new Date(),
198
+ ...config.sessionId !== void 0 && { sessionId: config.sessionId }
199
+ };
200
+ this.forks.set(forkId, fork);
201
+ this.processes.set(forkId, process2);
202
+ this.usedPorts.add(port);
203
+ process2.on("exit", (code) => {
204
+ this.handleProcessExit(forkId, code);
205
+ });
206
+ process2.on("error", (error) => {
207
+ this.emit("fork:error", forkId, error);
208
+ });
209
+ await this.saveRegistry();
210
+ this.emit("fork:created", fork);
211
+ return fork;
212
+ }
213
+ /**
214
+ * Wait for a fork to be ready
215
+ *
216
+ * @param forkId - Fork ID to wait for
217
+ * @param timeoutMs - Timeout in milliseconds
218
+ */
219
+ async waitForReady(forkId, timeoutMs = 3e4) {
220
+ const fork = this.forks.get(forkId);
221
+ if (!fork) {
222
+ throw new Error(`Fork ${forkId} not found`);
223
+ }
224
+ await waitForRpc(fork.rpcUrl, Math.ceil(timeoutMs / 500), 500);
225
+ }
226
+ /**
227
+ * Kill a specific fork
228
+ *
229
+ * @param forkId - Fork ID to kill
230
+ */
231
+ async kill(forkId) {
232
+ const process2 = this.processes.get(forkId);
233
+ const fork = this.forks.get(forkId);
234
+ if (process2) {
235
+ process2.kill("SIGTERM");
236
+ await new Promise((resolve) => setTimeout(resolve, 500));
237
+ if (!process2.killed) {
238
+ process2.kill("SIGKILL");
239
+ }
240
+ }
241
+ if (fork) {
242
+ this.usedPorts.delete(fork.port);
243
+ }
244
+ this.forks.delete(forkId);
245
+ this.processes.delete(forkId);
246
+ await this.saveRegistry();
247
+ }
248
+ /**
249
+ * Kill all forks
250
+ */
251
+ async killAll() {
252
+ const forkIds = Array.from(this.forks.keys());
253
+ await Promise.all(forkIds.map((id) => this.kill(id)));
254
+ }
255
+ /**
256
+ * List all active forks
257
+ *
258
+ * @returns Array of active forks
259
+ */
260
+ list() {
261
+ return Array.from(this.forks.values());
262
+ }
263
+ /**
264
+ * Get a fork by ID
265
+ *
266
+ * @param forkId - Fork ID
267
+ * @returns Fork if found, undefined otherwise
268
+ */
269
+ get(forkId) {
270
+ return this.forks.get(forkId);
271
+ }
272
+ /**
273
+ * Export environment variables for a fork
274
+ *
275
+ * @param forkId - Fork ID
276
+ * @returns Environment variables object
277
+ */
278
+ exportEnv(forkId) {
279
+ const fork = this.forks.get(forkId);
280
+ if (!fork) {
281
+ throw new Error(`Fork ${forkId} not found`);
282
+ }
283
+ return {
284
+ RPC_URL: fork.rpcUrl,
285
+ CHAIN_ID: fork.network.chainId.toString(),
286
+ FORK_BLOCK: fork.blockNumber.toString(),
287
+ FORK_ID: fork.id
288
+ };
289
+ }
290
+ /**
291
+ * Load fork registry from disk
292
+ */
293
+ async loadRegistry() {
294
+ try {
295
+ if (!existsSync(this.registryPath)) {
296
+ return;
297
+ }
298
+ const content = await readFile(this.registryPath, "utf-8");
299
+ const data = JSON.parse(content);
300
+ const registry = ForkRegistrySchema.parse(data);
301
+ for (const registryFork of registry.forks) {
302
+ try {
303
+ process.kill(registryFork.pid, 0);
304
+ const fork = {
305
+ id: registryFork.id,
306
+ network: registryFork.network,
307
+ blockNumber: registryFork.blockNumber,
308
+ rpcUrl: registryFork.rpcUrl,
309
+ port: registryFork.port,
310
+ pid: registryFork.pid,
311
+ createdAt: registryFork.createdAt,
312
+ ...registryFork.sessionId !== void 0 && { sessionId: registryFork.sessionId }
313
+ };
314
+ const ready = await this.checkForkHealth(fork);
315
+ if (ready) {
316
+ this.forks.set(fork.id, fork);
317
+ this.usedPorts.add(fork.port);
318
+ }
319
+ } catch {
320
+ }
321
+ }
322
+ } catch {
323
+ }
324
+ }
325
+ /**
326
+ * Save fork registry to disk
327
+ */
328
+ async saveRegistry() {
329
+ const registry = {
330
+ forks: Array.from(this.forks.values()).map((fork) => ({
331
+ ...fork,
332
+ createdAt: fork.createdAt.toISOString()
333
+ })),
334
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
335
+ };
336
+ const dir = dirname(this.registryPath);
337
+ if (!existsSync(dir)) {
338
+ await mkdir(dir, { recursive: true });
339
+ }
340
+ await writeFile(this.registryPath, JSON.stringify(registry, null, 2));
341
+ }
342
+ /**
343
+ * Check if a fork is still healthy
344
+ */
345
+ async checkForkHealth(fork) {
346
+ try {
347
+ await rpcCall(fork.rpcUrl, "eth_chainId", [], 2e3);
348
+ return true;
349
+ } catch {
350
+ return false;
351
+ }
352
+ }
353
+ /**
354
+ * Handle process exit
355
+ */
356
+ handleProcessExit(forkId, code) {
357
+ const fork = this.forks.get(forkId);
358
+ if (fork) {
359
+ this.usedPorts.delete(fork.port);
360
+ }
361
+ this.forks.delete(forkId);
362
+ this.processes.delete(forkId);
363
+ this.emit("fork:exit", forkId, code);
364
+ void this.saveRegistry();
365
+ }
366
+ /**
367
+ * Find an available port
368
+ */
369
+ findAvailablePort() {
370
+ for (let port = DEFAULT_PORT_START; port <= DEFAULT_PORT_END; port++) {
371
+ if (!this.usedPorts.has(port)) {
372
+ return port;
373
+ }
374
+ }
375
+ throw new Error("No available ports in range");
376
+ }
377
+ /**
378
+ * Generate a unique fork ID
379
+ */
380
+ generateForkId() {
381
+ const timestamp = Date.now().toString(36);
382
+ const random = Math.random().toString(36).substring(2, 8);
383
+ return `fork-${timestamp}-${random}`;
384
+ }
385
+ };
386
+ var defaultManager = null;
387
+ function getForkManager() {
388
+ if (!defaultManager) {
389
+ defaultManager = new ForkManager();
390
+ }
391
+ return defaultManager;
392
+ }
393
+ function resetForkManager() {
394
+ defaultManager = null;
395
+ }
396
+ var DEFAULT_BASE_PATH = "grimoires/anchor/sessions";
397
+ var SnapshotManager = class {
398
+ snapshots = /* @__PURE__ */ new Map();
399
+ taskToSnapshot = /* @__PURE__ */ new Map();
400
+ basePath;
401
+ sessionId = null;
402
+ constructor(config) {
403
+ this.basePath = config?.basePath ?? DEFAULT_BASE_PATH;
404
+ }
405
+ /**
406
+ * Initialize the manager for a session
407
+ *
408
+ * @param sessionId - Session ID to manage snapshots for
409
+ */
410
+ async init(sessionId) {
411
+ this.sessionId = sessionId;
412
+ await this.loadSnapshots();
413
+ }
414
+ /**
415
+ * Create a new snapshot
416
+ *
417
+ * @param config - Snapshot configuration
418
+ * @param rpcUrl - RPC URL of the fork
419
+ * @returns Promise resolving to snapshot metadata
420
+ */
421
+ async create(config, rpcUrl) {
422
+ const snapshotId = await rpcCall(rpcUrl, "evm_snapshot");
423
+ const blockNumberHex = await rpcCall(rpcUrl, "eth_blockNumber");
424
+ const blockNumber = parseInt(blockNumberHex, 16);
425
+ const metadata = {
426
+ id: snapshotId,
427
+ forkId: config.forkId,
428
+ sessionId: config.sessionId,
429
+ blockNumber,
430
+ createdAt: /* @__PURE__ */ new Date(),
431
+ ...config.taskId !== void 0 && { taskId: config.taskId },
432
+ ...config.description !== void 0 && { description: config.description }
433
+ };
434
+ this.snapshots.set(snapshotId, metadata);
435
+ if (config.taskId) {
436
+ this.taskToSnapshot.set(config.taskId, snapshotId);
437
+ }
438
+ await this.saveSnapshot(metadata);
439
+ return metadata;
440
+ }
441
+ /**
442
+ * Revert to a snapshot
443
+ *
444
+ * @param rpcUrl - RPC URL of the fork
445
+ * @param snapshotId - Snapshot ID to revert to
446
+ * @returns Promise resolving to true if successful
447
+ */
448
+ async revert(rpcUrl, snapshotId) {
449
+ const result = await rpcCall(rpcUrl, "evm_revert", [snapshotId]);
450
+ return result;
451
+ }
452
+ /**
453
+ * Get snapshot metadata by ID
454
+ *
455
+ * @param snapshotId - Snapshot ID
456
+ * @returns Snapshot metadata if found
457
+ */
458
+ get(snapshotId) {
459
+ return this.snapshots.get(snapshotId);
460
+ }
461
+ /**
462
+ * List all snapshots sorted by creation time
463
+ *
464
+ * @returns Array of snapshot metadata
465
+ */
466
+ list() {
467
+ return Array.from(this.snapshots.values()).sort(
468
+ (a, b) => a.createdAt.getTime() - b.createdAt.getTime()
469
+ );
470
+ }
471
+ /**
472
+ * Get snapshot for a specific task
473
+ *
474
+ * @param taskId - Task ID
475
+ * @returns Snapshot metadata if found
476
+ */
477
+ getForTask(taskId) {
478
+ const snapshotId = this.taskToSnapshot.get(taskId);
479
+ if (!snapshotId)
480
+ return void 0;
481
+ return this.snapshots.get(snapshotId);
482
+ }
483
+ /**
484
+ * Get the count of snapshots
485
+ *
486
+ * @returns Number of snapshots
487
+ */
488
+ count() {
489
+ return this.snapshots.size;
490
+ }
491
+ /**
492
+ * Cleanup old snapshots, keeping the most recent
493
+ *
494
+ * @param keepLast - Number of recent snapshots to keep
495
+ */
496
+ async cleanup(keepLast) {
497
+ const sorted = this.list();
498
+ const toDelete = sorted.slice(0, -keepLast);
499
+ for (const snapshot of toDelete) {
500
+ await this.deleteSnapshot(snapshot.id);
501
+ }
502
+ }
503
+ /**
504
+ * Get snapshot directory path for current session
505
+ */
506
+ getSnapshotDir() {
507
+ if (!this.sessionId) {
508
+ throw new Error("SnapshotManager not initialized with session ID");
509
+ }
510
+ return join(this.basePath, this.sessionId, "snapshots");
511
+ }
512
+ /**
513
+ * Get file path for a snapshot
514
+ */
515
+ getSnapshotPath(snapshotId) {
516
+ return join(this.getSnapshotDir(), `${snapshotId}.json`);
517
+ }
518
+ /**
519
+ * Load existing snapshots from disk
520
+ */
521
+ async loadSnapshots() {
522
+ const dir = this.getSnapshotDir();
523
+ if (!existsSync(dir)) {
524
+ return;
525
+ }
526
+ try {
527
+ const files = await readdir(dir);
528
+ for (const file of files) {
529
+ if (!file.endsWith(".json"))
530
+ continue;
531
+ try {
532
+ const content = await readFile(join(dir, file), "utf-8");
533
+ const data = JSON.parse(content);
534
+ data.createdAt = new Date(data.createdAt);
535
+ this.snapshots.set(data.id, data);
536
+ if (data.taskId) {
537
+ this.taskToSnapshot.set(data.taskId, data.id);
538
+ }
539
+ } catch {
540
+ }
541
+ }
542
+ } catch {
543
+ }
544
+ }
545
+ /**
546
+ * Save snapshot metadata to disk
547
+ */
548
+ async saveSnapshot(metadata) {
549
+ const dir = this.getSnapshotDir();
550
+ if (!existsSync(dir)) {
551
+ await mkdir(dir, { recursive: true });
552
+ }
553
+ await writeFile(
554
+ this.getSnapshotPath(metadata.id),
555
+ JSON.stringify(metadata, null, 2)
556
+ );
557
+ }
558
+ /**
559
+ * Delete a snapshot from memory and disk
560
+ */
561
+ async deleteSnapshot(snapshotId) {
562
+ const metadata = this.snapshots.get(snapshotId);
563
+ this.snapshots.delete(snapshotId);
564
+ if (metadata?.taskId) {
565
+ this.taskToSnapshot.delete(metadata.taskId);
566
+ }
567
+ try {
568
+ await unlink(this.getSnapshotPath(snapshotId));
569
+ } catch {
570
+ }
571
+ }
572
+ };
573
+ var defaultManager2 = null;
574
+ function getSnapshotManager() {
575
+ if (!defaultManager2) {
576
+ defaultManager2 = new SnapshotManager();
577
+ }
578
+ return defaultManager2;
579
+ }
580
+ function resetSnapshotManager() {
581
+ defaultManager2 = null;
582
+ }
583
+ var DEFAULT_BASE_PATH2 = "grimoires/anchor/checkpoints";
584
+ var DEFAULT_SNAPSHOT_INTERVAL = 10;
585
+ var DEFAULT_MAX_CHECKPOINTS = 5;
586
+ var CheckpointManager = class {
587
+ checkpoints = /* @__PURE__ */ new Map();
588
+ snapshotCount = 0;
589
+ firstSnapshotId = null;
590
+ lastSnapshotId = null;
591
+ basePath;
592
+ snapshotInterval;
593
+ maxCheckpoints;
594
+ sessionId = null;
595
+ forkId = null;
596
+ constructor(config) {
597
+ this.basePath = config?.basePath ?? DEFAULT_BASE_PATH2;
598
+ this.snapshotInterval = config?.snapshotInterval ?? DEFAULT_SNAPSHOT_INTERVAL;
599
+ this.maxCheckpoints = config?.maxCheckpoints ?? DEFAULT_MAX_CHECKPOINTS;
600
+ }
601
+ /**
602
+ * Initialize the manager for a session
603
+ *
604
+ * @param sessionId - Session ID
605
+ * @param forkId - Fork ID
606
+ */
607
+ async init(sessionId, forkId) {
608
+ this.sessionId = sessionId;
609
+ this.forkId = forkId;
610
+ await this.loadCheckpoints();
611
+ }
612
+ /**
613
+ * Called when a snapshot is created. May trigger checkpoint.
614
+ *
615
+ * @param snapshotId - ID of the created snapshot
616
+ * @param rpcUrl - RPC URL of the fork
617
+ * @returns True if checkpoint was created
618
+ */
619
+ async onSnapshot(snapshotId, rpcUrl) {
620
+ this.snapshotCount++;
621
+ if (!this.firstSnapshotId) {
622
+ this.firstSnapshotId = snapshotId;
623
+ }
624
+ this.lastSnapshotId = snapshotId;
625
+ if (this.snapshotCount >= this.snapshotInterval) {
626
+ await this.create(rpcUrl);
627
+ return true;
628
+ }
629
+ return false;
630
+ }
631
+ /**
632
+ * Create a checkpoint by exporting state
633
+ *
634
+ * @param rpcUrl - RPC URL of the fork
635
+ * @returns Checkpoint metadata
636
+ */
637
+ async create(rpcUrl) {
638
+ if (!this.sessionId || !this.forkId) {
639
+ throw new Error("CheckpointManager not initialized");
640
+ }
641
+ const state = await rpcCall(rpcUrl, "anvil_dumpState");
642
+ const blockNumberHex = await rpcCall(rpcUrl, "eth_blockNumber");
643
+ const blockNumber = parseInt(blockNumberHex, 16);
644
+ const checkpointId = this.generateCheckpointId();
645
+ const metadata = {
646
+ id: checkpointId,
647
+ sessionId: this.sessionId,
648
+ forkId: this.forkId,
649
+ snapshotRange: {
650
+ first: this.firstSnapshotId ?? "",
651
+ last: this.lastSnapshotId ?? ""
652
+ },
653
+ blockNumber,
654
+ createdAt: /* @__PURE__ */ new Date(),
655
+ snapshotCount: this.snapshotCount
656
+ };
657
+ await this.saveCheckpoint(checkpointId, state, metadata);
658
+ this.checkpoints.set(checkpointId, metadata);
659
+ this.snapshotCount = 0;
660
+ this.firstSnapshotId = null;
661
+ this.lastSnapshotId = null;
662
+ await this.cleanup();
663
+ return metadata;
664
+ }
665
+ /**
666
+ * Restore from a checkpoint
667
+ *
668
+ * @param checkpointId - Checkpoint ID to restore
669
+ * @param forkManager - ForkManager instance
670
+ * @param network - Network configuration
671
+ * @returns New fork with restored state
672
+ */
673
+ async restore(checkpointId, forkManager, network) {
674
+ if (!this.sessionId) {
675
+ throw new Error("CheckpointManager not initialized");
676
+ }
677
+ const checkpoint = this.checkpoints.get(checkpointId);
678
+ if (!checkpoint) {
679
+ throw new Error(`Checkpoint ${checkpointId} not found`);
680
+ }
681
+ const statePath = this.getStatePath(checkpointId);
682
+ const state = await readFile(statePath, "utf-8");
683
+ await forkManager.killAll();
684
+ const fork = await forkManager.fork({
685
+ network,
686
+ blockNumber: checkpoint.blockNumber,
687
+ sessionId: this.sessionId
688
+ });
689
+ await rpcCall(fork.rpcUrl, "anvil_loadState", [state]);
690
+ this.forkId = fork.id;
691
+ return fork;
692
+ }
693
+ /**
694
+ * Find the checkpoint containing a specific snapshot
695
+ *
696
+ * @param snapshotId - Snapshot ID to find
697
+ * @returns Checkpoint metadata if found
698
+ */
699
+ findCheckpointForSnapshot(snapshotId) {
700
+ const sorted = this.list().sort(
701
+ (a, b) => b.createdAt.getTime() - a.createdAt.getTime()
702
+ );
703
+ for (const checkpoint of sorted) {
704
+ if (checkpoint.snapshotRange.first <= snapshotId && checkpoint.snapshotRange.last >= snapshotId) {
705
+ return checkpoint;
706
+ }
707
+ }
708
+ return sorted[0];
709
+ }
710
+ /**
711
+ * Get checkpoint by ID
712
+ *
713
+ * @param checkpointId - Checkpoint ID
714
+ * @returns Checkpoint metadata if found
715
+ */
716
+ get(checkpointId) {
717
+ return this.checkpoints.get(checkpointId);
718
+ }
719
+ /**
720
+ * List all checkpoints sorted by time
721
+ *
722
+ * @returns Array of checkpoint metadata
723
+ */
724
+ list() {
725
+ return Array.from(this.checkpoints.values()).sort(
726
+ (a, b) => a.createdAt.getTime() - b.createdAt.getTime()
727
+ );
728
+ }
729
+ /**
730
+ * Get the latest checkpoint
731
+ *
732
+ * @returns Latest checkpoint metadata
733
+ */
734
+ latest() {
735
+ const sorted = this.list();
736
+ return sorted[sorted.length - 1];
737
+ }
738
+ /**
739
+ * Cleanup old checkpoints, keeping only the most recent
740
+ */
741
+ async cleanup() {
742
+ const sorted = this.list();
743
+ if (sorted.length <= this.maxCheckpoints) {
744
+ return;
745
+ }
746
+ const toDelete = sorted.slice(0, sorted.length - this.maxCheckpoints);
747
+ for (const checkpoint of toDelete) {
748
+ await this.deleteCheckpoint(checkpoint.id);
749
+ }
750
+ }
751
+ /**
752
+ * Get session directory path
753
+ */
754
+ getSessionDir() {
755
+ if (!this.sessionId) {
756
+ throw new Error("Session ID not set");
757
+ }
758
+ return join(this.basePath, this.sessionId);
759
+ }
760
+ /**
761
+ * Get checkpoint directory path
762
+ */
763
+ getCheckpointDir(checkpointId) {
764
+ return join(this.getSessionDir(), checkpointId);
765
+ }
766
+ /**
767
+ * Get state file path
768
+ */
769
+ getStatePath(checkpointId) {
770
+ return join(this.getCheckpointDir(checkpointId), "state.json");
771
+ }
772
+ /**
773
+ * Get metadata file path
774
+ */
775
+ getMetaPath(checkpointId) {
776
+ return join(this.getCheckpointDir(checkpointId), "meta.json");
777
+ }
778
+ /**
779
+ * Load checkpoints from disk
780
+ */
781
+ async loadCheckpoints() {
782
+ const dir = this.getSessionDir();
783
+ if (!existsSync(dir)) {
784
+ return;
785
+ }
786
+ try {
787
+ const entries = await readdir(dir, { withFileTypes: true });
788
+ for (const entry of entries) {
789
+ if (!entry.isDirectory())
790
+ continue;
791
+ const metaPath = this.getMetaPath(entry.name);
792
+ if (!existsSync(metaPath))
793
+ continue;
794
+ try {
795
+ const content = await readFile(metaPath, "utf-8");
796
+ const data = JSON.parse(content);
797
+ data.createdAt = new Date(data.createdAt);
798
+ this.checkpoints.set(data.id, data);
799
+ } catch {
800
+ }
801
+ }
802
+ } catch {
803
+ }
804
+ }
805
+ /**
806
+ * Save checkpoint to disk
807
+ */
808
+ async saveCheckpoint(checkpointId, state, metadata) {
809
+ const dir = this.getCheckpointDir(checkpointId);
810
+ if (!existsSync(dir)) {
811
+ await mkdir(dir, { recursive: true });
812
+ }
813
+ await writeFile(this.getStatePath(checkpointId), state);
814
+ await writeFile(this.getMetaPath(checkpointId), JSON.stringify(metadata, null, 2));
815
+ }
816
+ /**
817
+ * Delete a checkpoint from disk
818
+ */
819
+ async deleteCheckpoint(checkpointId) {
820
+ this.checkpoints.delete(checkpointId);
821
+ const dir = this.getCheckpointDir(checkpointId);
822
+ if (existsSync(dir)) {
823
+ await rm(dir, { recursive: true });
824
+ }
825
+ }
826
+ /**
827
+ * Generate a unique checkpoint ID
828
+ */
829
+ generateCheckpointId() {
830
+ const timestamp = Date.now().toString(36);
831
+ const random = Math.random().toString(36).substring(2, 6);
832
+ return `cp-${timestamp}-${random}`;
833
+ }
834
+ };
835
+ var defaultManager3 = null;
836
+ function getCheckpointManager() {
837
+ if (!defaultManager3) {
838
+ defaultManager3 = new CheckpointManager();
839
+ }
840
+ return defaultManager3;
841
+ }
842
+ function resetCheckpointManager() {
843
+ defaultManager3 = null;
844
+ }
845
+ var TaskSchema = z.object({
846
+ id: z.string(),
847
+ type: z.enum(["fork", "ground", "warden", "generate", "validate", "write"]),
848
+ status: z.enum(["pending", "running", "complete", "blocked", "failed"]),
849
+ snapshotId: z.string().optional(),
850
+ checkpointId: z.string().optional(),
851
+ dependencies: z.array(z.string()),
852
+ input: z.unknown(),
853
+ output: z.unknown().optional(),
854
+ error: z.string().optional(),
855
+ createdAt: z.string().transform((s) => new Date(s)),
856
+ completedAt: z.string().transform((s) => new Date(s)).optional()
857
+ });
858
+ var TaskGraphDataSchema = z.object({
859
+ sessionId: z.string(),
860
+ tasks: z.array(TaskSchema),
861
+ headTaskId: z.string().optional(),
862
+ lastUpdated: z.string().transform((s) => new Date(s))
863
+ });
864
+ var DEFAULT_BASE_PATH3 = "grimoires/anchor/sessions";
865
+ var taskCounter = 0;
866
+ function generateTaskId(type) {
867
+ return `${type}-${Date.now().toString(36)}-${(++taskCounter).toString(36)}`;
868
+ }
869
+ function resetTaskCounter() {
870
+ taskCounter = 0;
871
+ }
872
+ var TaskGraph = class {
873
+ tasks = /* @__PURE__ */ new Map();
874
+ dependents = /* @__PURE__ */ new Map();
875
+ sessionId;
876
+ basePath;
877
+ autoSave;
878
+ headTaskId;
879
+ constructor(config) {
880
+ this.sessionId = config.sessionId;
881
+ this.basePath = config.basePath ?? DEFAULT_BASE_PATH3;
882
+ this.autoSave = config.autoSave ?? true;
883
+ }
884
+ /**
885
+ * Initialize the graph by loading persisted state
886
+ */
887
+ async init() {
888
+ await this.load();
889
+ }
890
+ /**
891
+ * Add a task to the graph
892
+ *
893
+ * @param task - Task to add
894
+ */
895
+ async addTask(task) {
896
+ this.validateNoCycle(task);
897
+ this.tasks.set(task.id, task);
898
+ for (const depId of task.dependencies) {
899
+ if (!this.dependents.has(depId)) {
900
+ this.dependents.set(depId, /* @__PURE__ */ new Set());
901
+ }
902
+ this.dependents.get(depId).add(task.id);
903
+ }
904
+ this.headTaskId = task.id;
905
+ if (this.autoSave) {
906
+ await this.save();
907
+ }
908
+ }
909
+ /**
910
+ * Update task status
911
+ *
912
+ * @param taskId - Task ID
913
+ * @param status - New status
914
+ */
915
+ async updateStatus(taskId, status) {
916
+ const task = this.tasks.get(taskId);
917
+ if (!task) {
918
+ throw new Error(`Task ${taskId} not found`);
919
+ }
920
+ task.status = status;
921
+ if (status === "complete" || status === "failed") {
922
+ task.completedAt = /* @__PURE__ */ new Date();
923
+ }
924
+ if (this.autoSave) {
925
+ await this.save();
926
+ }
927
+ }
928
+ /**
929
+ * Set the snapshot binding for a task
930
+ *
931
+ * @param taskId - Task ID
932
+ * @param snapshotId - Snapshot ID
933
+ */
934
+ async setSnapshot(taskId, snapshotId) {
935
+ const task = this.tasks.get(taskId);
936
+ if (!task) {
937
+ throw new Error(`Task ${taskId} not found`);
938
+ }
939
+ task.snapshotId = snapshotId;
940
+ if (this.autoSave) {
941
+ await this.save();
942
+ }
943
+ }
944
+ /**
945
+ * Set the checkpoint binding for a task
946
+ *
947
+ * @param taskId - Task ID
948
+ * @param checkpointId - Checkpoint ID
949
+ */
950
+ async setCheckpoint(taskId, checkpointId) {
951
+ const task = this.tasks.get(taskId);
952
+ if (!task) {
953
+ throw new Error(`Task ${taskId} not found`);
954
+ }
955
+ task.checkpointId = checkpointId;
956
+ if (this.autoSave) {
957
+ await this.save();
958
+ }
959
+ }
960
+ /**
961
+ * Set task output
962
+ *
963
+ * @param taskId - Task ID
964
+ * @param output - Task output
965
+ */
966
+ async setOutput(taskId, output) {
967
+ const task = this.tasks.get(taskId);
968
+ if (!task) {
969
+ throw new Error(`Task ${taskId} not found`);
970
+ }
971
+ task.output = output;
972
+ if (this.autoSave) {
973
+ await this.save();
974
+ }
975
+ }
976
+ /**
977
+ * Set task error
978
+ *
979
+ * @param taskId - Task ID
980
+ * @param error - Error message
981
+ */
982
+ async setError(taskId, error) {
983
+ const task = this.tasks.get(taskId);
984
+ if (!task) {
985
+ throw new Error(`Task ${taskId} not found`);
986
+ }
987
+ task.error = error;
988
+ task.status = "failed";
989
+ if (this.autoSave) {
990
+ await this.save();
991
+ }
992
+ }
993
+ /**
994
+ * Get a task by ID
995
+ *
996
+ * @param taskId - Task ID
997
+ * @returns Task if found
998
+ */
999
+ getTask(taskId) {
1000
+ return this.tasks.get(taskId);
1001
+ }
1002
+ /**
1003
+ * Get all tasks
1004
+ *
1005
+ * @returns Array of all tasks
1006
+ */
1007
+ getAllTasks() {
1008
+ return Array.from(this.tasks.values());
1009
+ }
1010
+ /**
1011
+ * Get tasks by status
1012
+ *
1013
+ * @param status - Status to filter by
1014
+ * @returns Array of matching tasks
1015
+ */
1016
+ getTasksByStatus(status) {
1017
+ return Array.from(this.tasks.values()).filter((t) => t.status === status);
1018
+ }
1019
+ /**
1020
+ * Check if a task can run (all dependencies complete)
1021
+ *
1022
+ * @param taskId - Task ID
1023
+ * @returns True if all dependencies are complete
1024
+ */
1025
+ canRun(taskId) {
1026
+ const task = this.tasks.get(taskId);
1027
+ if (!task)
1028
+ return false;
1029
+ if (task.status !== "pending")
1030
+ return false;
1031
+ for (const depId of task.dependencies) {
1032
+ const dep = this.tasks.get(depId);
1033
+ if (!dep || dep.status !== "complete") {
1034
+ return false;
1035
+ }
1036
+ }
1037
+ return true;
1038
+ }
1039
+ /**
1040
+ * Get the next runnable task (pending with all deps complete)
1041
+ *
1042
+ * @returns Next runnable task or undefined
1043
+ */
1044
+ getNextRunnable() {
1045
+ for (const task of this.tasks.values()) {
1046
+ if (this.canRun(task.id)) {
1047
+ return task;
1048
+ }
1049
+ }
1050
+ return void 0;
1051
+ }
1052
+ /**
1053
+ * Propagate blocked status to all dependents of a failed task
1054
+ *
1055
+ * @param taskId - ID of the failed task
1056
+ */
1057
+ async propagateBlocked(taskId) {
1058
+ const dependentIds = this.dependents.get(taskId);
1059
+ if (!dependentIds)
1060
+ return;
1061
+ for (const depId of dependentIds) {
1062
+ const task = this.tasks.get(depId);
1063
+ if (task && task.status === "pending") {
1064
+ task.status = "blocked";
1065
+ await this.propagateBlocked(depId);
1066
+ }
1067
+ }
1068
+ if (this.autoSave) {
1069
+ await this.save();
1070
+ }
1071
+ }
1072
+ /**
1073
+ * Find the recovery point for a failed task
1074
+ *
1075
+ * @param taskId - ID of the task needing recovery
1076
+ * @returns Last complete task with snapshot, or undefined
1077
+ */
1078
+ findRecoveryPoint(taskId) {
1079
+ const task = this.tasks.get(taskId);
1080
+ if (!task)
1081
+ return void 0;
1082
+ const visited = /* @__PURE__ */ new Set();
1083
+ const queue = [...task.dependencies];
1084
+ let bestRecovery;
1085
+ while (queue.length > 0) {
1086
+ const id = queue.pop();
1087
+ if (visited.has(id))
1088
+ continue;
1089
+ visited.add(id);
1090
+ const dep = this.tasks.get(id);
1091
+ if (!dep)
1092
+ continue;
1093
+ if (dep.status === "complete" && dep.snapshotId) {
1094
+ if (!bestRecovery || dep.createdAt > bestRecovery.createdAt) {
1095
+ bestRecovery = dep;
1096
+ }
1097
+ }
1098
+ queue.push(...dep.dependencies);
1099
+ }
1100
+ return bestRecovery;
1101
+ }
1102
+ /**
1103
+ * Check if there are any blocked tasks
1104
+ *
1105
+ * @returns True if any tasks are blocked
1106
+ */
1107
+ hasBlocked() {
1108
+ for (const task of this.tasks.values()) {
1109
+ if (task.status === "blocked") {
1110
+ return true;
1111
+ }
1112
+ }
1113
+ return false;
1114
+ }
1115
+ /**
1116
+ * Check if all tasks are complete
1117
+ *
1118
+ * @returns True if all tasks are complete
1119
+ */
1120
+ isComplete() {
1121
+ for (const task of this.tasks.values()) {
1122
+ if (task.status !== "complete") {
1123
+ return false;
1124
+ }
1125
+ }
1126
+ return this.tasks.size > 0;
1127
+ }
1128
+ /**
1129
+ * Get the graph file path
1130
+ */
1131
+ getGraphPath() {
1132
+ return join(this.basePath, this.sessionId, "graph.json");
1133
+ }
1134
+ /**
1135
+ * Export graph data as JSON-serializable object
1136
+ *
1137
+ * @returns Task graph data
1138
+ */
1139
+ toJSON() {
1140
+ return {
1141
+ sessionId: this.sessionId,
1142
+ tasks: Array.from(this.tasks.values()),
1143
+ lastUpdated: /* @__PURE__ */ new Date(),
1144
+ ...this.headTaskId !== void 0 && { headTaskId: this.headTaskId }
1145
+ };
1146
+ }
1147
+ /**
1148
+ * Save the graph to disk
1149
+ */
1150
+ async save() {
1151
+ const data = this.toJSON();
1152
+ const path = this.getGraphPath();
1153
+ const dir = dirname(path);
1154
+ if (!existsSync(dir)) {
1155
+ await mkdir(dir, { recursive: true });
1156
+ }
1157
+ await writeFile(path, JSON.stringify(data, null, 2));
1158
+ }
1159
+ /**
1160
+ * Load the graph from disk
1161
+ */
1162
+ async load() {
1163
+ const path = this.getGraphPath();
1164
+ if (!existsSync(path)) {
1165
+ return;
1166
+ }
1167
+ try {
1168
+ const content = await readFile(path, "utf-8");
1169
+ const raw = JSON.parse(content);
1170
+ const data = TaskGraphDataSchema.parse(raw);
1171
+ this.tasks.clear();
1172
+ this.dependents.clear();
1173
+ for (const task of data.tasks) {
1174
+ this.tasks.set(task.id, task);
1175
+ for (const depId of task.dependencies) {
1176
+ if (!this.dependents.has(depId)) {
1177
+ this.dependents.set(depId, /* @__PURE__ */ new Set());
1178
+ }
1179
+ this.dependents.get(depId).add(task.id);
1180
+ }
1181
+ }
1182
+ this.headTaskId = data.headTaskId;
1183
+ } catch {
1184
+ }
1185
+ }
1186
+ /**
1187
+ * Validate that adding a task doesn't create a cycle
1188
+ */
1189
+ validateNoCycle(newTask) {
1190
+ const visited = /* @__PURE__ */ new Set();
1191
+ const stack = /* @__PURE__ */ new Set();
1192
+ const hasCycle = (taskId) => {
1193
+ if (stack.has(taskId))
1194
+ return true;
1195
+ if (visited.has(taskId))
1196
+ return false;
1197
+ visited.add(taskId);
1198
+ stack.add(taskId);
1199
+ const task = taskId === newTask.id ? newTask : this.tasks.get(taskId);
1200
+ if (task) {
1201
+ for (const depId of task.dependencies) {
1202
+ if (hasCycle(depId))
1203
+ return true;
1204
+ }
1205
+ }
1206
+ stack.delete(taskId);
1207
+ return false;
1208
+ };
1209
+ if (hasCycle(newTask.id)) {
1210
+ throw new Error(`Adding task ${newTask.id} would create a circular dependency`);
1211
+ }
1212
+ }
1213
+ };
1214
+
1215
+ // src/lifecycle/session-manager.ts
1216
+ var DEFAULT_BASE_PATH4 = "grimoires/anchor/sessions";
1217
+ var SessionManager = class {
1218
+ sessions = /* @__PURE__ */ new Map();
1219
+ currentSession = null;
1220
+ basePath;
1221
+ constructor(config) {
1222
+ this.basePath = config?.basePath ?? DEFAULT_BASE_PATH4;
1223
+ }
1224
+ /**
1225
+ * Initialize the manager by loading session index
1226
+ */
1227
+ async init() {
1228
+ await this.loadSessionIndex();
1229
+ }
1230
+ /**
1231
+ * Create a new session
1232
+ *
1233
+ * @param network - Network to fork
1234
+ * @param options - Session options
1235
+ * @returns Created session
1236
+ */
1237
+ async create(network, options) {
1238
+ const sessionId = this.generateSessionId();
1239
+ const forkManager = new ForkManager();
1240
+ await forkManager.init();
1241
+ const fork = await forkManager.fork({
1242
+ network,
1243
+ sessionId,
1244
+ ...options?.blockNumber !== void 0 && { blockNumber: options.blockNumber }
1245
+ });
1246
+ const snapshotManager = new SnapshotManager();
1247
+ await snapshotManager.init(sessionId);
1248
+ const checkpointManager = new CheckpointManager();
1249
+ await checkpointManager.init(sessionId, fork.id);
1250
+ const taskGraph = new TaskGraph({
1251
+ sessionId,
1252
+ basePath: this.basePath,
1253
+ autoSave: true
1254
+ });
1255
+ await taskGraph.init();
1256
+ const initialSnapshot = await snapshotManager.create(
1257
+ {
1258
+ forkId: fork.id,
1259
+ sessionId,
1260
+ description: "Initial session snapshot"
1261
+ },
1262
+ fork.rpcUrl
1263
+ );
1264
+ const forkTask = {
1265
+ id: `fork-${fork.id}`,
1266
+ type: "fork",
1267
+ status: "complete",
1268
+ snapshotId: initialSnapshot.id,
1269
+ dependencies: [],
1270
+ input: { network, blockNumber: fork.blockNumber },
1271
+ output: { forkId: fork.id, rpcUrl: fork.rpcUrl },
1272
+ createdAt: /* @__PURE__ */ new Date(),
1273
+ completedAt: /* @__PURE__ */ new Date()
1274
+ };
1275
+ await taskGraph.addTask(forkTask);
1276
+ const metadata = {
1277
+ id: sessionId,
1278
+ network,
1279
+ forkId: fork.id,
1280
+ createdAt: /* @__PURE__ */ new Date(),
1281
+ lastActivity: /* @__PURE__ */ new Date(),
1282
+ status: "active",
1283
+ initialBlock: fork.blockNumber
1284
+ };
1285
+ this.sessions.set(sessionId, metadata);
1286
+ await this.saveSession(metadata);
1287
+ await this.saveSessionIndex();
1288
+ this.currentSession = {
1289
+ metadata,
1290
+ fork,
1291
+ forkManager,
1292
+ snapshotManager,
1293
+ checkpointManager,
1294
+ taskGraph
1295
+ };
1296
+ return this.currentSession;
1297
+ }
1298
+ /**
1299
+ * Resume an existing session
1300
+ *
1301
+ * @param sessionId - Session ID to resume
1302
+ * @returns Resumed session
1303
+ */
1304
+ async resume(sessionId) {
1305
+ const metadata = this.sessions.get(sessionId);
1306
+ if (!metadata) {
1307
+ throw new Error(`Session ${sessionId} not found`);
1308
+ }
1309
+ const forkManager = new ForkManager();
1310
+ await forkManager.init();
1311
+ const snapshotManager = new SnapshotManager();
1312
+ await snapshotManager.init(sessionId);
1313
+ const checkpointManager = new CheckpointManager();
1314
+ const taskGraph = new TaskGraph({
1315
+ sessionId,
1316
+ basePath: this.basePath,
1317
+ autoSave: true
1318
+ });
1319
+ await taskGraph.init();
1320
+ let fork = forkManager.get(metadata.forkId);
1321
+ if (!fork || taskGraph.hasBlocked()) {
1322
+ fork = await this.recover(
1323
+ sessionId,
1324
+ metadata,
1325
+ forkManager,
1326
+ snapshotManager,
1327
+ checkpointManager,
1328
+ taskGraph
1329
+ );
1330
+ }
1331
+ if (!fork) {
1332
+ throw new Error(`Failed to restore fork for session ${sessionId}`);
1333
+ }
1334
+ await checkpointManager.init(sessionId, fork.id);
1335
+ metadata.lastActivity = /* @__PURE__ */ new Date();
1336
+ metadata.forkId = fork.id;
1337
+ metadata.status = "active";
1338
+ await this.saveSession(metadata);
1339
+ this.currentSession = {
1340
+ metadata,
1341
+ fork,
1342
+ forkManager,
1343
+ snapshotManager,
1344
+ checkpointManager,
1345
+ taskGraph
1346
+ };
1347
+ return this.currentSession;
1348
+ }
1349
+ /**
1350
+ * Recover a session from checkpoint or snapshot
1351
+ */
1352
+ async recover(sessionId, metadata, forkManager, snapshotManager, checkpointManager, taskGraph) {
1353
+ const latestCheckpoint = checkpointManager.latest();
1354
+ if (latestCheckpoint) {
1355
+ console.log(`Recovering session ${sessionId} from checkpoint ${latestCheckpoint.id}`);
1356
+ return await checkpointManager.restore(
1357
+ latestCheckpoint.id,
1358
+ forkManager,
1359
+ metadata.network
1360
+ );
1361
+ }
1362
+ const blockedTasks = taskGraph.getTasksByStatus("blocked");
1363
+ const failedTasks = taskGraph.getTasksByStatus("failed");
1364
+ const problematicTask = blockedTasks[0] ?? failedTasks[0];
1365
+ if (problematicTask) {
1366
+ const recoveryPoint = taskGraph.findRecoveryPoint(problematicTask.id);
1367
+ if (recoveryPoint?.snapshotId) {
1368
+ const fork = await forkManager.fork({
1369
+ network: metadata.network,
1370
+ blockNumber: metadata.initialBlock,
1371
+ sessionId
1372
+ });
1373
+ const success = await snapshotManager.revert(fork.rpcUrl, recoveryPoint.snapshotId);
1374
+ if (!success) {
1375
+ throw new Error(`Failed to revert to snapshot ${recoveryPoint.snapshotId}`);
1376
+ }
1377
+ for (const task of [...blockedTasks, ...failedTasks]) {
1378
+ await taskGraph.updateStatus(task.id, "pending");
1379
+ }
1380
+ return fork;
1381
+ }
1382
+ }
1383
+ console.log(`No recovery point found, creating fresh fork for session ${sessionId}`);
1384
+ return await forkManager.fork({
1385
+ network: metadata.network,
1386
+ blockNumber: metadata.initialBlock,
1387
+ sessionId
1388
+ });
1389
+ }
1390
+ /**
1391
+ * Get current session
1392
+ *
1393
+ * @returns Current session or null
1394
+ */
1395
+ current() {
1396
+ return this.currentSession;
1397
+ }
1398
+ /**
1399
+ * List all sessions
1400
+ *
1401
+ * @param filter - Optional filter for status
1402
+ * @returns Array of session metadata
1403
+ */
1404
+ list(filter) {
1405
+ let sessions = Array.from(this.sessions.values());
1406
+ if (filter?.status) {
1407
+ sessions = sessions.filter((s) => s.status === filter.status);
1408
+ }
1409
+ return sessions.sort((a, b) => b.lastActivity.getTime() - a.lastActivity.getTime());
1410
+ }
1411
+ /**
1412
+ * Get session by ID
1413
+ *
1414
+ * @param sessionId - Session ID
1415
+ * @returns Session metadata if found
1416
+ */
1417
+ get(sessionId) {
1418
+ return this.sessions.get(sessionId);
1419
+ }
1420
+ /**
1421
+ * Update session status
1422
+ *
1423
+ * @param sessionId - Session ID
1424
+ * @param status - New status
1425
+ */
1426
+ async updateStatus(sessionId, status) {
1427
+ const metadata = this.sessions.get(sessionId);
1428
+ if (!metadata) {
1429
+ throw new Error(`Session ${sessionId} not found`);
1430
+ }
1431
+ metadata.status = status;
1432
+ metadata.lastActivity = /* @__PURE__ */ new Date();
1433
+ await this.saveSession(metadata);
1434
+ }
1435
+ /**
1436
+ * Get session directory path
1437
+ */
1438
+ getSessionDir(sessionId) {
1439
+ return join(this.basePath, sessionId);
1440
+ }
1441
+ /**
1442
+ * Get session metadata path
1443
+ */
1444
+ getSessionPath(sessionId) {
1445
+ return join(this.getSessionDir(sessionId), "session.json");
1446
+ }
1447
+ /**
1448
+ * Load session index
1449
+ */
1450
+ async loadSessionIndex() {
1451
+ if (!existsSync(this.basePath)) {
1452
+ return;
1453
+ }
1454
+ try {
1455
+ const entries = await readdir(this.basePath, { withFileTypes: true });
1456
+ for (const entry of entries) {
1457
+ if (!entry.isDirectory())
1458
+ continue;
1459
+ const sessionPath = this.getSessionPath(entry.name);
1460
+ if (!existsSync(sessionPath))
1461
+ continue;
1462
+ try {
1463
+ const content = await readFile(sessionPath, "utf-8");
1464
+ const data = JSON.parse(content);
1465
+ data.createdAt = new Date(data.createdAt);
1466
+ data.lastActivity = new Date(data.lastActivity);
1467
+ this.sessions.set(data.id, data);
1468
+ } catch {
1469
+ }
1470
+ }
1471
+ } catch {
1472
+ }
1473
+ }
1474
+ /**
1475
+ * Save session index
1476
+ */
1477
+ async saveSessionIndex() {
1478
+ if (!existsSync(this.basePath)) {
1479
+ await mkdir(this.basePath, { recursive: true });
1480
+ }
1481
+ const index = Array.from(this.sessions.values()).map((s) => ({
1482
+ id: s.id,
1483
+ status: s.status,
1484
+ lastActivity: s.lastActivity
1485
+ }));
1486
+ await writeFile(
1487
+ join(this.basePath, "index.json"),
1488
+ JSON.stringify(index, null, 2)
1489
+ );
1490
+ }
1491
+ /**
1492
+ * Save session metadata
1493
+ */
1494
+ async saveSession(metadata) {
1495
+ const dir = this.getSessionDir(metadata.id);
1496
+ if (!existsSync(dir)) {
1497
+ await mkdir(dir, { recursive: true });
1498
+ }
1499
+ await writeFile(this.getSessionPath(metadata.id), JSON.stringify(metadata, null, 2));
1500
+ }
1501
+ /**
1502
+ * Generate unique session ID
1503
+ */
1504
+ generateSessionId() {
1505
+ const timestamp = Date.now().toString(36);
1506
+ const random = Math.random().toString(36).substring(2, 6);
1507
+ return `session-${timestamp}-${random}`;
1508
+ }
1509
+ };
1510
+ var defaultManager4 = null;
1511
+ function getSessionManager() {
1512
+ if (!defaultManager4) {
1513
+ defaultManager4 = new SessionManager();
1514
+ }
1515
+ return defaultManager4;
1516
+ }
1517
+ function resetSessionManager() {
1518
+ defaultManager4 = null;
1519
+ }
1520
+ var DEFAULT_PHYSICS_PATH = ".claude/rules/01-sigil-physics.md";
1521
+ var cachedPhysics = null;
1522
+ var cachedPath = null;
1523
+ function parseSyncStrategy(value) {
1524
+ const normalized = value.toLowerCase().trim();
1525
+ if (normalized === "pessimistic")
1526
+ return "pessimistic";
1527
+ if (normalized === "optimistic")
1528
+ return "optimistic";
1529
+ if (normalized === "immediate")
1530
+ return "immediate";
1531
+ return "optimistic";
1532
+ }
1533
+ function parseTiming(value) {
1534
+ const match = value.match(/(\d+)\s*ms/i);
1535
+ if (match && match[1]) {
1536
+ return parseInt(match[1], 10);
1537
+ }
1538
+ const num = parseInt(value, 10);
1539
+ return isNaN(num) ? 200 : num;
1540
+ }
1541
+ function parseConfirmation(value) {
1542
+ const normalized = value.toLowerCase().trim();
1543
+ if (normalized === "required" || normalized === "yes")
1544
+ return "required";
1545
+ if (normalized.includes("toast") || normalized.includes("undo"))
1546
+ return "toast_undo";
1547
+ if (normalized === "none" || normalized === "no")
1548
+ return "none";
1549
+ return "none";
1550
+ }
1551
+ function parseEffectType(value) {
1552
+ const normalized = value.toLowerCase().replace(/[\s-]/g, "_").trim();
1553
+ const mapping = {
1554
+ financial: "financial",
1555
+ destructive: "destructive",
1556
+ soft_delete: "soft_delete",
1557
+ "soft delete": "soft_delete",
1558
+ standard: "standard",
1559
+ navigation: "navigation",
1560
+ query: "query",
1561
+ local_state: "local",
1562
+ "local state": "local",
1563
+ local: "local",
1564
+ high_freq: "high_freq",
1565
+ "high-freq": "high_freq",
1566
+ highfreq: "high_freq"
1567
+ };
1568
+ return mapping[normalized] ?? null;
1569
+ }
1570
+ function parsePhysicsTable(content) {
1571
+ const physics = /* @__PURE__ */ new Map();
1572
+ const tableMatch = content.match(
1573
+ /<physics_table>[\s\S]*?\|[\s\S]*?<\/physics_table>/
1574
+ );
1575
+ if (!tableMatch) {
1576
+ console.warn("Physics table not found in content");
1577
+ return getDefaultPhysics();
1578
+ }
1579
+ const tableContent = tableMatch[0];
1580
+ const lines = tableContent.split("\n");
1581
+ for (const line of lines) {
1582
+ if (!line.includes("|") || line.includes("---") || line.includes("Effect")) {
1583
+ continue;
1584
+ }
1585
+ const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
1586
+ if (cells.length < 4)
1587
+ continue;
1588
+ const effectStr = cells[0];
1589
+ const syncStr = cells[1];
1590
+ const timingStr = cells[2];
1591
+ const confirmStr = cells[3];
1592
+ const whyParts = cells.slice(4);
1593
+ if (!effectStr || !syncStr || !timingStr || !confirmStr)
1594
+ continue;
1595
+ const effect = parseEffectType(effectStr);
1596
+ if (!effect)
1597
+ continue;
1598
+ const rule = {
1599
+ effect,
1600
+ sync: parseSyncStrategy(syncStr),
1601
+ timing: parseTiming(timingStr),
1602
+ confirmation: parseConfirmation(confirmStr),
1603
+ rationale: whyParts.join(" ").trim()
1604
+ };
1605
+ physics.set(effect, rule);
1606
+ }
1607
+ return physics;
1608
+ }
1609
+ function getDefaultPhysics() {
1610
+ const physics = /* @__PURE__ */ new Map();
1611
+ physics.set("financial", {
1612
+ effect: "financial",
1613
+ sync: "pessimistic",
1614
+ timing: 800,
1615
+ confirmation: "required",
1616
+ rationale: "Money can't roll back. Users need time to verify."
1617
+ });
1618
+ physics.set("destructive", {
1619
+ effect: "destructive",
1620
+ sync: "pessimistic",
1621
+ timing: 600,
1622
+ confirmation: "required",
1623
+ rationale: "Permanent actions need deliberation."
1624
+ });
1625
+ physics.set("soft_delete", {
1626
+ effect: "soft_delete",
1627
+ sync: "optimistic",
1628
+ timing: 200,
1629
+ confirmation: "toast_undo",
1630
+ rationale: "Undo exists, so we can be fast."
1631
+ });
1632
+ physics.set("standard", {
1633
+ effect: "standard",
1634
+ sync: "optimistic",
1635
+ timing: 200,
1636
+ confirmation: "none",
1637
+ rationale: "Low stakes = snappy feedback."
1638
+ });
1639
+ physics.set("navigation", {
1640
+ effect: "navigation",
1641
+ sync: "immediate",
1642
+ timing: 150,
1643
+ confirmation: "none",
1644
+ rationale: "URL changes feel instant."
1645
+ });
1646
+ physics.set("query", {
1647
+ effect: "query",
1648
+ sync: "optimistic",
1649
+ timing: 150,
1650
+ confirmation: "none",
1651
+ rationale: "Data retrieval, no state change."
1652
+ });
1653
+ physics.set("local", {
1654
+ effect: "local",
1655
+ sync: "immediate",
1656
+ timing: 100,
1657
+ confirmation: "none",
1658
+ rationale: "No server = instant expected."
1659
+ });
1660
+ physics.set("high_freq", {
1661
+ effect: "high_freq",
1662
+ sync: "immediate",
1663
+ timing: 0,
1664
+ confirmation: "none",
1665
+ rationale: "Animation becomes friction."
1666
+ });
1667
+ return physics;
1668
+ }
1669
+ async function loadPhysics(path) {
1670
+ const physicsPath = path ?? DEFAULT_PHYSICS_PATH;
1671
+ if (cachedPhysics && cachedPath === physicsPath) {
1672
+ return cachedPhysics;
1673
+ }
1674
+ if (!existsSync(physicsPath)) {
1675
+ console.warn(`Physics file not found at ${physicsPath}, using defaults`);
1676
+ cachedPhysics = getDefaultPhysics();
1677
+ cachedPath = physicsPath;
1678
+ return cachedPhysics;
1679
+ }
1680
+ try {
1681
+ const content = await readFile(physicsPath, "utf-8");
1682
+ cachedPhysics = parsePhysicsTable(content);
1683
+ cachedPath = physicsPath;
1684
+ if (cachedPhysics.size === 0) {
1685
+ console.warn("No physics rules parsed, using defaults");
1686
+ cachedPhysics = getDefaultPhysics();
1687
+ }
1688
+ return cachedPhysics;
1689
+ } catch (error) {
1690
+ console.warn(`Error loading physics from ${physicsPath}:`, error);
1691
+ cachedPhysics = getDefaultPhysics();
1692
+ cachedPath = physicsPath;
1693
+ return cachedPhysics;
1694
+ }
1695
+ }
1696
+ async function getPhysicsRule(effect, physics) {
1697
+ const table = physics ?? await loadPhysics();
1698
+ return table.get(effect);
1699
+ }
1700
+ function clearPhysicsCache() {
1701
+ cachedPhysics = null;
1702
+ cachedPath = null;
1703
+ }
1704
+ function isPhysicsCached() {
1705
+ return cachedPhysics !== null;
1706
+ }
1707
+ var DEFAULT_VOCABULARY_PATH = ".claude/rules/08-sigil-lexicon.md";
1708
+ var cachedVocabulary = null;
1709
+ var cachedPath2 = null;
1710
+ function parseKeywordsFromBlock(block) {
1711
+ const keywords = [];
1712
+ const lines = block.split("\n");
1713
+ for (const line of lines) {
1714
+ const colonIndex = line.indexOf(":");
1715
+ const content = colonIndex >= 0 ? line.slice(colonIndex + 1) : line;
1716
+ const words = content.split(/[,\s]+/).map((w) => w.trim().toLowerCase()).filter((w) => w.length > 0 && !w.includes("```"));
1717
+ keywords.push(...words);
1718
+ }
1719
+ return [...new Set(keywords)];
1720
+ }
1721
+ function parseEffectKeywords(content) {
1722
+ const effects = /* @__PURE__ */ new Map();
1723
+ const sectionMatch = content.match(
1724
+ /<effect_keywords>[\s\S]*?<\/effect_keywords>/
1725
+ );
1726
+ if (!sectionMatch) {
1727
+ return effects;
1728
+ }
1729
+ const section = sectionMatch[0];
1730
+ const effectPatterns = [
1731
+ {
1732
+ effect: "financial",
1733
+ pattern: /###\s*Financial[\s\S]*?```([\s\S]*?)```/i
1734
+ },
1735
+ {
1736
+ effect: "destructive",
1737
+ pattern: /###\s*Destructive[\s\S]*?```([\s\S]*?)```/i
1738
+ },
1739
+ {
1740
+ effect: "soft_delete",
1741
+ pattern: /###\s*Soft\s*Delete[\s\S]*?```([\s\S]*?)```/i
1742
+ },
1743
+ {
1744
+ effect: "standard",
1745
+ pattern: /###\s*Standard[\s\S]*?```([\s\S]*?)```/i
1746
+ },
1747
+ {
1748
+ effect: "local",
1749
+ pattern: /###\s*Local\s*State[\s\S]*?```([\s\S]*?)```/i
1750
+ },
1751
+ {
1752
+ effect: "navigation",
1753
+ pattern: /###\s*Navigation[\s\S]*?```([\s\S]*?)```/i
1754
+ },
1755
+ {
1756
+ effect: "query",
1757
+ pattern: /###\s*Query[\s\S]*?```([\s\S]*?)```/i
1758
+ }
1759
+ ];
1760
+ for (const { effect, pattern } of effectPatterns) {
1761
+ const match = section.match(pattern);
1762
+ if (match && match[1]) {
1763
+ const keywords = parseKeywordsFromBlock(match[1]);
1764
+ if (keywords.length > 0) {
1765
+ effects.set(effect, {
1766
+ keywords,
1767
+ effect,
1768
+ category: "lexicon"
1769
+ });
1770
+ }
1771
+ }
1772
+ }
1773
+ return effects;
1774
+ }
1775
+ function parseTypeOverrides(content) {
1776
+ const overrides = /* @__PURE__ */ new Map();
1777
+ const sectionMatch = content.match(
1778
+ /<type_overrides>[\s\S]*?<\/type_overrides>/
1779
+ );
1780
+ if (!sectionMatch) {
1781
+ return overrides;
1782
+ }
1783
+ const section = sectionMatch[0];
1784
+ const lines = section.split("\n");
1785
+ for (const line of lines) {
1786
+ if (!line.includes("|") || line.includes("---") || line.includes("Type Pattern")) {
1787
+ continue;
1788
+ }
1789
+ const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
1790
+ if (cells.length < 2)
1791
+ continue;
1792
+ const typePattern = cells[0];
1793
+ const forcedEffect = cells[1];
1794
+ if (!typePattern || !forcedEffect)
1795
+ continue;
1796
+ const types = typePattern.replace(/`/g, "").split(",").map((t) => t.trim().toLowerCase()).filter((t) => t.length > 0);
1797
+ const effect = mapEffectString(forcedEffect);
1798
+ if (effect) {
1799
+ for (const type of types) {
1800
+ overrides.set(type, effect);
1801
+ }
1802
+ }
1803
+ }
1804
+ return overrides;
1805
+ }
1806
+ function parseDomainDefaults(content) {
1807
+ const defaults = /* @__PURE__ */ new Map();
1808
+ const sectionMatch = content.match(
1809
+ /<domain_context>[\s\S]*?<\/domain_context>/
1810
+ );
1811
+ if (!sectionMatch) {
1812
+ return defaults;
1813
+ }
1814
+ const section = sectionMatch[0];
1815
+ const domainHeaderPattern = /###\s*([\w\/]+)\s*\n```([\s\S]*?)```/gi;
1816
+ let match;
1817
+ while ((match = domainHeaderPattern.exec(section)) !== null) {
1818
+ const domainName = match[1];
1819
+ const domainContent = match[2];
1820
+ if (!domainName || !domainContent)
1821
+ continue;
1822
+ const defaultMatch = domainContent.match(/Default:\s*([\w\s()]+)/i);
1823
+ if (defaultMatch && defaultMatch[1]) {
1824
+ const effect = mapEffectString(defaultMatch[1]);
1825
+ if (effect) {
1826
+ const keywordMatch = domainContent.match(/Keywords:\s*([\w,\s]+)/i);
1827
+ if (keywordMatch && keywordMatch[1]) {
1828
+ const keywords = keywordMatch[1].split(",").map((k) => k.trim().toLowerCase()).filter((k) => k.length > 0);
1829
+ for (const keyword of keywords) {
1830
+ defaults.set(keyword, effect);
1831
+ }
1832
+ }
1833
+ defaults.set(domainName.toLowerCase().replace("/", "_"), effect);
1834
+ }
1835
+ }
1836
+ }
1837
+ return defaults;
1838
+ }
1839
+ function mapEffectString(value) {
1840
+ const normalized = value.toLowerCase().trim();
1841
+ if (normalized.includes("financial"))
1842
+ return "financial";
1843
+ if (normalized.includes("destructive"))
1844
+ return "destructive";
1845
+ if (normalized.includes("soft") && normalized.includes("delete"))
1846
+ return "soft_delete";
1847
+ if (normalized.includes("standard"))
1848
+ return "standard";
1849
+ if (normalized.includes("local"))
1850
+ return "local";
1851
+ if (normalized.includes("navigation"))
1852
+ return "navigation";
1853
+ if (normalized.includes("query"))
1854
+ return "query";
1855
+ if (normalized.includes("immediate"))
1856
+ return "local";
1857
+ return null;
1858
+ }
1859
+ function getDefaultVocabulary() {
1860
+ const effects = /* @__PURE__ */ new Map();
1861
+ effects.set("financial", {
1862
+ keywords: [
1863
+ "claim",
1864
+ "deposit",
1865
+ "withdraw",
1866
+ "transfer",
1867
+ "swap",
1868
+ "send",
1869
+ "pay",
1870
+ "purchase",
1871
+ "mint",
1872
+ "burn",
1873
+ "stake",
1874
+ "unstake",
1875
+ "bridge",
1876
+ "approve",
1877
+ "redeem",
1878
+ "harvest"
1879
+ ],
1880
+ effect: "financial",
1881
+ category: "default"
1882
+ });
1883
+ effects.set("destructive", {
1884
+ keywords: [
1885
+ "delete",
1886
+ "remove",
1887
+ "destroy",
1888
+ "revoke",
1889
+ "terminate",
1890
+ "purge",
1891
+ "erase",
1892
+ "wipe"
1893
+ ],
1894
+ effect: "destructive",
1895
+ category: "default"
1896
+ });
1897
+ effects.set("soft_delete", {
1898
+ keywords: ["archive", "hide", "trash", "dismiss", "snooze", "mute"],
1899
+ effect: "soft_delete",
1900
+ category: "default"
1901
+ });
1902
+ effects.set("standard", {
1903
+ keywords: [
1904
+ "save",
1905
+ "update",
1906
+ "edit",
1907
+ "create",
1908
+ "add",
1909
+ "like",
1910
+ "follow",
1911
+ "bookmark"
1912
+ ],
1913
+ effect: "standard",
1914
+ category: "default"
1915
+ });
1916
+ effects.set("local", {
1917
+ keywords: ["toggle", "switch", "expand", "collapse", "select", "focus"],
1918
+ effect: "local",
1919
+ category: "default"
1920
+ });
1921
+ effects.set("navigation", {
1922
+ keywords: ["navigate", "go", "back", "forward", "link", "route"],
1923
+ effect: "navigation",
1924
+ category: "default"
1925
+ });
1926
+ effects.set("query", {
1927
+ keywords: ["fetch", "load", "get", "list", "search", "find"],
1928
+ effect: "query",
1929
+ category: "default"
1930
+ });
1931
+ const typeOverrides = /* @__PURE__ */ new Map([
1932
+ ["currency", "financial"],
1933
+ ["money", "financial"],
1934
+ ["amount", "financial"],
1935
+ ["wei", "financial"],
1936
+ ["bigint", "financial"],
1937
+ ["token", "financial"],
1938
+ ["balance", "financial"],
1939
+ ["price", "financial"],
1940
+ ["fee", "financial"],
1941
+ ["password", "destructive"],
1942
+ ["secret", "destructive"],
1943
+ ["key", "destructive"],
1944
+ ["permission", "destructive"],
1945
+ ["role", "destructive"],
1946
+ ["access", "destructive"],
1947
+ ["theme", "local"],
1948
+ ["preference", "local"],
1949
+ ["setting", "local"],
1950
+ ["filter", "local"],
1951
+ ["sort", "local"],
1952
+ ["view", "local"]
1953
+ ]);
1954
+ const domainDefaults = /* @__PURE__ */ new Map([
1955
+ ["wallet", "financial"],
1956
+ ["token", "financial"],
1957
+ ["nft", "financial"],
1958
+ ["contract", "financial"],
1959
+ ["chain", "financial"],
1960
+ ["gas", "financial"],
1961
+ ["cart", "standard"],
1962
+ ["checkout", "financial"],
1963
+ ["payment", "financial"]
1964
+ ]);
1965
+ return { effects, typeOverrides, domainDefaults };
1966
+ }
1967
+ async function loadVocabulary(path) {
1968
+ const vocabPath = path ?? DEFAULT_VOCABULARY_PATH;
1969
+ if (cachedVocabulary && cachedPath2 === vocabPath) {
1970
+ return cachedVocabulary;
1971
+ }
1972
+ if (!existsSync(vocabPath)) {
1973
+ console.warn(`Vocabulary file not found at ${vocabPath}, using defaults`);
1974
+ cachedVocabulary = getDefaultVocabulary();
1975
+ cachedPath2 = vocabPath;
1976
+ return cachedVocabulary;
1977
+ }
1978
+ try {
1979
+ const content = await readFile(vocabPath, "utf-8");
1980
+ const effects = parseEffectKeywords(content);
1981
+ const typeOverrides = parseTypeOverrides(content);
1982
+ const domainDefaults = parseDomainDefaults(content);
1983
+ if (effects.size === 0) {
1984
+ console.warn("No vocabulary parsed, using defaults");
1985
+ cachedVocabulary = getDefaultVocabulary();
1986
+ } else {
1987
+ cachedVocabulary = { effects, typeOverrides, domainDefaults };
1988
+ }
1989
+ cachedPath2 = vocabPath;
1990
+ return cachedVocabulary;
1991
+ } catch (error) {
1992
+ console.warn(`Error loading vocabulary from ${vocabPath}:`, error);
1993
+ cachedVocabulary = getDefaultVocabulary();
1994
+ cachedPath2 = vocabPath;
1995
+ return cachedVocabulary;
1996
+ }
1997
+ }
1998
+ async function resolveEffectFromKeywords(keywords, vocabulary) {
1999
+ const vocab = vocabulary ?? await loadVocabulary();
2000
+ const normalizedKeywords = keywords.map((k) => k.toLowerCase().trim());
2001
+ const priorityOrder = [
2002
+ "financial",
2003
+ "destructive",
2004
+ "soft_delete",
2005
+ "standard",
2006
+ "local",
2007
+ "navigation",
2008
+ "query",
2009
+ "high_freq"
2010
+ ];
2011
+ for (const effect of priorityOrder) {
2012
+ const entry = vocab.effects.get(effect);
2013
+ if (entry) {
2014
+ for (const keyword of normalizedKeywords) {
2015
+ if (entry.keywords.includes(keyword)) {
2016
+ return effect;
2017
+ }
2018
+ }
2019
+ }
2020
+ }
2021
+ for (const keyword of normalizedKeywords) {
2022
+ const override = vocab.typeOverrides.get(keyword);
2023
+ if (override) {
2024
+ return override;
2025
+ }
2026
+ }
2027
+ for (const keyword of normalizedKeywords) {
2028
+ const domainDefault = vocab.domainDefaults.get(keyword);
2029
+ if (domainDefault) {
2030
+ return domainDefault;
2031
+ }
2032
+ }
2033
+ return null;
2034
+ }
2035
+ function clearVocabularyCache() {
2036
+ cachedVocabulary = null;
2037
+ cachedPath2 = null;
2038
+ }
2039
+ function isVocabularyCached() {
2040
+ return cachedVocabulary !== null;
2041
+ }
2042
+
2043
+ // src/warden/grounding-gate.ts
2044
+ var ZONE_TO_EFFECT = {
2045
+ critical: "financial",
2046
+ elevated: "destructive",
2047
+ standard: "standard",
2048
+ local: "local"
2049
+ };
2050
+ var EFFECT_TO_ZONE = {
2051
+ financial: "critical",
2052
+ destructive: "elevated",
2053
+ soft_delete: "standard",
2054
+ standard: "standard",
2055
+ navigation: "local",
2056
+ query: "local",
2057
+ local: "local",
2058
+ high_freq: "local"
2059
+ };
2060
+ function parseZone(value) {
2061
+ const normalized = value.toLowerCase().trim();
2062
+ if (normalized === "critical")
2063
+ return "critical";
2064
+ if (normalized === "elevated")
2065
+ return "elevated";
2066
+ if (normalized === "standard")
2067
+ return "standard";
2068
+ if (normalized === "local")
2069
+ return "local";
2070
+ return null;
2071
+ }
2072
+ function parseSyncStrategy2(value) {
2073
+ const normalized = value.toLowerCase().trim();
2074
+ if (normalized.includes("pessimistic"))
2075
+ return "pessimistic";
2076
+ if (normalized.includes("optimistic"))
2077
+ return "optimistic";
2078
+ if (normalized.includes("immediate"))
2079
+ return "immediate";
2080
+ return void 0;
2081
+ }
2082
+ function parseTiming2(value) {
2083
+ const match = value.match(/(\d+)\s*ms/i);
2084
+ if (match && match[1]) {
2085
+ return parseInt(match[1], 10);
2086
+ }
2087
+ return void 0;
2088
+ }
2089
+ function parseConfirmation2(value) {
2090
+ const normalized = value.toLowerCase().trim();
2091
+ if (normalized.includes("required") || normalized === "yes")
2092
+ return "required";
2093
+ if (normalized.includes("toast") || normalized.includes("undo"))
2094
+ return "toast_undo";
2095
+ if (normalized.includes("none") || normalized === "no")
2096
+ return "none";
2097
+ return void 0;
2098
+ }
2099
+ function extractKeywords(text) {
2100
+ const cleanedText = text.replace(/Zone:\s*\w+/gi, "").replace(/Effect:\s*[\w\s]+/gi, "").replace(/Sync:\s*\w+/gi, "").replace(/Confirmation:\s*[\w\s+]+/gi, "");
2101
+ const keywordPatterns = [
2102
+ // Financial
2103
+ "claim",
2104
+ "deposit",
2105
+ "withdraw",
2106
+ "transfer",
2107
+ "swap",
2108
+ "send",
2109
+ "pay",
2110
+ "purchase",
2111
+ "mint",
2112
+ "burn",
2113
+ "stake",
2114
+ "unstake",
2115
+ "bridge",
2116
+ "approve",
2117
+ "redeem",
2118
+ "harvest",
2119
+ // Destructive
2120
+ "delete",
2121
+ "remove",
2122
+ "destroy",
2123
+ "revoke",
2124
+ "terminate",
2125
+ "purge",
2126
+ "erase",
2127
+ "wipe",
2128
+ // Soft delete
2129
+ "archive",
2130
+ "hide",
2131
+ "trash",
2132
+ "dismiss",
2133
+ "snooze",
2134
+ "mute",
2135
+ // Standard
2136
+ "save",
2137
+ "update",
2138
+ "edit",
2139
+ "create",
2140
+ "add",
2141
+ "like",
2142
+ "follow",
2143
+ "bookmark",
2144
+ // Local
2145
+ "toggle",
2146
+ "switch",
2147
+ "expand",
2148
+ "collapse",
2149
+ "select",
2150
+ "focus",
2151
+ // Navigation
2152
+ "navigate",
2153
+ "go",
2154
+ "back",
2155
+ "forward",
2156
+ "link",
2157
+ "route",
2158
+ // Query
2159
+ "fetch",
2160
+ "load",
2161
+ "get",
2162
+ "list",
2163
+ "search",
2164
+ "find",
2165
+ // Domain/type hints
2166
+ "wallet",
2167
+ "token",
2168
+ "nft",
2169
+ "contract",
2170
+ "chain",
2171
+ "gas",
2172
+ "currency",
2173
+ "money",
2174
+ "amount",
2175
+ "balance",
2176
+ "price",
2177
+ "fee",
2178
+ // Effect type names (for inline detection in prose)
2179
+ "financial",
2180
+ "destructive"
2181
+ ];
2182
+ const words = cleanedText.toLowerCase().split(/[\s,.:;!?()\[\]{}]+/);
2183
+ const foundKeywords = [];
2184
+ for (const word of words) {
2185
+ if (keywordPatterns.includes(word)) {
2186
+ foundKeywords.push(word);
2187
+ }
2188
+ }
2189
+ return [...new Set(foundKeywords)];
2190
+ }
2191
+ function parseGroundingStatement(text) {
2192
+ const statement = {
2193
+ component: "",
2194
+ citedZone: null,
2195
+ detectedKeywords: [],
2196
+ inferredEffect: null,
2197
+ claimedPhysics: {},
2198
+ raw: text
2199
+ };
2200
+ const componentMatch = text.match(/(?:Component|Button|Modal|Form|Dialog):\s*["']?([^\s"'│|]+)/i);
2201
+ if (componentMatch && componentMatch[1]) {
2202
+ statement.component = componentMatch[1].trim();
2203
+ } else {
2204
+ const buttonMatch = text.match(/["']?(\w+(?:Button|Modal|Form|Dialog|Card|Input))["']?/);
2205
+ if (buttonMatch && buttonMatch[1]) {
2206
+ statement.component = buttonMatch[1];
2207
+ }
2208
+ }
2209
+ const zoneMatch = text.match(/Zone:\s*(\w+)/i);
2210
+ if (zoneMatch && zoneMatch[1]) {
2211
+ statement.citedZone = parseZone(zoneMatch[1]);
2212
+ }
2213
+ const effectMatch = text.match(/Effect:\s*(\w+(?:\s+\w+)?)/i);
2214
+ if (effectMatch && effectMatch[1]) {
2215
+ const effectStr = effectMatch[1].toLowerCase();
2216
+ if (effectStr.includes("financial"))
2217
+ statement.inferredEffect = "financial";
2218
+ else if (effectStr.includes("destructive"))
2219
+ statement.inferredEffect = "destructive";
2220
+ else if (effectStr.includes("soft"))
2221
+ statement.inferredEffect = "soft_delete";
2222
+ else if (effectStr.includes("standard"))
2223
+ statement.inferredEffect = "standard";
2224
+ else if (effectStr.includes("local"))
2225
+ statement.inferredEffect = "local";
2226
+ else if (effectStr.includes("navigation"))
2227
+ statement.inferredEffect = "navigation";
2228
+ else if (effectStr.includes("query"))
2229
+ statement.inferredEffect = "query";
2230
+ }
2231
+ const syncMatch = text.match(/Sync:\s*(\w+)/i);
2232
+ if (syncMatch && syncMatch[1]) {
2233
+ const parsed = parseSyncStrategy2(syncMatch[1]);
2234
+ if (parsed)
2235
+ statement.claimedPhysics.sync = parsed;
2236
+ } else {
2237
+ if (text.toLowerCase().includes("pessimistic")) {
2238
+ statement.claimedPhysics.sync = "pessimistic";
2239
+ } else if (text.toLowerCase().includes("optimistic")) {
2240
+ statement.claimedPhysics.sync = "optimistic";
2241
+ } else if (text.toLowerCase().includes("immediate")) {
2242
+ statement.claimedPhysics.sync = "immediate";
2243
+ }
2244
+ }
2245
+ const timingMatch = text.match(/Timing:\s*(\d+\s*ms)/i);
2246
+ if (timingMatch && timingMatch[1]) {
2247
+ const parsed = parseTiming2(timingMatch[1]);
2248
+ if (parsed !== void 0)
2249
+ statement.claimedPhysics.timing = parsed;
2250
+ } else {
2251
+ const inlineTiming = text.match(/(\d+)\s*ms/);
2252
+ if (inlineTiming && inlineTiming[1]) {
2253
+ statement.claimedPhysics.timing = parseInt(inlineTiming[1], 10);
2254
+ }
2255
+ }
2256
+ const confirmMatch = text.match(/Confirm(?:ation)?:\s*(\w+(?:\s*\+\s*\w+)?)/i);
2257
+ if (confirmMatch && confirmMatch[1]) {
2258
+ const parsed = parseConfirmation2(confirmMatch[1]);
2259
+ if (parsed)
2260
+ statement.claimedPhysics.confirmation = parsed;
2261
+ } else {
2262
+ if (text.toLowerCase().includes("confirmation required")) {
2263
+ statement.claimedPhysics.confirmation = "required";
2264
+ } else if (text.toLowerCase().includes("toast") && text.toLowerCase().includes("undo")) {
2265
+ statement.claimedPhysics.confirmation = "toast_undo";
2266
+ }
2267
+ }
2268
+ statement.detectedKeywords = extractKeywords(text);
2269
+ return statement;
2270
+ }
2271
+ function determineRequiredZone(keywords, effect, vocabulary) {
2272
+ if (effect) {
2273
+ return EFFECT_TO_ZONE[effect];
2274
+ }
2275
+ for (const keyword of keywords) {
2276
+ const override = vocabulary.typeOverrides.get(keyword.toLowerCase());
2277
+ if (override) {
2278
+ return EFFECT_TO_ZONE[override];
2279
+ }
2280
+ }
2281
+ const financialKeywords = vocabulary.effects.get("financial")?.keywords ?? [];
2282
+ for (const keyword of keywords) {
2283
+ if (financialKeywords.includes(keyword.toLowerCase())) {
2284
+ return "critical";
2285
+ }
2286
+ }
2287
+ const destructiveKeywords = vocabulary.effects.get("destructive")?.keywords ?? [];
2288
+ for (const keyword of keywords) {
2289
+ if (destructiveKeywords.includes(keyword.toLowerCase())) {
2290
+ return "elevated";
2291
+ }
2292
+ }
2293
+ return "standard";
2294
+ }
2295
+ function checkRelevance(statement, vocabulary) {
2296
+ const { detectedKeywords, component } = statement;
2297
+ if (detectedKeywords.length === 0) {
2298
+ const componentLower = component.toLowerCase();
2299
+ const allKeywords = [];
2300
+ for (const entry of vocabulary.effects.values()) {
2301
+ allKeywords.push(...entry.keywords);
2302
+ }
2303
+ const hasRelevantComponent = allKeywords.some((k) => componentLower.includes(k));
2304
+ if (!hasRelevantComponent) {
2305
+ return {
2306
+ passed: false,
2307
+ reason: "No relevant keywords detected in statement or component name"
2308
+ };
2309
+ }
2310
+ }
2311
+ return {
2312
+ passed: true,
2313
+ reason: `Keywords detected: ${detectedKeywords.join(", ") || "from component name"}`
2314
+ };
2315
+ }
2316
+ function checkHierarchy(statement, requiredZone) {
2317
+ const { citedZone } = statement;
2318
+ if (!citedZone) {
2319
+ return {
2320
+ passed: false,
2321
+ reason: "No zone cited in statement"
2322
+ };
2323
+ }
2324
+ const requiredIndex = ZONE_HIERARCHY.indexOf(requiredZone);
2325
+ const citedIndex = ZONE_HIERARCHY.indexOf(citedZone);
2326
+ if (citedIndex > requiredIndex) {
2327
+ return {
2328
+ passed: false,
2329
+ reason: `Zone "${citedZone}" is less restrictive than required "${requiredZone}"`
2330
+ };
2331
+ }
2332
+ if (citedIndex < requiredIndex) {
2333
+ return {
2334
+ passed: true,
2335
+ reason: `Zone "${citedZone}" is more restrictive than required "${requiredZone}" (OK)`
2336
+ };
2337
+ }
2338
+ return {
2339
+ passed: true,
2340
+ reason: `Zone "${citedZone}" matches required zone`
2341
+ };
2342
+ }
2343
+ async function checkRules(statement, requiredZone, physics) {
2344
+ const { claimedPhysics } = statement;
2345
+ const requiredEffect = ZONE_TO_EFFECT[requiredZone];
2346
+ const rule = physics.get(requiredEffect);
2347
+ if (!rule) {
2348
+ return {
2349
+ passed: false,
2350
+ reason: `No physics rule found for effect "${requiredEffect}"`
2351
+ };
2352
+ }
2353
+ const violations = [];
2354
+ if (claimedPhysics.sync && claimedPhysics.sync !== rule.sync) {
2355
+ if (claimedPhysics.sync !== "pessimistic") {
2356
+ violations.push(`Sync: claimed "${claimedPhysics.sync}", required "${rule.sync}"`);
2357
+ }
2358
+ }
2359
+ if (claimedPhysics.timing !== void 0) {
2360
+ const timingDiff = Math.abs(claimedPhysics.timing - rule.timing);
2361
+ if (timingDiff > 100 && claimedPhysics.timing < rule.timing) {
2362
+ violations.push(
2363
+ `Timing: claimed ${claimedPhysics.timing}ms, required minimum ${rule.timing}ms`
2364
+ );
2365
+ }
2366
+ }
2367
+ if (claimedPhysics.confirmation) {
2368
+ if (rule.confirmation === "required" && claimedPhysics.confirmation !== "required") {
2369
+ violations.push(
2370
+ `Confirmation: claimed "${claimedPhysics.confirmation}", required "${rule.confirmation}"`
2371
+ );
2372
+ }
2373
+ }
2374
+ if (violations.length > 0) {
2375
+ return {
2376
+ passed: false,
2377
+ reason: violations.join("; ")
2378
+ };
2379
+ }
2380
+ return {
2381
+ passed: true,
2382
+ reason: "Physics rules validated"
2383
+ };
2384
+ }
2385
+ async function validateGrounding(input, options) {
2386
+ const physics = await loadPhysics(options?.physicsPath);
2387
+ const vocabulary = await loadVocabulary(options?.vocabularyPath);
2388
+ const statement = typeof input === "string" ? parseGroundingStatement(input) : input;
2389
+ if (!statement.inferredEffect && statement.detectedKeywords.length > 0) {
2390
+ statement.inferredEffect = await resolveEffectFromKeywords(
2391
+ statement.detectedKeywords,
2392
+ vocabulary
2393
+ );
2394
+ }
2395
+ const requiredZone = determineRequiredZone(
2396
+ statement.detectedKeywords,
2397
+ statement.inferredEffect,
2398
+ vocabulary
2399
+ );
2400
+ const relevanceCheck = checkRelevance(statement, vocabulary);
2401
+ const hierarchyCheck = checkHierarchy(statement, requiredZone);
2402
+ const rulesCheck = await checkRules(statement, requiredZone, physics);
2403
+ let status = "VALID";
2404
+ let correction;
2405
+ if (!relevanceCheck.passed) {
2406
+ status = "DRIFT";
2407
+ correction = "Statement lacks relevant keywords for effect detection.";
2408
+ } else if (!hierarchyCheck.passed) {
2409
+ status = "DECEPTIVE";
2410
+ correction = `Zone mismatch: cited "${statement.citedZone}", required "${requiredZone}".`;
2411
+ } else if (!rulesCheck.passed) {
2412
+ status = "DRIFT";
2413
+ correction = `Physics violation: ${rulesCheck.reason}`;
2414
+ }
2415
+ return {
2416
+ status,
2417
+ checks: {
2418
+ relevance: relevanceCheck,
2419
+ hierarchy: hierarchyCheck,
2420
+ rules: rulesCheck
2421
+ },
2422
+ requiredZone,
2423
+ citedZone: statement.citedZone,
2424
+ ...correction !== void 0 && { correction }
2425
+ };
2426
+ }
2427
+ async function isGroundingValid(input, options) {
2428
+ const result = await validateGrounding(input, options);
2429
+ return result.status === "VALID";
2430
+ }
2431
+ function getExitCode(result) {
2432
+ switch (result.status) {
2433
+ case "VALID":
2434
+ return 0;
2435
+ case "DRIFT":
2436
+ return 1;
2437
+ case "DECEPTIVE":
2438
+ return 2;
2439
+ default:
2440
+ return 3;
2441
+ }
2442
+ }
2443
+
2444
+ // src/index.ts
2445
+ var VERSION = "4.3.1";
2446
+
2447
+ export { CheckpointManager, ExitCode, ForkManager, ForkRegistrySchema, ForkSchema, RpcError, RpcTimeoutError, SessionManager, SnapshotManager, TaskGraph, VERSION, ZONE_HIERARCHY, clearPhysicsCache, clearVocabularyCache, generateTaskId, getCheckpointManager, getDefaultPhysics, getDefaultVocabulary, getExitCode, getForkManager, getPhysicsRule, getSessionManager, getSnapshotManager, isGroundingValid, isPhysicsCached, isRpcReady, isVocabularyCached, loadPhysics, loadVocabulary, parseGroundingStatement, resetCheckpointManager, resetForkManager, resetSessionManager, resetSnapshotManager, resetTaskCounter, resolveEffectFromKeywords, rpcCall, validateGrounding, waitForRpc };
2448
+ //# sourceMappingURL=out.js.map
2449
+ //# sourceMappingURL=index.js.map