claude-mem-lite 3.12.1 → 3.14.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": "3.12.1",
13
+ "version": "3.14.0",
14
14
  "source": "./",
15
15
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.12.1",
3
+ "version": "3.14.0",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/adopt-cli.mjs CHANGED
@@ -1,25 +1,32 @@
1
- // Phase C (Invited-Memory plan, T11): CLI handlers for
2
- // claude-mem-lite adopt [--all] [--force] [--dry-run] [--status]
3
- // claude-mem-lite unadopt [--all]
1
+ // CLAUDE.md-steering plan (v3.13): CLI handlers for
2
+ // claude-mem-lite adopt [--all] [--force] [--dry-run] [--status] [--disable|--enable]
3
+ // claude-mem-lite unadopt [--all] [--force] [--dry-run] [--status]
4
4
  //
5
- // adopt = write sentinel section into MEMORY.md + drop plugin_claude_mem_lite.md
6
- // unadopt = precise sentinel removal + doc cleanup
7
- // --all = scan every project under ~/.claude/projects/*/memory/
8
- // --force = override UserEditedError
9
- // --dry-run = print intent without writing
10
- // --status = list all adopted projects + versions
5
+ // adopt = write the managed block into <cwd>/CLAUDE.md + drop
6
+ // <cwd>/.claude/plugin_claude_mem_lite.md, and migrate this project's
7
+ // legacy memory-dir sentinel away.
8
+ // unadopt = remove the CLAUDE.md block + detail doc (and clean any legacy residue).
9
+ //
10
+ // The project path is needed to write CLAUDE.md, but the per-project memdir slug
11
+ // (~/.claude/projects/<encoded>/) is a LOSSY encoding of the real cwd — it cannot
12
+ // be decoded back to a filesystem path. So `--all` cannot adopt arbitrary projects;
13
+ // it is redefined as a legacy-cleanup sweep (strip old memory-dir sentinels across
14
+ // every memdir). New-scheme adoption happens per-project on SessionStart (cwd known).
11
15
 
12
16
  import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
13
17
  import { homedir } from 'os';
14
18
  import { join } from 'path';
15
19
  import {
16
- memdirPath, writePluginSection, removePluginSection,
17
- writePluginDoc, removePluginDoc,
18
- isAdopted, hasPluginState, readMemoryIndex,
19
- UserEditedError, BudgetExceededError,
20
+ memdirPath, removePluginSection, removePluginDoc,
21
+ isAdopted as memdirIsAdopted, hasPluginState,
20
22
  } from './memdir.mjs';
21
23
  import {
22
- PLUGIN_SLUG, CURRENT_SENTINEL_VERSION, getIndexLine, getDetailDoc,
24
+ writeManaged, removeManaged, isAdopted as claudeMdIsAdopted,
25
+ needsRefresh, migrateLegacyMemoryDir, hasLegacyMemdirSentinel,
26
+ claudeMdPath, detailDocPath,
27
+ } from './claudemd.mjs';
28
+ import {
29
+ PLUGIN_SLUG, CURRENT_SENTINEL_VERSION, buildClaudeMdBlock, getDetailDoc,
23
30
  } from './adopt-content.mjs';
24
31
 
25
32
  function log(msg) { console.log(msg); }
@@ -56,7 +63,8 @@ function hasFlag(args, flag) { return Array.isArray(args) && args.includes(flag)
56
63
  // `rm ~/.claude-mem-lite/runtime/.auto-adopt-*`. Managed via
57
64
  // `claude-mem-lite adopt --disable` / `--enable`. silentAutoAdopt checks it
58
65
  // at entry and skips WITHOUT writing the runtime marker, so toggling
59
- // `--enable` re-arms auto-adopt on the next SessionStart.
66
+ // `--enable` re-arms auto-adopt on the next SessionStart. Kept in the memdir
67
+ // (not the project tree) so it survives `unadopt` cleaning out .claude/.
60
68
  const DISABLE_SENTINEL_BASENAME = '.mem-no-auto-adopt';
61
69
 
62
70
  export function disableSentinelPath(memdir) {
@@ -68,97 +76,98 @@ export function isAutoAdoptDisabled(memdir) {
68
76
  }
69
77
 
70
78
  /**
71
- * cmdAdopt — write sentinel section + plugin doc to memdir.
72
- * Exit code 1 on any hard failure; skipped (--all + UserEditedError) doesn't
73
- * fail the batch.
79
+ * cmdAdopt — write the CLAUDE.md managed block + detail doc for the current
80
+ * project, and migrate its legacy memory-dir sentinel away.
81
+ *
82
+ * `--all` does NOT adopt every project (their real paths are unrecoverable from
83
+ * the lossy memdir slug) — it sweeps the legacy memory-dir cleanup across all
84
+ * memdirs. `--status`/`--disable`/`--enable` as before.
74
85
  */
75
86
  export function cmdAdopt(args = []) {
76
87
  if (hasFlag(args, '--status')) return statusAll();
77
88
  if (hasFlag(args, '--disable')) return cmdDisable(args);
78
89
  if (hasFlag(args, '--enable')) return cmdEnable(args);
90
+ if (hasFlag(args, '--all')) return migrateAll(args);
79
91
 
80
- const all = hasFlag(args, '--all');
81
92
  const force = hasFlag(args, '--force');
82
93
  const dryRun = hasFlag(args, '--dry-run');
94
+ const cwd = detectCwd();
83
95
 
84
- const targets = all
85
- ? listAllMemdirs().map((m) => m.memdir)
86
- : [memdirPath(detectCwd())];
87
-
88
- if (targets.length === 0) {
89
- log('[adopt] no memdirs to adopt (use without --all for current project)');
90
- return;
91
- }
92
-
93
- let created = 0, updated = 0, unchanged = 0, skipped = 0, failed = 0;
94
- for (const memdir of targets) {
95
- const r = adoptOne(memdir, { force, dryRun, all });
96
- if (r.action === 'created') created++;
97
- else if (r.action === 'updated') updated++;
98
- else if (r.action === 'unchanged') unchanged++;
99
- else if (r.action === 'skipped') skipped++;
100
- else if (r.action === 'dry-run') unchanged++;
101
- else failed++;
102
- }
103
-
104
- log('');
105
- log(`[adopt] ${targets.length} target(s): ${created} created, ${updated} updated, ${unchanged} unchanged, ${skipped} skipped, ${failed} failed`);
106
- if (failed > 0) process.exitCode = 1;
96
+ adoptOne(cwd, { force, dryRun });
107
97
  }
108
98
 
109
- function adoptOne(memdir, { force, dryRun, all }) {
110
- const contentLine = getIndexLine();
99
+ function adoptOne(cwd, { force, dryRun }) {
100
+ const block = buildClaudeMdBlock();
101
+ const doc = getDetailDoc();
111
102
  const version = CURRENT_SENTINEL_VERSION;
112
103
 
113
104
  if (dryRun) {
114
- log(`[adopt --dry-run] ${memdir}`);
115
- log(` MEMORY.md line: ${contentLine}`);
116
- log(` detail file: plugin_claude_mem_lite.md (${getDetailDoc().length} chars)`);
105
+ log(`[adopt --dry-run] ${cwd}`);
106
+ log(` CLAUDE.md block: ${claudeMdPath(cwd)} (${block.length} chars, ${version})`);
107
+ log(` detail doc: ${detailDocPath(cwd, PLUGIN_SLUG)} (${doc.length} chars)`);
108
+ if (hasLegacyMemdirSentinel(cwd, PLUGIN_SLUG)) {
109
+ log(` legacy migrate: would strip memory-dir sentinel @ ${memdirPath(cwd)}`);
110
+ }
117
111
  return { action: 'dry-run' };
118
112
  }
119
113
 
120
114
  try {
121
- const r = writePluginSection(memdir, { slug: PLUGIN_SLUG, version, contentLine, force });
122
- writePluginDoc(memdir, PLUGIN_SLUG, getDetailDoc());
123
- log(`[adopt] ${memdir} ${r.action}`);
115
+ const mig = migrateLegacyMemoryDir(cwd, PLUGIN_SLUG, { force });
116
+ const r = writeManaged(cwd, { slug: PLUGIN_SLUG, version, block, doc });
117
+ const migNote = mig.action === 'removed' ? ' (+migrated legacy memdir)' : '';
118
+ log(`[adopt] ${cwd} → ${r.action}${migNote}`);
124
119
  return r;
125
120
  } catch (e) {
126
- if (e instanceof UserEditedError && all) {
127
- log(`[adopt] ${memdir} → skipped (user-edited; pass --force to override)`);
128
- return { action: 'skipped' };
129
- }
130
- if (e instanceof UserEditedError) {
131
- log(`[adopt] ${memdir} → refused: ${e.message}`);
132
- log('[adopt] pass --force to overwrite, or edit/uninstall manually.');
133
- return { action: 'failed' };
134
- }
135
- if (e instanceof BudgetExceededError) {
136
- log(`[adopt] ${memdir} → failed: ${e.message}`);
137
- return { action: 'failed' };
138
- }
139
- log(`[adopt] ${memdir} → error: ${e.message}`);
121
+ log(`[adopt] ${cwd} error: ${e.message}`);
122
+ process.exitCode = 1;
140
123
  return { action: 'failed' };
141
124
  }
142
125
  }
143
126
 
144
127
  /**
145
- * silentAutoAdoptplugin-mode first-run auto-adopt helper (v2.33.0+).
146
- *
147
- * Preconditions (caller must gate): CLAUDE_PLUGIN_ROOT set, MEM_NO_AUTO_ADOPT!=1,
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.)
151
- *
152
- * Behavior:
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`.
157
- * - Writes a per-project first-attempt marker under `markerDir` so a later
158
- * `/unadopt` is respected (no re-adopt loop).
159
- * - Silent: never logs, never throws. Returns structured result.
128
+ * migrateAll`claude-mem-lite adopt --all`: legacy-cleanup sweep. Strips the
129
+ * old memory-dir sentinel + detail doc from every memdir. Does NOT write any
130
+ * CLAUDE.md block (target paths are unrecoverable) that happens per-project on
131
+ * the next SessionStart. Respects the foreign-content guard unless --force.
132
+ */
133
+ function migrateAll(args) {
134
+ const force = hasFlag(args, '--force');
135
+ const dryRun = hasFlag(args, '--dry-run');
136
+ const dirs = listAllMemdirs();
137
+ if (dirs.length === 0) { log('[adopt --all] no memdirs found'); return; }
138
+
139
+ let removed = 0, absent = 0, skipped = 0;
140
+ for (const { projectSlug, memdir } of dirs) {
141
+ if (dryRun) {
142
+ const has = memdirIsAdopted(memdir, PLUGIN_SLUG);
143
+ const action = !has ? 'absent'
144
+ : (hasPluginState(memdir, PLUGIN_SLUG) || force) ? 'would-remove' : 'would-skip-foreign';
145
+ log(`[adopt --all --dry-run] ${projectSlug} → ${action}`);
146
+ if (action === 'would-remove') removed++;
147
+ else if (action === 'would-skip-foreign') skipped++;
148
+ else absent++;
149
+ continue;
150
+ }
151
+ const r = removePluginSection(memdir, PLUGIN_SLUG, { force });
152
+ if (r.action === 'removed') { removePluginDoc(memdir, PLUGIN_SLUG); removed++; }
153
+ else if (r.action === 'skipped-foreign') skipped++;
154
+ else absent++;
155
+ }
156
+ log('');
157
+ log(`[adopt --all] legacy memory-dir cleanup over ${dirs.length} project(s): ${removed} cleaned, ${skipped} skipped-foreign, ${absent} none.`);
158
+ log('[adopt --all] CLAUDE.md adoption is per-project — it runs automatically on each project\'s next SessionStart.');
159
+ }
160
+
161
+ /**
162
+ * silentAutoAdopt — SessionStart idempotent sync (migration vehicle).
160
163
  *
161
- * Returns { ok, action, reason } caller uses for telemetry / debugLog only.
164
+ * Called every plugin-mode SessionStart (NOT gated by the one-shot marker, so
165
+ * existing users whose marker predates v3.13 still migrate). Order:
166
+ * 1. respect per-project `.mem-no-auto-adopt` opt-out → skip.
167
+ * 2. migrate legacy memory-dir sentinel away (idempotent; no-op once gone).
168
+ * 3. adopt the CLAUDE.md scheme if absent; else refresh if shipped content
169
+ * drifted (unless CLAUDE_MEM_NO_TEMPLATE_REFRESH=1).
170
+ * Silent: never logs, never throws. Returns { ok, action, reason } for debugLog.
162
171
  */
163
172
  export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
164
173
  const memdir = memdirPath(cwd);
@@ -166,27 +175,26 @@ export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
166
175
  if (isAutoAdoptDisabled(memdir)) {
167
176
  return { ok: true, action: 'disabled', reason: 'disabled-by-sentinel' };
168
177
  }
169
- if (isAdopted(memdir, PLUGIN_SLUG)) {
170
- writeMarker(markerDir, markerKey);
171
- return { ok: true, action: 'already-adopted' };
178
+ migrateLegacyMemoryDir(cwd, PLUGIN_SLUG);
179
+
180
+ const block = buildClaudeMdBlock();
181
+ const doc = getDetailDoc();
182
+ const version = CURRENT_SENTINEL_VERSION;
183
+
184
+ let action = 'already-adopted';
185
+ if (!claudeMdIsAdopted(cwd, PLUGIN_SLUG)) {
186
+ writeManaged(cwd, { slug: PLUGIN_SLUG, version, block, doc });
187
+ action = 'adopted';
188
+ } else if (process.env.CLAUDE_MEM_NO_TEMPLATE_REFRESH !== '1'
189
+ && needsRefresh(cwd, { slug: PLUGIN_SLUG, version, block, doc })) {
190
+ writeManaged(cwd, { slug: PLUGIN_SLUG, version, block, doc });
191
+ action = 'refreshed';
172
192
  }
173
- writePluginSection(memdir, {
174
- slug: PLUGIN_SLUG,
175
- version: CURRENT_SENTINEL_VERSION,
176
- contentLine: getIndexLine(),
177
- force: false,
178
- });
179
- writePluginDoc(memdir, PLUGIN_SLUG, getDetailDoc());
180
- writeMarker(markerDir, markerKey);
181
- return { ok: true, action: 'adopted' };
193
+ if (markerDir && markerKey) writeMarker(markerDir, markerKey);
194
+ return { ok: true, action };
182
195
  } catch (e) {
183
- // Budget exceeded, user-edited conflict, or FS error write marker so we
184
- // don't retry on every SessionStart. User can run /adopt --force manually.
185
- try { writeMarker(markerDir, markerKey); } catch { /* marker best-effort */ }
186
- const reason = e instanceof UserEditedError ? 'user-edited'
187
- : e instanceof BudgetExceededError ? 'budget-exceeded'
188
- : 'error';
189
- return { ok: false, action: 'skipped', reason, err: e };
196
+ try { if (markerDir && markerKey) writeMarker(markerDir, markerKey); } catch { /* best-effort */ }
197
+ return { ok: false, action: 'skipped', reason: 'error', err: e };
190
198
  }
191
199
  }
192
200
 
@@ -202,13 +210,8 @@ export function hasAutoAdoptMarker(markerDir, markerKey) {
202
210
 
203
211
  /**
204
212
  * cmdDisable — `claude-mem-lite adopt --disable [--all]`.
205
- *
206
213
  * 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"
214
+ * project permanently. Does NOT remove an existing block — pair with `unadopt`.
212
215
  */
213
216
  function cmdDisable(args) {
214
217
  const all = hasFlag(args, '--all');
@@ -216,10 +219,7 @@ function cmdDisable(args) {
216
219
  ? listAllMemdirs().map((m) => m.memdir)
217
220
  : [memdirPath(detectCwd())];
218
221
 
219
- if (targets.length === 0) {
220
- log('[adopt --disable] no memdirs found');
221
- return;
222
- }
222
+ if (targets.length === 0) { log('[adopt --disable] no memdirs found'); return; }
223
223
 
224
224
  let disabled = 0, already = 0;
225
225
  for (const memdir of targets) {
@@ -234,17 +234,13 @@ function cmdDisable(args) {
234
234
  log(`[adopt --disable] ${memdir} → disabled`);
235
235
  disabled++;
236
236
  }
237
-
238
237
  log('');
239
238
  log(`[adopt --disable] ${targets.length} target(s): ${disabled} newly disabled, ${already} already disabled`);
240
239
  }
241
240
 
242
241
  /**
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.
242
+ * cmdEnable — `claude-mem-lite adopt --enable [--all]`. Removes the
243
+ * `.mem-no-auto-adopt` sentinel so the next SessionStart can auto-adopt again.
248
244
  */
249
245
  function cmdEnable(args) {
250
246
  const all = hasFlag(args, '--all');
@@ -252,10 +248,7 @@ function cmdEnable(args) {
252
248
  ? listAllMemdirs().map((m) => m.memdir)
253
249
  : [memdirPath(detectCwd())];
254
250
 
255
- if (targets.length === 0) {
256
- log('[adopt --enable] no memdirs found');
257
- return;
258
- }
251
+ if (targets.length === 0) { log('[adopt --enable] no memdirs found'); return; }
259
252
 
260
253
  let enabled = 0, absent = 0;
261
254
  for (const memdir of targets) {
@@ -269,56 +262,48 @@ function cmdEnable(args) {
269
262
  log(`[adopt --enable] ${memdir} → enabled`);
270
263
  enabled++;
271
264
  }
272
-
273
265
  log('');
274
266
  log(`[adopt --enable] ${targets.length} target(s): ${enabled} re-enabled, ${absent} not-disabled`);
275
267
  }
276
268
 
269
+ /**
270
+ * statusAll — report the current project's new-scheme adoption, plus a sweep of
271
+ * how many memdirs still carry the legacy sentinel (i.e. await migration).
272
+ */
277
273
  function statusAll() {
274
+ const cwd = detectCwd();
275
+ const adoptedHere = claudeMdIsAdopted(cwd, PLUGIN_SLUG);
276
+ log('[adopt --status] current project:');
277
+ log(` cwd: ${cwd}`);
278
+ log(` CLAUDE.md: ${adoptedHere ? `✓ adopted (${CURRENT_SENTINEL_VERSION})` : '✗ not adopted'}`);
279
+ if (hasLegacyMemdirSentinel(cwd, PLUGIN_SLUG)) {
280
+ log(' legacy: ⚠ memory-dir sentinel still present (migrates on next SessionStart, or run `adopt`)');
281
+ }
282
+
278
283
  const dirs = listAllMemdirs();
279
- log('[adopt --status] scanning ~/.claude/projects/*/memory/');
280
- if (dirs.length === 0) { log(' (no memdirs found)'); return; }
281
- let adopted = 0, disabled = 0;
282
- for (const { projectSlug, memdir } of dirs) {
283
- const isAdoptedHere = isAdopted(memdir, PLUGIN_SLUG);
284
- const isDisabledHere = isAutoAdoptDisabled(memdir);
285
- if (isAdoptedHere) {
286
- const idx = readMemoryIndex(memdir, PLUGIN_SLUG);
287
- const suffix = isDisabledHere ? ' [auto-adopt disabled]' : '';
288
- log(` ✓ ${projectSlug} (${idx.version})${suffix}`);
289
- adopted++;
290
- if (isDisabledHere) disabled++;
291
- } else if (isDisabledHere) {
292
- log(` ✗ ${projectSlug} (auto-adopt disabled, no sentinel)`);
293
- disabled++;
294
- }
284
+ let legacy = 0, disabled = 0;
285
+ for (const { memdir } of dirs) {
286
+ if (memdirIsAdopted(memdir, PLUGIN_SLUG)) legacy++;
287
+ if (isAutoAdoptDisabled(memdir)) disabled++;
295
288
  }
296
289
  log('');
297
- log(`[adopt --status] ${adopted}/${dirs.length} adopted${disabled > 0 ? `, ${disabled} disabled` : ''}`);
290
+ log(`[adopt --status] scanned ${dirs.length} memdir(s): ${legacy} with legacy sentinel (await migration), ${disabled} auto-adopt-disabled.`);
291
+ if (legacy > 0) log('[adopt --status] run `claude-mem-lite adopt --all` to sweep legacy memory-dir sentinels now.');
298
292
 
299
- // Gating snapshot — helps debug "why didn't auto-adopt fire?"
300
293
  const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ? 'set' : 'unset';
301
294
  const noAutoAdopt = process.env.MEM_NO_AUTO_ADOPT === '1' ? '1 (opt-out)' : 'unset';
302
295
  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)`);
296
+ log('Auto-adopt gates (next SessionStart fires only if these pass):');
297
+ log(` CLAUDE_PLUGIN_ROOT = ${pluginRoot} (any install path is consent; gate is the per-project opt-out below)`);
305
298
  log(` MEM_NO_AUTO_ADOPT = ${noAutoAdopt} (global escape hatch)`);
306
299
  log('Per-project opt-out: `claude-mem-lite adopt --disable` (run --enable to re-arm).');
307
300
  }
308
301
 
309
302
  /**
310
- * cmdUnadopt — precise removal of sentinel section + plugin doc.
311
- * Exit code stays 0: unadopt is idempotent; "absent" isn't an error.
312
- *
313
- * Flags:
314
- * --all Operate on every memdir under ~/.claude/projects/*\/memory/
315
- * --status Read-only: list currently-adopted memdirs (mirrors `adopt --status`).
316
- * --dry-run Preview what would be removed; no filesystem writes.
317
- *
318
- * Pre-fix history: unrecognized flags (e.g. `--status` extrapolated from `adopt --status`,
319
- * or `--dry-run` extrapolated from `adopt --dry-run`) were silently ignored and the
320
- * destructive default ran anyway, removing the sentinel block when the user expected
321
- * a read-only probe.
303
+ * cmdUnadopt — remove the CLAUDE.md managed block + detail doc for the current
304
+ * project, and clean any legacy memory-dir residue. `--all` sweeps the legacy
305
+ * memory-dir cleanup across every memdir (CLAUDE.md blocks for other projects
306
+ * can't be located from the lossy slug). Idempotent: exit code stays 0.
322
307
  */
323
308
  export function cmdUnadopt(args = []) {
324
309
  if (hasFlag(args, '--status')) return statusAll();
@@ -326,41 +311,21 @@ export function cmdUnadopt(args = []) {
326
311
  const all = hasFlag(args, '--all');
327
312
  const dryRun = hasFlag(args, '--dry-run');
328
313
  const force = hasFlag(args, '--force');
329
- const targets = all
330
- ? listAllMemdirs().map((m) => m.memdir)
331
- : [memdirPath(detectCwd())];
332
314
 
333
- if (targets.length === 0) {
334
- log('[unadopt] no memdirs found');
335
- return;
336
- }
315
+ if (all) return migrateAll(['--all', ...(force ? ['--force'] : []), ...(dryRun ? ['--dry-run'] : [])]);
337
316
 
338
- let removed = 0, absent = 0, skipped = 0;
339
- for (const memdir of targets) {
340
- if (dryRun) {
341
- // Mirror the live foreign-content guard: a sentinel with no state sidecar would be
342
- // skipped (not removed) unless --force, so dry-run must report it the same way.
343
- const action = !isAdopted(memdir, PLUGIN_SLUG) ? 'absent'
344
- : (hasPluginState(memdir, PLUGIN_SLUG) || force) ? 'would-remove'
345
- : 'would-skip-foreign';
346
- log(`[unadopt --dry-run] ${memdir} → ${action}`);
347
- if (action === 'would-remove') removed++;
348
- else if (action === 'would-skip-foreign') skipped++;
349
- else absent++;
350
- continue;
351
- }
352
- const r = removePluginSection(memdir, PLUGIN_SLUG, { force });
353
- if (r.action === 'removed') { removePluginDoc(memdir, PLUGIN_SLUG); removed++; }
354
- else if (r.action === 'skipped-foreign') skipped++;
355
- else absent++;
356
- log(`[unadopt] ${memdir} → ${r.action}`);
317
+ const cwd = detectCwd();
318
+ if (dryRun) {
319
+ const blockState = claudeMdIsAdopted(cwd, PLUGIN_SLUG) ? 'would-remove CLAUDE.md block + detail doc' : 'no CLAUDE.md block';
320
+ const legacy = hasLegacyMemdirSentinel(cwd, PLUGIN_SLUG) ? 'would-clean legacy memory-dir sentinel' : 'no legacy residue';
321
+ log(`[unadopt --dry-run] ${cwd}`);
322
+ log(` ${blockState}`);
323
+ log(` ${legacy}`);
324
+ return;
357
325
  }
358
326
 
359
- if (skipped > 0) {
360
- log('[unadopt] skipped-foreign = a sentinel block with no plugin state file (not proven plugin-written).');
361
- log('[unadopt] pass --force to remove it anyway.');
362
- }
363
- log('');
364
- const verb = dryRun ? 'would remove' : 'removed';
365
- log(`[unadopt${dryRun ? ' --dry-run' : ''}] ${targets.length} target(s): ${removed} ${verb}, ${skipped} skipped-foreign, ${absent} absent`);
327
+ const r = removeManaged(cwd, PLUGIN_SLUG);
328
+ const mig = migrateLegacyMemoryDir(cwd, PLUGIN_SLUG, { force });
329
+ const migNote = mig.action === 'removed' ? ' (+cleaned legacy memdir)' : '';
330
+ log(`[unadopt] ${cwd} → ${r.action}${migNote}`);
366
331
  }