claude-mem-lite 3.14.0 → 3.16.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/README.md +29 -16
- package/adopt-cli.mjs +76 -3
- package/adopt-content.mjs +20 -15
- package/claudemd.mjs +12 -1
- package/commands/adopt.md +1 -1
- package/commands/unadopt.md +6 -4
- package/format-utils.mjs +5 -0
- package/hook-context.mjs +10 -1
- package/hook-memory.mjs +20 -4
- package/install.mjs +9 -2
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.16.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.16.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/README.md
CHANGED
|
@@ -299,10 +299,15 @@ surface — reach them through the CLI column in the second table.
|
|
|
299
299
|
|
|
300
300
|
### Invited Memory (v2.32+)
|
|
301
301
|
|
|
302
|
-
Opt-in mechanism that installs a
|
|
303
|
-
|
|
304
|
-
the plugin's MCP-tool triggers as **
|
|
305
|
-
authority than MCP server instructions (which are framed
|
|
302
|
+
Opt-in mechanism that installs a slug-scoped managed block into the project's
|
|
303
|
+
own `CLAUDE.md` (plus an on-demand detail doc under `.claude/`) so Claude Code
|
|
304
|
+
loads the plugin's MCP-tool triggers as **project instructions** — a higher
|
|
305
|
+
instruction-following authority than MCP server instructions (which are framed
|
|
306
|
+
as tool metadata). Claude Code loads `CLAUDE.md` and the memdir `MEMORY.md` at
|
|
307
|
+
equal weight, so steering lives in `CLAUDE.md` (the canonical home for project
|
|
308
|
+
instructions) rather than polluting `MEMORY.md`, which is reserved for the user's
|
|
309
|
+
own memories. The pre-v3.13 scheme wrote into `MEMORY.md`; it is migrated away
|
|
310
|
+
automatically on the next SessionStart.
|
|
306
311
|
|
|
307
312
|
```bash
|
|
308
313
|
claude-mem-lite adopt # install for current project
|
|
@@ -317,30 +322,38 @@ claude-mem-lite unadopt # remove sentinel + doc (runtime marker stays
|
|
|
317
322
|
Slash commands `/adopt` and `/unadopt` wrap the same CLI.
|
|
318
323
|
|
|
319
324
|
**What adoption changes:**
|
|
320
|
-
- A `<!-- claude-mem-lite:begin v1 -->…<!-- claude-mem-lite:end -->`
|
|
321
|
-
added to
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
+
- A `<!-- claude-mem-lite:begin v1 -->…<!-- claude-mem-lite:end -->` managed
|
|
326
|
+
block is added to `<cwd>/CLAUDE.md` under its own
|
|
327
|
+
`## claude-mem-lite — persistent memory` header, containing a compact trigger
|
|
328
|
+
table pointing at `mem_recall` / `mem_save` / `mem_defer` with their key
|
|
329
|
+
arguments. The block is slug-scoped: only this region is managed; the rest of
|
|
330
|
+
your `CLAUDE.md` is preserved verbatim, and it coexists with other plugins'
|
|
331
|
+
blocks (e.g. `code-graph-mcp`) in the same file.
|
|
332
|
+
- A `<cwd>/.claude/plugin_claude_mem_lite.md` detail file is written (not
|
|
333
|
+
auto-loaded; read on demand when the `CLAUDE.md` block points to it). The
|
|
334
|
+
block auto-refreshes when the shipped content drifts (version bump or template
|
|
335
|
+
change), unless `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`.
|
|
325
336
|
- Post-adopt the conservative hook layer auto-trims: MCP server instructions
|
|
326
337
|
drop the `WHEN TO USE` section, SessionStart injection drops the `File Lessons`
|
|
327
338
|
/ `Key Context` sections. `#ID` references and the `Recent` table still fire
|
|
328
339
|
so `mem_get` remains reachable.
|
|
329
340
|
|
|
330
341
|
**When does it take effect?**
|
|
331
|
-
- The
|
|
332
|
-
lesson suffix) apply on the **next SessionStart** (any new
|
|
333
|
-
in the adopted project).
|
|
342
|
+
- The `CLAUDE.md` managed block and the hook-layer trim (`File Lessons` /
|
|
343
|
+
`Key Context` / lesson suffix) apply on the **next SessionStart** (any new
|
|
344
|
+
Claude Code session in the adopted project).
|
|
334
345
|
- The MCP server instructions are built once at server boot and MCP has no
|
|
335
346
|
"push" protocol — the `WHEN TO USE` / `Decision rules` trim only applies
|
|
336
347
|
after Claude Code restarts and re-spawns the mem-lite MCP server. A single
|
|
337
348
|
`/exit` + fresh session is enough. Same caveat applies to `unadopt`.
|
|
338
349
|
|
|
339
350
|
**Safety:**
|
|
340
|
-
- Hash-guarded: editing the
|
|
341
|
-
unless you pass `--force`.
|
|
342
|
-
-
|
|
343
|
-
|
|
351
|
+
- Hash-guarded: editing the managed-block body yourself blocks automatic
|
|
352
|
+
rewrites unless you pass `--force`.
|
|
353
|
+
- Slug-scoped & dedup-guarded: only the `claude-mem-lite:begin…end` region is
|
|
354
|
+
ever rewritten, and duplicate / CRLF-orphaned copies are collapsed to one.
|
|
355
|
+
Unlike the legacy `MEMORY.md` scheme there is no line-budget gate — `CLAUDE.md`
|
|
356
|
+
has no truncation cap.
|
|
344
357
|
- **Auto-adopt fires on the first SessionStart per project for any install
|
|
345
358
|
path (v2.82.1+).** Per-project opt-out: `claude-mem-lite adopt --disable`
|
|
346
359
|
(writes a durable `<memdir>/.mem-no-auto-adopt` sentinel that survives marker
|
package/adopt-cli.mjs
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
// it is redefined as a legacy-cleanup sweep (strip old memory-dir sentinels across
|
|
14
14
|
// every memdir). New-scheme adoption happens per-project on SessionStart (cwd known).
|
|
15
15
|
|
|
16
|
-
import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
|
|
16
|
+
import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync, readFileSync } from 'fs';
|
|
17
17
|
import { homedir } from 'os';
|
|
18
|
-
import { join } from 'path';
|
|
18
|
+
import { join, isAbsolute } from 'path';
|
|
19
19
|
import {
|
|
20
20
|
memdirPath, removePluginSection, removePluginDoc,
|
|
21
21
|
isAdopted as memdirIsAdopted, hasPluginState,
|
|
@@ -54,6 +54,25 @@ function listAllMemdirs() {
|
|
|
54
54
|
return out;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function claudeConfigPath() { return join(homedir(), '.claude.json'); }
|
|
58
|
+
|
|
59
|
+
// Real adopted-project paths come from Claude Code's own ~/.claude.json `projects`
|
|
60
|
+
// map (keys are absolute cwds Claude Code has opened). The memdir slug under
|
|
61
|
+
// ~/.claude/projects/ is a LOSSY encoding that can't be decoded back to a path,
|
|
62
|
+
// so this is the only source that lets `unadopt --all` reach scattered CLAUDE.md
|
|
63
|
+
// managed blocks. Filtered to absolute, still-existing dirs; claudeMdIsAdopted()
|
|
64
|
+
// then gates which actually carry our block. Caveat: a project Claude Code never
|
|
65
|
+
// recorded is invisible here and needs a per-project `unadopt`.
|
|
66
|
+
function listKnownProjectDirs() {
|
|
67
|
+
const p = claudeConfigPath();
|
|
68
|
+
if (!existsSync(p)) return [];
|
|
69
|
+
try {
|
|
70
|
+
const cfg = JSON.parse(readFileSync(p, 'utf8'));
|
|
71
|
+
const projects = cfg && cfg.projects && typeof cfg.projects === 'object' ? Object.keys(cfg.projects) : [];
|
|
72
|
+
return projects.filter((d) => typeof d === 'string' && isAbsolute(d) && existsSync(d));
|
|
73
|
+
} catch { return []; }
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
function hasFlag(args, flag) { return Array.isArray(args) && args.includes(flag); }
|
|
58
77
|
|
|
59
78
|
// ─── Per-project auto-adopt opt-out sentinel ─────────────────────────────────
|
|
@@ -290,6 +309,12 @@ function statusAll() {
|
|
|
290
309
|
log(`[adopt --status] scanned ${dirs.length} memdir(s): ${legacy} with legacy sentinel (await migration), ${disabled} auto-adopt-disabled.`);
|
|
291
310
|
if (legacy > 0) log('[adopt --status] run `claude-mem-lite adopt --all` to sweep legacy memory-dir sentinels now.');
|
|
292
311
|
|
|
312
|
+
const known = listKnownProjectDirs();
|
|
313
|
+
let adoptedCount = 0;
|
|
314
|
+
for (const dir of known) if (claudeMdIsAdopted(dir, PLUGIN_SLUG)) adoptedCount++;
|
|
315
|
+
log(`[adopt --status] known projects (~/.claude.json): ${known.length} scanned, ${adoptedCount} with a CLAUDE.md managed block.`);
|
|
316
|
+
if (adoptedCount > 0) log('[adopt --status] run `claude-mem-lite unadopt --all` to remove every CLAUDE.md block.');
|
|
317
|
+
|
|
293
318
|
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ? 'set' : 'unset';
|
|
294
319
|
const noAutoAdopt = process.env.MEM_NO_AUTO_ADOPT === '1' ? '1 (opt-out)' : 'unset';
|
|
295
320
|
log('');
|
|
@@ -305,6 +330,54 @@ function statusAll() {
|
|
|
305
330
|
* memory-dir cleanup across every memdir (CLAUDE.md blocks for other projects
|
|
306
331
|
* can't be located from the lossy slug). Idempotent: exit code stays 0.
|
|
307
332
|
*/
|
|
333
|
+
/**
|
|
334
|
+
* unadoptAll — `claude-mem-lite unadopt --all`. Removes the CLAUDE.md managed
|
|
335
|
+
* block + detail doc from EVERY adopted project Claude Code knows about (real
|
|
336
|
+
* paths from ~/.claude.json `projects`), then sweeps the legacy memory-dir
|
|
337
|
+
* residue across all memdirs. removeManaged is slug-scoped, so user content and
|
|
338
|
+
* other plugins' blocks are never touched. Honors --dry-run / --force.
|
|
339
|
+
*
|
|
340
|
+
* Unlike `adopt --all` (still a legacy-only sweep — adopting arbitrary projects
|
|
341
|
+
* is unsafe), unadopt is purely subtractive, so reaching every known project is
|
|
342
|
+
* both safe and what the uninstall hint promises.
|
|
343
|
+
*/
|
|
344
|
+
function unadoptAll(args) {
|
|
345
|
+
const force = hasFlag(args, '--force');
|
|
346
|
+
const dryRun = hasFlag(args, '--dry-run');
|
|
347
|
+
|
|
348
|
+
// 1. New scheme: scrub CLAUDE.md managed blocks across known project paths.
|
|
349
|
+
const projectDirs = listKnownProjectDirs();
|
|
350
|
+
let blocks = 0;
|
|
351
|
+
for (const dir of projectDirs) {
|
|
352
|
+
if (!claudeMdIsAdopted(dir, PLUGIN_SLUG)) continue;
|
|
353
|
+
if (dryRun) {
|
|
354
|
+
log(`[unadopt --all --dry-run] ${dir} → would-remove CLAUDE.md block + detail doc`);
|
|
355
|
+
blocks++;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const r = removeManaged(dir, PLUGIN_SLUG);
|
|
359
|
+
if (r.action === 'removed') { log(`[unadopt --all] ${dir} → removed`); blocks++; }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 2. Legacy memory-dir cleanup across every memdir (foreign-content guarded).
|
|
363
|
+
const dirs = listAllMemdirs();
|
|
364
|
+
let legacy = 0;
|
|
365
|
+
for (const { memdir } of dirs) {
|
|
366
|
+
if (dryRun) {
|
|
367
|
+
if (memdirIsAdopted(memdir, PLUGIN_SLUG) && (hasPluginState(memdir, PLUGIN_SLUG) || force)) legacy++;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const r = removePluginSection(memdir, PLUGIN_SLUG, { force });
|
|
371
|
+
if (r.action === 'removed') { removePluginDoc(memdir, PLUGIN_SLUG); legacy++; }
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
log('');
|
|
375
|
+
log(`[unadopt --all] ${dryRun ? 'would remove' : 'removed'} ${blocks} CLAUDE.md block(s) across ${projectDirs.length} known project(s); ${legacy} legacy memory-dir sentinel(s) ${dryRun ? 'pending' : 'cleaned'}.`);
|
|
376
|
+
if (projectDirs.length === 0) {
|
|
377
|
+
log('[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.');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
308
381
|
export function cmdUnadopt(args = []) {
|
|
309
382
|
if (hasFlag(args, '--status')) return statusAll();
|
|
310
383
|
|
|
@@ -312,7 +385,7 @@ export function cmdUnadopt(args = []) {
|
|
|
312
385
|
const dryRun = hasFlag(args, '--dry-run');
|
|
313
386
|
const force = hasFlag(args, '--force');
|
|
314
387
|
|
|
315
|
-
if (all) return
|
|
388
|
+
if (all) return unadoptAll(args);
|
|
316
389
|
|
|
317
390
|
const cwd = detectCwd();
|
|
318
391
|
if (dryRun) {
|
package/adopt-content.mjs
CHANGED
|
@@ -3,16 +3,21 @@
|
|
|
3
3
|
// <cwd>/.claude/plugin_claude_mem_lite.md detail doc. Kept separate from the
|
|
4
4
|
// claudemd.mjs primitives so the strings are testable without side effects.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// needsRefresh()
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// v2
|
|
6
|
+
// CURRENT_SENTINEL_VERSION tags the managed block as `<!-- claude-mem-lite:begin
|
|
7
|
+
// vN -->`. needsRefresh() only checks for *inequality* against the installed tag,
|
|
8
|
+
// so any change triggers an in-place refresh (version change = intended content
|
|
9
|
+
// change, overwritten rather than treated as a user edit) — the value need not be
|
|
10
|
+
// monotonic. We deliberately use `v1` (not `v2`) so the version digit differs from
|
|
11
|
+
// the sibling code-graph-mcp plugin's `<!-- code-graph-mcp:begin v2 -->` block in
|
|
12
|
+
// the same CLAUDE.md; the slug already scopes the two independently (claudemd.mjs),
|
|
13
|
+
// so this is a cosmetic distinguisher, not a functional one. The pre-v3.13 legacy
|
|
14
|
+
// memory-dir MEMORY.md sentinel also carried `v1`, but it lives in a different file
|
|
15
|
+
// and is migrated away (claudemd.migrateLegacyMemoryDir), so there is no collision.
|
|
11
16
|
|
|
12
17
|
import { CLI_INVOKE } from './cli-path.mjs';
|
|
13
18
|
|
|
14
19
|
export const PLUGIN_SLUG = 'claude-mem-lite';
|
|
15
|
-
export const CURRENT_SENTINEL_VERSION = '
|
|
20
|
+
export const CURRENT_SENTINEL_VERSION = 'v1';
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* The concise managed block injected into <cwd>/CLAUDE.md (between the
|
|
@@ -26,19 +31,19 @@ export function buildClaudeMdBlock() {
|
|
|
26
31
|
// resolves to an absolute path that differs per install — it would make this
|
|
27
32
|
// committed/refreshed block churn across machines). The robust CLI table lives
|
|
28
33
|
// in the detail doc (.claude/, gitignored).
|
|
29
|
-
return `## claude-mem-lite —
|
|
34
|
+
return `## claude-mem-lite — persistent memory
|
|
30
35
|
|
|
31
|
-
PreToolUse hooks
|
|
36
|
+
PreToolUse hooks already run \`mem_recall\` for past lessons before Read/Edit/Write. The calls worth making proactively:
|
|
32
37
|
|
|
33
|
-
|
|
|
38
|
+
| When | Call |
|
|
34
39
|
|------|------|
|
|
35
|
-
| Edit/Write
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
+
| Before Edit/Write | hook already recalled; if a \`#NN\` lesson was injected, cite \`#NN\` next time you produce user-visible text (citing = adopting the feedback; uncited lessons decay) |
|
|
41
|
+
| After fixing a non-trivial bug | \`mem_save(type="bugfix", lesson_learned="<root cause + fix>", importance=2)\` |
|
|
42
|
+
| After a non-obvious architecture decision | \`mem_save(type="decision", lesson_learned="<constraint + tradeoff>")\` |
|
|
43
|
+
| Deferring to a future session | \`mem_defer({title, priority:1|2|3, detail})\`; when fixed, add \`closes_deferred=[N]\` to \`mem_save\` |
|
|
44
|
+
| Looking up past work / history | \`mem_search "keywords"\` · \`mem_recent\` · \`mem_timeline\` |
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
Full tool + CLI tables, citation/decay rules, and save discipline → \`.claude/plugin_claude_mem_lite.md\``;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
/**
|
package/claudemd.mjs
CHANGED
|
@@ -200,7 +200,18 @@ export function removeManaged(cwd, slug) {
|
|
|
200
200
|
if (blockAtStart) raw = raw.replace(/^\s+/, '');
|
|
201
201
|
action = 'removed';
|
|
202
202
|
}
|
|
203
|
-
if (action === 'removed')
|
|
203
|
+
if (action === 'removed') {
|
|
204
|
+
// When the managed block was the ENTIRE file (adopt created CLAUDE.md
|
|
205
|
+
// because none existed), removing it leaves nothing but whitespace.
|
|
206
|
+
// Delete the now-empty file rather than writing a 0-byte CLAUDE.md, so
|
|
207
|
+
// unadopt fully restores the pre-adopt state — mirrors the emptied-.claude/
|
|
208
|
+
// cleanup below ("unadopt leaves no trace").
|
|
209
|
+
if (raw.trim() === '') {
|
|
210
|
+
try { unlinkSync(p); } catch { atomicWrite(p, raw); }
|
|
211
|
+
} else {
|
|
212
|
+
atomicWrite(p, raw);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
204
215
|
}
|
|
205
216
|
const dp = detailDocPath(cwd, slug);
|
|
206
217
|
if (existsSync(dp)) try { unlinkSync(dp); } catch { /* best-effort */ }
|
package/commands/adopt.md
CHANGED
|
@@ -16,7 +16,7 @@ force it immediately (e.g. after editing the block out by hand).
|
|
|
16
16
|
|
|
17
17
|
## What it writes
|
|
18
18
|
|
|
19
|
-
1. **`<cwd>/CLAUDE.md`** — a concise `<!-- claude-mem-lite:begin
|
|
19
|
+
1. **`<cwd>/CLAUDE.md`** — a concise `<!-- claude-mem-lite:begin v1 -->…<!-- :end -->`
|
|
20
20
|
managed block (trigger table → `mem_recall` / `mem_save` / `mem_defer`).
|
|
21
21
|
Slug-scoped: only this block is managed; the rest of your `CLAUDE.md` is
|
|
22
22
|
preserved verbatim. Auto-refreshes when the shipped content drifts.
|
package/commands/unadopt.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: unadopt
|
|
3
|
-
description: "Use when: user wants to remove the claude-mem-lite steering block from the current project (or
|
|
3
|
+
description: "Use when: user wants to remove the claude-mem-lite steering block from the current project (or from every project Claude Code knows about with --all). Removes the <cwd>/CLAUDE.md managed block + <cwd>/.claude/plugin_claude_mem_lite.md and cleans any legacy memory-dir residue. User content outside the sentinel is preserved. Benign no-op when not adopted."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /unadopt
|
|
@@ -24,9 +24,11 @@ project (slug-scoped — other plugins' blocks survive).
|
|
|
24
24
|
|
|
25
25
|
- `--force` — also remove a legacy memory-dir block lacking a state sidecar
|
|
26
26
|
- `--dry-run` — preview what would be removed; no writes
|
|
27
|
-
- `--all` —
|
|
28
|
-
project (
|
|
29
|
-
|
|
27
|
+
- `--all` — remove the CLAUDE.md managed block from every project in Claude
|
|
28
|
+
Code's known-project list (`~/.claude.json` `projects`), plus sweep any legacy
|
|
29
|
+
memory-dir sentinels. Slug-scoped, so user content and other plugins' blocks
|
|
30
|
+
survive. A project Claude Code never opened isn't listed — `cd` into it and run
|
|
31
|
+
`/unadopt` there. Pair with `--dry-run` to preview.
|
|
30
32
|
- `--status` — read-only adoption probe (mirrors `/adopt --status`)
|
|
31
33
|
|
|
32
34
|
## Note: this does not stop auto-adopt
|
package/format-utils.mjs
CHANGED
|
@@ -61,6 +61,11 @@ export function fmtDate(iso) {
|
|
|
61
61
|
export function fmtTime(iso) {
|
|
62
62
|
if (!iso) return '';
|
|
63
63
|
const d = new Date(iso);
|
|
64
|
+
// Guard against an unparseable timestamp (e.g. corrupt/imported created_at):
|
|
65
|
+
// a bare new Date('garbage') yields Invalid Date → getUTCHours() is NaN →
|
|
66
|
+
// "NaN:NaN" leaking into the SessionStart Recent table. Degrade to '' like the
|
|
67
|
+
// falsy-input case above.
|
|
68
|
+
if (Number.isNaN(d.getTime())) return '';
|
|
64
69
|
return `${String(d.getUTCHours()).padStart(2, '0')}:${String(d.getUTCMinutes()).padStart(2, '0')}`;
|
|
65
70
|
}
|
|
66
71
|
|
package/hook-context.mjs
CHANGED
|
@@ -27,6 +27,15 @@ function inferProjectDir() {
|
|
|
27
27
|
* @param {string} project Project name to check velocity for
|
|
28
28
|
* @returns {{tier1: number, tier2: number, tier3: number, sessWindow: number}} Time window durations in ms
|
|
29
29
|
*/
|
|
30
|
+
// Sanitize a string for a GitHub-flavored-markdown table cell: a literal `|`
|
|
31
|
+
// in a title (e.g. "grep | sort | uniq") would otherwise open phantom columns
|
|
32
|
+
// and corrupt the <claude-mem-context> Recent table the model+user see every
|
|
33
|
+
// SessionStart. Escape pipes and collapse any CR/LF/tab to a space so one obs
|
|
34
|
+
// stays one row/cell.
|
|
35
|
+
function mdCell(s) {
|
|
36
|
+
return String(s ?? '').replace(/[\r\n\t]+/g, ' ').replace(/\|/g, '\\|');
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
export function computeAdaptiveWindows(db, project) {
|
|
31
40
|
const sevenDaysAgo = Date.now() - 7 * 86400000;
|
|
32
41
|
const row = db.prepare(`
|
|
@@ -446,7 +455,7 @@ export function buildSessionContextLines(db, project, now = new Date(), currentC
|
|
|
446
455
|
obsLines.push('|----|------|---|-------|');
|
|
447
456
|
for (const o of obsToShow) {
|
|
448
457
|
const proj = o.project && o.project !== project ? ` (${o.project})` : '';
|
|
449
|
-
obsLines.push(`| #${o.id} | ${fmtTime(o.created_at)} | ${typeIcon(o.type)} | ${truncate(o.title || '(untitled)', 60)
|
|
458
|
+
obsLines.push(`| #${o.id} | ${fmtTime(o.created_at)} | ${typeIcon(o.type)} | ${mdCell(truncate(o.title || '(untitled)', 60) + proj)} |`);
|
|
450
459
|
}
|
|
451
460
|
}
|
|
452
461
|
|
package/hook-memory.mjs
CHANGED
|
@@ -16,8 +16,23 @@ const MEMORY_TYPE_BOOST = { decision: 1.5, discovery: 1.3, bugfix: 1.1, feature:
|
|
|
16
16
|
// Adaptive BM25 thresholds — scale with corpus size to filter noise.
|
|
17
17
|
// Larger corpora produce more weak matches from common words.
|
|
18
18
|
const BM25_THRESHOLD = { TINY: 0, SMALL: 1.5, MEDIUM: 2.5, LARGE: 3.5 };
|
|
19
|
-
// OR fallback max token count —
|
|
20
|
-
|
|
19
|
+
// OR fallback max token count — when an AND query returns nothing, retry as OR
|
|
20
|
+
// only if the query has at most this many significant terms. Natural-language
|
|
21
|
+
// user prompts ("how did we fix the parser null deref bug?") almost always
|
|
22
|
+
// exceed this after synonym expansion, so the old hard 2 silently denied them
|
|
23
|
+
// the OR rescue and the UPS injection path returned 0 results for real prompts
|
|
24
|
+
// (semantic-keyed recall measured at 26%, #8255; 0/11 on a realistic NL corpus).
|
|
25
|
+
// Precision for OR results is already enforced downstream by three independent
|
|
26
|
+
// gates — the 0.4x OR penalty, the adaptive BM25 threshold, and the 40%
|
|
27
|
+
// term-coverage filter (v27) — so the token gate is a redundant, overly-strict
|
|
28
|
+
// fourth guard predating term-coverage. Default raised to 8 (covers typical NL
|
|
29
|
+
// prompts); env override `MEM_OR_FALLBACK_MAX_TOKENS` ∈ [0, 50], invalid → 8.
|
|
30
|
+
function getOrFallbackMaxTokens() {
|
|
31
|
+
const raw = process.env.MEM_OR_FALLBACK_MAX_TOKENS;
|
|
32
|
+
if (raw === undefined || raw === '') return 8;
|
|
33
|
+
const n = parseInt(raw, 10);
|
|
34
|
+
return Number.isInteger(n) && n >= 0 && n <= 50 ? n : 8;
|
|
35
|
+
}
|
|
21
36
|
|
|
22
37
|
// v27: term-coverage post-filter. Drops high-BM25 candidates whose visible
|
|
23
38
|
// fields (title + lesson_learned) cover <N% of the query's significant terms.
|
|
@@ -215,9 +230,10 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
215
230
|
// unconditionally; mirror that here. Noise is contained downstream (0.4x OR
|
|
216
231
|
// penalty + BM25 threshold + term-coverage filter). queryIsCjkDominant (not
|
|
217
232
|
// mere CJK presence) is the gate — see its definition at the function top.
|
|
233
|
+
const orFallbackMaxTokens = getOrFallbackMaxTokens();
|
|
218
234
|
if (rows.length === 0) {
|
|
219
235
|
const orQuery = relaxFtsQueryToOr(ftsQuery);
|
|
220
|
-
if (orQuery && (queryIsCjkDominant || queryTokenCount <=
|
|
236
|
+
if (orQuery && (queryIsCjkDominant || queryTokenCount <= orFallbackMaxTokens)) {
|
|
221
237
|
try { rows = selectStmt.all(orQuery, project, cutoff); usedOrFallback = true; } catch {}
|
|
222
238
|
}
|
|
223
239
|
}
|
|
@@ -248,7 +264,7 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
248
264
|
crossRows = crossStmt.all(ftsQuery, project, cutoff);
|
|
249
265
|
if (crossRows.length === 0) {
|
|
250
266
|
const orQuery = relaxFtsQueryToOr(ftsQuery);
|
|
251
|
-
if (orQuery && (queryIsCjkDominant || queryTokenCount <=
|
|
267
|
+
if (orQuery && (queryIsCjkDominant || queryTokenCount <= orFallbackMaxTokens)) {
|
|
252
268
|
try { crossRows = crossStmt.all(orQuery, project, cutoff); crossUsedOr = true; } catch {}
|
|
253
269
|
}
|
|
254
270
|
}
|
package/install.mjs
CHANGED
|
@@ -1116,8 +1116,15 @@ async function uninstall() {
|
|
|
1116
1116
|
cleanupMemHooksFromSettings(settings);
|
|
1117
1117
|
|
|
1118
1118
|
// 2b. Uninstall does NOT auto-unadopt — an adopted project may be in active use
|
|
1119
|
-
//
|
|
1120
|
-
|
|
1119
|
+
// in other Claude Code sessions, and adoption lives in EACH project's own
|
|
1120
|
+
// CLAUDE.md. `unadopt --all` now strips every block across the projects Claude
|
|
1121
|
+
// Code knows about (~/.claude.json), so point at it — but note the timing: a
|
|
1122
|
+
// --purge run removes the CLI symlink, so this is best done BEFORE uninstall.
|
|
1123
|
+
log('Invited-memory: project adoption left in place (each adopted project keeps its');
|
|
1124
|
+
log(' CLAUDE.md managed block + .claude/plugin_claude_mem_lite.md). To remove it from');
|
|
1125
|
+
log(' every known project, run `claude-mem-lite unadopt --all` — best done BEFORE');
|
|
1126
|
+
log(' uninstall, while the CLI is still on PATH. A project Claude Code never opened');
|
|
1127
|
+
log(' is not in the known list — run `claude-mem-lite unadopt` from inside it.');
|
|
1121
1128
|
|
|
1122
1129
|
// 3. Clean plugin registry entries conservatively (avoid deleting other plugins
|
|
1123
1130
|
// from the same marketplace publisher)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.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
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|