@cognistore/mcp-server 0.9.8

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 (2) hide show
  1. package/dist/index.js +1757 -0
  2. package/package.json +42 -0
package/dist/index.js ADDED
@@ -0,0 +1,1757 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ // src/index.ts
9
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
+
11
+ // ../../packages/core/dist/db/client.js
12
+ import BetterSqlite3 from "better-sqlite3";
13
+ import { drizzle } from "drizzle-orm/better-sqlite3";
14
+ import * as sqliteVec from "sqlite-vec";
15
+
16
+ // ../../packages/shared/dist/types/knowledge.js
17
+ var KnowledgeType;
18
+ (function(KnowledgeType2) {
19
+ KnowledgeType2["DECISION"] = "decision";
20
+ KnowledgeType2["PATTERN"] = "pattern";
21
+ KnowledgeType2["FIX"] = "fix";
22
+ KnowledgeType2["CONSTRAINT"] = "constraint";
23
+ KnowledgeType2["GOTCHA"] = "gotcha";
24
+ })(KnowledgeType || (KnowledgeType = {}));
25
+ var KnowledgeStatus;
26
+ (function(KnowledgeStatus2) {
27
+ KnowledgeStatus2["DRAFT"] = "draft";
28
+ KnowledgeStatus2["ACTIVE"] = "active";
29
+ KnowledgeStatus2["COMPLETED"] = "completed";
30
+ KnowledgeStatus2["ARCHIVED"] = "archived";
31
+ })(KnowledgeStatus || (KnowledgeStatus = {}));
32
+ var TaskStatus;
33
+ (function(TaskStatus2) {
34
+ TaskStatus2["PENDING"] = "pending";
35
+ TaskStatus2["IN_PROGRESS"] = "in_progress";
36
+ TaskStatus2["COMPLETED"] = "completed";
37
+ })(TaskStatus || (TaskStatus = {}));
38
+ var TaskPriority;
39
+ (function(TaskPriority2) {
40
+ TaskPriority2["LOW"] = "low";
41
+ TaskPriority2["MEDIUM"] = "medium";
42
+ TaskPriority2["HIGH"] = "high";
43
+ })(TaskPriority || (TaskPriority = {}));
44
+
45
+ // ../../packages/shared/dist/constants/defaults.js
46
+ var DEFAULT_EMBEDDING_MODEL = "all-minilm";
47
+ var DEFAULT_EMBEDDING_DIMENSIONS = 384;
48
+ var DEFAULT_SIMILARITY_THRESHOLD = 0.3;
49
+ var DEFAULT_SEARCH_LIMIT = 10;
50
+ var DEFAULT_OLLAMA_HOST = "http://localhost:11434";
51
+ var DEFAULT_SQLITE_PATH = "~/.cognistore/knowledge.db";
52
+ var KNOWLEDGE_TYPES = Object.values(KnowledgeType);
53
+
54
+ // ../../packages/shared/dist/utils/validation.js
55
+ import { z } from "zod";
56
+ var knowledgeTypeSchema = z.nativeEnum(KnowledgeType);
57
+ var knowledgeStatusSchema = z.nativeEnum(KnowledgeStatus);
58
+ var taskStatusSchema = z.nativeEnum(TaskStatus);
59
+ var taskPrioritySchema = z.nativeEnum(TaskPriority);
60
+ var createKnowledgeSchema = z.object({
61
+ title: z.string().min(1, "Title is required"),
62
+ content: z.string().min(1, "Content is required"),
63
+ tags: z.array(z.string().min(1)).min(1, "At least one tag is required"),
64
+ type: knowledgeTypeSchema,
65
+ scope: z.string().min(1, "Scope is required"),
66
+ source: z.string().min(1, "Source is required"),
67
+ confidenceScore: z.number().min(0).max(1).optional().default(1),
68
+ expiresAt: z.date().nullable().optional().default(null),
69
+ relatedIds: z.array(z.string().uuid()).nullable().optional().default(null),
70
+ agentId: z.string().nullable().optional().default(null)
71
+ });
72
+ var updateKnowledgeSchema = z.object({
73
+ title: z.string().min(1).optional(),
74
+ content: z.string().min(1).optional(),
75
+ tags: z.array(z.string().min(1)).min(1).optional(),
76
+ type: knowledgeTypeSchema.optional(),
77
+ scope: z.string().min(1).optional(),
78
+ source: z.string().min(1).optional(),
79
+ confidenceScore: z.number().min(0).max(1).optional(),
80
+ expiresAt: z.date().nullable().optional(),
81
+ relatedIds: z.array(z.string().uuid()).nullable().optional(),
82
+ agentId: z.string().nullable().optional()
83
+ });
84
+ var searchOptionsSchema = z.object({
85
+ tags: z.array(z.string()).optional(),
86
+ type: knowledgeTypeSchema.optional(),
87
+ scope: z.string().optional(),
88
+ limit: z.number().int().min(1).max(100).optional().default(DEFAULT_SEARCH_LIMIT),
89
+ threshold: z.number().min(0).max(1).optional().default(DEFAULT_SIMILARITY_THRESHOLD)
90
+ });
91
+ var createPlanSchema = z.object({
92
+ title: z.string().min(1, "Title is required"),
93
+ content: z.string().min(1, "Content is required"),
94
+ tags: z.array(z.string().min(1)).min(1, "At least one tag is required"),
95
+ scope: z.string().min(1, "Scope is required"),
96
+ source: z.string().min(1, "Source is required"),
97
+ status: knowledgeStatusSchema.optional().default(KnowledgeStatus.DRAFT)
98
+ });
99
+ var updatePlanSchema = z.object({
100
+ title: z.string().min(1).optional(),
101
+ content: z.string().min(1).optional(),
102
+ tags: z.array(z.string().min(1)).min(1).optional(),
103
+ scope: z.string().min(1).optional(),
104
+ status: knowledgeStatusSchema.optional(),
105
+ source: z.string().min(1).optional()
106
+ });
107
+ var createPlanTaskSchema = z.object({
108
+ planId: z.string().min(1),
109
+ description: z.string().min(1, "Description is required"),
110
+ status: taskStatusSchema.optional().default(TaskStatus.PENDING),
111
+ priority: taskPrioritySchema.optional().default(TaskPriority.MEDIUM),
112
+ notes: z.string().nullable().optional().default(null),
113
+ position: z.number().int().min(0).optional()
114
+ });
115
+ var updatePlanTaskSchema = z.object({
116
+ description: z.string().min(1).optional(),
117
+ status: taskStatusSchema.optional(),
118
+ priority: taskPrioritySchema.optional(),
119
+ notes: z.string().nullable().optional(),
120
+ position: z.number().int().min(0).optional()
121
+ });
122
+
123
+ // ../../packages/core/dist/db/schema/index.js
124
+ var schema_exports = {};
125
+ __export(schema_exports, {
126
+ createEmbeddingsTable: () => createEmbeddingsTable,
127
+ deleteEmbedding: () => deleteEmbedding,
128
+ insertEmbedding: () => insertEmbedding,
129
+ knowledgeEntries: () => knowledgeEntries,
130
+ knowledgeTypeEnum: () => knowledgeTypeEnum,
131
+ searchKnn: () => searchKnn,
132
+ updateEmbedding: () => updateEmbedding
133
+ });
134
+
135
+ // ../../packages/core/dist/db/schema/knowledge.js
136
+ import { sqliteTable, text, integer, real, index } from "drizzle-orm/sqlite-core";
137
+ var knowledgeTypeEnum = ["decision", "pattern", "fix", "constraint", "gotcha"];
138
+ var knowledgeEntries = sqliteTable("knowledge_entries", {
139
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
140
+ title: text("title").notNull().default(""),
141
+ content: text("content").notNull(),
142
+ tags: text("tags", { mode: "json" }).notNull().$type().default([]),
143
+ type: text("type").notNull(),
144
+ scope: text("scope").notNull(),
145
+ source: text("source").notNull(),
146
+ version: integer("version").notNull().default(1),
147
+ expiresAt: text("expires_at"),
148
+ confidenceScore: real("confidence_score").notNull().default(1),
149
+ relatedIds: text("related_ids", { mode: "json" }).$type(),
150
+ agentId: text("agent_id"),
151
+ createdAt: text("created_at").notNull().$defaultFn(() => (/* @__PURE__ */ new Date()).toISOString()),
152
+ updatedAt: text("updated_at").notNull().$defaultFn(() => (/* @__PURE__ */ new Date()).toISOString())
153
+ }, (table) => [
154
+ index("idx_type").on(table.type),
155
+ index("idx_scope").on(table.scope)
156
+ ]);
157
+
158
+ // ../../packages/core/dist/db/schema/sqlite-vec.js
159
+ var VIRTUAL_TABLE_NAME = "knowledge_embeddings";
160
+ function createEmbeddingsTable(sqlite, dimensions = 384) {
161
+ sqlite.exec(`
162
+ CREATE VIRTUAL TABLE IF NOT EXISTS ${VIRTUAL_TABLE_NAME} USING vec0(
163
+ id TEXT PRIMARY KEY,
164
+ embedding float[${dimensions}] distance_metric=cosine
165
+ )
166
+ `);
167
+ }
168
+ function insertEmbedding(sqlite, id, embedding) {
169
+ const stmt = sqlite.prepare(`INSERT INTO ${VIRTUAL_TABLE_NAME}(id, embedding) VALUES (?, ?)`);
170
+ stmt.run(id, Buffer.from(new Float32Array(embedding).buffer));
171
+ }
172
+ function updateEmbedding(sqlite, id, embedding) {
173
+ const stmt = sqlite.prepare(`UPDATE ${VIRTUAL_TABLE_NAME} SET embedding = ? WHERE id = ?`);
174
+ stmt.run(Buffer.from(new Float32Array(embedding).buffer), id);
175
+ }
176
+ function deleteEmbedding(sqlite, id) {
177
+ const stmt = sqlite.prepare(`DELETE FROM ${VIRTUAL_TABLE_NAME} WHERE id = ?`);
178
+ stmt.run(id);
179
+ }
180
+ function searchKnn(sqlite, queryEmbedding, k) {
181
+ const stmt = sqlite.prepare(`
182
+ SELECT id, distance
183
+ FROM ${VIRTUAL_TABLE_NAME}
184
+ WHERE embedding MATCH ?
185
+ AND k = ?
186
+ `);
187
+ return stmt.all(Buffer.from(new Float32Array(queryEmbedding).buffer), k);
188
+ }
189
+
190
+ // ../../packages/core/dist/db/migrate.js
191
+ import { readdirSync, readFileSync, existsSync } from "fs";
192
+ import { resolve } from "path";
193
+ var EMBEDDED_MIGRATIONS = {
194
+ "0.8.0": `
195
+ CREATE TABLE IF NOT EXISTS knowledge_entries (
196
+ id TEXT PRIMARY KEY,
197
+ content TEXT NOT NULL,
198
+ tags TEXT NOT NULL DEFAULT '[]',
199
+ type TEXT NOT NULL,
200
+ scope TEXT NOT NULL,
201
+ source TEXT NOT NULL,
202
+ version INTEGER NOT NULL DEFAULT 1,
203
+ expires_at TEXT,
204
+ confidence_score REAL NOT NULL DEFAULT 1.0,
205
+ related_ids TEXT,
206
+ agent_id TEXT,
207
+ created_at TEXT NOT NULL,
208
+ updated_at TEXT NOT NULL
209
+ );
210
+ CREATE INDEX IF NOT EXISTS idx_type ON knowledge_entries(type);
211
+ CREATE INDEX IF NOT EXISTS idx_scope ON knowledge_entries(scope);
212
+
213
+ CREATE TABLE IF NOT EXISTS operations_log (
214
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
215
+ operation TEXT NOT NULL CHECK(operation IN ('read', 'write')),
216
+ created_at TEXT NOT NULL
217
+ );
218
+ CREATE INDEX IF NOT EXISTS idx_ops_created_at ON operations_log(created_at);
219
+ CREATE INDEX IF NOT EXISTS idx_ops_operation ON operations_log(operation);
220
+ `,
221
+ "0.9.0": `
222
+ ALTER TABLE knowledge_entries ADD COLUMN title TEXT NOT NULL DEFAULT '';
223
+
224
+ CREATE TABLE IF NOT EXISTS plans (
225
+ id TEXT PRIMARY KEY,
226
+ title TEXT NOT NULL,
227
+ content TEXT NOT NULL,
228
+ tags TEXT NOT NULL DEFAULT '[]',
229
+ scope TEXT NOT NULL,
230
+ status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft', 'active', 'completed', 'archived')),
231
+ source TEXT NOT NULL DEFAULT '',
232
+ created_at TEXT NOT NULL,
233
+ updated_at TEXT NOT NULL
234
+ );
235
+ CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status);
236
+ CREATE INDEX IF NOT EXISTS idx_plans_scope ON plans(scope);
237
+
238
+ CREATE TABLE IF NOT EXISTS plan_relations (
239
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
240
+ plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
241
+ knowledge_id TEXT NOT NULL REFERENCES knowledge_entries(id) ON DELETE CASCADE,
242
+ relation_type TEXT NOT NULL CHECK(relation_type IN ('input', 'output')),
243
+ created_at TEXT NOT NULL,
244
+ UNIQUE(plan_id, knowledge_id, relation_type)
245
+ );
246
+ CREATE INDEX IF NOT EXISTS idx_plan_relations_plan ON plan_relations(plan_id);
247
+ CREATE INDEX IF NOT EXISTS idx_plan_relations_knowledge ON plan_relations(knowledge_id);
248
+
249
+ CREATE TABLE IF NOT EXISTS plan_tasks (
250
+ id TEXT PRIMARY KEY,
251
+ plan_id TEXT NOT NULL REFERENCES plans(id) ON DELETE CASCADE,
252
+ description TEXT NOT NULL,
253
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed')),
254
+ priority TEXT NOT NULL DEFAULT 'medium' CHECK(priority IN ('low', 'medium', 'high')),
255
+ notes TEXT,
256
+ position INTEGER NOT NULL DEFAULT 0,
257
+ created_at TEXT NOT NULL,
258
+ updated_at TEXT NOT NULL
259
+ );
260
+ CREATE INDEX IF NOT EXISTS idx_plan_tasks_plan ON plan_tasks(plan_id);
261
+ CREATE INDEX IF NOT EXISTS idx_plan_tasks_status ON plan_tasks(status);
262
+ `
263
+ };
264
+ function runMigrations(sqlite, migrationsDir) {
265
+ sqlite.exec(`
266
+ CREATE TABLE IF NOT EXISTS schema_version (
267
+ version TEXT PRIMARY KEY,
268
+ applied_at TEXT NOT NULL
269
+ );
270
+ `);
271
+ const applied = new Set(sqlite.prepare("SELECT version FROM schema_version").all().map((r) => r.version));
272
+ if (applied.size === 0) {
273
+ const tableExists = sqlite.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='knowledge_entries'").get();
274
+ if (tableExists) {
275
+ sqlite.prepare("INSERT INTO schema_version (version, applied_at) VALUES (?, ?)").run("0.8.0", (/* @__PURE__ */ new Date()).toISOString());
276
+ applied.add("0.8.0");
277
+ console.log("Migration: bootstrapped existing DB as v0.8.0");
278
+ }
279
+ }
280
+ let migrations;
281
+ if (existsSync(migrationsDir)) {
282
+ const files = readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort((a, b) => compareSemver(a.replace(".sql", ""), b.replace(".sql", "")));
283
+ migrations = files.map((f) => ({
284
+ version: f.replace(".sql", ""),
285
+ sql: readFileSync(resolve(migrationsDir, f), "utf-8")
286
+ }));
287
+ } else {
288
+ migrations = Object.entries(EMBEDDED_MIGRATIONS).map(([version, sql2]) => ({ version, sql: sql2 })).sort((a, b) => compareSemver(a.version, b.version));
289
+ console.log("Migration: using embedded migrations (bundled mode)");
290
+ }
291
+ for (const { version, sql: sql2 } of migrations) {
292
+ if (applied.has(version))
293
+ continue;
294
+ const cleanedSql = sql2.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n");
295
+ const statements = cleanedSql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
296
+ for (const stmt of statements) {
297
+ try {
298
+ sqlite.exec(stmt + ";");
299
+ } catch (err) {
300
+ if (err?.message?.includes("duplicate column"))
301
+ continue;
302
+ if (err?.message?.includes("already exists"))
303
+ continue;
304
+ throw err;
305
+ }
306
+ }
307
+ sqlite.prepare("INSERT INTO schema_version (version, applied_at) VALUES (?, ?)").run(version, (/* @__PURE__ */ new Date()).toISOString());
308
+ console.log(`Migration: ${version} applied`);
309
+ }
310
+ }
311
+ function runSeeds(sqlite, seedsDir, isFreshInstall) {
312
+ if (!isFreshInstall)
313
+ return;
314
+ if (!existsSync(seedsDir))
315
+ return;
316
+ const files = readdirSync(seedsDir).filter((f) => f.endsWith(".sql")).sort();
317
+ for (const file of files) {
318
+ const sqlPath = resolve(seedsDir, file);
319
+ const sqlContent = readFileSync(sqlPath, "utf-8");
320
+ sqlite.exec(sqlContent);
321
+ console.log(`Seed: ${file} applied`);
322
+ }
323
+ }
324
+ function compareSemver(a, b) {
325
+ const pa = a.split(".").map(Number);
326
+ const pb = b.split(".").map(Number);
327
+ for (let i = 0; i < 3; i++) {
328
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0))
329
+ return (pa[i] ?? 0) - (pb[i] ?? 0);
330
+ }
331
+ return 0;
332
+ }
333
+
334
+ // ../../packages/core/dist/db/client.js
335
+ import { homedir } from "os";
336
+ import { mkdirSync } from "fs";
337
+ import { dirname, resolve as resolve2 } from "path";
338
+ import { fileURLToPath } from "url";
339
+ var __dirname = dirname(fileURLToPath(import.meta.url));
340
+ function resolvePath(path) {
341
+ if (path.startsWith("~")) {
342
+ return path.replace("~", homedir());
343
+ }
344
+ return path;
345
+ }
346
+ function createDbClient(dbPath) {
347
+ const resolvedPath = resolvePath(dbPath ?? process.env.SQLITE_PATH ?? DEFAULT_SQLITE_PATH);
348
+ mkdirSync(dirname(resolvedPath), { recursive: true });
349
+ const sqlite = new BetterSqlite3(resolvedPath);
350
+ sqlite.pragma("journal_mode = WAL");
351
+ sqlite.pragma("busy_timeout = 5000");
352
+ sqlite.pragma("foreign_keys = ON");
353
+ sqliteVec.load(sqlite);
354
+ const hasSchemaVersion = sqlite.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'").get();
355
+ const hasKnowledgeEntries = sqlite.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='knowledge_entries'").get();
356
+ const isFreshInstall = !hasSchemaVersion && !hasKnowledgeEntries;
357
+ const migrationsDir = resolve2(__dirname, "migrations");
358
+ const seedsDir = resolve2(__dirname, "seeds");
359
+ runMigrations(sqlite, migrationsDir);
360
+ runSeeds(sqlite, seedsDir, isFreshInstall);
361
+ createEmbeddingsTable(sqlite);
362
+ createPlansEmbeddingsTable(sqlite);
363
+ const db = drizzle(sqlite, { schema: schema_exports });
364
+ return { db, sqlite };
365
+ }
366
+ function createPlansEmbeddingsTable(sqlite) {
367
+ try {
368
+ sqlite.exec(`
369
+ CREATE VIRTUAL TABLE IF NOT EXISTS plans_embeddings USING vec0(
370
+ id TEXT PRIMARY KEY,
371
+ embedding float[384] distance_metric=cosine
372
+ );
373
+ `);
374
+ } catch {
375
+ }
376
+ }
377
+
378
+ // ../../packages/core/dist/repositories/knowledge.repository.js
379
+ import { eq, sql, and, or, isNull } from "drizzle-orm";
380
+ var KnowledgeRepository = class {
381
+ db;
382
+ sqlite;
383
+ constructor(db, sqlite) {
384
+ this.db = db;
385
+ this.sqlite = sqlite;
386
+ }
387
+ async create(input) {
388
+ const id = crypto.randomUUID();
389
+ const now = (/* @__PURE__ */ new Date()).toISOString();
390
+ const [entry] = await this.db.insert(knowledgeEntries).values({
391
+ id,
392
+ title: input.title,
393
+ content: input.content,
394
+ tags: input.tags,
395
+ type: input.type,
396
+ scope: input.scope,
397
+ source: input.source,
398
+ confidenceScore: input.confidenceScore ?? 1,
399
+ expiresAt: input.expiresAt ? input.expiresAt.toISOString() : null,
400
+ relatedIds: input.relatedIds ?? null,
401
+ agentId: input.agentId ?? null,
402
+ createdAt: now,
403
+ updatedAt: now
404
+ }).returning();
405
+ insertEmbedding(this.sqlite, id, input.embedding);
406
+ return entry;
407
+ }
408
+ async findById(id) {
409
+ const [entry] = await this.db.select().from(knowledgeEntries).where(eq(knowledgeEntries.id, id));
410
+ return entry ?? null;
411
+ }
412
+ async update(id, updates) {
413
+ const { embedding, ...rest } = updates;
414
+ const values = {
415
+ ...rest,
416
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
417
+ };
418
+ if (rest.expiresAt !== void 0) {
419
+ values.expiresAt = rest.expiresAt ? rest.expiresAt.toISOString() : null;
420
+ }
421
+ const [entry] = await this.db.update(knowledgeEntries).set({
422
+ ...values,
423
+ version: sql`${knowledgeEntries.version} + 1`
424
+ }).where(eq(knowledgeEntries.id, id)).returning();
425
+ if (embedding) {
426
+ updateEmbedding(this.sqlite, id, embedding);
427
+ }
428
+ return entry ?? null;
429
+ }
430
+ async delete(id) {
431
+ const [entry] = await this.db.delete(knowledgeEntries).where(eq(knowledgeEntries.id, id)).returning();
432
+ if (entry) {
433
+ deleteEmbedding(this.sqlite, id);
434
+ }
435
+ return entry ?? null;
436
+ }
437
+ /**
438
+ * Semantic search using cosine similarity on embeddings.
439
+ * Uses sqlite-vec KNN search on the virtual table, then filters by metadata.
440
+ * IMPORTANT: When a specific scope is provided, global knowledge is ALWAYS included.
441
+ */
442
+ async searchBySimilarity(queryEmbedding, options) {
443
+ const limit = options?.limit ?? DEFAULT_SEARCH_LIMIT;
444
+ const threshold = options?.threshold ?? DEFAULT_SIMILARITY_THRESHOLD;
445
+ const candidateLimit = limit * 5;
446
+ const knnResults = searchKnn(this.sqlite, queryEmbedding, candidateLimit);
447
+ if (knnResults.length === 0) {
448
+ return [];
449
+ }
450
+ const candidateIds = knnResults.map((r) => r.id);
451
+ const distanceMap = new Map(knnResults.map((r) => [r.id, r.distance]));
452
+ const conditions = [];
453
+ conditions.push(sql`${knowledgeEntries.id} IN (${sql.join(candidateIds.map((id) => sql`${id}`), sql`, `)})`);
454
+ if (options?.scope) {
455
+ conditions.push(or(eq(knowledgeEntries.scope, "global"), eq(knowledgeEntries.scope, options.scope)));
456
+ }
457
+ if (options?.tags && options.tags.length > 0) {
458
+ const tagConditions = options.tags.map((tag) => sql`EXISTS (SELECT 1 FROM json_each(${knowledgeEntries.tags}) WHERE value = ${tag})`);
459
+ conditions.push(or(...tagConditions));
460
+ }
461
+ if (options?.type) {
462
+ conditions.push(eq(knowledgeEntries.type, options.type));
463
+ }
464
+ conditions.push(or(isNull(knowledgeEntries.expiresAt), sql`${knowledgeEntries.expiresAt} > ${(/* @__PURE__ */ new Date()).toISOString()}`));
465
+ const whereClause = and(...conditions);
466
+ const entries = await this.db.select().from(knowledgeEntries).where(whereClause);
467
+ return entries.map((entry) => ({
468
+ entry,
469
+ similarity: 1 - (distanceMap.get(entry.id) ?? 1)
470
+ })).filter((r) => r.similarity >= threshold).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
471
+ }
472
+ async listRecent(limit = 20, filters) {
473
+ const conditions = [];
474
+ if (filters?.type)
475
+ conditions.push(sql`${knowledgeEntries.type} = ${filters.type}`);
476
+ if (filters?.scope)
477
+ conditions.push(sql`${knowledgeEntries.scope} = ${filters.scope}`);
478
+ if (conditions.length === 0) {
479
+ return this.db.select().from(knowledgeEntries).orderBy(sql`${knowledgeEntries.createdAt} DESC`).limit(limit);
480
+ }
481
+ const where = conditions.length === 1 ? conditions[0] : sql`${conditions[0]} AND ${conditions[1]}`;
482
+ return this.db.select().from(knowledgeEntries).where(where).orderBy(sql`${knowledgeEntries.createdAt} DESC`).limit(limit);
483
+ }
484
+ async listTags() {
485
+ const result = await this.db.all(sql`SELECT DISTINCT value FROM knowledge_entries, json_each(knowledge_entries.tags)`);
486
+ return result.map((r) => r.value);
487
+ }
488
+ async topTags(limit = 10) {
489
+ const result = await this.db.all(sql`SELECT value as tag, COUNT(*) as count FROM knowledge_entries, json_each(knowledge_entries.tags) GROUP BY value ORDER BY count DESC LIMIT ${limit}`);
490
+ return result;
491
+ }
492
+ async count() {
493
+ const [result] = await this.db.select({ count: sql`count(*)` }).from(knowledgeEntries);
494
+ return Number(result.count);
495
+ }
496
+ async lastUpdatedAt() {
497
+ const result = await this.db.all(sql`SELECT MAX(updated_at) as latest FROM knowledge_entries`);
498
+ return result[0]?.latest ?? null;
499
+ }
500
+ async countByType() {
501
+ const results = await this.db.select({
502
+ type: knowledgeEntries.type,
503
+ count: sql`count(*)`
504
+ }).from(knowledgeEntries).groupBy(knowledgeEntries.type);
505
+ return results.map((r) => ({ type: r.type, count: Number(r.count) }));
506
+ }
507
+ async countByScope() {
508
+ const results = await this.db.select({
509
+ scope: knowledgeEntries.scope,
510
+ count: sql`count(*)`
511
+ }).from(knowledgeEntries).groupBy(knowledgeEntries.scope);
512
+ return results.map((r) => ({ scope: r.scope, count: Number(r.count) }));
513
+ }
514
+ async listAll() {
515
+ return this.db.select().from(knowledgeEntries).orderBy(sql`${knowledgeEntries.createdAt} DESC`);
516
+ }
517
+ async listScopes() {
518
+ const rows = this.sqlite.prepare(`SELECT DISTINCT scope FROM knowledge_entries
519
+ UNION
520
+ SELECT DISTINCT scope FROM plans
521
+ ORDER BY scope`).all();
522
+ return rows.map((r) => r.scope);
523
+ }
524
+ // ─── Plans (separate table) ──────────────────────────────────
525
+ createPlan(input) {
526
+ const id = crypto.randomUUID();
527
+ const now = (/* @__PURE__ */ new Date()).toISOString();
528
+ this.sqlite.prepare("INSERT INTO plans (id, title, content, tags, scope, status, source, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(id, input.title, input.content, JSON.stringify(input.tags), input.scope, input.status ?? "draft", input.source, now, now);
529
+ try {
530
+ this.sqlite.prepare("INSERT INTO plans_embeddings (id, embedding) VALUES (?, ?)").run(id, new Float32Array(input.embedding).buffer);
531
+ } catch {
532
+ }
533
+ return this.getPlanById(id);
534
+ }
535
+ getPlanById(id) {
536
+ return this.sqlite.prepare("SELECT * FROM plans WHERE id = ?").get(id) ?? null;
537
+ }
538
+ updatePlan(id, updates) {
539
+ const setClauses = [];
540
+ const values = [];
541
+ for (const [key, value] of Object.entries(updates)) {
542
+ if (value === void 0)
543
+ continue;
544
+ const col = key === "tags" ? "tags" : key.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
545
+ setClauses.push(`${col} = ?`);
546
+ values.push(key === "tags" ? JSON.stringify(value) : value);
547
+ }
548
+ if (setClauses.length === 0)
549
+ return this.getPlanById(id);
550
+ setClauses.push("updated_at = ?");
551
+ values.push((/* @__PURE__ */ new Date()).toISOString());
552
+ values.push(id);
553
+ this.sqlite.prepare(`UPDATE plans SET ${setClauses.join(", ")} WHERE id = ?`).run(...values);
554
+ return this.getPlanById(id);
555
+ }
556
+ deletePlan(id) {
557
+ const result = this.sqlite.prepare("DELETE FROM plans WHERE id = ?").run(id);
558
+ try {
559
+ this.sqlite.prepare("DELETE FROM plans_embeddings WHERE id = ?").run(id);
560
+ } catch {
561
+ }
562
+ return result.changes > 0;
563
+ }
564
+ listAllPlans() {
565
+ return this.sqlite.prepare("SELECT * FROM plans ORDER BY created_at DESC").all();
566
+ }
567
+ listPlans(limit = 20, status) {
568
+ if (status) {
569
+ return this.sqlite.prepare("SELECT * FROM plans WHERE status = ? ORDER BY created_at DESC LIMIT ?").all(status, limit);
570
+ }
571
+ return this.sqlite.prepare("SELECT * FROM plans ORDER BY created_at DESC LIMIT ?").all(limit);
572
+ }
573
+ // ─── Plan Relations ─────────────────────────────────────────
574
+ addPlanRelation(planId, knowledgeId, relationType) {
575
+ this.sqlite.prepare("INSERT OR IGNORE INTO plan_relations (plan_id, knowledge_id, relation_type, created_at) VALUES (?, ?, ?, ?)").run(planId, knowledgeId, relationType, (/* @__PURE__ */ new Date()).toISOString());
576
+ }
577
+ getPlanRelations(planId) {
578
+ return this.sqlite.prepare("SELECT knowledge_id as id, relation_type as relationType FROM plan_relations WHERE plan_id = ? ORDER BY created_at").all(planId);
579
+ }
580
+ deletePlanRelations(planId) {
581
+ return this.sqlite.prepare("DELETE FROM plan_relations WHERE plan_id = ?").run(planId).changes;
582
+ }
583
+ getPlansForKnowledge(knowledgeId) {
584
+ return this.sqlite.prepare(`
585
+ SELECT pr.plan_id as planId, pr.relation_type as relationType, p.title, p.status
586
+ FROM plan_relations pr
587
+ JOIN plans p ON p.id = pr.plan_id
588
+ WHERE pr.knowledge_id = ?
589
+ ORDER BY pr.created_at
590
+ `).all(knowledgeId);
591
+ }
592
+ // ─── Plan Tasks ─────────────────────────────────────────────
593
+ createPlanTask(input) {
594
+ const id = crypto.randomUUID();
595
+ const now = (/* @__PURE__ */ new Date()).toISOString();
596
+ const maxPos = this.sqlite.prepare("SELECT MAX(position) as max FROM plan_tasks WHERE plan_id = ?").get(input.planId);
597
+ const position = input.position ?? (maxPos?.max ?? -1) + 1;
598
+ this.sqlite.prepare("INSERT INTO plan_tasks (id, plan_id, description, status, priority, notes, position, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(id, input.planId, input.description, input.status ?? "pending", input.priority ?? "medium", input.notes ?? null, position, now, now);
599
+ return this.getPlanTaskById(id);
600
+ }
601
+ updatePlanTask(id, updates) {
602
+ const setClauses = [];
603
+ const values = [];
604
+ if (updates.description !== void 0) {
605
+ setClauses.push("description = ?");
606
+ values.push(updates.description);
607
+ }
608
+ if (updates.status !== void 0) {
609
+ setClauses.push("status = ?");
610
+ values.push(updates.status);
611
+ }
612
+ if (updates.priority !== void 0) {
613
+ setClauses.push("priority = ?");
614
+ values.push(updates.priority);
615
+ }
616
+ if (updates.notes !== void 0) {
617
+ setClauses.push("notes = ?");
618
+ values.push(updates.notes);
619
+ }
620
+ if (updates.position !== void 0) {
621
+ setClauses.push("position = ?");
622
+ values.push(updates.position);
623
+ }
624
+ if (setClauses.length === 0)
625
+ return this.getPlanTaskById(id);
626
+ setClauses.push("updated_at = ?");
627
+ values.push((/* @__PURE__ */ new Date()).toISOString());
628
+ values.push(id);
629
+ this.sqlite.prepare(`UPDATE plan_tasks SET ${setClauses.join(", ")} WHERE id = ?`).run(...values);
630
+ return this.getPlanTaskById(id);
631
+ }
632
+ deletePlanTask(id) {
633
+ return this.sqlite.prepare("DELETE FROM plan_tasks WHERE id = ?").run(id).changes > 0;
634
+ }
635
+ listPlanTasks(planId) {
636
+ return this.sqlite.prepare("SELECT * FROM plan_tasks WHERE plan_id = ? ORDER BY position ASC").all(planId);
637
+ }
638
+ getPlanTaskById(id) {
639
+ return this.sqlite.prepare("SELECT * FROM plan_tasks WHERE id = ?").get(id) ?? null;
640
+ }
641
+ getPlanTaskStats() {
642
+ const result = this.sqlite.prepare(`
643
+ SELECT
644
+ COUNT(*) as total,
645
+ SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
646
+ SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress,
647
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed
648
+ FROM plan_tasks
649
+ `).get();
650
+ return { total: result?.total ?? 0, pending: result?.pending ?? 0, inProgress: result?.in_progress ?? 0, completed: result?.completed ?? 0 };
651
+ }
652
+ // ─── Operations Log ────────────────────────────────────────────
653
+ logOperation(operation) {
654
+ this.sqlite.prepare("INSERT INTO operations_log (operation, created_at) VALUES (?, ?)").run(operation, (/* @__PURE__ */ new Date()).toISOString());
655
+ }
656
+ getOperationCounts() {
657
+ const now = /* @__PURE__ */ new Date();
658
+ const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1e3).toISOString();
659
+ const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1e3).toISOString();
660
+ const result = this.sqlite.prepare(`SELECT
661
+ SUM(CASE WHEN operation = 'read' AND created_at >= ? THEN 1 ELSE 0 END) as reads_1h,
662
+ SUM(CASE WHEN operation = 'read' AND created_at >= ? THEN 1 ELSE 0 END) as reads_24h,
663
+ SUM(CASE WHEN operation = 'write' AND created_at >= ? THEN 1 ELSE 0 END) as writes_1h,
664
+ SUM(CASE WHEN operation = 'write' AND created_at >= ? THEN 1 ELSE 0 END) as writes_24h
665
+ FROM operations_log
666
+ WHERE created_at >= ?`).get(oneHourAgo, oneDayAgo, oneHourAgo, oneDayAgo, oneDayAgo);
667
+ return {
668
+ readsLastHour: result?.reads_1h ?? 0,
669
+ readsLastDay: result?.reads_24h ?? 0,
670
+ writesLastHour: result?.writes_1h ?? 0,
671
+ writesLastDay: result?.writes_24h ?? 0
672
+ };
673
+ }
674
+ getOperationsByDay(days = 15) {
675
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
676
+ const rows = this.sqlite.prepare(`
677
+ SELECT date(created_at) as date, operation, COUNT(*) as count
678
+ FROM operations_log
679
+ WHERE created_at >= ?
680
+ GROUP BY date(created_at), operation
681
+ ORDER BY date(created_at)
682
+ `).all(cutoff);
683
+ const map = {};
684
+ const now = /* @__PURE__ */ new Date();
685
+ for (let i = days - 1; i >= 0; i--) {
686
+ const d = new Date(now);
687
+ d.setDate(d.getDate() - i);
688
+ const dateStr = d.toISOString().split("T")[0];
689
+ map[dateStr] = { reads: 0, writes: 0 };
690
+ }
691
+ for (const row of rows) {
692
+ if (!map[row.date])
693
+ map[row.date] = { reads: 0, writes: 0 };
694
+ if (row.operation === "read")
695
+ map[row.date].reads = row.count;
696
+ else if (row.operation === "write")
697
+ map[row.date].writes = row.count;
698
+ }
699
+ return Object.entries(map).map(([date, counts]) => ({ date, ...counts }));
700
+ }
701
+ cleanupOldOperations() {
702
+ const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
703
+ return this.sqlite.prepare("DELETE FROM operations_log WHERE created_at < ?").run(cutoff).changes;
704
+ }
705
+ };
706
+
707
+ // ../../packages/core/dist/services/knowledge.service.js
708
+ var KnowledgeService = class {
709
+ repository;
710
+ embeddingProvider;
711
+ constructor(repository, embeddingProvider) {
712
+ this.repository = repository;
713
+ this.embeddingProvider = embeddingProvider;
714
+ }
715
+ logOp(op) {
716
+ try {
717
+ this.repository.logOperation(op);
718
+ } catch {
719
+ }
720
+ }
721
+ async add(input) {
722
+ const tagsText = input.tags.join(" ");
723
+ const embedding = await this.embeddingProvider.embed(tagsText);
724
+ const entry = await this.repository.create({ ...input, embedding });
725
+ this.logOp("write");
726
+ return this.toKnowledgeEntry(entry);
727
+ }
728
+ async search(query, options) {
729
+ const queryEmbedding = await this.embeddingProvider.embed(query);
730
+ const results = await this.repository.searchBySimilarity(queryEmbedding, options);
731
+ this.logOp("read");
732
+ return results.map((r) => ({
733
+ entry: this.toKnowledgeEntry(r.entry),
734
+ similarity: r.similarity
735
+ }));
736
+ }
737
+ async getById(id) {
738
+ const entry = await this.repository.findById(id);
739
+ return entry ? this.toKnowledgeEntry(entry) : null;
740
+ }
741
+ async update(id, updates) {
742
+ let embedding;
743
+ if (updates.tags && updates.tags.length > 0) {
744
+ embedding = await this.embeddingProvider.embed(updates.tags.join(" "));
745
+ }
746
+ const entry = await this.repository.update(id, { ...updates, embedding });
747
+ if (entry)
748
+ this.logOp("write");
749
+ return entry ? this.toKnowledgeEntry(entry) : null;
750
+ }
751
+ async delete(id) {
752
+ const entry = await this.repository.delete(id);
753
+ if (entry)
754
+ this.logOp("write");
755
+ return entry !== null;
756
+ }
757
+ async listAll() {
758
+ const entries = await this.repository.listAll();
759
+ return entries.map((e) => this.toKnowledgeEntry(e));
760
+ }
761
+ async listScopes() {
762
+ return this.repository.listScopes();
763
+ }
764
+ async bulkDelete(ids) {
765
+ let deleted = 0;
766
+ const errors = [];
767
+ for (const id of ids) {
768
+ try {
769
+ const result = await this.repository.delete(id);
770
+ if (result) {
771
+ deleted++;
772
+ this.logOp("write");
773
+ }
774
+ } catch (err) {
775
+ errors.push(`Failed to delete ${id}: ${err instanceof Error ? err.message : String(err)}`);
776
+ }
777
+ }
778
+ return { deleted, errors };
779
+ }
780
+ async importKnowledge(entries) {
781
+ let imported = 0;
782
+ let skipped = 0;
783
+ const errors = [];
784
+ const existing = await this.repository.listAll();
785
+ const existingHashes = /* @__PURE__ */ new Set();
786
+ for (const e of existing) {
787
+ const hash = this.hashContent((e.title ?? "") + e.content);
788
+ existingHashes.add(hash);
789
+ }
790
+ for (const entry of entries) {
791
+ try {
792
+ const hash = this.hashContent((entry.title ?? "") + entry.content);
793
+ if (existingHashes.has(hash)) {
794
+ skipped++;
795
+ continue;
796
+ }
797
+ const tagsText = entry.tags.join(" ");
798
+ const embedding = await this.embeddingProvider.embed(tagsText);
799
+ await this.repository.create({ ...entry, embedding });
800
+ existingHashes.add(hash);
801
+ imported++;
802
+ this.logOp("write");
803
+ } catch (err) {
804
+ errors.push(`Failed to import "${entry.title}": ${err instanceof Error ? err.message : String(err)}`);
805
+ }
806
+ }
807
+ return { imported, skipped, errors };
808
+ }
809
+ hashContent(text2) {
810
+ let hash = 0;
811
+ for (let i = 0; i < text2.length; i++) {
812
+ const char = text2.charCodeAt(i);
813
+ hash = (hash << 5) - hash + char;
814
+ hash |= 0;
815
+ }
816
+ return hash.toString(36);
817
+ }
818
+ async listRecent(limit = 20, filters) {
819
+ const entries = await this.repository.listRecent(limit, filters);
820
+ return entries.map((e) => this.toKnowledgeEntry(e));
821
+ }
822
+ async topTags(limit = 10) {
823
+ return this.repository.topTags(limit);
824
+ }
825
+ async listTags() {
826
+ return this.repository.listTags();
827
+ }
828
+ async getStats() {
829
+ const [count, byType, byScope, lastUpdatedAt] = await Promise.all([
830
+ this.repository.count(),
831
+ this.repository.countByType(),
832
+ this.repository.countByScope(),
833
+ this.repository.lastUpdatedAt()
834
+ ]);
835
+ return { total: count, byType, byScope, lastUpdatedAt };
836
+ }
837
+ // ─── Plans (separate entity) ────────────────────────────────
838
+ async createPlan(input) {
839
+ const { tasks, ...planInput } = input;
840
+ const embedding = await this.embeddingProvider.embed(input.tags.join(" "));
841
+ const row = this.repository.createPlan({ ...planInput, embedding });
842
+ const plan = this.toPlan(row);
843
+ if (tasks && tasks.length > 0) {
844
+ try {
845
+ for (let i = 0; i < tasks.length; i++) {
846
+ this.repository.createPlanTask({ planId: plan.id, description: tasks[i].description, priority: tasks[i].priority, position: i });
847
+ }
848
+ } catch (err) {
849
+ this.repository.deletePlan(plan.id);
850
+ throw err;
851
+ }
852
+ }
853
+ return plan;
854
+ }
855
+ getPlanById(id) {
856
+ const row = this.repository.getPlanById(id);
857
+ return row ? this.toPlan(row) : null;
858
+ }
859
+ updatePlan(id, updates) {
860
+ const row = this.repository.updatePlan(id, updates);
861
+ return row ? this.toPlan(row) : null;
862
+ }
863
+ deletePlan(id) {
864
+ return this.repository.deletePlan(id);
865
+ }
866
+ listAllPlans() {
867
+ const rows = this.repository.listAllPlans();
868
+ return rows.map((r) => this.toPlan(r));
869
+ }
870
+ listPlans(limit = 20, status) {
871
+ const rows = this.repository.listPlans(limit, status);
872
+ return rows.map((r) => this.toPlan(r));
873
+ }
874
+ async importPlans(plans) {
875
+ let imported = 0;
876
+ let skipped = 0;
877
+ const errors = [];
878
+ const existing = this.repository.listAllPlans();
879
+ const existingHashes = /* @__PURE__ */ new Set();
880
+ for (const p of existing) {
881
+ existingHashes.add(this.hashContent(p.title + p.content));
882
+ }
883
+ for (const plan of plans) {
884
+ try {
885
+ const hash = this.hashContent(plan.title + plan.content);
886
+ if (existingHashes.has(hash)) {
887
+ skipped++;
888
+ continue;
889
+ }
890
+ const { tasks, ...planInput } = plan;
891
+ const embedding = await this.embeddingProvider.embed(planInput.tags.join(" "));
892
+ const row = this.repository.createPlan({ ...planInput, embedding });
893
+ const createdPlan = this.toPlan(row);
894
+ if (tasks && tasks.length > 0) {
895
+ for (let i = 0; i < tasks.length; i++) {
896
+ this.repository.createPlanTask({
897
+ planId: createdPlan.id,
898
+ description: tasks[i].description,
899
+ status: tasks[i].status,
900
+ priority: tasks[i].priority,
901
+ notes: tasks[i].notes,
902
+ position: i
903
+ });
904
+ }
905
+ }
906
+ existingHashes.add(hash);
907
+ imported++;
908
+ } catch (err) {
909
+ errors.push(`Failed to import plan "${plan.title}": ${err instanceof Error ? err.message : String(err)}`);
910
+ }
911
+ }
912
+ return { imported, skipped, errors };
913
+ }
914
+ // ─── Plan Relations ─────────────────────────────────────────
915
+ addPlanRelation(planId, knowledgeId, relationType) {
916
+ this.repository.addPlanRelation(planId, knowledgeId, relationType);
917
+ }
918
+ async getPlanRelations(planId) {
919
+ const relations = this.repository.getPlanRelations(planId);
920
+ const results = [];
921
+ for (const rel of relations) {
922
+ const entry = await this.repository.findById(rel.id);
923
+ if (entry)
924
+ results.push({ entry: this.toKnowledgeEntry(entry), relationType: rel.relationType });
925
+ }
926
+ return results;
927
+ }
928
+ getPlansForKnowledge(knowledgeId) {
929
+ return this.repository.getPlansForKnowledge(knowledgeId);
930
+ }
931
+ // ─── Plan Tasks ─────────────────────────────────────────────
932
+ createPlanTask(input) {
933
+ return this.toPlanTask(this.repository.createPlanTask(input));
934
+ }
935
+ updatePlanTask(id, updates) {
936
+ const row = this.repository.updatePlanTask(id, updates);
937
+ return row ? this.toPlanTask(row) : null;
938
+ }
939
+ deletePlanTask(id) {
940
+ return this.repository.deletePlanTask(id);
941
+ }
942
+ listPlanTasks(planId) {
943
+ return this.repository.listPlanTasks(planId).map((r) => this.toPlanTask(r));
944
+ }
945
+ getPlanTaskStats() {
946
+ return this.repository.getPlanTaskStats();
947
+ }
948
+ // ─── Operations ─────────────────────────────────────────────
949
+ getOperationCounts() {
950
+ return this.repository.getOperationCounts();
951
+ }
952
+ getOperationsByDay(days = 15) {
953
+ return this.repository.getOperationsByDay(days);
954
+ }
955
+ cleanupOldOperations() {
956
+ return this.repository.cleanupOldOperations();
957
+ }
958
+ // ─── Converters ─────────────────────────────────────────────
959
+ toKnowledgeEntry(row) {
960
+ return {
961
+ id: row.id,
962
+ title: row.title ?? "",
963
+ content: row.content,
964
+ embedding: [],
965
+ tags: Array.isArray(row.tags) ? row.tags : JSON.parse(row.tags ?? "[]"),
966
+ type: row.type,
967
+ scope: row.scope,
968
+ source: row.source,
969
+ version: row.version,
970
+ expiresAt: row.expiresAt ? new Date(row.expiresAt) : null,
971
+ confidenceScore: row.confidenceScore,
972
+ relatedIds: row.relatedIds ? Array.isArray(row.relatedIds) ? row.relatedIds : JSON.parse(row.relatedIds) : null,
973
+ agentId: row.agentId,
974
+ createdAt: new Date(row.createdAt ?? row.created_at),
975
+ updatedAt: new Date(row.updatedAt ?? row.updated_at)
976
+ };
977
+ }
978
+ toPlan(row) {
979
+ return {
980
+ id: row.id,
981
+ title: row.title,
982
+ content: row.content,
983
+ tags: Array.isArray(row.tags) ? row.tags : JSON.parse(row.tags ?? "[]"),
984
+ scope: row.scope,
985
+ status: row.status,
986
+ source: row.source ?? "",
987
+ createdAt: new Date(row.created_at ?? row.createdAt),
988
+ updatedAt: new Date(row.updated_at ?? row.updatedAt)
989
+ };
990
+ }
991
+ toPlanTask(row) {
992
+ return {
993
+ id: row.id,
994
+ planId: row.plan_id ?? row.planId,
995
+ description: row.description,
996
+ status: row.status,
997
+ priority: row.priority,
998
+ notes: row.notes ?? null,
999
+ position: row.position,
1000
+ createdAt: new Date(row.created_at ?? row.createdAt),
1001
+ updatedAt: new Date(row.updated_at ?? row.updatedAt)
1002
+ };
1003
+ }
1004
+ };
1005
+
1006
+ // ../../packages/embeddings/dist/client.js
1007
+ var OllamaEmbeddingClient = class {
1008
+ host;
1009
+ model;
1010
+ dimensions;
1011
+ maxRetries;
1012
+ constructor(config) {
1013
+ this.host = config?.host ?? (process.env.OLLAMA_HOST ?? DEFAULT_OLLAMA_HOST);
1014
+ this.model = config?.model ?? (process.env.OLLAMA_MODEL ?? DEFAULT_EMBEDDING_MODEL);
1015
+ this.dimensions = config?.dimensions ?? (Number(process.env.EMBEDDING_DIMENSIONS) || DEFAULT_EMBEDDING_DIMENSIONS);
1016
+ this.maxRetries = config?.maxRetries ?? 3;
1017
+ }
1018
+ async embed(text2) {
1019
+ const body = {
1020
+ model: this.model,
1021
+ prompt: text2
1022
+ };
1023
+ const response = await this.fetchWithRetry(`${this.host}/api/embeddings`, {
1024
+ method: "POST",
1025
+ headers: { "Content-Type": "application/json" },
1026
+ body: JSON.stringify(body)
1027
+ });
1028
+ if (!response.ok) {
1029
+ const errorText = await response.text();
1030
+ throw new Error(`Ollama embedding failed (${response.status}): ${errorText}`);
1031
+ }
1032
+ const data = await response.json();
1033
+ if (!data.embedding || !Array.isArray(data.embedding)) {
1034
+ throw new Error("Invalid embedding response from Ollama");
1035
+ }
1036
+ if (data.embedding.length !== this.dimensions) {
1037
+ throw new Error(`Embedding dimension mismatch: expected ${this.dimensions}, got ${data.embedding.length}. Check that OLLAMA_MODEL and EMBEDDING_DIMENSIONS are compatible.`);
1038
+ }
1039
+ return data.embedding;
1040
+ }
1041
+ async embedBatch(texts, concurrency = 3) {
1042
+ const results = [];
1043
+ for (let i = 0; i < texts.length; i += concurrency) {
1044
+ const batch = texts.slice(i, i + concurrency);
1045
+ const batchResults = await Promise.all(batch.map((text2) => this.embed(text2)));
1046
+ results.push(...batchResults);
1047
+ }
1048
+ return results;
1049
+ }
1050
+ async isHealthy() {
1051
+ try {
1052
+ const response = await fetch(`${this.host}/api/tags`);
1053
+ return response.ok;
1054
+ } catch {
1055
+ return false;
1056
+ }
1057
+ }
1058
+ async isModelAvailable() {
1059
+ try {
1060
+ const response = await fetch(`${this.host}/api/tags`);
1061
+ if (!response.ok)
1062
+ return false;
1063
+ const data = await response.json();
1064
+ return data.models.some((m) => m.name === this.model || m.name.startsWith(`${this.model}:`));
1065
+ } catch {
1066
+ return false;
1067
+ }
1068
+ }
1069
+ async pullModel() {
1070
+ const response = await fetch(`${this.host}/api/pull`, {
1071
+ method: "POST",
1072
+ headers: { "Content-Type": "application/json" },
1073
+ body: JSON.stringify({ name: this.model })
1074
+ });
1075
+ if (!response.ok) {
1076
+ throw new Error(`Failed to pull model ${this.model}: ${response.statusText}`);
1077
+ }
1078
+ const reader = response.body?.getReader();
1079
+ if (reader) {
1080
+ while (true) {
1081
+ const { done } = await reader.read();
1082
+ if (done)
1083
+ break;
1084
+ }
1085
+ }
1086
+ }
1087
+ async ensureModel() {
1088
+ const available = await this.isModelAvailable();
1089
+ if (!available) {
1090
+ await this.pullModel();
1091
+ }
1092
+ }
1093
+ getConfig() {
1094
+ return {
1095
+ host: this.host,
1096
+ model: this.model,
1097
+ dimensions: this.dimensions
1098
+ };
1099
+ }
1100
+ async fetchWithRetry(url, init) {
1101
+ let lastError;
1102
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
1103
+ try {
1104
+ return await fetch(url, init);
1105
+ } catch (error) {
1106
+ lastError = error instanceof Error ? error : new Error(String(error));
1107
+ if (attempt < this.maxRetries - 1) {
1108
+ const delay = Math.pow(2, attempt) * 500;
1109
+ await new Promise((r) => setTimeout(r, delay));
1110
+ }
1111
+ }
1112
+ }
1113
+ throw new Error(`Failed after ${this.maxRetries} retries: ${lastError?.message}`);
1114
+ }
1115
+ };
1116
+
1117
+ // ../../packages/embeddings/dist/health.js
1118
+ async function checkOllamaHealth(client) {
1119
+ try {
1120
+ const healthy = await client.isHealthy();
1121
+ if (!healthy) {
1122
+ return { connected: false, error: "Ollama is not responding" };
1123
+ }
1124
+ const config = client.getConfig();
1125
+ const modelAvailable = await client.isModelAvailable();
1126
+ return {
1127
+ connected: true,
1128
+ model: config.model,
1129
+ modelAvailable,
1130
+ error: modelAvailable ? void 0 : `Model "${config.model}" is not available. Run: ollama pull ${config.model}`
1131
+ };
1132
+ } catch (error) {
1133
+ return {
1134
+ connected: false,
1135
+ error: error instanceof Error ? error.message : String(error)
1136
+ };
1137
+ }
1138
+ }
1139
+
1140
+ // ../../packages/sdk/dist/config.js
1141
+ function resolveConfig(userConfig) {
1142
+ return {
1143
+ database: {
1144
+ path: userConfig?.database?.path ?? (process.env.SQLITE_PATH ?? DEFAULT_SQLITE_PATH)
1145
+ },
1146
+ ollama: {
1147
+ host: userConfig?.ollama?.host ?? (process.env.OLLAMA_HOST ?? DEFAULT_OLLAMA_HOST),
1148
+ model: userConfig?.ollama?.model ?? (process.env.OLLAMA_MODEL ?? DEFAULT_EMBEDDING_MODEL),
1149
+ dimensions: userConfig?.ollama?.dimensions ?? (Number(process.env.EMBEDDING_DIMENSIONS) || DEFAULT_EMBEDDING_DIMENSIONS)
1150
+ }
1151
+ };
1152
+ }
1153
+
1154
+ // ../../packages/sdk/dist/errors.js
1155
+ var KnowledgeBaseError = class extends Error {
1156
+ code;
1157
+ constructor(message, code) {
1158
+ super(message);
1159
+ this.code = code;
1160
+ this.name = "KnowledgeBaseError";
1161
+ }
1162
+ };
1163
+ var ConnectionError = class extends KnowledgeBaseError {
1164
+ constructor(message) {
1165
+ super(message, "CONNECTION_ERROR");
1166
+ this.name = "ConnectionError";
1167
+ }
1168
+ };
1169
+ var EmbeddingError = class extends KnowledgeBaseError {
1170
+ constructor(message) {
1171
+ super(message, "EMBEDDING_ERROR");
1172
+ this.name = "EmbeddingError";
1173
+ }
1174
+ };
1175
+ var ValidationError = class extends KnowledgeBaseError {
1176
+ constructor(message) {
1177
+ super(message, "VALIDATION_ERROR");
1178
+ this.name = "ValidationError";
1179
+ }
1180
+ };
1181
+
1182
+ // ../../packages/sdk/dist/sdk.js
1183
+ var KnowledgeSDK = class {
1184
+ config;
1185
+ db = null;
1186
+ sqlite = null;
1187
+ service = null;
1188
+ ollamaClient;
1189
+ initialized = false;
1190
+ constructor(config) {
1191
+ this.config = resolveConfig(config);
1192
+ this.ollamaClient = new OllamaEmbeddingClient({
1193
+ host: this.config.ollama.host,
1194
+ model: this.config.ollama.model,
1195
+ dimensions: this.config.ollama.dimensions
1196
+ });
1197
+ }
1198
+ async initialize() {
1199
+ if (this.initialized)
1200
+ return;
1201
+ try {
1202
+ try {
1203
+ const { db, sqlite } = createDbClient(this.config.database.path);
1204
+ this.db = db;
1205
+ this.sqlite = sqlite;
1206
+ } catch (error) {
1207
+ throw new ConnectionError(`Failed to open database: ${error instanceof Error ? error.message : String(error)}`);
1208
+ }
1209
+ try {
1210
+ await this.ollamaClient.ensureModel();
1211
+ } catch (error) {
1212
+ throw new EmbeddingError(`Failed to ensure embedding model: ${error instanceof Error ? error.message : String(error)}`);
1213
+ }
1214
+ const repository = new KnowledgeRepository(this.db, this.sqlite);
1215
+ this.service = new KnowledgeService(repository, this.ollamaClient);
1216
+ this.initialized = true;
1217
+ } catch (error) {
1218
+ await this.cleanup();
1219
+ throw error;
1220
+ }
1221
+ }
1222
+ async close() {
1223
+ await this.cleanup();
1224
+ this.initialized = false;
1225
+ }
1226
+ async addKnowledge(input) {
1227
+ this.ensureInitialized();
1228
+ const parsed = createKnowledgeSchema.safeParse(input);
1229
+ if (!parsed.success) {
1230
+ throw new ValidationError(`Invalid input: ${parsed.error.message}`);
1231
+ }
1232
+ try {
1233
+ return await this.service.add(parsed.data);
1234
+ } catch (error) {
1235
+ throw this.wrapError(error, "Failed to add knowledge");
1236
+ }
1237
+ }
1238
+ async getKnowledge(query, options) {
1239
+ this.ensureInitialized();
1240
+ if (!query || query.trim().length === 0) {
1241
+ throw new ValidationError("Query cannot be empty");
1242
+ }
1243
+ const parsedOptions = options ? searchOptionsSchema.parse(options) : void 0;
1244
+ try {
1245
+ return await this.service.search(query, parsedOptions);
1246
+ } catch (error) {
1247
+ throw this.wrapError(error, "Failed to search knowledge");
1248
+ }
1249
+ }
1250
+ async getKnowledgeById(id) {
1251
+ this.ensureInitialized();
1252
+ try {
1253
+ return await this.service.getById(id);
1254
+ } catch (error) {
1255
+ throw this.wrapError(error, "Failed to get knowledge");
1256
+ }
1257
+ }
1258
+ async updateKnowledge(id, updates) {
1259
+ this.ensureInitialized();
1260
+ const parsed = updateKnowledgeSchema.safeParse(updates);
1261
+ if (!parsed.success) {
1262
+ throw new ValidationError(`Invalid updates: ${parsed.error.message}`);
1263
+ }
1264
+ try {
1265
+ return await this.service.update(id, parsed.data);
1266
+ } catch (error) {
1267
+ throw this.wrapError(error, "Failed to update knowledge");
1268
+ }
1269
+ }
1270
+ async deleteKnowledge(id) {
1271
+ this.ensureInitialized();
1272
+ try {
1273
+ return await this.service.delete(id);
1274
+ } catch (error) {
1275
+ throw this.wrapError(error, "Failed to delete knowledge");
1276
+ }
1277
+ }
1278
+ async listRecent(limit = 20, filters) {
1279
+ this.ensureInitialized();
1280
+ try {
1281
+ return await this.service.listRecent(limit, filters);
1282
+ } catch (error) {
1283
+ throw this.wrapError(error, "Failed to list recent knowledge");
1284
+ }
1285
+ }
1286
+ async getTopTags(limit = 10) {
1287
+ this.ensureInitialized();
1288
+ try {
1289
+ return await this.service.topTags(limit);
1290
+ } catch (error) {
1291
+ throw this.wrapError(error, "Failed to get top tags");
1292
+ }
1293
+ }
1294
+ async listTags() {
1295
+ this.ensureInitialized();
1296
+ try {
1297
+ return await this.service.listTags();
1298
+ } catch (error) {
1299
+ throw this.wrapError(error, "Failed to list tags");
1300
+ }
1301
+ }
1302
+ async getStats() {
1303
+ this.ensureInitialized();
1304
+ try {
1305
+ return await this.service.getStats();
1306
+ } catch (error) {
1307
+ throw this.wrapError(error, "Failed to get stats");
1308
+ }
1309
+ }
1310
+ async listAllKnowledge() {
1311
+ this.ensureInitialized();
1312
+ try {
1313
+ return await this.service.listAll();
1314
+ } catch (error) {
1315
+ throw this.wrapError(error, "Failed to list all knowledge");
1316
+ }
1317
+ }
1318
+ async listScopes() {
1319
+ this.ensureInitialized();
1320
+ try {
1321
+ return await this.service.listScopes();
1322
+ } catch (error) {
1323
+ throw this.wrapError(error, "Failed to list scopes");
1324
+ }
1325
+ }
1326
+ async bulkDeleteKnowledge(ids) {
1327
+ this.ensureInitialized();
1328
+ try {
1329
+ return await this.service.bulkDelete(ids);
1330
+ } catch (error) {
1331
+ throw this.wrapError(error, "Failed to bulk delete knowledge");
1332
+ }
1333
+ }
1334
+ async importKnowledge(entries) {
1335
+ this.ensureInitialized();
1336
+ try {
1337
+ return await this.service.importKnowledge(entries);
1338
+ } catch (error) {
1339
+ throw this.wrapError(error, "Failed to import knowledge");
1340
+ }
1341
+ }
1342
+ async importPlans(plans) {
1343
+ this.ensureInitialized();
1344
+ try {
1345
+ return await this.service.importPlans(plans);
1346
+ } catch (error) {
1347
+ throw this.wrapError(error, "Failed to import plans");
1348
+ }
1349
+ }
1350
+ listAllPlans() {
1351
+ this.ensureInitialized();
1352
+ return this.service.listAllPlans();
1353
+ }
1354
+ // ─── Plans (separate entity) ─────────────────────────────────
1355
+ async createPlan(input) {
1356
+ this.ensureInitialized();
1357
+ const { relatedKnowledgeIds, tasks, ...rest } = input;
1358
+ const parsed = createPlanSchema.safeParse(rest);
1359
+ if (!parsed.success) {
1360
+ throw new ValidationError(`Invalid plan input: ${parsed.error.message}`);
1361
+ }
1362
+ try {
1363
+ const plan = await this.service.createPlan({ ...parsed.data, tasks });
1364
+ if (relatedKnowledgeIds) {
1365
+ for (const kid of relatedKnowledgeIds) {
1366
+ try {
1367
+ this.service.addPlanRelation(plan.id, kid, "input");
1368
+ } catch {
1369
+ }
1370
+ }
1371
+ }
1372
+ return plan;
1373
+ } catch (error) {
1374
+ throw this.wrapError(error, "Failed to create plan");
1375
+ }
1376
+ }
1377
+ getPlanById(id) {
1378
+ this.ensureInitialized();
1379
+ return this.service.getPlanById(id);
1380
+ }
1381
+ updatePlan(id, updates) {
1382
+ this.ensureInitialized();
1383
+ return this.service.updatePlan(id, updates);
1384
+ }
1385
+ deletePlan(id) {
1386
+ this.ensureInitialized();
1387
+ return this.service.deletePlan(id);
1388
+ }
1389
+ listPlans(limit = 20, status) {
1390
+ this.ensureInitialized();
1391
+ return this.service.listPlans(limit, status);
1392
+ }
1393
+ addPlanRelation(planId, knowledgeId, relationType) {
1394
+ this.ensureInitialized();
1395
+ this.service.addPlanRelation(planId, knowledgeId, relationType);
1396
+ }
1397
+ async getPlanRelations(planId) {
1398
+ this.ensureInitialized();
1399
+ return this.service.getPlanRelations(planId);
1400
+ }
1401
+ getPlansForKnowledge(knowledgeId) {
1402
+ this.ensureInitialized();
1403
+ return this.service.getPlansForKnowledge(knowledgeId);
1404
+ }
1405
+ // ─── Plan Tasks ─────────────────────────────────────────────
1406
+ createPlanTask(input) {
1407
+ this.ensureInitialized();
1408
+ return this.service.createPlanTask(input);
1409
+ }
1410
+ updatePlanTask(id, updates) {
1411
+ this.ensureInitialized();
1412
+ return this.service.updatePlanTask(id, updates);
1413
+ }
1414
+ deletePlanTask(id) {
1415
+ this.ensureInitialized();
1416
+ return this.service.deletePlanTask(id);
1417
+ }
1418
+ listPlanTasks(planId) {
1419
+ this.ensureInitialized();
1420
+ return this.service.listPlanTasks(planId);
1421
+ }
1422
+ getPlanTaskStats() {
1423
+ this.ensureInitialized();
1424
+ return this.service.getPlanTaskStats();
1425
+ }
1426
+ // ─── Operations ─────────────────────────────────────────────
1427
+ getOperationCounts() {
1428
+ this.ensureInitialized();
1429
+ return this.service.getOperationCounts();
1430
+ }
1431
+ getOperationsByDay(days = 15) {
1432
+ this.ensureInitialized();
1433
+ return this.service.getOperationsByDay(days);
1434
+ }
1435
+ cleanupOldOperations() {
1436
+ if (!this.initialized || !this.service)
1437
+ return 0;
1438
+ return this.service.cleanupOldOperations();
1439
+ }
1440
+ async healthCheck() {
1441
+ const ollamaHealth = await checkOllamaHealth(this.ollamaClient);
1442
+ let dbConnected = false;
1443
+ let dbError;
1444
+ if (this.sqlite) {
1445
+ try {
1446
+ this.sqlite.prepare("SELECT 1").get();
1447
+ dbConnected = true;
1448
+ } catch (error) {
1449
+ dbError = error instanceof Error ? error.message : String(error);
1450
+ }
1451
+ } else {
1452
+ dbError = "Not initialized";
1453
+ }
1454
+ return {
1455
+ database: {
1456
+ connected: dbConnected,
1457
+ path: this.config.database.path,
1458
+ error: dbError
1459
+ },
1460
+ ollama: {
1461
+ connected: ollamaHealth.connected,
1462
+ model: ollamaHealth.model,
1463
+ host: this.config.ollama.host,
1464
+ error: ollamaHealth.error
1465
+ }
1466
+ };
1467
+ }
1468
+ /**
1469
+ * Remove orphan embeddings (no matching entry) and run VACUUM.
1470
+ * Returns count of orphans removed and final DB size.
1471
+ */
1472
+ async cleanupDatabase() {
1473
+ this.ensureInitialized();
1474
+ try {
1475
+ const orphanIds = this.sqlite.prepare(`SELECT id FROM knowledge_embeddings_rowids WHERE id NOT IN (SELECT id FROM knowledge_entries)`).all();
1476
+ let removed = 0;
1477
+ const deleteStmt = this.sqlite.prepare(`DELETE FROM knowledge_embeddings WHERE id = ?`);
1478
+ for (const row of orphanIds) {
1479
+ deleteStmt.run(row.id);
1480
+ removed++;
1481
+ }
1482
+ this.sqlite.exec("VACUUM");
1483
+ return { orphansRemoved: removed, vacuumed: true };
1484
+ } catch (error) {
1485
+ throw this.wrapError(error, "Failed to cleanup database");
1486
+ }
1487
+ }
1488
+ ensureInitialized() {
1489
+ if (!this.initialized || !this.service) {
1490
+ throw new ConnectionError("SDK not initialized. Call initialize() first.");
1491
+ }
1492
+ }
1493
+ async cleanup() {
1494
+ if (this.sqlite) {
1495
+ try {
1496
+ this.sqlite.close();
1497
+ } catch {
1498
+ }
1499
+ this.sqlite = null;
1500
+ }
1501
+ this.db = null;
1502
+ this.service = null;
1503
+ }
1504
+ wrapError(error, context) {
1505
+ if (error instanceof Error && error.name.endsWith("Error")) {
1506
+ return error;
1507
+ }
1508
+ return new ConnectionError(`${context}: ${error instanceof Error ? error.message : String(error)}`);
1509
+ }
1510
+ };
1511
+
1512
+ // src/server.ts
1513
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1514
+ import { z as z2 } from "zod";
1515
+ var knowledgeTypeValues = ["decision", "pattern", "fix", "constraint", "gotcha"];
1516
+ var knowledgeStatusValues = ["draft", "active", "completed", "archived"];
1517
+ function createServer(sdk) {
1518
+ const server = new McpServer({
1519
+ name: "cognistore",
1520
+ version: "0.1.0"
1521
+ });
1522
+ server.tool(
1523
+ "addKnowledge",
1524
+ "Store a new knowledge entry with semantic embedding. Content is vectorized for future semantic search.",
1525
+ {
1526
+ title: z2.string().describe("Short descriptive title for the knowledge entry"),
1527
+ content: z2.string().describe("The knowledge content text to store"),
1528
+ tags: z2.array(z2.string()).describe("Mandatory categorical tags for filtering"),
1529
+ type: z2.enum(knowledgeTypeValues).describe("Type of knowledge entry"),
1530
+ scope: z2.string().describe('Scope: "global" or "workspace:<project-name>"'),
1531
+ source: z2.string().describe("Source of the knowledge"),
1532
+ confidenceScore: z2.number().min(0).max(1).optional().describe("Confidence score 0-1"),
1533
+ agentId: z2.string().optional().describe("ID of the agent that created this")
1534
+ },
1535
+ async (params) => {
1536
+ const result = await sdk.addKnowledge({
1537
+ title: params.title,
1538
+ content: params.content,
1539
+ tags: params.tags,
1540
+ type: params.type,
1541
+ scope: params.scope,
1542
+ source: params.source,
1543
+ confidenceScore: params.confidenceScore,
1544
+ agentId: params.agentId
1545
+ });
1546
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1547
+ }
1548
+ );
1549
+ server.tool(
1550
+ "getKnowledge",
1551
+ "Search knowledge semantically. When a specific scope is provided, global knowledge is always included.",
1552
+ {
1553
+ query: z2.string().describe("Natural language query to search for"),
1554
+ tags: z2.array(z2.string()).optional().describe("Optional tag filters"),
1555
+ type: z2.enum(knowledgeTypeValues).optional().describe("Optional type filter"),
1556
+ scope: z2.string().optional().describe("Optional scope filter (global always included)"),
1557
+ limit: z2.number().optional().describe("Max results (default: 10)"),
1558
+ threshold: z2.number().optional().describe("Min similarity 0-1 (default: 0.3)")
1559
+ },
1560
+ async (params) => {
1561
+ const results = await sdk.getKnowledge(params.query, {
1562
+ tags: params.tags,
1563
+ type: params.type,
1564
+ scope: params.scope,
1565
+ limit: params.limit,
1566
+ threshold: params.threshold
1567
+ });
1568
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
1569
+ }
1570
+ );
1571
+ server.tool(
1572
+ "updateKnowledge",
1573
+ "Update an existing knowledge entry. If content changes, embedding is regenerated. Version auto-increments.",
1574
+ {
1575
+ id: z2.string().describe("UUID of the knowledge entry to update"),
1576
+ title: z2.string().optional().describe("New title"),
1577
+ content: z2.string().optional().describe("New content text"),
1578
+ tags: z2.array(z2.string()).optional().describe("New tags"),
1579
+ type: z2.enum(knowledgeTypeValues).optional().describe("New type"),
1580
+ scope: z2.string().optional().describe("New scope"),
1581
+ source: z2.string().optional().describe("New source"),
1582
+ confidenceScore: z2.number().min(0).max(1).optional().describe("New confidence score")
1583
+ },
1584
+ async (params) => {
1585
+ const { id, ...updates } = params;
1586
+ const result = await sdk.updateKnowledge(id, {
1587
+ title: updates.title,
1588
+ content: updates.content,
1589
+ tags: updates.tags,
1590
+ type: updates.type,
1591
+ scope: updates.scope,
1592
+ source: updates.source,
1593
+ confidenceScore: updates.confidenceScore
1594
+ });
1595
+ const text2 = result ? JSON.stringify(result, null, 2) : "Knowledge entry not found";
1596
+ return { content: [{ type: "text", text: text2 }] };
1597
+ }
1598
+ );
1599
+ server.tool(
1600
+ "deleteKnowledge",
1601
+ "Delete a knowledge entry by ID.",
1602
+ {
1603
+ id: z2.string().describe("UUID of the knowledge entry to delete")
1604
+ },
1605
+ async (params) => {
1606
+ const deleted = await sdk.deleteKnowledge(params.id);
1607
+ return {
1608
+ content: [{ type: "text", text: deleted ? "Deleted successfully" : "Not found" }]
1609
+ };
1610
+ }
1611
+ );
1612
+ server.tool(
1613
+ "listTags",
1614
+ "List all unique tags across all knowledge entries.",
1615
+ {},
1616
+ async () => {
1617
+ const tags = await sdk.listTags();
1618
+ return { content: [{ type: "text", text: JSON.stringify(tags) }] };
1619
+ }
1620
+ );
1621
+ server.tool(
1622
+ "healthCheck",
1623
+ "Check health of the knowledge base infrastructure (database, Ollama).",
1624
+ {},
1625
+ async () => {
1626
+ const health = await sdk.healthCheck();
1627
+ return { content: [{ type: "text", text: JSON.stringify(health, null, 2) }] };
1628
+ }
1629
+ );
1630
+ server.tool(
1631
+ "createPlan",
1632
+ "Create a new plan in the knowledge base. Plans are the ONLY way to persist implementation plans \u2014 never use local files. Status starts as draft.",
1633
+ {
1634
+ title: z2.string().describe("Plan title (short, descriptive)"),
1635
+ content: z2.string().describe("Full plan content (steps, approach, considerations)"),
1636
+ tags: z2.array(z2.string()).describe("Tags for categorization"),
1637
+ scope: z2.string().describe('Scope: "global" or "workspace:<project-name>"'),
1638
+ source: z2.string().describe("Source/context of the plan"),
1639
+ relatedKnowledgeIds: z2.array(z2.string()).optional().describe("IDs of knowledge entries consulted during planning (input relations)"),
1640
+ tasks: z2.array(z2.object({
1641
+ description: z2.string(),
1642
+ priority: z2.enum(["low", "medium", "high"]).optional()
1643
+ })).optional().describe("Initial tasks for the plan todo list")
1644
+ },
1645
+ async (params) => {
1646
+ const result = await sdk.createPlan({
1647
+ title: params.title,
1648
+ content: params.content,
1649
+ tags: params.tags,
1650
+ scope: params.scope,
1651
+ source: params.source,
1652
+ relatedKnowledgeIds: params.relatedKnowledgeIds,
1653
+ tasks: params.tasks
1654
+ });
1655
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1656
+ }
1657
+ );
1658
+ server.tool(
1659
+ "updatePlan",
1660
+ "Update an existing plan. Use to change status (draft \u2192 active \u2192 completed \u2192 archived), title, content, tags, or scope.",
1661
+ {
1662
+ planId: z2.string().describe("UUID of the plan to update"),
1663
+ title: z2.string().optional().describe("New title"),
1664
+ content: z2.string().optional().describe("New content"),
1665
+ tags: z2.array(z2.string()).optional().describe("New tags"),
1666
+ scope: z2.string().optional().describe("New scope"),
1667
+ status: z2.enum(knowledgeStatusValues).optional().describe("New status"),
1668
+ source: z2.string().optional().describe("New source")
1669
+ },
1670
+ async (params) => {
1671
+ const { planId, ...updates } = params;
1672
+ const result = sdk.updatePlan(planId, updates);
1673
+ const text2 = result ? JSON.stringify(result, null, 2) : "Plan not found";
1674
+ return { content: [{ type: "text", text: text2 }] };
1675
+ }
1676
+ );
1677
+ server.tool(
1678
+ "addPlanRelation",
1679
+ 'Link a knowledge entry to a plan. Use "input" for entries consulted during planning, "output" for entries created/updated during execution.',
1680
+ {
1681
+ planId: z2.string().describe("UUID of the plan"),
1682
+ knowledgeId: z2.string().describe("UUID of the knowledge entry to link"),
1683
+ relationType: z2.enum(["input", "output"]).describe('"input" = consulted during planning, "output" = created/updated during execution')
1684
+ },
1685
+ async (params) => {
1686
+ sdk.addPlanRelation(params.planId, params.knowledgeId, params.relationType);
1687
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, ...params }) }] };
1688
+ }
1689
+ );
1690
+ server.tool(
1691
+ "addPlanTask",
1692
+ "Add a task to a plan todo list. Position is auto-calculated.",
1693
+ {
1694
+ planId: z2.string().describe("UUID of the plan"),
1695
+ description: z2.string().describe("Task description"),
1696
+ priority: z2.enum(["low", "medium", "high"]).optional().describe("Priority (default: medium)"),
1697
+ notes: z2.string().optional().describe("Optional notes")
1698
+ },
1699
+ async (params) => {
1700
+ const task = sdk.createPlanTask(params);
1701
+ return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1702
+ }
1703
+ );
1704
+ server.tool(
1705
+ "updatePlanTask",
1706
+ "Update a plan task. Mark in_progress when starting, completed when done. Add notes for context.",
1707
+ {
1708
+ taskId: z2.string().describe("UUID of the task"),
1709
+ status: z2.enum(["pending", "in_progress", "completed"]).optional().describe("New status"),
1710
+ description: z2.string().optional().describe("New description"),
1711
+ priority: z2.enum(["low", "medium", "high"]).optional().describe("New priority"),
1712
+ notes: z2.string().nullable().optional().describe("Notes about progress or blockers")
1713
+ },
1714
+ async (params) => {
1715
+ const { taskId, ...updates } = params;
1716
+ const task = sdk.updatePlanTask(taskId, updates);
1717
+ const text2 = task ? JSON.stringify(task, null, 2) : "Task not found";
1718
+ return { content: [{ type: "text", text: text2 }] };
1719
+ }
1720
+ );
1721
+ server.tool(
1722
+ "listPlanTasks",
1723
+ "List all tasks for a plan, ordered by position. Use to check progress or resume work.",
1724
+ {
1725
+ planId: z2.string().describe("UUID of the plan")
1726
+ },
1727
+ async (params) => {
1728
+ const tasks = sdk.listPlanTasks(params.planId);
1729
+ return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
1730
+ }
1731
+ );
1732
+ return server;
1733
+ }
1734
+
1735
+ // src/index.ts
1736
+ async function main() {
1737
+ const sdk = new KnowledgeSDK();
1738
+ try {
1739
+ await sdk.initialize();
1740
+ } catch (error) {
1741
+ console.error("Failed to initialize AI Knowledge SDK:", error instanceof Error ? error.message : error);
1742
+ process.exit(1);
1743
+ }
1744
+ const server = createServer(sdk);
1745
+ const transport = new StdioServerTransport();
1746
+ await server.connect(transport);
1747
+ const shutdown = async () => {
1748
+ await sdk.close();
1749
+ process.exit(0);
1750
+ };
1751
+ process.on("SIGINT", shutdown);
1752
+ process.on("SIGTERM", shutdown);
1753
+ }
1754
+ main().catch((error) => {
1755
+ console.error("Fatal error:", error);
1756
+ process.exit(1);
1757
+ });