claude-mem-lite 2.54.0 → 2.55.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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.54.0",
13
+ "version": "2.55.0",
14
14
  "source": "./",
15
15
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.54.0",
3
+ "version": "2.55.0",
4
4
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/hook-update.mjs CHANGED
@@ -3,12 +3,12 @@
3
3
  // Skips in dev mode (symlinked installs). Silent on network failure.
4
4
 
5
5
  import { execSync, execFileSync } from 'node:child_process';
6
- import { readFileSync, writeFileSync, copyFileSync, readdirSync, existsSync, lstatSync, mkdirSync, rmSync, renameSync } from 'node:fs';
6
+ import { readFileSync, writeFileSync, copyFileSync, cpSync, readdirSync, existsSync, lstatSync, mkdirSync, rmSync, renameSync } from 'node:fs';
7
7
  import { join, dirname } from 'node:path';
8
8
  import { tmpdir, homedir } from 'node:os';
9
9
  import { DB_DIR } from './schema.mjs';
10
10
  import { debugCatch, debugLog } from './utils.mjs';
11
- import { SOURCE_FILES } from './source-files.mjs';
11
+ import { SOURCE_FILES, HOOK_SCRIPT_FILES } from './source-files.mjs';
12
12
 
13
13
  // ── Configuration ──────────────────────────────────────────
14
14
  const GITHUB_REPO = 'sdsrss/claude-mem-lite';
@@ -328,16 +328,30 @@ function copyReleaseIntoStaging(sourceDir, stagingDir) {
328
328
  copied++;
329
329
  }
330
330
 
331
- for (const dirName of ['scripts', 'registry']) {
332
- const srcDir = join(sourceDir, dirName);
333
- const destDir = join(stagingDir, dirName);
334
- if (!existsSync(srcDir)) continue;
335
- mkdirSync(destDir, { recursive: true });
336
- for (const entry of readdirSync(srcDir)) {
337
- copyFileSync(join(srcDir, entry), join(destDir, entry));
331
+ // scripts/ is curated to HOOK_SCRIPT_FILES — settings.json hook commands
332
+ // resolve only to these 5 files, and plugin mode does not consume this
333
+ // directory at all. Pre-v2.55 used cpSync({recursive:true}) which silently
334
+ // shipped dev-only files (mock-claude.mjs, extract-repos.mjs, p0-forward-probe.mjs…)
335
+ // from the GitHub Releases tarball into every user's data dir.
336
+ const stagingScripts = join(stagingDir, 'scripts');
337
+ const sourceScripts = join(sourceDir, 'scripts');
338
+ if (existsSync(sourceScripts)) {
339
+ mkdirSync(stagingScripts, { recursive: true });
340
+ for (const name of HOOK_SCRIPT_FILES) {
341
+ const src = join(sourceScripts, name);
342
+ if (existsSync(src)) copyFileSync(src, join(stagingScripts, name));
338
343
  }
339
344
  }
340
345
 
346
+ // registry/ stays recursive — preinstalled.json is the only current entry
347
+ // but the directory is consumed wholesale by the registry indexer and may
348
+ // grow subtrees. Pre-v2.55 readdirSync+copyFileSync would EISDIR-throw on
349
+ // any subdir and silently roll back the entire update.
350
+ const sourceRegistry = join(sourceDir, 'registry');
351
+ if (existsSync(sourceRegistry)) {
352
+ cpSync(sourceRegistry, join(stagingDir, 'registry'), { recursive: true });
353
+ }
354
+
341
355
  const stagedScripts = join(stagingDir, 'scripts');
342
356
  if (existsSync(stagedScripts)) {
343
357
  for (const sf of readdirSync(stagedScripts).filter(n => n.endsWith('.sh'))) {
package/install.mjs CHANGED
@@ -29,23 +29,13 @@ import { createRequire } from 'module';
29
29
 
30
30
  import { RESOURCE_METADATA } from './install-metadata.mjs';
31
31
  import { scanPluginCacheHookPollution } from './plugin-cache-guard.mjs';
32
- import { SOURCE_FILES } from './source-files.mjs';
32
+ import { SOURCE_FILES, HOOK_SCRIPT_FILES } from './source-files.mjs';
33
33
 
34
- /**
35
- * Hook scripts that non-dev install must copy into ~/.claude-mem-lite/scripts/
36
- * to keep settings.json hook commands resolvable. Single source of truth so
37
- * adding a new PreToolUse/PostToolUse hook script can't drift from the install
38
- * copy block (which previously hand-listed only 3 of these and silently
39
- * dropped pre-tool-recall.js + pre-skill-bridge.js — every fresh install left
40
- * settings.json pointing at non-existent files).
41
- */
42
- export const HOOK_SCRIPT_FILES = [
43
- 'post-tool-use.sh',
44
- 'user-prompt-search.js',
45
- 'prompt-search-utils.mjs',
46
- 'pre-tool-recall.js',
47
- 'pre-skill-bridge.js',
48
- ];
34
+ // Re-export for backward compatibility — tests/install-hook-scripts.test.mjs
35
+ // and any external consumers still import HOOK_SCRIPT_FILES from install.mjs.
36
+ // The constant itself moved to source-files.mjs in v2.55 so hook-update.mjs
37
+ // can share it without a static cycle.
38
+ export { HOOK_SCRIPT_FILES };
49
39
 
50
40
  export function copyHookScripts(srcDir, destDir) {
51
41
  for (const name of HOOK_SCRIPT_FILES) {
@@ -349,12 +339,10 @@ async function install() {
349
339
  if (existsSync(join(PROJECT_DIR, 'registry'))) {
350
340
  symlinkSync(join(PROJECT_DIR, 'registry'), regLink);
351
341
  }
352
- // Symlink commands/ directory
353
- const cmdLink = join(DATA_DIR, 'commands');
354
- if (existsSync(cmdLink)) try { rmSync(cmdLink, { recursive: true, force: true }); } catch {}
355
- if (existsSync(join(PROJECT_DIR, 'commands'))) {
356
- symlinkSync(join(PROJECT_DIR, 'commands'), cmdLink);
357
- }
342
+ // commands/ is intentionally NOT linked: Claude Code reads slash commands
343
+ // from the plugin cache (~/.claude/plugins/cache/<mp>/<plugin>/<ver>/commands/)
344
+ // or user-level ~/.claude/commands/, never from ~/.claude-mem-lite/commands/.
345
+ // Pre-v2.55 maintained a symlink/copy here that had no consumers.
358
346
  ok('Symlinks created in ~/.claude-mem-lite/ → dev dir');
359
347
  } else {
360
348
  log('Installing to ~/.claude-mem-lite/...');
@@ -375,15 +363,7 @@ async function install() {
375
363
  copyHookScripts(join(PROJECT_DIR, 'scripts'), scriptsDir);
376
364
  // Ensure bash script is executable
377
365
  try { execFileSync('chmod', ['+x', join(scriptsDir, 'post-tool-use.sh')], { stdio: 'pipe' }); } catch {}
378
- // Copy commands directory
379
- const commandsDir = join(DATA_DIR, 'commands');
380
- if (!existsSync(commandsDir)) mkdirSync(commandsDir, { recursive: true });
381
- const commandsSrc = join(PROJECT_DIR, 'commands');
382
- if (existsSync(commandsSrc)) {
383
- for (const f of readdirSync(commandsSrc).filter(f => f.endsWith('.md'))) {
384
- copyFileSync(join(commandsSrc, f), join(commandsDir, f));
385
- }
386
- }
366
+ // commands/ is intentionally NOT copied — see dev-mode branch above.
387
367
  // Copy registry manifest
388
368
  const registryDir = join(DATA_DIR, 'registry');
389
369
  if (!existsSync(registryDir)) mkdirSync(registryDir, { recursive: true });
@@ -1614,9 +1594,10 @@ function syncVersions() {
1614
1594
  const marketJson = JSON.parse(readFileSync(marketJsonPath, 'utf8'));
1615
1595
  const plugin = marketJson.plugins?.[0];
1616
1596
  if (plugin && plugin.version !== version) {
1597
+ const prev = plugin.version;
1617
1598
  plugin.version = version;
1618
1599
  writeFileSync(marketJsonPath, JSON.stringify(marketJson, null, 2) + '\n');
1619
- ok(`marketplace.json: ${plugin.version} → ${version}`);
1600
+ ok(`marketplace.json: ${prev} → ${version}`);
1620
1601
  } else if (plugin) {
1621
1602
  ok(`marketplace.json: already ${version}`);
1622
1603
  }
@@ -1624,6 +1605,27 @@ function syncVersions() {
1624
1605
  warn('marketplace.json not found');
1625
1606
  }
1626
1607
 
1608
+ // Sync CLAUDE.md `**Version**: x.y.z` line — install-e2e asserts this
1609
+ // matches package.json so omitting it here would break CI on every release.
1610
+ const claudeMdPath = join(PROJECT_DIR, 'CLAUDE.md');
1611
+ if (existsSync(claudeMdPath)) {
1612
+ const orig = readFileSync(claudeMdPath, 'utf8');
1613
+ const versionLine = /^- \*\*Version\*\*: .+$/m;
1614
+ if (versionLine.test(orig)) {
1615
+ const patched = orig.replace(versionLine, `- **Version**: ${version}`);
1616
+ if (patched !== orig) {
1617
+ writeFileSync(claudeMdPath, patched);
1618
+ ok(`CLAUDE.md: → ${version}`);
1619
+ } else {
1620
+ ok(`CLAUDE.md: already ${version}`);
1621
+ }
1622
+ } else {
1623
+ warn('CLAUDE.md: `**Version**:` line not found — skipped');
1624
+ }
1625
+ } else {
1626
+ warn('CLAUDE.md not found');
1627
+ }
1628
+
1627
1629
  console.log('');
1628
1630
  }
1629
1631
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.54.0",
3
+ "version": "2.55.0",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {
package/scripts/setup.sh CHANGED
@@ -136,5 +136,53 @@ if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" ]]; then
136
136
  fi
137
137
  fi
138
138
 
139
+ # 9. Residue detection (plugin mode only): warn once if legacy direct-install
140
+ # hooks remain in ~/.claude/settings.json. A user who installed via global
141
+ # `claude-mem-lite install` and later switched to the marketplace plugin
142
+ # will run every hook twice (direct settings.json hooks AND plugin hooks)
143
+ # until they run `claude-mem-lite uninstall` to clear the settings.json
144
+ # entries. /plugin uninstall does not touch settings.json.
145
+ RESIDUE_MARKER="$DATA_DIR/runtime/.residue-warned-v2.55"
146
+ if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" && ! -f "$RESIDUE_MARKER" ]]; then
147
+ SETTINGS="$HOME/.claude/settings.json"
148
+ if [[ -f "$SETTINGS" ]]; then
149
+ SETTINGS_PATH="$SETTINGS" node -e '
150
+ const fs = require("fs");
151
+ try {
152
+ const raw = fs.readFileSync(process.env.SETTINGS_PATH, "utf8");
153
+ const data = JSON.parse(raw);
154
+ const hooks = data.hooks || {};
155
+ const events = Object.keys(hooks);
156
+ const found = [];
157
+ for (const ev of events) {
158
+ const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
159
+ for (const entry of list) {
160
+ const inner = Array.isArray(entry?.hooks) ? entry.hooks : [];
161
+ for (const h of inner) {
162
+ const cmd = String(h?.command || "");
163
+ if (cmd.includes(".claude-mem-lite/") || cmd.includes("claude-mem-lite/scripts") || cmd.includes("claude-mem-lite/hook.mjs")) {
164
+ found.push(ev);
165
+ break;
166
+ }
167
+ }
168
+ }
169
+ }
170
+ if (found.length) {
171
+ process.stderr.write("\n");
172
+ process.stderr.write("\x1b[33m⚠\x1b[0m Legacy direct-install hooks detected in " + process.env.SETTINGS_PATH + "\n");
173
+ process.stderr.write(" Events with stale entries: " + [...new Set(found)].join(", ") + "\n");
174
+ process.stderr.write(" These will fire alongside plugin hooks (each tool call runs twice).\n");
175
+ process.stderr.write(" Fix: run \x1b[1mclaude-mem-lite uninstall\x1b[0m to clear settings.json,\n");
176
+ process.stderr.write(" then keep using the plugin install. (One-time warning.)\n\n");
177
+ process.exit(2);
178
+ }
179
+ } catch {}
180
+ ' || true
181
+ fi
182
+ # Mark the warning as shown regardless of result — silence is fine if no
183
+ # residue, and the warning above is one-shot per data-dir.
184
+ touch "$RESIDUE_MARKER"
185
+ fi
186
+
139
187
  log_ok "claude-mem-lite ready"
140
188
  exit 0
package/source-files.mjs CHANGED
@@ -53,3 +53,23 @@ export const SOURCE_FILES = [
53
53
  'adopt-content.mjs',
54
54
  'adopt-cli.mjs',
55
55
  ];
56
+
57
+ /**
58
+ * Hook scripts that direct-install (non-plugin) mode must materialize under
59
+ * ~/.claude-mem-lite/scripts/ — settings.json hook commands resolve to these
60
+ * absolute paths. Plugin mode does not consume this directory (it runs scripts
61
+ * from ${CLAUDE_PLUGIN_ROOT} instead).
62
+ *
63
+ * Single source of truth for both install.mjs (initial install) and
64
+ * hook-update.mjs (auto-update): pre-v2.55 hook-update copied the entire
65
+ * scripts/ tree from the GitHub Releases tarball, which silently shipped
66
+ * dev-only files (mock-claude.mjs, extract-repos.mjs, p0-forward-probe.mjs…)
67
+ * to every user's data dir on the first auto-update.
68
+ */
69
+ export const HOOK_SCRIPT_FILES = [
70
+ 'post-tool-use.sh',
71
+ 'user-prompt-search.js',
72
+ 'prompt-search-utils.mjs',
73
+ 'pre-tool-recall.js',
74
+ 'pre-skill-bridge.js',
75
+ ];