context-mode 0.9.21 → 1.0.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.
Files changed (102) hide show
  1. package/.claude-plugin/hooks/hooks.json +46 -4
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/.claude-plugin/plugin.json +4 -4
  4. package/README.md +377 -191
  5. package/build/adapters/claude-code/config.d.ts +8 -0
  6. package/build/adapters/claude-code/config.js +8 -0
  7. package/build/adapters/claude-code/hooks.d.ts +53 -0
  8. package/build/adapters/claude-code/hooks.js +88 -0
  9. package/build/adapters/claude-code/index.d.ts +50 -0
  10. package/build/adapters/claude-code/index.js +523 -0
  11. package/build/adapters/codex/config.d.ts +8 -0
  12. package/build/adapters/codex/config.js +8 -0
  13. package/build/adapters/codex/hooks.d.ts +21 -0
  14. package/build/adapters/codex/hooks.js +27 -0
  15. package/build/adapters/codex/index.d.ts +44 -0
  16. package/build/adapters/codex/index.js +223 -0
  17. package/build/adapters/detect.d.ts +26 -0
  18. package/build/adapters/detect.js +131 -0
  19. package/build/adapters/gemini-cli/config.d.ts +8 -0
  20. package/build/adapters/gemini-cli/config.js +8 -0
  21. package/build/adapters/gemini-cli/hooks.d.ts +44 -0
  22. package/build/adapters/gemini-cli/hooks.js +64 -0
  23. package/build/adapters/gemini-cli/index.d.ts +57 -0
  24. package/build/adapters/gemini-cli/index.js +468 -0
  25. package/build/adapters/opencode/config.d.ts +8 -0
  26. package/build/adapters/opencode/config.js +8 -0
  27. package/build/adapters/opencode/hooks.d.ts +38 -0
  28. package/build/adapters/opencode/hooks.js +50 -0
  29. package/build/adapters/opencode/index.d.ts +52 -0
  30. package/build/adapters/opencode/index.js +386 -0
  31. package/build/adapters/types.d.ts +218 -0
  32. package/build/adapters/types.js +13 -0
  33. package/build/adapters/vscode-copilot/config.d.ts +8 -0
  34. package/build/adapters/vscode-copilot/config.js +8 -0
  35. package/build/adapters/vscode-copilot/hooks.d.ts +49 -0
  36. package/build/adapters/vscode-copilot/hooks.js +76 -0
  37. package/build/adapters/vscode-copilot/index.d.ts +58 -0
  38. package/build/adapters/vscode-copilot/index.js +512 -0
  39. package/build/cli.d.ts +9 -6
  40. package/build/cli.js +133 -423
  41. package/build/db-base.d.ts +84 -0
  42. package/build/db-base.js +128 -0
  43. package/build/executor.d.ts +6 -7
  44. package/build/executor.js +111 -51
  45. package/build/opencode-plugin.d.ts +37 -0
  46. package/build/opencode-plugin.js +118 -0
  47. package/build/runtime.js +1 -1
  48. package/build/server.js +436 -117
  49. package/build/session/db.d.ts +110 -0
  50. package/build/session/db.js +285 -0
  51. package/build/session/extract.d.ts +51 -0
  52. package/build/session/extract.js +407 -0
  53. package/build/session/snapshot.d.ts +70 -0
  54. package/build/session/snapshot.js +309 -0
  55. package/build/store.d.ts +4 -22
  56. package/build/store.js +67 -55
  57. package/build/truncate.d.ts +59 -0
  58. package/build/truncate.js +157 -0
  59. package/build/types.d.ts +101 -0
  60. package/build/types.js +20 -0
  61. package/configs/claude-code/CLAUDE.md +62 -0
  62. package/configs/codex/AGENTS.md +58 -0
  63. package/configs/codex/config.toml +5 -0
  64. package/configs/gemini-cli/GEMINI.md +58 -0
  65. package/configs/gemini-cli/mcp.json +7 -0
  66. package/configs/gemini-cli/settings.json +49 -0
  67. package/configs/opencode/AGENTS.md +58 -0
  68. package/configs/opencode/opencode.json +10 -0
  69. package/configs/vscode-copilot/copilot-instructions.md +58 -0
  70. package/configs/vscode-copilot/hooks.json +16 -0
  71. package/configs/vscode-copilot/mcp.json +8 -0
  72. package/hooks/core/formatters.mjs +86 -0
  73. package/hooks/core/routing.mjs +262 -0
  74. package/hooks/core/stdin.mjs +19 -0
  75. package/hooks/formatters/claude-code.mjs +57 -0
  76. package/hooks/formatters/gemini-cli.mjs +55 -0
  77. package/hooks/formatters/vscode-copilot.mjs +55 -0
  78. package/hooks/gemini-cli/aftertool.mjs +58 -0
  79. package/hooks/gemini-cli/beforetool.mjs +25 -0
  80. package/hooks/gemini-cli/precompress.mjs +51 -0
  81. package/hooks/gemini-cli/sessionstart.mjs +117 -0
  82. package/hooks/hooks.json +46 -4
  83. package/hooks/posttooluse.mjs +53 -0
  84. package/hooks/precompact.mjs +55 -0
  85. package/hooks/pretooluse.mjs +23 -266
  86. package/hooks/routing-block.mjs +19 -6
  87. package/hooks/session-directive.mjs +353 -0
  88. package/hooks/session-helpers.mjs +112 -0
  89. package/hooks/sessionstart.mjs +123 -16
  90. package/hooks/userpromptsubmit.mjs +58 -0
  91. package/hooks/vscode-copilot/posttooluse.mjs +58 -0
  92. package/hooks/vscode-copilot/precompact.mjs +51 -0
  93. package/hooks/vscode-copilot/pretooluse.mjs +25 -0
  94. package/hooks/vscode-copilot/sessionstart.mjs +115 -0
  95. package/package.json +20 -17
  96. package/skills/context-mode/SKILL.md +49 -49
  97. package/skills/{doctor → ctx-doctor}/SKILL.md +3 -3
  98. package/skills/{stats → ctx-stats}/SKILL.md +3 -3
  99. package/skills/{upgrade → ctx-upgrade}/SKILL.md +3 -3
  100. package/start.mjs +47 -0
  101. package/hooks/pretooluse.sh +0 -147
  102. package/server.bundle.mjs +0 -341
@@ -0,0 +1,110 @@
1
+ /**
2
+ * SessionDB — Persistent per-project SQLite database for session events.
3
+ *
4
+ * Stores raw events captured by hooks during a Claude Code session,
5
+ * session metadata, and resume snapshots. Extends SQLiteBase from
6
+ * the shared package.
7
+ */
8
+ import { SQLiteBase } from "../db-base.js";
9
+ import type { SessionEvent } from "../types.js";
10
+ /** A stored event row from the session_events table. */
11
+ export interface StoredEvent {
12
+ id: number;
13
+ session_id: string;
14
+ type: string;
15
+ category: string;
16
+ priority: number;
17
+ data: string;
18
+ source_hook: string;
19
+ created_at: string;
20
+ data_hash: string;
21
+ }
22
+ /** Session metadata row from the session_meta table. */
23
+ export interface SessionMeta {
24
+ session_id: string;
25
+ project_dir: string;
26
+ started_at: string;
27
+ last_event_at: string | null;
28
+ event_count: number;
29
+ compact_count: number;
30
+ }
31
+ /** Resume snapshot row from the session_resume table. */
32
+ export interface ResumeRow {
33
+ snapshot: string;
34
+ event_count: number;
35
+ consumed: number;
36
+ }
37
+ export declare class SessionDB extends SQLiteBase {
38
+ /**
39
+ * Cached prepared statements. Stored in a Map to avoid the JS private-field
40
+ * inheritance issue where `#field` declarations in a subclass are not
41
+ * accessible during base-class constructor calls.
42
+ *
43
+ * `declare` ensures TypeScript does NOT emit a field initializer at runtime.
44
+ * Without `declare`, even `stmts!: Map<...>` emits `this.stmts = undefined`
45
+ * after super() returns, wiping what prepareStatements() stored. The Map
46
+ * is created inside prepareStatements() instead.
47
+ */
48
+ private stmts;
49
+ constructor(opts?: {
50
+ dbPath?: string;
51
+ });
52
+ /** Shorthand to retrieve a cached statement. */
53
+ private stmt;
54
+ protected initSchema(): void;
55
+ protected prepareStatements(): void;
56
+ /**
57
+ * Insert a session event with deduplication and FIFO eviction.
58
+ *
59
+ * Deduplication: skips if the same type + data_hash appears in the
60
+ * last DEDUP_WINDOW events for this session.
61
+ *
62
+ * Eviction: if session exceeds MAX_EVENTS_PER_SESSION, evicts the
63
+ * lowest-priority (then oldest) event.
64
+ */
65
+ insertEvent(sessionId: string, event: SessionEvent, sourceHook?: string): void;
66
+ /**
67
+ * Retrieve events for a session with optional filtering.
68
+ */
69
+ getEvents(sessionId: string, opts?: {
70
+ type?: string;
71
+ minPriority?: number;
72
+ limit?: number;
73
+ }): StoredEvent[];
74
+ /**
75
+ * Get the total event count for a session.
76
+ */
77
+ getEventCount(sessionId: string): number;
78
+ /**
79
+ * Ensure a session metadata entry exists. Idempotent (INSERT OR IGNORE).
80
+ */
81
+ ensureSession(sessionId: string, projectDir: string): void;
82
+ /**
83
+ * Get session statistics/metadata.
84
+ */
85
+ getSessionStats(sessionId: string): SessionMeta | null;
86
+ /**
87
+ * Increment the compact_count for a session (tracks snapshot rebuilds).
88
+ */
89
+ incrementCompactCount(sessionId: string): void;
90
+ /**
91
+ * Upsert a resume snapshot for a session. Resets consumed flag on update.
92
+ */
93
+ upsertResume(sessionId: string, snapshot: string, eventCount?: number): void;
94
+ /**
95
+ * Retrieve the resume snapshot for a session.
96
+ */
97
+ getResume(sessionId: string): ResumeRow | null;
98
+ /**
99
+ * Mark the resume snapshot as consumed (already injected into conversation).
100
+ */
101
+ markResumeConsumed(sessionId: string): void;
102
+ /**
103
+ * Delete all data for a session (events, meta, resume).
104
+ */
105
+ deleteSession(sessionId: string): void;
106
+ /**
107
+ * Remove sessions older than maxAgeDays. Returns the count of deleted sessions.
108
+ */
109
+ cleanupOldSessions(maxAgeDays?: number): number;
110
+ }
@@ -0,0 +1,285 @@
1
+ /**
2
+ * SessionDB — Persistent per-project SQLite database for session events.
3
+ *
4
+ * Stores raw events captured by hooks during a Claude Code session,
5
+ * session metadata, and resume snapshots. Extends SQLiteBase from
6
+ * the shared package.
7
+ */
8
+ import { SQLiteBase, defaultDBPath } from "../db-base.js";
9
+ import { createHash } from "node:crypto";
10
+ // ─────────────────────────────────────────────────────────
11
+ // Constants
12
+ // ─────────────────────────────────────────────────────────
13
+ /** Maximum events per session before FIFO eviction kicks in. */
14
+ const MAX_EVENTS_PER_SESSION = 1000;
15
+ /** Number of recent events to check for deduplication. */
16
+ const DEDUP_WINDOW = 5;
17
+ // ─────────────────────────────────────────────────────────
18
+ // Statement keys (typed enum to avoid string typos)
19
+ // ─────────────────────────────────────────────────────────
20
+ const S = {
21
+ insertEvent: "insertEvent",
22
+ getEvents: "getEvents",
23
+ getEventsByType: "getEventsByType",
24
+ getEventsByPriority: "getEventsByPriority",
25
+ getEventsByTypeAndPriority: "getEventsByTypeAndPriority",
26
+ getEventCount: "getEventCount",
27
+ checkDuplicate: "checkDuplicate",
28
+ evictLowestPriority: "evictLowestPriority",
29
+ updateMetaLastEvent: "updateMetaLastEvent",
30
+ ensureSession: "ensureSession",
31
+ getSessionStats: "getSessionStats",
32
+ incrementCompactCount: "incrementCompactCount",
33
+ upsertResume: "upsertResume",
34
+ getResume: "getResume",
35
+ markResumeConsumed: "markResumeConsumed",
36
+ deleteEvents: "deleteEvents",
37
+ deleteMeta: "deleteMeta",
38
+ deleteResume: "deleteResume",
39
+ getOldSessions: "getOldSessions",
40
+ };
41
+ // ─────────────────────────────────────────────────────────
42
+ // SessionDB
43
+ // ─────────────────────────────────────────────────────────
44
+ export class SessionDB extends SQLiteBase {
45
+ constructor(opts) {
46
+ super(opts?.dbPath ?? defaultDBPath("session"));
47
+ }
48
+ /** Shorthand to retrieve a cached statement. */
49
+ stmt(key) {
50
+ return this.stmts.get(key);
51
+ }
52
+ // ── Schema ──
53
+ initSchema() {
54
+ // ── Migration: fix data_hash generated column from older schema ──
55
+ // Old schema had data_hash as GENERATED ALWAYS AS — new schema uses explicit INSERT.
56
+ // Detect and recreate table if needed (session data is ephemeral, safe to drop).
57
+ try {
58
+ const colInfo = this.db.pragma("table_xinfo(session_events)");
59
+ const hashCol = colInfo.find((c) => c.name === "data_hash");
60
+ if (hashCol && hashCol.hidden !== 0) {
61
+ // hidden != 0 means generated column — must recreate
62
+ this.db.exec("DROP TABLE session_events");
63
+ }
64
+ }
65
+ catch { /* table doesn't exist yet — fine */ }
66
+ this.db.exec(`
67
+ CREATE TABLE IF NOT EXISTS session_events (
68
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
69
+ session_id TEXT NOT NULL,
70
+ type TEXT NOT NULL,
71
+ category TEXT NOT NULL,
72
+ priority INTEGER NOT NULL DEFAULT 2,
73
+ data TEXT NOT NULL,
74
+ source_hook TEXT NOT NULL,
75
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
76
+ data_hash TEXT NOT NULL DEFAULT ''
77
+ );
78
+
79
+ CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id);
80
+ CREATE INDEX IF NOT EXISTS idx_session_events_type ON session_events(session_id, type);
81
+ CREATE INDEX IF NOT EXISTS idx_session_events_priority ON session_events(session_id, priority);
82
+
83
+ CREATE TABLE IF NOT EXISTS session_meta (
84
+ session_id TEXT PRIMARY KEY,
85
+ project_dir TEXT NOT NULL,
86
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
87
+ last_event_at TEXT,
88
+ event_count INTEGER NOT NULL DEFAULT 0,
89
+ compact_count INTEGER NOT NULL DEFAULT 0
90
+ );
91
+
92
+ CREATE TABLE IF NOT EXISTS session_resume (
93
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ session_id TEXT NOT NULL UNIQUE,
95
+ snapshot TEXT NOT NULL,
96
+ event_count INTEGER NOT NULL,
97
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
98
+ consumed INTEGER NOT NULL DEFAULT 0
99
+ );
100
+ `);
101
+ }
102
+ prepareStatements() {
103
+ this.stmts = new Map();
104
+ const p = (key, sql) => {
105
+ this.stmts.set(key, this.db.prepare(sql));
106
+ };
107
+ // ── Events ──
108
+ p(S.insertEvent, `INSERT INTO session_events (session_id, type, category, priority, data, source_hook, data_hash)
109
+ VALUES (?, ?, ?, ?, ?, ?, ?)`);
110
+ p(S.getEvents, `SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
111
+ FROM session_events WHERE session_id = ? ORDER BY id ASC LIMIT ?`);
112
+ p(S.getEventsByType, `SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
113
+ FROM session_events WHERE session_id = ? AND type = ? ORDER BY id ASC LIMIT ?`);
114
+ p(S.getEventsByPriority, `SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
115
+ FROM session_events WHERE session_id = ? AND priority >= ? ORDER BY id ASC LIMIT ?`);
116
+ p(S.getEventsByTypeAndPriority, `SELECT id, session_id, type, category, priority, data, source_hook, created_at, data_hash
117
+ FROM session_events WHERE session_id = ? AND type = ? AND priority >= ? ORDER BY id ASC LIMIT ?`);
118
+ p(S.getEventCount, `SELECT COUNT(*) AS cnt FROM session_events WHERE session_id = ?`);
119
+ p(S.checkDuplicate, `SELECT 1 FROM (
120
+ SELECT type, data_hash FROM session_events
121
+ WHERE session_id = ? ORDER BY id DESC LIMIT ?
122
+ ) AS recent
123
+ WHERE recent.type = ? AND recent.data_hash = ?
124
+ LIMIT 1`);
125
+ p(S.evictLowestPriority, `DELETE FROM session_events WHERE id = (
126
+ SELECT id FROM session_events WHERE session_id = ?
127
+ ORDER BY priority ASC, id ASC LIMIT 1
128
+ )`);
129
+ p(S.updateMetaLastEvent, `UPDATE session_meta
130
+ SET last_event_at = datetime('now'), event_count = event_count + 1
131
+ WHERE session_id = ?`);
132
+ // ── Meta ──
133
+ p(S.ensureSession, `INSERT OR IGNORE INTO session_meta (session_id, project_dir) VALUES (?, ?)`);
134
+ p(S.getSessionStats, `SELECT session_id, project_dir, started_at, last_event_at, event_count, compact_count
135
+ FROM session_meta WHERE session_id = ?`);
136
+ p(S.incrementCompactCount, `UPDATE session_meta SET compact_count = compact_count + 1 WHERE session_id = ?`);
137
+ // ── Resume ──
138
+ p(S.upsertResume, `INSERT INTO session_resume (session_id, snapshot, event_count)
139
+ VALUES (?, ?, ?)
140
+ ON CONFLICT(session_id) DO UPDATE SET
141
+ snapshot = excluded.snapshot,
142
+ event_count = excluded.event_count,
143
+ created_at = datetime('now'),
144
+ consumed = 0`);
145
+ p(S.getResume, `SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?`);
146
+ p(S.markResumeConsumed, `UPDATE session_resume SET consumed = 1 WHERE session_id = ?`);
147
+ // ── Delete ──
148
+ p(S.deleteEvents, `DELETE FROM session_events WHERE session_id = ?`);
149
+ p(S.deleteMeta, `DELETE FROM session_meta WHERE session_id = ?`);
150
+ p(S.deleteResume, `DELETE FROM session_resume WHERE session_id = ?`);
151
+ // ── Cleanup ──
152
+ p(S.getOldSessions, `SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')`);
153
+ }
154
+ // ═══════════════════════════════════════════
155
+ // Events
156
+ // ═══════════════════════════════════════════
157
+ /**
158
+ * Insert a session event with deduplication and FIFO eviction.
159
+ *
160
+ * Deduplication: skips if the same type + data_hash appears in the
161
+ * last DEDUP_WINDOW events for this session.
162
+ *
163
+ * Eviction: if session exceeds MAX_EVENTS_PER_SESSION, evicts the
164
+ * lowest-priority (then oldest) event.
165
+ */
166
+ insertEvent(sessionId, event, sourceHook = "PostToolUse") {
167
+ // SHA256-based dedup hash (first 16 hex chars = 8 bytes of entropy)
168
+ const dataHash = createHash("sha256")
169
+ .update(event.data)
170
+ .digest("hex")
171
+ .slice(0, 16)
172
+ .toUpperCase();
173
+ // Atomic: dedup check + eviction + insert in a single transaction
174
+ // to prevent race conditions from concurrent hook calls.
175
+ const transaction = this.db.transaction(() => {
176
+ // Deduplication check: same type + data_hash in last N events
177
+ const dup = this.stmt(S.checkDuplicate).get(sessionId, DEDUP_WINDOW, event.type, dataHash);
178
+ if (dup)
179
+ return;
180
+ // Enforce max events with FIFO eviction of lowest priority
181
+ const countRow = this.stmt(S.getEventCount).get(sessionId);
182
+ if (countRow.cnt >= MAX_EVENTS_PER_SESSION) {
183
+ this.stmt(S.evictLowestPriority).run(sessionId);
184
+ }
185
+ // Insert the event
186
+ this.stmt(S.insertEvent).run(sessionId, event.type, event.category, event.priority, event.data, sourceHook, dataHash);
187
+ // Update meta if session exists
188
+ this.stmt(S.updateMetaLastEvent).run(sessionId);
189
+ });
190
+ transaction();
191
+ }
192
+ /**
193
+ * Retrieve events for a session with optional filtering.
194
+ */
195
+ getEvents(sessionId, opts) {
196
+ const limit = opts?.limit ?? 1000;
197
+ const type = opts?.type;
198
+ const minPriority = opts?.minPriority;
199
+ if (type && minPriority !== undefined) {
200
+ return this.stmt(S.getEventsByTypeAndPriority).all(sessionId, type, minPriority, limit);
201
+ }
202
+ if (type) {
203
+ return this.stmt(S.getEventsByType).all(sessionId, type, limit);
204
+ }
205
+ if (minPriority !== undefined) {
206
+ return this.stmt(S.getEventsByPriority).all(sessionId, minPriority, limit);
207
+ }
208
+ return this.stmt(S.getEvents).all(sessionId, limit);
209
+ }
210
+ /**
211
+ * Get the total event count for a session.
212
+ */
213
+ getEventCount(sessionId) {
214
+ const row = this.stmt(S.getEventCount).get(sessionId);
215
+ return row.cnt;
216
+ }
217
+ // ═══════════════════════════════════════════
218
+ // Meta
219
+ // ═══════════════════════════════════════════
220
+ /**
221
+ * Ensure a session metadata entry exists. Idempotent (INSERT OR IGNORE).
222
+ */
223
+ ensureSession(sessionId, projectDir) {
224
+ this.stmt(S.ensureSession).run(sessionId, projectDir);
225
+ }
226
+ /**
227
+ * Get session statistics/metadata.
228
+ */
229
+ getSessionStats(sessionId) {
230
+ const row = this.stmt(S.getSessionStats).get(sessionId);
231
+ return row ?? null;
232
+ }
233
+ /**
234
+ * Increment the compact_count for a session (tracks snapshot rebuilds).
235
+ */
236
+ incrementCompactCount(sessionId) {
237
+ this.stmt(S.incrementCompactCount).run(sessionId);
238
+ }
239
+ // ═══════════════════════════════════════════
240
+ // Resume
241
+ // ═══════════════════════════════════════════
242
+ /**
243
+ * Upsert a resume snapshot for a session. Resets consumed flag on update.
244
+ */
245
+ upsertResume(sessionId, snapshot, eventCount) {
246
+ this.stmt(S.upsertResume).run(sessionId, snapshot, eventCount ?? 0);
247
+ }
248
+ /**
249
+ * Retrieve the resume snapshot for a session.
250
+ */
251
+ getResume(sessionId) {
252
+ const row = this.stmt(S.getResume).get(sessionId);
253
+ return row ?? null;
254
+ }
255
+ /**
256
+ * Mark the resume snapshot as consumed (already injected into conversation).
257
+ */
258
+ markResumeConsumed(sessionId) {
259
+ this.stmt(S.markResumeConsumed).run(sessionId);
260
+ }
261
+ // ═══════════════════════════════════════════
262
+ // Lifecycle
263
+ // ═══════════════════════════════════════════
264
+ /**
265
+ * Delete all data for a session (events, meta, resume).
266
+ */
267
+ deleteSession(sessionId) {
268
+ this.db.transaction(() => {
269
+ this.stmt(S.deleteEvents).run(sessionId);
270
+ this.stmt(S.deleteResume).run(sessionId);
271
+ this.stmt(S.deleteMeta).run(sessionId);
272
+ })();
273
+ }
274
+ /**
275
+ * Remove sessions older than maxAgeDays. Returns the count of deleted sessions.
276
+ */
277
+ cleanupOldSessions(maxAgeDays = 7) {
278
+ const negDays = `-${maxAgeDays}`;
279
+ const oldSessions = this.stmt(S.getOldSessions).all(negDays);
280
+ for (const { session_id } of oldSessions) {
281
+ this.deleteSession(session_id);
282
+ }
283
+ return oldSessions.length;
284
+ }
285
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Session event extraction — pure functions, zero side effects.
3
+ * Extracts structured events from Claude Code tool calls and user messages.
4
+ *
5
+ * All 13 event categories as specified in PRD Section 3.
6
+ */
7
+ export interface SessionEvent {
8
+ /** e.g. "file_read", "file_write", "cwd", "error_tool", "git", "task",
9
+ * "decision", "rule", "env", "role", "skill", "subagent", "data", "intent" */
10
+ type: string;
11
+ /** e.g. "file", "cwd", "error", "git", "task", "decision",
12
+ * "rule", "env", "role", "skill", "subagent", "data", "intent" */
13
+ category: string;
14
+ /** Extracted payload, truncated to 300 chars max */
15
+ data: string;
16
+ /** 1=critical (rules, files, tasks) … 5=low */
17
+ priority: number;
18
+ }
19
+ export interface ToolCall {
20
+ toolName: string;
21
+ toolInput: Record<string, unknown>;
22
+ toolResponse?: string;
23
+ isError?: boolean;
24
+ }
25
+ /**
26
+ * Hook input shape as received from Claude Code PostToolUse hook stdin.
27
+ * Uses snake_case to match the raw hook JSON.
28
+ */
29
+ export interface HookInput {
30
+ tool_name: string;
31
+ tool_input: Record<string, unknown>;
32
+ tool_response?: string;
33
+ /** Optional structured output from the tool (may carry isError) */
34
+ tool_output?: {
35
+ isError?: boolean;
36
+ };
37
+ }
38
+ /**
39
+ * Extract session events from a PostToolUse hook input.
40
+ *
41
+ * Accepts the raw hook JSON shape (snake_case keys) as received from stdin.
42
+ * Returns an array of zero or more SessionEvents. Never throws.
43
+ */
44
+ export declare function extractEvents(input: HookInput): SessionEvent[];
45
+ /**
46
+ * Extract session events from a UserPromptSubmit hook input (user message text).
47
+ *
48
+ * Handles: decision, role, intent, data categories.
49
+ * Returns an array of zero or more SessionEvents. Never throws.
50
+ */
51
+ export declare function extractUserEvents(message: string): SessionEvent[];