@a13xu/lucid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 a13xu
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,99 @@
1
+ # @a13xu/lucid
2
+
3
+ Persistent memory for Claude Code agents, backed by **SQLite + FTS5**.
4
+
5
+ Stores a knowledge graph (entities, relations, observations) with full-text search — indexed queries under 1ms, no JSON files, no linear scans.
6
+
7
+ ## Install
8
+
9
+ **Requirements:** Node.js 18+
10
+
11
+ ```bash
12
+ # Run directly (no install needed)
13
+ npx @a13xu/lucid
14
+
15
+ # Or install globally
16
+ npm install -g @a13xu/lucid
17
+ lucid
18
+ ```
19
+
20
+ ### Add to Claude Code
21
+
22
+ ```bash
23
+ claude mcp add --transport stdio lucid -- npx -y @a13xu/lucid
24
+ ```
25
+
26
+ Or add manually to `.mcp.json` in your project root:
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "lucid": {
32
+ "type": "stdio",
33
+ "command": "npx",
34
+ "args": ["-y", "@a13xu/lucid"],
35
+ "env": {
36
+ "MEMORY_DB_PATH": "/your/project/.claude/memory.db"
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Default DB path: `~/.claude/memory.db`
44
+
45
+ ## Why SQLite + FTS5 instead of JSON?
46
+
47
+ | | JSON file | SQLite + FTS5 |
48
+ |---|---|---|
49
+ | Search | O(n) linear scan | O(log n) indexed |
50
+ | Write | Rewrite entire file | Atomic incremental |
51
+ | Concurrent reads | Lock entire file | WAL mode |
52
+ | Stemming / unicode | Manual | Built-in |
53
+
54
+ ## Tools (6)
55
+
56
+ | Tool | Description |
57
+ |---|---|
58
+ | `remember` | Store a fact about an entity (project, person, tool, decision…) |
59
+ | `relate` | Create a directed relationship between two entities |
60
+ | `recall` | Full-text search across all memory (FTS5 + LIKE fallback) |
61
+ | `recall_all` | Return the entire knowledge graph with stats |
62
+ | `forget` | Remove an entity and all its relations |
63
+ | `memory_stats` | DB size, WAL status, entity/relation counts |
64
+
65
+ ### Entity types
66
+ `person` · `project` · `decision` · `pattern` · `tool` · `config` · `bug` · `convention`
67
+
68
+ ### Relation types
69
+ `uses` · `depends_on` · `created_by` · `part_of` · `replaced_by` · `conflicts_with` · `tested_by`
70
+
71
+ ## Usage examples
72
+
73
+ ```
74
+ You: "Remember that this project uses PostgreSQL with Prisma ORM"
75
+ Claude: [calls remember] → Created "PostgreSQL" [tool]
76
+
77
+ You: "What do you know about the database?"
78
+ Claude: [calls recall("database PostgreSQL")] → returns entity + observations
79
+
80
+ You: "How are the services connected?"
81
+ Claude: [calls recall_all] → returns full knowledge graph
82
+ ```
83
+
84
+ ## Debugging
85
+
86
+ ```bash
87
+ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"test","version":"1.0"},"protocolVersion":"2024-11-05"}}' \
88
+ | npx @a13xu/lucid
89
+ ```
90
+
91
+ In Claude Code: run `/mcp` — you should see `lucid` listed with 6 tools.
92
+
93
+ ## Tech stack
94
+
95
+ - **Runtime:** Node.js 18+, TypeScript, ES modules
96
+ - **MCP SDK:** `@modelcontextprotocol/sdk`
97
+ - **Database:** `better-sqlite3` (synchronous, no async overhead)
98
+ - **Validation:** `zod`
99
+ - **Transport:** stdio
@@ -0,0 +1,34 @@
1
+ import Database from "better-sqlite3";
2
+ import type { EntityRow, RelationRow } from "./types.js";
3
+ export declare function initDatabase(): Database.Database;
4
+ type Stmt<P extends unknown[], R> = Database.Statement<P, R>;
5
+ type WriteStmt<P extends unknown[]> = Database.Statement<P, unknown>;
6
+ export interface Statements {
7
+ getEntityByName: Stmt<[string], EntityRow>;
8
+ insertEntity: WriteStmt<[string, string, string]>;
9
+ updateEntity: WriteStmt<[string, number]>;
10
+ deleteEntity: WriteStmt<[string]>;
11
+ getAllEntities: Stmt<[], EntityRow>;
12
+ getRelationsForEntity: Stmt<[number, number], RelationRow & {
13
+ from_name: string;
14
+ to_name: string;
15
+ }>;
16
+ insertRelation: WriteStmt<[number, number, string]>;
17
+ searchFTS: Stmt<[string], EntityRow>;
18
+ searchLike: Stmt<[string, string, string], EntityRow>;
19
+ countEntities: Stmt<[], {
20
+ count: number;
21
+ }>;
22
+ countRelations: Stmt<[], {
23
+ count: number;
24
+ }>;
25
+ getWalMode: Stmt<[], {
26
+ journal_mode: string;
27
+ }>;
28
+ getAllRelations: Stmt<[], RelationRow & {
29
+ from_name: string;
30
+ to_name: string;
31
+ }>;
32
+ }
33
+ export declare function prepareStatements(db: Database.Database): Statements;
34
+ export {};
@@ -0,0 +1,114 @@
1
+ import Database from "better-sqlite3";
2
+ import { homedir } from "os";
3
+ import { mkdirSync } from "fs";
4
+ import { dirname, join } from "path";
5
+ // ---------------------------------------------------------------------------
6
+ // DB path
7
+ // ---------------------------------------------------------------------------
8
+ function resolveDbPath() {
9
+ const envPath = process.env["MEMORY_DB_PATH"];
10
+ if (envPath)
11
+ return envPath;
12
+ return join(homedir(), ".claude", "memory.db");
13
+ }
14
+ // ---------------------------------------------------------------------------
15
+ // Init
16
+ // ---------------------------------------------------------------------------
17
+ export function initDatabase() {
18
+ const dbPath = resolveDbPath();
19
+ mkdirSync(dirname(dbPath), { recursive: true });
20
+ const db = new Database(dbPath);
21
+ // Pragmas obligatorii
22
+ db.pragma("journal_mode = WAL");
23
+ db.pragma("synchronous = NORMAL");
24
+ db.pragma("cache_size = -8000");
25
+ db.pragma("temp_store = MEMORY");
26
+ db.pragma("mmap_size = 67108864");
27
+ db.pragma("foreign_keys = ON");
28
+ createSchema(db);
29
+ console.error(`[lucid] DB: ${dbPath}`);
30
+ return db;
31
+ }
32
+ function createSchema(db) {
33
+ db.exec(`
34
+ CREATE TABLE IF NOT EXISTS entities (
35
+ id INTEGER PRIMARY KEY,
36
+ name TEXT NOT NULL UNIQUE COLLATE NOCASE,
37
+ type TEXT NOT NULL,
38
+ observations TEXT NOT NULL DEFAULT '[]',
39
+ created_at INTEGER DEFAULT (unixepoch()),
40
+ updated_at INTEGER DEFAULT (unixepoch())
41
+ );
42
+
43
+ CREATE TABLE IF NOT EXISTS relations (
44
+ id INTEGER PRIMARY KEY,
45
+ from_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
46
+ to_entity INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
47
+ relation_type TEXT NOT NULL,
48
+ created_at INTEGER DEFAULT (unixepoch()),
49
+ UNIQUE(from_entity, to_entity, relation_type)
50
+ );
51
+
52
+ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
53
+ name,
54
+ type,
55
+ observations,
56
+ content='entities',
57
+ content_rowid='id',
58
+ tokenize='porter unicode61'
59
+ );
60
+
61
+ CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN
62
+ INSERT INTO entities_fts(rowid, name, type, observations)
63
+ VALUES (new.id, new.name, new.type, new.observations);
64
+ END;
65
+
66
+ CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN
67
+ INSERT INTO entities_fts(entities_fts, rowid, name, type, observations)
68
+ VALUES('delete', old.id, old.name, old.type, old.observations);
69
+ INSERT INTO entities_fts(rowid, name, type, observations)
70
+ VALUES (new.id, new.name, new.type, new.observations);
71
+ END;
72
+
73
+ CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN
74
+ INSERT INTO entities_fts(entities_fts, rowid, name, type, observations)
75
+ VALUES('delete', old.id, old.name, old.type, old.observations);
76
+ END;
77
+
78
+ CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity);
79
+ CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity);
80
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
81
+ `);
82
+ }
83
+ export function prepareStatements(db) {
84
+ return {
85
+ getEntityByName: db.prepare("SELECT * FROM entities WHERE name = ? COLLATE NOCASE"),
86
+ insertEntity: db.prepare("INSERT INTO entities (name, type, observations) VALUES (?, ?, ?)"),
87
+ // SQL: SET observations = ?, WHERE id = ? → 2 params
88
+ updateEntity: db.prepare("UPDATE entities SET observations = ?, updated_at = unixepoch() WHERE id = ?"),
89
+ deleteEntity: db.prepare("DELETE FROM entities WHERE name = ? COLLATE NOCASE"),
90
+ getAllEntities: db.prepare("SELECT * FROM entities ORDER BY updated_at DESC"),
91
+ getRelationsForEntity: db.prepare(`SELECT r.*, ef.name AS from_name, et.name AS to_name
92
+ FROM relations r
93
+ JOIN entities ef ON r.from_entity = ef.id
94
+ JOIN entities et ON r.to_entity = et.id
95
+ WHERE r.from_entity = ? OR r.to_entity = ?`),
96
+ insertRelation: db.prepare("INSERT OR IGNORE INTO relations (from_entity, to_entity, relation_type) VALUES (?, ?, ?)"),
97
+ searchFTS: db.prepare(`SELECT e.* FROM entities_fts
98
+ JOIN entities e ON entities_fts.rowid = e.id
99
+ WHERE entities_fts MATCH ?
100
+ ORDER BY rank
101
+ LIMIT 20`),
102
+ searchLike: db.prepare(`SELECT * FROM entities
103
+ WHERE name LIKE ? OR type LIKE ? OR observations LIKE ?
104
+ ORDER BY updated_at DESC
105
+ LIMIT 20`),
106
+ countEntities: db.prepare("SELECT COUNT(*) as count FROM entities"),
107
+ countRelations: db.prepare("SELECT COUNT(*) as count FROM relations"),
108
+ getWalMode: db.prepare("PRAGMA journal_mode"),
109
+ getAllRelations: db.prepare(`SELECT r.*, ef.name AS from_name, et.name AS to_name
110
+ FROM relations r
111
+ JOIN entities ef ON r.from_entity = ef.id
112
+ JOIN entities et ON r.to_entity = et.id`),
113
+ };
114
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { z } from "zod";
6
+ import { initDatabase, prepareStatements } from "./database.js";
7
+ import { remember, RememberSchema } from "./tools/remember.js";
8
+ import { relate, RelateSchema } from "./tools/relate.js";
9
+ import { recall, RecallSchema } from "./tools/recall.js";
10
+ import { recallAll } from "./tools/recall-all.js";
11
+ import { forget, ForgetSchema } from "./tools/forget.js";
12
+ import { memoryStats } from "./tools/stats.js";
13
+ // ---------------------------------------------------------------------------
14
+ // Init DB
15
+ // ---------------------------------------------------------------------------
16
+ const db = initDatabase();
17
+ const stmts = prepareStatements(db);
18
+ // ---------------------------------------------------------------------------
19
+ // MCP Server
20
+ // ---------------------------------------------------------------------------
21
+ const server = new Server({ name: "lucid", version: "1.0.0" }, { capabilities: { tools: {} } });
22
+ // ---------------------------------------------------------------------------
23
+ // Tool definitions
24
+ // ---------------------------------------------------------------------------
25
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
26
+ tools: [
27
+ {
28
+ name: "remember",
29
+ description: "Store a fact, decision, or observation about an entity in the knowledge graph.",
30
+ inputSchema: {
31
+ type: "object",
32
+ properties: {
33
+ entity: { type: "string", description: "Entity name (project, person, concept, tool)" },
34
+ entityType: {
35
+ type: "string",
36
+ enum: ["person", "project", "decision", "pattern", "tool", "config", "bug", "convention"],
37
+ },
38
+ observation: { type: "string", description: "The fact to remember" },
39
+ },
40
+ required: ["entity", "entityType", "observation"],
41
+ },
42
+ },
43
+ {
44
+ name: "relate",
45
+ description: "Create a directed relationship between two entities in the knowledge graph.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ from: { type: "string", description: "Source entity name" },
50
+ to: { type: "string", description: "Target entity name" },
51
+ relationType: {
52
+ type: "string",
53
+ enum: ["uses", "depends_on", "created_by", "part_of", "replaced_by", "conflicts_with", "tested_by"],
54
+ },
55
+ },
56
+ required: ["from", "to", "relationType"],
57
+ },
58
+ },
59
+ {
60
+ name: "recall",
61
+ description: "Search memory using full-text search. Fast, indexed, supports partial matches and stemming.",
62
+ inputSchema: {
63
+ type: "object",
64
+ properties: {
65
+ query: { type: "string", description: "Search terms" },
66
+ },
67
+ required: ["query"],
68
+ },
69
+ },
70
+ {
71
+ name: "recall_all",
72
+ description: "Get the entire knowledge graph with statistics.",
73
+ inputSchema: { type: "object", properties: {} },
74
+ },
75
+ {
76
+ name: "forget",
77
+ description: "Remove an entity and all its relations from memory.",
78
+ inputSchema: {
79
+ type: "object",
80
+ properties: {
81
+ entity: { type: "string", description: "Entity name to remove" },
82
+ },
83
+ required: ["entity"],
84
+ },
85
+ },
86
+ {
87
+ name: "memory_stats",
88
+ description: "Get memory usage statistics.",
89
+ inputSchema: { type: "object", properties: {} },
90
+ },
91
+ ],
92
+ }));
93
+ // ---------------------------------------------------------------------------
94
+ // Tool handlers
95
+ // ---------------------------------------------------------------------------
96
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
97
+ const { name, arguments: args } = request.params;
98
+ try {
99
+ let text;
100
+ switch (name) {
101
+ case "remember": {
102
+ const input = RememberSchema.parse(args);
103
+ text = remember(stmts, input);
104
+ break;
105
+ }
106
+ case "relate": {
107
+ const input = RelateSchema.parse(args);
108
+ text = relate(stmts, input);
109
+ break;
110
+ }
111
+ case "recall": {
112
+ const input = RecallSchema.parse(args);
113
+ text = recall(stmts, input);
114
+ break;
115
+ }
116
+ case "recall_all": {
117
+ text = recallAll(db, stmts);
118
+ break;
119
+ }
120
+ case "forget": {
121
+ const input = ForgetSchema.parse(args);
122
+ text = forget(stmts, input);
123
+ break;
124
+ }
125
+ case "memory_stats": {
126
+ text = memoryStats(db, stmts);
127
+ break;
128
+ }
129
+ default:
130
+ return {
131
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
132
+ isError: true,
133
+ };
134
+ }
135
+ return { content: [{ type: "text", text }] };
136
+ }
137
+ catch (err) {
138
+ const message = err instanceof z.ZodError
139
+ ? `Validation error: ${err.errors.map((e) => e.message).join(", ")}`
140
+ : err instanceof Error
141
+ ? err.message
142
+ : String(err);
143
+ return {
144
+ content: [{ type: "text", text: `Error: ${message}` }],
145
+ isError: true,
146
+ };
147
+ }
148
+ });
149
+ // ---------------------------------------------------------------------------
150
+ // Start
151
+ // ---------------------------------------------------------------------------
152
+ const transport = new StdioServerTransport();
153
+ await server.connect(transport);
154
+ console.error("[lucid] Server started on stdio.");
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ import type { Statements } from "../database.js";
3
+ export declare const ForgetSchema: z.ZodObject<{
4
+ entity: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ entity: string;
7
+ }, {
8
+ entity: string;
9
+ }>;
10
+ export type ForgetInput = z.infer<typeof ForgetSchema>;
11
+ export declare function forget(stmts: Statements, input: ForgetInput): string;
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ export const ForgetSchema = z.object({
3
+ entity: z.string().min(1),
4
+ });
5
+ export function forget(stmts, input) {
6
+ const existing = stmts.getEntityByName.get(input.entity);
7
+ if (!existing) {
8
+ return `Entity "${input.entity}" not found in memory.`;
9
+ }
10
+ // ON DELETE CASCADE șterge relațiile automat
11
+ stmts.deleteEntity.run(input.entity);
12
+ return `Removed "${input.entity}" and all its relations from memory.`;
13
+ }
@@ -0,0 +1,3 @@
1
+ import Database from "better-sqlite3";
2
+ import type { Statements } from "../database.js";
3
+ export declare function recallAll(db: Database.Database, stmts: Statements): string;
@@ -0,0 +1,45 @@
1
+ import { statSync } from "fs";
2
+ export function recallAll(db, stmts) {
3
+ const entityCount = stmts.countEntities.get().count;
4
+ const relationCount = stmts.countRelations.get().count;
5
+ const allEntities = stmts.getAllEntities.all();
6
+ const allRelations = stmts.getAllRelations.all();
7
+ // Numără toate observațiile
8
+ let observationCount = 0;
9
+ const entities = allEntities.map((row) => {
10
+ const observations = JSON.parse(row.observations);
11
+ observationCount += observations.length;
12
+ const relations = allRelations
13
+ .filter((r) => r.from_entity === row.id || r.to_entity === row.id)
14
+ .map((r) => ({ from: r.from_name, to: r.to_name, type: r.relation_type }));
15
+ return {
16
+ id: row.id,
17
+ name: row.name,
18
+ type: row.type,
19
+ observations,
20
+ created_at: row.created_at,
21
+ updated_at: row.updated_at,
22
+ relations,
23
+ };
24
+ });
25
+ let dbSizeBytes = 0;
26
+ try {
27
+ dbSizeBytes = statSync(db.name).size;
28
+ }
29
+ catch {
30
+ // fișierul poate fi in-memory sau inaccesibil
31
+ }
32
+ const graph = {
33
+ stats: {
34
+ entity_count: entityCount,
35
+ relation_count: relationCount,
36
+ observation_count: observationCount,
37
+ db_size_bytes: dbSizeBytes,
38
+ db_size_kb: Math.round(dbSizeBytes / 1024),
39
+ wal_mode: true,
40
+ fts5_enabled: true,
41
+ },
42
+ entities,
43
+ };
44
+ return JSON.stringify(graph, null, 2);
45
+ }
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ import type { Statements } from "../database.js";
3
+ export declare const RecallSchema: z.ZodObject<{
4
+ query: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ query: string;
7
+ }, {
8
+ query: string;
9
+ }>;
10
+ export type RecallInput = z.infer<typeof RecallSchema>;
11
+ export declare function recall(stmts: Statements, input: RecallInput): string;
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ export const RecallSchema = z.object({
3
+ query: z.string().min(1),
4
+ });
5
+ function sanitizeFTSQuery(query) {
6
+ return query
7
+ .replace(/[^\w\s\u00C0-\u024F]/g, "") // păstrează litere + diacritice
8
+ .trim()
9
+ .split(/\s+/)
10
+ .filter(Boolean)
11
+ .join(" OR ");
12
+ }
13
+ function buildEntityWithRelations(stmts, row) {
14
+ const observations = JSON.parse(row.observations);
15
+ const relRows = stmts.getRelationsForEntity.all(row.id, row.id);
16
+ const relations = relRows.map((r) => ({
17
+ from: r.from_name,
18
+ to: r.to_name,
19
+ type: r.relation_type,
20
+ }));
21
+ return {
22
+ id: row.id,
23
+ name: row.name,
24
+ type: row.type,
25
+ observations,
26
+ created_at: row.created_at,
27
+ updated_at: row.updated_at,
28
+ relations,
29
+ };
30
+ }
31
+ export function recall(stmts, input) {
32
+ const sanitized = sanitizeFTSQuery(input.query);
33
+ let rows = [];
34
+ if (sanitized) {
35
+ try {
36
+ rows = stmts.searchFTS.all(sanitized);
37
+ }
38
+ catch {
39
+ // FTS query invalid — fallback la LIKE
40
+ console.error(`[lucid] FTS fallback for query: ${sanitized}`);
41
+ }
42
+ }
43
+ // Fallback LIKE dacă FTS nu a returnat rezultate
44
+ if (rows.length === 0) {
45
+ const like = `%${input.query}%`;
46
+ rows = stmts.searchLike.all(like, like, like);
47
+ }
48
+ if (rows.length === 0) {
49
+ return `No results found for "${input.query}".`;
50
+ }
51
+ const entities = rows.map((row) => buildEntityWithRelations(stmts, row));
52
+ return JSON.stringify(entities, null, 2);
53
+ }
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import type { Statements } from "../database.js";
3
+ export declare const RelateSchema: z.ZodObject<{
4
+ from: z.ZodString;
5
+ to: z.ZodString;
6
+ relationType: z.ZodEnum<["uses", "depends_on", "created_by", "part_of", "replaced_by", "conflicts_with", "tested_by"]>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ from: string;
9
+ to: string;
10
+ relationType: "uses" | "depends_on" | "created_by" | "part_of" | "replaced_by" | "conflicts_with" | "tested_by";
11
+ }, {
12
+ from: string;
13
+ to: string;
14
+ relationType: "uses" | "depends_on" | "created_by" | "part_of" | "replaced_by" | "conflicts_with" | "tested_by";
15
+ }>;
16
+ export type RelateInput = z.infer<typeof RelateSchema>;
17
+ export declare function relate(stmts: Statements, input: RelateInput): string;
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ export const RelateSchema = z.object({
3
+ from: z.string().min(1),
4
+ to: z.string().min(1),
5
+ relationType: z.enum([
6
+ "uses", "depends_on", "created_by", "part_of",
7
+ "replaced_by", "conflicts_with", "tested_by",
8
+ ]),
9
+ });
10
+ export function relate(stmts, input) {
11
+ const fromEntity = stmts.getEntityByName.get(input.from);
12
+ if (!fromEntity) {
13
+ return `Error: Entity "${input.from}" not found. Use remember() to create it first.`;
14
+ }
15
+ const toEntity = stmts.getEntityByName.get(input.to);
16
+ if (!toEntity) {
17
+ return `Error: Entity "${input.to}" not found. Use remember() to create it first.`;
18
+ }
19
+ stmts.insertRelation.run(fromEntity.id, toEntity.id, input.relationType);
20
+ return `${input.from} --[${input.relationType}]--> ${input.to}`;
21
+ }
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import type { Statements } from "../database.js";
3
+ export declare const RememberSchema: z.ZodObject<{
4
+ entity: z.ZodString;
5
+ entityType: z.ZodEnum<["person", "project", "decision", "pattern", "tool", "config", "bug", "convention"]>;
6
+ observation: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ entity: string;
9
+ entityType: "person" | "project" | "decision" | "pattern" | "tool" | "config" | "bug" | "convention";
10
+ observation: string;
11
+ }, {
12
+ entity: string;
13
+ entityType: "person" | "project" | "decision" | "pattern" | "tool" | "config" | "bug" | "convention";
14
+ observation: string;
15
+ }>;
16
+ export type RememberInput = z.infer<typeof RememberSchema>;
17
+ export declare function remember(stmts: Statements, input: RememberInput): string;
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ export const RememberSchema = z.object({
3
+ entity: z.string().min(1),
4
+ entityType: z.enum([
5
+ "person", "project", "decision", "pattern",
6
+ "tool", "config", "bug", "convention",
7
+ ]),
8
+ observation: z.string().min(1),
9
+ });
10
+ export function remember(stmts, input) {
11
+ const { entity, entityType, observation } = input;
12
+ const existing = stmts.getEntityByName.get(entity);
13
+ if (existing) {
14
+ const observations = JSON.parse(existing.observations);
15
+ // Nu adăuga duplicate
16
+ if (!observations.includes(observation)) {
17
+ observations.push(observation);
18
+ stmts.updateEntity.run(JSON.stringify(observations), existing.id);
19
+ }
20
+ return `Updated "${entity}" [${existing.type}] — ${observations.length} observation(s) total.`;
21
+ }
22
+ else {
23
+ stmts.insertEntity.run(entity, entityType, JSON.stringify([observation]));
24
+ return `Created "${entity}" [${entityType}].`;
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ import Database from "better-sqlite3";
2
+ import type { Statements } from "../database.js";
3
+ export declare function memoryStats(db: Database.Database, stmts: Statements): string;
@@ -0,0 +1,30 @@
1
+ import { statSync } from "fs";
2
+ export function memoryStats(db, stmts) {
3
+ const entityCount = stmts.countEntities.get().count;
4
+ const relationCount = stmts.countRelations.get().count;
5
+ const walMode = stmts.getWalMode.get().journal_mode === "wal";
6
+ // Numără observațiile
7
+ const allEntities = stmts.getAllEntities.all();
8
+ let observationCount = 0;
9
+ for (const row of allEntities) {
10
+ const obs = JSON.parse(row.observations);
11
+ observationCount += obs.length;
12
+ }
13
+ let dbSizeBytes = 0;
14
+ try {
15
+ dbSizeBytes = statSync(db.name).size;
16
+ }
17
+ catch {
18
+ // in-memory sau inaccesibil
19
+ }
20
+ const stats = {
21
+ entity_count: entityCount,
22
+ relation_count: relationCount,
23
+ observation_count: observationCount,
24
+ db_size_bytes: dbSizeBytes,
25
+ db_size_kb: Math.round(dbSizeBytes / 1024),
26
+ wal_mode: walMode,
27
+ fts5_enabled: true,
28
+ };
29
+ return JSON.stringify(stats, null, 2);
30
+ }
@@ -0,0 +1,53 @@
1
+ export type EntityType = "person" | "project" | "decision" | "pattern" | "tool" | "config" | "bug" | "convention";
2
+ export type RelationType = "uses" | "depends_on" | "created_by" | "part_of" | "replaced_by" | "conflicts_with" | "tested_by";
3
+ export interface Entity {
4
+ id: number;
5
+ name: string;
6
+ type: EntityType;
7
+ observations: string[];
8
+ created_at: number;
9
+ updated_at: number;
10
+ }
11
+ export interface EntityRow {
12
+ id: number;
13
+ name: string;
14
+ type: string;
15
+ observations: string;
16
+ created_at: number;
17
+ updated_at: number;
18
+ }
19
+ export interface Relation {
20
+ id: number;
21
+ from_entity: number;
22
+ to_entity: number;
23
+ relation_type: RelationType;
24
+ created_at: number;
25
+ }
26
+ export interface RelationRow {
27
+ id: number;
28
+ from_entity: number;
29
+ to_entity: number;
30
+ relation_type: string;
31
+ created_at: number;
32
+ }
33
+ export interface EntityWithRelations extends Entity {
34
+ relations: RelationDisplay[];
35
+ }
36
+ export interface RelationDisplay {
37
+ from: string;
38
+ to: string;
39
+ type: string;
40
+ }
41
+ export interface MemoryStats {
42
+ entity_count: number;
43
+ relation_count: number;
44
+ observation_count: number;
45
+ db_size_bytes: number;
46
+ db_size_kb: number;
47
+ wal_mode: boolean;
48
+ fts5_enabled: boolean;
49
+ }
50
+ export interface KnowledgeGraph {
51
+ stats: MemoryStats;
52
+ entities: EntityWithRelations[];
53
+ }
package/build/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@a13xu/lucid",
3
+ "version": "1.0.0",
4
+ "description": "Persistent memory for Claude Code agents — SQLite + FTS5 knowledge graph via MCP",
5
+ "type": "module",
6
+ "bin": {
7
+ "lucid": "./build/index.js"
8
+ },
9
+ "files": [
10
+ "build/**/*.js",
11
+ "build/**/*.d.ts",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "claude",
21
+ "claude-code",
22
+ "memory",
23
+ "sqlite",
24
+ "knowledge-graph",
25
+ "ai",
26
+ "anthropic"
27
+ ],
28
+ "author": "a13xu",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/a13xu/lucid.git"
33
+ },
34
+ "homepage": "https://github.com/a13xu/lucid#readme",
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "dependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.0.0",
40
+ "better-sqlite3": "^11.0.0",
41
+ "zod": "^3.23.8"
42
+ },
43
+ "devDependencies": {
44
+ "@types/better-sqlite3": "^7.6.11",
45
+ "@types/node": "^20.0.0",
46
+ "typescript": "^5.4.0"
47
+ }
48
+ }