claude-mem-lite 6.19.2 → 6.19.3
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 +8 -4
- package/lib/export-columns.mjs +3 -2
- package/lib/provenance.mjs +16 -7
- package/mem-cli.mjs +16 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/secret-scrub.mjs +24 -4
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.19.
|
|
12
|
+
"version": "6.19.3",
|
|
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.3",
|
|
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,17 @@ 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). */
|
|
19
23
|
export function getActiveMemorySessionId(db, project) {
|
|
20
24
|
const row = db
|
|
21
25
|
.prepare(
|
|
22
|
-
"SELECT memory_session_id FROM sdk_sessions WHERE project = ? AND status = 'active' ORDER BY started_at_epoch DESC LIMIT 1",
|
|
26
|
+
"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
27
|
)
|
|
24
|
-
.get(project);
|
|
28
|
+
.get(project, `${HOOK_SESSION_ID_PREFIX}%`);
|
|
25
29
|
return row?.memory_session_id ?? '';
|
|
26
30
|
}
|
|
27
31
|
|
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.3",
|
|
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.3",
|
|
10
10
|
"os": [
|
|
11
11
|
"darwin",
|
|
12
12
|
"linux",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.19.
|
|
3
|
+
"version": "6.19.3",
|
|
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",
|
package/secret-scrub.mjs
CHANGED
|
@@ -197,12 +197,16 @@ 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, open). Keeping such a block was tried for 6.19.3 and withdrawn before the tag: each of
|
|
208
|
+
// 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).
|
|
206
210
|
[
|
|
207
211
|
/-----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
212
|
'***PEM_KEY***',
|
|
@@ -406,6 +410,16 @@ const ARMOR = 4;
|
|
|
406
410
|
const CUT = 5;
|
|
407
411
|
const OTHER = 6;
|
|
408
412
|
|
|
413
|
+
// Character-class checks by code, not a regex per character. (What brought the many-BEGINs linearity
|
|
414
|
+
// shape back under its budget after the v6.19.2 CI run was cutOffKeyEnd stopping before another
|
|
415
|
+
// BEGIN line, not these checks: under coverage the shape measured 5.6-6.2x benign with these checks
|
|
416
|
+
// alone, 1.9-2.3x with the stop alone.)
|
|
417
|
+
function isB64Code(c) {
|
|
418
|
+
return (
|
|
419
|
+
(c >= 65 && c <= 90) || (c >= 97 && c <= 122) || (c >= 48 && c <= 57) || c === 43 || c === 47 || c === 61
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
409
423
|
function backslashesBefore(text, i, floor) {
|
|
410
424
|
let j = i;
|
|
411
425
|
while (j > floor && text[j - 1] === '\\') j--;
|
|
@@ -508,7 +522,7 @@ function lineCore(text, s, e, shape) {
|
|
|
508
522
|
// (`</key>`), and escaped breaks before it. Read from the end: an unanchored
|
|
509
523
|
// `(?:\\+[rn])*["']…$` retried every start in a backslash run.
|
|
510
524
|
let q = b;
|
|
511
|
-
while (q > a &&
|
|
525
|
+
while (q > a && ' \t+,;)]}'.includes(line[q - 1])) q--;
|
|
512
526
|
const tag = q > a && line[q - 1] === '>' ? /<\/[A-Za-z][\w:.-]{0,40}>$/.exec(line.slice(a, q)) : null;
|
|
513
527
|
if (tag) b = q - tag[0].length;
|
|
514
528
|
else if (q > a && (line[q - 1] === '"' || line[q - 1] === "'" || line[q - 1] === '`')) {
|
|
@@ -559,7 +573,11 @@ function cutRun(core) {
|
|
|
559
573
|
function cutOffKeyEnd(text, b, be) {
|
|
560
574
|
// The rest of the BEGIN line: nothing, or base64 chunks, then a break that sets the depth.
|
|
561
575
|
let i = be;
|
|
562
|
-
|
|
576
|
+
for (
|
|
577
|
+
let c = text.charCodeAt(i);
|
|
578
|
+
i < text.length && (isB64Code(c) || c === 32 || c === 9);
|
|
579
|
+
c = text.charCodeAt(++i)
|
|
580
|
+
);
|
|
563
581
|
const rest = text.slice(be, i).trim();
|
|
564
582
|
let u;
|
|
565
583
|
let next;
|
|
@@ -608,13 +626,15 @@ function cutOffKeyEnd(text, b, be) {
|
|
|
608
626
|
// for one, it stripped this key's identical body line and one copy of a repeated cut key went
|
|
609
627
|
// per pass (v6.19.2 pre-tag round-5 review F2).
|
|
610
628
|
let run = 0;
|
|
611
|
-
while (run < 16 && b - run > ls &&
|
|
629
|
+
while (run < 16 && b - run > ls && isB64Code(text.charCodeAt(b - run - 1))) run++;
|
|
612
630
|
const shape = run >= 16 ? null : prefixShape(text.slice(ls, b));
|
|
613
631
|
|
|
614
632
|
let armors = 0;
|
|
615
633
|
let shorts = 0;
|
|
616
634
|
let pendingBlank = false;
|
|
617
635
|
while (next !== -1) {
|
|
636
|
+
// Another BEGIN line is never part of this key; stop before reading it whole.
|
|
637
|
+
if (!shape && text.startsWith('-----BEGIN ', next)) break;
|
|
618
638
|
const line = nextLine(text, next, u);
|
|
619
639
|
const { core, start, end: coreEnd } = lineCore(text, next, line.end, shape);
|
|
620
640
|
const kind = classify(core);
|