@a13xu/lucid 1.9.1 → 1.9.4
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/README.md +20 -3
- package/build/index.js +2 -2
- package/build/indexer/ast.js +28 -0
- package/build/indexer/file.js +34 -0
- package/build/indexer/project.js +83 -3
- package/build/tools/init.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,7 +9,11 @@ Stores a persistent knowledge graph (entities, relations, observations), indexes
|
|
|
9
9
|
**Requirements:** Node.js 18+
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
#
|
|
12
|
+
# Option 1 — global install (recommended, faster startup)
|
|
13
|
+
npm install -g @a13xu/lucid
|
|
14
|
+
claude mcp add --transport stdio lucid -- lucid
|
|
15
|
+
|
|
16
|
+
# Option 2 — no install needed (uses npx on each start)
|
|
13
17
|
claude mcp add --transport stdio lucid -- npx -y @a13xu/lucid
|
|
14
18
|
```
|
|
15
19
|
|
|
@@ -43,7 +47,7 @@ Default DB path: `~/.claude/memory.db`
|
|
|
43
47
|
6. "What do we know?" → recall("query") → knowledge graph search
|
|
44
48
|
```
|
|
45
49
|
|
|
46
|
-
## Tools (
|
|
50
|
+
## Tools (20)
|
|
47
51
|
|
|
48
52
|
### Memory
|
|
49
53
|
| Tool | Description |
|
|
@@ -76,6 +80,19 @@ Default DB path: `~/.claude/memory.db`
|
|
|
76
80
|
| `check_drift` | Analyze a code snippet inline without saving to disk. |
|
|
77
81
|
| `get_checklist` | Return the full 5-pass validation protocol (Logic Trace, Contract Verification, Stupid Mistakes, Integration Sanity, Explain It). |
|
|
78
82
|
|
|
83
|
+
### Reward system
|
|
84
|
+
| Tool | Description |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `reward` | Signal that the last `get_context()` result was helpful (+1). Rewarded files rank higher in future similar queries. |
|
|
87
|
+
| `penalize` | Signal that the last `get_context()` result was unhelpful (-1). Penalized files rank lower in future queries. |
|
|
88
|
+
| `show_rewards` | Show top rewarded experiences and most rewarded files. Rewards decay exponentially (half-life ~14 days). |
|
|
89
|
+
|
|
90
|
+
### Code Quality Guard
|
|
91
|
+
| Tool | Description |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `coding_rules` | Get the 25 Golden Rules checklist — naming, single responsibility, file/function size, error handling, frontend component rules, architecture separation. |
|
|
94
|
+
| `check_code_quality` | Analyze a file or snippet against the 25 Golden Rules. Detects file/function bloat, vague naming, deep nesting, dead code, and for React/Vue files: prop explosion, inline styles, fetch-in-component, direct DOM access. Complements `validate_file`. |
|
|
95
|
+
|
|
79
96
|
## Token optimization in depth
|
|
80
97
|
|
|
81
98
|
### How `get_context` works
|
|
@@ -207,7 +224,7 @@ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},
|
|
|
207
224
|
| npx @a13xu/lucid
|
|
208
225
|
```
|
|
209
226
|
|
|
210
|
-
In Claude Code: run `/mcp` — you should see `lucid` with
|
|
227
|
+
In Claude Code: run `/mcp` — you should see `lucid` with 20 tools.
|
|
211
228
|
|
|
212
229
|
## Tech stack
|
|
213
230
|
|
package/build/index.js
CHANGED
|
@@ -52,7 +52,7 @@ else {
|
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
53
53
|
// MCP Server
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
|
-
const server = new Server({ name: "lucid", version: "1.9.
|
|
55
|
+
const server = new Server({ name: "lucid", version: "1.9.4" }, { capabilities: { tools: {} } });
|
|
56
56
|
// ---------------------------------------------------------------------------
|
|
57
57
|
// Tool definitions
|
|
58
58
|
// ---------------------------------------------------------------------------
|
|
@@ -178,7 +178,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
178
178
|
type: "object",
|
|
179
179
|
properties: {
|
|
180
180
|
pattern: { type: "string", description: "Regex pattern to search for." },
|
|
181
|
-
language: { type: "string", enum: ["python", "javascript", "typescript", "generic"], description: "Filter by language." },
|
|
181
|
+
language: { type: "string", enum: ["python", "javascript", "typescript", "vue", "generic"], description: "Filter by language." },
|
|
182
182
|
context: { type: "number", description: "Lines of context before/after each match (0-10, default 2)." },
|
|
183
183
|
},
|
|
184
184
|
required: ["pattern"],
|
package/build/indexer/ast.js
CHANGED
|
@@ -108,6 +108,32 @@ function skeletonPython(source) {
|
|
|
108
108
|
return { imports, exports, todos, summary };
|
|
109
109
|
}
|
|
110
110
|
// ---------------------------------------------------------------------------
|
|
111
|
+
// Vue SFC
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
function skeletonVue(source) {
|
|
114
|
+
// Extract <script> or <script setup> block and run TS skeleton on it
|
|
115
|
+
const scriptMatch = source.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
116
|
+
const sk = scriptMatch ? skeletonTS(scriptMatch[1]) : { imports: [], exports: [], todos: [], summary: "" };
|
|
117
|
+
// Prepend Vue macro signatures (defineProps, defineEmits, defineExpose)
|
|
118
|
+
const scriptContent = scriptMatch?.[1] ?? "";
|
|
119
|
+
for (const macro of ["defineProps", "defineEmits", "defineExpose"]) {
|
|
120
|
+
const m = scriptContent.match(new RegExp(`${macro}[\\s\\S]*?(?=\\n\\n|\\n[^\\s]|$)`, "m"));
|
|
121
|
+
if (m)
|
|
122
|
+
sk.exports.unshift(m[0].split("\n")[0].slice(0, 120));
|
|
123
|
+
}
|
|
124
|
+
// HTML comment as summary fallback
|
|
125
|
+
if (!sk.summary) {
|
|
126
|
+
const htmlComment = source.match(/<!--\s*([\s\S]*?)\s*-->/)?.[1];
|
|
127
|
+
if (htmlComment)
|
|
128
|
+
sk.summary = htmlComment.replace(/\n/g, " ").trim().slice(0, 150);
|
|
129
|
+
}
|
|
130
|
+
// Also note top-level template structure (first tag inside <template>)
|
|
131
|
+
const templateMatch = source.match(/<template[^>]*>\s*<(\w[\w-]*)/);
|
|
132
|
+
if (templateMatch)
|
|
133
|
+
sk.exports.unshift(`<template> root: <${templateMatch[1]}>`);
|
|
134
|
+
return sk;
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
111
137
|
// Generic (markdown, yaml, json, etc.)
|
|
112
138
|
// ---------------------------------------------------------------------------
|
|
113
139
|
function skeletonGeneric(source) {
|
|
@@ -135,6 +161,8 @@ export function extractSkeleton(source, language) {
|
|
|
135
161
|
return skeletonTS(source);
|
|
136
162
|
case "python":
|
|
137
163
|
return skeletonPython(source);
|
|
164
|
+
case "vue":
|
|
165
|
+
return skeletonVue(source);
|
|
138
166
|
default:
|
|
139
167
|
return skeletonGeneric(source);
|
|
140
168
|
}
|
package/build/indexer/file.js
CHANGED
|
@@ -36,6 +36,36 @@ function extractPython(source) {
|
|
|
36
36
|
}
|
|
37
37
|
return { exports, description, todos };
|
|
38
38
|
}
|
|
39
|
+
function extractVue(source) {
|
|
40
|
+
const exports = [];
|
|
41
|
+
const todos = [];
|
|
42
|
+
// Extract <script> or <script setup> block
|
|
43
|
+
const scriptMatch = source.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
44
|
+
const scriptContent = scriptMatch?.[1] ?? "";
|
|
45
|
+
// defineExpose({ foo, bar }) — symbols available to parent components
|
|
46
|
+
const exposeMatch = scriptContent.match(/defineExpose\(\s*\{([^}]+)\}/);
|
|
47
|
+
if (exposeMatch) {
|
|
48
|
+
for (const m of exposeMatch[1].matchAll(/\b([a-zA-Z_]\w*)\b/g)) {
|
|
49
|
+
if (!["true", "false", "null", "undefined"].includes(m[1])) {
|
|
50
|
+
exports.push(m[1]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Regular named exports inside <script> (non-setup composables, types, etc.)
|
|
55
|
+
for (const m of scriptContent.matchAll(/export\s+(?:async\s+)?(?:function|class|const|type|interface|enum)\s+(\w+)/g)) {
|
|
56
|
+
exports.push(m[1]);
|
|
57
|
+
}
|
|
58
|
+
// TODOs from entire SFC (template + script + style)
|
|
59
|
+
for (const m of source.matchAll(/\/\/\s*(TODO|FIXME|HACK)[:\s]+(.+)/gi)) {
|
|
60
|
+
todos.push(`${m[1]}: ${m[2].trim()}`);
|
|
61
|
+
}
|
|
62
|
+
// Description: first HTML comment, then first JSDoc in script
|
|
63
|
+
const htmlComment = source.match(/<!--\s*([\s\S]*?)\s*-->/)?.[1] ?? "";
|
|
64
|
+
const jsdoc = scriptContent.match(/^\/\*\*([\s\S]*?)\*\//m)?.[1] ?? "";
|
|
65
|
+
const raw = htmlComment || jsdoc;
|
|
66
|
+
const description = raw.replace(/\s*\*\s*/g, " ").trim().slice(0, 200);
|
|
67
|
+
return { exports, description, todos };
|
|
68
|
+
}
|
|
39
69
|
function extractGeneric(source) {
|
|
40
70
|
const todos = [];
|
|
41
71
|
for (const m of source.matchAll(/(?:\/\/|#)\s*(TODO|FIXME|HACK)[:\s]+(.+)/gi)) {
|
|
@@ -63,6 +93,10 @@ export function indexFile(filepath) {
|
|
|
63
93
|
extracted = extractPython(source);
|
|
64
94
|
language = "python";
|
|
65
95
|
}
|
|
96
|
+
else if (ext === ".vue") {
|
|
97
|
+
extracted = extractVue(source);
|
|
98
|
+
language = "vue";
|
|
99
|
+
}
|
|
66
100
|
else {
|
|
67
101
|
extracted = extractGeneric(source);
|
|
68
102
|
language = "generic";
|
package/build/indexer/project.js
CHANGED
|
@@ -181,9 +181,53 @@ function indexLogicGuardianYaml(path, stmts, results) {
|
|
|
181
181
|
results.push({ entity: `${patternMatches.length} drift patterns`, type: "pattern", observations: patternMatches.length, source: "logic-guardian.yaml" });
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
function indexNuxtConfig(path, projectName, stmts, results) {
|
|
185
|
+
const content = readFile(path);
|
|
186
|
+
if (!content)
|
|
187
|
+
return;
|
|
188
|
+
const obs = [`nuxt config: ${basename(path)}`];
|
|
189
|
+
// Modules list: modules: ['@nuxtjs/...', ...]
|
|
190
|
+
const modulesMatch = content.match(/modules\s*:\s*\[([^\]]+)\]/);
|
|
191
|
+
if (modulesMatch) {
|
|
192
|
+
const modules = [...modulesMatch[1].matchAll(/['"`](@?[\w/@-]+)['"`]/g)].map((m) => m[1]);
|
|
193
|
+
if (modules.length > 0)
|
|
194
|
+
obs.push(`nuxt modules: ${modules.join(", ")}`);
|
|
195
|
+
}
|
|
196
|
+
// extends (Nuxt layers)
|
|
197
|
+
const extendsMatch = content.match(/extends\s*:\s*\[?['"`]([^'"`]+)['"`]/);
|
|
198
|
+
if (extendsMatch)
|
|
199
|
+
obs.push(`extends layer: ${extendsMatch[1]}`);
|
|
200
|
+
// ssr setting
|
|
201
|
+
const ssrMatch = content.match(/ssr\s*:\s*(true|false)/);
|
|
202
|
+
if (ssrMatch)
|
|
203
|
+
obs.push(`ssr: ${ssrMatch[1]}`);
|
|
204
|
+
// runtimeConfig public keys
|
|
205
|
+
const rtMatch = content.match(/runtimeConfig\s*:\s*\{([\s\S]*?)(?=\n\s{0,4}\w|\n\})/);
|
|
206
|
+
if (rtMatch) {
|
|
207
|
+
const keys = [...rtMatch[1].matchAll(/^\s{2,}(\w+)\s*:/gm)].map((m) => m[1]);
|
|
208
|
+
if (keys.length > 0)
|
|
209
|
+
obs.push(`runtimeConfig keys: ${keys.slice(0, 10).join(", ")}`);
|
|
210
|
+
}
|
|
211
|
+
upsert(stmts, projectName, "project", obs);
|
|
212
|
+
results.push({ entity: projectName, type: "project", observations: obs.length, source: basename(path) });
|
|
213
|
+
}
|
|
184
214
|
// Source file indexing — extrage exporturi, clase, funcții principale
|
|
185
|
-
const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]);
|
|
186
|
-
const SKIP_DIRS = new Set([
|
|
215
|
+
const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".py", ".go", ".rs"]);
|
|
216
|
+
const SKIP_DIRS = new Set([
|
|
217
|
+
"node_modules", ".git",
|
|
218
|
+
// Generic build output
|
|
219
|
+
"build", "dist",
|
|
220
|
+
// Python
|
|
221
|
+
"__pycache__", "venv", ".venv",
|
|
222
|
+
// Rust
|
|
223
|
+
"target",
|
|
224
|
+
// Next.js
|
|
225
|
+
".next",
|
|
226
|
+
// Nuxt
|
|
227
|
+
".nuxt", ".output",
|
|
228
|
+
// General
|
|
229
|
+
".cache", "coverage", ".nyc_output",
|
|
230
|
+
]);
|
|
187
231
|
const MAX_SOURCE_FILES = 10_000;
|
|
188
232
|
function indexSourceFile(filepath, rootDir, projectName, stmts) {
|
|
189
233
|
const content = readFile(filepath);
|
|
@@ -204,6 +248,27 @@ function indexSourceFile(filepath, rootDir, projectName, stmts) {
|
|
|
204
248
|
exports.push(m[1]);
|
|
205
249
|
}
|
|
206
250
|
}
|
|
251
|
+
// Vue SFC — extract component name + defineExpose + named exports from <script>
|
|
252
|
+
if (lang === ".vue") {
|
|
253
|
+
// Component name from filename (always present)
|
|
254
|
+
exports.push(basename(filepath, ".vue"));
|
|
255
|
+
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
256
|
+
if (scriptMatch) {
|
|
257
|
+
const sc = scriptMatch[1];
|
|
258
|
+
// defineExpose({ foo, bar }) — what the component exposes to parents via template refs
|
|
259
|
+
const exposeMatch = sc.match(/defineExpose\(\s*\{([^}]+)\}/);
|
|
260
|
+
if (exposeMatch) {
|
|
261
|
+
for (const m of exposeMatch[1].matchAll(/\b([a-zA-Z_]\w*)\b/g)) {
|
|
262
|
+
if (!["true", "false", "null", "undefined"].includes(m[1]))
|
|
263
|
+
exports.push(m[1]);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Named exports (composables, types re-exported from SFC)
|
|
267
|
+
for (const m of sc.matchAll(/export\s+(?:async\s+)?(?:function|class|const|type|interface)\s+(\w+)/g)) {
|
|
268
|
+
exports.push(m[1]);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
207
272
|
if (exports.length === 0)
|
|
208
273
|
return [];
|
|
209
274
|
// Cale relativă față de rădăcina proiectului
|
|
@@ -314,7 +379,22 @@ export function indexProject(directory, stmts) {
|
|
|
314
379
|
if (existsSync(join(dir, "logic-guardian.yaml"))) {
|
|
315
380
|
indexLogicGuardianYaml(join(dir, "logic-guardian.yaml"), stmts, results);
|
|
316
381
|
}
|
|
317
|
-
// 7.
|
|
382
|
+
// 7. Nuxt config files (nuxt.config.ts/.js, app.config.ts)
|
|
383
|
+
for (const p of ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mts"]) {
|
|
384
|
+
const full = join(dir, p);
|
|
385
|
+
if (existsSync(full)) {
|
|
386
|
+
indexNuxtConfig(full, projectName, stmts, results);
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
for (const p of ["app.config.ts", "app.config.js"]) {
|
|
391
|
+
const full = join(dir, p);
|
|
392
|
+
if (existsSync(full)) {
|
|
393
|
+
indexNuxtConfig(full, projectName, stmts, results);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// 8. Surse
|
|
318
398
|
scanSources(dir, projectName, stmts, results);
|
|
319
399
|
return results;
|
|
320
400
|
}
|
package/build/tools/init.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a13xu/lucid",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.4",
|
|
4
4
|
"description": "Token-efficient memory, code indexing, and validation for Claude Code agents — SQLite + FTS5, TF-IDF + Qdrant retrieval, AST skeleton pruning, diff-aware context, Logic Guardian drift detection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
47
|
-
"better-sqlite3": "^
|
|
47
|
+
"better-sqlite3": "^12.0.0",
|
|
48
48
|
"zod": "^3.23.8"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/better-sqlite3": "^7.6.11",
|
|
52
|
-
"@types/node": "^
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
53
|
"typescript": "^5.4.0"
|
|
54
54
|
}
|
|
55
55
|
}
|