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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/adopt-cli.mjs +126 -9
- package/hook.mjs +16 -11
- package/package.json +1 -1
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
|
-
*
|
|
129
|
-
*
|
|
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
|
-
* -
|
|
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
|
-
|
|
283
|
+
const isAdoptedHere = isAdopted(memdir, PLUGIN_SLUG);
|
|
284
|
+
const isDisabledHere = isAutoAdoptDisabled(memdir);
|
|
285
|
+
if (isAdoptedHere) {
|
|
183
286
|
const idx = readMemoryIndex(memdir, PLUGIN_SLUG);
|
|
184
|
-
|
|
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
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
//
|
|
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();
|