claude-mem-lite 3.92.0 → 3.93.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 +13 -0
- package/hash-utils.mjs +12 -0
- package/hook-llm.mjs +40 -9
- package/hook-optimize.mjs +4 -1
- package/hook-shared.mjs +6 -1
- package/hook.mjs +45 -31
- package/install.mjs +15 -11
- package/lib/export-columns.mjs +45 -0
- package/lib/get-core.mjs +17 -0
- package/lib/hook-stdin.mjs +134 -0
- package/lib/plugin-key.mjs +44 -0
- package/lib/registry-core.mjs +48 -0
- package/lib/resolve-data-dir.mjs +58 -0
- package/mem-cli.mjs +21 -37
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -1
- package/registry-recommend.mjs +2 -2
- package/schema.mjs +18 -1
- package/scripts/hook-launcher.mjs +10 -0
- package/scripts/post-tool-recall.js +7 -4
- package/scripts/pre-agent-inject.js +12 -4
- package/scripts/pre-skill-bridge.js +9 -5
- package/scripts/pre-tool-recall.js +35 -7
- package/scripts/user-prompt-search.js +24 -24
- package/server.mjs +28 -46
- package/source-files.mjs +2 -0
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { ensureDb, DB_DIR, REGISTRY_DB_PATH } from '../schema.mjs';
|
|
7
7
|
import { relaxFtsQueryToOr, truncate, typeIcon, inferProject, OBS_BM25, notLowSignalTitleClause, stripPrivate, neutralizeContextDelimiters, MAX_UPS_PROMPT_BYTES } from '../utils.mjs';
|
|
8
|
+
import { readHookStdin } from '../lib/hook-stdin.mjs';
|
|
9
|
+
import { resolveRuntimeDir } from '../lib/resolve-data-dir.mjs';
|
|
8
10
|
import { liveObsFilterSql, injectionRelevanceSql } from '../lib/inject-search-core.mjs';
|
|
9
11
|
import { fileMatchClause, fileMatchParams, basenameAnySep } from '../lib/file-edge-match.mjs';
|
|
10
12
|
import { cjkPrecisionOk } from '../nlp.mjs';
|
|
@@ -25,13 +27,20 @@ import { DAY_MS } from '../lib/time-constants.mjs';
|
|
|
25
27
|
|
|
26
28
|
// Telemetry sink (lib/hook-telemetry.mjs contract): env override for tests, else
|
|
27
29
|
// <data-dir>/runtime — the same dir the sibling hook scripts + `stats` read.
|
|
28
|
-
|
|
30
|
+
// EVERY runtime path in this file goes through this constant, including the two
|
|
31
|
+
// below. They used to be `join(DB_DIR, 'runtime', …)`, which reproduced the exact
|
|
32
|
+
// defect P1-14 closed one seam over: this file's RUNTIME_DIR honoured the override
|
|
33
|
+
// while its MARKER did not, so under `CLAUDE_MEM_RUNTIME_DIR` the `fyi` face wrote
|
|
34
|
+
// the shared cross-hook marker to <data>/runtime while `pre-tool-recall` (`pretool`)
|
|
35
|
+
// wrote it to the override and `hook.mjs` (`ups`) read the override. Caught by the
|
|
36
|
+
// v3.93.0 pre-tag test-effectiveness review, from a surviving mutation.
|
|
37
|
+
const RUNTIME_DIR = resolveRuntimeDir(DB_DIR);
|
|
29
38
|
// D#120: one marker file per CC session — payload-only session keying (M-6) let
|
|
30
39
|
// two concurrent windows full-replace each other's marker, killing dedup between
|
|
31
40
|
// them and resetting `count` on every alternation. Derived per invocation once
|
|
32
41
|
// the session id is parsed from stdin; no session id → legacy project-keyed file.
|
|
33
42
|
const injectedIdsFileFor = (sessionId) =>
|
|
34
|
-
join(
|
|
43
|
+
join(RUNTIME_DIR, injectedIdsFileName(inferProject(), sessionId));
|
|
35
44
|
// Per-prompt UPS cap. Cut from 5 → 3 after the 2026-05-09 per-hook recall
|
|
36
45
|
// scan (#8255): UPS contributed 74% of silent injected IDs (131/177) at 26%
|
|
37
46
|
// recall, vs PreToolUse:Read at 94% recall on a tighter file-keyed set.
|
|
@@ -482,28 +491,19 @@ function searchRecent(db, project, limit) {
|
|
|
482
491
|
|
|
483
492
|
// ─── stdin Reader ───────────────────────────────────────────────────────────
|
|
484
493
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// 256KB full-payload tier; both tiers live in utils.mjs (G19).
|
|
497
|
-
if (data.length > MAX_UPS_PROMPT_BYTES) {
|
|
498
|
-
process.stdin.destroy();
|
|
499
|
-
clearTimeout(timeout);
|
|
500
|
-
resolve(data.slice(0, MAX_UPS_PROMPT_BYTES));
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
process.stdin.on('end', () => { clearTimeout(timeout); resolve(data); });
|
|
504
|
-
process.stdin.on('error', err => { clearTimeout(timeout); reject(err); });
|
|
505
|
-
process.stdin.resume();
|
|
494
|
+
// P1-9: shared mechanism (lib/hook-stdin.mjs), this entry point's own caliber. 2 s and
|
|
495
|
+
// MAX_UPS_PROMPT_BYTES (64 KB, #9494's huge-prompt guard) are deliberately tighter than the
|
|
496
|
+
// 256 KB full-payload tier hook.mjs uses — both tiers live in utils.mjs (G19) — because the
|
|
497
|
+
// payload here is a user PROMPT, not a tool response. `rejectOnTimeout` matches the previous
|
|
498
|
+
// behaviour: the caller treats a timeout as "skip the injection".
|
|
499
|
+
// Returns a bare string, as this script's callers expect.
|
|
500
|
+
async function readStdin() {
|
|
501
|
+
const { text } = await readHookStdin({
|
|
502
|
+
timeoutMs: 2000,
|
|
503
|
+
maxBytes: MAX_UPS_PROMPT_BYTES,
|
|
504
|
+
rejectOnTimeout: true,
|
|
506
505
|
});
|
|
506
|
+
return text;
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
// ─── Format Output ──────────────────────────────────────────────────────────
|
|
@@ -550,7 +550,7 @@ function formatPromptResults(rows) {
|
|
|
550
550
|
// pointer line so Claude can decide to invoke via SkillTool. The cooldown
|
|
551
551
|
// and match mechanics below are unchanged.
|
|
552
552
|
|
|
553
|
-
const SKILL_COOLDOWN_FILE = join(
|
|
553
|
+
const SKILL_COOLDOWN_FILE = join(RUNTIME_DIR, `.skill-cooldown-${inferProject()}`);
|
|
554
554
|
const SKILL_COOLDOWN_MS = 300_000; // 5 minutes
|
|
555
555
|
|
|
556
556
|
function loadManagedSkillNames() {
|
package/server.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
// snapshotDb left with maintain-core: the pre-maintain snapshot is part of the op ORDER
|
|
22
22
|
// (it must see the pre-existing pending rows), so it moved into runMaintainOps (P1-5).
|
|
23
23
|
import { deleteObservations, previewDeleteRows } from './lib/delete-core.mjs';
|
|
24
|
-
import { fetchObsDetail, fetchPromptDetail, fetchEventDetail, OBS_FIELDS, SESSION_DETAIL_FIELDS, PROMPT_DETAIL_FIELDS, EVENT_DETAIL_FIELDS, supersededNotice } from './lib/get-core.mjs';
|
|
24
|
+
import { fetchObsDetail, fetchPromptDetail, fetchEventDetail, fetchSessionDetail, OBS_FIELDS, SESSION_DETAIL_FIELDS, PROMPT_DETAIL_FIELDS, EVENT_DETAIL_FIELDS, supersededNotice } from './lib/get-core.mjs';
|
|
25
25
|
import { collectBrowseTiers, getActiveMemorySessionId, BROWSE_TIERS, BROWSE_TIER_LABELS } from './lib/browse-core.mjs';
|
|
26
26
|
import { effectiveQuiet, RUNTIME_DIR } from './hook-shared.mjs';
|
|
27
27
|
import { computeStatsFeed } from './lib/stats-core.mjs';
|
|
@@ -45,13 +45,12 @@ import { optimizePreview, optimizeRun } from './hook-optimize.mjs';
|
|
|
45
45
|
import { join, sep } from 'path';
|
|
46
46
|
import { homedir } from 'os';
|
|
47
47
|
import { ensureRegistryDb, collectRegistryStats, listResourcesRanked, formatRegistryListLine } from './registry.mjs';
|
|
48
|
-
import { IMPORT_STRING_FIELDS, importResource, removeResource, reindexResources, enrichImportedResources, enrichNamedResource } from './lib/registry-core.mjs';
|
|
48
|
+
import { IMPORT_STRING_FIELDS, importResource, removeResource, reindexResources, enrichImportedResources, enrichNamedResource, resourceUseHint } from './lib/registry-core.mjs';
|
|
49
49
|
import { searchResources } from './registry-retriever.mjs';
|
|
50
50
|
import { probeOtherSources as probeIdSources, bucketIdTokens, splitDeferredTokens } from './lib/id-routing.mjs';
|
|
51
51
|
import { saveWithClosures, formatSupersedeSkipped, formatSupersededNote } from './lib/save-observation.mjs';
|
|
52
52
|
import { applyObsUpdate } from './lib/observation-write.mjs';
|
|
53
|
-
import { EXPORT_COLUMNS_SQL } from './lib/export-columns.mjs';
|
|
54
|
-
import { liveObsFilterSql } from './lib/inject-search-core.mjs';
|
|
53
|
+
import { EXPORT_COLUMNS_SQL, buildExportWhere } from './lib/export-columns.mjs';
|
|
55
54
|
import { recallByFile } from './lib/recall-core.mjs';
|
|
56
55
|
import { fetchRecent } from './lib/recent-core.mjs';
|
|
57
56
|
import { AUTO_MERGE_THRESHOLD } from './lib/dedup-constants.mjs';
|
|
@@ -646,8 +645,7 @@ server.registerTool(
|
|
|
646
645
|
}
|
|
647
646
|
|
|
648
647
|
if (bySrc.session.length > 0) {
|
|
649
|
-
const
|
|
650
|
-
const rows = db.prepare(`SELECT * FROM session_summaries WHERE id IN (${ph}) ORDER BY created_at_epoch ASC`).all(...bySrc.session);
|
|
648
|
+
const rows = fetchSessionDetail(db, bySrc.session);
|
|
651
649
|
// SESSION_DETAIL_FIELDS (get-core, P2-12): shared full set — adds remaining_items
|
|
652
650
|
// (searchable via FTS but previously unrendered on BOTH detail faces).
|
|
653
651
|
const sessFields = SESSION_DETAIL_FIELDS;
|
|
@@ -1307,27 +1305,12 @@ server.registerTool(
|
|
|
1307
1305
|
return { content: [{ type: 'text', text: `No matching resources for: "${args.query}"` }] };
|
|
1308
1306
|
}
|
|
1309
1307
|
const home = homedir();
|
|
1310
|
-
const toPortable = (p) => p && p.startsWith(home) ? '~' + p.slice(home.length) : (p || '');
|
|
1311
1308
|
const lines = results.map(r => {
|
|
1312
1309
|
const qualityBadge = r.quality_tier === 'installed' ? '[✓]' : r.quality_tier === 'verified' ? '[★]' : '[○]';
|
|
1313
1310
|
const categoryLabel = r.category ? ` [${r.category}]` : '';
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
if (isManaged) {
|
|
1318
|
-
// Managed: use Read(path) or mem_use — Skill() won't work for managed resources
|
|
1319
|
-
// Agents always have complete .md paths (e.g., agents/group/agents/name.md)
|
|
1320
|
-
// Only skills can be directory paths (9 cases) — resolve to /SKILL.md
|
|
1321
|
-
const resolvedPath = portablePath.endsWith('.md') ? portablePath : `${portablePath}/SKILL.md`;
|
|
1322
|
-
howToUse = `Read("${resolvedPath}") or mem_use(name="${r.name}"${r.type === 'agent' ? ', type="agent"' : ''})`;
|
|
1323
|
-
} else if (r.invocation_name) {
|
|
1324
|
-
// Native plugin/user skill: Skill() with full invocation name
|
|
1325
|
-
howToUse = r.type === 'skill'
|
|
1326
|
-
? `Skill("${r.invocation_name}")`
|
|
1327
|
-
: `Agent(subagent_type="${r.invocation_name}")`;
|
|
1328
|
-
} else {
|
|
1329
|
-
howToUse = `mem_use(name="${r.name}"${r.type === 'agent' ? ', type="agent"' : ''})`;
|
|
1330
|
-
}
|
|
1311
|
+
// P2-6: the invocation rule is shared; only the Markdown bold and the two-space
|
|
1312
|
+
// indent below are this face's.
|
|
1313
|
+
const { portablePath, howToUse } = resourceUseHint(r, { home, managedPrefix: join(DB_DIR, 'managed') + sep });
|
|
1331
1314
|
const pathLine = portablePath ? `\n Path: ${portablePath}` : '';
|
|
1332
1315
|
return `${qualityBadge} ${r.type === 'skill' ? 'S' : 'A'} **${r.name}**${categoryLabel} — ${truncate(r.capability_summary || '', 80)}${pathLine}\n Use: ${howToUse}`;
|
|
1333
1316
|
});
|
|
@@ -1594,33 +1577,32 @@ export async function handleExportForTest(db, args) {
|
|
|
1594
1577
|
}
|
|
1595
1578
|
|
|
1596
1579
|
async function runExport(db, args) {
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
//
|
|
1600
|
-
//
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
// are not — tombstone export is opt-in, supersession is never exported.
|
|
1604
|
-
wheres.push(args.include_compressed ? 'superseded_at IS NULL' : liveObsFilterSql(''));
|
|
1605
|
-
if (args.project) { wheres.push('project = ?'); params.push(_resolveProjectShared(db, args.project)); }
|
|
1606
|
-
if (args.type) { wheres.push('type = ?'); params.push(args.type); }
|
|
1607
|
-
// T3-P1-A: surface invalid dates instead of silently dropping the filter — mirrors
|
|
1608
|
-
// mem_search, which threw. A dropped filter can quietly expand the export blast radius.
|
|
1580
|
+
// PARSE + VALIDATE here; the SQL predicate comes from buildExportWhere (P2-5), shared
|
|
1581
|
+
// with mem-cli.mjs cmdExport. This face THROWS where the CLI prints a usage message —
|
|
1582
|
+
// T3-P1-A: surface invalid dates instead of silently dropping the filter (mirrors
|
|
1583
|
+
// mem_search). A dropped filter can quietly expand the export blast radius.
|
|
1584
|
+
let fromEpoch = null;
|
|
1585
|
+
let toEpoch = null;
|
|
1609
1586
|
if (args.date_from) {
|
|
1610
|
-
|
|
1611
|
-
if (isNaN(
|
|
1612
|
-
wheres.push('created_at_epoch >= ?');
|
|
1613
|
-
params.push(epoch);
|
|
1587
|
+
fromEpoch = new Date(args.date_from).getTime();
|
|
1588
|
+
if (isNaN(fromEpoch)) throw new Error(`Invalid date_from: "${args.date_from}" (use ISO 8601 or YYYY-MM-DD)`);
|
|
1614
1589
|
}
|
|
1615
1590
|
if (args.date_to) {
|
|
1616
1591
|
const d = args.date_to.length === 10 ? args.date_to + 'T23:59:59.999Z' : args.date_to;
|
|
1617
|
-
|
|
1618
|
-
if (isNaN(
|
|
1619
|
-
wheres.push('created_at_epoch <= ?');
|
|
1620
|
-
params.push(epoch);
|
|
1592
|
+
toEpoch = new Date(d).getTime();
|
|
1593
|
+
if (isNaN(toEpoch)) throw new Error(`Invalid date_to: "${args.date_to}" (use ISO 8601 or YYYY-MM-DD)`);
|
|
1621
1594
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1595
|
+
const { params, where } = buildExportWhere({
|
|
1596
|
+
includeCompressed: Boolean(args.include_compressed),
|
|
1597
|
+
// `|| args.project`: buildExportWhere gates the predicate on TRUTHINESS, so a falsy
|
|
1598
|
+
// resolution would DROP the project filter and export the whole store. The code this
|
|
1599
|
+
// replaced pushed `project = ?` unconditionally, so an unresolvable name yielded zero
|
|
1600
|
+
// rows — which is the safe direction, and the comment above is specifically about not
|
|
1601
|
+
// letting a dropped filter widen the export.
|
|
1602
|
+
project: args.project ? (_resolveProjectShared(db, args.project) || args.project) : null,
|
|
1603
|
+
type: args.type || null,
|
|
1604
|
+
fromEpoch, toEpoch,
|
|
1605
|
+
});
|
|
1624
1606
|
// No clamp (audit 2026-08-14 A2): `Math.min(args.limit ?? 200, 1000)` made an MCP-driven
|
|
1625
1607
|
// backup of a >1000-row store impossible, on the tool whose own description says "USE
|
|
1626
1608
|
// when: Backing up memory before a migration or reinstall" — while the CLI twin exported
|
package/source-files.mjs
CHANGED
|
@@ -110,6 +110,8 @@ export const SOURCE_FILES = [
|
|
|
110
110
|
// Single-envelope stdout for hook processes — imported by hook.mjs. Claude Code
|
|
111
111
|
// parses hook stdout as ONE JSON document; missing from the manifest → an updated
|
|
112
112
|
// install throws ERR_MODULE_NOT_FOUND on every hook fire.
|
|
113
|
+
'lib/hook-stdin.mjs',
|
|
114
|
+
'lib/plugin-key.mjs',
|
|
113
115
|
'lib/hook-stdout.mjs',
|
|
114
116
|
// audit P0/P1: inter-process install lock + atomic config writes — imported by
|
|
115
117
|
// install.mjs (settings.json + install lock) and hook-update.mjs (.claude.json
|