@a13xu/lucid 1.12.0 → 1.16.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.
@@ -6,6 +6,7 @@ import { extractSkeleton, renderSkeleton } from "../indexer/ast.js";
6
6
  import { searchQdrant } from "./qdrant.js";
7
7
  import { getQdrantConfig } from "../config.js";
8
8
  import { getFileRewardsMap } from "../memory/experience.js";
9
+ import { tryCompressTextSemantic } from "../compression/semantic.js";
9
10
  // ---------------------------------------------------------------------------
10
11
  // Token estimation (1 token ≈ 4 chars is the standard heuristic)
11
12
  // ---------------------------------------------------------------------------
@@ -183,6 +184,11 @@ export async function assembleContext(query, stmts, cfg, opts = {}) {
183
184
  }
184
185
  if (isRecent)
185
186
  reason += " +recent";
187
+ // Semantic compression — applied after skeleton/full decision, before token counting
188
+ if (cfg.semanticCompression?.enabled) {
189
+ content = await tryCompressTextSemantic(content, cfg.semanticCompression.ratio ?? 0.5, cfg.semanticCompression.minLength ?? 300);
190
+ reason += " +compressed";
191
+ }
186
192
  const contentTokens = estimateTokens(content);
187
193
  if (contentTokens < 10) {
188
194
  skippedFiles++;
@@ -8,7 +8,7 @@ export interface VectorChunk {
8
8
  score: number;
9
9
  }
10
10
  /** Index one file into Qdrant (called by sync_file when Qdrant is configured). */
11
- export declare function indexFileInQdrant(filepath: string, text: string, cfg: QdrantCfg): Promise<void>;
11
+ export declare function indexFileInQdrant(filepath: string, text: string, cfg: QdrantCfg, compressionCfg?: ResolvedConfig["semanticCompression"]): Promise<void>;
12
12
  /** Top-k semantic search across all indexed chunks. */
13
13
  export declare function searchQdrant(query: string, topK: number, cfg: QdrantCfg): Promise<VectorChunk[]>;
14
14
  /** Check if Qdrant collection exists and is reachable. */
@@ -2,6 +2,7 @@
2
2
  // Only active when QDRANT_URL is set (via env var or lucid.config.json)
3
3
  // Falls back silently to TF-IDF when unavailable
4
4
  import { safeFetch } from "../security/ssrf.js";
5
+ import { tryCompressTextSemantic } from "../compression/semantic.js";
5
6
  // ---------------------------------------------------------------------------
6
7
  // Embedding generation (OpenAI-compatible endpoint)
7
8
  // ---------------------------------------------------------------------------
@@ -85,16 +86,24 @@ function stableId(s) {
85
86
  // Public API
86
87
  // ---------------------------------------------------------------------------
87
88
  /** Index one file into Qdrant (called by sync_file when Qdrant is configured). */
88
- export async function indexFileInQdrant(filepath, text, cfg) {
89
+ export async function indexFileInQdrant(filepath, text, cfg, compressionCfg) {
89
90
  await ensureCollection(cfg);
90
91
  const chunks = chunkFile(filepath, text);
91
92
  if (chunks.length === 0)
92
93
  return;
94
+ const compressForEmbedding = compressionCfg?.enabled && compressionCfg.applyToEmbeddings !== false;
93
95
  // Batch embed (max 96 texts per request for most providers)
94
96
  const BATCH = 32;
95
97
  for (let b = 0; b < chunks.length; b += BATCH) {
96
98
  const batch = chunks.slice(b, b + BATCH);
97
- const vectors = await embed(batch.map((c) => c.text), cfg);
99
+ let textsToEmbed;
100
+ if (compressForEmbedding) {
101
+ textsToEmbed = await Promise.all(batch.map((c) => tryCompressTextSemantic(c.text, compressionCfg.ratio ?? 0.5, compressionCfg.minLength ?? 300)));
102
+ }
103
+ else {
104
+ textsToEmbed = batch.map((c) => c.text);
105
+ }
106
+ const vectors = await embed(textsToEmbed, cfg);
98
107
  const points = batch.map((c, idx) => ({
99
108
  id: c.id,
100
109
  vector: vectors[idx],
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export declare const CompressTextSchema: z.ZodObject<{
3
+ text: z.ZodString;
4
+ ratio: z.ZodOptional<z.ZodNumber>;
5
+ min_length: z.ZodOptional<z.ZodNumber>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ text: string;
8
+ ratio?: number | undefined;
9
+ min_length?: number | undefined;
10
+ }, {
11
+ text: string;
12
+ ratio?: number | undefined;
13
+ min_length?: number | undefined;
14
+ }>;
15
+ export declare function handleCompressText(args: z.infer<typeof CompressTextSchema>): Promise<string>;
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ import { compressTextSemantic } from "../compression/semantic.js";
3
+ export const CompressTextSchema = z.object({
4
+ text: z.string().min(1).describe("Text to compress"),
5
+ ratio: z.number().min(0.1).max(0.9).optional().describe("Target compression ratio: 0.3 = keep 30%, 0.5 = keep 50% (default: 0.5)"),
6
+ min_length: z.number().int().optional().describe("Skip compression for texts shorter than this in chars (default: 300)"),
7
+ });
8
+ export async function handleCompressText(args) {
9
+ const result = await compressTextSemantic(args.text, args.ratio ?? 0.5, args.min_length ?? 300);
10
+ return JSON.stringify({
11
+ compressed: result.compressed,
12
+ original_length: result.originalLength,
13
+ compressed_length: result.compressedLength,
14
+ ratio_kept: result.ratio,
15
+ method: result.method,
16
+ tokens_saved: Math.ceil((result.originalLength - result.compressedLength) / 4),
17
+ }, null, 2);
18
+ }
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { resolve, join } from "path";
3
+ import { homedir } from "os";
3
4
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
4
5
  import { fileURLToPath } from "url";
5
6
  import { indexProject } from "../indexer/project.js";
@@ -29,16 +30,37 @@ export const InitProjectSchema = z.object({
29
30
  projectName: z.string().optional(),
30
31
  });
31
32
  const LUCID_MARKER = "Lucid: call sync_file";
33
+ const LUCID_UPDATE_MARKER = "lucid-update-check";
32
34
  const LUCID_HOOK = {
33
35
  matcher: "Write|Edit|NotebookEdit",
34
36
  hooks: [
35
37
  {
36
38
  type: "command",
37
- command: `echo '🔄 ${LUCID_MARKER}(path) to keep knowledge graph up to date'`,
39
+ command: `lucid-sync 2>/dev/null || echo '🔄 ${LUCID_MARKER}(path) install lucid globally: npm i -g @a13xu/lucid'`,
38
40
  },
39
41
  ],
40
42
  };
41
- function installHook(dir) {
43
+ // SessionStart hook: checks npm registry and notifies if update is available.
44
+ // Uses only Node.js built-in https module — no external dependencies required.
45
+ const LUCID_UPDATE_HOOK = {
46
+ hooks: [
47
+ {
48
+ type: "command",
49
+ command: `node -e "const h=require('https');` +
50
+ `h.get('https://registry.npmjs.org/@a13xu/lucid/latest',` +
51
+ `function(r){var d='';r.on('data',function(c){d+=c});` +
52
+ `r.on('end',function(){` +
53
+ `try{var v=JSON.parse(d).version;` +
54
+ `var s=require('child_process').execSync(` +
55
+ `'npm list -g @a13xu/lucid --depth=0 2>/dev/null',{encoding:'utf8'});` +
56
+ `var m=s.match(/lucid@([\\d.]+)/);` +
57
+ `if(m&&m[1]&&v!==m[1])` +
58
+ `console.log('[Lucid] Update available: v'+m[1]+' → v'+v+'. Call update_lucid().')}` +
59
+ `catch(e){}})}).on('error',function(){})" 2>/dev/null || true`,
60
+ },
61
+ ],
62
+ };
63
+ function installHooks(dir) {
42
64
  const claudeDir = join(dir, ".claude");
43
65
  const settingsPath = join(claudeDir, "settings.json");
44
66
  let settings = {};
@@ -51,20 +73,34 @@ function installHook(dir) {
51
73
  }
52
74
  }
53
75
  const hooks = (settings["hooks"] ?? {});
76
+ let changed = false;
77
+ // ── PostToolUse: sync_file reminder ──────────────────────────────────────
54
78
  const postToolUse = hooks["PostToolUse"] ?? [];
55
- // Detectează atât formatul vechi cât și cel nou
56
- const alreadyInstalled = postToolUse.some((h) => {
79
+ const syncAlreadyInstalled = postToolUse.some((h) => {
57
80
  const cmd = h.command ?? h.hooks?.[0]?.command ?? "";
58
81
  return cmd.includes(LUCID_MARKER);
59
82
  });
60
- if (alreadyInstalled) {
83
+ if (!syncAlreadyInstalled) {
84
+ hooks["PostToolUse"] = [...postToolUse, LUCID_HOOK];
85
+ changed = true;
86
+ }
87
+ // ── SessionStart: version check ───────────────────────────────────────────
88
+ const sessionStart = hooks["SessionStart"] ?? [];
89
+ const updateAlreadyInstalled = sessionStart.some((h) => {
90
+ const cmd = h.command ?? h.hooks?.[0]?.command ?? "";
91
+ return cmd.includes(LUCID_UPDATE_MARKER);
92
+ });
93
+ if (!updateAlreadyInstalled) {
94
+ hooks["SessionStart"] = [...sessionStart, LUCID_UPDATE_HOOK];
95
+ changed = true;
96
+ }
97
+ if (!changed) {
61
98
  return { installed: false, reason: "already installed" };
62
99
  }
63
- hooks["PostToolUse"] = [...postToolUse, LUCID_HOOK];
64
100
  settings["hooks"] = hooks;
65
101
  mkdirSync(claudeDir, { recursive: true });
66
102
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
67
- return { installed: true, reason: "hook added to .claude/settings.json" };
103
+ return { installed: true, reason: "hooks added to .claude/settings.json" };
68
104
  }
69
105
  // ---------------------------------------------------------------------------
70
106
  // Adaugă instrucțiune în CLAUDE.md
@@ -111,13 +147,14 @@ export async function handleInitProject(stmts, input) {
111
147
  }
112
148
  // ── Hook PostToolUse ──────────────────────────────────────────────────────
113
149
  lines.push(``);
114
- const hookResult = installHook(dir);
150
+ const hookResult = installHooks(dir);
115
151
  if (hookResult.installed) {
116
- lines.push(`🔗 Claude Code hook installed (.claude/settings.json)`);
117
- lines.push(` After every Write/Edit, you will see a reminder to call sync_file().`);
152
+ lines.push(`🔗 Claude Code hooks installed (.claude/settings.json)`);
153
+ lines.push(` PostToolUse: reminder to call sync_file() after every Write/Edit`);
154
+ lines.push(` SessionStart: auto-check for Lucid updates on session start`);
118
155
  }
119
156
  else {
120
- lines.push(`🔗 Hook: ${hookResult.reason}`);
157
+ lines.push(`🔗 Hooks: ${hookResult.reason}`);
121
158
  }
122
159
  // ── Skills ────────────────────────────────────────────────────────────────
123
160
  const skillsResult = installSkills(dir);
@@ -131,6 +168,17 @@ export async function handleInitProject(stmts, input) {
131
168
  else if (skillsResult.skipped.length > 0) {
132
169
  lines.push(`📚 Skills: already installed (${skillsResult.skipped.length} skill(s))`);
133
170
  }
171
+ // ── Global skills (~/.claude/skills/) ────────────────────────────────────
172
+ const globalSkillsResult = installGlobalSkills();
173
+ if (globalSkillsResult.installed.length > 0) {
174
+ lines.push(`🌐 Global skills installed in ~/.claude/skills/:`);
175
+ for (const s of globalSkillsResult.installed) {
176
+ lines.push(` • /${s} (available in all projects)`);
177
+ }
178
+ }
179
+ else if (globalSkillsResult.skipped.length > 0) {
180
+ lines.push(`🌐 Global skills: already installed (${globalSkillsResult.skipped.length} skill(s))`);
181
+ }
134
182
  // ── CLAUDE.md injection ───────────────────────────────────────────────────
135
183
  const injected = injectClaudeMdInstruction(dir);
136
184
  if (injected) {
@@ -241,6 +289,9 @@ function installSkills(projectDir) {
241
289
  }
242
290
  return result;
243
291
  }
292
+ function installGlobalSkills() {
293
+ return installSkills(homedir());
294
+ }
244
295
  function buildChannelSummary(cfg) {
245
296
  const channels = [];
246
297
  if (cfg.adminEmail && cfg.smtpHost)
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export declare const SuggestModelSchema: z.ZodObject<{
3
+ task_description: z.ZodString;
4
+ }, "strip", z.ZodTypeAny, {
5
+ task_description: string;
6
+ }, {
7
+ task_description: string;
8
+ }>;
9
+ export declare function handleSuggestModel(args: z.infer<typeof SuggestModelSchema>): string;
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ export const SuggestModelSchema = z.object({
3
+ task_description: z.string().min(1).describe("Natural language description of the task you are about to perform"),
4
+ });
5
+ const HAIKU_TRIGGERS = [
6
+ "list", "show", "find", "search", "where", "what is",
7
+ "recall", "get recent", "status",
8
+ ];
9
+ const MODEL_IDS = {
10
+ haiku: "claude-haiku-4-5-20251001",
11
+ sonnet: "claude-sonnet-4-6",
12
+ };
13
+ const CONTEXT_BUDGETS = {
14
+ haiku: 2000,
15
+ sonnet: 8000,
16
+ };
17
+ export function handleSuggestModel(args) {
18
+ const lower = args.task_description.toLowerCase();
19
+ const haikuTrigger = HAIKU_TRIGGERS.find((t) => lower.includes(t));
20
+ const model = haikuTrigger ? "haiku" : "sonnet";
21
+ const reasoning = haikuTrigger
22
+ ? `Task matches retrieval/lookup pattern ("${haikuTrigger}") — Haiku is faster for read-only queries.`
23
+ : "No simple retrieval trigger detected — defaulting to Sonnet for reasoning, code generation, and analysis.";
24
+ return JSON.stringify({
25
+ model,
26
+ model_id: MODEL_IDS[model],
27
+ reasoning,
28
+ context_budget: CONTEXT_BUDGETS[model],
29
+ }, null, 2);
30
+ }
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import type { Statements } from "../database.js";
3
+ export declare const SmartContextSchema: z.ZodObject<{
4
+ query: z.ZodString;
5
+ task_type: z.ZodOptional<z.ZodEnum<["simple", "moderate", "complex"]>>;
6
+ dirs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ query: string;
9
+ dirs?: string[] | undefined;
10
+ task_type?: "simple" | "moderate" | "complex" | undefined;
11
+ }, {
12
+ query: string;
13
+ dirs?: string[] | undefined;
14
+ task_type?: "simple" | "moderate" | "complex" | undefined;
15
+ }>;
16
+ export declare function handleSmartContext(stmts: Statements, args: z.infer<typeof SmartContextSchema>): Promise<string>;
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import { assembleContext } from "../retrieval/context.js";
3
+ import { recall } from "./recall.js";
4
+ import { loadConfig } from "../config.js";
5
+ import { createExperience } from "../memory/experience.js";
6
+ export const SmartContextSchema = z.object({
7
+ query: z.string().min(1).describe("What you are working on — used for both code retrieval and knowledge graph search"),
8
+ task_type: z.enum(["simple", "moderate", "complex"]).optional().describe("Token budget: simple=2000, moderate=6000 (default), complex=12000"),
9
+ dirs: z.array(z.string()).optional().describe("Whitelist: only return files from these directories"),
10
+ });
11
+ const TASK_BUDGETS = {
12
+ simple: 2000,
13
+ moderate: 6000,
14
+ complex: 12000,
15
+ };
16
+ export async function handleSmartContext(stmts, args) {
17
+ const cfg = loadConfig();
18
+ const maxTokens = TASK_BUDGETS[args.task_type ?? "moderate"] ?? 6000;
19
+ // 1. Knowledge graph entities (synchronous)
20
+ const recallResult = recall(stmts, { query: args.query });
21
+ // 2. Code context with adaptive budget (async)
22
+ const contextResult = await assembleContext(args.query, stmts, cfg, {
23
+ maxTokens,
24
+ dirs: args.dirs,
25
+ });
26
+ // 3. Log experience so reward()/penalize() work after this call
27
+ const expId = createExperience(args.query, contextResult.files.map((f) => f.filepath), contextResult.strategy, stmts);
28
+ const budgetUsedPct = Math.round((contextResult.totalTokens / maxTokens) * 100);
29
+ const sections = [
30
+ "## Knowledge Context (entities)",
31
+ recallResult,
32
+ "",
33
+ "## Code Context (files)",
34
+ ];
35
+ if (contextResult.files.length === 0) {
36
+ sections.push("No relevant files found. Run init_project() or sync_project() first.");
37
+ }
38
+ else {
39
+ for (const f of contextResult.files) {
40
+ sections.push(`// ─── ${f.filepath} [${f.language}] ~${f.tokens}t (${f.reason}) ───`);
41
+ sections.push(f.content);
42
+ sections.push("");
43
+ }
44
+ if (contextResult.truncated) {
45
+ sections.push(`// ⚠️ Truncated — ${contextResult.skippedFiles} files skipped. Use task_type="complex" for more.`);
46
+ }
47
+ }
48
+ sections.push("", "---");
49
+ sections.push(`Strategy: ${contextResult.strategy}`);
50
+ sections.push(`Files: ${contextResult.files.length} files, ${contextResult.totalTokens} tokens`);
51
+ sections.push(`Budget used: ${budgetUsedPct}%`);
52
+ sections.push(`Experience #${expId} logged. Call reward() if helpful, penalize() if not.`);
53
+ return sections.join("\n");
54
+ }
@@ -6,6 +6,8 @@ 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
+ import { indexFileInQdrant } from "../retrieval/qdrant.js";
10
+ import { loadConfig, getQdrantConfig } from "../config.js";
9
11
  const SUPPORTED_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".py", ".go", ".rs"]);
10
12
  // ---------------------------------------------------------------------------
11
13
  // sync_file
@@ -51,6 +53,12 @@ export function handleSyncFile(stmts, args) {
51
53
  const implicitRewarded = implicitRewardFromSync(filepath, stmts);
52
54
  if (implicitRewarded)
53
55
  lines.push(` 🎯 Implicit reward +0.3 (file was in recent context)`);
56
+ // Qdrant vector indexing — fire-and-forget (Qdrant is optional, silent on failure)
57
+ const cfg = loadConfig();
58
+ const qdrantCfg = getQdrantConfig(cfg);
59
+ if (qdrantCfg) {
60
+ void indexFileInQdrant(filepath, source, qdrantCfg, cfg.semanticCompression).catch(() => { });
61
+ }
54
62
  return lines.join("\n");
55
63
  }
56
64
  // ---------------------------------------------------------------------------
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ export declare function getCurrentVersion(): string;
3
+ export declare function checkForUpdatesOnStartup(): Promise<void>;
4
+ export declare const UpdateLucidSchema: z.ZodObject<{
5
+ force: z.ZodOptional<z.ZodBoolean>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ force?: boolean | undefined;
8
+ }, {
9
+ force?: boolean | undefined;
10
+ }>;
11
+ export declare function handleUpdateLucid(args: z.infer<typeof UpdateLucidSchema>): Promise<string>;
@@ -0,0 +1,133 @@
1
+ import { z } from "zod";
2
+ import { join } from "path";
3
+ import { readFileSync, existsSync } from "fs";
4
+ import { fileURLToPath } from "url";
5
+ import { execSync } from "child_process";
6
+ // ---------------------------------------------------------------------------
7
+ // Package root resolution
8
+ // ---------------------------------------------------------------------------
9
+ // build/tools/updater.js → ../../ = package root
10
+ const PACKAGE_ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "../..");
11
+ export function getCurrentVersion() {
12
+ try {
13
+ const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf-8"));
14
+ return pkg.version ?? "unknown";
15
+ }
16
+ catch {
17
+ return "unknown";
18
+ }
19
+ }
20
+ // ---------------------------------------------------------------------------
21
+ // npm registry check
22
+ // ---------------------------------------------------------------------------
23
+ async function fetchLatestVersion() {
24
+ try {
25
+ const res = await fetch("https://registry.npmjs.org/@a13xu/lucid/latest", { signal: AbortSignal.timeout(5000) });
26
+ if (!res.ok)
27
+ return null;
28
+ const data = (await res.json());
29
+ return data.version ?? null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function compareVersions(a, b) {
36
+ const pa = a.split(".").map(Number);
37
+ const pb = b.split(".").map(Number);
38
+ for (let i = 0; i < 3; i++) {
39
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
40
+ if (diff !== 0)
41
+ return diff;
42
+ }
43
+ return 0;
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // Install method detection
47
+ // ---------------------------------------------------------------------------
48
+ function detectInstallMethod() {
49
+ // If the package root is inside node_modules, it's npm-installed
50
+ if (PACKAGE_ROOT.includes("node_modules")) {
51
+ return "global-npm";
52
+ }
53
+ // Check if there's a .git folder — local source checkout
54
+ if (existsSync(join(PACKAGE_ROOT, ".git"))) {
55
+ return "local-source";
56
+ }
57
+ return "global-npm";
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Startup check (non-blocking — call from index.ts without await)
61
+ // ---------------------------------------------------------------------------
62
+ export async function checkForUpdatesOnStartup() {
63
+ const current = getCurrentVersion();
64
+ const latest = await fetchLatestVersion();
65
+ if (latest && compareVersions(latest, current) > 0) {
66
+ console.error(`[lucid] ⬆️ Update available: v${current} → v${latest}. ` +
67
+ `Call update_lucid() to update, or: npm install -g @a13xu/lucid@latest`);
68
+ }
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Schema & handler
72
+ // ---------------------------------------------------------------------------
73
+ export const UpdateLucidSchema = z.object({
74
+ force: z
75
+ .boolean()
76
+ .optional()
77
+ .describe("Force reinstall even if already on latest version"),
78
+ });
79
+ // Example call:
80
+ // handleUpdateLucid({ force: false })
81
+ export async function handleUpdateLucid(args) {
82
+ const current = getCurrentVersion();
83
+ const lines = [`🔍 Checking for updates... (current: v${current})`];
84
+ const latest = await fetchLatestVersion();
85
+ if (!latest) {
86
+ return lines.concat("❌ Could not reach npm registry. Check your internet connection.").join("\n");
87
+ }
88
+ lines.push(`📦 Latest on npm: v${latest}`);
89
+ const upToDate = compareVersions(latest, current) <= 0;
90
+ if (upToDate && !args.force) {
91
+ lines.push(`✅ Lucid is up to date.`);
92
+ return lines.join("\n");
93
+ }
94
+ if (upToDate && args.force) {
95
+ lines.push(`⚠️ Already on v${latest}, reinstalling (force=true)...`);
96
+ }
97
+ else {
98
+ lines.push(`🔄 Updating v${current} → v${latest}...`);
99
+ }
100
+ const method = detectInstallMethod();
101
+ if (method === "local-source") {
102
+ lines.push(``);
103
+ lines.push(`📁 Local source installation detected (${PACKAGE_ROOT}).`);
104
+ lines.push(`Run these commands to update:`);
105
+ lines.push(` cd "${PACKAGE_ROOT}"`);
106
+ lines.push(` git pull`);
107
+ lines.push(` npm run build`);
108
+ lines.push(`Then restart Claude Code.`);
109
+ return lines.join("\n");
110
+ }
111
+ // Global npm install
112
+ try {
113
+ lines.push(`Running: npm install -g @a13xu/lucid@${latest}`);
114
+ execSync(`npm install -g @a13xu/lucid@${latest}`, {
115
+ timeout: 120_000,
116
+ stdio: "pipe",
117
+ });
118
+ lines.push(`✅ Updated to v${latest}`);
119
+ lines.push(``);
120
+ lines.push(`⚠️ Restart Claude Code to load the new version:`);
121
+ lines.push(` • VS Code: Cmd/Ctrl+Shift+P → "Restart Claude Code"`);
122
+ lines.push(` • CLI: exit and re-run \`claude\``);
123
+ }
124
+ catch (err) {
125
+ const msg = err instanceof Error ? err.message : String(err);
126
+ lines.push(`❌ Auto-update failed: ${msg}`);
127
+ lines.push(` Run manually: npm install -g @a13xu/lucid@${latest}`);
128
+ if (process.platform !== "win32") {
129
+ lines.push(` Or with sudo: sudo npm install -g @a13xu/lucid@${latest}`);
130
+ }
131
+ }
132
+ return lines.join("\n");
133
+ }
package/package.json CHANGED
@@ -1,59 +1,64 @@
1
- {
2
- "name": "@a13xu/lucid",
3
- "version": "1.12.0",
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
- "type": "module",
6
- "bin": {
7
- "lucid": "./build/index.js"
8
- },
9
- "files": [
10
- "build/**/*.js",
11
- "build/**/*.d.ts",
12
- "skills/**/*.md",
13
- "README.md"
14
- ],
15
- "scripts": {
16
- "build": "tsc",
17
- "prepublishOnly": "npm run build"
18
- },
19
- "keywords": [
20
- "mcp",
21
- "claude",
22
- "claude-code",
23
- "memory",
24
- "sqlite",
25
- "knowledge-graph",
26
- "code-indexing",
27
- "logic-guardian",
28
- "drift-detection",
29
- "tfidf",
30
- "qdrant",
31
- "token-optimization",
32
- "context-pruning",
33
- "ai",
34
- "anthropic"
35
- ],
36
- "author": "a13xu",
37
- "license": "MIT",
38
- "repository": {
39
- "type": "git",
40
- "url": "https://github.com/a13xu/lucid.git"
41
- },
42
- "homepage": "https://github.com/a13xu/lucid#readme",
43
- "publishConfig": {
44
- "access": "public"
45
- },
46
- "engines": {
47
- "node": ">=18"
48
- },
49
- "dependencies": {
50
- "@modelcontextprotocol/sdk": "^1.0.0",
51
- "better-sqlite3": "^12.0.0",
52
- "zod": "^3.23.8"
53
- },
54
- "devDependencies": {
55
- "@types/better-sqlite3": "^7.6.11",
56
- "@types/node": "^22.0.0",
57
- "typescript": "^5.4.0"
58
- }
59
- }
1
+ {
2
+ "name": "@a13xu/lucid",
3
+ "version": "1.16.0",
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
+ "type": "module",
6
+ "bin": {
7
+ "lucid": "./build/index.js",
8
+ "lucid-sync": "./build/lucid-sync.js"
9
+ },
10
+ "files": [
11
+ "build/**/*.js",
12
+ "build/**/*.d.ts",
13
+ "skills/**/*.md",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "mcp",
22
+ "claude",
23
+ "claude-code",
24
+ "memory",
25
+ "sqlite",
26
+ "knowledge-graph",
27
+ "code-indexing",
28
+ "logic-guardian",
29
+ "drift-detection",
30
+ "tfidf",
31
+ "qdrant",
32
+ "token-optimization",
33
+ "context-pruning",
34
+ "ai",
35
+ "anthropic"
36
+ ],
37
+ "author": "a13xu",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/a13xu/lucid.git"
42
+ },
43
+ "homepage": "https://github.com/a13xu/lucid#readme",
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "dependencies": {
51
+ "@huggingface/transformers": "^4.0.0",
52
+ "@modelcontextprotocol/sdk": "^1.0.0",
53
+ "better-sqlite3": "^12.0.0",
54
+ "chokidar": "^4.0.3",
55
+ "express": "^5.2.1",
56
+ "zod": "^3.23.8"
57
+ },
58
+ "devDependencies": {
59
+ "@types/better-sqlite3": "^7.6.11",
60
+ "@types/express": "^5.0.6",
61
+ "@types/node": "^22.0.0",
62
+ "typescript": "^5.4.0"
63
+ }
64
+ }