@a13xu/lucid 1.1.0 → 1.4.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 +21 -21
- package/README.md +117 -99
- package/build/database.d.ts +19 -0
- package/build/database.js +91 -62
- package/build/guardian/checklist.js +66 -66
- package/build/index.js +78 -0
- package/build/indexer/file.d.ts +15 -0
- package/build/indexer/file.js +100 -0
- package/build/indexer/project.d.ts +8 -0
- package/build/indexer/project.js +312 -0
- package/build/store/content.d.ts +3 -0
- package/build/store/content.js +11 -0
- package/build/tools/grep.d.ts +17 -0
- package/build/tools/grep.js +65 -0
- package/build/tools/init.d.ts +11 -0
- package/build/tools/init.js +110 -0
- package/build/tools/sync.d.ts +18 -0
- package/build/tools/sync.js +61 -0
- package/package.json +48 -48
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolve, join } from "path";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { indexProject } from "../indexer/project.js";
|
|
5
|
+
export const InitProjectSchema = z.object({
|
|
6
|
+
directory: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Instalează PostToolUse hook în .claude/settings.json
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
function installHook(dir) {
|
|
12
|
+
const claudeDir = join(dir, ".claude");
|
|
13
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
14
|
+
const HOOK_CMD = 'node -e "const p=process.argv[1]; if(p) require(\'child_process\').execSync(\'node \'+require(\'path\').resolve(\'node_modules/.bin/lucid\'||\'\')+\' --noop\', {stdio:\'ignore\'})" "$TOOL_INPUT_PATH" 2>/dev/null || true';
|
|
15
|
+
// Hook mai simplu și portabil: apelează sync_file prin claude mcp
|
|
16
|
+
const HOOK = {
|
|
17
|
+
matcher: "Write|Edit|NotebookEdit",
|
|
18
|
+
command: "echo '{\"tool\":\"sync_file\",\"path\":\"'\"$TOOL_INPUT_PATH\"'\"}' | true",
|
|
19
|
+
};
|
|
20
|
+
// Citim sau cream settings.json
|
|
21
|
+
let settings = {};
|
|
22
|
+
if (existsSync(settingsPath)) {
|
|
23
|
+
try {
|
|
24
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { installed: false, reason: "Could not parse .claude/settings.json" };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Verifică dacă hook-ul e deja instalat
|
|
31
|
+
const hooks = settings["hooks"] ?? {};
|
|
32
|
+
const postToolUse = hooks["PostToolUse"] ?? [];
|
|
33
|
+
const alreadyInstalled = postToolUse.some((h) => h.matcher?.includes("Write") && String(h).includes("lucid"));
|
|
34
|
+
if (alreadyInstalled) {
|
|
35
|
+
return { installed: false, reason: "already installed" };
|
|
36
|
+
}
|
|
37
|
+
// Adaugă hook-ul — notifică Claude să cheme sync_file
|
|
38
|
+
const lucidHook = {
|
|
39
|
+
matcher: "Write|Edit|NotebookEdit",
|
|
40
|
+
command: "echo '🔄 Lucid: call sync_file(path) to keep knowledge graph up to date'",
|
|
41
|
+
};
|
|
42
|
+
hooks["PostToolUse"] = [...postToolUse, lucidHook];
|
|
43
|
+
settings["hooks"] = hooks;
|
|
44
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
45
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
46
|
+
return { installed: true, reason: "hook added to .claude/settings.json" };
|
|
47
|
+
}
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Adaugă instrucțiune în CLAUDE.md
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
const LUCID_SYNC_INSTRUCTION = `
|
|
52
|
+
<!-- LUCID_SYNC -->
|
|
53
|
+
## 🔄 Lucid — Auto-sync after file changes
|
|
54
|
+
|
|
55
|
+
After EVERY Write, Edit, or NotebookEdit tool call, you MUST immediately call:
|
|
56
|
+
\`\`\`
|
|
57
|
+
sync_file(path="<path of the file you just wrote/edited>")
|
|
58
|
+
\`\`\`
|
|
59
|
+
|
|
60
|
+
This keeps the Lucid knowledge graph up to date with the latest code.
|
|
61
|
+
If multiple files changed (refactor, git pull), call sync_project() instead.
|
|
62
|
+
<!-- /LUCID_SYNC -->
|
|
63
|
+
`;
|
|
64
|
+
function injectClaudeMdInstruction(dir) {
|
|
65
|
+
const claudeMdPath = join(dir, "CLAUDE.md");
|
|
66
|
+
if (!existsSync(claudeMdPath))
|
|
67
|
+
return false;
|
|
68
|
+
const content = readFileSync(claudeMdPath, "utf-8");
|
|
69
|
+
if (content.includes("LUCID_SYNC"))
|
|
70
|
+
return false; // already injected
|
|
71
|
+
writeFileSync(claudeMdPath, content.trimEnd() + "\n" + LUCID_SYNC_INSTRUCTION, "utf-8");
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Main handler
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
export function handleInitProject(stmts, input) {
|
|
78
|
+
const dir = resolve(input.directory ?? process.cwd());
|
|
79
|
+
const results = indexProject(dir, stmts);
|
|
80
|
+
const lines = [`✅ Project indexed: ${dir}`, ``];
|
|
81
|
+
if (results.length === 0) {
|
|
82
|
+
lines.push("No indexable files found.");
|
|
83
|
+
lines.push("Expected: CLAUDE.md, package.json, README.md, src/");
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
lines.push(`Indexed ${results.length} source(s):`);
|
|
87
|
+
for (const r of results) {
|
|
88
|
+
lines.push(` • [${r.type}] "${r.entity}" — ${r.observations} observation(s) from ${r.source}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Instalează hook PostToolUse
|
|
92
|
+
lines.push(``);
|
|
93
|
+
const hookResult = installHook(dir);
|
|
94
|
+
if (hookResult.installed) {
|
|
95
|
+
lines.push(`🔗 Claude Code hook installed (.claude/settings.json)`);
|
|
96
|
+
lines.push(` After every Write/Edit, you will see a reminder to call sync_file().`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
lines.push(`🔗 Hook: ${hookResult.reason}`);
|
|
100
|
+
}
|
|
101
|
+
// Injectează instrucțiune în CLAUDE.md
|
|
102
|
+
const injected = injectClaudeMdInstruction(dir);
|
|
103
|
+
if (injected) {
|
|
104
|
+
lines.push(`📋 CLAUDE.md updated with sync_file() instruction`);
|
|
105
|
+
}
|
|
106
|
+
lines.push(``);
|
|
107
|
+
lines.push(`From now on, call sync_file(path) after every file you write or edit.`);
|
|
108
|
+
lines.push(`Use recall() to query accumulated project knowledge.`);
|
|
109
|
+
return lines.join("\n");
|
|
110
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Statements } from "../database.js";
|
|
3
|
+
export declare const SyncFileSchema: z.ZodObject<{
|
|
4
|
+
path: z.ZodString;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
path: string;
|
|
7
|
+
}, {
|
|
8
|
+
path: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function handleSyncFile(stmts: Statements, args: z.infer<typeof SyncFileSchema>): string;
|
|
11
|
+
export declare const SyncProjectSchema: z.ZodObject<{
|
|
12
|
+
directory: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
directory?: string | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
directory?: string | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function handleSyncProject(stmts: Statements, args: z.infer<typeof SyncProjectSchema>): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolve, extname } from "path";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { indexFile, upsertFileIndex } from "../indexer/file.js";
|
|
5
|
+
import { indexProject } from "../indexer/project.js";
|
|
6
|
+
const SUPPORTED_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]);
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// sync_file
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export const SyncFileSchema = z.object({
|
|
11
|
+
path: z.string().min(1),
|
|
12
|
+
});
|
|
13
|
+
export function handleSyncFile(stmts, args) {
|
|
14
|
+
const filepath = resolve(args.path);
|
|
15
|
+
if (!existsSync(filepath))
|
|
16
|
+
return `File not found: ${filepath}`;
|
|
17
|
+
if (!SUPPORTED_EXTS.has(extname(filepath).toLowerCase())) {
|
|
18
|
+
return `Unsupported file type: ${extname(filepath)}. Supported: ${[...SUPPORTED_EXTS].join(", ")}`;
|
|
19
|
+
}
|
|
20
|
+
const index = indexFile(filepath);
|
|
21
|
+
if (!index)
|
|
22
|
+
return `Could not read file: ${filepath}`;
|
|
23
|
+
const source = readFileSync(filepath, "utf-8");
|
|
24
|
+
const result = upsertFileIndex(index, source, stmts);
|
|
25
|
+
if (!result.stored) {
|
|
26
|
+
return `⏭️ Unchanged: ${filepath} (hash match — skipped)`;
|
|
27
|
+
}
|
|
28
|
+
const ratio = Math.round((1 - (result.savedBytes + Buffer.byteLength(source, "utf-8") - result.savedBytes) / Buffer.byteLength(source, "utf-8")) * 100);
|
|
29
|
+
const saved = Math.round(result.savedBytes / 1024 * 10) / 10;
|
|
30
|
+
const lines = [
|
|
31
|
+
`✅ Synced: ${filepath}`,
|
|
32
|
+
` exports: ${index.exports.join(", ") || "none"}`,
|
|
33
|
+
` compressed: saved ${saved}KB`,
|
|
34
|
+
];
|
|
35
|
+
if (index.description)
|
|
36
|
+
lines.push(` description: ${index.description}`);
|
|
37
|
+
if (index.todos.length > 0)
|
|
38
|
+
lines.push(` TODOs: ${index.todos.length} open`);
|
|
39
|
+
return lines.join("\n");
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// sync_project
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
export const SyncProjectSchema = z.object({
|
|
45
|
+
directory: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
export function handleSyncProject(stmts, args) {
|
|
48
|
+
const dir = resolve(args.directory ?? process.cwd());
|
|
49
|
+
const results = indexProject(dir, stmts);
|
|
50
|
+
if (results.length === 0)
|
|
51
|
+
return `No changes indexed in: ${dir}`;
|
|
52
|
+
const stats = stmts.fileStorageStats.get();
|
|
53
|
+
const ratio = stats.total_original > 0
|
|
54
|
+
? Math.round((1 - stats.total_compressed / stats.total_original) * 100)
|
|
55
|
+
: 0;
|
|
56
|
+
return [
|
|
57
|
+
`✅ Project re-synced: ${dir}`,
|
|
58
|
+
` ${results.length} source(s) updated`,
|
|
59
|
+
` Storage: ${Math.round(stats.total_compressed / 1024)}KB compressed / ${Math.round(stats.total_original / 1024)}KB original (${ratio}% saved)`,
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@a13xu/lucid",
|
|
3
|
-
"version": "1.
|
|
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
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@a13xu/lucid",
|
|
3
|
+
"version": "1.4.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
|
+
}
|