@jmtrin/opencode-kevin 0.1.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.
Files changed (39) hide show
  1. package/README.md +248 -0
  2. package/dist/migrations/001_initial.sql +92 -0
  3. package/dist/migrations/002_indexes.sql +14 -0
  4. package/dist/plugin/ContextInjector.d.ts +27 -0
  5. package/dist/plugin/ContextInjector.js +131 -0
  6. package/dist/plugin/ContextInjector.js.map +1 -0
  7. package/dist/plugin/MemoryService.d.ts +49 -0
  8. package/dist/plugin/MemoryService.js +228 -0
  9. package/dist/plugin/MemoryService.js.map +1 -0
  10. package/dist/plugin/Migrate.d.ts +13 -0
  11. package/dist/plugin/Migrate.js +54 -0
  12. package/dist/plugin/Migrate.js.map +1 -0
  13. package/dist/plugin/Reflector.d.ts +30 -0
  14. package/dist/plugin/Reflector.js +117 -0
  15. package/dist/plugin/Reflector.js.map +1 -0
  16. package/dist/plugin/Retrospective.d.ts +13 -0
  17. package/dist/plugin/Retrospective.js +77 -0
  18. package/dist/plugin/Retrospective.js.map +1 -0
  19. package/dist/plugin/Store.d.ts +14 -0
  20. package/dist/plugin/Store.js +36 -0
  21. package/dist/plugin/Store.js.map +1 -0
  22. package/dist/plugin/ToolCallObserver.d.ts +28 -0
  23. package/dist/plugin/ToolCallObserver.js +150 -0
  24. package/dist/plugin/ToolCallObserver.js.map +1 -0
  25. package/dist/plugin/index.d.ts +9 -0
  26. package/dist/plugin/index.js +340 -0
  27. package/dist/plugin/index.js.map +1 -0
  28. package/dist/plugin/redact.d.ts +1 -0
  29. package/dist/plugin/redact.js +12 -0
  30. package/dist/plugin/redact.js.map +1 -0
  31. package/dist/plugin/sqlite-adapter.d.ts +12 -0
  32. package/dist/plugin/sqlite-adapter.js +27 -0
  33. package/dist/plugin/sqlite-adapter.js.map +1 -0
  34. package/dist/plugin/uuid.d.ts +1 -0
  35. package/dist/plugin/uuid.js +51 -0
  36. package/dist/plugin/uuid.js.map +1 -0
  37. package/migrations/001_initial.sql +92 -0
  38. package/migrations/002_indexes.sql +14 -0
  39. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # Kevin
2
+
3
+ > Observe and learn: the learning layer OpenCode was missing.
4
+
5
+ Kevin is an [OpenCode](https://opencode.ai) plugin that **observes** every agent tool call, **learns** from failures by generating lessons, and **shares** what it learned proactively in future sessions. It does not plan, orchestrate, or compete with the plugin ecosystem. It only learns.
6
+
7
+ - **Local-first**: SQLite + FTS5, no external services, no network calls.
8
+ - **Global memory**: a single `~/.opencode-kevin/kevin.db` shared across all your projects (WAL mode → safe for concurrent sessions). No per-project folders.
9
+ - **Standalone**: works without any other plugin. With the ecosystem, it learns more richly.
10
+
11
+ ---
12
+
13
+ ## Installation
14
+
15
+ ### 1. Declare the plugin
16
+
17
+ Add Kevin to your OpenCode config. For **all projects** (global):
18
+
19
+ ```jsonc
20
+ // ~/.config/opencode/opencode.jsonc
21
+ {
22
+ "$schema": "https://opencode.ai/config.json",
23
+ "plugin": [
24
+ "@jmtrin/opencode-kevin@latest"
25
+ ]
26
+ }
27
+ ```
28
+
29
+ For a **single project**, put the same `plugin` array in `./opencode.json` or `.opencode/opencode.json` at the project root.
30
+
31
+ ### 2. Restart OpenCode
32
+
33
+ Config is loaded once at startup and is **not hot-reloaded** — quit and reopen OpenCode after editing. On start, OpenCode resolves the npm spec, caches the plugin in `~/.cache/opencode/packages/@jmtrin/opencode-kevin/`, and exposes five tools: `kevin_save`, `kevin_query`, `kevin_recall`, `kevin_status`, `kevin_retrospective`.
34
+
35
+ ### 3. Where data lives
36
+
37
+ Kevin stores everything in a single **global, shared** location under your home directory — no per-project `.kevin/` folders:
38
+
39
+ | Path | Content |
40
+ |---|---|
41
+ | `~/.opencode-kevin/kevin.db` | SQLite database (memories, tool calls, retrospectives). WAL mode → safe for concurrent OpenCode sessions across projects. |
42
+ | `~/.opencode-kevin/retrospectives/<session>.md` | Per-session retrospective markdown. |
43
+
44
+ Migrations run automatically on startup.
45
+
46
+ ### Requirements
47
+
48
+ - Node.js >= 20
49
+ - OpenCode with plugin support (`@opencode-ai/plugin` >= 1.17)
50
+
51
+ ### Verification
52
+
53
+ ```bash
54
+ npm run verify
55
+ ```
56
+
57
+ Checks Node version, SQLite, migration, MemoryService save/query, Reflector, ContextInjector, and TypeScript strict mode.
58
+
59
+ ### Advanced (optional)
60
+
61
+ Override defaults via the plugin tuple form:
62
+
63
+ ```jsonc
64
+ {
65
+ "plugin": [
66
+ ["@jmtrin/opencode-kevin", {
67
+ "dbPath": "/custom/path/kevin.db",
68
+ "retrospectivesDir": "/custom/path/retrospectives",
69
+ "throttleMs": 120000
70
+ }]
71
+ ]
72
+ }
73
+ ```
74
+
75
+ Use `:memory:` for `dbPath` in tests.
76
+
77
+ ---
78
+
79
+ ## Cycle: Observe → Learn → Share
80
+
81
+ ```
82
+ Tool call (success or failure)
83
+
84
+
85
+ ┌─────────────────┐
86
+ │ OBSERVE │ ToolCallObserver records every call
87
+ │ ToolCallObserver│ (tool, args redacted, success, duration, error_type)
88
+ └────────┬────────┘
89
+ │ on failure
90
+
91
+ ┌─────────────────┐
92
+ │ LEARN │ Reflector generates a heuristic lesson
93
+ │ Reflector │ redacts paths/secrets, throttled 1/min,
94
+ └────────┬────────┘ persists type:error memory
95
+
96
+
97
+ ┌─────────────────┐
98
+ │ SHARE │ ContextInjector injects relevant lessons
99
+ │ ContextInjector │ pre-prompt (1500 tokens) and on compacting (2000 tokens)
100
+ └────────┬────────┘
101
+ │ session.idle
102
+
103
+ ┌─────────────────┐
104
+ │ RETROSPECTIVE │ Retrospective generates ~/.opencode-kevin/retrospectives/<session>.md
105
+ └─────────────────┘ with summary of failures and lessons
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Tools
111
+
112
+ Kevin exposes 5 tools callable by the agent:
113
+
114
+ ### `kevin_save`
115
+
116
+ Saves an explicit memory.
117
+
118
+ ```
119
+ kevin_save({ type: "decision", content: "We use vitest for tests", scope: "project" })
120
+ // → { "id": "0195a3b2-..." }
121
+ ```
122
+
123
+ `type`: `error` | `pattern` | `decision` | `context`. `scope`: `project` (persists) | `session` (TTL 24h).
124
+
125
+ ### `kevin_query`
126
+
127
+ Searches memories by text (FTS5 + bm25).
128
+
129
+ ```
130
+ kevin_query({ query: "typecheck", type: "error", limit: 5 })
131
+ // → [{ "id": "...", "type": "error", "content": "...", "scope": "project" }, ...]
132
+ ```
133
+
134
+ ### `kevin_recall`
135
+
136
+ Retrieves relevant memories (greedy fill by relevance). Without `query`, returns all memories in scope.
137
+
138
+ ```
139
+ kevin_recall({ query: "auth", limit: 3 })
140
+ // → [{ "id": "...", "type": "decision", ... }, ...]
141
+ ```
142
+
143
+ ### `kevin_status`
144
+
145
+ Global counts.
146
+
147
+ ```
148
+ kevin_status({})
149
+ // → { "memories": 42, "tool_calls": 318, "retrospectives": 7 }
150
+ ```
151
+
152
+ ### `kevin_retrospective`
153
+
154
+ Generates a retrospective for a session (uses current session if `session_id` is omitted).
155
+
156
+ ```
157
+ kevin_retrospective({ session_id: "sess-abc" })
158
+ // → { "file_path": "~/.opencode-kevin/retrospectives/sess-abc.md" }
159
+ // or → { "message": "No failures in session sess-abc." }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Hooks
165
+
166
+ Kevin subscribes to 6 OpenCode hooks:
167
+
168
+ | Hook | What Kevin does |
169
+ |---|---|
170
+ | `tool.execute.before` | Records tool call start (callID + redacted args) |
171
+ | `tool.execute.after` | Records result; on failure → Reflector.invoke async (throttled) |
172
+ | `experimental.chat.system.transform` | Injects relevant lessons in `<kevin-context>` (1500 tokens) |
173
+ | `experimental.session.compacting` | Re-injects lessons in `<kevin-memory>` after compacting (2000 tokens) |
174
+ | `event` (`session.created`) | Captures current `sessionID` |
175
+ | `event` (`session.idle`) | Generates retrospective.md for the session |
176
+
177
+ **Redaction**: absolute paths (`C:\Users\...`, `/home/...`) → `<path>` and secrets (`API_KEY=`, `Bearer`, `token`) → `<redacted>` before persisting anything.
178
+
179
+ **Throttle**: Reflector generates at most 1 lesson per minute (configurable via `throttleMs`).
180
+
181
+ **Truncation**: content > 4KB keeps the lesson searchable; only the additional context is truncated (`metadata.truncated = true`).
182
+
183
+ ---
184
+
185
+ ## Configuration
186
+
187
+ Kevin accepts options via the plugin's tuple form (see Installation → Advanced). Programmatic defaults:
188
+
189
+ ```ts
190
+ import { KevinPlugin } from "@jmtrin/opencode-kevin";
191
+
192
+ // defaults
193
+ KevinPlugin(input, {
194
+ dbPath: "~/.opencode-kevin/kevin.db", // or ":memory:" for tests
195
+ migrationsDir: "<package>/dist/migrations", // resolved automatically
196
+ retrospectivesDir: "~/.opencode-kevin/retrospectives",
197
+ throttleMs: 60_000,
198
+ });
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Development
204
+
205
+ ```bash
206
+ git clone https://github.com/jmtrin/opencode-kevin.git
207
+ cd opencode-kevin
208
+ npm install
209
+ npm run typecheck # tsc --noEmit (strict)
210
+ npm run lint # biome check .
211
+ npm test # vitest run (unit + integration + e2e)
212
+ npm run verify # post-install verification
213
+ ```
214
+
215
+ ### Publishing (maintainer)
216
+
217
+ ```bash
218
+ npm login # as the jmtrin account that owns the @jmtrin scope
219
+ npm publish --access public
220
+ ```
221
+
222
+ `prepublishOnly` runs `npm run build` (tsc + copy migrations) automatically. The `files` field ships only `dist/plugin`, `dist/migrations`, and `migrations`. `dist/` is gitignored and rebuilt on publish.
223
+
224
+ ### Structure
225
+
226
+ ```
227
+ plugin/
228
+ index.ts # Entry point: KevinPlugin
229
+ Store.ts # Wrapper better-sqlite3 (WAL, FK, transactions)
230
+ Migrate.ts # Idempotent migrations
231
+ MemoryService.ts # save/query/getRelevant (FTS5 + bm25)
232
+ ToolCallObserver.ts # onBefore/onAfter + redact + inferErrorType
233
+ Reflector.ts # Heuristic lessons + throttle + truncation
234
+ ContextInjector.ts # deriveQuery + pre-prompt/compacting injection
235
+ Retrospective.ts # Generates retrospective.md + table insert
236
+ migrations/
237
+ 001_initial.sql # schema: memories, tool_calls, retrospectives
238
+ 002_indexes.sql # FTS5 + indexes
239
+ tests/{unit,integration,e2e}/
240
+ scripts/
241
+ copy-migrations.mjs # build step: copies *.sql to dist/migrations
242
+ verify-install.ts # npm run verify
243
+ ```
244
+
245
+ ---
246
+ ## License
247
+
248
+ MIT
@@ -0,0 +1,92 @@
1
+ -- ============================================================
2
+ -- Kevin 0.1.0 — Schema inicial
3
+ -- ============================================================
4
+
5
+ -- Tabla de versiones para migraciones
6
+ CREATE TABLE IF NOT EXISTS schema_version (
7
+ version TEXT PRIMARY KEY,
8
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
9
+ );
10
+
11
+ -- ============================================================
12
+ -- memories: lecciones aprendidas
13
+ -- ============================================================
14
+ CREATE TABLE IF NOT EXISTS memories (
15
+ id TEXT PRIMARY KEY,
16
+ type TEXT NOT NULL CHECK(type IN ('error', 'pattern', 'decision', 'context')),
17
+ content TEXT NOT NULL,
18
+ scope TEXT NOT NULL DEFAULT 'project' CHECK(scope IN ('project', 'session')),
19
+ relevance_score REAL DEFAULT 0.5,
20
+ source_tool TEXT,
21
+ source_session TEXT,
22
+ metadata TEXT,
23
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
24
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
25
+ expires_at TEXT
26
+ );
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
29
+ CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
30
+ CREATE INDEX IF NOT EXISTS idx_memories_relevance ON memories(relevance_score DESC);
31
+ CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
32
+
33
+ -- FTS5: búsqueda full-text con remoción de diacríticos (mejor para español)
34
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
35
+ content,
36
+ content='memories',
37
+ tokenize='unicode61 remove_diacritics 1'
38
+ );
39
+
40
+ -- Triggers para mantener FTS5 sincronizado (FTS5 external-content se indexa por rowid)
41
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
42
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
43
+ END;
44
+
45
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
46
+ INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
47
+ END;
48
+
49
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
50
+ INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
51
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
52
+ END;
53
+
54
+ -- ============================================================
55
+ -- tool_calls: observación de tool calls del agente
56
+ -- ============================================================
57
+ CREATE TABLE IF NOT EXISTS tool_calls (
58
+ id TEXT PRIMARY KEY,
59
+ session_id TEXT NOT NULL,
60
+ ts TEXT NOT NULL DEFAULT (datetime('now')),
61
+ tool TEXT NOT NULL,
62
+ args_summary TEXT,
63
+ success INTEGER NOT NULL CHECK(success IN (0,1)),
64
+ duration_ms INTEGER,
65
+ agent TEXT,
66
+ error_type TEXT,
67
+ metadata TEXT
68
+ );
69
+
70
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);
71
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool);
72
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_ts ON tool_calls(ts);
73
+ CREATE INDEX IF NOT EXISTS idx_tool_calls_success ON tool_calls(success);
74
+
75
+ -- ============================================================
76
+ -- retrospectives: resúmenes de sesión
77
+ -- ============================================================
78
+ CREATE TABLE IF NOT EXISTS retrospectives (
79
+ id TEXT PRIMARY KEY,
80
+ session_id TEXT NOT NULL,
81
+ ts TEXT NOT NULL DEFAULT (datetime('now')),
82
+ failure_count INTEGER DEFAULT 0,
83
+ success_count INTEGER DEFAULT 0,
84
+ lessons_count INTEGER DEFAULT 0,
85
+ file_path TEXT,
86
+ metadata TEXT
87
+ );
88
+
89
+ -- ============================================================
90
+ -- Seed: versión inicial
91
+ -- ============================================================
92
+ INSERT OR IGNORE INTO schema_version (version) VALUES ('001');
@@ -0,0 +1,14 @@
1
+ -- ============================================================
2
+ -- Kevin 0.1.1 — Migration 002: indexes adicionales
3
+ -- ============================================================
4
+
5
+ -- F#29: uniqueness on retrospectives.session_id
6
+ -- Prevents duplicate retrospective rows under concurrent session.idle events.
7
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_retrospectives_session
8
+ ON retrospectives(session_id);
9
+
10
+ -- F#31: index on memories.expires_at
11
+ -- Every query/queryRelevant/loadAll filters WHERE (expires_at IS NULL OR expires_at > datetime('now')).
12
+ -- Without this index the filter is a linear scan on large tables.
13
+ CREATE INDEX IF NOT EXISTS idx_memories_expires
14
+ ON memories(expires_at);
@@ -0,0 +1,27 @@
1
+ import type { MemoryService } from "./MemoryService.js";
2
+ export interface ChatMessage {
3
+ role: string;
4
+ content: string;
5
+ }
6
+ export interface SystemTransformInput {
7
+ sessionID?: string;
8
+ messages: ChatMessage[];
9
+ }
10
+ export interface SystemTransformOutput {
11
+ system: string[];
12
+ }
13
+ export interface CompactingInput {
14
+ sessionID: string;
15
+ messages: ChatMessage[];
16
+ }
17
+ export interface CompactingOutput {
18
+ context: string[];
19
+ }
20
+ export declare class ContextInjector {
21
+ private memoryService;
22
+ constructor(memoryService: MemoryService);
23
+ deriveQuery(messages: ChatMessage[]): string;
24
+ onSystemTransform(input: SystemTransformInput, output: SystemTransformOutput): void;
25
+ onCompacting(input: CompactingInput, output: CompactingOutput): void;
26
+ private formatMemories;
27
+ }
@@ -0,0 +1,131 @@
1
+ const SYSTEM_TRANSFORM_TOKENS = 1500;
2
+ const COMPACTING_TOKENS = 2000;
3
+ const STOP_WORDS = new Set([
4
+ "a",
5
+ "an",
6
+ "and",
7
+ "are",
8
+ "at",
9
+ "be",
10
+ "been",
11
+ "but",
12
+ "by",
13
+ "did",
14
+ "do",
15
+ "does",
16
+ "el",
17
+ "eso",
18
+ "for",
19
+ "how",
20
+ "i",
21
+ "if",
22
+ "in",
23
+ "is",
24
+ "it",
25
+ "la",
26
+ "las",
27
+ "los",
28
+ "mi",
29
+ "my",
30
+ "o",
31
+ "of",
32
+ "on",
33
+ "or",
34
+ "para",
35
+ "por",
36
+ "que",
37
+ "she",
38
+ "su",
39
+ "that",
40
+ "the",
41
+ "this",
42
+ "to",
43
+ "tu",
44
+ "un",
45
+ "una",
46
+ "we",
47
+ "were",
48
+ "what",
49
+ "when",
50
+ "where",
51
+ "which",
52
+ "who",
53
+ "why",
54
+ "with",
55
+ "y",
56
+ "you",
57
+ "como",
58
+ "con",
59
+ "de",
60
+ "en",
61
+ "he",
62
+ "they",
63
+ "was",
64
+ "sin",
65
+ ]);
66
+ function isWordChar(ch) {
67
+ return /[a-z0-9áéíóúüñ]/i.test(ch);
68
+ }
69
+ export class ContextInjector {
70
+ memoryService;
71
+ constructor(memoryService) {
72
+ this.memoryService = memoryService;
73
+ }
74
+ deriveQuery(messages) {
75
+ let lastUserContent = "";
76
+ for (let i = messages.length - 1; i >= 0; i--) {
77
+ if (messages[i].role === "user") {
78
+ lastUserContent = messages[i].content;
79
+ break;
80
+ }
81
+ }
82
+ if (!lastUserContent)
83
+ return "";
84
+ const tokens = lastUserContent
85
+ .toLowerCase()
86
+ .split(/\s+/)
87
+ .map((t) => {
88
+ let out = "";
89
+ for (const ch of t) {
90
+ if (isWordChar(ch))
91
+ out += ch;
92
+ }
93
+ return out;
94
+ })
95
+ .filter((t) => t.length > 0 && !STOP_WORDS.has(t));
96
+ return tokens.join(" ");
97
+ }
98
+ onSystemTransform(input, output) {
99
+ const query = this.deriveQuery(input.messages);
100
+ if (!query)
101
+ return;
102
+ const memories = this.memoryService.getRelevant({
103
+ query,
104
+ maxTokens: SYSTEM_TRANSFORM_TOKENS,
105
+ });
106
+ if (memories.length === 0)
107
+ return;
108
+ output.system.push(this.formatMemories(memories, "context"));
109
+ }
110
+ onCompacting(input, output) {
111
+ const query = this.deriveQuery(input.messages);
112
+ if (!query)
113
+ return;
114
+ const memories = this.memoryService.getRelevant({
115
+ query,
116
+ maxTokens: COMPACTING_TOKENS,
117
+ });
118
+ if (memories.length === 0)
119
+ return;
120
+ output.context.push(this.formatMemories(memories, "memory"));
121
+ }
122
+ formatMemories(memories, format) {
123
+ const lines = memories.map((m) => `[${m.type}] ${m.content}`);
124
+ const body = lines.join("\n");
125
+ if (format === "context") {
126
+ return `<kevin-context>Lecciones relevantes:\n${body}\n</kevin-context>`;
127
+ }
128
+ return `<kevin-memory>\n${body}\n</kevin-memory>`;
129
+ }
130
+ }
131
+ //# sourceMappingURL=ContextInjector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContextInjector.js","sourceRoot":"","sources":["../../plugin/ContextInjector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { Store } from "./Store.js";
2
+ export type MemoryType = "error" | "pattern" | "decision" | "context";
3
+ export type MemoryScope = "project" | "session";
4
+ export interface Memory {
5
+ id: string;
6
+ type: MemoryType;
7
+ content: string;
8
+ scope: MemoryScope;
9
+ relevanceScore: number;
10
+ sourceTool?: string | null;
11
+ sourceSession?: string | null;
12
+ metadata?: Record<string, unknown> | null;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ expiresAt?: string | null;
16
+ }
17
+ export interface SaveInput {
18
+ type: MemoryType;
19
+ content: string;
20
+ scope?: MemoryScope;
21
+ relevanceScore?: number;
22
+ sourceTool?: string;
23
+ sourceSession?: string;
24
+ metadata?: Record<string, unknown>;
25
+ expiresAt?: string;
26
+ }
27
+ export interface QueryInput {
28
+ text: string;
29
+ type?: string;
30
+ scope?: MemoryScope | "all";
31
+ limit?: number;
32
+ }
33
+ export interface GetRelevantInput {
34
+ query?: string;
35
+ maxTokens?: number;
36
+ scope?: MemoryScope | "all";
37
+ }
38
+ export declare class MemoryService {
39
+ private store;
40
+ constructor(store: Store);
41
+ save(input: SaveInput): string;
42
+ getById(id: string): Memory | null;
43
+ update(id: string, fields: Partial<Memory>): void;
44
+ delete(id: string): void;
45
+ query(input: QueryInput): Memory[];
46
+ private loadAll;
47
+ private queryRelevant;
48
+ getRelevant(input: GetRelevantInput): Memory[];
49
+ }