claude-mem-lite 2.81.0 → 2.82.1

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.81.0",
13
+ "version": "2.82.1",
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.81.0",
3
+ "version": "2.82.1",
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/adopt-cli.mjs CHANGED
@@ -9,7 +9,7 @@
9
9
  // --dry-run = print intent without writing
10
10
  // --status = list all adopted projects + versions
11
11
 
12
- import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync } from 'fs';
12
+ import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
13
13
  import { homedir } from 'os';
14
14
  import { join } from 'path';
15
15
  import {
@@ -49,6 +49,24 @@ function listAllMemdirs() {
49
49
 
50
50
  function hasFlag(args, flag) { return Array.isArray(args) && args.includes(flag); }
51
51
 
52
+ // ─── Per-project auto-adopt opt-out sentinel ─────────────────────────────────
53
+ // `<memdir>/.mem-no-auto-adopt` is the durable, project-scoped escape hatch.
54
+ // Survives marker deletion, sentinel removal, and plugin reinstalls — that's
55
+ // the point: "user said no for this project" should not be reversible by
56
+ // `rm ~/.claude-mem-lite/runtime/.auto-adopt-*`. Managed via
57
+ // `claude-mem-lite adopt --disable` / `--enable`. silentAutoAdopt checks it
58
+ // at entry and skips WITHOUT writing the runtime marker, so toggling
59
+ // `--enable` re-arms auto-adopt on the next SessionStart.
60
+ const DISABLE_SENTINEL_BASENAME = '.mem-no-auto-adopt';
61
+
62
+ export function disableSentinelPath(memdir) {
63
+ return join(memdir, DISABLE_SENTINEL_BASENAME);
64
+ }
65
+
66
+ export function isAutoAdoptDisabled(memdir) {
67
+ return existsSync(disableSentinelPath(memdir));
68
+ }
69
+
52
70
  /**
53
71
  * cmdAdopt — write sentinel section + plugin doc to memdir.
54
72
  * Exit code 1 on any hard failure; skipped (--all + UserEditedError) doesn't
@@ -56,6 +74,8 @@ function hasFlag(args, flag) { return Array.isArray(args) && args.includes(flag)
56
74
  */
57
75
  export function cmdAdopt(args = []) {
58
76
  if (hasFlag(args, '--status')) return statusAll();
77
+ if (hasFlag(args, '--disable')) return cmdDisable(args);
78
+ if (hasFlag(args, '--enable')) return cmdEnable(args);
59
79
 
60
80
  const all = hasFlag(args, '--all');
61
81
  const force = hasFlag(args, '--force');
@@ -122,14 +142,18 @@ function adoptOne(memdir, { force, dryRun, all }) {
122
142
  }
123
143
 
124
144
  /**
125
- * silentAutoAdopt — plugin-mode first-run auto-adopt helper (v2.33.0).
145
+ * silentAutoAdopt — plugin-mode first-run auto-adopt helper (v2.33.0+).
126
146
  *
127
147
  * Preconditions (caller must gate): CLAUDE_PLUGIN_ROOT set, MEM_NO_AUTO_ADOPT!=1,
128
- * MEM_QUIET_HOOKS!=1, first-attempt marker absent. This helper does NOT re-check
129
- * those — it only does the write + marker persistence.
148
+ * first-attempt marker absent. This helper does NOT re-check those — it only
149
+ * does the write + marker persistence. (v2.82.0: dropped MEM_QUIET_HOOKS gate;
150
+ * quiet is a stdout control, not a side-effect control.)
130
151
  *
131
152
  * Behavior:
132
- * - Writes plugin sentinel + detail doc to the memdir for `cwd`.
153
+ * - If `<memdir>/.mem-no-auto-adopt` exists: skip silently, do NOT write the
154
+ * runtime marker. This keeps `--enable` re-armable: deleting the disable
155
+ * sentinel lets the next SessionStart try again.
156
+ * - Else: writes plugin sentinel + detail doc to the memdir for `cwd`.
133
157
  * - Writes a per-project first-attempt marker under `markerDir` so a later
134
158
  * `/unadopt` is respected (no re-adopt loop).
135
159
  * - Silent: never logs, never throws. Returns structured result.
@@ -139,6 +163,9 @@ function adoptOne(memdir, { force, dryRun, all }) {
139
163
  export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
140
164
  const memdir = memdirPath(cwd);
141
165
  try {
166
+ if (isAutoAdoptDisabled(memdir)) {
167
+ return { ok: true, action: 'disabled', reason: 'disabled-by-sentinel' };
168
+ }
142
169
  if (isAdopted(memdir, PLUGIN_SLUG)) {
143
170
  writeMarker(markerDir, markerKey);
144
171
  return { ok: true, action: 'already-adopted' };
@@ -173,20 +200,110 @@ export function hasAutoAdoptMarker(markerDir, markerKey) {
173
200
  return existsSync(join(markerDir, `.auto-adopt-${markerKey}`));
174
201
  }
175
202
 
203
+ /**
204
+ * cmdDisable — `claude-mem-lite adopt --disable [--all]`.
205
+ *
206
+ * Writes `<memdir>/.mem-no-auto-adopt` so SessionStart auto-adopt skips this
207
+ * project permanently. Idempotent: re-running on an already-disabled memdir is
208
+ * a no-op. Does NOT remove an existing sentinel — pair with `unadopt` if you
209
+ * want both. The two operations are deliberately separate:
210
+ * - `unadopt` = "remove the contract now"
211
+ * - `adopt --disable` = "and don't auto-write it back"
212
+ */
213
+ function cmdDisable(args) {
214
+ const all = hasFlag(args, '--all');
215
+ const targets = all
216
+ ? listAllMemdirs().map((m) => m.memdir)
217
+ : [memdirPath(detectCwd())];
218
+
219
+ if (targets.length === 0) {
220
+ log('[adopt --disable] no memdirs found');
221
+ return;
222
+ }
223
+
224
+ let disabled = 0, already = 0;
225
+ for (const memdir of targets) {
226
+ if (!existsSync(memdir)) mkdirSync(memdir, { recursive: true });
227
+ const path = disableSentinelPath(memdir);
228
+ if (existsSync(path)) {
229
+ log(`[adopt --disable] ${memdir} → already-disabled`);
230
+ already++;
231
+ continue;
232
+ }
233
+ writeFileSync(path, JSON.stringify({ disabledAt: new Date().toISOString() }) + '\n');
234
+ log(`[adopt --disable] ${memdir} → disabled`);
235
+ disabled++;
236
+ }
237
+
238
+ log('');
239
+ log(`[adopt --disable] ${targets.length} target(s): ${disabled} newly disabled, ${already} already disabled`);
240
+ }
241
+
242
+ /**
243
+ * cmdEnable — `claude-mem-lite adopt --enable [--all]`.
244
+ *
245
+ * Removes the `<memdir>/.mem-no-auto-adopt` sentinel so the next SessionStart
246
+ * can auto-adopt again. Idempotent. Does NOT trigger an immediate adoption —
247
+ * run plain `claude-mem-lite adopt` if you want that now.
248
+ */
249
+ function cmdEnable(args) {
250
+ const all = hasFlag(args, '--all');
251
+ const targets = all
252
+ ? listAllMemdirs().map((m) => m.memdir)
253
+ : [memdirPath(detectCwd())];
254
+
255
+ if (targets.length === 0) {
256
+ log('[adopt --enable] no memdirs found');
257
+ return;
258
+ }
259
+
260
+ let enabled = 0, absent = 0;
261
+ for (const memdir of targets) {
262
+ const path = disableSentinelPath(memdir);
263
+ if (!existsSync(path)) {
264
+ log(`[adopt --enable] ${memdir} → absent`);
265
+ absent++;
266
+ continue;
267
+ }
268
+ try { unlinkSync(path); } catch { /* best-effort */ }
269
+ log(`[adopt --enable] ${memdir} → enabled`);
270
+ enabled++;
271
+ }
272
+
273
+ log('');
274
+ log(`[adopt --enable] ${targets.length} target(s): ${enabled} re-enabled, ${absent} not-disabled`);
275
+ }
276
+
176
277
  function statusAll() {
177
278
  const dirs = listAllMemdirs();
178
279
  log('[adopt --status] scanning ~/.claude/projects/*/memory/');
179
280
  if (dirs.length === 0) { log(' (no memdirs found)'); return; }
180
- let adopted = 0;
281
+ let adopted = 0, disabled = 0;
181
282
  for (const { projectSlug, memdir } of dirs) {
182
- if (isAdopted(memdir, PLUGIN_SLUG)) {
283
+ const isAdoptedHere = isAdopted(memdir, PLUGIN_SLUG);
284
+ const isDisabledHere = isAutoAdoptDisabled(memdir);
285
+ if (isAdoptedHere) {
183
286
  const idx = readMemoryIndex(memdir, PLUGIN_SLUG);
184
- log(` ✓ ${projectSlug} (${idx.version})`);
287
+ const suffix = isDisabledHere ? ' [auto-adopt disabled]' : '';
288
+ log(` ✓ ${projectSlug} (${idx.version})${suffix}`);
185
289
  adopted++;
290
+ if (isDisabledHere) disabled++;
291
+ } else if (isDisabledHere) {
292
+ log(` ✗ ${projectSlug} (auto-adopt disabled, no sentinel)`);
293
+ disabled++;
186
294
  }
187
295
  }
188
296
  log('');
189
- log(`[adopt --status] ${adopted}/${dirs.length} adopted`);
297
+ log(`[adopt --status] ${adopted}/${dirs.length} adopted${disabled > 0 ? `, ${disabled} disabled` : ''}`);
298
+
299
+ // Gating snapshot — helps debug "why didn't auto-adopt fire?"
300
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ? 'set' : 'unset';
301
+ const noAutoAdopt = process.env.MEM_NO_AUTO_ADOPT === '1' ? '1 (opt-out)' : 'unset';
302
+ log('');
303
+ log('Auto-adopt gates (next SessionStart will fire only if both pass):');
304
+ log(` CLAUDE_PLUGIN_ROOT = ${pluginRoot} (plugin-mode install required; npx stays opt-in)`);
305
+ log(` MEM_NO_AUTO_ADOPT = ${noAutoAdopt} (global escape hatch)`);
306
+ log('Per-project opt-out: `claude-mem-lite adopt --disable` (run --enable to re-arm).');
190
307
  }
191
308
 
192
309
  /**
package/hook.mjs CHANGED
@@ -652,22 +652,27 @@ async function handleSessionStart() {
652
652
  }
653
653
  } catch (e) { debugCatch(e, 'session-start-cache-heal'); }
654
654
 
655
- // v2.33.0: plugin-mode first-run auto-adopt. /plugin install IS consent to
656
- // integrationwriting the MEMORY.md sentinel once per project on first
657
- // SessionStart avoids the opt-in friction. Scope is narrow:
658
- // - gated by CLAUDE_PLUGIN_ROOT (npm/npx installs stay opt-in)
659
- // - gated by !MEM_NO_AUTO_ADOPT (explicit escape hatch)
660
- // - gated by !MEM_QUIET_HOOKS (quiet = no side-effects semantics)
655
+ // First-run auto-adopt (v2.33.0 plugin-mode v2.82.1 install-mode-agnostic).
656
+ // ANY install path `/plugin install`, `npm install -g`, `npx`, manual is
657
+ // consent to integration. Writing the MEMORY.md sentinel once per project on
658
+ // first SessionStart avoids the opt-in friction that left ~zero users on
659
+ // auto-adopt (runtime-marker directory was empty machine-wide despite v2.33
660
+ // shipping ~5 weeks earlier `install.mjs`-written hooks don't propagate
661
+ // ${CLAUDE_PLUGIN_ROOT}, so the v2.33.0 gate was a no-op for npm/manual
662
+ // installs, which is most of them). Scope is now:
663
+ // - gated by !MEM_NO_AUTO_ADOPT (explicit global escape hatch)
664
+ // - per-project opt-out via `<memdir>/.mem-no-auto-adopt` sentinel
665
+ // (managed by `claude-mem-lite adopt --disable / --enable`); checked
666
+ // inside silentAutoAdopt so the helper is safe to call directly too.
661
667
  // - first-attempt marker persists in RUNTIME_DIR so a subsequent /unadopt
662
668
  // is respected (no re-adopt loop).
669
+ // Note v2.82.0: removed MEM_QUIET_HOOKS gate. That env var suppresses stdout
670
+ // noise; it must NOT also disable side-effect work (PostToolUse writes the
671
+ // DB unconditionally — auto-adopt should follow the same rule).
663
672
  // Failures (user-edited sentinel, budget exceeded, FS errors) are swallowed;
664
673
  // the marker is still written so we don't retry on every SessionStart.
665
674
  try {
666
- if (
667
- process.env.CLAUDE_PLUGIN_ROOT
668
- && process.env.MEM_NO_AUTO_ADOPT !== '1'
669
- && process.env.MEM_QUIET_HOOKS !== '1'
670
- ) {
675
+ if (process.env.MEM_NO_AUTO_ADOPT !== '1') {
671
676
  const project = inferProject();
672
677
  if (!hasAutoAdoptMarker(RUNTIME_DIR, project)) {
673
678
  const cwd = process.env.CLAUDE_PROJECT_DIR || process.cwd();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.81.0",
3
+ "version": "2.82.1",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",