somtum 0.1.2

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 (194) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +459 -0
  3. package/dist/cli/config_cmd.d.ts +10 -0
  4. package/dist/cli/config_cmd.d.ts.map +1 -0
  5. package/dist/cli/config_cmd.js +96 -0
  6. package/dist/cli/config_cmd.js.map +1 -0
  7. package/dist/cli/doctor.d.ts +17 -0
  8. package/dist/cli/doctor.d.ts.map +1 -0
  9. package/dist/cli/doctor.js +169 -0
  10. package/dist/cli/doctor.js.map +1 -0
  11. package/dist/cli/edit.d.ts +4 -0
  12. package/dist/cli/edit.d.ts.map +1 -0
  13. package/dist/cli/edit.js +63 -0
  14. package/dist/cli/edit.js.map +1 -0
  15. package/dist/cli/export.d.ts +19 -0
  16. package/dist/cli/export.d.ts.map +1 -0
  17. package/dist/cli/export.js +84 -0
  18. package/dist/cli/export.js.map +1 -0
  19. package/dist/cli/forget.d.ts +11 -0
  20. package/dist/cli/forget.d.ts.map +1 -0
  21. package/dist/cli/forget.js +32 -0
  22. package/dist/cli/forget.js.map +1 -0
  23. package/dist/cli/hook.d.ts +2 -0
  24. package/dist/cli/hook.d.ts.map +1 -0
  25. package/dist/cli/hook.js +66 -0
  26. package/dist/cli/hook.js.map +1 -0
  27. package/dist/cli/import.d.ts +18 -0
  28. package/dist/cli/import.d.ts.map +1 -0
  29. package/dist/cli/import.js +90 -0
  30. package/dist/cli/import.js.map +1 -0
  31. package/dist/cli/index.d.ts +3 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +223 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/cli/init.d.ts +24 -0
  36. package/dist/cli/init.d.ts.map +1 -0
  37. package/dist/cli/init.js +144 -0
  38. package/dist/cli/init.js.map +1 -0
  39. package/dist/cli/purge.d.ts +16 -0
  40. package/dist/cli/purge.d.ts.map +1 -0
  41. package/dist/cli/purge.js +69 -0
  42. package/dist/cli/purge.js.map +1 -0
  43. package/dist/cli/rebuild.d.ts +15 -0
  44. package/dist/cli/rebuild.d.ts.map +1 -0
  45. package/dist/cli/rebuild.js +41 -0
  46. package/dist/cli/rebuild.js.map +1 -0
  47. package/dist/cli/reindex.d.ts +6 -0
  48. package/dist/cli/reindex.d.ts.map +1 -0
  49. package/dist/cli/reindex.js +47 -0
  50. package/dist/cli/reindex.js.map +1 -0
  51. package/dist/cli/search.d.ts +16 -0
  52. package/dist/cli/search.d.ts.map +1 -0
  53. package/dist/cli/search.js +62 -0
  54. package/dist/cli/search.js.map +1 -0
  55. package/dist/cli/show.d.ts +12 -0
  56. package/dist/cli/show.d.ts.map +1 -0
  57. package/dist/cli/show.js +49 -0
  58. package/dist/cli/show.js.map +1 -0
  59. package/dist/cli/stats.d.ts +27 -0
  60. package/dist/cli/stats.d.ts.map +1 -0
  61. package/dist/cli/stats.js +80 -0
  62. package/dist/cli/stats.js.map +1 -0
  63. package/dist/cli/sync.d.ts +18 -0
  64. package/dist/cli/sync.d.ts.map +1 -0
  65. package/dist/cli/sync.js +138 -0
  66. package/dist/cli/sync.js.map +1 -0
  67. package/dist/config.d.ts +12 -0
  68. package/dist/config.d.ts.map +1 -0
  69. package/dist/config.js +52 -0
  70. package/dist/config.js.map +1 -0
  71. package/dist/core/cache.d.ts +29 -0
  72. package/dist/core/cache.d.ts.map +1 -0
  73. package/dist/core/cache.js +137 -0
  74. package/dist/core/cache.js.map +1 -0
  75. package/dist/core/db.d.ts +12 -0
  76. package/dist/core/db.d.ts.map +1 -0
  77. package/dist/core/db.js +80 -0
  78. package/dist/core/db.js.map +1 -0
  79. package/dist/core/embeddings.d.ts +15 -0
  80. package/dist/core/embeddings.d.ts.map +1 -0
  81. package/dist/core/embeddings.js +74 -0
  82. package/dist/core/embeddings.js.map +1 -0
  83. package/dist/core/embeddings_bootstrap.d.ts +3 -0
  84. package/dist/core/embeddings_bootstrap.d.ts.map +1 -0
  85. package/dist/core/embeddings_bootstrap.js +16 -0
  86. package/dist/core/embeddings_bootstrap.js.map +1 -0
  87. package/dist/core/embeddings_xenova.d.ts +3 -0
  88. package/dist/core/embeddings_xenova.d.ts.map +1 -0
  89. package/dist/core/embeddings_xenova.js +28 -0
  90. package/dist/core/embeddings_xenova.js.map +1 -0
  91. package/dist/core/extractor.d.ts +27 -0
  92. package/dist/core/extractor.d.ts.map +1 -0
  93. package/dist/core/extractor.js +133 -0
  94. package/dist/core/extractor.js.map +1 -0
  95. package/dist/core/file_summary.d.ts +33 -0
  96. package/dist/core/file_summary.d.ts.map +1 -0
  97. package/dist/core/file_summary.js +127 -0
  98. package/dist/core/file_summary.js.map +1 -0
  99. package/dist/core/fingerprint.d.ts +13 -0
  100. package/dist/core/fingerprint.d.ts.map +1 -0
  101. package/dist/core/fingerprint.js +36 -0
  102. package/dist/core/fingerprint.js.map +1 -0
  103. package/dist/core/index_gen.d.ts +16 -0
  104. package/dist/core/index_gen.d.ts.map +1 -0
  105. package/dist/core/index_gen.js +173 -0
  106. package/dist/core/index_gen.js.map +1 -0
  107. package/dist/core/memory_files.d.ts +4 -0
  108. package/dist/core/memory_files.d.ts.map +1 -0
  109. package/dist/core/memory_files.js +36 -0
  110. package/dist/core/memory_files.js.map +1 -0
  111. package/dist/core/privacy.d.ts +9 -0
  112. package/dist/core/privacy.d.ts.map +1 -0
  113. package/dist/core/privacy.js +32 -0
  114. package/dist/core/privacy.js.map +1 -0
  115. package/dist/core/project_id.d.ts +3 -0
  116. package/dist/core/project_id.d.ts.map +1 -0
  117. package/dist/core/project_id.js +31 -0
  118. package/dist/core/project_id.js.map +1 -0
  119. package/dist/core/reindex.d.ts +10 -0
  120. package/dist/core/reindex.d.ts.map +1 -0
  121. package/dist/core/reindex.js +40 -0
  122. package/dist/core/reindex.js.map +1 -0
  123. package/dist/core/retrieval_stats.d.ts +22 -0
  124. package/dist/core/retrieval_stats.d.ts.map +1 -0
  125. package/dist/core/retrieval_stats.js +56 -0
  126. package/dist/core/retrieval_stats.js.map +1 -0
  127. package/dist/core/retriever/bm25.d.ts +15 -0
  128. package/dist/core/retriever/bm25.d.ts.map +1 -0
  129. package/dist/core/retriever/bm25.js +51 -0
  130. package/dist/core/retriever/bm25.js.map +1 -0
  131. package/dist/core/retriever/embeddings.d.ts +9 -0
  132. package/dist/core/retriever/embeddings.d.ts.map +1 -0
  133. package/dist/core/retriever/embeddings.js +34 -0
  134. package/dist/core/retriever/embeddings.js.map +1 -0
  135. package/dist/core/retriever/factory.d.ts +6 -0
  136. package/dist/core/retriever/factory.d.ts.map +1 -0
  137. package/dist/core/retriever/factory.js +53 -0
  138. package/dist/core/retriever/factory.js.map +1 -0
  139. package/dist/core/retriever/hybrid.d.ts +14 -0
  140. package/dist/core/retriever/hybrid.d.ts.map +1 -0
  141. package/dist/core/retriever/hybrid.js +52 -0
  142. package/dist/core/retriever/hybrid.js.map +1 -0
  143. package/dist/core/retriever/llm_index.d.ts +18 -0
  144. package/dist/core/retriever/llm_index.d.ts.map +1 -0
  145. package/dist/core/retriever/llm_index.js +90 -0
  146. package/dist/core/retriever/llm_index.js.map +1 -0
  147. package/dist/core/retriever/types.d.ts +16 -0
  148. package/dist/core/retriever/types.d.ts.map +1 -0
  149. package/dist/core/retriever/types.js +2 -0
  150. package/dist/core/retriever/types.js.map +1 -0
  151. package/dist/core/schema.d.ts +470 -0
  152. package/dist/core/schema.d.ts.map +1 -0
  153. package/dist/core/schema.js +153 -0
  154. package/dist/core/schema.js.map +1 -0
  155. package/dist/core/store.d.ts +35 -0
  156. package/dist/core/store.d.ts.map +1 -0
  157. package/dist/core/store.js +172 -0
  158. package/dist/core/store.js.map +1 -0
  159. package/dist/core/tokens.d.ts +3 -0
  160. package/dist/core/tokens.d.ts.map +1 -0
  161. package/dist/core/tokens.js +12 -0
  162. package/dist/core/tokens.js.map +1 -0
  163. package/dist/core/transcript.d.ts +16 -0
  164. package/dist/core/transcript.d.ts.map +1 -0
  165. package/dist/core/transcript.js +128 -0
  166. package/dist/core/transcript.js.map +1 -0
  167. package/dist/db/migrations/001_init.sql +90 -0
  168. package/dist/db/migrations/002_cache_files.sql +6 -0
  169. package/dist/db/migrations/003_retrieval_stats.sql +17 -0
  170. package/dist/hooks/post_session.d.ts +65 -0
  171. package/dist/hooks/post_session.d.ts.map +1 -0
  172. package/dist/hooks/post_session.js +249 -0
  173. package/dist/hooks/post_session.js.map +1 -0
  174. package/dist/hooks/pre_prompt.d.ts +55 -0
  175. package/dist/hooks/pre_prompt.d.ts.map +1 -0
  176. package/dist/hooks/pre_prompt.js +98 -0
  177. package/dist/hooks/pre_prompt.js.map +1 -0
  178. package/dist/hooks/pre_read.d.ts +56 -0
  179. package/dist/hooks/pre_read.d.ts.map +1 -0
  180. package/dist/hooks/pre_read.js +77 -0
  181. package/dist/hooks/pre_read.js.map +1 -0
  182. package/dist/index.d.ts +15 -0
  183. package/dist/index.d.ts.map +1 -0
  184. package/dist/index.js +15 -0
  185. package/dist/index.js.map +1 -0
  186. package/dist/mcp/server.d.ts +20 -0
  187. package/dist/mcp/server.d.ts.map +1 -0
  188. package/dist/mcp/server.js +128 -0
  189. package/dist/mcp/server.js.map +1 -0
  190. package/dist/mcp/tools.d.ts +72 -0
  191. package/dist/mcp/tools.d.ts.map +1 -0
  192. package/dist/mcp/tools.js +153 -0
  193. package/dist/mcp/tools.js.map +1 -0
  194. package/package.json +79 -0
@@ -0,0 +1,137 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { ulid } from 'ulid';
3
+ import { CacheEntrySchema } from './schema.js';
4
+ import { cosineSimilarity, decodeVector, encodeVector, } from './embeddings.js';
5
+ // Bump on any change to how context_fingerprint is computed. On bump,
6
+ // invalidate all existing cache entries at next startup.
7
+ export const CACHE_FINGERPRINT_VERSION = 1;
8
+ function rowToEntry(row) {
9
+ return CacheEntrySchema.parse({
10
+ id: row.id,
11
+ prompt_hash: row.prompt_hash,
12
+ prompt_text: row.prompt_text,
13
+ prompt_embedding: row.prompt_embedding,
14
+ response: row.response,
15
+ model: row.model,
16
+ context_fingerprint: row.context_fingerprint,
17
+ fingerprint_version: row.fingerprint_version,
18
+ created_at: row.created_at,
19
+ last_hit_at: row.last_hit_at,
20
+ hit_count: row.hit_count,
21
+ false_hit_count: row.false_hit_count,
22
+ invalidated: row.invalidated === 1,
23
+ files_touched: JSON.parse(row.files_touched),
24
+ });
25
+ }
26
+ export function normalizePrompt(prompt) {
27
+ // Minimal normalization: collapse runs of whitespace and trim.
28
+ // The fuller form (case-folding commands, preserving quoted strings)
29
+ // lands when fuzzy matching does.
30
+ return prompt.trim().replace(/\s+/g, ' ');
31
+ }
32
+ export function hashPrompt(prompt) {
33
+ return createHash('sha256').update(normalizePrompt(prompt)).digest('hex');
34
+ }
35
+ export class PromptCache {
36
+ db;
37
+ constructor(db) {
38
+ this.db = db;
39
+ }
40
+ lookupByHash(promptHash) {
41
+ const row = this.db
42
+ .prepare(`SELECT * FROM cache_entries
43
+ WHERE prompt_hash = ? AND invalidated = 0`)
44
+ .get(promptHash);
45
+ return row ? rowToEntry(row) : null;
46
+ }
47
+ put(input) {
48
+ const id = ulid();
49
+ const now = Date.now();
50
+ const filesTouched = JSON.stringify(input.files_touched ?? []);
51
+ this.db
52
+ .prepare(`INSERT INTO cache_entries
53
+ (id, prompt_hash, prompt_text, prompt_embedding, response, model,
54
+ context_fingerprint, fingerprint_version, created_at, last_hit_at,
55
+ hit_count, false_hit_count, invalidated, files_touched)
56
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, ?)
57
+ ON CONFLICT(prompt_hash) DO UPDATE SET
58
+ response = excluded.response,
59
+ model = excluded.model,
60
+ context_fingerprint = excluded.context_fingerprint,
61
+ fingerprint_version = excluded.fingerprint_version,
62
+ last_hit_at = excluded.last_hit_at,
63
+ files_touched = excluded.files_touched,
64
+ prompt_embedding = COALESCE(excluded.prompt_embedding, cache_entries.prompt_embedding),
65
+ invalidated = 0`)
66
+ .run(id, input.prompt_hash, input.prompt_text, input.prompt_embedding ?? null, input.response, input.model, input.context_fingerprint, CACHE_FINGERPRINT_VERSION, now, now, filesTouched);
67
+ const entry = this.lookupByHash(input.prompt_hash);
68
+ if (!entry)
69
+ throw new Error('PromptCache.put: readback failed');
70
+ return entry;
71
+ }
72
+ // Nearest-neighbor fuzzy match over stored prompt embeddings. Returns the
73
+ // best hit with similarity >= threshold, or null. The caller is expected
74
+ // to validate the file-fingerprint before treating it as a true hit.
75
+ async lookupFuzzy(prompt, embedder, threshold) {
76
+ const rows = this.db
77
+ .prepare(`SELECT id, prompt_embedding FROM cache_entries
78
+ WHERE invalidated = 0 AND prompt_embedding IS NOT NULL`)
79
+ .all();
80
+ if (rows.length === 0)
81
+ return null;
82
+ const [qv] = await embedder.embed([prompt]);
83
+ if (!qv)
84
+ return null;
85
+ let bestId = null;
86
+ let bestSim = -Infinity;
87
+ for (const row of rows) {
88
+ const v = decodeVector(row.prompt_embedding, embedder.dim);
89
+ const s = cosineSimilarity(qv, v);
90
+ if (s > bestSim) {
91
+ bestSim = s;
92
+ bestId = row.id;
93
+ }
94
+ }
95
+ if (!bestId || bestSim < threshold)
96
+ return null;
97
+ const entry = this.getById(bestId);
98
+ if (!entry)
99
+ return null;
100
+ return { entry, similarity: bestSim };
101
+ }
102
+ getById(id) {
103
+ const row = this.db
104
+ .prepare(`SELECT * FROM cache_entries WHERE id = ?`)
105
+ .get(id);
106
+ return row ? rowToEntry(row) : null;
107
+ }
108
+ setPromptEmbedding(id, v) {
109
+ this.db
110
+ .prepare(`UPDATE cache_entries SET prompt_embedding = ? WHERE id = ?`)
111
+ .run(encodeVector(v), id);
112
+ }
113
+ listMissingPromptEmbeddings() {
114
+ return this.db
115
+ .prepare(`SELECT id, prompt_text FROM cache_entries
116
+ WHERE prompt_embedding IS NULL AND invalidated = 0`)
117
+ .all();
118
+ }
119
+ recordFalseHit(id) {
120
+ this.db
121
+ .prepare(`UPDATE cache_entries SET false_hit_count = false_hit_count + 1 WHERE id = ?`)
122
+ .run(id);
123
+ }
124
+ touch(id) {
125
+ this.db
126
+ .prepare(`UPDATE cache_entries SET hit_count = hit_count + 1, last_hit_at = ? WHERE id = ?`)
127
+ .run(Date.now(), id);
128
+ }
129
+ invalidate(id) {
130
+ this.db.prepare(`UPDATE cache_entries SET invalidated = 1 WHERE id = ?`).run(id);
131
+ }
132
+ count() {
133
+ const row = this.db.prepare(`SELECT COUNT(*) AS n FROM cache_entries`).get();
134
+ return row.n;
135
+ }
136
+ }
137
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/core/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import Database from 'better-sqlite3';
2
+ export type DB = Database.Database;
3
+ export interface OpenOptions {
4
+ path: string;
5
+ readonly?: boolean;
6
+ migrationsDir?: string;
7
+ }
8
+ export declare function openDb(options: OpenOptions): DB;
9
+ export declare function runMigrations(db: DB, dir?: string): number;
10
+ export declare function appliedVersions(db: DB): number[];
11
+ export declare function hasFts5(db: DB): boolean;
12
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/core/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAKtC,MAAM,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC;AAWnC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,EAAE,CAgB/C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,GAAE,MAAuB,GAAG,MAAM,CAmD1E;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,CAKhD;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAQvC"}
@@ -0,0 +1,80 @@
1
+ import Database from 'better-sqlite3';
2
+ import { readFileSync, readdirSync, existsSync, mkdirSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const MIGRATIONS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'db', 'migrations');
6
+ const MIGRATION_FILENAME = /^(\d{3,})_.+\.sql$/;
7
+ export function openDb(options) {
8
+ const dir = dirname(options.path);
9
+ if (!existsSync(dir))
10
+ mkdirSync(dir, { recursive: true });
11
+ const db = new Database(options.path, {
12
+ readonly: options.readonly ?? false,
13
+ fileMustExist: false,
14
+ });
15
+ db.pragma('journal_mode = WAL');
16
+ db.pragma('foreign_keys = ON');
17
+ db.pragma('synchronous = NORMAL');
18
+ if (!options.readonly) {
19
+ runMigrations(db, options.migrationsDir ?? MIGRATIONS_DIR);
20
+ }
21
+ return db;
22
+ }
23
+ export function runMigrations(db, dir = MIGRATIONS_DIR) {
24
+ db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
25
+ version INTEGER PRIMARY KEY,
26
+ name TEXT NOT NULL,
27
+ applied_at INTEGER NOT NULL
28
+ );`);
29
+ if (!existsSync(dir)) {
30
+ throw new Error(`Migrations directory not found: ${dir}`);
31
+ }
32
+ const applied = new Set(db
33
+ .prepare(`SELECT version FROM schema_migrations`)
34
+ .all()
35
+ .map((r) => r.version));
36
+ const files = readdirSync(dir)
37
+ .filter((f) => MIGRATION_FILENAME.test(f))
38
+ .sort();
39
+ let count = 0;
40
+ const insert = db.prepare(`INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)`);
41
+ for (const file of files) {
42
+ const match = MIGRATION_FILENAME.exec(file);
43
+ if (!match || match[1] === undefined)
44
+ continue;
45
+ const version = Number.parseInt(match[1], 10);
46
+ if (applied.has(version))
47
+ continue;
48
+ const sql = readFileSync(join(dir, file), 'utf8');
49
+ // BEGIN IMMEDIATE fails fast under writer contention instead of hanging.
50
+ db.exec('BEGIN IMMEDIATE');
51
+ try {
52
+ db.exec(sql);
53
+ insert.run(version, file, Date.now());
54
+ db.exec('COMMIT');
55
+ }
56
+ catch (err) {
57
+ db.exec('ROLLBACK');
58
+ throw err;
59
+ }
60
+ count += 1;
61
+ }
62
+ return count;
63
+ }
64
+ export function appliedVersions(db) {
65
+ return db
66
+ .prepare(`SELECT version FROM schema_migrations ORDER BY version ASC`)
67
+ .all()
68
+ .map((r) => r.version);
69
+ }
70
+ export function hasFts5(db) {
71
+ try {
72
+ db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS _fts5_probe USING fts5(x)`);
73
+ db.exec(`DROP TABLE IF EXISTS _fts5_probe`);
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/core/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export declare const EMBEDDING_DIM = 384;
2
+ export interface Embedder {
3
+ embed(texts: string[]): Promise<Float32Array[]>;
4
+ readonly name: string;
5
+ readonly dim: number;
6
+ }
7
+ export declare function setEmbedder(e: Embedder | null): void;
8
+ export declare function setEmbedderLoader(l: (() => Promise<Embedder>) | null): void;
9
+ export declare function getEmbedder(): Promise<Embedder>;
10
+ export declare function isEmbedderReady(): boolean;
11
+ export declare function encodeVector(v: Float32Array): Buffer;
12
+ export declare function decodeVector(buf: Buffer, dim?: number): Float32Array;
13
+ export declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
14
+ export declare function normalize(v: Float32Array): Float32Array;
15
+ //# sourceMappingURL=embeddings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../../src/core/embeddings.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,MAAM,WAAW,QAAQ;IAGvB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAKD,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,CAEpD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAG3E;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC,CASrD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAKD,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAEpD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,YAAY,CAOnF;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM,CAczE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,YAAY,GAAG,YAAY,CAQvD"}
@@ -0,0 +1,74 @@
1
+ // Embedder abstraction.
2
+ //
3
+ // A real embedder loads bge-small-en-v1.5 (~30MB once quantized) lazily on
4
+ // first use. Tests inject a deterministic fake via `setEmbedder` so they
5
+ // don't need the model on disk.
6
+ //
7
+ // Vectors are 384-dim f32. We store them as a Buffer in SQLite: no extra
8
+ // dependency, decode is a single Float32Array view over the buffer bytes.
9
+ export const EMBEDDING_DIM = 384;
10
+ let current = null;
11
+ let loader = null;
12
+ export function setEmbedder(e) {
13
+ current = e;
14
+ }
15
+ export function setEmbedderLoader(l) {
16
+ loader = l;
17
+ current = null;
18
+ }
19
+ export async function getEmbedder() {
20
+ if (current)
21
+ return current;
22
+ if (!loader) {
23
+ throw new Error('No embedder configured. Call setEmbedder() or setEmbedderLoader() before use.');
24
+ }
25
+ current = await loader();
26
+ return current;
27
+ }
28
+ export function isEmbedderReady() {
29
+ return current !== null;
30
+ }
31
+ // Encode/decode vectors as byte buffers for SQLite storage.
32
+ // Using Float32Array.buffer keeps the layout stable across platforms that
33
+ // agree on little-endian — which is every platform Node runs on in practice.
34
+ export function encodeVector(v) {
35
+ return Buffer.from(v.buffer, v.byteOffset, v.byteLength);
36
+ }
37
+ export function decodeVector(buf, dim = EMBEDDING_DIM) {
38
+ if (buf.length !== dim * 4) {
39
+ throw new Error(`embedding blob wrong size: got ${buf.length} bytes, want ${dim * 4}`);
40
+ }
41
+ // Copy to detach from the underlying Buffer pool.
42
+ const copy = Buffer.from(buf);
43
+ return new Float32Array(copy.buffer, copy.byteOffset, dim);
44
+ }
45
+ export function cosineSimilarity(a, b) {
46
+ if (a.length !== b.length)
47
+ return 0;
48
+ let dot = 0;
49
+ let na = 0;
50
+ let nb = 0;
51
+ for (let i = 0; i < a.length; i += 1) {
52
+ const ai = a[i] ?? 0;
53
+ const bi = b[i] ?? 0;
54
+ dot += ai * bi;
55
+ na += ai * ai;
56
+ nb += bi * bi;
57
+ }
58
+ if (na === 0 || nb === 0)
59
+ return 0;
60
+ return dot / Math.sqrt(na * nb);
61
+ }
62
+ export function normalize(v) {
63
+ let n = 0;
64
+ for (let i = 0; i < v.length; i += 1)
65
+ n += (v[i] ?? 0) ** 2;
66
+ const mag = Math.sqrt(n);
67
+ if (mag === 0)
68
+ return v;
69
+ const out = new Float32Array(v.length);
70
+ for (let i = 0; i < v.length; i += 1)
71
+ out[i] = (v[i] ?? 0) / mag;
72
+ return out;
73
+ }
74
+ //# sourceMappingURL=embeddings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../src/core/embeddings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Config } from './schema.js';
2
+ export declare function ensureEmbedderConfigured(config: Config): void;
3
+ //# sourceMappingURL=embeddings_bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings_bootstrap.d.ts","sourceRoot":"","sources":["../../src/core/embeddings_bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAM1C,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQ7D"}
@@ -0,0 +1,16 @@
1
+ import { setEmbedderLoader, isEmbedderReady } from './embeddings.js';
2
+ // Idempotent: wires up a lazy loader for the real Xenova embedder when
3
+ // embeddings are enabled. Tests bypass this by calling `setEmbedder` directly
4
+ // with a deterministic fake.
5
+ export function ensureEmbedderConfigured(config) {
6
+ if (!config.retrieval.embeddings.enabled)
7
+ return;
8
+ if (isEmbedderReady())
9
+ return;
10
+ const modelName = config.retrieval.embeddings.model;
11
+ setEmbedderLoader(async () => {
12
+ const { createXenovaEmbedder } = await import('./embeddings_xenova.js');
13
+ return createXenovaEmbedder(modelName);
14
+ });
15
+ }
16
+ //# sourceMappingURL=embeddings_bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings_bootstrap.js","sourceRoot":"","sources":["../../src/core/embeddings_bootstrap.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAErE,uEAAuE;AACvE,8EAA8E;AAC9E,6BAA6B;AAC7B,MAAM,UAAU,wBAAwB,CAAC,MAAc;IACrD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO;QAAE,OAAO;IACjD,IAAI,eAAe,EAAE;QAAE,OAAO;IAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC;IACpD,iBAAiB,CAAC,KAAK,IAAI,EAAE;QAC3B,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;QACxE,OAAO,oBAAoB,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type Embedder } from './embeddings.js';
2
+ export declare function createXenovaEmbedder(modelName?: string): Promise<Embedder>;
3
+ //# sourceMappingURL=embeddings_xenova.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings_xenova.d.ts","sourceRoot":"","sources":["../../src/core/embeddings_xenova.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,QAAQ,EAAa,MAAM,iBAAiB,CAAC;AAE1E,wBAAsB,oBAAoB,CACxC,SAAS,GAAE,MAAmC,GAC7C,OAAO,CAAC,QAAQ,CAAC,CAkCnB"}
@@ -0,0 +1,28 @@
1
+ // Xenova/transformers-backed embedder. Kept in its own module so the heavy
2
+ // dynamic import only happens when embeddings are actually enabled.
3
+ import { EMBEDDING_DIM, normalize } from './embeddings.js';
4
+ export async function createXenovaEmbedder(modelName = 'Xenova/bge-small-en-v1.5') {
5
+ // Dynamic import keeps startup lean; transformers pulls in ~30MB of deps.
6
+ const { pipeline } = (await import('@xenova/transformers'));
7
+ const extract = await pipeline('feature-extraction', modelName);
8
+ return {
9
+ name: modelName,
10
+ dim: EMBEDDING_DIM,
11
+ async embed(texts) {
12
+ if (texts.length === 0)
13
+ return [];
14
+ // Mean-pool + L2-normalize to get a single vector per input.
15
+ const out = await extract(texts, { pooling: 'mean', normalize: true });
16
+ const dim = out.dims[out.dims.length - 1] ?? EMBEDDING_DIM;
17
+ const batch = texts.length;
18
+ const flat = out.data;
19
+ const vectors = [];
20
+ for (let i = 0; i < batch; i += 1) {
21
+ const slice = flat.subarray(i * dim, (i + 1) * dim);
22
+ vectors.push(normalize(new Float32Array(slice)));
23
+ }
24
+ return vectors;
25
+ },
26
+ };
27
+ }
28
+ //# sourceMappingURL=embeddings_xenova.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings_xenova.js","sourceRoot":"","sources":["../../src/core/embeddings_xenova.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,oEAAoE;AAEpE,OAAO,EAAE,aAAa,EAAiB,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,YAAoB,0BAA0B;IAE9C,0EAA0E;IAC1E,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAUzD,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC;IAEhE,OAAO;QACL,IAAI,EAAE,SAAS;QACf,GAAG,EAAE,aAAa;QAClB,KAAK,CAAC,KAAK,CAAC,KAAe;YACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAClC,6DAA6D;YAC7D,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,aAAa,CAAC;YAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;YACtB,MAAM,OAAO,GAAmB,EAAE,CAAC;YACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;gBACpD,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnD,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { type ExtractedObservation } from './schema.js';
3
+ export interface LlmCaller {
4
+ complete(args: {
5
+ model: string;
6
+ system: string;
7
+ user: string;
8
+ }): Promise<{
9
+ text: string;
10
+ inputTokens: number;
11
+ outputTokens: number;
12
+ }>;
13
+ }
14
+ export declare function anthropicCaller(client: Anthropic): LlmCaller;
15
+ export interface ExtractOptions {
16
+ model: string;
17
+ maxObservations: number;
18
+ maxRetries: number;
19
+ }
20
+ export interface ExtractionOutcome {
21
+ observations: ExtractedObservation[];
22
+ tokensSpent: number;
23
+ retries: number;
24
+ }
25
+ export declare function extract(transcript: string, caller: LlmCaller, options: ExtractOptions): Promise<ExtractionOutcome>;
26
+ export declare function estimateTokensSaved(transcriptTokens: number, observation: ExtractedObservation, totalObservations: number): number;
27
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/core/extractor.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAGL,KAAK,oBAAoB,EAE1B,MAAM,aAAa,CAAC;AAKrB,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE;QACb,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAmB5D;AAyDD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,OAAO,CAC3B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAsD5B;AAMD,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,oBAAoB,EACjC,iBAAiB,EAAE,MAAM,GACxB,MAAM,CAKR"}
@@ -0,0 +1,133 @@
1
+ import { ExtractorResponseSchema, ObservationKind, } from './schema.js';
2
+ import { countTokens } from './tokens.js';
3
+ export function anthropicCaller(client) {
4
+ return {
5
+ async complete({ model, system, user }) {
6
+ const resp = await client.messages.create({
7
+ model,
8
+ max_tokens: 4096,
9
+ system,
10
+ messages: [{ role: 'user', content: user }],
11
+ });
12
+ const text = resp.content
13
+ .map((b) => (b.type === 'text' ? b.text : ''))
14
+ .join('');
15
+ return {
16
+ text,
17
+ inputTokens: resp.usage.input_tokens,
18
+ outputTokens: resp.usage.output_tokens,
19
+ };
20
+ },
21
+ };
22
+ }
23
+ const JSON_SCHEMA_HINT = {
24
+ type: 'object',
25
+ required: ['observations'],
26
+ properties: {
27
+ observations: {
28
+ type: 'array',
29
+ items: {
30
+ type: 'object',
31
+ required: ['kind', 'title', 'body'],
32
+ properties: {
33
+ kind: { enum: ObservationKind.options },
34
+ title: { type: 'string', maxLength: 80 },
35
+ body: { type: 'string' },
36
+ files: { type: 'array', items: { type: 'string' } },
37
+ tags: { type: 'array', items: { type: 'string' } },
38
+ },
39
+ },
40
+ },
41
+ },
42
+ };
43
+ const SYSTEM_PROMPT = `You are Somtum's session extractor.
44
+
45
+ Read the provided Claude Code session transcript and return durable observations:
46
+
47
+ EXTRACT:
48
+ - Decisions with rationale ("we use pnpm because X")
49
+ - Learnings ("library Y's retry logic breaks on Z")
50
+ - Bug fixes with root cause
51
+ - Commands that worked after trial-and-error
52
+ - File summaries for large files read in full
53
+
54
+ REJECT:
55
+ - Successful reads of unchanged files
56
+ - Code written then immediately discarded
57
+ - Conversational filler
58
+
59
+ OUTPUT: a single JSON object matching this schema exactly. No prose, no markdown fences.
60
+
61
+ ${JSON.stringify(JSON_SCHEMA_HINT, null, 2)}
62
+
63
+ Rules:
64
+ - title must be <= 80 chars.
65
+ - kind must be one of: ${ObservationKind.options.join(', ')}.
66
+ - If nothing is worth capturing, return {"observations": []}.
67
+ - Never echo API keys, tokens, or secrets — we redact after, but don't write them.`;
68
+ function extractJsonBlob(text) {
69
+ const trimmed = text.trim();
70
+ // Models occasionally wrap JSON in ```json … ``` fences despite instructions.
71
+ const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
72
+ if (fence?.[1])
73
+ return fence[1].trim();
74
+ return trimmed;
75
+ }
76
+ export async function extract(transcript, caller, options) {
77
+ let attempt = 0;
78
+ let lastError;
79
+ let totalInput = 0;
80
+ let totalOutput = 0;
81
+ while (attempt <= options.maxRetries) {
82
+ const userPrompt = attempt === 0
83
+ ? `Transcript:\n\n${transcript}`
84
+ : `Transcript:\n\n${transcript}\n\nPrevious attempt produced output that failed schema validation with error:\n${lastError}\n\nReturn valid JSON only.`;
85
+ const { text, inputTokens, outputTokens } = await caller.complete({
86
+ model: options.model,
87
+ system: SYSTEM_PROMPT,
88
+ user: userPrompt,
89
+ });
90
+ totalInput += inputTokens;
91
+ totalOutput += outputTokens;
92
+ const blob = extractJsonBlob(text);
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(blob);
96
+ }
97
+ catch (err) {
98
+ lastError = `JSON.parse failed: ${err.message}`;
99
+ attempt += 1;
100
+ continue;
101
+ }
102
+ const validation = ExtractorResponseSchema.safeParse(parsed);
103
+ if (!validation.success) {
104
+ lastError = validation.error.message;
105
+ attempt += 1;
106
+ continue;
107
+ }
108
+ const observations = validation.data.observations.slice(0, options.maxObservations);
109
+ return {
110
+ observations,
111
+ tokensSpent: totalInput + totalOutput,
112
+ retries: attempt,
113
+ };
114
+ }
115
+ console.warn(`[somtum] extractor gave up after ${options.maxRetries + 1} attempts; last error: ${lastError}`);
116
+ return {
117
+ observations: [],
118
+ tokensSpent: totalInput + totalOutput,
119
+ retries: attempt,
120
+ };
121
+ }
122
+ // Rough savings estimate: an observation replaces its proportional share of
123
+ // the transcript (1/N where N is the total number of observations). If the
124
+ // observation is larger than its share, savings is clamped to zero — better
125
+ // to undercount than to overclaim.
126
+ export function estimateTokensSaved(transcriptTokens, observation, totalObservations) {
127
+ if (totalObservations <= 0)
128
+ return 0;
129
+ const share = Math.floor(transcriptTokens / totalObservations);
130
+ const obsTokens = countTokens(`${observation.title}\n${observation.body}`);
131
+ return Math.max(0, share - obsTokens);
132
+ }
133
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","sourceRoot":"","sources":["../../src/core/extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { DB } from './db.js';
2
+ import type { LlmCaller } from './extractor.js';
3
+ import { type FileFingerprint } from './schema.js';
4
+ export interface FileSummaryRow extends FileFingerprint {
5
+ }
6
+ export declare class FileFingerprintStore {
7
+ private readonly db;
8
+ constructor(db: DB);
9
+ get(projectId: string, path: string): FileFingerprint | null;
10
+ upsert(input: FileFingerprint): void;
11
+ }
12
+ export interface FileStat {
13
+ contentHash: string;
14
+ mtime: number;
15
+ tokens: number;
16
+ bytes: number;
17
+ }
18
+ export declare function statFile(path: string, opts: {
19
+ cwd: string;
20
+ }): FileStat | null;
21
+ export declare function matchesAnyGlob(path: string, globs: string[]): boolean;
22
+ export interface SummarizeOptions {
23
+ model: string;
24
+ caller: LlmCaller;
25
+ maxBytes?: number;
26
+ }
27
+ export interface SummaryOutcome {
28
+ summary: string;
29
+ tokensSpent: number;
30
+ }
31
+ export declare function summarizeFile(path: string, contents: string, opts: SummarizeOptions): Promise<SummaryOutcome>;
32
+ export declare function summaryHash(summary: string): string;
33
+ //# sourceMappingURL=file_summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file_summary.d.ts","sourceRoot":"","sources":["../../src/core/file_summary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,EAAE,KAAK,eAAe,EAAyB,MAAM,aAAa,CAAC;AAE1E,MAAM,WAAW,cAAe,SAAQ,eAAe;CAGtD;AAED,qBAAa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAEnC,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAgB5D,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;CAuBrC;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,GAAG,IAAI,CAkB7E;AA2BD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAErE;AAiBD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,cAAc,CAAC,CAiBzB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD"}