@a13xu/lucid 1.9.4 → 1.9.5

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
@@ -1,8 +1,14 @@
1
1
  # @a13xu/lucid
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@a13xu/lucid)](https://www.npmjs.com/package/@a13xu/lucid)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@a13xu/lucid)](https://www.npmjs.com/package/@a13xu/lucid)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ > **MCP server for Claude Code** — persistent memory, smart code indexing, and code quality validation. Works out of the box with zero configuration.
8
+
3
9
  Token-efficient memory, code indexing, and validation for Claude Code agents — backed by **SQLite + FTS5**.
4
10
 
5
- Stores a persistent knowledge graph (entities, relations, observations), indexes source files as compressed binary with change detection, retrieves minimal relevant context via TF-IDF or Qdrant, and validates code for LLM drift patterns.
11
+ Stores a persistent knowledge graph (entities, relations, observations), indexes source files as compressed binary with change detection, retrieves minimal relevant context via TF-IDF or Qdrant, and validates code for LLM drift patterns. Supports TypeScript, JavaScript, Python, **Vue, Nuxt**.
6
12
 
7
13
  ## Install
8
14
 
@@ -226,6 +232,15 @@ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},
226
232
 
227
233
  In Claude Code: run `/mcp` — you should see `lucid` with 20 tools.
228
234
 
235
+ ## Contributing
236
+
237
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/a13xu/lucid).
238
+
239
+ 1. Fork the repo
240
+ 2. `npm install` → `npm run build`
241
+ 3. Test locally: `claude mcp add --transport stdio lucid-dev -- node /path/to/lucid/build/index.js`
242
+ 4. Open a PR
243
+
229
244
  ## Tech stack
230
245
 
231
246
  - **Runtime:** Node.js 18+, TypeScript, ES modules
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.4" }, { capabilities: { tools: {} } });
55
+ const server = new Server({ name: "lucid", version: "1.9.5" }, { capabilities: { tools: {} } });
56
56
  // ---------------------------------------------------------------------------
57
57
  // Tool definitions
58
58
  // ---------------------------------------------------------------------------
@@ -6,6 +6,8 @@ export interface FileIndex {
6
6
  todos: string[];
7
7
  language: string;
8
8
  }
9
+ /** Build a FileIndex from already-read source — no IO. */
10
+ export declare function buildFileIndex(filepath: string, source: string): FileIndex;
9
11
  export declare function indexFile(filepath: string): FileIndex | null;
10
12
  export interface UpsertResult {
11
13
  observations: string[];
@@ -73,14 +73,8 @@ function extractGeneric(source) {
73
73
  }
74
74
  return { exports: [], description: "", todos };
75
75
  }
76
- export function indexFile(filepath) {
77
- let source;
78
- try {
79
- source = readFileSync(filepath, { encoding: "utf-8" });
80
- }
81
- catch {
82
- return null;
83
- }
76
+ /** Build a FileIndex from already-read source — no IO. */
77
+ export function buildFileIndex(filepath, source) {
84
78
  const ext = extname(filepath).toLowerCase();
85
79
  const module = filepath.replace(/\\/g, "/");
86
80
  let extracted;
@@ -103,6 +97,16 @@ export function indexFile(filepath) {
103
97
  }
104
98
  return { module, language, ...extracted };
105
99
  }
100
+ export function indexFile(filepath) {
101
+ let source;
102
+ try {
103
+ source = readFileSync(filepath, { encoding: "utf-8" });
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ return buildFileIndex(filepath, source);
109
+ }
106
110
  export function upsertFileIndex(index, source, stmts) {
107
111
  const fileHash = sha256(source);
108
112
  // Change detection — skip everything se hash-ul e identic
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
2
  import { join, extname, basename } from "path";
3
+ import { buildFileIndex, upsertFileIndex } from "./file.js";
3
4
  // ---------------------------------------------------------------------------
4
5
  // Helpers
5
6
  // ---------------------------------------------------------------------------
@@ -232,54 +233,23 @@ const MAX_SOURCE_FILES = 10_000;
232
233
  function indexSourceFile(filepath, rootDir, projectName, stmts) {
233
234
  const content = readFile(filepath);
234
235
  if (!content)
235
- return [];
236
- const exports = [];
237
- const lang = extname(filepath);
238
- // TypeScript / JavaScript
239
- if ([".ts", ".tsx", ".js", ".jsx"].includes(lang)) {
240
- for (const m of content.matchAll(/export\s+(?:async\s+)?(?:function|class|const|type|interface)\s+(\w+)/g)) {
241
- exports.push(m[1]);
242
- }
243
- }
244
- // Python
245
- if (lang === ".py") {
246
- for (const m of content.matchAll(/^(?:def|class|async def)\s+(\w+)/gm)) {
247
- if (!m[1].startsWith("_"))
248
- exports.push(m[1]);
249
- }
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
- }
272
- if (exports.length === 0)
273
- return [];
274
- // Cale relativă față de rădăcina proiectului
275
- const relPath = filepath.replace(/\\/g, "/").replace(rootDir.replace(/\\/g, "/") + "/", "");
276
- const obs = [`exports from ${relPath}: ${exports.slice(0, 10).join(", ")}`];
277
- upsert(stmts, projectName, "project", obs);
278
- return exports;
236
+ return { exports: [], stored: false };
237
+ // Build structured index (language-aware, single read)
238
+ const fileIdx = buildFileIndex(filepath, content);
239
+ // Store compressed content in source file index → enables get_context() + grep_code()
240
+ const result = upsertFileIndex(fileIdx, content, stmts);
241
+ // Add exports to knowledge graph (for recall())
242
+ if (fileIdx.exports.length > 0) {
243
+ const relPath = filepath.replace(/\\/g, "/").replace(rootDir.replace(/\\/g, "/") + "/", "");
244
+ const obs = [`exports from ${relPath}: ${fileIdx.exports.slice(0, 10).join(", ")}`];
245
+ upsert(stmts, projectName, "project", obs);
246
+ }
247
+ return { exports: fileIdx.exports, stored: result.stored };
279
248
  }
280
249
  function scanSources(dir, projectName, stmts, results) {
281
250
  const rootDir = dir.replace(/\\/g, "/");
282
251
  let fileCount = 0;
252
+ let storedCount = 0;
283
253
  const exportedSymbols = [];
284
254
  function walk(d) {
285
255
  if (fileCount >= MAX_SOURCE_FILES)
@@ -306,9 +276,11 @@ function scanSources(dir, projectName, stmts, results) {
306
276
  walk(full);
307
277
  }
308
278
  else if (SOURCE_EXTS.has(extname(entry).toLowerCase())) {
309
- const syms = indexSourceFile(full, rootDir, projectName, stmts);
279
+ const { exports: syms, stored } = indexSourceFile(full, rootDir, projectName, stmts);
310
280
  exportedSymbols.push(...syms);
311
281
  fileCount++;
282
+ if (stored)
283
+ storedCount++;
312
284
  if (fileCount >= MAX_SOURCE_FILES)
313
285
  return;
314
286
  }
@@ -320,7 +292,7 @@ function scanSources(dir, projectName, stmts, results) {
320
292
  entity: projectName,
321
293
  type: "project",
322
294
  observations: fileCount,
323
- source: `${fileCount} source files (${exportedSymbols.length} exports)`,
295
+ source: `${fileCount} source files (${storedCount} compressed, ${exportedSymbols.length} exports)`,
324
296
  });
325
297
  }
326
298
  }
@@ -6,7 +6,7 @@ import { indexProject } from "../indexer/project.js";
6
6
  import { computeDiff } from "../retrieval/context.js";
7
7
  import { decompress } from "../store/content.js";
8
8
  import { implicitRewardFromSync } from "../memory/experience.js";
9
- const SUPPORTED_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]);
9
+ const SUPPORTED_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".py", ".go", ".rs"]);
10
10
  // ---------------------------------------------------------------------------
11
11
  // sync_file
12
12
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a13xu/lucid",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
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": {
@@ -39,6 +39,9 @@
39
39
  "url": "https://github.com/a13xu/lucid.git"
40
40
  },
41
41
  "homepage": "https://github.com/a13xu/lucid#readme",
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
42
45
  "engines": {
43
46
  "node": ">=18"
44
47
  },