@reactive-skills/runtime 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Reactive Skills Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @reactive-skills/runtime
2
+
3
+ Reactive Skills Architecture (RSA) core runtime - FSM engine, event store, guard evaluator, projection engine, and MCP server.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @reactive-skills/runtime
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { FSMEngine, EventStore, LegacySkillAdapter, ProjectionEngine } from '@reactive-skills/runtime';
15
+ ```
16
+
17
+ ## Core Modules
18
+
19
+ - `FSMEngine` - Hierarchical State Machine loader, state path resolver, and signal-driven transition engine
20
+ - `EventStore` - Immutable append-only event ledger (JSONL + SQLite)
21
+ - `GuardEvaluator` - Sandbox evaluator for transition guard expressions
22
+ - `ProjectionEngine` - Handlebars deliverable generator for read-model projections
23
+ - `LegacySkillAdapter` - Backward compatibility wrapper and converter for SKILL.md
24
+ - `McpServer` - Stdio Model Context Protocol (MCP) server integration
25
+
26
+ ## Skill Manifest Schema
27
+
28
+ Reactive skills use `skill.yaml` with `schema_version: "2.0.0"`:
29
+
30
+ ```yaml
31
+ schema_version: "2.0.0"
32
+ name: "my-skill"
33
+ description: "Skill description"
34
+ initial_state: "START"
35
+
36
+ context_keys:
37
+ - "project_type"
38
+ - "mission"
39
+
40
+ states:
41
+ START:
42
+ description: "Initial state"
43
+ prompt_template: "states/start.md"
44
+ on_enter:
45
+ - set_context:
46
+ active_phase: "start"
47
+ transitions:
48
+ DONE:
49
+ target: "DONE"
50
+ guard: "event.payload.completed === true"
51
+ ```
52
+
53
+ ## Integration Modes
54
+
55
+ The runtime has no dependency on any specific integration mode:
56
+
57
+ 1. **Direct API** - Import and use `FSMEngine` directly in your application
58
+ 2. **MCP stdio** - Use the `McpServer` class to expose tools over stdio
59
+ 3. **Legacy hooks (removed)** - `runtime-hooks.ts` was removed in v2.0. Use `on_enter`/`on_exit` lifecycle hooks and explicit `engine.handleSignal()` calls instead
60
+
61
+ ## Event Store
62
+
63
+ Dual-mode event sourcing:
64
+
65
+ 1. **JSONL** (`.reactive/skills/<skill>/events.jsonl`) - Human-readable append-only log
66
+ 2. **SQLite** (`.reactive/skills/<skill>/events.db`) - Indexed relational database
67
+
68
+ ## License
69
+
70
+ MIT
71
+
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ const args = process.argv.slice(2);
5
+ const command = args[0];
6
+ function printHelp() {
7
+ console.log(`
8
+ reactive-skills-dev: Developer CLI for Reactive Skills Architecture
9
+
10
+ Usage:
11
+ reactive-skills-dev init <name> Scaffold new reactive skill in skills/<name>/
12
+ reactive-skills-dev upgrade <path> Convert legacy SKILL.md to reactive format
13
+ reactive-skills-dev inspect <skill> Print statechart, transitions, and guards
14
+ reactive-skills-dev migrate [path] Retroactively migrate project to latest schema
15
+ reactive-skills-dev sync Sync skills to all agent config directories
16
+ `);
17
+ }
18
+ async function main() {
19
+ if (!command || command === 'help' || command === '--help') {
20
+ printHelp();
21
+ return;
22
+ }
23
+ // INIT
24
+ if (command === 'init') {
25
+ const name = args[1];
26
+ if (!name) {
27
+ console.error(JSON.stringify({ error: 'Skill name required. Usage: reactive-skills-dev init <name>' }));
28
+ process.exit(1);
29
+ }
30
+ const skillDir = path.resolve(process.cwd(), 'skills', name);
31
+ fs.mkdirSync(path.join(skillDir, 'states'), { recursive: true });
32
+ fs.mkdirSync(path.join(skillDir, 'guards'), { recursive: true });
33
+ fs.mkdirSync(path.join(skillDir, 'templates'), { recursive: true });
34
+ const manifest = `name: ${name}\nversion: "1.0.0"\ndescription: "${name} reactive skill"\ninitial_state: INITIAL\nstates:\n INITIAL:\n description: "Starting state"\n transitions: {}\n`;
35
+ fs.writeFileSync(path.join(skillDir, 'skill.yaml'), manifest, 'utf8');
36
+ fs.writeFileSync(path.join(skillDir, 'states', 'INITIAL.md'), `# State: INITIAL\n\nDescribe what the agent should do in this state.\n`, 'utf8');
37
+ console.log(`Scaffolded reactive skill '${name}' at skills/${name}/`);
38
+ return;
39
+ }
40
+ // UPGRADE
41
+ if (command === 'upgrade') {
42
+ const skillPath = args[1];
43
+ if (!skillPath) {
44
+ console.error(JSON.stringify({ error: 'Path required. Usage: reactive-skills-dev upgrade <path>' }));
45
+ process.exit(1);
46
+ }
47
+ const { LegacySkillAdapter } = await import('../core/legacy-adapter.js');
48
+ const targetDir = path.resolve(process.cwd(), skillPath);
49
+ const result = LegacySkillAdapter.upgradeToModular(targetDir);
50
+ console.log(`Upgraded legacy SKILL.md at ${targetDir}`);
51
+ console.log(JSON.stringify(result, null, 2));
52
+ return;
53
+ }
54
+ // INSPECT
55
+ if (command === 'inspect') {
56
+ const skillPath = args[1] && !args[1].startsWith('--') ? args[1] : 'skills/synthesis';
57
+ const targetDir = path.resolve(process.cwd(), skillPath);
58
+ const { FSMEngine } = await import('../core/fsm-engine.js');
59
+ const engine = new FSMEngine({ skillDir: targetDir });
60
+ const manifest = engine.getManifest();
61
+ const isJson = args.includes('--json');
62
+ if (isJson) {
63
+ console.log(JSON.stringify(manifest, null, 2));
64
+ }
65
+ else {
66
+ console.log(`\nReactive Skill: ${manifest.name} (v${manifest.version || '1.0.0'})`);
67
+ console.log(`Description: ${manifest.description}`);
68
+ console.log(`Initial State: ${manifest.initial_state}`);
69
+ console.log(`\nStates & Transitions:`);
70
+ for (const [sName, sDef] of Object.entries(manifest.states)) {
71
+ console.log(` [${sName}]`);
72
+ if (sDef.transitions) {
73
+ for (const [sig, trans] of Object.entries(sDef.transitions)) {
74
+ const t = typeof trans === 'string' ? { target: trans } : trans;
75
+ console.log(` on '${sig}' -> ${t.target}${t.guard ? ` [guard: ${t.guard}]` : ''}`);
76
+ }
77
+ }
78
+ }
79
+ }
80
+ return;
81
+ }
82
+ // MIGRATE
83
+ if (command === 'migrate') {
84
+ const targetPath = args[1] || process.cwd();
85
+ const { ProjectMigrator } = await import('../core/migration.js');
86
+ const result = ProjectMigrator.migrate(targetPath);
87
+ const isJson = args.includes('--json');
88
+ if (isJson) {
89
+ console.log(JSON.stringify(result, null, 2));
90
+ }
91
+ else {
92
+ console.log(`\nProject Migrated to ${result.schemaVersion}:`);
93
+ console.log(`Directory: ${result.projectDir}`);
94
+ for (const note of result.notes) {
95
+ console.log(` - ${note}`);
96
+ }
97
+ }
98
+ return;
99
+ }
100
+ // SYNC (delegated to canonical sync engine)
101
+ if (command === 'sync') {
102
+ const { syncEngineCommand } = await import('../sync/cli.js');
103
+ const output = await syncEngineCommand(args.slice(1));
104
+ console.log(output);
105
+ return;
106
+ }
107
+ console.error(JSON.stringify({ error: `Unknown command: ${command}` }));
108
+ printHelp();
109
+ process.exit(1);
110
+ }
111
+ main().catch(err => {
112
+ console.error(JSON.stringify({ error: err.message }));
113
+ process.exit(1);
114
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { FSMEngine } from '../core/fsm-engine.js';
6
+ import { EventStore } from '../core/event-store.js';
7
+ const args = process.argv.slice(2);
8
+ const command = args[0];
9
+ const isJson = args.includes('--json');
10
+ function printHelp() {
11
+ console.log(`
12
+ ⚡ reactive-skills: Agent Experience Interface for Reactive Skills
13
+
14
+ Usage:
15
+ reactive-skills mcp Run the stdio Model Context Protocol (MCP) server
16
+ reactive-skills install <skill> Install skill globally and sync
17
+ reactive-skills migrate [path] Retroactively migrate project to latest schema
18
+ reactive-skills state [skill] Get active state slice and prompt
19
+ reactive-skills emit <signal> Emit signal and step state machine
20
+ reactive-skills query "<sql>" Query SQLite event store
21
+ reactive-skills inspect <skill> Inspect statechart and transitions
22
+ reactive-skills events [limit] Tail event store ledger
23
+ reactive-skills sync Sync skills across agent platforms
24
+ reactive-skills init <name> Scaffold new reactive skill
25
+ reactive-skills upgrade <path> Upgrade legacy SKILL.md
26
+ `);
27
+ }
28
+ async function main() {
29
+ if (!command || command === 'help' || command === '--help') {
30
+ printHelp();
31
+ return;
32
+ }
33
+ // 0. RUN MCP SERVER
34
+ if (command === 'mcp') {
35
+ const { runMcpServer } = await import('../mcp/server.js');
36
+ await runMcpServer();
37
+ return;
38
+ }
39
+ // 0.5 MIGRATE PROJECT RETROACTIVELY
40
+ if (command === 'migrate') {
41
+ const targetPath = args[1] || process.cwd();
42
+ const { ProjectMigrator } = await import('../core/migration.js');
43
+ const result = ProjectMigrator.migrate(targetPath);
44
+ if (isJson) {
45
+ console.log(JSON.stringify(result, null, 2));
46
+ }
47
+ else {
48
+ console.log(`\n🔄 Project Migrated to ${result.schemaVersion}:`);
49
+ console.log(`📁 Directory: ${result.projectDir}`);
50
+ for (const note of result.notes) {
51
+ console.log(` • ${note}`);
52
+ }
53
+ }
54
+ return;
55
+ }
56
+ // 1. INSTALL SKILL GLOBALLY (delegated to sync engine for the sync step)
57
+ if (command === 'install') {
58
+ const skillName = args[1];
59
+ if (!skillName) {
60
+ console.error(JSON.stringify({ error: 'Skill name required. Usage: reactive-skills install <skill-name>' }));
61
+ process.exit(1);
62
+ }
63
+ const homeDir = os.homedir();
64
+ const sourceCandidates = [
65
+ path.resolve(process.cwd(), 'skills', skillName),
66
+ path.resolve(process.cwd(), skillName),
67
+ path.resolve(import.meta.dirname, '..', '..', 'skills', skillName),
68
+ ];
69
+ const sourceDir = sourceCandidates.find(d => fs.existsSync(d));
70
+ if (!sourceDir) {
71
+ console.error(JSON.stringify({ error: `Skill '${skillName}' not found in local workspace.` }));
72
+ process.exit(1);
73
+ }
74
+ const destAgent = path.join(homeDir, '.agents', 'skills', skillName);
75
+ fs.mkdirSync(path.dirname(destAgent), { recursive: true });
76
+ fs.mkdirSync(destAgent, { recursive: true });
77
+ // Copy skill using the canonical engine's copySkillDir (via runSync with single target)
78
+ const { runSync } = await import('../sync/engine.js');
79
+ const report = runSync({
80
+ sourceDir: path.dirname(sourceDir),
81
+ targetDirs: [path.join(homeDir, '.agents', 'skills')],
82
+ targetSkill: skillName,
83
+ dryRun: false,
84
+ backup: false,
85
+ });
86
+ if (report.errors.length > 0) {
87
+ console.error(JSON.stringify({ error: report.errors.join('; ') }));
88
+ process.exit(1);
89
+ }
90
+ if (isJson) {
91
+ console.log(JSON.stringify({ ok: true, skill: skillName, installedTo: [destAgent], synced: true }));
92
+ }
93
+ else {
94
+ console.log(`✅ Skill '${skillName}' installed to ${destAgent} and synced globally.`);
95
+ }
96
+ return;
97
+ }
98
+ // 2. GET ACTIVE STATE SLICE
99
+ if (command === 'state') {
100
+ const skillPath = args[1] && !args[1].startsWith('--') ? args[1] : path.resolve(process.cwd(), 'skills', 'synthesis');
101
+ const targetDir = path.resolve(process.cwd(), skillPath);
102
+ if (!fs.existsSync(targetDir)) {
103
+ console.error(JSON.stringify({ error: `Skill path not found: ${targetDir}` }));
104
+ process.exit(1);
105
+ }
106
+ const engine = new FSMEngine({ skillDir: targetDir });
107
+ if (engine.isStrictExecution()) {
108
+ engine.recordTurnStart();
109
+ }
110
+ const slice = engine.generatePromptSlice();
111
+ if (isJson) {
112
+ console.log(JSON.stringify(slice, null, 2));
113
+ }
114
+ else {
115
+ console.log(slice.formattedXml);
116
+ }
117
+ return;
118
+ }
119
+ // 3. EMIT SIGNAL
120
+ if (command === 'emit') {
121
+ const signalName = args[1];
122
+ if (!signalName) {
123
+ console.error(JSON.stringify({ error: 'Signal name required. Usage: reactive-skills emit <SIGNAL> [--payload JSON]' }));
124
+ process.exit(1);
125
+ }
126
+ let payload = {};
127
+ const payloadIdx = args.indexOf('--payload');
128
+ if (payloadIdx !== -1 && args[payloadIdx + 1]) {
129
+ try {
130
+ payload = JSON.parse(args[payloadIdx + 1]);
131
+ }
132
+ catch {
133
+ payload = { raw: args[payloadIdx + 1] };
134
+ }
135
+ }
136
+ const skillPath = path.resolve(process.cwd(), 'skills', 'synthesis');
137
+ const store = new EventStore({ enableSqlite: true, skillId: 'synthesis' });
138
+ const engine = new FSMEngine({ skillDir: skillPath, eventStore: store });
139
+ const result = await engine.handleSignal(signalName, payload);
140
+ console.log(JSON.stringify(result, null, 2));
141
+ return;
142
+ }
143
+ // 4. SQL QUERY ON EVENTS.DB
144
+ if (command === 'query') {
145
+ const sql = args[1];
146
+ if (!sql) {
147
+ console.error(JSON.stringify({ error: 'SQL query required. Usage: reactive-skills query "SELECT * FROM events"' }));
148
+ process.exit(1);
149
+ }
150
+ const store = new EventStore({ enableSqlite: true });
151
+ const driver = store.getSqliteDriver();
152
+ if (!driver) {
153
+ console.error(JSON.stringify({ error: 'SQLite driver not enabled in active event store.' }));
154
+ process.exit(1);
155
+ }
156
+ const rows = driver.querySql(sql);
157
+ console.log(JSON.stringify(rows, null, 2));
158
+ return;
159
+ }
160
+ // 5. INSPECT SKILL
161
+ if (command === 'inspect') {
162
+ const skillPath = args[1] && !args[1].startsWith('--') ? args[1] : 'skills/synthesis';
163
+ const targetDir = path.resolve(process.cwd(), skillPath);
164
+ const engine = new FSMEngine({ skillDir: targetDir });
165
+ const manifest = engine.getManifest();
166
+ if (isJson) {
167
+ console.log(JSON.stringify(manifest, null, 2));
168
+ }
169
+ else {
170
+ console.log(`\n⚡ Reactive Skill: ${manifest.name} (v${manifest.version || '1.0.0'})`);
171
+ console.log(`📖 Description: ${manifest.description}`);
172
+ console.log(`🏁 Initial State: ${manifest.initial_state}`);
173
+ console.log(`\n📋 States & Transitions:`);
174
+ for (const [sName, sDef] of Object.entries(manifest.states)) {
175
+ console.log(` [State: ${sName}]`);
176
+ if (sDef.transitions) {
177
+ for (const [sig, trans] of Object.entries(sDef.transitions)) {
178
+ const t = typeof trans === 'string' ? { target: trans } : trans;
179
+ console.log(` • on '${sig}' -> ${t.target}${t.guard ? ` [guard: ${t.guard}]` : ''}`);
180
+ }
181
+ }
182
+ }
183
+ }
184
+ return;
185
+ }
186
+ // 6. SYNC SKILLS (delegated to canonical sync engine)
187
+ if (command === 'sync') {
188
+ const { syncEngineCommand } = await import('../sync/cli.js');
189
+ const output = await syncEngineCommand(args.slice(1));
190
+ console.log(output);
191
+ return;
192
+ }
193
+ // 7. EVENTS
194
+ if (command === 'events') {
195
+ const limit = parseInt(args[1] || '20', 10);
196
+ const store = new EventStore();
197
+ const events = store.getAll();
198
+ const slice = events.slice(-limit);
199
+ if (isJson) {
200
+ console.log(JSON.stringify(slice, null, 2));
201
+ }
202
+ else {
203
+ console.log(`\n📜 Event Store Ledger (.reactive/events.jsonl) - Showing last ${slice.length} events:`);
204
+ for (const e of slice) {
205
+ console.log(`[#${e.seq}] ${e.timestamp} | ${e.type.padEnd(18)} | State: ${(e.state || '-').padEnd(14)} | ${JSON.stringify(e.payload)}`);
206
+ }
207
+ }
208
+ return;
209
+ }
210
+ printHelp();
211
+ }
212
+ main().catch(err => {
213
+ console.error('Error running reactive-skills:', err);
214
+ process.exit(1);
215
+ });
@@ -0,0 +1,182 @@
1
+ import { EventContext, SignalEvent } from './types.js';
2
+ export declare const EVENT_STORE_SCHEMA_VERSION = 2;
3
+ export type EventListener = (event: SignalEvent) => void;
4
+ export declare function createSortableId(): string;
5
+ export interface EventStoreOptions {
6
+ storagePath?: string;
7
+ sqlitePath?: string;
8
+ workspaceDir?: string;
9
+ skillId?: string;
10
+ runId?: string;
11
+ run_id?: string;
12
+ correlationId?: string;
13
+ correlation_id?: string;
14
+ requestId?: string;
15
+ request_id?: string;
16
+ traceParent?: string;
17
+ trace_parent?: string;
18
+ parentRunId?: string;
19
+ parent_run_id?: string;
20
+ schemaVersion?: string;
21
+ schema_version?: string;
22
+ inMemory?: boolean;
23
+ enableSqlite?: boolean;
24
+ maxInMemoryEvents?: number;
25
+ maxJsonlBytes?: number;
26
+ }
27
+ export interface EventQueryOptions {
28
+ type?: string;
29
+ state?: string;
30
+ sinceSeq?: number;
31
+ limit?: number;
32
+ }
33
+ /**
34
+ * SQLite Storage Driver for EventStore
35
+ * Provides ACID relational storage, indexing, and direct SQL querying
36
+ */
37
+ export declare class SQLiteStorageDriver {
38
+ private db;
39
+ constructor(dbPath: string);
40
+ private initTables;
41
+ private ensureSchemaVersion;
42
+ private runMigrations;
43
+ insertEvent(event: SignalEvent): void;
44
+ beginTransaction(): void;
45
+ commitTransaction(): void;
46
+ queryEvents(options?: EventQueryOptions): SignalEvent[];
47
+ getLatestSequence(): number;
48
+ nextSequence(): number;
49
+ getRecentEvents(limit: number): SignalEvent[];
50
+ querySql(sql: string, params?: any[]): any[];
51
+ saveSnapshot(seq: number, state: string, context: Record<string, any>): void;
52
+ getLatestSnapshot(): {
53
+ seq: number;
54
+ state: string;
55
+ context: Record<string, any>;
56
+ } | null;
57
+ saveProjectionWatermark(name: string, eventSeq: number, projectionVersion: string): void;
58
+ getProjectionWatermark(name: string): {
59
+ eventSeq: number;
60
+ projectionVersion: string;
61
+ } | null;
62
+ saveProjection(name: string, content: string): void;
63
+ getProjection(name: string): string | null;
64
+ close(): void;
65
+ static getSchemaVersion(dbPath: string): number;
66
+ private rowToEvent;
67
+ clear(): void;
68
+ /**
69
+ * Purge every table including seq_counter so a rebuilt store starts from a
70
+ * clean slate. Used by EventStore.rebuildFromJsonl().
71
+ */
72
+ clearAll(): void;
73
+ /**
74
+ * Replace the entire events table with the supplied ordered list.
75
+ * Preserves caller-supplied seq numbers (JSONL heritage). seq_counter is
76
+ * reset to the max seq so future appends continue from the correct point.
77
+ */
78
+ rebuildEvents(orderedEvents: SignalEvent[]): void;
79
+ }
80
+ /**
81
+ * Immutable Append-Only Event Store
82
+ * All signals, tool results, guard evaluations, and state transitions are recorded here.
83
+ */
84
+ export declare class EventStore {
85
+ private events;
86
+ private listeners;
87
+ private storagePath;
88
+ private sqliteDriver;
89
+ private sqlitePath;
90
+ private seqCounter;
91
+ private maxInMemoryEvents;
92
+ private eventContext;
93
+ private projectionWatermarks;
94
+ private latestSnapshot;
95
+ private maxJsonlBytes;
96
+ constructor(options?: EventStoreOptions);
97
+ private initializeStorage;
98
+ /**
99
+ * Scan all JSONL files for the authoritative max seq and event count.
100
+ * Returns { count: 0, maxSeq: 0 } when no JSONL is present.
101
+ */
102
+ private readJsonlStats;
103
+ /**
104
+ * Rebuild SQLite from JSONL. Reads every JSONL file, deduplicates by event
105
+ * id (keeping the highest seq on conflict), clears SQLite, and re-inserts
106
+ * in seq order preserving original seq numbers. seq_counter is reset to the
107
+ * max seq. In-memory state is left untouched; callers must refresh it.
108
+ */
109
+ rebuildFromJsonl(): number;
110
+ /**
111
+ * Read every event from every JSONL file (active + archived segments).
112
+ */
113
+ private readAllJsonlEvents;
114
+ /**
115
+ * Deduplicate events by id, keeping the entry with the highest seq.
116
+ */
117
+ private deduplicateById;
118
+ /**
119
+ * Backfill JSONL with events SQLite has that JSONL is missing. SQLite is
120
+ * authoritative; this keeps the audit log complete when a JSONL write
121
+ * failed mid-write. Best-effort: failures are logged, not thrown.
122
+ */
123
+ syncJsonlToSqlite(): number;
124
+ private getJsonlPaths;
125
+ private rotateJsonlIfNeeded;
126
+ /**
127
+ * Append a new event to the immutable log.
128
+ *
129
+ * SQLite is the authoritative store when enabled; JSONL is an append-only
130
+ * audit mirror that can optionally be checked into git. If JSONL mirroring
131
+ * fails the run stays live because SQLite already persisted the event.
132
+ */
133
+ append<T = Record<string, any>>(type: string, payload: T, metadata?: {
134
+ source?: string;
135
+ causationId?: string;
136
+ correlationId?: string;
137
+ requestId?: string;
138
+ traceParent?: string;
139
+ parentRunId?: string;
140
+ skillId?: string;
141
+ runId?: string;
142
+ state?: string;
143
+ }): SignalEvent<T>;
144
+ /**
145
+ * Subscribe to new events
146
+ */
147
+ subscribe(listener: EventListener): () => void;
148
+ /**
149
+ * Get all recorded events (delegates to SQLite if available, otherwise in-memory buffer)
150
+ */
151
+ getAll(): SignalEvent[];
152
+ getSince(seq: number): SignalEvent[];
153
+ getLatestSequence(): number;
154
+ getEventContext(): EventContext;
155
+ saveProjectionWatermark(name: string, eventSeq: number, projectionVersion: string): void;
156
+ getProjectionWatermark(name: string): {
157
+ eventSeq: number;
158
+ projectionVersion: string;
159
+ } | null;
160
+ saveSnapshot(seq: number, state: string, context: Record<string, any>): void;
161
+ getLatestSnapshot(): {
162
+ seq: number;
163
+ state: string;
164
+ context: Record<string, any>;
165
+ } | null;
166
+ /**
167
+ * Query recorded events (relational index in SQLite, or in-memory filter)
168
+ */
169
+ query(filter?: EventQueryOptions): SignalEvent[];
170
+ /**
171
+ * Close storage drivers and release file handles
172
+ */
173
+ close(): void;
174
+ /**
175
+ * Get SQLite driver instance if enabled
176
+ */
177
+ getSqliteDriver(): SQLiteStorageDriver | null;
178
+ /**
179
+ * Clear in-memory and file events (for test isolation)
180
+ */
181
+ clear(): void;
182
+ }