agentcache 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) 2024 a21.ai
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,193 @@
1
+ # AgentCache
2
+
3
+ **Knowledge cache for AI coding agents** — learns how you work, remembers across sessions, works everywhere.
4
+
5
+ AgentCache observes your coding sessions and compiles reusable knowledge (rules, lessons, architectural decisions, context) into a local database. Every future session — regardless of IDE or LLM — gets the benefit of everything you've learned before.
6
+
7
+ ## Why
8
+
9
+ Every AI coding session starts from zero. The agent doesn't know your team's conventions, past mistakes, or architectural decisions. You repeat yourself. Bugs recur. Context is lost.
10
+
11
+ AgentCache fixes this. It's a persistent knowledge layer that:
12
+ - **Learns** rules, lessons, and decisions from your sessions
13
+ - **Injects** relevant knowledge at the start of every new session
14
+ - **Works everywhere** — any IDE, any LLM, simultaneously
15
+ - **Stays local** — SQLite database on your machine, nothing leaves your disk
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install -g agentcache
21
+ ```
22
+
23
+ That's it. No `init`, no per-project setup, no config files.
24
+
25
+ On install, AgentCache automatically:
26
+ 1. Creates `~/.loop/loop.db` (your knowledge store)
27
+ 2. Detects installed IDEs (Claude Code, Cursor, Roo Code, Windsurf, Continue, Codex)
28
+ 3. Registers itself as an MCP server in each
29
+ 4. Sets up Claude Code hooks for automatic transcript recovery
30
+
31
+ ## How It Works
32
+
33
+ ```
34
+ ┌─────────────────────────────────────────────────────────────────┐
35
+ │ Your Machine │
36
+ │ │
37
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
38
+ │ │ Claude │ │ Cursor │ │ Roo │ │ Codex │ ... │
39
+ │ │ Code │ │ │ │ Code │ │ │ │
40
+ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
41
+ │ │ │ │ │ │
42
+ │ └──────────────┴──────────────┴──────────────┘ │
43
+ │ │ │
44
+ │ MCP Protocol (stdio) │
45
+ │ │ │
46
+ │ ┌────────────┴────────────┐ │
47
+ │ │ AgentCache MCP Server │ │
48
+ │ │ (agentcache serve) │ │
49
+ │ └────────────┬────────────┘ │
50
+ │ │ │
51
+ │ ┌─────────┴─────────┐ │
52
+ │ │ ~/.loop/loop.db │ │
53
+ │ │ (SQLite + WAL) │ │
54
+ │ └───────────────────┘ │
55
+ └─────────────────────────────────────────────────────────────────┘
56
+ ```
57
+
58
+ ### The Cycle
59
+
60
+ 1. **Session starts** — agent calls `loop_inject_context` → gets compiled rules, lessons, decisions
61
+ 2. **During session** — agent calls `loop_compile_submit` incrementally as it learns things
62
+ 3. **Session ends** — knowledge is already saved. If agent didn't submit (abrupt exit), transcript recovery handles it next session.
63
+
64
+ ### Knowledge Types
65
+
66
+ | Type | Scope | Example |
67
+ |------|-------|---------|
68
+ | **Rule** | Global | "Always use snake_case for database columns" |
69
+ | **Lesson** | Global | "Don't mock the database in integration tests — we got burned when mocked tests passed but prod migration failed" |
70
+ | **Decision** | Project | "Using Drizzle ORM over Prisma because we need raw SQL escape hatches" |
71
+ | **Context** | Project | "Currently migrating from REST to GraphQL, both coexist" |
72
+
73
+ Rules and lessons are **global** — they apply to all your projects. Decisions and context are **project-specific**.
74
+
75
+ ## MCP Tools
76
+
77
+ AgentCache exposes 8 tools via the Model Context Protocol:
78
+
79
+ | Tool | Purpose |
80
+ |------|---------|
81
+ | `loop_inject_context` | Load compiled knowledge at session start |
82
+ | `loop_compile_submit` | Submit observations incrementally during session |
83
+ | `loop_compile_cluster` | Resolve clustering when observations overlap existing knowledge |
84
+ | `loop_compile_extract` | Process queued transcripts from previous sessions |
85
+ | `loop_enforce` | Check tool calls against enforced policy rules |
86
+ | `loop_save_observation` | Save a permanent observation (USER authority, never auto-deprecated) |
87
+ | `loop_get_knowledge` | Query the knowledge database |
88
+ | `loop_deprecate_knowledge` | Mark knowledge as deprecated when it's no longer valid |
89
+
90
+ ## CLI Commands
91
+
92
+ ```bash
93
+ agentcache setup # Re-detect IDEs and register (runs automatically on install)
94
+ agentcache serve # Start MCP server (IDEs spawn this automatically)
95
+ agentcache status # Show knowledge stats for current project
96
+ agentcache compile-session # Queue transcript for compilation (Stop hook)
97
+ agentcache discover # Discover uncompiled transcripts (SessionStart hook)
98
+ agentcache enforce # Policy enforcement (PreToolUse hook)
99
+ ```
100
+
101
+ ## Design Principles
102
+
103
+ ### Zero Config
104
+
105
+ `npm install -g agentcache` is the only step. It detects your IDEs, registers itself, and starts working. No dotfiles in your project. No init commands. No config to maintain.
106
+
107
+ ### Universal
108
+
109
+ AgentCache uses MCP (Model Context Protocol) as its only interface. Any IDE that supports MCP works. Any LLM — Claude, GPT, Gemini, Qwen, Llama — can use the tools. No IDE-specific code paths. No LLM-specific logic.
110
+
111
+ ### Developer-Scoped
112
+
113
+ One database per developer (`~/.loop/loop.db`), not per project. Rules and lessons learned in one project benefit all your projects. Project-specific decisions stay scoped to their project.
114
+
115
+ ### Resilient to Abrupt Exits
116
+
117
+ Sessions can end without warning (crash, ctrl-c, network drop). AgentCache handles this through:
118
+ - **Incremental submission** — observations are saved as they happen, not batched at the end
119
+ - **Transcript recovery** — for Claude Code and Continue, transcripts persist on disk and are compiled next session
120
+ - **Pending queue in SQLite** — concurrent access is safe, nothing lost to race conditions
121
+
122
+ ### Anti-Bloat
123
+
124
+ AgentCache prevents knowledge from growing unbounded:
125
+ - **Confidence promotion** — observations need repeated confirmation before becoming high-confidence
126
+ - **Decay** — auto-compiled items not seen in 30 days get archived
127
+ - **Budget caps** — max 20 rules, 10 lessons, 10 decisions, 5 context items injected per session
128
+ - **Priority ranking** — USER authority first, then by confidence and recency
129
+
130
+ ## Supported IDEs
131
+
132
+ | IDE | MCP | Transcript Recovery | Hooks |
133
+ |-----|-----|--------------------|----|
134
+ | Claude Code | Yes | Full (JSONL) | Stop, SessionStart, PreToolUse |
135
+ | Cursor | Yes | Incremental only | — |
136
+ | Roo Code | Yes | Incremental only | — |
137
+ | Windsurf | Yes | Incremental only | — |
138
+ | Continue | Yes | Full (JSON) | — |
139
+ | Codex | Yes | Incremental only | — |
140
+
141
+ "Incremental only" means if the agent submits observations during the session, they're saved. If the session terminates before any submission, those observations are lost (no transcript access).
142
+
143
+ ## Data Storage
144
+
145
+ All data lives in `~/.loop/loop.db` (SQLite with WAL mode for concurrent access).
146
+
147
+ ```
148
+ ~/.loop/
149
+ └── loop.db # All knowledge, observations, sessions, pending queue
150
+ ```
151
+
152
+ No data leaves your machine. No network calls. No telemetry. No accounts.
153
+
154
+ ## How Knowledge Compiles
155
+
156
+ ```
157
+ Observations (raw)
158
+
159
+
160
+ Extract → Normalize → Canonicalize → Cluster → Detect Contradictions → Compile
161
+ │ │
162
+ │ "Always use ESLint" │
163
+ │ "Always use ESLint" ──→ deduplicated, confidence promoted │
164
+ │ "Use Prettier not ESLint" ──→ contradiction detected │
165
+ │ ▼
166
+ Knowledge Items (compiled)
167
+ - status: active/deprecated/superseded
168
+ - confidence: low/medium/high
169
+ - authority: AUTO/USER
170
+ ```
171
+
172
+ The compiler is the LLM itself (the agent in your session). AgentCache provides extraction prompts and the agent processes them — no separate API keys or LLM calls needed.
173
+
174
+ ## Project Identity
175
+
176
+ Projects are identified by a hash of their full filesystem path, not just the folder name. This means:
177
+ - `/work/api` and `/personal/api` are different projects
178
+ - Renaming a folder creates a new project identity
179
+ - Knowledge doesn't leak between same-named projects
180
+
181
+ ## Contributing
182
+
183
+ ```bash
184
+ git clone https://github.com/raghav-a21ai/loop-eng
185
+ cd loop-eng
186
+ npm install
187
+ npm run build
188
+ npm test
189
+ ```
190
+
191
+ ## License
192
+
193
+ MIT
@@ -0,0 +1,9 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ export {
8
+ __export
9
+ };
@@ -0,0 +1,466 @@
1
+ // src/storage/sqlite.ts
2
+ import Database from "better-sqlite3";
3
+ var SqliteKnowledgeRepository = class {
4
+ db;
5
+ constructor(dbPath) {
6
+ this.db = new Database(dbPath);
7
+ this.db.pragma("journal_mode = WAL");
8
+ this.db.pragma("foreign_keys = ON");
9
+ this.initSchema();
10
+ }
11
+ initSchema() {
12
+ this.db.exec(`
13
+ CREATE TABLE IF NOT EXISTS sessions (
14
+ id TEXT PRIMARY KEY,
15
+ project TEXT NOT NULL,
16
+ started_at INTEGER NOT NULL,
17
+ ended_at INTEGER NOT NULL,
18
+ git_branch TEXT,
19
+ git_commit TEXT,
20
+ provider TEXT NOT NULL DEFAULT 'claude',
21
+ model TEXT NOT NULL,
22
+ transcript_path TEXT NOT NULL,
23
+ observation_count INTEGER NOT NULL DEFAULT 0
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS observations (
27
+ id TEXT PRIMARY KEY,
28
+ session_id TEXT NOT NULL REFERENCES sessions(id),
29
+ timestamp INTEGER NOT NULL,
30
+ type TEXT NOT NULL,
31
+ content TEXT NOT NULL,
32
+ source_quote TEXT NOT NULL,
33
+ confidence TEXT NOT NULL,
34
+ project TEXT NOT NULL,
35
+ scope TEXT NOT NULL DEFAULT 'project'
36
+ );
37
+
38
+ CREATE TABLE IF NOT EXISTS knowledge_items (
39
+ id TEXT PRIMARY KEY,
40
+ canonical_hash TEXT NOT NULL UNIQUE,
41
+ type TEXT NOT NULL,
42
+ title TEXT NOT NULL,
43
+ content TEXT NOT NULL,
44
+ confidence TEXT NOT NULL DEFAULT 'low',
45
+ observation_count INTEGER NOT NULL DEFAULT 1,
46
+ authority TEXT NOT NULL DEFAULT 'AUTO',
47
+ status TEXT NOT NULL DEFAULT 'active',
48
+ superseded_by_id TEXT,
49
+ enforce INTEGER NOT NULL DEFAULT 0,
50
+ project TEXT NOT NULL,
51
+ scope TEXT NOT NULL DEFAULT 'project',
52
+ created_at INTEGER NOT NULL,
53
+ updated_at INTEGER NOT NULL,
54
+ last_seen_at INTEGER NOT NULL,
55
+ metadata TEXT NOT NULL DEFAULT '{}'
56
+ );
57
+
58
+ CREATE TABLE IF NOT EXISTS compile_runs (
59
+ id TEXT PRIMARY KEY,
60
+ project TEXT NOT NULL,
61
+ session_id TEXT REFERENCES sessions(id),
62
+ compiler_version TEXT NOT NULL,
63
+ prompt_versions TEXT NOT NULL DEFAULT '{}',
64
+ started_at INTEGER NOT NULL,
65
+ ended_at INTEGER NOT NULL,
66
+ duration_ms INTEGER NOT NULL,
67
+ observations_processed INTEGER NOT NULL DEFAULT 0,
68
+ knowledge_created INTEGER NOT NULL DEFAULT 0,
69
+ knowledge_reinforced INTEGER NOT NULL DEFAULT 0,
70
+ knowledge_deprecated INTEGER NOT NULL DEFAULT 0,
71
+ knowledge_superseded INTEGER NOT NULL DEFAULT 0,
72
+ knowledge_ignored INTEGER NOT NULL DEFAULT 0,
73
+ contradictions_detected INTEGER NOT NULL DEFAULT 0,
74
+ diagnostics TEXT
75
+ );
76
+
77
+ CREATE TABLE IF NOT EXISTS contradictions (
78
+ id TEXT PRIMARY KEY,
79
+ project TEXT NOT NULL,
80
+ item_a_id TEXT NOT NULL,
81
+ item_b_id TEXT NOT NULL,
82
+ topic TEXT NOT NULL,
83
+ description TEXT NOT NULL,
84
+ recommendation TEXT NOT NULL,
85
+ resolved INTEGER NOT NULL DEFAULT 0,
86
+ created_at INTEGER NOT NULL
87
+ );
88
+
89
+ CREATE TABLE IF NOT EXISTS pending_transcripts (
90
+ id TEXT PRIMARY KEY,
91
+ transcript_path TEXT NOT NULL UNIQUE,
92
+ project TEXT NOT NULL,
93
+ project_root TEXT NOT NULL,
94
+ provider TEXT NOT NULL,
95
+ queued_at INTEGER NOT NULL
96
+ );
97
+
98
+ CREATE INDEX IF NOT EXISTS idx_observations_project ON observations(project);
99
+ CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
100
+ CREATE INDEX IF NOT EXISTS idx_knowledge_project ON knowledge_items(project);
101
+ CREATE INDEX IF NOT EXISTS idx_knowledge_enforce ON knowledge_items(enforce);
102
+ CREATE INDEX IF NOT EXISTS idx_knowledge_status ON knowledge_items(status);
103
+ CREATE INDEX IF NOT EXISTS idx_ki_scope_status ON knowledge_items(scope, status);
104
+ CREATE INDEX IF NOT EXISTS idx_ki_project_status ON knowledge_items(project, status);
105
+ CREATE INDEX IF NOT EXISTS idx_compile_runs_project ON compile_runs(project);
106
+ CREATE INDEX IF NOT EXISTS idx_contradictions_project ON contradictions(project, resolved);
107
+ `);
108
+ }
109
+ saveSession(session) {
110
+ this.db.prepare(
111
+ `INSERT INTO sessions (id, project, started_at, ended_at, git_branch, git_commit, provider, model, transcript_path, observation_count)
112
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
113
+ ).run(
114
+ session.id,
115
+ session.project,
116
+ session.startedAt,
117
+ session.endedAt,
118
+ session.gitBranch,
119
+ session.gitCommit,
120
+ session.provider,
121
+ session.model,
122
+ session.transcriptPath,
123
+ session.observationCount
124
+ );
125
+ }
126
+ getSession(id) {
127
+ const row = this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(id);
128
+ if (!row) return null;
129
+ return this.mapSession(row);
130
+ }
131
+ getCompiledTranscriptPaths(project) {
132
+ const rows = this.db.prepare("SELECT transcript_path FROM sessions WHERE project = ? AND transcript_path != ''").all(project);
133
+ return rows.map((r) => r.transcript_path);
134
+ }
135
+ getAllCompiledTranscriptPaths() {
136
+ const rows = this.db.prepare("SELECT transcript_path FROM sessions WHERE transcript_path != ''").all();
137
+ return rows.map((r) => r.transcript_path);
138
+ }
139
+ saveObservation(obs) {
140
+ this.db.prepare(
141
+ `INSERT INTO observations (id, session_id, timestamp, type, content, source_quote, confidence, project, scope)
142
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
143
+ ).run(
144
+ obs.id,
145
+ obs.sessionId,
146
+ obs.timestamp,
147
+ obs.type,
148
+ obs.content,
149
+ obs.sourceQuote,
150
+ obs.confidence,
151
+ obs.project,
152
+ obs.scope
153
+ );
154
+ }
155
+ saveObservations(obs) {
156
+ const insert = this.db.prepare(
157
+ `INSERT INTO observations (id, session_id, timestamp, type, content, source_quote, confidence, project, scope)
158
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
159
+ );
160
+ const transaction = this.db.transaction((observations) => {
161
+ for (const o of observations) {
162
+ insert.run(
163
+ o.id,
164
+ o.sessionId,
165
+ o.timestamp,
166
+ o.type,
167
+ o.content,
168
+ o.sourceQuote,
169
+ o.confidence,
170
+ o.project,
171
+ o.scope
172
+ );
173
+ }
174
+ });
175
+ transaction(obs);
176
+ }
177
+ getObservations(project, since) {
178
+ let sql = "SELECT * FROM observations WHERE project = ?";
179
+ const params = [project];
180
+ if (since !== void 0) {
181
+ sql += " AND timestamp >= ?";
182
+ params.push(since);
183
+ }
184
+ const rows = this.db.prepare(sql).all(...params);
185
+ return rows.map((row) => this.mapObservation(row));
186
+ }
187
+ saveKnowledgeItem(item) {
188
+ this.db.prepare(
189
+ `INSERT INTO knowledge_items (id, canonical_hash, type, title, content, confidence, observation_count, authority, status, superseded_by_id, enforce, project, scope, created_at, updated_at, last_seen_at, metadata)
190
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
191
+ ).run(
192
+ item.id,
193
+ item.canonicalHash,
194
+ item.type,
195
+ item.title,
196
+ item.content,
197
+ item.confidence,
198
+ item.observationCount,
199
+ item.authority,
200
+ item.status,
201
+ item.supersededById ?? null,
202
+ item.enforce ? 1 : 0,
203
+ item.project,
204
+ item.scope,
205
+ item.createdAt,
206
+ item.updatedAt,
207
+ item.lastSeenAt,
208
+ JSON.stringify(item.metadata)
209
+ );
210
+ }
211
+ updateKnowledgeItem(id, patch) {
212
+ const fieldMap = {
213
+ canonicalHash: "canonical_hash",
214
+ type: "type",
215
+ title: "title",
216
+ content: "content",
217
+ confidence: "confidence",
218
+ observationCount: "observation_count",
219
+ authority: "authority",
220
+ status: "status",
221
+ supersededById: "superseded_by_id",
222
+ enforce: "enforce",
223
+ project: "project",
224
+ scope: "scope",
225
+ createdAt: "created_at",
226
+ updatedAt: "updated_at",
227
+ lastSeenAt: "last_seen_at",
228
+ metadata: "metadata"
229
+ };
230
+ const setClauses = [];
231
+ const values = [];
232
+ for (const [key, value] of Object.entries(patch)) {
233
+ const column = fieldMap[key];
234
+ if (!column) continue;
235
+ setClauses.push(`${column} = ?`);
236
+ if (key === "enforce") {
237
+ values.push(value ? 1 : 0);
238
+ } else if (key === "metadata") {
239
+ values.push(JSON.stringify(value));
240
+ } else {
241
+ values.push(value);
242
+ }
243
+ }
244
+ if (setClauses.length === 0) return;
245
+ values.push(id);
246
+ this.db.prepare(`UPDATE knowledge_items SET ${setClauses.join(", ")} WHERE id = ?`).run(...values);
247
+ }
248
+ getKnowledgeItems(project, filter) {
249
+ let sql = "SELECT * FROM knowledge_items WHERE project = ?";
250
+ const params = [project];
251
+ if (filter) {
252
+ if (filter.type !== void 0) {
253
+ sql += " AND type = ?";
254
+ params.push(filter.type);
255
+ }
256
+ if (filter.status !== void 0) {
257
+ sql += " AND status = ?";
258
+ params.push(filter.status);
259
+ }
260
+ if (filter.enforce !== void 0) {
261
+ sql += " AND enforce = ?";
262
+ params.push(filter.enforce ? 1 : 0);
263
+ }
264
+ if (filter.authority !== void 0) {
265
+ sql += " AND authority = ?";
266
+ params.push(filter.authority);
267
+ }
268
+ }
269
+ const rows = this.db.prepare(sql).all(...params);
270
+ return rows.map((row) => this.mapKnowledgeItem(row));
271
+ }
272
+ getKnowledgeItem(id) {
273
+ const row = this.db.prepare("SELECT * FROM knowledge_items WHERE id = ?").get(id);
274
+ if (!row) return null;
275
+ return this.mapKnowledgeItem(row);
276
+ }
277
+ getKnowledgeForContext(project) {
278
+ const decayThreshold = Date.now() - 30 * 24 * 60 * 60 * 1e3;
279
+ this.db.prepare(
280
+ `UPDATE knowledge_items SET status = 'archived', updated_at = ?
281
+ WHERE status = 'active' AND authority = 'AUTO' AND last_seen_at < ?`
282
+ ).run(Date.now(), decayThreshold);
283
+ const rows = this.db.prepare(
284
+ `SELECT * FROM knowledge_items WHERE status = 'active' AND (scope = 'global' OR project = ?)
285
+ ORDER BY
286
+ CASE authority WHEN 'USER' THEN 0 ELSE 1 END,
287
+ CASE confidence WHEN 'high' THEN 3 WHEN 'medium' THEN 2 ELSE 1 END DESC,
288
+ last_seen_at DESC
289
+ LIMIT 50`
290
+ ).all(project);
291
+ return rows.map((row) => this.mapKnowledgeItem(row));
292
+ }
293
+ getEnforcedRules(project) {
294
+ const rows = this.db.prepare(
295
+ `SELECT * FROM knowledge_items WHERE enforce = 1 AND status = 'active' AND (scope = 'global' OR project = ?)`
296
+ ).all(project);
297
+ return rows.map((row) => this.mapKnowledgeItem(row));
298
+ }
299
+ saveCompileRun(run) {
300
+ this.db.prepare(
301
+ `INSERT INTO compile_runs (id, project, session_id, compiler_version, prompt_versions, started_at, ended_at, duration_ms, observations_processed, knowledge_created, knowledge_reinforced, knowledge_deprecated, knowledge_superseded, knowledge_ignored, contradictions_detected, diagnostics)
302
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
303
+ ).run(
304
+ run.id,
305
+ run.project,
306
+ run.sessionId,
307
+ run.compilerVersion,
308
+ JSON.stringify(run.promptVersions),
309
+ run.startedAt,
310
+ run.endedAt,
311
+ run.durationMs,
312
+ run.observationsProcessed,
313
+ run.knowledgeCreated,
314
+ run.knowledgeReinforced,
315
+ run.knowledgeDeprecated,
316
+ run.knowledgeSuperseded,
317
+ run.knowledgeIgnored,
318
+ run.contradictionsDetected,
319
+ run.diagnostics
320
+ );
321
+ }
322
+ getCompileRuns(project, limit) {
323
+ let sql = "SELECT * FROM compile_runs WHERE project = ? ORDER BY started_at DESC";
324
+ const params = [project];
325
+ if (limit !== void 0) {
326
+ sql += " LIMIT ?";
327
+ params.push(limit);
328
+ }
329
+ const rows = this.db.prepare(sql).all(...params);
330
+ return rows.map((row) => this.mapCompileRun(row));
331
+ }
332
+ saveContradiction(report) {
333
+ this.db.prepare(
334
+ `INSERT INTO contradictions (id, project, item_a_id, item_b_id, topic, description, recommendation, resolved, created_at)
335
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
336
+ ).run(
337
+ report.id,
338
+ report.project,
339
+ report.itemAId,
340
+ report.itemBId,
341
+ report.topic,
342
+ report.description,
343
+ report.recommendation,
344
+ report.resolved ? 1 : 0,
345
+ report.createdAt
346
+ );
347
+ }
348
+ getUnresolvedContradictions(project) {
349
+ const rows = this.db.prepare("SELECT * FROM contradictions WHERE project = ? AND resolved = 0").all(project);
350
+ return rows.map((row) => this.mapContradiction(row));
351
+ }
352
+ resolveContradiction(id) {
353
+ this.db.prepare("UPDATE contradictions SET resolved = 1 WHERE id = ?").run(id);
354
+ }
355
+ queueTranscript(entry) {
356
+ this.db.prepare(
357
+ `INSERT OR IGNORE INTO pending_transcripts (id, transcript_path, project, project_root, provider, queued_at)
358
+ VALUES (?, ?, ?, ?, ?, ?)`
359
+ ).run(entry.id, entry.transcriptPath, entry.project, entry.projectRoot, entry.provider, entry.queuedAt);
360
+ }
361
+ popPendingTranscript() {
362
+ const row = this.db.prepare("SELECT * FROM pending_transcripts ORDER BY queued_at ASC LIMIT 1").get();
363
+ if (!row) return null;
364
+ this.db.prepare("DELETE FROM pending_transcripts WHERE id = ?").run(row.id);
365
+ return {
366
+ id: row.id,
367
+ transcriptPath: row.transcript_path,
368
+ project: row.project,
369
+ projectRoot: row.project_root,
370
+ provider: row.provider,
371
+ queuedAt: row.queued_at
372
+ };
373
+ }
374
+ getPendingCount() {
375
+ const row = this.db.prepare("SELECT COUNT(*) as count FROM pending_transcripts").get();
376
+ return row.count;
377
+ }
378
+ close() {
379
+ this.db.close();
380
+ }
381
+ mapSession(row) {
382
+ return {
383
+ id: row.id,
384
+ project: row.project,
385
+ startedAt: row.started_at,
386
+ endedAt: row.ended_at,
387
+ gitBranch: row.git_branch,
388
+ gitCommit: row.git_commit,
389
+ provider: row.provider,
390
+ model: row.model,
391
+ transcriptPath: row.transcript_path,
392
+ observationCount: row.observation_count
393
+ };
394
+ }
395
+ mapObservation(row) {
396
+ return {
397
+ id: row.id,
398
+ sessionId: row.session_id,
399
+ timestamp: row.timestamp,
400
+ type: row.type,
401
+ content: row.content,
402
+ sourceQuote: row.source_quote,
403
+ confidence: row.confidence,
404
+ project: row.project,
405
+ scope: row.scope || "project"
406
+ };
407
+ }
408
+ mapKnowledgeItem(row) {
409
+ return {
410
+ id: row.id,
411
+ canonicalHash: row.canonical_hash,
412
+ type: row.type,
413
+ title: row.title,
414
+ content: row.content,
415
+ confidence: row.confidence,
416
+ observationCount: row.observation_count,
417
+ authority: row.authority,
418
+ status: row.status,
419
+ supersededById: row.superseded_by_id ?? void 0,
420
+ enforce: row.enforce === 1,
421
+ project: row.project,
422
+ scope: row.scope || "project",
423
+ createdAt: row.created_at,
424
+ updatedAt: row.updated_at,
425
+ lastSeenAt: row.last_seen_at,
426
+ metadata: JSON.parse(row.metadata)
427
+ };
428
+ }
429
+ mapCompileRun(row) {
430
+ return {
431
+ id: row.id,
432
+ project: row.project,
433
+ sessionId: row.session_id,
434
+ compilerVersion: row.compiler_version,
435
+ promptVersions: JSON.parse(row.prompt_versions),
436
+ startedAt: row.started_at,
437
+ endedAt: row.ended_at,
438
+ durationMs: row.duration_ms,
439
+ observationsProcessed: row.observations_processed,
440
+ knowledgeCreated: row.knowledge_created,
441
+ knowledgeReinforced: row.knowledge_reinforced,
442
+ knowledgeDeprecated: row.knowledge_deprecated,
443
+ knowledgeSuperseded: row.knowledge_superseded,
444
+ knowledgeIgnored: row.knowledge_ignored,
445
+ contradictionsDetected: row.contradictions_detected,
446
+ diagnostics: row.diagnostics
447
+ };
448
+ }
449
+ mapContradiction(row) {
450
+ return {
451
+ id: row.id,
452
+ project: row.project,
453
+ itemAId: row.item_a_id,
454
+ itemBId: row.item_b_id,
455
+ topic: row.topic,
456
+ description: row.description,
457
+ recommendation: row.recommendation,
458
+ resolved: row.resolved === 1,
459
+ createdAt: row.created_at
460
+ };
461
+ }
462
+ };
463
+
464
+ export {
465
+ SqliteKnowledgeRepository
466
+ };