@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 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
- # Add to Claude Code (no install needed)
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 (15)
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 15 tools.
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.1" }, { capabilities: { tools: {} } });
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"],
@@ -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
  }
@@ -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";
@@ -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(["node_modules", ".git", "build", "dist", "__pycache__", ".next", "venv", ".venv", "target", ".cache", "coverage", ".nyc_output"]);
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. Surse
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
  }
@@ -29,7 +29,7 @@ export const InitProjectSchema = z.object({
29
29
  });
30
30
  const LUCID_MARKER = "Lucid: call sync_file";
31
31
  const LUCID_HOOK = {
32
- matcher: { tools: ["Write", "Edit", "NotebookEdit"] },
32
+ matcher: "Write|Edit|NotebookEdit",
33
33
  hooks: [
34
34
  {
35
35
  type: "command",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a13xu/lucid",
3
- "version": "1.9.1",
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": "^11.0.0",
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": "^20.0.0",
52
+ "@types/node": "^22.0.0",
53
53
  "typescript": "^5.4.0"
54
54
  }
55
55
  }