claude-mem-lite 6.19.2 → 6.19.4
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/hook-shared.mjs +2 -1
- package/lib/browse-core.mjs +14 -4
- package/lib/export-columns.mjs +3 -2
- package/lib/provenance.mjs +16 -7
- package/mem-cli.mjs +16 -3
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
- package/secret-scrub.mjs +27 -4
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.19.
|
|
12
|
+
"version": "6.19.4",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"homepage": "https://github.com/sdsrss/claude-mem-lite",
|
|
15
15
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword 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)."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.19.
|
|
3
|
+
"version": "6.19.4",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword 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/hook-shared.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './lib/schema-skew.mjs';
|
|
31
31
|
import { isDbUnusableError, DB_UNUSABLE_MARKER_PREFIX } from './lib/db-unusable.mjs';
|
|
32
32
|
import { shouldRecordOnce } from './lib/record-once.mjs';
|
|
33
|
+
import { hookSessionId } from './lib/provenance.mjs';
|
|
33
34
|
// Audit 2026-09-05 P1-2 (carried from 2026-09-02 P2-9): `callLLM`, the quiet/adoption
|
|
34
35
|
// predicates and the handoff constants moved into `lib/` because two lib modules
|
|
35
36
|
// imported them from here and dragged this file's whole import graph — haiku-client,
|
|
@@ -385,7 +386,7 @@ export function getSessionId() {
|
|
|
385
386
|
|
|
386
387
|
export function createSessionId() {
|
|
387
388
|
const project = inferProject();
|
|
388
|
-
const id =
|
|
389
|
+
const id = hookSessionId(project, randomUUID().slice(0, 8));
|
|
389
390
|
const file = sessionFile();
|
|
390
391
|
const tmp = file + `.tmp-${process.pid}`;
|
|
391
392
|
writeFileSync(tmp, JSON.stringify({ id, startedAt: Date.now(), project }), { mode: 0o600 });
|
package/lib/browse-core.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { TIER_CASE_SQL, tierSqlParams } from '../tier.mjs';
|
|
8
8
|
import { liveObsFilterSql } from './inject-search-core.mjs';
|
|
9
|
+
import { HOOK_SESSION_ID_PREFIX } from './provenance.mjs';
|
|
9
10
|
|
|
10
11
|
export const BROWSE_TIERS = ['working', 'active', 'archive'];
|
|
11
12
|
export const BROWSE_TIER_LABELS = {
|
|
@@ -14,14 +15,23 @@ export const BROWSE_TIER_LABELS = {
|
|
|
14
15
|
archive: '🔵 Archive',
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
/** Newest active
|
|
18
|
-
*
|
|
18
|
+
/** Newest active hook session id for the project ('' when none) — the tier classifier's
|
|
19
|
+
* "current session" input, needed identically by both faces. Only the hook's own sessions
|
|
20
|
+
* count: a writer's row (`manual-`, `compress-`, …) is inserted active on that writer's first
|
|
21
|
+
* write in a project and stays active until the 24h sweep, so taken as the current session it
|
|
22
|
+
* put all of that writer's rows, of any age, in the working tier (D#153).
|
|
23
|
+
* From a session's first Stop its own row is no longer active (Stop marks it completed on every
|
|
24
|
+
* turn and nothing marks it active again), so this is '' or another hook session of the project
|
|
25
|
+
* still marked active (a parallel one, or one that ended without a Stop); tier rule 2 holds for
|
|
26
|
+
* the session in progress only before its first reply, and rules 3-4 carry its rows of the last
|
|
27
|
+
* 2 hours. Accepted (D#161): measured 2026-09-28, 1 row in this machine's database was one
|
|
28
|
+
* rule 2 alone would have kept in the working tier; hook sessions had written 12 of 238 rows. */
|
|
19
29
|
export function getActiveMemorySessionId(db, project) {
|
|
20
30
|
const row = db
|
|
21
31
|
.prepare(
|
|
22
|
-
"SELECT memory_session_id FROM sdk_sessions WHERE project = ? AND status = 'active' ORDER BY started_at_epoch DESC LIMIT 1",
|
|
32
|
+
"SELECT memory_session_id FROM sdk_sessions WHERE project = ? AND status = 'active' AND memory_session_id LIKE ? ORDER BY started_at_epoch DESC LIMIT 1",
|
|
23
33
|
)
|
|
24
|
-
.get(project);
|
|
34
|
+
.get(project, `${HOOK_SESSION_ID_PREFIX}%`);
|
|
25
35
|
return row?.memory_session_id ?? '';
|
|
26
36
|
}
|
|
27
37
|
|
package/lib/export-columns.mjs
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
// twin-drift class permanently.
|
|
12
12
|
//
|
|
13
13
|
// Full round-trippable set: content + value-signals (access/cited/uncited/injection/decay)
|
|
14
|
-
// + branch + timing. `id` + `memory_session_id` are informational
|
|
15
|
-
//
|
|
14
|
+
// + branch + timing. `id` + `memory_session_id` are informational: restore remaps id, and of the
|
|
15
|
+
// session id it keeps only whether the row was an explicit save (`manual-<project>`) or not
|
|
16
|
+
// (`restore-<project>`; see cmdRestore). Session-idempotency keys (last_decided/
|
|
16
17
|
// last_cited/last_access_session_id, demoted_at, optimized_at) are intentionally NOT
|
|
17
18
|
// exported — they are meaningless after a row is re-bucketed under a restore session.
|
|
18
19
|
// last_access_session_id (v48) joins that set for the same reason: it answers "did THIS cc
|
package/lib/provenance.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Which writer produced an observation, read back from its memory_session_id. saveObservation
|
|
2
2
|
// writes every explicit save under MANUAL_SESSION_ID_PREFIX. The hook capture (`hook-`),
|
|
3
3
|
// transcript import (`import-`), compression summaries and cluster-merge keepers (`compress-`),
|
|
4
|
-
// rows narrow re-enrich rewrote (`enrich-`), promoted events (`promote-`)
|
|
5
|
-
// older stores (a bare
|
|
4
|
+
// rows narrow re-enrich rewrote (`enrich-`), promoted events (`promote-`), machine-written rows
|
|
5
|
+
// `restore` brought back from a backup (`restore-`) and rows imported from older stores (a bare
|
|
6
|
+
// session uuid) are machine-written.
|
|
6
7
|
//
|
|
7
8
|
// Search and get mark the machine-written side, because explicit saves are the large majority
|
|
8
9
|
// of a typical store and a mark on nearly every line would carry no information. An unknown id
|
|
@@ -14,14 +15,22 @@ export const MANUAL_SESSION_ID_PREFIX = 'manual-';
|
|
|
14
15
|
// uuid has them (schema.mjs, sdk_sessions_id_mix_check_ai). A writer row uses `<prefix><project>`
|
|
15
16
|
// for both, which has that shape for one project length and dash layout (27 characters under
|
|
16
17
|
// `compress-`, 29 under `manual-`), and the refusal failed every write in such a project (D#147).
|
|
17
|
-
//
|
|
18
|
-
//
|
|
18
|
+
// The hook's own id, `hook-<project>-<8 hex>`, has it for a 22-character project with dashes at
|
|
19
|
+
// 3, 8, 13 and 18 (D#156). That id gets one more character; every other id is unchanged. Counted
|
|
20
|
+
// in code points, as SQLite's length() and LIKE's `_` count them.
|
|
19
21
|
const UUID_SHAPED_RE = /^[\s\S]{8}-[\s\S]{4}-[\s\S]{4}-[\s\S]{4}-[\s\S]{12}$/u;
|
|
22
|
+
const outOfUuidShape = (id) => (UUID_SHAPED_RE.test(id) ? `${id}~` : id);
|
|
20
23
|
|
|
21
|
-
/** The session id a non-hook writer (`manual-`, `compress-`, `enrich-`, `promote-`) stores for a project. */
|
|
24
|
+
/** The session id a non-hook writer (`manual-`, `compress-`, `enrich-`, `promote-`, `restore-`) stores for a project. */
|
|
22
25
|
export function writerSessionId(prefix, project) {
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
return outOfUuidShape(`${prefix}${project}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const HOOK_SESSION_ID_PREFIX = 'hook-';
|
|
30
|
+
|
|
31
|
+
/** The hook's id for one session in a project: `hook-<project>-<tail>`. */
|
|
32
|
+
export function hookSessionId(project, tail) {
|
|
33
|
+
return outOfUuidShape(`${HOOK_SESSION_ID_PREFIX}${project}-${tail}`);
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
const AUTO_MARK = '🤖';
|
package/mem-cli.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { resolveProject } from './project-utils.mjs';
|
|
|
25
25
|
import { resolveCliProject as cliProject } from './lib/cli-project.mjs';
|
|
26
26
|
import { reRankWithContext } from './search-scoring.mjs';
|
|
27
27
|
import { searchObservationsHybrid } from './search-engine.mjs';
|
|
28
|
-
import { autoHeaderNote, autoLegend, autoTag } from './lib/provenance.mjs';
|
|
28
|
+
import { autoHeaderNote, autoLegend, autoTag, isAutoWritten, writerSessionId } from './lib/provenance.mjs';
|
|
29
29
|
import {
|
|
30
30
|
fetchObsDetail,
|
|
31
31
|
fetchPromptDetail,
|
|
@@ -2268,8 +2268,18 @@ function cmdExport(db, args) {
|
|
|
2268
2268
|
// (access/cited/uncited/injection/decay), branch, and concepts/facts/files_read that
|
|
2269
2269
|
// saveObservation derives or zeros — so a restored backup keeps its citation-decay
|
|
2270
2270
|
// history and original timing (created_at via the `now` param). Source ids are
|
|
2271
|
-
// discarded (local AUTOINCREMENT; export omits related_ids)
|
|
2272
|
-
//
|
|
2271
|
+
// discarded (local AUTOINCREMENT; export omits related_ids). Session ids are not restored,
|
|
2272
|
+
// only whether a row was an explicit save: see RESTORE_SESSION_ID_PREFIX.
|
|
2273
|
+
// A machine-written row (any exported id but `manual-`) is restored under `restore-<project>`, so
|
|
2274
|
+
// lib/provenance.mjs still reads it as auto-written; before D#157 every row went under
|
|
2275
|
+
// `manual-<project>` and rendered as an explicit save. The exported id is not reused: saveObservation
|
|
2276
|
+
// stores it as both session ids of an active session row, and a bare session uuid
|
|
2277
|
+
// (rows imported from older stores) is the shape sdk_sessions refuses, a `hook-` id could become
|
|
2278
|
+
// browse's current session (the only active hook row once Stop has marked the real one completed)
|
|
2279
|
+
// until a SessionStart sweeps it, and under --project the id names another project. A row exported
|
|
2280
|
+
// without the column restores as an explicit save, as before.
|
|
2281
|
+
const RESTORE_SESSION_ID_PREFIX = 'restore-';
|
|
2282
|
+
|
|
2273
2283
|
function cmdRestore(db, argv) {
|
|
2274
2284
|
const { positional, flags } = parseArgs(argv);
|
|
2275
2285
|
const file = positional[0];
|
|
@@ -2396,6 +2406,9 @@ function cmdRestore(db, argv) {
|
|
|
2396
2406
|
files,
|
|
2397
2407
|
lesson_learned: r.lesson_learned || null,
|
|
2398
2408
|
now: new Date(createdEpoch),
|
|
2409
|
+
sessionId: isAutoWritten(r.memory_session_id)
|
|
2410
|
+
? writerSessionId(RESTORE_SESSION_ID_PREFIX, project)
|
|
2411
|
+
: undefined,
|
|
2399
2412
|
});
|
|
2400
2413
|
if (res.kind !== 'saved') {
|
|
2401
2414
|
skipped++;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.19.
|
|
3
|
+
"version": "6.19.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-mem-lite",
|
|
9
|
-
"version": "6.19.
|
|
9
|
+
"version": "6.19.4",
|
|
10
10
|
"os": [
|
|
11
11
|
"darwin",
|
|
12
12
|
"linux",
|
|
@@ -2625,9 +2625,9 @@
|
|
|
2625
2625
|
"license": "ISC"
|
|
2626
2626
|
},
|
|
2627
2627
|
"node_modules/ip-address": {
|
|
2628
|
-
"version": "10.
|
|
2629
|
-
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.
|
|
2630
|
-
"integrity": "sha512-
|
|
2628
|
+
"version": "10.7.2",
|
|
2629
|
+
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz",
|
|
2630
|
+
"integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==",
|
|
2631
2631
|
"license": "MIT",
|
|
2632
2632
|
"engines": {
|
|
2633
2633
|
"node": ">= 12"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.19.
|
|
3
|
+
"version": "6.19.4",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword 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",
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
"@hono/node-server": ">=2.0.11",
|
|
231
231
|
"body-parser": ">=2.3.0",
|
|
232
232
|
"fast-uri": ">=3.1.4 <4",
|
|
233
|
-
"ip-address": ">=10.
|
|
233
|
+
"ip-address": ">=10.6.0",
|
|
234
234
|
"brace-expansion": ">=5.0.9"
|
|
235
235
|
},
|
|
236
236
|
"devDependencies": {
|
package/secret-scrub.mjs
CHANGED
|
@@ -197,12 +197,19 @@ export const SECRET_PATTERNS = [
|
|
|
197
197
|
],
|
|
198
198
|
// PEM private key blocks. `[A-Z0-9 ]*` covers every armor label — RSA/EC/DSA/
|
|
199
199
|
// OPENSSH plus ENCRYPTED and PGP (… PRIVATE KEY BLOCK) — that the fixed
|
|
200
|
-
// alternation missed
|
|
200
|
+
// alternation missed.
|
|
201
201
|
// The body stops at the next `-----BEGIN ` (D#130): with `[\s\S]*?` every header with no END
|
|
202
202
|
// scanned to the end of the text, on each of scrubSecrets' passes — quadratic, 8.3 s on 500k
|
|
203
203
|
// chars. A block whose END is missing is left to the next pattern. Nor does it cross a mark this
|
|
204
204
|
// scrubber wrote: on a later pass, a BEGIN that a scrubbed key used to block reached a far END
|
|
205
205
|
// and erased the prose between (v6.19.2 pre-tag defect review F5).
|
|
206
|
+
// Text that names a BEGIN and, later, an END with no key between them loses the text between
|
|
207
|
+
// (D#155, accepted). Keeping such a block was tried for 6.19.3 and withdrawn before the tag: each
|
|
208
|
+
// of the three keep rules measured stored keys this pattern erases, either a key it took for text
|
|
209
|
+
// or a key tail the kept markers hid from scrubKeyTails (docs/audits/20260928-v6.19.3-pretag-*.md).
|
|
210
|
+
// On this machine (2026-09-28) all 967 such blocks in 1,064 transcripts came from this
|
|
211
|
+
// repository's scrubber work; the other projects' transcripts held 6 BEGIN markers and no END.
|
|
212
|
+
// That shows the shape is rare here, not that text about PEM markers elsewhere keeps its words.
|
|
206
213
|
[
|
|
207
214
|
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----(?:(?!-----BEGIN [A-Z0-9 ]*PRIVATE KEY|\*\*\*PEM_KEY\*\*\*)[\s\S])*?-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----/g,
|
|
208
215
|
'***PEM_KEY***',
|
|
@@ -406,6 +413,16 @@ const ARMOR = 4;
|
|
|
406
413
|
const CUT = 5;
|
|
407
414
|
const OTHER = 6;
|
|
408
415
|
|
|
416
|
+
// Character-class checks by code, not a regex per character. (What brought the many-BEGINs linearity
|
|
417
|
+
// shape back under its budget after the v6.19.2 CI run was cutOffKeyEnd stopping before another
|
|
418
|
+
// BEGIN line, not these checks: under coverage the shape measured 5.6-6.2x benign with these checks
|
|
419
|
+
// alone, 1.9-2.3x with the stop alone.)
|
|
420
|
+
function isB64Code(c) {
|
|
421
|
+
return (
|
|
422
|
+
(c >= 65 && c <= 90) || (c >= 97 && c <= 122) || (c >= 48 && c <= 57) || c === 43 || c === 47 || c === 61
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
409
426
|
function backslashesBefore(text, i, floor) {
|
|
410
427
|
let j = i;
|
|
411
428
|
while (j > floor && text[j - 1] === '\\') j--;
|
|
@@ -508,7 +525,7 @@ function lineCore(text, s, e, shape) {
|
|
|
508
525
|
// (`</key>`), and escaped breaks before it. Read from the end: an unanchored
|
|
509
526
|
// `(?:\\+[rn])*["']…$` retried every start in a backslash run.
|
|
510
527
|
let q = b;
|
|
511
|
-
while (q > a &&
|
|
528
|
+
while (q > a && ' \t+,;)]}'.includes(line[q - 1])) q--;
|
|
512
529
|
const tag = q > a && line[q - 1] === '>' ? /<\/[A-Za-z][\w:.-]{0,40}>$/.exec(line.slice(a, q)) : null;
|
|
513
530
|
if (tag) b = q - tag[0].length;
|
|
514
531
|
else if (q > a && (line[q - 1] === '"' || line[q - 1] === "'" || line[q - 1] === '`')) {
|
|
@@ -559,7 +576,11 @@ function cutRun(core) {
|
|
|
559
576
|
function cutOffKeyEnd(text, b, be) {
|
|
560
577
|
// The rest of the BEGIN line: nothing, or base64 chunks, then a break that sets the depth.
|
|
561
578
|
let i = be;
|
|
562
|
-
|
|
579
|
+
for (
|
|
580
|
+
let c = text.charCodeAt(i);
|
|
581
|
+
i < text.length && (isB64Code(c) || c === 32 || c === 9);
|
|
582
|
+
c = text.charCodeAt(++i)
|
|
583
|
+
);
|
|
563
584
|
const rest = text.slice(be, i).trim();
|
|
564
585
|
let u;
|
|
565
586
|
let next;
|
|
@@ -608,13 +629,15 @@ function cutOffKeyEnd(text, b, be) {
|
|
|
608
629
|
// for one, it stripped this key's identical body line and one copy of a repeated cut key went
|
|
609
630
|
// per pass (v6.19.2 pre-tag round-5 review F2).
|
|
610
631
|
let run = 0;
|
|
611
|
-
while (run < 16 && b - run > ls &&
|
|
632
|
+
while (run < 16 && b - run > ls && isB64Code(text.charCodeAt(b - run - 1))) run++;
|
|
612
633
|
const shape = run >= 16 ? null : prefixShape(text.slice(ls, b));
|
|
613
634
|
|
|
614
635
|
let armors = 0;
|
|
615
636
|
let shorts = 0;
|
|
616
637
|
let pendingBlank = false;
|
|
617
638
|
while (next !== -1) {
|
|
639
|
+
// Another BEGIN line is never part of this key; stop before reading it whole.
|
|
640
|
+
if (!shape && text.startsWith('-----BEGIN ', next)) break;
|
|
618
641
|
const line = nextLine(text, next, u);
|
|
619
642
|
const { core, start, end: coreEnd } = lineCore(text, next, line.end, shape);
|
|
620
643
|
const kind = classify(core);
|