claude-mem-lite 3.96.0 → 3.97.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/adopt-cli.mjs +125 -51
- package/bash-utils.mjs +105 -29
- package/claudemd.mjs +63 -19
- package/cli/activity.mjs +36 -18
- package/cli/common.mjs +101 -18
- package/cli/doctor.mjs +29 -9
- package/cli.mjs +69 -10
- package/deep-search.mjs +63 -20
- package/format-utils.mjs +15 -7
- package/haiku-client.mjs +189 -111
- package/hash-utils.mjs +15 -6
- package/hook-context.mjs +135 -46
- package/hook-episode.mjs +76 -23
- package/hook-handoff.mjs +216 -84
- package/hook-llm.mjs +476 -216
- package/hook-memory.mjs +106 -43
- package/hook-optimize.mjs +272 -109
- package/hook-precompact.mjs +1 -1
- package/hook-semaphore.mjs +46 -12
- package/hook-shared.mjs +101 -131
- package/hook-update.mjs +286 -88
- package/hook.mjs +1207 -617
- package/install-metadata.mjs +246 -125
- package/install.mjs +1327 -917
- package/lib/activity.mjs +52 -29
- package/lib/atomic-write.mjs +23 -4
- package/lib/binding-probe.mjs +40 -38
- package/lib/browse-core.mjs +22 -8
- package/lib/citation-tracker.mjs +189 -79
- package/lib/cite-back-hint.mjs +37 -19
- package/lib/cite-recall-path.mjs +3 -1
- package/lib/cli-flags.mjs +4 -2
- package/lib/cli-project.mjs +8 -5
- package/lib/compress-core.mjs +34 -9
- package/lib/cooldown-path.mjs +3 -1
- package/lib/db-backup.mjs +33 -11
- package/lib/deferred-work.mjs +73 -32
- package/lib/delete-core.mjs +25 -9
- package/lib/doctor-benchmark.mjs +5 -1
- package/lib/doctor-drift.mjs +25 -8
- package/lib/edge-attribution.mjs +23 -9
- package/lib/env-number.mjs +7 -8
- package/lib/err-sampler.mjs +22 -11
- package/lib/error-recall-core.mjs +15 -17
- package/lib/events-injection.mjs +25 -8
- package/lib/export-columns.mjs +49 -10
- package/lib/fast-summary.mjs +22 -9
- package/lib/file-edge-match.mjs +4 -2
- package/lib/file-intel.mjs +38 -13
- package/lib/frontmatter.mjs +22 -5
- package/lib/get-core.mjs +78 -11
- package/lib/handoff-constants.mjs +18 -0
- package/lib/hook-stdin.mjs +10 -3
- package/lib/hook-stdout.mjs +21 -7
- package/lib/hook-telemetry.mjs +44 -17
- package/lib/id-routing.mjs +11 -6
- package/lib/import-jsonl.mjs +92 -41
- package/lib/inject-search-core.mjs +5 -2
- package/lib/injected-ids.mjs +19 -12
- package/lib/install-shape.mjs +47 -23
- package/lib/keyctx-marker.mjs +3 -1
- package/lib/lesson-bridge.mjs +8 -5
- package/lib/llm-call.mjs +76 -0
- package/lib/llm-provider-probe.mjs +24 -12
- package/lib/low-signal-patterns.mjs +25 -22
- package/lib/maintain-core.mjs +221 -92
- package/lib/mem-override.mjs +4 -2
- package/lib/metrics.mjs +30 -25
- package/lib/native-binding-hint.mjs +29 -8
- package/lib/observation-write.mjs +74 -19
- package/lib/patha-exclude-meter.mjs +10 -6
- package/lib/persist-reminder.mjs +22 -10
- package/lib/plan-reader.mjs +2 -2
- package/lib/private-strip.mjs +3 -3
- package/lib/proc-lock.mjs +15 -3
- package/lib/proxy-fetch.mjs +49 -13
- package/lib/quiet-scope.mjs +42 -0
- package/lib/recall-core.mjs +19 -7
- package/lib/recent-core.mjs +18 -5
- package/lib/registry-core.mjs +12 -6
- package/lib/release-digest.mjs +4 -1
- package/lib/relevance-floor.mjs +5 -2
- package/lib/reread-guard.mjs +16 -8
- package/lib/resolve-data-dir.mjs +1 -1
- package/lib/rrf.mjs +4 -1
- package/lib/save-enrich.mjs +36 -18
- package/lib/save-observation.mjs +78 -35
- package/lib/scrub-record.mjs +20 -6
- package/lib/search-core.mjs +456 -123
- package/lib/shard-gc.mjs +50 -0
- package/lib/startup-dashboard.mjs +20 -9
- package/lib/stats-core.mjs +101 -35
- package/lib/stats-quality.mjs +56 -20
- package/lib/summary-extractor.mjs +2 -2
- package/lib/task-imperative.mjs +12 -4
- package/lib/timeline-core.mjs +88 -28
- package/lib/tmp-fixture-sweep.mjs +32 -9
- package/lib/transcript-scan.mjs +20 -4
- package/lib/upgrade-banner.mjs +11 -7
- package/mem-cli.mjs +1314 -506
- package/memdir.mjs +53 -21
- package/nlp.mjs +68 -38
- package/npm-shrinkwrap.json +2 -2
- package/package.json +9 -2
- package/plugin-cache-guard.mjs +31 -15
- package/project-utils.mjs +44 -20
- package/registry-enricher.mjs +26 -6
- package/registry-github.mjs +4 -2
- package/registry-importer.mjs +151 -40
- package/registry-recommend.mjs +189 -96
- package/registry-retriever.mjs +229 -70
- package/registry-scanner.mjs +11 -6
- package/registry.mjs +163 -66
- package/rerank.mjs +18 -4
- package/resource-discovery.mjs +14 -4
- package/schema.mjs +258 -99
- package/scoring-sql.mjs +21 -10
- package/scripts/binding-probe-cli.mjs +17 -12
- package/scripts/hook-launcher.mjs +148 -31
- package/scripts/launch-preflight.mjs +5 -7
- package/scripts/launch.mjs +21 -9
- package/scripts/post-tool-recall.js +21 -5
- package/scripts/pre-agent-inject.js +51 -13
- package/scripts/pre-skill-bridge.js +22 -7
- package/scripts/pre-tool-recall.js +157 -77
- package/scripts/prompt-search-utils.mjs +70 -25
- package/scripts/setup.sh +16 -1
- package/scripts/user-prompt-search.js +238 -97
- package/search-engine.mjs +329 -96
- package/search-scoring.mjs +75 -33
- package/secret-scrub.mjs +48 -12
- package/server/fts-check.mjs +12 -9
- package/server.mjs +674 -243
- package/skip-tools.mjs +19 -8
- package/source-files.mjs +54 -21
- package/stop-words.mjs +152 -19
- package/synonyms.mjs +579 -164
- package/tfidf.mjs +130 -40
- package/tier.mjs +1 -1
- package/tool-schemas.mjs +381 -131
- package/utils.mjs +184 -36
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.97.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.
|
|
3
|
+
"version": "3.97.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
|
@@ -17,20 +17,28 @@ import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync
|
|
|
17
17
|
import { homedir } from 'os';
|
|
18
18
|
import { join, isAbsolute } from 'path';
|
|
19
19
|
import {
|
|
20
|
-
memdirPath,
|
|
21
|
-
|
|
20
|
+
memdirPath,
|
|
21
|
+
removePluginSection,
|
|
22
|
+
removePluginDoc,
|
|
23
|
+
isAdopted as memdirIsAdopted,
|
|
24
|
+
hasPluginState,
|
|
22
25
|
} from './memdir.mjs';
|
|
23
26
|
import {
|
|
24
|
-
writeManaged,
|
|
27
|
+
writeManaged,
|
|
28
|
+
removeManaged,
|
|
29
|
+
isAdopted as claudeMdIsAdopted,
|
|
25
30
|
hasResidue as claudeMdHasResidue,
|
|
26
|
-
needsRefresh,
|
|
27
|
-
|
|
31
|
+
needsRefresh,
|
|
32
|
+
migrateLegacyMemoryDir,
|
|
33
|
+
hasLegacyMemdirSentinel,
|
|
34
|
+
claudeMdPath,
|
|
35
|
+
detailDocPath,
|
|
28
36
|
} from './claudemd.mjs';
|
|
29
|
-
import {
|
|
30
|
-
PLUGIN_SLUG, CURRENT_SENTINEL_VERSION, buildClaudeMdBlock, getDetailDoc,
|
|
31
|
-
} from './adopt-content.mjs';
|
|
37
|
+
import { PLUGIN_SLUG, CURRENT_SENTINEL_VERSION, buildClaudeMdBlock, getDetailDoc } from './adopt-content.mjs';
|
|
32
38
|
|
|
33
|
-
function log(msg) {
|
|
39
|
+
function log(msg) {
|
|
40
|
+
console.log(msg);
|
|
41
|
+
}
|
|
34
42
|
|
|
35
43
|
function detectCwd() {
|
|
36
44
|
return process.env.CLAUDE_PROJECT_DIR || process.env.PWD || process.cwd();
|
|
@@ -50,12 +58,16 @@ function listAllMemdirs() {
|
|
|
50
58
|
if (existsSync(memdir) && statSync(memdir).isDirectory()) {
|
|
51
59
|
out.push({ projectSlug: name, memdir });
|
|
52
60
|
}
|
|
53
|
-
} catch {
|
|
61
|
+
} catch {
|
|
62
|
+
/* ignore entries we can't stat */
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
return out;
|
|
56
66
|
}
|
|
57
67
|
|
|
58
|
-
function claudeConfigPath() {
|
|
68
|
+
function claudeConfigPath() {
|
|
69
|
+
return join(homedir(), '.claude.json');
|
|
70
|
+
}
|
|
59
71
|
|
|
60
72
|
// Real adopted-project paths come from Claude Code's own ~/.claude.json `projects`
|
|
61
73
|
// map (keys are absolute cwds Claude Code has opened). The memdir slug under
|
|
@@ -71,10 +83,14 @@ function listKnownProjectDirs() {
|
|
|
71
83
|
const cfg = JSON.parse(readFileSync(p, 'utf8'));
|
|
72
84
|
const projects = cfg && cfg.projects && typeof cfg.projects === 'object' ? Object.keys(cfg.projects) : [];
|
|
73
85
|
return projects.filter((d) => typeof d === 'string' && isAbsolute(d) && existsSync(d));
|
|
74
|
-
} catch {
|
|
86
|
+
} catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
function hasFlag(args, flag) {
|
|
91
|
+
function hasFlag(args, flag) {
|
|
92
|
+
return Array.isArray(args) && args.includes(flag);
|
|
93
|
+
}
|
|
78
94
|
|
|
79
95
|
// ─── Per-project auto-adopt opt-out sentinel ─────────────────────────────────
|
|
80
96
|
// `<memdir>/.mem-no-auto-adopt` is the durable, project-scoped escape hatch.
|
|
@@ -154,14 +170,22 @@ function migrateAll(args) {
|
|
|
154
170
|
const force = hasFlag(args, '--force');
|
|
155
171
|
const dryRun = hasFlag(args, '--dry-run');
|
|
156
172
|
const dirs = listAllMemdirs();
|
|
157
|
-
if (dirs.length === 0) {
|
|
173
|
+
if (dirs.length === 0) {
|
|
174
|
+
log('[adopt --all] no memdirs found');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
158
177
|
|
|
159
|
-
let removed = 0,
|
|
178
|
+
let removed = 0,
|
|
179
|
+
absent = 0,
|
|
180
|
+
skipped = 0;
|
|
160
181
|
for (const { projectSlug, memdir } of dirs) {
|
|
161
182
|
if (dryRun) {
|
|
162
183
|
const has = memdirIsAdopted(memdir, PLUGIN_SLUG);
|
|
163
|
-
const action = !has
|
|
164
|
-
|
|
184
|
+
const action = !has
|
|
185
|
+
? 'absent'
|
|
186
|
+
: hasPluginState(memdir, PLUGIN_SLUG) || force
|
|
187
|
+
? 'would-remove'
|
|
188
|
+
: 'would-skip-foreign';
|
|
165
189
|
log(`[adopt --all --dry-run] ${projectSlug} → ${action}`);
|
|
166
190
|
if (action === 'would-remove') removed++;
|
|
167
191
|
else if (action === 'would-skip-foreign') skipped++;
|
|
@@ -169,13 +193,19 @@ function migrateAll(args) {
|
|
|
169
193
|
continue;
|
|
170
194
|
}
|
|
171
195
|
const r = removePluginSection(memdir, PLUGIN_SLUG, { force });
|
|
172
|
-
if (r.action === 'removed') {
|
|
173
|
-
|
|
196
|
+
if (r.action === 'removed') {
|
|
197
|
+
removePluginDoc(memdir, PLUGIN_SLUG);
|
|
198
|
+
removed++;
|
|
199
|
+
} else if (r.action === 'skipped-foreign') skipped++;
|
|
174
200
|
else absent++;
|
|
175
201
|
}
|
|
176
202
|
log('');
|
|
177
|
-
log(
|
|
178
|
-
|
|
203
|
+
log(
|
|
204
|
+
`[adopt --all] legacy memory-dir cleanup over ${dirs.length} project(s): ${removed} cleaned, ${skipped} skipped-foreign, ${absent} none.`,
|
|
205
|
+
);
|
|
206
|
+
log(
|
|
207
|
+
"[adopt --all] CLAUDE.md adoption is per-project — it runs automatically on each project's next SessionStart.",
|
|
208
|
+
);
|
|
179
209
|
}
|
|
180
210
|
|
|
181
211
|
/**
|
|
@@ -205,15 +235,21 @@ export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
|
|
|
205
235
|
if (!claudeMdIsAdopted(cwd, PLUGIN_SLUG)) {
|
|
206
236
|
writeManaged(cwd, { slug: PLUGIN_SLUG, version, block, doc });
|
|
207
237
|
action = 'adopted';
|
|
208
|
-
} else if (
|
|
209
|
-
|
|
238
|
+
} else if (
|
|
239
|
+
process.env.CLAUDE_MEM_NO_TEMPLATE_REFRESH !== '1' &&
|
|
240
|
+
needsRefresh(cwd, { slug: PLUGIN_SLUG, version, block, doc })
|
|
241
|
+
) {
|
|
210
242
|
writeManaged(cwd, { slug: PLUGIN_SLUG, version, block, doc });
|
|
211
243
|
action = 'refreshed';
|
|
212
244
|
}
|
|
213
245
|
if (markerDir && markerKey) writeMarker(markerDir, markerKey);
|
|
214
246
|
return { ok: true, action };
|
|
215
247
|
} catch (e) {
|
|
216
|
-
try {
|
|
248
|
+
try {
|
|
249
|
+
if (markerDir && markerKey) writeMarker(markerDir, markerKey);
|
|
250
|
+
} catch {
|
|
251
|
+
/* best-effort */
|
|
252
|
+
}
|
|
217
253
|
return { ok: false, action: 'skipped', reason: 'error', err: e };
|
|
218
254
|
}
|
|
219
255
|
}
|
|
@@ -235,13 +271,15 @@ export function hasAutoAdoptMarker(markerDir, markerKey) {
|
|
|
235
271
|
*/
|
|
236
272
|
function cmdDisable(args) {
|
|
237
273
|
const all = hasFlag(args, '--all');
|
|
238
|
-
const targets = all
|
|
239
|
-
? listAllMemdirs().map((m) => m.memdir)
|
|
240
|
-
: [memdirPath(detectCwd())];
|
|
274
|
+
const targets = all ? listAllMemdirs().map((m) => m.memdir) : [memdirPath(detectCwd())];
|
|
241
275
|
|
|
242
|
-
if (targets.length === 0) {
|
|
276
|
+
if (targets.length === 0) {
|
|
277
|
+
log('[adopt --disable] no memdirs found');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
243
280
|
|
|
244
|
-
let disabled = 0,
|
|
281
|
+
let disabled = 0,
|
|
282
|
+
already = 0;
|
|
245
283
|
for (const memdir of targets) {
|
|
246
284
|
if (!existsSync(memdir)) mkdirSync(memdir, { recursive: true });
|
|
247
285
|
const path = disableSentinelPath(memdir);
|
|
@@ -255,7 +293,9 @@ function cmdDisable(args) {
|
|
|
255
293
|
disabled++;
|
|
256
294
|
}
|
|
257
295
|
log('');
|
|
258
|
-
log(
|
|
296
|
+
log(
|
|
297
|
+
`[adopt --disable] ${targets.length} target(s): ${disabled} newly disabled, ${already} already disabled`,
|
|
298
|
+
);
|
|
259
299
|
}
|
|
260
300
|
|
|
261
301
|
/**
|
|
@@ -264,13 +304,15 @@ function cmdDisable(args) {
|
|
|
264
304
|
*/
|
|
265
305
|
function cmdEnable(args) {
|
|
266
306
|
const all = hasFlag(args, '--all');
|
|
267
|
-
const targets = all
|
|
268
|
-
? listAllMemdirs().map((m) => m.memdir)
|
|
269
|
-
: [memdirPath(detectCwd())];
|
|
307
|
+
const targets = all ? listAllMemdirs().map((m) => m.memdir) : [memdirPath(detectCwd())];
|
|
270
308
|
|
|
271
|
-
if (targets.length === 0) {
|
|
309
|
+
if (targets.length === 0) {
|
|
310
|
+
log('[adopt --enable] no memdirs found');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
272
313
|
|
|
273
|
-
let enabled = 0,
|
|
314
|
+
let enabled = 0,
|
|
315
|
+
absent = 0;
|
|
274
316
|
for (const memdir of targets) {
|
|
275
317
|
const path = disableSentinelPath(memdir);
|
|
276
318
|
if (!existsSync(path)) {
|
|
@@ -278,7 +320,11 @@ function cmdEnable(args) {
|
|
|
278
320
|
absent++;
|
|
279
321
|
continue;
|
|
280
322
|
}
|
|
281
|
-
try {
|
|
323
|
+
try {
|
|
324
|
+
unlinkSync(path);
|
|
325
|
+
} catch {
|
|
326
|
+
/* best-effort */
|
|
327
|
+
}
|
|
282
328
|
log(`[adopt --enable] ${memdir} → enabled`);
|
|
283
329
|
enabled++;
|
|
284
330
|
}
|
|
@@ -301,26 +347,35 @@ function statusAll() {
|
|
|
301
347
|
}
|
|
302
348
|
|
|
303
349
|
const dirs = listAllMemdirs();
|
|
304
|
-
let legacy = 0,
|
|
350
|
+
let legacy = 0,
|
|
351
|
+
disabled = 0;
|
|
305
352
|
for (const { memdir } of dirs) {
|
|
306
353
|
if (memdirIsAdopted(memdir, PLUGIN_SLUG)) legacy++;
|
|
307
354
|
if (isAutoAdoptDisabled(memdir)) disabled++;
|
|
308
355
|
}
|
|
309
356
|
log('');
|
|
310
|
-
log(
|
|
311
|
-
|
|
357
|
+
log(
|
|
358
|
+
`[adopt --status] scanned ${dirs.length} memdir(s): ${legacy} with legacy sentinel (await migration), ${disabled} auto-adopt-disabled.`,
|
|
359
|
+
);
|
|
360
|
+
if (legacy > 0)
|
|
361
|
+
log('[adopt --status] run `claude-mem-lite adopt --all` to sweep legacy memory-dir sentinels now.');
|
|
312
362
|
|
|
313
363
|
const known = listKnownProjectDirs();
|
|
314
364
|
let adoptedCount = 0;
|
|
315
365
|
for (const dir of known) if (claudeMdHasResidue(dir, PLUGIN_SLUG)) adoptedCount++;
|
|
316
|
-
log(
|
|
317
|
-
|
|
366
|
+
log(
|
|
367
|
+
`[adopt --status] known projects (~/.claude.json): ${known.length} scanned, ${adoptedCount} with a CLAUDE.md managed block or partial residue (detail doc/state).`,
|
|
368
|
+
);
|
|
369
|
+
if (adoptedCount > 0)
|
|
370
|
+
log('[adopt --status] run `claude-mem-lite unadopt --all` to remove every CLAUDE.md block.');
|
|
318
371
|
|
|
319
372
|
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ? 'set' : 'unset';
|
|
320
373
|
const noAutoAdopt = process.env.MEM_NO_AUTO_ADOPT === '1' ? '1 (opt-out)' : 'unset';
|
|
321
374
|
log('');
|
|
322
375
|
log('Auto-adopt gates (next SessionStart fires only if these pass):');
|
|
323
|
-
log(
|
|
376
|
+
log(
|
|
377
|
+
` CLAUDE_PLUGIN_ROOT = ${pluginRoot} (any install path is consent; gate is the per-project opt-out below)`,
|
|
378
|
+
);
|
|
324
379
|
log(` MEM_NO_AUTO_ADOPT = ${noAutoAdopt} (global escape hatch)`);
|
|
325
380
|
log('Per-project opt-out: `claude-mem-lite adopt --disable` (run --enable to re-arm).');
|
|
326
381
|
}
|
|
@@ -348,20 +403,28 @@ function unadoptAll(args) {
|
|
|
348
403
|
|
|
349
404
|
// 1. New scheme: scrub CLAUDE.md managed blocks across known project paths.
|
|
350
405
|
const projectDirs = listKnownProjectDirs();
|
|
351
|
-
let blocks = 0,
|
|
406
|
+
let blocks = 0,
|
|
407
|
+
partial = 0;
|
|
352
408
|
for (const dir of projectDirs) {
|
|
353
409
|
// hasResidue, not isAdopted: the sweep must also catch PARTIAL residue
|
|
354
410
|
// (block without detail doc, or an orphaned doc/state sidecar) —
|
|
355
411
|
// isAdopted's block-AND-doc gate skipped those projects forever.
|
|
356
412
|
if (!claudeMdHasResidue(dir, PLUGIN_SLUG)) continue;
|
|
357
413
|
if (dryRun) {
|
|
358
|
-
log(
|
|
414
|
+
log(
|
|
415
|
+
`[unadopt --all --dry-run] ${dir} → would-remove plugin residue (CLAUDE.md block and/or detail doc/state)`,
|
|
416
|
+
);
|
|
359
417
|
blocks++;
|
|
360
418
|
continue;
|
|
361
419
|
}
|
|
362
420
|
const r = removeManaged(dir, PLUGIN_SLUG);
|
|
363
|
-
if (r.action === 'removed') {
|
|
364
|
-
|
|
421
|
+
if (r.action === 'removed') {
|
|
422
|
+
log(`[unadopt --all] ${dir} → removed`);
|
|
423
|
+
blocks++;
|
|
424
|
+
} else {
|
|
425
|
+
log(`[unadopt --all] ${dir} → cleaned partial residue (detail doc/state, no block)`);
|
|
426
|
+
partial++;
|
|
427
|
+
}
|
|
365
428
|
}
|
|
366
429
|
|
|
367
430
|
// 2. Legacy memory-dir cleanup across every memdir (foreign-content guarded).
|
|
@@ -373,14 +436,21 @@ function unadoptAll(args) {
|
|
|
373
436
|
continue;
|
|
374
437
|
}
|
|
375
438
|
const r = removePluginSection(memdir, PLUGIN_SLUG, { force });
|
|
376
|
-
if (r.action === 'removed') {
|
|
439
|
+
if (r.action === 'removed') {
|
|
440
|
+
removePluginDoc(memdir, PLUGIN_SLUG);
|
|
441
|
+
legacy++;
|
|
442
|
+
}
|
|
377
443
|
}
|
|
378
444
|
|
|
379
445
|
log('');
|
|
380
446
|
const partialNote = partial > 0 ? ` (+${partial} partial-residue cleanup(s))` : '';
|
|
381
|
-
log(
|
|
447
|
+
log(
|
|
448
|
+
`[unadopt --all] ${dryRun ? 'would remove' : 'removed'} ${blocks} CLAUDE.md block(s)${partialNote} across ${projectDirs.length} known project(s); ${legacy} legacy memory-dir sentinel(s) ${dryRun ? 'pending' : 'cleaned'}.`,
|
|
449
|
+
);
|
|
382
450
|
if (projectDirs.length === 0) {
|
|
383
|
-
log(
|
|
451
|
+
log(
|
|
452
|
+
'[unadopt --all] no known projects found in ~/.claude.json — if a project was adopted but never opened in Claude Code, run `claude-mem-lite unadopt` from inside it.',
|
|
453
|
+
);
|
|
384
454
|
}
|
|
385
455
|
}
|
|
386
456
|
|
|
@@ -395,8 +465,12 @@ export function cmdUnadopt(args = []) {
|
|
|
395
465
|
|
|
396
466
|
const cwd = detectCwd();
|
|
397
467
|
if (dryRun) {
|
|
398
|
-
const blockState = claudeMdHasResidue(cwd, PLUGIN_SLUG)
|
|
399
|
-
|
|
468
|
+
const blockState = claudeMdHasResidue(cwd, PLUGIN_SLUG)
|
|
469
|
+
? 'would-remove CLAUDE.md block + detail doc'
|
|
470
|
+
: 'no CLAUDE.md block';
|
|
471
|
+
const legacy = hasLegacyMemdirSentinel(cwd, PLUGIN_SLUG)
|
|
472
|
+
? 'would-clean legacy memory-dir sentinel'
|
|
473
|
+
: 'no legacy residue';
|
|
400
474
|
log(`[unadopt --dry-run] ${cwd}`);
|
|
401
475
|
log(` ${blockState}`);
|
|
402
476
|
log(` ${legacy}`);
|
package/bash-utils.mjs
CHANGED
|
@@ -6,12 +6,38 @@ import { basename } from 'path';
|
|
|
6
6
|
// Read/search commands whose output legitimately contains "error"-like keywords without
|
|
7
7
|
// being a failure. Matched against the PRIMARY command (see isReadOnlyCommand).
|
|
8
8
|
const SEARCH_VERBS = new Set([
|
|
9
|
-
'grep',
|
|
9
|
+
'grep',
|
|
10
|
+
'rg',
|
|
11
|
+
'ag',
|
|
12
|
+
'ack',
|
|
13
|
+
'cat',
|
|
14
|
+
'head',
|
|
15
|
+
'tail',
|
|
16
|
+
'less',
|
|
17
|
+
'more',
|
|
18
|
+
'find',
|
|
19
|
+
'locate',
|
|
20
|
+
'wc',
|
|
21
|
+
'file',
|
|
22
|
+
'which',
|
|
23
|
+
'type',
|
|
10
24
|
]);
|
|
11
25
|
// Command prefixes that wrap the real command (env-assignments handled separately).
|
|
12
26
|
const CMD_WRAPPERS = new Set(['sudo', 'doas', 'env', 'time', 'command', 'nice', 'nohup', 'stdbuf', 'xargs']);
|
|
13
27
|
// git read subcommands whose output contains commit/log/match text, not failures.
|
|
14
|
-
const GIT_READ_SUBCMDS = new Set([
|
|
28
|
+
const GIT_READ_SUBCMDS = new Set([
|
|
29
|
+
'grep',
|
|
30
|
+
'log',
|
|
31
|
+
'show',
|
|
32
|
+
'diff',
|
|
33
|
+
'blame',
|
|
34
|
+
'ls-files',
|
|
35
|
+
'cat-file',
|
|
36
|
+
'whatchanged',
|
|
37
|
+
'shortlog',
|
|
38
|
+
'reflog',
|
|
39
|
+
'status',
|
|
40
|
+
]);
|
|
15
41
|
|
|
16
42
|
// Hard failure fingerprints — a real crash / thrown exception / non-zero-exit marker,
|
|
17
43
|
// as opposed to output that merely CONTAINS the word "error" (search results, log
|
|
@@ -21,7 +47,8 @@ const GIT_READ_SUBCMDS = new Set(['grep', 'log', 'show', 'diff', 'blame', 'ls-fi
|
|
|
21
47
|
// intentionally excluded — they appear too often in benign search/log output. Gates
|
|
22
48
|
// the bugfix-shape save-nudge (lib/cite-back-hint.mjs) so `node cli.mjs search "error"`
|
|
23
49
|
// + an edit in the same episode no longer looks like an unsaved fix.
|
|
24
|
-
const HARD_ERROR_RE =
|
|
50
|
+
const HARD_ERROR_RE =
|
|
51
|
+
/\bERR!|\bpanic\b|traceback|segfault|core dumped|\benoent\b|command not found|assertion\s?error|\n\s+at\s+\S|(?:type|reference|range|syntax|eval|uri)error:/i;
|
|
25
52
|
|
|
26
53
|
// True when the command's PRIMARY operation (left of the first pipe, past any
|
|
27
54
|
// env-assignments / wrapper like `sudo`/`env`/`time`) is a read/search — including
|
|
@@ -66,9 +93,12 @@ export function detectBashSignificance(input, response) {
|
|
|
66
93
|
// stays a real failure while `sudo grep`, `git grep`, `git log --grep`, `cat f | head`
|
|
67
94
|
// remain exempt and `run-cat-tests` doesn't trip a substring match.
|
|
68
95
|
const isSearchCmd = isReadOnlyCommand(cmd);
|
|
69
|
-
const looksLikeError =
|
|
70
|
-
&&
|
|
71
|
-
|
|
96
|
+
const looksLikeError =
|
|
97
|
+
!isSearchCmd &&
|
|
98
|
+
/\berror\b|\bERR!|fail(ed|ure)?|exception|panic|traceback|errno|enoent|command not found/i.test(
|
|
99
|
+
response,
|
|
100
|
+
) &&
|
|
101
|
+
response.length > 15;
|
|
72
102
|
// Green test summary exemption — "0 fail/failed/failures" in test-runner
|
|
73
103
|
// output (bun/jest/pytest) gets matched by the broad `fail(ed|ure)?` token
|
|
74
104
|
// above, driving episode.isError=true for passing runs. A live cluster-merge
|
|
@@ -76,45 +106,79 @@ export function detectBashSignificance(input, response) {
|
|
|
76
106
|
// from this path. Flip back to non-error iff a "0 fail" marker is present
|
|
77
107
|
// AND no hard-error signal (panic / ENOENT / AssertionError / TypeError /
|
|
78
108
|
// explicit FAIL banner / npm ERR!) coexists in the output.
|
|
79
|
-
const hasGreenTestSummary = looksLikeError
|
|
80
|
-
&& /\b0\s+(fail|failed|failures)\b/i.test(response);
|
|
109
|
+
const hasGreenTestSummary = looksLikeError && /\b0\s+(fail|failed|failures)\b/i.test(response);
|
|
81
110
|
// NOTE: do not add `\bFAIL\s` here — with /i flag it would re-match the
|
|
82
111
|
// very `0 fail\n` token green-summary is trying to exempt. A real test
|
|
83
112
|
// failure produces "N fail" (N≥1) which never triggers hasGreenTestSummary,
|
|
84
113
|
// so a uppercase-FAIL fingerprint isn't needed for correctness.
|
|
85
|
-
const hasHardErrorSignal =
|
|
86
|
-
&&
|
|
114
|
+
const hasHardErrorSignal =
|
|
115
|
+
hasGreenTestSummary &&
|
|
116
|
+
/\bERR!|panic|traceback|enoent|command not found|exception|AssertionError|TypeError:|SyntaxError:/i.test(
|
|
117
|
+
response,
|
|
118
|
+
);
|
|
87
119
|
const isError = looksLikeError && !(hasGreenTestSummary && !hasHardErrorSignal);
|
|
88
120
|
// Strict subset of isError: a genuine failure fingerprint, not just the word "error"
|
|
89
121
|
// in benign output. Consumers that must avoid false positives (the bugfix-shape
|
|
90
122
|
// save-nudge) gate on this instead of isError.
|
|
91
123
|
const isHardError = isError && HARD_ERROR_RE.test(response);
|
|
92
124
|
// Match actual test runner invocations, not commands that merely reference "test" as a keyword
|
|
93
|
-
const isTest =
|
|
94
|
-
|
|
125
|
+
const isTest =
|
|
126
|
+
/\b(npm\s+test|npm\s+run\s+test|yarn\s+test|pnpm\s+test|pnpm\s+run\s+test|bun\s+test|go\s+test|cargo\s+test)\b/i.test(
|
|
127
|
+
cmd,
|
|
128
|
+
) || /\b(jest|pytest|vitest|mocha|cypress|playwright)\b/i.test(cmd);
|
|
95
129
|
const isBuild = /\b(build|compile|tsc|webpack|vite|rollup|esbuild|make|cargo)\b/i.test(cmd);
|
|
96
130
|
// Allow intervening global git options (`-C <path>`, `-c k=v`, `--no-pager`, …) between
|
|
97
131
|
// `git` and the subcommand — `git -C /repo push` is the standard multi-repo/scripted form.
|
|
98
|
-
const isGit =
|
|
132
|
+
const isGit =
|
|
133
|
+
/\bgit\s+(?:(?:-[cC]\s+\S+|--?[\w-]+(?:=\S+)?)\s+)*(commit|merge|rebase|cherry-pick|push)\b/i.test(cmd);
|
|
99
134
|
// Deploy + publish/release: the actual "ship". Package publish and GitHub
|
|
100
135
|
// release are rare, high-value events; without them a release session records
|
|
101
136
|
// the git push but not the publish that defines it. `npm run publish-*` is
|
|
102
137
|
// excluded (custom script, ambiguous) — only the direct publish verb counts.
|
|
103
|
-
const isDeploy =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
138
|
+
const isDeploy =
|
|
139
|
+
/\b(deploy|docker|kubectl|terraform)\b/i.test(cmd) ||
|
|
140
|
+
/\b(?:npm|pnpm|yarn|bun|cargo)\s+publish\b/i.test(cmd) ||
|
|
141
|
+
/\bgh\s+release\s+(?:create|edit|upload|delete)\b/i.test(cmd) ||
|
|
142
|
+
/\btwine\s+upload\b/i.test(cmd);
|
|
107
143
|
return {
|
|
108
|
-
isError,
|
|
144
|
+
isError,
|
|
145
|
+
isHardError,
|
|
146
|
+
isTest,
|
|
147
|
+
isBuild,
|
|
148
|
+
isGit,
|
|
149
|
+
isDeploy,
|
|
109
150
|
isSignificant: isError || isTest || isBuild || isGit || isDeploy,
|
|
110
151
|
};
|
|
111
152
|
}
|
|
112
153
|
|
|
113
154
|
const ERROR_STOP_WORDS = new Set([
|
|
114
|
-
'error',
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
155
|
+
'error',
|
|
156
|
+
'failed',
|
|
157
|
+
'cannot',
|
|
158
|
+
'could',
|
|
159
|
+
'with',
|
|
160
|
+
'from',
|
|
161
|
+
'that',
|
|
162
|
+
'this',
|
|
163
|
+
'have',
|
|
164
|
+
'been',
|
|
165
|
+
'were',
|
|
166
|
+
'does',
|
|
167
|
+
'will',
|
|
168
|
+
'would',
|
|
169
|
+
'should',
|
|
170
|
+
'must',
|
|
171
|
+
'true',
|
|
172
|
+
'false',
|
|
173
|
+
'null',
|
|
174
|
+
'undefined',
|
|
175
|
+
'function',
|
|
176
|
+
'return',
|
|
177
|
+
'const',
|
|
178
|
+
'node',
|
|
179
|
+
'require',
|
|
180
|
+
'stack',
|
|
181
|
+
'trace',
|
|
118
182
|
]);
|
|
119
183
|
|
|
120
184
|
const ERROR_LINE_RE = /error|fail|exception|cannot|not found|undefined|null/i;
|
|
@@ -183,10 +247,15 @@ const ERROR_NAMER_MAX = 1;
|
|
|
183
247
|
function collectErrorTerms(cmd, response) {
|
|
184
248
|
const seen = new Set();
|
|
185
249
|
const cmdWords = [];
|
|
186
|
-
const cmdParts = String(cmd || '')
|
|
250
|
+
const cmdParts = String(cmd || '')
|
|
251
|
+
.split(/[\s/\\|&;]+/)
|
|
252
|
+
.filter((w) => w.length > 2 && !/^-/.test(w));
|
|
187
253
|
for (const w of cmdParts.slice(0, 3)) {
|
|
188
254
|
const lw = w.toLowerCase();
|
|
189
|
-
if (!ERROR_STOP_WORDS.has(lw) && !seen.has(lw)) {
|
|
255
|
+
if (!ERROR_STOP_WORDS.has(lw) && !seen.has(lw)) {
|
|
256
|
+
seen.add(lw);
|
|
257
|
+
cmdWords.push(lw);
|
|
258
|
+
}
|
|
190
259
|
}
|
|
191
260
|
const errWords = [];
|
|
192
261
|
// The failure's NAME goes in first — see ERROR_NAMER_RE for the measurement that put
|
|
@@ -218,11 +287,16 @@ function collectErrorTerms(cmd, response) {
|
|
|
218
287
|
.filter((l) => ERROR_LINE_RE.test(l) || HARD_ERROR_RE.test(l))
|
|
219
288
|
.slice(0, 3);
|
|
220
289
|
for (const line of errLines) {
|
|
221
|
-
const tokens = line
|
|
222
|
-
.
|
|
290
|
+
const tokens = line
|
|
291
|
+
.replace(/[^a-zA-Z0-9_.-]/g, ' ')
|
|
292
|
+
.split(/\s+/)
|
|
293
|
+
.filter((w) => w.length > 3 && !/^\d+$/.test(w));
|
|
223
294
|
for (const t of tokens.slice(0, 5)) {
|
|
224
295
|
const lt = t.toLowerCase();
|
|
225
|
-
if (!ERROR_STOP_WORDS.has(lt) && !seen.has(lt)) {
|
|
296
|
+
if (!ERROR_STOP_WORDS.has(lt) && !seen.has(lt)) {
|
|
297
|
+
seen.add(lt);
|
|
298
|
+
errWords.push(lt);
|
|
299
|
+
}
|
|
226
300
|
}
|
|
227
301
|
}
|
|
228
302
|
return { cmdWords, errWords };
|
|
@@ -376,9 +450,11 @@ export function extractFilePaths(input) {
|
|
|
376
450
|
if (match) {
|
|
377
451
|
for (const m of match) {
|
|
378
452
|
const p = m.trim();
|
|
379
|
-
if (
|
|
453
|
+
if (
|
|
454
|
+
!isExcludedPath(p) &&
|
|
380
455
|
// Skip single-component paths like /exit, /clear — likely slash commands, not files
|
|
381
|
-
|
|
456
|
+
(p.indexOf('/', 1) !== -1 || /\.\w+$/.test(p))
|
|
457
|
+
) {
|
|
382
458
|
paths.push(p);
|
|
383
459
|
}
|
|
384
460
|
}
|